@lunora/client 1.0.0-alpha.27 → 1.0.0-alpha.29
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/dist/auth/index.d.mts +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.mjs +1 -60
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +1 -21
- package/dist/packem_shared/CONFLICT_ERROR_CODE-B2SU0CaJ.mjs +1 -0
- package/dist/packem_shared/ClientServiceWorker-CRQ5yl7-.mjs +1 -0
- package/dist/packem_shared/DEFAULT_MAX_BUFFER-TBn2s7Rz.mjs +1 -0
- package/dist/packem_shared/LunoraClient-DDEoZdgP.mjs +1 -0
- package/dist/packem_shared/OfflineQueue-B603_q8m.mjs +1 -0
- package/dist/packem_shared/SKIP-CArzT3Vl.mjs +1 -0
- package/dist/packem_shared/SubscriptionRegistry-CdrOD7sZ.mjs +1 -0
- package/dist/packem_shared/TabCoordinator-ijLic02x.mjs +1 -0
- package/dist/packem_shared/applyDelta-DXqQORbM.mjs +1 -0
- package/dist/packem_shared/createAsyncStoragePersistence-BRLqeXsC.mjs +1 -0
- package/dist/packem_shared/createClientQuery-CrdRoicO.mjs +1 -0
- package/dist/packem_shared/createInMemoryBookmarkStorage-DCd5Ej-t.mjs +1 -0
- package/dist/packem_shared/createInMemoryPersistence-CQYXy8UP.mjs +1 -0
- package/dist/packem_shared/createInMemoryQueryCache-DjCKrLjk.mjs +1 -0
- package/dist/packem_shared/createLocalStore-B3Tw_Tw1.mjs +1 -0
- package/dist/packem_shared/createMutationRunner-lQXH8Amk.mjs +1 -0
- package/dist/packem_shared/createMutatorRunner-BVzo0FBZ.mjs +1 -0
- package/dist/packem_shared/createReconnect-CFT7YRSR.mjs +1 -0
- package/dist/packem_shared/createReply-DF0jRsv-.mjs +1 -0
- package/dist/packem_shared/createServerClient-B7KqO8tv.mjs +1 -0
- package/dist/packem_shared/createSnapshotPrecondition-DOKRAJ4b.mjs +1 -0
- package/dist/packem_shared/deserializePreloaded-CHfOFpka.mjs +1 -0
- package/dist/packem_shared/getServerSession-CGjgRBAE.mjs +1 -0
- package/dist/packem_shared/httpStream-Cz4_QyjV.mjs +11 -0
- package/dist/packem_shared/idb-utility-C-DR8nG1.mjs +1 -0
- package/dist/packem_shared/local-store-C53xDvEr.mjs +1 -0
- package/dist/packem_shared/{lunora-client.d-B1iZb2Ph.d.mts → lunora-client.d-Bt65hm_E.d.mts} +135 -1
- package/dist/packem_shared/{lunora-client.d-B1iZb2Ph.d.ts → lunora-client.d-Bt65hm_E.d.ts} +135 -1
- package/dist/packem_shared/offline-queue-D73Dq95V.mjs +1 -0
- package/dist/packem_shared/{preload.d-CLwoXl3R.d.mts → preload.d-BmztUzzG.d.mts} +1 -1
- package/dist/packem_shared/{preload.d-Cqb4Kh2I.d.ts → preload.d-DGGRMWB5.d.ts} +1 -1
- package/dist/packem_shared/preloadQuery-DjwSjFwt.mjs +1 -0
- package/dist/packem_shared/wire-key-D_zOxXK4.mjs +1 -0
- package/dist/pagination/index.mjs +1 -61
- package/dist/query/index.d.mts +2 -2
- package/dist/query/index.d.ts +2 -2
- package/dist/query/index.mjs +1 -1
- package/dist/ssr/index.d.mts +3 -3
- package/dist/ssr/index.d.ts +3 -3
- package/dist/ssr/index.mjs +1 -4
- package/dist/upload.mjs +1 -24
- package/package.json +2 -2
- package/dist/packem_shared/CONFLICT_ERROR_CODE-B8gQ8tyU.mjs +0 -33
- package/dist/packem_shared/ClientServiceWorker-C3PAFwy0.mjs +0 -100
- package/dist/packem_shared/DEFAULT_MAX_BUFFER-7hFnzNk9.mjs +0 -110
- package/dist/packem_shared/LunoraClient-DKATLGHY.mjs +0 -3631
- package/dist/packem_shared/OfflineQueue-BgarnAub.mjs +0 -1
- package/dist/packem_shared/SKIP-vItZChkw.mjs +0 -50
- package/dist/packem_shared/SubscriptionRegistry-CxS_Inha.mjs +0 -31
- package/dist/packem_shared/TabCoordinator-D_5oNTTt.mjs +0 -258
- package/dist/packem_shared/applyDelta-CRKZ1PBt.mjs +0 -81
- package/dist/packem_shared/createAsyncStoragePersistence-1Z5BZ8RC.mjs +0 -45
- package/dist/packem_shared/createClientQuery-dJZg1ohm.mjs +0 -80
- package/dist/packem_shared/createInMemoryBookmarkStorage-BoN7a7TH.mjs +0 -11
- package/dist/packem_shared/createInMemoryPersistence-DZ2VHWgm.mjs +0 -79
- package/dist/packem_shared/createInMemoryQueryCache-DiaGZkA2.mjs +0 -112
- package/dist/packem_shared/createLocalStore-BtqUmOQA.mjs +0 -2
- package/dist/packem_shared/createMutationRunner-BqsavzvG.mjs +0 -21
- package/dist/packem_shared/createMutatorRunner-BETvCd0p.mjs +0 -31
- package/dist/packem_shared/createReconnect-Di_-oHH7.mjs +0 -22
- package/dist/packem_shared/createReply-lI4tVS2w.mjs +0 -36
- package/dist/packem_shared/createServerClient-Bp2r9tcB.mjs +0 -11
- package/dist/packem_shared/createSnapshotPrecondition-CxQ1T4ZP.mjs +0 -18
- package/dist/packem_shared/deserializePreloaded-C0eJTY_W.mjs +0 -4
- package/dist/packem_shared/getServerSession-8jXewqxd.mjs +0 -13
- package/dist/packem_shared/httpStream-DIdL8NEw.mjs +0 -168
- package/dist/packem_shared/idb-utility-DrSVX43Q.mjs +0 -48
- package/dist/packem_shared/local-store-DIq-UWfD.mjs +0 -106
- package/dist/packem_shared/offline-queue-N-1JvYb4.mjs +0 -211
- package/dist/packem_shared/preloadQuery-lobFkD2Z.mjs +0 -13
- package/dist/packem_shared/wire-key-Djie6aaR.mjs +0 -266
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { O as OfflineQueue, n as nextId, r as reportPersistenceError } from './offline-queue-N-1JvYb4.mjs';
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
const SKIP = "skip";
|
|
2
|
-
const toSubscriptionError = (error) => {
|
|
3
|
-
if (error instanceof Error) {
|
|
4
|
-
return { message: error.message };
|
|
5
|
-
}
|
|
6
|
-
return { message: String(error) };
|
|
7
|
-
};
|
|
8
|
-
const createQuerySubscription = (client, function_, args, sinks, options = {}) => {
|
|
9
|
-
if (args === SKIP) {
|
|
10
|
-
sinks.onReset?.();
|
|
11
|
-
return () => {
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
let cancelled = false;
|
|
15
|
-
const handleError = (error) => {
|
|
16
|
-
if (cancelled) {
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
sinks.onError?.(error);
|
|
20
|
-
};
|
|
21
|
-
const onError = sinks.onError ? handleError : void 0;
|
|
22
|
-
let unsubscribe;
|
|
23
|
-
try {
|
|
24
|
-
unsubscribe = client.subscribe(
|
|
25
|
-
function_,
|
|
26
|
-
args,
|
|
27
|
-
(value) => {
|
|
28
|
-
if (cancelled) {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
sinks.onData(value);
|
|
32
|
-
},
|
|
33
|
-
{ onError, shardKey: options.shardKey }
|
|
34
|
-
);
|
|
35
|
-
} catch (error) {
|
|
36
|
-
if (!sinks.onError) {
|
|
37
|
-
throw error;
|
|
38
|
-
}
|
|
39
|
-
handleError(toSubscriptionError(error));
|
|
40
|
-
return () => {
|
|
41
|
-
cancelled = true;
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
return () => {
|
|
45
|
-
cancelled = true;
|
|
46
|
-
unsubscribe();
|
|
47
|
-
};
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export { SKIP, createQuerySubscription, toSubscriptionError };
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { s as stableWireKey } from './wire-key-Djie6aaR.mjs';
|
|
2
|
-
|
|
3
|
-
class SubscriptionRegistry {
|
|
4
|
-
static key(functionPath, args, shardKey) {
|
|
5
|
-
return `${functionPath}::${stableWireKey(args)}::${shardKey ?? ""}`;
|
|
6
|
-
}
|
|
7
|
-
byKey = /* @__PURE__ */ new Map();
|
|
8
|
-
byId = /* @__PURE__ */ new Map();
|
|
9
|
-
get(key) {
|
|
10
|
-
return this.byKey.get(key);
|
|
11
|
-
}
|
|
12
|
-
getById(id) {
|
|
13
|
-
return this.byId.get(id);
|
|
14
|
-
}
|
|
15
|
-
add(state) {
|
|
16
|
-
this.byKey.set(SubscriptionRegistry.key(state.fn.__lunoraRef, state.args, state.shardKey), state);
|
|
17
|
-
this.byId.set(state.id, state);
|
|
18
|
-
}
|
|
19
|
-
remove(state) {
|
|
20
|
-
const key = SubscriptionRegistry.key(state.fn.__lunoraRef, state.args, state.shardKey);
|
|
21
|
-
if (this.byKey.get(key) === state) {
|
|
22
|
-
this.byKey.delete(key);
|
|
23
|
-
}
|
|
24
|
-
this.byId.delete(state.id);
|
|
25
|
-
}
|
|
26
|
-
all() {
|
|
27
|
-
return [...this.byKey.values()];
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export { SubscriptionRegistry };
|
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
let nextTabId = 0;
|
|
2
|
-
const DEFAULT_CHANNEL = "lunora-bridge";
|
|
3
|
-
const DEFAULT_HEARTBEAT_MS = 1e3;
|
|
4
|
-
const DEFAULT_LEADER_TIMEOUT_MS = 3e3;
|
|
5
|
-
class TabCoordinator {
|
|
6
|
-
bc;
|
|
7
|
-
tabId;
|
|
8
|
-
heartbeatInterval;
|
|
9
|
-
leaderTimeout;
|
|
10
|
-
/** The tab id of the current known leader, or `undefined` if no leader. */
|
|
11
|
-
knownLeader = void 0;
|
|
12
|
-
/** `true` when this tab believes it is the leader. */
|
|
13
|
-
leader = false;
|
|
14
|
-
/** `true` once `start()` has been called. */
|
|
15
|
-
running = false;
|
|
16
|
-
/** Timestamp of the most recent leader heartbeat. */
|
|
17
|
-
lastHeartbeat = 0;
|
|
18
|
-
heartbeatTimer = void 0;
|
|
19
|
-
leaderCheckTimer = void 0;
|
|
20
|
-
/** Callbacks set via constructor options. */
|
|
21
|
-
onBecomeLeader;
|
|
22
|
-
onStopBeingLeader;
|
|
23
|
-
onSubscriptionData;
|
|
24
|
-
onSubscriptionError;
|
|
25
|
-
constructor(options = {}) {
|
|
26
|
-
nextTabId += 1;
|
|
27
|
-
this.tabId = `tab_${String(nextTabId)}_${crypto.randomUUID()}`;
|
|
28
|
-
this.heartbeatInterval = options.heartbeatInterval ?? DEFAULT_HEARTBEAT_MS;
|
|
29
|
-
this.leaderTimeout = options.leaderTimeout ?? DEFAULT_LEADER_TIMEOUT_MS;
|
|
30
|
-
this.onBecomeLeader = options.onBecomeLeader;
|
|
31
|
-
this.onStopBeingLeader = options.onStopBeingLeader;
|
|
32
|
-
this.onSubscriptionData = options.onSubscriptionData;
|
|
33
|
-
this.onSubscriptionError = options.onSubscriptionError;
|
|
34
|
-
if (typeof BroadcastChannel === "undefined") {
|
|
35
|
-
this.bc = void 0;
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
this.bc = new BroadcastChannel(options.channelName ?? DEFAULT_CHANNEL);
|
|
39
|
-
this.bc.addEventListener("message", (event) => {
|
|
40
|
-
this.handleMessage(event.data);
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
// -----------------------------------------------------------------------
|
|
44
|
-
// Public API
|
|
45
|
-
// -----------------------------------------------------------------------
|
|
46
|
-
/**
|
|
47
|
-
* Start the coordinator: attempt to claim leadership and begin the
|
|
48
|
-
* heartbeat/leader-check cycle. Safe to call multiple times.
|
|
49
|
-
*/
|
|
50
|
-
start() {
|
|
51
|
-
if (this.running) {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
this.running = true;
|
|
55
|
-
if (this.bc === void 0) {
|
|
56
|
-
this.becomeLeader();
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
this.broadcast({ type: "claim-leadership", tabId: this.tabId, ts: Date.now() });
|
|
60
|
-
setTimeout(() => {
|
|
61
|
-
if (!this.running) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
if (this.knownLeader === void 0) {
|
|
65
|
-
this.becomeLeader();
|
|
66
|
-
}
|
|
67
|
-
}, this.leaderTimeout);
|
|
68
|
-
this.leaderCheckTimer = setInterval(() => {
|
|
69
|
-
this.checkLeaderHealth();
|
|
70
|
-
}, this.leaderTimeout);
|
|
71
|
-
this.heartbeatTimer = setInterval(() => {
|
|
72
|
-
this.sendHeartbeat();
|
|
73
|
-
}, this.heartbeatInterval);
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Stop the coordinator: yield leadership (if held), close the channel, and
|
|
77
|
-
* clear all timers. Safe to call multiple times.
|
|
78
|
-
*/
|
|
79
|
-
stop() {
|
|
80
|
-
this.running = false;
|
|
81
|
-
if (this.leader) {
|
|
82
|
-
this.broadcast({ type: "yield-leadership", tabId: this.tabId });
|
|
83
|
-
this.leader = false;
|
|
84
|
-
this.knownLeader = void 0;
|
|
85
|
-
this.onStopBeingLeader?.();
|
|
86
|
-
}
|
|
87
|
-
if (this.heartbeatTimer !== void 0) {
|
|
88
|
-
clearInterval(this.heartbeatTimer);
|
|
89
|
-
this.heartbeatTimer = void 0;
|
|
90
|
-
}
|
|
91
|
-
if (this.leaderCheckTimer !== void 0) {
|
|
92
|
-
clearInterval(this.leaderCheckTimer);
|
|
93
|
-
this.leaderCheckTimer = void 0;
|
|
94
|
-
}
|
|
95
|
-
this.bc?.close();
|
|
96
|
-
}
|
|
97
|
-
/** `true` when this tab is the current WebSocket leader. */
|
|
98
|
-
isLeader() {
|
|
99
|
-
return this.leader;
|
|
100
|
-
}
|
|
101
|
-
/** The tab id of the current leader, or `undefined` if unknown / no leader. */
|
|
102
|
-
get leaderTabId() {
|
|
103
|
-
return this.knownLeader;
|
|
104
|
-
}
|
|
105
|
-
/** The id of this tab. */
|
|
106
|
-
get id() {
|
|
107
|
-
return this.tabId;
|
|
108
|
-
}
|
|
109
|
-
/** `true` when the coordinator has been started and is not yet stopped. */
|
|
110
|
-
get isRunning() {
|
|
111
|
-
return this.running;
|
|
112
|
-
}
|
|
113
|
-
// -----------------------------------------------------------------------
|
|
114
|
-
// Broadcasting
|
|
115
|
-
// -----------------------------------------------------------------------
|
|
116
|
-
/**
|
|
117
|
-
* Broadcast subscription data to all follower tabs. Only the leader should
|
|
118
|
-
* call this.
|
|
119
|
-
*/
|
|
120
|
-
broadcastSubscriptionData(key, data) {
|
|
121
|
-
if (!this.leader) {
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
this.broadcast({ type: "subscription-data", tabId: this.tabId, key, data });
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Broadcast a subscription error to all follower tabs. Only the leader
|
|
128
|
-
* should call this.
|
|
129
|
-
*/
|
|
130
|
-
broadcastSubscriptionError(key, error) {
|
|
131
|
-
if (!this.leader) {
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
this.broadcast({ type: "subscription-error", tabId: this.tabId, key, error });
|
|
135
|
-
}
|
|
136
|
-
// -----------------------------------------------------------------------
|
|
137
|
-
// Internal
|
|
138
|
-
// -----------------------------------------------------------------------
|
|
139
|
-
broadcast(message) {
|
|
140
|
-
this.bc?.postMessage(message);
|
|
141
|
-
}
|
|
142
|
-
handleMessage(message) {
|
|
143
|
-
switch (message.type) {
|
|
144
|
-
case "claim-leadership": {
|
|
145
|
-
this.handleClaimLeadership(message);
|
|
146
|
-
break;
|
|
147
|
-
}
|
|
148
|
-
case "heartbeat": {
|
|
149
|
-
this.handleHeartbeat(message);
|
|
150
|
-
break;
|
|
151
|
-
}
|
|
152
|
-
case "subscription-data": {
|
|
153
|
-
if (this.leader || message.tabId === this.tabId) {
|
|
154
|
-
break;
|
|
155
|
-
}
|
|
156
|
-
this.onSubscriptionData?.(message.key, message.data);
|
|
157
|
-
break;
|
|
158
|
-
}
|
|
159
|
-
case "subscription-error": {
|
|
160
|
-
if (this.leader || message.tabId === this.tabId) {
|
|
161
|
-
break;
|
|
162
|
-
}
|
|
163
|
-
this.onSubscriptionError?.(message.key, message.error);
|
|
164
|
-
break;
|
|
165
|
-
}
|
|
166
|
-
case "yield-leadership": {
|
|
167
|
-
if (this.knownLeader === message.tabId) {
|
|
168
|
-
this.knownLeader = void 0;
|
|
169
|
-
}
|
|
170
|
-
break;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
handleClaimLeadership(message) {
|
|
175
|
-
if (message.tabId === this.tabId) {
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
if (this.leader) {
|
|
179
|
-
this.broadcast({ type: "heartbeat", tabId: this.tabId, ts: Date.now() });
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
if (this.knownLeader === void 0 && message.tabId < this.tabId) {
|
|
183
|
-
this.knownLeader = message.tabId;
|
|
184
|
-
this.lastHeartbeat = message.ts;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
handleHeartbeat(message) {
|
|
188
|
-
if (message.tabId === this.tabId) {
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
if (this.leader) {
|
|
192
|
-
this.resolveLeaderVsLeaderTieBreak(message);
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
this.lastHeartbeat = message.ts;
|
|
196
|
-
this.knownLeader = message.tabId;
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Resolve two leaders existing at once — e.g. this tab was backgrounded
|
|
200
|
-
* and its heartbeat/health timers were throttled while a foreground
|
|
201
|
-
* follower's were not, so the follower's `checkLeaderHealth` timed out
|
|
202
|
-
* the (still-alive) leader and self-promoted. `BroadcastChannel` message
|
|
203
|
-
* delivery isn't subject to the same timer-throttling clamp, so even a
|
|
204
|
-
* backgrounded leader eventually observes the pretender's heartbeat here
|
|
205
|
-
* — resolve the split-brain deterministically with the same
|
|
206
|
-
* lexicographically-smaller-tabId rule used at claim-adoption. If the
|
|
207
|
-
* other tab wins, step down; if we win, reassert immediately so the
|
|
208
|
-
* pretender demotes itself the moment it processes our heartbeat.
|
|
209
|
-
*/
|
|
210
|
-
resolveLeaderVsLeaderTieBreak(message) {
|
|
211
|
-
if (message.tabId < this.tabId) {
|
|
212
|
-
this.leader = false;
|
|
213
|
-
this.knownLeader = message.tabId;
|
|
214
|
-
this.lastHeartbeat = message.ts;
|
|
215
|
-
this.onStopBeingLeader?.();
|
|
216
|
-
} else {
|
|
217
|
-
this.broadcast({ type: "heartbeat", tabId: this.tabId, ts: Date.now() });
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
becomeLeader() {
|
|
221
|
-
if (this.leader) {
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
this.leader = true;
|
|
225
|
-
this.knownLeader = this.tabId;
|
|
226
|
-
this.lastHeartbeat = Date.now();
|
|
227
|
-
this.onBecomeLeader?.();
|
|
228
|
-
this.broadcast({ type: "heartbeat", tabId: this.tabId, ts: Date.now() });
|
|
229
|
-
}
|
|
230
|
-
sendHeartbeat() {
|
|
231
|
-
if (!this.leader) {
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
this.broadcast({ type: "heartbeat", tabId: this.tabId, ts: Date.now() });
|
|
235
|
-
}
|
|
236
|
-
checkLeaderHealth() {
|
|
237
|
-
if (!this.running) {
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
if (this.leader) {
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
if (this.knownLeader !== void 0) {
|
|
244
|
-
const elapsed = Date.now() - this.lastHeartbeat;
|
|
245
|
-
if (elapsed > this.leaderTimeout) {
|
|
246
|
-
this.knownLeader = void 0;
|
|
247
|
-
this.broadcast({ type: "claim-leadership", tabId: this.tabId, ts: Date.now() });
|
|
248
|
-
setTimeout(() => {
|
|
249
|
-
if (this.running && this.knownLeader === void 0) {
|
|
250
|
-
this.becomeLeader();
|
|
251
|
-
}
|
|
252
|
-
}, this.leaderTimeout);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
export { TabCoordinator };
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
const ID_FIELD = "_id";
|
|
2
|
-
const CREATION_FIELD = "_creationTime";
|
|
3
|
-
const rowId = (row) => {
|
|
4
|
-
if (typeof row !== "object" || row === null) {
|
|
5
|
-
return void 0;
|
|
6
|
-
}
|
|
7
|
-
const id = row[ID_FIELD];
|
|
8
|
-
return typeof id === "string" ? id : void 0;
|
|
9
|
-
};
|
|
10
|
-
const isDescending = (list) => {
|
|
11
|
-
let previous;
|
|
12
|
-
for (const existingRow of list) {
|
|
13
|
-
const existing = existingRow[CREATION_FIELD];
|
|
14
|
-
if (typeof existing !== "number") {
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
17
|
-
if (previous !== void 0) {
|
|
18
|
-
if (existing < previous) {
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
if (existing > previous) {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
previous = existing;
|
|
26
|
-
}
|
|
27
|
-
return false;
|
|
28
|
-
};
|
|
29
|
-
const insertionIndex = (list, row) => {
|
|
30
|
-
const creation = row[CREATION_FIELD];
|
|
31
|
-
if (typeof creation !== "number") {
|
|
32
|
-
return list.length;
|
|
33
|
-
}
|
|
34
|
-
const descending = isDescending(list);
|
|
35
|
-
for (const [index, existingRow] of list.entries()) {
|
|
36
|
-
const existing = existingRow[CREATION_FIELD];
|
|
37
|
-
if (typeof existing === "number" && (descending ? existing < creation : existing > creation)) {
|
|
38
|
-
return index;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return list.length;
|
|
42
|
-
};
|
|
43
|
-
const isMutationDelta = (value) => {
|
|
44
|
-
if (typeof value !== "object" || value === null) {
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
const candidate = value;
|
|
48
|
-
return typeof candidate["key"] === "string" && typeof candidate["table"] === "string" && (candidate["op"] === "insert" || candidate["op"] === "update" || candidate["op"] === "delete");
|
|
49
|
-
};
|
|
50
|
-
const applyDelta = (current, delta) => {
|
|
51
|
-
if (!Array.isArray(current)) {
|
|
52
|
-
return void 0;
|
|
53
|
-
}
|
|
54
|
-
const rows = [];
|
|
55
|
-
for (const element of current) {
|
|
56
|
-
const id = rowId(element);
|
|
57
|
-
if (id === void 0) {
|
|
58
|
-
return void 0;
|
|
59
|
-
}
|
|
60
|
-
rows.push(element);
|
|
61
|
-
}
|
|
62
|
-
const { key, op, row } = delta;
|
|
63
|
-
if (op === "delete") {
|
|
64
|
-
const next2 = rows.filter((existing) => existing[ID_FIELD] !== key);
|
|
65
|
-
return next2.length === rows.length ? [...rows] : next2;
|
|
66
|
-
}
|
|
67
|
-
if (row === void 0) {
|
|
68
|
-
return void 0;
|
|
69
|
-
}
|
|
70
|
-
const existingIndex = rows.findIndex((existing) => existing[ID_FIELD] === key);
|
|
71
|
-
if (existingIndex === -1) {
|
|
72
|
-
const next2 = [...rows];
|
|
73
|
-
next2.splice(insertionIndex(rows, row), 0, row);
|
|
74
|
-
return next2;
|
|
75
|
-
}
|
|
76
|
-
const next = [...rows];
|
|
77
|
-
next[existingIndex] = row;
|
|
78
|
-
return next;
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
export { applyDelta, isMutationDelta };
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
const DEFAULT_KEY = "lunora:offline-mutations";
|
|
2
|
-
const createAsyncStoragePersistence = (options) => {
|
|
3
|
-
const { storage } = options;
|
|
4
|
-
const key = options.key ?? DEFAULT_KEY;
|
|
5
|
-
let chain = Promise.resolve();
|
|
6
|
-
const serialize = (run) => {
|
|
7
|
-
const next = chain.then(run, run);
|
|
8
|
-
chain = next.then(
|
|
9
|
-
() => void 0,
|
|
10
|
-
() => void 0
|
|
11
|
-
);
|
|
12
|
-
return next;
|
|
13
|
-
};
|
|
14
|
-
const readAll = async () => {
|
|
15
|
-
const raw = await storage.getItem(key);
|
|
16
|
-
if (raw === null) {
|
|
17
|
-
return [];
|
|
18
|
-
}
|
|
19
|
-
try {
|
|
20
|
-
const parsed = JSON.parse(raw);
|
|
21
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
22
|
-
} catch {
|
|
23
|
-
return [];
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
const writeAll = (mutations) => storage.setItem(key, JSON.stringify(mutations));
|
|
27
|
-
return {
|
|
28
|
-
append: (mutation) => serialize(async () => {
|
|
29
|
-
const mutations = await readAll();
|
|
30
|
-
mutations.push(mutation);
|
|
31
|
-
await writeAll(mutations);
|
|
32
|
-
}),
|
|
33
|
-
clear: () => serialize(() => storage.removeItem(key)),
|
|
34
|
-
load: () => serialize(readAll),
|
|
35
|
-
remove: (id) => serialize(async () => {
|
|
36
|
-
const mutations = await readAll();
|
|
37
|
-
const remaining = mutations.filter((mutation) => mutation.id !== id);
|
|
38
|
-
if (remaining.length !== mutations.length) {
|
|
39
|
-
await writeAll(remaining);
|
|
40
|
-
}
|
|
41
|
-
})
|
|
42
|
-
};
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export { createAsyncStoragePersistence };
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
class ClientQueryStore {
|
|
2
|
-
/** Current values, keyed by the ref's stable key. Absent = never set. */
|
|
3
|
-
values = /* @__PURE__ */ new Map();
|
|
4
|
-
/** Subscribers keyed by ref key — notified on every set. */
|
|
5
|
-
subscribers = /* @__PURE__ */ new Map();
|
|
6
|
-
/**
|
|
7
|
-
* Return the current value for `ref`, or `ref.defaultValue` if none has
|
|
8
|
-
* been set explicitly. Returns `ref.defaultValue` when the slot has been
|
|
9
|
-
* set to `undefined` (which is distinct from "never set").
|
|
10
|
-
*/
|
|
11
|
-
get(ref) {
|
|
12
|
-
if (this.values.has(ref.key)) {
|
|
13
|
-
return this.values.get(ref.key);
|
|
14
|
-
}
|
|
15
|
-
return ref.defaultValue;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Set a new value for `ref` and notify every subscriber. Pass `undefined`
|
|
19
|
-
* to reset the slot to `ref.defaultValue` — delegates to {@link reset} so
|
|
20
|
-
* `get` reports the default rather than a stored `undefined`.
|
|
21
|
-
*/
|
|
22
|
-
set(ref, value) {
|
|
23
|
-
if (value === void 0) {
|
|
24
|
-
this.reset(ref);
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
this.values.set(ref.key, value);
|
|
28
|
-
this.notify(ref.key, value);
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Delete the stored value for `ref`, resetting to `ref.defaultValue` and
|
|
32
|
-
* notifying subscribers.
|
|
33
|
-
*/
|
|
34
|
-
reset(ref) {
|
|
35
|
-
this.values.delete(ref.key);
|
|
36
|
-
this.notify(ref.key, ref.defaultValue);
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Subscribe to changes for `ref`. The callback is NOT invoked on
|
|
40
|
-
* registration — callers should read the current value via
|
|
41
|
-
* {@link get} first. Returns an unsubscribe function.
|
|
42
|
-
*/
|
|
43
|
-
subscribe(ref, callback) {
|
|
44
|
-
let subs = this.subscribers.get(ref.key);
|
|
45
|
-
if (!subs) {
|
|
46
|
-
subs = /* @__PURE__ */ new Set();
|
|
47
|
-
this.subscribers.set(ref.key, subs);
|
|
48
|
-
}
|
|
49
|
-
subs.add(callback);
|
|
50
|
-
return () => {
|
|
51
|
-
subs.delete(callback);
|
|
52
|
-
if (subs.size === 0) {
|
|
53
|
-
this.subscribers.delete(ref.key);
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Notify every subscriber of a value change for the given key with the
|
|
59
|
-
* resolved value — the caller passes the just-stored value on `set` or
|
|
60
|
-
* `ref.defaultValue` on `reset`, so a subscriber always sees the same value
|
|
61
|
-
* {@link get} would return (never a stale re-read after the store mutation).
|
|
62
|
-
*/
|
|
63
|
-
notify(key, value) {
|
|
64
|
-
const subs = this.subscribers.get(key);
|
|
65
|
-
if (!subs) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
for (const callback of subs) {
|
|
69
|
-
try {
|
|
70
|
-
callback(value);
|
|
71
|
-
} catch {
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
const createClientQuery = (key, defaultValue) => {
|
|
77
|
-
return { defaultValue, key };
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
export { ClientQueryStore, createClientQuery };
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { LunoraError } from '@lunora/errors';
|
|
2
|
-
import { p as promisifyRequest, c as createWithStore, a as createDatabaseOpener } from './idb-utility-DrSVX43Q.mjs';
|
|
3
|
-
|
|
4
|
-
const createInMemoryPersistence = () => {
|
|
5
|
-
const entries = /* @__PURE__ */ new Map();
|
|
6
|
-
const clone = (mutation) => {
|
|
7
|
-
return {
|
|
8
|
-
args: { ...mutation.args },
|
|
9
|
-
functionPath: mutation.functionPath,
|
|
10
|
-
id: mutation.id,
|
|
11
|
-
identity: mutation.identity,
|
|
12
|
-
shardKey: mutation.shardKey
|
|
13
|
-
};
|
|
14
|
-
};
|
|
15
|
-
return {
|
|
16
|
-
append: (mutation) => {
|
|
17
|
-
entries.set(mutation.id, clone(mutation));
|
|
18
|
-
return Promise.resolve();
|
|
19
|
-
},
|
|
20
|
-
clear: () => {
|
|
21
|
-
entries.clear();
|
|
22
|
-
return Promise.resolve();
|
|
23
|
-
},
|
|
24
|
-
load: () => Promise.resolve([...entries.values()].map((mutation) => clone(mutation))),
|
|
25
|
-
remove: (id) => {
|
|
26
|
-
entries.delete(id);
|
|
27
|
-
return Promise.resolve();
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
};
|
|
31
|
-
const DEFAULT_DATABASE = "lunora-outbox";
|
|
32
|
-
const DEFAULT_STORE = "offline-mutations";
|
|
33
|
-
const ID_INDEX = "by_id";
|
|
34
|
-
const createIndexedDbPersistence = (options = {}) => {
|
|
35
|
-
const factory = options.indexedDB ?? (typeof indexedDB === "undefined" ? void 0 : indexedDB);
|
|
36
|
-
if (!factory) {
|
|
37
|
-
throw new LunoraError("INTERNAL", "createIndexedDbPersistence: no IndexedDB available — pass `indexedDB` or use createInMemoryPersistence()");
|
|
38
|
-
}
|
|
39
|
-
const databaseName = options.databaseName ?? DEFAULT_DATABASE;
|
|
40
|
-
const storeName = options.storeName ?? DEFAULT_STORE;
|
|
41
|
-
const openDatabase = createDatabaseOpener(factory, databaseName, 1, (database) => {
|
|
42
|
-
if (!database.objectStoreNames.contains(storeName)) {
|
|
43
|
-
const store = database.createObjectStore(storeName, { autoIncrement: true });
|
|
44
|
-
store.createIndex(ID_INDEX, "id", { unique: true });
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
const withStore = createWithStore(openDatabase, storeName);
|
|
48
|
-
return {
|
|
49
|
-
append: async (mutation) => {
|
|
50
|
-
await withStore("readwrite", (store) => promisifyRequest(store.add(mutation)));
|
|
51
|
-
},
|
|
52
|
-
clear: async () => {
|
|
53
|
-
await withStore("readwrite", (store) => promisifyRequest(store.clear()));
|
|
54
|
-
},
|
|
55
|
-
load: async () => withStore("readonly", (store) => promisifyRequest(store.getAll())),
|
|
56
|
-
remove: async (id) => {
|
|
57
|
-
await withStore("readwrite", async (store) => {
|
|
58
|
-
const key = await promisifyRequest(store.index(ID_INDEX).getKey(id));
|
|
59
|
-
if (key !== void 0) {
|
|
60
|
-
await promisifyRequest(store.delete(key));
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
};
|
|
66
|
-
const resolvePersistenceAdapter = (option, autoProbe = true) => {
|
|
67
|
-
if (option === false) {
|
|
68
|
-
return void 0;
|
|
69
|
-
}
|
|
70
|
-
if (option) {
|
|
71
|
-
return option;
|
|
72
|
-
}
|
|
73
|
-
if (!autoProbe || typeof indexedDB === "undefined") {
|
|
74
|
-
return void 0;
|
|
75
|
-
}
|
|
76
|
-
return createIndexedDbPersistence();
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
export { createInMemoryPersistence, createIndexedDbPersistence, resolvePersistenceAdapter };
|