json-joy
Version:
Collection of libraries for building collaborative editing apps.
159 lines (158 loc) • 6.38 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.applyOperation = applyOperation;
exports.applyPatch = applyPatch;
const clone_1 = require("@jsonjoy.com/util/lib/json-clone/clone");
const hasOwnProperty_1 = require("@jsonjoy.com/util/lib/hasOwnProperty");
const json_pointer_1 = require("@jsonjoy.com/json-pointer");
const deepEqual_1 = require("@jsonjoy.com/util/lib/json-equal/deepEqual");
const { isArray } = Array;
function applyOperation(doc, operation) {
const path = operation.path;
const isRoot = !path;
if (isRoot) {
switch (operation.op) {
case 'add':
case 'replace':
doc = operation.value;
return { doc: operation.value, old: doc };
case 'remove':
return { doc: null, old: doc };
case 'move': {
const { val } = (0, json_pointer_1.findByPointer)(operation.from, doc);
return { doc: val, old: doc };
}
case 'copy': {
const { val } = (0, json_pointer_1.findByPointer)(operation.from, doc);
return { doc: val, old: doc };
}
case 'test': {
if (!(0, deepEqual_1.deepEqual)(operation.value, doc))
throw new Error('TEST');
return { doc };
}
}
return { doc };
}
let indexOfSlash = 0;
let indexAfterSlash = 1;
let obj = doc;
let key = '';
while (indexOfSlash > -1) {
indexOfSlash = path.indexOf('/', indexAfterSlash);
key = indexOfSlash > -1 ? path.substring(indexAfterSlash, indexOfSlash) : path.substring(indexAfterSlash);
indexAfterSlash = indexOfSlash + 1;
if (isArray(obj)) {
const length = obj.length;
if (key === '-')
key = length;
else {
const key2 = ~~key;
if ('' + key2 !== key)
throw new Error('INVALID_INDEX');
key = key2;
if (key < 0 || key > length)
throw new Error('INVALID_INDEX');
}
if (indexOfSlash === -1) {
switch (operation.op) {
case 'add': {
const old = obj[key];
if (key < obj.length)
obj.splice(key, 0, operation.value);
else
obj.push(operation.value);
return { doc, old };
}
case 'replace': {
const old = obj[key];
obj[key] = operation.value;
return { doc, old };
}
case 'remove': {
const old = obj[key];
obj.splice(key, 1);
return { doc, old };
}
case 'move': {
const removeResult = applyOperation(doc, { op: 'remove', path: operation.from });
return applyOperation(removeResult.doc, { op: 'add', path: operation.path, value: removeResult.old });
}
case 'copy': {
const old = obj[key];
const { val } = (0, json_pointer_1.findByPointer)(operation.from, doc);
const value = (0, clone_1.clone)(val);
if (key < obj.length)
obj.splice(key, 0, value);
else
obj.push(value);
return { doc, old };
}
case 'test': {
if (!(0, deepEqual_1.deepEqual)(operation.value, obj[key]))
throw new Error('TEST');
return { doc };
}
}
break;
}
obj = obj[key];
}
else if (typeof obj === 'object' && !!obj) {
key = (0, json_pointer_1.unescapeComponent)(key);
if (indexOfSlash === -1) {
switch (operation.op) {
case 'add': {
const old = obj[key];
obj[key] = operation.value;
return { doc, old };
}
case 'replace': {
const old = obj[key];
obj[key] = operation.value;
return { doc, old };
}
case 'remove': {
const old = obj[key];
delete obj[key];
return { doc, old };
}
case 'move': {
const removeResult = applyOperation(doc, { op: 'remove', path: operation.from });
const addResult = applyOperation(doc, { op: 'add', path: operation.path, value: removeResult.old });
return addResult;
}
case 'copy': {
const { val } = (0, json_pointer_1.findByPointer)(operation.from, doc);
const value = (0, clone_1.clone)(val);
const old = obj[key];
obj[key] = value;
return { doc, old };
}
case 'test': {
if (!(0, deepEqual_1.deepEqual)(operation.value, obj[key]))
throw new Error('TEST');
return { doc };
}
}
break;
}
obj = (0, hasOwnProperty_1.hasOwnProperty)(obj, key) ? obj[key] : undefined;
}
else
throw new Error('NOT_FOUND');
}
return { doc };
}
function applyPatch(doc, patch, options) {
if (!options.mutate)
doc = (0, clone_1.clone)(doc);
const res = [];
for (let i = 0; i < patch.length; i++) {
const operation = patch[i];
const opResult = applyOperation(doc, operation);
res.push(opResult);
doc = opResult.doc;
}
return { doc, res };
}