{"id":24083361,"url":"https://github.com/mitranim/emerge","last_synced_at":"2025-04-30T18:23:30.537Z","repository":{"id":46153247,"uuid":"45300491","full_name":"mitranim/emerge","owner":"mitranim","description":"Use plain JS types as immutable data, with efficient merging and memory sharing","archived":false,"fork":false,"pushed_at":"2021-07-02T09:12:12.000Z","size":243,"stargazers_count":23,"open_issues_count":0,"forks_count":4,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-26T06:56:56.527Z","etag":null,"topics":["data","functional-data-structure","functional-programming","immutable","structural-sharing"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mitranim.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-10-31T12:17:13.000Z","updated_at":"2024-03-12T17:24:36.000Z","dependencies_parsed_at":"2022-08-26T16:02:30.906Z","dependency_job_id":null,"html_url":"https://github.com/mitranim/emerge","commit_stats":null,"previous_names":[],"tags_count":41,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitranim%2Femerge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitranim%2Femerge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitranim%2Femerge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitranim%2Femerge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mitranim","download_url":"https://codeload.github.com/mitranim/emerge/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251758823,"owners_count":21639115,"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":["data","functional-data-structure","functional-programming","immutable","structural-sharing"],"created_at":"2025-01-09T23:56:29.016Z","updated_at":"2025-04-30T18:23:30.513Z","avatar_url":"https://github.com/mitranim.png","language":"JavaScript","readme":"## Description\n\nUtilities for using plain JavaScript dicts and lists as \u003ca href=\"https://en.wikipedia.org/wiki/Immutable_object\" target=\"_blank\"\u003eimmutable\u003c/a\u003e data structures, with structural equality and memory-efficient updates using structural sharing.\n\nJS and JSON have half-decent generic data structures, barring a few flaws:\n\n1) Updates always mutate in-place.\n2) No value equality, only reference equality.\n3) Only strings and symbols as dict keys.\n4) No sets, or no custom equality for ES2015 sets.\n5) No ordered dicts.\n6) Poor algorithmic complexity on list shift/unshift/splice.\n\nEmerge addresses (1) and (2). It provides functions to \"update\" dicts and lists by creating new versions that share as much structure as possible with old versions. This is known as [#_structural sharing_](#structural-sharing). It conserves memory and allows to use identity ([#`is`](#isone-other)) on sibling values as a fast substitute for \"proper\" value equality ([#`equal`](#equalone-other)), which Emerge also provides.\n\nInspired by [Clojure's ideas](https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/AreWeThereYet.md) and the [`clojure.core`](https://clojuredocs.org/core-library) data utils.\n\nFP-friendly: only plain JS dicts and lists, no classes, no OO, bring your own data. Faster than all alternatives that I measured. Very lightweight (≈8 KiB _un_-minified), dependency-free. Written as one file with simple ES2015 exports. A good module bundler and minifier should drop out any functions you don't use.\n\nCompatible with native JS modules.\n\n## TOC\n\n* [#Description](#description)\n* [#Why](#why)\n* [#Installation](#installation)\n* [#API](#api)\n  * [#`put`](#putprev-key-value)\n  * [#`putIn`](#putinprev-path-value)\n  * [#`putBy`](#putbyprev-key-fun-args)\n  * [#`putInBy`](#putinbyprev-path-fun-args)\n  * [#`patch`](#patchdicts)\n  * [#`merge`](#mergedicts)\n  * [#`insert`](#insertlist-index-value)\n  * [#`remove`](#removevalue-key)\n  * [#`removeIn`](#removeinvalue-path)\n  * [#`is`](#isone-other)\n  * [#`equal`](#equalone-other)\n  * [#`equalBy`](#equalbyone-other-fun)\n  * [#`get`](#getvalue-key)\n  * [#`getIn`](#getinvalue-path)\n  * [#`scan`](#scanvalue-path)\n* [#Merge Semantics](#merge-semantics)\n* [#Structural Sharing](#structural-sharing)\n* [#Gotchas](#gotchas)\n* [#Compatibility](#compatibility)\n* [#Changelog](#changelog)\n* [#License](#license)\n\n## Why\n\n### Why not ImmutableJS or something similar?\n\n1. Plain data. Emerge uses plain dicts and lists.\n\n  * Uniform interface to data: read at path, set at path, merge. Just a few functions that work on all structures.\n  * Easy to explore in a REPL.\n  * No need for interop calls.\n  * Complete compatibility with JSON.\n\n2. Size. At the time of writing, ImmutableJS is ≈ 57 KiB minified, unacceptable.\n   Emerge is just a handful of KiB minified.\n\n3. Performance. Emerge is probably about as efficient as this kind of stuff gets.\n\n### Why not SeamlessImmutable?\n\nSI is a popular library for merging and patching dicts and lists. Like Emerge, it sticks with plain JS data structures, and provides similar functions to Emerge.\n\nAt the time of writing, Emerge is _way_ faster, more memory-efficient, and smaller than SI.\n\n## Installation\n\n### Node / Webpack\n\n```sh\nnpm i -E emerge\n```\n\nExample usage:\n\n```js\nimport * as e from 'emerge'\n\ne.put({one: 10}, 'two', 20)\n// {one: 10, two: 20}\n\ne.patch({one: 10}, {two: 20})\n// {one: 10, two: 20}\n\ne.remove({one: 10, two: 20}, 'two')\n// {one: 10}\n\n/* Structural sharing */\n\nconst prev = {one: [10], two: [20]}\n\n// Patched version, keeping as much old structure as possible,\n// even in the face of redundant overrides\nconst next = e.patch(prev, {one: [10], two: 20})\n// {one: [10], two: 20}\n\n// Unchanged values retain their references\nnext.one === prev.one  // true\n```\n\n### Native Browser Modules\n\nEmerge can be used as a native JS module in a browser:\n\n```js\nimport * as e from './node_modules/emerge/emerge.mjs'\n```\n\nCan use a CDN:\n\n```js\nimport * as e from 'https://cdn.jsdelivr.net/npm/emerge@0.5.1/emerge.mjs'\n```\n\n## API\n\nAll examples on this page imply an import:\n\n```js\nimport * as e from 'emerge'\n```\n\n### `put(prev, key, value)`\n\nSimilar to [`clojure.core/assoc`](https://clojuredocs.org/clojure.core/assoc).\n\nReturns a data structure with `value` set at the given `key`. Works on dicts and lists. Also accepts `null` and `undefined`, treating them as `{}`. Rejects other operands.\n\nUses [#structural sharing](#structural-sharing), may return the original input.\n\n```js\n/* Dicts */\n\ne.put({}, 'one', 10)\n// {one: 10}\n\ne.put({one: 10}, 'two', 20)\n// {one: 10, two: 20}\n\n/* Lists */\n\ne.put([], 0, 'one')\n// ['one']\n\ne.put(['one'], 10, 'two')\n// ['one', 'two']\n\n/* Structural sharing */\n\nconst prev = {one: [10], two: [20]}\n\ne.put(prev, 'two', [20]) === prev\ne.put(prev, 'two', 20).one === prev.one\n```\n\nWhen putting into a list, the key must be an integer index within bounds, otherwise this produces an exception.\n\n### `putIn(prev, path, value)`\n\nSimilar to [`clojure.core/assoc-in`](https://clojuredocs.org/clojure.core/assoc-in). Like [#`put`](#putprev-key-value), but updates at a nested `path` rather than one key. Uses [#structural sharing](#structural-sharing), may return the original input.\n\nWhen `path` is `[]`:\n\n* If `prev` is a primitive, returns `value` as-is, even if `value` is not a data structure.\n* If `prev` is a structure, performs a `put`-style deduplication, updating `prev` with the contents of `value` while preserving as many references as possible.\n\nOtherwise, uses exactly the same rules as `put`:\n\n* Works for nested dicts and lists.\n* Creates nested dicts as needed.\n* Accepts `null` and `undefined`, treating them as `{}`.\n* When called with a non-empty path, rejects inputs other than `null`, `undefined`, a list, or a dict.\n\n```js\n/* Dicts */\n\ne.putIn({}, ['one'], 10)\n// {one: 10}\n\ne.putIn({one: 10}, ['one', 'two'], 20)\n// {one: {two: 20}}\n\ne.putIn(undefined, ['one'], 10)\n// {one: 10}\n\n/* Lists */\n\ne.putIn([], [0], 'one')\n// ['one']\n\ne.putIn(['one', 'two'], [10], 'three')\n// ['one', 'three']\n\n/* Mixed */\n\ne.putIn({one: [{two: 20}]}, ['one', 0, 'three'], 30)\n// {one: [{two: 20, three: 30}]}\n\n/* Structural sharing */\n\nconst prev = {one: [10], two: [20]}\n\ne.putIn(prev, [], {one: [10], two: [20]}) === prev\ne.putIn(prev, ['one'], [10]) === prev\ne.putIn(prev, ['two'], 20).one === prev.one\n```\n\n### `putBy(prev, key, fun, ...args)`\n\nwhere `fun: ƒ(prevValue, ...args)`\n\nSimilar to [#`put`](#putprev-key-value), but takes a function and calls it with the previous value at the given key, passing the additional arguments, to produce the new value. Can be combined with other Emerge functions like [#`patch`](#patchdicts) for great effect.\n\nUses [#structural sharing](#structural-sharing), may return the original input.\n\n```js\ne.putBy({one: {two: 20}}, 'one', e.patch, {three: 30})\n// {one: {two: 20, three: 30}}\n```\n\n### `putInBy(prev, path, fun, ...args)`\n\nwhere `fun: ƒ(prevValue, ...args)`\n\nSimilar to [#`putIn`](#putinprev-path-value) and [#`putBy`](#putbyprev-key-fun-args). Takes a function and calls it with the previous value at the given path, passing the additional arguments, to produce the new value. Can be combined with other Emerge functions like [#`patch`](#patchdicts) for great effect. See `putIn` for the rules and examples.\n\nUses [#structural sharing](#structural-sharing), may return the original input.\n\n```js\ne.putInBy({one: {two: {three: 30}}}, ['one', 'two'], e.patch, {four: 4})\n// {one: {two: {three: 30, four: 4}}}\n```\n\n### `patch(...dicts)`\n\nTakes any number of dicts and combines their properties. Ignores `null` and `undefined` inputs. Always produces a dict. Rejects other non-dict inputs.\n\nUses [#structural sharing](#structural-sharing), may return the original input.\n\n```js\ne.patch()\n// {}\n\ne.patch({one: 10}, {two: 20}, {three: 30})\n// {one: 10, two: 20, three: 30}\n\n// Ignores null and undefined operands\ne.patch({one: 10}, undefined)\n// {one: 10}\n\n// Combines only at the top level\ne.patch({one: {two: 20}}, {one: {three: 30}})\n// {one: {three: 30}}\n\n/* Structural sharing */\n\nconst prev = {one: [10], two: [20]}\n\ne.patch(prev) === prev\ne.patch(prev, {}) === prev\ne.patch(prev, {one: [10]}) === prev\ne.patch(prev, {one: [10], two: [20]}) === prev\ne.patch(prev, {two: 200}).one === prev.one\n```\n\n### `merge(...dicts)`\n\nSame as [#`patch`](#patchdicts), but combines dicts at any depth:\n\nUses [#structural sharing](#structural-sharing), may return the original input.\n\n```js\ne.merge({one: {two: 20}}, {one: {three: 30}})\n// {one: {two: 20, three: 30}}\n```\n\n### `insert(list, index, value)`\n\nReturns a version of `list` with `value` inserted at the given `index`. Index must be a natural number within the list's bounds + 1, which allows to insert or append elements. Going outside these bounds or providing an invalid index produces an exception.\n\nAccepts `null` and `undefined`, treating them as `[]`. Rejects other operands.\n\nUses [#structural sharing](#structural-sharing), but never returns the original input because it always adds a new element. To update an existing element, use [#`put`](#putprev-key-value).\n\n```js\ne.insert(undefined, 0, 'one')\n// ['one']\n\ne.insert([], 0, 'one')\n// ['one']\n\ne.insert(['one'], 1, 'two')\n// ['one', 'two']\n\ne.insert(['one', 'two'], 0, 'three')\n// ['three', 'one', 'two']\n```\n\n### `remove(value, key)`\n\nReturns a version of `value` with the element at `key` removed. Works on dicts and lists. Accepts `null` and `undefined`, treating them as `{}`. Rejects other operands.\n\nWhen `value` is a list, `key` must be an integer. Non-natural numbers such as `1.1` or `-1` are ok and are simply ignored without removing an element.\n\nUses [#structural sharing](#structural-sharing), may return the original input.\n\n```js\n/* Dicts */\n\ne.remove({one: 10, two: 20}, 'two')\n// {one: 10}\n\ne.remove({one: 10, two: 20}, 'three')\n// {one: 10, two: 20}\n\n/* Lists */\n\ne.remove(['one', 'two', 'three'], 0)\n// ['two', 'three']\n\ne.remove(['one', 'two', 'three'], 1)\n// ['one', 'three']\n\ne.remove(['one', 'two', 'three'], -1)\n// ['one', 'two', 'three']\n\n/* Structural sharing */\n\nconst prev = {one: [10], two: [20]}\n\ne.remove(prev, 'three') === prev\n```\n\n### `removeIn(value, path)`\n\nLike [#`remove`](#removevalue-key), but removes at a nested `path` rather than one key.\n\nWhen `path` is `[]`, returns `undefined`. Accepts `null` and `undefined`, treating them as `{}`. Rejects other operands.\n\nUses [#structural sharing](#structural-sharing), may return the original input.\n\n```js\n/* Dicts */\n\ne.removeIn({one: 10, two: 20}, [])\n// undefined\n\ne.removeIn({one: 10, two: 20}, ['two'])\n// {one: 10}\n\ne.removeIn({one: 10, two: 20}, ['three'])\n// {one: 10, two: 20}\n\n/* Lists */\n\ne.removeIn(['one', 'two', 'three'], [])\n// undefined\n\ne.removeIn(['one', 'two', 'three'], [0])\n// ['two', 'three']\n\ne.removeIn(['one', 'two', 'three'], [1])\n// ['one', 'three']\n\ne.removeIn(['one', 'two', 'three'], [-1])\n// ['one', 'two', 'three']\n\n/* Mixed */\n\ne.removeIn({one: [10], two: [20]}, ['two', 0])\n// {one: [10], two: []}\n\n/* Structural sharing */\n\nconst prev = {one: [10], two: [20]}\n\ne.removeIn(prev, ['three']) === prev\n```\n\n### `is(one, other)`\n\n[_SameValueZero_](https://www.ecma-international.org/ecma-262/6.0/#sec-samevaluezero) as defined by the language spec. Same as `===`, but considers `NaN` equal to `NaN`.\n\nNote that [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) implements [_SameValue_](https://www.ecma-international.org/ecma-262/6.0/#sec-samevalue), which treats `-0` and `+0` as _distinct values_. This is typically undesirable.\n\n`is` should be preferred over `===` or `Object.is`. Used internally in Emerge for all identity tests.\n\n```js\ne.is(NaN, NaN)  // true\ne.is(10,  10)   // true\ne.is(10,  '10') // false\n```\n\n### `equal(one, other)`\n\nTrue if the inputs are equal by _value_ rather than by identity. Ignores prototypes and non-enumerable properties.\n\nEdge case [#gotcha](#gotchas): ignores symbol keys.\n\n```js\nconst prev = {one: NaN, two: [20]}\nconst next = {one: NaN, two: [20]}\n\ne.equal(prev, next)  // true\n```\n\n### `equalBy(one, other, fun)`\n\nwhere `fun: ƒ(oneValue, otherValue)`\n\nCustomisable equality. Uses `fun` to compare properties of lists and dicts, and `is` to compare other values. Not recursive by itself, but `fun` may invoke `equalBy` to implement a recursive algorithm.\n\nEdge case [#gotcha](#gotchas): ignores symbol keys.\n\n```js\n// Shallow equality\ne.equalBy({one: 10},   {one: 10},   e.is) // true\ne.equalBy({list: []}, {list: []}, e.is) // false\n\n// Deep equality: `e.equal` is just a recursive `e.equalBy`\nfunction equal(one, other) {\n  return e.equalBy(one, other, e.equal)\n}\n\n// Add support for arbitrary types\nfunction myEqual(one, other) {\n  return isDate(one)\n    ? isDate(other) \u0026\u0026 one.valueOf() === other.valueOf()\n    : e.equalBy(one, other, myEqual)\n}\n\nfunction isDate(value) {\n  return value instanceof Date\n}\n```\n\n### `get(value, key)`\n\nReads property `key` on `value`. Unlike dot or bracket notation, safe to use on `null` or `undefined` values.\n\n```js\ne.get(null, 'one')\n// undefined\n\ne.get({one: 10}, 'one')\n// 10\n```\n\n### `getIn(value, path)`\n\nLike `get`, but takes a list of keys and reads a nested property at that path. If unreachable, returns `undefined`.\n\n```js\ne.getIn({one: {two: 20}}, ['one', 'two'])\n// 20\n```\n\n### `scan(value, ...path)`\n\nLike `getIn`, but the path is formed by multiple arguments after the first.\n\n```js\ne.scan({one: {two: 20}}, 'one', 'two')\n// 20\n```\n\n## Merge Semantics\n\nWhen creating new structures, Emerge follows a few special rules:\n\n* Passing `null` and `undefined` as the first operand to \"update\" functions is the same as passing `{}`.\n* _Non-values_ (see below) are treated atomically: included or replaced wholesale.\n* Uses structural sharing (see below).\n\nEmerge differentiates between _values_ (data) and _references_ (non-data). The\nfollowing types are considered values:\n\n* Primitives (`null`, `undefined`, numbers, strings, booleans, symbols).\n* Lists (`[]` or `new Array`).\n* Plain dicts (`{}`, `new Object`, `Object.create(null)`).\n\nThe rest are considered references:\n\n* Functions.\n* Non-plain objects (`new class {}`, `Object.create({})`).\n\nReferences are considered outside the scope of Emerge, and treated atomically. Emerge includes and replaces them wholesale. This lets you use Emerge for data structures with non-data mixed in.\n\nEmerge doesn't use `Object.freeze`. If you're consciously treating your data as immutable, you don't need this straight jacket. `Object.freeze` requires the library to choose between inconvenience (\"mutating\" the incoming data by freezing it), an inconsistent API (sometimes returning the mutable input), or a **massive** performance penalty by always copying any mutable input. Emerge rejects the false trilemma, choosing simplicity, performance, and freedom. As a nice side effect, data structures produced by Emerge are 100% vanilla and can be passed to any 3d party API, even one that mutates its inputs.\n\n## Structural Sharing\n\nThe concept is also known as \"\u003ca href=\"https://en.wikipedia.org/wiki/Persistent_data_structure\" target=\"_blank\"\u003epersistent data structures\u003c/a\u003e\".\n\nWhen creating data structures, Emerge attempts to preserve as many unmodified references as possible, even returning the original if the result would be [#`equal`](#equalone-other). This conserves memory and makes subsequent equality tests much cheaper.\n\nBeware of the difference between a missing property, `null`, and `undefined`. The following structures would be considered different by Emerge:\n\n* `{}`\n* `{value: null}`\n* `{value: undefined}`\n\nTo minimize gotchas, I recommend avoiding `null` in your code.\n\n## Gotchas\n\n[#`equal`](#equalone-other) and [#`equalBy`](#equalbyone-other-fun) ignore symbol keys in dicts. Dicts with different sets of symbol keys may be considered equal:\n\n```js\ne.equal({}, {[Symbol.for('one')]})\n// true\n```\n\nBecause equality is used to determine whether to replace or keep old references, Emerge currently doesn't allow symbol keys in \"update\" functions, rejecting them with an exception. This helps to minimize gotchas.\n\n## Compatibility\n\nVersions `\u003e= 0.5.0` **require ES2015+**. Versions `\u003c= 0.5.0` work in **ES5** (IE9+).\n\n## Changelog\n\n### 0.5.1\n\nAdded missing `\"type\": \"module\"` to `package.json`.\n\nReduced unminified size from ≈10.4 to ≈8.3 KiB (moved implementation notes to another file, shortened some function names).\n\n### 0.5.0\n\nBreaking changes: explicit property removal, only ES2015.\n\nMajor changes:\n\n* Setting properties to `null` or `undefined` no longer removes them. Call `remove` or `removeIn` to explicitly remove a property.\n* Renamed `insertAtIndex` → `insert`.\n* Renamed `removeAtIndex` → `remove`, now supports dicts.\n* Added `removeIn`.\n* Only ES2015 source code.\n\nMinor changes:\n\n* Stricter key validation: reject keys that aren't strings or numbers to minimize gotchas. Symbol keys are rejected because Emerge's equality tests completely ignore them; allowing them in updates could have lead to gotchas.\n* Relicensed under the Unlicense: https://unlicense.org.\n\n**Warning**: Emerge no longer provides ES5 code. If you wish to use newer versions with older browsers (IE9 and its ilk), you may have to configure your build system to transpile Emerge, alongside your application's code.\n\n### 0.4.0\n\nBreaking change: stricter input validation.\n\n* `patch` and `merge` now accept only `null`,  `undefined`, and dicts.\n\n* In other update functions, the first argument must be `null`, `undefined`, a list, or a dict.\n\n* Other inputs are rejected with an exception.\n\nIn earlier versions, the following code \"worked\":\n\n```js\ne.patch('not dict', {key: 'value'})\n// {key: 'value'}\n\ne.patch(['not dict'], {key: 'value'})\n// {key: 'value'}\n```\n\nStarting with 0.4.0, it fails loudly rather than silently:\n\n```js\ne.patch('not dict', {key: 'value'})\n// Error: Expected \"not dict\" to satisfy test isDict\n\ne.patch(['not dict'], {key: 'value'})\n// Error: Expected [\"not dict\"] to satisfy test isDict\n```\n\nThis should help catch silly errors, and should not affect well-written code.\n\n### 0.3.0\n\nLists now must be arrays. Other types of lists, such as `arguments` and DOM lists, don't count.\n\n  * `getIn`, `putIn`, and `putInBy` require the path to be an array\n  * `arguments` is considered a plain dict\n  * other non-array lists, such as DOM lists, are considered non-data\n\nThis simplifies the code. Whether it simplifies the mental assumptions depends on how you think about `arguments`. I found that it's better to avoid mixing it with your data to prevent gotchas. This change should help catch the `arguments` gotchas early.\n\n### 0.2.0\n\nPerformance improvements, simplified internals, and a breaking API cleanup that's been brewing for a long time. Through convergent evolution, the API is now closer than ever to the data functions in `clojure.core`.\n\n* `put` now inserts value by key; faster and more convenient than `putIn` or `patch` for single properties\n* `patch` and `merge` now operate only on dicts and always return a dict\n* `patch` and `merge` now accept any number of arguments\n* removed `patchDicts` and `mergeDicts`; `patch` and `merge` fully replace them and are now 2-3x faster for small dicts and argument counts\n* list operations in `put` and `putIn` are stricter: attempting to update an out-of-bounds index or add a non-index property produces an exception\n* added list-only operations: `insertAtIndex` and `removeAtIndex`\n* because `put` now acts on a single property by key, `putBy` takes a function to be applied to that property; the old `putBy` functionality has been removed\n* `patchBy` has been removed\n* `putBy` and `equalBy` now accept the operator function as the _last_ argument; this is consistent with `putInBy`, easier to remember (mnemonic: operator comes last), lets us accept additional arguments, and is more convenient with lambdas\n* `putInBy` is now limited to 10 additional arguments for the operator\n* `putBy` now accepts up to 10 additional arguments for the operator, like `putInBy`\n\n#### Migration guide for 0.1.2 → 0.2.0\n\n* replace `patchDicts` → `patch`\n* replace `mergeDicts` → `merge`\n* make sure `patch` and `merge` are only used for dicts\n* replace `patchIn(root, path, val)` → `putInBy(root, path, patch, val)` or define your own `patchIn`\n* replace `mergeIn(root, path, val)` → `putInBy(root, path, merge, val)` or define your own `mergeIn`\n* old `putBy` and `patchBy` functionality has been removed; `putBy` now takes a key and applies the operator to the property at that key\n* `getAt` has been removed; if you actually need it, copy the one-liner from the `0.1.2` source\n* change the argument order from `putBy(fun, val)` to `putBy(val, fun)`\n* change the argument order from `equalBy(fun, one, other)` to `equalBy(one, other, fun)`\n* make sure `putBy` and `putInBy` are called with no more than 10 additional arguments; if you need more, copy the one-liners from the source and modify them\n* for `patch` calls where the next value has just one property, use `put`\n* for `putIn` calls with a single-key path, use `put`\n* for `putInBy` calls with a single-key path, use `putBy`\n\n## License\n\nhttps://unlicense.org\n\n## Misc\n\nI'm receptive to suggestions. If this library _almost_ satisfies you but needs changes, open an issue or chat me up. Contacts: https://mitranim.com/#contacts\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmitranim%2Femerge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmitranim%2Femerge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmitranim%2Femerge/lists"}