statewire 0.10.0 → 0.12.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 (51) hide show
  1. package/README.md +2 -2
  2. package/dist/client/connection.d.ts +6 -4
  3. package/dist/client/connection.d.ts.map +1 -1
  4. package/dist/client/connection.js +10 -2
  5. package/dist/client/connection.js.map +1 -1
  6. package/dist/client/core.d.ts +1 -1
  7. package/dist/client/session.d.ts +10 -2
  8. package/dist/client/session.d.ts.map +1 -1
  9. package/dist/client/session.js +246 -97
  10. package/dist/client/session.js.map +1 -1
  11. package/dist/host/host.d.ts +1 -1
  12. package/dist/host/http.js +4 -3
  13. package/dist/host/http.js.map +1 -1
  14. package/dist/host/socket.d.ts +6 -5
  15. package/dist/host/socket.d.ts.map +1 -1
  16. package/dist/host/socket.js +12 -20
  17. package/dist/host/socket.js.map +1 -1
  18. package/dist/host-internal.d.ts +2 -1
  19. package/dist/host-internal.d.ts.map +1 -1
  20. package/dist/host.d.ts +2 -2
  21. package/dist/host.d.ts.map +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/statewire.d.ts +29 -3
  24. package/dist/statewire.d.ts.map +1 -1
  25. package/dist/statewire.js.map +1 -1
  26. package/dist/transports/fake-sse.js +4 -4
  27. package/dist/transports/fake-sse.js.map +1 -1
  28. package/dist/transports/fixtures.d.ts +1 -1
  29. package/dist/transports/remote.d.ts +1 -1
  30. package/dist/transports/remote.js +1 -0
  31. package/dist/transports/remote.js.map +1 -1
  32. package/dist/transports/sse.d.ts +2 -2
  33. package/dist/transports/testing.d.ts +1 -1
  34. package/dist/transports/testing.js +2 -4
  35. package/dist/transports/testing.js.map +1 -1
  36. package/dist/wire/meta.d.ts +5 -3
  37. package/dist/wire/meta.d.ts.map +1 -1
  38. package/dist/wire/meta.js +7 -4
  39. package/dist/wire/meta.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/client/connection.ts +12 -6
  42. package/src/client/session.ts +332 -114
  43. package/src/host/http.ts +10 -9
  44. package/src/host/socket.ts +18 -28
  45. package/src/host-internal.ts +2 -1
  46. package/src/host.ts +0 -1
  47. package/src/statewire.ts +33 -3
  48. package/src/transports/fake-sse.ts +6 -6
  49. package/src/transports/remote.ts +1 -0
  50. package/src/transports/testing.ts +2 -7
  51. package/src/wire/meta.ts +12 -4
