@dotpress/cache 2.0.0-alpha.4 → 2.0.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/als.d.ts CHANGED
@@ -1,8 +1,4 @@
1
- import { Memo, MemoDefinition } from './index.js';
2
- import { MemoScope } from './plugin.js';
3
- import './duration-2mZnG0_i.js';
4
- import './store-D0BfLqyM.js';
5
- import '@dotpress/core';
1
+ import { MemoScope, Memo, MemoDefinition } from './index.js';
6
2
 
7
3
  /**
8
4
  * Runs a function inside the scope of a memo (a new one by default): the
package/dist/als.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createMemo
3
- } from "./chunk-6ZNJJARH.js";
3
+ } from "./chunk-NAI2ODC5.js";
4
4
 
5
5
  // src/als.ts
6
6
  import { AsyncLocalStorage } from "node:async_hooks";
package/dist/als.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/als.ts"],"sourcesContent":["// AsyncLocalStorage: Node.js, Bun, Deno, and Cloudflare Workers with the\n// nodejs_compat (or nodejs_als) compatibility flag.\n/// <reference types=\"node\" />\nimport { AsyncLocalStorage } from 'node:async_hooks'\nimport { createMemo, type Memo, type MemoDefinition } from './memo.js'\nimport type { MemoScope } from './plugin.js'\n\nconst storage = new AsyncLocalStorage<Memo>()\n\n/**\n * Runs a function inside the scope of a memo (a new one by default): the\n * memoized loaders called during its run, awaits included, share it.\n *\n * ```ts\n * // A listener, a job, a script\n * await runWithMemo(() => sendInvoices(customerId))\n * ```\n */\nexport const runWithMemo = <T>(fn: () => T, memo: Memo = createMemo()): T => {\n if (typeof fn !== 'function') {\n throw new TypeError('runWithMemo(): expected a function.')\n }\n return storage.run(memo, fn)\n}\n\n/** The memo of the current scope; throws outside a scope. */\nexport const currentMemo = (): Memo => {\n const memo = storage.getStore()\n if (!memo) {\n throw new Error(\n 'No memo scope: run the code with runWithMemo(), or install cachePlugin({ memoScope: asyncLocalMemo }).'\n )\n }\n return memo\n}\n\n/**\n * A function that loads through the memo of the current scope, without\n * passing a context around.\n *\n * ```ts\n * export const getUser = memoized(loadUser)\n * // anywhere during a request or a runWithMemo() call\n * const user = await getUser(id)\n * ```\n */\nexport const memoized = <A extends readonly unknown[], T>(\n definition: MemoDefinition<A, T>\n): ((...args: A) => Promise<T>) => {\n return (...args: A) => currentMemo()(definition, ...args)\n}\n\n/**\n * Scope of `cachePlugin({ memoScope: asyncLocalMemo })`: each request runs\n * with `ctx.memo` as the memo of the scope.\n */\nexport const asyncLocalMemo: MemoScope = Object.freeze({\n run: <T>(memo: Memo, next: () => Promise<T>) => storage.run(memo, next),\n})\n"],"mappings":";;;;;AAGA,SAAS,yBAAyB;AAIlC,IAAM,UAAU,IAAI,kBAAwB;AAWrC,IAAM,cAAc,CAAI,IAAa,OAAa,WAAW,MAAS;AAC3E,MAAI,OAAO,OAAO,YAAY;AAC5B,UAAM,IAAI,UAAU,qCAAqC;AAAA,EAC3D;AACA,SAAO,QAAQ,IAAI,MAAM,EAAE;AAC7B;AAGO,IAAM,cAAc,MAAY;AACrC,QAAM,OAAO,QAAQ,SAAS;AAC9B,MAAI,CAAC,MAAM;AACT,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAYO,IAAM,WAAW,CACtB,eACiC;AACjC,SAAO,IAAI,SAAY,YAAY,EAAE,YAAY,GAAG,IAAI;AAC1D;AAMO,IAAM,iBAA4B,OAAO,OAAO;AAAA,EACrD,KAAK,CAAI,MAAY,SAA2B,QAAQ,IAAI,MAAM,IAAI;AACxE,CAAC;","names":[]}
1
+ {"version":3,"sources":["../src/als.ts"],"sourcesContent":["// AsyncLocalStorage: Node.js, Bun, Deno, and Cloudflare Workers with the\n// nodejs_compat (or nodejs_als) compatibility flag.\n/// <reference types=\"node\" />\nimport { AsyncLocalStorage } from 'node:async_hooks'\nimport {\n createMemo,\n type Memo,\n type MemoDefinition,\n type MemoScope,\n} from './memo.js'\n\nconst storage = new AsyncLocalStorage<Memo>()\n\n/**\n * Runs a function inside the scope of a memo (a new one by default): the\n * memoized loaders called during its run, awaits included, share it.\n *\n * ```ts\n * // A listener, a job, a script\n * await runWithMemo(() => sendInvoices(customerId))\n * ```\n */\nexport const runWithMemo = <T>(fn: () => T, memo: Memo = createMemo()): T => {\n if (typeof fn !== 'function') {\n throw new TypeError('runWithMemo(): expected a function.')\n }\n return storage.run(memo, fn)\n}\n\n/** The memo of the current scope; throws outside a scope. */\nexport const currentMemo = (): Memo => {\n const memo = storage.getStore()\n if (!memo) {\n throw new Error(\n 'No memo scope: run the code with runWithMemo(), or install cachePlugin({ memoScope: asyncLocalMemo }).'\n )\n }\n return memo\n}\n\n/**\n * A function that loads through the memo of the current scope, without\n * passing a context around.\n *\n * ```ts\n * export const getUser = memoized(loadUser)\n * // anywhere during a request or a runWithMemo() call\n * const user = await getUser(id)\n * ```\n */\nexport const memoized = <A extends readonly unknown[], T>(\n definition: MemoDefinition<A, T>\n): ((...args: A) => Promise<T>) => {\n return (...args: A) => currentMemo()(definition, ...args)\n}\n\n/**\n * Scope of `cachePlugin({ memoScope: asyncLocalMemo })`: each request runs\n * with `ctx.memo` as the memo of the scope.\n */\nexport const asyncLocalMemo: MemoScope = Object.freeze({\n run: <T>(memo: Memo, next: () => Promise<T>) => storage.run(memo, next),\n})\n"],"mappings":";;;;;AAGA,SAAS,yBAAyB;AAQlC,IAAM,UAAU,IAAI,kBAAwB;AAWrC,IAAM,cAAc,CAAI,IAAa,OAAa,WAAW,MAAS;AAC3E,MAAI,OAAO,OAAO,YAAY;AAC5B,UAAM,IAAI,UAAU,qCAAqC;AAAA,EAC3D;AACA,SAAO,QAAQ,IAAI,MAAM,EAAE;AAC7B;AAGO,IAAM,cAAc,MAAY;AACrC,QAAM,OAAO,QAAQ,SAAS;AAC9B,MAAI,CAAC,MAAM;AACT,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAYO,IAAM,WAAW,CACtB,eACiC;AACjC,SAAO,IAAI,SAAY,YAAY,EAAE,YAAY,GAAG,IAAI;AAC1D;AAMO,IAAM,iBAA4B,OAAO,OAAO;AAAA,EACrD,KAAK,CAAI,MAAY,SAA2B,QAAQ,IAAI,MAAM,IAAI;AACxE,CAAC;","names":[]}
@@ -278,4 +278,4 @@ export {
278
278
  defineMemo,
279
279
  createMemo
280
280
  };
