statewire 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +2 -2
  2. package/dist/client/connection.d.ts +23 -19
  3. package/dist/client/connection.d.ts.map +1 -1
  4. package/dist/client/connection.js +143 -103
  5. package/dist/client/connection.js.map +1 -1
  6. package/dist/client/core.d.ts +30 -3
  7. package/dist/client/core.d.ts.map +1 -1
  8. package/dist/client/core.js +222 -260
  9. package/dist/client/core.js.map +1 -1
  10. package/dist/client/session.d.ts +34 -39
  11. package/dist/client/session.d.ts.map +1 -1
  12. package/dist/client/session.js +380 -373
  13. package/dist/client/session.js.map +1 -1
  14. package/dist/client/sessionStore.d.ts +1 -0
  15. package/dist/client/sessionStore.d.ts.map +1 -1
  16. package/dist/client/sessionStore.js.map +1 -1
  17. package/dist/client/timing.d.ts +1 -2
  18. package/dist/client/timing.d.ts.map +1 -1
  19. package/dist/client/timing.js +1 -5
  20. package/dist/client/timing.js.map +1 -1
  21. package/dist/host/draft.d.ts +2 -5
  22. package/dist/host/draft.d.ts.map +1 -1
  23. package/dist/host/draft.js.map +1 -1
  24. package/dist/host/host.d.ts +5 -8
  25. package/dist/host/host.d.ts.map +1 -1
  26. package/dist/host/host.js +23 -32
  27. package/dist/host/host.js.map +1 -1
  28. package/dist/host/socket.d.ts +3 -2
  29. package/dist/host/socket.d.ts.map +1 -1
  30. package/dist/host/socket.js +484 -424
  31. package/dist/host/socket.js.map +1 -1
  32. package/dist/host-internal.d.ts +4 -0
  33. package/dist/host-internal.d.ts.map +1 -1
  34. package/dist/host.d.ts +10 -10
  35. package/dist/host.d.ts.map +1 -1
  36. package/dist/host.js +2 -2
  37. package/dist/index.d.ts +1 -2
  38. package/dist/index.js +1 -2
  39. package/dist/statewire.d.ts +29 -24
  40. package/dist/statewire.d.ts.map +1 -1
  41. package/dist/statewire.js +8 -2
  42. package/dist/statewire.js.map +1 -1
  43. package/dist/transports/fake-sse.d.ts +1 -1
  44. package/dist/transports/fake-sse.d.ts.map +1 -1
  45. package/dist/transports/fake-sse.js +5 -2
  46. package/dist/transports/fake-sse.js.map +1 -1
  47. package/dist/transports/fixtures.d.ts +1 -2
  48. package/dist/transports/fixtures.d.ts.map +1 -1
  49. package/dist/transports/fixtures.js +5 -7
  50. package/dist/transports/fixtures.js.map +1 -1
  51. package/dist/transports/remote.d.ts +2 -1
  52. package/dist/transports/remote.d.ts.map +1 -1
  53. package/dist/transports/remote.js +10 -4
  54. package/dist/transports/remote.js.map +1 -1
  55. package/dist/transports/sse.d.ts +32 -46
  56. package/dist/transports/sse.d.ts.map +1 -1
  57. package/dist/transports/sse.js +56 -59
  58. package/dist/transports/sse.js.map +1 -1
  59. package/dist/transports/testing.d.ts +7 -5
  60. package/dist/transports/testing.d.ts.map +1 -1
  61. package/dist/transports/testing.js +41 -14
  62. package/dist/transports/testing.js.map +1 -1
  63. package/dist/transports/ws.d.ts +2 -3
  64. package/dist/transports/ws.d.ts.map +1 -1
  65. package/dist/transports/ws.js +25 -80
  66. package/dist/transports/ws.js.map +1 -1
  67. package/dist/types.d.ts +8 -8
  68. package/dist/types.d.ts.map +1 -1
  69. package/dist/types.js +3 -4
  70. package/dist/types.js.map +1 -1
  71. package/dist/wire/ops.d.ts +1 -5
  72. package/dist/wire/ops.d.ts.map +1 -1
  73. package/dist/wire/ops.js +1 -4
  74. package/dist/wire/ops.js.map +1 -1
  75. package/package.json +1 -1
  76. package/src/client/connection.ts +175 -128
  77. package/src/client/core.ts +287 -278
  78. package/src/client/session.ts +474 -480
  79. package/src/client/sessionStore.ts +1 -0
  80. package/src/client/timing.ts +0 -6
  81. package/src/host/draft.ts +1 -3
  82. package/src/host/host.ts +41 -58
  83. package/src/host/socket.ts +653 -448
  84. package/src/host-internal.ts +4 -0
  85. package/src/host.ts +13 -10
  86. package/src/index.ts +0 -1
  87. package/src/statewire.ts +38 -32
  88. package/src/transports/fake-sse.ts +2 -1
  89. package/src/transports/fixtures.ts +4 -9
  90. package/src/transports/remote.ts +12 -5
  91. package/src/transports/sse.ts +99 -115
  92. package/src/transports/testing.ts +47 -19
  93. package/src/transports/ws.ts +44 -115
  94. package/src/types.ts +15 -14
  95. package/src/wire/ops.ts +9 -16
