statewire 0.4.0 → 0.6.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 (44) hide show
  1. package/README.md +1 -1
  2. package/dist/client/connection.d.ts +1 -1
  3. package/dist/client/connection.js.map +1 -1
  4. package/dist/client/core.d.ts.map +1 -1
  5. package/dist/client/core.js +1 -0
  6. package/dist/client/core.js.map +1 -1
  7. package/dist/client/session.d.ts +1 -5
  8. package/dist/client/session.d.ts.map +1 -1
  9. package/dist/client/session.js +4 -16
  10. package/dist/client/session.js.map +1 -1
  11. package/dist/index.d.ts +2 -1
  12. package/dist/index.js +2 -1
  13. package/dist/statewire.d.ts +8 -14
  14. package/dist/statewire.d.ts.map +1 -1
  15. package/dist/statewire.js.map +1 -1
  16. package/dist/transports/remote.js +1 -4
  17. package/dist/transports/remote.js.map +1 -1
  18. package/dist/transports/sse.d.ts +4 -1
  19. package/dist/transports/sse.d.ts.map +1 -1
  20. package/dist/transports/sse.js +41 -25
  21. package/dist/transports/sse.js.map +1 -1
  22. package/dist/transports/testing.d.ts +1 -2
  23. package/dist/transports/testing.d.ts.map +1 -1
  24. package/dist/transports/testing.js +1 -3
  25. package/dist/transports/testing.js.map +1 -1
  26. package/dist/transports/ws.d.ts +2 -1
  27. package/dist/transports/ws.d.ts.map +1 -1
  28. package/dist/transports/ws.js +25 -6
  29. package/dist/transports/ws.js.map +1 -1
  30. package/dist/util/recordMutations.d.ts +3 -1
  31. package/dist/util/recordMutations.d.ts.map +1 -1
  32. package/dist/util/recordMutations.js +3 -1
  33. package/dist/util/recordMutations.js.map +1 -1
  34. package/package.json +1 -1
  35. package/src/client/connection.ts +1 -1
  36. package/src/client/core.ts +1 -0
  37. package/src/client/session.ts +9 -26
  38. package/src/index.ts +1 -0
  39. package/src/statewire.ts +8 -14
  40. package/src/transports/remote.ts +1 -7
  41. package/src/transports/sse.ts +71 -27
  42. package/src/transports/testing.ts +1 -6
  43. package/src/transports/ws.ts +35 -4
  44. package/src/util/recordMutations.ts +0 -0
