diff --git a/src/index.ts b/src/index.ts index 915d1b3..137cda8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -55,7 +55,7 @@ export type OptimisticWrapperFunction< readonly size: number; // Snapshot of wrap options used to create this wrapper function. - options: OptimisticWrapOptions; + options: OptionsWithCacheInstance; // "Dirty" any cached Entry stored for the given arguments, marking that Entry // and its ancestors as potentially needing to be recomputed. The .dirty(...) @@ -90,6 +90,9 @@ export type OptimisticWrapperFunction< }; export { CommonCache } +export interface CommonCacheConstructor extends Function { + new >(max?: number, dispose?: (value: V, key?: K) => void): CommonCache; +} export type OptimisticWrapOptions< TArgs extends any[], @@ -110,7 +113,17 @@ export type OptimisticWrapOptions< // If provided, the subscribe function should either return an unsubscribe // function or return nothing. subscribe?: (...args: TArgs) => void | (() => any); - cache?: CommonCache, Entry, NoInfer>>; + cache?: CommonCache, Entry, NoInfer>> + | CommonCacheConstructor, NoInfer, NoInfer>; +}; + +export interface OptionsWithCacheInstance< + TArgs extends any[], + TKeyArgs extends any[] = TArgs, + TCacheKey = any, + TResult = any, +> extends OptimisticWrapOptions { + cache: CommonCache, Entry, NoInfer>>; }; const caches = new Set>(); @@ -125,11 +138,20 @@ export function wrap< makeCacheKey = (defaultMakeCacheKey as () => TCacheKey), keyArgs, subscribe, - cache = new StrongCache( - max, - entry => entry.dispose(), - ), + cache: cacheOption, }: OptimisticWrapOptions = Object.create(null)) { + let cache: CommonCache>; + if (cacheOption) { + cache = typeof cacheOption === "function" + ? new cacheOption(max) + : cacheOption; + } else { + cache = new StrongCache>( + max, + entry => entry.dispose(), + ); + } + const optimistic = function (): TResult { const key = makeCacheKey.apply( null, @@ -171,9 +193,7 @@ export function wrap< } as OptimisticWrapperFunction; Object.defineProperty(optimistic, "size", { - get() { - return cache.size; - }, + get: () => cache.size, configurable: false, enumerable: false, }); @@ -183,6 +203,7 @@ export function wrap< makeCacheKey, keyArgs, subscribe, + cache, }); function dirtyKey(key: TCacheKey) { diff --git a/src/tests/api.ts b/src/tests/api.ts index 6cafa99..985765a 100644 --- a/src/tests/api.ts +++ b/src/tests/api.ts @@ -37,7 +37,7 @@ describe("optimism", function () { assert.strictEqual(test("a"), "aNaCl"); }); - it("can manually set the `Cache` implementation", () => { + it("can manually specify a cache instance", () => { class Cache implements CommonCache { private _cache = new Map() has = this._cache.has.bind(this._cache); @@ -69,6 +69,36 @@ describe("optimism", function () { assert.strictEqual(wrapped({ value: "test" }), "test modified"); }); + it("can manually specify a cache constructor", () => { + class Cache implements CommonCache { + private _cache = new Map() + has = this._cache.has.bind(this._cache); + get = this._cache.get.bind(this._cache); + delete = this._cache.delete.bind(this._cache); + get size(){ return this._cache.size } + set(key: K, value: V): V { + this._cache.set(key, value); + return value; + } + clean(){}; + } + + const wrapped = wrap( + (obj: { value: string }) => obj.value + " transformed", + { + cache: Cache, + makeCacheKey(obj) { + return obj.value; + }, + } + ); + assert.ok(wrapped.options.cache instanceof Cache); + assert.strictEqual(wrapped({ value: "test" }), "test transformed"); + assert.strictEqual(wrapped({ value: "test" }), "test transformed"); + wrapped.options.cache.get("test").value[0] = "test modified"; + assert.strictEqual(wrapped({ value: "test" }), "test modified"); + }); + it("works with two layers of functions", function () { const files: { [key: string]: string } = { "a.js": "a", @@ -725,7 +755,7 @@ describe("optimism", function () { assert.strictEqual(sumFirst.forget(9), false); }); - it("exposes optimistic.size property, returning cache.map.size", function () { + it("exposes optimistic.{size,options.cache.size} properties", function () { const d = dep(); const fib = wrap((n: number): number => { d("shared"); @@ -736,7 +766,12 @@ describe("optimism", function () { }, }); - assert.strictEqual(fib.size, 0); + function size() { + assert.strictEqual(fib.options.cache.size, fib.size); + return fib.size; + } + + assert.strictEqual(size(), 0); assert.strictEqual(fib(0), 0); assert.strictEqual(fib(1), 1); @@ -748,22 +783,22 @@ describe("optimism", function () { assert.strictEqual(fib(7), 13); assert.strictEqual(fib(8), 21); - assert.strictEqual(fib.size, 9); + assert.strictEqual(size(), 9); fib.dirty(6); // Merely dirtying an Entry does not remove it from the LRU cache. - assert.strictEqual(fib.size, 9); + assert.strictEqual(size(), 9); fib.forget(6); // Forgetting an Entry both dirties it and removes it from the LRU cache. - assert.strictEqual(fib.size, 8); + assert.strictEqual(size(), 8); fib.forget(4); - assert.strictEqual(fib.size, 7); + assert.strictEqual(size(), 7); // This way of calling d.dirty causes any parent Entry objects to be // forgotten (removed from the LRU cache). d.dirty("shared", "forget"); - assert.strictEqual(fib.size, 0); + assert.strictEqual(size(), 0); }); });