{"id":19451234,"url":"https://github.com/thekashey/kashe","last_synced_at":"2025-04-04T23:09:09.581Z","repository":{"id":34301625,"uuid":"176199473","full_name":"theKashey/kashe","owner":"theKashey","description":"A memoization library based on weakmaps. 🤯 Sometimes cache is kashe","archived":false,"fork":false,"pushed_at":"2025-02-13T02:12:38.000Z","size":486,"stargazers_count":68,"open_issues_count":3,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-28T22:12:29.781Z","etag":null,"topics":["memoization","memoize","reselect","weakmap"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/theKashey.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-03-18T03:40:35.000Z","updated_at":"2024-07-06T14:23:58.000Z","dependencies_parsed_at":"2024-06-11T09:04:48.293Z","dependency_job_id":"f5cfe77b-40b5-4285-86d7-91081d9ead2d","html_url":"https://github.com/theKashey/kashe","commit_stats":{"total_commits":43,"total_committers":2,"mean_commits":21.5,"dds":"0.023255813953488413","last_synced_commit":"a8242a2fbc5bf9c1af74927068ead3a8f5f62af8"},"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Fkashe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Fkashe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Fkashe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Fkashe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/theKashey","download_url":"https://codeload.github.com/theKashey/kashe/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247261612,"owners_count":20910108,"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":["memoization","memoize","reselect","weakmap"],"created_at":"2024-11-10T16:40:50.218Z","updated_at":"2025-04-04T23:09:09.549Z","avatar_url":"https://github.com/theKashey.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003ch1\u003ekashe 🔥\u003c/h1\u003e\n  \u003cbr/\u003e\n  It's like \u003cb\u003ecache\u003c/b\u003e, but with forget-me-bomb... remember it? No?\n  \u003cbr/\u003e\n    \u003ca href=\"https://www.npmjs.com/package/kashe\"\u003e\n      \u003cimg src=\"https://img.shields.io/npm/v/kashe.svg?style=flat-square\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://travis-ci.org/theKashey/kashe\"\u003e\n       \u003cimg alt=\"Travis\" src=\"https://img.shields.io/travis/theKashey/kashe/master.svg?style=flat-square\"\u003e\n    \u003c/a\u003e\n\u003c/div\u003e\n\nA WeakMap based memoization library for a better and safer caching\n\n## Memoization\nMemoization is cool technique. But is it reliable and _safe_?\n\nWhat is the difference between `lodash.memoize`, `memoize-one`, and `React.useMemo`?\n\n- [lodash.memoize](https://lodash.com/docs/4.17.11#memoize) is a cool thing. But by default it has endless cache size.\n- [memoize-one only](https://github.com/alexreardon/memoize-one) remembers the latest arguments and result. No need to worry about __cache busting__ mechanisms such as maxAge, maxSize, exclusions and so on which can be prone to __memory leaks__.\n- [react.useMemo](https://reactjs.org/docs/hooks-reference.html#usememo) is the greatest of all. Still memoize only __one__ call, but doing it on per-component level.\nThe downside of `useMemo` is React. You cannot use it outside of Functional Component.\n\nWhat about `reselect`, a tool powering up all the `redux` ecosystem? Still - __single cache item__.\n\u003e Reselect 5.0 migrated to `weakMapMemoize` which effectively is a `kashe`\n\n- __Is it server-side friendly?__ Nope, server handles many requests from many clients, and memoized value is constantly got wiped.\n- __Is it server-side _safe_?__ Oh no! Cross request memoization could be a killer! What if memoized value not got rejected??\n- __Is it test friendly?__ Nope, tests should always work the same, while memoization will make it... less predictable.\n\nSo - it's time to fix all the problems above. Wanna know more - [read the article](https://dev.to/thekashey/memoization-forget-me-bomb-34kh)\n\n\u003e In short - to better REMEMBER something, you have to better FORGET it\n\n# API\n- kashe/weakKashe - memoization\n## Supporting API\n- box - prefixed memoization\n- inbox - nested prefixed memoization\n- fork - nested memoization\n\n\u003e TLDR: `kashe` uses passed arguments as a key to an internal WeakMap to store a result. It does not store anything permanently - \nit's always _weak_. __Once argument used as a key is \"gone\" - data is \"gone\"__.\n\n### kashe\n- `kashe(function: T):T` - transparent weak memoization. Requires first argument to be an object or array or function. The\nfirst argument would be used to store a result.\n```js\nimport {kashe} from 'kashe';\n\nconst selector = state =\u003e [state.a, state.b];\nconst memoizedSelector = kashe(selector);\nmemoizedSelector(state) === memoizedSelector(state);\n\nconst complexSelector = (state, field) =\u003e ({ field: state[field]});\nconst memoizedComplexSelector = kashe(complexSelector);\nmemoizedComplexSelector(state, 'a') === memoizedComplexSelector(state, 'a');\n```\n`kashe` uses nested memoization, creating a cascade of caches for every \"weakmappable\" argument found.\nThis greatly increases cache size and hit rate.\n\n\n### weakKashe\nFor the cases like selectors and mappers some times it's easier to use __not strict__ cache.\n```js\nconst {weakKashe} from 'kashe';\n\nconst weakMap = weakKashe((data, iterator, ...deps) =\u003e data.map(iterator)); \n\nconst derived = weakMap(data, line =\u003e ({...line, somethingElse}), localVariable1);\n```\nIn this case:\n- cache would be stored in the `data`\n- arguments would be matched not by \"strict\" equality, but by the \"toString\" equality.\n- as a result, the second `kashe` argument, always the new function, would not _destroy_ cache\n- keep in mind - this is not 100% safe operation. Consider adding _local scope_ variables to control cache precision. \n\n### boxed\n- `boxed(function(...args)=\u003eT):(_, ...args)=\u003eT` - \"prefixes\" a call to function with \"weakmappable\" argument. __All arguments__ shall be equal to return a cached result.\nUse `boxed` to make any function kashe-memoizable, buy adding a leading argument.\n\n__Literally \"puts function in a box\"__.\n\n\u003e `boxed` effectively creates a \"fork\" in the cache letting you fine control execution\n```js\nimport {boxed} from 'kashe';\n\nconst addTwo = (a,b) =\u003e a+b; // could not be \"kashe\" memoized\nconst bAddTwo = boxed(addTwo);\nconst cacheKey = {}; // any object\n\nbAddTwo(cacheKey, 1, 2) === bAddTwo(cacheKey, 1, 2) === 3\nbAddTwo(otherCacheKey, 1, 2) // -\u003e a new call\n\nbAddTwo(cacheKey, 10, 20) // -\u003e a new call - arguments dont match\nbAddTwo(cacheKey, 1, 2) // -\u003e a new call - original result replaced by 10+20\n```\n\n### inboxed\n- `inboxed(function(...args)=\u003eT):(_, ...args)=\u003eT` - \"nest\" a call to a function with \"weakmappable\" argument.\nUse `inboxed` to make any function kashe-memoizable, buy adding a leading argument. \n\n\u003e Difference from `boxed` - `inboxed` \"nests\" all the cache below it.\nIt creates a micro-universe giving you fine control over caching behavior and purging caches. Think concurrent server environment.\n\n`inboxed` will affect every other `kashe` call inside it.\n```js\nimport {inboxed} from 'kashe';\n\nconst selector = (state) =\u003e ({state}) // could be \"kashe\"-memoized\nconst memoizedSelector = kashe(selector);\n\nconst bSelector = boxed(memoizedSelector);\nconst ibSelector = inboxed(memoizedSelector);\nconst cacheKey = {}; // any object\n\nibSelector(cacheKey, state) === ibSelector(cacheKey, state)\nibSelector(otherCacheKey, state) // a new call. Other key used for inbox, and other cache would be used for memoizedSelector  \nibSelector(cacheKey, otherState) // a new call\nibSelector(cacheKey, state) // cacheKey has cache for `state`\n\n// but!\nbSelector(cacheKey, state) === bSelector(otherCacheKey, state)\n\n// bSelector is not \"sharing\" it's own result (key is different), but underlaying\n// `memoizedSelector` shares, and `state` argument is the same.\n```\n\n#### The difference between inboxed and boxed\n- `boxed` could __increase__ probability to cache a value\n- `inboxed` could __decrease__ probability to cache a value\n\n`inboxed` is scoping all the _nested_ caches _behind_ a first argument. It if changes - cache changes.\n\u003e Yet again - first argument is WHERE cache is stored.\n\n`boxed` is just storing result in a first argument. If cache is not found it is still possible to discover\nit in a nested cache.\n\n```js\nconst memoizedSelector = kashe(selector);\n\nconst inboxedSelector = inboxed(memoizedSelector);\nconst boxedSelector = boxed(memoizedSelector);\n\n// state1 !== state2. selectors would use different caches, memoizedSelector included\ninboxedSelector(state1, data) !== inboxedSelector(state2, data)\n\n// state1 !== state2. memoization would fail, but memoizedSelector would return the same values\n  boxedSelector(state1, data) ===   boxedSelector(state2, data)\n```\n\n`inboxedSelector` is more memory safe, but CPU intensive. It guratines all selectors would be _clean_ for a session(first argument).\n`boxedSelector` is useful as long as everything here is still holds only ONE result. It may be wiped from nested selector, but still exists in a boxed\n```js\nmemoizedSelector(data1);\nboxedSelector(state, data1); // they are the same\nboxedSelector(state, data2); // updating cache for both selectors\nmemoizedSelector(data2); // they are the same\nmemoizedSelector(data1); // cache is updated\nboxedSelector(state, data2); // !!!! result is still stored in `state`\n```\n\n### fork\n- `fork(function: T):T` - create a copy of a selector, with overidden internal cache.\n`fork` has the same effect `inbox` has, but not adding a leading argument. First argument still expected to be an object, array, or a function.\n```js\nconst selector = (state) =\u003e ({state});\n\nconst memoized = kashe(selector);\nmemoized(state) === memoized(state);\n\nconst forked = fork(memoized);\nmemoized(state) !== memoized(state);\n```\n\n#### Size\n1.01 kb\n\n# Cook-book\n\n## Per-instance one argument memoization\nLet's imagine a simple HOC\n```js\nconst hoc = WrappedComponent =\u003e \u003cSomeStuff\u003e\u003cWrappedComponent/\u003e\u003c/SomeStuff\u003e;\n```\nYou want to call this function 10 times, and always get the same result\n```js\nhoc(ComponentA);\nhoc(ComponentA); // !!! a new call === a new result, a new component, so remount! We dont need it.\n\nconst memoizedHoc = memoizeOne(hoc);\n\nmemoizedHoc(ComponentA);\nmemoizedHoc(ComponentA); // YES! It works as expected!\nmemoizedHoc(ComponentB); // BAM! Previous result got wiped\nmemoizedHoc(ComponentA); // A new result, and BAM! Previous result got wiped \n\nconst kasheHoc = kashe(hoc);\n\nkasheHoc(ComponentA);\nkasheHoc(ComponentA); // YES! It works as expected!\nkasheHoc(ComponentB); // YES! It works as expected! Result is stored in a first argument.\nkasheHoc(ComponentA); // YES! It works as expected! Result is still inside ComponentA \n```\nBut what about concurrent execution, where _scope_ may matter, and where you dont want to leave any traces?\n```js\n// first client\nkasheHoc(ComponentA);\n// second client\nkasheHoc(ComponentA); // We got cached result :(\n\n// lets fix, and \"prefix\" selector\n// using `box` for memoized `kasheHoc` would nullify the effect.\n\nconst boxedKasheHoc = inbox(kasheHoc);\n// first client\nboxedKasheHoc(client1Key, ComponentA);\n// second client\nboxedKasheHoc(client2Key, ComponentA); // another client key - another memoization! \nboxedKasheHoc(client2Key, ComponentB); // another argument key - another memoization!\nboxedKasheHoc(client2Key, ComponentA); // result is cached\n```\n\n# Reselect API\nA `Reselect`-compatible API\n\u003e TLDR: it just replaces default memoization for reselect - `createSelectorCreator(strongMemoize);`. `strongMemoize` - \nis not public API yet. \n\nReselect is a great library, but it has one limitation - stores only one result. There are a few attempts\nto \"fix\" it\n  - [re-reselect](https://github.com/toomuchdesign/re-reselect), to let you store result in a \"buckets\". All that data would be kept in a memory.\n  - [memoize-state](https://github.com/theKashey/memoize-state), to use only data pieces, which matters. Still no problem to _miss a cache_.\n  - constructing selectors in [mapStateToProps factory function](https://react-redux.js.org/api/connect#factory-functions), to create per-instance memoization.\n   That requires a different code structure.\n   \nMagically - `kashe` is ideally compatible with `reselect` API      \n```js\nimport {createSelector} from 'kashe/reselect'\n\nconst getDataSlice = (state, props) =\u003e state[props.sliceId]\nconst dataSelector = createSelector(getDataSlice, slice =\u003e ({slice})) // lets make it harder\n\nconst slice1Value = dataSelector(state, { sliceId: 1 });\nconst slice2Value = dataSelector(state, { sliceId: 2 });\n// the real `reselect` would replace stored value by a new one\n\nconst unknownValue = dataSelector(state, { sliceId: 1 });\n// the real `reselect` would return a new object here\n\n\n// `kashe/reselect` - would return `slice1Value`\n```\n\n## Troubleshoting\n#### solving `Error: No weak-mappable object found to read a cache from.`\nIf all selectors returned a non \"weak-mappable\" object (like array, object, function, symbol) - kashe would throw.\nThis is intentional, as long as it stores cache inside such objects, and without them it could not work.\nHowever, if you think that it should work that way - just give it that \"cache\"\n```js\nconst cache = {};\nconst selector = createSelector(\n  someSelector, \n  () =\u003e cache,  // \u003c---- cache for a selector\n  selectedData =\u003e {/*...*/}\n);\n```\n\n# Memoize-one\n`kashe` could not replace `memoize-one` as long as it requires at least one argument to be a object or array.\nBut if at least one is in list - go for it.\n\n# React.useMemo\nYou may use React.useRef/useState/Context to create and propagate a per-instance, or per-tree variable, you may use\nfor `kashe`\n\n```js\nconst KasheContext = React.createContext();\n// create a \"value provider\". useRef would give you an object you may use\nconst CacheKeyProvider = ({children}) =\u003e (\n  \u003cKasheContext.Provider value={useRef(null)}\u003e{children}\u003c/KasheContext.Provider\u003e\n);\n\nconst memoizedFunction = kashe(aFunction);\n\nconst OtherComponent = () =\u003e {\n  const kasheKey = useContext(KasheContext);\n  const localKasheKey = useRef();\n  // use per-render key to store data\n  const memoizedData1 = memoizedFunction(kasheKey, firstArgument, secondArgument);\n  // use per-instance key to store data\n  const memoizedData2 = memoizedFunction(localKasheKey, firstArgument, secondArgument);\n}\n```\nSo - almost the same as `React.useMemo`, but you might use it in Class Components and `mapStateToProps`.\n\n## Usage in class components\nSee [Don’t Stop the Data Flow in Rendering](https://overreacted.io/writing-resilient-components/#dont-stop-the-data-flow-in-rendering)\nfor details about memoization in react.\n```js\n\n// wrap slowlyCalculateTextColor with leading \"state\" argument \nconst generateTextColor = boxed(slowlyCalculateTextColor);\n\nclass MyComponent extends React.Component {\n  // ...\n  render () {\n    // use `this` as `state`\n    const textColor = generateTextColor(this, this.props.color);\n    return (\n        \u003cbutton className={'Button-' + color + ' Button-text-' + textColor}\u003e\n          {children}\n        \u003c/button\u003e\n    );\n  }\n}\n```\n\n## mapStateToProps\n```js\nconst mapStateToProps = () =\u003e {\n  const selector1 = fork(selectors.selector1);\n  return state =\u003e ({\n    value1: selector1(state), // \"per-instance\" selector\n    value2: selectors.selector2(box, state), // normal selector\n    value3: memoizedFunction(selector1, state.data), // use \"selector1\" as a cache-key for another function\n  })\n};\n```\n\n# See also\nThe nearest analog of `kashe` is [weak-memoize](https://github.com/emotion-js/emotion/tree/master/packages/weak-memoize), but it does accept only one argument.\n\n# Speed\n```html\n// a simple one argument function\nmemoize-one one argument  x 58,277,071 ops/sec ±1.60% (87 runs sampled)\nkashe       one argument  x 19,724,367 ops/sec ±0.76% (91 runs sampled)\n\n// a simple two arguments function\nmemoize-one two arguments x 42,526,871 ops/sec ±0.77% (90 runs sampled)\nkashe       two arguments x 16,929,449 ops/sec ±0.84% (89 runs sampled)\n\n// using more than one object to call - memoize-one is failing, while kashe still works\n// PS: multiply results by 2 \nmemoize-one    two states x   308,917 ops/sec ±0.56% (92 runs sampled)\nkashe          two states x 8,992,170 ops/sec ±0.96% (83 runs sampled)\n```\n\n# Kashe-y?\nWhen I first time I heard my nickname - `kashey` pronounces as `cache` - I decides to create a caching library one day. Here we go.\n\n# License\nMIT\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthekashey%2Fkashe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthekashey%2Fkashe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthekashey%2Fkashe/lists"}