@bayonai/offline 0.8.0 → 0.9.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.
@@ -0,0 +1,42 @@
1
+ import { type PendingWriteRecord, type PendingWriteStore, type PendingWriteSync, type SyncPendingWritesResult } from "./pendingWrites.js";
2
+ import type { BrowserPendingWriteRecoveryCoordination } from "./browserCoordination.js";
3
+ export declare const initialPendingWriteRetryDelayMs = 2000;
4
+ export declare const maxPendingWriteRetryDelayMs = 60000;
5
+ export type BrowserPendingWriteRecoveryEnvironment = {
6
+ addEventListener: (type: "focus" | "offline" | "online", listener: () => void) => void;
7
+ clearTimeout: (timer: ReturnType<typeof setTimeout>) => void;
8
+ isOnline: () => boolean;
9
+ removeEventListener: (type: "focus" | "offline" | "online", listener: () => void) => void;
10
+ setTimeout: (callback: () => void, delayMs: number) => ReturnType<typeof setTimeout>;
11
+ };
12
+ export type BrowserPendingWriteRecovery<Payload> = {
13
+ /** Signals that a caller has persisted a record outside the store subscription. */
14
+ notifyQueued: () => void;
15
+ /** Runs one immediate, silent recovery attempt when the browser is online. */
16
+ recoverNow: () => Promise<SyncPendingWritesResult | null>;
17
+ start: () => void;
18
+ stop: () => void;
19
+ };
20
+ type BrowserPendingWriteRecoveryOptions<Payload> = {
21
+ /** Optional BroadcastChannel/Web Locks coordination supplied by the package factory. */
22
+ coordination?: BrowserPendingWriteRecoveryCoordination;
23
+ environment?: BrowserPendingWriteRecoveryEnvironment;
24
+ initialRetryDelayMs?: number;
25
+ maxRetryDelayMs?: number;
26
+ /** Diagnostic callback only. Recovery never renders or presents product UI. */
27
+ onError?: (error: unknown) => void;
28
+ /** Diagnostic callback only. Recovery never renders or presents product UI. */
29
+ onResult?: (result: SyncPendingWritesResult) => void;
30
+ scopeKey?: string;
31
+ select?: (record: PendingWriteRecord<Payload>) => boolean;
32
+ store: PendingWriteStore<Payload>;
33
+ } & ({
34
+ /** Runs one complete host-owned recovery pass, such as through an ordered executor. */
35
+ recover: () => Promise<SyncPendingWritesResult>;
36
+ sync?: never;
37
+ } | {
38
+ recover?: never;
39
+ sync: PendingWriteSync<Payload>;
40
+ });
41
+ export declare function createBrowserPendingWriteRecovery<Payload>(options: BrowserPendingWriteRecoveryOptions<Payload>): BrowserPendingWriteRecovery<Payload>;
42
+ export {};
@@ -0,0 +1,49 @@
1
+ import { type PendingWriteRecord, type PendingWriteStore } from "./pendingWrites.js";
2
+ export type DurableCommandErrorCategory = "auth" | "conflict" | "connectivity" | "local-storage" | "permission" | "unexpected" | "validation";
3
+ export type DurableCommandOperation<Command> = {
4
+ command: Command;
5
+ error?: DurableCommandErrorCategory;
6
+ id: string;
7
+ status: "accepted" | "failed" | "syncing";
8
+ };
9
+ export type DurableCommandSnapshot<State, Command> = {
10
+ operations: DurableCommandOperation<Command>[];
11
+ state: State;
12
+ };
13
+ export type DurableCommandDispatch = {
14
+ accepted: Promise<void>;
15
+ operationId: string;
16
+ };
17
+ export type DurableCommandExecutionContext = {
18
+ /** Stable across retries so the remote command can be idempotent. */
19
+ operationId: string;
20
+ };
21
+ export type DurableCommandStore<State, Command, Scope> = {
22
+ cancel: (operationId: string) => Promise<boolean>;
23
+ close: () => void;
24
+ dispatch: (command: Command, options?: {
25
+ notBefore?: string;
26
+ }) => DurableCommandDispatch;
27
+ getSnapshot: () => DurableCommandSnapshot<State, Command>;
28
+ recover: () => Promise<void>;
29
+ refresh: (scope: Scope) => Promise<void>;
30
+ subscribe: (listener: () => void) => () => void;
31
+ };
32
+ /**
33
+ * Hosts provide domain projection and transport; this package only owns the
34
+ * durable command lifecycle. A command becomes visible immediately, but is
35
+ * never sent remotely until IndexedDB has accepted it.
36
+ */
37
+ export declare function createDurableCommandStore<State, Command, Scope, Result = unknown>({ canCancel, classifyError, createId, execute, initialState, load, now, project, reconcile, shouldRetry, store, }: {
38
+ canCancel?: (command: Command, now: string) => boolean;
39
+ classifyError?: (error: unknown) => DurableCommandErrorCategory;
40
+ createId?: () => string;
41
+ execute: (command: Command, context: DurableCommandExecutionContext) => Promise<Result>;
42
+ initialState: State;
43
+ load: (scope: Scope) => Promise<State>;
44
+ now?: () => string;
45
+ project: (state: State, commands: readonly PendingWriteRecord<Command>[]) => State;
46
+ reconcile?: (state: State, command: Command, result: Result) => State;
47
+ shouldRetry?: (category: DurableCommandErrorCategory, command: Command) => boolean;
48
+ store: PendingWriteStore<Command>;
49
+ }): DurableCommandStore<State, Command, Scope>;
@@ -0,0 +1,26 @@
1
+ export type BrowserKeyValueDriver = {
2
+ clear: () => Promise<void>;
3
+ close: () => Promise<void>;
4
+ delete: (key: string) => Promise<void>;
5
+ deleteIf: (key: string, predicate: KeyValuePredicate) => Promise<boolean>;
6
+ get: (key: string) => Promise<unknown | null>;
7
+ list: () => Promise<unknown[]>;
8
+ listEntries: () => Promise<Array<{
9
+ key: string;
10
+ value: unknown;
11
+ }>>;
12
+ set: (key: string, value: unknown) => Promise<void>;
13
+ setIf: (key: string, value: unknown, predicate: KeyValuePredicate) => Promise<boolean>;
14
+ };
15
+ export type KeyValuePredicate = (value: unknown | null) => boolean;
16
+ export type BrowserKeyValueDriverOptions = {
17
+ databaseName: string;
18
+ databaseVersion?: number;
19
+ indexedDB?: IDBFactory;
20
+ operationTimeoutMs: number;
21
+ storeName: string;
22
+ };
23
+ export declare class OfflineStorageError extends Error {
24
+ name: string;
25
+ }
26
+ export declare function createIdbKeyValueDriver({ databaseName, databaseVersion, indexedDB, operationTimeoutMs, storeName, }: BrowserKeyValueDriverOptions): BrowserKeyValueDriver | null;
@@ -0,0 +1,7 @@
1
+ export * from "./readCache.js";
2
+ export * from "./pendingWrites.js";
3
+ export * from "./browserRecovery.js";
4
+ export * from "./browserCoordination.js";
5
+ export * from "./browserFallbackRecovery.js";
6
+ export * from "./durableCommandStore.js";
7
+ export { OfflineStorageError } from "./idbDriver.js";
@@ -0,0 +1,179 @@
1
+ import { type BrowserKeyValueDriverOptions } from "./idbDriver.js";
2
+ export declare const pendingWriteSchemaVersion = 1;
3
+ export declare const pendingWriteOperationTimeoutMs = 2000;
4
+ export declare const pendingWriteSyncLeaseTimeoutMs = 60000;
5
+ export type PendingWriteStatus = "pending" | "syncing" | "failed";
6
+ export type PendingWriteRecord<Payload = unknown> = {
7
+ error?: string;
8
+ id: string;
9
+ /** Do not execute this accepted command before this ISO timestamp. */
10
+ notBefore?: string;
11
+ payload: Payload;
12
+ savedAt: string;
13
+ snapshotId: string;
14
+ /** Timestamp of the active remote-sync claim, absent when not owned. */
15
+ syncingAt?: string;
16
+ syncStatus: PendingWriteStatus;
17
+ };
18
+ export type PendingWriteRecordInput<Payload = unknown> = Omit<PendingWriteRecord<Payload>, "snapshotId"> & {
19
+ snapshotId?: string;
20
+ };
21
+ export type PendingWritePayloadNormalizer<Payload = unknown> = (payload: unknown) => Payload | null;
22
+ export type PendingWriteDriver = {
23
+ clear: (key: string) => Promise<void>;
24
+ clearIf: (key: string, predicate: PendingWritePredicate) => Promise<boolean>;
25
+ clearAll?: () => Promise<void>;
26
+ close?: () => Promise<void>;
27
+ get: (key: string) => Promise<unknown | null>;
28
+ list: () => Promise<unknown[]>;
29
+ listEntries?: () => Promise<Array<{
30
+ key: string;
31
+ value: unknown;
32
+ }>>;
33
+ set: (key: string, value: unknown) => Promise<void>;
34
+ setIf: (key: string, value: unknown, predicate: PendingWritePredicate) => Promise<boolean>;
35
+ };
36
+ export type PendingWritePredicate = (value: unknown | null) => boolean;
37
+ export type PendingWriteStoreChange<Payload = unknown> = {
38
+ record: PendingWriteRecord<Payload>;
39
+ type: "set";
40
+ } | {
41
+ id: string;
42
+ type: "clear";
43
+ } | {
44
+ type: "clearAll";
45
+ } | {
46
+ record: PendingWriteRecord<Payload>;
47
+ type: "replace";
48
+ };
49
+ export type PendingWriteStoreChangeListener<Payload = unknown> = (change: PendingWriteStoreChange<Payload>) => void;
50
+ export type PendingWriteStore<Payload = unknown> = {
51
+ claimIfSnapshotId: (id: string, snapshotId: string, record: PendingWriteRecord<Payload>, now: string) => Promise<boolean>;
52
+ clear: (id: string) => Promise<void>;
53
+ clearAll: () => Promise<void>;
54
+ clearIfSnapshotId: (id: string, snapshotId: string) => Promise<boolean>;
55
+ close: () => Promise<void>;
56
+ get: (id: string) => Promise<PendingWriteRecord<Payload> | null>;
57
+ list: () => Promise<Array<PendingWriteRecord<Payload>>>;
58
+ replaceIfSnapshotId: (id: string, snapshotId: string, record: PendingWriteRecord<Payload>) => Promise<boolean>;
59
+ set: (record: PendingWriteRecordInput<Payload>) => Promise<PendingWriteRecord<Payload>>;
60
+ /**
61
+ * Observes changes made through this in-memory store instance. It deliberately
62
+ * does not claim cross-tab/process delivery.
63
+ */
64
+ subscribe: (listener: PendingWriteStoreChangeListener<Payload>) => () => void;
65
+ };
66
+ export type PendingWriteStoreOptions<Payload = unknown> = {
67
+ keyPrefix: string;
68
+ normalizeLegacyRecord?: PendingWritePayloadNormalizer<PendingWriteRecordInput<Payload>>;
69
+ normalizePayload?: PendingWritePayloadNormalizer<Payload>;
70
+ schemaVersion?: number;
71
+ };
72
+ export type BrowserPendingWriteStoreOptions<Payload = unknown> = Partial<Pick<BrowserKeyValueDriverOptions, "databaseVersion" | "indexedDB">> & PendingWriteStoreOptions<Payload> & {
73
+ databaseName: string;
74
+ operationTimeoutMs?: number;
75
+ storeName: string;
76
+ };
77
+ export type PendingWriteKeyOptions = {
78
+ id: string;
79
+ prefix: string;
80
+ };
81
+ export type SyncPendingWriteResult = {
82
+ status: "idle";
83
+ } | {
84
+ status: "synced";
85
+ } | {
86
+ status: "superseded";
87
+ } | {
88
+ error: string;
89
+ failure: unknown;
90
+ status: "failed";
91
+ };
92
+ export type SyncPendingWritesResult = {
93
+ attempted: number;
94
+ failed: number;
95
+ idle: number;
96
+ /** Records still selected after this attempt, including newer superseding writes. */
97
+ remaining: number;
98
+ superseded: number;
99
+ synced: number;
100
+ };
101
+ export type PendingWriteSyncContext = {
102
+ /** Stable durable-record identity; safe to use as a remote idempotency key. */
103
+ recordId: string;
104
+ };
105
+ export type PendingWriteSync<Payload, Result = unknown> = (payload: Payload, context: PendingWriteSyncContext) => Promise<Result>;
106
+ export type RunPendingWriteResult<TResult, TQueued> = {
107
+ result: TResult;
108
+ status: "synced";
109
+ } | {
110
+ queued: TQueued;
111
+ status: "queued";
112
+ };
113
+ export type PendingWriteRunOptions<TResult, TQueued> = {
114
+ enqueue: () => Promise<TQueued>;
115
+ isOnline: () => boolean;
116
+ shouldQueue: (error: unknown) => boolean;
117
+ write: () => Promise<TResult>;
118
+ };
119
+ /**
120
+ * Serializes recovery and new writes for one caller-defined scope.
121
+ *
122
+ * A queued write that cannot be replayed stays ahead of later writes. This
123
+ * preserves remote write order; callers retain ownership of scope selection
124
+ * and idempotent remote mutation semantics.
125
+ */
126
+ export type OrderedPendingWriteExecutor<Payload> = {
127
+ recover: () => Promise<SyncPendingWritesResult>;
128
+ run: <TResult, TQueued>(options: PendingWriteRunOptions<TResult, TQueued>) => Promise<RunPendingWriteResult<TResult, TQueued>>;
129
+ };
130
+ export type PendingWriteSummary = {
131
+ failedCount: number;
132
+ pendingCount: number;
133
+ syncingCount: number;
134
+ totalCount: number;
135
+ };
136
+ export declare function createPendingWriteStore<Payload = unknown>(driver: PendingWriteDriver, { keyPrefix, normalizeLegacyRecord, normalizePayload, schemaVersion, }: PendingWriteStoreOptions<Payload>): PendingWriteStore<Payload>;
137
+ export declare function createMemoryPendingWriteDriver(): PendingWriteDriver;
138
+ export declare function createBrowserPendingWriteDriver({ databaseName, databaseVersion, indexedDB, operationTimeoutMs, storeName, }: Omit<BrowserPendingWriteStoreOptions, "keyPrefix" | "normalizePayload" | "schemaVersion">): PendingWriteDriver | null;
139
+ export declare function createBrowserPendingWriteStore<Payload = unknown>({ databaseName, databaseVersion, indexedDB, keyPrefix, normalizeLegacyRecord, normalizePayload, operationTimeoutMs, schemaVersion, storeName, }: BrowserPendingWriteStoreOptions<Payload>): PendingWriteStore<Payload>;
140
+ export declare function createPendingWriteKey({ id, prefix }: PendingWriteKeyOptions): string;
141
+ export declare function createPendingWriteSnapshotId(): string;
142
+ export declare function syncPendingWrite<Payload>({ id, now, store, sync, }: {
143
+ id: string;
144
+ now?: () => string;
145
+ store: PendingWriteStore<Payload>;
146
+ sync: PendingWriteSync<Payload>;
147
+ }): Promise<SyncPendingWriteResult>;
148
+ /**
149
+ * Replays selected writes oldest first and coalesces overlapping runs for the
150
+ * same store/scope. Callers own the selection predicate and idempotent remote
151
+ * mutation semantics.
152
+ */
153
+ export declare function syncPendingWrites<Payload>({ now, scopeKey, select, stopOnFailure, store, sync, }: {
154
+ now?: () => string;
155
+ scopeKey?: string;
156
+ select?: (record: PendingWriteRecord<Payload>) => boolean;
157
+ /** Preserve caller ordering by leaving later records for a future retry. */
158
+ stopOnFailure?: boolean;
159
+ store: PendingWriteStore<Payload>;
160
+ sync: PendingWriteSync<Payload>;
161
+ }): Promise<SyncPendingWritesResult>;
162
+ /**
163
+ * Attempts a remote write now and persists a caller-created durable fallback
164
+ * only when offline or when the supplied retry classifier accepts the error.
165
+ */
166
+ export declare function runPendingWrite<TResult, TQueued>({ enqueue, isOnline, shouldQueue, write, }: PendingWriteRunOptions<TResult, TQueued>): Promise<RunPendingWriteResult<TResult, TQueued>>;
167
+ export declare function summarizePendingWrites<Payload>(records: Array<PendingWriteRecord<Payload>>): PendingWriteSummary;
168
+ /**
169
+ * Creates a serial execution lane for a durable scope such as one user or
170
+ * workspace. Use its `recover` method for background replay and `run` for new
171
+ * writes so a newer remote mutation cannot overtake an older queued mutation.
172
+ */
173
+ export declare function createOrderedPendingWriteExecutor<Payload>({ now, scopeKey, select, store, sync, }: {
174
+ now?: () => string;
175
+ scopeKey?: string;
176
+ select?: (record: PendingWriteRecord<Payload>) => boolean;
177
+ store: PendingWriteStore<Payload>;
178
+ sync: PendingWriteSync<Payload>;
179
+ }): OrderedPendingWriteExecutor<Payload>;
@@ -31,6 +31,29 @@ function createPendingWriteStore(driver, { keyPrefix, normalizeLegacyRecord, nor
31
31
  }
32
32
  }
