statewire 0.19.4 → 0.21.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.
Files changed (73) hide show
  1. package/README.md +15 -1
  2. package/dist/client/connection.d.ts.map +1 -1
  3. package/dist/client/connection.js +18 -4
  4. package/dist/client/connection.js.map +1 -1
  5. package/dist/client/session.d.ts +10 -1
  6. package/dist/client/session.d.ts.map +1 -1
  7. package/dist/client/session.js +92 -27
  8. package/dist/client/session.js.map +1 -1
  9. package/dist/client/sessionStore.d.ts +2 -0
  10. package/dist/client/sessionStore.d.ts.map +1 -1
  11. package/dist/client/sessionStore.js.map +1 -1
  12. package/dist/host/host.d.ts +2 -1
  13. package/dist/host/host.d.ts.map +1 -1
  14. package/dist/host/host.js +20 -12
  15. package/dist/host/host.js.map +1 -1
  16. package/dist/host/http.d.ts.map +1 -1
  17. package/dist/host/http.js +14 -9
  18. package/dist/host/http.js.map +1 -1
  19. package/dist/host/socket.d.ts +14 -3
  20. package/dist/host/socket.d.ts.map +1 -1
  21. package/dist/host/socket.js +162 -20
  22. package/dist/host/socket.js.map +1 -1
  23. package/dist/host-internal.d.ts +5 -2
  24. package/dist/host-internal.d.ts.map +1 -1
  25. package/dist/host.d.ts +6 -0
  26. package/dist/host.d.ts.map +1 -1
  27. package/dist/index.d.ts +1 -1
  28. package/dist/statewire.d.ts +3 -0
  29. package/dist/statewire.d.ts.map +1 -1
  30. package/dist/statewire.js +23 -5
  31. package/dist/statewire.js.map +1 -1
  32. package/dist/transports/remote.d.ts.map +1 -1
  33. package/dist/transports/remote.js +3 -2
  34. package/dist/transports/remote.js.map +1 -1
  35. package/dist/transports/sse.d.ts +5 -0
  36. package/dist/transports/sse.d.ts.map +1 -1
  37. package/dist/transports/sse.js +12 -0
  38. package/dist/transports/sse.js.map +1 -1
  39. package/dist/transports/testing.d.ts +2 -0
  40. package/dist/transports/testing.d.ts.map +1 -1
  41. package/dist/transports/testing.js +11 -4
  42. package/dist/transports/testing.js.map +1 -1
  43. package/dist/types.d.ts +4 -0
  44. package/dist/types.d.ts.map +1 -1
  45. package/dist/types.js.map +1 -1
  46. package/dist/wire/context.d.ts +14 -0
  47. package/dist/wire/context.d.ts.map +1 -0
  48. package/dist/wire/context.js +31 -0
  49. package/dist/wire/context.js.map +1 -0
  50. package/dist/wire/limits.d.ts +2 -1
  51. package/dist/wire/limits.d.ts.map +1 -1
  52. package/dist/wire/limits.js +2 -1
  53. package/dist/wire/limits.js.map +1 -1
  54. package/dist/wire/protocol.d.ts +1 -1
  55. package/dist/wire/protocol.js +4 -4
  56. package/dist/wire/protocol.js.map +1 -1
  57. package/package.json +1 -1
  58. package/src/client/connection.ts +16 -4
  59. package/src/client/session.ts +128 -19
  60. package/src/client/sessionStore.ts +5 -1
  61. package/src/host/host.ts +18 -3
  62. package/src/host/http.ts +37 -5
  63. package/src/host/socket.ts +232 -13
  64. package/src/host-internal.ts +4 -0
  65. package/src/host.ts +5 -0
  66. package/src/statewire.ts +34 -3
  67. package/src/transports/remote.ts +3 -0
  68. package/src/transports/sse.ts +19 -0
  69. package/src/transports/testing.ts +14 -3
  70. package/src/types.ts +12 -0
  71. package/src/wire/context.ts +40 -0
  72. package/src/wire/limits.ts +2 -0
  73. package/src/wire/protocol.ts +1 -1
