{"id":20819577,"url":"https://github.com/calmm-js/partial.lenses.history","last_synced_at":"2025-05-07T15:23:20.402Z","repository":{"id":57319748,"uuid":"133234965","full_name":"calmm-js/partial.lenses.history","owner":"calmm-js","description":"Partial Lenses History is a JavaScript library for Undo-Redo","archived":false,"fork":false,"pushed_at":"2024-07-23T10:51:15.000Z","size":208,"stargazers_count":6,"open_issues_count":1,"forks_count":4,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-22T14:13:08.518Z","etag":null,"topics":["immutable","state","time-travel","undo-redo"],"latest_commit_sha":null,"homepage":null,"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/calmm-js.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2018-05-13T12:28:40.000Z","updated_at":"2024-09-02T18:54:17.000Z","dependencies_parsed_at":"2022-08-26T01:11:12.151Z","dependency_job_id":null,"html_url":"https://github.com/calmm-js/partial.lenses.history","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calmm-js%2Fpartial.lenses.history","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calmm-js%2Fpartial.lenses.history/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calmm-js%2Fpartial.lenses.history/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calmm-js%2Fpartial.lenses.history/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/calmm-js","download_url":"https://codeload.github.com/calmm-js/partial.lenses.history/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252903217,"owners_count":21822397,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["immutable","state","time-travel","undo-redo"],"created_at":"2024-11-17T22:06:49.124Z","updated_at":"2025-05-07T15:23:20.373Z","avatar_url":"https://github.com/calmm-js.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# \u003ca id=\"partial-lenses-history\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#) [Partial Lenses History](#partial-lenses-history) \u0026middot; [![Gitter](https://img.shields.io/gitter/room/calmm-js/chat.js.svg)](https://gitter.im/calmm-js/chat) [![GitHub stars](https://img.shields.io/github/stars/calmm-js/partial.lenses.history.svg?style=social)](https://github.com/calmm-js/partial.lenses.history) [![npm](https://img.shields.io/npm/dm/partial.lenses.history.svg)](https://www.npmjs.com/package/partial.lenses.history)\n\nPartial Lenses History is a JavaScript library for state manipulation with\nUndo-Redo history.  Basic features:\n* [History can be serialized as JSON](#on-serializability)\n* [All operations on history are either `O(1)` or `O(log n)`](#on-performance)\n* Interactive documentation (the [▶\n  links](https://calmm-js.github.io/partial.lenses.history/index.html)) and live\n  examples:\n  * The [Basic Undo-Redo](https://codesandbox.io/s/y0mzonm98x) CodeSandbox\n    provides a simple example that is discussed [below](#a-basic-example).\n  * The [Form using Context](https://codesandbox.io/s/2rq54pgrp) CodeSandbox\n    also demonstrates this library.\n* [Mostly](#on-side-effects) functional API:\n  * [Immutable data structures](#on-immutability)\n  * [Curried functions](http://fr.umio.us/favoring-curry/)\n* [Supports tree-shaking](https://webpack.js.org/guides/tree-shaking/)\n* [Contract checking in non-production builds](./src/partial.lenses.history.js)\n* [MIT license](./LICENSE.md)\n\n[![npm version](https://badge.fury.io/js/partial.lenses.history.svg)](http://badge.fury.io/js/partial.lenses.history)\n[![Build Status](https://travis-ci.org/calmm-js/partial.lenses.history.svg?branch=master)](https://travis-ci.org/calmm-js/partial.lenses.history)\n[![Code Coverage](https://img.shields.io/codecov/c/github/calmm-js/partial.lenses.history/master.svg)](https://codecov.io/github/calmm-js/partial.lenses.history?branch=master)\n[![](https://david-dm.org/calmm-js/partial.lenses.history.svg)](https://david-dm.org/calmm-js/partial.lenses.history)\n[![](https://david-dm.org/calmm-js/partial.lenses.history/dev-status.svg)](https://david-dm.org/calmm-js/partial.lenses.history?type=dev)\n\n## \u003ca id=\"contents\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#contents) [Contents](#contents)\n\n* [A basic example](#a-basic-example)\n* [Reference](#reference)\n  * [Basic properties](#basic-properties)\n    * [On serializability](#on-serializability)\n    * [On performance](#on-performance)\n    * [On immutability](#on-immutability)\n    * [On side-effects](#on-side-effects)\n  * [Creating](#creating)\n    * [`H.init({[maxCount, pushEquals, replacePeriod]}, value) ~\u003e history`](#H-init) \u003csmall\u003e\u003csup\u003ev0.1.0\u003c/sup\u003e\u003c/small\u003e\n  * [Present](#present)\n    * [`H.present ~\u003e valueLens`](#H-present) \u003csmall\u003e\u003csup\u003ev0.2.0\u003c/sup\u003e\u003c/small\u003e\n  * [Undo](#undo)\n    * [`H.undoForget(history) ~\u003e history`](#H-undoForget) \u003csmall\u003e\u003csup\u003ev0.1.0\u003c/sup\u003e\u003c/small\u003e\n    * [`H.undoIndex ~\u003e numberLens`](#H-undoIndex) \u003csmall\u003e\u003csup\u003ev0.2.0\u003c/sup\u003e\u003c/small\u003e\n  * [Redo](redo)\n    * [`H.redoForget(history) ~\u003e history`](#H-redoForget) \u003csmall\u003e\u003csup\u003ev0.1.0\u003c/sup\u003e\u003c/small\u003e\n    * [`H.redoIndex ~\u003e numberLens`](#H-redoIndex) \u003csmall\u003e\u003csup\u003ev0.2.0\u003c/sup\u003e\u003c/small\u003e\n  * [Time travel](#time-travel)\n    * [`H.count(history) ~\u003e number`](#H-count) \u003csmall\u003e\u003csup\u003ev0.1.0\u003c/sup\u003e\u003c/small\u003e\n    * [`H.index ~\u003e numberLens`](#H-index) \u003csmall\u003e\u003csup\u003ev0.2.0\u003c/sup\u003e\u003c/small\u003e\n    * [`H.indexMax(history) ~\u003e number`](#H-indexMax) \u003csmall\u003e\u003csup\u003ev0.2.3\u003c/sup\u003e\u003c/small\u003e\n\n## \u003ca id=\"a-basic-example\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#a-basic-example) [A basic example](#a-basic-example)\n\nThis section describes the [Basic\nUndo-Redo](https://codesandbox.io/s/y0mzonm98x) CodeSandbox example that was\nwritten to demonstrate usage of this library.  There is a text area and edits\nretain history that can then be viewed through the undo and redo buttons.  You\nprobably want to open the example beside this tutorial.\n\nLooking at the code, the first thing you might notice is the import statement:\n\n```jsx\nimport * as H from 'kefir.partial.lenses.history'\n```\n\nWhen used in a [Karet](https://github.com/calmm-js/karet) UI, this library is\nintended to be used through the [Kefir Partial Lenses\nHistory](https://github.com/calmm-js/kefir.partial.lenses.history) library,\nwhich is a simple [lifted](https://github.com/calmm-js/karet.util#U-lift)\nwrapper around this library.  Lifting allows the functions of this library to be\ndirectly used on [Kefir](https://kefirjs.github.io/kefir/)\n[properties](https://kefirjs.github.io/kefir/#about-observables) and\n[atoms](https://github.com/calmm-js/kefir.atom) representing time-varying values\nand reactive variables.  However, this library does not depend on Karet or Kefir\nand can be used with pretty much any UI framework.\n\nTo use history, one must first use [`H.init`](#H-init) to create the initial\nhistory value and then store the value:\n\n```jsx\nconst history = U.atom(H.init({}, ''))\n```\n\nIn this case we use [`U.atom`](https://github.com/calmm-js/karet.util#U-atom) to\ncreate an [atom](https://github.com/calmm-js/kefir.atom) to store the history.\n\n\u003e In a plain React UI, for example, one would typically store the history in\n\u003e component state:\n\u003e\n\u003e ```jsx\n\u003e this.state = {history: H.init({}, '')}\n\u003e ```\n\nTo access the present value from history, one uses the [`H.present`](#H-present)\n[lens](https://github.com/calmm-js/partial.lenses/#lenses):\n\n```jsx\nconst text = U.view(H.present, history)\n```\n\nAs we are using atoms, we can use the\n[`U.view`](https://github.com/calmm-js/karet.util#U-view) function to create a\nbidirectional view of the present that we can then use to both read and write\nthe present value.\n\n\u003e In a plain React UI, one could use\n\u003e [`L.get`](https://github.com/calmm-js/partial.lenses/#L-get) to read the\n\u003e present value from component state:\n\u003e\n\u003e ```jsx\n\u003e const currentText = L.get(['history', H.present], this.state)\n\u003e ```\n\u003e\n\u003e and [`L.set`](https://github.com/calmm-js/partial.lenses/#L-set) to write to\n\u003e the present value in component state:\n\u003e\n\u003e ```jsx\n\u003e this.setState(L.set(['history', H.present], newText))\n\u003e ```\n\u003e\n\u003e The point here is that this library is not at all limited to Karet UIs.  In\n\u003e the remainder we will only discuss the actual example.\n\nNow that we have the `text` view, we can use it to access the text without\nknowing anything about the history.  So we can simply instantiate a\n[`U.TextArea`](https://github.com/calmm-js/karet.util#U-TextArea) with the\n`text` as the `value`:\n\n```jsx\n\u003cU.TextArea placeholder=\"Retains history\" value={text} /\u003e\n```\n\nNow edits through the text area generate history.  Note that, while in this case\nwe only store simple strings in history, values stored in history can be\narbitrarily complex trees of objects.\n\nOf course, to actually make use of the history, we need to provide access to the\nhistory itself, rather than just the present value.  To that end we implement a\ncountdown button component:\n\n```jsx\nconst CountdownButton = ({count, shortcut, children, ...props}) =\u003e (\n  \u003cbutton disabled={R.not(count)} onClick={U.doModify(count, R.dec)} {...props}\u003e\n    {children}\n    {U.when(count, U.string` (${count})`)}\n    {U.when(\n      shortcut,\n      U.thru(\n        U.fromEvents(document.body, 'keydown', false),\n        U.skipUnless(shortcut),\n        U.consume(U.actions(U.preventDefault, U.doModify(count, R.dec)))\n      )\n    )}\n  \u003c/button\u003e\n)\n```\n\nThe above `CountdownButton` component expects to receive a `count` atom\ncontaining a non-negative integer and it then renders a button that is enabled\nwhen the count is positive.  Clicking the button decrements the `count`.\nAdditionally, given a `shortcut` event predicate, it also binds a keyboard event\nhandler to the document that performs the same decrement action.  Note that the\nabove `CountdownButton` knows nothing about history.  It is just a generic\nbutton that decrements a counter.\n\nTo wire countdown buttons to perform undo and redo actions on history, we use\nthe [`H.undoIndex`](#H-undoIndex) and [`H.redoIndex`](#H-redoIndex) lenses to\nview the history.  Here is how it looks like for the undo button:\n\n```jsx\n\u003cCountdownButton\n  count={U.view(H.undoIndex, history)}\n  title=\"Ctrl-z\"\n  shortcut={e =\u003e e.ctrlKey \u0026\u0026 e.key === 'z'}\u003e\n  Undo\n\u003c/CountdownButton\u003e\n```\n\nModifying the undo index actually modifies the history.  That pretty much covers\nbasic usage of this library.\n\n## \u003ca id=\"reference\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#reference) [Reference](#reference)\n\nThe [combinators](https://wiki.haskell.org/Combinator) provided by this library\nare available as named imports.  Typically one just imports the library as:\n\n```jsx\nimport * as H from 'partial.lenses.history'\n```\n\nThe examples also use the [Partial\nLenses](https://github.com/calmm-js/partial.lenses) library imported as\n\n```jsx\nimport * as L from 'partial.lenses'\n```\n\nand the following helper function,\n[`thru`](https://github.com/calmm-js/karet.util/#U-thru), that pipes a value\nthrough the given sequence of functions:\n\n```js\nfunction thru(x, ...fns) {\n  return fns.reduce((x, fn) =\u003e fn(x), x)\n}\n```\n\n#### \u003ca id=\"basic-properties\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#creating) [Basic properties](#basic-properties)\n\n#### \u003ca id=\"on-serializability\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#creating) [On serializability](#on-serializability)\n\nThe history data type should be considered opaque.  However, the history data\nstructure itself only uses [JSON](https://www.json.org/) compatible types.\nAssuming that `JSON.parse(JSON.stringify(v))` is considered equivalent to `v`\nfor any value `v` put into history, then it is guaranteed that\n`JSON.parse(JSON.stringify(history))` is considered equivalent to `history`.\n\n#### \u003ca id=\"on-performance\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#creating) [On performance](#on-performance)\n\nThe internal implementation of history uses a simple but fairly efficient data\nstructure (currently a radix search trie) that can perform all the operations\nexposed by this library in either `O(1)` or `O(log n)` time.\n\n#### \u003ca id=\"on-immutability\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#creating) [On immutability](#on-immutability)\n\nSince version 1.1.0 the history data structure is kept\n[frozen](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)\nwhen `NODE_ENV` is not `production`.  Only the history data structure itself is\nfrozen.  Values inserted into history are not frozen by this library.\n\n#### \u003ca id=\"on-side-effects\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#creating) [On side-effects](#on-side-effects)\n\nCertain operations, namely [`H.init`](#H-init) and\n[`L.set(H.present)`](#H-present) in this library are not pure functions, because\nthey [take\ntimestamps](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now)\nunderneath.\n\n### \u003ca id=\"creating\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#creating) [Creating](#creating)\n\n#### \u003ca id=\"H-init\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#H-init) [`H.init({[maxCount, pushEquals, replacePeriod]}, value) ~\u003e history`](#H-init) \u003csmall\u003e\u003csup\u003ev0.1.0\u003c/sup\u003e\u003c/small\u003e\n\n`H.init` creates a new history state object with the given initial `value`.  The\nnamed parameters, `maxCount`, `replacePeriod`, and `pushEquals`, are optional\nand control how history is updated when the state is modified through\n[`H.present`](#H-present).\n* `maxCount` defaults to `2^31-1` and specifies the maximum number of entries to\n  keep in history.\n* `pushEquals` defaults to `false` and determines whether writing a value that\n  is equal to the present value updates history or not.\n* `replacePeriod` defaults to `0` and specifies a period in milliseconds during\n  which an update replaces the present value without adding history.\n\nFor example:\n\n```js\nthru(\n  H.init({}, 101),\n  L.get(H.present)\n)\n// 101\n```\n\nNote that `H.init` is not a pure function, because it takes a timestamp\nunderneath.\n\n### \u003ca id=\"present\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#present) [Present](#present)\n\n#### \u003ca id=\"H-present\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#H-present) [`H.present ~\u003e valueLens`](#H-present) \u003csmall\u003e\u003csup\u003ev0.2.0\u003c/sup\u003e\u003c/small\u003e\n\n`H.present` is a\n[lens](https://github.com/calmm-js/partial.lenses/#partial-lenses) that focuses\non the present value of history.\n\nFor example:\n\n```js\nthru(\n  H.init({}, 42),\n  L.modify(H.present, x =\u003e -x),\n  L.get(H.present)\n)\n// -42\n```\n\nNote that modifications through `H.present` are not referentially transparent\noperations, because setting through `H.present` takes a timestamp underneath.\n\n### \u003ca id=\"undo\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#undo) [Undo](#undo)\n\n#### \u003ca id=\"H-undoForget\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#H-undoForget) [`H.undoForget(history) ~\u003e history`](#H-undoForget) \u003csmall\u003e\u003csup\u003ev0.1.0\u003c/sup\u003e\u003c/small\u003e\n\n`H.undoForget` removes all entries prior to present from history.\n\nFor example:\n\n```js\nthru(\n  H.init({}, '1st'),\n  L.set(H.present, '2nd'),\n  L.set(H.present, '3rd'),\n  H.undoForget,\n  L.get(H.undoIndex)\n)\n// 0\n```\n\n#### \u003ca id=\"H-undoIndex\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#H-undoIndex) [`H.undoIndex ~\u003e numberLens`](#H-undoIndex) \u003csmall\u003e\u003csup\u003ev0.2.0\u003c/sup\u003e\u003c/small\u003e\n\n`H.undoIndex` is a\n[lens](https://github.com/calmm-js/partial.lenses/#partial-lenses) that focuses\non the undo position of history.\n\nFor example:\n\n```js\nthru(\n  H.init({}, '1st'),\n  L.set(H.present, '2nd'),\n  L.set(H.present, '3rd'),\n  L.modify(H.undoIndex, n =\u003e n-1),\n  L.get(H.present)\n)\n// '2nd'\n```\n\n### \u003ca id=\"redo\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#redo) [Redo](redo)\n\n#### \u003ca id=\"H-redoForget\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#H-redoForget) [`H.redoForget(history) ~\u003e history`](#H-redoForget) \u003csmall\u003e\u003csup\u003ev0.1.0\u003c/sup\u003e\u003c/small\u003e\n\n`H.redoForget` removes all entries following present from history.\n\nFor example:\n\n```js\nthru(\n  H.init({}, '1st'),\n  L.set(H.present, '2nd'),\n  L.set(H.present, '3rd'),\n  L.set(H.index, 0),\n  H.redoForget,\n  L.get(H.redoIndex)\n)\n// 0\n```\n\n#### \u003ca id=\"H-redoIndex\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#H-redoIndex) [`H.redoIndex ~\u003e numberLens`](#H-redoIndex) \u003csmall\u003e\u003csup\u003ev0.2.0\u003c/sup\u003e\u003c/small\u003e\n\n`H.redoIndex` is a\n[lens](https://github.com/calmm-js/partial.lenses/#partial-lenses) that focuses\non the redo position of history.\n\nFor example:\n\n```js\nthru(\n  H.init({}, '1st'),\n  L.set(H.present, '2nd'),\n  L.set(H.present, '3rd'),\n  L.set(H.index, 0),\n  L.modify(H.redoIndex, n =\u003e n-1),\n  L.get(H.present)\n)\n// '2nd'\n```\n\n### \u003ca id=\"time-travel\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#time-travel) [Time travel](#time-travel)\n\n#### \u003ca id=\"H-count\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#H-count) [`H.count(history) ~\u003e number`](#H-count) \u003csmall\u003e\u003csup\u003ev0.1.0\u003c/sup\u003e\u003c/small\u003e\n\n`H.count` returns the number of entries in history.  See also\n[`H.indexMax`](#H-indexMax).\n\nFor example:\n\n```js\nthru(\n  H.init({}, '1st'),\n  L.set(H.present, '2nd'),\n  L.set(H.present, '3rd'),\n  H.count\n)\n// 3\n```\n\n#### \u003ca id=\"H-index\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#H-index) [`H.index ~\u003e numberLens`](#H-index) \u003csmall\u003e\u003csup\u003ev0.2.0\u003c/sup\u003e\u003c/small\u003e\n\n`H.index` is a\n[lens](https://github.com/calmm-js/partial.lenses/#partial-lenses) that focuses\non the index of present of history.\n\nFor example:\n\n```js\nthru(\n  H.init({}, '1st'),\n  L.set(H.present, '2nd'),\n  L.set(H.present, '3rd'),\n  L.set(H.index, 1),\n  L.get(H.present)\n)\n// '2nd'\n```\n\n#### \u003ca id=\"H-indexMax\"\u003e\u003c/a\u003e [≡](#contents) [▶](https://calmm-js.github.io/partial.lenses.history/index.html#H-indexMax) [`H.indexMax(history) ~\u003e number`](#H-indexMax) \u003csmall\u003e\u003csup\u003ev0.2.3\u003c/sup\u003e\u003c/small\u003e\n\n`H.indexMax` returns the maximum history index.  See also [`H.count`](#H-count).\n\nFor example:\n\n```js\nthru(\n  H.init({}, '1st'),\n  L.set(H.present, '2nd'),\n  L.set(H.present, '3rd'),\n  H.indexMax\n)\n// 2\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcalmm-js%2Fpartial.lenses.history","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcalmm-js%2Fpartial.lenses.history","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcalmm-js%2Fpartial.lenses.history/lists"}