{"_id":"@ahmetilhn/memofy","_rev":"6-001c29ba0e8767c0865c9ef367413cba","name":"@ahmetilhn/memofy","dist-tags":{"latest":"3.0.1"},"versions":{"1.1.0":{"name":"@ahmetilhn/memofy","version":"1.1.0","keywords":["memoization","cache","performance","optimization","function","memoize","typescript","javascript","utility","decorator","usememo","use-memo","memoization","memoizer","cacheable functions","memofy","useMemo"],"author":{"name":"Ahmet ilhan","email":"ahmetilhan.dev@gmail.com"},"license":"MIT","_id":"@ahmetilhn/memofy@1.1.0","maintainers":[{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"}],"homepage":"https://github.com/ahmetilhn/memofy#readme","bugs":{"url":"https://github.com/ahmetilhn/memofy/issues"},"dist":{"shasum":"274d16afb994361b392f7d25e84d7ede0a995b1b","tarball":"https://registry.npmjs.org/@ahmetilhn/memofy/-/memofy-1.1.0.tgz","fileCount":6,"integrity":"sha512-d0ZMizrRdARi6o9VemtH9ib66aSZo8pFSe+fDStSZxmfk074OBI6KJuS+myxht2y+nHdp2Rx+DeH8B/LMKSX0w==","signatures":[{"sig":"MEUCIEKobxJrkOh3tPxgiozM0iba4wzHn/gBPvPpncdVAsm6AiEAg7genyaR4LoGVKyCiKbIGIyW08xOfm2i7OUds52G/sc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":12645},"main":"build/index.js","types":"build/index.d.ts","gitHead":"d0c5dee70fb4597c93a1ad4974bd62dfbf4d85f1","private":false,"scripts":{"dev":"sh ./scripts/dev-build.sh","test":"jest","build":"sh ./scripts/prod-build.sh"},"_npmUser":{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"},"repository":{"url":"git+ssh://git@github.com/ahmetilhn/memofy.git","type":"git"},"_npmVersion":"9.8.1","description":"Prevents re-execution of large javascript functions that have been processed once with the same parameter.","directories":{},"_nodeVersion":"18.18.2","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tslib":"^2.7.0","rollup":"^4.21.0","babel-jest":"^29.7.0","@babel/core":"^7.25.2","@types/jest":"^29.5.12","@babel/preset-env":"^7.25.4","rollup-plugin-dts":"^6.1.1","@rollup/plugin-babel":"^6.0.4","@rollup/plugin-terser":"^0.4.4","jest-environment-jsdom":"^29.7.0","@babel/preset-typescript":"^7.24.7","@rollup/plugin-typescript":"^11.1.6","@rollup/plugin-node-resolve":"^15.2.3"},"peerDependencies":{"amigo-js":"^3.1.1"},"_npmOperationalInternal":{"tmp":"tmp/memofy_1.1.0_1741793537997_0.8057740545248331","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@ahmetilhn/memofy","version":"1.1.1","keywords":["memoization","cache","performance","optimization","function","memoize","typescript","javascript","utility","decorator","usememo","use-memo","memoization","memoizer","cacheable functions","memofy","useMemo"],"author":{"name":"Ahmet ilhan","email":"ahmetilhan.dev@gmail.com"},"license":"MIT","_id":"@ahmetilhn/memofy@1.1.1","maintainers":[{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"}],"homepage":"https://github.com/ahmetilhn/memofy#readme","bugs":{"url":"https://github.com/ahmetilhn/memofy/issues"},"dist":{"shasum":"40239e1d8b55468985b68cdba2892c81ff5366b8","tarball":"https://registry.npmjs.org/@ahmetilhn/memofy/-/memofy-1.1.1.tgz","fileCount":5,"integrity":"sha512-GtOOvmYKft3Id6NmRNt8/j2sihDLhKFsy6caiRmEuzEB9twvthx94JNzSmr3zTjqpKiddO1GjSudCxUEMN0TeA==","signatures":[{"sig":"MEYCIQDMl7zRUbIe6JVF5qlA70CKnuvFdiFbRjbkre0xRmNAKAIhANUAqwI6ZshSXf1Pr4qep5cys//rsMxN67lJUjyLe8hv","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":12084},"main":"build/index.js","types":"build/index.d.ts","gitHead":"99609b7a710b0fef2d5395c1b153cb28ab4233f8","private":false,"scripts":{"dev":"sh ./scripts/dev-build.sh","test":"jest","build":"sh ./scripts/prod-build.sh"},"_npmUser":{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"},"repository":{"url":"git+ssh://git@github.com/ahmetilhn/memofy.git","type":"git"},"_npmVersion":"9.8.1","description":"Prevents re-execution of large javascript functions that have been processed once with the same parameter.","directories":{},"_nodeVersion":"18.18.2","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tslib":"^2.7.0","rollup":"^4.21.0","babel-jest":"^29.7.0","@babel/core":"^7.25.2","@types/jest":"^29.5.12","@babel/preset-env":"^7.25.4","rollup-plugin-dts":"^6.1.1","@rollup/plugin-babel":"^6.0.4","@rollup/plugin-terser":"^0.4.4","jest-environment-jsdom":"^29.7.0","@babel/preset-typescript":"^7.24.7","@rollup/plugin-typescript":"^11.1.6","@rollup/plugin-node-resolve":"^15.2.3"},"peerDependencies":{"amigo-js":"^3.1.1"},"_npmOperationalInternal":{"tmp":"tmp/memofy_1.1.1_1741793951496_0.40975332760721894","host":"s3://npm-registry-packages-npm-production"}},"1.1.2":{"name":"@ahmetilhn/memofy","version":"1.1.2","keywords":["memoization","cache","performance","optimization","function","memoize","typescript","javascript","utility","decorator","usememo","use-memo","memoization","memoizer","cacheable functions","memofy","useMemo"],"author":{"name":"Ahmet ilhan","email":"ahmetilhan.dev@gmail.com"},"license":"MIT","_id":"@ahmetilhn/memofy@1.1.2","maintainers":[{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"}],"homepage":"https://github.com/ahmetilhn/memofy#readme","bugs":{"url":"https://github.com/ahmetilhn/memofy/issues"},"dist":{"shasum":"4b63b820aff1f04d3cc8559be1a634b95238918c","tarball":"https://registry.npmjs.org/@ahmetilhn/memofy/-/memofy-1.1.2.tgz","fileCount":6,"integrity":"sha512-fcI/wu6GZyDfEY6usfkmTCzeQ6SnEfzXyVTwu9m2tZR3CRQ8UMlMutnStq6EHQYunyAQB7BhUWoCfOSu2QBm9Q==","signatures":[{"sig":"MEUCIAvlPfK2XS8TxNWH87+L7sJAUPx6eo0WoQ9pfItJ0wjpAiEAgELNhATg0QmVEtGy/DHYvOP8DMj7wkwwvnh/g5KrQDY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":12332},"main":"build/index.js","types":"build/index.d.ts","gitHead":"6ad134d152929e0212d65ce549e774bee86458da","private":false,"scripts":{"dev":"sh ./scripts/dev-build.sh","test":"jest","build":"sh ./scripts/prod-build.sh"},"_npmUser":{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"},"repository":{"url":"git+ssh://git@github.com/ahmetilhn/memofy.git","type":"git"},"_npmVersion":"9.8.1","description":"Prevents re-execution of large javascript functions that have been processed once with the same parameter.","directories":{},"_nodeVersion":"18.18.2","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tslib":"^2.7.0","rollup":"^4.21.0","babel-jest":"^29.7.0","@babel/core":"^7.25.2","@types/jest":"^29.5.12","@babel/preset-env":"^7.25.4","rollup-plugin-dts":"^6.1.1","@rollup/plugin-babel":"^6.0.4","@rollup/plugin-terser":"^0.4.4","jest-environment-jsdom":"^29.7.0","@babel/preset-typescript":"^7.24.7","@rollup/plugin-typescript":"^11.1.6","@rollup/plugin-node-resolve":"^15.2.3"},"peerDependencies":{"amigo-js":"^3.1.1"},"_npmOperationalInternal":{"tmp":"tmp/memofy_1.1.2_1748379887533_0.6471154510055439","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"@ahmetilhn/memofy","version":"2.0.0","keywords":["memoization","cache","performance","optimization","function","memoize","typescript","javascript","utility","decorator","usememo","use-memo","memoization","memoizer","cacheable functions","memofy","useMemo"],"author":{"name":"Ahmet ilhan","email":"ahmetilhan.dev@gmail.com"},"license":"MIT","_id":"@ahmetilhn/memofy@2.0.0","maintainers":[{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"}],"homepage":"https://github.com/ahmetilhn/memofy#readme","bugs":{"url":"https://github.com/ahmetilhn/memofy/issues"},"dist":{"shasum":"8d1a483a6552bbe1dda2af0d62a7c54e21ba9743","tarball":"https://registry.npmjs.org/@ahmetilhn/memofy/-/memofy-2.0.0.tgz","fileCount":6,"integrity":"sha512-KPgLaxzngcg+F5bb+uaJKLXhA0TXWeCqqTu+gL+ioHHmxTRB3miiDDQZ4XXmucZ6lgu/B3/wz+QPtshDBR4zog==","signatures":[{"sig":"MEUCIG0ChwEyH2mO1PQp6r48DZwIRc6FQtpQPbwfWavsc6YlAiEAlxRtMmUpOiPehF0hLLNn9S0uQaridTuOtEVTOft0fis=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":11909},"main":"build/index.js","types":"build/index.d.ts","gitHead":"546dc9f086c9242d1dca660f13dbea161da50452","private":false,"scripts":{"dev":"sh ./scripts/dev-build.sh","test":"jest","build":"sh ./scripts/prod-build.sh"},"_npmUser":{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"},"repository":{"url":"git+ssh://git@github.com/ahmetilhn/memofy.git","type":"git"},"_npmVersion":"9.8.1","description":"Prevents re-execution of large javascript functions that have been processed once with the same parameter.","directories":{},"_nodeVersion":"18.18.2","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","rollup":"^4.41.1","ts-jest":"^29.3.4","babel-jest":"^29.7.0","typescript":"^5.8.3","@babel/core":"^7.27.4","@types/jest":"^29.5.14","@babel/preset-env":"^7.27.2","rollup-plugin-dts":"^6.2.1","@rollup/plugin-babel":"^6.0.4","@rollup/plugin-terser":"^0.4.4","jest-environment-jsdom":"^29.7.0","@babel/preset-typescript":"^7.27.1","@rollup/plugin-typescript":"^11.1.6","@rollup/plugin-node-resolve":"^16.0.1"},"peerDependencies":{"@ahmetilhn/handy-utils":"^3.2.3"},"_npmOperationalInternal":{"tmp":"tmp/memofy_2.0.0_1749044063351_0.054294995211494834","host":"s3://npm-registry-packages-npm-production"}},"3.0.0":{"name":"@ahmetilhn/memofy","version":"3.0.0","keywords":["memoization","cache","lru","ttl","performance","optimization","function","memoize","typescript","javascript","utility","usememo","use-memo","memoizer","cacheable functions","memofy","react","vue","nuxt","nextjs","ssr"],"author":{"name":"Ahmet ilhan","email":"ahmetilhn.dev@gmail.com"},"license":"MIT","_id":"@ahmetilhn/memofy@3.0.0","maintainers":[{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"}],"homepage":"https://github.com/ahmetilhn/memofy#readme","bugs":{"url":"https://github.com/ahmetilhn/memofy/issues"},"dist":{"shasum":"8322174a313ecba6f84166f1342db60dc9aca397","tarball":"https://registry.npmjs.org/@ahmetilhn/memofy/-/memofy-3.0.0.tgz","fileCount":10,"integrity":"sha512-l+mEWu1/Celo/d8e9VflYitVQW9FXLDEMKrHuUkOpA/OroYcFl6gfqdbILC8HcAIMyfSvzZzUzQi+qKYUGCLyA==","signatures":[{"sig":"MEYCIQCQ4Wb2MHjulwmm7EVC67yyI8fT14qF86acavBCxhrTmQIhAKT/GeLTl70fgb+F8oJb5iKyJI5MKi7sxL5vS7dYnJpv","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ahmetilhn%2fmemofy@3.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":158725},"main":"./build/index.cjs","types":"./build/index.d.ts","unpkg":"./build/index.umd.js","module":"./build/index.mjs","engines":{"node":">=24"},"exports":{".":{"types":"./build/index.d.ts","import":"./build/index.mjs","default":"./build/index.mjs","require":"./build/index.cjs"},"./package.json":"./package.json"},"gitHead":"8b308b07592b4749f7e2c8225614fc4cf8cd0264","private":false,"scripts":{"dev":"sh ./scripts/dev-build.sh","size":"node ./scripts/report-size.mjs","test":"jest","build":"sh ./scripts/prod-build.sh","verify":"npm run typecheck && npm run test && npm run build && npm run size","typecheck":"tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json","prepublishOnly":"npm run verify"},"_npmUser":{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"},"jsdelivr":"./build/index.umd.js","repository":{"url":"git+ssh://git@github.com/ahmetilhn/memofy.git","type":"git"},"_npmVersion":"11.17.0","description":"Fast, dependency-free memoization for JavaScript and TypeScript functions, with dependency tracking, LRU eviction and TTL.","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","_hasShrinkwrap":false,"devDependencies":{"jest":"^30.4.2","tslib":"^2.8.1","rollup":"^4.62.4","babel-jest":"^30.4.1","typescript":"^6.0.3","@babel/core":"^8.0.1","@types/jest":"^30.0.0","@babel/preset-env":"^8.0.2","rollup-plugin-dts":"^6.5.1","@rollup/plugin-terser":"^1.0.0","jest-environment-jsdom":"^30.4.1","@babel/preset-typescript":"^8.0.1","@rollup/plugin-typescript":"^12.3.0"},"_npmOperationalInternal":{"tmp":"tmp/memofy_3.0.0_1786821710234_0.37816581673387706","host":"s3://npm-registry-packages-npm-production"}},"3.0.1":{"name":"@ahmetilhn/memofy","version":"3.0.1","description":"Fast, dependency-free memoization for JavaScript and TypeScript functions, with dependency tracking, LRU eviction and TTL.","main":"./build/index.cjs","module":"./build/index.mjs","types":"./build/index.d.ts","unpkg":"./build/index.umd.js","jsdelivr":"./build/index.umd.js","exports":{".":{"types":"./build/index.d.ts","import":"./build/index.mjs","require":"./build/index.cjs","default":"./build/index.mjs"},"./package.json":"./package.json"},"sideEffects":false,"engines":{"node":">=24"},"scripts":{"build":"sh ./scripts/prod-build.sh","dev":"sh ./scripts/dev-build.sh","test":"jest","typecheck":"tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json","verify":"npm run typecheck && npm run test && npm run build && npm run size","size":"node ./scripts/report-size.mjs","prepublishOnly":"npm run verify"},"repository":{"type":"git","url":"git+ssh://git@github.com/ahmetilhn/memofy.git"},"keywords":["memoization","cache","lru","ttl","performance","optimization","function","memoize","typescript","javascript","utility","usememo","use-memo","memoizer","cacheable functions","memofy","react","vue","nuxt","nextjs","ssr"],"private":false,"author":{"name":"Ahmet ilhan","email":"ahmetilhn.dev@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/ahmetilhn/memofy/issues"},"homepage":"https://github.com/ahmetilhn/memofy#readme","devDependencies":{"@babel/core":"^8.0.1","@babel/preset-env":"^8.0.2","@babel/preset-typescript":"^8.0.1","@rollup/plugin-terser":"^1.0.0","@rollup/plugin-typescript":"^12.3.0","@types/jest":"^30.0.0","babel-jest":"^30.4.1","jest":"^30.4.2","jest-environment-jsdom":"^30.4.1","rollup":"^4.62.4","rollup-plugin-dts":"^6.5.1","tslib":"^2.8.1","typescript":"^6.0.3"},"gitHead":"434b74a2fb5a083d4bddc32b5edd27404520f305","_id":"@ahmetilhn/memofy@3.0.1","_nodeVersion":"24.19.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-DHJas7LIHHVAu1oAuWr9gMlmeJtnW86lTeIRbCAj3Y7CDmh1C/lpb/r0J+egiuSig5jTLzBx3eWBC8saTTTFHQ==","shasum":"381f78fd5c3f2623343ceaf6982ef6dc2b631400","tarball":"https://registry.npmjs.org/@ahmetilhn/memofy/-/memofy-3.0.1.tgz","fileCount":10,"unpackedSize":169593,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ahmetilhn%2fmemofy@3.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIFTy+RoslUUE9XbMjLSeuD0bi/cVUd8M4Xr4f3Ow2Ht/AiAgIczfWMatj2wZ6Kjqd9D83inat5qiPGHlHZWR4VsnCA=="}]},"_npmUser":{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"},"directories":{},"maintainers":[{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/memofy_3.0.1_1786824019653_0.9789292028894478"},"_hasShrinkwrap":false}},"time":{"created":"2025-03-12T15:32:17.873Z","modified":"2026-08-15T20:00:20.115Z","1.1.0":"2025-03-12T15:32:18.176Z","1.1.1":"2025-03-12T15:39:11.719Z","1.1.2":"2025-05-27T21:04:47.712Z","2.0.0":"2025-06-04T13:34:23.518Z","3.0.0":"2026-08-15T19:21:50.380Z","3.0.1":"2026-08-15T20:00:19.804Z"},"bugs":{"url":"https://github.com/ahmetilhn/memofy/issues"},"author":{"name":"Ahmet ilhan","email":"ahmetilhn.dev@gmail.com"},"license":"MIT","homepage":"https://github.com/ahmetilhn/memofy#readme","keywords":["memoization","cache","lru","ttl","performance","optimization","function","memoize","typescript","javascript","utility","usememo","use-memo","memoizer","cacheable functions","memofy","react","vue","nuxt","nextjs","ssr"],"repository":{"type":"git","url":"git+ssh://git@github.com/ahmetilhn/memofy.git"},"description":"Fast, dependency-free memoization for JavaScript and TypeScript functions, with dependency tracking, LRU eviction and TTL.","maintainers":[{"name":"ahmetilhn","email":"ahmetilhan.dev@gmail.com"}],"readme":"# memofy\n\n[![npm version](https://img.shields.io/npm/v/@ahmetilhn/memofy.svg)](https://www.npmjs.com/package/@ahmetilhn/memofy)\n[![npm downloads](https://img.shields.io/npm/dm/@ahmetilhn/memofy.svg)](https://www.npmjs.com/package/@ahmetilhn/memofy)\n[![license](https://img.shields.io/npm/l/@ahmetilhn/memofy.svg)](./LICENSE)\n\nMemoization for JavaScript and TypeScript functions. Skips re-running expensive\nwork when the arguments have already been seen.\n\n- **Zero dependencies.** 2.2 KB gzipped, self-contained.\n- **Constant-time lookups.** Arguments are serialised into a structural key, so\n  a cache hit costs the same at 10 entries and at 50 000.\n- **Bounded by default.** LRU eviction with an optional TTL, so a long-running\n  app cannot leak memory through the cache.\n- **Correct on hard inputs.** Circular references, `NaN`, `-0`, `Map`, `Set`,\n  `Date`, `RegExp`, `URL`, typed arrays and class instances all key correctly.\n- **Works anywhere.** ESM, CommonJS and UMD builds; SSR-safe; no framework\n  coupling. React, Vue, Nuxt, Next, Svelte or plain `<script>`.\n- **100% test coverage**, enforced in CI across 182 tests.\n\n---\n\n## Table of contents\n\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Quick start](#quick-start)\n- [When to use it](#when-to-use-it)\n- [API reference](#api-reference)\n  - [`new Memofy(params?)`](#new-memofyparams)\n  - [`memofy.memoize(fn, deps?, context?, options?)`](#memofymemoizefn-deps-context-options)\n  - [`memofy.clear()` / `memofy.init()`](#memofyclear--memofyinit)\n  - [The memoized function](#the-memoized-function)\n  - [`createKey(args)`](#createkeyargs)\n  - [Exported types](#exported-types)\n- [Guides](#guides)\n  - [Dependencies](#dependencies)\n  - [Context and `this`](#context-and-this)\n  - [Async functions](#async-functions)\n  - [Cache size and expiry](#cache-size-and-expiry)\n  - [Custom keys with `resolver`](#custom-keys-with-resolver)\n  - [Debugging](#debugging)\n- [Framework integration](#framework-integration)\n- [How arguments are compared](#how-arguments-are-compared)\n- [Performance](#performance)\n- [Bundle size](#bundle-size)\n- [FAQ and troubleshooting](#faq-and-troubleshooting)\n- [Migrating from v2](#migrating-from-v2)\n- [Contributing](#contributing)\n- [Releasing](#releasing)\n- [License](#license)\n\n---\n\n## Requirements\n\nNode.js **24 or newer**. In the browser, any engine supporting ES2022 — every\nevergreen release since 2022.\n\n## Installation\n\n```bash\nnpm install @ahmetilhn/memofy\n```\n\n```bash\nyarn add @ahmetilhn/memofy\n```\n\n## Quick start\n\n```js\nimport Memofy from \"@ahmetilhn/memofy\";\n\nconst memofy = new Memofy();\n\nconst slowSum = (a, b) => {\n  // pretend this is expensive\n  return a + b;\n};\n\nconst sum = memofy.memoize(slowSum);\n\nsum(1, 2); // runs slowSum\nsum(1, 2); // returns the cached result\n```\n\nNamed and CommonJS imports work too:\n\n```js\nimport { Memofy } from \"@ahmetilhn/memofy\";\nconst { Memofy } = require(\"@ahmetilhn/memofy\");\n```\n\nBrowser, no build step:\n\n```html\n<script src=\"https://unpkg.com/@ahmetilhn/memofy\"></script>\n<script>\n  const memofy = new memofy.Memofy();\n</script>\n```\n\n## When to use it\n\nMemoization trades memory for time, and building the cache key is not free.\nRoughly:\n\n| Situation                                              | Worth memoizing? |\n| ------------------------------------------------------ | ---------------- |\n| Function takes longer than ~1 µs and repeats its inputs | Yes              |\n| Parsing, formatting, tree walking, derived state        | Yes              |\n| Deduplicating in-flight network requests                | Yes              |\n| Trivial arithmetic, property access                     | No — the key costs more |\n| Every call has unique arguments                         | No — pure overhead |\n| The function is not pure (reads a clock, random, I/O state) | No — you will cache stale answers |\n\nSee [Performance](#performance) for measured key-building costs.\n\n---\n\n## API reference\n\n### `new Memofy(params?)`\n\nCreates an isolated cache universe. Functions memoized by one instance never\nshare results with another.\n\n```ts\nnew Memofy(params?: MemofyParams)\n```\n\n| Option                  | Type      | Default | Description                                                          |\n| ----------------------- | --------- | ------- | -------------------------------------------------------------------- |\n| `maxSize`               | `number`  | `1000`  | Cached results per memoized function. `0` or `Infinity` = unbounded.  |\n| `ttl`                   | `number`  | —       | Milliseconds a result stays valid. Omitted means no expiry.           |\n| `cacheRejectedPromises` | `boolean` | `false` | Keep rejected promises cached instead of evicting them.               |\n| `hasLogs`               | `boolean` | `false` | Log every cache hit to the console.                                   |\n| `trace`                 | `boolean` | `false` | Expose the instance as `window.__memofy__` in browsers.               |\n\n```js\nconst memofy = new Memofy({ maxSize: 500, ttl: 60_000 });\n```\n\nInvalid values fall back to the default rather than throwing: a negative\n`maxSize`, a non-numeric `ttl` or a non-boolean flag is ignored.\n\n---\n\n### `memofy.memoize(fn, deps?, context?, options?)`\n\nReturns a memoized copy of `fn` with the same call signature.\n\n```ts\nmemoize<F extends AnyFunction>(\n  fn: F,\n  deps?: Dependencies,\n  context?: ThisParameterType<F>,\n  options?: MemoizeOptions<F>\n): Memoized<F>\n```\n\n| Parameter | Type                                      | Description                                                                 |\n| --------- | ----------------------------------------- | --------------------------------------------------------------------------- |\n| `fn`      | `F`                                       | The function to memoize.                                                     |\n| `deps`    | `unknown[] \\| (() => unknown[])`          | Invalidate the cache when these change. See [Dependencies](#dependencies).   |\n| `context` | `ThisParameterType<F>`                    | Bound as `this`. See [Context and `this`](#context-and-this).                |\n| `options` | `MemoizeOptions<F>`                       | Per-function overrides.                                                      |\n\n`options` accepts `maxSize`, `ttl` and `cacheRejectedPromises` — overriding the\ninstance defaults — plus:\n\n| Option     | Type                              | Description                                                                       |\n| ---------- | --------------------------------- | --------------------------------------------------------------------------------- |\n| `resolver` | `(...args: Parameters<F>) => string` | Build the cache key yourself. See [Custom keys](#custom-keys-with-resolver).   |\n\nEach call to `memoize` creates its **own** cache. Memoizing the same function\ntwice gives two independent caches:\n\n```js\nconst a = memofy.memoize(fn);\nconst b = memofy.memoize(fn);\na(1); // runs\nb(1); // runs again — separate caches\n```\n\n---\n\n### `memofy.clear()` / `memofy.init()`\n\n```ts\nclear(): void\ninit(): void\n```\n\n`clear()` empties the caches of every function memoized by this instance.\n`init()` does the same and re-publishes `window.__memofy__` when `trace` is on;\ncalling it is optional, since the constructor already runs it.\n\n```js\nmemofy.clear(); // every memoized function starts cold again\n```\n\nCaches are held inside each memoized function's closure, so `clear()` runs in\nconstant time regardless of how many functions exist, and a memoized function\nthat goes out of scope takes its cache with it — nothing keeps it alive.\n\n---\n\n### The memoized function\n\nThe returned function keeps `fn`'s parameters and return type, and adds two\nmembers:\n\n```ts\ntype Memoized<F> = OmitThisParameter<F> & {\n  clear: () => void;\n  readonly size: number;\n};\n```\n\n```js\nconst double = memofy.memoize((n) => n * 2);\n\ndouble(1);\ndouble(2);\ndouble(1);\n\ndouble.size; // 2 — distinct argument sets cached\ndouble.clear(); // drop just this function's cache\ndouble.size; // 0\n```\n\n`this` is removed from the signature because the `context` you pass to\n`memoize` is already bound — callers invoke it as a plain function.\n\n---\n\n### `createKey(args)`\n\nThe key builder is exported for testing, debugging, or writing a `resolver` on\ntop of it.\n\n```ts\ncreateKey(args: ArrayLike<unknown>): string\n```\n\n```js\nimport { createKey } from \"@ahmetilhn/memofy\";\n\ncreateKey([1, \"a\"]) === createKey([1, \"a\"]); // true\ncreateKey([{ a: 1, b: 2 }]) === createKey([{ b: 2, a: 1 }]); // true — order-free\ncreateKey([1]) === createKey([\"1\"]); // false — type-tagged\n```\n\nThrows a `RangeError` if the arguments nest deeper than 500 levels.\n\n---\n\n### Exported types\n\n```ts\nimport type {\n  AnyFunction,\n  Dependencies,\n  Memoized,\n  MemoizeOptions,\n  MemofyParams,\n} from \"@ahmetilhn/memofy\";\n```\n\n---\n\n## Guides\n\n### Dependencies\n\nPass `deps` to invalidate the cache when something outside the arguments\nchanges — the same idea as React's `useMemo`.\n\n```js\nconst settings = { taxRate: 0.2 };\n\nconst total = memofy.memoize(\n  (price) => price * (1 + settings.taxRate),\n  [settings]\n);\n\ntotal(100); // 120 — runs\ntotal(100); // 120 — cached\n\nsettings.taxRate = 0.1;\ntotal(100); // 110 — dependency changed, so it runs again\n```\n\nA dependency change clears the **whole** cache for that function, not just the\ncurrent arguments.\n\n**Array form vs getter form.** An array is captured once, so it can only observe\nmutations *inside* the values it holds. To track a rebindable primitive, pass a\ngetter — it is re-read on every call:\n\n```js\nlet taxRate = 0.2;\n\n// Wrong: taxRate was copied into the array and can never change.\nmemofy.memoize(fn, [taxRate]);\n\n// Right: re-read on every call.\nmemofy.memoize(fn, () => [taxRate]);\n```\n\nDependencies are compared with the same structural rules as arguments, so\nobjects, `Map`s and `Date`s all work.\n\n---\n\n### Context and `this`\n\nThe third parameter binds `this`. Each `memoize` call has its own cache, so two\ncontexts never share results.\n\n```js\nfunction greet() {\n  return `hi ${this.name}`;\n}\n\nconst greetAda = memofy.memoize(greet, [], { name: \"Ada\" });\nconst greetGrace = memofy.memoize(greet, [], { name: \"Grace\" });\n\ngreetAda(); // \"hi Ada\"\ngreetGrace(); // \"hi Grace\"\n```\n\nFor class instances, memoize in the constructor so each instance gets its own\ncache:\n\n```js\nclass Cart {\n  constructor(items) {\n    this.items = items;\n    this.total = memofy.memoize(\n      function () {\n        return this.items.reduce((sum, item) => sum + item.price, 0);\n      },\n      [this.items],\n      this\n    );\n  }\n}\n```\n\nTypeScript checks the context against the function's `this` parameter:\n\n```ts\nfunction greet(this: { name: string }): string {\n  return this.name;\n}\n\nmemofy.memoize(greet, [], { name: \"Ada\" }); // ok\nmemofy.memoize(greet, [], { wrong: true }); // compile error\n```\n\n---\n\n### Async functions\n\nA promise is cached like any other value, so concurrent calls with the same\narguments share one in-flight request.\n\n```js\nconst loadUser = memofy.memoize(async (id) => {\n  const response = await fetch(`/api/users/${id}`);\n  return response.json();\n});\n\nawait Promise.all([loadUser(1), loadUser(1)]); // one request\n```\n\n**Rejected promises are evicted once they settle**, so a transient failure does\nnot poison that argument set for the process lifetime:\n\n```js\nawait loadUser(1); // network error → rejects, entry removed\nawait loadUser(1); // tries again\n```\n\nSet `cacheRejectedPromises: true` to keep failures cached — useful when the\nfailure is deterministic and retrying is pointless.\n\nNote that attaching the eviction handler marks the promise as handled, so an\nunawaited rejection will not raise an unhandled-rejection warning from memofy's\nside. Handle the promise you receive as you normally would.\n\n---\n\n### Cache size and expiry\n\nEvery memoized function gets a bounded LRU cache — 1000 entries by default.\nReading an entry marks it most-recently-used, so hot entries survive.\n\n```js\nconst run = memofy.memoize(fn, [], undefined, { maxSize: 50 });\n\nfor (let i = 0; i < 10_000; i++) run(i);\nrun.size; // 50\n```\n\nOpt out with `maxSize: 0` (or `Infinity`) when you know the input space is small\nand bounded:\n\n```js\nconst memofy = new Memofy({ maxSize: 0 });\n```\n\n`ttl` expires entries by age. Expiry is evaluated lazily on read — memofy never\nschedules a timer, so it cannot keep a Node process alive or leak handles:\n\n```js\nconst rates = memofy.memoize(fetchRates, [], undefined, { ttl: 60_000 });\n```\n\n---\n\n### Custom keys with `resolver`\n\nWhen a cheap discriminator already exists, skip structural serialisation\nentirely:\n\n```js\nconst render = memofy.memoize(\n  (user) => expensiveTemplate(user),\n  [],\n  undefined,\n  { resolver: (user) => `${user.id}:${user.updatedAt}` }\n);\n```\n\nThis is worth reaching for when:\n\n- arguments are large (serialising a 100-element array costs ~3 µs),\n- arguments contain getters with side effects,\n- only part of the argument actually affects the result.\n\nThe resolver runs with the same `context` as the function, and TypeScript types\nits parameters from `fn`.\n\n---\n\n### Debugging\n\n`hasLogs` logs every cache hit:\n\n```js\nconst memofy = new Memofy({ hasLogs: true });\n// memofy: \"getTotal\" returned a cached value. 42\n```\n\n`trace` exposes the instance in browsers so you can inspect or clear from the\nconsole:\n\n```js\nnew Memofy({ trace: true });\n// window.__memofy__.clear()\n```\n\nBoth are no-ops outside the browser, so leaving `trace` on will not break SSR.\n\n---\n\n## Framework integration\n\nmemofy is framework-agnostic; there is nothing to install beyond the package.\n\n### React\n\nCreate the instance once, outside the component, and memoize outside render.\n\n```jsx\nimport Memofy from \"@ahmetilhn/memofy\";\n\nconst memofy = new Memofy();\nconst formatRows = memofy.memoize((rows, locale) =>\n  expensiveFormat(rows, locale)\n);\n\nfunction Table({ rows, locale }) {\n  return <tbody>{formatRows(rows, locale).map(renderRow)}</tbody>;\n}\n```\n\nUnlike `useMemo`, the cache survives unmounts and is shared across components,\nwhich is the point when the same inputs recur. Use `useMemo` for per-component\nvalues tied to a render; use memofy for pure computations shared app-wide.\n\n### Vue / Nuxt (client side)\n\n```js\n// composables/useFormatter.js\nimport Memofy from \"@ahmetilhn/memofy\";\n\nconst memofy = new Memofy();\n\nexport const formatPrice = memofy.memoize((cents, currency) =>\n  new Intl.NumberFormat(currency).format(cents / 100)\n);\n```\n\n### Svelte\n\n```js\n// lib/search.js\nimport Memofy from \"@ahmetilhn/memofy\";\n\nconst memofy = new Memofy({ maxSize: 200 });\nexport const search = memofy.memoize((query, items) => rank(query, items));\n```\n\n### SSR: Nuxt, Next, and any Node server\n\nA module-level instance is shared by every request in the same process. That is\nfine for pure functions of their arguments, and **wrong** for anything scoped to\na user — one visitor would be served another's cached result.\n\nFor per-user work, create an instance per request:\n\n```js\n// Next.js route handler\nexport async function GET(request) {\n  const memofy = new Memofy(); // isolated to this request\n  const loadOrders = memofy.memoize((userId) =>\n    db.orders.findMany({ userId })\n  );\n\n  return Response.json(await loadOrders(getUserId(request)));\n}\n```\n\n```js\n// Nuxt server route\nexport default defineEventHandler(async (event) => {\n  const memofy = new Memofy();\n  const loadCart = memofy.memoize((userId) => fetchCart(userId));\n\n  return loadCart(await getUserId(event));\n});\n```\n\nFor process-wide caches of genuinely shared data, keep the module-level instance\nand set a `ttl` so entries do not go stale.\n\n### Plain JavaScript\n\n```html\n<script type=\"module\">\n  import Memofy from \"https://unpkg.com/@ahmetilhn/memofy/build/index.mjs\";\n\n  const memofy = new Memofy();\n  const fib = memofy.memoize((n) => (n < 2 ? n : fib(n - 1) + fib(n - 2)));\n  console.log(fib(35));\n</script>\n```\n\n---\n\n## How arguments are compared\n\nArguments are serialised into a type-tagged structural key, which is what makes\nlookups constant-time and comparisons predictable:\n\n| Input                                      | Behaviour                                          |\n| ------------------------------------------- | -------------------------------------------------- |\n| Primitives                                  | `Object.is` semantics — `NaN` matches, `0 ≠ -0`     |\n| Plain objects, arrays                       | By content; key order does not matter               |\n| `Date`, `RegExp`, `Map`, `Set`, `Error`     | By value; `Map`/`Set` ignore insertion order        |\n| `URL`, `URLSearchParams`                    | By their string form                                |\n| Typed arrays, `ArrayBuffer`, `DataView`     | Byte by byte                                        |\n| Class instances                             | By prototype **and** own fields                     |\n| Functions, `WeakMap`, `Promise`, DOM nodes  | By reference — they expose no structure             |\n| Circular references                         | Handled                                             |\n\nOnly own enumerable properties participate, including symbol keys.\n\nTwo caveats worth knowing:\n\n- Building a key reads getters, so a getter with side effects will fire.\n- If a key cannot be built — a getter throws, or nesting exceeds 500 levels —\n  memofy calls the original function directly. You lose caching, never\n  correctness.\n\nBoth are avoidable with a [`resolver`](#custom-keys-with-resolver).\n\n---\n\n## Performance\n\n`node scripts/bench.mjs` on Node 24, Apple Silicon:\n\n| Benchmark                              | Result         |\n| -------------------------------------- | -------------- |\n| Expensive function, cached vs uncached | **72x** faster |\n| Cache hit, 100-entry cache             | 0.20 µs        |\n| Cache hit, 1 000-entry cache           | 0.17 µs        |\n| Cache hit, 10 000-entry cache          | 0.16 µs        |\n| Cache hit, 50 000-entry cache          | 0.13 µs        |\n| Key building, two primitive arguments  | 0.15 µs        |\n| Key building, small object             | 0.51 µs        |\n| Key building, array of 100 numbers     | 3.18 µs        |\n\nCache hit cost does not grow with cache size. Recency is tracked with a\ndoubly-linked list rather than by re-inserting into a `Map`: V8 rehashes an\nordered hash map on deletion, so the obvious `delete` + `set` promotion costs\n~20 µs per read on a 10k-entry cache, while relinking two pointers stays flat.\n\nMemoize functions that cost more than roughly a microsecond; below that, key\nbuilding dominates.\n\n---\n\n## Bundle size\n\n| Build          | Raw     | Gzip    | Brotli  |\n| -------------- | ------- | ------- | ------- |\n| UMD (minified) | 5.63 KB | 2.23 KB | 2.03 KB |\n\nESM and CommonJS builds ship unminified with source maps for your bundler to\nprocess. The package is side-effect free and fully tree-shakeable — importing\nwithout using it leaves nothing behind.\n\nEntry points:\n\n| Consumer               | File               |\n| ---------------------- | ------------------ |\n| ESM / bundlers         | `build/index.mjs`  |\n| CommonJS               | `build/index.cjs`  |\n| `<script>` / CDN       | `build/index.umd.js` (global `memofy`) |\n| TypeScript             | `build/index.d.ts` |\n\n---\n\n## FAQ and troubleshooting\n\n**My function still runs every time.**\nMost often the arguments differ in a way you did not expect — a fresh callback\nor a `Date` created per call. `createKey([...args])` on two calls tells you\nimmediately whether they key the same. A `deps` getter returning a new object\nevery call has the same effect.\n\n**Two different inputs return the same result.**\nThat should not happen. Every type is tagged and every payload length-prefixed\nprecisely to avoid collisions. If you find one, it is a bug worth reporting.\n\n**Can I memoize a method and keep `this`?**\nYes — pass the instance as the third argument. See\n[Context and `this`](#context-and-this).\n\n**Does the cache leak memory?**\nNo. Every cache is bounded (1000 entries by default), holds no timers, and lives\nin the memoized function's closure, so dropping the function drops the cache.\nPassing `maxSize: 0` opts into an unbounded cache; that one is on you.\n\n**Can I share a cache between two memoized functions?**\nNo, by design — that was the source of several v2 bugs. Memoize once and share\nthe returned function.\n\n**Does it work with recursion?**\nYes, if the recursive call goes through the memoized name:\n\n```js\nconst fib = memofy.memoize((n) => (n < 2 ? n : fib(n - 1) + fib(n - 2)));\n```\n\n**Is it safe in SSR?**\nYes, provided you scope instances correctly. See\n[SSR](#ssr-nuxt-next-and-any-node-server).\n\n---\n\n## Migrating from v2\n\nv2 had defects that made memoization silently incorrect. The fixes change\nobservable behaviour, hence the major version.\n\n| Behaviour                          | v2                                      | v3                           |\n| ---------------------------------- | --------------------------------------- | ---------------------------- |\n| Falsy results (`0`, `\"\"`, `false`) | Never cached, re-inserted on every call | Cached                       |\n| Zero-argument functions            | Never cached                            | Cached                       |\n| `context`                          | Not part of the key; contexts collided  | Each `memoize` has own cache |\n| Dependency change                  | Fresh value once, then stale forever    | Always fresh                 |\n| `URL`, `Map`, `Set`, `RegExp` args | All collided into one entry             | Compared by value            |\n| `NaN` argument                     | Never hit, grew the cache               | Cached                       |\n| Circular argument                  | Threw `RangeError`                      | Handled                      |\n| Rejected promises                  | Cached forever                          | Evicted once settled         |\n| Cache size                         | Unbounded                               | LRU, 1000 per function       |\n| Lookup cost                        | O(n) deep-equality scan                 | O(1)                         |\n| `@ahmetilhn/handy-utils`           | Required peer dependency                | Removed                      |\n| Minimum Node.js                    | Unspecified                             | 24                           |\n\nRequired code changes:\n\n```diff\n- import { initMemofy } from \"@ahmetilhn/memofy\";\n- const memofy = new Memofy();\n+ import Memofy from \"@ahmetilhn/memofy\";\n+ const memofy = new Memofy();\n```\n\n- `initMemofy` no longer exists; the constructor initialises the instance.\n- `npm uninstall @ahmetilhn/handy-utils` unless you use it directly.\n- If you relied on an unbounded cache, pass `{ maxSize: 0 }`.\n- If you depended on primitive `deps` never invalidating, that was a bug; use\n  the getter form to track them.\n\n---\n\n## Contributing\n\n```bash\nnpm install\nnpm run verify   # typecheck, test with coverage, build, bundle-size budget\nnpm run bench    # performance numbers\n```\n\n182 tests, enforced at 100% for statements, branches, functions and lines.\n\n## Releasing\n\nReleases are automatic. Bump `version` in `package.json` and merge to `master`:\n\n```bash\nnpm version patch   # or minor / major\ngit push origin master\n```\n\nThe publish workflow then type-checks, tests, builds, checks the bundle-size\nbudget, installs the packed tarball into a clean project to confirm both entry\npoints work, publishes to npm with provenance, and pushes a `v<version>` tag.\n\nA commit that does not change the version is not an error — the workflow sees\nthe version already on npm and skips the release.\n\n## License\n\nMIT © [Ahmet ilhan](https://github.com/ahmetilhn)\n","readmeFilename":"README.md"}