statewire 0.10.0 → 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/session.d.ts +6 -0
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +213 -64
- package/dist/client/session.js.map +1 -1
- package/dist/statewire.d.ts +15 -0
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js.map +1 -1
- package/package.json +1 -1
- package/src/client/session.ts +275 -63
- package/src/statewire.ts +16 -0
package/dist/client/session.d.ts
CHANGED
|
@@ -32,11 +32,17 @@ type StatewireSessionOptions = {
|
|
|
32
32
|
sessionStore?: StatewireClient.SessionStorage | undefined;
|
|
33
33
|
onDone?: (() => void) | undefined;
|
|
34
34
|
};
|
|
35
|
+
type Shared = {
|
|
36
|
+
txq: Promise<void>;
|
|
37
|
+
parked: StatewireClient.SessionVerdict[];
|
|
38
|
+
unsubscribe: (() => void) | null;
|
|
39
|
+
};
|
|
35
40
|
type Session = {
|
|
36
41
|
record: StatewireClient.SessionRecord;
|
|
37
42
|
ctx: StatewireRemoteProps;
|
|
38
43
|
recordPersisted: boolean;
|
|
39
44
|
store: StatewireClient.SessionStorage;
|
|
45
|
+
shared: Shared | false;
|
|
40
46
|
ephemeral: boolean;
|
|
41
47
|
/** An ephemeral record died with its attach: the storage is off for good. */
|
|
42
48
|
deactivated: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","names":[],"sources":["../../src/client/session.ts"],"mappings":";;;;;;KA4BK;EACH;EACA;EACA;;KAGG;WACM;WACA,SAAS;WACT,KAAK;;EAEd;;EAEA;;EAEA;EACA,UAAU;EACV,SAAS,OAAO;;KAGb;WACM;WACA;EACT;;KAiBG;KAEO;EACV,eAAe,gBAAgB;EAC/B;;KAGG;EACH,QAAQ,gBAAgB;EACxB,KAAK;EACL;EACA,OAAO,gBAAgB;EACvB;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YAAY;EACZ;EACA,0BAA0B,qBAAqB;EAC/C;EACA,SAAS;EACT;EACA;EACA,SAAS;EACT,QAAQ,UAAU;EAClB,QAAQ;EACR;EACA;EACA;EACA,WAAW;EACX;EACA,WAAW;EACX;EACA,UAAU,mBAAmB,sBAAsB;;
|
|
1
|
+
{"version":3,"file":"session.d.ts","names":[],"sources":["../../src/client/session.ts"],"mappings":";;;;;;KA4BK;EACH;EACA;EACA;;KAGG;WACM;WACA,SAAS;WACT,KAAK;;EAEd;;EAEA;;EAEA;EACA,UAAU;EACV,SAAS,OAAO;;KAGb;WACM;WACA;EACT;;KAiBG;KAEO;EACV,eAAe,gBAAgB;EAC/B;;KAGG;EACH,KAAK;EACL,QAAQ,gBAAgB;EACxB;;KAGG;EACH,QAAQ,gBAAgB;EACxB,KAAK;EACL;EACA,OAAO,gBAAgB;EACvB,QAAQ;EACR;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YAAY;EACZ;EACA,0BAA0B,qBAAqB;EAC/C;EACA,SAAS;EACT;EACA;EACA,SAAS;EACT,QAAQ,UAAU;EAClB,QAAQ;EACR;EACA;EACA;EACA,WAAW;EACX;EACA,WAAW;EACX;EACA,UAAU,mBAAmB,sBAAsB;;cAgpB/C,eAAgB,GAAG,SAAS,WAAW;cA8GvC,aAAc,GAAG,SAAS,OAAO;cAgC1B,sBACX,SAAS,4BACR"}
|
package/dist/client/session.js
CHANGED
|
@@ -47,14 +47,43 @@ const backlog = (s) => s.pending.filter((p) => p.seq > s.durableSeq).map((p) =>
|
|
|
47
47
|
seq: p.seq,
|
|
48
48
|
...p.command
|
|
49
49
|
}));
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
if (
|
|
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;
|
|
53
62
|
setRecord(s, {
|
|
54
63
|
...s.record,
|
|
55
|
-
commands
|
|
64
|
+
commands,
|
|
65
|
+
...verdicts !== void 0 && { verdicts }
|
|
56
66
|
});
|
|
57
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;
|
|
86
|
+
};
|
|
58
87
|
const setIdle = (s, level) => {
|
|
59
88
|
if (level === s.idle) return;
|
|
60
89
|
s.idle = level;
|
|
@@ -151,11 +180,36 @@ const resetRecord = (s, clientId) => {
|
|
|
151
180
|
});
|
|
152
181
|
for (const old of survivors) {
|
|
153
182
|
s.lifecycle?.onCovered?.(old.inv);
|
|
154
|
-
|
|
183
|
+
enqueueInner(s, old.command, old.inv.params, old.resolve, old.reject);
|
|
184
|
+
}
|
|
185
|
+
restateContext(s);
|
|
186
|
+
publish(s);
|
|
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);
|
|
155
208
|
}
|
|
156
209
|
restateContext(s);
|
|
157
210
|
publish(s);
|
|
158
211
|
wake(s);
|
|
212
|
+
pump(s);
|
|
159
213
|
};
|
|
160
214
|
const ephemeralDetach = (s) => {
|
|
161
215
|
s.ephemeral = false;
|
|
@@ -171,6 +225,50 @@ const rejectedFailure = (cmd) => {
|
|
|
171
225
|
...cmd.payload !== void 0 && { payload: cmd.payload }
|
|
172
226
|
};
|
|
173
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
|
+
};
|
|
174
272
|
const handleCmd = (s, cmd) => {
|
|
175
273
|
if (!Number.isSafeInteger(cmd.seq) || cmd.seq <= 0) protocolError("cmd.seq must be a positive safe integer");
|
|
176
274
|
if (cmd.dur === true) {
|
|
@@ -183,35 +281,29 @@ const handleCmd = (s, cmd) => {
|
|
|
183
281
|
if (entry) cover(s, entry);
|
|
184
282
|
return;
|
|
185
283
|
}
|
|
284
|
+
parkVerdict(s, cmd);
|
|
186
285
|
if (!entry) return;
|
|
187
286
|
cover(s, entry);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
ok: false,
|
|
209
|
-
failure: {
|
|
210
|
-
fate: "expired",
|
|
211
|
-
cause: "server"
|
|
212
|
-
}
|
|
213
|
-
} : protocolError(`unknown cmd type: ${String(cmd.type)}`);
|
|
214
|
-
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);
|
|
215
307
|
};
|
|
216
308
|
const handleCmds = (s, cmds) => {
|
|
217
309
|
for (const cmd of cmds) handleCmd(s, cmd);
|
|
@@ -242,7 +334,7 @@ const snapClocks = (s, seqWm, ctxWm) => {
|
|
|
242
334
|
});
|
|
243
335
|
for (const old of tail) {
|
|
244
336
|
s.lifecycle?.onCovered?.(old.inv);
|
|
245
|
-
|
|
337
|
+
enqueueInner(s, old.command, old.inv.params, old.resolve, old.reject);
|
|
246
338
|
}
|
|
247
339
|
}
|
|
248
340
|
if (!(s.ctxPending.every((c, i) => c.seq === ctxWm + 1 + i) && s.record.ctxNextSeq === ctxWm + s.ctxPending.length + 1)) {
|
|
@@ -265,17 +357,28 @@ const handleHello = (s, { ops, syn }) => {
|
|
|
265
357
|
if (s.ephemeral) s.deactivated = true;
|
|
266
358
|
const [attachCmd, attachCtx] = s.attachLaneSeqs;
|
|
267
359
|
s.attachLaneSeqs = [/* @__PURE__ */ new Set(), /* @__PURE__ */ new Set()];
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
+
});
|
|
279
382
|
};
|
|
280
383
|
const handleOps = (s, { ops, cmds }) => {
|
|
281
384
|
ingestOps(s, ops);
|
|
@@ -308,7 +411,7 @@ const handleDetach = (s) => {
|
|
|
308
411
|
const handleFinish = (s, { reason }) => {
|
|
309
412
|
if (reason === "evicted" || reason === "error") return;
|
|
310
413
|
s.attach = "finished";
|
|
311
|
-
settleAll(s, reason === "superseded" ? "superseded" : "gone");
|
|
414
|
+
if (!(s.shared && reason === "superseded")) settleAll(s, reason === "superseded" ? "superseded" : "gone");
|
|
312
415
|
publish(s);
|
|
313
416
|
wake(s);
|
|
314
417
|
};
|
|
@@ -376,7 +479,8 @@ const pump = async (s) => {
|
|
|
376
479
|
for (const entry of lanes.cmdEntries) entry.attempted = true;
|
|
377
480
|
let result;
|
|
378
481
|
try {
|
|
379
|
-
|
|
482
|
+
const send = () => transport.send(lanes.frame);
|
|
483
|
+
result = await (s.shared && !s.deactivated ? s.store.lock(send) : send());
|
|
380
484
|
} catch {
|
|
381
485
|
if (!s.running || isFinished(s)) return;
|
|
382
486
|
s.frameFailures += 1;
|
|
@@ -413,16 +517,33 @@ const pump = async (s) => {
|
|
|
413
517
|
}
|
|
414
518
|
};
|
|
415
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
|
+
}
|
|
416
533
|
if (!s.recordPersisted) {
|
|
417
534
|
s.recordPersisted = true;
|
|
418
535
|
s.store.save(s.record);
|
|
419
536
|
}
|
|
420
|
-
|
|
537
|
+
adoptStored(s);
|
|
421
538
|
s.running = true;
|
|
422
539
|
pump(s);
|
|
423
540
|
};
|
|
424
541
|
const stop = (s) => {
|
|
425
542
|
s.running = false;
|
|
543
|
+
if (s.shared) {
|
|
544
|
+
s.shared.unsubscribe?.();
|
|
545
|
+
s.shared.unsubscribe = null;
|
|
546
|
+
}
|
|
426
547
|
wake(s);
|
|
427
548
|
settleAll(s, "unmount");
|
|
428
549
|
};
|
|
@@ -431,7 +552,7 @@ const setTransport = (s, transport) => {
|
|
|
431
552
|
s.transport = transport;
|
|
432
553
|
wake(s);
|
|
433
554
|
};
|
|
434
|
-
const
|
|
555
|
+
const enqueueInner = (s, command, params, resolve, reject) => {
|
|
435
556
|
const seq = s.record.nextSeq;
|
|
436
557
|
const entry = {
|
|
437
558
|
seq,
|
|
@@ -453,13 +574,22 @@ const enqueue = (s, command, params, resolve, reject) => {
|
|
|
453
574
|
setRecord(s, {
|
|
454
575
|
...s.record,
|
|
455
576
|
nextSeq: seq + 1,
|
|
456
|
-
commands:
|
|
577
|
+
commands: mergeBacklog(s)
|
|
457
578
|
});
|
|
458
579
|
s.lifecycle?.onStart(entry.inv);
|
|
459
|
-
pump(s);
|
|
460
580
|
};
|
|
461
|
-
const
|
|
462
|
-
|
|
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;
|
|
463
593
|
for (const { seq, ...command } of stored) {
|
|
464
594
|
const params = command.meta === void 0 ? command.params : reviveValue([...command.params], command.meta);
|
|
465
595
|
const entry = {
|
|
@@ -481,6 +611,7 @@ const resume = (s) => {
|
|
|
481
611
|
s.pending.push(entry);
|
|
482
612
|
s.lifecycle?.onStart(entry.inv);
|
|
483
613
|
}
|
|
614
|
+
s.pending.sort((a, b) => a.seq - b.seq);
|
|
484
615
|
};
|
|
485
616
|
const sendCommand = (s, method, params) => {
|
|
486
617
|
if (!s.running) return rejectedSend({
|
|
@@ -504,31 +635,44 @@ const sendCommand = (s, method, params) => {
|
|
|
504
635
|
};
|
|
505
636
|
const setContext = (s, value) => {
|
|
506
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;
|
|
507
639
|
const { value: encoded, meta } = encodeValue(value);
|
|
508
|
-
|
|
640
|
+
const ops = [{
|
|
509
641
|
op: "replace",
|
|
510
642
|
path: [],
|
|
511
643
|
value: encoded,
|
|
512
644
|
...meta !== void 0 && { meta }
|
|
513
645
|
}];
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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);
|
|
523
668
|
});
|
|
524
|
-
wake(s);
|
|
525
|
-
pump(s);
|
|
526
669
|
};
|
|
527
670
|
const useStatewireSession = (options) => {
|
|
528
671
|
const [defaultStore] = useState(createMemorySessionStore);
|
|
529
672
|
const mounted = useContext(SessionStorageContext);
|
|
530
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");
|
|
531
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");
|
|
532
676
|
const [s] = useState(() => {
|
|
533
677
|
const stored = store.load();
|
|
534
678
|
if (stored !== null && !Array.isArray(stored.commands)) throw new Error("statewire: session storage loaded a record without a commands array");
|
|
@@ -554,6 +698,11 @@ const useStatewireSession = (options) => {
|
|
|
554
698
|
},
|
|
555
699
|
recordPersisted: stored !== null,
|
|
556
700
|
store,
|
|
701
|
+
shared: store.lock !== void 0 ? {
|
|
702
|
+
txq: Promise.resolve(),
|
|
703
|
+
parked: [],
|
|
704
|
+
unsubscribe: null
|
|
705
|
+
} : false,
|
|
557
706
|
ephemeral: false,
|
|
558
707
|
deactivated: false,
|
|
559
708
|
sessionKnown: false,
|