UNPKG

json-joy

Version:

Collection of libraries for building collaborative editing apps.

178 lines (177 loc) 6.6 kB
import { ClockDecoder } from '../../../../json-crdt-patch/codec/clock/ClockDecoder'; import { CrdtReader } from '../../../../json-crdt-patch/util/binary/CrdtReader'; import { Model, UNDEFINED } from '../../../model/Model'; import { CborDecoderBase } from '@jsonjoy.com/json-pack/lib/cbor/CborDecoderBase'; import * as nodes from '../../../nodes'; import { CRDT_MAJOR } from '../../structural/binary/constants'; import { sort } from '@jsonjoy.com/util/lib/sort/insertion'; import { SESSION } from '../../../../json-crdt-patch/constants'; export class Decoder { doc; clockDecoder = undefined; time = -1; decoder = new CborDecoderBase(new CrdtReader()); decode(view, meta) { this.clockDecoder = undefined; this.time = -1; this.decoder.reader.reset(meta); this.decodeClockTable(); const clock = this.clockDecoder.clock; this.doc = Model.withLogicalClock(clock); this.doc.root = new nodes.RootNode(this.doc, this.cRoot(view).id); this.clockDecoder = undefined; return this.doc; } decodeClockTable() { const reader = this.decoder.reader; const clockTableOffset = reader.u32(); const offset = reader.x; reader.x += clockTableOffset; const length = reader.vu57(); const sessionId = reader.vu57(); const time = reader.vu57(); this.clockDecoder = new ClockDecoder(sessionId, time); for (let i = 1; i < length; i++) { const sid = reader.vu57(); const time = reader.vu57(); this.clockDecoder.pushTuple(sid, time); } reader.x = offset; } ts() { const decoderTime = this.time; const [sessionIndex, timeDiff] = this.decoder.reader.id(); return this.clockDecoder.decodeId(sessionIndex, timeDiff); } cRoot(view) { const reader = this.decoder.reader; const peek = reader.uint8[reader.x]; return !peek ? UNDEFINED : this.cNode(view); } cNode(view) { const reader = this.decoder.reader; const id = this.ts(); 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.cCon(view, id, length); case CRDT_MAJOR.VAL: return this.cVal(view, id); case CRDT_MAJOR.OBJ: return this.cObj(view, id, length); case CRDT_MAJOR.VEC: return this.cVec(view, id, length); case CRDT_MAJOR.STR: return this.cStr(view, id, length); case CRDT_MAJOR.BIN: return this.cBin(view, id, length); case CRDT_MAJOR.ARR: return this.cArr(view, id, length); } throw new Error('UNKNOWN_NODE'); } cCon(view, id, length) { const doc = this.doc; const node = new nodes.ConNode(id, length ? this.ts() : view); doc.index.set(id, node); return node; } cVal(view, id) { const child = this.cNode(view); const doc = this.doc; const node = new nodes.ValNode(doc, id, child.id); doc.index.set(id, node); return node; } cObj(view, id, length) { const obj = new nodes.ObjNode(this.doc, id); if (!view || typeof view !== 'object') throw new Error('INVALID_OBJ'); const keys = sort(Object.keys(view)); if (keys.length !== length) throw new Error('INVALID_OBJ'); const objKeys = obj.keys; for (let i = 0; i < length; i++) { const key = keys[i]; const childNode = this.cNode(view[key]); objKeys.set(key, childNode.id); } this.doc.index.set(id, obj); return obj; } cVec(view, id, length) { const obj = new nodes.VecNode(this.doc, id); if (!Array.isArray(view) || view.length !== length) throw new Error('INVALID_VEC'); const elements = obj.elements; const reader = this.decoder.reader; for (let i = 0; i < length; i++) { const child = this.cNode(view[i]); const childId = child.id; if (childId.sid === SESSION.SYSTEM) elements.push(undefined); else elements.push(childId); } this.doc.index.set(id, obj); return obj; } cStr(view, id, length) { if (typeof view !== 'string') throw new Error('INVALID_STR'); const node = new nodes.StrNode(id); const reader = this.decoder.reader; let offset = 0; node.ingest(length, () => { const id = this.ts(); const [deleted, span] = reader.b1vu56(); if (deleted) return new nodes.StrChunk(id, span, ''); const text = view.slice(offset, offset + span); offset += span; return new nodes.StrChunk(id, text.length, text); }); this.doc.index.set(id, node); return node; } cBin(view, id, length) { if (!(view instanceof Uint8Array)) throw new Error('INVALID_BIN'); const node = new nodes.BinNode(id); const reader = this.decoder.reader; let offset = 0; node.ingest(length, () => { const id = this.ts(); const [deleted, span] = reader.b1vu56(); if (deleted) return new nodes.BinChunk(id, span, undefined); const slice = view.slice(offset, offset + span); offset += span; return new nodes.BinChunk(id, slice.length, slice); }); this.doc.index.set(id, node); return node; } cArr(view, id, length) { if (!Array.isArray(view)) throw new Error('INVALID_ARR'); const obj = new nodes.ArrNode(this.doc, id); const reader = this.decoder.reader; let i = 0; obj.ingest(length, () => { const id = this.ts(); const [deleted, span] = reader.b1vu56(); if (deleted) return new nodes.ArrChunk(id, span, undefined); const ids = []; for (let j = 0; j < span; j++) ids.push(this.cNode(view[i++]).id); return new nodes.ArrChunk(id, span, ids); }); this.doc.index.set(id, obj); return obj; } }