@dotpress/cache 0.0.0-stage → 2.0.0-alpha.1

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.
@@ -0,0 +1,384 @@
1
+ import { D as Duration } from './duration-2mZnG0_i.js';
2
+ import { C as CacheStore } from './store-D0BfLqyM.js';
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;
101
+
102
+ /** Base class of the errors thrown by `@dotpress/cache`. */
103
+ declare class CacheError extends Error {
104
+ name: string;
105
+ }
106
+ /**
107
+ * The `batch` function of a memo returned a result that does not match
108
+ * the keys: every call of the batch fails with this error.
109
+ */
110
+ declare class MemoBatchError extends CacheError {
111
+ /** Name of the memo. */
112
+ readonly memo: string;
113
+ name: string;
114
+ constructor(
115
+ /** Name of the memo. */
116
+ memo: string, message: string);
117
+ }
118
+ /**
119
+ * Operation of a cache store: `invalidate` writes the tokens of tags,
120
+ * `lock` and `unlock` hold the distributed lock of a load.
121
+ */
122
+ type StoreOperation = 'get' | 'set' | 'delete' | 'invalidate' | 'lock' | 'unlock';
123
+ /** A cache store failed or did not answer in time (`failOpen: false`). */
124
+ declare class CacheStoreError extends CacheError {
125
+ /** Name of the store. */
126
+ readonly store: string;
127
+ readonly operation: StoreOperation;
128
+ /** Key of the store. */
129
+ readonly key: string;
130
+ name: string;
131
+ constructor(
132
+ /** Name of the store. */
133
+ store: string, operation: StoreOperation,
134
+ /** Key of the store. */
135
+ key: string, options: {
136
+ cause: unknown;
137
+ });
138
+ }
139
+ /**
140
+ * The loader of `getOrSet()` did not finish within `hardTimeout`, and no
141
+ * stale value could be served. The load goes on, and caches its value.
142
+ */
143
+ declare class CacheTimeoutError extends CacheError {
144
+ /** Name of the entry. */
145
+ readonly entry: string;
146
+ /** Key of the store. */
147
+ readonly key: string;
148
+ /** Timeout, in milliseconds. */
149
+ readonly timeout: number;
150
+ name: string;
151
+ constructor(
152
+ /** Name of the entry. */
153
+ entry: string,
154
+ /** Key of the store. */
155
+ key: string,
156
+ /** Timeout, in milliseconds. */
157
+ timeout: number);
158
+ }
159
+
160
+ /**
161
+ * Logger of the cache. A Dotpress or Pino logger fits; `cachePlugin` uses
162
+ * the logger of the request.
163
+ */
164
+ interface CacheLogger {
165
+ debug?(data: object, message: string): void;
166
+ info(data: object, message: string): void;
167
+ warn(data: object, message: string): void;
168
+ error(data: object, message: string): void;
169
+ }
170
+
171
+ /**
172
+ * A schema that validates cached values when they are read: a Zod schema,
173
+ * or anything with the same `safeParseAsync()`.
174
+ */
175
+ interface CacheSchema<T> {
176
+ safeParseAsync(value: unknown): Promise<{
177
+ success: true;
178
+ data: T;
179
+ } | {
180
+ success: false;
181
+ }>;
182
+ }
183
+ declare const untyped: unique symbol;
184
+ /** Value type of an entry without schema: given by the loader. */
185
+ interface Untyped {
186
+ readonly [untyped]: true;
187
+ }
188
+ declare const entryTypes: unique symbol;
189
+ /** A kind of cached value, created by `defineCacheEntry()`. */
190
+ interface CacheEntry<A extends readonly unknown[], T> {
191
+ readonly name: string;
192
+ readonly version: number;
193
+ /** Types only. */
194
+ readonly [entryTypes]?: {
195
+ args: A;
196
+ value: (value: T) => T;
197
+ };
198
+ }
199
+ /** Value of an entry: `unknown` without schema. */
200
+ type CacheValue<T> = T extends Untyped ? unknown : T;
201
+ /**
202
+ * Lifetime and resilience of values: options of an entry, or defaults of
203
+ * the cache.
204
+ */
205
+ interface CachePolicy {
206
+ /** Lifetime of the values. Defaults to 1 hour. */
207
+ ttl?: Duration | undefined;
208
+ /**
209
+ * Stale-if-error: how long after expiry a value can still be served when
210
+ * the loader fails (or times out). Defaults to 0 (disabled).
211
+ */
212
+ grace?: Duration | undefined;
213
+ /**
214
+ * After a failed load served from the grace period, the loader is not
215
+ * called again before this delay: the stale value is served. Defaults to
216
+ * 10 seconds.
217
+ */
218
+ graceBackoff?: Duration | undefined;
219
+ /**
220
+ * How long after expiry a value is still served while a background load
221
+ * refreshes it. Defaults to 0 (disabled).
222
+ */
223
+ staleWhileRevalidate?: Duration | undefined;
224
+ /**
225
+ * Soft timeout: a load slower than this serves the stale value of the
226
+ * grace period, if any, and goes on in the background.
227
+ */
228
+ timeout?: Duration | undefined;
229
+ /**
230
+ * Hard timeout: a load slower than this fails (`CacheTimeoutError`), or
231
+ * serves the stale value of the grace period, if any. It goes on in the
232
+ * background.
233
+ */
234
+ hardTimeout?: Duration | undefined;
235
+ /**
236
+ * Distributed single flight: before loading a missing value, takes a
237
+ * lock in the store (Redis…) for at most this duration; the other
238
+ * instances wait for the value meanwhile, then load it themselves.
239
+ * Ignored by stores without locks. Disabled by default.
240
+ */
241
+ lock?: Duration | undefined;
242
+ }
243
+ /** Options of `defineCacheEntry()`. */
244
+ interface CacheEntryOptions<A extends readonly unknown[], T> extends CachePolicy {
245
+ /**
246
+ * Name of the entry, in keys and logs: letters, digits, `.`, `_` and
247
+ * `-`.
248
+ */
249
+ name: string;
250
+ /**
251
+ * Key of the arguments: a string, a number, or a value compared like the
252
+ * arguments of a memo. Defaults to the argument (or the arguments).
253
+ */
254
+ key?: ((...args: A) => unknown) | undefined;
255
+ /**
256
+ * Validates cached values when they are read (entries from an older
257
+ * version of the app) and written. JSON turns dates into strings: use
258
+ * `z.coerce.date()`.
259
+ */
260
+ schema?: CacheSchema<T> | undefined;
261
+ /**
262
+ * Version of the values, in the key: increase it when their shape
263
+ * changes, to ignore the values written before. Defaults to 1.
264
+ */
265
+ version?: number | undefined;
266
+ /**
267
+ * Tags of the arguments: `invalidateTags()` expires every value with one
268
+ * of them.
269
+ */
270
+ tags?: ((...args: A) => readonly string[]) | undefined;
271
+ }
272
+ /**
273
+ * Defines a kind of cached value: its key, its lifetime, and optionally
274
+ * its schema.
275
+ *
276
+ * ```ts
277
+ * export const userCache = defineCacheEntry({
278
+ * name: 'user',
279
+ * key: (id: string) => id,
280
+ * schema: userSchema,
281
+ * ttl: '1h',
282
+ * })
283
+ * ```
284
+ */
285
+ declare function defineCacheEntry<A extends readonly unknown[], T>(options: CacheEntryOptions<A, T> & {
286
+ schema: CacheSchema<T>;
287
+ }): CacheEntry<A, T>;
288
+ declare function defineCacheEntry<A extends readonly unknown[]>(options: CacheEntryOptions<A, never> & {
289
+ schema?: undefined;
290
+ }): CacheEntry<A, Untyped>;
291
+ declare function defineCacheEntry<A extends readonly unknown[], T>(options: CacheEntryOptions<A, T>): CacheEntry<A, T>;
292
+ /** Turns values into strings for the store, and back. */
293
+ interface CacheSerializer {
294
+ serialize(value: unknown): string;
295
+ deserialize(text: string): unknown;
296
+ }
297
+ /** Details of a store failure, for `onError`. */
298
+ interface CacheErrorInfo {
299
+ readonly operation: StoreOperation;
300
+ /** Key of the store. */
301
+ readonly key: string;
302
+ /** Name of the entry. */
303
+ readonly entry: string;
304
+ }
305
+ /** Something that happened to a value, for `onEvent`. */
306
+ interface CacheEvent {
307
+ /**
308
+ * `hit`: fresh value; `miss`: loaded; `stale`: a value past its `ttl`
309
+ * served, for the `reason`.
310
+ */
311
+ readonly type: 'hit' | 'miss' | 'stale';
312
+ readonly reason?: 'revalidate' | 'error' | 'timeout' | 'backoff';
313
+ /** Name of the entry. */
314
+ readonly entry: string;
315
+ /** Key of the store. */
316
+ readonly key: string;
317
+ }
318
+ /** Options of `createCache()`: the policy is the default of the entries. */
319
+ interface CacheOptions extends CachePolicy {
320
+ store: CacheStore;
321
+ /** Prefix of every key, like the name of the app. */
322
+ prefix?: string | undefined;
323
+ /** Defaults to JSON. */
324
+ serializer?: CacheSerializer | undefined;
325
+ logger?: CacheLogger | undefined;
326
+ /**
327
+ * Called for each store failure, besides the log: metrics, alerts. Its
328
+ * own errors are logged and ignored.
329
+ */
330
+ onError?: ((error: CacheStoreError, info: CacheErrorInfo) => void | Promise<void>) | undefined;
331
+ /**
332
+ * When the store fails, behaves as a cache miss (reads) or ignores the
333
+ * write, after logging it. Set false to throw a `CacheStoreError`.
334
+ * Defaults to true.
335
+ */
336
+ failOpen?: boolean | undefined;
337
+ /** A store operation slower than this counts as a failure. */
338
+ storeTimeout?: Duration | undefined;
339
+ /**
340
+ * Called for each hit, miss and stale value served: metrics. Its errors
341
+ * are logged and ignored.
342
+ */
343
+ onEvent?: ((event: CacheEvent) => void) | undefined;
344
+ }
345
+ /** A cache, created by `createCache()`. */
346
+ interface Cache {
347
+ /**
348
+ * The fresh value of the arguments, or undefined when missing, expired
349
+ * or invalidated.
350
+ */
351
+ get<A extends readonly unknown[], T>(entry: CacheEntry<A, T>, ...args: A): Promise<CacheValue<T> | undefined>;
352
+ /** Sets the value of the arguments (the last argument). */
353
+ set<A extends readonly unknown[], T>(entry: CacheEntry<A, T>, ...argsAndValue: [...A, CacheValue<T>]): Promise<void>;
354
+ /** Removes the value of the arguments. */
355
+ delete<A extends readonly unknown[], T>(entry: CacheEntry<A, T>, ...args: A): Promise<void>;
356
+ /**
357
+ * The value of the arguments, loaded by the last argument when missing
358
+ * and cached. Calls made while it loads share the load. Applies the
359
+ * grace period, stale-while-revalidate and timeouts of the entry.
360
+ */
361
+ getOrSet<A extends readonly unknown[], R>(entry: CacheEntry<A, Untyped>, ...argsAndLoader: [...A, () => R | Promise<R>]): Promise<R>;
362
+ getOrSet<A extends readonly unknown[], T>(entry: CacheEntry<A, T>, ...argsAndLoader: [...A, () => T | Promise<T>]): Promise<T>;
363
+ /**
364
+ * Expires every value with one of the tags (they can still be served by
365
+ * the grace period when the loader fails).
366
+ */
367
+ invalidateTags(tags: readonly string[]): Promise<void>;
368
+ /** The same cache, logging with another logger. */
369
+ withLogger(logger: CacheLogger): Cache;
370
+ }
371
+ /**
372
+ * Creates a cache on a store. Reads and writes fail open by default: a
373
+ * store failure is logged (and passed to `onError`), and the app keeps
374
+ * working without cache.
375
+ *
376
+ * ```ts
377
+ * const cache = createCache({ store: createMemoryStore(), prefix: 'app', ttl: '10m' })
378
+ * const user = await cache.getOrSet(userCache, id, () => db.users.findById(id))
379
+ * ```
380
+ */
381
+ declare const createCache: (options: CacheOptions) => Cache;
382
+
383
+ export { CacheError, CacheStore, CacheStoreError, CacheTimeoutError, Duration, MemoBatchError, createCache, createMemo, defineCacheEntry, defineMemo };
384
+ export type { BatchMemoOptions, Cache, CacheEntry, CacheEntryOptions, CacheErrorInfo, CacheEvent, CacheLogger, CacheOptions, CachePolicy, CacheSchema, CacheSerializer, CacheValue, LoadMemoOptions, Memo, MemoDefinition, StoreOperation, Untyped };