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
package/dist/statewire.js CHANGED
@@ -16,7 +16,10 @@ const useStatewire = (options) => {
16
16
  ...options.applicationProtocols !== void 0 && { applicationProtocols: options.applicationProtocols },
17
17
  ...options.keyPrefixes !== void 0 && { keyPrefixes: options.keyPrefixes }
18
18
  };
19
- const link = useMemo(() => options.isNew === void 0 ? {} : { isNew: options.isNew }, [options.isNew]);
19
+ const link = useMemo(() => ({
20
+ ...options.isNew === void 0 ? {} : { isNew: options.isNew },
21
+ ...options.context === void 0 ? {} : { context: options.context }
22
+ }), [options.isNew, options.context]);
20
23
  const value = useResource(SessionTransport({
21
24
  transport: options.transport,
22
25
  session,
@@ -24,6 +27,12 @@ const useStatewire = (options) => {
24
27
  protocol,
25
28
  link
26
29
  }));
30
+ const localContext = value.connection === void 0 ? value.setContext : void 0;
31
+ useEffect(() => {
32
+ const current = latest.current;
33
+ if (localContext === void 0 || current.context === void 0) return;
34
+ for (const [protocol_0, context] of Object.entries(current.context)) localContext(protocol_0, context);
35
+ }, [options.context, localContext]);
27
36
  const core = useStatewireCore(() => latest.current, value);
28
37
  useEffect(() => core.rematerialize(), [core, options.materializeKey]);
29
38
  const [subscribe] = useState(() => (listener) => core.subscribe(() => flushTapSync(listener)));
@@ -31,10 +40,10 @@ const useStatewire = (options) => {
31
40
  const isBusy = useSyncExternalStore(subscribe, core.getBusy, core.getBusy);
32
41
  const failed = useSyncExternalStore(subscribe, core.getFailed, core.getFailed);
33
42
  const connection = value.connection ?? CONNECTED_CONNECTION;
34
- const { documents: source, selection, invoke: transportInvoke, failures: transportFailures } = value;
35
- const invoke = useCallback((protocol_0, method, params) => {
43
+ const { documents: source, selection, invoke: transportInvoke, setContext: transportSetContext, failures: transportFailures } = value;
44
+ const invoke = useCallback((protocol_1, method, params) => {
36
45
  if (!transportInvoke) throw new Error("statewire: transport does not support protocol commands");
37
- return transportInvoke(protocol_0, method, params);
46
+ return transportInvoke(protocol_1, method, params);
38
47
  }, [transportInvoke]);
39
48
  return useMemo(() => {
40
49
  const failures = () => {
@@ -46,6 +55,10 @@ const useStatewire = (options) => {
46
55
  documents: source,
47
56
  selection,
48
57
  invoke,
58
+ setContext: (protocol_2, value_0) => {
59
+ if (!transportSetContext) throw new Error("statewire: transport does not support context");
60
+ transportSetContext(protocol_2, value_0);
61
+ },
49
62
  getState: core.getState,
50
63
  connection,
51
64
  commands: core.commands,
@@ -63,7 +76,8 @@ const useStatewire = (options) => {
63
76
  source,
64
77
  selection,
65
78
  invoke,
66
- transportFailures
79
+ transportFailures,
80
+ transportSetContext
67
81
  ]);
68
82
  };
69
83
  const StatewireResource = (options) => resource(useStatewire)(options);
@@ -92,6 +106,9 @@ var StatewireClient = class {
92
106
  invoke(protocol, method, params) {
93
107
  return this.root.getValue().invoke(protocol, method, params);
94
108
  }
109
+ setContext(protocol, value) {
110
+ this.root.getValue().setContext(protocol, value);
111
+ }
95
112
  get isBusy() {
96
113
  return this.root.getValue().isBusy;
97
114
  }
@@ -1 +1 @@
1
- {"version":3,"file":"statewire.js","names":["StatewireDocuments","StatewireProtocol","StatewireHost","useCallback","useEffect","useMemo","useRef","useState","useSyncExternalStore","createTapRoot","flushTapSync","resource","useContextProvider","useResource","useResources","withKey","ResourceElement","CONNECTED_CONNECTION","useStatewireCore","Statewire","TERMINAL","DocumentStorageContext","LinkContext","ProtocolContext","SessionStorageContext","useSessionTransport","transport","session","documents","protocol","link","StatewireClient","SessionStorage","Storage","Options","isNew","SessionTransport","useStatewire","Commands","options","TState","TCommands","Helpers","latest","current","storage","undefined","wireVersion","applicationProtocols","keyPrefixes","value","core","rematerialize","materializeKey","subscribe","listener","state","getState","isBusy","getBusy","failed","getFailed","connection","source","selection","invoke","transportInvoke","failures","transportFailures","method","params","Error","NonNullable","commands","CommandsProxy","retryFailed","entry","FailedCommand","retry","discardFailed","discard","StatewireResource","root","disposed","constructor","ClientRoot","getValue","Connection","Source","Selection","Promise","dispose","unmount","Readonly","Record","Range","Offer","initialState","optimistic","Optimistic","materialize","Materialize","onStateChange","ctx","changes","Changes","onCommandChange","update","CommandUpdate","onConnectionChange","onError","error","ConnectionError","failureCovered","ConnectionState","status","reason","degraded","attempt","cause","code","nextRetryAt","lastError","message","payload","reconnect","ConnectionStatus","draft","command","Command","OptimisticContext","ts","seq","clientId","SendFailure","fate","predecessorLost","OnSessionLost","pending","ReadonlyArray","produced","SessionCommand","meta","failure","wire","protocols","SessionLane","nextSeq","verdicts","SessionVerdict","SessionRecord","lanes","type","ready","flush","load","save","record","migrate","lock","fn","T","fin","Fin","HeadersOption","HeadersInit","staleAuth","HttpOptions","sessionStore","url","headers","fetch","globalThis","onSessionLost","onChange","onDone","WebsocketOptions","query","webSocket","WebSocket"],"sources":["../src/statewire.ts"],"sourcesContent":["import type { StatewireDocuments } from \"./wire/documents\";\nimport type { StatewireProtocol } from \"./wire/protocol\";\nimport type { StatewireHost } from \"./host\";\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n} 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 { CONNECTED_CONNECTION, useStatewireCore } from \"./client/core\";\nimport type { Statewire } from \"./types\";\nimport type { TERMINAL } from \"./client/connection\";\nimport {\n DocumentStorageContext,\n LinkContext,\n ProtocolContext,\n SessionStorageContext,\n} from \"./client/sessionStore\";\n\nconst useSessionTransport = ({\n transport,\n session,\n documents,\n protocol,\n link,\n}: {\n transport: ResourceElement<Statewire<any, any>>;\n session: StatewireClient.SessionStorage | undefined;\n documents: StatewireDocuments.Storage | undefined;\n protocol: StatewireProtocol.Options;\n link: { isNew?: boolean | undefined };\n}): Statewire<unknown> =>\n useContextProvider(SessionStorageContext, session, () =>\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useContextProvider(DocumentStorageContext, documents, () =>\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useContextProvider(ProtocolContext, protocol, () =>\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useContextProvider(LinkContext, link, () =>\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useResource(transport),\n ),\n ),\n ),\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 documents = useResources(\n options.storage?.documents === undefined\n ? []\n : [withKey(\"documents\", options.storage.documents)],\n )[0];\n const protocol: StatewireProtocol.Options = {\n ...(options.protocol !== undefined && { protocol: options.protocol }),\n ...(options.wireVersion !== undefined && {\n wireVersion: options.wireVersion,\n }),\n ...(options.applicationProtocols !== undefined && {\n applicationProtocols: options.applicationProtocols,\n }),\n ...(options.keyPrefixes !== undefined && {\n keyPrefixes: options.keyPrefixes,\n }),\n };\n const link = useMemo(\n () => (options.isNew === undefined ? {} : { isNew: options.isNew }),\n [options.isNew],\n );\n const value = useResource(\n SessionTransport({\n transport: options.transport,\n session,\n documents,\n protocol,\n link,\n }),\n );\n const core = useStatewireCore(() => latest.current, value);\n useEffect(() => core.rematerialize(), [core, options.materializeKey]);\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 failed = useSyncExternalStore(\n subscribe,\n core.getFailed,\n core.getFailed,\n );\n const connection = value.connection ?? CONNECTED_CONNECTION;\n const {\n documents: source,\n selection,\n invoke: transportInvoke,\n failures: transportFailures,\n } = value;\n\n const invoke = useCallback(\n (protocol: string, method: string, params: readonly unknown[]) => {\n if (!transportInvoke)\n throw new Error(\n \"statewire: transport does not support protocol commands\",\n );\n return transportInvoke(protocol, method, params);\n },\n [transportInvoke],\n );\n\n return useMemo(() => {\n const failures = (): NonNullable<typeof transportFailures> => {\n if (!transportFailures) {\n throw new Error(\"statewire: this transport has no failed-send lane\");\n }\n return transportFailures;\n };\n return {\n state,\n documents: source,\n selection,\n invoke,\n getState: core.getState as () => TState,\n connection,\n commands: core.commands as Statewire.CommandsProxy<TCommands>,\n isBusy,\n failed,\n retryFailed: (entry: StatewireClient.FailedCommand) =>\n failures().retry(entry),\n discardFailed: (entry: StatewireClient.FailedCommand) =>\n failures().discard(entry),\n };\n }, [\n state,\n isBusy,\n failed,\n connection,\n core,\n source,\n selection,\n invoke,\n transportFailures,\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 documents(): StatewireDocuments.Source | undefined {\n return this.root.getValue().documents;\n }\n get selection(): StatewireProtocol.Selection | undefined {\n return this.root.getValue().selection;\n }\n invoke(\n protocol: string,\n method: string,\n params: readonly unknown[],\n ): Promise<unknown> {\n return this.root.getValue().invoke(protocol, method, params);\n }\n get isBusy(): boolean {\n return this.root.getValue().isBusy;\n }\n\n get failed(): readonly StatewireClient.FailedCommand[] {\n return this.root.getValue().failed;\n }\n\n retryFailed(entry: StatewireClient.FailedCommand): Promise<unknown> {\n return this.root.getValue().retryFailed(entry);\n }\n\n discardFailed(entry: StatewireClient.FailedCommand): void {\n this.root.getValue().discardFailed(entry);\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 protocol?: string;\n keyPrefixes?: Readonly<Record<string, readonly string[]>>;\n wireVersion?: StatewireProtocol.Range;\n applicationProtocols?: readonly StatewireProtocol.Offer[] | \"*\";\n transport: ResourceElement<Statewire<TState, TCommands>>;\n /** The session does not exist on the server yet: the transport's first attach waits for the first command send; the transport's own `isNew` wins. */\n isNew?: boolean;\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 | {\n session?: ResourceElement<SessionStorage> | undefined;\n documents?: ResourceElement<StatewireDocuments.Storage> | undefined;\n }\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 /** Republishes through `materialize` when it changes (`Object.is`); `materialize` itself is read fresh on every publish. */\n materializeKey?: unknown;\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 onError?: (error: ConnectionError) => void;\n /** Drops a retained failed entry once a canonical snapshot shows its effect landed; checked against each new snapshot. */\n failureCovered?: (\n state: NonNullable<TState>,\n entry: FailedCommand,\n ) => boolean;\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 documents: StatewireDocuments.Source | undefined;\n selection: StatewireProtocol.Selection | undefined;\n invoke: (\n protocol: string,\n method: string,\n params: readonly unknown[],\n ) => Promise<unknown>;\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 /** Non-accepted sends retained in the session record; reload with it. */\n failed: readonly FailedCommand[];\n /** Re-enqueues the entry's command on fresh clocks and removes the entry. */\n retryFailed: (entry: FailedCommand) => Promise<unknown>;\n /** Removes the entry. */\n discardFailed: (entry: FailedCommand) => void;\n };\n\n /** Connection lifecycle and whether its current state warrants a problem indicator. */\n export type ConnectionState =\n | { status: \"standby\"; reason: \"deferred\" | \"idle\"; degraded: false }\n | { status: \"connecting\"; degraded: false }\n | { status: \"connected\"; degraded: false }\n | {\n status: \"retrying\";\n degraded: true;\n attempt: number;\n /** Backoff details are absent while the retry dial is in flight. */\n cause?: \"dropped\" | \"reconnect\" | \"error\" | \"unauthorized\";\n code?: string;\n nextRetryAt?: number;\n lastError?: string;\n message?: string;\n }\n | {\n status: \"stopped\";\n degraded: true;\n reason:\n | \"gone\"\n | \"forbidden\"\n | \"protocol-error\"\n | \"unauthorized\"\n | \"storage-error\";\n message?: string;\n payload?: unknown;\n };\n export type Connection = ConnectionState & {\n error?: ConnectionError;\n reconnect: () => void;\n };\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 fold client-known facts into the state: server-accepted,\n * still-uncommitted intents (e.g. dispatch records into messages) or the live\n * state of a sibling statewire. Runs after the optimistic folds on every\n * published state; `materializeKey` republishes when the facts change.\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 non-accepted send retained for the user: the wire statement, its failure, and its original clocks. */\n export type FailedCommand = {\n readonly protocol: string;\n readonly produced?: SessionCommand[\"produced\"];\n readonly command: string;\n readonly params: readonly unknown[];\n readonly meta?: unknown;\n readonly failure: SendFailure;\n readonly ts: number;\n readonly seq: number;\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 produced?: {\n wire: StatewireProtocol.Range;\n protocols: readonly StatewireProtocol.Offer[] | \"*\";\n };\n method: string;\n params: unknown[];\n meta?: unknown;\n };\n\n /** A durable lane: its cmd clock and the commands the server has not marked durable. */\n export type SessionLane = {\n nextSeq: number;\n commands: readonly SessionCommand[];\n /** Terminal verdicts parked for the sharer that minted the seq (shared storage only). */\n verdicts?: readonly SessionVerdict[];\n /** Non-accepted sends retained for the user; survive record rebases and resets. */\n failed?: readonly FailedCommand[];\n };\n\n /** The persisted session: the client identity plus one lane per durable protocol. */\n export type SessionRecord = {\n clientId: string;\n lanes: Readonly<Record<string, SessionLane>>;\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 with optional asynchronous hydration and persistence. */\n export type SessionStorage = {\n /** Resolves when a synchronous load can return the hydrated record. */\n ready?: Promise<void>;\n /** Resolves after all queued saves are persisted; awaited before sending statements. */\n flush?: () => void | Promise<void>;\n load: () => SessionRecord | null;\n save: (record: SessionRecord) => void;\n /** Upgrade incompatible stored commands; null discards the old record and starts a new client identity. */\n migrate?: (\n record: SessionRecord,\n current: NonNullable<SessionCommand[\"produced\"]>,\n ) => SessionRecord | null;\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 ConnectionError = Error & { fin?: StatewireHost.Fin };\n\n export type HeadersOption =\n | HeadersInit\n | ((ctx: { staleAuth: boolean }) => HeadersInit | Promise<HeadersInit>);\n\n export type HttpOptions = StatewireProtocol.Options & {\n sessionStore?: SessionStorage | undefined;\n url: string;\n /** The session does not exist on the server yet: the first attach waits for the first command send (or an explicit `reconnect()`). */\n isNew?: boolean;\n headers?: HeadersOption;\n fetch?: typeof globalThis.fetch;\n onSessionLost?: OnSessionLost;\n onChange?: (connection: Connection) => void;\n onError?: (error: ConnectionError) => void;\n /** The broadcast idle flag's rising edge: the instance went quiet. */\n onDone?: () => void;\n };\n\n export type WebsocketOptions = StatewireProtocol.Options & {\n sessionStore?: SessionStorage | undefined;\n url: string;\n /** The session does not exist on the server yet: the first attach waits for the first command send (or an explicit `reconnect()`). */\n isNew?: boolean;\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 onError?: (error: ConnectionError) => void;\n /** The broadcast idle flag's rising edge: the instance went quiet. */\n onDone?: () => void;\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"statewire.js","names":["StatewireDocuments","StatewireProtocol","StatewireHost","useCallback","useEffect","useMemo","useRef","useState","useSyncExternalStore","createTapRoot","flushTapSync","resource","useContextProvider","useResource","useResources","withKey","ResourceElement","CONNECTED_CONNECTION","useStatewireCore","Statewire","TERMINAL","DocumentStorageContext","LinkContext","ProtocolContext","SessionStorageContext","useSessionTransport","transport","session","documents","protocol","link","StatewireClient","SessionStorage","Storage","Options","isNew","context","Readonly","Record","Context","SessionTransport","useStatewire","Commands","options","TState","TCommands","Helpers","latest","current","storage","undefined","wireVersion","applicationProtocols","keyPrefixes","value","localContext","connection","setContext","Object","entries","core","rematerialize","materializeKey","subscribe","listener","state","getState","isBusy","getBusy","failed","getFailed","source","selection","invoke","transportInvoke","transportSetContext","failures","transportFailures","method","params","Error","NonNullable","commands","CommandsProxy","retryFailed","entry","FailedCommand","retry","discardFailed","discard","StatewireResource","root","disposed","constructor","ClientRoot","getValue","Connection","Source","Selection","Promise","dispose","unmount","Range","Offer","initialState","optimistic","Optimistic","materialize","Materialize","onStateChange","ctx","changes","Changes","onCommandChange","update","CommandUpdate","onConnectionChange","onError","error","ConnectionError","failureCovered","ConnectionState","status","reason","degraded","attempt","cause","code","nextRetryAt","lastError","message","payload","reconnect","ConnectionStatus","draft","command","Command","OptimisticContext","ts","seq","clientId","SendFailure","fate","predecessorLost","OnSessionLost","pending","ReadonlyArray","produced","SessionCommand","meta","failure","wire","protocols","SessionLane","nextSeq","verdicts","SessionVerdict","SessionRecord","lanes","type","ready","flush","load","save","record","migrate","lock","fn","T","fin","Fin","HeadersOption","HeadersInit","staleAuth","HttpOptions","sessionStore","url","headers","fetch","globalThis","onSessionLost","onChange","onDone","WebsocketOptions","query","webSocket","WebSocket"],"sources":["../src/statewire.ts"],"sourcesContent":["import type { StatewireDocuments } from \"./wire/documents\";\nimport type { StatewireProtocol } from \"./wire/protocol\";\nimport type { StatewireHost } from \"./host\";\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n} 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 { CONNECTED_CONNECTION, useStatewireCore } from \"./client/core\";\nimport type { Statewire } from \"./types\";\nimport type { TERMINAL } from \"./client/connection\";\nimport {\n DocumentStorageContext,\n LinkContext,\n ProtocolContext,\n SessionStorageContext,\n} from \"./client/sessionStore\";\n\nconst useSessionTransport = ({\n transport,\n session,\n documents,\n protocol,\n link,\n}: {\n transport: ResourceElement<Statewire<any, any>>;\n session: StatewireClient.SessionStorage | undefined;\n documents: StatewireDocuments.Storage | undefined;\n protocol: StatewireProtocol.Options;\n link: {\n isNew?: boolean | undefined;\n context?: Readonly<Record<string, Statewire.Context>> | undefined;\n };\n}): Statewire<unknown> =>\n useContextProvider(SessionStorageContext, session, () =>\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useContextProvider(DocumentStorageContext, documents, () =>\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useContextProvider(ProtocolContext, protocol, () =>\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useContextProvider(LinkContext, link, () =>\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useResource(transport),\n ),\n ),\n ),\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 documents = useResources(\n options.storage?.documents === undefined\n ? []\n : [withKey(\"documents\", options.storage.documents)],\n )[0];\n const protocol: StatewireProtocol.Options = {\n ...(options.protocol !== undefined && { protocol: options.protocol }),\n ...(options.wireVersion !== undefined && {\n wireVersion: options.wireVersion,\n }),\n ...(options.applicationProtocols !== undefined && {\n applicationProtocols: options.applicationProtocols,\n }),\n ...(options.keyPrefixes !== undefined && {\n keyPrefixes: options.keyPrefixes,\n }),\n };\n const link = useMemo(\n () => ({\n ...(options.isNew === undefined ? {} : { isNew: options.isNew }),\n ...(options.context === undefined ? {} : { context: options.context }),\n }),\n [options.isNew, options.context],\n );\n const value = useResource(\n SessionTransport({\n transport: options.transport,\n session,\n documents,\n protocol,\n link,\n }),\n );\n const localContext =\n value.connection === undefined ? value.setContext : undefined;\n useEffect(() => {\n const current = latest.current;\n if (localContext === undefined || current.context === undefined) return;\n for (const [protocol, context] of Object.entries(current.context))\n localContext(protocol, context);\n }, [options.context, localContext]);\n const core = useStatewireCore(() => latest.current, value);\n useEffect(() => core.rematerialize(), [core, options.materializeKey]);\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 failed = useSyncExternalStore(\n subscribe,\n core.getFailed,\n core.getFailed,\n );\n const connection = value.connection ?? CONNECTED_CONNECTION;\n const {\n documents: source,\n selection,\n invoke: transportInvoke,\n setContext: transportSetContext,\n failures: transportFailures,\n } = value;\n\n const invoke = useCallback(\n (protocol: string, method: string, params: readonly unknown[]) => {\n if (!transportInvoke)\n throw new Error(\n \"statewire: transport does not support protocol commands\",\n );\n return transportInvoke(protocol, method, params);\n },\n [transportInvoke],\n );\n\n return useMemo(() => {\n const failures = (): NonNullable<typeof transportFailures> => {\n if (!transportFailures) {\n throw new Error(\"statewire: this transport has no failed-send lane\");\n }\n return transportFailures;\n };\n return {\n state,\n documents: source,\n selection,\n invoke,\n setContext: (protocol: string, value: Statewire.Context) => {\n if (!transportSetContext)\n throw new Error(\"statewire: transport does not support context\");\n transportSetContext(protocol, value);\n },\n getState: core.getState as () => TState,\n connection,\n commands: core.commands as Statewire.CommandsProxy<TCommands>,\n isBusy,\n failed,\n retryFailed: (entry: StatewireClient.FailedCommand) =>\n failures().retry(entry),\n discardFailed: (entry: StatewireClient.FailedCommand) =>\n failures().discard(entry),\n };\n }, [\n state,\n isBusy,\n failed,\n connection,\n core,\n source,\n selection,\n invoke,\n transportFailures,\n transportSetContext,\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 documents(): StatewireDocuments.Source | undefined {\n return this.root.getValue().documents;\n }\n get selection(): StatewireProtocol.Selection | undefined {\n return this.root.getValue().selection;\n }\n invoke(\n protocol: string,\n method: string,\n params: readonly unknown[],\n ): Promise<unknown> {\n return this.root.getValue().invoke(protocol, method, params);\n }\n setContext(protocol: string, value: Statewire.Context): void {\n this.root.getValue().setContext(protocol, value);\n }\n get isBusy(): boolean {\n return this.root.getValue().isBusy;\n }\n\n get failed(): readonly StatewireClient.FailedCommand[] {\n return this.root.getValue().failed;\n }\n\n retryFailed(entry: StatewireClient.FailedCommand): Promise<unknown> {\n return this.root.getValue().retryFailed(entry);\n }\n\n discardFailed(entry: StatewireClient.FailedCommand): void {\n this.root.getValue().discardFailed(entry);\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 protocol?: string;\n context?: Readonly<Record<string, Statewire.Context>>;\n keyPrefixes?: Readonly<Record<string, readonly string[]>>;\n wireVersion?: StatewireProtocol.Range;\n applicationProtocols?: readonly StatewireProtocol.Offer[] | \"*\";\n transport: ResourceElement<Statewire<TState, TCommands>>;\n /** The session does not exist on the server yet: the transport's first attach waits for the first command send; the transport's own `isNew` wins. */\n isNew?: boolean;\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 | {\n session?: ResourceElement<SessionStorage> | undefined;\n documents?: ResourceElement<StatewireDocuments.Storage> | undefined;\n }\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 /** Republishes through `materialize` when it changes (`Object.is`); `materialize` itself is read fresh on every publish. */\n materializeKey?: unknown;\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 onError?: (error: ConnectionError) => void;\n /** Drops a retained failed entry once a canonical snapshot shows its effect landed; checked against each new snapshot. */\n failureCovered?: (\n state: NonNullable<TState>,\n entry: FailedCommand,\n ) => boolean;\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 documents: StatewireDocuments.Source | undefined;\n selection: StatewireProtocol.Selection | undefined;\n setContext: (protocol: string, value: Statewire.Context) => void;\n invoke: (\n protocol: string,\n method: string,\n params: readonly unknown[],\n ) => Promise<unknown>;\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 /** Non-accepted sends retained in the session record; reload with it. */\n failed: readonly FailedCommand[];\n /** Re-enqueues the entry's command on fresh clocks and removes the entry. */\n retryFailed: (entry: FailedCommand) => Promise<unknown>;\n /** Removes the entry. */\n discardFailed: (entry: FailedCommand) => void;\n };\n\n /** Connection lifecycle and whether its current state warrants a problem indicator. */\n export type ConnectionState =\n | { status: \"standby\"; reason: \"deferred\" | \"idle\"; degraded: false }\n | { status: \"connecting\"; degraded: false }\n | { status: \"connected\"; degraded: false }\n | {\n status: \"retrying\";\n degraded: true;\n attempt: number;\n /** Backoff details are absent while the retry dial is in flight. */\n cause?: \"dropped\" | \"reconnect\" | \"error\" | \"unauthorized\";\n code?: string;\n nextRetryAt?: number;\n lastError?: string;\n message?: string;\n }\n | {\n status: \"stopped\";\n degraded: true;\n reason:\n | \"gone\"\n | \"forbidden\"\n | \"protocol-error\"\n | \"unauthorized\"\n | \"storage-error\";\n message?: string;\n payload?: unknown;\n };\n export type Connection = ConnectionState & {\n error?: ConnectionError;\n reconnect: () => void;\n };\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 fold client-known facts into the state: server-accepted,\n * still-uncommitted intents (e.g. dispatch records into messages) or the live\n * state of a sibling statewire. Runs after the optimistic folds on every\n * published state; `materializeKey` republishes when the facts change.\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 non-accepted send retained for the user: the wire statement, its failure, and its original clocks. */\n export type FailedCommand = {\n readonly protocol: string;\n readonly produced?: SessionCommand[\"produced\"];\n readonly command: string;\n readonly params: readonly unknown[];\n readonly meta?: unknown;\n readonly failure: SendFailure;\n readonly ts: number;\n readonly seq: number;\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 produced?: {\n wire: StatewireProtocol.Range;\n protocols: readonly StatewireProtocol.Offer[] | \"*\";\n };\n method: string;\n params: unknown[];\n meta?: unknown;\n };\n\n /** A durable lane: its cmd clock and the commands the server has not marked durable. */\n export type SessionLane = {\n nextSeq: number;\n commands: readonly SessionCommand[];\n /** Terminal verdicts parked for the sharer that minted the seq (shared storage only). */\n verdicts?: readonly SessionVerdict[];\n /** Non-accepted sends retained for the user; survive record rebases and resets. */\n failed?: readonly FailedCommand[];\n };\n\n /** The persisted session: the client identity plus one lane per durable protocol. */\n export type SessionRecord = {\n clientId: string;\n lanes: Readonly<Record<string, SessionLane>>;\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 with optional asynchronous hydration and persistence. */\n export type SessionStorage = {\n /** Resolves when a synchronous load can return the hydrated record. */\n ready?: Promise<void>;\n /** Resolves after all queued saves are persisted; awaited before sending statements. */\n flush?: () => void | Promise<void>;\n load: () => SessionRecord | null;\n save: (record: SessionRecord) => void;\n /** Upgrade incompatible stored commands; null discards the old record and starts a new client identity. */\n migrate?: (\n record: SessionRecord,\n current: NonNullable<SessionCommand[\"produced\"]>,\n ) => SessionRecord | null;\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 ConnectionError = Error & { fin?: StatewireHost.Fin };\n\n export type HeadersOption =\n | HeadersInit\n | ((ctx: { staleAuth: boolean }) => HeadersInit | Promise<HeadersInit>);\n\n export type HttpOptions = StatewireProtocol.Options & {\n sessionStore?: SessionStorage | undefined;\n url: string;\n /** The session does not exist on the server yet: the first attach waits for the first command send (or an explicit `reconnect()`). */\n isNew?: boolean;\n headers?: HeadersOption;\n fetch?: typeof globalThis.fetch;\n onSessionLost?: OnSessionLost;\n onChange?: (connection: Connection) => void;\n onError?: (error: ConnectionError) => void;\n /** The broadcast idle flag's rising edge: the instance went quiet. */\n onDone?: () => void;\n };\n\n export type WebsocketOptions = StatewireProtocol.Options & {\n sessionStore?: SessionStorage | undefined;\n url: string;\n /** The session does not exist on the server yet: the first attach waits for the first command send (or an explicit `reconnect()`). */\n isNew?: boolean;\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 onError?: (error: ConnectionError) => void;\n /** The broadcast idle flag's rising edge: the instance went quiet. */\n onDone?: () => void;\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"remote.d.ts","names":[],"sources":["../../src/transports/remote.ts"],"mappings":";;;;;cAgBa,qBACX;EACE;EACA,YAAY,YAAY,gBAAgB;EACxC;IACE,yBAEJ,SAAS,UACT,UAAU,SAAS,UAAU,KAAK,yBAAyB,eAC1D"}
1
+ {"version":3,"file":"remote.d.ts","names":[],"sources":["../../src/transports/remote.ts"],"mappings":";;;;;cAiBa,qBACX;EACE;EACA,YAAY,YAAY,gBAAgB;EACxC;IACE,yBAEJ,SAAS,UACT,UAAU,SAAS,UAAU,KAAK,yBAAyB,eAC1D"}
@@ -1,5 +1,5 @@
1
1
  import { LinkContext, ProtocolContext } from "../client/sessionStore.js";
2
- import { discardFailed, documentSource, failedCommands, invokeProtocol, retryFailed, setTransport, useStatewireSession } from "../client/session.js";
2
+ import { discardFailed, documentSource, failedCommands, invokeProtocol, retryFailed, setContext, setTransport, useStatewireSession } from "../client/session.js";
3
3
  import { useContext, useEffect, useMemo } from "@assistant-ui/tap/react-shim";
4
4
  //#region src/transports/remote.ts
5
5
  const useStatewireRemote = (options, useLink) => {
@@ -13,7 +13,8 @@ const useStatewireRemote = (options, useLink) => {
13
13
  useEffect(() => setTransport(s, conn), [s, conn]);
14
14
  const lanes = useMemo(() => ({
15
15
  documents: documentSource(s),
16
- invoke: (protocol, method, params) => invokeProtocol(s, protocol, method, params),
16
+ setContext: (protocol, value) => setContext(s, protocol, value),
17
+ invoke: (protocol_0, method, params) => invokeProtocol(s, protocol_0, method, params),
17
18
  failures: {
18
19
  list: () => failedCommands(s),
19
20
  retry: (entry) => retryFailed(s, entry),
@@ -1 +1 @@
1
- {"version":3,"file":"remote.js","names":["useContext","useEffect","useMemo","LinkContext","ProtocolContext","Statewire","documentSource","invokeProtocol","discardFailed","failedCommands","retryFailed","setTransport","useStatewireSession","StatewireSessionOptions","StatewireClient","RemoteLink","StatewireRemoteProps","useStatewireRemote","url","onChange","connection","Connection","onDone","options","TOptions","useLink","ctx","configured","s","conn","lanes","documents","invoke","protocol","method","params","failures","list","retry","entry","FailedCommand","discard","state","canonical","selection","commands","source","isBusy","idle"],"sources":["../../src/transports/remote.ts"],"sourcesContent":["import { useContext, useEffect, useMemo } from \"react\";\nimport { LinkContext, ProtocolContext } from \"../client/sessionStore\";\nimport type { Statewire } from \"../types\";\nimport {\n documentSource,\n invokeProtocol,\n discardFailed,\n failedCommands,\n retryFailed,\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 configured = {\n ...useContext(ProtocolContext),\n ...useContext(LinkContext),\n ...options,\n };\n const s = useStatewireSession(configured);\n const conn = useLink(configured, s.ctx);\n useEffect(() => setTransport(s, conn), [s, conn]);\n const lanes = useMemo(\n () => ({\n documents: documentSource(s),\n invoke: (protocol: string, method: string, params: readonly unknown[]) =>\n invokeProtocol(s, protocol, method, params),\n failures: {\n list: () => failedCommands(s),\n retry: (entry: StatewireClient.FailedCommand) => retryFailed(s, entry),\n discard: (entry: StatewireClient.FailedCommand) =>\n discardFailed(s, entry),\n },\n }),\n [s],\n );\n\n return {\n state: s.canonical,\n ...lanes,\n get selection() {\n return s.selection;\n },\n commands: s.commands,\n connection: conn.connection,\n source: s.source,\n get isBusy() {\n return !s.idle;\n },\n };\n};\n"],"mappings":";;;;AAgBA,MAAaiB,sBAOXM,SACAE,YACwB;CACxB,MAAME,aAAa;EACjB,GAAG3B,WAAWI,eAAe;EAC7B,GAAGJ,WAAWG,WAAW;EACzB,GAAGoB;CACL;CACA,MAAMK,IAAIhB,oBAAoBe,UAAU;CACxC,MAAME,OAAOJ,QAAQE,YAAYC,EAAEF,GAAG;CACtCzB,gBAAgBU,aAAaiB,GAAGC,IAAI,GAAG,CAACD,GAAGC,IAAI,CAAC;CAChD,MAAMC,QAAQ5B,eACL;EACL6B,WAAWzB,eAAesB,CAAC;EAC3BI,SAASC,UAAkBC,QAAgBC,WACzC5B,eAAeqB,GAAGK,UAAUC,QAAQC,MAAM;EAC5CC,UAAU;GACRC,YAAY5B,eAAemB,CAAC;GAC5BU,QAAQC,UAAyC7B,YAAYkB,GAAGW,KAAK;GACrEE,UAAUF,YACR/B,cAAcoB,GAAGW,OAAK;EAC1B;CACF,IACA,CAACX,CAAC,CACJ;CAEA,OAAO;EACLc,OAAOd,EAAEe;EACT,GAAGb;EACH,IAAIc,YAAY;GACd,OAAOhB,EAAEgB;EACX;EACAC,UAAUjB,EAAEiB;EACZzB,YAAYS,KAAKT;EACjB0B,QAAQlB,EAAEkB;EACV,IAAIC,SAAS;GACX,OAAO,CAACnB,EAAEoB;EACZ;CACF;AACF"}
1
+ {"version":3,"file":"remote.js","names":["useContext","useEffect","useMemo","LinkContext","ProtocolContext","Statewire","documentSource","invokeProtocol","discardFailed","failedCommands","retryFailed","setTransport","useStatewireSession","setContext","StatewireSessionOptions","StatewireClient","RemoteLink","StatewireRemoteProps","useStatewireRemote","url","onChange","connection","Connection","onDone","options","TOptions","useLink","ctx","configured","s","conn","lanes","documents","protocol","value","Context","invoke","method","params","failures","list","retry","entry","FailedCommand","discard","state","canonical","selection","commands","source","isBusy","idle"],"sources":["../../src/transports/remote.ts"],"sourcesContent":["import { useContext, useEffect, useMemo } from \"react\";\nimport { LinkContext, ProtocolContext } from \"../client/sessionStore\";\nimport type { Statewire } from \"../types\";\nimport {\n documentSource,\n invokeProtocol,\n discardFailed,\n failedCommands,\n retryFailed,\n setTransport,\n useStatewireSession,\n setContext,\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 configured = {\n ...useContext(ProtocolContext),\n ...useContext(LinkContext),\n ...options,\n };\n const s = useStatewireSession(configured);\n const conn = useLink(configured, s.ctx);\n useEffect(() => setTransport(s, conn), [s, conn]);\n const lanes = useMemo(\n () => ({\n documents: documentSource(s),\n setContext: (protocol: string, value: Statewire.Context) =>\n setContext(s, protocol, value),\n invoke: (protocol: string, method: string, params: readonly unknown[]) =>\n invokeProtocol(s, protocol, method, params),\n failures: {\n list: () => failedCommands(s),\n retry: (entry: StatewireClient.FailedCommand) => retryFailed(s, entry),\n discard: (entry: StatewireClient.FailedCommand) =>\n discardFailed(s, entry),\n },\n }),\n [s],\n );\n\n return {\n state: s.canonical,\n ...lanes,\n get selection() {\n return s.selection;\n },\n commands: s.commands,\n connection: conn.connection,\n source: s.source,\n get isBusy() {\n return !s.idle;\n },\n };\n};\n"],"mappings":";;;;AAiBA,MAAakB,sBAOXM,SACAE,YACwB;CACxB,MAAME,aAAa;EACjB,GAAG5B,WAAWI,eAAe;EAC7B,GAAGJ,WAAWG,WAAW;EACzB,GAAGqB;CACL;CACA,MAAMK,IAAIjB,oBAAoBgB,UAAU;CACxC,MAAME,OAAOJ,QAAQE,YAAYC,EAAEF,GAAG;CACtC1B,gBAAgBU,aAAakB,GAAGC,IAAI,GAAG,CAACD,GAAGC,IAAI,CAAC;CAChD,MAAMC,QAAQ7B,eACL;EACL8B,WAAW1B,eAAeuB,CAAC;EAC3BhB,aAAaoB,UAAkBC,UAC7BrB,WAAWgB,GAAGI,UAAUC,KAAK;EAC/BE,SAASH,YAAkBI,QAAgBC,WACzC/B,eAAesB,GAAGI,YAAUI,QAAQC,MAAM;EAC5CC,UAAU;GACRC,YAAY/B,eAAeoB,CAAC;GAC5BY,QAAQC,UAAyChC,YAAYmB,GAAGa,KAAK;GACrEE,UAAUF,YACRlC,cAAcqB,GAAGa,OAAK;EAC1B;CACF,IACA,CAACb,CAAC,CACJ;CAEA,OAAO;EACLgB,OAAOhB,EAAEiB;EACT,GAAGf;EACH,IAAIgB,YAAY;GACd,OAAOlB,EAAEkB;EACX;EACAC,UAAUnB,EAAEmB;EACZ3B,YAAYS,KAAKT;EACjB4B,QAAQpB,EAAEoB;EACV,IAAIC,SAAS;GACX,OAAO,CAACrB,EAAEsB;EACZ;CACF;AACF"}
@@ -1,4 +1,5 @@
1
1
  import { StatewireHost } from "../host.js";
2
+ import { StatewireDocument } from "../document.js";
2
3
  import { StatewireCmd, StatewireSyn } from "../client/connection.js";
3
4
  import { StatewireClient } from "../statewire.js";
4
5
  import { Statewire } from "../types.js";
@@ -6,6 +7,9 @@ import { ResourceElement } from "@assistant-ui/tap";
6
7
  //#region src/transports/sse.d.ts
7
8
  /** A client frame's lanes: stamped statements, strictly consecutive per lane. */
8
9
  type StatewireFrame = {
10
+ ctx?: ({
11
+ protocol: string;
12
+ } & StatewireDocument.Encoded)[];
9
13
  cmd?: {
10
14
  protocol: string;
11
15
  method: string;
@@ -1 +1 @@
1
- {"version":3,"file":"sse.d.ts","names":[],"sources":["../../src/transports/sse.ts"],"mappings":";;;;;;;KA0BY;EACV;IACE;IACA;IACA;IACA;IACA;;;KAIQ;EACV,4BAA4B;EAC5B;;EAEA,uBAAuB;EACvB,UAAU;IAAQ;IAAgB,KAAK;;EACvC,QAAQ;IAAQ;IAAgB,MAAM;;EACtC,SAAS,MAAM;EACf,YAAY,KAAK;EACjB,SAAS;EACT;EACA,WAAW,MAAM,cAAc;;KAGrB;EACN;;EACA;EAAiB,KAAK;;EACtB;;EACA;;KAEM,kBAAkB,gBAAgB;;KAGlC;EACV,gBAAgB,OAAO;EACvB,YAAY,gBAAgB;;EAE5B;EACA,OAAO,OAAO,mBAAmB,QAAQ;;cAoP9B,gBACX,cACA,kBAAkB,UAAU,WAAW,UAAU,UAEjD,SAAS,gBAAgB,gBACtB,gBAAgB,UAAU,oBAAoB"}
1
+ {"version":3,"file":"sse.d.ts","names":[],"sources":["../../src/transports/sse.ts"],"mappings":";;;;;;;;KA2BY;EACV;IAAS;MAAqB,kBAAkB;EAChD;IACE;IACA;IACA;IACA;IACA;;;KAIQ;EACV,4BAA4B;EAC5B;;EAEA,uBAAuB;EACvB,UAAU;IAAQ;IAAgB,KAAK;;EACvC,QAAQ;IAAQ;IAAgB,MAAM;;EACtC,SAAS,MAAM;EACf,YAAY,KAAK;EACjB,SAAS;EACT;EACA,WAAW,MAAM,cAAc;;KAGrB;EACN;;EACA;EAAiB,KAAK;;EACtB;;EACA;;KAEM,kBAAkB,gBAAgB;;KAGlC;EACV,gBAAgB,OAAO;EACvB,YAAY,gBAAgB;;EAE5B;EACA,OAAO,OAAO,mBAAmB,QAAQ;;cAyP9B,gBACX,cACA,kBAAkB,UAAU,WAAW,UAAU,UAEjD,SAAS,gBAAgB,gBACtB,gBAAgB,UAAU,oBAAoB"}
@@ -77,9 +77,14 @@ const attemptStream = async (c, options, abort) => {
77
77
  if (!res.body) throw new Error("statewire: protocol error: stream response has no body");
78
78
  c.lease = res.headers.get(LEASE_HEADER);
79
79
  const reader = res.body.getReader();
80
+ const cancel = () => {
81
+ reader.cancel().catch(() => {});
82
+ };
83
+ abort.signal.addEventListener("abort", cancel, { once: true });
80
84
  try {
81
85
  return await consume(c, reader, heartbeat, () => abort.signal.aborted);
82
86
  } finally {
87
+ abort.signal.removeEventListener("abort", cancel);
83
88
  reader.cancel().catch(() => {});
84
89
  }
85
90
  };
@@ -1 +1 @@
1
- {"version":3,"file":"sse.js","names":["flushTapSync","resource","ResourceElement","createSseParser","timing","settleFin","classify","handleEnvelope","resolveHeaders","useLink","validateRefusalSyn","AttemptOutcome","Conn","StatewireCmd","StatewireEnvelope","StatewireSyn","Statewire","StatewireClient","StatewireHost","useStatewireRemote","negotiationHeaders","parseNegotiation","StatewireFrame","cmd","protocol","method","params","meta","seq","StatewireRemoteProps","beforeAttach","Promise","clientId","takeAttachLanes","onHello","args","ops","syn","onOps","cmds","onCmds","onRefusal","onIdle","level","onDetach","onFinish","Fin","StatewireSubmitResult","type","ConnectionState","RemoteLink","storageFailed","error","Error","connection","Connection","parked","send","frame","LEASE_HEADER","baseUrl","options","HttpOptions","url","replace","withExtra","headers","Headers","extra","Record","merged","name","value","Object","entries","set","timedFetch","input","init","RequestInit","signal","AbortSignal","timeout","Response","fetch","globalThis","consume","c","reader","ReadableStreamDefaultReader","Uint8Array","heartbeat","AbortController","dead","decoder","TextDecoder","parse","timer","setTimeout","abort","heartbeatTimeoutMs","result","ReadableStreamReadResult","read","clearTimeout","done","data","decode","stream","env","JSON","attemptStream","persistence","ctx","aborted","lease","authStale","has","negotiation","lanes","body","undefined","stringify","headerTimer","res","accept","cache","any","ok","clone","json","catch","fin","httpFin","get","getReader","cancel","postFrame","lifetime","retryAfterMs","header","trim","test","retry","Number","isSafeInteger","reason","status","message","code","refusalSyn","sendFrame","headerRetry","useSseLink","attemptOnce","SyntaxError","startsWith","useStatewireHttp","StatewireHttp","Commands","TState","TCommands"],"sources":["../../src/transports/sse.ts"],"sourcesContent":["import { flushTapSync, resource } from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { createSseParser } from \"./sse-parser\";\nimport { timing } from \"../client/timing\";\nimport {\n settleFin,\n classify,\n handleEnvelope,\n resolveHeaders,\n useLink,\n validateRefusalSyn,\n} from \"../client/connection\";\nimport type {\n AttemptOutcome,\n Conn,\n StatewireCmd,\n StatewireEnvelope,\n StatewireSyn,\n} from \"../client/connection\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireClient } from \"../statewire\";\nimport type { StatewireHost } from \"../host\";\nimport { useStatewireRemote } from \"./remote\";\nimport { negotiationHeaders, parseNegotiation } from \"../wire/protocol\";\n\n/** A client frame's lanes: stamped statements, strictly consecutive per lane. */\nexport type StatewireFrame = {\n cmd?: {\n protocol: string;\n method: string;\n params: unknown[];\n meta?: unknown;\n seq: number;\n }[];\n};\n\nexport type StatewireRemoteProps = {\n beforeAttach?: () => void | Promise<void>;\n clientId: string;\n /** Lanes to ride the attach frame: ephemeral-lane statements, plus durable cmds on no-lease supersession; records the included entries. */\n takeAttachLanes: () => StatewireFrame | null;\n onHello: (args: { ops: unknown[]; syn: StatewireSyn }) => void;\n onOps: (args: { ops: unknown[]; cmds: StatewireCmd[] }) => void;\n onCmds: (cmds: StatewireCmd[]) => void;\n onRefusal: (syn: StatewireSyn) => void;\n onIdle: (level: boolean) => void;\n onDetach: () => void;\n onFinish: (args: StatewireHost.Fin) => void;\n};\n\nexport type StatewireSubmitResult =\n | { type: \"ok\" }\n | { type: \"refusal\"; syn: StatewireSyn }\n | { type: \"stale-lease\" }\n | { type: \"no-lease\" };\n\nexport type ConnectionState = StatewireClient.ConnectionState;\n\n/** The live wire a remote session drives: connection status plus frame submission. */\nexport type RemoteLink = {\n storageFailed: (error: Error) => void;\n connection: StatewireClient.Connection;\n /** A deferred first attach still waiting; `reconnect()` releases it. */\n parked: boolean;\n send: (frame: StatewireFrame) => Promise<StatewireSubmitResult>;\n};\n\nconst LEASE_HEADER = \"Statewire-Lease\";\n\nconst baseUrl = (options: StatewireClient.HttpOptions) =>\n options.url.replace(/\\/$/, \"\");\n\nconst withExtra = (headers: Headers, extra: Record<string, string>) => {\n const merged = new Headers(headers);\n for (const [name, value] of Object.entries(extra)) merged.set(name, value);\n return merged;\n};\n\nconst timedFetch = async (\n options: StatewireClient.HttpOptions,\n input: string,\n init: RequestInit & { signal: AbortSignal },\n timeout: AbortSignal,\n): Promise<Response> => {\n try {\n return await (options.fetch ?? globalThis.fetch)(input, init);\n } catch {\n throw classify(timeout);\n }\n};\n\nconst consume = async (\n c: Conn,\n reader: ReadableStreamDefaultReader<Uint8Array>,\n heartbeat: AbortController,\n dead: () => boolean,\n): Promise<AttemptOutcome> => {\n const decoder = new TextDecoder();\n const parse = createSseParser();\n for (;;) {\n const timer = setTimeout(\n () => heartbeat.abort(),\n timing.heartbeatTimeoutMs,\n );\n let result: ReadableStreamReadResult<Uint8Array>;\n try {\n result = await reader.read();\n } catch {\n throw classify(heartbeat.signal);\n } finally {\n clearTimeout(timer);\n }\n if (result.done) return \"ended\";\n for (const data of parse(decoder.decode(result.value, { stream: true }))) {\n if (dead()) return \"ended\";\n const env = JSON.parse(data) as StatewireEnvelope;\n const done = flushTapSync(() => handleEnvelope(c, env));\n if (done) return done;\n }\n }\n};\n\nconst attemptStream = async (\n c: Conn,\n options: StatewireClient.HttpOptions,\n abort: AbortController,\n): Promise<AttemptOutcome> => {\n const persistence = c.ctx.beforeAttach?.();\n if (persistence) await persistence;\n if (abort.signal.aborted) return \"ended\";\n c.lease = null;\n const headers = await resolveHeaders(options.headers, c.authStale);\n for (const [name, value] of Object.entries(negotiationHeaders(options))) {\n if (!headers.has(name)) headers.set(name, value);\n }\n c.negotiation = parseNegotiation(headers);\n c.authStale = false;\n const lanes = flushTapSync(() => c.ctx.takeAttachLanes());\n const body = lanes === null ? undefined : JSON.stringify(lanes);\n const heartbeat = new AbortController();\n const headerTimer = setTimeout(\n () => heartbeat.abort(),\n timing.heartbeatTimeoutMs,\n );\n let res: Response;\n try {\n res = await timedFetch(\n options,\n `${baseUrl(options)}/stream`,\n {\n method: body === undefined ? \"GET\" : \"POST\",\n headers: withExtra(headers, {\n \"content-type\": \"application/json\",\n \"Statewire-Client-Id\": c.ctx.clientId,\n accept: \"text/event-stream\",\n }),\n ...(body === undefined ? {} : { body }),\n cache: \"no-store\",\n signal: AbortSignal.any([abort.signal, heartbeat.signal]),\n },\n heartbeat.signal,\n );\n } finally {\n clearTimeout(headerTimer);\n }\n if (!res.ok) {\n const body = await res\n .clone()\n .json()\n .catch(() => null);\n return (\n flushTapSync(() =>\n body?.fin === undefined\n ? settleFin(c, httpFin(res))\n : handleEnvelope(c, body),\n ) ?? \"stop\"\n );\n }\n if (!res.body)\n throw new Error(\"statewire: protocol error: stream response has no body\");\n c.lease = res.headers.get(LEASE_HEADER);\n const reader = res.body.getReader();\n try {\n return await consume(c, reader, heartbeat, () => abort.signal.aborted);\n } finally {\n reader.cancel().catch(() => {});\n }\n};\n\nconst postFrame = async (\n c: Conn,\n options: StatewireClient.HttpOptions,\n lease: string,\n frame: StatewireFrame,\n timeout: AbortSignal,\n): Promise<Response> =>\n timedFetch(\n options,\n `${baseUrl(options)}/frames`,\n {\n method: \"POST\",\n headers: withExtra(new Headers(), {\n \"content-type\": \"application/json\",\n [LEASE_HEADER]: lease,\n }),\n body: JSON.stringify(frame),\n signal: AbortSignal.any([\n c.lifetime?.signal ?? AbortSignal.abort(),\n timeout,\n ]),\n },\n timeout,\n );\n\n// Delta-seconds only; anything else reads as absent.\nconst retryAfterMs = (res: Response): number | null => {\n const header = res.headers.get(\"retry-after\")?.trim();\n if (header === undefined || !/^\\d+$/.test(header)) return null;\n const retry = Number(header) * 1000;\n return Number.isSafeInteger(retry) ? retry : null;\n};\n\nconst httpFin = (res: Response): StatewireHost.Fin => ({\n reason:\n res.status === 401\n ? \"unauthorized\"\n : res.status === 403\n ? \"forbidden\"\n : res.status === 429 || res.status === 503\n ? \"reconnect\"\n : res.status >= 400 && res.status < 500\n ? \"protocol-error\"\n : \"error\",\n status: res.status,\n message: `HTTP ${res.status}`,\n ...(res.status === 429 || res.status === 503 ? { code: \"overloaded\" } : {}),\n ...(retryAfterMs(res) !== null ? { retry: retryAfterMs(res)! } : {}),\n});\n\nconst refusalSyn = async (res: Response): Promise<StatewireSyn> => {\n const body = (await res.json().catch(() => null)) as {\n syn?: StatewireSyn;\n } | null;\n const syn = body?.syn;\n if (typeof syn !== \"object\" || syn === null) {\n throw new Error(`HTTP ${res.status}`);\n }\n return validateRefusalSyn(syn);\n};\n\nconst sendFrame = async (\n c: Conn,\n options: StatewireClient.HttpOptions,\n frame: StatewireFrame,\n): Promise<StatewireSubmitResult> => {\n const lease = c.lease;\n if (lease === null) return { type: \"no-lease\" };\n const timeout = AbortSignal.timeout(timing.heartbeatTimeoutMs);\n const res = await postFrame(c, options, lease, frame, timeout);\n if (res.ok) return { type: \"ok\" };\n if (res.status === 429 || res.status === 409) {\n // The syn body retry wins; Retry-After covers 429s minted below the host.\n const headerRetry = res.status === 429 ? retryAfterMs(res) : null;\n const syn: StatewireSyn = await refusalSyn(res).catch((error: unknown) => {\n if (headerRetry === null) throw error;\n return { retry: headerRetry };\n });\n if (syn.retry === undefined && headerRetry !== null)\n syn.retry = headerRetry;\n return { type: \"refusal\", syn };\n }\n if (res.status === 423) return { type: \"stale-lease\" };\n flushTapSync(() => settleFin(c, httpFin(res)));\n c.abort?.abort();\n throw new Error(`HTTP ${res.status}`);\n};\n\nconst useSseLink = (\n options: StatewireClient.HttpOptions,\n ctx: StatewireRemoteProps,\n): RemoteLink =>\n useLink({\n ...options,\n ctx,\n attemptOnce: (c, abort) => attemptStream(c, options, abort),\n send: async (c, frame) => {\n try {\n return await sendFrame(c, options, frame);\n } catch (error) {\n if (\n error instanceof SyntaxError ||\n (error instanceof Error &&\n error.message.startsWith(\"statewire: protocol error:\"))\n ) {\n flushTapSync(() =>\n settleFin(c, { reason: \"protocol-error\", message: error.message }),\n );\n c.abort?.abort();\n }\n throw error;\n }\n },\n });\n\nconst useStatewireHttp = (\n options: StatewireClient.HttpOptions,\n): Statewire<any, any> => useStatewireRemote(options, useSseLink);\n\nexport const StatewireHttp = resource(useStatewireHttp) as <\n TState = any,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireClient.HttpOptions,\n) => ResourceElement<Statewire<TState | undefined, TCommands>>;\n"],"mappings":"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"}
1
+ {"version":3,"file":"sse.js","names":["StatewireDocument","flushTapSync","resource","ResourceElement","createSseParser","timing","settleFin","classify","handleEnvelope","resolveHeaders","useLink","validateRefusalSyn","AttemptOutcome","Conn","StatewireCmd","StatewireEnvelope","StatewireSyn","Statewire","StatewireClient","StatewireHost","useStatewireRemote","negotiationHeaders","parseNegotiation","StatewireFrame","ctx","protocol","Encoded","cmd","method","params","meta","seq","StatewireRemoteProps","beforeAttach","Promise","clientId","takeAttachLanes","onHello","args","ops","syn","onOps","cmds","onCmds","onRefusal","onIdle","level","onDetach","onFinish","Fin","StatewireSubmitResult","type","ConnectionState","RemoteLink","storageFailed","error","Error","connection","Connection","parked","send","frame","LEASE_HEADER","baseUrl","options","HttpOptions","url","replace","withExtra","headers","Headers","extra","Record","merged","name","value","Object","entries","set","timedFetch","input","init","RequestInit","signal","AbortSignal","timeout","Response","fetch","globalThis","consume","c","reader","ReadableStreamDefaultReader","Uint8Array","heartbeat","AbortController","dead","decoder","TextDecoder","parse","timer","setTimeout","abort","heartbeatTimeoutMs","result","ReadableStreamReadResult","read","clearTimeout","done","data","decode","stream","env","JSON","attemptStream","persistence","aborted","lease","authStale","has","negotiation","lanes","body","undefined","stringify","headerTimer","res","accept","cache","any","ok","clone","json","catch","fin","httpFin","get","getReader","cancel","addEventListener","once","removeEventListener","postFrame","lifetime","retryAfterMs","header","trim","test","retry","Number","isSafeInteger","reason","status","message","code","refusalSyn","sendFrame","headerRetry","useSseLink","attemptOnce","SyntaxError","startsWith","useStatewireHttp","StatewireHttp","Commands","TState","TCommands"],"sources":["../../src/transports/sse.ts"],"sourcesContent":["import type { StatewireDocument } from \"../document\";\nimport { flushTapSync, resource } from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { createSseParser } from \"./sse-parser\";\nimport { timing } from \"../client/timing\";\nimport {\n settleFin,\n classify,\n handleEnvelope,\n resolveHeaders,\n useLink,\n validateRefusalSyn,\n} from \"../client/connection\";\nimport type {\n AttemptOutcome,\n Conn,\n StatewireCmd,\n StatewireEnvelope,\n StatewireSyn,\n} from \"../client/connection\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireClient } from \"../statewire\";\nimport type { StatewireHost } from \"../host\";\nimport { useStatewireRemote } from \"./remote\";\nimport { negotiationHeaders, parseNegotiation } from \"../wire/protocol\";\n\n/** A client frame's lanes: stamped statements, strictly consecutive per lane. */\nexport type StatewireFrame = {\n ctx?: ({ protocol: string } & StatewireDocument.Encoded)[];\n cmd?: {\n protocol: string;\n method: string;\n params: unknown[];\n meta?: unknown;\n seq: number;\n }[];\n};\n\nexport type StatewireRemoteProps = {\n beforeAttach?: () => void | Promise<void>;\n clientId: string;\n /** Lanes to ride the attach frame: ephemeral-lane statements, plus durable cmds on no-lease supersession; records the included entries. */\n takeAttachLanes: () => StatewireFrame | null;\n onHello: (args: { ops: unknown[]; syn: StatewireSyn }) => void;\n onOps: (args: { ops: unknown[]; cmds: StatewireCmd[] }) => void;\n onCmds: (cmds: StatewireCmd[]) => void;\n onRefusal: (syn: StatewireSyn) => void;\n onIdle: (level: boolean) => void;\n onDetach: () => void;\n onFinish: (args: StatewireHost.Fin) => void;\n};\n\nexport type StatewireSubmitResult =\n | { type: \"ok\" }\n | { type: \"refusal\"; syn: StatewireSyn }\n | { type: \"stale-lease\" }\n | { type: \"no-lease\" };\n\nexport type ConnectionState = StatewireClient.ConnectionState;\n\n/** The live wire a remote session drives: connection status plus frame submission. */\nexport type RemoteLink = {\n storageFailed: (error: Error) => void;\n connection: StatewireClient.Connection;\n /** A deferred first attach still waiting; `reconnect()` releases it. */\n parked: boolean;\n send: (frame: StatewireFrame) => Promise<StatewireSubmitResult>;\n};\n\nconst LEASE_HEADER = \"Statewire-Lease\";\n\nconst baseUrl = (options: StatewireClient.HttpOptions) =>\n options.url.replace(/\\/$/, \"\");\n\nconst withExtra = (headers: Headers, extra: Record<string, string>) => {\n const merged = new Headers(headers);\n for (const [name, value] of Object.entries(extra)) merged.set(name, value);\n return merged;\n};\n\nconst timedFetch = async (\n options: StatewireClient.HttpOptions,\n input: string,\n init: RequestInit & { signal: AbortSignal },\n timeout: AbortSignal,\n): Promise<Response> => {\n try {\n return await (options.fetch ?? globalThis.fetch)(input, init);\n } catch {\n throw classify(timeout);\n }\n};\n\nconst consume = async (\n c: Conn,\n reader: ReadableStreamDefaultReader<Uint8Array>,\n heartbeat: AbortController,\n dead: () => boolean,\n): Promise<AttemptOutcome> => {\n const decoder = new TextDecoder();\n const parse = createSseParser();\n for (;;) {\n const timer = setTimeout(\n () => heartbeat.abort(),\n timing.heartbeatTimeoutMs,\n );\n let result: ReadableStreamReadResult<Uint8Array>;\n try {\n result = await reader.read();\n } catch {\n throw classify(heartbeat.signal);\n } finally {\n clearTimeout(timer);\n }\n if (result.done) return \"ended\";\n for (const data of parse(decoder.decode(result.value, { stream: true }))) {\n if (dead()) return \"ended\";\n const env = JSON.parse(data) as StatewireEnvelope;\n const done = flushTapSync(() => handleEnvelope(c, env));\n if (done) return done;\n }\n }\n};\n\nconst attemptStream = async (\n c: Conn,\n options: StatewireClient.HttpOptions,\n abort: AbortController,\n): Promise<AttemptOutcome> => {\n const persistence = c.ctx.beforeAttach?.();\n if (persistence) await persistence;\n if (abort.signal.aborted) return \"ended\";\n c.lease = null;\n const headers = await resolveHeaders(options.headers, c.authStale);\n for (const [name, value] of Object.entries(negotiationHeaders(options))) {\n if (!headers.has(name)) headers.set(name, value);\n }\n c.negotiation = parseNegotiation(headers);\n c.authStale = false;\n const lanes = flushTapSync(() => c.ctx.takeAttachLanes());\n const body = lanes === null ? undefined : JSON.stringify(lanes);\n const heartbeat = new AbortController();\n const headerTimer = setTimeout(\n () => heartbeat.abort(),\n timing.heartbeatTimeoutMs,\n );\n let res: Response;\n try {\n res = await timedFetch(\n options,\n `${baseUrl(options)}/stream`,\n {\n method: body === undefined ? \"GET\" : \"POST\",\n headers: withExtra(headers, {\n \"content-type\": \"application/json\",\n \"Statewire-Client-Id\": c.ctx.clientId,\n accept: \"text/event-stream\",\n }),\n ...(body === undefined ? {} : { body }),\n cache: \"no-store\",\n signal: AbortSignal.any([abort.signal, heartbeat.signal]),\n },\n heartbeat.signal,\n );\n } finally {\n clearTimeout(headerTimer);\n }\n if (!res.ok) {\n const body = await res\n .clone()\n .json()\n .catch(() => null);\n return (\n flushTapSync(() =>\n body?.fin === undefined\n ? settleFin(c, httpFin(res))\n : handleEnvelope(c, body),\n ) ?? \"stop\"\n );\n }\n if (!res.body)\n throw new Error(\"statewire: protocol error: stream response has no body\");\n c.lease = res.headers.get(LEASE_HEADER);\n const reader = res.body.getReader();\n const cancel = () => {\n void reader.cancel().catch(() => {});\n };\n abort.signal.addEventListener(\"abort\", cancel, { once: true });\n try {\n return await consume(c, reader, heartbeat, () => abort.signal.aborted);\n } finally {\n abort.signal.removeEventListener(\"abort\", cancel);\n reader.cancel().catch(() => {});\n }\n};\n\nconst postFrame = async (\n c: Conn,\n options: StatewireClient.HttpOptions,\n lease: string,\n frame: StatewireFrame,\n timeout: AbortSignal,\n): Promise<Response> =>\n timedFetch(\n options,\n `${baseUrl(options)}/frames`,\n {\n method: \"POST\",\n headers: withExtra(new Headers(), {\n \"content-type\": \"application/json\",\n [LEASE_HEADER]: lease,\n }),\n body: JSON.stringify(frame),\n signal: AbortSignal.any([\n c.lifetime?.signal ?? AbortSignal.abort(),\n timeout,\n ]),\n },\n timeout,\n );\n\n// Delta-seconds only; anything else reads as absent.\nconst retryAfterMs = (res: Response): number | null => {\n const header = res.headers.get(\"retry-after\")?.trim();\n if (header === undefined || !/^\\d+$/.test(header)) return null;\n const retry = Number(header) * 1000;\n return Number.isSafeInteger(retry) ? retry : null;\n};\n\nconst httpFin = (res: Response): StatewireHost.Fin => ({\n reason:\n res.status === 401\n ? \"unauthorized\"\n : res.status === 403\n ? \"forbidden\"\n : res.status === 429 || res.status === 503\n ? \"reconnect\"\n : res.status >= 400 && res.status < 500\n ? \"protocol-error\"\n : \"error\",\n status: res.status,\n message: `HTTP ${res.status}`,\n ...(res.status === 429 || res.status === 503 ? { code: \"overloaded\" } : {}),\n ...(retryAfterMs(res) !== null ? { retry: retryAfterMs(res)! } : {}),\n});\n\nconst refusalSyn = async (res: Response): Promise<StatewireSyn> => {\n const body = (await res.json().catch(() => null)) as {\n syn?: StatewireSyn;\n } | null;\n const syn = body?.syn;\n if (typeof syn !== \"object\" || syn === null) {\n throw new Error(`HTTP ${res.status}`);\n }\n return validateRefusalSyn(syn);\n};\n\nconst sendFrame = async (\n c: Conn,\n options: StatewireClient.HttpOptions,\n frame: StatewireFrame,\n): Promise<StatewireSubmitResult> => {\n const lease = c.lease;\n if (lease === null) return { type: \"no-lease\" };\n const timeout = AbortSignal.timeout(timing.heartbeatTimeoutMs);\n const res = await postFrame(c, options, lease, frame, timeout);\n if (res.ok) return { type: \"ok\" };\n if (res.status === 429 || res.status === 409) {\n // The syn body retry wins; Retry-After covers 429s minted below the host.\n const headerRetry = res.status === 429 ? retryAfterMs(res) : null;\n const syn: StatewireSyn = await refusalSyn(res).catch((error: unknown) => {\n if (headerRetry === null) throw error;\n return { retry: headerRetry };\n });\n if (syn.retry === undefined && headerRetry !== null)\n syn.retry = headerRetry;\n return { type: \"refusal\", syn };\n }\n if (res.status === 423) return { type: \"stale-lease\" };\n flushTapSync(() => settleFin(c, httpFin(res)));\n c.abort?.abort();\n throw new Error(`HTTP ${res.status}`);\n};\n\nconst useSseLink = (\n options: StatewireClient.HttpOptions,\n ctx: StatewireRemoteProps,\n): RemoteLink =>\n useLink({\n ...options,\n ctx,\n attemptOnce: (c, abort) => attemptStream(c, options, abort),\n send: async (c, frame) => {\n try {\n return await sendFrame(c, options, frame);\n } catch (error) {\n if (\n error instanceof SyntaxError ||\n (error instanceof Error &&\n error.message.startsWith(\"statewire: protocol error:\"))\n ) {\n flushTapSync(() =>\n settleFin(c, { reason: \"protocol-error\", message: error.message }),\n );\n c.abort?.abort();\n }\n throw error;\n }\n },\n });\n\nconst useStatewireHttp = (\n options: StatewireClient.HttpOptions,\n): Statewire<any, any> => useStatewireRemote(options, useSseLink);\n\nexport const StatewireHttp = resource(useStatewireHttp) as <\n TState = any,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireClient.HttpOptions,\n) => ResourceElement<Statewire<TState | undefined, TCommands>>;\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { StatewireFrame } from "./sse.js";
1
2
  import { StatewireEnvelope } from "../client/connection.js";
2
3
  import { Statewire } from "../types.js";
3
4
  import { SocketRecorder, commandFrame, createCounterElement, createSocketRecorder, settle } from "./fixtures.js";
@@ -15,6 +16,7 @@ type MockTransport<TState> = {
15
16
  method: string;
16
17
  params: unknown[];
17
18
  }[];
19
+ contexts: NonNullable<StatewireFrame["ctx"]>;
18
20
  /** Replace the replicated state and broadcast it as a root snapshot op. */
19
21
  setState: (state: TState) => Promise<void>;
20
22
  /** Deliver a bare ack cmd packet on the stream. */
@@ -1 +1 @@
1
- {"version":3,"file":"testing.d.ts","names":[],"sources":["../../src/transports/testing.ts"],"mappings":";;;;;;;cAYa,YAAI;KAIL,cAAc;;EAExB,WAAW,gBAAgB,UAAU;;EAErC;IAAY;IAAkB;IAAgB;;;EAE9C,WAAW,OAAO,WAAW;;EAE7B,UAAU,gBAAgB;;EAE1B,SAAS,aAAa,sBAAsB;;EAE5C,eAAe,UAAU,sBAAsB;;EAE/C,SAAS,gBAAgB,qBAAqB;WACrC;WACA;;cAUE,sBAAuB,QAClC,cAAc,QACd,mBACA;EAAW;MACV,cAAc"}
1
+ {"version":3,"file":"testing.d.ts","names":[],"sources":["../../src/transports/testing.ts"],"mappings":";;;;;;;;cAYa,YAAI;KAIL,cAAc;;EAExB,WAAW,gBAAgB,UAAU;;EAErC;IAAY;IAAkB;IAAgB;;EAC9C,UAAU,YAAY;;EAEtB,WAAW,OAAO,WAAW;;EAE7B,UAAU,gBAAgB;;EAE1B,SAAS,aAAa,sBAAsB;;EAE5C,eAAe,UAAU,sBAAsB;;EAE/C,SAAS,gBAAgB,qBAAqB;WACrC;WACA;;cAUE,sBAAuB,QAClC,cAAc,QACd,mBACA;EAAW;MACV,cAAc"}
@@ -1,4 +1,4 @@
1
- import { WIRE_VERSION } from "../wire/protocol.js";
1
+ import { WIRE_VERSION, parseNegotiation } from "../wire/protocol.js";
2
2
  import { applyOptimistic } from "../util/recordMutations.js";
3
3
  import { StatewireHttp } from "./sse.js";
4
4
  import { openSSEStream, sseFrame } from "./fake-sse.js";
@@ -20,12 +20,14 @@ const createMockTransport = (initialState, protocol = "default", options = {}) =
20
20
  const clients = /* @__PURE__ */ new Map();
21
21
  const streams = /* @__PURE__ */ new Set();
22
22
  const commands = [];
23
+ const contexts = [];
23
24
  const sendEnvelope = (envelope) => {
24
25
  for (const stream of streams) stream.send(sseFrame(envelope));
25
26
  return tick();
26
27
  };
27
28
  const admit = (id, frame) => {
28
29
  const rec = clients.get(id);
30
+ contexts.push(...frame.ctx ?? []);
29
31
  for (const statement of frame.cmd ?? []) {
30
32
  if (statement.seq <= (rec[statement.protocol] ?? 0)) continue;
31
33
  rec[statement.protocol] = statement.seq;
@@ -39,12 +41,16 @@ const createMockTransport = (initialState, protocol = "default", options = {}) =
39
41
  const fakeFetch = (async (input, init) => {
40
42
  if (finished) throw new TypeError("fetch failed (finished)");
41
43
  const url = String(input);
42
- const id = new Headers(init?.headers).get("Statewire-Client-Id") ?? "";
44
+ const headers = new Headers(init?.headers);
45
+ const id = headers.get("Statewire-Client-Id") ?? "";
43
46
  if (url.endsWith("/stream")) {
44
47
  clientId = id;
45
48
  if (!clients.has(id)) clients.set(id, {});
46
49
  const rec = clients.get(id);
47
50
  const ephemeral = ephemeralLanes();
51
+ const offers = parseNegotiation(headers).applicationProtocols;
52
+ const applicationVersion = offers === "*" ? WIRE_VERSION : offers.find((offer) => offer.name === protocol)?.version;
53
+ if (applicationVersion === void 0) throw new Error("mock protocol was not offered");
48
54
  for (const lane of ephemeral) delete rec[lane];
49
55
  const frame = JSON.parse(String(init?.body ?? "{}"));
50
56
  admit(id, frame);
@@ -60,11 +66,11 @@ const createMockTransport = (initialState, protocol = "default", options = {}) =
60
66
  version: WIRE_VERSION,
61
67
  protocols: [{
62
68
  name: protocol,
63
- version: WIRE_VERSION,
69
+ version: applicationVersion,
64
70
  seq: rec[protocol] ?? 0
65
71
  }, ...ephemeral.map((name) => ({
66
72
  name,
67
- version: WIRE_VERSION,
73
+ version: applicationVersion,
68
74
  record: "ephemeral",
69
75
  seq: 0
70
76
  }))]
@@ -84,6 +90,7 @@ const createMockTransport = (initialState, protocol = "default", options = {}) =
84
90
  fetch: fakeFetch
85
91
  }),
86
92
  commands,
93
+ contexts,
87
94
  setState: (next) => {
88
95
  state = next;
89
96
  return sendEnvelope({ ops: [{
@@ -1 +1 @@
1
- {"version":3,"file":"testing.js","names":["ResourceElement","StatewireEnvelope","StatewireHttp","StatewireFrame","Statewire","openSSEStream","sseFrame","FakeStreamHandle","WIRE_VERSION","tick","Promise","r","setTimeout","applyOptimistic","MockTransport","transport","TState","commands","protocol","method","params","setState","state","applied","seq","settle","payload","sendEnvelope","envelope","finish","reason","message","clientId","attached","mockCount","createMockTransport","initialState","options","ephemeral","ephemeralLanes","map","child","finished","clients","Map","Record","streams","Set","stream","send","admit","id","frame","rec","get","statement","cmd","push","fakeFetch","input","RequestInfo","URL","init","RequestInit","Response","TypeError","url","String","headers","Headers","endsWith","has","set","lane","JSON","parse","body","handle","ops","op","main","value","syn","version","protocols","name","record","const","signal","undefined","json","status","fetch","next","path","type","fin","close","size"],"sources":["../../src/transports/testing.ts"],"sourcesContent":["import type { ResourceElement } from \"@assistant-ui/tap\";\nimport type { StatewireEnvelope } from \"../client/connection\";\nimport { StatewireHttp } from \"./sse\";\nimport type { StatewireFrame } from \"./sse\";\nimport type { Statewire } from \"../types\";\nimport { openSSEStream, sseFrame } from \"./fake-sse\";\nimport type { FakeStreamHandle } from \"./fake-sse\";\nimport { WIRE_VERSION } from \"../wire/protocol\";\n\nexport * from \"./fixtures\";\n\n/** Let queued microtasks and op batches drain through the statewire client. */\nexport const tick = () => new Promise<void>((r) => setTimeout(r, 0));\n\nexport { applyOptimistic } from \"../util/recordMutations\";\n\nexport type MockTransport<TState> = {\n /** Pass as the `transport:` option of useStatewire / Harness. */\n transport: ResourceElement<Statewire<TState | undefined, any>>;\n /** Captured statements in arrival order. */\n commands: { protocol: string; method: string; params: unknown[] }[];\n /** Replace the replicated state and broadcast it as a root snapshot op. */\n setState: (state: TState) => Promise<void>;\n /** Deliver a bare ack cmd packet on the stream. */\n applied: (seq: number) => Promise<void>;\n /** Deliver a terminal result cmd packet on the stream. */\n settle: (seq: number, payload?: unknown) => Promise<void>;\n /** Deliver an arbitrary envelope on the stream. */\n sendEnvelope: (envelope: StatewireEnvelope) => Promise<void>;\n /** End the stream with a fin; further attaches fail like a dead server. */\n finish: (reason: string, message?: string) => Promise<void>;\n readonly clientId: string;\n readonly attached: boolean;\n};\n\n/**\n * A mock statewire backend: serves `initialState` as the attach snapshot,\n * records submitted frames (receipt only — acks and results are delivered\n * from test code), and exposes the wire as a transport ResourceElement.\n */\nlet mockCount = 0;\n\nexport const createMockTransport = <TState>(\n initialState: TState,\n protocol = \"default\",\n options: { ephemeral?: readonly string[] } = {},\n): MockTransport<TState> => {\n // Read per attach: a test can grow the lane set between connects.\n const ephemeralLanes = () =>\n (options.ephemeral ?? []).map((child) => `${protocol}/${child}`);\n let state: unknown = initialState;\n let clientId = \"\";\n let finished = false;\n const clients = new Map<string, Record<string, number>>();\n const streams = new Set<FakeStreamHandle>();\n const commands: MockTransport<unknown>[\"commands\"] = [];\n\n const sendEnvelope = (envelope: StatewireEnvelope) => {\n for (const stream of streams) stream.send(sseFrame(envelope));\n return tick();\n };\n\n const admit = (id: string, frame: StatewireFrame) => {\n const rec = clients.get(id)!;\n for (const statement of frame.cmd ?? []) {\n if (statement.seq <= (rec[statement.protocol] ?? 0)) continue;\n rec[statement.protocol] = statement.seq;\n commands.push({\n protocol: statement.protocol,\n method: statement.method,\n params: statement.params,\n });\n }\n };\n\n const fakeFetch = (async (\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> => {\n if (finished) throw new TypeError(\"fetch failed (finished)\");\n const url = String(input);\n const headers = new Headers(init?.headers);\n const id = headers.get(\"Statewire-Client-Id\") ?? \"\";\n if (url.endsWith(\"/stream\")) {\n clientId = id;\n if (!clients.has(id)) clients.set(id, {});\n const rec = clients.get(id)!;\n const ephemeral = ephemeralLanes();\n for (const lane of ephemeral) delete rec[lane];\n const frame = JSON.parse(String(init?.body ?? \"{}\")) as StatewireFrame;\n admit(id, frame);\n return openSSEStream(\n streams,\n id,\n (handle) => {\n handle.send(\n sseFrame({\n ops: [{ op: \"mount\", protocol, main: true, value: state }],\n syn: {\n version: WIRE_VERSION,\n protocols: [\n {\n name: protocol,\n version: WIRE_VERSION,\n seq: rec[protocol] ?? 0,\n },\n ...ephemeral.map((name) => ({\n name,\n version: WIRE_VERSION,\n record: \"ephemeral\" as const,\n seq: 0,\n })),\n ],\n },\n }),\n );\n },\n init?.signal ?? undefined,\n { \"Statewire-Lease\": \"mock-lease\" },\n );\n }\n if (url.endsWith(\"/frames\")) {\n admit(clientId, JSON.parse(String(init?.body ?? \"{}\")) as StatewireFrame);\n return Response.json({});\n }\n return new Response(null, { status: 404 });\n }) as typeof fetch;\n\n return {\n transport: StatewireHttp({\n url: `http://mock-${++mockCount}/api`,\n fetch: fakeFetch,\n }),\n commands,\n setState: (next) => {\n state = next;\n return sendEnvelope({ ops: [{ op: \"replace\", path: [], value: next }] });\n },\n applied: (seq) =>\n sendEnvelope({ cmd: [{ protocol, seq, type: \"applied\" }] }),\n settle: (seq, payload) =>\n sendEnvelope({\n cmd: [\n {\n protocol,\n seq,\n type: \"result\",\n ...(payload !== undefined && { payload }),\n },\n ],\n }),\n sendEnvelope,\n finish: async (reason, message) => {\n await sendEnvelope({\n fin: { reason, ...(message !== undefined && { message }) },\n });\n finished = true;\n for (const stream of [...streams]) stream.close();\n await tick();\n },\n get clientId() {\n return clientId;\n },\n get attached() {\n return streams.size > 0;\n },\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"testing.js","names":["ResourceElement","StatewireEnvelope","StatewireHttp","StatewireFrame","Statewire","openSSEStream","sseFrame","FakeStreamHandle","WIRE_VERSION","parseNegotiation","tick","Promise","r","setTimeout","applyOptimistic","MockTransport","transport","TState","commands","protocol","method","params","contexts","NonNullable","setState","state","applied","seq","settle","payload","sendEnvelope","envelope","finish","reason","message","clientId","attached","mockCount","createMockTransport","initialState","options","ephemeral","ephemeralLanes","map","child","finished","clients","Map","Record","streams","Set","stream","send","admit","id","frame","rec","get","push","ctx","statement","cmd","fakeFetch","input","RequestInfo","URL","init","RequestInit","Response","TypeError","url","String","headers","Headers","endsWith","has","set","offers","applicationProtocols","applicationVersion","find","offer","name","version","undefined","Error","lane","JSON","parse","body","handle","ops","op","main","value","syn","protocols","record","const","signal","json","status","fetch","next","path","type","fin","close","size"],"sources":["../../src/transports/testing.ts"],"sourcesContent":["import type { ResourceElement } from \"@assistant-ui/tap\";\nimport type { StatewireEnvelope } from \"../client/connection\";\nimport { StatewireHttp } from \"./sse\";\nimport type { StatewireFrame } from \"./sse\";\nimport type { Statewire } from \"../types\";\nimport { openSSEStream, sseFrame } from \"./fake-sse\";\nimport type { FakeStreamHandle } from \"./fake-sse\";\nimport { WIRE_VERSION, parseNegotiation } from \"../wire/protocol\";\n\nexport * from \"./fixtures\";\n\n/** Let queued microtasks and op batches drain through the statewire client. */\nexport const tick = () => new Promise<void>((r) => setTimeout(r, 0));\n\nexport { applyOptimistic } from \"../util/recordMutations\";\n\nexport type MockTransport<TState> = {\n /** Pass as the `transport:` option of useStatewire / Harness. */\n transport: ResourceElement<Statewire<TState | undefined, any>>;\n /** Captured statements in arrival order. */\n commands: { protocol: string; method: string; params: unknown[] }[];\n contexts: NonNullable<StatewireFrame[\"ctx\"]>;\n /** Replace the replicated state and broadcast it as a root snapshot op. */\n setState: (state: TState) => Promise<void>;\n /** Deliver a bare ack cmd packet on the stream. */\n applied: (seq: number) => Promise<void>;\n /** Deliver a terminal result cmd packet on the stream. */\n settle: (seq: number, payload?: unknown) => Promise<void>;\n /** Deliver an arbitrary envelope on the stream. */\n sendEnvelope: (envelope: StatewireEnvelope) => Promise<void>;\n /** End the stream with a fin; further attaches fail like a dead server. */\n finish: (reason: string, message?: string) => Promise<void>;\n readonly clientId: string;\n readonly attached: boolean;\n};\n\n/**\n * A mock statewire backend: serves `initialState` as the attach snapshot,\n * records submitted frames (receipt only — acks and results are delivered\n * from test code), and exposes the wire as a transport ResourceElement.\n */\nlet mockCount = 0;\n\nexport const createMockTransport = <TState>(\n initialState: TState,\n protocol = \"default\",\n options: { ephemeral?: readonly string[] } = {},\n): MockTransport<TState> => {\n // Read per attach: a test can grow the lane set between connects.\n const ephemeralLanes = () =>\n (options.ephemeral ?? []).map((child) => `${protocol}/${child}`);\n let state: unknown = initialState;\n let clientId = \"\";\n let finished = false;\n const clients = new Map<string, Record<string, number>>();\n const streams = new Set<FakeStreamHandle>();\n const commands: MockTransport<unknown>[\"commands\"] = [];\n const contexts: MockTransport<unknown>[\"contexts\"] = [];\n\n const sendEnvelope = (envelope: StatewireEnvelope) => {\n for (const stream of streams) stream.send(sseFrame(envelope));\n return tick();\n };\n\n const admit = (id: string, frame: StatewireFrame) => {\n const rec = clients.get(id)!;\n contexts.push(...(frame.ctx ?? []));\n for (const statement of frame.cmd ?? []) {\n if (statement.seq <= (rec[statement.protocol] ?? 0)) continue;\n rec[statement.protocol] = statement.seq;\n commands.push({\n protocol: statement.protocol,\n method: statement.method,\n params: statement.params,\n });\n }\n };\n\n const fakeFetch = (async (\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> => {\n if (finished) throw new TypeError(\"fetch failed (finished)\");\n const url = String(input);\n const headers = new Headers(init?.headers);\n const id = headers.get(\"Statewire-Client-Id\") ?? \"\";\n if (url.endsWith(\"/stream\")) {\n clientId = id;\n if (!clients.has(id)) clients.set(id, {});\n const rec = clients.get(id)!;\n const ephemeral = ephemeralLanes();\n const offers = parseNegotiation(headers).applicationProtocols;\n const applicationVersion =\n offers === \"*\"\n ? WIRE_VERSION\n : offers.find((offer) => offer.name === protocol)?.version;\n if (applicationVersion === undefined)\n throw new Error(\"mock protocol was not offered\");\n for (const lane of ephemeral) delete rec[lane];\n const frame = JSON.parse(String(init?.body ?? \"{}\")) as StatewireFrame;\n admit(id, frame);\n return openSSEStream(\n streams,\n id,\n (handle) => {\n handle.send(\n sseFrame({\n ops: [{ op: \"mount\", protocol, main: true, value: state }],\n syn: {\n version: WIRE_VERSION,\n protocols: [\n {\n name: protocol,\n version: applicationVersion,\n seq: rec[protocol] ?? 0,\n },\n ...ephemeral.map((name) => ({\n name,\n version: applicationVersion,\n record: \"ephemeral\" as const,\n seq: 0,\n })),\n ],\n },\n }),\n );\n },\n init?.signal ?? undefined,\n { \"Statewire-Lease\": \"mock-lease\" },\n );\n }\n if (url.endsWith(\"/frames\")) {\n admit(clientId, JSON.parse(String(init?.body ?? \"{}\")) as StatewireFrame);\n return Response.json({});\n }\n return new Response(null, { status: 404 });\n }) as typeof fetch;\n\n return {\n transport: StatewireHttp({\n url: `http://mock-${++mockCount}/api`,\n fetch: fakeFetch,\n }),\n commands,\n contexts,\n setState: (next) => {\n state = next;\n return sendEnvelope({ ops: [{ op: \"replace\", path: [], value: next }] });\n },\n applied: (seq) =>\n sendEnvelope({ cmd: [{ protocol, seq, type: \"applied\" }] }),\n settle: (seq, payload) =>\n sendEnvelope({\n cmd: [\n {\n protocol,\n seq,\n type: \"result\",\n ...(payload !== undefined && { payload }),\n },\n ],\n }),\n sendEnvelope,\n finish: async (reason, message) => {\n await sendEnvelope({\n fin: { reason, ...(message !== undefined && { message }) },\n });\n finished = true;\n for (const stream of [...streams]) stream.close();\n await tick();\n },\n get clientId() {\n return clientId;\n },\n get attached() {\n return streams.size > 0;\n },\n };\n};\n"],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -5,6 +5,7 @@ import { StatewireClient } from "./statewire.js";
5
5
  import { StatewirePath } from "./wire/ops.js";
6
6
  //#region src/types.d.ts
7
7
  declare namespace Statewire {
8
+ type Context = Readonly<Record<string, unknown>>;
8
9
  type Commands = Record<string, (...args: never[]) => unknown>;
9
10
  type Command<TCommands extends Commands = Commands> = { [K in keyof TCommands]: {
10
11
  method: K;
@@ -49,6 +50,9 @@ interface Statewire<TState = any, TCommands extends Statewire.Commands = Statewi
49
50
  state: TState;
50
51
  documents?: StatewireDocuments.Source;
51
52
  selection?: StatewireProtocol.Selection | undefined;
53
+ setContext?: (protocol: string, value: Statewire.Context) => void;
54
+ validateContext?: (protocol: string, value: Statewire.Context, attach: StatewireHost.Attach) => void;
55
+ contextChanged?: (protocol: string, value: Statewire.Context, attach: StatewireHost.Attach) => void;
52
56
  invoke?: (protocol: string, method: string, params: readonly unknown[]) => Promise<unknown>;
53
57
  commands: Statewire.CommandsProxy<TCommands>;
54
58
  connection?: StatewireClient.Connection;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;kBAMiB;OACH,WAAW,mBAAmB;OAE9B,QAAQ,kBAAkB,WAAW,eAC9C,WAAW;IAAc,QAAQ;IAAG,QAAQ,WAAW,UAAU;aAC5D;OAEI,cAAc,kBAAkB,eACzC,WAAW,gBACP,MAAM,WAAW,UAAU,QAC3B,QAAQ,QAAQ,WAAW,UAAU;OAGhC,cAAc,kBAAkB,WAAW,eACpD,WAAW;aACD;IACT,QAAQ;IACR,QAAQ,WAAW,UAAU;IAC7B;IACA;IACA;IACA,UAAU,gBAAgB;aAEtB;;OAGI;IACV;;IAEA,mBAAmB;;IAEnB,cAAc;;OAGJ;IACN;IAAe,MAAM;IAAe;;IACpC;IAAW,MAAM;IAAe;;IAChC;IAAc,MAAM;;OAEd;IACV,YAAY;IACZ,eAAe;;;UAIF,UACf,cACA,kBAAkB,UAAU,WAAW,UAAU;EAEjD,OAAO;EACP,YAAY,mBAAmB;EAC/B,YAAY,kBAAkB;EAC9B,UACE,kBACA,gBACA,+BACG;EACL,UAAU,UAAU,cAAc;EAClC,aAAa,gBAAgB;EAC7B,SAAS,UAAU;;EAEnB;EACA;aAAkB;;;EAElB,aAAa,QAAQ,cAAc;;EAEnC,YAAY,QAAQ,cAAc;;EAElC;IACE,qBAAqB,gBAAgB;IACrC,QAAQ,OAAO,gBAAgB,kBAAkB;IACjD,UAAU,OAAO,gBAAgB;;;cAuBxB,2BAA2B;WAC7B,SAAS,gBAAgB;EAEtB,YAAA,SAAS,gBAAgB"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;kBAMiB;OACH,UAAU,SAAS;OACnB,WAAW,mBAAmB;OAE9B,QAAQ,kBAAkB,WAAW,eAC9C,WAAW;IAAc,QAAQ;IAAG,QAAQ,WAAW,UAAU;aAC5D;OAEI,cAAc,kBAAkB,eACzC,WAAW,gBACP,MAAM,WAAW,UAAU,QAC3B,QAAQ,QAAQ,WAAW,UAAU;OAGhC,cAAc,kBAAkB,WAAW,eACpD,WAAW;aACD;IACT,QAAQ;IACR,QAAQ,WAAW,UAAU;IAC7B;IACA;IACA;IACA,UAAU,gBAAgB;aAEtB;;OAGI;IACV;;IAEA,mBAAmB;;IAEnB,cAAc;;OAGJ;IACN;IAAe,MAAM;IAAe;;IACpC;IAAW,MAAM;IAAe;;IAChC;IAAc,MAAM;;OAEd;IACV,YAAY;IACZ,eAAe;;;UAIF,UACf,cACA,kBAAkB,UAAU,WAAW,UAAU;EAEjD,OAAO;EACP,YAAY,mBAAmB;EAC/B,YAAY,kBAAkB;EAC9B,cAAc,kBAAkB,OAAO,UAAU;EACjD,mBACE,kBACA,OAAO,UAAU,SACjB,QAAQ,cAAc;EAExB,kBACE,kBACA,OAAO,UAAU,SACjB,QAAQ,cAAc;EAExB,UACE,kBACA,gBACA,+BACG;EACL,UAAU,UAAU,cAAc;EAClC,aAAa,gBAAgB;EAC7B,SAAS,UAAU;;EAEnB;EACA;aAAkB;;;EAElB,aAAa,QAAQ,cAAc;;EAEnC,YAAY,QAAQ,cAAc;;EAElC;IACE,qBAAqB,gBAAgB;IACrC,QAAQ,OAAO,gBAAgB,kBAAkB;IACjD,UAAU,OAAO,gBAAgB;;;cAuBxB,2BAA2B;WAC7B,SAAS,gBAAgB;EAEtB,YAAA,SAAS,gBAAgB"}
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":["StatewireDocuments","StatewireHost","StatewireProtocol","StatewireClient","StatewirePath","Statewire","Commands","Record","args","Command","TCommands","method","K","params","Parameters","CommandsProxy","Promise","Awaited","ReturnType","CommandUpdate","protocol","seq","status","result","failure","SendFailure","StateSource","snapshot","takeChanged","setOnChange","listener","Op","op","path","value","Changes","isChangedAt","getChangedKeys","state","TState","documents","Source","selection","Selection","invoke","commands","connection","Connection","source","isBusy","work","isIdle","attaching","attach","Attach","detached","failures","list","FailedCommand","retry","entry","discard","describe","fate","code","message","cause","StatewireSendError","Error","constructor","name"],"sources":["../src/types.ts"],"sourcesContent":["import type { StatewireDocuments } from \"./wire/documents\";\nimport type { StatewireHost } from \"./host\";\nimport type { StatewireProtocol } from \"./wire/protocol\";\nimport type { StatewireClient } from \"./statewire\";\nimport type { StatewirePath } from \"./wire/ops\";\n\nexport namespace Statewire {\n export type Commands = Record<string, (...args: never[]) => unknown>;\n\n export type Command<TCommands extends Commands = Commands> = {\n [K in keyof TCommands]: { method: K; params: Parameters<TCommands[K]> };\n }[keyof TCommands];\n\n export type CommandsProxy<TCommands extends Commands> = {\n [K in keyof TCommands]: (\n ...args: Parameters<TCommands[K]>\n ) => Promise<Awaited<ReturnType<TCommands[K]>>>;\n };\n\n export type CommandUpdate<TCommands extends Commands = Commands> = {\n [K in keyof TCommands]: {\n readonly protocol?: string;\n method: K;\n params: Parameters<TCommands[K]>;\n seq: number;\n status: \"queued\" | \"posted\" | \"applied\" | \"settled\";\n result?: unknown;\n failure?: StatewireClient.SendFailure;\n };\n }[keyof TCommands];\n\n /** Push-based canonical state feed a statewire hands to the client core. */\n export type StateSource = {\n snapshot: () => unknown;\n /** Drain the paths changed since the last take (`[]` entry = whole state). */\n takeChanged: () => StatewirePath[];\n /** Synchronous, per change; the consumer owns coalescing. */\n setOnChange: (listener: (() => void) | null) => void;\n };\n\n export type Op =\n | { op: \"replace\"; path: StatewirePath; value: unknown }\n | { op: \"add\"; path: StatewirePath; value: unknown }\n | { op: \"remove\"; path: StatewirePath };\n\n export type Changes = {\n isChangedAt(path: readonly (string | number)[]): boolean;\n getChangedKeys(path: readonly (string | number)[]): string[];\n };\n}\n\nexport interface Statewire<\n TState = any,\n TCommands extends Statewire.Commands = Statewire.Commands,\n> {\n state: TState;\n documents?: StatewireDocuments.Source;\n selection?: StatewireProtocol.Selection | undefined;\n invoke?: (\n protocol: string,\n method: string,\n params: readonly unknown[],\n ) => Promise<unknown>;\n commands: Statewire.CommandsProxy<TCommands>;\n connection?: StatewireClient.Connection;\n source?: Statewire.StateSource;\n /** Negation of the server's broadcast idle level; absent means always busy. */\n isBusy?: boolean;\n work?: { readonly isIdle: boolean };\n /** Called by the serving host once an attach is registered, before its hello: documents mounted here ride the hello. */\n attaching?: (attach: StatewireHost.Attach) => void;\n /** Called by the serving host when an attach ends; its ephemeral documents and includes are gone. */\n detached?: (attach: StatewireHost.Attach) => void;\n /** The retained failed-send lane; present on transports with a durable session. */\n failures?: {\n list: () => readonly StatewireClient.FailedCommand[];\n retry: (entry: StatewireClient.FailedCommand) => Promise<unknown>;\n discard: (entry: StatewireClient.FailedCommand) => void;\n };\n}\n\nconst describe = (failure: StatewireClient.SendFailure): string => {\n switch (failure.fate) {\n case \"rejected\":\n return `statewire: command rejected${failure.code ? ` (${failure.code})` : \"\"}${failure.message ? `: ${failure.message}` : \"\"}`;\n case \"aborted\":\n return `statewire: command aborted (${failure.cause})`;\n case \"lost\":\n return `statewire: command lost (${failure.cause})`;\n case \"failed\":\n return failure.message\n ? `statewire: command failed: ${failure.message}`\n : \"statewire: command failed\";\n case \"crashed\":\n return \"statewire: command crashed\";\n case \"expired\":\n return `statewire: command result expired (${failure.cause})`;\n }\n};\n\nexport class StatewireSendError extends Error {\n readonly failure: StatewireClient.SendFailure;\n\n constructor(failure: StatewireClient.SendFailure) {\n super(describe(failure));\n this.name = \"StatewireSendError\";\n this.failure = failure;\n }\n}\n"],"mappings":";AAiFA,MAAM8D,YAAYtC,YAAiD;CACjE,QAAQA,QAAQuC,MAAhB;EACE,KAAK,YACH,OAAO,8BAA8BvC,QAAQwC,OAAO,KAAKxC,QAAQwC,KAAI,KAAM,KAAKxC,QAAQyC,UAAU,KAAKzC,QAAQyC,YAAY;EAC7H,KAAK,WACH,OAAO,+BAA+BzC,QAAQ0C,MAAK;EACrD,KAAK,QACH,OAAO,4BAA4B1C,QAAQ0C,MAAK;EAClD,KAAK,UACH,OAAO1C,QAAQyC,UACX,8BAA8BzC,QAAQyC,YACtC;EACN,KAAK,WACH,OAAO;EACT,KAAK,WACH,OAAO,sCAAsCzC,QAAQ0C,MAAK;CAC9D;AACF;AAEA,IAAaC,qBAAb,cAAwCC,MAAM;CAC5C;CAEAC,YAAY7C,SAAsC;EAChD,MAAMsC,SAAStC,OAAO,CAAC;EACvB,KAAK8C,OAAO;EACZ,KAAK9C,UAAUA;CACjB;AACF"}
1
+ {"version":3,"file":"types.js","names":["StatewireDocuments","StatewireHost","StatewireProtocol","StatewireClient","StatewirePath","Statewire","Context","Readonly","Record","Commands","args","Command","TCommands","method","K","params","Parameters","CommandsProxy","Promise","Awaited","ReturnType","CommandUpdate","protocol","seq","status","result","failure","SendFailure","StateSource","snapshot","takeChanged","setOnChange","listener","Op","op","path","value","Changes","isChangedAt","getChangedKeys","state","TState","documents","Source","selection","Selection","setContext","validateContext","attach","Attach","contextChanged","invoke","commands","connection","Connection","source","isBusy","work","isIdle","attaching","detached","failures","list","FailedCommand","retry","entry","discard","describe","fate","code","message","cause","StatewireSendError","Error","constructor","name"],"sources":["../src/types.ts"],"sourcesContent":["import type { StatewireDocuments } from \"./wire/documents\";\nimport type { StatewireHost } from \"./host\";\nimport type { StatewireProtocol } from \"./wire/protocol\";\nimport type { StatewireClient } from \"./statewire\";\nimport type { StatewirePath } from \"./wire/ops\";\n\nexport namespace Statewire {\n export type Context = Readonly<Record<string, unknown>>;\n export type Commands = Record<string, (...args: never[]) => unknown>;\n\n export type Command<TCommands extends Commands = Commands> = {\n [K in keyof TCommands]: { method: K; params: Parameters<TCommands[K]> };\n }[keyof TCommands];\n\n export type CommandsProxy<TCommands extends Commands> = {\n [K in keyof TCommands]: (\n ...args: Parameters<TCommands[K]>\n ) => Promise<Awaited<ReturnType<TCommands[K]>>>;\n };\n\n export type CommandUpdate<TCommands extends Commands = Commands> = {\n [K in keyof TCommands]: {\n readonly protocol?: string;\n method: K;\n params: Parameters<TCommands[K]>;\n seq: number;\n status: \"queued\" | \"posted\" | \"applied\" | \"settled\";\n result?: unknown;\n failure?: StatewireClient.SendFailure;\n };\n }[keyof TCommands];\n\n /** Push-based canonical state feed a statewire hands to the client core. */\n export type StateSource = {\n snapshot: () => unknown;\n /** Drain the paths changed since the last take (`[]` entry = whole state). */\n takeChanged: () => StatewirePath[];\n /** Synchronous, per change; the consumer owns coalescing. */\n setOnChange: (listener: (() => void) | null) => void;\n };\n\n export type Op =\n | { op: \"replace\"; path: StatewirePath; value: unknown }\n | { op: \"add\"; path: StatewirePath; value: unknown }\n | { op: \"remove\"; path: StatewirePath };\n\n export type Changes = {\n isChangedAt(path: readonly (string | number)[]): boolean;\n getChangedKeys(path: readonly (string | number)[]): string[];\n };\n}\n\nexport interface Statewire<\n TState = any,\n TCommands extends Statewire.Commands = Statewire.Commands,\n> {\n state: TState;\n documents?: StatewireDocuments.Source;\n selection?: StatewireProtocol.Selection | undefined;\n setContext?: (protocol: string, value: Statewire.Context) => void;\n validateContext?: (\n protocol: string,\n value: Statewire.Context,\n attach: StatewireHost.Attach,\n ) => void;\n contextChanged?: (\n protocol: string,\n value: Statewire.Context,\n attach: StatewireHost.Attach,\n ) => void;\n invoke?: (\n protocol: string,\n method: string,\n params: readonly unknown[],\n ) => Promise<unknown>;\n commands: Statewire.CommandsProxy<TCommands>;\n connection?: StatewireClient.Connection;\n source?: Statewire.StateSource;\n /** Negation of the server's broadcast idle level; absent means always busy. */\n isBusy?: boolean;\n work?: { readonly isIdle: boolean };\n /** Called by the serving host once an attach is registered, before its hello: documents mounted here ride the hello. */\n attaching?: (attach: StatewireHost.Attach) => void;\n /** Called by the serving host when an attach ends; its ephemeral documents and includes are gone. */\n detached?: (attach: StatewireHost.Attach) => void;\n /** The retained failed-send lane; present on transports with a durable session. */\n failures?: {\n list: () => readonly StatewireClient.FailedCommand[];\n retry: (entry: StatewireClient.FailedCommand) => Promise<unknown>;\n discard: (entry: StatewireClient.FailedCommand) => void;\n };\n}\n\nconst describe = (failure: StatewireClient.SendFailure): string => {\n switch (failure.fate) {\n case \"rejected\":\n return `statewire: command rejected${failure.code ? ` (${failure.code})` : \"\"}${failure.message ? `: ${failure.message}` : \"\"}`;\n case \"aborted\":\n return `statewire: command aborted (${failure.cause})`;\n case \"lost\":\n return `statewire: command lost (${failure.cause})`;\n case \"failed\":\n return failure.message\n ? `statewire: command failed: ${failure.message}`\n : \"statewire: command failed\";\n case \"crashed\":\n return \"statewire: command crashed\";\n case \"expired\":\n return `statewire: command result expired (${failure.cause})`;\n }\n};\n\nexport class StatewireSendError extends Error {\n readonly failure: StatewireClient.SendFailure;\n\n constructor(failure: StatewireClient.SendFailure) {\n super(describe(failure));\n this.name = \"StatewireSendError\";\n this.failure = failure;\n }\n}\n"],"mappings":";AA6FA,MAAMmE,YAAYzC,YAAiD;CACjE,QAAQA,QAAQ0C,MAAhB;EACE,KAAK,YACH,OAAO,8BAA8B1C,QAAQ2C,OAAO,KAAK3C,QAAQ2C,KAAI,KAAM,KAAK3C,QAAQ4C,UAAU,KAAK5C,QAAQ4C,YAAY;EAC7H,KAAK,WACH,OAAO,+BAA+B5C,QAAQ6C,MAAK;EACrD,KAAK,QACH,OAAO,4BAA4B7C,QAAQ6C,MAAK;EAClD,KAAK,UACH,OAAO7C,QAAQ4C,UACX,8BAA8B5C,QAAQ4C,YACtC;EACN,KAAK,WACH,OAAO;EACT,KAAK,WACH,OAAO,sCAAsC5C,QAAQ6C,MAAK;CAC9D;AACF;AAEA,IAAaC,qBAAb,cAAwCC,MAAM;CAC5C;CAEAC,YAAYhD,SAAsC;EAChD,MAAMyC,SAASzC,OAAO,CAAC;EACvB,KAAKiD,OAAO;EACZ,KAAKjD,UAAUA;CACjB;AACF"}
@@ -0,0 +1,14 @@
1
+ import { StatewireDocument } from "../document.js";
2
+ import { Statewire } from "../types.js";
3
+ import "../index.js";
4
+ //#region src/wire/context.d.ts
5
+ declare const contextValue: (encoded: unknown) => Statewire.Context;
6
+ declare const encodeContext: (protocol: string, value: Statewire.Context) => {
7
+ value: StatewireDocument.Value;
8
+ meta?: StatewireDocument.MetaEntry[];
9
+ protocol: string;
10
+ };
11
+ declare const captureContext: (value: Statewire.Context) => Statewire.Context;
12
+ //#endregion
13
+ export { captureContext, contextValue, encodeContext };
14
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","names":[],"sources":["../../src/wire/context.ts"],"mappings":";;;;cAWa,eAAgB,qBAAmB,UAAU;cAa7C,gBAAiB,kBAAkB,OAAO,UAAU;;;;;cAcpD,iBAAkB,OAAO,UAAU,YAAU,UAAU"}