@bayonai/offline 0.13.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.
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createReadResourceObserver = createReadResourceObserver;
4
+ /**
5
+ * Mounted presentation only: hosts retain cache, transport, identity and initial
6
+ * read policy. Accepting a snapshot preserves its source age; a successful
7
+ * refresh does not require persistence to have survived an unrelated write.
8
+ */
9
+ function createReadResourceObserver({ enabled = true, current, now, freshForMs, canRetain, isConnected = () => true, canonical, captureSnapshot, read, retireSnapshot, classifyFailure, subscribeWake, onUpdate, onData, onError, }) {
10
+ let disposed = false;
11
+ let retired = false;
12
+ let confirmedAt;
13
+ let retainable = true;
14
+ let pending;
15
+ let stop = () => { };
16
+ const detach = () => { const cleanup = stop; stop = () => { }; cleanup(); };
17
+ const active = () => !disposed && !retired && current();
18
+ const canRefresh = () => enabled && active() && retainable && confirmedAt !== undefined;
19
+ const accept = (update, age = now()) => {
20
+ if (!active())
21
+ return;
22
+ if ("data" in update) {
23
+ confirmedAt = age;
24
+ retainable = canRetain(update.data);
25
+ onData?.(update.data);
26
+ }
27
+ if (active())
28
+ onUpdate(update);
29
+ };
30
+ const adoptCanonical = () => {
31
+ const snapshot = canonical();
32
+ if (!snapshot || !active() || now() - snapshot.confirmedAt >= freshForMs)
33
+ return false;
34
+ accept({ status: "fresh", data: snapshot.data }, snapshot.confirmedAt);
35
+ return true;
36
+ };
37
+ const run = async (force) => {
38
+ if (!canRefresh())
39
+ return;
40
+ if (!force && now() - confirmedAt < freshForMs)
41
+ return;
42
+ if (!isConnected()) {
43
+ accept({ status: "retry", error: new Error("Resource unavailable while disconnected") });
44
+ return;
45
+ }
46
+ if (!force && adoptCanonical())
47
+ return;
48
+ accept({ status: "refreshing" });
49
+ if (!canRefresh())
50
+ return;
51
+ const snapshot = captureSnapshot();
52
+ let result;
53
+ try {
54
+ result = await read();
55
+ }
56
+ catch (error) {
57
+ if (!active())
58
+ return;
59
+ const failure = classifyFailure(error);
60
+ if (failure === "superseded") {
61
+ adoptCanonical();
62
+ return;
63
+ }
64
+ if (failure === "retired")
65
+ return;
66
+ if (onError)
67
+ onError(error);
68
+ else
69
+ console.error("[Bayon offline] Mounted resource refresh failed.", { error });
70
+ if (failure === "transient")
71
+ accept({ status: "retry", error });
72
+ else {
73
+ try {
74
+ await retireSnapshot(snapshot);
75
+ }
76
+ catch (storageError) {
77
+ // Storage cleanup cannot turn a known denial into retained presentation.
78
+ console.error("[Bayon offline] Unable to retire a mounted read snapshot.", { error: storageError });
79
+ }
80
+ if (!active())
81
+ return;
82
+ confirmedAt = undefined;
83
+ accept({ status: "failed", error });
84
+ }
85
+ return;
86
+ }
87
+ accept({ status: "fresh", data: result.data }, result.confirmedAt);
88
+ };
89
+ const refresh = ({ force = true } = {}) => {
90
+ if (!canRefresh())
91
+ return;
92
+ // Install ownership before callbacks can synchronously request another read.
93
+ if (!pending)
94
+ pending = Promise.resolve().then(() => run(force)).finally(() => { pending = undefined; });
95
+ return pending;
96
+ };
97
+ if (enabled && subscribeWake)
98
+ stop = subscribeWake(() => refresh({ force: false }) ?? Promise.resolve());
99
+ return {
100
+ accept, canRefresh, refresh,
101
+ retire() {
102
+ if (disposed || retired)
103
+ return;
104
+ retired = true;
105
+ detach();
106
+ // Clearing protected presentation is safe even after the host changed scope.
107
+ onUpdate({ status: "invalidated" });
108
+ },
109
+ dispose() { disposed = true; detach(); },
110
+ };
111
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bayonai/offline",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "Reusable offline read-cache, pending-write, and silent recovery primitives.",
5
5
  "license": "MIT",
6
6
  "author": "Bayon AI",