@nifrajs/cache 4.0.0 → 5.0.0

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/README.md CHANGED
@@ -31,7 +31,12 @@ await cache.invalidateTag(`user:${id}`)
31
31
  **immediately** while a **background** refresh runs (deduped). Latency stays flat; data self-heals.
32
32
  - **Miss/expired**: awaits `loader`. Concurrent misses for the same key share **one** call (no stampede).
33
33
  - A throwing `loader` is **not** cached (and in the background path goes to `onError`, never rejects the caller).
34
- - **`set` / `get` / `has` / `delete`**, **`invalidateTag(tag)`**, **`clear()`**.
34
+ - **Store outage**: a store `get` that throws or rejects is a miss, so the loader answers; a `set` that
35
+ fails still returns the loaded value, and a `fence` that fails leaves that load uncached. Each
36
+ failure goes to `onStoreError(error, { op })`, which by default logs one line per operation per
37
+ minute without the key. The explicit `get`, `set`, `delete`, `invalidateTag` and `clear` still
38
+ reject, so a failed invalidation is never silent.
39
+ - **`set` / `get` / `has` / `delete`**, **`invalidateTag(tag)`**, **`clear()`**. A `delete`, `invalidateTag`, `clear` or newer `set` also stops a `wrap` load or background refresh already running for that key or tag from storing its result, and the next `wrap` loads afresh. A `delete`, `invalidateTag` or `clear` runs after this cache's own async store writes it covers, so a store that applies requests out of order cannot land one after it.
35
40
  - TTL: `ttlMs` (→ stale) + optional `swrMs` (→ served-stale window) + optional `tags`.
36
41
 
37
42
  ## Stores
@@ -45,6 +50,14 @@ opt-in. For a cache shared across instances, implement `CacheStore`
45
50
  const cache = createCache({ store: new RedisCacheStore(redis) })
