statewire 0.0.1 → 0.0.3

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 (148) hide show
  1. package/README.md +14 -13
  2. package/dist/client/changes.d.ts +2 -2
  3. package/dist/client/changes.d.ts.map +1 -1
  4. package/dist/client/changes.js +3 -10
  5. package/dist/client/changes.js.map +1 -1
  6. package/dist/client/connection.d.ts +18 -39
  7. package/dist/client/connection.d.ts.map +1 -1
  8. package/dist/client/connection.js +61 -54
  9. package/dist/client/connection.js.map +1 -1
  10. package/dist/client/core.d.ts +7 -11
  11. package/dist/client/core.d.ts.map +1 -1
  12. package/dist/client/core.js +58 -55
  13. package/dist/client/core.js.map +1 -1
  14. package/dist/client/session.d.ts +12 -26
  15. package/dist/client/session.d.ts.map +1 -1
  16. package/dist/client/session.js +70 -43
  17. package/dist/client/session.js.map +1 -1
  18. package/dist/client/sessionStore.d.ts +1 -1
  19. package/dist/client/sessionStore.js.map +1 -1
  20. package/dist/client/timing.d.ts +2 -1
  21. package/dist/client/timing.d.ts.map +1 -1
  22. package/dist/client/timing.js +5 -1
  23. package/dist/client/timing.js.map +1 -1
  24. package/dist/host/adapter.d.ts +8 -0
  25. package/dist/host/adapter.d.ts.map +1 -0
  26. package/dist/host/adapter.js +23 -0
  27. package/dist/host/adapter.js.map +1 -0
  28. package/dist/host/draft.d.ts +21 -7
  29. package/dist/host/draft.d.ts.map +1 -1
  30. package/dist/host/draft.js +190 -103
  31. package/dist/host/draft.js.map +1 -1
  32. package/dist/host/host.d.ts +19 -35
  33. package/dist/host/host.d.ts.map +1 -1
  34. package/dist/host/host.js +86 -54
  35. package/dist/host/host.js.map +1 -1
  36. package/dist/host/socket.d.ts +14 -0
  37. package/dist/host/socket.d.ts.map +1 -0
  38. package/dist/host/socket.js +511 -0
  39. package/dist/host/socket.js.map +1 -0
  40. package/dist/host-internal.d.ts +27 -0
  41. package/dist/host-internal.d.ts.map +1 -0
  42. package/dist/host-internal.js +3 -0
  43. package/dist/host.d.ts +54 -0
  44. package/dist/host.d.ts.map +1 -0
  45. package/dist/host.js +4 -0
  46. package/dist/index.d.ts +5 -28
  47. package/dist/index.js +2 -3
  48. package/dist/statewire.d.ts +97 -10
  49. package/dist/statewire.d.ts.map +1 -1
  50. package/dist/statewire.js +32 -36
  51. package/dist/statewire.js.map +1 -1
  52. package/dist/transports/fake-sse.d.ts +12 -0
  53. package/dist/transports/fake-sse.d.ts.map +1 -0
  54. package/dist/transports/fake-sse.js +39 -0
  55. package/dist/transports/fake-sse.js.map +1 -0
  56. package/dist/transports/fixtures.d.ts +33 -0
  57. package/dist/transports/fixtures.d.ts.map +1 -0
  58. package/dist/transports/fixtures.js +50 -0
  59. package/dist/transports/fixtures.js.map +1 -0
  60. package/dist/transports/remote.d.ts +6 -13
  61. package/dist/transports/remote.d.ts.map +1 -1
  62. package/dist/transports/remote.js +7 -25
  63. package/dist/transports/remote.js.map +1 -1
  64. package/dist/transports/sse.d.ts +32 -20
  65. package/dist/transports/sse.d.ts.map +1 -1
  66. package/dist/transports/sse.js +57 -46
  67. package/dist/transports/sse.js.map +1 -1
  68. package/dist/transports/testing.d.ts +8 -6
  69. package/dist/transports/testing.d.ts.map +1 -1
  70. package/dist/transports/testing.js +19 -46
  71. package/dist/transports/testing.js.map +1 -1
  72. package/dist/transports/ws.d.ts +4 -13
  73. package/dist/transports/ws.d.ts.map +1 -1
  74. package/dist/transports/ws.js +59 -63
  75. package/dist/transports/ws.js.map +1 -1
  76. package/dist/types.d.ts +55 -100
  77. package/dist/types.d.ts.map +1 -1
  78. package/dist/types.js.map +1 -1
  79. package/dist/util/draftTraps.d.ts +6 -0
  80. package/dist/util/draftTraps.d.ts.map +1 -0
  81. package/dist/util/draftTraps.js +25 -0
  82. package/dist/util/draftTraps.js.map +1 -0
  83. package/dist/util/macrotask.d.ts +5 -0
  84. package/dist/util/macrotask.d.ts.map +1 -0
  85. package/dist/util/macrotask.js +25 -0
  86. package/dist/util/macrotask.js.map +1 -0
  87. package/dist/util/methodProxy.d.ts +5 -0
  88. package/dist/util/methodProxy.d.ts.map +1 -0
  89. package/dist/util/methodProxy.js +17 -0
  90. package/dist/util/methodProxy.js.map +1 -0
  91. package/dist/util/randomId.d.ts +5 -0
  92. package/dist/util/randomId.d.ts.map +1 -0
  93. package/dist/util/randomId.js +6 -0
  94. package/dist/util/randomId.js.map +1 -0
  95. package/dist/{client/optimistic.d.ts → util/recordMutations.d.ts} +2 -2
  96. package/dist/util/recordMutations.d.ts.map +1 -0
  97. package/dist/{client/optimistic.js → util/recordMutations.js} +5 -24
  98. package/dist/util/recordMutations.js.map +1 -0
  99. package/dist/util/statePath.d.ts +14 -0
  100. package/dist/util/statePath.d.ts.map +1 -0
  101. package/dist/util/statePath.js +31 -0
  102. package/dist/util/statePath.js.map +1 -0
  103. package/dist/wire/meta.d.ts +6 -1
  104. package/dist/wire/meta.d.ts.map +1 -1
  105. package/dist/wire/meta.js +30 -28
  106. package/dist/wire/meta.js.map +1 -1
  107. package/dist/wire/ops.d.ts +5 -26
  108. package/dist/wire/ops.d.ts.map +1 -1
  109. package/dist/wire/ops.js +67 -80
  110. package/dist/wire/ops.js.map +1 -1
  111. package/package.json +15 -11
  112. package/src/client/changes.ts +5 -15
  113. package/src/client/connection.ts +87 -113
  114. package/src/client/core.ts +57 -91
  115. package/src/client/session.ts +129 -90
  116. package/src/client/sessionStore.ts +1 -1
  117. package/src/client/timing.ts +6 -0
  118. package/src/host/adapter.ts +33 -0
  119. package/src/host/draft.ts +293 -125
  120. package/src/host/host.ts +122 -110
  121. package/src/host/socket.ts +618 -0
  122. package/src/host-internal.ts +28 -0
  123. package/src/host.ts +72 -0
  124. package/src/index.ts +2 -62
  125. package/src/statewire.ts +168 -61
  126. package/src/transports/fake-sse.ts +51 -0
  127. package/src/transports/fixtures.ts +60 -0
  128. package/src/transports/remote.ts +14 -51
  129. package/src/transports/sse.ts +87 -89
  130. package/src/transports/testing.ts +33 -63
  131. package/src/transports/ws.ts +78 -100
  132. package/src/types.ts +52 -131
  133. package/src/util/draftTraps.ts +32 -0
  134. package/src/util/macrotask.ts +25 -0
  135. package/src/util/methodProxy.ts +16 -0
  136. package/src/util/randomId.ts +4 -0
  137. package/src/{client/optimistic.ts → util/recordMutations.ts} +0 -0
  138. package/src/util/statePath.ts +61 -0
  139. package/src/wire/meta.ts +42 -47
  140. package/src/wire/ops.ts +107 -106
  141. package/LICENSE +0 -21
  142. package/dist/client/optimistic.d.ts.map +0 -1
  143. package/dist/client/optimistic.js.map +0 -1
  144. package/dist/index.d.ts.map +0 -1
  145. package/dist/server.d.ts +0 -15
  146. package/dist/server.d.ts.map +0 -1
  147. package/dist/server.js +0 -3
  148. package/src/server.ts +0 -32
