From 13420e6ee55e0fbea6ef6b532f640eb596a032c7 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 5 Nov 2023 19:01:16 +0100 Subject: [PATCH 1/8] =?UTF-8?q?feat(json-crdt):=20=F0=9F=8E=B8=20use=20lat?= =?UTF-8?q?est=20node=20opcodes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codec/compact/constants.ts | 32 ------------------- .../codec/structural/compact/Decoder.ts | 29 ++++++++--------- .../codec/structural/compact/Encoder.ts | 18 +++++------ 3 files changed, 23 insertions(+), 56 deletions(-) delete mode 100644 src/json-crdt-patch/codec/compact/constants.ts 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/codec/structural/compact/Decoder.ts b/src/json-crdt/codec/structural/compact/Decoder.ts index fe7a8154b0..9cdafb8096 100644 --- a/src/json-crdt/codec/structural/compact/Decoder.ts +++ b/src/json-crdt/codec/structural/compact/Decoder.ts @@ -2,8 +2,7 @@ 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, ORIGIN, SESSION} from '../../../../json-crdt-patch/constants'; export class Decoder { protected time?: number; @@ -47,22 +46,22 @@ export class Decoder { 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: + case JsonCrdtDataType.con: return this.decodeConst(doc, data); - case Code.MakeConstId: + case JsonCrdtDataType.con + 10: return this.decodeConstId(doc, data); - case Code.MakeBinary: - return this.decodeBin(doc, data); - case Code.MakeTuple: + case JsonCrdtDataType.val: + return this.decodeVal(doc, data); + case JsonCrdtDataType.obj: + return this.decodeObj(doc, data); + case JsonCrdtDataType.vec: return this.decodeTup(doc, data); + case JsonCrdtDataType.str: + return this.decodeStr(doc, data); + case JsonCrdtDataType.bin: + return this.decodeBin(doc, data); + case JsonCrdtDataType.arr: + return this.decodeArr(doc, data); } } throw new Error('UNKNOWN_NODE'); diff --git a/src/json-crdt/codec/structural/compact/Encoder.ts b/src/json-crdt/codec/structural/compact/Encoder.ts index beba774d04..66ba3fc22e 100644 --- a/src/json-crdt/codec/structural/compact/Encoder.ts +++ b/src/json-crdt/codec/structural/compact/Encoder.ts @@ -1,8 +1,8 @@ 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 {Model} from '../../../model'; export class Encoder { @@ -61,7 +61,7 @@ export class Encoder { } protected encodeObj(arr: unknown[], obj: nodes.ObjNode): void { - const res: unknown[] = [Code.MakeObject]; + const res: unknown[] = [JsonCrdtDataType.obj]; arr.push(res); this.ts(res, obj.id); obj.nodes((node, key) => { @@ -71,7 +71,7 @@ export class Encoder { } protected cTup(arr: unknown[], obj: nodes.VecNode): void { - const res: unknown[] = [Code.MakeTuple]; + const res: unknown[] = [JsonCrdtDataType.vec]; arr.push(res); this.ts(res, obj.id); const elements = obj.elements; @@ -88,7 +88,7 @@ export class Encoder { } protected encodeArr(arr: unknown[], obj: nodes.ArrNode): void { - const res: unknown[] = [Code.MakeArray, obj.size()]; + const res: unknown[] = [JsonCrdtDataType.arr, obj.size()]; arr.push(res); this.ts(res, obj.id); const iterator = obj.iterator(); @@ -108,7 +108,7 @@ export class Encoder { } protected encodeStr(arr: unknown[], obj: nodes.StrNode): void { - const res: unknown[] = [Code.MakeString, obj.size()]; + const res: unknown[] = [JsonCrdtDataType.str, obj.size()]; arr.push(res); this.ts(res, obj.id); const iterator = obj.iterator(); @@ -122,7 +122,7 @@ export class Encoder { } protected encodeBin(arr: unknown[], obj: nodes.BinNode): void { - const res: unknown[] = [Code.MakeBinary, obj.size()]; + const res: unknown[] = [JsonCrdtDataType.bin, obj.size()]; arr.push(res); this.ts(res, obj.id); const iterator = obj.iterator(); @@ -136,7 +136,7 @@ export class Encoder { } protected cVal(arr: unknown[], obj: nodes.ValNode): void { - const res: unknown[] = [Code.MakeValue]; + const res: unknown[] = [JsonCrdtDataType.val]; arr.push(res); this.ts(res, obj.id); this.cNode(res, obj.node()); @@ -146,11 +146,11 @@ export class Encoder { const val = obj.val; const res: unknown[] = []; if (val instanceof Timestamp) { - res.push(Code.MakeConstId); + res.push(JsonCrdtDataType.con + 10); this.ts(res, obj.id); this.ts(res, val); } else { - res.push(Code.MakeConst); + res.push(JsonCrdtDataType.con); this.ts(res, obj.id); if (val !== undefined) res.push(val); } From 92426dfe3edf36ae8f0ab5ea9941a98a04b132ca Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 5 Nov 2023 19:04:17 +0100 Subject: [PATCH 2/8] =?UTF-8?q?refactor(json-crdt):=20=F0=9F=92=A1=20renam?= =?UTF-8?q?e=20method=20names=20according=20to=20node=20type=20names?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codec/structural/compact/Decoder.ts | 32 ++++++++--------- .../codec/structural/compact/Encoder.ts | 36 +++++++++---------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/json-crdt/codec/structural/compact/Decoder.ts b/src/json-crdt/codec/structural/compact/Decoder.ts index 9cdafb8096..239d885f51 100644 --- a/src/json-crdt/codec/structural/compact/Decoder.ts +++ b/src/json-crdt/codec/structural/compact/Decoder.ts @@ -47,27 +47,27 @@ export class Decoder { if (data instanceof Array) { switch (data[0]) { case JsonCrdtDataType.con: - return this.decodeConst(doc, data); + return this.decCon(doc, data); case JsonCrdtDataType.con + 10: - return this.decodeConstId(doc, data); + return this.decConId(doc, data); case JsonCrdtDataType.val: - return this.decodeVal(doc, data); + return this.decVal(doc, data); case JsonCrdtDataType.obj: - return this.decodeObj(doc, data); + return this.decObj(doc, data); case JsonCrdtDataType.vec: - return this.decodeTup(doc, data); + return this.decVec(doc, data); case JsonCrdtDataType.str: - return this.decodeStr(doc, data); + return this.decStr(doc, data); case JsonCrdtDataType.bin: - return this.decodeBin(doc, data); + return this.decBin(doc, data); case JsonCrdtDataType.arr: - return this.decodeArr(doc, data); + return this.decArr(doc, data); } } throw new Error('UNKNOWN_NODE'); } - protected decodeObj(doc: Model, data: unknown[]): nodes.ObjNode { + protected decObj(doc: Model, data: unknown[]): nodes.ObjNode { const [id, index] = this.ts(data, 1); const obj = new nodes.ObjNode(doc, id); const length = data.length; @@ -81,7 +81,7 @@ export class Decoder { return obj; } - protected decodeTup(doc: Model, data: unknown[]): nodes.VecNode { + protected decVec(doc: Model, data: unknown[]): nodes.VecNode { const [id, index] = this.ts(data, 1); const obj = new nodes.VecNode(doc, id); const length = data.length; @@ -98,7 +98,7 @@ export class Decoder { return obj; } - protected decodeArr(doc: Model, data: unknown[]): nodes.ArrNode { + protected decArr(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); @@ -116,7 +116,7 @@ export class Decoder { return obj; } - protected decodeStr(doc: Model, data: unknown[]): nodes.StrNode { + protected decStr(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); @@ -133,7 +133,7 @@ export class Decoder { return node; } - protected decodeBin(doc: Model, data: unknown[]): nodes.BinNode { + protected decBin(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); @@ -151,7 +151,7 @@ export class Decoder { return node; } - protected decodeVal(doc: Model, data: unknown[]): nodes.ValNode { + protected decVal(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); @@ -159,7 +159,7 @@ export class Decoder { return obj; } - protected decodeConst(doc: Model, data: unknown[]): nodes.ConNode { + protected decCon(doc: Model, data: unknown[]): nodes.ConNode { const [id, index] = this.ts(data, 1); const value = data[index]; const obj = new nodes.ConNode(id, value); @@ -167,7 +167,7 @@ export class Decoder { return obj; } - protected decodeConstId(doc: Model, data: unknown[]): nodes.ConNode { + protected decConId(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); diff --git a/src/json-crdt/codec/structural/compact/Encoder.ts b/src/json-crdt/codec/structural/compact/Encoder.ts index 66ba3fc22e..f5c88ec958 100644 --- a/src/json-crdt/codec/structural/compact/Encoder.ts +++ b/src/json-crdt/codec/structural/compact/Encoder.ts @@ -50,17 +50,17 @@ export class Encoder { protected cNode(arr: unknown[], node: nodes.JsonNode): void { // 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); + if (node instanceof nodes.ObjNode) return this.cObj(arr, node); + else if (node instanceof nodes.ArrNode) return this.cArr(arr, node); + else if (node instanceof nodes.StrNode) return this.cStr(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); + else if (node instanceof nodes.VecNode) return this.cVec(arr, node); + else if (node instanceof nodes.ConNode) return this.cCon(arr, node); + else if (node instanceof nodes.BinNode) return this.cBin(arr, node); throw new Error('UNKNOWN_NODE'); } - protected encodeObj(arr: unknown[], obj: nodes.ObjNode): void { + protected cObj(arr: unknown[], obj: nodes.ObjNode): void { const res: unknown[] = [JsonCrdtDataType.obj]; arr.push(res); this.ts(res, obj.id); @@ -70,7 +70,7 @@ export class Encoder { }); } - protected cTup(arr: unknown[], obj: nodes.VecNode): void { + protected cVec(arr: unknown[], obj: nodes.VecNode): void { const res: unknown[] = [JsonCrdtDataType.vec]; arr.push(res); this.ts(res, obj.id); @@ -87,16 +87,16 @@ export class Encoder { } } - protected encodeArr(arr: unknown[], obj: nodes.ArrNode): void { + protected cArr(arr: unknown[], obj: nodes.ArrNode): void { const res: unknown[] = [JsonCrdtDataType.arr, obj.size()]; arr.push(res); this.ts(res, obj.id); const iterator = obj.iterator(); let chunk; - while ((chunk = iterator())) this.encodeArrChunk(res, chunk); + while ((chunk = iterator())) this.cArrChunk(res, chunk); } - protected encodeArrChunk(arr: unknown[], chunk: nodes.ArrChunk): void { + protected cArrChunk(arr: unknown[], chunk: nodes.ArrChunk): void { this.ts(arr, chunk.id); if (chunk.del) arr.push(chunk.span); else { @@ -107,30 +107,30 @@ export class Encoder { } } - protected encodeStr(arr: unknown[], obj: nodes.StrNode): void { + protected cStr(arr: unknown[], obj: nodes.StrNode): void { const res: unknown[] = [JsonCrdtDataType.str, 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); + while ((chunk = iterator())) this.cStrChunk(res, chunk as nodes.StrChunk); } - protected encodeStrChunk(arr: unknown[], chunk: nodes.StrChunk): void { + protected cStrChunk(arr: unknown[], chunk: nodes.StrChunk): void { this.ts(arr, chunk.id); arr.push(chunk.del ? chunk.span : chunk.data!); } - protected encodeBin(arr: unknown[], obj: nodes.BinNode): void { + protected cBin(arr: unknown[], obj: nodes.BinNode): void { const res: unknown[] = [JsonCrdtDataType.bin, 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); + while ((chunk = iterator())) this.cBinChunk(res, chunk as nodes.BinChunk); } - protected encodeBinChunk(arr: unknown[], chunk: nodes.BinChunk): void { + protected cBinChunk(arr: unknown[], chunk: nodes.BinChunk): void { this.ts(arr, chunk.id); arr.push(chunk.del ? chunk.span : chunk.data!); } @@ -142,7 +142,7 @@ export class Encoder { this.cNode(res, obj.node()); } - protected cConst(arr: unknown[], obj: nodes.ConNode): void { + protected cCon(arr: unknown[], obj: nodes.ConNode): void { const val = obj.val; const res: unknown[] = []; if (val instanceof Timestamp) { From b89c4d03beca77a4bd525cec76f4c146aa405309 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 5 Nov 2023 22:34:19 +0100 Subject: [PATCH 3/8] =?UTF-8?q?feat(json-crdt):=20=F0=9F=8E=B8=20add=20com?= =?UTF-8?q?pact=20encoding=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codec/structural/compact/types.ts | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 src/json-crdt/codec/structural/compact/types.ts 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..afe85fff01 --- /dev/null +++ b/src/json-crdt/codec/structural/compact/types.ts @@ -0,0 +1,93 @@ +import {JsonCrdtDataType} from '../../../../json-crdt-patch/constants'; + +export type JsonCrdtCompactDocument = [ + time: JsonCrdtCompactClockTable | 0, + 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, +]; From e0b7ad1b376448ed65e9de5fd9674ade3f9c877f Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 5 Nov 2023 22:58:07 +0100 Subject: [PATCH 4/8] =?UTF-8?q?feat(json-crdt):=20=F0=9F=8E=B8=20add=20JSO?= =?UTF-8?q?N=20CRDT=20compact=20encoder=20according=20to=20the=20specifica?= =?UTF-8?q?tion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codec/structural/compact/Encoder.ts | 173 ++++++++---------- .../codec/structural/compact/types.ts | 2 +- 2 files changed, 75 insertions(+), 100 deletions(-) diff --git a/src/json-crdt/codec/structural/compact/Encoder.ts b/src/json-crdt/codec/structural/compact/Encoder.ts index f5c88ec958..3a354922ec 100644 --- a/src/json-crdt/codec/structural/compact/Encoder.ts +++ b/src/json-crdt/codec/structural/compact/Encoder.ts @@ -3,6 +3,7 @@ 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 type * as t from './types'; import type {Model} from '../../../model'; export class Encoder { @@ -14,146 +15,120 @@ export class Encoder { 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()), + ]; + if (!isServerTime) doc[0] = 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.cObj(arr, node); - else if (node instanceof nodes.ArrNode) return this.cArr(arr, node); - else if (node instanceof nodes.StrNode) return this.cStr(arr, node); - else if (node instanceof nodes.ValNode) return this.cVal(arr, node); - else if (node instanceof nodes.VecNode) return this.cVec(arr, node); - else if (node instanceof nodes.ConNode) return this.cCon(arr, node); - else if (node instanceof nodes.BinNode) return this.cBin(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 cObj(arr: unknown[], obj: nodes.ObjNode): void { - const res: unknown[] = [JsonCrdtDataType.obj]; - 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 cVec(arr: unknown[], obj: nodes.VecNode): void { - const res: unknown[] = [JsonCrdtDataType.vec]; - 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 cArr(arr: unknown[], obj: nodes.ArrNode): void { - const res: unknown[] = [JsonCrdtDataType.arr, obj.size()]; - arr.push(res); - this.ts(res, obj.id); - const iterator = obj.iterator(); - let chunk; - while ((chunk = iterator())) this.cArrChunk(res, chunk); - } - - protected cArrChunk(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 cStr(arr: unknown[], obj: nodes.StrNode): void { - const res: unknown[] = [JsonCrdtDataType.str, obj.size()]; - arr.push(res); - this.ts(res, obj.id); - const iterator = obj.iterator(); - let chunk; - while ((chunk = iterator())) this.cStrChunk(res, chunk as nodes.StrChunk); - } - - protected cStrChunk(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 cBin(arr: unknown[], obj: nodes.BinNode): void { - const res: unknown[] = [JsonCrdtDataType.bin, obj.size()]; - arr.push(res); - this.ts(res, obj.id); - const iterator = obj.iterator(); - let chunk; - while ((chunk = iterator())) this.cBinChunk(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 cBinChunk(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[] = [JsonCrdtDataType.val]; - arr.push(res); - this.ts(res, obj.id); - this.cNode(res, obj.node()); - } - - protected cCon(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(JsonCrdtDataType.con + 10); - this.ts(res, obj.id); - this.ts(res, val); - } else { - res.push(JsonCrdtDataType.con); - 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 index afe85fff01..fb2ff4163f 100644 --- a/src/json-crdt/codec/structural/compact/types.ts +++ b/src/json-crdt/codec/structural/compact/types.ts @@ -84,7 +84,7 @@ export type JsonCrdtCompactArr = [ export type JsonCrdtCompactArrChunk = [ id: JsonCrdtCompactTimestamp, - data: JsonCrdtCompactNode, + data: JsonCrdtCompactNode[], ]; export type JsonCrdtCompactTombstone = [ From 42292a4eb818da4d661a3c5ad909eebc4a806ed2 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 5 Nov 2023 23:46:01 +0100 Subject: [PATCH 5/8] =?UTF-8?q?feat(json-crdt):=20=F0=9F=8E=B8=20add=20com?= =?UTF-8?q?pact=20json=20decoder=20according=20to=20specification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../structural/compact-binary/Decoder.ts | 4 +- .../codec/structural/compact/Decoder.ts | 225 ++++++++---------- .../codec/structural/compact/Encoder.ts | 4 +- .../codec/structural/compact/types.ts | 4 +- 4 files changed, 107 insertions(+), 130 deletions(-) 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 239d885f51..21d2ea7490 100644 --- a/src/json-crdt/codec/structural/compact/Decoder.ts +++ b/src/json-crdt/codec/structural/compact/Decoder.ts @@ -3,174 +3,151 @@ import {ClockDecoder} from '../../../../json-crdt-patch/codec/clock/ClockDecoder import {ITimestampStruct, Timestamp} from '../../../../json-crdt-patch/clock'; import {Model, UNDEFINED} from '../../../model/Model'; import {JsonCrdtDataType, ORIGIN, 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 JsonCrdtDataType.con: - return this.decCon(doc, data); - case JsonCrdtDataType.con + 10: - return this.decConId(doc, data); - case JsonCrdtDataType.val: - return this.decVal(doc, data); - case JsonCrdtDataType.obj: - return this.decObj(doc, data); - case JsonCrdtDataType.vec: - return this.decVec(doc, data); - case JsonCrdtDataType.str: - return this.decStr(doc, data); - case JsonCrdtDataType.bin: - return this.decBin(doc, data); - case JsonCrdtDataType.arr: - return this.decArr(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 decObj(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 decVec(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]; + for (const key in map) { + 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 decArr(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 decStr(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 decBin(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 decVal(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 decCon(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 decConId(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 3a354922ec..989a953a60 100644 --- a/src/json-crdt/codec/structural/compact/Encoder.ts +++ b/src/json-crdt/codec/structural/compact/Encoder.ts @@ -11,7 +11,7 @@ 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; @@ -26,7 +26,7 @@ export class Encoder { 0, !root.val.time ? 0 : this.cNode(root.node()), ]; - if (!isServerTime) doc[0] = this.clock!.toJson(); + doc[0] = isServerTime ? this.time! : this.clock!.toJson(); return doc; } diff --git a/src/json-crdt/codec/structural/compact/types.ts b/src/json-crdt/codec/structural/compact/types.ts index fb2ff4163f..acdf2bf534 100644 --- a/src/json-crdt/codec/structural/compact/types.ts +++ b/src/json-crdt/codec/structural/compact/types.ts @@ -1,7 +1,7 @@ -import {JsonCrdtDataType} from '../../../../json-crdt-patch/constants'; +import type {JsonCrdtDataType} from '../../../../json-crdt-patch/constants'; export type JsonCrdtCompactDocument = [ - time: JsonCrdtCompactClockTable | 0, + time: JsonCrdtCompactClockTable | number, root: JsonCrdtCompactNode | 0, ]; From 15b89a1ffbb0e3ab7d9bf1ebb515b68e15b24273 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 5 Nov 2023 23:46:28 +0100 Subject: [PATCH 6/8] =?UTF-8?q?style(json-crdt):=20=F0=9F=92=84=20run=20Pr?= =?UTF-8?q?ettier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codec/structural/compact/Decoder.ts | 25 +++++++--- .../codec/structural/compact/Encoder.ts | 21 ++++---- .../codec/structural/compact/types.ts | 50 ++++--------------- 3 files changed, 39 insertions(+), 57 deletions(-) diff --git a/src/json-crdt/codec/structural/compact/Decoder.ts b/src/json-crdt/codec/structural/compact/Decoder.ts index 21d2ea7490..c315e30c13 100644 --- a/src/json-crdt/codec/structural/compact/Decoder.ts +++ b/src/json-crdt/codec/structural/compact/Decoder.ts @@ -17,7 +17,9 @@ export class Decoder { } else { this.clockDecoder = ClockDecoder.fromArr(time as number[]); } - const model = isServerTime ? Model.withServerClock(time as number) : Model.withLogicalClock(this.clockDecoder!.clock); + 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; @@ -38,13 +40,20 @@ export class Decoder { 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); + 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'); } diff --git a/src/json-crdt/codec/structural/compact/Encoder.ts b/src/json-crdt/codec/structural/compact/Encoder.ts index 989a953a60..cd0219b230 100644 --- a/src/json-crdt/codec/structural/compact/Encoder.ts +++ b/src/json-crdt/codec/structural/compact/Encoder.ts @@ -22,18 +22,17 @@ export class Encoder { this.clock.reset(model.clock); } const root = model.root; - const doc: t.JsonCrdtCompactDocument = [ - 0, - !root.val.time ? 0 : this.cNode(root.node()), - ]; + 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(ts: ITimestampStruct): t.JsonCrdtCompactTimestamp { switch (ts.sid) { - case SESSION.SYSTEM: return [ts.sid, ts.time]; - case SESSION.SERVER: return this.time! - ts.time; + case SESSION.SYSTEM: + return [ts.sid, ts.time]; + case SESSION.SERVER: + return this.time! - ts.time; default: { const relativeId = this.clock!.append(ts); return [-relativeId.sessionIndex, relativeId.timeDiff]; @@ -55,7 +54,7 @@ export class Encoder { protected cObj(obj: nodes.ObjNode): t.JsonCrdtCompactObj { const map: t.JsonCrdtCompactObj[2] = {}; - obj.nodes((child, key) => map[key] = this.cNode(child)); + obj.nodes((child, key) => (map[key] = this.cNode(child))); const res: t.JsonCrdtCompactObj = [JsonCrdtDataType.obj, this.ts(obj.id), map]; return res; } @@ -100,7 +99,9 @@ export class Encoder { 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); + 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; } @@ -108,7 +109,9 @@ export class Encoder { 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); + 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; } diff --git a/src/json-crdt/codec/structural/compact/types.ts b/src/json-crdt/codec/structural/compact/types.ts index acdf2bf534..ba3ddf8eb0 100644 --- a/src/json-crdt/codec/structural/compact/types.ts +++ b/src/json-crdt/codec/structural/compact/types.ts @@ -1,15 +1,10 @@ import type {JsonCrdtDataType} from '../../../../json-crdt-patch/constants'; -export type JsonCrdtCompactDocument = [ - time: JsonCrdtCompactClockTable | number, - root: JsonCrdtCompactNode | 0, -]; +export type JsonCrdtCompactDocument = [time: JsonCrdtCompactClockTable | number, root: JsonCrdtCompactNode | 0]; export type JsonCrdtCompactClockTable = number[]; -export type JsonCrdtCompactTimestamp = - | JsonCrdtCompactTimestampServer - | JsonCrdtCompactTimestampLogical; +export type JsonCrdtCompactTimestamp = JsonCrdtCompactTimestampServer | JsonCrdtCompactTimestampLogical; export type JsonCrdtCompactTimestampServer = number; export type JsonCrdtCompactTimestampLogical = [sessionId: number, time: number]; @@ -24,23 +19,10 @@ export type JsonCrdtCompactNode = | 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, -]; + | [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, @@ -60,10 +42,7 @@ export type JsonCrdtCompactStr = [ chunks: Array, ]; -export type JsonCrdtCompactStrChunk = [ - id: JsonCrdtCompactTimestamp, - data: string, -]; +export type JsonCrdtCompactStrChunk = [id: JsonCrdtCompactTimestamp, data: string]; export type JsonCrdtCompactBin = [ type: JsonCrdtDataType.bin, @@ -71,10 +50,7 @@ export type JsonCrdtCompactBin = [ chunks: Array, ]; -export type JsonCrdtCompactBinChunk = [ - id: JsonCrdtCompactTimestamp, - data: Uint8Array, -]; +export type JsonCrdtCompactBinChunk = [id: JsonCrdtCompactTimestamp, data: Uint8Array]; export type JsonCrdtCompactArr = [ type: JsonCrdtDataType.arr, @@ -82,12 +58,6 @@ export type JsonCrdtCompactArr = [ chunks: Array, ]; -export type JsonCrdtCompactArrChunk = [ - id: JsonCrdtCompactTimestamp, - data: JsonCrdtCompactNode[], -]; +export type JsonCrdtCompactArrChunk = [id: JsonCrdtCompactTimestamp, data: JsonCrdtCompactNode[]]; -export type JsonCrdtCompactTombstone = [ - id: JsonCrdtCompactTimestamp, - span: number, -]; +export type JsonCrdtCompactTombstone = [id: JsonCrdtCompactTimestamp, span: number]; From eccb1a3408d471a651f2035c8f930d4a6d8787dc Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 5 Nov 2023 23:51:55 +0100 Subject: [PATCH 7/8] =?UTF-8?q?fix(json-crdt):=20=F0=9F=90=9B=20iterate=20?= =?UTF-8?q?over=20chunks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt/codec/structural/compact/Decoder.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/json-crdt/codec/structural/compact/Decoder.ts b/src/json-crdt/codec/structural/compact/Decoder.ts index c315e30c13..0c8f293384 100644 --- a/src/json-crdt/codec/structural/compact/Decoder.ts +++ b/src/json-crdt/codec/structural/compact/Decoder.ts @@ -116,7 +116,7 @@ export class Decoder { const size = chunks.length; let i = 0; obj.ingest(size, () => { - const chunk = chunks[i]; + const chunk = chunks[i++]; const chunkId = this.ts(chunk[0]); const content = chunk[1]; if (typeof content === 'number') return new nodes.StrChunk(chunkId, content, ''); @@ -133,7 +133,7 @@ export class Decoder { const size = chunks.length; let i = 0; obj.ingest(size, () => { - const chunk = chunks[i]; + 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); @@ -150,7 +150,7 @@ export class Decoder { const size = chunks.length; let i = 0; obj.ingest(size, () => { - const chunk = chunks[i]; + 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); From 66da4b93a399d77d3c524c237e48108cbdd8e5b2 Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 6 Nov 2023 01:26:55 +0100 Subject: [PATCH 8/8] =?UTF-8?q?fix(json-crdt):=20=F0=9F=90=9B=20correctly?= =?UTF-8?q?=20iterate=20through=20object?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt/__tests__/fuzzer/Picker.ts | 2 +- src/json-crdt/codec/structural/compact/Decoder.ts | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) 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/Decoder.ts b/src/json-crdt/codec/structural/compact/Decoder.ts index 0c8f293384..a1e417529c 100644 --- a/src/json-crdt/codec/structural/compact/Decoder.ts +++ b/src/json-crdt/codec/structural/compact/Decoder.ts @@ -2,7 +2,7 @@ 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 {JsonCrdtDataType, ORIGIN, SESSION} from '../../../../json-crdt-patch/constants'; +import {JsonCrdtDataType, SESSION} from '../../../../json-crdt-patch/constants'; import type * as t from './types'; export class Decoder { @@ -83,7 +83,10 @@ export class Decoder { const id = this.ts(node[1]); const obj = new nodes.ObjNode(model, id); const map = node[2] as t.JsonCrdtCompactObj[2]; - for (const key in map) { + 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); }