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