UNPKG

json-joy

Version:

Collection of libraries for building collaborative editing apps.

309 lines 11.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.JsonCrdtDiff = exports.DiffError = void 0; const tslib_1 = require("tslib"); const deepEqual_1 = require("@jsonjoy.com/util/lib/json-equal/deepEqual"); const cmpUint8Array_1 = require("@jsonjoy.com/buffers/lib/cmpUint8Array"); const json_crdt_patch_1 = require("../json-crdt-patch"); const nodes_1 = require("../json-crdt/nodes"); const str = tslib_1.__importStar(require("../util/diff/str")); const bin = tslib_1.__importStar(require("../util/diff/bin")); const line = tslib_1.__importStar(require("../util/diff/line")); const structHashCrdt_1 = require("../json-hash/structHashCrdt"); const structHashSchema_1 = require("../json-hash/structHashSchema"); class DiffError extends Error { constructor(message = 'DIFF') { super(message); } } exports.DiffError = DiffError; class JsonCrdtDiff { constructor(model) { this.model = model; this.builder = new json_crdt_patch_1.PatchBuilder(model.clock.clone()); } diffStr(src, dst) { const view = src.view(); if (view === dst) return; const builder = this.builder; str.apply(str.diff(view, dst), view.length, (pos, txt) => builder.insStr(src.id, !pos ? src.id : src.find(pos - 1), txt), (pos, len) => builder.del(src.id, src.findInterval(pos, len))); } diffBin(src, dst) { const view = src.view(); if ((0, cmpUint8Array_1.cmpUint8Array)(view, dst)) return; const builder = this.builder; bin.apply(bin.diff(view, dst), view.length, (pos, txt) => builder.insBin(src.id, !pos ? src.id : src.find(pos - 1), txt), (pos, len) => builder.del(src.id, src.findInterval(pos, len))); } diffArr(src, dst) { if (src.size() === 0) { const length = dst.length; if (length === 0) return; let after = src.id; for (let i = 0; i < length; i++) after = this.builder.insArr(src.id, after, [this.buildView(dst[i])]); return; } else if (dst.length === 0) { const spans = []; for (const chunk of src.chunks()) { if (chunk.del) continue; const id = chunk.id; spans.push((0, json_crdt_patch_1.tss)(id.sid, id.time, chunk.span)); } if (spans.length) this.builder.del(src.id, spans); return; } const srcLines = []; src.children((node) => srcLines.push((0, structHashCrdt_1.structHashCrdt)(node))); const dstLines = []; const dstLength = dst.length; for (let i = 0; i < dstLength; i++) dstLines.push((0, structHashSchema_1.structHashSchema)(dst[i])); const linePatch = line.diff(srcLines, dstLines); if (!linePatch.length) return; const inserts = []; const deletes = []; line.apply(linePatch, (posSrc) => { const span = src.findInterval(posSrc, 1); if (!span || !span.length) throw new DiffError(); deletes.push(...span); }, (posSrc, posDst) => { const view = dst[posDst]; const after = posSrc >= 0 ? src.find(posSrc) : src.id; if (!after) throw new DiffError(); inserts.push([after, [view]]); }, (posSrc, posDst) => { const view = dst[posDst]; try { this.diffAny(src.getNode(posSrc), view); } catch (error) { if (error instanceof DiffError) { const span = src.findInterval(posSrc, 1); deletes.push(...span); const after = posSrc ? src.find(posSrc - 1) : src.id; if (!after) throw new DiffError(); inserts.push([after, [view]]); } else throw error; } }); const builder = this.builder; const length = inserts.length; for (let i = 0; i < length; i++) { const [after, views] = inserts[i]; builder.insArr(src.id, after, views.map((view) => this.buildView(view))); } if (deletes.length) builder.del(src.id, deletes); } diffObj(src, dst) { const builder = this.builder; const inserts = []; const srcKeys = new Set(); src.forEach((key) => { srcKeys.add(key); const dstValue = dst[key]; if (dstValue === void 0) inserts.push([key, builder.con(undefined)]); }); const keys = Object.keys(dst); const length = keys.length; for (let i = 0; i < length; i++) { const key = keys[i]; const dstValue = dst[key]; if (srcKeys.has(key)) { const child = src.get(key); if (child) { try { this.diffAny(child, dstValue); continue; } catch (error) { if (!(error instanceof DiffError)) throw error; } } } inserts.push([key, this.buildConView(dstValue)]); // inserts.push([key, src.get(key) instanceof ConNode ? builder.con(dstValue) : this.buildConView(dstValue)]); } if (inserts.length) builder.insObj(src.id, inserts); } diffVec(src, dst) { const builder = this.builder; const edits = []; const elements = src.elements; const srcLength = elements.length; const dstLength = dst.length; const index = src.doc.index; const min = Math.min(srcLength, dstLength); for (let i = dstLength; i < srcLength; i++) { const id = elements[i]; if (id) { const child = index.get(id); const isDeleted = !child || (child instanceof nodes_1.ConNode && child.val === void 0); if (isDeleted) continue; edits.push([i, builder.con(void 0)]); } } CHILDREN: for (let i = 0; i < min; i++) { const value = dst[i]; const child = src.get(i); if (child) { try { this.diffAny(child, value); continue; } catch (error) { if (!(error instanceof DiffError)) throw error; } if (child instanceof nodes_1.ConNode && typeof value !== 'object') { const valueId = builder.con(value); edits.push([i, valueId]); continue CHILDREN; } } edits.push([i, this.buildConView(value)]); } for (let i = srcLength; i < dstLength; i++) edits.push([i, this.buildConView(dst[i])]); if (edits.length) builder.insVec(src.id, edits); } diffVal(src, dst) { try { this.diffAny(src.node(), dst); } catch (error) { if (error instanceof DiffError) { const builder = this.builder; builder.setVal(src.id, this.buildConView(dst)); } else throw error; } } diffAny(src, dst) { if (src instanceof nodes_1.ConNode) { if (dst instanceof json_crdt_patch_1.nodes.con) dst = dst.raw; const val = src.val; if (val !== dst && ((val instanceof json_crdt_patch_1.Timestamp && !(dst instanceof json_crdt_patch_1.Timestamp)) || (!(val instanceof json_crdt_patch_1.Timestamp) && dst instanceof json_crdt_patch_1.Timestamp) || !(0, deepEqual_1.deepEqual)(src.val, dst))) throw new DiffError(); } else if (src instanceof nodes_1.StrNode) { if (dst instanceof json_crdt_patch_1.nodes.str) dst = dst.raw; if (typeof dst !== 'string') throw new DiffError(); this.diffStr(src, dst); } else if (src instanceof nodes_1.ObjNode) { if (dst instanceof json_crdt_patch_1.nodes.obj) dst = dst.opt ? { ...dst.obj, ...dst.opt } : dst.obj; if (dst instanceof json_crdt_patch_1.NodeBuilder) throw new DiffError(); if (dst instanceof Uint8Array) throw new DiffError(); if (!dst || typeof dst !== 'object' || Array.isArray(dst)) throw new DiffError(); this.diffObj(src, dst); } else if (src instanceof nodes_1.ValNode) { if (dst instanceof json_crdt_patch_1.nodes.val) dst = dst.value; this.diffVal(src, dst); } else if (src instanceof nodes_1.ArrNode) { if (dst instanceof json_crdt_patch_1.nodes.arr) dst = dst.arr; if (!Array.isArray(dst)) throw new DiffError(); this.diffArr(src, dst); } else if (src instanceof nodes_1.VecNode) { if (dst instanceof json_crdt_patch_1.nodes.vec) dst = dst.value; if (!Array.isArray(dst)) throw new DiffError(); this.diffVec(src, dst); } else if (src instanceof nodes_1.BinNode) { if (dst instanceof json_crdt_patch_1.nodes.bin) dst = dst.raw; if (!(dst instanceof Uint8Array)) throw new DiffError(); this.diffBin(src, dst); } else { throw new DiffError(); } } diff(src, dst) { this.diffAny(src, dst); return this.builder.flush(); } /** Diffs only keys present in the destination object. */ diffDstKeys(src, dst) { const builder = this.builder; const inserts = []; const keys = Object.keys(dst); const keyLength = keys.length; for (let i = 0; i < keyLength; i++) { const key = keys[i]; const child = src.get(key); const dstValue = dst[key]; if (!child) { inserts.push([key, this.buildConView(dstValue)]); continue; } try { this.diffAny(child, dstValue); } catch (error) { if (error instanceof DiffError) inserts.push([key, this.buildConView(dstValue)]); else throw error; } } if (inserts.length) builder.insObj(src.id, inserts); return this.builder.flush(); } buildView(dst) { const builder = this.builder; if (dst instanceof json_crdt_patch_1.Timestamp) return builder.con(dst); if (dst instanceof json_crdt_patch_1.nodes.con) return builder.con(dst.raw); return builder.json(dst); } buildConView(dst) { const builder = this.builder; if (dst instanceof json_crdt_patch_1.Timestamp) return builder.con(dst); if (dst instanceof json_crdt_patch_1.nodes.con) return builder.con(dst.raw); return builder.constOrJson(dst); } } exports.JsonCrdtDiff = JsonCrdtDiff; //# sourceMappingURL=JsonCrdtDiff.js.map