statewire 0.14.0 → 0.16.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 (100) hide show
  1. package/README.md +29 -80
  2. package/dist/client/connection.d.ts +19 -6
  3. package/dist/client/connection.d.ts.map +1 -1
  4. package/dist/client/connection.js +79 -40
  5. package/dist/client/connection.js.map +1 -1
  6. package/dist/client/core.d.ts +1 -0
  7. package/dist/client/core.d.ts.map +1 -1
  8. package/dist/client/core.js +5 -0
  9. package/dist/client/core.js.map +1 -1
  10. package/dist/client/session.d.ts +2 -0
  11. package/dist/client/session.d.ts.map +1 -1
  12. package/dist/client/session.js +98 -19
  13. package/dist/client/session.js.map +1 -1
  14. package/dist/host/adapter.d.ts +1 -1
  15. package/dist/host/adapter.d.ts.map +1 -1
  16. package/dist/host/adapter.js +7 -1
  17. package/dist/host/adapter.js.map +1 -1
  18. package/dist/host/host.d.ts +1 -0
  19. package/dist/host/host.d.ts.map +1 -1
  20. package/dist/host/host.js +45 -5
  21. package/dist/host/host.js.map +1 -1
  22. package/dist/host/http.d.ts +1 -1
  23. package/dist/host/http.d.ts.map +1 -1
  24. package/dist/host/http.js +75 -47
  25. package/dist/host/http.js.map +1 -1
  26. package/dist/host/socket.d.ts +23 -7
  27. package/dist/host/socket.d.ts.map +1 -1
  28. package/dist/host/socket.js +275 -74
  29. package/dist/host/socket.js.map +1 -1
  30. package/dist/host/work.d.ts +7 -0
  31. package/dist/host/work.d.ts.map +1 -0
  32. package/dist/host/work.js +53 -0
  33. package/dist/host/work.js.map +1 -0
  34. package/dist/host-internal.d.ts +35 -5
  35. package/dist/host-internal.d.ts.map +1 -1
  36. package/dist/host-internal.js +2 -1
  37. package/dist/host.d.ts +16 -3
  38. package/dist/host.d.ts.map +1 -1
  39. package/dist/host.js +2 -1
  40. package/dist/index.d.ts +3 -3
  41. package/dist/index.js +3 -3
  42. package/dist/statewire.d.ts +13 -4
  43. package/dist/statewire.d.ts.map +1 -1
  44. package/dist/statewire.js.map +1 -1
  45. package/dist/transports/sse.d.ts +2 -5
  46. package/dist/transports/sse.d.ts.map +1 -1
  47. package/dist/transports/sse.js +50 -59
  48. package/dist/transports/sse.js.map +1 -1
  49. package/dist/transports/testing.js +4 -4
  50. package/dist/transports/testing.js.map +1 -1
  51. package/dist/transports/ws.d.ts +3 -3
  52. package/dist/transports/ws.d.ts.map +1 -1
  53. package/dist/transports/ws.js +57 -44
  54. package/dist/transports/ws.js.map +1 -1
  55. package/dist/types.d.ts +3 -0
  56. package/dist/types.d.ts.map +1 -1
  57. package/dist/types.js.map +1 -1
  58. package/dist/util/randomId.js +1 -1
  59. package/dist/util/randomId.js.map +1 -1
  60. package/dist/wire/documents.d.ts +20 -0
  61. package/dist/wire/documents.d.ts.map +1 -0
  62. package/dist/wire/documents.js +74 -0
  63. package/dist/wire/documents.js.map +1 -0
  64. package/dist/wire/limits.d.ts +10 -0
  65. package/dist/wire/limits.d.ts.map +1 -0
  66. package/dist/wire/limits.js +11 -0
  67. package/dist/wire/limits.js.map +1 -0
  68. package/dist/wire/meta.d.ts.map +1 -1
  69. package/dist/wire/meta.js +1 -1
  70. package/dist/wire/meta.js.map +1 -1
  71. package/dist/wire/ops.d.ts.map +1 -1
  72. package/dist/wire/ops.js +1 -2
  73. package/dist/wire/ops.js.map +1 -1
  74. package/dist/wire/version.d.ts +7 -0
  75. package/dist/wire/version.d.ts.map +1 -0
  76. package/dist/wire/version.js +8 -0
  77. package/dist/wire/version.js.map +1 -0
  78. package/package.json +1 -1
  79. package/src/client/connection.ts +159 -52
  80. package/src/client/core.ts +6 -0
  81. package/src/client/session.ts +105 -32
  82. package/src/host/adapter.ts +10 -2
  83. package/src/host/host.ts +54 -4
  84. package/src/host/http.ts +132 -66
  85. package/src/host/socket.ts +410 -81
  86. package/src/host/work.ts +50 -0
  87. package/src/host-internal.ts +32 -4
  88. package/src/host.ts +18 -2
  89. package/src/index.ts +2 -2
  90. package/src/statewire.ts +18 -4
  91. package/src/transports/sse.ts +70 -78
  92. package/src/transports/testing.ts +4 -4
  93. package/src/transports/ws.ts +36 -55
  94. package/src/types.ts +1 -0
  95. package/src/util/randomId.ts +3 -3
  96. package/src/wire/documents.ts +109 -0
  97. package/src/wire/limits.ts +6 -0
  98. package/src/wire/meta.ts +6 -1
  99. package/src/wire/ops.ts +1 -2
  100. package/src/wire/version.ts +3 -0
package/README.md CHANGED
@@ -1,90 +1,39 @@
1
1
  # statewire
2
2
 