@@ -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","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 clientId: rec.id,\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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"}
1
+ {"version":3,"file":"socket.js","names":["useEffect","useState","createTapRoot","flushTapSync","useResource","ResourceElement","getCommandsHost","getStateHost","CommandsHost","CommandsOutcome","StatewireSendError","Statewire","StatewireHost","StatewireHostInternal","encodeValue","reviveValue","createMacrotaskPoster","StatewireMetaEntry","StatewirePath","StatewireCmd","StatewireEnvelope","CLIENT_ID_RE","RESULT_TTL_MS","RESULT_LIMIT","CLIENT_RECORD_LIMIT","WireOp","op","path","value","meta","Admission","seq","method","applied","appliedSent","settled","ClientRec","id","lastSeq","ctxSeq","touched","context","Record","results","Map","ts","cmd","inflight","Conn","clientId","socket","Socket","open","sawFrame","lastSentAt","violation","reason","Object","assign","Error","name","violationReason","error","message","CtxStatement","CmdStatement","params","ParsedFrame","headers","ctx","isPlainObject","Array","isArray","parseLane","length","statements","prev","statement","Number","isInteger","push","contextValue","ops","undefined","parseFrame","raw","first","frame","JSON","parse","values","some","encodePayload","payload","cmdOf","adm","outcome","type","failure","fate","code","String","Host","heartbeatMs","clientRecordLimit","finOnIdle","clients","conns","Set","pendingOps","pendingApplied","pendingCmds","idleWire","finished","Fin","disposed","flushScheduled","heartbeatTimer","ReturnType","setInterval","stateHost","StateHost","commandsHost","current","post","connect","SocketConnection","finish","fin","scheduleFlush","h","recordOp","attach","next","nextState","state","nextCommands","commands","submit","setOnOp","closeConn","conn","delete","close","size","clearInterval","sendTo","data","Date","now","send","ensureHeartbeat","unref","setHeartbeatMs","ms","idleLevel","rec","flush","takeChanged","queueApplied","level","edge","cmds","appliedEntries","get","map","packet","idle","stringify","verdicts","list","set","queueCmd","retain","newest","Math","max","keys","entry","min","admittedThrough","reemit","cached","settleCmd","settleWire","callerOf","Caller","isConnected","rejectStatement","admitCmd","names","methods","sort","caller","onApplied","onSettle","catch","admitFrame","through","ctxNew","filter","s","cmdNew","syn","at","dur","gcClients","excess","live","oldest","a","b","has","dead","refuse","test","known","created","other","add","snapshot","hello","record","resume","suspend","useSocketHost","transport","options","SocketHostOptions","Pick","SocketHost","host","heartbeatIntervalMs","StatewireSocketHost","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, 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 type { StatewireMetaEntry } from \"../wire/meta\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport type { StatewireCmd, StatewireEnvelope } from \"../client/connection\";\n\nconst CLIENT_ID_RE = /^[A-Za-z0-9._~-]{1,256}$/;\nconst RESULT_TTL_MS = 30_000;\nconst RESULT_LIMIT = 256;\nconst CLIENT_RECORD_LIMIT = 1024;\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 applied: boolean;\n appliedSent: boolean;\n settled: boolean;\n};\n\ntype ClientRec = {\n id: string;\n lastSeq: number;\n ctxSeq: number;\n touched: number;\n context: Record<string, unknown> | null;\n results: Map<number, { ts: number; cmd: StatewireCmd }>;\n inflight: Map<number, Admission>;\n};\n\ntype Conn = {\n clientId: string;\n socket: StatewireHostInternal.Socket;\n open: boolean;\n sawFrame: boolean;\n lastSentAt: number;\n};\n\n// ─── frame parsing ──────────────────────────────────────\n\nconst violation = (reason: string): never => {\n throw Object.assign(new Error(reason), { name: \"StatewireViolation\" });\n};\n\nconst violationReason = (error: unknown): string | null =>\n error instanceof Error && error.name === \"StatewireViolation\"\n ? error.message\n : null;\n\ntype CtxStatement = { seq: number; value: Record<string, unknown> };\ntype CmdStatement = {\n seq: number;\n method: string;\n params: unknown;\n meta?: unknown;\n};\n\ntype ParsedFrame = {\n headers: Record<string, string> | null;\n ctx: CtxStatement[];\n cmd: CmdStatement[];\n};\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst parseLane = (value: unknown): Record<string, unknown>[] => {\n if (!Array.isArray(value)) return violation(\"malformed frame\");\n if (value.length === 0) return violation(\"empty lane\");\n const statements: Record<string, unknown>[] = [];\n let prev: number | null = null;\n for (const statement of value) {\n if (!isPlainObject(statement)) return violation(\"malformed statement\");\n const seq = statement[\"seq\"];\n if (typeof seq !== \"number\" || !Number.isInteger(seq) || seq < 1) {\n return violation(\"malformed statement\");\n }\n if (prev !== null && seq !== prev + 1) return violation(\"malformed frame\");\n prev = seq;\n statements.push(statement);\n }\n return statements;\n};\n\n/** v1 ctx statements state the context whole: one root replace with an object value. */\nconst contextValue = (ops: unknown): Record<string, unknown> => {\n if (!Array.isArray(ops) || ops.length !== 1) {\n throw new Error(\"context must be a single-op array\");\n }\n const op = ops[0] as Record<string, unknown> | null;\n if (\n !isPlainObject(op) ||\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 =\n op[\"meta\"] !== undefined\n ? reviveValue(op[\"value\"], op[\"meta\"])\n : op[\"value\"];\n if (!isPlainObject(value)) {\n throw new Error(\"context value must be an object\");\n }\n return value;\n};\n\nconst parseFrame = (raw: string, first: boolean): ParsedFrame => {\n let frame: unknown;\n try {\n frame = JSON.parse(raw);\n } catch {\n return violation(\"malformed frame\");\n }\n if (!isPlainObject(frame)) return violation(\"malformed frame\");\n if (\n frame[\"headers\"] === undefined &&\n frame[\"ctx\"] === undefined &&\n frame[\"cmd\"] === undefined\n ) {\n return violation(\"malformed frame\");\n }\n let headers: Record<string, string> | null = null;\n if (frame[\"headers\"] !== undefined) {\n if (!first) return violation(\"mid-session headers\");\n const raw = frame[\"headers\"];\n if (\n !isPlainObject(raw) ||\n Object.values(raw).some((value) => typeof value !== \"string\")\n ) {\n return violation(\"malformed headers\");\n }\n headers = raw as Record<string, string>;\n }\n const ctx: CtxStatement[] = [];\n if (frame[\"ctx\"] !== undefined) {\n for (const statement of parseLane(frame[\"ctx\"])) {\n try {\n ctx.push({\n seq: statement[\"seq\"] as number,\n value: contextValue(statement[\"ops\"]),\n });\n } catch {\n return violation(\"invalid-context\");\n }\n }\n }\n const cmd: CmdStatement[] = [];\n if (frame[\"cmd\"] !== undefined) {\n for (const statement of parseLane(frame[\"cmd\"])) {\n const method = statement[\"method\"];\n if (typeof method !== \"string\" || !(\"params\" in statement)) {\n return violation(\"malformed statement\");\n }\n cmd.push({\n seq: statement[\"seq\"] as number,\n method,\n params: statement[\"params\"],\n ...(statement[\"meta\"] !== undefined && { meta: statement[\"meta\"] }),\n });\n }\n }\n return { headers, ctx, cmd };\n};\n\n// ─── verdict encoding ───────────────────────────────────\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 cmdOf = (adm: Admission, outcome: CommandsOutcome): StatewireCmd => {\n if (outcome.type === \"accepted\") {\n return { seq: adm.seq, type: \"result\", ...encodePayload(outcome.payload) };\n }\n const failure =\n outcome.error instanceof StatewireSendError ? outcome.error.failure : null;\n switch (failure?.fate) {\n case \"failed\":\n return {\n seq: adm.seq,\n type: \"failed\",\n ...(failure.message !== undefined && { message: failure.message }),\n };\n case \"rejected\": {\n if (failure.code !== undefined) {\n const message =\n failure.code === \"unknown-command\"\n ? `unknown method: '${adm.method}'`\n : failure.message;\n return {\n seq: adm.seq,\n type: \"rejected\",\n code: failure.code,\n ...(message !== undefined && { message }),\n };\n }\n return {\n seq: adm.seq,\n type: \"rejected\",\n ...(failure.message !== undefined && { message: failure.message }),\n ...encodePayload(failure.payload),\n };\n }\n default:\n return {\n seq: adm.seq,\n type: \"failed\",\n message:\n outcome.error instanceof Error\n ? outcome.error.message\n : String(outcome.error),\n };\n }\n};\n\n// ─── host ───────────────────────────────────────────────\n\ntype Host = {\n heartbeatMs: number;\n clientRecordLimit: number;\n finOnIdle: boolean;\n clients: Map<string, ClientRec>;\n conns: Set<Conn>;\n pendingOps: WireOp[];\n pendingApplied: Map<string, number[]>;\n pendingCmds: Map<string, StatewireCmd[]>;\n idleWire: boolean;\n finished: StatewireHost.Fin | null;\n disposed: boolean;\n flushScheduled: boolean;\n heartbeatTimer: ReturnType<typeof setInterval> | null;\n stateHost: StatewireHost.StateHost | null;\n commandsHost: CommandsHost | null;\n current: Statewire<unknown> | null;\n post: () => void;\n connect: (\n clientId: string,\n socket: StatewireHostInternal.Socket,\n ) => StatewireHostInternal.SocketConnection;\n finish: (fin: StatewireHost.Fin) => void;\n};\n\nconst scheduleFlush = (h: Host) => {\n if (h.flushScheduled || h.disposed) return;\n h.flushScheduled = true;\n h.post();\n};\n\nconst recordOp = (\n h: Host,\n op: {\n op: string;\n path?: StatewirePath;\n value?: unknown;\n },\n) => {\n if (op.op === \"replace\" || op.op === \"add\") {\n const { value, meta } = encodeValue(op.value);\n h.pendingOps.push({ ...op, value, ...(meta !== undefined && { meta }) });\n } else {\n h.pendingOps.push(op);\n }\n scheduleFlush(h);\n};\n\nconst attach = (h: Host, next: Statewire<unknown>) => {\n if (next === h.current) return;\n h.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 if (!nextState || !nextCommands?.submit) {\n throw new Error(\n \"StatewireSocketHost requires a statewire/host transport (useStatewireState with object state, useStatewireCommands)\",\n );\n }\n if (nextState !== h.stateHost) {\n h.stateHost?.setOnOp(null);\n h.stateHost = nextState;\n h.stateHost.setOnOp((op) => recordOp(h, op));\n }\n h.commandsHost = nextCommands;\n};\n\nconst closeConn = (h: Host, conn: Conn, code?: number, reason?: string) => {\n if (!conn.open) return;\n conn.open = false;\n h.conns.delete(conn);\n try {\n conn.socket.close(code, reason);\n } catch {\n /* peer already gone */\n }\n if (h.conns.size === 0 && h.heartbeatTimer !== null) {\n clearInterval(h.heartbeatTimer);\n h.heartbeatTimer = null;\n }\n};\n\nconst sendTo = (h: Host, 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(h, conn);\n }\n};\n\nconst ensureHeartbeat = (h: Host) => {\n if (h.heartbeatTimer !== null || h.heartbeatMs <= 0) return;\n h.heartbeatTimer = setInterval(() => {\n const now = Date.now();\n for (const conn of [...h.conns]) {\n if (now - conn.lastSentAt >= h.heartbeatMs) sendTo(h, conn, \"{}\");\n }\n }, h.heartbeatMs);\n (h.heartbeatTimer as unknown as { unref?: () => void }).unref?.();\n};\n\nconst setHeartbeatMs = (h: Host, ms: number) => {\n if (ms === h.heartbeatMs) return;\n h.heartbeatMs = ms;\n if (h.heartbeatTimer !== null) {\n clearInterval(h.heartbeatTimer);\n h.heartbeatTimer = null;\n ensureHeartbeat(h);\n }\n};\n\nconst idleLevel = (h: Host): boolean => {\n for (const rec of h.clients.values()) {\n if (rec.inflight.size > 0) return false;\n }\n return true;\n};\n\nconst flush = (h: Host, fin?: StatewireHost.Fin) => {\n flushTapSync(() => {});\n h.stateHost?.takeChanged();\n for (const rec of h.clients.values()) {\n for (const adm of rec.inflight.values()) {\n if (adm.applied && !adm.settled && !adm.appliedSent) {\n adm.appliedSent = true;\n queueApplied(h, rec.id, adm.seq);\n }\n }\n }\n const level = idleLevel(h);\n const edge = level !== h.idleWire;\n if (\n h.pendingOps.length === 0 &&\n h.pendingApplied.size === 0 &&\n h.pendingCmds.size === 0 &&\n fin === undefined &&\n !edge\n ) {\n return;\n }\n h.idleWire = level;\n if (fin === undefined && edge && level && h.finOnIdle) {\n fin = { reason: \"idle\" };\n }\n const ops = h.pendingOps;\n const applied = h.pendingApplied;\n const cmds = h.pendingCmds;\n h.pendingOps = [];\n h.pendingApplied = new Map();\n h.pendingCmds = new Map();\n // Applied entries anchor to their effects: piggybacked on the ops packet\n // when one is flushing, standalone otherwise. Terminal verdicts ride their\n // own packet.\n for (const conn of [...h.conns]) {\n const appliedEntries: StatewireCmd[] = (\n applied.get(conn.clientId) ?? []\n ).map((seq) => ({ seq, type: \"applied\" }));\n if (ops.length > 0) {\n const packet: StatewireEnvelope = {\n ops,\n ...(appliedEntries.length > 0 && { cmd: appliedEntries }),\n ...(edge && { idle: level }),\n };\n sendTo(h, conn, JSON.stringify(packet));\n } else if (appliedEntries.length > 0) {\n sendTo(h, conn, JSON.stringify({ cmd: appliedEntries }));\n }\n const verdicts = cmds.get(conn.clientId) ?? [];\n if (verdicts.length > 0) {\n sendTo(h, conn, JSON.stringify({ cmd: verdicts }));\n }\n if (edge && ops.length === 0) {\n sendTo(h, conn, JSON.stringify({ idle: level }));\n }\n if (fin !== undefined) {\n sendTo(h, conn, JSON.stringify({ fin }));\n closeConn(h, conn);\n }\n }\n};\n\nconst queueApplied = (h: Host, clientId: string, seq: number) => {\n const list = h.pendingApplied.get(clientId);\n if (list) list.push(seq);\n else h.pendingApplied.set(clientId, [seq]);\n scheduleFlush(h);\n};\n\nconst queueCmd = (h: Host, clientId: string, cmd: StatewireCmd) => {\n const list = h.pendingCmds.get(clientId);\n if (list) list.push(cmd);\n else h.pendingCmds.set(clientId, [cmd]);\n scheduleFlush(h);\n};\n\nconst retain = (rec: ClientRec, cmd: StatewireCmd) => {\n const now = Date.now();\n rec.results.set(cmd.seq, { ts: now, cmd });\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/** Cmd-lane watermark: the last statement admitted, settled or not. */\nconst admittedThrough = (rec: ClientRec): number =>\n Math.max(rec.lastSeq, ...rec.inflight.keys());\n\n// A re-asked seq never re-runs: the stored verdict re-emits, an applied\n// inflight run re-answers applied; one not yet applied answers nothing (its\n// answers ride the stream when ready), an evicted verdict answers expired.\nconst reemit = (h: Host, rec: ClientRec, seq: number) => {\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 queueCmd(h, rec.id, cached.cmd);\n return;\n }\n rec.results.delete(seq);\n }\n const adm = rec.inflight.get(seq);\n if (adm) {\n if (adm.applied) queueApplied(h, rec.id, seq);\n return;\n }\n queueCmd(h, rec.id, { seq, type: \"expired\" });\n};\n\nconst settleCmd = (\n h: Host,\n rec: ClientRec,\n adm: Admission,\n cmd: StatewireCmd,\n) => {\n if (adm.settled) return;\n adm.settled = true;\n rec.inflight.delete(adm.seq);\n retain(rec, cmd);\n rec.lastSeq = Math.max(rec.lastSeq, adm.seq);\n adm.applied = true;\n queueCmd(h, rec.id, cmd);\n};\n\nconst settleWire = (\n h: Host,\n rec: ClientRec,\n adm: Admission,\n outcome: CommandsOutcome,\n) => settleCmd(h, rec, adm, cmdOf(adm, outcome));\n\nconst callerOf = (h: Host, rec: ClientRec): StatewireHost.Caller => ({\n clientId: rec.id,\n get context() {\n return rec.context;\n },\n get isConnected() {\n if (h.clients.get(rec.id) !== rec) return false;\n for (const conn of h.conns) {\n if (conn.clientId === rec.id && conn.open) return true;\n }\n return false;\n },\n});\n\nconst rejectStatement = (\n h: Host,\n rec: ClientRec,\n adm: Admission,\n message: string,\n) =>\n settleCmd(h, rec, adm, {\n seq: adm.seq,\n type: \"rejected\",\n code: \"invalid-params\",\n message,\n });\n\nconst admitCmd = (h: Host, rec: ClientRec, statement: CmdStatement) => {\n const adm: Admission = {\n seq: statement.seq,\n method: statement.method,\n applied: false,\n appliedSent: false,\n settled: false,\n };\n rec.inflight.set(statement.seq, adm);\n if (!Array.isArray(statement.params)) {\n rejectStatement(h, rec, adm, \"params must be an array\");\n return;\n }\n let params = statement.params;\n if (statement.meta !== undefined) {\n try {\n params = reviveValue(params, statement.meta) as unknown[];\n } catch (error) {\n rejectStatement(\n h,\n rec,\n adm,\n `invalid params: ${(error as Error).message}`,\n );\n return;\n }\n }\n if (statement.method === \"statewire/commands\") {\n if (params.length > 0) {\n rejectStatement(h, rec, adm, \"statewire/commands takes no params\");\n return;\n }\n const names = [\n ...(h.commandsHost?.methods?.() ?? []),\n \"statewire/commands\",\n ].sort();\n settleCmd(h, rec, adm, { seq: adm.seq, type: \"result\", payload: names });\n return;\n }\n h.commandsHost!.submit!({\n method: statement.method,\n params,\n clientId: rec.id,\n caller: callerOf(h, rec),\n onApplied: () => {\n if (adm.settled || adm.applied) return;\n adm.applied = true;\n rec.lastSeq = Math.max(rec.lastSeq, adm.seq);\n scheduleFlush(h);\n },\n onSettle: (outcome) => settleWire(h, rec, adm, outcome),\n }).catch((error: unknown) => {\n if (!adm.settled) settleWire(h, rec, adm, { type: \"rejected\", error });\n });\n};\n\n// One admission law, both lanes: absorbed statements answer from the record,\n// then the new suffix is admitted whole or the frame is refused whole via a\n// {syn} stating both watermarks, nothing consumed. A gap is never a close.\nconst admitFrame = (h: Host, conn: Conn, frame: ParsedFrame) => {\n const rec = h.clients.get(conn.clientId)!;\n rec.touched = Date.now();\n const through = admittedThrough(rec);\n for (const statement of frame.cmd) {\n if (statement.seq <= through) reemit(h, rec, statement.seq);\n }\n const ctxNew = frame.ctx.filter((s) => s.seq > rec.ctxSeq);\n const cmdNew = frame.cmd.filter((s) => s.seq > through);\n if (\n (ctxNew.length > 0 && ctxNew[0]!.seq !== rec.ctxSeq + 1) ||\n (cmdNew.length > 0 && cmdNew[0]!.seq !== through + 1)\n ) {\n sendTo(\n h,\n conn,\n JSON.stringify({ syn: { seq: through, ctxSeq: rec.ctxSeq } }),\n );\n return;\n }\n for (const statement of ctxNew) {\n rec.context = statement.value;\n rec.ctxSeq = statement.seq;\n }\n for (const statement of cmdNew) admitCmd(h, rec, statement);\n if (cmdNew.length > 0) {\n // Memory host: admitted means retained — dur rides the admitting flush.\n queueCmd(h, rec.id, { seq: cmdNew.at(-1)!.seq, dur: true });\n }\n scheduleFlush(h);\n};\n\nconst message = (h: Host, conn: Conn, data: string) => {\n if (!conn.open || h.disposed || h.finished !== null) return;\n const first = !conn.sawFrame;\n conn.sawFrame = true;\n let frame: ParsedFrame;\n try {\n frame = parseFrame(data, first);\n } catch (error) {\n const reason = violationReason(error);\n if (reason === null) throw error;\n closeConn(h, conn, 1008, reason);\n return;\n }\n if (frame.ctx.length === 0 && frame.cmd.length === 0) return;\n admitFrame(h, conn, frame);\n};\n\nconst gcClients = (h: Host) => {\n let excess = h.clients.size - h.clientRecordLimit;\n if (excess <= 0) return;\n const live = new Set([...h.conns].map((conn) => conn.clientId));\n const oldest = [...h.clients.values()].sort((a, b) => a.touched - b.touched);\n for (const rec of oldest) {\n if (excess <= 0) return;\n if (live.has(rec.id) || rec.inflight.size > 0) continue;\n h.clients.delete(rec.id);\n excess -= 1;\n }\n};\n\nconst connect = (\n h: Host,\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 (h.disposed) return refuse(1001, \"disposed\");\n if (!CLIENT_ID_RE.test(clientId)) return refuse(1008, \"invalid client id\");\n flush(h);\n if (h.finished !== null) {\n try {\n socket.send(JSON.stringify({ fin: h.finished }));\n } catch {\n /* peer already gone */\n }\n return refuse(1000, \"finished\");\n }\n const known = h.clients.has(clientId);\n const rec = known\n ? h.clients.get(clientId)!\n : (() => {\n const created: ClientRec = {\n id: clientId,\n lastSeq: 0,\n ctxSeq: 0,\n touched: Date.now(),\n context: null,\n results: new Map(),\n inflight: new Map(),\n };\n h.clients.set(clientId, created);\n return created;\n })();\n rec.touched = Date.now();\n for (const other of [...h.conns]) {\n if (other.clientId !== clientId) continue;\n sendTo(h, other, JSON.stringify({ fin: { reason: \"superseded\" } }));\n closeConn(h, other);\n }\n const conn: Conn = {\n clientId,\n socket,\n open: true,\n sawFrame: false,\n lastSentAt: 0,\n };\n h.conns.add(conn);\n gcClients(h);\n ensureHeartbeat(h);\n const { value, meta } = encodeValue(h.stateHost!.snapshot());\n const hello: StatewireEnvelope = {\n ops: [\n {\n op: \"replace\",\n path: [],\n value,\n ...(meta !== undefined && { meta }),\n },\n ],\n syn: {\n seq: admittedThrough(rec),\n ctxSeq: rec.ctxSeq,\n record: known ? \"resumed\" : \"fresh\",\n ...(h.idleWire && { idle: true }),\n },\n };\n sendTo(h, conn, JSON.stringify(hello));\n return {\n clientId,\n message: (data) => message(h, conn, data),\n close: () => closeConn(h, conn),\n };\n};\n\nconst finish = (h: Host, fin: StatewireHost.Fin) => {\n if (h.finished !== null || h.disposed) return;\n h.finished = fin;\n flush(h, fin);\n};\n\nconst resume = (h: Host) => {\n h.disposed = false;\n h.stateHost?.setOnOp((op) => recordOp(h, op));\n};\n\nconst suspend = (h: Host) => {\n h.disposed = true;\n if (h.heartbeatTimer !== null) {\n clearInterval(h.heartbeatTimer);\n h.heartbeatTimer = null;\n }\n h.stateHost?.setOnOp(null);\n};\n\nconst useSocketHost = (\n transport: ResourceElement<Statewire>,\n options?: StatewireHostInternal.SocketHostOptions,\n): Pick<StatewireHostInternal.SocketHost, \"connect\" | \"finish\"> => {\n const [h] = useState((): Host => {\n const host: Host = {\n heartbeatMs: 15_000,\n clientRecordLimit: CLIENT_RECORD_LIMIT,\n finOnIdle: false,\n clients: new Map(),\n conns: new Set(),\n pendingOps: [],\n pendingApplied: new Map(),\n pendingCmds: new Map(),\n idleWire: true,\n finished: null,\n disposed: false,\n flushScheduled: false,\n heartbeatTimer: null,\n stateHost: null,\n commandsHost: null,\n current: null,\n post: createMacrotaskPoster(() => {\n host.flushScheduled = false;\n flush(host);\n }),\n connect: (clientId, socket) => connect(host, clientId, socket),\n finish: (fin) => finish(host, fin),\n };\n return host;\n });\n setHeartbeatMs(h, options?.heartbeatIntervalMs ?? 15_000);\n h.clientRecordLimit = options?.clientRecordLimit ?? CLIENT_RECORD_LIMIT;\n h.finOnIdle = options?.finOnIdle ?? false;\n const value = useResource(transport) as Statewire<unknown>;\n attach(h, value);\n useEffect(() => {\n attach(h, value);\n });\n useEffect(() => {\n resume(h);\n return () => suspend(h);\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 packet protocol — snapshot-first hello with `syn`,\n * statement-lane client frames under one admission law, `{cmd}` answer\n * arrays, per-client-id supersede, idle edges, 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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"}
@@ -19,6 +19,10 @@ declare namespace StatewireHostInternal {
19
19
  };
20
20
  type SocketHostOptions = {
21
21
  heartbeatIntervalMs?: number;
22
+ /** LRU cap on stored client records; live or in-flight records are exempt. */
23
+ clientRecordLimit?: number;
24
+ /** Hang every attach up with `fin: idle` at the idle flag's rising edge. */
25
+ finOnIdle?: boolean;
22
26
  };
23
27
  type Fin = StatewireHost.Fin;
24
28
  }
