Skip to content

Commit adedc7e

Browse files
authored
Expose frozen wrap(...).options on functions returned by wrap (#504)
* Expose frozen `wrapped.options` on functions returned by `wrap`. * Simplify/lazify usage of `defaultMakeCacheKey`.
1 parent 0340e19 commit adedc7e

File tree

2 files changed

+53
-21
lines changed

2 files changed

+53
-21
lines changed

src/index.ts

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,20 @@ export {
2323
// of computation. Subscriptions are supported.
2424
export { dep, OptimisticDependencyFunction } from "./dep.js";
2525

26-
function makeDefaultMakeCacheKeyFunction<
27-
TKeyArgs extends any[],
28-
TCacheKey = any,
29-
>(): (...args: TKeyArgs) => TCacheKey {
30-
const keyTrie = new Trie<TCacheKey>(typeof WeakMap === "function");
31-
return function () {
32-
return keyTrie.lookupArray(arguments);
33-
};
34-
}
35-
3626
// The defaultMakeCacheKey function is remarkably powerful, because it gives
3727
// a unique object for any shallow-identical list of arguments. If you need
3828
// to implement a custom makeCacheKey function, you may find it helpful to
3929
// delegate the final work to defaultMakeCacheKey, which is why we export it
4030
// here. However, you may want to avoid defaultMakeCacheKey if your runtime
4131
// does not support WeakMap, or you have the ability to return a string key.
4232
// In those cases, just write your own custom makeCacheKey functions.
43-
export const defaultMakeCacheKey = makeDefaultMakeCacheKeyFunction();
33+
let defaultKeyTrie: Trie<object> | undefined;
34+
export function defaultMakeCacheKey(...args: any[]): object {
35+
const trie = defaultKeyTrie || (
36+
defaultKeyTrie = new Trie(typeof WeakMap === "function")
37+
);
38+
return trie.lookupArray(args);
39+
}
4440

4541
// If you're paranoid about memory leaks, or you want to avoid using WeakMap
4642
// under the hood, but you still need the behavior of defaultMakeCacheKey,
@@ -56,6 +52,9 @@ export type OptimisticWrapperFunction<
5652
// Get the current number of Entry objects in the LRU cache.
5753
readonly size: number;
5854

55+
// Snapshot of wrap options used to create this wrapper function.
56+
options: OptimisticWrapOptions<TArgs, TKeyArgs, TCacheKey>;
57+
5958
// "Dirty" any cached Entry stored for the given arguments, marking that Entry
6059
// and its ancestors as potentially needing to be recomputed. The .dirty(...)
6160
// method of an optimistic function takes the same parameter types as the
@@ -115,19 +114,17 @@ export function wrap<
115114
TResult,
116115
TKeyArgs extends any[] = TArgs,
117116
TCacheKey = any,
118-
>(
119-
originalFunction: (...args: TArgs) => TResult,
120-
options: OptimisticWrapOptions<TArgs, TKeyArgs> = Object.create(null),
121-
) {
117+
>(originalFunction: (...args: TArgs) => TResult, {
118+
max = Math.pow(2, 16),
119+
makeCacheKey = defaultMakeCacheKey,
120+
keyArgs,
121+
subscribe,
122+
}: OptimisticWrapOptions<TArgs, TKeyArgs> = Object.create(null)) {
122123
const cache = new Cache<TCacheKey, Entry<TArgs, TResult>>(
123-
options.max || Math.pow(2, 16),
124+
max,
124125
entry => entry.dispose(),
125126
);
126127

127-
const keyArgs = options.keyArgs;
128-
const makeCacheKey = options.makeCacheKey ||
129-
makeDefaultMakeCacheKeyFunction<TKeyArgs, TCacheKey>();
130-
131128
const optimistic = function (): TResult {
132129
const key = makeCacheKey.apply(
133130
null,
@@ -141,7 +138,7 @@ export function wrap<
141138
let entry = cache.get(key)!;
142139
if (!entry) {
143140
cache.set(key, entry = new Entry(originalFunction));
144-
entry.subscribe = options.subscribe;
141+
entry.subscribe = subscribe;
145142
// Give the Entry the ability to trigger cache.delete(key), even though
146143
// the Entry itself does not know about key or cache.
147144
entry.forget = () => cache.delete(key);
@@ -176,6 +173,13 @@ export function wrap<
176173
enumerable: false,
177174
});
178175

176+
Object.freeze(optimistic.options = {
177+
max,
178+
makeCacheKey,
179+
keyArgs,
180+
subscribe,
181+
});
182+
179183
function dirtyKey(key: TCacheKey) {
180184
const entry = cache.get(key);
181185
if (entry) {

src/tests/api.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,31 @@ describe("optimism", function () {
502502
assert.strictEqual(sumNums("a", 1, "b", 2, "c", 3), sumObj2);
503503
});
504504

505+
it("supports wrap(fn, {...}).options to reflect input options", function () {
506+
const keyArgs: () => [] = () => [];
507+
function makeCacheKey() { return "constant"; }
508+
function subscribe() {}
509+
510+
let counter1 = 0;
511+
const wrapped = wrap(() => ++counter1, {
512+
max: 10,
513+
keyArgs,
514+
makeCacheKey,
515+
subscribe,
516+
});
517+
assert.strictEqual(wrapped.options.max, 10);
518+
assert.strictEqual(wrapped.options.keyArgs, keyArgs);
519+
assert.strictEqual(wrapped.options.makeCacheKey, makeCacheKey);
520+
assert.strictEqual(wrapped.options.subscribe, subscribe);
521+
522+
let counter2 = 0;
523+
const wrappedWithDefaults = wrap(() => ++counter2);
524+
assert.strictEqual(wrappedWithDefaults.options.max, Math.pow(2, 16));
525+
assert.strictEqual(wrappedWithDefaults.options.keyArgs, void 0);
526+
assert.strictEqual(typeof wrappedWithDefaults.options.makeCacheKey, "function");
527+
assert.strictEqual(wrappedWithDefaults.options.subscribe, void 0);
528+
});
529+
505530
it("tolerates cycles when propagating dirty/clean signals", function () {
506531
let counter = 0;
507532
const dep = wrap(() => ++counter);
@@ -541,6 +566,8 @@ describe("optimism", function () {
541566
max: 10
542567
});
543568

569+
assert.strictEqual(fib.options.max, 10);
570+
544571
assert.strictEqual(fib(78), 8944394323791464);
545572
assert.strictEqual(fib(68), 72723460248141);
546573
assert.strictEqual(fib(58), 591286729879);
@@ -633,6 +660,7 @@ describe("optimism", function () {
633660
}
634661
});
635662

663+
assert.strictEqual(sumFirst.options.makeCacheKey!(7), 14);
636664
assert.strictEqual(sumFirst(10), 55);
637665

638666
/*

0 commit comments

Comments
 (0)