// Copyright 2018-2026 the Deno authors. MIT license.
import type { MemoizationCache } from "./memoize.ts";
/**
* Default serialization of arguments list for use as cache keys. Equivalence
* follows [`SameValueZero`](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevaluezero)
* reference equality, such that `getKey(x, y) === getKey(x, y)` for all values
* of `x` and `y`, but `getKey({}) !== getKey({})`.
*
* @param cache The cache for which the keys will be used.
* @returns `getKey`, the function for getting cache keys.
*/
export function _serializeArgList<Return>(
cache: MemoizationCache<unknown, Return>,
): (this: unknown, ...args: unknown[]) => string {
// Three cooperating data structures track weak (reference-type) arguments:
// 1. weakKeyToKeySegmentCache: WeakMap from object/symbol → segment id
// (e.g. `"{0}"`) so the same reference always maps to the same segment.
// 2. weakKeySegmentToKeyCache: Map from segment id → set of composite cache
// keys that contain that segment, used by the finalization callback.
// 3. registry (FinalizationRegistry): when a weak key is garbage-collected,
// looks up its segment in (2) and deletes all associated entries from the
// caller-provided cache.
const weakKeyToKeySegmentCache = new WeakMap<WeakKey, string>();
const weakKeySegmentToKeyCache = new Map<string, Set<string>>();
let nextWeakKeyId = 0;
const registry = new FinalizationRegistry<string>((keySegment) => {
for (const key of weakKeySegmentToKeyCache.get(keySegment) ?? []) {
cache.delete(key);
}
weakKeySegmentToKeyCache.delete(keySegment);
});
return function getKey(...args) {
const weakKeySegments: string[] = [];
const keySegments = [this, ...args].map((arg) => {
if (typeof arg === "undefined") return "undefined";
if (typeof arg === "bigint") return `${arg}n`;
if (typeof arg === "number") {
return String(arg);
}
if (
arg === null ||
typeof arg === "string" ||
typeof arg === "boolean"
) {
// This branch will need to be updated if further types are added to
// the language that support value equality,
// e.g. https://github.com/tc39/proposal-record-tuple
return JSON.stringify(arg);
}
if (typeof arg === "symbol") {
try {
new WeakRef(arg);
} catch {
return `Symbol.for(${JSON.stringify(arg.description)})`;
}
}
try {
new WeakRef(arg as WeakKey);
} catch {
throw new Error(
"Should be unreachable: please open an issue at https://github.com/denoland/std/issues/new",
);
}
let keySegment = weakKeyToKeySegmentCache.get(arg as WeakKey);
if (keySegment === undefined) {
keySegment = `{${nextWeakKeyId++}}`;
registry.register(arg as WeakKey, keySegment);
weakKeyToKeySegmentCache.set(arg as WeakKey, keySegment);
}
weakKeySegments.push(keySegment);
return keySegment;
});
const key = keySegments.join(",");
for (const keySegment of weakKeySegments) {
let keys = weakKeySegmentToKeyCache.get(keySegment);
if (keys === undefined) {
keys = new Set();
weakKeySegmentToKeyCache.set(keySegment, keys);
}
keys.add(key);
}
return key;
};
}
|