@bayonai/offline 0.12.0 → 0.15.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/CHANGELOG.md +28 -1
- package/README.md +98 -1
- package/RECOVERY.md +3 -0
- package/dist/browserReadLifecycle.d.ts +5 -0
- package/dist/browserReadLifecycle.js +24 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/pendingWriteConsolidation.js +2 -1
- package/dist/pendingWrites.d.ts +3 -3
- package/dist/pendingWrites.js +20 -15
- package/dist/readCache.d.ts +38 -8
- package/dist/readCache.js +396 -41
- package/dist/readResourceBatch.d.ts +13 -0
- package/dist/readResourceBatch.js +116 -0
- package/dist/readResourceObserver.d.ts +57 -0
- package/dist/readResourceObserver.js +108 -0
- package/dist-cjs/browserReadLifecycle.d.ts +5 -0
- package/dist-cjs/browserReadLifecycle.js +27 -0
- package/dist-cjs/index.d.ts +3 -0
- package/dist-cjs/index.js +3 -0
- package/dist-cjs/pendingWriteConsolidation.js +2 -1
- package/dist-cjs/pendingWrites.d.ts +3 -3
- package/dist-cjs/pendingWrites.js +20 -15
- package/dist-cjs/readCache.d.ts +38 -8
- package/dist-cjs/readCache.js +396 -41
- package/dist-cjs/readResourceBatch.d.ts +13 -0
- package/dist-cjs/readResourceBatch.js +119 -0
- package/dist-cjs/readResourceObserver.d.ts +57 -0
- package/dist-cjs/readResourceObserver.js +111 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 0.15.0 - 2026-11-07
|
|
4
|
+
|
|
5
|
+
- Add framework-neutral mounted read observation, atomic batch presentation and
|
|
6
|
+
native browser wake binding, with host-owned authorization, transport, cache
|
|
7
|
+
policy and exact-snapshot retirement. Preserve original source age, independent
|
|
8
|
+
source promises, scoped disposal and initial-only refresh intent.
|
|
9
|
+
- Add opt-in canonical cache delivery with atomic commit receipts. Newer
|
|
10
|
+
cross-instance confirmations and local invalidation fence obsolete presentation;
|
|
11
|
+
default immediate callbacks and nonblocking writes remain unchanged.
|
|
12
|
+
- Preserve canonical updates separately from raw child results and retire denied
|
|
13
|
+
groups immediately, including before initial settlement. Handle reentrant wakes,
|
|
14
|
+
undefined positions/errors and listener cleanup without duplicate reads.
|
|
15
|
+
|
|
16
|
+
## 0.14.0 - 2026-11-07
|
|
17
|
+
|
|
18
|
+
- Serialize overlapping replay calls with different selection or execution
|
|
19
|
+
policies per store and scope. Identical calls still coalesce; each distinct
|
|
20
|
+
caller receives its own results. Callers that depended on scope-only
|
|
21
|
+
coalescing should use stable callback references or handle separate summaries.
|
|
22
|
+
|
|
23
|
+
## 0.13.0 - 2026-11-06
|
|
24
|
+
|
|
25
|
+
- Add opt-in host-classified read failure handling, conditional terminal
|
|
26
|
+
retirement, source-required reads and layered provenance without renewing
|
|
27
|
+
retained cache age. Snapshot identifiers protect newer cross-instance writes;
|
|
28
|
+
existing version-one envelopes remain readable without a database upgrade.
|
|
29
|
+
|
|
30
|
+
## 0.12.0 - 2026-11-06
|
|
4
31
|
|
|
5
32
|
- Add framework-neutral pending-change projection and observable tracker, with host-supplied existing queues/drafts, account and stale-read fencing, stable external-store snapshots, grouping/counts/guidance, and retained last known records on read failures.
|
|
6
33
|
|
package/README.md
CHANGED
|
@@ -74,6 +74,30 @@ exceptions are logged and isolated from the read.
|
|
|
74
74
|
|
|
75
75
|
When refresh fails, `refreshFailure: "use-retained"` returns retained data
|
|
76
76
|
with `status: "stale"` and the error; `"throw"` propagates the loader error.
|
|
77
|
+
An opt-in callback can classify the original failure as `"use-retained"`,
|
|
78
|
+
`"throw"`, or `"retire"`. Retire propagates the original failure and
|
|
79
|
+
conditionally removes the captured snapshot; a later confirmation is preserved.
|
|
80
|
+
Built-in memory and IndexedDB drivers support atomic snapshot comparison.
|
|
81
|
+
`loadReadCacheResource({ deliveryPolicy: "cache-current", ... })` opts into
|
|
82
|
+
awaiting an atomic commit receipt before presenting the canonical retained
|
|
83
|
+
cache snapshot. Canonical updates carry `cache-hit` provenance and their stored
|
|
84
|
+
age, including when this call wrote that snapshot. The returned promise still
|
|
85
|
+
settles with this call's source result. A lost ownership comparison may adopt
|
|
86
|
+
another instance's eligible confirmation; local invalidation suppresses delivery.
|
|
87
|
+
This fences confirmations committed before the comparison, not future writes or
|
|
88
|
+
cross-tab notifications. The default `immediate` policy keeps nonblocking writes.
|
|
89
|
+
With `requireSource`, only the successfully committed source result can be
|
|
90
|
+
presented as source-confirmed; a lost ownership comparison suppresses delivery.
|
|
91
|
+
Opted-in presentation callback errors are logged independently of source success.
|
|
92
|
+
Custom caches must implement `commitIfVersion`, returning `{ written, version }`
|
|
93
|
+
with the generation captured inside the atomic commit after its own invalidation.
|
|
94
|
+
Built-in caches require the driver's atomic `setIf` for this mode. Missing atomic
|
|
95
|
+
support or storage failure logs an error and suppresses uncertain presentation.
|
|
96
|
+
Custom adapters must implement `deleteIfVersion` with equivalent atomic
|
|
97
|
+
ownership checks; without it, retirement is logged and cannot be guaranteed.
|
|
98
|
+
New envelopes carry an opaque snapshot identifier. Existing version-one
|
|
99
|
+
envelopes remain readable; legacy comparison is limited to serializable values
|
|
100
|
+
and cannot distinguish identical writes by older, identifier-unaware clients.
|
|
77
101
|
This also applies to forced refresh. Set `forceFresh: true` on
|
|
78
102
|
`loadReadCacheResource` or `readThroughCache` to bypass freshness, retain
|
|
79
103
|
eligible data as the failure fallback, and advance the cache generation so an
|
|
@@ -81,6 +105,19 @@ older in-flight load cannot overwrite the refresh. `readThroughCache` returns
|
|
|
81
105
|
the `status` field alongside its existing `fromCache`, `stale`, and `error`
|
|
82
106
|
fields.
|
|
83
107
|
|
|
108
|
+
Set `includeProvenance: true` to receive `cache-hit`, `source-confirmed`, or
|
|
109
|
+
`retained-fallback` provenance. Cache-derived results include their original
|
|
110
|
+
`savedAt`; fallback also carries `originalError` without normalizing it. Layered
|
|
111
|
+
adapters may use `loadFreshResult` to pass a provenance-bearing
|
|
112
|
+
`readThroughCache` result. Cached and fallback values from an inner layer are
|
|
113
|
+
not persisted as fresh outer confirmations. `requireSource: true` rejects
|
|
114
|
+
cached or fallback results and preserves a fallback's original failure. The
|
|
115
|
+
host still owns authorization, account scoping and retention durations.
|
|
116
|
+
Source confirmations include `confirmedAt` when provenance is requested;
|
|
117
|
+
the built-in cache preserves that time on outer writes. Custom adapters may
|
|
118
|
+
implement `setIfVersionAt` to preserve it. Without that capability, layered
|
|
119
|
+
confirmations are delivered but not persisted by the outer adapter.
|
|
120
|
+
|
|
84
121
|
For compatibility, callers that omit `policy` retain the previous `maxAgeMs`
|
|
85
122
|
freshness behavior and have no age-based fallback expiry. New callers should
|
|
86
123
|
use `policy`; when supplied it takes precedence over the legacy `maxAgeMs`.
|
|
@@ -257,6 +294,17 @@ the later write queued until the predecessor is delivered. This protects remote
|
|
|
257
294
|
write order; it does not replace idempotent server-side mutation handling for
|
|
258
295
|
ambiguous request outcomes.
|
|
259
296
|
|
|
297
|
+
Overlapping `syncPendingWrites` calls for the same store and scope share a
|
|
298
|
+
single replay only when their selection and sync callbacks, clock, and
|
|
299
|
+
stop-on-failure setting are identical. Different requests run in arrival order
|
|
300
|
+
and each receives results for its own selection. Use stable callback references
|
|
301
|
+
when callers intentionally share a run. Separate scopes remain independent.
|
|
302
|
+
A failed storage operation rejects its own call and does not prevent a later
|
|
303
|
+
queued recovery from attempting a fresh read. Migration: callers that previously
|
|
304
|
+
relied on scope-only coalescing with newly created inline callbacks should either
|
|
305
|
+
keep stable callback references or expect each serialized call to report its own
|
|
306
|
+
attempts and remaining count.
|
|
307
|
+
|
|
260
308
|
## Durable Command Store
|
|
261
309
|
|
|
262
310
|
Use `createDurableCommandStore` when a host needs one client-side owner for
|
|
@@ -383,7 +431,7 @@ pnpm --filter @bayonai/offline pack:smoke
|
|
|
383
431
|
2. Run the 100% coverage gate and production dependency audit.
|
|
384
432
|
3. Run `pnpm --filter @bayonai/offline pack:smoke` and confirm the tarball
|
|
385
433
|
includes only `package.json`, `dist`, `dist-cjs`, `README.md`,
|
|
386
|
-
`CHANGELOG.md`, and `LICENSE`.
|
|
434
|
+
`CHANGELOG.md`, `RECOVERY.md`, and `LICENSE`.
|
|
387
435
|
4. Install the generated tarball into an isolated consumer and verify ESM,
|
|
388
436
|
CommonJS, and TypeScript declaration imports.
|
|
389
437
|
5. Confirm `package.json` metadata, version, repository, license, and
|
|
@@ -485,3 +533,52 @@ Read failures retain the current account's last known rows and expose `readError
|
|
|
485
533
|
provide `onError` for host diagnostics (otherwise the tracker logs to console).
|
|
486
534
|
Call `setOffline` when connectivity changes. The tracker never persists, executes,
|
|
487
535
|
consolidates or retires operations, and adds no schema, jobs or provider imports.
|
|
536
|
+
|
|
537
|
+
## Mounted read presentation
|
|
538
|
+
|
|
539
|
+
`createReadResourceObserver<T, Snapshot>` owns mounted refresh presentation over
|
|
540
|
+
the host's existing cache and shared source reader. The host calls
|
|
541
|
+
`accept(update, confirmedAt)` for its initial delivery; the timestamp must be
|
|
542
|
+
the original cache/source confirmation time. The controller stores age and
|
|
543
|
+
eligibility, not another copy of the value. `canRetain(data)` excludes mutation
|
|
544
|
+
affordances or other values requiring a strict contract.
|
|
545
|
+
|
|
546
|
+
`refresh()` explicitly forces a confirmation; wake subscriptions invoke ordinary
|
|
547
|
+
freshness checks. A fresh canonical host snapshot may satisfy ordinary recovery
|
|
548
|
+
without a source request. A host-classified superseded read adopts that snapshot
|
|
549
|
+
immediately. Transient failures preserve the mounted value and original age;
|
|
550
|
+
terminal failures call `retireSnapshot` with the exact token captured before the
|
|
551
|
+
read, clear confirmation, and emit `failed`. The host must condition retirement
|
|
552
|
+
on token ownership and current authority. Failed cleanup is logged separately
|
|
553
|
+
and does not hide the terminal failure.
|
|
554
|
+
|
|
555
|
+
The host owns initial force versus persistent strict mode, identity, authorization,
|
|
556
|
+
connectivity/visibility, read eligibility, TTLs and transport sharing. Set
|
|
557
|
+
`enabled: false` for strict/noncacheable/command observations; their initial data
|
|
558
|
+
can still be accepted but they never subscribe or refresh. The controller uses
|
|
559
|
+
no timer or retry engine. `subscribeWake` returns an unsubscribe function;
|
|
560
|
+
`dispose()` silently fences callbacks and detaches it. `retire()` emits one safe
|
|
561
|
+
invalidation even after scope changes. Neither cancels an already-running shared
|
|
562
|
+
source promise.
|
|
563
|
+
|
|
564
|
+
`observeReadResourceBatch<T>` composes host child observations into one mounted
|
|
565
|
+
presentation barrier. It retains child order, duplicate positions and explicit
|
|
566
|
+
`undefined` results. Children retain all source/cache ownership; the barrier
|
|
567
|
+
records only their current presentation values and source ages. During refresh,
|
|
568
|
+
a temporary failure keeps the last complete array visible. A terminal child
|
|
569
|
+
failure retires the whole presentation immediately, including while another
|
|
570
|
+
child stalls, and later sibling completions cannot revive it.
|
|
571
|
+
|
|
572
|
+
The initial presentation follows accepted child callbacks, filling positions
|
|
573
|
+
without a callback from settled child promises. The batch promise preserves the
|
|
574
|
+
original child promise results, which may differ from newer canonical callback
|
|
575
|
+
values. Use child callbacks for presentation and promise results for source-call
|
|
576
|
+
settlement. Forced refresh, ordinary freshness checks, strict-value exclusion,
|
|
577
|
+
scope checks and disposal follow the scalar contract. No second cache, automatic
|
|
578
|
+
write replay, schema change or scheduling service is introduced.
|
|
579
|
+
|
|
580
|
+
`bindBrowserReadLifecycle({ window, document }, notify)` forwards native focus,
|
|
581
|
+
online and visibility-change events and returns idempotent cleanup. Hosts keep
|
|
582
|
+
visibility/connectivity checks and transport coordination inside `notify`.
|
|
583
|
+
Synchronous throws and rejected wake promises are logged without escaping the
|
|
584
|
+
native event callback. This binding does not acquire pending-write replay ownership.
|
package/RECOVERY.md
CHANGED
|
@@ -10,6 +10,9 @@ This is the package-owned product contract for all offline consumers, establishe
|
|
|
10
10
|
- `createBrowserPendingWriteRecovery` owns silent startup, focus, reconnect and
|
|
11
11
|
capped exponential-backoff recovery. Reuse its coordination and ordered executor;
|
|
12
12
|
never add a second replay owner, a billable job or a recurring server service.
|
|
13
|
+
- Concurrent replay for one store and scope serializes different selection and
|
|
14
|
+
execution policies; only identical requests share results. Each caller must
|
|
15
|
+
see its own selection and failure policy, including after a rejected read.
|
|
13
16
|
- `getPendingChangesGuidance` supplies common guidance without domain payloads.
|
|
14
17
|
Ready work syncs automatically; unfinished drafts need editing before submission.
|
|
15
18
|
Rejected work stays preserved. No retry button is part of pending inspection.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Native wake binding only; hosts own visibility, eligibility and request sharing. */
|
|
2
|
+
export function bindBrowserReadLifecycle(targets, notify) {
|
|
3
|
+
const report = (error) => console.error("[Bayon offline] Browser read refresh failed.", { error });
|
|
4
|
+
const wake = () => {
|
|
5
|
+
try {
|
|
6
|
+
void notify().catch(report);
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
report(error);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
targets.window.addEventListener("focus", wake);
|
|
13
|
+
targets.window.addEventListener("online", wake);
|
|
14
|
+
targets.document.addEventListener("visibilitychange", wake);
|
|
15
|
+
let stopped = false;
|
|
16
|
+
return () => {
|
|
17
|
+
if (stopped)
|
|
18
|
+
return;
|
|
19
|
+
stopped = true;
|
|
20
|
+
targets.window.removeEventListener("focus", wake);
|
|
21
|
+
targets.window.removeEventListener("online", wake);
|
|
22
|
+
targets.document.removeEventListener("visibilitychange", wake);
|
|
23
|
+
};
|
|
24
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export * from "./readCache.js";
|
|
2
|
+
export * from "./readResourceObserver.js";
|
|
3
|
+
export * from "./readResourceBatch.js";
|
|
4
|
+
export * from "./browserReadLifecycle.js";
|
|
2
5
|
export * from "./pendingWrites.js";
|
|
3
6
|
export * from "./browserRecovery.js";
|
|
4
7
|
export * from "./browserCoordination.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export * from "./readCache.js";
|
|
2
|
+
export * from "./readResourceObserver.js";
|
|
3
|
+
export * from "./readResourceBatch.js";
|
|
4
|
+
export * from "./browserReadLifecycle.js";
|
|
2
5
|
export * from "./pendingWrites.js";
|
|
3
6
|
export * from "./browserRecovery.js";
|
|
4
7
|
export * from "./browserCoordination.js";
|
|
@@ -35,7 +35,8 @@ export function projectPendingWriteFinalState(records, getKey, { isBarrier = ()
|
|
|
35
35
|
const previous = latest.get(key);
|
|
36
36
|
if (previous) {
|
|
37
37
|
superseded.add(previous);
|
|
38
|
-
|
|
38
|
+
// Every latest entry creates its matching dependency list below.
|
|
39
|
+
for (const effect of dependents.get(key))
|
|
39
40
|
superseded.add(effect);
|
|
40
41
|
}
|
|
41
42
|
latest.set(key, record);
|
package/dist/pendingWrites.d.ts
CHANGED
|
@@ -151,9 +151,9 @@ export declare function syncPendingWrite<Payload>({ id, now, store, sync, }: {
|
|
|
151
151
|
sync: PendingWriteSync<Payload>;
|
|
152
152
|
}): Promise<SyncPendingWriteResult>;
|
|
153
153
|
/**
|
|
154
|
-
* Replays selected writes oldest first
|
|
155
|
-
*
|
|
156
|
-
* mutation semantics.
|
|
154
|
+
* Replays selected writes oldest first. Identical overlapping calls coalesce;
|
|
155
|
+
* calls with different options run serially for the same store and scope.
|
|
156
|
+
* Callers own the selection predicate and idempotent remote mutation semantics.
|
|
157
157
|
*/
|
|
158
158
|
export declare function syncPendingWrites<Payload>({ now, scopeKey, select, stopOnFailure, store, sync, }: {
|
|
159
159
|
now?: () => string;
|
package/dist/pendingWrites.js
CHANGED
|
@@ -285,30 +285,34 @@ export async function syncPendingWrite({ id, now = () => new Date().toISOString(
|
|
|
285
285
|
: { status: "superseded" };
|
|
286
286
|
}
|
|
287
287
|
/**
|
|
288
|
-
* Replays selected writes oldest first
|
|
289
|
-
*
|
|
290
|
-
* mutation semantics.
|
|
288
|
+
* Replays selected writes oldest first. Identical overlapping calls coalesce;
|
|
289
|
+
* calls with different options run serially for the same store and scope.
|
|
290
|
+
* Callers own the selection predicate and idempotent remote mutation semantics.
|
|
291
291
|
*/
|
|
292
|
-
export function syncPendingWrites({ now, scopeKey = "all", select =
|
|
292
|
+
export function syncPendingWrites({ now, scopeKey = "all", select = selectAllPendingWrites, stopOnFailure = false, store, sync, }) {
|
|
293
293
|
let inFlightByScope = pendingWriteSyncs.get(store);
|
|
294
294
|
if (!inFlightByScope) {
|
|
295
295
|
inFlightByScope = new Map();
|
|
296
296
|
pendingWriteSyncs.set(store, inFlightByScope);
|
|
297
297
|
}
|
|
298
298
|
const existing = inFlightByScope.get(scopeKey);
|
|
299
|
-
if (existing
|
|
300
|
-
|
|
299
|
+
if (existing &&
|
|
300
|
+
existing.select === select &&
|
|
301
|
+
existing.sync === sync &&
|
|
302
|
+
existing.now === now &&
|
|
303
|
+
existing.stopOnFailure === stopOnFailure) {
|
|
304
|
+
return existing.promise;
|
|
301
305
|
}
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
});
|
|
309
|
-
inFlightByScope.set(scopeKey, syncRun);
|
|
306
|
+
const run = () => performPendingWriteSync({ now, select, stopOnFailure, store, sync });
|
|
307
|
+
// Product decision: distinct filters must never share counts or run in
|
|
308
|
+
// parallel; each waits for the preceding scope run and uses its own policy.
|
|
309
|
+
const syncRun = existing ? existing.promise.then(run, run) : run();
|
|
310
|
+
const entry = { now, select, stopOnFailure, sync, promise: syncRun };
|
|
311
|
+
inFlightByScope.set(scopeKey, entry);
|
|
310
312
|
const clearInFlight = () => {
|
|
311
|
-
inFlightByScope.
|
|
313
|
+
if (inFlightByScope.get(scopeKey) === entry) {
|
|
314
|
+
inFlightByScope.delete(scopeKey);
|
|
315
|
+
}
|
|
312
316
|
};
|
|
313
317
|
void syncRun.then(clearInFlight, clearInFlight);
|
|
314
318
|
return syncRun;
|
|
@@ -346,6 +350,7 @@ export function summarizePendingWrites(records) {
|
|
|
346
350
|
return summary;
|
|
347
351
|
}, { failedCount: 0, pendingCount: 0, syncingCount: 0, totalCount: 0 });
|
|
348
352
|
}
|
|
353
|
+
const selectAllPendingWrites = () => true;
|
|
349
354
|
const pendingWriteSyncs = new WeakMap();
|
|
350
355
|
async function performPendingWriteSync({ now, select, stopOnFailure, store, sync, }) {
|
|
351
356
|
const summary = {
|
package/dist/readCache.d.ts
CHANGED
|
@@ -13,14 +13,21 @@ export type ReadCacheDriver = {
|
|
|
13
13
|
clear: () => Promise<void>;
|
|
14
14
|
close?: () => Promise<void>;
|
|
15
15
|
delete: (key: string) => Promise<void>;
|
|
16
|
+
deleteIf?: (key: string, predicate: (value: unknown | null) => boolean) => Promise<boolean>;
|
|
16
17
|
get: (key: string) => Promise<unknown | null>;
|
|
17
18
|
listEntries?: () => Promise<Array<{
|
|
18
19
|
key: string;
|
|
19
20
|
value: unknown;
|
|
20
21
|
}>>;
|
|
21
22
|
set: (key: string, value: unknown) => Promise<void>;
|
|
23
|
+
setIf?: (key: string, value: unknown, predicate: (value: unknown | null) => boolean) => Promise<boolean>;
|
|
22
24
|
};
|
|
23
25
|
export type ReadCache = {
|
|
26
|
+
/** Atomic ownership proof for opt-in canonical presentation; version is captured at commit. */
|
|
27
|
+
commitIfVersion?: <T>(key: string, value: T, version: string, snapshot: ReadCacheHit<unknown> | null, confirmedAt?: string) => Promise<{
|
|
28
|
+
written: boolean;
|
|
29
|
+
version: string;
|
|
30
|
+
}>;
|
|
24
31
|
/**
|
|
25
32
|
* Resolves a stable cache for the duration of a resource load. Dynamic
|
|
26
33
|
* adapters use this to pin the active account before asynchronous work.
|
|
@@ -29,12 +36,15 @@ export type ReadCache = {
|
|
|
29
36
|
clear: () => Promise<void>;
|
|
30
37
|
close: () => Promise<void>;
|
|
31
38
|
delete: (key: string) => Promise<void>;
|
|
39
|
+
/** Retire only a snapshot still owned by the captured generation. */
|
|
40
|
+
deleteIfVersion?: (key: string, version: string, snapshot: ReadCacheHit<unknown>) => Promise<boolean>;
|
|
32
41
|
/** Advances a key's generation without deleting its retained value. */
|
|
33
42
|
invalidate?: (key: string) => void;
|
|
34
43
|
get: <T>(key: string) => Promise<ReadCacheHit<T> | null>;
|
|
35
44
|
getVersion: (key: string) => string;
|
|
36
45
|
set: <T>(key: string, value: T) => Promise<void>;
|
|
37
|
-
setIfVersion: <T>(key: string, value: T, version: string) => Promise<boolean>;
|
|
46
|
+
setIfVersion: <T>(key: string, value: T, version: string, snapshot?: ReadCacheHit<unknown> | null, confirmedAt?: string) => Promise<boolean>;
|
|
47
|
+
setIfVersionAt?: <T>(key: string, value: T, version: string, snapshot: ReadCacheHit<unknown> | null, confirmedAt: string) => Promise<boolean>;
|
|
38
48
|
};
|
|
39
49
|
export type ReadCachePolicy = {
|
|
40
50
|
/** Time that a cached value can suppress a remote read. */
|
|
@@ -42,9 +52,10 @@ export type ReadCachePolicy = {
|
|
|
42
52
|
/** Maximum age at which cached data remains eligible as an offline fallback. */
|
|
43
53
|
retainForMs: number;
|
|
44
54
|
/** Behavior when revalidation fails while retained data is available. */
|
|
45
|
-
refreshFailure: "use-retained" | "throw";
|
|
55
|
+
refreshFailure: "use-retained" | "throw" | ((error: unknown) => "use-retained" | "throw" | "retire");
|
|
46
56
|
};
|
|
47
57
|
export type ReadCacheStatus = "fresh" | "stale" | "miss";
|
|
58
|
+
export type ReadCacheProvenance = "cache-hit" | "source-confirmed" | "retained-fallback";
|
|
48
59
|
/** Payload-free cache lifecycle events suitable for host-owned telemetry. */
|
|
49
60
|
export type ReadCacheOutcome = "fresh-hit" | "stale-hit" | "cache-miss" | "forced-refresh" | "loader-success" | "loader-failure";
|
|
50
61
|
export type ReadThroughCacheResult<T> = {
|
|
@@ -53,12 +64,27 @@ export type ReadThroughCacheResult<T> = {
|
|
|
53
64
|
fromCache: boolean;
|
|
54
65
|
stale: boolean;
|
|
55
66
|
status: ReadCacheStatus;
|
|
67
|
+
provenance?: ReadCacheProvenance;
|
|
68
|
+
confirmedAt?: string;
|
|
69
|
+
savedAt?: string;
|
|
70
|
+
originalError?: unknown;
|
|
56
71
|
};
|
|
57
72
|
export type ReadCacheResourceUpdate<T> = {
|
|
58
73
|
data: T;
|
|
59
74
|
error?: Error;
|
|
60
75
|
fromCache: boolean;
|
|
61
76
|
status: ReadCacheStatus;
|
|
77
|
+
provenance?: ReadCacheProvenance;
|
|
78
|
+
confirmedAt?: string;
|
|
79
|
+
savedAt?: string;
|
|
80
|
+
originalError?: unknown;
|
|
81
|
+
};
|
|
82
|
+
export type ReadCacheLoader<T> = {
|
|
83
|
+
loadFresh: () => Promise<T>;
|
|
84
|
+
loadFreshResult?: never;
|
|
85
|
+
} | {
|
|
86
|
+
loadFresh?: never;
|
|
87
|
+
loadFreshResult: () => Promise<ReadThroughCacheResult<T>>;
|
|
62
88
|
};
|
|
63
89
|
export type BrowserReadCacheOptions = Partial<Pick<BrowserKeyValueDriverOptions, "databaseVersion" | "indexedDB">> & {
|
|
64
90
|
databaseName: string;
|
|
@@ -84,29 +110,33 @@ export declare function pruneReadCache({ cache, maxAgeMs, now, }: {
|
|
|
84
110
|
now?: () => number;
|
|
85
111
|
}): Promise<number>;
|
|
86
112
|
export declare function createReadCacheKey({ namespace, params, prefix, scope, }: ReadCacheKeyOptions): string;
|
|
87
|
-
export declare function readThroughCache<T>({ cache, forceFresh, key, loadFresh, maxAgeMs, onOutcome, policy, }: {
|
|
113
|
+
export declare function readThroughCache<T>({ cache, forceFresh, includeProvenance, key, loadFresh, loadFreshResult, maxAgeMs, onOutcome, policy, requireSource, }: {
|
|
88
114
|
cache: ReadCache;
|
|
89
115
|
/**
|
|
90
116
|
* Revalidates after advancing the key generation, retaining the prior value
|
|
91
117
|
* as a possible fallback if refresh fails.
|
|
92
118
|
*/
|
|
93
119
|
forceFresh?: boolean;
|
|
120
|
+
includeProvenance?: boolean;
|
|
94
121
|
key: string;
|
|
95
|
-
loadFresh: () => Promise<T>;
|
|
96
122
|
maxAgeMs?: number;
|
|
97
123
|
onOutcome?: (outcome: ReadCacheOutcome) => void;
|
|
98
124
|
policy?: ReadCachePolicy;
|
|
99
|
-
|
|
100
|
-
|
|
125
|
+
requireSource?: boolean;
|
|
126
|
+
} & ReadCacheLoader<T>): Promise<ReadThroughCacheResult<T>>;
|
|
127
|
+
export declare function loadReadCacheResource<T>({ cache, deliveryPolicy, forceFresh, includeProvenance, key, loadFresh, loadFreshResult, maxAgeMs, onOutcome, policy, onCacheWriteError, onUpdate, skipCache, requireSource, }: {
|
|
101
128
|
cache: ReadCache;
|
|
129
|
+
/** Opt in to presenting the eligible canonical cache after conditional persistence. */
|
|
130
|
+
deliveryPolicy?: "immediate" | "cache-current";
|
|
102
131
|
/** Revalidate while retaining eligible data as fallback on loader failure. */
|
|
103
132
|
forceFresh?: boolean;
|
|
133
|
+
includeProvenance?: boolean;
|
|
104
134
|
key: string;
|
|
105
|
-
loadFresh: () => Promise<T>;
|
|
106
135
|
maxAgeMs?: number;
|
|
107
136
|
onOutcome?: (outcome: ReadCacheOutcome) => void;
|
|
108
137
|
policy?: ReadCachePolicy;
|
|
109
138
|
onCacheWriteError?: (error: Error) => void;
|
|
110
139
|
onUpdate: (update: ReadCacheResourceUpdate<T>) => void;
|
|
111
140
|
skipCache?: boolean;
|
|
112
|
-
|
|
141
|
+
requireSource?: boolean;
|
|
142
|
+
} & ReadCacheLoader<T>): Promise<T>;
|