json-joy
Version:
Collection of libraries for building collaborative editing apps.
397 lines (396 loc) • 11.4 kB
JavaScript
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;