3
- React/TypeScript client for the **Statewire** protocol: a server owns a JSON state object and streams it to clients as packets (`POST /stream` attach, hello-snapshot first); clients change it by submitting stamped statement frames (`POST /frames`, `{"cmd": [{"method", "params", "seq"}]}`). The client keeps your UI on the canonical state with optimistic updates rebased on top, survives disconnects, evictions, and reloads of the server, and tells you exactly what happened to every command you sent.
3
+ One JSON object replicated from a server to any number of clients, with typed commands back: the server streams snapshot-first ops, the client keeps optimistic updates rebased on top and settles every command it sent. Part of [harness-sdk](https://github.com/assistant-ui/harness-sdk), with `harness-sdk` and `pinned`.
4
4
 
5
- ```tsx
6
- import { useStatewire, StatewireSSE } from "statewire";
7
-
8
- type Commands = {
9
- increment(): void;
10
- };
11
-
12
- const { state, connection, commands } = useStatewire<State, Commands>({
13
- transport: StatewireSSE({ url: "/api/threads/demo" }),
14
- optimistic: (draft, command) => {
15
- if (command.method === "increment") draft.count += 1;
16
- },
17
- });
18
-
19
- const onClick = async () => {
20
- await commands.increment(); // resolves when the server has provably applied it
21
- };
22
- ```
23
-
24
- ## What you get
25
-
26
- - **`state`** — `undefined` until the first snapshot, then canonical server state with your pending commands' optimistic effects rebased on top. When the server's ack arrives, the optimistic overlay drops in the same tick: no flicker.
27
- - **`connection`** — connection health as a tagged union: `connection.status` is `"connecting" | "live" | "reconnecting" | "gone"`, and each variant carries exactly the data that exists in that situation: `reconnecting` has `cause` (`"dropped"`, `"evicted"`, or `"error"`), `attempt`, `nextRetryAt`, `lastError?`, `message?`; `gone` carries the server's `message?` and `payload?`. `connection.reconnect()` re-attaches manually from `gone`, or "retry now" during backoff.
28
- - **`commands`** — a typed proxy over the `Commands` method map: `commands.increment()` submits `{ method: "increment", params: [] }` and resolves with the handler's result only once the stream ack proves the effect is in canonical state. Rejects with `StatewireSendError`, whose `failure.fate` carries the verdict; the boundary is definitiveness:
29
- - `"aborted"` — definitely not applied (never POSTed, or every attempt accounted for); safe to resend
30
- - `"lost"` — a POST left the machine and never got a definitive answer; whether the server applied it is unknown — check the snapshot for your payload ids
31
- - `"rejected"` — the server said no; not applied (`message`, optional `payload` from the backend's `StatewireReject`)
32
- - `"unknown-command"` / `"invalid-params"` — the server does not know the method, or the args failed its schema
33
- - `"crashed"` — acked, but the processing died before producing a result
34
- - `"result-unavailable"` — acked, but the result can no longer be delivered
35
-
36
- Every failure also carries `cause` — which channel settled it: `"server"` = the server's answer, `"stream-gone"` = terminal `finish(gone)`, `"session-lost"` = the server forgot the client id (eviction), `"unmount"` is client-side. An `"aborted"` failure carries `predecessorLost: true` when an earlier in-flight command of the same batch has uncertain fate. The taxonomy's single exported type is `StatewireClient.SendFailure`; the `fate` and `cause` unions are reachable as `SendFailure["fate"]` / `SendFailure["cause"]`.
37
-
38
- An eviction replays every unsettled command under the rotated client id, in seq order — fresh seqs, relative order preserved, the original promise settles with the replay's outcome. Context submissions settle as session-lost failures and the stored context re-sends whole; replay may re-run a command the lost session already applied.
39
-
40
- Reconnects are not errors: transport drops and server evictions self-heal with exponential backoff while `state` stays renderable. Commands carry a monotonic seq, so retries are deduplicated server-side and a reconnect splices already-applied commands instead of re-running them.
41
-
42
- A client id may hold any number of concurrent attaches: each gets its own hello and lease, and cmd answers fan out to every live attach of the id. A lease lives and dies with its attach — a `423` means that attach is gone: re-attach and resend.
5
+ ## Install
43
6
 
44
- HTTP attach and follow-up frame bodies are limited to 8 MiB; oversized bodies receive `413 Payload Too Large` before JSON parsing.
45
-
46
- ## Three ways to mount
47
-
48
- One implementation, three entry points:
49
-
50
- ```ts
51
- useStatewire(options) // React hook
52
- StatewireResource(options) // @assistant-ui/tap resource, for composition
53
- new StatewireClient(options) // framework-free: .state/.connection/.commands,
54
- // .subscribe(cb), .dispose()
7
+ ```sh
8
+ pnpm add statewire react
55
9
  ```
56
10
 
57
- `StatewireClient#subscribe` follows the external-store contract (`useSyncExternalStore`-compatible). `dispose()` settles pending commands like an unmount.
58
-
59
- Every public type merges onto the class as a namespace — `StatewireClient.Connection`, `StatewireClient.ConnectionStatus`, `StatewireClient.Options`, `StatewireClient.SendFailure`, … — so one import covers the whole surface.
11
+ ## Use
60
12
 
61
- There is no session key: a transport `url` change reattaches in place and the attach snapshot arbitrates identity (an unknown client evicts — pending settles, the client id rotates). For a forced fresh session, key the resource — `withKey(threadId, StatewireResource(options))`; pending commands settle with reason `"unmount"`.
62
-
63
- ## Transport
64
-
65
- ```ts
66
- StatewireSSE({
67
- url, // endpoint root — the transport appends /stream and /commands
68
- headers, // HeadersInit or (async) () => HeadersInit, re-evaluated per request
69
- fetch, // custom fetch — the escape hatch for everything else
70
- })
13
+ ```tsx
14
+ import { StatewireHttp, useStatewire } from "statewire";
15
+
16
+ type CounterState = { count: number };
17
+ type CounterCommands = { increment(): void; reset(): void };
18
+
19
+ export const App = () => {
20
+ const { state, connection, commands } = useStatewire<
21
+ CounterState | undefined,
22
+ CounterCommands
23
+ >({ transport: StatewireHttp({ url: "/threads/main" }) });
24
+ return (
25
+ <main>
26
+ <p>{connection.status}</p>
27
+ <h1>{state?.count ?? 0}</h1>
28
+ <button onClick={() => void commands.increment()}>+1</button>
29
+ </main>
30
+ );
31
+ };
71
32
  ```
72
33
 
73
- `StatewireWS({ url, protocols, query, webSocket })` is the full-duplex sibling over `/ws`. A transport is a tap resource producing the `Statewire` value the client consumes; `createMockTransport` from `statewire/testing` is a complete in-memory driver for tests.
74
-
75
- ## Backend
76
-
77
- Any server speaking the Statewire convention works. The Python implementation lives in this repo (`python/statewire/statewire`):
78
-
79
- ```python
80
- from statewire import Statewire, StatewireReject, command
81
-
82
- class Counter(Statewire):
83
- @command
84
- async def increment(self):
85
- if self.state["count"] >= 10:
86
- raise StatewireReject("counter is full", payload={"max": 10})
87
- self.state["count"] += 1
88
- ```
34
+ ## Docs
89
35
 
90
- See `examples/vite-statewire-example` (frontend) and `python/statewire/examples/demo_app.py` (backend) for a complete runnable pair, and the repo's protocol spec for the wire format.
36
+ - Get started: https://github.com/assistant-ui/harness-sdk/blob/main/apps/docs/app/docs/get-started/statewire/page.mdx
37
+ - Guide: https://github.com/assistant-ui/harness-sdk/blob/main/apps/docs/app/docs/guides/statewire/consume/page.mdx
38
+ - Reference: https://github.com/assistant-ui/harness-sdk/blob/main/apps/docs/app/docs/reference/statewire/page.mdx
39
+ - Spec: https://github.com/assistant-ui/harness-sdk/blob/main/apps/docs/app/docs/spec/statewire-protocol/page.mdx
@@ -1,10 +1,11 @@
1
1
  import { StatewireHost } from "../host.js";
2
- import { ConnectionState, StatewireRemoteProps } from "../transports/sse.js";
2
+ import { ConnectionState, RemoteLink, StatewireFrame, StatewireRemoteProps, StatewireSubmitResult } from "../transports/sse.js";
3
3
  import { StatewireClient } from "../statewire.js";
4
4
  //#region src/client/connection.d.ts
5
5
  type Episode = {
6
- cause: "dropped" | "evicted" | "error" | "unauthorized";
6
+ cause: "dropped" | "reconnect" | "error" | "unauthorized";
7
7
  message?: string;
8
+ code?: string;
8
9
  };
9
10
  /** The terminal cmd verdict types; "applied" is the sole non-terminal type. */
10
11
  declare const TERMINAL: readonly ["result", "rejected", "failed", "crashed", "expired"];
@@ -42,11 +43,13 @@ type AttemptOutcome = "stop" | "reattach" | "ended" | "idle";
42
43
  type Conn = {
43
44
  connection: StatewireClient.Connection;
44
45
  parked: boolean;
45
- hasSnapshot: boolean;
46
46
  lease: string | null;
47
47
  attempt: number;
48
48
  authDenials: number;
49
+ authStale: boolean;
49
50
  liveAt: number | null;
51
+ retryAt: number;
52
+ error: StatewireClient.ConnectionError | undefined;
50
53
  episode: Episode | null;
51
54
  awaitingHello: boolean;
52
55
  held: StatewireCmd[];
@@ -54,6 +57,7 @@ type Conn = {
54
57
  abort: AbortController | null;
55
58
  lifetime: AbortController | null;
56
59
  ctx: StatewireRemoteProps;
60
+ onError: (error: StatewireClient.ConnectionError) => void;
57
61
  onChange: (connection: StatewireClient.Connection) => void;
58
62
  attemptOnce: (abort: AbortController) => Promise<AttemptOutcome>;
59
63
  notify: (connection: StatewireClient.Connection) => void;
@@ -61,15 +65,24 @@ type Conn = {
61
65
  };
62
66
  declare const degradedOf: (state: ConnectionState) => boolean;
63
67
  declare const validateRefusalSyn: (value: unknown) => StatewireSyn;
68
+ declare const settleFin: (c: Conn, fin: StatewireHost.Fin) => AttemptOutcome;
64
69
  declare const handleEnvelope: (c: Conn, value: unknown) => AttemptOutcome | null;
65
- declare const useStatewireConn: (props: {
70
+ declare const unsupportedVersion: (c: Conn, version: string) => AttemptOutcome;
71
+ declare const resolveHeaders: (headers: StatewireClient.HeadersOption | undefined, staleAuth: boolean) => Promise<Headers>;
72
+ declare const attachFrame: (c: Conn, headers: StatewireClient.HeadersOption | undefined) => Promise<{
73
+ headers: Headers;
74
+ body: string;
75
+ }>;
76
+ declare const useLink: (props: {
66
77
  url: string;
67
78
  isNew?: boolean | undefined;
68
79
  onChange?: ((connection: StatewireClient.Connection) => void) | undefined;
80
+ onError?: ((error: StatewireClient.ConnectionError) => void) | undefined;
69
81
  ctx: StatewireRemoteProps;
70
82
  attemptOnce: (c: Conn, abort: AbortController) => Promise<AttemptOutcome>;
71
- }) => Conn;
83
+ send: (c: Conn, frame: StatewireFrame) => Promise<StatewireSubmitResult>;
84
+ }) => RemoteLink;
72
85
  declare const classify: (timeout: AbortSignal) => Error;
73
86
  //#endregion
74
- export { AttemptOutcome, Conn, Episode, Gate, StatewireCmd, StatewireEnvelope, StatewireSyn, TERMINAL, classify, degradedOf, handleEnvelope, useStatewireConn, validateRefusalSyn };
87
+ export { AttemptOutcome, Conn, Episode, Gate, StatewireCmd, StatewireEnvelope, StatewireSyn, TERMINAL, attachFrame, classify, degradedOf, handleEnvelope, resolveHeaders, settleFin, unsupportedVersion, useLink, validateRefusalSyn };
75
88
  //# sourceMappingURL=connection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"connection.d.ts","names":[],"sources":["../../src/client/connection.ts"],"mappings":";;;;KAQY;EACV;EACA;;;cAIW;KAQD;EACV;EACA,2BAA2B;EAC3B;EACA;EACA;EACA;;EAEA;;KAGU;EACV;EACA;EACA;EACA;EACA;EACA;;KAGU;EACV;EACA,MAAM;EACN;EACA;EACA,MAAM;EACN,MAAM,cAAc;;KAGV;EAAS;EAAkB;;KAoC3B;KAEA;EACV,YAAY,gBAAgB;EAC5B;EACA;EACA;EACA;EACA;EACA;EACA,SAAS;EACT;EACA,MAAM;EACN,MAAM;EACN,OAAO;EACP,UAAU;EACV,KAAK;EACL,WAAW,YAAY,gBAAgB;EACvC,cAAc,OAAO,oBAAoB,QAAQ;EACjD,SAAS,YAAY,gBAAgB;EACrC;;cAGW,aAAc,OAAO;cAmErB,qBAAsB,mBAAiB;cA+NvC,iBACX,GAAG,MACH,mBACC;cAkGU,mBAAoB;EAC/B;EACA;EACA,aAAa,YAAY,gBAAgB;EACzC,KAAK;EACL,cAAc,GAAG,MAAM,OAAO,oBAAoB,QAAQ;MACxD;cAgES,WAAY,SAAS,gBAAW"}
1
+ {"version":3,"file":"connection.d.ts","names":[],"sources":["../../src/client/connection.ts"],"mappings":";;;;KAcY;EACV;EACA;EACA;;;cAIW;KAQD;EACV;EACA,2BAA2B;EAC3B;EACA;EACA;EACA;;EAEA;;KAGU;EACV;EACA;EACA;EACA;EACA;EACA;;KAGU;EACV;EACA,MAAM;EACN;EACA;EACA,MAAM;EACN,MAAM,cAAc;;KAGV;EAAS;EAAkB;;KAoC3B;KAEA;EACV,YAAY,gBAAgB;EAC5B;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OAAO,gBAAgB;EACvB,SAAS;EACT;EACA,MAAM;EACN,MAAM;EACN,OAAO;EACP,UAAU;EACV,KAAK;EACL,UAAU,OAAO,gBAAgB;EACjC,WAAW,YAAY,gBAAgB;EACvC,cAAc,OAAO,oBAAoB,QAAQ;EACjD,SAAS,YAAY,gBAAgB;EACrC;;cAGW,aAAc,OAAO;cAuErB,qBAAsB,mBAAiB;cAyKvC,YAAa,GAAG,MAAM,KAAK,cAAc,QAAM;cA+D/C,iBACX,GAAG,MACH,mBACC;cAkIU,qBAAsB,GAAG,MAAM,oBAAkB;cAUjD,iBACX,SAAS,gBAAgB,2BACzB,uBACC,QAAQ;cAME,cACX,GAAG,MACH,SAAS,gBAAgB,8BAAyB;;;;cAcvC,UAAW;EACtB;EACA;EACA,aAAa,YAAY,gBAAgB;EACzC,YAAY,OAAO,gBAAgB;EACnC,KAAK;EACL,cAAc,GAAG,MAAM,OAAO,oBAAoB,QAAQ;EAC1D,OAAO,GAAG,MAAM,OAAO,mBAAmB,QAAQ;MAChD;cA+ES,WAAY,SAAS,gBAAW"}
@@ -41,17 +41,18 @@ const openGate = (g) => {
41
41
  if (g.wake) g.wake();
42
42
  else g.latched = true;
43
43
  };
44
- const degradedOf = (state) => state.status === "connecting" && state.attempt > 0 || state.status === "disconnected" && state.reason === "gone";
44
+ const degradedOf = (state) => state.status === "connecting" && state.attempt > 0 || state.status === "disconnected" && state.reason !== "idle" && state.reason !== "deferred";
45
45
  const sameState = (a, b) => {
46
46
  if (a.status !== b.status) return false;
47
- if (a.status === "connecting" && b.status === "connecting") return a.attempt === b.attempt && a.cause === b.cause && a.nextRetryAt === b.nextRetryAt && a.lastError === b.lastError;
47
+ if (a.status === "connecting" && b.status === "connecting") return a.attempt === b.attempt && a.cause === b.cause && a.code === b.code && a.nextRetryAt === b.nextRetryAt && a.lastError === b.lastError;
48
48
  if (a.status === "disconnected" && b.status === "disconnected") return a.reason === b.reason;
49
49
  return true;
50
50
  };
51
51
  const setStatus = (c, next) => {
52
- if (sameState(next, c.connection)) return;
52
+ if (sameState(next, c.connection) && c.connection.error === c.error) return;
53
53
  c.connection = {
54
54
  ...next,
55
+ ...c.error === void 0 ? {} : { error: c.error },
55
56
  reconnect: c.reconnect,
56
57
  degraded: degradedOf(next)
57
58
  };
@@ -95,6 +96,9 @@ const validateFin = (value) => {
95
96
  if (!isObject(value)) return protocolError("fin must be an object");
96
97
  if (typeof value["reason"] !== "string") protocolError("fin.reason must be a string");
97
98
  validateOptionalString(value["message"], "fin.message");
99
+ validateOptionalString(value["code"], "fin.code");
100
+ if (value["status"] !== void 0 && !isNonnegativeSafeInteger(value["status"])) protocolError("fin.status must be a nonnegative safe integer");
101
+ if (value["retry"] !== void 0 && !isNonnegativeSafeInteger(value["retry"])) protocolError("fin.retry must be a nonnegative safe integer");
98
102
  return true;
99
103
  };
100
104
  const validateEnvelope = (value) => {
@@ -126,9 +130,7 @@ const cmdsOf = (env) => {
126
130
  const snapshotOf = (env) => {
127
131
  const ops = env.ops;
128
132
  if (ops === void 0) return protocolError("hello missing snapshot");
129
- if (ops.length !== 1 || !isObject(ops[0])) return protocolError("hello snapshot must be one root replace op");
130
- const op = ops[0];
131
- if (op["op"] !== "replace" || !Array.isArray(op["path"]) || op["path"].length !== 0 || !Object.hasOwn(op, "value")) return protocolError("hello snapshot must be one root replace op");
133
+ if (ops.length === 0) return protocolError("hello missing snapshot");
132
134
  return reviveOps(ops);
133
135
  };
134
136
  const helloSynOf = (env) => {
@@ -146,7 +148,7 @@ const handleHello = (c, env) => {
146
148
  });
147
149
  c.ctx.onIdle(syn.idle ?? false);
148
150
  c.awaitingHello = false;
149
- c.hasSnapshot = true;
151
+ c.error = void 0;
150
152
  c.liveAt = Date.now();
151
153
  c.authDenials = 0;
152
154
  c.episode = null;
@@ -186,28 +188,18 @@ const deliverEnvelope = (c, env) => {
186
188
  }
187
189
  if (typeof env.idle === "boolean") c.ctx.onIdle(env.idle);
188
190
  };
189
- const transientCauseOf = (reason) => {
190
- switch (reason) {
191
- case "evicted": return "evicted";
192
- case "error": return "error";
193
- case "unauthorized": return "unauthorized";
194
- default: return "error";
195
- }
196
- };
197
191
  const settleFin = (c, fin) => {
198
- let finish = fin;
199
- if (finish.meta !== void 0) {
200
- const { meta, ...rest } = finish;
201
- finish = {
202
- ...rest,
203
- payload: reviveValue(rest.payload, meta)
204
- };
205
- }
192
+ const finish = fin.meta === void 0 ? fin : {
193
+ ...fin,
194
+ payload: reviveValue(fin.payload, fin.meta)
195
+ };
206
196
  releaseHeld(c);
197
+ c.retryAt = Date.now() + (finish.retry ?? 0);
207
198
  if (finish.reason === "idle") {
208
199
  c.ctx.onIdle(true);
209
200
  c.attempt = 0;
210
201
  c.episode = null;
202
+ c.error = void 0;
211
203
  setStatus(c, {
212
204
  status: "disconnected",
213
205
  reason: "idle"
@@ -215,23 +207,26 @@ const settleFin = (c, fin) => {
215
207
  return "idle";
216
208
  }
217
209
  const denial = finish.reason === "unauthorized";
218
- const terminal = finish.reason === "gone" || denial && c.authDenials > 0;
219
- if (denial) c.authDenials += 1;
220
- c.ctx.onFinish(terminal ? finish : {
221
- ...finish,
222
- reason: "error"
223
- });
210
+ const terminal = finish.reason === "gone" || finish.reason === "forbidden" || finish.reason === "protocol-error" || denial && c.authDenials > 0;
211
+ if (denial) {
212
+ c.authDenials += 1;
213
+ c.authStale = true;
214
+ }
215
+ c.error = finish.reason !== "reconnect" && finish.reason !== "gone" && (!denial || terminal) ? Object.assign(new Error(finish.message ?? finish.reason), { fin: finish }) : void 0;
216
+ if (c.error) c.onError(c.error);
224
217
  if (!terminal) {
225
218
  c.episode = {
226
- cause: transientCauseOf(finish.reason),
227
- ...finish.message !== void 0 && { message: finish.message }
219
+ cause: finish.reason === "reconnect" ? "reconnect" : denial ? "unauthorized" : "error",
220
+ ...finish.message !== void 0 && { message: finish.message },
221
+ ...finish.code !== void 0 && { code: finish.code }
228
222
  };
229
223
  if (denial) openGate(c.gate);
230
224
  return "reattach";
231
225
  }
226
+ c.ctx.onFinish(finish);
232
227
  setStatus(c, {
233
228
  status: "disconnected",
234
- reason: "gone",
229
+ reason: denial ? "unauthorized" : finish.reason === "forbidden" ? "forbidden" : finish.reason === "protocol-error" ? "protocol-error" : "gone",
235
230
  ...finish.message !== void 0 && { message: finish.message },
236
231
  ...finish.payload !== void 0 && { payload: finish.payload }
237
232
  });
@@ -262,8 +257,7 @@ const scheduleRetry = (c, failure, immediate) => {
262
257
  c.liveAt = null;
263
258
  c.attempt += 1;
264
259
  c.episode ??= { cause: "dropped" };
265
- if (immediate) return 0;
266
- const delay = backoff(c.attempt);
260
+ const delay = Math.max(immediate ? 0 : backoff(c.attempt), c.retryAt - Date.now());
267
261
  setStatus(c, {
268
262
  status: "connecting",
269
263
  attempt: c.attempt,
@@ -292,6 +286,10 @@ const run = async (c, lifetime, gate) => {
292
286
  c.parked = false;
293
287
  }
294
288
  while (alive()) {
289
+ if (c.retryAt > Date.now()) {
290
+ await waitGate(gate, c.retryAt - Date.now());
291
+ continue;
292
+ }
295
293
  flushTapSync(() => dialing(c));
296
294
  const abort = new AbortController();
297
295
  c.abort = abort;
@@ -302,7 +300,17 @@ const run = async (c, lifetime, gate) => {
302
300
  try {
303
301
  outcome = await c.attemptOnce(abort);
304
302
  } catch (error) {
305
- if (!abort.signal.aborted) failure = error instanceof Error && error.message ? error.message : "network";
303
+ if (!abort.signal.aborted) {
304
+ if (error instanceof SyntaxError || error instanceof Error && /^(statewire: protocol error:|Statewire cannot apply)/.test(error.message)) outcome = flushTapSync(() => settleFin(c, {
305
+ reason: "protocol-error",
306
+ message: error.message
307
+ }));
308
+ else if (c.authStale) outcome = flushTapSync(() => settleFin(c, {
309
+ reason: "unauthorized",
310
+ message: error instanceof Error ? error.message : "credential refresh failed"
311
+ }));
312
+ failure = error instanceof Error && error.message ? error.message : "network";
313
+ }
306
314
  }
307
315
  abort.abort();
308
316
  if (!alive()) return;
@@ -311,6 +319,7 @@ const run = async (c, lifetime, gate) => {
311
319
  c.ctx.onDetach();
312
320
  });
313
321
  if (!alive()) return;
322
+ if (c.connection.status === "disconnected" && !["deferred", "idle"].includes(c.connection.reason)) outcome = "stop";
314
323
  if (outcome === "stop" || outcome === "idle") {
315
324
  await waitGate(gate);
316
325
  continue;
@@ -321,18 +330,37 @@ const run = async (c, lifetime, gate) => {
321
330
  await waitGate(gate, delay);
322
331
  }
323
332
  };
324
- const useStatewireConn = (props) => {
333
+ const unsupportedVersion = (c, version) => flushTapSync(() => handleEnvelope(c, { fin: {
334
+ reason: "protocol-error",
335
+ message: `unsupported protocol version: ${version || "\"\""}`
336
+ } })) ?? "stop";
337
+ const resolveHeaders = async (headers, staleAuth) => new Headers(typeof headers === "function" ? await headers({ staleAuth }) : headers);
338
+ const attachFrame = async (c, headers) => {
339
+ const resolved = await resolveHeaders(headers, c.authStale);
340
+ c.authStale = false;
341
+ const lanes = flushTapSync(() => c.ctx.takeAttachLanes());
342
+ return {
343
+ headers: resolved,
344
+ body: JSON.stringify({
345
+ headers: Object.fromEntries(resolved),
346
+ ...lanes ?? {}
347
+ })
348
+ };
349
+ };
350
+ const useLink = (props) => {
325
351
  const latest = useRef(props);
326
352
  latest.current = props;
327
353
  const [, setConnection] = useState(null);
328
354
  const [c] = useState(() => {
329
355
  const conn = {
330
356
  parked: props.isNew ?? false,
331
- hasSnapshot: false,
332
357
  lease: null,
333
358
  attempt: 0,
334
359
  authDenials: 0,
360
+ authStale: false,
335
361
  liveAt: null,
362
+ retryAt: 0,
363
+ error: void 0,
336
364
  episode: null,
337
365
  awaitingHello: true,
338
366
  held: [],
@@ -342,6 +370,7 @@ const useStatewireConn = (props) => {
342
370
  get ctx() {
343
371
  return latest.current.ctx;
344
372
  },
373
+ onError: (error) => latest.current.onError?.(error),
345
374
  onChange: (connection) => latest.current.onChange?.(connection),
346
375
  attemptOnce: (abort) => latest.current.attemptOnce(conn, abort),
347
376
  notify: setConnection,
@@ -360,12 +389,22 @@ const useStatewireConn = (props) => {
360
389
  };
361
390
  return conn;
362
391
  });
392
+ const [link] = useState(() => ({
393
+ get connection() {
394
+ return c.connection;
395
+ },
396
+ get parked() {
397
+ return c.parked;
398
+ },
399
+ send: (frame) => latest.current.send(c, frame)
400
+ }));
363
401
  useEffect(() => {
364
- c.hasSnapshot = false;
365
402
  c.lease = null;
366
403
  c.attempt = 0;
367
404
  c.authDenials = 0;
405
+ c.authStale = false;
368
406
  c.liveAt = null;
407
+ c.retryAt = 0;
369
408
  c.episode = null;
370
409
  setStatus(c, c.parked ? {
371
410
  status: "disconnected",
@@ -385,10 +424,10 @@ const useStatewireConn = (props) => {
385
424
  openGate(c.gate);
386
425
  };
387
426
  }, [c, props.url]);
388
- return c;
427
+ return link;
389
428
  };
390
429
  const classify = (timeout) => /* @__PURE__ */ new Error(timeout.aborted ? "timeout" : "network");
391
430
  //#endregion
392
- export { TERMINAL, classify, degradedOf, handleEnvelope, useStatewireConn, validateRefusalSyn };
431
+ export { TERMINAL, attachFrame, classify, degradedOf, handleEnvelope, resolveHeaders, settleFin, unsupportedVersion, useLink, validateRefusalSyn };
393
432
 
394
433
  //# sourceMappingURL=connection.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"connection.js","names":["useEffect","useRef","useState","flushTapSync","reviveOps","reviveValue","backoff","timing","ConnectionState","StatewireRemoteProps","StatewireClient","StatewireHost","Episode","cause","message","TERMINAL","const","StatewireCmd","seq","type","code","payload","meta","dur","StatewireSyn","ctxSeq","record","id","idle","retry","StatewireEnvelope","ops","cmd","next","syn","fin","Fin","Gate","latched","wake","newGate","undefined","takeGate","g","was","waitGate","timeoutMs","Promise","resolve","timer","setTimeout","clearTimeout","openGate","AttemptOutcome","Conn","connection","Connection","parked","hasSnapshot","lease","attempt","authDenials","liveAt","episode","awaitingHello","held","gate","abort","AbortController","lifetime","ctx","onChange","attemptOnce","notify","reconnect","degradedOf","state","status","reason","sameState","a","b","nextRetryAt","lastError","setStatus","c","degraded","protocolError","Error","isObject","value","Record","Array","isArray","isNonnegativeSafeInteger","Number","isSafeInteger","isPositiveSafeInteger","validateOptionalString","field","validateSynScalars","validateRefusalSyn","validateCmd","validateFin","validateEnvelope","entry","releaseHeld","length","onCmds","cmdsOf","env","snapshotOf","op","Object","hasOwn","helloSynOf","handleHello","onHello","onIdle","Date","now","warnedUnknownSynFact","warnUnknownSynFact","console","warn","deliverSyn","onRefusal","keys","deliverEnvelope","onOps","cmds","push","transientCauseOf","settleFin","finish","rest","denial","terminal","onFinish","handleEnvelope","dialing","scheduleRetry","failure","immediate","backoffResetMs","delay","reconnectConn","run","alive","outcome","error","signal","aborted","onDetach","useStatewireConn","props","url","isNew","latest","current","setConnection","conn","classify","timeout","AbortSignal"],"sources":["../../src/client/connection.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { flushTapSync } from \"@assistant-ui/tap\";\nimport { reviveOps, reviveValue } from \"../wire/meta\";\nimport { backoff, timing } from \"./timing\";\nimport type { ConnectionState, StatewireRemoteProps } from \"../transports/sse\";\nimport type { StatewireClient } from \"../statewire\";\nimport type { StatewireHost } from \"../host\";\n\nexport type Episode = {\n cause: \"dropped\" | \"evicted\" | \"error\" | \"unauthorized\";\n message?: string;\n};\n\n/** The terminal cmd verdict types; \"applied\" is the sole non-terminal type. */\nexport const TERMINAL = [\n \"result\",\n \"rejected\",\n \"failed\",\n \"crashed\",\n \"expired\",\n] as const;\n\nexport type StatewireCmd = {\n seq: number;\n type?: \"applied\" | (typeof TERMINAL)[number];\n code?: string;\n message?: string;\n payload?: unknown;\n meta?: unknown;\n /** Cumulative: resending through this seq will never be needed. */\n dur?: boolean;\n};\n\nexport type StatewireSyn = {\n seq?: number;\n ctxSeq?: number;\n record?: \"ephemeral\";\n id?: string;\n idle?: boolean;\n retry?: number;\n};\n\nexport type StatewireEnvelope = {\n ops?: unknown[];\n cmd?: StatewireCmd[];\n next?: true;\n idle?: boolean;\n syn?: StatewireSyn;\n fin?: StatewireHost.Fin;\n};\n\nexport type Gate = { latched: boolean; wake: (() => void) | undefined };\n\nconst newGate = (): Gate => ({ latched: false, wake: undefined });\n\nconst takeGate = (g: Gate) => {\n const was = g.latched;\n g.latched = false;\n return was;\n};\n\nconst waitGate = (g: Gate, timeoutMs?: number) => {\n if (g.latched) {\n g.latched = false;\n return Promise.resolve();\n }\n return new Promise<void>((resolve) => {\n const timer =\n timeoutMs === undefined\n ? undefined\n : setTimeout(() => {\n g.wake = undefined;\n resolve();\n }, timeoutMs);\n g.wake = () => {\n clearTimeout(timer);\n g.wake = undefined;\n resolve();\n };\n });\n};\n\nconst openGate = (g: Gate) => {\n if (g.wake) g.wake();\n else g.latched = true;\n};\n\nexport type AttemptOutcome = \"stop\" | \"reattach\" | \"ended\" | \"idle\";\n\nexport type Conn = {\n connection: StatewireClient.Connection;\n parked: boolean;\n hasSnapshot: boolean;\n lease: string | null;\n attempt: number;\n authDenials: number;\n liveAt: number | null;\n episode: Episode | null;\n awaitingHello: boolean;\n held: StatewireCmd[];\n gate: Gate;\n abort: AbortController | null;\n lifetime: AbortController | null;\n ctx: StatewireRemoteProps;\n onChange: (connection: StatewireClient.Connection) => void;\n attemptOnce: (abort: AbortController) => Promise<AttemptOutcome>;\n notify: (connection: StatewireClient.Connection) => void;\n reconnect: () => void;\n};\n\nexport const degradedOf = (state: ConnectionState): boolean =>\n (state.status === \"connecting\" && state.attempt > 0) ||\n (state.status === \"disconnected\" && state.reason === \"gone\");\n\nconst sameState = (a: ConnectionState, b: ConnectionState): boolean => {\n if (a.status !== b.status) return false;\n if (a.status === \"connecting\" && b.status === \"connecting\") {\n return (\n a.attempt === b.attempt &&\n a.cause === b.cause &&\n a.nextRetryAt === b.nextRetryAt &&\n a.lastError === b.lastError\n );\n }\n if (a.status === \"disconnected\" && b.status === \"disconnected\") {\n return a.reason === b.reason;\n }\n return true;\n};\n\nconst setStatus = (c: Conn, next: ConnectionState) => {\n if (sameState(next, c.connection)) return;\n c.connection = {\n ...next,\n reconnect: c.reconnect,\n degraded: degradedOf(next),\n };\n c.notify(c.connection);\n c.onChange(c.connection);\n};\n\nconst protocolError = (message: string): never => {\n throw new Error(`statewire: protocol error: ${message}`);\n};\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst isNonnegativeSafeInteger = (value: unknown): value is number =>\n Number.isSafeInteger(value) && (value as number) >= 0;\n\nconst isPositiveSafeInteger = (value: unknown): value is number =>\n Number.isSafeInteger(value) && (value as number) > 0;\n\nconst validateOptionalString = (value: unknown, field: string): void => {\n if (value !== undefined && typeof value !== \"string\") {\n protocolError(`${field} must be a string`);\n }\n};\n\nconst validateSynScalars = (syn: Record<string, unknown>) => {\n if (syn[\"seq\"] !== undefined && !isNonnegativeSafeInteger(syn[\"seq\"])) {\n protocolError(\"syn.seq must be a nonnegative safe integer\");\n }\n if (syn[\"ctxSeq\"] !== undefined && !isNonnegativeSafeInteger(syn[\"ctxSeq\"])) {\n protocolError(\"syn.ctxSeq must be a nonnegative safe integer\");\n }\n if (syn[\"retry\"] !== undefined && !isNonnegativeSafeInteger(syn[\"retry\"])) {\n protocolError(\"syn.retry must be a nonnegative safe integer\");\n }\n validateOptionalString(syn[\"id\"], \"syn.id\");\n if (syn[\"idle\"] !== undefined && typeof syn[\"idle\"] !== \"boolean\") {\n protocolError(\"syn.idle must be a boolean\");\n }\n validateOptionalString(syn[\"record\"], \"syn.record\");\n};\n\nexport const validateRefusalSyn = (value: unknown): StatewireSyn => {\n if (!isObject(value)) return protocolError(\"syn must be an object\");\n validateSynScalars(value);\n if (\n value[\"id\"] !== undefined ||\n value[\"idle\"] !== undefined ||\n value[\"record\"] !== undefined\n ) {\n protocolError(\"invalid refusal syn\");\n }\n if (\n value[\"seq\"] === undefined &&\n value[\"ctxSeq\"] === undefined &&\n value[\"retry\"] === undefined\n ) {\n protocolError(\"refusal syn has no watermarks or retry\");\n }\n return value as StatewireSyn;\n};\n\nconst validateCmd = (value: unknown): value is StatewireCmd => {\n if (!isObject(value)) return protocolError(\"cmd entry must be an object\");\n if (!isPositiveSafeInteger(value[\"seq\"])) {\n protocolError(\"cmd.seq must be a positive safe integer\");\n }\n validateOptionalString(value[\"type\"], \"cmd.type\");\n validateOptionalString(value[\"code\"], \"cmd.code\");\n validateOptionalString(value[\"message\"], \"cmd.message\");\n if (value[\"dur\"] !== undefined && value[\"dur\"] !== true) {\n protocolError(\"cmd.dur must be true\");\n }\n return true;\n};\n\nconst validateFin = (value: unknown): value is StatewireHost.Fin => {\n if (!isObject(value)) return protocolError(\"fin must be an object\");\n if (typeof value[\"reason\"] !== \"string\") {\n protocolError(\"fin.reason must be a string\");\n }\n validateOptionalString(value[\"message\"], \"fin.message\");\n return true;\n};\n\nconst validateEnvelope = (value: unknown): StatewireEnvelope => {\n if (!isObject(value)) return protocolError(\"packet must be an object\");\n if (value[\"ops\"] !== undefined && !Array.isArray(value[\"ops\"])) {\n protocolError(\"ops must be an array\");\n }\n if (value[\"cmd\"] !== undefined) {\n const cmd = value[\"cmd\"];\n if (!Array.isArray(cmd)) return protocolError(\"cmd must be an array\");\n for (const entry of cmd) validateCmd(entry);\n }\n if (value[\"idle\"] !== undefined && typeof value[\"idle\"] !== \"boolean\") {\n protocolError(\"idle must be a boolean\");\n }\n if (value[\"next\"] !== undefined) {\n if (value[\"next\"] !== true) protocolError(\"next must be true\");\n if (\n value[\"cmd\"] === undefined ||\n value[\"ops\"] !== undefined ||\n value[\"fin\"] !== undefined\n ) {\n protocolError(\"next must ride a cmd-only packet\");\n }\n }\n if (value[\"fin\"] !== undefined) validateFin(value[\"fin\"]);\n return value as StatewireEnvelope;\n};\n\n// A hold's release delivers, never settles: fin and bare close both drain it.\nconst releaseHeld = (c: Conn) => {\n if (c.held.length === 0) return;\n const held = c.held;\n c.held = [];\n c.ctx.onCmds(held);\n};\n\nconst cmdsOf = (env: StatewireEnvelope): StatewireCmd[] => {\n if (env.cmd === undefined) return [];\n return env.cmd;\n};\n\nconst snapshotOf = (env: StatewireEnvelope): unknown[] => {\n const ops = env.ops;\n if (ops === undefined) return protocolError(\"hello missing snapshot\");\n if (ops.length !== 1 || !isObject(ops[0])) {\n return protocolError(\"hello snapshot must be one root replace op\");\n }\n const op = ops[0];\n if (\n op[\"op\"] !== \"replace\" ||\n !Array.isArray(op[\"path\"]) ||\n op[\"path\"].length !== 0 ||\n !Object.hasOwn(op, \"value\")\n ) {\n return protocolError(\"hello snapshot must be one root replace op\");\n }\n return reviveOps(ops);\n};\n\nconst helloSynOf = (env: StatewireEnvelope): StatewireSyn => {\n if (!isObject(env.syn)) return protocolError(\"expected hello\");\n validateSynScalars(env.syn);\n if (env.syn[\"retry\"] !== undefined) {\n protocolError(\"hello syn cannot carry retry\");\n }\n return env.syn as StatewireSyn;\n};\n\nconst handleHello = (c: Conn, env: StatewireEnvelope) => {\n const syn = helloSynOf(env);\n const ops = snapshotOf(env);\n c.ctx.onHello({\n ops,\n syn,\n });\n c.ctx.onIdle(syn.idle ?? false);\n c.awaitingHello = false;\n c.hasSnapshot = true;\n c.liveAt = Date.now();\n c.authDenials = 0;\n c.episode = null;\n setStatus(c, { status: \"connected\" });\n};\n\nlet warnedUnknownSynFact = false;\n\nconst warnUnknownSynFact = () => {\n if (warnedUnknownSynFact) return;\n warnedUnknownSynFact = true;\n console.warn(\"statewire: ignoring unknown syn fact\");\n};\n\nconst deliverSyn = (c: Conn, value: unknown) => {\n if (!isObject(value)) return protocolError(\"syn must be an object\");\n validateSynScalars(value);\n if (value[\"record\"] !== undefined || value[\"idle\"] !== undefined) {\n protocolError(\"invalid mid-stream syn\");\n }\n if (\n value[\"seq\"] !== undefined ||\n value[\"ctxSeq\"] !== undefined ||\n value[\"retry\"] !== undefined\n ) {\n c.ctx.onRefusal(validateRefusalSyn(value));\n return;\n }\n if (value[\"id\"] !== undefined) return;\n if (Object.keys(value).length === 0) {\n protocolError(\"invalid mid-stream syn\");\n }\n warnUnknownSynFact();\n};\n\nconst deliverEnvelope = (c: Conn, env: StatewireEnvelope) => {\n if (env.syn !== undefined) deliverSyn(c, env.syn);\n if (env.ops !== undefined) {\n const held = c.held;\n c.held = [];\n c.ctx.onOps({\n ops: reviveOps(env.ops),\n cmds: [...held, ...cmdsOf(env)],\n });\n } else if (env.cmd !== undefined) {\n const cmds = cmdsOf(env);\n if (env.next === true) c.held.push(...cmds);\n else c.ctx.onCmds(cmds);\n }\n if (typeof env.idle === \"boolean\") c.ctx.onIdle(env.idle);\n};\n\nconst transientCauseOf = (\n reason: StatewireHost.Fin[\"reason\"],\n): Episode[\"cause\"] => {\n switch (reason) {\n case \"evicted\":\n return \"evicted\";\n case \"error\":\n return \"error\";\n case \"unauthorized\":\n return \"unauthorized\";\n default:\n return \"error\";\n }\n};\n\nconst settleFin = (c: Conn, fin: StatewireHost.Fin): AttemptOutcome => {\n let finish = fin;\n if (finish.meta !== undefined) {\n const { meta, ...rest } = finish;\n finish = { ...rest, payload: reviveValue(rest.payload, meta) };\n }\n releaseHeld(c);\n if (finish.reason === \"idle\") {\n c.ctx.onIdle(true);\n c.attempt = 0;\n c.episode = null;\n setStatus(c, { status: \"disconnected\", reason: \"idle\" });\n return \"idle\";\n }\n // An auth denial gets one immediate redial; a consecutive repeat parks as gone.\n const denial = finish.reason === \"unauthorized\";\n const terminal = finish.reason === \"gone\" || (denial && c.authDenials > 0);\n if (denial) c.authDenials += 1;\n c.ctx.onFinish(terminal ? finish : { ...finish, reason: \"error\" });\n if (!terminal) {\n c.episode = {\n cause: transientCauseOf(finish.reason),\n ...(finish.message !== undefined && { message: finish.message }),\n };\n if (denial) openGate(c.gate);\n return \"reattach\";\n }\n setStatus(c, {\n status: \"disconnected\",\n reason: \"gone\",\n ...(finish.message !== undefined && { message: finish.message }),\n ...(finish.payload !== undefined && { payload: finish.payload }),\n });\n return \"stop\";\n};\n\nexport const handleEnvelope = (\n c: Conn,\n value: unknown,\n): AttemptOutcome | null => {\n const env = validateEnvelope(value);\n if (c.awaitingHello) {\n if (env.fin !== undefined) return settleFin(c, env.fin);\n handleHello(c, env);\n return null;\n }\n deliverEnvelope(c, env);\n if (env.fin !== undefined) return settleFin(c, env.fin);\n if (c.connection.status !== \"connected\") {\n c.attempt = 0;\n c.episode = null;\n setStatus(c, { status: \"connected\" });\n }\n return null;\n};\n\nconst dialing = (c: Conn) =>\n setStatus(c, { status: \"connecting\", attempt: c.attempt });\n\nconst scheduleRetry = (\n c: Conn,\n failure: string | undefined,\n immediate: boolean,\n) => {\n if (c.liveAt !== null && Date.now() - c.liveAt >= timing.backoffResetMs) {\n c.attempt = 0;\n }\n c.liveAt = null;\n c.attempt += 1;\n c.episode ??= { cause: \"dropped\" };\n if (immediate) return 0;\n const delay = backoff(c.attempt);\n setStatus(c, {\n status: \"connecting\",\n attempt: c.attempt,\n ...c.episode,\n nextRetryAt: Date.now() + delay,\n ...(failure !== undefined && { lastError: failure }),\n });\n return delay;\n};\n\nconst reconnectConn = (c: Conn) => {\n if (!c.lifetime) return;\n if (c.connection.status === \"disconnected\") {\n // A reconnect out of a park starts a fresh episode: first dial, attempt 0.\n c.attempt = 0;\n c.authDenials = 0;\n c.episode = null;\n flushTapSync(() => dialing(c));\n }\n openGate(c.gate);\n c.abort?.abort();\n};\n\nconst run = async (c: Conn, lifetime: AbortController, gate: Gate) => {\n const alive = () => c.lifetime === lifetime;\n if (c.parked) {\n await waitGate(gate);\n if (!alive()) return;\n c.parked = false;\n }\n while (alive()) {\n // The dial is in flight: attempt alone, no backoff fields.\n flushTapSync(() => dialing(c));\n const abort = new AbortController();\n c.abort = abort;\n c.awaitingHello = true;\n c.held = [];\n let outcome: AttemptOutcome | undefined;\n let failure: string | undefined;\n try {\n outcome = await c.attemptOnce(abort);\n } catch (error) {\n if (!abort.signal.aborted) {\n failure =\n error instanceof Error && error.message ? error.message : \"network\";\n }\n }\n abort.abort();\n if (!alive()) return;\n flushTapSync(() => {\n releaseHeld(c);\n c.ctx.onDetach();\n });\n if (!alive()) return;\n if (outcome === \"stop\" || outcome === \"idle\") {\n await waitGate(gate);\n continue;\n }\n const immediate = takeGate(gate);\n const delay = flushTapSync(() => scheduleRetry(c, failure, immediate));\n if (delay === 0) continue;\n await waitGate(gate, delay);\n }\n};\n\nexport const useStatewireConn = (props: {\n url: string;\n isNew?: boolean | undefined;\n onChange?: ((connection: StatewireClient.Connection) => void) | undefined;\n ctx: StatewireRemoteProps;\n attemptOnce: (c: Conn, abort: AbortController) => Promise<AttemptOutcome>;\n}): Conn => {\n const latest = useRef(props);\n latest.current = props;\n const [, setConnection] = useState<StatewireClient.Connection | null>(null);\n const [c] = useState((): Conn => {\n const conn: Conn = {\n parked: props.isNew ?? false,\n hasSnapshot: false,\n lease: null,\n attempt: 0,\n authDenials: 0,\n liveAt: null,\n episode: null,\n awaitingHello: true,\n held: [],\n abort: null,\n gate: newGate(),\n lifetime: null,\n get ctx() {\n return latest.current.ctx;\n },\n onChange: (connection) => latest.current.onChange?.(connection),\n attemptOnce: (abort) => latest.current.attemptOnce(conn, abort),\n notify: setConnection,\n reconnect: () => reconnectConn(conn),\n connection: {\n ...((props.isNew ?? false)\n ? { status: \"disconnected\", reason: \"deferred\" }\n : { status: \"connecting\", attempt: 0 }),\n degraded: false,\n reconnect: () => conn.reconnect(),\n },\n };\n return conn;\n });\n\n useEffect(() => {\n c.hasSnapshot = false;\n c.lease = null;\n c.attempt = 0;\n c.authDenials = 0;\n c.liveAt = null;\n c.episode = null;\n setStatus(\n c,\n c.parked\n ? { status: \"disconnected\", reason: \"deferred\" }\n : { status: \"connecting\", attempt: 0 },\n );\n const lifetime = new AbortController();\n c.lifetime = lifetime;\n c.gate = newGate();\n void run(c, lifetime, c.gate);\n return () => {\n c.abort?.abort();\n lifetime.abort();\n if (c.lifetime === lifetime) c.lifetime = null;\n openGate(c.gate);\n };\n }, [c, props.url]);\n\n return c;\n};\n\nexport const classify = (timeout: AbortSignal) =>\n new Error(timeout.aborted ? \"timeout\" : \"network\");\n"],"mappings":"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"}
1
+ {"version":3,"file":"connection.js","names":["useEffect","useRef","useState","flushTapSync","reviveOps","reviveValue","backoff","timing","ConnectionState","RemoteLink","StatewireFrame","StatewireRemoteProps","StatewireSubmitResult","StatewireClient","StatewireHost","Episode","cause","message","code","TERMINAL","const","StatewireCmd","seq","type","payload","meta","dur","StatewireSyn","ctxSeq","record","id","idle","retry","StatewireEnvelope","ops","cmd","next","syn","fin","Fin","Gate","latched","wake","newGate","undefined","takeGate","g","was","waitGate","timeoutMs","Promise","resolve","timer","setTimeout","clearTimeout","openGate","AttemptOutcome","Conn","connection","Connection","parked","lease","attempt","authDenials","authStale","liveAt","retryAt","error","ConnectionError","episode","awaitingHello","held","gate","abort","AbortController","lifetime","ctx","onError","onChange","attemptOnce","notify","reconnect","degradedOf","state","status","reason","sameState","a","b","nextRetryAt","lastError","setStatus","c","degraded","protocolError","Error","isObject","value","Record","Array","isArray","isNonnegativeSafeInteger","Number","isSafeInteger","isPositiveSafeInteger","validateOptionalString","field","validateSynScalars","validateRefusalSyn","validateCmd","validateFin","validateEnvelope","entry","releaseHeld","length","onCmds","cmdsOf","env","snapshotOf","helloSynOf","handleHello","onHello","onIdle","Date","now","warnedUnknownSynFact","warnUnknownSynFact","console","warn","deliverSyn","onRefusal","Object","keys","deliverEnvelope","onOps","cmds","push","settleFin","finish","denial","terminal","report","assign","onFinish","handleEnvelope","dialing","scheduleRetry","failure","immediate","backoffResetMs","delay","Math","max","reconnectConn","run","alive","outcome","signal","aborted","SyntaxError","test","onDetach","includes","unsupportedVersion","version","resolveHeaders","headers","HeadersOption","staleAuth","Headers","attachFrame","resolved","lanes","takeAttachLanes","body","JSON","stringify","fromEntries","useLink","props","url","isNew","send","frame","latest","current","setConnection","conn","link","classify","timeout","AbortSignal"],"sources":["../../src/client/connection.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { flushTapSync } from \"@assistant-ui/tap\";\nimport { reviveOps, reviveValue } from \"../wire/meta\";\nimport { backoff, timing } from \"./timing\";\nimport type {\n ConnectionState,\n RemoteLink,\n StatewireFrame,\n StatewireRemoteProps,\n StatewireSubmitResult,\n} from \"../transports/sse\";\nimport type { StatewireClient } from \"../statewire\";\nimport type { StatewireHost } from \"../host\";\n\nexport type Episode = {\n cause: \"dropped\" | \"reconnect\" | \"error\" | \"unauthorized\";\n message?: string;\n code?: string;\n};\n\n/** The terminal cmd verdict types; \"applied\" is the sole non-terminal type. */\nexport const TERMINAL = [\n \"result\",\n \"rejected\",\n \"failed\",\n \"crashed\",\n \"expired\",\n] as const;\n\nexport type StatewireCmd = {\n seq: number;\n type?: \"applied\" | (typeof TERMINAL)[number];\n code?: string;\n message?: string;\n payload?: unknown;\n meta?: unknown;\n /** Cumulative: resending through this seq will never be needed. */\n dur?: boolean;\n};\n\nexport type StatewireSyn = {\n seq?: number;\n ctxSeq?: number;\n record?: \"ephemeral\";\n id?: string;\n idle?: boolean;\n retry?: number;\n};\n\nexport type StatewireEnvelope = {\n ops?: unknown[];\n cmd?: StatewireCmd[];\n next?: true;\n idle?: boolean;\n syn?: StatewireSyn;\n fin?: StatewireHost.Fin;\n};\n\nexport type Gate = { latched: boolean; wake: (() => void) | undefined };\n\nconst newGate = (): Gate => ({ latched: false, wake: undefined });\n\nconst takeGate = (g: Gate) => {\n const was = g.latched;\n g.latched = false;\n return was;\n};\n\nconst waitGate = (g: Gate, timeoutMs?: number) => {\n if (g.latched) {\n g.latched = false;\n return Promise.resolve();\n }\n return new Promise<void>((resolve) => {\n const timer =\n timeoutMs === undefined\n ? undefined\n : setTimeout(() => {\n g.wake = undefined;\n resolve();\n }, timeoutMs);\n g.wake = () => {\n clearTimeout(timer);\n g.wake = undefined;\n resolve();\n };\n });\n};\n\nconst openGate = (g: Gate) => {\n if (g.wake) g.wake();\n else g.latched = true;\n};\n\nexport type AttemptOutcome = \"stop\" | \"reattach\" | \"ended\" | \"idle\";\n\nexport type Conn = {\n connection: StatewireClient.Connection;\n parked: boolean;\n lease: string | null;\n attempt: number;\n authDenials: number;\n authStale: boolean;\n liveAt: number | null;\n retryAt: number;\n error: StatewireClient.ConnectionError | undefined;\n episode: Episode | null;\n awaitingHello: boolean;\n held: StatewireCmd[];\n gate: Gate;\n abort: AbortController | null;\n lifetime: AbortController | null;\n ctx: StatewireRemoteProps;\n onError: (error: StatewireClient.ConnectionError) => void;\n onChange: (connection: StatewireClient.Connection) => void;\n attemptOnce: (abort: AbortController) => Promise<AttemptOutcome>;\n notify: (connection: StatewireClient.Connection) => void;\n reconnect: () => void;\n};\n\nexport const degradedOf = (state: ConnectionState): boolean =>\n (state.status === \"connecting\" && state.attempt > 0) ||\n (state.status === \"disconnected\" &&\n state.reason !== \"idle\" &&\n state.reason !== \"deferred\");\n\nconst sameState = (a: ConnectionState, b: ConnectionState): boolean => {\n if (a.status !== b.status) return false;\n if (a.status === \"connecting\" && b.status === \"connecting\") {\n return (\n a.attempt === b.attempt &&\n a.cause === b.cause &&\n a.code === b.code &&\n a.nextRetryAt === b.nextRetryAt &&\n a.lastError === b.lastError\n );\n }\n if (a.status === \"disconnected\" && b.status === \"disconnected\") {\n return a.reason === b.reason;\n }\n return true;\n};\n\nconst setStatus = (c: Conn, next: ConnectionState) => {\n if (sameState(next, c.connection) && c.connection.error === c.error) return;\n c.connection = {\n ...next,\n ...(c.error === undefined ? {} : { error: c.error }),\n reconnect: c.reconnect,\n degraded: degradedOf(next),\n };\n c.notify(c.connection);\n c.onChange(c.connection);\n};\n\nconst protocolError = (message: string): never => {\n throw new Error(`statewire: protocol error: ${message}`);\n};\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst isNonnegativeSafeInteger = (value: unknown): value is number =>\n Number.isSafeInteger(value) && (value as number) >= 0;\n\nconst isPositiveSafeInteger = (value: unknown): value is number =>\n Number.isSafeInteger(value) && (value as number) > 0;\n\nconst validateOptionalString = (value: unknown, field: string): void => {\n if (value !== undefined && typeof value !== \"string\") {\n protocolError(`${field} must be a string`);\n }\n};\n\nconst validateSynScalars = (syn: Record<string, unknown>) => {\n if (syn[\"seq\"] !== undefined && !isNonnegativeSafeInteger(syn[\"seq\"])) {\n protocolError(\"syn.seq must be a nonnegative safe integer\");\n }\n if (syn[\"ctxSeq\"] !== undefined && !isNonnegativeSafeInteger(syn[\"ctxSeq\"])) {\n protocolError(\"syn.ctxSeq must be a nonnegative safe integer\");\n }\n if (syn[\"retry\"] !== undefined && !isNonnegativeSafeInteger(syn[\"retry\"])) {\n protocolError(\"syn.retry must be a nonnegative safe integer\");\n }\n validateOptionalString(syn[\"id\"], \"syn.id\");\n if (syn[\"idle\"] !== undefined && typeof syn[\"idle\"] !== \"boolean\") {\n protocolError(\"syn.idle must be a boolean\");\n }\n validateOptionalString(syn[\"record\"], \"syn.record\");\n};\n\nexport const validateRefusalSyn = (value: unknown): StatewireSyn => {\n if (!isObject(value)) return protocolError(\"syn must be an object\");\n validateSynScalars(value);\n if (\n value[\"id\"] !== undefined ||\n value[\"idle\"] !== undefined ||\n value[\"record\"] !== undefined\n ) {\n protocolError(\"invalid refusal syn\");\n }\n if (\n value[\"seq\"] === undefined &&\n value[\"ctxSeq\"] === undefined &&\n value[\"retry\"] === undefined\n ) {\n protocolError(\"refusal syn has no watermarks or retry\");\n }\n return value as StatewireSyn;\n};\n\nconst validateCmd = (value: unknown): value is StatewireCmd => {\n if (!isObject(value)) return protocolError(\"cmd entry must be an object\");\n if (!isPositiveSafeInteger(value[\"seq\"])) {\n protocolError(\"cmd.seq must be a positive safe integer\");\n }\n validateOptionalString(value[\"type\"], \"cmd.type\");\n validateOptionalString(value[\"code\"], \"cmd.code\");\n validateOptionalString(value[\"message\"], \"cmd.message\");\n if (value[\"dur\"] !== undefined && value[\"dur\"] !== true) {\n protocolError(\"cmd.dur must be true\");\n }\n return true;\n};\n\nconst validateFin = (value: unknown): value is StatewireHost.Fin => {\n if (!isObject(value)) return protocolError(\"fin must be an object\");\n if (typeof value[\"reason\"] !== \"string\") {\n protocolError(\"fin.reason must be a string\");\n }\n validateOptionalString(value[\"message\"], \"fin.message\");\n validateOptionalString(value[\"code\"], \"fin.code\");\n if (\n value[\"status\"] !== undefined &&\n !isNonnegativeSafeInteger(value[\"status\"])\n )\n protocolError(\"fin.status must be a nonnegative safe integer\");\n if (value[\"retry\"] !== undefined && !isNonnegativeSafeInteger(value[\"retry\"]))\n protocolError(\"fin.retry must be a nonnegative safe integer\");\n return true;\n};\n\nconst validateEnvelope = (value: unknown): StatewireEnvelope => {\n if (!isObject(value)) return protocolError(\"packet must be an object\");\n if (value[\"ops\"] !== undefined && !Array.isArray(value[\"ops\"])) {\n protocolError(\"ops must be an array\");\n }\n if (value[\"cmd\"] !== undefined) {\n const cmd = value[\"cmd\"];\n if (!Array.isArray(cmd)) return protocolError(\"cmd must be an array\");\n for (const entry of cmd) validateCmd(entry);\n }\n if (value[\"idle\"] !== undefined && typeof value[\"idle\"] !== \"boolean\") {\n protocolError(\"idle must be a boolean\");\n }\n if (value[\"next\"] !== undefined) {\n if (value[\"next\"] !== true) protocolError(\"next must be true\");\n if (\n value[\"cmd\"] === undefined ||\n value[\"ops\"] !== undefined ||\n value[\"fin\"] !== undefined\n ) {\n protocolError(\"next must ride a cmd-only packet\");\n }\n }\n if (value[\"fin\"] !== undefined) validateFin(value[\"fin\"]);\n return value as StatewireEnvelope;\n};\n\n// A hold's release delivers, never settles: fin and bare close both drain it.\nconst releaseHeld = (c: Conn) => {\n if (c.held.length === 0) return;\n const held = c.held;\n c.held = [];\n c.ctx.onCmds(held);\n};\n\nconst cmdsOf = (env: StatewireEnvelope): StatewireCmd[] => {\n if (env.cmd === undefined) return [];\n return env.cmd;\n};\n\nconst snapshotOf = (env: StatewireEnvelope): unknown[] => {\n const ops = env.ops;\n if (ops === undefined) return protocolError(\"hello missing snapshot\");\n if (ops.length === 0) return protocolError(\"hello missing snapshot\");\n return reviveOps(ops);\n};\n\nconst helloSynOf = (env: StatewireEnvelope): StatewireSyn => {\n if (!isObject(env.syn)) return protocolError(\"expected hello\");\n validateSynScalars(env.syn);\n if (env.syn[\"retry\"] !== undefined) {\n protocolError(\"hello syn cannot carry retry\");\n }\n return env.syn as StatewireSyn;\n};\n\nconst handleHello = (c: Conn, env: StatewireEnvelope) => {\n const syn = helloSynOf(env);\n const ops = snapshotOf(env);\n c.ctx.onHello({\n ops,\n syn,\n });\n c.ctx.onIdle(syn.idle ?? false);\n c.awaitingHello = false;\n c.error = undefined;\n c.liveAt = Date.now();\n c.authDenials = 0;\n c.episode = null;\n setStatus(c, { status: \"connected\" });\n};\n\nlet warnedUnknownSynFact = false;\n\nconst warnUnknownSynFact = () => {\n if (warnedUnknownSynFact) return;\n warnedUnknownSynFact = true;\n console.warn(\"statewire: ignoring unknown syn fact\");\n};\n\nconst deliverSyn = (c: Conn, value: unknown) => {\n if (!isObject(value)) return protocolError(\"syn must be an object\");\n validateSynScalars(value);\n if (value[\"record\"] !== undefined || value[\"idle\"] !== undefined) {\n protocolError(\"invalid mid-stream syn\");\n }\n if (\n value[\"seq\"] !== undefined ||\n value[\"ctxSeq\"] !== undefined ||\n value[\"retry\"] !== undefined\n ) {\n c.ctx.onRefusal(validateRefusalSyn(value));\n return;\n }\n if (value[\"id\"] !== undefined) return;\n if (Object.keys(value).length === 0) {\n protocolError(\"invalid mid-stream syn\");\n }\n warnUnknownSynFact();\n};\n\nconst deliverEnvelope = (c: Conn, env: StatewireEnvelope) => {\n if (env.syn !== undefined) deliverSyn(c, env.syn);\n if (env.ops !== undefined) {\n const held = c.held;\n c.held = [];\n c.ctx.onOps({\n ops: reviveOps(env.ops),\n cmds: [...held, ...cmdsOf(env)],\n });\n } else if (env.cmd !== undefined) {\n const cmds = cmdsOf(env);\n if (env.next === true) c.held.push(...cmds);\n else c.ctx.onCmds(cmds);\n }\n if (typeof env.idle === \"boolean\") c.ctx.onIdle(env.idle);\n};\n\nexport const settleFin = (c: Conn, fin: StatewireHost.Fin): AttemptOutcome => {\n const finish =\n fin.meta === undefined\n ? fin\n : { ...fin, payload: reviveValue(fin.payload, fin.meta) };\n releaseHeld(c);\n c.retryAt = Date.now() + (finish.retry ?? 0);\n if (finish.reason === \"idle\") {\n c.ctx.onIdle(true);\n c.attempt = 0;\n c.episode = null;\n c.error = undefined;\n setStatus(c, { status: \"disconnected\", reason: \"idle\" });\n return \"idle\";\n }\n const denial = finish.reason === \"unauthorized\";\n const terminal =\n finish.reason === \"gone\" ||\n finish.reason === \"forbidden\" ||\n finish.reason === \"protocol-error\" ||\n (denial && c.authDenials > 0);\n if (denial) {\n c.authDenials += 1;\n c.authStale = true;\n }\n const report =\n finish.reason !== \"reconnect\" &&\n finish.reason !== \"gone\" &&\n (!denial || terminal);\n c.error = report\n ? Object.assign(new Error(finish.message ?? finish.reason), { fin: finish })\n : undefined;\n if (c.error) c.onError(c.error);\n if (!terminal) {\n c.episode = {\n cause:\n finish.reason === \"reconnect\"\n ? \"reconnect\"\n : denial\n ? \"unauthorized\"\n : \"error\",\n ...(finish.message !== undefined && { message: finish.message }),\n ...(finish.code !== undefined && { code: finish.code }),\n };\n if (denial) openGate(c.gate);\n return \"reattach\";\n }\n c.ctx.onFinish(finish);\n setStatus(c, {\n status: \"disconnected\",\n reason: denial\n ? \"unauthorized\"\n : finish.reason === \"forbidden\"\n ? \"forbidden\"\n : finish.reason === \"protocol-error\"\n ? \"protocol-error\"\n : \"gone\",\n ...(finish.message !== undefined && { message: finish.message }),\n ...(finish.payload !== undefined && { payload: finish.payload }),\n });\n return \"stop\";\n};\n\nexport const handleEnvelope = (\n c: Conn,\n value: unknown,\n): AttemptOutcome | null => {\n const env = validateEnvelope(value);\n if (c.awaitingHello) {\n if (env.fin !== undefined) return settleFin(c, env.fin);\n handleHello(c, env);\n return null;\n }\n deliverEnvelope(c, env);\n if (env.fin !== undefined) return settleFin(c, env.fin);\n if (c.connection.status !== \"connected\") {\n c.attempt = 0;\n c.episode = null;\n setStatus(c, { status: \"connected\" });\n }\n return null;\n};\n\nconst dialing = (c: Conn) =>\n setStatus(c, { status: \"connecting\", attempt: c.attempt });\n\nconst scheduleRetry = (\n c: Conn,\n failure: string | undefined,\n immediate: boolean,\n) => {\n if (c.liveAt !== null && Date.now() - c.liveAt >= timing.backoffResetMs) {\n c.attempt = 0;\n }\n c.liveAt = null;\n c.attempt += 1;\n c.episode ??= { cause: \"dropped\" };\n const delay = Math.max(\n immediate ? 0 : backoff(c.attempt),\n c.retryAt - Date.now(),\n );\n setStatus(c, {\n status: \"connecting\",\n attempt: c.attempt,\n ...c.episode,\n nextRetryAt: Date.now() + delay,\n ...(failure !== undefined && { lastError: failure }),\n });\n return delay;\n};\n\nconst reconnectConn = (c: Conn) => {\n if (!c.lifetime) return;\n if (c.connection.status === \"disconnected\") {\n // A reconnect out of a park starts a fresh episode: first dial, attempt 0.\n c.attempt = 0;\n c.authDenials = 0;\n c.episode = null;\n flushTapSync(() => dialing(c));\n }\n openGate(c.gate);\n c.abort?.abort();\n};\n\nconst run = async (c: Conn, lifetime: AbortController, gate: Gate) => {\n const alive = () => c.lifetime === lifetime;\n if (c.parked) {\n await waitGate(gate);\n if (!alive()) return;\n c.parked = false;\n }\n while (alive()) {\n if (c.retryAt > Date.now()) {\n await waitGate(gate, c.retryAt - Date.now());\n continue;\n }\n // The dial is in flight: attempt alone, no backoff fields.\n flushTapSync(() => dialing(c));\n const abort = new AbortController();\n c.abort = abort;\n c.awaitingHello = true;\n c.held = [];\n let outcome: AttemptOutcome | undefined;\n let failure: string | undefined;\n try {\n outcome = await c.attemptOnce(abort);\n } catch (error) {\n if (!abort.signal.aborted) {\n if (\n error instanceof SyntaxError ||\n (error instanceof Error &&\n /^(statewire: protocol error:|Statewire cannot apply)/.test(\n error.message,\n ))\n ) {\n outcome = flushTapSync(() =>\n settleFin(c, { reason: \"protocol-error\", message: error.message }),\n );\n } else if (c.authStale) {\n outcome = flushTapSync(() =>\n settleFin(c, {\n reason: \"unauthorized\",\n message:\n error instanceof Error\n ? error.message\n : \"credential refresh failed\",\n }),\n );\n }\n failure =\n error instanceof Error && error.message ? error.message : \"network\";\n }\n }\n abort.abort();\n if (!alive()) return;\n flushTapSync(() => {\n releaseHeld(c);\n c.ctx.onDetach();\n });\n if (!alive()) return;\n if (\n c.connection.status === \"disconnected\" &&\n ![\"deferred\", \"idle\"].includes(c.connection.reason)\n )\n outcome = \"stop\";\n if (outcome === \"stop\" || outcome === \"idle\") {\n await waitGate(gate);\n continue;\n }\n const immediate = takeGate(gate);\n const delay = flushTapSync(() => scheduleRetry(c, failure, immediate));\n if (delay === 0) continue;\n await waitGate(gate, delay);\n }\n};\n\nexport const unsupportedVersion = (c: Conn, version: string): AttemptOutcome =>\n flushTapSync(() =>\n handleEnvelope(c, {\n fin: {\n reason: \"protocol-error\",\n message: `unsupported protocol version: ${version || '\"\"'}`,\n },\n }),\n ) ?? \"stop\";\n\nexport const resolveHeaders = async (\n headers: StatewireClient.HeadersOption | undefined,\n staleAuth: boolean,\n): Promise<Headers> =>\n new Headers(\n typeof headers === \"function\" ? await headers({ staleAuth }) : headers,\n );\n\n// The stale flag clears only once the resolver returns: a throwing refresh keeps it for the next attempt.\nexport const attachFrame = async (\n c: Conn,\n headers: StatewireClient.HeadersOption | undefined,\n) => {\n const resolved = await resolveHeaders(headers, c.authStale);\n c.authStale = false;\n const lanes = flushTapSync(() => c.ctx.takeAttachLanes());\n return {\n headers: resolved,\n body: JSON.stringify({\n headers: Object.fromEntries(resolved),\n ...(lanes ?? {}),\n }),\n };\n};\n\nexport const useLink = (props: {\n url: string;\n isNew?: boolean | undefined;\n onChange?: ((connection: StatewireClient.Connection) => void) | undefined;\n onError?: ((error: StatewireClient.ConnectionError) => void) | undefined;\n ctx: StatewireRemoteProps;\n attemptOnce: (c: Conn, abort: AbortController) => Promise<AttemptOutcome>;\n send: (c: Conn, frame: StatewireFrame) => Promise<StatewireSubmitResult>;\n}): RemoteLink => {\n const latest = useRef(props);\n latest.current = props;\n const [, setConnection] = useState<StatewireClient.Connection | null>(null);\n const [c] = useState((): Conn => {\n const conn: Conn = {\n parked: props.isNew ?? false,\n lease: null,\n attempt: 0,\n authDenials: 0,\n authStale: false,\n liveAt: null,\n retryAt: 0,\n error: undefined,\n episode: null,\n awaitingHello: true,\n held: [],\n abort: null,\n gate: newGate(),\n lifetime: null,\n get ctx() {\n return latest.current.ctx;\n },\n onError: (error) => latest.current.onError?.(error),\n onChange: (connection) => latest.current.onChange?.(connection),\n attemptOnce: (abort) => latest.current.attemptOnce(conn, abort),\n notify: setConnection,\n reconnect: () => reconnectConn(conn),\n connection: {\n ...((props.isNew ?? false)\n ? { status: \"disconnected\", reason: \"deferred\" }\n : { status: \"connecting\", attempt: 0 }),\n degraded: false,\n reconnect: () => conn.reconnect(),\n },\n };\n return conn;\n });\n const [link] = useState(\n (): RemoteLink => ({\n get connection() {\n return c.connection;\n },\n get parked() {\n return c.parked;\n },\n send: (frame) => latest.current.send(c, frame),\n }),\n );\n\n useEffect(() => {\n c.lease = null;\n c.attempt = 0;\n c.authDenials = 0;\n c.authStale = false;\n c.liveAt = null;\n c.retryAt = 0;\n c.episode = null;\n setStatus(\n c,\n c.parked\n ? { status: \"disconnected\", reason: \"deferred\" }\n : { status: \"connecting\", attempt: 0 },\n );\n const lifetime = new AbortController();\n c.lifetime = lifetime;\n c.gate = newGate();\n void run(c, lifetime, c.gate);\n return () => {\n c.abort?.abort();\n lifetime.abort();\n if (c.lifetime === lifetime) c.lifetime = null;\n openGate(c.gate);\n };\n }, [c, props.url]);\n\n return link;\n};\n\nexport const classify = (timeout: AbortSignal) =>\n new Error(timeout.aborted ? \"timeout\" : \"network\");\n"],"mappings":"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"}
@@ -21,6 +21,7 @@ type Core = {
21
21
  notifiedPublished: unknown;
22
22
  notifiedOverlayPaths: readonly StatewirePath[];
23
23
  notifiedConnection: StatewireClient.Connection | null;
24
+ notifiedError: StatewireClient.ConnectionError | undefined;
24
25
  notifiedBusy: boolean;
25
26
  notifiedFailed: readonly StatewireClient.FailedCommand[];
26
27
  stale: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"core.d.ts","names":[],"sources":["../../src/client/core.ts"],"mappings":";;;;;cAYa,sBAAsB,gBAAgB;KAiB9C;EAAY,KAAK;EAAoB;;KAErC;EACH,kBAAkB,gBAAgB;EAClC,OAAO;EACP,QAAQ,UAAU;EAClB,UAAU;EACV,SAAS,UAAU;EACnB,gBAAgB;EAChB;EACA;EACA,uBAAuB;EACvB;EACA,+BAA+B;EAC/B,oBAAoB,gBAAgB;EACpC;EACA,yBAAyB,gBAAgB;EACzC;EACA;EACA;EACA;EACA;EACA,WAAW;EACX;EACA,kBAAkB;EAClB;EACA;EACA,0BAA0B,gBAAgB;EAC1C,UAAU,UAAU;EACpB,YAAY;;cA+ND,mBACX,kBAAkB,gBAAgB,mBAClC,OAAO,uBAAkB"}
1
+ {"version":3,"file":"core.d.ts","names":[],"sources":["../../src/client/core.ts"],"mappings":";;;;;cAYa,sBAAsB,gBAAgB;KAiB9C;EAAY,KAAK;EAAoB;;KAErC;EACH,kBAAkB,gBAAgB;EAClC,OAAO;EACP,QAAQ,UAAU;EAClB,UAAU;EACV,SAAS,UAAU;EACnB,gBAAgB;EAChB;EACA;EACA,uBAAuB;EACvB;EACA,+BAA+B;EAC/B,oBAAoB,gBAAgB;EACpC,eAAe,gBAAgB;EAC/B;EACA,yBAAyB,gBAAgB;EACzC;EACA;EACA;EACA;EACA;EACA,WAAW;EACX;EACA,kBAAkB;EAClB;EACA;EACA,0BAA0B,gBAAgB;EAC1C,UAAU,UAAU;EACpB,YAAY;;cAmOD,mBACX,kBAAkB,gBAAgB,mBAClC,OAAO,uBAAkB"}