Skip to content

Commit 42292a4

Browse files
committed
feat(json-crdt): 🎸 add compact json decoder according to specification
1 parent e0b7ad1 commit 42292a4

File tree

4 files changed

+107
-130
lines changed

4 files changed

+107
-130
lines changed

src/json-crdt/codec/structural/compact-binary/Decoder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ export class Decoder {
66
protected decoder = new CompactDecoder();
77

88
public decode(uint8: Uint8Array): Model {
9-
const json = decoder.decode(uint8);
10-
const doc = this.decoder.decode(json as unknown[]);
9+
const json = decoder.read(uint8);
10+
const doc = this.decoder.decode(json as any);
1111
return doc;
1212
}
1313
}

src/json-crdt/codec/structural/compact/Decoder.ts

Lines changed: 101 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -3,174 +3,151 @@ import {ClockDecoder} from '../../../../json-crdt-patch/codec/clock/ClockDecoder
33
import {ITimestampStruct, Timestamp} from '../../../../json-crdt-patch/clock';
44
import {Model, UNDEFINED} from '../../../model/Model';
55
import {JsonCrdtDataType, ORIGIN, SESSION} from '../../../../json-crdt-patch/constants';
6+
import type * as t from './types';
67

78
export class Decoder {
89
protected time?: number;
910
protected clockDecoder?: ClockDecoder;
1011

11-
public decode(data: unknown[]): Model {
12-
const x = data[0];
13-
const isServerTime = typeof x === 'number';
12+
public decode(doc: t.JsonCrdtCompactDocument): Model {
13+
const [time, root] = doc;
14+
const isServerTime = typeof time === 'number';
1415
if (isServerTime) {
15-
this.time = x;
16+
this.time = time;
1617
} else {
17-
this.clockDecoder = ClockDecoder.fromArr(x as number[]);
18+
this.clockDecoder = ClockDecoder.fromArr(time as number[]);
1819
}
19-
const doc = isServerTime ? Model.withServerClock(x as number) : Model.withLogicalClock(this.clockDecoder!.clock);
20-
const val = data[1] ? this.decodeNode(doc, data[1]) : UNDEFINED;
21-
doc.root = new nodes.RootNode(doc, val.id);
22-
return doc;
20+
const model = isServerTime ? Model.withServerClock(time as number) : Model.withLogicalClock(this.clockDecoder!.clock);
21+
const val = root ? this.decNode(model, root) : UNDEFINED;
22+
model.root = new nodes.RootNode(model, val.id);
23+
return model;
2324
}
2425

25-
protected ts(arr: unknown[], index: number): [ITimestampStruct, number] {
26-
const x = arr[index];
26+
protected ts(x: t.JsonCrdtCompactTimestamp): ITimestampStruct {
2727
if (typeof x === 'number') {
28-
if (x < 0) {
29-
const sessionIndex = -x;
30-
const timeDiff = arr[index + 1] as number;
31-
return [this.clockDecoder!.decodeId(sessionIndex, timeDiff), index + 2];
32-
} else {
33-
return [new Timestamp(SESSION.SERVER, this.time! - x), index + 1];
34-
}
28+
return new Timestamp(SESSION.SERVER, this.time! - x);
3529
} else {
36-
const time = (x as [number])[0];
37-
switch (time) {
38-
case ORIGIN.time:
39-
return [ORIGIN, index + 1];
40-
default:
41-
return [new Timestamp(SESSION.SYSTEM, time), index + 1];
30+
const [sid, time] = x as [number, number];
31+
if (sid < 0) {
32+
return this.clockDecoder!.decodeId(-sid, time);
33+
} else {
34+
return new Timestamp(sid, time);
4235
}
4336
}
4437
}
4538

46-
protected decodeNode(doc: Model, data: unknown): nodes.JsonNode {
47-
if (data instanceof Array) {
48-
switch (data[0]) {
49-
case JsonCrdtDataType.con:
50-
return this.decCon(doc, data);
51-
case JsonCrdtDataType.con + 10:
52-
return this.decConId(doc, data);
53-
case JsonCrdtDataType.val:
54-
return this.decVal(doc, data);
55-
case JsonCrdtDataType.obj:
56-
return this.decObj(doc, data);
57-
case JsonCrdtDataType.vec:
58-
return this.decVec(doc, data);
59-
case JsonCrdtDataType.str:
60-
return this.decStr(doc, data);
61-
case JsonCrdtDataType.bin:
62-
return this.decBin(doc, data);
63-
case JsonCrdtDataType.arr:
64-
return this.decArr(doc, data);
65-
}
39+
protected decNode(model: Model, node: t.JsonCrdtCompactNode): nodes.JsonNode {
40+
switch (node[0]) {
41+
case JsonCrdtDataType.con: return this.decCon(model, node);
42+
case JsonCrdtDataType.val: return this.decVal(model, node);
43+
case JsonCrdtDataType.obj: return this.decObj(model, node);
44+
case JsonCrdtDataType.vec: return this.decVec(model, node);
45+
case JsonCrdtDataType.str: return this.decStr(model, node);
46+
case JsonCrdtDataType.bin: return this.decBin(model, node);
47+
case JsonCrdtDataType.arr: return this.decArr(model, node);
6648
}
6749
throw new Error('UNKNOWN_NODE');
6850
}
6951

70-
protected decObj(doc: Model, data: unknown[]): nodes.ObjNode {
71-
const [id, index] = this.ts(data, 1);
72-
const obj = new nodes.ObjNode(doc, id);
73-
const length = data.length;
74-
for (let i = index; i < length; ) {
75-
const key = data[i] as string;
76-
const val = this.decodeNode(doc, data[++i]);
77-
obj.put(key, val.id);
78-
i++;
52+
protected decCon(doc: Model, node: t.JsonCrdtCompactCon): nodes.ConNode {
53+
const id = this.ts(node[1]);
54+
let data: unknown | undefined | Timestamp = node[2];
55+
if (node.length > 3) {
56+
const specialData = node[3] as unknown;
57+
if (!specialData) data = undefined;
58+
else data = this.ts(specialData as t.JsonCrdtCompactTimestamp);
7959
}
60+
const obj = new nodes.ConNode(id, data);
61+
doc.index.set(id, obj);
62+
return obj;
63+
}
64+
65+
protected decVal(doc: Model, node: t.JsonCrdtCompactVal): nodes.ValNode {
66+
const id = this.ts(node[1]);
67+
const child = this.decNode(doc, node[2]);
68+
const obj = new nodes.ValNode(doc, id, child.id);
8069
doc.index.set(id, obj);
8170
return obj;
8271
}
8372

84-
protected decVec(doc: Model, data: unknown[]): nodes.VecNode {
85-
const [id, index] = this.ts(data, 1);
86-
const obj = new nodes.VecNode(doc, id);
87-
const length = data.length;
73+
protected decObj(model: Model, node: t.JsonCrdtCompactObj): nodes.ObjNode {
74+
const id = this.ts(node[1]);
75+
const obj = new nodes.ObjNode(model, id);
76+
const map = node[2] as t.JsonCrdtCompactObj[2];
77+
for (const key in map) {
78+
const val = this.decNode(model, map[key]);
79+
obj.put(key, val.id);
80+
}
81+
model.index.set(id, obj);
82+
return obj;
83+
}
84+
85+
protected decVec(model: Model, node: t.JsonCrdtCompactVec): nodes.VecNode {
86+
const id = this.ts(node[1]);
87+
const obj = new nodes.VecNode(model, id);
88+
const map = node[2] as t.JsonCrdtCompactVec[2];
8889
const elements = obj.elements;
89-
for (let i = index; i < length; ) {
90-
const component = data[i++];
91-
if (!component) elements.push(undefined);
90+
const length = map.length;
91+
for (let i = 0; i < length; i++) {
92+
const item = map[i];
93+
if (!item) elements.push(undefined);
9294
else {
93-
const node = this.decodeNode(doc, component);
94-
elements.push(node.id);
95+
const child = this.decNode(model, item);
96+
elements.push(child.id);
9597
}
9698
}
97-
doc.index.set(id, obj);
99+
model.index.set(id, obj);
98100
return obj;
99101
}
100102

101-
protected decArr(doc: Model, data: unknown[]): nodes.ArrNode {
102-
const size = data[1] as number;
103-
const [id, index] = this.ts(data, 2);
104-
const obj = new nodes.ArrNode(doc, id);
105-
const self = this;
106-
let i = index;
103+
protected decStr(doc: Model, node: t.JsonCrdtCompactStr): nodes.StrNode {
104+
const id = this.ts(node[1]);
105+
const obj = new nodes.StrNode(id);
106+
const chunks = node[2] as t.JsonCrdtCompactStr[2];
107+
const size = chunks.length;
108+
let i = 0;
107109
obj.ingest(size, () => {
108-
const [chunkId, idx] = self.ts(data, i);
109-
const content = data[idx];
110-
i = idx + 1;
111-
if (typeof content === 'number') return new nodes.ArrChunk(chunkId, content, undefined);
112-
const ids = (content as unknown[]).map((c) => this.decodeNode(doc, c).id);
113-
return new nodes.ArrChunk(chunkId, (content as string).length, ids);
114-
});
115-
doc.index.set(id, obj);
116-
return obj;
117-
}
118-
119-
protected decStr(doc: Model, data: unknown[]): nodes.StrNode {
120-
const size = data[1] as number;
121-
const [id, index] = this.ts(data, 2);
122-
const node = new nodes.StrNode(id);
123-
const self = this;
124-
let i = index;
125-
node.ingest(size, () => {
126-
const [chunkId, idx] = self.ts(data, i);
127-
const content = data[idx];
128-
i = idx + 1;
110+
const chunk = chunks[i];
111+
const chunkId = this.ts(chunk[0]);
112+
const content = chunk[1];
129113
if (typeof content === 'number') return new nodes.StrChunk(chunkId, content, '');
130114
return new nodes.StrChunk(chunkId, (content as string).length, content as string);
131115
});
132-
doc.index.set(id, node);
133-
return node;
134-
}
135-
136-
protected decBin(doc: Model, data: unknown[]): nodes.BinNode {
137-
const size = data[1] as number;
138-
const [id, index] = this.ts(data, 2);
139-
const node = new nodes.BinNode(id);
140-
const self = this;
141-
let i = index;
142-
node.ingest(size, () => {
143-
const [chunkId, idx] = self.ts(data, i);
144-
const content = data[idx];
145-
i = idx + 1;
146-
if (typeof content === 'number') return new nodes.BinChunk(chunkId, content, undefined);
147-
const buf = content as Uint8Array;
148-
return new nodes.BinChunk(chunkId, buf.length, buf);
149-
});
150-
doc.index.set(id, node);
151-
return node;
152-
}
153-
154-
protected decVal(doc: Model, data: unknown[]): nodes.ValNode {
155-
const [id, index] = this.ts(data, 1);
156-
const child = this.decodeNode(doc, data[index]);
157-
const obj = new nodes.ValNode(doc, id, child.id);
158116
doc.index.set(id, obj);
159117
return obj;
160118
}
161119

162-
protected decCon(doc: Model, data: unknown[]): nodes.ConNode {
163-
const [id, index] = this.ts(data, 1);
164-
const value = data[index];
165-
const obj = new nodes.ConNode(id, value);
120+
protected decBin(doc: Model, node: t.JsonCrdtCompactBin): nodes.BinNode {
121+
const id = this.ts(node[1]);
122+
const obj = new nodes.BinNode(id);
123+
const chunks = node[2] as t.JsonCrdtCompactBin[2];
124+
const size = chunks.length;
125+
let i = 0;
126+
obj.ingest(size, () => {
127+
const chunk = chunks[i];
128+
const chunkId = this.ts(chunk[0]);
129+
const content = chunk[1];
130+
if (typeof content === 'number') return new nodes.BinChunk(chunkId, content, undefined);
131+
return new nodes.BinChunk(chunkId, content.length, content);
132+
});
166133
doc.index.set(id, obj);
167134
return obj;
168135
}
169136

170-
protected decConId(doc: Model, data: unknown[]): nodes.ConNode {
171-
const [id, index] = this.ts(data, 1);
172-
const val = this.ts(data, index)[0];
173-
const obj = new nodes.ConNode(id, val);
137+
protected decArr(doc: Model, node: t.JsonCrdtCompactArr): nodes.ArrNode {
138+
const id = this.ts(node[1]);
139+
const obj = new nodes.ArrNode(doc, id);
140+
const chunks = node[2] as t.JsonCrdtCompactArr[2];
141+
const size = chunks.length;
142+
let i = 0;
143+
obj.ingest(size, () => {
144+
const chunk = chunks[i];
145+
const chunkId = this.ts(chunk[0]);
146+
const content = chunk[1];
147+
if (typeof content === 'number') return new nodes.ArrChunk(chunkId, content, undefined);
148+
const ids = (content as t.JsonCrdtCompactNode[]).map((c) => this.decNode(doc, c).id);
149+
return new nodes.ArrChunk(chunkId, content.length, ids);
150+
});
174151
doc.index.set(id, obj);
175152
return obj;
176153
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export class Encoder {
1111
protected clock?: ClockEncoder;
1212
protected model!: Model;
1313

14-
public encode(model: Model): unknown[] {
14+
public encode(model: Model): t.JsonCrdtCompactDocument {
1515
this.model = model;
1616
const isServerTime = model.clock.sid === SESSION.SERVER;
1717
const clock = model.clock;
@@ -26,7 +26,7 @@ export class Encoder {
2626
0,
2727
!root.val.time ? 0 : this.cNode(root.node()),
2828
];
29-
if (!isServerTime) doc[0] = this.clock!.toJson();
29+
doc[0] = isServerTime ? this.time! : this.clock!.toJson();
3030
return doc;
3131
}
3232

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import {JsonCrdtDataType} from '../../../../json-crdt-patch/constants';
1+
import type {JsonCrdtDataType} from '../../../../json-crdt-patch/constants';
22

33
export type JsonCrdtCompactDocument = [
4-
time: JsonCrdtCompactClockTable | 0,
4+
time: JsonCrdtCompactClockTable | number,
55
root: JsonCrdtCompactNode | 0,
66
];
77

0 commit comments

Comments
 (0)