@bayonai/offline 0.1.0 → 0.3.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.
@@ -7,28 +7,55 @@ export type PendingWriteRecord<Payload = unknown> = {
7
7
  id: string;
8
8
  payload: Payload;
9
9
  savedAt: string;
10
+ snapshotId: string;
10
11
  syncStatus: PendingWriteStatus;
11
12
  };
13
+ export type PendingWriteRecordInput<Payload = unknown> = Omit<PendingWriteRecord<Payload>, "snapshotId"> & {
14
+ snapshotId?: string;
15
+ };
12
16
  export type PendingWritePayloadNormalizer<Payload = unknown> = (payload: unknown) => Payload | null;
13
17
  export type PendingWriteDriver = {
14
18
  clear: (key: string) => Promise<void>;
19
+ clearIf: (key: string, predicate: PendingWritePredicate) => Promise<boolean>;
15
20
  clearAll: () => Promise<void>;
16
21
  close?: () => Promise<void>;
17
22
  get: (key: string) => Promise<unknown | null>;
18
23
  list: () => Promise<unknown[]>;
19
24
  set: (key: string, value: unknown) => Promise<void>;
25
+ setIf: (key: string, value: unknown, predicate: PendingWritePredicate) => Promise<boolean>;
26
+ };
27
+ export type PendingWritePredicate = (value: unknown | null) => boolean;
28
+ export type PendingWriteStoreChange<Payload = unknown> = {
29
+ record: PendingWriteRecord<Payload>;
30
+ type: "set";
31
+ } | {
32
+ id: string;
33
+ type: "clear";
34
+ } | {
35
+ type: "clearAll";
36
+ } | {
37
+ record: PendingWriteRecord<Payload>;
38
+ type: "replace";
20
39
  };
40
+ export type PendingWriteStoreChangeListener<Payload = unknown> = (change: PendingWriteStoreChange<Payload>) => void;
21
41
  export type PendingWriteStore<Payload = unknown> = {
22
42
  clear: (id: string) => Promise<void>;
23
43
  clearAll: () => Promise<void>;
24
- clearIfSavedAt: (id: string, savedAt: string) => Promise<boolean>;
44
+ clearIfSnapshotId: (id: string, snapshotId: string) => Promise<boolean>;
25
45
  close: () => Promise<void>;
26
46
  get: (id: string) => Promise<PendingWriteRecord<Payload> | null>;
27
47
  list: () => Promise<Array<PendingWriteRecord<Payload>>>;
28
- set: (record: PendingWriteRecord<Payload>) => Promise<void>;
48
+ replaceIfSnapshotId: (id: string, snapshotId: string, record: PendingWriteRecord<Payload>) => Promise<boolean>;
49
+ set: (record: PendingWriteRecordInput<Payload>) => Promise<PendingWriteRecord<Payload>>;
50
+ /**
51
+ * Observes changes made through this in-memory store instance. It deliberately
52
+ * does not claim cross-tab/process delivery.
53
+ */
54
+ subscribe: (listener: PendingWriteStoreChangeListener<Payload>) => () => void;
29
55
  };
30
56
  export type PendingWriteStoreOptions<Payload = unknown> = {
31
57
  keyPrefix: string;
58
+ normalizeLegacyRecord?: PendingWritePayloadNormalizer<PendingWriteRecordInput<Payload>>;
32
59
  normalizePayload?: PendingWritePayloadNormalizer<Payload>;
33
60
  schemaVersion?: number;
34
61
  };
@@ -51,13 +78,59 @@ export type SyncPendingWriteResult = {
51
78
  error: string;
52
79
  status: "failed";
53
80
  };
