statewire 0.19.3 → 0.20.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 (73) hide show
  1. package/README.md +15 -1
  2. package/dist/client/connection.d.ts.map +1 -1
  3. package/dist/client/connection.js +5 -4
  4. package/dist/client/connection.js.map +1 -1
  5. package/dist/client/session.d.ts +8 -1
  6. package/dist/client/session.d.ts.map +1 -1
  7. package/dist/client/session.js +52 -12
  8. package/dist/client/session.js.map +1 -1
  9. package/dist/client/sessionStore.d.ts +2 -0
  10. package/dist/client/sessionStore.d.ts.map +1 -1
  11. package/dist/client/sessionStore.js.map +1 -1
  12. package/dist/client/timing.d.ts +2 -0
  13. package/dist/client/timing.d.ts.map +1 -1
  14. package/dist/client/timing.js +5 -4
  15. package/dist/client/timing.js.map +1 -1
  16. package/dist/host/host.d.ts +2 -1
  17. package/dist/host/host.d.ts.map +1 -1
  18. package/dist/host/host.js +6 -1
  19. package/dist/host/host.js.map +1 -1
  20. package/dist/host/http.js +1 -1
  21. package/dist/host/socket.d.ts +6 -1
  22. package/dist/host/socket.d.ts.map +1 -1
  23. package/dist/host/socket.js +79 -9
  24. package/dist/host/socket.js.map +1 -1
  25. package/dist/host-internal.d.ts +1 -1
  26. package/dist/host.d.ts +6 -0
  27. package/dist/host.d.ts.map +1 -1
  28. package/dist/index.d.ts +1 -1
  29. package/dist/statewire.d.ts +3 -0
  30. package/dist/statewire.d.ts.map +1 -1
  31. package/dist/statewire.js +22 -5
  32. package/dist/statewire.js.map +1 -1
  33. package/dist/transports/remote.d.ts.map +1 -1
  34. package/dist/transports/remote.js +3 -2
  35. package/dist/transports/remote.js.map +1 -1
  36. package/dist/transports/sse.d.ts +4 -0
  37. package/dist/transports/sse.d.ts.map +1 -1
  38. package/dist/transports/sse.js +5 -0
  39. package/dist/transports/sse.js.map +1 -1
  40. package/dist/transports/testing.d.ts +2 -0
  41. package/dist/transports/testing.d.ts.map +1 -1
  42. package/dist/transports/testing.js +11 -4
  43. package/dist/transports/testing.js.map +1 -1
  44. package/dist/types.d.ts +4 -0
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/types.js.map +1 -1
  47. package/dist/wire/context.d.ts +14 -0
  48. package/dist/wire/context.d.ts.map +1 -0
  49. package/dist/wire/context.js +31 -0
  50. package/dist/wire/context.js.map +1 -0
  51. package/dist/wire/limits.d.ts +2 -1
  52. package/dist/wire/limits.d.ts.map +1 -1
  53. package/dist/wire/limits.js +2 -1
  54. package/dist/wire/limits.js.map +1 -1
  55. package/dist/wire/protocol.d.ts +1 -1
  56. package/dist/wire/protocol.js +4 -4
  57. package/dist/wire/protocol.js.map +1 -1
  58. package/package.json +1 -1
  59. package/src/client/connection.ts +6 -3
  60. package/src/client/session.ts +74 -4
  61. package/src/client/sessionStore.ts +5 -1
  62. package/src/client/timing.ts +12 -7
  63. package/src/host/host.ts +9 -0
  64. package/src/host/socket.ts +121 -7
  65. package/src/host.ts +5 -0
  66. package/src/statewire.ts +29 -3
  67. package/src/transports/remote.ts +3 -0
  68. package/src/transports/sse.ts +7 -0
  69. package/src/transports/testing.ts +14 -3
  70. package/src/types.ts +12 -0
  71. package/src/wire/context.ts +40 -0
  72. package/src/wire/limits.ts +2 -0
  73. package/src/wire/protocol.ts +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"host.js","names":["useEffect","useState","applyOps","sanitizeValue","StatewirePath","StatewireReject","createStateDraft","createMethodProxy","StateDraft","StatewireSendError","Statewire","StatewireHost","isDev","process","env","stateHosts","WeakMap","StateHost","documentIdentity","protocol","key","JSON","stringify","getStateHost","state","get","StateCell","current","draft","changed","documents","Map","Document","onOp","op","Op","onChange","onDocuments","ctl","StateCtl","host","bump","useStatewireState","init","TState","setVersion","cell","commit","c","flush","v","apply","ops","result","invalidate","push","changedPaths","snapshot","setOnOp","listener","setOnChange","takeChanged","mount","value","options","main","undefined","id","doc","Error","set","unmount","delete","setOnDocuments","record","changedPath","proxy","const","Settled","type","error","CommandsInvocation","method","params","ts","seq","clientId","CommandsOutcome","payload","CommandsListener","onStart","inv","onPosted","onApplied","onCovered","onSettle","outcome","CommandsSubmission","caller","Caller","attach","Attach","CommandsHost","setListener","submit","call","Promise","methods","abort","commandsHosts","getCommandsHost","commands","bindCommandsHost","QueuedCall","resolve","reject","origin","ActiveInvocation","applied","controller","AbortController","settled","CommandsCell","handlers","Record","args","queue","active","running","unmounted","stallTimer","ReturnType","setTimeout","STALL_WARN_MS","LOCAL_CALLER","Object","freeze","auth","isConnected","toSendError","fate","cause","message","code","String","applyInvocation","settleInvocation","abortInvocations","clear","isThenable","PromiseLike","then","handlerOf","prototype","propertyIsEnumerable","handler","dispatch","ctx","Ctx","signal","this","resolved","pump","watchStall","shift","length","console","warn","useStatewireCommands","Commands","THandlers","ThisType","CommandsProxy","HandledCommands","built","Date","now","keys","filter","clearTimeout","queueMicrotask","queued","startsWith"],"sources":["../../src/host/host.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { applyOps, sanitizeValue } from \"../wire/ops\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport { StatewireReject } from \"./engine\";\nimport { createStateDraft } from \"./draft\";\nimport { createMethodProxy } from \"../util/methodProxy\";\nimport type { StateDraft } from \"./draft\";\nimport { StatewireSendError } from \"../types\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireHost } from \"../host\";\n\nconst isDev = () =>\n typeof process === \"undefined\" || process.env[\"NODE_ENV\"] !== \"production\";\n\n// ─── state ──────────────────────────────────────────────\n\nconst stateHosts = new WeakMap<object, StatewireHost.StateHost>();\n\nexport const documentIdentity = (protocol: string, key?: readonly string[]) =>\n JSON.stringify([protocol, key ?? null]);\n\n/** Host-side accessor for a `useStatewireState` state object's source (`snapshot()` reads the committed state). */\nexport const getStateHost = (\n state: object,\n): StatewireHost.StateHost | undefined => stateHosts.get(state);\n\ntype StateCell = {\n current: unknown;\n draft: StateDraft<object> | null;\n changed: StatewirePath[];\n documents: Map<string, StatewireHost.Document>;\n onOp: ((op: Statewire.Op) => void) | null;\n onChange: (() => void) | null;\n onDocuments: (() => void) | null;\n ctl: StatewireHost.StateCtl;\n host: StatewireHost.StateHost;\n bump: () => void;\n};\n\nexport const useStatewireState = <TState>(\n init: () => TState,\n): readonly [TState, StatewireHost.StateCtl<TState>] => {\n const [, setVersion] = useState(0);\n const [cell] = useState((): StateCell => {\n const commit = () => {\n if (c.draft) c.current = c.draft.flush();\n return c.current;\n };\n const c: StateCell = {\n current: sanitizeValue(init()),\n draft: null,\n changed: [[]],\n documents: new Map(),\n onOp: null,\n onChange: null,\n onDocuments: null,\n bump: () => setVersion((v) => v + 1),\n ctl: {\n apply: (ops) => {\n const result = applyOps(commit(), ops);\n c.current = result.state;\n c.draft?.invalidate();\n c.changed.push(...result.changedPaths);\n if (c.onOp) for (const op of ops) c.onOp(op);\n c.bump();\n c.onChange?.();\n },\n snapshot: commit,\n },\n host: {\n snapshot: commit,\n setOnOp: (listener) => {\n c.onOp = listener;\n },\n setOnChange: (listener) => {\n c.onChange = listener;\n },\n takeChanged: () => {\n const changed = c.changed;\n c.changed = [];\n return changed;\n },\n mount: (protocol, value, options) => {\n const key = options?.key;\n const main = options?.main ?? key === undefined;\n if (main) {\n for (const [id, doc] of c.documents) {\n if (\n doc.protocol === protocol &&\n doc.main &&\n id !== documentIdentity(protocol, key)\n )\n throw new Error(\n `statewire: ${protocol} already has a main document`,\n );\n }\n }\n c.documents.set(documentIdentity(protocol, key), {\n protocol,\n key: key === undefined ? null : [...key],\n main,\n value: sanitizeValue(value),\n });\n c.onDocuments?.();\n },\n unmount: (protocol, key) => {\n if (!c.documents.delete(documentIdentity(protocol, key)))\n throw new Error(\"statewire: document is not mounted\");\n c.onDocuments?.();\n },\n documents: () => c.documents,\n setOnDocuments: (listener) => {\n c.onDocuments = listener;\n },\n },\n };\n return c;\n });\n\n let value = cell.ctl.snapshot();\n if (typeof value === \"object\" && value !== null) {\n cell.draft ??= createStateDraft<object>({\n snapshot: () => cell.current,\n record: (op, changedPath) => {\n cell.changed.push(changedPath);\n cell.onOp?.(op);\n cell.bump();\n cell.onChange?.();\n },\n });\n stateHosts.set(cell.draft.proxy, cell.host);\n value = cell.draft.proxy;\n }\n return [value as TState, cell.ctl as StatewireHost.StateCtl<TState>] as const;\n};\n\n// ─── commands ───────────────────────────────────────────\n\ntype Settled =\n | { type: \"resolved\"; value: unknown }\n | { type: \"rejected\"; error: unknown };\n\nexport type CommandsInvocation = {\n readonly protocol?: string;\n readonly method: string;\n readonly params: readonly unknown[];\n readonly ts: number;\n readonly seq: number;\n readonly clientId?: string;\n};\n\nexport type CommandsOutcome =\n | { type: \"accepted\"; payload?: unknown }\n | { type: \"rejected\"; error: unknown };\n\nexport type CommandsListener = {\n onStart: (inv: CommandsInvocation) => void;\n onPosted?: (inv: CommandsInvocation) => void;\n onApplied: (inv: CommandsInvocation) => void;\n /** Canonical state now covers the invocation's effect — drop any overlay. */\n onCovered?: (inv: CommandsInvocation) => void;\n onSettle: (inv: CommandsInvocation, outcome: CommandsOutcome) => void;\n};\n\nexport type CommandsSubmission = {\n method: string;\n params: readonly unknown[];\n clientId?: string;\n caller?: StatewireHost.Caller;\n attach?: StatewireHost.Attach;\n onApplied?: () => void;\n onSettle?: (outcome: CommandsOutcome) => void;\n};\n\nexport type CommandsHost = {\n setListener: (listener: CommandsListener | null) => void;\n submit?: (call: CommandsSubmission) => Promise<unknown>;\n /** Registered command names, read live. */\n methods?: () => string[];\n abort?: () => void;\n};\n\nconst commandsHosts = new WeakMap<object, CommandsHost>();\n\nexport const getCommandsHost = (commands: object): CommandsHost | undefined =>\n commandsHosts.get(commands);\n\nexport const bindCommandsHost = (commands: object, host: CommandsHost) => {\n commandsHosts.set(commands, host);\n};\n\ntype QueuedCall = {\n method: string;\n params: readonly unknown[];\n ts: number;\n resolve: (value: unknown) => void;\n reject: (error: unknown) => void;\n origin?: CommandsSubmission;\n};\n\ntype ActiveInvocation = CommandsInvocation & {\n applied: boolean;\n controller: AbortController;\n settled: boolean;\n origin?: CommandsSubmission;\n};\n\ntype CommandsCell = {\n handlers: Record<string, (...args: never[]) => unknown> | null;\n queue: QueuedCall[];\n active: Map<ActiveInvocation, QueuedCall>;\n running: boolean;\n listener: CommandsListener | null;\n seq: number;\n unmounted: boolean;\n stallTimer: ReturnType<typeof setTimeout> | null;\n};\n\nconst STALL_WARN_MS = 5000;\n\n// In-process dispatch has no wire client: trivially attached.\nconst LOCAL_CALLER: StatewireHost.Caller = Object.freeze({\n get clientId(): string {\n throw new Error(\"statewire: in-process dispatch has no client id\");\n },\n auth: undefined,\n isConnected: true,\n});\n\nconst toSendError = (error: unknown): StatewireSendError =>\n error instanceof StatewireSendError\n ? error\n : error instanceof StatewireReject\n ? new StatewireSendError({\n fate: \"rejected\",\n cause: \"server\",\n message: error.message,\n ...(error.code !== undefined && { code: error.code }),\n ...(error.payload !== undefined && { payload: error.payload }),\n })\n : new StatewireSendError({\n fate: \"failed\",\n cause: \"server\",\n message: error instanceof Error ? error.message : String(error),\n });\n\nconst applyInvocation = (cell: CommandsCell, inv: ActiveInvocation) => {\n if (inv.applied) return;\n inv.applied = true;\n cell.listener?.onApplied(inv);\n cell.listener?.onCovered?.(inv);\n inv.origin?.onApplied?.();\n};\n\nconst settleInvocation = (\n cell: CommandsCell,\n inv: ActiveInvocation,\n call: QueuedCall,\n settled: Settled,\n) => {\n if (inv.settled) return;\n inv.settled = true;\n cell.active.delete(inv);\n if (settled.type === \"resolved\") {\n applyInvocation(cell, inv);\n const outcome: CommandsOutcome =\n settled.value === undefined\n ? { type: \"accepted\" }\n : { type: \"accepted\", payload: sanitizeValue(settled.value) };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.resolve(outcome.payload);\n } else {\n cell.listener?.onCovered?.(inv);\n const error = toSendError(settled.error);\n const outcome: CommandsOutcome = { type: \"rejected\", error };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.reject(error);\n }\n};\n\nconst abortInvocations = (cell: CommandsCell) => {\n for (const [inv, call] of cell.active) {\n inv.settled = true;\n inv.controller.abort();\n const error = new StatewireSendError({ fate: \"crashed\", cause: \"server\" });\n const outcome: CommandsOutcome = { type: \"rejected\", error };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.reject(error);\n }\n cell.active.clear();\n};\n\nconst isThenable = (value: unknown): value is PromiseLike<unknown> =>\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { then?: unknown }).then === \"function\";\n\n// A command is an own, enumerable, function-valued property — exactly what\n// `Object.keys` shows, so dispatch, `statewire/commands`, and the reserved-\n// namespace guard share one view; prototype-chain names are not commands.\nconst handlerOf = (\n handlers: Record<string, (...args: never[]) => unknown>,\n method: string,\n): ((...args: never[]) => unknown) | undefined => {\n if (!Object.prototype.propertyIsEnumerable.call(handlers, method)) {\n return undefined;\n }\n const handler = handlers[method];\n return typeof handler === \"function\" ? handler : undefined;\n};\n\nconst dispatch = (cell: CommandsCell, call: QueuedCall) => {\n const handler = handlerOf(cell.handlers!, call.method);\n if (handler === undefined) {\n const error = new StatewireSendError({\n fate: \"rejected\",\n cause: \"server\",\n code: \"unknown-command\",\n });\n call.origin?.onSettle?.({ type: \"rejected\", error });\n call.reject(error);\n return;\n }\n const inv: ActiveInvocation = {\n method: call.method,\n params: call.params,\n ts: call.ts,\n seq: ++cell.seq,\n applied: false,\n controller: new AbortController(),\n settled: false,\n ...(call.origin !== undefined && { origin: call.origin }),\n ...(call.origin?.clientId !== undefined && {\n clientId: call.origin.clientId,\n }),\n };\n cell.active.set(inv, call);\n cell.listener?.onStart(inv);\n cell.listener?.onPosted?.(inv);\n const attach = call.origin?.attach;\n const ctx: StatewireHost.Ctx = {\n caller: call.origin?.caller ?? LOCAL_CALLER,\n get attach() {\n if (attach === undefined)\n throw new Error(\"statewire: in-process dispatch has no attach\");\n return attach;\n },\n signal: inv.controller.signal,\n applied: () => {\n if (!inv.settled) applyInvocation(cell, inv);\n },\n };\n try {\n const value = (\n handler as (\n this: StatewireHost.Ctx,\n ...args: readonly unknown[]\n ) => unknown\n ).call(ctx, ...call.params);\n if (isThenable(value)) {\n value.then(\n (resolved) =>\n settleInvocation(cell, inv, call, {\n type: \"resolved\",\n value: resolved,\n }),\n (error: unknown) =>\n settleInvocation(cell, inv, call, { type: \"rejected\", error }),\n );\n } else {\n settleInvocation(cell, inv, call, { type: \"resolved\", value });\n }\n } catch (error) {\n settleInvocation(cell, inv, call, { type: \"rejected\", error });\n }\n};\n\n// Prefix-only advancement: each handler runs synchronously to its first\n// await, then the pump moves on; settlement rides the returned promise.\nconst pump = (cell: CommandsCell) => {\n if (cell.running || !cell.handlers) {\n watchStall(cell);\n return;\n }\n cell.running = true;\n try {\n while (cell.handlers) {\n const call = cell.queue.shift();\n if (!call) break;\n dispatch(cell, call);\n }\n } finally {\n cell.running = false;\n }\n};\n\nconst watchStall = (cell: CommandsCell) => {\n if (!isDev() || cell.stallTimer !== null || cell.queue.length === 0) return;\n if (cell.handlers) return;\n cell.stallTimer = setTimeout(() => {\n cell.stallTimer = null;\n if (cell.handlers === null && cell.queue.length > 0 && !cell.unmounted) {\n console.warn(\n `statewire: ${cell.queue.length} buffered command(s) waiting over ${STALL_WARN_MS}ms — did the host forget to arm useStatewireCommands?`,\n );\n }\n }, STALL_WARN_MS);\n};\n\nexport const useStatewireCommands = <THandlers extends Statewire.Commands>(\n commands:\n | (THandlers & ThisType<StatewireHost.Ctx>)\n | undefined\n | null\n | false,\n): Statewire.CommandsProxy<StatewireHost.HandledCommands<THandlers>> => {\n const [built] = useState(() => {\n const cell: CommandsCell = {\n handlers: null,\n queue: [],\n active: new Map(),\n running: false,\n listener: null,\n seq: 0,\n unmounted: false,\n stallTimer: null,\n };\n const proxy = createMethodProxy<\n Statewire.CommandsProxy<StatewireHost.HandledCommands<THandlers>>\n >(\n (method, params) =>\n new Promise((resolve, reject) => {\n if (cell.unmounted) {\n reject(new Error(\"statewire: commands called after unmount\"));\n return;\n }\n cell.queue.push({\n method,\n params,\n ts: Date.now(),\n resolve,\n reject,\n });\n pump(cell);\n }),\n );\n commandsHosts.set(proxy, {\n abort: () => abortInvocations(cell),\n setListener: (listener) => {\n cell.listener = listener;\n },\n methods: () => {\n const handlers = cell.handlers;\n if (!handlers) return [];\n return Object.keys(handlers).filter(\n (method) => handlerOf(handlers, method) !== undefined,\n );\n },\n submit: (origin) =>\n new Promise((resolve, reject) => {\n if (cell.unmounted) {\n reject(new Error(\"statewire: commands called after unmount\"));\n return;\n }\n cell.queue.push({\n method: origin.method,\n params: origin.params,\n ts: Date.now(),\n resolve,\n reject,\n origin,\n });\n pump(cell);\n }),\n });\n return { cell, proxy };\n });\n\n useEffect(() => {\n const { cell } = built;\n cell.unmounted = false;\n return () => {\n cell.unmounted = true;\n cell.handlers = null;\n if (cell.stallTimer !== null) {\n clearTimeout(cell.stallTimer);\n cell.stallTimer = null;\n }\n queueMicrotask(() => {\n if (!cell.unmounted) return;\n abortInvocations(cell);\n const queued = cell.queue;\n cell.queue = [];\n for (const call of queued) {\n call.reject(new Error(\"statewire: unmounted\"));\n }\n });\n };\n }, [built]);\n\n useEffect(() => {\n if (commands) {\n for (const method of Object.keys(commands)) {\n if (method.startsWith(\"statewire/\")) {\n throw new Error(\n `statewire: cannot register \"${method}\": the \"statewire/\" method namespace is reserved`,\n );\n }\n }\n }\n built.cell.handlers = commands || null;\n pump(built.cell);\n });\n\n return built.proxy;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"host.js","names":["captureContext","useEffect","useState","applyOps","sanitizeValue","StatewirePath","StatewireReject","createStateDraft","createMethodProxy","StateDraft","StatewireSendError","Statewire","StatewireHost","isDev","process","env","stateHosts","WeakMap","StateHost","documentIdentity","protocol","key","JSON","stringify","getStateHost","state","get","StateCell","current","draft","changed","documents","Map","Document","onOp","op","Op","onChange","onDocuments","ctl","StateCtl","host","bump","useStatewireState","init","TState","setVersion","cell","commit","c","flush","v","apply","ops","result","invalidate","push","changedPaths","snapshot","setOnOp","listener","setOnChange","takeChanged","mount","value","options","main","undefined","id","doc","Error","set","unmount","delete","setOnDocuments","record","changedPath","proxy","const","Settled","type","error","CommandsInvocation","method","params","ts","seq","clientId","CommandsOutcome","payload","CommandsListener","onStart","inv","onPosted","onApplied","onCovered","onSettle","outcome","CommandsSubmission","caller","Caller","context","Context","attach","Attach","CommandsHost","setListener","submit","call","Promise","methods","abort","commandsHosts","getCommandsHost","commands","bindCommandsHost","QueuedCall","resolve","reject","origin","ActiveInvocation","applied","controller","AbortController","settled","CommandsCell","handlers","Record","args","queue","active","running","unmounted","stallTimer","ReturnType","setTimeout","STALL_WARN_MS","LOCAL_CALLER","Object","freeze","auth","isConnected","toSendError","fate","cause","message","code","String","applyInvocation","settleInvocation","abortInvocations","clear","isThenable","PromiseLike","then","handlerOf","prototype","propertyIsEnumerable","handler","dispatch","ctx","Ctx","signal","this","resolved","pump","watchStall","shift","length","console","warn","useStatewireCommands","Commands","THandlers","ThisType","CommandsOptions","CommandsProxy","HandledCommands","built","Date","now","keys","filter","clearTimeout","queueMicrotask","queued","startsWith"],"sources":["../../src/host/host.ts"],"sourcesContent":["import { captureContext } from \"../wire/context\";\nimport { useEffect, useState } from \"react\";\nimport { applyOps, sanitizeValue } from \"../wire/ops\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport { StatewireReject } from \"./engine\";\nimport { createStateDraft } from \"./draft\";\nimport { createMethodProxy } from \"../util/methodProxy\";\nimport type { StateDraft } from \"./draft\";\nimport { StatewireSendError } from \"../types\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireHost } from \"../host\";\n\nconst isDev = () =>\n typeof process === \"undefined\" || process.env[\"NODE_ENV\"] !== \"production\";\n\n// ─── state ──────────────────────────────────────────────\n\nconst stateHosts = new WeakMap<object, StatewireHost.StateHost>();\n\nexport const documentIdentity = (protocol: string, key?: readonly string[]) =>\n JSON.stringify([protocol, key ?? null]);\n\n/** Host-side accessor for a `useStatewireState` state object's source (`snapshot()` reads the committed state). */\nexport const getStateHost = (\n state: object,\n): StatewireHost.StateHost | undefined => stateHosts.get(state);\n\ntype StateCell = {\n current: unknown;\n draft: StateDraft<object> | null;\n changed: StatewirePath[];\n documents: Map<string, StatewireHost.Document>;\n onOp: ((op: Statewire.Op) => void) | null;\n onChange: (() => void) | null;\n onDocuments: (() => void) | null;\n ctl: StatewireHost.StateCtl;\n host: StatewireHost.StateHost;\n bump: () => void;\n};\n\nexport const useStatewireState = <TState>(\n init: () => TState,\n): readonly [TState, StatewireHost.StateCtl<TState>] => {\n const [, setVersion] = useState(0);\n const [cell] = useState((): StateCell => {\n const commit = () => {\n if (c.draft) c.current = c.draft.flush();\n return c.current;\n };\n const c: StateCell = {\n current: sanitizeValue(init()),\n draft: null,\n changed: [[]],\n documents: new Map(),\n onOp: null,\n onChange: null,\n onDocuments: null,\n bump: () => setVersion((v) => v + 1),\n ctl: {\n apply: (ops) => {\n const result = applyOps(commit(), ops);\n c.current = result.state;\n c.draft?.invalidate();\n c.changed.push(...result.changedPaths);\n if (c.onOp) for (const op of ops) c.onOp(op);\n c.bump();\n c.onChange?.();\n },\n snapshot: commit,\n },\n host: {\n snapshot: commit,\n setOnOp: (listener) => {\n c.onOp = listener;\n },\n setOnChange: (listener) => {\n c.onChange = listener;\n },\n takeChanged: () => {\n const changed = c.changed;\n c.changed = [];\n return changed;\n },\n mount: (protocol, value, options) => {\n const key = options?.key;\n const main = options?.main ?? key === undefined;\n if (main) {\n for (const [id, doc] of c.documents) {\n if (\n doc.protocol === protocol &&\n doc.main &&\n id !== documentIdentity(protocol, key)\n )\n throw new Error(\n `statewire: ${protocol} already has a main document`,\n );\n }\n }\n c.documents.set(documentIdentity(protocol, key), {\n protocol,\n key: key === undefined ? null : [...key],\n main,\n value: sanitizeValue(value),\n });\n c.onDocuments?.();\n },\n unmount: (protocol, key) => {\n if (!c.documents.delete(documentIdentity(protocol, key)))\n throw new Error(\"statewire: document is not mounted\");\n c.onDocuments?.();\n },\n documents: () => c.documents,\n setOnDocuments: (listener) => {\n c.onDocuments = listener;\n },\n },\n };\n return c;\n });\n\n let value = cell.ctl.snapshot();\n if (typeof value === \"object\" && value !== null) {\n cell.draft ??= createStateDraft<object>({\n snapshot: () => cell.current,\n record: (op, changedPath) => {\n cell.changed.push(changedPath);\n cell.onOp?.(op);\n cell.bump();\n cell.onChange?.();\n },\n });\n stateHosts.set(cell.draft.proxy, cell.host);\n value = cell.draft.proxy;\n }\n return [value as TState, cell.ctl as StatewireHost.StateCtl<TState>] as const;\n};\n\n// ─── commands ───────────────────────────────────────────\n\ntype Settled =\n | { type: \"resolved\"; value: unknown }\n | { type: \"rejected\"; error: unknown };\n\nexport type CommandsInvocation = {\n readonly protocol?: string;\n readonly method: string;\n readonly params: readonly unknown[];\n readonly ts: number;\n readonly seq: number;\n readonly clientId?: string;\n};\n\nexport type CommandsOutcome =\n | { type: \"accepted\"; payload?: unknown }\n | { type: \"rejected\"; error: unknown };\n\nexport type CommandsListener = {\n onStart: (inv: CommandsInvocation) => void;\n onPosted?: (inv: CommandsInvocation) => void;\n onApplied: (inv: CommandsInvocation) => void;\n /** Canonical state now covers the invocation's effect — drop any overlay. */\n onCovered?: (inv: CommandsInvocation) => void;\n onSettle: (inv: CommandsInvocation, outcome: CommandsOutcome) => void;\n};\n\nexport type CommandsSubmission = {\n method: string;\n params: readonly unknown[];\n clientId?: string;\n caller?: StatewireHost.Caller;\n context?: Statewire.Context;\n attach?: StatewireHost.Attach;\n onApplied?: () => void;\n onSettle?: (outcome: CommandsOutcome) => void;\n};\n\nexport type CommandsHost = {\n setListener: (listener: CommandsListener | null) => void;\n submit?: (call: CommandsSubmission) => Promise<unknown>;\n /** Registered command names, read live. */\n methods?: () => string[];\n abort?: () => void;\n};\n\nconst commandsHosts = new WeakMap<object, CommandsHost>();\n\nexport const getCommandsHost = (commands: object): CommandsHost | undefined =>\n commandsHosts.get(commands);\n\nexport const bindCommandsHost = (commands: object, host: CommandsHost) => {\n commandsHosts.set(commands, host);\n};\n\ntype QueuedCall = {\n context?: Statewire.Context;\n method: string;\n params: readonly unknown[];\n ts: number;\n resolve: (value: unknown) => void;\n reject: (error: unknown) => void;\n origin?: CommandsSubmission;\n};\n\ntype ActiveInvocation = CommandsInvocation & {\n applied: boolean;\n controller: AbortController;\n settled: boolean;\n origin?: CommandsSubmission;\n};\n\ntype CommandsCell = {\n context: (() => Statewire.Context) | undefined;\n handlers: Record<string, (...args: never[]) => unknown> | null;\n queue: QueuedCall[];\n active: Map<ActiveInvocation, QueuedCall>;\n running: boolean;\n listener: CommandsListener | null;\n seq: number;\n unmounted: boolean;\n stallTimer: ReturnType<typeof setTimeout> | null;\n};\n\nconst STALL_WARN_MS = 5000;\n\n// In-process dispatch has no wire client: trivially attached.\nconst LOCAL_CALLER: StatewireHost.Caller = Object.freeze({\n get clientId(): string {\n throw new Error(\"statewire: in-process dispatch has no client id\");\n },\n auth: undefined,\n isConnected: true,\n});\n\nconst toSendError = (error: unknown): StatewireSendError =>\n error instanceof StatewireSendError\n ? error\n : error instanceof StatewireReject\n ? new StatewireSendError({\n fate: \"rejected\",\n cause: \"server\",\n message: error.message,\n ...(error.code !== undefined && { code: error.code }),\n ...(error.payload !== undefined && { payload: error.payload }),\n })\n : new StatewireSendError({\n fate: \"failed\",\n cause: \"server\",\n message: error instanceof Error ? error.message : String(error),\n });\n\nconst applyInvocation = (cell: CommandsCell, inv: ActiveInvocation) => {\n if (inv.applied) return;\n inv.applied = true;\n cell.listener?.onApplied(inv);\n cell.listener?.onCovered?.(inv);\n inv.origin?.onApplied?.();\n};\n\nconst settleInvocation = (\n cell: CommandsCell,\n inv: ActiveInvocation,\n call: QueuedCall,\n settled: Settled,\n) => {\n if (inv.settled) return;\n inv.settled = true;\n cell.active.delete(inv);\n if (settled.type === \"resolved\") {\n applyInvocation(cell, inv);\n const outcome: CommandsOutcome =\n settled.value === undefined\n ? { type: \"accepted\" }\n : { type: \"accepted\", payload: sanitizeValue(settled.value) };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.resolve(outcome.payload);\n } else {\n cell.listener?.onCovered?.(inv);\n const error = toSendError(settled.error);\n const outcome: CommandsOutcome = { type: \"rejected\", error };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.reject(error);\n }\n};\n\nconst abortInvocations = (cell: CommandsCell) => {\n for (const [inv, call] of cell.active) {\n inv.settled = true;\n inv.controller.abort();\n const error = new StatewireSendError({ fate: \"crashed\", cause: \"server\" });\n const outcome: CommandsOutcome = { type: \"rejected\", error };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.reject(error);\n }\n cell.active.clear();\n};\n\nconst isThenable = (value: unknown): value is PromiseLike<unknown> =>\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { then?: unknown }).then === \"function\";\n\n// A command is an own, enumerable, function-valued property — exactly what\n// `Object.keys` shows, so dispatch, `statewire/commands`, and the reserved-\n// namespace guard share one view; prototype-chain names are not commands.\nconst handlerOf = (\n handlers: Record<string, (...args: never[]) => unknown>,\n method: string,\n): ((...args: never[]) => unknown) | undefined => {\n if (!Object.prototype.propertyIsEnumerable.call(handlers, method)) {\n return undefined;\n }\n const handler = handlers[method];\n return typeof handler === \"function\" ? handler : undefined;\n};\n\nconst dispatch = (cell: CommandsCell, call: QueuedCall) => {\n const handler = handlerOf(cell.handlers!, call.method);\n if (handler === undefined) {\n const error = new StatewireSendError({\n fate: \"rejected\",\n cause: \"server\",\n code: \"unknown-command\",\n });\n call.origin?.onSettle?.({ type: \"rejected\", error });\n call.reject(error);\n return;\n }\n const inv: ActiveInvocation = {\n method: call.method,\n params: call.params,\n ts: call.ts,\n seq: ++cell.seq,\n applied: false,\n controller: new AbortController(),\n settled: false,\n ...(call.origin !== undefined && { origin: call.origin }),\n ...(call.origin?.clientId !== undefined && {\n clientId: call.origin.clientId,\n }),\n };\n cell.active.set(inv, call);\n cell.listener?.onStart(inv);\n cell.listener?.onPosted?.(inv);\n const attach = call.origin?.attach;\n const ctx: StatewireHost.Ctx = {\n caller: call.origin?.caller ?? LOCAL_CALLER,\n context: call.context ?? call.origin?.context ?? Object.freeze({}),\n get attach() {\n if (attach === undefined)\n throw new Error(\"statewire: in-process dispatch has no attach\");\n return attach;\n },\n signal: inv.controller.signal,\n applied: () => {\n if (!inv.settled) applyInvocation(cell, inv);\n },\n };\n try {\n const value = (\n handler as (\n this: StatewireHost.Ctx,\n ...args: readonly unknown[]\n ) => unknown\n ).call(ctx, ...call.params);\n if (isThenable(value)) {\n value.then(\n (resolved) =>\n settleInvocation(cell, inv, call, {\n type: \"resolved\",\n value: resolved,\n }),\n (error: unknown) =>\n settleInvocation(cell, inv, call, { type: \"rejected\", error }),\n );\n } else {\n settleInvocation(cell, inv, call, { type: \"resolved\", value });\n }\n } catch (error) {\n settleInvocation(cell, inv, call, { type: \"rejected\", error });\n }\n};\n\n// Prefix-only advancement: each handler runs synchronously to its first\n// await, then the pump moves on; settlement rides the returned promise.\nconst pump = (cell: CommandsCell) => {\n if (cell.running || !cell.handlers) {\n watchStall(cell);\n return;\n }\n cell.running = true;\n try {\n while (cell.handlers) {\n const call = cell.queue.shift();\n if (!call) break;\n dispatch(cell, call);\n }\n } finally {\n cell.running = false;\n }\n};\n\nconst watchStall = (cell: CommandsCell) => {\n if (!isDev() || cell.stallTimer !== null || cell.queue.length === 0) return;\n if (cell.handlers) return;\n cell.stallTimer = setTimeout(() => {\n cell.stallTimer = null;\n if (cell.handlers === null && cell.queue.length > 0 && !cell.unmounted) {\n console.warn(\n `statewire: ${cell.queue.length} buffered command(s) waiting over ${STALL_WARN_MS}ms — did the host forget to arm useStatewireCommands?`,\n );\n }\n }, STALL_WARN_MS);\n};\n\nexport const useStatewireCommands = <THandlers extends Statewire.Commands>(\n commands:\n | (THandlers & ThisType<StatewireHost.Ctx>)\n | undefined\n | null\n | false,\n options?: StatewireHost.CommandsOptions,\n): Statewire.CommandsProxy<StatewireHost.HandledCommands<THandlers>> => {\n const [built] = useState(() => {\n const cell: CommandsCell = {\n context: undefined,\n handlers: null,\n queue: [],\n active: new Map(),\n running: false,\n listener: null,\n seq: 0,\n unmounted: false,\n stallTimer: null,\n };\n const proxy = createMethodProxy<\n Statewire.CommandsProxy<StatewireHost.HandledCommands<THandlers>>\n >(\n (method, params) =>\n new Promise((resolve, reject) => {\n if (cell.unmounted) {\n reject(new Error(\"statewire: commands called after unmount\"));\n return;\n }\n cell.queue.push({\n context: captureContext(cell.context?.() ?? {}),\n method,\n params,\n ts: Date.now(),\n resolve,\n reject,\n });\n pump(cell);\n }),\n );\n commandsHosts.set(proxy, {\n abort: () => abortInvocations(cell),\n setListener: (listener) => {\n cell.listener = listener;\n },\n methods: () => {\n const handlers = cell.handlers;\n if (!handlers) return [];\n return Object.keys(handlers).filter(\n (method) => handlerOf(handlers, method) !== undefined,\n );\n },\n submit: (origin) =>\n new Promise((resolve, reject) => {\n if (cell.unmounted) {\n reject(new Error(\"statewire: commands called after unmount\"));\n return;\n }\n cell.queue.push({\n method: origin.method,\n params: origin.params,\n ts: Date.now(),\n resolve,\n reject,\n origin,\n });\n pump(cell);\n }),\n });\n return { cell, proxy };\n });\n\n built.cell.context = options?.context;\n useEffect(() => {\n const { cell } = built;\n cell.unmounted = false;\n return () => {\n cell.unmounted = true;\n cell.handlers = null;\n if (cell.stallTimer !== null) {\n clearTimeout(cell.stallTimer);\n cell.stallTimer = null;\n }\n queueMicrotask(() => {\n if (!cell.unmounted) return;\n abortInvocations(cell);\n const queued = cell.queue;\n cell.queue = [];\n for (const call of queued) {\n call.reject(new Error(\"statewire: unmounted\"));\n }\n });\n };\n }, [built]);\n\n useEffect(() => {\n if (commands) {\n for (const method of Object.keys(commands)) {\n if (method.startsWith(\"statewire/\")) {\n throw new Error(\n `statewire: cannot register \"${method}\": the \"statewire/\" method namespace is reserved`,\n );\n }\n }\n }\n built.cell.handlers = commands || null;\n pump(built.cell);\n });\n\n return built.proxy;\n};\n"],"mappings":"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"}