281
- //# sourceMappingURL=chunk-6ZNJJARH.js.map
281
+ //# sourceMappingURL=chunk-NAI2ODC5.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/keys.ts","../src/memo.ts"],"sourcesContent":["/** Base class of the errors thrown by `@dotpress/cache`. */\nexport class CacheError extends Error {\n override name = 'CacheError'\n}\n\n/**\n * The `batch` function of a memo returned a result that does not match\n * the keys: every call of the batch fails with this error.\n */\nexport class MemoBatchError extends CacheError {\n override name = 'MemoBatchError'\n\n constructor(\n /** Name of the memo. */\n readonly memo: string,\n message: string\n ) {\n super(`Memo \"${memo}\": ${message}`)\n }\n}\n\n/**\n * Operation of a cache store: `invalidate` writes the tokens of tags,\n * `lock` and `unlock` hold the distributed lock of a load.\n */\nexport type StoreOperation =\n 'get' | 'set' | 'delete' | 'invalidate' | 'lock' | 'unlock'\n\n/** A cache store failed or did not answer in time (`failOpen: false`). */\nexport class CacheStoreError extends CacheError {\n override name = 'CacheStoreError'\n\n constructor(\n /** Name of the store. */\n readonly store: string,\n readonly operation: StoreOperation,\n /** Key of the store. */\n readonly key: string,\n options: { cause: unknown }\n ) {\n super(\n `Cache store \"${store}\" failed to ${operation} \"${key}\": ${options.cause instanceof Error ? options.cause.message : String(options.cause)}`,\n options\n )\n }\n}\n\n/**\n * The loader of `getOrSet()` did not finish within `hardTimeout`, and no\n * stale value could be served. The load goes on, and caches its value.\n */\nexport class CacheTimeoutError extends CacheError {\n override name = 'CacheTimeoutError'\n\n constructor(\n /** Name of the entry. */\n readonly entry: string,\n /** Key of the store. */\n readonly key: string,\n /** Timeout, in milliseconds. */\n readonly timeout: number\n ) {\n super(\n `Cache entry \"${entry}\": the loader of \"${key}\" did not finish within ${timeout} milliseconds.`\n )\n }\n}\n","/**\n * Serializes a value to a key: the same for equal values, whatever the\n * order of the properties of objects.\n */\nexport const keyOf = (name: string, value: unknown): string => {\n const seen = new Set<object>()\n const serialize = (item: unknown): string => {\n switch (typeof item) {\n case 'string':\n return JSON.stringify(item)\n case 'number':\n case 'boolean':\n return String(item)\n case 'bigint':\n return `${item}n`\n case 'undefined':\n return 'undefined'\n }\n if (item === null) return 'null'\n if (item instanceof Date) return `Date(${item.getTime()})`\n if (typeof item === 'object') {\n const prototype = Object.getPrototypeOf(item) as unknown\n const plain = prototype === Object.prototype || prototype === null\n if (Array.isArray(item) || plain) {\n if (seen.has(item)) {\n throw new TypeError(\n `${name}: cannot build a key from a circular value.`\n )\n }\n seen.add(item)\n const serialized = Array.isArray(item)\n ? `[${item.map(serialize).join(',')}]`\n : `{${Object.keys(item)\n .filter(\n (key) => (item as Record<string, unknown>)[key] !== undefined\n )\n .sort()\n .map(\n (key) =>\n `${JSON.stringify(key)}:${serialize((item as Record<string, unknown>)[key])}`\n )\n .join(',')}}`\n seen.delete(item)\n return serialized\n }\n }\n throw new TypeError(\n `${name}: cannot build a key from ${describe(item)}; add a key option that returns a string.`\n )\n }\n return serialize(value)\n}\n\nconst describe = (value: unknown) =>\n typeof value === 'function'\n ? 'a function'\n : typeof value === 'symbol'\n ? 'a symbol'\n : `an instance of ${(value as object).constructor?.name ?? 'an unknown class'}`\n","import { MemoBatchError } from './errors.js'\nimport { keyOf } from './keys.js'\n\n/** Options shared by every memo. */\ninterface BaseMemoOptions {\n /**\n * Name of the memo, used in errors: letters, digits, `.`, `_`, `:` and\n * `-`.\n */\n name: string\n}\n\n/** Options of a memo that loads one value at a time. */\nexport interface LoadMemoOptions<\n A extends readonly unknown[],\n T,\n> extends BaseMemoOptions {\n /** Loads the value of the arguments. */\n load: (...args: A) => T | Promise<T>\n /**\n * Key of the arguments. Defaults to the arguments themselves: strings,\n * numbers, booleans, `null`, `undefined`, bigints, dates, arrays and\n * plain objects.\n */\n key?: ((...args: A) => unknown) | undefined\n batch?: never\n}\n\n/** Options of a memo that loads the values of several keys at once. */\nexport interface BatchMemoOptions<K, R> extends BaseMemoOptions {\n /**\n * Loads the values of the keys requested in the same tick, without\n * duplicates. Returns their values in the order of the keys (an `Error`\n * fails its key only), or a `Map` by key (a missing key gives\n * `undefined`).\n */\n batch: (keys: readonly K[]) => R | Promise<R>\n /** Key of a requested key, for keys that are objects. */\n key?: ((key: K) => unknown) | undefined\n /** Keys per call of `batch`. Defaults to no limit. */\n maxBatchSize?: number | undefined\n /**\n * Schedules a batch. Defaults to a timer (`setTimeout(run, 0)`): the\n * batch receives every key requested before it.\n */\n schedule?: ((run: () => void) => void) | undefined\n load?: never\n}\n\ndeclare const memoTypes: unique symbol\n\n/** A memoized loader, created by `defineMemo()`. */\nexport interface MemoDefinition<A extends readonly unknown[], T> {\n readonly name: string\n /** Types only. */\n readonly [memoTypes]?: { args: A; value: T }\n}\n\n/** Memoized values of a unit of work: a request, a message, a job. */\nexport interface Memo {\n /**\n * Loads the value of the arguments once per memo: later calls, and\n * calls made while it loads, share the result. Failures are shared by\n * the calls in progress, not kept.\n */\n <A extends readonly unknown[], T>(\n memo: MemoDefinition<A, T>,\n ...args: A\n ): Promise<T>\n /** Sets the value of the arguments, after an update for instance. */\n set<A extends readonly unknown[], T>(\n memo: MemoDefinition<A, T>,\n ...argsAndValue: [...A, T]\n ): void\n /** Forgets the value of the arguments: the next call loads it again. */\n delete<A extends readonly unknown[], T>(\n memo: MemoDefinition<A, T>,\n ...args: A\n ): void\n /** Forgets the values of a memo, or of every memo. */\n clear<A extends readonly unknown[], T>(memo?: MemoDefinition<A, T>): void\n}\n\ntype Config =\n | {\n kind: 'load'\n name: string\n load: (...args: readonly unknown[]) => unknown\n key: ((...args: readonly unknown[]) => unknown) | undefined\n }\n | {\n kind: 'batch'\n name: string\n batch: (keys: readonly unknown[]) => unknown\n key: ((key: unknown) => unknown) | undefined\n maxBatchSize: number\n schedule: (run: () => void) => void\n }\n\nconst configs = new WeakMap<object, Config>()\n\nconst NAME = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/\nconst OPTIONS = {\n load: new Set(['name', 'load', 'key']),\n batch: new Set(['name', 'batch', 'key', 'maxBatchSize', 'schedule']),\n}\n\n/**\n * Defines a memoized loader: `memo(loadUser, id)` loads a user once per\n * memo (request, message, job), and calls made while it loads share the\n * same load (single flight). With `batch`, the keys requested in the same\n * tick are loaded in one call.\n *\n * ```ts\n * const loadUser = defineMemo({\n * name: 'user',\n * load: (id: string) => db.users.findById(id),\n * })\n *\n * const loadOrder = defineMemo({\n * name: 'order',\n * batch: (ids: readonly string[]) => db.orders.findManyById(ids), // a Map by id\n * })\n * ```\n */\nexport function defineMemo<K, T>(\n options: BatchMemoOptions<K, ReadonlyMap<K, T>>\n): MemoDefinition<[key: K], T | undefined>\nexport function defineMemo<K, T>(\n options: BatchMemoOptions<K, readonly (T | Error)[]>\n): MemoDefinition<[key: K], T>\nexport function defineMemo<A extends readonly unknown[], T>(\n options: LoadMemoOptions<A, T>\n): MemoDefinition<A, T>\nexport function defineMemo(options: unknown): { readonly name: string } {\n const label = 'defineMemo()'\n if (typeof options !== 'object' || options === null) {\n throw new TypeError(`${label}: expected an object of options.`)\n }\n const values = options as Record<string, unknown>\n const { name } = values\n if (typeof name !== 'string' || !NAME.test(name)) {\n throw new TypeError(\n `${label}: name must have 1 to 128 letters, digits, \".\", \"_\", \":\" or \"-\", starting with a letter or a digit.`\n )\n }\n const named = `defineMemo(\"${name}\")`\n const kind = values.batch === undefined ? 'load' : 'batch'\n if (kind === 'batch' && values.load !== undefined) {\n throw new TypeError(`${named}: set load or batch, not both.`)\n }\n for (const key of Object.keys(values)) {\n if (!OPTIONS[kind].has(key)) {\n throw new TypeError(`${named}: unknown option \"${key}\".`)\n }\n }\n const { key } = values\n if (key !== undefined && typeof key !== 'function') {\n throw new TypeError(`${named}: key must be a function.`)\n }\n\n let config: Config\n if (kind === 'load') {\n if (typeof values.load !== 'function') {\n throw new TypeError(`${named}: load or batch must be a function.`)\n }\n config = {\n kind,\n name,\n load: values.load as (...args: readonly unknown[]) => unknown,\n key: key as ((...args: readonly unknown[]) => unknown) | undefined,\n }\n } else {\n const {\n batch,\n maxBatchSize = Infinity,\n schedule = defaultSchedule,\n } = values\n if (typeof batch !== 'function') {\n throw new TypeError(`${named}: batch must be a function.`)\n }\n if (\n maxBatchSize !== Infinity &&\n (!Number.isInteger(maxBatchSize) || (maxBatchSize as number) < 1)\n ) {\n throw new TypeError(`${named}: maxBatchSize must be a positive integer.`)\n }\n if (typeof schedule !== 'function') {\n throw new TypeError(`${named}: schedule must be a function.`)\n }\n config = {\n kind,\n name,\n batch: batch as (keys: readonly unknown[]) => unknown,\n key: key as ((key: unknown) => unknown) | undefined,\n maxBatchSize: maxBatchSize as number,\n schedule: schedule as (run: () => void) => void,\n }\n }\n\n const memo = Object.freeze({ name })\n configs.set(memo, config)\n return memo\n}\n\nconst defaultSchedule = (run: () => void) => {\n setTimeout(run, 0)\n}\n\ninterface Deferred {\n key: unknown\n resolve: (value: unknown) => void\n reject: (error: unknown) => void\n}\n\n/**\n * Creates a memo: one per request, message or job. Plugins provide\n * `ctx.memo`; create one yourself elsewhere.\n *\n * ```ts\n * const memo = createMemo()\n * const [ada, grace] = await Promise.all([memo(loadUser, '1'), memo(loadUser, '2')])\n * ```\n */\nexport const createMemo = (): Memo => {\n const values = new Map<Config, Map<string, Promise<unknown>>>()\n const queues = new Map<Config, Deferred[]>()\n\n const configOf = (label: string, memo: unknown) => {\n const config =\n typeof memo === 'object' && memo !== null ? configs.get(memo) : undefined\n if (!config) {\n throw new TypeError(`${label}: expected a memo created by defineMemo().`)\n }\n return config\n }\n\n const keyFor = (config: Config, args: readonly unknown[]) =>\n config.kind === 'load'\n ? keyOf(`Memo \"${config.name}\"`, config.key ? config.key(...args) : args)\n : keyOf(\n `Memo \"${config.name}\"`,\n config.key ? config.key(args[0]) : args[0]\n )\n\n const valuesOf = (config: Config) => {\n let map = values.get(config)\n if (!map) {\n map = new Map()\n values.set(config, map)\n }\n return map\n }\n\n /** Runs the queued keys of a batch memo, in calls of maxBatchSize. */\n const runBatch = (config: Extract<Config, { kind: 'batch' }>) => {\n const queue = queues.get(config) ?? []\n queues.delete(config)\n for (let start = 0; start < queue.length; start += config.maxBatchSize) {\n const chunk = queue.slice(start, start + config.maxBatchSize)\n void (async () => {\n let result: unknown\n try {\n result = await config.batch(chunk.map(({ key }) => key))\n } catch (error) {\n for (const { reject } of chunk) reject(error)\n return\n }\n if (Array.isArray(result)) {\n if (result.length !== chunk.length) {\n const error = new MemoBatchError(\n config.name,\n `batch() returned ${result.length} values for ${chunk.length} keys: return one value per key, in the order of the keys, or a Map.`\n )\n for (const { reject } of chunk) reject(error)\n return\n }\n chunk.forEach(({ resolve, reject }, index) => {\n const value: unknown = result[index]\n if (value instanceof Error) reject(value)\n else resolve(value)\n })\n } else if (result instanceof Map) {\n for (const { key, resolve } of chunk) resolve(result.get(key))\n } else {\n const error = new MemoBatchError(\n config.name,\n 'batch() must return an array of values, in the order of the keys, or a Map.'\n )\n for (const { reject } of chunk) reject(error)\n }\n })()\n }\n }\n\n const loadOf = (config: Config, args: readonly unknown[]) => {\n if (config.kind === 'load') {\n return (async () => config.load(...args))()\n }\n return new Promise<unknown>((resolve, reject) => {\n let queue = queues.get(config)\n if (!queue) {\n queue = []\n queues.set(config, queue)\n config.schedule(() => runBatch(config))\n }\n queue.push({ key: args[0], resolve, reject })\n })\n }\n\n const memo = (<A extends readonly unknown[], T>(\n definition: MemoDefinition<A, T>,\n ...args: A\n ): Promise<T> => {\n const config = configOf('memo()', definition)\n const map = valuesOf(config)\n const key = keyFor(config, args)\n const known = map.get(key)\n if (known) return known as Promise<T>\n\n const loading = loadOf(config, args)\n map.set(key, loading)\n // Failures are not kept: the next call loads again.\n loading.catch(() => {\n if (map.get(key) === loading) map.delete(key)\n })\n return loading as Promise<T>\n }) as Memo\n\n memo.set = (definition, ...argsAndValue) => {\n const config = configOf('memo.set()', definition)\n const args = argsAndValue.slice(0, -1)\n valuesOf(config).set(\n keyFor(config, args),\n Promise.resolve(argsAndValue.at(-1))\n )\n }\n memo.delete = (definition, ...args) => {\n const config = configOf('memo.delete()', definition)\n valuesOf(config).delete(keyFor(config, args))\n }\n memo.clear = (definition) => {\n if (definition === undefined) {\n values.clear()\n return\n }\n values.delete(configOf('memo.clear()', definition))\n }\n return Object.freeze(memo)\n}\n"],"mappings":"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","names":["key","memo"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/keys.ts","../src/memo.ts"],"sourcesContent":["/** Base class of the errors thrown by `@dotpress/cache`. */\nexport class CacheError extends Error {\n override name = 'CacheError'\n}\n\n/**\n * The `batch` function of a memo returned a result that does not match\n * the keys: every call of the batch fails with this error.\n */\nexport class MemoBatchError extends CacheError {\n override name = 'MemoBatchError'\n\n constructor(\n /** Name of the memo. */\n readonly memo: string,\n message: string\n ) {\n super(`Memo \"${memo}\": ${message}`)\n }\n}\n\n/**\n * Operation of a cache store: `invalidate` writes the tokens of tags,\n * `lock` and `unlock` hold the distributed lock of a load.\n */\nexport type StoreOperation =\n 'get' | 'set' | 'delete' | 'invalidate' | 'lock' | 'unlock'\n\n/** A cache store failed or did not answer in time (`failOpen: false`). */\nexport class CacheStoreError extends CacheError {\n override name = 'CacheStoreError'\n\n constructor(\n /** Name of the store. */\n readonly store: string,\n readonly operation: StoreOperation,\n /** Key of the store. */\n readonly key: string,\n options: { cause: unknown }\n ) {\n super(\n `Cache store \"${store}\" failed to ${operation} \"${key}\": ${options.cause instanceof Error ? options.cause.message : String(options.cause)}`,\n options\n )\n }\n}\n\n/**\n * The loader of `getOrSet()` did not finish within `hardTimeout`, and no\n * stale value could be served. The load goes on, and caches its value.\n */\nexport class CacheTimeoutError extends CacheError {\n override name = 'CacheTimeoutError'\n\n constructor(\n /** Name of the entry. */\n readonly entry: string,\n /** Key of the store. */\n readonly key: string,\n /** Timeout, in milliseconds. */\n readonly timeout: number\n ) {\n super(\n `Cache entry \"${entry}\": the loader of \"${key}\" did not finish within ${timeout} milliseconds.`\n )\n }\n}\n","/**\n * Serializes a value to a key: the same for equal values, whatever the\n * order of the properties of objects.\n */\nexport const keyOf = (name: string, value: unknown): string => {\n const seen = new Set<object>()\n const serialize = (item: unknown): string => {\n switch (typeof item) {\n case 'string':\n return JSON.stringify(item)\n case 'number':\n case 'boolean':\n return String(item)\n case 'bigint':\n return `${item}n`\n case 'undefined':\n return 'undefined'\n }\n if (item === null) return 'null'\n if (item instanceof Date) return `Date(${item.getTime()})`\n if (typeof item === 'object') {\n const prototype = Object.getPrototypeOf(item) as unknown\n const plain = prototype === Object.prototype || prototype === null\n if (Array.isArray(item) || plain) {\n if (seen.has(item)) {\n throw new TypeError(\n `${name}: cannot build a key from a circular value.`\n )\n }\n seen.add(item)\n const serialized = Array.isArray(item)\n ? `[${item.map(serialize).join(',')}]`\n : `{${Object.keys(item)\n .filter(\n (key) => (item as Record<string, unknown>)[key] !== undefined\n )\n .sort()\n .map(\n (key) =>\n `${JSON.stringify(key)}:${serialize((item as Record<string, unknown>)[key])}`\n )\n .join(',')}}`\n seen.delete(item)\n return serialized\n }\n }\n throw new TypeError(\n `${name}: cannot build a key from ${describe(item)}; add a key option that returns a string.`\n )\n }\n return serialize(value)\n}\n\nconst describe = (value: unknown) =>\n typeof value === 'function'\n ? 'a function'\n : typeof value === 'symbol'\n ? 'a symbol'\n : `an instance of ${(value as object).constructor?.name ?? 'an unknown class'}`\n","import { MemoBatchError } from './errors.js'\nimport { keyOf } from './keys.js'\n\n/** Options shared by every memo. */\ninterface BaseMemoOptions {\n /**\n * Name of the memo, used in errors: letters, digits, `.`, `_`, `:` and\n * `-`.\n */\n name: string\n}\n\n/** Options of a memo that loads one value at a time. */\nexport interface LoadMemoOptions<\n A extends readonly unknown[],\n T,\n> extends BaseMemoOptions {\n /** Loads the value of the arguments. */\n load: (...args: A) => T | Promise<T>\n /**\n * Key of the arguments. Defaults to the arguments themselves: strings,\n * numbers, booleans, `null`, `undefined`, bigints, dates, arrays and\n * plain objects.\n */\n key?: ((...args: A) => unknown) | undefined\n batch?: never\n}\n\n/** Options of a memo that loads the values of several keys at once. */\nexport interface BatchMemoOptions<K, R> extends BaseMemoOptions {\n /**\n * Loads the values of the keys requested in the same tick, without\n * duplicates. Returns their values in the order of the keys (an `Error`\n * fails its key only), or a `Map` by key (a missing key gives\n * `undefined`).\n */\n batch: (keys: readonly K[]) => R | Promise<R>\n /** Key of a requested key, for keys that are objects. */\n key?: ((key: K) => unknown) | undefined\n /** Keys per call of `batch`. Defaults to no limit. */\n maxBatchSize?: number | undefined\n /**\n * Schedules a batch. Defaults to a timer (`setTimeout(run, 0)`): the\n * batch receives every key requested before it.\n */\n schedule?: ((run: () => void) => void) | undefined\n load?: never\n}\n\ndeclare const memoTypes: unique symbol\n\n/** A memoized loader, created by `defineMemo()`. */\nexport interface MemoDefinition<A extends readonly unknown[], T> {\n readonly name: string\n /** Types only. */\n readonly [memoTypes]?: { args: A; value: T }\n}\n\n/** Memoized values of a unit of work: a request, a message, a job. */\nexport interface Memo {\n /**\n * Loads the value of the arguments once per memo: later calls, and\n * calls made while it loads, share the result. Failures are shared by\n * the calls in progress, not kept.\n */\n <A extends readonly unknown[], T>(\n memo: MemoDefinition<A, T>,\n ...args: A\n ): Promise<T>\n /** Sets the value of the arguments, after an update for instance. */\n set<A extends readonly unknown[], T>(\n memo: MemoDefinition<A, T>,\n ...argsAndValue: [...A, T]\n ): void\n /** Forgets the value of the arguments: the next call loads it again. */\n delete<A extends readonly unknown[], T>(\n memo: MemoDefinition<A, T>,\n ...args: A\n ): void\n /** Forgets the values of a memo, or of every memo. */\n clear<A extends readonly unknown[], T>(memo?: MemoDefinition<A, T>): void\n}\n\ntype Config =\n | {\n kind: 'load'\n name: string\n load: (...args: readonly unknown[]) => unknown\n key: ((...args: readonly unknown[]) => unknown) | undefined\n }\n | {\n kind: 'batch'\n name: string\n batch: (keys: readonly unknown[]) => unknown\n key: ((key: unknown) => unknown) | undefined\n maxBatchSize: number\n schedule: (run: () => void) => void\n }\n\nconst configs = new WeakMap<object, Config>()\n\nconst NAME = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/\nconst OPTIONS = {\n load: new Set(['name', 'load', 'key']),\n batch: new Set(['name', 'batch', 'key', 'maxBatchSize', 'schedule']),\n}\n\n/**\n * Defines a memoized loader: `memo(loadUser, id)` loads a user once per\n * memo (request, message, job), and calls made while it loads share the\n * same load (single flight). With `batch`, the keys requested in the same\n * tick are loaded in one call.\n *\n * ```ts\n * const loadUser = defineMemo({\n * name: 'user',\n * load: (id: string) => db.users.findById(id),\n * })\n *\n * const loadOrder = defineMemo({\n * name: 'order',\n * batch: (ids: readonly string[]) => db.orders.findManyById(ids), // a Map by id\n * })\n * ```\n */\nexport function defineMemo<K, T>(\n options: BatchMemoOptions<K, ReadonlyMap<K, T>>\n): MemoDefinition<[key: K], T | undefined>\nexport function defineMemo<K, T>(\n options: BatchMemoOptions<K, readonly (T | Error)[]>\n): MemoDefinition<[key: K], T>\nexport function defineMemo<A extends readonly unknown[], T>(\n options: LoadMemoOptions<A, T>\n): MemoDefinition<A, T>\nexport function defineMemo(options: unknown): { readonly name: string } {\n const label = 'defineMemo()'\n if (typeof options !== 'object' || options === null) {\n throw new TypeError(`${label}: expected an object of options.`)\n }\n const values = options as Record<string, unknown>\n const { name } = values\n if (typeof name !== 'string' || !NAME.test(name)) {\n throw new TypeError(\n `${label}: name must have 1 to 128 letters, digits, \".\", \"_\", \":\" or \"-\", starting with a letter or a digit.`\n )\n }\n const named = `defineMemo(\"${name}\")`\n const kind = values.batch === undefined ? 'load' : 'batch'\n if (kind === 'batch' && values.load !== undefined) {\n throw new TypeError(`${named}: set load or batch, not both.`)\n }\n for (const key of Object.keys(values)) {\n if (!OPTIONS[kind].has(key)) {\n throw new TypeError(`${named}: unknown option \"${key}\".`)\n }\n }\n const { key } = values\n if (key !== undefined && typeof key !== 'function') {\n throw new TypeError(`${named}: key must be a function.`)\n }\n\n let config: Config\n if (kind === 'load') {\n if (typeof values.load !== 'function') {\n throw new TypeError(`${named}: load or batch must be a function.`)\n }\n config = {\n kind,\n name,\n load: values.load as (...args: readonly unknown[]) => unknown,\n key: key as ((...args: readonly unknown[]) => unknown) | undefined,\n }\n } else {\n const {\n batch,\n maxBatchSize = Infinity,\n schedule = defaultSchedule,\n } = values\n if (typeof batch !== 'function') {\n throw new TypeError(`${named}: batch must be a function.`)\n }\n if (\n maxBatchSize !== Infinity &&\n (!Number.isInteger(maxBatchSize) || (maxBatchSize as number) < 1)\n ) {\n throw new TypeError(`${named}: maxBatchSize must be a positive integer.`)\n }\n if (typeof schedule !== 'function') {\n throw new TypeError(`${named}: schedule must be a function.`)\n }\n config = {\n kind,\n name,\n batch: batch as (keys: readonly unknown[]) => unknown,\n key: key as ((key: unknown) => unknown) | undefined,\n maxBatchSize: maxBatchSize as number,\n schedule: schedule as (run: () => void) => void,\n }\n }\n\n const memo = Object.freeze({ name })\n configs.set(memo, config)\n return memo\n}\n\nconst defaultSchedule = (run: () => void) => {\n setTimeout(run, 0)\n}\n\ninterface Deferred {\n key: unknown\n resolve: (value: unknown) => void\n reject: (error: unknown) => void\n}\n\n/**\n * Creates a memo: one per request, message or job. Plugins provide\n * `ctx.memo`; create one yourself elsewhere.\n *\n * ```ts\n * const memo = createMemo()\n * const [ada, grace] = await Promise.all([memo(loadUser, '1'), memo(loadUser, '2')])\n * ```\n */\nexport const createMemo = (): Memo => {\n const values = new Map<Config, Map<string, Promise<unknown>>>()\n const queues = new Map<Config, Deferred[]>()\n\n const configOf = (label: string, memo: unknown) => {\n const config =\n typeof memo === 'object' && memo !== null ? configs.get(memo) : undefined\n if (!config) {\n throw new TypeError(`${label}: expected a memo created by defineMemo().`)\n }\n return config\n }\n\n const keyFor = (config: Config, args: readonly unknown[]) =>\n config.kind === 'load'\n ? keyOf(`Memo \"${config.name}\"`, config.key ? config.key(...args) : args)\n : keyOf(\n `Memo \"${config.name}\"`,\n config.key ? config.key(args[0]) : args[0]\n )\n\n const valuesOf = (config: Config) => {\n let map = values.get(config)\n if (!map) {\n map = new Map()\n values.set(config, map)\n }\n return map\n }\n\n /** Runs the queued keys of a batch memo, in calls of maxBatchSize. */\n const runBatch = (config: Extract<Config, { kind: 'batch' }>) => {\n const queue = queues.get(config) ?? []\n queues.delete(config)\n for (let start = 0; start < queue.length; start += config.maxBatchSize) {\n const chunk = queue.slice(start, start + config.maxBatchSize)\n void (async () => {\n let result: unknown\n try {\n result = await config.batch(chunk.map(({ key }) => key))\n } catch (error) {\n for (const { reject } of chunk) reject(error)\n return\n }\n if (Array.isArray(result)) {\n if (result.length !== chunk.length) {\n const error = new MemoBatchError(\n config.name,\n `batch() returned ${result.length} values for ${chunk.length} keys: return one value per key, in the order of the keys, or a Map.`\n )\n for (const { reject } of chunk) reject(error)\n return\n }\n chunk.forEach(({ resolve, reject }, index) => {\n const value: unknown = result[index]\n if (value instanceof Error) reject(value)\n else resolve(value)\n })\n } else if (result instanceof Map) {\n for (const { key, resolve } of chunk) resolve(result.get(key))\n } else {\n const error = new MemoBatchError(\n config.name,\n 'batch() must return an array of values, in the order of the keys, or a Map.'\n )\n for (const { reject } of chunk) reject(error)\n }\n })()\n }\n }\n\n const loadOf = (config: Config, args: readonly unknown[]) => {\n if (config.kind === 'load') {\n return (async () => config.load(...args))()\n }\n return new Promise<unknown>((resolve, reject) => {\n let queue = queues.get(config)\n if (!queue) {\n queue = []\n queues.set(config, queue)\n config.schedule(() => runBatch(config))\n }\n queue.push({ key: args[0], resolve, reject })\n })\n }\n\n const memo = (<A extends readonly unknown[], T>(\n definition: MemoDefinition<A, T>,\n ...args: A\n ): Promise<T> => {\n const config = configOf('memo()', definition)\n const map = valuesOf(config)\n const key = keyFor(config, args)\n const known = map.get(key)\n if (known) return known as Promise<T>\n\n const loading = loadOf(config, args)\n map.set(key, loading)\n // Failures are not kept: the next call loads again.\n loading.catch(() => {\n if (map.get(key) === loading) map.delete(key)\n })\n return loading as Promise<T>\n }) as Memo\n\n memo.set = (definition, ...argsAndValue) => {\n const config = configOf('memo.set()', definition)\n const args = argsAndValue.slice(0, -1)\n valuesOf(config).set(\n keyFor(config, args),\n Promise.resolve(argsAndValue.at(-1))\n )\n }\n memo.delete = (definition, ...args) => {\n const config = configOf('memo.delete()', definition)\n valuesOf(config).delete(keyFor(config, args))\n }\n memo.clear = (definition) => {\n if (definition === undefined) {\n values.clear()\n return\n }\n values.delete(configOf('memo.clear()', definition))\n }\n return Object.freeze(memo)\n}\n\n/**\n * Runs each request inside a scope for its memo, like `asyncLocalMemo` of\n * `@dotpress/cache/als`.\n */\nexport interface MemoScope {\n run<T>(memo: Memo, next: () => Promise<T>): Promise<T>\n}\n"],"mappings":"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","names":["key","memo"]}
@@ -1,4 +1,4 @@
1
- import { Duration } from './index.js';
1
+ import { Duration } from '@dotpress/core';
2
2
  import { CacheStore } from './index.js';
3
3
 
4
4
  /** A Workers KV binding (the fields used here). */
@@ -1,8 +1,5 @@
1
- import {
2
- toMilliseconds
3
- } from "./chunk-2IEBMYSF.js";
4
-
5
1
  // src/stores/cloudflare-kv.ts
2
+ import { parseDuration } from "@dotpress/core";
6
3
  var MAX_KEY_BYTES = 512;
7
4
  var MAX_BULK = 100;
8
5
  var MIN_EXPIRATION_TTL = 60;
@@ -25,7 +22,7 @@ var createCloudflareKvStore = (options) => {
25
22
  }
26
23
  let cacheTtl;