package/README.md CHANGED
@@ -35,7 +35,7 @@ const onClick = async () => {
35
35
 
36
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"]`.
37
37
 
38
- Session-lost failures are opt-out per command: the transport option `onCommandSessionLost({ method, params, seq })` is offered each pending command during an eviction, in seq order; return `"replay"` to re-enqueue it under the rotated client id (fresh seq, relative order preserved, the original promise settles with the replay's outcome) instead of settling it as a failure. Context submissions are never offered; replay may re-run a command the lost session already applied.
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
 
@@ -3,7 +3,7 @@ import { StatewireHost } from "../host.js";
3
3
  import { StatewireRemoteProps } from "../transports/sse.js";
4
4
  //#region src/client/connection.d.ts
5
5
  type Episode = {
6
- cause: "dropped" | "evicted" | "error";
6
+ cause: "dropped" | "evicted" | "error" | "unauthorized";
7
7
  message?: string;
8
8
  };
9
9
  type StatewireResponse = {
@@ -1 +1 @@
1
- {"version":3,"file":"connection.js","names":["useEffect","useRef","useState","flushTapSync","reviveOps","reviveResponse","reviveValue","retryDelay","ConnectionState","StatewireRemoteProps","StatewireClient","StatewireHost","Episode","cause","message","StatewireResponse","seq","type","code","payload","meta","StatewireSyn","lastSeq","lease","StatewireEnvelope","ops","res","ack","syn","fin","Fin","createGate","latched","wake","take","was","wait","timeoutMs","Promise","resolve","timer","undefined","setTimeout","clearTimeout","open","Gate","ReturnType","Conn","connection","Connection","hasSnapshot","attempt","episode","gate","abort","AbortController","lifetime","ctx","onChange","attemptOnce","notify","reconnect","setStatus","c","next","changed","status","deliverEnvelope","env","Pick","map","onSnapshot","onOps","isTransientFinish","reason","settleEnvelope","finish","rest","onFinish","handleEnvelope","scheduleRetry","failure","immediate","delay","nextRetryAt","Date","now","lastError","reconnectConn","run","alive","gone","error","signal","aborted","Error","useStatewireConn","props","url","latest","current","setConnection","conn","classify","timeout","AbortSignal","parseRetryAfter","header","value","trim","test","Number","date","parse","isNaN","Math","max","overloadedError","retryAfterMs","Object","assign"],"sources":["../../src/client/connection.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { flushTapSync } from \"@assistant-ui/tap\";\nimport { reviveOps, reviveResponse, reviveValue } from \"../wire/meta\";\nimport { retryDelay } 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\";\n message?: string;\n};\n\nexport type StatewireResponse = {\n seq: number;\n type: \"accepted\" | \"rejected\" | \"pending\" | \"crashed\" | \"result-unavailable\";\n code?: string;\n message?: string;\n payload?: unknown;\n meta?: unknown;\n};\n\nexport type StatewireSyn = {\n lastSeq: number;\n lease?: string;\n};\n\nexport type StatewireEnvelope = {\n ops?: unknown[];\n res?: StatewireResponse[];\n ack?: number;\n syn?: StatewireSyn;\n fin?: StatewireHost.Fin;\n};\n\nconst createGate = () => {\n let latched = false;\n let wake: (() => void) | undefined;\n return {\n take: () => {\n const was = latched;\n latched = false;\n return was;\n },\n wait: (timeoutMs?: number) => {\n if (latched) {\n latched = false;\n return Promise.resolve();\n }\n return new Promise<void>((resolve) => {\n const timer =\n timeoutMs === undefined\n ? undefined\n : setTimeout(() => {\n wake = undefined;\n resolve();\n }, timeoutMs);\n wake = () => {\n clearTimeout(timer);\n wake = undefined;\n resolve();\n };\n });\n },\n open: () => {\n if (wake) wake();\n else latched = true;\n },\n };\n};\n\nexport type Gate = ReturnType<typeof createGate>;\n\nexport type Conn = {\n connection: StatewireClient.Connection;\n hasSnapshot: boolean;\n lease: string | null;\n attempt: number;\n episode: Episode | null;\n gate: Gate;\n abort: AbortController | null;\n lifetime: AbortController | null;\n ctx: StatewireRemoteProps;\n onChange: (connection: StatewireClient.Connection) => void;\n attemptOnce: (\n abort: AbortController,\n ) => Promise<\"stop\" | \"reattach\" | \"ended\">;\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 deliverEnvelope = (\n c: Conn,\n env: Pick<StatewireEnvelope, \"ops\" | \"res\" | \"ack\" | \"syn\">,\n) => {\n if (env.syn?.lease !== undefined) c.lease = env.syn.lease;\n const ctx = c.ctx;\n const ops = env.ops === undefined ? [] : reviveOps(env.ops);\n const res = env.res?.map(reviveResponse);\n if (env.syn !== undefined) {\n ctx.onSnapshot({ ops, lastSeq: env.syn.lastSeq });\n c.hasSnapshot = true;\n } else if (\n env.ops !== undefined ||\n env.res !== undefined ||\n env.ack !== undefined\n ) {\n ctx.onOps({\n ops,\n ...(res !== undefined && { res }),\n ...(env.ack !== undefined && { ack: env.ack }),\n });\n }\n};\n\nconst isTransientFinish = (\n reason: StatewireHost.Fin[\"reason\"],\n): reason is \"evicted\" | \"error\" => reason === \"evicted\" || reason === \"error\";\n\nconst settleEnvelope = (\n c: Conn,\n env: StatewireEnvelope,\n): \"stop\" | \"reattach\" | null => {\n let finish = env.fin;\n if (finish) {\n if (finish.meta !== undefined) {\n const { meta, ...rest } = finish;\n finish = { ...rest, payload: reviveValue(rest.payload, meta) };\n }\n c.ctx.onFinish(finish);\n if (isTransientFinish(finish.reason)) {\n c.episode = {\n cause: finish.reason,\n ...(finish.message !== undefined && { message: finish.message }),\n };\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 if (c.connection.status !== \"live\") {\n c.attempt = 0;\n c.episode = null;\n setStatus(c, { status: \"live\" });\n }\n return null;\n};\n\nexport const handleEnvelope = (\n c: Conn,\n env: StatewireEnvelope,\n): \"stop\" | \"reattach\" | null => {\n deliverEnvelope(c, env);\n return settleEnvelope(c, env);\n};\n\nconst scheduleRetry = (\n c: Conn,\n failure: string | undefined,\n immediate: boolean,\n cause?: unknown,\n) => {\n c.attempt += 1;\n c.episode ??= { cause: \"dropped\" };\n const delay = immediate ? 0 : retryDelay(cause, c.attempt);\n if (c.hasSnapshot) {\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 }\n return delay;\n};\n\nconst reconnectConn = (c: Conn) => {\n if (!c.lifetime) return;\n if (c.connection.status === \"gone\") {\n c.attempt = 0;\n c.episode = { cause: \"dropped\" };\n flushTapSync(() => {\n if (c.hasSnapshot) scheduleRetry(c, undefined, true);\n else setStatus(c, { status: \"connecting\" });\n });\n }\n c.gate.open();\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 let failure: string | undefined;\n let cause: unknown;\n let gone = false;\n try {\n gone = (await c.attemptOnce(abort)) === \"stop\";\n } catch (error) {\n if (!abort.signal.aborted) {\n failure =\n error instanceof Error && error.message ? error.message : \"network\";\n cause = error;\n }\n }\n abort.abort();\n if (!alive()) return;\n if (gone) {\n await gate.wait();\n continue;\n }\n const immediate = gate.take();\n const delay = flushTapSync(() =>\n scheduleRetry(c, failure, immediate, cause),\n );\n if (delay === 0) continue;\n await gate.wait(delay);\n }\n};\n\nexport const useStatewireConn = (props: {\n url: string;\n onChange?: ((connection: StatewireClient.Connection) => void) | undefined;\n ctx: StatewireRemoteProps;\n attemptOnce: (\n c: Conn,\n abort: AbortController,\n ) => Promise<\"stop\" | \"reattach\" | \"ended\">;\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 episode: null,\n abort: null,\n gate: createGate(),\n lifetime: null,\n ctx: props.ctx,\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.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 = createGate();\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 c.gate.open();\n };\n }, [c, props.url]);\n\n return c;\n};\n\nexport const classify = (timeout: AbortSignal) =>\n new Error(timeout.aborted ? \"timeout\" : \"network\");\n\nexport const parseRetryAfter = (header: string | null): number | undefined => {\n if (!header) return undefined;\n const value = header.trim();\n if (/^\\d+$/.test(value)) return Number(value) * 1000;\n const date = Date.parse(value);\n return Number.isNaN(date) ? undefined : Math.max(0, date - Date.now());\n};\n\nexport const overloadedError = (retryAfterMs?: number) =>\n Object.assign(new Error(\"overloaded\"), { retryAfterMs });\n"],"mappings":"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"}
1
+ {"version":3,"file":"connection.js","names":["useEffect","useRef","useState","flushTapSync","reviveOps","reviveResponse","reviveValue","retryDelay","ConnectionState","StatewireRemoteProps","StatewireClient","StatewireHost","Episode","cause","message","StatewireResponse","seq","type","code","payload","meta","StatewireSyn","lastSeq","lease","StatewireEnvelope","ops","res","ack","syn","fin","Fin","createGate","latched","wake","take","was","wait","timeoutMs","Promise","resolve","timer","undefined","setTimeout","clearTimeout","open","Gate","ReturnType","Conn","connection","Connection","hasSnapshot","attempt","episode","gate","abort","AbortController","lifetime","ctx","onChange","attemptOnce","notify","reconnect","setStatus","c","next","changed","status","deliverEnvelope","env","Pick","map","onSnapshot","onOps","isTransientFinish","reason","settleEnvelope","finish","rest","onFinish","handleEnvelope","scheduleRetry","failure","immediate","delay","nextRetryAt","Date","now","lastError","reconnectConn","run","alive","gone","error","signal","aborted","Error","useStatewireConn","props","url","latest","current","setConnection","conn","classify","timeout","AbortSignal","parseRetryAfter","header","value","trim","test","Number","date","parse","isNaN","Math","max","overloadedError","retryAfterMs","Object","assign"],"sources":["../../src/client/connection.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { flushTapSync } from \"@assistant-ui/tap\";\nimport { reviveOps, reviveResponse, reviveValue } from \"../wire/meta\";\nimport { retryDelay } 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 StatewireResponse = {\n seq: number;\n type: \"accepted\" | \"rejected\" | \"pending\" | \"crashed\" | \"result-unavailable\";\n code?: string;\n message?: string;\n payload?: unknown;\n meta?: unknown;\n};\n\nexport type StatewireSyn = {\n lastSeq: number;\n lease?: string;\n};\n\nexport type StatewireEnvelope = {\n ops?: unknown[];\n res?: StatewireResponse[];\n ack?: number;\n syn?: StatewireSyn;\n fin?: StatewireHost.Fin;\n};\n\nconst createGate = () => {\n let latched = false;\n let wake: (() => void) | undefined;\n return {\n take: () => {\n const was = latched;\n latched = false;\n return was;\n },\n wait: (timeoutMs?: number) => {\n if (latched) {\n latched = false;\n return Promise.resolve();\n }\n return new Promise<void>((resolve) => {\n const timer =\n timeoutMs === undefined\n ? undefined\n : setTimeout(() => {\n wake = undefined;\n resolve();\n }, timeoutMs);\n wake = () => {\n clearTimeout(timer);\n wake = undefined;\n resolve();\n };\n });\n },\n open: () => {\n if (wake) wake();\n else latched = true;\n },\n };\n};\n\nexport type Gate = ReturnType<typeof createGate>;\n\nexport type Conn = {\n connection: StatewireClient.Connection;\n hasSnapshot: boolean;\n lease: string | null;\n attempt: number;\n episode: Episode | null;\n gate: Gate;\n abort: AbortController | null;\n lifetime: AbortController | null;\n ctx: StatewireRemoteProps;\n onChange: (connection: StatewireClient.Connection) => void;\n attemptOnce: (\n abort: AbortController,\n ) => Promise<\"stop\" | \"reattach\" | \"ended\">;\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 deliverEnvelope = (\n c: Conn,\n env: Pick<StatewireEnvelope, \"ops\" | \"res\" | \"ack\" | \"syn\">,\n) => {\n if (env.syn?.lease !== undefined) c.lease = env.syn.lease;\n const ctx = c.ctx;\n const ops = env.ops === undefined ? [] : reviveOps(env.ops);\n const res = env.res?.map(reviveResponse);\n if (env.syn !== undefined) {\n ctx.onSnapshot({ ops, lastSeq: env.syn.lastSeq });\n c.hasSnapshot = true;\n } else if (\n env.ops !== undefined ||\n env.res !== undefined ||\n env.ack !== undefined\n ) {\n ctx.onOps({\n ops,\n ...(res !== undefined && { res }),\n ...(env.ack !== undefined && { ack: env.ack }),\n });\n }\n};\n\nconst isTransientFinish = (\n reason: StatewireHost.Fin[\"reason\"],\n): reason is \"evicted\" | \"error\" => reason === \"evicted\" || reason === \"error\";\n\nconst settleEnvelope = (\n c: Conn,\n env: StatewireEnvelope,\n): \"stop\" | \"reattach\" | null => {\n let finish = env.fin;\n if (finish) {\n if (finish.meta !== undefined) {\n const { meta, ...rest } = finish;\n finish = { ...rest, payload: reviveValue(rest.payload, meta) };\n }\n c.ctx.onFinish(finish);\n if (isTransientFinish(finish.reason)) {\n c.episode = {\n cause: finish.reason,\n ...(finish.message !== undefined && { message: finish.message }),\n };\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 if (c.connection.status !== \"live\") {\n c.attempt = 0;\n c.episode = null;\n setStatus(c, { status: \"live\" });\n }\n return null;\n};\n\nexport const handleEnvelope = (\n c: Conn,\n env: StatewireEnvelope,\n): \"stop\" | \"reattach\" | null => {\n deliverEnvelope(c, env);\n return settleEnvelope(c, env);\n};\n\nconst scheduleRetry = (\n c: Conn,\n failure: string | undefined,\n immediate: boolean,\n cause?: unknown,\n) => {\n c.attempt += 1;\n c.episode ??= { cause: \"dropped\" };\n const delay = immediate ? 0 : retryDelay(cause, c.attempt);\n if (c.hasSnapshot) {\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 }\n return delay;\n};\n\nconst reconnectConn = (c: Conn) => {\n if (!c.lifetime) return;\n if (c.connection.status === \"gone\") {\n c.attempt = 0;\n c.episode = { cause: \"dropped\" };\n flushTapSync(() => {\n if (c.hasSnapshot) scheduleRetry(c, undefined, true);\n else setStatus(c, { status: \"connecting\" });\n });\n }\n c.gate.open();\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 let failure: string | undefined;\n let cause: unknown;\n let gone = false;\n try {\n gone = (await c.attemptOnce(abort)) === \"stop\";\n } catch (error) {\n if (!abort.signal.aborted) {\n failure =\n error instanceof Error && error.message ? error.message : \"network\";\n cause = error;\n }\n }\n abort.abort();\n if (!alive()) return;\n if (gone) {\n await gate.wait();\n continue;\n }\n const immediate = gate.take();\n const delay = flushTapSync(() =>\n scheduleRetry(c, failure, immediate, cause),\n );\n if (delay === 0) continue;\n await gate.wait(delay);\n }\n};\n\nexport const useStatewireConn = (props: {\n url: string;\n onChange?: ((connection: StatewireClient.Connection) => void) | undefined;\n ctx: StatewireRemoteProps;\n attemptOnce: (\n c: Conn,\n abort: AbortController,\n ) => Promise<\"stop\" | \"reattach\" | \"ended\">;\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 episode: null,\n abort: null,\n gate: createGate(),\n lifetime: null,\n ctx: props.ctx,\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.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 = createGate();\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 c.gate.open();\n };\n }, [c, props.url]);\n\n return c;\n};\n\nexport const classify = (timeout: AbortSignal) =>\n new Error(timeout.aborted ? \"timeout\" : \"network\");\n\nexport const parseRetryAfter = (header: string | null): number | undefined => {\n if (!header) return undefined;\n const value = header.trim();\n if (/^\\d+$/.test(value)) return Number(value) * 1000;\n const date = Date.parse(value);\n return Number.isNaN(date) ? undefined : Math.max(0, date - Date.now());\n};\n\nexport const overloadedError = (retryAfterMs?: number) =>\n Object.assign(new Error(\"overloaded\"), { retryAfterMs });\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"core.d.ts","names":[],"sources":["../../src/client/core.ts"],"mappings":";;;cAYa,iBAAiB,gBAAgB;cA6RjC,mBACX,kBAAkB,gBAAgB,wBAClC,OAAO;EAjFe,SAAA,MAAA;;;;EAsEE,YAAA"}
1
+ {"version":3,"file":"core.d.ts","names":[],"sources":["../../src/client/core.ts"],"mappings":";;;cAYa,iBAAiB,gBAAgB;cA8RjC,mBACX,kBAAkB,gBAAgB,wBAClC,OAAO;EAjFe,SAAA,MAAA;;;;EAsEE,YAAA"}
@@ -126,6 +126,7 @@ const createCore = (getOptions) => {
126
126
  const connection = getConnection();
127
127
  const connectionChanged = connection !== notifiedConnection;
128
128
  notifiedConnection = connection;
129
+ if (connectionChanged) options.onConnectionChange?.(connection);
129
130
  if (stateChanged || connectionChanged) for (const listener of [...listeners]) listener();
130
131
  } finally {
131
132
  flushing = false;
@@ -1 +1 @@
1
- {"version":3,"file":"core.js","names":["useEffect","useState","createChanges","getCommandsHost","getEmitHost","getStateHost","CommandsInvocation","CommandsListener","recordMutations","createMacrotaskPoster","createMethodProxy","StatewirePath","StatewireSendError","Statewire","StatewireClient","LIVE_CONNECTION","Connection","status","reconnect","failureOf","error","SendFailure","failure","fate","cause","message","Error","String","Overlay","inv","dropped","createCore","getOptions","Options","value","source","StateSource","overlays","updates","CommandUpdate","events","pendingChanged","canonical","published","overlayPaths","notifiedPublished","notifiedOverlayPaths","notifiedConnection","stale","overlaysDirty","flushing","listeners","Set","post","scheduled","flush","schedule","markStale","drainCanonical","push","takeChanged","snapshot","state","recompute","prevCanonical","optimistic","next","paths","undefined","overlay","ctx","OptimisticContext","ts","seq","clientId","recorded","draft","method","params","Command","changedPaths","console","getConnection","connection","options","stateChanged","prev","changed","onStateChange","changes","length","batch","update","onCommandChange","event","onEvent","connectionChanged","listener","note","extra","result","commandsListener","onStart","onPosted","onAck","onCovered","some","o","filter","onSettle","outcome","type","payload","eventSink","attach","nextSource","setOnChange","commands","setListener","emit","setSink","detach","CommandsProxy","Promise","reject","send","Record","args","promise","getState","subscribe","add","delete","useStatewireCore","$","_c","t0","core","t1","t2","t3"],"sources":["../../src/client/core.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { createChanges } from \"./changes\";\nimport { getCommandsHost, getEmitHost, getStateHost } from \"../host/host\";\nimport type { CommandsInvocation, CommandsListener } from \"../host/host\";\nimport { recordMutations } from \"../util/recordMutations\";\nimport { createMacrotaskPoster } from \"../util/macrotask\";\nimport { createMethodProxy } from \"../util/methodProxy\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport { StatewireSendError } from \"../types\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireClient } from \"../statewire\";\n\nexport const LIVE_CONNECTION: StatewireClient.Connection = {\n status: \"live\",\n reconnect: () => {},\n};\n\nconst failureOf = (error: unknown): StatewireClient.SendFailure =>\n error instanceof StatewireSendError\n ? error.failure\n : {\n fate: \"rejected\",\n cause: \"server\",\n message: error instanceof Error ? error.message : String(error),\n };\n\ntype Overlay = { inv: CommandsInvocation; dropped: boolean };\n\nconst createCore = (\n getOptions: () => StatewireClient.Options<any, any, any>,\n) => {\n let value: Statewire<unknown> | null = null;\n let source: Statewire.StateSource | null = null;\n let overlays: Overlay[] = [];\n let updates: Statewire.CommandUpdate[] = [];\n let events: unknown[] = [];\n let pendingChanged: StatewirePath[] = [];\n let canonical: unknown;\n let published: unknown;\n let overlayPaths: readonly StatewirePath[] = [];\n let notifiedPublished: unknown;\n let notifiedOverlayPaths: readonly StatewirePath[] = [];\n let notifiedConnection: StatewireClient.Connection | null = null;\n let stale = false;\n let overlaysDirty = false;\n let flushing = false;\n const listeners = new Set<() => void>();\n\n const post = createMacrotaskPoster(() => {\n scheduled = false;\n flush();\n });\n let scheduled = false;\n const schedule = () => {\n if (scheduled) return;\n scheduled = true;\n post();\n };\n\n const markStale = () => {\n stale = true;\n schedule();\n };\n\n const drainCanonical = () => {\n if (source) {\n pendingChanged.push(...source.takeChanged());\n canonical = source.snapshot();\n } else if (value && value.state !== canonical) {\n pendingChanged.push([]);\n canonical = value.state;\n }\n };\n\n const recompute = () => {\n if (!stale || !value) return;\n stale = false;\n const prevCanonical = canonical;\n drainCanonical();\n if (canonical === prevCanonical && !overlaysDirty) return;\n overlaysDirty = false;\n const { optimistic } = getOptions();\n let next = canonical;\n let paths: readonly StatewirePath[] = [];\n if (optimistic && canonical !== undefined) {\n for (const overlay of overlays) {\n if (overlay.dropped) continue;\n const { inv } = overlay;\n const ctx: StatewireClient.OptimisticContext = {\n ts: inv.ts,\n seq: inv.seq,\n ...(inv.clientId !== undefined && { clientId: inv.clientId }),\n };\n try {\n const recorded = recordMutations(next, (draft) =>\n optimistic(\n draft as never,\n { method: inv.method, params: inv.params } as Statewire.Command,\n ctx,\n ),\n );\n next = recorded.state;\n paths = [...paths, ...recorded.changedPaths];\n } catch (error) {\n overlay.dropped = true;\n console.error(\n `statewire: optimistic reducer threw for \"${inv.method}\"; dropping its optimistic update`,\n error,\n );\n }\n }\n }\n published = next;\n overlayPaths = paths;\n };\n\n const getConnection = (): StatewireClient.Connection =>\n value?.connection ?? LIVE_CONNECTION;\n\n const flush = () => {\n if (!value) return;\n if (flushing) {\n schedule();\n return;\n }\n flushing = true;\n try {\n recompute();\n const options = getOptions();\n const stateChanged = published !== notifiedPublished;\n if (stateChanged) {\n const prev = notifiedPublished;\n const changed = [\n ...pendingChanged,\n ...notifiedOverlayPaths,\n ...overlayPaths,\n ];\n pendingChanged = [];\n notifiedPublished = published;\n notifiedOverlayPaths = overlayPaths;\n options.onStateChange?.(published as never, {\n changes: createChanges(prev, published, changed),\n });\n }\n if (updates.length > 0 && published !== undefined) {\n const batch = updates;\n updates = [];\n for (const update of batch) {\n options.onCommandChange?.(update, { state: published as never });\n }\n }\n if (events.length > 0 && published !== undefined) {\n const batch = events;\n events = [];\n for (const event of batch) {\n options.onEvent?.(event, { state: published as never });\n }\n }\n const connection = getConnection();\n const connectionChanged = connection !== notifiedConnection;\n notifiedConnection = connection;\n if (stateChanged || connectionChanged) {\n for (const listener of [...listeners]) listener();\n }\n } finally {\n flushing = false;\n }\n };\n\n const note = (\n inv: CommandsInvocation,\n status: Statewire.CommandUpdate[\"status\"],\n extra?: { result?: unknown; failure?: StatewireClient.SendFailure },\n ) => {\n if (!getOptions().onCommandChange) return;\n updates.push({\n method: inv.method,\n params: inv.params as never[],\n seq: inv.seq,\n status,\n ...extra,\n });\n schedule();\n };\n\n const commandsListener: CommandsListener = {\n onStart: (inv) => {\n overlays.push({ inv, dropped: false });\n overlaysDirty = true;\n markStale();\n note(inv, \"queued\");\n },\n onPosted: (inv) => note(inv, \"posted\"),\n onAck: (inv) => note(inv, \"acked\"),\n onCovered: (inv) => {\n if (!overlays.some((o) => o.inv === inv)) return;\n overlays = overlays.filter((o) => o.inv !== inv);\n overlaysDirty = true;\n markStale();\n },\n onSettle: (inv, outcome) => {\n note(\n inv,\n \"settled\",\n outcome.type === \"accepted\"\n ? { result: outcome.payload }\n : { failure: failureOf(outcome.error) },\n );\n flush();\n },\n };\n\n const eventSink = (event: unknown) => {\n if (!getOptions().onEvent) return;\n events.push(event);\n schedule();\n };\n\n const attach = (next: Statewire<unknown>) => {\n if (next === value) return;\n const prev = value;\n value = next;\n const nextSource =\n next.source ??\n (typeof next.state === \"object\" && next.state !== null\n ? getStateHost(next.state)\n : undefined) ??\n null;\n if (nextSource !== source) {\n source?.setOnChange(null);\n source = nextSource;\n source?.setOnChange(markStale);\n markStale();\n }\n if (next.commands !== prev?.commands) {\n if (prev) getCommandsHost(prev.commands)?.setListener(null);\n getCommandsHost(next.commands)?.setListener(commandsListener);\n if (overlays.length > 0) {\n overlays = [];\n overlaysDirty = true;\n }\n markStale();\n }\n if (next.emit !== prev?.emit) {\n if (prev) getEmitHost(prev.emit)?.setSink(null);\n getEmitHost(next.emit)?.setSink(eventSink);\n }\n if (!source) markStale();\n schedule();\n };\n\n const detach = () => {\n source?.setOnChange(null);\n source = null;\n if (value) {\n getCommandsHost(value.commands)?.setListener(null);\n getEmitHost(value.emit)?.setSink(null);\n value = null;\n }\n };\n\n const commands = createMethodProxy<Statewire.CommandsProxy<any>>(\n (method, params) => {\n if (!value) {\n return Promise.reject(\n new StatewireSendError({ fate: \"aborted\", cause: \"unmount\" }),\n );\n }\n const send = (\n value.commands as Record<\n string,\n (...args: unknown[]) => Promise<unknown>\n >\n )[method]!;\n const promise = send(...params);\n flush();\n return promise;\n },\n );\n\n return {\n attach,\n detach,\n getState: () => {\n recompute();\n return published;\n },\n commands,\n subscribe: (listener: () => void) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n};\n\nexport const useStatewireCore = (\n getOptions: () => StatewireClient.Options<any, any, any>,\n value: Statewire<unknown>,\n) => {\n const [core] = useState(() => createCore(getOptions));\n // Attach during render so the first snapshot reads the transport's state;\n // re-attaching the same transport is a no-op and the single-slot host\n // listeners make a swap land on the last writer.\n core.attach(value);\n useEffect(() => {\n core.attach(value);\n });\n useEffect(() => () => core.detach(), [core]);\n return core;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"core.js","names":["useEffect","useState","createChanges","getCommandsHost","getEmitHost","getStateHost","CommandsInvocation","CommandsListener","recordMutations","createMacrotaskPoster","createMethodProxy","StatewirePath","StatewireSendError","Statewire","StatewireClient","LIVE_CONNECTION","Connection","status","reconnect","failureOf","error","SendFailure","failure","fate","cause","message","Error","String","Overlay","inv","dropped","createCore","getOptions","Options","value","source","StateSource","overlays","updates","CommandUpdate","events","pendingChanged","canonical","published","overlayPaths","notifiedPublished","notifiedOverlayPaths","notifiedConnection","stale","overlaysDirty","flushing","listeners","Set","post","scheduled","flush","schedule","markStale","drainCanonical","push","takeChanged","snapshot","state","recompute","prevCanonical","optimistic","next","paths","undefined","overlay","ctx","OptimisticContext","ts","seq","clientId","recorded","draft","method","params","Command","changedPaths","console","getConnection","connection","options","stateChanged","prev","changed","onStateChange","changes","length","batch","update","onCommandChange","event","onEvent","connectionChanged","onConnectionChange","listener","note","extra","result","commandsListener","onStart","onPosted","onAck","onCovered","some","o","filter","onSettle","outcome","type","payload","eventSink","attach","nextSource","setOnChange","commands","setListener","emit","setSink","detach","CommandsProxy","Promise","reject","send","Record","args","promise","getState","subscribe","add","delete","useStatewireCore","$","_c","t0","core","t1","t2","t3"],"sources":["../../src/client/core.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { createChanges } from \"./changes\";\nimport { getCommandsHost, getEmitHost, getStateHost } from \"../host/host\";\nimport type { CommandsInvocation, CommandsListener } from \"../host/host\";\nimport { recordMutations } from \"../util/recordMutations\";\nimport { createMacrotaskPoster } from \"../util/macrotask\";\nimport { createMethodProxy } from \"../util/methodProxy\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport { StatewireSendError } from \"../types\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireClient } from \"../statewire\";\n\nexport const LIVE_CONNECTION: StatewireClient.Connection = {\n status: \"live\",\n reconnect: () => {},\n};\n\nconst failureOf = (error: unknown): StatewireClient.SendFailure =>\n error instanceof StatewireSendError\n ? error.failure\n : {\n fate: \"rejected\",\n cause: \"server\",\n message: error instanceof Error ? error.message : String(error),\n };\n\ntype Overlay = { inv: CommandsInvocation; dropped: boolean };\n\nconst createCore = (\n getOptions: () => StatewireClient.Options<any, any, any>,\n) => {\n let value: Statewire<unknown> | null = null;\n let source: Statewire.StateSource | null = null;\n let overlays: Overlay[] = [];\n let updates: Statewire.CommandUpdate[] = [];\n let events: unknown[] = [];\n let pendingChanged: StatewirePath[] = [];\n let canonical: unknown;\n let published: unknown;\n let overlayPaths: readonly StatewirePath[] = [];\n let notifiedPublished: unknown;\n let notifiedOverlayPaths: readonly StatewirePath[] = [];\n let notifiedConnection: StatewireClient.Connection | null = null;\n let stale = false;\n let overlaysDirty = false;\n let flushing = false;\n const listeners = new Set<() => void>();\n\n const post = createMacrotaskPoster(() => {\n scheduled = false;\n flush();\n });\n let scheduled = false;\n const schedule = () => {\n if (scheduled) return;\n scheduled = true;\n post();\n };\n\n const markStale = () => {\n stale = true;\n schedule();\n };\n\n const drainCanonical = () => {\n if (source) {\n pendingChanged.push(...source.takeChanged());\n canonical = source.snapshot();\n } else if (value && value.state !== canonical) {\n pendingChanged.push([]);\n canonical = value.state;\n }\n };\n\n const recompute = () => {\n if (!stale || !value) return;\n stale = false;\n const prevCanonical = canonical;\n drainCanonical();\n if (canonical === prevCanonical && !overlaysDirty) return;\n overlaysDirty = false;\n const { optimistic } = getOptions();\n let next = canonical;\n let paths: readonly StatewirePath[] = [];\n if (optimistic && canonical !== undefined) {\n for (const overlay of overlays) {\n if (overlay.dropped) continue;\n const { inv } = overlay;\n const ctx: StatewireClient.OptimisticContext = {\n ts: inv.ts,\n seq: inv.seq,\n ...(inv.clientId !== undefined && { clientId: inv.clientId }),\n };\n try {\n const recorded = recordMutations(next, (draft) =>\n optimistic(\n draft as never,\n { method: inv.method, params: inv.params } as Statewire.Command,\n ctx,\n ),\n );\n next = recorded.state;\n paths = [...paths, ...recorded.changedPaths];\n } catch (error) {\n overlay.dropped = true;\n console.error(\n `statewire: optimistic reducer threw for \"${inv.method}\"; dropping its optimistic update`,\n error,\n );\n }\n }\n }\n published = next;\n overlayPaths = paths;\n };\n\n const getConnection = (): StatewireClient.Connection =>\n value?.connection ?? LIVE_CONNECTION;\n\n const flush = () => {\n if (!value) return;\n if (flushing) {\n schedule();\n return;\n }\n flushing = true;\n try {\n recompute();\n const options = getOptions();\n const stateChanged = published !== notifiedPublished;\n if (stateChanged) {\n const prev = notifiedPublished;\n const changed = [\n ...pendingChanged,\n ...notifiedOverlayPaths,\n ...overlayPaths,\n ];\n pendingChanged = [];\n notifiedPublished = published;\n notifiedOverlayPaths = overlayPaths;\n options.onStateChange?.(published as never, {\n changes: createChanges(prev, published, changed),\n });\n }\n if (updates.length > 0 && published !== undefined) {\n const batch = updates;\n updates = [];\n for (const update of batch) {\n options.onCommandChange?.(update, { state: published as never });\n }\n }\n if (events.length > 0 && published !== undefined) {\n const batch = events;\n events = [];\n for (const event of batch) {\n options.onEvent?.(event, { state: published as never });\n }\n }\n const connection = getConnection();\n const connectionChanged = connection !== notifiedConnection;\n notifiedConnection = connection;\n if (connectionChanged) options.onConnectionChange?.(connection);\n if (stateChanged || connectionChanged) {\n for (const listener of [...listeners]) listener();\n }\n } finally {\n flushing = false;\n }\n };\n\n const note = (\n inv: CommandsInvocation,\n status: Statewire.CommandUpdate[\"status\"],\n extra?: { result?: unknown; failure?: StatewireClient.SendFailure },\n ) => {\n if (!getOptions().onCommandChange) return;\n updates.push({\n method: inv.method,\n params: inv.params as never[],\n seq: inv.seq,\n status,\n ...extra,\n });\n schedule();\n };\n\n const commandsListener: CommandsListener = {\n onStart: (inv) => {\n overlays.push({ inv, dropped: false });\n overlaysDirty = true;\n markStale();\n note(inv, \"queued\");\n },\n onPosted: (inv) => note(inv, \"posted\"),\n onAck: (inv) => note(inv, \"acked\"),\n onCovered: (inv) => {\n if (!overlays.some((o) => o.inv === inv)) return;\n overlays = overlays.filter((o) => o.inv !== inv);\n overlaysDirty = true;\n markStale();\n },\n onSettle: (inv, outcome) => {\n note(\n inv,\n \"settled\",\n outcome.type === \"accepted\"\n ? { result: outcome.payload }\n : { failure: failureOf(outcome.error) },\n );\n flush();\n },\n };\n\n const eventSink = (event: unknown) => {\n if (!getOptions().onEvent) return;\n events.push(event);\n schedule();\n };\n\n const attach = (next: Statewire<unknown>) => {\n if (next === value) return;\n const prev = value;\n value = next;\n const nextSource =\n next.source ??\n (typeof next.state === \"object\" && next.state !== null\n ? getStateHost(next.state)\n : undefined) ??\n null;\n if (nextSource !== source) {\n source?.setOnChange(null);\n source = nextSource;\n source?.setOnChange(markStale);\n markStale();\n }\n if (next.commands !== prev?.commands) {\n if (prev) getCommandsHost(prev.commands)?.setListener(null);\n getCommandsHost(next.commands)?.setListener(commandsListener);\n if (overlays.length > 0) {\n overlays = [];\n overlaysDirty = true;\n }\n markStale();\n }\n if (next.emit !== prev?.emit) {\n if (prev) getEmitHost(prev.emit)?.setSink(null);\n getEmitHost(next.emit)?.setSink(eventSink);\n }\n if (!source) markStale();\n schedule();\n };\n\n const detach = () => {\n source?.setOnChange(null);\n source = null;\n if (value) {\n getCommandsHost(value.commands)?.setListener(null);\n getEmitHost(value.emit)?.setSink(null);\n value = null;\n }\n };\n\n const commands = createMethodProxy<Statewire.CommandsProxy<any>>(\n (method, params) => {\n if (!value) {\n return Promise.reject(\n new StatewireSendError({ fate: \"aborted\", cause: \"unmount\" }),\n );\n }\n const send = (\n value.commands as Record<\n string,\n (...args: unknown[]) => Promise<unknown>\n >\n )[method]!;\n const promise = send(...params);\n flush();\n return promise;\n },\n );\n\n return {\n attach,\n detach,\n getState: () => {\n recompute();\n return published;\n },\n commands,\n subscribe: (listener: () => void) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n};\n\nexport const useStatewireCore = (\n getOptions: () => StatewireClient.Options<any, any, any>,\n value: Statewire<unknown>,\n) => {\n const [core] = useState(() => createCore(getOptions));\n // Attach during render so the first snapshot reads the transport's state;\n // re-attaching the same transport is a no-op and the single-slot host\n // listeners make a swap land on the last writer.\n core.attach(value);\n useEffect(() => {\n core.attach(value);\n });\n useEffect(() => () => core.detach(), [core]);\n return core;\n};\n"],"mappings":"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"}
@@ -1,5 +1,4 @@
1
1
  import { StatewireSessionRecord, StatewireSessionStore, StatewireSessionStoreOptions } from "./sessionStore.js";
2
- import { StatewireClient } from "../statewire.js";
3
2
  import { Statewire, StatewireSendError } from "../types.js";
4
3
  import { StatewirePath } from "../wire/ops.js";
5
4
  import { CommandsInvocation, CommandsListener } from "../host/host.js";
@@ -43,9 +42,7 @@ type Pending = {
43
42
  reject: (error: StatewireSendError) => void;
44
43
  };
45
44
  type AttachState = "detached" | "attached" | "finished";
46
- type StatewireSessionOptions = StatewireSessionStoreOptions & {
47
- onCommandSessionLost?: ((command: StatewireClient.SessionLostCommand) => "replay" | void) | undefined;
48
- };
45
+ type StatewireSessionOptions = StatewireSessionStoreOptions;
49
46
  type Session = {
50
47
  record: StatewireSessionRecord;
51
48
  ctx: StatewireRemoteProps;
@@ -65,7 +62,6 @@ type Session = {
65
62
  transport: RemoteLink | null;
66
63
  waiters: (() => void)[];
67
64
  lifecycle: CommandsListener | null;
68
- onCommandSessionLost: StatewireSessionOptions["onCommandSessionLost"];
69
65
  notify: () => void;
70
66
  emitEvent: (event: unknown) => void;
71
67
  send: (command: StatewireWireCommand) => Promise<unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","names":[],"sources":["../../src/client/session.ts"],"mappings":";;;;;;;;;;;;;KA+BK;EACC;EAAc;;EACd;EAAc;EAAwB;;EACtC;EAAc;EAAwB;;EACtC;EAAuB;EAAiB;;EACxC;EAAoB;EAAiB;;EACrC;;KAED;WACM;WACA,SAAS;;WAET,KAAK;EACd,OAAO;EACP,UAAU;EACV,SAAS,OAAO;;KAqGb;KAEO,0BAA0B;EACpC,yBACM,SAAS,gBAAgB;;KAI5B;EACH,QAAQ;EACR,KAAK;EACL;EACA,OAAO;EACP;EACA,SAAS;EACT,SAAS;EACT;EACA;EACA,SAAS;EACT,QAAQ,UAAU;EAClB,QAAQ;EACR;EACA;EACA;EACA,WAAW;EACX;EACA,WAAW;EACX,sBAAsB;EACtB;EACA,YAAY;EACZ,OAAO,SAAS,yBAAyB;EACzC,UAAU,mBAAmB,sBAAsB;;cAmf/C,eAAgB,GAAG,SAAS,WAAW;cAsDvC,aACJ,GAAG,SACH,OAAO,4BACN;cAQU,sBACX,SAAS,yBACT,OAAO,4BACN"}
1
+ {"version":3,"file":"session.d.ts","names":[],"sources":["../../src/client/session.ts"],"mappings":";;;;;;;;;;;;KA+BK;EACC;EAAc;;EACd;EAAc;EAAwB;;EACtC;EAAc;EAAwB;;EACtC;EAAuB;EAAiB;;EACxC;EAAoB;EAAiB;;EACrC;;KAED;WACM;WACA,SAAS;;WAET,KAAK;EACd,OAAO;EACP,UAAU;EACV,SAAS,OAAO;;KAqGb;KAEO,0BAA0B;KAEjC;EACH,QAAQ;EACR,KAAK;EACL;EACA,OAAO;EACP;EACA,SAAS;EACT,SAAS;EACT;EACA;EACA,SAAS;EACT,QAAQ,UAAU;EAClB,QAAQ;EACR;EACA;EACA;EACA,WAAW;EACX;EACA,WAAW;EACX;EACA,YAAY;EACZ,OAAO,SAAS,yBAAyB;EACzC,UAAU,mBAAmB,sBAAsB;;cAwe/C,eAAgB,GAAG,SAAS,WAAW;cAsDvC,aACJ,GAAG,SACH,OAAO,4BACN;cAQU,sBACX,SAAS,yBACT,OAAO,4BACN"}
@@ -123,7 +123,6 @@ const createSession = (options) => {
123
123
  transport: null,
124
124
  waiters: [],
125
125
  lifecycle: null,
126
- onCommandSessionLost: options.onCommandSessionLost,
127
126
  notify: () => {},
128
127
  emitEvent: () => {},
129
128
  send: (command) => sendCommand(s, command),
@@ -198,20 +197,10 @@ const settleAll = (s, detach, verdict) => {
198
197
  }
199
198
  };
200
199
  const claimReplays = (s) => {
201
- const handler = s.onCommandSessionLost;
202
- if (!handler) return [];
203
- const claimed = /* @__PURE__ */ new Set();
204
- for (const entry of s.pending) {
205
- if (!entry.inv || isResolved(entry.state)) continue;
206
- if (handler({
207
- method: entry.inv.method,
208
- params: [...entry.inv.params],
209
- seq: entry.seq
210
- }) === "replay") claimed.add(entry);
211
- }
212
- if (claimed.size === 0) return [];
213
- s.pending = s.pending.filter((p) => !claimed.has(p));
214
- return [...claimed];
200
+ const claimed = s.pending.filter((entry) => entry.inv !== null && !isResolved(entry.state));
201
+ if (claimed.length === 0) return [];
202
+ s.pending = s.pending.filter((p) => !claimed.includes(p));
203
+ return claimed;
215
204
  };
216
205
  const evict = (s, verdict) => {
217
206
  const replays = claimReplays(s);
@@ -549,7 +538,6 @@ const setContext = (s, value) => {
549
538
  };
550
539
  const useStatewireSession = (options, emit) => {
551
540
  const [s] = useState(() => createSession(options));
552
- s.onCommandSessionLost = options.onCommandSessionLost;
553
541
  useEffect(() => {
554
542
  s.emitEvent = emit;
555
543
  return () => {
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","names":["useEffect","useState","applyOps","StatewirePath","StatewireResponse","bindCommandsHost","CommandsInvocation","CommandsListener","Statewire","StatewireClient","createMemorySessionStore","StatewireSessionRecord","StatewireSessionStore","StatewireSessionStoreOptions","retryDelay","createMethodProxy","randomId","StatewireSendError","RemoteLink","StatewireRemoteProps","StatewireSubmitResult","StatewireWireCommand","PendingState","at","pendingMarked","failedAttempts","posted","Pending","seq","command","inv","state","resolve","payload","reject","error","Outcome","ok","failure","SendFailure","isResolved","isStateSettled","inFlight","Error","legalTransitions","Record","queued","trying","done","transition","s","Session","entry","next","outcome","prev","includes","settles","undefined","covers","lifecycle","onCovered","onSettle","type","pending","filter","p","carried","Exclude","newClientId","rejectedSend","Promise","result","catch","AttachState","StatewireSessionOptions","onCommandSessionLost","SessionLostCommand","record","ctx","recordPersisted","store","lastAck","context","canonical","lastPublished","changed","source","StateSource","attach","attachEpoch","running","pumping","transport","waiters","notify","emitEvent","event","send","commands","params","createSession","options","sessionStore","stored","load","clientId","nextSeq","onSnapshot","args","handleSnapshot","onOps","handleOps","onFinish","handleFinish","snapshot","takeChanged","setOnChange","listener","sendCommand","method","setListener","publish","wake","waiter","waitForWake","r","push","sleepUntilWake","delayMs","clearTimeout","timer","setTimeout","w","settleAll","detach","verdict","cause","evicted","unmount","gone","superseded","const","batch","detachFailure","fate","predecessorLost","some","claimReplays","handler","claimed","Set","decision","add","size","has","evict","replays","save","old","enqueue","noteAcked","onAck","spliceAcked","settleVoid","protocolError","message","applyResponse","response","find","code","ingestOps","ops","applied","changedPaths","events","publishIngested","lastSeq","connection","reconnect","pump","res","ack","horizon","Math","max","reason","isFinished","sendable","epoch","onPosted","console","start","stop","setTransport","ts","Date","now","onStart","setContext","value","Array","isArray","useStatewireSession","emit"],"sources":["../../src/client/session.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { applyOps } from \"../wire/ops\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport type { StatewireResponse } from \"./connection\";\nimport { bindCommandsHost } from \"../host/host\";\nimport type { CommandsInvocation, CommandsListener } from \"../host/host\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireClient } from \"../statewire\";\nimport { createMemorySessionStore } from \"./sessionStore\";\nimport type {\n StatewireSessionRecord,\n StatewireSessionStore,\n StatewireSessionStoreOptions,\n} from \"./sessionStore\";\nimport { retryDelay } from \"./timing\";\nimport { createMethodProxy } from \"../util/methodProxy\";\nimport { randomId } from \"../util/randomId\";\nimport { StatewireSendError } from \"../types\";\nimport type {\n RemoteLink,\n StatewireRemoteProps,\n StatewireSubmitResult,\n StatewireWireCommand,\n} from \"../transports/sse\";\n\n/**\n * queued/trying/posted: overlay live, result pending. awaiting-result: acked\n * (overlay gone) but the result rides a late res; `posted: false` re-sends.\n * settled-live: result settled but the overlay stays until the covering ack.\n * done: removed from the queue.\n */\ntype PendingState =\n | { at: \"queued\"; pendingMarked: boolean }\n | { at: \"trying\"; pendingMarked: boolean; failedAttempts: number }\n | { at: \"posted\"; pendingMarked: boolean; failedAttempts: number }\n | { at: \"awaiting-result\"; posted: boolean; failedAttempts: number }\n | { at: \"settled-live\"; posted: boolean; failedAttempts: number }\n | { at: \"done\" };\n\ntype Pending = {\n readonly seq: number;\n readonly command: StatewireWireCommand;\n /** null for context submissions — they carry no command lifecycle. */\n readonly inv: CommandsInvocation | null;\n state: PendingState;\n resolve: (payload: unknown) => void;\n reject: (error: StatewireSendError) => void;\n};\n\ntype Outcome =\n | { ok: true; payload: unknown }\n | { ok: false; failure: StatewireClient.SendFailure };\n\nconst isResolved = (state: PendingState) =>\n state.at === \"settled-live\" || state.at === \"done\";\n\nconst isStateSettled = (state: PendingState) =>\n state.at === \"awaiting-result\" || state.at === \"done\";\n\n// The pump admits an entry as trying before sending; only the pump itself\n// requeues, and a done entry is never still pending.\nconst inFlight = (state: PendingState) => {\n if (state.at === \"queued\" || state.at === \"done\") {\n throw new Error(`statewire: ${state.at} entry observed after a send`);\n }\n return state;\n};\n\nconst legalTransitions: Record<PendingState[\"at\"], PendingState[\"at\"][]> = {\n queued: [\"queued\", \"trying\", \"awaiting-result\", \"settled-live\", \"done\"],\n // trying -> queued: an answered unknown-client proves the seq was never admitted.\n trying: [\n \"queued\",\n \"trying\",\n \"posted\",\n \"awaiting-result\",\n \"settled-live\",\n \"done\",\n ],\n posted: [\"posted\", \"trying\", \"awaiting-result\", \"settled-live\", \"done\"],\n \"awaiting-result\": [\"awaiting-result\", \"done\"],\n \"settled-live\": [\"settled-live\", \"done\"],\n done: [],\n};\n\nconst transition = (\n s: Session,\n entry: Pending,\n next: PendingState,\n outcome?: Outcome,\n) => {\n const prev = entry.state;\n if (!legalTransitions[prev.at].includes(next.at)) {\n throw new Error(\n `statewire: illegal pending transition ${prev.at} -> ${next.at}`,\n );\n }\n const settles = !isResolved(prev) && isResolved(next);\n if (settles && outcome === undefined) {\n throw new Error(\n `statewire: settling transition ${prev.at} -> ${next.at} without an outcome`,\n );\n }\n const covers = !isStateSettled(prev) && isStateSettled(next);\n entry.state = next;\n if (covers && entry.inv) s.lifecycle?.onCovered?.(entry.inv);\n if (settles && outcome !== undefined) {\n if (outcome.ok) {\n entry.resolve(outcome.payload);\n if (entry.inv)\n s.lifecycle?.onSettle(entry.inv, {\n type: \"accepted\",\n payload: outcome.payload,\n });\n } else {\n entry.reject(new StatewireSendError(outcome.failure));\n if (entry.inv)\n s.lifecycle?.onSettle(entry.inv, {\n type: \"rejected\",\n error: new StatewireSendError(outcome.failure),\n });\n }\n }\n if (next.at === \"done\") {\n s.pending = s.pending.filter((p) => p !== entry);\n }\n};\n\nconst carried = (\n state: Exclude<PendingState, { at: \"done\" }>,\n): { posted: boolean; failedAttempts: number } =>\n state.at === \"queued\"\n ? { posted: false, failedAttempts: 0 }\n : state.at === \"trying\" || state.at === \"posted\"\n ? { posted: state.at === \"posted\", failedAttempts: state.failedAttempts }\n : { posted: state.posted, failedAttempts: state.failedAttempts };\n\nconst newClientId = () => `c-${randomId()}`;\n\nconst rejectedSend = (\n failure: StatewireClient.SendFailure,\n): Promise<unknown> => {\n const result = Promise.reject<unknown>(new StatewireSendError(failure));\n result.catch(() => {});\n return result;\n};\n\ntype AttachState = \"detached\" | \"attached\" | \"finished\";\n\nexport type StatewireSessionOptions = StatewireSessionStoreOptions & {\n onCommandSessionLost?:\n | ((command: StatewireClient.SessionLostCommand) => \"replay\" | void)\n | undefined;\n};\n\ntype Session = {\n record: StatewireSessionRecord;\n ctx: StatewireRemoteProps;\n recordPersisted: boolean;\n store: StatewireSessionStore;\n lastAck: number;\n context: Record<string, unknown> | undefined;\n pending: Pending[];\n canonical: unknown;\n lastPublished: unknown;\n changed: StatewirePath[];\n source: Statewire.StateSource;\n attach: AttachState;\n attachEpoch: number;\n running: boolean;\n pumping: boolean;\n transport: RemoteLink | null;\n waiters: (() => void)[];\n lifecycle: CommandsListener | null;\n onCommandSessionLost: StatewireSessionOptions[\"onCommandSessionLost\"];\n notify: () => void;\n emitEvent: (event: unknown) => void;\n send: (command: StatewireWireCommand) => Promise<unknown>;\n commands: Record<string, (...params: unknown[]) => Promise<unknown>>;\n};\n\nconst createSession = (options: StatewireSessionOptions): Session => {\n const store = options.sessionStore ?? createMemorySessionStore();\n const stored = store.load();\n const record = stored ?? { clientId: newClientId(), nextSeq: 1 };\n const s: Session = {\n record,\n ctx: {\n get clientId() {\n return s.record.clientId;\n },\n onSnapshot: (args) => handleSnapshot(s, args),\n onOps: (args) => handleOps(s, args),\n onFinish: (args) => handleFinish(s, args),\n },\n recordPersisted: stored !== null,\n store,\n lastAck: -1,\n context: undefined,\n pending: [],\n canonical: undefined,\n lastPublished: undefined,\n changed: [],\n source: {\n snapshot: () => s.canonical,\n takeChanged: () => {\n const changed = s.changed;\n s.changed = [];\n return changed;\n },\n setOnChange: (listener) => {\n s.notify = listener ?? (() => {});\n },\n },\n attach: \"detached\",\n attachEpoch: 0,\n running: false,\n pumping: false,\n transport: null,\n waiters: [],\n lifecycle: null,\n onCommandSessionLost: options.onCommandSessionLost,\n notify: () => {},\n emitEvent: () => {},\n send: (command) => sendCommand(s, command),\n commands: createMethodProxy((method, params) => s.send({ method, params })),\n };\n bindCommandsHost(s.commands, {\n setListener: (listener) => {\n s.lifecycle = listener;\n },\n });\n return s;\n};\n\nconst publish = (s: Session) => {\n if (s.canonical === s.lastPublished) return;\n s.lastPublished = s.canonical;\n s.notify();\n};\n\nconst wake = (s: Session) => {\n const waiters = s.waiters;\n s.waiters = [];\n for (const waiter of waiters) waiter();\n};\n\nconst waitForWake = (s: Session) => new Promise<void>((r) => s.waiters.push(r));\n\nconst sleepUntilWake = (s: Session, delayMs: number) =>\n new Promise<void>((resolve) => {\n const waiter = () => {\n clearTimeout(timer);\n resolve();\n };\n const timer = setTimeout(() => {\n s.waiters = s.waiters.filter((w) => w !== waiter);\n resolve();\n }, delayMs);\n s.waiters.push(waiter);\n });\n\nconst settleAll = (\n s: Session,\n detach: \"evicted\" | \"unmount\" | \"gone\" | \"superseded\",\n verdict?: {\n entry: Pending;\n failure: StatewireClient.SendFailure;\n },\n) => {\n const cause = (\n {\n evicted: \"session-lost\",\n unmount: \"unmount\",\n gone: \"stream-gone\",\n superseded: \"superseded\",\n } as const\n )[detach];\n const batch = s.pending;\n const detachFailure = (entry: Pending): StatewireClient.SendFailure => {\n const state = entry.state;\n if (state.at === \"awaiting-result\")\n return { fate: \"result-unavailable\", cause };\n if (state.at === \"queued\") {\n const predecessorLost = batch.some(\n (p) => p.seq < entry.seq && p.state.at !== \"queued\",\n );\n return predecessorLost\n ? { fate: \"aborted\", cause, predecessorLost }\n : { fate: \"aborted\", cause };\n }\n return { fate: \"lost\", cause };\n };\n for (const entry of [...batch]) {\n const failure =\n entry === verdict?.entry ? verdict.failure : detachFailure(entry);\n transition(s, entry, { at: \"done\" }, { ok: false, failure });\n }\n};\n\n// Offers every unsettled real command for replay, in seq order. Not caught:\n// a throwing callback aborts the eviction before any entry settles; the\n// attach retry evicts again and re-offers.\nconst claimReplays = (s: Session): Pending[] => {\n const handler = s.onCommandSessionLost;\n if (!handler) return [];\n const claimed = new Set<Pending>();\n for (const entry of s.pending) {\n if (!entry.inv || isResolved(entry.state)) continue;\n const decision = handler({\n method: entry.inv.method,\n params: [...entry.inv.params],\n seq: entry.seq,\n });\n if (decision === \"replay\") claimed.add(entry);\n }\n if (claimed.size === 0) return [];\n s.pending = s.pending.filter((p) => !claimed.has(p));\n return [...claimed];\n};\n\nconst evict = (\n s: Session,\n verdict?: {\n entry: Pending;\n failure: StatewireClient.SendFailure;\n },\n) => {\n const replays = claimReplays(s);\n settleAll(s, \"evicted\", verdict);\n s.record = { clientId: newClientId(), nextSeq: 1 };\n s.store.save(s.record);\n s.attach = \"detached\";\n // The server dropped this client's record and its stored context with it:\n // the new session opens with a fresh full root replace.\n if (s.context !== undefined) void s.send({ context: s.context });\n for (const old of replays) {\n if (old.inv) s.lifecycle?.onCovered?.(old.inv);\n enqueue(s, old.command, old.resolve, old.reject);\n }\n publish(s);\n wake(s);\n};\n\nconst noteAcked = (s: Session, seq: number) => {\n for (const entry of s.pending) {\n if (entry.seq > seq || isStateSettled(entry.state)) continue;\n if (entry.inv) s.lifecycle?.onAck(entry.inv);\n }\n};\n\nconst spliceAcked = (s: Session, seq: number, settleVoid: boolean) => {\n for (const entry of [...s.pending]) {\n const state = entry.state;\n if (entry.seq > seq || isStateSettled(state)) continue;\n if (state.at === \"settled-live\") {\n transition(s, entry, { at: \"done\" });\n } else if (settleVoid && !state.pendingMarked) {\n transition(s, entry, { at: \"done\" }, { ok: true, payload: undefined });\n } else {\n transition(s, entry, { at: \"awaiting-result\", ...carried(state) });\n }\n }\n};\n\nconst protocolError = (message: string): never => {\n throw new Error(`statewire: protocol error: ${message}`);\n};\n\nconst applyResponse = (s: Session, response: StatewireResponse) => {\n const entry = s.pending.find((p) => p.seq === response.seq);\n if (!entry) return;\n const state = entry.state;\n if (state.at === \"done\") return;\n switch (response.type) {\n case \"pending\":\n if (\n state.at === \"queued\" ||\n state.at === \"trying\" ||\n state.at === \"posted\"\n ) {\n transition(s, entry, { ...state, pendingMarked: true });\n }\n break;\n case \"accepted\":\n if (state.at === \"settled-live\") break;\n transition(\n s,\n entry,\n state.at === \"awaiting-result\"\n ? { at: \"done\" }\n : { at: \"settled-live\", ...carried(state) },\n { ok: true, payload: response.payload },\n );\n break;\n case \"rejected\":\n transition(\n s,\n entry,\n { at: \"done\" },\n {\n ok: false,\n failure:\n response.code === \"unknown-command\"\n ? { fate: \"unknown-command\", cause: \"server\" }\n : response.code === \"invalid-params\"\n ? {\n fate: \"invalid-params\",\n cause: \"server\",\n message:\n response.message ??\n protocolError(\"rejected response missing message\"),\n }\n : {\n fate: \"rejected\",\n cause: \"server\",\n message:\n response.message ??\n protocolError(\"rejected response missing message\"),\n ...(response.payload !== undefined && {\n payload: response.payload,\n }),\n },\n },\n );\n break;\n case \"crashed\":\n case \"result-unavailable\":\n if (state.at === \"settled-live\") break;\n transition(\n s,\n entry,\n state.at === \"awaiting-result\"\n ? { at: \"done\" }\n : { at: \"settled-live\", ...carried(state) },\n { ok: false, failure: { fate: response.type, cause: \"server\" } },\n );\n break;\n }\n};\n\nconst ingestOps = (s: Session, ops: unknown[]) => {\n const applied = applyOps(s.canonical, ops);\n s.canonical = applied.state;\n s.changed.push(...applied.changedPaths);\n return applied.events;\n};\n\nconst publishIngested = (s: Session, events: unknown[]) => {\n publish(s);\n for (const event of events) s.emitEvent(event);\n wake(s);\n};\n\nconst handleSnapshot = (\n s: Session,\n { ops, lastSeq }: { ops: unknown[]; lastSeq: number },\n) => {\n if (lastSeq === -1 && s.attach !== \"detached\") {\n evict(s);\n s.transport?.connection.reconnect();\n publishIngested(s, ingestOps(s, ops));\n return;\n }\n const events = ingestOps(s, ops);\n s.attach = \"attached\";\n s.attachEpoch += 1;\n // The attach snapshot is not the true covering envelope: acked entries\n // stay awaiting their result and re-send under seq dedup.\n noteAcked(s, lastSeq);\n spliceAcked(s, lastSeq, false);\n s.lastAck = lastSeq;\n for (const entry of s.pending) {\n const state = entry.state;\n if (entry.seq <= lastSeq) {\n if (state.at === \"awaiting-result\" && state.posted) {\n transition(s, entry, { ...state, posted: false });\n }\n if (state.at === \"awaiting-result\") void pump(s);\n } else if (state.at === \"posted\") {\n // Above the ack watermark a prior receipt no longer counts.\n transition(s, entry, {\n at: \"trying\",\n pendingMarked: state.pendingMarked,\n failedAttempts: state.failedAttempts,\n });\n void pump(s);\n } else if (state.at === \"settled-live\" && state.posted) {\n transition(s, entry, { ...state, posted: false });\n void pump(s);\n }\n }\n publishIngested(s, events);\n};\n\nconst handleOps = (\n s: Session,\n {\n ops,\n res,\n ack,\n }: { ops: unknown[]; res?: StatewireResponse[]; ack?: number },\n) => {\n const horizon = ack === undefined ? s.lastAck : Math.max(s.lastAck, ack);\n if (res !== undefined) {\n for (const response of res) {\n if (response.seq > horizon) {\n protocolError(\"response seq beyond the ack\");\n }\n }\n }\n const events = ingestOps(s, ops);\n if (ack !== undefined) noteAcked(s, ack);\n if (res !== undefined) for (const response of res) applyResponse(s, response);\n if (ack !== undefined) {\n spliceAcked(s, ack, true);\n s.lastAck = Math.max(s.lastAck, ack);\n }\n publishIngested(s, events);\n};\n\nconst handleFinish = (s: Session, { reason }: { reason: string }) => {\n if (reason === \"evicted\" || reason === \"error\") return;\n s.attach = \"finished\";\n settleAll(s, reason === \"superseded\" ? \"superseded\" : \"gone\");\n publish(s);\n wake(s);\n};\n\nconst isFinished = (s: Session) => s.attach === \"finished\";\n\nconst sendable = (p: Pending) =>\n p.state.at === \"queued\" ||\n p.state.at === \"trying\" ||\n ((p.state.at === \"awaiting-result\" || p.state.at === \"settled-live\") &&\n !p.state.posted);\n\nconst pump = async (s: Session) => {\n if (s.pumping) return;\n s.pumping = true;\n try {\n while (s.running && !isFinished(s)) {\n const entry = s.pending.find(sendable);\n if (!entry) return;\n const transport = s.transport;\n if (s.attach !== \"attached\" || !transport) {\n await waitForWake(s);\n continue;\n }\n const clientId = s.record.clientId;\n const epoch = s.attachEpoch;\n if (entry.state.at === \"queued\") {\n transition(s, entry, {\n at: \"trying\",\n pendingMarked: entry.state.pendingMarked,\n failedAttempts: 0,\n });\n }\n let result: StatewireSubmitResult;\n try {\n result = await transport.send({\n seq: entry.seq,\n command: entry.command,\n });\n } catch (error) {\n if (!s.running || isFinished(s)) return;\n if (!s.pending.includes(entry)) continue;\n const state = inFlight(entry.state);\n transition(s, entry, {\n ...state,\n failedAttempts: state.failedAttempts + 1,\n });\n await sleepUntilWake(s, retryDelay(error, state.failedAttempts + 1));\n continue;\n }\n if (!s.running) return;\n if (!s.pending.includes(entry)) continue;\n if (result.type === \"receipt\") {\n const response = result.response;\n const state = entry.state;\n if (state.at === \"trying\" || state.at === \"posted\") {\n transition(s, entry, {\n at: \"posted\",\n pendingMarked: state.pendingMarked,\n failedAttempts: state.failedAttempts,\n });\n } else if (\n state.at === \"awaiting-result\" ||\n state.at === \"settled-live\"\n ) {\n transition(s, entry, { ...state, posted: true });\n }\n if (entry.inv) s.lifecycle?.onPosted?.(entry.inv);\n if (response !== undefined) {\n applyResponse(s, response);\n publish(s);\n }\n } else if (result.type === \"rejected\") {\n transition(\n s,\n entry,\n { at: \"done\" },\n {\n ok: false,\n failure: {\n fate: \"rejected\",\n cause: \"server\",\n message: result.message,\n },\n },\n );\n publish(s);\n } else if (result.type === \"stale-lease\") {\n if (epoch === s.attachEpoch) await waitForWake(s);\n } else if (result.type === \"unknown-client\") {\n // Eviction is never decided on the POST leg: re-attach first and let\n // the snapshot's lastSeq deliver the verdict. An answered attempt\n // proves the server never admitted this seq, so a clean entry requeues.\n if (epoch === s.attachEpoch) {\n const state = entry.state;\n if (state.at === \"trying\" && state.failedAttempts === 0) {\n transition(s, entry, {\n at: \"queued\",\n pendingMarked: state.pendingMarked,\n });\n }\n s.transport?.connection.reconnect();\n while (\n s.running &&\n !isFinished(s) &&\n epoch === s.attachEpoch &&\n clientId === s.record.clientId\n ) {\n await waitForWake(s);\n }\n }\n } else if (result.type === \"seq-gap\") {\n if (clientId === s.record.clientId) {\n console.error(\n `statewire: server reports a seq gap for seq ${entry.seq}; ` +\n \"treating the session as corrupt and rotating the client id\",\n );\n // Every attempt answered means the server never saw it; a thrown\n // attempt may have left the machine unanswered, so the verdict is \"lost\".\n const state = inFlight(entry.state);\n const failure: StatewireClient.SendFailure =\n state.at === \"awaiting-result\"\n ? { fate: \"result-unavailable\", cause: \"session-lost\" }\n : {\n fate: state.failedAttempts > 0 ? \"lost\" : \"aborted\",\n cause: \"session-lost\",\n };\n evict(s, { entry, failure });\n s.transport?.connection.reconnect();\n }\n }\n }\n } finally {\n s.pumping = false;\n }\n};\n\nconst start = (s: Session) => {\n if (!s.recordPersisted) {\n s.recordPersisted = true;\n s.store.save(s.record);\n }\n s.running = true;\n void pump(s);\n};\n\nconst stop = (s: Session) => {\n s.running = false;\n wake(s);\n settleAll(s, \"unmount\");\n};\n\nconst setTransport = (s: Session, transport: RemoteLink) => {\n if (s.transport === transport) return;\n s.transport = transport;\n wake(s);\n};\n\nconst enqueue = (\n s: Session,\n command: StatewireWireCommand,\n resolve: (payload: unknown) => void,\n reject: (error: StatewireSendError) => void,\n) => {\n const seq = s.record.nextSeq;\n s.record = { ...s.record, nextSeq: seq + 1 };\n s.store.save(s.record);\n const entry: Pending = {\n seq,\n command,\n inv:\n command.method === undefined\n ? null\n : {\n method: command.method,\n params: command.params,\n ts: Date.now(),\n seq,\n clientId: s.record.clientId,\n },\n state: { at: \"queued\", pendingMarked: false },\n resolve,\n reject,\n };\n s.pending.push(entry);\n if (entry.inv) s.lifecycle?.onStart(entry.inv);\n void pump(s);\n};\n\nconst sendCommand = (\n s: Session,\n command: StatewireWireCommand,\n): Promise<unknown> => {\n if (!s.running) {\n return rejectedSend({ fate: \"aborted\", cause: \"unmount\" });\n }\n if (isFinished(s)) {\n return rejectedSend({ fate: \"aborted\", cause: \"stream-gone\" });\n }\n const result = new Promise<unknown>((resolve, reject) => {\n enqueue(s, command, resolve, reject);\n });\n result.catch(() => {});\n return result;\n};\n\nconst setContext = (\n s: Session,\n value: Record<string, unknown>,\n): Promise<unknown> => {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new Error(\"statewire: context must be a plain object\");\n }\n s.context = value;\n return s.send({ context: value });\n};\n\nexport const useStatewireSession = (\n options: StatewireSessionOptions,\n emit: (event: unknown) => void,\n): Session => {\n const [s] = useState(() => createSession(options));\n s.onCommandSessionLost = options.onCommandSessionLost;\n useEffect(() => {\n s.emitEvent = emit;\n return () => {\n s.emitEvent = () => {};\n };\n }, [s, emit]);\n useEffect(() => {\n start(s);\n return () => stop(s);\n }, [s]);\n return s;\n};\n\nexport { setContext, setTransport };\n"],"mappings":"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"}
1
+ {"version":3,"file":"session.js","names":["useEffect","useState","applyOps","StatewirePath","StatewireResponse","bindCommandsHost","CommandsInvocation","CommandsListener","Statewire","StatewireClient","createMemorySessionStore","StatewireSessionRecord","StatewireSessionStore","StatewireSessionStoreOptions","retryDelay","createMethodProxy","randomId","StatewireSendError","RemoteLink","StatewireRemoteProps","StatewireSubmitResult","StatewireWireCommand","PendingState","at","pendingMarked","failedAttempts","posted","Pending","seq","command","inv","state","resolve","payload","reject","error","Outcome","ok","failure","SendFailure","isResolved","isStateSettled","inFlight","Error","legalTransitions","Record","queued","trying","done","transition","s","Session","entry","next","outcome","prev","includes","settles","undefined","covers","lifecycle","onCovered","onSettle","type","pending","filter","p","carried","Exclude","newClientId","rejectedSend","Promise","result","catch","AttachState","StatewireSessionOptions","record","ctx","recordPersisted","store","lastAck","context","canonical","lastPublished","changed","source","StateSource","attach","attachEpoch","running","pumping","transport","waiters","notify","emitEvent","event","send","commands","params","createSession","options","sessionStore","stored","load","clientId","nextSeq","onSnapshot","args","handleSnapshot","onOps","handleOps","onFinish","handleFinish","snapshot","takeChanged","setOnChange","listener","sendCommand","method","setListener","publish","wake","waiter","waitForWake","r","push","sleepUntilWake","delayMs","clearTimeout","timer","setTimeout","w","settleAll","detach","verdict","cause","evicted","unmount","gone","superseded","const","batch","detachFailure","fate","predecessorLost","some","claimReplays","claimed","length","evict","replays","save","old","enqueue","noteAcked","onAck","spliceAcked","settleVoid","protocolError","message","applyResponse","response","find","code","ingestOps","ops","applied","changedPaths","events","publishIngested","lastSeq","connection","reconnect","pump","res","ack","horizon","Math","max","reason","isFinished","sendable","epoch","onPosted","console","start","stop","setTransport","ts","Date","now","onStart","setContext","value","Array","isArray","useStatewireSession","emit"],"sources":["../../src/client/session.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { applyOps } from \"../wire/ops\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport type { StatewireResponse } from \"./connection\";\nimport { bindCommandsHost } from \"../host/host\";\nimport type { CommandsInvocation, CommandsListener } from \"../host/host\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireClient } from \"../statewire\";\nimport { createMemorySessionStore } from \"./sessionStore\";\nimport type {\n StatewireSessionRecord,\n StatewireSessionStore,\n StatewireSessionStoreOptions,\n} from \"./sessionStore\";\nimport { retryDelay } from \"./timing\";\nimport { createMethodProxy } from \"../util/methodProxy\";\nimport { randomId } from \"../util/randomId\";\nimport { StatewireSendError } from \"../types\";\nimport type {\n RemoteLink,\n StatewireRemoteProps,\n StatewireSubmitResult,\n StatewireWireCommand,\n} from \"../transports/sse\";\n\n/**\n * queued/trying/posted: overlay live, result pending. awaiting-result: acked\n * (overlay gone) but the result rides a late res; `posted: false` re-sends.\n * settled-live: result settled but the overlay stays until the covering ack.\n * done: removed from the queue.\n */\ntype PendingState =\n | { at: \"queued\"; pendingMarked: boolean }\n | { at: \"trying\"; pendingMarked: boolean; failedAttempts: number }\n | { at: \"posted\"; pendingMarked: boolean; failedAttempts: number }\n | { at: \"awaiting-result\"; posted: boolean; failedAttempts: number }\n | { at: \"settled-live\"; posted: boolean; failedAttempts: number }\n | { at: \"done\" };\n\ntype Pending = {\n readonly seq: number;\n readonly command: StatewireWireCommand;\n /** null for context submissions — they carry no command lifecycle. */\n readonly inv: CommandsInvocation | null;\n state: PendingState;\n resolve: (payload: unknown) => void;\n reject: (error: StatewireSendError) => void;\n};\n\ntype Outcome =\n | { ok: true; payload: unknown }\n | { ok: false; failure: StatewireClient.SendFailure };\n\nconst isResolved = (state: PendingState) =>\n state.at === \"settled-live\" || state.at === \"done\";\n\nconst isStateSettled = (state: PendingState) =>\n state.at === \"awaiting-result\" || state.at === \"done\";\n\n// The pump admits an entry as trying before sending; only the pump itself\n// requeues, and a done entry is never still pending.\nconst inFlight = (state: PendingState) => {\n if (state.at === \"queued\" || state.at === \"done\") {\n throw new Error(`statewire: ${state.at} entry observed after a send`);\n }\n return state;\n};\n\nconst legalTransitions: Record<PendingState[\"at\"], PendingState[\"at\"][]> = {\n queued: [\"queued\", \"trying\", \"awaiting-result\", \"settled-live\", \"done\"],\n // trying -> queued: an answered unknown-client proves the seq was never admitted.\n trying: [\n \"queued\",\n \"trying\",\n \"posted\",\n \"awaiting-result\",\n \"settled-live\",\n \"done\",\n ],\n posted: [\"posted\", \"trying\", \"awaiting-result\", \"settled-live\", \"done\"],\n \"awaiting-result\": [\"awaiting-result\", \"done\"],\n \"settled-live\": [\"settled-live\", \"done\"],\n done: [],\n};\n\nconst transition = (\n s: Session,\n entry: Pending,\n next: PendingState,\n outcome?: Outcome,\n) => {\n const prev = entry.state;\n if (!legalTransitions[prev.at].includes(next.at)) {\n throw new Error(\n `statewire: illegal pending transition ${prev.at} -> ${next.at}`,\n );\n }\n const settles = !isResolved(prev) && isResolved(next);\n if (settles && outcome === undefined) {\n throw new Error(\n `statewire: settling transition ${prev.at} -> ${next.at} without an outcome`,\n );\n }\n const covers = !isStateSettled(prev) && isStateSettled(next);\n entry.state = next;\n if (covers && entry.inv) s.lifecycle?.onCovered?.(entry.inv);\n if (settles && outcome !== undefined) {\n if (outcome.ok) {\n entry.resolve(outcome.payload);\n if (entry.inv)\n s.lifecycle?.onSettle(entry.inv, {\n type: \"accepted\",\n payload: outcome.payload,\n });\n } else {\n entry.reject(new StatewireSendError(outcome.failure));\n if (entry.inv)\n s.lifecycle?.onSettle(entry.inv, {\n type: \"rejected\",\n error: new StatewireSendError(outcome.failure),\n });\n }\n }\n if (next.at === \"done\") {\n s.pending = s.pending.filter((p) => p !== entry);\n }\n};\n\nconst carried = (\n state: Exclude<PendingState, { at: \"done\" }>,\n): { posted: boolean; failedAttempts: number } =>\n state.at === \"queued\"\n ? { posted: false, failedAttempts: 0 }\n : state.at === \"trying\" || state.at === \"posted\"\n ? { posted: state.at === \"posted\", failedAttempts: state.failedAttempts }\n : { posted: state.posted, failedAttempts: state.failedAttempts };\n\nconst newClientId = () => `c-${randomId()}`;\n\nconst rejectedSend = (\n failure: StatewireClient.SendFailure,\n): Promise<unknown> => {\n const result = Promise.reject<unknown>(new StatewireSendError(failure));\n result.catch(() => {});\n return result;\n};\n\ntype AttachState = \"detached\" | \"attached\" | \"finished\";\n\nexport type StatewireSessionOptions = StatewireSessionStoreOptions;\n\ntype Session = {\n record: StatewireSessionRecord;\n ctx: StatewireRemoteProps;\n recordPersisted: boolean;\n store: StatewireSessionStore;\n lastAck: number;\n context: Record<string, unknown> | undefined;\n pending: Pending[];\n canonical: unknown;\n lastPublished: unknown;\n changed: StatewirePath[];\n source: Statewire.StateSource;\n attach: AttachState;\n attachEpoch: number;\n running: boolean;\n pumping: boolean;\n transport: RemoteLink | null;\n waiters: (() => void)[];\n lifecycle: CommandsListener | null;\n notify: () => void;\n emitEvent: (event: unknown) => void;\n send: (command: StatewireWireCommand) => Promise<unknown>;\n commands: Record<string, (...params: unknown[]) => Promise<unknown>>;\n};\n\nconst createSession = (options: StatewireSessionOptions): Session => {\n const store = options.sessionStore ?? createMemorySessionStore();\n const stored = store.load();\n const record = stored ?? { clientId: newClientId(), nextSeq: 1 };\n const s: Session = {\n record,\n ctx: {\n get clientId() {\n return s.record.clientId;\n },\n onSnapshot: (args) => handleSnapshot(s, args),\n onOps: (args) => handleOps(s, args),\n onFinish: (args) => handleFinish(s, args),\n },\n recordPersisted: stored !== null,\n store,\n lastAck: -1,\n context: undefined,\n pending: [],\n canonical: undefined,\n lastPublished: undefined,\n changed: [],\n source: {\n snapshot: () => s.canonical,\n takeChanged: () => {\n const changed = s.changed;\n s.changed = [];\n return changed;\n },\n setOnChange: (listener) => {\n s.notify = listener ?? (() => {});\n },\n },\n attach: \"detached\",\n attachEpoch: 0,\n running: false,\n pumping: false,\n transport: null,\n waiters: [],\n lifecycle: null,\n notify: () => {},\n emitEvent: () => {},\n send: (command) => sendCommand(s, command),\n commands: createMethodProxy((method, params) => s.send({ method, params })),\n };\n bindCommandsHost(s.commands, {\n setListener: (listener) => {\n s.lifecycle = listener;\n },\n });\n return s;\n};\n\nconst publish = (s: Session) => {\n if (s.canonical === s.lastPublished) return;\n s.lastPublished = s.canonical;\n s.notify();\n};\n\nconst wake = (s: Session) => {\n const waiters = s.waiters;\n s.waiters = [];\n for (const waiter of waiters) waiter();\n};\n\nconst waitForWake = (s: Session) => new Promise<void>((r) => s.waiters.push(r));\n\nconst sleepUntilWake = (s: Session, delayMs: number) =>\n new Promise<void>((resolve) => {\n const waiter = () => {\n clearTimeout(timer);\n resolve();\n };\n const timer = setTimeout(() => {\n s.waiters = s.waiters.filter((w) => w !== waiter);\n resolve();\n }, delayMs);\n s.waiters.push(waiter);\n });\n\nconst settleAll = (\n s: Session,\n detach: \"evicted\" | \"unmount\" | \"gone\" | \"superseded\",\n verdict?: {\n entry: Pending;\n failure: StatewireClient.SendFailure;\n },\n) => {\n const cause = (\n {\n evicted: \"session-lost\",\n unmount: \"unmount\",\n gone: \"stream-gone\",\n superseded: \"superseded\",\n } as const\n )[detach];\n const batch = s.pending;\n const detachFailure = (entry: Pending): StatewireClient.SendFailure => {\n const state = entry.state;\n if (state.at === \"awaiting-result\")\n return { fate: \"result-unavailable\", cause };\n if (state.at === \"queued\") {\n const predecessorLost = batch.some(\n (p) => p.seq < entry.seq && p.state.at !== \"queued\",\n );\n return predecessorLost\n ? { fate: \"aborted\", cause, predecessorLost }\n : { fate: \"aborted\", cause };\n }\n return { fate: \"lost\", cause };\n };\n for (const entry of [...batch]) {\n const failure =\n entry === verdict?.entry ? verdict.failure : detachFailure(entry);\n transition(s, entry, { at: \"done\" }, { ok: false, failure });\n }\n};\n\n// Claims every unsettled real command for replay under the rotated client id,\n// in seq order; context submissions settle and the context re-sends whole.\nconst claimReplays = (s: Session): Pending[] => {\n const claimed = s.pending.filter(\n (entry) => entry.inv !== null && !isResolved(entry.state),\n );\n if (claimed.length === 0) return [];\n s.pending = s.pending.filter((p) => !claimed.includes(p));\n return claimed;\n};\n\nconst evict = (\n s: Session,\n verdict?: {\n entry: Pending;\n failure: StatewireClient.SendFailure;\n },\n) => {\n const replays = claimReplays(s);\n settleAll(s, \"evicted\", verdict);\n s.record = { clientId: newClientId(), nextSeq: 1 };\n s.store.save(s.record);\n s.attach = \"detached\";\n // The server dropped this client's record and its stored context with it:\n // the new session opens with a fresh full root replace.\n if (s.context !== undefined) void s.send({ context: s.context });\n for (const old of replays) {\n if (old.inv) s.lifecycle?.onCovered?.(old.inv);\n enqueue(s, old.command, old.resolve, old.reject);\n }\n publish(s);\n wake(s);\n};\n\nconst noteAcked = (s: Session, seq: number) => {\n for (const entry of s.pending) {\n if (entry.seq > seq || isStateSettled(entry.state)) continue;\n if (entry.inv) s.lifecycle?.onAck(entry.inv);\n }\n};\n\nconst spliceAcked = (s: Session, seq: number, settleVoid: boolean) => {\n for (const entry of [...s.pending]) {\n const state = entry.state;\n if (entry.seq > seq || isStateSettled(state)) continue;\n if (state.at === \"settled-live\") {\n transition(s, entry, { at: \"done\" });\n } else if (settleVoid && !state.pendingMarked) {\n transition(s, entry, { at: \"done\" }, { ok: true, payload: undefined });\n } else {\n transition(s, entry, { at: \"awaiting-result\", ...carried(state) });\n }\n }\n};\n\nconst protocolError = (message: string): never => {\n throw new Error(`statewire: protocol error: ${message}`);\n};\n\nconst applyResponse = (s: Session, response: StatewireResponse) => {\n const entry = s.pending.find((p) => p.seq === response.seq);\n if (!entry) return;\n const state = entry.state;\n if (state.at === \"done\") return;\n switch (response.type) {\n case \"pending\":\n if (\n state.at === \"queued\" ||\n state.at === \"trying\" ||\n state.at === \"posted\"\n ) {\n transition(s, entry, { ...state, pendingMarked: true });\n }\n break;\n case \"accepted\":\n if (state.at === \"settled-live\") break;\n transition(\n s,\n entry,\n state.at === \"awaiting-result\"\n ? { at: \"done\" }\n : { at: \"settled-live\", ...carried(state) },\n { ok: true, payload: response.payload },\n );\n break;\n case \"rejected\":\n transition(\n s,\n entry,\n { at: \"done\" },\n {\n ok: false,\n failure:\n response.code === \"unknown-command\"\n ? { fate: \"unknown-command\", cause: \"server\" }\n : response.code === \"invalid-params\"\n ? {\n fate: \"invalid-params\",\n cause: \"server\",\n message:\n response.message ??\n protocolError(\"rejected response missing message\"),\n }\n : {\n fate: \"rejected\",\n cause: \"server\",\n message:\n response.message ??\n protocolError(\"rejected response missing message\"),\n ...(response.payload !== undefined && {\n payload: response.payload,\n }),\n },\n },\n );\n break;\n case \"crashed\":\n case \"result-unavailable\":\n if (state.at === \"settled-live\") break;\n transition(\n s,\n entry,\n state.at === \"awaiting-result\"\n ? { at: \"done\" }\n : { at: \"settled-live\", ...carried(state) },\n { ok: false, failure: { fate: response.type, cause: \"server\" } },\n );\n break;\n }\n};\n\nconst ingestOps = (s: Session, ops: unknown[]) => {\n const applied = applyOps(s.canonical, ops);\n s.canonical = applied.state;\n s.changed.push(...applied.changedPaths);\n return applied.events;\n};\n\nconst publishIngested = (s: Session, events: unknown[]) => {\n publish(s);\n for (const event of events) s.emitEvent(event);\n wake(s);\n};\n\nconst handleSnapshot = (\n s: Session,\n { ops, lastSeq }: { ops: unknown[]; lastSeq: number },\n) => {\n if (lastSeq === -1 && s.attach !== \"detached\") {\n evict(s);\n s.transport?.connection.reconnect();\n publishIngested(s, ingestOps(s, ops));\n return;\n }\n const events = ingestOps(s, ops);\n s.attach = \"attached\";\n s.attachEpoch += 1;\n // The attach snapshot is not the true covering envelope: acked entries\n // stay awaiting their result and re-send under seq dedup.\n noteAcked(s, lastSeq);\n spliceAcked(s, lastSeq, false);\n s.lastAck = lastSeq;\n for (const entry of s.pending) {\n const state = entry.state;\n if (entry.seq <= lastSeq) {\n if (state.at === \"awaiting-result\" && state.posted) {\n transition(s, entry, { ...state, posted: false });\n }\n if (state.at === \"awaiting-result\") void pump(s);\n } else if (state.at === \"posted\") {\n // Above the ack watermark a prior receipt no longer counts.\n transition(s, entry, {\n at: \"trying\",\n pendingMarked: state.pendingMarked,\n failedAttempts: state.failedAttempts,\n });\n void pump(s);\n } else if (state.at === \"settled-live\" && state.posted) {\n transition(s, entry, { ...state, posted: false });\n void pump(s);\n }\n }\n publishIngested(s, events);\n};\n\nconst handleOps = (\n s: Session,\n {\n ops,\n res,\n ack,\n }: { ops: unknown[]; res?: StatewireResponse[]; ack?: number },\n) => {\n const horizon = ack === undefined ? s.lastAck : Math.max(s.lastAck, ack);\n if (res !== undefined) {\n for (const response of res) {\n if (response.seq > horizon) {\n protocolError(\"response seq beyond the ack\");\n }\n }\n }\n const events = ingestOps(s, ops);\n if (ack !== undefined) noteAcked(s, ack);\n if (res !== undefined) for (const response of res) applyResponse(s, response);\n if (ack !== undefined) {\n spliceAcked(s, ack, true);\n s.lastAck = Math.max(s.lastAck, ack);\n }\n publishIngested(s, events);\n};\n\nconst handleFinish = (s: Session, { reason }: { reason: string }) => {\n if (reason === \"evicted\" || reason === \"error\") return;\n s.attach = \"finished\";\n settleAll(s, reason === \"superseded\" ? \"superseded\" : \"gone\");\n publish(s);\n wake(s);\n};\n\nconst isFinished = (s: Session) => s.attach === \"finished\";\n\nconst sendable = (p: Pending) =>\n p.state.at === \"queued\" ||\n p.state.at === \"trying\" ||\n ((p.state.at === \"awaiting-result\" || p.state.at === \"settled-live\") &&\n !p.state.posted);\n\nconst pump = async (s: Session) => {\n if (s.pumping) return;\n s.pumping = true;\n try {\n while (s.running && !isFinished(s)) {\n const entry = s.pending.find(sendable);\n if (!entry) return;\n const transport = s.transport;\n if (s.attach !== \"attached\" || !transport) {\n await waitForWake(s);\n continue;\n }\n const clientId = s.record.clientId;\n const epoch = s.attachEpoch;\n if (entry.state.at === \"queued\") {\n transition(s, entry, {\n at: \"trying\",\n pendingMarked: entry.state.pendingMarked,\n failedAttempts: 0,\n });\n }\n let result: StatewireSubmitResult;\n try {\n result = await transport.send({\n seq: entry.seq,\n command: entry.command,\n });\n } catch (error) {\n if (!s.running || isFinished(s)) return;\n if (!s.pending.includes(entry)) continue;\n const state = inFlight(entry.state);\n transition(s, entry, {\n ...state,\n failedAttempts: state.failedAttempts + 1,\n });\n await sleepUntilWake(s, retryDelay(error, state.failedAttempts + 1));\n continue;\n }\n if (!s.running) return;\n if (!s.pending.includes(entry)) continue;\n if (result.type === \"receipt\") {\n const response = result.response;\n const state = entry.state;\n if (state.at === \"trying\" || state.at === \"posted\") {\n transition(s, entry, {\n at: \"posted\",\n pendingMarked: state.pendingMarked,\n failedAttempts: state.failedAttempts,\n });\n } else if (\n state.at === \"awaiting-result\" ||\n state.at === \"settled-live\"\n ) {\n transition(s, entry, { ...state, posted: true });\n }\n if (entry.inv) s.lifecycle?.onPosted?.(entry.inv);\n if (response !== undefined) {\n applyResponse(s, response);\n publish(s);\n }\n } else if (result.type === \"rejected\") {\n transition(\n s,\n entry,\n { at: \"done\" },\n {\n ok: false,\n failure: {\n fate: \"rejected\",\n cause: \"server\",\n message: result.message,\n },\n },\n );\n publish(s);\n } else if (result.type === \"stale-lease\") {\n if (epoch === s.attachEpoch) await waitForWake(s);\n } else if (result.type === \"unknown-client\") {\n // Eviction is never decided on the POST leg: re-attach first and let\n // the snapshot's lastSeq deliver the verdict. An answered attempt\n // proves the server never admitted this seq, so a clean entry requeues.\n if (epoch === s.attachEpoch) {\n const state = entry.state;\n if (state.at === \"trying\" && state.failedAttempts === 0) {\n transition(s, entry, {\n at: \"queued\",\n pendingMarked: state.pendingMarked,\n });\n }\n s.transport?.connection.reconnect();\n while (\n s.running &&\n !isFinished(s) &&\n epoch === s.attachEpoch &&\n clientId === s.record.clientId\n ) {\n await waitForWake(s);\n }\n }\n } else if (result.type === \"seq-gap\") {\n if (clientId === s.record.clientId) {\n console.error(\n `statewire: server reports a seq gap for seq ${entry.seq}; ` +\n \"treating the session as corrupt and rotating the client id\",\n );\n // Every attempt answered means the server never saw it; a thrown\n // attempt may have left the machine unanswered, so the verdict is \"lost\".\n const state = inFlight(entry.state);\n const failure: StatewireClient.SendFailure =\n state.at === \"awaiting-result\"\n ? { fate: \"result-unavailable\", cause: \"session-lost\" }\n : {\n fate: state.failedAttempts > 0 ? \"lost\" : \"aborted\",\n cause: \"session-lost\",\n };\n evict(s, { entry, failure });\n s.transport?.connection.reconnect();\n }\n }\n }\n } finally {\n s.pumping = false;\n }\n};\n\nconst start = (s: Session) => {\n if (!s.recordPersisted) {\n s.recordPersisted = true;\n s.store.save(s.record);\n }\n s.running = true;\n void pump(s);\n};\n\nconst stop = (s: Session) => {\n s.running = false;\n wake(s);\n settleAll(s, \"unmount\");\n};\n\nconst setTransport = (s: Session, transport: RemoteLink) => {\n if (s.transport === transport) return;\n s.transport = transport;\n wake(s);\n};\n\nconst enqueue = (\n s: Session,\n command: StatewireWireCommand,\n resolve: (payload: unknown) => void,\n reject: (error: StatewireSendError) => void,\n) => {\n const seq = s.record.nextSeq;\n s.record = { ...s.record, nextSeq: seq + 1 };\n s.store.save(s.record);\n const entry: Pending = {\n seq,\n command,\n inv:\n command.method === undefined\n ? null\n : {\n method: command.method,\n params: command.params,\n ts: Date.now(),\n seq,\n clientId: s.record.clientId,\n },\n state: { at: \"queued\", pendingMarked: false },\n resolve,\n reject,\n };\n s.pending.push(entry);\n if (entry.inv) s.lifecycle?.onStart(entry.inv);\n void pump(s);\n};\n\nconst sendCommand = (\n s: Session,\n command: StatewireWireCommand,\n): Promise<unknown> => {\n if (!s.running) {\n return rejectedSend({ fate: \"aborted\", cause: \"unmount\" });\n }\n if (isFinished(s)) {\n return rejectedSend({ fate: \"aborted\", cause: \"stream-gone\" });\n }\n const result = new Promise<unknown>((resolve, reject) => {\n enqueue(s, command, resolve, reject);\n });\n result.catch(() => {});\n return result;\n};\n\nconst setContext = (\n s: Session,\n value: Record<string, unknown>,\n): Promise<unknown> => {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new Error(\"statewire: context must be a plain object\");\n }\n s.context = value;\n return s.send({ context: value });\n};\n\nexport const useStatewireSession = (\n options: StatewireSessionOptions,\n emit: (event: unknown) => void,\n): Session => {\n const [s] = useState(() => createSession(options));\n useEffect(() => {\n s.emitEvent = emit;\n return () => {\n s.emitEvent = () => {};\n };\n }, [s, emit]);\n useEffect(() => {\n start(s);\n return () => stop(s);\n }, [s]);\n return s;\n};\n\nexport { setContext, setTransport };\n"],"mappings":";;;;;;;;;AAqDA,MAAMwC,cAAcT,UAClBA,MAAMR,OAAO,kBAAkBQ,MAAMR,OAAO;AAE9C,MAAMkB,kBAAkBV,UACtBA,MAAMR,OAAO,qBAAqBQ,MAAMR,OAAO;AAIjD,MAAMmB,YAAYX,UAAwB;CACxC,IAAIA,MAAMR,OAAO,YAAYQ,MAAMR,OAAO,QACxC,MAAM,IAAIoB,MAAM,cAAcZ,MAAMR,GAAE,6BAA8B;CAEtE,OAAOQ;AACT;AAEA,MAAMa,mBAAqE;CACzEE,QAAQ;EAAC;EAAU;EAAU;EAAmB;EAAgB;CAAM;CAEtEC,QAAQ;EACN;EACA;EACA;EACA;EACA;EACA;CAAM;CAERrB,QAAQ;EAAC;EAAU;EAAU;EAAmB;EAAgB;CAAM;CACtE,mBAAmB,CAAC,mBAAmB,MAAM;CAC7C,gBAAgB,CAAC,gBAAgB,MAAM;CACvCsB,MAAM,CAAA;AACR;AAEA,MAAMC,cACJC,GACAE,OACAC,MACAC,YACG;CACH,MAAMC,OAAOH,MAAMrB;CACnB,IAAI,CAACa,iBAAiBW,KAAKhC,GAAG,CAACiC,SAASH,KAAK9B,EAAE,GAC7C,MAAM,IAAIoB,MACR,yCAAyCY,KAAKhC,GAAE,MAAO8B,KAAK9B,IAC9D;CAEF,MAAMkC,UAAU,CAACjB,WAAWe,IAAI,KAAKf,WAAWa,IAAI;CACpD,IAAII,WAAWH,YAAYI,KAAAA,GACzB,MAAM,IAAIf,MACR,kCAAkCY,KAAKhC,GAAE,MAAO8B,KAAK9B,GAAE,oBACzD;CAEF,MAAMoC,SAAS,CAAClB,eAAec,IAAI,KAAKd,eAAeY,IAAI;CAC3DD,MAAMrB,QAAQsB;CACd,IAAIM,UAAUP,MAAMtB,KAAKoB,EAAEU,WAAWC,YAAYT,MAAMtB,GAAG;CAC3D,IAAI2B,WAAWH,YAAYI,KAAAA,GAAW;EACpC,IAAIJ,QAAQjB,IAAI;GACde,MAAMpB,QAAQsB,QAAQrB,OAAO;GAC7B,IAAImB,MAAMtB,KACRoB,EAAEU,WAAWE,SAASV,MAAMtB,KAAK;IAC/BiC,MAAM;IACN9B,SAASqB,QAAQrB;GACnB,CAAC;EACL,OAAO;GACLmB,MAAMlB,OAAO,IAAIjB,mBAAmBqC,QAAQhB,OAAO,CAAC;GACpD,IAAIc,MAAMtB,KACRoB,EAAEU,WAAWE,SAASV,MAAMtB,KAAK;IAC/BiC,MAAM;IACN5B,OAAO,IAAIlB,mBAAmBqC,QAAQhB,OAAO;GAC/C,CAAC;EACL;CACF;CACA,IAAIe,KAAK9B,OAAO,QACd2B,EAAEc,UAAUd,EAAEc,QAAQC,QAAQC,MAAMA,MAAMd,KAAK;AAEnD;AAEA,MAAMe,WACJpC,UAEAA,MAAMR,OAAO,WACT;CAAEG,QAAQ;CAAOD,gBAAgB;AAAE,IACnCM,MAAMR,OAAO,YAAYQ,MAAMR,OAAO,WACpC;CAAEG,QAAQK,MAAMR,OAAO;CAAUE,gBAAgBM,MAAMN;AAAe,IACtE;CAAEC,QAAQK,MAAML;CAAQD,gBAAgBM,MAAMN;AAAe;AAErE,MAAM4C,oBAAoB,KAAKrD,SAAS;AAExC,MAAMsD,gBACJhC,YACqB;CACrB,MAAMkC,SAASD,QAAQrC,OAAgB,IAAIjB,mBAAmBqB,OAAO,CAAC;CACtEkC,OAAOC,YAAY,CAAC,CAAC;CACrB,OAAOD;AACT;AA+BA,MAAM2B,iBAAiBC,YAA8C;CACnE,MAAMrB,QAAQqB,QAAQC,gBAAgB3F,yBAAyB;CAC/D,MAAM4F,SAASvB,MAAMwB,KAAK;CAE1B,MAAMrD,IAAa;EACjB0B,QAFa0B,UAAU;GAAEE,UAAUnC,YAAY;GAAGoC,SAAS;EAAE;EAG7D5B,KAAK;GACH,IAAI2B,WAAW;IACb,OAAOtD,EAAE0B,OAAO4B;GAClB;GACAE,aAAaC,SAASC,eAAe1D,GAAGyD,IAAI;GAC5CE,QAAQF,SAASG,UAAU5D,GAAGyD,IAAI;GAClCI,WAAWJ,SAASK,aAAa9D,GAAGyD,IAAI;EAC1C;EACA7B,iBAAiBwB,WAAW;EAC5BvB;EACAC,SAAS;EACTC,SAASvB,KAAAA;EACTM,SAAS,CAAA;EACTkB,WAAWxB,KAAAA;EACXyB,eAAezB,KAAAA;EACf0B,SAAS,CAAA;EACTC,QAAQ;GACN4B,gBAAgB/D,EAAEgC;GAClBgC,mBAAmB;IACjB,MAAM9B,UAAUlC,EAAEkC;IAClBlC,EAAEkC,UAAU,CAAA;IACZ,OAAOA;GACT;GACA+B,cAAcC,aAAa;IACzBlE,EAAE2C,SAASuB,mBAAmB,CAAC;GACjC;EACF;EACA7B,QAAQ;EACRC,aAAa;EACbC,SAAS;EACTC,SAAS;EACTC,WAAW;EACXC,SAAS,CAAA;EACThC,WAAW;EACXiC,cAAc,CAAC;EACfC,iBAAiB,CAAC;EAClBE,OAAOnE,YAAYwF,YAAYnE,GAAGrB,OAAO;EACzCoE,UAAUlF,mBAAmBuG,QAAQpB,WAAWhD,EAAE8C,KAAK;GAAEsB;GAAQpB;EAAO,CAAC,CAAC;CAC5E;CACA7F,iBAAiB6C,EAAE+C,UAAU,EAC3BsB,cAAcH,aAAa;EACzBlE,EAAEU,YAAYwD;CAChB,EACF,CAAC;CACD,OAAOlE;AACT;AAEA,MAAMsE,WAAWtE,MAAe;CAC9B,IAAIA,EAAEgC,cAAchC,EAAEiC,eAAe;CACrCjC,EAAEiC,gBAAgBjC,EAAEgC;CACpBhC,EAAE2C,OAAO;AACX;AAEA,MAAM4B,QAAQvE,MAAe;CAC3B,MAAM0C,UAAU1C,EAAE0C;CAClB1C,EAAE0C,UAAU,CAAA;CACZ,KAAK,MAAM8B,UAAU9B,SAAS8B,OAAO;AACvC;AAEA,MAAMC,eAAezE,MAAe,IAAIqB,SAAeqD,MAAM1E,EAAE0C,QAAQiC,KAAKD,CAAC,CAAC;AAE9E,MAAME,kBAAkB5E,GAAY6E,YAClC,IAAIxD,SAAevC,YAAY;CAC7B,MAAM0F,eAAe;EACnBM,aAAaC,KAAK;EAClBjG,QAAQ;CACV;CACA,MAAMiG,QAAQC,iBAAiB;EAC7BhF,EAAE0C,UAAU1C,EAAE0C,QAAQ3B,QAAQkE,MAAMA,MAAMT,MAAM;EAChD1F,QAAQ;CACV,GAAG+F,OAAO;CACV7E,EAAE0C,QAAQiC,KAAKH,MAAM;AACvB,CAAC;AAEH,MAAMU,aACJlF,GACAmF,QACAC,YAIG;CACH,MAAMC,QACJ;EACEC,SAAS;EACTC,SAAS;EACTC,MAAM;EACNC,YAAY;CACd,EACAN;CACF,MAAMQ,QAAQ3F,EAAEc;CAChB,MAAM8E,iBAAiB1F,UAAgD;EACrE,MAAMrB,QAAQqB,MAAMrB;EACpB,IAAIA,MAAMR,OAAO,mBACf,OAAO;GAAEwH,MAAM;GAAsBR;EAAM;EAC7C,IAAIxG,MAAMR,OAAO,UAAU;GACzB,MAAMyH,kBAAkBH,MAAMI,MAC3B/E,MAAMA,EAAEtC,MAAMwB,MAAMxB,OAAOsC,EAAEnC,MAAMR,OAAO,QAC7C;GACA,OAAOyH,kBACH;IAAED,MAAM;IAAWR;IAAOS;GAAgB,IAC1C;IAAED,MAAM;IAAWR;GAAM;EAC/B;EACA,OAAO;GAAEQ,MAAM;GAAQR;EAAM;CAC/B;CACA,KAAK,MAAMnF,SAAS,CAAC,GAAGyF,KAAK,GAAG;EAC9B,MAAMvG,UACJc,UAAUkF,SAASlF,QAAQkF,QAAQhG,UAAUwG,cAAc1F,KAAK;EAClEH,WAAWC,GAAGE,OAAO,EAAE7B,IAAI,OAAO,GAAG;GAAEc,IAAI;GAAOC;EAAQ,CAAC;CAC7D;AACF;AAIA,MAAM4G,gBAAgBhG,MAA0B;CAC9C,MAAMiG,UAAUjG,EAAEc,QAAQC,QACvBb,UAAUA,MAAMtB,QAAQ,QAAQ,CAACU,WAAWY,MAAMrB,KAAK,CAC1D;CACA,IAAIoH,QAAQC,WAAW,GAAG,OAAO,CAAA;CACjClG,EAAEc,UAAUd,EAAEc,QAAQC,QAAQC,MAAM,CAACiF,QAAQ3F,SAASU,CAAC,CAAC;CACxD,OAAOiF;AACT;AAEA,MAAME,SACJnG,GACAoF,YAIG;CACH,MAAMgB,UAAUJ,aAAahG,CAAC;CAC9BkF,UAAUlF,GAAG,WAAWoF,OAAO;CAC/BpF,EAAE0B,SAAS;EAAE4B,UAAUnC,YAAY;EAAGoC,SAAS;CAAE;CACjDvD,EAAE6B,MAAMwE,KAAKrG,EAAE0B,MAAM;CACrB1B,EAAEqC,SAAS;CAGX,IAAIrC,EAAE+B,YAAYvB,KAAAA,GAAW,EAAOsC,KAAK,EAAEf,SAAS/B,EAAE+B,QAAQ,CAAC;CAC/D,KAAK,MAAMuE,OAAOF,SAAS;EACzB,IAAIE,IAAI1H,KAAKoB,EAAEU,WAAWC,YAAY2F,IAAI1H,GAAG;EAC7C2H,QAAQvG,GAAGsG,IAAI3H,SAAS2H,IAAIxH,SAASwH,IAAItH,MAAM;CACjD;CACAsF,QAAQtE,CAAC;CACTuE,KAAKvE,CAAC;AACR;AAEA,MAAMwG,aAAaxG,GAAYtB,QAAgB;CAC7C,KAAK,MAAMwB,SAASF,EAAEc,SAAS;EAC7B,IAAIZ,MAAMxB,MAAMA,OAAOa,eAAeW,MAAMrB,KAAK,GAAG;EACpD,IAAIqB,MAAMtB,KAAKoB,EAAEU,WAAW+F,MAAMvG,MAAMtB,GAAG;CAC7C;AACF;AAEA,MAAM8H,eAAe1G,GAAYtB,KAAaiI,eAAwB;CACpE,KAAK,MAAMzG,SAAS,CAAC,GAAGF,EAAEc,OAAO,GAAG;EAClC,MAAMjC,QAAQqB,MAAMrB;EACpB,IAAIqB,MAAMxB,MAAMA,OAAOa,eAAeV,KAAK,GAAG;EAC9C,IAAIA,MAAMR,OAAO,gBACf0B,WAAWC,GAAGE,OAAO,EAAE7B,IAAI,OAAO,CAAC;OAC9B,IAAIsI,cAAc,CAAC9H,MAAMP,eAC9ByB,WAAWC,GAAGE,OAAO,EAAE7B,IAAI,OAAO,GAAG;GAAEc,IAAI;GAAMJ,SAASyB,KAAAA;EAAU,CAAC;OAErET,WAAWC,GAAGE,OAAO;GAAE7B,IAAI;GAAmB,GAAG4C,QAAQpC,KAAK;EAAE,CAAC;CAErE;AACF;AAEA,MAAM+H,iBAAiBC,YAA2B;CAChD,MAAM,IAAIpH,MAAM,8BAA8BoH,SAAS;AACzD;AAEA,MAAMC,iBAAiB9G,GAAY+G,aAAgC;CACjE,MAAM7G,QAAQF,EAAEc,QAAQkG,MAAMhG,MAAMA,EAAEtC,QAAQqI,SAASrI,GAAG;CAC1D,IAAI,CAACwB,OAAO;CACZ,MAAMrB,QAAQqB,MAAMrB;CACpB,IAAIA,MAAMR,OAAO,QAAQ;CACzB,QAAQ0I,SAASlG,MAAjB;EACE,KAAK;GACH,IACEhC,MAAMR,OAAO,YACbQ,MAAMR,OAAO,YACbQ,MAAMR,OAAO,UAEb0B,WAAWC,GAAGE,OAAO;IAAE,GAAGrB;IAAOP,eAAe;GAAK,CAAC;GAExD;EACF,KAAK;GACH,IAAIO,MAAMR,OAAO,gBAAgB;GACjC0B,WACEC,GACAE,OACArB,MAAMR,OAAO,oBACT,EAAEA,IAAI,OAAO,IACb;IAAEA,IAAI;IAAgB,GAAG4C,QAAQpC,KAAK;GAAE,GAC5C;IAAEM,IAAI;IAAMJ,SAASgI,SAAShI;GAAQ,CACxC;GACA;EACF,KAAK;GACHgB,WACEC,GACAE,OACA,EAAE7B,IAAI,OAAO,GACb;IACEc,IAAI;IACJC,SACE2H,SAASE,SAAS,oBACd;KAAEpB,MAAM;KAAmBR,OAAO;IAAS,IAC3C0B,SAASE,SAAS,mBAChB;KACEpB,MAAM;KACNR,OAAO;KACPwB,SACEE,SAASF,WACTD,cAAc,mCAAmC;IACrD,IACA;KACEf,MAAM;KACNR,OAAO;KACPwB,SACEE,SAASF,WACTD,cAAc,mCAAmC;KACnD,GAAIG,SAAShI,YAAYyB,KAAAA,KAAa,EACpCzB,SAASgI,SAAShI,QACpB;IACF;GACV,CACF;GACA;EACF,KAAK;EACL,KAAK;GACH,IAAIF,MAAMR,OAAO,gBAAgB;GACjC0B,WACEC,GACAE,OACArB,MAAMR,OAAO,oBACT,EAAEA,IAAI,OAAO,IACb;IAAEA,IAAI;IAAgB,GAAG4C,QAAQpC,KAAK;GAAE,GAC5C;IAAEM,IAAI;IAAOC,SAAS;KAAEyG,MAAMkB,SAASlG;KAAMwE,OAAO;IAAS;GAAE,CACjE;CAEJ;AACF;AAEA,MAAM6B,aAAalH,GAAYmH,QAAmB;CAChD,MAAMC,UAAUpK,SAASgD,EAAEgC,WAAWmF,GAAG;CACzCnH,EAAEgC,YAAYoF,QAAQvI;CACtBmB,EAAEkC,QAAQyC,KAAK,GAAGyC,QAAQC,YAAY;CACtC,OAAOD,QAAQE;AACjB;AAEA,MAAMC,mBAAmBvH,GAAYsH,WAAsB;CACzDhD,QAAQtE,CAAC;CACT,KAAK,MAAM6C,SAASyE,QAAQtH,EAAE4C,UAAUC,KAAK;CAC7C0B,KAAKvE,CAAC;AACR;AAEA,MAAM0D,kBACJ1D,GACA,EAAEmH,KAAKK,cACJ;CACH,IAAIA,YAAY,MAAMxH,EAAEqC,WAAW,YAAY;EAC7C8D,MAAMnG,CAAC;EACPA,EAAEyC,WAAWgF,WAAWC,UAAU;EAClCH,gBAAgBvH,GAAGkH,UAAUlH,GAAGmH,GAAG,CAAC;EACpC;CACF;CACA,MAAMG,SAASJ,UAAUlH,GAAGmH,GAAG;CAC/BnH,EAAEqC,SAAS;CACXrC,EAAEsC,eAAe;CAGjBkE,UAAUxG,GAAGwH,OAAO;CACpBd,YAAY1G,GAAGwH,SAAS,KAAK;CAC7BxH,EAAE8B,UAAU0F;CACZ,KAAK,MAAMtH,SAASF,EAAEc,SAAS;EAC7B,MAAMjC,QAAQqB,MAAMrB;EACpB,IAAIqB,MAAMxB,OAAO8I,SAAS;GACxB,IAAI3I,MAAMR,OAAO,qBAAqBQ,MAAML,QAC1CuB,WAAWC,GAAGE,OAAO;IAAE,GAAGrB;IAAOL,QAAQ;GAAM,CAAC;GAElD,IAAIK,MAAMR,OAAO,mBAAmB,KAAU2B,CAAC;EACjD,OAAO,IAAInB,MAAMR,OAAO,UAAU;GAEhC0B,WAAWC,GAAGE,OAAO;IACnB7B,IAAI;IACJC,eAAeO,MAAMP;IACrBC,gBAAgBM,MAAMN;GACxB,CAAC;GACD,KAAUyB,CAAC;EACb,OAAO,IAAInB,MAAMR,OAAO,kBAAkBQ,MAAML,QAAQ;GACtDuB,WAAWC,GAAGE,OAAO;IAAE,GAAGrB;IAAOL,QAAQ;GAAM,CAAC;GAChD,KAAUwB,CAAC;EACb;CACF;CACAuH,gBAAgBvH,GAAGsH,MAAM;AAC3B;AAEA,MAAM1D,aACJ5D,GACA,EACEmH,KACAS,KACAC,UAEC;CACH,MAAMC,UAAUD,QAAQrH,KAAAA,IAAYR,EAAE8B,UAAUiG,KAAKC,IAAIhI,EAAE8B,SAAS+F,GAAG;CACvE,IAAID,QAAQpH,KAAAA,GACL;OAAA,MAAMuG,YAAYa,KACrB,IAAIb,SAASrI,MAAMoJ,SACjBlB,cAAc,6BAA6B;CAAA;CAIjD,MAAMU,SAASJ,UAAUlH,GAAGmH,GAAG;CAC/B,IAAIU,QAAQrH,KAAAA,GAAWgG,UAAUxG,GAAG6H,GAAG;CACvC,IAAID,QAAQpH,KAAAA,GAAW,KAAK,MAAMuG,YAAYa,KAAKd,cAAc9G,GAAG+G,QAAQ;CAC5E,IAAIc,QAAQrH,KAAAA,GAAW;EACrBkG,YAAY1G,GAAG6H,KAAK,IAAI;EACxB7H,EAAE8B,UAAUiG,KAAKC,IAAIhI,EAAE8B,SAAS+F,GAAG;CACrC;CACAN,gBAAgBvH,GAAGsH,MAAM;AAC3B;AAEA,MAAMxD,gBAAgB9D,GAAY,EAAEiI,aAAiC;CACnE,IAAIA,WAAW,aAAaA,WAAW,SAAS;CAChDjI,EAAEqC,SAAS;CACX6C,UAAUlF,GAAGiI,WAAW,eAAe,eAAe,MAAM;CAC5D3D,QAAQtE,CAAC;CACTuE,KAAKvE,CAAC;AACR;AAEA,MAAMkI,cAAclI,MAAeA,EAAEqC,WAAW;AAEhD,MAAM8F,YAAYnH,MAChBA,EAAEnC,MAAMR,OAAO,YACf2C,EAAEnC,MAAMR,OAAO,aACb2C,EAAEnC,MAAMR,OAAO,qBAAqB2C,EAAEnC,MAAMR,OAAO,mBACnD,CAAC2C,EAAEnC,MAAML;AAEb,MAAMmJ,OAAO,OAAO3H,MAAe;CACjC,IAAIA,EAAEwC,SAAS;CACfxC,EAAEwC,UAAU;CACZ,IAAI;EACF,OAAOxC,EAAEuC,WAAW,CAAC2F,WAAWlI,CAAC,GAAG;GAClC,MAAME,QAAQF,EAAEc,QAAQkG,KAAKmB,QAAQ;GACrC,IAAI,CAACjI,OAAO;GACZ,MAAMuC,YAAYzC,EAAEyC;GACpB,IAAIzC,EAAEqC,WAAW,cAAc,CAACI,WAAW;IACzC,MAAMgC,YAAYzE,CAAC;IACnB;GACF;GACA,MAAMsD,WAAWtD,EAAE0B,OAAO4B;GAC1B,MAAM8E,QAAQpI,EAAEsC;GAChB,IAAIpC,MAAMrB,MAAMR,OAAO,UACrB0B,WAAWC,GAAGE,OAAO;IACnB7B,IAAI;IACJC,eAAe4B,MAAMrB,MAAMP;IAC3BC,gBAAgB;GAClB,CAAC;GAEH,IAAI+C;GACJ,IAAI;IACFA,SAAS,MAAMmB,UAAUK,KAAK;KAC5BpE,KAAKwB,MAAMxB;KACXC,SAASuB,MAAMvB;IACjB,CAAC;GACH,SAASM,OAAO;IACd,IAAI,CAACe,EAAEuC,WAAW2F,WAAWlI,CAAC,GAAG;IACjC,IAAI,CAACA,EAAEc,QAAQR,SAASJ,KAAK,GAAG;IAChC,MAAMrB,QAAQW,SAASU,MAAMrB,KAAK;IAClCkB,WAAWC,GAAGE,OAAO;KACnB,GAAGrB;KACHN,gBAAgBM,MAAMN,iBAAiB;IACzC,CAAC;IACD,MAAMqG,eAAe5E,GAAGpC,WAAWqB,OAAOJ,MAAMN,iBAAiB,CAAC,CAAC;IACnE;GACF;GACA,IAAI,CAACyB,EAAEuC,SAAS;GAChB,IAAI,CAACvC,EAAEc,QAAQR,SAASJ,KAAK,GAAG;GAChC,IAAIoB,OAAOT,SAAS,WAAW;IAC7B,MAAMkG,WAAWzF,OAAOyF;IACxB,MAAMlI,QAAQqB,MAAMrB;IACpB,IAAIA,MAAMR,OAAO,YAAYQ,MAAMR,OAAO,UACxC0B,WAAWC,GAAGE,OAAO;KACnB7B,IAAI;KACJC,eAAeO,MAAMP;KACrBC,gBAAgBM,MAAMN;IACxB,CAAC;SACI,IACLM,MAAMR,OAAO,qBACbQ,MAAMR,OAAO,gBAEb0B,WAAWC,GAAGE,OAAO;KAAE,GAAGrB;KAAOL,QAAQ;IAAK,CAAC;IAEjD,IAAI0B,MAAMtB,KAAKoB,EAAEU,WAAW2H,WAAWnI,MAAMtB,GAAG;IAChD,IAAImI,aAAavG,KAAAA,GAAW;KAC1BsG,cAAc9G,GAAG+G,QAAQ;KACzBzC,QAAQtE,CAAC;IACX;GACF,OAAO,IAAIsB,OAAOT,SAAS,YAAY;IACrCd,WACEC,GACAE,OACA,EAAE7B,IAAI,OAAO,GACb;KACEc,IAAI;KACJC,SAAS;MACPyG,MAAM;MACNR,OAAO;MACPwB,SAASvF,OAAOuF;KAClB;IACF,CACF;IACAvC,QAAQtE,CAAC;GACX,OAAO,IAAIsB,OAAOT,SAAS,eACrBuH;QAAAA,UAAUpI,EAAEsC,aAAa,MAAMmC,YAAYzE,CAAC;GAAA,OAC3C,IAAIsB,OAAOT,SAAS,kBAIrBuH;QAAAA,UAAUpI,EAAEsC,aAAa;KAC3B,MAAMzD,QAAQqB,MAAMrB;KACpB,IAAIA,MAAMR,OAAO,YAAYQ,MAAMN,mBAAmB,GACpDwB,WAAWC,GAAGE,OAAO;MACnB7B,IAAI;MACJC,eAAeO,MAAMP;KACvB,CAAC;KAEH0B,EAAEyC,WAAWgF,WAAWC,UAAU;KAClC,OACE1H,EAAEuC,WACF,CAAC2F,WAAWlI,CAAC,KACboI,UAAUpI,EAAEsC,eACZgB,aAAatD,EAAE0B,OAAO4B,UAEtB,MAAMmB,YAAYzE,CAAC;IAEvB;UACK,IAAIsB,OAAOT,SAAS,WACrByC;QAAAA,aAAatD,EAAE0B,OAAO4B,UAAU;KAClCgF,QAAQrJ,MACN,+CAA+CiB,MAAMxB,IAAG,6DAE1D;KAGA,MAAMG,QAAQW,SAASU,MAAMrB,KAAK;KAClC,MAAMO,UACJP,MAAMR,OAAO,oBACT;MAAEwH,MAAM;MAAsBR,OAAO;KAAe,IACpD;MACEQ,MAAMhH,MAAMN,iBAAiB,IAAI,SAAS;MAC1C8G,OAAO;KACT;KACNc,MAAMnG,GAAG;MAAEE;MAAOd;KAAQ,CAAC;KAC3BY,EAAEyC,WAAWgF,WAAWC,UAAU;IACpC;;EAEJ;CACF,UAAU;EACR1H,EAAEwC,UAAU;CACd;AACF;AAEA,MAAM+F,SAASvI,MAAe;CAC5B,IAAI,CAACA,EAAE4B,iBAAiB;EACtB5B,EAAE4B,kBAAkB;EACpB5B,EAAE6B,MAAMwE,KAAKrG,EAAE0B,MAAM;CACvB;CACA1B,EAAEuC,UAAU;CACZ,KAAUvC,CAAC;AACb;AAEA,MAAMwI,QAAQxI,MAAe;CAC3BA,EAAEuC,UAAU;CACZgC,KAAKvE,CAAC;CACNkF,UAAUlF,GAAG,SAAS;AACxB;AAEA,MAAMyI,gBAAgBzI,GAAYyC,cAA0B;CAC1D,IAAIzC,EAAEyC,cAAcA,WAAW;CAC/BzC,EAAEyC,YAAYA;CACd8B,KAAKvE,CAAC;AACR;AAEA,MAAMuG,WACJvG,GACArB,SACAG,SACAE,WACG;CACH,MAAMN,MAAMsB,EAAE0B,OAAO6B;CACrBvD,EAAE0B,SAAS;EAAE,GAAG1B,EAAE0B;EAAQ6B,SAAS7E,MAAM;CAAE;CAC3CsB,EAAE6B,MAAMwE,KAAKrG,EAAE0B,MAAM;CACrB,MAAMxB,QAAiB;EACrBxB;EACAC;EACAC,KACED,QAAQyF,WAAW5D,KAAAA,IACf,OACA;GACE4D,QAAQzF,QAAQyF;GAChBpB,QAAQrE,QAAQqE;GAChB0F,IAAIC,KAAKC,IAAI;GACblK;GACA4E,UAAUtD,EAAE0B,OAAO4B;EACrB;EACNzE,OAAO;GAAER,IAAI;GAAUC,eAAe;EAAM;EAC5CQ;EACAE;CACF;CACAgB,EAAEc,QAAQ6D,KAAKzE,KAAK;CACpB,IAAIA,MAAMtB,KAAKoB,EAAEU,WAAWmI,QAAQ3I,MAAMtB,GAAG;CAC7C,KAAUoB,CAAC;AACb;AAEA,MAAMmE,eACJnE,GACArB,YACqB;CACrB,IAAI,CAACqB,EAAEuC,SACL,OAAOnB,aAAa;EAAEyE,MAAM;EAAWR,OAAO;CAAU,CAAC;CAE3D,IAAI6C,WAAWlI,CAAC,GACd,OAAOoB,aAAa;EAAEyE,MAAM;EAAWR,OAAO;CAAc,CAAC;CAE/D,MAAM/D,SAAS,IAAID,SAAkBvC,SAASE,WAAW;EACvDuH,QAAQvG,GAAGrB,SAASG,SAASE,MAAM;CACrC,CAAC;CACDsC,OAAOC,YAAY,CAAC,CAAC;CACrB,OAAOD;AACT;AAEA,MAAMwH,cACJ9I,GACA+I,UACqB;CACrB,IAAI,OAAOA,UAAU,YAAYA,UAAU,QAAQC,MAAMC,QAAQF,KAAK,GACpE,MAAM,IAAItJ,MAAM,2CAA2C;CAE7DO,EAAE+B,UAAUgH;CACZ,OAAO/I,EAAE8C,KAAK,EAAEf,SAASgH,MAAM,CAAC;AAClC;AAEA,MAAaG,uBACXhG,SACAiG,SACY;CACZ,MAAM,CAACnJ,KAAKjD,eAAekG,cAAcC,OAAO,CAAC;CACjDpG,gBAAgB;EACdkD,EAAE4C,YAAYuG;EACd,aAAa;GACXnJ,EAAE4C,kBAAkB,CAAC;EACvB;CACF,GAAG,CAAC5C,GAAGmJ,IAAI,CAAC;CACZrM,gBAAgB;EACdyL,MAAMvI,CAAC;EACP,aAAawI,KAAKxI,CAAC;CACrB,GAAG,CAACA,CAAC,CAAC;CACN,OAAOA;AACT"}
package/dist/index.d.ts CHANGED
@@ -2,4 +2,5 @@ import { StatewireClient, StatewireResource, useStatewire } from "./statewire.js
2
2
  import { Statewire, StatewireSendError } from "./types.js";
3
3
  import { StatewireSSE } from "./transports/sse.js";
4
4
  import { StatewireWS } from "./transports/ws.js";
5
- export { type Statewire, StatewireClient, StatewireResource, StatewireSSE, StatewireSendError, StatewireWS, useStatewire };
5
+ import { applyOptimistic } from "./util/recordMutations.js";
6
+ export { type Statewire, StatewireClient, StatewireResource, StatewireSSE, StatewireSendError, StatewireWS, applyOptimistic, useStatewire };
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
+ import { applyOptimistic } from "./util/recordMutations.js";
1
2
  import { StatewireSendError } from "./types.js";
2
3
  import { StatewireClient, StatewireResource, useStatewire } from "./statewire.js";
3
4
  import { StatewireSSE } from "./transports/sse.js";
4
5
  import { StatewireWS } from "./transports/ws.js";
5
- export { StatewireClient, StatewireResource, StatewireSSE, StatewireSendError, StatewireWS, useStatewire };
6
+ export { StatewireClient, StatewireResource, StatewireSSE, StatewireSendError, StatewireWS, applyOptimistic, useStatewire };
@@ -28,6 +28,7 @@ declare namespace StatewireClient {
28
28
  onCommandChange?: (update: Statewire.CommandUpdate<TCommands>, ctx: {
29
29
  state: NonNullable<TState>;
30
30
  }) => void;
31
+ onConnectionChange?: (connection: Connection) => void;
31
32
  };
32
33
  type Helpers<TState, TCommands extends Statewire.Commands = Statewire.Commands> = {
33
34
  /** The statewire's state type — remote statewires include `undefined` until the first attach snapshot applies. */
@@ -45,7 +46,7 @@ declare namespace StatewireClient {
45
46
  status: "live";
46
47
  } | {
47
48
  status: "reconnecting";
48
- cause: "dropped" | "evicted" | "error";
49
+ cause: "dropped" | "evicted" | "error" | "unauthorized";
49
50
  attempt: number;
50
51
  nextRetryAt: number;
51
52
  lastError?: string;
@@ -104,30 +105,23 @@ declare namespace StatewireClient {
104
105
  fate: "lost";
105
106
  cause: "unmount" | "stream-gone" | "session-lost" | "superseded";
106
107
  };
107
- /**
108
- * A pending command offered for replay while the session is being evicted.
109
- * Return "replay" to re-enqueue it under the rotated client id instead of
110
- * settling it as a session-lost failure.
111
- */
112
- type SessionLostCommand = {
113
- method: string;
114
- params: unknown[];
115
- seq: number;
116
- };
108
+ /** Static headers, or a resolver invoked per request/attach; `staleAuth` is true right after an auth rejection. */
109
+ type HeadersOption = HeadersInit | ((ctx: {
110
+ staleAuth: boolean;
111
+ }) => HeadersInit | Promise<HeadersInit>);
117
112
  type SSEOptions = StatewireSessionStoreOptions & {
118
113
  url: string;
119
- headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
114
+ headers?: HeadersOption;
120
115
  fetch?: typeof globalThis.fetch;
121
116
  onChange?: (connection: Connection) => void;
122
- onCommandSessionLost?: (command: SessionLostCommand) => "replay" | void;
123
117
  };
124
118
  type WSOptions = StatewireSessionStoreOptions & {
125
119
  url: string;
120
+ headers?: HeadersOption;
126
121
  protocols?: string | string[];
127
122
  query?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
128
123
  webSocket?: new (url: string, protocols?: string | string[]) => WebSocket;
129
124
  onChange?: (connection: Connection) => void;
130
- onCommandSessionLost?: (command: SessionLostCommand) => "replay" | void;
131
125
  };
132
126
  }
133
127
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"statewire.d.ts","names":[],"sources":["../src/statewire.ts"],"mappings":";;;;cAYa,eACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD,cAEA,SAAS,gBAAgB,QAAQ,QAAQ,WAAW,YACnD,gBAAgB,QAAQ,QAAQ;cAqCtB,oBACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD,cAEA,SAAS,gBAAgB,QAAQ,QAAQ,WAAW,YACnD,gBAAgB,gBAAgB,QAAQ,QAAQ;cAGtC,gBACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD;mBAEiB;UACT;EAEI,YAAA,SAAS,gBAAgB,QAAQ,QAAQ,WAAW;MAK5D,SAAS;MAIT,cAAc,gBAAgB;MAI9B,YAAY,UAAU,cAAc;EAIxC,WAAW,OAAO,0BAA0B;EAI5C,UAAU;EAIV;;kBAOe;OACH,QACV,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD;IAEA,WAAW,gBAAgB,UAAU,QAAQ,WAAW;IACxD,aAAa,WAAW,YAAY,SAAS;IAC7C,WAAW,OAAO,QAAQ;MAAO,OAAO,YAAY;;IACpD,iBACE,OAAO,YAAY,SACnB;MAAO,SAAS,UAAU;;IAE5B,mBACE,QAAQ,UAAU,cAAc,YAChC;MAAO,OAAO,YAAY;;;OAIlB,QACV,QACA,kBAAkB,UAAU,WAAW,UAAU;;IAGjD,OAAO;;IAEP,gBAAgB;IAChB,YAAY;IACZ,UAAU,UAAU,cAAc;;IAElC,aAAa,OAAO,4BAA4B;;OAGtC;IACN;;IACA;;IAEA;IACA;IACA;IACA;IACA;IACA;;IAEA;IAAgB;IAAkB;;OAC5B,aAAa;IAAoB;;OACjC,mBAAmB;;;;;OAMnB,WACV,QACA,kBAAkB,UAAU,WAAW,UAAU,aAEjD,OAAO,QACP,SAAS,UAAU,QAAQ,YAC3B,KAAK;OAGK;IACV;IACA;IACA;;;;;;;;;;;OAYU;IACN;IAAkB;IAAiB;IAAiB;;IACpD;IAAyB;;IACzB;IAAwB;IAAiB;;IACzC;IAAiB;;IAEjB;IACA;;IAQA;IACA;IACA;;IAGA;IACA;;;;;;;OAQM;IACV;IACA;IACA;;OAGU,aAAa;IACvB;IACA,UAAU,qBAAqB,cAAc,QAAQ;IACrD,eAAe,WAAW;IAC1B,YAAY,YAAY;IACxB,wBAAwB,SAAS;;OAGvB,YAAY;IACtB;IACA;IACA,QACI,gCACO,yBAAyB,QAAQ;IAC5C,iBAAiB,aAAa,kCAAkC;IAChE,YAAY,YAAY;IACxB,wBAAwB,SAAS"}
1
+ {"version":3,"file":"statewire.d.ts","names":[],"sources":["../src/statewire.ts"],"mappings":";;;;cAYa,eACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD,cAEA,SAAS,gBAAgB,QAAQ,QAAQ,WAAW,YACnD,gBAAgB,QAAQ,QAAQ;cAqCtB,oBACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD,cAEA,SAAS,gBAAgB,QAAQ,QAAQ,WAAW,YACnD,gBAAgB,gBAAgB,QAAQ,QAAQ;cAGtC,gBACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD;mBAEiB;UACT;EAEI,YAAA,SAAS,gBAAgB,QAAQ,QAAQ,WAAW;MAK5D,SAAS;MAIT,cAAc,gBAAgB;MAI9B,YAAY,UAAU,cAAc;EAIxC,WAAW,OAAO,0BAA0B;EAI5C,UAAU;EAIV;;kBAOe;OACH,QACV,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD;IAEA,WAAW,gBAAgB,UAAU,QAAQ,WAAW;IACxD,aAAa,WAAW,YAAY,SAAS;IAC7C,WAAW,OAAO,QAAQ;MAAO,OAAO,YAAY;;IACpD,iBACE,OAAO,YAAY,SACnB;MAAO,SAAS,UAAU;;IAE5B,mBACE,QAAQ,UAAU,cAAc,YAChC;MAAO,OAAO,YAAY;;IAE5B,sBAAsB,YAAY;;OAGxB,QACV,QACA,kBAAkB,UAAU,WAAW,UAAU;;IAGjD,OAAO;;IAEP,gBAAgB;IAChB,YAAY;IACZ,UAAU,UAAU,cAAc;;IAElC,aAAa,OAAO,4BAA4B;;OAGtC;IACN;;IACA;;IAEA;IACA;IACA;IACA;IACA;IACA;;IAEA;IAAgB;IAAkB;;OAC5B,aAAa;IAAoB;;OACjC,mBAAmB;;;;;OAMnB,WACV,QACA,kBAAkB,UAAU,WAAW,UAAU,aAEjD,OAAO,QACP,SAAS,UAAU,QAAQ,YAC3B,KAAK;OAGK;IACV;IACA;IACA;;;;;;;;;;;OAYU;IACN;IAAkB;IAAiB;IAAiB;;IACpD;IAAyB;;IACzB;IAAwB;IAAiB;;IACzC;IAAiB;;IAEjB;IACA;;IAQA;IACA;IACA;;IAGA;IACA;;;OAIM,gBACR,gBACE;IAAO;QAAyB,cAAc,QAAQ;OAEhD,aAAa;IACvB;IACA,UAAU;IACV,eAAe,WAAW;IAC1B,YAAY,YAAY;;OAGd,YAAY;IACtB;IACA,UAAU;IACV;IACA,QACI,gCACO,yBAAyB,QAAQ;IAC5C,iBAAiB,aAAa,kCAAkC;IAChE,YAAY,YAAY"}