54
- export declare function createPendingWriteStore<Payload = unknown>(driver: PendingWriteDriver, { keyPrefix, normalizePayload, schemaVersion, }: PendingWriteStoreOptions<Payload>): PendingWriteStore<Payload>;
81
+ export type SyncPendingWritesResult = {
82
+ attempted: number;
83
+ failed: number;
84
+ idle: number;
85
+ /** Records still selected after this attempt, including newer superseding writes. */
86
+ remaining: number;
87
+ superseded: number;
88
+ synced: number;
89
+ };
90
+ export type PendingWriteSync<Payload> = (payload: Payload) => Promise<unknown>;
91
+ export type RunPendingWriteResult<TResult, TQueued> = {
92
+ result: TResult;
93
+ status: "synced";
94
+ } | {
95
+ queued: TQueued;
96
+ status: "queued";
97
+ };
98
+ export type PendingWriteSummary = {
99
+ failedCount: number;
100
+ pendingCount: number;
101
+ syncingCount: number;
102
+ totalCount: number;
103
+ };
104
+ export declare function createPendingWriteStore<Payload = unknown>(driver: PendingWriteDriver, { keyPrefix, normalizeLegacyRecord, normalizePayload, schemaVersion, }: PendingWriteStoreOptions<Payload>): PendingWriteStore<Payload>;
55
105
  export declare function createMemoryPendingWriteDriver(): PendingWriteDriver;
56
106
  export declare function createBrowserPendingWriteDriver({ databaseName, databaseVersion, indexedDB, operationTimeoutMs, storeName, }: Omit<BrowserPendingWriteStoreOptions, "keyPrefix" | "normalizePayload" | "schemaVersion">): PendingWriteDriver | null;
57
- export declare function createBrowserPendingWriteStore<Payload = unknown>({ databaseName, databaseVersion, indexedDB, keyPrefix, normalizePayload, operationTimeoutMs, schemaVersion, storeName, }: BrowserPendingWriteStoreOptions<Payload>): PendingWriteStore<Payload>;
107
+ export declare function createBrowserPendingWriteStore<Payload = unknown>({ databaseName, databaseVersion, indexedDB, keyPrefix, normalizeLegacyRecord, normalizePayload, operationTimeoutMs, schemaVersion, storeName, }: BrowserPendingWriteStoreOptions<Payload>): PendingWriteStore<Payload>;
58
108
  export declare function createPendingWriteKey({ id, prefix }: PendingWriteKeyOptions): string;
109
+ export declare function createPendingWriteSnapshotId(): `${string}-${string}-${string}-${string}-${string}`;
59
110
  export declare function syncPendingWrite<Payload>({ id, store, sync, }: {
60
111
  id: string;
61
112
  store: PendingWriteStore<Payload>;
62
- sync: (payload: Payload) => Promise<unknown>;
113
+ sync: PendingWriteSync<Payload>;
63
114
  }): Promise<SyncPendingWriteResult>;
115
+ /**
116
+ * Replays selected writes oldest first and coalesces overlapping runs for the
117
+ * same store/scope. Callers own the selection predicate and idempotent remote
118
+ * mutation semantics.
119
+ */
120
+ export declare function syncPendingWrites<Payload>({ scopeKey, select, store, sync, }: {
121
+ scopeKey?: string;
122
+ select?: (record: PendingWriteRecord<Payload>) => boolean;
123
+ store: PendingWriteStore<Payload>;
124
+ sync: PendingWriteSync<Payload>;
125
+ }): Promise<SyncPendingWritesResult>;
126
+ /**
127
+ * Attempts a remote write now and persists a caller-created durable fallback
128
+ * only when offline or when the supplied retry classifier accepts the error.
129
+ */
130
+ export declare function runPendingWrite<TResult, TQueued>({ enqueue, isOnline, shouldQueue, write, }: {
131
+ enqueue: () => Promise<TQueued>;
132
+ isOnline: () => boolean;
133
+ shouldQueue: (error: unknown) => boolean;
134
+ write: () => Promise<TResult>;
135
+ }): Promise<RunPendingWriteResult<TResult, TQueued>>;
136
+ export declare function summarizePendingWrites<Payload>(records: Array<PendingWriteRecord<Payload>>): PendingWriteSummary;
@@ -1,41 +1,81 @@
1
1
  import { createIdbKeyValueDriver, } from "./idbDriver.js";
