statewire 0.16.0 → 0.17.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/README.md +12 -4
- package/dist/client/connection.d.ts +5 -1
- package/dist/client/connection.d.ts.map +1 -1
- package/dist/client/connection.js +22 -6
- package/dist/client/connection.js.map +1 -1
- package/dist/client/core.d.ts.map +1 -1
- package/dist/client/core.js +2 -0
- package/dist/client/core.js.map +1 -1
- package/dist/client/session.d.ts +15 -4
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +235 -52
- package/dist/client/session.js.map +1 -1
- package/dist/client/sessionStore.d.ts +5 -1
- package/dist/client/sessionStore.d.ts.map +1 -1
- package/dist/client/sessionStore.js +3 -1
- package/dist/client/sessionStore.js.map +1 -1
- package/dist/document.d.ts +25 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +88 -0
- package/dist/document.js.map +1 -0
- package/dist/host/draft.js +1 -1
- package/dist/host/host.d.ts +1 -0
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js.map +1 -1
- package/dist/host/http.d.ts.map +1 -1
- package/dist/host/http.js +27 -9
- package/dist/host/http.js.map +1 -1
- package/dist/host/socket.d.ts +12 -5
- package/dist/host/socket.d.ts.map +1 -1
- package/dist/host/socket.js +107 -43
- package/dist/host/socket.js.map +1 -1
- package/dist/host-internal.d.ts +9 -1
- package/dist/host-internal.d.ts.map +1 -1
- package/dist/host-internal.js +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2 -1
- package/dist/statewire.d.ts +33 -2
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js +34 -3
- package/dist/statewire.js.map +1 -1
- package/dist/transports/fixtures.js +1 -0
- package/dist/transports/fixtures.js.map +1 -1
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/transports/remote.js +15 -8
- package/dist/transports/remote.js.map +1 -1
- package/dist/transports/sse.d.ts.map +1 -1
- package/dist/transports/sse.js +8 -8
- package/dist/transports/sse.js.map +1 -1
- package/dist/transports/testing.d.ts +1 -1
- package/dist/transports/testing.d.ts.map +1 -1
- package/dist/transports/testing.js +12 -4
- package/dist/transports/testing.js.map +1 -1
- package/dist/transports/ws.js +2 -2
- package/dist/transports/ws.js.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/wire/documents.d.ts +25 -2
- package/dist/wire/documents.d.ts.map +1 -1
- package/dist/wire/documents.js +28 -8
- package/dist/wire/documents.js.map +1 -1
- package/dist/wire/meta.d.ts +3 -18
- package/dist/wire/meta.d.ts.map +1 -1
- package/dist/wire/meta.js +8 -57
- package/dist/wire/meta.js.map +1 -1
- package/dist/wire/ops.js +1 -1
- package/dist/wire/protocol.d.ts +55 -0
- package/dist/wire/protocol.d.ts.map +1 -0
- package/dist/wire/protocol.js +221 -0
- package/dist/wire/protocol.js.map +1 -0
- package/dist/wire/version.d.ts +1 -3
- package/dist/wire/version.d.ts.map +1 -1
- package/dist/wire/version.js +1 -3
- package/dist/wire/version.js.map +1 -1
- package/package.json +1 -1
- package/src/client/connection.ts +43 -13
- package/src/client/core.ts +6 -0
- package/src/client/session.ts +340 -45
- package/src/client/sessionStore.ts +8 -0
- package/src/document.ts +185 -0
- package/src/host/host.ts +1 -0
- package/src/host/http.ts +36 -7
- package/src/host/socket.ts +149 -57
- package/src/host-internal.ts +12 -1
- package/src/index.ts +5 -0
- package/src/statewire.ts +109 -6
- package/src/transports/fixtures.ts +1 -1
- package/src/transports/remote.ts +16 -7
- package/src/transports/sse.ts +18 -8
- package/src/transports/testing.ts +10 -2
- package/src/transports/ws.ts +1 -1
- package/src/types.ts +11 -0
- package/src/wire/documents.ts +126 -8
- package/src/wire/meta.ts +11 -94
- package/src/wire/protocol.ts +327 -0
- package/src/wire/version.ts +0 -2
package/dist/client/session.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import { encodeDocument, reviveDocumentValue } from "../document.js";
|
|
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";
|
|
1
5
|
import { createMethodProxy } from "../util/methodProxy.js";
|
|
2
6
|
import { StatewireSendError } from "../types.js";
|
|
3
7
|
import { bindCommandsHost } from "../host/host.js";
|
|
4
|
-
import { encodeParams, encodeValue, reviveResponse, reviveValue } from "../wire/meta.js";
|
|
5
8
|
import "../wire/limits.js";
|
|
6
|
-
import { SessionStorageContext, createMemorySessionStore } from "./sessionStore.js";
|
|
9
|
+
import { DocumentStorageContext, SessionStorageContext, createMemorySessionStore } from "./sessionStore.js";
|
|
7
10
|
import { backoff } from "./timing.js";
|
|
8
11
|
import { TERMINAL } from "./connection.js";
|
|
9
|
-
import { applyDocumentOps } from "../wire/documents.js";
|
|
10
12
|
import { randomId } from "../util/randomId.js";
|
|
11
13
|
import { useContext, useEffect, useState } from "@assistant-ui/tap/react-shim";
|
|
12
14
|
//#region src/client/session.ts
|
|
@@ -79,7 +81,21 @@ const transact = (s, fn) => {
|
|
|
79
81
|
const run = shared.txq.then(() => s.store.lock(async () => {
|
|
80
82
|
const stored = s.store.load();
|
|
81
83
|
if (stored !== null && stored.clientId !== s.record.clientId) adoptReset(s, stored);
|
|
82
|
-
else if (stored !== null)
|
|
84
|
+
else if (stored !== null) {
|
|
85
|
+
s.record = stored;
|
|
86
|
+
if (stored.context !== void 0) {
|
|
87
|
+
const posted = new Set(s.ctxPending.filter((p) => p.posted).map((p) => p.seq));
|
|
88
|
+
s.ctxOps = stored.context.ops;
|
|
89
|
+
s.contextDocuments = applyDocumentOps({
|
|
90
|
+
mode: "mounted",
|
|
91
|
+
documents: /* @__PURE__ */ new Map()
|
|
92
|
+
}, stored.context.ops);
|
|
93
|
+
s.ctxPending = stored.context.pending.map((p) => ({
|
|
94
|
+
...p,
|
|
95
|
+
posted: posted.has(p.seq)
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
83
99
|
fn();
|
|
84
100
|
}));
|
|
85
101
|
shared.txq = run.then(() => {}, () => {});
|
|
@@ -103,6 +119,8 @@ const retainFailedInner = (s, entry, failure) => {
|
|
|
103
119
|
setRecord(s, {
|
|
104
120
|
...s.record,
|
|
105
121
|
failed: [...s.record.failed ?? [], {
|
|
122
|
+
protocol: entry.command.protocol,
|
|
123
|
+
...entry.command.produced !== void 0 && { produced: entry.command.produced },
|
|
106
124
|
command: entry.command.method,
|
|
107
125
|
params: entry.command.params,
|
|
108
126
|
...entry.command.meta !== void 0 && { meta: entry.command.meta },
|
|
@@ -294,8 +312,8 @@ const handleCmd = (s, cmd) => {
|
|
|
294
312
|
if (entry) cover(s, entry);
|
|
295
313
|
return;
|
|
296
314
|
}
|
|
297
|
-
parkVerdict(s, cmd);
|
|
298
315
|
if (!entry) return;
|
|
316
|
+
parkVerdict(s, cmd);
|
|
299
317
|
cover(s, entry);
|
|
300
318
|
settleEntry(s, entry, outcomeOf(cmd));
|
|
301
319
|
};
|
|
@@ -324,17 +342,36 @@ const handleCmds = (s, cmds) => {
|
|
|
324
342
|
wake(s);
|
|
325
343
|
};
|
|
326
344
|
const ingestOps = (s, ops) => {
|
|
327
|
-
const applied = applyDocumentOps(s.documents, ops);
|
|
345
|
+
const applied = applyDocumentOps(s.documents, ops, s.selection?.protocols);
|
|
328
346
|
s.documents = applied;
|
|
329
|
-
s.
|
|
330
|
-
s.changed.push(...applied.changedPaths);
|
|
347
|
+
const next = s.selection === void 0 ? applied.documents.get(0)?.value : s.options.applicationProtocols === "*" ? Object.fromEntries([...applied.documents].map(([id, d]) => [`${d.protocol}:${d.key === null ? id : JSON.stringify(d.key)}`, d.value])) : mainDocument(applied, s.options.protocol ?? "default")?.value;
|
|
348
|
+
if (next !== s.canonical) s.changed.push(...applied.changedPaths.length ? applied.changedPaths : [[]]);
|
|
349
|
+
s.canonical = next;
|
|
350
|
+
};
|
|
351
|
+
const cacheDocuments = (s) => {
|
|
352
|
+
if (s.documentStore && s.selection) for (const document of s.documents.documents.values()) {
|
|
353
|
+
if (!document.protocol || !document.key) continue;
|
|
354
|
+
const key = resolveDocumentKey(document.key, s.options.keyPrefixes?.[document.protocol]);
|
|
355
|
+
const version = s.selection.protocols.find((p) => p.name === document.protocol)?.version;
|
|
356
|
+
if (key !== void 0 && version !== void 0) s.documentStore.save([
|
|
357
|
+
s.documentStore.namespace ?? "",
|
|
358
|
+
document.protocol,
|
|
359
|
+
...key
|
|
360
|
+
], {
|
|
361
|
+
version,
|
|
362
|
+
value: document.value
|
|
363
|
+
});
|
|
364
|
+
}
|
|
331
365
|
};
|
|
332
366
|
const publishIngested = (s) => {
|
|
333
367
|
publish(s);
|
|
368
|
+
cacheDocuments(s);
|
|
369
|
+
for (const listener of s.documentListeners) listener();
|
|
334
370
|
wake(s);
|
|
335
371
|
};
|
|
336
372
|
const snapCtxClock = (s, ctxWm) => {
|
|
337
373
|
if (!(s.ctxPending.every((c, i) => c.seq === ctxWm + 1 + i) && s.record.ctxNextSeq === ctxWm + s.ctxPending.length + 1)) {
|
|
374
|
+
if (ctxWm > 0) throw new Error("statewire: context history is missing; use a new client id");
|
|
338
375
|
setRecord(s, {
|
|
339
376
|
...s.record,
|
|
340
377
|
ctxNextSeq: ctxWm + 1
|
|
@@ -350,17 +387,21 @@ const sessionLostInner = (s, seqWm, ctxWm) => {
|
|
|
350
387
|
setRecord(s, {
|
|
351
388
|
clientId: s.record.clientId,
|
|
352
389
|
nextSeq: seqWm + 1,
|
|
353
|
-
ctxNextSeq:
|
|
390
|
+
ctxNextSeq: s.record.ctxNextSeq,
|
|
354
391
|
commands: [],
|
|
355
392
|
...s.record.failed !== void 0 && { failed: s.record.failed }
|
|
356
393
|
});
|
|
357
|
-
|
|
394
|
+
snapCtxClock(s, ctxWm);
|
|
358
395
|
if (held.length > 0 && s.onSessionLost?.(held.map((p) => p.inv)) === "requeue") for (const old of held) enqueueInner(s, old.command, old.inv.params, () => {}, () => {});
|
|
359
396
|
publish(s);
|
|
360
397
|
wake(s);
|
|
361
398
|
};
|
|
362
399
|
const handleHello = (s, { ops, syn }) => {
|
|
363
400
|
if (syn.record !== void 0 && syn.record !== "ephemeral") protocolError(`unknown record: ${String(syn.record)}`);
|
|
401
|
+
s.selection = syn.version === void 0 || syn.protocols === void 0 ? void 0 : {
|
|
402
|
+
version: syn.version,
|
|
403
|
+
protocols: syn.protocols
|
|
404
|
+
};
|
|
364
405
|
s.documents = {
|
|
365
406
|
mode: null,
|
|
366
407
|
documents: /* @__PURE__ */ new Map()
|
|
@@ -382,6 +423,7 @@ const handleHello = (s, { ops, syn }) => {
|
|
|
382
423
|
s.ctxPending = s.ctxPending.filter((c) => c.seq > ctxWatermark);
|
|
383
424
|
for (const c of s.ctxPending) c.posted = attachCtx.has(c.seq);
|
|
384
425
|
snapCtxClock(s, ctxWatermark);
|
|
426
|
+
if (s.ctxOps !== void 0) persistContext(s);
|
|
385
427
|
syncBacklogInner(s);
|
|
386
428
|
s.sessionKnown = true;
|
|
387
429
|
};
|
|
@@ -411,6 +453,7 @@ const handleRefusal = (s, syn) => {
|
|
|
411
453
|
if (typeof ctxSeq === "number") {
|
|
412
454
|
s.ctxPending = s.ctxPending.filter((c) => c.seq > ctxSeq);
|
|
413
455
|
for (const c of s.ctxPending) c.posted = false;
|
|
456
|
+
if (s.ctxOps !== void 0) persistContext(s);
|
|
414
457
|
}
|
|
415
458
|
if (typeof syn.retry === "number") {
|
|
416
459
|
s.resumeAt = Date.now() + syn.retry;
|
|
@@ -470,10 +513,13 @@ const buildLanes = (s) => {
|
|
|
470
513
|
ops: c.ops,
|
|
471
514
|
seq: c.seq
|
|
472
515
|
})) },
|
|
473
|
-
...cmdEntries.length > 0 && { cmd: cmdEntries.map((p) =>
|
|
474
|
-
...p.command
|
|
475
|
-
|
|
476
|
-
|
|
516
|
+
...cmdEntries.length > 0 && { cmd: cmdEntries.map((p) => {
|
|
517
|
+
const { produced: _produced, ...command } = p.command;
|
|
518
|
+
return {
|
|
519
|
+
...command,
|
|
520
|
+
seq: p.seq
|
|
521
|
+
};
|
|
522
|
+
}) }
|
|
477
523
|
},
|
|
478
524
|
cmdEntries,
|
|
479
525
|
ctxEntries
|
|
@@ -592,6 +638,7 @@ const enqueueInner = (s, command, params, resolve, reject) => {
|
|
|
592
638
|
seq,
|
|
593
639
|
command,
|
|
594
640
|
inv: {
|
|
641
|
+
protocol: command.protocol,
|
|
595
642
|
method: command.method,
|
|
596
643
|
params,
|
|
597
644
|
ts: Date.now(),
|
|
@@ -645,11 +692,14 @@ const adoptStored = (s) => {
|
|
|
645
692
|
const stored = s.record.commands.filter((c) => !known.has(c.seq));
|
|
646
693
|
if (stored.length === 0) return;
|
|
647
694
|
for (const { seq, ...command } of stored) {
|
|
648
|
-
const
|
|
695
|
+
const offers = protocolOffers(s.options);
|
|
696
|
+
if (offers !== "*" && !offers.some((offer) => offer.name === command.protocol)) continue;
|
|
697
|
+
const params = command.meta === void 0 ? command.params : reviveDocumentValue([...command.params], command.meta);
|
|
649
698
|
const entry = {
|
|
650
699
|
seq,
|
|
651
700
|
command,
|
|
652
701
|
inv: {
|
|
702
|
+
protocol: command.protocol,
|
|
653
703
|
method: command.method,
|
|
654
704
|
params,
|
|
655
705
|
ts: Date.now(),
|
|
@@ -667,7 +717,7 @@ const adoptStored = (s) => {
|
|
|
667
717
|
}
|
|
668
718
|
s.pending.sort((a, b) => a.seq - b.seq);
|
|
669
719
|
};
|
|
670
|
-
const sendCommand = (s, method, params) => {
|
|
720
|
+
const sendCommand = (s, method, params, protocol = s.options.protocol ?? "default") => {
|
|
671
721
|
if (!s.running) return rejectedSend({
|
|
672
722
|
fate: "aborted",
|
|
673
723
|
cause: "unmount"
|
|
@@ -679,6 +729,11 @@ const sendCommand = (s, method, params) => {
|
|
|
679
729
|
const result = new Promise((resolve, reject) => {
|
|
680
730
|
const { params: encoded, meta } = encodeParams(params);
|
|
681
731
|
enqueue(s, {
|
|
732
|
+
protocol,
|
|
733
|
+
produced: {
|
|
734
|
+
wire: s.options.wireVersion ?? { version: "2026-09-07" },
|
|
735
|
+
protocols: protocolOffers(s.options) === "*" && s.selection ? s.selection.protocols : protocolOffers(s.options)
|
|
736
|
+
},
|
|
682
737
|
method,
|
|
683
738
|
params: encoded,
|
|
684
739
|
...meta !== void 0 && { meta }
|
|
@@ -721,7 +776,7 @@ const retryFailed = (s, entry) => {
|
|
|
721
776
|
});
|
|
722
777
|
const found = findFailed(s, entry);
|
|
723
778
|
if (found === void 0) throw unknownFailed(entry);
|
|
724
|
-
const params = found.meta === void 0 ? found.params :
|
|
779
|
+
const params = found.meta === void 0 ? found.params : reviveDocumentValue([...found.params], found.meta);
|
|
725
780
|
const result = new Promise((resolve, reject) => {
|
|
726
781
|
const retry = () => {
|
|
727
782
|
if (!s.running) {
|
|
@@ -745,6 +800,8 @@ const retryFailed = (s, entry) => {
|
|
|
745
800
|
}
|
|
746
801
|
takeFailed(s, current);
|
|
747
802
|
enqueueInner(s, {
|
|
803
|
+
protocol: current.protocol,
|
|
804
|
+
...current.produced !== void 0 && { produced: current.produced },
|
|
748
805
|
method: current.command,
|
|
749
806
|
params: [...current.params],
|
|
750
807
|
...current.meta !== void 0 && { meta: current.meta }
|
|
@@ -764,59 +821,167 @@ const retryFailed = (s, entry) => {
|
|
|
764
821
|
result.catch(() => {});
|
|
765
822
|
return result;
|
|
766
823
|
};
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
824
|
+
const contextSnapshot = (documents) => [...documents.documents].map(([doc, d]) => {
|
|
825
|
+
const encoded = encodeDocument(d.value);
|
|
826
|
+
return {
|
|
827
|
+
op: "mount",
|
|
828
|
+
...doc === 0 ? {} : { doc },
|
|
829
|
+
protocol: d.protocol,
|
|
830
|
+
...d.main ? { main: true } : {},
|
|
831
|
+
...d.key === null ? {} : { key: d.key },
|
|
832
|
+
value: encoded.value,
|
|
833
|
+
...encoded.meta === void 0 ? {} : { meta: encoded.meta }
|
|
834
|
+
};
|
|
835
|
+
});
|
|
836
|
+
const persistContext = (s) => {
|
|
837
|
+
const ops = contextSnapshot(s.contextDocuments);
|
|
777
838
|
s.ctxOps = ops;
|
|
778
|
-
|
|
839
|
+
setRecord(s, {
|
|
840
|
+
...s.record,
|
|
841
|
+
context: {
|
|
842
|
+
ops,
|
|
843
|
+
pending: s.ctxPending.map(({ seq, ops }) => ({
|
|
844
|
+
seq,
|
|
845
|
+
ops
|
|
846
|
+
}))
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
};
|
|
850
|
+
const changeContext = (s, build) => {
|
|
851
|
+
if (!s.running || isFinished(s)) return;
|
|
852
|
+
const submit = () => {
|
|
853
|
+
if (!s.running || isFinished(s)) return;
|
|
854
|
+
const previous = s.record.context === void 0 ? s.contextDocuments : applyDocumentOps({
|
|
855
|
+
mode: "mounted",
|
|
856
|
+
documents: /* @__PURE__ */ new Map()
|
|
857
|
+
}, s.record.context.ops);
|
|
858
|
+
const operations = build(previous);
|
|
859
|
+
if (!operations.length) throw new Error("statewire: context operations must not be empty");
|
|
860
|
+
const offers = protocolOffers(s.options);
|
|
861
|
+
const protocols = s.selection?.protocols ?? (offers === "*" ? void 0 : offers);
|
|
862
|
+
s.contextDocuments = applyDocumentOps(previous, operations, protocols, true);
|
|
779
863
|
const seq = s.record.ctxNextSeq;
|
|
780
|
-
|
|
781
|
-
...s.record,
|
|
782
|
-
ctxNextSeq: seq + 1
|
|
783
|
-
});
|
|
784
|
-
s.ctxPending.push({
|
|
864
|
+
s.ctxPending = [...s.ctxPending, {
|
|
785
865
|
seq,
|
|
786
|
-
ops
|
|
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
|
+
}),
|
|
787
872
|
posted: false
|
|
788
|
-
}
|
|
873
|
+
}];
|
|
874
|
+
s.record = {
|
|
875
|
+
...s.record,
|
|
876
|
+
ctxNextSeq: seq + 1
|
|
877
|
+
};
|
|
878
|
+
persistContext(s);
|
|
789
879
|
};
|
|
790
880
|
if (!s.shared) {
|
|
791
|
-
|
|
881
|
+
submit();
|
|
792
882
|
wake(s);
|
|
793
883
|
pump(s);
|
|
794
|
-
|
|
795
|
-
}
|
|
796
|
-
transact(s, () => {
|
|
797
|
-
if (!s.running || isFinished(s)) return;
|
|
798
|
-
mint();
|
|
799
|
-
}).then(() => {
|
|
884
|
+
} else transact(s, submit).then(() => {
|
|
800
885
|
wake(s);
|
|
801
886
|
pump(s);
|
|
802
887
|
});
|
|
803
888
|
};
|
|
889
|
+
const updateContext = (s, operations) => changeContext(s, () => operations);
|
|
890
|
+
const setContext = (s, value, protocol = s.options.protocol ?? "default") => {
|
|
891
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("statewire: context must be a plain object");
|
|
892
|
+
const encoded = encodeDocument(value);
|
|
893
|
+
changeContext(s, (documents) => {
|
|
894
|
+
const found = [...documents.documents].find(([, d]) => d.protocol === protocol && d.main);
|
|
895
|
+
const doc = found?.[0] ?? Math.max(-1, ...documents.documents.keys()) + 1;
|
|
896
|
+
return [{
|
|
897
|
+
...found === void 0 ? {
|
|
898
|
+
op: "mount",
|
|
899
|
+
protocol,
|
|
900
|
+
main: true
|
|
901
|
+
} : {
|
|
902
|
+
op: "replace",
|
|
903
|
+
path: []
|
|
904
|
+
},
|
|
905
|
+
...doc === 0 ? {} : { doc },
|
|
906
|
+
value: encoded.value,
|
|
907
|
+
...encoded.meta === void 0 ? {} : { meta: encoded.meta }
|
|
908
|
+
}];
|
|
909
|
+
});
|
|
910
|
+
};
|
|
911
|
+
const documentSource = (s) => ({
|
|
912
|
+
get: (protocol, key, prefix = s.options.keyPrefixes?.[protocol]) => {
|
|
913
|
+
const mounted = findDocument(s.documents, protocol, key, prefix);
|
|
914
|
+
if (mounted !== void 0) return mounted.value;
|
|
915
|
+
if (!s.documentStore || key === void 0) return void 0;
|
|
916
|
+
const absolute = resolveDocumentKey(key, prefix);
|
|
917
|
+
if (absolute === void 0) return void 0;
|
|
918
|
+
const cached = s.documentStore.load([
|
|
919
|
+
s.documentStore.namespace ?? "",
|
|
920
|
+
protocol,
|
|
921
|
+
...absolute
|
|
922
|
+
]);
|
|
923
|
+
if (cached === void 0) return void 0;
|
|
924
|
+
const offers = protocolOffers(s.options);
|
|
925
|
+
const offer = offers === "*" ? s.selection?.protocols.find((p) => p.name === protocol) : offers.find((p) => p.name === protocol);
|
|
926
|
+
return offer !== void 0 && inRange(cached.version, offer) ? cached.value : void 0;
|
|
927
|
+
},
|
|
928
|
+
list: () => s.documents.documents,
|
|
929
|
+
subscribe: (listener) => {
|
|
930
|
+
s.documentListeners.add(listener);
|
|
931
|
+
return () => {
|
|
932
|
+
s.documentListeners.delete(listener);
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
const invokeProtocol = (s, protocol, method, params) => sendCommand(s, method, params, protocol);
|
|
937
|
+
const storedCommandCompatible = (command, current) => {
|
|
938
|
+
if (!command.produced || !command.protocol || !inRange(command.produced.wire.version, current.wire)) return false;
|
|
939
|
+
const previous = command.produced.protocols;
|
|
940
|
+
if (current.protocols === "*") return true;
|
|
941
|
+
if (previous === "*") return false;
|
|
942
|
+
const produced = previous.find((p) => p.name === command.protocol);
|
|
943
|
+
const offer = current.protocols.find((p) => p.name === command.protocol);
|
|
944
|
+
return produced !== void 0 && offer !== void 0 && inRange(produced.version, offer);
|
|
945
|
+
};
|
|
804
946
|
const useStatewireSession = (options) => {
|
|
805
947
|
const [defaultStore] = useState(createMemorySessionStore);
|
|
806
948
|
const mounted = useContext(SessionStorageContext);
|
|
949
|
+
const documentStore = useContext(DocumentStorageContext);
|
|
807
950
|
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");
|
|
808
951
|
const store = options.sessionStore ?? mounted ?? defaultStore;
|
|
809
952
|
if (store.lock === void 0 !== (store.subscribe === void 0)) throw new Error("statewire: session storage must provide lock and subscribe together");
|
|
810
953
|
const [s] = useState(() => {
|
|
811
|
-
|
|
954
|
+
let stored = store.load();
|
|
955
|
+
if (stored !== null && !Array.isArray(stored.commands)) throw new Error("statewire: session storage loaded a record without a commands array");
|
|
956
|
+
const current = {
|
|
957
|
+
wire: options.wireVersion ?? { version: "2026-09-07" },
|
|
958
|
+
protocols: protocolOffers(options)
|
|
959
|
+
};
|
|
960
|
+
if (stored !== null && !stored.commands.every((command_0) => storedCommandCompatible(command_0, current))) {
|
|
961
|
+
if (!store.migrate) throw new Error("statewire: stored commands require a storage migration");
|
|
962
|
+
stored = store.migrate(stored, current);
|
|
963
|
+
if (stored !== null && !stored.commands.every((command) => storedCommandCompatible(command, current))) throw new Error("statewire: migrated commands are incompatible");
|
|
964
|
+
if (stored !== null) store.save(stored);
|
|
965
|
+
}
|
|
812
966
|
if (stored !== null && !Array.isArray(stored.commands)) throw new Error("statewire: session storage loaded a record without a commands array");
|
|
967
|
+
const record = stored ?? {
|
|
968
|
+
clientId: randomId(),
|
|
969
|
+
nextSeq: 1,
|
|
970
|
+
ctxNextSeq: 1,
|
|
971
|
+
commands: []
|
|
972
|
+
};
|
|
973
|
+
if (stored === null) store.save(record);
|
|
813
974
|
const session = {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
}
|
|
975
|
+
options,
|
|
976
|
+
selection: void 0,
|
|
977
|
+
contextDocuments: stored?.context === void 0 ? {
|
|
978
|
+
mode: "mounted",
|
|
979
|
+
documents: /* @__PURE__ */ new Map()
|
|
980
|
+
} : applyDocumentOps({
|
|
981
|
+
mode: "mounted",
|
|
982
|
+
documents: /* @__PURE__ */ new Map()
|
|
983
|
+
}, stored.context.ops),
|
|
984
|
+
record,
|
|
820
985
|
ctx: {
|
|
821
986
|
get clientId() {
|
|
822
987
|
return session.record.clientId;
|
|
@@ -832,6 +997,7 @@ const useStatewireSession = (options) => {
|
|
|
832
997
|
},
|
|
833
998
|
recordPersisted: stored !== null,
|
|
834
999
|
store,
|
|
1000
|
+
documentStore,
|
|
835
1001
|
shared: store.lock !== void 0 ? {
|
|
836
1002
|
txq: Promise.resolve(),
|
|
837
1003
|
parked: [],
|
|
@@ -845,13 +1011,17 @@ const useStatewireSession = (options) => {
|
|
|
845
1011
|
idle: false,
|
|
846
1012
|
onSessionLost: void 0,
|
|
847
1013
|
onDone: void 0,
|
|
848
|
-
ctxOps:
|
|
849
|
-
ctxPending:
|
|
1014
|
+
ctxOps: stored?.context?.ops,
|
|
1015
|
+
ctxPending: stored?.context?.pending.map((p) => ({
|
|
1016
|
+
...p,
|
|
1017
|
+
posted: false
|
|
1018
|
+
})) ?? [],
|
|
850
1019
|
lanesOnAttach: false,
|
|
851
1020
|
attachLaneSeqs: [/* @__PURE__ */ new Set(), /* @__PURE__ */ new Set()],
|
|
852
1021
|
frameFailures: 0,
|
|
853
1022
|
pending: [],
|
|
854
1023
|
canonical: void 0,
|
|
1024
|
+
documentListeners: /* @__PURE__ */ new Set(),
|
|
855
1025
|
documents: {
|
|
856
1026
|
mode: null,
|
|
857
1027
|
documents: /* @__PURE__ */ new Map()
|
|
@@ -885,9 +1055,22 @@ const useStatewireSession = (options) => {
|
|
|
885
1055
|
return session;
|
|
886
1056
|
});
|
|
887
1057
|
if (store.lock !== void 0 !== (s.shared !== false)) throw new Error("statewire: session storage changed sharedness across renders — lock/subscribe must be present from the first render or never");
|
|
1058
|
+
s.options = options;
|
|
888
1059
|
s.store = store;
|
|
1060
|
+
s.documentStore = documentStore;
|
|
889
1061
|
s.onSessionLost = options.onSessionLost;
|
|
890
1062
|
s.onDone = options.onDone;
|
|
1063
|
+
const keyPrefixes = JSON.stringify(options.keyPrefixes);
|
|
1064
|
+
useEffect(() => {
|
|
1065
|
+
cacheDocuments(s);
|
|
1066
|
+
}, [
|
|
1067
|
+
s,
|
|
1068
|
+
documentStore,
|
|
1069
|
+
keyPrefixes
|
|
1070
|
+
]);
|
|
1071
|
+
useEffect(() => documentStore?.subscribe?.(() => {
|
|
1072
|
+
for (const listener_1 of s.documentListeners) listener_1();
|
|
1073
|
+
}), [s, documentStore]);
|
|
891
1074
|
useEffect(() => {
|
|
892
1075
|
start(s);
|
|
893
1076
|
return () => stop(s);
|
|
@@ -895,6 +1078,6 @@ const useStatewireSession = (options) => {
|
|
|
895
1078
|
return s;
|
|
896
1079
|
};
|
|
897
1080
|
//#endregion
|
|
898
|
-
export { discardFailed, failedCommands, retryFailed, setContext, setTransport, useStatewireSession };
|
|
1081
|
+
export { discardFailed, documentSource, failedCommands, invokeProtocol, retryFailed, setContext, setTransport, updateContext, useStatewireSession };
|
|
899
1082
|
|
|
900
1083
|
//# sourceMappingURL=session.js.map
|