package/dist/host/http.js CHANGED
@@ -1,5 +1,5 @@
1
- import { negotiateProtocols } from "../wire/protocol.js";
2
1
  import { MAX_FRAME_BODY_BYTES } from "../wire/limits.js";
2
+ import { negotiateProtocols } from "../wire/protocol.js";
3
3
  import { CLIENT_ID_RE, PRINCIPAL_MISMATCH_MESSAGE, SUBSCRIBER_LIMIT_MESSAGE, admitFrame, atSubscriberLimit, authEquals, closeConn, flush, parseFrame, parseFrameValue, register, sendTo } from "./socket.js";
4
4
  //#region src/host/http.ts
5
5
  const CLIENT_ID_HEADER = "Statewire-Client-Id";
@@ -1,9 +1,9 @@
1
1
  import { CommandsHost } from "./host.js";
2
2
  import { StatewireHost } from "../host.js";
3
3
  import { StatewireProtocol } from "../wire/protocol.js";
4
+ import { StatewireDocument } from "../document.js";
4
5
  import { Statewire } from "../types.js";
5
6
  import { StatewirePath } from "../wire/ops.js";
6
- import { StatewireDocument } from "../document.js";
7
7
  import { StatewireHostInternal } from "../host-internal.js";
8
8
  import { ResourceElement } from "@assistant-ui/tap";