@@ -1 +1 @@
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","frames","serveFrames","stream","serveStream","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","lease","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","parseFrameValue","frame","first","raw","values","some","parseFrame","JSON","parse","encodePayload","payload","cmdOf","adm","outcome","type","failure","fate","code","String","SocketHostState","heartbeatMs","clientRecordLimit","finOnIdle","authorize","SocketHostOptions","clients","conns","Set","pendingOps","pendingApplied","pendingCmds","idleWire","finished","Fin","disposed","flushScheduled","heartbeatTimer","ReturnType","setInterval","stateHost","StateHost","commandsHost","current","post","connect","SocketConnection","finish","fin","request","Request","Promise","Response","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","FrameAdmission","syn","admitFrame","through","ctxNew","filter","s","cmdNew","at","dur","admission","gcClients","excess","live","oldest","a","b","has","Registration","connection","register","refuse","test","known","created","other","add","snapshot","hello","record","registration","resume","suspend","useSocketHost","transport","options","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\";\nimport { frames as serveFrames, stream as serveStream } from \"./http\";\n\nexport const 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\nexport type ClientRec = {\n id: string;\n lastSeq: number;\n ctxSeq: number;\n lease: string | null;\n touched: number;\n context: Record<string, unknown> | null;\n results: Map<number, { ts: number; cmd: StatewireCmd }>;\n inflight: Map<number, Admission>;\n};\n\nexport type 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\nexport type 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\nexport const parseFrameValue = (\n frame: unknown,\n first: boolean,\n): ParsedFrame => {\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\nexport const 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 return parseFrameValue(frame, first);\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\nexport type SocketHostState = {\n heartbeatMs: number;\n clientRecordLimit: number;\n finOnIdle: boolean;\n authorize: StatewireHostInternal.SocketHostOptions[\"authorize\"];\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 stream: (request: Request) => Promise<Response>;\n frames: (request: Request) => Promise<Response>;\n};\n\nconst scheduleFlush = (h: SocketHostState) => {\n if (h.flushScheduled || h.disposed) return;\n h.flushScheduled = true;\n h.post();\n};\n\nconst recordOp = (\n h: SocketHostState,\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: SocketHostState, 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\nexport const closeConn = (\n h: SocketHostState,\n conn: Conn,\n code?: number,\n reason?: string,\n) => {\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\nexport const sendTo = (h: SocketHostState, 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: SocketHostState) => {\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: SocketHostState, 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: SocketHostState): boolean => {\n for (const rec of h.clients.values()) {\n if (rec.inflight.size > 0) return false;\n }\n return true;\n};\n\nexport const flush = (h: SocketHostState, 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: SocketHostState, 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: SocketHostState, 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: SocketHostState, 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: SocketHostState,\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: SocketHostState,\n rec: ClientRec,\n adm: Admission,\n outcome: CommandsOutcome,\n) => settleCmd(h, rec, adm, cmdOf(adm, outcome));\n\nconst callerOf = (\n h: SocketHostState,\n rec: ClientRec,\n): 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: SocketHostState,\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 = (\n h: SocketHostState,\n rec: ClientRec,\n statement: CmdStatement,\n) => {\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.\nexport type FrameAdmission =\n | { type: \"ok\" }\n | { type: \"refused\"; syn: { syn: { seq: number; ctxSeq: number } } };\n\nexport const admitFrame = (\n h: SocketHostState,\n rec: ClientRec,\n frame: ParsedFrame,\n): FrameAdmission => {\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 return {\n type: \"refused\",\n syn: { syn: { seq: through, ctxSeq: rec.ctxSeq } },\n };\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 return { type: \"ok\" };\n};\n\nconst message = (h: SocketHostState, 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 const rec = h.clients.get(conn.clientId)!;\n const admission = admitFrame(h, rec, frame);\n if (admission.type === \"refused\") {\n sendTo(h, conn, JSON.stringify(admission.syn));\n }\n};\n\nconst gcClients = (h: SocketHostState) => {\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\nexport type Registration = {\n rec: ClientRec;\n conn: Conn;\n connection: StatewireHostInternal.SocketConnection;\n};\n\nexport const register = (\n h: SocketHostState,\n clientId: string,\n socket: StatewireHostInternal.Socket,\n): Registration | null => {\n const refuse = (code: number, reason: string) => {\n try {\n socket.close(code, reason);\n } catch {\n /* peer already gone */\n }\n return null;\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 refuse(1000, \"finished\");\n return null;\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 lease: null,\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 rec.lease = null;\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 const connection: StatewireHostInternal.SocketConnection = {\n clientId,\n message: (data) => message(h, conn, data),\n close: () => closeConn(h, conn),\n };\n return { rec, conn, connection };\n};\n\nconst connect = (\n h: SocketHostState,\n clientId: string,\n socket: StatewireHostInternal.Socket,\n): StatewireHostInternal.SocketConnection => {\n const registration = register(h, clientId, socket);\n return (\n registration?.connection ?? {\n clientId,\n message: () => {},\n close: () => {},\n }\n );\n};\n\nconst finish = (h: SocketHostState, fin: StatewireHost.Fin) => {\n if (h.finished !== null || h.disposed) return;\n h.finished = fin;\n flush(h, fin);\n};\n\nconst resume = (h: SocketHostState) => {\n h.disposed = false;\n h.stateHost?.setOnOp((op) => recordOp(h, op));\n};\n\nconst suspend = (h: SocketHostState) => {\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<\n StatewireHostInternal.SocketHost,\n \"connect\" | \"finish\" | \"stream\" | \"frames\"\n> => {\n const [h] = useState((): SocketHostState => {\n const host: SocketHostState = {\n heartbeatMs: 15_000,\n clientRecordLimit: CLIENT_RECORD_LIMIT,\n finOnIdle: false,\n authorize: undefined,\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 stream: (request) => serveStream(host, request),\n frames: (request) => serveFrames(host, request),\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 h.authorize = options?.authorize;\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 stream: (request) => flushTapSync(() => root.getValue().stream(request)),\n frames: (request) => flushTapSync(() => root.getValue().frames(request)),\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","frames","serveFrames","stream","serveStream","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","lease","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","rejectUnknown","parseFrameValue","frame","first","keys","unknown","filter","key","sort","raw","values","some","parseFrame","JSON","parse","encodePayload","payload","cmdOf","adm","outcome","type","failure","fate","code","String","SocketHostState","heartbeatMs","clientRecordLimit","finOnIdle","authorize","SocketHostOptions","clients","conns","Set","pendingOps","pendingApplied","pendingCmds","idleWire","finished","Fin","disposed","flushScheduled","heartbeatTimer","ReturnType","setInterval","stateHost","StateHost","commandsHost","current","post","connect","SocketConnection","finish","fin","request","Request","Promise","Response","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","entry","min","admittedThrough","reemit","cached","settleCmd","settleWire","callerOf","Caller","isConnected","rejectStatement","admitCmd","names","methods","caller","onApplied","onSettle","catch","FrameAdmission","syn","admitFrame","through","ctxNew","s","cmdNew","at","dur","admission","gcClients","excess","live","oldest","a","b","has","Registration","connection","register","refuse","test","known","created","add","snapshot","hello","registration","resume","suspend","useSocketHost","transport","options","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\";\nimport { frames as serveFrames, stream as serveStream } from \"./http\";\n\nexport const 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\nexport type 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\nexport type Conn = {\n clientId: string;\n socket: StatewireHostInternal.Socket;\n open: boolean;\n sawFrame: boolean;\n lastSentAt: number;\n lease: string | null;\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\nexport type 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\"], { rejectUnknown: true })\n : op[\"value\"];\n if (!isPlainObject(value)) {\n throw new Error(\"context value must be an object\");\n }\n return value;\n};\n\nexport const parseFrameValue = (\n frame: unknown,\n first: boolean,\n): ParsedFrame => {\n if (!isPlainObject(frame)) return violation(\"malformed frame\");\n const keys = Object.keys(frame);\n if (keys.length === 0) return violation(\"malformed frame\");\n const unknown = keys\n .filter((key) => key !== \"headers\" && key !== \"ctx\" && key !== \"cmd\")\n .sort();\n if (unknown.length > 0)\n return violation(`unknown frame key: '${unknown[0]}'`);\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\nexport const 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 return parseFrameValue(frame, first);\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\nexport type SocketHostState = {\n heartbeatMs: number;\n clientRecordLimit: number;\n finOnIdle: boolean;\n authorize: StatewireHostInternal.SocketHostOptions[\"authorize\"];\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 | null;\n finish: (fin: StatewireHost.Fin) => void;\n stream: (request: Request) => Promise<Response>;\n frames: (request: Request) => Promise<Response>;\n};\n\nconst scheduleFlush = (h: SocketHostState) => {\n if (h.flushScheduled || h.disposed) return;\n h.flushScheduled = true;\n h.post();\n};\n\nconst recordOp = (\n h: SocketHostState,\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: SocketHostState, 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\nexport const closeConn = (\n h: SocketHostState,\n conn: Conn,\n code?: number,\n reason?: string,\n) => {\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\nexport const sendTo = (h: SocketHostState, 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: SocketHostState) => {\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: SocketHostState, 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: SocketHostState): boolean => {\n for (const rec of h.clients.values()) {\n if (rec.inflight.size > 0) return false;\n }\n return true;\n};\n\nexport const flush = (h: SocketHostState, 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: SocketHostState, 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: SocketHostState, 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: SocketHostState, 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: SocketHostState,\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: SocketHostState,\n rec: ClientRec,\n adm: Admission,\n outcome: CommandsOutcome,\n) => settleCmd(h, rec, adm, cmdOf(adm, outcome));\n\nconst callerOf = (\n h: SocketHostState,\n rec: ClientRec,\n): 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: SocketHostState,\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 = (\n h: SocketHostState,\n rec: ClientRec,\n statement: CmdStatement,\n) => {\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, {\n rejectUnknown: true,\n }) 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.\nexport type FrameAdmission =\n | { type: \"ok\" }\n | { type: \"refused\"; syn: { syn: { seq: number; ctxSeq: number } } };\n\nexport const admitFrame = (\n h: SocketHostState,\n rec: ClientRec,\n frame: ParsedFrame,\n): FrameAdmission => {\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 return {\n type: \"refused\",\n syn: { syn: { seq: through, ctxSeq: rec.ctxSeq } },\n };\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 return { type: \"ok\" };\n};\n\nconst message = (h: SocketHostState, 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 const rec = h.clients.get(conn.clientId)!;\n const admission = admitFrame(h, rec, frame);\n if (admission.type === \"refused\") {\n sendTo(h, conn, JSON.stringify(admission.syn));\n }\n};\n\nconst gcClients = (h: SocketHostState) => {\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\nexport type Registration = {\n rec: ClientRec;\n conn: Conn;\n connection: StatewireHostInternal.SocketConnection;\n};\n\nexport const register = (\n h: SocketHostState,\n clientId: string,\n socket: StatewireHostInternal.Socket,\n): Registration | null => {\n const refuse = (code: number, reason: string) => {\n try {\n socket.close(code, reason);\n } catch {\n /* peer already gone */\n }\n return null;\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 refuse(1000, \"finished\");\n return null;\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 const conn: Conn = {\n clientId,\n socket,\n open: true,\n sawFrame: false,\n lastSentAt: 0,\n lease: null,\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 ...(h.idleWire && { idle: true }),\n },\n };\n sendTo(h, conn, JSON.stringify(hello));\n const connection: StatewireHostInternal.SocketConnection = {\n clientId,\n message: (data) => message(h, conn, data),\n close: () => closeConn(h, conn),\n };\n return { rec, conn, connection };\n};\n\nconst connect = (\n h: SocketHostState,\n clientId: string,\n socket: StatewireHostInternal.Socket,\n): StatewireHostInternal.SocketConnection | null => {\n const registration = register(h, clientId, socket);\n return registration?.connection ?? null;\n};\n\nconst finish = (h: SocketHostState, fin: StatewireHost.Fin) => {\n if (h.finished !== null || h.disposed) return;\n h.finished = fin;\n flush(h, fin);\n};\n\nconst resume = (h: SocketHostState) => {\n h.disposed = false;\n h.stateHost?.setOnOp((op) => recordOp(h, op));\n};\n\nconst suspend = (h: SocketHostState) => {\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<\n StatewireHostInternal.SocketHost,\n \"connect\" | \"finish\" | \"stream\" | \"frames\"\n> => {\n const [h] = useState((): SocketHostState => {\n const host: SocketHostState = {\n heartbeatMs: 15_000,\n clientRecordLimit: CLIENT_RECORD_LIMIT,\n finOnIdle: false,\n authorize: undefined,\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 stream: (request) => serveStream(host, request),\n frames: (request) => serveFrames(host, request),\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 h.authorize = options?.authorize;\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 fanned out to every attach of a client id, idle edges, and `fin` on\n * 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 stream: (request) => flushTapSync(() => root.getValue().stream(request)),\n frames: (request) => flushTapSync(() => root.getValue().frames(request)),\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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"}
@@ -13,7 +13,8 @@ declare namespace StatewireHostInternal {
13
13
  close: () => void;
14
14
  };