33
33
  };
34
+ async function listScopedRecords() {
35
+ const readOptions = { normalizeLegacyRecord, normalizePayload };
36
+ const entries = await driver.listEntries?.();
37
+ if (entries) {
38
+ const keyPrefixBoundary = `${keyPrefix}:`;
39
+ return entries
40
+ .filter(({ key }) => key.startsWith(keyPrefixBoundary))
41
+ .map(({ value }) => toPendingWriteEnvelope(value, schemaVersion, readOptions)
42
+ ?.record)
43
+ .filter((record) => Boolean(record));
44
+ }
45
+ // Compatibility path for custom drivers created before listEntries was
46
+ // available. Point reads prove that each candidate belongs to this prefix.
47
+ const candidateIds = new Set((await driver.list())
48
+ .map((value) => toPendingWriteEnvelope(value, schemaVersion, readOptions)
49
+ ?.record.id)
50
+ .filter((id) => Boolean(id)));
51
+ const records = await Promise.all([...candidateIds].map(async (id) => {
52
+ const value = await driver.get(createPendingWriteKey({ id, prefix: keyPrefix }));
53
+ return toPendingWriteEnvelope(value, schemaVersion, readOptions)?.record;
54
+ }));
55
+ return records.filter((record) => Boolean(record));
56
+ }
34
57
  return {
35
58
  async claimIfSnapshotId(id, snapshotId, record, now) {
36
59
  const claimed = await driver.setIf(createPendingWriteKey({ id, prefix: keyPrefix }), { record, schemaVersion }, (value) => {
@@ -51,7 +74,20 @@ function createPendingWriteStore(driver, { keyPrefix, normalizeLegacyRecord, nor
51
74
  notify({ id, type: "clear" });
52
75
  },
53
76
  async clearAll() {
54
- await driver.clearAll();
77
+ const entries = await driver.listEntries?.();
78
+ if (entries) {
79
+ const keyPrefixBoundary = `${keyPrefix}:`;
80
+ await Promise.all(entries
81
+ .filter(({ key }) => key.startsWith(keyPrefixBoundary))
82
+ .map(({ key }) => driver.clear(key)));
83
+ }
84
+ else {
85
+ const records = await listScopedRecords();
86
+ await Promise.all(records.map((record) => driver.clearIf(createPendingWriteKey({ id: record.id, prefix: keyPrefix }), (value) => toPendingWriteEnvelope(value, schemaVersion, {
87
+ normalizeLegacyRecord,
88
+ normalizePayload,
89
+ })?.record.snapshotId === record.snapshotId)));
90
+ }
55
91
  notify({ type: "clearAll" });
56
92
  },
57
93
  async clearIfSnapshotId(id, snapshotId) {
@@ -71,13 +107,7 @@ function createPendingWriteStore(driver, { keyPrefix, normalizeLegacyRecord, nor
71
107
  return (toPendingWriteEnvelope(await driver.get(createPendingWriteKey({ id, prefix: keyPrefix })), schemaVersion, { normalizeLegacyRecord, normalizePayload })?.record ?? null);
72
108
  },
73
109
  async list() {
74
- return (await driver.list())
75
- .map((value) => toPendingWriteEnvelope(value, schemaVersion, {
76
- normalizeLegacyRecord,
77
- normalizePayload,
78
- })?.record)
79
- .filter((record) => Boolean(record))
80
- .sort((left, right) => left.savedAt.localeCompare(right.savedAt) ||
110
+ return (await listScopedRecords()).sort((left, right) => left.savedAt.localeCompare(right.savedAt) ||
81
111
  left.id.localeCompare(right.id));
82
112
  },
83
113
  async replaceIfSnapshotId(id, snapshotId, record) {
@@ -132,6 +162,9 @@ function createMemoryPendingWriteDriver() {
132
162
  async list() {
133
163
  return [...records.values()];
134
164
  },
165
+ async listEntries() {
166
+ return [...records].map(([key, value]) => ({ key, value }));
167
+ },
135
168
  async set(key, value) {
136
169
  records.set(key, value);
137
170
  },
@@ -388,7 +421,6 @@ function createUnavailablePendingWriteDriver() {
388
421
  async clearIf() {
389
422
  return false;
390
423
  },
391
- async clearAll() { },
392
424
  async close() { },
393
425
  async get() {
394
426
  return null;
@@ -412,6 +444,7 @@ function toPendingWriteDriver(driver) {
412
444
  close: driver.close,
413
445
  get: driver.get,
414
446
  list: driver.list,
447
+ listEntries: driver.listEntries,
415
448
  set: driver.set,
416
449
  setIf: driver.setIf,
417
450
  };
@@ -0,0 +1,112 @@
1
+ import { type BrowserKeyValueDriverOptions } from "./idbDriver.js";
2
+ export declare const readCacheSchemaVersion = 1;
3
+ export declare const readCacheFreshnessMaxAgeMs = 30000;
4
+ export declare const durableReadCacheFreshnessMaxAgeMs: number;
5
+ export declare const identityReadCacheFreshnessMaxAgeMs: number;
6
+ export declare const browserReadCacheOperationTimeoutMs = 1000;
7
+ export type ReadCacheHit<T> = {
8
+ fromCache: true;
9
+ savedAt: string;
10
+ value: T;
11
+ };
12
+ export type ReadCacheDriver = {
13
+ clear: () => Promise<void>;
14
+ close?: () => Promise<void>;
15
+ delete: (key: string) => Promise<void>;
16
+ get: (key: string) => Promise<unknown | null>;
17
+ listEntries?: () => Promise<Array<{
18
+ key: string;
19
+ value: unknown;
20
+ }>>;
21
+ set: (key: string, value: unknown) => Promise<void>;
22
+ };
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;
29
+ clear: () => Promise<void>;
30
+ close: () => Promise<void>;
31
+ delete: (key: string) => Promise<void>;
32
+ /** Advances a key's generation without deleting its retained value. */
33
+ invalidate?: (key: string) => void;
34
+ get: <T>(key: string) => Promise<ReadCacheHit<T> | null>;
35
+ getVersion: (key: string) => string;
36
+ set: <T>(key: string, value: T) => Promise<void>;
37
+ setIfVersion: <T>(key: string, value: T, version: string) => Promise<boolean>;
38
+ };
39
+ export type ReadCachePolicy = {
40
+ /** Time that a cached value can suppress a remote read. */
41
+ freshForMs: number;
42
+ /** Maximum age at which cached data remains eligible as an offline fallback. */
43
+ retainForMs: number;
44
+ /** Behavior when revalidation fails while retained data is available. */
45
+ refreshFailure: "use-retained" | "throw";
46
+ };
47
+ export type ReadCacheStatus = "fresh" | "stale" | "miss";
48
+ /** Payload-free cache lifecycle events suitable for host-owned telemetry. */
49
+ export type ReadCacheOutcome = "fresh-hit" | "stale-hit" | "cache-miss" | "forced-refresh" | "loader-success" | "loader-failure";
50
+ export type ReadThroughCacheResult<T> = {
51
+ data: T;
52
+ error: Error | null;
53
+ fromCache: boolean;
54
+ stale: boolean;
55
+ status: ReadCacheStatus;
56
+ };
57
+ export type ReadCacheResourceUpdate<T> = {
58
+ data: T;
59
+ error?: Error;
60
+ fromCache: boolean;
61
+ status: ReadCacheStatus;
62
+ };
63
+ export type BrowserReadCacheOptions = Partial<Pick<BrowserKeyValueDriverOptions, "databaseVersion" | "indexedDB">> & {
64
+ databaseName: string;
65
+ operationTimeoutMs?: number;
66
+ storeName: string;
67
+ };
68
+ export type ReadCacheKeyOptions = {
69
+ namespace: string;
70
+ params?: Record<string, string | number | boolean | null | undefined>;
71
+ prefix: string;
72
+ scope: string;
73
+ };
74
+ export declare function createReadCache(driver: ReadCacheDriver): ReadCache;
75
+ export declare function createMemoryReadCacheDriver(): ReadCacheDriver;
76
+ export declare function createBrowserReadCache({ databaseName, databaseVersion, indexedDB, operationTimeoutMs, storeName, }: BrowserReadCacheOptions): ReadCache;
77
+ /**
78
+ * Deletes only valid read-cache envelopes older than the supplied age. It is
79
+ * opt-in and returns zero for custom cache adapters that cannot enumerate keys.
80
+ */
81
+ export declare function pruneReadCache({ cache, maxAgeMs, now, }: {
82
+ cache: ReadCache;
83
+ maxAgeMs: number;
84
+ now?: () => number;
85
+ }): Promise<number>;
86
+ export declare function createReadCacheKey({ namespace, params, prefix, scope, }: ReadCacheKeyOptions): string;
87
+ export declare function readThroughCache<T>({ cache, forceFresh, key, loadFresh, maxAgeMs, onOutcome, policy, }: {
88
+ cache: ReadCache;
89
+ /**
90
+ * Revalidates after advancing the key generation, retaining the prior value
91
+ * as a possible fallback if refresh fails.
92
+ */
93
+ forceFresh?: boolean;
94
+ key: string;
95
+ loadFresh: () => Promise<T>;
96
+ maxAgeMs?: number;
97
+ onOutcome?: (outcome: ReadCacheOutcome) => void;
98
+ policy?: ReadCachePolicy;
99
+ }): Promise<ReadThroughCacheResult<T>>;
100
+ export declare function loadReadCacheResource<T>({ cache, forceFresh, key, loadFresh, maxAgeMs, onOutcome, policy, onCacheWriteError, onUpdate, skipCache, }: {
101
+ cache: ReadCache;
102
+ /** Revalidate while retaining eligible data as fallback on loader failure. */
103
+ forceFresh?: boolean;
104
+ key: string;
105
+ loadFresh: () => Promise<T>;
106
+ maxAgeMs?: number;
107
+ onOutcome?: (outcome: ReadCacheOutcome) => void;
108
+ policy?: ReadCachePolicy;
109
+ onCacheWriteError?: (error: Error) => void;
110
+ onUpdate: (update: ReadCacheResourceUpdate<T>) => void;
111
+ skipCache?: boolean;
112
+ }): Promise<T>;