{"id":26022815,"url":"https://github.com/rinaldo/evolve-ts","last_synced_at":"2025-06-25T13:04:15.881Z","repository":{"id":142811562,"uuid":"374171164","full_name":"Rinaldo/evolve-ts","owner":"Rinaldo","description":"Immutably update nested objects with patches containing new values or functions to update values","archived":false,"fork":false,"pushed_at":"2023-03-29T22:00:47.000Z","size":245,"stargazers_count":15,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-25T13:04:11.450Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/Rinaldo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","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":"2021-06-05T17:13:00.000Z","updated_at":"2024-05-28T17:18:58.000Z","dependencies_parsed_at":"2023-08-18T04:02:32.888Z","dependency_job_id":null,"html_url":"https://github.com/Rinaldo/evolve-ts","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/Rinaldo/evolve-ts","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rinaldo%2Fevolve-ts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rinaldo%2Fevolve-ts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rinaldo%2Fevolve-ts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rinaldo%2Fevolve-ts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Rinaldo","download_url":"https://codeload.github.com/Rinaldo/evolve-ts/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rinaldo%2Fevolve-ts/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261879271,"owners_count":23223736,"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":[],"created_at":"2025-03-06T10:37:01.775Z","updated_at":"2025-06-25T13:04:15.859Z","avatar_url":"https://github.com/Rinaldo.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# evolve-ts\n\n`npm install evolve-ts`\n\nImmutably update nested objects with patches containing values or functions to update values\n\n- **Simple yet powerful**: simple syntax for performing immutable updates\n- **Type-safe**: Robust type checking and type inferences\n- **Tiny**: \u003c 1kb gzipped, zero dependencies\n\n## Usage\n\nLet's say we have the following state\n```javascript\nimport { evolve, unset } from \"evolve-ts\"\n\nconst state = {\n    user: {\n        name: \"Alice\",\n        age: 22\n    }\n}\n```\n\nWe can set a value\n```javascript\nevolve({ user: { name: \"Bob\" } }, state)\n// { user: { name: \"Bob\", age: 22 } }\n```\n\nUpdate a value\n```javascript\nevolve({ user: { age: age =\u003e age + 1 } }, state)\n// { user: { name: \"Alice\", age: 23 } }\n```\n\nRemove a key\n```javascript\nevolve({ user: { age: unset } }, state)\n// { user: { name: \"Alice\" } }\n```\n\nAll together now!\n```javascript\nimport { evolve, unset } from \"evolve-ts\"\n\nconst alice = {\n    name: \"Alice\",\n    age: 22,\n    active: true,\n    likes: [\"wonderland\"],\n    nested: {\n        foo: true,\n        bar: true,\n    }\n}\n\nevolve({\n    name: \"Bob\", // sets the value of name\n    age: age =\u003e age + 1, // updates the value of age\n    active: unset, // removes the active key\n    likes: [\"building\"], // sets the value of likes (only objects are merged)\n    nested: {\n        foo: false // sets the value of foo\n    }\n}, alice)\n/*\n{\n    name: \"Bob\",\n    age: 23,\n    likes: [\"building\"],\n    nested1: {\n        foo: false,\n        bar: true,\n    }\n}\n*/\n```\n\n### Setting object values directly\n\nObject values are merged recursively, but return values of functions are set directly. To replace an object value use a `constant` function.\n\n```javascript\nevolve({\n    user: () =\u003e ({\n        name: \"Bob\",\n        age: 33,\n    })\n}, state)\n\n// { user: { name: \"Bob\", age: 33 } }\n```\n\n### Removing object entries\n\nEntries can be removed by setting the value to `unset` or using an updater function that returns `unset`. \n\n```javascript\nevolve({\n    user: {\n        name: unset,\n        age: () =\u003e unset\n    }\n}, state)\n\n// { user: {} }\n```\n\n### Working with Arrays\n\nevolve-ts provides two functions to update arrays, `map` and `adjust`.\n\n`map` is a version of the map function that can either take a callback or a patch. The following are all equivalent, they all increment the number of likes for each user in the users array.\n```javascript\nimport { map } from \"evolve-ts\"\nconst inc = n =\u003e n + 1\n\nmap({ likes: inc })(users)\n\nmap(evolve({ likes: inc }))(users)\n\nmap(user =\u003e ({ ...user, likes: inc }))(users)\n```\n\n`adjust` conditionally maps values with a callback function or patch. Value(s) to map can be specified with an index or predicate function. Negative indexes are treated as offsets from the array length.\n\n```javascript\nimport { adjust } from \"evolve-ts\"\n\n// set the first user as preferred\nadjust(0, { preferred: true })(users)\n\n// set the last user as preferred\nadjust(-1, { preferred: true })(users)\n\n// set all users who have 100 or more likes as preferred\nadjust(user =\u003e user.likes \u003e 99, { preferred: true })(users)\n\n// like map, can also take a callback to update the value\nadjust(0, user =\u003e ({ ...user, preferred: true }))(users)\n```\n\nOther array helpers such as `append` and `filter` are not re-implemented by evolve-ts as they are already included in [fp-ts](https://www.npmjs.com/package/fp-ts), [Ramda](https://www.npmjs.com/package/ramda), and many other libraries.\n\n```javascript\nimport { adjust, evolve, map } from \"evolve-ts\"\nimport { append, filter, inc } from \"\" // your favorite functional utility library\n\n\nconst state = {\n    users: [\n        { name: \"Alice\", age: 22, id: 0 },\n        { name: \"Bob\", age: 33, id: 1 }\n    ]\n}\n\n// add a new user\nevolve({\n    users: append({ name: \"Claire\", age: 44, id: 2 })\n}, state)\n\n// increment the ages of all users\nevolve({\n    users: map({ age: inc })\n}, state)\n\n// set the age of a user by id\nevolve({\n    users: adjust(user =\u003e user.id === 1, { age: 55 })\n}, state)\n\n// remove a user by id\nevolve({\n    users: filter(user =\u003e user.id !== 1)\n}, state)\n```\n\n## Currying\n\n```javascript\nimport { evolve } from \"evolve-ts\"\n\nconst incrementAge = evolve({ age: age =\u003e age + 1 })\n\nincrementAge({ name: \"Alice\", age: 22 })\n// { name: \"Alice\", age: 23 }\n```\n\n## TypeScript Support\n\nThe `evolve` function is strictly typed and does not allow polymorphism. The return type is always the same as the target type.\n```typescript\nimport { evolve, unset } from \"evolve-ts\"\n\n// cannot change the type of values in target\nevolve({ age: \"22\" }, { name: \"Alice\", age: 22 })\n// TypeError: Type 'string' is not assignable to type 'number | ((value: number) =\u003e number)'\n\n// updaters have correctly inferred types (updater argument in this example is inferred as number)\nevolve({ age: age =\u003e age + 1 }, { name: \"Alice\", age: 22 })\n// ReturnType: { name: string; age: number; }\n\n// unset can be used on optional keys\nevolve({ age: unset }, { name: \"Alice\", age: 22 } as { name: string; age?: number; })\n// ReturnType: { name: string; age?: number; }\n\n// unset cannot be used on required keys\nevolve({ age: unset }, { name: \"Alice\", age: 22 })\n// TypeError: Type 'typeof Unset' is not assignable to type 'number | ((value: number) =\u003e number)'\n\n// cannot add extraneous properties to the patch\nevolve({ name: \"Alice\", age: 23 }, { age: 22 })\n// TypeError: ...'name' does not exist in type 'Patch\u003c{ age: number; }\u003e'\n\n// cannot set non-nullable properties to undefined when exactOptionalPropertyTypes is enabled\nevolve({ name: undefined }, { name: \"Alice\" })\n// Type 'undefined' is not assignable to type 'string | ((value: string) =\u003e string)'\n```\n\nThe `evolve_` function is a type alias for `evolve` that allows polymorphism while still producing strongly typed results.\n```typescript\nimport { evolve_, unset } from \"evolve-ts\"\n\n// changing age from number to string\nevolve_({ age: \"22\" }, { name: \"Alice\", age: 22 })\n// ReturnType: { name: string; age: string; }\n\n// adding name key\nevolve_({ name: \"Alice\", age: 23 }, { age: 22 })\n// ReturnType: { name: string, age: number; }\n\n// adding age key with updater function\nevolve_({ age: (): number =\u003e 22 }, { name: \"Alice\" })\n// ReturnType: { name: string, age: number; }\n\n// removing age key\nevolve_({ age: unset }, { name: \"Alice\", age: 22 })\n// ReturnType: { name: string; }\n```\n\n## Shallow Updates\n\nThe behavior of `shallowEvolve` is similar to the spread operator except it accepts updater functions.\n\n```typescript\nimport { evolve, shallowEvolve } from \"evolve-ts\"\n\ndeclare const user\n\nshallowEvolve({ user }) /* equivalent to */ evolve({ user: () =\u003e user })\n\n// evolve can be used within shallowEvolve if a deep merge is needed for a particular key\nshallowEvolve({ user: evolve(user) }) /* equivalent to */ evolve({ user })\n```\n\n## Provided Functions\n\n- `evolve`: Takes a patch object and a target object and returns a version of the target object with updates from the patch applied. A patch is a subset of the target object containing either values or functions to update values. Functions are called with existing values from the target, non-object values are set into the target, and object values are merged recursively.\n- `evolve_`: Type alias for evolve that allows polymorphism while still producing strongly typed results.\n- `map`: Maps values in an array with a callback function or patch.\n- `adjust`: Conditionally maps values in an array with a callback function or patch. Value(s) to map can be specified with an index or predicate function. Negative indexes are treated as offsets from the array length.\n- `unset`: Sentinel value that causes its key to be removed from the output.\n- `shallowEvolve`: Like `evolve` but performs shallow updates.\n- `shallowMap`: Like `map` but performs shallow updates.\n- `shallowAdjust`: Like `adjust` but performs shallow updates.\n\n\n## Why evolve-ts?\n\nevolve-ts was created as a lightweight alternative to [updeep](https://www.npmjs.com/package/updeep) and [immer](https://www.npmjs.com/package/immer). It has all of updeep's core functionality, strong TypeScript support, is only a fraction of the size of updeep or immer, and is dependency free.\n\n## Caveats\n\nevolve-ts treats all functions in patches as updater functions, so if your state contains function values you want to update you must wrap the updates in a `constant` function.\n\n```typescript\nimport { evolve } from \"evolve-ts\"\n\nconst state = {\n    name: \"Alice\",\n    greet: (person) =\u003e console.log(`Hi ${person} I'm Alice!`)\n}\n// need to use a wrapper to set new function values\nevolve({\n    name: \"Bob\",\n    greet: () =\u003e (person) =\u003e console.log(`Hi ${person} I'm Bob!`)\n}, state)\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frinaldo%2Fevolve-ts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frinaldo%2Fevolve-ts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frinaldo%2Fevolve-ts/lists"}