statewire 0.9.2 → 0.11.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 +1 -0
- package/dist/client/connection.d.ts.map +1 -1
- package/dist/client/connection.js +8 -1
- package/dist/client/connection.js.map +1 -1
- package/dist/client/session.d.ts +13 -4
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +263 -66
- package/dist/client/session.js.map +1 -1
- package/dist/client/sessionStore.d.ts +5 -14
- package/dist/client/sessionStore.d.ts.map +1 -1
- package/dist/client/sessionStore.js +4 -1
- package/dist/client/sessionStore.js.map +1 -1
- package/dist/host/http.d.ts +7 -0
- package/dist/host/http.d.ts.map +1 -0
- package/dist/host/http.js +175 -0
- package/dist/host/http.js.map +1 -0
- package/dist/host/socket.d.ts +102 -1
- package/dist/host/socket.d.ts.map +1 -1
- package/dist/host/socket.js +48 -27
- package/dist/host/socket.js.map +1 -1
- package/dist/host-internal.d.ts +5 -0
- package/dist/host-internal.d.ts.map +1 -1
- package/dist/statewire.d.ts +48 -7
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js +9 -2
- package/dist/statewire.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/client/connection.ts +13 -1
- package/src/client/session.ts +352 -70
- package/src/client/sessionStore.ts +8 -15
- package/src/host/http.ts +280 -0
- package/src/host/socket.ts +120 -57
- package/src/host-internal.ts +5 -0
- package/src/statewire.ts +80 -8
- package/src/types.ts +1 -1
package/dist/client/session.js
CHANGED
|
@@ -2,11 +2,11 @@ import { applyOps } from "../wire/ops.js";
|
|
|
2
2
|
import { createMethodProxy } from "../util/methodProxy.js";
|
|
3
3
|
import { StatewireSendError } from "../types.js";
|
|
4
4
|
import { bindCommandsHost } from "../host/host.js";
|
|
5
|
-
import { encodeParams, encodeValue, reviveResponse } from "../wire/meta.js";
|
|
5
|
+
import { encodeParams, encodeValue, reviveResponse, reviveValue } from "../wire/meta.js";
|
|
6
|
+
import { SessionStorageContext, createMemorySessionStore } from "./sessionStore.js";
|
|
6
7
|
import { backoff } from "./timing.js";
|
|
7
|
-
import { createMemorySessionStore } from "./sessionStore.js";
|
|
8
8
|
import { randomId } from "../util/randomId.js";
|
|
9
|
-
import { useEffect, useState } from "@assistant-ui/tap/react-shim";
|
|
9
|
+
import { useContext, useEffect, useState } from "@assistant-ui/tap/react-shim";
|
|
10
10
|
//#region src/client/session.ts
|
|
11
11
|
const protocolError = (message) => {
|
|
12
12
|
throw new Error(`statewire: protocol error: ${message}`);
|
|
@@ -41,7 +41,48 @@ const sleepUntilWake = (s, delayMs) => new Promise((resolve) => {
|
|
|
41
41
|
});
|
|
42
42
|
const setRecord = (s, record) => {
|
|
43
43
|
s.record = record;
|
|
44
|
-
if (!s.
|
|
44
|
+
if (!s.deactivated) s.store.save(record);
|
|
45
|
+
};
|
|
46
|
+
const backlog = (s) => s.pending.filter((p) => p.seq > s.durableSeq).map((p) => ({
|
|
47
|
+
seq: p.seq,
|
|
48
|
+
...p.command
|
|
49
|
+
}));
|
|
50
|
+
const mergeBacklog = (s) => {
|
|
51
|
+
const own = backlog(s);
|
|
52
|
+
if (!s.shared) return own;
|
|
53
|
+
const known = new Set(s.pending.map((p) => p.seq));
|
|
54
|
+
return [...s.record.commands.filter((c) => !known.has(c.seq) && c.seq > s.durableSeq), ...own].sort((a, b) => a.seq - b.seq);
|
|
55
|
+
};
|
|
56
|
+
const syncBacklogInner = (s) => {
|
|
57
|
+
const parked = s.shared ? s.shared.parked.splice(0) : [];
|
|
58
|
+
const parkedSeqs = new Set(parked.map((v) => v.seq));
|
|
59
|
+
const commands = mergeBacklog(s).filter((c) => !parkedSeqs.has(c.seq));
|
|
60
|
+
const verdicts = parked.length > 0 ? [...s.record.verdicts ?? [], ...parked].slice(-64) : s.record.verdicts;
|
|
61
|
+
if (verdicts === s.record.verdicts && commands.length === s.record.commands.length && commands.every((c, i) => c.seq === s.record.commands[i].seq)) return;
|
|
62
|
+
setRecord(s, {
|
|
63
|
+
...s.record,
|
|
64
|
+
commands,
|
|
65
|
+
...verdicts !== void 0 && { verdicts }
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
const syncBacklog = (s) => {
|
|
69
|
+
if (!s.shared) return syncBacklogInner(s);
|
|
70
|
+
transact(s, () => syncBacklogInner(s));
|
|
71
|
+
};
|
|
72
|
+
const transact = (s, fn) => {
|
|
73
|
+
if (!s.shared || s.deactivated) {
|
|
74
|
+
fn();
|
|
75
|
+
return Promise.resolve();
|
|
76
|
+
}
|
|
77
|
+
const shared = s.shared;
|
|
78
|
+
const run = shared.txq.then(() => s.store.lock(async () => {
|
|
79
|
+
const stored = s.store.load();
|
|
80
|
+
if (stored !== null && stored.clientId !== s.record.clientId) adoptReset(s, stored);
|
|
81
|
+
else if (stored !== null) s.record = stored;
|
|
82
|
+
fn();
|
|
83
|
+
}));
|
|
84
|
+
shared.txq = run.then(() => {}, () => {});
|
|
85
|
+
return run;
|
|
45
86
|
};
|
|
46
87
|
const setIdle = (s, level) => {
|
|
47
88
|
if (level === s.idle) return;
|
|
@@ -134,15 +175,41 @@ const resetRecord = (s, clientId) => {
|
|
|
134
175
|
setRecord(s, {
|
|
135
176
|
clientId,
|
|
136
177
|
nextSeq: 1,
|
|
137
|
-
ctxNextSeq: 1
|
|
178
|
+
ctxNextSeq: 1,
|
|
179
|
+
commands: []
|
|
138
180
|
});
|
|
139
181
|
for (const old of survivors) {
|
|
140
182
|
s.lifecycle?.onCovered?.(old.inv);
|
|
141
|
-
|
|
183
|
+
enqueueInner(s, old.command, old.inv.params, old.resolve, old.reject);
|
|
142
184
|
}
|
|
143
185
|
restateContext(s);
|
|
144
186
|
publish(s);
|
|
145
187
|
wake(s);
|
|
188
|
+
pump(s);
|
|
189
|
+
};
|
|
190
|
+
const adoptReset = (s, stored) => {
|
|
191
|
+
const survivors = [];
|
|
192
|
+
for (const entry of [...s.pending]) if (entry.attempted || entry.applied) {
|
|
193
|
+
s.lifecycle?.onCovered?.(entry.inv);
|
|
194
|
+
settleEntry(s, entry, {
|
|
195
|
+
ok: false,
|
|
196
|
+
failure: {
|
|
197
|
+
fate: "lost",
|
|
198
|
+
cause: "session-lost"
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
} else survivors.push(entry);
|
|
202
|
+
s.pending = [];
|
|
203
|
+
s.durableSeq = 0;
|
|
204
|
+
s.record = stored;
|
|
205
|
+
for (const old of survivors) {
|
|
206
|
+
s.lifecycle?.onCovered?.(old.inv);
|
|
207
|
+
enqueueInner(s, old.command, old.inv.params, old.resolve, old.reject);
|
|
208
|
+
}
|
|
209
|
+
restateContext(s);
|
|
210
|
+
publish(s);
|
|
211
|
+
wake(s);
|
|
212
|
+
pump(s);
|
|
146
213
|
};
|
|
147
214
|
const ephemeralDetach = (s) => {
|
|
148
215
|
s.ephemeral = false;
|
|
@@ -158,6 +225,50 @@ const rejectedFailure = (cmd) => {
|
|
|
158
225
|
...cmd.payload !== void 0 && { payload: cmd.payload }
|
|
159
226
|
};
|
|
160
227
|
};
|
|
228
|
+
const outcomeOf = (cmd) => cmd.type === "result" ? {
|
|
229
|
+
ok: true,
|
|
230
|
+
payload: reviveResponse(cmd).payload
|
|
231
|
+
} : cmd.type === "rejected" ? {
|
|
232
|
+
ok: false,
|
|
233
|
+
failure: rejectedFailure(reviveResponse(cmd))
|
|
234
|
+
} : cmd.type === "failed" ? {
|
|
235
|
+
ok: false,
|
|
236
|
+
failure: {
|
|
237
|
+
fate: "failed",
|
|
238
|
+
cause: "server",
|
|
239
|
+
...cmd.message !== void 0 && { message: cmd.message }
|
|
240
|
+
}
|
|
241
|
+
} : cmd.type === "crashed" ? {
|
|
242
|
+
ok: false,
|
|
243
|
+
failure: {
|
|
244
|
+
fate: "crashed",
|
|
245
|
+
cause: "server"
|
|
246
|
+
}
|
|
247
|
+
} : cmd.type === "expired" ? {
|
|
248
|
+
ok: false,
|
|
249
|
+
failure: {
|
|
250
|
+
fate: "expired",
|
|
251
|
+
cause: "server"
|
|
252
|
+
}
|
|
253
|
+
} : protocolError(`unknown cmd type: ${String(cmd.type)}`);
|
|
254
|
+
const TERMINAL_CMD_TYPES = /* @__PURE__ */ new Set([
|
|
255
|
+
"result",
|
|
256
|
+
"rejected",
|
|
257
|
+
"failed",
|
|
258
|
+
"crashed",
|
|
259
|
+
"expired"
|
|
260
|
+
]);
|
|
261
|
+
const parkVerdict = (s, cmd) => {
|
|
262
|
+
if (!s.shared || !TERMINAL_CMD_TYPES.has(cmd.type)) return;
|
|
263
|
+
s.shared.parked.push({
|
|
264
|
+
seq: cmd.seq,
|
|
265
|
+
type: cmd.type,
|
|
266
|
+
...cmd.code !== void 0 && { code: cmd.code },
|
|
267
|
+
...cmd.message !== void 0 && { message: cmd.message },
|
|
268
|
+
...cmd.payload !== void 0 && { payload: cmd.payload },
|
|
269
|
+
...cmd.meta !== void 0 && { meta: cmd.meta }
|
|
270
|
+
});
|
|
271
|
+
};
|
|
161
272
|
const handleCmd = (s, cmd) => {
|
|
162
273
|
if (!Number.isSafeInteger(cmd.seq) || cmd.seq <= 0) protocolError("cmd.seq must be a positive safe integer");
|
|
163
274
|
if (cmd.dur === true) {
|
|
@@ -170,38 +281,33 @@ const handleCmd = (s, cmd) => {
|
|
|
170
281
|
if (entry) cover(s, entry);
|
|
171
282
|
return;
|
|
172
283
|
}
|
|
284
|
+
parkVerdict(s, cmd);
|
|
173
285
|
if (!entry) return;
|
|
174
286
|
cover(s, entry);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
ok: false,
|
|
196
|
-
failure: {
|
|
197
|
-
fate: "expired",
|
|
198
|
-
cause: "server"
|
|
199
|
-
}
|
|
200
|
-
} : protocolError(`unknown cmd type: ${String(cmd.type)}`);
|
|
201
|
-
settleEntry(s, entry, outcome);
|
|
287
|
+
settleEntry(s, entry, outcomeOf(cmd));
|
|
288
|
+
};
|
|
289
|
+
const consumeVerdicts = (s) => {
|
|
290
|
+
const verdicts = s.record.verdicts ?? [];
|
|
291
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
292
|
+
for (const verdict of verdicts) {
|
|
293
|
+
const entry = s.pending.find((p) => p.seq === verdict.seq);
|
|
294
|
+
if (!entry) continue;
|
|
295
|
+
consumed.add(verdict.seq);
|
|
296
|
+
cover(s, entry);
|
|
297
|
+
settleEntry(s, entry, outcomeOf(verdict));
|
|
298
|
+
}
|
|
299
|
+
if (consumed.size === 0) return;
|
|
300
|
+
setRecord(s, {
|
|
301
|
+
...s.record,
|
|
302
|
+
commands: s.record.commands.filter((c) => !consumed.has(c.seq)),
|
|
303
|
+
verdicts: verdicts.filter((v) => !consumed.has(v.seq))
|
|
304
|
+
});
|
|
305
|
+
publish(s);
|
|
306
|
+
wake(s);
|
|
202
307
|
};
|
|
203
308
|
const handleCmds = (s, cmds) => {
|
|
204
309
|
for (const cmd of cmds) handleCmd(s, cmd);
|
|
310
|
+
syncBacklog(s);
|
|
205
311
|
wake(s);
|
|
206
312
|
};
|
|
207
313
|
const ingestOps = (s, ops) => {
|
|
@@ -228,7 +334,7 @@ const snapClocks = (s, seqWm, ctxWm) => {
|
|
|
228
334
|
});
|
|
229
335
|
for (const old of tail) {
|
|
230
336
|
s.lifecycle?.onCovered?.(old.inv);
|
|
231
|
-
|
|
337
|
+
enqueueInner(s, old.command, old.inv.params, old.resolve, old.reject);
|
|
232
338
|
}
|
|
233
339
|
}
|
|
234
340
|
if (!(s.ctxPending.every((c, i) => c.seq === ctxWm + 1 + i) && s.record.ctxNextSeq === ctxWm + s.ctxPending.length + 1)) {
|
|
@@ -248,22 +354,36 @@ const handleHello = (s, { ops, syn }) => {
|
|
|
248
354
|
s.frameFailures = 0;
|
|
249
355
|
s.resumeAt = 0;
|
|
250
356
|
s.ephemeral = record === "ephemeral";
|
|
357
|
+
if (s.ephemeral) s.deactivated = true;
|
|
251
358
|
const [attachCmd, attachCtx] = s.attachLaneSeqs;
|
|
252
359
|
s.attachLaneSeqs = [/* @__PURE__ */ new Set(), /* @__PURE__ */ new Set()];
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
360
|
+
const reconcile = () => {
|
|
361
|
+
if (record === "resumed") {
|
|
362
|
+
if (s.shared) adoptStored(s);
|
|
363
|
+
for (const entry of s.pending) entry.posted = attachCmd.has(entry.seq);
|
|
364
|
+
const ctxWatermark = syn.ctxSeq ?? 0;
|
|
365
|
+
s.ctxPending = s.ctxPending.filter((c) => c.seq > ctxWatermark);
|
|
366
|
+
for (const c of s.ctxPending) c.posted = attachCtx.has(c.seq);
|
|
367
|
+
snapClocks(s, syn.seq ?? 0, ctxWatermark);
|
|
368
|
+
syncBacklogInner(s);
|
|
369
|
+
} else resetRecord(s, s.record.clientId);
|
|
370
|
+
s.sessionKnown = true;
|
|
371
|
+
};
|
|
372
|
+
if (!s.shared || s.deactivated) {
|
|
373
|
+
reconcile();
|
|
374
|
+
publishIngested(s);
|
|
375
|
+
pump(s);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
transact(s, reconcile).then(() => {
|
|
379
|
+
publishIngested(s);
|
|
380
|
+
pump(s);
|
|
381
|
+
});
|
|
263
382
|
};
|
|
264
383
|
const handleOps = (s, { ops, cmds }) => {
|
|
265
384
|
ingestOps(s, ops);
|
|
266
385
|
for (const cmd of cmds) handleCmd(s, cmd);
|
|
386
|
+
syncBacklog(s);
|
|
267
387
|
publishIngested(s);
|
|
268
388
|
};
|
|
269
389
|
const handleRefusal = (s, syn) => {
|
|
@@ -291,7 +411,7 @@ const handleDetach = (s) => {
|
|
|
291
411
|
const handleFinish = (s, { reason }) => {
|
|
292
412
|
if (reason === "evicted" || reason === "error") return;
|
|
293
413
|
s.attach = "finished";
|
|
294
|
-
settleAll(s, reason === "superseded" ? "superseded" : "gone");
|
|
414
|
+
if (!(s.shared && reason === "superseded")) settleAll(s, reason === "superseded" ? "superseded" : "gone");
|
|
295
415
|
publish(s);
|
|
296
416
|
wake(s);
|
|
297
417
|
};
|
|
@@ -359,7 +479,8 @@ const pump = async (s) => {
|
|
|
359
479
|
for (const entry of lanes.cmdEntries) entry.attempted = true;
|
|
360
480
|
let result;
|
|
361
481
|
try {
|
|
362
|
-
|
|
482
|
+
const send = () => transport.send(lanes.frame);
|
|
483
|
+
result = await (s.shared && !s.deactivated ? s.store.lock(send) : send());
|
|
363
484
|
} catch {
|
|
364
485
|
if (!s.running || isFinished(s)) return;
|
|
365
486
|
s.frameFailures += 1;
|
|
@@ -396,15 +517,33 @@ const pump = async (s) => {
|
|
|
396
517
|
}
|
|
397
518
|
};
|
|
398
519
|
const start = (s) => {
|
|
520
|
+
if (s.shared) {
|
|
521
|
+
s.shared.unsubscribe = s.store.subscribe(() => {
|
|
522
|
+
transact(s, () => consumeVerdicts(s));
|
|
523
|
+
});
|
|
524
|
+
s.running = true;
|
|
525
|
+
transact(s, () => {
|
|
526
|
+
if (s.store.load() === null) s.store.save(s.record);
|
|
527
|
+
s.recordPersisted = true;
|
|
528
|
+
adoptStored(s);
|
|
529
|
+
consumeVerdicts(s);
|
|
530
|
+
}).then(() => void pump(s));
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
399
533
|
if (!s.recordPersisted) {
|
|
400
534
|
s.recordPersisted = true;
|
|
401
535
|
s.store.save(s.record);
|
|
402
536
|
}
|
|
537
|
+
adoptStored(s);
|
|
403
538
|
s.running = true;
|
|
404
539
|
pump(s);
|
|
405
540
|
};
|
|
406
541
|
const stop = (s) => {
|
|
407
542
|
s.running = false;
|
|
543
|
+
if (s.shared) {
|
|
544
|
+
s.shared.unsubscribe?.();
|
|
545
|
+
s.shared.unsubscribe = null;
|
|
546
|
+
}
|
|
408
547
|
wake(s);
|
|
409
548
|
settleAll(s, "unmount");
|
|
410
549
|
};
|
|
@@ -413,12 +552,8 @@ const setTransport = (s, transport) => {
|
|
|
413
552
|
s.transport = transport;
|
|
414
553
|
wake(s);
|
|
415
554
|
};
|
|
416
|
-
const
|
|
555
|
+
const enqueueInner = (s, command, params, resolve, reject) => {
|
|
417
556
|
const seq = s.record.nextSeq;
|
|
418
|
-
setRecord(s, {
|
|
419
|
-
...s.record,
|
|
420
|
-
nextSeq: seq + 1
|
|
421
|
-
});
|
|
422
557
|
const entry = {
|
|
423
558
|
seq,
|
|
424
559
|
command,
|
|
@@ -436,8 +571,47 @@ const enqueue = (s, command, params, resolve, reject) => {
|
|
|
436
571
|
reject
|
|
437
572
|
};
|
|
438
573
|
s.pending.push(entry);
|
|
574
|
+
setRecord(s, {
|
|
575
|
+
...s.record,
|
|
576
|
+
nextSeq: seq + 1,
|
|
577
|
+
commands: mergeBacklog(s)
|
|
578
|
+
});
|
|
439
579
|
s.lifecycle?.onStart(entry.inv);
|
|
440
|
-
|
|
580
|
+
};
|
|
581
|
+
const enqueue = (s, command, params, resolve, reject) => {
|
|
582
|
+
if (!s.shared) {
|
|
583
|
+
enqueueInner(s, command, params, resolve, reject);
|
|
584
|
+
pump(s);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
transact(s, () => enqueueInner(s, command, params, resolve, reject)).then(() => void pump(s));
|
|
588
|
+
};
|
|
589
|
+
const adoptStored = (s) => {
|
|
590
|
+
const known = new Set(s.pending.map((p) => p.seq));
|
|
591
|
+
const stored = s.record.commands.filter((c) => !known.has(c.seq));
|
|
592
|
+
if (stored.length === 0) return;
|
|
593
|
+
for (const { seq, ...command } of stored) {
|
|
594
|
+
const params = command.meta === void 0 ? command.params : reviveValue([...command.params], command.meta);
|
|
595
|
+
const entry = {
|
|
596
|
+
seq,
|
|
597
|
+
command,
|
|
598
|
+
inv: {
|
|
599
|
+
method: command.method,
|
|
600
|
+
params,
|
|
601
|
+
ts: Date.now(),
|
|
602
|
+
seq,
|
|
603
|
+
clientId: s.record.clientId
|
|
604
|
+
},
|
|
605
|
+
attempted: true,
|
|
606
|
+
posted: false,
|
|
607
|
+
applied: false,
|
|
608
|
+
resolve: () => {},
|
|
609
|
+
reject: () => {}
|
|
610
|
+
};
|
|
611
|
+
s.pending.push(entry);
|
|
612
|
+
s.lifecycle?.onStart(entry.inv);
|
|
613
|
+
}
|
|
614
|
+
s.pending.sort((a, b) => a.seq - b.seq);
|
|
441
615
|
};
|
|
442
616
|
const sendCommand = (s, method, params) => {
|
|
443
617
|
if (!s.running) return rejectedSend({
|
|
@@ -461,36 +635,53 @@ const sendCommand = (s, method, params) => {
|
|
|
461
635
|
};
|
|
462
636
|
const setContext = (s, value) => {
|
|
463
637
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("statewire: context must be a plain object");
|
|
638
|
+
if (!s.running || isFinished(s)) return;
|
|
464
639
|
const { value: encoded, meta } = encodeValue(value);
|
|
465
|
-
|
|
640
|
+
const ops = [{
|
|
466
641
|
op: "replace",
|
|
467
642
|
path: [],
|
|
468
643
|
value: encoded,
|
|
469
644
|
...meta !== void 0 && { meta }
|
|
470
645
|
}];
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
646
|
+
s.ctxOps = ops;
|
|
647
|
+
const mint = () => {
|
|
648
|
+
const seq = s.record.ctxNextSeq;
|
|
649
|
+
setRecord(s, {
|
|
650
|
+
...s.record,
|
|
651
|
+
ctxNextSeq: seq + 1
|
|
652
|
+
});
|
|
653
|
+
s.ctxPending.push({
|
|
654
|
+
seq,
|
|
655
|
+
ops,
|
|
656
|
+
posted: false
|
|
657
|
+
});
|
|
658
|
+
};
|
|
659
|
+
if (!s.shared) {
|
|
660
|
+
mint();
|
|
661
|
+
wake(s);
|
|
662
|
+
pump(s);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
transact(s, mint).then(() => {
|
|
666
|
+
wake(s);
|
|
667
|
+
pump(s);
|
|
480
668
|
});
|
|
481
|
-
wake(s);
|
|
482
|
-
pump(s);
|
|
483
669
|
};
|
|
484
670
|
const useStatewireSession = (options) => {
|
|
485
671
|
const [defaultStore] = useState(createMemorySessionStore);
|
|
486
|
-
const
|
|
672
|
+
const mounted = useContext(SessionStorageContext);
|
|
673
|
+
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");
|
|
674
|
+
const store = options.sessionStore ?? mounted ?? defaultStore;
|
|
675
|
+
if (store.lock === void 0 !== (store.subscribe === void 0)) throw new Error("statewire: session storage must provide lock and subscribe together");
|
|
487
676
|
const [s] = useState(() => {
|
|
488
677
|
const stored = store.load();
|
|
678
|
+
if (stored !== null && !Array.isArray(stored.commands)) throw new Error("statewire: session storage loaded a record without a commands array");
|
|
489
679
|
const session = {
|
|
490
680
|
record: stored ?? {
|
|
491
681
|
clientId: newClientId(),
|
|
492
682
|
nextSeq: 1,
|
|
493
|
-
ctxNextSeq: 1
|
|
683
|
+
ctxNextSeq: 1,
|
|
684
|
+
commands: []
|
|
494
685
|
},
|
|
495
686
|
ctx: {
|
|
496
687
|
get clientId() {
|
|
@@ -507,7 +698,13 @@ const useStatewireSession = (options) => {
|
|
|
507
698
|
},
|
|
508
699
|
recordPersisted: stored !== null,
|
|
509
700
|
store,
|
|
701
|
+
shared: store.lock !== void 0 ? {
|
|
702
|
+
txq: Promise.resolve(),
|
|
703
|
+
parked: [],
|
|
704
|
+
unsubscribe: null
|
|
705
|
+
} : false,
|
|
510
706
|
ephemeral: false,
|
|
707
|
+
deactivated: false,
|
|
511
708
|
sessionKnown: false,
|
|
512
709
|
durableSeq: 0,
|
|
513
710
|
resumeAt: 0,
|