statewire 0.23.0 → 0.24.1
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/client/core.d.ts +4 -3
- package/dist/client/core.d.ts.map +1 -1
- package/dist/client/core.js +56 -45
- package/dist/client/core.js.map +1 -1
- package/dist/client/session.d.ts +56 -12
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +632 -260
- package/dist/client/session.js.map +1 -1
- package/dist/client/sessionStore.d.ts +1 -0
- package/dist/client/sessionStore.d.ts.map +1 -1
- package/dist/client/sessionStore.js +1 -0
- package/dist/client/sessionStore.js.map +1 -1
- package/dist/host/host.d.ts +6 -5
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js.map +1 -1
- package/dist/indexedDb.d.ts +15 -0
- package/dist/indexedDb.d.ts.map +1 -0
- package/dist/indexedDb.js +171 -0
- package/dist/indexedDb.js.map +1 -0
- package/dist/statewire.d.ts +117 -59
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js +13 -25
- package/dist/statewire.js.map +1 -1
- package/dist/storage.d.ts +42 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +387 -125
- package/dist/storage.js.map +1 -1
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/transports/remote.js +7 -9
- package/dist/transports/remote.js.map +1 -1
- package/dist/types.d.ts +6 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -1
- package/src/client/core.ts +81 -64
- package/src/client/session.ts +886 -424
- package/src/client/sessionStore.ts +2 -0
- package/src/host/host.ts +10 -3
- package/src/indexedDb.ts +167 -0
- package/src/statewire.ts +133 -86
- package/src/storage.ts +548 -112
- package/src/transports/remote.ts +13 -14
- package/src/types.ts +6 -7
package/dist/client/session.js
CHANGED
|
@@ -43,10 +43,11 @@ const laneEntries = (s, protocol) => s.pending.filter((p) => isStamped(p) && p.c
|
|
|
43
43
|
const sortPending = (s) => {
|
|
44
44
|
s.pending.sort((a, b) => (a.seq ?? Infinity) - (b.seq ?? Infinity) || 0);
|
|
45
45
|
};
|
|
46
|
-
const marks = (s, lane, removed) => {
|
|
46
|
+
const marks = (s, lane, removed, failure) => {
|
|
47
47
|
const added = removed.filter((c) => c.by !== void 0 && c.by !== s.store.owner?.id).map((c) => ({
|
|
48
48
|
id: c.id,
|
|
49
|
-
by: c.by
|
|
49
|
+
by: c.by,
|
|
50
|
+
...failure && { failure }
|
|
50
51
|
}));
|
|
51
52
|
const next = [...lane.removed ?? [], ...added];
|
|
52
53
|
return next.length > 0 ? { removed: next } : {};
|
|
@@ -59,12 +60,11 @@ const withOutbox = (lane, outbox) => {
|
|
|
59
60
|
} : rest;
|
|
60
61
|
};
|
|
61
62
|
const outboxOf = (lane) => lane.outbox ?? [];
|
|
62
|
-
const sameHold = (a, b) => a === b || JSON.stringify(a) === JSON.stringify(b);
|
|
63
63
|
const changed = (s, p) => s.lifecycle?.onChange?.(p.inv);
|
|
64
64
|
const stamp = (s, p, seq) => {
|
|
65
65
|
p.seq = seq;
|
|
66
66
|
p.inv.seq = seq;
|
|
67
|
-
delete p.inv.
|
|
67
|
+
delete p.inv.held;
|
|
68
68
|
p.away = false;
|
|
69
69
|
p.attempted = false;
|
|
70
70
|
p.posted = false;
|
|
@@ -78,12 +78,31 @@ const unstamp = (s, p) => {
|
|
|
78
78
|
p.posted = false;
|
|
79
79
|
changed(s, p);
|
|
80
80
|
};
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
const follow = (s, p, stored) => {
|
|
82
|
+
let dirty = false;
|
|
83
|
+
if (p.inv.held === true !== (stored.held === true)) {
|
|
84
|
+
if (stored.held) p.inv.held = true;
|
|
85
|
+
else delete p.inv.held;
|
|
86
|
+
dirty = true;
|
|
87
|
+
}
|
|
88
|
+
if (p.inv.localMeta !== stored.localMeta) {
|
|
89
|
+
if (stored.localMeta === void 0) delete p.inv.localMeta;
|
|
90
|
+
else p.inv.localMeta = stored.localMeta;
|
|
91
|
+
dirty = true;
|
|
92
|
+
}
|
|
93
|
+
if (p.command.params !== stored.params) {
|
|
94
|
+
p.command = {
|
|
95
|
+
...p.command,
|
|
96
|
+
params: stored.params,
|
|
97
|
+
...stored.meta === void 0 ? {} : { meta: stored.meta }
|
|
98
|
+
};
|
|
99
|
+
if (stored.meta === void 0) delete p.command.meta;
|
|
100
|
+
p.inv.params = paramsOf(stored);
|
|
101
|
+
dirty = true;
|
|
102
|
+
}
|
|
103
|
+
if (dirty) changed(s, p);
|
|
86
104
|
};
|
|
105
|
+
const paramsOf = (stored) => stored.meta === void 0 ? stored.params : reviveDocumentValue(structuredClone(stored.params), stored.meta);
|
|
87
106
|
const dropPending = (s, p) => {
|
|
88
107
|
s.pending = s.pending.filter((q) => q !== p);
|
|
89
108
|
s.lifecycle?.onCovered?.(p.inv);
|
|
@@ -120,10 +139,12 @@ const setRecord = (s, record) => {
|
|
|
120
139
|
if (!s.deactivated) try {
|
|
121
140
|
s.store.save(record);
|
|
122
141
|
} catch (cause) {
|
|
142
|
+
if (isQuota(cause)) throw cause;
|
|
123
143
|
storageFailed(s, cause);
|
|
124
144
|
throw s.storageError;
|
|
125
145
|
}
|
|
126
146
|
s.record = record;
|
|
147
|
+
s.notify();
|
|
127
148
|
};
|
|
128
149
|
const toOutbox = (p) => {
|
|
129
150
|
const { protocol: _protocol, ...command } = p.command;
|
|
@@ -132,7 +153,8 @@ const toOutbox = (p) => {
|
|
|
132
153
|
...command,
|
|
133
154
|
ts: p.inv.ts,
|
|
134
155
|
...p.by !== void 0 && { by: p.by },
|
|
135
|
-
...p.inv.
|
|
156
|
+
...p.inv.held && { held: true },
|
|
157
|
+
...p.inv.localMeta !== void 0 && { localMeta: p.inv.localMeta }
|
|
136
158
|
};
|
|
137
159
|
};
|
|
138
160
|
const toStored = (s, p) => {
|
|
@@ -143,7 +165,8 @@ const toStored = (s, p) => {
|
|
|
143
165
|
...s.store.owner !== void 0 && { owner: s.store.owner.id },
|
|
144
166
|
...p.by !== void 0 && { by: p.by },
|
|
145
167
|
...command,
|
|
146
|
-
ts: p.inv.ts
|
|
168
|
+
ts: p.inv.ts,
|
|
169
|
+
...p.inv.localMeta !== void 0 && { localMeta: p.inv.localMeta }
|
|
147
170
|
};
|
|
148
171
|
};
|
|
149
172
|
const backlog = (s, protocol) => laneEntries(s, protocol).map((p) => toStored(s, p));
|
|
@@ -154,20 +177,25 @@ const mergeBacklog = (s, protocol) => {
|
|
|
154
177
|
};
|
|
155
178
|
const durableLanes = (s) => [.../* @__PURE__ */ new Set([...Object.keys(s.record.lanes), ...s.pending.map((p) => p.command.protocol).filter((protocol) => !isEphemeral(s, protocol))])];
|
|
156
179
|
const syncBacklogInner = (s) => {
|
|
180
|
+
if (s.shared && s.shared.notices.length > 0) noticeInner(s, s.shared.notices.splice(0));
|
|
157
181
|
const parked = s.shared ? s.shared.parked.splice(0) : [];
|
|
182
|
+
const settled = s.shared ? new Set(s.shared.settled) : /* @__PURE__ */ new Set();
|
|
183
|
+
if (s.shared) s.shared.settled.clear();
|
|
158
184
|
let lanes = s.record.lanes;
|
|
159
185
|
for (const protocol of durableLanes(s)) {
|
|
160
186
|
const lane = laneOf(s.record, protocol);
|
|
161
|
-
const parkedHere = parked.filter((v) => v.protocol === protocol).
|
|
187
|
+
const parkedHere = parked.filter((v) => v.protocol === protocol).flatMap(({ verdict }) => {
|
|
162
188
|
const id = verdict.id ?? lane.commands.find((c) => c.seq === verdict.seq)?.id;
|
|
163
|
-
|
|
189
|
+
if (id === void 0 || settled.has(id)) return [];
|
|
190
|
+
return [{
|
|
164
191
|
...verdict,
|
|
165
192
|
id
|
|
166
|
-
};
|
|
193
|
+
}];
|
|
167
194
|
});
|
|
168
195
|
const parkedSeqs = new Set(parkedHere.map((v) => v.seq));
|
|
169
196
|
const commands = mergeBacklog(s, protocol).filter((c) => !parkedSeqs.has(c.seq));
|
|
170
|
-
const
|
|
197
|
+
const kept = (lane.verdicts ?? []).filter((v) => v.id === void 0 || !settled.has(v.id));
|
|
198
|
+
const verdicts = parkedHere.length > 0 || kept.length !== (lane.verdicts ?? []).length ? [...kept, ...parkedHere].slice(-64) : lane.verdicts;
|
|
171
199
|
if (verdicts === lane.verdicts && commands.length === lane.commands.length && commands.every((c, i) => c.seq === lane.commands[i].seq)) continue;
|
|
172
200
|
lanes = {
|
|
173
201
|
...lanes,
|
|
@@ -202,8 +230,14 @@ const transact = (s, fn) => {
|
|
|
202
230
|
}
|
|
203
231
|
const shared = s.shared;
|
|
204
232
|
const result = shared.txq.then(() => s.store.lock(async () => {
|
|
205
|
-
|
|
206
|
-
|
|
233
|
+
const stored = await readStored(s);
|
|
234
|
+
shared.inTx = true;
|
|
235
|
+
try {
|
|
236
|
+
refresh(s, stored);
|
|
237
|
+
fn();
|
|
238
|
+
} finally {
|
|
239
|
+
shared.inTx = false;
|
|
240
|
+
}
|
|
207
241
|
watchOwners(s);
|
|
208
242
|
s.notify();
|
|
209
243
|
const persistence = s.store.flush?.();
|
|
@@ -215,14 +249,15 @@ const transact = (s, fn) => {
|
|
|
215
249
|
shared.txq = result.then(() => {}, () => {});
|
|
216
250
|
return result;
|
|
217
251
|
};
|
|
218
|
-
const
|
|
219
|
-
|
|
252
|
+
const readStored = async (s) => s.store.read ? await s.store.read() : s.store.load();
|
|
253
|
+
const refresh = (s, stored) => {
|
|
220
254
|
if (stored !== null && stored.clientId !== s.record.clientId) {
|
|
221
255
|
adoptReset(s, stored);
|
|
222
256
|
return;
|
|
223
257
|
}
|
|
224
|
-
if (stored !== null) s.record =
|
|
258
|
+
if (stored !== null) s.record = boundInbox(s.store, stored);
|
|
225
259
|
reconcileLocal(s);
|
|
260
|
+
followReleases(s);
|
|
226
261
|
};
|
|
227
262
|
const reconcileLocal = (s) => {
|
|
228
263
|
const me = s.store.owner?.id;
|
|
@@ -240,7 +275,7 @@ const reconcileLocal = (s) => {
|
|
|
240
275
|
}
|
|
241
276
|
unstamp(s, p);
|
|
242
277
|
}
|
|
243
|
-
|
|
278
|
+
follow(s, p, queued);
|
|
244
279
|
} else if (stamped && stamped.owner !== me) {
|
|
245
280
|
if (p.adopted) dropPending(s, p);
|
|
246
281
|
else if (!p.away) {
|
|
@@ -249,12 +284,20 @@ const reconcileLocal = (s) => {
|
|
|
249
284
|
}
|
|
250
285
|
} else if (stamped) {
|
|
251
286
|
if (p.seq !== stamped.seq) stamp(s, p, stamped.seq);
|
|
252
|
-
} else if (isOutboxed(p)) {
|
|
253
|
-
|
|
287
|
+
} else if (!p.adopted && (isOutboxed(p) || p.away)) {
|
|
288
|
+
const mark = lane.removed?.find((m) => m.id === p.id);
|
|
289
|
+
if (mark === void 0) {
|
|
254
290
|
p.away = true;
|
|
255
291
|
continue;
|
|
256
292
|
}
|
|
257
|
-
|
|
293
|
+
s.lifecycle?.onCovered?.(p.inv);
|
|
294
|
+
settleEntry(s, p, {
|
|
295
|
+
ok: false,
|
|
296
|
+
failure: mark.failure ?? {
|
|
297
|
+
fate: "aborted",
|
|
298
|
+
cause: "removed"
|
|
299
|
+
}
|
|
300
|
+
}, mark.failure ? "noticed" : "memory");
|
|
258
301
|
const { removed: _removed, ...rest } = lane;
|
|
259
302
|
const left = lane.removed.filter((m) => m.id !== p.id);
|
|
260
303
|
setRecord(s, withLane(s.record, protocol, {
|
|
@@ -264,6 +307,48 @@ const reconcileLocal = (s) => {
|
|
|
264
307
|
}
|
|
265
308
|
}
|
|
266
309
|
};
|
|
310
|
+
const locate = (s, id) => {
|
|
311
|
+
for (const [protocol, lane] of Object.entries(s.record.lanes)) {
|
|
312
|
+
const stored = outboxOf(lane).find((c) => c.id === id);
|
|
313
|
+
if (stored) return {
|
|
314
|
+
protocol,
|
|
315
|
+
stored
|
|
316
|
+
};
|
|
317
|
+
if (lane.commands.some((c) => c.id === id)) return { protocol };
|
|
318
|
+
}
|
|
319
|
+
const p = s.pending.find((q) => q.id === id);
|
|
320
|
+
if (p === void 0) return void 0;
|
|
321
|
+
return isOutboxed(p) && isEphemeral(s, p.command.protocol) ? {
|
|
322
|
+
protocol: p.command.protocol,
|
|
323
|
+
stored: toOutbox(p)
|
|
324
|
+
} : { protocol: p.command.protocol };
|
|
325
|
+
};
|
|
326
|
+
const settleReleases = (s, id, outcome) => {
|
|
327
|
+
const waiters = s.releases.get(id);
|
|
328
|
+
if (waiters === void 0) return;
|
|
329
|
+
s.releases.delete(id);
|
|
330
|
+
for (const waiter of waiters) if (outcome.ok) waiter.resolve(outcome.payload);
|
|
331
|
+
else waiter.reject(new StatewireSendError(outcome.failure));
|
|
332
|
+
};
|
|
333
|
+
const followReleases = (s) => {
|
|
334
|
+
for (const id of [...s.releases.keys()]) {
|
|
335
|
+
if (locate(s, id) !== void 0) continue;
|
|
336
|
+
const lanes = Object.values(s.record.lanes);
|
|
337
|
+
const notice = lanes.flatMap((lane) => lane.inbox ?? []).find((n) => n.id === id);
|
|
338
|
+
const mark = lanes.flatMap((lane) => lane.removed ?? []).find((m) => m.id === id);
|
|
339
|
+
const failure = notice?.failure ?? mark?.failure;
|
|
340
|
+
settleReleases(s, id, failure ? {
|
|
341
|
+
ok: false,
|
|
342
|
+
failure
|
|
343
|
+
} : {
|
|
344
|
+
ok: true,
|
|
345
|
+
payload: notice?.result === void 0 ? void 0 : paramsOf({
|
|
346
|
+
params: notice.result.value,
|
|
347
|
+
meta: notice.result.meta
|
|
348
|
+
})[0]
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
};
|
|
267
352
|
const watchOwners = (s) => {
|
|
268
353
|
const owner = s.store.owner;
|
|
269
354
|
const shared = s.shared;
|
|
@@ -385,69 +470,125 @@ const cover = (s, entry) => {
|
|
|
385
470
|
s.lifecycle?.onApplied(entry.inv);
|
|
386
471
|
s.lifecycle?.onCovered?.(entry.inv);
|
|
387
472
|
};
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
473
|
+
const MAX_INBOX = 256;
|
|
474
|
+
const updateOf = (protocol, stored) => ({
|
|
475
|
+
protocol,
|
|
476
|
+
id: stored.id,
|
|
477
|
+
method: stored.method,
|
|
478
|
+
params: paramsOf(stored),
|
|
479
|
+
...stored.seq !== void 0 && { seq: stored.seq },
|
|
480
|
+
status: "settled",
|
|
481
|
+
...stored.result !== void 0 && { result: paramsOf({
|
|
482
|
+
params: stored.result.value,
|
|
483
|
+
meta: stored.result.meta
|
|
484
|
+
})[0] },
|
|
485
|
+
...stored.failure !== void 0 && { failure: stored.failure },
|
|
486
|
+
...stored.localMeta !== void 0 && { localMeta: stored.localMeta }
|
|
487
|
+
});
|
|
488
|
+
const boundInbox = (store, record) => {
|
|
399
489
|
let lanes = record.lanes;
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
490
|
+
const dropped = [];
|
|
491
|
+
for (const [protocol, lane] of Object.entries(lanes)) {
|
|
492
|
+
const inbox = lane.inbox;
|
|
493
|
+
if (inbox === void 0 || inbox.length <= 256) continue;
|
|
494
|
+
const sorted = [...inbox].sort((x, y) => x.ts - y.ts);
|
|
495
|
+
const cut = sorted.length - 256;
|
|
496
|
+
dropped.push(...sorted.slice(0, cut).map((n) => updateOf(protocol, n)));
|
|
497
|
+
lanes = {
|
|
498
|
+
...lanes,
|
|
499
|
+
[protocol]: {
|
|
500
|
+
...lane,
|
|
501
|
+
inbox: sorted.slice(cut)
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
if (dropped.length > 0) store.onEvict?.({
|
|
506
|
+
reason: "inbox",
|
|
507
|
+
dropped
|
|
508
|
+
});
|
|
407
509
|
return lanes === record.lanes ? record : {
|
|
408
510
|
...record,
|
|
409
511
|
lanes
|
|
410
512
|
};
|
|
411
513
|
};
|
|
412
|
-
const
|
|
413
|
-
const
|
|
414
|
-
|
|
514
|
+
const toNotice = (stored, outcome) => {
|
|
515
|
+
const result = outcome.ok ? encodeParams(outcome.payload === void 0 ? [] : [outcome.payload]) : void 0;
|
|
516
|
+
return {
|
|
517
|
+
id: stored.id,
|
|
518
|
+
method: stored.method,
|
|
519
|
+
params: stored.params,
|
|
520
|
+
...stored.meta !== void 0 && { meta: stored.meta },
|
|
521
|
+
ts: stored.ts,
|
|
522
|
+
...stored.seq !== void 0 && { seq: stored.seq },
|
|
523
|
+
...stored.localMeta !== void 0 && { localMeta: stored.localMeta },
|
|
524
|
+
...result !== void 0 && { result: {
|
|
525
|
+
value: result.params,
|
|
526
|
+
...result.meta !== void 0 && { meta: result.meta }
|
|
527
|
+
} },
|
|
528
|
+
...!outcome.ok && { failure: outcome.failure }
|
|
529
|
+
};
|
|
530
|
+
};
|
|
531
|
+
const lostNotice = (stored) => toNotice(stored, {
|
|
532
|
+
ok: false,
|
|
533
|
+
failure: LOST
|
|
534
|
+
});
|
|
535
|
+
const LOST = {
|
|
536
|
+
fate: "lost",
|
|
537
|
+
cause: "session-lost"
|
|
538
|
+
};
|
|
539
|
+
const withNotices = (lane, notices) => {
|
|
540
|
+
const known = new Set((lane.inbox ?? []).map((n) => n.id));
|
|
541
|
+
const added = notices.filter((n) => !known.has(n.id));
|
|
542
|
+
return added.length === 0 ? lane : {
|
|
415
543
|
...lane,
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
...entry.command.produced !== void 0 && { produced: entry.command.produced },
|
|
419
|
-
command: entry.command.method,
|
|
420
|
-
params: entry.command.params,
|
|
421
|
-
...entry.command.meta !== void 0 && { meta: entry.command.meta },
|
|
422
|
-
failure,
|
|
423
|
-
ts: entry.inv.ts,
|
|
424
|
-
seq: entry.seq
|
|
425
|
-
}])
|
|
426
|
-
}));
|
|
427
|
-
s.notify();
|
|
544
|
+
inbox: [...lane.inbox ?? [], ...added]
|
|
545
|
+
};
|
|
428
546
|
};
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
547
|
+
const noticeInner = (s, notices) => {
|
|
548
|
+
let record = s.record;
|
|
549
|
+
for (const { entry, outcome } of notices) {
|
|
550
|
+
const protocol = entry.command.protocol;
|
|
551
|
+
const lane = laneOf(record, protocol);
|
|
552
|
+
record = withLane(record, protocol, {
|
|
553
|
+
...withNotices(lane, [toNotice(toStored(s, entry), outcome)]),
|
|
554
|
+
commands: lane.commands.filter((c) => c.id !== entry.id)
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
setRecord(s, boundInbox(s.store, record));
|
|
558
|
+
deliver(s);
|
|
559
|
+
};
|
|
560
|
+
const notice = (s, entry, outcome) => {
|
|
561
|
+
if (!s.shared || s.shared.inTx) {
|
|
562
|
+
noticeInner(s, [{
|
|
563
|
+
entry,
|
|
564
|
+
outcome
|
|
565
|
+
}]);
|
|
432
566
|
return;
|
|
433
567
|
}
|
|
434
|
-
|
|
568
|
+
s.shared.notices.push({
|
|
569
|
+
entry,
|
|
570
|
+
outcome
|
|
571
|
+
});
|
|
572
|
+
syncBacklog(s);
|
|
435
573
|
};
|
|
436
|
-
const settleEntry = (s, entry, outcome) => {
|
|
574
|
+
const settleEntry = (s, entry, outcome, mode = "notice") => {
|
|
437
575
|
s.pending = s.pending.filter((p) => p !== entry);
|
|
576
|
+
if (s.shared && !entry.adopted) s.shared.settled.add(entry.id);
|
|
577
|
+
settleReleases(s, entry.id, outcome);
|
|
578
|
+
const durable = mode === "noticed" || mode === "notice" && (outcome.ok || outcome.failure.cause !== "unmount") && isStamped(entry) && !isEphemeral(s, entry.command.protocol);
|
|
579
|
+
if (durable && mode === "notice") notice(s, entry, outcome);
|
|
438
580
|
if (outcome.ok) {
|
|
439
581
|
entry.resolve(outcome.payload);
|
|
440
582
|
s.lifecycle?.onSettle(entry.inv, {
|
|
441
583
|
type: "accepted",
|
|
442
584
|
payload: outcome.payload
|
|
443
|
-
});
|
|
585
|
+
}, durable);
|
|
444
586
|
} else {
|
|
445
|
-
if (!(outcome.failure.cause === "unmount") && isStamped(entry) && !entry.adopted && !isEphemeral(s, entry.command.protocol)) retainFailed(s, entry, outcome.failure);
|
|
446
587
|
entry.reject(new StatewireSendError(outcome.failure));
|
|
447
588
|
s.lifecycle?.onSettle(entry.inv, {
|
|
448
589
|
type: "rejected",
|
|
449
590
|
error: new StatewireSendError(outcome.failure)
|
|
450
|
-
});
|
|
591
|
+
}, durable);
|
|
451
592
|
}
|
|
452
593
|
};
|
|
453
594
|
const settleAll = (s, detach) => {
|
|
@@ -508,7 +649,10 @@ const settleRemoved = (s, entry) => {
|
|
|
508
649
|
const requeue = (s, survivors) => {
|
|
509
650
|
for (const old of survivors) {
|
|
510
651
|
s.lifecycle?.onCovered?.(old.inv);
|
|
511
|
-
enqueueInner(s, old.command, old.inv.params, old.resolve, old.reject,
|
|
652
|
+
enqueueInner(s, old.command, old.inv.params, old.resolve, old.reject, {
|
|
653
|
+
...old.inv.held && { hold: true },
|
|
654
|
+
...old.inv.localMeta !== void 0 && { localMeta: old.inv.localMeta }
|
|
655
|
+
});
|
|
512
656
|
}
|
|
513
657
|
};
|
|
514
658
|
const rebaseRecord = (s, adopt) => {
|
|
@@ -522,15 +666,14 @@ const rebaseRecord = (s, adopt) => {
|
|
|
522
666
|
adopt();
|
|
523
667
|
let lanes = s.record.lanes;
|
|
524
668
|
for (const [protocol, lane] of Object.entries(previous)) {
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
669
|
+
const current = laneOf({
|
|
670
|
+
...s.record,
|
|
671
|
+
lanes
|
|
672
|
+
}, protocol);
|
|
673
|
+
if (!lane.inbox?.length || current.inbox === lane.inbox) continue;
|
|
528
674
|
lanes = {
|
|
529
675
|
...lanes,
|
|
530
|
-
[protocol]:
|
|
531
|
-
...current,
|
|
532
|
-
failed: boundFailed([...current.failed ?? [], ...failed])
|
|
533
|
-
}
|
|
676
|
+
[protocol]: withNotices(current, lane.inbox)
|
|
534
677
|
};
|
|
535
678
|
}
|
|
536
679
|
if (lanes !== s.record.lanes) setRecord(s, {
|
|
@@ -548,7 +691,7 @@ const resetRecord = (s, clientId) => rebaseRecord(s, () => setRecord(s, {
|
|
|
548
691
|
}));
|
|
549
692
|
const adoptReset = (s, stored) => {
|
|
550
693
|
rebaseRecord(s, () => {
|
|
551
|
-
s.record =
|
|
694
|
+
s.record = boundInbox(s.store, stored);
|
|
552
695
|
});
|
|
553
696
|
if (s.attach === "attached") s.transport?.connection.reconnect();
|
|
554
697
|
};
|
|
@@ -725,17 +868,13 @@ const publishIngested = (s) => {
|
|
|
725
868
|
wake(s);
|
|
726
869
|
};
|
|
727
870
|
const sessionLostInner = (s, protocol, seqWm) => {
|
|
871
|
+
const lane = laneOf(s.record, protocol);
|
|
872
|
+
const failure = {
|
|
873
|
+
fate: "lost",
|
|
874
|
+
cause: "session-lost"
|
|
875
|
+
};
|
|
728
876
|
const held = laneEntries(s, protocol);
|
|
729
|
-
settleLost(s, held);
|
|
730
877
|
s.durableSeq[protocol] = seqWm;
|
|
731
|
-
const { failed, outbox, removed } = laneOf(s.record, protocol);
|
|
732
|
-
setRecord(s, withLane(s.record, protocol, {
|
|
733
|
-
nextSeq: seqWm + 1,
|
|
734
|
-
commands: [],
|
|
735
|
-
...outbox !== void 0 && { outbox },
|
|
736
|
-
...removed !== void 0 && { removed },
|
|
737
|
-
...failed !== void 0 && { failed }
|
|
738
|
-
}));
|
|
739
878
|
if (held.length > 0 && s.onSessionLost?.(held.map((p) => ({
|
|
740
879
|
id: p.id,
|
|
741
880
|
method: p.inv.method,
|
|
@@ -743,11 +882,51 @@ const sessionLostInner = (s, protocol, seqWm) => {
|
|
|
743
882
|
ts: p.inv.ts,
|
|
744
883
|
seq: p.seq,
|
|
745
884
|
...p.inv.clientId !== void 0 && { clientId: p.inv.clientId }
|
|
746
|
-
}))) === "requeue")
|
|
885
|
+
}))) === "requeue") {
|
|
886
|
+
if (s.onDeprecatedPath) s.onDeprecatedPath({
|
|
887
|
+
kind: "session-lost-requeue",
|
|
888
|
+
count: held.length
|
|
889
|
+
});
|
|
890
|
+
else console.warn(`statewire: onSessionLost "requeue" re-enqueued ${held.length} commands; it is deprecated and will be removed`);
|
|
891
|
+
for (const p of held) {
|
|
892
|
+
s.lifecycle?.onCovered?.(p.inv);
|
|
893
|
+
settleEntry(s, p, {
|
|
894
|
+
ok: false,
|
|
895
|
+
failure
|
|
896
|
+
}, "memory");
|
|
897
|
+
}
|
|
898
|
+
setRecord(s, withLane(s.record, protocol, {
|
|
899
|
+
...lane,
|
|
900
|
+
nextSeq: seqWm + 1,
|
|
901
|
+
commands: []
|
|
902
|
+
}));
|
|
903
|
+
for (const old of held) enqueueInner(s, old.command, old.inv.params, () => {}, () => {}, { ...old.inv.localMeta !== void 0 && { localMeta: old.inv.localMeta } });
|
|
904
|
+
} else {
|
|
905
|
+
const stamps = mergeBacklog(s, protocol);
|
|
906
|
+
const queued = outboxOf(lane);
|
|
907
|
+
const { outbox: _outbox, ...rest } = lane;
|
|
908
|
+
setRecord(s, withLane(s.record, protocol, {
|
|
909
|
+
...withNotices(rest, [...stamps.map((c) => lostNotice(c)), ...queued.map((c) => lostNotice(c))]),
|
|
910
|
+
nextSeq: seqWm + 1,
|
|
911
|
+
commands: [],
|
|
912
|
+
...marks(s, lane, [...stamps.map((c) => ({
|
|
913
|
+
id: c.id,
|
|
914
|
+
by: c.by ?? c.owner
|
|
915
|
+
})), ...queued], failure)
|
|
916
|
+
}));
|
|
917
|
+
for (const p of s.pending.filter((q) => q.command.protocol === protocol)) {
|
|
918
|
+
s.lifecycle?.onCovered?.(p.inv);
|
|
919
|
+
settleEntry(s, p, {
|
|
920
|
+
ok: false,
|
|
921
|
+
failure
|
|
922
|
+
}, "noticed");
|
|
923
|
+
}
|
|
924
|
+
deliver(s);
|
|
925
|
+
}
|
|
747
926
|
publish(s);
|
|
748
927
|
wake(s);
|
|
749
928
|
};
|
|
750
|
-
const demote = (s, protocol, watermark, demoted) => {
|
|
929
|
+
const demote = (s, protocol, watermark, demoted, waiting) => {
|
|
751
930
|
const lane = laneOf(s.record, protocol);
|
|
752
931
|
const local = laneEntries(s, protocol).filter((p) => p.seq > watermark);
|
|
753
932
|
const above = new Map(lane.commands.filter((c) => c.seq > watermark).map((c) => [c.id, c]));
|
|
@@ -758,6 +937,12 @@ const demote = (s, protocol, watermark, demoted) => {
|
|
|
758
937
|
sessionLostInner(s, protocol, watermark);
|
|
759
938
|
return;
|
|
760
939
|
}
|
|
940
|
+
const me = s.store.owner?.id;
|
|
941
|
+
const foreign = moved.find((c) => c.owner !== void 0 && c.owner !== me && !local.some((p) => p.id === c.id));
|
|
942
|
+
if (foreign !== void 0) {
|
|
943
|
+
for (const p of local) if (p.seq > foreign.seq) waiting.add(p);
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
761
946
|
setRecord(s, withLane(s.record, protocol, {
|
|
762
947
|
...withOutbox(lane, [...moved.map(({ seq: _seq, owner: _owner, ...command }) => command), ...outboxOf(lane)]),
|
|
763
948
|
nextSeq: watermark + 1,
|
|
@@ -768,6 +953,19 @@ const demote = (s, protocol, watermark, demoted) => {
|
|
|
768
953
|
else unstamp(s, p);
|
|
769
954
|
sortPending(s);
|
|
770
955
|
};
|
|
956
|
+
const recheckLater = (s, watermarks) => {
|
|
957
|
+
if (s.rechecks.watermarks !== watermarks) s.rechecks = {
|
|
958
|
+
watermarks,
|
|
959
|
+
count: 0
|
|
960
|
+
};
|
|
961
|
+
s.rechecks.count += 1;
|
|
962
|
+
if (s.recheck !== void 0) return;
|
|
963
|
+
s.recheck = setTimeout(() => {
|
|
964
|
+
s.recheck = void 0;
|
|
965
|
+
if (s.running && s.attach === "attached") s.transport?.connection.reconnect();
|
|
966
|
+
}, backoff(s.rechecks.count + 1));
|
|
967
|
+
};
|
|
968
|
+
const gapFill = (s) => s.selection !== void 0 && s.selection.version >= "2026-11-01";
|
|
771
969
|
const selectionOf = (syn) => {
|
|
772
970
|
if (syn.version === void 0 || syn.protocols === void 0) return void 0;
|
|
773
971
|
return {
|
|
@@ -807,18 +1005,24 @@ const handleHello = (s, { ops, syn }) => {
|
|
|
807
1005
|
s.attachEntries = /* @__PURE__ */ new Set();
|
|
808
1006
|
const reconcile = () => {
|
|
809
1007
|
const demoted = /* @__PURE__ */ new Set();
|
|
1008
|
+
const waiting = /* @__PURE__ */ new Set();
|
|
810
1009
|
for (const lane of lanes) {
|
|
811
1010
|
if (lane.record === "ephemeral") {
|
|
812
1011
|
s.ephemeralSeqs[lane.name] = Math.max(Object.hasOwn(s.ephemeralSeqs, lane.name) ? s.ephemeralSeqs[lane.name] : 0, ...laneEntries(s, lane.name).map((p) => p.seq));
|
|
813
1012
|
continue;
|
|
814
1013
|
}
|
|
815
|
-
demote(s, lane.name, lane.seq ?? 0, demoted);
|
|
1014
|
+
demote(s, lane.name, lane.seq ?? 0, demoted, waiting);
|
|
816
1015
|
s.durableSeq[lane.name] = lane.seq ?? 0;
|
|
817
1016
|
}
|
|
818
1017
|
for (const entry of s.pending) {
|
|
819
|
-
entry.posted = attached.has(entry) || isStamped(entry) && entry.seq <= durableSeqOf(s, entry.command.protocol) && !isEphemeral(s, entry.command.protocol);
|
|
1018
|
+
entry.posted = attached.has(entry) || waiting.has(entry) || isStamped(entry) && entry.seq <= durableSeqOf(s, entry.command.protocol) && !isEphemeral(s, entry.command.protocol);
|
|
820
1019
|
entry.stale = false;
|
|
821
1020
|
}
|
|
1021
|
+
if (waiting.size > 0) recheckLater(s, JSON.stringify(lanes.map((lane) => [lane.name, lane.seq])));
|
|
1022
|
+
else s.rechecks = {
|
|
1023
|
+
watermarks: "",
|
|
1024
|
+
count: 0
|
|
1025
|
+
};
|
|
822
1026
|
syncBacklogInner(s);
|
|
823
1027
|
s.sessionKnown = true;
|
|
824
1028
|
flushInner(s, demoted);
|
|
@@ -827,11 +1031,13 @@ const handleHello = (s, { ops, syn }) => {
|
|
|
827
1031
|
reconcile();
|
|
828
1032
|
publishIngested(s);
|
|
829
1033
|
pump(s);
|
|
1034
|
+
deliver(s);
|
|
830
1035
|
return;
|
|
831
1036
|
}
|
|
832
1037
|
transactBackground(s, reconcile, () => {
|
|
833
1038
|
publishIngested(s);
|
|
834
1039
|
pump(s);
|
|
1040
|
+
deliver(s);
|
|
835
1041
|
});
|
|
836
1042
|
};
|
|
837
1043
|
const handleOps = (s, { ops, cmds }) => {
|
|
@@ -976,12 +1182,13 @@ const pump = async (s) => {
|
|
|
976
1182
|
if (!s.hydrated) await s.ready;
|
|
977
1183
|
const transport = s.transport;
|
|
978
1184
|
const flushable = flushCandidates(s).length > 0;
|
|
979
|
-
const queued = s.pending.some((p) => isOutboxed(p) && p.inv.
|
|
1185
|
+
const queued = s.pending.some((p) => isOutboxed(p) && !p.inv.held) || queuedInRecord(s);
|
|
980
1186
|
if (buildLanes(s) === null && !flushable && (!queued || s.attach === "attached")) return;
|
|
981
|
-
|
|
1187
|
+
const waiting = s.pending.length > 0 || queued;
|
|
1188
|
+
if (transport?.parked && !waiting) return;
|
|
982
1189
|
if (s.attach !== "attached" || !transport) {
|
|
983
|
-
if (
|
|
984
|
-
if (transport?.connection.status === "standby" && transport.connection.reason === "idle" || transport?.parked
|
|
1190
|
+
if (!waiting) return;
|
|
1191
|
+
if (transport?.connection.status === "standby" && transport.connection.reason === "idle" || transport?.parked) transport.connection.reconnect();
|
|
985
1192
|
await waitForWake(s);
|
|
986
1193
|
continue;
|
|
987
1194
|
}
|
|
@@ -1011,14 +1218,22 @@ const pump = async (s) => {
|
|
|
1011
1218
|
for (const entry of built.entries) entry.attempted = true;
|
|
1012
1219
|
return transport.send(built.frame);
|
|
1013
1220
|
};
|
|
1014
|
-
result = await (s.shared && !s.deactivated && lanes.entries.length > 0 ? s.store.lock(() => {
|
|
1015
|
-
refresh(s);
|
|
1221
|
+
result = await (s.shared && !s.deactivated && lanes.entries.length > 0 ? s.store.lock(async () => {
|
|
1222
|
+
refresh(s, await readStored(s));
|
|
1016
1223
|
lanes = buildLanes(s);
|
|
1017
1224
|
if (!lanes || epoch !== s.attachEpoch) return null;
|
|
1018
1225
|
return send(lanes);
|
|
1019
1226
|
}) : send(lanes));
|
|
1020
1227
|
} catch {
|
|
1021
1228
|
if (!s.running || isFinished(s)) return;
|
|
1229
|
+
if (lanes !== null && lanes.entries.length > 0 && gapFill(s)) {
|
|
1230
|
+
if (epoch !== s.attachEpoch) continue;
|
|
1231
|
+
for (const entry of lanes.entries) if (s.pending.includes(entry)) entry.posted = true;
|
|
1232
|
+
s.frameFailures += 1;
|
|
1233
|
+
await sleepUntilWake(s, backoff(s.frameFailures));
|
|
1234
|
+
if (s.running && epoch === s.attachEpoch && s.attach === "attached") transport.connection.reconnect();
|
|
1235
|
+
continue;
|
|
1236
|
+
}
|
|
1022
1237
|
if (lanes?.frame.ctx?.length) {
|
|
1023
1238
|
s.lanesOnAttach = true;
|
|
1024
1239
|
transport.connection.reconnect();
|
|
@@ -1077,6 +1292,7 @@ const start = (s) => {
|
|
|
1077
1292
|
transactBackground(s, () => consumeVerdicts(s), () => {
|
|
1078
1293
|
wake(s);
|
|
1079
1294
|
pump(s);
|
|
1295
|
+
deliver(s);
|
|
1080
1296
|
});
|
|
1081
1297
|
});
|
|
1082
1298
|
s.running = true;
|
|
@@ -1085,7 +1301,10 @@ const start = (s) => {
|
|
|
1085
1301
|
s.recordPersisted = true;
|
|
1086
1302
|
adoptOwner(s, void 0);
|
|
1087
1303
|
consumeVerdicts(s);
|
|
1088
|
-
}, () =>
|
|
1304
|
+
}, () => {
|
|
1305
|
+
pump(s);
|
|
1306
|
+
deliver(s);
|
|
1307
|
+
});
|
|
1089
1308
|
return;
|
|
1090
1309
|
}
|
|
1091
1310
|
if (!s.recordPersisted) {
|
|
@@ -1095,9 +1314,12 @@ const start = (s) => {
|
|
|
1095
1314
|
adoptOwner(s, void 0);
|
|
1096
1315
|
s.running = true;
|
|
1097
1316
|
pump(s);
|
|
1317
|
+
deliver(s);
|
|
1098
1318
|
};
|
|
1099
1319
|
const stop = (s) => {
|
|
1100
1320
|
s.running = false;
|
|
1321
|
+
clearTimeout(s.recheck);
|
|
1322
|
+
s.recheck = void 0;
|
|
1101
1323
|
if (s.shared) {
|
|
1102
1324
|
s.shared.unsubscribe?.();
|
|
1103
1325
|
s.shared.unsubscribe = null;
|
|
@@ -1113,7 +1335,12 @@ const setTransport = (s, transport) => {
|
|
|
1113
1335
|
s.transport = transport;
|
|
1114
1336
|
wake(s);
|
|
1115
1337
|
};
|
|
1116
|
-
const
|
|
1338
|
+
const QUOTA = {
|
|
1339
|
+
fate: "aborted",
|
|
1340
|
+
cause: "quota"
|
|
1341
|
+
};
|
|
1342
|
+
const isQuota = (error) => error instanceof Error && error.name === "StatewireQuotaError";
|
|
1343
|
+
const enqueueInner = (s, command, params, resolve, reject, options = {}) => {
|
|
1117
1344
|
const protocol = command.protocol;
|
|
1118
1345
|
const id = randomId();
|
|
1119
1346
|
const entry = {
|
|
@@ -1127,7 +1354,8 @@ const enqueueInner = (s, command, params, resolve, reject, hold) => {
|
|
|
1127
1354
|
ts: Date.now(),
|
|
1128
1355
|
id,
|
|
1129
1356
|
clientId: s.record.clientId,
|
|
1130
|
-
...hold
|
|
1357
|
+
...options.hold && { held: true },
|
|
1358
|
+
...options.localMeta !== void 0 && { localMeta: options.localMeta }
|
|
1131
1359
|
},
|
|
1132
1360
|
adopted: false,
|
|
1133
1361
|
by: s.store.owner?.id,
|
|
@@ -1139,30 +1367,38 @@ const enqueueInner = (s, command, params, resolve, reject, hold) => {
|
|
|
1139
1367
|
resolve,
|
|
1140
1368
|
reject
|
|
1141
1369
|
};
|
|
1142
|
-
s.pending.push(entry);
|
|
1143
1370
|
if (!isEphemeral(s, protocol)) {
|
|
1144
1371
|
const lane = laneOf(s.record, protocol);
|
|
1145
|
-
|
|
1372
|
+
try {
|
|
1373
|
+
setRecord(s, withLane(s.record, protocol, withOutbox(lane, [...outboxOf(lane), toOutbox(entry)])));
|
|
1374
|
+
} catch (error) {
|
|
1375
|
+
if (!isQuota(error)) throw error;
|
|
1376
|
+
reject(new StatewireSendError(QUOTA));
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
s.pending.push(entry);
|
|
1146
1380
|
reorder(s, protocol);
|
|
1147
|
-
}
|
|
1381
|
+
} else s.pending.push(entry);
|
|
1148
1382
|
s.lifecycle?.onStart(entry.inv);
|
|
1149
1383
|
s.notify();
|
|
1150
1384
|
};
|
|
1151
1385
|
const negotiated = (s, protocol) => s.selection?.protocols.some((p) => p.name === protocol) ?? false;
|
|
1152
|
-
const
|
|
1153
|
-
const
|
|
1154
|
-
const cached = views.get(stored);
|
|
1386
|
+
const viewOf = (s, protocol, stored, state) => {
|
|
1387
|
+
const cached = s.views.get(stored);
|
|
1155
1388
|
if (cached?.protocol === protocol) return cached;
|
|
1156
1389
|
const view = {
|
|
1157
1390
|
id: stored.id,
|
|
1158
1391
|
protocol,
|
|
1159
1392
|
command: {
|
|
1160
1393
|
method: stored.method,
|
|
1161
|
-
params:
|
|
1394
|
+
params: paramsOf(stored)
|
|
1162
1395
|
},
|
|
1163
|
-
|
|
1396
|
+
ts: stored.ts,
|
|
1397
|
+
state: state ?? (stored.held ? "held" : "queued"),
|
|
1398
|
+
localMeta: stored.localMeta,
|
|
1399
|
+
durable: s.store.durable !== false && !isEphemeral(s, protocol)
|
|
1164
1400
|
};
|
|
1165
|
-
views.set(stored, view);
|
|
1401
|
+
s.views.set(stored, view);
|
|
1166
1402
|
return view;
|
|
1167
1403
|
};
|
|
1168
1404
|
/** The lane's outbox as this client may flush it: the record's on a durable lane, its own on an ephemeral one. */
|
|
@@ -1171,7 +1407,7 @@ const bubble = (s, protocol, queue) => {
|
|
|
1171
1407
|
const may = s.options.canOvertake?.[protocol];
|
|
1172
1408
|
const order = [];
|
|
1173
1409
|
for (const stored of queue) {
|
|
1174
|
-
if (stored.
|
|
1410
|
+
if (stored.held) {
|
|
1175
1411
|
order.push({
|
|
1176
1412
|
stored,
|
|
1177
1413
|
blocked: true
|
|
@@ -1179,7 +1415,7 @@ const bubble = (s, protocol, queue) => {
|
|
|
1179
1415
|
continue;
|
|
1180
1416
|
}
|
|
1181
1417
|
let at = order.length;
|
|
1182
|
-
while (at > 0 && order[at - 1].blocked && may !== void 0 && may(viewOf(protocol, stored), viewOf(protocol, order[at - 1].stored))) at--;
|
|
1418
|
+
while (at > 0 && order[at - 1].blocked && may !== void 0 && may(viewOf(s, protocol, stored), viewOf(s, protocol, order[at - 1].stored))) at--;
|
|
1183
1419
|
order.splice(at, 0, {
|
|
1184
1420
|
stored,
|
|
1185
1421
|
blocked: at > 0 && order[at - 1].blocked
|
|
@@ -1208,8 +1444,30 @@ const flushCandidates = (s) => flushLanes(s).flatMap((protocol) => {
|
|
|
1208
1444
|
stored
|
|
1209
1445
|
}));
|
|
1210
1446
|
});
|
|
1447
|
+
const queuedInRecord = (s) => Object.keys(s.record.lanes).some((protocol) => (s.selection ? negotiated(s, protocol) : offered(s, protocol)) && bubble(s, protocol, laneQueue(s, protocol)).free > 0);
|
|
1211
1448
|
const reasonOf = (s, stored, demoted) => stored.released ? "release" : demoted.has(stored.id) ? "reconnect" : s.mountIds.has(stored.id) ? "reload" : "enqueue";
|
|
1212
1449
|
const NO_IDS = /* @__PURE__ */ new Set();
|
|
1450
|
+
const patched = (stored, patch) => {
|
|
1451
|
+
const { held: _held, released: _released, localMeta: _meta, ...rest } = stored;
|
|
1452
|
+
const held = patch.held ?? stored.held === true;
|
|
1453
|
+
const localMeta = patch.localMeta === void 0 ? stored.localMeta : patch.localMeta.value;
|
|
1454
|
+
let next = {
|
|
1455
|
+
...rest,
|
|
1456
|
+
...held && { held: true },
|
|
1457
|
+
...!held && (stored.held || stored.released) && { released: true },
|
|
1458
|
+
...localMeta !== void 0 && { localMeta }
|
|
1459
|
+
};
|
|
1460
|
+
if (patch.params !== void 0) {
|
|
1461
|
+
const { params, meta } = encodeParams(patch.params);
|
|
1462
|
+
const { meta: _old, ...plain } = next;
|
|
1463
|
+
next = {
|
|
1464
|
+
...plain,
|
|
1465
|
+
params,
|
|
1466
|
+
...meta !== void 0 && { meta }
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
return next;
|
|
1470
|
+
};
|
|
1213
1471
|
const flushInner = (s, demoted = NO_IDS) => {
|
|
1214
1472
|
if (s.attach !== "attached" || !s.sessionKnown || !s.running || isFinished(s)) return;
|
|
1215
1473
|
const candidates = flushCandidates(s);
|
|
@@ -1217,11 +1475,11 @@ const flushInner = (s, demoted = NO_IDS) => {
|
|
|
1217
1475
|
const groups = /* @__PURE__ */ new Map();
|
|
1218
1476
|
for (const { protocol, stored } of candidates) {
|
|
1219
1477
|
const reason = reasonOf(s, stored, demoted);
|
|
1220
|
-
groups.set(reason, [...groups.get(reason) ?? [], viewOf(protocol, stored)]);
|
|
1478
|
+
groups.set(reason, [...groups.get(reason) ?? [], viewOf(s, protocol, stored)]);
|
|
1221
1479
|
}
|
|
1222
1480
|
const flushing = {
|
|
1223
1481
|
ids: new Set(candidates.map((c) => c.stored.id)),
|
|
1224
|
-
|
|
1482
|
+
patches: /* @__PURE__ */ new Map(),
|
|
1225
1483
|
removed: /* @__PURE__ */ new Set()
|
|
1226
1484
|
};
|
|
1227
1485
|
s.flushing = flushing;
|
|
@@ -1250,17 +1508,14 @@ const flushInner = (s, demoted = NO_IDS) => {
|
|
|
1250
1508
|
if (own) settleRemoved(s, own);
|
|
1251
1509
|
continue;
|
|
1252
1510
|
}
|
|
1253
|
-
const
|
|
1254
|
-
if (
|
|
1511
|
+
const patch = flushing.patches.get(stored.id);
|
|
1512
|
+
if (patch === void 0) {
|
|
1255
1513
|
queue.push(stored);
|
|
1256
1514
|
continue;
|
|
1257
1515
|
}
|
|
1258
|
-
const
|
|
1259
|
-
queue.push(
|
|
1260
|
-
|
|
1261
|
-
hold
|
|
1262
|
-
});
|
|
1263
|
-
if (own) setHold(s, own, hold);
|
|
1516
|
+
const next = patched(stored, patch);
|
|
1517
|
+
queue.push(next);
|
|
1518
|
+
if (own) follow(s, own, next);
|
|
1264
1519
|
}
|
|
1265
1520
|
const { order, free } = bubble(s, protocol, queue);
|
|
1266
1521
|
let nextSeq = lane.nextSeq;
|
|
@@ -1276,7 +1531,7 @@ const flushInner = (s, demoted = NO_IDS) => {
|
|
|
1276
1531
|
}
|
|
1277
1532
|
const seq = Math.max(nextSeq, durableSeqOf(s, protocol) + 1);
|
|
1278
1533
|
nextSeq = seq + 1;
|
|
1279
|
-
const {
|
|
1534
|
+
const { held: _held, released: _released, ...command } = stored;
|
|
1280
1535
|
const stamped = {
|
|
1281
1536
|
...command,
|
|
1282
1537
|
seq,
|
|
@@ -1313,81 +1568,262 @@ const flush = (s) => {
|
|
|
1313
1568
|
return transact(s, () => flushInner(s));
|
|
1314
1569
|
};
|
|
1315
1570
|
const outboxEntries = (s) => {
|
|
1316
|
-
const local = s.pending.filter((p) =>
|
|
1317
|
-
const parts = [
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1571
|
+
const local = s.pending.filter((p) => isEphemeral(s, p.command.protocol) && !p.away);
|
|
1572
|
+
const parts = [...Object.values(s.record.lanes).flatMap((lane) => [lane.commands, lane.outbox]), ...local.flatMap((p) => [
|
|
1573
|
+
p,
|
|
1574
|
+
p.seq,
|
|
1575
|
+
p.inv.held,
|
|
1576
|
+
p.inv.localMeta
|
|
1577
|
+
])];
|
|
1322
1578
|
if (!(parts.length === s.outboxParts.length && parts.every((part, i) => part === s.outboxParts[i]))) {
|
|
1323
1579
|
s.outboxParts = parts;
|
|
1324
|
-
s.outboxList = [...Object.entries(s.record.lanes).flatMap(([protocol, lane]) => outboxOf(lane).map((c) => viewOf(protocol, c))), ...local.map((p) => viewOf(p.command.protocol, toOutbox(p)))];
|
|
1580
|
+
s.outboxList = [...Object.entries(s.record.lanes).flatMap(([protocol, lane]) => [...[...lane.commands].sort((a, b) => a.seq - b.seq).map((c) => viewOf(s, protocol, c, "sending")), ...outboxOf(lane).map((c) => viewOf(s, protocol, c))]), ...[...local].sort((a, b) => (a.seq ?? Infinity) - (b.seq ?? Infinity)).map((p) => isStamped(p) ? viewOf(s, p.command.protocol, toStored(s, p), "sending") : viewOf(s, p.command.protocol, toOutbox(p)))];
|
|
1325
1581
|
}
|
|
1326
1582
|
return s.outboxList;
|
|
1327
1583
|
};
|
|
1328
|
-
const
|
|
1329
|
-
const
|
|
1584
|
+
const entryOf = (s, id) => {
|
|
1585
|
+
const at = locate(s, id);
|
|
1586
|
+
if (at === void 0) throw new Error(`outbox: no entry ${id}`);
|
|
1587
|
+
if (at.stored === void 0) throw new Error(`outbox: ${id} is in flight`);
|
|
1588
|
+
return {
|
|
1589
|
+
protocol: at.protocol,
|
|
1590
|
+
stored: at.stored
|
|
1591
|
+
};
|
|
1592
|
+
};
|
|
1593
|
+
const applyOutboxOp = (s, id, op) => {
|
|
1594
|
+
const at = locate(s, id);
|
|
1595
|
+
if (at?.stored === void 0) return;
|
|
1596
|
+
const { protocol, stored } = at;
|
|
1330
1597
|
const own = s.pending.find((p) => p.id === id && isOutboxed(p));
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
else {
|
|
1341
|
-
const
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
released: true
|
|
1348
|
-
};
|
|
1349
|
-
setRecord(s, withLane(s.record, protocol, withOutbox(lane, outboxOf(lane).map((c) => c.id === id ? next : c))));
|
|
1350
|
-
}
|
|
1351
|
-
if (own) {
|
|
1352
|
-
if (op.type === "remove") settleRemoved(s, own);
|
|
1353
|
-
else setHold(s, own, op.type === "hold" ? op.hold : void 0);
|
|
1598
|
+
if (op.type === "remove") {
|
|
1599
|
+
if (!isEphemeral(s, protocol)) {
|
|
1600
|
+
const lane = laneOf(s.record, protocol);
|
|
1601
|
+
setRecord(s, withLane(s.record, protocol, {
|
|
1602
|
+
...withOutbox(lane, outboxOf(lane).filter((c) => c.id !== id)),
|
|
1603
|
+
...marks(s, lane, [stored])
|
|
1604
|
+
}));
|
|
1605
|
+
}
|
|
1606
|
+
if (own) settleRemoved(s, own);
|
|
1607
|
+
} else {
|
|
1608
|
+
const next = patched(stored, op.patch(viewOf(s, protocol, stored)));
|
|
1609
|
+
if (!isEphemeral(s, protocol)) {
|
|
1610
|
+
const lane = laneOf(s.record, protocol);
|
|
1611
|
+
setRecord(s, withLane(s.record, protocol, withOutbox(lane, outboxOf(lane).map((c) => c.id === id ? next : c))));
|
|
1612
|
+
}
|
|
1613
|
+
if (own) follow(s, own, next);
|
|
1354
1614
|
}
|
|
1355
1615
|
reorder(s, protocol);
|
|
1356
1616
|
s.notify();
|
|
1357
|
-
return done;
|
|
1358
1617
|
};
|
|
1359
|
-
const
|
|
1618
|
+
const toPatch = (patch) => {
|
|
1619
|
+
if (typeof patch !== "object" || patch === null) throw new Error("outbox: update must return a patch object");
|
|
1620
|
+
if (patch.held !== void 0 && typeof patch.held !== "boolean") throw new Error("outbox: held must be a boolean");
|
|
1621
|
+
if (patch.command !== void 0 && (Object.keys(patch.command).some((k) => k !== "params") || !Array.isArray(patch.command.params))) throw new Error("outbox: a patch may only replace command.params");
|
|
1622
|
+
return {
|
|
1623
|
+
...patch.held !== void 0 && { held: patch.held },
|
|
1624
|
+
..."localMeta" in patch && { localMeta: { value: patch.localMeta } },
|
|
1625
|
+
...patch.command !== void 0 && { params: patch.command.params }
|
|
1626
|
+
};
|
|
1627
|
+
};
|
|
1628
|
+
const outboxOp = (s, id, op) => {
|
|
1629
|
+
entryOf(s, id);
|
|
1360
1630
|
const flushing = s.flushing;
|
|
1361
|
-
if (flushing?.ids.has(
|
|
1362
|
-
if (op.type === "remove") flushing.removed.add(
|
|
1363
|
-
else
|
|
1364
|
-
|
|
1365
|
-
|
|
1631
|
+
if (flushing?.ids.has(id)) {
|
|
1632
|
+
if (op.type === "remove") flushing.removed.add(id);
|
|
1633
|
+
else {
|
|
1634
|
+
const at = entryOf(s, id);
|
|
1635
|
+
const patch = op.patch(viewOf(s, at.protocol, at.stored));
|
|
1636
|
+
flushing.patches.set(id, {
|
|
1637
|
+
...flushing.patches.get(id),
|
|
1638
|
+
...patch
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
return Promise.resolve();
|
|
1366
1642
|
}
|
|
1367
|
-
|
|
1643
|
+
const done = () => {
|
|
1644
|
+
wake(s);
|
|
1645
|
+
pump(s);
|
|
1646
|
+
};
|
|
1647
|
+
if (!s.shared) {
|
|
1648
|
+
applyOutboxOp(s, id, op);
|
|
1649
|
+
done();
|
|
1650
|
+
return Promise.resolve();
|
|
1651
|
+
}
|
|
1652
|
+
return transact(s, () => applyOutboxOp(s, id, op)).then(done);
|
|
1653
|
+
};
|
|
1654
|
+
const background = (promise) => {
|
|
1655
|
+
promise.catch(() => {});
|
|
1656
|
+
};
|
|
1657
|
+
const holdOutbox = (s, id) => background(outboxOp(s, id, {
|
|
1658
|
+
type: "patch",
|
|
1659
|
+
patch: () => ({ held: true })
|
|
1660
|
+
}));
|
|
1661
|
+
const releaseOutbox = (s, id) => {
|
|
1662
|
+
entryOf(s, id);
|
|
1663
|
+
const result = new Promise((resolve, reject) => {
|
|
1664
|
+
const waiters = s.releases.get(id) ?? [];
|
|
1665
|
+
waiters.push({
|
|
1666
|
+
resolve,
|
|
1667
|
+
reject
|
|
1668
|
+
});
|
|
1669
|
+
s.releases.set(id, waiters);
|
|
1670
|
+
});
|
|
1671
|
+
result.catch(() => {});
|
|
1672
|
+
outboxOp(s, id, {
|
|
1673
|
+
type: "patch",
|
|
1674
|
+
patch: () => ({ held: false })
|
|
1675
|
+
}).catch((error) => settleWaiters(s, id, error));
|
|
1676
|
+
return result;
|
|
1677
|
+
};
|
|
1678
|
+
const settleWaiters = (s, id, error) => {
|
|
1679
|
+
const waiters = s.releases.get(id) ?? [];
|
|
1680
|
+
s.releases.delete(id);
|
|
1681
|
+
for (const waiter of waiters) waiter.reject(error);
|
|
1682
|
+
};
|
|
1683
|
+
const removeOutbox = (s, id) => background(outboxOp(s, id, { type: "remove" }));
|
|
1684
|
+
const updateOutbox = (s, id, fn) => background(outboxOp(s, id, {
|
|
1685
|
+
type: "patch",
|
|
1686
|
+
patch: (entry) => toPatch(fn(entry))
|
|
1687
|
+
}));
|
|
1688
|
+
const producedOf = (s) => ({
|
|
1689
|
+
wire: s.options.wireVersion ?? { version: "2026-11-01" },
|
|
1690
|
+
protocols: protocolOffers(s.options) === "*" && s.selection ? s.selection.protocols : protocolOffers(s.options)
|
|
1691
|
+
});
|
|
1692
|
+
const placed = (queue, entry, { after, before }) => {
|
|
1693
|
+
const anchor = after ?? before;
|
|
1694
|
+
if (anchor === void 0) return [...queue, entry];
|
|
1695
|
+
const index = queue.findIndex((c) => c.id === anchor);
|
|
1696
|
+
if (index === -1) return [...queue, entry];
|
|
1697
|
+
const at = after !== void 0 ? index + 1 : index;
|
|
1698
|
+
return [
|
|
1699
|
+
...queue.slice(0, at),
|
|
1700
|
+
entry,
|
|
1701
|
+
...queue.slice(at)
|
|
1702
|
+
];
|
|
1703
|
+
};
|
|
1704
|
+
const insertOutbox = (s, entry) => {
|
|
1705
|
+
if (!s.hydrated) throw new Error("outbox: storage is not hydrated yet");
|
|
1706
|
+
if (s.storageError !== void 0) throw s.storageError;
|
|
1707
|
+
if (entry.after !== void 0 && entry.before !== void 0) throw new Error("outbox: after and before are exclusive");
|
|
1708
|
+
for (const anchor of [entry.after, entry.before]) if (anchor !== void 0) entryOf(s, anchor);
|
|
1709
|
+
const { protocol } = entry;
|
|
1710
|
+
if (!(s.selection ? negotiated(s, protocol) : offered(s, protocol))) throw new Error(`statewire: protocol "${protocol}" is not negotiated`);
|
|
1711
|
+
const { params, meta } = encodeParams(entry.command.params);
|
|
1712
|
+
const id = entry.from === void 0 ? randomId() : `${entry.from}~`;
|
|
1713
|
+
const stored = {
|
|
1714
|
+
id,
|
|
1715
|
+
produced: producedOf(s),
|
|
1716
|
+
method: entry.command.method,
|
|
1717
|
+
params,
|
|
1718
|
+
...meta !== void 0 && { meta },
|
|
1719
|
+
ts: Date.now(),
|
|
1720
|
+
...entry.held && { held: true },
|
|
1721
|
+
...entry.localMeta !== void 0 && { localMeta: entry.localMeta },
|
|
1722
|
+
...s.store.owner !== void 0 && { by: s.store.owner.id }
|
|
1723
|
+
};
|
|
1368
1724
|
const run = () => {
|
|
1369
|
-
|
|
1725
|
+
if (isEphemeral(s, protocol)) {
|
|
1726
|
+
enqueueInner(s, {
|
|
1727
|
+
protocol,
|
|
1728
|
+
produced: stored.produced,
|
|
1729
|
+
method: stored.method,
|
|
1730
|
+
params,
|
|
1731
|
+
...meta !== void 0 && { meta }
|
|
1732
|
+
}, entry.command.params, () => {}, () => {}, {
|
|
1733
|
+
...entry.held && { hold: true },
|
|
1734
|
+
...entry.localMeta !== void 0 && { localMeta: entry.localMeta }
|
|
1735
|
+
});
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1738
|
+
let base = s.record;
|
|
1739
|
+
if (entry.from !== void 0) {
|
|
1740
|
+
const source = Object.entries(base.lanes).find(([, l]) => l.inbox?.some((n) => n.id === entry.from));
|
|
1741
|
+
if (source === void 0 || locate(s, id) !== void 0) return;
|
|
1742
|
+
base = withLane(base, source[0], withoutNotice(source[1], entry.from));
|
|
1743
|
+
}
|
|
1744
|
+
const lane = laneOf(base, protocol);
|
|
1745
|
+
try {
|
|
1746
|
+
setRecord(s, withLane(base, protocol, withOutbox(lane, placed(outboxOf(lane), stored, entry))));
|
|
1747
|
+
} catch (error) {
|
|
1748
|
+
if (!isQuota(error)) throw error;
|
|
1749
|
+
if (entry.from !== void 0) s.skipped.add(entry.from);
|
|
1750
|
+
settleReleases(s, id, {
|
|
1751
|
+
ok: false,
|
|
1752
|
+
failure: QUOTA
|
|
1753
|
+
});
|
|
1754
|
+
return;
|
|
1755
|
+
}
|
|
1756
|
+
s.mountIds.delete(id);
|
|
1757
|
+
reorder(s, protocol);
|
|
1758
|
+
s.notify();
|
|
1370
1759
|
};
|
|
1371
1760
|
const done = () => {
|
|
1372
1761
|
wake(s);
|
|
1373
1762
|
pump(s);
|
|
1374
|
-
return status;
|
|
1375
1763
|
};
|
|
1376
1764
|
if (!s.shared) {
|
|
1377
1765
|
run();
|
|
1378
|
-
|
|
1766
|
+
done();
|
|
1767
|
+
} else background(transact(s, run).then(done));
|
|
1768
|
+
return id;
|
|
1769
|
+
};
|
|
1770
|
+
const nextNotice = (s) => {
|
|
1771
|
+
for (const [protocol, lane] of Object.entries(s.record.lanes)) {
|
|
1772
|
+
const found = (lane.inbox ?? []).find((n) => !s.skipped.has(n.id));
|
|
1773
|
+
if (found) return {
|
|
1774
|
+
protocol,
|
|
1775
|
+
found
|
|
1776
|
+
};
|
|
1379
1777
|
}
|
|
1380
|
-
return transact(s, run).then(done);
|
|
1381
1778
|
};
|
|
1382
|
-
const
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1779
|
+
const withoutNotice = (lane, id) => {
|
|
1780
|
+
const inbox = (lane.inbox ?? []).filter((n) => n.id !== id);
|
|
1781
|
+
const { inbox: _inbox, ...rest } = lane;
|
|
1782
|
+
return inbox.length > 0 ? {
|
|
1783
|
+
...rest,
|
|
1784
|
+
inbox
|
|
1785
|
+
} : rest;
|
|
1786
|
+
};
|
|
1787
|
+
const ack = (s, protocol, id) => {
|
|
1788
|
+
const lane = laneOf(s.record, protocol);
|
|
1789
|
+
if (s.skipped.has(id) || !lane.inbox?.some((n) => n.id === id)) return;
|
|
1790
|
+
setRecord(s, withLane(s.record, protocol, withoutNotice(lane, id)));
|
|
1791
|
+
};
|
|
1792
|
+
const deliver = (s) => {
|
|
1793
|
+
const handler = s.onSettled;
|
|
1794
|
+
if (handler === void 0 || s.delivering || !s.running || !s.hydrated || !s.sessionKnown) return;
|
|
1795
|
+
if (nextNotice(s) === void 0) return;
|
|
1796
|
+
s.delivering = true;
|
|
1797
|
+
const run = async () => {
|
|
1798
|
+
for (;;) {
|
|
1799
|
+
if (s.shared) await transact(s, () => {});
|
|
1800
|
+
else await null;
|
|
1801
|
+
const next = nextNotice(s);
|
|
1802
|
+
if (next === void 0 || !s.running) return;
|
|
1803
|
+
const { protocol, found } = next;
|
|
1804
|
+
try {
|
|
1805
|
+
await handler(updateOf(protocol, found));
|
|
1806
|
+
} catch (error) {
|
|
1807
|
+
console.error("statewire: onCommandChange threw on a settled update; it stays for the next mount", error);
|
|
1808
|
+
s.skipped.add(found.id);
|
|
1809
|
+
continue;
|
|
1810
|
+
}
|
|
1811
|
+
if (s.shared) await transact(s, () => ack(s, protocol, found.id));
|
|
1812
|
+
else ack(s, protocol, found.id);
|
|
1813
|
+
s.notify();
|
|
1814
|
+
}
|
|
1815
|
+
};
|
|
1816
|
+
(s.store.notifyLock ? s.store.notifyLock(run) : run()).then(() => {
|
|
1817
|
+
s.delivering = false;
|
|
1818
|
+
deliver(s);
|
|
1819
|
+
}, (error) => {
|
|
1820
|
+
s.delivering = false;
|
|
1821
|
+
console.error("statewire: settled update delivery stopped", error);
|
|
1822
|
+
});
|
|
1823
|
+
};
|
|
1824
|
+
const enqueue = (s, command, params, resolve, reject, options) => {
|
|
1389
1825
|
if (!s.shared) {
|
|
1390
|
-
enqueueInner(s, command, params, resolve, reject,
|
|
1826
|
+
enqueueInner(s, command, params, resolve, reject, options);
|
|
1391
1827
|
wake(s);
|
|
1392
1828
|
pump(s);
|
|
1393
1829
|
return;
|
|
@@ -1400,7 +1836,7 @@ const enqueue = (s, command, params, resolve, reject, hold) => {
|
|
|
1400
1836
|
}));
|
|
1401
1837
|
return;
|
|
1402
1838
|
}
|
|
1403
|
-
enqueueInner(s, command, params, resolve, reject,
|
|
1839
|
+
enqueueInner(s, command, params, resolve, reject, options);
|
|
1404
1840
|
}).then(() => {
|
|
1405
1841
|
wake(s);
|
|
1406
1842
|
pump(s);
|
|
@@ -1412,6 +1848,7 @@ const offered = (s, protocol) => {
|
|
|
1412
1848
|
};
|
|
1413
1849
|
const sendCommand = (s, method, params, protocol = s.options.protocol ?? "default", options) => {
|
|
1414
1850
|
if (!(s.selection ? s.selection.protocols.some((p) => p.name === protocol) : offered(s, protocol))) throw new Error(`statewire: protocol "${protocol}" is not negotiated`);
|
|
1851
|
+
if (options?.hold !== void 0 && typeof options.hold !== "boolean") throw new Error("statewire: invoke hold must be a boolean");
|
|
1415
1852
|
if (!s.hydrated) {
|
|
1416
1853
|
const result = s.ready.then(() => sendCommand(s, method, params, protocol, options));
|
|
1417
1854
|
result.catch(() => {});
|
|
@@ -1434,93 +1871,14 @@ const sendCommand = (s, method, params, protocol = s.options.protocol ?? "defaul
|
|
|
1434
1871
|
const { params: encoded, meta } = encodeParams(params);
|
|
1435
1872
|
enqueue(s, {
|
|
1436
1873
|
protocol,
|
|
1437
|
-
produced:
|
|
1438
|
-
wire: s.options.wireVersion ?? { version: "2026-11-01" },
|
|
1439
|
-
protocols: protocolOffers(s.options) === "*" && s.selection ? s.selection.protocols : protocolOffers(s.options)
|
|
1440
|
-
},
|
|
1874
|
+
produced: producedOf(s),
|
|
1441
1875
|
method,
|
|
1442
1876
|
params: encoded,
|
|
1443
1877
|
...meta !== void 0 && { meta }
|
|
1444
|
-
}, params, resolve, reject,
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
};
|
|
1449
|
-
const findFailed = (s, entry) => (laneOf(s.record, entry.protocol).failed ?? []).find((f) => f.seq === entry.seq && f.ts === entry.ts && f.command === entry.command);
|
|
1450
|
-
const unknownFailed = (entry) => /* @__PURE__ */ new Error(`statewire: unknown failed command "${entry.command}"`);
|
|
1451
|
-
const takeFailed = (s, entry) => {
|
|
1452
|
-
const lane = laneOf(s.record, entry.protocol);
|
|
1453
|
-
const found = findFailed(s, entry);
|
|
1454
|
-
if (found === void 0) throw unknownFailed(entry);
|
|
1455
|
-
setRecord(s, withLane(s.record, entry.protocol, {
|
|
1456
|
-
...lane,
|
|
1457
|
-
failed: (lane.failed ?? []).filter((f) => f !== found)
|
|
1458
|
-
}));
|
|
1459
|
-
s.notify();
|
|
1460
|
-
return found;
|
|
1461
|
-
};
|
|
1462
|
-
const discardFailed = (s, entry) => {
|
|
1463
|
-
if (!s.shared) {
|
|
1464
|
-
takeFailed(s, entry);
|
|
1465
|
-
return;
|
|
1466
|
-
}
|
|
1467
|
-
if (findFailed(s, entry) === void 0) throw unknownFailed(entry);
|
|
1468
|
-
transactBackground(s, () => {
|
|
1469
|
-
if (findFailed(s, entry) !== void 0) takeFailed(s, entry);
|
|
1470
|
-
});
|
|
1471
|
-
};
|
|
1472
|
-
const retryFailed = (s, entry) => {
|
|
1473
|
-
if (!s.running) return rejectedSend({
|
|
1474
|
-
fate: "aborted",
|
|
1475
|
-
cause: "unmount"
|
|
1476
|
-
});
|
|
1477
|
-
if (isFinished(s)) return rejectedSend({
|
|
1478
|
-
fate: "aborted",
|
|
1479
|
-
cause: "stream-gone"
|
|
1480
|
-
});
|
|
1481
|
-
const found = findFailed(s, entry);
|
|
1482
|
-
if (found === void 0) throw unknownFailed(entry);
|
|
1483
|
-
const params = found.meta === void 0 ? found.params : reviveDocumentValue(structuredClone(found.params), found.meta);
|
|
1484
|
-
const result = new Promise((resolve, reject) => {
|
|
1485
|
-
const retry = () => {
|
|
1486
|
-
if (!s.running) {
|
|
1487
|
-
reject(new StatewireSendError({
|
|
1488
|
-
fate: "aborted",
|
|
1489
|
-
cause: "unmount"
|
|
1490
|
-
}));
|
|
1491
|
-
return;
|
|
1492
|
-
}
|
|
1493
|
-
if (isFinished(s)) {
|
|
1494
|
-
reject(new StatewireSendError({
|
|
1495
|
-
fate: "aborted",
|
|
1496
|
-
cause: "stream-gone"
|
|
1497
|
-
}));
|
|
1498
|
-
return;
|
|
1499
|
-
}
|
|
1500
|
-
const current = findFailed(s, entry);
|
|
1501
|
-
if (current === void 0) {
|
|
1502
|
-
reject(unknownFailed(entry));
|
|
1503
|
-
return;
|
|
1504
|
-
}
|
|
1505
|
-
takeFailed(s, current);
|
|
1506
|
-
enqueueInner(s, {
|
|
1507
|
-
protocol: current.protocol,
|
|
1508
|
-
...current.produced !== void 0 && { produced: current.produced },
|
|
1509
|
-
method: current.command,
|
|
1510
|
-
params: [...current.params],
|
|
1511
|
-
...current.meta !== void 0 && { meta: current.meta }
|
|
1512
|
-
}, params, resolve, reject);
|
|
1513
|
-
};
|
|
1514
|
-
if (!s.shared) {
|
|
1515
|
-
retry();
|
|
1516
|
-
wake(s);
|
|
1517
|
-
pump(s);
|
|
1518
|
-
return;
|
|
1519
|
-
}
|
|
1520
|
-
transact(s, retry).then(() => {
|
|
1521
|
-
wake(s);
|
|
1522
|
-
pump(s);
|
|
1523
|
-
}, reject);
|
|
1878
|
+
}, params, resolve, reject, {
|
|
1879
|
+
...options?.hold && { hold: true },
|
|
1880
|
+
...options?.localMeta !== void 0 && { localMeta: options.localMeta }
|
|
1881
|
+
});
|
|
1524
1882
|
});
|
|
1525
1883
|
result.catch(() => {});
|
|
1526
1884
|
return result;
|
|
@@ -1583,7 +1941,7 @@ const loadSessionRecord = (store, options) => {
|
|
|
1583
1941
|
const record = stored === null ? {
|
|
1584
1942
|
clientId: randomId(),
|
|
1585
1943
|
lanes: {}
|
|
1586
|
-
} :
|
|
1944
|
+
} : boundInbox(store, stored);
|
|
1587
1945
|
if (record !== stored) store.save(record);
|
|
1588
1946
|
return record;
|
|
1589
1947
|
};
|
|
@@ -1601,7 +1959,7 @@ const useStatewireSession = (options) => {
|
|
|
1601
1959
|
const documentStore = useContext(DocumentStorageContext);
|
|
1602
1960
|
if (options.sessionStore !== void 0 && mounted !== void 0) throw new Error("statewire: session storage is set twice — drop the transport's sessionStore or the client's storage.session");
|
|
1603
1961
|
const store = options.sessionStore ?? mounted ?? defaultStore;
|
|
1604
|
-
if (store.lock === void 0 !== (store.subscribe === void 0) || store.lock === void 0 !== (store.owner === void 0)) throw new Error("statewire: session storage must provide lock, subscribe and
|
|
1962
|
+
if (store.lock === void 0 !== (store.subscribe === void 0) || store.lock === void 0 !== (store.owner === void 0) || store.lock === void 0 !== (store.notifyLock === void 0)) throw new Error("statewire: session storage must provide lock, subscribe, owner and notifyLock together");
|
|
1605
1963
|
const [s] = useState(() => {
|
|
1606
1964
|
const record = store.ready === void 0 ? loadSessionRecord(store, options) : {
|
|
1607
1965
|
clientId: randomId(),
|
|
@@ -1640,6 +1998,9 @@ const useStatewireSession = (options) => {
|
|
|
1640
1998
|
txq: Promise.resolve(),
|
|
1641
1999
|
syncQueued: false,
|
|
1642
2000
|
parked: [],
|
|
2001
|
+
settled: /* @__PURE__ */ new Set(),
|
|
2002
|
+
notices: [],
|
|
2003
|
+
inTx: false,
|
|
1643
2004
|
unsubscribe: null,
|
|
1644
2005
|
watched: /* @__PURE__ */ new Set(),
|
|
1645
2006
|
abort: null
|
|
@@ -1652,6 +2013,12 @@ const useStatewireSession = (options) => {
|
|
|
1652
2013
|
resumeAt: 0,
|
|
1653
2014
|
idle: false,
|
|
1654
2015
|
onSessionLost: void 0,
|
|
2016
|
+
onDeprecatedPath: void 0,
|
|
2017
|
+
onSettled: void 0,
|
|
2018
|
+
releases: /* @__PURE__ */ new Map(),
|
|
2019
|
+
delivering: false,
|
|
2020
|
+
skipped: /* @__PURE__ */ new Set(),
|
|
2021
|
+
views: /* @__PURE__ */ new WeakMap(),
|
|
1655
2022
|
onFlush: void 0,
|
|
1656
2023
|
onDone: void 0,
|
|
1657
2024
|
mountIds: storedIds(record),
|
|
@@ -1661,9 +2028,12 @@ const useStatewireSession = (options) => {
|
|
|
1661
2028
|
lanesOnAttach: false,
|
|
1662
2029
|
attachEntries: /* @__PURE__ */ new Set(),
|
|
1663
2030
|
frameFailures: 0,
|
|
2031
|
+
recheck: void 0,
|
|
2032
|
+
rechecks: {
|
|
2033
|
+
watermarks: "",
|
|
2034
|
+
count: 0
|
|
2035
|
+
},
|
|
1664
2036
|
pending: [],
|
|
1665
|
-
failedList: [],
|
|
1666
|
-
failedParts: [],
|
|
1667
2037
|
canonical: void 0,
|
|
1668
2038
|
documentListeners: /* @__PURE__ */ new Set(),
|
|
1669
2039
|
documents: {
|
|
@@ -1724,6 +2094,8 @@ const useStatewireSession = (options) => {
|
|
|
1724
2094
|
s.store = store;
|
|
1725
2095
|
s.documentStore = documentStore;
|
|
1726
2096
|
s.onSessionLost = options.onSessionLost;
|
|
2097
|
+
s.onDeprecatedPath = options.onDeprecatedPath;
|
|
2098
|
+
s.onSettled = options.onSettled;
|
|
1727
2099
|
s.onFlush = options.onFlush;
|
|
1728
2100
|
s.onDone = options.onDone;
|
|
1729
2101
|
const keyPrefixes = JSON.stringify(options.keyPrefixes);
|
|
@@ -1743,6 +2115,6 @@ const useStatewireSession = (options) => {
|
|
|
1743
2115
|
}, [s]);
|
|
1744
2116
|
return s;
|
|
1745
2117
|
};
|
|
1746
|
-
export {
|
|
2118
|
+
export { MAX_INBOX, documentSource, holdOutbox, insertOutbox, invokeProtocol, outboxEntries, releaseOutbox, removeOutbox, setContext, setTransport, updateOutbox, useStatewireSession };
|
|
1747
2119
|
|
|
1748
2120
|
//# sourceMappingURL=session.js.map
|