15
15
  type SocketHost = {
16
- connect: (clientId: string, socket: Socket) => SocketConnection;
16
+ /** Null means refused: the socket was told (fin and/or close) and is dead. */
17
+ connect: (clientId: string, socket: Socket) => SocketConnection | null;
17
18
  stream: (request: Request) => Promise<Response>;
18
19
  frames: (request: Request) => Promise<Response>;
19
20
  finish: (fin: StatewireHost.Fin) => void;
@@ -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,SAAS,YAAY,QAAQ;IACtC,SAAS,SAAS,YAAY,QAAQ;IACtC,SAAS,KAAK,cAAc;IAC5B;;OAGU;IACV,aAAa;MACX,SAAS;iBACE;IACb;;IAEA;;IAEA;;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;;IAEV,UAAU,kBAAkB,QAAQ,WAAW;IAC/C,SAAS,SAAS,YAAY,QAAQ;IACtC,SAAS,SAAS,YAAY,QAAQ;IACtC,SAAS,KAAK,cAAc;IAC5B;;OAGU;IACV,aAAa;MACX,SAAS;iBACE;IACb;;IAEA;;IAEA;;OAGU,MAAM,cAAc"}
package/dist/host.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { Statewire } from "./types.js";
2
1
  import { StatewireReject } from "./host/engine.js";
3
2
  import { append } from "./host/draft.js";
4
3
  import { getStateHost, useStatewireCommands, useStatewireState } from "./host/host.js";
4
+ import { Statewire } from "./types.js";
5
5
  //#region src/host.d.ts
6
6
  declare namespace StatewireHost {
7
7
  type StateCtl<TState = unknown> = {
@@ -39,7 +39,7 @@ 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" | "unauthorized" | "gone" | "superseded" | "idle" | (string & {});
42
+ reason: "evicted" | "error" | "unauthorized" | "gone" | "idle" | (string & {});
43
43
  message?: string;
44
44
  payload?: unknown;
45
45
  meta?: unknown;
@@ -1 +1 @@
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;IAQA;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/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
+ import { StatewireSSE } from "./transports/sse.js";
1
2
  import { StatewireClient, StatewireResource, useStatewire } from "./statewire.js";
2
3
  import { Statewire, StatewireSendError } from "./types.js";
3
- import { StatewireSSE } from "./transports/sse.js";
4
4
  import { StatewireWS } from "./transports/ws.js";
5
5
  export { type Statewire, StatewireClient, StatewireResource, StatewireSSE, StatewireSendError, StatewireWS, useStatewire };
@@ -1,3 +1,4 @@
1
+ import { TERMINAL } from "./client/connection.js";
1
2
  import { Statewire } from "./types.js";
2
3
  import { ResourceElement } from "@assistant-ui/tap";
3
4
  //#region src/statewire.d.ts
@@ -88,7 +89,7 @@ declare namespace StatewireClient {
88
89
  * fate: "aborted" = never sent, "rejected" = the server refused it
89
90
  * (`code` "unknown-command" | "invalid-params" for the static rejects,
90
91
  * code-less for handler rejects), "lost" = sent but no verdict will ever
91
- * arrive — the record is gone (`record: "fresh"`) or the channel died: it
92
+ * arrive — the record is gone (a proven seq gap) or the channel died: it
92
93
  * may or may not have run, "failed" = the handler ran and threw,
93
94
  * "crashed" = the handler died involuntarily, "expired" = settled —
94
95
  * result, rejected, failed, or crashed — but the verdict outlived the
@@ -114,12 +115,20 @@ declare namespace StatewireClient {
114
115
  cause: "server";
115
116
  } | {
116
117
  fate: "aborted";
117
- cause: "unmount" | "stream-gone" | "session-lost" | "superseded";
118
+ cause: "unmount" | "stream-gone" | "session-lost";
118
119
  predecessorLost?: true;
119
120
  } | {
120
121
  fate: "lost";
121
- cause: "unmount" | "stream-gone" | "session-lost" | "superseded";
122
+ cause: "unmount" | "stream-gone" | "session-lost";
122
123
  };
124
+ /** Held commands a proven record gap settled `{fate: "lost", cause: "session-lost"}`; return "requeue" to re-enqueue them on the reset clocks. */
125
+ type OnSessionLost = (pending: ReadonlyArray<{
126
+ method: string;
127
+ params: readonly unknown[];
128
+ ts: number;
129
+ seq: number;
130
+ clientId?: string;
131
+ }>) => "requeue" | "drop";
123
132
  /** A command as persisted while un-durable: its lane seq plus the wire statement. */
124
133
  type SessionCommand = {
125
134
  seq: number;
@@ -133,11 +142,26 @@ declare namespace StatewireClient {
133
142
  nextSeq: number;
134
143
  ctxNextSeq: number;
135
144
  commands: readonly SessionCommand[];
145
+ /** Terminal verdicts parked for the sharer that minted the seq (shared storage only). */
146
+ verdicts?: readonly SessionVerdict[];
147
+ };
148
+ /** A terminal verdict one sharer's connection carried, parked for the sharer awaiting the seq. */
149
+ type SessionVerdict = {
150
+ seq: number;
151
+ type: (typeof TERMINAL)[number];
152
+ code?: string;
153
+ message?: string;
154
+ payload?: unknown;
155
+ meta?: unknown;
136
156
  };
137
157
  /** Session storage seam; `save` must persist synchronously in the caller's flow. */
138
158
  type SessionStorage = {
139
159
  load: () => SessionRecord | null;
140
160
  save: (record: SessionRecord) => void;
161
+ /** Serializes record mutations across sharers; presence marks the storage shared. */
162
+ lock?: <T>(fn: () => T | Promise<T>) => Promise<T>;
163
+ /** Notifies of record saves by other sharers; returns unsubscribe. Required alongside `lock`. */
164
+ subscribe?: (listener: () => void) => () => void;
141
165
  };
142
166
  /** Static headers, or a resolver invoked per request/attach; `staleAuth` is true right after an auth rejection. */
143
167
  type HeadersOption = HeadersInit | ((ctx: {
@@ -148,6 +172,7 @@ declare namespace StatewireClient {
148
172
  url: string;
149
173
  headers?: HeadersOption;
150
174
  fetch?: typeof globalThis.fetch;
175
+ onSessionLost?: OnSessionLost;
151
176
  onChange?: (connection: Connection) => void;
152
177
  /** The broadcast idle flag's rising edge: the instance went quiet. */
153
178
  onDone?: () => void;
@@ -159,6 +184,7 @@ declare namespace StatewireClient {
159
184
  protocols?: string | string[];
160
185
  query?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
161
186
  webSocket?: new (url: string, protocols?: string | string[]) => WebSocket;
187
+ onSessionLost?: OnSessionLost;
162
188
  onChange?: (connection: Connection) => void;
163
189
  /** The broadcast idle flag's rising edge: the instance went quiet. */
164
190
  onDone?: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"statewire.d.ts","names":[],"sources":["../src/statewire.ts"],"mappings":";;;cA+Ba,eACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UAEjD,SAAS,gBAAgB,QAAQ,QAAQ,eACxC,gBAAgB,QAAQ,QAAQ;cA8CtB,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;MACM,UAAU,gBAAgB;;;IAGhC,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;;;;;;;;;;;;;OAc9B;IAEN;IACA;IACA;IACA;IACA;;IAEA;IAAgB;IAAiB;;IACjC;IAAiB;;IACjB;IAAiB;;IAEjB;IACA;IACA;;IAGA;IACA;;;OAIM;IACV;IACA;IACA;IACA;;;OAIU;IACV;IACA;IACA;IACA,mBAAmB;;;OAIT;IACV,YAAY;IACZ,OAAO,QAAQ;;;OAIL,gBACR,gBACE;IAAO;QAAyB,cAAc,QAAQ;OAEhD;IACV,eAAe;IACf;IACA,UAAU;IACV,eAAe,WAAW;IAC1B,YAAY,YAAY;;IAExB;;OAGU;IACV,eAAe;IACf;IACA,UAAU;IACV;IACA,QACI,gCACO,yBAAyB,QAAQ;IAC5C,iBAAiB,aAAa,kCAAkC;IAChE,YAAY,YAAY;;IAExB"}
1
+ {"version":3,"file":"statewire.d.ts","names":[],"sources":["../src/statewire.ts"],"mappings":";;;;cAgCa,eACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UAEjD,SAAS,gBAAgB,QAAQ,QAAQ,eACxC,gBAAgB,QAAQ,QAAQ;cA8CtB,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;MACM,UAAU,gBAAgB;;;IAGhC,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;;;;;;;;;;;;;OAc9B;IAEN;IACA;IACA;IACA;IACA;;IAEA;IAAgB;IAAiB;;IACjC;IAAiB;;IACjB;IAAiB;;IAEjB;IACA;IACA;;IAGA;IACA;;;OAIM,iBACV,SAAS;IACP;IACA;IACA;IACA;IACA;;;OAKQ;IACV;IACA;IACA;IACA;;;OAIU;IACV;IACA;IACA;IACA,mBAAmB;;IAEnB,oBAAoB;;;OAIV;IACV;IACA,cAAc;IACd;IACA;IACA;IACA;;;OAIU;IACV,YAAY;IACZ,OAAO,QAAQ;;IAEf,QAAQ,GAAG,UAAU,IAAI,QAAQ,OAAO,QAAQ;;IAEhD,aAAa;;;OAIH,gBACR,gBACE;IAAO;QAAyB,cAAc,QAAQ;OAEhD;IACV,eAAe;IACf;IACA,UAAU;IACV,eAAe,WAAW;IAC1B,gBAAgB;IAChB,YAAY,YAAY;;IAExB;;OAGU;IACV,eAAe;IACf;IACA,UAAU;IACV;IACA,QACI,gCACO,yBAAyB,QAAQ;IAC5C,iBAAiB,aAAa,kCAAkC;IAChE,gBAAgB;IAChB,YAAY,YAAY;;IAExB"}
@@ -1 +1 @@
1
- {"version":3,"file":"statewire.js","names":["useMemo","useRef","useState","useSyncExternalStore","createTapRoot","flushTapSync","resource","useContextProvider","useResource","useResources","withKey","ResourceElement","LIVE_CONNECTION","useStatewireCore","Statewire","SessionStorageContext","useSessionTransport","transport","session","StatewireClient","SessionStorage","SessionTransport","useStatewire","Commands","options","Options","TState","TCommands","Helpers","latest","current","storage","undefined","value","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","SessionCommand","method","params","meta","SessionRecord","nextSeq","ctxNextSeq","load","save","record","HeadersOption","HeadersInit","staleAuth","Promise","SSEOptions","sessionStore","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 useContextProvider,\n useResource,\n useResources,\n withKey,\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 { SessionStorageContext } from \"./client/sessionStore\";\n\nconst useSessionTransport = ({\n transport,\n session,\n}: {\n transport: ResourceElement<Statewire<any, any>>;\n session: StatewireClient.SessionStorage | undefined;\n}): Statewire<unknown> =>\n useContextProvider(SessionStorageContext, session, () =>\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useResource(transport),\n );\n\n// The transport owns the session; it reads the storage off the context this\n// wrapper's own fiber provides, so plain-React callers get a provider too.\nconst SessionTransport = resource(useSessionTransport);\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 session = useResources(\n options.storage?.session !== undefined\n ? [withKey(\"session\", options.storage.session)]\n : [],\n )[0];\n const value = useResource(\n SessionTransport({ transport: options.transport, session }),\n );\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 /** Durable session: the mounted storage holds this client's lane identity and its un-durable commands, which resend on the next attach. */\n storage?:\n | { session?: ResourceElement<SessionStorage> | undefined }\n | undefined;\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 no verdict will ever\n * arrive — the record is gone (`record: \"fresh\"`) or the channel died: it\n * may or may not have run, \"failed\" = the handler ran and threw,\n * \"crashed\" = the handler died involuntarily, \"expired\" = settled —\n * result, rejected, failed, or crashed — but the verdict outlived the\n * server's retention.\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?: string;\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 /** A command as persisted while un-durable: its lane seq plus the wire statement. */\n export type SessionCommand = {\n seq: number;\n method: string;\n params: unknown[];\n meta?: unknown;\n };\n\n /** The persisted session: the lane identity, the lane clocks, and the commands the server has not marked durable. */\n export type SessionRecord = {\n clientId: string;\n nextSeq: number;\n ctxNextSeq: number;\n commands: readonly SessionCommand[];\n };\n\n /** Session storage seam; `save` must persist synchronously in the caller's flow. */\n export type SessionStorage = {\n load: () => SessionRecord | null;\n save: (record: SessionRecord) => void;\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 = {\n sessionStore?: SessionStorage | undefined;\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 = {\n sessionStore?: SessionStorage | undefined;\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":";;;;;AAeA,MAAMgB,uBAAuB,EAC3BC,WACAC,cAKAX,mBAAmBQ,uBAAuBG,eAExCV,YAAYS,SAAS,CACvB;AAIF,MAAMI,mBAAmBf,SAASU,mBAAmB;AAErD,MAAaM,gBAIXE,YAC+C;CAC/C,MAAMK,SAAS5B,OAAOuB,OAAO;CAC7BK,OAAOC,UAAUN;CAEjB,MAAMN,UAAUT,aACde,QAAQO,SAASb,YAAYc,KAAAA,IACzB,CAACtB,QAAQ,WAAWc,QAAQO,QAAQb,OAAO,CAAC,IAC5C,CAAA,CACN,CAAC,CAAC;CACF,MAAMe,QAAQzB,YACZa,iBAAiB;EAAEJ,WAAWO,QAAQP;EAAWC;CAAQ,CAAC,CAC5D;CACA,MAAMgB,OAAOrB,uBAAuBgB,OAAOC,SAASG,KAAK;CACzD,MAAM,CAACE,aAAajC,gBACXkC,aACLF,KAAKC,gBAAgB9B,aAAa+B,QAAQ,CAAC,CAC/C;CAEA,MAAMC,QAAQlC,qBACZgC,WACAD,KAAKI,UACLJ,KAAKI,QACP;CACA,MAAMC,SAASpC,qBAAqBgC,WAAWD,KAAKM,SAASN,KAAKM,OAAO;CACzE,MAAMC,aAAaR,MAAMQ,cAAc7B;CAEvC,OAAOZ,eACE;EACLqC;EACAC,UAAUJ,KAAKI;EACfG;EACAC,UAAUR,KAAKQ;EACfH;EACAK,aAAaC,iBAA0C;GACrD,IAAI,CAACZ,MAAMW,YACT,MAAM,IAAIG,MACR,yDACF;GAEFd,MAAMW,WAAWC,YAAY;EAC/B;CACF,IACA;EAACR;EAAOE;EAAQE;EAAYP;EAAMD;CAAK,CACzC;AACF;AAEA,MAAae,qBAIXxB,YAEAlB,SAASgB,YAA+B,CAAC,CAACE,OAAO;AAEnD,IAAaL,kBAAb,MAGE;CACA;CACA,WAAmB;CAEnBgC,YAAY3B,SAAqD;EAC/D,MAAM4B,mBAAmB9B,aAAgCE,OAAO;EAChE,KAAKyB,OAAO7C,cAAcgD,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,WAAWX,OAAsC;EAC/C,KAAKgB,KAAKI,SAAS,CAAC,CAACT,WAAWX,KAAK;CACvC;CAEAE,UAAUC,UAAkC;EAC1C,OAAO,KAAKa,KAAKd,UAAUC,QAAQ;CACrC;CAEAmB,UAAgB;EACd,IAAI,KAAKL,UAAU;EACnB,KAAKA,WAAW;EAChB,KAAKD,KAAKO,QAAQ;CACpB;AACF"}
1
+ {"version":3,"file":"statewire.js","names":["useMemo","useRef","useState","useSyncExternalStore","createTapRoot","flushTapSync","resource","useContextProvider","useResource","useResources","withKey","ResourceElement","LIVE_CONNECTION","useStatewireCore","Statewire","TERMINAL","SessionStorageContext","useSessionTransport","transport","session","StatewireClient","SessionStorage","SessionTransport","useStatewire","Commands","options","Options","TState","TCommands","Helpers","latest","current","storage","undefined","value","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","OnSessionLost","pending","ReadonlyArray","method","params","SessionCommand","meta","SessionRecord","nextSeq","ctxNextSeq","verdicts","SessionVerdict","type","load","save","record","lock","fn","T","Promise","HeadersOption","HeadersInit","staleAuth","SSEOptions","sessionStore","url","headers","fetch","globalThis","onSessionLost","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 useContextProvider,\n useResource,\n useResources,\n withKey,\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 { TERMINAL } from \"./client/connection\";\nimport { SessionStorageContext } from \"./client/sessionStore\";\n\nconst useSessionTransport = ({\n transport,\n session,\n}: {\n transport: ResourceElement<Statewire<any, any>>;\n session: StatewireClient.SessionStorage | undefined;\n}): Statewire<unknown> =>\n useContextProvider(SessionStorageContext, session, () =>\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useResource(transport),\n );\n\n// The transport owns the session; it reads the storage off the context this\n// wrapper's own fiber provides, so plain-React callers get a provider too.\nconst SessionTransport = resource(useSessionTransport);\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 session = useResources(\n options.storage?.session !== undefined\n ? [withKey(\"session\", options.storage.session)]\n : [],\n )[0];\n const value = useResource(\n SessionTransport({ transport: options.transport, session }),\n );\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 /** Durable session: the mounted storage holds this client's lane identity and its un-durable commands, which resend on the next attach. */\n storage?:\n | { session?: ResourceElement<SessionStorage> | undefined }\n | undefined;\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 no verdict will ever\n * arrive — the record is gone (a proven seq gap) or the channel died: it\n * may or may not have run, \"failed\" = the handler ran and threw,\n * \"crashed\" = the handler died involuntarily, \"expired\" = settled —\n * result, rejected, failed, or crashed — but the verdict outlived the\n * server's retention.\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?: string;\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\";\n predecessorLost?: true;\n }\n | {\n fate: \"lost\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\";\n };\n\n /** Held commands a proven record gap settled `{fate: \"lost\", cause: \"session-lost\"}`; return \"requeue\" to re-enqueue them on the reset clocks. */\n export type OnSessionLost = (\n pending: ReadonlyArray<{\n method: string;\n params: readonly unknown[];\n ts: number;\n seq: number;\n clientId?: string;\n }>,\n ) => \"requeue\" | \"drop\";\n\n /** A command as persisted while un-durable: its lane seq plus the wire statement. */\n export type SessionCommand = {\n seq: number;\n method: string;\n params: unknown[];\n meta?: unknown;\n };\n\n /** The persisted session: the lane identity, the lane clocks, and the commands the server has not marked durable. */\n export type SessionRecord = {\n clientId: string;\n nextSeq: number;\n ctxNextSeq: number;\n commands: readonly SessionCommand[];\n /** Terminal verdicts parked for the sharer that minted the seq (shared storage only). */\n verdicts?: readonly SessionVerdict[];\n };\n\n /** A terminal verdict one sharer's connection carried, parked for the sharer awaiting the seq. */\n export type SessionVerdict = {\n seq: number;\n type: (typeof TERMINAL)[number];\n code?: string;\n message?: string;\n payload?: unknown;\n meta?: unknown;\n };\n\n /** Session storage seam; `save` must persist synchronously in the caller's flow. */\n export type SessionStorage = {\n load: () => SessionRecord | null;\n save: (record: SessionRecord) => void;\n /** Serializes record mutations across sharers; presence marks the storage shared. */\n lock?: <T>(fn: () => T | Promise<T>) => Promise<T>;\n /** Notifies of record saves by other sharers; returns unsubscribe. Required alongside `lock`. */\n subscribe?: (listener: () => void) => () => void;\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 = {\n sessionStore?: SessionStorage | undefined;\n url: string;\n headers?: HeadersOption;\n fetch?: typeof globalThis.fetch;\n onSessionLost?: OnSessionLost;\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 = {\n sessionStore?: SessionStorage | undefined;\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 onSessionLost?: OnSessionLost;\n onChange?: (connection: Connection) => void;\n /** The broadcast idle flag's rising edge: the instance went quiet. */\n onDone?: () => void;\n };\n}\n"],"mappings":";;;;;AAgBA,MAAMiB,uBAAuB,EAC3BC,WACAC,cAKAZ,mBAAmBS,uBAAuBG,eAExCX,YAAYU,SAAS,CACvB;AAIF,MAAMI,mBAAmBhB,SAASW,mBAAmB;AAErD,MAAaM,gBAIXE,YAC+C;CAC/C,MAAMK,SAAS7B,OAAOwB,OAAO;CAC7BK,OAAOC,UAAUN;CAEjB,MAAMN,UAAUV,aACdgB,QAAQO,SAASb,YAAYc,KAAAA,IACzB,CAACvB,QAAQ,WAAWe,QAAQO,QAAQb,OAAO,CAAC,IAC5C,CAAA,CACN,CAAC,CAAC;CACF,MAAMe,QAAQ1B,YACZc,iBAAiB;EAAEJ,WAAWO,QAAQP;EAAWC;CAAQ,CAAC,CAC5D;CACA,MAAMgB,OAAOtB,uBAAuBiB,OAAOC,SAASG,KAAK;CACzD,MAAM,CAACE,aAAalC,gBACXmC,aACLF,KAAKC,gBAAgB/B,aAAagC,QAAQ,CAAC,CAC/C;CAEA,MAAMC,QAAQnC,qBACZiC,WACAD,KAAKI,UACLJ,KAAKI,QACP;CACA,MAAMC,SAASrC,qBAAqBiC,WAAWD,KAAKM,SAASN,KAAKM,OAAO;CACzE,MAAMC,aAAaR,MAAMQ,cAAc9B;CAEvC,OAAOZ,eACE;EACLsC;EACAC,UAAUJ,KAAKI;EACfG;EACAC,UAAUR,KAAKQ;EACfH;EACAK,aAAaC,iBAA0C;GACrD,IAAI,CAACZ,MAAMW,YACT,MAAM,IAAIG,MACR,yDACF;GAEFd,MAAMW,WAAWC,YAAY;EAC/B;CACF,IACA;EAACR;EAAOE;EAAQE;EAAYP;EAAMD;CAAK,CACzC;AACF;AAEA,MAAae,qBAIXxB,YAEAnB,SAASiB,YAA+B,CAAC,CAACE,OAAO;AAEnD,IAAaL,kBAAb,MAGE;CACA;CACA,WAAmB;CAEnBgC,YAAY3B,SAAqD;EAC/D,MAAM4B,mBAAmB9B,aAAgCE,OAAO;EAChE,KAAKyB,OAAO9C,cAAciD,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,WAAWX,OAAsC;EAC/C,KAAKgB,KAAKI,SAAS,CAAC,CAACT,WAAWX,KAAK;CACvC;CAEAE,UAAUC,UAAkC;EAC1C,OAAO,KAAKa,KAAKd,UAAUC,QAAQ;CACrC;CAEAmB,UAAgB;EACd,IAAI,KAAKL,UAAU;EACnB,KAAKA,WAAW;EAChB,KAAKD,KAAKO,QAAQ;CACpB;AACF"}
@@ -15,7 +15,9 @@ const openSSEStream = (streams, clientId, onOpen, signal, headers) => {
15
15
  if (!open) return;
16
16
  open = false;
17
17
  streams.delete(handle);
18
- controller.close();
18
+ try {
19
+ controller.close();
20
+ } catch {}
19
21
  }
20
22
  };
21
23
  streams.add(handle);
@@ -25,9 +27,7 @@ const openSSEStream = (streams, clientId, onOpen, signal, headers) => {
25
27
  streams.delete(handle);
26
28
  }
27
29
  });
28
- signal?.addEventListener("abort", () => {
29
- for (const stream of [...streams]) if (stream.clientId === clientId) stream.close();
30
- });
30
+ signal?.addEventListener("abort", () => handle.close());
31
31
  return new Response(body, {
32
32
  status: 200,
33
33
  headers: {
@@ -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","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"}
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","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 try {\n controller.close();\n } catch {\n // the reader already canceled\n }\n },\n };\n streams.add(handle);\n onOpen(handle);\n },\n cancel: () => {\n streams.delete(handle);\n },\n });\n signal?.addEventListener(\"abort\", () => handle.close());\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;KACrB,IAAI;MACFY,WAAWrB,MAAM;KACnB,QAAQ,CACN;IAEJ;GACF;GACAM,QAAQoB,IAAIjB,MAAM;GAClBD,OAAOC,MAAM;EACf;EACAkB,cAAc;GACZrB,QAAQmB,OAAOhB,MAAM;EACvB;CACF,CAAC;CACDC,QAAQkB,iBAAiB,eAAenB,OAAOT,MAAM,CAAC;CACtD,OAAO,IAAIc,SAASG,MAAM;EACxBY,QAAQ;EACRjB,SAAS;GAAE,gBAAgB;GAAqB,GAAGA;EAAQ;CAC7D,CAAC;AACH"}
@@ -1,5 +1,5 @@
1
- import { Statewire } from "../types.js";
2
1
  import { StatewireHost } from "../host.js";
2
+ import { Statewire } from "../types.js";
3
3
  import "../index.js";
4
4
  //#region src/transports/fixtures.d.ts
5
5
  /** Let several rounds of queued microtasks and op batches drain. */
@@ -1,6 +1,6 @@
1
+ import { RemoteLink, StatewireRemoteProps } from "./sse.js";
1
2
  import { StatewireClient } from "../statewire.js";
2
3
  import { Statewire } from "../types.js";
3
- import { RemoteLink, StatewireRemoteProps } from "./sse.js";
4
4
  import { StatewireSessionOptions } from "../client/session.js";
5
5
  //#region src/transports/remote.d.ts
6
6
  declare const useStatewireRemote: <TOptions extends {
@@ -4,6 +4,7 @@ import { useEffect } from "@assistant-ui/tap/react-shim";
4
4
  const useStatewireRemote = (options, useLink) => {
5
5
  const s = useStatewireSession({
6
6
  sessionStore: options.sessionStore,
7
+ onSessionLost: options.onSessionLost,
7
8
  onDone: options.onDone
8
9
  });
9
10
  const conn = useLink(options, s.ctx);
@@ -1 +1 @@
1
- {"version":3,"file":"remote.js","names":["useEffect","Statewire","setContext","setTransport","useStatewireSession","StatewireSessionOptions","StatewireClient","RemoteLink","StatewireRemoteProps","useStatewireRemote","url","onChange","connection","Connection","onDone","options","TOptions","useLink","ctx","s","sessionStore","conn","state","canonical","commands","source","durableSeq","isBusy","idle","value"],"sources":["../../src/transports/remote.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport type { Statewire } from \"../types\";\nimport {\n setContext,\n setTransport,\n useStatewireSession,\n} from \"../client/session\";\nimport type { StatewireSessionOptions } from \"../client/session\";\nimport type { StatewireClient } from \"../statewire\";\nimport type { RemoteLink, StatewireRemoteProps } from \"./sse\";\n\nexport const useStatewireRemote = <\n TOptions extends {\n url: string;\n onChange?: (connection: StatewireClient.Connection) => void;\n onDone?: () => void;\n } & StatewireSessionOptions,\n>(\n options: TOptions,\n useLink: (options: TOptions, ctx: StatewireRemoteProps) => RemoteLink,\n): Statewire<any, any> => {\n const s = useStatewireSession({\n sessionStore: options.sessionStore,\n onDone: options.onDone,\n });\n const conn = useLink(options, s.ctx);\n useEffect(() => setTransport(s, conn), [s, conn]);\n\n return {\n state: s.canonical,\n commands: s.commands,\n connection: conn.connection,\n source: s.source,\n get durableSeq() {\n return s.durableSeq;\n },\n get isBusy() {\n return !s.idle;\n },\n setContext: (value) => setContext(s, value),\n };\n};\n"],"mappings":";;;AAWA,MAAaS,sBAOXM,SACAE,YACwB;CACxB,MAAME,IAAIf,oBAAoB;EAC5BgB,cAAcL,QAAQK;EACtBN,QAAQC,QAAQD;CAClB,CAAC;CACD,MAAMO,OAAOJ,QAAQF,SAASI,EAAED,GAAG;CACnClB,gBAAgBG,aAAagB,GAAGE,IAAI,GAAG,CAACF,GAAGE,IAAI,CAAC;CAEhD,OAAO;EACLC,OAAOH,EAAEI;EACTC,UAAUL,EAAEK;EACZZ,YAAYS,KAAKT;EACjBa,QAAQN,EAAEM;EACV,IAAIC,aAAa;GACf,OAAOP,EAAEO;EACX;EACA,IAAIC,SAAS;GACX,OAAO,CAACR,EAAES;EACZ;EACA1B,aAAa2B,UAAU3B,WAAWiB,GAAGU,KAAK;CAC5C;AACF"}
1
+ {"version":3,"file":"remote.js","names":["useEffect","Statewire","setContext","setTransport","useStatewireSession","StatewireSessionOptions","StatewireClient","RemoteLink","StatewireRemoteProps","useStatewireRemote","url","onChange","connection","Connection","onDone","options","TOptions","useLink","ctx","s","sessionStore","onSessionLost","conn","state","canonical","commands","source","durableSeq","isBusy","idle","value"],"sources":["../../src/transports/remote.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport type { Statewire } from \"../types\";\nimport {\n setContext,\n setTransport,\n useStatewireSession,\n} from \"../client/session\";\nimport type { StatewireSessionOptions } from \"../client/session\";\nimport type { StatewireClient } from \"../statewire\";\nimport type { RemoteLink, StatewireRemoteProps } from \"./sse\";\n\nexport const useStatewireRemote = <\n TOptions extends {\n url: string;\n onChange?: (connection: StatewireClient.Connection) => void;\n onDone?: () => void;\n } & StatewireSessionOptions,\n>(\n options: TOptions,\n useLink: (options: TOptions, ctx: StatewireRemoteProps) => RemoteLink,\n): Statewire<any, any> => {\n const s = useStatewireSession({\n sessionStore: options.sessionStore,\n onSessionLost: options.onSessionLost,\n onDone: options.onDone,\n });\n const conn = useLink(options, s.ctx);\n useEffect(() => setTransport(s, conn), [s, conn]);\n\n return {\n state: s.canonical,\n commands: s.commands,\n connection: conn.connection,\n source: s.source,\n get durableSeq() {\n return s.durableSeq;\n },\n get isBusy() {\n return !s.idle;\n },\n setContext: (value) => setContext(s, value),\n };\n};\n"],"mappings":";;;AAWA,MAAaS,sBAOXM,SACAE,YACwB;CACxB,MAAME,IAAIf,oBAAoB;EAC5BgB,cAAcL,QAAQK;EACtBC,eAAeN,QAAQM;EACvBP,QAAQC,QAAQD;CAClB,CAAC;CACD,MAAMQ,OAAOL,QAAQF,SAASI,EAAED,GAAG;CACnClB,gBAAgBG,aAAagB,GAAGG,IAAI,GAAG,CAACH,GAAGG,IAAI,CAAC;CAEhD,OAAO;EACLC,OAAOJ,EAAEK;EACTC,UAAUN,EAAEM;EACZb,YAAYU,KAAKV;EACjBc,QAAQP,EAAEO;EACV,IAAIC,aAAa;GACf,OAAOR,EAAEQ;EACX;EACA,IAAIC,SAAS;GACX,OAAO,CAACT,EAAEU;EACZ;EACA3B,aAAa4B,UAAU5B,WAAWiB,GAAGW,KAAK;CAC5C;AACF"}
@@ -1,7 +1,7 @@
1
- import { StatewireClient } from "../statewire.js";
2
- import { Statewire } from "../types.js";
3
1
  import { StatewireHost } from "../host.js";
4
2
  import { StatewireCmd, StatewireSyn } from "../client/connection.js";
3
+ import { StatewireClient } from "../statewire.js";
4
+ import { Statewire } from "../types.js";
5
5
  import { ResourceElement } from "@assistant-ui/tap";
6
6
  //#region src/transports/sse.d.ts
7
7
  /** A client frame's lanes: stamped statements, strictly consecutive per lane. */
@@ -1,5 +1,5 @@
1
- import { Statewire } from "../types.js";
2
1
  import { StatewireEnvelope } from "../client/connection.js";
2
+ import { Statewire } from "../types.js";
3
3
  import { SocketRecorder, commandFrame, createCounterElement, createSocketRecorder, settle } from "./fixtures.js";
4
4
  import { applyOptimistic } from "../util/recordMutations.js";
5
5
  import { ResourceElement } from "@assistant-ui/tap";
@@ -43,8 +43,7 @@ const createMockTransport = (initialState) => {
43
43
  const id = new Headers(init?.headers).get("Statewire-Client-Id") ?? "";
44
44
  if (url.endsWith("/stream")) {
45
45
  clientId = id;
46
- const known = clients.has(id);
47
- if (!known) clients.set(id, {
46
+ if (!clients.has(id)) clients.set(id, {
48
47
  seq: 0,
49
48
  ctxSeq: 0
50
49
  });
@@ -60,8 +59,7 @@ const createMockTransport = (initialState) => {
60
59
  }],
61
60
  syn: {
62
61
  seq: rec.seq,
63
- ctxSeq: rec.ctxSeq,
64
- record: known ? "resumed" : "fresh"
62
+ ctxSeq: rec.ctxSeq
65
63
  }
66
64
  }));
67
65
  }, init?.signal ?? void 0, { "Statewire-Lease": "mock-lease" });