Skip to content

Commit 0755c28

Browse files
authored
feat: memo, add keyHash option (#54)
* apply a custom hashing func or use the default
1 parent 3986841 commit 0755c28

3 files changed

Lines changed: 132 additions & 1 deletion

File tree

src/__tests__/__snapshots__/server.caching.test.ts.snap

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,124 @@ exports[`memo should fire "onCacheRollout" callback on cache rollout, one entry
720720
]
721721
`;
722722

723+
exports[`memo should memoize a function, allow custom key hashing: async 1`] = `
724+
[
725+
{
726+
"cacheKeys": [
727+
"custom-hash-",
728+
],
729+
"cacheLength": 2,
730+
"errorValue": undefined,
731+
"successValue": "[EMPTY]",
732+
"type": "memo promise",
733+
},
734+
{
735+
"cacheKeys": [
736+
"custom-hash-",
737+
],
738+
"cacheLength": 2,
739+
"errorValue": undefined,
740+
"successValue": "[EMPTY]",
741+
"type": "memo promise",
742+
},
743+
{
744+
"cacheKeys": [
745+
"custom-hash-1",
746+
],
747+
"cacheLength": 2,
748+
"errorValue": undefined,
749+
"successValue": "1",
750+
"type": "memo promise",
751+
},
752+
{
753+
"cacheKeys": [
754+
"custom-hash-1",
755+
],
756+
"cacheLength": 2,
757+
"errorValue": undefined,
758+
"successValue": "1",
759+
"type": "memo promise",
760+
},
761+
{
762+
"cacheKeys": [
763+
"custom-hash-2,true",
764+
],
765+
"cacheLength": 2,
766+
"errorValue": "2",
767+
"successValue": undefined,
768+
"type": "memo promise",
769+
},
770+
{
771+
"cacheKeys": [
772+
"custom-hash-2,true",
773+
],
774+
"cacheLength": 2,
775+
"errorValue": "2",
776+
"successValue": undefined,
777+
"type": "memo promise",
778+
},
779+
]
780+
`;
781+
782+
exports[`memo should memoize a function, allow custom key hashing: sync 1`] = `
783+
[
784+
{
785+
"cacheKeys": [
786+
"custom-hash-",
787+
],
788+
"cacheLength": 2,
789+
"errorValue": undefined,
790+
"successValue": "[EMPTY]",
791+
"type": "memo",
792+
},
793+
{
794+
"cacheKeys": [
795+
"custom-hash-",
796+
],
797+
"cacheLength": 2,
798+
"errorValue": undefined,
799+
"successValue": "[EMPTY]",
800+
"type": "memo",
801+
},
802+
{
803+
"cacheKeys": [
804+
"custom-hash-1",
805+
],
806+
"cacheLength": 2,
807+
"errorValue": undefined,
808+
"successValue": "1",
809+
"type": "memo",
810+
},
811+
{
812+
"cacheKeys": [
813+
"custom-hash-1",
814+
],
815+
"cacheLength": 2,
816+
"errorValue": undefined,
817+
"successValue": "1",
818+
"type": "memo",
819+
},
820+
{
821+
"cacheKeys": [
822+
"custom-hash-2,true",
823+
],
824+
"cacheLength": 2,
825+
"errorValue": "2",
826+
"successValue": undefined,
827+
"type": "memo error",
828+
},
829+
{
830+
"cacheKeys": [
831+
"custom-hash-2,true",
832+
],
833+
"cacheLength": 2,
834+
"errorValue": "2",
835+
"successValue": undefined,
836+
"type": "memo error",
837+
},
838+
]
839+
`;
840+
723841
exports[`memo should memoize a function, cacheLimit: async 1`] = `
724842
[
725843
{

src/__tests__/server.caching.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ describe('memo', () => {
3434
description: 'disable memoization when cacheLimit is zero',
3535
options: { cacheLimit: 0 },
3636
params: [[], [], [1], [1], [2, true], [2, true]]
37+
},
38+
{
39+
description: 'allow custom key hashing',
40+
options: {
41+
keyHash: (value: unknown) => `custom-hash-${value}`
42+
},
43+
params: [[], [], [1], [1], [2, true], [2, true]]
3744
}
3845
])('should memoize a function, $description', async ({ options, params }) => {
3946
const log: any[] = [];

src/server.caching.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ type MemoDebugHandler<TReturn = unknown> = (info: { type: string; value: unknown
5656
* @property [cacheLimit] Number of entries to cache before overwriting previous entries (default: 1)
5757
* @property {MemoDebugHandler<TReturn>} [debug] Debug callback function
5858
* @property [expire] Expandable milliseconds until cache expires
59+
* @property [keyHash] Function to generate a predictable hash key from the provided arguments. Defaults to internal `generateHash`.
5960
* @property {OnMemoCacheHandler<TReturn>} [onCacheExpire] Callback when cache expires. Only fires when the `expire` option is set.
6061
* @property {OnMemoCacheHandler<TReturn>} [onCacheRollout] Callback when cache entries are rolled off due to cache limit.
6162
*/
@@ -64,6 +65,7 @@ interface MemoOptions<TReturn = unknown> {
6465
cacheLimit?: number;
6566
debug?: MemoDebugHandler<TReturn>;
6667
expire?: number;
68+
keyHash?: (args: unknown[]) => unknown;
6769
onCacheExpire?: OnMemoCacheHandler<TReturn>;
6870
onCacheRollout?: OnMemoCacheHandler<TReturn>;
6971
}
@@ -93,6 +95,7 @@ const memo = <TArgs extends unknown[], TReturn = unknown>(
9395
cacheLimit = 1,
9496
debug = () => {},
9597
expire,
98+
keyHash = generateHash,
9699
onCacheExpire,
97100
onCacheRollout
98101
}: MemoOptions<TReturn> = {}
@@ -104,6 +107,9 @@ const memo = <TArgs extends unknown[], TReturn = unknown>(
104107
const isOnCacheRolloutPromise = isPromise(onCacheRollout);
105108
const isOnCacheRollout = typeof onCacheRollout === 'function' || isOnCacheRolloutPromise;
106109
const updatedExpire = Number.parseInt(String(expire), 10) || undefined;
110+
const setKey = function (value: unknown[]): unknown {
111+
return keyHash.call(null, value);
112+
};
107113

108114
const ized = function () {
109115
const cache: MemoCache<TReturn> = [];
@@ -159,7 +165,7 @@ const memo = <TArgs extends unknown[], TReturn = unknown>(
159165
return bypassValue;
160166
}
161167

162-
const key = generateHash(args);
168+
const key = setKey(args);
163169

164170
// Parse, memoize and return the original value
165171
if (cache.indexOf(key) < 0) {

0 commit comments

Comments
 (0)