Skip to content

Commit e0b7ad1

Browse files
committed
feat(json-crdt): 🎸 add JSON CRDT compact encoder according to the specification
1 parent b89c4d0 commit e0b7ad1

File tree

2 files changed

+75
-100
lines changed

2 files changed

+75
-100
lines changed

‎src/json-crdt/codec/structural/compact/Encoder.ts‎

Lines changed: 74 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {ClockEncoder} from '../../../../json-crdt-patch/codec/clock/ClockEncoder
33
import {ITimestampStruct, Timestamp} from '../../../../json-crdt-patch/clock';
44
import {JsonCrdtDataType} from '../../../../json-crdt-patch/constants';
55
import {SESSION} from '../../../../json-crdt-patch/constants';
6+
import type * as t from './types';
67
import type {Model} from '../../../model';
78

89
export class Encoder {
@@ -14,146 +15,120 @@ export class Encoder {
1415
this.model = model;
1516
const isServerTime = model.clock.sid === SESSION.SERVER;
1617
const clock = model.clock;
17-
const arr: unknown[] = isServerTime ? [clock.time] : [null];
1818
if (isServerTime) {
1919
this.time = clock.time;
2020
} else {
2121
this.clock = new ClockEncoder();
2222
this.clock.reset(model.clock);
2323
}
24-
this.encodeRoot(arr, model.root);
25-
if (!isServerTime) arr[0] = this.clock!.toJson();
26-
return arr;
24+
const root = model.root;
25+
const doc: t.JsonCrdtCompactDocument = [
26+
0,
27+
!root.val.time ? 0 : this.cNode(root.node()),
28+
];
29+
if (!isServerTime) doc[0] = this.clock!.toJson();
30+
return doc;
2731
}
2832

29-
protected ts(arr: unknown[], ts: ITimestampStruct): void {
33+
protected ts(ts: ITimestampStruct): t.JsonCrdtCompactTimestamp {
3034
switch (ts.sid) {
31-
case SESSION.SYSTEM: {
32-
arr.push([ts.time]);
33-
break;
34-
}
35-
case SESSION.SERVER: {
36-
arr.push(this.time! - ts.time);
37-
break;
38-
}
35+
case SESSION.SYSTEM: return [ts.sid, ts.time];
36+
case SESSION.SERVER: return this.time! - ts.time;
3937
default: {
4038
const relativeId = this.clock!.append(ts);
41-
arr.push(-relativeId.sessionIndex, relativeId.timeDiff);
39+
return [-relativeId.sessionIndex, relativeId.timeDiff];
4240
}
4341
}
4442
}
4543

46-
protected encodeRoot(arr: unknown[], root: nodes.RootNode): void {
47-
if (!root.val.time) arr.push(0);
48-
else this.cNode(arr, root.node());
49-
}
50-
51-
protected cNode(arr: unknown[], node: nodes.JsonNode): void {
44+
protected cNode(node: nodes.JsonNode): t.JsonCrdtCompactNode {
5245
// TODO: PERF: use switch with `node.constructor`.
53-
if (node instanceof nodes.ObjNode) return this.cObj(arr, node);
54-
else if (node instanceof nodes.ArrNode) return this.cArr(arr, node);
55-
else if (node instanceof nodes.StrNode) return this.cStr(arr, node);
56-
else if (node instanceof nodes.ValNode) return this.cVal(arr, node);
57-
else if (node instanceof nodes.VecNode) return this.cVec(arr, node);
58-
else if (node instanceof nodes.ConNode) return this.cCon(arr, node);
59-
else if (node instanceof nodes.BinNode) return this.cBin(arr, node);
46+
if (node instanceof nodes.ObjNode) return this.cObj(node);
47+
else if (node instanceof nodes.ArrNode) return this.cArr(node);
48+
else if (node instanceof nodes.StrNode) return this.cStr(node);
49+
else if (node instanceof nodes.ValNode) return this.cVal(node);
50+
else if (node instanceof nodes.VecNode) return this.cVec(node);
51+
else if (node instanceof nodes.ConNode) return this.cCon(node);
52+
else if (node instanceof nodes.BinNode) return this.cBin(node);
6053
throw new Error('UNKNOWN_NODE');
6154
}
6255

63-
protected cObj(arr: unknown[], obj: nodes.ObjNode): void {
64-
const res: unknown[] = [JsonCrdtDataType.obj];
65-
arr.push(res);
66-
this.ts(res, obj.id);
67-
obj.nodes((node, key) => {
68-
res.push(key);
69-
this.cNode(res, node);
70-
});
56+
protected cObj(obj: nodes.ObjNode): t.JsonCrdtCompactObj {
57+
const map: t.JsonCrdtCompactObj[2] = {};
58+
obj.nodes((child, key) => map[key] = this.cNode(child));
59+
const res: t.JsonCrdtCompactObj = [JsonCrdtDataType.obj, this.ts(obj.id), map];
60+
return res;
7161
}
7262

73-
protected cVec(arr: unknown[], obj: nodes.VecNode): void {
74-
const res: unknown[] = [JsonCrdtDataType.vec];
75-
arr.push(res);
76-
this.ts(res, obj.id);
77-
const elements = obj.elements;
63+
protected cVec(vec: nodes.VecNode): t.JsonCrdtCompactVec {
64+
const elements = vec.elements;
7865
const length = elements.length;
7966
const index = this.model.index;
67+
const map: t.JsonCrdtCompactVec[2] = [];
8068
for (let i = 0; i < length; i++) {
8169
const elementId = elements[i];
82-
if (!elementId) res.push(0);
70+
if (!elementId) map.push(0);
8371
else {
8472
const node = index.get(elementId)!;
85-
this.cNode(res, node);
73+
map.push(this.cNode(node));
8674
}
8775
}
76+
const res: t.JsonCrdtCompactVec = [JsonCrdtDataType.vec, this.ts(vec.id), map];
77+
return res;
8878
}
8979

90-
protected cArr(arr: unknown[], obj: nodes.ArrNode): void {
91-
const res: unknown[] = [JsonCrdtDataType.arr, obj.size()];
92-
arr.push(res);
93-
this.ts(res, obj.id);
94-
const iterator = obj.iterator();
95-
let chunk;
96-
while ((chunk = iterator())) this.cArrChunk(res, chunk);
97-
}
98-
99-
protected cArrChunk(arr: unknown[], chunk: nodes.ArrChunk): void {
100-
this.ts(arr, chunk.id);
101-
if (chunk.del) arr.push(chunk.span);
102-
else {
103-
const nodes: unknown[] = [];
104-
const index = this.model.index;
105-
for (const n of chunk.data!) this.cNode(nodes, index.get(n)!);
106-
arr.push(nodes);
80+
protected cArr(node: nodes.ArrNode): t.JsonCrdtCompactArr {
81+
const chunks: t.JsonCrdtCompactArr[2] = [];
82+
const index = this.model.index;
83+
for (let chunk = node.first(); chunk; chunk = node.next(chunk)) {
84+
const deleted = chunk.del;
85+
const span = chunk.span;
86+
const chunkIdEncoded = this.ts(chunk.id);
87+
if (deleted) {
88+
chunks.push([chunkIdEncoded, span]);
89+
} else {
90+
const nodeIds = chunk.data!;
91+
const nodes: t.JsonCrdtCompactArrChunk[1] = [];
92+
for (let i = 0; i < span; i++) nodes.push(this.cNode(index.get(nodeIds[i])!));
93+
chunks.push([chunkIdEncoded, nodes]);
94+
}
10795
}
96+
const res: t.JsonCrdtCompactArr = [JsonCrdtDataType.arr, this.ts(node.id), chunks];
97+
return res;
10898
}
10999

110-
protected cStr(arr: unknown[], obj: nodes.StrNode): void {
111-
const res: unknown[] = [JsonCrdtDataType.str, obj.size()];
112-
arr.push(res);
113-
this.ts(res, obj.id);
114-
const iterator = obj.iterator();
115-
let chunk;
116-
while ((chunk = iterator())) this.cStrChunk(res, chunk as nodes.StrChunk);
117-
}
118-
119-
protected cStrChunk(arr: unknown[], chunk: nodes.StrChunk): void {
120-
this.ts(arr, chunk.id);
121-
arr.push(chunk.del ? chunk.span : chunk.data!);
100+
protected cStr(node: nodes.StrNode): t.JsonCrdtCompactStr {
101+
const chunks: t.JsonCrdtCompactStr[2] = [];
102+
for (let chunk = node.first(); chunk; chunk = node.next(chunk))
103+
chunks.push([this.ts(chunk.id), chunk.del ? chunk.span : chunk.data!] as t.JsonCrdtCompactStrChunk | t.JsonCrdtCompactTombstone);
104+
const res: t.JsonCrdtCompactStr = [JsonCrdtDataType.str, this.ts(node.id), chunks];
105+
return res;
122106
}
123107

124-
protected cBin(arr: unknown[], obj: nodes.BinNode): void {
125-
const res: unknown[] = [JsonCrdtDataType.bin, obj.size()];
126-
arr.push(res);
127-
this.ts(res, obj.id);
128-
const iterator = obj.iterator();
129-
let chunk;
130-
while ((chunk = iterator())) this.cBinChunk(res, chunk as nodes.BinChunk);
108+
protected cBin(node: nodes.BinNode): t.JsonCrdtCompactBin {
109+
const chunks: t.JsonCrdtCompactBin[2] = [];
110+
for (let chunk = node.first(); chunk; chunk = node.next(chunk))
111+
chunks.push([this.ts(chunk.id), chunk.del ? chunk.span : chunk.data!] as t.JsonCrdtCompactBinChunk | t.JsonCrdtCompactTombstone);
112+
const res: t.JsonCrdtCompactBin = [JsonCrdtDataType.bin, this.ts(node.id), chunks];
113+
return res;
131114
}
132115

133-
protected cBinChunk(arr: unknown[], chunk: nodes.BinChunk): void {
134-
this.ts(arr, chunk.id);
135-
arr.push(chunk.del ? chunk.span : chunk.data!);
116+
protected cVal(node: nodes.ValNode): t.JsonCrdtCompactVal {
117+
const res: t.JsonCrdtCompactVal = [JsonCrdtDataType.val, this.ts(node.id), this.cNode(node.node())];
118+
return res;
136119
}
137120

138-
protected cVal(arr: unknown[], obj: nodes.ValNode): void {
139-
const res: unknown[] = [JsonCrdtDataType.val];
140-
arr.push(res);
141-
this.ts(res, obj.id);
142-
this.cNode(res, obj.node());
143-
}
144-
145-
protected cCon(arr: unknown[], obj: nodes.ConNode): void {
146-
const val = obj.val;
147-
const res: unknown[] = [];
121+
protected cCon(node: nodes.ConNode): t.JsonCrdtCompactCon {
122+
const val = node.val;
123+
const res: t.JsonCrdtCompactCon = [JsonCrdtDataType.con, this.ts(node.id), val];
148124
if (val instanceof Timestamp) {
149-
res.push(JsonCrdtDataType.con + 10);
150-
this.ts(res, obj.id);
151-
this.ts(res, val);
152-
} else {
153-
res.push(JsonCrdtDataType.con);
154-
this.ts(res, obj.id);
155-
if (val !== undefined) res.push(val);
125+
res[2] = 0;
126+
const specialData = this.ts(val);
127+
res.push(specialData);
128+
} else if (val === undefined) {
129+
res[2] = 0;
130+
res.push(0);
156131
}
157-
arr.push(res);
132+
return res;
158133
}
159134
}

‎src/json-crdt/codec/structural/compact/types.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export type JsonCrdtCompactArr = [
8484

8585
export type JsonCrdtCompactArrChunk = [
8686
id: JsonCrdtCompactTimestamp,
87-
data: JsonCrdtCompactNode,
87+
data: JsonCrdtCompactNode[],
8888
];
8989

9090
export type JsonCrdtCompactTombstone = [

0 commit comments

Comments
 (0)