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 +3 -1
- package/dist/client/session.d.ts +7 -2
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +51 -20
- package/dist/client/session.js.map +1 -1
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js +3 -0
- package/dist/host/host.js.map +1 -1
- package/dist/host/socket.d.ts.map +1 -1
- package/dist/host/socket.js +1 -0
- package/dist/host/socket.js.map +1 -1
- package/dist/host.d.ts +5 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/statewire.d.ts +14 -1
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js.map +1 -1
- package/dist/transports/remote.d.ts +2 -2
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/transports/remote.js +4 -1
- package/dist/transports/remote.js.map +1 -1
- package/package.json +1 -1
- package/src/client/session.ts +63 -19
- package/src/host/host.ts +3 -0
- package/src/host/socket.ts +1 -0
- package/src/host.ts +5 -3
- package/src/statewire.ts +15 -6
- package/src/transports/remote.ts +9 -3
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"`
|
|
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
|
|
package/dist/client/session.d.ts
CHANGED
|
@@ -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:
|
|
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":"
|
|
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"}
|
package/dist/client/session.js
CHANGED
|
@@ -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")
|
|
174
|
-
|
|
175
|
-
|
|
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
|
|
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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"}
|
package/dist/host/host.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
});
|
package/dist/host/host.js.map
CHANGED
|
@@ -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":";;;;;;;AAWA,MAAMa,cACJ,OAAOC,YAAY,eAAeA,QAAQC,IAAI,gBAAgB;AAIhE,MAAMC,6BAAa,IAAIC,QAAyC;;AAGhE,MAAaE,gBACXC,UACwCJ,WAAWK,IAAID,KAAK;AAa9D,MAAaa,qBACXC,SACsD;CACtD,MAAM,GAAGE,cAAcnC,SAAS,CAAC;CACjC,MAAM,CAACoC,QAAQpC,eAA0B;EACvC,MAAMqC,eAAe;GACnB,IAAIC,EAAEf,OAAOe,EAAEhB,UAAUgB,EAAEf,MAAMgB,MAAM;GACvC,OAAOD,EAAEhB;EACX;EACA,MAAMgB,IAAe;GACnBhB,SAASpB,cAAc+B,KAAK,CAAC;GAC7BV,OAAO;GACPC,SAAS,CAAC,CAAA,CAAE;GACZC,MAAM;GACNE,UAAU;GACVI,YAAYI,YAAYK,MAAMA,IAAI,CAAC;GACnCZ,KAAK;IACHa,QAAQC,QAAQ;KACd,MAAMC,SAAS1C,SAASoC,OAAO,GAAGK,GAAG;KACrC,IAAIC,OAAOC,OAAOC,SAAS,GACzB,MAAM,IAAIC,MACR,iEACF;KAEFR,EAAEhB,UAAUqB,OAAOxB;KACnBmB,EAAEf,OAAOwB,WAAW;KACpBT,EAAEd,QAAQwB,KAAK,GAAGL,OAAOM,YAAY;KACrC,IAAIX,EAAEb,MAAM,KAAK,MAAMC,MAAMgB,KAAKJ,EAAEb,KAAKC,EAAc;KACvDY,EAAEP,KAAK;KACPO,EAAEX,WAAW;IACf;IACAuB,UAAUb;GACZ;GACAP,MAAM;IACJoB,UAAUb;IACVc,UAAUC,aAAa;KACrBd,EAAEb,OAAO2B;IACX;IACAC,cAAcD,eAAa;KACzBd,EAAEX,WAAWyB;IACf;IACAE,mBAAmB;KACjB,MAAM9B,UAAUc,EAAEd;KAClBc,EAAEd,UAAU,CAAA;KACZ,OAAOA;IACT;GACF;EACF;EACA,OAAOc;CACT,CAAC;CAED,IAAIiB,QAAQnB,KAAKR,IAAIsB,SAAS;CAC9B,IAAI,OAAOK,UAAU,YAAYA,UAAU,MAAM;EAC/CnB,KAAKb,UAAUlB,iBAAyB;GACtC6C,gBAAgBd,KAAKd;GACrBkC,SAAS9B,IAAI+B,gBAAgB;IAC3BrB,KAAKZ,QAAQwB,KAAKS,WAAW;IAC7BrB,KAAKX,OAAOC,EAAE;IACdU,KAAKL,KAAK;IACVK,KAAKT,WAAW;GAClB;EACF,CAAC;EACDZ,WAAW2C,IAAItB,KAAKb,MAAMoC,OAAOvB,KAAKN,IAAI;EAC1CyB,QAAQnB,KAAKb,MAAMoC;CACrB;CACA,OAAO,CAACJ,OAAiBnB,KAAKR,GAAqC;AACrE;AAmBA,MAAM2C,kCAAkB,IAAIvD,QAAgC;;;;;;;;AAS5D,MAAawD,mBAAmBC,OAAOC,QAEnCC,aAG+B;CAC/B,MAAMxD,QAAwB;EAAEkD,OAAO;EAAOF,SAAS;EAAMG,MAAM;CAAK;CACxE,MAAMW,YAAYR,OAAOS,OACvB,CAAC,CACH;CACAX,gBAAgBb,IAAIuB,WAAW9D,KAAK;CACpC,MAAMgE,UAAUhB,YAA8B;EAC5C,IAAIhD,MAAMgD,SAAS;EACnBhD,MAAMgD,UAAUA;EAChBhD,MAAMkD,QAAQ;EACdlD,MAAMmD,MAAMJ,SAASC,OAAO;CAC9B;CACA,MAAMU,YAAY;EAChB,IAAI1D,MAAMkD,OAAO;EACjBlD,MAAMkD,QAAQ;EACdlD,MAAMmD,MAAML,MAAM;CACpB;CACA,CAAM,YAAY;EAChB,IAAI;GACFkB,OAAO;IAAErB,MAAM;IAAYP,OAAO,MAAMoB,SAAS,EAAEE,IAAI,CAAC;GAAE,CAAC;EAC7D,SAASd,OAAO;GACdoB,OAAO;IAAErB,MAAM;IAAYC;GAAM,CAAC;EACpC;CACF,EAAA,CAAG;CACH,OAAOkB;AACT,GACA,EACEG,qBAGK;CACH,IAAIE;CACJ,IAAIC;CACJ,MAAMC,UAAU,IAAIC,SAAYC,KAAKC,QAAQ;EAC3CL,UAAUI;EACVH,SAASI;CACX,CAAC;CACD,IAAId;CAKJ,OAAO,CAJWL,kBAAqB,EAAEK,KAAKe,QAAQ;EACpDf,MAAMe;EACN,OAAOJ;CACT,CACQP,GAAW;EAAEJ,WAAWA,IAAI;EAAGS;EAASC;CAAO,CAAC;AAC1D,EACF,CACF;AAqCA,MAAM2B,gCAAgB,IAAIlG,QAA8B;AAExD,MAAamG,mBAAmBC,aAC9BF,cAAc9F,IAAIgG,QAAQ;AAE5B,MAAaC,oBAAoBD,UAAkBtF,SAAuB;CACxEoF,cAAcxD,IAAI0D,UAAUtF,IAAI;AAClC;AA0BA,MAAMqG,gBAAgB;AAGtB,MAAMC,eAAqC3D,OAAOS,OAAO;CACvDmD,SAAS;CACTC,aAAa;AACf,CAAC;AAED,MAAMC,eAAexE,UACnBA,iBAAiBtD,qBACbsD,QACA,IAAItD,mBAAmB;CACrB+H,MAAM;CACNC,OAAO;CACPC,SAAS3E,iBAAiBjB,QAAQiB,MAAM2E,UAAUC,OAAO5E,KAAK;CAC9D,GAAIA,iBAAiB3D,mBACnB2D,MAAMqC,YAAYwC,KAAAA,KAAa,EAAExC,SAASrC,MAAMqC,QAAQ;AAC5D,CAAC;AAEP,MAAMyC,iBAAiBzG,MAAoBmE,QAA0B;CACnE,IAAIA,IAAIlC,OAAO;CACfkC,IAAIlC,QAAQ;CACZjC,KAAKgB,UAAUa,MAAMsC,GAAG;CACxBA,IAAIgB,QAAQtD,QAAQ;AACtB;AAEA,MAAM6E,oBACJ1G,MACAmE,KACAU,MACA9C,YACG;CACH0E,cAAczG,MAAMmE,GAAG;CACvB,IAAIpC,QAAQL,SAAS,YAAY;EAC/B,MAAM4C,UACJvC,QAAQZ,UAAUqF,KAAAA,IACd,EAAE9E,MAAM,WAAW,IACnB;GAAEA,MAAM;GAAYsC,SAASlG,cAAciE,QAAQZ,KAAK;EAAE;EAChEnB,KAAKgB,UAAUc,SAASqC,KAAKG,OAAO;EACpCH,IAAIgB,QAAQrD,WAAWwC,OAAO;EAC9BO,KAAK3B,QAAQoB,QAAQN,OAAO;CAC9B,OAAO;EACL,MAAMrC,QAAQwE,YAAYpE,QAAQJ,KAAK;EACvC,MAAM2C,UAA2B;GAAE5C,MAAM;GAAYC;EAAM;EAC3D3B,KAAKgB,UAAUc,SAASqC,KAAKG,OAAO;EACpCH,IAAIgB,QAAQrD,WAAWwC,OAAO;EAC9BO,KAAK1B,OAAOxB,KAAK;CACnB;AACF;AAEA,MAAMgF,QAAQ3G,SAAuB;CACnC,IAAIA,KAAK0F,WAAW,CAAC1F,KAAKsF,UAAU;EAClCsB,WAAW5G,IAAI;EACf;CACF;CACA,MAAM6E,OAAO7E,KAAKyF,MAAMoB,MAAM;CAC9B,IAAI,CAAChC,MAAM;CACX,MAAMiC,UAAU9G,KAAKsF,SAAST,KAAKnB;CACnC,IAAIoD,YAAYN,KAAAA,GAAW;EACzB,MAAM7E,QAAQ,IAAItD,mBAAmB;GACnC+H,MAAM;GACNC,OAAO;EACT,CAAC;EACDxB,KAAKM,QAAQrD,WAAW;GAAEJ,MAAM;GAAYC;EAAM,CAAC;EACnDkD,KAAK1B,OAAOxB,KAAK;EACjBgF,KAAK3G,IAAI;EACT;CACF;CACA,MAAMmE,MAAwB;EAC5BT,QAAQmB,KAAKnB;EACbC,QAAQkB,KAAKlB;EACbC,IAAIiB,KAAKjB;EACTC,KAAK,EAAE7D,KAAK6D;EACZ5B,OAAO;EACP,GAAI4C,KAAKM,WAAWqB,KAAAA,KAAa,EAAErB,QAAQN,KAAKM,OAAO;EACvD,GAAIN,KAAKM,QAAQrB,aAAa0C,KAAAA,KAAa,EACzC1C,UAAUe,KAAKM,OAAOrB,SACxB;CACF;CACA9D,KAAK0F,UAAU;CACf1F,KAAKgB,UAAUkD,QAAQC,GAAG;CAC1BnE,KAAKgB,UAAUoD,WAAWD,GAAG;CAC7BnE,KAAKgB,UAAUqD,YAAYF,GAAG;CAC9B,CAAM,YAAY;EAChB,IAAI;GACF,MAAMhD,QAAQ,MACZ2F,QACAjC,KAAKA,KAAKM,QAAQX,UAAUwB,cAAc,GAAGnB,KAAKlB,MAAM;GAC1D,MAAMd,YACJ,OAAO1B,UAAU,YAAYA,UAAU,OACnCgB,gBAAgBnD,IAAImC,KAAK,IACzBqF,KAAAA;GACN,IAAI3D,WAAW;IACb,IAAIA,UAAUZ,OAAOwE,cAAczG,MAAMmE,GAAG;IAC5C,IAAItB,UAAUd,SACZ2E,iBAAiB1G,MAAMmE,KAAKU,MAAMhC,UAAUd,OAAO;SAEnDc,UAAUX,OAAO;KACfL,aAAa4E,cAAczG,MAAMmE,GAAG;KACpCrC,WAAWC,YAAY2E,iBAAiB1G,MAAMmE,KAAKU,MAAM9C,OAAO;IAClE;GAEJ,OACE2E,iBAAiB1G,MAAMmE,KAAKU,MAAM;IAAEnD,MAAM;IAAYP;GAAM,CAAC;EAEjE,SAASQ,OAAO;GACd+E,iBAAiB1G,MAAMmE,KAAKU,MAAM;IAAEnD,MAAM;IAAYC;GAAM,CAAC;EAC/D,UAAU;GACR3B,KAAK0F,UAAU;GACfiB,KAAK3G,IAAI;EACX;CACF,EAAA,CAAG;AACL;AAEA,MAAM4G,cAAc5G,SAAuB;CACzC,IAAI,CAACxB,MAAM,KAAKwB,KAAK4F,eAAe,QAAQ5F,KAAKyF,MAAMhF,WAAW,GAAG;CACrE,IAAIT,KAAKsF,UAAU;CACnBtF,KAAK4F,aAAaE,iBAAiB;EACjC9F,KAAK4F,aAAa;EAClB,IAAI5F,KAAKsF,aAAa,QAAQtF,KAAKyF,MAAMhF,SAAS,KAAK,CAACT,KAAK2F,WAC3DoB,QAAQC,KACN,cAAchH,KAAKyF,MAAMhF,OAAM,oCAAqCsF,cAAa,sDACnF;CAEJ,GAAGA,aAAa;AAClB;AAEA,MAAakB,wBACXjC,aAKsE;CACtE,MAAM,CAACuC,SAAS3J,eAAe;EAC7B,MAAMoC,OAAqB;GACzBsF,UAAU;GACVG,OAAO,CAAA;GACPC,SAAS;GACT1E,UAAU;GACV6C,KAAK;GACL8B,WAAW;GACXC,YAAY;EACd;EACA,MAAMrE,QAAQrD,mBAGXwF,QAAQC,WACP,IAAIN,SAASH,SAASC,WAAW;GAC/B,IAAInD,KAAK2F,WAAW;IAClBxC,uBAAO,IAAIzC,MAAM,0CAA0C,CAAC;IAC5D;GACF;GACAV,KAAKyF,MAAM7E,KAAK;IACd8C;IACAC;IACAC,IAAI4D,KAAKC,IAAI;IACbvE;IACAC;GACF,CAAC;GACDwD,KAAK3G,IAAI;EACX,CAAC,CACL;EACA8E,cAAcxD,IAAIC,OAAO;GACvBoD,cAAc3D,aAAa;IACzBhB,KAAKgB,WAAWA;GAClB;GACA4D,SAASO,WACP,IAAI9B,SAASH,WAASC,aAAW;IAC/B,IAAInD,KAAK2F,WAAW;KAClBxC,yBAAO,IAAIzC,MAAM,0CAA0C,CAAC;KAC5D;IACF;IACAV,KAAKyF,MAAM7E,KAAK;KACd8C,QAAQyB,OAAOzB;KACfC,QAAQwB,OAAOxB;KACfC,IAAI4D,KAAKC,IAAI;KACbvE,SAAAA;KACAC,QAAAA;KACAgC;IACF,CAAC;IACDwB,KAAK3G,IAAI;GACX,CAAC;EACL,CAAC;EACD,OAAO;GAAEA;GAAMuB;EAAM;CACvB,CAAC;CAED5D,gBAAgB;EACd,MAAM,EAAEqC,MAAAA,WAASuH;EACjBvH,OAAK2F,YAAY;EACjB,aAAa;GACX3F,OAAK2F,YAAY;GACjB3F,OAAKsF,WAAW;GAChB,IAAItF,OAAK4F,eAAe,MAAM;IAC5B8B,aAAa1H,OAAK4F,UAAU;IAC5B5F,OAAK4F,aAAa;GACpB;GACA+B,qBAAqB;IACnB,IAAI,CAAC3H,OAAK2F,WAAW;IACrB,MAAMiC,SAAS5H,OAAKyF;IACpBzF,OAAKyF,QAAQ,CAAA;IACb,KAAK,MAAMZ,QAAQ+C,QACjB/C,KAAK1B,uBAAO,IAAIzC,MAAM,sBAAsB,CAAC;GAEjD,CAAC;EACH;CACF,GAAG,CAAC6G,KAAK,CAAC;CAEV5J,gBAAgB;EACd4J,MAAMvH,KAAKsF,WAAWN,YAAY;EAClC2B,KAAKY,MAAMvH,IAAI;CACjB,CAAC;CAED,OAAOuH,MAAMhG;AACf;AAQA,MAAM0G,4BAAY,IAAIrJ,QAA0B;AAEhD,MAAasJ,eAAeC,SAC1BA,QAAQF,UAAUjJ,IAAImJ,IAAI;AAE5B,MAAaC,yBAAmB;CAC9B,MAAA,CAAAb,SAAgB3J,SAASyK,KAcxB;CAAE,OACId;AAAK;AAhBkB,SAAAc,QAAA;CAE5B,MAAArI,OAAa,EAAA+H,MAAQ,KAA0C;CAC/D,MAAAI,QAAaH,UAAA;EACX,IAAI,CAAChI,KAAI+H,MACP,MAAM,IAAIrH,MAAM,oDAAoD;EAEtEV,KAAI+H,KAAMjK,cAAckK,KAAK,CAAC;CAAC;CAEjCC,UAAS3G,IAAK6G,MAAM,EAAAL,UACTC,SAAA;EACP/H,KAAI+H,OAAQA;CAAH,EAEb,CAAC;CAAC,OACKI;AAAI"}
|
|
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":";;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"socket.d.ts","names":[],"sources":["../../src/host/socket.ts"],"mappings":";;;;;;;;;;cAwlBa,sBACX,WAAW,gBAAgB,YAC3B,UAAU,sBAAsB,sBAC/B,sBAAsB"}
|
package/dist/host/socket.js
CHANGED
package/dist/host/socket.js.map
CHANGED
|
@@ -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: `
|
|
37
|
-
*
|
|
38
|
-
* client
|
|
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
|
};
|
package/dist/host.d.ts.map
CHANGED
|
@@ -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
|
|
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"}
|
package/dist/statewire.d.ts
CHANGED
|
@@ -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: "
|
|
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
|
package/dist/statewire.d.ts.map
CHANGED
|
@@ -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;;
|
|
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"}
|
package/dist/statewire.js.map
CHANGED
|
@@ -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
|
-
} &
|
|
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,
|
|
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({
|
|
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","
|
|
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
package/src/client/session.ts
CHANGED
|
@@ -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:
|
|
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
|
|
285
|
+
const predecessorLost = batch.some(
|
|
278
286
|
(p) => p.seq < entry.seq && p.state.at !== "queued",
|
|
279
287
|
);
|
|
280
|
-
return
|
|
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
|
|
684
|
+
const enqueue = (
|
|
649
685
|
s: Session,
|
|
650
686
|
command: StatewireWireCommand,
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
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:
|
|
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
|
});
|
package/src/host/socket.ts
CHANGED
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: `
|
|
52
|
-
*
|
|
53
|
-
* client
|
|
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
|
-
|
|
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
|
}
|
package/src/transports/remote.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
setTransport,
|
|
7
7
|
useStatewireSession,
|
|
8
8
|
} from "../client/session";
|
|
9
|
-
import type {
|
|
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
|
-
} &
|
|
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(
|
|
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
|
|