statewire 0.0.0-0 → 0.0.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 (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -1
  3. package/dist/client/changes.d.ts +7 -0
  4. package/dist/client/changes.d.ts.map +1 -0
  5. package/dist/client/changes.js +32 -0
  6. package/dist/client/changes.js.map +1 -0
  7. package/dist/client/connection.d.ts +84 -0
  8. package/dist/client/connection.d.ts.map +1 -0
  9. package/dist/client/connection.js +204 -0
  10. package/dist/client/connection.js.map +1 -0
  11. package/dist/client/core.d.ts +18 -0
  12. package/dist/client/core.d.ts.map +1 -0
  13. package/dist/client/core.js +269 -0
  14. package/dist/client/core.js.map +1 -0
  15. package/dist/client/optimistic.d.ts +10 -0
  16. package/dist/client/optimistic.d.ts.map +1 -0
  17. package/dist/client/optimistic.js +128 -0
  18. package/dist/client/optimistic.js.map +1 -0
  19. package/dist/client/session.d.ts +88 -0
  20. package/dist/client/session.d.ts.map +1 -0
  21. package/dist/client/session.js +509 -0
  22. package/dist/client/session.js.map +1 -0
  23. package/dist/client/sessionStore.d.ts +16 -0
  24. package/dist/client/sessionStore.d.ts.map +1 -0
  25. package/dist/client/sessionStore.js +14 -0
  26. package/dist/client/sessionStore.js.map +1 -0
  27. package/dist/client/timing.d.ts +10 -0
  28. package/dist/client/timing.d.ts.map +1 -0
  29. package/dist/client/timing.js +11 -0
  30. package/dist/client/timing.js.map +1 -0
  31. package/dist/host/draft.d.ts +14 -0
  32. package/dist/host/draft.d.ts.map +1 -0
  33. package/dist/host/draft.js +124 -0
  34. package/dist/host/draft.js.map +1 -0
  35. package/dist/host/engine.d.ts +14 -0
  36. package/dist/host/engine.d.ts.map +1 -0
  37. package/dist/host/engine.js +17 -0
  38. package/dist/host/engine.js.map +1 -0
  39. package/dist/host/host.d.ts +79 -0
  40. package/dist/host/host.d.ts.map +1 -0
  41. package/dist/host/host.js +303 -0
  42. package/dist/host/host.js.map +1 -0
  43. package/dist/index.d.ts +28 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +6 -0
  46. package/dist/server.d.ts +15 -0
  47. package/dist/server.d.ts.map +1 -0
  48. package/dist/server.js +3 -0
  49. package/dist/statewire.d.ts +20 -0
  50. package/dist/statewire.d.ts.map +1 -0
  51. package/dist/statewire.js +65 -0
  52. package/dist/statewire.js.map +1 -0
  53. package/dist/transports/remote.d.ts +19 -0
  54. package/dist/transports/remote.d.ts.map +1 -0
  55. package/dist/transports/remote.js +40 -0
  56. package/dist/transports/remote.js.map +1 -0
  57. package/dist/transports/sse-parser.d.ts +5 -0
  58. package/dist/transports/sse-parser.d.ts.map +1 -0
  59. package/dist/transports/sse-parser.js +26 -0
  60. package/dist/transports/sse-parser.js.map +1 -0
  61. package/dist/transports/sse.d.ts +74 -0
  62. package/dist/transports/sse.d.ts.map +1 -0
  63. package/dist/transports/sse.js +136 -0
  64. package/dist/transports/sse.js.map +1 -0
  65. package/dist/transports/testing.d.ts +31 -0
  66. package/dist/transports/testing.d.ts.map +1 -0
  67. package/dist/transports/testing.js +121 -0
  68. package/dist/transports/testing.js.map +1 -0
  69. package/dist/transports/ws.d.ts +18 -0
  70. package/dist/transports/ws.d.ts.map +1 -0
  71. package/dist/transports/ws.js +172 -0
  72. package/dist/transports/ws.js.map +1 -0
  73. package/dist/types.d.ts +107 -0
  74. package/dist/types.d.ts.map +1 -0
  75. package/dist/types.js +24 -0
  76. package/dist/types.js.map +1 -0
  77. package/dist/wire/meta.d.ts +22 -0
  78. package/dist/wire/meta.d.ts.map +1 -0
  79. package/dist/wire/meta.js +73 -0
  80. package/dist/wire/meta.js.map +1 -0
  81. package/dist/wire/ops.d.ts +40 -0
  82. package/dist/wire/ops.d.ts.map +1 -0
  83. package/dist/wire/ops.js +125 -0
  84. package/dist/wire/ops.js.map +1 -0
  85. package/package.json +49 -6
  86. package/src/client/changes.ts +48 -0
  87. package/src/client/connection.ts +327 -0
  88. package/src/client/core.ts +346 -0
  89. package/src/client/optimistic.ts +0 -0
  90. package/src/client/session.ts +679 -0
  91. package/src/client/sessionStore.ts +23 -0
  92. package/src/client/timing.ts +8 -0
  93. package/src/host/draft.ts +155 -0
  94. package/src/host/engine.ts +13 -0
  95. package/src/host/host.ts +481 -0
  96. package/src/index.ts +65 -0
  97. package/src/server.ts +32 -0
  98. package/src/statewire.ts +107 -0
  99. package/src/transports/remote.ts +72 -0
  100. package/src/transports/sse-parser.ts +24 -0
  101. package/src/transports/sse.ts +280 -0
  102. package/src/transports/testing.ts +155 -0
  103. package/src/transports/ws.ts +272 -0
  104. package/src/types.ts +169 -0
  105. package/src/wire/meta.ts +106 -0
  106. package/src/wire/ops.ts +201 -0
  107. package/index.js +0 -1
@@ -0,0 +1,121 @@
1
+ import { recordMutations } from "../client/optimistic.js";
2
+ import { StatewireSSE } from "./sse.js";
3
+ //#region src/transports/testing.ts
4
+ /** Let queued microtasks and op batches drain through the statewire client. */
5
+ const tick = () => new Promise((r) => setTimeout(r, 0));
6
+ /** Run a mutator against a recording draft, as the client does for `optimistic`. */
7
+ const applyOptimistic = (state, mutate) => recordMutations(state, mutate).state;
8
+ /**
9
+ * A mock statewire backend: serves `initialState` as the attach snapshot,
10
+ * records command POSTs (receipt only — acks and results are delivered from
11
+ * test code), and exposes the wire as a transport ResourceElement.
12
+ */
13
+ let mockCount = 0;
14
+ const createMockTransport = (initialState) => {
15
+ let state = initialState;
16
+ let clientId = "";
17
+ let finished = false;
18
+ const clients = /* @__PURE__ */ new Map();
19
+ const streams = /* @__PURE__ */ new Set();
20
+ const commands = [];
21
+ const sendEnvelope = (envelope) => {
22
+ for (const stream of streams) stream.send(`data: ${JSON.stringify(envelope)}\n\n`);
23
+ return tick();
24
+ };
25
+ const handleStream = (id, signal) => {
26
+ clientId = id;
27
+ const lastSeq = clients.get(id) ?? 0;
28
+ clients.set(id, lastSeq);
29
+ const encoder = new TextEncoder();
30
+ let handle;
31
+ const body = new ReadableStream({
32
+ start: (controller) => {
33
+ let open = true;
34
+ handle = {
35
+ clientId: id,
36
+ send: (text) => {
37
+ if (open) controller.enqueue(encoder.encode(text));
38
+ },
39
+ close: () => {
40
+ if (!open) return;
41
+ open = false;
42
+ streams.delete(handle);
43
+ controller.close();
44
+ }
45
+ };
46
+ streams.add(handle);
47
+ handle.send(`data: ${JSON.stringify({
48
+ ops: [{
49
+ op: "replace",
50
+ path: [],
51
+ value: state
52
+ }],
53
+ syn: {
54
+ lastSeq,
55
+ lease: "mock-lease"
56
+ }
57
+ })}\n\n`);
58
+ },
59
+ cancel: () => {
60
+ streams.delete(handle);
61
+ }
62
+ });
63
+ signal?.addEventListener("abort", () => {
64
+ for (const stream of [...streams]) if (stream.clientId === id) stream.close();
65
+ });
66
+ return new Response(body, {
67
+ status: 200,
68
+ headers: { "content-type": "text/event-stream" }
69
+ });
70
+ };
71
+ const fakeFetch = (async (input, init) => {
72
+ if (finished) throw new TypeError("fetch failed (finished)");
73
+ const url = String(input);
74
+ const headers = new Headers(init?.headers);
75
+ const id = headers.get("Statewire-Client-Id") ?? "";
76
+ if (url.endsWith("/stream")) return handleStream(id, init?.signal ?? void 0);
77
+ if (url.endsWith("/commands")) {
78
+ const seq = Number(headers.get("Statewire-Command-Seq"));
79
+ clients.set(id, Math.max(clients.get(id) ?? 0, seq));
80
+ commands.push(JSON.parse(String(init?.body)));
81
+ return Response.json({});
82
+ }
83
+ return new Response(null, { status: 404 });
84
+ });
85
+ return {
86
+ transport: StatewireSSE({
87
+ url: `http://mock-${++mockCount}/api`,
88
+ fetch: fakeFetch
89
+ }),
90
+ commands,
91
+ setState: (next) => {
92
+ state = next;
93
+ return sendEnvelope({ ops: [{
94
+ op: "replace",
95
+ path: [],
96
+ value: next
97
+ }] });
98
+ },
99
+ ack: (seq) => sendEnvelope({ ack: seq }),
100
+ sendEnvelope,
101
+ finish: async (reason, message) => {
102
+ await sendEnvelope({ fin: {
103
+ reason,
104
+ ...message !== void 0 && { message }
105
+ } });
106
+ finished = true;
107
+ for (const stream of [...streams]) stream.close();
108
+ await tick();
109
+ },
110
+ get clientId() {
111
+ return clientId;
112
+ },
113
+ get attached() {
114
+ return streams.size > 0;
115
+ }
116
+ };
117
+ };
118
+ //#endregion
119
+ export { applyOptimistic, createMockTransport, tick };
120
+
121
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.js","names":["ResourceElement","StatewireEnvelope","recordMutations","StatewireSSE","StatewireTransport","tick","Promise","r","setTimeout","applyOptimistic","state","TState","mutate","draft","StreamHandle","clientId","send","text","close","MockTransport","transport","commands","method","params","setState","ack","seq","sendEnvelope","envelope","finish","reason","message","attached","mockCount","createMockTransport","initialState","finished","clients","Map","streams","Set","stream","JSON","stringify","handleStream","id","signal","AbortSignal","lastSeq","get","set","encoder","TextEncoder","handle","body","ReadableStream","Uint8Array","start","controller","open","enqueue","encode","delete","add","ops","op","path","value","syn","lease","cancel","addEventListener","Response","status","headers","fakeFetch","input","RequestInfo","URL","init","RequestInit","TypeError","url","String","Headers","endsWith","undefined","Number","Math","max","push","parse","json","fetch","next","fin","size"],"sources":["../../src/transports/testing.ts"],"sourcesContent":["import type { ResourceElement } from \"@assistant-ui/tap\";\nimport type { StatewireEnvelope } from \"../client/connection\";\nimport { recordMutations } from \"../client/optimistic\";\nimport { StatewireSSE } from \"./sse\";\nimport type { StatewireTransport } from \"../types\";\n\n/** Let queued microtasks and op batches drain through the statewire client. */\nexport const tick = () => new Promise<void>((r) => setTimeout(r, 0));\n\n/** Run a mutator against a recording draft, as the client does for `optimistic`. */\nexport const applyOptimistic = <TState>(\n state: TState,\n mutate: (draft: TState) => void,\n): TState => recordMutations(state, mutate).state;\n\ntype StreamHandle = {\n clientId: string;\n send: (text: string) => void;\n close: () => void;\n};\n\nexport type MockTransport<TState> = {\n /** Pass as the `transport:` option of useStatewire / Harness. */\n transport: ResourceElement<StatewireTransport<TState | undefined, any, any>>;\n /** Captured `{ method, params }` command bodies, in POST order. */\n commands: { method: string; params: unknown[] }[];\n /** Replace the replicated state and broadcast it as a root snapshot op. */\n setState: (state: TState) => Promise<void>;\n /** Deliver an ack watermark on the stream. */\n ack: (seq: number) => Promise<void>;\n /** Deliver an arbitrary envelope on the stream. */\n sendEnvelope: (envelope: StatewireEnvelope) => Promise<void>;\n /** End the stream with a fin; further attaches fail like a dead server. */\n finish: (reason: string, message?: string) => Promise<void>;\n readonly clientId: string;\n readonly attached: boolean;\n};\n\n/**\n * A mock statewire backend: serves `initialState` as the attach snapshot,\n * records command POSTs (receipt only — acks and results are delivered from\n * test code), and exposes the wire as a transport ResourceElement.\n */\nlet mockCount = 0;\n\nexport const createMockTransport = <TState>(\n initialState: TState,\n): MockTransport<TState> => {\n let state: unknown = initialState;\n let clientId = \"\";\n let finished = false;\n const clients = new Map<string, number>();\n const streams = new Set<StreamHandle>();\n const commands: { method: string; params: unknown[] }[] = [];\n\n const sendEnvelope = (envelope: StatewireEnvelope) => {\n for (const stream of streams) {\n stream.send(`data: ${JSON.stringify(envelope)}\\n\\n`);\n }\n return tick();\n };\n\n const handleStream = (id: string, signal: AbortSignal | undefined) => {\n clientId = id;\n const lastSeq = clients.get(id) ?? 0;\n clients.set(id, lastSeq);\n const encoder = new TextEncoder();\n let handle: StreamHandle;\n const body = new ReadableStream<Uint8Array>({\n start: (controller) => {\n let open = true;\n handle = {\n clientId: id,\n send: (text) => {\n if (open) controller.enqueue(encoder.encode(text));\n },\n close: () => {\n if (!open) return;\n open = false;\n streams.delete(handle);\n controller.close();\n },\n };\n streams.add(handle);\n handle.send(\n `data: ${JSON.stringify({\n ops: [{ op: \"replace\", path: [], value: state }],\n syn: { lastSeq, lease: \"mock-lease\" },\n })}\\n\\n`,\n );\n },\n cancel: () => {\n streams.delete(handle);\n },\n });\n signal?.addEventListener(\"abort\", () => {\n for (const stream of [...streams]) {\n if (stream.clientId === id) stream.close();\n }\n });\n return new Response(body, {\n status: 200,\n headers: { \"content-type\": \"text/event-stream\" },\n });\n };\n\n const fakeFetch = (async (\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> => {\n if (finished) throw new TypeError(\"fetch failed (finished)\");\n const url = String(input);\n const headers = new Headers(init?.headers);\n const id = headers.get(\"Statewire-Client-Id\") ?? \"\";\n if (url.endsWith(\"/stream\"))\n return handleStream(id, init?.signal ?? undefined);\n if (url.endsWith(\"/commands\")) {\n const seq = Number(headers.get(\"Statewire-Command-Seq\"));\n clients.set(id, Math.max(clients.get(id) ?? 0, seq));\n commands.push(\n JSON.parse(String(init?.body)) as { method: string; params: unknown[] },\n );\n return Response.json({});\n }\n return new Response(null, { status: 404 });\n }) as typeof fetch;\n\n return {\n transport: StatewireSSE({\n url: `http://mock-${++mockCount}/api`,\n fetch: fakeFetch,\n }),\n commands,\n setState: (next) => {\n state = next;\n return sendEnvelope({ ops: [{ op: \"replace\", path: [], value: next }] });\n },\n ack: (seq) => sendEnvelope({ ack: seq }),\n sendEnvelope,\n finish: async (reason, message) => {\n await sendEnvelope({\n fin: { reason, ...(message !== undefined && { message }) },\n });\n finished = true;\n for (const stream of [...streams]) stream.close();\n await tick();\n },\n get clientId() {\n return clientId;\n },\n get attached() {\n return streams.size > 0;\n },\n };\n};\n"],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { StatewireSessionStore } from "../client/sessionStore.js";
2
+ import { StatewireConnection } from "./sse.js";
3
+ import { StatewireCommands, StatewireTransport } from "../types.js";
4
+ import { ResourceElement } from "@assistant-ui/tap";
5
+ //#region src/transports/ws.d.ts
6
+ type StatewireWSOptions = {
7
+ url: string;
8
+ protocols?: string | string[];
9
+ query?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
10
+ webSocket?: new (url: string, protocols?: string | string[]) => WebSocket;
11
+ onChange?: (connection: StatewireConnection) => void;
12
+ sessionStore?: StatewireSessionStore;
13
+ };
14
+ declare const WS_SUBPROTOCOL = "statewire.v1";
15
+ declare const StatewireWS: <TState = any, TCommands extends StatewireCommands = StatewireCommands, TEvent = any>(options: StatewireWSOptions) => ResourceElement<StatewireTransport<TState | undefined, TCommands, TEvent>>;
16
+ //#endregion
17
+ export { StatewireWS, StatewireWSOptions, WS_SUBPROTOCOL };
18
+ //# sourceMappingURL=ws.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws.d.ts","names":[],"sources":["../../src/transports/ws.ts"],"mappings":";;;;;KAuBY;EACV;EACA;EACA,QACI,gCACO,yBAAyB,QAAQ;EAC5C,iBAAiB,aAAa,kCAAkC;EAChE,YAAY,YAAY;EACxB,eAAe;;cAuBJ;cAmNA,cACX,cACA,kBAAkB,oBAAoB,mBACtC,cAEA,SAAS,uBACN,gBAAgB,mBAAmB,oBAAoB,WAAW"}
@@ -0,0 +1,172 @@
1
+ import { encodeParams } from "../wire/meta.js";
2
+ import { timing } from "../client/timing.js";
3
+ import { createGate, handleEnvelope, overloadedError, reconnectConn } from "../client/connection.js";
4
+ import { useStatewireRemote } from "./remote.js";
5
+ import { flushTapSync, resource } from "@assistant-ui/tap";
6
+ //#region src/transports/ws.ts
7
+ const OPEN = 1;
8
+ const WS_SUBPROTOCOL = "statewire.v1";
9
+ const createConn = (latest, notify) => {
10
+ const c = {
11
+ hasSnapshot: false,
12
+ lease: null,
13
+ attempt: 0,
14
+ episode: null,
15
+ abort: null,
16
+ socket: null,
17
+ pending: [],
18
+ gate: createGate(),
19
+ lifetime: null,
20
+ latest,
21
+ notify,
22
+ reconnect: () => reconnectConn(c),
23
+ attemptOnce: (abort) => attemptSocket(c, abort),
24
+ send: (args) => sendCommand(c, args),
25
+ connection: {
26
+ status: "connecting",
27
+ reconnect: () => c.reconnect()
28
+ }
29
+ };
30
+ return c;
31
+ };
32
+ const socketUrl = async (c) => {
33
+ const { url, query } = c.latest.current.options;
34
+ const params = new URLSearchParams(typeof query === "function" ? await query() : query);
35
+ params.set("client", c.latest.current.ctx.clientId);
36
+ return `${url.replace(/\/$/, "").replace(/^http/, "ws")}/ws?${params.toString()}`;
37
+ };
38
+ const settleOverloaded = (c, { seq, retryAfterMs }) => {
39
+ if (seq === void 0) throw overloadedError(retryAfterMs);
40
+ const entry = c.pending.find((p) => p.seq === seq);
41
+ if (!entry) return;
42
+ c.pending = c.pending.filter((p) => p !== entry);
43
+ entry.reject(overloadedError(retryAfterMs));
44
+ };
45
+ const resolveReceipts = (c, ack) => {
46
+ while (c.pending[0] && c.pending[0].seq <= ack) c.pending.shift().resolve({ type: "receipt" });
47
+ };
48
+ const resolveResponded = (c, seqs) => {
49
+ const remaining = [];
50
+ for (const entry of c.pending) if (seqs.has(entry.seq)) entry.resolve({ type: "receipt" });
51
+ else remaining.push(entry);
52
+ c.pending = remaining;
53
+ };
54
+ const failPending = (c, error) => {
55
+ const pending = c.pending;
56
+ c.pending = [];
57
+ for (const entry of pending) entry.reject(error);
58
+ };
59
+ const runSocket = (c, ws, abort) => new Promise((resolve, reject) => {
60
+ let timer;
61
+ let timedOut = false;
62
+ let outcome = null;
63
+ let closed = false;
64
+ let attached = false;
65
+ const arm = () => {
66
+ clearTimeout(timer);
67
+ timer = setTimeout(() => {
68
+ timedOut = true;
69
+ ws.close();
70
+ }, timing.heartbeatTimeoutMs);
71
+ };
72
+ arm();
73
+ abort.signal.addEventListener("abort", () => ws.close());
74
+ ws.onerror = () => {};
75
+ const stale = () => Boolean(outcome) || closed || abort.signal.aborted;
76
+ const fail = (error) => {
77
+ clearTimeout(timer);
78
+ reject(error instanceof Error ? error : /* @__PURE__ */ new Error("protocol"));
79
+ ws.close();
80
+ };
81
+ const processEnv = (env) => {
82
+ const done = flushTapSync(() => handleEnvelope(c, env, attached));
83
+ attached = true;
84
+ if (env.ack !== void 0) resolveReceipts(c, env.ack);
85
+ if (env.res !== void 0) resolveResponded(c, new Set(env.res.map((r) => r.seq)));
86
+ if (done) {
87
+ outcome = done;
88
+ ws.close();
89
+ }
90
+ };
91
+ ws.onmessage = (event) => {
92
+ if (stale()) return;
93
+ arm();
94
+ let frame;
95
+ try {
96
+ frame = JSON.parse(String(event.data));
97
+ } catch {
98
+ fail(/* @__PURE__ */ new Error("protocol"));
99
+ return;
100
+ }
101
+ try {
102
+ if (frame.overloaded !== void 0) settleOverloaded(c, frame.overloaded);
103
+ else processEnv(frame);
104
+ } catch (error) {
105
+ fail(error);
106
+ }
107
+ };
108
+ ws.onclose = () => {
109
+ closed = true;
110
+ clearTimeout(timer);
111
+ if (c.socket === ws) failPending(c, /* @__PURE__ */ new Error("network"));
112
+ if (outcome) resolve(outcome);
113
+ else reject(/* @__PURE__ */ new Error(timedOut ? "timeout" : "network"));
114
+ };
115
+ });
116
+ const attemptSocket = async (c, abort) => {
117
+ let ws = null;
118
+ try {
119
+ const url = await socketUrl(c);
120
+ abort.signal.throwIfAborted();
121
+ const { protocols, webSocket } = c.latest.current.options;
122
+ const WS = webSocket ?? globalThis.WebSocket;
123
+ if (!WS) throw new Error("no WebSocket implementation");
124
+ ws = new WS(url, [WS_SUBPROTOCOL, ...protocols === void 0 ? [] : typeof protocols === "string" ? [protocols] : protocols]);
125
+ c.socket = ws;
126
+ return await runSocket(c, ws, abort);
127
+ } finally {
128
+ if (c.socket === ws) c.socket = null;
129
+ }
130
+ };
131
+ const sendCommand = (c, { seq, command }) => {
132
+ const ws = c.socket;
133
+ if (!ws || ws.readyState !== OPEN) return Promise.reject(/* @__PURE__ */ new Error("network"));
134
+ return new Promise((resolve, reject) => {
135
+ const entry = {
136
+ seq,
137
+ resolve: (result) => {
138
+ clearTimeout(timer);
139
+ resolve(result);
140
+ },
141
+ reject: (error) => {
142
+ clearTimeout(timer);
143
+ reject(error);
144
+ }
145
+ };
146
+ const timer = setTimeout(() => {
147
+ c.pending = c.pending.filter((p) => p !== entry);
148
+ reject(/* @__PURE__ */ new Error("timeout"));
149
+ ws.close();
150
+ }, timing.heartbeatTimeoutMs);
151
+ c.pending.push(entry);
152
+ try {
153
+ ws.send(JSON.stringify({
154
+ method: command.method,
155
+ ...encodeParams(command.params),
156
+ seq
157
+ }));
158
+ } catch {
159
+ clearTimeout(timer);
160
+ c.pending = c.pending.filter((p) => p !== entry);
161
+ reject(/* @__PURE__ */ new Error("network"));
162
+ }
163
+ });
164
+ };
165
+ const useStatewireWS = (options) => {
166
+ return useStatewireRemote(options, createConn);
167
+ };
168
+ const StatewireWS = resource(useStatewireWS);
169
+ //#endregion
170
+ export { StatewireWS, WS_SUBPROTOCOL };
171
+
172
+ //# sourceMappingURL=ws.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws.js","names":["flushTapSync","resource","ResourceElement","encodeParams","timing","createGate","handleEnvelope","overloadedError","reconnectConn","ConnBase","StatewireEnvelope","StatewireTransport","useStatewireRemote","StatewireSessionStore","StatewireCommands","RemoteLink","StatewireSubmitResult","StatewireConnection","StatewireTransportProps","StatewireWireCommand","StatewireWSOptions","url","protocols","query","Record","Promise","webSocket","WebSocket","onChange","connection","sessionStore","Latest","options","ctx","PendingCommand","seq","resolve","result","reject","error","Error","Conn","socket","pending","latest","current","send","OPEN","WS_SUBPROTOCOL","createConn","notify","c","hasSnapshot","lease","attempt","episode","abort","gate","lifetime","reconnect","attemptOnce","attemptSocket","args","sendCommand","status","socketUrl","params","URLSearchParams","set","clientId","base","replace","toString","settleOverloaded","retryAfterMs","undefined","entry","find","p","filter","resolveReceipts","ack","shift","type","resolveResponded","seqs","Set","remaining","has","push","failPending","runSocket","ws","AbortController","timer","ReturnType","setTimeout","timedOut","outcome","closed","attached","arm","clearTimeout","close","heartbeatTimeoutMs","signal","addEventListener","onerror","stale","Boolean","aborted","fail","processEnv","env","done","res","map","r","onmessage","event","frame","overloaded","JSON","parse","String","data","onclose","throwIfAborted","WS","globalThis","extra","command","readyState","stringify","method","useStatewireWS","StatewireWS","TState","TCommands","TEvent"],"sources":["../../src/transports/ws.ts"],"sourcesContent":["import { flushTapSync, resource } from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { encodeParams } from \"../wire/meta\";\nimport { timing } from \"../client/timing\";\nimport {\n createGate,\n handleEnvelope,\n overloadedError,\n reconnectConn,\n} from \"../client/connection\";\nimport type { ConnBase, StatewireEnvelope } from \"../client/connection\";\nimport type { StatewireTransport } from \"../types\";\nimport { useStatewireRemote } from \"./remote\";\nimport type { StatewireSessionStore } from \"../client/sessionStore\";\nimport type { StatewireCommands } from \"../types\";\nimport type {\n RemoteLink,\n StatewireSubmitResult,\n StatewireConnection,\n StatewireTransportProps,\n StatewireWireCommand,\n} from \"./sse\";\n\nexport type StatewireWSOptions = {\n url: string;\n protocols?: string | string[];\n query?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n webSocket?: new (url: string, protocols?: string | string[]) => WebSocket;\n onChange?: (connection: StatewireConnection) => void;\n sessionStore?: StatewireSessionStore;\n};\n\ntype Latest = {\n options: StatewireWSOptions;\n ctx: StatewireTransportProps;\n};\n\ntype PendingCommand = {\n seq: number;\n resolve: (result: StatewireSubmitResult) => void;\n reject: (error: Error) => void;\n};\n\ntype Conn = ConnBase & {\n socket: WebSocket | null;\n pending: PendingCommand[];\n latest: { readonly current: Latest };\n send: RemoteLink[\"send\"];\n};\n\nconst OPEN = 1;\n\nexport const WS_SUBPROTOCOL = \"statewire.v1\";\n\nconst createConn = (\n latest: { readonly current: Latest },\n notify: (connection: StatewireConnection) => void,\n): Conn => {\n const c: Conn = {\n hasSnapshot: false,\n lease: null,\n attempt: 0,\n episode: null,\n abort: null,\n socket: null,\n pending: [],\n gate: createGate(),\n lifetime: null,\n latest,\n notify,\n reconnect: () => reconnectConn(c),\n attemptOnce: (abort) => attemptSocket(c, abort),\n send: (args) => sendCommand(c, args),\n connection: { status: \"connecting\", reconnect: () => c.reconnect() },\n };\n return c;\n};\n\nconst socketUrl = async (c: Conn) => {\n const { url, query } = c.latest.current.options;\n const params = new URLSearchParams(\n typeof query === \"function\" ? await query() : query,\n );\n params.set(\"client\", c.latest.current.ctx.clientId);\n const base = url.replace(/\\/$/, \"\").replace(/^http/, \"ws\");\n return `${base}/ws?${params.toString()}`;\n};\n\nconst settleOverloaded = (\n c: Conn,\n { seq, retryAfterMs }: { seq?: number; retryAfterMs?: number },\n) => {\n if (seq === undefined) throw overloadedError(retryAfterMs);\n const entry = c.pending.find((p) => p.seq === seq);\n if (!entry) return;\n c.pending = c.pending.filter((p) => p !== entry);\n entry.reject(overloadedError(retryAfterMs));\n};\n\nconst resolveReceipts = (c: Conn, ack: number) => {\n while (c.pending[0] && c.pending[0].seq <= ack) {\n c.pending.shift()!.resolve({ type: \"receipt\" });\n }\n};\n\nconst resolveResponded = (c: Conn, seqs: Set<number>) => {\n const remaining: PendingCommand[] = [];\n for (const entry of c.pending) {\n if (seqs.has(entry.seq)) entry.resolve({ type: \"receipt\" });\n else remaining.push(entry);\n }\n c.pending = remaining;\n};\n\nconst failPending = (c: Conn, error: Error) => {\n const pending = c.pending;\n c.pending = [];\n for (const entry of pending) entry.reject(error);\n};\n\nconst runSocket = (\n c: Conn,\n ws: WebSocket,\n abort: AbortController,\n): Promise<\"stop\" | \"reattach\"> =>\n new Promise((resolve, reject) => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n let timedOut = false;\n let outcome: \"stop\" | \"reattach\" | null = null;\n let closed = false;\n let attached = false;\n const arm = () => {\n clearTimeout(timer);\n timer = setTimeout(() => {\n timedOut = true;\n ws.close();\n }, timing.heartbeatTimeoutMs);\n };\n arm();\n abort.signal.addEventListener(\"abort\", () => ws.close());\n ws.onerror = () => {};\n const stale = () => Boolean(outcome) || closed || abort.signal.aborted;\n const fail = (error: unknown) => {\n clearTimeout(timer);\n reject(error instanceof Error ? error : new Error(\"protocol\"));\n ws.close();\n };\n const processEnv = (env: StatewireEnvelope) => {\n const done = flushTapSync(() => handleEnvelope(c, env, attached));\n attached = true;\n if (env.ack !== undefined) resolveReceipts(c, env.ack);\n if (env.res !== undefined) {\n resolveResponded(c, new Set(env.res.map((r) => r.seq)));\n }\n if (done) {\n outcome = done;\n ws.close();\n }\n };\n ws.onmessage = (event) => {\n if (stale()) return;\n arm();\n let frame: StatewireEnvelope & {\n overloaded?: { seq?: number; retryAfterMs?: number };\n };\n try {\n frame = JSON.parse(String(event.data)) as typeof frame;\n } catch {\n fail(new Error(\"protocol\"));\n return;\n }\n try {\n if (frame.overloaded !== undefined) {\n settleOverloaded(c, frame.overloaded);\n } else {\n processEnv(frame);\n }\n } catch (error) {\n fail(error);\n }\n };\n ws.onclose = () => {\n closed = true;\n clearTimeout(timer);\n if (c.socket === ws) failPending(c, new Error(\"network\"));\n if (outcome) resolve(outcome);\n else reject(new Error(timedOut ? \"timeout\" : \"network\"));\n };\n });\n\nconst attemptSocket = async (\n c: Conn,\n abort: AbortController,\n): Promise<\"stop\" | \"reattach\"> => {\n let ws: WebSocket | null = null;\n try {\n const url = await socketUrl(c);\n abort.signal.throwIfAborted();\n const { protocols, webSocket } = c.latest.current.options;\n const WS = webSocket ?? globalThis.WebSocket;\n if (!WS) throw new Error(\"no WebSocket implementation\");\n const extra =\n protocols === undefined\n ? []\n : typeof protocols === \"string\"\n ? [protocols]\n : protocols;\n ws = new WS(url, [WS_SUBPROTOCOL, ...extra]);\n c.socket = ws;\n return await runSocket(c, ws, abort);\n } finally {\n if (c.socket === ws) c.socket = null;\n }\n};\n\nconst sendCommand = (\n c: Conn,\n { seq, command }: { seq: number; command: StatewireWireCommand },\n): Promise<StatewireSubmitResult> => {\n const ws = c.socket;\n if (!ws || ws.readyState !== OPEN) {\n return Promise.reject(new Error(\"network\"));\n }\n return new Promise((resolve, reject) => {\n const entry: PendingCommand = {\n seq,\n resolve: (result) => {\n clearTimeout(timer);\n resolve(result);\n },\n reject: (error) => {\n clearTimeout(timer);\n reject(error);\n },\n };\n // A timed-out command fails the socket: the retry then rides reconnect +\n // attach-seq arbitration.\n const timer = setTimeout(() => {\n c.pending = c.pending.filter((p) => p !== entry);\n reject(new Error(\"timeout\"));\n ws.close();\n }, timing.heartbeatTimeoutMs);\n c.pending.push(entry);\n try {\n ws.send(\n JSON.stringify({\n method: command.method,\n ...encodeParams(command.params),\n seq,\n }),\n );\n } catch {\n clearTimeout(timer);\n c.pending = c.pending.filter((p) => p !== entry);\n reject(new Error(\"network\"));\n }\n });\n};\n\nconst useStatewireWS = (\n options: StatewireWSOptions,\n): StatewireTransport<any, any, any> => useStatewireRemote(options, createConn);\n\nexport const StatewireWS = resource(useStatewireWS) as <\n TState = any,\n TCommands extends StatewireCommands = StatewireCommands,\n TEvent = any,\n>(\n options: StatewireWSOptions,\n) => ResourceElement<StatewireTransport<TState | undefined, TCommands, TEvent>>;\n"],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ import { StatewirePath } from "./wire/ops.js";
2
+ import { StatewireConnection } from "./transports/sse.js";
3
+ import { ResourceElement } from "@assistant-ui/tap";
4
+ //#region src/types.d.ts
5
+ type StatewireCommands = Record<string, (...args: never[]) => unknown>;
6
+ /** Push-based canonical state feed a transport hands to the client core. */
7
+ type StatewireStateSource = {
8
+ root: () => unknown;
9
+ /** Drain the paths changed since the last take (`[]` entry = whole state). */
10
+ takeChanged: () => StatewirePath[];
11
+ /** Synchronous, per change; the consumer owns coalescing. */
12
+ setOnChange: (listener: (() => void) | null) => void;
13
+ };
14
+ type StatewireTransport<TState, TCommands extends StatewireCommands = StatewireCommands, TEvent = any> = {
15
+ state: TState;
16
+ commands: StatewireCommandsProxy<TCommands>;
17
+ emit: (event: TEvent) => void;
18
+ connection?: StatewireConnection;
19
+ source?: StatewireStateSource;
20
+ };
21
+ type StatewireCommand<TCommands extends StatewireCommands = StatewireCommands> = { [K in keyof TCommands]: {
22
+ method: K;
23
+ params: Parameters<TCommands[K]>;
24
+ }; }[keyof TCommands];
25
+ type StatewireCommandsProxy<TCommands extends StatewireCommands> = { [K in keyof TCommands]: (...args: Parameters<TCommands[K]>) => Promise<Awaited<ReturnType<TCommands[K]>>>; };
26
+ type StatewireCommandUpdate<TCommands extends StatewireCommands = StatewireCommands> = { [K in keyof TCommands]: {
27
+ method: K;
28
+ params: Parameters<TCommands[K]>;
29
+ seq: number;
30
+ status: "queued" | "posted" | "acked" | "settled";
31
+ result?: unknown;
32
+ failure?: SendFailure;
33
+ }; }[keyof TCommands];
34
+ type StatewireOptimisticContext = {
35
+ ts: number;
36
+ seq: number;
37
+ clientId?: string;
38
+ };
39
+ /**
40
+ * Mutate the draft in place; the client records the next state plus the exact
41
+ * changed paths. Re-run against the new base on every rebase.
42
+ */
43
+ type StatewireOptimistic<TState, TCommands extends StatewireCommands = StatewireCommands> = (draft: TState, command: StatewireCommand<TCommands>, ctx: StatewireOptimisticContext) => void;
44
+ type StatewireChanges = {
45
+ isChangedAt(path: readonly (string | number)[]): boolean;
46
+ getChangedKeys(path: readonly (string | number)[]): string[];
47
+ };
48
+ type StatewireOptions<TState, TCommands extends StatewireCommands = StatewireCommands, TEvent = any> = {
49
+ transport: ResourceElement<StatewireTransport<TState, TCommands, TEvent>>;
50
+ optimistic?: StatewireOptimistic<NonNullable<TState>, TCommands>;
51
+ onEvent?: (event: TEvent, ctx: {
52
+ state: NonNullable<TState>;
53
+ }) => void;
54
+ onStateChange?: (state: NonNullable<TState>, ctx: {
55
+ changes: StatewireChanges;
56
+ }) => void;
57
+ onCommandChange?: (update: StatewireCommandUpdate<TCommands>, ctx: {
58
+ state: NonNullable<TState>;
59
+ }) => void;
60
+ };
61
+ type StatewireHelpers<TState, TCommands extends StatewireCommands = StatewireCommands> = {
62
+ /** The transport's state type — remote transports include `undefined` until the first attach snapshot applies. */
63
+ state: TState;
64
+ connection: StatewireConnection;
65
+ commands: StatewireCommandsProxy<TCommands>;
66
+ };
67
+ /**
68
+ * fate: "aborted" = never sent, "rejected" = the handler refused,
69
+ * "unknown-command" = no such command, "invalid-params" = the args did not
70
+ * match the command's schema, "lost" = sent but never covered by an ack,
71
+ * "crashed" = acked but the processing died, "result-unavailable" = acked
72
+ * but the result is gone.
73
+ * cause is which channel settled it: "server" = the server's answer;
74
+ * the rest are channel deaths the client settled locally.
75
+ */
76
+ type SendFailure = {
77
+ fate: "rejected";
78
+ cause: "server";
79
+ message: string;
80
+ payload?: unknown;
81
+ } | {
82
+ fate: "unknown-command";
83
+ cause: "server";
84
+ } | {
85
+ fate: "invalid-params";
86
+ cause: "server";
87
+ message: string;
88
+ } | {
89
+ fate: "crashed";
90
+ cause: "server";
91
+ } | {
92
+ fate: "result-unavailable";
93
+ cause: "server" | "unmount" | "stream-gone" | "session-lost" | "superseded";
94
+ } | {
95
+ fate: "aborted";
96
+ cause: "cascade" | "unmount" | "stream-gone" | "session-lost" | "superseded";
97
+ } | {
98
+ fate: "lost";
99
+ cause: "unmount" | "stream-gone" | "session-lost" | "superseded";
100
+ };
101
+ declare class StatewireSendError extends Error {
102
+ readonly failure: SendFailure;
103
+ constructor(failure: SendFailure);
104
+ }
105
+ //#endregion
106
+ export { SendFailure, StatewireChanges, StatewireCommand, StatewireCommandUpdate, StatewireCommands, StatewireCommandsProxy, StatewireHelpers, StatewireOptimistic, StatewireOptimisticContext, StatewireOptions, StatewireSendError, StatewireStateSource, StatewireTransport };
107
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;KAIY,oBAAoB,mBAAmB;;KAGvC;EACV;;EAEA,mBAAmB;;EAEnB,cAAc;;KAGJ,mBACV,QACA,kBAAkB,oBAAoB,mBACtC;EAEA,OAAO;EACP,UAAU,uBAAuB;EACjC,OAAO,OAAO;EACd,aAAa;EACb,SAAS;;KAGC,iBACV,kBAAkB,oBAAoB,wBAErC,WAAW;EAAc,QAAQ;EAAG,QAAQ,WAAW,UAAU;WAC5D;KAEI,uBAAuB,kBAAkB,wBAClD,WAAW,gBACP,MAAM,WAAW,UAAU,QAC3B,QAAQ,QAAQ,WAAW,UAAU;KAGhC,uBACV,kBAAkB,oBAAoB,wBAErC,WAAW;EACV,QAAQ;EACR,QAAQ,WAAW,UAAU;EAC7B;EACA;EACA;EACA,UAAU;WAEN;KAEI;EACV;EACA;EACA;;;;;;KAOU,oBACV,QACA,kBAAkB,oBAAoB,sBAEtC,OAAO,QACP,SAAS,iBAAiB,YAC1B,KAAK;KAGK;EACV,YAAY;EACZ,eAAe;;KAGL,iBACV,QACA,kBAAkB,oBAAoB,mBACtC;EAEA,WAAW,gBAAgB,mBAAmB,QAAQ,WAAW;EACjE,aAAa,oBAAoB,YAAY,SAAS;EACtD,WAAW,OAAO,QAAQ;IAAO,OAAO,YAAY;;EACpD,iBACE,OAAO,YAAY,SACnB;IAAO,SAAS;;EAElB,mBACE,QAAQ,uBAAuB,YAC/B;IAAO,OAAO,YAAY;;;KAIlB,iBACV,QACA,kBAAkB,oBAAoB;;EAGtC,OAAO;EACP,YAAY;EACZ,UAAU,uBAAuB;;;;;;;;;;;KAYvB;EACN;EAAkB;EAAiB;EAAiB;;EACpD;EAAyB;;EACzB;EAAwB;EAAiB;;EACzC;EAAiB;;EAEjB;EACA;;EAQA;EACA;;EAQA;EACA;;cAsBO,2BAA2B;WAC7B,SAAS;EAEN,YAAA,SAAS"}
package/dist/types.js ADDED
@@ -0,0 +1,24 @@
1
+ //#region src/types.ts
2
+ const describe = (failure) => {
3
+ switch (failure.fate) {
4
+ case "rejected": return `statewire: command rejected: ${failure.message}`;
5
+ case "unknown-command": return "statewire: unknown command";
6
+ case "invalid-params": return `statewire: invalid params: ${failure.message}`;
7
+ case "aborted": return `statewire: command aborted (${failure.cause})`;
8
+ case "lost": return `statewire: command lost (${failure.cause})`;
9
+ case "crashed": return "statewire: command crashed";
10
+ case "result-unavailable": return `statewire: command result unavailable (${failure.cause})`;
11
+ }
12
+ };
13
+ var StatewireSendError = class extends Error {
14
+ failure;
15
+ constructor(failure) {
16
+ super(describe(failure));
17
+ this.name = "StatewireSendError";
18
+ this.failure = failure;
19
+ }
20
+ };
21
+ //#endregion
22
+ export { StatewireSendError };
23
+
24
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","names":["ResourceElement","StatewireConnection","StatewirePath","StatewireCommands","Record","args","StatewireStateSource","root","takeChanged","setOnChange","listener","StatewireTransport","state","TState","commands","StatewireCommandsProxy","TCommands","emit","event","TEvent","connection","source","StatewireCommand","method","K","params","Parameters","Promise","Awaited","ReturnType","StatewireCommandUpdate","seq","status","result","failure","SendFailure","StatewireOptimisticContext","ts","clientId","StatewireOptimistic","draft","command","ctx","StatewireChanges","isChangedAt","path","getChangedKeys","StatewireOptions","transport","optimistic","NonNullable","onEvent","onStateChange","changes","onCommandChange","update","StatewireHelpers","fate","cause","message","payload","describe","StatewireSendError","Error","constructor","name"],"sources":["../src/types.ts"],"sourcesContent":["import type { ResourceElement } from \"@assistant-ui/tap\";\nimport type { StatewireConnection } from \"./transports/sse\";\nimport type { StatewirePath } from \"./wire/ops\";\n\nexport type StatewireCommands = Record<string, (...args: never[]) => unknown>;\n\n/** Push-based canonical state feed a transport hands to the client core. */\nexport type StatewireStateSource = {\n root: () => unknown;\n /** Drain the paths changed since the last take (`[]` entry = whole state). */\n takeChanged: () => StatewirePath[];\n /** Synchronous, per change; the consumer owns coalescing. */\n setOnChange: (listener: (() => void) | null) => void;\n};\n\nexport type StatewireTransport<\n TState,\n TCommands extends StatewireCommands = StatewireCommands,\n TEvent = any,\n> = {\n state: TState;\n commands: StatewireCommandsProxy<TCommands>;\n emit: (event: TEvent) => void;\n connection?: StatewireConnection;\n source?: StatewireStateSource;\n};\n\nexport type StatewireCommand<\n TCommands extends StatewireCommands = StatewireCommands,\n> = {\n [K in keyof TCommands]: { method: K; params: Parameters<TCommands[K]> };\n}[keyof TCommands];\n\nexport type StatewireCommandsProxy<TCommands extends StatewireCommands> = {\n [K in keyof TCommands]: (\n ...args: Parameters<TCommands[K]>\n ) => Promise<Awaited<ReturnType<TCommands[K]>>>;\n};\n\nexport type StatewireCommandUpdate<\n TCommands extends StatewireCommands = StatewireCommands,\n> = {\n [K in keyof TCommands]: {\n method: K;\n params: Parameters<TCommands[K]>;\n seq: number;\n status: \"queued\" | \"posted\" | \"acked\" | \"settled\";\n result?: unknown;\n failure?: SendFailure;\n };\n}[keyof TCommands];\n\nexport type StatewireOptimisticContext = {\n ts: number;\n seq: number;\n clientId?: string;\n};\n\n/**\n * Mutate the draft in place; the client records the next state plus the exact\n * changed paths. Re-run against the new base on every rebase.\n */\nexport type StatewireOptimistic<\n TState,\n TCommands extends StatewireCommands = StatewireCommands,\n> = (\n draft: TState,\n command: StatewireCommand<TCommands>,\n ctx: StatewireOptimisticContext,\n) => void;\n\nexport type StatewireChanges = {\n isChangedAt(path: readonly (string | number)[]): boolean;\n getChangedKeys(path: readonly (string | number)[]): string[];\n};\n\nexport type StatewireOptions<\n TState,\n TCommands extends StatewireCommands = StatewireCommands,\n TEvent = any,\n> = {\n transport: ResourceElement<StatewireTransport<TState, TCommands, TEvent>>;\n optimistic?: StatewireOptimistic<NonNullable<TState>, TCommands>;\n onEvent?: (event: TEvent, ctx: { state: NonNullable<TState> }) => void;\n onStateChange?: (\n state: NonNullable<TState>,\n ctx: { changes: StatewireChanges },\n ) => void;\n onCommandChange?: (\n update: StatewireCommandUpdate<TCommands>,\n ctx: { state: NonNullable<TState> },\n ) => void;\n};\n\nexport type StatewireHelpers<\n TState,\n TCommands extends StatewireCommands = StatewireCommands,\n> = {\n /** The transport's state type — remote transports include `undefined` until the first attach snapshot applies. */\n state: TState;\n connection: StatewireConnection;\n commands: StatewireCommandsProxy<TCommands>;\n};\n\n/**\n * fate: \"aborted\" = never sent, \"rejected\" = the handler refused,\n * \"unknown-command\" = no such command, \"invalid-params\" = the args did not\n * match the command's schema, \"lost\" = sent but never covered by an ack,\n * \"crashed\" = acked but the processing died, \"result-unavailable\" = acked\n * but the result is gone.\n * cause is which channel settled it: \"server\" = the server's answer;\n * the rest are channel deaths the client settled locally.\n */\nexport type SendFailure =\n | { fate: \"rejected\"; cause: \"server\"; message: string; payload?: unknown }\n | { fate: \"unknown-command\"; cause: \"server\" }\n | { fate: \"invalid-params\"; cause: \"server\"; message: string }\n | { fate: \"crashed\"; cause: \"server\" }\n | {\n fate: \"result-unavailable\";\n cause:\n | \"server\"\n | \"unmount\"\n | \"stream-gone\"\n | \"session-lost\"\n | \"superseded\";\n }\n | {\n fate: \"aborted\";\n cause:\n | \"cascade\"\n | \"unmount\"\n | \"stream-gone\"\n | \"session-lost\"\n | \"superseded\";\n }\n | {\n fate: \"lost\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\" | \"superseded\";\n };\n\nconst describe = (failure: SendFailure): string => {\n switch (failure.fate) {\n case \"rejected\":\n return `statewire: command rejected: ${failure.message}`;\n case \"unknown-command\":\n return \"statewire: unknown command\";\n case \"invalid-params\":\n return `statewire: invalid params: ${failure.message}`;\n case \"aborted\":\n return `statewire: command aborted (${failure.cause})`;\n case \"lost\":\n return `statewire: command lost (${failure.cause})`;\n case \"crashed\":\n return \"statewire: command crashed\";\n case \"result-unavailable\":\n return `statewire: command result unavailable (${failure.cause})`;\n }\n};\n\nexport class StatewireSendError extends Error {\n readonly failure: SendFailure;\n\n constructor(failure: SendFailure) {\n super(describe(failure));\n this.name = \"StatewireSendError\";\n this.failure = failure;\n }\n}\n"],"mappings":";AA6IA,MAAM6D,YAAY3B,YAAiC;CACjD,QAAQA,QAAQuB,MAAhB;EACE,KAAK,YACH,OAAO,gCAAgCvB,QAAQyB;EACjD,KAAK,mBACH,OAAO;EACT,KAAK,kBACH,OAAO,8BAA8BzB,QAAQyB;EAC/C,KAAK,WACH,OAAO,+BAA+BzB,QAAQwB,MAAK;EACrD,KAAK,QACH,OAAO,4BAA4BxB,QAAQwB,MAAK;EAClD,KAAK,WACH,OAAO;EACT,KAAK,sBACH,OAAO,0CAA0CxB,QAAQwB,MAAK;CAClE;AACF;AAEA,IAAaI,qBAAb,cAAwCC,MAAM;CAC5C;CAEAC,YAAY9B,SAAsB;EAChC,MAAM2B,SAAS3B,OAAO,CAAC;EACvB,KAAK+B,OAAO;EACZ,KAAK/B,UAAUA;CACjB;AACF"}
@@ -0,0 +1,22 @@
1
+ //#region src/wire/meta.d.ts
2
+ type StatewireMetaEntry = {
3
+ path: (string | number)[];
4
+ type: string;
5
+ };
6
+ /** Revive meta-typed locations inside a wire value (in place). Unknown types
7
+ * are skipped; a path that does not resolve to a valid target faults. */
8
+ declare const reviveValue: (value: unknown, meta: unknown) => unknown;
9
+ declare const reviveOps: (ops: unknown[]) => unknown[];
10
+ declare const reviveResponse: <T extends {
11
+ payload?: unknown;
12
+ meta?: unknown;
13
+ }>(response: T) => T;
14
+ /** Encode Date instances inside command params as canonical ISO strings,
15
+ * recording their paths (starting at the param index) in meta. */
16
+ declare const encodeParams: (params: readonly unknown[]) => {
17
+ params: unknown[];
18
+ meta?: StatewireMetaEntry[];
19
+ };
20
+ //#endregion
21
+ export { StatewireMetaEntry, encodeParams, reviveOps, reviveResponse, reviveValue };
22
+ //# sourceMappingURL=meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meta.d.ts","names":[],"sources":["../../src/wire/meta.ts"],"mappings":";KAAY;EACV;EACA;;;;cAiBW,cAAe,gBAAgB;cA6C/B,YAAa;cAOb,iBAAkB;EAAY;EAAmB;GAC5D,UAAU,MACT;;;cAQU,eACX;EACG;EAAmB,OAAO"}
@@ -0,0 +1,73 @@
1
+ //#region src/wire/meta.ts
2
+ const fault = (message) => {
3
+ throw new Error(`statewire: protocol error: ${message}`);
4
+ };
5
+ const isPath = (path) => Array.isArray(path) && path.every((seg) => typeof seg === "string" || typeof seg === "number" && Number.isInteger(seg));
6
+ /** Revive meta-typed locations inside a wire value (in place). Unknown types
7
+ * are skipped; a path that does not resolve to a valid target faults. */
8
+ const reviveValue = (value, meta) => {
9
+ if (!Array.isArray(meta)) return fault("meta must be an array");
10
+ for (const entry of meta) {
11
+ const path = typeof entry === "object" && entry !== null ? entry.path : null;
12
+ if (!isPath(path)) return fault("meta entry must carry a path array");
13
+ if (entry.type !== "date") continue;
14
+ let parent = null;
15
+ let key = "";
16
+ let node = value;
17
+ for (const seg of path) {
18
+ parent = node;
19
+ key = seg;
20
+ if (typeof seg === "string" && typeof node === "object" && node !== null && !Array.isArray(node) && Object.hasOwn(node, seg)) node = node[seg];
21
+ else if (typeof seg === "number" && Array.isArray(node) && seg >= 0 && seg < node.length) node = node[seg];
22
+ else return fault(`meta path ${path.join(".")} does not resolve`);
23
+ }
24
+ if (typeof node !== "string") return fault(`meta path ${path.join(".")} must name a string`);
25
+ const date = new Date(node);
26
+ if (Number.isNaN(date.getTime())) return fault(`invalid date at meta path ${path.join(".")}`);
27
+ if (parent === null) return date;
28
+ parent[key] = date;
29
+ }
30
+ return value;
31
+ };
32
+ const reviveOps = (ops) => ops.map((op) => {
33
+ if (typeof op !== "object" || op === null || !("meta" in op)) return op;
34
+ const { meta, ...rest } = op;
35
+ return {
36
+ ...rest,
37
+ value: reviveValue(rest.value, meta)
38
+ };
39
+ });
40
+ const reviveResponse = (response) => {
41
+ if (response.meta === void 0) return response;
42
+ const { meta, ...rest } = response;
43
+ return {
44
+ ...rest,
45
+ payload: reviveValue(rest.payload, meta)
46
+ };
47
+ };
48
+ /** Encode Date instances inside command params as canonical ISO strings,
49
+ * recording their paths (starting at the param index) in meta. */
50
+ const encodeParams = (params) => {
51
+ const meta = [];
52
+ const walk = (node, path) => {
53
+ if (node instanceof Date) {
54
+ meta.push({
55
+ path,
56
+ type: "date"
57
+ });
58
+ return node.toISOString();
59
+ }
60
+ if (Array.isArray(node)) return node.map((item, i) => walk(item, [...path, i]));
61
+ if (typeof node === "object" && node !== null) return Object.fromEntries(Object.keys(node).map((key) => [key, walk(node[key], [...path, key])]));
62
+ return node;
63
+ };
64
+ const encoded = params.map((param, i) => walk(param, [i]));
65
+ return meta.length > 0 ? {
66
+ params: encoded,
67
+ meta
68
+ } : { params: encoded };
69
+ };
70
+ //#endregion
71
+ export { encodeParams, reviveOps, reviveResponse, reviveValue };
72
+
73
+ //# sourceMappingURL=meta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meta.js","names":["StatewireMetaEntry","path","type","fault","message","Error","isPath","Array","isArray","every","seg","Number","isInteger","reviveValue","value","meta","entry","parent","key","node","Object","hasOwn","Record","length","join","date","Date","isNaN","getTime","reviveOps","ops","map","op","rest","reviveResponse","payload","response","T","undefined","encodeParams","params","walk","push","toISOString","item","i","fromEntries","keys","encoded","param"],"sources":["../../src/wire/meta.ts"],"sourcesContent":["export type StatewireMetaEntry = {\n path: (string | number)[];\n type: string;\n};\n\nconst fault = (message: string): never => {\n throw new Error(`statewire: protocol error: ${message}`);\n};\n\nconst isPath = (path: unknown): path is (string | number)[] =>\n Array.isArray(path) &&\n path.every(\n (seg) =>\n typeof seg === \"string\" ||\n (typeof seg === \"number\" && Number.isInteger(seg)),\n );\n\n/** Revive meta-typed locations inside a wire value (in place). Unknown types\n * are skipped; a path that does not resolve to a valid target faults. */\nexport const reviveValue = (value: unknown, meta: unknown): unknown => {\n if (!Array.isArray(meta)) return fault(\"meta must be an array\");\n for (const entry of meta as { path?: unknown; type?: unknown }[]) {\n const path =\n typeof entry === \"object\" && entry !== null ? entry.path : null;\n if (!isPath(path)) return fault(\"meta entry must carry a path array\");\n if (entry.type !== \"date\") continue;\n let parent: unknown = null;\n let key: string | number = \"\";\n let node = value;\n for (const seg of path) {\n parent = node;\n key = seg;\n if (\n typeof seg === \"string\" &&\n typeof node === \"object\" &&\n node !== null &&\n !Array.isArray(node) &&\n Object.hasOwn(node, seg)\n ) {\n node = (node as Record<string, unknown>)[seg];\n } else if (\n typeof seg === \"number\" &&\n Array.isArray(node) &&\n seg >= 0 &&\n seg < node.length\n ) {\n node = node[seg];\n } else {\n return fault(`meta path ${path.join(\".\")} does not resolve`);\n }\n }\n if (typeof node !== \"string\") {\n return fault(`meta path ${path.join(\".\")} must name a string`);\n }\n const date = new Date(node);\n if (Number.isNaN(date.getTime())) {\n return fault(`invalid date at meta path ${path.join(\".\")}`);\n }\n if (parent === null) return date;\n (parent as Record<string | number, unknown>)[key] = date;\n }\n return value;\n};\n\nexport const reviveOps = (ops: unknown[]): unknown[] =>\n ops.map((op) => {\n if (typeof op !== \"object\" || op === null || !(\"meta\" in op)) return op;\n const { meta, ...rest } = op as { meta: unknown; value?: unknown };\n return { ...rest, value: reviveValue(rest.value, meta) };\n });\n\nexport const reviveResponse = <T extends { payload?: unknown; meta?: unknown }>(\n response: T,\n): T => {\n if (response.meta === undefined) return response;\n const { meta, ...rest } = response;\n return { ...rest, payload: reviveValue(rest.payload, meta) } as T;\n};\n\n/** Encode Date instances inside command params as canonical ISO strings,\n * recording their paths (starting at the param index) in meta. */\nexport const encodeParams = (\n params: readonly unknown[],\n): { params: unknown[]; meta?: StatewireMetaEntry[] } => {\n const meta: StatewireMetaEntry[] = [];\n const walk = (node: unknown, path: (string | number)[]): unknown => {\n if (node instanceof Date) {\n meta.push({ path, type: \"date\" });\n return node.toISOString();\n }\n if (Array.isArray(node)) {\n return node.map((item, i) => walk(item, [...path, i]));\n }\n if (typeof node === \"object\" && node !== null) {\n return Object.fromEntries(\n Object.keys(node).map((key) => [\n key,\n walk((node as Record<string, unknown>)[key], [...path, key]),\n ]),\n );\n }\n return node;\n };\n const encoded = params.map((param, i) => walk(param, [i]));\n return meta.length > 0 ? { params: encoded, meta } : { params: encoded };\n};\n"],"mappings":"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"}