@@ -5,7 +5,7 @@ import type { StatewireFrame } from "./sse";
5
5
  import type { Statewire } from "../types";
6
6
  import { openSSEStream, sseFrame } from "./fake-sse";
7
7
  import type { FakeStreamHandle } from "./fake-sse";
8
- import { WIRE_VERSION } from "../wire/protocol";
8
+ import { WIRE_VERSION, parseNegotiation } from "../wire/protocol";
9
9
 
10
10
  export * from "./fixtures";
11
11
 
@@ -19,6 +19,7 @@ export type MockTransport<TState> = {
19
19
  transport: ResourceElement<Statewire<TState | undefined, any>>;
20
20
  /** Captured statements in arrival order. */
21
21
  commands: { protocol: string; method: string; params: unknown[] }[];
22
+ contexts: NonNullable<StatewireFrame["ctx"]>;
22
23
  /** Replace the replicated state and broadcast it as a root snapshot op. */
23
24
  setState: (state: TState) => Promise<void>;
24
25
  /** Deliver a bare ack cmd packet on the stream. */
@@ -54,6 +55,7 @@ export const createMockTransport = <TState>(
54
55
  const clients = new Map<string, Record<string, number>>();
55
56
  const streams = new Set<FakeStreamHandle>();
56
57
  const commands: MockTransport<unknown>["commands"] = [];
58
+ const contexts: MockTransport<unknown>["contexts"] = [];
57
59
 
58
60
  const sendEnvelope = (envelope: StatewireEnvelope) => {
59
61
  for (const stream of streams) stream.send(sseFrame(envelope));
@@ -62,6 +64,7 @@ export const createMockTransport = <TState>(
62
64
 
63
65
  const admit = (id: string, frame: StatewireFrame) => {
64
66
  const rec = clients.get(id)!;
67
+ contexts.push(...(frame.ctx ?? []));
65
68
  for (const statement of frame.cmd ?? []) {
66
69
  if (statement.seq <= (rec[statement.protocol] ?? 0)) continue;
67
70
  rec[statement.protocol] = statement.seq;
@@ -86,6 +89,13 @@ export const createMockTransport = <TState>(
86
89
  if (!clients.has(id)) clients.set(id, {});
87
90
  const rec = clients.get(id)!;
88
91
  const ephemeral = ephemeralLanes();
92
+ const offers = parseNegotiation(headers).applicationProtocols;
93
+ const applicationVersion =
94
+ offers === "*"
95
+ ? WIRE_VERSION
96
+ : offers.find((offer) => offer.name === protocol)?.version;
97
+ if (applicationVersion === undefined)
98
+ throw new Error("mock protocol was not offered");
89
99
  for (const lane of ephemeral) delete rec[lane];
90
100
  const frame = JSON.parse(String(init?.body ?? "{}")) as StatewireFrame;
91
101
  admit(id, frame);
@@ -101,12 +111,12 @@ export const createMockTransport = <TState>(
101
111
  protocols: [
102
112
  {
103
113
  name: protocol,
104
- version: WIRE_VERSION,
114
+ version: applicationVersion,
105
115
  seq: rec[protocol] ?? 0,
106
116
  },
107
117
  ...ephemeral.map((name) => ({
108
118
  name,
109
- version: WIRE_VERSION,
119
+ version: applicationVersion,
110
120
  record: "ephemeral" as const,
111
121
  seq: 0,
112
122
  })),
@@ -132,6 +142,7 @@ export const createMockTransport = <TState>(
132
142
  fetch: fakeFetch,
133
143
  }),
134
144
  commands,
145
+ contexts,
135
146
  setState: (next) => {
136
147
  state = next;
137
148
  return sendEnvelope({ ops: [{ op: "replace", path: [], value: next }] });
package/src/types.ts CHANGED
@@ -5,6 +5,7 @@ import type { StatewireClient } from "./statewire";
5
5
  import type { StatewirePath } from "./wire/ops";
6
6
 
7
7
  export namespace Statewire {
8
+ export type Context = Readonly<Record<string, unknown>>;
8
9
  export type Commands = Record<string, (...args: never[]) => unknown>;
9
10
 
10
11
  export type Command<TCommands extends Commands = Commands> = {
@@ -56,6 +57,17 @@ export interface Statewire<
56
57
  state: TState;
57
58
  documents?: StatewireDocuments.Source;
58
59
  selection?: StatewireProtocol.Selection | undefined;
60
+ setContext?: (protocol: string, value: Statewire.Context) => void;
61
+ validateContext?: (
62
+ protocol: string,
63
+ value: Statewire.Context,
64
+ attach: StatewireHost.Attach,
65
+ ) => void;
66
+ contextChanged?: (
67
+ protocol: string,
68
+ value: Statewire.Context,
69
+ attach: StatewireHost.Attach,
70
+ ) => void;
59
71
  invoke?: (
60
72
  protocol: string,
61
73
  method: string,
@@ -0,0 +1,40 @@
1
+ import { decodeDocument, encodeDocument } from "../document";
2
+ import type { Statewire } from "../types";
3
+ import { MAX_CONTEXT_BYTES } from "./limits";
4
+ import { protocolToken } from "./protocol";
5
+
6
+ const freeze = (value: unknown): void => {
7
+ if (typeof value !== "object" || value === null) return;
8
+ for (const child of Object.values(value)) freeze(child);
9
+ Object.freeze(value);
10
+ };
11
+
12
+ export const contextValue = (encoded: unknown): Statewire.Context => {
13
+ const value = decodeDocument(encoded);
14
+ if (
15
+ typeof value !== "object" ||
16
+ value === null ||
17
+ Array.isArray(value) ||
18
+ value instanceof Date
19
+ )
20
+ throw new TypeError("statewire: context root must be an object");
21
+ freeze(value);
22
+ return value as Statewire.Context;
23
+ };
24
+
25
+ export const encodeContext = (protocol: string, value: Statewire.Context) => {
26
+ if (!protocolToken.test(protocol))
27
+ throw new TypeError("statewire: invalid context protocol");
28
+ const encoded = encodeDocument(value);
29
+ contextValue(encoded);
30
+ const entry = { protocol, ...encoded };
31
+ if (
32
+ new TextEncoder().encode(JSON.stringify(entry)).byteLength >
33
+ MAX_CONTEXT_BYTES
34
+ )
35
+ throw new TypeError("statewire: context entry too large");
36
+ return entry;
37
+ };
38
+
39
+ export const captureContext = (value: Statewire.Context): Statewire.Context =>
40
+ contextValue(encodeDocument(value));
@@ -4,3 +4,5 @@ export const MAX_FRAME_BODY_BYTES = 8 * 1024 * 1024;
4
4
  export const MAX_EPHEMERAL_DOCUMENT_BYTES = 64 * 1024;
5
5
  export const MAX_INFLIGHT_COMMANDS_PER_HOST = 1024;
6
6
  export const COMMAND_CAPACITY_RETRY_MS = 1_000;
7
+
8
+ export const MAX_CONTEXT_BYTES = 64 * 1024;
@@ -18,7 +18,7 @@ export namespace StatewireProtocol {
18
18
  };
19
19
  }
20
20
 
21
- export const WIRE_VERSION = "2026-09-13";
21
+ export const WIRE_VERSION = "2026-09-30";
22
22
  export const DEFAULT_PROTOCOL = "default";
23
23
  export const PROTOCOL_HEADER = "Statewire-Protocol";
24
24
  export const VERSION_HEADER = "Statewire-Version";