UNPKG

json-joy

Version:

Collection of libraries for building collaborative editing apps.

397 lines (396 loc) 11.4 kB
import { NodeBuilder } from './DelayedValueBuilder'; /* tslint:disable no-namespace class-name */ /** * This namespace contains all the node builders. Each node builder is a * schema for a specific node type. Each node builder has a `build` method * that takes a {@link NodeBuilder} and returns the ID of the node. */ export var nodes; (function (nodes) { /** * The `con` class represents a "con" JSON CRDT node. As the generic type * parameter, it takes the type of the raw value. * * Example: * * ```ts * s.con(0); * s.con(''); * s.con<number>(123); * s.con<0 | 1>(0); * ``` */ class con extends NodeBuilder { raw; type = 'con'; constructor(raw) { super((builder) => builder.const(raw)); this.raw = raw; } } nodes.con = con; /** * The `str` class represents a "str" JSON CRDT node. As the generic type * parameter, it takes the type of the raw value. * * Example: * * ```ts * s.str(''); * s.str('hello'); * s.str<string>('world'); * s.str<'' | 'hello' | 'world'>('hello'); * ``` */ class str extends NodeBuilder { raw; type = 'str'; constructor(raw) { super((builder) => builder.json(raw)); this.raw = raw; } } nodes.str = str; /** * The `bin` class represents a "bin" JSON CRDT node. */ class bin extends NodeBuilder { raw; type = 'bin'; constructor(raw) { super((builder) => builder.json(raw)); this.raw = raw; } } nodes.bin = bin; /** * The `val` class represents a "val" JSON CRDT node. As the generic type * parameter, it takes the type of the inner node builder. * * Example: * * ```ts * s.val(s.con(0)); * s.val(s.str('')); * s.val(s.str('hello')); * ``` */ class val extends NodeBuilder { value; type = 'val'; constructor(value) { super((builder) => { const valId = builder.val(); const valueId = value.build(builder); builder.setVal(valId, valueId); return valId; }); this.value = value; } } nodes.val = val; /** * The `vec` class represents a "vec" JSON CRDT node. As the generic type * parameter, it takes a tuple of node builders. * * Example: * * ```ts * s.vec(s.con(0), s.con(1)); * s.vec(s.str(''), s.str('hello')); * ``` */ class vec extends NodeBuilder { value; type = 'vec'; constructor(value) { super((builder) => { const vecId = builder.vec(); const length = value.length; if (length) { const elementPairs = []; for (let i = 0; i < length; i++) { const element = value[i]; const elementId = element.build(builder); elementPairs.push([i, elementId]); } builder.insVec(vecId, elementPairs); } return vecId; }); this.value = value; } } nodes.vec = vec; /** * The `obj` class represents a "obj" JSON CRDT node. As the generic type * parameter, it takes a record of node builders. The optional generic type * parameter is a record of optional keys. * * Example: * * ```ts * s.obj({ * name: s.str(''), * age: s.con(0), * }); * ``` * * Specify optional keys as the second argument: * * ```ts * s.obj( * { * href: s.str('https://example.com'), * }, * { * title: s.str(''), * }, * ) * ``` * * Or, specify only the type, using the `optional` method: * * ```ts * s.obj({ * href: s.str('https://example.com'), * }) * .optional<nodes.obj({ * title: nodes.str, * })>() * ``` */ class obj extends NodeBuilder { obj; opt; type = 'obj'; constructor(obj, opt) { super((builder) => { const objId = builder.obj(); const keyValuePairs = []; const merged = { ...obj, ...opt }; const keys = Object.keys(merged); const length = keys.length; if (length) { for (let i = 0; i < length; i++) { const key = keys[i]; const valueId = merged[key].build(builder); keyValuePairs.push([key, valueId]); } builder.insObj(objId, keyValuePairs); } return objId; }); this.obj = obj; this.opt = opt; } optional() { return this; } } nodes.obj = obj; /** * The `arr` class represents a "arr" JSON CRDT node. As the generic type * parameter, it an array of node builders. * * Example: * * ```ts * s.arr([s.con(0), s.con(1)]); * s.arr([s.str(''), s.str('hello')]); * ``` */ class arr extends NodeBuilder { arr; type = 'arr'; constructor(arr) { super((builder) => { const arrId = builder.arr(); const length = arr.length; if (length) { const valueIds = []; for (let i = 0; i < length; i++) valueIds.push(arr[i].build(builder)); builder.insArr(arrId, arrId, valueIds); } return arrId; }); this.arr = arr; } } nodes.arr = arr; /** * Convenience class for recursively creating a node tree from any POJO. It * uses the {@link Builder.json} method to create a JSON node. It can be used * similar to TypeScript's *any* type, where the value can be anything. * * Example: * * ```typescript * s.json({name: 'Alice', age: 30}); * ``` */ class json extends NodeBuilder { value; type = 'json'; constructor(value) { super((builder) => builder.json(value)); this.value = value; } } nodes.json = json; /** * Convenience class for recursively creating a node tree from any POJO. It * uses the {@link Builder.constOrJson} method to create a JSON node. It can * be used similar to TypeScript's *any* type, where the value can be anything. * * Example: * * ```typescript * s.jsonCon({name: 'Alice', age: 30}); * ``` */ class jsonCon extends NodeBuilder { value; type = 'jsonCon'; constructor(value) { super((builder) => builder.constOrJson(value)); this.value = value; } } nodes.jsonCon = jsonCon; /** * Creates an extension node schema. The extension node is a tuple with a * sentinel header and a data node. The sentinel header is a 3-byte * {@link Uint8Array}, which makes this "vec" node to be treated as an * extension "ext" node. * * The 3-byte header consists of the extension ID, the SID of the tuple ID, * and the time of the tuple ID: * * - 1 byte for the extension id * - 1 byte for the sid of the tuple id, modulo 256 * - 1 byte for the time of the tuple id, modulo 256 */ class ext extends NodeBuilder { id; data; type = 'ext'; /** * @param id A unique extension ID. * @param data Schema of the data node of the extension. */ constructor(id, data) { super((builder) => { const buf = new Uint8Array([id, 0, 0]); const tupleId = builder.vec(); buf[1] = tupleId.sid % 256; buf[2] = tupleId.time % 256; builder.insVec(tupleId, [ [0, builder.constOrJson(s.con(buf))], [1, data.build(builder)], ]); return tupleId; }); this.id = id; this.data = data; } } nodes.ext = ext; })(nodes || (nodes = {})); /* tslint:enable no-namespace class-name */ /** * Schema builder. Use this to create a JSON CRDT model schema and the default * value. * * Example: * * ```typescript * const schema = s.obj({ * name: s.str(''), * age: s.con(0), * tags: s.arr<nodes.con<string>>([]), * }); * ``` */ export const schema = { /** * Creates a "con" node schema and the default value. * * @param raw Raw default value. */ con: (raw) => new nodes.con(raw), /** * Creates a "str" node schema and the default value. * * @param str Default value. */ str: (str) => new nodes.str(str || ''), /** * Creates a "bin" node schema and the default value. * * @param bin Default value. */ bin: (bin) => new nodes.bin(bin), /** * Creates a "val" node schema and the default value. * * @param val Default value. */ val: (val) => new nodes.val(val), /** * Creates a "vec" node schema and the default value. * * @param vec Default value. */ vec: (...vec) => new nodes.vec(vec), /** * Creates a "obj" node schema and the default value. * * @param obj Default value, required object keys. * @param opt Default value of optional object keys. */ obj: (obj, opt) => new nodes.obj(obj, opt), /** * This is an alias for {@link schema.obj}. It creates a "map" node schema, * which is an object where a key can be any string and the value is of the * same type. * * @param obj Default value. */ map: (obj) => schema.obj(obj), /** * Creates an "arr" node schema and the default value. * * @param arr Default value. */ arr: (arr) => new nodes.arr(arr), /** * Recursively creates a node tree from any POJO. It uses the * {@link Builder.json} method to create a JSON node. It can be used similar * to TypeScript's *any* type, where the value can be anything. * * @param value Default value. */ json: (value) => new nodes.json(value), /** * Recursively creates a node tree from any POJO. It uses the * {@link Builder.constOrJson} method to create a JSON node. It can be used * similar to TypeScript's *any* type, where the value can be anything. * * @param value Default value. */ jsonCon: (value) => new nodes.jsonCon(value), /** * Creates an extension node schema. * * @param id A unique extension ID. * @param data Schema of the data node of the extension. */ ext: (id, data) => new nodes.ext(id, data), }; /** * Schema builder. Use this to create a JSON CRDT model schema and the default * value. Alias for {@link schema}. */ export const s = schema;