27
24
  if (options.cacheTtl !== void 0) {
28
- const ms = toMilliseconds(`${label}: cacheTtl`, options.cacheTtl);
25
+ const ms = parseDuration(options.cacheTtl, `${label}: cacheTtl`);
29
26
  if (ms < 3e4) {
30
27
  throw new TypeError(`${label}: cacheTtl must be at least 30 seconds.`);
31
28
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/stores/cloudflare-kv.ts"],"sourcesContent":["import { toMilliseconds, type Duration } from '../duration.js'\nimport type { CacheStore } from '../store.js'\n\n/** A Workers KV binding (the fields used here). */\nexport interface KvNamespace {\n get(\n key: string,\n options?: { type?: 'text'; cacheTtl?: number }\n ): Promise<string | null>\n get(\n keys: string[],\n options?: { type?: 'text'; cacheTtl?: number }\n ): Promise<Map<string, string | null>>\n put(\n key: string,\n value: string,\n options?: { expirationTtl?: number }\n ): Promise<void>\n delete(key: string): Promise<void>\n}\n\n/** Options of `createCloudflareKvStore()`. */\nexport interface CloudflareKvStoreOptions {\n /**\n * The KV binding, or a function that returns it (with `env` imported\n * from `cloudflare:workers`, resolved on use).\n */\n kv: KvNamespace | (() => KvNamespace)\n /**\n * How long Cloudflare keeps read values in the cache of the location\n * (`cacheTtl` of `get`, at least 30 seconds): faster reads, but writes\n * and invalidations from other locations are seen later. Defaults to\n * the 60 seconds of KV.\n */\n cacheTtl?: Duration | undefined\n /** Name of the store, in logs and errors. Defaults to `cloudflare-kv`. */\n name?: string | undefined\n}\n\n// Limits of Workers KV: keys of 512 bytes, 100 keys per bulk read, and an\n// expiration of 60 seconds at least.\nconst MAX_KEY_BYTES = 512\nconst MAX_BULK = 100\nconst MIN_EXPIRATION_TTL = 60\n\n/**\n * Creates a store on [Workers KV](https://developers.cloudflare.com/kv),\n * through the binding of a Worker: no dependency. KV is eventually\n * consistent (a write can take 60 seconds or more to reach other\n * locations) and has no locks: a fit for values read often and changed\n * rarely.\n *\n * ```ts\n * import { env } from 'cloudflare:workers'\n *\n * const store = createCloudflareKvStore({ kv: () => env.CACHE })\n * ```\n */\nexport const createCloudflareKvStore = (\n options: CloudflareKvStoreOptions\n): CacheStore => {\n const label = 'createCloudflareKvStore()'\n if (typeof options !== 'object' || options === null) {\n throw new TypeError(`${label}: expected an object of options.`)\n }\n for (const key of Object.keys(options)) {\n if (!['kv', 'cacheTtl', 'name'].includes(key)) {\n throw new TypeError(`${label}: unknown option \"${key}\".`)\n }\n }\n const { kv, name = 'cloudflare-kv' } = options\n const isBinding = (value: unknown): value is KvNamespace =>\n typeof (value as KvNamespace | null)?.get === 'function' &&\n typeof (value as KvNamespace).put === 'function' &&\n typeof (value as KvNamespace).delete === 'function'\n if (typeof kv !== 'function' && !isBinding(kv)) {\n throw new TypeError(\n `${label}: kv must be a KV binding, or a function that returns it.`\n )\n }\n let cacheTtl: number | undefined\n if (options.cacheTtl !== undefined) {\n const ms = toMilliseconds(`${label}: cacheTtl`, options.cacheTtl)\n if (ms < 30_000) {\n throw new TypeError(`${label}: cacheTtl must be at least 30 seconds.`)\n }\n cacheTtl = Math.round(ms / 1000)\n }\n if (typeof name !== 'string' || name === '') {\n throw new TypeError(`${label}: name must be a non-empty string.`)\n }\n\n const binding = () => {\n const value = typeof kv === 'function' ? kv() : kv\n if (!isBinding(value)) {\n throw new TypeError(`${label}: kv() must return a KV binding.`)\n }\n return value\n }\n const read = { type: 'text' as const, ...(cacheTtl ? { cacheTtl } : {}) }\n\n return Object.freeze({\n name,\n maxKeyLength: MAX_KEY_BYTES,\n get: async (key: string) => (await binding().get(key, read)) ?? undefined,\n getMany: async (keys: readonly string[]) => {\n const namespace = binding()\n const unique = [...new Set(keys)]\n const values = new Map<string, string | null>()\n for (let start = 0; start < unique.length; start += MAX_BULK) {\n const found = await namespace.get(\n unique.slice(start, start + MAX_BULK),\n read\n )\n for (const [key, value] of found) values.set(key, value)\n }\n return keys.map((key) => values.get(key) ?? undefined)\n },\n set: async (key: string, value: string, ttl: number) => {\n // The cache keeps the real expiry in the value; KV needs 60 s.\n await binding().put(key, value, {\n expirationTtl: Math.max(MIN_EXPIRATION_TTL, Math.ceil(ttl / 1000)),\n })\n },\n delete: async (key: string) => {\n await binding().delete(key)\n },\n })\n}\n"],"mappings":";;;;;AAyCA,IAAM,gBAAgB;AACtB,IAAM,WAAW;AACjB,IAAM,qBAAqB;AAepB,IAAM,0BAA0B,CACrC,YACe;AACf,QAAM,QAAQ;AACd,MAAI,OAAO,YAAY,YAAY,YAAY,MAAM;AACnD,UAAM,IAAI,UAAU,GAAG,KAAK,kCAAkC;AAAA,EAChE;AACA,aAAW,OAAO,OAAO,KAAK,OAAO,GAAG;AACtC,QAAI,CAAC,CAAC,MAAM,YAAY,MAAM,EAAE,SAAS,GAAG,GAAG;AAC7C,YAAM,IAAI,UAAU,GAAG,KAAK,qBAAqB,GAAG,IAAI;AAAA,IAC1D;AAAA,EACF;AACA,QAAM,EAAE,IAAI,OAAO,gBAAgB,IAAI;AACvC,QAAM,YAAY,CAAC,UACjB,OAAQ,OAA8B,QAAQ,cAC9C,OAAQ,MAAsB,QAAQ,cACtC,OAAQ,MAAsB,WAAW;AAC3C,MAAI,OAAO,OAAO,cAAc,CAAC,UAAU,EAAE,GAAG;AAC9C,UAAM,IAAI;AAAA,MACR,GAAG,KAAK;AAAA,IACV;AAAA,EACF;AACA,MAAI;AACJ,MAAI,QAAQ,aAAa,QAAW;AAClC,UAAM,KAAK,eAAe,GAAG,KAAK,cAAc,QAAQ,QAAQ;AAChE,QAAI,KAAK,KAAQ;AACf,YAAM,IAAI,UAAU,GAAG,KAAK,yCAAyC;AAAA,IACvE;AACA,eAAW,KAAK,MAAM,KAAK,GAAI;AAAA,EACjC;AACA,MAAI,OAAO,SAAS,YAAY,SAAS,IAAI;AAC3C,UAAM,IAAI,UAAU,GAAG,KAAK,oCAAoC;AAAA,EAClE;AAEA,QAAM,UAAU,MAAM;AACpB,UAAM,QAAQ,OAAO,OAAO,aAAa,GAAG,IAAI;AAChD,QAAI,CAAC,UAAU,KAAK,GAAG;AACrB,YAAM,IAAI,UAAU,GAAG,KAAK,kCAAkC;AAAA,IAChE;AACA,WAAO;AAAA,EACT;AACA,QAAM,OAAO,EAAE,MAAM,QAAiB,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC,EAAG;AAExE,SAAO,OAAO,OAAO;AAAA,IACnB;AAAA,IACA,cAAc;AAAA,IACd,KAAK,OAAO,QAAiB,MAAM,QAAQ,EAAE,IAAI,KAAK,IAAI,KAAM;AAAA,IAChE,SAAS,OAAO,SAA4B;AAC1C,YAAM,YAAY,QAAQ;AAC1B,YAAM,SAAS,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC;AAChC,YAAM,SAAS,oBAAI,IAA2B;AAC9C,eAAS,QAAQ,GAAG,QAAQ,OAAO,QAAQ,SAAS,UAAU;AAC5D,cAAM,QAAQ,MAAM,UAAU;AAAA,UAC5B,OAAO,MAAM,OAAO,QAAQ,QAAQ;AAAA,UACpC;AAAA,QACF;AACA,mBAAW,CAAC,KAAK,KAAK,KAAK,MAAO,QAAO,IAAI,KAAK,KAAK;AAAA,MACzD;AACA,aAAO,KAAK,IAAI,CAAC,QAAQ,OAAO,IAAI,GAAG,KAAK,MAAS;AAAA,IACvD;AAAA,IACA,KAAK,OAAO,KAAa,OAAe,QAAgB;AAEtD,YAAM,QAAQ,EAAE,IAAI,KAAK,OAAO;AAAA,QAC9B,eAAe,KAAK,IAAI,oBAAoB,KAAK,KAAK,MAAM,GAAI,CAAC;AAAA,MACnE,CAAC;AAAA,IACH;AAAA,IACA,QAAQ,OAAO,QAAgB;AAC7B,YAAM,QAAQ,EAAE,OAAO,GAAG;AAAA,IAC5B;AAAA,EACF,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../src/stores/cloudflare-kv.ts"],"sourcesContent":["import { parseDuration, type Duration } from '@dotpress/core'\nimport type { CacheStore } from '../store.js'\n\n/** A Workers KV binding (the fields used here). */\nexport interface KvNamespace {\n get(\n key: string,\n options?: { type?: 'text'; cacheTtl?: number }\n ): Promise<string | null>\n get(\n keys: string[],\n options?: { type?: 'text'; cacheTtl?: number }\n ): Promise<Map<string, string | null>>\n put(\n key: string,\n value: string,\n options?: { expirationTtl?: number }\n ): Promise<void>\n delete(key: string): Promise<void>\n}\n\n/** Options of `createCloudflareKvStore()`. */\nexport interface CloudflareKvStoreOptions {\n /**\n * The KV binding, or a function that returns it (with `env` imported\n * from `cloudflare:workers`, resolved on use).\n */\n kv: KvNamespace | (() => KvNamespace)\n /**\n * How long Cloudflare keeps read values in the cache of the location\n * (`cacheTtl` of `get`, at least 30 seconds): faster reads, but writes\n * and invalidations from other locations are seen later. Defaults to\n * the 60 seconds of KV.\n */\n cacheTtl?: Duration | undefined\n /** Name of the store, in logs and errors. Defaults to `cloudflare-kv`. */\n name?: string | undefined\n}\n\n// Limits of Workers KV: keys of 512 bytes, 100 keys per bulk read, and an\n// expiration of 60 seconds at least.\nconst MAX_KEY_BYTES = 512\nconst MAX_BULK = 100\nconst MIN_EXPIRATION_TTL = 60\n\n/**\n * Creates a store on [Workers KV](https://developers.cloudflare.com/kv),\n * through the binding of a Worker: no dependency. KV is eventually\n * consistent (a write can take 60 seconds or more to reach other\n * locations) and has no locks: a fit for values read often and changed\n * rarely.\n *\n * ```ts\n * import { env } from 'cloudflare:workers'\n *\n * const store = createCloudflareKvStore({ kv: () => env.CACHE })\n * ```\n */\nexport const createCloudflareKvStore = (\n options: CloudflareKvStoreOptions\n): CacheStore => {\n const label = 'createCloudflareKvStore()'\n if (typeof options !== 'object' || options === null) {\n throw new TypeError(`${label}: expected an object of options.`)\n }\n for (const key of Object.keys(options)) {\n if (!['kv', 'cacheTtl', 'name'].includes(key)) {\n throw new TypeError(`${label}: unknown option \"${key}\".`)\n }\n }\n const { kv, name = 'cloudflare-kv' } = options\n const isBinding = (value: unknown): value is KvNamespace =>\n typeof (value as KvNamespace | null)?.get === 'function' &&\n typeof (value as KvNamespace).put === 'function' &&\n typeof (value as KvNamespace).delete === 'function'\n if (typeof kv !== 'function' && !isBinding(kv)) {\n throw new TypeError(\n `${label}: kv must be a KV binding, or a function that returns it.`\n )\n }\n let cacheTtl: number | undefined\n if (options.cacheTtl !== undefined) {\n const ms = parseDuration(options.cacheTtl, `${label}: cacheTtl`)\n if (ms < 30_000) {\n throw new TypeError(`${label}: cacheTtl must be at least 30 seconds.`)\n }\n cacheTtl = Math.round(ms / 1000)\n }\n if (typeof name !== 'string' || name === '') {\n throw new TypeError(`${label}: name must be a non-empty string.`)\n }\n\n const binding = () => {\n const value = typeof kv === 'function' ? kv() : kv\n if (!isBinding(value)) {\n throw new TypeError(`${label}: kv() must return a KV binding.`)\n }\n return value\n }\n const read = { type: 'text' as const, ...(cacheTtl ? { cacheTtl } : {}) }\n\n return Object.freeze({\n name,\n maxKeyLength: MAX_KEY_BYTES,\n get: async (key: string) => (await binding().get(key, read)) ?? undefined,\n getMany: async (keys: readonly string[]) => {\n const namespace = binding()\n const unique = [...new Set(keys)]\n const values = new Map<string, string | null>()\n for (let start = 0; start < unique.length; start += MAX_BULK) {\n const found = await namespace.get(\n unique.slice(start, start + MAX_BULK),\n read\n )\n for (const [key, value] of found) values.set(key, value)\n }\n return keys.map((key) => values.get(key) ?? undefined)\n },\n set: async (key: string, value: string, ttl: number) => {\n // The cache keeps the real expiry in the value; KV needs 60 s.\n await binding().put(key, value, {\n expirationTtl: Math.max(MIN_EXPIRATION_TTL, Math.ceil(ttl / 1000)),\n })\n },\n delete: async (key: string) => {\n await binding().delete(key)\n },\n })\n}\n"],"mappings":";AAAA,SAAS,qBAAoC;AAyC7C,IAAM,gBAAgB;AACtB,IAAM,WAAW;AACjB,IAAM,qBAAqB;AAepB,IAAM,0BAA0B,CACrC,YACe;AACf,QAAM,QAAQ;AACd,MAAI,OAAO,YAAY,YAAY,YAAY,MAAM;AACnD,UAAM,IAAI,UAAU,GAAG,KAAK,kCAAkC;AAAA,EAChE;AACA,aAAW,OAAO,OAAO,KAAK,OAAO,GAAG;AACtC,QAAI,CAAC,CAAC,MAAM,YAAY,MAAM,EAAE,SAAS,GAAG,GAAG;AAC7C,YAAM,IAAI,UAAU,GAAG,KAAK,qBAAqB,GAAG,IAAI;AAAA,IAC1D;AAAA,EACF;AACA,QAAM,EAAE,IAAI,OAAO,gBAAgB,IAAI;AACvC,QAAM,YAAY,CAAC,UACjB,OAAQ,OAA8B,QAAQ,cAC9C,OAAQ,MAAsB,QAAQ,cACtC,OAAQ,MAAsB,WAAW;AAC3C,MAAI,OAAO,OAAO,cAAc,CAAC,UAAU,EAAE,GAAG;AAC9C,UAAM,IAAI;AAAA,MACR,GAAG,KAAK;AAAA,IACV;AAAA,EACF;AACA,MAAI;AACJ,MAAI,QAAQ,aAAa,QAAW;AAClC,UAAM,KAAK,cAAc,QAAQ,UAAU,GAAG,KAAK,YAAY;AAC/D,QAAI,KAAK,KAAQ;AACf,YAAM,IAAI,UAAU,GAAG,KAAK,yCAAyC;AAAA,IACvE;AACA,eAAW,KAAK,MAAM,KAAK,GAAI;AAAA,EACjC;AACA,MAAI,OAAO,SAAS,YAAY,SAAS,IAAI;AAC3C,UAAM,IAAI,UAAU,GAAG,KAAK,oCAAoC;AAAA,EAClE;AAEA,QAAM,UAAU,MAAM;AACpB,UAAM,QAAQ,OAAO,OAAO,aAAa,GAAG,IAAI;AAChD,QAAI,CAAC,UAAU,KAAK,GAAG;AACrB,YAAM,IAAI,UAAU,GAAG,KAAK,kCAAkC;AAAA,IAChE;AACA,WAAO;AAAA,EACT;AACA,QAAM,OAAO,EAAE,MAAM,QAAiB,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC,EAAG;AAExE,SAAO,OAAO,OAAO;AAAA,IACnB;AAAA,IACA,cAAc;AAAA,IACd,KAAK,OAAO,QAAiB,MAAM,QAAQ,EAAE,IAAI,KAAK,IAAI,KAAM;AAAA,IAChE,SAAS,OAAO,SAA4B;AAC1C,YAAM,YAAY,QAAQ;AAC1B,YAAM,SAAS,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC;AAChC,YAAM,SAAS,oBAAI,IAA2B;AAC9C,eAAS,QAAQ,GAAG,QAAQ,OAAO,QAAQ,SAAS,UAAU;AAC5D,cAAM,QAAQ,MAAM,UAAU;AAAA,UAC5B,OAAO,MAAM,OAAO,QAAQ,QAAQ;AAAA,UACpC;AAAA,QACF;AACA,mBAAW,CAAC,KAAK,KAAK,KAAK,MAAO,QAAO,IAAI,KAAK,KAAK;AAAA,MACzD;AACA,aAAO,KAAK,IAAI,CAAC,QAAQ,OAAO,IAAI,GAAG,KAAK,MAAS;AAAA,IACvD;AAAA,IACA,KAAK,OAAO,KAAa,OAAe,QAAgB;AAEtD,YAAM,QAAQ,EAAE,IAAI,KAAK,OAAO;AAAA,QAC9B,eAAe,KAAK,IAAI,oBAAoB,KAAK,KAAK,MAAM,GAAI,CAAC;AAAA,MACnE,CAAC;AAAA,IACH;AAAA,IACA,QAAQ,OAAO,QAAgB;AAC7B,YAAM,QAAQ,EAAE,OAAO,GAAG;AAAA,IAC5B;AAAA,EACF,CAAC;AACH;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,103 +1,7 @@
1
- import { D as Duration } from './duration-2mZnG0_i.js';
1
+ import { Duration } from '@dotpress/core';
2
2
  import { C as CacheStore } from './store-D0BfLqyM.js';
3
3
  export { M as MemoryStore, a as MemoryStoreOptions, c as createMemoryStore } from './store-D0BfLqyM.js';
4
-
5
- /** Options shared by every memo. */
6
- interface BaseMemoOptions {
7
- /**
8
- * Name of the memo, used in errors: letters, digits, `.`, `_`, `:` and
9
- * `-`.
10
- */
11
- name: string;
12
- }
13
- /** Options of a memo that loads one value at a time. */
14
- interface LoadMemoOptions<A extends readonly unknown[], T> extends BaseMemoOptions {
15
- /** Loads the value of the arguments. */
16
- load: (...args: A) => T | Promise<T>;
17
- /**
18
- * Key of the arguments. Defaults to the arguments themselves: strings,
19
- * numbers, booleans, `null`, `undefined`, bigints, dates, arrays and
20
- * plain objects.
21
- */
22
- key?: ((...args: A) => unknown) | undefined;
23
- batch?: never;
24
- }
25
- /** Options of a memo that loads the values of several keys at once. */
26
- interface BatchMemoOptions<K, R> extends BaseMemoOptions {
27
- /**
28
- * Loads the values of the keys requested in the same tick, without
29
- * duplicates. Returns their values in the order of the keys (an `Error`
30
- * fails its key only), or a `Map` by key (a missing key gives
31
- * `undefined`).
32
- */
33
- batch: (keys: readonly K[]) => R | Promise<R>;
34
- /** Key of a requested key, for keys that are objects. */
35
- key?: ((key: K) => unknown) | undefined;
36
- /** Keys per call of `batch`. Defaults to no limit. */
37
- maxBatchSize?: number | undefined;
38
- /**
39
- * Schedules a batch. Defaults to a timer (`setTimeout(run, 0)`): the
40
- * batch receives every key requested before it.
41
- */
42
- schedule?: ((run: () => void) => void) | undefined;
43
- load?: never;
44
- }
45
- declare const memoTypes: unique symbol;
46
- /** A memoized loader, created by `defineMemo()`. */
47
- interface MemoDefinition<A extends readonly unknown[], T> {
48
- readonly name: string;
49
- /** Types only. */
50
- readonly [memoTypes]?: {
51
- args: A;
52
- value: T;
53
- };
54
- }
55
- /** Memoized values of a unit of work: a request, a message, a job. */
56
- interface Memo {
57
- /**
58
- * Loads the value of the arguments once per memo: later calls, and
59
- * calls made while it loads, share the result. Failures are shared by
60
- * the calls in progress, not kept.
61
- */
62
- <A extends readonly unknown[], T>(memo: MemoDefinition<A, T>, ...args: A): Promise<T>;
63
- /** Sets the value of the arguments, after an update for instance. */
64
- set<A extends readonly unknown[], T>(memo: MemoDefinition<A, T>, ...argsAndValue: [...A, T]): void;
65
- /** Forgets the value of the arguments: the next call loads it again. */
66
- delete<A extends readonly unknown[], T>(memo: MemoDefinition<A, T>, ...args: A): void;
67
- /** Forgets the values of a memo, or of every memo. */
68
- clear<A extends readonly unknown[], T>(memo?: MemoDefinition<A, T>): void;
69
- }
70
- /**
71
- * Defines a memoized loader: `memo(loadUser, id)` loads a user once per
72
- * memo (request, message, job), and calls made while it loads share the
73
- * same load (single flight). With `batch`, the keys requested in the same
74
- * tick are loaded in one call.
75
- *
76
- * ```ts
77
- * const loadUser = defineMemo({
78
- * name: 'user',
79
- * load: (id: string) => db.users.findById(id),
80
- * })
81
- *
82
- * const loadOrder = defineMemo({
83
- * name: 'order',
84
- * batch: (ids: readonly string[]) => db.orders.findManyById(ids), // a Map by id
85
- * })
86
- * ```
87
- */
88
- declare function defineMemo<K, T>(options: BatchMemoOptions<K, ReadonlyMap<K, T>>): MemoDefinition<[key: K], T | undefined>;
89
- declare function defineMemo<K, T>(options: BatchMemoOptions<K, readonly (T | Error)[]>): MemoDefinition<[key: K], T>;
90
- declare function defineMemo<A extends readonly unknown[], T>(options: LoadMemoOptions<A, T>): MemoDefinition<A, T>;
91
- /**
92
- * Creates a memo: one per request, message or job. Plugins provide
93
- * `ctx.memo`; create one yourself elsewhere.
94
- *
95
- * ```ts
96
- * const memo = createMemo()
97
- * const [ada, grace] = await Promise.all([memo(loadUser, '1'), memo(loadUser, '2')])
98
- * ```
99
- */
100
- declare const createMemo: () => Memo;
4
+ export { B as BatchMemoOptions, L as LoadMemoOptions, a as Memo, b as MemoDefinition, M as MemoScope, c as createMemo, d as defineMemo } from './memo-BOS8USBl.js';
101
5
 
102
6
  /** Base class of the errors thrown by `@dotpress/cache`. */
103
7
  declare class CacheError extends Error {
@@ -302,8 +206,8 @@ interface CacheErrorInfo {
302
206
  /** Name of the entry. */
303
207
  readonly entry: string;
304
208
  }
305
- /** Something that happened to a value, for `onEvent`. */
306
- interface CacheEvent {
209
+ /** An access to a value, for `onAccess`. */
210
+ interface CacheAccess {
307
211
  /**
308
212
  * `hit`: fresh value; `miss`: loaded; `stale`: a value past its `ttl`
309
213
  * served, for the `reason`.
@@ -315,6 +219,10 @@ interface CacheEvent {
315
219
  /** Key of the store. */
316
220
  readonly key: string;
317
221
  }
222
+ /** Context of a cache: the logger of a request, for instance. */
223
+ interface CacheContext {
224
+ logger?: CacheLogger | undefined;
225
+ }
318
226
  /** Options of `createCache()`: the policy is the default of the entries. */
319
227
  interface CacheOptions extends CachePolicy {
320
228
  store: CacheStore;
@@ -340,7 +248,7 @@ interface CacheOptions extends CachePolicy {
340
248
  * Called for each hit, miss and stale value served: metrics. Its errors
341
249
  * are logged and ignored.
342
250
  */
343
- onEvent?: ((event: CacheEvent) => void) | undefined;
251
+ onAccess?: ((event: CacheAccess) => void) | undefined;
344
252
  }
345
253
  /** A cache, created by `createCache()`. */
346
254
  interface Cache {
@@ -368,8 +276,8 @@ interface Cache {
368
276
  * the grace period when the loader fails).
369
277
  */
370
278
  invalidateTags(tags: readonly string[]): Promise<void>;
371
- /** The same cache, logging with another logger. */
372
- withLogger(logger: CacheLogger): Cache;
279
+ /** The same cache, logging with another logger (`ctx.cache` is one). */
280
+ withContext(context?: CacheContext): Cache;
373
281
  }
374
282
  /**
375
283
  * Creates a cache on a store. Reads and writes fail open by default: a
@@ -383,5 +291,5 @@ interface Cache {
383
291
  */
384
292
  declare const createCache: (options: CacheOptions) => Cache;
385
293
 
386
- export { CacheError, CacheStore, CacheStoreError, CacheTimeoutError, Duration, MemoBatchError, createCache, createMemo, defineCacheEntry, defineMemo };
387
- export type { BatchMemoOptions, Cache, CacheEntry, CacheEntryOptions, CacheErrorInfo, CacheEvent, CacheLogger, CacheOptions, CachePolicy, CacheSchema, CacheSerializer, CacheValue, LoadMemoOptions, Memo, MemoDefinition, StoreOperation, Untyped };
294
+ export { CacheError, CacheStore, CacheStoreError, CacheTimeoutError, MemoBatchError, createCache, defineCacheEntry };
295
+ export type { Cache, CacheAccess, CacheContext, CacheEntry, CacheEntryOptions, CacheErrorInfo, CacheLogger, CacheOptions, CachePolicy, CacheSchema, CacheSerializer, CacheValue, StoreOperation, Untyped };
package/dist/index.js CHANGED
@@ -6,10 +6,10 @@ import {
6
6
  createMemo,
7
7
  defineMemo,
8
8
  keyOf
9
- } from "./chunk-6ZNJJARH.js";
10
- import {
11
- toMilliseconds
12
- } from "./chunk-2IEBMYSF.js";
9
+ } from "./chunk-NAI2ODC5.js";
10
+
11
+ // src/cache.ts
12
+ import { parseDuration } from "@dotpress/core";
13
13
 
14
14
  // src/logger.ts
15
15
  var noop = () => void 0;
@@ -119,7 +119,7 @@ var policyOf = (label, values) => {
119
119
  for (const option of POLICY) {
120
120
  const value = values[option];
121
121
  if (value === void 0) continue;
122
- policy[option] = option === "ttl" || option === "timeout" || option === "hardTimeout" || option === "lock" ? positive(`${label}: ${option}`, value) : toMilliseconds(`${label}: ${option}`, value);
122
+ policy[option] = option === "ttl" || option === "timeout" || option === "hardTimeout" || option === "lock" ? positive(`${label}: ${option}`, value) : parseDuration(value, `${label}: ${option}`);
123
123
  }
124
124
  return policy;
125
125
  };
@@ -167,7 +167,7 @@ function defineCacheEntry(options) {
167
167
  return entry;
168
168
  }
169
169
  var positive = (label, value) => {
170
- const ms = toMilliseconds(label, value);
170
+ const ms = parseDuration(value, label);
171
171
  if (ms < 1) throw new TypeError(`${label} must be at least 1 millisecond.`);
172
172
  return ms;
173
173
  };
@@ -180,7 +180,7 @@ var CACHE_OPTIONS = /* @__PURE__ */ new Set([
180
180
  "onError",
181
181
  "failOpen",
182
182
  "storeTimeout",
183
- "onEvent",
183
+ "onAccess",
184
184
  ...POLICY
185
185
  ]);
186
186
  var jsonSerializer = {
@@ -219,7 +219,7 @@ var createCache = (options) => {
219
219
  prefix,
220
220
  serializer = jsonSerializer,
221
221
  onError,
222
- onEvent,
222
+ onAccess,
223
223
  failOpen = true
224
224
  } = options;
225
225
  if (!isStore(store)) {
@@ -242,8 +242,8 @@ var createCache = (options) => {
242
242
  if (onError !== void 0 && typeof onError !== "function") {
243
243
  throw new TypeError(`${label}: onError must be a function.`);
244
244
  }
245
- if (onEvent !== void 0 && typeof onEvent !== "function") {
246
- throw new TypeError(`${label}: onEvent must be a function.`);
245
+ if (onAccess !== void 0 && typeof onAccess !== "function") {
246
+ throw new TypeError(`${label}: onAccess must be a function.`);
247
247
  }
248
248
  if (typeof failOpen !== "boolean") {
249
249
  throw new TypeError(`${label}: failOpen must be a boolean.`);
@@ -355,11 +355,11 @@ var createCache = (options) => {
355
355
  { entry, key, ...reason ? { reason } : {} },
356
356
  type === "hit" ? "Cache hit" : type === "miss" ? "Cache miss" : "Cache served a stale value"
357
357
  );
358
- if (!onEvent) return;
358
+ if (!onAccess) return;
359
359
  try {
360
- onEvent(event);
360
+ onAccess(event);
361
361
  } catch (error) {
362
- logger.error({ error: describe(error) }, "Cache onEvent hook failed");
362
+ logger.error({ error: describe(error) }, "Cache onAccess hook failed");
363
363
  }
364
364
  };
365
365
  const tokensOf = async (entry, tagKeys) => {
@@ -676,7 +676,19 @@ var createCache = (options) => {
676
676
  );
677
677
  logger.debug?.({ tags }, "Cache tags invalidated");
678
678
  },
679
- withLogger: (other) => create(checkLogger("withLogger()", other))
679
+ withContext: (context = {}) => {
680
+ if (typeof context !== "object" || context === null) {
681
+ throw new TypeError("withContext(): expected an object.");
682
+ }
683
+ for (const key of Object.keys(context)) {
684
+ if (key !== "logger") {
685
+ throw new TypeError(`withContext(): unknown option "${key}".`);
686
+ }
687
+ }
688
+ return create(
689
+ context.logger === void 0 ? logger : checkLogger("withContext()", context.logger)
690
+ );
691
+ }
680
692
  };
681
693
  return Object.freeze(cache);
682
694
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/logger.ts","../src/store.ts","../src/cache.ts"],"sourcesContent":["/**\n * Logger of the cache. A Dotpress or Pino logger fits; `cachePlugin` uses\n * the logger of the request.\n */\nexport interface CacheLogger {\n debug?(data: object, message: string): void\n info(data: object, message: string): void\n warn(data: object, message: string): void\n error(data: object, message: string): void\n}\n\nconst noop = () => undefined\n\n/** Logger that discards everything. */\nexport const silentLogger: CacheLogger = {\n debug: noop,\n info: noop,\n warn: noop,\n error: noop,\n}\n\n/** Checks a logger option. */\nexport const checkLogger = (label: string, logger: unknown) => {\n for (const level of ['info', 'warn', 'error'] as const) {\n if (\n typeof (logger as Record<string, unknown> | null)?.[level] !== 'function'\n ) {\n throw new TypeError(\n `${label}: logger must have info(), warn() and error() methods.`\n )\n }\n }\n return logger as CacheLogger\n}\n","/**\n * Where the cache keeps its entries: Redis, Upstash, Cloudflare KV,\n * memory… Values are strings, serialized by the cache.\n */\nexport interface CacheStore {\n /** Name of the store, in logs and errors. */\n readonly name: string\n /**\n * Longest key accepted, in bytes (UTF-8). Longer keys are hashed by the\n * cache. Undefined without limit.\n */\n readonly maxKeyLength?: number | undefined\n /** Value of a key, or undefined when it is missing or expired. */\n get(key: string): Promise<string | undefined>\n /**\n * Values of several keys, in their order: one round trip for the tags of\n * an entry. Optional: the cache calls `get` for each key without it.\n */\n getMany?(keys: readonly string[]): Promise<(string | undefined)[]>\n /** Sets a value that expires after `ttl` milliseconds (at least 1). */\n set(key: string, value: string, ttl: number): Promise<void>\n /** Removes a key; missing keys are ignored. */\n delete(key: string): Promise<void>\n /**\n * Takes a lock for `ttl` milliseconds: returns its token, or undefined\n * when another holder has it. Optional: the `lock` option of the cache\n * needs it.\n */\n lock?(key: string, ttl: number): Promise<string | undefined>\n /** Releases a lock, if the token still holds it. */\n unlock?(key: string, token: string): Promise<void>\n}\n\n/** Checks a store option. */\nexport const isStore = (value: unknown): value is CacheStore => {\n const store = value as Partial<CacheStore> | null\n return (\n typeof store?.name === 'string' &&\n typeof store.get === 'function' &&\n typeof store.set === 'function' &&\n typeof store.delete === 'function'\n )\n}\n\n/** Options of `createMemoryStore()`. */\nexport interface MemoryStoreOptions {\n /**\n * Entries kept at most: the least recently used ones are removed\n * first. Defaults to 10,000.\n */\n maxEntries?: number | undefined\n}\n\n/** Store in the memory of the process: for one instance, and for tests. */\nexport interface MemoryStore extends CacheStore {\n /** Number of entries, expired ones included until they are read. */\n readonly size: number\n /** Removes every entry. */\n clear(): void\n}\n\n/**\n * Creates a store in the memory of the process, least recently used\n * entries removed first. Each instance has its own: use a shared store\n * (Redis…) for several instances.\n */\nexport const createMemoryStore = (\n options: MemoryStoreOptions = {}\n): MemoryStore => {\n const label = 'createMemoryStore()'\n if (typeof options !== 'object' || options === null) {\n throw new TypeError(`${label}: expected an object of options.`)\n }\n for (const key of Object.keys(options)) {\n if (key !== 'maxEntries') {\n throw new TypeError(`${label}: unknown option \"${key}\".`)\n }\n }\n const { maxEntries = 10_000 } = options\n if (!Number.isInteger(maxEntries) || maxEntries < 1) {\n throw new TypeError(`${label}: maxEntries must be a positive integer.`)\n }\n\n // Insertion order of a Map: the first entry is the least recently used.\n const entries = new Map<string, { value: string; expiresAt: number }>()\n const locks = new Map<string, { token: string; expiresAt: number }>()\n\n const read = (key: string) => {\n const entry = entries.get(key)\n if (!entry) return undefined\n entries.delete(key)\n if (entry.expiresAt <= Date.now()) return undefined\n entries.set(key, entry)\n return entry.value\n }\n\n return Object.freeze({\n name: 'memory',\n get size() {\n return entries.size\n },\n get: async (key: string) => read(key),\n getMany: async (keys: readonly string[]) => keys.map(read),\n set: async (key: string, value: string, ttl: number) => {\n entries.delete(key)\n entries.set(key, { value, expiresAt: Date.now() + ttl })\n for (const oldest of entries.keys()) {\n if (entries.size <= maxEntries) break\n entries.delete(oldest)\n }\n },\n delete: async (key: string) => {\n entries.delete(key)\n },\n lock: async (key: string, ttl: number) => {\n const held = locks.get(key)\n if (held && held.expiresAt > Date.now()) return undefined\n const token = crypto.randomUUID()\n locks.set(key, { token, expiresAt: Date.now() + ttl })\n return token\n },\n unlock: async (key: string, token: string) => {\n if (locks.get(key)?.token === token) locks.delete(key)\n },\n clear: () => {\n entries.clear()\n locks.clear()\n },\n })\n}\n","import { toMilliseconds, type Duration } from './duration.js'\nimport {\n CacheStoreError,\n CacheTimeoutError,\n type StoreOperation,\n} from './errors.js'\nimport { keyOf } from './keys.js'\nimport { checkLogger, silentLogger, type CacheLogger } from './logger.js'\nimport { isStore, type CacheStore } from './store.js'\n\n/**\n * A schema that validates cached values when they are read: a Zod schema,\n * or anything with the same `safeParseAsync()`.\n */\nexport interface CacheSchema<T> {\n safeParseAsync(\n value: unknown\n ): Promise<{ success: true; data: T } | { success: false }>\n}\n\ndeclare const untyped: unique symbol\n/** Value type of an entry without schema: given by the loader. */\nexport interface Untyped {\n readonly [untyped]: true\n}\n\ndeclare const entryTypes: unique symbol\n\n/** A kind of cached value, created by `defineCacheEntry()`. */\nexport interface CacheEntry<A extends readonly unknown[], T> {\n readonly name: string\n readonly version: number\n /** Types only. */\n readonly [entryTypes]?: { args: A; value: (value: T) => T }\n}\n\n/** Value of an entry: `unknown` without schema. */\nexport type CacheValue<T> = T extends Untyped ? unknown : T\n\n/**\n * Lifetime and resilience of values: options of an entry, or defaults of\n * the cache.\n */\nexport interface CachePolicy {\n /** Lifetime of the values. Defaults to 1 hour. */\n ttl?: Duration | undefined\n /**\n * Stale-if-error: how long after expiry a value can still be served when\n * the loader fails (or times out). Defaults to 0 (disabled).\n */\n grace?: Duration | undefined\n /**\n * After a failed load served from the grace period, the loader is not\n * called again before this delay: the stale value is served. Defaults to\n * 10 seconds.\n */\n graceBackoff?: Duration | undefined\n /**\n * How long after expiry a value is still served while a background load\n * refreshes it. Defaults to 0 (disabled).\n */\n staleWhileRevalidate?: Duration | undefined\n /**\n * Soft timeout: a load slower than this serves the stale value of the\n * grace period, if any, and goes on in the background.\n */\n timeout?: Duration | undefined\n /**\n * Hard timeout: a load slower than this fails (`CacheTimeoutError`), or\n * serves the stale value of the grace period, if any. It goes on in the\n * background.\n */\n hardTimeout?: Duration | undefined\n /**\n * Distributed single flight: before loading a missing value, takes a\n * lock in the store (Redis…) for at most this duration; the other\n * instances wait for the value meanwhile, then load it themselves.\n * Ignored by stores without locks. Disabled by default.\n */\n lock?: Duration | undefined\n}\n\n/** Options of `defineCacheEntry()`. */\nexport interface CacheEntryOptions<\n A extends readonly unknown[],\n T,\n> extends CachePolicy {\n /**\n * Name of the entry, in keys and logs: letters, digits, `.`, `_` and\n * `-`.\n */\n name: string\n /**\n * Key of the arguments: a string, a number, or a value compared like the\n * arguments of a memo. Defaults to the argument (or the arguments).\n */\n key?: ((...args: A) => unknown) | undefined\n /**\n * Validates cached values when they are read (entries from an older\n * version of the app) and written. JSON turns dates into strings: use\n * `z.coerce.date()`.\n */\n schema?: CacheSchema<T> | undefined\n /**\n * Version of the values, in the key: increase it when their shape\n * changes, to ignore the values written before. Defaults to 1.\n */\n version?: number | undefined\n /**\n * Tags of the arguments: `invalidateTags()` expires every value with one\n * of them.\n */\n tags?: ((...args: A) => readonly string[]) | undefined\n}\n\n/** A policy in milliseconds; undefined when not set. */\ntype Policy = { [K in keyof CachePolicy]: number | undefined }\n\ninterface EntryConfig {\n name: string\n version: number\n key: ((...args: readonly unknown[]) => unknown) | undefined\n schema: CacheSchema<unknown> | undefined\n tags: ((...args: readonly unknown[]) => readonly string[]) | undefined\n policy: Policy\n}\n\nconst entries = new WeakMap<object, EntryConfig>()\n\nconst NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/\nconst POLICY = [\n 'ttl',\n 'grace',\n 'graceBackoff',\n 'staleWhileRevalidate',\n 'timeout',\n 'hardTimeout',\n 'lock',\n] as const\nconst ENTRY_OPTIONS = new Set([\n 'name',\n 'key',\n 'schema',\n 'version',\n 'tags',\n ...POLICY,\n])\n\n/** Reads the durations of a policy (`ttl` and timeouts at least 1 ms). */\nconst policyOf = (label: string, values: Record<string, unknown>): Policy => {\n const policy: Policy = {}\n for (const option of POLICY) {\n const value = values[option]\n if (value === undefined) continue\n policy[option] =\n option === 'ttl' ||\n option === 'timeout' ||\n option === 'hardTimeout' ||\n option === 'lock'\n ? positive(`${label}: ${option}`, value)\n : toMilliseconds(`${label}: ${option}`, value)\n }\n return policy\n}\n\n/**\n * Defines a kind of cached value: its key, its lifetime, and optionally\n * its schema.\n *\n * ```ts\n * export const userCache = defineCacheEntry({\n * name: 'user',\n * key: (id: string) => id,\n * schema: userSchema,\n * ttl: '1h',\n * })\n * ```\n */\nexport function defineCacheEntry<A extends readonly unknown[], T>(\n options: CacheEntryOptions<A, T> & { schema: CacheSchema<T> }\n): CacheEntry<A, T>\nexport function defineCacheEntry<A extends readonly unknown[]>(\n options: CacheEntryOptions<A, never> & { schema?: undefined }\n): CacheEntry<A, Untyped>\nexport function defineCacheEntry<A extends readonly unknown[], T>(\n options: CacheEntryOptions<A, T>\n): CacheEntry<A, T>\nexport function defineCacheEntry(options: unknown): {\n readonly name: string\n readonly version: number\n} {\n const label = 'defineCacheEntry()'\n if (typeof options !== 'object' || options === null) {\n throw new TypeError(`${label}: expected an object of options.`)\n }\n const values = options as Record<string, unknown>\n const { name, key, schema, tags, version = 1 } = values\n if (typeof name !== 'string' || !NAME.test(name)) {\n throw new TypeError(\n `${label}: name must have 1 to 128 letters, digits, \".\", \"_\" or \"-\", starting with a letter or a digit.`\n )\n }\n const named = `defineCacheEntry(\"${name}\")`\n for (const option of Object.keys(values)) {\n if (!ENTRY_OPTIONS.has(option)) {\n throw new TypeError(`${named}: unknown option \"${option}\".`)\n }\n }\n if (key !== undefined && typeof key !== 'function') {\n throw new TypeError(`${named}: key must be a function.`)\n }\n if (\n schema !== undefined &&\n typeof (schema as Partial<CacheSchema<unknown>> | null)?.safeParseAsync !==\n 'function'\n ) {\n throw new TypeError(\n `${named}: schema must have a safeParseAsync() method, like a Zod schema.`\n )\n }\n if (!Number.isInteger(version) || (version as number) < 0) {\n throw new TypeError(`${named}: version must be a non-negative integer.`)\n }\n if (tags !== undefined && typeof tags !== 'function') {\n throw new TypeError(`${named}: tags must be a function.`)\n }\n\n const entry = Object.freeze({ name, version: version as number })\n entries.set(entry, {\n name,\n version: version as number,\n key: key as EntryConfig['key'],\n schema: schema as CacheSchema<unknown> | undefined,\n tags: tags as EntryConfig['tags'],\n policy: policyOf(named, values),\n })\n return entry\n}\n\nconst positive = (label: string, value: unknown) => {\n const ms = toMilliseconds(label, value)\n if (ms < 1) throw new TypeError(`${label} must be at least 1 millisecond.`)\n return ms\n}\n\n/** Turns values into strings for the store, and back. */\nexport interface CacheSerializer {\n serialize(value: unknown): string\n deserialize(text: string): unknown\n}\n\n/** Details of a store failure, for `onError`. */\nexport interface CacheErrorInfo {\n readonly operation: StoreOperation\n /** Key of the store. */\n readonly key: string\n /** Name of the entry. */\n readonly entry: string\n}\n\n/** Something that happened to a value, for `onEvent`. */\nexport interface CacheEvent {\n /**\n * `hit`: fresh value; `miss`: loaded; `stale`: a value past its `ttl`\n * served, for the `reason`.\n */\n readonly type: 'hit' | 'miss' | 'stale'\n readonly reason?: 'revalidate' | 'error' | 'timeout' | 'backoff'\n /** Name of the entry. */\n readonly entry: string\n /** Key of the store. */\n readonly key: string\n}\n\n/** Options of `createCache()`: the policy is the default of the entries. */\nexport interface CacheOptions extends CachePolicy {\n store: CacheStore\n /** Prefix of every key, like the name of the app. */\n prefix?: string | undefined\n /** Defaults to JSON. */\n serializer?: CacheSerializer | undefined\n logger?: CacheLogger | undefined\n /**\n * Called for each store failure, besides the log: metrics, alerts. Its\n * own errors are logged and ignored.\n */\n onError?:\n | ((error: CacheStoreError, info: CacheErrorInfo) => void | Promise<void>)\n | undefined\n /**\n * When the store fails, behaves as a cache miss (reads) or ignores the\n * write, after logging it. Set false to throw a `CacheStoreError`.\n * Defaults to true.\n */\n failOpen?: boolean | undefined\n /** A store operation slower than this counts as a failure. */\n storeTimeout?: Duration | undefined\n /**\n * Called for each hit, miss and stale value served: metrics. Its errors\n * are logged and ignored.\n */\n onEvent?: ((event: CacheEvent) => void) | undefined\n}\n\n/** A cache, created by `createCache()`. */\nexport interface Cache {\n /**\n * The fresh value of the arguments, or undefined when missing, expired\n * or invalidated.\n */\n get<A extends readonly unknown[], T>(\n entry: CacheEntry<A, T>,\n ...args: A\n ): Promise<CacheValue<T> | undefined>\n /** Sets the value of the arguments (the last argument). */\n set<A extends readonly unknown[], T>(\n entry: CacheEntry<A, T>,\n ...argsAndValue: [...A, CacheValue<T>]\n ): Promise<void>\n /** Removes the value of the arguments. */\n delete<A extends readonly unknown[], T>(\n entry: CacheEntry<A, T>,\n ...args: A\n ): Promise<void>\n /**\n * The value of the arguments, loaded by the last argument when missing\n * and cached. Calls made while it loads share the load. Applies the\n * grace period, stale-while-revalidate and timeouts of the entry.\n *\n * A loader may return `undefined` (not found): it is not cached, and\n * `getOrSet` returns it.\n */\n getOrSet<A extends readonly unknown[], R>(\n entry: CacheEntry<A, Untyped>,\n ...argsAndLoader: [...A, () => R | Promise<R>]\n ): Promise<R>\n getOrSet<A extends readonly unknown[], T, R extends T | undefined = T>(\n entry: CacheEntry<A, T>,\n ...argsAndLoader: [...A, () => R | Promise<R>]\n ): Promise<[R] extends [T] ? T : T | undefined>\n /**\n * Expires every value with one of the tags (they can still be served by\n * the grace period when the loader fails).\n */\n invalidateTags(tags: readonly string[]): Promise<void>\n /** The same cache, logging with another logger. */\n withLogger(logger: CacheLogger): Cache\n}\n\nconst CACHE_OPTIONS = new Set([\n 'store',\n 'prefix',\n 'ttl',\n 'serializer',\n 'logger',\n 'onError',\n 'failOpen',\n 'storeTimeout',\n 'onEvent',\n ...POLICY,\n])\n\nconst jsonSerializer: CacheSerializer = {\n serialize: (value) => JSON.stringify(value),\n deserialize: (text) => JSON.parse(text) as unknown,\n}\n\n/**\n * Stored form of a value: `{ v, c (created), e (expires), d (data),\n * t (token of each tag) }`.\n */\ninterface StoredEntry {\n v: 1\n c: number\n e: number\n d: string\n t?: Record<string, string>\n}\n\nconst parseStored = (text: string): StoredEntry | undefined => {\n try {\n const stored = JSON.parse(text) as Partial<StoredEntry> | null\n if (\n stored?.v === 1 &&\n typeof stored.c === 'number' &&\n typeof stored.e === 'number' &&\n typeof stored.d === 'string' &&\n (stored.t === undefined ||\n (typeof stored.t === 'object' && stored.t !== null))\n ) {\n return stored as StoredEntry\n }\n } catch {\n // Not an entry of this cache.\n }\n return undefined\n}\n\nconst encoder = new TextEncoder()\n\n// Tokens of tags outlive the values that use them: 30 days at least.\nconst TAG_LIFETIME = 30 * 86_400_000\n// While another instance holds the lock, the value is read again so often.\nconst LOCK_POLL = 50\n\n/** SHA-256 of a text, in hexadecimal. */\nconst sha256 = async (text: string) => {\n const digest = await crypto.subtle.digest('SHA-256', encoder.encode(text))\n return [...new Uint8Array(digest)]\n .map((byte) => byte.toString(16).padStart(2, '0'))\n .join('')\n}\n\n/**\n * Creates a cache on a store. Reads and writes fail open by default: a\n * store failure is logged (and passed to `onError`), and the app keeps\n * working without cache.\n *\n * ```ts\n * const cache = createCache({ store: createMemoryStore(), prefix: 'app', ttl: '10m' })\n * const user = await cache.getOrSet(userCache, id, () => db.users.findById(id))\n * ```\n */\nexport const createCache = (options: CacheOptions): Cache => {\n const label = 'createCache()'\n if (typeof options !== 'object' || options === null) {\n throw new TypeError(`${label}: expected an object of options.`)\n }\n for (const key of Object.keys(options)) {\n if (!CACHE_OPTIONS.has(key)) {\n throw new TypeError(`${label}: unknown option \"${key}\".`)\n }\n }\n const {\n store,\n prefix,\n serializer = jsonSerializer,\n onError,\n onEvent,\n failOpen = true,\n } = options\n if (!isStore(store)) {\n throw new TypeError(\n `${label}: store must be a cache store (createMemoryStore(), createRedisStore()…).`\n )\n }\n if (prefix !== undefined && (typeof prefix !== 'string' || prefix === '')) {\n throw new TypeError(`${label}: prefix must be a non-empty string.`)\n }\n const defaults = policyOf(\n label,\n options as unknown as Record<string, unknown>\n )\n if (\n typeof serializer?.serialize !== 'function' ||\n typeof serializer.deserialize !== 'function'\n ) {\n throw new TypeError(\n `${label}: serializer must have serialize() and deserialize() methods.`\n )\n }\n if (onError !== undefined && typeof onError !== 'function') {\n throw new TypeError(`${label}: onError must be a function.`)\n }\n if (onEvent !== undefined && typeof onEvent !== 'function') {\n throw new TypeError(`${label}: onEvent must be a function.`)\n }\n if (typeof failOpen !== 'boolean') {\n throw new TypeError(`${label}: failOpen must be a boolean.`)\n }\n const storeTimeout =\n options.storeTimeout === undefined\n ? undefined\n : positive(`${label}: storeTimeout`, options.storeTimeout)\n const rootLogger =\n options.logger === undefined\n ? silentLogger\n : checkLogger(label, options.logger)\n\n // Shared by every logger of the cache: loads in progress, and the end of\n // the backoff of the keys whose loader failed.\n const loading = new Map<string, Promise<unknown>>()\n const backoffs = new Map<string, number>()\n\n const configOf = (method: string, entry: unknown) => {\n const config =\n typeof entry === 'object' && entry !== null\n ? entries.get(entry)\n : undefined\n if (!config) {\n throw new TypeError(\n `cache.${method}(): expected an entry created by defineCacheEntry().`\n )\n }\n return config\n }\n\n /** Policy of an entry, with the defaults of the cache. */\n const policyFor = (config: EntryConfig) => {\n const value = (option: keyof CachePolicy) =>\n config.policy[option] ?? defaults[option]\n return {\n ttl: value('ttl') ?? 3_600_000,\n grace: value('grace') ?? 0,\n graceBackoff: value('graceBackoff') ?? 10_000,\n staleWhileRevalidate: value('staleWhileRevalidate') ?? 0,\n timeout: value('timeout'),\n hardTimeout: value('hardTimeout'),\n lock: value('lock'),\n }\n }\n\n /** A key of the store, hashed when longer than the store accepts. */\n const fit = async (base: string, part: string) => {\n const key = `${base}:${part}`\n return encoder.encode(key).length > (store.maxKeyLength ?? Infinity)\n ? `${base}:#${await sha256(part)}`\n : key\n }\n\n /** Key of the store for the arguments of an entry. */\n const keyFor = async (config: EntryConfig, args: readonly unknown[]) => {\n const raw = config.key\n ? config.key(...args)\n : args.length === 1\n ? args[0]\n : args\n const base = [prefix, config.name, `v${config.version}`]\n .filter((item) => item !== undefined)\n .join(':')\n if (!config.key && args.length === 0) return base\n return fit(\n base,\n typeof raw === 'string' ? raw : keyOf(`Cache entry \"${config.name}\"`, raw)\n )\n }\n\n /** Checks the tags returned by an entry, or given to invalidate. */\n const checkTags = (label: string, tags: unknown) => {\n if (\n !Array.isArray(tags) ||\n !tags.every((tag) => typeof tag === 'string' && tag !== '')\n ) {\n throw new TypeError(\n `${label}: tags must be an array of non-empty strings.`\n )\n }\n return [...new Set(tags as string[])]\n }\n\n /** Keys of the tokens of tags (`_` cannot start the name of an entry). */\n const tagKeysOf = (tags: readonly string[]) => {\n const base = prefix === undefined ? '_tag' : `${prefix}:_tag`\n return Promise.all(\n tags.map(async (tag) => ({ tag, key: await fit(base, tag) }))\n )\n }\n\n const create = (logger: CacheLogger): Cache => {\n const describe = (error: unknown) =>\n error instanceof Error ? error.message : String(error)\n\n /** Runs a store operation: failures are logged, and fail open. */\n const guard = async <R>(\n operation: StoreOperation,\n key: string,\n entry: string,\n run: () => Promise<R>,\n fallback: R\n ): Promise<R> => {\n let timer: ReturnType<typeof setTimeout> | undefined\n try {\n const running = run()\n return storeTimeout === undefined\n ? await running\n : await Promise.race([\n running,\n new Promise<never>((_, reject) => {\n timer = setTimeout(\n () =>\n reject(\n new Error(`no answer within ${storeTimeout} milliseconds`)\n ),\n storeTimeout\n )\n }),\n ])\n } catch (cause) {\n const error = new CacheStoreError(store.name, operation, key, {\n cause,\n })\n const data = {\n store: store.name,\n operation,\n key,\n entry,\n error: error.message,\n }\n // A failed deletion or invalidation may leave outdated values.\n if (operation === 'delete' || operation === 'invalidate') {\n logger.error(data, 'Cache store failed')\n } else {\n logger.warn(data, 'Cache store failed')\n }\n if (onError) {\n try {\n await onError(error, { operation, key, entry })\n } catch (hookError) {\n logger.error(\n { error: describe(hookError) },\n 'Cache onError hook failed'\n )\n }\n }\n if (!failOpen) throw error\n return fallback\n } finally {\n clearTimeout(timer)\n }\n }\n\n /** Reports an event to the log (debug) and `onEvent`. */\n const report = (event: CacheEvent) => {\n const { type, reason, entry, key } = event\n logger.debug?.(\n { entry, key, ...(reason ? { reason } : {}) },\n type === 'hit'\n ? 'Cache hit'\n : type === 'miss'\n ? 'Cache miss'\n : 'Cache served a stale value'\n )\n if (!onEvent) return\n try {\n onEvent(event)\n } catch (error) {\n logger.error({ error: describe(error) }, 'Cache onEvent hook failed')\n }\n }\n\n /**\n * Current tokens of tags (undefined for a missing tag), or undefined\n * when the store cannot read them.\n */\n const tokensOf = async (\n entry: string,\n tagKeys: readonly { tag: string; key: string }[]\n ) => {\n const keys = tagKeys.map(({ key }) => key)\n const values = await guard<(string | undefined)[] | undefined>(\n 'get',\n keys.join(' '),\n entry,\n () =>\n store.getMany\n ? store.getMany(keys)\n : Promise.all(keys.map((key) => store.get(key))),\n undefined\n )\n return values?.map((token, index) => ({\n tag: (tagKeys[index] as { tag: string }).tag,\n token,\n }))\n }\n\n /** Deserializes and validates a value: `{ value }`, or undefined. */\n const decode = async (config: EntryConfig, key: string, data: string) => {\n let value: unknown\n try {\n value = serializer.deserialize(data)\n } catch (error) {\n logger.warn(\n { entry: config.name, key, error: describe(error) },\n 'Cache value cannot be deserialized'\n )\n return undefined\n }\n if (!config.schema) return { value }\n const result = await config.schema.safeParseAsync(value)\n return result.success ? { value: result.data } : undefined\n }\n\n /**\n * Reads a stored value: its value, expiry, and whether its tags are\n * still valid. Undefined when missing, invalid, or past every stale\n * window.\n */\n const lookup = async (\n config: EntryConfig,\n policy: ReturnType<typeof policyFor>,\n key: string\n ) => {\n const text = await guard(\n 'get',\n key,\n config.name,\n () => store.get(key),\n undefined\n )\n if (text === undefined) return undefined\n const stored = parseStored(text)\n const now = Date.now()\n if (\n !stored ||\n stored.e + Math.max(policy.grace, policy.staleWhileRevalidate) <= now\n ) {\n return undefined\n }\n const decoded = await decode(config, key, stored.d)\n if (!decoded) return undefined\n const tags = Object.entries(stored.t ?? {})\n let tagsValid = true\n if (tags.length > 0) {\n const current = await tokensOf(\n config.name,\n await tagKeysOf(tags.map(([tag]) => tag))\n )\n // Unreadable tokens: the value may have been invalidated.\n tagsValid =\n current?.every(({ tag, token }) => token === stored.t?.[tag]) ?? false\n }\n return {\n value: decoded.value,\n expiresAt: stored.e,\n fresh: stored.e > now && tagsValid,\n tagsValid,\n }\n }\n\n /**\n * Writes a value, after checking that it reads back: returns the value\n * as read later, or undefined when it cannot be cached.\n */\n const write = async (\n config: EntryConfig,\n key: string,\n args: readonly unknown[],\n value: unknown\n ): Promise<{ value: unknown } | undefined> => {\n if (value === undefined) return undefined\n let data: string\n try {\n data = serializer.serialize(value)\n if (typeof data !== 'string') throw new TypeError('not a string')\n } catch (error) {\n logger.warn(\n { entry: config.name, key, error: describe(error) },\n 'Cache value cannot be serialized'\n )\n return undefined\n }\n const checked = config.schema\n ? await decode(config, key, data)\n : { value }\n if (!checked) {\n logger.warn(\n { entry: config.name, key },\n 'Cache value does not match the schema of the entry: not cached'\n )\n return undefined\n }\n const policy = policyFor(config)\n const lifetime =\n policy.ttl + Math.max(policy.grace, policy.staleWhileRevalidate)\n const now = Date.now()\n const stored: StoredEntry = {\n v: 1,\n c: now,\n e: now + policy.ttl,\n d: data,\n }\n if (config.tags) {\n const tags = checkTags(\n `defineCacheEntry(\"${config.name}\")`,\n config.tags(...args)\n )\n if (tags.length > 0) {\n const tagKeys = await tagKeysOf(tags)\n const current = await tokensOf(config.name, tagKeys)\n // Unreadable tokens: creating new ones would invalidate every\n // value of these tags, so the value is not cached.\n if (!current) return checked\n stored.t = {}\n for (const [index, { tag, token }] of current.entries()) {\n if (token !== undefined) {\n stored.t[tag] = token\n continue\n }\n // First value with this tag: its token is created.\n const created = crypto.randomUUID()\n const tagKey = (tagKeys[index] as { key: string }).key\n await guard(\n 'set',\n tagKey,\n config.name,\n () =>\n store.set(tagKey, created, Math.max(lifetime, TAG_LIFETIME)),\n undefined\n )\n stored.t[tag] = created\n }\n }\n }\n await guard(\n 'set',\n key,\n config.name,\n () => store.set(key, JSON.stringify(stored), lifetime),\n undefined\n )\n return checked\n }\n\n /**\n * Loads and writes a value, once per key at a time, and once for every\n * instance with the `lock` option.\n */\n const load = (\n config: EntryConfig,\n key: string,\n args: readonly unknown[],\n loader: () => unknown\n ) => {\n const running = loading.get(key)\n if (running) return running\n const loaded = (async () => {\n const policy = policyFor(config)\n const lockKey = `${key}:lock`\n let token: string | undefined\n if (policy.lock !== undefined && store.lock) {\n const lock = store.lock.bind(store)\n const ttl = policy.lock\n // null: the store failed, load without lock.\n const taken = await guard<string | undefined | null>(\n 'lock',\n lockKey,\n config.name,\n () => lock(lockKey, ttl),\n null\n )\n if (taken === undefined) {\n // Another instance loads it: wait for its value.\n const until = Date.now() + ttl\n while (Date.now() < until) {\n await new Promise((resolve) => setTimeout(resolve, LOCK_POLL))\n const found = await lookup(config, policy, key)\n if (found?.fresh) return found.value\n }\n logger.warn(\n { entry: config.name, key, lock: ttl },\n 'Cache lock not released in time: loading anyway'\n )\n } else if (taken !== null) {\n token = taken\n }\n }\n try {\n const value: unknown = await loader()\n const written = await write(config, key, args, value)\n backoffs.delete(key)\n return written ? written.value : value\n } finally {\n if (token !== undefined && store.unlock) {\n const unlock = store.unlock.bind(store)\n const held = token\n await guard(\n 'unlock',\n lockKey,\n config.name,\n () => unlock(lockKey, held),\n undefined\n )\n }\n }\n })()\n loading.set(key, loaded)\n void loaded.then(\n () => loading.delete(key),\n () => loading.delete(key)\n )\n return loaded\n }\n\n /** Logs the failure of a load that nobody waits for anymore. */\n const inBackground = (\n config: EntryConfig,\n key: string,\n loaded: Promise<unknown>\n ) => {\n loaded.catch((error: unknown) => {\n logger.warn(\n { entry: config.name, key, error: describe(error) },\n 'Cache refresh failed'\n )\n })\n }\n\n /** Rejects after `ms` (never when undefined), until cleared. */\n const deadline = (ms: number | undefined, error: () => unknown) => {\n let timer: ReturnType<typeof setTimeout> | undefined\n const promise = new Promise<never>((_, reject) => {\n if (ms !== undefined) timer = setTimeout(() => reject(error()), ms)\n })\n return { promise, clear: () => clearTimeout(timer) }\n }\n\n const cache: Cache = {\n get: (async (entry: unknown, ...args: unknown[]) => {\n const config = configOf('get', entry)\n const key = await keyFor(config, args)\n const found = await lookup(config, policyFor(config), key)\n report({ type: found?.fresh ? 'hit' : 'miss', entry: config.name, key })\n return found?.fresh ? found.value : undefined\n }) as Cache['get'],\n\n set: (async (entry: unknown, ...argsAndValue: unknown[]) => {\n const config = configOf('set', entry)\n const args = argsAndValue.slice(0, -1)\n const key = await keyFor(config, args)\n await write(config, key, args, argsAndValue.at(-1))\n }) as Cache['set'],\n\n delete: (async (entry: unknown, ...args: unknown[]) => {\n const config = configOf('delete', entry)\n const key = await keyFor(config, args)\n await guard(\n 'delete',\n key,\n config.name,\n () => store.delete(key),\n undefined\n )\n }) as Cache['delete'],\n\n getOrSet: (async (entry: unknown, ...argsAndLoader: unknown[]) => {\n const config = configOf('getOrSet', entry)\n const loader = argsAndLoader.at(-1)\n if (typeof loader !== 'function') {\n throw new TypeError(\n 'cache.getOrSet(): the last argument must be the function that loads the value.'\n )\n }\n const args = argsAndLoader.slice(0, -1)\n const key = await keyFor(config, args)\n const policy = policyFor(config)\n const found = await lookup(config, policy, key)\n const event = { entry: config.name, key }\n if (found?.fresh) {\n report({ type: 'hit', ...event })\n return found.value\n }\n const now = Date.now()\n\n // Stale-while-revalidate: serve, and refresh in the background.\n if (\n found?.tagsValid &&\n found.expiresAt + policy.staleWhileRevalidate > now\n ) {\n report({ type: 'stale', reason: 'revalidate', ...event })\n inBackground(\n config,\n key,\n load(config, key, args, loader as () => unknown)\n )\n return found.value\n }\n\n // Grace period: stale value served if the loader fails. Without it,\n // an invalidated value is never served.\n const stale =\n found && policy.grace > 0 && found.expiresAt + policy.grace > now\n ? found\n : undefined\n if (stale && (backoffs.get(key) ?? 0) > now) {\n report({ type: 'stale', reason: 'backoff', ...event })\n return stale.value\n }\n\n report({ type: 'miss', ...event })\n const loaded = load(config, key, args, loader as () => unknown)\n const slow = Symbol('slow')\n const soft = deadline(stale ? policy.timeout : undefined, () => slow)\n const hard = deadline(\n policy.hardTimeout,\n () =>\n new CacheTimeoutError(\n config.name,\n key,\n policy.hardTimeout as number\n )\n )\n try {\n return await Promise.race([loaded, soft.promise, hard.promise])\n } catch (error) {\n // The load goes on, and caches its value.\n if (error === slow || error instanceof CacheTimeoutError) {\n inBackground(config, key, loaded)\n }\n if (!stale) throw error\n if (error === slow) {\n logger.warn(\n { ...event, timeout: policy.timeout },\n 'Cache served a stale value: the loader is slow'\n )\n report({ type: 'stale', reason: 'timeout', ...event })\n return stale.value\n }\n backoffs.set(key, Date.now() + policy.graceBackoff)\n logger.warn(\n { ...event, error: describe(error) },\n 'Cache served a stale value: the loader failed'\n )\n report({ type: 'stale', reason: 'error', ...event })\n return stale.value\n } finally {\n soft.clear()\n hard.clear()\n }\n }) as Cache['getOrSet'],\n\n invalidateTags: async (tags) => {\n const tagKeys = await tagKeysOf(\n checkTags('cache.invalidateTags()', tags)\n )\n await Promise.all(\n tagKeys.map(({ tag, key }) =>\n guard(\n 'invalidate',\n key,\n `tag:${tag}`,\n () => store.set(key, crypto.randomUUID(), TAG_LIFETIME),\n undefined\n )\n )\n )\n logger.debug?.({ tags }, 'Cache tags invalidated')\n },\n\n withLogger: (other) => create(checkLogger('withLogger()', other)),\n }\n return Object.freeze(cache)\n }\n\n return create(rootLogger)\n}\n"],"mappings":"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","names":["entries","label"]}
1
+ {"version":3,"sources":["../src/cache.ts","../src/logger.ts","../src/store.ts"],"sourcesContent":["import { parseDuration, type Duration } from '@dotpress/core'\nimport {\n CacheStoreError,\n CacheTimeoutError,\n type StoreOperation,\n} from './errors.js'\nimport { keyOf } from './keys.js'\nimport { checkLogger, silentLogger, type CacheLogger } from './logger.js'\nimport { isStore, type CacheStore } from './store.js'\n\n/**\n * A schema that validates cached values when they are read: a Zod schema,\n * or anything with the same `safeParseAsync()`.\n */\nexport interface CacheSchema<T> {\n safeParseAsync(\n value: unknown\n ): Promise<{ success: true; data: T } | { success: false }>\n}\n\ndeclare const untyped: unique symbol\n/** Value type of an entry without schema: given by the loader. */\nexport interface Untyped {\n readonly [untyped]: true\n}\n\ndeclare const entryTypes: unique symbol\n\n/** A kind of cached value, created by `defineCacheEntry()`. */\nexport interface CacheEntry<A extends readonly unknown[], T> {\n readonly name: string\n readonly version: number\n /** Types only. */\n readonly [entryTypes]?: { args: A; value: (value: T) => T }\n}\n\n/** Value of an entry: `unknown` without schema. */\nexport type CacheValue<T> = T extends Untyped ? unknown : T\n\n/**\n * Lifetime and resilience of values: options of an entry, or defaults of\n * the cache.\n */\nexport interface CachePolicy {\n /** Lifetime of the values. Defaults to 1 hour. */\n ttl?: Duration | undefined\n /**\n * Stale-if-error: how long after expiry a value can still be served when\n * the loader fails (or times out). Defaults to 0 (disabled).\n */\n grace?: Duration | undefined\n /**\n * After a failed load served from the grace period, the loader is not\n * called again before this delay: the stale value is served. Defaults to\n * 10 seconds.\n */\n graceBackoff?: Duration | undefined\n /**\n * How long after expiry a value is still served while a background load\n * refreshes it. Defaults to 0 (disabled).\n */\n staleWhileRevalidate?: Duration | undefined\n /**\n * Soft timeout: a load slower than this serves the stale value of the\n * grace period, if any, and goes on in the background.\n */\n timeout?: Duration | undefined\n /**\n * Hard timeout: a load slower than this fails (`CacheTimeoutError`), or\n * serves the stale value of the grace period, if any. It goes on in the\n * background.\n */\n hardTimeout?: Duration | undefined\n /**\n * Distributed single flight: before loading a missing value, takes a\n * lock in the store (Redis…) for at most this duration; the other\n * instances wait for the value meanwhile, then load it themselves.\n * Ignored by stores without locks. Disabled by default.\n */\n lock?: Duration | undefined\n}\n\n/** Options of `defineCacheEntry()`. */\nexport interface CacheEntryOptions<\n A extends readonly unknown[],\n T,\n> extends CachePolicy {\n /**\n * Name of the entry, in keys and logs: letters, digits, `.`, `_` and\n * `-`.\n */\n name: string\n /**\n * Key of the arguments: a string, a number, or a value compared like the\n * arguments of a memo. Defaults to the argument (or the arguments).\n */\n key?: ((...args: A) => unknown) | undefined\n /**\n * Validates cached values when they are read (entries from an older\n * version of the app) and written. JSON turns dates into strings: use\n * `z.coerce.date()`.\n */\n schema?: CacheSchema<T> | undefined\n /**\n * Version of the values, in the key: increase it when their shape\n * changes, to ignore the values written before. Defaults to 1.\n */\n version?: number | undefined\n /**\n * Tags of the arguments: `invalidateTags()` expires every value with one\n * of them.\n */\n tags?: ((...args: A) => readonly string[]) | undefined\n}\n\n/** A policy in milliseconds; undefined when not set. */\ntype Policy = { [K in keyof CachePolicy]: number | undefined }\n\ninterface EntryConfig {\n name: string\n version: number\n key: ((...args: readonly unknown[]) => unknown) | undefined\n schema: CacheSchema<unknown> | undefined\n tags: ((...args: readonly unknown[]) => readonly string[]) | undefined\n policy: Policy\n}\n\nconst entries = new WeakMap<object, EntryConfig>()\n\nconst NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/\nconst POLICY = [\n 'ttl',\n 'grace',\n 'graceBackoff',\n 'staleWhileRevalidate',\n 'timeout',\n 'hardTimeout',\n 'lock',\n] as const\nconst ENTRY_OPTIONS = new Set([\n 'name',\n 'key',\n 'schema',\n 'version',\n 'tags',\n ...POLICY,\n])\n\n/** Reads the durations of a policy (`ttl` and timeouts at least 1 ms). */\nconst policyOf = (label: string, values: Record<string, unknown>): Policy => {\n const policy: Policy = {}\n for (const option of POLICY) {\n const value = values[option]\n if (value === undefined) continue\n policy[option] =\n option === 'ttl' ||\n option === 'timeout' ||\n option === 'hardTimeout' ||\n option === 'lock'\n ? positive(`${label}: ${option}`, value)\n : parseDuration(value, `${label}: ${option}`)\n }\n return policy\n}\n\n/**\n * Defines a kind of cached value: its key, its lifetime, and optionally\n * its schema.\n *\n * ```ts\n * export const userCache = defineCacheEntry({\n * name: 'user',\n * key: (id: string) => id,\n * schema: userSchema,\n * ttl: '1h',\n * })\n * ```\n */\nexport function defineCacheEntry<A extends readonly unknown[], T>(\n options: CacheEntryOptions<A, T> & { schema: CacheSchema<T> }\n): CacheEntry<A, T>\nexport function defineCacheEntry<A extends readonly unknown[]>(\n options: CacheEntryOptions<A, never> & { schema?: undefined }\n): CacheEntry<A, Untyped>\nexport function defineCacheEntry<A extends readonly unknown[], T>(\n options: CacheEntryOptions<A, T>\n): CacheEntry<A, T>\nexport function defineCacheEntry(options: unknown): {\n readonly name: string\n readonly version: number\n} {\n const label = 'defineCacheEntry()'\n if (typeof options !== 'object' || options === null) {\n throw new TypeError(`${label}: expected an object of options.`)\n }\n const values = options as Record<string, unknown>\n const { name, key, schema, tags, version = 1 } = values\n if (typeof name !== 'string' || !NAME.test(name)) {\n throw new TypeError(\n `${label}: name must have 1 to 128 letters, digits, \".\", \"_\" or \"-\", starting with a letter or a digit.`\n )\n }\n const named = `defineCacheEntry(\"${name}\")`\n for (const option of Object.keys(values)) {\n if (!ENTRY_OPTIONS.has(option)) {\n throw new TypeError(`${named}: unknown option \"${option}\".`)\n }\n }\n if (key !== undefined && typeof key !== 'function') {\n throw new TypeError(`${named}: key must be a function.`)\n }\n if (\n schema !== undefined &&\n typeof (schema as Partial<CacheSchema<unknown>> | null)?.safeParseAsync !==\n 'function'\n ) {\n throw new TypeError(\n `${named}: schema must have a safeParseAsync() method, like a Zod schema.`\n )\n }\n if (!Number.isInteger(version) || (version as number) < 0) {\n throw new TypeError(`${named}: version must be a non-negative integer.`)\n }\n if (tags !== undefined && typeof tags !== 'function') {\n throw new TypeError(`${named}: tags must be a function.`)\n }\n\n const entry = Object.freeze({ name, version: version as number })\n entries.set(entry, {\n name,\n version: version as number,\n key: key as EntryConfig['key'],\n schema: schema as CacheSchema<unknown> | undefined,\n tags: tags as EntryConfig['tags'],\n policy: policyOf(named, values),\n })\n return entry\n}\n\nconst positive = (label: string, value: unknown) => {\n const ms = parseDuration(value, label)\n if (ms < 1) throw new TypeError(`${label} must be at least 1 millisecond.`)\n return ms\n}\n\n/** Turns values into strings for the store, and back. */\nexport interface CacheSerializer {\n serialize(value: unknown): string\n deserialize(text: string): unknown\n}\n\n/** Details of a store failure, for `onError`. */\nexport interface CacheErrorInfo {\n readonly operation: StoreOperation\n /** Key of the store. */\n readonly key: string\n /** Name of the entry. */\n readonly entry: string\n}\n\n/** An access to a value, for `onAccess`. */\nexport interface CacheAccess {\n /**\n * `hit`: fresh value; `miss`: loaded; `stale`: a value past its `ttl`\n * served, for the `reason`.\n */\n readonly type: 'hit' | 'miss' | 'stale'\n readonly reason?: 'revalidate' | 'error' | 'timeout' | 'backoff'\n /** Name of the entry. */\n readonly entry: string\n /** Key of the store. */\n readonly key: string\n}\n\n/** Context of a cache: the logger of a request, for instance. */\nexport interface CacheContext {\n logger?: CacheLogger | undefined\n}\n\n/** Options of `createCache()`: the policy is the default of the entries. */\nexport interface CacheOptions extends CachePolicy {\n store: CacheStore\n /** Prefix of every key, like the name of the app. */\n prefix?: string | undefined\n /** Defaults to JSON. */\n serializer?: CacheSerializer | undefined\n logger?: CacheLogger | undefined\n /**\n * Called for each store failure, besides the log: metrics, alerts. Its\n * own errors are logged and ignored.\n */\n onError?:\n | ((error: CacheStoreError, info: CacheErrorInfo) => void | Promise<void>)\n | undefined\n /**\n * When the store fails, behaves as a cache miss (reads) or ignores the\n * write, after logging it. Set false to throw a `CacheStoreError`.\n * Defaults to true.\n */\n failOpen?: boolean | undefined\n /** A store operation slower than this counts as a failure. */\n storeTimeout?: Duration | undefined\n /**\n * Called for each hit, miss and stale value served: metrics. Its errors\n * are logged and ignored.\n */\n onAccess?: ((event: CacheAccess) => void) | undefined\n}\n\n/** A cache, created by `createCache()`. */\nexport interface Cache {\n /**\n * The fresh value of the arguments, or undefined when missing, expired\n * or invalidated.\n */\n get<A extends readonly unknown[], T>(\n entry: CacheEntry<A, T>,\n ...args: A\n ): Promise<CacheValue<T> | undefined>\n /** Sets the value of the arguments (the last argument). */\n set<A extends readonly unknown[], T>(\n entry: CacheEntry<A, T>,\n ...argsAndValue: [...A, CacheValue<T>]\n ): Promise<void>\n /** Removes the value of the arguments. */\n delete<A extends readonly unknown[], T>(\n entry: CacheEntry<A, T>,\n ...args: A\n ): Promise<void>\n /**\n * The value of the arguments, loaded by the last argument when missing\n * and cached. Calls made while it loads share the load. Applies the\n * grace period, stale-while-revalidate and timeouts of the entry.\n *\n * A loader may return `undefined` (not found): it is not cached, and\n * `getOrSet` returns it.\n */\n getOrSet<A extends readonly unknown[], R>(\n entry: CacheEntry<A, Untyped>,\n ...argsAndLoader: [...A, () => R | Promise<R>]\n ): Promise<R>\n getOrSet<A extends readonly unknown[], T, R extends T | undefined = T>(\n entry: CacheEntry<A, T>,\n ...argsAndLoader: [...A, () => R | Promise<R>]\n ): Promise<[R] extends [T] ? T : T | undefined>\n /**\n * Expires every value with one of the tags (they can still be served by\n * the grace period when the loader fails).\n */\n invalidateTags(tags: readonly string[]): Promise<void>\n /** The same cache, logging with another logger (`ctx.cache` is one). */\n withContext(context?: CacheContext): Cache\n}\n\nconst CACHE_OPTIONS = new Set([\n 'store',\n 'prefix',\n 'ttl',\n 'serializer',\n 'logger',\n 'onError',\n 'failOpen',\n 'storeTimeout',\n 'onAccess',\n ...POLICY,\n])\n\nconst jsonSerializer: CacheSerializer = {\n serialize: (value) => JSON.stringify(value),\n deserialize: (text) => JSON.parse(text) as unknown,\n}\n\n/**\n * Stored form of a value: `{ v, c (created), e (expires), d (data),\n * t (token of each tag) }`.\n */\ninterface StoredEntry {\n v: 1\n c: number\n e: number\n d: string\n t?: Record<string, string>\n}\n\nconst parseStored = (text: string): StoredEntry | undefined => {\n try {\n const stored = JSON.parse(text) as Partial<StoredEntry> | null\n if (\n stored?.v === 1 &&\n typeof stored.c === 'number' &&\n typeof stored.e === 'number' &&\n typeof stored.d === 'string' &&\n (stored.t === undefined ||\n (typeof stored.t === 'object' && stored.t !== null))\n ) {\n return stored as StoredEntry\n }\n } catch {\n // Not an entry of this cache.\n }\n return undefined\n}\n\nconst encoder = new TextEncoder()\n\n// Tokens of tags outlive the values that use them: 30 days at least.\nconst TAG_LIFETIME = 30 * 86_400_000\n// While another instance holds the lock, the value is read again so often.\nconst LOCK_POLL = 50\n\n/** SHA-256 of a text, in hexadecimal. */\nconst sha256 = async (text: string) => {\n const digest = await crypto.subtle.digest('SHA-256', encoder.encode(text))\n return [...new Uint8Array(digest)]\n .map((byte) => byte.toString(16).padStart(2, '0'))\n .join('')\n}\n\n/**\n * Creates a cache on a store. Reads and writes fail open by default: a\n * store failure is logged (and passed to `onError`), and the app keeps\n * working without cache.\n *\n * ```ts\n * const cache = createCache({ store: createMemoryStore(), prefix: 'app', ttl: '10m' })\n * const user = await cache.getOrSet(userCache, id, () => db.users.findById(id))\n * ```\n */\nexport const createCache = (options: CacheOptions): Cache => {\n const label = 'createCache()'\n if (typeof options !== 'object' || options === null) {\n throw new TypeError(`${label}: expected an object of options.`)\n }\n for (const key of Object.keys(options)) {\n if (!CACHE_OPTIONS.has(key)) {\n throw new TypeError(`${label}: unknown option \"${key}\".`)\n }\n }\n const {\n store,\n prefix,\n serializer = jsonSerializer,\n onError,\n onAccess,\n failOpen = true,\n } = options\n if (!isStore(store)) {\n throw new TypeError(\n `${label}: store must be a cache store (createMemoryStore(), createRedisStore()…).`\n )\n }\n if (prefix !== undefined && (typeof prefix !== 'string' || prefix === '')) {\n throw new TypeError(`${label}: prefix must be a non-empty string.`)\n }\n const defaults = policyOf(\n label,\n options as unknown as Record<string, unknown>\n )\n if (\n typeof serializer?.serialize !== 'function' ||\n typeof serializer.deserialize !== 'function'\n ) {\n throw new TypeError(\n `${label}: serializer must have serialize() and deserialize() methods.`\n )\n }\n if (onError !== undefined && typeof onError !== 'function') {\n throw new TypeError(`${label}: onError must be a function.`)\n }\n if (onAccess !== undefined && typeof onAccess !== 'function') {\n throw new TypeError(`${label}: onAccess must be a function.`)\n }\n if (typeof failOpen !== 'boolean') {\n throw new TypeError(`${label}: failOpen must be a boolean.`)\n }\n const storeTimeout =\n options.storeTimeout === undefined\n ? undefined\n : positive(`${label}: storeTimeout`, options.storeTimeout)\n const rootLogger =\n options.logger === undefined\n ? silentLogger\n : checkLogger(label, options.logger)\n\n // Shared by every logger of the cache: loads in progress, and the end of\n // the backoff of the keys whose loader failed.\n const loading = new Map<string, Promise<unknown>>()\n const backoffs = new Map<string, number>()\n\n const configOf = (method: string, entry: unknown) => {\n const config =\n typeof entry === 'object' && entry !== null\n ? entries.get(entry)\n : undefined\n if (!config) {\n throw new TypeError(\n `cache.${method}(): expected an entry created by defineCacheEntry().`\n )\n }\n return config\n }\n\n /** Policy of an entry, with the defaults of the cache. */\n const policyFor = (config: EntryConfig) => {\n const value = (option: keyof CachePolicy) =>\n config.policy[option] ?? defaults[option]\n return {\n ttl: value('ttl') ?? 3_600_000,\n grace: value('grace') ?? 0,\n graceBackoff: value('graceBackoff') ?? 10_000,\n staleWhileRevalidate: value('staleWhileRevalidate') ?? 0,\n timeout: value('timeout'),\n hardTimeout: value('hardTimeout'),\n lock: value('lock'),\n }\n }\n\n /** A key of the store, hashed when longer than the store accepts. */\n const fit = async (base: string, part: string) => {\n const key = `${base}:${part}`\n return encoder.encode(key).length > (store.maxKeyLength ?? Infinity)\n ? `${base}:#${await sha256(part)}`\n : key\n }\n\n /** Key of the store for the arguments of an entry. */\n const keyFor = async (config: EntryConfig, args: readonly unknown[]) => {\n const raw = config.key\n ? config.key(...args)\n : args.length === 1\n ? args[0]\n : args\n const base = [prefix, config.name, `v${config.version}`]\n .filter((item) => item !== undefined)\n .join(':')\n if (!config.key && args.length === 0) return base\n return fit(\n base,\n typeof raw === 'string' ? raw : keyOf(`Cache entry \"${config.name}\"`, raw)\n )\n }\n\n /** Checks the tags returned by an entry, or given to invalidate. */\n const checkTags = (label: string, tags: unknown) => {\n if (\n !Array.isArray(tags) ||\n !tags.every((tag) => typeof tag === 'string' && tag !== '')\n ) {\n throw new TypeError(\n `${label}: tags must be an array of non-empty strings.`\n )\n }\n return [...new Set(tags as string[])]\n }\n\n /** Keys of the tokens of tags (`_` cannot start the name of an entry). */\n const tagKeysOf = (tags: readonly string[]) => {\n const base = prefix === undefined ? '_tag' : `${prefix}:_tag`\n return Promise.all(\n tags.map(async (tag) => ({ tag, key: await fit(base, tag) }))\n )\n }\n\n const create = (logger: CacheLogger): Cache => {\n const describe = (error: unknown) =>\n error instanceof Error ? error.message : String(error)\n\n /** Runs a store operation: failures are logged, and fail open. */\n const guard = async <R>(\n operation: StoreOperation,\n key: string,\n entry: string,\n run: () => Promise<R>,\n fallback: R\n ): Promise<R> => {\n let timer: ReturnType<typeof setTimeout> | undefined\n try {\n const running = run()\n return storeTimeout === undefined\n ? await running\n : await Promise.race([\n running,\n new Promise<never>((_, reject) => {\n timer = setTimeout(\n () =>\n reject(\n new Error(`no answer within ${storeTimeout} milliseconds`)\n ),\n storeTimeout\n )\n }),\n ])\n } catch (cause) {\n const error = new CacheStoreError(store.name, operation, key, {\n cause,\n })\n const data = {\n store: store.name,\n operation,\n key,\n entry,\n error: error.message,\n }\n // A failed deletion or invalidation may leave outdated values.\n if (operation === 'delete' || operation === 'invalidate') {\n logger.error(data, 'Cache store failed')\n } else {\n logger.warn(data, 'Cache store failed')\n }\n if (onError) {\n try {\n await onError(error, { operation, key, entry })\n } catch (hookError) {\n logger.error(\n { error: describe(hookError) },\n 'Cache onError hook failed'\n )\n }\n }\n if (!failOpen) throw error\n return fallback\n } finally {\n clearTimeout(timer)\n }\n }\n\n /** Reports an event to the log (debug) and `onAccess`. */\n const report = (event: CacheAccess) => {\n const { type, reason, entry, key } = event\n logger.debug?.(\n { entry, key, ...(reason ? { reason } : {}) },\n type === 'hit'\n ? 'Cache hit'\n : type === 'miss'\n ? 'Cache miss'\n : 'Cache served a stale value'\n )\n if (!onAccess) return\n try {\n onAccess(event)\n } catch (error) {\n logger.error({ error: describe(error) }, 'Cache onAccess hook failed')\n }\n }\n\n /**\n * Current tokens of tags (undefined for a missing tag), or undefined\n * when the store cannot read them.\n */\n const tokensOf = async (\n entry: string,\n tagKeys: readonly { tag: string; key: string }[]\n ) => {\n const keys = tagKeys.map(({ key }) => key)\n const values = await guard<(string | undefined)[] | undefined>(\n 'get',\n keys.join(' '),\n entry,\n () =>\n store.getMany\n ? store.getMany(keys)\n : Promise.all(keys.map((key) => store.get(key))),\n undefined\n )\n return values?.map((token, index) => ({\n tag: (tagKeys[index] as { tag: string }).tag,\n token,\n }))\n }\n\n /** Deserializes and validates a value: `{ value }`, or undefined. */\n const decode = async (config: EntryConfig, key: string, data: string) => {\n let value: unknown\n try {\n value = serializer.deserialize(data)\n } catch (error) {\n logger.warn(\n { entry: config.name, key, error: describe(error) },\n 'Cache value cannot be deserialized'\n )\n return undefined\n }\n if (!config.schema) return { value }\n const result = await config.schema.safeParseAsync(value)\n return result.success ? { value: result.data } : undefined\n }\n\n /**\n * Reads a stored value: its value, expiry, and whether its tags are\n * still valid. Undefined when missing, invalid, or past every stale\n * window.\n */\n const lookup = async (\n config: EntryConfig,\n policy: ReturnType<typeof policyFor>,\n key: string\n ) => {\n const text = await guard(\n 'get',\n key,\n config.name,\n () => store.get(key),\n undefined\n )\n if (text === undefined) return undefined\n const stored = parseStored(text)\n const now = Date.now()\n if (\n !stored ||\n stored.e + Math.max(policy.grace, policy.staleWhileRevalidate) <= now\n ) {\n return undefined\n }\n const decoded = await decode(config, key, stored.d)\n if (!decoded) return undefined\n const tags = Object.entries(stored.t ?? {})\n let tagsValid = true\n if (tags.length > 0) {\n const current = await tokensOf(\n config.name,\n await tagKeysOf(tags.map(([tag]) => tag))\n )\n // Unreadable tokens: the value may have been invalidated.\n tagsValid =\n current?.every(({ tag, token }) => token === stored.t?.[tag]) ?? false\n }\n return {\n value: decoded.value,\n expiresAt: stored.e,\n fresh: stored.e > now && tagsValid,\n tagsValid,\n }\n }\n\n /**\n * Writes a value, after checking that it reads back: returns the value\n * as read later, or undefined when it cannot be cached.\n */\n const write = async (\n config: EntryConfig,\n key: string,\n args: readonly unknown[],\n value: unknown\n ): Promise<{ value: unknown } | undefined> => {\n if (value === undefined) return undefined\n let data: string\n try {\n data = serializer.serialize(value)\n if (typeof data !== 'string') throw new TypeError('not a string')\n } catch (error) {\n logger.warn(\n { entry: config.name, key, error: describe(error) },\n 'Cache value cannot be serialized'\n )\n return undefined\n }\n const checked = config.schema\n ? await decode(config, key, data)\n : { value }\n if (!checked) {\n logger.warn(\n { entry: config.name, key },\n 'Cache value does not match the schema of the entry: not cached'\n )\n return undefined\n }\n const policy = policyFor(config)\n const lifetime =\n policy.ttl + Math.max(policy.grace, policy.staleWhileRevalidate)\n const now = Date.now()\n const stored: StoredEntry = {\n v: 1,\n c: now,\n e: now + policy.ttl,\n d: data,\n }\n if (config.tags) {\n const tags = checkTags(\n `defineCacheEntry(\"${config.name}\")`,\n config.tags(...args)\n )\n if (tags.length > 0) {\n const tagKeys = await tagKeysOf(tags)\n const current = await tokensOf(config.name, tagKeys)\n // Unreadable tokens: creating new ones would invalidate every\n // value of these tags, so the value is not cached.\n if (!current) return checked\n stored.t = {}\n for (const [index, { tag, token }] of current.entries()) {\n if (token !== undefined) {\n stored.t[tag] = token\n continue\n }\n // First value with this tag: its token is created.\n const created = crypto.randomUUID()\n const tagKey = (tagKeys[index] as { key: string }).key\n await guard(\n 'set',\n tagKey,\n config.name,\n () =>\n store.set(tagKey, created, Math.max(lifetime, TAG_LIFETIME)),\n undefined\n )\n stored.t[tag] = created\n }\n }\n }\n await guard(\n 'set',\n key,\n config.name,\n () => store.set(key, JSON.stringify(stored), lifetime),\n undefined\n )\n return checked\n }\n\n /**\n * Loads and writes a value, once per key at a time, and once for every\n * instance with the `lock` option.\n */\n const load = (\n config: EntryConfig,\n key: string,\n args: readonly unknown[],\n loader: () => unknown\n ) => {\n const running = loading.get(key)\n if (running) return running\n const loaded = (async () => {\n const policy = policyFor(config)\n const lockKey = `${key}:lock`\n let token: string | undefined\n if (policy.lock !== undefined && store.lock) {\n const lock = store.lock.bind(store)\n const ttl = policy.lock\n // null: the store failed, load without lock.\n const taken = await guard<string | undefined | null>(\n 'lock',\n lockKey,\n config.name,\n () => lock(lockKey, ttl),\n null\n )\n if (taken === undefined) {\n // Another instance loads it: wait for its value.\n const until = Date.now() + ttl\n while (Date.now() < until) {\n await new Promise((resolve) => setTimeout(resolve, LOCK_POLL))\n const found = await lookup(config, policy, key)\n if (found?.fresh) return found.value\n }\n logger.warn(\n { entry: config.name, key, lock: ttl },\n 'Cache lock not released in time: loading anyway'\n )\n } else if (taken !== null) {\n token = taken\n }\n }\n try {\n const value: unknown = await loader()\n const written = await write(config, key, args, value)\n backoffs.delete(key)\n return written ? written.value : value\n } finally {\n if (token !== undefined && store.unlock) {\n const unlock = store.unlock.bind(store)\n const held = token\n await guard(\n 'unlock',\n lockKey,\n config.name,\n () => unlock(lockKey, held),\n undefined\n )\n }\n }\n })()\n loading.set(key, loaded)\n void loaded.then(\n () => loading.delete(key),\n () => loading.delete(key)\n )\n return loaded\n }\n\n /** Logs the failure of a load that nobody waits for anymore. */\n const inBackground = (\n config: EntryConfig,\n key: string,\n loaded: Promise<unknown>\n ) => {\n loaded.catch((error: unknown) => {\n logger.warn(\n { entry: config.name, key, error: describe(error) },\n 'Cache refresh failed'\n )\n })\n }\n\n /** Rejects after `ms` (never when undefined), until cleared. */\n const deadline = (ms: number | undefined, error: () => unknown) => {\n let timer: ReturnType<typeof setTimeout> | undefined\n const promise = new Promise<never>((_, reject) => {\n if (ms !== undefined) timer = setTimeout(() => reject(error()), ms)\n })\n return { promise, clear: () => clearTimeout(timer) }\n }\n\n const cache: Cache = {\n get: (async (entry: unknown, ...args: unknown[]) => {\n const config = configOf('get', entry)\n const key = await keyFor(config, args)\n const found = await lookup(config, policyFor(config), key)\n report({ type: found?.fresh ? 'hit' : 'miss', entry: config.name, key })\n return found?.fresh ? found.value : undefined\n }) as Cache['get'],\n\n set: (async (entry: unknown, ...argsAndValue: unknown[]) => {\n const config = configOf('set', entry)\n const args = argsAndValue.slice(0, -1)\n const key = await keyFor(config, args)\n await write(config, key, args, argsAndValue.at(-1))\n }) as Cache['set'],\n\n delete: (async (entry: unknown, ...args: unknown[]) => {\n const config = configOf('delete', entry)\n const key = await keyFor(config, args)\n await guard(\n 'delete',\n key,\n config.name,\n () => store.delete(key),\n undefined\n )\n }) as Cache['delete'],\n\n getOrSet: (async (entry: unknown, ...argsAndLoader: unknown[]) => {\n const config = configOf('getOrSet', entry)\n const loader = argsAndLoader.at(-1)\n if (typeof loader !== 'function') {\n throw new TypeError(\n 'cache.getOrSet(): the last argument must be the function that loads the value.'\n )\n }\n const args = argsAndLoader.slice(0, -1)\n const key = await keyFor(config, args)\n const policy = policyFor(config)\n const found = await lookup(config, policy, key)\n const event = { entry: config.name, key }\n if (found?.fresh) {\n report({ type: 'hit', ...event })\n return found.value\n }\n const now = Date.now()\n\n // Stale-while-revalidate: serve, and refresh in the background.\n if (\n found?.tagsValid &&\n found.expiresAt + policy.staleWhileRevalidate > now\n ) {\n report({ type: 'stale', reason: 'revalidate', ...event })\n inBackground(\n config,\n key,\n load(config, key, args, loader as () => unknown)\n )\n return found.value\n }\n\n // Grace period: stale value served if the loader fails. Without it,\n // an invalidated value is never served.\n const stale =\n found && policy.grace > 0 && found.expiresAt + policy.grace > now\n ? found\n : undefined\n if (stale && (backoffs.get(key) ?? 0) > now) {\n report({ type: 'stale', reason: 'backoff', ...event })\n return stale.value\n }\n\n report({ type: 'miss', ...event })\n const loaded = load(config, key, args, loader as () => unknown)\n const slow = Symbol('slow')\n const soft = deadline(stale ? policy.timeout : undefined, () => slow)\n const hard = deadline(\n policy.hardTimeout,\n () =>\n new CacheTimeoutError(\n config.name,\n key,\n policy.hardTimeout as number\n )\n )\n try {\n return await Promise.race([loaded, soft.promise, hard.promise])\n } catch (error) {\n // The load goes on, and caches its value.\n if (error === slow || error instanceof CacheTimeoutError) {\n inBackground(config, key, loaded)\n }\n if (!stale) throw error\n if (error === slow) {\n logger.warn(\n { ...event, timeout: policy.timeout },\n 'Cache served a stale value: the loader is slow'\n )\n report({ type: 'stale', reason: 'timeout', ...event })\n return stale.value\n }\n backoffs.set(key, Date.now() + policy.graceBackoff)\n logger.warn(\n { ...event, error: describe(error) },\n 'Cache served a stale value: the loader failed'\n )\n report({ type: 'stale', reason: 'error', ...event })\n return stale.value\n } finally {\n soft.clear()\n hard.clear()\n }\n }) as Cache['getOrSet'],\n\n invalidateTags: async (tags) => {\n const tagKeys = await tagKeysOf(\n checkTags('cache.invalidateTags()', tags)\n )\n await Promise.all(\n tagKeys.map(({ tag, key }) =>\n guard(\n 'invalidate',\n key,\n `tag:${tag}`,\n () => store.set(key, crypto.randomUUID(), TAG_LIFETIME),\n undefined\n )\n )\n )\n logger.debug?.({ tags }, 'Cache tags invalidated')\n },\n\n withContext: (context: CacheContext = {}) => {\n if (typeof context !== 'object' || context === null) {\n throw new TypeError('withContext(): expected an object.')\n }\n for (const key of Object.keys(context)) {\n if (key !== 'logger') {\n throw new TypeError(`withContext(): unknown option \"${key}\".`)\n }\n }\n return create(\n context.logger === undefined\n ? logger\n : checkLogger('withContext()', context.logger)\n )\n },\n }\n return Object.freeze(cache)\n }\n\n return create(rootLogger)\n}\n","/**\n * Logger of the cache. A Dotpress or Pino logger fits; `cachePlugin` uses\n * the logger of the request.\n */\nexport interface CacheLogger {\n debug?(data: object, message: string): void\n info(data: object, message: string): void\n warn(data: object, message: string): void\n error(data: object, message: string): void\n}\n\nconst noop = () => undefined\n\n/** Logger that discards everything. */\nexport const silentLogger: CacheLogger = {\n debug: noop,\n info: noop,\n warn: noop,\n error: noop,\n}\n\n/** Checks a logger option. */\nexport const checkLogger = (label: string, logger: unknown) => {\n for (const level of ['info', 'warn', 'error'] as const) {\n if (\n typeof (logger as Record<string, unknown> | null)?.[level] !== 'function'\n ) {\n throw new TypeError(\n `${label}: logger must have info(), warn() and error() methods.`\n )\n }\n }\n return logger as CacheLogger\n}\n","/**\n * Where the cache keeps its entries: Redis, Upstash, Cloudflare KV,\n * memory… Values are strings, serialized by the cache.\n */\nexport interface CacheStore {\n /** Name of the store, in logs and errors. */\n readonly name: string\n /**\n * Longest key accepted, in bytes (UTF-8). Longer keys are hashed by the\n * cache. Undefined without limit.\n */\n readonly maxKeyLength?: number | undefined\n /** Value of a key, or undefined when it is missing or expired. */\n get(key: string): Promise<string | undefined>\n /**\n * Values of several keys, in their order: one round trip for the tags of\n * an entry. Optional: the cache calls `get` for each key without it.\n */\n getMany?(keys: readonly string[]): Promise<(string | undefined)[]>\n /** Sets a value that expires after `ttl` milliseconds (at least 1). */\n set(key: string, value: string, ttl: number): Promise<void>\n /** Removes a key; missing keys are ignored. */\n delete(key: string): Promise<void>\n /**\n * Takes a lock for `ttl` milliseconds: returns its token, or undefined\n * when another holder has it. Optional: the `lock` option of the cache\n * needs it.\n */\n lock?(key: string, ttl: number): Promise<string | undefined>\n /** Releases a lock, if the token still holds it. */\n unlock?(key: string, token: string): Promise<void>\n}\n\n/** Checks a store option. */\nexport const isStore = (value: unknown): value is CacheStore => {\n const store = value as Partial<CacheStore> | null\n return (\n typeof store?.name === 'string' &&\n typeof store.get === 'function' &&\n typeof store.set === 'function' &&\n typeof store.delete === 'function'\n )\n}\n\n/** Options of `createMemoryStore()`. */\nexport interface MemoryStoreOptions {\n /**\n * Entries kept at most: the least recently used ones are removed\n * first. Defaults to 10,000.\n */\n maxEntries?: number | undefined\n}\n\n/** Store in the memory of the process: for one instance, and for tests. */\nexport interface MemoryStore extends CacheStore {\n /** Number of entries, expired ones included until they are read. */\n readonly size: number\n /** Removes every entry. */\n clear(): void\n}\n\n/**\n * Creates a store in the memory of the process, least recently used\n * entries removed first. Each instance has its own: use a shared store\n * (Redis…) for several instances.\n */\nexport const createMemoryStore = (\n options: MemoryStoreOptions = {}\n): MemoryStore => {\n const label = 'createMemoryStore()'\n if (typeof options !== 'object' || options === null) {\n throw new TypeError(`${label}: expected an object of options.`)\n }\n for (const key of Object.keys(options)) {\n if (key !== 'maxEntries') {\n throw new TypeError(`${label}: unknown option \"${key}\".`)\n }\n }\n const { maxEntries = 10_000 } = options\n if (!Number.isInteger(maxEntries) || maxEntries < 1) {\n throw new TypeError(`${label}: maxEntries must be a positive integer.`)\n }\n\n // Insertion order of a Map: the first entry is the least recently used.\n const entries = new Map<string, { value: string; expiresAt: number }>()\n const locks = new Map<string, { token: string; expiresAt: number }>()\n\n const read = (key: string) => {\n const entry = entries.get(key)\n if (!entry) return undefined\n entries.delete(key)\n if (entry.expiresAt <= Date.now()) return undefined\n entries.set(key, entry)\n return entry.value\n }\n\n return Object.freeze({\n name: 'memory',\n get size() {\n return entries.size\n },\n get: async (key: string) => read(key),\n getMany: async (keys: readonly string[]) => keys.map(read),\n set: async (key: string, value: string, ttl: number) => {\n entries.delete(key)\n entries.set(key, { value, expiresAt: Date.now() + ttl })\n for (const oldest of entries.keys()) {\n if (entries.size <= maxEntries) break\n entries.delete(oldest)\n }\n },\n delete: async (key: string) => {\n entries.delete(key)\n },\n lock: async (key: string, ttl: number) => {\n const held = locks.get(key)\n if (held && held.expiresAt > Date.now()) return undefined\n const token = crypto.randomUUID()\n locks.set(key, { token, expiresAt: Date.now() + ttl })\n return token\n },\n unlock: async (key: string, token: string) => {\n if (locks.get(key)?.token === token) locks.delete(key)\n },\n clear: () => {\n entries.clear()\n locks.clear()\n },\n })\n}\n"],"mappings":"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","names":["entries","label"]}
@@ -0,0 +1,106 @@
1
+ /** Options shared by every memo. */
2
+ interface BaseMemoOptions {
3
+ /**
4
+ * Name of the memo, used in errors: letters, digits, `.`, `_`, `:` and
5
+ * `-`.
6
+ */
7
+ name: string;
8
+ }
9
+ /** Options of a memo that loads one value at a time. */
10
+ interface LoadMemoOptions<A extends readonly unknown[], T> extends BaseMemoOptions {
11
+ /** Loads the value of the arguments. */
12
+ load: (...args: A) => T | Promise<T>;
13
+ /**
14
+ * Key of the arguments. Defaults to the arguments themselves: strings,
15
+ * numbers, booleans, `null`, `undefined`, bigints, dates, arrays and
16
+ * plain objects.
17
+ */
18
+ key?: ((...args: A) => unknown) | undefined;
19
+ batch?: never;
20
+ }
21
+ /** Options of a memo that loads the values of several keys at once. */
22
+ interface BatchMemoOptions<K, R> extends BaseMemoOptions {
23
+ /**
24
+ * Loads the values of the keys requested in the same tick, without
25
+ * duplicates. Returns their values in the order of the keys (an `Error`
26
+ * fails its key only), or a `Map` by key (a missing key gives
27
+ * `undefined`).
28
+ */
29
+ batch: (keys: readonly K[]) => R | Promise<R>;
30
+ /** Key of a requested key, for keys that are objects. */
31
+ key?: ((key: K) => unknown) | undefined;
32
+ /** Keys per call of `batch`. Defaults to no limit. */
33
+ maxBatchSize?: number | undefined;
34
+ /**
35
+ * Schedules a batch. Defaults to a timer (`setTimeout(run, 0)`): the
36
+ * batch receives every key requested before it.
37
+ */
38
+ schedule?: ((run: () => void) => void) | undefined;
39
+ load?: never;
40
+ }
41
+ declare const memoTypes: unique symbol;
42
+ /** A memoized loader, created by `defineMemo()`. */
43
+ interface MemoDefinition<A extends readonly unknown[], T> {
44
+ readonly name: string;
45
+ /** Types only. */
46
+ readonly [memoTypes]?: {
47
+ args: A;
48
+ value: T;
49
+ };
50
+ }
51
+ /** Memoized values of a unit of work: a request, a message, a job. */
52
+ interface Memo {
53
+ /**
54
+ * Loads the value of the arguments once per memo: later calls, and
55
+ * calls made while it loads, share the result. Failures are shared by
56
+ * the calls in progress, not kept.
57
+ */
58
+ <A extends readonly unknown[], T>(memo: MemoDefinition<A, T>, ...args: A): Promise<T>;
59
+ /** Sets the value of the arguments, after an update for instance. */
60
+ set<A extends readonly unknown[], T>(memo: MemoDefinition<A, T>, ...argsAndValue: [...A, T]): void;
61
+ /** Forgets the value of the arguments: the next call loads it again. */
62
+ delete<A extends readonly unknown[], T>(memo: MemoDefinition<A, T>, ...args: A): void;
63
+ /** Forgets the values of a memo, or of every memo. */
64
+ clear<A extends readonly unknown[], T>(memo?: MemoDefinition<A, T>): void;
65
+ }
66
+ /**
67
+ * Defines a memoized loader: `memo(loadUser, id)` loads a user once per
68
+ * memo (request, message, job), and calls made while it loads share the
69
+ * same load (single flight). With `batch`, the keys requested in the same
70
+ * tick are loaded in one call.
71
+ *
72
+ * ```ts
73
+ * const loadUser = defineMemo({
74
+ * name: 'user',
75
+ * load: (id: string) => db.users.findById(id),
76
+ * })
77
+ *
78
+ * const loadOrder = defineMemo({
79
+ * name: 'order',
80
+ * batch: (ids: readonly string[]) => db.orders.findManyById(ids), // a Map by id
81
+ * })
82
+ * ```
83
+ */
84
+ declare function defineMemo<K, T>(options: BatchMemoOptions<K, ReadonlyMap<K, T>>): MemoDefinition<[key: K], T | undefined>;
85
+ declare function defineMemo<K, T>(options: BatchMemoOptions<K, readonly (T | Error)[]>): MemoDefinition<[key: K], T>;
86
+ declare function defineMemo<A extends readonly unknown[], T>(options: LoadMemoOptions<A, T>): MemoDefinition<A, T>;
87
+ /**
88
+ * Creates a memo: one per request, message or job. Plugins provide
89
+ * `ctx.memo`; create one yourself elsewhere.
90
+ *
91
+ * ```ts
92
+ * const memo = createMemo()
93
+ * const [ada, grace] = await Promise.all([memo(loadUser, '1'), memo(loadUser, '2')])
94
+ * ```
95
+ */
96
+ declare const createMemo: () => Memo;
97
+ /**
98
+ * Runs each request inside a scope for its memo, like `asyncLocalMemo` of
99
+ * `@dotpress/cache/als`.
100
+ */
101
+ interface MemoScope {
102
+ run<T>(memo: Memo, next: () => Promise<T>): Promise<T>;
103
+ }
104
+
105
+ export { createMemo as c, defineMemo as d };
106
+ export type { BatchMemoOptions as B, LoadMemoOptions as L, MemoScope as M, Memo as a, MemoDefinition as b };
package/dist/plugin.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as _dotpress_core from '@dotpress/core';
2
- import { Cache, Memo } from './index.js';
3
- import './duration-2mZnG0_i.js';
2
+ import { Cache } from './index.js';
3
+ import { M as MemoScope } from './memo-BOS8USBl.js';
4
+ import { Memo } from './index.js';
4
5
  import './store-D0BfLqyM.js';
5
6
 
6
7
  declare module '@dotpress/core' {
@@ -11,13 +12,6 @@ declare module '@dotpress/core' {
11
12
  memo: Memo;
12
13
  }
13
14
  }
14
- /**
15
- * Runs each request inside a scope for its memo, like `asyncLocalMemo` of
16
- * `@dotpress/cache/als`.
17
- */
18
- interface MemoScope {
19
- run<T>(memo: Memo, next: () => Promise<T>): Promise<T>;
20
- }
21
15
  /** Options of `cachePlugin()`. */
22
16
  interface CachePluginOptions {
23
17
  /** Provides `ctx.cache`. */
@@ -47,5 +41,5 @@ declare const cachePlugin: (options?: CachePluginOptions) => {
47
41
  readonly setup: (api: _dotpress_core.PluginAPI) => void;
48
42
  };
49
43
 
50
- export { cachePlugin };
51
- export type { CachePluginOptions, MemoScope };
44
+ export { MemoScope, cachePlugin };
45
+ export type { CachePluginOptions };
package/dist/plugin.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createMemo
3
- } from "./chunk-6ZNJJARH.js";
3
+ } from "./chunk-NAI2ODC5.js";
4
4
 
5
5
  // src/plugin.ts
6
6
  import { declareContextKey, definePlugin } from "@dotpress/core";
@@ -17,7 +17,7 @@ var cachePlugin = (options = {}) => {
17
17
  }
18
18
  }
19
19
  const { cache, memoScope } = options;
20
- if (cache !== void 0 && typeof cache?.withLogger !== "function") {
20
+ if (cache !== void 0 && typeof cache?.withContext !== "function") {
21
21
  throw new TypeError(`${label}: cache must be created by createCache().`);
22
22
  }
23
23
  if (memoScope !== void 0 && typeof memoScope?.run !== "function") {
@@ -35,7 +35,7 @@ var cachePlugin = (options = {}) => {
35
35
  '"ctx.cache" is used, but cachePlugin() has no cache: pass cachePlugin({ cache }).'
36
36
  );
37
37
  }
38
- return cache.withLogger(ctx.logger);
38
+ return cache.withContext({ logger: ctx.logger });
39
39
  });
40
40
  if (memoScope) {
41
41
  api.hooks.wrapRequest((ctx, next) => memoScope.run(ctx.memo, next));
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import { declareContextKey, definePlugin } from '@dotpress/core'\nimport type { Cache } from './cache.js'\nimport { createMemo, type Memo } from './memo.js'\n\ndeclare module '@dotpress/core' {\n interface RequestContext {\n /** Cache (`cachePlugin`), logging with the request logger. */\n cache: Cache\n /** Memo of the request (`cachePlugin`): a new one for each request. */\n memo: Memo\n }\n}\n\n// Accessing ctx.cache or ctx.memo without the plugin throws an explicit\n// error.\ndeclareContextKey('cache', 'cachePlugin')\ndeclareContextKey('memo', 'cachePlugin')\n\n/**\n * Runs each request inside a scope for its memo, like `asyncLocalMemo` of\n * `@dotpress/cache/als`.\n */\nexport interface MemoScope {\n run<T>(memo: Memo, next: () => Promise<T>): Promise<T>\n}\n\n/** Options of `cachePlugin()`. */\nexport interface CachePluginOptions {\n /** Provides `ctx.cache`. */\n cache?: Cache | undefined\n /**\n * Runs each request inside the scope of its memo, so that memoized\n * loaders work without `ctx` (`asyncLocalMemo` of `@dotpress/cache/als`).\n */\n memoScope?: MemoScope | undefined\n}\n\n/**\n * Provides `ctx.memo` (a new memo for each request) and, with a cache,\n * `ctx.cache` (logging with the request logger).\n *\n * ```ts\n * await createApp({ adapter, plugins: [cachePlugin({ cache })] })\n *\n * defineRoute({\n * path: '/me',\n * handler: ({ memo, cache, auth }) =>\n * memo(loadUser, auth.userId), // or cache.getOrSet(userCache, …)\n * })\n * ```\n */\nexport const cachePlugin = (options: CachePluginOptions = {}) => {\n const label = 'cachePlugin()'\n if (typeof options !== 'object' || options === null) {\n throw new TypeError(`${label}: expected an object of options.`)\n }\n for (const key of Object.keys(options)) {\n if (key !== 'cache' && key !== 'memoScope') {\n throw new TypeError(`${label}: unknown option \"${key}\".`)\n }\n }\n const { cache, memoScope } = options\n if (cache !== undefined && typeof cache?.withLogger !== 'function') {\n throw new TypeError(`${label}: cache must be created by createCache().`)\n }\n if (memoScope !== undefined && typeof memoScope?.run !== 'function') {\n throw new TypeError(\n `${label}: memoScope must have a run() method, like asyncLocalMemo.`\n )\n }\n\n return definePlugin({\n name: 'cache',\n setup(api) {\n api.decorateContext('memo', () => createMemo())\n api.decorateContext('cache', (ctx) => {\n if (!cache) {\n throw new Error(\n '\"ctx.cache\" is used, but cachePlugin() has no cache: pass cachePlugin({ cache }).'\n )\n }\n return cache.withLogger(ctx.logger)\n })\n if (memoScope) {\n api.hooks.wrapRequest((ctx, next) => memoScope.run(ctx.memo, next))\n }\n },\n })\n}\n"],"mappings":";;;;;AAAA,SAAS,mBAAmB,oBAAoB;AAehD,kBAAkB,SAAS,aAAa;AACxC,kBAAkB,QAAQ,aAAa;AAmChC,IAAM,cAAc,CAAC,UAA8B,CAAC,MAAM;AAC/D,QAAM,QAAQ;AACd,MAAI,OAAO,YAAY,YAAY,YAAY,MAAM;AACnD,UAAM,IAAI,UAAU,GAAG,KAAK,kCAAkC;AAAA,EAChE;AACA,aAAW,OAAO,OAAO,KAAK,OAAO,GAAG;AACtC,QAAI,QAAQ,WAAW,QAAQ,aAAa;AAC1C,YAAM,IAAI,UAAU,GAAG,KAAK,qBAAqB,GAAG,IAAI;AAAA,IAC1D;AAAA,EACF;AACA,QAAM,EAAE,OAAO,UAAU,IAAI;AAC7B,MAAI,UAAU,UAAa,OAAO,OAAO,eAAe,YAAY;AAClE,UAAM,IAAI,UAAU,GAAG,KAAK,2CAA2C;AAAA,EACzE;AACA,MAAI,cAAc,UAAa,OAAO,WAAW,QAAQ,YAAY;AACnE,UAAM,IAAI;AAAA,MACR,GAAG,KAAK;AAAA,IACV;AAAA,EACF;AAEA,SAAO,aAAa;AAAA,IAClB,MAAM;AAAA,IACN,MAAM,KAAK;AACT,UAAI,gBAAgB,QAAQ,MAAM,WAAW,CAAC;AAC9C,UAAI,gBAAgB,SAAS,CAAC,QAAQ;AACpC,YAAI,CAAC,OAAO;AACV,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AACA,eAAO,MAAM,WAAW,IAAI,MAAM;AAAA,MACpC,CAAC;AACD,UAAI,WAAW;AACb,YAAI,MAAM,YAAY,CAAC,KAAK,SAAS,UAAU,IAAI,IAAI,MAAM,IAAI,CAAC;AAAA,MACpE;AAAA,IACF;AAAA,EACF,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import { declareContextKey, definePlugin } from '@dotpress/core'\nimport type { Cache } from './cache.js'\nimport { createMemo, type Memo, type MemoScope } from './memo.js'\n\nexport type { MemoScope } from './memo.js'\n\ndeclare module '@dotpress/core' {\n interface RequestContext {\n /** Cache (`cachePlugin`), logging with the request logger. */\n cache: Cache\n /** Memo of the request (`cachePlugin`): a new one for each request. */\n memo: Memo\n }\n}\n\n// Accessing ctx.cache or ctx.memo without the plugin throws an explicit\n// error.\ndeclareContextKey('cache', 'cachePlugin')\ndeclareContextKey('memo', 'cachePlugin')\n\n/** Options of `cachePlugin()`. */\nexport interface CachePluginOptions {\n /** Provides `ctx.cache`. */\n cache?: Cache | undefined\n /**\n * Runs each request inside the scope of its memo, so that memoized\n * loaders work without `ctx` (`asyncLocalMemo` of `@dotpress/cache/als`).\n */\n memoScope?: MemoScope | undefined\n}\n\n/**\n * Provides `ctx.memo` (a new memo for each request) and, with a cache,\n * `ctx.cache` (logging with the request logger).\n *\n * ```ts\n * await createApp({ adapter, plugins: [cachePlugin({ cache })] })\n *\n * defineRoute({\n * path: '/me',\n * handler: ({ memo, cache, auth }) =>\n * memo(loadUser, auth.userId), // or cache.getOrSet(userCache, …)\n * })\n * ```\n */\nexport const cachePlugin = (options: CachePluginOptions = {}) => {\n const label = 'cachePlugin()'\n if (typeof options !== 'object' || options === null) {\n throw new TypeError(`${label}: expected an object of options.`)\n }\n for (const key of Object.keys(options)) {\n if (key !== 'cache' && key !== 'memoScope') {\n throw new TypeError(`${label}: unknown option \"${key}\".`)\n }\n }\n const { cache, memoScope } = options\n if (cache !== undefined && typeof cache?.withContext !== 'function') {\n throw new TypeError(`${label}: cache must be created by createCache().`)\n }\n if (memoScope !== undefined && typeof memoScope?.run !== 'function') {\n throw new TypeError(\n `${label}: memoScope must have a run() method, like asyncLocalMemo.`\n )\n }\n\n return definePlugin({\n name: 'cache',\n setup(api) {\n api.decorateContext('memo', () => createMemo())\n api.decorateContext('cache', (ctx) => {\n if (!cache) {\n throw new Error(\n '\"ctx.cache\" is used, but cachePlugin() has no cache: pass cachePlugin({ cache }).'\n )\n }\n return cache.withContext({ logger: ctx.logger })\n })\n if (memoScope) {\n api.hooks.wrapRequest((ctx, next) => memoScope.run(ctx.memo, next))\n }\n },\n })\n}\n"],"mappings":";;;;;AAAA,SAAS,mBAAmB,oBAAoB;AAiBhD,kBAAkB,SAAS,aAAa;AACxC,kBAAkB,QAAQ,aAAa;AA2BhC,IAAM,cAAc,CAAC,UAA8B,CAAC,MAAM;AAC/D,QAAM,QAAQ;AACd,MAAI,OAAO,YAAY,YAAY,YAAY,MAAM;AACnD,UAAM,IAAI,UAAU,GAAG,KAAK,kCAAkC;AAAA,EAChE;AACA,aAAW,OAAO,OAAO,KAAK,OAAO,GAAG;AACtC,QAAI,QAAQ,WAAW,QAAQ,aAAa;AAC1C,YAAM,IAAI,UAAU,GAAG,KAAK,qBAAqB,GAAG,IAAI;AAAA,IAC1D;AAAA,EACF;AACA,QAAM,EAAE,OAAO,UAAU,IAAI;AAC7B,MAAI,UAAU,UAAa,OAAO,OAAO,gBAAgB,YAAY;AACnE,UAAM,IAAI,UAAU,GAAG,KAAK,2CAA2C;AAAA,EACzE;AACA,MAAI,cAAc,UAAa,OAAO,WAAW,QAAQ,YAAY;AACnE,UAAM,IAAI;AAAA,MACR,GAAG,KAAK;AAAA,IACV;AAAA,EACF;AAEA,SAAO,aAAa;AAAA,IAClB,MAAM;AAAA,IACN,MAAM,KAAK;AACT,UAAI,gBAAgB,QAAQ,MAAM,WAAW,CAAC;AAC9C,UAAI,gBAAgB,SAAS,CAAC,QAAQ;AACpC,YAAI,CAAC,OAAO;AACV,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AACA,eAAO,MAAM,YAAY,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,MACjD,CAAC;AACD,UAAI,WAAW;AACb,YAAI,MAAM,YAAY,CAAC,KAAK,SAAS,UAAU,IAAI,IAAI,MAAM,IAAI,CAAC;AAAA,MACpE;AAAA,IACF;AAAA,EACF,CAAC;AACH;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotpress/cache",
3
- "version": "2.0.0-alpha.4",
3
+ "version": "2.0.0-alpha.5",
4
4
  "description": "Request memoization with batching, and a resilient cache with interchangeable stores",
5
5
  "author": "Renaud Guyon",
6
6
  "license": "MIT",
@@ -50,7 +50,7 @@
50
50
  "node": ">=22.12"
51
51
  },
52
52
  "peerDependencies": {
53
- "@dotpress/core": "^2.0.0-alpha.4"
53
+ "@dotpress/core": "^2.0.0-alpha.5"
54
54
  },
55
55
  "peerDependenciesMeta": {
56
56
  "@dotpress/core": {
@@ -58,8 +58,8 @@
58
58
  }
59
59
  },
60
60
  "devDependencies": {
61
- "@dotpress/core": "2.0.0-alpha.4",
62
- "@dotpress/testing": "2.0.0-alpha.4",
61
+ "@dotpress/core": "2.0.0-alpha.5",
62
+ "@dotpress/testing": "2.0.0-alpha.5",
63
63
  "ioredis": "~6.0.0",
64
64
  "miniflare": "~4.20260730.0",
65
65
  "redis": "~6.3.1",
@@ -1,27 +0,0 @@
1
- // src/duration.ts
2
- var UNITS = {
3
- ms: 1,
4
- s: 1e3,
5
- m: 6e4,
6
- h: 36e5,
7
- d: 864e5
8
- };
9
- var PATTERN = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/;
10
- var toMilliseconds = (label, value) => {
11
- if (typeof value === "number") {
12
- if (Number.isFinite(value) && value >= 0) return Math.round(value * 1e3);
13
- } else if (typeof value === "string") {
14
- const match = PATTERN.exec(value);
15
- if (match) {
16
- return Math.round(Number(match[1]) * UNITS[match[2]]);
17
- }
18
- }
19
- throw new TypeError(
20
- `${label} must be a number of seconds, or a duration like '30s', '10m', '1h' or '7d'.`
21
- );
22
- };
23
-
24
- export {
25
- toMilliseconds
26
- };
27
- //# sourceMappingURL=chunk-2IEBMYSF.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/duration.ts"],"sourcesContent":["/**\n * A duration: a number of seconds, or a string with a unit: `'500ms'`,\n * `'30s'`, `'10m'`, `'1h'`, `'7d'`.\n */\nexport type Duration = number | `${number}${DurationUnit}`\n\ntype DurationUnit = 'ms' | 's' | 'm' | 'h' | 'd'\n\nconst UNITS: Readonly<Record<DurationUnit, number>> = {\n ms: 1,\n s: 1000,\n m: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n}\n\nconst PATTERN = /^(\\d+(?:\\.\\d+)?)(ms|s|m|h|d)$/\n\n/**\n * Converts a duration to whole milliseconds, or throws a `TypeError` that\n * names the option.\n */\nexport const toMilliseconds = (label: string, value: unknown): number => {\n if (typeof value === 'number') {\n if (Number.isFinite(value) && value >= 0) return Math.round(value * 1000)\n } else if (typeof value === 'string') {\n const match = PATTERN.exec(value)\n if (match) {\n return Math.round(Number(match[1]) * UNITS[match[2] as DurationUnit])\n }\n }\n throw new TypeError(\n `${label} must be a number of seconds, or a duration like '30s', '10m', '1h' or '7d'.`\n )\n}\n"],"mappings":";AAQA,IAAM,QAAgD;AAAA,EACpD,IAAI;AAAA,EACJ,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AAEA,IAAM,UAAU;AAMT,IAAM,iBAAiB,CAAC,OAAe,UAA2B;AACvE,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI,OAAO,SAAS,KAAK,KAAK,SAAS,EAAG,QAAO,KAAK,MAAM,QAAQ,GAAI;AAAA,EAC1E,WAAW,OAAO,UAAU,UAAU;AACpC,UAAM,QAAQ,QAAQ,KAAK,KAAK;AAChC,QAAI,OAAO;AACT,aAAO,KAAK,MAAM,OAAO,MAAM,CAAC,CAAC,IAAI,MAAM,MAAM,CAAC,CAAiB,CAAC;AAAA,IACtE;AAAA,EACF;AACA,QAAM,IAAI;AAAA,IACR,GAAG,KAAK;AAAA,EACV;AACF;","names":[]}
@@ -1,8 +0,0 @@
1
- /**
2
- * A duration: a number of seconds, or a string with a unit: `'500ms'`,
3
- * `'30s'`, `'10m'`, `'1h'`, `'7d'`.
4
- */
5
- type Duration = number | `${number}${DurationUnit}`;
6
- type DurationUnit = 'ms' | 's' | 'm' | 'h' | 'd';
7
-
8
- export type { Duration as D };