statewire 0.0.2 → 0.0.4

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 (48) hide show
  1. package/dist/client/connection.d.ts +1 -1
  2. package/dist/client/session.d.ts +7 -4
  3. package/dist/client/session.d.ts.map +1 -1
  4. package/dist/client/session.js +15 -8
  5. package/dist/client/session.js.map +1 -1
  6. package/dist/host/host.d.ts +3 -2
  7. package/dist/host/host.d.ts.map +1 -1
  8. package/dist/host/host.js +5 -1
  9. package/dist/host/host.js.map +1 -1
  10. package/dist/host/socket.d.ts.map +1 -1
  11. package/dist/host/socket.js +58 -6
  12. package/dist/host/socket.js.map +1 -1
  13. package/dist/host.d.ts +11 -1
  14. package/dist/host.d.ts.map +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/statewire.d.ts +19 -1
  17. package/dist/statewire.d.ts.map +1 -1
  18. package/dist/statewire.js +10 -2
  19. package/dist/statewire.js.map +1 -1
  20. package/dist/transports/fixtures.d.ts +1 -1
  21. package/dist/transports/remote.d.ts +1 -1
  22. package/dist/transports/remote.d.ts.map +1 -1
  23. package/dist/transports/remote.js +3 -2
  24. package/dist/transports/remote.js.map +1 -1
  25. package/dist/transports/sse.d.ts +26 -19
  26. package/dist/transports/sse.d.ts.map +1 -1
  27. package/dist/transports/sse.js +15 -5
  28. package/dist/transports/sse.js.map +1 -1
  29. package/dist/transports/testing.d.ts +5 -4
  30. package/dist/transports/testing.d.ts.map +1 -1
  31. package/dist/transports/testing.js.map +1 -1
  32. package/dist/transports/ws.d.ts.map +1 -1
  33. package/dist/transports/ws.js +2 -3
  34. package/dist/transports/ws.js.map +1 -1
  35. package/dist/types.d.ts +2 -0
  36. package/dist/types.d.ts.map +1 -1
  37. package/dist/types.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/client/session.ts +43 -21
  40. package/src/host/host.ts +15 -4
  41. package/src/host/socket.ts +85 -9
  42. package/src/host.ts +11 -0
  43. package/src/statewire.ts +27 -2
  44. package/src/transports/remote.ts +6 -1
  45. package/src/transports/sse.ts +20 -20
  46. package/src/transports/testing.ts +6 -4
  47. package/src/transports/ws.ts +2 -8
  48. package/src/types.ts +2 -0
@@ -18,9 +18,15 @@ const parseFrame = (raw) => {
18
18
  return null;
19
19
  }
20
20
  if (typeof frame !== "object" || frame === null || Array.isArray(frame)) return null;
21
- const { method, seq, params, meta } = frame;
22
- if (typeof method !== "string") return null;
21
+ const { method, seq, params, meta, context } = frame;
22
+ if (method === void 0 && context === void 0) return null;
23
23
  if (typeof seq !== "number" || !Number.isInteger(seq) || seq < 1) return null;
24
+ if (method === void 0) return {
25
+ seq,
26
+ params: [],
27
+ ...context !== void 0 && { context }
28
+ };
29
+ if (typeof method !== "string") return null;
24
30
  if (!Array.isArray(params)) return null;
