statewire 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +2 -2
  2. package/dist/client/connection.d.ts +6 -4
  3. package/dist/client/connection.d.ts.map +1 -1
  4. package/dist/client/connection.js +10 -2
  5. package/dist/client/connection.js.map +1 -1
  6. package/dist/client/core.d.ts +1 -1
  7. package/dist/client/session.d.ts +4 -2
  8. package/dist/client/session.d.ts.map +1 -1
  9. package/dist/client/session.js +102 -102
  10. package/dist/client/session.js.map +1 -1
  11. package/dist/host/host.d.ts +1 -1
  12. package/dist/host/http.js +4 -3
  13. package/dist/host/http.js.map +1 -1
  14. package/dist/host/socket.d.ts +6 -5
  15. package/dist/host/socket.d.ts.map +1 -1
  16. package/dist/host/socket.js +12 -20
  17. package/dist/host/socket.js.map +1 -1
  18. package/dist/host-internal.d.ts +2 -1
  19. package/dist/host-internal.d.ts.map +1 -1
  20. package/dist/host.d.ts +2 -2
  21. package/dist/host.d.ts.map +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/statewire.d.ts +15 -4
  24. package/dist/statewire.d.ts.map +1 -1
  25. package/dist/statewire.js.map +1 -1
  26. package/dist/transports/fake-sse.js +4 -4
  27. package/dist/transports/fake-sse.js.map +1 -1
  28. package/dist/transports/fixtures.d.ts +1 -1
  29. package/dist/transports/remote.d.ts +1 -1
  30. package/dist/transports/remote.js +1 -0
  31. package/dist/transports/remote.js.map +1 -1
  32. package/dist/transports/sse.d.ts +2 -2
  33. package/dist/transports/testing.d.ts +1 -1
  34. package/dist/transports/testing.js +2 -4
  35. package/dist/transports/testing.js.map +1 -1
  36. package/dist/wire/meta.d.ts +5 -3
  37. package/dist/wire/meta.d.ts.map +1 -1
  38. package/dist/wire/meta.js +7 -4
  39. package/dist/wire/meta.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/client/connection.ts +12 -6
  42. package/src/client/session.ts +145 -139
  43. package/src/host/http.ts +10 -9
  44. package/src/host/socket.ts +18 -28
  45. package/src/host-internal.ts +2 -1
  46. package/src/host.ts +0 -1
  47. package/src/statewire.ts +18 -4
  48. package/src/transports/fake-sse.ts +6 -6
  49. package/src/transports/remote.ts +1 -0
  50. package/src/transports/testing.ts +2 -7
  51. package/src/wire/meta.ts +12 -4
package/README.md CHANGED
@@ -33,13 +33,13 @@ const onClick = async () => {
33
33
  - `"crashed"` — acked, but the processing died before producing a result
34
34
  - `"result-unavailable"` — acked, but the result can no longer be delivered
35
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), `"superseded"` = another attach took the writer lease, `"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"]`.
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
37
 
38
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
39
 
40
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
41
 
42
- Sessions are single-writer: every attach takes a writer lease, and any older stream for the same client id ends with `finish(superseded)` — that client goes `gone` (no reconnect) and its pending sends fail with reason `"superseded"`.
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.
43
43
 
44
44
  ## Three ways to mount
45
45
 
@@ -1,14 +1,16 @@
1
- import { StatewireClient } from "../statewire.js";
2
1
  import { StatewireHost } from "../host.js";
3
2
  import { StatewireRemoteProps } from "../transports/sse.js";
3
+ import { StatewireClient } from "../statewire.js";
4
4
  //#region src/client/connection.d.ts
5
5
  type Episode = {
6
6
  cause: "dropped" | "evicted" | "error" | "unauthorized";
7
7
  message?: string;
8
8
  };
