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.
Files changed (96) hide show
  1. package/README.md +12 -4
  2. package/dist/client/connection.d.ts +5 -1
  3. package/dist/client/connection.d.ts.map +1 -1
  4. package/dist/client/connection.js +22 -6
  5. package/dist/client/connection.js.map +1 -1
  6. package/dist/client/core.d.ts.map +1 -1
  7. package/dist/client/core.js +2 -0
  8. package/dist/client/core.js.map +1 -1
  9. package/dist/client/session.d.ts +15 -4
  10. package/dist/client/session.d.ts.map +1 -1
  11. package/dist/client/session.js +235 -52
  12. package/dist/client/session.js.map +1 -1
  13. package/dist/client/sessionStore.d.ts +5 -1
  14. package/dist/client/sessionStore.d.ts.map +1 -1
  15. package/dist/client/sessionStore.js +3 -1
  16. package/dist/client/sessionStore.js.map +1 -1
  17. package/dist/document.d.ts +25 -0
  18. package/dist/document.d.ts.map +1 -0
  19. package/dist/document.js +88 -0
  20. package/dist/document.js.map +1 -0
  21. package/dist/host/draft.js +1 -1
  22. package/dist/host/host.d.ts +1 -0
  23. package/dist/host/host.d.ts.map +1 -1
  24. package/dist/host/host.js.map +1 -1
  25. package/dist/host/http.d.ts.map +1 -1
  26. package/dist/host/http.js +27 -9
  27. package/dist/host/http.js.map +1 -1
  28. package/dist/host/socket.d.ts +12 -5
  29. package/dist/host/socket.d.ts.map +1 -1
  30. package/dist/host/socket.js +107 -43
  31. package/dist/host/socket.js.map +1 -1
  32. package/dist/host-internal.d.ts +9 -1
  33. package/dist/host-internal.d.ts.map +1 -1
  34. package/dist/host-internal.js +1 -1
  35. package/dist/index.d.ts +4 -1
  36. package/dist/index.js +2 -1
  37. package/dist/statewire.d.ts +33 -2
  38. package/dist/statewire.d.ts.map +1 -1
  39. package/dist/statewire.js +34 -3
  40. package/dist/statewire.js.map +1 -1
  41. package/dist/transports/fixtures.js +1 -0
  42. package/dist/transports/fixtures.js.map +1 -1
  43. package/dist/transports/remote.d.ts.map +1 -1
  44. package/dist/transports/remote.js +15 -8
  45. package/dist/transports/remote.js.map +1 -1
  46. package/dist/transports/sse.d.ts.map +1 -1
  47. package/dist/transports/sse.js +8 -8
  48. package/dist/transports/sse.js.map +1 -1
  49. package/dist/transports/testing.d.ts +1 -1
  50. package/dist/transports/testing.d.ts.map +1 -1
  51. package/dist/transports/testing.js +12 -4
  52. package/dist/transports/testing.js.map +1 -1
  53. package/dist/transports/ws.js +2 -2
  54. package/dist/transports/ws.js.map +1 -1
  55. package/dist/types.d.ts +7 -0
  56. package/dist/types.d.ts.map +1 -1
  57. package/dist/types.js.map +1 -1
  58. package/dist/wire/documents.d.ts +25 -2
  59. package/dist/wire/documents.d.ts.map +1 -1
  60. package/dist/wire/documents.js +28 -8
  61. package/dist/wire/documents.js.map +1 -1
  62. package/dist/wire/meta.d.ts +3 -18
  63. package/dist/wire/meta.d.ts.map +1 -1
  64. package/dist/wire/meta.js +8 -57
  65. package/dist/wire/meta.js.map +1 -1
  66. package/dist/wire/ops.js +1 -1
  67. package/dist/wire/protocol.d.ts +55 -0
  68. package/dist/wire/protocol.d.ts.map +1 -0
  69. package/dist/wire/protocol.js +221 -0
  70. package/dist/wire/protocol.js.map +1 -0
  71. package/dist/wire/version.d.ts +1 -3
  72. package/dist/wire/version.d.ts.map +1 -1
  73. package/dist/wire/version.js +1 -3
  74. package/dist/wire/version.js.map +1 -1
  75. package/package.json +1 -1
  76. package/src/client/connection.ts +43 -13
  77. package/src/client/core.ts +6 -0
  78. package/src/client/session.ts +340 -45
  79. package/src/client/sessionStore.ts +8 -0
  80. package/src/document.ts +185 -0
  81. package/src/host/host.ts +1 -0
  82. package/src/host/http.ts +36 -7
  83. package/src/host/socket.ts +149 -57
  84. package/src/host-internal.ts +12 -1
  85. package/src/index.ts +5 -0
  86. package/src/statewire.ts +109 -6
  87. package/src/transports/fixtures.ts +1 -1
  88. package/src/transports/remote.ts +16 -7
  89. package/src/transports/sse.ts +18 -8
  90. package/src/transports/testing.ts +10 -2
  91. package/src/transports/ws.ts +1 -1
  92. package/src/types.ts +11 -0
  93. package/src/wire/documents.ts +126 -8
  94. package/src/wire/meta.ts +11 -94
  95. package/src/wire/protocol.ts +327 -0
  96. package/src/wire/version.ts +0 -2
@@ -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) s.record = stored;
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.canonical = applied.documents.get(0)?.value;
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: ctxWm + 1,
390
+ ctxNextSeq: s.record.ctxNextSeq,
354
391
  commands: [],
355
392
  ...s.record.failed !== void 0 && { failed: s.record.failed }
356
393
  });
357
- restateContext(s);
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
- seq: p.seq
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 params = command.meta === void 0 ? command.params : reviveValue([...command.params], command.meta);
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 : reviveValue([...found.params], found.meta);
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 setContext = (s, value) => {
768
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("statewire: context must be a plain object");
769
- if (!s.running || isFinished(s)) return;
770
- const { value: encoded, meta } = encodeValue(value);
771
- const ops = [{
772
- op: "replace",
773
- path: [],
774
- value: encoded,
775
- ...meta !== void 0 && { meta }
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
- const mint = () => {
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
- setRecord(s, {
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
- mint();
881
+ submit();
792
882
  wake(s);
793
883
  pump(s);
794
- return;
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
- const stored = store.load();
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
- record: stored ?? {
815
- clientId: randomId(),
816
- nextSeq: 1,
817
- ctxNextSeq: 1,
818
- commands: []
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: void 0,
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