46
51
  ```
47
52
 
53
+ A store several caches or processes share should also implement the optional `fence(key, tags)` and
54
+ `setIfFence(key, entry, tags, token)`. A `wrap` load takes a fence before it runs and stores its result
55
+ through `setIfFence`, which must refuse, in one atomic step, when a `set` or `delete` of the key, an
56
+ `invalidateTag` of one of its tags, or a `clear` ran since that fence, from any cache. Without them, a
57
+ load running in one process can store a value another process invalidated meanwhile. `MemoryCache`
58
+ implements both, and `cacheStoreCertificationProfile({ fencing: true })` from
59
+ `@nifrajs/testing/certification` checks a store's implementation.
60
+
48
61
  On **Cloudflare Workers** the in-memory cache is per-isolate and short-lived - back it with **CF KV** (or
49
62
  the Cache API) via a `CacheStore` for anything that should survive across requests/instances.
50
63
 
@@ -52,7 +65,8 @@ the Cache API) via a `CacheStore` for anything that should survive across reques
52
65
 
53
66
  `observer` receives one event per operation after it settles: `{ op, outcome, startedAt, durationMs,
54
67
  key, tag, tagCount, context }`. `outcome` is `hit`, `stale` or `miss` for reads, `ok` for writes and a
55
- finished background refresh (`op: "revalidate"`), `error` when the store or loader threw. An observer
68
+ finished background refresh (`op: "revalidate"`), `error` when the store or loader threw. A `wrap`
69
+ that loaded through a store outage reports `miss`. An observer
56
70
  that throws is swallowed; it never changes a result. Without an observer the cache reads no extra clock.
57
71
 
58
72
  `cache.for(c)` binds a request (or job) context, and every event carries it. That is how
@@ -75,7 +89,7 @@ whenever it is set.
75
89
 
76
90
  ## API
77
91
 
78
- - `createCache(options?)` → `Cache` - `{ store?, defaultTtlMs?, now?, onError?, observer?, beacon?, capabilities? }`.
92
+ - `createCache(options?)` → `Cache` - `{ store?, defaultTtlMs?, now?, onError?, onStoreError?, observer?, beacon?, capabilities? }`.
79
93
  - `cache.wrap(key, loader, { ttlMs?, swrMs?, tags? })` · `get` · `has` · `set` · `delete` · `invalidateTag` · `clear`.
80
94
  - `cache.for(context)` → the same surface bound to a request or job context.
81
95
  - `MemoryCache({ maxEntries?, now? })` - the default store; implement `CacheStore` for your own.
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EACV,KAAK,EAIL,YAAY,EAMb,MAAM,YAAY,CAAA;AAuCnB,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,OAAO,GAAE,YAAiB,GAAG,KAAK,CA6O7D"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EACV,KAAK,EAIL,YAAY,EAOb,MAAM,YAAY,CAAA;AAmDnB,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,OAAO,GAAE,YAAiB,GAAG,KAAK,CAyW7D"}
package/dist/cache.js CHANGED
@@ -16,6 +16,16 @@ function assertDuration(value, name) {
16
16
  throw new RangeError(`[nifra/cache] ${name} must be a finite non-negative number`);
17
17
  }
18
18
  }
19
+ const STORE_REPORT_INTERVAL_MS = 60_000;
20
+ const STORE_REPORT_DETAIL_CHARS = 200;
21
+ /** The error's own words with the key cut out, since a store may name the key it failed on. */
22
+ function storeErrorDetail(error, key) {
23
+ const text = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
24
+ const redacted = key === "" ? text : text.split(key).join("<key>");
25
+ return redacted.length > STORE_REPORT_DETAIL_CHARS
26
+ ? `${redacted.slice(0, STORE_REPORT_DETAIL_CHARS)}...`
27
+ : redacted;
28
+ }
19
29
  function notify(observer, event) {
20
30
  try {
21
31
  const result = observer(event);
@@ -47,9 +57,62 @@ export function createCache(options = {}) {
47
57
  const store = options.store ?? new MemoryCache({ now });
48
58
  const onError = options.onError ??
49
59
  ((error, key) => console.error(`[nifra/cache] revalidate ${JSON.stringify(key)} failed:`, error));
60
+ const lastStoreReport = new Map();
61
+ const reportStoreError = (op, key, error) => {
62
+ try {
63
+ if (options.onStoreError !== undefined) {
64
+ options.onStoreError(error, { op });
65
+ return;
66
+ }
67
+ const last = lastStoreReport.get(op);
68
+ const at = now();
69
+ if (last !== undefined && at - last < STORE_REPORT_INTERVAL_MS)
70
+ return;
71
+ lastStoreReport.set(op, at);
72
+ console.error(`[nifra/cache] store ${op} failed; loading without the cache (${storeErrorDetail(error, key)})`);
73
+ }
74
+ catch {
75
+ // A failing reporter must not turn a loaded value into an error.
76
+ }
77
+ };
78
+ /** `wrap`'s read: a store that throws or rejects is a miss, so the loader answers. */
79
+ const readEntry = async (key) => {
80
+ try {
81
+ return await store.get(key);
82
+ }
83
+ catch (error) {
84
+ reportStoreError("get", key, error);
85
+ return undefined;
86
+ }
87
+ };
50
88
  const observer = options.observer;
51
89
  // Single-flight: concurrent loads for the same key share one promise (miss stampede + SWR dedup).
90
+ // A delete, tag invalidation, clear or newer set withdraws a load's right to store its result, so
91
+ // a refresh that started before the invalidation cannot write the old value back. A store with
92
+ // `fence`/`setIfFence` also refuses a load's write after such a change from another cache sharing
93
+ // it, and a delete, invalidation or clear waits for this cache's async writes it covers, so a store
94
+ // that applies requests out of order cannot land one of them after it.
52
95
  const inflight = new Map();
96
+ const withdraw = (matches) => {
97
+ for (const [key, flight] of inflight) {
98
+ if (!matches(key, flight.tags))
99
+ continue;
100
+ flight.withdraw();
101
+ inflight.delete(key);
102
+ }
103
+ };
104
+ const fencing = typeof store.fence === "function" && typeof store.setIfFence === "function";
105
+ const writing = new Set();
106
+ // Run a delete, invalidation or clear after every async write it covers has settled.
107
+ const after = (matches, change) => {
108
+ const covered = [];
109
+ for (const write of writing)
110
+ if (matches(write.key, write.tags))
111
+ covered.push(write.done);
112
+ if (covered.length === 0)
113
+ return Promise.resolve(change());
114
+ return Promise.allSettled(covered).then(change);
115
+ };
53
116
  async function get(key) {
54
117
  const entry = await store.get(key);
55
118
  return entry === undefined ? undefined : entry.value;
@@ -57,27 +120,94 @@ export function createCache(options = {}) {
57
120
  async function has(key) {
58
121
  return (await store.get(key)) !== undefined;
59
122
  }
60
- async function set(key, value, opts = {}) {
123
+ function entryOf(value, opts) {
61
124
  const ttlMs = opts.ttlMs ?? defaultTtlMs;
62
125
  const swrMs = opts.swrMs ?? 0;
63
126
  assertDuration(ttlMs, "ttlMs");
64
127
  assertDuration(swrMs, "swrMs");
65
128
  const t = now();
66
- await store.set(key, { value, staleAt: t + ttlMs, expiresAt: t + ttlMs + swrMs }, opts.tags ?? []);
129
+ return { value, staleAt: t + ttlMs, expiresAt: t + ttlMs + swrMs };
130
+ }
131
+ async function put(key, entry, tags, fenced) {
132
+ const pending = fenced !== undefined && store.setIfFence !== undefined
133
+ ? store.setIfFence(key, entry, tags, fenced.token)
134
+ : store.set(key, entry, tags);
135
+ if (!(pending instanceof Promise))
136
+ return;
137
+ const record = { key, tags, done: pending };
138
+ writing.add(record);
139
+ try {
140
+ await pending;
141
+ }
142
+ finally {
143
+ writing.delete(record);
144
+ }
145
+ }
146
+ async function write(key, value, opts) {
147
+ await put(key, entryOf(value, opts), opts.tags ?? []);
148
+ }
149
+ function set(key, value, opts = {}) {
150
+ withdraw((flightKey) => flightKey === key);
151
+ return write(key, value, opts);
152
+ }
153
+ function remove(key) {
154
+ const matches = (writeKey) => writeKey === key;
155
+ withdraw(matches);
156
+ return after(matches, () => store.delete(key));
157
+ }
158
+ function invalidateTag(tag) {
159
+ const matches = (_key, tags) => tags.includes(tag);
160
+ withdraw(matches);
161
+ return after(matches, () => store.invalidateTag(tag));
162
+ }
163
+ function clear() {
164
+ withdraw(() => true);
165
+ return after(() => true, () => store.clear());
67
166
  }
68
167
  function load(key, loader, opts) {
69
168
  const existing = inflight.get(key);
70
169
  if (existing !== undefined)
71
- return existing;
170
+ return existing.promise;
171
+ let withdrawn = false;
72
172
  const p = (async () => {
173
+ // Taken before the loader runs, so a change from another cache during the load refuses its
174
+ // write. Awaited only when async: a sync store keeps the loader starting synchronously.
175
+ let fenced;
176
+ let storable = true;
177
+ if (fencing) {
178
+ try {
179
+ const token = store.fence?.(key, opts.tags ?? []);
180
+ fenced = { token: token instanceof Promise ? await token : token };
181
+ }
182
+ catch (error) {
183
+ // Without a token the write could land after another cache's invalidation: load uncached.
184
+ reportStoreError("fence", key, error);
185
+ storable = false;
186
+ }
187
+ }
73
188
  const value = await loader();
74
- await set(key, value, opts);
189
+ if (!withdrawn && storable) {
190
+ // Built outside the try: a bad ttlMs is the caller's error, not a store outage.
191
+ const entry = entryOf(value, opts);
192
+ try {
193
+ await put(key, entry, opts.tags ?? [], fenced);
194
+ }
195
+ catch (error) {
196
+ reportStoreError("set", key, error);
197
+ }
198
+ }
75
199
  return value;
76
200
  })().finally(() => {
77
- if (inflight.get(key) === p)
201
+ if (inflight.get(key)?.promise === p)
78
202
  inflight.delete(key);
79
203
  });
80
- inflight.set(key, p);
204
+ inflight.set(key, {
205
+ promise: p,
206
+ tags: opts.tags ?? [],
207
+ withdraw: () => {
208
+ withdrawn = true;
209
+ },
210
+ });
81
211
  return p;
82
212
  }
83
213
  function revalidate(key, loader, opts, context) {
@@ -99,7 +229,7 @@ export function createCache(options = {}) {
99
229
  });
100
230
  }
101
231
  async function wrap(key, loader, opts = {}) {
102
- const entry = await store.get(key);
232
+ const entry = await readEntry(key);
103
233
  if (entry !== undefined) {
104
234
  if (now() < entry.staleAt)
105
235
  return entry.value; // fresh hit
@@ -146,7 +276,7 @@ export function createCache(options = {}) {
146
276
  const started = performance.now();
147
277
  let outcome = "error";
148
278
  try {
149
- const entry = await store.get(key);
279
+ const entry = await readEntry(key);
150
280
  if (entry !== undefined) {
151
281
  outcome = readOutcome(entry);
152
282
  if (outcome === "stale")
@@ -163,9 +293,9 @@ export function createCache(options = {}) {
163
293
  report(observer, "wrap", outcome, started, key, undefined, opts.tags?.length ?? 0, context);
164
294
  }
165
295
  },
166
- delete: (key) => settle("delete", key, undefined, 0, () => Promise.resolve(store.delete(key)), "ok"),
167
- invalidateTag: (tag) => settle("invalidateTag", undefined, tag, 1, () => Promise.resolve(store.invalidateTag(tag)), "ok"),
168
- clear: () => settle("clear", undefined, undefined, 0, () => Promise.resolve(store.clear()), "ok"),
296
+ delete: (key) => settle("delete", key, undefined, 0, () => remove(key), "ok"),
297
+ invalidateTag: (tag) => settle("invalidateTag", undefined, tag, 1, () => invalidateTag(tag), "ok"),
298
+ clear: () => settle("clear", undefined, undefined, 0, clear, "ok"),
169
299
  for: bind,
170
300
  };
171
301
  }
@@ -177,9 +307,9 @@ export function createCache(options = {}) {
177
307
  has,
178
308
  set,
179
309
  wrap,
180
- delete: (key) => Promise.resolve(store.delete(key)),
181
- invalidateTag: (tag) => Promise.resolve(store.invalidateTag(tag)),
182
- clear: () => Promise.resolve(store.clear()),
310
+ delete: remove,
311
+ invalidateTag,
312
+ clear,
183
313
  for: bind,
184
314
  }
185
315
  : observed(observer, undefined);
package/dist/cache.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -9,5 +9,5 @@
9
9
  */
10
10
  export { createCache } from "./cache.js";
11
11
  export { MemoryCache, type MemoryCacheOptions } from "./memory-cache.js";
12
- export type { Cache, CacheCapabilities, CacheEvent, CacheObserver, CacheOperation, CacheOptions, CacheOutcome, CacheStore, CapabilityBeacon, SetOptions, StoredEntry, WrapOptions, } from "./types.js";
12
+ export type { Cache, CacheCapabilities, CacheEvent, CacheObserver, CacheOperation, CacheOptions, CacheOutcome, CacheStore, CacheStoreOp, CapabilityBeacon, SetOptions, StoredEntry, WrapOptions, } from "./types.js";
13
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACxE,YAAY,EACV,KAAK,EACL,iBAAiB,EACjB,UAAU,EACV,aAAa,EACb,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,WAAW,GACZ,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACxE,YAAY,EACV,KAAK,EACL,iBAAiB,EACjB,UAAU,EACV,aAAa,EACb,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,WAAW,GACZ,MAAM,YAAY,CAAA"}
@@ -17,14 +17,23 @@ export declare class MemoryCache implements CacheStore {
17
17
  private expirySweep;
18
18
  private readonly maxEntries;
19
19
  private readonly now;
20
+ private readonly keyChanges;
21
+ private readonly tagChanges;
22
+ private clearedAt;
23
+ private forgottenAt;
24
+ private epoch;
20
25
  constructor(options?: MemoryCacheOptions);
21
26
  get(key: string): StoredEntry | undefined;
22
27
  set(key: string, entry: StoredEntry, tags: readonly string[]): void;
23
28
  delete(key: string): void;
24
29
  invalidateTag(tag: string): void;
25
30
  clear(): void;
31
+ /** The current epoch: {@link MemoryCache.setIfFence} compares what changed after it. */
32
+ fence(): number;
33
+ setIfFence(key: string, entry: StoredEntry, tags: readonly string[], token: unknown): boolean;
26
34
  /** Live entry count (for observability/tests). */
27
35
  size(): number;
36
+ private record;
28
37
  /** Remove a key and unlink it from every tag set it belonged to. */
29
38
  private evict;
30
39
  private sweepExpired;
@@ -1 +1 @@
1
- {"version":3,"file":"memory-cache.d.ts","sourceRoot":"","sources":["../src/memory-cache.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AASzD,MAAM,WAAW,kBAAkB;IACjC;uCACmC;IACnC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,4DAA4D;IAC5D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CAC5B;AAED,qBAAa,WAAY,YAAW,UAAU;IAE5C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyB;IAC9C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAC1D,OAAO,CAAC,WAAW,CAAsB;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;gBAEtB,OAAO,GAAE,kBAAuB;IAQ5C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAazC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;IAyBnE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIzB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAMhC,KAAK,IAAI,IAAI;IAMb,kDAAkD;IAClD,IAAI,IAAI,MAAM;IAId,oEAAoE;IACpE,OAAO,CAAC,KAAK;IAab,OAAO,CAAC,YAAY;CAkBrB"}
1
+ {"version":3,"file":"memory-cache.d.ts","sourceRoot":"","sources":["../src/memory-cache.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAazD,MAAM,WAAW,kBAAkB;IACjC;uCACmC;IACnC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,4DAA4D;IAC5D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CAC5B;AAED,qBAAa,WAAY,YAAW,UAAU;IAE5C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyB;IAC9C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAC1D,OAAO,CAAC,WAAW,CAAsB;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;IAIlC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA4B;IACvD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA4B;IACvD,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,KAAK,CAAI;gBAEL,OAAO,GAAE,kBAAuB;IAQ5C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAazC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;IA0BnE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAKzB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAOhC,KAAK,IAAI,IAAI;IAUb,wFAAwF;IACxF,KAAK,IAAI,MAAM;IAIf,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO;IAU7F,kDAAkD;IAClD,IAAI,IAAI,MAAM;IAId,OAAO,CAAC,MAAM;IAWd,oEAAoE;IACpE,OAAO,CAAC,KAAK;IAab,OAAO,CAAC,YAAY;CAkBrB"}
@@ -1,3 +1,6 @@
1
+ // How many distinct keys (and, separately, tags) the fence remembers changing. A load that outlives
2
+ // this many distinct changes is refused its write, which costs one cache miss, never a stale value.
3
+ const MAX_TRACKED_CHANGES = 65_536;
1
4
  export class MemoryCache {
2
5
  // Map iteration is insertion-ordered, so the first key is the LRU victim; `get`/`set` re-insert to touch.
3
6
  rows = new Map();
@@ -5,6 +8,14 @@ export class MemoryCache {
5
8
  expirySweep = this.rows.entries();
6
9
  maxEntries;
7
10
  now;
11
+ // The epoch of each key's and tag's latest change, oldest first. Only the change that matters to a
12
+ // load is compared, so unrelated writes never age its fence out. Bounded: a fence older than a
13
+ // forgotten change is refused.
14
+ keyChanges = new Map();
15
+ tagChanges = new Map();
16
+ clearedAt = 0;
17
+ forgottenAt = 0;
18
+ epoch = 0;
8
19
  constructor(options = {}) {
9
20
  this.maxEntries = options.maxEntries ?? 10_000;
10
21
  if (!Number.isSafeInteger(this.maxEntries) || this.maxEntries < 0) {
@@ -26,6 +37,7 @@ export class MemoryCache {
26
37
  return { value: row.value, expiresAt: row.expiresAt, staleAt: row.staleAt };
27
38
  }
28
39
  set(key, entry, tags) {
40
+ this.record(this.keyChanges, key);
29
41
  // Incremental expiry keeps cold, high-cardinality keys from occupying the entire bound until
30
42
  // their exact key is read. A fixed batch avoids an O(n) latency spike on a write.
31
43
  this.sweepExpired(8);
@@ -51,9 +63,11 @@ export class MemoryCache {
51
63
  }
52
64
  }
53
65
  delete(key) {
66
+ this.record(this.keyChanges, key);
54
67
  this.evict(key);
55
68
  }
56
69
  invalidateTag(tag) {
70
+ this.record(this.tagChanges, tag);
57
71
  const keys = this.tagIndex.get(tag);
58
72
  if (keys === undefined)
59
73
  return;
@@ -61,14 +75,47 @@ export class MemoryCache {
61
75
  this.evict(key);
62
76
  }
63
77
  clear() {
78
+ // Every older fence is refused from here on, so the earlier changes need no remembering.
79
+ this.clearedAt = ++this.epoch;
80
+ this.keyChanges.clear();
81
+ this.tagChanges.clear();
64
82
  this.rows.clear();
65
83
  this.tagIndex.clear();
66
84
  this.expirySweep = this.rows.entries();
67
85
  }
86
+ /** The current epoch: {@link MemoryCache.setIfFence} compares what changed after it. */
87
+ fence() {
88
+ return this.epoch;
89
+ }
90
+ setIfFence(key, entry, tags, token) {
91
+ if (typeof token !== "number" || !Number.isSafeInteger(token) || token > this.epoch)
92
+ return false;
93
+ if (token < this.clearedAt || token < this.forgottenAt)
94
+ return false;
95
+ if ((this.keyChanges.get(key) ?? 0) > token)
96
+ return false;
97
+ for (const tag of tags)
98
+ if ((this.tagChanges.get(tag) ?? 0) > token)
99
+ return false;
100
+ this.set(key, entry, tags);
101
+ return true;
102
+ }
68
103
  /** Live entry count (for observability/tests). */
69
104
  size() {
70
105
  return this.rows.size;
71
106
  }
107
+ record(changes, name) {
108
+ changes.delete(name);
109
+ changes.set(name, ++this.epoch);
110
+ if (changes.size <= MAX_TRACKED_CHANGES)
111
+ return;
112
+ const first = changes.entries().next();
113
+ if (first.done)
114
+ return;
115
+ const [oldest, at] = first.value;
116
+ changes.delete(oldest);
117
+ this.forgottenAt = Math.max(this.forgottenAt, at);
118
+ }
72
119
  /** Remove a key and unlink it from every tag set it belonged to. */
73
120
  evict(key) {
74
121
  const row = this.rows.get(key);
@@ -1 +1 @@
1
- {"version":3,"file":"memory-cache.js","sourceRoot":"","sources":["../src/memory-cache.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"memory-cache.js","sourceRoot":"","sources":["../src/memory-cache.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -26,7 +26,22 @@ export interface CacheStore {
26
26
  /** Drop every entry carrying `tag`. */
27
27
  invalidateTag(tag: string): void | Promise<void>;
28
28
  clear(): void | Promise<void>;
29
+ /**
30
+ * Optional, with {@link CacheStore.setIfFence}, for a store several caches or processes share. An
31
+ * opaque token naming this moment for `key` and `tags`. A `wrap` load takes one before it starts.
32
+ */
33
+ fence?(key: string, tags: readonly string[]): unknown | Promise<unknown>;
34
+ /**
35
+ * Optional, with {@link CacheStore.fence}. Store `entry` only when nothing has run since `fence`
36
+ * returned `token` that a load must not overwrite: a `set` or `delete` of `key`, an `invalidateTag`
37
+ * of one of `tags`, or a `clear`, from any cache sharing the store. The check and the write are one
38
+ * atomic step. Resolves `true` when it wrote. A store that cannot tell may refuse; the load's value
39
+ * still reaches its caller, it is just not cached.
40
+ */
41
+ setIfFence?(key: string, entry: StoredEntry, tags: readonly string[], token: unknown): boolean | Promise<boolean>;
29
42
  }
43
+ /** The store call a {@link CacheOptions.onStoreError} report is about. */
44
+ export type CacheStoreOp = "get" | "set" | "fence";
30
45
  export interface SetOptions {
31
46
  /** Time-to-live (ms) before the value goes stale. Default: the cache's `defaultTtlMs`. */
32
47
  readonly ttlMs?: number;
@@ -104,6 +119,14 @@ export interface CacheOptions {
104
119
  readonly now?: () => number;
105
120
  /** Called when a background SWR revalidation throws. Default: `console.error`. */
106
121
  readonly onError?: (error: unknown, key: string) => void;
122
+ /**
123
+ * Called when `wrap` meets a store failure: a `get` that throws or rejects is answered as a miss, so
124
+ * the loader runs, and a `set` that fails still returns the loaded value. A `fence` that fails
125
+ * leaves that load uncached. Other operations reject as before. Default: one `console.error` line per operation per minute, never naming the key.
126
+ */
127
+ readonly onStoreError?: (error: unknown, info: {
128
+ readonly op: CacheStoreOp;
129
+ }) => void;
107
130
  /**
108
131
  * Receives one {@link CacheEvent} per operation - the seam `cacheTracing()` from `@nifrajs/otel/cache`
109
132
  * plugs into. Without one the cache reads no extra clock and allocates no event.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nifrajs/cache",
3
- "version": "4.0.0",
3
+ "version": "5.0.0",
4
4
  "description": "Typed KV cache for nifra - TTL, stale-while-revalidate, tag invalidation, and single-flight stampede protection on a pluggable store (in-memory default; bring CF KV / Redis). Dependency-free, runs on Bun/Node/Deno/Workers.",
5
5
  "keywords": [
6
6
  "bun",
package/src/cache.ts CHANGED
@@ -19,6 +19,7 @@ import type {
19
19
  CacheOptions,
20
20
  CacheOutcome,
21
21
  CacheStore,
22
+ CacheStoreOp,
22
23
  SetOptions,
23
24
  StoredEntry,
24
25
  WrapOptions,
@@ -30,6 +31,18 @@ function assertDuration(value: number, name: string): void {
30
31
  }
31
32
  }
32
33
 
34
+ const STORE_REPORT_INTERVAL_MS = 60_000
35
+ const STORE_REPORT_DETAIL_CHARS = 200
36
+
37
+ /** The error's own words with the key cut out, since a store may name the key it failed on. */
38
+ function storeErrorDetail(error: unknown, key: string): string {
39
+ const text = error instanceof Error ? `${error.name}: ${error.message}` : String(error)
40
+ const redacted = key === "" ? text : text.split(key).join("<key>")
41
+ return redacted.length > STORE_REPORT_DETAIL_CHARS
42
+ ? `${redacted.slice(0, STORE_REPORT_DETAIL_CHARS)}...`
43
+ : redacted
44
+ }
45
+
33
46
  function notify(observer: CacheObserver, event: CacheEvent): void {
34
47
  try {
35
48
  const result: unknown = observer(event)
@@ -73,9 +86,68 @@ export function createCache(options: CacheOptions = {}): Cache {
73
86
  ((error, key) =>
74
87
  console.error(`[nifra/cache] revalidate ${JSON.stringify(key)} failed:`, error))
75
88
 
89
+ const lastStoreReport = new Map<CacheStoreOp, number>()
90
+ const reportStoreError = (op: CacheStoreOp, key: string, error: unknown): void => {
91
+ try {
92
+ if (options.onStoreError !== undefined) {
93
+ options.onStoreError(error, { op })
94
+ return
95
+ }
96
+ const last = lastStoreReport.get(op)
97
+ const at = now()
98
+ if (last !== undefined && at - last < STORE_REPORT_INTERVAL_MS) return
99
+ lastStoreReport.set(op, at)
100
+ console.error(
101
+ `[nifra/cache] store ${op} failed; loading without the cache (${storeErrorDetail(error, key)})`,
102
+ )
103
+ } catch {
104
+ // A failing reporter must not turn a loaded value into an error.
105
+ }
106
+ }
107
+ /** `wrap`'s read: a store that throws or rejects is a miss, so the loader answers. */
108
+ const readEntry = async (key: string): Promise<StoredEntry | undefined> => {
109
+ try {
110
+ return await store.get(key)
111
+ } catch (error) {
112
+ reportStoreError("get", key, error)
113
+ return undefined
114
+ }
115
+ }
116
+
76
117
  const observer = options.observer
77
118
  // Single-flight: concurrent loads for the same key share one promise (miss stampede + SWR dedup).
78
- const inflight = new Map<string, Promise<unknown>>()
119
+ // A delete, tag invalidation, clear or newer set withdraws a load's right to store its result, so
120
+ // a refresh that started before the invalidation cannot write the old value back. A store with
121
+ // `fence`/`setIfFence` also refuses a load's write after such a change from another cache sharing
122
+ // it, and a delete, invalidation or clear waits for this cache's async writes it covers, so a store
123
+ // that applies requests out of order cannot land one of them after it.
124
+ const inflight = new Map<
125
+ string,
126
+ { readonly promise: Promise<unknown>; readonly tags: readonly string[]; withdraw(): void }
127
+ >()
128
+ const withdraw = (matches: (key: string, tags: readonly string[]) => boolean): void => {
129
+ for (const [key, flight] of inflight) {
130
+ if (!matches(key, flight.tags)) continue
131
+ flight.withdraw()
132
+ inflight.delete(key)
133
+ }
134
+ }
135
+ const fencing = typeof store.fence === "function" && typeof store.setIfFence === "function"
136
+ const writing = new Set<{
137
+ readonly key: string
138
+ readonly tags: readonly string[]
139
+ readonly done: Promise<unknown>
140
+ }>()
141
+ // Run a delete, invalidation or clear after every async write it covers has settled.
142
+ const after = (
143
+ matches: (key: string, tags: readonly string[]) => boolean,
144
+ change: () => void | Promise<void>,
145
+ ): Promise<void> => {
146
+ const covered: Promise<unknown>[] = []
147
+ for (const write of writing) if (matches(write.key, write.tags)) covered.push(write.done)
148
+ if (covered.length === 0) return Promise.resolve(change())
149
+ return Promise.allSettled(covered).then(change)
150
+ }
79
151
 
80
152
  async function get<T = unknown>(key: string): Promise<T | undefined> {
81
153
  const entry = await store.get(key)
@@ -86,30 +158,104 @@ export function createCache(options: CacheOptions = {}): Cache {
86
158
  return (await store.get(key)) !== undefined
87
159
  }
88
160
 
89
- async function set<T>(key: string, value: T, opts: SetOptions = {}): Promise<void> {
161
+ function entryOf(value: unknown, opts: SetOptions): StoredEntry {
90
162
  const ttlMs = opts.ttlMs ?? defaultTtlMs
91
163
  const swrMs = opts.swrMs ?? 0
92
164
  assertDuration(ttlMs, "ttlMs")
93
165
  assertDuration(swrMs, "swrMs")
94
166
  const t = now()
95
- await store.set(
96
- key,
97
- { value, staleAt: t + ttlMs, expiresAt: t + ttlMs + swrMs },
98
- opts.tags ?? [],
167
+ return { value, staleAt: t + ttlMs, expiresAt: t + ttlMs + swrMs }
168
+ }
169
+
170
+ async function put(
171
+ key: string,
172
+ entry: StoredEntry,
173
+ tags: readonly string[],
174
+ fenced?: { readonly token: unknown },
175
+ ): Promise<void> {
176
+ const pending =
177
+ fenced !== undefined && store.setIfFence !== undefined
178
+ ? store.setIfFence(key, entry, tags, fenced.token)
179
+ : store.set(key, entry, tags)
180
+ if (!(pending instanceof Promise)) return
181
+ const record = { key, tags, done: pending }
182
+ writing.add(record)
183
+ try {
184
+ await pending
185
+ } finally {
186
+ writing.delete(record)
187
+ }
188
+ }
189
+
190
+ async function write<T>(key: string, value: T, opts: SetOptions): Promise<void> {
191
+ await put(key, entryOf(value, opts), opts.tags ?? [])
192
+ }
193
+
194
+ function set<T>(key: string, value: T, opts: SetOptions = {}): Promise<void> {
195
+ withdraw((flightKey) => flightKey === key)
196
+ return write(key, value, opts)
197
+ }
198
+
199
+ function remove(key: string): Promise<void> {
200
+ const matches = (writeKey: string): boolean => writeKey === key
201
+ withdraw(matches)
202
+ return after(matches, () => store.delete(key))
203
+ }
204
+
205
+ function invalidateTag(tag: string): Promise<void> {
206
+ const matches = (_key: string, tags: readonly string[]): boolean => tags.includes(tag)
207
+ withdraw(matches)
208
+ return after(matches, () => store.invalidateTag(tag))
209
+ }
210
+
211
+ function clear(): Promise<void> {
212
+ withdraw(() => true)
213
+ return after(
214
+ () => true,
215
+ () => store.clear(),
99
216
  )
100
217
  }
101
218
 
102
219
  function load(key: string, loader: () => unknown, opts: WrapOptions): Promise<unknown> {
103
220
  const existing = inflight.get(key)
104
- if (existing !== undefined) return existing
221
+ if (existing !== undefined) return existing.promise
222
+ let withdrawn = false
105
223
  const p = (async () => {
224
+ // Taken before the loader runs, so a change from another cache during the load refuses its
225
+ // write. Awaited only when async: a sync store keeps the loader starting synchronously.
226
+ let fenced: { readonly token: unknown } | undefined
227
+ let storable = true
228
+ if (fencing) {
229
+ try {
230
+ const token = store.fence?.(key, opts.tags ?? [])
231
+ fenced = { token: token instanceof Promise ? await token : token }
232
+ } catch (error) {
233
+ // Without a token the write could land after another cache's invalidation: load uncached.
234
+ reportStoreError("fence", key, error)
235
+ storable = false
236
+ }
237
+ }
106
238
  const value = await loader()
107
- await set(key, value, opts)
239
+ if (!withdrawn && storable) {
240
+ // Built outside the try: a bad ttlMs is the caller's error, not a store outage.
241
+ const entry = entryOf(value, opts)
242
+ try {
243
+ await put(key, entry, opts.tags ?? [], fenced)
244
+ } catch (error) {
245
+ reportStoreError("set", key, error)
246
+ }
247
+ }
108
248
  return value
109
249
  })().finally(() => {
110
- if (inflight.get(key) === p) inflight.delete(key)
250
+ if (inflight.get(key)?.promise === p) inflight.delete(key)
251
+ })
252
+ inflight.set(key, {
253
+ promise: p,
254
+ tags: opts.tags ?? [],
255
+ withdraw: () => {
256
+ withdrawn = true
257
+ },
111
258
  })
112
- inflight.set(key, p)
113
259
  return p
114
260
  }
115
261
 
@@ -143,7 +289,7 @@ export function createCache(options: CacheOptions = {}): Cache {
143
289
  loader: () => T,
144
290
  opts: WrapOptions = {},
145
291
  ): Promise<Awaited<T>> {
146
- const entry = await store.get(key)
292
+ const entry = await readEntry(key)
147
293
  if (entry !== undefined) {
148
294
  if (now() < entry.staleAt) return entry.value as Awaited<T> // fresh hit
149
295
  revalidate(key, loader as () => unknown, opts, undefined) // stale-but-live → serve stale, refresh in background
@@ -200,7 +346,7 @@ export function createCache(options: CacheOptions = {}): Cache {
200
346
  const started = performance.now()
201
347
  let outcome: CacheOutcome = "error"
202
348
  try {
203
- const entry = await store.get(key)
349
+ const entry = await readEntry(key)
204
350
  if (entry !== undefined) {
205
351
  outcome = readOutcome(entry)
206
352
  if (outcome === "stale") revalidate(key, loader, opts, context)
@@ -224,19 +370,10 @@ export function createCache(options: CacheOptions = {}): Cache {
224
370
  )
225
371
  }
226
372
  },
227
- delete: (key) =>
228
- settle("delete", key, undefined, 0, () => Promise.resolve(store.delete(key)), "ok"),
373
+ delete: (key) => settle("delete", key, undefined, 0, () => remove(key), "ok"),
229
374
  invalidateTag: (tag) =>
230
- settle(
231
- "invalidateTag",
232
- undefined,
233
- tag,
234
- 1,
235
- () => Promise.resolve(store.invalidateTag(tag)),
236
- "ok",
237
- ),
238
- clear: () =>
239
- settle("clear", undefined, undefined, 0, () => Promise.resolve(store.clear()), "ok"),
375
+ settle("invalidateTag", undefined, tag, 1, () => invalidateTag(tag), "ok"),
376
+ clear: () => settle("clear", undefined, undefined, 0, clear, "ok"),
240
377
  for: bind,
241
378
  }
242
379
  }
@@ -251,9 +388,9 @@ export function createCache(options: CacheOptions = {}): Cache {
251
388
  has,
252
389
  set,
253
390
  wrap,
254
- delete: (key) => Promise.resolve(store.delete(key)),
255
- invalidateTag: (tag) => Promise.resolve(store.invalidateTag(tag)),
256
- clear: () => Promise.resolve(store.clear()),
391
+ delete: remove,
392
+ invalidateTag,
393
+ clear,
257
394
  for: bind,
258
395
  }
259
396
  : observed(observer, undefined)
package/src/index.ts CHANGED
@@ -19,6 +19,7 @@ export type {
19
19
  CacheOptions,
20
20
  CacheOutcome,
21
21
  CacheStore,
22
+ CacheStoreOp,
22
23
  CapabilityBeacon,
23
24
  SetOptions,
24
25
  StoredEntry,
@@ -12,6 +12,10 @@ interface Row {
12
12
  tags: readonly string[]
13
13
  }
14
14
 
15
+ // How many distinct keys (and, separately, tags) the fence remembers changing. A load that outlives
16
+ // this many distinct changes is refused its write, which costs one cache miss, never a stale value.
17
+ const MAX_TRACKED_CHANGES = 65_536
18
+
15
19
  export interface MemoryCacheOptions {
16
20
  /** Evict the least-recently-used entry once the count exceeds this. Default 10,000; `0` explicitly
17
21
  * opts into an unbounded cache. */
@@ -27,6 +31,14 @@ export class MemoryCache implements CacheStore {
27
31
  private expirySweep = this.rows.entries()
28
32
  private readonly maxEntries: number
29
33
  private readonly now: () => number
34
+ // The epoch of each key's and tag's latest change, oldest first. Only the change that matters to a
35
+ // load is compared, so unrelated writes never age its fence out. Bounded: a fence older than a
36
+ // forgotten change is refused.
37
+ private readonly keyChanges = new Map<string, number>()
38
+ private readonly tagChanges = new Map<string, number>()
39
+ private clearedAt = 0
40
+ private forgottenAt = 0
41
+ private epoch = 0
30
42
 
31
43
  constructor(options: MemoryCacheOptions = {}) {
32
44
  this.maxEntries = options.maxEntries ?? 10_000
@@ -50,6 +62,7 @@ export class MemoryCache implements CacheStore {
50
62
  }
51
63
 
52
64
  set(key: string, entry: StoredEntry, tags: readonly string[]): void {
65
+ this.record(this.keyChanges, key)
53
66
  // Incremental expiry keeps cold, high-cardinality keys from occupying the entire bound until
54
67
  // their exact key is read. A fixed batch avoids an O(n) latency spike on a write.
55
68
  this.sweepExpired(8)
@@ -75,26 +88,58 @@ export class MemoryCache implements CacheStore {
75
88
  }
76
89
 
77
90
  delete(key: string): void {
91
+ this.record(this.keyChanges, key)
78
92
  this.evict(key)
79
93
  }
80
94
 
81
95
  invalidateTag(tag: string): void {
96
+ this.record(this.tagChanges, tag)
82
97
  const keys = this.tagIndex.get(tag)
83
98
  if (keys === undefined) return
84
99
  for (const key of [...keys]) this.evict(key)
85
100
  }
86
101
 
87
102
  clear(): void {
103
+ // Every older fence is refused from here on, so the earlier changes need no remembering.
104
+ this.clearedAt = ++this.epoch
105
+ this.keyChanges.clear()
106
+ this.tagChanges.clear()
88
107
  this.rows.clear()
89
108
  this.tagIndex.clear()
90
109
  this.expirySweep = this.rows.entries()
91
110
  }
92
111
 
112
+ /** The current epoch: {@link MemoryCache.setIfFence} compares what changed after it. */
113
+ fence(): number {
114
+ return this.epoch
115
+ }
116
+
117
+ setIfFence(key: string, entry: StoredEntry, tags: readonly string[], token: unknown): boolean {
118
+ if (typeof token !== "number" || !Number.isSafeInteger(token) || token > this.epoch)
119
+ return false
120
+ if (token < this.clearedAt || token < this.forgottenAt) return false
121
+ if ((this.keyChanges.get(key) ?? 0) > token) return false
122
+ for (const tag of tags) if ((this.tagChanges.get(tag) ?? 0) > token) return false
123
+ this.set(key, entry, tags)
124
+ return true
125
+ }
126
+
93
127
  /** Live entry count (for observability/tests). */
94
128
  size(): number {
95
129
  return this.rows.size
96
130
  }
97
131
 
132
+ private record(changes: Map<string, number>, name: string): void {
133
+ changes.delete(name)
134
+ changes.set(name, ++this.epoch)
135
+ if (changes.size <= MAX_TRACKED_CHANGES) return
136
+ const first = changes.entries().next()
137
+ if (first.done) return
138
+ const [oldest, at] = first.value
139
+ changes.delete(oldest)
140
+ this.forgottenAt = Math.max(this.forgottenAt, at)
141
+ }
142
+
98
143
  /** Remove a key and unlink it from every tag set it belonged to. */
99
144
  private evict(key: string): void {
100
145
  const row = this.rows.get(key)
package/src/types.ts CHANGED
@@ -28,8 +28,29 @@ export interface CacheStore {
28
28
  /** Drop every entry carrying `tag`. */
29
29
  invalidateTag(tag: string): void | Promise<void>
30
30
  clear(): void | Promise<void>
31
+ /**
32
+ * Optional, with {@link CacheStore.setIfFence}, for a store several caches or processes share. An
33
+ * opaque token naming this moment for `key` and `tags`. A `wrap` load takes one before it starts.
34
+ */
35
+ fence?(key: string, tags: readonly string[]): unknown | Promise<unknown>
36
+ /**
37
+ * Optional, with {@link CacheStore.fence}. Store `entry` only when nothing has run since `fence`
38
+ * returned `token` that a load must not overwrite: a `set` or `delete` of `key`, an `invalidateTag`
39
+ * of one of `tags`, or a `clear`, from any cache sharing the store. The check and the write are one
40
+ * atomic step. Resolves `true` when it wrote. A store that cannot tell may refuse; the load's value
41
+ * still reaches its caller, it is just not cached.
42
+ */
43
+ setIfFence?(
44
+ key: string,
45
+ entry: StoredEntry,
46
+ tags: readonly string[],
47
+ token: unknown,
48
+ ): boolean | Promise<boolean>
31
49
  }
32
50
 
51
+ /** The store call a {@link CacheOptions.onStoreError} report is about. */
52
+ export type CacheStoreOp = "get" | "set" | "fence"
53
+
33
54
  export interface SetOptions {
34
55
  /** Time-to-live (ms) before the value goes stale. Default: the cache's `defaultTtlMs`. */
35
56
  readonly ttlMs?: number
@@ -123,6 +144,12 @@ export interface CacheOptions {
123
144
  readonly now?: () => number
124
145
  /** Called when a background SWR revalidation throws. Default: `console.error`. */
125
146
  readonly onError?: (error: unknown, key: string) => void
147
+ /**
148
+ * Called when `wrap` meets a store failure: a `get` that throws or rejects is answered as a miss, so
149
+ * the loader runs, and a `set` that fails still returns the loaded value. A `fence` that fails
150
+ * leaves that load uncached. Other operations reject as before. Default: one `console.error` line per operation per minute, never naming the key.
151
+ */
152
+ readonly onStoreError?: (error: unknown, info: { readonly op: CacheStoreOp }) => void
126
153
  /**
127
154
  * Receives one {@link CacheEvent} per operation - the seam `cacheTracing()` from `@nifrajs/otel/cache`
128
155
  * plugs into. Without one the cache reads no extra clock and allocates no event.