diff --git a/src/json-crdt-patch/codec/compact/constants.ts b/src/json-crdt-patch/codec/compact/constants.ts deleted file mode 100644 index 74433e3d00..0000000000 --- a/src/json-crdt-patch/codec/compact/constants.ts +++ /dev/null @@ -1,32 +0,0 @@ -// TODO: Remove this file. - -/** - * Operation opcodes. Any change to this enum is a BREAKING CHANGE. - */ -export const enum Code { - // "Make" operations go first, as they are reused in structural encoding. - MakeUndefined = 0, - MakeConst, - MakeValue, - MakeObject, - MakeString, - MakeArray, - MakeBinary, - MakeTuple, - MakeConstId, - - /** - * @todo Separate type operations from all other operations. - */ - - // Other operations. - InsertStringSubstring, - Delete, - SetObjectKeys, - SetValue, - InsertArrayElements, - DeleteOne, - NoopOne, - Noop, - InsertBinaryData, -} diff --git a/src/json-crdt/__tests__/fuzzer/Picker.ts b/src/json-crdt/__tests__/fuzzer/Picker.ts index 42a4ece843..4e3a130c60 100644 --- a/src/json-crdt/__tests__/fuzzer/Picker.ts +++ b/src/json-crdt/__tests__/fuzzer/Picker.ts @@ -48,7 +48,7 @@ export class Picker { if (Math.random() > 0.45) return [this.generateObjectKey(), InsObjOp]; const keys = [...node.keys.keys()]; if (!keys.length) return [this.generateObjectKey(), InsObjOp]; - const key = keys[Math.floor(Math.random() * keys.length)]; + const key = Fuzzer.pick(keys); return [key, DelOp]; } diff --git a/src/json-crdt/codec/structural/compact-binary/Decoder.ts b/src/json-crdt/codec/structural/compact-binary/Decoder.ts index a5d11b6e7d..d3708ba0c0 100644 --- a/src/json-crdt/codec/structural/compact-binary/Decoder.ts +++ b/src/json-crdt/codec/structural/compact-binary/Decoder.ts @@ -6,8 +6,8 @@ export class Decoder { protected decoder = new CompactDecoder(); public decode(uint8: Uint8Array): Model { - const json = decoder.decode(uint8); - const doc = this.decoder.decode(json as unknown[]); + const json = decoder.read(uint8); + const doc = this.decoder.decode(json as any); return doc; } } diff --git a/src/json-crdt/codec/structural/compact/Decoder.ts b/src/json-crdt/codec/structural/compact/Decoder.ts index fe7a8154b0..a1e417529c 100644 --- a/src/json-crdt/codec/structural/compact/Decoder.ts +++ b/src/json-crdt/codec/structural/compact/Decoder.ts @@ -2,176 +2,164 @@ import * as nodes from '../../../nodes'; import {ClockDecoder} from '../../../../json-crdt-patch/codec/clock/ClockDecoder'; import {ITimestampStruct, Timestamp} from '../../../../json-crdt-patch/clock'; import {Model, UNDEFINED} from '../../../model/Model'; -import {ORIGIN, SESSION} from '../../../../json-crdt-patch/constants'; -import {Code} from '../../../../json-crdt-patch/codec/compact/constants'; +import {JsonCrdtDataType, SESSION} from '../../../../json-crdt-patch/constants'; +import type * as t from './types'; export class Decoder { protected time?: number; protected clockDecoder?: ClockDecoder; - public decode(data: unknown[]): Model { - const x = data[0]; - const isServerTime = typeof x === 'number'; + public decode(doc: t.JsonCrdtCompactDocument): Model { + const [time, root] = doc; + const isServerTime = typeof time === 'number'; if (isServerTime) { - this.time = x; + this.time = time; } else { - this.clockDecoder = ClockDecoder.fromArr(x as number[]); + this.clockDecoder = ClockDecoder.fromArr(time as number[]); } - const doc = isServerTime ? Model.withServerClock(x as number) : Model.withLogicalClock(this.clockDecoder!.clock); - const val = data[1] ? this.decodeNode(doc, data[1]) : UNDEFINED; - doc.root = new nodes.RootNode(doc, val.id); - return doc; + const model = isServerTime + ? Model.withServerClock(time as number) + : Model.withLogicalClock(this.clockDecoder!.clock); + const val = root ? this.decNode(model, root) : UNDEFINED; + model.root = new nodes.RootNode(model, val.id); + return model; } - protected ts(arr: unknown[], index: number): [ITimestampStruct, number] { - const x = arr[index]; + protected ts(x: t.JsonCrdtCompactTimestamp): ITimestampStruct { if (typeof x === 'number') { - if (x < 0) { - const sessionIndex = -x; - const timeDiff = arr[index + 1] as number; - return [this.clockDecoder!.decodeId(sessionIndex, timeDiff), index + 2]; - } else { - return [new Timestamp(SESSION.SERVER, this.time! - x), index + 1]; - } + return new Timestamp(SESSION.SERVER, this.time! - x); } else { - const time = (x as [number])[0]; - switch (time) { - case ORIGIN.time: - return [ORIGIN, index + 1]; - default: - return [new Timestamp(SESSION.SYSTEM, time), index + 1]; + const [sid, time] = x as [number, number]; + if (sid < 0) { + return this.clockDecoder!.decodeId(-sid, time); + } else { + return new Timestamp(sid, time); } } } - protected decodeNode(doc: Model, data: unknown): nodes.JsonNode { - if (data instanceof Array) { - switch (data[0]) { - case Code.MakeObject: - return this.decodeObj(doc, data); - case Code.MakeArray: - return this.decodeArr(doc, data); - case Code.MakeString: - return this.decodeStr(doc, data); - case Code.MakeValue: - return this.decodeVal(doc, data); - case Code.MakeConst: - return this.decodeConst(doc, data); - case Code.MakeConstId: - return this.decodeConstId(doc, data); - case Code.MakeBinary: - return this.decodeBin(doc, data); - case Code.MakeTuple: - return this.decodeTup(doc, data); - } + protected decNode(model: Model, node: t.JsonCrdtCompactNode): nodes.JsonNode { + switch (node[0]) { + case JsonCrdtDataType.con: + return this.decCon(model, node); + case JsonCrdtDataType.val: + return this.decVal(model, node); + case JsonCrdtDataType.obj: + return this.decObj(model, node); + case JsonCrdtDataType.vec: + return this.decVec(model, node); + case JsonCrdtDataType.str: + return this.decStr(model, node); + case JsonCrdtDataType.bin: + return this.decBin(model, node); + case JsonCrdtDataType.arr: + return this.decArr(model, node); } throw new Error('UNKNOWN_NODE'); } - protected decodeObj(doc: Model, data: unknown[]): nodes.ObjNode { - const [id, index] = this.ts(data, 1); - const obj = new nodes.ObjNode(doc, id); - const length = data.length; - for (let i = index; i < length; ) { - const key = data[i] as string; - const val = this.decodeNode(doc, data[++i]); - obj.put(key, val.id); - i++; + protected decCon(doc: Model, node: t.JsonCrdtCompactCon): nodes.ConNode { + const id = this.ts(node[1]); + let data: unknown | undefined | Timestamp = node[2]; + if (node.length > 3) { + const specialData = node[3] as unknown; + if (!specialData) data = undefined; + else data = this.ts(specialData as t.JsonCrdtCompactTimestamp); } + const obj = new nodes.ConNode(id, data); + doc.index.set(id, obj); + return obj; + } + + protected decVal(doc: Model, node: t.JsonCrdtCompactVal): nodes.ValNode { + const id = this.ts(node[1]); + const child = this.decNode(doc, node[2]); + const obj = new nodes.ValNode(doc, id, child.id); doc.index.set(id, obj); return obj; } - protected decodeTup(doc: Model, data: unknown[]): nodes.VecNode { - const [id, index] = this.ts(data, 1); - const obj = new nodes.VecNode(doc, id); - const length = data.length; + protected decObj(model: Model, node: t.JsonCrdtCompactObj): nodes.ObjNode { + const id = this.ts(node[1]); + const obj = new nodes.ObjNode(model, id); + const map = node[2] as t.JsonCrdtCompactObj[2]; + const keys = Object.keys(map); + const length = keys.length; + for (let i = 0; i < length; i++) { + const key = keys[i]; + const val = this.decNode(model, map[key]); + obj.put(key, val.id); + } + model.index.set(id, obj); + return obj; + } + + protected decVec(model: Model, node: t.JsonCrdtCompactVec): nodes.VecNode { + const id = this.ts(node[1]); + const obj = new nodes.VecNode(model, id); + const map = node[2] as t.JsonCrdtCompactVec[2]; const elements = obj.elements; - for (let i = index; i < length; ) { - const component = data[i++]; - if (!component) elements.push(undefined); + const length = map.length; + for (let i = 0; i < length; i++) { + const item = map[i]; + if (!item) elements.push(undefined); else { - const node = this.decodeNode(doc, component); - elements.push(node.id); + const child = this.decNode(model, item); + elements.push(child.id); } } - doc.index.set(id, obj); + model.index.set(id, obj); return obj; } - protected decodeArr(doc: Model, data: unknown[]): nodes.ArrNode { - const size = data[1] as number; - const [id, index] = this.ts(data, 2); - const obj = new nodes.ArrNode(doc, id); - const self = this; - let i = index; + protected decStr(doc: Model, node: t.JsonCrdtCompactStr): nodes.StrNode { + const id = this.ts(node[1]); + const obj = new nodes.StrNode(id); + const chunks = node[2] as t.JsonCrdtCompactStr[2]; + const size = chunks.length; + let i = 0; obj.ingest(size, () => { - const [chunkId, idx] = self.ts(data, i); - const content = data[idx]; - i = idx + 1; - if (typeof content === 'number') return new nodes.ArrChunk(chunkId, content, undefined); - const ids = (content as unknown[]).map((c) => this.decodeNode(doc, c).id); - return new nodes.ArrChunk(chunkId, (content as string).length, ids); - }); - doc.index.set(id, obj); - return obj; - } - - protected decodeStr(doc: Model, data: unknown[]): nodes.StrNode { - const size = data[1] as number; - const [id, index] = this.ts(data, 2); - const node = new nodes.StrNode(id); - const self = this; - let i = index; - node.ingest(size, () => { - const [chunkId, idx] = self.ts(data, i); - const content = data[idx]; - i = idx + 1; + const chunk = chunks[i++]; + const chunkId = this.ts(chunk[0]); + const content = chunk[1]; if (typeof content === 'number') return new nodes.StrChunk(chunkId, content, ''); return new nodes.StrChunk(chunkId, (content as string).length, content as string); }); - doc.index.set(id, node); - return node; - } - - protected decodeBin(doc: Model, data: unknown[]): nodes.BinNode { - const size = data[1] as number; - const [id, index] = this.ts(data, 2); - const node = new nodes.BinNode(id); - const self = this; - let i = index; - node.ingest(size, () => { - const [chunkId, idx] = self.ts(data, i); - const content = data[idx]; - i = idx + 1; - if (typeof content === 'number') return new nodes.BinChunk(chunkId, content, undefined); - const buf = content as Uint8Array; - return new nodes.BinChunk(chunkId, buf.length, buf); - }); - doc.index.set(id, node); - return node; - } - - protected decodeVal(doc: Model, data: unknown[]): nodes.ValNode { - const [id, index] = this.ts(data, 1); - const child = this.decodeNode(doc, data[index]); - const obj = new nodes.ValNode(doc, id, child.id); doc.index.set(id, obj); return obj; } - protected decodeConst(doc: Model, data: unknown[]): nodes.ConNode { - const [id, index] = this.ts(data, 1); - const value = data[index]; - const obj = new nodes.ConNode(id, value); + protected decBin(doc: Model, node: t.JsonCrdtCompactBin): nodes.BinNode { + const id = this.ts(node[1]); + const obj = new nodes.BinNode(id); + const chunks = node[2] as t.JsonCrdtCompactBin[2]; + const size = chunks.length; + let i = 0; + obj.ingest(size, () => { + const chunk = chunks[i++]; + const chunkId = this.ts(chunk[0]); + const content = chunk[1]; + if (typeof content === 'number') return new nodes.BinChunk(chunkId, content, undefined); + return new nodes.BinChunk(chunkId, content.length, content); + }); doc.index.set(id, obj); return obj; } - protected decodeConstId(doc: Model, data: unknown[]): nodes.ConNode { - const [id, index] = this.ts(data, 1); - const val = this.ts(data, index)[0]; - const obj = new nodes.ConNode(id, val); + protected decArr(doc: Model, node: t.JsonCrdtCompactArr): nodes.ArrNode { + const id = this.ts(node[1]); + const obj = new nodes.ArrNode(doc, id); + const chunks = node[2] as t.JsonCrdtCompactArr[2]; + const size = chunks.length; + let i = 0; + obj.ingest(size, () => { + const chunk = chunks[i++]; + const chunkId = this.ts(chunk[0]); + const content = chunk[1]; + if (typeof content === 'number') return new nodes.ArrChunk(chunkId, content, undefined); + const ids = (content as t.JsonCrdtCompactNode[]).map((c) => this.decNode(doc, c).id); + return new nodes.ArrChunk(chunkId, content.length, ids); + }); doc.index.set(id, obj); return obj; } diff --git a/src/json-crdt/codec/structural/compact/Encoder.ts b/src/json-crdt/codec/structural/compact/Encoder.ts index beba774d04..cd0219b230 100644 --- a/src/json-crdt/codec/structural/compact/Encoder.ts +++ b/src/json-crdt/codec/structural/compact/Encoder.ts @@ -1,8 +1,9 @@ import * as nodes from '../../../nodes'; import {ClockEncoder} from '../../../../json-crdt-patch/codec/clock/ClockEncoder'; import {ITimestampStruct, Timestamp} from '../../../../json-crdt-patch/clock'; +import {JsonCrdtDataType} from '../../../../json-crdt-patch/constants'; import {SESSION} from '../../../../json-crdt-patch/constants'; -import {Code} from '../../../../json-crdt-patch/codec/compact/constants'; +import type * as t from './types'; import type {Model} from '../../../model'; export class Encoder { @@ -10,150 +11,127 @@ export class Encoder { protected clock?: ClockEncoder; protected model!: Model; - public encode(model: Model): unknown[] { + public encode(model: Model): t.JsonCrdtCompactDocument { this.model = model; const isServerTime = model.clock.sid === SESSION.SERVER; const clock = model.clock; - const arr: unknown[] = isServerTime ? [clock.time] : [null]; if (isServerTime) { this.time = clock.time; } else { this.clock = new ClockEncoder(); this.clock.reset(model.clock); } - this.encodeRoot(arr, model.root); - if (!isServerTime) arr[0] = this.clock!.toJson(); - return arr; + const root = model.root; + const doc: t.JsonCrdtCompactDocument = [0, !root.val.time ? 0 : this.cNode(root.node())]; + doc[0] = isServerTime ? this.time! : this.clock!.toJson(); + return doc; } - protected ts(arr: unknown[], ts: ITimestampStruct): void { + protected ts(ts: ITimestampStruct): t.JsonCrdtCompactTimestamp { switch (ts.sid) { - case SESSION.SYSTEM: { - arr.push([ts.time]); - break; - } - case SESSION.SERVER: { - arr.push(this.time! - ts.time); - break; - } + case SESSION.SYSTEM: + return [ts.sid, ts.time]; + case SESSION.SERVER: + return this.time! - ts.time; default: { const relativeId = this.clock!.append(ts); - arr.push(-relativeId.sessionIndex, relativeId.timeDiff); + return [-relativeId.sessionIndex, relativeId.timeDiff]; } } } - protected encodeRoot(arr: unknown[], root: nodes.RootNode): void { - if (!root.val.time) arr.push(0); - else this.cNode(arr, root.node()); - } - - protected cNode(arr: unknown[], node: nodes.JsonNode): void { + protected cNode(node: nodes.JsonNode): t.JsonCrdtCompactNode { // TODO: PERF: use switch with `node.constructor`. - if (node instanceof nodes.ObjNode) return this.encodeObj(arr, node); - else if (node instanceof nodes.ArrNode) return this.encodeArr(arr, node); - else if (node instanceof nodes.StrNode) return this.encodeStr(arr, node); - else if (node instanceof nodes.ValNode) return this.cVal(arr, node); - else if (node instanceof nodes.VecNode) return this.cTup(arr, node); - else if (node instanceof nodes.ConNode) return this.cConst(arr, node); - else if (node instanceof nodes.BinNode) return this.encodeBin(arr, node); + if (node instanceof nodes.ObjNode) return this.cObj(node); + else if (node instanceof nodes.ArrNode) return this.cArr(node); + else if (node instanceof nodes.StrNode) return this.cStr(node); + else if (node instanceof nodes.ValNode) return this.cVal(node); + else if (node instanceof nodes.VecNode) return this.cVec(node); + else if (node instanceof nodes.ConNode) return this.cCon(node); + else if (node instanceof nodes.BinNode) return this.cBin(node); throw new Error('UNKNOWN_NODE'); } - protected encodeObj(arr: unknown[], obj: nodes.ObjNode): void { - const res: unknown[] = [Code.MakeObject]; - arr.push(res); - this.ts(res, obj.id); - obj.nodes((node, key) => { - res.push(key); - this.cNode(res, node); - }); + protected cObj(obj: nodes.ObjNode): t.JsonCrdtCompactObj { + const map: t.JsonCrdtCompactObj[2] = {}; + obj.nodes((child, key) => (map[key] = this.cNode(child))); + const res: t.JsonCrdtCompactObj = [JsonCrdtDataType.obj, this.ts(obj.id), map]; + return res; } - protected cTup(arr: unknown[], obj: nodes.VecNode): void { - const res: unknown[] = [Code.MakeTuple]; - arr.push(res); - this.ts(res, obj.id); - const elements = obj.elements; + protected cVec(vec: nodes.VecNode): t.JsonCrdtCompactVec { + const elements = vec.elements; const length = elements.length; const index = this.model.index; + const map: t.JsonCrdtCompactVec[2] = []; for (let i = 0; i < length; i++) { const elementId = elements[i]; - if (!elementId) res.push(0); + if (!elementId) map.push(0); else { const node = index.get(elementId)!; - this.cNode(res, node); + map.push(this.cNode(node)); } } + const res: t.JsonCrdtCompactVec = [JsonCrdtDataType.vec, this.ts(vec.id), map]; + return res; } - protected encodeArr(arr: unknown[], obj: nodes.ArrNode): void { - const res: unknown[] = [Code.MakeArray, obj.size()]; - arr.push(res); - this.ts(res, obj.id); - const iterator = obj.iterator(); - let chunk; - while ((chunk = iterator())) this.encodeArrChunk(res, chunk); - } - - protected encodeArrChunk(arr: unknown[], chunk: nodes.ArrChunk): void { - this.ts(arr, chunk.id); - if (chunk.del) arr.push(chunk.span); - else { - const nodes: unknown[] = []; - const index = this.model.index; - for (const n of chunk.data!) this.cNode(nodes, index.get(n)!); - arr.push(nodes); + protected cArr(node: nodes.ArrNode): t.JsonCrdtCompactArr { + const chunks: t.JsonCrdtCompactArr[2] = []; + const index = this.model.index; + for (let chunk = node.first(); chunk; chunk = node.next(chunk)) { + const deleted = chunk.del; + const span = chunk.span; + const chunkIdEncoded = this.ts(chunk.id); + if (deleted) { + chunks.push([chunkIdEncoded, span]); + } else { + const nodeIds = chunk.data!; + const nodes: t.JsonCrdtCompactArrChunk[1] = []; + for (let i = 0; i < span; i++) nodes.push(this.cNode(index.get(nodeIds[i])!)); + chunks.push([chunkIdEncoded, nodes]); + } } + const res: t.JsonCrdtCompactArr = [JsonCrdtDataType.arr, this.ts(node.id), chunks]; + return res; } - protected encodeStr(arr: unknown[], obj: nodes.StrNode): void { - const res: unknown[] = [Code.MakeString, obj.size()]; - arr.push(res); - this.ts(res, obj.id); - const iterator = obj.iterator(); - let chunk; - while ((chunk = iterator())) this.encodeStrChunk(res, chunk as nodes.StrChunk); - } - - protected encodeStrChunk(arr: unknown[], chunk: nodes.StrChunk): void { - this.ts(arr, chunk.id); - arr.push(chunk.del ? chunk.span : chunk.data!); + protected cStr(node: nodes.StrNode): t.JsonCrdtCompactStr { + const chunks: t.JsonCrdtCompactStr[2] = []; + for (let chunk = node.first(); chunk; chunk = node.next(chunk)) + chunks.push([this.ts(chunk.id), chunk.del ? chunk.span : chunk.data!] as + | t.JsonCrdtCompactStrChunk + | t.JsonCrdtCompactTombstone); + const res: t.JsonCrdtCompactStr = [JsonCrdtDataType.str, this.ts(node.id), chunks]; + return res; } - protected encodeBin(arr: unknown[], obj: nodes.BinNode): void { - const res: unknown[] = [Code.MakeBinary, obj.size()]; - arr.push(res); - this.ts(res, obj.id); - const iterator = obj.iterator(); - let chunk; - while ((chunk = iterator())) this.encodeBinChunk(res, chunk as nodes.BinChunk); + protected cBin(node: nodes.BinNode): t.JsonCrdtCompactBin { + const chunks: t.JsonCrdtCompactBin[2] = []; + for (let chunk = node.first(); chunk; chunk = node.next(chunk)) + chunks.push([this.ts(chunk.id), chunk.del ? chunk.span : chunk.data!] as + | t.JsonCrdtCompactBinChunk + | t.JsonCrdtCompactTombstone); + const res: t.JsonCrdtCompactBin = [JsonCrdtDataType.bin, this.ts(node.id), chunks]; + return res; } - protected encodeBinChunk(arr: unknown[], chunk: nodes.BinChunk): void { - this.ts(arr, chunk.id); - arr.push(chunk.del ? chunk.span : chunk.data!); + protected cVal(node: nodes.ValNode): t.JsonCrdtCompactVal { + const res: t.JsonCrdtCompactVal = [JsonCrdtDataType.val, this.ts(node.id), this.cNode(node.node())]; + return res; } - protected cVal(arr: unknown[], obj: nodes.ValNode): void { - const res: unknown[] = [Code.MakeValue]; - arr.push(res); - this.ts(res, obj.id); - this.cNode(res, obj.node()); - } - - protected cConst(arr: unknown[], obj: nodes.ConNode): void { - const val = obj.val; - const res: unknown[] = []; + protected cCon(node: nodes.ConNode): t.JsonCrdtCompactCon { + const val = node.val; + const res: t.JsonCrdtCompactCon = [JsonCrdtDataType.con, this.ts(node.id), val]; if (val instanceof Timestamp) { - res.push(Code.MakeConstId); - this.ts(res, obj.id); - this.ts(res, val); - } else { - res.push(Code.MakeConst); - this.ts(res, obj.id); - if (val !== undefined) res.push(val); + res[2] = 0; + const specialData = this.ts(val); + res.push(specialData); + } else if (val === undefined) { + res[2] = 0; + res.push(0); } - arr.push(res); + return res; } } diff --git a/src/json-crdt/codec/structural/compact/types.ts b/src/json-crdt/codec/structural/compact/types.ts new file mode 100644 index 0000000000..ba3ddf8eb0 --- /dev/null +++ b/src/json-crdt/codec/structural/compact/types.ts @@ -0,0 +1,63 @@ +import type {JsonCrdtDataType} from '../../../../json-crdt-patch/constants'; + +export type JsonCrdtCompactDocument = [time: JsonCrdtCompactClockTable | number, root: JsonCrdtCompactNode | 0]; + +export type JsonCrdtCompactClockTable = number[]; + +export type JsonCrdtCompactTimestamp = JsonCrdtCompactTimestampServer | JsonCrdtCompactTimestampLogical; + +export type JsonCrdtCompactTimestampServer = number; +export type JsonCrdtCompactTimestampLogical = [sessionId: number, time: number]; + +export type JsonCrdtCompactNode = + | JsonCrdtCompactCon + | JsonCrdtCompactVal + | JsonCrdtCompactObj + | JsonCrdtCompactVec + | JsonCrdtCompactStr + | JsonCrdtCompactBin + | JsonCrdtCompactArr; + +export type JsonCrdtCompactCon = + | [type: JsonCrdtDataType.con, id: JsonCrdtCompactTimestamp, data: unknown] + | [type: JsonCrdtDataType.con, id: JsonCrdtCompactTimestamp, data: 0, specialData: JsonCrdtCompactTimestamp | 0]; + +export type JsonCrdtCompactVal = [type: JsonCrdtDataType.val, id: JsonCrdtCompactTimestamp, child: JsonCrdtCompactNode]; + +export type JsonCrdtCompactObj = [ + type: JsonCrdtDataType.obj, + id: JsonCrdtCompactTimestamp, + map: Record, +]; + +export type JsonCrdtCompactVec = [ + type: JsonCrdtDataType.vec, + id: JsonCrdtCompactTimestamp, + map: (JsonCrdtCompactNode | 0)[], +]; + +export type JsonCrdtCompactStr = [ + type: JsonCrdtDataType.str, + id: JsonCrdtCompactTimestamp, + chunks: Array, +]; + +export type JsonCrdtCompactStrChunk = [id: JsonCrdtCompactTimestamp, data: string]; + +export type JsonCrdtCompactBin = [ + type: JsonCrdtDataType.bin, + id: JsonCrdtCompactTimestamp, + chunks: Array, +]; + +export type JsonCrdtCompactBinChunk = [id: JsonCrdtCompactTimestamp, data: Uint8Array]; + +export type JsonCrdtCompactArr = [ + type: JsonCrdtDataType.arr, + id: JsonCrdtCompactTimestamp, + chunks: Array, +]; + +export type JsonCrdtCompactArrChunk = [id: JsonCrdtCompactTimestamp, data: JsonCrdtCompactNode[]]; + +export type JsonCrdtCompactTombstone = [id: JsonCrdtCompactTimestamp, span: number];