Add JSON Patch functions and immutability helpers
This commit is contained in:
parent
1bc28041d3
commit
635616ac1c
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"test": "./src/**/*.test.js"
|
||||
}
|
|
@ -139,9 +139,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
|
@ -172,12 +172,6 @@
|
|||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"assertion-error": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
|
||||
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
|
||||
"dev": true
|
||||
},
|
||||
"async-limiter": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
|
||||
|
@ -239,20 +233,6 @@
|
|||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"dev": true
|
||||
},
|
||||
"chai": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
|
||||
"integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"assertion-error": "^1.1.0",
|
||||
"check-error": "^1.0.2",
|
||||
"deep-eql": "^3.0.1",
|
||||
"get-func-name": "^2.0.0",
|
||||
"pathval": "^1.1.0",
|
||||
"type-detect": "^4.0.5"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
|
@ -264,12 +244,6 @@
|
|||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"check-error": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
|
||||
"integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
|
||||
"dev": true
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz",
|
||||
|
@ -286,6 +260,45 @@
|
|||
"readdirp": "~3.3.0"
|
||||
}
|
||||
},
|
||||
"cliui": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
|
||||
"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"string-width": "^3.1.0",
|
||||
"strip-ansi": "^5.2.0",
|
||||
"wrap-ansi": "^5.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
|
||||
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"emoji-regex": "^7.0.1",
|
||||
"is-fullwidth-code-point": "^2.0.0",
|
||||
"strip-ansi": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
||||
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^4.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
|
@ -325,21 +338,21 @@
|
|||
"integrity": "sha512-pMD+MVR538ipqkG5JXeOEbKWS5um1H4LUUccUQG68qpeqBYbzYy79Gh55jkd2TtPdRfUaLWdv6LPP//5Zt0aPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
|
||||
"dev": true
|
||||
},
|
||||
"deep-eql": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
|
||||
"integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"type-detect": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"define-properties": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
|
||||
|
@ -361,6 +374,12 @@
|
|||
"integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
|
||||
"dev": true
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
|
||||
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
|
||||
"dev": true
|
||||
},
|
||||
"es-abstract": {
|
||||
"version": "1.17.5",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz",
|
||||
|
@ -418,6 +437,15 @@
|
|||
"to-regex-range": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"find-up": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
|
||||
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"locate-path": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"flat": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz",
|
||||
|
@ -425,14 +453,6 @@
|
|||
"dev": true,
|
||||
"requires": {
|
||||
"is-buffer": "~2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"is-buffer": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
|
||||
"integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
|
@ -460,12 +480,6 @@
|
|||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"dev": true
|
||||
},
|
||||
"get-func-name": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
|
||||
"integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
|
||||
"dev": true
|
||||
},
|
||||
"get-port": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz",
|
||||
|
@ -553,6 +567,12 @@
|
|||
"binary-extensions": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"is-buffer": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
|
||||
"integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==",
|
||||
"dev": true
|
||||
},
|
||||
"is-callable": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
|
||||
|
@ -571,6 +591,12 @@
|
|||
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
|
||||
"dev": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
|
||||
"dev": true
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
|
||||
|
@ -692,11 +718,25 @@
|
|||
"integrity": "sha512-ykt2pgN0aqIy6KQC1CqdWTWkmUwNgaOS6dcpHVjyBJONA+Xi7AtSB1vuxC/U/0tjIP3wcRudwQk1YYzUvzk2bA==",
|
||||
"dev": true
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
||||
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-locate": "^3.0.0",
|
||||
"path-exists": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
||||
"dev": true
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||
},
|
||||
"lodash-es": {
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz",
|
||||
"integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ=="
|
||||
},
|
||||
"log-symbols": {
|
||||
"version": "3.0.0",
|
||||
|
@ -760,18 +800,18 @@
|
|||
"dev": true
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz",
|
||||
"integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==",
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
||||
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"mocha": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz",
|
||||
"integrity": "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==",
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.2.tgz",
|
||||
"integrity": "sha512-o96kdRKMKI3E8U0bjnfqW4QMk12MwZ4mhdBTf+B5a1q9+aq2HRnj+3ZdJu0B/ZhJeK78MgYuv6L8d/rA5AeBJA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-colors": "3.2.3",
|
||||
|
@ -787,7 +827,7 @@
|
|||
"js-yaml": "3.13.1",
|
||||
"log-symbols": "3.0.0",
|
||||
"minimatch": "3.0.4",
|
||||
"mkdirp": "0.5.3",
|
||||
"mkdirp": "0.5.5",
|
||||
"ms": "2.1.1",
|
||||
"node-environment-flags": "1.0.6",
|
||||
"object.assign": "4.1.0",
|
||||
|
@ -816,41 +856,6 @@
|
|||
"readdirp": "~3.2.0"
|
||||
}
|
||||
},
|
||||
"cliui": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
|
||||
"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"string-width": "^3.1.0",
|
||||
"strip-ansi": "^5.2.0",
|
||||
"wrap-ansi": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
|
||||
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
|
||||
"dev": true
|
||||
},
|
||||
"find-up": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
|
||||
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"locate-path": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
|
||||
|
@ -865,43 +870,6 @@
|
|||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
|
||||
"dev": true
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
||||
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-locate": "^3.0.0",
|
||||
"path-exists": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
|
||||
"dev": true
|
||||
},
|
||||
"p-locate": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
|
||||
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-limit": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"path-exists": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
|
||||
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
|
||||
"dev": true
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz",
|
||||
|
@ -911,26 +879,6 @@
|
|||
"picomatch": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"string-width": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
|
||||
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"emoji-regex": "^7.0.1",
|
||||
"is-fullwidth-code-point": "^2.0.0",
|
||||
"strip-ansi": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
||||
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz",
|
||||
|
@ -939,54 +887,6 @@
|
|||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"which": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
|
||||
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.0",
|
||||
"string-width": "^3.0.0",
|
||||
"strip-ansi": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"yargs": {
|
||||
"version": "13.3.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
|
||||
"integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cliui": "^5.0.0",
|
||||
"find-up": "^3.0.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^3.0.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^13.1.2"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "13.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
|
||||
"integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -996,6 +896,12 @@
|
|||
"integrity": "sha512-d2RKzMD4JNyHMbnbWnznPaa8vbdlq/4pNZ3IgdaGrVbBhebBsGUUE/6qorTMYNS6TwuH3ilfOlD2bf4Igh8CKg==",
|
||||
"dev": true
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
|
||||
"dev": true
|
||||
},
|
||||
"node-environment-flags": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz",
|
||||
|
@ -1004,14 +910,6 @@
|
|||
"requires": {
|
||||
"object.getownpropertydescriptors": "^2.0.3",
|
||||
"semver": "^5.7.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"normalize-path": {
|
||||
|
@ -1078,12 +976,27 @@
|
|||
"p-try": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"p-locate": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
|
||||
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-limit": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"p-try": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||
"dev": true
|
||||
},
|
||||
"path-exists": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
|
||||
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
|
||||
"dev": true
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
|
@ -1096,12 +1009,6 @@
|
|||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
||||
"dev": true
|
||||
},
|
||||
"pathval": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
|
||||
"integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
|
||||
"dev": true
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
|
||||
|
@ -1221,6 +1128,12 @@
|
|||
"chokidar": ">=2.0.0 <4.0.0"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true
|
||||
},
|
||||
"serialize-javascript": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
|
||||
|
@ -1286,6 +1199,16 @@
|
|||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
||||
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-fullwidth-code-point": "^2.0.0",
|
||||
"strip-ansi": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"string.prototype.trimend": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz",
|
||||
|
@ -1328,6 +1251,15 @@
|
|||
"es-abstract": "^1.17.5"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
||||
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"strip-indent": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
|
||||
|
@ -1403,11 +1335,14 @@
|
|||
"is-number": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"type-detect": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
|
||||
"dev": true
|
||||
"which": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"which-module": {
|
||||
"version": "2.0.0",
|
||||
|
@ -1422,37 +1357,43 @@
|
|||
"dev": true,
|
||||
"requires": {
|
||||
"string-width": "^1.0.2 || 2"
|
||||
}
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
|
||||
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.0",
|
||||
"string-width": "^3.0.0",
|
||||
"strip-ansi": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
|
||||
"dev": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
||||
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
|
||||
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"emoji-regex": "^7.0.1",
|
||||
"is-fullwidth-code-point": "^2.0.0",
|
||||
"strip-ansi": "^4.0.0"
|
||||
"strip-ansi": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
||||
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
||||
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^3.0.0"
|
||||
"ansi-regex": "^4.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1478,72 +1419,28 @@
|
|||
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
|
||||
"dev": true
|
||||
},
|
||||
"yargs-unparser": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz",
|
||||
"integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==",
|
||||
"yargs": {
|
||||
"version": "13.3.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
|
||||
"integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"flat": "^4.1.0",
|
||||
"lodash": "^4.17.15",
|
||||
"yargs": "^13.3.0"
|
||||
"cliui": "^5.0.0",
|
||||
"find-up": "^3.0.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^3.0.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^13.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"cliui": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
|
||||
"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"string-width": "^3.1.0",
|
||||
"strip-ansi": "^5.2.0",
|
||||
"wrap-ansi": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
|
||||
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
|
||||
"dev": true
|
||||
},
|
||||
"find-up": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
|
||||
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"locate-path": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
|
||||
"dev": true
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
||||
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-locate": "^3.0.0",
|
||||
"path-exists": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"p-locate": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
|
||||
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-limit": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"path-exists": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
|
||||
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
|
||||
"ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
|
@ -1565,47 +1462,29 @@
|
|||
"requires": {
|
||||
"ansi-regex": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
|
||||
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.0",
|
||||
"string-width": "^3.0.0",
|
||||
"strip-ansi": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"yargs": {
|
||||
"version": "13.3.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
|
||||
"integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cliui": "^5.0.0",
|
||||
"find-up": "^3.0.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^3.0.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^13.1.2"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "13.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
|
||||
"integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "13.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
|
||||
"integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"yargs-unparser": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz",
|
||||
"integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"flat": "^4.1.0",
|
||||
"lodash": "^4.17.15",
|
||||
"yargs": "^13.3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,12 +11,13 @@
|
|||
"dependencies": {
|
||||
"@fortawesome/free-regular-svg-icons": "5.13.0",
|
||||
"@fortawesome/free-solid-svg-icons": "5.13.0",
|
||||
"lodash": "4.17.15",
|
||||
"svelte-awesome": "2.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "11.1.0",
|
||||
"@rollup/plugin-node-resolve": "7.1.3",
|
||||
"mocha": "7.1.1",
|
||||
"mocha": "7.1.2",
|
||||
"rollup": "2.7.2",
|
||||
"rollup-plugin-livereload": "1.2.0",
|
||||
"rollup-plugin-svelte": "5.2.1",
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import Icon from 'svelte-awesome'
|
||||
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||
import Node from './JSONNode.svelte'
|
||||
import { search } from './utils'
|
||||
import { search } from './search'
|
||||
import { beforeUpdate, afterUpdate } from 'svelte'
|
||||
|
||||
export let searchText = ''
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { getType, SEARCH_PROPERTY, SEARCH_VALUE } from './utils'
|
||||
import { SEARCH_PROPERTY, SEARCH_VALUE } from './search'
|
||||
import { getJSONNodeType } from './utils/typeUtils.js'
|
||||
|
||||
export let key = 'root'
|
||||
export let value
|
||||
|
@ -12,7 +13,7 @@
|
|||
|
||||
let limit = DEFAULT_LIMIT
|
||||
|
||||
$: type = getType(value)
|
||||
$: type = getJSONNodeType(value)
|
||||
|
||||
$: props = type === 'object'
|
||||
? Object.keys(value).map(key => {
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
import { getJSONNodeType } from './utils/typeUtils'
|
||||
|
||||
export const SEARCH_PROPERTY = Symbol('searchProperty')
|
||||
export const SEARCH_VALUE = Symbol('searchValue')
|
||||
|
||||
export function getType (json) {
|
||||
return Array.isArray(json)
|
||||
? 'array'
|
||||
: json && typeof json === 'object'
|
||||
? 'object'
|
||||
: 'value'
|
||||
}
|
||||
|
||||
export function search (key, value, searchText) {
|
||||
let results
|
||||
|
||||
|
@ -16,7 +10,7 @@ export function search (key, value, searchText) {
|
|||
results = createOrAdd(results, SEARCH_PROPERTY, true)
|
||||
}
|
||||
|
||||
const type = getType(value)
|
||||
const type = getJSONNodeType(value)
|
||||
if (type === 'array') {
|
||||
value.forEach((item, index) => {
|
||||
let childResults = search(index, item, searchText)
|
|
@ -0,0 +1,58 @@
|
|||
/** JSDoc type definitions */
|
||||
|
||||
/**
|
||||
* @typedef {{} | [] | string | number | boolean | null} JSON
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* name: string?,
|
||||
* mode: 'code' | 'form' | 'text' | 'tree' | 'view'?,
|
||||
* modes: string[]?,
|
||||
* history: boolean?,
|
||||
* indentation: number | string?,
|
||||
* onChange: function (patch: JSONPatchDocument, revert: JSONPatchDocument)?,
|
||||
* onChangeText: function ()?,
|
||||
* onChangeMode: function (mode: string, prevMode: string)?,
|
||||
* onError: function (err: Error)?,
|
||||
* isPropertyEditable: function (Path)?
|
||||
* isValueEditable: function (Path)?,
|
||||
* escapeUnicode: boolean?,
|
||||
* expand: function(path: Path) : boolean?,
|
||||
* ajv: Object?,
|
||||
* ace: Object?
|
||||
* }} Options
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {string[]} Path
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* op: 'add' | 'remove' | 'replace' | 'copy' | 'move' | 'test',
|
||||
* path: string,
|
||||
* from?: string,
|
||||
* value?: *
|
||||
* }} JSONPatchOperation
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {JSONPatchOperation[]} JSONPatchDocument
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* fromJSON: function(json: JSON, previousObject: * | undefined),
|
||||
* toJSON: function(object: *),
|
||||
* clone: function(object: *)
|
||||
* }} JSONPatchOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* patch: JSONPatchDocument,
|
||||
* revert: JSONPatchDocument,
|
||||
* error: Error | null
|
||||
* }} JSONPatchResult
|
||||
*/
|
|
@ -1,13 +0,0 @@
|
|||
import { getType } from './utils.js'
|
||||
import assert from 'assert'
|
||||
|
||||
describe('utils', () => {
|
||||
it('should test equality of positive values', function () {
|
||||
assert.strictEqual(getType([]), 'array')
|
||||
assert.strictEqual(getType({}), 'object')
|
||||
assert.strictEqual(getType(null), 'value')
|
||||
assert.strictEqual(getType(2), 'value')
|
||||
assert.strictEqual(getType('hello'), 'value')
|
||||
assert.strictEqual(getType('hello'), 'value')
|
||||
})
|
||||
})
|
|
@ -0,0 +1,293 @@
|
|||
import { isObjectOrArray } from './typeUtils.js'
|
||||
|
||||
/**
|
||||
* Immutability helpers
|
||||
*
|
||||
* inspiration:
|
||||
*
|
||||
* https://www.npmjs.com/package/seamless-immutable
|
||||
* https://www.npmjs.com/package/ih
|
||||
* https://www.npmjs.com/package/mutatis
|
||||
* https://github.com/mariocasciaro/object-path-immutable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Shallow clone of an Object, Array, or value
|
||||
* Also copies any symbols on the Objects and Arrays
|
||||
* @param {*} value
|
||||
* @return {*}
|
||||
*/
|
||||
export function shallowCloneWithSymbols (value) {
|
||||
if (Array.isArray(value)) {
|
||||
// copy array items
|
||||
let arr = value.slice()
|
||||
|
||||
// copy all symbols
|
||||
Object.getOwnPropertySymbols(value).forEach(symbol => arr[symbol] = value[symbol])
|
||||
|
||||
return arr
|
||||
}
|
||||
else if (typeof value === 'object') {
|
||||
// copy properties
|
||||
let obj = {}
|
||||
for (let prop in value) {
|
||||
if (value.hasOwnProperty(prop)) {
|
||||
obj[prop] = value[prop]
|
||||
}
|
||||
}
|
||||
|
||||
// copy all symbols
|
||||
Object.getOwnPropertySymbols(value).forEach(symbol => obj[symbol] = value[symbol])
|
||||
|
||||
return obj
|
||||
}
|
||||
else {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* helper function to get a nested property in an object or array
|
||||
*
|
||||
* @param {Object | Array} object
|
||||
* @param {Path} path
|
||||
* @return {* | undefined} Returns the field when found, or undefined when the
|
||||
* path doesn't exist
|
||||
*/
|
||||
export function getIn (object, path) {
|
||||
let value = object
|
||||
let i = 0
|
||||
|
||||
while(i < path.length) {
|
||||
if (isObjectOrArray(value)) {
|
||||
value = value[path[i]]
|
||||
}
|
||||
else {
|
||||
value = undefined
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* helper function to replace a nested property in an object with a new value
|
||||
* without mutating the object itself.
|
||||
*
|
||||
* @param {Object | Array} object
|
||||
* @param {Path} path
|
||||
* @param {*} value
|
||||
* @return {Object | Array} Returns a new, updated object or array
|
||||
*/
|
||||
export function setIn (object, path, value) {
|
||||
if (path.length === 0) {
|
||||
return value
|
||||
}
|
||||
|
||||
if (!isObjectOrArray(object)) {
|
||||
throw new Error('Path does not exist')
|
||||
}
|
||||
|
||||
const key = path[0]
|
||||
const updatedValue = setIn(object[key], path.slice(1), value)
|
||||
if (object[key] === updatedValue) {
|
||||
// return original object unchanged when the new value is identical to the old one
|
||||
return object
|
||||
}
|
||||
else {
|
||||
const updatedObject = shallowCloneWithSymbols(object)
|
||||
updatedObject[key] = updatedValue
|
||||
return updatedObject
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* helper function to replace a nested property in an object with a new value
|
||||
* without mutating the object itself.
|
||||
*
|
||||
* @param {Object | Array} object
|
||||
* @param {Path} path
|
||||
* @param {function} callback
|
||||
* @return {Object | Array} Returns a new, updated object or array
|
||||
*/
|
||||
export function updateIn (object, path, callback) {
|
||||
if (path.length === 0) {
|
||||
return callback(object)
|
||||
}
|
||||
|
||||
if (!isObjectOrArray(object)) {
|
||||
throw new Error('Path doesn\'t exist')
|
||||
}
|
||||
|
||||
const key = path[0]
|
||||
const updatedValue = updateIn(object[key], path.slice(1), callback)
|
||||
// TODO: create a function applyProp(...) which does the following if/else construct
|
||||
if (object[key] === updatedValue) {
|
||||
// return original object unchanged when the new value is identical to the old one
|
||||
return object
|
||||
}
|
||||
else {
|
||||
const updatedObject = shallowCloneWithSymbols(object)
|
||||
updatedObject[key] = updatedValue
|
||||
return updatedObject
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* helper function to delete a nested property in an object
|
||||
* without mutating the object itself.
|
||||
*
|
||||
* @param {Object | Array} object
|
||||
* @param {Path} path
|
||||
* @return {Object | Array} Returns a new, updated object or array
|
||||
*/
|
||||
export function deleteIn (object, path) {
|
||||
if (path.length === 0) {
|
||||
return object
|
||||
}
|
||||
|
||||
if (!isObjectOrArray(object)) {
|
||||
return object
|
||||
}
|
||||
|
||||
if (path.length === 1) {
|
||||
const key = path[0]
|
||||
if (!(key in object)) {
|
||||
// key doesn't exist. return object unchanged
|
||||
return object
|
||||
}
|
||||
else {
|
||||
const updatedObject = shallowCloneWithSymbols(object)
|
||||
|
||||
if (Array.isArray(updatedObject) && typeof key !== 'symbol') {
|
||||
updatedObject.splice(key, 1)
|
||||
}
|
||||
else {
|
||||
delete updatedObject[key]
|
||||
}
|
||||
|
||||
return updatedObject
|
||||
}
|
||||
}
|
||||
|
||||
const key = path[0]
|
||||
const updatedValue = deleteIn(object[key], path.slice(1))
|
||||
if (object[key] === updatedValue) {
|
||||
// object is unchanged
|
||||
return object
|
||||
}
|
||||
else {
|
||||
const updatedObject = shallowCloneWithSymbols(object)
|
||||
updatedObject[key] = updatedValue
|
||||
return updatedObject
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new item in an array at a specific index.
|
||||
* Example usage:
|
||||
*
|
||||
* insertAt({arr: [1,2,3]}, ['arr', '2'], 'inserted') // [1,2,'inserted',3]
|
||||
*
|
||||
* @param {Object | Array} object
|
||||
* @param {Path} path
|
||||
* @param {*} value
|
||||
* @return {Array}
|
||||
*/
|
||||
export function insertAt (object, path, value) {
|
||||
const parentPath = path.slice(0, path.length - 1)
|
||||
const index = path[path.length - 1]
|
||||
|
||||
return updateIn(object, parentPath, (items) => {
|
||||
if (!Array.isArray(items)) {
|
||||
throw new TypeError('Array expected at path ' + JSON.stringify(parentPath))
|
||||
}
|
||||
|
||||
const updatedItems = shallowCloneWithSymbols(items)
|
||||
updatedItems.splice(index, 0, value)
|
||||
|
||||
return updatedItems
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a JSON object, traverse over the whole object,
|
||||
* and allow replacing Objects/Arrays/values.
|
||||
* Does not iterate over symbols.
|
||||
* @param {JSON} json
|
||||
* @param {function (json: JSON, path: Path) : JSON} callback
|
||||
* @param {Path} [path]
|
||||
* @return {JSON}
|
||||
*/
|
||||
export function transform (json, callback, path = []) {
|
||||
const updated1 = callback(json, path)
|
||||
|
||||
if (Array.isArray(json)) { // array
|
||||
let updated2 = undefined
|
||||
|
||||
for (let i = 0; i < updated1.length; i++) {
|
||||
const before = updated1[i]
|
||||
// we stringify the index here, so the Path only contains strings and can be safely
|
||||
// stringified/parsed to JSONPointer without loosing information.
|
||||
// We do not want to rely on path keys being numeric/string.
|
||||
const after = transform(before, callback, path.concat(i + ''))
|
||||
if (after !== before) {
|
||||
if (!updated2) {
|
||||
updated2 = shallowCloneWithSymbols(updated1)
|
||||
}
|
||||
updated2[i] = after
|
||||
}
|
||||
}
|
||||
|
||||
return updated2 ? updated2 : updated1
|
||||
}
|
||||
else if (json && typeof json === 'object') { // object
|
||||
let updated2 = undefined
|
||||
|
||||
for (let key in updated1) {
|
||||
if (updated1.hasOwnProperty(key)) {
|
||||
const before = updated1[key]
|
||||
const after = transform(before, callback, path.concat(key))
|
||||
if (after !== before) {
|
||||
if (!updated2) {
|
||||
updated2 = shallowCloneWithSymbols(updated1)
|
||||
}
|
||||
updated2[key] = after
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updated2 ? updated2 : updated1
|
||||
}
|
||||
else { // number, string, boolean, null
|
||||
return updated1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a path exists in a JSON object
|
||||
* @param {JSON} json
|
||||
* @param {Path} path
|
||||
* @return {boolean} Returns true if the path exists, else returns false
|
||||
* @private
|
||||
*/
|
||||
export function existsIn (json, path) {
|
||||
if (json === undefined) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (path.length === 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (Array.isArray(json)) {
|
||||
// index of an array
|
||||
return existsIn(json[parseInt(path[0], 10)], path.slice(1))
|
||||
}
|
||||
else { // Object
|
||||
// object property. find the index of this property
|
||||
return existsIn(json[path[0]], path.slice(1))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,323 @@
|
|||
import { deleteIn, existsIn, getIn, insertAt, setIn, transform, updateIn } from './immutabilityHelpers.js'
|
||||
import { expect } from './testUtils.js' // FIXME: replace jest with mocha tests, or move to jest
|
||||
|
||||
const test = it // FIXME: replace jest with mocha tests, or move to jest
|
||||
|
||||
test('getIn', () => {
|
||||
const obj = {
|
||||
a: {
|
||||
b: {
|
||||
c: 2
|
||||
}
|
||||
},
|
||||
d: 3,
|
||||
e: [
|
||||
4,
|
||||
{
|
||||
f: 5,
|
||||
g: 6
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
expect(getIn(obj, ['a', 'b'])).toEqual({c: 2})
|
||||
expect(getIn(obj, ['e', '1', 'f'])).toEqual(5)
|
||||
expect(getIn(obj, ['e', '999', 'f'])).toBeUndefined()
|
||||
expect(getIn(obj, ['non', 'existing', 'path'])).toBeUndefined()
|
||||
})
|
||||
|
||||
test('setIn basic', () => {
|
||||
const obj = {
|
||||
a: {
|
||||
b: {
|
||||
c: 2
|
||||
}
|
||||
},
|
||||
d: 3
|
||||
}
|
||||
|
||||
const updated = setIn(obj, ['a', 'b', 'c'], 4)
|
||||
expect(updated).toEqual({
|
||||
a: {
|
||||
b: {
|
||||
c: 4
|
||||
}
|
||||
},
|
||||
d: 3
|
||||
})
|
||||
|
||||
// original should be unchanged
|
||||
expect(obj).toEqual({
|
||||
a: {
|
||||
b: {
|
||||
c: 2
|
||||
}
|
||||
},
|
||||
d: 3
|
||||
})
|
||||
|
||||
expect(obj).not.toBe(updated)
|
||||
})
|
||||
|
||||
test('setIn non existing path', () => {
|
||||
const obj = {}
|
||||
|
||||
expect(() => setIn(obj, ['a', 'b', 'c'], 4)).toThrow(/Path does not exist/)
|
||||
})
|
||||
|
||||
test('setIn replace value with object should throw an exception', () => {
|
||||
const obj = {
|
||||
a: 42,
|
||||
d: 3
|
||||
}
|
||||
|
||||
expect(() => setIn(obj, ['a', 'b', 'c'], 4)).toThrow(/Path does not exist/)
|
||||
})
|
||||
|
||||
test('setIn replace value inside nested array', () => {
|
||||
const obj = {
|
||||
a: [
|
||||
1,
|
||||
2,
|
||||
{
|
||||
b: 3,
|
||||
c: 4
|
||||
}
|
||||
],
|
||||
d: 5
|
||||
}
|
||||
|
||||
const updated = setIn(obj, ['a', '2', 'c'], 8)
|
||||
|
||||
expect(updated).toEqual({
|
||||
a: [
|
||||
1,
|
||||
2,
|
||||
{
|
||||
b: 3,
|
||||
c: 8
|
||||
}
|
||||
],
|
||||
d: 5
|
||||
})
|
||||
})
|
||||
|
||||
test('setIn identical value should return the original object', () => {
|
||||
const obj = {a:1, b:2}
|
||||
|
||||
const updated = setIn(obj, ['b'], 2)
|
||||
|
||||
expect(updated).toBe(obj) // strict equal
|
||||
})
|
||||
|
||||
test('setIn identical value should return the original object (2)', () => {
|
||||
const obj = {a:1, b: { c: 2}}
|
||||
|
||||
const updated = setIn(obj, ['b', 'c'], 2)
|
||||
|
||||
expect(updated).toBe(obj) // strict equal
|
||||
})
|
||||
|
||||
test('updateIn', () => {
|
||||
const obj = {
|
||||
a: {
|
||||
b: {
|
||||
c: 2
|
||||
}
|
||||
},
|
||||
d: 3
|
||||
}
|
||||
|
||||
const updated = updateIn(obj, ['a', 'b', 'c'], (value) => value + 100)
|
||||
expect(updated).toEqual({
|
||||
a: {
|
||||
b: {
|
||||
c: 102
|
||||
}
|
||||
},
|
||||
d: 3
|
||||
})
|
||||
|
||||
// original should be unchanged
|
||||
expect(obj).toEqual({
|
||||
a: {
|
||||
b: {
|
||||
c: 2
|
||||
}
|
||||
},
|
||||
d: 3
|
||||
})
|
||||
|
||||
expect(obj).not.toBe(updated)
|
||||
})
|
||||
|
||||
test('updateIn (2)', () => {
|
||||
const obj = {
|
||||
a: {
|
||||
b: {
|
||||
c: 2
|
||||
}
|
||||
},
|
||||
d: 3
|
||||
}
|
||||
|
||||
const updated = updateIn(obj, ['a', 'b' ], (obj) => [1,2,3])
|
||||
expect(updated).toEqual({
|
||||
a: {
|
||||
b: [1,2,3]
|
||||
},
|
||||
d: 3
|
||||
})
|
||||
})
|
||||
|
||||
test('updateIn (3)', () => {
|
||||
const obj = {
|
||||
a: {
|
||||
b: {
|
||||
c: 2
|
||||
}
|
||||
},
|
||||
d: 3
|
||||
}
|
||||
|
||||
const updated = updateIn(obj, ['a', 'e' ], (value) => 'foo-' + value)
|
||||
expect(updated).toEqual({
|
||||
a: {
|
||||
b: {
|
||||
c: 2
|
||||
},
|
||||
e: 'foo-undefined'
|
||||
},
|
||||
d: 3
|
||||
})
|
||||
})
|
||||
|
||||
test('updateIn return identical value should return the original object', () => {
|
||||
const obj = {
|
||||
a: 2,
|
||||
b: 3
|
||||
}
|
||||
|
||||
const updated = updateIn(obj, ['b' ], (value) => 3)
|
||||
expect(updated).toBe(obj)
|
||||
})
|
||||
|
||||
test('deleteIn', () => {
|
||||
const obj = {
|
||||
a: {
|
||||
b: {
|
||||
c: 2,
|
||||
d: 3
|
||||
}
|
||||
},
|
||||
e: 4
|
||||
}
|
||||
|
||||
const updated = deleteIn(obj, ['a', 'b', 'c'])
|
||||
expect(updated).toEqual({
|
||||
a: {
|
||||
b: {
|
||||
d: 3
|
||||
}
|
||||
},
|
||||
e: 4
|
||||
})
|
||||
|
||||
// original should be unchanged
|
||||
expect(obj).toEqual({
|
||||
a: {
|
||||
b: {
|
||||
c: 2,
|
||||
d: 3
|
||||
}
|
||||
},
|
||||
e: 4
|
||||
})
|
||||
|
||||
expect(obj).not.toBe(updated)
|
||||
})
|
||||
|
||||
test('deleteIn array', () => {
|
||||
const obj = {
|
||||
a: {
|
||||
b: [1, {c: 2, d: 3} , 4]
|
||||
},
|
||||
e: 5
|
||||
}
|
||||
|
||||
const updated = deleteIn(obj, ['a', 'b', '1', 'c'])
|
||||
expect(updated).toEqual({
|
||||
a: {
|
||||
b: [1, {d: 3} , 4]
|
||||
},
|
||||
e: 5
|
||||
})
|
||||
|
||||
// original should be unchanged
|
||||
expect(obj).toEqual({
|
||||
a: {
|
||||
b: [1, {c: 2, d: 3} , 4]
|
||||
},
|
||||
e: 5
|
||||
})
|
||||
|
||||
expect(obj).not.toBe(updated)
|
||||
})
|
||||
|
||||
test('deleteIn non existing path', () => {
|
||||
const obj = { a: {}}
|
||||
|
||||
const updated = deleteIn(obj, ['a', 'b'])
|
||||
expect(updated).toBe(obj)
|
||||
})
|
||||
|
||||
test('insertAt', () => {
|
||||
const obj = { a: [1,2,3]}
|
||||
|
||||
const updated = insertAt(obj, ['a', '2'], 8)
|
||||
expect(updated).toEqual({a: [1,2,8,3]})
|
||||
})
|
||||
|
||||
test('transform (no change)', () => {
|
||||
const json = {a: [1,2,3], b: {c: 4}}
|
||||
const updated = transform(json, (value, path) => value)
|
||||
expect(updated).toBe(json)
|
||||
})
|
||||
|
||||
test('transform (change based on value)', () => {
|
||||
const json = {a: [1,2,3], b: {c: 4}}
|
||||
|
||||
const updated = transform(json,
|
||||
(value, path) => value === 2 ? 20 : value)
|
||||
const expected = {a: [1,20,3], b: {c: 4}}
|
||||
|
||||
expect(updated).toEqual(expected)
|
||||
expect(updated.b).toBe(json.b) // should not have replaced b
|
||||
})
|
||||
|
||||
test('transform (change based on path)', () => {
|
||||
const json = {a: [1,2,3], b: {c: 4}}
|
||||
|
||||
const updated = transform(json,
|
||||
(value, path) => path.join('.') === 'a.1' ? 20 : value)
|
||||
const expected = {a: [1,20,3], b: {c: 4}}
|
||||
|
||||
expect(updated).toEqual(expected)
|
||||
expect(updated.b).toBe(json.b) // should not have replaced b
|
||||
})
|
||||
|
||||
test('existsIn', () => {
|
||||
const json = {
|
||||
"obj": {
|
||||
"arr": [1,2, {"first":3,"last":4}]
|
||||
},
|
||||
"str": "hello world",
|
||||
"nill": null,
|
||||
"bool": false
|
||||
}
|
||||
|
||||
expect(existsIn(json, ['obj', 'arr', 2, 'first'])).toEqual(true)
|
||||
expect(existsIn(json, ['obj', 'foo'])).toEqual(false)
|
||||
expect(existsIn(json, ['obj', 'foo', 'bar'])).toEqual(false)
|
||||
expect(existsIn(json, [])).toEqual(true)
|
||||
})
|
|
@ -0,0 +1,314 @@
|
|||
import {
|
||||
deleteIn,
|
||||
existsIn,
|
||||
getIn,
|
||||
insertAt,
|
||||
setIn
|
||||
} from './immutabilityHelpers.js'
|
||||
import { compileJSONPointer, parseJSONPointer } from './jsonPointer.js'
|
||||
import initial from 'lodash/initial.js'
|
||||
import isEqual from 'lodash/isEqual.js'
|
||||
|
||||
const DEFAULT_OPTIONS = {
|
||||
fromJSON: (json, previousObject) => json,
|
||||
toJSON: (object) => object,
|
||||
clone: (object) => object
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a patch to a JSON object
|
||||
* The original JSON object will not be changed,
|
||||
* instead, the patch is applied in an immutable way
|
||||
* @param {JSON} json
|
||||
* @param {JSONPatchDocument} operations Array with JSON patch actions
|
||||
* @param {JSONPatchOptions} [options]
|
||||
* @return {{json: JSON, revert: JSONPatchDocument, error: Error | null}}
|
||||
*/
|
||||
export function immutableJSONPatch (json, operations, options = DEFAULT_OPTIONS) {
|
||||
let updatedJson = json
|
||||
let revert = []
|
||||
|
||||
for (let i = 0; i < operations.length; i++) {
|
||||
const operation = operations[i]
|
||||
const path = operation.path ? parseJSONPointer(operation.path) : null
|
||||
const from = operation.from ? parseJSONPointer(operation.from) : null
|
||||
|
||||
switch (operation.op) {
|
||||
case 'add': {
|
||||
const result = add(updatedJson, path, operation.value, options)
|
||||
updatedJson = result.json
|
||||
revert = result.revert.concat(revert)
|
||||
break
|
||||
}
|
||||
|
||||
case 'remove': {
|
||||
const result = remove(updatedJson, path, options)
|
||||
updatedJson = result.json
|
||||
revert = result.revert.concat(revert)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'replace': {
|
||||
const result = replace(updatedJson, path, operation.value, options)
|
||||
updatedJson = result.json
|
||||
revert = result.revert.concat(revert)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'copy': {
|
||||
if (!operation.from) {
|
||||
return {
|
||||
json: updatedJson,
|
||||
revert: [],
|
||||
error: new Error('Property "from" expected in copy action ' + JSON.stringify(operation))
|
||||
}
|
||||
}
|
||||
|
||||
const result = copy(updatedJson, path, from, options)
|
||||
updatedJson = result.json
|
||||
revert = result.revert.concat(revert)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'move': {
|
||||
if (!operation.from) {
|
||||
return {
|
||||
json: updatedJson,
|
||||
revert: [],
|
||||
error: new Error('Property "from" expected in move action ' + JSON.stringify(operation))
|
||||
}
|
||||
}
|
||||
|
||||
const result = move(updatedJson, path, from, options)
|
||||
updatedJson = result.json
|
||||
revert = result.revert.concat(revert)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'test': {
|
||||
// when a test fails, cancel the whole patch and return the error
|
||||
const error = test(updatedJson, path, operation.value, options)
|
||||
if (error) {
|
||||
return { json, revert: [], error}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
// unknown patch operation. Cancel the whole patch and return an error
|
||||
return {
|
||||
json,
|
||||
revert: [],
|
||||
error: new Error('Unknown JSONPatch op ' + JSON.stringify(operation.op))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
json: updatedJson,
|
||||
revert,
|
||||
error: null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace an existing item
|
||||
* @param {JSON} json
|
||||
* @param {Path} path
|
||||
* @param {JSON} value
|
||||
* @param {JSONPatchOptions} [options]
|
||||
* @return {{json: JSON, revert: JSONPatchDocument}}
|
||||
*/
|
||||
export function replace (json, path, value, options) {
|
||||
const oldValue = getIn(json, path)
|
||||
const newValue = options.fromJSON(value, oldValue)
|
||||
|
||||
return {
|
||||
json: setIn(json, path, newValue),
|
||||
revert: [{
|
||||
op: 'replace',
|
||||
path: compileJSONPointer(path),
|
||||
value: oldValue
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item or property
|
||||
* @param {JSON} json
|
||||
* @param {Path} path
|
||||
* @param {JSONPatchOptions} [options]
|
||||
* @return {{json: JSON, revert: JSONPatchDocument}}
|
||||
*/
|
||||
export function remove (json, path, options) {
|
||||
const oldValue = getIn(json, path)
|
||||
|
||||
return {
|
||||
json: deleteIn(json, path),
|
||||
revert: [{
|
||||
op: 'add',
|
||||
path: compileJSONPointer(path),
|
||||
value: options.toJSON(oldValue)
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {JSON} json
|
||||
* @param {Path} path
|
||||
* @param {JSON} value
|
||||
* @param {JSONPatchOptions} [options]
|
||||
* @return {{json: JSON, revert: JSONPatchDocument}}
|
||||
* @private
|
||||
*/
|
||||
export function add (json, path, value, options) {
|
||||
const resolvedPath = resolvePathIndex(json, path)
|
||||
const parent = getIn(json, initial(path))
|
||||
const parentIsArray = Array.isArray(parent)
|
||||
const oldValue = parentIsArray
|
||||
? undefined
|
||||
: getIn(json, resolvedPath)
|
||||
const newValue = options.fromJSON(value, oldValue)
|
||||
|
||||
const updatedJson = parentIsArray
|
||||
? insertAt(json, resolvedPath, newValue)
|
||||
: setIn(json, resolvedPath, newValue)
|
||||
|
||||
if (!parentIsArray && existsIn(json, resolvedPath)) {
|
||||
return {
|
||||
json: updatedJson,
|
||||
revert: [{
|
||||
op: 'replace',
|
||||
path: compileJSONPointer(resolvedPath),
|
||||
value: options.toJSON(oldValue)
|
||||
}]
|
||||
}
|
||||
}
|
||||
else {
|
||||
return {
|
||||
json: updatedJson,
|
||||
revert: [{
|
||||
op: 'remove',
|
||||
path: compileJSONPointer(resolvedPath)
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a value
|
||||
* @param {JSON} json
|
||||
* @param {Path} path
|
||||
* @param {Path} from
|
||||
* @param {JSONPatchOptions} [options]
|
||||
* @return {{json: JSON, revert: JSONPatchDocument}}
|
||||
* @private
|
||||
*/
|
||||
export function copy (json, path, from, options) {
|
||||
const value = options.clone
|
||||
? options.clone(getIn(json, from))
|
||||
: options.fromJSON(options.toJSON(getIn(json, from)), undefined)
|
||||
|
||||
return add(json, path, value, {
|
||||
fromJSON: DEFAULT_OPTIONS.fromJSON,
|
||||
toJSON: options.toJSON,
|
||||
clone: options.clone
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a value
|
||||
* @param {JSON} json
|
||||
* @param {Path} path
|
||||
* @param {Path} from
|
||||
* @param {JSONPatchOptions} [options]
|
||||
* @return {{json: JSON, revert: JSONPatchDocument}}
|
||||
* @private
|
||||
*/
|
||||
export function move (json, path, from, options) {
|
||||
const resolvedPath = resolvePathIndex(json, path)
|
||||
const parent = getIn(json, initial(path))
|
||||
const parentIsArray = Array.isArray(parent)
|
||||
const oldValue = getIn(json, path)
|
||||
const value = getIn(json, from)
|
||||
|
||||
const removedJson = remove(json, from, options).json
|
||||
const updatedJson = parentIsArray
|
||||
? insertAt(removedJson, resolvedPath, value)
|
||||
: setIn(removedJson, resolvedPath, value)
|
||||
|
||||
if (oldValue !== undefined && !parentIsArray) {
|
||||
// replaces an existing value in an object
|
||||
return {
|
||||
json: updatedJson,
|
||||
revert: [
|
||||
{
|
||||
op: 'move',
|
||||
from: compileJSONPointer(resolvedPath),
|
||||
path: compileJSONPointer(from)
|
||||
},
|
||||
{
|
||||
op: 'add',
|
||||
path: compileJSONPointer(resolvedPath),
|
||||
value: options.toJSON(oldValue)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
else {
|
||||
return {
|
||||
json: updatedJson,
|
||||
revert: [
|
||||
{
|
||||
op: 'move',
|
||||
from: compileJSONPointer(resolvedPath),
|
||||
path: compileJSONPointer(from)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the data contains the provided value at the specified path.
|
||||
* Throws an error when the test fails.
|
||||
* @param {JSON} json
|
||||
* @param {Path} path
|
||||
* @param {JSON} value
|
||||
* @param {JSONPatchOptions} [options]
|
||||
* @return {null | Error} Returns an error when the tests, returns null otherwise
|
||||
*/
|
||||
export function test (json, path, value, options) {
|
||||
if (value === undefined) {
|
||||
return new Error('Test failed, no value provided')
|
||||
}
|
||||
|
||||
if (!existsIn(json, path)) {
|
||||
return new Error('Test failed, path not found')
|
||||
}
|
||||
|
||||
const actualValue = options.toJSON(getIn(json, path))
|
||||
if (!isEqual(actualValue, value)) {
|
||||
return new Error('Test failed, value differs')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the path of an index like '''
|
||||
* @param {JSON} json
|
||||
* @param {Path} path
|
||||
* @returns {Path} Returns the resolved path
|
||||
*/
|
||||
export function resolvePathIndex (json, path) {
|
||||
const parent = getIn(json, initial(path))
|
||||
|
||||
return (path[path.length - 1] === '-')
|
||||
? path.slice(0, path.length - 1).concat(parent.length)
|
||||
: path
|
||||
}
|
|
@ -0,0 +1,373 @@
|
|||
import { immutableJSONPatch } from './immutableJSONPatch.js'
|
||||
import { expect } from './testUtils.js' // FIXME: replace jest with mocha tests, or move to jest
|
||||
|
||||
const test = it // TODO: replace jest with mocha tests, or move to jest
|
||||
|
||||
test('test toBe', () => {
|
||||
const a = { x: 2 }
|
||||
const b = { x: 2 }
|
||||
|
||||
// just to be sure toBe does what I think it does...
|
||||
expect(a).toBe(a)
|
||||
expect(b).not.toBe(a)
|
||||
expect(b).toEqual(a)
|
||||
})
|
||||
|
||||
test('jsonpatch add', () => {
|
||||
const json = {
|
||||
arr: [1,2,3],
|
||||
obj: {a : 2}
|
||||
}
|
||||
|
||||
const patch = [
|
||||
{op: 'add', path: '/obj/b', value: {foo: 'bar'}}
|
||||
]
|
||||
|
||||
const result = immutableJSONPatch(json, patch)
|
||||
|
||||
expect(result.json).toEqual({
|
||||
arr: [1,2,3],
|
||||
obj: {a : 2, b: {foo: 'bar'}}
|
||||
})
|
||||
expect(result.revert).toEqual([
|
||||
{op: 'remove', path: '/obj/b'}
|
||||
])
|
||||
expect(result.json.arr).toBe(json.arr)
|
||||
})
|
||||
|
||||
test('jsonpatch add: insert in matrix', () => {
|
||||
const json = {
|
||||
arr: [1,2,3],
|
||||
obj: {a : 2}
|
||||
}
|
||||
|
||||
const patch = [
|
||||
{op: 'add', path: '/arr/1', value: 4}
|
||||
]
|
||||
|
||||
const result = immutableJSONPatch(json, patch)
|
||||
|
||||
expect(result.json).toEqual({
|
||||
arr: [1,4,2,3],
|
||||
obj: {a : 2}
|
||||
})
|
||||
expect(result.revert).toEqual([
|
||||
{op: 'remove', path: '/arr/1'}
|
||||
])
|
||||
expect(result.json.obj).toBe(json.obj)
|
||||
})
|
||||
|
||||
test('jsonpatch add: append to matrix', () => {
|
||||
const json = {
|
||||
arr: [1,2,3],
|
||||
obj: {a : 2}
|
||||
}
|
||||
|
||||
const patch = [
|
||||
{op: 'add', path: '/arr/-', value: 4}
|
||||
]
|
||||
|
||||
const result = immutableJSONPatch(json, patch)
|
||||
|
||||
expect(result.json).toEqual({
|
||||
arr: [1,2,3,4],
|
||||
obj: {a : 2}
|
||||
})
|
||||
expect(result.revert).toEqual([
|
||||
{op: 'remove', path: '/arr/3'}
|
||||
])
|
||||
expect(result.json.obj).toBe(json.obj)
|
||||
})
|
||||
|
||||
test('jsonpatch remove', () => {
|
||||
const json = {
|
||||
arr: [1,2,3],
|
||||
obj: {a : 4},
|
||||
unchanged: {}
|
||||
}
|
||||
|
||||
const patch = [
|
||||
{op: 'remove', path: '/obj/a'},
|
||||
{op: 'remove', path: '/arr/1'},
|
||||
]
|
||||
|
||||
const result = immutableJSONPatch(json, patch)
|
||||
|
||||
expect(result.json).toEqual({
|
||||
arr: [1,3],
|
||||
obj: {},
|
||||
unchanged: {}
|
||||
})
|
||||
expect(result.revert).toEqual([
|
||||
{op: 'add', path: '/arr/1', value: 2},
|
||||
{op: 'add', path: '/obj/a', value: 4}
|
||||
])
|
||||
|
||||
// test revert
|
||||
const result2 = immutableJSONPatch(result.json, result.revert)
|
||||
|
||||
expect(result2.json).toEqual(json)
|
||||
expect(result2.revert).toEqual(patch)
|
||||
expect(result.json.unchanged).toBe(json.unchanged)
|
||||
})
|
||||
|
||||
test('jsonpatch replace', () => {
|
||||
const json = {
|
||||
arr: [1,2,3],
|
||||
obj: {a : 4},
|
||||
unchanged: {}
|
||||
}
|
||||
|
||||
const patch = [
|
||||
{op: 'replace', path: '/obj/a', value: 400},
|
||||
{op: 'replace', path: '/arr/1', value: 200},
|
||||
]
|
||||
|
||||
const result = immutableJSONPatch(json, patch)
|
||||
|
||||
expect(result.json).toEqual({
|
||||
arr: [1,200,3],
|
||||
obj: {a: 400},
|
||||
unchanged: {}
|
||||
})
|
||||
expect(result.revert).toEqual([
|
||||
{op: 'replace', path: '/arr/1', value: 2},
|
||||
{op: 'replace', path: '/obj/a', value: 4}
|
||||
])
|
||||
|
||||
// test revert
|
||||
const result2 = immutableJSONPatch(result.json, result.revert)
|
||||
|
||||
expect(result2.json).toEqual(json)
|
||||
expect(result2.revert).toEqual([
|
||||
{op: 'replace', path: '/obj/a', value: 400},
|
||||
{op: 'replace', path: '/arr/1', value: 200}
|
||||
])
|
||||
expect(result.json.unchanged).toBe(json.unchanged)
|
||||
})
|
||||
|
||||
test('jsonpatch copy', () => {
|
||||
const json = {
|
||||
arr: [1,2,3],
|
||||
obj: {a : 4}
|
||||
}
|
||||
|
||||
const patch = [
|
||||
{op: 'copy', from: '/obj', path: '/arr/2'},
|
||||
]
|
||||
|
||||
const result = immutableJSONPatch(json, patch)
|
||||
|
||||
expect(result.json).toEqual({
|
||||
arr: [1, 2, {a:4}, 3],
|
||||
obj: {a: 4}
|
||||
})
|
||||
expect(result.revert).toEqual([
|
||||
{op: 'remove', path: '/arr/2'}
|
||||
])
|
||||
|
||||
// test revert
|
||||
const result2 = immutableJSONPatch(result.json, result.revert)
|
||||
|
||||
expect(result2.json).toEqual(json)
|
||||
expect(result2.revert).toEqual([
|
||||
{op: 'add', path: '/arr/2', value: {a: 4}}
|
||||
])
|
||||
expect(result.json.obj).toBe(json.obj)
|
||||
expect(result.json.arr[2]).toBe(json.obj)
|
||||
})
|
||||
|
||||
test('jsonpatch move', () => {
|
||||
const json = {
|
||||
arr: [1,2,3],
|
||||
obj: {a : 4},
|
||||
unchanged: {}
|
||||
}
|
||||
|
||||
const patch = [
|
||||
{op: 'move', from: '/obj', path: '/arr/2'},
|
||||
]
|
||||
|
||||
const result = immutableJSONPatch(json, patch)
|
||||
|
||||
expect(result.error).toEqual(null)
|
||||
expect(result.json).toEqual({
|
||||
arr: [1, 2, {a:4}, 3],
|
||||
unchanged: {}
|
||||
})
|
||||
expect(result.revert).toEqual([
|
||||
{op: 'move', from: '/arr/2', path: '/obj'}
|
||||
])
|
||||
|
||||
// test revert
|
||||
const result2 = immutableJSONPatch(result.json, result.revert)
|
||||
|
||||
expect(result2.json).toEqual(json)
|
||||
expect(result2.revert).toEqual(patch)
|
||||
expect(result.json.arr[2]).toBe(json.obj)
|
||||
expect(result.json.unchanged).toBe(json.unchanged)
|
||||
})
|
||||
|
||||
test('jsonpatch move and replace', () => {
|
||||
const json = { a: 2, b: 3 }
|
||||
|
||||
const patch = [
|
||||
{op: 'move', from: '/a', path: '/b'},
|
||||
]
|
||||
|
||||
const result = immutableJSONPatch(json, patch)
|
||||
|
||||
expect(result.json).toEqual({ b : 2 })
|
||||
expect(result.revert).toEqual([
|
||||
{op:'move', from: '/b', path: '/a'},
|
||||
{op:'add', path:'/b', value: 3}
|
||||
])
|
||||
|
||||
// test revert
|
||||
const result2 = immutableJSONPatch(result.json, result.revert)
|
||||
|
||||
expect(result2.json).toEqual(json)
|
||||
expect(result2.revert).toEqual([
|
||||
{op: 'remove', path: '/b'},
|
||||
{op: 'move', from: '/a', path: '/b'}
|
||||
])
|
||||
})
|
||||
|
||||
test('jsonpatch move and replace (nested)', () => {
|
||||
const json = {
|
||||
arr: [1,2,3],
|
||||
obj: {a : 4},
|
||||
unchanged: {}
|
||||
}
|
||||
|
||||
const patch = [
|
||||
{op: 'move', from: '/obj', path: '/arr'},
|
||||
]
|
||||
|
||||
const result = immutableJSONPatch(json, patch)
|
||||
|
||||
expect(result.json).toEqual({
|
||||
arr: {a:4},
|
||||
unchanged: {}
|
||||
})
|
||||
expect(result.revert).toEqual([
|
||||
{op:'move', from: '/arr', path: '/obj'},
|
||||
{op:'add', path:'/arr', value: [1,2,3]}
|
||||
])
|
||||
|
||||
// test revert
|
||||
const result2 = immutableJSONPatch(result.json, result.revert)
|
||||
|
||||
expect(result2.json).toEqual(json)
|
||||
expect(result2.revert).toEqual([
|
||||
{op: 'remove', path: '/arr'},
|
||||
{op: 'move', from: '/obj', path: '/arr'}
|
||||
])
|
||||
expect(result.json.unchanged).toBe(json.unchanged)
|
||||
expect(result2.json.unchanged).toBe(json.unchanged)
|
||||
})
|
||||
|
||||
test('jsonpatch test (ok)', () => {
|
||||
const json = {
|
||||
arr: [1,2,3],
|
||||
obj: {a : 4}
|
||||
}
|
||||
|
||||
const patch = [
|
||||
{op: 'test', path: '/arr', value: [1,2,3]},
|
||||
{op: 'add', path: '/added', value: 'ok'}
|
||||
]
|
||||
|
||||
const result = immutableJSONPatch(json, patch)
|
||||
|
||||
expect(result.json).toEqual({
|
||||
arr: [1,2,3],
|
||||
obj: {a : 4},
|
||||
added: 'ok'
|
||||
})
|
||||
expect(result.revert).toEqual([
|
||||
{op: 'remove', path: '/added'}
|
||||
])
|
||||
})
|
||||
|
||||
test('jsonpatch test (fail: path not found)', () => {
|
||||
const json = {
|
||||
arr: [1,2,3],
|
||||
obj: {a : 4}
|
||||
}
|
||||
|
||||
const patch = [
|
||||
{op: 'test', path: '/arr/5', value: [1,2,3]},
|
||||
{op: 'add', path: '/added', value: 'ok'}
|
||||
]
|
||||
|
||||
const result = immutableJSONPatch(json, patch)
|
||||
|
||||
// patch shouldn't be applied
|
||||
expect(result.json).toEqual({
|
||||
arr: [1,2,3],
|
||||
obj: {a : 4}
|
||||
})
|
||||
expect(result.revert).toEqual([])
|
||||
expect(result.error.toString()).toEqual('Error: Test failed, path not found')
|
||||
})
|
||||
|
||||
test('jsonpatch test (fail: value not equal)', () => {
|
||||
const json = {
|
||||
arr: [1,2,3],
|
||||
obj: {a : 4}
|
||||
}
|
||||
|
||||
const patch = [
|
||||
{op: 'test', path: '/obj', value: {a:4, b: 6}},
|
||||
{op: 'add', path: '/added', value: 'ok'}
|
||||
]
|
||||
|
||||
const result = immutableJSONPatch(json, patch)
|
||||
|
||||
// patch shouldn't be applied
|
||||
expect(result.json).toEqual({
|
||||
arr: [1,2,3],
|
||||
obj: {a : 4}
|
||||
})
|
||||
expect(result.revert).toEqual([])
|
||||
expect(result.error.toString()).toEqual('Error: Test failed, value differs')
|
||||
})
|
||||
|
||||
test('jsonpatch options', () => {
|
||||
const json = {
|
||||
arr: [1,2,3],
|
||||
obj: {a : 2}
|
||||
}
|
||||
|
||||
const patch = [
|
||||
{op: 'add', path: '/obj/a', value: 4 }
|
||||
]
|
||||
const result = immutableJSONPatch(json, patch, {
|
||||
fromJSON: function (value, previousObject) {
|
||||
return { value, previousObject }
|
||||
},
|
||||
toJSON: value => value
|
||||
})
|
||||
expect(result.json).toEqual({
|
||||
arr: [1,2,3],
|
||||
obj: {a : { value: 4, previousObject: 2 }}
|
||||
})
|
||||
|
||||
|
||||
const patch2 = [
|
||||
{op: 'add', path: '/obj/b', value: 4 }
|
||||
]
|
||||
const result2 = immutableJSONPatch(json, patch2, {
|
||||
fromJSON: function (value, previousObject) {
|
||||
return { value, previousObject }
|
||||
},
|
||||
toJSON: value => value
|
||||
})
|
||||
expect(result2.json).toEqual({
|
||||
arr: [1,2,3],
|
||||
obj: {a : 2, b: { value: 4, previousObject: undefined }}
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: test all operations with JSONPatchOptions (not just add)
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* Parse a JSON Pointer
|
||||
* WARNING: this is not a complete implementation
|
||||
* @param {string} pointer
|
||||
* @return {Path}
|
||||
*/
|
||||
export function parseJSONPointer (pointer) {
|
||||
const path = pointer.split('/')
|
||||
path.shift() // remove the first empty entrypa
|
||||
|
||||
return path.map(p => p.replace(/~1/g, '/').replace(/~0/g, '~'))
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a JSON Pointer
|
||||
* WARNING: this is not a complete implementation
|
||||
* @param {Path} path
|
||||
* @return {string}
|
||||
*/
|
||||
export function compileJSONPointer (path) {
|
||||
return path
|
||||
.map(p => '/' + String(p).replace(/~/g, '~0').replace(/\//g, '~1'))
|
||||
.join('')
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import {compileJSONPointer, parseJSONPointer} from './jsonPointer.js'
|
||||
import { expect } from './testUtils.js' // FIXME: replace jest with mocha tests, or move to jest
|
||||
|
||||
const test = it // TODO: replace jest with mocha tests, or move to jest
|
||||
|
||||
test('parseJSONPointer', () => {
|
||||
expect(parseJSONPointer('/obj/a')).toEqual(['obj', 'a'])
|
||||
expect(parseJSONPointer('/arr/-')).toEqual(['arr', '-'])
|
||||
expect(parseJSONPointer('/foo/~1~0 ~0~1')).toEqual(['foo', '/~ ~/'])
|
||||
expect(parseJSONPointer('/obj')).toEqual(['obj'])
|
||||
expect(parseJSONPointer('/')).toEqual([''])
|
||||
expect(parseJSONPointer('')).toEqual([])
|
||||
})
|
||||
|
||||
test('compileJSONPointer', () => {
|
||||
expect(compileJSONPointer(['foo', 'bar'])).toEqual('/foo/bar')
|
||||
expect(compileJSONPointer(['foo', '/~ ~/'])).toEqual('/foo/~1~0 ~0~1')
|
||||
expect(compileJSONPointer([''])).toEqual('/')
|
||||
expect(compileJSONPointer([])).toEqual('')
|
||||
})
|
|
@ -0,0 +1,29 @@
|
|||
import { deepStrictEqual, strictEqual, notStrictEqual, throws } from "assert"
|
||||
|
||||
// TODO: integrate jest or switch to mocha
|
||||
// sort of mimicking jest
|
||||
export function expect (actual) {
|
||||
return {
|
||||
toEqual (expected) {
|
||||
return deepStrictEqual(actual, expected)
|
||||
},
|
||||
|
||||
toBe (expected) {
|
||||
return strictEqual(actual, expected)
|
||||
},
|
||||
|
||||
toBeUndefined () {
|
||||
return strictEqual(actual, undefined)
|
||||
},
|
||||
|
||||
toThrow (error) {
|
||||
return throws(actual, error)
|
||||
},
|
||||
|
||||
not: {
|
||||
toBe (expected) {
|
||||
return notStrictEqual(actual, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
|
||||
// TODO: unit test typeUtils.js
|
||||
|
||||
/**
|
||||
* Test whether a value is an Object (and not an Array!)
|
||||
*
|
||||
* @param {*} value
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function isObject (value) {
|
||||
return typeof value === 'object' &&
|
||||
value !== null &&
|
||||
!Array.isArray(value) &&
|
||||
(!value._meta || typeof value._meta.value === 'undefined')
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a value is not an object or array, but null, number, string, or
|
||||
* boolean.
|
||||
* @param {*} value
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function isValue (value) {
|
||||
return (value === null ||
|
||||
typeof value === 'number' ||
|
||||
typeof value === 'string' ||
|
||||
typeof value === 'boolean')
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a value is an Object or an Array
|
||||
*
|
||||
* @param {*} value
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function isObjectOrArray (value) {
|
||||
return typeof value === 'object' && value !== null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JSON node type of a value: 'array', 'object', or 'value'
|
||||
* @param {*} json
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getJSONNodeType (json) {
|
||||
return Array.isArray(json)
|
||||
? 'array'
|
||||
: json && typeof json === 'object'
|
||||
? 'object'
|
||||
: 'value'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of a value
|
||||
* @param {*} value
|
||||
* @return {String} type
|
||||
*/
|
||||
export function valueType(value) {
|
||||
if (value === null) {
|
||||
return 'null'
|
||||
}
|
||||
if (value === undefined) {
|
||||
return 'undefined'
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
return 'number'
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return 'string'
|
||||
}
|
||||
if (typeof value === 'boolean') {
|
||||
return 'boolean'
|
||||
}
|
||||
if (value instanceof RegExp) {
|
||||
return 'regexp'
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return 'array'
|
||||
}
|
||||
|
||||
return 'object'
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a text contains a url (matches when a string starts
|
||||
* with 'http://*' or 'https://*' and has no whitespace characters)
|
||||
* @param {String} text
|
||||
*/
|
||||
const isUrlRegex = /^https?:\/\/\S+$/
|
||||
export function isUrl (text) {
|
||||
return (typeof text === 'string') && isUrlRegex.test(text)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert contents of a string to the correct JSON type. This can be a string,
|
||||
* a number, a boolean, etc
|
||||
* @param {String} str
|
||||
* @return {*} castedStr
|
||||
* @private
|
||||
*/
|
||||
export function stringConvert (str) {
|
||||
const num = Number(str) // will nicely fail with '123ab'
|
||||
const numFloat = parseFloat(str) // will nicely fail with ' '
|
||||
|
||||
if (str === '') {
|
||||
return ''
|
||||
}
|
||||
else if (str === 'null') {
|
||||
return null
|
||||
}
|
||||
else if (str === 'true') {
|
||||
return true
|
||||
}
|
||||
else if (str === 'false') {
|
||||
return false
|
||||
}
|
||||
else if (!isNaN(num) && !isNaN(numFloat)) {
|
||||
return num
|
||||
}
|
||||
else {
|
||||
return str
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import { getJSONNodeType } from './typeUtils.js'
|
||||
import { expect } from './testUtils.js' // FIXME: replace jest with mocha tests, or move to jest
|
||||
|
||||
const test = it // TODO: replace jest with mocha tests, or move to jest
|
||||
|
||||
test('test getJsonType', () => {
|
||||
expect(getJSONNodeType([])).toEqual('array')
|
||||
expect(getJSONNodeType({})).toEqual('object')
|
||||
expect(getJSONNodeType(null)).toEqual('value')
|
||||
expect(getJSONNodeType(2)).toEqual('value')
|
||||
expect(getJSONNodeType('hello')).toEqual('value')
|
||||
expect(getJSONNodeType('hello')).toEqual('value')
|
||||
})
|
Loading…
Reference in New Issue