{"id":26022813,"url":"https://github.com/rinaldo/optix","last_synced_at":"2025-03-06T10:37:01.771Z","repository":{"id":57315904,"uuid":"341425337","full_name":"Rinaldo/optix","owner":"Rinaldo","description":"Optix is a data manipulation library that can focus on one or many elements in a nested structure to get or set their values. Optix features robust Typescript support and is smaller and faster than true optics libraries.","archived":false,"fork":false,"pushed_at":"2021-05-10T03:18:52.000Z","size":324,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-05-27T23:50:07.461Z","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}},"created_at":"2021-02-23T04:16:05.000Z","updated_at":"2021-06-14T21:34:35.000Z","dependencies_parsed_at":"2022-08-25T22:40:51.167Z","dependency_job_id":null,"html_url":"https://github.com/Rinaldo/optix","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rinaldo%2Foptix","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rinaldo%2Foptix/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rinaldo%2Foptix/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rinaldo%2Foptix/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Rinaldo","download_url":"https://codeload.github.com/Rinaldo/optix/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242194174,"owners_count":20087523,"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.018Z","updated_at":"2025-03-06T10:37:01.754Z","avatar_url":"https://github.com/Rinaldo.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Optix\n\n`npm install optix`\n\nOptix is a data manipulation library that can focus on one or many elements in a nested structure to get or set their values. Optix features robust Typescript support and is smaller and faster than true optics libraries.\n\n- **Simple yet powerful**: optics-like capabilities with a simple, intuitive syntax\n- **Type-safe**: Robust type checking with minimal type annotations\n- **Tiny**: \u003c 1kb gzipped, zero dependencies\n\n## Usage\n\nLet's say we have the following data structure\n```js\nimport { get, set, find, filter, remove, all } from 'optix'\n\nconst state1 = {\n    title: 'Introduction',\n    steps: [\n        { title: 'Introduce get', completed: false },\n        { title: 'Introduce set', completed: false },\n        { title: 'Introduce find', completed: false },\n        { title: 'Introduce filter', completed: false },\n        { title: 'Introduce remove', completed: false },\n        { title: 'Introduce all', completed: false },\n    ]\n}\n```\n\nWe can focus on the title of the first step and `get` its value\n```js\nget('steps', 0, 'title')(state1)\n// 'Introduce get'\n```\nWe can focus on the completed key of the first two steps and `set` them both to true\n```js\nconst state2 = set('steps', [0, 1], 'completed')(true)(state1)\n// {\n//     title: 'Introduction',\n//     steps: [\n//         { title: 'Introduce get', completed: true },\n//         { title: 'Introduce set', completed: true },\n//         { title: 'Introduce find', completed: false },\n//         { title: 'Introduce filter', completed: false },\n//         { title: 'Introduce remove', completed: false },\n//         { title: 'Introduce all', completed: false },\n//     ]\n// }\n```\nWe can `find` the index of a step to focus on and `set` it to be completed\n```js\nconst state3 = set('steps', find(step =\u003e step.title === 'Introduce find'), 'completed')(true)(state2)\n// {\n//     title: 'Introduction',\n//     steps: [\n//         { title: 'Introduce get', completed: true },\n//         { title: 'Introduce set', completed: true },\n//         { title: 'Introduce find', completed: true },\n//         { title: 'Introduce filter', completed: false },\n//         { title: 'Introduce remove', completed: false },\n//         { title: 'Introduce all', completed: false },\n//     ]\n// }\n```\nWe can focus on the incomplete steps with a `filter` and `get` their titles\n```js\nget('steps', filter(step =\u003e !step.completed), 'title')\n// ['Introduce filter', 'Introduce remove', 'Introduce all']\n```\nWe can focus on `all` the steps and `remove` their completed keys\n```js\nconst state4 = remove('steps', all, 'completed')(state3)\n// {\n//     title: 'Introduction',\n//     steps: [\n//         { title: 'Introduce get' },\n//         { title: 'Introduce set' },\n//         { title: 'Introduce find' },\n//         { title: 'Introduce filter' },\n//         { title: 'Introduce remove' },\n//         { title: 'Introduce all' },\n//     ]\n// }\n```\nWe can even find and filter items in maps/records\n```js\nimport { get, findByVal, filterByVal } from 'optix'\n\nconst state = {\n    users: {\n        alice: { name: 'Alice', age: 22 },\n        bob: { name: 'Bob', age: 33 },\n        claire: { name: 'Claire', age: 44 },\n    }\n}\n\nget('users', findByVal(user =\u003e user.name.startsWith('C')))(state)\n// { name: 'Claire', age: 44 }\n\nget('users', filterByVal(user =\u003e user.age \u003c 40))(state)\n// [{ name: 'Alice', age: 22 }, { name: 'Bob', age: 33 }]\n```\n\n\n## API Reference\n\n### Main Functions\n\nThe main functions all take any number of PathItems to form a Path. Each PathItem can focus on one or many elements in an object or array.\n```typescript\ntype GetKey = (map: { [key: string]: any }) =\u003e string | undefined\ntype GetKeys = (map: { [key: string]: any }) =\u003e string[]\ntype GetIndex = (arr: any[]) =\u003e number | undefined\ntype GetIndexes = (arr: any[]) =\u003e number[]\n\ntype PathItem = string | number | string[] | number[] | GetKey | GetIndex | GetKeys | GetIndexes\n\ntype Path = PathItem[]\n```\n*All updates are performed immutably*\n\n**get**\n\n`path =\u003e object =\u003e valueAtPath`\n```js\nget('foo', 'bar')({ foo: { bar: 'baz' } })\n// 'baz'\n\nget('letters', [0, 1])({ letters: ['a', 'b', 'c'] })\n// ['a', 'b']\n\nget('letters', arr =\u003e arr.length - 1)({ letters: ['a', 'b', 'c'] })\n// 'c'\n```\n\n**set**\n\n`path =\u003e newValueAtPath =\u003e object =\u003e updatedObject`\n```js\nset('foo', 'bar')('BAZ')({ foo: { bar: 'baz' } })\n// { foo: { bar: 'BAZ' } }\n\nset('letters', [0, 1])('z')({ letters: ['a', 'b', 'c'] })\n// { letters: ['z', 'z', 'c'] }\n\nset('letters', arr =\u003e arr.length - 1)('z')({ letters: ['a', 'b', 'c'] })\n// { letters: ['a', 'b', 'z'] }\n```\n\n**update**\n\n`path =\u003e updaterAtPath =\u003e object =\u003e updatedObject`\n```js\nconst toUpper = str =\u003e str.toUpperCase()\n\nupdate('foo', 'bar')(toUpper)({ foo: { bar: 'baz' } })\n// { foo: { bar: 'BAZ' } }\n\nupdate('letters', [0, 1])(toUpper)({ letters: ['a', 'b', 'c'] })\n// { letters: ['A', 'B', 'c'] }\n\nupdate('letters', arr =\u003e arr.length - 1)(toUpper)({ letters: ['a', 'b', 'c'] })\n// { letters: ['a', 'b', 'C'] }\n```\n\n**remove**\n\n`path =\u003e object =\u003e updatedObject`\n```js\nremove('foo', 'bar')({ foo: { bar: 'baz' } })\n// { foo: {} }\n\nremove('letters', [0, 1])({ letters: ['a', 'b', 'c'] })\n// { letters: ['c'] }\n\nremove('letters', arr =\u003e arr.length - 1)({ letters: ['a', 'b', 'c'] })\n// { letters: ['a', 'b'] }\n```\n\n### Array query helpers\n\nOptix provides helper functions that can be used within paths to find or filter items in an array\n```js\nimport { get, all, filter, find, last } from 'optix'\n\nconst arrayHelpers = [\n    { name: 'all', type: 'traversal' },\n    { name: 'filter', type: 'prism' },\n    { name: 'find', type: 'lens' },\n    { name: 'last', type: 'lens' },\n]\n```\n**all** - *focus on all items in the array*\n\n`array =\u003e index`\n```js\nget(all, 'name')(arrayHelpers)\n// ['all', 'filter', 'find', 'last']\n```\n\n**filter** - *focus on all items that match the predicate*\n\n`predicate =\u003e array =\u003e indexes`\n```js\nget(filter(helper =\u003e helper.type === 'prism'), 'name')(arrayHelpers)\n// ['filter']\n```\n\n**find** - *focus on the first item that matches the predicate*\n\n`predicate =\u003e array =\u003e index`\n```js\nget(find(helper =\u003e helper.type === 'lens'), 'name')(arrayHelpers)\n// 'find'\n```\n\n**last** - *focus on the last item in the array*\n\n`array =\u003e index`\n```js\nget(last, 'name')(arrayHelpers)\n// 'last'\n```\n\n### Record query helpers\nOptix provides helper functions that can be used within paths to find or filter items in a record/map\n```js\nimport { get, filterByKey, filterByVal, findByKey, findByVal, keys } from 'optix'\n\nconst recordHelpers = {\n    filterByKey: { description: 'Filter By Key', type: 'prism', predicate: 'key' },\n    filterByVal: { description: 'Filter By Value', type: 'prism', predicate: 'value' },\n    findByKey: { description: 'Find By Key', type: 'lens', predicate: 'key' },\n    findByVal: { description: 'Find By Value', type: 'lens', predicate: 'value' },\n    keys: { description: 'All Keys', type: 'traversal' },\n}\n```\n\n**filterByKey** - *focus on all items that match the predicate*\n\n`predicate =\u003e record =\u003e keys`\n```js\nget(filterByKey(key =\u003e key.startsWith('filterBy')), 'description')(recordHelpers)\n// ['Filter By Key', 'Filter By Value']\n```\n\n**filterByVal** - *focus on all items that match the predicate*\n\n`predicate =\u003e record =\u003e keys`\n```js\nget(filterByVal(val =\u003e val.type === 'prism'), 'description')(recordHelpers)\n// ['Filter By Key', 'Filter By Value']\n```\n\n**findByKey** - *focus on the first item that matches the predicate*\n\n`predicate =\u003e record =\u003e key`\n```js\nget(findByKey(key =\u003e key.startsWith('findBy')), 'description')(recordHelpers)\n// 'Find By Key'\n```\n\n**findByVal** - *focus on the first item that matches the predicate*\n\n`predicate =\u003e record =\u003e key`\n```js\nget(findByVal(val =\u003e val.type === 'lens' \u0026\u0026 val.predicate === 'value'), 'description')(recordHelpers)\n// 'Find By Value'\n```\n\n**keys** - *focus on all items in the object*\n\n`record =\u003e key`\n```js\nget(keys, 'description')(recordHelpers)\n// ['Filter By Key', 'Filter By Value', 'Find By Key', 'Find By Value', 'All Keys']\n```\n\n### Creating custom query helpers\n\nCustom query helpers are easy to make, they just need to return one or more keys or indexes. Lodash's [findIndex](https://lodash.com/docs/4.17.15#findIndex) and [findKey](https://lodash.com/docs/4.17.15#findKey) can be used as drop-in replacements for `find` and `findByVal` respectively.\n\n### Invalid keys\n\nKeys must be strings or non-negative numbers. Operations will halted if an invalid key is found.\n```js\n// find will return -1 since Dave does not exist in the array\n// a shallow copy of the array will be returned, no element will be modified\nset(find(person =\u003e person.name === 'Dave'), 'name')('David')([{ name: 'Alice' }, { name: 'Bob' }])\n// [{ name: 'Alice' }, { name: 'Bob' }]\n```\n\n### TypeScript Notes\n\n`get` does not require any typings\n```typescript\nconst getFooCompleted = get('foo', 'completed') // expect type { foo: { completed: unknown } }\nconst bool = getFooCompleted({ foo: { completed: false } }) // return type is boolean\n// false\n```\n\n`set` does not require any typings\n\n*if the new value is missing keys the return type will be unknown*\n```typescript\nconst setFooCompleted = set('foo', 'completed')(true) // expect type { foo: { completed: boolean } }\nconst completedFoo = setFooCompleted({ foo: { completed: false } }) // return type same as final argument type\n// { foo: { completed: true } }\n```\n\n`update` requires the updater to be typed\n\n*if the new value is missing keys the return type will be unknown*\n```typescript\nconst obj = { foo: { completed: false } }\nconst toggleFooCompleted1 = update('foo', 'completed')((bool: boolean) =\u003e !bool) // expect type { foo: { completed: boolean } }\nconst toggleFooCompleted2 = update('foo', 'completed')\u003cboolean\u003e((bool) =\u003e !bool) // same as above\nconst completedFoo = toggleFooCompleted1({ foo: { completed: false } }) // return type same as final argument type\n// { foo: { completed: true } }\n```\n\n`remove` does not require explicit typings\n\n*if the path targets a required key the return type will be unknown*\n```typescript\nconst foo: { foo: { completed?: boolean } } = { foo: { completed: false } }\n\nconst removeFooCompleted = remove('foo', 'completed') // expect type { foo: { completed: unknown } }\nconst fooWithoutCompleted = removeFooCompleted(foo) // return type same as final argument type\n// { foo: {} }\n```\n\n**Polymorphism**\n\nOptix provides aliased versions of the main functions with typings that support polymorphism\n\n```typescript\nimport { polySet, polyUpdate, polyRemove } from 'optix'\n\n// changing boolean to string\npolySet('foo')('bar')({ foo: true }) // return type { foo: string }\n// { foo: 'bar' }\n\n// removing required key\npolyRemove('foo')({ foo: true, bar: true, baz: true }) // return type { bar: boolean; baz: boolean }\n// { bar: true, baz: true }\n```\n\n**Query Helpers**\n\nThe `find*` and `filter*` helpers can be typed with either a type argument or by typing the callback\n```typescript\ninterface User {\n    name: string\n    id: string\n}\n\ninterface State {\n    list: User[]\n    map: { [id: string]: User }\n}\n\nconst state: State = {\n    list: [{ name: 'Alice', id: 'A123' }, { name: 'Bob', id: 'B234' }, { name: 'Claire', id: 'C345' }],\n    map: {\n        A123: { name: 'Alice', id: 'A123' },\n        B234: { name: 'Bob', id: 'B234' },\n        C345: { name: 'Claire', id: 'C345' }\n    }\n}\n\nget('list', find\u003ctypeof State.list\u003e((user) =\u003e user.name === 'Alice'))(state)\nget('list', find((user: User) =\u003e user.name === 'Alice'))(state)\n\nget('map', findByVal\u003ctypeof State.map\u003e((user) =\u003e user.name === 'Alice'))(state)\nget('map', findByVal((user: User) =\u003e user.name === 'Alice'))(state)\n```\n\n\n## Alternate data-first syntax\nIf you want a data-first, lodash-like syntax there's a set of functions just for you!\n\n*these functions do not have full TypeScript support*\n```js\nimport { _get, _set, _update, _delete } from 'optix'\n\n_get({ foo: { bar: 'baz' } }, ['foo', 'bar'])\n// 'baz'\n\n_set({ foo: { bar: 'baz' } }, ['foo', 'bar'], 'BAZ')\n// { foo: { bar: 'BAZ' } }\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frinaldo%2Foptix","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frinaldo%2Foptix","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frinaldo%2Foptix/lists"}