2
2
  export const pendingWriteSchemaVersion = 1;
3
3
  export const pendingWriteOperationTimeoutMs = 2_000;
4
- export function createPendingWriteStore(driver, { keyPrefix, normalizePayload, schemaVersion = pendingWriteSchemaVersion, }) {
4
+ export function createPendingWriteStore(driver, { keyPrefix, normalizeLegacyRecord, normalizePayload, schemaVersion = pendingWriteSchemaVersion, }) {
5
+ const listeners = new Set();
6
+ const notify = (change) => {
7
+ for (const listener of listeners) {
8
+ try {
9
+ listener(change);
10
+ }
11
+ catch (error) {
12
+ console.error("[Bayon offline] Pending-write store listener failed.", {
13
+ error,
14
+ type: change.type,
15
+ });
16
+ }
17
+ }
18
+ };
5
19
  return {
6
20
  async clear(id) {
7
21
  await driver.clear(createPendingWriteKey({ id, prefix: keyPrefix }));
22
+ notify({ id, type: "clear" });
8
23
  },
9
24
  async clearAll() {
10
25
  await driver.clearAll();
26
+ notify({ type: "clearAll" });
11
27
  },
12
- async clearIfSavedAt(id, savedAt) {
13
- const current = await this.get(id);
14
- if (!current || current.savedAt !== savedAt) {
15
- return false;
28
+ async clearIfSnapshotId(id, snapshotId) {
29
+ const cleared = await driver.clearIf(createPendingWriteKey({ id, prefix: keyPrefix }), (value) => toPendingWriteEnvelope(value, schemaVersion, {
30
+ normalizeLegacyRecord,
31
+ normalizePayload,
32
+ })?.record.snapshotId === snapshotId);
33
+ if (cleared) {
34
+ notify({ id, type: "clear" });
16
35
  }
17
- await this.clear(id);
18
- return true;
36
+ return cleared;
19
37
  },
20
38
  async close() {
21
39
  await driver.close?.();
22
40
  },
23
41
  async get(id) {
24
- return (toPendingWriteEnvelope(await driver.get(createPendingWriteKey({ id, prefix: keyPrefix })), schemaVersion, { normalizePayload })?.record ?? null);
42
+ return (toPendingWriteEnvelope(await driver.get(createPendingWriteKey({ id, prefix: keyPrefix })), schemaVersion, { normalizeLegacyRecord, normalizePayload })?.record ?? null);
25
43
  },
26
44
  async list() {
27
45
  return (await driver.list())
28
46
  .map((value) => toPendingWriteEnvelope(value, schemaVersion, {
47
+ normalizeLegacyRecord,
29
48
  normalizePayload,
30
49
  })?.record)
31
50
  .filter((record) => Boolean(record))
32
- .sort((left, right) => left.savedAt.localeCompare(right.savedAt));
51
+ .sort((left, right) => left.savedAt.localeCompare(right.savedAt) ||
52
+ left.id.localeCompare(right.id));
53
+ },
54
+ async replaceIfSnapshotId(id, snapshotId, record) {
55
+ const replaced = await driver.setIf(createPendingWriteKey({ id, prefix: keyPrefix }), { record, schemaVersion }, (value) => toPendingWriteEnvelope(value, schemaVersion, {
56
+ normalizeLegacyRecord,
57
+ normalizePayload,
58
+ })?.record.snapshotId === snapshotId);
59
+ if (replaced) {
60
+ notify({ record, type: "replace" });
61
+ }
62
+ return replaced;
33
63
  },
34
64
  async set(record) {
65
+ const storedRecord = {
66
+ ...record,
67
+ snapshotId: record.snapshotId ?? createPendingWriteSnapshotId(),
68
+ };
35
69
  await driver.set(createPendingWriteKey({ id: record.id, prefix: keyPrefix }), {
36
- record,
70
+ record: storedRecord,
37
71
  schemaVersion,
38
72
  });
73
+ notify({ record: storedRecord, type: "set" });
74
+ return storedRecord;
75
+ },
76
+ subscribe(listener) {
77
+ listeners.add(listener);
78
+ return () => listeners.delete(listener);
39
79
  },
40
80
  };
41
81
  }
@@ -45,6 +85,14 @@ export function createMemoryPendingWriteDriver() {
45
85
  async clear(key) {
46
86
  records.delete(key);
47
87
  },
88
+ async clearIf(key, predicate) {
89
+ const current = records.get(key) ?? null;
90
+ if (!predicate(current)) {
91
+ return false;
92
+ }
93
+ records.delete(key);
94
+ return true;
95
+ },
48
96
  async clearAll() {
49
97
  records.clear();
50
98
  },
@@ -58,6 +106,13 @@ export function createMemoryPendingWriteDriver() {
58
106
  async set(key, value) {
59
107
  records.set(key, value);
60
108
  },
109
+ async setIf(key, value, predicate) {
110
+ if (!predicate(records.get(key) ?? null)) {
111
+ return false;
112
+ }
113
+ records.set(key, value);
114
+ return true;
115
+ },
61
116
  };
62
117
  }
63
118
  export function createBrowserPendingWriteDriver({ databaseName, databaseVersion, indexedDB = globalThis.indexedDB, operationTimeoutMs = pendingWriteOperationTimeoutMs, storeName, }) {
@@ -70,7 +125,7 @@ export function createBrowserPendingWriteDriver({ databaseName, databaseVersion,
70
125
  });
71
126
  return driver ? toPendingWriteDriver(driver) : null;
72
127
  }
73
- export function createBrowserPendingWriteStore({ databaseName, databaseVersion, indexedDB = globalThis.indexedDB, keyPrefix, normalizePayload, operationTimeoutMs = pendingWriteOperationTimeoutMs, schemaVersion = pendingWriteSchemaVersion, storeName, }) {
128
+ export function createBrowserPendingWriteStore({ databaseName, databaseVersion, indexedDB = globalThis.indexedDB, keyPrefix, normalizeLegacyRecord, normalizePayload, operationTimeoutMs = pendingWriteOperationTimeoutMs, schemaVersion = pendingWriteSchemaVersion, storeName, }) {
74
129
  const driver = createBrowserPendingWriteDriver({
75
130
  databaseName,
76
131
  databaseVersion,
@@ -78,11 +133,14 @@ export function createBrowserPendingWriteStore({ databaseName, databaseVersion,
78
133
  operationTimeoutMs,
79
134
  storeName,
80
135
  });
81
- return createPendingWriteStore(driver ?? createNoopPendingWriteDriver(), { keyPrefix, normalizePayload, schemaVersion });
136
+ return createPendingWriteStore(driver ?? createNoopPendingWriteDriver(), { keyPrefix, normalizeLegacyRecord, normalizePayload, schemaVersion });
82
137
  }
83
138
  export function createPendingWriteKey({ id, prefix }) {
84
139
  return `${prefix}:${encodeURIComponent(id)}`;
85
140
  }
141
+ export function createPendingWriteSnapshotId() {
142
+ return globalThis.crypto.randomUUID();
143
+ }
86
144
  export async function syncPendingWrite({ id, store, sync, }) {
87
145
  const pending = await store.get(id);
88
146
  if (!pending) {
@@ -91,7 +149,7 @@ export async function syncPendingWrite({ id, store, sync, }) {
91
149
  await markPendingSnapshot({
92
150
  id,
93
151
  nextRecord: { ...pending, error: undefined, syncStatus: "syncing" },
94
- savedAt: pending.savedAt,
152
+ snapshotId: pending.snapshotId,
95
153
  store,
96
154
  });
97
155
  try {
@@ -102,18 +160,100 @@ export async function syncPendingWrite({ id, store, sync, }) {
102
160
  await markPendingSnapshot({
103
161
  id,
104
162
  nextRecord: { ...pending, error: message, syncStatus: "failed" },
105
- savedAt: pending.savedAt,
163
+ snapshotId: pending.snapshotId,
106
164
  store,
107
165
  });
108
166
  return { error: message, status: "failed" };
109
167
  }
110
- return (await store.clearIfSavedAt(id, pending.savedAt))
168
+ return (await store.clearIfSnapshotId(id, pending.snapshotId))
111
169
  ? { status: "synced" }
112
170
  : { status: "superseded" };
113
171
  }
172
+ /**
173
+ * Replays selected writes oldest first and coalesces overlapping runs for the
174
+ * same store/scope. Callers own the selection predicate and idempotent remote
175
+ * mutation semantics.
176
+ */
177
+ export function syncPendingWrites({ scopeKey = "all", select = () => true, store, sync, }) {
178
+ let inFlightByScope = pendingWriteSyncs.get(store);
179
+ if (!inFlightByScope) {
180
+ inFlightByScope = new Map();
181
+ pendingWriteSyncs.set(store, inFlightByScope);
182
+ }
183
+ const existing = inFlightByScope.get(scopeKey);
184
+ if (existing) {
185
+ return existing;
186
+ }
187
+ const syncRun = performPendingWriteSync({ select, store, sync });
188
+ inFlightByScope.set(scopeKey, syncRun);
189
+ const clearInFlight = () => {
190
+ inFlightByScope.delete(scopeKey);
191
+ };
192
+ void syncRun.then(clearInFlight, clearInFlight);
193
+ return syncRun;
194
+ }
195
+ /**
196
+ * Attempts a remote write now and persists a caller-created durable fallback
197
+ * only when offline or when the supplied retry classifier accepts the error.
198
+ */
199
+ export async function runPendingWrite({ enqueue, isOnline, shouldQueue, write, }) {
200
+ if (!isOnline()) {
201
+ return { queued: await enqueue(), status: "queued" };
202
+ }
203
+ try {
204
+ return { result: await write(), status: "synced" };
205
+ }
206
+ catch (error) {
207
+ if (!shouldQueue(error)) {
208
+ throw error;
209
+ }
210
+ return { queued: await enqueue(), status: "queued" };
211
+ }
212
+ }
213
+ export function summarizePendingWrites(records) {
214
+ return records.reduce((summary, record) => {
215
+ summary.totalCount += 1;
216
+ if (record.syncStatus === "failed") {
217
+ summary.failedCount += 1;
218
+ }
219
+ else if (record.syncStatus === "syncing") {
220
+ summary.syncingCount += 1;
221
+ }
222
+ else {
223
+ summary.pendingCount += 1;
224
+ }
225
+ return summary;
226
+ }, { failedCount: 0, pendingCount: 0, syncingCount: 0, totalCount: 0 });
227
+ }
228
+ const pendingWriteSyncs = new WeakMap();
229
+ async function performPendingWriteSync({ select, store, sync, }) {
230
+ const summary = {
231
+ attempted: 0,
232
+ failed: 0,
233
+ idle: 0,
234
+ remaining: 0,
235
+ superseded: 0,
236
+ synced: 0,
237
+ };
238
+ const records = (await store.list()).filter(select);
239
+ for (const record of records) {
240
+ const result = await syncPendingWrite({
241
+ id: record.id,
242
+ store,
243
+ sync,
244
+ });
245
+ summary.attempted += 1;
246
+ summary[result.status] += 1;
247
+ }
248
+ summary.remaining = (await store.list()).filter(select).length;
249
+ return summary;
250
+ }
114
251
  function createNoopPendingWriteDriver() {
115
252
  return {
116
253
  async clear() { },
254
+ async clearIf() {
255
+ return false;
256
+ },
117
257
  async clearAll() { },
118
258
  async close() { },
119
259
  async get() {
@@ -123,43 +263,41 @@ function createNoopPendingWriteDriver() {
123
263
  return [];
124
264
  },
125
265
  async set() { },
266
+ async setIf() {
267
+ return false;
268
+ },
126
269
  };
127
270
  }
128
271
  function toPendingWriteDriver(driver) {
129
272
  return {
130
273
  clear: driver.delete,
274
+ clearIf: driver.deleteIf,
131
275
  clearAll: driver.clear,
132
276
  close: driver.close,
133
277
  get: driver.get,
134
278
  list: driver.list,
135
279
  set: driver.set,
280
+ setIf: driver.setIf,
136
281
  };
137
282
  }
138
- async function markPendingSnapshot({ id, nextRecord, savedAt, store, }) {
139
- const current = await store.get(id);
140
- if (current?.savedAt !== savedAt) {
141
- return false;
142
- }
143
- await store.set(nextRecord);
144
- return true;
283
+ async function markPendingSnapshot({ id, nextRecord, snapshotId, store, }) {
284
+ return store.replaceIfSnapshotId(id, snapshotId, nextRecord);
145
285
  }
146
- function toPendingWriteEnvelope(value, schemaVersion, { normalizePayload }) {
147
- if (!value ||
148
- typeof value !== "object" ||
149
- !("schemaVersion" in value) ||
150
- value.schemaVersion !== schemaVersion ||
151
- !("record" in value) ||
152
- !isPendingWriteRecord(value.record)) {
153
- return null;
154
- }
155
- const record = normalizePendingWriteRecord(value.record, normalizePayload);
286
+ function toPendingWriteEnvelope(value, schemaVersion, { normalizeLegacyRecord, normalizePayload }) {
287
+ const envelopeRecord = readPendingWriteEnvelopeRecord(value, schemaVersion);
288
+ const legacyRecord = envelopeRecord
289
+ ? null
290
+ : (normalizeLegacyRecord?.(value) ?? null);
291
+ const record = normalizePendingWriteRecord(envelopeRecord ?? legacyRecord, normalizePayload);
156
292
  return record ? { record, schemaVersion } : null;
157
293
  }
158
294
  function normalizePendingWriteRecord(record, normalizePayload) {
159
- if (!normalizePayload) {
160
- return record;
295
+ if (!record) {
296
+ return null;
161
297
  }
162
- const payload = normalizePayload(record.payload);
298
+ const payload = normalizePayload
299
+ ? normalizePayload(record.payload)
300
+ : record.payload;
163
301
  if (payload === null) {
164
302
  return null;
165
303
  }
@@ -168,9 +306,24 @@ function normalizePendingWriteRecord(record, normalizePayload) {
168
306
  id: record.id,
169
307
  payload,
170
308
  savedAt: record.savedAt,
309
+ snapshotId: record.snapshotId ?? createLegacyPendingWriteSnapshotId(record),
171
310
  syncStatus: record.syncStatus,
172
311
  };
173
312
  }
313
+ function readPendingWriteEnvelopeRecord(value, schemaVersion) {
314
+ if (!value ||
315
+ typeof value !== "object" ||
316
+ !("schemaVersion" in value) ||
317
+ value.schemaVersion !== schemaVersion ||
318
+ !("record" in value) ||
319
+ !isPendingWriteRecord(value.record)) {
320
+ return null;
321
+ }
322
+ return value.record;
323
+ }
324
+ function createLegacyPendingWriteSnapshotId(record) {
325
+ return `legacy:${encodeURIComponent(record.id)}:${encodeURIComponent(record.savedAt)}`;
326
+ }
174
327
  function isPendingWriteRecord(value) {
175
328
  return (value !== null &&
176
329
  typeof value === "object" &&
@@ -178,6 +331,7 @@ function isPendingWriteRecord(value) {
178
331
  typeof value.id === "string" &&
179
332
  "savedAt" in value &&
180
333
  typeof value.savedAt === "string" &&
334
+ (!("snapshotId" in value) || typeof value.snapshotId === "string") &&
181
335
  "syncStatus" in value &&
182
336
  (value.syncStatus === "pending" ||
183
337
  value.syncStatus === "syncing" ||
@@ -1,6 +1,8 @@
1
1
  import { type BrowserKeyValueDriverOptions } from "./idbDriver";
2
2
  export declare const readCacheSchemaVersion = 1;
3
3
  export declare const readCacheFreshnessMaxAgeMs = 30000;
4
+ export declare const durableReadCacheFreshnessMaxAgeMs: number;
5
+ export declare const identityReadCacheFreshnessMaxAgeMs: number;
4
6
  export declare const browserReadCacheOperationTimeoutMs = 2000;
5
7
  export type ReadCacheHit<T> = {
6
8
  fromCache: true;
@@ -10,14 +12,27 @@ export type ReadCacheHit<T> = {
10
12
  export type ReadCacheDriver = {
11
13
  clear: () => Promise<void>;
12
14
  close?: () => Promise<void>;
15
+ delete: (key: string) => Promise<void>;
13
16
  get: (key: string) => Promise<unknown | null>;
17
+ listEntries?: () => Promise<Array<{
18
+ key: string;
19
+ value: unknown;
20
+ }>>;
14
21
  set: (key: string, value: unknown) => Promise<void>;
15
22
  };
16
23
  export type ReadCache = {
24
+ /**
25
+ * Resolves a stable cache for the duration of a resource load. Dynamic
26
+ * adapters use this to pin the active account before asynchronous work.
27
+ */
28
+ capture?: () => ReadCache;
17
29
  clear: () => Promise<void>;
18
30
  close: () => Promise<void>;
31
+ delete: (key: string) => Promise<void>;
19
32
  get: <T>(key: string) => Promise<ReadCacheHit<T> | null>;
33
+ getVersion: (key: string) => string;
20
34
  set: <T>(key: string, value: T) => Promise<void>;
35
+ setIfVersion: <T>(key: string, value: T, version: string) => Promise<boolean>;
21
36
  };
22
37
  export type ReadThroughCacheResult<T> = {
23
38
  data: T;
@@ -44,16 +59,33 @@ export type ReadCacheKeyOptions = {
44
59
  export declare function createReadCache(driver: ReadCacheDriver): ReadCache;
45
60
  export declare function createMemoryReadCacheDriver(): ReadCacheDriver;
46
61
  export declare function createBrowserReadCache({ databaseName, databaseVersion, indexedDB, operationTimeoutMs, storeName, }: BrowserReadCacheOptions): ReadCache;
62
+ /**
63
+ * Deletes only valid read-cache envelopes older than the supplied age. It is
64
+ * opt-in and returns zero for custom cache adapters that cannot enumerate keys.
65
+ */
66
+ export declare function pruneReadCache({ cache, maxAgeMs, now, }: {
67
+ cache: ReadCache;
68
+ maxAgeMs: number;
69
+ now?: () => number;
70
+ }): Promise<number>;
47
71
  export declare function createReadCacheKey({ namespace, params, prefix, scope, }: ReadCacheKeyOptions): string;
48
- export declare function readThroughCache<T>({ cache, key, loadFresh, }: {
72
+ export declare function readThroughCache<T>({ cache, forceFresh, key, loadFresh, maxAgeMs, }: {
49
73
  cache: ReadCache;
74
+ /**
75
+ * Invalidates the current value before loading. Use after a successful write
76
+ * when a cached or earlier in-flight response must not become authoritative.
77
+ */
78
+ forceFresh?: boolean;
50
79
  key: string;
51
80
  loadFresh: () => Promise<T>;
81
+ maxAgeMs?: number;
52
82
  }): Promise<ReadThroughCacheResult<T>>;
53
- export declare function loadReadCacheResource<T>({ cache, key, loadFresh, maxAgeMs, onUpdate, }: {
83
+ export declare function loadReadCacheResource<T>({ cache, key, loadFresh, maxAgeMs, onCacheWriteError, onUpdate, skipCache, }: {
54
84
  cache: ReadCache;
55
85
  key: string;
56
86
  loadFresh: () => Promise<T>;
57
87
  maxAgeMs?: number;
88
+ onCacheWriteError?: (error: Error) => void;
58
89
  onUpdate: (update: ReadCacheResourceUpdate<T>) => void;
90
+ skipCache?: boolean;
59
91
  }): Promise<T>;