statewire 0.0.4 → 0.0.5

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.
package/README.md CHANGED
@@ -33,7 +33,9 @@ const onClick = async () => {
33
33
  - `"crashed"` — acked, but the processing died before producing a result
34
34
  - `"result-unavailable"` — acked, but the result can no longer be delivered
35
35
 
36
- Every failure also carries `cause` — which channel settled it: `"server"` = the server's answer, `"stream-gone"` = terminal `finish(gone)`, `"session-lost"` = the server forgot the client id (eviction), `"superseded"` = another attach took the writer lease, `"unmount"` / `"cascade"` are client-side. The taxonomy's single exported type is `StatewireClient.SendFailure`; the `fate` and `cause` unions are reachable as `SendFailure["fate"]` / `SendFailure["cause"]`.
36
+ Every failure also carries `cause` — which channel settled it: `"server"` = the server's answer, `"stream-gone"` = terminal `finish(gone)`, `"session-lost"` = the server forgot the client id (eviction), `"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
+
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.
37
39
 
38
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.
39
41
 
@@ -1,4 +1,5 @@
1
1
  import { StatewireSessionRecord, StatewireSessionStore, StatewireSessionStoreOptions } from "./sessionStore.js";
2
+ import { StatewireClient } from "../statewire.js";
2
3
  import { Statewire, StatewireSendError } from "../types.js";
3
4
  import { StatewirePath } from "../wire/ops.js";
4
5
  import { CommandsInvocation, CommandsListener } from "../host/host.js";
@@ -42,6 +43,9 @@ type Pending = {
42
43
  reject: (error: StatewireSendError) => void;
43
44
  };
44
45
  type AttachState = "detached" | "attached" | "finished";
46
+ type StatewireSessionOptions = StatewireSessionStoreOptions & {
47
+ onCommandSessionLost?: ((command: StatewireClient.SessionLostCommand) => "replay" | void) | undefined;
48
+ };
45
49
  type Session = {
46
50
  record: StatewireSessionRecord;
47
51
  ctx: StatewireRemoteProps;
@@ -61,6 +65,7 @@ type Session = {
61
65
  transport: RemoteLink | null;
62
66
  waiters: (() => void)[];
63
67
  lifecycle: CommandsListener | null;
68
+ onCommandSessionLost: StatewireSessionOptions["onCommandSessionLost"];
64
69
  notify: () => void;
65
70
  emitEvent: (event: unknown) => void;
66
71
  send: (command: StatewireWireCommand) => Promise<unknown>;
@@ -68,7 +73,7 @@ type Session = {
68
73
  };
69
74
  declare const setTransport: (s: Session, transport: RemoteLink) => void;
70
75
  declare const setContext: (s: Session, value: Record<string, unknown>) => Promise<unknown>;
71
- declare const useStatewireSession: (options: StatewireSessionStoreOptions, emit: (event: unknown) => void) => Session;
76
+ declare const useStatewireSession: (options: StatewireSessionOptions, emit: (event: unknown) => void) => Session;
72
77
  //#endregion
73
- export { setContext, setTransport, useStatewireSession };
78
+ export { StatewireSessionOptions, setContext, setTransport, useStatewireSession };
74
79
  //# sourceMappingURL=session.d.ts.map
@@ -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;KAEA;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;;cAsd/C,eAAgB,GAAG,SAAS,WAAW;cA+CvC,aACJ,GAAG,SACH,OAAO,4BACN;cAQU,sBACX,SAAS,8BACT,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;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"}
@@ -121,6 +121,7 @@ const createSession = (options) => {
121
121
  transport: null,
122
122
  waiters: [],
123
123
  lifecycle: null,
124
+ onCommandSessionLost: options.onCommandSessionLost,
124
125
  notify: () => {},
125
126
  emitEvent: () => {},
126
127
  send: (command) => sendCommand(s, command),
@@ -170,10 +171,17 @@ const settleAll = (s, detach, verdict) => {
170
171
  fate: "result-unavailable",
171
172
  cause
172
173
  };
173
- if (state.at === "queued") return {
174
- fate: "aborted",
175
- cause: batch.some((p) => p.seq < entry.seq && p.state.at !== "queued") ? "cascade" : cause
176
- };
174
+ if (state.at === "queued") {
175
+ const predecessorLost = batch.some((p) => p.seq < entry.seq && p.state.at !== "queued");
176
+ return predecessorLost ? {
177
+ fate: "aborted",
178
+ cause,
179
+ predecessorLost
180
+ } : {
181
+ fate: "aborted",
182
+ cause
183
+ };
184
+ }
177
185
  return {
178
186
  fate: "lost",
179
187
  cause
@@ -187,7 +195,24 @@ const settleAll = (s, detach, verdict) => {
187
195
  });
188
196
  }
189
197
  };
198
+ const claimReplays = (s) => {
199
+ const handler = s.onCommandSessionLost;
200
+ if (!handler) return [];
201
+ const claimed = /* @__PURE__ */ new Set();
202
+ for (const entry of s.pending) {
203
+ if (!entry.inv || isResolved(entry.state)) continue;
204
+ if (handler({
205
+ method: entry.inv.method,
206
+ params: [...entry.inv.params],
207
+ seq: entry.seq
208
+ }) === "replay") claimed.add(entry);
209
+ }
210
+ if (claimed.size === 0) return [];
211
+ s.pending = s.pending.filter((p) => !claimed.has(p));
212
+ return [...claimed];
213
+ };
190
214
  const evict = (s, verdict) => {
215
+ const replays = claimReplays(s);
191
216
  settleAll(s, "evicted", verdict);
192
217
  s.record = {
193
218
  clientId: newClientId(),
@@ -196,6 +221,10 @@ const evict = (s, verdict) => {
196
221
  s.store.save(s.record);
197
222
  s.attach = "detached";
198
223
  if (s.context !== void 0) s.send({ context: s.context });
224
+ for (const old of replays) {
225
+ if (old.inv) s.lifecycle?.onCovered?.(old.inv);
226
+ enqueue(s, old.command, old.resolve, old.reject);
227
+ }
199
228
  publish(s);
200
229
  wake(s);
201
230
  };
@@ -469,15 +498,7 @@ const setTransport = (s, transport) => {
469
498
  s.transport = transport;
470
499
  wake(s);
471
500
  };
472
- const sendCommand = (s, command) => {
473
- if (!s.running) return rejectedSend({
474
- fate: "aborted",
475
- cause: "unmount"
476
- });
477
- if (isFinished(s)) return rejectedSend({
478
- fate: "aborted",
479
- cause: "stream-gone"
480
- });
501
+ const enqueue = (s, command, resolve, reject) => {
481
502
  const seq = s.record.nextSeq;
482
503
  s.record = {
483
504
  ...s.record,
@@ -498,17 +519,26 @@ const sendCommand = (s, command) => {
498
519
  at: "queued",
499
520
  pendingMarked: false
500
521
  },
501
- resolve: () => {},
502
- reject: () => {}
522
+ resolve,
523
+ reject
503
524
  };
504
- const result = new Promise((resolve, reject) => {
505
- entry.resolve = resolve;
506
- entry.reject = reject;
507
- });
508
- result.catch(() => {});
509
525
  s.pending.push(entry);
510
526
  if (entry.inv) s.lifecycle?.onStart(entry.inv);
511
527
  pump(s);
528
+ };
529
+ const sendCommand = (s, command) => {
530
+ if (!s.running) return rejectedSend({
531
+ fate: "aborted",
532
+ cause: "unmount"
533
+ });
534
+ if (isFinished(s)) return rejectedSend({
535
+ fate: "aborted",
536
+ cause: "stream-gone"
537
+ });
538
+ const result = new Promise((resolve, reject) => {
539
+ enqueue(s, command, resolve, reject);
540
+ });
541
+ result.catch(() => {});
512
542
  return result;
513
543
  };
514
544
  const setContext = (s, value) => {
@@ -518,6 +548,7 @@ const setContext = (s, value) => {
518
548
  };
519
549
  const useStatewireSession = (options, emit) => {
520
550
  const [s] = useState(() => createSession(options));
551
+ s.onCommandSessionLost = options.onCommandSessionLost;
521
552
  useEffect(() => {
522
553
  s.emitEvent = emit;
523
554
  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","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","cascades","some","evict","save","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\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: StatewireSessionStoreOptions): 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 cascades = batch.some(\n (p) => p.seq < entry.seq && p.state.at !== \"queued\",\n );\n return { fate: \"aborted\", cause: cascades ? \"cascade\" : 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\nconst evict = (\n s: Session,\n verdict?: {\n entry: Pending;\n failure: StatewireClient.SendFailure;\n },\n) => {\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 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 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 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 const result = new Promise<unknown>((resolve, reject) => {\n entry.resolve = resolve;\n entry.reject = reject;\n });\n result.catch(() => {});\n s.pending.push(entry);\n if (entry.inv) s.lifecycle?.onStart(entry.inv);\n void pump(s);\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: StatewireSessionStoreOptions,\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":"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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","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 +1 @@
1
- {"version":3,"file":"host.d.ts","names":[],"sources":["../../src/host/host.ts"],"mappings":";;;;cAmBa,eACX,kBACC,cAAc;cAaJ,oBAAqB,QAChC,YAAY,qBACD,QAAQ,cAAc,SAAS;;;;;;;;cA4F/B,oBACV,UAAC,WACW;EACT;MACI,WAAW,YAAY,cAC5B,cAAc,UAAU;EA2BT,gBAAA,gBACd,cAAc,UAAU,IACxB,cAAc,mBAAmB;;KAkB3B;WACD;WACA;WACA;WACA;WACA;;KAGC;EACN;EAAkB;;EAClB;EAAkB;;KAEZ;EACV,UAAU,KAAK;EACf,YAAY,KAAK;EACjB,QAAQ,KAAK;;EAEb,aAAa,KAAK;EAClB,WAAW,KAAK,oBAAoB,SAAS;;KAGnC;EACV;EACA;EACA;EACA,SAAS,cAAc;EACvB;EACA,YAAY,SAAS;;KAGX;EACV,cAAc,UAAU;EACxB,UAAU,MAAM,uBAAuB;;cAK5B,kBAAmB,qBAAmB;cAGtC,mBAAoB,kBAAkB,MAAM;cA2J5C,uBAAwB,kBAAkB,UAAU,UAC/D,WACK,YAAY,SAAS,cAAc,wCAIvC,UAAU,cAAc,cAAc,gBAAgB;KAqF7C;EACV,UAAU,QAAQ;;cAKP,cAAe,6BAA2B;cAG1C,mBAAoB,eAAa,OAAO"}
1
+ {"version":3,"file":"host.d.ts","names":[],"sources":["../../src/host/host.ts"],"mappings":";;;;cAmBa,eACX,kBACC,cAAc;cAaJ,oBAAqB,QAChC,YAAY,qBACD,QAAQ,cAAc,SAAS;;;;;;;;cA4F/B,oBACV,UAAC,WACW;EACT;MACI,WAAW,YAAY,cAC5B,cAAc,UAAU;EA2BT,gBAAA,gBACd,cAAc,UAAU,IACxB,cAAc,mBAAmB;;KAkB3B;WACD;WACA;WACA;WACA;WACA;;KAGC;EACN;EAAkB;;EAClB;EAAkB;;KAEZ;EACV,UAAU,KAAK;EACf,YAAY,KAAK;EACjB,QAAQ,KAAK;;EAEb,aAAa,KAAK;EAClB,WAAW,KAAK,oBAAoB,SAAS;;KAGnC;EACV;EACA;EACA;EACA,SAAS,cAAc;EACvB;EACA,YAAY,SAAS;;KAGX;EACV,cAAc,UAAU;EACxB,UAAU,MAAM,uBAAuB;;cAK5B,kBAAmB,qBAAmB;cAGtC,mBAAoB,kBAAkB,MAAM;cA8J5C,uBAAwB,kBAAkB,UAAU,UAC/D,WACK,YAAY,SAAS,cAAc,wCAIvC,UAAU,cAAc,cAAc,gBAAgB;KAqF7C;EACV,UAAU,QAAQ;;cAKP,cAAe,6BAA2B;cAG1C,mBAAoB,eAAa,OAAO"}
package/dist/host/host.js CHANGED
@@ -134,6 +134,9 @@ const bindCommandsHost = (commands, host) => {
134
134
  };
135
135
  const STALL_WARN_MS = 5e3;
136
136
  const LOCAL_CALLER = Object.freeze({
137
+ get clientId() {
138
+ throw new Error("statewire: in-process dispatch has no client id");
139
+ },
137
140
  context: null,
138
141
  isConnected: true
139
142
  });
@@ -1 +1 @@
1
- {"version":3,"file":"host.js","names":["useEffect","useState","applyOps","sanitizeValue","StatewirePath","StatewireReject","createStateDraft","createMethodProxy","ServerOp","StateDraft","StatewireSendError","Statewire","StatewireHost","isDev","process","env","stateHosts","WeakMap","StateHost","getStateHost","state","get","StateCell","current","draft","changed","onOp","op","onChange","ctl","StateCtl","host","bump","useStatewireState","init","TState","setVersion","cell","commit","c","flush","v","apply","ops","result","events","length","Error","invalidate","push","changedPaths","snapshot","setOnOp","listener","setOnChange","takeChanged","value","record","changedPath","set","proxy","const","ExecutionSettled","type","error","ExecutionBind","onAck","onSettle","settled","ExecutionState","acked","bind","executionStates","CommandExecution","Object","assign","executor","resolvers","ack","T","PromiseLike","Execution","execution","freeze","settle","withResolvers","ExecutionResolvers","resolve","reject","promise","Promise","res","rej","a","CommandsInvocation","method","params","ts","seq","clientId","CommandsOutcome","payload","CommandsListener","onStart","inv","onPosted","onCovered","outcome","CommandsSubmission","caller","Caller","CommandsHost","setListener","submit","call","commandsHosts","getCommandsHost","commands","bindCommandsHost","QueuedCall","origin","ActiveInvocation","CommandsCell","handlers","Record","args","queue","running","unmounted","stallTimer","ReturnType","setTimeout","STALL_WARN_MS","LOCAL_CALLER","context","isConnected","toSendError","fate","cause","message","String","undefined","ackInvocation","settleInvocation","pump","watchStall","shift","handler","console","warn","useStatewireCommands","Commands","THandlers","ThisType","CommandsProxy","HandledCommands","built","Date","now","clearTimeout","queueMicrotask","queued","EmitHost","setSink","sink","event","emitHosts","getEmitHost","emit","useStatewireEmit","_temp"],"sources":["../../src/host/host.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { applyOps, sanitizeValue } from \"../wire/ops\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport { StatewireReject } from \"./engine\";\nimport { createStateDraft } from \"./draft\";\nimport { createMethodProxy } from \"../util/methodProxy\";\nimport type { ServerOp, StateDraft } from \"./draft\";\nimport { StatewireSendError } from \"../types\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireHost } from \"../host\";\n\nconst isDev = () =>\n typeof process === \"undefined\" || process.env[\"NODE_ENV\"] !== \"production\";\n\n// ─── state ──────────────────────────────────────────────\n\nconst stateHosts = new WeakMap<object, StatewireHost.StateHost>();\n\n/** Host-side accessor for a `useStatewireState` state object's source (`snapshot()` reads the committed state). */\nexport const getStateHost = (\n state: object,\n): StatewireHost.StateHost | undefined => stateHosts.get(state);\n\ntype StateCell = {\n current: unknown;\n draft: StateDraft<object> | null;\n changed: StatewirePath[];\n onOp: ((op: ServerOp) => void) | null;\n onChange: (() => void) | null;\n ctl: StatewireHost.StateCtl;\n host: StatewireHost.StateHost;\n bump: () => void;\n};\n\nexport const useStatewireState = <TState>(\n init: () => TState,\n): readonly [TState, StatewireHost.StateCtl<TState>] => {\n const [, setVersion] = useState(0);\n const [cell] = useState((): StateCell => {\n const commit = () => {\n if (c.draft) c.current = c.draft.flush();\n return c.current;\n };\n const c: StateCell = {\n current: sanitizeValue(init()),\n draft: null,\n changed: [[]],\n onOp: null,\n onChange: null,\n bump: () => setVersion((v) => v + 1),\n ctl: {\n apply: (ops) => {\n const result = applyOps(commit(), ops);\n if (result.events.length > 0) {\n throw new Error(\n \"statewire: event ops do not apply to state — route them to emit\",\n );\n }\n c.current = result.state;\n c.draft?.invalidate();\n c.changed.push(...result.changedPaths);\n if (c.onOp) for (const op of ops) c.onOp(op as ServerOp);\n c.bump();\n c.onChange?.();\n },\n snapshot: commit,\n },\n host: {\n snapshot: commit,\n setOnOp: (listener) => {\n c.onOp = listener;\n },\n setOnChange: (listener) => {\n c.onChange = listener;\n },\n takeChanged: () => {\n const changed = c.changed;\n c.changed = [];\n return changed;\n },\n },\n };\n return c;\n });\n\n let value = cell.ctl.snapshot();\n if (typeof value === \"object\" && value !== null) {\n cell.draft ??= createStateDraft<object>({\n snapshot: () => cell.current,\n record: (op, changedPath) => {\n cell.changed.push(changedPath);\n cell.onOp?.(op);\n cell.bump();\n cell.onChange?.();\n },\n });\n stateHosts.set(cell.draft.proxy, cell.host);\n value = cell.draft.proxy;\n }\n return [value as TState, cell.ctl as StatewireHost.StateCtl<TState>] as const;\n};\n\n// ─── commands ───────────────────────────────────────────\n\ntype ExecutionSettled =\n | { type: \"resolved\"; value: unknown }\n | { type: \"rejected\"; error: unknown };\n\ntype ExecutionBind = {\n onAck: () => void;\n onSettle: (settled: ExecutionSettled) => void;\n};\n\ntype ExecutionState = {\n acked: boolean;\n settled: ExecutionSettled | null;\n bind: ExecutionBind | null;\n};\n\nconst executionStates = new WeakMap<object, ExecutionState>();\n\n/**\n * `CommandExecution(executor)` runs the (possibly async) `executor({ ack })`\n * immediately: its return value resolves the execution, a throw/rejection\n * rejects it; `ack()` releases the client's ack watermark before the executor\n * finishes. `CommandExecution.withResolvers()` returns `[execution,\n * { ack, resolve, reject }]` instead — settle them from your own spawned work.\n */\nexport const CommandExecution = Object.assign(\n <T = void>(\n executor: (resolvers: {\n ack: () => void;\n }) => T | void | PromiseLike<T | void>,\n ): StatewireHost.Execution<T> => {\n const state: ExecutionState = { acked: false, settled: null, bind: null };\n const execution = Object.freeze(\n {},\n ) as unknown as StatewireHost.Execution<T>;\n executionStates.set(execution, state);\n const settle = (settled: ExecutionSettled) => {\n if (state.settled) return;\n state.settled = settled;\n state.acked = true;\n state.bind?.onSettle(settled);\n };\n const ack = () => {\n if (state.acked) return;\n state.acked = true;\n state.bind?.onAck();\n };\n void (async () => {\n try {\n settle({ type: \"resolved\", value: await executor({ ack }) });\n } catch (error) {\n settle({ type: \"rejected\", error });\n }\n })();\n return execution;\n },\n {\n withResolvers: <T = void>(): [\n StatewireHost.Execution<T>,\n StatewireHost.ExecutionResolvers<T>,\n ] => {\n let resolve!: (value: T) => void;\n let reject!: (error: unknown) => void;\n const promise = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n let ack!: () => void;\n const execution = CommandExecution<T>(({ ack: a }) => {\n ack = a;\n return promise;\n });\n return [execution, { ack: () => ack(), resolve, reject }];\n },\n },\n);\n\nexport type CommandsInvocation = {\n readonly method: string;\n readonly params: readonly unknown[];\n readonly ts: number;\n readonly seq: number;\n readonly clientId?: string;\n};\n\nexport type CommandsOutcome =\n | { type: \"accepted\"; payload?: unknown }\n | { type: \"rejected\"; error: unknown };\n\nexport type CommandsListener = {\n onStart: (inv: CommandsInvocation) => void;\n onPosted?: (inv: CommandsInvocation) => void;\n onAck: (inv: CommandsInvocation) => void;\n /** Canonical state now covers the invocation's effect — drop any overlay. */\n onCovered?: (inv: CommandsInvocation) => void;\n onSettle: (inv: CommandsInvocation, outcome: CommandsOutcome) => void;\n};\n\nexport type CommandsSubmission = {\n method: string;\n params: readonly unknown[];\n clientId?: string;\n caller?: StatewireHost.Caller;\n onAck?: () => void;\n onSettle?: (outcome: CommandsOutcome) => void;\n};\n\nexport type CommandsHost = {\n setListener: (listener: CommandsListener | null) => void;\n submit?: (call: CommandsSubmission) => Promise<unknown>;\n};\n\nconst commandsHosts = new WeakMap<object, CommandsHost>();\n\nexport const getCommandsHost = (commands: object): CommandsHost | undefined =>\n commandsHosts.get(commands);\n\nexport const bindCommandsHost = (commands: object, host: CommandsHost) => {\n commandsHosts.set(commands, host);\n};\n\ntype QueuedCall = {\n method: string;\n params: readonly unknown[];\n ts: number;\n resolve: (value: unknown) => void;\n reject: (error: unknown) => void;\n origin?: CommandsSubmission;\n};\n\ntype ActiveInvocation = CommandsInvocation & {\n acked: boolean;\n origin?: CommandsSubmission;\n};\n\ntype CommandsCell = {\n handlers: Record<string, (...args: never[]) => unknown> | null;\n queue: QueuedCall[];\n running: boolean;\n listener: CommandsListener | null;\n seq: number;\n unmounted: boolean;\n stallTimer: ReturnType<typeof setTimeout> | null;\n};\n\nconst STALL_WARN_MS = 5000;\n\n// In-process dispatch has no wire client: never any ambient context, trivially attached.\nconst LOCAL_CALLER: StatewireHost.Caller = Object.freeze({\n context: null,\n isConnected: true,\n});\n\nconst toSendError = (error: unknown): StatewireSendError =>\n error instanceof StatewireSendError\n ? error\n : new StatewireSendError({\n fate: \"rejected\",\n cause: \"server\",\n message: error instanceof Error ? error.message : String(error),\n ...(error instanceof StatewireReject &&\n error.payload !== undefined && { payload: error.payload }),\n });\n\nconst ackInvocation = (cell: CommandsCell, inv: ActiveInvocation) => {\n if (inv.acked) return;\n inv.acked = true;\n cell.listener?.onAck(inv);\n inv.origin?.onAck?.();\n};\n\nconst settleInvocation = (\n cell: CommandsCell,\n inv: ActiveInvocation,\n call: QueuedCall,\n settled: ExecutionSettled,\n) => {\n ackInvocation(cell, inv);\n if (settled.type === \"resolved\") {\n const outcome: CommandsOutcome =\n settled.value === undefined\n ? { type: \"accepted\" }\n : { type: \"accepted\", payload: sanitizeValue(settled.value) };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.resolve(outcome.payload);\n } else {\n const error = toSendError(settled.error);\n const outcome: CommandsOutcome = { type: \"rejected\", error };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.reject(error);\n }\n};\n\nconst pump = (cell: CommandsCell) => {\n if (cell.running || !cell.handlers) {\n watchStall(cell);\n return;\n }\n const call = cell.queue.shift();\n if (!call) return;\n const handler = cell.handlers[call.method];\n if (handler === undefined) {\n const error = new StatewireSendError({\n fate: \"unknown-command\",\n cause: \"server\",\n });\n call.origin?.onSettle?.({ type: \"rejected\", error });\n call.reject(error);\n pump(cell);\n return;\n }\n const inv: ActiveInvocation = {\n method: call.method,\n params: call.params,\n ts: call.ts,\n seq: ++cell.seq,\n acked: false,\n ...(call.origin !== undefined && { origin: call.origin }),\n ...(call.origin?.clientId !== undefined && {\n clientId: call.origin.clientId,\n }),\n };\n cell.running = true;\n cell.listener?.onStart(inv);\n cell.listener?.onPosted?.(inv);\n cell.listener?.onCovered?.(inv);\n void (async () => {\n try {\n const value = await (\n handler as (...args: readonly unknown[]) => unknown\n ).call(call.origin?.caller ?? LOCAL_CALLER, ...call.params);\n const execution =\n typeof value === \"object\" && value !== null\n ? executionStates.get(value)\n : undefined;\n if (execution) {\n if (execution.acked) ackInvocation(cell, inv);\n if (execution.settled) {\n settleInvocation(cell, inv, call, execution.settled);\n } else {\n execution.bind = {\n onAck: () => ackInvocation(cell, inv),\n onSettle: (settled) => settleInvocation(cell, inv, call, settled),\n };\n }\n } else {\n settleInvocation(cell, inv, call, { type: \"resolved\", value });\n }\n } catch (error) {\n settleInvocation(cell, inv, call, { type: \"rejected\", error });\n } finally {\n cell.running = false;\n pump(cell);\n }\n })();\n};\n\nconst watchStall = (cell: CommandsCell) => {\n if (!isDev() || cell.stallTimer !== null || cell.queue.length === 0) return;\n if (cell.handlers) return;\n cell.stallTimer = setTimeout(() => {\n cell.stallTimer = null;\n if (cell.handlers === null && cell.queue.length > 0 && !cell.unmounted) {\n console.warn(\n `statewire: ${cell.queue.length} buffered command(s) waiting over ${STALL_WARN_MS}ms — did the host forget to arm useStatewireCommands?`,\n );\n }\n }, STALL_WARN_MS);\n};\n\nexport const useStatewireCommands = <THandlers extends Statewire.Commands>(\n commands:\n | (THandlers & ThisType<StatewireHost.Caller>)\n | undefined\n | null\n | false,\n): Statewire.CommandsProxy<StatewireHost.HandledCommands<THandlers>> => {\n const [built] = useState(() => {\n const cell: CommandsCell = {\n handlers: null,\n queue: [],\n running: false,\n listener: null,\n seq: 0,\n unmounted: false,\n stallTimer: null,\n };\n const proxy = createMethodProxy<\n Statewire.CommandsProxy<StatewireHost.HandledCommands<THandlers>>\n >(\n (method, params) =>\n new Promise((resolve, reject) => {\n if (cell.unmounted) {\n reject(new Error(\"statewire: commands called after unmount\"));\n return;\n }\n cell.queue.push({\n method,\n params,\n ts: Date.now(),\n resolve,\n reject,\n });\n pump(cell);\n }),\n );\n commandsHosts.set(proxy, {\n setListener: (listener) => {\n cell.listener = listener;\n },\n submit: (origin) =>\n new Promise((resolve, reject) => {\n if (cell.unmounted) {\n reject(new Error(\"statewire: commands called after unmount\"));\n return;\n }\n cell.queue.push({\n method: origin.method,\n params: origin.params,\n ts: Date.now(),\n resolve,\n reject,\n origin,\n });\n pump(cell);\n }),\n });\n return { cell, proxy };\n });\n\n useEffect(() => {\n const { cell } = built;\n cell.unmounted = false;\n return () => {\n cell.unmounted = true;\n cell.handlers = null;\n if (cell.stallTimer !== null) {\n clearTimeout(cell.stallTimer);\n cell.stallTimer = null;\n }\n queueMicrotask(() => {\n if (!cell.unmounted) return;\n const queued = cell.queue;\n cell.queue = [];\n for (const call of queued) {\n call.reject(new Error(\"statewire: unmounted\"));\n }\n });\n };\n }, [built]);\n\n useEffect(() => {\n built.cell.handlers = commands || null;\n pump(built.cell);\n });\n\n return built.proxy;\n};\n\n// ─── emit ───────────────────────────────────────────────\n\nexport type EmitHost = {\n setSink: (sink: ((event: unknown) => void) | null) => void;\n};\n\nconst emitHosts = new WeakMap<object, EmitHost>();\n\nexport const getEmitHost = (emit: object | undefined): EmitHost | undefined =>\n emit && emitHosts.get(emit);\n\nexport const useStatewireEmit = <TEvent>(): ((event: TEvent) => void) => {\n const [built] = useState(() => {\n const cell = { sink: null as ((event: unknown) => void) | null };\n const emit = (event: TEvent) => {\n if (!cell.sink) {\n throw new Error(\"statewire: emit before a host bound the event sink\");\n }\n cell.sink(sanitizeValue(event));\n };\n emitHosts.set(emit, {\n setSink: (sink) => {\n cell.sink = sink;\n },\n });\n return emit;\n });\n return built;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"host.js","names":["useEffect","useState","applyOps","sanitizeValue","StatewirePath","StatewireReject","createStateDraft","createMethodProxy","ServerOp","StateDraft","StatewireSendError","Statewire","StatewireHost","isDev","process","env","stateHosts","WeakMap","StateHost","getStateHost","state","get","StateCell","current","draft","changed","onOp","op","onChange","ctl","StateCtl","host","bump","useStatewireState","init","TState","setVersion","cell","commit","c","flush","v","apply","ops","result","events","length","Error","invalidate","push","changedPaths","snapshot","setOnOp","listener","setOnChange","takeChanged","value","record","changedPath","set","proxy","const","ExecutionSettled","type","error","ExecutionBind","onAck","onSettle","settled","ExecutionState","acked","bind","executionStates","CommandExecution","Object","assign","executor","resolvers","ack","T","PromiseLike","Execution","execution","freeze","settle","withResolvers","ExecutionResolvers","resolve","reject","promise","Promise","res","rej","a","CommandsInvocation","method","params","ts","seq","clientId","CommandsOutcome","payload","CommandsListener","onStart","inv","onPosted","onCovered","outcome","CommandsSubmission","caller","Caller","CommandsHost","setListener","submit","call","commandsHosts","getCommandsHost","commands","bindCommandsHost","QueuedCall","origin","ActiveInvocation","CommandsCell","handlers","Record","args","queue","running","unmounted","stallTimer","ReturnType","setTimeout","STALL_WARN_MS","LOCAL_CALLER","context","isConnected","toSendError","fate","cause","message","String","undefined","ackInvocation","settleInvocation","pump","watchStall","shift","handler","console","warn","useStatewireCommands","Commands","THandlers","ThisType","CommandsProxy","HandledCommands","built","Date","now","clearTimeout","queueMicrotask","queued","EmitHost","setSink","sink","event","emitHosts","getEmitHost","emit","useStatewireEmit","_temp"],"sources":["../../src/host/host.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { applyOps, sanitizeValue } from \"../wire/ops\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport { StatewireReject } from \"./engine\";\nimport { createStateDraft } from \"./draft\";\nimport { createMethodProxy } from \"../util/methodProxy\";\nimport type { ServerOp, StateDraft } from \"./draft\";\nimport { StatewireSendError } from \"../types\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireHost } from \"../host\";\n\nconst isDev = () =>\n typeof process === \"undefined\" || process.env[\"NODE_ENV\"] !== \"production\";\n\n// ─── state ──────────────────────────────────────────────\n\nconst stateHosts = new WeakMap<object, StatewireHost.StateHost>();\n\n/** Host-side accessor for a `useStatewireState` state object's source (`snapshot()` reads the committed state). */\nexport const getStateHost = (\n state: object,\n): StatewireHost.StateHost | undefined => stateHosts.get(state);\n\ntype StateCell = {\n current: unknown;\n draft: StateDraft<object> | null;\n changed: StatewirePath[];\n onOp: ((op: ServerOp) => void) | null;\n onChange: (() => void) | null;\n ctl: StatewireHost.StateCtl;\n host: StatewireHost.StateHost;\n bump: () => void;\n};\n\nexport const useStatewireState = <TState>(\n init: () => TState,\n): readonly [TState, StatewireHost.StateCtl<TState>] => {\n const [, setVersion] = useState(0);\n const [cell] = useState((): StateCell => {\n const commit = () => {\n if (c.draft) c.current = c.draft.flush();\n return c.current;\n };\n const c: StateCell = {\n current: sanitizeValue(init()),\n draft: null,\n changed: [[]],\n onOp: null,\n onChange: null,\n bump: () => setVersion((v) => v + 1),\n ctl: {\n apply: (ops) => {\n const result = applyOps(commit(), ops);\n if (result.events.length > 0) {\n throw new Error(\n \"statewire: event ops do not apply to state — route them to emit\",\n );\n }\n c.current = result.state;\n c.draft?.invalidate();\n c.changed.push(...result.changedPaths);\n if (c.onOp) for (const op of ops) c.onOp(op as ServerOp);\n c.bump();\n c.onChange?.();\n },\n snapshot: commit,\n },\n host: {\n snapshot: commit,\n setOnOp: (listener) => {\n c.onOp = listener;\n },\n setOnChange: (listener) => {\n c.onChange = listener;\n },\n takeChanged: () => {\n const changed = c.changed;\n c.changed = [];\n return changed;\n },\n },\n };\n return c;\n });\n\n let value = cell.ctl.snapshot();\n if (typeof value === \"object\" && value !== null) {\n cell.draft ??= createStateDraft<object>({\n snapshot: () => cell.current,\n record: (op, changedPath) => {\n cell.changed.push(changedPath);\n cell.onOp?.(op);\n cell.bump();\n cell.onChange?.();\n },\n });\n stateHosts.set(cell.draft.proxy, cell.host);\n value = cell.draft.proxy;\n }\n return [value as TState, cell.ctl as StatewireHost.StateCtl<TState>] as const;\n};\n\n// ─── commands ───────────────────────────────────────────\n\ntype ExecutionSettled =\n | { type: \"resolved\"; value: unknown }\n | { type: \"rejected\"; error: unknown };\n\ntype ExecutionBind = {\n onAck: () => void;\n onSettle: (settled: ExecutionSettled) => void;\n};\n\ntype ExecutionState = {\n acked: boolean;\n settled: ExecutionSettled | null;\n bind: ExecutionBind | null;\n};\n\nconst executionStates = new WeakMap<object, ExecutionState>();\n\n/**\n * `CommandExecution(executor)` runs the (possibly async) `executor({ ack })`\n * immediately: its return value resolves the execution, a throw/rejection\n * rejects it; `ack()` releases the client's ack watermark before the executor\n * finishes. `CommandExecution.withResolvers()` returns `[execution,\n * { ack, resolve, reject }]` instead — settle them from your own spawned work.\n */\nexport const CommandExecution = Object.assign(\n <T = void>(\n executor: (resolvers: {\n ack: () => void;\n }) => T | void | PromiseLike<T | void>,\n ): StatewireHost.Execution<T> => {\n const state: ExecutionState = { acked: false, settled: null, bind: null };\n const execution = Object.freeze(\n {},\n ) as unknown as StatewireHost.Execution<T>;\n executionStates.set(execution, state);\n const settle = (settled: ExecutionSettled) => {\n if (state.settled) return;\n state.settled = settled;\n state.acked = true;\n state.bind?.onSettle(settled);\n };\n const ack = () => {\n if (state.acked) return;\n state.acked = true;\n state.bind?.onAck();\n };\n void (async () => {\n try {\n settle({ type: \"resolved\", value: await executor({ ack }) });\n } catch (error) {\n settle({ type: \"rejected\", error });\n }\n })();\n return execution;\n },\n {\n withResolvers: <T = void>(): [\n StatewireHost.Execution<T>,\n StatewireHost.ExecutionResolvers<T>,\n ] => {\n let resolve!: (value: T) => void;\n let reject!: (error: unknown) => void;\n const promise = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n let ack!: () => void;\n const execution = CommandExecution<T>(({ ack: a }) => {\n ack = a;\n return promise;\n });\n return [execution, { ack: () => ack(), resolve, reject }];\n },\n },\n);\n\nexport type CommandsInvocation = {\n readonly method: string;\n readonly params: readonly unknown[];\n readonly ts: number;\n readonly seq: number;\n readonly clientId?: string;\n};\n\nexport type CommandsOutcome =\n | { type: \"accepted\"; payload?: unknown }\n | { type: \"rejected\"; error: unknown };\n\nexport type CommandsListener = {\n onStart: (inv: CommandsInvocation) => void;\n onPosted?: (inv: CommandsInvocation) => void;\n onAck: (inv: CommandsInvocation) => void;\n /** Canonical state now covers the invocation's effect — drop any overlay. */\n onCovered?: (inv: CommandsInvocation) => void;\n onSettle: (inv: CommandsInvocation, outcome: CommandsOutcome) => void;\n};\n\nexport type CommandsSubmission = {\n method: string;\n params: readonly unknown[];\n clientId?: string;\n caller?: StatewireHost.Caller;\n onAck?: () => void;\n onSettle?: (outcome: CommandsOutcome) => void;\n};\n\nexport type CommandsHost = {\n setListener: (listener: CommandsListener | null) => void;\n submit?: (call: CommandsSubmission) => Promise<unknown>;\n};\n\nconst commandsHosts = new WeakMap<object, CommandsHost>();\n\nexport const getCommandsHost = (commands: object): CommandsHost | undefined =>\n commandsHosts.get(commands);\n\nexport const bindCommandsHost = (commands: object, host: CommandsHost) => {\n commandsHosts.set(commands, host);\n};\n\ntype QueuedCall = {\n method: string;\n params: readonly unknown[];\n ts: number;\n resolve: (value: unknown) => void;\n reject: (error: unknown) => void;\n origin?: CommandsSubmission;\n};\n\ntype ActiveInvocation = CommandsInvocation & {\n acked: boolean;\n origin?: CommandsSubmission;\n};\n\ntype CommandsCell = {\n handlers: Record<string, (...args: never[]) => unknown> | null;\n queue: QueuedCall[];\n running: boolean;\n listener: CommandsListener | null;\n seq: number;\n unmounted: boolean;\n stallTimer: ReturnType<typeof setTimeout> | null;\n};\n\nconst STALL_WARN_MS = 5000;\n\n// In-process dispatch has no wire client: never any ambient context, trivially attached.\nconst LOCAL_CALLER: StatewireHost.Caller = Object.freeze({\n get clientId(): string {\n throw new Error(\"statewire: in-process dispatch has no client id\");\n },\n context: null,\n isConnected: true,\n});\n\nconst toSendError = (error: unknown): StatewireSendError =>\n error instanceof StatewireSendError\n ? error\n : new StatewireSendError({\n fate: \"rejected\",\n cause: \"server\",\n message: error instanceof Error ? error.message : String(error),\n ...(error instanceof StatewireReject &&\n error.payload !== undefined && { payload: error.payload }),\n });\n\nconst ackInvocation = (cell: CommandsCell, inv: ActiveInvocation) => {\n if (inv.acked) return;\n inv.acked = true;\n cell.listener?.onAck(inv);\n inv.origin?.onAck?.();\n};\n\nconst settleInvocation = (\n cell: CommandsCell,\n inv: ActiveInvocation,\n call: QueuedCall,\n settled: ExecutionSettled,\n) => {\n ackInvocation(cell, inv);\n if (settled.type === \"resolved\") {\n const outcome: CommandsOutcome =\n settled.value === undefined\n ? { type: \"accepted\" }\n : { type: \"accepted\", payload: sanitizeValue(settled.value) };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.resolve(outcome.payload);\n } else {\n const error = toSendError(settled.error);\n const outcome: CommandsOutcome = { type: \"rejected\", error };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.reject(error);\n }\n};\n\nconst pump = (cell: CommandsCell) => {\n if (cell.running || !cell.handlers) {\n watchStall(cell);\n return;\n }\n const call = cell.queue.shift();\n if (!call) return;\n const handler = cell.handlers[call.method];\n if (handler === undefined) {\n const error = new StatewireSendError({\n fate: \"unknown-command\",\n cause: \"server\",\n });\n call.origin?.onSettle?.({ type: \"rejected\", error });\n call.reject(error);\n pump(cell);\n return;\n }\n const inv: ActiveInvocation = {\n method: call.method,\n params: call.params,\n ts: call.ts,\n seq: ++cell.seq,\n acked: false,\n ...(call.origin !== undefined && { origin: call.origin }),\n ...(call.origin?.clientId !== undefined && {\n clientId: call.origin.clientId,\n }),\n };\n cell.running = true;\n cell.listener?.onStart(inv);\n cell.listener?.onPosted?.(inv);\n cell.listener?.onCovered?.(inv);\n void (async () => {\n try {\n const value = await (\n handler as (...args: readonly unknown[]) => unknown\n ).call(call.origin?.caller ?? LOCAL_CALLER, ...call.params);\n const execution =\n typeof value === \"object\" && value !== null\n ? executionStates.get(value)\n : undefined;\n if (execution) {\n if (execution.acked) ackInvocation(cell, inv);\n if (execution.settled) {\n settleInvocation(cell, inv, call, execution.settled);\n } else {\n execution.bind = {\n onAck: () => ackInvocation(cell, inv),\n onSettle: (settled) => settleInvocation(cell, inv, call, settled),\n };\n }\n } else {\n settleInvocation(cell, inv, call, { type: \"resolved\", value });\n }\n } catch (error) {\n settleInvocation(cell, inv, call, { type: \"rejected\", error });\n } finally {\n cell.running = false;\n pump(cell);\n }\n })();\n};\n\nconst watchStall = (cell: CommandsCell) => {\n if (!isDev() || cell.stallTimer !== null || cell.queue.length === 0) return;\n if (cell.handlers) return;\n cell.stallTimer = setTimeout(() => {\n cell.stallTimer = null;\n if (cell.handlers === null && cell.queue.length > 0 && !cell.unmounted) {\n console.warn(\n `statewire: ${cell.queue.length} buffered command(s) waiting over ${STALL_WARN_MS}ms — did the host forget to arm useStatewireCommands?`,\n );\n }\n }, STALL_WARN_MS);\n};\n\nexport const useStatewireCommands = <THandlers extends Statewire.Commands>(\n commands:\n | (THandlers & ThisType<StatewireHost.Caller>)\n | undefined\n | null\n | false,\n): Statewire.CommandsProxy<StatewireHost.HandledCommands<THandlers>> => {\n const [built] = useState(() => {\n const cell: CommandsCell = {\n handlers: null,\n queue: [],\n running: false,\n listener: null,\n seq: 0,\n unmounted: false,\n stallTimer: null,\n };\n const proxy = createMethodProxy<\n Statewire.CommandsProxy<StatewireHost.HandledCommands<THandlers>>\n >(\n (method, params) =>\n new Promise((resolve, reject) => {\n if (cell.unmounted) {\n reject(new Error(\"statewire: commands called after unmount\"));\n return;\n }\n cell.queue.push({\n method,\n params,\n ts: Date.now(),\n resolve,\n reject,\n });\n pump(cell);\n }),\n );\n commandsHosts.set(proxy, {\n setListener: (listener) => {\n cell.listener = listener;\n },\n submit: (origin) =>\n new Promise((resolve, reject) => {\n if (cell.unmounted) {\n reject(new Error(\"statewire: commands called after unmount\"));\n return;\n }\n cell.queue.push({\n method: origin.method,\n params: origin.params,\n ts: Date.now(),\n resolve,\n reject,\n origin,\n });\n pump(cell);\n }),\n });\n return { cell, proxy };\n });\n\n useEffect(() => {\n const { cell } = built;\n cell.unmounted = false;\n return () => {\n cell.unmounted = true;\n cell.handlers = null;\n if (cell.stallTimer !== null) {\n clearTimeout(cell.stallTimer);\n cell.stallTimer = null;\n }\n queueMicrotask(() => {\n if (!cell.unmounted) return;\n const queued = cell.queue;\n cell.queue = [];\n for (const call of queued) {\n call.reject(new Error(\"statewire: unmounted\"));\n }\n });\n };\n }, [built]);\n\n useEffect(() => {\n built.cell.handlers = commands || null;\n pump(built.cell);\n });\n\n return built.proxy;\n};\n\n// ─── emit ───────────────────────────────────────────────\n\nexport type EmitHost = {\n setSink: (sink: ((event: unknown) => void) | null) => void;\n};\n\nconst emitHosts = new WeakMap<object, EmitHost>();\n\nexport const getEmitHost = (emit: object | undefined): EmitHost | undefined =>\n emit && emitHosts.get(emit);\n\nexport const useStatewireEmit = <TEvent>(): ((event: TEvent) => void) => {\n const [built] = useState(() => {\n const cell = { sink: null as ((event: unknown) => void) | null };\n const emit = (event: TEvent) => {\n if (!cell.sink) {\n throw new Error(\"statewire: emit before a host bound the event sink\");\n }\n cell.sink(sanitizeValue(event));\n };\n emitHosts.set(emit, {\n setSink: (sink) => {\n cell.sink = sink;\n },\n });\n return emit;\n });\n return built;\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"socket.d.ts","names":[],"sources":["../../src/host/socket.ts"],"mappings":";;;;;;;;;;cAulBa,sBACX,WAAW,gBAAgB,YAC3B,UAAU,sBAAsB,sBAC/B,sBAAsB"}
1
+ {"version":3,"file":"socket.d.ts","names":[],"sources":["../../src/host/socket.ts"],"mappings":";;;;;;;;;;cAwlBa,sBACX,WAAW,gBAAgB,YAC3B,UAAU,sBAAsB,sBAC/B,sBAAsB"}
@@ -262,6 +262,7 @@ const createSocketHost = (options) => {
262
262
  };
263
263
  const settleWire = (rec, adm, outcome) => settleResponse(rec, adm, responseOf(adm, outcome));
264
264
  const callerOf = (rec) => ({
265
+ clientId: rec.id,
265
266
  get context() {
266
267
  return rec.context;
267
268
  },
@@ -1 +1 @@
1
- {"version":3,"file":"socket.js","names":["useEffect","useState","createTapRoot","flushTapSync","useResource","ResourceElement","getCommandsHost","getEmitHost","getStateHost","CommandsHost","CommandsOutcome","StatewireSendError","Statewire","StatewireHost","StatewireHostInternal","encodeValue","reviveValue","createMacrotaskPoster","randomId","StatewireMetaEntry","StatewirePath","StatewireEnvelope","StatewireResponse","CLIENT_ID_RE","RESULT_TTL_MS","RESULT_LIMIT","WireOp","op","path","value","meta","Admission","seq","method","acked","pendingSent","settled","release","ClientRec","id","lastSeq","context","Record","results","Map","ts","res","inflight","Conn","clientId","socket","Socket","open","lastSentAt","Frame","params","parseFrame","raw","frame","JSON","parse","Array","isArray","undefined","Number","isInteger","contextValue","length","Error","encodePayload","payload","responseOf","adm","outcome","type","failure","error","fate","code","message","String","createSocketHost","options","SocketHostOptions","heartbeatMs","heartbeatIntervalMs","clients","conns","Set","pendingOps","dirtyAcks","pendingRes","inbox","Promise","resolve","finished","Fin","disposed","flushScheduled","heartbeatTimer","ReturnType","setInterval","stateHost","StateHost","commandsHost","emitHost","current","post","flush","scheduleFlush","recordOp","push","attach","next","nextState","state","nextCommands","commands","nextEmit","emit","submit","setOnOp","setSink","event","closeConn","conn","reason","delete","close","size","clearInterval","sendTo","data","Date","now","send","ensureHeartbeat","unref","fin","takeChanged","rec","values","queueRes","ops","acks","envelope","clientRes","get","has","ack","Object","keys","stringify","response","list","set","advance","add","retain","newest","Math","max","entry","min","duplicateAnswer","cached","settleResponse","settleWire","callerOf","Caller","isConnected","admit","released","caller","onAck","onSettle","catch","then","connect","SocketConnection","dead","refuse","test","known","created","other","snapshot","syn","lease","finish","resume","suspend","useSocketHost","transport","$","_c","t0","h","t1","t2","t3","StatewireSocketHost","SocketHost","SocketHostRoot","root","getValue","dispose","unmount"],"sources":["../../src/host/socket.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { createTapRoot, flushTapSync, useResource } from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { getCommandsHost, getEmitHost, getStateHost } from \"./host\";\nimport type { CommandsHost, CommandsOutcome } from \"./host\";\nimport { StatewireSendError } from \"../types\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireHost } from \"../host\";\nimport type { StatewireHostInternal } from \"../host-internal\";\nimport { encodeValue, reviveValue } from \"../wire/meta\";\nimport { createMacrotaskPoster } from \"../util/macrotask\";\nimport { randomId } from \"../util/randomId\";\nimport type { StatewireMetaEntry } from \"../wire/meta\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport type {\n StatewireEnvelope,\n StatewireResponse,\n} from \"../client/connection\";\n\nconst CLIENT_ID_RE = /^[A-Za-z0-9._~-]{1,256}$/;\nconst RESULT_TTL_MS = 30_000;\nconst RESULT_LIMIT = 256;\n\ntype WireOp = {\n op: string;\n path?: StatewirePath;\n value?: unknown;\n meta?: StatewireMetaEntry[];\n};\n\ntype Admission = {\n seq: number;\n method: string;\n acked: boolean;\n pendingSent: boolean;\n settled: boolean;\n release: () => void;\n};\n\ntype ClientRec = {\n id: string;\n lastSeq: number;\n context: Record<string, unknown> | null;\n results: Map<number, { ts: number; res: StatewireResponse }>;\n inflight: Map<number, Admission>;\n};\n\ntype Conn = {\n clientId: string;\n socket: StatewireHostInternal.Socket;\n open: boolean;\n lastSentAt: number;\n};\n\ntype Frame = {\n method?: string;\n seq: number;\n params: unknown[];\n context?: unknown;\n};\n\nconst parseFrame = (raw: string): Frame | null => {\n let frame: unknown;\n try {\n frame = JSON.parse(raw);\n } catch {\n return null;\n }\n if (typeof frame !== \"object\" || frame === null || Array.isArray(frame)) {\n return null;\n }\n const { method, seq, params, meta, context } = frame as Record<\n string,\n unknown\n >;\n if (method === undefined && context === undefined) return null;\n if (typeof seq !== \"number\" || !Number.isInteger(seq) || seq < 1) return null;\n if (method === undefined) {\n return { seq, params: [], ...(context !== undefined && { context }) };\n }\n if (typeof method !== \"string\") return null;\n if (!Array.isArray(params)) return null;\n if (meta !== undefined) {\n try {\n reviveValue(params, meta);\n } catch {\n return null;\n }\n }\n return { method, seq, params, ...(context !== undefined && { context }) };\n};\n\n/** v1 context submissions are exactly one root replace with an object value. */\nconst contextValue = (context: unknown): Record<string, unknown> => {\n if (!Array.isArray(context) || context.length !== 1) {\n throw new Error(\"context must be a single-op array\");\n }\n const op = context[0] as Record<string, unknown> | null;\n if (\n typeof op !== \"object\" ||\n op === null ||\n op[\"op\"] !== \"replace\" ||\n !Array.isArray(op[\"path\"]) ||\n op[\"path\"].length !== 0\n ) {\n throw new Error(\"context op must be a root replace\");\n }\n const value = op[\"value\"];\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new Error(\"context value must be an object\");\n }\n return value as Record<string, unknown>;\n};\n\nconst encodePayload = (\n payload: unknown,\n): { payload?: unknown; meta?: StatewireMetaEntry[] } => {\n if (payload === undefined) return {};\n const { value, meta } = encodeValue(payload);\n return { payload: value, ...(meta !== undefined && { meta }) };\n};\n\nconst responseOf = (\n adm: Admission,\n outcome: CommandsOutcome,\n): StatewireResponse => {\n if (outcome.type === \"accepted\") {\n return {\n seq: adm.seq,\n type: \"accepted\",\n ...encodePayload(outcome.payload),\n };\n }\n const failure =\n outcome.error instanceof StatewireSendError ? outcome.error.failure : null;\n switch (failure?.fate) {\n case \"unknown-command\":\n return {\n seq: adm.seq,\n type: \"rejected\",\n code: \"unknown-command\",\n message: `unknown method: '${adm.method}'`,\n };\n case \"invalid-params\":\n return {\n seq: adm.seq,\n type: \"rejected\",\n code: \"invalid-params\",\n message: failure.message,\n };\n case \"crashed\":\n return { seq: adm.seq, type: \"crashed\" };\n case \"rejected\":\n return {\n seq: adm.seq,\n type: \"rejected\",\n message: failure.message,\n ...encodePayload(failure.payload),\n };\n default:\n return {\n seq: adm.seq,\n type: \"rejected\",\n message:\n outcome.error instanceof Error\n ? outcome.error.message\n : String(outcome.error),\n };\n }\n};\n\nconst createSocketHost = (\n options?: StatewireHostInternal.SocketHostOptions,\n) => {\n const heartbeatMs = options?.heartbeatIntervalMs ?? 15_000;\n\n const clients = new Map<string, ClientRec>();\n const conns = new Set<Conn>();\n let pendingOps: WireOp[] = [];\n let dirtyAcks = new Set<string>();\n let pendingRes = new Map<string, StatewireResponse[]>();\n let inbox: Promise<void> = Promise.resolve();\n let finished: StatewireHost.Fin | null = null;\n let disposed = false;\n let flushScheduled = false;\n let heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n\n let stateHost: StatewireHost.StateHost | null = null;\n let commandsHost: CommandsHost | null = null;\n let emitHost: ReturnType<typeof getEmitHost> | null = null;\n let current: Statewire<unknown> | null = null;\n\n const post = createMacrotaskPoster(() => {\n flushScheduled = false;\n flush();\n });\n const scheduleFlush = () => {\n if (flushScheduled || disposed) return;\n flushScheduled = true;\n post();\n };\n\n const recordOp = (op: {\n op: string;\n path?: StatewirePath;\n value?: unknown;\n }) => {\n if (op.op === \"replace\" || op.op === \"add\" || op.op === \"event\") {\n const { value, meta } = encodeValue(op.value);\n pendingOps.push({ ...op, value, ...(meta !== undefined && { meta }) });\n } else {\n pendingOps.push(op);\n }\n scheduleFlush();\n };\n\n const attach = (next: Statewire<unknown>) => {\n if (next === current) return;\n current = next;\n const nextState =\n typeof next.state === \"object\" && next.state !== null\n ? getStateHost(next.state)\n : undefined;\n const nextCommands = getCommandsHost(next.commands);\n const nextEmit = getEmitHost(next.emit);\n if (!nextState || !nextCommands?.submit || !nextEmit) {\n throw new Error(\n \"StatewireSocketHost requires a statewire/host transport (useStatewireState with object state, useStatewireCommands, useStatewireEmit)\",\n );\n }\n if (nextState !== stateHost) {\n stateHost?.setOnOp(null);\n stateHost = nextState;\n stateHost.setOnOp(recordOp);\n }\n commandsHost = nextCommands;\n if (nextEmit !== emitHost) {\n emitHost?.setSink(null);\n emitHost = nextEmit;\n emitHost.setSink((event) => recordOp({ op: \"event\", value: event }));\n }\n };\n\n const closeConn = (conn: Conn, code?: number, reason?: string) => {\n if (!conn.open) return;\n conn.open = false;\n conns.delete(conn);\n try {\n conn.socket.close(code, reason);\n } catch {\n /* peer already gone */\n }\n if (conns.size === 0 && heartbeatTimer !== null) {\n clearInterval(heartbeatTimer);\n heartbeatTimer = null;\n }\n };\n\n const sendTo = (conn: Conn, data: string) => {\n if (!conn.open) return;\n conn.lastSentAt = Date.now();\n try {\n conn.socket.send(data);\n } catch {\n closeConn(conn);\n }\n };\n\n const ensureHeartbeat = () => {\n if (heartbeatTimer !== null || heartbeatMs <= 0) return;\n heartbeatTimer = setInterval(() => {\n const now = Date.now();\n for (const conn of [...conns]) {\n if (now - conn.lastSentAt >= heartbeatMs) sendTo(conn, \"{}\");\n }\n }, heartbeatMs);\n (heartbeatTimer as unknown as { unref?: () => void }).unref?.();\n };\n\n const flush = (fin?: StatewireHost.Fin) => {\n flushTapSync(() => {});\n stateHost?.takeChanged();\n for (const rec of clients.values()) {\n for (const adm of rec.inflight.values()) {\n if (adm.acked && !adm.settled && !adm.pendingSent) {\n adm.pendingSent = true;\n queueRes(rec.id, { seq: adm.seq, type: \"pending\" });\n }\n }\n }\n if (\n pendingOps.length === 0 &&\n dirtyAcks.size === 0 &&\n pendingRes.size === 0 &&\n fin === undefined\n ) {\n return;\n }\n const ops = pendingOps;\n const acks = dirtyAcks;\n const res = pendingRes;\n pendingOps = [];\n dirtyAcks = new Set();\n pendingRes = new Map();\n for (const conn of [...conns]) {\n const envelope: StatewireEnvelope = {};\n if (ops.length > 0) envelope.ops = ops;\n const clientRes = res.get(conn.clientId);\n if (clientRes !== undefined) envelope.res = clientRes;\n if (acks.has(conn.clientId)) {\n const rec = clients.get(conn.clientId);\n if (rec !== undefined) envelope.ack = rec.lastSeq;\n }\n if (fin !== undefined) envelope.fin = fin;\n if (Object.keys(envelope).length === 0) continue;\n sendTo(conn, JSON.stringify(envelope));\n if (fin !== undefined) closeConn(conn);\n }\n };\n\n const queueRes = (clientId: string, response: StatewireResponse) => {\n const list = pendingRes.get(clientId);\n if (list) list.push(response);\n else pendingRes.set(clientId, [response]);\n };\n\n const advance = (rec: ClientRec, seq: number) => {\n rec.lastSeq = seq;\n dirtyAcks.add(rec.id);\n };\n\n const retain = (rec: ClientRec, response: StatewireResponse) => {\n const now = Date.now();\n rec.results.set(response.seq, { ts: now, res: response });\n const newest = Math.max(...rec.results.keys());\n for (const [seq, entry] of rec.results) {\n if (seq !== newest && now - entry.ts > RESULT_TTL_MS) {\n rec.results.delete(seq);\n }\n }\n while (rec.results.size > RESULT_LIMIT) {\n rec.results.delete(Math.min(...rec.results.keys()));\n }\n };\n\n const duplicateAnswer = (rec: ClientRec, seq: number): StatewireResponse => {\n const cached = rec.results.get(seq);\n if (cached) {\n const newest = Math.max(...rec.results.keys());\n if (seq === newest || Date.now() - cached.ts <= RESULT_TTL_MS) {\n return cached.res;\n }\n rec.results.delete(seq);\n }\n if (rec.inflight.has(seq)) return { seq, type: \"pending\" };\n return { seq, type: \"result-unavailable\" };\n };\n\n const settleResponse = (\n rec: ClientRec,\n adm: Admission,\n response: StatewireResponse,\n ) => {\n if (adm.settled) return;\n adm.settled = true;\n rec.inflight.delete(adm.seq);\n retain(rec, response);\n if (!adm.acked) {\n adm.acked = true;\n advance(rec, adm.seq);\n }\n if (\n adm.pendingSent ||\n response.type !== \"accepted\" ||\n response.payload !== undefined\n ) {\n queueRes(rec.id, response);\n }\n scheduleFlush();\n adm.release();\n };\n\n const settleWire = (\n rec: ClientRec,\n adm: Admission,\n outcome: CommandsOutcome,\n ) => settleResponse(rec, adm, responseOf(adm, outcome));\n\n const callerOf = (rec: ClientRec): StatewireHost.Caller => ({\n get context() {\n return rec.context;\n },\n get isConnected() {\n if (clients.get(rec.id) !== rec) return false;\n for (const conn of conns) {\n if (conn.clientId === rec.id && conn.open) return true;\n }\n return false;\n },\n });\n\n const admit = (conn: Conn, frame: Frame): Promise<void> | void => {\n if (!conn.open || disposed || finished !== null) return;\n const rec = clients.get(conn.clientId)!;\n if (frame.seq <= rec.lastSeq || rec.inflight.has(frame.seq)) {\n queueRes(rec.id, duplicateAnswer(rec, frame.seq));\n scheduleFlush();\n return;\n }\n if (frame.seq > rec.lastSeq + 1) {\n closeConn(\n conn,\n 1008,\n `seq gap: expected ${rec.lastSeq + 1}, got ${frame.seq}`,\n );\n return;\n }\n const adm: Admission = {\n seq: frame.seq,\n method: frame.method ?? \"\",\n acked: false,\n pendingSent: false,\n settled: false,\n release: () => {},\n };\n if (frame.context !== undefined) {\n let value: Record<string, unknown>;\n try {\n value = contextValue(frame.context);\n } catch (error) {\n rec.inflight.set(frame.seq, adm);\n settleResponse(rec, adm, {\n seq: frame.seq,\n type: \"rejected\",\n code: \"invalid-context\",\n message: `invalid context: ${(error as Error).message}`,\n });\n return;\n }\n rec.context = value;\n if (frame.method === undefined) {\n rec.inflight.set(frame.seq, adm);\n settleResponse(rec, adm, { seq: frame.seq, type: \"accepted\" });\n return;\n }\n }\n const released = new Promise<void>((resolve) => {\n adm.release = resolve;\n });\n rec.inflight.set(frame.seq, adm);\n commandsHost!.submit!({\n method: frame.method!,\n params: frame.params,\n clientId: rec.id,\n caller: callerOf(rec),\n onAck: () => {\n if (adm.settled || adm.acked) return;\n adm.acked = true;\n advance(rec, adm.seq);\n scheduleFlush();\n adm.release();\n },\n onSettle: (outcome) => settleWire(rec, adm, outcome),\n }).catch((error: unknown) => {\n if (!adm.settled) settleWire(rec, adm, { type: \"rejected\", error });\n });\n return released;\n };\n\n const message = (conn: Conn, data: string) => {\n if (!conn.open || disposed || finished !== null) return;\n const frame = parseFrame(data);\n if (frame === null) {\n closeConn(conn, 1008, \"invalid frame\");\n return;\n }\n inbox = inbox.then(() => admit(conn, frame));\n };\n\n const connect = (\n clientId: string,\n socket: StatewireHostInternal.Socket,\n ): StatewireHostInternal.SocketConnection => {\n const dead: StatewireHostInternal.SocketConnection = {\n clientId,\n message: () => {},\n close: () => {},\n };\n const refuse = (code: number, reason: string) => {\n try {\n socket.close(code, reason);\n } catch {\n /* peer already gone */\n }\n return dead;\n };\n if (disposed) return refuse(1001, \"disposed\");\n if (!CLIENT_ID_RE.test(clientId)) return refuse(1008, \"invalid client id\");\n flush();\n if (finished !== null) {\n try {\n socket.send(JSON.stringify({ fin: finished }));\n } catch {\n /* peer already gone */\n }\n return refuse(1000, \"finished\");\n }\n const known = clients.has(clientId);\n const rec = known\n ? clients.get(clientId)!\n : (() => {\n const created: ClientRec = {\n id: clientId,\n lastSeq: 0,\n context: null,\n results: new Map(),\n inflight: new Map(),\n };\n clients.set(clientId, created);\n return created;\n })();\n for (const other of [...conns]) {\n if (other.clientId !== clientId) continue;\n sendTo(other, JSON.stringify({ fin: { reason: \"superseded\" } }));\n closeConn(other);\n }\n const conn: Conn = { clientId, socket, open: true, lastSentAt: 0 };\n conns.add(conn);\n ensureHeartbeat();\n const { value, meta } = encodeValue(stateHost!.snapshot());\n const snapshot: StatewireEnvelope = {\n ops: [\n {\n op: \"replace\",\n path: [],\n value,\n ...(meta !== undefined && { meta }),\n },\n ],\n syn: { lastSeq: known ? rec.lastSeq : -1, lease: randomId() },\n };\n sendTo(conn, JSON.stringify(snapshot));\n return {\n clientId,\n message: (data) => message(conn, data),\n close: () => closeConn(conn),\n };\n };\n\n const finish = (fin: StatewireHost.Fin) => {\n if (finished !== null || disposed) return;\n finished = fin;\n flush(fin);\n };\n\n return {\n connect,\n finish,\n attach,\n resume: () => {\n disposed = false;\n stateHost?.setOnOp(recordOp);\n emitHost?.setSink((event) => recordOp({ op: \"event\", value: event }));\n },\n suspend: () => {\n disposed = true;\n if (heartbeatTimer !== null) {\n clearInterval(heartbeatTimer);\n heartbeatTimer = null;\n }\n stateHost?.setOnOp(null);\n emitHost?.setSink(null);\n },\n };\n};\n\nconst useSocketHost = (\n transport: ResourceElement<Statewire>,\n options?: StatewireHostInternal.SocketHostOptions,\n): Pick<StatewireHostInternal.SocketHost, \"connect\" | \"finish\"> => {\n const [h] = useState(() => createSocketHost(options));\n const value = useResource(transport) as Statewire<unknown>;\n h.attach(value);\n useEffect(() => {\n h.attach(value);\n });\n useEffect(() => {\n h.resume();\n return () => h.suspend();\n }, [h]);\n return h;\n};\n\n/**\n * Transport-agnostic WS binding over a statewire/host transport:\n * feed it sockets (anything with `send`/`close`) and raw text frames, and it\n * speaks the Statewire envelope protocol — snapshot-first attach with `syn`,\n * delta/ack/res envelopes, per-client-id supersede, and `fin` on finish.\n */\nexport const StatewireSocketHost = (\n transport: ResourceElement<Statewire>,\n options?: StatewireHostInternal.SocketHostOptions,\n): StatewireHostInternal.SocketHost => {\n const SocketHostRoot = () => useSocketHost(transport, options);\n const root = createTapRoot(SocketHostRoot);\n let disposed = false;\n return {\n connect: (clientId, socket) =>\n flushTapSync(() => root.getValue().connect(clientId, socket)),\n finish: (fin) => flushTapSync(() => root.getValue().finish(fin)),\n dispose: () => {\n if (disposed) return;\n disposed = true;\n flushTapSync(() => root.getValue().finish({ reason: \"evicted\" }));\n root.unmount();\n },\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"socket.js","names":["useEffect","useState","createTapRoot","flushTapSync","useResource","ResourceElement","getCommandsHost","getEmitHost","getStateHost","CommandsHost","CommandsOutcome","StatewireSendError","Statewire","StatewireHost","StatewireHostInternal","encodeValue","reviveValue","createMacrotaskPoster","randomId","StatewireMetaEntry","StatewirePath","StatewireEnvelope","StatewireResponse","CLIENT_ID_RE","RESULT_TTL_MS","RESULT_LIMIT","WireOp","op","path","value","meta","Admission","seq","method","acked","pendingSent","settled","release","ClientRec","id","lastSeq","context","Record","results","Map","ts","res","inflight","Conn","clientId","socket","Socket","open","lastSentAt","Frame","params","parseFrame","raw","frame","JSON","parse","Array","isArray","undefined","Number","isInteger","contextValue","length","Error","encodePayload","payload","responseOf","adm","outcome","type","failure","error","fate","code","message","String","createSocketHost","options","SocketHostOptions","heartbeatMs","heartbeatIntervalMs","clients","conns","Set","pendingOps","dirtyAcks","pendingRes","inbox","Promise","resolve","finished","Fin","disposed","flushScheduled","heartbeatTimer","ReturnType","setInterval","stateHost","StateHost","commandsHost","emitHost","current","post","flush","scheduleFlush","recordOp","push","attach","next","nextState","state","nextCommands","commands","nextEmit","emit","submit","setOnOp","setSink","event","closeConn","conn","reason","delete","close","size","clearInterval","sendTo","data","Date","now","send","ensureHeartbeat","unref","fin","takeChanged","rec","values","queueRes","ops","acks","envelope","clientRes","get","has","ack","Object","keys","stringify","response","list","set","advance","add","retain","newest","Math","max","entry","min","duplicateAnswer","cached","settleResponse","settleWire","callerOf","Caller","isConnected","admit","released","caller","onAck","onSettle","catch","then","connect","SocketConnection","dead","refuse","test","known","created","other","snapshot","syn","lease","finish","resume","suspend","useSocketHost","transport","$","_c","t0","h","t1","t2","t3","StatewireSocketHost","SocketHost","SocketHostRoot","root","getValue","dispose","unmount"],"sources":["../../src/host/socket.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { createTapRoot, flushTapSync, useResource } from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { getCommandsHost, getEmitHost, getStateHost } from \"./host\";\nimport type { CommandsHost, CommandsOutcome } from \"./host\";\nimport { StatewireSendError } from \"../types\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireHost } from \"../host\";\nimport type { StatewireHostInternal } from \"../host-internal\";\nimport { encodeValue, reviveValue } from \"../wire/meta\";\nimport { createMacrotaskPoster } from \"../util/macrotask\";\nimport { randomId } from \"../util/randomId\";\nimport type { StatewireMetaEntry } from \"../wire/meta\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport type {\n StatewireEnvelope,\n StatewireResponse,\n} from \"../client/connection\";\n\nconst CLIENT_ID_RE = /^[A-Za-z0-9._~-]{1,256}$/;\nconst RESULT_TTL_MS = 30_000;\nconst RESULT_LIMIT = 256;\n\ntype WireOp = {\n op: string;\n path?: StatewirePath;\n value?: unknown;\n meta?: StatewireMetaEntry[];\n};\n\ntype Admission = {\n seq: number;\n method: string;\n acked: boolean;\n pendingSent: boolean;\n settled: boolean;\n release: () => void;\n};\n\ntype ClientRec = {\n id: string;\n lastSeq: number;\n context: Record<string, unknown> | null;\n results: Map<number, { ts: number; res: StatewireResponse }>;\n inflight: Map<number, Admission>;\n};\n\ntype Conn = {\n clientId: string;\n socket: StatewireHostInternal.Socket;\n open: boolean;\n lastSentAt: number;\n};\n\ntype Frame = {\n method?: string;\n seq: number;\n params: unknown[];\n context?: unknown;\n};\n\nconst parseFrame = (raw: string): Frame | null => {\n let frame: unknown;\n try {\n frame = JSON.parse(raw);\n } catch {\n return null;\n }\n if (typeof frame !== \"object\" || frame === null || Array.isArray(frame)) {\n return null;\n }\n const { method, seq, params, meta, context } = frame as Record<\n string,\n unknown\n >;\n if (method === undefined && context === undefined) return null;\n if (typeof seq !== \"number\" || !Number.isInteger(seq) || seq < 1) return null;\n if (method === undefined) {\n return { seq, params: [], ...(context !== undefined && { context }) };\n }\n if (typeof method !== \"string\") return null;\n if (!Array.isArray(params)) return null;\n if (meta !== undefined) {\n try {\n reviveValue(params, meta);\n } catch {\n return null;\n }\n }\n return { method, seq, params, ...(context !== undefined && { context }) };\n};\n\n/** v1 context submissions are exactly one root replace with an object value. */\nconst contextValue = (context: unknown): Record<string, unknown> => {\n if (!Array.isArray(context) || context.length !== 1) {\n throw new Error(\"context must be a single-op array\");\n }\n const op = context[0] as Record<string, unknown> | null;\n if (\n typeof op !== \"object\" ||\n op === null ||\n op[\"op\"] !== \"replace\" ||\n !Array.isArray(op[\"path\"]) ||\n op[\"path\"].length !== 0\n ) {\n throw new Error(\"context op must be a root replace\");\n }\n const value = op[\"value\"];\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new Error(\"context value must be an object\");\n }\n return value as Record<string, unknown>;\n};\n\nconst encodePayload = (\n payload: unknown,\n): { payload?: unknown; meta?: StatewireMetaEntry[] } => {\n if (payload === undefined) return {};\n const { value, meta } = encodeValue(payload);\n return { payload: value, ...(meta !== undefined && { meta }) };\n};\n\nconst responseOf = (\n adm: Admission,\n outcome: CommandsOutcome,\n): StatewireResponse => {\n if (outcome.type === \"accepted\") {\n return {\n seq: adm.seq,\n type: \"accepted\",\n ...encodePayload(outcome.payload),\n };\n }\n const failure =\n outcome.error instanceof StatewireSendError ? outcome.error.failure : null;\n switch (failure?.fate) {\n case \"unknown-command\":\n return {\n seq: adm.seq,\n type: \"rejected\",\n code: \"unknown-command\",\n message: `unknown method: '${adm.method}'`,\n };\n case \"invalid-params\":\n return {\n seq: adm.seq,\n type: \"rejected\",\n code: \"invalid-params\",\n message: failure.message,\n };\n case \"crashed\":\n return { seq: adm.seq, type: \"crashed\" };\n case \"rejected\":\n return {\n seq: adm.seq,\n type: \"rejected\",\n message: failure.message,\n ...encodePayload(failure.payload),\n };\n default:\n return {\n seq: adm.seq,\n type: \"rejected\",\n message:\n outcome.error instanceof Error\n ? outcome.error.message\n : String(outcome.error),\n };\n }\n};\n\nconst createSocketHost = (\n options?: StatewireHostInternal.SocketHostOptions,\n) => {\n const heartbeatMs = options?.heartbeatIntervalMs ?? 15_000;\n\n const clients = new Map<string, ClientRec>();\n const conns = new Set<Conn>();\n let pendingOps: WireOp[] = [];\n let dirtyAcks = new Set<string>();\n let pendingRes = new Map<string, StatewireResponse[]>();\n let inbox: Promise<void> = Promise.resolve();\n let finished: StatewireHost.Fin | null = null;\n let disposed = false;\n let flushScheduled = false;\n let heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n\n let stateHost: StatewireHost.StateHost | null = null;\n let commandsHost: CommandsHost | null = null;\n let emitHost: ReturnType<typeof getEmitHost> | null = null;\n let current: Statewire<unknown> | null = null;\n\n const post = createMacrotaskPoster(() => {\n flushScheduled = false;\n flush();\n });\n const scheduleFlush = () => {\n if (flushScheduled || disposed) return;\n flushScheduled = true;\n post();\n };\n\n const recordOp = (op: {\n op: string;\n path?: StatewirePath;\n value?: unknown;\n }) => {\n if (op.op === \"replace\" || op.op === \"add\" || op.op === \"event\") {\n const { value, meta } = encodeValue(op.value);\n pendingOps.push({ ...op, value, ...(meta !== undefined && { meta }) });\n } else {\n pendingOps.push(op);\n }\n scheduleFlush();\n };\n\n const attach = (next: Statewire<unknown>) => {\n if (next === current) return;\n current = next;\n const nextState =\n typeof next.state === \"object\" && next.state !== null\n ? getStateHost(next.state)\n : undefined;\n const nextCommands = getCommandsHost(next.commands);\n const nextEmit = getEmitHost(next.emit);\n if (!nextState || !nextCommands?.submit || !nextEmit) {\n throw new Error(\n \"StatewireSocketHost requires a statewire/host transport (useStatewireState with object state, useStatewireCommands, useStatewireEmit)\",\n );\n }\n if (nextState !== stateHost) {\n stateHost?.setOnOp(null);\n stateHost = nextState;\n stateHost.setOnOp(recordOp);\n }\n commandsHost = nextCommands;\n if (nextEmit !== emitHost) {\n emitHost?.setSink(null);\n emitHost = nextEmit;\n emitHost.setSink((event) => recordOp({ op: \"event\", value: event }));\n }\n };\n\n const closeConn = (conn: Conn, code?: number, reason?: string) => {\n if (!conn.open) return;\n conn.open = false;\n conns.delete(conn);\n try {\n conn.socket.close(code, reason);\n } catch {\n /* peer already gone */\n }\n if (conns.size === 0 && heartbeatTimer !== null) {\n clearInterval(heartbeatTimer);\n heartbeatTimer = null;\n }\n };\n\n const sendTo = (conn: Conn, data: string) => {\n if (!conn.open) return;\n conn.lastSentAt = Date.now();\n try {\n conn.socket.send(data);\n } catch {\n closeConn(conn);\n }\n };\n\n const ensureHeartbeat = () => {\n if (heartbeatTimer !== null || heartbeatMs <= 0) return;\n heartbeatTimer = setInterval(() => {\n const now = Date.now();\n for (const conn of [...conns]) {\n if (now - conn.lastSentAt >= heartbeatMs) sendTo(conn, \"{}\");\n }\n }, heartbeatMs);\n (heartbeatTimer as unknown as { unref?: () => void }).unref?.();\n };\n\n const flush = (fin?: StatewireHost.Fin) => {\n flushTapSync(() => {});\n stateHost?.takeChanged();\n for (const rec of clients.values()) {\n for (const adm of rec.inflight.values()) {\n if (adm.acked && !adm.settled && !adm.pendingSent) {\n adm.pendingSent = true;\n queueRes(rec.id, { seq: adm.seq, type: \"pending\" });\n }\n }\n }\n if (\n pendingOps.length === 0 &&\n dirtyAcks.size === 0 &&\n pendingRes.size === 0 &&\n fin === undefined\n ) {\n return;\n }\n const ops = pendingOps;\n const acks = dirtyAcks;\n const res = pendingRes;\n pendingOps = [];\n dirtyAcks = new Set();\n pendingRes = new Map();\n for (const conn of [...conns]) {\n const envelope: StatewireEnvelope = {};\n if (ops.length > 0) envelope.ops = ops;\n const clientRes = res.get(conn.clientId);\n if (clientRes !== undefined) envelope.res = clientRes;\n if (acks.has(conn.clientId)) {\n const rec = clients.get(conn.clientId);\n if (rec !== undefined) envelope.ack = rec.lastSeq;\n }\n if (fin !== undefined) envelope.fin = fin;\n if (Object.keys(envelope).length === 0) continue;\n sendTo(conn, JSON.stringify(envelope));\n if (fin !== undefined) closeConn(conn);\n }\n };\n\n const queueRes = (clientId: string, response: StatewireResponse) => {\n const list = pendingRes.get(clientId);\n if (list) list.push(response);\n else pendingRes.set(clientId, [response]);\n };\n\n const advance = (rec: ClientRec, seq: number) => {\n rec.lastSeq = seq;\n dirtyAcks.add(rec.id);\n };\n\n const retain = (rec: ClientRec, response: StatewireResponse) => {\n const now = Date.now();\n rec.results.set(response.seq, { ts: now, res: response });\n const newest = Math.max(...rec.results.keys());\n for (const [seq, entry] of rec.results) {\n if (seq !== newest && now - entry.ts > RESULT_TTL_MS) {\n rec.results.delete(seq);\n }\n }\n while (rec.results.size > RESULT_LIMIT) {\n rec.results.delete(Math.min(...rec.results.keys()));\n }\n };\n\n const duplicateAnswer = (rec: ClientRec, seq: number): StatewireResponse => {\n const cached = rec.results.get(seq);\n if (cached) {\n const newest = Math.max(...rec.results.keys());\n if (seq === newest || Date.now() - cached.ts <= RESULT_TTL_MS) {\n return cached.res;\n }\n rec.results.delete(seq);\n }\n if (rec.inflight.has(seq)) return { seq, type: \"pending\" };\n return { seq, type: \"result-unavailable\" };\n };\n\n const settleResponse = (\n rec: ClientRec,\n adm: Admission,\n response: StatewireResponse,\n ) => {\n if (adm.settled) return;\n adm.settled = true;\n rec.inflight.delete(adm.seq);\n retain(rec, response);\n if (!adm.acked) {\n adm.acked = true;\n advance(rec, adm.seq);\n }\n if (\n adm.pendingSent ||\n response.type !== \"accepted\" ||\n response.payload !== undefined\n ) {\n queueRes(rec.id, response);\n }\n scheduleFlush();\n adm.release();\n };\n\n const settleWire = (\n rec: ClientRec,\n adm: Admission,\n outcome: CommandsOutcome,\n ) => settleResponse(rec, adm, responseOf(adm, outcome));\n\n const callerOf = (rec: ClientRec): StatewireHost.Caller => ({\n clientId: rec.id,\n get context() {\n return rec.context;\n },\n get isConnected() {\n if (clients.get(rec.id) !== rec) return false;\n for (const conn of conns) {\n if (conn.clientId === rec.id && conn.open) return true;\n }\n return false;\n },\n });\n\n const admit = (conn: Conn, frame: Frame): Promise<void> | void => {\n if (!conn.open || disposed || finished !== null) return;\n const rec = clients.get(conn.clientId)!;\n if (frame.seq <= rec.lastSeq || rec.inflight.has(frame.seq)) {\n queueRes(rec.id, duplicateAnswer(rec, frame.seq));\n scheduleFlush();\n return;\n }\n if (frame.seq > rec.lastSeq + 1) {\n closeConn(\n conn,\n 1008,\n `seq gap: expected ${rec.lastSeq + 1}, got ${frame.seq}`,\n );\n return;\n }\n const adm: Admission = {\n seq: frame.seq,\n method: frame.method ?? \"\",\n acked: false,\n pendingSent: false,\n settled: false,\n release: () => {},\n };\n if (frame.context !== undefined) {\n let value: Record<string, unknown>;\n try {\n value = contextValue(frame.context);\n } catch (error) {\n rec.inflight.set(frame.seq, adm);\n settleResponse(rec, adm, {\n seq: frame.seq,\n type: \"rejected\",\n code: \"invalid-context\",\n message: `invalid context: ${(error as Error).message}`,\n });\n return;\n }\n rec.context = value;\n if (frame.method === undefined) {\n rec.inflight.set(frame.seq, adm);\n settleResponse(rec, adm, { seq: frame.seq, type: \"accepted\" });\n return;\n }\n }\n const released = new Promise<void>((resolve) => {\n adm.release = resolve;\n });\n rec.inflight.set(frame.seq, adm);\n commandsHost!.submit!({\n method: frame.method!,\n params: frame.params,\n clientId: rec.id,\n caller: callerOf(rec),\n onAck: () => {\n if (adm.settled || adm.acked) return;\n adm.acked = true;\n advance(rec, adm.seq);\n scheduleFlush();\n adm.release();\n },\n onSettle: (outcome) => settleWire(rec, adm, outcome),\n }).catch((error: unknown) => {\n if (!adm.settled) settleWire(rec, adm, { type: \"rejected\", error });\n });\n return released;\n };\n\n const message = (conn: Conn, data: string) => {\n if (!conn.open || disposed || finished !== null) return;\n const frame = parseFrame(data);\n if (frame === null) {\n closeConn(conn, 1008, \"invalid frame\");\n return;\n }\n inbox = inbox.then(() => admit(conn, frame));\n };\n\n const connect = (\n clientId: string,\n socket: StatewireHostInternal.Socket,\n ): StatewireHostInternal.SocketConnection => {\n const dead: StatewireHostInternal.SocketConnection = {\n clientId,\n message: () => {},\n close: () => {},\n };\n const refuse = (code: number, reason: string) => {\n try {\n socket.close(code, reason);\n } catch {\n /* peer already gone */\n }\n return dead;\n };\n if (disposed) return refuse(1001, \"disposed\");\n if (!CLIENT_ID_RE.test(clientId)) return refuse(1008, \"invalid client id\");\n flush();\n if (finished !== null) {\n try {\n socket.send(JSON.stringify({ fin: finished }));\n } catch {\n /* peer already gone */\n }\n return refuse(1000, \"finished\");\n }\n const known = clients.has(clientId);\n const rec = known\n ? clients.get(clientId)!\n : (() => {\n const created: ClientRec = {\n id: clientId,\n lastSeq: 0,\n context: null,\n results: new Map(),\n inflight: new Map(),\n };\n clients.set(clientId, created);\n return created;\n })();\n for (const other of [...conns]) {\n if (other.clientId !== clientId) continue;\n sendTo(other, JSON.stringify({ fin: { reason: \"superseded\" } }));\n closeConn(other);\n }\n const conn: Conn = { clientId, socket, open: true, lastSentAt: 0 };\n conns.add(conn);\n ensureHeartbeat();\n const { value, meta } = encodeValue(stateHost!.snapshot());\n const snapshot: StatewireEnvelope = {\n ops: [\n {\n op: \"replace\",\n path: [],\n value,\n ...(meta !== undefined && { meta }),\n },\n ],\n syn: { lastSeq: known ? rec.lastSeq : -1, lease: randomId() },\n };\n sendTo(conn, JSON.stringify(snapshot));\n return {\n clientId,\n message: (data) => message(conn, data),\n close: () => closeConn(conn),\n };\n };\n\n const finish = (fin: StatewireHost.Fin) => {\n if (finished !== null || disposed) return;\n finished = fin;\n flush(fin);\n };\n\n return {\n connect,\n finish,\n attach,\n resume: () => {\n disposed = false;\n stateHost?.setOnOp(recordOp);\n emitHost?.setSink((event) => recordOp({ op: \"event\", value: event }));\n },\n suspend: () => {\n disposed = true;\n if (heartbeatTimer !== null) {\n clearInterval(heartbeatTimer);\n heartbeatTimer = null;\n }\n stateHost?.setOnOp(null);\n emitHost?.setSink(null);\n },\n };\n};\n\nconst useSocketHost = (\n transport: ResourceElement<Statewire>,\n options?: StatewireHostInternal.SocketHostOptions,\n): Pick<StatewireHostInternal.SocketHost, \"connect\" | \"finish\"> => {\n const [h] = useState(() => createSocketHost(options));\n const value = useResource(transport) as Statewire<unknown>;\n h.attach(value);\n useEffect(() => {\n h.attach(value);\n });\n useEffect(() => {\n h.resume();\n return () => h.suspend();\n }, [h]);\n return h;\n};\n\n/**\n * Transport-agnostic WS binding over a statewire/host transport:\n * feed it sockets (anything with `send`/`close`) and raw text frames, and it\n * speaks the Statewire envelope protocol — snapshot-first attach with `syn`,\n * delta/ack/res envelopes, per-client-id supersede, and `fin` on finish.\n */\nexport const StatewireSocketHost = (\n transport: ResourceElement<Statewire>,\n options?: StatewireHostInternal.SocketHostOptions,\n): StatewireHostInternal.SocketHost => {\n const SocketHostRoot = () => useSocketHost(transport, options);\n const root = createTapRoot(SocketHostRoot);\n let disposed = false;\n return {\n connect: (clientId, socket) =>\n flushTapSync(() => root.getValue().connect(clientId, socket)),\n finish: (fin) => flushTapSync(() => root.getValue().finish(fin)),\n dispose: () => {\n if (disposed) return;\n disposed = true;\n flushTapSync(() => root.getValue().finish({ reason: \"evicted\" }));\n root.unmount();\n },\n };\n};\n"],"mappings":"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"}
package/dist/host.d.ts CHANGED
@@ -33,11 +33,13 @@ declare namespace StatewireHost {
33
33
  };
34
34
  /**
35
35
  * The submitting client, bound as `this` of a command handler. Holdable
36
- * across a long run: `context` reads the client's current ambient context
37
- * live (null until the client sends one), `isConnected` reports whether the
38
- * client has an active stream attachment.
36
+ * across a long run: `clientId` is the client's `Statewire-Client-Id`
37
+ * (in-process dispatch has none — reading it throws), `context` reads the
38
+ * client's current ambient context live (null until the client sends one),
39
+ * `isConnected` reports whether the client has an active stream attachment.
39
40
  */
40
41
  type Caller = {
42
+ readonly clientId: string;
41
43
  readonly context: Record<string, unknown> | null;
42
44
  readonly isConnected: boolean;
43
45
  };
@@ -1 +1 @@
1
- {"version":3,"file":"host.d.ts","names":[],"sources":["../src/host.ts"],"mappings":";;;;;cAac;KAET,gBAAgB,KAAK,UAAU,cAAc,gBAAgB,KAAK,IAAI;kBAE1D;OACH,SAAS;;IAEnB,QAAQ,cAAc,UAAU;;IAEhC,gBAAgB;;OAGN,YAAY,UAAU;;IAEhC,UAAU,YAAY,IAAI;;OAGhB,cAAc,UAAU;;;;;;;OAQxB,UAAU;cACV,mBAAmB,OAAO;;OAG1B,mBAAmB;IAC7B;IACA,UAAU,OAAO;IACjB,SAAS;;;;;;;;OASC;aACD,SAAS;aACT;;OAGC,gBAAgB,kBAAkB,UAAU,eACrD,WAAW,gBACP,MAAM,WAAW,UAAU,QAC3B,gBAAgB,QAAQ,WAAW,UAAU;OAGxC;IACV;IACA;IACA;IACA"}
1
+ {"version":3,"file":"host.d.ts","names":[],"sources":["../src/host.ts"],"mappings":";;;;;cAac;KAET,gBAAgB,KAAK,UAAU,cAAc,gBAAgB,KAAK,IAAI;kBAE1D;OACH,SAAS;;IAEnB,QAAQ,cAAc,UAAU;;IAEhC,gBAAgB;;OAGN,YAAY,UAAU;;IAEhC,UAAU,YAAY,IAAI;;OAGhB,cAAc,UAAU;;;;;;;OAQxB,UAAU;cACV,mBAAmB,OAAO;;OAG1B,mBAAmB;IAC7B;IACA,UAAU,OAAO;IACjB,SAAS;;;;;;;;;OAUC;aACD;aACA,SAAS;aACT;;OAGC,gBAAgB,kBAAkB,UAAU,eACrD,WAAW,gBACP,MAAM,WAAW,UAAU,QAC3B,gBAAgB,QAAQ,WAAW,UAAU;OAGxC;IACV;IACA;IACA;IACA"}
@@ -98,16 +98,28 @@ declare namespace StatewireClient {
98
98
  cause: "server" | "unmount" | "stream-gone" | "session-lost" | "superseded";
99
99
  } | {
100
100
  fate: "aborted";
101
- cause: "cascade" | "unmount" | "stream-gone" | "session-lost" | "superseded";
101
+ cause: "unmount" | "stream-gone" | "session-lost" | "superseded";
102
+ predecessorLost?: true;
102
103
  } | {
103
104
  fate: "lost";
104
105
  cause: "unmount" | "stream-gone" | "session-lost" | "superseded";
105
106
  };
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
+ };
106
117
  type SSEOptions = StatewireSessionStoreOptions & {
107
118
  url: string;
108
119
  headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
109
120
  fetch?: typeof globalThis.fetch;
110
121
  onChange?: (connection: Connection) => void;
122
+ onCommandSessionLost?: (command: SessionLostCommand) => "replay" | void;
111
123
  };
112
124
  type WSOptions = StatewireSessionStoreOptions & {
113
125
  url: string;
@@ -115,6 +127,7 @@ declare namespace StatewireClient {
115
127
  query?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
116
128
  webSocket?: new (url: string, protocols?: string | string[]) => WebSocket;
117
129
  onChange?: (connection: Connection) => void;
130
+ onCommandSessionLost?: (command: SessionLostCommand) => "replay" | void;
118
131
  };
119
132
  }
120
133
  //#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;;IAQA;IACA;;OAGM,aAAa;IACvB;IACA,UAAU,qBAAqB,cAAc,QAAQ;IACrD,eAAe,WAAW;IAC1B,YAAY,YAAY;;OAGd,YAAY;IACtB;IACA;IACA,QACI,gCACO,yBAAyB,QAAQ;IAC5C,iBAAiB,aAAa,kCAAkC;IAChE,YAAY,YAAY"}
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 +1 @@
1
- {"version":3,"file":"statewire.js","names":["useMemo","useRef","useState","useSyncExternalStore","createTapRoot","flushTapSync","resource","useResource","ResourceElement","LIVE_CONNECTION","useStatewireCore","Statewire","StatewireSessionStoreOptions","useStatewire","Commands","options","StatewireClient","Options","TState","TCommands","TEvent","Helpers","latest","current","value","transport","core","subscribe","listener","state","getState","connection","commands","CommandsProxy","setContext","contextValue","Record","Error","StatewireResource","root","disposed","constructor","ClientRoot","getValue","Connection","Promise","dispose","unmount","optimistic","Optimistic","NonNullable","onEvent","event","ctx","onStateChange","changes","Changes","onCommandChange","update","CommandUpdate","ConnectionState","status","cause","attempt","nextRetryAt","lastError","message","payload","reconnect","ConnectionStatus","draft","command","Command","OptimisticContext","ts","seq","clientId","SendFailure","fate","SSEOptions","url","headers","HeadersInit","fetch","globalThis","onChange","WSOptions","protocols","query","webSocket","WebSocket"],"sources":["../src/statewire.ts"],"sourcesContent":["import { useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport {\n createTapRoot,\n flushTapSync,\n resource,\n useResource,\n} from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { LIVE_CONNECTION, useStatewireCore } from \"./client/core\";\nimport type { Statewire } from \"./types\";\nimport type { StatewireSessionStoreOptions } from \"./client/sessionStore\";\n\nexport const useStatewire = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n>(\n options: StatewireClient.Options<TState, TCommands, TEvent>,\n): StatewireClient.Helpers<TState, TCommands> => {\n const latest = useRef(options);\n latest.current = options;\n\n const value = useResource(options.transport) as Statewire<unknown>;\n const core = useStatewireCore(() => latest.current, value);\n const [subscribe] = useState(\n () => (listener: () => void) =>\n core.subscribe(() => flushTapSync(listener)),\n );\n\n const state = useSyncExternalStore(\n subscribe,\n core.getState,\n core.getState,\n ) as TState;\n const connection = value.connection ?? LIVE_CONNECTION;\n\n return useMemo(\n () => ({\n state,\n getState: core.getState as () => TState,\n connection,\n commands: core.commands as Statewire.CommandsProxy<TCommands>,\n setContext: (contextValue: Record<string, unknown>) => {\n if (!value.setContext) {\n throw new Error(\n \"statewire: this transport has no client context channel\",\n );\n }\n return value.setContext(contextValue);\n },\n }),\n [state, connection, core, value],\n );\n};\n\nexport const StatewireResource = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n>(\n options: StatewireClient.Options<TState, TCommands, TEvent>,\n): ResourceElement<StatewireClient.Helpers<TState, TCommands>> =>\n resource(useStatewire<TState, TCommands, TEvent>)(options);\n\nexport class StatewireClient<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n> {\n private readonly root;\n private disposed = false;\n\n constructor(options: StatewireClient.Options<TState, TCommands, TEvent>) {\n const ClientRoot = () => useStatewire<TState, TCommands, TEvent>(options);\n this.root = createTapRoot(ClientRoot);\n }\n\n get state(): TState {\n return this.root.getValue().getState();\n }\n\n get connection(): StatewireClient.Connection {\n return this.root.getValue().connection;\n }\n\n get commands(): Statewire.CommandsProxy<TCommands> {\n return this.root.getValue().commands;\n }\n\n setContext(value: Record<string, unknown>): Promise<unknown> {\n return this.root.getValue().setContext(value);\n }\n\n subscribe(listener: () => void): () => void {\n return this.root.subscribe(listener);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.root.unmount();\n }\n}\n\nexport namespace StatewireClient {\n export type Options<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n > = {\n transport: ResourceElement<Statewire<TState, TCommands, TEvent>>;\n optimistic?: Optimistic<NonNullable<TState>, TCommands>;\n onEvent?: (event: TEvent, ctx: { state: NonNullable<TState> }) => void;\n onStateChange?: (\n state: NonNullable<TState>,\n ctx: { changes: Statewire.Changes },\n ) => void;\n onCommandChange?: (\n update: Statewire.CommandUpdate<TCommands>,\n ctx: { state: NonNullable<TState> },\n ) => void;\n };\n\n export type Helpers<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = {\n /** The statewire's state type — remote statewires include `undefined` until the first attach snapshot applies. */\n state: TState;\n /** Identity-stable across renders; reads the current canonical state without subscribing the caller. */\n getState: () => TState;\n connection: Connection;\n commands: Statewire.CommandsProxy<TCommands>;\n /** Replace this client's ambient context on the server; throws on transports without a client context channel. */\n setContext: (value: Record<string, unknown>) => Promise<unknown>;\n };\n\n export type ConnectionState =\n | { status: \"connecting\" }\n | { status: \"live\" }\n | {\n status: \"reconnecting\";\n cause: \"dropped\" | \"evicted\" | \"error\";\n attempt: number;\n nextRetryAt: number;\n lastError?: string;\n message?: string;\n }\n | { status: \"gone\"; message?: string; payload?: unknown };\n export type Connection = ConnectionState & { reconnect: () => void };\n export type ConnectionStatus = Connection[\"status\"];\n\n /**\n * Mutate the draft in place; the client records the next state plus the exact\n * changed paths. Re-run against the new base on every rebase.\n */\n export type Optimistic<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = (\n draft: TState,\n command: Statewire.Command<TCommands>,\n ctx: OptimisticContext,\n ) => void;\n\n export type OptimisticContext = {\n ts: number;\n seq: number;\n clientId?: string;\n };\n\n /**\n * fate: \"aborted\" = never sent, \"rejected\" = the handler refused,\n * \"unknown-command\" = no such command, \"invalid-params\" = the args did not\n * match the command's schema, \"lost\" = sent but never covered by an ack,\n * \"crashed\" = acked but the processing died, \"result-unavailable\" = acked\n * but the result is gone.\n * cause is which channel settled it: \"server\" = the server's answer;\n * the rest are channel deaths the client settled locally.\n */\n export type SendFailure =\n | { fate: \"rejected\"; cause: \"server\"; message: string; payload?: unknown }\n | { fate: \"unknown-command\"; cause: \"server\" }\n | { fate: \"invalid-params\"; cause: \"server\"; message: string }\n | { fate: \"crashed\"; cause: \"server\" }\n | {\n fate: \"result-unavailable\";\n cause:\n | \"server\"\n | \"unmount\"\n | \"stream-gone\"\n | \"session-lost\"\n | \"superseded\";\n }\n | {\n fate: \"aborted\";\n cause:\n | \"cascade\"\n | \"unmount\"\n | \"stream-gone\"\n | \"session-lost\"\n | \"superseded\";\n }\n | {\n fate: \"lost\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\" | \"superseded\";\n };\n\n export type SSEOptions = StatewireSessionStoreOptions & {\n url: string;\n headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);\n fetch?: typeof globalThis.fetch;\n onChange?: (connection: Connection) => void;\n };\n\n export type WSOptions = StatewireSessionStoreOptions & {\n url: string;\n protocols?: string | string[];\n query?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n webSocket?: new (url: string, protocols?: string | string[]) => WebSocket;\n onChange?: (connection: Connection) => void;\n };\n}\n"],"mappings":";;;;AAYA,MAAaa,gBAKXE,YAC+C;CAC/C,MAAMO,SAASrB,OAAOc,OAAO;CAC7BO,OAAOC,UAAUR;CAEjB,MAAMS,QAAQjB,YAAYQ,QAAQU,SAAS;CAC3C,MAAMC,OAAOhB,uBAAuBY,OAAOC,SAASC,KAAK;CACzD,MAAM,CAACG,aAAazB,gBACX0B,aACLF,KAAKC,gBAAgBtB,aAAauB,QAAQ,CAAC,CAC/C;CAEA,MAAMC,QAAQ1B,qBACZwB,WACAD,KAAKI,UACLJ,KAAKI,QACP;CACA,MAAMC,aAAaP,MAAMO,cAActB;CAEvC,OAAOT,eACE;EACL6B;EACAC,UAAUJ,KAAKI;EACfC;EACAC,UAAUN,KAAKM;EACfE,aAAaC,iBAA0C;GACrD,IAAI,CAACX,MAAMU,YACT,MAAM,IAAIG,MACR,yDACF;GAEF,OAAOb,MAAMU,WAAWC,YAAY;EACtC;CACF,IACA;EAACN;EAAOE;EAAYL;EAAMF;CAAK,CACjC;AACF;AAEA,MAAac,qBAKXvB,YAEAT,SAASO,YAAuC,CAAC,CAACE,OAAO;AAE3D,IAAaC,kBAAb,MAIE;CACA;CACA,WAAmB;CAEnByB,YAAY1B,SAA6D;EACvE,MAAM2B,mBAAmB7B,aAAwCE,OAAO;EACxE,KAAKwB,OAAOnC,cAAcsC,UAAU;CACtC;CAEA,IAAIb,QAAgB;EAClB,OAAO,KAAKU,KAAKI,SAAS,CAAC,CAACb,SAAS;CACvC;CAEA,IAAIC,aAAyC;EAC3C,OAAO,KAAKQ,KAAKI,SAAS,CAAC,CAACZ;CAC9B;CAEA,IAAIC,WAA+C;EACjD,OAAO,KAAKO,KAAKI,SAAS,CAAC,CAACX;CAC9B;CAEAE,WAAWV,OAAkD;EAC3D,OAAO,KAAKe,KAAKI,SAAS,CAAC,CAACT,WAAWV,KAAK;CAC9C;CAEAG,UAAUC,UAAkC;EAC1C,OAAO,KAAKW,KAAKZ,UAAUC,QAAQ;CACrC;CAEAkB,UAAgB;EACd,IAAI,KAAKN,UAAU;EACnB,KAAKA,WAAW;EAChB,KAAKD,KAAKQ,QAAQ;CACpB;AACF"}
1
+ {"version":3,"file":"statewire.js","names":["useMemo","useRef","useState","useSyncExternalStore","createTapRoot","flushTapSync","resource","useResource","ResourceElement","LIVE_CONNECTION","useStatewireCore","Statewire","StatewireSessionStoreOptions","useStatewire","Commands","options","StatewireClient","Options","TState","TCommands","TEvent","Helpers","latest","current","value","transport","core","subscribe","listener","state","getState","connection","commands","CommandsProxy","setContext","contextValue","Record","Error","StatewireResource","root","disposed","constructor","ClientRoot","getValue","Connection","Promise","dispose","unmount","optimistic","Optimistic","NonNullable","onEvent","event","ctx","onStateChange","changes","Changes","onCommandChange","update","CommandUpdate","ConnectionState","status","cause","attempt","nextRetryAt","lastError","message","payload","reconnect","ConnectionStatus","draft","command","Command","OptimisticContext","ts","seq","clientId","SendFailure","fate","predecessorLost","SessionLostCommand","method","params","SSEOptions","url","headers","HeadersInit","fetch","globalThis","onChange","onCommandSessionLost","WSOptions","protocols","query","webSocket","WebSocket"],"sources":["../src/statewire.ts"],"sourcesContent":["import { useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport {\n createTapRoot,\n flushTapSync,\n resource,\n useResource,\n} from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { LIVE_CONNECTION, useStatewireCore } from \"./client/core\";\nimport type { Statewire } from \"./types\";\nimport type { StatewireSessionStoreOptions } from \"./client/sessionStore\";\n\nexport const useStatewire = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n>(\n options: StatewireClient.Options<TState, TCommands, TEvent>,\n): StatewireClient.Helpers<TState, TCommands> => {\n const latest = useRef(options);\n latest.current = options;\n\n const value = useResource(options.transport) as Statewire<unknown>;\n const core = useStatewireCore(() => latest.current, value);\n const [subscribe] = useState(\n () => (listener: () => void) =>\n core.subscribe(() => flushTapSync(listener)),\n );\n\n const state = useSyncExternalStore(\n subscribe,\n core.getState,\n core.getState,\n ) as TState;\n const connection = value.connection ?? LIVE_CONNECTION;\n\n return useMemo(\n () => ({\n state,\n getState: core.getState as () => TState,\n connection,\n commands: core.commands as Statewire.CommandsProxy<TCommands>,\n setContext: (contextValue: Record<string, unknown>) => {\n if (!value.setContext) {\n throw new Error(\n \"statewire: this transport has no client context channel\",\n );\n }\n return value.setContext(contextValue);\n },\n }),\n [state, connection, core, value],\n );\n};\n\nexport const StatewireResource = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n>(\n options: StatewireClient.Options<TState, TCommands, TEvent>,\n): ResourceElement<StatewireClient.Helpers<TState, TCommands>> =>\n resource(useStatewire<TState, TCommands, TEvent>)(options);\n\nexport class StatewireClient<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n> {\n private readonly root;\n private disposed = false;\n\n constructor(options: StatewireClient.Options<TState, TCommands, TEvent>) {\n const ClientRoot = () => useStatewire<TState, TCommands, TEvent>(options);\n this.root = createTapRoot(ClientRoot);\n }\n\n get state(): TState {\n return this.root.getValue().getState();\n }\n\n get connection(): StatewireClient.Connection {\n return this.root.getValue().connection;\n }\n\n get commands(): Statewire.CommandsProxy<TCommands> {\n return this.root.getValue().commands;\n }\n\n setContext(value: Record<string, unknown>): Promise<unknown> {\n return this.root.getValue().setContext(value);\n }\n\n subscribe(listener: () => void): () => void {\n return this.root.subscribe(listener);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.root.unmount();\n }\n}\n\nexport namespace StatewireClient {\n export type Options<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n > = {\n transport: ResourceElement<Statewire<TState, TCommands, TEvent>>;\n optimistic?: Optimistic<NonNullable<TState>, TCommands>;\n onEvent?: (event: TEvent, ctx: { state: NonNullable<TState> }) => void;\n onStateChange?: (\n state: NonNullable<TState>,\n ctx: { changes: Statewire.Changes },\n ) => void;\n onCommandChange?: (\n update: Statewire.CommandUpdate<TCommands>,\n ctx: { state: NonNullable<TState> },\n ) => void;\n };\n\n export type Helpers<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = {\n /** The statewire's state type — remote statewires include `undefined` until the first attach snapshot applies. */\n state: TState;\n /** Identity-stable across renders; reads the current canonical state without subscribing the caller. */\n getState: () => TState;\n connection: Connection;\n commands: Statewire.CommandsProxy<TCommands>;\n /** Replace this client's ambient context on the server; throws on transports without a client context channel. */\n setContext: (value: Record<string, unknown>) => Promise<unknown>;\n };\n\n export type ConnectionState =\n | { status: \"connecting\" }\n | { status: \"live\" }\n | {\n status: \"reconnecting\";\n cause: \"dropped\" | \"evicted\" | \"error\";\n attempt: number;\n nextRetryAt: number;\n lastError?: string;\n message?: string;\n }\n | { status: \"gone\"; message?: string; payload?: unknown };\n export type Connection = ConnectionState & { reconnect: () => void };\n export type ConnectionStatus = Connection[\"status\"];\n\n /**\n * Mutate the draft in place; the client records the next state plus the exact\n * changed paths. Re-run against the new base on every rebase.\n */\n export type Optimistic<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = (\n draft: TState,\n command: Statewire.Command<TCommands>,\n ctx: OptimisticContext,\n ) => void;\n\n export type OptimisticContext = {\n ts: number;\n seq: number;\n clientId?: string;\n };\n\n /**\n * fate: \"aborted\" = never sent, \"rejected\" = the handler refused,\n * \"unknown-command\" = no such command, \"invalid-params\" = the args did not\n * match the command's schema, \"lost\" = sent but never covered by an ack,\n * \"crashed\" = acked but the processing died, \"result-unavailable\" = acked\n * but the result is gone.\n * cause is which channel settled it: \"server\" = the server's answer;\n * the rest are channel deaths the client settled locally.\n */\n export type SendFailure =\n | { fate: \"rejected\"; cause: \"server\"; message: string; payload?: unknown }\n | { fate: \"unknown-command\"; cause: \"server\" }\n | { fate: \"invalid-params\"; cause: \"server\"; message: string }\n | { fate: \"crashed\"; cause: \"server\" }\n | {\n fate: \"result-unavailable\";\n cause:\n | \"server\"\n | \"unmount\"\n | \"stream-gone\"\n | \"session-lost\"\n | \"superseded\";\n }\n | {\n fate: \"aborted\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\" | \"superseded\";\n predecessorLost?: true;\n }\n | {\n fate: \"lost\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\" | \"superseded\";\n };\n\n /**\n * A pending command offered for replay while the session is being evicted.\n * Return \"replay\" to re-enqueue it under the rotated client id instead of\n * settling it as a session-lost failure.\n */\n export type SessionLostCommand = {\n method: string;\n params: unknown[];\n seq: number;\n };\n\n export type SSEOptions = StatewireSessionStoreOptions & {\n url: string;\n headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);\n fetch?: typeof globalThis.fetch;\n onChange?: (connection: Connection) => void;\n onCommandSessionLost?: (command: SessionLostCommand) => \"replay\" | void;\n };\n\n export type WSOptions = StatewireSessionStoreOptions & {\n url: string;\n protocols?: string | string[];\n query?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n webSocket?: new (url: string, protocols?: string | string[]) => WebSocket;\n onChange?: (connection: Connection) => void;\n onCommandSessionLost?: (command: SessionLostCommand) => \"replay\" | void;\n };\n}\n"],"mappings":";;;;AAYA,MAAaa,gBAKXE,YAC+C;CAC/C,MAAMO,SAASrB,OAAOc,OAAO;CAC7BO,OAAOC,UAAUR;CAEjB,MAAMS,QAAQjB,YAAYQ,QAAQU,SAAS;CAC3C,MAAMC,OAAOhB,uBAAuBY,OAAOC,SAASC,KAAK;CACzD,MAAM,CAACG,aAAazB,gBACX0B,aACLF,KAAKC,gBAAgBtB,aAAauB,QAAQ,CAAC,CAC/C;CAEA,MAAMC,QAAQ1B,qBACZwB,WACAD,KAAKI,UACLJ,KAAKI,QACP;CACA,MAAMC,aAAaP,MAAMO,cAActB;CAEvC,OAAOT,eACE;EACL6B;EACAC,UAAUJ,KAAKI;EACfC;EACAC,UAAUN,KAAKM;EACfE,aAAaC,iBAA0C;GACrD,IAAI,CAACX,MAAMU,YACT,MAAM,IAAIG,MACR,yDACF;GAEF,OAAOb,MAAMU,WAAWC,YAAY;EACtC;CACF,IACA;EAACN;EAAOE;EAAYL;EAAMF;CAAK,CACjC;AACF;AAEA,MAAac,qBAKXvB,YAEAT,SAASO,YAAuC,CAAC,CAACE,OAAO;AAE3D,IAAaC,kBAAb,MAIE;CACA;CACA,WAAmB;CAEnByB,YAAY1B,SAA6D;EACvE,MAAM2B,mBAAmB7B,aAAwCE,OAAO;EACxE,KAAKwB,OAAOnC,cAAcsC,UAAU;CACtC;CAEA,IAAIb,QAAgB;EAClB,OAAO,KAAKU,KAAKI,SAAS,CAAC,CAACb,SAAS;CACvC;CAEA,IAAIC,aAAyC;EAC3C,OAAO,KAAKQ,KAAKI,SAAS,CAAC,CAACZ;CAC9B;CAEA,IAAIC,WAA+C;EACjD,OAAO,KAAKO,KAAKI,SAAS,CAAC,CAACX;CAC9B;CAEAE,WAAWV,OAAkD;EAC3D,OAAO,KAAKe,KAAKI,SAAS,CAAC,CAACT,WAAWV,KAAK;CAC9C;CAEAG,UAAUC,UAAkC;EAC1C,OAAO,KAAKW,KAAKZ,UAAUC,QAAQ;CACrC;CAEAkB,UAAgB;EACd,IAAI,KAAKN,UAAU;EACnB,KAAKA,WAAW;EAChB,KAAKD,KAAKQ,QAAQ;CACpB;AACF"}
@@ -1,12 +1,12 @@
1
- import { StatewireSessionStoreOptions } from "../client/sessionStore.js";
2
1
  import { StatewireClient } from "../statewire.js";
3
2
  import { Statewire } from "../types.js";
4
3
  import { RemoteLink, StatewireRemoteProps } from "./sse.js";
4
+ import { StatewireSessionOptions } from "../client/session.js";
5
5
  //#region src/transports/remote.d.ts
6
6
  declare const useStatewireRemote: <TOptions extends {
7
7
  url: string;
8
8
  onChange?: (connection: StatewireClient.Connection) => void;
9
- } & StatewireSessionStoreOptions>(options: TOptions, useLink: (options: TOptions, ctx: StatewireRemoteProps) => RemoteLink) => Statewire<any, any, any>;
9
+ } & StatewireSessionOptions>(options: TOptions, useLink: (options: TOptions, ctx: StatewireRemoteProps) => RemoteLink) => Statewire<any, any, any>;
10
10
  //#endregion
11
11
  export { useStatewireRemote };
12
12
  //# sourceMappingURL=remote.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"remote.d.ts","names":[],"sources":["../../src/transports/remote.ts"],"mappings":";;;;;cAYa,qBACX;EACE;EACA,YAAY,YAAY,gBAAgB;IACtC,8BAEJ,SAAS,UACT,UAAU,SAAS,UAAU,KAAK,yBAAyB,eAC1D"}
1
+ {"version":3,"file":"remote.d.ts","names":[],"sources":["../../src/transports/remote.ts"],"mappings":";;;;;cAYa,qBACX;EACE;EACA,YAAY,YAAY,gBAAgB;IACtC,yBAEJ,SAAS,UACT,UAAU,SAAS,UAAU,KAAK,yBAAyB,eAC1D"}
@@ -4,7 +4,10 @@ import { useEffect } from "@assistant-ui/tap/react-shim";
4
4
  //#region src/transports/remote.ts
5
5
  const useStatewireRemote = (options, useLink) => {
6
6
  const emit = useStatewireEmit();
7
- const s = useStatewireSession({ sessionStore: options.sessionStore }, emit);
7
+ const s = useStatewireSession({
8
+ sessionStore: options.sessionStore,
9
+ onCommandSessionLost: options.onCommandSessionLost
10
+ }, emit);
8
11
  const conn = useLink(options, s.ctx);
9
12
  useEffect(() => setTransport(s, conn), [s, conn]);
10
13
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"remote.js","names":["useEffect","useStatewireEmit","Statewire","setContext","setTransport","useStatewireSession","StatewireSessionStoreOptions","StatewireClient","RemoteLink","StatewireRemoteProps","useStatewireRemote","url","onChange","connection","Connection","options","TOptions","useLink","ctx","emit","s","sessionStore","conn","state","canonical","commands","source","value"],"sources":["../../src/transports/remote.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport { useStatewireEmit } from \"../host/host\";\nimport type { Statewire } from \"../types\";\nimport {\n setContext,\n setTransport,\n useStatewireSession,\n} from \"../client/session\";\nimport type { StatewireSessionStoreOptions } from \"../client/sessionStore\";\nimport type { StatewireClient } from \"../statewire\";\nimport type { RemoteLink, StatewireRemoteProps } from \"./sse\";\n\nexport const useStatewireRemote = <\n TOptions extends {\n url: string;\n onChange?: (connection: StatewireClient.Connection) => void;\n } & StatewireSessionStoreOptions,\n>(\n options: TOptions,\n useLink: (options: TOptions, ctx: StatewireRemoteProps) => RemoteLink,\n): Statewire<any, any, any> => {\n const emit = useStatewireEmit<any>();\n const s = useStatewireSession({ sessionStore: options.sessionStore }, emit);\n const conn = useLink(options, s.ctx);\n useEffect(() => setTransport(s, conn), [s, conn]);\n\n return {\n state: s.canonical,\n commands: s.commands,\n emit,\n connection: conn.connection,\n source: s.source,\n setContext: (value) => setContext(s, value),\n };\n};\n"],"mappings":";;;;AAYA,MAAaU,sBAMXK,SACAE,YAC6B;CAC7B,MAAME,OAAOlB,iBAAsB;CACnC,MAAMmB,IAAIf,oBAAoB,EAAEgB,cAAcN,QAAQM,aAAa,GAAGF,IAAI;CAC1E,MAAMG,OAAOL,QAAQF,SAASK,EAAEF,GAAG;CACnClB,gBAAgBI,aAAagB,GAAGE,IAAI,GAAG,CAACF,GAAGE,IAAI,CAAC;CAEhD,OAAO;EACLC,OAAOH,EAAEI;EACTC,UAAUL,EAAEK;EACZN;EACAN,YAAYS,KAAKT;EACjBa,QAAQN,EAAEM;EACVvB,aAAawB,UAAUxB,WAAWiB,GAAGO,KAAK;CAC5C;AACF"}
1
+ {"version":3,"file":"remote.js","names":["useEffect","useStatewireEmit","Statewire","setContext","setTransport","useStatewireSession","StatewireSessionOptions","StatewireClient","RemoteLink","StatewireRemoteProps","useStatewireRemote","url","onChange","connection","Connection","options","TOptions","useLink","ctx","emit","s","sessionStore","onCommandSessionLost","conn","state","canonical","commands","source","value"],"sources":["../../src/transports/remote.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport { useStatewireEmit } from \"../host/host\";\nimport type { Statewire } from \"../types\";\nimport {\n setContext,\n setTransport,\n useStatewireSession,\n} from \"../client/session\";\nimport type { StatewireSessionOptions } from \"../client/session\";\nimport type { StatewireClient } from \"../statewire\";\nimport type { RemoteLink, StatewireRemoteProps } from \"./sse\";\n\nexport const useStatewireRemote = <\n TOptions extends {\n url: string;\n onChange?: (connection: StatewireClient.Connection) => void;\n } & StatewireSessionOptions,\n>(\n options: TOptions,\n useLink: (options: TOptions, ctx: StatewireRemoteProps) => RemoteLink,\n): Statewire<any, any, any> => {\n const emit = useStatewireEmit<any>();\n const s = useStatewireSession(\n {\n sessionStore: options.sessionStore,\n onCommandSessionLost: options.onCommandSessionLost,\n },\n emit,\n );\n const conn = useLink(options, s.ctx);\n useEffect(() => setTransport(s, conn), [s, conn]);\n\n return {\n state: s.canonical,\n commands: s.commands,\n emit,\n connection: conn.connection,\n source: s.source,\n setContext: (value) => setContext(s, value),\n };\n};\n"],"mappings":";;;;AAYA,MAAaU,sBAMXK,SACAE,YAC6B;CAC7B,MAAME,OAAOlB,iBAAsB;CACnC,MAAMmB,IAAIf,oBACR;EACEgB,cAAcN,QAAQM;EACtBC,sBAAsBP,QAAQO;CAChC,GACAH,IACF;CACA,MAAMI,OAAON,QAAQF,SAASK,EAAEF,GAAG;CACnClB,gBAAgBI,aAAagB,GAAGG,IAAI,GAAG,CAACH,GAAGG,IAAI,CAAC;CAEhD,OAAO;EACLC,OAAOJ,EAAEK;EACTC,UAAUN,EAAEM;EACZP;EACAN,YAAYU,KAAKV;EACjBc,QAAQP,EAAEO;EACVxB,aAAayB,UAAUzB,WAAWiB,GAAGQ,KAAK;CAC5C;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "statewire",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "State over the wire: snapshot-first delta streaming and command submission for any state and any command",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -147,6 +147,12 @@ const rejectedSend = (
147
147
 
148
148
  type AttachState = "detached" | "attached" | "finished";
149
149
 
150
+ export type StatewireSessionOptions = StatewireSessionStoreOptions & {
151
+ onCommandSessionLost?:
152
+ | ((command: StatewireClient.SessionLostCommand) => "replay" | void)
153
+ | undefined;
154
+ };
155
+
150
156
  type Session = {
151
157
  record: StatewireSessionRecord;
152
158
  ctx: StatewireRemoteProps;
@@ -166,13 +172,14 @@ type Session = {
166
172
  transport: RemoteLink | null;
167
173
  waiters: (() => void)[];
168
174
  lifecycle: CommandsListener | null;
175
+ onCommandSessionLost: StatewireSessionOptions["onCommandSessionLost"];
169
176
  notify: () => void;
170
177
  emitEvent: (event: unknown) => void;
171
178
  send: (command: StatewireWireCommand) => Promise<unknown>;
172
179
  commands: Record<string, (...params: unknown[]) => Promise<unknown>>;
173
180
  };
174
181
 
175
- const createSession = (options: StatewireSessionStoreOptions): Session => {
182
+ const createSession = (options: StatewireSessionOptions): Session => {
176
183
  const store = options.sessionStore ?? createMemorySessionStore();
177
184
  const stored = store.load();
178
185
  const record = stored ?? { clientId: newClientId(), nextSeq: 1 };
@@ -212,6 +219,7 @@ const createSession = (options: StatewireSessionStoreOptions): Session => {
212
219
  transport: null,
213
220
  waiters: [],
214
221
  lifecycle: null,
222
+ onCommandSessionLost: options.onCommandSessionLost,
215
223
  notify: () => {},
216
224
  emitEvent: () => {},
217
225
  send: (command) => sendCommand(s, command),
@@ -274,10 +282,12 @@ const settleAll = (
274
282
  if (state.at === "awaiting-result")
275
283
  return { fate: "result-unavailable", cause };
276
284
  if (state.at === "queued") {
277
- const cascades = batch.some(
285
+ const predecessorLost = batch.some(
278
286
  (p) => p.seq < entry.seq && p.state.at !== "queued",
279
287
  );
280
- return { fate: "aborted", cause: cascades ? "cascade" : cause };
288
+ return predecessorLost
289
+ ? { fate: "aborted", cause, predecessorLost }
290
+ : { fate: "aborted", cause };
281
291
  }
282
292
  return { fate: "lost", cause };
283
293
  };
@@ -288,6 +298,27 @@ const settleAll = (
288
298
  }
289
299
  };
290
300
 
301
+ // Offers every unsettled real command for replay, in seq order. Not caught:
302
+ // a throwing callback aborts the eviction before any entry settles; the
303
+ // attach retry evicts again and re-offers.
304
+ const claimReplays = (s: Session): Pending[] => {
305
+ const handler = s.onCommandSessionLost;
306
+ if (!handler) return [];
307
+ const claimed = new Set<Pending>();
308
+ for (const entry of s.pending) {
309
+ if (!entry.inv || isResolved(entry.state)) continue;
310
+ const decision = handler({
311
+ method: entry.inv.method,
312
+ params: [...entry.inv.params],
313
+ seq: entry.seq,
314
+ });
315
+ if (decision === "replay") claimed.add(entry);
316
+ }
317
+ if (claimed.size === 0) return [];
318
+ s.pending = s.pending.filter((p) => !claimed.has(p));
319
+ return [...claimed];
320
+ };
321
+
291
322
  const evict = (
292
323
  s: Session,
293
324
  verdict?: {
@@ -295,6 +326,7 @@ const evict = (
295
326
  failure: StatewireClient.SendFailure;
296
327
  },
297
328
  ) => {
329
+ const replays = claimReplays(s);
298
330
  settleAll(s, "evicted", verdict);
299
331
  s.record = { clientId: newClientId(), nextSeq: 1 };
300
332
  s.store.save(s.record);
@@ -302,6 +334,10 @@ const evict = (
302
334
  // The server dropped this client's record and its stored context with it:
303
335
  // the new session opens with a fresh full root replace.
304
336
  if (s.context !== undefined) void s.send({ context: s.context });
337
+ for (const old of replays) {
338
+ if (old.inv) s.lifecycle?.onCovered?.(old.inv);
339
+ enqueue(s, old.command, old.resolve, old.reject);
340
+ }
305
341
  publish(s);
306
342
  wake(s);
307
343
  };
@@ -645,16 +681,12 @@ const setTransport = (s: Session, transport: RemoteLink) => {
645
681
  wake(s);
646
682
  };
647
683
 
648
- const sendCommand = (
684
+ const enqueue = (
649
685
  s: Session,
650
686
  command: StatewireWireCommand,
651
- ): Promise<unknown> => {
652
- if (!s.running) {
653
- return rejectedSend({ fate: "aborted", cause: "unmount" });
654
- }
655
- if (isFinished(s)) {
656
- return rejectedSend({ fate: "aborted", cause: "stream-gone" });
657
- }
687
+ resolve: (payload: unknown) => void,
688
+ reject: (error: StatewireSendError) => void,
689
+ ) => {
658
690
  const seq = s.record.nextSeq;
659
691
  s.record = { ...s.record, nextSeq: seq + 1 };
660
692
  s.store.save(s.record);
@@ -672,17 +704,28 @@ const sendCommand = (
672
704
  clientId: s.record.clientId,
673
705
  },
674
706
  state: { at: "queued", pendingMarked: false },
675
- resolve: () => {},
676
- reject: () => {},
707
+ resolve,
708
+ reject,
677
709
  };
678
- const result = new Promise<unknown>((resolve, reject) => {
679
- entry.resolve = resolve;
680
- entry.reject = reject;
681
- });
682
- result.catch(() => {});
683
710
  s.pending.push(entry);
684
711
  if (entry.inv) s.lifecycle?.onStart(entry.inv);
685
712
  void pump(s);
713
+ };
714
+
715
+ const sendCommand = (
716
+ s: Session,
717
+ command: StatewireWireCommand,
718
+ ): Promise<unknown> => {
719
+ if (!s.running) {
720
+ return rejectedSend({ fate: "aborted", cause: "unmount" });
721
+ }
722
+ if (isFinished(s)) {
723
+ return rejectedSend({ fate: "aborted", cause: "stream-gone" });
724
+ }
725
+ const result = new Promise<unknown>((resolve, reject) => {
726
+ enqueue(s, command, resolve, reject);
727
+ });
728
+ result.catch(() => {});
686
729
  return result;
687
730
  };
688
731
 
@@ -698,10 +741,11 @@ const setContext = (
698
741
  };
699
742
 
700
743
  export const useStatewireSession = (
701
- options: StatewireSessionStoreOptions,
744
+ options: StatewireSessionOptions,
702
745
  emit: (event: unknown) => void,
703
746
  ): Session => {
704
747
  const [s] = useState(() => createSession(options));
748
+ s.onCommandSessionLost = options.onCommandSessionLost;
705
749
  useEffect(() => {
706
750
  s.emitEvent = emit;
707
751
  return () => {
package/src/host/host.ts CHANGED
@@ -250,6 +250,9 @@ const STALL_WARN_MS = 5000;
250
250
 
251
251
  // In-process dispatch has no wire client: never any ambient context, trivially attached.
252
252
  const LOCAL_CALLER: StatewireHost.Caller = Object.freeze({
253
+ get clientId(): string {
254
+ throw new Error("statewire: in-process dispatch has no client id");
255
+ },
253
256
  context: null,
254
257
  isConnected: true,
255
258
  });
@@ -387,6 +387,7 @@ const createSocketHost = (
387
387
  ) => settleResponse(rec, adm, responseOf(adm, outcome));
388
388
 
389
389
  const callerOf = (rec: ClientRec): StatewireHost.Caller => ({
390
+ clientId: rec.id,
390
391
  get context() {
391
392
  return rec.context;
392
393
  },
package/src/host.ts CHANGED
@@ -48,11 +48,13 @@ export namespace StatewireHost {
48
48
 
49
49
  /**
50
50
  * The submitting client, bound as `this` of a command handler. Holdable
51
- * across a long run: `context` reads the client's current ambient context
52
- * live (null until the client sends one), `isConnected` reports whether the
53
- * client has an active stream attachment.
51
+ * across a long run: `clientId` is the client's `Statewire-Client-Id`
52
+ * (in-process dispatch has none — reading it throws), `context` reads the
53
+ * client's current ambient context live (null until the client sends one),
54
+ * `isConnected` reports whether the client has an active stream attachment.
54
55
  */
55
56
  export type Caller = {
57
+ readonly clientId: string;
56
58
  readonly context: Record<string, unknown> | null;
57
59
  readonly isConnected: boolean;
58
60
  };
package/src/statewire.ts CHANGED
@@ -194,23 +194,31 @@ export namespace StatewireClient {
194
194
  }
195
195
  | {
196
196
  fate: "aborted";
197
- cause:
198
- | "cascade"
199
- | "unmount"
200
- | "stream-gone"
201
- | "session-lost"
202
- | "superseded";
197
+ cause: "unmount" | "stream-gone" | "session-lost" | "superseded";
198
+ predecessorLost?: true;
203
199
  }
204
200
  | {
205
201
  fate: "lost";
206
202
  cause: "unmount" | "stream-gone" | "session-lost" | "superseded";
207
203
  };
208
204
 
205
+ /**
206
+ * A pending command offered for replay while the session is being evicted.
207
+ * Return "replay" to re-enqueue it under the rotated client id instead of
208
+ * settling it as a session-lost failure.
209
+ */
210
+ export type SessionLostCommand = {
211
+ method: string;
212
+ params: unknown[];
213
+ seq: number;
214
+ };
215
+
209
216
  export type SSEOptions = StatewireSessionStoreOptions & {
210
217
  url: string;
211
218
  headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
212
219
  fetch?: typeof globalThis.fetch;
213
220
  onChange?: (connection: Connection) => void;
221
+ onCommandSessionLost?: (command: SessionLostCommand) => "replay" | void;
214
222
  };
215
223
 
216
224
  export type WSOptions = StatewireSessionStoreOptions & {
@@ -221,5 +229,6 @@ export namespace StatewireClient {
221
229
  | (() => Record<string, string> | Promise<Record<string, string>>);
222
230
  webSocket?: new (url: string, protocols?: string | string[]) => WebSocket;
223
231
  onChange?: (connection: Connection) => void;
232
+ onCommandSessionLost?: (command: SessionLostCommand) => "replay" | void;
224
233
  };
225
234
  }
@@ -6,7 +6,7 @@ import {
6
6
  setTransport,
7
7
  useStatewireSession,
8
8
  } from "../client/session";
9
- import type { StatewireSessionStoreOptions } from "../client/sessionStore";
9
+ import type { StatewireSessionOptions } from "../client/session";
10
10
  import type { StatewireClient } from "../statewire";
11
11
  import type { RemoteLink, StatewireRemoteProps } from "./sse";
12
12
 
@@ -14,13 +14,19 @@ export const useStatewireRemote = <
14
14
  TOptions extends {
15
15
  url: string;
16
16
  onChange?: (connection: StatewireClient.Connection) => void;
17
- } & StatewireSessionStoreOptions,
17
+ } & StatewireSessionOptions,
18
18
  >(
19
19
  options: TOptions,
20
20
  useLink: (options: TOptions, ctx: StatewireRemoteProps) => RemoteLink,
21
21
  ): Statewire<any, any, any> => {
22
22
  const emit = useStatewireEmit<any>();
23
- const s = useStatewireSession({ sessionStore: options.sessionStore }, emit);
23
+ const s = useStatewireSession(
24
+ {
25
+ sessionStore: options.sessionStore,
26
+ onCommandSessionLost: options.onCommandSessionLost,
27
+ },
28
+ emit,
29
+ );
24
30
  const conn = useLink(options, s.ctx);
25
31
  useEffect(() => setTransport(s, conn), [s, conn]);
26
32