{"id":13465123,"url":"https://github.com/planttheidea/moize","last_synced_at":"2025-04-13T14:03:15.473Z","repository":{"id":14256048,"uuid":"76174100","full_name":"planttheidea/moize","owner":"planttheidea","description":"The consistently-fast, complete memoization solution for JS","archived":false,"fork":false,"pushed_at":"2024-03-28T17:36:40.000Z","size":7946,"stargazers_count":910,"open_issues_count":8,"forks_count":18,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-03-30T10:04:23.011Z","etag":null,"topics":["javascript","memoization","performance"],"latest_commit_sha":null,"homepage":"https://planttheidea.github.io/moize/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/planttheidea.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-12-11T12:52:39.000Z","updated_at":"2025-03-01T19:42:05.000Z","dependencies_parsed_at":"2023-02-18T16:32:39.424Z","dependency_job_id":"1c672bd8-486c-43a8-858f-215c39584d26","html_url":"https://github.com/planttheidea/moize","commit_stats":{"total_commits":537,"total_committers":16,"mean_commits":33.5625,"dds":0.1936685288640596,"last_synced_commit":"3a7959c8a32e38d14dceae234119ed43bedc53d8"},"previous_names":[],"tags_count":34,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/planttheidea%2Fmoize","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/planttheidea%2Fmoize/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/planttheidea%2Fmoize/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/planttheidea%2Fmoize/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/planttheidea","download_url":"https://codeload.github.com/planttheidea/moize/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247411226,"owners_count":20934653,"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":["javascript","memoization","performance"],"created_at":"2024-07-31T14:01:00.206Z","updated_at":"2025-04-06T11:05:34.437Z","avatar_url":"https://github.com/planttheidea.png","language":"TypeScript","readme":"\u003e moize\n\n\u003cimg src=\"https://img.shields.io/badge/build-passing-brightgreen.svg\"/\u003e\n\u003cimg src=\"https://img.shields.io/badge/coverage-100%25-brightgreen.svg\"/\u003e\n\u003cimg src=\"https://img.shields.io/badge/license-MIT-blue.svg\"/\u003e\n\n`moize` is a [consistently blazing fast](#benchmarks) memoization library for JavaScript. It handles multiple parameters (including default values) without any additional configuration, and offers a large number of options to satisfy any number of potential use-cases.\n\n- [Importing](#importing)\n  - [ESM in browsers](#esm-in-browsers)\n  - [ESM in NodeJS](#esm-in-nodejs)\n  - [CommonJS](#commonjs)\n- [Usage](#usage)\n- [Configuration options](#configuration-options)\n  - [isDeepEqual](#isdeepequal)\n  - [isPromise](#ispromise)\n  - [isReact](#isreact)\n  - [isSerialized](#isserialized)\n  - [isShallowEqual](#isshallowequal)\n  - [matchesArg](#matchesarg)\n  - [matchesKey](#matcheskey)\n  - [maxAge](#maxage)\n  - [maxArgs](#maxargs)\n  - [maxSize](#maxsize)\n  - [onCacheAdd](#oncacheadd)\n  - [onCacheChange](#oncachechange)\n  - [onCacheHit](#oncachehit)\n  - [onExpire](#onexpire)\n  - [profileName](#profilename)\n  - [serializer](#serializer)\n  - [transformArgs](#transformargs)\n  - [updateCacheForKey](#updatecacheforkey)\n  - [updateExpire](#updateexpire)\n- [Usage with shortcut methods](#usage-with-shortcut-methods)\n  - [moize.deep](#moizedeep)\n  - [moize.infinite](#moizeinfinite)\n  - [moize.matchesArg](#moizematchesarg)\n  - [moize.matchesKey](#moizematcheskey)\n  - [moize.maxAge](#moizemaxage)\n  - [moize.maxArgs](#moizemaxargs)\n  - [moize.maxSize](#moizemaxsize)\n  - [moize.profile](#moizeprofile)\n  - [moize.promise](#moizepromise)\n  - [moize.react](#moizereact)\n  - [moize.serialize](#moizeserialize)\n  - [moize.serializeWith](#moizeserializewith)\n  - [moize.shallow](#moizeshallow)\n  - [moize.transformArgs](#moizetransformargs)\n  - [moize.updateCacheForKey](#moizeupdatecacheforkey)\n- [useMoize hook](#usemoize-hook)\n- [Composition](#composition)\n- [Collecting statistics](#collecting-statistics)\n  - [Stats methods](#stats-methods)\n  - [clearStats](#clearstats)\n  - [collectStats](#collectstats)\n  - [getStats([profileName])](#getstatsprofilename)\n- [Introspection](#introspection)\n  - [isCollectingStats](#iscollectingstats)\n  - [isMoized](#ismoized)\n- [Direct cache manipulation](#direct-cache-manipulation)\n  - [cache](#cache)\n  - [cacheSnapshot](#cachesnapshot)\n  - [add(key, value)](#addkey-value)\n  - [clear()](#clear)\n  - [get(key)](#getkey)\n  - [getStats()](#getstats)\n  - [has(key)](#haskey)\n  - [keys()](#keys)\n  - [remove(key)](#removekey)\n  - [update(key, value)](#updatekey-value)\n  - [values()](#values)\n- [Benchmarks](#benchmarks)\n- [Filesize](#filesize)\n- [Browser support](#browser-support)\n- [Development](#development)\n\n```\n$ npm i moize --save\n```\n\n# Importing\n\n## ESM in browsers\n\n```ts\nimport moize from 'moize';\n```\n\n## ESM in NodeJS\n\n```ts\nimport moize from 'moize/mjs/index.mjs';\n```\n\n## CommonJS\n\n```ts\nconst moize = require('moize');\n```\n\n# Usage\n\n```ts\nimport moize from 'moize';\n\nconst method = (a: number, b: number) =\u003e a + b;\n\nconst memoized = moize(method);\n\nmemoized(2, 4); // 6\nmemoized(2, 4); // 6, pulled from cache\n```\n\nAll parameter types are supported, including circular objects, functions, etc. There are also a number of [shortcut methods](#usage-with-shortcut-methods) to memoize for unique use-cases.\n\n# Configuration options\n\n`moize` optionally accepts an object of options as either the second parameter or as the first step in a curried function:\n\n```ts\n// inline\nmoize(fn, options);\n\n// curried\nmoize(options)(fn);\n```\n\nThe full shape of these options:\n\n```ts\ntype Options = {\n    // is the cache based on deep equality of each key argument\n    isDeepEqual: boolean;\n    // is the result a promise\n    isPromise: boolean;\n    // is the result a React component\n    isReact: boolean;\n    // should the parameters be serialized instead of directly referenced\n    isSerialized: boolean;\n    // is the cache based on shallow equality of each key argument\n    isShallowEqual: boolean;\n    // custom method to compare equality between two key arguments\n    matchesArg: (cachedKeyArg: any, keyArg: any) =\u003e boolean;\n    // custom method to compare equality across all key arguments\n    matchesKey: (cachedKey: any[], key: any[]) =\u003e boolean;\n    // amount of time in milliseconds before the cache will expire\n    maxAge: number;\n    // maximum number of arguments passed to use as key for caching\n    maxArgs: number;\n    // maximum size of cache for this method\n    maxSize: number;\n    // method fired when a new entry is added to cache\n    onCacheAdd: (\n        cache: moize.Cache,\n        options: moize.Options,\n        moized: (...args: any[]) =\u003e any\n    ) =\u003e void;\n    // method fire when either a new entry is added to cache or the LRU ordering of the cache has changed\n    onCacheChange: (\n        cache: moize.Cache,\n        options: moize.Options,\n        moized: (...args: any[]) =\u003e any\n    ) =\u003e void;\n    // method fired when an existing entry in cache is used\n    onCacheHit: (\n        cache: moize.Cache,\n        options: moize.Options,\n        moized: (...args: any[]) =\u003e any\n    ) =\u003e void;\n    // method to fire when a cache entry expires (in combination with maxAge)\n    onExpire: (key: any[]) =\u003e void;\n    // the unique identifier to give the memoized method when collecting statistics\n    profileName: string;\n    // method to serialize the arguments to build a unique cache key\n    serializer: (key: any[]) =\u003e string;\n    // method to transform the args into a custom format for key storage in cache\n    transformArgs: (key: any[]) =\u003e any[];\n    // should the cache entry be refreshed by calling the underlying function with the same parameters and\n    // updating the value stored in cache to be the new result\n    updateCacheForKey: (key: any[]) =\u003e boolean;\n    // should the cache entry's expiration be refreshed when the cache entry is hit (in combination with maxAge)\n    updateExpire: boolean;\n};\n```\n\nAll default values can be found [here](src/constants.ts).\n\n## isDeepEqual\n\n_defaults to false_\n\nShould deep equality be used to compare cache each key argument.\n\n```ts\ntype Arg = {\n    one: {\n        nested: string;\n    };\n    two: string;\n};\n\nconst fn = ({ one, two }: Arg) =\u003e [one, two];\n\nconst memoized = moize(fn, { isDeepEqual: true });\n\nmemoized({ one: { nested: 'one' }, two: 'two' });\nmemoized({ one: { nested: 'one' }, two: 'two' }); // pulls from cache\n```\n\nThis is also available via the shortcut method of [`moize.deep`](#moizedeep)\n\n```ts\nconst memoized = moize.deep(fn);\n```\n\n## isPromise\n\n_defaults to false_\n\nIs the computed value in the function a `Promise`.\n\n```ts\nconst fn = async (item: Promise\u003cstring\u003e) =\u003e await item;\n\nconst memoized = moize(fn, { isPromise: true });\n```\n\nThis is also available via the shortcut method of [`moize.promise`](#moizepromise).\n\n```ts\nconst memoized = -moize.promise(fn);\n```\n\nThe `Promise` itself will be stored in cache, so that cached returns will always maintain the `Promise` contract. For common usage reasons, if the `Promise` is rejected, the cache entry will be deleted.\n\n## isReact\n\n_defaults to false_\n\nIs the function passed a stateless functional `React` component.\n\n```tsx\ntype Props = {\n    one: string;\n    two: number;\n};\n\nconst Component = ({ one, two }: Props) =\u003e (\n    \u003cdiv\u003e\n        {one}: {two}\n    \u003c/div\u003e\n);\n\nconst MemoizedFoo = moize(Component, { isReact: true });\n```\n\nThis is also available via the shortcut method of [`moize.react`](#moizereact).\n\n```ts\nconst MemoizedFoo = moize.react(Component);\n```\n\nThe method will do a shallow equal comparison of both `props` and legacy `context` of the component based on strict equality. If you want to do a deep equals comparison, set [`isDeepEqual`](#isdeepequal) to true.\n\n**NOTE**: This will memoize on each instance of the component passed, which is equivalent to `PureComponent` or `React.memo`. If you want to\nmemoize on _all_ instances (which is how this option worked prior to version 6), use the following options:\n\n```ts\nconst memoized = moize(Component, { isShallowEqual: true, maxArgs: 2 });\n```\n\n## isSerialized\n\n_defaults to false_\n\nSerializes the parameters passed into a string and uses this as the key for cache comparison.\n\n```ts\nconst fn = (mutableObject: { one: Record\u003cstring, any\u003e }) =\u003e\n    mutableObject.property;\n\nconst memoized = moize(fn, { isSerialized: true });\n```\n\nThis is also available via the shortcut method of [`moize.serialize`](#moizeserialize).\n\n```ts\nconst memoized = moize.serialize(fn);\n```\n\nIf `serialize` is combined with either `maxArgs` or `transformArgs`, the following order is used:\n\n1.  limit by `maxArgs` (if applicable)\n1.  transform by `transformArgs` (if applicable)\n1.  serialize by `serializer`\n\n**NOTE**: This is much slower than the default key storage, and usually the same requirements can be meet with `isDeepEqual`, so use at your discretion.\n\n## isShallowEqual\n\n_defaults to false_\n\nShould shallow equality be used to compare cache each key argument.\n\n```ts\ntype Arg = {\n    one: string;\n    two: string;\n};\n\nconst fn = ({ one, two }: Arg) =\u003e [one, two];\n\nconst memoized = moize(fn, { isShallowEqual: true });\n\nmemoized({ one: 'one', two: 'two' });\nmemoized({ one: 'one', two: 'two' }); // pulls from cache\n```\n\nThis is also available via the shortcut method of [`moize.shallow`](#moizeshallow)\n\n```ts\nconst memoized = moize.shallow(fn);\n```\n\n## matchesArg\n\n_defaults to [SameValueZero](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) equality_\n\nCustom method used to compare equality of keys for cache purposes by comparing each argument.\n\n```ts\ntype Arg = {\n    one: string;\n    two: string;\n};\n\nconst fn = ({ one, two }: Arg) =\u003e [one, two];\n\nconst hasOneProperty = (cacheKeyArg: Arg, keyArg: Arg) =\u003e\n    Object.keys(cacheKeyArg).length === 1 \u0026\u0026 Object.keys(keyArg).length === 1;\n\nconst memoized = moize(fn, { matchesArg: hasOneProperty });\n\nmemoized({ one: 'two' };\nmemoized({ two: 'three' }); // pulls from cache\n```\n\nThis is also available via the shortcut method of [`moize.matchesArg`](#moizematchesarg)\n\n```ts\nconst memoized = moize.matchesArg(hasOneProperty)(fn);\n```\n\n**NOTE**: This comparison is used iteratively on each argument, rather than comparing the two keys as a whole. If you want to compare the key as a whole, you should use [`matchesKey`](#matcheskey).\n\n## matchesKey\n\nCustom method used to compare equality of keys for cache purposes by comparing the entire key.\n\n```ts\ntype Arg = {\n    one: string;\n    two: string;\n};\n\nconst fn = ({ one, two }: Arg) =\u003e [one, two];\n\nconst isFooEqualAndHasBar = (cacheKey: [Arg], key: [Arg]) =\u003e\n    cacheKey[0].one === key[0].one \u0026\u0026\n    cacheKey[1].hasOwnProperty('two') \u0026\u0026\n    key[1].hasOwnProperty('two');\n\nconst memoized = moize(fn, { matchesKey: isFooEqualAndHasBar });\n\nmemoized({ one: 'two' }, { two: null });\nmemoized({ one: 'two' }, { two: 'three' }); // pulls from cache\n```\n\nThis is also available via the shortcut method of [`moize.matchesKey`](#moizematcheskey)\n\n```ts\nconst memoized = moize.matchesKey(isFooEqualAndHasBar)(fn);\n```\n\n**NOTE**: This comparison uses the two keys as a whole, which is usually less performant than the `matchArg` comparison used iteratively on each argument. Generally speaking you should use the [`matchArg`](#matchesarg) option for equality comparison.\n\n## maxAge\n\nThe maximum amount of time in milliseconds that you want a computed value to be stored in cache for this method.\n\n```ts\nconst fn = (item: Record\u003cstring, any\u003e) =\u003e item;\n\nconst MAX_AGE = 1000 * 60 * 5; // five minutes;\n\nconst memoized = moize(fn, { maxAge: MAX_AGE });\n```\n\nThis is also available via the shortcut method of [`moize.maxAge`](#moizemaxage).\n\n```ts\nconst memoized = moize.maxAge(MAX_AGE)(fn);\n```\n\n**TIP**: A common usage of this is in tandem with `isPromise` for AJAX calls, and in that scenario the expected behavior is usually to have the `maxAge` countdown begin upon resolution of the promise. If this is your intended use case, you should also apply the `updateExpire` option.\n\n## maxArgs\n\nThe maximum number of arguments (starting from the first) used in creating the key for the cache.\n\n```ts\nconst fn = (item1: string, item2: string, item3: string) =\u003e\n    item1 + item2 + item3;\n\nconst memoized = moize(fn, { maxArgs: 2 });\n\nmemoize('one', 'two', 'three');\nmemoize('one', 'two', 'four'); // pulls from cache, as the first two args are the same\n```\n\nThis is also available via the shortcut method of [`moize.maxArgs`](#moizemaxargs).\n\n```ts\nconst memoized = moize.maxArgs(2)(fn);\n```\n\nIf `maxArgs` is combined with either `serialize` or `transformArgs`, the following order is used:\n\n1.  limit by `maxArgs`\n1.  transform by `transformArgs` (if applicable)\n1.  serialize by `serializer` (if applicable)\n\n## maxSize\n\n_defaults to 1_\n\nThe maximum number of values you want stored in cache for this method. Clearance of the cache once the `maxSize` is reached is on a [Least Recently Used](https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_Recently_Used_.28LRU.29) basis.\n\n```ts\nconst fn = (item: string) =\u003e item;\n\nconst memoized = moize(fn, { maxSize: 5 });\n```\n\nThis is also available via the shortcut method of [`moize.maxSize`](#moizemaxsize).\n\n```ts\nconst memoized = moize.maxSize(5)(fn);\n```\n\n## onCacheAdd\n\nMethod to fire when an item has been added to cache. Receives the cache, options, and memoized function as a parameters.\n\n```ts\nconst fn = (one: string, two: string) =\u003e [one, two];\n\nconst logCacheKeys = (\n    cache: Cache,\n    options: Options,\n    moized: Moized\u003ctypeof fn\u003e\n) =\u003e console.log(cache.keys);\n\nconst moized = moize(fn, { maxSize: 2, onCacheAdd: logCacheKeys });\n\nmoized('one', 'two'); // [[\"one\",\"two\"]]\nmoized('one', 'two');\nmoized('two', 'one'); // [[\"two\",\"one\"], [\"one\",\"two\"]]\nmoized('one', 'two');\n```\n\n**NOTE**: When combined with `onCacheChange`, this method will always fire first.\n\n## onCacheChange\n\nMethod to fire when an item has been either added to cache, or existing cache was reordered based on a cache hit. Receives the cache, options, and memoized function as a parameters.\n\n```ts\nconst fn = (one: string, two: string) =\u003e [one, two];\n\nconst logCacheKeys = (\n    cache: Cache,\n    options: Options,\n    moized: Moized\u003ctypeof fn\u003e\n) =\u003e console.log(cache.keys);\n\nconst moized = moize(fn, { maxSize: 2, onCacheChange: logCacheKeys });\n\nmoized('one', 'two'); // [[\"one\",\"two\"]]\nmoized('one', 'two');\nmoized('two', 'one'); // [[\"two\",\"one\"], [\"one\",\"two\"]]\nmoized('one', 'two'); // [[\"one\",\"two\"], [\"two\",\"one\"]]\n```\n\n**NOTE**: When combined with `onCacheAdd` or `onCacheHit`, this method will always fire last.\n\n## onCacheHit\n\nMethod to fire when an existing cache item is found. Receives the cache, options, and memoized function as a parameters.\n\n```ts\nconst fn = (one: string, two: string) =\u003e [one, two];\n\nconst logCacheKeys = (\n    cache: Cache,\n    options: Options,\n    moized: Moized\u003ctypeof fn\u003e\n) =\u003e console.log(cache.keys);\n\nconst moized = moize(fn, { maxSize: 2, onCacheHit: logCacheKeys });\n\nmoized('one', 'two');\nmoized('one', 'two'); // [[\"one\",\"two\"]]\nmoized('two', 'one');\nmoized('one', 'two'); // [[\"two\",\"one\"], [\"one\",\"two\"]]\n```\n\n**NOTE**: When combined with `onCacheChange`, this method will always fire first.\n\n## onExpire\n\nA callback that is called when the cached entry expires.\n\n```ts\nconst fn = (item: string) =\u003e item;\n\nconst logKey = (key: Key\u003cstring\u003e) =\u003e console.log(key);\n\nconst memoized = moize(fn, { maxAge: 10000, onExpire: logKey });\n```\n\nIf you return `false` from this method, it will prevent the key's removal and refresh the expiration in the same vein as `updateExpire` based on `maxAge`:\n\n```ts\nconst fn = (item: string) =\u003e item;\n\nlet expirationAttempts = 0;\n\nconst limitExpirationAttempts = (key: Key\u003cstring\u003e) =\u003e {\n    expirationAttempts += 1;\n\n    return expirationAttempts \u003c 2;\n};\n\nconst memoized = moize(fn, {\n    maxAge: 10000,\n    onExpire: limitExpirationAttempts,\n});\n\nmemoized('one'); // will expire key after 30 seconds, or 3 expiration attempts\n```\n\n**NOTE**: You must set a [`maxAge`](#maxage) for this option to take effect.\n\n## profileName\n\n_defaults to function name when it exists, or `Anonymous {count}` otherwise_\n\nName to use as unique identifier for the function when collecting statistics.\n\n```ts\nmoize.collectStats();\n\nconst fn = (item: string) =\u003e item;\n\nconst memoized = moize(fn, { profileName: 'my fancy identity' });\n```\n\nThis is also available via the shortcut method of [`moize.profile`](#moizeprofile).\n\n```ts\nconst memoized = moize.profile('profile-name')(fn);\n```\n\n**NOTE**: You must be collecting statistics for this option to provide value, as it is the identifier used for statistics collection.\n\n## serializer\n\n_defaults to serializeArguments in utils.js_\n\nMethod used in place of the internal serializer when serializing the parameters for cache key comparison. The function accepts a single argument, the `Array` of `args`, and must also return an `Array`.\n\n```ts\nconst fn = (one: string, two: string) =\u003e [one, two];\n\nconst customSerializer = (args: string[]) =\u003e [JSON.stringify(args[0])];\n\nconst memoized = moize(fn, {\n    isSerialized: true,\n    serializer,\n});\n```\n\nThis is also available via the shortcut method of [`moize.serializeWith`](#moizeserializewith).\n\n```ts\nconst memoized = moize.serializeWith(customSerializer)(fn);\n```\n\n**NOTE**: You must set [`isSerialized`](#isserialized) for this option to take effect.\n\n## transformArgs\n\nTransform the arguments passed before it is used as a key. The function accepts a single argument, the `Array` of `args`, and must also return an `Array`.\n\n```ts\nconst fn = (one: string | null, two: string | null, three: string | null) =\u003e [\n    two,\n    three,\n];\n\nconst ignoreFirstArg = (args: (string | null)[]) =\u003e args.slice(1);\n\nconst moized = moize(fn, { transformArgs: ignoreFirstArg });\n\nmoize('one', 'two', 'three');\nmoize(null, 'two', 'three'); // pulled from cache\n```\n\nThis is also available via the shortcut method of [`moize.transformArgs`](#moizetransformargs).\n\n```ts\nconst memoized = moize.transformArgs(argTransformer)(fn);\n```\n\nIf `transformArgs` is combined with either `maxArgs` or `serialize`, the following order is used:\n\n1.  limit by `maxArgs` (if applicable)\n1.  transform by `transformArgs`\n1.  serialize by `serializer` (if applicable)\n\n## updateCacheForKey\n\nIf you want to update the cache for a given key instead of leverage the value currently stored in cache.\n\n```ts\nconst fn = (item: string) =\u003e item;\n\nlet lastUpdate = Date.now();\n\nconst memoized = moize(fn, {\n    updateCacheForKey([item]: [string]) {\n        const now = Date.now();\n        const last = lastUpdated;\n\n        lastUpdate = now;\n\n        // its been more than 5 minutes since last update\n        return last + 300000 \u003c now;\n    },\n});\n\nmemoized('one');\nmemoized('one'); // pulled from cache\n\n// 5 minutes later\n\nmemoized('one'); // re-calls method and updates cache\n```\n\nThis is also available via the shortcut method of [`moize.updateCacheForKey`](#moizeupdatecacheforkey).\n\n```ts\nconst memoized = moize.updateCacheForKey(shouldCacheUpdate)(fn);\n```\n\n## updateExpire\n\nWhen a `maxAge` is set, clear the scheduled expiration of the key when that key is retrieved, setting a new expiration based on the most recent retrieval from cache.\n\n```ts\nconst fn = (item: string) =\u003e item;\n\nconst MAX_AGE = 1000 * 60 * 5; // five minutes\n\nconst memoized = moize(fn, { maxAge: MAX_AGE, updateExpire: true });\n\nmemoized('one');\n\nsetTimeout(() =\u003e {\n    /**\n     * hits cache, which updates the expire to be 5 minutes\n     * from this run instead of the first\n     */\n    memoized('one');\n}, 1000 * 60);\n```\n\n# Usage with shortcut methods\n\n## moize.deep\n\nPre-applies the [`isDeepEqual`](#isdeepequal) option.\n\n```ts\nimport moize from 'moize';\n\nconst fn = (one: string, two: string) =\u003e `${one} ${two}`;\n\nexport default moize.deep(fn);\n```\n\n## moize.infinite\n\nPre-applies the [`maxSize`](#maxsize) option with `Infinity`.\n\n```ts\nimport moize from 'moize';\n\nconst fn = (one: string, two: string) =\u003e `${one} ${two}`;\n\nexport default moize.infinite(fn);\n```\n\n**NOTE**: This mimics default behavior of `moize` prior to version 6.\n\n## moize.matchesArg\n\nPre-applies the [`matchesArg`](#matchesarg) option as a curriable method.\n\n```ts\nimport moize from 'moize';\n\nconst isEqualOrFoo = (cacheKeyArg: string, keyArg: string) =\u003e\n    cacheKeyArg === keyArg || keyArg === 'one';\n\nconst fn = (one: string, two: string) =\u003e `${one} ${two}`;\n\nexport default moize.matchesArg(isEqualOrFoo)(fn);\n```\n\n## moize.matchesKey\n\nPre-applies the [`matchesKey`](#matcheskey) option as a curriable method.\n\n```ts\nimport moize from 'moize';\n\nconst fn = (one: string, two: string) =\u003e `${one} ${two}`;\n\nconst isEqualOrHasFoo = (cacheKey: Key\u003cstring\u003e, key: Key\u003cstring\u003e) =\u003e\n    key.every((keyArg, index) =\u003e keyArg === cacheKey[index]) ||\n    key.some((keyArg) =\u003e keyArg === 'one');\n\nexport default moize.matchesKey(isEqualOrHasFoo)(fn);\n```\n\n## moize.maxAge\n\nPre-applies the [`maxAge`](#maxage) option as a curriable method.\n\n```ts\nimport moize from 'moize';\n\nconst fn = (one: string, two: string) =\u003e `${one} ${two}`;\n\nexport default moize.maxAge(5000)(fn);\n```\n\n## moize.maxArgs\n\nPre-applies the [`maxArgs`](#maxargs) option as a curriable method.\n\n```ts\nimport moize from 'moize';\n\nconst fn = (one: string, two: string) =\u003e `${one} ${two}`;\n\nexport default moize.maxArgs(1)(fn);\n```\n\n## moize.maxSize\n\nPre-applies the [`maxSize`](#maxsize) option as a curriable method.\n\n```ts\nimport moize from 'moize';\n\nconst fn = (one: string, two: string) =\u003e `${one} ${two}`;\n\nexport default moize.maxSize(5)(fn);\n```\n\n## moize.profile\n\nPre-applies the [`profileName`](#profilename) option as a curriable method.\n\n```ts\nimport moize from 'moize';\n\nconst fn = (one: string, two: string) =\u003e `${one} ${two}`;\n\nexport default moize.profile('my fancy identity')(fn);\n```\n\n**NOTE**: You must be collecting statistics for this option to provide value, as it is the identifier used for statistics collection.\n\n## moize.promise\n\nPre-applies the [`isPromise`](#ispromise) and [`updateExpire`](#updateexpire) options. The `updateExpire` option does nothing if [`maxAge`](#maxage) is not also applied, but ensures that the expiration begins at the resolution of the promise rather than the instantiation of it.\n\n```ts\nimport moize from 'moize';\n\nconst fn = async (one: string, two: Record\u003cstring, any\u003e) =\u003e\n    await someApiCall(one, two);\n\nexport default moize.promise(fn);\n```\n\n**NOTE**: If you do not want the promise to update its expiration when the cache is hit, then you should use the `isPromise` option directly instead.\n\n## moize.react\n\nPre-applies the [`isReact`](#isreact)) option for memoizing functional components in [React](https://github.com/facebook/react). `Key` comparisons are based on a shallow equal comparison of both props and legacy context.\n\n```tsx\nimport moize from 'moize';\n\ntype Props = {\n    one: string;\n    two: number;\n};\n\nconst Component = ({ one, two }: Props) =\u003e (\n    \u003cdiv\u003e\n        {one} {two}\n    \u003c/div\u003e\n);\n\nexport default moize.react(Component);\n```\n\n**NOTE**: This method will not operate with components made via the `class` instantiation, as they do not offer the same [referential transparency](https://en.wikipedia.org/wiki/Referential_transparency).\n\n## moize.serialize\n\nPre-applies the [`isSerialized`](#isSerialized) option.\n\n```ts\nimport moize from 'moize';\n\nconst fn = (one: Record\u003cstring, any\u003e, two: Record\u003cstring, any\u003e) =\u003e ({\n    one,\n    two,\n});\n\nexport default moize.serialize(fn);\n```\n\n**NOTE**: If you want to provide a custom [`serializer`](#serializer), you should use [`moize.serializeWith`](#moizeserializewith):\n\n```ts\nmoize.serializeWith(customSerializer)(fn);\n```\n\n## moize.serializeWith\n\nPre-applies the [`isSerialized`](#isSerialized) and [`serializer`](#serializer) options.\n\n```ts\nimport moize from 'moize';\n\nconst fn = (one: Record\u003cstring, any\u003e, two: Record\u003cstring, any\u003e) =\u003e ({\n    one,\n    two,\n});\n\nexport default moize.serializeWith(JSON.stringify)(fn);\n```\n\n**NOTE**: If you want to use the default [`serializer`](#serializer), you should use [`moize.serialize`](#moizeserialize):\n\n```ts\nmoize.serialize(customSerializer)(fn);\n```\n\n## moize.shallow\n\nPre-applies the [`isShallowEqual`](#isshallowequal) option.\n\n```ts\nimport moize from 'moize';\n\nconst fn = (one: string, two: string) =\u003e `${one} ${two}`;\n\nexport default moize.shallow(fn);\n```\n\n## moize.transformArgs\n\nPre-applies the [`transformArgs`](#transformargs) option.\n\n```ts\nimport moize from 'moize';\n\nconst fn = ([one, two]: string[]) =\u003e [`${one} ${two}`];\n\nexport default moize.transformArgs(fn);\n```\n\n## moize.updateCacheForKey\n\nPre-applies the [`updateCacheForKey`](#updatecacheforkey) option.\n\n```ts\nimport moize from 'moize';\n\nlet lastUpdated = Date.now();\n\nconst fn = () =\u003e {\n    const now = Date.now();\n    const last = lastUpdated;\n\n    lastUpdate = now;\n\n    // its been more than 5 minutes since last update\n    return last + 300000 \u003c now;\n};\n\nexport default moize.updateCacheForKey(fn);\n```\n\n# useMoize hook\n\nIf you are using React 16.8+ and are using hooks, you can easily create a custom `useMoize` hook for your project:\n\n```ts\nimport { useRef } from 'react';\n\nexport function useMoize(fn, args, options) {\n    const moizedFnRef = useRef(moize(fn, options));\n\n    return moizedFnRef.current(...args);\n}\n```\n\nWhich can then be used as such:\n\n```tsx\nimport React from 'react';\n\nimport { useMoize } from './moize-hooks';\n\nfunction MyComponent({ first, second, object }) {\n    // standard usage\n    const sum = useMoize((a, b) =\u003e a + b, [first, second]);\n    // with options\n    const deepSum = useMoize((obj) =\u003e obj.a + obj.b, [object], {\n        isDeepEqual: true,\n    });\n\n    return (\n        \u003cdiv\u003e\n            Sum of {first} and {second} is {sum}. Sum of {object.a} and{' '}\n            {object.b} is {deepSum}.\n        \u003c/div\u003e\n    );\n}\n```\n\nNaturally you can tweak as needed for your project (default options, option-specific hooks, etc).\n\n**NOTE**: This is very similar to [`useCallback`](https://reactjs.org/docs/hooks-reference.html#usecallback) built-in hook, with two main differences:\n\n-   There is a third parameter passed (the [`options`](#configuration-options) passed to `moize`)\n-   The second argument array is the list of arguments passed to the memoized function\n\nIn both `useCallback` and `useMemo`, the array is a list of _dependencies_ which determine whether the funciton is called. These can be different than the arguments, although in general practice they are equivalent. The decision to use them directly was both for this common use-case reasons, but also because the implementation complexity would have increased substantially if not.\n\n# Composition\n\nStarting with version `2.3.0`, you can compose `moize` methods. This will create a new memoized method with the original function that shallowly merges the options of the two setups. Example:\n\n```tsx\nimport moize from 'moize';\n\nconst Component = (props: Record\u003cstring, any\u003e) =\u003e \u003cdiv {...props} /\u003e;\n\n// memoizing with react, as since 2.0.0\nconst MemoizedFoo = moize.react(Component);\n\n// creating a separately-memoized method that has maxSize of 5\nconst LastFiveFoo = moize.maxSize(5)(MemoizedFoo);\n```\n\nYou can also create an options-first curriable version of `moize` if you only pass the options:\n\n```ts\nimport moize from 'moize';\n\n// creates a function that will memoize what is passed\nconst limitedSerializedMoize = moize({ maxSize: 5, serialize: true });\n\nconst getWord = (bird) =\u003e `${bird} is the word`;\n\nconst moizedGetWord = limitedSerializedMoize(getWord);\n```\n\nYou can also combine all of these options with `moize.compose` to create `moize` wrappers with pre-defined options.\n\n```ts\nimport moize from 'moize';\n\n// creates a moizer that will have the options of\n// {isReact: true, maxAge: 5000, maxSize: 5}\nconst superLimitedReactMoize = moize.compose(\n    moize.react,\n    moize.maxSize(5),\n    moize.maxAge(5000)\n);\n```\n\n# Collecting statistics\n\nAs-of version 5, you can collect statistics of moize to determine if your cached methods are effective.\n\n```ts\nimport moize from 'moize';\n\nmoize.collectStats();\n\nconst fn = (one: string, two: string) =\u003e [one, two];\n\nconst moized = moize(fn);\n\nmoized('one', 'two');\nmoized('one', 'two');\n\nmoized.getStats(); // {\"calls\": 2, \"hits\": 1, \"usage\": \"50%\"}\n```\n\n**NOTE**: It is recommended not to activate this in production, as it will have a performance decrease.\n\n## Stats methods\n\n## clearStats\n\nCear statistics on `moize`d functions.\n\n```ts\nmoize.clearStats(); // clears all stats\nmoize.clearStats('profile-name'); // clears stats only for 'profile-name'\n```\n\n## collectStats\n\nSet whether collecting statistics on `moize`d functions.\n\n```ts\nmoize.collectStats(true); // start collecting stats\nmoize.collectStats(); // same as passing true\nmoize.collectStats(false); // stop collecting stats\n```\n\n**NOTE**: If collecting statistics, it is recommended to provide a custom [`profileName`](#profilename) or use [`moize.profile()`](#moizeprofile) for all memoized functions. This allows easier mapping of resulting statistics to their origin function when it has a common name or is anonymous.\n\n## getStats([profileName])\n\nGet the statistics for a specific function, or globally.\n\n```ts\nmoize.collectStats();\n\nconst fn = (one: string, two: string) =\u003e [one, two];\n\nconst moized = moize(fn);\n\nconst otherFn = (one: string[]) =\u003e one.slice(0, 1);\n\nconst otherMoized = moize(otherFn, { profileName: 'otherMoized' });\n\nmoized('one', 'two');\nmoized('one', 'two');\n\nmoized.getStats(); // {\"calls\": 2, \"hits\": 1, \"usage\": \"50%\"}\n\notherMoized(['three']);\n\nmoize.getStats('otherMoized'); // {\"calls\": 1, \"hits\": 0, \"usage\": \"0%\"}\n\nmoize.getStats();\n/*\n {\n   \"calls\": 3,\n   \"hits\": 1,\n   \"profiles\": {\n     \"fn at Object..src/utils.js (http://localhost:3000/app.js:153:68)\": {\n       \"calls\": 2,\n       \"hits\": 1,\n       \"usage\": \"50%\"\n     },\n     \"otherMoized\": {\n       \"calls\": 1,\n       \"hits\": 0,\n       \"usage\": \"0%\"\n     }\n   },\n   \"usage\": \"33.3333%\"\n }\n */\n```\n\n# Introspection\n\n## isCollectingStats\n\nAre statistics being collected on memoization usage.\n\n```ts\nmoize.collectStats(true);\nmoize.isCollectingStats(); // true\nmoize.collectStats(false);\nmoize.isCollectingStats(); // false\n```\n\n## isMoized\n\nIs the function passed a moized function.\n\n```ts\nconst fn = () =\u003e {};\nconst moizedFn = moize(fn);\n\nmoize.isMoized(fn); // false\nmoize.isMoized(moizedFn); // true\n```\n\n# Direct cache manipulation\n\nThe cache is available on the `moize`d function as a property, and while it is not recommended to modify it directly, that option is available for edge cases.\n\n## cache\n\nThe shape of the `cache` is as follows:\n\n```ts\ntype Cache = {\n    keys: any[][];\n    size: number;\n    values: any[];\n};\n```\n\nRegardless of how the key is transformed, it is always stored as an array (if the value returned is not an array, it is coalesced to one).\n\n**NOTE**: The order of `keys` and `values` should always align, so be aware when manually manipulating the cache that you need to manually keep in sync any changes to those arrays.\n\n## cacheSnapshot\n\nThe `cache` is mutated internally for performance reasons, so logging out the cache at a specific step in the workflow may not give you the information you need. As such, to help with debugging you can request the `cacheSnapshot`, which has the same shape as the `cache` but is a shallow clone of each property for persistence.\n\nThere are also convenience methods provided on the `moize`d function which allow for programmatic manipulation of the cache.\n\n## add(key, value)\n\nThis will manually add the _value_ at _key_ in cache if _key_ does not already exist. _key_ should be an `Array` of values, meant to reflect the arguments passed to the method.\n\n```ts\n// single parameter is straightforward\nconst memoized = moize((item: string) =\u003e item: string);\n\nmemoized.add(['one'], 'two');\n\n// pulls from cache\nmemoized('one');\n```\n\n**NOTE**: This will only add `key`s that do not exist in the cache, and will do nothing if the `key` already exists. If you want to update keys that already exist, use [`update`](#updatekey-value).\n\n## clear()\n\nThis will clear all values in the cache, resetting it to an empty state.\n\n```ts\nconst memoized = moize((item: string) =\u003e item);\n\nmemoized.clear();\n```\n\n## get(key)\n\nReturns the value in cache if the key matches, else returns `undefined`. _key_ should be an `Array` of values, meant to reflect the arguments passed to the method.\n\n```ts\nconst memoized = moize((one: string, two: string) =\u003e [one, two);\n\nmemoized('one', 'two');\n\nconsole.log(memoized.get(['one', 'two'])); // [\"one\",\"two\"]\nconsole.log(memoized.get(['two', 'three'])); // undefined\n```\n\n## getStats()\n\nReturns the statistics for the function.\n\n```ts\nmoize.collectStats();\n\nconst memoized = moize((one: string, two: string) =\u003e [one, two);\n\nmemoized('one', 'two');\nmemoized('one', 'two');\n\nconsole.log(memoized.getStats()); // {\"calls\": 2, \"hits\": 1, \"usage\": \"50%\"}\n```\n\n**NOTE**: You must be collecting statistics for this to be populated.\n\n## has(key)\n\nThis will return `true` if a cache entry exists for the _key_ passed, else will return `false`. _key_ should be an `Array` of values, meant to reflect the arguments passed to the method.\n\n```ts\nconst memoized = moize((one: string, two: string) =\u003e [one, two]);\n\nmemoized('one', 'two');\n\nconsole.log(memoized.has(['one', 'two'])); // true\nconsole.log(memoized.has(['two', 'three'])); // false\n```\n\n## keys()\n\nThis will return a list of the current keys in `cache`.\n\n```ts\nconst memoized = moize.maxSize(2)((item: any) =\u003e item);\n\nmemoized('one');\nmemoized({ two: 'three' });\n\nconst keys = memoized.keys(); // [['one'], [{two: 'three'}]]\n```\n\n## remove(key)\n\nThis will remove the provided _key_ from cache. _key_ should be an `Array` of values, meant to reflect the arguments passed to the method.\n\n```ts\nconst memoized = moize((item: { one: string }) =\u003e item);\n\nconst arg = { one: 'one' };\n\nmemoized(arg);\n\nmemoized.remove([arg]);\n\n// will re-execute, as it is no longer in cache\nmemoized(arg);\n```\n\n**NOTE**: This will only remove `key`s that exist in the cache, and will do nothing if the `key` does not exist.\n\n## update(key, value)\n\nThis will manually update the _value_ at _key_ in cache if _key_ exists. _key_ should be an `Array` of values, meant to reflect the arguments passed to the method.\n\n```ts\n// single parameter is straightforward\nconst memoized = moize((item: string) =\u003e item);\n\nmemoized.add(['one'], 'two');\n\n// pulls from cache\nmemoized('one');\n```\n\n**NOTE**: This will only update `key`s that exist in the cache, and will do nothing if the `key` does not exist. If you want to add keys that do not already exist, use [`add`](#addkey-value).\n\n## values()\n\nThis will return a list of the current values in `cache`.\n\n```ts\nconst memoized = moize.maxSize(2)((item: string | { two: string }) =\u003e ({\n    item,\n}));\n\nmemoized('one');\nmemoized({ two: 'three' });\n\nconst values = memoized.values(); // [{item: 'one'}, {item: {two: 'three'}}]\n```\n\n# Benchmarks\n\nAll values provided are the number of operations per second calculated by the [Benchmark suite](https://benchmarkjs.com/), where a higher value is better. Each benchmark was performed using the default configuration of the library, with a fibonacci calculation based on a starting parameter of `35`, using single and multiple parameters with different object types. The results were averaged to determine overall speed across possible usage.\n\n**NOTE**: `lodash`, `ramda`, and `underscore` do not support multiple-parameter memoization without use of a `resolver` function. For consistency in comparison, each use the same `resolver` that returns the result of `JSON.stringify` on the arguments.\n\n| Name         | Overall (average) | Single (average) | Multiple (average) | single primitive | single array   | single object  | multiple primitive | multiple array | multiple object |\n| ------------ | ----------------- | ---------------- | ------------------ | ---------------- | -------------- | -------------- | ------------------ | -------------- | --------------- |\n| **moize**    | **71,177,801**    | **98,393,482**   | **43,962,121**     | **139,808,786**  | **97,571,202** | **57,800,460** | **44,509,528**     | **44,526,039** | **42,850,796**  |\n| lru-memoize  | 48,391,839        | 64,270,849       | 32,512,830         | 77,863,436       | 59,876,764     | 55,072,348     | 29,917,027         | 33,308,028     | 34,313,435      |\n| mem          | 42,348,320        | 83,158,473       | 1,538,166          | 128,731,510      | 73,473,478     | 47,270,433     | 2,012,120          | 1,565,253      | 1,037,126       |\n| fast-memoize | 33,145,713        | 64,942,152       | 1,349,274          | 190,677,799      | 2,149,467      | 1,999,192      | 1,718,229          | 1,297,911      | 1,031,683       |\n| lodash       | 25,700,293        | 49,941,573       | 1,459,013          | 67,513,655       | 48,874,559     | 33,436,506     | 1,861,982          | 1,402,532      | 1,112,527       |\n| memoizee     | 21,546,499        | 27,447,855       | 15,645,143         | 29,701,124       | 27,294,197     | 25,348,244     | 15,359,792         | 15,855,421     | 15,720,217      |\n| ramda        | 18,804,380        | 35,919,033       | 1,689,727          | 101,557,928      | 1,895,956      | 4,303,215      | 2,305,025          | 1,597,131      | 1,167,025       |\n| memoizerific | 6,745,058         | 7,382,030        | 6,108,086          | 8,488,885        | 6,427,832      | 7,229,375      | 5,772,461          | 6,278,344      | 6,273,453       |\n| underscore   | 6,701,695         | 11,698,265       | 1,705,126          | 18,249,423       | 4,695,658      | 12,149,714     | 2,310,412          | 1,630,769      | 1,174,197       |\n| addy-osmani  | 4,926,732         | 6,370,152        | 3,483,311          | 12,506,809       | 3,568,399      | 3,035,249      | 6,898,542          | 2,009,089      | 1,542,304       |\n\n# Filesize\n\n`moize` is fairly small (~3.86KB when minified and gzipped), however it provides a large number of configuration options to satisfy a number of edge cases. If filesize is a concern, you may consider using [`micro-memoize`](https://github.com/planttheidea/micro-memoize). This is the memoization library that powers `moize` under-the-hood, and will handle most common use cases at 1/4 the size of `moize`.\n\n# Browser support\n\n-   Chrome (all versions)\n-   Firefox (all versions)\n-   Edge (all versions)\n-   Opera 15+\n-   IE 9+\n-   Safari 6+\n-   iOS 8+\n-   Android 4+\n\n# Development\n\nStandard stuff, clone the repo and `npm install` dependencies. The npm scripts available:\n\n-   `benchmark` =\u003e run the benchmark suite pitting `moize` against other libraries in common use-cases\n-   `benchmark:alternative` =\u003e run the benchmark suite for alternative forms of caching\n-   `benchmark:array` =\u003e run the benchmark suite for memoized methods using single and multiple `array` parameters\n-   `benchmark:object` =\u003e run the benchmark suite for memoized methods using single and multiple `object` parameters\n-   `benchmark:primitive` =\u003e run the benchmark suite for memoized methods using single and multiple `object` parameters\n-   `benchmark:react` =\u003e run the benchmark suite for memoized React components\n-   `build` =\u003e run rollup to build the distributed files in `dist`\n-   `clean:dist` =\u003e run `rimraf` on the `dist` folder\n-   `clean:docs` =\u003e run `rimraf` on the `docs` folder\n-   `clean:mjs` =\u003e run `rimraf` on the `mjs` folder\n-   `copy:mjs` =\u003e run `clean:mjs` and the `es-to-mjs` script\n-   `copy:types` =\u003e copy internal types to be available for consumer\n-   `dev` =\u003e run webpack dev server to run example app (playground!)\n-   `dist` =\u003e runs `clean:dist` and `build`\n-   `docs` =\u003e runs `clean:docs` and builds the docs via `jsdoc`\n-   `flow` =\u003e runs `flow check` on the files in `src`\n-   `lint` =\u003e runs ESLint against all files in the `src` folder\n-   `lint:fix` =\u003e runs `lint`, fixing any errors if possible\n-   `test` =\u003e run `jest` test functions with `NODE_ENV=test`\n-   `test:coverage` =\u003e run `test` but with code coverage\n-   `test:watch` =\u003e run `test`, but with persistent watcher\n-   `typecheck` =\u003e run `tsc` against source code to validate TypeScript\n","funding_links":[],"categories":["TypeScript","Utilities","Ramda"],"sub_categories":["React Components","planttheidea/moize"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplanttheidea%2Fmoize","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fplanttheidea%2Fmoize","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplanttheidea%2Fmoize/lists"}