{"_id":"@breautek/merge-change","_rev":"1-1fba3ae0299bb5f4982a0a07562028ca","name":"@breautek/merge-change","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@breautek/merge-change","version":"1.0.0","description":"Simple library for deep merge of objects and other types (also for patch and immutable updates). Declarative operations to specific merge, for example to remove properties. Customize merging between specific types. Calculating diffs.","main":"index.js","scripts":{"start":"node index.js","test":"node ./node_modules/jest/bin/jest.js --runInBand --verbose --forceExit"},"repository":{"type":"git","url":"git+https://github.com/VladimirShestakov/merge-change.git"},"keywords":["merge","object","patch","update","deep","mixin","change","extend","config","util","compose","make","create","build","clone","assemble","mutate","assign","refresh","improve","enhance"],"author":{"name":"Vladimir Shestakov"},"license":"MIT","bugs":{"url":"https://github.com/VladimirShestakov/merge-change.git"},"homepage":"https://github.com/VladimirShestakov/merge-change.git#readme","devDependencies":{"eslint":"^8.3.0","jest":"^27.0.6"},"gitHead":"0bd093f4f2476d6958b091d1e9351feebaf23979","_id":"@breautek/merge-change@1.0.0","_nodeVersion":"16.5.0","_npmVersion":"7.23.0","dist":{"integrity":"sha512-H0sjxDCpabl4BXxMcyrKWOD1Dq19GiUfLt9aUY/k0mlrEj/Wwt3zpp9+pQHF1lB/QzOFz6DxPwQ6BvcoevCwSA==","shasum":"390b119f3be11dc16e56cc565663c57ff1e90812","tarball":"https://registry.npmjs.org/@breautek/merge-change/-/merge-change-1.0.0.tgz","fileCount":24,"unpackedSize":75656,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnExbCRA9TVsSAnZWagAAp/cP/ROmIb4L333XFUa5jfsA\nj05+kz5puTMn+z1GKmH2hUeq2WKCCKLn3gmkCX8qQxiNKA/mPCwvevMKIQTV\ndg9StvXrlLpVu59sYLBPkNWeZtSh6DvkKKS6UyX7QskSIZDOj1gNQ4BuaK8+\nXsIjY1B8EPIqdHSCp3bkG2nE9Y2q4aB9EAaPy+WUojGO6XtWdnwfH0yRZiZm\nIJf0gci74j+Eg02H8nsRqB5IwUlWemfql8hZGR0k9zTzwcaDDI/BM+MycR2x\nu+yCCjJhDacejQUyqjmtuuZIT8gBZaBP8HXlx6QC5QWSqoh/pNqjO6brRWHM\nW0r6tNk3VNxxeEGSx4JChqzZutD8Me6pNJv7DrsqjuCppEou/+245t0j3Efy\n2Y/qnMifcEOAO1Yb1A2GfWyGpbBOogbn0EMirufNxfPybD0LtNJ+ntkbx1nX\nwkRERbtt64NV0p9VXe8D8gw/0OwsN7qXi+gH+pUUAUxNydNMZcTyjbBrxoVS\n6pKVwOJpVP8ZLJ/lnyaRoyWRNiKaJekac2gySKurWtt0MjByun0mtnoJg8Bz\ne1PsFEuKR0Ji3jBW811Ig8cgDy8IdZUp0Ufso9SRJ7uXhznjQkARsx3QKqT/\nV1g5aMceZORsrbw0rc8r+xPbxoNU3tKzvozqYdivSSdgogYtHyHnlByOsJzG\n79Xs\r\n=G5n6\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIGqKNKh3ARfdnJhxsC4q4T7IAESEyyZ6a4SIjKWms9mZAiB0bpC/zdguq0dEy13k9WwinEu5FzJq1KJlto2yIQ+DqQ=="}]},"_npmUser":{"name":"normanbreau","email":"norman@nbsolutions.ca"},"directories":{},"maintainers":[{"name":"normanbreau","email":"norman@nbsolutions.ca"},{"name":"tpnormanbreau","email":"norman.breau@totalpave.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/merge-change_1.0.0_1637633115215_0.2431538286510211"},"_hasShrinkwrap":false}},"time":{"created":"2021-11-23T02:05:15.159Z","1.0.0":"2021-11-23T02:05:15.598Z","modified":"2022-04-04T20:30:14.312Z"},"maintainers":[{"name":"normanbreau","email":"norman@nbsolutions.ca"},{"name":"tpnormanbreau","email":"norman.breau@totalpave.com"}],"description":"Simple library for deep merge of objects and other types (also for patch and immutable updates). Declarative operations to specific merge, for example to remove properties. Customize merging between specific types. Calculating diffs.","homepage":"https://github.com/VladimirShestakov/merge-change.git#readme","keywords":["merge","object","patch","update","deep","mixin","change","extend","config","util","compose","make","create","build","clone","assemble","mutate","assign","refresh","improve","enhance"],"repository":{"type":"git","url":"git+https://github.com/VladimirShestakov/merge-change.git"},"author":{"name":"Vladimir Shestakov"},"bugs":{"url":"https://github.com/VladimirShestakov/merge-change.git"},"license":"MIT","readme":"# merge-change\r\n\r\nSimple library for **deep merge** of objects and other types, also for **patches** and **immutable updates**.\r\nBy default, merge works for \"plain objects\".\r\nValues of other types are replaced, but you can **customize merging** between specific types.\r\nAlso, you can use **declarative operations** to specific merge like `unset`, `leave`, `push` and other.\r\nFor example to remove some properties of object, to replace \"plain objects\", to concat arrays.\r\nCalculating diffs between two values.\r\n\r\n## Install\r\n\r\nInstall with [npm](https://www.npmjs.com/):\r\n\r\n```sh\r\n$ npm install --save merge-change\r\n```\r\n\r\n## API\r\n\r\n### Merge\r\n\r\nMerge with **deep cloning** without changing the source objects. Great for creating or extending objects from the example (source).\r\n\r\n```js\r\nmc.merge(source, ...values);\r\n```\r\nExample\r\n```js\r\nconst mc = require('merge-change');\r\n\r\n// Create new object with adding \"a.three\" and deleting \"a.one\"\r\nlet first = {\r\n  a: {\r\n    one: true,\r\n    two: 2\r\n  }\r\n};\r\nlet second = {\r\n  a: {\r\n    three: 3,\r\n    $unset: ['one'] // $unset is a declarative operations\r\n  }\r\n};\r\n\r\nconst result = mc.merge(first, second);\r\n\r\nconsole.log(result);\r\n```\r\n```log\r\n{ a: { two: 2,  three: 3} }\r\n```\r\n\r\n### Patch\r\n\r\nMerge with **mutation** of the source objects. Nice for patching. New instances will not be created.\r\n\r\n```js\r\nmc.patch(source, ...patches);\r\n```\r\n\r\n```js\r\nlet first = {\r\n  a: {\r\n    one: true,\r\n    two: 2\r\n  }\r\n};\r\nlet second = {\r\n  a: {\r\n    three: 3,\r\n    $unset: ['one'] // $unset is a declarative operations\r\n  }\r\n};\r\n\r\nconst result = mc.patch(first, second); // => { a: { two: 2,  three: 3} }\r\n\r\n// result is a mutated first argument\r\nconsole.log(result === first); // => true\r\nconsole.log(result !== second); // => true\r\n```\r\n\r\n### Update\r\n\r\n**Immutable merge** - create new instances only if there are diffs (also in inner properties). Nice for state management.\r\n\r\n```js\r\nmc.update(source, ...changes);\r\n```\r\n\r\n```js\r\nlet first = {\r\n  a: {\r\n    one: true,\r\n    two: 2,\r\n    sub: {\r\n      value: 3\r\n    }\r\n  }\r\n};\r\nlet second = {\r\n  a: {\r\n    three: 3,\r\n    $unset: ['one'] // $unset is a declarative operations\r\n  }\r\n};\r\n\r\nconst result = mc.update(first, second); // => { a: { two: 2,  three: 3, sub: { value: 3 }} }\r\n\r\n// result is a new object\r\nconsole.log(result !== first); // => true\r\nconsole.log(result !== second); // => true\r\n\r\n// object \"a.sub\" is unchanged\r\nconsole.log(result.a.sub === first.a.sub); // => true\r\n```\r\n\r\n## Declarative operations\r\n\r\nWhen merging objects, you can perform delete and replace properties at the same time.\r\nUse declarative operations in second or next arguments. Supported in all merge methods.\r\nThe syntax is similar to mongodb.\r\n\r\n### `$set`\r\n\r\nTo set (or replace) property without deep merge.\r\n\r\n```js\r\nconst result = mc.merge(\r\n  {\r\n    a: {\r\n      one: 1, \r\n      two: 2\r\n    }\r\n  }, \r\n  {\r\n    $set: {\r\n      a: {\r\n        three: 3\r\n      },\r\n      'a.two': 20 // Fields keys can be path.\r\n    }\r\n  }\r\n);\r\nconsole.log(result);\r\n```\r\n\r\nResult\r\n```json\r\n{\r\n  \"a\": {\r\n    \"one\": 1, \r\n    \"two\": 20,\r\n    \"three\": 3\r\n  }\r\n}\r\n```\r\n\r\n### `$unset`\r\n\r\nTo unset properties by name (or path)\r\n\r\n ```js\r\n const result = mc.merge(\r\n   {\r\n     a: {\r\n       one: 1, \r\n       two: 2\r\n     }\r\n   }, \r\n   {\r\n     $unset: ['a.two']\r\n   }\r\n );\r\n console.log(result);\r\n ```\r\n\r\nResult\r\n ```json\r\n {\r\n   \"a\": {\r\n     \"one\": 1\r\n   }\r\n }\r\n ```\r\n\r\n#### To unset all fields used `*`\r\n\r\n ```js\r\n const result = mc.merge(\r\n   {\r\n     a: {\r\n       one: 1, \r\n       two: 2\r\n     }\r\n   }, \r\n   {\r\n     $unset: ['a.*']\r\n   }\r\n );\r\n console.log(result);\r\n ```\r\n\r\nResult\r\n ```json\r\n {\r\n   \"a\": {}\r\n }\r\n ```\r\n\r\n### `$leave`\r\n\r\nTo leave properties by name (or path). All other properties will be removed.\r\n\r\n ```js\r\n const result = mc(\r\n   {\r\n     a: {\r\n       one: 1, \r\n       two: 2,\r\n       tree: 3\r\n     }\r\n   }, \r\n   {\r\n     a: {\r\n       $leave: ['two']\r\n     }\r\n   }\r\n );\r\n console.log(result);\r\n ```\r\n\r\nResult\r\n```json\r\n {\r\n   \"a\": {\r\n     \"two\": 2\r\n   }\r\n }\r\n ```\r\n\r\n### `$push`\r\n\r\nTo push one value to the array property. The source property must be an array.\r\n\r\n ```js\r\n const result = mc(\r\n   // First object\r\n   {\r\n     prop1: ['a', 'b'],\r\n     prop2: ['a', 'b'],\r\n   }, \r\n   // Merge    \r\n   {\r\n     $push: {\r\n       prop1: ['c', 'd'],\r\n       prop2: {x: 'c'}\r\n     },\r\n   }\r\n );\r\n console.log(result);\r\n ```\r\n\r\nResult\r\n ```json\r\n {\r\n   \"prop1\": [\"a\", \"b\", [\"c\", \"d\"]],\r\n   \"prop2\": [\"a\", \"b\", {\"x\": \"c\"}]\r\n }\r\n ```\r\n\r\n### `$concat`\r\n\r\nTo union arrays.\r\n\r\nThe source property must be an array. The property in secondary arguments may not be an array.\r\n\r\n ```js\r\n const result = mc(\r\n   // First object\r\n   {\r\n     prop1: ['a', 'b'],\r\n     prop2: ['a', 'b'],\r\n   }, \r\n   // Merge    \r\n   {\r\n     $concat: {\r\n       prop1: ['c', 'd'],\r\n       prop2: {x: 'c'}\r\n     },\r\n   }\r\n );\r\n console.log(result);\r\n ```\r\n\r\nResult\r\n ```json\r\n {\r\n   \"prop1\": [\"a\", \"b\", \"c\", \"d\"],\r\n   \"prop2\": [\"a\", \"b\", {\"x\": \"c\"}]\r\n }\r\n ```\r\n\r\n\r\n## Customize merge\r\n\r\nYou can declare function for merge custom types (or override default logic). Returns previous merge method.\r\n\r\n`mc.addMerge(type1, type2, callback)`\r\n\r\n- `type1, type2` - constructor name of the first and second values: `Number, String, Boolean, Object, Array, Date, RegExp, Function, Undefined, Null, Symbol, Set, Map` and other system and custom constructor names\r\n- `callback` - merge function with argument: (first, second, kind)\r\n    - `first` - first value for merge\r\n    - `second` - second value for merge\r\n    - `kind` - name of merging method, such as \"merge\", \"patch\", \"update\". \r\n\r\nFor example, if you always need to union arrays, you can declare method to merge array with array. \r\n\r\n```js\r\nconst previous = mc.addMerge('Array', 'Array', function(first, second, kind){\r\n  // merge - creaete new array with deep clone\r\n  if (kind === 'merge'){\r\n    return first.concat(second).map(item => mc.merge(undefined, item));\r\n  }\r\n  // patch - mutate first array\r\n  if (kind === 'patch'){\r\n    first.splice(first.length, 0, ...second);\r\n    return first;\r\n  }\r\n  // update - return first array if second is empty, or create new without clone\r\n  if (second.length === 0){\r\n    return first;\r\n  } else {\r\n    return first.concat(second);\r\n  }\r\n});\r\n\r\n// reset custom method\r\nmc.addMerge('Array', 'Array', previous);\r\n```\r\n\r\n## Customize declarative operation\r\n\r\nYou can declare function for declarative operation (or override default logic). Returns previous operation method.\r\n\r\n`mc.addOperation(name, callback)`\r\n\r\n- `name` - operation name, for example \"$concat\"\r\n- `callback` - operation function with argument: (source, params). Return new value or source.\r\n    - `source` - the value in which the operation is defined (`source: {$concat: params}`)\r\n    - `params` - value of operator (`$concat: params`)\r\n\r\nFor example, if sometimes need to union arrays, you can declare declarative operation $concat (it exists in the library).\r\n\r\n```js\r\nconst previous = mc.addOperation('$concat', function(source, params){\r\n  const paths = Object.keys(params);\r\n  for (const path of paths) {\r\n    let value = params[path];\r\n    let array = utils.get(source, path, []);\r\n    if (Array.isArray(array)) {\r\n      array = array.concat(value);\r\n      utils.set(source, path, array);\r\n    } else {\r\n      throw new Error('Cannot concat on not array');\r\n    }\r\n  }\r\n  return paths.length > 0;\r\n});\r\n\r\n// reset custom operation\r\nmc.addOperation('$concat', previous);\r\n```\r\n\r\n## Utils\r\n\r\nUseful functions - utilities\r\n\r\n```js\r\nconst utils = require('merge-change').utils;\r\n```\r\n\r\n### `utils.diff(source, compare, {ignore = [], separator = '.'})`\r\n\r\nTo calculate the difference between `source` and `compare` value. \r\nThe return value is an object with `$set` and `$unset` operators. Return value can be used in merge functions.\r\nThe `ignore` parameter - is a list of properties that are not included in the comparison.\r\n\r\n```js\r\nconst first = {\r\n  name: 'value',\r\n  profile: {\r\n    surname: 'Surname',\r\n    birthday: new Date(),\r\n    avatar: {\r\n      url: 'pic.png'\r\n    }\r\n  },\r\n  access: [100, 350, 200],\r\n  secret: 'x'\r\n}\r\n\r\nconst second = {\r\n  login: 'value',\r\n  profile: {\r\n    surname: 'Surname2',\r\n    avatar: {\r\n      url: 'new/pic.png'\r\n    }\r\n  },\r\n  access: [700]\r\n}\r\n\r\nconst diff = utils.diff(first, second, {ignore: ['secret'], separator: '/'});\r\n```\r\nResult (diff)\r\n```\r\n{\r\n  $set: {\r\n    'login': 'value',\r\n    'profile.surname': 'Surname2',\r\n    'profile.avatar.url': 'new/pic.png',\r\n    'access': [ 700 ]\r\n  },\r\n  $unset: [ \r\n    'profile.birthday', \r\n    'name'\r\n  ]\r\n}\r\n```\r\n\r\n### `utils.type(value)`\r\n\r\nGet real type of any value. The return value is a string - the name of the constructor.\r\n\r\n```js\r\nutils.type(null); // => 'Null'\r\nutils.type(true); // => 'Boolean'\r\nutils.type(new ObjectId()); // => 'ObjectID'\r\n```\r\n\r\n### `utils.instanceof(value, className)`\r\n\r\nChecking instance of class. `className` is string (not constructor). The return value is a boolean.\r\n\r\n```js\r\nutils.instanceof(100, 'Number'); // => true\r\nutils.instanceof(new MyClass(), 'MyClass'); // => true\r\nutils.instanceof(new MyClass(), 'Object'); // => true\r\n```\r\n\r\n### `utils.plain(value)`\r\n\r\nConverting deep value to plain types if value has plain representation. For example, all dates are converted to a string, but RegEx not.\r\nTo customize conversion, you can define the `[methods.toPlain]()` method in your object.\r\nNice for unit tests.\r\n\r\n> The method is similar to converting to JSON, only objects (arrays, functions...) are not converted to string representation.\r\n\r\n```js\r\nconst plain = utils.plain({\r\n  date: new Date('2021-01-07T19:10:21.759Z'),\r\n  prop: {\r\n    _id: new ObjectId('6010a8c75b9b393070e42e68')\r\n  }\r\n});\r\n```\r\nResult (plain)\r\n```\r\n{\r\n  date: '2021-01-07T19:10:21.759Z',\r\n  prop: { \r\n    _id: '6010a8c75b9b393070e42e68' \r\n  }\r\n}\r\n```\r\n\r\n### `utils.flat(value, path = '', separator = '.', clearUndefined = false)`\r\n\r\nConverting a nested structure to a flat object.\r\nProperty names become path with `separator`.\r\nTo customize conversion, you can define the `[methods.toFlat]()` method in your object.\r\n\r\n```js\r\nconst value = {\r\n  a: {\r\n    b: {\r\n      c: 100\r\n    }\r\n  }\r\n};\r\nconst flat = utils.flat(value, 'parent', '.');\r\n```\r\nResult (flat)\r\n```\r\n{\r\n  'parent.a.b.c': 100\r\n}\r\n```\r\n\r\n## License\r\n\r\nCopyright © 2020, [VladimirShestakov](https://github.com/VladimirShestakov).\r\nReleased under the [MIT License](LICENSE).\r\n","readmeFilename":"README.md"}