statewire 0.18.2 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/connection.d.ts +8 -5
- package/dist/client/connection.d.ts.map +1 -1
- package/dist/client/connection.js +24 -13
- package/dist/client/connection.js.map +1 -1
- package/dist/client/session.d.ts +13 -15
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +291 -256
- package/dist/client/session.js.map +1 -1
- package/dist/client/timing.d.ts +2 -1
- package/dist/client/timing.d.ts.map +1 -1
- package/dist/client/timing.js +2 -1
- package/dist/client/timing.js.map +1 -1
- package/dist/host/engine.d.ts +2 -0
- package/dist/host/engine.d.ts.map +1 -1
- package/dist/host/engine.js +2 -0
- package/dist/host/engine.js.map +1 -1
- package/dist/host/host.d.ts +3 -1
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js +38 -7
- package/dist/host/host.js.map +1 -1
- package/dist/host/http.d.ts.map +1 -1
- package/dist/host/http.js +8 -17
- package/dist/host/http.js.map +1 -1
- package/dist/host/socket.d.ts +47 -23
- package/dist/host/socket.d.ts.map +1 -1
- package/dist/host/socket.js +389 -245
- package/dist/host/socket.js.map +1 -1
- package/dist/host/synced.d.ts +9 -0
- package/dist/host/synced.d.ts.map +1 -0
- package/dist/host/synced.js +45 -0
- package/dist/host/synced.js.map +1 -0
- package/dist/host-internal.d.ts +20 -8
- package/dist/host-internal.d.ts.map +1 -1
- package/dist/host.d.ts +33 -9
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +2 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -1
- package/dist/statewire.d.ts +8 -10
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js +0 -14
- package/dist/statewire.js.map +1 -1
- package/dist/storage.d.ts +2 -5
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +55 -41
- package/dist/storage.js.map +1 -1
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/transports/remote.js +1 -6
- package/dist/transports/remote.js.map +1 -1
- package/dist/transports/sse.d.ts +2 -5
- package/dist/transports/sse.d.ts.map +1 -1
- package/dist/transports/sse.js.map +1 -1
- package/dist/transports/testing.d.ts +7 -5
- package/dist/transports/testing.d.ts.map +1 -1
- package/dist/transports/testing.js +19 -19
- package/dist/transports/testing.js.map +1 -1
- package/dist/types.d.ts +3 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/wire/limits.d.ts +2 -2
- package/dist/wire/limits.js +2 -2
- package/dist/wire/limits.js.map +1 -1
- package/dist/wire/protocol.d.ts +6 -1
- package/dist/wire/protocol.d.ts.map +1 -1
- package/dist/wire/protocol.js +33 -12
- package/dist/wire/protocol.js.map +1 -1
- package/package.json +1 -1
- package/src/client/connection.ts +46 -33
- package/src/client/session.ts +434 -365
- package/src/client/timing.ts +2 -0
- package/src/host/engine.ts +3 -1
- package/src/host/host.ts +50 -3
- package/src/host/http.ts +22 -23
- package/src/host/socket.ts +550 -321
- package/src/host/synced.ts +60 -0
- package/src/host-internal.ts +21 -3
- package/src/host.ts +40 -8
- package/src/index.ts +1 -0
- package/src/statewire.ts +8 -30
- package/src/storage.ts +96 -79
- package/src/transports/remote.ts +0 -7
- package/src/transports/sse.ts +8 -3
- package/src/transports/testing.ts +34 -19
- package/src/types.ts +3 -5
- package/src/wire/limits.ts +1 -1
- package/src/wire/protocol.ts +40 -11
package/dist/client/session.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { reviveDocumentValue } from "../document.js";
|
|
2
2
|
import { inRange, protocolOffers } from "../wire/protocol.js";
|
|
3
|
-
import { encodeParams, reviveResponse } from "../wire/meta.js";
|
|
4
|
-
import { applyDocumentOps, findDocument, mainDocument, resolveDocumentKey } from "../wire/documents.js";
|
|
5
3
|
import { createMethodProxy } from "../util/methodProxy.js";
|
|
6
4
|
import { StatewireSendError } from "../types.js";
|
|
7
5
|
import { bindCommandsHost } from "../host/host.js";
|
|
6
|
+
import { encodeParams, reviveResponse } from "../wire/meta.js";
|
|
8
7
|
import "../wire/limits.js";
|
|
8
|
+
import { applyDocumentOps, findDocument, mainDocument, resolveDocumentKey } from "../wire/documents.js";
|
|
9
9
|
import { DocumentStorageContext, SessionStorageContext, createMemorySessionStore } from "./sessionStore.js";
|
|
10
|
-
import { backoff } from "./timing.js";
|
|
10
|
+
import { backoff, retryHint } from "./timing.js";
|
|
11
11
|
import { wrapStorageError } from "./storageError.js";
|
|
12
12
|
import { TERMINAL } from "./connection.js";
|
|
13
13
|
import { randomId } from "../util/randomId.js";
|
|
@@ -21,6 +21,21 @@ const rejectedSend = (failure) => {
|
|
|
21
21
|
result.catch(() => {});
|
|
22
22
|
return result;
|
|
23
23
|
};
|
|
24
|
+
const EMPTY_LANE = {
|
|
25
|
+
nextSeq: 1,
|
|
26
|
+
commands: []
|
|
27
|
+
};
|
|
28
|
+
const laneOf = (record, protocol) => record.lanes[protocol] ?? EMPTY_LANE;
|
|
29
|
+
const withLane = (record, protocol, lane) => ({
|
|
30
|
+
...record,
|
|
31
|
+
lanes: {
|
|
32
|
+
...record.lanes,
|
|
33
|
+
[protocol]: lane
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
const isEphemeral = (s, protocol) => s.selection?.protocols.some((p) => p.name === protocol && p.record === "ephemeral") ?? false;
|
|
37
|
+
const durableSeqOf = (s, protocol) => s.durableSeq[protocol] ?? 0;
|
|
38
|
+
const laneEntries = (s, protocol) => s.pending.filter((p) => p.command.protocol === protocol);
|
|
24
39
|
const publish = (s) => {
|
|
25
40
|
if (s.canonical === s.lastPublished) return;
|
|
26
41
|
s.lastPublished = s.canonical;
|
|
@@ -58,26 +73,43 @@ const setRecord = (s, record) => {
|
|
|
58
73
|
throw s.storageError;
|
|
59
74
|
}
|
|
60
75
|
};
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
const toStored = (p) => {
|
|
77
|
+
const { protocol: _protocol, ...command } = p.command;
|
|
78
|
+
return {
|
|
79
|
+
seq: p.seq,
|
|
80
|
+
...command
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
const backlog = (s, protocol) => laneEntries(s, protocol).filter((p) => p.seq > durableSeqOf(s, protocol)).map(toStored);
|
|
84
|
+
const mergeBacklog = (s, protocol) => {
|
|
85
|
+
const own = backlog(s, protocol);
|
|
67
86
|
if (!s.shared) return own;
|
|
68
|
-
const known = new Set(s.
|
|
69
|
-
return [...s.record.commands.filter((c) => !known.has(c.seq) && c.seq > s
|
|
87
|
+
const known = new Set(laneEntries(s, protocol).map((p) => p.seq));
|
|
88
|
+
return [...laneOf(s.record, protocol).commands.filter((c) => !known.has(c.seq) && c.seq > durableSeqOf(s, protocol)), ...own].sort((a, b) => a.seq - b.seq);
|
|
70
89
|
};
|
|
90
|
+
const durableLanes = (s) => [.../* @__PURE__ */ new Set([...Object.keys(s.record.lanes), ...s.pending.map((p) => p.command.protocol).filter((protocol) => !isEphemeral(s, protocol))])];
|
|
71
91
|
const syncBacklogInner = (s) => {
|
|
72
92
|
const parked = s.shared ? s.shared.parked.splice(0) : [];
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
93
|
+
let lanes = s.record.lanes;
|
|
94
|
+
for (const protocol of durableLanes(s)) {
|
|
95
|
+
const lane = laneOf(s.record, protocol);
|
|
96
|
+
const parkedHere = parked.filter((v) => v.protocol === protocol).map((v) => v.verdict);
|
|
97
|
+
const parkedSeqs = new Set(parkedHere.map((v) => v.seq));
|
|
98
|
+
const commands = mergeBacklog(s, protocol).filter((c) => !parkedSeqs.has(c.seq));
|
|
99
|
+
const verdicts = parkedHere.length > 0 ? [...lane.verdicts ?? [], ...parkedHere].slice(-64) : lane.verdicts;
|
|
100
|
+
if (verdicts === lane.verdicts && commands.length === lane.commands.length && commands.every((c, i) => c.seq === lane.commands[i].seq)) continue;
|
|
101
|
+
lanes = {
|
|
102
|
+
...lanes,
|
|
103
|
+
[protocol]: {
|
|
104
|
+
...lane,
|
|
105
|
+
commands,
|
|
106
|
+
...verdicts !== void 0 && { verdicts }
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (lanes !== s.record.lanes) setRecord(s, {
|
|
78
111
|
...s.record,
|
|
79
|
-
|
|
80
|
-
...verdicts !== void 0 && { verdicts }
|
|
112
|
+
lanes
|
|
81
113
|
});
|
|
82
114
|
};
|
|
83
115
|
const syncBacklog = (s) => {
|
|
@@ -119,12 +151,19 @@ const cover = (s, entry) => {
|
|
|
119
151
|
s.lifecycle?.onApplied(entry.inv);
|
|
120
152
|
s.lifecycle?.onCovered?.(entry.inv);
|
|
121
153
|
};
|
|
122
|
-
const
|
|
123
|
-
const
|
|
154
|
+
const failedCommands = (s) => {
|
|
155
|
+
const parts = Object.values(s.record.lanes).flatMap((lane) => lane.failed === void 0 ? [] : [lane.failed]);
|
|
156
|
+
if (!(parts.length === s.failedParts.length && parts.every((part, i) => part === s.failedParts[i]))) {
|
|
157
|
+
s.failedParts = parts;
|
|
158
|
+
s.failedList = parts.flat();
|
|
159
|
+
}
|
|
160
|
+
return s.failedList;
|
|
161
|
+
};
|
|
124
162
|
const retainFailedInner = (s, entry, failure) => {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
163
|
+
const lane = laneOf(s.record, entry.command.protocol);
|
|
164
|
+
setRecord(s, withLane(s.record, entry.command.protocol, {
|
|
165
|
+
...lane,
|
|
166
|
+
failed: [...lane.failed ?? [], {
|
|
128
167
|
protocol: entry.command.protocol,
|
|
129
168
|
...entry.command.produced !== void 0 && { produced: entry.command.produced },
|
|
130
169
|
command: entry.command.method,
|
|
@@ -134,7 +173,7 @@ const retainFailedInner = (s, entry, failure) => {
|
|
|
134
173
|
ts: entry.inv.ts,
|
|
135
174
|
seq: entry.seq
|
|
136
175
|
}]
|
|
137
|
-
});
|
|
176
|
+
}));
|
|
138
177
|
s.notify();
|
|
139
178
|
};
|
|
140
179
|
const retainFailed = (s, entry, failure) => {
|
|
@@ -153,7 +192,7 @@ const settleEntry = (s, entry, outcome) => {
|
|
|
153
192
|
payload: outcome.payload
|
|
154
193
|
});
|
|
155
194
|
} else {
|
|
156
|
-
if (!((outcome.failure.cause === "unmount" || outcome.failure.cause === "stream-gone") && entry.seq > s.
|
|
195
|
+
if (!((outcome.failure.cause === "unmount" || outcome.failure.cause === "stream-gone") && entry.seq > durableSeqOf(s, entry.command.protocol)) && !isEphemeral(s, entry.command.protocol)) retainFailed(s, entry, outcome.failure);
|
|
157
196
|
entry.reject(new StatewireSendError(outcome.failure));
|
|
158
197
|
s.lifecycle?.onSettle(entry.inv, {
|
|
159
198
|
type: "rejected",
|
|
@@ -166,7 +205,7 @@ const settleAll = (s, detach) => {
|
|
|
166
205
|
const batch = s.pending;
|
|
167
206
|
const detachFailure = (entry) => {
|
|
168
207
|
if (!entry.attempted) {
|
|
169
|
-
const predecessorLost = batch.some((p) => p.seq < entry.seq && p.attempted);
|
|
208
|
+
const predecessorLost = batch.some((p) => p.command.protocol === entry.command.protocol && p.seq < entry.seq && p.attempted);
|
|
170
209
|
return predecessorLost ? {
|
|
171
210
|
fate: "aborted",
|
|
172
211
|
cause,
|
|
@@ -201,30 +240,45 @@ const settleLost = (s, entries) => {
|
|
|
201
240
|
});
|
|
202
241
|
}
|
|
203
242
|
};
|
|
243
|
+
const requeue = (s, survivors) => {
|
|
244
|
+
for (const old of survivors) {
|
|
245
|
+
s.lifecycle?.onCovered?.(old.inv);
|
|
246
|
+
enqueueInner(s, old.command, old.inv.params, old.resolve, old.reject);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
204
249
|
const rebaseRecord = (s, adopt) => {
|
|
205
250
|
const held = [...s.pending];
|
|
206
251
|
const survivors = held.filter((p) => !p.attempted && !p.applied);
|
|
207
252
|
settleLost(s, held.filter((p) => p.attempted || p.applied));
|
|
208
253
|
s.pending = [];
|
|
209
|
-
s.durableSeq =
|
|
210
|
-
const
|
|
254
|
+
s.durableSeq = {};
|
|
255
|
+
const previous = s.record.lanes;
|
|
211
256
|
adopt();
|
|
212
|
-
|
|
257
|
+
let lanes = s.record.lanes;
|
|
258
|
+
for (const [protocol, lane] of Object.entries(previous)) {
|
|
259
|
+
const failed = lane.failed;
|
|
260
|
+
if (failed === void 0 || laneOf(s.record, protocol).failed === failed) continue;
|
|
261
|
+
const current = laneOf(s.record, protocol);
|
|
262
|
+
lanes = {
|
|
263
|
+
...lanes,
|
|
264
|
+
[protocol]: {
|
|
265
|
+
...current,
|
|
266
|
+
failed: [...current.failed ?? [], ...failed]
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (lanes !== s.record.lanes) setRecord(s, {
|
|
213
271
|
...s.record,
|
|
214
|
-
|
|
272
|
+
lanes
|
|
215
273
|
});
|
|
216
|
-
|
|
217
|
-
s.lifecycle?.onCovered?.(old.inv);
|
|
218
|
-
enqueueInner(s, old.command, old.inv.params, old.resolve, old.reject);
|
|
219
|
-
}
|
|
274
|
+
requeue(s, survivors);
|
|
220
275
|
publish(s);
|
|
221
276
|
wake(s);
|
|
222
277
|
pump(s);
|
|
223
278
|
};
|
|
224
279
|
const resetRecord = (s, clientId) => rebaseRecord(s, () => setRecord(s, {
|
|
225
280
|
clientId,
|
|
226
|
-
|
|
227
|
-
commands: []
|
|
281
|
+
lanes: {}
|
|
228
282
|
}));
|
|
229
283
|
const adoptReset = (s, stored) => rebaseRecord(s, () => {
|
|
230
284
|
s.record = stored;
|
|
@@ -234,6 +288,14 @@ const ephemeralDetach = (s) => {
|
|
|
234
288
|
s.sessionKnown = false;
|
|
235
289
|
resetRecord(s, randomId());
|
|
236
290
|
};
|
|
291
|
+
const detachEphemeralLanes = (s) => {
|
|
292
|
+
const held = s.pending.filter((p) => isEphemeral(s, p.command.protocol));
|
|
293
|
+
if (held.length === 0) return;
|
|
294
|
+
s.pending = s.pending.filter((p) => !held.includes(p));
|
|
295
|
+
settleLost(s, held.filter((p) => p.attempted));
|
|
296
|
+
requeue(s, held.filter((p) => !p.attempted));
|
|
297
|
+
publish(s);
|
|
298
|
+
};
|
|
237
299
|
const rejectedFailure = (cmd) => {
|
|
238
300
|
return {
|
|
239
301
|
fate: "rejected",
|
|
@@ -278,50 +340,72 @@ const outcomeOf = (cmd) => {
|
|
|
278
340
|
default: return protocolError(`unknown cmd type: ${String(cmd.type)}`);
|
|
279
341
|
}
|
|
280
342
|
};
|
|
343
|
+
const settleVerdict = (s, entry, cmd) => {
|
|
344
|
+
const outcome = outcomeOf(cmd);
|
|
345
|
+
if (outcome.ok) cover(s, entry);
|
|
346
|
+
else s.lifecycle?.onCovered?.(entry.inv);
|
|
347
|
+
settleEntry(s, entry, outcome);
|
|
348
|
+
};
|
|
281
349
|
const TERMINAL_CMD_TYPES = new Set(TERMINAL);
|
|
282
350
|
const parkVerdict = (s, cmd) => {
|
|
283
351
|
if (!s.shared || !TERMINAL_CMD_TYPES.has(cmd.type)) return;
|
|
352
|
+
if (isEphemeral(s, cmd.protocol)) return;
|
|
284
353
|
s.shared.parked.push({
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
354
|
+
protocol: cmd.protocol,
|
|
355
|
+
verdict: {
|
|
356
|
+
seq: cmd.seq,
|
|
357
|
+
type: cmd.type,
|
|
358
|
+
...cmd.code !== void 0 && { code: cmd.code },
|
|
359
|
+
...cmd.message !== void 0 && { message: cmd.message },
|
|
360
|
+
...cmd.payload !== void 0 && { payload: cmd.payload },
|
|
361
|
+
...cmd.meta !== void 0 && { meta: cmd.meta }
|
|
362
|
+
}
|
|
291
363
|
});
|
|
292
364
|
};
|
|
293
365
|
const handleCmd = (s, cmd) => {
|
|
294
|
-
if (
|
|
295
|
-
|
|
296
|
-
s.durableSeq =
|
|
297
|
-
for (const entry of s.
|
|
366
|
+
if (cmd.dur === true && !isEphemeral(s, cmd.protocol)) {
|
|
367
|
+
const watermark = Math.max(durableSeqOf(s, cmd.protocol), cmd.seq);
|
|
368
|
+
s.durableSeq[cmd.protocol] = watermark;
|
|
369
|
+
for (const entry of laneEntries(s, cmd.protocol)) if (entry.seq <= watermark) entry.posted = true;
|
|
298
370
|
}
|
|
299
371
|
if (cmd.type === void 0) return;
|
|
300
|
-
const entry = s.pending.find((p) => p.seq === cmd.seq);
|
|
372
|
+
const entry = s.pending.find((p) => p.command.protocol === cmd.protocol && p.seq === cmd.seq);
|
|
301
373
|
if (cmd.type === "applied") {
|
|
302
374
|
if (entry) cover(s, entry);
|
|
303
375
|
return;
|
|
304
376
|
}
|
|
305
377
|
if (!entry) return;
|
|
306
378
|
parkVerdict(s, cmd);
|
|
307
|
-
|
|
308
|
-
settleEntry(s, entry, outcomeOf(cmd));
|
|
379
|
+
settleVerdict(s, entry, cmd);
|
|
309
380
|
};
|
|
310
381
|
const consumeVerdicts = (s) => {
|
|
311
|
-
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
382
|
+
let lanes = s.record.lanes;
|
|
383
|
+
for (const [protocol, lane] of Object.entries(s.record.lanes)) {
|
|
384
|
+
const verdicts = lane.verdicts ?? [];
|
|
385
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
386
|
+
for (const verdict of verdicts) {
|
|
387
|
+
const entry = laneEntries(s, protocol).find((p) => p.seq === verdict.seq);
|
|
388
|
+
if (!entry) continue;
|
|
389
|
+
consumed.add(verdict.seq);
|
|
390
|
+
settleVerdict(s, entry, {
|
|
391
|
+
...verdict,
|
|
392
|
+
protocol
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
if (consumed.size === 0) continue;
|
|
396
|
+
lanes = {
|
|
397
|
+
...lanes,
|
|
398
|
+
[protocol]: {
|
|
399
|
+
...lane,
|
|
400
|
+
commands: lane.commands.filter((c) => !consumed.has(c.seq)),
|
|
401
|
+
verdicts: verdicts.filter((v) => !consumed.has(v.seq))
|
|
402
|
+
}
|
|
403
|
+
};
|
|
319
404
|
}
|
|
320
|
-
if (
|
|
405
|
+
if (lanes === s.record.lanes) return;
|
|
321
406
|
setRecord(s, {
|
|
322
407
|
...s.record,
|
|
323
|
-
|
|
324
|
-
verdicts: verdicts.filter((v) => !consumed.has(v.seq))
|
|
408
|
+
lanes
|
|
325
409
|
});
|
|
326
410
|
publish(s);
|
|
327
411
|
wake(s);
|
|
@@ -359,28 +443,36 @@ const publishIngested = (s) => {
|
|
|
359
443
|
for (const listener of s.documentListeners) listener();
|
|
360
444
|
wake(s);
|
|
361
445
|
};
|
|
362
|
-
const sessionLostInner = (s, seqWm) => {
|
|
363
|
-
const held =
|
|
446
|
+
const sessionLostInner = (s, protocol, seqWm) => {
|
|
447
|
+
const held = laneEntries(s, protocol);
|
|
364
448
|
settleLost(s, held);
|
|
365
|
-
s.
|
|
366
|
-
s.
|
|
367
|
-
setRecord(s, {
|
|
368
|
-
clientId: s.record.clientId,
|
|
449
|
+
s.durableSeq[protocol] = seqWm;
|
|
450
|
+
const failed = laneOf(s.record, protocol).failed;
|
|
451
|
+
setRecord(s, withLane(s.record, protocol, {
|
|
369
452
|
nextSeq: seqWm + 1,
|
|
370
453
|
commands: [],
|
|
371
|
-
...
|
|
372
|
-
});
|
|
454
|
+
...failed !== void 0 && { failed }
|
|
455
|
+
}));
|
|
373
456
|
if (held.length > 0 && s.onSessionLost?.(held.map((p) => p.inv)) === "requeue") for (const old of held) enqueueInner(s, old.command, old.inv.params, () => {}, () => {});
|
|
374
457
|
publish(s);
|
|
375
458
|
wake(s);
|
|
376
459
|
};
|
|
377
|
-
const
|
|
378
|
-
if (syn.
|
|
379
|
-
|
|
380
|
-
s.selection = syn.version === void 0 || syn.protocols === void 0 ? void 0 : {
|
|
460
|
+
const selectionOf = (syn) => {
|
|
461
|
+
if (syn.version === void 0 || syn.protocols === void 0) return void 0;
|
|
462
|
+
return {
|
|
381
463
|
version: syn.version,
|
|
382
|
-
protocols: syn.protocols
|
|
464
|
+
protocols: syn.protocols.map(({ name, version, record }) => {
|
|
465
|
+
if (version === void 0) return protocolError(`hello lane ${name} has no version`);
|
|
466
|
+
return {
|
|
467
|
+
name,
|
|
468
|
+
version,
|
|
469
|
+
...record !== void 0 && { record }
|
|
470
|
+
};
|
|
471
|
+
})
|
|
383
472
|
};
|
|
473
|
+
};
|
|
474
|
+
const handleHello = (s, { ops, syn }) => {
|
|
475
|
+
s.selection = selectionOf(syn);
|
|
384
476
|
s.documents = {
|
|
385
477
|
mode: null,
|
|
386
478
|
documents: /* @__PURE__ */ new Map()
|
|
@@ -390,15 +482,16 @@ const handleHello = (s, { ops, syn }) => {
|
|
|
390
482
|
s.attachEpoch += 1;
|
|
391
483
|
s.frameFailures = 0;
|
|
392
484
|
s.resumeAt = 0;
|
|
393
|
-
|
|
485
|
+
const lanes = syn.protocols ?? [];
|
|
486
|
+
s.ephemeral = lanes.length > 0 && lanes.every((lane) => lane.record === "ephemeral");
|
|
394
487
|
if (s.ephemeral) s.deactivated = true;
|
|
395
|
-
const
|
|
396
|
-
s.
|
|
488
|
+
const attached = s.attachEntries;
|
|
489
|
+
s.attachEntries = /* @__PURE__ */ new Set();
|
|
397
490
|
const reconcile = () => {
|
|
398
491
|
if (s.shared) adoptStored(s);
|
|
399
|
-
s.
|
|
400
|
-
|
|
401
|
-
for (const
|
|
492
|
+
for (const lane of lanes) if (lane.record === "ephemeral") s.ephemeralSeqs[lane.name] = Math.max(s.ephemeralSeqs[lane.name] ?? 0, ...laneEntries(s, lane.name).map((p) => p.seq));
|
|
493
|
+
else s.durableSeq[lane.name] = lane.seq ?? 0;
|
|
494
|
+
for (const entry of s.pending) entry.posted = attached.has(entry);
|
|
402
495
|
syncBacklogInner(s);
|
|
403
496
|
s.sessionKnown = true;
|
|
404
497
|
};
|
|
@@ -420,26 +513,26 @@ const handleOps = (s, { ops, cmds }) => {
|
|
|
420
513
|
publishIngested(s);
|
|
421
514
|
};
|
|
422
515
|
const handleRefusal = (s, syn) => {
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
const watermark = Math.max(seq, s
|
|
426
|
-
for (const entry of s
|
|
427
|
-
}
|
|
428
|
-
if (typeof ctxSeq === "number") {
|
|
429
|
-
s.ctxPending = s.ctxPending.filter((c) => c.seq > ctxSeq);
|
|
430
|
-
for (const c of s.ctxPending) c.posted = false;
|
|
431
|
-
const firstCtx = s.ctxPending.find((c) => !c.posted);
|
|
432
|
-
if (firstCtx !== void 0 && firstCtx.seq > ctxSeq + 1) protocolError(`ctx gap on a live attach: seq ${firstCtx.seq} after watermark ${ctxSeq}`);
|
|
516
|
+
const lanes = (syn.protocols ?? []).filter((lane) => lane.seq !== void 0);
|
|
517
|
+
for (const { name, seq } of lanes) {
|
|
518
|
+
const watermark = isEphemeral(s, name) ? seq : Math.max(seq, durableSeqOf(s, name));
|
|
519
|
+
for (const entry of laneEntries(s, name)) entry.posted = entry.seq <= watermark;
|
|
433
520
|
}
|
|
434
521
|
if (typeof syn.retry === "number") {
|
|
435
|
-
s.resumeAt = Date.now() + syn.retry;
|
|
522
|
+
s.resumeAt = Date.now() + retryHint(syn.retry);
|
|
436
523
|
wake(s);
|
|
437
524
|
pump(s);
|
|
438
525
|
return;
|
|
439
526
|
}
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
527
|
+
const gaps = lanes.filter(({ name, seq }) => {
|
|
528
|
+
const first = laneEntries(s, name).find((p) => !p.posted);
|
|
529
|
+
return first !== void 0 && first.seq > Math.max(seq, durableSeqOf(s, name)) + 1;
|
|
530
|
+
});
|
|
531
|
+
for (const { name, seq } of gaps) if (isEphemeral(s, name)) protocolError(`gap on ephemeral lane ${name} after watermark ${seq}`);
|
|
532
|
+
if (gaps.length > 0) {
|
|
533
|
+
transactBackground(s, () => {
|
|
534
|
+
for (const { name, seq } of gaps) sessionLostInner(s, name, seq);
|
|
535
|
+
}, () => {
|
|
443
536
|
wake(s);
|
|
444
537
|
pump(s);
|
|
445
538
|
});
|
|
@@ -450,7 +543,9 @@ const handleRefusal = (s, syn) => {
|
|
|
450
543
|
};
|
|
451
544
|
const handleDetach = (s) => {
|
|
452
545
|
if (s.attach === "attached") s.attach = "detached";
|
|
546
|
+
s.ephemeralSeqs = {};
|
|
453
547
|
if (s.ephemeral) ephemeralDetach(s);
|
|
548
|
+
else detachEphemeralLanes(s);
|
|
454
549
|
wake(s);
|
|
455
550
|
};
|
|
456
551
|
const handleFinish = (s, { reason }) => {
|
|
@@ -462,59 +557,40 @@ const handleFinish = (s, { reason }) => {
|
|
|
462
557
|
};
|
|
463
558
|
const isFinished = (s) => s.attach === "finished";
|
|
464
559
|
const buildLanes = (s, cmdGate = "all") => {
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
if (ctxEntries.length >= 256) break;
|
|
469
|
-
if (ctxEntries.length > 0 && entry.seq !== ctxEntries.at(-1).seq + 1) break;
|
|
470
|
-
ctxEntries.push(entry);
|
|
471
|
-
}
|
|
472
|
-
const cmdEntries = [];
|
|
560
|
+
const entries = [];
|
|
561
|
+
const last = {};
|
|
562
|
+
const stopped = /* @__PURE__ */ new Set();
|
|
473
563
|
for (const entry of s.pending) {
|
|
474
|
-
if (
|
|
475
|
-
|
|
476
|
-
if (
|
|
477
|
-
if (
|
|
478
|
-
|
|
479
|
-
|
|
564
|
+
if (entries.length >= 256) break;
|
|
565
|
+
const protocol = entry.command.protocol;
|
|
566
|
+
if (entry.posted || stopped.has(protocol)) continue;
|
|
567
|
+
if (!isEphemeral(s, protocol) && (cmdGate === "none" || cmdGate === "persisted" && (s.persistedRecord?.clientId !== s.record.clientId || entry.seq >= laneOf(s.persistedRecord, protocol).nextSeq)) || protocol in last && entry.seq !== last[protocol] + 1) {
|
|
568
|
+
stopped.add(protocol);
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
last[protocol] = entry.seq;
|
|
572
|
+
entries.push(entry);
|
|
480
573
|
}
|
|
481
|
-
if (
|
|
574
|
+
if (entries.length === 0) return null;
|
|
482
575
|
return {
|
|
483
|
-
frame: {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
...command,
|
|
492
|
-
seq: p.seq
|
|
493
|
-
};
|
|
494
|
-
}) }
|
|
495
|
-
},
|
|
496
|
-
cmdEntries,
|
|
497
|
-
ctxEntries
|
|
576
|
+
frame: { cmd: entries.map((p) => {
|
|
577
|
+
const { produced: _produced, ...command } = p.command;
|
|
578
|
+
return {
|
|
579
|
+
...command,
|
|
580
|
+
seq: p.seq
|
|
581
|
+
};
|
|
582
|
+
}) },
|
|
583
|
+
entries
|
|
498
584
|
};
|
|
499
585
|
};
|
|
500
|
-
const resetContextLane = (s) => {
|
|
501
|
-
const ops = contextSnapshot(s.contextDocuments);
|
|
502
|
-
s.ctxPending = ops.length === 0 ? [] : [{
|
|
503
|
-
seq: 1,
|
|
504
|
-
ops,
|
|
505
|
-
posted: false
|
|
506
|
-
}];
|
|
507
|
-
s.ctxNextSeq = s.ctxPending.length + 1;
|
|
508
|
-
};
|
|
509
586
|
const takeAttachLanes = (s) => {
|
|
510
|
-
s.
|
|
511
|
-
resetContextLane(s);
|
|
587
|
+
s.attachEntries = /* @__PURE__ */ new Set();
|
|
512
588
|
const wanted = s.lanesOnAttach;
|
|
513
589
|
s.lanesOnAttach = false;
|
|
514
590
|
const lanes = buildLanes(s, wanted && s.sessionKnown ? "persisted" : "none");
|
|
515
591
|
if (!lanes) return null;
|
|
516
|
-
s.
|
|
517
|
-
for (const entry of lanes.
|
|
592
|
+
s.attachEntries = new Set(lanes.entries);
|
|
593
|
+
for (const entry of lanes.entries) entry.attempted = true;
|
|
518
594
|
return lanes.frame;
|
|
519
595
|
};
|
|
520
596
|
const persistBeforeSend = (s) => {
|
|
@@ -558,7 +634,7 @@ const pump = async (s) => {
|
|
|
558
634
|
if (persistence) await persistence;
|
|
559
635
|
if (!s.running || isFinished(s)) return;
|
|
560
636
|
if (epoch !== s.attachEpoch || s.attach !== "attached") continue;
|
|
561
|
-
for (const entry of lanes.
|
|
637
|
+
for (const entry of lanes.entries) entry.attempted = true;
|
|
562
638
|
let result;
|
|
563
639
|
try {
|
|
564
640
|
const send = () => transport.send(lanes.frame);
|
|
@@ -573,11 +649,10 @@ const pump = async (s) => {
|
|
|
573
649
|
s.frameFailures = 0;
|
|
574
650
|
if (result.type === "ok") {
|
|
575
651
|
if (epoch === s.attachEpoch) {
|
|
576
|
-
for (const entry of lanes.
|
|
652
|
+
for (const entry of lanes.entries) if (s.pending.includes(entry)) {
|
|
577
653
|
entry.posted = true;
|
|
578
654
|
s.lifecycle?.onPosted?.(entry.inv);
|
|
579
655
|
}
|
|
580
|
-
for (const entry of lanes.ctxEntries) entry.posted = true;
|
|
581
656
|
}
|
|
582
657
|
} else if (result.type === "refusal") {
|
|
583
658
|
if (epoch === s.attachEpoch) handleRefusal(s, result.syn);
|
|
@@ -644,12 +719,14 @@ const setTransport = (s, transport) => {
|
|
|
644
719
|
wake(s);
|
|
645
720
|
};
|
|
646
721
|
const enqueueInner = (s, command, params, resolve, reject) => {
|
|
647
|
-
const
|
|
722
|
+
const protocol = command.protocol;
|
|
723
|
+
const ephemeral = isEphemeral(s, protocol);
|
|
724
|
+
const seq = ephemeral ? (s.ephemeralSeqs[protocol] ?? 0) + 1 : Math.max(laneOf(s.record, protocol).nextSeq, durableSeqOf(s, protocol) + 1);
|
|
648
725
|
const entry = {
|
|
649
726
|
seq,
|
|
650
727
|
command,
|
|
651
728
|
inv: {
|
|
652
|
-
protocol
|
|
729
|
+
protocol,
|
|
653
730
|
method: command.method,
|
|
654
731
|
params,
|
|
655
732
|
ts: Date.now(),
|
|
@@ -663,11 +740,12 @@ const enqueueInner = (s, command, params, resolve, reject) => {
|
|
|
663
740
|
reject
|
|
664
741
|
};
|
|
665
742
|
s.pending.push(entry);
|
|
666
|
-
|
|
667
|
-
|
|
743
|
+
if (ephemeral) s.ephemeralSeqs[protocol] = seq;
|
|
744
|
+
else setRecord(s, withLane(s.record, protocol, {
|
|
745
|
+
...laneOf(s.record, protocol),
|
|
668
746
|
nextSeq: seq + 1,
|
|
669
|
-
commands: mergeBacklog(s)
|
|
670
|
-
});
|
|
747
|
+
commands: mergeBacklog(s, protocol)
|
|
748
|
+
}));
|
|
671
749
|
s.lifecycle?.onStart(entry.inv);
|
|
672
750
|
};
|
|
673
751
|
const enqueue = (s, command, params, resolve, reject) => {
|
|
@@ -698,37 +776,47 @@ const enqueue = (s, command, params, resolve, reject) => {
|
|
|
698
776
|
pump(s);
|
|
699
777
|
}, reject);
|
|
700
778
|
};
|
|
779
|
+
const offered = (s, protocol) => {
|
|
780
|
+
const offers = protocolOffers(s.options);
|
|
781
|
+
return offers === "*" || offers.some((offer) => offer.name === protocol);
|
|
782
|
+
};
|
|
701
783
|
const adoptStored = (s) => {
|
|
702
|
-
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
seq,
|
|
711
|
-
command,
|
|
712
|
-
inv: {
|
|
713
|
-
protocol: command.protocol,
|
|
714
|
-
method: command.method,
|
|
715
|
-
params,
|
|
716
|
-
ts: Date.now(),
|
|
784
|
+
let adopted = false;
|
|
785
|
+
for (const [protocol, lane] of Object.entries(s.record.lanes)) {
|
|
786
|
+
if (!offered(s, protocol)) continue;
|
|
787
|
+
const known = new Set(laneEntries(s, protocol).map((p) => p.seq));
|
|
788
|
+
for (const { seq, ...command } of lane.commands) {
|
|
789
|
+
if (known.has(seq)) continue;
|
|
790
|
+
const params = command.meta === void 0 ? command.params : reviveDocumentValue([...command.params], command.meta);
|
|
791
|
+
const entry = {
|
|
717
792
|
seq,
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
793
|
+
command: {
|
|
794
|
+
protocol,
|
|
795
|
+
...command
|
|
796
|
+
},
|
|
797
|
+
inv: {
|
|
798
|
+
protocol,
|
|
799
|
+
method: command.method,
|
|
800
|
+
params,
|
|
801
|
+
ts: Date.now(),
|
|
802
|
+
seq,
|
|
803
|
+
clientId: s.record.clientId
|
|
804
|
+
},
|
|
805
|
+
attempted: true,
|
|
806
|
+
posted: false,
|
|
807
|
+
applied: false,
|
|
808
|
+
resolve: () => {},
|
|
809
|
+
reject: () => {}
|
|
810
|
+
};
|
|
811
|
+
s.pending.push(entry);
|
|
812
|
+
s.lifecycle?.onStart(entry.inv);
|
|
813
|
+
adopted = true;
|
|
814
|
+
}
|
|
728
815
|
}
|
|
729
|
-
s.pending.sort((a, b) => a.seq - b.seq);
|
|
816
|
+
if (adopted) s.pending.sort((a, b) => a.seq - b.seq);
|
|
730
817
|
};
|
|
731
818
|
const sendCommand = (s, method, params, protocol = s.options.protocol ?? "default") => {
|
|
819
|
+
if (!(s.selection ? s.selection.protocols.some((p) => p.name === protocol) : offered(s, protocol))) throw new Error(`statewire: protocol "${protocol}" is not negotiated`);
|
|
732
820
|
if (!s.hydrated) {
|
|
733
821
|
const result = s.ready.then(() => sendCommand(s, method, params, protocol));
|
|
734
822
|
result.catch(() => {});
|
|
@@ -752,7 +840,7 @@ const sendCommand = (s, method, params, protocol = s.options.protocol ?? "defaul
|
|
|
752
840
|
enqueue(s, {
|
|
753
841
|
protocol,
|
|
754
842
|
produced: {
|
|
755
|
-
wire: s.options.wireVersion ?? { version: "2026-09-
|
|
843
|
+
wire: s.options.wireVersion ?? { version: "2026-09-13" },
|
|
756
844
|
protocols: protocolOffers(s.options) === "*" && s.selection ? s.selection.protocols : protocolOffers(s.options)
|
|
757
845
|
},
|
|
758
846
|
method,
|
|
@@ -763,16 +851,16 @@ const sendCommand = (s, method, params, protocol = s.options.protocol ?? "defaul
|
|
|
763
851
|
result.catch(() => {});
|
|
764
852
|
return result;
|
|
765
853
|
};
|
|
766
|
-
const findFailed = (s, entry) => (s.record.failed ?? []).find((f) => f.seq === entry.seq && f.ts === entry.ts && f.command === entry.command);
|
|
854
|
+
const findFailed = (s, entry) => (laneOf(s.record, entry.protocol).failed ?? []).find((f) => f.seq === entry.seq && f.ts === entry.ts && f.command === entry.command);
|
|
767
855
|
const unknownFailed = (entry) => /* @__PURE__ */ new Error(`statewire: unknown failed command "${entry.command}"`);
|
|
768
856
|
const takeFailed = (s, entry) => {
|
|
769
|
-
const
|
|
857
|
+
const lane = laneOf(s.record, entry.protocol);
|
|
770
858
|
const found = findFailed(s, entry);
|
|
771
859
|
if (found === void 0) throw unknownFailed(entry);
|
|
772
|
-
setRecord(s, {
|
|
773
|
-
...
|
|
774
|
-
failed: failed.filter((f) => f !== found)
|
|
775
|
-
});
|
|
860
|
+
setRecord(s, withLane(s.record, entry.protocol, {
|
|
861
|
+
...lane,
|
|
862
|
+
failed: (lane.failed ?? []).filter((f) => f !== found)
|
|
863
|
+
}));
|
|
776
864
|
s.notify();
|
|
777
865
|
return found;
|
|
778
866
|
};
|
|
@@ -842,60 +930,6 @@ const retryFailed = (s, entry) => {
|
|
|
842
930
|
result.catch(() => {});
|
|
843
931
|
return result;
|
|
844
932
|
};
|
|
845
|
-
const contextSnapshot = (documents) => [...documents.documents].map(([doc, d]) => {
|
|
846
|
-
const encoded = encodeDocument(d.value);
|
|
847
|
-
return {
|
|
848
|
-
op: "mount",
|
|
849
|
-
...doc === 0 ? {} : { doc },
|
|
850
|
-
protocol: d.protocol,
|
|
851
|
-
...d.main ? { main: true } : {},
|
|
852
|
-
...d.key === null ? {} : { key: d.key },
|
|
853
|
-
value: encoded.value,
|
|
854
|
-
...encoded.meta === void 0 ? {} : { meta: encoded.meta }
|
|
855
|
-
};
|
|
856
|
-
});
|
|
857
|
-
const changeContext = (s, build) => {
|
|
858
|
-
if (!s.running || isFinished(s)) return;
|
|
859
|
-
const operations = build(s.contextDocuments);
|
|
860
|
-
if (!operations.length) throw new Error("statewire: context operations must not be empty");
|
|
861
|
-
const offers = protocolOffers(s.options);
|
|
862
|
-
const protocols = s.selection?.protocols ?? (offers === "*" ? void 0 : offers);
|
|
863
|
-
s.contextDocuments = applyDocumentOps(s.contextDocuments, operations, protocols, true);
|
|
864
|
-
s.ctxPending = [...s.ctxPending, {
|
|
865
|
-
seq: s.ctxNextSeq++,
|
|
866
|
-
ops: operations.map((op) => {
|
|
867
|
-
const value = op;
|
|
868
|
-
if (value["doc"] !== 0) return value;
|
|
869
|
-
const { doc: _doc, ...rest } = value;
|
|
870
|
-
return rest;
|
|
871
|
-
}),
|
|
872
|
-
posted: false
|
|
873
|
-
}];
|
|
874
|
-
wake(s);
|
|
875
|
-
pump(s);
|
|
876
|
-
};
|
|
877
|
-
const updateContext = (s, operations) => changeContext(s, () => operations);
|
|
878
|
-
const setContext = (s, value, protocol = s.options.protocol ?? "default") => {
|
|
879
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("statewire: context must be a plain object");
|
|
880
|
-
const encoded = encodeDocument(value);
|
|
881
|
-
changeContext(s, (documents) => {
|
|
882
|
-
const found = [...documents.documents].find(([, d]) => d.protocol === protocol && d.main);
|
|
883
|
-
const doc = found?.[0] ?? Math.max(-1, ...documents.documents.keys()) + 1;
|
|
884
|
-
return [{
|
|
885
|
-
...found === void 0 ? {
|
|
886
|
-
op: "mount",
|
|
887
|
-
protocol,
|
|
888
|
-
main: true
|
|
889
|
-
} : {
|
|
890
|
-
op: "replace",
|
|
891
|
-
path: []
|
|
892
|
-
},
|
|
893
|
-
...doc === 0 ? {} : { doc },
|
|
894
|
-
value: encoded.value,
|
|
895
|
-
...encoded.meta === void 0 ? {} : { meta: encoded.meta }
|
|
896
|
-
}];
|
|
897
|
-
});
|
|
898
|
-
};
|
|
899
933
|
const documentSource = (s) => ({
|
|
900
934
|
get: (protocol, key, prefix = s.options.keyPrefixes?.[protocol]) => {
|
|
901
935
|
const mounted = findDocument(s.documents, protocol, key, prefix);
|
|
@@ -922,33 +956,38 @@ const documentSource = (s) => ({
|
|
|
922
956
|
}
|
|
923
957
|
});
|
|
924
958
|
const invokeProtocol = (s, protocol, method, params) => sendCommand(s, method, params, protocol);
|
|
925
|
-
const storedCommandCompatible = (command, current) => {
|
|
926
|
-
if (!command.produced || !
|
|
959
|
+
const storedCommandCompatible = (protocol, command, current) => {
|
|
960
|
+
if (!command.produced || !inRange(command.produced.wire.version, current.wire)) return false;
|
|
927
961
|
const previous = command.produced.protocols;
|
|
928
962
|
if (current.protocols === "*") return true;
|
|
929
963
|
if (previous === "*") return false;
|
|
930
|
-
const produced = previous.find((p) => p.name ===
|
|
931
|
-
const offer = current.protocols.find((p) => p.name ===
|
|
964
|
+
const produced = previous.find((p) => p.name === protocol);
|
|
965
|
+
const offer = current.protocols.find((p) => p.name === protocol);
|
|
932
966
|
return produced !== void 0 && offer !== void 0 && inRange(produced.version, offer);
|
|
933
967
|
};
|
|
968
|
+
const validateLanes = (record) => {
|
|
969
|
+
if (typeof record.lanes !== "object" || record.lanes === null || Object.values(record.lanes).some((lane) => !Array.isArray(lane.commands))) throw new Error("statewire: session storage loaded a record without command lanes");
|
|
970
|
+
};
|
|
934
971
|
const loadSessionRecord = (store, options) => {
|
|
935
972
|
let stored = store.load();
|
|
936
|
-
if (stored !== null
|
|
973
|
+
if (stored !== null) validateLanes(stored);
|
|
937
974
|
const current = {
|
|
938
|
-
wire: options.wireVersion ?? { version: "2026-09-
|
|
975
|
+
wire: options.wireVersion ?? { version: "2026-09-13" },
|
|
939
976
|
protocols: protocolOffers(options)
|
|
940
977
|
};
|
|
941
|
-
|
|
978
|
+
const compatible = (record) => Object.entries(record.lanes).every(([protocol, lane]) => lane.commands.every((command) => storedCommandCompatible(protocol, command, current)));
|
|
979
|
+
if (stored !== null && !compatible(stored)) {
|
|
942
980
|
if (!store.migrate) throw new Error("statewire: stored commands require a storage migration");
|
|
943
981
|
stored = store.migrate(stored, current);
|
|
944
|
-
if (stored !== null
|
|
945
|
-
|
|
982
|
+
if (stored !== null) {
|
|
983
|
+
validateLanes(stored);
|
|
984
|
+
if (!compatible(stored)) throw new Error("statewire: migrated commands are incompatible");
|
|
985
|
+
store.save(stored);
|
|
986
|
+
}
|
|
946
987
|
}
|
|
947
|
-
if (stored !== null && !Array.isArray(stored.commands)) throw new Error("statewire: session storage loaded a record without a commands array");
|
|
948
988
|
const record = stored ?? {
|
|
949
989
|
clientId: randomId(),
|
|
950
|
-
|
|
951
|
-
commands: []
|
|
990
|
+
lanes: {}
|
|
952
991
|
};
|
|
953
992
|
if (stored === null) store.save(record);
|
|
954
993
|
return record;
|
|
@@ -963,16 +1002,11 @@ const useStatewireSession = (options) => {
|
|
|
963
1002
|
const [s] = useState(() => {
|
|
964
1003
|
const record = store.ready === void 0 ? loadSessionRecord(store, options) : {
|
|
965
1004
|
clientId: randomId(),
|
|
966
|
-
|
|
967
|
-
commands: []
|
|
1005
|
+
lanes: {}
|
|
968
1006
|
};
|
|
969
1007
|
const session = {
|
|
970
1008
|
options,
|
|
971
1009
|
selection: void 0,
|
|
972
|
-
contextDocuments: {
|
|
973
|
-
mode: "mounted",
|
|
974
|
-
documents: /* @__PURE__ */ new Map()
|
|
975
|
-
},
|
|
976
1010
|
record,
|
|
977
1011
|
ctx: {
|
|
978
1012
|
beforeAttach: () => persistBeforeSend(session),
|
|
@@ -1003,17 +1037,18 @@ const useStatewireSession = (options) => {
|
|
|
1003
1037
|
ephemeral: false,
|
|
1004
1038
|
deactivated: false,
|
|
1005
1039
|
sessionKnown: false,
|
|
1006
|
-
durableSeq:
|
|
1040
|
+
durableSeq: {},
|
|
1041
|
+
ephemeralSeqs: {},
|
|
1007
1042
|
resumeAt: 0,
|
|
1008
1043
|
idle: false,
|
|
1009
1044
|
onSessionLost: void 0,
|
|
1010
1045
|
onDone: void 0,
|
|
1011
|
-
ctxNextSeq: 1,
|
|
1012
|
-
ctxPending: [],
|
|
1013
1046
|
lanesOnAttach: false,
|
|
1014
|
-
|
|
1047
|
+
attachEntries: /* @__PURE__ */ new Set(),
|
|
1015
1048
|
frameFailures: 0,
|
|
1016
1049
|
pending: [],
|
|
1050
|
+
failedList: [],
|
|
1051
|
+
failedParts: [],
|
|
1017
1052
|
canonical: void 0,
|
|
1018
1053
|
documentListeners: /* @__PURE__ */ new Set(),
|
|
1019
1054
|
documents: {
|
|
@@ -1087,6 +1122,6 @@ const useStatewireSession = (options) => {
|
|
|
1087
1122
|
return s;
|
|
1088
1123
|
};
|
|
1089
1124
|
//#endregion
|
|
1090
|
-
export { discardFailed, documentSource, failedCommands, invokeProtocol, retryFailed,
|
|
1125
|
+
export { discardFailed, documentSource, failedCommands, invokeProtocol, retryFailed, setTransport, useStatewireSession };
|
|
1091
1126
|
|
|
1092
1127
|
//# sourceMappingURL=session.js.map
|