Skip to content

Commit

Permalink
Accept either cache instance or constructor for wrapOptions.cache.
Browse files Browse the repository at this point in the history
Answering "why not both?" to this discussion:
#615 (comment)
  • Loading branch information
benjamn committed Nov 28, 2023
1 parent 789bdec commit 76a371e
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 17 deletions.
39 changes: 30 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export type OptimisticWrapperFunction<
readonly size: number;

// Snapshot of wrap options used to create this wrapper function.
options: OptimisticWrapOptions<TArgs, TKeyArgs, TCacheKey>;
options: OptionsWithCacheInstance<TArgs, TKeyArgs, TCacheKey>;

// "Dirty" any cached Entry stored for the given arguments, marking that Entry
// and its ancestors as potentially needing to be recomputed. The .dirty(...)
Expand Down Expand Up @@ -90,6 +90,9 @@ export type OptimisticWrapperFunction<
};

export { CommonCache }
export interface CommonCacheConstructor<TCacheKey, TResult, TArgs extends any[]> extends Function {
new <K extends TCacheKey, V extends Entry<TArgs, TResult>>(max?: number, dispose?: (value: V, key?: K) => void): CommonCache<K,V>;
}

export type OptimisticWrapOptions<
TArgs extends any[],
Expand All @@ -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<NoInfer<TCacheKey>, Entry<NoInfer<TArgs>, NoInfer<TResult>>>;
cache?: CommonCache<NoInfer<TCacheKey>, Entry<NoInfer<TArgs>, NoInfer<TResult>>>
| CommonCacheConstructor<NoInfer<TCacheKey>, NoInfer<TResult>, NoInfer<TArgs>>;
};

export interface OptionsWithCacheInstance<
TArgs extends any[],
TKeyArgs extends any[] = TArgs,
TCacheKey = any,
TResult = any,
> extends OptimisticWrapOptions<TArgs, TKeyArgs, TCacheKey, TResult> {
cache: CommonCache<NoInfer<TCacheKey>, Entry<NoInfer<TArgs>, NoInfer<TResult>>>;
};

const caches = new Set<CommonCache<any, AnyEntry>>();
Expand All @@ -125,11 +138,20 @@ export function wrap<
makeCacheKey = (defaultMakeCacheKey as () => TCacheKey),
keyArgs,
subscribe,
cache = new StrongCache(
max,
entry => entry.dispose(),
),
cache: cacheOption,
}: OptimisticWrapOptions<TArgs, TKeyArgs, TCacheKey, TResult> = Object.create(null)) {
let cache: CommonCache<TCacheKey, Entry<TArgs, TResult>>;
if (cacheOption) {
cache = typeof cacheOption === "function"
? new cacheOption(max)
: cacheOption;
} else {
cache = new StrongCache<TCacheKey, Entry<TArgs, TResult>>(
max,
entry => entry.dispose(),
);
}

const optimistic = function (): TResult {
const key = makeCacheKey.apply(
null,
Expand Down Expand Up @@ -171,9 +193,7 @@ export function wrap<
} as OptimisticWrapperFunction<TArgs, TResult, TKeyArgs, TCacheKey>;

Object.defineProperty(optimistic, "size", {
get() {
return cache.size;
},
get: () => cache.size,
configurable: false,
enumerable: false,
});
Expand All @@ -183,6 +203,7 @@ export function wrap<
makeCacheKey,
keyArgs,
subscribe,
cache,
});

function dirtyKey(key: TCacheKey) {
Expand Down
51 changes: 43 additions & 8 deletions src/tests/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<K, V> implements CommonCache<K, V> {
private _cache = new Map<K, V>()
has = this._cache.has.bind(this._cache);
Expand Down Expand Up @@ -69,6 +69,36 @@ describe("optimism", function () {
assert.strictEqual(wrapped({ value: "test" }), "test modified");
});

it("can manually specify a cache constructor", () => {
class Cache<K, V> implements CommonCache<K, V> {
private _cache = new Map<K, V>()
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",
Expand Down Expand Up @@ -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<string>();
const fib = wrap((n: number): number => {
d("shared");
Expand All @@ -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);
Expand All @@ -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);
});
});

0 comments on commit 76a371e

Please sign in to comment.