{"id":13547939,"url":"https://github.com/jdeal/qim","last_synced_at":"2025-04-02T20:31:10.870Z","repository":{"id":69927275,"uuid":"84145514","full_name":"jdeal/qim","owner":"jdeal","description":"Immutable/functional select/update queries for plain JS.","archived":false,"fork":false,"pushed_at":"2019-02-14T01:58:13.000Z","size":385,"stargazers_count":181,"open_issues_count":3,"forks_count":9,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-07T18:03:02.512Z","etag":null,"topics":[],"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/jdeal.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}},"created_at":"2017-03-07T02:35:31.000Z","updated_at":"2024-11-28T16:33:13.000Z","dependencies_parsed_at":"2023-05-29T04:00:12.945Z","dependency_job_id":null,"html_url":"https://github.com/jdeal/qim","commit_stats":{"total_commits":210,"total_committers":5,"mean_commits":42.0,"dds":"0.36190476190476195","last_synced_commit":"6ac76147acf85a28d671c120934047f685059eaf"},"previous_names":[],"tags_count":52,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jdeal%2Fqim","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jdeal%2Fqim/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jdeal%2Fqim/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jdeal%2Fqim/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jdeal","download_url":"https://codeload.github.com/jdeal/qim/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246887941,"owners_count":20850170,"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":"2024-08-01T12:01:03.366Z","updated_at":"2025-04-02T20:31:08.064Z","avatar_url":"https://github.com/jdeal.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# qim\n\nImmutable/functional select/update queries for plain JS.\n\n[![travis](https://travis-ci.org/jdeal/qim.svg?branch=master)](https://travis-ci.org/jdeal/qim)\n\n\u003cimg src=\"media/qim-logo.png\" width=\"300\"\u003e\n\nWARNING: Qim is really useful, but it's still considered somewhat experimental. It might have a few rough edges, and the API might change a little in the future! It's used in production at https://zapier.com though, so feel free to try it out!\n\nQim makes it simple to reach in and modify complex nested JS objects. This is possible with a query path that is just a simple JS array, much like you might use with `set` and `update` from [Lodash](https://lodash.com/), but with a more powerful concept of \"navigators\" (borrowed from [Specter](https://github.com/nathanmarz/specter), a Clojure library). Instead of just string keys, Qim's navigators can act as predicates, wildcards, slices, and other tools. Those same navigators allow you to reach in and select parts of JS objects as well.\n\nQim's updates are immutable, returning new objects, but those objects share any unchanged parts with the original object.\n\nQim's API is curried and data last, so it should fit well with other functional libraries like [Lodash/fp](https://github.com/lodash/lodash/wiki/FP-Guide) and `ramda`.\n\nAnd Qim does its best to stay performant!\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n**Contents**\n\n- [A simple (kind-of-contrived) example](#a-simple-kind-of-contrived-example)\n- [A more complex (not-too-contrived) example](#a-more-complex-not-too-contrived-example)\n- [Installation](#installation)\n- [Usage](#usage)\n- [API](#api)\n  - [`apply(query, transform, object)`](#applyquery-transform-object)\n  - [`find(query, object)`](#findquery-object)\n  - [`has(query, object)`](#hasquery-object)\n  - [`select(query, object)`](#selectquery-object)\n  - [`set(query, value, object)`](#setquery-value-object)\n  - [`update(query, object)`](#updatequery-object)\n- [Navigators](#navigators)\n  - [Built-in, type-based navigators](#built-in-type-based-navigators)\n    - [Key (string/integer)](#key-stringinteger)\n    - [Predicate (function)](#predicate-function)\n    - [Nested (array)](#nested-array)\n    - [Stop (undefined/null)](#stop-undefinednull)\n  - [Named navigators](#named-navigators)\n    - [`$apply(fn)`](#applyfn)\n    - [`$begin`](#begin)\n    - [`$default(value)`](#defaultvalue)\n    - [`$each`](#each)\n    - [`$eachKey`](#eachkey)\n    - [`$eachPair`](#eachpair)\n    - [`$end`](#end)\n    - [`$first`](#first)\n    - [`$last`](#last)\n    - [`$lens(fn, fromFn)`](#lensfn-fromfn)\n    - [`$merge(spec)`](#mergespec)\n    - [`$mergeDeep(spec)`](#mergedeepspec)\n    - [`$nav(path, ...morePaths)`](#navpath-morepaths)\n    - [`$none`](#none)\n    - [`$pick(keys, ...keys)`](#pickkeys-keys)\n    - [`$pushContext(key, (obj, context) =\u003e contextValue)`](#pushcontextkey-obj-context--contextvalue)\n    - [`$set(value)`](#setvalue)\n    - [`$setContext(key, (value, context) =\u003e contextValue)`](#setcontextkey-value-context--contextvalue)\n    - [`$slice(begin, end)`](#slicebegin-end)\n    - [`$traverse({select, update})`](#traverseselect-update)\n- [Custom navigators](#custom-navigators)\n  - [Path navigators](#path-navigators)\n  - [Parameterized path navigators](#parameterized-path-navigators)\n  - [Lens navigators](#lens-navigators)\n  - [Parameterized lens navigators](#parameterized-lens-navigators)\n  - [Core navigators](#core-navigators)\n  - [Parameterized core navigators](#parameterized-core-navigators)\n- [Performance](#performance)\n- [TODO](#todo)\n- [Contributing](#contributing)\n- [Thanks](#thanks)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n## A simple (kind-of-contrived) example\n\nLet's start with some data like this:\n\n```js\nconst state = {\n  users: {\n    joe: {\n      name: {\n        first: 'Joe',\n        last: 'Foo'\n      },\n      other: 'stuff'\n    },\n    mary: {\n      name: {\n        first: 'Mary',\n        last: 'Bar'\n      },\n      other: 'stuff'\n    }\n  },\n  other: 'stuff'\n};\n```\n\nLet's import a couple things from Qim:\n\n```js\nimport {select, $each} from 'qim';\n```\n\nNow let's grab all the first names:\n\n```js\nconst firstNames = select(['users', $each, 'name', 'first'], state);\n```\n\n(We'll explain `$each` a little more later, but you can probably guess: it's like a wildcard.)\n\n`firstNames` now looks like:\n\n```js\n['Joe', 'Mary']\n```\n\nLet's import a couple more things:\n\n```js\nimport {update, $apply} from 'qim';\n```\n\nAnd now we can upper-case all our first names.\n\n```js\nconst newState = update(['users', $each, 'name', 'first',\n  $apply(firstName =\u003e firstName.toUpperCase())\n], state);\n```\n\nNotice we used the same path from our `select` but added an `$apply` to do a transformation. (Again, we'll explain `$apply` better in the next section.)\n\nAfter that, `newState` looks like:\n\n```js\nconst state = {\n  users: {\n    joe: {\n      name: {\n        first: 'JOE',\n        last: 'Foo'\n      },\n      other: 'stuff'\n    },\n    mary: {\n      name: {\n        first: 'MARY',\n        last: 'Bar'\n      },\n      other: 'stuff'\n    }\n  },\n  other: 'stuff'\n};\n```\n\nJust for comparison, let's grab the first names with plain JS:\n\n```js\nconst firstNames = Object.keys(state.users)\n  .map(username =\u003e state.users[username].name.first);\n```\n\nThat's not too bad, but this is a very simple example. The `$each` from Qim makes things a lot more expressive. Let's look at Lodash/fp and Ramda too:\n\n```js\nconst {map, get} from 'lodash/fp';\n\nconst firstName = flow(\n  get('users'),\n  map(get(['name', 'first']))\n)(state);\n```\n\n```js\nimport R from 'ramda';\n\nconst firstName = R.pipe(\n  R.prop('users'),\n  R.values,\n  R.map(R.path(['name', 'first']))\n)(state);\n```\n\nLodash/fp is nice and expressive, but it costs a lot in terms of performance.\n\n| Test           |   Ops/Sec |\n| :------------- | --------: |\n| native         | 2,223,356 |\n| lodash/fp flow |    17,110 |\n| Ramda pipe     |   278,705 |\n| qim select     |   953,949 |\n\nRamda performs a lot better, but it's a little less concise.\n\nQim is slower than native, but it's doing more than the native equivalent, because it's accounting for things like missing keys. And as you'll see soon, it has a _lot_ more expressive power.\n\nThat update in plain JS is a _lot_ more verbose, even for this really simple example:\n\n```js\nconst newState = {\n  ...state,\n  users: Object.keys(state.users)\n    .reduce((users, username) =\u003e {\n      const user = state.users[username];\n      users[username] = {\n        ...user,\n        name: {\n          ...user.name,\n          first: user.name.first.toUpperCase()\n        }\n      };\n      return users;\n    }, {})\n};\n```\n\nSo we go with something like Lodash/fp:\n\n```js\nconst newState = fp.update('users', fp.mapValues(\n  fp.update(['name', 'first'], firstName =\u003e firstName.toUpperCase())\n), state)\n```\n\nOr Ramda:\n\n```js\nR.over(R.lensProp('users'), R.map(\n  R.over(R.lensPath(['name', 'first']), firstName =\u003e firstName.toUpperCase())\n), state)\n```\n\nAgain, performance is going to take a hit for Lodash/fp.\n\n| Test             | Ops/Sec |\n| :--------------- | ------: |\n| native           | 300,219 |\n| lodash/fp update |  16,663 |\n| Ramda update     | 117,961 |\n| qim update       | 176,196 |\n\nRamda is much faster, but we've had to start using lenses. Again, native is the fastest, but at a cost of being awfully unreadable. Qim's main goal isn't to be performant but rather to be expressive. Lodash/fp looks pretty nice, but remember how closely the `update` resembled the `select` with Qim? With Lodash/fp, an update is a different animal. With Ramda, we've had to switch to completely different concepts. As we'll see with a more complex example, Qim will retain its simple, expressive query power for updates while Lodash/fp and Ramda are going to get more complicated.\n\n## A more complex (not-too-contrived) example\n\nLet's start with some data like this:\n\n```js\nconst state = {\n  entity: {\n    account: {\n      100: {\n        owner: 'joe',\n        type: 'savings',\n        balance: 90\n      },\n      200: {\n        owner: 'mary',\n        type: 'savings',\n        balance: 1100\n      },\n      300: {\n        owner: 'bob',\n        type: 'checking',\n        balance: 50\n      }\n    }\n  }\n};\n```\n\nLet's say we want to change our `state` so that for every _savings account_, we:\n\n1. Add 5% interest to any balance \u003e 1000.\n2. Subtract 10 from any balance \u003c 100. Cause fees are how banks make money, right?\n\n(And I know banks should have transactions, yada, yada.)\n\nOkay, drum roll... with Qim, we can do that like this:\n\n```js\nimport {update, $each, $apply} from 'qim';\n\nconst newState = update(['entity', 'account', $each,\n  account =\u003e account.type === 'savings', 'balance',\n  [bal =\u003e bal \u003e= 1000, $apply(bal =\u003e bal * 1.05)],\n  [bal =\u003e bal \u003c 100, $apply(bal =\u003e bal - 10)]\n], state);\n```\n\nEven without any explanation, hopefully you have a rough idea of what's going on. Like we saw in the simple example with `$each` and `$apply`, instead of only accepting an array of strings for a path, Qim's `update` function accepts an array of navigators. Using different types of navigators together creates a rich query path for updating a nested object. We'll look closer at this particular query in a bit, but first let's try the same thing with vanilla JS.\n\n```js\nconst newState = {\n  ...state,\n  entity: {\n    ...state.entity,\n    account: Object.keys(state.entity.account).reduce((result, id) =\u003e {\n      const account = state.entity.account[id];\n      if (account.type === 'savings') {\n        if (account.balance \u003e= 1000) {\n          result[id] = {\n            ...account,\n            balance: account.balance * 1.05\n          };\n          return result;\n        }\n        if (account.balance \u003c 100) {\n          result[id] = {\n            ...account,\n            balance: account.balance - 10\n          };\n          return result;\n        }\n      }\n      result[id] = account;\n      return result;\n    }, {})\n  }\n};\n```\n\nYuck. That is ugly. Lots of references to things we don't really care about. Okay, hopefully nobody writes code like that. Let's use Lodash/fp to clean that up.\n\n```js\nimport fp from 'lodash/fp';\n\nconst newState = fp.update(['entity', 'account'], fp.mapValues(account =\u003e\n  account.type === 'savings' ? (\n    fp.update('balance', fp.cond([\n      [bal =\u003e bal \u003e= 1000, bal =\u003e bal * 1.05],\n      [bal =\u003e bal \u003c 100, bal =\u003e bal - 10],\n      [fp.stubTrue, bal =\u003e bal]\n    ]), account)\n  ) : account\n), state)\n```\n\nOkay, that's a lot more concise, but there are still some problems:\n\n1. We have to return `account` in the case where it's not a savings account. Our original requirement was really to filter out savings accounts and operate on those, but we can't really do that, because we want to modify the whole state. Using a `filter` would strip out the accounts we don't modify.\n2. Similarly, we have to return the balance even if it's not a low or high balance that we want to change.\n3. If we nest deeper _and_ break out of point-free style, it gets pretty awkward to write or read the code. We could clean that up by splitting this into multiple functions, but remember how concise the requirement is vs the resulting code complexity.\n4. If none of our accounts actually match these criteria, we'll still end up with a new state object.\n\nQim boils this down to the essential declarative parts, using an expressive query path, and it avoids unnecessary mutations.\n\nLet's stretch out the previous example to take a closer look at some of the navigators used.\n\n```js\nconst newState = update([\n  // A string navigates to that key in the object.\n  'entity', 'account',\n  // $each is like a wildcard that matches each value of an object or array.\n  $each,\n  // Functions act like predicates and navigate only if it matches.\n  account =\u003e account.type === 'savings',\n  // Another key navigator.\n  'balance',\n  // Arrays are just nested queries and will descend...\n  [\n    // Another predicate to test for a high balance.\n    bal =\u003e bal \u003e= 1000,\n    // $apply is used to transform that part of the object.\n    $apply(bal =\u003e bal * 1.05)\n  ],\n  // ...and then return\n  [\n    // Another predicate to test for a low balance.\n    bal =\u003e bal \u003c 100,\n    // Having the transform function inside the query path allows\n    // us to do multiple transformations on different paths.\n    $apply(bal =\u003e bal - 10)\n  ]\n], state);\n```\n\nBecause navigators are only ever working on the part of the object that you've navigated to, you don't ever have to worry about the parts of the object that you don't touch. Those parts remain intact.\n\nThese modifications are immutable, and they share unmodified branches:\n\n```js\nconsole.log(newState !== state);\n// true\nconsole.log(newState[300] === state[300]);\n// true\n```\n\nChanging something to its current value is a no-op:\n\n```js\nconst newState = update(['entity', 'account', 300, 'type', () =\u003e 'checking'], state);\nconsole.log(newState === state);\n// true\n```\n\nAnd of course these navigators are useful for selecting data too. Instead of modifying an object, the `select` method navigates to each matching part of the query and returns all the matching parts in an array.\n\n```js\nimport {select} from 'qim';\n\nconst names = select(['entity', 'account', $each, 'owner'], state);\n// ['joe', 'mary', 'bob']\n```\n\nLet's get a little more fancy. Let's grab all the usernames of people that have high balances.\n\n```js\nimport {has} from 'qim';\n\n// All functions are curried, so you can leave off the data to get a function.\nconst hasHighBalance = has(['balance', bal =\u003e bal \u003e= 1000]);\n\nconst usernames = select(['entity', 'account', $each, hasHighBalance, 'owner'], state);\n// ['mary']\n```\n\n`has` checks if a selection returns anything. We use currying to create a function for checking if an account balance\nis high, and we use that as a predicate to select the owners with a high balance.\n\nCool, huh?\n\n## Installation\n\n```bash\nnpm install qim --save\n```\n\n## Usage\n\nAll functions and navigators are available as named exports:\n\n```js\nimport {select, update, $each, $apply} from 'qim';\n```\n\nOr of course you can just import everything:\n\n```js\nimport * as qim from 'qim';\n```\n\nYou can also import individual functions. If you import from `qim`, then don't do this! `qim` points to a bundle that includes all the individual modules, so importing individual modules will import them again.\n\n```js\nimport select from 'qim/select';\nimport update from 'qim/select';\nimport $each from 'qim/$each';\nimport $apply from 'qim/$apply';\n```\n\nIf you `npm install qim`, you'll have UMD builds in `node_modules/qim/build/umd/qim.js` and `node_modules/qim/build/umd.min.js`.\n\n## API\n\nAll methods in this section are curried, meaning if you leave off any of the arguments, you'll get a function that takes the remaining arguments. Specifically, this is most useful for the last `object` parameter. For example:\n\n```js\nconst archiveAllMessages = update(['messages', $each, 'isArchived', $set(true)]);\nconst archivedMessageState = archiveAllMessages(state);\n```\n\n### `apply(query, transform, object)`\n\nJust a convenience method for updating with a single transform ($apply) function.\n\n```js\napply(\n  ['users', 'joe', 'name'],\n  name =\u003e name.toUpperCase(),\n  {users: {joe: {name: 'Joe'}}}\n)\n// {users: {joe: {name: 'JOE'}}}\n```\n\n```js\napply(\n  ['numbers', $each, value =\u003e value % 2 === 0],\n  num =\u003e num * num,\n  {numbers: [1, 2, 3, 4, 5, 6]}\n)\n// {numbers: [1, 4, 3, 16, 5, 36]}\n```\n\n### `find(query, object)`\n\nLike `select`, but only returns a single result. If many results would be returned from a `select`, it will return the first result.\n\n```js\nfind(\n  [$each, value =\u003e value % 2 === 0],\n  [1, 2, 3, 4, 5, 6]\n)\n// 2\n```\n\nGenerally, this will perform much better than taking the first item of the array returned by a `select`.\n\n### `has(query, object)`\n\nReturns true if an object has a matching result.\n\n```js\nhas(\n  [$each, value =\u003e value % 2 === 0],\n  [1, 2, 3]\n)\n// true\n```\n\n```js\nhas(\n  [$each, value =\u003e value % 2 === 0],\n  [1, 3, 5]\n)\n// false\n```\n\n### `select(query, object)`\n\nReturns an array of selected results from an object.\n\n```js\nselect(\n  ['numbers', $each, value =\u003e value % 2 === 0],\n  {numbers: [1, 2, 3, 4, 5, 6]}\n)\n// [2, 4, 6]\n```\n\n### `set(query, value, object)`\n\nJust a convenience method to set a query path to a constant value.\n\n```js\nset(\n  ['users', 'joe', 'name'],\n  'Joseph',\n  {users: {joe: {name: 'Joe'}}}\n)\n// {users: {joe: {name: 'Joseph'}}}\n```\n\n```js\nset(\n  ['numbers', $each, value =\u003e value % 2 === 0],\n  0,\n  {numbers: [1, 2, 3, 4, 5, 6]}\n)\n// {numbers: [1, 0, 3, 0, 5, 0]}\n```\n\n### `update(query, object)`\n\nReturns a mutation of an object without changing the original object.\n\n```js\nupdate(\n  ['users', 'joe', 'name', $apply(name =\u003e name.toUpperCase())],\n  {users: {joe: {name: 'Joe'}}}\n)\n// {users: {joe: {name: 'JOE'}}}\n```\n\n```js\nupdate(\n  ['numbers', $each, value =\u003e value % 2 === 0, $apply(value =\u003e value * 2)],\n  {numbers: [1, 2, 3, 4, 5, 6]}\n)\n// {'numbers': [1, 4, 3, 8, 5, 12]}\n```\n\n## Navigators\n\n### Built-in, type-based navigators\n\n#### Key (string/integer)\n\nNavigates to that key of an object/array.\n\n```js\nselect(\n  ['users', 'name', 'joe'],\n  {users: {name: {joe: 'Joe'}}}\n)\n// [Joe]\n```\n\n```js\nselect(\n  ['0', '0'],\n  [['a', 'b'], ['c', 'd']]\n)\n// ['a']\n```\n\n```js\nupdate(\n  ['users', 'name', 'joe', $set('Joseph')],\n  {users: {name: {joe: 'Joseph'}}}\n)\n// {\"users\": {\"name\": {\"joe\": \"Joseph\"}}}\n```\n\n```js\nupdate(\n  ['0', '0', $apply(letter =\u003e letter.toUpperCase())],\n  [['a', 'b'], ['c', 'd']]\n)\n// [['A', 'b'], ['c', 'd']]\n```\n\n#### Predicate (function)\n\nPasses the currently navigated value to the function and continues navigating if the function returns true.\n\n```js\nselect(\n  [$each, value =\u003e value \u003e 0],\n  [-2, -1, 0, 1, 2, 3]\n)\n// [1, 2, 3]\n```\n\n```js\nupdate(\n  [$each, value =\u003e value \u003e 0, $set(0)],\n  [-2, -1, 0, 1, 2, 3]\n)\n// [-2, -1, 0, 0, 0, 0]\n```\n\n#### Nested (array)\n\nBranches and performs a sub-query. Mainly useful for `update`, since you may want to update different branches of an object in different ways. You can branch with  `select`, but this is less useful since you typically want to select homogenous value types.\n\n```js\nupdate(\n  [$each,\n    ['x', $apply(x =\u003e x + 1)],\n    ['y', $apply(y =\u003e y * 10)]\n  ],\n  [{x: 1, y: 1}, {x: 2, y: 2}]\n)\n// [{x: 2, y: 10}, {x: 3, y: 20}]\n```\n\n#### Stop (undefined/null)\n\nStops navigation. This is most useful inside `$nav` or custom path navigators to stop navigation for certain values.\n\n```js\nselect(['x', undefined, 'y'], {x: {y: 1}})\n// []\n```\n\n```js\nupdate(['x', undefined, 'y', $apply(value =\u003e value + 1)], {x: {y: 1}})\n// {x: {y: 1}}\n```\n\nFor nested queries, `undefined`/`null` only stops the current nested query, not the whole query.\n\n```js\nselect([\n  ['a', undefined, 'x'],\n  ['b', 'x']\n], {a: {x: 'ax'}, b: {x: 'bx'}})\n// ['bx']\n```\n\n```js\nupdate([\n  ['a', undefined, 'x', $apply(s =\u003e s.toUpperCase())],\n  ['b', 'x', $apply(s =\u003e s.toUpperCase())]\n], {a: {x: 'ax'}, b: {x: 'bx'}})\n// {a: {x: 'ax'}, b: {x: 'BX'}}\n```\n\n### Named navigators\n\nBy convention, all navigators are prefixed with a `$`. This is mainly intended to visually distinguish them in a query path. But it also is meant to distinguish them from normal functions. Navigators are declarative, meaning they represent a navigation to be performed, rather than actually doing an operation.\n\n#### `$apply(fn)`\n\nTransforms the currently navigated value using the provided function. Typically used for `update`, but can be used for `select` to transform the values selected.\n\n```js\nselect(\n  ['numbers', $each, $apply(value =\u003e value * 2)],\n  {numbers: [0, 1, 2, 3]}\n)\n// [0, 2, 4, 6]\n```\n\n```js\nupdate(\n  [$each, $apply(value =\u003e value * 2)],\n  {numbers: [0, 1, 2, 3]}\n)\n// {numbers: [0, 2, 4, 6]}\n```\n\n#### `$begin`\n\nNavigates to an empty list at the beginning of an array. Useful for adding things to the beginning of a list.\n\n```js\nupdate(\n  [$begin, $set([-2, -1, 0])],\n  [1, 2, 3]\n)\n// [-2, -1, 0, 1, 2, 3]\n```\n\n#### `$default(value)`\n\nBy default, Qim will create missing objects when you try to update a path that doesn't exist. You can use `$default` to change this behavior and provide your own default value.\n\n```js\nset(['x', $default([]), 0], 'a', {})\n// {x: ['a']}\n```\n\n```js\nset(['x', $default({a: 0}), 'y'], 0, {})\n// {x: {a: 0, y: 0}}\n```\n\n```js\nset(['names', $default(['a', 'b']), 0], 'joe', {})\n// {names: ['joe', 'b']}\n```\n\n#### `$each`\n\nNavigates to each value of an array or object.\n\n```js\nupdate(\n  [$each, $apply(num =\u003e num * 2)],\n  [1, 2, 3]\n)\n// [2, 4, 6]\n```\n\n```js\nupdate(\n  [$each, $apply(num =\u003e num * 2)],\n  {x: 1, y: 2, z: 3}\n)\n// {x: 2, y: 4, z: 6}\n```\n\n```js\nselect(\n  [$each],\n  [1, 2, 3]\n)\n// [1, 2, 3]\n```\n\n```js\nselect(\n  [$each],\n  {x: 1, y: 2, z: 3}\n)\n// [1, 2, 3]\n```\n\n#### `$eachKey`\n\nNavigates to each key of an array or object.\n\n```js\nupdate(\n  [$eachKey, $apply(key =\u003e key.toUpperCase())],\n  {x: 1, y: 2, z: 3}\n)\n// {X: 1, Y: 2, Z: 3}\n```\n\n```js\nselect(\n  [$eachKey],\n  {x: 1, y: 2, z: 3}\n)\n// ['x', 'y', 'x']\n```\n\n#### `$eachPair`\n\nNavigates to each key/value pair of an array or object. A key/value pair is just an array of `[key, value]`.\n\n```js\nupdate(\n  [$eachPair, $apply(([key, value]) =\u003e [key.toUpperCase(), value * 2])],\n  {x: 1, y: 2, z: 3}\n)\n// {X: 2, Y: 4, Z: 6}\n```\n\n```js\nupdate(\n  [$eachPair,\n    [0, $apply(key =\u003e key.toUpperCase())],\n    [1, $apply(value =\u003e value * 2)]\n  ],\n  {x: 1, y: 2, z: 3}\n)\n// {X: 2, Y: 4, Z: 6}\n```\n\n```js\nselect(\n  [$eachPair],\n  {x: 1, y: 2, z: 3}\n)\n// [['x', 1], ['y', 2], ['z', 3]]\n```\n\n#### `$end`\n\nNavigates to an empty list at the end of an array. Useful for adding things to the end of a list.\n\n```js\nupdate(\n  [$end, $set([4, 5, 6])],\n  [1, 2, 3]\n)\n// [1, 2, 3, 4, 5, 6]\n```\n\n#### `$first`\n\nNavigates to the first value of an array or object.\n\n```js\nselect([$first], [0, 1, 2])\n// [0]\n\nselect([$first], {x: 0, y: 1, z: 2})\n// [0]\n\nupdate([$first, $set('first')], [0, 1, 2])\n// ['first', 1, 2]\n\nupdate([$first, $set('first')], {x: 0, y: 1, z: 2})\n// {x: 'first', y: 1, z: 2}\n```\n\n#### `$last`\n\nNavigates to the last value of an array or object.\n\n```js\nselect([$last], [0, 1, 2])\n// [2]\n\nselect([$last], {x: 0, y: 1, z: 2})\n// [2]\n\nupdate([$last, $set('last')], [0, 1, 2])\n// [0, 1, 'last']\n\nupdate([$last, $set('last')], {x: 0, y: 1, z: 2})\n// {x: 0, y: 1, z: 'last'}\n```\n\n#### `$lens(fn, fromFn)`\n\nLens is like `$apply`, and the first function behaves identically, in that it transforms the current value using the provided function. But it also takes a second function that can be used to apply the result of a transformation to the current object during an update.\n\n```js\nconst $pct = $lens(\n  // The first function transforms the result, just like $apply.\n  // This simple example transforms a decimal value to its percentage equivalent.\n  n =\u003e n * 100,\n  // The second function inverts the transformation on the way back.\n  // This simple example transforms a percentage value to its decimal equivalent.\n  pct =\u003e pct / 100\n);\n\nupdate(\n  ['x', $pct, pct =\u003e pct \u003e 50, $apply(pct =\u003e pct + 5)],\n  {x: .75}\n)\n// {x: .80}\n```\n\nSee [custom navigators](#custom-navigators) for more about `$lens`.\n\n#### `$merge(spec)`\n\nSimilar to `$apply(object =\u003e ({...object, ...spec}))` except:\n\n- Does not create a new object if `object` already has the same keys/values as `spec`.\n- Will create a new array if `object` is an array so can be used to merge arrays.\n\n```js\nupdate([$merge({y: 2})], {x: 1})\n// {x: 1, y: 2}\n```\n\n```js\nupdate([$merge(['a'])], ['x', 'y', 'z'])\n// ['a', 'y', 'z']\n```\n\n#### `$mergeDeep(spec)`\n\nDeep merging version of `$merge`. Typically, deep merging is better handled with nested queries. But if you must...\n\n```js\nupdate([$mergeDeep({a: {ab: 2}})], {a: {aa: 1}, b: 2})\n// {a: {aa: 1, ab: 2}, b: 2}\n```\n\n#### `$nav(path, ...morePaths)`\n\nGiven a query path, `$nav` navigates as if that query was a single selector. This is useful for using queries as navigators (instead of nested queries). This has the same affect as spreading (`...`) a query into another query.\n\n```js\nconst $eachUser = $nav(['users', $each]);\n\nselect(\n  [$eachUser, 'name'],\n  {\n    users: {\n      joe: {\n        name: 'Joe'\n      },\n      mary: {\n        name: 'Mary'\n      }\n    }\n  }\n)\n// ['Joe', 'Mary']\n```\n\n```js\nconst $eachUser = $nav(['users', $each]);\n\nupdate(\n  [$eachUser, 'name', $apply(name =\u003e name.toUpperCase())],\n  {\n    users: {\n      joe: {\n        name: 'Joe'\n      },\n      mary: {\n        name: 'Mary'\n      }\n    }\n  }\n)\n// {users: {joe: {name: 'JOE'}, mary: {name: 'MARY'}}}\n```\n\nYou can pass multiple paths to `$nav` to navigate to all of those paths.\n\n```js\nupdate(\n  ['users', $nav(['joe'], ['mary']), 'name', $apply(name =\u003e name.toUpperCase())],\n  {\n    users: {\n      joe: {\n        name: 'Joe'\n      },\n      mary: {\n        name: 'Mary'\n      }\n    }\n  }\n)\n// {users: {joe: {name: 'JOE'}, mary: {name: 'MARY'}}}\n```\n\nIf `path` is a function, it will be passed the current object, and it can return a dynamic query. This can be used to inline a custom navigator or just to get the current value in scope.\n\n```js\nupdate(\n  [\n    $each, $nav(\n      obj =\u003e ['isEqual', $set(obj.x === obj.y)]\n    )\n  ],\n  [{x: 1, y: 1}, {x: 1, y: 2}]\n)\n// [\n//   {x: 1, y: 1, isEqual: true},\n//   {x: 1, y: 2, isEqual: false}\n// ]\n```\n\nSee [custom navigators](#custom-navigators) for more about `$nav`.\n\n#### `$none`\n\nNavigates to nothing so you can delete properties from objects and items from arrays. It would be great to use `undefined` for this, but technically `undefined` is a value in JS, so `$none` exists to allow for the edge case of being able to set a property or item to `undefined`.\n\n```js\nupdate(\n  ['x', $none],\n  {x: 1, y: 2}\n)\n// {y: 2}\n```\n\n```js\nupdate(\n  [0, $none],\n  ['a', 'b']\n)\n// ['b']\n```\n\n```js\nupdate(\n  [$each, value =\u003e value % 2 === 0, $none],\n  [1, 2, 3, 4, 5, 6]\n)\n// [1, 3, 5]\n```\n\n#### `$pick(keys, ...keys)`\n\nNavigates to a subset of an object, using the provided keys (or arrays of keys).\n\n```js\nselect(\n  [$pick('joe', 'mary'), $each, 'name'],\n  {joe: {name: 'Joe'}, mary: {name: 'Mary'}, bob: {name: 'Bob'}}\n)\n// ['Joe', 'Mary']\n```\n\n```js\nupdate(\n  [$pick('x', 'y'), $set({a: 1})],\n  {x: 1, y: 1, z: 1}\n)\n// {a: 1, z: 1}\n```\n\n```js\nupdate(\n  [$pick('x', 'y'), $each, $apply(val =\u003e val + 1)],\n  {x: 1, y: 1, z: 1}\n)\n{x: 2, y: 2, z: 1}\n```\n\n#### `$pushContext(key, (obj, context) =\u003e contextValue)`\n\nThis is a convenient form of `$setContext` where the current value for the key is assumed to be an array and the new value is pushed onto that array. This is especially useful for recursive queries where you want to retain parent context values.\n\n```js\nselect(\n  [\n    $eachPair, $pushContext('path', find(0)), 1,\n    $eachPair, $pushContext('path', find(0)), 1,\n    $apply((message, ctx) =\u003e ({path: ctx.path, message}))\n  ],\n  {error: {foo: 'a', bar: 'b'}, warning: {baz: 'c', qux: 'd'}}\n)\n// [\n//   {path: ['error', 'foo'], message: 'a'},\n//   {path: ['error', 'bar'], message: 'b'},\n//   {path: ['warning', 'baz'], message: 'c'},\n//   {path: ['warning', 'qux'], message: 'd'}\n// ]\n```\n\n#### `$set(value)`\n\nJust a convenience for setting a value, rather than using `$apply(() =\u003e value)`. (Also a teensy bit more performant.)\n\n```js\nupdate(\n  [$each, $set(0)],\n  [1, 2, 3]\n)\n// [0, 0, 0]\n```\n\n#### `$setContext(key, (value, context) =\u003e contextValue)`\n\nSets a context value for later retrieval in an `$apply` or `$nav`.\n\nContext is a way to grab a piece of data at one point in a query and use it at a later point in a query. A good example is grabbing a key from a key/value pair and retrieving that key along with a descendant value so that it can be returned in a selection or used in an update. You could use a `$nav` function to pull that key into scope, but for multiple levels of nesting, this can get unwieldy. And for recursive queries, this is impossible without creating some kind of complex enveloping mechanism.\n\n`$setContext` takes a key and a function as arguments. The key is just a way of keeping that piece of context separate from other pieces of context. The function takes the current value and context and returns a value to store for that piece of context. Note that context is never mutated, so the new context only applies to the rest of the query.\n\n```js\nselect(\n  [$setContext('first', find($first)), $each, $apply((letter, ctx) =\u003e `${ctx.first}${letter}`)],\n  ['a', 'b', 'c']\n)\n// ['aa', 'ab', 'ac']\n```\n\n```js\nselect(\n  [\n    $eachPair, $setContext('level', find(0)), 1,\n    $eachPair, $setContext('key', find(0)), 1,\n    $apply((message, ctx) =\u003e ({level: ctx.level, key: ctx.key, message}))\n  ],\n  {error: {foo: 'a', bar: 'b'}, warning: {baz: 'c', qux: 'd'}}\n)\n// [\n//   {level: 'error', key: 'foo', message: 'a'},\n//   {level: 'error', key: 'bar', message: 'b'},\n//   {level: 'warning', key: 'baz', message: 'c'},\n//   {level: 'warning', key: 'qux', message: 'd'}\n// ]\n```\n\n```js\nselect(\n  [\n    $setContext('favorites', select(['favorites', $each])),\n    'stooges',\n    $nav((nodes, ctx) =\u003e [$pick(ctx.favorites)]),\n    $each, 'name'\n  ],\n  {\n    stooges: {\n      a: {name: 'Moe'},\n      b: {name: 'Larry'},\n      c: {name: 'Curly'}\n    },\n    favorites: ['a', 'c']\n  }\n)\n// ['Moe', 'Curly']\n```\n\n#### `$slice(begin, end)`\n\nNavigates to a slice of an array from `begin` to `end` index.\n\n```js\nselect(\n  [$slice(0, 3), $each],\n  [1, 2, 3, 4, 5, 6]\n)\n// [1, 2, 3]\n```\n\n```js\nupdate(\n  [$slice(0, 3), $each, $set(0)],\n  [1, 2, 3, 4, 5, 6]\n)\n// [0, 0, 0, 4, 5, 6]\n```\n\n```js\nupdate(\n  [$slice(2, 4), $set([])],\n  ['a', 'b', 'c', 'd', 'e', 'f']\n)\n// ['a', 'b', 'e', 'f']\n```\n\n#### `$traverse({select, update})`\n\nSee [custom navigators](#custom-navigators).\n\n## Custom navigators\n\nCustom navigators are simply composed of other navigators. For example, you could create a `$toggle` navigator that flips boolean values like this:\n\n```js\nconst $toggle = $apply(val =\u003e !Boolean(val));\n\nupdate(['isOn', $toggle], {isOn: false})\n// {isOn: true}\n```\n\nIn particular though, the important navigators for building other navigators are `$nav`, `$lens`, and `$traverse`.\n\n`$nav` lets you build \"path\" navigators. A path navigator is the highest level option. If you want to abstract away multiple other navigators, or you want to dynamically choose a navigator based on the current object being navigated to, or you want to do a recursive query, you probably want a path navigator.\n\n`$lens` lets you build \"lens\" navigators. A lens navigator is generally going to be simpler than a core navigator and will be almost as performant, but doesn't offer quite as much control. Think of a lens navigator as a two-way `$apply`. You transform the current object for the rest of the query, and for an update, you define a transformation to be applied to the object. If you _always_ want to call the rest of the query, and you only want to call the rest of the query _once_, then a lens navigator is a good fit.\n\n`$traverse` lets you build, for lack of a better term, \"core\" navigators. It's the lowest-level option and gives you complete control of data selection and updates. Generally, core navigators are going to be the most performant, but they're also going to require the most code. If you need to _maybe_ call the rest of the query, or you want to call the rest of the query _many_ times, you probably need a core navigator.\n\n\n### Path navigators\n\nThe simplest path navigator just points to a query path. This is useful for using queries as navigators (instead of nested queries). This has the same affect as spreading (`...`) a query into another query.\n\n```js\nconst $eachUser = $nav(['users', $each]);\n\nselect(\n  [$eachUser, 'name'],\n  {\n    users: {\n      joe: {\n        name: 'Joe'\n      },\n      mary: {\n        name: 'Mary'\n      }\n    }\n  }\n)\n// ['Joe', 'Mary']\n\nupdate(\n  [$eachUser, 'name', $apply(name =\u003e name.toUpperCase())],\n  {\n    users: {\n      joe: {\n        name: 'Joe'\n      },\n      mary: {\n        name: 'Mary'\n      }\n    }\n  }\n)\n// {users: {joe: {name: 'JOE'}, mary: {name: 'MARY'}}}\n```\n\nThe `path` passed to `$nav` can also be a function, which allows it to provide a different path based on the current value.\n\n```js\nconst $fileNames = $nav(\n  (obj) =\u003e {\n    if (obj.type === 'folder') {\n      return ['files', $each, 'name'];\n    }\n    if (obj.type === 'file') {\n      return ['name'];\n    }\n    // Returns `undefined` which stops navigation.\n  }\n);\n\nselect(\n  [$each, $fileNames],\n  [{type: 'file', name: 'foo'}, {type: 'folder', files: [{name: 'bar'}]}, {type: 'other', name: 'thing'}]\n)\n['foo', 'bar']\n```\n\nYou can also create recursive queries using path navigators.\n\n```js\nconst $walk = $nav(item =\u003e\n   (Array.isArray(item) ? [$each, $walk] : [])\n);\n\nselect(\n  [$walk, val =\u003e val % 2 === 0],\n  [0, 1, 2, [3, 4, 5, [6, 7, 8]]]\n)\n// [0, 2, 4, 6, 8]\n\nupdate(\n  [$walk, val =\u003e val % 2 === 0, $apply(val =\u003e val * 10)],\n  [0, 1, 2, [3, 4, 5, [6, 7, 8]]]\n)\n// [0, 1, 20, [3, 40, 5, [60, 7, 80]]]\n```\n\n### Parameterized path navigators\n\nTo parameterize a path navigator, just create a function that returns a path navigator.\n\n```js\nconst $take = (count) =\u003e $nav(\n  (obj) =\u003e {\n    if (!Array.isArray(obj)) {\n      throw new Error('$take only works on arrays');\n    }\n    return [$slice(0, count)];\n  }\n);\n\nselect(\n  [$take(3), $each],\n  [0, 1, 2, 3, 4, 5]\n)\n// [0, 1, 2]\n\nupdate(\n  [$take(3), $each, $apply(val =\u003e val * 10)],\n  [0, 1, 2, 3, 4, 5]\n)\n// [0, 10, 20, 3, 4]\n```\n\n### Lens navigators\n\nLens navigators are built by using `$lens`, which takes two functions. The first function applies a transformation for either a select or update. The second function is only invoked for an update and allows you to apply that transformation to the current object.\n\n```js\n// Create a navigator that selects or modifies the length of an array.\nconst $length = $lens(\n  (object) =\u003e {\n    if (Array.isArray(object)) {\n      return object.length;\n    }\n    throw new Error('$length only works on arrays');\n  },\n  (newLength, object) =\u003e {\n    const newLength = next(object.length);\n    if (newLength \u003c object.length) {\n      return object.slice(0, newLength);\n    }\n    if (newLength \u003e object.length) {\n      object = object.slice(0);\n      for (let i = 0; i \u003c newLength - object.length; i++) {\n        object.push(undefined);\n      }\n      return object;\n    }\n    return object;\n  }\n);\n\nselect([$length], [1, 1, 1])\n// [3]\n\nset([$length], 3, [1, 1, 1])\n// [1, 1, 1]\n\nset([$length], 2, [1, 1, 1])\n// [1, 1]\n\nset([$length], 4, [1, 1, 1])\n// [1, 1, 1, undefined]\n```\n\n### Parameterized lens navigators\n\nTo parameterize a lens navigator, just create a function that returns a lens navigator.\n\n```js\nconst $split = (char) =\u003e $lens(\n  (string) =\u003e string.split(char),\n  (splitString) =\u003e splitString.join(char)\n);\n\nupdate(\n  [$split('@'), 0, $apply(name =\u003e name.toUpperCase())],\n  'joe.foo@example.com'\n)\n// JOE.FOO@example.com\n```\n\n### Core navigators\n\nCore navigators are built by using `$traverse`, which takes an object with a `select` and `update` function.\n\n```js\n// Create a navigator that selects or modifies the length of an array.\nconst $length = $traverse({\n  select: (object, next) =\u003e {\n    if (Array.isArray(object)) {\n      return next(object.length);\n    }\n    throw new Error('$length only works on arrays');\n  },\n  update: (object, next) =\u003e {\n    if (Array.isArray(object)) {\n      const newLength = next(object.length);\n      if (newLength \u003c object.length) {\n        return object.slice(0, newLength);\n      }\n      if (newLength \u003e object.length) {\n        object = object.slice(0);\n        for (let i = 0; i \u003c newLength - object.length; i++) {\n          object.push(undefined);\n        }\n        return object;\n      }\n      return object;\n    }\n    throw new Error('$length only works on arrays');\n  }\n});\n\nselect([$length], [1, 1, 1])\n// [3]\n\nset([$length], 3, [1, 1, 1])\n// [1, 1, 1]\n\nset([$length], 2, [1, 1, 1])\n// [1, 1]\n\nset([$length], 4, [1, 1, 1])\n// [1, 1, 1, undefined]\n```\n\n### Parameterized core navigators\n\nTo parameterize a core navigator, just create a function that returns a core navigator.\n\n```js\n// Create a navigator that selects or updates the first n items of an array.\nconst $take = (count) =\u003e $traverse({\n  select: (object, next) =\u003e {\n    if (Array.isArray(object)) {\n      return next(object.slice(0, count));\n    }\n    throw new Error('$take only works on arrays');\n  },\n  update: (object, next) =\u003e {\n    if (Array.isArray(object)) {\n      const result = next(object.slice(0, count));\n      const newArray = object.slice(0);\n      newArray.splice(0, count, ...result);\n      return newArray;\n    }\n    throw new Error('$take only works on arrays');\n  }\n});\n\nselect([$take(2)], ['a', 'b', 'c'])\n// [['a', 'b']]\n\nset([$take(2)], ['x'], ['a', 'b', 'c'])\n// ['x', 'c']\n```\n\n## Performance\n\nQim aims to be _performant enough_.\n\nFor Lodash operations that are immutable (like `get` and `set`), Qim should have similar performance. Many Lodash functions mutate (like `update`), and in nearly all cases, Qim will be faster than Lodash/fp's immutable functions. Likewise, Qim will typically be faster than React's immutability helper (now an [external package](https://github.com/kolodny/immutability-helper)). Ramda seems to perform much better than Lodash/fp, and sometimes it will be faster than Qim, while sometimes Qim will be faster.\n\nIn some cases, a custom native helper function using `Object.assign` or `slice` along with a mutation may be faster for simple operations, but Qim aims to be as close as possible, while still allowing for a flexible querying API.\n\nComparing to Immutable.js is difficult in that it heavily depends on your particular use case. The sweet spot for Immutable.js is lots of transformations of _large_ objects or arrays (thousands of items). And even then, you need to avoid marshalling data back and forth between Immutable and plain JS. If you marshal the data back and forth, you'll lose most of the benefit, and if you work with smaller objects and arrays, you're unlikely to see much benefit.\n\nIf you want flexible select and update of plain JS objects, Qim is likely to be a good fit. You can check out the [current benchmarks](docs/benchmark-results.md) to get an idea how Qim stacks up. As with all benchmarks, be careful reading into them too much. Also, Qim is new, and performance tradeoffs could change in favor of simplifying the code or API. Overall, remember that the goal of Qim is to be expressive and _performant enough_, not to win at every benchmark.\n\n## TODO\n\n- Work with iterables (like Map).\n- More tests.\n- Better error messages.\n- Expose ES6 modules.\n- Static typing? Qim might be diametrically opposed to static types, but worth seeing how good/bad they would fit.\n\n## Contributing\n\n- Do the usual fork and PR thing.\n- Make sure tests pass with `npm test` and preferaby add additional tests.\n- Make sure to run `npm run benchmark` to make sure the benchmarks pass. Currently, the benchmarks are running against Node 6.2.2 on a late 2013 MacBook Pro. The benchmarks are by no means perfect, and small regressions may be allowed in exchange for significant improvements, but for the most part, performance needs to remain consistent or improve.\n- Thanks!\n\n## Thanks\n\nAs mentioned above, the navigator concept borrows heavily from [Specter](https://github.com/nathanmarz/specter), a Clojure library written by Nathan Marz.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjdeal%2Fqim","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjdeal%2Fqim","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjdeal%2Fqim/lists"}