@@ -1 +1 @@
1
- {"version":3,"file":"host-internal.d.ts","names":[],"sources":["../src/host-internal.ts"],"mappings":";;;;kBAIiB;OACH;IACV,OAAO;IACP,QAAQ,eAAe;;OAGb;aACD;IACT,UAAU;IACV;;OAGU;IACV,UAAU,kBAAkB,QAAQ,WAAW;IAC/C,SAAS,KAAK,cAAc;IAC5B;;OAGU;IACV;;OAGU,MAAM,cAAc"}
1
+ {"version":3,"file":"host-internal.d.ts","names":[],"sources":["../src/host-internal.ts"],"mappings":";;;;kBAIiB;OACH;IACV,OAAO;IACP,QAAQ,eAAe;;OAGb;aACD;IACT,UAAU;IACV;;OAGU;IACV,UAAU,kBAAkB,QAAQ,WAAW;IAC/C,SAAS,KAAK,cAAc;IAC5B;;OAGU;IACV;;IAEA;;IAEA;;OAGU,MAAM,cAAc"}
package/dist/host.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Statewire } from "./types.js";
2
- import { ServerOp, append } from "./host/draft.js";
3
2
  import { StatewireReject } from "./host/engine.js";
4
- import { getStateHost, useStatewireCommands, useStatewireEmit, useStatewireState } from "./host/host.js";
3
+ import { append } from "./host/draft.js";
4
+ import { getStateHost, useStatewireCommands, useStatewireState } from "./host/host.js";
5
5
  //#region src/host.d.ts
