UNPKG

json-joy

Version:

Collection of libraries for building collaborative editing apps.

155 lines (154 loc) 5.89 kB
import { Model } from '../../model'; import { Log } from '../Log'; import { Patch } from '../../../json-crdt-patch'; import { FileModelEncoding } from './constants'; import { SESSION } from '../../../json-crdt-patch/constants'; export class LogDecoder { opts; constructor(opts = {}) { this.opts = opts; } decode(blob, params = {}) { switch (params.format) { case 'ndjson': { const components = this.decodeNdjsonComponents(blob); const result = this.deserialize(components, params); return result; } default: { // 'seq.cbor' const components = this.decodeSeqCborComponents(blob); const result = this.deserialize(components, params); return result; } } } decodeNdjsonComponents(blob) { const decoder = this.opts.jsonDecoder; if (!decoder) throw new Error('NO_JSON_DECODER'); const reader = decoder.reader; reader.reset(blob); const components = []; while (reader.x < blob.length) { components.push(decoder.readAny()); const nl = reader.u8(); if (nl !== '\n'.charCodeAt(0)) throw new Error('NDJSON_UNEXPECTED_NEWLINE'); } return components; } decodeSeqCborComponents(blob) { const decoder = this.opts.cborDecoder; if (!decoder) throw new Error('NO_CBOR_DECODER'); const reader = decoder.reader; reader.reset(blob); const components = []; while (reader.x < blob.length) components.push(decoder.val()); return components; } deserialize(components, params = {}) { const [view, metadata, model, , ...frontier] = components; const result = {}; if (params.view) result.view = view; if (params.history) result.history = this.deserializeHistory(components); if (params.frontier) { if (!model) result.history = this.deserializeHistory(components); if (result.history) { result.frontier = result.history; } else if (model) { const modelFormat = metadata[1]; const start = () => { const isSidecar = modelFormat === FileModelEncoding.SidecarBinary; if (isSidecar) { const decoder = this.opts.sidecarDecoder; if (!decoder) throw new Error('NO_SIDECAR_DECODER'); if (!(model instanceof Uint8Array)) throw new Error('NOT_BLOB'); return decoder.decode(view, model); } return this.deserializeModel(model); }; const log = new Log(start); const end = log.end; if (frontier && frontier.length) for (const patch of frontier) end.applyPatch(this.deserializePatch(patch)); result.frontier = log; } else { throw new Error('NO_MODEL'); } } return result; } deserializeHistory(components) { const [, , , history, ...frontier] = components; const [startSerialized] = history; const start = () => { if (!history || !startSerialized) { // TODO: Handle case where new model should be started with server clock: `return Model.withServerClock()`. // TODO: Handle case where model has to be started with extensions... return Model.withLogicalClock(SESSION.GLOBAL); } return this.deserializeModel(startSerialized); }; const log = new Log(start); const end = log.end; if (history) { const [, patches] = history; if (patches) for (const patch of patches) end.applyPatch(this.deserializePatch(patch)); } if (frontier.length) for (const patch of frontier) end.applyPatch(this.deserializePatch(patch)); return log; } deserializeModel(serialized) { if (!serialized) throw new Error('NO_MODEL'); if (serialized instanceof Uint8Array) return Model.fromBinary(serialized); if (Array.isArray(serialized)) { const decoder = this.opts.structuralCompactDecoder; if (!decoder) throw new Error('NO_STRUCTURAL_COMPACT_DECODER'); return decoder.decode(serialized); } if (typeof serialized === 'object') { const decoder = this.opts.structuralVerboseDecoder; if (!decoder) throw new Error('NO_STRUCTURAL_VERBOSE_DECODER'); return decoder.decode(serialized); } throw new Error('UNKNOWN_MODEL'); } deserializePatch(serialized) { if (!serialized) throw new Error('NO_MODEL'); if (serialized instanceof Uint8Array) return Patch.fromBinary(serialized); if (Array.isArray(serialized)) { const decodeCompact = this.opts.patchCompactDecoder; if (!decodeCompact) throw new Error('NO_PATCH_COMPACT_DECODER'); return decodeCompact(serialized); } if (typeof serialized === 'object') { const decodeVerbose = this.opts.patchVerboseDecoder; if (!decodeVerbose) throw new Error('NO_PATCH_VERBOSE_DECODER'); return decodeVerbose(serialized); } throw new Error('UNKNOWN_MODEL'); } }