diff --git a/.gitignore b/.gitignore index 540ff0e6..05939c84 100644 --- a/.gitignore +++ b/.gitignore @@ -9,12 +9,8 @@ npm-debug.log .idea # Typescript -*.d.ts +commonjs/*.d.ts test/spec/typings/typingsSpec.js -# compiled files -src/*.js -lib/* - # SauceLabs logs *.log \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 7c35fe92..1950c4ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js dist: trusty -node_js: lts/* +node_js: node before_script: - npm install - npm run serve & diff --git a/README.md b/README.md index e1e9f262..404021fc 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,12 @@ JSON-Patch [![Build Status](https://travis-ci.org/Starcounter-Jack/JSON-Patch.svg?branch=master)](https://travis-ci.org/Starcounter-Jack/JSON-Patch) With JSON-Patch, you can: -- **applyPatch** to apply patches -- **applyOperation** to apply single operations +- **apply** patches (arrays) and single operations on JS object - **validate** a sequence of patches -- **observe** for changes (and generate patches when a change is detected) -- **compare** two objects (to obtain the difference). +- **observe** for changes and **generate** patches when a change is detected +- **compare** two objects to obtain the difference - -[![Sauce Test Status](https://saucelabs.com/browser-matrix/json-patch.svg)](https://travis-ci.org/Starcounter-Jack/JSON-Patch) +Tested in IE11, Firefox, Chrome, Safari and Node.js ## Why you should use JSON-Patch @@ -24,195 +22,70 @@ JSON Patch plays well with the HTTP PATCH verb (method) and REST style programmi Mark Nottingham has a [nice blog]( http://www.mnot.net/blog/2012/09/05/patch) about it. -## Footprint -4 KB minified and gzipped (12 KB minified) - -## Performance - -##### [`add` benchmark](https://run.perf.zone/view/JSON-Patch-Add-Operation-1535541298893) - -![image](https://user-images.githubusercontent.com/17054134/44784357-aa422480-ab8d-11e8-8a7e-037e692dd842.png) - -##### [`replace` benchmark](https://run.perf.zone/view/JSON-Patch-Replace-Operation-1535540952263) - -![image](https://user-images.githubusercontent.com/17054134/44784275-5fc0a800-ab8d-11e8-8a90-e87b8d5409d0.png) - -Tested on 29.08.2018. Compared libraries: - -- [Starcounter-Jack/JSON-Patch](https://www.npmjs.com/package/fast-json-patch) 2.0.6 -- [bruth/jsonpatch-js](https://www.npmjs.com/package/json-patch) 0.7.0 -- [dharmafly/jsonpatch.js](https://www.npmjs.com/package/jsonpatch) 3.0.1 -- [jiff](https://www.npmjs.com/package/jiff) 0.7.3 -- [RFC6902](https://www.npmjs.com/package/rfc6902) 2.4.0 - -We aim the tests to be fair. Our library puts performance as the #1 priority, while other libraries can have different priorities. If you'd like to update the benchmarks or add a library, please fork the [perf.zone](https://perf.zone) benchmarks linked above and open an issue to include new results. - -## Features -* Allows you to apply patches on object trees for incoming traffic. -* Allows you to freely manipulate object trees and then generate patches for outgoing traffic. -* Tested in IE11, Firefox, Chrome, Safari and Node.js - ## Install -Install the current version (and save it as a dependency): - -### npm +[Download as ZIP](https://github.com/Starcounter-Jack/JSON-Patch/archive/master.zip) or install the current version using a package manager (and save it as a dependency): ```sh -$ npm install fast-json-patch --save -``` -### bower +# NPM +npm install fast-json-patch --save -```sh -$ bower install fast-json-patch --save +# or Bower +bower install fast-json-patch --save ``` -### [download as ZIP](https://github.com/Starcounter-Jack/JSON-Patch/archive/master.zip) - ## Adding to your project ### In a web browser -Include `dist/fast-json-patch.js`. +Load the bundled distribution script: -### In Node.js - -Call require to get the instance: - -```js -var jsonpatch = require('fast-json-patch') +```html + ``` -Or use ES6 style: +In [browsers that support ECMAScript modules](https://caniuse.com/#feat=es6-module), the below code uses this library as a module: -```js -import { applyOperation } from 'fast-json-patch' -``` - -You can also require all API functions individually, all jsonpatch functions can be used as pure functions: - -```js -const { applyOperation } = require('fast-json-patch'); -``` - -## Usage - -#### Applying patches: - -```js -var document = { firstName: "Albert", contactDetails: { phoneNumbers: [] } }; -var patch = [ - { op: "replace", path: "/firstName", value: "Joachim" }, - { op: "add", path: "/lastName", value: "Wester" }, - { op: "add", path: "/contactDetails/phoneNumbers/0", value: { number: "555-123" } } -]; -document = jsonpatch.applyPatch(document, patch).newDocument; -// document == { firstName: "Joachim", lastName: "Wester", contactDetails: { phoneNumbers: [{number:"555-123"}] } }; -``` - -##### For apply individual operations you can use `applyOperation` - -`jsonpatch.applyOperation` accepts a single operation object instead of a sequence, and returns the object after applying the operation. It works with all the standard JSON patch operations (`add, replace, move, test, remove and copy`). - -```js -var document = { firstName: "Albert", contactDetails: { phoneNumbers: [] } }; -var operation = { op: "replace", path: "/firstName", value: "Joachim" }; -document = jsonpatch.applyOperation(document, operation).newDocument; -// document == { firstName: "Joachim", contactDetails: { phoneNumbers: [] }} +```html + ``` -#### Using `applyReducer` with `reduce` - -If you have an array of operations, you can simple reduce them using `applyReducer` as your reducer: - -```js -var document = { firstName: "Albert", contactDetails: { phoneNumbers: [ ] } }; -var patch = [ - { op:"replace", path: "/firstName", value: "Joachim" }, - { op:"add", path: "/lastName", value: "Wester" }, - { op:"add", path: "/contactDetails/phoneNumbers/0", value: { number: "555-123" } } -]; -var updatedDocument = patch.reduce(applyReducer, document); -// updatedDocument == { firstName:"Joachim", lastName:"Wester", contactDetails:{ phoneNumbers[ {number:"555-123"} ] } }; -``` +### In Node.js -Generating patches: +In Node 12+ with `--experimental-modules` flag, the below code uses this library as an ECMAScript module: ```js -var document = { firstName: "Joachim", lastName: "Wester", contactDetails: { phoneNumbers: [ { number:"555-123" }] } }; -var observer = jsonpatch.observe(document); -document.firstName = "Albert"; -document.contactDetails.phoneNumbers[0].number = "123"; -document.contactDetails.phoneNumbers.push({ number:"456" }); -var patch = jsonpatch.generate(observer); -// patch == [ -// { op: "replace", path: "/firstName", value: "Albert"}, -// { op: "replace", path: "/contactDetails/phoneNumbers/0/number", value: "123" }, -// { op: "add", path: "/contactDetails/phoneNumbers/1", value: {number:"456"}} -// ]; +import * as jsonpatch from 'fast-json-patch/index.mjs'; +import { applyOperation } from 'fast-json-patch/index.mjs'; ``` -Generating patches with test operations for values in the first object: +In Webpack (and most surely other bundlers based on Babel), the below code uses this library as an ECMAScript module: ```js -var document = { firstName: "Joachim", lastName: "Wester", contactDetails: { phoneNumbers: [ { number:"555-123" }] } }; -var observer = jsonpatch.observe(document); -document.firstName = "Albert"; -document.contactDetails.phoneNumbers[0].number = "123"; -document.contactDetails.phoneNumbers.push({ number:"456" }); -var patch = jsonpatch.generate(observer, true); -// patch == [ -// { op: "test", path: "/firstName", value: "Joachim"}, -// { op: "replace", path: "/firstName", value: "Albert"}, -// { op: "test", path: "/contactDetails/phoneNumbers/0/number", value: "555-123" }, -// { op: "replace", path: "/contactDetails/phoneNumbers/0/number", value: "123" }, -// { op: "add", path: "/contactDetails/phoneNumbers/1", value: {number:"456"}} -// ]; +import * as jsonpatch from 'fast-json-patch'; +import { applyOperation } from 'fast-json-patch'; ``` -Comparing two object trees: +In standard Node, the below code uses this library as a CommonJS module: ```js -var documentA = {user: {firstName: "Albert", lastName: "Einstein"}}; -var documentB = {user: {firstName: "Albert", lastName: "Collins"}}; -var diff = jsonpatch.compare(documentA, documentB); -//diff == [{op: "replace", path: "/user/lastName", value: "Collins"}] +const { applyOperation } = require('fast-json-patch'); +const applyOperation = require('fast-json-patch').applyOperation; ``` -Comparing two object trees with test operations for values in the first object: +## Directories -```js -var documentA = {user: {firstName: "Albert", lastName: "Einstein"}}; -var documentB = {user: {firstName: "Albert", lastName: "Collins"}}; -var diff = jsonpatch.compare(documentA, documentB, true); -//diff == [ -// {op: "test", path: "/user/lastName", value: "Einstein"}, -// {op: "replace", path: "/user/lastName", value: "Collins"} -// ]; -``` +Directories used in this package: -Validating a sequence of patches: - -```js -var obj = {user: {firstName: "Albert"}}; -var patches = [{op: "replace", path: "/user/firstName", value: "Albert"}, {op: "replace", path: "/user/lastName", value: "Einstein"}]; -var errors = jsonpatch.validate(patches, obj); -if (errors.length == 0) { - //there are no errors! -} -else { - for (var i=0; i < errors.length; i++) { - if (!errors[i]) { - console.log("Valid patch at index", i, patches[i]); - } - else { - console.error("Invalid patch at index", i, errors[i], patches[i]); - } - } -} -``` +- `dist/` - contains ES5 files for a Web browser +- `commonjs/` - contains CommonJS module and typings +- `module/` - contains ECMAScript module and typings +- `src/` - contains TypeScript source files ## API @@ -243,6 +116,19 @@ Returns an array of [`OperationResult`](#operationresult-type) objects - one ite - See [Validation notes](#validation-notes). +Example: + +```js +var document = { firstName: "Albert", contactDetails: { phoneNumbers: [] } }; +var patch = [ + { op: "replace", path: "/firstName", value: "Joachim" }, + { op: "add", path: "/lastName", value: "Wester" }, + { op: "add", path: "/contactDetails/phoneNumbers/0", value: { number: "555-123" } } +]; +document = jsonpatch.applyPatch(document, patch).newDocument; +// document == { firstName: "Joachim", lastName: "Wester", contactDetails: { phoneNumbers: [{number:"555-123"}] } }; +``` + #### `function applyOperation(document: T, operation: Operation, validateOperation: boolean | Validator = false, mutateDocument: boolean = true, banPrototypeModifications: boolean = true, index: number = 0): OperationResult` Applies single operation object `operation` on `document`. @@ -264,6 +150,15 @@ Returns an [`OperationResult`](#operationresult-type) object `{newDocument: any, - See [Validation notes](#validation-notes). +Example: + +```js +var document = { firstName: "Albert", contactDetails: { phoneNumbers: [] } }; +var operation = { op: "replace", path: "/firstName", value: "Joachim" }; +document = jsonpatch.applyOperation(document, operation).newDocument; +// document == { firstName: "Joachim", contactDetails: { phoneNumbers: [] }} +``` + #### `jsonpatch.applyReducer(document: T, operation: Operation, index: number): T` **Ideal for `patch.reduce(jsonpatch.applyReducer, document)`**. @@ -274,6 +169,19 @@ Returns the a modified document. Note: It throws `TEST_OPERATION_FAILED` error if `test` operation fails. +Example: + +```js +var document = { firstName: "Albert", contactDetails: { phoneNumbers: [ ] } }; +var patch = [ + { op:"replace", path: "/firstName", value: "Joachim" }, + { op:"add", path: "/lastName", value: "Wester" }, + { op:"add", path: "/contactDetails/phoneNumbers/0", value: { number: "555-123" } } +]; +var updatedDocument = patch.reduce(applyReducer, document); +// updatedDocument == { firstName:"Joachim", lastName:"Wester", contactDetails:{ phoneNumbers[ {number:"555-123"} ] } }; +``` + #### `jsonpatch.deepClone(value: any): any` Returns deeply cloned value. @@ -306,6 +214,40 @@ method, it will be triggered synchronously as well. If `invertible` is true, the If there are no pending changes in `obj`, returns an empty array (length 0). +Example: + +```js +var document = { firstName: "Joachim", lastName: "Wester", contactDetails: { phoneNumbers: [ { number:"555-123" }] } }; +var observer = jsonpatch.observe(document); +document.firstName = "Albert"; +document.contactDetails.phoneNumbers[0].number = "123"; +document.contactDetails.phoneNumbers.push({ number:"456" }); +var patch = jsonpatch.generate(observer); +// patch == [ +// { op: "replace", path: "/firstName", value: "Albert"}, +// { op: "replace", path: "/contactDetails/phoneNumbers/0/number", value: "123" }, +// { op: "add", path: "/contactDetails/phoneNumbers/1", value: {number:"456"}} +// ]; +``` + +Example of generating patches with test operations for values in the first object: + +```js +var document = { firstName: "Joachim", lastName: "Wester", contactDetails: { phoneNumbers: [ { number:"555-123" }] } }; +var observer = jsonpatch.observe(document); +document.firstName = "Albert"; +document.contactDetails.phoneNumbers[0].number = "123"; +document.contactDetails.phoneNumbers.push({ number:"456" }); +var patch = jsonpatch.generate(observer, true); +// patch == [ +// { op: "test", path: "/firstName", value: "Joachim"}, +// { op: "replace", path: "/firstName", value: "Albert"}, +// { op: "test", path: "/contactDetails/phoneNumbers/0/number", value: "555-123" }, +// { op: "replace", path: "/contactDetails/phoneNumbers/0/number", value: "123" }, +// { op: "add", path: "/contactDetails/phoneNumbers/1", value: {number:"456"}} +// ]; +``` + #### `jsonpatch.unobserve(document: any, observer: Observer): void` Destroys the observer set up on `document`. @@ -318,6 +260,27 @@ Compares object trees `document1` and `document2` and returns the difference rel If there are no differences, returns an empty array (length 0). +Example: + +```js +var documentA = {user: {firstName: "Albert", lastName: "Einstein"}}; +var documentB = {user: {firstName: "Albert", lastName: "Collins"}}; +var diff = jsonpatch.compare(documentA, documentB); +//diff == [{op: "replace", path: "/user/lastName", value: "Collins"}] +``` + +Example of comparing two object trees with test operations for values in the first object: + +```js +var documentA = {user: {firstName: "Albert", lastName: "Einstein"}}; +var documentB = {user: {firstName: "Albert", lastName: "Collins"}}; +var diff = jsonpatch.compare(documentA, documentB, true); +//diff == [ +// {op: "test", path: "/user/lastName", value: "Einstein"}, +// {op: "replace", path: "/user/lastName", value: "Collins"} +// ]; +``` + #### `jsonpatch.validate(patch: Operation[], document?: any, validator?: Function): JsonPatchError` See [Validation notes](#validation-notes) @@ -350,6 +313,27 @@ OPERATION_PATH_ILLEGAL_ARRAY_INDEX | Expected an unsigned base-10 integer value, OPERATION_VALUE_OUT_OF_BOUNDS | The specified index MUST NOT be greater than the number of elements in the array TEST_OPERATION_FAILED | When operation is `test` and the test fails, applies to `applyReducer`. +Example: + +```js +var obj = {user: {firstName: "Albert"}}; +var patches = [{op: "replace", path: "/user/firstName", value: "Albert"}, {op: "replace", path: "/user/lastName", value: "Einstein"}]; +var errors = jsonpatch.validate(patches, obj); +if (errors.length == 0) { + //there are no errors! +} +else { + for (var i=0; i < errors.length; i++) { + if (!errors[i]) { + console.log("Valid patch at index", i, patches[i]); + } + else { + console.error("Invalid patch at index", i, errors[i], patches[i]); + } + } +} +``` + ## `OperationResult` Type Functions `applyPatch` and `applyOperation` both return `OperationResult` object. This object is: @@ -404,6 +388,29 @@ See the [ECMAScript spec](http://www.ecma-international.org/ecma-262/6.0/index.h To see the list of recent changes, see [Releases](https://github.com/Starcounter-Jack/JSON-Patch/releases). +## Footprint +4 KB minified and gzipped (12 KB minified) + +## Performance + +##### [`add` benchmark](https://run.perf.zone/view/JSON-Patch-Add-Operation-1535541298893) + +![image](https://user-images.githubusercontent.com/17054134/44784357-aa422480-ab8d-11e8-8a7e-037e692dd842.png) + +##### [`replace` benchmark](https://run.perf.zone/view/JSON-Patch-Replace-Operation-1535540952263) + +![image](https://user-images.githubusercontent.com/17054134/44784275-5fc0a800-ab8d-11e8-8a90-e87b8d5409d0.png) + +Tested on 29.08.2018. Compared libraries: + +- [Starcounter-Jack/JSON-Patch](https://www.npmjs.com/package/fast-json-patch) 2.0.6 +- [bruth/jsonpatch-js](https://www.npmjs.com/package/json-patch) 0.7.0 +- [dharmafly/jsonpatch.js](https://www.npmjs.com/package/jsonpatch) 3.0.1 +- [jiff](https://www.npmjs.com/package/jiff) 0.7.3 +- [RFC6902](https://www.npmjs.com/package/rfc6902) 2.4.0 + +We aim the tests to be fair. Our library puts performance as the #1 priority, while other libraries can have different priorities. If you'd like to update the benchmarks or add a library, please fork the [perf.zone](https://perf.zone) benchmarks linked above and open an issue to include new results. + ## License MIT diff --git a/commonjs/core.js b/commonjs/core.js new file mode 100644 index 00000000..8e13ca8c --- /dev/null +++ b/commonjs/core.js @@ -0,0 +1,448 @@ +Object.defineProperty(exports, "__esModule", { value: true }); +var helpers_js_1 = require("./helpers.js"); +exports.JsonPatchError = helpers_js_1.PatchError; +exports.deepClone = helpers_js_1._deepClone; +/* We use a Javascript hash to store each + function. Each hash entry (property) uses + the operation identifiers specified in rfc6902. + In this way, we can map each patch operation + to its dedicated function in efficient way. + */ +/* The operations applicable to an object */ +var objOps = { + add: function (obj, key, document) { + obj[key] = this.value; + return { newDocument: document }; + }, + remove: function (obj, key, document) { + var removed = obj[key]; + delete obj[key]; + return { newDocument: document, removed: removed }; + }, + replace: function (obj, key, document) { + var removed = obj[key]; + obj[key] = this.value; + return { newDocument: document, removed: removed }; + }, + move: function (obj, key, document) { + /* in case move target overwrites an existing value, + return the removed value, this can be taxing performance-wise, + and is potentially unneeded */ + var removed = getValueByPointer(document, this.path); + if (removed) { + removed = helpers_js_1._deepClone(removed); + } + var originalValue = applyOperation(document, { op: "remove", path: this.from }).removed; + applyOperation(document, { op: "add", path: this.path, value: originalValue }); + return { newDocument: document, removed: removed }; + }, + copy: function (obj, key, document) { + var valueToCopy = getValueByPointer(document, this.from); + // enforce copy by value so further operations don't affect source (see issue #177) + applyOperation(document, { op: "add", path: this.path, value: helpers_js_1._deepClone(valueToCopy) }); + return { newDocument: document }; + }, + test: function (obj, key, document) { + return { newDocument: document, test: _areEquals(obj[key], this.value) }; + }, + _get: function (obj, key, document) { + this.value = obj[key]; + return { newDocument: document }; + } +}; +/* The operations applicable to an array. Many are the same as for the object */ +var arrOps = { + add: function (arr, i, document) { + if (helpers_js_1.isInteger(i)) { + arr.splice(i, 0, this.value); + } + else { // array props + arr[i] = this.value; + } + // this may be needed when using '-' in an array + return { newDocument: document, index: i }; + }, + remove: function (arr, i, document) { + var removedList = arr.splice(i, 1); + return { newDocument: document, removed: removedList[0] }; + }, + replace: function (arr, i, document) { + var removed = arr[i]; + arr[i] = this.value; + return { newDocument: document, removed: removed }; + }, + move: objOps.move, + copy: objOps.copy, + test: objOps.test, + _get: objOps._get +}; +/** + * Retrieves a value from a JSON document by a JSON pointer. + * Returns the value. + * + * @param document The document to get the value from + * @param pointer an escaped JSON pointer + * @return The retrieved value + */ +function getValueByPointer(document, pointer) { + if (pointer == '') { + return document; + } + var getOriginalDestination = { op: "_get", path: pointer }; + applyOperation(document, getOriginalDestination); + return getOriginalDestination.value; +} +exports.getValueByPointer = getValueByPointer; +/** + * Apply a single JSON Patch Operation on a JSON document. + * Returns the {newDocument, result} of the operation. + * It modifies the `document` and `operation` objects - it gets the values by reference. + * If you would like to avoid touching your values, clone them: + * `jsonpatch.applyOperation(document, jsonpatch._deepClone(operation))`. + * + * @param document The document to patch + * @param operation The operation to apply + * @param validateOperation `false` is without validation, `true` to use default jsonpatch's validation, or you can pass a `validateOperation` callback to be used for validation. + * @param mutateDocument Whether to mutate the original document or clone it before applying + * @param banPrototypeModifications Whether to ban modifications to `__proto__`, defaults to `true`. + * @return `{newDocument, result}` after the operation + */ +function applyOperation(document, operation, validateOperation, mutateDocument, banPrototypeModifications, index) { + if (validateOperation === void 0) { validateOperation = false; } + if (mutateDocument === void 0) { mutateDocument = true; } + if (banPrototypeModifications === void 0) { banPrototypeModifications = true; } + if (index === void 0) { index = 0; } + if (validateOperation) { + if (typeof validateOperation == 'function') { + validateOperation(operation, 0, document, operation.path); + } + else { + validator(operation, 0); + } + } + /* ROOT OPERATIONS */ + if (operation.path === "") { + var returnValue = { newDocument: document }; + if (operation.op === 'add') { + returnValue.newDocument = operation.value; + return returnValue; + } + else if (operation.op === 'replace') { + returnValue.newDocument = operation.value; + returnValue.removed = document; //document we removed + return returnValue; + } + else if (operation.op === 'move' || operation.op === 'copy') { // it's a move or copy to root + returnValue.newDocument = getValueByPointer(document, operation.from); // get the value by json-pointer in `from` field + if (operation.op === 'move') { // report removed item + returnValue.removed = document; + } + return returnValue; + } + else if (operation.op === 'test') { + returnValue.test = _areEquals(document, operation.value); + if (returnValue.test === false) { + throw new exports.JsonPatchError("Test operation failed", 'TEST_OPERATION_FAILED', index, operation, document); + } + returnValue.newDocument = document; + return returnValue; + } + else if (operation.op === 'remove') { // a remove on root + returnValue.removed = document; + returnValue.newDocument = null; + return returnValue; + } + else if (operation.op === '_get') { + operation.value = document; + return returnValue; + } + else { /* bad operation */ + if (validateOperation) { + throw new exports.JsonPatchError('Operation `op` property is not one of operations defined in RFC-6902', 'OPERATION_OP_INVALID', index, operation, document); + } + else { + return returnValue; + } + } + } /* END ROOT OPERATIONS */ + else { + if (!mutateDocument) { + document = helpers_js_1._deepClone(document); + } + var path = operation.path || ""; + var keys = path.split('/'); + var obj = document; + var t = 1; //skip empty element - http://jsperf.com/to-shift-or-not-to-shift + var len = keys.length; + var existingPathFragment = undefined; + var key = void 0; + var validateFunction = void 0; + if (typeof validateOperation == 'function') { + validateFunction = validateOperation; + } + else { + validateFunction = validator; + } + while (true) { + key = keys[t]; + if (banPrototypeModifications && key == '__proto__') { + throw new TypeError('JSON-Patch: modifying `__proto__` prop is banned for security reasons, if this was on purpose, please set `banPrototypeModifications` flag false and pass it to this function. More info in fast-json-patch README'); + } + if (validateOperation) { + if (existingPathFragment === undefined) { + if (obj[key] === undefined) { + existingPathFragment = keys.slice(0, t).join('/'); + } + else if (t == len - 1) { + existingPathFragment = operation.path; + } + if (existingPathFragment !== undefined) { + validateFunction(operation, 0, document, existingPathFragment); + } + } + } + t++; + if (Array.isArray(obj)) { + if (key === '-') { + key = obj.length; + } + else { + if (validateOperation && !helpers_js_1.isInteger(key)) { + throw new exports.JsonPatchError("Expected an unsigned base-10 integer value, making the new referenced value the array element with the zero-based index", "OPERATION_PATH_ILLEGAL_ARRAY_INDEX", index, operation, document); + } // only parse key when it's an integer for `arr.prop` to work + else if (helpers_js_1.isInteger(key)) { + key = ~~key; + } + } + if (t >= len) { + if (validateOperation && operation.op === "add" && key > obj.length) { + throw new exports.JsonPatchError("The specified index MUST NOT be greater than the number of elements in the array", "OPERATION_VALUE_OUT_OF_BOUNDS", index, operation, document); + } + var returnValue = arrOps[operation.op].call(operation, obj, key, document); // Apply patch + if (returnValue.test === false) { + throw new exports.JsonPatchError("Test operation failed", 'TEST_OPERATION_FAILED', index, operation, document); + } + return returnValue; + } + } + else { + if (key && key.indexOf('~') != -1) { + key = helpers_js_1.unescapePathComponent(key); + } + if (t >= len) { + var returnValue = objOps[operation.op].call(operation, obj, key, document); // Apply patch + if (returnValue.test === false) { + throw new exports.JsonPatchError("Test operation failed", 'TEST_OPERATION_FAILED', index, operation, document); + } + return returnValue; + } + } + obj = obj[key]; + } + } +} +exports.applyOperation = applyOperation; +/** + * Apply a full JSON Patch array on a JSON document. + * Returns the {newDocument, result} of the patch. + * It modifies the `document` object and `patch` - it gets the values by reference. + * If you would like to avoid touching your values, clone them: + * `jsonpatch.applyPatch(document, jsonpatch._deepClone(patch))`. + * + * @param document The document to patch + * @param patch The patch to apply + * @param validateOperation `false` is without validation, `true` to use default jsonpatch's validation, or you can pass a `validateOperation` callback to be used for validation. + * @param mutateDocument Whether to mutate the original document or clone it before applying + * @param banPrototypeModifications Whether to ban modifications to `__proto__`, defaults to `true`. + * @return An array of `{newDocument, result}` after the patch + */ +function applyPatch(document, patch, validateOperation, mutateDocument, banPrototypeModifications) { + if (mutateDocument === void 0) { mutateDocument = true; } + if (banPrototypeModifications === void 0) { banPrototypeModifications = true; } + if (validateOperation) { + if (!Array.isArray(patch)) { + throw new exports.JsonPatchError('Patch sequence must be an array', 'SEQUENCE_NOT_AN_ARRAY'); + } + } + if (!mutateDocument) { + document = helpers_js_1._deepClone(document); + } + var results = new Array(patch.length); + for (var i = 0, length_1 = patch.length; i < length_1; i++) { + // we don't need to pass mutateDocument argument because if it was true, we already deep cloned the object, we'll just pass `true` + results[i] = applyOperation(document, patch[i], validateOperation, true, banPrototypeModifications, i); + document = results[i].newDocument; // in case root was replaced + } + results.newDocument = document; + return results; +} +exports.applyPatch = applyPatch; +/** + * Apply a single JSON Patch Operation on a JSON document. + * Returns the updated document. + * Suitable as a reducer. + * + * @param document The document to patch + * @param operation The operation to apply + * @return The updated document + */ +function applyReducer(document, operation, index) { + var operationResult = applyOperation(document, operation); + if (operationResult.test === false) { // failed test + throw new exports.JsonPatchError("Test operation failed", 'TEST_OPERATION_FAILED', index, operation, document); + } + return operationResult.newDocument; +} +exports.applyReducer = applyReducer; +/** + * Validates a single operation. Called from `jsonpatch.validate`. Throws `JsonPatchError` in case of an error. + * @param {object} operation - operation object (patch) + * @param {number} index - index of operation in the sequence + * @param {object} [document] - object where the operation is supposed to be applied + * @param {string} [existingPathFragment] - comes along with `document` + */ +function validator(operation, index, document, existingPathFragment) { + if (typeof operation !== 'object' || operation === null || Array.isArray(operation)) { + throw new exports.JsonPatchError('Operation is not an object', 'OPERATION_NOT_AN_OBJECT', index, operation, document); + } + else if (!objOps[operation.op]) { + throw new exports.JsonPatchError('Operation `op` property is not one of operations defined in RFC-6902', 'OPERATION_OP_INVALID', index, operation, document); + } + else if (typeof operation.path !== 'string') { + throw new exports.JsonPatchError('Operation `path` property is not a string', 'OPERATION_PATH_INVALID', index, operation, document); + } + else if (operation.path.indexOf('/') !== 0 && operation.path.length > 0) { + // paths that aren't empty string should start with "/" + throw new exports.JsonPatchError('Operation `path` property must start with "/"', 'OPERATION_PATH_INVALID', index, operation, document); + } + else if ((operation.op === 'move' || operation.op === 'copy') && typeof operation.from !== 'string') { + throw new exports.JsonPatchError('Operation `from` property is not present (applicable in `move` and `copy` operations)', 'OPERATION_FROM_REQUIRED', index, operation, document); + } + else if ((operation.op === 'add' || operation.op === 'replace' || operation.op === 'test') && operation.value === undefined) { + throw new exports.JsonPatchError('Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)', 'OPERATION_VALUE_REQUIRED', index, operation, document); + } + else if ((operation.op === 'add' || operation.op === 'replace' || operation.op === 'test') && helpers_js_1.hasUndefined(operation.value)) { + throw new exports.JsonPatchError('Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)', 'OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED', index, operation, document); + } + else if (document) { + if (operation.op == "add") { + var pathLen = operation.path.split("/").length; + var existingPathLen = existingPathFragment.split("/").length; + if (pathLen !== existingPathLen + 1 && pathLen !== existingPathLen) { + throw new exports.JsonPatchError('Cannot perform an `add` operation at the desired path', 'OPERATION_PATH_CANNOT_ADD', index, operation, document); + } + } + else if (operation.op === 'replace' || operation.op === 'remove' || operation.op === '_get') { + if (operation.path !== existingPathFragment) { + throw new exports.JsonPatchError('Cannot perform the operation at a path that does not exist', 'OPERATION_PATH_UNRESOLVABLE', index, operation, document); + } + } + else if (operation.op === 'move' || operation.op === 'copy') { + var existingValue = { op: "_get", path: operation.from, value: undefined }; + var error = validate([existingValue], document); + if (error && error.name === 'OPERATION_PATH_UNRESOLVABLE') { + throw new exports.JsonPatchError('Cannot perform the operation from a path that does not exist', 'OPERATION_FROM_UNRESOLVABLE', index, operation, document); + } + } + } +} +exports.validator = validator; +/** + * Validates a sequence of operations. If `document` parameter is provided, the sequence is additionally validated against the object document. + * If error is encountered, returns a JsonPatchError object + * @param sequence + * @param document + * @returns {JsonPatchError|undefined} + */ +function validate(sequence, document, externalValidator) { + try { + if (!Array.isArray(sequence)) { + throw new exports.JsonPatchError('Patch sequence must be an array', 'SEQUENCE_NOT_AN_ARRAY'); + } + if (document) { + //clone document and sequence so that we can safely try applying operations + applyPatch(helpers_js_1._deepClone(document), helpers_js_1._deepClone(sequence), externalValidator || true); + } + else { + externalValidator = externalValidator || validator; + for (var i = 0; i < sequence.length; i++) { + externalValidator(sequence[i], i, document, undefined); + } + } + } + catch (e) { + if (e instanceof exports.JsonPatchError) { + return e; + } + else { + throw e; + } + } +} +exports.validate = validate; +// based on https://github.com/epoberezkin/fast-deep-equal +// MIT License +// Copyright (c) 2017 Evgeny Poberezkin +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +function _areEquals(a, b) { + if (a === b) + return true; + if (a && b && typeof a == 'object' && typeof b == 'object') { + var arrA = Array.isArray(a), arrB = Array.isArray(b), i, length, key; + if (arrA && arrB) { + length = a.length; + if (length != b.length) + return false; + for (i = length; i-- !== 0;) + if (!_areEquals(a[i], b[i])) + return false; + return true; + } + if (arrA != arrB) + return false; + var keys = Object.keys(a); + length = keys.length; + if (length !== Object.keys(b).length) + return false; + for (i = length; i-- !== 0;) + if (!b.hasOwnProperty(keys[i])) + return false; + for (i = length; i-- !== 0;) { + key = keys[i]; + if (!_areEquals(a[key], b[key])) + return false; + } + return true; + } + return a !== a && b !== b; +} +exports._areEquals = _areEquals; +; +/** + * Default export for backwards compat + */ +exports.default = { + JsonPatchError: exports.JsonPatchError, + deepClone: exports.deepClone, + getValueByPointer: getValueByPointer, + applyOperation: applyOperation, + applyPatch: applyPatch, + applyReducer: applyReducer, + validator: validator, + validate: validate, + _areEquals: _areEquals +}; diff --git a/commonjs/duplex.js b/commonjs/duplex.js new file mode 100644 index 00000000..569b23f0 --- /dev/null +++ b/commonjs/duplex.js @@ -0,0 +1,207 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +/*! + * https://github.com/Starcounter-Jack/JSON-Patch + * (c) 2017 Joachim Wester + * MIT license + */ +var helpers_js_1 = require("./helpers.js"); +var core_js_1 = require("./core.js"); +var beforeDict = new WeakMap(); +var Mirror = /** @class */ (function () { + function Mirror(obj) { + this.observers = new Map(); + this.obj = obj; + } + return Mirror; +}()); +var ObserverInfo = /** @class */ (function () { + function ObserverInfo(callback, observer) { + this.callback = callback; + this.observer = observer; + } + return ObserverInfo; +}()); +function getMirror(obj) { + return beforeDict.get(obj); +} +function getObserverFromMirror(mirror, callback) { + return mirror.observers.get(callback); +} +function removeObserverFromMirror(mirror, observer) { + mirror.observers.delete(observer.callback); +} +/** + * Detach an observer from an object + */ +function unobserve(root, observer) { + observer.unobserve(); +} +exports.unobserve = unobserve; +/** + * Observes changes made to an object, which can then be retrieved using generate + */ +function observe(obj, callback) { + var patches = []; + var observer; + var mirror = getMirror(obj); + if (!mirror) { + mirror = new Mirror(obj); + beforeDict.set(obj, mirror); + } + else { + var observerInfo = getObserverFromMirror(mirror, callback); + observer = observerInfo && observerInfo.observer; + } + if (observer) { + return observer; + } + observer = {}; + mirror.value = helpers_js_1._deepClone(obj); + if (callback) { + observer.callback = callback; + observer.next = null; + var dirtyCheck = function () { + generate(observer); + }; + var fastCheck = function () { + clearTimeout(observer.next); + observer.next = setTimeout(dirtyCheck); + }; + if (typeof window !== 'undefined') { //not Node + window.addEventListener('mouseup', fastCheck); + window.addEventListener('keyup', fastCheck); + window.addEventListener('mousedown', fastCheck); + window.addEventListener('keydown', fastCheck); + window.addEventListener('change', fastCheck); + } + } + observer.patches = patches; + observer.object = obj; + observer.unobserve = function () { + generate(observer); + clearTimeout(observer.next); + removeObserverFromMirror(mirror, observer); + if (typeof window !== 'undefined') { + window.removeEventListener('mouseup', fastCheck); + window.removeEventListener('keyup', fastCheck); + window.removeEventListener('mousedown', fastCheck); + window.removeEventListener('keydown', fastCheck); + window.removeEventListener('change', fastCheck); + } + }; + mirror.observers.set(callback, new ObserverInfo(callback, observer)); + return observer; +} +exports.observe = observe; +/** + * Generate an array of patches from an observer + */ +function generate(observer, invertible) { + if (invertible === void 0) { invertible = false; } + var mirror = beforeDict.get(observer.object); + _generate(mirror.value, observer.object, observer.patches, "", invertible); + if (observer.patches.length) { + core_js_1.applyPatch(mirror.value, observer.patches); + } + var temp = observer.patches; + if (temp.length > 0) { + observer.patches = []; + if (observer.callback) { + observer.callback(temp); + } + } + return temp; +} +exports.generate = generate; +// Dirty check if obj is different from mirror, generate patches and update mirror +function _generate(mirror, obj, patches, path, invertible) { + if (obj === mirror) { + return; + } + if (typeof obj.toJSON === "function") { + obj = obj.toJSON(); + } + var newKeys = helpers_js_1._objectKeys(obj); + var oldKeys = helpers_js_1._objectKeys(mirror); + var changed = false; + var deleted = false; + //if ever "move" operation is implemented here, make sure this test runs OK: "should not generate the same patch twice (move)" + for (var t = oldKeys.length - 1; t >= 0; t--) { + var key = oldKeys[t]; + var oldVal = mirror[key]; + if (helpers_js_1.hasOwnProperty(obj, key) && !(obj[key] === undefined && oldVal !== undefined && Array.isArray(obj) === false)) { + var newVal = obj[key]; + if (typeof oldVal == "object" && oldVal != null && typeof newVal == "object" && newVal != null) { + _generate(oldVal, newVal, patches, path + "/" + helpers_js_1.escapePathComponent(key), invertible); + } + else { + if (oldVal !== newVal) { + changed = true; + if (invertible) { + patches.push({ op: "test", path: path + "/" + helpers_js_1.escapePathComponent(key), value: helpers_js_1._deepClone(oldVal) }); + } + patches.push({ op: "replace", path: path + "/" + helpers_js_1.escapePathComponent(key), value: helpers_js_1._deepClone(newVal) }); + } + } + } + else if (Array.isArray(mirror) === Array.isArray(obj)) { + if (invertible) { + patches.push({ op: "test", path: path + "/" + helpers_js_1.escapePathComponent(key), value: helpers_js_1._deepClone(oldVal) }); + } + patches.push({ op: "remove", path: path + "/" + helpers_js_1.escapePathComponent(key) }); + deleted = true; // property has been deleted + } + else { + if (invertible) { + patches.push({ op: "test", path: path, value: mirror }); + } + patches.push({ op: "replace", path: path, value: obj }); + changed = true; + } + } + if (!deleted && newKeys.length == oldKeys.length) { + return; + } + for (var t = 0; t < newKeys.length; t++) { + var key = newKeys[t]; + if (!helpers_js_1.hasOwnProperty(mirror, key) && obj[key] !== undefined) { + patches.push({ op: "add", path: path + "/" + helpers_js_1.escapePathComponent(key), value: helpers_js_1._deepClone(obj[key]) }); + } + } +} +/** + * Create an array of patches from the differences in two objects + */ +function compare(tree1, tree2, invertible) { + if (invertible === void 0) { invertible = false; } + var patches = []; + _generate(tree1, tree2, patches, '', invertible); + return patches; +} +exports.compare = compare; +/** + * Default export for backwards compat + */ +// import just to re-export as default +var core = require("./core.js"); +var helpers_js_2 = require("./helpers.js"); +exports.default = __assign({}, core, { + // duplex + unobserve: unobserve, + observe: observe, + generate: generate, + compare: compare, + // helpers + JsonPatchError: helpers_js_2.PatchError, deepClone: helpers_js_1._deepClone, escapePathComponent: helpers_js_1.escapePathComponent, + unescapePathComponent: helpers_js_2.unescapePathComponent }); diff --git a/commonjs/helpers.js b/commonjs/helpers.js new file mode 100644 index 00000000..0ac28b4d --- /dev/null +++ b/commonjs/helpers.js @@ -0,0 +1,181 @@ +/*! + * https://github.com/Starcounter-Jack/JSON-Patch + * (c) 2017 Joachim Wester + * MIT license + */ +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var _hasOwnProperty = Object.prototype.hasOwnProperty; +function hasOwnProperty(obj, key) { + return _hasOwnProperty.call(obj, key); +} +exports.hasOwnProperty = hasOwnProperty; +function _objectKeys(obj) { + if (Array.isArray(obj)) { + var keys = new Array(obj.length); + for (var k = 0; k < keys.length; k++) { + keys[k] = "" + k; + } + return keys; + } + if (Object.keys) { + return Object.keys(obj); + } + var keys = []; + for (var i in obj) { + if (hasOwnProperty(obj, i)) { + keys.push(i); + } + } + return keys; +} +exports._objectKeys = _objectKeys; +; +/** +* Deeply clone the object. +* https://jsperf.com/deep-copy-vs-json-stringify-json-parse/25 (recursiveDeepCopy) +* @param {any} obj value to clone +* @return {any} cloned obj +*/ +function _deepClone(obj) { + switch (typeof obj) { + case "object": + return JSON.parse(JSON.stringify(obj)); //Faster than ES5 clone - http://jsperf.com/deep-cloning-of-objects/5 + case "undefined": + return null; //this is how JSON.stringify behaves for array items + default: + return obj; //no need to clone primitives + } +} +exports._deepClone = _deepClone; +//3x faster than cached /^\d+$/.test(str) +function isInteger(str) { + var i = 0; + var len = str.length; + var charCode; + while (i < len) { + charCode = str.charCodeAt(i); + if (charCode >= 48 && charCode <= 57) { + i++; + continue; + } + return false; + } + return true; +} +exports.isInteger = isInteger; +/** +* Escapes a json pointer path +* @param path The raw pointer +* @return the Escaped path +*/ +function escapePathComponent(path) { + if (path.indexOf('/') === -1 && path.indexOf('~') === -1) + return path; + return path.replace(/~/g, '~0').replace(/\//g, '~1'); +} +exports.escapePathComponent = escapePathComponent; +/** + * Unescapes a json pointer path + * @param path The escaped pointer + * @return The unescaped path + */ +function unescapePathComponent(path) { + return path.replace(/~1/g, '/').replace(/~0/g, '~'); +} +exports.unescapePathComponent = unescapePathComponent; +function _getPathRecursive(root, obj) { + var found; + for (var key in root) { + if (hasOwnProperty(root, key)) { + if (root[key] === obj) { + return escapePathComponent(key) + '/'; + } + else if (typeof root[key] === 'object') { + found = _getPathRecursive(root[key], obj); + if (found != '') { + return escapePathComponent(key) + '/' + found; + } + } + } + } + return ''; +} +exports._getPathRecursive = _getPathRecursive; +function getPath(root, obj) { + if (root === obj) { + return '/'; + } + var path = _getPathRecursive(root, obj); + if (path === '') { + throw new Error("Object not found in root"); + } + return '/' + path; +} +exports.getPath = getPath; +/** +* Recursively checks whether an object has any undefined values inside. +*/ +function hasUndefined(obj) { + if (obj === undefined) { + return true; + } + if (obj) { + if (Array.isArray(obj)) { + for (var i = 0, len = obj.length; i < len; i++) { + if (hasUndefined(obj[i])) { + return true; + } + } + } + else if (typeof obj === "object") { + var objKeys = _objectKeys(obj); + var objKeysLength = objKeys.length; + for (var i = 0; i < objKeysLength; i++) { + if (hasUndefined(obj[objKeys[i]])) { + return true; + } + } + } + } + return false; +} +exports.hasUndefined = hasUndefined; +function patchErrorMessageFormatter(message, args) { + var messageParts = [message]; + for (var key in args) { + var value = typeof args[key] === 'object' ? JSON.stringify(args[key], null, 2) : args[key]; // pretty print + if (typeof value !== 'undefined') { + messageParts.push(key + ": " + value); + } + } + return messageParts.join('\n'); +} +var PatchError = /** @class */ (function (_super) { + __extends(PatchError, _super); + function PatchError(message, name, index, operation, tree) { + var _newTarget = this.constructor; + var _this = _super.call(this, patchErrorMessageFormatter(message, { name: name, index: index, operation: operation, tree: tree })) || this; + _this.name = name; + _this.index = index; + _this.operation = operation; + _this.tree = tree; + Object.setPrototypeOf(_this, _newTarget.prototype); // restore prototype chain, see https://stackoverflow.com/a/48342359 + _this.message = patchErrorMessageFormatter(message, { name: name, index: index, operation: operation, tree: tree }); + return _this; + } + return PatchError; +}(Error)); +exports.PatchError = PatchError; diff --git a/dist/fast-json-patch.js b/dist/fast-json-patch.js index 4c120aae..4cd50e28 100644 --- a/dist/fast-json-patch.js +++ b/dist/fast-json-patch.js @@ -278,10 +278,9 @@ exports.PatchError = PatchError; /***/ (function(module, exports, __webpack_require__) { Object.defineProperty(exports, "__esModule", { value: true }); -var areEquals = __webpack_require__(3); -var helpers_1 = __webpack_require__(0); -exports.JsonPatchError = helpers_1.PatchError; -exports.deepClone = helpers_1._deepClone; +var helpers_js_1 = __webpack_require__(0); +exports.JsonPatchError = helpers_js_1.PatchError; +exports.deepClone = helpers_js_1._deepClone; /* We use a Javascript hash to store each function. Each hash entry (property) uses the operation identifiers specified in rfc6902. @@ -310,7 +309,7 @@ var objOps = { and is potentially unneeded */ var removed = getValueByPointer(document, this.path); if (removed) { - removed = helpers_1._deepClone(removed); + removed = helpers_js_1._deepClone(removed); } var originalValue = applyOperation(document, { op: "remove", path: this.from }).removed; applyOperation(document, { op: "add", path: this.path, value: originalValue }); @@ -319,11 +318,11 @@ var objOps = { copy: function (obj, key, document) { var valueToCopy = getValueByPointer(document, this.from); // enforce copy by value so further operations don't affect source (see issue #177) - applyOperation(document, { op: "add", path: this.path, value: helpers_1._deepClone(valueToCopy) }); + applyOperation(document, { op: "add", path: this.path, value: helpers_js_1._deepClone(valueToCopy) }); return { newDocument: document }; }, test: function (obj, key, document) { - return { newDocument: document, test: areEquals(obj[key], this.value) }; + return { newDocument: document, test: _areEquals(obj[key], this.value) }; }, _get: function (obj, key, document) { this.value = obj[key]; @@ -333,7 +332,7 @@ var objOps = { /* The operations applicable to an array. Many are the same as for the object */ var arrOps = { add: function (arr, i, document) { - if (helpers_1.isInteger(i)) { + if (helpers_js_1.isInteger(i)) { arr.splice(i, 0, this.value); } else { // array props @@ -420,7 +419,7 @@ function applyOperation(document, operation, validateOperation, mutateDocument, return returnValue; } else if (operation.op === 'test') { - returnValue.test = areEquals(document, operation.value); + returnValue.test = _areEquals(document, operation.value); if (returnValue.test === false) { throw new exports.JsonPatchError("Test operation failed", 'TEST_OPERATION_FAILED', index, operation, document); } @@ -447,7 +446,7 @@ function applyOperation(document, operation, validateOperation, mutateDocument, } /* END ROOT OPERATIONS */ else { if (!mutateDocument) { - document = helpers_1._deepClone(document); + document = helpers_js_1._deepClone(document); } var path = operation.path || ""; var keys = path.split('/'); @@ -487,10 +486,10 @@ function applyOperation(document, operation, validateOperation, mutateDocument, key = obj.length; } else { - if (validateOperation && !helpers_1.isInteger(key)) { + if (validateOperation && !helpers_js_1.isInteger(key)) { throw new exports.JsonPatchError("Expected an unsigned base-10 integer value, making the new referenced value the array element with the zero-based index", "OPERATION_PATH_ILLEGAL_ARRAY_INDEX", index, operation, document); } // only parse key when it's an integer for `arr.prop` to work - else if (helpers_1.isInteger(key)) { + else if (helpers_js_1.isInteger(key)) { key = ~~key; } } @@ -507,7 +506,7 @@ function applyOperation(document, operation, validateOperation, mutateDocument, } else { if (key && key.indexOf('~') != -1) { - key = helpers_1.unescapePathComponent(key); + key = helpers_js_1.unescapePathComponent(key); } if (t >= len) { var returnValue = objOps[operation.op].call(operation, obj, key, document); // Apply patch @@ -545,7 +544,7 @@ function applyPatch(document, patch, validateOperation, mutateDocument, banProto } } if (!mutateDocument) { - document = helpers_1._deepClone(document); + document = helpers_js_1._deepClone(document); } var results = new Array(patch.length); for (var i = 0, length_1 = patch.length; i < length_1; i++) { @@ -601,7 +600,7 @@ function validator(operation, index, document, existingPathFragment) { else if ((operation.op === 'add' || operation.op === 'replace' || operation.op === 'test') && operation.value === undefined) { throw new exports.JsonPatchError('Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)', 'OPERATION_VALUE_REQUIRED', index, operation, document); } - else if ((operation.op === 'add' || operation.op === 'replace' || operation.op === 'test') && helpers_1.hasUndefined(operation.value)) { + else if ((operation.op === 'add' || operation.op === 'replace' || operation.op === 'test') && helpers_js_1.hasUndefined(operation.value)) { throw new exports.JsonPatchError('Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)', 'OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED', index, operation, document); } else if (document) { @@ -641,7 +640,7 @@ function validate(sequence, document, externalValidator) { } if (document) { //clone document and sequence so that we can safely try applying operations - applyPatch(helpers_1._deepClone(document), helpers_1._deepClone(sequence), externalValidator || true); + applyPatch(helpers_js_1._deepClone(document), helpers_js_1._deepClone(sequence), externalValidator || true); } else { externalValidator = externalValidator || validator; @@ -660,6 +659,58 @@ function validate(sequence, document, externalValidator) { } } exports.validate = validate; +// based on https://github.com/epoberezkin/fast-deep-equal +// MIT License +// Copyright (c) 2017 Evgeny Poberezkin +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +function _areEquals(a, b) { + if (a === b) + return true; + if (a && b && typeof a == 'object' && typeof b == 'object') { + var arrA = Array.isArray(a), arrB = Array.isArray(b), i, length, key; + if (arrA && arrB) { + length = a.length; + if (length != b.length) + return false; + for (i = length; i-- !== 0;) + if (!_areEquals(a[i], b[i])) + return false; + return true; + } + if (arrA != arrB) + return false; + var keys = Object.keys(a); + length = keys.length; + if (length !== Object.keys(b).length) + return false; + for (i = length; i-- !== 0;) + if (!b.hasOwnProperty(keys[i])) + return false; + for (i = length; i-- !== 0;) { + key = keys[i]; + if (!_areEquals(a[key], b[key])) + return false; + } + return true; + } + return a !== a && b !== b; +} +exports._areEquals = _areEquals; +; /** * Default export for backwards compat */ @@ -671,7 +722,8 @@ exports.default = { applyPatch: applyPatch, applyReducer: applyReducer, validator: validator, - validate: validate + validate: validate, + _areEquals: _areEquals }; @@ -679,6 +731,23 @@ exports.default = { /* 2 */ /***/ (function(module, exports, __webpack_require__) { +var core = __webpack_require__(1); +Object.assign(exports, core); + +var duplex = __webpack_require__(3); +Object.assign(exports, duplex); + +var helpers = __webpack_require__(0); +exports.JsonPatchError = helpers.PatchError; +exports.deepClone = helpers._deepClone; +exports.escapePathComponent = helpers.escapePathComponent; +exports.unescapePathComponent = helpers.unescapePathComponent; + + +/***/ }), +/* 3 */ +/***/ (function(module, exports, __webpack_require__) { + var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { @@ -696,22 +765,8 @@ Object.defineProperty(exports, "__esModule", { value: true }); * (c) 2017 Joachim Wester * MIT license */ -var helpers_1 = __webpack_require__(0); -var core_1 = __webpack_require__(1); -/* export all core functions and types */ -var core_2 = __webpack_require__(1); -exports.applyOperation = core_2.applyOperation; -exports.applyPatch = core_2.applyPatch; -exports.applyReducer = core_2.applyReducer; -exports.getValueByPointer = core_2.getValueByPointer; -exports.validate = core_2.validate; -exports.validator = core_2.validator; -/* export some helpers */ -var helpers_2 = __webpack_require__(0); -exports.JsonPatchError = helpers_2.PatchError; -exports.deepClone = helpers_2._deepClone; -exports.escapePathComponent = helpers_2.escapePathComponent; -exports.unescapePathComponent = helpers_2.unescapePathComponent; +var helpers_js_1 = __webpack_require__(0); +var core_js_1 = __webpack_require__(1); var beforeDict = new WeakMap(); var Mirror = /** @class */ (function () { function Mirror(obj) { @@ -762,7 +817,7 @@ function observe(obj, callback) { return observer; } observer = {}; - mirror.value = helpers_1._deepClone(obj); + mirror.value = helpers_js_1._deepClone(obj); if (callback) { observer.callback = callback; observer.next = null; @@ -807,7 +862,7 @@ function generate(observer, invertible) { var mirror = beforeDict.get(observer.object); _generate(mirror.value, observer.object, observer.patches, "", invertible); if (observer.patches.length) { - core_1.applyPatch(mirror.value, observer.patches); + core_js_1.applyPatch(mirror.value, observer.patches); } var temp = observer.patches; if (temp.length > 0) { @@ -827,34 +882,34 @@ function _generate(mirror, obj, patches, path, invertible) { if (typeof obj.toJSON === "function") { obj = obj.toJSON(); } - var newKeys = helpers_1._objectKeys(obj); - var oldKeys = helpers_1._objectKeys(mirror); + var newKeys = helpers_js_1._objectKeys(obj); + var oldKeys = helpers_js_1._objectKeys(mirror); var changed = false; var deleted = false; //if ever "move" operation is implemented here, make sure this test runs OK: "should not generate the same patch twice (move)" for (var t = oldKeys.length - 1; t >= 0; t--) { var key = oldKeys[t]; var oldVal = mirror[key]; - if (helpers_1.hasOwnProperty(obj, key) && !(obj[key] === undefined && oldVal !== undefined && Array.isArray(obj) === false)) { + if (helpers_js_1.hasOwnProperty(obj, key) && !(obj[key] === undefined && oldVal !== undefined && Array.isArray(obj) === false)) { var newVal = obj[key]; if (typeof oldVal == "object" && oldVal != null && typeof newVal == "object" && newVal != null) { - _generate(oldVal, newVal, patches, path + "/" + helpers_1.escapePathComponent(key), invertible); + _generate(oldVal, newVal, patches, path + "/" + helpers_js_1.escapePathComponent(key), invertible); } else { if (oldVal !== newVal) { changed = true; if (invertible) { - patches.push({ op: "test", path: path + "/" + helpers_1.escapePathComponent(key), value: helpers_1._deepClone(oldVal) }); + patches.push({ op: "test", path: path + "/" + helpers_js_1.escapePathComponent(key), value: helpers_js_1._deepClone(oldVal) }); } - patches.push({ op: "replace", path: path + "/" + helpers_1.escapePathComponent(key), value: helpers_1._deepClone(newVal) }); + patches.push({ op: "replace", path: path + "/" + helpers_js_1.escapePathComponent(key), value: helpers_js_1._deepClone(newVal) }); } } } else if (Array.isArray(mirror) === Array.isArray(obj)) { if (invertible) { - patches.push({ op: "test", path: path + "/" + helpers_1.escapePathComponent(key), value: helpers_1._deepClone(oldVal) }); + patches.push({ op: "test", path: path + "/" + helpers_js_1.escapePathComponent(key), value: helpers_js_1._deepClone(oldVal) }); } - patches.push({ op: "remove", path: path + "/" + helpers_1.escapePathComponent(key) }); + patches.push({ op: "remove", path: path + "/" + helpers_js_1.escapePathComponent(key) }); deleted = true; // property has been deleted } else { @@ -870,8 +925,8 @@ function _generate(mirror, obj, patches, path, invertible) { } for (var t = 0; t < newKeys.length; t++) { var key = newKeys[t]; - if (!helpers_1.hasOwnProperty(mirror, key) && obj[key] !== undefined) { - patches.push({ op: "add", path: path + "/" + helpers_1.escapePathComponent(key), value: helpers_1._deepClone(obj[key]) }); + if (!helpers_js_1.hasOwnProperty(mirror, key) && obj[key] !== undefined) { + patches.push({ op: "add", path: path + "/" + helpers_js_1.escapePathComponent(key), value: helpers_js_1._deepClone(obj[key]) }); } } } @@ -890,7 +945,7 @@ exports.compare = compare; */ // import just to re-export as default var core = __webpack_require__(1); -var helpers_3 = __webpack_require__(0); +var helpers_js_2 = __webpack_require__(0); exports.default = __assign({}, core, { // duplex unobserve: unobserve, @@ -898,70 +953,8 @@ exports.default = __assign({}, core, { generate: generate, compare: compare, // helpers - JsonPatchError: helpers_3.PatchError, deepClone: helpers_1._deepClone, escapePathComponent: helpers_1.escapePathComponent, - unescapePathComponent: helpers_3.unescapePathComponent }); - - -/***/ }), -/* 3 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var isArray = Array.isArray; -var keyList = Object.keys; -var hasProp = Object.prototype.hasOwnProperty; - -module.exports = function equal(a, b) { - if (a === b) return true; - - if (a && b && typeof a == 'object' && typeof b == 'object') { - var arrA = isArray(a) - , arrB = isArray(b) - , i - , length - , key; - - if (arrA && arrB) { - length = a.length; - if (length != b.length) return false; - for (i = length; i-- !== 0;) - if (!equal(a[i], b[i])) return false; - return true; - } - - if (arrA != arrB) return false; - - var dateA = a instanceof Date - , dateB = b instanceof Date; - if (dateA != dateB) return false; - if (dateA && dateB) return a.getTime() == b.getTime(); - - var regexpA = a instanceof RegExp - , regexpB = b instanceof RegExp; - if (regexpA != regexpB) return false; - if (regexpA && regexpB) return a.toString() == b.toString(); - - var keys = keyList(a); - length = keys.length; - - if (length !== keyList(b).length) - return false; - - for (i = length; i-- !== 0;) - if (!hasProp.call(b, keys[i])) return false; - - for (i = length; i-- !== 0;) { - key = keys[i]; - if (!equal(a[key], b[key])) return false; - } - - return true; - } - - return a!==a && b!==b; -}; + JsonPatchError: helpers_js_2.PatchError, deepClone: helpers_js_1._deepClone, escapePathComponent: helpers_js_1.escapePathComponent, + unescapePathComponent: helpers_js_2.unescapePathComponent }); /***/ }) diff --git a/dist/fast-json-patch.min.js b/dist/fast-json-patch.min.js index aaf25009..89646f44 100644 --- a/dist/fast-json-patch.min.js +++ b/dist/fast-json-patch.min.js @@ -5,10 +5,10 @@ var jsonpatch=function(e){var t={};function r(n){if(t[n])return t[n].exports;var * (c) 2017 Joachim Wester * MIT license */ -var r,n=this&&this.__extends||(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)},function(e,t){function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)});Object.defineProperty(t,"__esModule",{value:!0});var o=Object.prototype.hasOwnProperty;function a(e,t){return o.call(e,t)}function i(e){if(Array.isArray(e)){for(var t=new Array(e.length),r=0;r=48&&t<=57))return!1;r++}return!0},t.escapePathComponent=p,t.unescapePathComponent=function(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")},t._getPathRecursive=u,t.getPath=function(e,t){if(e===t)return"/";var r=u(e,t);if(""===r)throw new Error("Object not found in root");return"/"+r},t.hasUndefined=function e(t){if(void 0===t)return!0;if(t)if(Array.isArray(t)){for(var r=0,n=t.length;r=w){if(u&&"add"===r.op&&O>v.length)throw new t.JsonPatchError("The specified index MUST NOT be greater than the number of elements in the array","OPERATION_VALUE_OUT_OF_BOUNDS",l,r,e);if(!1===(h=i[r.op].call(r,v,O,e)).test)throw new t.JsonPatchError("Test operation failed","TEST_OPERATION_FAILED",l,r,e);return h}}else if(O&&-1!=O.indexOf("~")&&(O=o.unescapePathComponent(O)),y>=w){if(!1===(h=a[r.op].call(r,v,O,e)).test)throw new t.JsonPatchError("Test operation failed","TEST_OPERATION_FAILED",l,r,e);return h}v=v[O]}}function s(e,r,n,a,i){if(void 0===a&&(a=!0),void 0===i&&(i=!0),n&&!Array.isArray(r))throw new t.JsonPatchError("Patch sequence must be an array","SEQUENCE_NOT_AN_ARRAY");a||(e=o._deepClone(e));for(var p=new Array(r.length),s=0,c=r.length;s0)throw new t.JsonPatchError('Operation `path` property must start with "/"',"OPERATION_PATH_INVALID",r,e,n);if(("move"===e.op||"copy"===e.op)&&"string"!=typeof e.from)throw new t.JsonPatchError("Operation `from` property is not present (applicable in `move` and `copy` operations)","OPERATION_FROM_REQUIRED",r,e,n);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&void 0===e.value)throw new t.JsonPatchError("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_REQUIRED",r,e,n);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&o.hasUndefined(e.value))throw new t.JsonPatchError("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED",r,e,n);if(n)if("add"==e.op){var p=e.path.split("/").length,u=i.split("/").length;if(p!==u+1&&p!==u)throw new t.JsonPatchError("Cannot perform an `add` operation at the desired path","OPERATION_PATH_CANNOT_ADD",r,e,n)}else if("replace"===e.op||"remove"===e.op||"_get"===e.op){if(e.path!==i)throw new t.JsonPatchError("Cannot perform the operation at a path that does not exist","OPERATION_PATH_UNRESOLVABLE",r,e,n)}else if("move"===e.op||"copy"===e.op){var s=l([{op:"_get",path:e.from,value:void 0}],n);if(s&&"OPERATION_PATH_UNRESOLVABLE"===s.name)throw new t.JsonPatchError("Cannot perform the operation from a path that does not exist","OPERATION_FROM_UNRESOLVABLE",r,e,n)}}function l(e,r,n){try{if(!Array.isArray(e))throw new t.JsonPatchError("Patch sequence must be an array","SEQUENCE_NOT_AN_ARRAY");if(r)s(o._deepClone(r),o._deepClone(e),n||!0);else{n=n||f;for(var a=0;a=48&&t<=57))return!1;r++}return!0},t.escapePathComponent=p,t.unescapePathComponent=function(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")},t._getPathRecursive=u,t.getPath=function(e,t){if(e===t)return"/";var r=u(e,t);if(""===r)throw new Error("Object not found in root");return"/"+r},t.hasUndefined=function e(t){if(void 0===t)return!0;if(t)if(Array.isArray(t)){for(var r=0,n=t.length;r=w){if(p&&"add"===r.op&&O>v.length)throw new t.JsonPatchError("The specified index MUST NOT be greater than the number of elements in the array","OPERATION_VALUE_OUT_OF_BOUNDS",f,r,e);if(!1===(h=a[r.op].call(r,v,O,e)).test)throw new t.JsonPatchError("Test operation failed","TEST_OPERATION_FAILED",f,r,e);return h}}else if(O&&-1!=O.indexOf("~")&&(O=n.unescapePathComponent(O)),y>=w){if(!1===(h=o[r.op].call(r,v,O,e)).test)throw new t.JsonPatchError("Test operation failed","TEST_OPERATION_FAILED",f,r,e);return h}v=v[O]}}function u(e,r,o,a,i){if(void 0===a&&(a=!0),void 0===i&&(i=!0),o&&!Array.isArray(r))throw new t.JsonPatchError("Patch sequence must be an array","SEQUENCE_NOT_AN_ARRAY");a||(e=n._deepClone(e));for(var u=new Array(r.length),s=0,c=r.length;s0)throw new t.JsonPatchError('Operation `path` property must start with "/"',"OPERATION_PATH_INVALID",r,e,a);if(("move"===e.op||"copy"===e.op)&&"string"!=typeof e.from)throw new t.JsonPatchError("Operation `from` property is not present (applicable in `move` and `copy` operations)","OPERATION_FROM_REQUIRED",r,e,a);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&void 0===e.value)throw new t.JsonPatchError("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_REQUIRED",r,e,a);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&n.hasUndefined(e.value))throw new t.JsonPatchError("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED",r,e,a);if(a)if("add"==e.op){var p=e.path.split("/").length,u=i.split("/").length;if(p!==u+1&&p!==u)throw new t.JsonPatchError("Cannot perform an `add` operation at the desired path","OPERATION_PATH_CANNOT_ADD",r,e,a)}else if("replace"===e.op||"remove"===e.op||"_get"===e.op){if(e.path!==i)throw new t.JsonPatchError("Cannot perform the operation at a path that does not exist","OPERATION_PATH_UNRESOLVABLE",r,e,a)}else if("move"===e.op||"copy"===e.op){var s=f([{op:"_get",path:e.from,value:void 0}],a);if(s&&"OPERATION_PATH_UNRESOLVABLE"===s.name)throw new t.JsonPatchError("Cannot perform the operation from a path that does not exist","OPERATION_FROM_UNRESOLVABLE",r,e,a)}}function f(e,r,o){try{if(!Array.isArray(e))throw new t.JsonPatchError("Patch sequence must be an array","SEQUENCE_NOT_AN_ARRAY");if(r)u(n._deepClone(r),n._deepClone(e),o||!0);else{o=o||c;for(var a=0;a0&&(e.patches=[],e.callback&&e.callback(n)),n}function d(e,t,r,n,a){if(t!==e){"function"==typeof t.toJSON&&(t=t.toJSON());for(var i=o._objectKeys(t),p=o._objectKeys(e),u=!1,s=p.length-1;s>=0;s--){var c=e[l=p[s]];if(!o.hasOwnProperty(t,l)||void 0===t[l]&&void 0!==c&&!1===Array.isArray(t))Array.isArray(e)===Array.isArray(t)?(a&&r.push({op:"test",path:n+"/"+o.escapePathComponent(l),value:o._deepClone(c)}),r.push({op:"remove",path:n+"/"+o.escapePathComponent(l)}),u=!0):(a&&r.push({op:"test",path:n,value:e}),r.push({op:"replace",path:n,value:t}),!0);else{var f=t[l];"object"==typeof c&&null!=c&&"object"==typeof f&&null!=f?d(c,f,r,n+"/"+o.escapePathComponent(l),a):c!==f&&(!0,a&&r.push({op:"test",path:n+"/"+o.escapePathComponent(l),value:o._deepClone(c)}),r.push({op:"replace",path:n+"/"+o.escapePathComponent(l),value:o._deepClone(f)}))}}if(u||i.length!=p.length)for(s=0;s0&&(e.patches=[],e.callback&&e.callback(n)),n}function l(e,t,r,n,a){if(t!==e){"function"==typeof t.toJSON&&(t=t.toJSON());for(var i=o._objectKeys(t),p=o._objectKeys(e),u=!1,s=p.length-1;s>=0;s--){var c=e[h=p[s]];if(!o.hasOwnProperty(t,h)||void 0===t[h]&&void 0!==c&&!1===Array.isArray(t))Array.isArray(e)===Array.isArray(t)?(a&&r.push({op:"test",path:n+"/"+o.escapePathComponent(h),value:o._deepClone(c)}),r.push({op:"remove",path:n+"/"+o.escapePathComponent(h)}),u=!0):(a&&r.push({op:"test",path:n,value:e}),r.push({op:"replace",path:n,value:t}),!0);else{var f=t[h];"object"==typeof c&&null!=c&&"object"==typeof f&&null!=f?l(c,f,r,n+"/"+o.escapePathComponent(h),a):c!==f&&(!0,a&&r.push({op:"test",path:n+"/"+o.escapePathComponent(h),value:o._deepClone(c)}),r.push({op:"replace",path:n+"/"+o.escapePathComponent(h),value:o._deepClone(f)}))}}if(u||i.length!=p.length)for(s=0;s(root: T, observer: duplex.Observer): void; + observe(obj: Object | T[], callback?: (patches: core.Operation[]) => void): duplex.Observer; + generate(observer: duplex.Observer, invertible?: boolean): core.Operation[]; + compare(tree1: Object | any[], tree2: Object | any[], invertible?: boolean): core.Operation[]; + getValueByPointer(document: any, pointer: string): any; + applyOperation(document: T, operation: core.Operation, validateOperation?: boolean | core.Validator, mutateDocument?: boolean, banPrototypeModifications?: boolean, index?: number): core.OperationResult; + applyPatch(document: T, patch: core.Operation[], validateOperation?: boolean | core.Validator, mutateDocument?: boolean, banPrototypeModifications?: boolean): core.PatchResult; + applyReducer(document: T, operation: core.Operation, index: number): T; + validator(operation: core.Operation, index: number, document?: any, existingPathFragment?: string): void; + validate(sequence: core.Operation[], document?: T, externalValidator?: core.Validator): JsonPatchError; + _areEquals(a: any, b: any): boolean; +}; +export default _default; \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 00000000..d53ed04b --- /dev/null +++ b/index.js @@ -0,0 +1,11 @@ +var core = require("./commonjs/core.js"); +Object.assign(exports, core); + +var duplex = require("./commonjs/duplex.js"); +Object.assign(exports, duplex); + +var helpers = require("./commonjs/helpers.js"); +exports.JsonPatchError = helpers.PatchError; +exports.deepClone = helpers._deepClone; +exports.escapePathComponent = helpers.escapePathComponent; +exports.unescapePathComponent = helpers.unescapePathComponent; diff --git a/index.mjs b/index.mjs new file mode 100644 index 00000000..b21cf4a6 --- /dev/null +++ b/index.mjs @@ -0,0 +1,31 @@ +export * from './module/core.mjs'; +export * from './module/duplex.mjs'; +export { + PatchError as JsonPatchError, + _deepClone as deepClone, + escapePathComponent, + unescapePathComponent +} from './module/helpers.mjs'; + + +/** + * Default export for backwards compat + */ + +import * as core from './module/core.mjs'; +import * as duplex from './module/duplex.mjs'; +import { + PatchError as JsonPatchError, + _deepClone as deepClone, + escapePathComponent, + unescapePathComponent +} from './module/helpers.mjs'; + +export default { + ...core, + ...duplex, + JsonPatchError, + deepClone, + escapePathComponent, + unescapePathComponent +} \ No newline at end of file diff --git a/index.ts b/index.ts new file mode 100644 index 00000000..10d72781 --- /dev/null +++ b/index.ts @@ -0,0 +1,31 @@ +export * from './src/core'; +export * from './src/duplex'; +export { + PatchError as JsonPatchError, + _deepClone as deepClone, + escapePathComponent, + unescapePathComponent +} from './src/helpers'; + + +/** + * Default export for backwards compat + */ + +import * as core from './src/core'; +import * as duplex from './src/duplex'; +import { + PatchError as JsonPatchError, + _deepClone as deepClone, + escapePathComponent, + unescapePathComponent +} from './src/helpers'; + +export default { + ...core, + ...duplex, + JsonPatchError, + deepClone, + escapePathComponent, + unescapePathComponent +} \ No newline at end of file diff --git a/jasmine-run.mjs b/jasmine-run.mjs new file mode 100644 index 00000000..68c9addd --- /dev/null +++ b/jasmine-run.mjs @@ -0,0 +1,23 @@ +import glob from 'glob'; +import Jasmine from 'jasmine'; + +const jasmine = new Jasmine(); +jasmine.loadConfigFile('test/jasmine.json'); + +const pattern = process.argv[2] || 'test/spec/*.js'; + +// Load your specs +glob(pattern, function (er, files) { + Promise.all( + files + // Use relative paths + .map(f => f.replace(/^([^\/])/, './$1')) + .map(f => import(f) + .catch(e => { + console.error('** Error loading ' + f + ': '); + console.error(e); + process.exit(1); + })) + ) + .then(() => jasmine.execute()); +}); \ No newline at end of file diff --git a/module/core.d.ts b/module/core.d.ts new file mode 100644 index 00000000..a5d7e0ed --- /dev/null +++ b/module/core.d.ts @@ -0,0 +1,126 @@ +import { PatchError, _deepClone } from './helpers.js'; +export declare const JsonPatchError: typeof PatchError; +export declare const deepClone: typeof _deepClone; +export declare type Operation = AddOperation | RemoveOperation | ReplaceOperation | MoveOperation | CopyOperation | TestOperation | GetOperation; +export interface Validator { + (operation: Operation, index: number, document: T, existingPathFragment: string): void; +} +export interface OperationResult { + removed?: any; + test?: boolean; + newDocument: T; +} +export interface BaseOperation { + path: string; +} +export interface AddOperation extends BaseOperation { + op: 'add'; + value: T; +} +export interface RemoveOperation extends BaseOperation { + op: 'remove'; +} +export interface ReplaceOperation extends BaseOperation { + op: 'replace'; + value: T; +} +export interface MoveOperation extends BaseOperation { + op: 'move'; + from: string; +} +export interface CopyOperation extends BaseOperation { + op: 'copy'; + from: string; +} +export interface TestOperation extends BaseOperation { + op: 'test'; + value: T; +} +export interface GetOperation extends BaseOperation { + op: '_get'; + value: T; +} +export interface PatchResult extends Array> { + newDocument: T; +} +/** + * Retrieves a value from a JSON document by a JSON pointer. + * Returns the value. + * + * @param document The document to get the value from + * @param pointer an escaped JSON pointer + * @return The retrieved value + */ +export declare function getValueByPointer(document: any, pointer: string): any; +/** + * Apply a single JSON Patch Operation on a JSON document. + * Returns the {newDocument, result} of the operation. + * It modifies the `document` and `operation` objects - it gets the values by reference. + * If you would like to avoid touching your values, clone them: + * `jsonpatch.applyOperation(document, jsonpatch._deepClone(operation))`. + * + * @param document The document to patch + * @param operation The operation to apply + * @param validateOperation `false` is without validation, `true` to use default jsonpatch's validation, or you can pass a `validateOperation` callback to be used for validation. + * @param mutateDocument Whether to mutate the original document or clone it before applying + * @param banPrototypeModifications Whether to ban modifications to `__proto__`, defaults to `true`. + * @return `{newDocument, result}` after the operation + */ +export declare function applyOperation(document: T, operation: Operation, validateOperation?: boolean | Validator, mutateDocument?: boolean, banPrototypeModifications?: boolean, index?: number): OperationResult; +/** + * Apply a full JSON Patch array on a JSON document. + * Returns the {newDocument, result} of the patch. + * It modifies the `document` object and `patch` - it gets the values by reference. + * If you would like to avoid touching your values, clone them: + * `jsonpatch.applyPatch(document, jsonpatch._deepClone(patch))`. + * + * @param document The document to patch + * @param patch The patch to apply + * @param validateOperation `false` is without validation, `true` to use default jsonpatch's validation, or you can pass a `validateOperation` callback to be used for validation. + * @param mutateDocument Whether to mutate the original document or clone it before applying + * @param banPrototypeModifications Whether to ban modifications to `__proto__`, defaults to `true`. + * @return An array of `{newDocument, result}` after the patch + */ +export declare function applyPatch(document: T, patch: Operation[], validateOperation?: boolean | Validator, mutateDocument?: boolean, banPrototypeModifications?: boolean): PatchResult; +/** + * Apply a single JSON Patch Operation on a JSON document. + * Returns the updated document. + * Suitable as a reducer. + * + * @param document The document to patch + * @param operation The operation to apply + * @return The updated document + */ +export declare function applyReducer(document: T, operation: Operation, index: number): T; +/** + * Validates a single operation. Called from `jsonpatch.validate`. Throws `JsonPatchError` in case of an error. + * @param {object} operation - operation object (patch) + * @param {number} index - index of operation in the sequence + * @param {object} [document] - object where the operation is supposed to be applied + * @param {string} [existingPathFragment] - comes along with `document` + */ +export declare function validator(operation: Operation, index: number, document?: any, existingPathFragment?: string): void; +/** + * Validates a sequence of operations. If `document` parameter is provided, the sequence is additionally validated against the object document. + * If error is encountered, returns a JsonPatchError object + * @param sequence + * @param document + * @returns {JsonPatchError|undefined} + */ +export declare function validate(sequence: Operation[], document?: T, externalValidator?: Validator): PatchError; +export declare function _areEquals(a: any, b: any): boolean; +declare const _default: { + JsonPatchError: typeof PatchError; + deepClone: typeof _deepClone; + getValueByPointer: typeof getValueByPointer; + applyOperation: typeof applyOperation; + applyPatch: typeof applyPatch; + applyReducer: typeof applyReducer; + validator: typeof validator; + validate: typeof validate; + _areEquals: typeof _areEquals; +}; +/** + * Default export for backwards compat + */ +export default _default; diff --git a/module/core.mjs b/module/core.mjs new file mode 100644 index 00000000..1db15f20 --- /dev/null +++ b/module/core.mjs @@ -0,0 +1,440 @@ +import { PatchError, _deepClone, isInteger, unescapePathComponent, hasUndefined } from './helpers.mjs'; +export var JsonPatchError = PatchError; +export var deepClone = _deepClone; +/* We use a Javascript hash to store each + function. Each hash entry (property) uses + the operation identifiers specified in rfc6902. + In this way, we can map each patch operation + to its dedicated function in efficient way. + */ +/* The operations applicable to an object */ +var objOps = { + add: function (obj, key, document) { + obj[key] = this.value; + return { newDocument: document }; + }, + remove: function (obj, key, document) { + var removed = obj[key]; + delete obj[key]; + return { newDocument: document, removed: removed }; + }, + replace: function (obj, key, document) { + var removed = obj[key]; + obj[key] = this.value; + return { newDocument: document, removed: removed }; + }, + move: function (obj, key, document) { + /* in case move target overwrites an existing value, + return the removed value, this can be taxing performance-wise, + and is potentially unneeded */ + var removed = getValueByPointer(document, this.path); + if (removed) { + removed = _deepClone(removed); + } + var originalValue = applyOperation(document, { op: "remove", path: this.from }).removed; + applyOperation(document, { op: "add", path: this.path, value: originalValue }); + return { newDocument: document, removed: removed }; + }, + copy: function (obj, key, document) { + var valueToCopy = getValueByPointer(document, this.from); + // enforce copy by value so further operations don't affect source (see issue #177) + applyOperation(document, { op: "add", path: this.path, value: _deepClone(valueToCopy) }); + return { newDocument: document }; + }, + test: function (obj, key, document) { + return { newDocument: document, test: _areEquals(obj[key], this.value) }; + }, + _get: function (obj, key, document) { + this.value = obj[key]; + return { newDocument: document }; + } +}; +/* The operations applicable to an array. Many are the same as for the object */ +var arrOps = { + add: function (arr, i, document) { + if (isInteger(i)) { + arr.splice(i, 0, this.value); + } + else { // array props + arr[i] = this.value; + } + // this may be needed when using '-' in an array + return { newDocument: document, index: i }; + }, + remove: function (arr, i, document) { + var removedList = arr.splice(i, 1); + return { newDocument: document, removed: removedList[0] }; + }, + replace: function (arr, i, document) { + var removed = arr[i]; + arr[i] = this.value; + return { newDocument: document, removed: removed }; + }, + move: objOps.move, + copy: objOps.copy, + test: objOps.test, + _get: objOps._get +}; +/** + * Retrieves a value from a JSON document by a JSON pointer. + * Returns the value. + * + * @param document The document to get the value from + * @param pointer an escaped JSON pointer + * @return The retrieved value + */ +export function getValueByPointer(document, pointer) { + if (pointer == '') { + return document; + } + var getOriginalDestination = { op: "_get", path: pointer }; + applyOperation(document, getOriginalDestination); + return getOriginalDestination.value; +} +/** + * Apply a single JSON Patch Operation on a JSON document. + * Returns the {newDocument, result} of the operation. + * It modifies the `document` and `operation` objects - it gets the values by reference. + * If you would like to avoid touching your values, clone them: + * `jsonpatch.applyOperation(document, jsonpatch._deepClone(operation))`. + * + * @param document The document to patch + * @param operation The operation to apply + * @param validateOperation `false` is without validation, `true` to use default jsonpatch's validation, or you can pass a `validateOperation` callback to be used for validation. + * @param mutateDocument Whether to mutate the original document or clone it before applying + * @param banPrototypeModifications Whether to ban modifications to `__proto__`, defaults to `true`. + * @return `{newDocument, result}` after the operation + */ +export function applyOperation(document, operation, validateOperation, mutateDocument, banPrototypeModifications, index) { + if (validateOperation === void 0) { validateOperation = false; } + if (mutateDocument === void 0) { mutateDocument = true; } + if (banPrototypeModifications === void 0) { banPrototypeModifications = true; } + if (index === void 0) { index = 0; } + if (validateOperation) { + if (typeof validateOperation == 'function') { + validateOperation(operation, 0, document, operation.path); + } + else { + validator(operation, 0); + } + } + /* ROOT OPERATIONS */ + if (operation.path === "") { + var returnValue = { newDocument: document }; + if (operation.op === 'add') { + returnValue.newDocument = operation.value; + return returnValue; + } + else if (operation.op === 'replace') { + returnValue.newDocument = operation.value; + returnValue.removed = document; //document we removed + return returnValue; + } + else if (operation.op === 'move' || operation.op === 'copy') { // it's a move or copy to root + returnValue.newDocument = getValueByPointer(document, operation.from); // get the value by json-pointer in `from` field + if (operation.op === 'move') { // report removed item + returnValue.removed = document; + } + return returnValue; + } + else if (operation.op === 'test') { + returnValue.test = _areEquals(document, operation.value); + if (returnValue.test === false) { + throw new JsonPatchError("Test operation failed", 'TEST_OPERATION_FAILED', index, operation, document); + } + returnValue.newDocument = document; + return returnValue; + } + else if (operation.op === 'remove') { // a remove on root + returnValue.removed = document; + returnValue.newDocument = null; + return returnValue; + } + else if (operation.op === '_get') { + operation.value = document; + return returnValue; + } + else { /* bad operation */ + if (validateOperation) { + throw new JsonPatchError('Operation `op` property is not one of operations defined in RFC-6902', 'OPERATION_OP_INVALID', index, operation, document); + } + else { + return returnValue; + } + } + } /* END ROOT OPERATIONS */ + else { + if (!mutateDocument) { + document = _deepClone(document); + } + var path = operation.path || ""; + var keys = path.split('/'); + var obj = document; + var t = 1; //skip empty element - http://jsperf.com/to-shift-or-not-to-shift + var len = keys.length; + var existingPathFragment = undefined; + var key = void 0; + var validateFunction = void 0; + if (typeof validateOperation == 'function') { + validateFunction = validateOperation; + } + else { + validateFunction = validator; + } + while (true) { + key = keys[t]; + if (banPrototypeModifications && key == '__proto__') { + throw new TypeError('JSON-Patch: modifying `__proto__` prop is banned for security reasons, if this was on purpose, please set `banPrototypeModifications` flag false and pass it to this function. More info in fast-json-patch README'); + } + if (validateOperation) { + if (existingPathFragment === undefined) { + if (obj[key] === undefined) { + existingPathFragment = keys.slice(0, t).join('/'); + } + else if (t == len - 1) { + existingPathFragment = operation.path; + } + if (existingPathFragment !== undefined) { + validateFunction(operation, 0, document, existingPathFragment); + } + } + } + t++; + if (Array.isArray(obj)) { + if (key === '-') { + key = obj.length; + } + else { + if (validateOperation && !isInteger(key)) { + throw new JsonPatchError("Expected an unsigned base-10 integer value, making the new referenced value the array element with the zero-based index", "OPERATION_PATH_ILLEGAL_ARRAY_INDEX", index, operation, document); + } // only parse key when it's an integer for `arr.prop` to work + else if (isInteger(key)) { + key = ~~key; + } + } + if (t >= len) { + if (validateOperation && operation.op === "add" && key > obj.length) { + throw new JsonPatchError("The specified index MUST NOT be greater than the number of elements in the array", "OPERATION_VALUE_OUT_OF_BOUNDS", index, operation, document); + } + var returnValue = arrOps[operation.op].call(operation, obj, key, document); // Apply patch + if (returnValue.test === false) { + throw new JsonPatchError("Test operation failed", 'TEST_OPERATION_FAILED', index, operation, document); + } + return returnValue; + } + } + else { + if (key && key.indexOf('~') != -1) { + key = unescapePathComponent(key); + } + if (t >= len) { + var returnValue = objOps[operation.op].call(operation, obj, key, document); // Apply patch + if (returnValue.test === false) { + throw new JsonPatchError("Test operation failed", 'TEST_OPERATION_FAILED', index, operation, document); + } + return returnValue; + } + } + obj = obj[key]; + } + } +} +/** + * Apply a full JSON Patch array on a JSON document. + * Returns the {newDocument, result} of the patch. + * It modifies the `document` object and `patch` - it gets the values by reference. + * If you would like to avoid touching your values, clone them: + * `jsonpatch.applyPatch(document, jsonpatch._deepClone(patch))`. + * + * @param document The document to patch + * @param patch The patch to apply + * @param validateOperation `false` is without validation, `true` to use default jsonpatch's validation, or you can pass a `validateOperation` callback to be used for validation. + * @param mutateDocument Whether to mutate the original document or clone it before applying + * @param banPrototypeModifications Whether to ban modifications to `__proto__`, defaults to `true`. + * @return An array of `{newDocument, result}` after the patch + */ +export function applyPatch(document, patch, validateOperation, mutateDocument, banPrototypeModifications) { + if (mutateDocument === void 0) { mutateDocument = true; } + if (banPrototypeModifications === void 0) { banPrototypeModifications = true; } + if (validateOperation) { + if (!Array.isArray(patch)) { + throw new JsonPatchError('Patch sequence must be an array', 'SEQUENCE_NOT_AN_ARRAY'); + } + } + if (!mutateDocument) { + document = _deepClone(document); + } + var results = new Array(patch.length); + for (var i = 0, length_1 = patch.length; i < length_1; i++) { + // we don't need to pass mutateDocument argument because if it was true, we already deep cloned the object, we'll just pass `true` + results[i] = applyOperation(document, patch[i], validateOperation, true, banPrototypeModifications, i); + document = results[i].newDocument; // in case root was replaced + } + results.newDocument = document; + return results; +} +/** + * Apply a single JSON Patch Operation on a JSON document. + * Returns the updated document. + * Suitable as a reducer. + * + * @param document The document to patch + * @param operation The operation to apply + * @return The updated document + */ +export function applyReducer(document, operation, index) { + var operationResult = applyOperation(document, operation); + if (operationResult.test === false) { // failed test + throw new JsonPatchError("Test operation failed", 'TEST_OPERATION_FAILED', index, operation, document); + } + return operationResult.newDocument; +} +/** + * Validates a single operation. Called from `jsonpatch.validate`. Throws `JsonPatchError` in case of an error. + * @param {object} operation - operation object (patch) + * @param {number} index - index of operation in the sequence + * @param {object} [document] - object where the operation is supposed to be applied + * @param {string} [existingPathFragment] - comes along with `document` + */ +export function validator(operation, index, document, existingPathFragment) { + if (typeof operation !== 'object' || operation === null || Array.isArray(operation)) { + throw new JsonPatchError('Operation is not an object', 'OPERATION_NOT_AN_OBJECT', index, operation, document); + } + else if (!objOps[operation.op]) { + throw new JsonPatchError('Operation `op` property is not one of operations defined in RFC-6902', 'OPERATION_OP_INVALID', index, operation, document); + } + else if (typeof operation.path !== 'string') { + throw new JsonPatchError('Operation `path` property is not a string', 'OPERATION_PATH_INVALID', index, operation, document); + } + else if (operation.path.indexOf('/') !== 0 && operation.path.length > 0) { + // paths that aren't empty string should start with "/" + throw new JsonPatchError('Operation `path` property must start with "/"', 'OPERATION_PATH_INVALID', index, operation, document); + } + else if ((operation.op === 'move' || operation.op === 'copy') && typeof operation.from !== 'string') { + throw new JsonPatchError('Operation `from` property is not present (applicable in `move` and `copy` operations)', 'OPERATION_FROM_REQUIRED', index, operation, document); + } + else if ((operation.op === 'add' || operation.op === 'replace' || operation.op === 'test') && operation.value === undefined) { + throw new JsonPatchError('Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)', 'OPERATION_VALUE_REQUIRED', index, operation, document); + } + else if ((operation.op === 'add' || operation.op === 'replace' || operation.op === 'test') && hasUndefined(operation.value)) { + throw new JsonPatchError('Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)', 'OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED', index, operation, document); + } + else if (document) { + if (operation.op == "add") { + var pathLen = operation.path.split("/").length; + var existingPathLen = existingPathFragment.split("/").length; + if (pathLen !== existingPathLen + 1 && pathLen !== existingPathLen) { + throw new JsonPatchError('Cannot perform an `add` operation at the desired path', 'OPERATION_PATH_CANNOT_ADD', index, operation, document); + } + } + else if (operation.op === 'replace' || operation.op === 'remove' || operation.op === '_get') { + if (operation.path !== existingPathFragment) { + throw new JsonPatchError('Cannot perform the operation at a path that does not exist', 'OPERATION_PATH_UNRESOLVABLE', index, operation, document); + } + } + else if (operation.op === 'move' || operation.op === 'copy') { + var existingValue = { op: "_get", path: operation.from, value: undefined }; + var error = validate([existingValue], document); + if (error && error.name === 'OPERATION_PATH_UNRESOLVABLE') { + throw new JsonPatchError('Cannot perform the operation from a path that does not exist', 'OPERATION_FROM_UNRESOLVABLE', index, operation, document); + } + } + } +} +/** + * Validates a sequence of operations. If `document` parameter is provided, the sequence is additionally validated against the object document. + * If error is encountered, returns a JsonPatchError object + * @param sequence + * @param document + * @returns {JsonPatchError|undefined} + */ +export function validate(sequence, document, externalValidator) { + try { + if (!Array.isArray(sequence)) { + throw new JsonPatchError('Patch sequence must be an array', 'SEQUENCE_NOT_AN_ARRAY'); + } + if (document) { + //clone document and sequence so that we can safely try applying operations + applyPatch(_deepClone(document), _deepClone(sequence), externalValidator || true); + } + else { + externalValidator = externalValidator || validator; + for (var i = 0; i < sequence.length; i++) { + externalValidator(sequence[i], i, document, undefined); + } + } + } + catch (e) { + if (e instanceof JsonPatchError) { + return e; + } + else { + throw e; + } + } +} +// based on https://github.com/epoberezkin/fast-deep-equal +// MIT License +// Copyright (c) 2017 Evgeny Poberezkin +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +export function _areEquals(a, b) { + if (a === b) + return true; + if (a && b && typeof a == 'object' && typeof b == 'object') { + var arrA = Array.isArray(a), arrB = Array.isArray(b), i, length, key; + if (arrA && arrB) { + length = a.length; + if (length != b.length) + return false; + for (i = length; i-- !== 0;) + if (!_areEquals(a[i], b[i])) + return false; + return true; + } + if (arrA != arrB) + return false; + var keys = Object.keys(a); + length = keys.length; + if (length !== Object.keys(b).length) + return false; + for (i = length; i-- !== 0;) + if (!b.hasOwnProperty(keys[i])) + return false; + for (i = length; i-- !== 0;) { + key = keys[i]; + if (!_areEquals(a[key], b[key])) + return false; + } + return true; + } + return a !== a && b !== b; +} +; +/** + * Default export for backwards compat + */ +export default { + JsonPatchError: JsonPatchError, + deepClone: deepClone, + getValueByPointer: getValueByPointer, + applyOperation: applyOperation, + applyPatch: applyPatch, + applyReducer: applyReducer, + validator: validator, + validate: validate, + _areEquals: _areEquals +}; diff --git a/module/duplex.d.ts b/module/duplex.d.ts new file mode 100644 index 00000000..3e369304 --- /dev/null +++ b/module/duplex.d.ts @@ -0,0 +1,63 @@ +/*! + * https://github.com/Starcounter-Jack/JSON-Patch + * (c) 2017 Joachim Wester + * MIT license + */ +import { _deepClone, escapePathComponent } from './helpers.js'; +import { applyPatch, Operation } from './core.js'; +export interface Observer { + object: T; + patches: Operation[]; + unobserve: () => void; + callback: (patches: Operation[]) => void; +} +/** + * Detach an observer from an object + */ +export declare function unobserve(root: T, observer: Observer): void; +/** + * Observes changes made to an object, which can then be retrieved using generate + */ +export declare function observe(obj: Object | Array, callback?: (patches: Operation[]) => void): Observer; +/** + * Generate an array of patches from an observer + */ +export declare function generate(observer: Observer, invertible?: boolean): Operation[]; +/** + * Create an array of patches from the differences in two objects + */ +export declare function compare(tree1: Object | Array, tree2: Object | Array, invertible?: boolean): Operation[]; +/** + * Default export for backwards compat + */ +import * as core from './core.js'; +import { PatchError as JsonPatchError, unescapePathComponent } from './helpers.js'; +declare const _default: { + unobserve: typeof unobserve; + observe: typeof observe; + generate: typeof generate; + compare: typeof compare; + JsonPatchError: typeof JsonPatchError; + deepClone: typeof _deepClone; + escapePathComponent: typeof escapePathComponent; + unescapePathComponent: typeof unescapePathComponent; + getValueByPointer(document: any, pointer: string): any; + applyOperation(document: T, operation: Operation, validateOperation?: boolean | core.Validator, mutateDocument?: boolean, banPrototypeModifications?: boolean, index?: number): core.OperationResult; + applyPatch(document: T, patch: Operation[], validateOperation?: boolean | core.Validator, mutateDocument?: boolean, banPrototypeModifications?: boolean): core.PatchResult; + applyReducer(document: T, operation: Operation, index: number): T; + validator(operation: Operation, index: number, document?: any, existingPathFragment?: string): void; + validate(sequence: Operation[], document?: T, externalValidator?: core.Validator): JsonPatchError; + _areEquals(a: any, b: any): boolean; + default: { + JsonPatchError: typeof JsonPatchError; + deepClone: typeof _deepClone; + getValueByPointer: typeof core.getValueByPointer; + applyOperation: typeof core.applyOperation; + applyPatch: typeof applyPatch; + applyReducer: typeof core.applyReducer; + validator: typeof core.validator; + validate: typeof core.validate; + _areEquals: typeof core._areEquals; + }; +}; +export default _default; diff --git a/module/duplex.mjs b/module/duplex.mjs new file mode 100644 index 00000000..8449eb55 --- /dev/null +++ b/module/duplex.mjs @@ -0,0 +1,202 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +/*! + * https://github.com/Starcounter-Jack/JSON-Patch + * (c) 2017 Joachim Wester + * MIT license + */ +import { _deepClone, _objectKeys, escapePathComponent, hasOwnProperty } from './helpers.mjs'; +import { applyPatch } from './core.mjs'; +var beforeDict = new WeakMap(); +var Mirror = /** @class */ (function () { + function Mirror(obj) { + this.observers = new Map(); + this.obj = obj; + } + return Mirror; +}()); +var ObserverInfo = /** @class */ (function () { + function ObserverInfo(callback, observer) { + this.callback = callback; + this.observer = observer; + } + return ObserverInfo; +}()); +function getMirror(obj) { + return beforeDict.get(obj); +} +function getObserverFromMirror(mirror, callback) { + return mirror.observers.get(callback); +} +function removeObserverFromMirror(mirror, observer) { + mirror.observers.delete(observer.callback); +} +/** + * Detach an observer from an object + */ +export function unobserve(root, observer) { + observer.unobserve(); +} +/** + * Observes changes made to an object, which can then be retrieved using generate + */ +export function observe(obj, callback) { + var patches = []; + var observer; + var mirror = getMirror(obj); + if (!mirror) { + mirror = new Mirror(obj); + beforeDict.set(obj, mirror); + } + else { + var observerInfo = getObserverFromMirror(mirror, callback); + observer = observerInfo && observerInfo.observer; + } + if (observer) { + return observer; + } + observer = {}; + mirror.value = _deepClone(obj); + if (callback) { + observer.callback = callback; + observer.next = null; + var dirtyCheck = function () { + generate(observer); + }; + var fastCheck = function () { + clearTimeout(observer.next); + observer.next = setTimeout(dirtyCheck); + }; + if (typeof window !== 'undefined') { //not Node + window.addEventListener('mouseup', fastCheck); + window.addEventListener('keyup', fastCheck); + window.addEventListener('mousedown', fastCheck); + window.addEventListener('keydown', fastCheck); + window.addEventListener('change', fastCheck); + } + } + observer.patches = patches; + observer.object = obj; + observer.unobserve = function () { + generate(observer); + clearTimeout(observer.next); + removeObserverFromMirror(mirror, observer); + if (typeof window !== 'undefined') { + window.removeEventListener('mouseup', fastCheck); + window.removeEventListener('keyup', fastCheck); + window.removeEventListener('mousedown', fastCheck); + window.removeEventListener('keydown', fastCheck); + window.removeEventListener('change', fastCheck); + } + }; + mirror.observers.set(callback, new ObserverInfo(callback, observer)); + return observer; +} +/** + * Generate an array of patches from an observer + */ +export function generate(observer, invertible) { + if (invertible === void 0) { invertible = false; } + var mirror = beforeDict.get(observer.object); + _generate(mirror.value, observer.object, observer.patches, "", invertible); + if (observer.patches.length) { + applyPatch(mirror.value, observer.patches); + } + var temp = observer.patches; + if (temp.length > 0) { + observer.patches = []; + if (observer.callback) { + observer.callback(temp); + } + } + return temp; +} +// Dirty check if obj is different from mirror, generate patches and update mirror +function _generate(mirror, obj, patches, path, invertible) { + if (obj === mirror) { + return; + } + if (typeof obj.toJSON === "function") { + obj = obj.toJSON(); + } + var newKeys = _objectKeys(obj); + var oldKeys = _objectKeys(mirror); + var changed = false; + var deleted = false; + //if ever "move" operation is implemented here, make sure this test runs OK: "should not generate the same patch twice (move)" + for (var t = oldKeys.length - 1; t >= 0; t--) { + var key = oldKeys[t]; + var oldVal = mirror[key]; + if (hasOwnProperty(obj, key) && !(obj[key] === undefined && oldVal !== undefined && Array.isArray(obj) === false)) { + var newVal = obj[key]; + if (typeof oldVal == "object" && oldVal != null && typeof newVal == "object" && newVal != null) { + _generate(oldVal, newVal, patches, path + "/" + escapePathComponent(key), invertible); + } + else { + if (oldVal !== newVal) { + changed = true; + if (invertible) { + patches.push({ op: "test", path: path + "/" + escapePathComponent(key), value: _deepClone(oldVal) }); + } + patches.push({ op: "replace", path: path + "/" + escapePathComponent(key), value: _deepClone(newVal) }); + } + } + } + else if (Array.isArray(mirror) === Array.isArray(obj)) { + if (invertible) { + patches.push({ op: "test", path: path + "/" + escapePathComponent(key), value: _deepClone(oldVal) }); + } + patches.push({ op: "remove", path: path + "/" + escapePathComponent(key) }); + deleted = true; // property has been deleted + } + else { + if (invertible) { + patches.push({ op: "test", path: path, value: mirror }); + } + patches.push({ op: "replace", path: path, value: obj }); + changed = true; + } + } + if (!deleted && newKeys.length == oldKeys.length) { + return; + } + for (var t = 0; t < newKeys.length; t++) { + var key = newKeys[t]; + if (!hasOwnProperty(mirror, key) && obj[key] !== undefined) { + patches.push({ op: "add", path: path + "/" + escapePathComponent(key), value: _deepClone(obj[key]) }); + } + } +} +/** + * Create an array of patches from the differences in two objects + */ +export function compare(tree1, tree2, invertible) { + if (invertible === void 0) { invertible = false; } + var patches = []; + _generate(tree1, tree2, patches, '', invertible); + return patches; +} +/** + * Default export for backwards compat + */ +// import just to re-export as default +import * as core from './core.mjs'; +import { PatchError as JsonPatchError, unescapePathComponent } from './helpers.mjs'; +export default __assign({}, core, { + // duplex + unobserve: unobserve, + observe: observe, + generate: generate, + compare: compare, + // helpers + JsonPatchError: JsonPatchError, deepClone: _deepClone, escapePathComponent: escapePathComponent, + unescapePathComponent: unescapePathComponent }); diff --git a/module/helpers.d.ts b/module/helpers.d.ts new file mode 100644 index 00000000..034b1a60 --- /dev/null +++ b/module/helpers.d.ts @@ -0,0 +1,41 @@ +/*! + * https://github.com/Starcounter-Jack/JSON-Patch + * (c) 2017 Joachim Wester + * MIT license + */ +export declare function hasOwnProperty(obj: any, key: any): any; +export declare function _objectKeys(obj: any): any[]; +/** +* Deeply clone the object. +* https://jsperf.com/deep-copy-vs-json-stringify-json-parse/25 (recursiveDeepCopy) +* @param {any} obj value to clone +* @return {any} cloned obj +*/ +export declare function _deepClone(obj: any): any; +export declare function isInteger(str: string): boolean; +/** +* Escapes a json pointer path +* @param path The raw pointer +* @return the Escaped path +*/ +export declare function escapePathComponent(path: string): string; +/** + * Unescapes a json pointer path + * @param path The escaped pointer + * @return The unescaped path + */ +export declare function unescapePathComponent(path: string): string; +export declare function _getPathRecursive(root: Object, obj: Object): string; +export declare function getPath(root: Object, obj: Object): string; +/** +* Recursively checks whether an object has any undefined values inside. +*/ +export declare function hasUndefined(obj: any): boolean; +export declare type JsonPatchErrorName = 'SEQUENCE_NOT_AN_ARRAY' | 'OPERATION_NOT_AN_OBJECT' | 'OPERATION_OP_INVALID' | 'OPERATION_PATH_INVALID' | 'OPERATION_FROM_REQUIRED' | 'OPERATION_VALUE_REQUIRED' | 'OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED' | 'OPERATION_PATH_CANNOT_ADD' | 'OPERATION_PATH_UNRESOLVABLE' | 'OPERATION_FROM_UNRESOLVABLE' | 'OPERATION_PATH_ILLEGAL_ARRAY_INDEX' | 'OPERATION_VALUE_OUT_OF_BOUNDS' | 'TEST_OPERATION_FAILED'; +export declare class PatchError extends Error { + name: JsonPatchErrorName; + index?: number; + operation?: any; + tree?: any; + constructor(message: string, name: JsonPatchErrorName, index?: number, operation?: any, tree?: any); +} diff --git a/module/helpers.mjs b/module/helpers.mjs new file mode 100644 index 00000000..10844fe8 --- /dev/null +++ b/module/helpers.mjs @@ -0,0 +1,171 @@ +/*! + * https://github.com/Starcounter-Jack/JSON-Patch + * (c) 2017 Joachim Wester + * MIT license + */ +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var _hasOwnProperty = Object.prototype.hasOwnProperty; +export function hasOwnProperty(obj, key) { + return _hasOwnProperty.call(obj, key); +} +export function _objectKeys(obj) { + if (Array.isArray(obj)) { + var keys = new Array(obj.length); + for (var k = 0; k < keys.length; k++) { + keys[k] = "" + k; + } + return keys; + } + if (Object.keys) { + return Object.keys(obj); + } + var keys = []; + for (var i in obj) { + if (hasOwnProperty(obj, i)) { + keys.push(i); + } + } + return keys; +} +; +/** +* Deeply clone the object. +* https://jsperf.com/deep-copy-vs-json-stringify-json-parse/25 (recursiveDeepCopy) +* @param {any} obj value to clone +* @return {any} cloned obj +*/ +export function _deepClone(obj) { + switch (typeof obj) { + case "object": + return JSON.parse(JSON.stringify(obj)); //Faster than ES5 clone - http://jsperf.com/deep-cloning-of-objects/5 + case "undefined": + return null; //this is how JSON.stringify behaves for array items + default: + return obj; //no need to clone primitives + } +} +//3x faster than cached /^\d+$/.test(str) +export function isInteger(str) { + var i = 0; + var len = str.length; + var charCode; + while (i < len) { + charCode = str.charCodeAt(i); + if (charCode >= 48 && charCode <= 57) { + i++; + continue; + } + return false; + } + return true; +} +/** +* Escapes a json pointer path +* @param path The raw pointer +* @return the Escaped path +*/ +export function escapePathComponent(path) { + if (path.indexOf('/') === -1 && path.indexOf('~') === -1) + return path; + return path.replace(/~/g, '~0').replace(/\//g, '~1'); +} +/** + * Unescapes a json pointer path + * @param path The escaped pointer + * @return The unescaped path + */ +export function unescapePathComponent(path) { + return path.replace(/~1/g, '/').replace(/~0/g, '~'); +} +export function _getPathRecursive(root, obj) { + var found; + for (var key in root) { + if (hasOwnProperty(root, key)) { + if (root[key] === obj) { + return escapePathComponent(key) + '/'; + } + else if (typeof root[key] === 'object') { + found = _getPathRecursive(root[key], obj); + if (found != '') { + return escapePathComponent(key) + '/' + found; + } + } + } + } + return ''; +} +export function getPath(root, obj) { + if (root === obj) { + return '/'; + } + var path = _getPathRecursive(root, obj); + if (path === '') { + throw new Error("Object not found in root"); + } + return '/' + path; +} +/** +* Recursively checks whether an object has any undefined values inside. +*/ +export function hasUndefined(obj) { + if (obj === undefined) { + return true; + } + if (obj) { + if (Array.isArray(obj)) { + for (var i = 0, len = obj.length; i < len; i++) { + if (hasUndefined(obj[i])) { + return true; + } + } + } + else if (typeof obj === "object") { + var objKeys = _objectKeys(obj); + var objKeysLength = objKeys.length; + for (var i = 0; i < objKeysLength; i++) { + if (hasUndefined(obj[objKeys[i]])) { + return true; + } + } + } + } + return false; +} +function patchErrorMessageFormatter(message, args) { + var messageParts = [message]; + for (var key in args) { + var value = typeof args[key] === 'object' ? JSON.stringify(args[key], null, 2) : args[key]; // pretty print + if (typeof value !== 'undefined') { + messageParts.push(key + ": " + value); + } + } + return messageParts.join('\n'); +} +var PatchError = /** @class */ (function (_super) { + __extends(PatchError, _super); + function PatchError(message, name, index, operation, tree) { + var _newTarget = this.constructor; + var _this = _super.call(this, patchErrorMessageFormatter(message, { name: name, index: index, operation: operation, tree: tree })) || this; + _this.name = name; + _this.index = index; + _this.operation = operation; + _this.tree = tree; + Object.setPrototypeOf(_this, _newTarget.prototype); // restore prototype chain, see https://stackoverflow.com/a/48342359 + _this.message = patchErrorMessageFormatter(message, { name: name, index: index, operation: operation, tree: tree }); + return _this; + } + return PatchError; +}(Error)); +export { PatchError }; diff --git a/package-lock.json b/package-lock.json index aefb0f17..85352857 100644 --- a/package-lock.json +++ b/package-lock.json @@ -192,34 +192,12 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, - "abab": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", - "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", - "dev": true - }, "acorn": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.2.1.tgz", "integrity": "sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q==", "dev": true }, - "acorn-globals": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.2.tgz", - "integrity": "sha512-BbzvZhVtZP+Bs1J1HcwrQe8ycfO0wStkSGxuul3He3GkHOIZ6eTqOkPuw9IP1X3+IkOo4wiJmwkobzXYz4wewQ==", - "dev": true, - "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" - } - }, - "acorn-walk": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", - "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", - "dev": true - }, "adm-zip": { "version": "0.4.13", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.13.tgz", @@ -319,12 +297,6 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", - "dev": true - }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", @@ -402,12 +374,6 @@ "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", "dev": true }, - "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", - "dev": true - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -593,12 +559,6 @@ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", "dev": true }, - "browser-process-hrtime": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", - "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", - "dev": true - }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -700,9 +660,9 @@ "dev": true }, "cacache": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.3.tgz", - "integrity": "sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==", + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.2.tgz", + "integrity": "sha512-ifKgxH2CKhJEg6tNdAwziu6Q33EvuG26tYcda6PT3WKisZcYDXsnEdnRv67Po3yCzFfaSoMjGZzJyD2c3DT1dg==", "dev": true, "requires": { "bluebird": "^3.5.5", @@ -710,6 +670,7 @@ "figgy-pudding": "^3.5.1", "glob": "^7.1.4", "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "mkdirp": "^0.5.1", @@ -1078,21 +1039,6 @@ "randomfill": "^1.0.3" } }, - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, - "cssstyle": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", - "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", - "dev": true, - "requires": { - "cssom": "0.3.x" - } - }, "cyclist": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", @@ -1108,17 +1054,6 @@ "assert-plus": "^1.0.0" } }, - "data-urls": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", - "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "whatwg-mimetype": "^2.2.0", - "whatwg-url": "^7.0.0" - } - }, "date-now": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", @@ -1146,12 +1081,6 @@ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, "define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", @@ -1232,15 +1161,6 @@ "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, - "domexception": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", - "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", - "dev": true, - "requires": { - "webidl-conversions": "^4.0.2" - } - }, "dot-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-2.1.1.tgz", @@ -1361,19 +1281,6 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, - "escodegen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", - "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", - "dev": true, - "requires": { - "esprima": "^3.1.3", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - } - }, "eslint-scope": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", @@ -1384,12 +1291,6 @@ "estraverse": "^4.1.1" } }, - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "dev": true - }, "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", @@ -1405,10 +1306,10 @@ "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", "dev": true }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "dev": true }, "eventemitter3": { @@ -1608,7 +1509,8 @@ "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true }, "fast-json-stable-stringify": { "version": "2.0.0", @@ -1616,12 +1518,6 @@ "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", "dev": true }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, "figgy-pudding": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", @@ -1782,7 +1678,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -1803,12 +1700,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1823,17 +1722,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -1950,7 +1852,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -1962,6 +1865,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1976,6 +1880,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1983,12 +1888,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -2007,6 +1914,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -2087,7 +1995,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -2099,6 +2008,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -2184,7 +2094,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -2220,6 +2131,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2239,6 +2151,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2282,12 +2195,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -2507,15 +2422,6 @@ "parse-passwd": "^1.0.0" } }, - "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.1" - } - }, "http-proxy": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", @@ -2570,15 +2476,6 @@ "debug": "^3.1.0" } }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, "ieee754": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", @@ -2613,6 +2510,12 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2647,12 +2550,6 @@ "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, - "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true - }, "is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", @@ -2871,40 +2768,6 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "dev": true }, - "jsdom": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.1.1.tgz", - "integrity": "sha512-cQZRBB33arrDAeCrAEWn1U3SvrvC8XysBua9Oqg1yWrsY/gYcusloJC3RZJXuY5eehSCmws8f2YeliCqGSkrtQ==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "acorn": "^6.1.1", - "acorn-globals": "^4.3.2", - "array-equal": "^1.0.0", - "cssom": "^0.3.6", - "cssstyle": "^1.2.2", - "data-urls": "^1.1.0", - "domexception": "^1.0.1", - "escodegen": "^1.11.1", - "html-encoding-sniffer": "^1.0.2", - "nwsapi": "^2.1.4", - "parse5": "5.1.0", - "pn": "^1.1.0", - "request": "^2.88.0", - "request-promise-native": "^1.0.7", - "saxes": "^3.1.9", - "symbol-tree": "^3.2.2", - "tough-cookie": "^3.0.1", - "w3c-hr-time": "^1.0.1", - "w3c-xmlserializer": "^1.1.2", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^7.0.0", - "ws": "^7.0.0", - "xml-name-validator": "^3.0.0" - } - }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -2938,16 +2801,6 @@ "minimist": "^1.2.0" } }, - "jsonfile": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-5.0.0.tgz", - "integrity": "sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^0.1.2" - } - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -2987,16 +2840,6 @@ "invert-kv": "^2.0.0" } }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, "lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -3039,12 +2882,6 @@ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, "lower-case": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", @@ -3403,12 +3240,6 @@ "path-key": "^2.0.0" } }, - "nwsapi": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.4.tgz", - "integrity": "sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw==", - "dev": true - }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -3503,28 +3334,6 @@ } } }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" - }, - "dependencies": { - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - } - } - }, "os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", @@ -3636,12 +3445,6 @@ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, - "parse5": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", - "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", - "dev": true - }, "pascal-case": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-2.0.1.tgz", @@ -3737,12 +3540,6 @@ "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==", "dev": true }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", - "dev": true - }, "portfinder": { "version": "1.0.21", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.21.tgz", @@ -3777,12 +3574,6 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -3808,9 +3599,9 @@ "dev": true }, "psl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", - "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.3.0.tgz", + "integrity": "sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==", "dev": true }, "public-encrypt": { @@ -3993,59 +3784,11 @@ "uuid": "^3.3.2" }, "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "dev": true, - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - } - } - } - }, - "request-promise-core": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", - "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", - "dev": true, - "requires": { - "lodash": "^4.17.11" - } - }, - "request-promise-native": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", - "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", - "dev": true, - "requires": { - "request-promise-core": "1.1.2", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - }, - "dependencies": { - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } } } }, @@ -4208,15 +3951,6 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true }, - "saxes": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz", - "integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==", - "dev": true, - "requires": { - "xmlchars": "^2.1.1" - } - }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", @@ -4391,12 +4125,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true } } }, @@ -4478,9 +4206,9 @@ "dev": true }, "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, "source-map-resolve": { @@ -4497,13 +4225,21 @@ } }, "source-map-support": { - "version": "0.5.12", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", - "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "source-map-url": { @@ -4568,12 +4304,6 @@ } } }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true - }, "stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", @@ -4675,12 +4405,6 @@ "upper-case": "^1.1.1" } }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -4696,24 +4420,39 @@ "commander": "^2.20.0", "source-map": "~0.6.1", "source-map-support": "~0.5.12" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "terser-webpack-plugin": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.3.0.tgz", - "integrity": "sha512-W2YWmxPjjkUcOWa4pBEv4OP4er1aeQJlSo2UhtCFQCuRXEHjOFscO8VyWHj9JLlA0RzQb8Y2/Ta78XZvT54uGg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz", + "integrity": "sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==", "dev": true, "requires": { - "cacache": "^11.3.2", - "find-cache-dir": "^2.0.0", + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", "is-wsl": "^1.1.0", - "loader-utils": "^1.2.3", "schema-utils": "^1.0.0", "serialize-javascript": "^1.7.0", "source-map": "^0.6.1", - "terser": "^4.0.0", - "webpack-sources": "^1.3.0", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", "worker-farm": "^1.7.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "through2": { @@ -4803,23 +4542,21 @@ } }, "tough-cookie": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", - "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", - "dev": true, - "requires": { - "ip-regex": "^2.1.0", - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "dev": true, "requires": { - "punycode": "^2.1.0" + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } } }, "tslib": { @@ -4849,15 +4586,6 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -4870,12 +4598,6 @@ "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", "dev": true }, - "underscore": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", - "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", - "dev": true - }, "union": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/union/-/union-0.4.6.tgz", @@ -4915,12 +4637,6 @@ "imurmurhash": "^0.1.4" } }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -5079,26 +4795,6 @@ "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==", "dev": true }, - "w3c-hr-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", - "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", - "dev": true, - "requires": { - "browser-process-hrtime": "^0.1.2" - } - }, - "w3c-xmlserializer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz", - "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==", - "dev": true, - "requires": { - "domexception": "^1.0.1", - "webidl-conversions": "^4.0.2", - "xml-name-validator": "^3.0.0" - } - }, "watchpack": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", @@ -5110,41 +4806,35 @@ "neo-async": "^2.5.0" } }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, "webpack": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.37.0.tgz", - "integrity": "sha512-iJPPvL7XpbcbwOthbzpa2BSPlmGp8lGDokAj/LdWtK80rsPoPOdANSbDBf2GAVLKZD3GhCuQ/gGkgN9HWs0Keg==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.39.0.tgz", + "integrity": "sha512-nrxFNSEKm4T1C/EsgOgN50skt//Pl4X7kgJC1MrlE47M292LSCVmMOC47iTGL0CGxbdwhKGgeThrJcw0bstEfA==", "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", "@webassemblyjs/helper-module-context": "1.8.5", "@webassemblyjs/wasm-edit": "1.8.5", "@webassemblyjs/wasm-parser": "1.8.5", - "acorn": "^6.2.0", - "ajv": "^6.1.0", - "ajv-keywords": "^3.1.0", - "chrome-trace-event": "^1.0.0", + "acorn": "^6.2.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^4.1.0", - "eslint-scope": "^4.0.0", + "eslint-scope": "^4.0.3", "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.3.0", - "loader-utils": "^1.1.0", - "memory-fs": "~0.4.1", - "micromatch": "^3.1.8", - "mkdirp": "~0.5.0", - "neo-async": "^2.5.0", - "node-libs-browser": "^2.0.0", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.1", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", "schema-utils": "^1.0.0", - "tapable": "^1.1.0", - "terser-webpack-plugin": "^1.1.0", - "watchpack": "^1.5.0", - "webpack-sources": "^1.3.0" + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.1", + "watchpack": "^1.6.0", + "webpack-sources": "^1.4.1" } }, "webpack-cli": { @@ -5197,39 +4887,21 @@ } }, "webpack-sources": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", - "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.1.tgz", + "integrity": "sha512-XSz38193PTo/1csJabKaV4b53uRVotlMgqJXm3s3eje0Bu6gQTxYDqpD38CmQfDBA+gN+QqaGjasuC8I/7eW3Q==", "dev": true, "requires": { "source-list-map": "^2.0.0", "source-map": "~0.6.1" - } - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", - "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "which": { @@ -5279,21 +4951,6 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, - "ws": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.1.1.tgz", - "integrity": "sha512-o41D/WmDeca0BqYhsr3nJzQyg9NF5X8l/UdnFNux9cS3lwB+swm8qGWX5rn+aD6xfBU3rGmtHij7g7x6LxFU3A==", - "dev": true, - "requires": { - "async-limiter": "^1.0.0" - } - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, "xml2js": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", @@ -5310,12 +4967,6 @@ "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", "dev": true }, - "xmlchars": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.1.1.tgz", - "integrity": "sha512-7hew1RPJ1iIuje/Y01bGD/mXokXxegAgVS+e+E0wSi2ILHQkYAH1+JXARwTjZSM4Z4Z+c73aKspEcqj+zPPL/w==", - "dev": true - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index d1e3d06f..14c6351f 100644 --- a/package.json +++ b/package.json @@ -22,34 +22,33 @@ "url": "http://www.starcounter.com/" }, "license": "MIT", - "main": "lib/duplex.js", - "typings": "lib/duplex.d.ts", - "engines": { - "node": ">= 0.4.0" - }, + "main": "index.js", + "module": "index.mjs", + "typings": "index.d.ts", "devDependencies": { "benchmark": "^2.1.4", "bluebird": "^3.5.5", "bluebird-retry": "^0.11.0", "chalk": "^2.4.2", + "event-target-shim": "^5.0.1", "fast-deep-equal": "^2.0.1", "http-server": "^0.11.1", "jasmine": "^3.4.0", - "jsdom": "^15.1.1", - "jsonfile": "^5.0.0", "request": "^2.88.0", "sauce-connect-launcher": "^1.2.7", "saucelabs": "^2.1.9", "selenium-webdriver": "^4.0.0-alpha.4", "typescript": "~3.5.2", - "underscore": "^1.9.1", "webpack": "^4.35.0", "webpack-cli": "^3.3.5" }, "scripts": { - "tsc": "tsc", - "version": "tsc && webpack && git add -A", - "build": "tsc && webpack", + "tsc": "npm run tsc-common && npm run tsc-module", + "tsc-common": "tsc", + "tsc-module": "tsc --module esnext --moduleResolution node --outDir \"module/\" && npm run tsc-to-mjs", + "tsc-to-mjs": "bash tsc-to-mjs.sh", + "version": "npm run tsc && webpack && git add -A", + "build": "npm run tsc && webpack", "serve": "http-server -p 5000 --silent", "tsc-watch": "tsc -w", "test": "npm run tsc && npm run test-core && npm run test-duplex && npm run test-commonjs && npm run test-webpack-import && npm run test-typings", @@ -57,13 +56,10 @@ "test-commonjs": "jasmine test/spec/commonjs/requireSpec.js", "test-webpack-import": "webpack --env.NODE_ENV=test && jasmine test/spec/webpack/importSpec.build.js", "test-typings": "tsc test/spec/typings/typingsSpec.ts", - "test-duplex": "jasmine DUPLEX=yes JASMINE_CONFIG_PATH=test/jasmine.json", - "test-core": "jasmine DUPLEX=no JASMINE_CONFIG_PATH=test/jasmine.json test/spec/jsonPatchTestsSpec.js test/spec/coreSpec.js test/spec/validateSpec.js", + "test-duplex": "node --experimental-modules jasmine-run.mjs test/**/*[sS]pec.mjs", + "test-core": "node --experimental-modules jasmine-run.mjs test/spec/jsonPatchTestsSpec.mjs test/spec/coreSpec.mjs test/spec/validateSpec.mjs", "bench": "npm run bench-core && npm run bench-duplex", "bench-core": "node test/spec/coreBenchmark.js", - "bench-duplex": "node test/spec/coreBenchmark.js DUPLEX=yes && node test/spec/duplexBenchmark.js" - }, - "dependencies": { - "fast-deep-equal": "^2.0.1" + "bench-duplex": "node test/spec/coreBenchmark.js && node test/spec/duplexBenchmark.js" } } diff --git a/src/core.ts b/src/core.ts index 35f2c21c..c8d743cb 100644 --- a/src/core.ts +++ b/src/core.ts @@ -5,8 +5,7 @@ */ declare var require: any; -const areEquals = require('fast-deep-equal'); -import { PatchError, _deepClone, isInteger, unescapePathComponent, hasUndefined } from './helpers'; +import { PatchError, _deepClone, isInteger, unescapePathComponent, hasUndefined } from './helpers.js'; export const JsonPatchError = PatchError; export const deepClone = _deepClone; @@ -64,13 +63,6 @@ export interface PatchResult extends Array> { newDocument: T; } -export interface Observer { - object: T; - patches: Operation[]; - unobserve: () => void; - callback: (patches: Operation[]) => void; -} - /* We use a Javascript hash to store each function. Each hash entry (property) uses the operation identifiers specified in rfc6902. @@ -123,7 +115,7 @@ const objOps = { return { newDocument: document } }, test: function (obj, key, document) { - return { newDocument: document, test: areEquals(obj[key], this.value) } + return { newDocument: document, test: _areEquals(obj[key], this.value) } }, _get: function (obj, key, document) { this.value = obj[key]; @@ -214,7 +206,7 @@ export function applyOperation(document: T, operation: Operation, validateOpe } return returnValue; } else if (operation.op === 'test') { - returnValue.test = areEquals(document, operation.value); + returnValue.test = _areEquals(document, operation.value); if (returnValue.test === false) { throw new JsonPatchError("Test operation failed", 'TEST_OPERATION_FAILED', index, operation, document); } @@ -458,17 +450,64 @@ export function validate(sequence: Operation[], document?: T, externalValidat } } -/** - * Default export for backwards compat - */ +// based on https://github.com/epoberezkin/fast-deep-equal +// MIT License + +// Copyright (c) 2017 Evgeny Poberezkin + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +export function _areEquals(a: any, b: any): boolean { + if (a === b) return true; + + if (a && b && typeof a == 'object' && typeof b == 'object') { + var arrA = Array.isArray(a) + , arrB = Array.isArray(b) + , i + , length + , key; + + if (arrA && arrB) { + length = a.length; + if (length != b.length) return false; + for (i = length; i-- !== 0;) + if (!_areEquals(a[i], b[i])) return false; + return true; + } + + if (arrA != arrB) return false; + + var keys = Object.keys(a); + length = keys.length; + + if (length !== Object.keys(b).length) + return false; + + for (i = length; i-- !== 0;) + if (!b.hasOwnProperty(keys[i])) return false; + + for (i = length; i-- !== 0;) { + key = keys[i]; + if (!_areEquals(a[key], b[key])) return false; + } + + return true; + } -export default { - JsonPatchError, - deepClone, - getValueByPointer, - applyOperation, - applyPatch, - applyReducer, - validator, - validate -} \ No newline at end of file + return a!==a && b!==b; +}; \ No newline at end of file diff --git a/src/duplex.ts b/src/duplex.ts index 4501efef..ba9658b7 100644 --- a/src/duplex.ts +++ b/src/duplex.ts @@ -3,14 +3,8 @@ * (c) 2017 Joachim Wester * MIT license */ -import { _deepClone, _objectKeys, escapePathComponent, hasOwnProperty } from './helpers'; -import { applyPatch, Operation } from './core'; - -/* export all core functions and types */ -export { applyOperation, applyPatch, applyReducer, getValueByPointer, Operation, AddOperation, RemoveOperation, ReplaceOperation, MoveOperation, CopyOperation, TestOperation, GetOperation, validate, validator, OperationResult } from './core'; - -/* export some helpers */ -export { PatchError as JsonPatchError, _deepClone as deepClone, escapePathComponent, unescapePathComponent } from './helpers'; +import { _deepClone, _objectKeys, escapePathComponent, hasOwnProperty } from './helpers.js'; +import { applyPatch, Operation } from './core.js'; export interface Observer { object: T; @@ -175,8 +169,8 @@ function _generate(mirror, obj, patches, path, invertible) { else { if (oldVal !== newVal) { changed = true; - if (invertible) { - patches.push({ op: "test", path: path + "/" + escapePathComponent(key), value: _deepClone(oldVal) }); + if (invertible) { + patches.push({ op: "test", path: path + "/" + escapePathComponent(key), value: _deepClone(oldVal) }); } patches.push({ op: "replace", path: path + "/" + escapePathComponent(key), value: _deepClone(newVal) }); } @@ -215,25 +209,4 @@ export function compare(tree1: Object | Array, tree2: Object | Array, var patches = []; _generate(tree1, tree2, patches, '', invertible); return patches; -} - -/** - * Default export for backwards compat - */ -// import just to re-export as default -import * as core from './core'; -import { PatchError as JsonPatchError, unescapePathComponent } from './helpers'; - -export default { - ...core, - // duplex - unobserve, - observe, - generate, - compare, - // helpers - JsonPatchError, - deepClone:_deepClone, - escapePathComponent, - unescapePathComponent } \ No newline at end of file diff --git a/src/helpers.ts b/src/helpers.ts index b7fe6b07..fad6242a 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -4,8 +4,6 @@ * MIT license */ -import { Operation } from "./core"; - const _hasOwnProperty = Object.prototype.hasOwnProperty; export function hasOwnProperty(obj, key) { return _hasOwnProperty.call(obj, key); diff --git a/test/index.html b/test/index.html index d95a7078..5bf9f79d 100644 --- a/test/index.html +++ b/test/index.html @@ -22,12 +22,13 @@ - - - - + + + + + diff --git a/test/jasmine.json b/test/jasmine.json index 45270cd2..a5dce12b 100644 --- a/test/jasmine.json +++ b/test/jasmine.json @@ -1,8 +1,5 @@ { "spec_dir": "test", - "spec_files": [ - "spec/*[sS]pec.js" - ], "helpers": [ "helpers/**/*.js" ], diff --git a/test/spec/commonjs/requireSpec.js b/test/spec/commonjs/requireSpec.js index a9e8fbd1..38e3119c 100644 --- a/test/spec/commonjs/requireSpec.js +++ b/test/spec/commonjs/requireSpec.js @@ -11,6 +11,7 @@ describe('CommonJS', function () { expect(jsonpatch.getValueByPointer).withContext("getValueByPointer should be a method within the object").toBeDefined(); expect(jsonpatch.validate).withContext("validate should be a method within the object").toBeDefined(); expect(jsonpatch.validator).withContext("validator should be a method within the object").toBeDefined(); + expect(jsonpatch._areEquals).withContext("_areEquals should be a method within the object").toBeDefined(); expect(jsonpatch.JsonPatchError).withContext("JsonPatchError should be a method within the object").toBeDefined(); expect(jsonpatch.deepClone).withContext("deepClone should be a method within the object").toBeDefined(); expect(jsonpatch.escapePathComponent).withContext("escapePathComponent should be a method within the object").toBeDefined(); diff --git a/test/spec/coreBenchmark.js b/test/spec/coreBenchmark.js index d721b126..82cf04ef 100644 --- a/test/spec/coreBenchmark.js +++ b/test/spec/coreBenchmark.js @@ -1,17 +1,17 @@ if (typeof jsonpatch === 'undefined') { - jsonpatch = require('./../../lib/duplex'); + jsonpatch = require('./../..'); } if (typeof Benchmark === 'undefined') { - var Benchmark = require('benchmark'); - var benchmarkResultsToConsole = require('./../lib/benchmark_console_reporter.js').benchmarkResultsToConsole; + global.Benchmark = require('benchmark'); + global.benchmarkResultsToConsole = require('./../lib/benchmark_console_reporter.js').benchmarkResultsToConsole; } -var suite = new Benchmark.Suite; -suite.add('add operation', { +const coreSuite = new Benchmark.Suite; +coreSuite.add('add operation', { setup: function(){ - var obj = { + const obj = { foo: 1, baz: [{ qux: 'hello' @@ -26,9 +26,9 @@ suite.add('add operation', { }]); } }); -suite.add('remove operation', { +coreSuite.add('remove operation', { setup: function(){ - var obj = { + const obj = { foo: 1, baz: [{ qux: 'hello' @@ -43,9 +43,9 @@ suite.add('remove operation', { }]); } }); -suite.add('replace operation', { +coreSuite.add('replace operation', { setup: function(){ - var obj = { + const obj = { foo: 1, baz: [{ qux: 'hello' @@ -60,9 +60,9 @@ suite.add('replace operation', { }]); } }); -suite.add('move operation', { +coreSuite.add('move operation', { setup: function(){ - var obj = { + const obj = { foo: 1, baz: [{ qux: 'hello' @@ -78,9 +78,9 @@ suite.add('move operation', { }]); } }); -suite.add('copy operation', { +coreSuite.add('copy operation', { setup: function(){ - var obj = { + const obj = { foo: 1, baz: [{ qux: 'hello' @@ -96,9 +96,9 @@ suite.add('copy operation', { }]); } }); -suite.add('test operation', { +coreSuite.add('test operation', { setup: function(){ - var obj = { + const obj = { foo: 1, baz: [{ qux: 'hello' @@ -118,10 +118,10 @@ suite.add('test operation', { // if we are in the browser with benchmark < 2.1.2 if(typeof benchmarkReporter !== 'undefined'){ - benchmarkReporter(suite); + benchmarkReporter(coreSuite); } else { - suite.on('complete', function () { - benchmarkResultsToConsole(suite); + coreSuite.on('complete', function () { + benchmarkResultsToConsole(coreSuite); }); - suite.run(); + coreSuite.run(); } diff --git a/test/spec/coreSpec.js b/test/spec/coreSpec.mjs similarity index 88% rename from test/spec/coreSpec.js rename to test/spec/coreSpec.mjs index a73e3418..b423e072 100644 --- a/test/spec/coreSpec.js +++ b/test/spec/coreSpec.mjs @@ -1,33 +1,27 @@ -var obj; -if (typeof jsonpatch === 'undefined') { - jsonpatch = require('./../../lib/duplex'); -} -if (typeof _ === 'undefined') { - _ = require('underscore'); -} +import * as jsonpatch from '../../index.mjs'; describe('jsonpatch.getValueByPointer', function() { it('should retrieve values by JSON pointer from tree - deep object', function() { - var obj = { + const obj = { person: { name: 'Marilyn' } }; - var name = jsonpatch.getValueByPointer(obj, '/person/name'); + const name = jsonpatch.getValueByPointer(obj, '/person/name'); expect(name).toEqual('Marilyn'); }); it('should retrieve values by JSON pointer from tree - deep array', function() { - var obj = { + const obj = { people: [{ name: 'Marilyn' }, { name: 'Monroe' }] }; - var name = jsonpatch.getValueByPointer(obj, '/people/1/name'); + const name = jsonpatch.getValueByPointer(obj, '/people/1/name'); expect(name).toEqual('Monroe'); }); it('should retrieve values by JSON pointer from tree - root object', function() { - var obj = { + const obj = { people: [{ name: 'Marilyn' }, { name: 'Monroe' }] }; - var retrievedObject = jsonpatch.getValueByPointer(obj, ''); + const retrievedObject = jsonpatch.getValueByPointer(obj, ''); expect(retrievedObject).toEqual({ people: [{ name: 'Marilyn' }, { name: 'Monroe' }] @@ -35,12 +29,12 @@ describe('jsonpatch.getValueByPointer', function() { }); it('should retrieve values by JSON pointer from tree - root array', function() { - var obj = [ + const obj = [ { people: [{ name: 'Marilyn' }, { name: 'Monroe' }] } ]; - var retrievedObject = jsonpatch.getValueByPointer(obj, ''); + const retrievedObject = jsonpatch.getValueByPointer(obj, ''); expect(retrievedObject).toEqual([ { @@ -51,15 +45,15 @@ describe('jsonpatch.getValueByPointer', function() { }); describe('jsonpatch.applyReducer - using with Array#reduce', function() { it('should work with Array.reduce on array of patches', function() { - var obj = { + const obj = { hello: 'world' }; - var patch = [ + const patch = [ { op: 'replace', path: '/hello', value: 1 }, { op: 'add', path: '/bye', value: { testing: 'isGood' } } ]; - obj = patch.reduce(jsonpatch.applyReducer, obj); - expect(obj).toEqual({ + const obj2 = patch.reduce(jsonpatch.applyReducer, obj); + expect(obj2).toEqual({ hello: 1, bye: { testing: 'isGood' } }); @@ -68,13 +62,13 @@ describe('jsonpatch.applyReducer - using with Array#reduce', function() { describe('root replacement with applyOperation', function() { describe('_get operation', function() { it('should get root value', function() { - var obj = [ + const obj = [ { people: [{ name: 'Marilyn' }, { name: 'Monroe' }] } ]; - var patch = { op: '_get', path: '' }; + const patch = { op: '_get', path: '' }; jsonpatch.applyOperation(obj, patch); @@ -85,11 +79,11 @@ describe('root replacement with applyOperation', function() { ]); }); it('should get deep value', function() { - var obj = { + const obj = { people: [{ name: 'Marilyn' }, { name: 'Monroe' }] }; - var patch = { op: '_get', path: '/people/1/name' }; + const patch = { op: '_get', path: '/people/1/name' }; jsonpatch.applyOperation(obj, patch); @@ -98,10 +92,10 @@ describe('root replacement with applyOperation', function() { }); describe('add operation', function() { it('should `add` an object (on a json document of type object)) - in place', function() { - var obj = { + const obj = { hello: 'world' }; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'add', path: '', value: { @@ -114,10 +108,10 @@ describe('root replacement with applyOperation', function() { }); }); it('should `add` an object (on a json document of type object) - and return', function() { - var obj = { + const obj = { hello: 'world' }; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'add', path: '', value: { @@ -129,12 +123,12 @@ describe('root replacement with applyOperation', function() { }); }); it('should `add` an object (on a json document of type array) - and return', function() { - var obj = [ + const obj = [ { hello: 'world' } ]; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'add', path: '', value: { @@ -146,12 +140,12 @@ describe('root replacement with applyOperation', function() { }); }); it('should `add` an array (on a json document of type array) - in place', function() { - var obj = [ + const obj = [ { hello: 'world' } ]; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'add', path: '', value: [ @@ -167,12 +161,12 @@ describe('root replacement with applyOperation', function() { ]); }); it('should `add` an array (on a json document of type array) - and return', function() { - var obj = [ + const obj = [ { hello: 'world' } ]; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'add', path: '', value: [ @@ -188,9 +182,9 @@ describe('root replacement with applyOperation', function() { ]); }); it('should `add` an array prop', function() { - var obj = []; + const obj = []; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'add', path: '/prop', value: 'arrayProp' @@ -199,10 +193,10 @@ describe('root replacement with applyOperation', function() { expect(newObj.prop).toEqual('arrayProp'); }); it('should `replace` an array prop', function() { - var obj = []; + const obj = []; obj.prop = 'oldArrayProp'; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'replace', path: '/prop', value: 'arrayProp' @@ -211,10 +205,10 @@ describe('root replacement with applyOperation', function() { expect(newObj.prop).toEqual('arrayProp'); }); it('should `add` an array (on a json document of type object) - and return', function() { - var obj = { + const obj = { hello: 'world' }; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'add', path: '', value: [ @@ -230,10 +224,10 @@ describe('root replacement with applyOperation', function() { ]); }); it('should `add` a primitive (on a json document of type object) - and return', function() { - var obj = { + const obj = { hello: 'world' }; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'add', path: '', value: 1 @@ -241,12 +235,12 @@ describe('root replacement with applyOperation', function() { expect(newObj).toEqual(1); }); it('should `add` with a primitive (on a json document of type array) - and return', function() { - var obj = [ + const obj = [ { hello: 'world' } ]; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'add', path: '', value: 1 @@ -256,10 +250,10 @@ describe('root replacement with applyOperation', function() { }); describe('replace operation', function() { it('should `replace` with an object (on a json document of type object)', function() { - var obj = { + const obj = { hello: 'world' }; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'replace', path: '', value: { @@ -271,12 +265,12 @@ describe('root replacement with applyOperation', function() { }); }); it('should `replace` with an object (on a json document of type array)', function() { - var obj = [ + const obj = [ { hello: 'world' } ]; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'replace', path: '', value: { @@ -288,12 +282,12 @@ describe('root replacement with applyOperation', function() { }); }); it('should `replace` with an array (on a json document of type array)', function() { - var obj = [ + const obj = [ { hello: 'world' } ]; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'replace', path: '', value: [ @@ -309,10 +303,10 @@ describe('root replacement with applyOperation', function() { ]); }); it('should `replace` with an array (on a json document of type object)', function() { - var obj = { + const obj = { hello: 'world' }; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'replace', path: '', value: [ @@ -328,10 +322,10 @@ describe('root replacement with applyOperation', function() { ]); }); it('should `replace` with a primitive (on a json document of type object)', function() { - var obj = { + const obj = { hello: 'world' }; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'add', path: '', value: 1 @@ -339,12 +333,12 @@ describe('root replacement with applyOperation', function() { expect(newObj).toEqual(1); }); it('should `replace` with a primitive (on a json document of type array)', function() { - var obj = [ + const obj = [ { hello: 'world' } ]; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'replace', path: '', value: 1 @@ -354,22 +348,22 @@ describe('root replacement with applyOperation', function() { }); describe('remove operation', function() { it('should `remove` root (on a json document of type array)', function() { - var obj = [ + const obj = [ { hello: 'world' } ]; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'remove', path: '' }).newDocument; expect(newObj).toEqual(null); }); it('should `remove` root (on a json document of type object)', function() { - var obj = { + const obj = { hello: 'world' }; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'remove', path: '' }).newDocument; @@ -379,10 +373,10 @@ describe('root replacement with applyOperation', function() { describe('move operation', function() { it('should `move` a child of type object to root (on a json document of type object)', function() { - var obj = { + const obj = { child: { name: 'Charles' } }; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'move', from: '/child', path: '' @@ -390,10 +384,10 @@ describe('root replacement with applyOperation', function() { expect(newObj).toEqual({ name: 'Charles' }); }); it('should `move` a child of type object to root (on a json document of type array)', function() { - var obj = { + const obj = { child: [{ name: 'Charles' }] }; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'move', from: '/child/0', path: '' @@ -401,10 +395,10 @@ describe('root replacement with applyOperation', function() { expect(newObj).toEqual({ name: 'Charles' }); }); it('should `move` a child of type array to root (on a json document of type object)', function() { - var obj = { + const obj = { child: [{ name: 'Charles' }] }; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'move', from: '/child', path: '' @@ -414,10 +408,10 @@ describe('root replacement with applyOperation', function() { }); describe('copy operation', function() { it('should `copy` a child of type object to root (on a json document of type object) - and return', function() { - var obj = { + const obj = { child: { name: 'Charles' } }; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'copy', from: '/child', path: '' @@ -425,10 +419,10 @@ describe('root replacement with applyOperation', function() { expect(newObj).toEqual({ name: 'Charles' }); }); it('should `copy` a child of type object to root (on a json document of type array) - and return', function() { - var obj = { + const obj = { child: [{ name: 'Charles' }] }; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'copy', from: '/child/0', path: '' @@ -436,10 +430,10 @@ describe('root replacement with applyOperation', function() { expect(newObj).toEqual({ name: 'Charles' }); }); it('should `copy` a child of type array to root (on a json document of type object) - and return', function() { - var obj = { + const obj = { child: [{ name: 'Charles' }] }; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'copy', from: '/child', path: '' @@ -449,10 +443,10 @@ describe('root replacement with applyOperation', function() { }); describe('test operation', function() { it('should `test` against root (on a json document of type object) - and return true', function() { - var obj = { + const obj = { hello: 'world' }; - var result = jsonpatch.applyOperation(obj, { + const result = jsonpatch.applyOperation(obj, { op: 'test', path: '', value: { @@ -462,7 +456,7 @@ describe('root replacement with applyOperation', function() { expect(result).toEqual(obj); }); it('should `test` against root (on a json document of type object) - and return false', function() { - var obj = { + const obj = { hello: 'world' }; expect(() => @@ -474,7 +468,7 @@ describe('root replacement with applyOperation', function() { ).toThrow(); }); it('should `test` against root (on a json document of type array) - and return false', function() { - var obj = [ + const obj = [ { hello: 'world' } @@ -493,7 +487,7 @@ describe('root replacement with applyOperation', function() { /* this is just a copy-paste of original specs, but with using applyOperation, to test for non-root patches */ describe('core - using applyOperation', function() { it("shouldn't touch original tree", function() { - obj = { + const obj = { foo: 1, baz: [ { @@ -501,7 +495,7 @@ describe('core - using applyOperation', function() { } ] }; - var newObj = jsonpatch.applyOperation( + const newObj = jsonpatch.applyOperation( obj, { op: 'add', @@ -522,7 +516,7 @@ describe('core - using applyOperation', function() { }); }); it('should apply add', function() { - obj = { + let obj = { foo: 1, baz: [ { @@ -530,7 +524,7 @@ describe('core - using applyOperation', function() { } ] }; - var newObj = jsonpatch.applyOperation(obj, { + let newObj = jsonpatch.applyOperation(obj, { op: 'add', path: '/bar', value: [1, 2, 3, 4] @@ -544,7 +538,7 @@ describe('core - using applyOperation', function() { ], bar: [1, 2, 3, 4] }); - var newObj = jsonpatch.applyOperation(newObj, { + newObj = jsonpatch.applyOperation(newObj, { op: 'add', path: '/baz/0/foo', value: 'world' @@ -568,7 +562,7 @@ describe('core - using applyOperation', function() { } ] }; - var newObj = jsonpatch.applyOperation(obj, { + newObj = jsonpatch.applyOperation(obj, { op: 'add', path: '/bar', value: true @@ -591,7 +585,7 @@ describe('core - using applyOperation', function() { } ] }; - var newObj = jsonpatch.applyOperation(obj, { + newObj = jsonpatch.applyOperation(obj, { op: 'add', path: '/bar', value: false @@ -614,7 +608,7 @@ describe('core - using applyOperation', function() { } ] }; - var newObj = jsonpatch.applyOperation(obj, { + newObj = jsonpatch.applyOperation(obj, { op: 'add', path: '/bar', value: null @@ -631,10 +625,10 @@ describe('core - using applyOperation', function() { }); it('should apply add on root', function() { - var obj = { + const obj = { hello: 'world' }; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'add', path: '', value: { @@ -647,7 +641,7 @@ describe('core - using applyOperation', function() { }); it('should apply remove', function() { - obj = { + const obj = { foo: 1, baz: [ { @@ -657,7 +651,7 @@ describe('core - using applyOperation', function() { bar: [1, 2, 3, 4] }; - var newObj = jsonpatch.applyOperation(obj, { + let newObj = jsonpatch.applyOperation(obj, { op: 'remove', path: '/bar' }).newDocument; @@ -669,7 +663,7 @@ describe('core - using applyOperation', function() { } ] }); - var newObj = jsonpatch.applyOperation(newObj, { + newObj = jsonpatch.applyOperation(newObj, { op: 'remove', path: '/baz/0/qux' }).newDocument; @@ -679,7 +673,7 @@ describe('core - using applyOperation', function() { }); }); it('should apply replace', function() { - obj = { + const obj = { foo: 1, baz: [ { @@ -687,7 +681,7 @@ describe('core - using applyOperation', function() { } ] }; - var newObj = jsonpatch.applyOperation(obj, { + let newObj = jsonpatch.applyOperation(obj, { op: 'replace', path: '/foo', value: [1, 2, 3, 4] @@ -700,7 +694,7 @@ describe('core - using applyOperation', function() { } ] }); - var newObj = jsonpatch.applyOperation(newObj, { + newObj = jsonpatch.applyOperation(newObj, { op: 'replace', path: '/baz/0/qux', value: 'world' @@ -715,10 +709,10 @@ describe('core - using applyOperation', function() { }); }); it('should apply replace on root', function() { - var obj = { + const obj = { hello: 'world' }; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'replace', path: '', value: { @@ -731,7 +725,7 @@ describe('core - using applyOperation', function() { }); }); it('should apply test', function() { - obj = { + const obj = { foo: { bar: [1, 2, 5, 4] }, @@ -830,7 +824,7 @@ describe('core - using applyOperation', function() { }); it('should apply test on root', function() { - var obj = { + const obj = { hello: 'world' }; expect( @@ -852,7 +846,7 @@ describe('core - using applyOperation', function() { }); it('should apply move', function() { - obj = { + const obj = { foo: 1, baz: [ { @@ -861,7 +855,7 @@ describe('core - using applyOperation', function() { ] }; - var newObj = jsonpatch.applyOperation(obj, { + let newObj = jsonpatch.applyOperation(obj, { op: 'move', from: '/foo', path: '/bar' @@ -875,7 +869,7 @@ describe('core - using applyOperation', function() { bar: 1 }); - var newObj = jsonpatch.applyOperation(newObj, { + newObj = jsonpatch.applyOperation(newObj, { op: 'move', from: '/baz/0/qux', path: '/baz/1' @@ -889,13 +883,13 @@ describe('core - using applyOperation', function() { it('should apply move on root', function() { //investigate if this test is right (https://github.com/Starcounter-Jack/JSON-Patch/issues/40) - var obj = { + const obj = { hello: 'world', location: { city: 'Vancouver' } }; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'move', from: '/location', path: '' @@ -907,7 +901,7 @@ describe('core - using applyOperation', function() { }); it('should apply copy', function() { - obj = { + const obj = { foo: 1, baz: [ { @@ -916,7 +910,7 @@ describe('core - using applyOperation', function() { ] }; - var newObj = jsonpatch.applyOperation(obj, { + let newObj = jsonpatch.applyOperation(obj, { op: 'copy', from: '/foo', path: '/bar' @@ -932,7 +926,7 @@ describe('core - using applyOperation', function() { bar: 1 }); - var newObj = jsonpatch.applyOperation(newObj, { + newObj = jsonpatch.applyOperation(newObj, { op: 'copy', from: '/baz/0/qux', path: '/baz/1' @@ -951,13 +945,13 @@ describe('core - using applyOperation', function() { }); it('should apply copy on root', function() { - var obj = { + const obj = { hello: 'world', location: { city: 'Vancouver' } }; - var newObj = jsonpatch.applyOperation(obj, { + const newObj = jsonpatch.applyOperation(obj, { op: 'copy', from: '/location', path: '' @@ -970,7 +964,7 @@ describe('core - using applyOperation', function() { describe('core', function() { it("shouldn't touch original tree", function() { - obj = { + const obj = { foo: 1, baz: [ { @@ -978,7 +972,7 @@ describe('core', function() { } ] }; - var newObj = jsonpatch.applyPatch( + const newObj = jsonpatch.applyPatch( obj, [ { @@ -1001,7 +995,7 @@ describe('core', function() { }); }); it('should apply add', function() { - obj = { + let obj = { foo: 1, baz: [ { @@ -1120,10 +1114,10 @@ describe('core', function() { }); it('should apply add on root', function() { - var obj = { + const obj = { hello: 'world' }; - newObj = jsonpatch.applyPatch(obj, [ + const newObj = jsonpatch.applyPatch(obj, [ { op: 'add', path: '', @@ -1139,7 +1133,7 @@ describe('core', function() { }); it('should apply remove', function() { - obj = { + const obj = { foo: 1, baz: [ { @@ -1178,7 +1172,7 @@ describe('core', function() { }); it('should apply replace', function() { - obj = { + const obj = { foo: 1, baz: [ { @@ -1221,10 +1215,10 @@ describe('core', function() { }); it('should apply replace on root', function() { - var obj = { + const obj = { hello: 'world' }; - var newObject = jsonpatch.applyPatch(obj, [ + const newObject = jsonpatch.applyPatch(obj, [ { op: 'replace', path: '', @@ -1240,7 +1234,7 @@ describe('core', function() { }); it('should apply test', function() { - obj = { + const obj = { foo: { bar: [1, 2, 5, 4] }, @@ -1360,7 +1354,7 @@ describe('core', function() { }); it('should apply test on root', function() { - var obj = { + const obj = { hello: 'world' }; expect( @@ -1388,7 +1382,7 @@ describe('core', function() { }); it('should apply move', function() { - obj = { + const obj = { foo: 1, baz: [ { @@ -1428,13 +1422,13 @@ describe('core', function() { it('should apply move on root', function() { //investigate if this test is right (https://github.com/Starcounter-Jack/JSON-Patch/issues/40) - var obj = { + const obj = { hello: 'world', location: { city: 'Vancouver' } }; - newObj = jsonpatch.applyPatch(obj, [ + const newObj = jsonpatch.applyPatch(obj, [ { op: 'move', from: '/location', @@ -1448,7 +1442,7 @@ describe('core', function() { }); it('should apply copy', function() { - obj = { + const obj = { foo: 1, baz: [ { @@ -1494,13 +1488,13 @@ describe('core', function() { }); it('should apply copy on root', function() { - var obj = { + const obj = { hello: 'world', location: { city: 'Vancouver' } }; - newObj = jsonpatch.applyPatch(obj, [ + const newObj = jsonpatch.applyPatch(obj, [ { op: 'copy', from: '/location', @@ -1514,8 +1508,8 @@ describe('core', function() { }); it('should apply copy, without leaving cross-reference between nodes', function() { - var obj = {}; - var patchset = [ + const obj = {}; + const patchset = [ { op: 'add', path: '/foo', value: [] }, { op: 'add', path: '/foo/-', value: 1 }, { op: 'copy', from: '/foo', path: '/bar' }, @@ -1531,8 +1525,8 @@ describe('core', function() { }); it('should use value object as a reference', function() { - var obj1 = {}; - var patch = [{ op: 'add', path: '/foo', value: [] }]; + const obj1 = {}; + const patch = [{ op: 'add', path: '/foo', value: [] }]; jsonpatch.applyPatch(obj1, patch, false); @@ -1540,7 +1534,7 @@ describe('core', function() { }); describe('returning removed elements >', function() { - var obj; + let obj; beforeEach(function() { obj = { name: 'jack', @@ -1550,7 +1544,7 @@ describe('core', function() { }); it('return removed element when removing from object', function() { - var result = jsonpatch.applyPatch(obj, [ + const result = jsonpatch.applyPatch(obj, [ { op: 'remove', path: '/name' @@ -1559,7 +1553,7 @@ describe('core', function() { expect(result[0].removed).toEqual('jack'); }); it('return removed element when replacing in object', function() { - var result = jsonpatch.applyPatch(obj, [ + const result = jsonpatch.applyPatch(obj, [ { op: 'replace', path: '/name', @@ -1569,7 +1563,7 @@ describe('core', function() { expect(result[0].removed).toEqual('jack'); }); it('return removed element when moving in object', function() { - var result = jsonpatch.applyPatch(obj, [ + const result = jsonpatch.applyPatch(obj, [ { op: 'move', from: '/name', @@ -1580,7 +1574,7 @@ describe('core', function() { }); it('return removed element when removing from array', function() { - var result = jsonpatch.applyPatch(obj, [ + const result = jsonpatch.applyPatch(obj, [ { op: 'remove', path: '/languages/1' @@ -1589,7 +1583,7 @@ describe('core', function() { expect(result[0].removed).toEqual('haskell'); }); it('return removed element when replacing in array', function() { - var result = jsonpatch.applyPatch(obj, [ + const result = jsonpatch.applyPatch(obj, [ { op: 'replace', path: '/languages/1', @@ -1599,7 +1593,7 @@ describe('core', function() { expect(result[0].removed).toEqual('haskell'); }); it('return removed element when moving in array', function() { - var result = jsonpatch.applyPatch(obj, [ + const result = jsonpatch.applyPatch(obj, [ { op: 'move', from: '/hobby', @@ -1609,7 +1603,7 @@ describe('core', function() { expect(result[0].removed).toEqual('haskell'); }); it('return root when removing root', function() { - var result = jsonpatch.applyPatch(obj, [ + const result = jsonpatch.applyPatch(obj, [ { op: 'remove', path: '' @@ -1622,7 +1616,7 @@ describe('core', function() { }); }); it('return root when replacing root', function() { - var result = jsonpatch.applyPatch(obj, [ + const result = jsonpatch.applyPatch(obj, [ { op: 'replace', path: '', @@ -1638,7 +1632,7 @@ describe('core', function() { }); }); it('return root when moving to root', function() { - var result = jsonpatch.applyPatch(obj, [ + const result = jsonpatch.applyPatch(obj, [ { op: 'move', from: '/languages', @@ -1657,7 +1651,7 @@ describe('core', function() { describe('undefined - JS to JSON projection / JSON to JS extension', function() { describe('jsonpatch should apply', function() { it('add for properties already set to `undefined` in target JS document', function() { - var obj = { + const obj = { hello: 'world', nothing: undefined }; @@ -1676,7 +1670,7 @@ describe('undefined - JS to JSON projection / JSON to JS extension', function() }); it('add for properties with value set to `undefined` (extension)', function() { - var obj = { + const obj = { hello: 'world' }; jsonpatch.applyPatch(obj, [ @@ -1694,7 +1688,7 @@ describe('undefined - JS to JSON projection / JSON to JS extension', function() }); it('remove on element already set to `undefined`, and remove it completely', function() { - obj = { + const obj = { foo: 1, not: undefined }; @@ -1710,7 +1704,7 @@ describe('undefined - JS to JSON projection / JSON to JS extension', function() }); }); it('remove on array element set to `undefined`', function() { - obj = { + const obj = { foo: 1, bar: [0, 1, undefined, 3] }; @@ -1728,7 +1722,7 @@ describe('undefined - JS to JSON projection / JSON to JS extension', function() }); it('replace on element set to `undefined`', function() { - obj = { + const obj = { foo: 1, not: undefined }; @@ -1746,7 +1740,7 @@ describe('undefined - JS to JSON projection / JSON to JS extension', function() }); }); it('replace on array element set to `undefined`', function() { - obj = { + const obj = { foo: 1, bar: [0, 1, undefined, 3] }; @@ -1764,7 +1758,7 @@ describe('undefined - JS to JSON projection / JSON to JS extension', function() }); }); it('replace element with `undefined` (extension)', function() { - obj = { + const obj = { foo: 1 }; @@ -1780,7 +1774,7 @@ describe('undefined - JS to JSON projection / JSON to JS extension', function() }); }); it('replace array element with `undefined` (extension)', function() { - obj = { + const obj = { foo: 1, bar: [0, 1, 2, 3] }; @@ -1798,7 +1792,7 @@ describe('undefined - JS to JSON projection / JSON to JS extension', function() }); }); it('test on element set to `undefined`', function() { - obj = { + const obj = { foo: 1, not: undefined }; @@ -1822,7 +1816,7 @@ describe('undefined - JS to JSON projection / JSON to JS extension', function() ).toBe(true); }); it('test on array element set to `undefined`', function() { - obj = { + const obj = { foo: 1, bar: [0, 1, undefined, 3] }; @@ -1856,7 +1850,7 @@ describe('undefined - JS to JSON projection / JSON to JS extension', function() }); it('move of `undefined`', function() { - obj = { + const obj = { foo: undefined, baz: 'defined' }; @@ -1886,7 +1880,7 @@ describe('undefined - JS to JSON projection / JSON to JS extension', function() }); it('copy of `undefined` as `null` (like `JSON.stringify` does)', function() { - obj = { + const obj = { foo: undefined, baz: 'defined' }; diff --git a/test/spec/duplexBenchmark.js b/test/spec/duplexBenchmark.js index 7e583793..1f07a6d2 100644 --- a/test/spec/duplexBenchmark.js +++ b/test/spec/duplexBenchmark.js @@ -7,18 +7,18 @@ if (typeof window === 'undefined') { } if (typeof jsonpatch === 'undefined') { - jsonpatch = require('./../../lib/duplex'); + jsonpatch = require('./../..'); } if (typeof Benchmark === 'undefined') { - var Benchmark = require('benchmark'); - var benchmarkResultsToConsole = require('./../lib/benchmark_console_reporter.js').benchmarkResultsToConsole; + global.Benchmark = require('benchmark'); + global.benchmarkResultsToConsole = require('./../lib/benchmark_console_reporter.js').benchmarkResultsToConsole; } -var suite = new Benchmark.Suite(); -suite.add('generate operation', { +const duplexSuite = new Benchmark.Suite(); +duplexSuite.add('generate operation', { setup: function() { - var obj = { + const obj = { firstName: 'Albert', lastName: 'Einstein', phoneNumbers: [ @@ -30,7 +30,7 @@ suite.add('generate operation', { } ] }; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); }, fn: function() { obj.firstName = 'Joachim'; @@ -38,12 +38,12 @@ suite.add('generate operation', { obj.phoneNumbers[0].number = '123'; obj.phoneNumbers[1].number = '456'; - var patches = jsonpatch.generate(observer); + const patches = jsonpatch.generate(observer); } }); -suite.add('generate operation and re-apply', { +duplexSuite.add('generate operation and re-apply', { setup: function() { - var obj = { + const obj = { firstName: 'Albert', lastName: 'Einstein', phoneNumbers: [ @@ -55,7 +55,7 @@ suite.add('generate operation and re-apply', { } ] }; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); }, fn: function() { obj.firstName = 'Joachim'; @@ -63,7 +63,7 @@ suite.add('generate operation and re-apply', { obj.phoneNumbers[0].number = '123'; obj.phoneNumbers[1].number = '456'; - var patches = jsonpatch.generate(observer); + const patches = jsonpatch.generate(observer); obj2 = { firstName: 'Albert', lastName: 'Einstein', @@ -80,9 +80,9 @@ suite.add('generate operation and re-apply', { jsonpatch.applyPatch(obj2, patches); } }); -suite.add('compare operation', { +duplexSuite.add('compare operation', { setup: function() { - var obj = { + const obj = { firstName: 'Albert', lastName: 'Einstein', phoneNumbers: [ @@ -94,7 +94,7 @@ suite.add('compare operation', { } ] }; - var obj2 = { + const obj2 = { firstName: 'Joachim', lastName: 'Wester', mobileNumbers: [ @@ -108,13 +108,13 @@ suite.add('compare operation', { }; }, fn: function() { - var patches = jsonpatch.compare(obj, obj2); + const patches = jsonpatch.compare(obj, obj2); } }); -suite.add('compare operation same but deep objects', { +duplexSuite.add('compare operation same but deep objects', { setup: function() { - var depth = 10; + const depth = 10; function shallowObj() { return { @@ -132,23 +132,23 @@ suite.add('compare operation same but deep objects', { } }; } - var obj = shallowObj(); - var node = obj; + const obj = shallowObj(); + const node = obj; while (depth-- > 0) { node.nested = shallowObj(); node = node.nested; } - var obj2 = obj; + const obj2 = obj; }, fn: function() { - var patches = jsonpatch.compare(obj, obj2); + const patches = jsonpatch.compare(obj, obj2); } }); // Benchmark generating test operations -suite.add('generate operation, invertible = true', { +duplexSuite.add('generate operation, invertible = true', { setup: function() { - var obj = { + const obj = { firstName: 'Albert', lastName: 'Einstein', phoneNumbers: [ @@ -160,7 +160,7 @@ suite.add('generate operation, invertible = true', { } ] }; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); }, fn: function() { obj.firstName = 'Joachim'; @@ -168,12 +168,12 @@ suite.add('generate operation, invertible = true', { obj.phoneNumbers[0].number = '123'; obj.phoneNumbers[1].number = '456'; - var patches = jsonpatch.generate(observer, true); + const patches = jsonpatch.generate(observer, true); } }); -suite.add('generate operation and re-apply, invertible = true', { +duplexSuite.add('generate operation and re-apply, invertible = true', { setup: function() { - var obj = { + const obj = { firstName: 'Albert', lastName: 'Einstein', phoneNumbers: [ @@ -185,7 +185,7 @@ suite.add('generate operation and re-apply, invertible = true', { } ] }; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); }, fn: function() { obj.firstName = 'Joachim'; @@ -193,7 +193,7 @@ suite.add('generate operation and re-apply, invertible = true', { obj.phoneNumbers[0].number = '123'; obj.phoneNumbers[1].number = '456'; - var patches = jsonpatch.generate(observer, true); + const patches = jsonpatch.generate(observer, true); obj2 = { firstName: 'Albert', lastName: 'Einstein', @@ -210,9 +210,9 @@ suite.add('generate operation and re-apply, invertible = true', { jsonpatch.applyPatch(obj2, patches); } }); -suite.add('compare operation, invertible = true', { +duplexSuite.add('compare operation, invertible = true', { setup: function() { - var obj = { + const obj = { firstName: 'Albert', lastName: 'Einstein', phoneNumbers: [ @@ -224,7 +224,7 @@ suite.add('compare operation, invertible = true', { } ] }; - var obj2 = { + const obj2 = { firstName: 'Joachim', lastName: 'Wester', mobileNumbers: [ @@ -238,13 +238,13 @@ suite.add('compare operation, invertible = true', { }; }, fn: function() { - var patches = jsonpatch.compare(obj, obj2, true); + const patches = jsonpatch.compare(obj, obj2, true); } }); -suite.add('compare operation same but deep objects, invertible = true', { +duplexSuite.add('compare operation same but deep objects, invertible = true', { setup: function() { - var depth = 10; + const depth = 10; function shallowObj() { return { @@ -262,25 +262,25 @@ suite.add('compare operation same but deep objects, invertible = true', { } }; } - var obj = shallowObj(); - var node = obj; + const obj = shallowObj(); + const node = obj; while (depth-- > 0) { node.nested = shallowObj(); node = node.nested; } - var obj2 = obj; + const obj2 = obj; }, fn: function() { - var patches = jsonpatch.compare(obj, obj2, true); + const patches = jsonpatch.compare(obj, obj2, true); } }); // if we are in the browser with benchmark < 2.1.2 if (typeof benchmarkReporter !== 'undefined') { - benchmarkReporter(suite); + benchmarkReporter(duplexSuite); } else { - suite.on('complete', function() { - benchmarkResultsToConsole(suite); + duplexSuite.on('complete', function() { + benchmarkResultsToConsole(duplexSuite); }); - suite.run(); + duplexSuite.run(); } diff --git a/test/spec/duplexSpec.js b/test/spec/duplexSpec.mjs similarity index 83% rename from test/spec/duplexSpec.js rename to test/spec/duplexSpec.mjs index 38f53e6c..c5d53e87 100644 --- a/test/spec/duplexSpec.js +++ b/test/spec/duplexSpec.mjs @@ -1,54 +1,38 @@ -var obj, obj2, patches; +import * as jsonpatch from '../../index.mjs'; +import {EventTarget, defineEventAttribute} from "../../node_modules/event-target-shim/dist/event-target-shim.mjs"; + if (typeof window === 'undefined') { - const jsdom = require("jsdom"); - const { JSDOM } = jsdom; - const dom = new JSDOM(); - global.window = dom.window; - global.document = dom.window.document; -} -if (typeof jsonpatch === 'undefined') { - jsonpatch = require('./../../lib/duplex'); -} -if (typeof _ === 'undefined') { - _ = require('underscore'); + global.window = new EventTarget(); } -function trigger(eventName, elem) { - if (typeof document !== 'undefined') { - fireEvent(elem || document.documentElement, eventName); +function trigger(eventName) { + let event; + if (typeof CustomEvent !== 'undefined') { + //browser with real EventTarget + event = new CustomEvent(eventName); } + else { + //browser with real EventTarget + event = { type: eventName }; + } + window.dispatchEvent(event) } function getPatchesUsingGenerate(objFactory, objChanger) { - var obj = objFactory(); - var observer = jsonpatch.observe(obj); + const obj = objFactory(); + const observer = jsonpatch.observe(obj); objChanger(obj); return jsonpatch.generate(observer); } function getPatchesUsingCompare(objFactory, objChanger) { - var obj = objFactory(); - var mirror = JSON.parse(JSON.stringify(obj)); + const obj = objFactory(); + const mirror = JSON.parse(JSON.stringify(obj)); objChanger(obj); return jsonpatch.compare(mirror, JSON.parse(JSON.stringify(obj))); } -//http://stackoverflow.com/questions/827716/emulate-clicking-a-link-with-javascript-that-works-with-ie -function fireEvent(obj, evt) { - var fireOnThis = obj; - if (document.createEvent) { - var evObj = document.createEvent( - evt.indexOf('mouse') > -1 ? 'MouseEvents' : 'KeyboardEvent' - ); - evObj.initEvent(evt, true, false); - fireOnThis.dispatchEvent(evObj); - } else if (document.createEventObject) { - var evObj = document.createEventObject(); - fireOnThis.fireEvent('on' + evt, evObj); - } -} - -var customMatchers = { +const customMatchers = { /** * This matcher is only needed in Chrome 28 (Chrome 28 cannot successfully compare observed objects immediately after they have been changed. Chrome 30 is unaffected) * @param obj @@ -67,7 +51,7 @@ var customMatchers = { return { compare: function(actual, expected) { return { - pass: _.isEqual(actual, expected) + pass: jsonpatch._areEquals(actual, expected) }; } }; @@ -118,7 +102,7 @@ describe('duplex', function() { describe('generate', function() { it('should generate replace', function() { - obj = { + const obj = { firstName: 'Albert', lastName: 'Einstein', phoneNumbers: [ @@ -131,14 +115,14 @@ describe('duplex', function() { ] }; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); obj.firstName = 'Joachim'; obj.lastName = 'Wester'; obj.phoneNumbers[0].number = '123'; obj.phoneNumbers[1].number = '456'; - var patches = jsonpatch.generate(observer); - obj2 = { + const patches = jsonpatch.generate(observer); + const obj2 = { firstName: 'Albert', lastName: 'Einstein', phoneNumbers: [ @@ -156,7 +140,7 @@ describe('duplex', function() { }); it('should generate replace (escaped chars)', function() { - obj = { + const obj = { '/name/first': 'Albert', '/name/last': 'Einstein', '~phone~/numbers': [ @@ -169,14 +153,14 @@ describe('duplex', function() { ] }; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); obj['/name/first'] = 'Joachim'; obj['/name/last'] = 'Wester'; obj['~phone~/numbers'][0].number = '123'; obj['~phone~/numbers'][1].number = '456'; - var patches = jsonpatch.generate(observer); - obj2 = { + const patches = jsonpatch.generate(observer); + const obj2 = { '/name/first': 'Albert', '/name/last': 'Einstein', '~phone~/numbers': [ @@ -198,23 +182,23 @@ describe('duplex', function() { ['invertible = TRUE', true] ], function (testInvertible) { return function () { - var person1 = { + const person1 = { firstName: 'Alexandra', lastName: 'Galbreath' }; - var person2 = { + const person2 = { firstName: 'Lisa', lastName: 'Mendoza' }; - var observer1 = jsonpatch.observe(person1); - var observer2 = jsonpatch.observe(person2); + const observer1 = jsonpatch.observe(person1); + const observer2 = jsonpatch.observe(person2); person1.firstName = 'Alexander'; person2.firstName = 'Lucas'; - var patch1 = jsonpatch.generate(observer1, testInvertible); - var patch2 = jsonpatch.generate(observer2, testInvertible); + const patch1 = jsonpatch.generate(observer1, testInvertible); + const patch2 = jsonpatch.generate(observer2, testInvertible); expect(patch1).toReallyEqual([ ...insertIf(testInvertible, { @@ -248,7 +232,7 @@ describe('duplex', function() { ['invertible = TRUE', true] ], function (testInvertible) { return function () { - obj = { + const obj = { firstName: 'Albert', lastName: 'Einstein', phoneNumbers: [ @@ -261,10 +245,10 @@ describe('duplex', function() { ] }; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); obj.firstName = 'Marcin'; - var patches = jsonpatch.generate(observer, testInvertible); + let patches = jsonpatch.generate(observer, testInvertible); expect(patches).toReallyEqual([ ...insertIf(testInvertible, { op: "test", @@ -313,7 +297,7 @@ describe('duplex', function() { ['invertible = TRUE', true] ], function (testInvertible) { return function () { - obj = { + const obj = { firstName: 'Albert', lastName: 'Einstein', phoneNumbers: [ @@ -326,10 +310,10 @@ describe('duplex', function() { ] }; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); obj.phoneNumbers[0].number = '123'; - var patches = jsonpatch.generate(observer, testInvertible); + let patches = jsonpatch.generate(observer, testInvertible); expect(patches).toReallyEqual([ ...insertIf(testInvertible, { op: "test", @@ -374,12 +358,12 @@ describe('duplex', function() { }); it('should generate replace (changes in new array cell, primitive values)', function() { - arr = [1]; + const arr = [1]; - var observer = jsonpatch.observe(arr); + const observer = jsonpatch.observe(arr); arr.push(2); - var patches = jsonpatch.generate(observer); + let patches = jsonpatch.generate(observer); expect(patches).toReallyEqual([ { op: 'add', @@ -390,7 +374,7 @@ describe('duplex', function() { arr[0] = 3; - var patches = jsonpatch.generate(observer); + patches = jsonpatch.generate(observer); expect(patches).toReallyEqual([ { op: 'replace', @@ -401,7 +385,7 @@ describe('duplex', function() { arr[1] = 4; - var patches = jsonpatch.generate(observer); + patches = jsonpatch.generate(observer); expect(patches).toReallyEqual([ { op: 'replace', @@ -412,20 +396,20 @@ describe('duplex', function() { }); it('should generate replace (changes in new array cell, complex values)', function() { - arr = [ + const arr = [ { id: 1, name: 'Ted' } ]; - var observer = jsonpatch.observe(arr); + const observer = jsonpatch.observe(arr); arr.push({ id: 2, name: 'Jerry' }); - var patches = jsonpatch.generate(observer); + let patches = jsonpatch.generate(observer); expect(patches).toReallyEqual([ { op: 'add', @@ -439,7 +423,7 @@ describe('duplex', function() { arr[0].id = 3; - var patches = jsonpatch.generate(observer); + patches = jsonpatch.generate(observer); expect(patches).toReallyEqual([ { op: 'replace', @@ -450,7 +434,7 @@ describe('duplex', function() { arr[1].id = 4; - var patches = jsonpatch.generate(observer); + patches = jsonpatch.generate(observer); expect(patches).toReallyEqual([ { op: 'replace', @@ -461,7 +445,7 @@ describe('duplex', function() { }); it('should generate add', function() { - obj = { + const obj = { lastName: 'Einstein', phoneNumbers: [ { @@ -469,7 +453,7 @@ describe('duplex', function() { } ] }; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); obj.firstName = 'Joachim'; obj.lastName = 'Wester'; @@ -478,8 +462,8 @@ describe('duplex', function() { number: '456' }); - patches = jsonpatch.generate(observer); - obj2 = { + const patches = jsonpatch.generate(observer); + const obj2 = { lastName: 'Einstein', phoneNumbers: [ { @@ -493,7 +477,7 @@ describe('duplex', function() { }); it('should generate remove', function() { - obj = { + const obj = { lastName: 'Einstein', firstName: 'Albert', phoneNumbers: [ @@ -505,15 +489,15 @@ describe('duplex', function() { } ] }; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); delete obj.firstName; obj.lastName = 'Wester'; obj.phoneNumbers[0].number = '123'; obj.phoneNumbers.pop(1); - patches = jsonpatch.generate(observer); - obj2 = { + const patches = jsonpatch.generate(observer); + const obj2 = { lastName: 'Einstein', firstName: 'Albert', phoneNumbers: [ @@ -535,15 +519,15 @@ describe('duplex', function() { ['invertible = TRUE', true] ], function (testInvertible) { return function () { - obj = { + const obj = { items: ['a', 'b', 'c'] }; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); obj.items.pop(); obj.items.pop(); - patches = jsonpatch.generate(observer, testInvertible); + const patches = jsonpatch.generate(observer, testInvertible); //array indexes must be sorted descending, otherwise there is an index collision in apply expect(patches).toReallyEqual([ @@ -567,7 +551,7 @@ describe('duplex', function() { }, ]); - obj2 = { + const obj2 = { items: ['a', 'b', 'c'] }; jsonpatch.applyPatch(obj2, patches); @@ -576,14 +560,14 @@ describe('duplex', function() { }); it('should not generate the same patch twice (replace)', function() { - obj = { + const obj = { lastName: 'Einstein' }; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); obj.lastName = 'Wester'; - patches = jsonpatch.generate(observer); + let patches = jsonpatch.generate(observer); expect(patches).toReallyEqual([ { op: 'replace', @@ -597,14 +581,14 @@ describe('duplex', function() { }); it('should not generate the same patch twice (add)', function() { - obj = { + const obj = { lastName: 'Einstein' }; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); obj.firstName = 'Albert'; - patches = jsonpatch.generate(observer); + let patches = jsonpatch.generate(observer); expect(patches).toReallyEqual([ { op: 'add', @@ -618,14 +602,14 @@ describe('duplex', function() { }); it('should not generate the same patch twice (remove)', function() { - obj = { + const obj = { lastName: 'Einstein' }; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); delete obj.lastName; - patches = jsonpatch.generate(observer); + let patches = jsonpatch.generate(observer); expect(patches).toReallyEqual([ { op: 'remove', @@ -638,15 +622,14 @@ describe('duplex', function() { }); it('should ignore array properties', function() { - var obj = { + const obj = { array: [1, 2, 3] }; - var patches; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); obj.array.value = 1; - patches = jsonpatch.generate(observer); + let patches = jsonpatch.generate(observer); expect(patches.length).toReallyEqual(0); obj.array.value = 2; @@ -655,9 +638,9 @@ describe('duplex', function() { }); it('should respect toJSON', function() { - var a = {}; + const a = {}; a.self = a; - var obj = { + const obj = { a: a, b: 3, toJSON: function() { @@ -667,16 +650,16 @@ describe('duplex', function() { } }; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); obj.b = 5; - patches = jsonpatch.generate(observer); + const patches = jsonpatch.generate(observer); expect(patches.length).toReallyEqual(1); }); it('should respect toJSON in nested objects', function() { - var a = {}; + const a = {}; a.self = a; - var outer = { + const outer = { obj: { a: a, b: 3, @@ -688,9 +671,9 @@ describe('duplex', function() { } }; - var observer = jsonpatch.observe(outer); + const observer = jsonpatch.observe(outer); outer.obj.b = 5; - patches = jsonpatch.generate(observer); + const patches = jsonpatch.generate(observer); expect(patches.length).toReallyEqual(1); expect(patches[0].path).toReallyEqual('/obj/b'); @@ -698,31 +681,31 @@ describe('duplex', function() { }); /*it('should not generate the same patch twice (move)', function() { //"move" is not implemented yet in jsonpatch.generate - obj = { lastName: {str: "Einstein"} }; - var observer = jsonpatch.observe(obj); + const obj = { lastName: {str: "Einstein"} }; + const observer = jsonpatch.observe(obj); obj.lastName2 = obj.lastName; delete obj.lastName; - patches = jsonpatch.generate(observer); + const patches = jsonpatch.generate(observer); expect(patches).toReallyEqual([ { op: 'move', from: '/lastName', to: '/lastName2' } ]); - patches = jsonpatch.generate(observer); + const patches = jsonpatch.generate(observer); expect(patches).toReallyEqual([]); });*/ xdescribe('undefined - JS to JSON projection', function() { it('when value is set to `undefined`, should generate remove (undefined is JSON.stringified to no value)', function() { - var obj = { + const obj = { foo: 'bar' }; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); obj.foo = undefined; - var patches = jsonpatch.generate(observer); + const patches = jsonpatch.generate(observer); expect(patches).toReallyEqual([ { op: 'remove', @@ -732,26 +715,26 @@ describe('duplex', function() { }); it('when new property is added, and set to `undefined`, nothing should be generated (undefined is JSON.stringified to no value)', function() { - var obj = { + const obj = { foo: 'bar' }; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); obj.baz = undefined; - var patches = jsonpatch.generate(observer); + const patches = jsonpatch.generate(observer); expect(patches).toReallyEqual([]); }); it('when array element is set to `undefined`, should generate replace to `null` (undefined array elements are JSON.stringified to `null`)', function() { - var obj = { + const obj = { foo: [0, 1, 2] }; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); obj.foo[1] = undefined; - var patches = jsonpatch.generate(observer); + const patches = jsonpatch.generate(observer); expect(patches).toReallyEqual([ { op: 'replace', @@ -762,14 +745,14 @@ describe('duplex', function() { }); it('when `undefined` property is set to something, should generate add (undefined is JSON.stringified to no value)', function() { - var obj = { + const obj = { foo: undefined }; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); obj.foo = 'something'; - var patches = jsonpatch.generate(observer); + const patches = jsonpatch.generate(observer); expect(patches).toReallyEqual([ { op: 'add', @@ -779,14 +762,14 @@ describe('duplex', function() { ]); }); it('when `undefined` array element is set to something, should generate replace (undefined array elements are JSON.stringified to `null`)', function() { - var obj = { + const obj = { foo: [0, undefined, 2] }; - var observer = jsonpatch.observe(obj); + const observer = jsonpatch.observe(obj); obj.foo[1] = 1; - var patches = jsonpatch.generate(observer); + const patches = jsonpatch.generate(observer); expect(patches).toReallyEqual([ { op: 'replace', @@ -800,42 +783,42 @@ describe('duplex', function() { describe('undefined - JSON to JS extension', function() { describe('should generate empty patch, when', function() { it('when new property is set to `undefined`', function() { - var objFactory = function() { + const objFactory = function() { return { foo: 'bar' }; }; - var objChanger = function(obj) { + const objChanger = function(obj) { obj.baz = undefined; }; - var genereatedPatches = getPatchesUsingGenerate( + const genereatedPatches = getPatchesUsingGenerate( objFactory, objChanger ); - var comparedPatches = getPatchesUsingCompare(objFactory, objChanger); + const comparedPatches = getPatchesUsingCompare(objFactory, objChanger); expect(genereatedPatches).toReallyEqual([]); expect(genereatedPatches).toReallyEqual(comparedPatches); }); it('when an `undefined` property is deleted', function() { - var objFactory = function() { + const objFactory = function() { return { foo: undefined }; }; - var objChanger = function(obj) { + const objChanger = function(obj) { delete obj.foo; }; - var genereatedPatches = getPatchesUsingGenerate( + const genereatedPatches = getPatchesUsingGenerate( objFactory, objChanger ); - var comparedPatches = getPatchesUsingCompare(objFactory, objChanger); + const comparedPatches = getPatchesUsingCompare(objFactory, objChanger); expect(genereatedPatches).toReallyEqual([]); expect(genereatedPatches).toReallyEqual(comparedPatches); @@ -844,21 +827,21 @@ describe('duplex', function() { describe('should generate add, when', function() { it('`undefined` property is set to something', function() { - var objFactory = function() { + const objFactory = function() { return { foo: undefined }; }; - var objChanger = function(obj) { + const objChanger = function(obj) { obj.foo = 'something'; }; - var genereatedPatches = getPatchesUsingGenerate( + const genereatedPatches = getPatchesUsingGenerate( objFactory, objChanger ); - var comparedPatches = getPatchesUsingCompare(objFactory, objChanger); + const comparedPatches = getPatchesUsingCompare(objFactory, objChanger); expect(genereatedPatches).toReallyEqual([ { @@ -873,21 +856,21 @@ describe('duplex', function() { describe('should generate remove, when', function() { it('value is set to `undefined`', function() { - var objFactory = function() { + const objFactory = function() { return { foo: 'bar' }; }; - var objChanger = function(obj) { + const objChanger = function(obj) { obj.foo = undefined; }; - var genereatedPatches = getPatchesUsingGenerate( + const genereatedPatches = getPatchesUsingGenerate( objFactory, objChanger ); - var comparedPatches = getPatchesUsingCompare(objFactory, objChanger); + const comparedPatches = getPatchesUsingCompare(objFactory, objChanger); expect(genereatedPatches).toReallyEqual([ { @@ -901,21 +884,21 @@ describe('duplex', function() { describe('should generate replace, when', function() { it('array element is set to `undefined`', function() { - var objFactory = function() { + const objFactory = function() { return { foo: [0, 1, 2] }; }; - var objChanger = function(obj) { + const objChanger = function(obj) { obj.foo[1] = undefined; }; - var genereatedPatches = getPatchesUsingGenerate( + const genereatedPatches = getPatchesUsingGenerate( objFactory, objChanger ); - var comparedPatches = getPatchesUsingCompare(objFactory, objChanger); + const comparedPatches = getPatchesUsingCompare(objFactory, objChanger); expect(genereatedPatches).toReallyEqual([ { @@ -927,21 +910,21 @@ describe('duplex', function() { expect(genereatedPatches).toReallyEqual(comparedPatches); }); it('`undefined` array element is set to something', function() { - var objFactory = function() { + const objFactory = function() { return { foo: [0, undefined, 2] }; }; - var objChanger = function(obj) { + const objChanger = function(obj) { obj.foo[1] = 1; }; - var genereatedPatches = getPatchesUsingGenerate( + const genereatedPatches = getPatchesUsingGenerate( objFactory, objChanger ); - var comparedPatches = getPatchesUsingCompare(objFactory, objChanger); + const comparedPatches = getPatchesUsingCompare(objFactory, objChanger); expect(genereatedPatches).toReallyEqual([ { @@ -959,10 +942,10 @@ describe('duplex', function() { describe('apply', function() { // https://tools.ietf.org/html/rfc6902#appendix-A.16 it('should add an Array Value', function() { - var obj = { + const obj = { foo: ['bar'] }; - var patches = [ + const patches = [ { op: 'add', path: '/foo/-', @@ -979,9 +962,9 @@ describe('duplex', function() { describe('callback', function() { it('should generate replace', function(done) { - var patches; + let patches; - obj = { + const obj = { firstName: 'Albert', lastName: 'Einstein', phoneNumbers: [ @@ -1006,7 +989,7 @@ describe('duplex', function() { trigger('keyup'); function patchesChanged() { - obj2 = { + const obj2 = { firstName: 'Albert', lastName: 'Einstein', phoneNumbers: [ @@ -1028,9 +1011,10 @@ describe('duplex', function() { it('should generate replace (double change, shallow object)', function( done ) { - var lastPatches, called = 0; + let lastPatches; + let called = 0; - obj = { + const obj = { firstName: 'Albert', lastName: 'Einstein', phoneNumbers: [ @@ -1097,9 +1081,10 @@ describe('duplex', function() { }); it('should generate replace (double change, deep object)', function(done) { - var lastPatches, called = 0; + let lastPatches; + let called = 0; - obj = { + const obj = { firstName: 'Albert', lastName: 'Einstein', phoneNumbers: [ @@ -1166,9 +1151,11 @@ describe('duplex', function() { }); it('generate should execute callback synchronously', function(done) { - var lastPatches, called = 0, res; + let lastPatches; + let called = 0; + let res; - obj = { + const obj = { firstName: 'Albert', lastName: 'Einstein', phoneNumbers: [ @@ -1181,7 +1168,7 @@ describe('duplex', function() { ] }; - var observer = jsonpatch.observe(obj, function(patches) { + const observer = jsonpatch.observe(obj, function(patches) { called++; lastPatches = patches; }); @@ -1216,9 +1203,9 @@ describe('duplex', function() { }); it('should unobserve then observe again', function(done) { - var called = 0; + let called = 0; - obj = { + const obj = { firstName: 'Albert', lastName: 'Einstein', phoneNumbers: [ @@ -1231,7 +1218,7 @@ describe('duplex', function() { ] }; - var observer = jsonpatch.observe(obj, function(patches) { + const observer = jsonpatch.observe(obj, function(patches) { called++; }); @@ -1252,7 +1239,7 @@ describe('duplex', function() { setTimeout(function() { expect(called).toReallyEqual(1); - observer = jsonpatch.observe(obj, function(patches) { + const observer2 = jsonpatch.observe(obj, function(patches) { called++; }); @@ -1268,9 +1255,9 @@ describe('duplex', function() { }); it('should unobserve then observe again (deep value)', function(done) { - var called = 0; + let called = 0; - obj = { + const obj = { firstName: 'Albert', lastName: 'Einstein', phoneNumbers: [ @@ -1283,7 +1270,7 @@ describe('duplex', function() { ] }; - var observer = jsonpatch.observe(obj, function(patches) { + const observer = jsonpatch.observe(obj, function(patches) { called++; }); @@ -1303,7 +1290,7 @@ describe('duplex', function() { setTimeout(function() { expect(called).toReallyEqual(1); - observer = jsonpatch.observe(obj, function(patches) { + const observer2 = jsonpatch.observe(obj, function(patches) { called++; }); @@ -1322,9 +1309,9 @@ describe('duplex', function() { it('calling unobserve should deliver pending changes synchronously', function( done ) { - var lastPatches = ''; + let lastPatches = ''; - obj = { + const obj = { firstName: 'Albert', lastName: 'Einstein', phoneNumbers: [ @@ -1337,7 +1324,7 @@ describe('duplex', function() { ] }; - var observer = jsonpatch.observe(obj, function(patches) { + const observer = jsonpatch.observe(obj, function(patches) { lastPatches = patches; }); @@ -1359,13 +1346,13 @@ describe('duplex', function() { it('should handle callbacks that calls observe() and unobserve() internally', function( done ) { - var obj = { + const obj = { foo: 'bar' }; - var observer; - var callbackCalled, count = 0; - var callback = jasmine + let observer; + let count = 0; + const callback = jasmine .createSpy('callback', function() { jsonpatch.unobserve(obj, observer); @@ -1401,12 +1388,12 @@ describe('duplex', function() { }); it('should generate patch after `mouseup` event while observing', function(done) { - obj = { + const obj = { lastName: 'Einstein' }; - var lastPatches; - var callCount = 0; - var observer = jsonpatch.observe(obj, function(patches) { + let lastPatches; + let callCount = 0; + const observer = jsonpatch.observe(obj, function(patches) { callCount++; lastPatches = patches; }); @@ -1436,12 +1423,12 @@ describe('duplex', function() { }); it('should generate patch after `mousedown` event while observing', function(done) { - obj = { + const obj = { lastName: 'Einstein' }; - var lastPatches; - var callCount = 0; - var observer = jsonpatch.observe(obj, function(patches) { + let lastPatches; + let callCount = 0; + const observer = jsonpatch.observe(obj, function(patches) { callCount++; lastPatches = patches; }); @@ -1470,12 +1457,12 @@ describe('duplex', function() { }); it('should generate patch after `keyup` event while observing', function(done) { - obj = { + const obj = { lastName: 'Einstein' }; - var lastPatches; - var callCount = 0; - var observer = jsonpatch.observe(obj, function(patches) { + let lastPatches; + let callCount = 0; + const observer = jsonpatch.observe(obj, function(patches) { callCount++; lastPatches = patches; }); @@ -1504,12 +1491,12 @@ describe('duplex', function() { }); it('should generate patch after `keydown` event while observing', function(done) { - obj = { + const obj = { lastName: 'Einstein' }; - var lastPatches; - var callCount = 0; - var observer = jsonpatch.observe(obj, function(patches) { + let lastPatches; + let callCount = 0; + const observer = jsonpatch.observe(obj, function(patches) { callCount++; lastPatches = patches; }); @@ -1538,12 +1525,12 @@ describe('duplex', function() { }); it('should generate patch after `change` event while observing', function(done) { - obj = { + const obj = { lastName: 'Einstein' }; - var lastPatches; - var callCount = 0; - var observer = jsonpatch.observe(obj, function(patches) { + let lastPatches; + let callCount = 0; + const observer = jsonpatch.observe(obj, function(patches) { callCount++; lastPatches = patches; }); @@ -1580,7 +1567,7 @@ describe('duplex', function() { const objA = ['jack']; const objB = {}; - var patches = jsonpatch.compare(objA, objB, testInvertible); + const patches = jsonpatch.compare(objA, objB, testInvertible); expect(patches).toEqual([ ...insertIf(testInvertible, { op: 'test', @@ -1602,7 +1589,7 @@ describe('duplex', function() { return function () { const arr = ['jack']; const obj = {}; - var patches = jsonpatch.compare({ arr: arr }, { arr: obj }, testInvertible); + const patches = jsonpatch.compare({ arr: arr }, { arr: obj }, testInvertible); expect(patches).toEqual([ ...insertIf(testInvertible, { op: 'test', @@ -1625,7 +1612,7 @@ describe('duplex', function() { const arr = ['jack']; const obj = {}; - var patches = jsonpatch.compare({ arr: { deeperArray: arr } }, { arr: { deeperArray: obj } }, testInvertible); + const patches = jsonpatch.compare({ arr: { deeperArray: arr } }, { arr: { deeperArray: obj } }, testInvertible); expect(patches).toEqual([ ...insertIf(testInvertible, { @@ -1646,12 +1633,12 @@ describe('duplex', function() { ['invertible = TRUE', true] ], function (testInvertible) { return function () { - var objA = { + const objA = { user: { firstName: 'Albert' } }; - var objB = { + const objB = { user: { firstName: 'Albert', lastName: 'Einstein' @@ -1674,13 +1661,13 @@ describe('duplex', function() { ['invertible = TRUE', true] ], function (testInvertible) { return function () { - var objA = { + const objA = { user: { firstName: 'Albert', lastName: 'Einstein' } }; - var objB = { + const objB = { user: { firstName: 'Albert' } @@ -1705,13 +1692,13 @@ describe('duplex', function() { ['invertible = TRUE', true] ], function (testInvertible) { return function () { - var objA = { + const objA = { user: { firstName: 'Albert', lastName: 'Einstein' } }; - var objB = { + const objB = { user: { firstName: 'Albert', lastName: 'Collins' @@ -1738,10 +1725,10 @@ describe('duplex', function() { ['invertible = TRUE', true] ], function (testInvertible) { return function () { - var objA = { + const objA = { user: null }; - var objB = { + const objB = { user: {} }; @@ -1765,10 +1752,10 @@ describe('duplex', function() { ['invertible = TRUE', true] ], function (testInvertible) { return function () { - var objA = { + const objA = { user: {} }; - var objB = { + const objB = { user: null }; @@ -1792,10 +1779,10 @@ describe('duplex', function() { ['invertible = TRUE', true] ], function (testInvertible) { return function () { - var objA = { + const objA = { user: undefined }; - var objB = { + const objB = { user: undefined }; @@ -1808,10 +1795,10 @@ describe('duplex', function() { ['invertible = TRUE', true] ], function (testInvertible) { return function () { - var objA = { + const objA = { user: 0 }; - var objB = { + const objB = { user: '' }; @@ -1833,11 +1820,11 @@ describe('duplex', function() { describe('Registering multiple observers with the same callback', function() { it('should register only one observer', function(done) { - var obj = { + const obj = { foo: 'bar' }; - var callback = jasmine.createSpy('callback'); + const callback = jasmine.createSpy('callback'); jsonpatch.observe(obj, callback); jsonpatch.observe(obj, callback); @@ -1855,30 +1842,30 @@ describe('duplex', function() { }); it('should return the same observer if callback has been already registered)', function() { - var obj = { + const obj = { foo: 'bar' }; - var callback = jasmine.createSpy('callback'); + const callback = jasmine.createSpy('callback'); - var observer1 = jsonpatch.observe(obj, callback); - var observer2 = jsonpatch.observe(obj, callback); + const observer1 = jsonpatch.observe(obj, callback); + const observer2 = jsonpatch.observe(obj, callback); expect(observer1).toBe(observer2); }); it('should return a different observer if callback has been unregistered and registered again', function() { - var obj = { + const obj = { foo: 'bar' }; - var callback = jasmine.createSpy('callback'); + const callback = jasmine.createSpy('callback'); - var observer1 = jsonpatch.observe(obj, callback); + const observer1 = jsonpatch.observe(obj, callback); jsonpatch.unobserve(obj, observer1); - var observer2 = jsonpatch.observe(obj, callback); + const observer2 = jsonpatch.observe(obj, callback); expect(observer1).not.toBe(observer2); }); @@ -1886,9 +1873,9 @@ describe('duplex', function() { it('should not call callback on key and mouse events after unobserve', function( done ) { - var called = 0; + let called = 0; - obj = { + const obj = { firstName: 'Albert', lastName: 'Einstein', phoneNumbers: [ @@ -1901,7 +1888,7 @@ describe('duplex', function() { ] }; - var observer = jsonpatch.observe(obj, function(patches) { + const observer = jsonpatch.observe(obj, function(patches) { called++; }); @@ -1932,7 +1919,7 @@ describe('duplex', function() { describe('compare', function() { it('should return patch difference between objects', function() { - var obj = { + const obj = { firstName: 'Albert', lastName: 'Einstein', phoneNumbers: [ @@ -1944,7 +1931,7 @@ describe('duplex', function() { } ] }; - var obj2 = { + const obj2 = { firstName: 'Joachim', lastName: 'Wester', mobileNumbers: [ @@ -1957,7 +1944,7 @@ describe('duplex', function() { ] }; - var patches = jsonpatch.compare(obj, obj2); + const patches = jsonpatch.compare(obj, obj2); expect(patches).toReallyEqual([ { op: 'remove', @@ -1989,7 +1976,7 @@ describe('duplex', function() { }); it('should not modify the source object', function() { - var obj = { + const obj = { foo: 'bar' }; jsonpatch.compare(obj, {}); @@ -1999,8 +1986,8 @@ describe('duplex', function() { it('should work with plain objects', function() { // Objects without Object prototype - var one = Object.create(null); - var two = Object.create(null); + const one = Object.create(null); + const two = Object.create(null); one.onlyOne = Object.create(null); two.onlyTwo = Object.create(null); one.both = Object.create(null); diff --git a/test/spec/json-patch-tests/spec_tests.json b/test/spec/json-patch-tests/spec_tests.json.mjs similarity index 99% rename from test/spec/json-patch-tests/spec_tests.json rename to test/spec/json-patch-tests/spec_tests.json.mjs index c160535b..0a064634 100644 --- a/test/spec/json-patch-tests/spec_tests.json +++ b/test/spec/json-patch-tests/spec_tests.json.mjs @@ -1,4 +1,4 @@ -[ +export default [ { "comment": "4.1. add with missing object", "doc": { "q": { "bar": 2 } }, diff --git a/test/spec/json-patch-tests/tests.json b/test/spec/json-patch-tests/tests.json.mjs old mode 100755 new mode 100644 similarity index 99% rename from test/spec/json-patch-tests/tests.json rename to test/spec/json-patch-tests/tests.json.mjs index ec1c0c9c..bde26462 --- a/test/spec/json-patch-tests/tests.json +++ b/test/spec/json-patch-tests/tests.json.mjs @@ -1,4 +1,4 @@ -[ +export default [ { "comment": "empty list, empty docs", "doc": {}, "patch": [], @@ -189,17 +189,17 @@ "patch": [{"op": "replace", "path": "/foo", "value": "truthy"}], "expected": {"foo": "truthy"}, "comment": "null value should be valid obj property to be replaced with something truthy" }, - + { "doc": {"foo": null}, "patch": [{"op": "move", "from": "/foo", "path": "/bar"}], "expected": {"bar": null}, "comment": "null value should be valid obj property to be moved" }, - + { "doc": {"foo": null}, "patch": [{"op": "copy", "from": "/foo", "path": "/bar"}], "expected": {"foo": null, "bar": null}, "comment": "null value should be valid obj property to be copied" }, - + { "doc": {"foo": null}, "patch": [{"op": "remove", "path": "/foo"}], "expected": {}, diff --git a/test/spec/jsonPatchTestsSpec.js b/test/spec/jsonPatchTestsSpec.mjs similarity index 56% rename from test/spec/jsonPatchTestsSpec.js rename to test/spec/jsonPatchTestsSpec.mjs index e515fd61..86b3aa2d 100644 --- a/test/spec/jsonPatchTestsSpec.js +++ b/test/spec/jsonPatchTestsSpec.mjs @@ -1,36 +1,22 @@ -if(typeof jsonpatch == 'undefined') { - var jsonpatch = require('../../lib/duplex') -} +import * as jsonpatch from '../../index.mjs'; + +import tests_json from './json-patch-tests/tests.json.mjs'; +import spec_tests_json from './json-patch-tests/spec_tests.json.mjs'; -var JSONtests = [ +const JSONtests = [ { name: 'tests.json', - path: 'spec/json-patch-tests/tests.json' + tests: tests_json }, { name: 'spec_tests.json', - path: 'spec/json-patch-tests/spec_tests.json' + tests: spec_tests_json } ]; -var loadJsonTestSuite; -if (typeof XMLHttpRequest === 'undefined') { - var jsonfile = require('jsonfile'); - loadJsonTestSuite = function(url, callback) { - return jsonfile.readFileSync('test/' + url); - }; -} else { - loadJsonTestSuite = function(url, callback) { - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, false); - xhr.send(); - return JSON.parse(xhr.responseText); - }; -} - if (typeof Array.prototype.forEach != 'function') { Array.prototype.forEach = function(callback) { - for (var i = 0; i < this.length; i++) { + for (const i = 0; i < this.length; i++) { callback.apply(this, [this[i], i, this]); } }; @@ -39,22 +25,22 @@ if (typeof Array.prototype.forEach != 'function') { describe('json-patch-tests', function() { JSONtests.forEach(function(jsonTest) { describe(jsonTest.name, function() { - loadJsonTestSuite(jsonTest.path).forEach(function(test) { + jsonTest.tests.forEach(function(test) { if (test.disabled) { return; } - var testName = test.comment || test.error || JSON.stringify(test.patch); + const testName = test.comment || test.error || JSON.stringify(test.patch); if (test.expected) { it('should succeed: ' + testName, function() { const results = jsonpatch.applyPatch(test.doc, test.patch, true); - test.doc = results.newDocument; + test.doc = results.newDocument; expect(test.doc).toEqual(test.expected); }); } else if (test.error || test.patch[0].op === 'test') { it('should throw an error: ' + testName, function() { - var errors = 0; + let errors = 0; try { - var res = jsonpatch.applyPatch(test.doc, test.patch, true); + const res = jsonpatch.applyPatch(test.doc, test.patch, true); if (res.test === false) { throw new Error('Test failed'); } @@ -73,4 +59,4 @@ describe('json-patch-tests', function() { }); }); }); -}); +}); \ No newline at end of file diff --git a/test/spec/validateSpec.js b/test/spec/validateSpec.mjs similarity index 88% rename from test/spec/validateSpec.js rename to test/spec/validateSpec.mjs index 12cdf108..41621f93 100644 --- a/test/spec/validateSpec.js +++ b/test/spec/validateSpec.mjs @@ -1,9 +1,8 @@ -if(typeof jsonpatch == 'undefined') { - var jsonpatch = require('../../lib/duplex') -} +import * as jsonpatch from '../../index.mjs'; + describe('validate', function() { it('should return an empty array if the patch is valid', function() { - var patch = [ + const patch = [ { op: 'test', path: '/a/b/c', @@ -34,7 +33,7 @@ describe('validate', function() { path: '/a/b/e' } ]; - var error = jsonpatch.validate(patch); + const error = jsonpatch.validate(patch); expect(error).toBeUndefined(); }); @@ -98,7 +97,7 @@ describe('validate', function() { it('should return an empty array if the operation is a valid object', function() { - var error = jsonpatch.validate([ + const error = jsonpatch.validate([ { op: 'add', value: 'foo', @@ -109,30 +108,30 @@ describe('validate', function() { }); it('should return an error if the operation is null', function() { - var error = jsonpatch.validate([null]); + const error = jsonpatch.validate([null]); expect(error instanceof jsonpatch.JsonPatchError).toBe(true); expect(error.name).toBe('OPERATION_NOT_AN_OBJECT'); }); it('should return an error which is instance of Error and jsonpatch.JsonPatchError', function() { - var error = jsonpatch.validate({}); + const error = jsonpatch.validate({}); expect(error instanceof jsonpatch.JsonPatchError).toBe(true); expect(error instanceof Error).toBe(true); expect(error.name).toBe('SEQUENCE_NOT_AN_ARRAY'); }); it('should return an error that contains the cloned patch and the patched object', function() { - var tree = { + const tree = { name: 'Elvis', cars: [] }; - var sequence = [ + const sequence = [ { op: 'remove', path: '/name/first' } ]; - var error = jsonpatch.validate(sequence, tree); + const error = jsonpatch.validate(sequence, tree); expect(error instanceof jsonpatch.JsonPatchError).toBe(true); expect(JSON.stringify(error.operation)).toBe(JSON.stringify(sequence[0])); expect(JSON.stringify(error.tree)).toBe(JSON.stringify(tree)); @@ -140,19 +139,19 @@ describe('validate', function() { }); it('should return an error if the operation is undefined', function() { - var error = jsonpatch.validate([undefined]); + const error = jsonpatch.validate([undefined]); expect(error instanceof jsonpatch.JsonPatchError).toBe(true); expect(error.name).toBe('OPERATION_NOT_AN_OBJECT'); }); it('should return an error if the operation is an array', function() { - var error = jsonpatch.validate([[]]); + const error = jsonpatch.validate([[]]); expect(error instanceof jsonpatch.JsonPatchError).toBe(true); expect(error.name).toBe('OPERATION_NOT_AN_OBJECT'); }); it('should return an error if the operation "op" property is not a string', function() { - var error = jsonpatch.validate([ + const error = jsonpatch.validate([ { path: '/a/b/c' } @@ -162,7 +161,7 @@ describe('validate', function() { }); it('should return an error if the operation "path" property is not a string', function() { - var error = jsonpatch.validate([ + const error = jsonpatch.validate([ { op: 'remove', value: 'foo' @@ -173,7 +172,7 @@ describe('validate', function() { }); it('should return an error if an "add" operation is missing "value" property', function() { - var error = jsonpatch.validate([ + const error = jsonpatch.validate([ { op: 'add', path: '/a/b/c' @@ -184,7 +183,7 @@ describe('validate', function() { }); it('should return an error if an "add" operation "value" property is "undefined"', function() { - var error = jsonpatch.validate([ + const error = jsonpatch.validate([ { op: 'add', path: '/a/b/c', @@ -196,7 +195,7 @@ describe('validate', function() { }); it('should return an error if a "replace" operation is missing "value" property', function() { - var error = jsonpatch.validate([ + const error = jsonpatch.validate([ { op: 'replace', path: '/a/b/c' @@ -207,7 +206,7 @@ describe('validate', function() { }); it('should return an error if a "replace" operation "value" property is "undefined"', function() { - var error = jsonpatch.validate([ + const error = jsonpatch.validate([ { op: 'replace', path: '/a/b/c', @@ -219,7 +218,7 @@ describe('validate', function() { }); it('should return an error if a "test" operation is missing "value" property', function() { - var error = jsonpatch.validate([ + const error = jsonpatch.validate([ { op: 'test', path: '/a/b/c' @@ -230,7 +229,7 @@ describe('validate', function() { }); it('should return an error if a "test" operation "value" property is "undefined"', function() { - var error = jsonpatch.validate([ + const error = jsonpatch.validate([ { op: 'test', path: '/a/b/c', @@ -242,7 +241,7 @@ describe('validate', function() { }); it('should return an error if an "add" operation "value" contains "undefined"', function() { - var error = jsonpatch.validate([ + const error = jsonpatch.validate([ { op: 'add', path: '/a/b/c', @@ -256,7 +255,7 @@ describe('validate', function() { }); it('should return an error if a "replace" operation "value" contains "undefined"', function() { - var error = jsonpatch.validate([ + const error = jsonpatch.validate([ { op: 'replace', path: '/a/b/c', @@ -270,7 +269,7 @@ describe('validate', function() { }); it('should return an error if a "test" operation "value" contains "undefined"', function() { - var error = jsonpatch.validate([ + const error = jsonpatch.validate([ { op: 'test', path: '/a/b/c', @@ -286,7 +285,7 @@ describe('validate', function() { }); it('should return an error if a "move" operation is missing "from" property', function() { - var error = jsonpatch.validate([ + const error = jsonpatch.validate([ { op: 'move', path: '/a/b/c' @@ -297,7 +296,7 @@ describe('validate', function() { }); it('should return an error if a "copy" operation is missing "from" property', function() { - var error = jsonpatch.validate([ + const error = jsonpatch.validate([ { op: 'copy', path: '/a/b/c' @@ -308,7 +307,7 @@ describe('validate', function() { }); it('should return an error if the "op" property is invalid', function() { - var error = jsonpatch.validate([ + const error = jsonpatch.validate([ { op: 'foobar', path: '/a/b/c' @@ -319,9 +318,9 @@ describe('validate', function() { }); it('should return error replacing an unexisting path', function() { - var sequence, - error, - tree = { + let sequence; + let error; + const tree = { '': 'empty string is a valid key', name: 'Elvis', cars: [ @@ -364,28 +363,28 @@ describe('validate', function() { }); it('should return error removing an unexisting path', function() { - var tree = { + const tree = { name: 'Elvis', cars: [] }; - var sequence = [ + const sequence = [ { op: 'remove', path: '/name/first' } ]; - var error = jsonpatch.validate(sequence, tree); + const error = jsonpatch.validate(sequence, tree); expect(error instanceof jsonpatch.JsonPatchError).toBe(true); expect(error.name).toBe('OPERATION_PATH_UNRESOLVABLE'); }); it('should allow adding property "b" in "a"', function() { - var tree = { + const tree = { a: { foo: 1 } }; - var sequence = [ + const sequence = [ { op: 'add', path: '/a/b', @@ -393,17 +392,17 @@ describe('validate', function() { } ]; - var error = jsonpatch.validate(sequence, tree); + const error = jsonpatch.validate(sequence, tree); expect(error).toBeUndefined(); }); it('should report error because "a" does not exist', function() { - var tree = { + const tree = { q: { bar: 2 } }; - var sequence = [ + const sequence = [ { op: 'add', path: '/a/b', @@ -411,16 +410,16 @@ describe('validate', function() { } ]; - var error = jsonpatch.validate(sequence, tree); + const error = jsonpatch.validate(sequence, tree); expect(error instanceof jsonpatch.JsonPatchError).toBe(true); expect(error.name).toBe('OPERATION_PATH_CANNOT_ADD'); }); it('should return error when replacing a removed path', function() { - var tree = { + const tree = { name: 'Elvis' }; - var sequence = [ + const sequence = [ { op: 'remove', path: '/name' @@ -431,15 +430,15 @@ describe('validate', function() { value: 'Freddie' } ]; - var error = jsonpatch.validate(sequence, tree); + const error = jsonpatch.validate(sequence, tree); expect(error.name).toBe('OPERATION_PATH_UNRESOLVABLE'); }); it('should allow to override validator to add custom validation', function() { - var tree = { + const tree = { password: 'Elvis' }; - var sequence = [ + const sequence = [ { op: 'replace', path: '/password', @@ -466,16 +465,16 @@ describe('validate', function() { tree ); } - var customError = jsonpatch.validate(sequence, tree, validator); + const customError = jsonpatch.validate(sequence, tree, validator); expect(customError.index).toBe(0); expect(customError.name).toBe('OPERATION_VALUE_MUST_NOT_CONTAIN_OLD_VALUE'); }); it('should pass replacing the tree root', function() { - var tree = { + const tree = { password: 'Elvis' }; - var sequence = [ + const sequence = [ { op: 'replace', path: '', @@ -483,43 +482,43 @@ describe('validate', function() { } ]; - var error = jsonpatch.validate(sequence, tree); + const error = jsonpatch.validate(sequence, tree); expect(error).toBeUndefined(); }); it('should return error moving from an unexisting path', function() { - var tree = { + const tree = { name: 'Elvis' }; - var sequence = [ + const sequence = [ { op: 'move', from: '/a/b/c', path: '/name' } ]; - var error = jsonpatch.validate(sequence, tree); + const error = jsonpatch.validate(sequence, tree); expect(error.name).toBe('OPERATION_FROM_UNRESOLVABLE'); }); it('should return error copying from an unexisting path', function() { - var tree = { + const tree = { name: 'Elvis' }; - var sequence = [ + const sequence = [ { op: 'copy', from: '/a/b/c', path: '/name' } ]; - var error = jsonpatch.validate(sequence, tree); + const error = jsonpatch.validate(sequence, tree); expect(error.name).toBe('OPERATION_FROM_UNRESOLVABLE'); }); it('should throw OPERATION_PATH_INVALID when applying patch without path', function() { - var a = {}; - var ex = null; + const a = {}; + let ex = null; try { jsonpatch.applyPatch( @@ -540,8 +539,8 @@ describe('validate', function() { }); it('should throw OPERATION_PATH_INVALID when applying patch with an invalid path. Issue #77.', function() { - var a = {}; - var ex = null; + const a = {}; + let ex = null; try { jsonpatch.applyPatch( @@ -562,8 +561,8 @@ describe('validate', function() { }); it('should throw OPERATION_OP_INVALID when applying patch without operation', function() { - var a = {}; - var ex = null; + const a = {}; + let ex = null; try { jsonpatch.applyPatch( @@ -584,8 +583,8 @@ describe('validate', function() { }); it('should throw OPERATION_VALUE_REQUIRED when applying patch without value', function() { - var a = {}; - var ex = null; + const a = {}; + let ex = null; try { jsonpatch.applyPatch( @@ -606,7 +605,7 @@ describe('validate', function() { }); it('should not modify patch value of type array (issue #76)', function () { - var patches = [ + const patches = [ {op: 'add', path: '/foo', value: []}, {op: 'add', path: '/foo/-', value: 1} ]; @@ -619,7 +618,7 @@ describe('validate', function() { }); it('should not modify patch value of type object (issue #76)', function () { - var patches = [ + const patches = [ {op: 'add', path: '/foo', value: {}}, {op: 'add', path: '/foo/bar', value: 1} ]; diff --git a/test/spec/webpack/importSpec.build.js b/test/spec/webpack/importSpec.build.js index 46430b25..48bc80d9 100644 --- a/test/spec/webpack/importSpec.build.js +++ b/test/spec/webpack/importSpec.build.js @@ -1,13 +1,7 @@ -!function(e){var t={};function o(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,o),r.l=!0,r.exports}o.m=e,o.c=t,o.d=function(e,t,n){o.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,t){if(1&t&&(e=o(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(o.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)o.d(n,r,function(t){return e[t]}.bind(null,r));return n},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,"a",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p="",o(o.s=3)}([function(e,t,o){var n=this&&this.__assign||function(){return(n=Object.assign||function(e){for(var t,o=1,n=arguments.length;o0&&(e.patches=[],e.callback&&e.callback(n)),n}function d(e,t,o,n,a){if(t!==e){"function"==typeof t.toJSON&&(t=t.toJSON());for(var i=r._objectKeys(t),p=r._objectKeys(e),c=!1,u=p.length-1;u>=0;u--){var s=e[f=p[u]];if(!r.hasOwnProperty(t,f)||void 0===t[f]&&void 0!==s&&!1===Array.isArray(t))Array.isArray(e)===Array.isArray(t)?(a&&o.push({op:"test",path:n+"/"+r.escapePathComponent(f),value:r._deepClone(s)}),o.push({op:"remove",path:n+"/"+r.escapePathComponent(f)}),c=!0):(a&&o.push({op:"test",path:n,value:e}),o.push({op:"replace",path:n,value:t}),!0);else{var h=t[f];"object"==typeof s&&null!=s&&"object"==typeof h&&null!=h?d(s,h,o,n+"/"+r.escapePathComponent(f),a):s!==h&&(!0,a&&o.push({op:"test",path:n+"/"+r.escapePathComponent(f),value:r._deepClone(s)}),o.push({op:"replace",path:n+"/"+r.escapePathComponent(f),value:r._deepClone(h)}))}}if(c||i.length!=p.length)for(u=0;u=48&&t<=57))return!1;o++}return!0},t.escapePathComponent=p,t.unescapePathComponent=function(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")},t._getPathRecursive=c,t.getPath=function(e,t){if(e===t)return"/";var o=c(e,t);if(""===o)throw new Error("Object not found in root");return"/"+o},t.hasUndefined=function e(t){if(void 0===t)return!0;if(t)if(Array.isArray(t)){for(var o=0,n=t.length;o=y){if(c&&"add"===o.op&&b>v.length)throw new t.JsonPatchError("The specified index MUST NOT be greater than the number of elements in the array","OPERATION_VALUE_OUT_OF_BOUNDS",f,o,e);if(!1===(l=i[o.op].call(o,v,b,e)).test)throw new t.JsonPatchError("Test operation failed","TEST_OPERATION_FAILED",f,o,e);return l}}else if(b&&-1!=b.indexOf("~")&&(b=r.unescapePathComponent(b)),w>=y){if(!1===(l=a[o.op].call(o,v,b,e)).test)throw new t.JsonPatchError("Test operation failed","TEST_OPERATION_FAILED",f,o,e);return l}v=v[b]}}function u(e,o,n,a,i){if(void 0===a&&(a=!0),void 0===i&&(i=!0),n&&!Array.isArray(o))throw new t.JsonPatchError("Patch sequence must be an array","SEQUENCE_NOT_AN_ARRAY");a||(e=r._deepClone(e));for(var p=new Array(o.length),u=0,s=o.length;u0)throw new t.JsonPatchError('Operation `path` property must start with "/"',"OPERATION_PATH_INVALID",o,e,n);if(("move"===e.op||"copy"===e.op)&&"string"!=typeof e.from)throw new t.JsonPatchError("Operation `from` property is not present (applicable in `move` and `copy` operations)","OPERATION_FROM_REQUIRED",o,e,n);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&void 0===e.value)throw new t.JsonPatchError("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_REQUIRED",o,e,n);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&r.hasUndefined(e.value))throw new t.JsonPatchError("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED",o,e,n);if(n)if("add"==e.op){var p=e.path.split("/").length,c=i.split("/").length;if(p!==c+1&&p!==c)throw new t.JsonPatchError("Cannot perform an `add` operation at the desired path","OPERATION_PATH_CANNOT_ADD",o,e,n)}else if("replace"===e.op||"remove"===e.op||"_get"===e.op){if(e.path!==i)throw new t.JsonPatchError("Cannot perform the operation at a path that does not exist","OPERATION_PATH_UNRESOLVABLE",o,e,n)}else if("move"===e.op||"copy"===e.op){var u=f([{op:"_get",path:e.from,value:void 0}],n);if(u&&"OPERATION_PATH_UNRESOLVABLE"===u.name)throw new t.JsonPatchError("Cannot perform the operation from a path that does not exist","OPERATION_FROM_UNRESOLVABLE",o,e,n)}}function f(e,o,n){try{if(!Array.isArray(e))throw new t.JsonPatchError("Patch sequence must be an array","SEQUENCE_NOT_AN_ARRAY");if(o)u(r._deepClone(o),r._deepClone(e),n||!0);else{n=n||h;for(var a=0;a=48&&t<=57))return!1;n++}return!0}function h(e){return-1===e.indexOf("/")&&-1===e.indexOf("~")?e:e.replace(/~/g,"~0").replace(/\//g,"~1")}function l(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")}function v(e,t){var n=[e];for(var o in t){var r="object"==typeof t[o]?JSON.stringify(t[o],null,2):t[o];void 0!==r&&n.push(o+": "+r)}return n.join("\n")}var w=function(e){function t(t,n,o,r,i){var a=this.constructor,u=e.call(this,v(t,{name:n,index:o,operation:r,tree:i}))||this;return u.name=n,u.index=o,u.operation=r,u.tree=i,Object.setPrototypeOf(u,a.prototype),u.message=v(t,{name:n,index:o,operation:r,tree:i}),u}return u(t,e),t}(Error),m=w,y=d,b={add:function(e,t,n){return e[t]=this.value,{newDocument:n}},remove:function(e,t,n){var o=e[t];return delete e[t],{newDocument:n,removed:o}},replace:function(e,t,n){var o=e[t];return e[t]=this.value,{newDocument:n,removed:o}},move:function(e,t,n){var o=_(n,this.path);o&&(o=d(o));var r=A(n,{op:"remove",path:this.from}).removed;return A(n,{op:"add",path:this.path,value:r}),{newDocument:n,removed:o}},copy:function(e,t,n){var o=_(n,this.from);return A(n,{op:"add",path:this.path,value:d(o)}),{newDocument:n}},test:function(e,t,n){return{newDocument:n,test:T(e[t],this.value)}},_get:function(e,t,n){return this.value=e[t],{newDocument:n}}},O={add:function(e,t,n){return s(t)?e.splice(t,0,this.value):e[t]=this.value,{newDocument:n,index:t}},remove:function(e,t,n){return{newDocument:n,removed:e.splice(t,1)[0]}},replace:function(e,t,n){var o=e[t];return e[t]=this.value,{newDocument:n,removed:o}},move:b.move,copy:b.copy,test:b.test,_get:b._get};function _(e,t){if(""==t)return e;var n={op:"_get",path:t};return A(e,n),n.value}function A(e,t,n,o,r,i){if(void 0===n&&(n=!1),void 0===o&&(o=!0),void 0===r&&(r=!0),void 0===i&&(i=0),n&&("function"==typeof n?n(t,0,e,t.path):P(t,0)),""===t.path){var a={newDocument:e};if("add"===t.op)return a.newDocument=t.value,a;if("replace"===t.op)return a.newDocument=t.value,a.removed=e,a;if("move"===t.op||"copy"===t.op)return a.newDocument=_(e,t.from),"move"===t.op&&(a.removed=e),a;if("test"===t.op){if(a.test=T(e,t.value),!1===a.test)throw new m("Test operation failed","TEST_OPERATION_FAILED",i,t,e);return a.newDocument=e,a}if("remove"===t.op)return a.removed=e,a.newDocument=null,a;if("_get"===t.op)return t.value=e,a;if(n)throw new m("Operation `op` property is not one of operations defined in RFC-6902","OPERATION_OP_INVALID",i,t,e);return a}o||(e=d(e));var u=(t.path||"").split("/"),p=e,c=1,f=u.length,h=void 0,v=void 0,w=void 0;for(w="function"==typeof n?n:P;;){if(v=u[c],r&&"__proto__"==v)throw new TypeError("JSON-Patch: modifying `__proto__` prop is banned for security reasons, if this was on purpose, please set `banPrototypeModifications` flag false and pass it to this function. More info in fast-json-patch README");if(n&&void 0===h&&(void 0===p[v]?h=u.slice(0,c).join("/"):c==f-1&&(h=t.path),void 0!==h&&w(t,0,e,h)),c++,Array.isArray(p)){if("-"===v)v=p.length;else{if(n&&!s(v))throw new m("Expected an unsigned base-10 integer value, making the new referenced value the array element with the zero-based index","OPERATION_PATH_ILLEGAL_ARRAY_INDEX",i,t,e);s(v)&&(v=~~v)}if(c>=f){if(n&&"add"===t.op&&v>p.length)throw new m("The specified index MUST NOT be greater than the number of elements in the array","OPERATION_VALUE_OUT_OF_BOUNDS",i,t,e);if(!1===(a=O[t.op].call(t,p,v,e)).test)throw new m("Test operation failed","TEST_OPERATION_FAILED",i,t,e);return a}}else if(v&&-1!=v.indexOf("~")&&(v=l(v)),c>=f){if(!1===(a=b[t.op].call(t,p,v,e)).test)throw new m("Test operation failed","TEST_OPERATION_FAILED",i,t,e);return a}p=p[v]}}function E(e,t,n,o,r){if(void 0===o&&(o=!0),void 0===r&&(r=!0),n&&!Array.isArray(t))throw new m("Patch sequence must be an array","SEQUENCE_NOT_AN_ARRAY");o||(e=d(e));for(var i=new Array(t.length),a=0,u=t.length;a0)throw new m('Operation `path` property must start with "/"',"OPERATION_PATH_INVALID",t,e,n);if(("move"===e.op||"copy"===e.op)&&"string"!=typeof e.from)throw new m("Operation `from` property is not present (applicable in `move` and `copy` operations)","OPERATION_FROM_REQUIRED",t,e,n);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&void 0===e.value)throw new m("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_REQUIRED",t,e,n);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&function e(t){if(void 0===t)return!0;if(t)if(Array.isArray(t)){for(var n=0,o=t.length;n0&&(e.patches=[],e.callback&&e.callback(o)),o}function S(e,t,n,o,r){if(t!==e){"function"==typeof t.toJSON&&(t=t.toJSON());for(var i=f(t),a=f(e),u=!1,p=a.length-1;p>=0;p--){var s=e[v=a[p]];if(!c(t,v)||void 0===t[v]&&void 0!==s&&!1===Array.isArray(t))Array.isArray(e)===Array.isArray(t)?(r&&n.push({op:"test",path:o+"/"+h(v),value:d(s)}),n.push({op:"remove",path:o+"/"+h(v)}),u=!0):(r&&n.push({op:"test",path:o,value:e}),n.push({op:"replace",path:o,value:t}),!0);else{var l=t[v];"object"==typeof s&&null!=s&&"object"==typeof l&&null!=l?S(s,l,n,o+"/"+h(v),r):s!==l&&(!0,r&&n.push({op:"test",path:o+"/"+h(v),value:d(s)}),n.push({op:"replace",path:o+"/"+h(v),value:d(l)}))}}if(u||i.length!=a.length)for(p=0;p { else { return [ { - entry: './lib/duplex.js', + entry: './index.js', mode: 'production', optimization: { minimize: false @@ -40,7 +40,7 @@ module.exports = env => { ] }, { - entry: './lib/duplex.js', + entry: './index.js', mode: 'production', output: { filename: 'fast-json-patch.min.js', @@ -61,4 +61,3 @@ module.exports = env => { -