UNPKG

immutable-json-patch

Version:

Immutable JSON patch with support for reverting operations

187 lines (174 loc) 6.33 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.add = add; exports.copy = copy; exports.immutableJSONPatch = immutableJSONPatch; exports.isArrayItem = isArrayItem; exports.move = move; exports.parseFrom = parseFrom; exports.parsePath = parsePath; exports.remove = remove; exports.replace = replace; exports.resolvePathIndex = resolvePathIndex; exports.test = test; exports.validateJSONPatchOperation = validateJSONPatchOperation; var _immutabilityHelpers = require("./immutabilityHelpers.js"); var _jsonPointer = require("./jsonPointer.js"); var _utils = require("./utils.js"); /** * Apply a patch to a JSON object * The original JSON object will not be changed, * instead, the patch is applied in an immutable way */ function immutableJSONPatch(document, operations, options) { let updatedDocument = document; for (let i = 0; i < operations.length; i++) { validateJSONPatchOperation(operations[i]); let operation = operations[i]; // TODO: test before if (options?.before) { const result = options.before(updatedDocument, operation); if (result !== undefined) { if (result.document !== undefined) { updatedDocument = result.document; } // @ts-ignore if (result.json !== undefined) { // TODO: deprecated since v5.0.0. Cleanup this warning some day throw new Error('Deprecation warning: returned object property ".json" has been renamed to ".document"'); } if (result.operation !== undefined) { operation = result.operation; } } } const previousDocument = updatedDocument; const path = parsePath(updatedDocument, operation.path); if (operation.op === 'add') { updatedDocument = add(updatedDocument, path, operation.value); } else if (operation.op === 'remove') { updatedDocument = remove(updatedDocument, path); } else if (operation.op === 'replace') { updatedDocument = replace(updatedDocument, path, operation.value); } else if (operation.op === 'copy') { updatedDocument = copy(updatedDocument, path, parseFrom(operation.from)); } else if (operation.op === 'move') { updatedDocument = move(updatedDocument, path, parseFrom(operation.from)); } else if (operation.op === 'test') { test(updatedDocument, path, operation.value); } else { throw new Error(`Unknown JSONPatch operation ${JSON.stringify(operation)}`); } // TODO: test after if (options?.after) { const result = options.after(updatedDocument, operation, previousDocument); if (result !== undefined) { updatedDocument = result; } } } return updatedDocument; } /** * Replace an existing item */ function replace(document, path, value) { return (0, _immutabilityHelpers.existsIn)(document, path) ? (0, _immutabilityHelpers.setIn)(document, path, value) : document; } /** * Remove an item or property */ function remove(document, path) { return (0, _immutabilityHelpers.deleteIn)(document, path); } /** * Add an item or property */ function add(document, path, value) { if (isArrayItem(document, path)) { return (0, _immutabilityHelpers.insertAt)(document, path, value); } return (0, _immutabilityHelpers.setIn)(document, path, value); } /** * Copy a value */ function copy(document, path, from) { const value = (0, _immutabilityHelpers.getIn)(document, from); if (isArrayItem(document, path)) { return (0, _immutabilityHelpers.insertAt)(document, path, value); } return (0, _immutabilityHelpers.setIn)(document, path, value); } /** * Move a value */ function move(document, path, from) { const value = (0, _immutabilityHelpers.getIn)(document, from); const removedJson = (0, _immutabilityHelpers.deleteIn)(document, from); return isArrayItem(removedJson, path) ? (0, _immutabilityHelpers.insertAt)(removedJson, path, value) : (0, _immutabilityHelpers.setIn)(removedJson, path, value); } /** * Test whether the data contains the provided value at the specified path. * Throws an error when the test fails */ function test(document, path, value) { if (value === undefined) { throw new Error(`Test failed: no value provided (path: "${(0, _jsonPointer.compileJSONPointer)(path)}")`); } if (!(0, _immutabilityHelpers.existsIn)(document, path)) { throw new Error(`Test failed: path not found (path: "${(0, _jsonPointer.compileJSONPointer)(path)}")`); } const actualValue = (0, _immutabilityHelpers.getIn)(document, path); if (!(0, _utils.isEqual)(actualValue, value)) { throw new Error(`Test failed, value differs (path: "${(0, _jsonPointer.compileJSONPointer)(path)}")`); } } function isArrayItem(document, path) { if (path.length === 0) { return false; } const parent = (0, _immutabilityHelpers.getIn)(document, (0, _utils.initial)(path)); return Array.isArray(parent); } /** * Resolve the path index of an array, resolves indexes '-' * @returns Returns the resolved path */ function resolvePathIndex(document, path) { if ((0, _utils.last)(path) !== '-') { return path; } const parentPath = (0, _utils.initial)(path); const parent = (0, _immutabilityHelpers.getIn)(document, parentPath); // @ts-ignore return parentPath.concat(parent.length); } /** * Validate a JSONPatch operation. * Throws an error when there is an issue */ function validateJSONPatchOperation(operation) { // TODO: write unit tests const ops = ['add', 'remove', 'replace', 'copy', 'move', 'test']; if (!ops.includes(operation.op)) { throw new Error(`Unknown JSONPatch op ${JSON.stringify(operation.op)}`); } if (typeof operation.path !== 'string') { throw new Error(`Required property "path" missing or not a string in operation ${JSON.stringify(operation)}`); } if (operation.op === 'copy' || operation.op === 'move') { if (typeof operation.from !== 'string') { throw new Error(`Required property "from" missing or not a string in operation ${JSON.stringify(operation)}`); } } } function parsePath(document, pointer) { return resolvePathIndex(document, (0, _jsonPointer.parseJSONPointer)(pointer)); } function parseFrom(fromPointer) { return (0, _jsonPointer.parseJSONPointer)(fromPointer); } //# sourceMappingURL=immutableJSONPatch.js.map