json-joy
Version:
Collection of libraries for building collaborative editing apps.
309 lines • 11.7 kB
JavaScript
;
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