25
31
  if (meta !== void 0) try {
26
32
  reviveValue(params, meta);
@@ -30,9 +36,19 @@ const parseFrame = (raw) => {
30
36
  return {
31
37
  method,
32
38
  seq,
33
- params
39
+ params,
40
+ ...context !== void 0 && { context }
34
41
  };
35
42
  };
43
+ /** v1 context submissions are exactly one root replace with an object value. */
44
+ const contextValue = (context) => {
45
+ if (!Array.isArray(context) || context.length !== 1) throw new Error("context must be a single-op array");
46
+ const op = context[0];
47
+ if (typeof op !== "object" || op === null || op["op"] !== "replace" || !Array.isArray(op["path"]) || op["path"].length !== 0) throw new Error("context op must be a root replace");
48
+ const value = op["value"];
49
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("context value must be an object");
50
+ return value;
51
+ };
36
52
  const encodePayload = (payload) => {
37
53
  if (payload === void 0) return {};
38
54
  const { value, meta } = encodeValue(payload);
@@ -231,11 +247,10 @@ const createSocketHost = (options) => {
231
247
  type: "result-unavailable"
232
248
  };
233
249
  };
234
- const settleWire = (rec, adm, outcome) => {
250
+ const settleResponse = (rec, adm, response) => {
235
251
  if (adm.settled) return;
236
252
  adm.settled = true;
237
253
  rec.inflight.delete(adm.seq);
238
- const response = responseOf(adm, outcome);
239
254
  retain(rec, response);
240
255
  if (!adm.acked) {
241
256
  adm.acked = true;
@@ -245,6 +260,17 @@ const createSocketHost = (options) => {
245
260
  scheduleFlush();
246
261
  adm.release();
247
262
  };
263
+ const settleWire = (rec, adm, outcome) => settleResponse(rec, adm, responseOf(adm, outcome));
264
+ const callerOf = (rec) => ({
265
+ get context() {
266
+ return rec.context;
267
+ },
268
+ get isConnected() {
269
+ if (clients.get(rec.id) !== rec) return false;
270
+ for (const conn of conns) if (conn.clientId === rec.id && conn.open) return true;
271
+ return false;
272
+ }
273
+ });
248
274
  const admit = (conn, frame) => {
249
275
  if (!conn.open || disposed || finished !== null) return;
250
276
  const rec = clients.get(conn.clientId);
@@ -259,12 +285,36 @@ const createSocketHost = (options) => {
259
285
  }
260
286
  const adm = {
261
287
  seq: frame.seq,
262
- method: frame.method,
288
+ method: frame.method ?? "",
263
289
  acked: false,
264
290
  pendingSent: false,
265
291
  settled: false,
266
292
  release: () => {}
267
293
  };
294
+ if (frame.context !== void 0) {
295
+ let value;
296
+ try {
297
+ value = contextValue(frame.context);
298
+ } catch (error) {
299
+ rec.inflight.set(frame.seq, adm);
300
+ settleResponse(rec, adm, {
301
+ seq: frame.seq,
302
+ type: "rejected",
303
+ code: "invalid-context",
304
+ message: `invalid context: ${error.message}`
305
+ });
306
+ return;
307
+ }
308
+ rec.context = value;
309
+ if (frame.method === void 0) {
310
+ rec.inflight.set(frame.seq, adm);
311
+ settleResponse(rec, adm, {
312
+ seq: frame.seq,
313
+ type: "accepted"
314
+ });
315
+ return;
316
+ }
317
+ }
268
318
  const released = new Promise((resolve) => {
269
319
  adm.release = resolve;
270
320
  });
@@ -273,6 +323,7 @@ const createSocketHost = (options) => {
273
323
  method: frame.method,
274
324
  params: frame.params,
275
325
  clientId: rec.id,
326
+ caller: callerOf(rec),
276
327
  onAck: () => {
277
328
  if (adm.settled || adm.acked) return;
278
329
  adm.acked = true;
@@ -324,6 +375,7 @@ const createSocketHost = (options) => {
324
375
  const created = {
325
376
  id: clientId,
326
377
  lastSeq: 0,
378
+ context: null,
327
379
  results: /* @__PURE__ */ new Map(),
328
380
  inflight: /* @__PURE__ */ new Map()
329
381
  };
@@ -1 +1 @@
1
- {"version":3,"file":"socket.js","names":["useEffect","useState","createTapRoot","flushTapSync","useResource","ResourceElement","getCommandsHost","getEmitHost","getStateHost","CommandsHost","CommandsOutcome","StatewireSendError","Statewire","StatewireHost","StatewireHostInternal","encodeValue","reviveValue","createMacrotaskPoster","randomId","StatewireMetaEntry","StatewirePath","StatewireEnvelope","StatewireResponse","CLIENT_ID_RE","RESULT_TTL_MS","RESULT_LIMIT","WireOp","op","path","value","meta","Admission","seq","method","acked","pendingSent","settled","release","ClientRec","id","lastSeq","results","Map","ts","res","inflight","Conn","clientId","socket","Socket","open","lastSentAt","Frame","params","parseFrame","raw","frame","JSON","parse","Array","isArray","Record","Number","isInteger","undefined","encodePayload","payload","responseOf","adm","outcome","type","failure","error","fate","code","message","Error","String","createSocketHost","options","SocketHostOptions","heartbeatMs","heartbeatIntervalMs","clients","conns","Set","pendingOps","dirtyAcks","pendingRes","inbox","Promise","resolve","finished","Fin","disposed","flushScheduled","heartbeatTimer","ReturnType","setInterval","stateHost","StateHost","commandsHost","emitHost","current","post","flush","scheduleFlush","recordOp","push","attach","next","nextState","state","nextCommands","commands","nextEmit","emit","submit","setOnOp","setSink","event","closeConn","conn","reason","delete","close","size","clearInterval","sendTo","data","Date","now","send","ensureHeartbeat","unref","fin","takeChanged","rec","values","queueRes","length","ops","acks","envelope","clientRes","get","has","ack","Object","keys","stringify","response","list","set","advance","add","retain","newest","Math","max","entry","min","duplicateAnswer","cached","settleWire","admit","released","onAck","onSettle","catch","then","connect","SocketConnection","dead","refuse","test","known","created","other","snapshot","syn","lease","finish","resume","suspend","useSocketHost","transport","$","_c","t0","h","t1","t2","t3","StatewireSocketHost","SocketHost","SocketHostRoot","root","getValue","dispose","unmount"],"sources":["../../src/host/socket.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { createTapRoot, flushTapSync, useResource } from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { getCommandsHost, getEmitHost, getStateHost } from \"./host\";\nimport type { CommandsHost, CommandsOutcome } from \"./host\";\nimport { StatewireSendError } from \"../types\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireHost } from \"../host\";\nimport type { StatewireHostInternal } from \"../host-internal\";\nimport { encodeValue, reviveValue } from \"../wire/meta\";\nimport { createMacrotaskPoster } from \"../util/macrotask\";\nimport { randomId } from \"../util/randomId\";\nimport type { StatewireMetaEntry } from \"../wire/meta\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport type {\n StatewireEnvelope,\n StatewireResponse,\n} from \"../client/connection\";\n\nconst CLIENT_ID_RE = /^[A-Za-z0-9._~-]{1,256}$/;\nconst RESULT_TTL_MS = 30_000;\nconst RESULT_LIMIT = 256;\n\ntype WireOp = {\n op: string;\n path?: StatewirePath;\n value?: unknown;\n meta?: StatewireMetaEntry[];\n};\n\ntype Admission = {\n seq: number;\n method: string;\n acked: boolean;\n pendingSent: boolean;\n settled: boolean;\n release: () => void;\n};\n\ntype ClientRec = {\n id: string;\n lastSeq: number;\n results: Map<number, { ts: number; res: StatewireResponse }>;\n inflight: Map<number, Admission>;\n};\n\ntype Conn = {\n clientId: string;\n socket: StatewireHostInternal.Socket;\n open: boolean;\n lastSentAt: number;\n};\n\ntype Frame = { method: string; seq: number; params: unknown[] };\n\nconst parseFrame = (raw: string): Frame | null => {\n let frame: unknown;\n try {\n frame = JSON.parse(raw);\n } catch {\n return null;\n }\n if (typeof frame !== \"object\" || frame === null || Array.isArray(frame)) {\n return null;\n }\n const { method, seq, params, meta } = frame as Record<string, unknown>;\n if (typeof method !== \"string\") return null;\n if (typeof seq !== \"number\" || !Number.isInteger(seq) || seq < 1) return null;\n if (!Array.isArray(params)) return null;\n if (meta !== undefined) {\n try {\n reviveValue(params, meta);\n } catch {\n return null;\n }\n }\n return { method, seq, params };\n};\n\nconst encodePayload = (\n payload: unknown,\n): { payload?: unknown; meta?: StatewireMetaEntry[] } => {\n if (payload === undefined) return {};\n const { value, meta } = encodeValue(payload);\n return { payload: value, ...(meta !== undefined && { meta }) };\n};\n\nconst responseOf = (\n adm: Admission,\n outcome: CommandsOutcome,\n): StatewireResponse => {\n if (outcome.type === \"accepted\") {\n return {\n seq: adm.seq,\n type: \"accepted\",\n ...encodePayload(outcome.payload),\n };\n }\n const failure =\n outcome.error instanceof StatewireSendError ? outcome.error.failure : null;\n switch (failure?.fate) {\n case \"unknown-command\":\n return {\n seq: adm.seq,\n type: \"rejected\",\n code: \"unknown-command\",\n message: `unknown method: '${adm.method}'`,\n };\n case \"invalid-params\":\n return {\n seq: adm.seq,\n type: \"rejected\",\n code: \"invalid-params\",\n message: failure.message,\n };\n case \"crashed\":\n return { seq: adm.seq, type: \"crashed\" };\n case \"rejected\":\n return {\n seq: adm.seq,\n type: \"rejected\",\n message: failure.message,\n ...encodePayload(failure.payload),\n };\n default:\n return {\n seq: adm.seq,\n type: \"rejected\",\n message:\n outcome.error instanceof Error\n ? outcome.error.message\n : String(outcome.error),\n };\n }\n};\n\nconst createSocketHost = (\n options?: StatewireHostInternal.SocketHostOptions,\n) => {\n const heartbeatMs = options?.heartbeatIntervalMs ?? 15_000;\n\n const clients = new Map<string, ClientRec>();\n const conns = new Set<Conn>();\n let pendingOps: WireOp[] = [];\n let dirtyAcks = new Set<string>();\n let pendingRes = new Map<string, StatewireResponse[]>();\n let inbox: Promise<void> = Promise.resolve();\n let finished: StatewireHost.Fin | null = null;\n let disposed = false;\n let flushScheduled = false;\n let heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n\n let stateHost: StatewireHost.StateHost | null = null;\n let commandsHost: CommandsHost | null = null;\n let emitHost: ReturnType<typeof getEmitHost> | null = null;\n let current: Statewire<unknown> | null = null;\n\n const post = createMacrotaskPoster(() => {\n flushScheduled = false;\n flush();\n });\n const scheduleFlush = () => {\n if (flushScheduled || disposed) return;\n flushScheduled = true;\n post();\n };\n\n const recordOp = (op: {\n op: string;\n path?: StatewirePath;\n value?: unknown;\n }) => {\n if (op.op === \"replace\" || op.op === \"add\" || op.op === \"event\") {\n const { value, meta } = encodeValue(op.value);\n pendingOps.push({ ...op, value, ...(meta !== undefined && { meta }) });\n } else {\n pendingOps.push(op);\n }\n scheduleFlush();\n };\n\n const attach = (next: Statewire<unknown>) => {\n if (next === current) return;\n current = next;\n const nextState =\n typeof next.state === \"object\" && next.state !== null\n ? getStateHost(next.state)\n : undefined;\n const nextCommands = getCommandsHost(next.commands);\n const nextEmit = getEmitHost(next.emit);\n if (!nextState || !nextCommands?.submit || !nextEmit) {\n throw new Error(\n \"StatewireSocketHost requires a statewire/host transport (useStatewireState with object state, useStatewireCommands, useStatewireEmit)\",\n );\n }\n if (nextState !== stateHost) {\n stateHost?.setOnOp(null);\n stateHost = nextState;\n stateHost.setOnOp(recordOp);\n }\n commandsHost = nextCommands;\n if (nextEmit !== emitHost) {\n emitHost?.setSink(null);\n emitHost = nextEmit;\n emitHost.setSink((event) => recordOp({ op: \"event\", value: event }));\n }\n };\n\n const closeConn = (conn: Conn, code?: number, reason?: string) => {\n if (!conn.open) return;\n conn.open = false;\n conns.delete(conn);\n try {\n conn.socket.close(code, reason);\n } catch {\n /* peer already gone */\n }\n if (conns.size === 0 && heartbeatTimer !== null) {\n clearInterval(heartbeatTimer);\n heartbeatTimer = null;\n }\n };\n\n const sendTo = (conn: Conn, data: string) => {\n if (!conn.open) return;\n conn.lastSentAt = Date.now();\n try {\n conn.socket.send(data);\n } catch {\n closeConn(conn);\n }\n };\n\n const ensureHeartbeat = () => {\n if (heartbeatTimer !== null || heartbeatMs <= 0) return;\n heartbeatTimer = setInterval(() => {\n const now = Date.now();\n for (const conn of [...conns]) {\n if (now - conn.lastSentAt >= heartbeatMs) sendTo(conn, \"{}\");\n }\n }, heartbeatMs);\n (heartbeatTimer as unknown as { unref?: () => void }).unref?.();\n };\n\n const flush = (fin?: StatewireHost.Fin) => {\n flushTapSync(() => {});\n stateHost?.takeChanged();\n for (const rec of clients.values()) {\n for (const adm of rec.inflight.values()) {\n if (adm.acked && !adm.settled && !adm.pendingSent) {\n adm.pendingSent = true;\n queueRes(rec.id, { seq: adm.seq, type: \"pending\" });\n }\n }\n }\n if (\n pendingOps.length === 0 &&\n dirtyAcks.size === 0 &&\n pendingRes.size === 0 &&\n fin === undefined\n ) {\n return;\n }\n const ops = pendingOps;\n const acks = dirtyAcks;\n const res = pendingRes;\n pendingOps = [];\n dirtyAcks = new Set();\n pendingRes = new Map();\n for (const conn of [...conns]) {\n const envelope: StatewireEnvelope = {};\n if (ops.length > 0) envelope.ops = ops;\n const clientRes = res.get(conn.clientId);\n if (clientRes !== undefined) envelope.res = clientRes;\n if (acks.has(conn.clientId)) {\n const rec = clients.get(conn.clientId);\n if (rec !== undefined) envelope.ack = rec.lastSeq;\n }\n if (fin !== undefined) envelope.fin = fin;\n if (Object.keys(envelope).length === 0) continue;\n sendTo(conn, JSON.stringify(envelope));\n if (fin !== undefined) closeConn(conn);\n }\n };\n\n const queueRes = (clientId: string, response: StatewireResponse) => {\n const list = pendingRes.get(clientId);\n if (list) list.push(response);\n else pendingRes.set(clientId, [response]);\n };\n\n const advance = (rec: ClientRec, seq: number) => {\n rec.lastSeq = seq;\n dirtyAcks.add(rec.id);\n };\n\n const retain = (rec: ClientRec, response: StatewireResponse) => {\n const now = Date.now();\n rec.results.set(response.seq, { ts: now, res: response });\n const newest = Math.max(...rec.results.keys());\n for (const [seq, entry] of rec.results) {\n if (seq !== newest && now - entry.ts > RESULT_TTL_MS) {\n rec.results.delete(seq);\n }\n }\n while (rec.results.size > RESULT_LIMIT) {\n rec.results.delete(Math.min(...rec.results.keys()));\n }\n };\n\n const duplicateAnswer = (rec: ClientRec, seq: number): StatewireResponse => {\n const cached = rec.results.get(seq);\n if (cached) {\n const newest = Math.max(...rec.results.keys());\n if (seq === newest || Date.now() - cached.ts <= RESULT_TTL_MS) {\n return cached.res;\n }\n rec.results.delete(seq);\n }\n if (rec.inflight.has(seq)) return { seq, type: \"pending\" };\n return { seq, type: \"result-unavailable\" };\n };\n\n const settleWire = (\n rec: ClientRec,\n adm: Admission,\n outcome: CommandsOutcome,\n ) => {\n if (adm.settled) return;\n adm.settled = true;\n rec.inflight.delete(adm.seq);\n const response = responseOf(adm, outcome);\n retain(rec, response);\n if (!adm.acked) {\n adm.acked = true;\n advance(rec, adm.seq);\n }\n if (\n adm.pendingSent ||\n response.type !== \"accepted\" ||\n response.payload !== undefined\n ) {\n queueRes(rec.id, response);\n }\n scheduleFlush();\n adm.release();\n };\n\n const admit = (conn: Conn, frame: Frame): Promise<void> | void => {\n if (!conn.open || disposed || finished !== null) return;\n const rec = clients.get(conn.clientId)!;\n if (frame.seq <= rec.lastSeq || rec.inflight.has(frame.seq)) {\n queueRes(rec.id, duplicateAnswer(rec, frame.seq));\n scheduleFlush();\n return;\n }\n if (frame.seq > rec.lastSeq + 1) {\n closeConn(\n conn,\n 1008,\n `seq gap: expected ${rec.lastSeq + 1}, got ${frame.seq}`,\n );\n return;\n }\n const adm: Admission = {\n seq: frame.seq,\n method: frame.method,\n acked: false,\n pendingSent: false,\n settled: false,\n release: () => {},\n };\n const released = new Promise<void>((resolve) => {\n adm.release = resolve;\n });\n rec.inflight.set(frame.seq, adm);\n commandsHost!.submit!({\n method: frame.method,\n params: frame.params,\n clientId: rec.id,\n onAck: () => {\n if (adm.settled || adm.acked) return;\n adm.acked = true;\n advance(rec, adm.seq);\n scheduleFlush();\n adm.release();\n },\n onSettle: (outcome) => settleWire(rec, adm, outcome),\n }).catch((error: unknown) => {\n if (!adm.settled) settleWire(rec, adm, { type: \"rejected\", error });\n });\n return released;\n };\n\n const message = (conn: Conn, data: string) => {\n if (!conn.open || disposed || finished !== null) return;\n const frame = parseFrame(data);\n if (frame === null) {\n closeConn(conn, 1008, \"invalid frame\");\n return;\n }\n inbox = inbox.then(() => admit(conn, frame));\n };\n\n const connect = (\n clientId: string,\n socket: StatewireHostInternal.Socket,\n ): StatewireHostInternal.SocketConnection => {\n const dead: StatewireHostInternal.SocketConnection = {\n clientId,\n message: () => {},\n close: () => {},\n };\n const refuse = (code: number, reason: string) => {\n try {\n socket.close(code, reason);\n } catch {\n /* peer already gone */\n }\n return dead;\n };\n if (disposed) return refuse(1001, \"disposed\");\n if (!CLIENT_ID_RE.test(clientId)) return refuse(1008, \"invalid client id\");\n flush();\n if (finished !== null) {\n try {\n socket.send(JSON.stringify({ fin: finished }));\n } catch {\n /* peer already gone */\n }\n return refuse(1000, \"finished\");\n }\n const known = clients.has(clientId);\n const rec = known\n ? clients.get(clientId)!\n : (() => {\n const created: ClientRec = {\n id: clientId,\n lastSeq: 0,\n results: new Map(),\n inflight: new Map(),\n };\n clients.set(clientId, created);\n return created;\n })();\n for (const other of [...conns]) {\n if (other.clientId !== clientId) continue;\n sendTo(other, JSON.stringify({ fin: { reason: \"superseded\" } }));\n closeConn(other);\n }\n const conn: Conn = { clientId, socket, open: true, lastSentAt: 0 };\n conns.add(conn);\n ensureHeartbeat();\n const { value, meta } = encodeValue(stateHost!.snapshot());\n const snapshot: StatewireEnvelope = {\n ops: [\n {\n op: \"replace\",\n path: [],\n value,\n ...(meta !== undefined && { meta }),\n },\n ],\n syn: { lastSeq: known ? rec.lastSeq : -1, lease: randomId() },\n };\n sendTo(conn, JSON.stringify(snapshot));\n return {\n clientId,\n message: (data) => message(conn, data),\n close: () => closeConn(conn),\n };\n };\n\n const finish = (fin: StatewireHost.Fin) => {\n if (finished !== null || disposed) return;\n finished = fin;\n flush(fin);\n };\n\n return {\n connect,\n finish,\n attach,\n resume: () => {\n disposed = false;\n stateHost?.setOnOp(recordOp);\n emitHost?.setSink((event) => recordOp({ op: \"event\", value: event }));\n },\n suspend: () => {\n disposed = true;\n if (heartbeatTimer !== null) {\n clearInterval(heartbeatTimer);\n heartbeatTimer = null;\n }\n stateHost?.setOnOp(null);\n emitHost?.setSink(null);\n },\n };\n};\n\nconst useSocketHost = (\n transport: ResourceElement<Statewire>,\n options?: StatewireHostInternal.SocketHostOptions,\n): Pick<StatewireHostInternal.SocketHost, \"connect\" | \"finish\"> => {\n const [h] = useState(() => createSocketHost(options));\n const value = useResource(transport) as Statewire<unknown>;\n h.attach(value);\n useEffect(() => {\n h.attach(value);\n });\n useEffect(() => {\n h.resume();\n return () => h.suspend();\n }, [h]);\n return h;\n};\n\n/**\n * Transport-agnostic WS binding over a statewire/host transport:\n * feed it sockets (anything with `send`/`close`) and raw text frames, and it\n * speaks the Statewire envelope protocol — snapshot-first attach with `syn`,\n * delta/ack/res envelopes, per-client-id supersede, and `fin` on finish.\n */\nexport const StatewireSocketHost = (\n transport: ResourceElement<Statewire>,\n options?: StatewireHostInternal.SocketHostOptions,\n): StatewireHostInternal.SocketHost => {\n const SocketHostRoot = () => useSocketHost(transport, options);\n const root = createTapRoot(SocketHostRoot);\n let disposed = false;\n return {\n connect: (clientId, socket) =>\n flushTapSync(() => root.getValue().connect(clientId, socket)),\n finish: (fin) => flushTapSync(() => root.getValue().finish(fin)),\n dispose: () => {\n if (disposed) return;\n disposed = true;\n flushTapSync(() => root.getValue().finish({ reason: \"evicted\" }));\n root.unmount();\n },\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"socket.js","names":["useEffect","useState","createTapRoot","flushTapSync","useResource","ResourceElement","getCommandsHost","getEmitHost","getStateHost","CommandsHost","CommandsOutcome","StatewireSendError","Statewire","StatewireHost","StatewireHostInternal","encodeValue","reviveValue","createMacrotaskPoster","randomId","StatewireMetaEntry","StatewirePath","StatewireEnvelope","StatewireResponse","CLIENT_ID_RE","RESULT_TTL_MS","RESULT_LIMIT","WireOp","op","path","value","meta","Admission","seq","method","acked","pendingSent","settled","release","ClientRec","id","lastSeq","context","Record","results","Map","ts","res","inflight","Conn","clientId","socket","Socket","open","lastSentAt","Frame","params","parseFrame","raw","frame","JSON","parse","Array","isArray","undefined","Number","isInteger","contextValue","length","Error","encodePayload","payload","responseOf","adm","outcome","type","failure","error","fate","code","message","String","createSocketHost","options","SocketHostOptions","heartbeatMs","heartbeatIntervalMs","clients","conns","Set","pendingOps","dirtyAcks","pendingRes","inbox","Promise","resolve","finished","Fin","disposed","flushScheduled","heartbeatTimer","ReturnType","setInterval","stateHost","StateHost","commandsHost","emitHost","current","post","flush","scheduleFlush","recordOp","push","attach","next","nextState","state","nextCommands","commands","nextEmit","emit","submit","setOnOp","setSink","event","closeConn","conn","reason","delete","close","size","clearInterval","sendTo","data","Date","now","send","ensureHeartbeat","unref","fin","takeChanged","rec","values","queueRes","ops","acks","envelope","clientRes","get","has","ack","Object","keys","stringify","response","list","set","advance","add","retain","newest","Math","max","entry","min","duplicateAnswer","cached","settleResponse","settleWire","callerOf","Caller","isConnected","admit","released","caller","onAck","onSettle","catch","then","connect","SocketConnection","dead","refuse","test","known","created","other","snapshot","syn","lease","finish","resume","suspend","useSocketHost","transport","$","_c","t0","h","t1","t2","t3","StatewireSocketHost","SocketHost","SocketHostRoot","root","getValue","dispose","unmount"],"sources":["../../src/host/socket.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { createTapRoot, flushTapSync, useResource } from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { getCommandsHost, getEmitHost, getStateHost } from \"./host\";\nimport type { CommandsHost, CommandsOutcome } from \"./host\";\nimport { StatewireSendError } from \"../types\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireHost } from \"../host\";\nimport type { StatewireHostInternal } from \"../host-internal\";\nimport { encodeValue, reviveValue } from \"../wire/meta\";\nimport { createMacrotaskPoster } from \"../util/macrotask\";\nimport { randomId } from \"../util/randomId\";\nimport type { StatewireMetaEntry } from \"../wire/meta\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport type {\n StatewireEnvelope,\n StatewireResponse,\n} from \"../client/connection\";\n\nconst CLIENT_ID_RE = /^[A-Za-z0-9._~-]{1,256}$/;\nconst RESULT_TTL_MS = 30_000;\nconst RESULT_LIMIT = 256;\n\ntype WireOp = {\n op: string;\n path?: StatewirePath;\n value?: unknown;\n meta?: StatewireMetaEntry[];\n};\n\ntype Admission = {\n seq: number;\n method: string;\n acked: boolean;\n pendingSent: boolean;\n settled: boolean;\n release: () => void;\n};\n\ntype ClientRec = {\n id: string;\n lastSeq: number;\n context: Record<string, unknown> | null;\n results: Map<number, { ts: number; res: StatewireResponse }>;\n inflight: Map<number, Admission>;\n};\n\ntype Conn = {\n clientId: string;\n socket: StatewireHostInternal.Socket;\n open: boolean;\n lastSentAt: number;\n};\n\ntype Frame = {\n method?: string;\n seq: number;\n params: unknown[];\n context?: unknown;\n};\n\nconst parseFrame = (raw: string): Frame | null => {\n let frame: unknown;\n try {\n frame = JSON.parse(raw);\n } catch {\n return null;\n }\n if (typeof frame !== \"object\" || frame === null || Array.isArray(frame)) {\n return null;\n }\n const { method, seq, params, meta, context } = frame as Record<\n string,\n unknown\n >;\n if (method === undefined && context === undefined) return null;\n if (typeof seq !== \"number\" || !Number.isInteger(seq) || seq < 1) return null;\n if (method === undefined) {\n return { seq, params: [], ...(context !== undefined && { context }) };\n }\n if (typeof method !== \"string\") return null;\n if (!Array.isArray(params)) return null;\n if (meta !== undefined) {\n try {\n reviveValue(params, meta);\n } catch {\n return null;\n }\n }\n return { method, seq, params, ...(context !== undefined && { context }) };\n};\n\n/** v1 context submissions are exactly one root replace with an object value. */\nconst contextValue = (context: unknown): Record<string, unknown> => {\n if (!Array.isArray(context) || context.length !== 1) {\n throw new Error(\"context must be a single-op array\");\n }\n const op = context[0] as Record<string, unknown> | null;\n if (\n typeof op !== \"object\" ||\n op === null ||\n op[\"op\"] !== \"replace\" ||\n !Array.isArray(op[\"path\"]) ||\n op[\"path\"].length !== 0\n ) {\n throw new Error(\"context op must be a root replace\");\n }\n const value = op[\"value\"];\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new Error(\"context value must be an object\");\n }\n return value as Record<string, unknown>;\n};\n\nconst encodePayload = (\n payload: unknown,\n): { payload?: unknown; meta?: StatewireMetaEntry[] } => {\n if (payload === undefined) return {};\n const { value, meta } = encodeValue(payload);\n return { payload: value, ...(meta !== undefined && { meta }) };\n};\n\nconst responseOf = (\n adm: Admission,\n outcome: CommandsOutcome,\n): StatewireResponse => {\n if (outcome.type === \"accepted\") {\n return {\n seq: adm.seq,\n type: \"accepted\",\n ...encodePayload(outcome.payload),\n };\n }\n const failure =\n outcome.error instanceof StatewireSendError ? outcome.error.failure : null;\n switch (failure?.fate) {\n case \"unknown-command\":\n return {\n seq: adm.seq,\n type: \"rejected\",\n code: \"unknown-command\",\n message: `unknown method: '${adm.method}'`,\n };\n case \"invalid-params\":\n return {\n seq: adm.seq,\n type: \"rejected\",\n code: \"invalid-params\",\n message: failure.message,\n };\n case \"crashed\":\n return { seq: adm.seq, type: \"crashed\" };\n case \"rejected\":\n return {\n seq: adm.seq,\n type: \"rejected\",\n message: failure.message,\n ...encodePayload(failure.payload),\n };\n default:\n return {\n seq: adm.seq,\n type: \"rejected\",\n message:\n outcome.error instanceof Error\n ? outcome.error.message\n : String(outcome.error),\n };\n }\n};\n\nconst createSocketHost = (\n options?: StatewireHostInternal.SocketHostOptions,\n) => {\n const heartbeatMs = options?.heartbeatIntervalMs ?? 15_000;\n\n const clients = new Map<string, ClientRec>();\n const conns = new Set<Conn>();\n let pendingOps: WireOp[] = [];\n let dirtyAcks = new Set<string>();\n let pendingRes = new Map<string, StatewireResponse[]>();\n let inbox: Promise<void> = Promise.resolve();\n let finished: StatewireHost.Fin | null = null;\n let disposed = false;\n let flushScheduled = false;\n let heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n\n let stateHost: StatewireHost.StateHost | null = null;\n let commandsHost: CommandsHost | null = null;\n let emitHost: ReturnType<typeof getEmitHost> | null = null;\n let current: Statewire<unknown> | null = null;\n\n const post = createMacrotaskPoster(() => {\n flushScheduled = false;\n flush();\n });\n const scheduleFlush = () => {\n if (flushScheduled || disposed) return;\n flushScheduled = true;\n post();\n };\n\n const recordOp = (op: {\n op: string;\n path?: StatewirePath;\n value?: unknown;\n }) => {\n if (op.op === \"replace\" || op.op === \"add\" || op.op === \"event\") {\n const { value, meta } = encodeValue(op.value);\n pendingOps.push({ ...op, value, ...(meta !== undefined && { meta }) });\n } else {\n pendingOps.push(op);\n }\n scheduleFlush();\n };\n\n const attach = (next: Statewire<unknown>) => {\n if (next === current) return;\n current = next;\n const nextState =\n typeof next.state === \"object\" && next.state !== null\n ? getStateHost(next.state)\n : undefined;\n const nextCommands = getCommandsHost(next.commands);\n const nextEmit = getEmitHost(next.emit);\n if (!nextState || !nextCommands?.submit || !nextEmit) {\n throw new Error(\n \"StatewireSocketHost requires a statewire/host transport (useStatewireState with object state, useStatewireCommands, useStatewireEmit)\",\n );\n }\n if (nextState !== stateHost) {\n stateHost?.setOnOp(null);\n stateHost = nextState;\n stateHost.setOnOp(recordOp);\n }\n commandsHost = nextCommands;\n if (nextEmit !== emitHost) {\n emitHost?.setSink(null);\n emitHost = nextEmit;\n emitHost.setSink((event) => recordOp({ op: \"event\", value: event }));\n }\n };\n\n const closeConn = (conn: Conn, code?: number, reason?: string) => {\n if (!conn.open) return;\n conn.open = false;\n conns.delete(conn);\n try {\n conn.socket.close(code, reason);\n } catch {\n /* peer already gone */\n }\n if (conns.size === 0 && heartbeatTimer !== null) {\n clearInterval(heartbeatTimer);\n heartbeatTimer = null;\n }\n };\n\n const sendTo = (conn: Conn, data: string) => {\n if (!conn.open) return;\n conn.lastSentAt = Date.now();\n try {\n conn.socket.send(data);\n } catch {\n closeConn(conn);\n }\n };\n\n const ensureHeartbeat = () => {\n if (heartbeatTimer !== null || heartbeatMs <= 0) return;\n heartbeatTimer = setInterval(() => {\n const now = Date.now();\n for (const conn of [...conns]) {\n if (now - conn.lastSentAt >= heartbeatMs) sendTo(conn, \"{}\");\n }\n }, heartbeatMs);\n (heartbeatTimer as unknown as { unref?: () => void }).unref?.();\n };\n\n const flush = (fin?: StatewireHost.Fin) => {\n flushTapSync(() => {});\n stateHost?.takeChanged();\n for (const rec of clients.values()) {\n for (const adm of rec.inflight.values()) {\n if (adm.acked && !adm.settled && !adm.pendingSent) {\n adm.pendingSent = true;\n queueRes(rec.id, { seq: adm.seq, type: \"pending\" });\n }\n }\n }\n if (\n pendingOps.length === 0 &&\n dirtyAcks.size === 0 &&\n pendingRes.size === 0 &&\n fin === undefined\n ) {\n return;\n }\n const ops = pendingOps;\n const acks = dirtyAcks;\n const res = pendingRes;\n pendingOps = [];\n dirtyAcks = new Set();\n pendingRes = new Map();\n for (const conn of [...conns]) {\n const envelope: StatewireEnvelope = {};\n if (ops.length > 0) envelope.ops = ops;\n const clientRes = res.get(conn.clientId);\n if (clientRes !== undefined) envelope.res = clientRes;\n if (acks.has(conn.clientId)) {\n const rec = clients.get(conn.clientId);\n if (rec !== undefined) envelope.ack = rec.lastSeq;\n }\n if (fin !== undefined) envelope.fin = fin;\n if (Object.keys(envelope).length === 0) continue;\n sendTo(conn, JSON.stringify(envelope));\n if (fin !== undefined) closeConn(conn);\n }\n };\n\n const queueRes = (clientId: string, response: StatewireResponse) => {\n const list = pendingRes.get(clientId);\n if (list) list.push(response);\n else pendingRes.set(clientId, [response]);\n };\n\n const advance = (rec: ClientRec, seq: number) => {\n rec.lastSeq = seq;\n dirtyAcks.add(rec.id);\n };\n\n const retain = (rec: ClientRec, response: StatewireResponse) => {\n const now = Date.now();\n rec.results.set(response.seq, { ts: now, res: response });\n const newest = Math.max(...rec.results.keys());\n for (const [seq, entry] of rec.results) {\n if (seq !== newest && now - entry.ts > RESULT_TTL_MS) {\n rec.results.delete(seq);\n }\n }\n while (rec.results.size > RESULT_LIMIT) {\n rec.results.delete(Math.min(...rec.results.keys()));\n }\n };\n\n const duplicateAnswer = (rec: ClientRec, seq: number): StatewireResponse => {\n const cached = rec.results.get(seq);\n if (cached) {\n const newest = Math.max(...rec.results.keys());\n if (seq === newest || Date.now() - cached.ts <= RESULT_TTL_MS) {\n return cached.res;\n }\n rec.results.delete(seq);\n }\n if (rec.inflight.has(seq)) return { seq, type: \"pending\" };\n return { seq, type: \"result-unavailable\" };\n };\n\n const settleResponse = (\n rec: ClientRec,\n adm: Admission,\n response: StatewireResponse,\n ) => {\n if (adm.settled) return;\n adm.settled = true;\n rec.inflight.delete(adm.seq);\n retain(rec, response);\n if (!adm.acked) {\n adm.acked = true;\n advance(rec, adm.seq);\n }\n if (\n adm.pendingSent ||\n response.type !== \"accepted\" ||\n response.payload !== undefined\n ) {\n queueRes(rec.id, response);\n }\n scheduleFlush();\n adm.release();\n };\n\n const settleWire = (\n rec: ClientRec,\n adm: Admission,\n outcome: CommandsOutcome,\n ) => settleResponse(rec, adm, responseOf(adm, outcome));\n\n const callerOf = (rec: ClientRec): StatewireHost.Caller => ({\n get context() {\n return rec.context;\n },\n get isConnected() {\n if (clients.get(rec.id) !== rec) return false;\n for (const conn of conns) {\n if (conn.clientId === rec.id && conn.open) return true;\n }\n return false;\n },\n });\n\n const admit = (conn: Conn, frame: Frame): Promise<void> | void => {\n if (!conn.open || disposed || finished !== null) return;\n const rec = clients.get(conn.clientId)!;\n if (frame.seq <= rec.lastSeq || rec.inflight.has(frame.seq)) {\n queueRes(rec.id, duplicateAnswer(rec, frame.seq));\n scheduleFlush();\n return;\n }\n if (frame.seq > rec.lastSeq + 1) {\n closeConn(\n conn,\n 1008,\n `seq gap: expected ${rec.lastSeq + 1}, got ${frame.seq}`,\n );\n return;\n }\n const adm: Admission = {\n seq: frame.seq,\n method: frame.method ?? \"\",\n acked: false,\n pendingSent: false,\n settled: false,\n release: () => {},\n };\n if (frame.context !== undefined) {\n let value: Record<string, unknown>;\n try {\n value = contextValue(frame.context);\n } catch (error) {\n rec.inflight.set(frame.seq, adm);\n settleResponse(rec, adm, {\n seq: frame.seq,\n type: \"rejected\",\n code: \"invalid-context\",\n message: `invalid context: ${(error as Error).message}`,\n });\n return;\n }\n rec.context = value;\n if (frame.method === undefined) {\n rec.inflight.set(frame.seq, adm);\n settleResponse(rec, adm, { seq: frame.seq, type: \"accepted\" });\n return;\n }\n }\n const released = new Promise<void>((resolve) => {\n adm.release = resolve;\n });\n rec.inflight.set(frame.seq, adm);\n commandsHost!.submit!({\n method: frame.method!,\n params: frame.params,\n clientId: rec.id,\n caller: callerOf(rec),\n onAck: () => {\n if (adm.settled || adm.acked) return;\n adm.acked = true;\n advance(rec, adm.seq);\n scheduleFlush();\n adm.release();\n },\n onSettle: (outcome) => settleWire(rec, adm, outcome),\n }).catch((error: unknown) => {\n if (!adm.settled) settleWire(rec, adm, { type: \"rejected\", error });\n });\n return released;\n };\n\n const message = (conn: Conn, data: string) => {\n if (!conn.open || disposed || finished !== null) return;\n const frame = parseFrame(data);\n if (frame === null) {\n closeConn(conn, 1008, \"invalid frame\");\n return;\n }\n inbox = inbox.then(() => admit(conn, frame));\n };\n\n const connect = (\n clientId: string,\n socket: StatewireHostInternal.Socket,\n ): StatewireHostInternal.SocketConnection => {\n const dead: StatewireHostInternal.SocketConnection = {\n clientId,\n message: () => {},\n close: () => {},\n };\n const refuse = (code: number, reason: string) => {\n try {\n socket.close(code, reason);\n } catch {\n /* peer already gone */\n }\n return dead;\n };\n if (disposed) return refuse(1001, \"disposed\");\n if (!CLIENT_ID_RE.test(clientId)) return refuse(1008, \"invalid client id\");\n flush();\n if (finished !== null) {\n try {\n socket.send(JSON.stringify({ fin: finished }));\n } catch {\n /* peer already gone */\n }\n return refuse(1000, \"finished\");\n }\n const known = clients.has(clientId);\n const rec = known\n ? clients.get(clientId)!\n : (() => {\n const created: ClientRec = {\n id: clientId,\n lastSeq: 0,\n context: null,\n results: new Map(),\n inflight: new Map(),\n };\n clients.set(clientId, created);\n return created;\n })();\n for (const other of [...conns]) {\n if (other.clientId !== clientId) continue;\n sendTo(other, JSON.stringify({ fin: { reason: \"superseded\" } }));\n closeConn(other);\n }\n const conn: Conn = { clientId, socket, open: true, lastSentAt: 0 };\n conns.add(conn);\n ensureHeartbeat();\n const { value, meta } = encodeValue(stateHost!.snapshot());\n const snapshot: StatewireEnvelope = {\n ops: [\n {\n op: \"replace\",\n path: [],\n value,\n ...(meta !== undefined && { meta }),\n },\n ],\n syn: { lastSeq: known ? rec.lastSeq : -1, lease: randomId() },\n };\n sendTo(conn, JSON.stringify(snapshot));\n return {\n clientId,\n message: (data) => message(conn, data),\n close: () => closeConn(conn),\n };\n };\n\n const finish = (fin: StatewireHost.Fin) => {\n if (finished !== null || disposed) return;\n finished = fin;\n flush(fin);\n };\n\n return {\n connect,\n finish,\n attach,\n resume: () => {\n disposed = false;\n stateHost?.setOnOp(recordOp);\n emitHost?.setSink((event) => recordOp({ op: \"event\", value: event }));\n },\n suspend: () => {\n disposed = true;\n if (heartbeatTimer !== null) {\n clearInterval(heartbeatTimer);\n heartbeatTimer = null;\n }\n stateHost?.setOnOp(null);\n emitHost?.setSink(null);\n },\n };\n};\n\nconst useSocketHost = (\n transport: ResourceElement<Statewire>,\n options?: StatewireHostInternal.SocketHostOptions,\n): Pick<StatewireHostInternal.SocketHost, \"connect\" | \"finish\"> => {\n const [h] = useState(() => createSocketHost(options));\n const value = useResource(transport) as Statewire<unknown>;\n h.attach(value);\n useEffect(() => {\n h.attach(value);\n });\n useEffect(() => {\n h.resume();\n return () => h.suspend();\n }, [h]);\n return h;\n};\n\n/**\n * Transport-agnostic WS binding over a statewire/host transport:\n * feed it sockets (anything with `send`/`close`) and raw text frames, and it\n * speaks the Statewire envelope protocol — snapshot-first attach with `syn`,\n * delta/ack/res envelopes, per-client-id supersede, and `fin` on finish.\n */\nexport const StatewireSocketHost = (\n transport: ResourceElement<Statewire>,\n options?: StatewireHostInternal.SocketHostOptions,\n): StatewireHostInternal.SocketHost => {\n const SocketHostRoot = () => useSocketHost(transport, options);\n const root = createTapRoot(SocketHostRoot);\n let disposed = false;\n return {\n connect: (clientId, socket) =>\n flushTapSync(() => root.getValue().connect(clientId, socket)),\n finish: (fin) => flushTapSync(() => root.getValue().finish(fin)),\n dispose: () => {\n if (disposed) return;\n disposed = true;\n flushTapSync(() => root.getValue().finish({ reason: \"evicted\" }));\n root.unmount();\n },\n };\n};\n"],"mappings":"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"}
package/dist/host.d.ts CHANGED
@@ -1,7 +1,7 @@
1
+ import { Statewire } from "./types.js";
1
2
  import { ServerOp, append } from "./host/draft.js";
2
3
  import { StatewireReject } from "./host/engine.js";
3
4
  import { CommandExecution, getStateHost, useStatewireCommands, useStatewireEmit, useStatewireState } from "./host/host.js";
4
- import { Statewire } from "./types.js";
5
5
  //#region src/host.d.ts
6
6
  declare const executionResult: unique symbol;
7
7
  type UnwrapExecution<V> = V extends StatewireHost.Execution<infer T> ? T : V;
@@ -31,6 +31,16 @@ declare namespace StatewireHost {
31
31
  resolve: (value: T) => void;
32
32
  reject: (error: unknown) => void;
33
33
  };
34
+ /**
35
+ * The submitting client, bound as `this` of a command handler. Holdable
36
+ * across a long run: `context` reads the client's current ambient context
37
+ * live (null until the client sends one), `isConnected` reports whether the
38
+ * client has an active stream attachment.
39
+ */
40
+ type Caller = {
41
+ readonly context: Record<string, unknown> | null;
42
+ readonly isConnected: boolean;
43
+ };
34
44
  type HandledCommands<THandlers extends Statewire.Commands> = { [K in keyof THandlers]: (...args: Parameters<THandlers[K]>) => UnwrapExecution<Awaited<ReturnType<THandlers[K]>>>; };
35
45
  type Fin = {
36
46
  reason: "evicted" | "error" | "gone" | "superseded" | (string & {});
@@ -1 +1 @@
1
- {"version":3,"file":"host.d.ts","names":[],"sources":["../src/host.ts"],"mappings":";;;;;cAac;KAET,gBAAgB,KAAK,UAAU,cAAc,gBAAgB,KAAK,IAAI;kBAE1D;OACH,SAAS;;IAEnB,QAAQ,cAAc,UAAU;;IAEhC,gBAAgB;;OAGN,YAAY,UAAU;;IAEhC,UAAU,YAAY,IAAI;;OAGhB,cAAc,UAAU;;;;;;;OAQxB,UAAU;cACV,mBAAmB,OAAO;;OAG1B,mBAAmB;IAC7B;IACA,UAAU,OAAO;IACjB,SAAS;;OAGC,gBAAgB,kBAAkB,UAAU,eACrD,WAAW,gBACP,MAAM,WAAW,UAAU,QAC3B,gBAAgB,QAAQ,WAAW,UAAU;OAGxC;IACV;IACA;IACA;IACA"}
1
+ {"version":3,"file":"host.d.ts","names":[],"sources":["../src/host.ts"],"mappings":";;;;;cAac;KAET,gBAAgB,KAAK,UAAU,cAAc,gBAAgB,KAAK,IAAI;kBAE1D;OACH,SAAS;;IAEnB,QAAQ,cAAc,UAAU;;IAEhC,gBAAgB;;OAGN,YAAY,UAAU;;IAEhC,UAAU,YAAY,IAAI;;OAGhB,cAAc,UAAU;;;;;;;OAQxB,UAAU;cACV,mBAAmB,OAAO;;OAG1B,mBAAmB;IAC7B;IACA,UAAU,OAAO;IACjB,SAAS;;;;;;;;OASC;aACD,SAAS;aACT;;OAGC,gBAAgB,kBAAkB,UAAU,eACrD,WAAW,gBACP,MAAM,WAAW,UAAU,QAC3B,gBAAgB,QAAQ,WAAW,UAAU;OAGxC;IACV;IACA;IACA;IACA"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { StatewireSSE } from "./transports/sse.js";
2
1
  import { StatewireClient, StatewireResource, useStatewire } from "./statewire.js";
3
2
  import { Statewire, StatewireSendError } from "./types.js";
3
+ import { StatewireSSE } from "./transports/sse.js";
4
4
  import { StatewireWS } from "./transports/ws.js";
5
5
  export { type Statewire, StatewireClient, StatewireResource, StatewireSSE, StatewireSendError, StatewireWS, useStatewire };
@@ -1,4 +1,3 @@
1
- import { ConnectionState } from "./transports/sse.js";
2
1
  import { StatewireSessionStoreOptions } from "./client/sessionStore.js";
3
2
  import { Statewire } from "./types.js";
4
3
  import { ResourceElement } from "@assistant-ui/tap";
@@ -12,6 +11,7 @@ declare class StatewireClient<TState, TCommands extends Statewire.Commands = Sta
12
11
  get state(): TState;
13
12
  get connection(): StatewireClient.Connection;
14
13
  get commands(): Statewire.CommandsProxy<TCommands>;
14
+ setContext(value: Record<string, unknown>): Promise<unknown>;
15
15
  subscribe(listener: () => void): () => void;
16
16
  dispose(): void;
17
17
  }
@@ -36,6 +36,24 @@ declare namespace StatewireClient {
36
36
  getState: () => TState;
37
37
  connection: Connection;
38
38
  commands: Statewire.CommandsProxy<TCommands>;
39
+ /** Replace this client's ambient context on the server; throws on transports without a client context channel. */
40
+ setContext: (value: Record<string, unknown>) => Promise<unknown>;
41
+ };
42
+ type ConnectionState = {
43
+ status: "connecting";
44
+ } | {
45
+ status: "live";
46
+ } | {
47
+ status: "reconnecting";
48
+ cause: "dropped" | "evicted" | "error";
49
+ attempt: number;
50
+ nextRetryAt: number;
51
+ lastError?: string;
52
+ message?: string;
53
+ } | {
54
+ status: "gone";
55
+ message?: string;
56
+ payload?: unknown;
39
57
  };
40
58
  type Connection = ConnectionState & {
41
59
  reconnect: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"statewire.d.ts","names":[],"sources":["../src/statewire.ts"],"mappings":";;;;;cAaa,eACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD,cAEA,SAAS,gBAAgB,QAAQ,QAAQ,WAAW,YACnD,gBAAgB,QAAQ,QAAQ;cA6BtB,oBACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD,cAEA,SAAS,gBAAgB,QAAQ,QAAQ,WAAW,YACnD,gBAAgB,gBAAgB,QAAQ,QAAQ;cAGtC,gBACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD;mBAEiB;UACT;EAEI,YAAA,SAAS,gBAAgB,QAAQ,QAAQ,WAAW;MAK5D,SAAS;MAIT,cAAc,gBAAgB;MAI9B,YAAY,UAAU,cAAc;EAIxC,UAAU;EAIV;;kBAOe;OACH,QACV,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD;IAEA,WAAW,gBAAgB,UAAU,QAAQ,WAAW;IACxD,aAAa,WAAW,YAAY,SAAS;IAC7C,WAAW,OAAO,QAAQ;MAAO,OAAO,YAAY;;IACpD,iBACE,OAAO,YAAY,SACnB;MAAO,SAAS,UAAU;;IAE5B,mBACE,QAAQ,UAAU,cAAc,YAChC;MAAO,OAAO,YAAY;;;OAIlB,QACV,QACA,kBAAkB,UAAU,WAAW,UAAU;;IAGjD,OAAO;;IAEP,gBAAgB;IAChB,YAAY;IACZ,UAAU,UAAU,cAAc;;OAGxB,aAAa;IAAoB;;OACjC,mBAAmB;;;;;OAMnB,WACV,QACA,kBAAkB,UAAU,WAAW,UAAU,aAEjD,OAAO,QACP,SAAS,UAAU,QAAQ,YAC3B,KAAK;OAGK;IACV;IACA;IACA;;;;;;;;;;;OAYU;IACN;IAAkB;IAAiB;IAAiB;;IACpD;IAAyB;;IACzB;IAAwB;IAAiB;;IACzC;IAAiB;;IAEjB;IACA;;IAQA;IACA;;IAQA;IACA;;OAGM,aAAa;IACvB;IACA,UAAU,qBAAqB,cAAc,QAAQ;IACrD,eAAe,WAAW;IAC1B,YAAY,YAAY;;OAGd,YAAY;IACtB;IACA;IACA,QACI,gCACO,yBAAyB,QAAQ;IAC5C,iBAAiB,aAAa,kCAAkC;IAChE,YAAY,YAAY"}
1
+ {"version":3,"file":"statewire.d.ts","names":[],"sources":["../src/statewire.ts"],"mappings":";;;;cAYa,eACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD,cAEA,SAAS,gBAAgB,QAAQ,QAAQ,WAAW,YACnD,gBAAgB,QAAQ,QAAQ;cAqCtB,oBACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD,cAEA,SAAS,gBAAgB,QAAQ,QAAQ,WAAW,YACnD,gBAAgB,gBAAgB,QAAQ,QAAQ;cAGtC,gBACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD;mBAEiB;UACT;EAEI,YAAA,SAAS,gBAAgB,QAAQ,QAAQ,WAAW;MAK5D,SAAS;MAIT,cAAc,gBAAgB;MAI9B,YAAY,UAAU,cAAc;EAIxC,WAAW,OAAO,0BAA0B;EAI5C,UAAU;EAIV;;kBAOe;OACH,QACV,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD;IAEA,WAAW,gBAAgB,UAAU,QAAQ,WAAW;IACxD,aAAa,WAAW,YAAY,SAAS;IAC7C,WAAW,OAAO,QAAQ;MAAO,OAAO,YAAY;;IACpD,iBACE,OAAO,YAAY,SACnB;MAAO,SAAS,UAAU;;IAE5B,mBACE,QAAQ,UAAU,cAAc,YAChC;MAAO,OAAO,YAAY;;;OAIlB,QACV,QACA,kBAAkB,UAAU,WAAW,UAAU;;IAGjD,OAAO;;IAEP,gBAAgB;IAChB,YAAY;IACZ,UAAU,UAAU,cAAc;;IAElC,aAAa,OAAO,4BAA4B;;OAGtC;IACN;;IACA;;IAEA;IACA;IACA;IACA;IACA;IACA;;IAEA;IAAgB;IAAkB;;OAC5B,aAAa;IAAoB;;OACjC,mBAAmB;;;;;OAMnB,WACV,QACA,kBAAkB,UAAU,WAAW,UAAU,aAEjD,OAAO,QACP,SAAS,UAAU,QAAQ,YAC3B,KAAK;OAGK;IACV;IACA;IACA;;;;;;;;;;;OAYU;IACN;IAAkB;IAAiB;IAAiB;;IACpD;IAAyB;;IACzB;IAAwB;IAAiB;;IACzC;IAAiB;;IAEjB;IACA;;IAQA;IACA;;IAQA;IACA;;OAGM,aAAa;IACvB;IACA,UAAU,qBAAqB,cAAc,QAAQ;IACrD,eAAe,WAAW;IAC1B,YAAY,YAAY;;OAGd,YAAY;IACtB;IACA;IACA,QACI,gCACO,yBAAyB,QAAQ;IAC5C,iBAAiB,aAAa,kCAAkC;IAChE,YAAY,YAAY"}
package/dist/statewire.js CHANGED
@@ -14,11 +14,16 @@ const useStatewire = (options) => {
14
14
  state,
15
15
  getState: core.getState,
16
16
  connection,
17
- commands: core.commands
17
+ commands: core.commands,
18
+ setContext: (contextValue) => {
19
+ if (!value.setContext) throw new Error("statewire: this transport has no client context channel");
20
+ return value.setContext(contextValue);
21
+ }
18
22
  }), [
19
23
  state,
20
24
  connection,
21
- core
25
+ core,
26
+ value
22
27
  ]);
23
28
  };
24
29
  const StatewireResource = (options) => resource(useStatewire)(options);
@@ -38,6 +43,9 @@ var StatewireClient = class {
38
43
  get commands() {
39
44
  return this.root.getValue().commands;
40
45
  }
46
+ setContext(value) {
47
+ return this.root.getValue().setContext(value);
48
+ }
41
49
  subscribe(listener) {
42
50
  return this.root.subscribe(listener);
43
51
  }
@@ -1 +1 @@
1
- {"version":3,"file":"statewire.js","names":["useMemo","useRef","useState","useSyncExternalStore","createTapRoot","flushTapSync","resource","useResource","ResourceElement","LIVE_CONNECTION","useStatewireCore","Statewire","ConnectionState","StatewireSessionStoreOptions","useStatewire","Commands","options","StatewireClient","Options","TState","TCommands","TEvent","Helpers","latest","current","value","transport","core","subscribe","listener","state","getState","connection","commands","CommandsProxy","StatewireResource","root","disposed","constructor","ClientRoot","getValue","Connection","dispose","unmount","optimistic","Optimistic","NonNullable","onEvent","event","ctx","onStateChange","changes","Changes","onCommandChange","update","CommandUpdate","reconnect","ConnectionStatus","draft","command","Command","OptimisticContext","ts","seq","clientId","SendFailure","fate","cause","message","payload","SSEOptions","url","headers","HeadersInit","Promise","fetch","globalThis","onChange","WSOptions","protocols","query","Record","webSocket","WebSocket"],"sources":["../src/statewire.ts"],"sourcesContent":["import { useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport {\n createTapRoot,\n flushTapSync,\n resource,\n useResource,\n} from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { LIVE_CONNECTION, useStatewireCore } from \"./client/core\";\nimport type { Statewire } from \"./types\";\nimport type { ConnectionState } from \"./transports/sse\";\nimport type { StatewireSessionStoreOptions } from \"./client/sessionStore\";\n\nexport const useStatewire = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n>(\n options: StatewireClient.Options<TState, TCommands, TEvent>,\n): StatewireClient.Helpers<TState, TCommands> => {\n const latest = useRef(options);\n latest.current = options;\n\n const value = useResource(options.transport) as Statewire<unknown>;\n const core = useStatewireCore(() => latest.current, value);\n const [subscribe] = useState(\n () => (listener: () => void) =>\n core.subscribe(() => flushTapSync(listener)),\n );\n\n const state = useSyncExternalStore(\n subscribe,\n core.getState,\n core.getState,\n ) as TState;\n const connection = value.connection ?? LIVE_CONNECTION;\n\n return useMemo(\n () => ({\n state,\n getState: core.getState as () => TState,\n connection,\n commands: core.commands as Statewire.CommandsProxy<TCommands>,\n }),\n [state, connection, core],\n );\n};\n\nexport const StatewireResource = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n>(\n options: StatewireClient.Options<TState, TCommands, TEvent>,\n): ResourceElement<StatewireClient.Helpers<TState, TCommands>> =>\n resource(useStatewire<TState, TCommands, TEvent>)(options);\n\nexport class StatewireClient<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n> {\n private readonly root;\n private disposed = false;\n\n constructor(options: StatewireClient.Options<TState, TCommands, TEvent>) {\n const ClientRoot = () => useStatewire<TState, TCommands, TEvent>(options);\n this.root = createTapRoot(ClientRoot);\n }\n\n get state(): TState {\n return this.root.getValue().getState();\n }\n\n get connection(): StatewireClient.Connection {\n return this.root.getValue().connection;\n }\n\n get commands(): Statewire.CommandsProxy<TCommands> {\n return this.root.getValue().commands;\n }\n\n subscribe(listener: () => void): () => void {\n return this.root.subscribe(listener);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.root.unmount();\n }\n}\n\nexport namespace StatewireClient {\n export type Options<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n > = {\n transport: ResourceElement<Statewire<TState, TCommands, TEvent>>;\n optimistic?: Optimistic<NonNullable<TState>, TCommands>;\n onEvent?: (event: TEvent, ctx: { state: NonNullable<TState> }) => void;\n onStateChange?: (\n state: NonNullable<TState>,\n ctx: { changes: Statewire.Changes },\n ) => void;\n onCommandChange?: (\n update: Statewire.CommandUpdate<TCommands>,\n ctx: { state: NonNullable<TState> },\n ) => void;\n };\n\n export type Helpers<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = {\n /** The statewire's state type — remote statewires include `undefined` until the first attach snapshot applies. */\n state: TState;\n /** Identity-stable across renders; reads the current canonical state without subscribing the caller. */\n getState: () => TState;\n connection: Connection;\n commands: Statewire.CommandsProxy<TCommands>;\n };\n\n export type Connection = ConnectionState & { reconnect: () => void };\n export type ConnectionStatus = Connection[\"status\"];\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 */\n export type Optimistic<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = (\n draft: TState,\n command: Statewire.Command<TCommands>,\n ctx: OptimisticContext,\n ) => void;\n\n export type OptimisticContext = {\n ts: number;\n seq: number;\n clientId?: string;\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 */\n export 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\n export type SSEOptions = StatewireSessionStoreOptions & {\n url: string;\n headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);\n fetch?: typeof globalThis.fetch;\n onChange?: (connection: Connection) => void;\n };\n\n export type WSOptions = StatewireSessionStoreOptions & {\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: Connection) => void;\n };\n}\n"],"mappings":";;;;AAaA,MAAac,gBAKXE,YAC+C;CAC/C,MAAMO,SAAStB,OAAOe,OAAO;CAC7BO,OAAOC,UAAUR;CAEjB,MAAMS,QAAQlB,YAAYS,QAAQU,SAAS;CAC3C,MAAMC,OAAOjB,uBAAuBa,OAAOC,SAASC,KAAK;CACzD,MAAM,CAACG,aAAa1B,gBACX2B,aACLF,KAAKC,gBAAgBvB,aAAawB,QAAQ,CAAC,CAC/C;CAEA,MAAMC,QAAQ3B,qBACZyB,WACAD,KAAKI,UACLJ,KAAKI,QACP;CACA,MAAMC,aAAaP,MAAMO,cAAcvB;CAEvC,OAAOT,eACE;EACL8B;EACAC,UAAUJ,KAAKI;EACfC;EACAC,UAAUN,KAAKM;CACjB,IACA;EAACH;EAAOE;EAAYL;CAAI,CAC1B;AACF;AAEA,MAAaQ,qBAKXnB,YAEAV,SAASQ,YAAuC,CAAC,CAACE,OAAO;AAE3D,IAAaC,kBAAb,MAIE;CACA;CACA,WAAmB;CAEnBqB,YAAYtB,SAA6D;EACvE,MAAMuB,mBAAmBzB,aAAwCE,OAAO;EACxE,KAAKoB,OAAOhC,cAAcmC,UAAU;CACtC;CAEA,IAAIT,QAAgB;EAClB,OAAO,KAAKM,KAAKI,SAAS,CAAC,CAACT,SAAS;CACvC;CAEA,IAAIC,aAAyC;EAC3C,OAAO,KAAKI,KAAKI,SAAS,CAAC,CAACR;CAC9B;CAEA,IAAIC,WAA+C;EACjD,OAAO,KAAKG,KAAKI,SAAS,CAAC,CAACP;CAC9B;CAEAL,UAAUC,UAAkC;EAC1C,OAAO,KAAKO,KAAKR,UAAUC,QAAQ;CACrC;CAEAa,UAAgB;EACd,IAAI,KAAKL,UAAU;EACnB,KAAKA,WAAW;EAChB,KAAKD,KAAKO,QAAQ;CACpB;AACF"}
1
+ {"version":3,"file":"statewire.js","names":["useMemo","useRef","useState","useSyncExternalStore","createTapRoot","flushTapSync","resource","useResource","ResourceElement","LIVE_CONNECTION","useStatewireCore","Statewire","StatewireSessionStoreOptions","useStatewire","Commands","options","StatewireClient","Options","TState","TCommands","TEvent","Helpers","latest","current","value","transport","core","subscribe","listener","state","getState","connection","commands","CommandsProxy","setContext","contextValue","Record","Error","StatewireResource","root","disposed","constructor","ClientRoot","getValue","Connection","Promise","dispose","unmount","optimistic","Optimistic","NonNullable","onEvent","event","ctx","onStateChange","changes","Changes","onCommandChange","update","CommandUpdate","ConnectionState","status","cause","attempt","nextRetryAt","lastError","message","payload","reconnect","ConnectionStatus","draft","command","Command","OptimisticContext","ts","seq","clientId","SendFailure","fate","SSEOptions","url","headers","HeadersInit","fetch","globalThis","onChange","WSOptions","protocols","query","webSocket","WebSocket"],"sources":["../src/statewire.ts"],"sourcesContent":["import { useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport {\n createTapRoot,\n flushTapSync,\n resource,\n useResource,\n} from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { LIVE_CONNECTION, useStatewireCore } from \"./client/core\";\nimport type { Statewire } from \"./types\";\nimport type { StatewireSessionStoreOptions } from \"./client/sessionStore\";\n\nexport const useStatewire = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n>(\n options: StatewireClient.Options<TState, TCommands, TEvent>,\n): StatewireClient.Helpers<TState, TCommands> => {\n const latest = useRef(options);\n latest.current = options;\n\n const value = useResource(options.transport) as Statewire<unknown>;\n const core = useStatewireCore(() => latest.current, value);\n const [subscribe] = useState(\n () => (listener: () => void) =>\n core.subscribe(() => flushTapSync(listener)),\n );\n\n const state = useSyncExternalStore(\n subscribe,\n core.getState,\n core.getState,\n ) as TState;\n const connection = value.connection ?? LIVE_CONNECTION;\n\n return useMemo(\n () => ({\n state,\n getState: core.getState as () => TState,\n connection,\n commands: core.commands as Statewire.CommandsProxy<TCommands>,\n setContext: (contextValue: Record<string, unknown>) => {\n if (!value.setContext) {\n throw new Error(\n \"statewire: this transport has no client context channel\",\n );\n }\n return value.setContext(contextValue);\n },\n }),\n [state, connection, core, value],\n );\n};\n\nexport const StatewireResource = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n>(\n options: StatewireClient.Options<TState, TCommands, TEvent>,\n): ResourceElement<StatewireClient.Helpers<TState, TCommands>> =>\n resource(useStatewire<TState, TCommands, TEvent>)(options);\n\nexport class StatewireClient<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n> {\n private readonly root;\n private disposed = false;\n\n constructor(options: StatewireClient.Options<TState, TCommands, TEvent>) {\n const ClientRoot = () => useStatewire<TState, TCommands, TEvent>(options);\n this.root = createTapRoot(ClientRoot);\n }\n\n get state(): TState {\n return this.root.getValue().getState();\n }\n\n get connection(): StatewireClient.Connection {\n return this.root.getValue().connection;\n }\n\n get commands(): Statewire.CommandsProxy<TCommands> {\n return this.root.getValue().commands;\n }\n\n setContext(value: Record<string, unknown>): Promise<unknown> {\n return this.root.getValue().setContext(value);\n }\n\n subscribe(listener: () => void): () => void {\n return this.root.subscribe(listener);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.root.unmount();\n }\n}\n\nexport namespace StatewireClient {\n export type Options<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n > = {\n transport: ResourceElement<Statewire<TState, TCommands, TEvent>>;\n optimistic?: Optimistic<NonNullable<TState>, TCommands>;\n onEvent?: (event: TEvent, ctx: { state: NonNullable<TState> }) => void;\n onStateChange?: (\n state: NonNullable<TState>,\n ctx: { changes: Statewire.Changes },\n ) => void;\n onCommandChange?: (\n update: Statewire.CommandUpdate<TCommands>,\n ctx: { state: NonNullable<TState> },\n ) => void;\n };\n\n export type Helpers<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = {\n /** The statewire's state type — remote statewires include `undefined` until the first attach snapshot applies. */\n state: TState;\n /** Identity-stable across renders; reads the current canonical state without subscribing the caller. */\n getState: () => TState;\n connection: Connection;\n commands: Statewire.CommandsProxy<TCommands>;\n /** Replace this client's ambient context on the server; throws on transports without a client context channel. */\n setContext: (value: Record<string, unknown>) => Promise<unknown>;\n };\n\n export type ConnectionState =\n | { status: \"connecting\" }\n | { status: \"live\" }\n | {\n status: \"reconnecting\";\n cause: \"dropped\" | \"evicted\" | \"error\";\n attempt: number;\n nextRetryAt: number;\n lastError?: string;\n message?: string;\n }\n | { status: \"gone\"; message?: string; payload?: unknown };\n export type Connection = ConnectionState & { reconnect: () => void };\n export type ConnectionStatus = Connection[\"status\"];\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 */\n export type Optimistic<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = (\n draft: TState,\n command: Statewire.Command<TCommands>,\n ctx: OptimisticContext,\n ) => void;\n\n export type OptimisticContext = {\n ts: number;\n seq: number;\n clientId?: string;\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 */\n export 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\n export type SSEOptions = StatewireSessionStoreOptions & {\n url: string;\n headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);\n fetch?: typeof globalThis.fetch;\n onChange?: (connection: Connection) => void;\n };\n\n export type WSOptions = StatewireSessionStoreOptions & {\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: Connection) => void;\n };\n}\n"],"mappings":";;;;AAYA,MAAaa,gBAKXE,YAC+C;CAC/C,MAAMO,SAASrB,OAAOc,OAAO;CAC7BO,OAAOC,UAAUR;CAEjB,MAAMS,QAAQjB,YAAYQ,QAAQU,SAAS;CAC3C,MAAMC,OAAOhB,uBAAuBY,OAAOC,SAASC,KAAK;CACzD,MAAM,CAACG,aAAazB,gBACX0B,aACLF,KAAKC,gBAAgBtB,aAAauB,QAAQ,CAAC,CAC/C;CAEA,MAAMC,QAAQ1B,qBACZwB,WACAD,KAAKI,UACLJ,KAAKI,QACP;CACA,MAAMC,aAAaP,MAAMO,cAActB;CAEvC,OAAOT,eACE;EACL6B;EACAC,UAAUJ,KAAKI;EACfC;EACAC,UAAUN,KAAKM;EACfE,aAAaC,iBAA0C;GACrD,IAAI,CAACX,MAAMU,YACT,MAAM,IAAIG,MACR,yDACF;GAEF,OAAOb,MAAMU,WAAWC,YAAY;EACtC;CACF,IACA;EAACN;EAAOE;EAAYL;EAAMF;CAAK,CACjC;AACF;AAEA,MAAac,qBAKXvB,YAEAT,SAASO,YAAuC,CAAC,CAACE,OAAO;AAE3D,IAAaC,kBAAb,MAIE;CACA;CACA,WAAmB;CAEnByB,YAAY1B,SAA6D;EACvE,MAAM2B,mBAAmB7B,aAAwCE,OAAO;EACxE,KAAKwB,OAAOnC,cAAcsC,UAAU;CACtC;CAEA,IAAIb,QAAgB;EAClB,OAAO,KAAKU,KAAKI,SAAS,CAAC,CAACb,SAAS;CACvC;CAEA,IAAIC,aAAyC;EAC3C,OAAO,KAAKQ,KAAKI,SAAS,CAAC,CAACZ;CAC9B;CAEA,IAAIC,WAA+C;EACjD,OAAO,KAAKO,KAAKI,SAAS,CAAC,CAACX;CAC9B;CAEAE,WAAWV,OAAkD;EAC3D,OAAO,KAAKe,KAAKI,SAAS,CAAC,CAACT,WAAWV,KAAK;CAC9C;CAEAG,UAAUC,UAAkC;EAC1C,OAAO,KAAKW,KAAKZ,UAAUC,QAAQ;CACrC;CAEAkB,UAAgB;EACd,IAAI,KAAKN,UAAU;EACnB,KAAKA,WAAW;EAChB,KAAKD,KAAKQ,QAAQ;CACpB;AACF"}
@@ -1,5 +1,5 @@
1
- import { StatewireHost } from "../host.js";
2
1
  import { Statewire } from "../types.js";
2
+ import { StatewireHost } from "../host.js";
3
3
  import "../index.js";
4
4
  //#region src/transports/fixtures.d.ts
5
5
  /** Let several rounds of queued microtasks and op batches drain. */
@@ -1,7 +1,7 @@
1
- import { RemoteLink, StatewireRemoteProps } from "./sse.js";
2
1
  import { StatewireSessionStoreOptions } from "../client/sessionStore.js";
3
2
  import { StatewireClient } from "../statewire.js";
4
3
  import { Statewire } from "../types.js";
4
+ import { RemoteLink, StatewireRemoteProps } from "./sse.js";
5
5
  //#region src/transports/remote.d.ts
6
6
  declare const useStatewireRemote: <TOptions extends {
7
7
  url: string;
@@ -1 +1 @@
1
- {"version":3,"file":"remote.d.ts","names":[],"sources":["../../src/transports/remote.ts"],"mappings":";;;;;cAQa,qBACX;EACE;EACA,YAAY,YAAY,gBAAgB;IACtC,8BAEJ,SAAS,UACT,UAAU,SAAS,UAAU,KAAK,yBAAyB,eAC1D"}
1
+ {"version":3,"file":"remote.d.ts","names":[],"sources":["../../src/transports/remote.ts"],"mappings":";;;;;cAYa,qBACX;EACE;EACA,YAAY,YAAY,gBAAgB;IACtC,8BAEJ,SAAS,UACT,UAAU,SAAS,UAAU,KAAK,yBAAyB,eAC1D"}
@@ -1,5 +1,5 @@
1
1
  import { useStatewireEmit } from "../host/host.js";
2
- import { setTransport, useStatewireSession } from "../client/session.js";
2
+ import { setContext, setTransport, useStatewireSession } from "../client/session.js";
3
3
  import { useEffect } from "@assistant-ui/tap/react-shim";
4
4
  //#region src/transports/remote.ts
5
5
  const useStatewireRemote = (options, useLink) => {
@@ -12,7 +12,8 @@ const useStatewireRemote = (options, useLink) => {
12
12
  commands: s.commands,
13
13
  emit,
14
14
  connection: conn.connection,
15
- source: s.source
15
+ source: s.source,
16
+ setContext: (value) => setContext(s, value)
16
17
  };
17
18
  };
18
19
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"remote.js","names":["useEffect","useStatewireEmit","Statewire","setTransport","useStatewireSession","StatewireSessionStoreOptions","StatewireClient","RemoteLink","StatewireRemoteProps","useStatewireRemote","url","onChange","connection","Connection","options","TOptions","useLink","ctx","emit","s","sessionStore","conn","state","canonical","commands","source"],"sources":["../../src/transports/remote.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport { useStatewireEmit } from \"../host/host\";\nimport type { Statewire } from \"../types\";\nimport { setTransport, useStatewireSession } from \"../client/session\";\nimport type { StatewireSessionStoreOptions } from \"../client/sessionStore\";\nimport type { StatewireClient } from \"../statewire\";\nimport type { RemoteLink, StatewireRemoteProps } from \"./sse\";\n\nexport const useStatewireRemote = <\n TOptions extends {\n url: string;\n onChange?: (connection: StatewireClient.Connection) => void;\n } & StatewireSessionStoreOptions,\n>(\n options: TOptions,\n useLink: (options: TOptions, ctx: StatewireRemoteProps) => RemoteLink,\n): Statewire<any, any, any> => {\n const emit = useStatewireEmit<any>();\n const s = useStatewireSession({ sessionStore: options.sessionStore }, emit);\n const conn = useLink(options, s.ctx);\n useEffect(() => setTransport(s, conn), [s, conn]);\n\n return {\n state: s.canonical,\n commands: s.commands,\n emit,\n connection: conn.connection,\n source: s.source,\n };\n};\n"],"mappings":";;;;AAQA,MAAaS,sBAMXK,SACAE,YAC6B;CAC7B,MAAME,OAAOjB,iBAAsB;CACnC,MAAMkB,IAAIf,oBAAoB,EAAEgB,cAAcN,QAAQM,aAAa,GAAGF,IAAI;CAC1E,MAAMG,OAAOL,QAAQF,SAASK,EAAEF,GAAG;CACnCjB,gBAAgBG,aAAagB,GAAGE,IAAI,GAAG,CAACF,GAAGE,IAAI,CAAC;CAEhD,OAAO;EACLC,OAAOH,EAAEI;EACTC,UAAUL,EAAEK;EACZN;EACAN,YAAYS,KAAKT;EACjBa,QAAQN,EAAEM;CACZ;AACF"}
1
+ {"version":3,"file":"remote.js","names":["useEffect","useStatewireEmit","Statewire","setContext","setTransport","useStatewireSession","StatewireSessionStoreOptions","StatewireClient","RemoteLink","StatewireRemoteProps","useStatewireRemote","url","onChange","connection","Connection","options","TOptions","useLink","ctx","emit","s","sessionStore","conn","state","canonical","commands","source","value"],"sources":["../../src/transports/remote.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport { useStatewireEmit } from \"../host/host\";\nimport type { Statewire } from \"../types\";\nimport {\n setContext,\n setTransport,\n useStatewireSession,\n} from \"../client/session\";\nimport type { StatewireSessionStoreOptions } from \"../client/sessionStore\";\nimport type { StatewireClient } from \"../statewire\";\nimport type { RemoteLink, StatewireRemoteProps } from \"./sse\";\n\nexport const useStatewireRemote = <\n TOptions extends {\n url: string;\n onChange?: (connection: StatewireClient.Connection) => void;\n } & StatewireSessionStoreOptions,\n>(\n options: TOptions,\n useLink: (options: TOptions, ctx: StatewireRemoteProps) => RemoteLink,\n): Statewire<any, any, any> => {\n const emit = useStatewireEmit<any>();\n const s = useStatewireSession({ sessionStore: options.sessionStore }, emit);\n const conn = useLink(options, s.ctx);\n useEffect(() => setTransport(s, conn), [s, conn]);\n\n return {\n state: s.canonical,\n commands: s.commands,\n emit,\n connection: conn.connection,\n source: s.source,\n setContext: (value) => setContext(s, value),\n };\n};\n"],"mappings":";;;;AAYA,MAAaU,sBAMXK,SACAE,YAC6B;CAC7B,MAAME,OAAOlB,iBAAsB;CACnC,MAAMmB,IAAIf,oBAAoB,EAAEgB,cAAcN,QAAQM,aAAa,GAAGF,IAAI;CAC1E,MAAMG,OAAOL,QAAQF,SAASK,EAAEF,GAAG;CACnClB,gBAAgBI,aAAagB,GAAGE,IAAI,GAAG,CAACF,GAAGE,IAAI,CAAC;CAEhD,OAAO;EACLC,OAAOH,EAAEI;EACTC,UAAUL,EAAEK;EACZN;EACAN,YAAYS,KAAKT;EACjBa,QAAQN,EAAEM;EACVvB,aAAawB,UAAUxB,WAAWiB,GAAGO,KAAK;CAC5C;AACF"}
@@ -1,7 +1,8 @@
1
- import { StatewireHost } from "../host.js";
2
- import { StatewireResponse } from "../client/connection.js";
3
1
  import { StatewireClient } from "../statewire.js";
4
2
  import { Statewire } from "../types.js";
3
+ import { StatewireMetaEntry } from "../wire/meta.js";
4
+ import { StatewireHost } from "../host.js";
5
+ import { StatewireResponse } from "../client/connection.js";
5
6
  import { ResourceElement } from "@assistant-ui/tap";
6
7
  //#region src/transports/sse.d.ts
7
8
  type StatewireRemoteProps = {
@@ -20,6 +21,27 @@ type StatewireRemoteProps = {
20
21
  type StatewireWireCommand = {
21
22
  method: string;
22
23
  params: readonly unknown[];
24
+ context?: never;
25
+ } | {
26
+ method?: never;
27
+ params?: never;
28
+ context: Record<string, unknown>;
29
+ };
30
+ /** The wire shape of a client context submission: one root replace op. */
31
+ declare const contextOps: (value: Record<string, unknown>) => {
32
+ op: string;
33
+ path: never[];
34
+ value: Record<string, unknown>;
35
+ }[];
36
+ declare const encodeWireCommand: (command: StatewireWireCommand) => {
37
+ context?: {
38
+ op: string;
39
+ path: never[];
40
+ value: Record<string, unknown>;
41
+ }[];
42
+ params?: unknown[];
43
+ meta?: StatewireMetaEntry[];
44
+ method?: string;
23
45
  };
24
46
  type StatewireSubmitResult = {
25
47
  type: "receipt";
@@ -34,22 +56,7 @@ type StatewireSubmitResult = {
34
56
  } | {
35
57
  type: "stale-lease";
36
58
  };
37
- type ConnectionState = {
38
- status: "connecting";
39
- } | {
40
- status: "live";
41
- } | {
42
- status: "reconnecting";
43
- cause: "dropped" | "evicted" | "error";
44
- attempt: number;
45
- nextRetryAt: number;
46
- lastError?: string;
47
- message?: string;
48
- } | {
49
- status: "gone";
50
- message?: string;
51
- payload?: unknown;
52
- };
59
+ type ConnectionState = StatewireClient.ConnectionState;
53
60
  /** The live wire a remote session drives: connection status plus command POST. */
54
61
  type RemoteLink = {
55
62
  connection: StatewireClient.Connection;
@@ -60,5 +67,5 @@ type RemoteLink = {
60
67
  };
61
68
  declare const StatewireSSE: <TState = any, TCommands extends Statewire.Commands = Statewire.Commands, TEvent = any>(options: StatewireClient.SSEOptions) => ResourceElement<Statewire<TState | undefined, TCommands, TEvent>>;
62
69
  //#endregion
63
- export { ConnectionState, RemoteLink, StatewireRemoteProps, StatewireSSE, StatewireSubmitResult, StatewireWireCommand };
70
+ export { ConnectionState, RemoteLink, StatewireRemoteProps, StatewireSSE, StatewireSubmitResult, StatewireWireCommand, contextOps, encodeWireCommand };
64
71
  //# sourceMappingURL=sse.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sse.d.ts","names":[],"sources":["../../src/transports/sse.ts"],"mappings":";;;;;;KAuBY;EACV;EACA,aAAa;IAAQ;IAAgB;;EACrC,QAAQ;IACN;IACA,MAAM;IACN;;EAEF,WAAW,MAAM,cAAc;;KAGrB;EACV;EACA;;KAGU;EACN;EAAiB,WAAW;;EAC5B;EAAkB;;EAClB;;EACA;;EACA;;KAEM;EACN;;EACA;;EAEA;EACA;EACA;EACA;EACA;EACA;;EAEA;EAAgB;EAAkB;;;KAG5B;EACV,YAAY,gBAAgB;EAC5B,OAAO;IACL;IACA,SAAS;QACL,QAAQ;;cAmMH,eACX,cACA,kBAAkB,UAAU,WAAW,UAAU,UACjD,cAEA,SAAS,gBAAgB,eACtB,gBAAgB,UAAU,oBAAoB,WAAW"}
1
+ {"version":3,"file":"sse.d.ts","names":[],"sources":["../../src/transports/sse.ts"],"mappings":";;;;;;;KAuBY;EACV;EACA,aAAa;IAAQ;IAAgB;;EACrC,QAAQ;IACN;IACA,MAAM;IACN;;EAEF,WAAW,MAAM,cAAc;;KAGrB;EACN;EAAgB;EAA4B;;EAC5C;EAAgB;EAAgB,SAAS;;;cAGlC,aAAc,OAAO;;;SAAA;;cAIrB,oBAAqB,SAAS;;;;;;;;;;KAU/B;EACN;EAAiB,WAAW;;EAC5B;EAAkB;;EAClB;;EACA;;EACA;;KAEM,kBAAkB,gBAAgB;;KAGlC;EACV,YAAY,gBAAgB;EAC5B,OAAO;IACL;IACA,SAAS;QACL,QAAQ;;cAgMH,eACX,cACA,kBAAkB,UAAU,WAAW,UAAU,UACjD,cAEA,SAAS,gBAAgB,eACtB,gBAAgB,UAAU,oBAAoB,WAAW"}
@@ -6,6 +6,19 @@ import { useStatewireRemote } from "./remote.js";
6
6
  import { useRef, useState } from "@assistant-ui/tap/react-shim";
7
7
  import { flushTapSync, resource } from "@assistant-ui/tap";
8
8
  //#region src/transports/sse.ts
9
+ /** The wire shape of a client context submission: one root replace op. */
10
+ const contextOps = (value) => [{
11
+ op: "replace",
12
+ path: [],
13
+ value
14
+ }];
15
+ const encodeWireCommand = (command) => ({
16
+ ...command.method !== void 0 && {
17
+ method: command.method,
18
+ ...encodeParams(command.params)
19
+ },
20
+ ...command.context !== void 0 && { context: contextOps(command.context) }
21
+ });
9
22
  const baseUrl = (options) => options.url.replace(/\/$/, "");
10
23
  const requestHeaders = async (options, extra) => {
11
24
  const { headers } = options;
@@ -79,10 +92,7 @@ const sendCommand = async (c, options, { seq, command }) => {
79
92
  "Statewire-Command-Seq": String(seq),
80
93
  ...c.lease !== null && { "Statewire-Lease": c.lease }
81
94
  }),
82
- body: JSON.stringify({
83
- method: command.method,
84
- ...encodeParams(command.params)
85
- }),
95
+ body: JSON.stringify(encodeWireCommand(command)),
86
96
  signal: AbortSignal.any([c.lifetime?.signal ?? AbortSignal.abort(), timeout])
87
97
  }, timeout);
88
98
  if (res.ok) {
@@ -132,6 +142,6 @@ const useSseLink = (options, ctx) => {
132
142
  const useStatewireSSE = (options) => useStatewireRemote(options, useSseLink);
133
143
  const StatewireSSE = resource(useStatewireSSE);
134
144
  //#endregion
135
- export { StatewireSSE };
145
+ export { StatewireSSE, contextOps, encodeWireCommand };
136
146
 
137
147
  //# sourceMappingURL=sse.js.map