json-joy
Version:
Collection of libraries for building collaborative editing apps.
156 lines (155 loc) • 5.6 kB
JavaScript
import * as nodes from '../../../nodes';
import { ClockTable } from '../../../../json-crdt-patch/codec/clock/ClockTable';
import { CrdtReader } from '../../../../json-crdt-patch/util/binary/CrdtReader';
import { Timestamp, ClockVector } from '../../../../json-crdt-patch/clock';
import { Model, UNDEFINED } from '../../../model/Model';
import { CborDecoderBase } from '@jsonjoy.com/json-pack/lib/cbor/CborDecoderBase';
import { CRDT_MAJOR } from '../../structural/binary/constants';
export class Decoder {
dec;
doc;
clockTable;
constructor(reader) {
this.dec = new CborDecoderBase(reader || new CrdtReader());
}
decode(fields, ModelConstructor = Model) {
const reader = this.dec.reader;
reader.reset(fields.c);
const clockTable = (this.clockTable = ClockTable.decode(reader));
return this.decodeFields(clockTable, fields, ModelConstructor);
}
decodeFields(clockTable, fields, ModelConstructor = Model) {
const reader = this.dec.reader;
const firstClock = clockTable.byIdx[0];
const vectorClock = new ClockVector(firstClock.sid, firstClock.time + 1);
const doc = (this.doc = new ModelConstructor(vectorClock));
const root = fields.r;
if (root && root.length) {
reader.reset(root);
const rootValue = this.ts();
doc.root.set(rootValue);
}
const index = doc.index;
const keys = Object.keys(fields);
const length = keys.length;
for (let i = 0; i < length; i++) {
const field = keys[i];
if (field.length < 3)
continue; // Skip "c" and "r".
const arr = fields[field];
const id = clockTable.parseField(field);
reader.reset(arr);
const node = this.decodeNode(id);
index.set(id, node);
}
return doc;
}
ts() {
const [sessionIndex, timeDiff] = this.dec.reader.id();
return new Timestamp(this.clockTable.byIdx[sessionIndex].sid, timeDiff);
}
decodeNode(id) {
const reader = this.dec.reader;
const octet = reader.u8();
const major = octet >> 5;
const minor = octet & 0b11111;
const length = minor < 24 ? minor : minor === 24 ? reader.u8() : minor === 25 ? reader.u16() : reader.u32();
switch (major) {
case CRDT_MAJOR.CON:
return this.decodeCon(id, length);
case CRDT_MAJOR.VAL:
return this.decodeVal(id);
case CRDT_MAJOR.OBJ:
return this.decodeObj(id, length);
case CRDT_MAJOR.VEC:
return this.decodeVec(id, length);
case CRDT_MAJOR.STR:
return this.decodeStr(id, length);
case CRDT_MAJOR.BIN:
return this.decodeBin(id, length);
case CRDT_MAJOR.ARR:
return this.decodeArr(id, length);
}
return UNDEFINED;
}
decodeCon(id, length) {
const decoder = this.dec;
const data = !length ? decoder.val() : this.ts();
const node = new nodes.ConNode(id, data);
return node;
}
decodeVal(id) {
const val = this.ts();
const node = new nodes.ValNode(this.doc, id, val);
return node;
}
decodeObj(id, length) {
const decoder = this.dec;
const obj = new nodes.ObjNode(this.doc, id);
const keys = obj.keys;
for (let i = 0; i < length; i++) {
const key = decoder.val() + '';
const val = this.ts();
keys.set(key, val);
}
return obj;
}
decodeVec(id, length) {
const reader = this.dec.reader;
const node = new nodes.VecNode(this.doc, id);
const elements = node.elements;
for (let i = 0; i < length; i++) {
const octet = reader.u8();
if (!octet)
elements.push(undefined);
else
elements.push(this.ts());
}
return node;
}
decodeStr(id, length) {
const node = new nodes.StrNode(id);
node.ingest(length, this.decodeStrChunk);
return node;
}
decodeStrChunk = () => {
const decoder = this.dec;
const reader = decoder.reader;
const id = this.ts();
const val = decoder.val();
if (typeof val === 'string')
return new nodes.StrChunk(id, val.length, val);
return new nodes.StrChunk(id, ~~val, '');
};
decodeBin(id, length) {
const node = new nodes.BinNode(id);
node.ingest(length, this.decodeBinChunk);
return node;
}
decodeBinChunk = () => {
const id = this.ts();
const reader = this.dec.reader;
const [deleted, length] = reader.b1vu56();
if (deleted)
return new nodes.BinChunk(id, length, undefined);
return new nodes.BinChunk(id, length, reader.buf(length));
};
decodeArr(id, length) {
const node = new nodes.ArrNode(this.doc, id);
node.ingest(length, this.decodeArrChunk);
return node;
}
decodeArrChunk = () => {
const id = this.ts();
const reader = this.dec.reader;
const [deleted, length] = reader.b1vu56();
if (deleted)
return new nodes.ArrChunk(id, length, undefined);
else {
const data = [];
for (let i = 0; i < length; i++)
data.push(this.ts());
return new nodes.ArrChunk(id, length, data);
}
};
}