9
9
  //#region src/host/socket.d.ts
@@ -63,6 +63,7 @@ type Conn = {
63
63
  /** The awaited `authorize` return captured at this attach's authorization. */
64
64
  auth: unknown;
65
65
  lanes: Map<string, Lane>;
66
+ contexts: Map<string, Statewire.Context>;
66
67
  documents: Map<string, StatewireHost.Document>;
67
68
  included: Set<string>;
68
69
  view: Map<string, {
@@ -91,6 +92,10 @@ type CmdStatement = {
91
92
  };
92
93
  type ParsedFrame = {
93
94
  cmd: CmdStatement[];
95
+ ctx?: {
96
+ protocol: string;
97
+ value: Statewire.Context;
98
+ }[];
94
99
  };
95
100
  declare const parseFrameValue: (frame: unknown) => ParsedFrame;
96
101
  declare const parseFrame: (raw: string) => ParsedFrame;
@@ -1 +1 @@
1
- {"version":3,"file":"socket.d.ts","names":[],"sources":["../../src/host/socket.ts"],"mappings":";;;;;;;;;cA+Ba,cAAY;cACZ;;;cAKA,aAAc,YAAY;cAoC1B;KAGR,MAAM,sBAAsB;KAE5B;EACH;EACA;EACA,OAAO;EACP;EACA,OAAO,kBAAkB;EACzB;EACA;EACA;;KAGG;EACH;EACA;EACA;EACA;EACA;EACA;;;KAIG;EACH;EACA;EACA,MAAM;EACN;EACA,SAAS;IAAc;IAAY,KAAK;;EACxC,UAAU,YAAY;;KAGZ;EACV;EACA;EACA,OAAO,YAAY;;EAEnB;;KAIU;EACV,WAAW,kBAAkB;EAC7B;EACA,QAAQ,sBAAsB;EAC9B;EACA;EACA;EACA;EACA;;EAEA;EACA,OAAO,YAAY;EACnB,WAAW,YAAY,cAAc;EACrC,UAAU;EACV,MAAM;IAAc;IAAa;;EACjC;EACA,gBAAgB;EAChB,aAAa;EACb,QAAQ,cAAc;;KAGnB;EACH,QAAQ,sBAAsB;EAC9B;EACA;EACA;EACA,OAAO,kBAAkB;EACzB,YAAY,sBAAsB;;KAc/B;EACH;EACA;EACA;EACA;EACA;;KAGU;EAAgB,KAAK;;cAOpB,kBAAmB,mBAAiB;cA4CpC,aAAc,gBAAc;KAyF7B;EACV,UAAU,kBAAkB;EAC5B;EACA,aAAa,kBAAkB;EAC/B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,WAAW,sBAAsB;EACjC,SAAS,YAAY;EACrB,OAAO,IAAI;EACX,cAAc,IAAI;EAClB,YAAY;EACZ,gBAAgB,YAAY;EAC5B,aAAa,YAAY;EACzB;EACA;EACA,UAAU,cAAc;EACxB;EACA;EACA,gBAAgB,kBAAkB;EAClC,WAAW,cAAc;EACzB,cAAc;EACd,SAAS;EACT,aAAa,sBAAsB;EACnC;EACA,UACE,kBACA,QAAQ,sBAAsB,QAC9B,UAAU,sBAAsB,yBAC7B,sBAAsB;EAC3B,SAAS,KAAK,cAAc;EAC5B,SAAS,SAAS,YAAY,QAAQ;EACtC,SAAS,SAAS,YAAY,QAAQ;;cAsD3B,YACX,GAAG,iBACH,MAAM,MACN,eACA;cAiBW,SAAU,GAAG,iBAAiB,MAAM,MAAM;cAmB1C,oBAAqB,GAAG;cA+KxB,QAAS,GAAG,iBAAiB,MAAM,cAAc;KAmUlD;EACN;;EAEA;EACA;IACE;MAAO;QAAa;QAAc;;MAAiB;;;;cAqB9C,aACX,GAAG,iBACH,MAAM,MACN,OAAO,gBACN;KA4YS;EACV,MAAM;EACN,YAAY,sBAAsB;;;;;;;cAQvB,WACX,GAAG,iBACH,kBACA,QAAQ,sBAAsB,QAC9B,oBACA,gBACA,YAAW,kBAAkB,WAI7B,QAAO,gBACN;;;;;;;;;;cAqUU,sBACX,WAAW,gBAAgB,YAC3B,UAAU,sBAAsB,mBAChC,cAAc,sBAAsB,0BACnC,sBAAsB"}
1
+ {"version":3,"file":"socket.d.ts","names":[],"sources":["../../src/host/socket.ts"],"mappings":";;;;;;;;;cAiCa,cAAY;cACZ;;;cAKA,aAAc,YAAY;cAoC1B;KAGR,MAAM,sBAAsB;KAE5B;EACH;EACA;EACA,OAAO;EACP;EACA,OAAO,kBAAkB;EACzB;EACA;EACA;;KAGG;EACH;EACA;EACA;EACA;EACA;EACA;;;KAIG;EACH;EACA;EACA,MAAM;EACN;EACA,SAAS;IAAc;IAAY,KAAK;;EACxC,UAAU,YAAY;;KAGZ;EACV;EACA;EACA,OAAO,YAAY;;EAEnB;;KAIU;EACV,WAAW,kBAAkB;EAC7B;EACA,QAAQ,sBAAsB;EAC9B;EACA;EACA;EACA;EACA;;EAEA;EACA,OAAO,YAAY;EACnB,UAAU,YAAY,UAAU;EAChC,WAAW,YAAY,cAAc;EACrC,UAAU;EACV,MAAM;IAAc;IAAa;;EACjC;EACA,gBAAgB;EAChB,aAAa;EACb,QAAQ,cAAc;;KAGnB;EACH,QAAQ,sBAAsB;EAC9B;EACA;EACA;EACA,OAAO,kBAAkB;EACzB,YAAY,sBAAsB;;KAc/B;EACH;EACA;EACA;EACA;EACA;;KAGU;EACV,KAAK;EACL;IAAQ;IAAkB,OAAO,UAAU;;;cAQhC,kBAAmB,mBAAiB;cAqFpC,aAAc,gBAAc;KAyF7B;EACV,UAAU,kBAAkB;EAC5B;EACA,aAAa,kBAAkB;EAC/B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,WAAW,sBAAsB;EACjC,SAAS,YAAY;EACrB,OAAO,IAAI;EACX,cAAc,IAAI;EAClB,YAAY;EACZ,gBAAgB,YAAY;EAC5B,aAAa,YAAY;EACzB;EACA;EACA,UAAU,cAAc;EACxB;EACA;EACA,gBAAgB,kBAAkB;EAClC,WAAW,cAAc;EACzB,cAAc;EACd,SAAS;EACT,aAAa,sBAAsB;EACnC;EACA,UACE,kBACA,QAAQ,sBAAsB,QAC9B,UAAU,sBAAsB,yBAC7B,sBAAsB;EAC3B,SAAS,KAAK,cAAc;EAC5B,SAAS,SAAS,YAAY,QAAQ;EACtC,SAAS,SAAS,YAAY,QAAQ;;cAsD3B,YACX,GAAG,iBACH,MAAM,MACN,eACA;cAiBW,SAAU,GAAG,iBAAiB,MAAM,MAAM;cAmB1C,oBAAqB,GAAG;cAsLxB,QAAS,GAAG,iBAAiB,MAAM,cAAc;KAoUlD;EACN;;EAEA;EACA;IACE;MAAO;QAAa;QAAc;;MAAiB;;;;cAyE9C,aACX,GAAG,iBACH,MAAM,MACN,OAAO,gBACN;KA8YS;EACV,MAAM;EACN,YAAY,sBAAsB;;;;;;;cAQvB,WACX,GAAG,iBACH,kBACA,QAAQ,sBAAsB,QAC9B,oBACA,gBACA,YAAW,kBAAkB,WAI7B,QAAO,gBACN;;;;;;;;;;cA0UU,sBACX,WAAW,gBAAgB,YAC3B,UAAU,sBAAsB,mBAChC,cAAc,sBAAsB,0BACnC,sBAAsB"}
@@ -1,9 +1,10 @@
1
1
  import { encodeDocument, reviveDocumentValue } from "../document.js";
2
+ import { COMMAND_CAPACITY_RETRY_MS, MAX_INFLIGHT_COMMANDS_PER_HOST } from "../wire/limits.js";
2
3
  import { DEFAULT_PROTOCOL, WIRE_VERSION, negotiateProtocols } from "../wire/protocol.js";
4
+ import { captureContext, contextValue } from "../wire/context.js";
3
5
  import { sanitizeValue } from "../wire/ops.js";
4
6
  import { StatewireSendError } from "../types.js";
5
7
  import { documentIdentity, getCommandsHost, getStateHost } from "./host.js";
6
- import { COMMAND_CAPACITY_RETRY_MS, MAX_INFLIGHT_COMMANDS_PER_HOST } from "../wire/limits.js";
7
8
  import { createMacrotaskPoster } from "../util/macrotask.js";
8
9
  import { frames, stream } from "./http.js";
9
10
  import { useEffect, useState } from "@assistant-ui/tap/react-shim";
@@ -40,11 +41,11 @@ const parseFrameValue = (frame) => {
40
41
  if (!isPlainObject(frame)) return violation("malformed frame");
41
42
  const keys = Object.keys(frame);
42
43
  if (keys.length === 0) return violation("malformed frame");
43
- const unknown = keys.filter((key) => key !== "cmd").sort();
44
+ const unknown = keys.filter((key) => key !== "cmd" && key !== "ctx").sort();
44
45
  if (unknown.length > 0) return violation(`unknown frame key: '${unknown[0]}'`);
45
- const lane = frame["cmd"];
46
+ const lane = frame["cmd"] ?? [];
46
47
  if (!Array.isArray(lane)) return violation("malformed frame");
47
- if (lane.length === 0) return violation("empty lane");
48
+ if (Object.hasOwn(frame, "cmd") && lane.length === 0) return violation("empty lane");
48
49
  if (lane.length > 256) return violation("too many commands");
49
50
  const prev = /* @__PURE__ */ new Map();
50
51
  const cmd = [];
@@ -65,7 +66,39 @@ const parseFrameValue = (frame) => {
65
66
  ...statement["meta"] !== void 0 && { meta: statement["meta"] }
66
67
  });
67
68
  }
68
- return { cmd };
69
+ const contexts = frame["ctx"];
70
+ const ctx = [];
71
+ if (contexts !== void 0) {
72
+ if (!Array.isArray(contexts) || contexts.length === 0) return violation("empty context lane");
73
+ const seen = /* @__PURE__ */ new Set();
74
+ for (const entry of contexts) {
75
+ if (!isPlainObject(entry) || typeof entry["protocol"] !== "string" || !Object.hasOwn(entry, "value") || Object.keys(entry).some((key) => ![
76
+ "protocol",
77
+ "value",
78
+ "meta"
79
+ ].includes(key))) return violation("malformed context");
80
+ const protocol = entry["protocol"];
81
+ if (seen.has(protocol)) return violation("duplicate context protocol");
82
+ seen.add(protocol);
83
+ if (frameEncoder.encode(JSON.stringify(entry)).byteLength > 65536) return violation("context entry too large");
84
+ try {
85
+ ctx.push({
86
+ protocol,
87
+ value: contextValue({
88
+ value: entry["value"],
89
+ ...entry["meta"] !== void 0 && { meta: entry["meta"] }
90
+ })
91
+ });
92
+ } catch (error) {
93
+ return violation(error instanceof Error ? error.message : "invalid context");
94
+ }
95
+ }
96
+ }
97
+ if (cmd.length === 0 && ctx.length === 0) return violation("empty frame");
98
+ return {
99
+ cmd,
100
+ ...ctx.length > 0 && { ctx }
101
+ };
69
102
  };