@@ -1,7 +1,11 @@
1
- import { createChanges } from "./changes.js";
2
- import { recordMutations } from "./optimistic.js";
1
+ import { recordMutations } from "../util/recordMutations.js";
2
+ import { createMethodProxy } from "../util/methodProxy.js";
3
3
  import { StatewireSendError } from "../types.js";
4
4
  import { getCommandsHost, getEmitHost, getStateHost } from "../host/host.js";
5
+ import { createMacrotaskPoster } from "../util/macrotask.js";
6
+ import { createChanges } from "./changes.js";
7
+ import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
8
+ import { useEffect, useState } from "@assistant-ui/tap/react-shim";
5
9
  //#region src/client/core.ts
6
10
  const LIVE_CONNECTION = {
7
11
  status: "live",
@@ -12,27 +16,7 @@ const failureOf = (error) => error instanceof StatewireSendError ? error.failure
12
16
  cause: "server",
13
17
  message: error instanceof Error ? error.message : String(error)
14
18
  };
15
- const createMacrotaskPoster = (callback) => {
16
- if (typeof MessageChannel !== "undefined") {
17
- let port1 = null;
18
- let port2;
19
- return () => {
20
- if (!port1) {
21
- const channel = new MessageChannel();
22
- channel.port1.onmessage = () => {
23
- port1?.unref?.();
24
- callback();
25
- };
26
- port1 = channel.port1;
27
- port2 = channel.port2;
28
- }
29
- port1.ref?.();
30
- port2.postMessage(null);
31
- };
32
- }
33
- return () => setTimeout(callback, 0);
34
- };
35
- const createStatewireCore = (getOptions) => {
19
+ const createCore = (getOptions) => {
36
20
  let value = null;
37
21
  let source = null;
38
22
  let overlays = [];
@@ -48,7 +32,6 @@ const createStatewireCore = (getOptions) => {
48
32
  let stale = false;
49
33
  let overlaysDirty = false;
50
34
  let flushing = false;
51
- let disposed = false;
52
35
  const listeners = /* @__PURE__ */ new Set();
53
36
  const post = createMacrotaskPoster(() => {
54
37
  scheduled = false;
@@ -56,7 +39,7 @@ const createStatewireCore = (getOptions) => {
56
39
  });
57
40
  let scheduled = false;
58
41
  const schedule = () => {
59
- if (scheduled || disposed) return;
42
+ if (scheduled) return;
60
43
  scheduled = true;
61
44
  post();
62
45
  };
@@ -67,7 +50,7 @@ const createStatewireCore = (getOptions) => {
67
50
  const drainCanonical = () => {
68
51
  if (source) {
69
52
  pendingChanged.push(...source.takeChanged());
70
- canonical = source.root();
53
+ canonical = source.snapshot();
71
54
  } else if (value && value.state !== canonical) {
72
55
  pendingChanged.push([]);
73
56
  canonical = value.state;
@@ -108,7 +91,7 @@ const createStatewireCore = (getOptions) => {
108
91
  };
109
92
  const getConnection = () => value?.connection ?? LIVE_CONNECTION;
110
93
  const flush = () => {
111
- if (disposed || !value) return;
94
+ if (!value) return;
112
95
  if (flushing) {
113
96
  schedule();
114
97
  return;
@@ -135,7 +118,7 @@ const createStatewireCore = (getOptions) => {
135
118
  updates = [];
136
119
  for (const update of batch) options.onCommandChange?.(update, { state: published });
137
120
  }
138
- if (events.length > 0) {
121
+ if (events.length > 0 && published !== void 0) {
139
122
  const batch = events;
140
123
  events = [];
141
124
  for (const event of batch) options.onEvent?.(event, { state: published });
@@ -188,7 +171,7 @@ const createStatewireCore = (getOptions) => {
188
171
  schedule();
189
172
  };
190
173
  const attach = (next) => {
191
- if (disposed || next === value) return;
174
+ if (next === value) return;
192
175
  const prev = value;
193
176
  value = next;
194
177
  const nextSource = next.source ?? (typeof next.state === "object" && next.state !== null ? getStateHost(next.state) : void 0) ?? null;
@@ -223,38 +206,23 @@ const createStatewireCore = (getOptions) => {
223
206
  value = null;
224
207
  }
225
208
  };
226
- const memo = /* @__PURE__ */ new Map();
227
209
  return {
228
210
  attach,
229
211
  detach,
230
- dispose: () => {
231
- detach();
232
- disposed = true;
233
- },
234
212
  getState: () => {
235
213
  recompute();
236
214
  return published;
237
215
  },
238
- getConnection,
239
- commands: new Proxy({}, { get: (_target, method) => {
240
- if (typeof method !== "string") return void 0;
241
- let fn = memo.get(method);
242
- if (fn === void 0) {
243
- fn = (...params) => {
244
- if (!value) return Promise.reject(new StatewireSendError({
245
- fate: "aborted",
246
- cause: "unmount"
247
- }));
248
- const send = value.commands[method];
249
- const promise = send(...params);
250
- flush();
251
- return promise;
252
- };
253
- memo.set(method, fn);
254
- }
255
- return fn;
256
- } }),
257
- flush,
216
+ commands: createMethodProxy((method, params) => {
217
+ if (!value) return Promise.reject(new StatewireSendError({
218
+ fate: "aborted",
219
+ cause: "unmount"
220
+ }));
221
+ const send = value.commands[method];
222
+ const promise = send(...params);
223
+ flush();
224
+ return promise;
225
+ }),
258
226
  subscribe: (listener) => {
259
227
  listeners.add(listener);
260
228
  return () => {
@@ -263,7 +231,42 @@ const createStatewireCore = (getOptions) => {
263
231
  }
264
232
  };
265
233
  };
234
+ const useStatewireCore = (getOptions, value) => {
235
+ const $ = c(8);
236
+ let t0;
237
+ if ($[0] !== getOptions) {
238
+ t0 = () => createCore(getOptions);
239
+ $[0] = getOptions;
240
+ $[1] = t0;
241
+ } else t0 = $[1];
242
+ const [core] = useState(t0);
243
+ core.attach(value);
244
+ let t1;
245
+ if ($[2] !== core || $[3] !== value) {
246
+ t1 = () => {
247
+ core.attach(value);
248
+ };
249
+ $[2] = core;
250
+ $[3] = value;
251
+ $[4] = t1;
252
+ } else t1 = $[4];
253
+ useEffect(t1);
254
+ let t2;
255
+ let t3;
256
+ if ($[5] !== core) {
257
+ t2 = () => () => core.detach();
258
+ t3 = [core];
259
+ $[5] = core;
260
+ $[6] = t2;
261
+ $[7] = t3;
262
+ } else {
263
+ t2 = $[6];
264
+ t3 = $[7];
265
+ }
266
+ useEffect(t2, t3);
267
+ return core;
268
+ };
266
269
  //#endregion
267
- export { LIVE_CONNECTION, createStatewireCore };
270
+ export { LIVE_CONNECTION, useStatewireCore };
268
271
 
269
272
  //# sourceMappingURL=core.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"core.js","names":["createChanges","getCommandsHost","getEmitHost","getStateHost","CommandsInvocation","CommandsListener","recordMutations","StatewirePath","StatewireSendError","SendFailure","StatewireSendFailure","StatewireStateSource","StatewireTransport","StatewireCommand","StatewireCommandsProxy","StatewireCommandUpdate","StatewireOptimisticContext","StatewireOptions","StatewireConnection","LIVE_CONNECTION","status","reconnect","failureOf","error","failure","fate","cause","message","Error","String","createMacrotaskPoster","callback","MessageChannel","port1","MessagePort","ref","unref","port2","channel","onmessage","postMessage","setTimeout","Overlay","inv","dropped","StatewireCore","ReturnType","createStatewireCore","getOptions","value","source","overlays","updates","events","pendingChanged","canonical","published","overlayPaths","notifiedPublished","notifiedOverlayPaths","notifiedConnection","stale","overlaysDirty","flushing","disposed","listeners","Set","post","scheduled","flush","schedule","markStale","drainCanonical","push","takeChanged","root","state","recompute","prevCanonical","optimistic","next","paths","undefined","overlay","ctx","ts","seq","clientId","recorded","draft","method","params","changedPaths","console","getConnection","connection","options","stateChanged","prev","changed","onStateChange","changes","length","batch","update","onCommandChange","event","onEvent","connectionChanged","listener","note","extra","result","commandsListener","onStart","onPosted","onAck","onCovered","some","o","filter","onSettle","outcome","type","payload","eventSink","attach","nextSource","setOnChange","commands","setListener","emit","setSink","detach","memo","Map","Promise","Proxy","get","_target","fn","reject","send","Record","args","promise","set","dispose","getState","subscribe","add","delete"],"sources":["../../src/client/core.ts"],"sourcesContent":["import { createChanges } from \"./changes\";\nimport { getCommandsHost, getEmitHost, getStateHost } from \"../host/host\";\nimport type { CommandsInvocation, CommandsListener } from \"../host/host\";\nimport { recordMutations } from \"./optimistic\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport { StatewireSendError } from \"../types\";\nimport type {\n SendFailure as StatewireSendFailure,\n StatewireStateSource,\n StatewireTransport,\n StatewireCommand,\n StatewireCommandsProxy,\n StatewireCommandUpdate,\n StatewireOptimisticContext,\n StatewireOptions,\n} from \"../types\";\nimport type { StatewireConnection } from \"../transports/sse\";\n\nexport const LIVE_CONNECTION: StatewireConnection = {\n status: \"live\",\n reconnect: () => {},\n};\n\nconst failureOf = (error: unknown): StatewireSendFailure =>\n error instanceof StatewireSendError\n ? error.failure\n : {\n fate: \"rejected\",\n cause: \"server\",\n message: error instanceof Error ? error.message : String(error),\n };\n\n// MessageChannel schedules the flush as a macrotask (tap's pattern): it runs\n// after the whole microtask drain, so every producer in the current turn\n// coalesces. The port is ref'd only while a flush is pending — an active\n// MessagePort holds the Node event loop open. ref/unref are Node-only.\nconst createMacrotaskPoster = (callback: () => void): (() => void) => {\n if (typeof MessageChannel !== \"undefined\") {\n let port1: (MessagePort & { ref?: () => void; unref?: () => void }) | null =\n null;\n let port2: MessagePort;\n return () => {\n if (!port1) {\n const channel = new MessageChannel();\n channel.port1.onmessage = () => {\n port1?.unref?.();\n callback();\n };\n port1 = channel.port1;\n port2 = channel.port2;\n }\n port1.ref?.();\n port2!.postMessage(null);\n };\n }\n return () => setTimeout(callback, 0);\n};\n\ntype Overlay = { inv: CommandsInvocation; dropped: boolean };\n\nexport type StatewireCore = ReturnType<typeof createStatewireCore>;\n\nexport const createStatewireCore = (\n getOptions: () => StatewireOptions<any, any, any>,\n) => {\n let value: StatewireTransport<unknown> | null = null;\n let source: StatewireStateSource | null = null;\n let overlays: Overlay[] = [];\n let updates: StatewireCommandUpdate[] = [];\n let events: unknown[] = [];\n let pendingChanged: StatewirePath[] = [];\n let canonical: unknown;\n let published: unknown;\n let overlayPaths: readonly StatewirePath[] = [];\n let notifiedPublished: unknown;\n let notifiedOverlayPaths: readonly StatewirePath[] = [];\n let notifiedConnection: StatewireConnection | null = null;\n let stale = false;\n let overlaysDirty = false;\n let flushing = false;\n let disposed = false;\n const listeners = new Set<() => void>();\n\n const post = createMacrotaskPoster(() => {\n scheduled = false;\n flush();\n });\n let scheduled = false;\n const schedule = () => {\n if (scheduled || disposed) return;\n scheduled = true;\n post();\n };\n\n const markStale = () => {\n stale = true;\n schedule();\n };\n\n const drainCanonical = () => {\n if (source) {\n pendingChanged.push(...source.takeChanged());\n canonical = source.root();\n } else if (value && value.state !== canonical) {\n pendingChanged.push([]);\n canonical = value.state;\n }\n };\n\n const recompute = () => {\n if (!stale || !value) return;\n stale = false;\n const prevCanonical = canonical;\n drainCanonical();\n if (canonical === prevCanonical && !overlaysDirty) return;\n overlaysDirty = false;\n const { optimistic } = getOptions();\n let next = canonical;\n let paths: readonly StatewirePath[] = [];\n if (optimistic && canonical !== undefined) {\n for (const overlay of overlays) {\n if (overlay.dropped) continue;\n const { inv } = overlay;\n const ctx: StatewireOptimisticContext = {\n ts: inv.ts,\n seq: inv.seq,\n ...(inv.clientId !== undefined && { clientId: inv.clientId }),\n };\n try {\n const recorded = recordMutations(next, (draft) =>\n optimistic(\n draft as never,\n { method: inv.method, params: inv.params } as StatewireCommand,\n ctx,\n ),\n );\n next = recorded.state;\n paths = [...paths, ...recorded.changedPaths];\n } catch (error) {\n overlay.dropped = true;\n console.error(\n `statewire: optimistic reducer threw for \"${inv.method}\"; dropping its optimistic update`,\n error,\n );\n }\n }\n }\n published = next;\n overlayPaths = paths;\n };\n\n const getConnection = (): StatewireConnection =>\n value?.connection ?? LIVE_CONNECTION;\n\n const flush = () => {\n if (disposed || !value) return;\n if (flushing) {\n schedule();\n return;\n }\n flushing = true;\n try {\n recompute();\n const options = getOptions();\n const stateChanged = published !== notifiedPublished;\n if (stateChanged) {\n const prev = notifiedPublished;\n const changed = [\n ...pendingChanged,\n ...notifiedOverlayPaths,\n ...overlayPaths,\n ];\n pendingChanged = [];\n notifiedPublished = published;\n notifiedOverlayPaths = overlayPaths;\n options.onStateChange?.(published as never, {\n changes: createChanges(prev, published, changed),\n });\n }\n if (updates.length > 0 && published !== undefined) {\n const batch = updates;\n updates = [];\n for (const update of batch) {\n options.onCommandChange?.(update, { state: published as never });\n }\n }\n if (events.length > 0) {\n const batch = events;\n events = [];\n for (const event of batch) {\n options.onEvent?.(event, { state: published as never });\n }\n }\n const connection = getConnection();\n const connectionChanged = connection !== notifiedConnection;\n notifiedConnection = connection;\n if (stateChanged || connectionChanged) {\n for (const listener of [...listeners]) listener();\n }\n } finally {\n flushing = false;\n }\n };\n\n const note = (\n inv: CommandsInvocation,\n status: StatewireCommandUpdate[\"status\"],\n extra?: { result?: unknown; failure?: StatewireSendFailure },\n ) => {\n if (!getOptions().onCommandChange) return;\n updates.push({\n method: inv.method,\n params: inv.params as never[],\n seq: inv.seq,\n status,\n ...extra,\n });\n schedule();\n };\n\n const commandsListener: CommandsListener = {\n onStart: (inv) => {\n overlays.push({ inv, dropped: false });\n overlaysDirty = true;\n markStale();\n note(inv, \"queued\");\n },\n onPosted: (inv) => note(inv, \"posted\"),\n onAck: (inv) => note(inv, \"acked\"),\n onCovered: (inv) => {\n if (!overlays.some((o) => o.inv === inv)) return;\n overlays = overlays.filter((o) => o.inv !== inv);\n overlaysDirty = true;\n markStale();\n },\n onSettle: (inv, outcome) => {\n note(\n inv,\n \"settled\",\n outcome.type === \"accepted\"\n ? { result: outcome.payload }\n : { failure: failureOf(outcome.error) },\n );\n flush();\n },\n };\n\n const eventSink = (event: unknown) => {\n if (!getOptions().onEvent) return;\n events.push(event);\n schedule();\n };\n\n const attach = (next: StatewireTransport<unknown>) => {\n if (disposed || next === value) return;\n const prev = value;\n value = next;\n const nextSource =\n next.source ??\n (typeof next.state === \"object\" && next.state !== null\n ? getStateHost(next.state)\n : undefined) ??\n null;\n if (nextSource !== source) {\n source?.setOnChange(null);\n source = nextSource;\n source?.setOnChange(markStale);\n markStale();\n }\n if (next.commands !== prev?.commands) {\n if (prev) getCommandsHost(prev.commands)?.setListener(null);\n getCommandsHost(next.commands)?.setListener(commandsListener);\n if (overlays.length > 0) {\n overlays = [];\n overlaysDirty = true;\n }\n markStale();\n }\n if (next.emit !== prev?.emit) {\n if (prev) getEmitHost(prev.emit)?.setSink(null);\n getEmitHost(next.emit)?.setSink(eventSink);\n }\n if (!source) markStale();\n schedule();\n };\n\n const detach = () => {\n source?.setOnChange(null);\n source = null;\n if (value) {\n getCommandsHost(value.commands)?.setListener(null);\n getEmitHost(value.emit)?.setSink(null);\n value = null;\n }\n };\n\n const memo = new Map<string, (...params: unknown[]) => Promise<unknown>>();\n const commands = new Proxy({} as StatewireCommandsProxy<any>, {\n get: (_target, method) => {\n if (typeof method !== \"string\") return undefined;\n let fn = memo.get(method);\n if (fn === undefined) {\n fn = (...params: unknown[]) => {\n if (!value) {\n return Promise.reject(\n new StatewireSendError({ fate: \"aborted\", cause: \"unmount\" }),\n );\n }\n const send = (\n value.commands as Record<\n string,\n (...args: unknown[]) => Promise<unknown>\n >\n )[method]!;\n const promise = send(...params);\n flush();\n return promise;\n };\n memo.set(method, fn);\n }\n return fn;\n },\n });\n\n return {\n attach,\n detach,\n dispose: () => {\n detach();\n disposed = true;\n },\n getState: () => {\n recompute();\n return published;\n },\n getConnection,\n commands,\n flush,\n subscribe: (listener: () => void) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"core.js","names":["useEffect","useState","createChanges","getCommandsHost","getEmitHost","getStateHost","CommandsInvocation","CommandsListener","recordMutations","createMacrotaskPoster","createMethodProxy","StatewirePath","StatewireSendError","Statewire","StatewireClient","LIVE_CONNECTION","Connection","status","reconnect","failureOf","error","SendFailure","failure","fate","cause","message","Error","String","Overlay","inv","dropped","createCore","getOptions","Options","value","source","StateSource","overlays","updates","CommandUpdate","events","pendingChanged","canonical","published","overlayPaths","notifiedPublished","notifiedOverlayPaths","notifiedConnection","stale","overlaysDirty","flushing","listeners","Set","post","scheduled","flush","schedule","markStale","drainCanonical","push","takeChanged","snapshot","state","recompute","prevCanonical","optimistic","next","paths","undefined","overlay","ctx","OptimisticContext","ts","seq","clientId","recorded","draft","method","params","Command","changedPaths","console","getConnection","connection","options","stateChanged","prev","changed","onStateChange","changes","length","batch","update","onCommandChange","event","onEvent","connectionChanged","listener","note","extra","result","commandsListener","onStart","onPosted","onAck","onCovered","some","o","filter","onSettle","outcome","type","payload","eventSink","attach","nextSource","setOnChange","commands","setListener","emit","setSink","detach","CommandsProxy","Promise","reject","send","Record","args","promise","getState","subscribe","add","delete","useStatewireCore","$","_c","t0","core","t1","t2","t3"],"sources":["../../src/client/core.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { createChanges } from \"./changes\";\nimport { getCommandsHost, getEmitHost, getStateHost } from \"../host/host\";\nimport type { CommandsInvocation, CommandsListener } from \"../host/host\";\nimport { recordMutations } from \"../util/recordMutations\";\nimport { createMacrotaskPoster } from \"../util/macrotask\";\nimport { createMethodProxy } from \"../util/methodProxy\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport { StatewireSendError } from \"../types\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireClient } from \"../statewire\";\n\nexport const LIVE_CONNECTION: StatewireClient.Connection = {\n status: \"live\",\n reconnect: () => {},\n};\n\nconst failureOf = (error: unknown): StatewireClient.SendFailure =>\n error instanceof StatewireSendError\n ? error.failure\n : {\n fate: \"rejected\",\n cause: \"server\",\n message: error instanceof Error ? error.message : String(error),\n };\n\ntype Overlay = { inv: CommandsInvocation; dropped: boolean };\n\nconst createCore = (\n getOptions: () => StatewireClient.Options<any, any, any>,\n) => {\n let value: Statewire<unknown> | null = null;\n let source: Statewire.StateSource | null = null;\n let overlays: Overlay[] = [];\n let updates: Statewire.CommandUpdate[] = [];\n let events: unknown[] = [];\n let pendingChanged: StatewirePath[] = [];\n let canonical: unknown;\n let published: unknown;\n let overlayPaths: readonly StatewirePath[] = [];\n let notifiedPublished: unknown;\n let notifiedOverlayPaths: readonly StatewirePath[] = [];\n let notifiedConnection: StatewireClient.Connection | null = null;\n let stale = false;\n let overlaysDirty = false;\n let flushing = false;\n const listeners = new Set<() => void>();\n\n const post = createMacrotaskPoster(() => {\n scheduled = false;\n flush();\n });\n let scheduled = false;\n const schedule = () => {\n if (scheduled) return;\n scheduled = true;\n post();\n };\n\n const markStale = () => {\n stale = true;\n schedule();\n };\n\n const drainCanonical = () => {\n if (source) {\n pendingChanged.push(...source.takeChanged());\n canonical = source.snapshot();\n } else if (value && value.state !== canonical) {\n pendingChanged.push([]);\n canonical = value.state;\n }\n };\n\n const recompute = () => {\n if (!stale || !value) return;\n stale = false;\n const prevCanonical = canonical;\n drainCanonical();\n if (canonical === prevCanonical && !overlaysDirty) return;\n overlaysDirty = false;\n const { optimistic } = getOptions();\n let next = canonical;\n let paths: readonly StatewirePath[] = [];\n if (optimistic && canonical !== undefined) {\n for (const overlay of overlays) {\n if (overlay.dropped) continue;\n const { inv } = overlay;\n const ctx: StatewireClient.OptimisticContext = {\n ts: inv.ts,\n seq: inv.seq,\n ...(inv.clientId !== undefined && { clientId: inv.clientId }),\n };\n try {\n const recorded = recordMutations(next, (draft) =>\n optimistic(\n draft as never,\n { method: inv.method, params: inv.params } as Statewire.Command,\n ctx,\n ),\n );\n next = recorded.state;\n paths = [...paths, ...recorded.changedPaths];\n } catch (error) {\n overlay.dropped = true;\n console.error(\n `statewire: optimistic reducer threw for \"${inv.method}\"; dropping its optimistic update`,\n error,\n );\n }\n }\n }\n published = next;\n overlayPaths = paths;\n };\n\n const getConnection = (): StatewireClient.Connection =>\n value?.connection ?? LIVE_CONNECTION;\n\n const flush = () => {\n if (!value) return;\n if (flushing) {\n schedule();\n return;\n }\n flushing = true;\n try {\n recompute();\n const options = getOptions();\n const stateChanged = published !== notifiedPublished;\n if (stateChanged) {\n const prev = notifiedPublished;\n const changed = [\n ...pendingChanged,\n ...notifiedOverlayPaths,\n ...overlayPaths,\n ];\n pendingChanged = [];\n notifiedPublished = published;\n notifiedOverlayPaths = overlayPaths;\n options.onStateChange?.(published as never, {\n changes: createChanges(prev, published, changed),\n });\n }\n if (updates.length > 0 && published !== undefined) {\n const batch = updates;\n updates = [];\n for (const update of batch) {\n options.onCommandChange?.(update, { state: published as never });\n }\n }\n if (events.length > 0 && published !== undefined) {\n const batch = events;\n events = [];\n for (const event of batch) {\n options.onEvent?.(event, { state: published as never });\n }\n }\n const connection = getConnection();\n const connectionChanged = connection !== notifiedConnection;\n notifiedConnection = connection;\n if (stateChanged || connectionChanged) {\n for (const listener of [...listeners]) listener();\n }\n } finally {\n flushing = false;\n }\n };\n\n const note = (\n inv: CommandsInvocation,\n status: Statewire.CommandUpdate[\"status\"],\n extra?: { result?: unknown; failure?: StatewireClient.SendFailure },\n ) => {\n if (!getOptions().onCommandChange) return;\n updates.push({\n method: inv.method,\n params: inv.params as never[],\n seq: inv.seq,\n status,\n ...extra,\n });\n schedule();\n };\n\n const commandsListener: CommandsListener = {\n onStart: (inv) => {\n overlays.push({ inv, dropped: false });\n overlaysDirty = true;\n markStale();\n note(inv, \"queued\");\n },\n onPosted: (inv) => note(inv, \"posted\"),\n onAck: (inv) => note(inv, \"acked\"),\n onCovered: (inv) => {\n if (!overlays.some((o) => o.inv === inv)) return;\n overlays = overlays.filter((o) => o.inv !== inv);\n overlaysDirty = true;\n markStale();\n },\n onSettle: (inv, outcome) => {\n note(\n inv,\n \"settled\",\n outcome.type === \"accepted\"\n ? { result: outcome.payload }\n : { failure: failureOf(outcome.error) },\n );\n flush();\n },\n };\n\n const eventSink = (event: unknown) => {\n if (!getOptions().onEvent) return;\n events.push(event);\n schedule();\n };\n\n const attach = (next: Statewire<unknown>) => {\n if (next === value) return;\n const prev = value;\n value = next;\n const nextSource =\n next.source ??\n (typeof next.state === \"object\" && next.state !== null\n ? getStateHost(next.state)\n : undefined) ??\n null;\n if (nextSource !== source) {\n source?.setOnChange(null);\n source = nextSource;\n source?.setOnChange(markStale);\n markStale();\n }\n if (next.commands !== prev?.commands) {\n if (prev) getCommandsHost(prev.commands)?.setListener(null);\n getCommandsHost(next.commands)?.setListener(commandsListener);\n if (overlays.length > 0) {\n overlays = [];\n overlaysDirty = true;\n }\n markStale();\n }\n if (next.emit !== prev?.emit) {\n if (prev) getEmitHost(prev.emit)?.setSink(null);\n getEmitHost(next.emit)?.setSink(eventSink);\n }\n if (!source) markStale();\n schedule();\n };\n\n const detach = () => {\n source?.setOnChange(null);\n source = null;\n if (value) {\n getCommandsHost(value.commands)?.setListener(null);\n getEmitHost(value.emit)?.setSink(null);\n value = null;\n }\n };\n\n const commands = createMethodProxy<Statewire.CommandsProxy<any>>(\n (method, params) => {\n if (!value) {\n return Promise.reject(\n new StatewireSendError({ fate: \"aborted\", cause: \"unmount\" }),\n );\n }\n const send = (\n value.commands as Record<\n string,\n (...args: unknown[]) => Promise<unknown>\n >\n )[method]!;\n const promise = send(...params);\n flush();\n return promise;\n },\n );\n\n return {\n attach,\n detach,\n getState: () => {\n recompute();\n return published;\n },\n commands,\n subscribe: (listener: () => void) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n};\n\nexport const useStatewireCore = (\n getOptions: () => StatewireClient.Options<any, any, any>,\n value: Statewire<unknown>,\n) => {\n const [core] = useState(() => createCore(getOptions));\n // Attach during render so the first snapshot reads the transport's state;\n // re-attaching the same transport is a no-op and the single-slot host\n // listeners make a swap land on the last writer.\n core.attach(value);\n useEffect(() => {\n core.attach(value);\n });\n useEffect(() => () => core.detach(), [core]);\n return core;\n};\n"],"mappings":"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"}
@@ -1,13 +1,9 @@
1
- import { StatewirePath } from "../wire/ops.js";
2
- import { StatewireResponse } from "./connection.js";
3
- import { StatewireSessionRecord, StatewireSessionStore } from "./sessionStore.js";
4
- import { RemoteLink, StatewireWireCommand } from "../transports/sse.js";
5
- import { StatewireSendError, StatewireStateSource } from "../types.js";
6
1
  import { CommandsInvocation, CommandsListener } from "../host/host.js";
2
+ import { RemoteLink, StatewireRemoteProps, StatewireWireCommand } from "../transports/sse.js";
3
+ import { StatewireSessionRecord, StatewireSessionStore, StatewireSessionStoreOptions } from "./sessionStore.js";
4
+ import { Statewire, StatewireSendError } from "../types.js";
5
+ import { StatewirePath } from "../wire/ops.js";
7
6
  //#region src/client/session.d.ts
8
- type SessionOptions = {
9
- sessionStore?: StatewireSessionStore | undefined;
10
- };
11
7
  /**
12
8
  * queued/trying/posted: overlay live, result pending. awaiting-result: acked
13
9
  * (overlay gone) but the result rides a late res; `posted: false` re-sends.
@@ -39,7 +35,8 @@ type PendingState = {
39
35
  type Pending = {
40
36
  readonly seq: number;
41
37
  readonly command: StatewireWireCommand;
42
- readonly inv: CommandsInvocation;
38
+ /** null for context submissions — they carry no command lifecycle. */
39
+ readonly inv: CommandsInvocation | null;
43
40
  state: PendingState;
44
41
  resolve: (payload: unknown) => void;
45
42
  reject: (error: StatewireSendError) => void;
@@ -47,14 +44,16 @@ type Pending = {
47
44
  type AttachState = "detached" | "attached" | "finished";
48
45
  type Session = {
49
46
  record: StatewireSessionRecord;
47
+ ctx: StatewireRemoteProps;
50
48
  recordPersisted: boolean;
51
49
  store: StatewireSessionStore;
52
50
  lastAck: number;
51
+ context: Record<string, unknown> | undefined;
53
52
  pending: Pending[];
54
53
  canonical: unknown;
55
54
  lastPublished: unknown;
56
55
  changed: StatewirePath[];
57
- source: StatewireStateSource;
56
+ source: Statewire.StateSource;
58
57
  attach: AttachState;
59
58
  attachEpoch: number;
60
59
  running: boolean;
@@ -67,22 +66,9 @@ type Session = {
67
66
  send: (command: StatewireWireCommand) => Promise<unknown>;
68
67
  commands: Record<string, (...params: unknown[]) => Promise<unknown>>;
69
68
  };
70
- declare const createSession: (options: SessionOptions) => Session;
71
- declare const handleSnapshot: (s: Session, { ops, lastSeq }: {
72
- ops: unknown[];
73
- lastSeq: number;
74
- }) => void;
75
- declare const handleOps: (s: Session, { ops, res, ack }: {
76
- ops: unknown[];
77
- res?: StatewireResponse[];
78
- ack?: number;
79
- }) => void;
80
- declare const handleFinish: (s: Session, { reason }: {
81
- reason: string;
82
- }) => void;
83
- declare const start: (s: Session) => void;
84
- declare const stop: (s: Session) => void;
85
69
  declare const setTransport: (s: Session, transport: RemoteLink) => void;
70
+ declare const setContext: (s: Session, value: Record<string, unknown>) => Promise<unknown>;
71
+ declare const useStatewireSession: (options: StatewireSessionStoreOptions, emit: (event: unknown) => void) => Session;
86
72
  //#endregion
87
- export { SessionOptions, createSession, handleFinish, handleOps, handleSnapshot, setTransport, start, stop };
73
+ export { setContext, setTransport, useStatewireSession };
88
74
  //# sourceMappingURL=session.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","names":[],"sources":["../../src/client/session.ts"],"mappings":";;;;;;;KAoBY;EACV,eAAe;;;;;;;;KASZ;EACC;EAAc;;EACd;EAAc;EAAwB;;EACtC;EAAc;EAAwB;;EACtC;EAAuB;EAAiB;;EACxC;EAAoB;EAAiB;;EACrC;;KAED;WACM;WACA,SAAS;WACT,KAAK;EACd,OAAO;EACP,UAAU;EACV,SAAS,OAAO;;KAyFb;KAEA;EACH,QAAQ;EACR;EACA,OAAO;EACP;EACA,SAAS;EACT;EACA;EACA,SAAS;EACT,QAAQ;EACR,QAAQ;EACR;EACA;EACA;EACA,WAAW;EACX;EACA,WAAW;EACX;EACA,YAAY;EACZ,OAAO,SAAS,yBAAyB;EACzC,UAAU,mBAAmB,sBAAsB;;cAoB/C,gBAAiB,SAAS,mBAAiB;cA0N3C,iBACJ,GAAG,WACH,KAAA;EAAoB;EAAgB;;cAuChC,YACJ,GAAG,WACH,KAAA,KAAA;EAIK;EAAgB,MAAM;EAAqB;;cAoB5C,eAAgB,GAAG,WAAS;EAAc;;cAuJ1C,QAAS,GAAG;cASZ,OAAQ,GAAG;cAMX,eAAgB,GAAG,SAAS,WAAW"}
1
+ {"version":3,"file":"session.d.ts","names":[],"sources":["../../src/client/session.ts"],"mappings":";;;;;;;;;;;;KA+BK;EACC;EAAc;;EACd;EAAc;EAAwB;;EACtC;EAAc;EAAwB;;EACtC;EAAuB;EAAiB;;EACxC;EAAoB;EAAiB;;EACrC;;KAED;WACM;WACA,SAAS;;WAET,KAAK;EACd,OAAO;EACP,UAAU;EACV,SAAS,OAAO;;KAqGb;KAEA;EACH,QAAQ;EACR,KAAK;EACL;EACA,OAAO;EACP;EACA,SAAS;EACT,SAAS;EACT;EACA;EACA,SAAS;EACT,QAAQ,UAAU;EAClB,QAAQ;EACR;EACA;EACA;EACA,WAAW;EACX;EACA,WAAW;EACX;EACA,YAAY;EACZ,OAAO,SAAS,yBAAyB;EACzC,UAAU,mBAAmB,sBAAsB;;cAsd/C,eAAgB,GAAG,SAAS,WAAW;cA+CvC,aACJ,GAAG,SACH,OAAO,4BACN;cAQU,sBACX,SAAS,8BACT,OAAO,4BACN"}
@@ -1,11 +1,18 @@
1
1
  import { applyOps } from "../wire/ops.js";
2
+ import { createMethodProxy } from "../util/methodProxy.js";
2
3
  import { StatewireSendError } from "../types.js";
3
4
  import { bindCommandsHost } from "../host/host.js";
4
- import { backoff } from "./timing.js";
5
+ import { randomId } from "../util/randomId.js";
6
+ import { retryDelay } from "./timing.js";
5
7
  import { createMemorySessionStore } from "./sessionStore.js";
8
+ import { useEffect, useState } from "@assistant-ui/tap/react-shim";
6
9
  //#region src/client/session.ts
7
10
  const isResolved = (state) => state.at === "settled-live" || state.at === "done";
8
11
  const isStateSettled = (state) => state.at === "awaiting-result" || state.at === "done";
12
+ const inFlight = (state) => {
13
+ if (state.at === "queued" || state.at === "done") throw new Error(`statewire: ${state.at} entry observed after a send`);
14
+ return state;
15
+ };
9
16
  const legalTransitions = {
10
17
  queued: [
11
18
  "queued",
@@ -40,16 +47,16 @@ const transition = (s, entry, next, outcome) => {
40
47
  if (settles && outcome === void 0) throw new Error(`statewire: settling transition ${prev.at} -> ${next.at} without an outcome`);
41
48
  const covers = !isStateSettled(prev) && isStateSettled(next);
42
49
  entry.state = next;
43
- if (covers) s.lifecycle?.onCovered?.(entry.inv);
50
+ if (covers && entry.inv) s.lifecycle?.onCovered?.(entry.inv);
44
51
  if (settles && outcome !== void 0) if (outcome.ok) {
45
52
  entry.resolve(outcome.payload);
46
- s.lifecycle?.onSettle(entry.inv, {
53
+ if (entry.inv) s.lifecycle?.onSettle(entry.inv, {
47
54
  type: "accepted",
48
55
  payload: outcome.payload
49
56
  });
50
57
  } else {
51
58
  entry.reject(new StatewireSendError(outcome.failure));
52
- s.lifecycle?.onSettle(entry.inv, {
59
+ if (entry.inv) s.lifecycle?.onSettle(entry.inv, {
53
60
  type: "rejected",
54
61
  error: new StatewireSendError(outcome.failure)
55
62
  });
@@ -66,27 +73,12 @@ const carried = (state) => state.at === "queued" ? {
66
73
  posted: state.posted,
67
74
  failedAttempts: state.failedAttempts
68
75
  };
69
- const newClientId = () => `c-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
76
+ const newClientId = () => `c-${randomId()}`;
70
77
  const rejectedSend = (failure) => {
71
78
  const result = Promise.reject(new StatewireSendError(failure));
72
79
  result.catch(() => {});
73
80
  return result;
74
81
  };
75
- const createCommandsProxy = (send) => {
76
- const memo = /* @__PURE__ */ new Map();
77
- return new Proxy({}, { get: (_target, method) => {
78
- if (typeof method !== "string") return void 0;
79
- let fn = memo.get(method);
80
- if (fn === void 0) {
81
- fn = (...params) => send({
82
- method,
83
- params
84
- });
85
- memo.set(method, fn);
86
- }
87
- return fn;
88
- } });
89
- };
90
82
  const createSession = (options) => {
91
83
  const store = options.sessionStore ?? createMemorySessionStore();
92
84
  const stored = store.load();
@@ -95,15 +87,24 @@ const createSession = (options) => {
95
87
  clientId: newClientId(),
96
88
  nextSeq: 1
97
89
  },
90
+ ctx: {
91
+ get clientId() {
92
+ return s.record.clientId;
93
+ },
94
+ onSnapshot: (args) => handleSnapshot(s, args),
95
+ onOps: (args) => handleOps(s, args),
96
+ onFinish: (args) => handleFinish(s, args)
97
+ },
98
98
  recordPersisted: stored !== null,
99
99
  store,
100
100
  lastAck: -1,
101
+ context: void 0,
101
102
  pending: [],
102
103
  canonical: void 0,
103
104
  lastPublished: void 0,
104
105
  changed: [],
105
106
  source: {
106
- root: () => s.canonical,
107
+ snapshot: () => s.canonical,
107
108
  takeChanged: () => {
108
109
  const changed = s.changed;
109
110
  s.changed = [];
@@ -123,7 +124,10 @@ const createSession = (options) => {
123
124
  notify: () => {},
124
125
  emitEvent: () => {},
125
126
  send: (command) => sendCommand(s, command),
126
- commands: createCommandsProxy((command) => s.send(command))
127
+ commands: createMethodProxy((method, params) => s.send({
128
+ method,
129
+ params
130
+ }))
127
131
  };
128
132
  bindCommandsHost(s.commands, { setListener: (listener) => {
129
133
  s.lifecycle = listener;
@@ -141,6 +145,17 @@ const wake = (s) => {
141
145
  for (const waiter of waiters) waiter();
142
146
  };
143
147
  const waitForWake = (s) => new Promise((r) => s.waiters.push(r));
148
+ const sleepUntilWake = (s, delayMs) => new Promise((resolve) => {
149
+ const waiter = () => {
150
+ clearTimeout(timer);
151
+ resolve();
152
+ };
153
+ const timer = setTimeout(() => {
154
+ s.waiters = s.waiters.filter((w) => w !== waiter);
155
+ resolve();
156
+ }, delayMs);
157
+ s.waiters.push(waiter);
158
+ });
144
159
  const settleAll = (s, detach, verdict) => {
145
160
  const cause = {
146
161
  evicted: "session-lost",
@@ -180,13 +195,14 @@ const evict = (s, verdict) => {
180
195
  };
181
196
  s.store.save(s.record);
182
197
  s.attach = "detached";
198
+ if (s.context !== void 0) s.send({ context: s.context });
183
199
  publish(s);
184
200
  wake(s);
185
201
  };
186
202
  const noteAcked = (s, seq) => {
187
203
  for (const entry of s.pending) {
188
204
  if (entry.seq > seq || isStateSettled(entry.state)) continue;
189
- s.lifecycle?.onAck(entry.inv);
205
+ if (entry.inv) s.lifecycle?.onAck(entry.inv);
190
206
  }
191
207
  };
192
208
  const spliceAcked = (s, seq, settleVoid) => {
@@ -238,11 +254,11 @@ const applyResponse = (s, response) => {
238
254
  } : response.code === "invalid-params" ? {
239
255
  fate: "invalid-params",
240
256
  cause: "server",
241
- message: response.message ?? ""
257
+ message: response.message ?? protocolError("rejected response missing message")
242
258
  } : {
243
259
  fate: "rejected",
244
260
  cause: "server",
245
- message: response.message ?? "",
261
+ message: response.message ?? protocolError("rejected response missing message"),
246
262
  ...response.payload !== void 0 && { payload: response.payload }
247
263
  }
248
264
  });
@@ -271,7 +287,7 @@ const ingestOps = (s, ops) => {
271
287
  };
272
288
  const publishIngested = (s, events) => {
273
289
  publish(s);
274
- for (const { event } of events) s.emitEvent(event);
290
+ for (const event of events) s.emitEvent(event);
275
291
  wake(s);
276
292
  };
277
293
  const handleSnapshot = (s, { ops, lastSeq }) => {
@@ -327,7 +343,7 @@ const handleOps = (s, { ops, res, ack }) => {
327
343
  publishIngested(s, events);
328
344
  };
329
345
  const handleFinish = (s, { reason }) => {
330
- if (reason === "ended" || reason === "error") return;
346
+ if (reason === "evicted" || reason === "error") return;
331
347
  s.attach = "finished";
332
348
  settleAll(s, reason === "superseded" ? "superseded" : "gone");
333
349
  publish(s);
@@ -363,15 +379,12 @@ const pump = async (s) => {
363
379
  } catch (error) {
364
380
  if (!s.running || isFinished(s)) return;
365
381
  if (!s.pending.includes(entry)) continue;
366
- const state = entry.state;
367
- if (state.at === "queued" || state.at === "done") continue;
382
+ const state = inFlight(entry.state);
368
383
  transition(s, entry, {
369
384
  ...state,
370
385
  failedAttempts: state.failedAttempts + 1
371
386
  });
372
- const { retryAfterMs } = error;
373
- const delay = typeof retryAfterMs === "number" ? retryAfterMs : backoff(state.failedAttempts + 1);
374
- await new Promise((r) => setTimeout(r, delay));
387
+ await sleepUntilWake(s, retryDelay(error, state.failedAttempts + 1));
375
388
  continue;
376
389
  }
377
390
  if (!s.running) return;
@@ -388,20 +401,18 @@ const pump = async (s) => {
388
401
  ...state,
389
402
  posted: true
390
403
  });
391
- s.lifecycle?.onPosted?.(entry.inv);
404
+ if (entry.inv) s.lifecycle?.onPosted?.(entry.inv);
392
405
  if (response !== void 0) {
393
406
  applyResponse(s, response);
394
407
  publish(s);
395
408
  }
396
409
  } else if (result.type === "rejected") {
397
- const { message, payload } = result;
398
410
  transition(s, entry, { at: "done" }, {
399
411
  ok: false,
400
412
  failure: {
401
413
  fate: "rejected",
402
414
  cause: "server",
403
- message,
404
- ...payload !== void 0 && { payload }
415
+ message: result.message
405
416
  }
406
417
  });
407
418
  publish(s);
@@ -420,13 +431,10 @@ const pump = async (s) => {
420
431
  } else if (result.type === "seq-gap") {
421
432
  if (clientId === s.record.clientId) {
422
433
  console.error(`statewire: server reports a seq gap for seq ${entry.seq}; treating the session as corrupt and rotating the client id`);
423
- const state = entry.state;
434
+ const state = inFlight(entry.state);
424
435
  const failure = state.at === "awaiting-result" ? {
425
436
  fate: "result-unavailable",
426
437
  cause: "session-lost"
427
- } : state.at === "queued" || state.at === "done" ? {
428
- fate: "aborted",
429
- cause: "session-lost"
430
438
  } : {
431
439
  fate: state.failedAttempts > 0 ? "lost" : "aborted",
432
440
  cause: "session-lost"
@@ -479,7 +487,7 @@ const sendCommand = (s, command) => {
479
487
  const entry = {
480
488
  seq,
481
489
  command,
482
- inv: {
490
+ inv: command.method === void 0 ? null : {
483
491
  method: command.method,
484
492
  params: command.params,
485
493
  ts: Date.now(),
@@ -499,11 +507,30 @@ const sendCommand = (s, command) => {
499
507
  });
500
508
  result.catch(() => {});
501
509
  s.pending.push(entry);
502
- s.lifecycle?.onStart(entry.inv);
510
+ if (entry.inv) s.lifecycle?.onStart(entry.inv);
503
511
  pump(s);
504
512
  return result;
505
513
  };
514
+ const setContext = (s, value) => {
515
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("statewire: context must be a plain object");
516
+ s.context = value;
517
+ return s.send({ context: value });
518
+ };
519
+ const useStatewireSession = (options, emit) => {
520
+ const [s] = useState(() => createSession(options));
521
+ useEffect(() => {
522
+ s.emitEvent = emit;
523
+ return () => {
524
+ s.emitEvent = () => {};
525
+ };
526
+ }, [s, emit]);
527
+ useEffect(() => {
528
+ start(s);
529
+ return () => stop(s);
530
+ }, [s]);
531
+ return s;
532
+ };
506
533
  //#endregion
507
- export { createSession, handleFinish, handleOps, handleSnapshot, setTransport, start, stop };
534
+ export { setContext, setTransport, useStatewireSession };
508
535
 
509
536
  //# sourceMappingURL=session.js.map