{"_id":"@-xun/memoize","_rev":"1-27b547e2a17392d9a1a92de1cd297c32","name":"@-xun/memoize","dist-tags":{"latest":"1.1.0"},"versions":{"1.0.0":{"name":"@-xun/memoize","version":"1.0.0","keywords":[],"author":{"name":"Xunnamius"},"license":"MIT","_id":"@-xun/memoize@1.0.0","maintainers":[{"name":"xunnamius","email":"git@bernarddickens.dev"}],"homepage":"https://github.com/Xunnamius/memoize#readme","bugs":{"url":"https://github.com/Xunnamius/memoize/issues"},"dist":{"shasum":"8de7a9b1dddf7fd4dc49f99a9c8187b0344d06ad","tarball":"https://registry.npmjs.org/@-xun/memoize/-/memoize-1.0.0.tgz","fileCount":11,"integrity":"sha512-u65xVwE+1rXR9UxmZfFeNRyt3Hav6CdtsTvP3Y8CnakQjMlpuyKF3Wnw4VTXudJ1K28bbvbiSXnUTxaiBz2sXw==","signatures":[{"sig":"MEUCIQCud8Q8/5zVM+1Z0KzkEQqm+5vIE0KtGBkR0YEuvuziPgIgfCs5xXRlWacp2p3yDWp7VAffKAXw0aa2kLTKvjE6zso=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":47247},"type":"commonjs","engines":{"node":"^18.20.0 || ^20.18.0 || ^22.12.0 || >=23.3.0"},"exports":{".":{"types":"./dist/src/index.d.ts","default":"./dist/src/index.js"},"./package":"./package.json","./package.json":"./package.json"},"gitHead":"11138b1d8b9c708294d37b0a10d8c79273a2c235","scripts":{"info":"symbiote project info --env NODE_NO_WARNINGS=1","lint":"npm run lint:package --","test":"npm run test:package:unit --","build":"npm run build:dist --","clean":"symbiote clean --env NODE_NO_WARNINGS=1","start":"symbiote start --env NODE_NO_WARNINGS=1 --","format":"symbiote format --env NODE_NO_WARNINGS=1 --hush","prepare":"symbiote project prepare --env NODE_NO_WARNINGS=1","release":"symbiote release --env NODE_NO_WARNINGS=1","renovate":"symbiote project renovate --env NODE_NO_WARNINGS=1 --github-reconfigure-repo --regenerate-assets --assets-preset 'basic cli lib lib-esm lib-web react nextjs'","build:dist":"symbiote build distributables --env NODE_NO_WARNINGS=1 --not-multiversal","build:docs":"symbiote build docs --env NODE_NO_WARNINGS=1","list-tasks":"symbiote list-tasks --env NODE_NO_WARNINGS=1 --scope this-package","lint:package":"symbiote lint --env NODE_NO_WARNINGS=1 --hush","lint:project":"symbiote project lint --env NODE_NO_WARNINGS=1","lint:packages":"symbiote lint --env NODE_NO_WARNINGS=1 --hush --scope unlimited","build:changelog":"symbiote build changelog --env NODE_NO_WARNINGS=1","test:package:all":"symbiote test --env NODE_NO_WARNINGS=1 --coverage","test:package:e2e":"symbiote test --env NODE_NO_WARNINGS=1 --tests end-to-end","test:package:unit":"symbiote test --env NODE_NO_WARNINGS=1 --tests unit","test:packages:all":"symbiote test --env NODE_NO_WARNINGS=1 --scope unlimited --coverage","test:package:integration":"symbiote test --env NODE_NO_WARNINGS=1 --tests integration"},"_npmUser":{"name":"xunnamius","email":"git@bernarddickens.dev"},"repository":{"url":"git+https://github.com/Xunnamius/memoize.git","type":"git"},"_npmVersion":"10.9.2","description":"An extensible memoization cache and global singleton used to speed up expensive function calls","directories":{},"sideEffects":false,"_nodeVersion":"22.13.1","dependencies":{"core-js":"^3.40.0","rejoinder":"^1.2.4","type-fest":"^4.33.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"typesVersions":{"*":{"index":["dist/src/index.d.ts"],"package":["package.json"]}},"_hasShrinkwrap":false,"devDependencies":{"@-xun/symbiote":"^2.18.1"},"_npmOperationalInternal":{"tmp":"tmp/memoize_1.0.0_1738397561751_0.5847482965898818","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@-xun/memoize","version":"1.1.0","description":"An extensible memoization cache and global singleton used to speed up expensive function calls","keywords":[],"homepage":"https://github.com/Xunnamius/memoize#readme","bugs":{"url":"https://github.com/Xunnamius/memoize/issues"},"repository":{"type":"git","url":"git+https://github.com/Xunnamius/memoize.git"},"license":"MIT","author":{"name":"Xunnamius"},"sideEffects":false,"type":"commonjs","exports":{".":{"types":"./dist/src/index.d.ts","default":"./dist/src/index.js"},"./package":"./package.json","./package.json":"./package.json"},"typesVersions":{"*":{"index":["dist/src/index.d.ts"],"package":["package.json"]}},"scripts":{"build":"npm run build:dist --","build:changelog":"symbiote build changelog --env NODE_NO_WARNINGS=1","build:dist":"symbiote build distributables --env NODE_NO_WARNINGS=1 --not-multiversal","build:docs":"symbiote build docs --env NODE_NO_WARNINGS=1","clean":"symbiote clean --env NODE_NO_WARNINGS=1","format":"symbiote format --env NODE_NO_WARNINGS=1 --hush","info":"symbiote project info --env NODE_NO_WARNINGS=1","lint":"npm run lint:package --","lint:package":"symbiote lint --env NODE_NO_WARNINGS=1 --hush","lint:packages":"symbiote lint --env NODE_NO_WARNINGS=1 --hush --scope unlimited","lint:project":"symbiote project lint --env NODE_NO_WARNINGS=1","list-tasks":"symbiote list-tasks --env NODE_NO_WARNINGS=1 --scope this-package","prepare":"symbiote project prepare --env NODE_NO_WARNINGS=1","release":"symbiote release --env NODE_NO_WARNINGS=1","renovate":"symbiote project renovate --env NODE_NO_WARNINGS=1 --github-reconfigure-repo --regenerate-assets --assets-preset 'basic cli lib lib-esm lib-web react nextjs'","start":"symbiote start --env NODE_NO_WARNINGS=1 --","test":"npm run test:package:unit --","test:package:all":"symbiote test --env NODE_NO_WARNINGS=1 --coverage","test:package:e2e":"symbiote test --env NODE_NO_WARNINGS=1 --tests end-to-end","test:package:integration":"symbiote test --env NODE_NO_WARNINGS=1 --tests integration","test:package:unit":"symbiote test --env NODE_NO_WARNINGS=1 --tests unit","test:packages:all":"symbiote test --env NODE_NO_WARNINGS=1 --scope unlimited --coverage"},"dependencies":{"core-js":"^3.40.0","rejoinder":"^1.2.4","type-fest":"^4.33.0"},"devDependencies":{"@-xun/symbiote":"^2.18.1"},"engines":{"node":"^18.20.0 || ^20.18.0 || ^22.12.0 || >=23.3.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_id":"@-xun/memoize@1.1.0","gitHead":"11fcb9ad3a90b1536ce4b7c6cc538b8e8cdbc061","_nodeVersion":"22.13.1","_npmVersion":"10.9.2","dist":{"integrity":"sha512-WaakH9c1uQHL8ibLMf3DpzOWz33AHixAr78QSkpWPY2NMlr0AH/S+T73sD8H56ENl7zqS4LSowL7pPcpKUuw3g==","shasum":"aaa2e510476158531632e0879a12ffe2b4482dbc","tarball":"https://registry.npmjs.org/@-xun/memoize/-/memoize-1.1.0.tgz","fileCount":11,"unpackedSize":51880,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIEMk7JjjaTxeCv4k2NgWbLQtXQTuCs50GjKwXBXe2T/AAiEA3L6g2dT8qs1n7/SvgZaqZJxm9i/AWmLhnmQcC4Axn/Y="}]},"_npmUser":{"name":"xunnamius","email":"git@bernarddickens.dev"},"directories":{},"maintainers":[{"name":"xunnamius","email":"git@bernarddickens.dev"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/memoize_1.1.0_1738507877629_0.17025596800029708"},"_hasShrinkwrap":false}},"time":{"created":"2025-02-01T08:12:41.598Z","modified":"2025-02-02T14:51:17.993Z","1.0.0":"2025-02-01T08:12:41.951Z","1.1.0":"2025-02-02T14:51:17.804Z"},"bugs":{"url":"https://github.com/Xunnamius/memoize/issues"},"author":{"name":"Xunnamius"},"license":"MIT","homepage":"https://github.com/Xunnamius/memoize#readme","keywords":[],"repository":{"type":"git","url":"git+https://github.com/Xunnamius/memoize.git"},"description":"An extensible memoization cache and global singleton used to speed up expensive function calls","maintainers":[{"name":"xunnamius","email":"git@bernarddickens.dev"}],"readme":"<!-- symbiote-template-region-start 1 -->\n\n<p align=\"center\" width=\"100%\">\n  <img width=\"300\" src=\"https://raw.githubusercontent.com/Xunnamius/memoize/refs/heads/main/logo.png\">\n</p>\n\n<p align=\"center\" width=\"100%\">\n<!-- symbiote-template-region-end -->\nAn extensible memoization cache and global singleton used to speed up expensive function calls\n<!-- symbiote-template-region-start 2 -->\n</p>\n\n<hr />\n\n<div align=\"center\">\n\n[![Black Lives Matter!][x-badge-blm-image]][x-badge-blm-link]\n[![Last commit timestamp][x-badge-lastcommit-image]][x-badge-repo-link]\n[![Codecov][x-badge-codecov-image]][x-badge-codecov-link]\n[![Source license][x-badge-license-image]][x-badge-license-link]\n[![Uses Semantic Release!][x-badge-semanticrelease-image]][x-badge-semanticrelease-link]\n\n[![NPM version][x-badge-npm-image]][x-badge-npm-link]\n[![Monthly Downloads][x-badge-downloads-image]][x-badge-downloads-link]\n\n</div>\n\n<br />\n\n# memoize (@-xun/memoize)\n\n<!-- symbiote-template-region-end -->\n\nAn extremely flexible memoization cache and global singleton used to speed up\nexpensive function calls.\n\nProvides a simple but powerful API. Supports any number of parameters and/or a\nfinal \"options\" object parameter, asynchronous and synchronous functions, and\nper-function \"scoped\" caching. Provides nuanced usage statistics and\nsuper-powered TypeScript types for smooth DX.\n\n<!-- symbiote-template-region-start 3 -->\n\n---\n\n<!-- remark-ignore-start -->\n<!-- symbiote-template-region-end -->\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->\n\n- [Install](#install)\n- [Usage](#usage)\n  - [`memoize`](#memoize)\n  - [`memoizer`](#memoizer)\n  - [Other Considerations](#other-considerations)\n- [Appendix](#appendix)\n  - [Published Package Details](#published-package-details)\n  - [License](#license)\n- [Contributing and Support](#contributing-and-support)\n  - [Contributors](#contributors)\n\n<!-- END doctoc generated TOC please keep comment here to allow auto update -->\n<!-- symbiote-template-region-start 4 -->\n<!-- remark-ignore-end -->\n\n<br />\n\n## Install\n\n<!-- symbiote-template-region-end -->\n\nTo install:\n\n```shell\nnpm install @-xun/memoize\n```\n\n## Usage\n\nOriginal function without memoization:\n\n```typescript\nfunction doExpensiveAnalysisOfFile(\n  filePath: string,\n  options: { activateFunctionality?: boolean } = {}\n) {\n  const { activateFunctionality } = options;\n  const complexResult = expensiveAnalysis(filePath, activateFunctionality);\n\n  return complexResult;\n}\n\ndoExpensiveAnalysisOfFile('/repos/project/some-file.js', {\n  activateFunctionality: true\n});\n```\n\n<br />\n\n<!-- remark-ignore-start -->\n\n### [`memoize`](./docs/src/functions/memoize.md)\n\n<!-- remark-ignore-end -->\n\n`memoize` can be used to wrap an existing function with caching/memoization\nfeatures.\n\n---\n\nSimple memoization:\n\n```typescript\nimport { memoize } from '@-xun/memoize';\n\n//                                                vv memoized function\nconst memoizedDoExpensiveAnalysisOfFile = memoize(doExpensiveAnalysisOfFile);\n\n//                                               vv cache id component(s)\nconst result = memoizedDoExpensiveAnalysisOfFile('/repos/project/some-file.js');\nmemoizedDoExpensiveAnalysisOfFile('/repos/project/some-file.js') === result; // true\n```\n\n<br />\n\nExpiring memoization, where cache entries are evicted after a certain amount of\ntime:\n\n```typescript\nimport { memoize } from '@-xun/memoize';\n\nconst memoizedDoExpensiveAnalysisOfFile = memoize(doExpensiveAnalysisOfFile, {\n  maxAgeMs: 10_000\n});\n\nconst result = memoizedDoExpensiveAnalysisOfFile('/repos/project/some-file.js');\nmemoizedDoExpensiveAnalysisOfFile('/repos/project/some-file.js') === result; // true\n```\n\n<br />\n\nMemoization of an async function, optionally allowing the caller to explicitly\nrecompute the cached value when desired:\n\n```typescript\nimport { memoize } from '@-xun/memoize';\n\n// Suppose \"asyncDoExpensiveAnalysisOfFile\" is defined as an async version of\n// \"doExpensiveAnalysisOfFile\"\n\nconst memoizedDoExpensiveAnalysisOfFile = memoize(\n  asyncDoExpensiveAnalysisOfFile,\n  { addUseCachedOption: true }\n);\n\nconst result = await memoizedDoExpensiveAnalysisOfFile(\n  '/repos/project/some-file.js',\n  {\n    // Will look in the cache for a result first (wrt the given filePath).\n    useCached: true\n  }\n);\n\n(await memoizedDoExpensiveAnalysisOfFile('/repos/project/some-file.js', {\n  // Will look in the cache for a result first (wrt the given filePath).\n  useCached: true\n})) === result; // true\n\n(await memoizedDoExpensiveAnalysisOfFile('/repos/project/some-file.js', {\n  // Will bypass the cache and force recomputation, then cache the result.\n  useCached: false\n})) !== result; // true\n```\n\n<br />\n\n<!-- remark-ignore-start -->\n\n### [`memoizer`](./docs/src/variables/memoizer.md)\n\n<!-- remark-ignore-end -->\n\n`memoizer` can be used to implement caching/memoization within a function\nitself.\n\n---\n\nBasic memoization:\n\n```typescript\nimport { memoizer } from '@-xun/memoize';\n\nfunction doExpensiveAnalysisOfFile(\n  filePath: string,\n  options: { activateFunctionality: boolean }\n): AnalysisResult {\n  const { activateFunctionality } = options;\n\n  //                               vv memoized function\n  let complexResult = memoizer.get(doExpensiveAnalysisOfFile, [\n    filePath, // <-- first cache id component\n    options //   <-- second cache id component\n  ]);\n\n  if (complexResult === undefined) {\n    complexResult = expensiveAnalysis(filePath, activateFunctionality);\n    memoizer.set(doExpensiveAnalysisOfFile, [filePath, options], complexResult);\n  }\n\n  return complexResult;\n}\n\nconst result = doExpensiveAnalysisOfFile('/repos/project/some-file.js', {\n  activateFunctionality: true\n});\n\ndoExpensiveAnalysisOfFile('/repos/project/some-file.js', {\n  activateFunctionality: true\n}) === result; // true\n\ndoExpensiveAnalysisOfFile('/repos/project/some-file.js', {\n  activateFunctionality: false\n}) !== result; // true\n```\n\n<br />\n\nOptional memoization, allowing the caller to explicitly recompute the cached\nvalue when desired:\n\n```typescript\nimport { memoizer } from '@-xun/memoize';\n\n// It is usually ideal to force the caller to acknowledge that they're dealing\n// with a memoized function, which can prevent bad surprises. Still, we could\n// have made useCached optional if we wanted to.\n\nfunction doExpensiveAnalysisOfFile(\n  filePath: string,\n  {\n    useCached,\n    ...cacheIdComponents\n  }: { activateFunctionality?: boolean; useCached: boolean }\n): AnalysisResult {\n  const { activateFunctionality } = cacheIdComponents;\n  let complexResult;\n\n  if (useCached) {\n    complexResult = memoizer.get(doExpensiveAnalysisOfFile, [\n      filePath,\n      cacheIdComponents\n    ]);\n  }\n\n  if (complexResult === undefined) {\n    complexResult = expensiveAnalysis(filePath, activateFunctionality);\n\n    memoizer.set(\n      doExpensiveAnalysisOfFile,\n      [filePath, cacheIdComponents],\n      complexResult\n    );\n  }\n\n  return complexResult;\n}\n\nconst result = doExpensiveAnalysisOfFile('/repos/project/some-file.js', {\n  // Will look in the cache for a result first (wrt the given filePath).\n  useCached: true\n});\n\ndoExpensiveAnalysisOfFile('/repos/project/some-file.js', {\n  // Will look in the cache for a result first (wrt the given filePath).\n  useCached: true\n}) === result; // true\n\ndoExpensiveAnalysisOfFile('/repos/project/some-file.js', {\n  // Will bypass the cache and force recomputation, then cache the result.\n  useCached: false\n}) !== result; // true\n```\n\n<br />\n\nMore complex memoization, where we accept an array of things with all, some, or\nnone have been cached already. Our goal here is to do as little work as\npossible:\n\n```typescript\nimport { memoizer } from '@-xun/memoize';\n\nfunction doExpensiveAnalysisOfFiles(\n  filePaths: string[],\n  {\n    useCached = true,\n    ...cacheIdComponents\n  }: { activateFunctionality?: boolean; useCached?: boolean } = {}\n): AnalysisResult[] {\n  const { activateFunctionality } = cacheIdComponents;\n  const complexResults = [];\n\n  for (const filePath of filePaths) {\n    let complexResult;\n\n    if (useCached) {\n      complexResult = memoizer.get<\n        typeof doExpensiveAnalysisOfFiles,\n        // DO \"unpack\" id components; go from `T | T[]` to `T` (this is the default).\n        'expect unpacked ids',\n        // DO \"unpack\" the return value; go from `T | T[]` to `T`.\n        'expect unpacked value'\n      >(doExpensiveAnalysisOfFiles, [filePath, cacheIdComponents]);\n    }\n\n    if (complexResult === undefined) {\n      complexResult = expensiveAnalysis(filePath, activateFunctionality);\n\n      memoizer.set<\n        typeof doExpensiveAnalysisOfFiles,\n        // DO \"unpack\" id components; go from `T | T[]` to `T` (this is the default).\n        'expect unpacked ids',\n        // DO \"unpack\" the return value; go from `T | T[]` to `T`.\n        'expect unpacked value'\n      >(\n        doExpensiveAnalysisOfFiles,\n        [filePath, cacheIdComponents],\n        complexResult\n      );\n    }\n\n    complexResults.push(complexResult);\n  }\n\n  return complexResults;\n}\n\nconst result = doExpensiveAnalysisOfFiles([\n  '/repos/project/some-file-1.js',\n  '/repos/project/some-file-2.js',\n  '/repos/project/some-file-3.js'\n]);\n\n// Even though the parameters are different, we can still take advantage of the\n// memoized result of the previous invocation! No extra work is done by the\n// following:\ndoExpensiveAnalysisOfFiles(['/repos/project/some-file-2.js'])[0] === result[1]; // true\n```\n\n<br />\n\nMore complex memoization, where we accept and memoize an array of things in one\nshot:\n\n```typescript\nimport { memoizer } from '@-xun/memoize';\n\nfunction doExpensiveAnalysisOfFiles(\n  filePaths: string[],\n  {\n    useCached,\n    ...cacheIdComponents\n  }: { activateFunctionality?: boolean; useCached: boolean }\n): AnalysisResult[] {\n  const { activateFunctionality } = cacheIdComponents;\n  let complexResults;\n\n  if (useCached) {\n    complexResults = memoizer.get<\n      typeof doExpensiveAnalysisOfFiles,\n      // DO NOT \"unpack\" id components; leave them as they are.\n      'expect ids as-is',\n      // DO NOT \"unpack\" the return value; leave it as-is (this is the default).\n      'expect value as-is'\n    >(doExpensiveAnalysisOfFiles, [filePaths, cacheIdComponents]);\n  }\n\n  if (complexResults === undefined) {\n    complexResults = expensiveAnalyses(filePaths, activateFunctionality);\n\n    memoizer.set<\n      typeof doExpensiveAnalysisOfFiles,\n      // DO NOT \"unpack\" id components; leave them as they are.\n      'expect ids as-is',\n      // DO NOT \"unpack\" the return value; leave it as-is (this is the default).\n      'expect value as-is'\n    >(\n      doExpensiveAnalysisOfFiles,\n      [filePaths, cacheIdComponents],\n      complexResults\n    );\n  }\n\n  return complexResults;\n}\n\nconst result = doExpensiveAnalysisOfFiles(\n  [\n    '/repos/project/some-file-1.js',\n    '/repos/project/some-file-2.js',\n    '/repos/project/some-file-3.js'\n  ],\n  {\n    activateFunctionality: true,\n    useCached: true\n  }\n);\n\ndoExpensiveAnalysisOfFiles(\n  [\n    '/repos/project/some-file-1.js',\n    '/repos/project/some-file-2.js',\n    '/repos/project/some-file-3.js'\n  ],\n  {\n    // This being false means a different cache key is generated and the\n    // previous results are not reused, even though filePaths (the other id\n    // component) is the same!\n    activateFunctionality: false,\n    useCached: true\n  }\n) !== result; // true\n```\n\n<br />\n\nMemoization of an async function using object-style parameters:\n\n```typescript\nimport { memoizer } from '@-xun/memoize';\n\nasync function doExpensiveAnalysisOfFile({\n  useCached,\n  ...cacheIdComponents\n}: {\n  filePath: string;\n  activateFunctionality?: boolean;\n  useCached: boolean;\n}): Promise<AnalysisResult> {\n  const { filePath, activateFunctionality } = cacheIdComponents;\n  let complexResult: AnalysisResult | undefined;\n\n  if (useCached) {\n    // memoizer.get returns a promise iff its first parameter is detected to be\n    // an async function or iff memoizer.set is called with `wasPromised: true`.\n    complexResult = await memoizer.get(doExpensiveAnalysisOfFile, [\n      cacheIdComponents\n    ]);\n  }\n\n  if (complexResult === undefined) {\n    // Do not put promises into the cache. Intellisense will attempt to stop you\n    // from doing so. memoizer.get will return the value wrapped in a promise.\n    complexResult = await expensiveAnalysis(filePath, activateFunctionality);\n    memoizer.set(doExpensiveAnalysisOfFile, [cacheIdComponents], complexResult);\n  }\n\n  return complexResult;\n}\n\nconst result = await doExpensiveAnalysisOfFile({\n  filePath: '/repos/project/some-file.js',\n  // Will look in the cache for a result first (wrt the given filePath).\n  useCached: true\n});\n\n(await doExpensiveAnalysisOfFile({\n  filePath: '/repos/project/some-file.js',\n  // Will look in the cache for a result first (wrt the given filePath).\n  useCached: true\n})) === result; // true\n\n(await doExpensiveAnalysisOfFile({\n  filePath: '/repos/project/some-file.js',\n  // Will bypass the cache and force recomputation, then cache the result.\n  useCached: false\n})) !== result; // true\n```\n\n<br />\n\nPiecemeal memoization, where we customize which parameters are considered as\ncomponents of the cache key and ignore the others:\n\n```typescript\nimport { memoizer } from '@-xun/memoize';\n\nfunction doExpensiveAnalysisOfFile({\n  useCached,\n  activateFunctionality = true,\n  // We only want to use a subset of options as the cache id components.\n  ...cacheIdComponents\n}: {\n  filePath: string;\n  activateFunctionality: boolean;\n  activateOtherFunctionality?: boolean;\n  somethingElse: number;\n  useCached: boolean;\n}): AnalysisResult {\n  // We'll use this type to tell memoizer what id components to look out for.\n  type MemoizedDoExpensiveAnalysisOfFile = (\n    ...args: [typeof cacheIdComponents]\n  ) => ReturnType<typeof doExpensiveAnalysisOfFile>;\n\n  // These three properties will be used as components for our cache \"id\". If\n  // one of them changes, the cache will miss. The other properties are ignored.\n  const { filePath, activateOtherFunctionality, somethingElse } =\n    cacheIdComponents;\n\n  let complexResult;\n\n  if (useCached) {\n    complexResult = memoizer.get<MemoizedDoExpensiveAnalysisOfFile>(\n      doExpensiveAnalysisOfFile as MemoizedDoExpensiveAnalysisOfFile,\n      [cacheIdComponents]\n    );\n  }\n\n  if (complexResult === undefined) {\n    complexResult = expensiveAnalysis(\n      filePath,\n      activateFunctionality,\n      activateOtherFunctionality\n    );\n\n    memoizer.set<MemoizedDoExpensiveAnalysisOfFile>(\n      doExpensiveAnalysisOfFile as MemoizedDoExpensiveAnalysisOfFile,\n      [cacheIdComponents],\n      complexResult\n    );\n  }\n\n  doSomethingElse(somethingElse);\n  return complexResult;\n}\n\nconst result = doExpensiveAnalysisOfFile({\n  filePath: '/repos/project/some-file.js',\n  useCached: true,\n  activateFunctionality: true,\n  somethingElse: 5\n});\n\n// Cache hit (despite activateFunctionality)\ndoExpensiveAnalysisOfFile({\n  filePath: '/repos/project/some-file.js',\n  useCached: true,\n  activateFunctionality: false,\n  somethingElse: 5\n}) === result; // true\n\n// Cache miss (despite activateFunctionality)\ndoExpensiveAnalysisOfFile({\n  filePath: '/repos/project/some-file.js',\n  useCached: true,\n  activateFunctionality: true,\n  somethingElse: 6\n}) !== result; // true\n```\n\n<br />\n\nExpiring cache entries (in this example: 10 seconds after being set unless set\nagain), clearing the cache on a per-scope basis, and accessing cache usage\nmetadata:\n\n```typescript\nimport { memoizer } from '@-xun/memoize';\n\nasync function doExpensiveAnalysisOfFile({\n  useCached,\n  ...cacheIdComponents\n}: {\n  filePath: string;\n  activateFunctionality?: boolean;\n  useCached: boolean;\n}): Promise<AnalysisResult> {\n  const { filePath, activateFunctionality } = cacheIdComponents;\n  let complexResult: AnalysisResult | undefined;\n\n  if (useCached) {\n    complexResult = await memoizer.get(doExpensiveAnalysisOfFile, [\n      cacheIdComponents\n    ]);\n  }\n\n  if (complexResult === undefined) {\n    complexResult = await expensiveAnalysis(filePath, activateFunctionality);\n    memoizer.set(\n      doExpensiveAnalysisOfFile,\n      [cacheIdComponents],\n      complexResult,\n      { maxAgeMs: 10_000 }\n    );\n  }\n\n  return complexResult;\n}\n\nconst result = await doExpensiveAnalysisOfFile({\n  filePath: '/repos/project/some-file.js',\n  useCached: true\n});\n\n// Hits the cache\n(await doExpensiveAnalysisOfFile({\n  filePath: '/repos/project/some-file.js',\n  useCached: true\n})) === result;\n\n// Clears the cache but only for the specified function\nmemoizer.clear([doExpensiveAnalysisOfFile]);\n\n// Misses the cache\n(await doExpensiveAnalysisOfFile({\n  filePath: '/repos/project/some-file.js',\n  useCached: true\n})) !== result;\n\n// If we waited 10 seconds and tried calling doExpensiveAnalysisOfFile again,\n// we would miss the cache again, expirations (below) would be set to 1, and\n// pendingExpirations (also below) would be set to 0.\n\n// If we waited only 5 seconds before calling doExpensiveAnalysisOfFile again,\n// we would hit the cache instead.\n\nconsole.log(memoizer);\n\n// {\n//   set: [Function: setInCache],\n//   sets: 2,\n//   setsOverwrites: 0,\n//   setsCreated: 2,\n//   get: [Function: getFromCache],\n//   gets: 3,\n//   getsHits: 1,\n//   getsMisses: 2,\n//   clear: [Function: clearCacheByScope],\n//   clearAll: [Function: clearCache],\n//   clears: 1,\n//   expirations: 0,\n//   pendingExpirations: 1,\n//   cachedScopes: 1,\n//   cachedEntries: 1,\n// }\n```\n\n### Other Considerations\n\n- The internal cache is implemented as a global singleton that will persist\n  across the entire runtime (but not cross-realm), even when imported from\n  different packages. No need to worry about any of the usual package hazards.\n\n- The `useCached` property, if used as part of an \"options\" object, is omitted\n  from the type of the secondary optional parameter. The name of this property\n  can be customized, and additional properties can be similarly omitted, using\n  the `SecondaryKeysToOmit` generic parameter on `memoizer.get` and\n  `memoizer.set`.\n\n- The order of id components will change the derived cache key, resulting in a\n  recomputation. If this is not desired, ensure id components are passed to\n  `memoizer`'s functions in consistently.\n\n- All id components passed to `memoize` and `memoizer`'s functions must be\n  [serializable][1] via `JSON.stringify` or explicitly `undefined`. If any id\n  components are defined but not serializable, create a wrapper function that\n  transforms any unserializable parameters into some serializable representation\n  before passing them to `memoize`/`memoizer`.\n\n> [!CAUTION]\n>\n> `JSON.stringify` will not consistently throw when it encounters unserializable\n> or semi-serializable id components!\n>\n> If used carelessly, this can lead to arbitrary cache key collisions where the\n> memoizer functions return the same result for obviously different sets of\n> function parameters when it clearly shouldn't.\n>\n> To prevent this, ensure your function's memoized parameters (specifically the\n> parameters used as id components) are serializable.\n\n<!-- symbiote-template-region-start 5 -->\n\n## Appendix\n\n<!-- symbiote-template-region-end -->\n\nFurther documentation can be found under [`docs/`][x-repo-docs].\n\n<!-- TODO: additional appendix sections here -->\n<!-- symbiote-template-region-start 6 -->\n\n### Published Package Details\n\nThis is a [CJS2 package][x-pkg-cjs-mojito] with statically-analyzable exports\nbuilt by Babel for use in Node.js versions that are not end-of-life. For\nTypeScript users, this package supports both `\"Node10\"` and `\"Node16\"` module\nresolution strategies.\n\n<!-- symbiote-template-region-end -->\n<!-- TODO: additional package details here -->\n<!-- symbiote-template-region-start 7 -->\n\n<details><summary>Expand details</summary>\n\nThat means both CJS2 (via `require(...)`) and ESM (via `import { ... } from ...`\nor `await import(...)`) source will load this package from the same entry points\nwhen using Node. This has several benefits, the foremost being: less code\nshipped/smaller package size, avoiding [dual package\nhazard][x-pkg-dual-package-hazard] entirely, distributables are not\npacked/bundled/uglified, a drastically less complex build process, and CJS\nconsumers aren't shafted.\n\nEach entry point (i.e. `ENTRY`) in [`package.json`'s\n`exports[ENTRY]`][x-repo-package-json] object includes one or more [export\nconditions][x-pkg-exports-conditions]. These entries may or may not include: an\n[`exports[ENTRY].types`][x-pkg-exports-types-key] condition pointing to a type\ndeclaration file for TypeScript and IDEs, a\n[`exports[ENTRY].module`][x-pkg-exports-module-key] condition pointing to\n(usually ESM) source for Webpack/Rollup, a `exports[ENTRY].node` and/or\n`exports[ENTRY].default` condition pointing to (usually CJS2) source for Node.js\n`require`/`import` and for browsers and other environments, and [other\nconditions][x-pkg-exports-conditions] not enumerated here. Check the\n[package.json][x-repo-package-json] file to see which export conditions are\nsupported.\n\nNote that, regardless of the [`{ \"type\": \"...\" }`][x-pkg-type] specified in\n[`package.json`][x-repo-package-json], any JavaScript files written in ESM\nsyntax (including distributables) will always have the `.mjs` extension. Note\nalso that [`package.json`][x-repo-package-json] may include the\n[`sideEffects`][x-pkg-side-effects-key] key, which is almost always `false` for\noptimal [tree shaking][x-pkg-tree-shaking] where appropriate.\n\n<!-- symbiote-template-region-end -->\n<!-- TODO: additional package details here -->\n<!-- symbiote-template-region-start 8 -->\n\n</details>\n\n### License\n\n<!-- symbiote-template-region-end -->\n\nSee [LICENSE][x-repo-license].\n\n<!-- TODO: additional license information and/or sections here -->\n<!-- symbiote-template-region-start 9 -->\n\n## Contributing and Support\n\n**[New issues][x-repo-choose-new-issue] and [pull requests][x-repo-pr-compare]\nare always welcome and greatly appreciated! 🤩** Just as well, you can [star 🌟\nthis project][x-badge-repo-link] to let me know you found it useful! ✊🏿 Or [buy\nme a beer][x-repo-sponsor], I'd appreciate it. Thank you!\n\nSee [CONTRIBUTING.md][x-repo-contributing] and [SUPPORT.md][x-repo-support] for\nmore information.\n\n<!-- symbiote-template-region-end -->\n<!-- TODO: additional contribution/support sections here -->\n<!-- symbiote-template-region-start 10 -->\n\n### Contributors\n\n<!-- symbiote-template-region-end -->\n<!-- symbiote-template-region-start root-package-only -->\n<!-- remark-ignore-start -->\n<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->\n\n[![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-)\n\n<!-- ALL-CONTRIBUTORS-BADGE:END -->\n<!-- remark-ignore-end -->\n\nThanks goes to these wonderful people ([emoji\nkey][x-repo-all-contributors-emojis]):\n\n<!-- remark-ignore-start -->\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n\n<table>\n  <tbody>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://xunn.io/\"><img src=\"https://avatars.githubusercontent.com/u/656017?v=4?s=100\" width=\"100px;\" alt=\"Bernard\"/><br /><sub><b>Bernard</b></sub></a><br /><a href=\"#infra-Xunnamius\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/Xunnamius/memoize/commits?author=Xunnamius\" title=\"Code\">💻</a> <a href=\"https://github.com/Xunnamius/memoize/commits?author=Xunnamius\" title=\"Documentation\">📖</a> <a href=\"#maintenance-Xunnamius\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/Xunnamius/memoize/commits?author=Xunnamius\" title=\"Tests\">⚠️</a> <a href=\"https://github.com/Xunnamius/memoize/pulls?q=is%3Apr+reviewed-by%3AXunnamius\" title=\"Reviewed Pull Requests\">👀</a></td>\n    </tr>\n  </tbody>\n  <tfoot>\n    <tr>\n      <td align=\"center\" size=\"13px\" colspan=\"7\">\n        <img src=\"https://raw.githubusercontent.com/all-contributors/all-contributors-cli/1b8533af435da9854653492b1327a23a4dbd0a10/assets/logo-small.svg\">\n          <a href=\"https://all-contributors.js.org/docs/en/bot/usage\">Add your contributions</a>\n        </img>\n      </td>\n    </tr>\n  </tfoot>\n</table>\n\n<!-- markdownlint-restore -->\n<!-- prettier-ignore-end -->\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n<!-- remark-ignore-end -->\n\nThis project follows the [all-contributors][x-repo-all-contributors]\nspecification. Contributions of any kind welcome!\n\n<!-- symbiote-template-region-end -->\n<!-- symbiote-template-region-start workspace-package-only -->\n<!-- (section elided by symbiote) -->\n<!-- symbiote-template-region-end -->\n\n[x-badge-blm-image]: https://xunn.at/badge-blm 'Join the movement!'\n[x-badge-blm-link]: https://xunn.at/donate-blm\n[x-badge-codecov-image]:\n  https://img.shields.io/codecov/c/github/Xunnamius/memoize/main?style=flat-square&token=HWRIOBAAPW&flag=package.main_root\n  'Is this package well-tested?'\n[x-badge-codecov-link]: https://codecov.io/gh/Xunnamius/memoize\n[x-badge-downloads-image]:\n  https://img.shields.io/npm/dm/@-xun/memoize?style=flat-square\n  'Number of times this package has been downloaded per month'\n[x-badge-downloads-link]: https://npmtrends.com/@-xun/memoize\n[x-badge-lastcommit-image]:\n  https://img.shields.io/github/last-commit/Xunnamius/memoize?style=flat-square\n  'Latest commit timestamp'\n[x-badge-license-image]:\n  https://img.shields.io/npm/l/@-xun/memoize?style=flat-square\n  \"This package's source license\"\n[x-badge-license-link]: https://github.com/Xunnamius/memoize/blob/main/LICENSE\n[x-badge-npm-image]:\n  https://xunn.at/npm-pkg-version/@-xun/memoize\n  'Install this package using npm or yarn!'\n[x-badge-npm-link]: https://npm.im/@-xun/memoize\n[x-badge-repo-link]: https://github.com/Xunnamius/memoize\n[x-badge-semanticrelease-image]:\n  https://xunn.at/badge-semantic-release\n  'This repo practices continuous integration and deployment!'\n[x-badge-semanticrelease-link]:\n  https://github.com/semantic-release/semantic-release\n[x-pkg-cjs-mojito]:\n  https://dev.to/jakobjingleheimer/configuring-commonjs-es-modules-for-nodejs-12ed#publish-only-a-cjs-distribution-with-property-exports\n[x-pkg-dual-package-hazard]:\n  https://nodejs.org/api/packages.html#dual-package-hazard\n[x-pkg-exports-conditions]:\n  https://webpack.js.org/guides/package-exports#reference-syntax\n[x-pkg-exports-module-key]:\n  https://webpack.js.org/guides/package-exports#providing-commonjs-and-esm-version-stateless\n[x-pkg-exports-types-key]:\n  https://devblogs.microsoft.com/typescript/announcing-typescript-4-5-beta#packagejson-exports-imports-and-self-referencing\n[x-pkg-side-effects-key]:\n  https://webpack.js.org/guides/tree-shaking#mark-the-file-as-side-effect-free\n[x-pkg-tree-shaking]: https://webpack.js.org/guides/tree-shaking\n[x-pkg-type]:\n  https://github.com/nodejs/node/blob/8d8e06a345043bec787e904edc9a2f5c5e9c275f/doc/api/packages.md#type\n[x-repo-all-contributors]: https://github.com/all-contributors/all-contributors\n[x-repo-all-contributors-emojis]: https://allcontributors.org/docs/en/emoji-key\n[x-repo-choose-new-issue]:\n  https://github.com/Xunnamius/memoize/issues/new/choose\n[x-repo-contributing]: /CONTRIBUTING.md\n[x-repo-docs]: docs\n[x-repo-license]: ./LICENSE\n[x-repo-package-json]: package.json\n[x-repo-pr-compare]: https://github.com/Xunnamius/memoize/compare\n[x-repo-sponsor]: https://github.com/sponsors/Xunnamius\n[x-repo-support]: /.github/SUPPORT.md\n[1]:\n  https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description\n","readmeFilename":"README.md"}