70
103
  const parseFrame = (raw) => {
71
104
  let frame;
@@ -293,6 +326,13 @@ const attachOf = (h, conn) => {
293
326
  return entry;
294
327
  };
295
328
  return {
329
+ get caller() {
330
+ return callerOf(h, h.clients.get(conn.clientId), conn);
331
+ },
332
+ context: (protocol) => {
333
+ requireSelected(protocol);
334
+ return captureContext(conn.contexts.get(protocol) ?? {});
335
+ },
296
336
  document: (protocol, key) => {
297
337
  if (key === void 0 && protocol === h.protocol.name) return h.stateHost.snapshot();
298
338
  const view = viewOf(h, conn);
@@ -537,6 +577,7 @@ const admitCmd = (h, lane, conn, statement) => {
537
577
  clientId: rec.id,
538
578
  caller: callerOf(h, rec, conn),
539
579
  attach: conn.attach,
580
+ context: captureContext(conn.contexts.get(statement.protocol) ?? {}),
540
581
  onApplied: () => {
541
582
  if (adm.settled || adm.applied) return;
542
583
  adm.applied = true;
@@ -562,7 +603,31 @@ const refusal = (h, conn, retry) => ({
562
603
  ...retry !== void 0 && { retry }
563
604
  } }
564
605
  });
606
+ const validateContexts = (h, conn, frame) => {
607
+ for (const statement of frame.cmd) {
608
+ if (!selects(conn, statement.protocol)) return violation("unsupported protocol");
609
+ if (conn.selection.protocols.find((p) => p.name === statement.protocol).record === "ephemeral") {
610
+ const through = watermark(h, conn, statement.protocol);
611
+ const first = frame.cmd.find((s) => s.protocol === statement.protocol && s.seq > through);
612
+ if (first && first.seq !== through + 1) return violation("ephemeral lane gap");
613
+ }
614
+ }
615
+ for (const entry of frame.ctx ?? []) {
616
+ if (!selects(conn, entry.protocol)) return violation("unsupported context protocol");
617
+ try {
618
+ h.current?.validateContext?.(entry.protocol, captureContext(entry.value), conn.attach);
619
+ } catch (error) {
620
+ return violation(error instanceof Error ? error.message : "invalid context");
621
+ }
622
+ }
623
+ };
624
+ const installContexts = (h, conn, frame) => {
625
+ for (const entry of frame.ctx ?? []) conn.contexts.set(entry.protocol, captureContext(entry.value));
626
+ for (const entry of frame.ctx ?? []) h.current?.contextChanged?.(entry.protocol, captureContext(entry.value), conn.attach);
627
+ };
565
628
  const admitFrame = (h, conn, frame) => {
629
+ validateContexts(h, conn, frame);
630
+ installContexts(h, conn, frame);
566
631
  const rec = h.clients.get(conn.clientId);
567
632
  rec.touched = Date.now();
568
633
  const lanes = new Map(frame.cmd.map((s) => [s.protocol, laneOf(h, conn, s.protocol)]));
@@ -626,7 +691,7 @@ const message = (h, conn, data) => {
626
691
  let admission;
627
692
  try {
628
693
  const frame = first ? parseInitialFrame(data).frame : parseFrame(data);
629
- if (frame.cmd.length === 0) return;
694
+ if (frame.cmd.length === 0 && !frame.ctx?.length) return;
630
695
  admission = admitFrame(h, conn, frame);
631
696
  } catch (error) {
632
697
  const reason = violationReason(error);
@@ -888,6 +953,7 @@ const register = (h, clientId, socket, sawFrame = false, auth = void 0, selectio
888
953
  lease: null,
889
954
  auth,
890
955
  lanes: /* @__PURE__ */ new Map(),
956
+ contexts: /* @__PURE__ */ new Map(),
891
957
  documents: /* @__PURE__ */ new Map(),
892
958
  included: /* @__PURE__ */ new Set(),
893
959
  view: /* @__PURE__ */ new Map(),
@@ -899,9 +965,13 @@ const register = (h, clientId, socket, sawFrame = false, auth = void 0, selectio
899
965
  conn.attach = attachOf(h, conn);
900
966
  h.conns.add(conn);
901
967
  try {
968
+ validateContexts(h, conn, frame);
969
+ installContexts(h, conn, frame);
902
970
  h.current?.attaching?.(conn.attach);
903
971
  } catch (error) {
904
- closeConn(h, conn, 1011, "attach failed");
972
+ const reason = violationReason(error);
973
+ if (reason !== null) violate(h, conn, reason);
974
+ else closeConn(h, conn, 1011, "attach failed");
905
975
  throw error;
906
976
  }
907
977
  gcClients(h);
@@ -1054,10 +1124,10 @@ const useSocketHost = (transport, options, persistence) => {
1054
1124
  });
1055
1125
  h.protocol = options?.protocol ?? {
1056
1126
  name: "default",
1057
- version: "2026-09-13"
1127
+ version: "2026-09-30"
1058
1128
  };
1059
1129
  h.key = options?.key;
1060
- h.wireVersion = options?.wireVersion ?? { version: "2026-09-13" };
1130
+ h.wireVersion = options?.wireVersion ?? { version: "2026-09-30" };
1061
1131
  setHeartbeatMs(h, options?.heartbeatIntervalMs ?? 15e3);
1062
1132
  h.pushback = options?.pushback;
1063
1133
  h.wsInitTimeoutMs = options?.wsInitTimeoutMs ?? 5e3;