Skip to content

Commit 0416f76

Browse files
authored
Merge pull request #377 from streamich/crdt-compact-encoding
JSON CRDT compact encoding format
2 parents 7363767 + 66da4b9 commit 0416f76

File tree

6 files changed

+259
-262
lines changed

6 files changed

+259
-262
lines changed

src/json-crdt-patch/codec/compact/constants.ts

Lines changed: 0 additions & 32 deletions
This file was deleted.

src/json-crdt/__tests__/fuzzer/Picker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export class Picker {
4848
if (Math.random() > 0.45) return [this.generateObjectKey(), InsObjOp];
4949
const keys = [...node.keys.keys()];
5050
if (!keys.length) return [this.generateObjectKey(), InsObjOp];
51-
const key = keys[Math.floor(Math.random() * keys.length)];
51+
const key = Fuzzer.pick(keys);
5252
return [key, DelOp];
5353
}
5454

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: 114 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -2,176 +2,164 @@ import * as nodes from '../../../nodes';
22
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';
5-
import {ORIGIN, SESSION} from '../../../../json-crdt-patch/constants';
6-
import {Code} from '../../../../json-crdt-patch/codec/compact/constants';
5+
import {JsonCrdtDataType, SESSION} from '../../../../json-crdt-patch/constants';
6+
import type * as t from './types';
77