9
+ /** The terminal cmd verdict types; "applied" is the sole non-terminal type. */
10
+ declare const TERMINAL: readonly ["result", "rejected", "failed", "crashed", "expired"];
9
11
  type StatewireCmd = {
10
12
  seq: number;
11
- type?: "applied" | "result" | "rejected" | "failed" | "crashed" | "expired";
13
+ type?: "applied" | (typeof TERMINAL)[number];
12
14
  code?: string;
13
15
  message?: string;
14
16
  payload?: unknown;
@@ -19,7 +21,7 @@ type StatewireCmd = {
19
21
  type StatewireSyn = {
20
22
  seq?: number;
21
23
  ctxSeq?: number;
22
- record?: "fresh" | "resumed" | "ephemeral";
24
+ record?: "ephemeral";
23
25
  id?: string;
24
26
  idle?: boolean;
25
27
  retry?: number;
@@ -66,5 +68,5 @@ declare const useStatewireConn: (props: {
66
68
  }) => Conn;
67
69
  declare const classify: (timeout: AbortSignal) => Error;
68
70
  //#endregion
69
- export { AttemptOutcome, Conn, Episode, Gate, StatewireCmd, StatewireEnvelope, StatewireSyn, classify, handleEnvelope, useStatewireConn, validateRefusalSyn };
71
+ export { AttemptOutcome, Conn, Episode, Gate, StatewireCmd, StatewireEnvelope, StatewireSyn, TERMINAL, classify, handleEnvelope, useStatewireConn, validateRefusalSyn };
70
72
  //# sourceMappingURL=connection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"connection.d.ts","names":[],"sources":["../../src/client/connection.ts"],"mappings":";;;;KAQY;EACV;EACA;;KAGU;EACV;EACA;EACA;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,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;;cA8CW,qBAAsB,mBAAiB;cAiOvC,iBACX,GAAG,MACH,mBACC;cAwFU,mBAAoB;EAC/B;EACA,aAAa,YAAY,gBAAgB;EACzC,KAAK;EACL,cAAc,GAAG,MAAM,OAAO,oBAAoB,QAAQ;MACxD;cAsDS,WAAY,SAAS,gBAAW"}
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,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;;cA8CW,qBAAsB,mBAAiB;cA8NvC,iBACX,GAAG,MACH,mBACC;cAwFU,mBAAoB;EAC/B;EACA,aAAa,YAAY,gBAAgB;EACzC,KAAK;EACL,cAAc,GAAG,MAAM,OAAO,oBAAoB,QAAQ;MACxD;cAsDS,WAAY,SAAS,gBAAW"}
@@ -3,6 +3,14 @@ import { backoff, timing } from "./timing.js";
3
3
  import { useEffect, useRef, useState } from "@assistant-ui/tap/react-shim";
4
4
  import { flushTapSync } from "@assistant-ui/tap";
5
5
  //#region src/client/connection.ts
6
+ /** The terminal cmd verdict types; "applied" is the sole non-terminal type. */
7
+ const TERMINAL = [
8
+ "result",
9
+ "rejected",
10
+ "failed",
11
+ "crashed",
12
+ "expired"
13
+ ];
6
14
  const newGate = () => ({
7
15
  latched: false,
8
16
  wake: void 0
@@ -196,7 +204,7 @@ const settleFin = (c, fin) => {
196
204
  return "idle";
197
205
  }
198
206
  const denial = finish.reason === "unauthorized";
199
- const terminal = finish.reason === "gone" || finish.reason === "superseded" || denial && c.authDenials > 0;
207
+ const terminal = finish.reason === "gone" || denial && c.authDenials > 0;
200
208
  if (denial) c.authDenials += 1;
201
209
  c.ctx.onFinish(terminal ? finish : {
202
210
  ...finish,
@@ -346,6 +354,6 @@ const useStatewireConn = (props) => {
346
354
  };
347
355
  const classify = (timeout) => /* @__PURE__ */ new Error(timeout.aborted ? "timeout" : "network");
348
356
  //#endregion
349
- export { classify, handleEnvelope, useStatewireConn, validateRefusalSyn };
357
+ export { TERMINAL, classify, handleEnvelope, useStatewireConn, validateRefusalSyn };
350
358
 
351
359
  //# 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","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","hasSnapshot","lease","attempt","authDenials","liveAt","episode","awaitingHello","held","gate","abort","AbortController","lifetime","ctx","onChange","attemptOnce","notify","reconnect","setStatus","c","changed","status","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","reason","settleFin","finish","rest","denial","terminal","onFinish","handleEnvelope","scheduleRetry","failure","immediate","backoffResetMs","delay","nextRetryAt","lastError","reconnectConn","run","alive","outcome","error","signal","aborted","onDetach","useStatewireConn","props","url","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\nexport type StatewireCmd = {\n seq: number;\n type?: \"applied\" | \"result\" | \"rejected\" | \"failed\" | \"crashed\" | \"expired\";\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?: \"fresh\" | \"resumed\" | \"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 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\nconst setStatus = (c: Conn, next: ConnectionState) => {\n const changed = next.status !== c.connection.status;\n c.connection = { ...next, reconnect: c.reconnect };\n c.notify(c.connection);\n if (changed) 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: \"live\" });\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: \"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 =\n finish.reason === \"gone\" ||\n finish.reason === \"superseded\" ||\n (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: \"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 !== \"live\") {\n c.attempt = 0;\n c.episode = null;\n setStatus(c, { status: \"live\" });\n }\n return null;\n};\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 = immediate ? 0 : backoff(c.attempt);\n setStatus(c, {\n status: \"reconnecting\",\n ...c.episode,\n attempt: c.attempt,\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 === \"gone\" || c.connection.status === \"idle\") {\n c.attempt = 0;\n c.authDenials = 0;\n c.episode = { cause: \"dropped\" };\n flushTapSync(() => {\n scheduleRetry(c, undefined, true);\n });\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 while (alive()) {\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 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 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: { status: \"connecting\", reconnect: () => conn.reconnect() },\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 if (c.connection.status !== \"connecting\") {\n setStatus(c, { status: \"connecting\" });\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","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","hasSnapshot","lease","attempt","authDenials","liveAt","episode","awaitingHello","held","gate","abort","AbortController","lifetime","ctx","onChange","attemptOnce","notify","reconnect","setStatus","c","changed","status","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","reason","settleFin","finish","rest","denial","terminal","onFinish","handleEnvelope","scheduleRetry","failure","immediate","backoffResetMs","delay","nextRetryAt","lastError","reconnectConn","run","alive","outcome","error","signal","aborted","onDetach","useStatewireConn","props","url","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 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\nconst setStatus = (c: Conn, next: ConnectionState) => {\n const changed = next.status !== c.connection.status;\n c.connection = { ...next, reconnect: c.reconnect };\n c.notify(c.connection);\n if (changed) 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: \"live\" });\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: \"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: \"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 !== \"live\") {\n c.attempt = 0;\n c.episode = null;\n setStatus(c, { status: \"live\" });\n }\n return null;\n};\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 = immediate ? 0 : backoff(c.attempt);\n setStatus(c, {\n status: \"reconnecting\",\n ...c.episode,\n attempt: c.attempt,\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 === \"gone\" || c.connection.status === \"idle\") {\n c.attempt = 0;\n c.authDenials = 0;\n c.episode = { cause: \"dropped\" };\n flushTapSync(() => {\n scheduleRetry(c, undefined, true);\n });\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 while (alive()) {\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 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 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: { status: \"connecting\", reconnect: () => conn.reconnect() },\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 if (c.connection.status !== \"connecting\") {\n setStatus(c, { status: \"connecting\" });\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,7 +1,7 @@
1
+ import { CommandsInvocation, CommandsListener } from "../host/host.js";
1
2
  import { StatewireClient } from "../statewire.js";
2
3
  import { Statewire } from "../types.js";
3
4
  import { StatewirePath } from "../wire/ops.js";
4
- import { CommandsInvocation, CommandsListener } from "../host/host.js";
5
5
  //#region src/client/core.d.ts
6
6
  declare const LIVE_CONNECTION: StatewireClient.Connection;
7
7
  type Overlay = {
@@ -1,8 +1,8 @@
1
+ import { CommandsInvocation, CommandsListener } from "../host/host.js";
2
+ import { RemoteLink, StatewireRemoteProps } from "../transports/sse.js";
1
3
  import { StatewireClient } from "../statewire.js";
2
4
  import { Statewire, StatewireSendError } from "../types.js";
3
5
  import { StatewirePath } from "../wire/ops.js";
4
- import { CommandsInvocation, CommandsListener } from "../host/host.js";
5
- import { RemoteLink, StatewireRemoteProps } from "../transports/sse.js";
6
6
  //#region src/client/session.d.ts
7
7
  type WireCommand = {
8
8
  method: string;
@@ -30,6 +30,7 @@ type CtxPending = {
30
30
  type AttachState = "detached" | "attached" | "finished";
31
31
  type StatewireSessionOptions = {
32
32
  sessionStore?: StatewireClient.SessionStorage | undefined;
33
+ onSessionLost?: StatewireClient.OnSessionLost | undefined;
33
34
  onDone?: (() => void) | undefined;
34
35
  };
35
36
  type Shared = {
@@ -50,6 +51,7 @@ type Session = {
50
51
  durableSeq: number;
51
52
  resumeAt: number;
52
53
  idle: boolean;
54
+ onSessionLost: StatewireClient.OnSessionLost | undefined;
53
55
  onDone: (() => void) | undefined;
54
56
  ctxOps: unknown[] | undefined;
55
57
  ctxPending: CtxPending[];
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","names":[],"sources":["../../src/client/session.ts"],"mappings":";;;;;;KA4BK;EACH;EACA;EACA;;KAGG;WACM;WACA,SAAS;WACT,KAAK;;EAEd;;EAEA;;EAEA;EACA,UAAU;EACV,SAAS,OAAO;;KAGb;WACM;WACA;EACT;;KAiBG;KAEO;EACV,eAAe,gBAAgB;EAC/B;;KAGG;EACH,KAAK;EACL,QAAQ,gBAAgB;EACxB;;KAGG;EACH,QAAQ,gBAAgB;EACxB,KAAK;EACL;EACA,OAAO,gBAAgB;EACvB,QAAQ;EACR;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YAAY;EACZ;EACA,0BAA0B,qBAAqB;EAC/C;EACA,SAAS;EACT;EACA;EACA,SAAS;EACT,QAAQ,UAAU;EAClB,QAAQ;EACR;EACA;EACA;EACA,WAAW;EACX;EACA,WAAW;EACX;EACA,UAAU,mBAAmB,sBAAsB;;cAgpB/C,eAAgB,GAAG,SAAS,WAAW;cA8GvC,aAAc,GAAG,SAAS,OAAO;cAgC1B,sBACX,SAAS,4BACR"}
1
+ {"version":3,"file":"session.d.ts","names":[],"sources":["../../src/client/session.ts"],"mappings":";;;;;;KA6BK;EACH;EACA;EACA;;KAGG;WACM;WACA,SAAS;WACT,KAAK;;EAEd;;EAEA;;EAEA;EACA,UAAU;EACV,SAAS,OAAO;;KAGb;WACM;WACA;EACT;;KAiBG;KAEO;EACV,eAAe,gBAAgB;EAC/B,gBAAgB,gBAAgB;EAChC;;KAGG;EACH,KAAK;EACL,QAAQ,gBAAgB;EACxB;;KAGG;EACH,QAAQ,gBAAgB;EACxB,KAAK;EACL;EACA,OAAO,gBAAgB;EACvB,QAAQ;EACR;;EAEA;EACA;EACA;EACA;EACA;EACA,eAAe,gBAAgB;EAC/B;EACA;EACA,YAAY;EACZ;EACA,0BAA0B,qBAAqB;EAC/C;EACA,SAAS;EACT;EACA;EACA,SAAS;EACT,QAAQ,UAAU;EAClB,QAAQ;EACR;EACA;EACA;EACA,WAAW;EACX;EACA,WAAW;EACX;EACA,UAAU,mBAAmB,sBAAsB;;cA4oB/C,eAAgB,GAAG,SAAS,WAAW;cA8GvC,aAAc,GAAG,SAAS,OAAO;cAgC1B,sBACX,SAAS,4BACR"}
@@ -5,6 +5,7 @@ import { bindCommandsHost } from "../host/host.js";
5
5
  import { encodeParams, encodeValue, reviveResponse, reviveValue } from "../wire/meta.js";
6
6
  import { SessionStorageContext, createMemorySessionStore } from "./sessionStore.js";
7
7
  import { backoff } from "./timing.js";
8
+ import { TERMINAL } from "./connection.js";
8
9
  import { randomId } from "../util/randomId.js";
9
10
  import { useContext, useEffect, useState } from "@assistant-ui/tap/react-shim";
10
11
  //#region src/client/session.ts
@@ -113,11 +114,7 @@ const settleEntry = (s, entry, outcome) => {
113
114
  }
114
115
  };
115
116
  const settleAll = (s, detach) => {
116
- const cause = {
117
- unmount: "unmount",
118
- gone: "stream-gone",
119
- superseded: "superseded"
120
- }[detach];
117
+ const cause = detach === "unmount" ? "unmount" : "stream-gone";
121
118
  const batch = s.pending;
122
119
  const detachFailure = (entry) => {
123
120
  if (!entry.attempted) {
@@ -158,9 +155,8 @@ const restateContext = (s) => {
158
155
  posted: false
159
156
  });
160
157
  };
161
- const resetRecord = (s, clientId) => {
162
- const survivors = [];
163
- for (const entry of [...s.pending]) if (entry.attempted || entry.applied) {
158
+ const settleLost = (s, entries) => {
159
+ for (const entry of entries) {
164
160
  s.lifecycle?.onCovered?.(entry.inv);
165
161
  settleEntry(s, entry, {
166
162
  ok: false,
@@ -169,39 +165,15 @@ const resetRecord = (s, clientId) => {
169
165
  cause: "session-lost"
170
166
  }
171
167
  });
172
- } else survivors.push(entry);
173
- s.pending = [];
174
- s.durableSeq = 0;
175
- setRecord(s, {
176
- clientId,
177
- nextSeq: 1,
178
- ctxNextSeq: 1,
179
- commands: []
180
- });
181
- for (const old of survivors) {
182
- s.lifecycle?.onCovered?.(old.inv);
183
- enqueueInner(s, old.command, old.inv.params, old.resolve, old.reject);
184
168
  }
185
- restateContext(s);
186
- publish(s);
187
- wake(s);
188
- pump(s);
189
169
  };
190
- const adoptReset = (s, stored) => {
191
- const survivors = [];
192
- for (const entry of [...s.pending]) if (entry.attempted || entry.applied) {
193
- s.lifecycle?.onCovered?.(entry.inv);
194
- settleEntry(s, entry, {
195
- ok: false,
196
- failure: {
197
- fate: "lost",
198
- cause: "session-lost"
199
- }
200
- });
201
- } else survivors.push(entry);
170
+ const rebaseRecord = (s, adopt) => {
171
+ const held = [...s.pending];
172
+ const survivors = held.filter((p) => !p.attempted && !p.applied);
173
+ settleLost(s, held.filter((p) => p.attempted || p.applied));
202
174
  s.pending = [];
203
175
  s.durableSeq = 0;
204
- s.record = stored;
176
+ adopt();
205
177
  for (const old of survivors) {
206
178
  s.lifecycle?.onCovered?.(old.inv);
207
179
  enqueueInner(s, old.command, old.inv.params, old.resolve, old.reject);
@@ -211,6 +183,15 @@ const adoptReset = (s, stored) => {
211
183
  wake(s);
212
184
  pump(s);
213
185
  };
186
+ const resetRecord = (s, clientId) => rebaseRecord(s, () => setRecord(s, {
187
+ clientId,
188
+ nextSeq: 1,
189
+ ctxNextSeq: 1,
190
+ commands: []
191
+ }));
192
+ const adoptReset = (s, stored) => rebaseRecord(s, () => {
193
+ s.record = stored;
194
+ });
214
195
  const ephemeralDetach = (s) => {
215
196
  s.ephemeral = false;
216
197
  s.sessionKnown = false;
@@ -225,39 +206,42 @@ const rejectedFailure = (cmd) => {
225
206
  ...cmd.payload !== void 0 && { payload: cmd.payload }
226
207
  };
227
208
  };
228
- const outcomeOf = (cmd) => cmd.type === "result" ? {
229
- ok: true,
230
- payload: reviveResponse(cmd).payload
231
- } : cmd.type === "rejected" ? {
232
- ok: false,
233
- failure: rejectedFailure(reviveResponse(cmd))
234
- } : cmd.type === "failed" ? {
235
- ok: false,
236
- failure: {
237
- fate: "failed",
238
- cause: "server",
239
- ...cmd.message !== void 0 && { message: cmd.message }
240
- }
241
- } : cmd.type === "crashed" ? {
242
- ok: false,
243
- failure: {
244
- fate: "crashed",
245
- cause: "server"
246
- }
247
- } : cmd.type === "expired" ? {
248
- ok: false,
249
- failure: {
250
- fate: "expired",
251
- cause: "server"
209
+ const outcomeOf = (cmd) => {
210
+ switch (cmd.type) {
211
+ case "result": return {
212
+ ok: true,
213
+ payload: reviveResponse(cmd).payload
214
+ };
215
+ case "rejected": return {
216
+ ok: false,
217
+ failure: rejectedFailure(reviveResponse(cmd))
218
+ };
219
+ case "failed": return {
220
+ ok: false,
221
+ failure: {
222
+ fate: "failed",
223
+ cause: "server",
224
+ ...cmd.message !== void 0 && { message: cmd.message }
225
+ }
226
+ };
227
+ case "crashed": return {
228
+ ok: false,
229
+ failure: {
230
+ fate: "crashed",
231
+ cause: "server"
232
+ }
233
+ };
234
+ case "expired": return {
235
+ ok: false,
236
+ failure: {
237
+ fate: "expired",
238
+ cause: "server"
239
+ }
240
+ };
241
+ default: return protocolError(`unknown cmd type: ${String(cmd.type)}`);
252
242
  }
253
- } : protocolError(`unknown cmd type: ${String(cmd.type)}`);
254
- const TERMINAL_CMD_TYPES = /* @__PURE__ */ new Set([
255
- "result",
256
- "rejected",
257
- "failed",
258
- "crashed",
259
- "expired"
260
- ]);
243
+ };
244
+ const TERMINAL_CMD_TYPES = new Set(TERMINAL);
261
245
  const parkVerdict = (s, cmd) => {
262
246
  if (!s.shared || !TERMINAL_CMD_TYPES.has(cmd.type)) return;
263
247
  s.shared.parked.push({
@@ -319,24 +303,7 @@ const publishIngested = (s) => {
319
303
  publish(s);
320
304
  wake(s);
321
305
  };
322
- const RECORDS = [
323
- "fresh",
324
- "resumed",
325
- "ephemeral"
326
- ];
327
- const snapClocks = (s, seqWm, ctxWm) => {
328
- const tail = s.pending.filter((p) => p.seq > seqWm);
329
- if (!(tail.every((p, i) => p.seq === seqWm + 1 + i) && s.record.nextSeq === seqWm + tail.length + 1)) {
330
- s.pending = s.pending.filter((p) => p.seq <= seqWm);
331
- setRecord(s, {
332
- ...s.record,
333
- nextSeq: seqWm + 1
334
- });
335
- for (const old of tail) {
336
- s.lifecycle?.onCovered?.(old.inv);
337
- enqueueInner(s, old.command, old.inv.params, old.resolve, old.reject);
338
- }
339
- }
306
+ const snapCtxClock = (s, ctxWm) => {
340
307
  if (!(s.ctxPending.every((c, i) => c.seq === ctxWm + 1 + i) && s.record.ctxNextSeq === ctxWm + s.ctxPending.length + 1)) {
341
308
  setRecord(s, {
342
309
  ...s.record,
@@ -345,28 +312,42 @@ const snapClocks = (s, seqWm, ctxWm) => {
345
312
  restateContext(s);
346
313
  }
347
314
  };
315
+ const sessionLostInner = (s, seqWm, ctxWm) => {
316
+ const held = [...s.pending];
317
+ settleLost(s, held);
318
+ s.pending = [];
319
+ s.durableSeq = seqWm;
320
+ setRecord(s, {
321
+ clientId: s.record.clientId,
322
+ nextSeq: seqWm + 1,
323
+ ctxNextSeq: ctxWm + 1,
324
+ commands: []
325
+ });
326
+ restateContext(s);
327
+ if (held.length > 0 && s.onSessionLost?.(held.map((p) => p.inv)) === "requeue") for (const old of held) enqueueInner(s, old.command, old.inv.params, () => {}, () => {});
328
+ publish(s);
329
+ wake(s);
330
+ };
348
331
  const handleHello = (s, { ops, syn }) => {
349
- const record = syn.record ?? "fresh";
350
- if (!RECORDS.includes(record)) protocolError(`unknown record: ${String(record)}`);
332
+ if (syn.record !== void 0 && syn.record !== "ephemeral") protocolError(`unknown record: ${String(syn.record)}`);
351
333
  ingestOps(s, ops);
352
334
  s.attach = "attached";
353
335
  s.attachEpoch += 1;
354
336
  s.frameFailures = 0;
355
337
  s.resumeAt = 0;
356
- s.ephemeral = record === "ephemeral";
338
+ s.ephemeral = syn.record === "ephemeral";
357
339
  if (s.ephemeral) s.deactivated = true;
358
340
  const [attachCmd, attachCtx] = s.attachLaneSeqs;
359
341
  s.attachLaneSeqs = [/* @__PURE__ */ new Set(), /* @__PURE__ */ new Set()];
360
342
  const reconcile = () => {
361
- if (record === "resumed") {
362
- if (s.shared) adoptStored(s);
363
- for (const entry of s.pending) entry.posted = attachCmd.has(entry.seq);
364
- const ctxWatermark = syn.ctxSeq ?? 0;
365
- s.ctxPending = s.ctxPending.filter((c) => c.seq > ctxWatermark);
366
- for (const c of s.ctxPending) c.posted = attachCtx.has(c.seq);
367
- snapClocks(s, syn.seq ?? 0, ctxWatermark);
368
- syncBacklogInner(s);
369
- } else resetRecord(s, s.record.clientId);
343
+ if (s.shared) adoptStored(s);
344
+ s.durableSeq = syn.seq ?? 0;
345
+ for (const entry of s.pending) entry.posted = attachCmd.has(entry.seq);
346
+ const ctxWatermark = syn.ctxSeq ?? 0;
347
+ s.ctxPending = s.ctxPending.filter((c) => c.seq > ctxWatermark);
348
+ for (const c of s.ctxPending) c.posted = attachCtx.has(c.seq);
349
+ snapCtxClock(s, ctxWatermark);
350
+ syncBacklogInner(s);
370
351
  s.sessionKnown = true;
371
352
  };
372
353
  if (!s.shared || s.deactivated) {
@@ -396,10 +377,26 @@ const handleRefusal = (s, syn) => {
396
377
  s.ctxPending = s.ctxPending.filter((c) => c.seq > ctxSeq);
397
378
  for (const c of s.ctxPending) c.posted = false;
398
379
  }
399
- if (typeof syn.retry === "number") s.resumeAt = Date.now() + syn.retry;
380
+ if (typeof syn.retry === "number") {
381
+ s.resumeAt = Date.now() + syn.retry;
382
+ wake(s);
383
+ pump(s);
384
+ return;
385
+ }
400
386
  const firstCmd = s.pending.find((p) => !p.posted);
401
387
  const firstCtx = s.ctxPending.find((c) => !c.posted);
402
- if (typeof syn.seq === "number" && firstCmd !== void 0 && firstCmd.seq > Math.max(syn.seq, s.durableSeq) + 1 || typeof syn.ctxSeq === "number" && firstCtx !== void 0 && firstCtx.seq > syn.ctxSeq + 1) s.transport?.connection.reconnect();
388
+ const cmdGap = typeof seq === "number" && firstCmd !== void 0 && firstCmd.seq > Math.max(seq, s.durableSeq) + 1;
389
+ const ctxGap = typeof ctxSeq === "number" && firstCtx !== void 0 && firstCtx.seq > ctxSeq + 1;
390
+ if (cmdGap || ctxGap) {
391
+ transact(s, () => {
392
+ if (cmdGap) sessionLostInner(s, seq, ctxSeq ?? 0);
393
+ else snapCtxClock(s, ctxSeq);
394
+ }).then(() => {
395
+ wake(s);
396
+ pump(s);
397
+ });
398
+ return;
399
+ }
403
400
  wake(s);
404
401
  pump(s);
405
402
  };
@@ -411,7 +408,7 @@ const handleDetach = (s) => {
411
408
  const handleFinish = (s, { reason }) => {
412
409
  if (reason === "evicted" || reason === "error") return;
413
410
  s.attach = "finished";
414
- if (!(s.shared && reason === "superseded")) settleAll(s, reason === "superseded" ? "superseded" : "gone");
411
+ settleAll(s, "gone");
415
412
  publish(s);
416
413
  wake(s);
417
414
  };
@@ -709,6 +706,7 @@ const useStatewireSession = (options) => {
709
706
  durableSeq: 0,
710
707
  resumeAt: 0,
711
708
  idle: false,
709
+ onSessionLost: void 0,
712
710
  onDone: void 0,
713
711
  ctxOps: void 0,
714
712
  ctxPending: [],
@@ -745,7 +743,9 @@ const useStatewireSession = (options) => {
745
743
  } });
746
744
  return session;
747
745
  });
746
+ if (store.lock !== void 0 !== (s.shared !== false)) throw new Error("statewire: session storage changed sharedness across renders — lock/subscribe must be present from the first render or never");
748
747
  s.store = store;
748
+ s.onSessionLost = options.onSessionLost;
749
749
  s.onDone = options.onDone;
750
750
  useEffect(() => {
751
751
  start(s);