[
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# TypeScript v1 declaration files\ntypings/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n\n# next.js build output\n.next\n\n# Editor files\n.vscode\n\n# Testing area for myself\nsandbox\n\ncoverage.lcov"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"singleQuote\": true,\n  \"trailingComma\": \"es5\"\n}\n"
  },
  {
    "path": ".travis.yml",
    "content": "after_success: npm run coverage\nlanguage: node_js\nnode_js:\n  - \"8\"\ncache:\n  directories:\n    - \"node_modules\""
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Frank Wilkerson\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# clean-set\n\n[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)\n[![Build Status](https://travis-ci.org/fwilkerson/clean-set.svg?branch=master)](https://travis-ci.org/fwilkerson/clean-set)\n[![codecov](https://codecov.io/gh/fwilkerson/clean-set/branch/master/graph/badge.svg)](https://codecov.io/gh/fwilkerson/clean-set)\n\n> Quickly update a value in a deeply nested object and clone each node touched for simple change tracking `===`.\n\nCheck out [dset](https://github.com/lukeed/dset) if you just want to do an in place mutation on a deeply nested value.\n\n## Install\n\n> npm i clean-set\n\nIncludes builds for commonjs, umd, and esm and is less than ~~200b~~ 182b gzip (thanks to [@lukeed](https://github.com/lukeed))\n\n## Usage\n\n```javascript\nlet current = {\n  a: { b: [], c: true },\n  d: [],\n  e: {\n    f: { g: 'hello' },\n    h: { i: 0 },\n  },\n};\n\nlet next = cleanSet(current, 'e.h.i', 1);\n\n/**\n * Alternatively you can provide a function for the final parameter to\n * receive the current value of that node.\n *\n * let next = cleanSet(current, 'e.h.i', i => i + 1);\n */\n\n// The value is assigned\nconsole.log(next.e.h.i !== current.e.h.i); // true\n\n// Each parent node touched is a new reference\nconsole.log(next.e.h !== current.e.h); // true\nconsole.log(next.e !== current.e); // true\nconsole.log(next !== current); // true\n\n// Untouched references remain the same\nconsole.log(next.e.f === current.e.f); // true\nconsole.log(next.a === current.a); // true\nconsole.log(next.a.b === current.a.b); // true\nconsole.log(next.d === current.d); // true\n```\n\nHere's what an object spread equivalent would look like.\n\n```javascript\nlet next = {\n  ...current,\n  e: {\n    ...current.e,\n    h: { ...current.e.h, i: 1 },\n  },\n};\n```\n\n## Benchmarks\n\nCheck out the [es bench link](https://esbench.com/bench/5b16f1cbf2949800a0f61cf2) to run the benchmarks yourself.\n\n> Note: YMMV canary and firefox dev have some impressive improvements for object assign and object spread respectively.\n\nChrome 67\n\n<img src=\"./assets/chrome_67.png\">\n"
  },
  {
    "path": "dist/clean-set.es.js",
    "content": "function r(r){var t=r&&r.pop?[]:{};for(var n in r)t[n]=r[n];return t}export default function(t,n,l){n.split&&(n=n.split(\".\"));for(var o=r(t),a=o,e=0,f=n.length;e<f;e++)a=a[n[e]]=e===f-1?l&&l.call?l(a[n[e]]):l:r(a[n[e]]);return o}\n//# sourceMappingURL=clean-set.es.js.map\n"
  },
  {
    "path": "dist/clean-set.js",
    "content": "function r(r){var n=r&&r.pop?[]:{};for(var t in r)n[t]=r[t];return n}module.exports=function(n,t,o){t.split&&(t=t.split(\".\"));for(var l=r(n),e=l,i=0,p=t.length;i<p;i++)e=e[t[i]]=i===p-1?o&&o.call?o(e[t[i]]):o:r(e[t[i]]);return l};\n//# sourceMappingURL=clean-set.js.map\n"
  },
  {
    "path": "dist/clean-set.modern.js",
    "content": "function t(t){let l=t&&t.pop?[]:{};for(let e in t)l[e]=t[e];return l}export default function(l,e,n){e.split&&(e=e.split(\".\"));let r=t(l),o=r,f=0,i=e.length;for(;f<i;f++)o=o[e[f]]=f===i-1?n&&n.call?n(o[e[f]]):n:t(o[e[f]]);return r}\n//# sourceMappingURL=clean-set.modern.js.map\n"
  },
  {
    "path": "index.d.ts",
    "content": "declare module 'clean-set' {\n  function cleanSet<A>(\n    source: A,\n    keys: string | string[],\n    update: <B>(value: B) => B\n  ): A;\n  function cleanSet<A>(source: A, keys: string | string[], update: any): A;\n  /**\n   * Update a value in a deeply nested object and clone each node touched\n   * @param source The object to be updated\n   * @param location The dot notation or array key path to the property you wish to update\n   * @param update Either a function that will receive the current value and return a new value OR the new value for the node\n   * @returns The new object\n   */\n  export default cleanSet;\n}\n"
  },
  {
    "path": "lib/index.js",
    "content": "export default function(source, keys, update) {\n  keys.split && (keys = keys.split('.'));\n\n  let next = copy(source),\n    last = next,\n    i = 0,\n    l = keys.length;\n\n  for (; i < l; i++) {\n    last = last[keys[i]] =\n      i === l - 1\n        ? update && !!update.call\n          ? update(last[keys[i]])\n          : update\n        : copy(last[keys[i]]);\n  }\n\n  return next;\n}\n\nfunction copy(source) {\n  let to = source && !!source.pop ? [] : {};\n  for (let i in source) to[i] = source[i];\n  return to;\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"clean-set\",\n  \"version\": \"1.1.2\",\n  \"description\": \"A fast deep assignment alternative to the object spread operator and Object.assign\",\n  \"umd:main\": \"dist/clean-set.min.js\",\n  \"module\": \"dist/clean-set.es.js\",\n  \"main\": \"dist/clean-set.js\",\n  \"source\": \"lib/index.js\",\n  \"typings\": \"index.d.ts\",\n  \"scripts\": {\n    \"build\": \"microbundle\",\n    \"coverage\": \"nyc report --reporter=text-lcov > coverage.lcov && codecov\",\n    \"tap\": \"tape -r esm tests/**/*.js | tap-difflet\",\n    \"test\": \"nyc --reporter=text npm run tap\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/fwilkerson/clean-set.git\"\n  },\n  \"keywords\": [],\n  \"author\": \"Frank A. Wilkerson\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/fwilkerson/clean-set/issues\"\n  },\n  \"homepage\": \"https://github.com/fwilkerson/clean-set#readme\",\n  \"devDependencies\": {\n    \"codecov\": \"^3.2.0\",\n    \"esm\": \"^3.2.18\",\n    \"microbundle\": \"^0.12.0\",\n    \"nyc\": \"^15.0.1\",\n    \"prettier\": \"^1.16.4\",\n    \"tap-difflet\": \"^0.7.1\",\n    \"tape\": \"^4.10.1\"\n  }\n}\n"
  },
  {
    "path": "tests/index.js",
    "content": "import test from 'tape';\n\nimport cleanSet from '../lib';\n\nlet data = {\n  a: { b: [], c: true },\n  d: [{ m: [{ n: 2 }] }],\n  e: {\n    f: { g: 'hello' },\n    h: { i: 0, j: [] },\n  },\n};\n\ntest('clean-set: basic functionality', tap => {\n  let next = cleanSet(data, 'e.h.i', 1);\n\n  tap.assert(next != null, 'next has a value');\n  tap.assert(next !== data, 'next has a new reference');\n\n  tap.assert(next.e.h.i === 1, 'value was updated');\n  tap.assert(next.e.h !== data.e.h, 'touched node has a new reference');\n  tap.assert(next.e !== data.e, 'touched node has a new reference');\n\n  tap.assert(next.a === data.a, 'untouched node kept their reference');\n  tap.assert(next.a.b === data.a.b, 'untouched node kept their reference');\n  tap.assert(next.d === data.d, 'untouched node kept their reference');\n  tap.assert(\n    next.d[0].m === data.d[0].m,\n    'untouched node kept their reference'\n  );\n  tap.assert(\n    next.d[0].m[0] === data.d[0].m[0],\n    'untouched node kept their reference'\n  );\n  tap.assert(next.e.f === data.e.f, 'untouched node kept their reference');\n  tap.assert(next.e.h.j === data.e.h.j, 'untouched node kept their reference');\n\n  tap.end();\n});\n\ntest('clean-set: update value as a function', tap => {\n  let next = cleanSet(data, 'e.h.j', j => j.concat('some-item'));\n\n  tap.assert(next != null, 'next has a value');\n  tap.assert(next !== data, 'next has a new reference');\n\n  tap.assert(next.e.h.j[0] === 'some-item', 'value was updated');\n  tap.assert(next.e.h.j !== data.e.h.j, 'touched node has a new reference');\n  tap.assert(next.e.h !== data.e.h, 'touched node has a new reference');\n  tap.assert(next.e !== data.e, 'touched node has a new reference');\n\n  tap.assert(next.a === data.a, 'untouched node kept their reference');\n  tap.assert(next.a.b === data.a.b, 'untouched node kept their reference');\n  tap.assert(next.d === data.d, 'untouched node kept their reference');\n  tap.assert(\n    next.d[0].m === data.d[0].m,\n    'untouched node kept their reference'\n  );\n  tap.assert(\n    next.d[0].m[0] === data.d[0].m[0],\n    'untouched node kept their reference'\n  );\n  tap.assert(next.e.f === data.e.f, 'untouched node kept their reference');\n\n  tap.end();\n});\n\ntest('clean-set: array key', tap => {\n  let next = cleanSet(data, ['e', 'h', 'i'], i => i + 1);\n\n  tap.assert(next != null, 'next has a value');\n  tap.assert(next !== data, 'next has a new reference');\n\n  tap.assert(next.e.h.i === 1, 'value was updated');\n  tap.assert(next.e.h !== data.e.h, 'touched node has a new reference');\n  tap.assert(next.e !== data.e, 'touched node has a new reference');\n\n  tap.assert(next.a === data.a, 'untouched node kept their reference');\n  tap.assert(next.a.b === data.a.b, 'untouched node kept their reference');\n  tap.assert(next.d === data.d, 'untouched node kept their reference');\n  tap.assert(\n    next.d[0].m === data.d[0].m,\n    'untouched node kept their reference'\n  );\n  tap.assert(\n    next.d[0].m[0] === data.d[0].m[0],\n    'untouched node kept their reference'\n  );\n  tap.assert(next.e.f === data.e.f, 'untouched node kept their reference');\n  tap.assert(next.e.h.j === data.e.h.j, 'untouched node kept their reference');\n\n  tap.end();\n});\n\ntest('clean-set: creates an object if none exists', tap => {\n  let next = cleanSet(data, 'e.h.k.l', true);\n\n  tap.assert(next != null, 'next has a value');\n  tap.assert(next !== data, 'next has a new reference');\n\n  tap.same(next.e.h.k, { l: true }, 'object was created and value was added');\n  tap.assert(next.e.h !== data.e.h, 'touched node has a new reference');\n  tap.assert(next.e !== data.e, 'touched node has a new reference');\n\n  tap.assert(next.a === data.a, 'untouched node kept their reference');\n  tap.assert(next.a.b === data.a.b, 'untouched node kept their reference');\n  tap.assert(next.d === data.d, 'untouched node kept their reference');\n  tap.assert(\n    next.d[0].m === data.d[0].m,\n    'untouched node kept their reference'\n  );\n  tap.assert(\n    next.d[0].m[0] === data.d[0].m[0],\n    'untouched node kept their reference'\n  );\n  tap.assert(next.e.f === data.e.f, 'untouched node kept their reference');\n  tap.assert(next.e.h.j === data.e.h.j, 'untouched node kept their reference');\n\n  tap.end();\n});\n\ntest('clean-set: supports accessing an index of an array', tap => {\n  // let next = cleanSet(data, 'd.0.m.0.n', n => n + 1); // either will work for this scenario\n  let next = cleanSet(data, ['d', 0, 'm', 0, 'n'], n => n + 1);\n\n  tap.assert(next != null, 'next has a value');\n  tap.assert(next !== data, 'next has a new reference');\n\n  tap.assert(next.d[0].m[0].n === 3, 'value was updated');\n  tap.assert(\n    next.d[0].m[0] !== data.d[0].m[0],\n    'touched node has a new reference'\n  );\n  tap.assert(next.d[0].m !== data.d[0].m, 'touched node has a new reference');\n  tap.assert(next.d[0] !== data.d[0], 'touched node has a new reference');\n  tap.assert(next.d !== data.d, 'touched node has a new reference');\n\n  tap.assert(next.a === data.a, 'untouched node kept their reference');\n  tap.assert(next.a.b === data.a.b, 'untouched node kept their reference');\n  tap.assert(next.e.f === data.e.f, 'untouched node kept their reference');\n  tap.assert(next.e.h.i === data.e.h.i, 'untouched node kept their reference');\n  tap.assert(next.e.h.j === data.e.h.j, 'untouched node kept their reference');\n\n  tap.end();\n});\n\ntest('clean-set: creates a record at the index if none exists', tap => {\n  let next = cleanSet(data, ['d', 2, 'o'], { p: 'creates at index' });\n\n  tap.assert(next != null, 'next has a value');\n  tap.assert(next !== data, 'next has a new reference');\n  tap.same(next.d[2].o, { p: 'creates at index' }, 'value was created');\n  tap.assert(next.d !== data.d, 'touched node has a new reference');\n\n  tap.assert(next.a === data.a, 'untouched node kept their reference');\n  tap.assert(next.a.b === data.a.b, 'untouched node kept their reference');\n  tap.assert(\n    next.d[0].m === data.d[0].m,\n    'untouched node kept their reference'\n  );\n  tap.assert(\n    next.d[0].m[0] === data.d[0].m[0],\n    'untouched node kept their reference'\n  );\n  tap.assert(next.e.f === data.e.f, 'untouched node kept their reference');\n  tap.assert(next.e.h.i === data.e.h.i, 'untouched node kept their reference');\n  tap.assert(next.e.h.j === data.e.h.j, 'untouched node kept their reference');\n\n  tap.end();\n});\n\ntest('clean-set: allows setting to null', tap => {\n  let next = cleanSet(data, 'q.r', null);\n\n  tap.assert(next != null, 'next has a value');\n  tap.assert(next !== data, 'next has a new reference');\n\n  tap.same(next.q, { r: null } , 'object was created and value set to null');\n\n  tap.end();\n});\n\n\ntest('clean-set: performance benchmark', tap => {\n  let cData = data,\n    n = 0,\n    start = +Date.now();\n  for (; n < 100000; n++) {\n    cData = cleanSet(cData, 'e.h.i', i => i + 1);\n  }\n  let timeTaken = +Date.now() - start;\n\n  tap.assert(timeTaken < 300, `cleanSet benchmark ran in ${timeTaken}ms`);\n  tap.end();\n});\n\ntest('Object.assign: performance benchmark', tap => {\n  let cData = data,\n    n = 0,\n    start = +Date.now();\n\n  for (; n < 100000; n++) {\n    cData = Object.assign({}, cData, {\n      e: Object.assign({}, cData.e, {\n        h: Object.assign({}, cData.e.h, { i: cData.e.h.i + 1 }),\n      }),\n    });\n  }\n  let timeTaken = +Date.now() - start;\n\n  tap.assert(timeTaken < 300, `Object.assign benchmark ran in ${timeTaken}ms`);\n\n  tap.end();\n});\n\ntest('Object spread: performance benchmark', tap => {\n  let cData = data,\n    n = 0,\n    start = +Date.now();\n\n  for (; n < 100000; n++) {\n    cData = {\n      ...cData,\n      e: { ...cData.e, h: { ...cData.e.h, i: cData.e.h.i + 1 } },\n    };\n  }\n  let timeTaken = +Date.now() - start;\n\n  tap.assert(timeTaken < 300, `Object spread benchmark ran in ${timeTaken}ms`);\n\n  tap.end();\n});\n"
  }
]