{"id":15415836,"url":"https://github.com/davidje13/json-immutability-helper","last_synced_at":"2026-03-18T02:05:07.703Z","repository":{"id":57284925,"uuid":"175998053","full_name":"davidje13/json-immutability-helper","owner":"davidje13","description":"json-serialisable general-purpose reducer","archived":false,"fork":false,"pushed_at":"2024-09-05T19:36:11.000Z","size":256,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-23T23:12:32.331Z","etag":null,"topics":["immutable","json","reducer"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/davidje13.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-03-16T16:45:03.000Z","updated_at":"2024-09-05T19:36:14.000Z","dependencies_parsed_at":"2024-10-21T15:38:51.490Z","dependency_job_id":null,"html_url":"https://github.com/davidje13/json-immutability-helper","commit_stats":{"total_commits":73,"total_committers":1,"mean_commits":73.0,"dds":0.0,"last_synced_commit":"1938d65d07e2f44ed781e1273bfe0f639dbce376"},"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidje13%2Fjson-immutability-helper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidje13%2Fjson-immutability-helper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidje13%2Fjson-immutability-helper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidje13%2Fjson-immutability-helper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/davidje13","download_url":"https://codeload.github.com/davidje13/json-immutability-helper/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241435161,"owners_count":19962400,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["immutable","json","reducer"],"created_at":"2024-10-01T17:09:55.102Z","updated_at":"2025-12-27T05:31:43.604Z","avatar_url":"https://github.com/davidje13.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JSON Immutability Helper\n\nJSON-serialisable mutability helpers for both client- and server-side\ncode.\n\nOriginally based on\n[`immutability-helper`](https://github.com/kolodny/immutability-helper),\nwith list, string and mathematical commands added, but now uses an\nalternative syntax.\n\nThis library includes helpers for integrating with React / preact\napps using hooks, but can also be used indepenently.\n\n## Install dependency\n\n```bash\nnpm install --save json-immutability-helper\n```\n\n## Motivation\n\nWhen working with collaborative state shared over a network, it can\nbe desirable to share state deltas rather than full state objects.\nThis allows parallel editing from different editors, and reduces\nbandwidth requirements.\n\nSharing functions between browsers and servers is not desirable, as\nit introduces security concerns. Instead, this package provides a\nfoundational set of primitive operations which cover typical\nmutations.\n\nBecause the operations are intended to be shared, all inputs are\nassumed to be potentially malicious, with necessary mitigations\napplied.\n\n## Usage\n\nThe core function `update` takes an object and a spec, and returns an\nupdated object. The input object and spec are unchanged (considered\nimmutable).\n\nOptimisations ensure that if any properties within the object are\nunchanged, they will be returned exactly (not a copy). This helps\nwhen detecting changes using shallow comparison.\n\nSimple usage:\n\n```javascript\nconst { update } = require('json-immutability-helper');\n\nconst initialState = { foo: 3 };\n\nconst updatedState = update(initialState, { foo: ['+', 1] });\n// updatedState = { foo: 4 }\n// initialState is unchanged\n```\n\nYou can define arbitrary hierarchies:\n\n```javascript\nconst { update } = require('json-immutability-helper');\n\nconst initialState = { foo: { bar: { baz: 1 } } };\n\nconst updatedState = update(initialState, {\n  foo: {\n    bar: {\n      baz: ['=', 7],\n    },\n    extra: ['=', 1]\n  },\n});\n// updatedState = { foo: { bar: { baz: 7 }, extra: 1 } }\n```\n\nNote that arrays in the spec define commands. To navigate to a\nparticular item in an array, use a number as an object key:\n\n```javascript\nconst { update } = require('json-immutability-helper');\n\nconst initialState = { foo: [2, 8] };\n\nconst updatedState = update(initialState, {\n  foo: {\n    0: ['=', 5],\n  },\n});\n// updatedState = { foo: [5, 8] }\n\n// To serialise as JSON, you can quote the index:\n\nconst spec = `\n{\n  \"foo\": {\n    \"0\": [\"=\", 5]\n  }\n}\n`;\n```\n\nWith list commands (note `.with(listCommands)`):\n\n```javascript\nconst listCommands = require('json-immutability-helper/commands/list');\nconst { update } = require('json-immutability-helper').with(listCommands);\n\nconst initialState = {\n  items: [\n    { myId: 3, myThing: 'this' },\n    { myId: 28, myThing: 'that' },\n  ],\n};\n\n// Change the property 'myThing' of the first item with myId=3\nconst updatedState = update(initialState, {\n  items: [\n    'update',\n    ['first', ['myId', 3]],\n    { myThing: ['=', 'updated this'] },\n  ],\n});\n\n// Note that the spec is fully JSON-serialisable:\n\nconst spec = `\n{\n  \"items\": [\n    \"update\",\n    [\"first\": [\"myId\", 3]],\n    { \"myThing\": [\"=\", \"updated this\"] }\n  ]\n}\n`;\nconst updatedJsonState = update(initialState, JSON.parse(spec));\n```\n\n## Locators\n\nThe main concepts introduced in this project are conditions and\nlocators. Several commands use conditions to decide which items to\nupdate, such as `if`, `update`, `delete`, `insert`, and `move`,\namong others.\n\nLocators match items in a list. The available locators are:\n\n- `'all'` - matches all items in a list\n- `'first'` - matches the first item in a list\n- `'last'` - matches the last item in a list\n- `['all', \u003ccondition\u003e]` - matches all items which meet the condition\n- `['first', \u003ccondition\u003e]` - matches the first item which meets the\n  condition\n- `['last', \u003ccondition\u003e]` - matches the last item which meets the\n  condition\n\n`first` and `last` are \"single\" locators (they match a maximum of 1\nitem and can be used as both `single-locator` and `multi-locator`s).\n`all` is a \"multi\" locator (matching an unlimited number of items\nand can only be used as a `multi-locator`).\n\n```javascript\nconst items = [1, 2, 3, 4];\n\nconst updatedItems = update(items, [\n  'update',\n  'last',\n  ['=', 5],\n]);\n\n// updatedItems = [1, 2, 3, 5];\n```\n\n## Conditions\n\nConditions have a similar structure to specs; object hierarchies\ndefine property access, and arrays define the conditions themselves:\n\n```javascript\nconst items = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }];\n\nconst updatedItems = update(items, [\n  'delete',\n  ['first', { id: ['=', 3] }],\n]);\n\n// updatedItems = [{ id: 1 }, { id: 2 }, { id: 4 }];\n```\n\n### Generic conditions\n\n- `['=', \u003cexpected\u003e]` matches if the value equals `expected` (if\n  multiple values are provided, this checks if the value matches any\n  of them). Note that this uses `Object.is` matching, so `null` and\n  `undefined` are distinct, and objects / arrays will never compare\n  equal (only test primitive types).\n- `['!=', \u003cexpected\u003e]` matches if the value does not equal `expected`\n  (if multiple values are provided, this checks if the value does not\n  match any of them)\n- `['~=', \u003cexpected\u003e]` matches if the value loosely equals `expected`\n  (if multiple values are provided, this checks if the value matches\n  any of them)\n- `['!~=', \u003cexpected\u003e]` matches if the value does not loosely equal\n  `expected` (if multiple values are provided, this checks if the\n  value does not match any of them)\n- `['\u003e', \u003cthreshold\u003e]` matches if the value is strictly greater than\n  `threshold`\n- `['\u003e=', \u003cthreshold\u003e]` matches if the value is greater than or equal\n  to `threshold`\n- `['\u003c', \u003cthreshold\u003e]` matches if the value is strictly less than\n  `threshold`\n- `['\u003c=', \u003cthreshold\u003e]` matches if the value is less than or equal to\n  `threshold`\n- `['exists']` matches if the value is set to any value (is not\n  `undefined`)\n- `['and', \u003ccondition1\u003e, \u003ccondition2\u003e, ...]` matches if all of the\n  sub-conditions match\n- `['or', \u003ccondition1\u003e, \u003ccondition2\u003e, ...]` matches if any of the\n  sub-conditions match\n- `['not', \u003ccondition\u003e]` negates the sub-condition\n\n### List conditions\n\nUse `.with(listCommands)` to enable these conditions.\n\n- `['some', \u003ccondition\u003e]` matches if any item in the list matches the\n  sub-condition\n- `['every', \u003ccondition\u003e]` matches if every item in the list matches\n  the sub-condition\n- `['none', \u003ccondition\u003e]` matches if no items in the list match the\n  sub-condition (equivalent to `['not', ['some', \u003ccondition\u003e]]`)\n- `['length', \u003ccondition\u003e]` checks the length of the list using the\n  given condition\n\n### Extending\n\nYou can add new conditions with:\n\n```javascript\nconst modifiedUpdate = update.with({\n  conditions: {\n    // example: \u003e\n    myGreaterThan: (parameters, context) =\u003e (actualValue) =\u003e {\n      // actualValue is the value of the object being tested\n\n      return actualValue \u003e parameters[0];\n    },\n\n    // example: and\n    myAnd: (parameters, context) =\u003e (actualValue) =\u003e {\n      const predicates = parameters.map((c) =\u003e context.makeConditionPredicate(c));\n      return (v) =\u003e predicates.every((p) =\u003e p(v));\n    },\n  },\n});\n```\n\n## Commands reference\n\n### Generic\n\n- `['=', value]`\n  sets the value to the literal value given.\n\n- `['unset']`\n  deletes the value. If the parent is an object, the property is\n  removed. If the parent is an array, the element is removed and\n  subsequent items re-packed. If used on the root, `update` will\n  return `undefined` (unless `allowUnset` is specified).\n\n- `['init', value]`\n  if undefined, sets the value to the literal value given.\n  Otherwise leaves the value unchanged.\n\n- `['if', condition, spec, elseSpec?]`\n  applies the given `spec` if the `condition` matches, otherwise\n  applies the `elseSpec` (if provided) or does nothing.\n\n- `['seq', specs...]`\n  applies the given `spec`s sequentially. This can be used to create\n  complex updates out of simple operations.\n\n  ```javascript\n  // Computes (value + 2 - 10)\n  const updated = update(state, [\n    'seq',\n    ['+', 2],\n    ['-', 10],\n  ]);\n  ```\n\n  (note that for mathematical operations it is usually better to\n  use `rpn`, described below).\n\n### Object\n\n- `['merge', object, initial?]`\n  Merges the keys of object into the current target. Similar to\n  calling `Object.assign`. Note that any values set to `undefined`\n  will be skipped (to avoid behaviour differences after a JSON\n  round-trip). If `initial` is provided and the target value is\n  undefined, it will be assigned the value of `initial` before\n  merging\n  (equivalent to `['seq', ['init', initial], ['merge', object]]`).\n\n### Boolean\n\n- `['~']`\n  toggles the current value\n  (`true` \u0026rarr; `false`; `false` \u0026rarr; `true`).\n\n### List\n\nUse `.with(listCommands)` to enable these commands.\n\n- `['push', items...]`\n  inserts one or more items at the end of the array.\n\n- `['unshift', items...]`\n  inserts one or more items at the start of the array.\n\n- `['addUnique', items...]`\n  inserts one or more items at the end of the array if they are not\n  already present in the array. Note that this only works with\n  primitive values (strings / numbers / booleans). For more complex\n  objects, consider defining your own condition:\n\n  ```javascript\n  [\n    'if',\n    ['none', { id: ['=', newID] }],\n    ['push', { id: newID, etc }],\n  ]\n  ```\n\n- `['splice', arguments...]`\n  invokes `splice` on the array repeatedly. Each argument should\n  be an array of parameters to send the `splice` function;\n  `[offset, length, items...]`. Note that offset can be negative\n  to count from the end of the array.\n\n- `['insert', 'before' | 'after', multi-locator, items...]`\n  inserts one or more items before or after the item identified by\n  the `locator` (or at the start / end of the array if no items\n  match, depending on the locator type).\n\n- `['update', multi-locator, spec, elseInsert?]`\n  applies the given `spec` to items in the array which match the\n  `locator`. If no item matches and `elseInsert` is set, this\n  inserts a new item (at the end of the array) and applies the `spec`\n  to it.\n\n- `['delete', multi-locator]`\n  deletes items in the array which match the `locator`. If no items\n  match, does nothing.\n\n- `['swap', single-locator, single-locator]`\n  swaps the positions of the two items matching the given locators.\n  If either locator does not find a match, does nothing.\n\n- `['move', multi-locator, 'before' | 'after', single-locator]`\n  moves all the items matching the multi-locator before or after the\n  item matching the single locator (preserving their original order).\n  If either locator does not find a match, does nothing.\n\n### String\n\nUse `.with(stringCommands)` to enable these commands.\n\n- `['replaceAll', search, replace]`\n  replaces all occurrences of `search` in the string with `replace`.\n  Note that the `search` is used as a literal, not as a regular\n  expression.\n\n- `['rpn', operations...]`\n  reverse Polish notation command; see below for details.\n\n### Number\n\n- `['+', value]`\n  adds the given `value` to the current value. This is available in\n  the default command set and does not need an extension.\n\n- `['-', value]`\n  subtracts the given `value` from the current value. This is\n  available in the default command set and does not need an\n  extension.\n\nUse `.with(mathCommands)` to enable the following\ncommand:\n\n- `['rpn', operations...]`\n  reverse Polish notation command; see below for details.\n\n### `rpn`\n\nThe `rpn` command lets you specify updates in reverse Polish\nnotation. This is especially useful for applying complex mathematical\noperations or string manipulations.\n\nSome examples:\n\n```javascript\n// compute x * 2\nupdate(5, ['rpn', 'x', 2, '*']); // = 10\n\n// compute x * 2 + 10\nupdate(5, ['rpn', 'x', 2, '*', 10, '+']); // = 20\n\n// compute sin(x) + 2 * cos(x)\nupdate(5, ['rpn', 'x', 'sin', 2, 'x', 'cos', '*', '+']); // ~= -0.3916\n```\n\nString manipulation is also supported, but should only be enabled if\nneeded due to its ability to construct large strings, which could be\nused by malicious clients to launch memory exhaustion attacks against\na server. To enable string manipulation, use `.with(stringCommands)`:\n\n```javascript\nconst stringCommands = require('json-immutability-helper/commands/string');\nconst { update } = require('json-immutability-helper').with(stringCommands);\n\n// compute x.substr(4, 3)\nupdate('foo bar baz', ['rpn', 'x', 4, 3, 'substr']); // = bar\n\n// compute x.leftPad(2)\nupdate('3', ['rpn', 'x', 2, 'leftPad']); // = 03\n```\n\nSome functions accept optional parameters or are variadic. By default\nit is assumed that the minimum number of parameters are passed to\neach function. To specify a different number, add `:\u003cnum\u003e` to the end\nof the function name:\n\n```javascript\n// compute max(x, -x, 2)\nupdate(4, ['rpn', 'x', 'x', 'neg', 2, 'max:3']); // = 4\n\n// compute log_2(x)\nupdate(8, ['rpn', 'x', 2, 'log:2']); // = 3\n```\n\nString literals can be specified as JSON-encoded strings (this means\nthat for transport, strings may be double-encoded).\nFor example: `'\"foo\"'`.\n\nAvailable constants:\n\n- `x`: the old value\n- `pi`: the mathematical constant \u0026pi; (3.141\u0026hellip;)\n- `e`: the mathematical constant *e* (2.718\u0026hellip;)\n- `Inf`: positive infinity\n- `NaN`: not-a-number\n\nAvailable functions/operators from `mathCommands`:\n\n- `value 'Number'`: converts the value to a number\n- `a b '+'`: adds two numbers\n- `a b c ... '+:n'`: adds many numbers\n- `a b '-'`: subtracts `b` from `a`\n- `a b '*'`: multiplies two numbers\n- `a b '/'`: divides `a` by `b`\n- `a b '//'`: divides `a` by `b`, returning the truncated integer\n  result\n- `a b '^'`: raises `a` to the power of `b`\n- `a b '%'`: returns the remainder of `a / b` (can be negative)\n- `a b 'mod'`: returns the *positive* remainder of `a / b`\n- `a 'neg'`: negates `a`\n- `a 'abs'`: returns the absolute value of `a`\n- `value 'log'`: returns the natural logarithm of `value`\n- `value base 'log:2'`: returns the logarithm of `value` in base\n  `base`\n- `value 'log2'`: returns the logarithm of `value` in base 2\n  (same as `value 2 'log:2'`)\n- `value 'log10'`: returns the logarithm of `value` in base 10\n  (same as `value 10 'log:2'`)\n- `value 'exp'`: returns the exponent of `value` (i.e. `e^value`)\n- `value base 'exp:2'`: returns `base^value`\n- `v1 v2 v3 ... 'max:n'`: returns the largest value\n  (the default arity is 2)\n- `v1 v2 v3 ... 'min:n'`: returns the smallest value\n  (the default arity is 2)\n- `a b 'bitor'`: returns the bitwise-or of `a` and `b`\n- `a b 'bitand'`: returns the bitwise-and of `a` and `b`\n- `a b 'bitxor'`: returns the bitwise-xor of `a` and `b`\n- `a 'bitneg'`: returns the bitwise negation of `a`\n- `x 'sin'`: returns `sin(x)` in radians\n- `x 'cos'`: returns `cos(x)` in radians\n- `x 'tan'`: returns `tan(x)` in radians\n- `x 'asin'`: returns `asin(x)` in radians\n- `x 'acos'`: returns `acos(x)` in radians\n- `x 'atan'`: returns `atan(x)` in radians\n- `x 'sinh'`: returns `sinh(x)`\n- `x 'cosh'`: returns `cosh(x)`\n- `x 'tanh'`: returns `tanh(x)`\n- `x 'asinh'`: returns `asinh(x)`\n- `x 'acosh'`: returns `acosh(x)`\n- `x 'atanh'`: returns `atanh(x)`\n- `x 'round'`: rounds `x` to the nearest integer (\"round halves-up\")\n  (for control over the number of decimal places, see `'String'`)\n- `x 'floor'`: returns the highest integer which is less than or\n  equal to `x` (\"round down\")\n- `x 'ceil'`: returns the lowest integer which is greater than or\n  equal to `x` (\"round up\")\n- `x 'trunc'`: returns the largest integer with absolute value less\n  than or equal to `abs(x)` (\"round towards zero\")\n\nAvailable functions/operators from `stringCommands`:\n\n- `value 'String'`: converts the value to a string\n- `value dp 'String:2'`: converts the value to a rounded string\n  (decimal places can be a positive or negative integer)\n- `string 'length'`: returns the length of `string` in characters\n- `a b c ... 'concat:n'`: concatenates strings\n  (the default arity is 2)\n- `string count 'repeat'`: repeats `string` `count` times\n- `string search 'indexOf'`: returns the index of the first\n  occurrence of `search` in `string` (0-based), or -1 if it is not\n  found\n- `string search start 'indexOf:3'`: returns the index of the first\n  occurrence of `search` in `string`, skipping the first `start`\n  characters\n- `string search 'lastIndexOf'`: returns the index of the last\n  occurrence of `search` in `string` (0-based), or -1 if it is not\n  found\n- `string search end 'lastIndexOf:3'`: returns the index of the last\n  occurrence of `search` in `string` within the range up to `end`\n- `string length 'padStart'`: pads the start of `string` with spaces\n  until it is at least `length` characters long\n- `string length padding 'padStart:3'`: pads the start of `string`\n  with `padding` until it is at least `length` characters long\n  (fragments of the `padding` string may be used)\n- `string length 'padEnd'`: pads the end of `string` with spaces\n  until it is at least `length` characters long\n- `string length padding 'padEnd:3'`: pads the end of `string` with\n  `padding` until it is at least `length` characters long (fragments\n  of the `padding` string may be used)\n- `string from 'slice'`: returns a substring of `string` from `from`\n  to the end of the string. If `from` is negative, it counts from the\n  end of the string.\n- `string from to 'slice:3'`: returns a substring of `string` from\n  `from` to `to` (exclusive). If `from` or `to` are negative, they\n  count from the end of the string.\n- `string from length 'substr'`: returns a substring of `string` from\n  `from` of length `length`. If `from` is negative it counts from the\n  end of the string.\n\nAs a basic protection against memory exhaution attacks, the generated\nstring length for all operations is capped to 1024 characters, and\n`String` only accepts decimal places within the range -20 \u0026ndash; 20.\nThese restrictions ensure that memory usage can only be linear in the\nnumber of operations, but could still become very high. As the risk\ncannot be fully mitigated, string operations are disabled by default\nand must be explicitly enabled by using `stringCommands`.\n\n## Other context methods\n\nYou can access the default context, or create your own scoped\ncontext:\n\n```javascript\nconst defaultContext = require('json-immutability-helper');\n```\n\n```javascript\nconst defaultContext = require('json-immutability-helper');\nconst myContext = defaultContext.with(/* extensions here */);\n```\n\n- `.with(...extensions)`\n  returns a new context which copies the current context with the\n  given extensions added. Does not mutate the current context.\n  If called with no extensions, this just makes a copy of the current\n  context, though this is generally not useful (the context is\n  immutable anyway).\n\n  Extensions can be:\n\n  - `listCommands`: `require('json-immutability-helper/commands/list')`\n  - `mathCommands`: `require('json-immutability-helper/commands/math')`\n  - `stringCommands`: `require('json-immutability-helper/commands/string')`\n\n  Or a custom extension (all fields are optional; any omitted field\n  is left unchanged):\n\n  ```javascript\n  const myExtension = {\n    commands: {\n      myCommand: (object, args, context) =\u003e newValue,\n      /* etc. */\n    },\n    conditions: {\n      myCondition: (params, context) =\u003e (actual) =\u003e boolean,\n      /* etc. */\n    },\n    limits: {\n      stringLength: 1024,\n      recursionDepth: 10,\n      recursionBreadth: 10000,\n    },\n    isEquals: Object.is,\n    copy: (o) =\u003e myCopyFunction(o),\n  }\n  ```\n\n  The `commands` section defines new commands which can be used in\n  the same places as built-in commands. `object` is the previous\n  value for the current position. `args` is an array of parameters\n  (excluding the command name). `context` is an object which contains\n  the methods listed here, as well as `update`.\n\n  The `conditions` section defines new conditions which can be used\n  in the same places as built-in conditions.\n\n  The `limits` are used by various built-in commands to ensure\n  resource usage does not grow too high. You can omit the entire\n  section or individual entries to leave the defaults, or specify\n  higher or lower limits.\n\n  `isEquals` is the function used internally to determine whether a\n  command caused a value to change (if this returns `false`, the\n  original value will be used rather than the new value)\n\n  `copy` is the function used internally to make shallow copies of\n  data structures. The default implementation can clone objects,\n  arrays, and primitive values.\n\n  Note that as a convenience it is also possible to call `.with` on\n  the `update` function itself. This does the same thing, but returns\n  a new `update` function rather than a new `context` (equivalent to\n  calling `update.context.with(...).update`).\n\n- `.combine(specs)`\n  generates a single spec which is equivalent to applying all the\n  given specs sequentially. Conceptually this is identical to using\n  `['seq', ...specs]`, but `combine` optimises common paths where\n  possible.\n  Note that the specs must be provided in an array, not as variadic\n  parameters.\n\n- `.makeConditionPredicate(condition)`\n  generates a predicate for the provided condition. This should be\n  used by custom commands when filtering based on a provided\n  condition is required.\n\n- `.invariant(check, message?)`\n  throws an exception if `check` is false. Includes the message if\n  specified (can be a string or a function which returns a string).\n\nThe default context's `update`, `combine` and `invariant` are also\navailable as direct imports:\n\n```javascript\nconst { update, combine, invariant } = require('json-immutability-helper');\n```\n\n## Extending with .with()\n\nBy default, `json-immutability-helper` exposes minimal commands for\nreduced code size and increased security. If you need additional\ncommands, you can add built-in extensions (see the command list above\nto see which commands need which extensions). Note that if you do not\nneed a particular extension you should not enable it, as all of these\nhave tradeoffs with bundle size and potential attacks (e.g. resource\nexhaustion by generating large strings).\n\n```javascript\nconst listCommands = require('json-immutability-helper/commands/list');\nconst mathCommands = require('json-immutability-helper/commands/math');\nconst stringCommands = require('json-immutability-helper/commands/string');\nconst { update } = require('json-immutability-helper').with(listCommands, mathCommands, stringCommands);\n```\n\nor with ES6 imports:\n\n```javascript\nimport listCommands from 'json-immutability-helper/commands/list';\nimport mathCommands from 'json-immutability-helper/commands/math';\nimport stringCommands from 'json-immutability-helper/commands/string';\nimport context from 'json-immutability-helper';\n\nconst { update } = context.with(listCommands, mathCommands, stringCommands);\n```\n\nAvoid calling `.with` inside functions or in loops. Ideally it should\nbe called once, and the resulting `update` function can be called\nmany times.\n\n## Helpers\n\nSome common helpers are also included:\n\n```javascript\nconst { getScopedState, makeScopedSpec, makeScopedReducer } = require('json-immutability-helper/helpers/scoped');\nconst { makeHooks } = require('json-immutability-helper/helpers/hooks');\n```\n\n### `getScopedState(context, state, path[, defaultValue])`\n\n```javascript\nconst context = require('json-immutability-helper');\nconst { getScopedState } = require('json-immutability-helper/helpers/scoped');\n\nconst value = { foo: { bar: [{ baz: 7 }] } };\nconst sub = getScopedState(context, value, ['foo', 'bar', 0, 'baz']);\n// sub = 7\n\nconst value2 = { foo: [{ id: 1, bar: 'a' }, { id: 2, bar: 'b' }] };\nconst sub2 = getScopedState(context, value, ['foo', { id: ['=', 2] }]);\n// sub2 = { id: 2, bar: 'b' }\n```\n\nNavigates multiple layers of the object, returning the state at the\nrequested path, or the default value / `undefined` if any part of the\npath could not be followed.\n\nThe path elements can be:\n- `string`s (object lookup)\n- integer `number`s (array lookup)\n- `Condition`s (see above)\n\nNote that because state is immutable, this is a static copy of the\n_current_ state; it will not automatically update to reflect changes\nto the original state.\n\n### `makeScopedSpec(path, spec[, options])`\n\n```javascript\nconst { makeScopedSpec } = require('json-immutability-helper/helpers/scoped');\n\nconst subSpec = makeScopedSpec(['foo', 'bar', 0, 'baz'], ['=', 7]);\n// subSpec = { foo: { bar: { 0: { baz: ['=', 7] } } } }\n\nconst subSpec2 = makeScopedSpec(['foo', { id: ['=', 2] }, 'bar'], ['=', 7]);\n// subSpec2 = { foo: ['update', ['all', { id: ['=', 2] }], { bar: ['=', 7] }] }\n```\n\nWraps the given spec in a nested path. This is the spec equivalent to\nfetching a sub-state from a state using `getScopedState`.\n\nThe path elements can be:\n- `string`s (object lookup)\n- integer `number`s (array lookup)\n- `Condition`s (see above)\n\nThe available `options` are:\n\n- `initialisePath` (boolean, defaults to `true` if `initialiseValue`\n  is set, else `false`): if `true`, any missing path elements will be\n  initialised automatically as either empty objects or empty arrays\n  (depending on the index type):\n\n  ```javascript\n  const subSpec = makeScopedSpec(['foo', 0], ['=', 7], { initialisePath: true });\n  // subSpec = ['seq', ['init', {}], { foo: ['seq', ['init', []], { 0: ['=', 7] }] }]\n  ```\n\n- `initialiseValue` (value, defaults to undefined): if set, the\n  innermost element will be initialised to this value if it is not\n  already set, before the spec is applied.\n\n  ```javascript\n  const subSpec = makeScopedSpec(['foo', 0], ['+', 1], { initialiseValue: 0 });\n  // subSpec = { foo: { 0: ['seq', ['init', 0], ['+', 1]] } }\n  ```\n\n### `makeScopedReducer(context, reducer, path[, options])`\n\nReturns a scoped reducer (an object containing `{ state, dispatch }`\nwhich delegates to the given reducer (also an object containing\n`{ state, dispatch }`). Uses `getScopedState` and `makeScopedSpec`\ninternally.\n\nNote that because state is immutable, the returned state is a static\ncopy of the _current_ state; it will not automatically update to\nreflect changes to the original state (nor will it update if the\nreturned `dispatch` method is called).\n\nThe available `options` are:\n\n- `initialisePath` (boolean, defaults to `true` if `initialiseValue`\n  is set, else `false`): if `true`, the dispatch will automatically\n  initialise any missing path elements as either empty objects or\n  empty arrays (depending on the index type):\n\n- `initialiseValue` (value, defaults to undefined): returned if the\n  value at the given path is not set. Also applied as an `init` value\n  when dispatching changes.\n\n  ```javascript\n  const sub = makeScopedReducer(context, reducer, ['foo'], { initialiseValue: [] });\n  sub.dispatch(['push', 1]); // safe even if foo was not already set, because it will be initialised to [] first\n  ```\n\n### `makeHooks(path, spec)`\n\n```javascript\nconst React = require('react');\nconst context = require('json-immutability-helper');\nconst { makeHooks } = require('json-immutability-helper/helpers/hooks');\n\nconst { useJSONReducer, useWrappedJSONReducer, useScopedReducer } = makeHooks(context, React);\n```\n\nThis is a convenience for making common React (or Preact) hooks from\nthe context. The second argument should be an object which contains\nat least:\n\n- `useState`\n- `useRef`\n- `useLayoutEffect` or `useEffect`\n- optionally `useMemo`\n- optionally `useReducer`\n\nFor `React`, the main React object covers this requirement. For\n`Preact`, you can use the `preact/hooks` extension.\n\nThe returned hooks are:\n\n- `useJSONReducer(initialArg, init?)`: wraps React's `useReducer`,\n  with `context.update` as the reducer. Returns an object with\n  `{ state, dispatch }`.\n\n- `useWrappedJSONReducer(next)`: same as `useJSONReducer`, but\n  delegates storage to `next`, which should be a 2-element array:\n  `[state, setState]` (as returned from e.g. `useState`). Returns\n  an object with `{ state, dispatch }`.\n\n- `useScopedReducer(reducer, path, options?)`: returns a scoped\n  reducer, using `getScopedState` and `makeScopedSpec` internally.\n  The `reducer` parameter should be an object with\n  `{ state, dispatch }` (as returned by `useJSONReducer` /\n  `useWrappedJSONReducer`, or another `useScopedReducer`).\n\n  The available options are the same as for `makeScopedReducer`.\n\nThe returned `dispatch` functions are always stable references.\nThe returned objects are memoised, so only change when the state\nchanges (unless `useMemo` was not provided).\n\nNote that `makeScopedReducer` and `useScopedReducer` have the same\nbehaviour, but `useScopedReducer` is a better option in siturations\nwhere hooks can be used, as it will return a stable dispatch function\nand memoises the returned entity (helping to reduce unnecessary\nre-rendering).\n\nAs a convenience, a user-space `useEvent` hook is also returned,\nbecause it is used internally. If you prefer, you can also pass in\nyour own `useEvent` hook (removes the need for `useRef` and\n`useLayoutEffect` / `useEffect`).\n\n#### Example usage of hooks:\n\n```jsx\nconst React = require('react');\nconst context = require('json-immutability-helper');\nconst { makeHooks } = require('json-immutability-helper/helpers/hooks');\n\nconst { useJSONReducer, useScopedReducer } = makeHooks(context, React);\n\nconst App = () =\u003e {\n  const scope = useJSONReducer({ items: [] });\n\n  return \u003cMyList scope={scope} /\u003e\n};\n\nconst MyList = ({ scope }) =\u003e {\n  const localScope = useScopedReducer(scope, ['items']);\n  const add = () =\u003e localScope.dispatch(['push', { id: crypto.randomUUID(), label: '' }]);\n\n  return (\n    \u003cul\u003e\n      {localScope.state.map((item) =\u003e (\n        \u003cli key={item.id}\u003e\n          \u003cMyItem scope={localScope} id={item.id} /\u003e\n        \u003c/li\u003e\n      ))}\n      \u003cli\u003e\u003cbutton type=\"button\" onClick={add}\u003eAdd\u003c/button\u003e\u003c/li\u003e\n    \u003c/ul\u003e\n  );\n};\n\nconst MyItem = ({ scope, id }) =\u003e {\n  const localScope = useScopedReducer(scope, [['id', id]]);\n\n  return (\n    \u003c\u003e\n      \u003cinput\n        type=\"text\" value={localScope.state.label}\n        onChange={(e) =\u003e localScope.dispatch({ label: ['=', e.currentTarget.value] })}\n      /\u003e\n      \u003cbutton onClick={() =\u003e localScope.dispatch(['unset'])}\u003eRemove\u003c/button\u003e\n    \u003c/\u003e\n  );\n};\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidje13%2Fjson-immutability-helper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavidje13%2Fjson-immutability-helper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidje13%2Fjson-immutability-helper/lists"}