UNPKG

json-joy

Version:

Collection of libraries for building collaborative editing apps.

232 lines (231 loc) 8.72 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/util/lib/buffers/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 json_hash_1 = require("../json-hash"); 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) { 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, json_hash_1.structHash)(dst[i])); const linePatch = line.diff(srcLines, dstLines); if (!linePatch.length) return; const inserts = []; const deletes = []; const patchLength = linePatch.length; for (let i = patchLength - 1; i >= 0; i--) { const [type, posSrc, posDst] = linePatch[i]; switch (type) { case 0 /* line.LINE_PATCH_OP_TYPE.EQL */: break; case 1 /* line.LINE_PATCH_OP_TYPE.INS */: { const view = dst[posDst]; const after = posSrc >= 0 ? src.find(posSrc) : src.id; if (!after) throw new DiffError(); inserts.push([after, [view]]); break; } case -1 /* line.LINE_PATCH_OP_TYPE.DEL */: { const span = src.findInterval(posSrc, 1); if (!span || !span.length) throw new DiffError(); deletes.push(...span); break; } case 2 /* line.LINE_PATCH_OP_TYPE.MIX */: { 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) => builder.json(view))); } if (deletes.length) builder.del(src.id, deletes); } diffObj(src, dst) { const builder = this.builder; const inserts = []; const srcKeys = new Set(); // biome-ignore lint: .forEach is fastest here src.forEach((key) => { srcKeys.add(key); const dstValue = dst[key]; if (dstValue === void 0) inserts.push([key, builder.const(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, src.get(key) instanceof nodes_1.ConNode ? builder.const(dstValue) : builder.constOrJson(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) return; edits.push([i, builder.const(void 0)]); } } 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; } } edits.push([i, builder.constOrJson(value)]); } for (let i = srcLength; i < dstLength; i++) edits.push([i, builder.constOrJson(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, builder.constOrJson(dst)); } else throw error; } } diffAny(src, dst) { if (src instanceof nodes_1.ConNode) { const val = src.val; if (val !== dst && !(0, deepEqual_1.deepEqual)(src.val, dst)) throw new DiffError(); } else if (src instanceof nodes_1.StrNode) { if (typeof dst !== 'string') throw new DiffError(); this.diffStr(src, dst); } else if (src instanceof nodes_1.ObjNode) { if (!dst || typeof dst !== 'object' || Array.isArray(dst)) throw new DiffError(); this.diffObj(src, dst); } else if (src instanceof nodes_1.ValNode) { this.diffVal(src, dst); } else if (src instanceof nodes_1.ArrNode) { if (!Array.isArray(dst)) throw new DiffError(); this.diffArr(src, dst); } else if (src instanceof nodes_1.VecNode) { if (!Array.isArray(dst)) throw new DiffError(); this.diffVec(src, dst); } else if (src instanceof nodes_1.BinNode) { 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(); } } exports.JsonCrdtDiff = JsonCrdtDiff;