6
6
  declare namespace StatewireHost {
7
7
  type StateCtl<TState = unknown> = {
@@ -12,18 +12,18 @@ declare namespace StatewireHost {
12
12
  };
13
13
  type StateHost = Statewire.StateSource & {
14
14
  /** Synchronous, per locally-recorded op; the host owns batching. */
15
- setOnOp: (listener: ((op: ServerOp) => void) | null) => void;
15
+ setOnOp: (listener: ((op: Statewire.Op) => void) | null) => void;
16
16
  };
17
- type StateSource = Statewire.StateSource;
18
17
  /**
19
18
  * Per-invocation handle bound as `this` of a command handler: `caller` is
20
- * the submitting client, `ack()` releases the client's optimistic overlay /
21
- * ack watermark before the handler settles (idempotent, a no-op after
22
- * settle). The command settles when the handler's returned promise does.
19
+ * the submitting client, `applied()` states the broadcast state now contains
20
+ * the command's effects — the client drops its optimistic overlay — before
21
+ * the handler settles (idempotent, a no-op after settle). The command
22
+ * settles when the handler's returned promise does.
23
23
  */
24
24
  type Ctx = {
25
25
  readonly caller: Caller;
26
- readonly ack: () => void;
26
+ readonly applied: () => void;
27
27
  };
28
28
  /**
29
29
  * The submitting client (`this.caller` in a command handler). Holdable
@@ -39,12 +39,12 @@ declare namespace StatewireHost {
39
39
  };
40
40
  type HandledCommands<THandlers extends Statewire.Commands> = { [K in keyof THandlers]: (...args: Parameters<THandlers[K]>) => Awaited<ReturnType<THandlers[K]>>; };
41
41
  type Fin = {
42
- reason: "evicted" | "error" | "gone" | "superseded" | (string & {});
42
+ reason: "evicted" | "error" | "gone" | "superseded" | "idle" | (string & {});
43
43
  message?: string;
44
44
  payload?: unknown;
45
45
  meta?: unknown;
46
46
  };
47
47
  }
48
48
  //#endregion
49
- export { StatewireHost, StatewireReject, append, getStateHost, useStatewireCommands, useStatewireEmit, useStatewireState };
49
+ export { StatewireHost, StatewireReject, append, getStateHost, useStatewireCommands, useStatewireState };
50
50
  //# sourceMappingURL=host.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"host.d.ts","names":[],"sources":["../src/host.ts"],"mappings":";;;;;kBAYiB;OACH,SAAS;;IAEnB,QAAQ,cAAc,UAAU;;IAEhC,gBAAgB;;OAGN,YAAY,UAAU;;IAEhC,UAAU,YAAY,IAAI;;OAGhB,cAAc,UAAU;;;;;;;OAQxB;aACD,QAAQ;aACR;;;;;;;;;OAUC;aACD;aACA,SAAS;aACT;;OAGC,gBAAgB,kBAAkB,UAAU,eACrD,WAAW,gBACP,MAAM,WAAW,UAAU,QAC3B,QAAQ,WAAW,UAAU;OAGxB;IACV;IACA;IACA;IACA"}
1
+ {"version":3,"file":"host.d.ts","names":[],"sources":["../src/host.ts"],"mappings":";;;;;kBAUiB;OACH,SAAS;;IAEnB,QAAQ,cAAc,UAAU;;IAEhC,gBAAgB;;OAGN,YAAY,UAAU;;IAEhC,UAAU,YAAY,IAAI,UAAU;;;;;;;;;OAU1B;aACD,QAAQ;aACR;;;;;;;;;OAUC;aACD;aACA,SAAS;aACT;;OAGC,gBAAgB,kBAAkB,UAAU,eACrD,WAAW,gBACP,MAAM,WAAW,UAAU,QAC3B,QAAQ,WAAW,UAAU;OAGxB;IACV;IAOA;IACA;IACA"}
package/dist/host.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import { StatewireReject } from "./host/engine.js";
2
2
  import { append } from "./host/draft.js";
3
- import { getStateHost, useStatewireCommands, useStatewireEmit, useStatewireState } from "./host/host.js";
4
- export { StatewireReject, append, getStateHost, useStatewireCommands, useStatewireEmit, useStatewireState };
3
+ import { getStateHost, useStatewireCommands, useStatewireState } from "./host/host.js";
4
+ export { StatewireReject, append, getStateHost, useStatewireCommands, useStatewireState };
package/dist/index.d.ts CHANGED
@@ -2,5 +2,4 @@ import { StatewireClient, StatewireResource, useStatewire } from "./statewire.js
2
2
  import { Statewire, StatewireSendError } from "./types.js";
3
3
  import { StatewireSSE } from "./transports/sse.js";
4
4
  import { StatewireWS } from "./transports/ws.js";
5
- import { applyOptimistic } from "./util/recordMutations.js";
6
- export { type Statewire, StatewireClient, StatewireResource, StatewireSSE, StatewireSendError, StatewireWS, applyOptimistic, useStatewire };
5
+ export { type Statewire, StatewireClient, StatewireResource, StatewireSSE, StatewireSendError, StatewireWS, useStatewire };
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
- import { applyOptimistic } from "./util/recordMutations.js";
2
1
  import { StatewireSendError } from "./types.js";
3
2
  import { StatewireClient, StatewireResource, useStatewire } from "./statewire.js";
4
3
  import { StatewireSSE } from "./transports/sse.js";
5
4
  import { StatewireWS } from "./transports/ws.js";
6
- export { StatewireClient, StatewireResource, StatewireSSE, StatewireSendError, StatewireWS, applyOptimistic, useStatewire };
5
+ export { StatewireClient, StatewireResource, StatewireSSE, StatewireSendError, StatewireWS, useStatewire };
@@ -2,29 +2,27 @@ import { StatewireSessionStoreOptions } from "./client/sessionStore.js";
2
2
  import { Statewire } from "./types.js";
3
3
  import { ResourceElement } from "@assistant-ui/tap";
4
4
  //#region src/statewire.d.ts
5
- declare const useStatewire: <TState, TCommands extends Statewire.Commands = Statewire.Commands, TEvent = any>(options: StatewireClient.Options<TState, TCommands, TEvent>) => StatewireClient.Helpers<TState, TCommands>;
6
- declare const StatewireResource: <TState, TCommands extends Statewire.Commands = Statewire.Commands, TEvent = any>(options: StatewireClient.Options<TState, TCommands, TEvent>) => ResourceElement<StatewireClient.Helpers<TState, TCommands>>;
7
- declare class StatewireClient<TState, TCommands extends Statewire.Commands = Statewire.Commands, TEvent = any> {
5
+ declare const useStatewire: <TState, TCommands extends Statewire.Commands = Statewire.Commands>(options: StatewireClient.Options<TState, TCommands>) => StatewireClient.Helpers<TState, TCommands>;
6
+ declare const StatewireResource: <TState, TCommands extends Statewire.Commands = Statewire.Commands>(options: StatewireClient.Options<TState, TCommands>) => ResourceElement<StatewireClient.Helpers<TState, TCommands>>;
7
+ declare class StatewireClient<TState, TCommands extends Statewire.Commands = Statewire.Commands> {
8
8
  private readonly root;
9
9
  private disposed;
10
- constructor(options: StatewireClient.Options<TState, TCommands, TEvent>);
10
+ constructor(options: StatewireClient.Options<TState, TCommands>);
11
11
  get state(): TState;
12
12
  get connection(): StatewireClient.Connection;
13
13
  get commands(): Statewire.CommandsProxy<TCommands>;
14
- setContext(value: Record<string, unknown>): Promise<unknown>;
14
+ get isBusy(): boolean;
15
+ setContext(value: Record<string, unknown>): void;
15
16
  subscribe(listener: () => void): () => void;
16
17
  dispose(): void;
17
18
  }
18
19
  declare namespace StatewireClient {
19
- type Options<TState, TCommands extends Statewire.Commands = Statewire.Commands, TEvent = any> = {
20
- transport: ResourceElement<Statewire<TState, TCommands, TEvent>>;
20
+ type Options<TState, TCommands extends Statewire.Commands = Statewire.Commands> = {
21
+ transport: ResourceElement<Statewire<TState, TCommands>>;
21
22
  /** Served as the canonical state until the attach snapshot; own commands fold over it. */
22
23
  initialState?: TState;
23
24
  optimistic?: Optimistic<NonNullable<TState>, TCommands>;
24
25
  materialize?: Materialize<NonNullable<TState>>;
25
- onEvent?: (event: TEvent, ctx: {
26
- state: NonNullable<TState>;
27
- }) => void;
28
26
  onStateChange?: (state: NonNullable<TState>, ctx: {
29
27
  changes: Statewire.Changes;
30
28
  }) => void;
@@ -40,13 +38,17 @@ declare namespace StatewireClient {
40
38
  getState: () => TState;
41
39
  connection: Connection;
42
40
  commands: Statewire.CommandsProxy<TCommands>;
41
+ /** Negation of the server's broadcast idle level (a transport without idle is always busy). */
42
+ isBusy: boolean;
43
43
  /** Replace this client's ambient context on the server; throws on transports without a client context channel. */
44
- setContext: (value: Record<string, unknown>) => Promise<unknown>;
44
+ setContext: (value: Record<string, unknown>) => void;
45
45
  };
46
46
  type ConnectionState = {
47
47
  status: "connecting";
48
48
  } | {
49
49
  status: "live";
50
+ } | {
51
+ status: "idle";
50
52
  } | {
51
53
  status: "reconnecting";
52
54
  cause: "dropped" | "evicted" | "error" | "unauthorized";
@@ -80,32 +82,31 @@ declare namespace StatewireClient {
80
82
  */
81
83
  type Materialize<TState> = (draft: TState) => void;
82
84
  /**
83
- * fate: "aborted" = never sent, "rejected" = the handler refused,
84
- * "unknown-command" = no such command, "invalid-params" = the args did not
85
- * match the command's schema, "lost" = sent but never covered by an ack,
86
- * "crashed" = acked but the processing died, "result-unavailable" = acked
87
- * but the result is gone.
85
+ * fate: "aborted" = never sent, "rejected" = the server refused it
86
+ * (`code` "unknown-command" | "invalid-params" for the static rejects,
87
+ * code-less for handler rejects), "lost" = sent but never covered by an
88
+ * applied, "failed" = the handler ran and threw, "crashed" = the handler
89
+ * died involuntarily, "expired" = concluded but the outcome is
90
+ * unavailable.
88
91
  * cause is which channel settled it: "server" = the server's answer;
89
92
  * the rest are channel deaths the client settled locally.
90
93
  */
91
94
  type SendFailure = {
92
95
  fate: "rejected";
93
96
  cause: "server";
94
- message: string;
97
+ code?: "unknown-command" | "invalid-params";
98
+ message?: string;
95
99
  payload?: unknown;
96
100
  } | {
97
- fate: "unknown-command";
101
+ fate: "failed";
98
102
  cause: "server";
99
- } | {
100
- fate: "invalid-params";
101
- cause: "server";
102
- message: string;
103
+ message?: string;
103
104
  } | {
104
105
  fate: "crashed";
105
106
  cause: "server";
106
107
  } | {
107
- fate: "result-unavailable";
108
- cause: "server" | "unmount" | "stream-gone" | "session-lost" | "superseded";
108
+ fate: "expired";
109
+ cause: "server";
109
110
  } | {
110
111
  fate: "aborted";
111
112
  cause: "unmount" | "stream-gone" | "session-lost" | "superseded";
@@ -123,6 +124,8 @@ declare namespace StatewireClient {
123
124
  headers?: HeadersOption;
124
125
  fetch?: typeof globalThis.fetch;
125
126
  onChange?: (connection: Connection) => void;
127
+ /** The broadcast idle flag's rising edge: the instance went quiet. */
128
+ onDone?: () => void;
126
129
  };
127
130
  type WSOptions = StatewireSessionStoreOptions & {
128
131
  url: string;
@@ -131,6 +134,8 @@ declare namespace StatewireClient {
131
134
  query?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
132
135
  webSocket?: new (url: string, protocols?: string | string[]) => WebSocket;
133
136
  onChange?: (connection: Connection) => void;
137
+ /** The broadcast idle flag's rising edge: the instance went quiet. */
138
+ onDone?: () => void;
134
139
  };
135
140
  }
136
141
  //#endregion
@@ -1 +1 @@
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;;IAExD,eAAe;IACf,aAAa,WAAW,YAAY,SAAS;IAC7C,cAAc,YAAY,YAAY;IACtC,WAAW,OAAO,QAAQ;MAAO,OAAO,YAAY;;IACpD,iBACE,OAAO,YAAY,SACnB;MAAO,SAAS,UAAU;;IAE5B,mBACE,QAAQ,UAAU,cAAc,YAChC;MAAO,OAAO,YAAY;;IAE5B,sBAAsB,YAAY;;OAGxB,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;;;;;;;OAQU,YAAY,WAAW,OAAO;;;;;;;;;;OAW9B;IACN;IAAkB;IAAiB;IAAiB;;IACpD;IAAyB;;IACzB;IAAwB;IAAiB;;IACzC;IAAiB;;IAEjB;IACA;;IAQA;IACA;IACA;;IAGA;IACA;;;OAIM,gBACR,gBACE;IAAO;QAAyB,cAAc,QAAQ;OAEhD,aAAa;IACvB;IACA,UAAU;IACV,eAAe,WAAW;IAC1B,YAAY,YAAY;;OAGd,YAAY;IACtB;IACA,UAAU;IACV;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,UAEjD,SAAS,gBAAgB,QAAQ,QAAQ,eACxC,gBAAgB,QAAQ,QAAQ;cAuCtB,oBACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UAEjD,SAAS,gBAAgB,QAAQ,QAAQ,eACxC,gBAAgB,gBAAgB,QAAQ,QAAQ;cAGtC,gBACX,QACA,kBAAkB,UAAU,WAAW,UAAU;mBAEhC;UACT;EAEI,YAAA,SAAS,gBAAgB,QAAQ,QAAQ;MAKjD,SAAS;MAIT,cAAc,gBAAgB;MAI9B,YAAY,UAAU,cAAc;MAIpC;EAIJ,WAAW,OAAO;EAIlB,UAAU;EAIV;;kBAOe;OACH,QACV,QACA,kBAAkB,UAAU,WAAW,UAAU;IAEjD,WAAW,gBAAgB,UAAU,QAAQ;;IAE7C,eAAe;IACf,aAAa,WAAW,YAAY,SAAS;IAC7C,cAAc,YAAY,YAAY;IACtC,iBACE,OAAO,YAAY,SACnB;MAAO,SAAS,UAAU;;IAE5B,mBACE,QAAQ,UAAU,cAAc,YAChC;MAAO,OAAO,YAAY;;IAE5B,sBAAsB,YAAY;;OAGxB,QACV,QACA,kBAAkB,UAAU,WAAW,UAAU;;IAGjD,OAAO;;IAEP,gBAAgB;IAChB,YAAY;IACZ,UAAU,UAAU,cAAc;;IAElC;;IAEA,aAAa,OAAO;;OAGV;IACN;;IACA;;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;;;;;;;OAQU,YAAY,WAAW,OAAO;;;;;;;;;;;OAY9B;IAEN;IACA;IACA;IACA;IACA;;IAEA;IAAgB;IAAiB;;IACjC;IAAiB;;IACjB;IAAiB;;IAEjB;IACA;IACA;;IAGA;IACA;;;OAIM,gBACR,gBACE;IAAO;QAAyB,cAAc,QAAQ;OAEhD,aAAa;IACvB;IACA,UAAU;IACV,eAAe,WAAW;IAC1B,YAAY,YAAY;;IAExB;;OAGU,YAAY;IACtB;IACA,UAAU;IACV;IACA,QACI,gCACO,yBAAyB,QAAQ;IAC5C,iBAAiB,aAAa,kCAAkC;IAChE,YAAY,YAAY;;IAExB"}
package/dist/statewire.js CHANGED
@@ -9,18 +9,21 @@ const useStatewire = (options) => {
9
9
  const core = useStatewireCore(() => latest.current, value);
10
10
  const [subscribe] = useState(() => (listener) => core.subscribe(() => flushTapSync(listener)));
11
11
  const state = useSyncExternalStore(subscribe, core.getState, core.getState);
12
+ const isBusy = useSyncExternalStore(subscribe, core.getBusy, core.getBusy);
12
13
  const connection = value.connection ?? LIVE_CONNECTION;
13
14
  return useMemo(() => ({
14
15
  state,
15
16
  getState: core.getState,
16
17
  connection,
17
18
  commands: core.commands,
19
+ isBusy,
18
20
  setContext: (contextValue) => {
19
21
  if (!value.setContext) throw new Error("statewire: this transport has no client context channel");
20
- return value.setContext(contextValue);
22
+ value.setContext(contextValue);
21
23
  }
22
24
  }), [
23
25
  state,
26
+ isBusy,
24
27
  connection,
25
28
  core,
26
29
  value
@@ -43,8 +46,11 @@ var StatewireClient = class {
43
46
  get commands() {
44
47
  return this.root.getValue().commands;
45
48
  }
49
+ get isBusy() {
50
+ return this.root.getValue().isBusy;
51
+ }
46
52
  setContext(value) {
47
- return this.root.getValue().setContext(value);
53
+ this.root.getValue().setContext(value);
48
54
  }
49
55
  subscribe(listener) {
50
56
  return this.root.subscribe(listener);
@@ -1 +1 @@
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","initialState","optimistic","Optimistic","NonNullable","materialize","Materialize","onEvent","event","ctx","onStateChange","changes","Changes","onCommandChange","update","CommandUpdate","onConnectionChange","ConnectionState","status","cause","attempt","nextRetryAt","lastError","message","payload","reconnect","ConnectionStatus","draft","command","Command","OptimisticContext","ts","seq","clientId","SendFailure","fate","predecessorLost","HeadersOption","HeadersInit","staleAuth","SSEOptions","url","headers","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 /** Served as the canonical state until the attach snapshot; own commands fold over it. */\n initialState?: TState;\n optimistic?: Optimistic<NonNullable<TState>, TCommands>;\n materialize?: Materialize<NonNullable<TState>>;\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 onConnectionChange?: (connection: Connection) => 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\" | \"unauthorized\";\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 * Mutate the draft to make server-accepted, still-uncommitted intents in\n * the state concrete (e.g. dispatch records into messages). Runs after the\n * optimistic folds on every published state.\n */\n export type Materialize<TState> = (draft: TState) => void;\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: \"unmount\" | \"stream-gone\" | \"session-lost\" | \"superseded\";\n predecessorLost?: true;\n }\n | {\n fate: \"lost\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\" | \"superseded\";\n };\n\n /** Static headers, or a resolver invoked per request/attach; `staleAuth` is true right after an auth rejection. */\n export type HeadersOption =\n | HeadersInit\n | ((ctx: { staleAuth: boolean }) => HeadersInit | Promise<HeadersInit>);\n\n export type SSEOptions = StatewireSessionStoreOptions & {\n url: string;\n headers?: HeadersOption;\n fetch?: typeof globalThis.fetch;\n onChange?: (connection: Connection) => void;\n };\n\n export type WSOptions = StatewireSessionStoreOptions & {\n url: string;\n headers?: HeadersOption;\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
+ {"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","Helpers","latest","current","value","transport","core","subscribe","listener","state","getState","isBusy","getBusy","connection","commands","CommandsProxy","setContext","contextValue","Record","Error","StatewireResource","root","disposed","constructor","ClientRoot","getValue","Connection","dispose","unmount","initialState","optimistic","Optimistic","NonNullable","materialize","Materialize","onStateChange","ctx","changes","Changes","onCommandChange","update","CommandUpdate","onConnectionChange","ConnectionState","status","cause","attempt","nextRetryAt","lastError","message","payload","reconnect","ConnectionStatus","draft","command","Command","OptimisticContext","ts","seq","clientId","SendFailure","fate","code","predecessorLost","HeadersOption","HeadersInit","staleAuth","Promise","SSEOptions","url","headers","fetch","globalThis","onChange","onDone","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>(\n options: StatewireClient.Options<TState, TCommands>,\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 isBusy = useSyncExternalStore(subscribe, core.getBusy, core.getBusy);\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 isBusy,\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 value.setContext(contextValue);\n },\n }),\n [state, isBusy, connection, core, value],\n );\n};\n\nexport const StatewireResource = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireClient.Options<TState, TCommands>,\n): ResourceElement<StatewireClient.Helpers<TState, TCommands>> =>\n resource(useStatewire<TState, TCommands>)(options);\n\nexport class StatewireClient<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n> {\n private readonly root;\n private disposed = false;\n\n constructor(options: StatewireClient.Options<TState, TCommands>) {\n const ClientRoot = () => useStatewire<TState, TCommands>(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 get isBusy(): boolean {\n return this.root.getValue().isBusy;\n }\n\n setContext(value: Record<string, unknown>): void {\n 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 > = {\n transport: ResourceElement<Statewire<TState, TCommands>>;\n /** Served as the canonical state until the attach snapshot; own commands fold over it. */\n initialState?: TState;\n optimistic?: Optimistic<NonNullable<TState>, TCommands>;\n materialize?: Materialize<NonNullable<TState>>;\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 onConnectionChange?: (connection: Connection) => 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 /** Negation of the server's broadcast idle level (a transport without idle is always busy). */\n isBusy: boolean;\n /** Replace this client's ambient context on the server; throws on transports without a client context channel. */\n setContext: (value: Record<string, unknown>) => void;\n };\n\n export type ConnectionState =\n | { status: \"connecting\" }\n | { status: \"live\" }\n | { status: \"idle\" }\n | {\n status: \"reconnecting\";\n cause: \"dropped\" | \"evicted\" | \"error\" | \"unauthorized\";\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 * Mutate the draft to make server-accepted, still-uncommitted intents in\n * the state concrete (e.g. dispatch records into messages). Runs after the\n * optimistic folds on every published state.\n */\n export type Materialize<TState> = (draft: TState) => void;\n\n /**\n * fate: \"aborted\" = never sent, \"rejected\" = the server refused it\n * (`code` \"unknown-command\" | \"invalid-params\" for the static rejects,\n * code-less for handler rejects), \"lost\" = sent but never covered by an\n * applied, \"failed\" = the handler ran and threw, \"crashed\" = the handler\n * died involuntarily, \"expired\" = concluded but the outcome is\n * unavailable.\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 | {\n fate: \"rejected\";\n cause: \"server\";\n code?: \"unknown-command\" | \"invalid-params\";\n message?: string;\n payload?: unknown;\n }\n | { fate: \"failed\"; cause: \"server\"; message?: string }\n | { fate: \"crashed\"; cause: \"server\" }\n | { fate: \"expired\"; cause: \"server\" }\n | {\n fate: \"aborted\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\" | \"superseded\";\n predecessorLost?: true;\n }\n | {\n fate: \"lost\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\" | \"superseded\";\n };\n\n /** Static headers, or a resolver invoked per request/attach; `staleAuth` is true right after an auth rejection. */\n export type HeadersOption =\n | HeadersInit\n | ((ctx: { staleAuth: boolean }) => HeadersInit | Promise<HeadersInit>);\n\n export type SSEOptions = StatewireSessionStoreOptions & {\n url: string;\n headers?: HeadersOption;\n fetch?: typeof globalThis.fetch;\n onChange?: (connection: Connection) => void;\n /** The broadcast idle flag's rising edge: the instance went quiet. */\n onDone?: () => void;\n };\n\n export type WSOptions = StatewireSessionStoreOptions & {\n url: string;\n headers?: HeadersOption;\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 /** The broadcast idle flag's rising edge: the instance went quiet. */\n onDone?: () => void;\n };\n}\n"],"mappings":";;;;AAYA,MAAaa,gBAIXE,YAC+C;CAC/C,MAAMM,SAASpB,OAAOc,OAAO;CAC7BM,OAAOC,UAAUP;CAEjB,MAAMQ,QAAQhB,YAAYQ,QAAQS,SAAS;CAC3C,MAAMC,OAAOf,uBAAuBW,OAAOC,SAASC,KAAK;CACzD,MAAM,CAACG,aAAaxB,gBACXyB,aACLF,KAAKC,gBAAgBrB,aAAasB,QAAQ,CAAC,CAC/C;CAEA,MAAMC,QAAQzB,qBACZuB,WACAD,KAAKI,UACLJ,KAAKI,QACP;CACA,MAAMC,SAAS3B,qBAAqBuB,WAAWD,KAAKM,SAASN,KAAKM,OAAO;CACzE,MAAMC,aAAaT,MAAMS,cAAcvB;CAEvC,OAAOT,eACE;EACL4B;EACAC,UAAUJ,KAAKI;EACfG;EACAC,UAAUR,KAAKQ;EACfH;EACAK,aAAaC,iBAA0C;GACrD,IAAI,CAACb,MAAMY,YACT,MAAM,IAAIG,MACR,yDACF;GAEFf,MAAMY,WAAWC,YAAY;EAC/B;CACF,IACA;EAACR;EAAOE;EAAQE;EAAYP;EAAMF;CAAK,CACzC;AACF;AAEA,MAAagB,qBAIXxB,YAEAT,SAASO,YAA+B,CAAC,CAACE,OAAO;AAEnD,IAAaC,kBAAb,MAGE;CACA;CACA,WAAmB;CAEnB0B,YAAY3B,SAAqD;EAC/D,MAAM4B,mBAAmB9B,aAAgCE,OAAO;EAChE,KAAKyB,OAAOpC,cAAcuC,UAAU;CACtC;CAEA,IAAIf,QAAgB;EAClB,OAAO,KAAKY,KAAKI,SAAS,CAAC,CAACf,SAAS;CACvC;CAEA,IAAIG,aAAyC;EAC3C,OAAO,KAAKQ,KAAKI,SAAS,CAAC,CAACZ;CAC9B;CAEA,IAAIC,WAA+C;EACjD,OAAO,KAAKO,KAAKI,SAAS,CAAC,CAACX;CAC9B;CAEA,IAAIH,SAAkB;EACpB,OAAO,KAAKU,KAAKI,SAAS,CAAC,CAACd;CAC9B;CAEAK,WAAWZ,OAAsC;EAC/C,KAAKiB,KAAKI,SAAS,CAAC,CAACT,WAAWZ,KAAK;CACvC;CAEAG,UAAUC,UAAkC;EAC1C,OAAO,KAAKa,KAAKd,UAAUC,QAAQ;CACrC;CAEAmB,UAAgB;EACd,IAAI,KAAKL,UAAU;EACnB,KAAKA,WAAW;EAChB,KAAKD,KAAKO,QAAQ;CACpB;AACF"}
@@ -6,7 +6,7 @@ type FakeStreamHandle = {
6
6
  close: () => void;
7
7
  };
8
8
  declare const sseFrame: (envelope: StatewireEnvelope) => string;
9
- declare const openSSEStream: (streams: Set<FakeStreamHandle>, clientId: string, onOpen: (handle: FakeStreamHandle) => void, signal?: AbortSignal) => Response;
9
+ declare const openSSEStream: (streams: Set<FakeStreamHandle>, clientId: string, onOpen: (handle: FakeStreamHandle) => void, signal?: AbortSignal, headers?: Record<string, string>) => Response;
10
10
  //#endregion
11
11
  export { FakeStreamHandle, openSSEStream, sseFrame };
12
12
  //# sourceMappingURL=fake-sse.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fake-sse.d.ts","names":[],"sources":["../../src/transports/fake-sse.ts"],"mappings":";;KAEY;EACV;EACA,OAAO;EACP;;cAGW,WAAY,UAAU;cAGtB,gBACX,SAAS,IAAI,mBACb,kBACA,SAAS,QAAQ,2BACjB,SAAS,gBACR"}
1
+ {"version":3,"file":"fake-sse.d.ts","names":[],"sources":["../../src/transports/fake-sse.ts"],"mappings":";;KAEY;EACV;EACA,OAAO;EACP;;cAGW,WAAY,UAAU;cAGtB,gBACX,SAAS,IAAI,mBACb,kBACA,SAAS,QAAQ,2BACjB,SAAS,aACT,UAAU,2BACT"}
@@ -1,6 +1,6 @@
1
1
  //#region src/transports/fake-sse.ts
2
2
  const sseFrame = (envelope) => `data: ${JSON.stringify(envelope)}\n\n`;
3
- const openSSEStream = (streams, clientId, onOpen, signal) => {
3
+ const openSSEStream = (streams, clientId, onOpen, signal, headers) => {
4
4
  const encoder = new TextEncoder();
5
5
  let handle;
6
6
  const body = new ReadableStream({
@@ -30,7 +30,10 @@ const openSSEStream = (streams, clientId, onOpen, signal) => {
30
30
  });
31
31
  return new Response(body, {
32
32
  status: 200,
33
- headers: { "content-type": "text/event-stream" }
33
+ headers: {
34
+ "content-type": "text/event-stream",
35
+ ...headers
36
+ }
34
37
  });
35
38
  };
36
39
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"fake-sse.js","names":["StatewireEnvelope","FakeStreamHandle","clientId","send","text","close","sseFrame","envelope","JSON","stringify","openSSEStream","streams","Set","onOpen","handle","signal","AbortSignal","Response","encoder","TextEncoder","body","ReadableStream","Uint8Array","start","controller","open","enqueue","encode","delete","add","cancel","addEventListener","stream","status","headers"],"sources":["../../src/transports/fake-sse.ts"],"sourcesContent":["import type { StatewireEnvelope } from \"../client/connection\";\n\nexport type FakeStreamHandle = {\n clientId: string;\n send: (text: string) => void;\n close: () => void;\n};\n\nexport const sseFrame = (envelope: StatewireEnvelope) =>\n `data: ${JSON.stringify(envelope)}\\n\\n`;\n\nexport const openSSEStream = (\n streams: Set<FakeStreamHandle>,\n clientId: string,\n onOpen: (handle: FakeStreamHandle) => void,\n signal?: AbortSignal,\n): Response => {\n const encoder = new TextEncoder();\n let handle: FakeStreamHandle;\n const body = new ReadableStream<Uint8Array>({\n start: (controller) => {\n let open = true;\n handle = {\n clientId,\n send: (text) => {\n if (open) controller.enqueue(encoder.encode(text));\n },\n close: () => {\n if (!open) return;\n open = false;\n streams.delete(handle);\n controller.close();\n },\n };\n streams.add(handle);\n onOpen(handle);\n },\n cancel: () => {\n streams.delete(handle);\n },\n });\n signal?.addEventListener(\"abort\", () => {\n for (const stream of [...streams]) {\n if (stream.clientId === clientId) stream.close();\n }\n });\n return new Response(body, {\n status: 200,\n headers: { \"content-type\": \"text/event-stream\" },\n });\n};\n"],"mappings":";AAQA,MAAaM,YAAYC,aACvB,SAASC,KAAKC,UAAUF,QAAQ,EAAC;AAEnC,MAAaG,iBACXC,SACAT,UACAW,QACAE,WACa;CACb,MAAMG,UAAU,IAAIC,YAAY;CAChC,IAAIL;CACJ,MAAMM,OAAO,IAAIC,eAA2B;EAC1CE,QAAQC,eAAe;GACrB,IAAIC,OAAO;GACXX,SAAS;IACPZ;IACAC,OAAOC,SAAS;KACd,IAAIqB,MAAMD,WAAWE,QAAQR,QAAQS,OAAOvB,IAAI,CAAC;IACnD;IACAC,aAAa;KACX,IAAI,CAACoB,MAAM;KACXA,OAAO;KACPd,QAAQiB,OAAOd,MAAM;KACrBU,WAAWnB,MAAM;IACnB;GACF;GACAM,QAAQkB,IAAIf,MAAM;GAClBD,OAAOC,MAAM;EACf;EACAgB,cAAc;GACZnB,QAAQiB,OAAOd,MAAM;EACvB;CACF,CAAC;CACDC,QAAQgB,iBAAiB,eAAe;EACtC,KAAK,MAAMC,UAAU,CAAC,GAAGrB,OAAO,GAC9B,IAAIqB,OAAO9B,aAAaA,UAAU8B,OAAO3B,MAAM;CAEnD,CAAC;CACD,OAAO,IAAIY,SAASG,MAAM;EACxBa,QAAQ;EACRC,SAAS,EAAE,gBAAgB,oBAAoB;CACjD,CAAC;AACH"}
1
+ {"version":3,"file":"fake-sse.js","names":["StatewireEnvelope","FakeStreamHandle","clientId","send","text","close","sseFrame","envelope","JSON","stringify","openSSEStream","streams","Set","onOpen","handle","signal","AbortSignal","headers","Record","Response","encoder","TextEncoder","body","ReadableStream","Uint8Array","start","controller","open","enqueue","encode","delete","add","cancel","addEventListener","stream","status"],"sources":["../../src/transports/fake-sse.ts"],"sourcesContent":["import type { StatewireEnvelope } from \"../client/connection\";\n\nexport type FakeStreamHandle = {\n clientId: string;\n send: (text: string) => void;\n close: () => void;\n};\n\nexport const sseFrame = (envelope: StatewireEnvelope) =>\n `data: ${JSON.stringify(envelope)}\\n\\n`;\n\nexport const openSSEStream = (\n streams: Set<FakeStreamHandle>,\n clientId: string,\n onOpen: (handle: FakeStreamHandle) => void,\n signal?: AbortSignal,\n headers?: Record<string, string>,\n): Response => {\n const encoder = new TextEncoder();\n let handle: FakeStreamHandle;\n const body = new ReadableStream<Uint8Array>({\n start: (controller) => {\n let open = true;\n handle = {\n clientId,\n send: (text) => {\n if (open) controller.enqueue(encoder.encode(text));\n },\n close: () => {\n if (!open) return;\n open = false;\n streams.delete(handle);\n controller.close();\n },\n };\n streams.add(handle);\n onOpen(handle);\n },\n cancel: () => {\n streams.delete(handle);\n },\n });\n signal?.addEventListener(\"abort\", () => {\n for (const stream of [...streams]) {\n if (stream.clientId === clientId) stream.close();\n }\n });\n return new Response(body, {\n status: 200,\n headers: { \"content-type\": \"text/event-stream\", ...headers },\n });\n};\n"],"mappings":";AAQA,MAAaM,YAAYC,aACvB,SAASC,KAAKC,UAAUF,QAAQ,EAAC;AAEnC,MAAaG,iBACXC,SACAT,UACAW,QACAE,QACAE,YACa;CACb,MAAMG,UAAU,IAAIC,YAAY;CAChC,IAAIP;CACJ,MAAMQ,OAAO,IAAIC,eAA2B;EAC1CE,QAAQC,eAAe;GACrB,IAAIC,OAAO;GACXb,SAAS;IACPZ;IACAC,OAAOC,SAAS;KACd,IAAIuB,MAAMD,WAAWE,QAAQR,QAAQS,OAAOzB,IAAI,CAAC;IACnD;IACAC,aAAa;KACX,IAAI,CAACsB,MAAM;KACXA,OAAO;KACPhB,QAAQmB,OAAOhB,MAAM;KACrBY,WAAWrB,MAAM;IACnB;GACF;GACAM,QAAQoB,IAAIjB,MAAM;GAClBD,OAAOC,MAAM;EACf;EACAkB,cAAc;GACZrB,QAAQmB,OAAOhB,MAAM;EACvB;CACF,CAAC;CACDC,QAAQkB,iBAAiB,eAAe;EACtC,KAAK,MAAMC,UAAU,CAAC,GAAGvB,OAAO,GAC9B,IAAIuB,OAAOhC,aAAaA,UAAUgC,OAAO7B,MAAM;CAEnD,CAAC;CACD,OAAO,IAAIc,SAASG,MAAM;EACxBa,QAAQ;EACRlB,SAAS;GAAE,gBAAgB;GAAqB,GAAGA;EAAQ;CAC7D,CAAC;AACH"}
@@ -12,9 +12,8 @@ declare const createCounterElement: () => import("@assistant-ui/tap").ResourceEl
12
12
  commands: Statewire.CommandsProxy<StatewireHost.HandledCommands<{
13
13
  increment: () => void;
14
14
  }>>;
15
- emit: (event: unknown) => void;
16
15
  }>;
17
- /** Serialize a command frame as a client would send it over a socket. */
16
+ /** Serialize a one-statement cmd-lane frame as a client would send it. */
18
17
  declare const commandFrame: (method: string, seq: number, params?: unknown[]) => string;
19
18
  type SocketRecorder = {
20
19
  sent: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"fixtures.d.ts","names":[],"sources":["../../src/transports/fixtures.ts"],"mappings":";;;;;cASa,cAAM;;cAKN,wDAAoB;;;;YAApB,UAAA,cAAA,cAAA;;;;;;cAaA,eACX,gBACA,aACA;KAGU;EACV;EACA;IAAU;IAAe;;EACzB;EACA,OAAO;EACP,QAAQ,eAAe;;;cAIZ,4BAA2B"}
1
+ {"version":3,"file":"fixtures.d.ts","names":[],"sources":["../../src/transports/fixtures.ts"],"mappings":";;;;;cAKa,cAAM;;cAKN,wDAAoB;;;;YAApB,UAAA,cAAA,cAAA;;;;;cAYA,eACX,gBACA,aACA;KAGU;EACV;EACA;IAAU;IAAe;;EACzB;EACA,OAAO;EACP,QAAQ,eAAe;;;cAIZ,4BAA2B"}