88
export class Decoder {
99
protected time?: number;
1010
protected clockDecoder?: ClockDecoder;
1111

12-
public decode(data: unknown[]): Model {
13-
const x = data[0];
14-
const isServerTime = typeof x === 'number';
12+
public decode(doc: t.JsonCrdtCompactDocument): Model {
13+
const [time, root] = doc;
14+
const isServerTime = typeof time === 'number';
1515
if (isServerTime) {
16-
this.time = x;
16+
this.time = time;
1717
} else {
18-
this.clockDecoder = ClockDecoder.fromArr(x as number[]);
18+
this.clockDecoder = ClockDecoder.fromArr(time as number[]);
1919
}
20-
const doc = isServerTime ? Model.withServerClock(x as number) : Model.withLogicalClock(this.clockDecoder!.clock);
21-
const val = data[1] ? this.decodeNode(doc, data[1]) : UNDEFINED;
22-
doc.root = new nodes.RootNode(doc, val.id);
23-
return doc;
20+
const model = isServerTime
21+
? Model.withServerClock(time as number)
22+
: Model.withLogicalClock(this.clockDecoder!.clock);
23+
const val = root ? this.decNode(model, root) : UNDEFINED;
24+
model.root = new nodes.RootNode(model, val.id);
25+
return model;
2426
}
2527

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

47-
protected decodeNode(doc: Model, data: unknown): nodes.JsonNode {
48-
if (data instanceof Array) {
49-
switch (data[0]) {
50-
case Code.MakeObject:
51-
return this.decodeObj(doc, data);
52-
case Code.MakeArray:
53-
return this.decodeArr(doc, data);
54-
case Code.MakeString:
55-
return this.decodeStr(doc, data);
56-
case Code.MakeValue:
57-
return this.decodeVal(doc, data);
58-
case Code.MakeConst:
59-
return this.decodeConst(doc, data);
60-
case Code.MakeConstId:
61-
return this.decodeConstId(doc, data);
62-
case Code.MakeBinary:
63-
return this.decodeBin(doc, data);
64-
case Code.MakeTuple:
65-
return this.decodeTup(doc, data);
66-
}
41+
protected decNode(model: Model, node: t.JsonCrdtCompactNode): nodes.JsonNode {
42+
switch (node[0]) {
43+
case JsonCrdtDataType.con:
44+
return this.decCon(model, node);
45+
case JsonCrdtDataType.val:
46+
return this.decVal(model, node);
47+
case JsonCrdtDataType.obj:
48+
return this.decObj(model, node);
49+
case JsonCrdtDataType.vec:
50+
return this.decVec(model, node);
51+
case JsonCrdtDataType.str:
52+
return this.decStr(model, node);
53+
case JsonCrdtDataType.bin:
54+
return this.decBin(model, node);
55+
case JsonCrdtDataType.arr:
56+
return this.decArr(model, node);
6757
}
6858
throw new Error('UNKNOWN_NODE');
6959
}
7060

71-
protected decodeObj(doc: Model, data: unknown[]): nodes.ObjNode {
72-
const [id, index] = this.ts(data, 1);
73-
const obj = new nodes.ObjNode(doc, id);
74-
const length = data.length;
75-
for (let i = index; i < length; ) {
76-
const key = data[i] as string;
77-
const val = this.decodeNode(doc, data[++i]);
78-
obj.put(key, val.id);
79-
i++;
61+
protected decCon(doc: Model, node: t.JsonCrdtCompactCon): nodes.ConNode {
62+
const id = this.ts(node[1]);
63+
let data: unknown | undefined | Timestamp = node[2];
64+
if (node.length > 3) {
65+
const specialData = node[3] as unknown;
66+
if (!specialData) data = undefined;
67+
else data = this.ts(specialData as t.JsonCrdtCompactTimestamp);
8068
}
69+
const obj = new nodes.ConNode(id, data);
70+
doc.index.set(id, obj);
71+
return obj;
72+
}
73+
74+
protected decVal(doc: Model, node: t.JsonCrdtCompactVal): nodes.ValNode {
75+
const id = this.ts(node[1]);
76+
const child = this.decNode(doc, node[2]);
77+
const obj = new nodes.ValNode(doc, id, child.id);
8178
doc.index.set(id, obj);
8279
return obj;
8380
}
8481

85-
protected decodeTup(doc: Model, data: unknown[]): nodes.VecNode {
86-
const [id, index] = this.ts(data, 1);
87-
const obj = new nodes.VecNode(doc, id);
88-
const length = data.length;
82+
protected decObj(model: Model, node: t.JsonCrdtCompactObj): nodes.ObjNode {
83+
const id = this.ts(node[1]);
84+
const obj = new nodes.ObjNode(model, id);
85+
const map = node[2] as t.JsonCrdtCompactObj[2];
86+
const keys = Object.keys(map);
87+
const length = keys.length;
88+
for (let i = 0; i < length; i++) {
89+
const key = keys[i];
90+
const val = this.decNode(model, map[key]);
91+
obj.put(key, val.id);
92+
}
93+
model.index.set(id, obj);
94+
return obj;
95+
}
96+
97+
protected decVec(model: Model, node: t.JsonCrdtCompactVec): nodes.VecNode {
98+
const id = this.ts(node[1]);
99+
const obj = new nodes.VecNode(model, id);
100+
const map = node[2] as t.JsonCrdtCompactVec[2];
89101
const elements = obj.elements;
90-
for (let i = index; i < length; ) {
91-
const component = data[i++];
92-
if (!component) elements.push(undefined);
102+
const length = map.length;
103+
for (let i = 0; i < length; i++) {
104+
const item = map[i];
105+
if (!item) elements.push(undefined);
93106
else {
94-
const node = this.decodeNode(doc, component);
95-
elements.push(node.id);
107+
const child = this.decNode(model, item);
108+
elements.push(child.id);
96109
}
97110
}
98-
doc.index.set(id, obj);
111+
model.index.set(id, obj);
99112
return obj;
100113
}
101114

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

163-
protected decodeConst(doc: Model, data: unknown[]): nodes.ConNode {
164-
const [id, index] = this.ts(data, 1);
165-
const value = data[index];
166-
const obj = new nodes.ConNode(id, value);
132+
protected decBin(doc: Model, node: t.JsonCrdtCompactBin): nodes.BinNode {
133+
const id = this.ts(node[1]);
134+
const obj = new nodes.BinNode(id);
135+
const chunks = node[2] as t.JsonCrdtCompactBin[2];
136+
const size = chunks.length;
137+
let i = 0;
138+
obj.ingest(size, () => {
139+
const chunk = chunks[i++];
140+
const chunkId = this.ts(chunk[0]);
141+
const content = chunk[1];
142+
if (typeof content === 'number') return new nodes.BinChunk(chunkId, content, undefined);
143+
return new nodes.BinChunk(chunkId, content.length, content);
144+
});
167145
doc.index.set(id, obj);
168146
return obj;
169147
}
170148

171-
protected decodeConstId(doc: Model, data: unknown[]): nodes.ConNode {
172-
const [id, index] = this.ts(data, 1);
173-
const val = this.ts(data, index)[0];
174-
const obj = new nodes.ConNode(id, val);
149+
protected decArr(doc: Model, node: t.JsonCrdtCompactArr): nodes.ArrNode {
150+
const id = this.ts(node[1]);
151+
const obj = new nodes.ArrNode(doc, id);
152+
const chunks = node[2] as t.JsonCrdtCompactArr[2];
153+
const size = chunks.length;
154+
let i = 0;
155+
obj.ingest(size, () => {
156+
const chunk = chunks[i++];
157+
const chunkId = this.ts(chunk[0]);
158+
const content = chunk[1];
159+
if (typeof content === 'number') return new nodes.ArrChunk(chunkId, content, undefined);
160+
const ids = (content as t.JsonCrdtCompactNode[]).map((c) => this.decNode(doc, c).id);
161+
return new nodes.ArrChunk(chunkId, content.length, ids);
162+
});
175163
doc.index.set(id, obj);
176164
return obj;
177165
}

0 commit comments

Comments
 (0)