statewire 0.0.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/session.js +14 -13
- package/dist/client/session.js.map +1 -1
- package/dist/host/host.d.ts +2 -14
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js +41 -92
- package/dist/host/host.js.map +1 -1
- package/dist/host.d.ts +11 -17
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +2 -2
- package/dist/util/statePath.js +8 -6
- package/dist/util/statePath.js.map +1 -1
- package/package.json +1 -1
- package/src/host/host.ts +55 -113
- package/src/host.ts +9 -19
package/dist/client/session.js
CHANGED
|
@@ -48,18 +48,20 @@ const transition = (s, entry, next, outcome) => {
|
|
|
48
48
|
const covers = !isStateSettled(prev) && isStateSettled(next);
|
|
49
49
|
entry.state = next;
|
|
50
50
|
if (covers && entry.inv) s.lifecycle?.onCovered?.(entry.inv);
|
|
51
|
-
if (settles && outcome !== void 0)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
51
|
+
if (settles && outcome !== void 0) {
|
|
52
|
+
if (outcome.ok) {
|
|
53
|
+
entry.resolve(outcome.payload);
|
|
54
|
+
if (entry.inv) s.lifecycle?.onSettle(entry.inv, {
|
|
55
|
+
type: "accepted",
|
|
56
|
+
payload: outcome.payload
|
|
57
|
+
});
|
|
58
|
+
} else {
|
|
59
|
+
entry.reject(new StatewireSendError(outcome.failure));
|
|
60
|
+
if (entry.inv) s.lifecycle?.onSettle(entry.inv, {
|
|
61
|
+
type: "rejected",
|
|
62
|
+
error: new StatewireSendError(outcome.failure)
|
|
63
|
+
});
|
|
64
|
+
}
|
|
63
65
|
}
|
|
64
66
|
if (next.at === "done") s.pending = s.pending.filter((p) => p !== entry);
|
|
65
67
|
};
|
|
@@ -305,7 +307,6 @@ const applyResponse = (s, response) => {
|
|
|
305
307
|
cause: "server"
|
|
306
308
|
}
|
|
307
309
|
});
|
|
308
|
-
break;
|
|
309
310
|
}
|
|
310
311
|
};
|
|
311
312
|
const ingestOps = (s, ops) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.js","names":["useEffect","useState","applyOps","StatewirePath","StatewireResponse","bindCommandsHost","CommandsInvocation","CommandsListener","Statewire","StatewireClient","createMemorySessionStore","StatewireSessionRecord","StatewireSessionStore","StatewireSessionStoreOptions","retryDelay","createMethodProxy","randomId","StatewireSendError","RemoteLink","StatewireRemoteProps","StatewireSubmitResult","StatewireWireCommand","PendingState","at","pendingMarked","failedAttempts","posted","Pending","seq","command","inv","state","resolve","payload","reject","error","Outcome","ok","failure","SendFailure","isResolved","isStateSettled","inFlight","Error","legalTransitions","Record","queued","trying","done","transition","s","Session","entry","next","outcome","prev","includes","settles","undefined","covers","lifecycle","onCovered","onSettle","type","pending","filter","p","carried","Exclude","newClientId","rejectedSend","Promise","result","catch","AttachState","StatewireSessionOptions","onCommandSessionLost","SessionLostCommand","record","ctx","recordPersisted","store","lastAck","context","canonical","lastPublished","changed","source","StateSource","attach","attachEpoch","running","pumping","transport","waiters","notify","emitEvent","event","send","commands","params","createSession","options","sessionStore","stored","load","clientId","nextSeq","onSnapshot","args","handleSnapshot","onOps","handleOps","onFinish","handleFinish","snapshot","takeChanged","setOnChange","listener","sendCommand","method","setListener","publish","wake","waiter","waitForWake","r","push","sleepUntilWake","delayMs","clearTimeout","timer","setTimeout","w","settleAll","detach","verdict","cause","evicted","unmount","gone","superseded","const","batch","detachFailure","fate","predecessorLost","some","claimReplays","handler","claimed","Set","decision","add","size","has","evict","replays","save","old","enqueue","noteAcked","onAck","spliceAcked","settleVoid","protocolError","message","applyResponse","response","find","code","ingestOps","ops","applied","changedPaths","events","publishIngested","lastSeq","connection","reconnect","pump","res","ack","horizon","Math","max","reason","isFinished","sendable","epoch","onPosted","console","start","stop","setTransport","ts","Date","now","onStart","setContext","value","Array","isArray","useStatewireSession","emit"],"sources":["../../src/client/session.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { applyOps } from \"../wire/ops\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport type { StatewireResponse } from \"./connection\";\nimport { bindCommandsHost } from \"../host/host\";\nimport type { CommandsInvocation, CommandsListener } from \"../host/host\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireClient } from \"../statewire\";\nimport { createMemorySessionStore } from \"./sessionStore\";\nimport type {\n StatewireSessionRecord,\n StatewireSessionStore,\n StatewireSessionStoreOptions,\n} from \"./sessionStore\";\nimport { retryDelay } from \"./timing\";\nimport { createMethodProxy } from \"../util/methodProxy\";\nimport { randomId } from \"../util/randomId\";\nimport { StatewireSendError } from \"../types\";\nimport type {\n RemoteLink,\n StatewireRemoteProps,\n StatewireSubmitResult,\n StatewireWireCommand,\n} from \"../transports/sse\";\n\n/**\n * queued/trying/posted: overlay live, result pending. awaiting-result: acked\n * (overlay gone) but the result rides a late res; `posted: false` re-sends.\n * settled-live: result settled but the overlay stays until the covering ack.\n * done: removed from the queue.\n */\ntype PendingState =\n | { at: \"queued\"; pendingMarked: boolean }\n | { at: \"trying\"; pendingMarked: boolean; failedAttempts: number }\n | { at: \"posted\"; pendingMarked: boolean; failedAttempts: number }\n | { at: \"awaiting-result\"; posted: boolean; failedAttempts: number }\n | { at: \"settled-live\"; posted: boolean; failedAttempts: number }\n | { at: \"done\" };\n\ntype Pending = {\n readonly seq: number;\n readonly command: StatewireWireCommand;\n /** null for context submissions — they carry no command lifecycle. */\n readonly inv: CommandsInvocation | null;\n state: PendingState;\n resolve: (payload: unknown) => void;\n reject: (error: StatewireSendError) => void;\n};\n\ntype Outcome =\n | { ok: true; payload: unknown }\n | { ok: false; failure: StatewireClient.SendFailure };\n\nconst isResolved = (state: PendingState) =>\n state.at === \"settled-live\" || state.at === \"done\";\n\nconst isStateSettled = (state: PendingState) =>\n state.at === \"awaiting-result\" || state.at === \"done\";\n\n// The pump admits an entry as trying before sending; only the pump itself\n// requeues, and a done entry is never still pending.\nconst inFlight = (state: PendingState) => {\n if (state.at === \"queued\" || state.at === \"done\") {\n throw new Error(`statewire: ${state.at} entry observed after a send`);\n }\n return state;\n};\n\nconst legalTransitions: Record<PendingState[\"at\"], PendingState[\"at\"][]> = {\n queued: [\"queued\", \"trying\", \"awaiting-result\", \"settled-live\", \"done\"],\n // trying -> queued: an answered unknown-client proves the seq was never admitted.\n trying: [\n \"queued\",\n \"trying\",\n \"posted\",\n \"awaiting-result\",\n \"settled-live\",\n \"done\",\n ],\n posted: [\"posted\", \"trying\", \"awaiting-result\", \"settled-live\", \"done\"],\n \"awaiting-result\": [\"awaiting-result\", \"done\"],\n \"settled-live\": [\"settled-live\", \"done\"],\n done: [],\n};\n\nconst transition = (\n s: Session,\n entry: Pending,\n next: PendingState,\n outcome?: Outcome,\n) => {\n const prev = entry.state;\n if (!legalTransitions[prev.at].includes(next.at)) {\n throw new Error(\n `statewire: illegal pending transition ${prev.at} -> ${next.at}`,\n );\n }\n const settles = !isResolved(prev) && isResolved(next);\n if (settles && outcome === undefined) {\n throw new Error(\n `statewire: settling transition ${prev.at} -> ${next.at} without an outcome`,\n );\n }\n const covers = !isStateSettled(prev) && isStateSettled(next);\n entry.state = next;\n if (covers && entry.inv) s.lifecycle?.onCovered?.(entry.inv);\n if (settles && outcome !== undefined) {\n if (outcome.ok) {\n entry.resolve(outcome.payload);\n if (entry.inv)\n s.lifecycle?.onSettle(entry.inv, {\n type: \"accepted\",\n payload: outcome.payload,\n });\n } else {\n entry.reject(new StatewireSendError(outcome.failure));\n if (entry.inv)\n s.lifecycle?.onSettle(entry.inv, {\n type: \"rejected\",\n error: new StatewireSendError(outcome.failure),\n });\n }\n }\n if (next.at === \"done\") {\n s.pending = s.pending.filter((p) => p !== entry);\n }\n};\n\nconst carried = (\n state: Exclude<PendingState, { at: \"done\" }>,\n): { posted: boolean; failedAttempts: number } =>\n state.at === \"queued\"\n ? { posted: false, failedAttempts: 0 }\n : state.at === \"trying\" || state.at === \"posted\"\n ? { posted: state.at === \"posted\", failedAttempts: state.failedAttempts }\n : { posted: state.posted, failedAttempts: state.failedAttempts };\n\nconst newClientId = () => `c-${randomId()}`;\n\nconst rejectedSend = (\n failure: StatewireClient.SendFailure,\n): Promise<unknown> => {\n const result = Promise.reject<unknown>(new StatewireSendError(failure));\n result.catch(() => {});\n return result;\n};\n\ntype AttachState = \"detached\" | \"attached\" | \"finished\";\n\nexport type StatewireSessionOptions = StatewireSessionStoreOptions & {\n onCommandSessionLost?:\n | ((command: StatewireClient.SessionLostCommand) => \"replay\" | void)\n | undefined;\n};\n\ntype Session = {\n record: StatewireSessionRecord;\n ctx: StatewireRemoteProps;\n recordPersisted: boolean;\n store: StatewireSessionStore;\n lastAck: number;\n context: Record<string, unknown> | undefined;\n pending: Pending[];\n canonical: unknown;\n lastPublished: unknown;\n changed: StatewirePath[];\n source: Statewire.StateSource;\n attach: AttachState;\n attachEpoch: number;\n running: boolean;\n pumping: boolean;\n transport: RemoteLink | null;\n waiters: (() => void)[];\n lifecycle: CommandsListener | null;\n onCommandSessionLost: StatewireSessionOptions[\"onCommandSessionLost\"];\n notify: () => void;\n emitEvent: (event: unknown) => void;\n send: (command: StatewireWireCommand) => Promise<unknown>;\n commands: Record<string, (...params: unknown[]) => Promise<unknown>>;\n};\n\nconst createSession = (options: StatewireSessionOptions): Session => {\n const store = options.sessionStore ?? createMemorySessionStore();\n const stored = store.load();\n const record = stored ?? { clientId: newClientId(), nextSeq: 1 };\n const s: Session = {\n record,\n ctx: {\n get clientId() {\n return s.record.clientId;\n },\n onSnapshot: (args) => handleSnapshot(s, args),\n onOps: (args) => handleOps(s, args),\n onFinish: (args) => handleFinish(s, args),\n },\n recordPersisted: stored !== null,\n store,\n lastAck: -1,\n context: undefined,\n pending: [],\n canonical: undefined,\n lastPublished: undefined,\n changed: [],\n source: {\n snapshot: () => s.canonical,\n takeChanged: () => {\n const changed = s.changed;\n s.changed = [];\n return changed;\n },\n setOnChange: (listener) => {\n s.notify = listener ?? (() => {});\n },\n },\n attach: \"detached\",\n attachEpoch: 0,\n running: false,\n pumping: false,\n transport: null,\n waiters: [],\n lifecycle: null,\n onCommandSessionLost: options.onCommandSessionLost,\n notify: () => {},\n emitEvent: () => {},\n send: (command) => sendCommand(s, command),\n commands: createMethodProxy((method, params) => s.send({ method, params })),\n };\n bindCommandsHost(s.commands, {\n setListener: (listener) => {\n s.lifecycle = listener;\n },\n });\n return s;\n};\n\nconst publish = (s: Session) => {\n if (s.canonical === s.lastPublished) return;\n s.lastPublished = s.canonical;\n s.notify();\n};\n\nconst wake = (s: Session) => {\n const waiters = s.waiters;\n s.waiters = [];\n for (const waiter of waiters) waiter();\n};\n\nconst waitForWake = (s: Session) => new Promise<void>((r) => s.waiters.push(r));\n\nconst sleepUntilWake = (s: Session, delayMs: number) =>\n new Promise<void>((resolve) => {\n const waiter = () => {\n clearTimeout(timer);\n resolve();\n };\n const timer = setTimeout(() => {\n s.waiters = s.waiters.filter((w) => w !== waiter);\n resolve();\n }, delayMs);\n s.waiters.push(waiter);\n });\n\nconst settleAll = (\n s: Session,\n detach: \"evicted\" | \"unmount\" | \"gone\" | \"superseded\",\n verdict?: {\n entry: Pending;\n failure: StatewireClient.SendFailure;\n },\n) => {\n const cause = (\n {\n evicted: \"session-lost\",\n unmount: \"unmount\",\n gone: \"stream-gone\",\n superseded: \"superseded\",\n } as const\n )[detach];\n const batch = s.pending;\n const detachFailure = (entry: Pending): StatewireClient.SendFailure => {\n const state = entry.state;\n if (state.at === \"awaiting-result\")\n return { fate: \"result-unavailable\", cause };\n if (state.at === \"queued\") {\n const predecessorLost = batch.some(\n (p) => p.seq < entry.seq && p.state.at !== \"queued\",\n );\n return predecessorLost\n ? { fate: \"aborted\", cause, predecessorLost }\n : { fate: \"aborted\", cause };\n }\n return { fate: \"lost\", cause };\n };\n for (const entry of [...batch]) {\n const failure =\n entry === verdict?.entry ? verdict.failure : detachFailure(entry);\n transition(s, entry, { at: \"done\" }, { ok: false, failure });\n }\n};\n\n// Offers every unsettled real command for replay, in seq order. Not caught:\n// a throwing callback aborts the eviction before any entry settles; the\n// attach retry evicts again and re-offers.\nconst claimReplays = (s: Session): Pending[] => {\n const handler = s.onCommandSessionLost;\n if (!handler) return [];\n const claimed = new Set<Pending>();\n for (const entry of s.pending) {\n if (!entry.inv || isResolved(entry.state)) continue;\n const decision = handler({\n method: entry.inv.method,\n params: [...entry.inv.params],\n seq: entry.seq,\n });\n if (decision === \"replay\") claimed.add(entry);\n }\n if (claimed.size === 0) return [];\n s.pending = s.pending.filter((p) => !claimed.has(p));\n return [...claimed];\n};\n\nconst evict = (\n s: Session,\n verdict?: {\n entry: Pending;\n failure: StatewireClient.SendFailure;\n },\n) => {\n const replays = claimReplays(s);\n settleAll(s, \"evicted\", verdict);\n s.record = { clientId: newClientId(), nextSeq: 1 };\n s.store.save(s.record);\n s.attach = \"detached\";\n // The server dropped this client's record and its stored context with it:\n // the new session opens with a fresh full root replace.\n if (s.context !== undefined) void s.send({ context: s.context });\n for (const old of replays) {\n if (old.inv) s.lifecycle?.onCovered?.(old.inv);\n enqueue(s, old.command, old.resolve, old.reject);\n }\n publish(s);\n wake(s);\n};\n\nconst noteAcked = (s: Session, seq: number) => {\n for (const entry of s.pending) {\n if (entry.seq > seq || isStateSettled(entry.state)) continue;\n if (entry.inv) s.lifecycle?.onAck(entry.inv);\n }\n};\n\nconst spliceAcked = (s: Session, seq: number, settleVoid: boolean) => {\n for (const entry of [...s.pending]) {\n const state = entry.state;\n if (entry.seq > seq || isStateSettled(state)) continue;\n if (state.at === \"settled-live\") {\n transition(s, entry, { at: \"done\" });\n } else if (settleVoid && !state.pendingMarked) {\n transition(s, entry, { at: \"done\" }, { ok: true, payload: undefined });\n } else {\n transition(s, entry, { at: \"awaiting-result\", ...carried(state) });\n }\n }\n};\n\nconst protocolError = (message: string): never => {\n throw new Error(`statewire: protocol error: ${message}`);\n};\n\nconst applyResponse = (s: Session, response: StatewireResponse) => {\n const entry = s.pending.find((p) => p.seq === response.seq);\n if (!entry) return;\n const state = entry.state;\n if (state.at === \"done\") return;\n switch (response.type) {\n case \"pending\":\n if (\n state.at === \"queued\" ||\n state.at === \"trying\" ||\n state.at === \"posted\"\n ) {\n transition(s, entry, { ...state, pendingMarked: true });\n }\n break;\n case \"accepted\":\n if (state.at === \"settled-live\") break;\n transition(\n s,\n entry,\n state.at === \"awaiting-result\"\n ? { at: \"done\" }\n : { at: \"settled-live\", ...carried(state) },\n { ok: true, payload: response.payload },\n );\n break;\n case \"rejected\":\n transition(\n s,\n entry,\n { at: \"done\" },\n {\n ok: false,\n failure:\n response.code === \"unknown-command\"\n ? { fate: \"unknown-command\", cause: \"server\" }\n : response.code === \"invalid-params\"\n ? {\n fate: \"invalid-params\",\n cause: \"server\",\n message:\n response.message ??\n protocolError(\"rejected response missing message\"),\n }\n : {\n fate: \"rejected\",\n cause: \"server\",\n message:\n response.message ??\n protocolError(\"rejected response missing message\"),\n ...(response.payload !== undefined && {\n payload: response.payload,\n }),\n },\n },\n );\n break;\n case \"crashed\":\n case \"result-unavailable\":\n if (state.at === \"settled-live\") break;\n transition(\n s,\n entry,\n state.at === \"awaiting-result\"\n ? { at: \"done\" }\n : { at: \"settled-live\", ...carried(state) },\n { ok: false, failure: { fate: response.type, cause: \"server\" } },\n );\n break;\n }\n};\n\nconst ingestOps = (s: Session, ops: unknown[]) => {\n const applied = applyOps(s.canonical, ops);\n s.canonical = applied.state;\n s.changed.push(...applied.changedPaths);\n return applied.events;\n};\n\nconst publishIngested = (s: Session, events: unknown[]) => {\n publish(s);\n for (const event of events) s.emitEvent(event);\n wake(s);\n};\n\nconst handleSnapshot = (\n s: Session,\n { ops, lastSeq }: { ops: unknown[]; lastSeq: number },\n) => {\n if (lastSeq === -1 && s.attach !== \"detached\") {\n evict(s);\n s.transport?.connection.reconnect();\n publishIngested(s, ingestOps(s, ops));\n return;\n }\n const events = ingestOps(s, ops);\n s.attach = \"attached\";\n s.attachEpoch += 1;\n // The attach snapshot is not the true covering envelope: acked entries\n // stay awaiting their result and re-send under seq dedup.\n noteAcked(s, lastSeq);\n spliceAcked(s, lastSeq, false);\n s.lastAck = lastSeq;\n for (const entry of s.pending) {\n const state = entry.state;\n if (entry.seq <= lastSeq) {\n if (state.at === \"awaiting-result\" && state.posted) {\n transition(s, entry, { ...state, posted: false });\n }\n if (state.at === \"awaiting-result\") void pump(s);\n } else if (state.at === \"posted\") {\n // Above the ack watermark a prior receipt no longer counts.\n transition(s, entry, {\n at: \"trying\",\n pendingMarked: state.pendingMarked,\n failedAttempts: state.failedAttempts,\n });\n void pump(s);\n } else if (state.at === \"settled-live\" && state.posted) {\n transition(s, entry, { ...state, posted: false });\n void pump(s);\n }\n }\n publishIngested(s, events);\n};\n\nconst handleOps = (\n s: Session,\n {\n ops,\n res,\n ack,\n }: { ops: unknown[]; res?: StatewireResponse[]; ack?: number },\n) => {\n const horizon = ack === undefined ? s.lastAck : Math.max(s.lastAck, ack);\n if (res !== undefined) {\n for (const response of res) {\n if (response.seq > horizon) {\n protocolError(\"response seq beyond the ack\");\n }\n }\n }\n const events = ingestOps(s, ops);\n if (ack !== undefined) noteAcked(s, ack);\n if (res !== undefined) for (const response of res) applyResponse(s, response);\n if (ack !== undefined) {\n spliceAcked(s, ack, true);\n s.lastAck = Math.max(s.lastAck, ack);\n }\n publishIngested(s, events);\n};\n\nconst handleFinish = (s: Session, { reason }: { reason: string }) => {\n if (reason === \"evicted\" || reason === \"error\") return;\n s.attach = \"finished\";\n settleAll(s, reason === \"superseded\" ? \"superseded\" : \"gone\");\n publish(s);\n wake(s);\n};\n\nconst isFinished = (s: Session) => s.attach === \"finished\";\n\nconst sendable = (p: Pending) =>\n p.state.at === \"queued\" ||\n p.state.at === \"trying\" ||\n ((p.state.at === \"awaiting-result\" || p.state.at === \"settled-live\") &&\n !p.state.posted);\n\nconst pump = async (s: Session) => {\n if (s.pumping) return;\n s.pumping = true;\n try {\n while (s.running && !isFinished(s)) {\n const entry = s.pending.find(sendable);\n if (!entry) return;\n const transport = s.transport;\n if (s.attach !== \"attached\" || !transport) {\n await waitForWake(s);\n continue;\n }\n const clientId = s.record.clientId;\n const epoch = s.attachEpoch;\n if (entry.state.at === \"queued\") {\n transition(s, entry, {\n at: \"trying\",\n pendingMarked: entry.state.pendingMarked,\n failedAttempts: 0,\n });\n }\n let result: StatewireSubmitResult;\n try {\n result = await transport.send({\n seq: entry.seq,\n command: entry.command,\n });\n } catch (error) {\n if (!s.running || isFinished(s)) return;\n if (!s.pending.includes(entry)) continue;\n const state = inFlight(entry.state);\n transition(s, entry, {\n ...state,\n failedAttempts: state.failedAttempts + 1,\n });\n await sleepUntilWake(s, retryDelay(error, state.failedAttempts + 1));\n continue;\n }\n if (!s.running) return;\n if (!s.pending.includes(entry)) continue;\n if (result.type === \"receipt\") {\n const response = result.response;\n const state = entry.state;\n if (state.at === \"trying\" || state.at === \"posted\") {\n transition(s, entry, {\n at: \"posted\",\n pendingMarked: state.pendingMarked,\n failedAttempts: state.failedAttempts,\n });\n } else if (\n state.at === \"awaiting-result\" ||\n state.at === \"settled-live\"\n ) {\n transition(s, entry, { ...state, posted: true });\n }\n if (entry.inv) s.lifecycle?.onPosted?.(entry.inv);\n if (response !== undefined) {\n applyResponse(s, response);\n publish(s);\n }\n } else if (result.type === \"rejected\") {\n transition(\n s,\n entry,\n { at: \"done\" },\n {\n ok: false,\n failure: {\n fate: \"rejected\",\n cause: \"server\",\n message: result.message,\n },\n },\n );\n publish(s);\n } else if (result.type === \"stale-lease\") {\n if (epoch === s.attachEpoch) await waitForWake(s);\n } else if (result.type === \"unknown-client\") {\n // Eviction is never decided on the POST leg: re-attach first and let\n // the snapshot's lastSeq deliver the verdict. An answered attempt\n // proves the server never admitted this seq, so a clean entry requeues.\n if (epoch === s.attachEpoch) {\n const state = entry.state;\n if (state.at === \"trying\" && state.failedAttempts === 0) {\n transition(s, entry, {\n at: \"queued\",\n pendingMarked: state.pendingMarked,\n });\n }\n s.transport?.connection.reconnect();\n while (\n s.running &&\n !isFinished(s) &&\n epoch === s.attachEpoch &&\n clientId === s.record.clientId\n ) {\n await waitForWake(s);\n }\n }\n } else if (result.type === \"seq-gap\") {\n if (clientId === s.record.clientId) {\n console.error(\n `statewire: server reports a seq gap for seq ${entry.seq}; ` +\n \"treating the session as corrupt and rotating the client id\",\n );\n // Every attempt answered means the server never saw it; a thrown\n // attempt may have left the machine unanswered, so the verdict is \"lost\".\n const state = inFlight(entry.state);\n const failure: StatewireClient.SendFailure =\n state.at === \"awaiting-result\"\n ? { fate: \"result-unavailable\", cause: \"session-lost\" }\n : {\n fate: state.failedAttempts > 0 ? \"lost\" : \"aborted\",\n cause: \"session-lost\",\n };\n evict(s, { entry, failure });\n s.transport?.connection.reconnect();\n }\n }\n }\n } finally {\n s.pumping = false;\n }\n};\n\nconst start = (s: Session) => {\n if (!s.recordPersisted) {\n s.recordPersisted = true;\n s.store.save(s.record);\n }\n s.running = true;\n void pump(s);\n};\n\nconst stop = (s: Session) => {\n s.running = false;\n wake(s);\n settleAll(s, \"unmount\");\n};\n\nconst setTransport = (s: Session, transport: RemoteLink) => {\n if (s.transport === transport) return;\n s.transport = transport;\n wake(s);\n};\n\nconst enqueue = (\n s: Session,\n command: StatewireWireCommand,\n resolve: (payload: unknown) => void,\n reject: (error: StatewireSendError) => void,\n) => {\n const seq = s.record.nextSeq;\n s.record = { ...s.record, nextSeq: seq + 1 };\n s.store.save(s.record);\n const entry: Pending = {\n seq,\n command,\n inv:\n command.method === undefined\n ? null\n : {\n method: command.method,\n params: command.params,\n ts: Date.now(),\n seq,\n clientId: s.record.clientId,\n },\n state: { at: \"queued\", pendingMarked: false },\n resolve,\n reject,\n };\n s.pending.push(entry);\n if (entry.inv) s.lifecycle?.onStart(entry.inv);\n void pump(s);\n};\n\nconst sendCommand = (\n s: Session,\n command: StatewireWireCommand,\n): Promise<unknown> => {\n if (!s.running) {\n return rejectedSend({ fate: \"aborted\", cause: \"unmount\" });\n }\n if (isFinished(s)) {\n return rejectedSend({ fate: \"aborted\", cause: \"stream-gone\" });\n }\n const result = new Promise<unknown>((resolve, reject) => {\n enqueue(s, command, resolve, reject);\n });\n result.catch(() => {});\n return result;\n};\n\nconst setContext = (\n s: Session,\n value: Record<string, unknown>,\n): Promise<unknown> => {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new Error(\"statewire: context must be a plain object\");\n }\n s.context = value;\n return s.send({ context: value });\n};\n\nexport const useStatewireSession = (\n options: StatewireSessionOptions,\n emit: (event: unknown) => void,\n): Session => {\n const [s] = useState(() => createSession(options));\n s.onCommandSessionLost = options.onCommandSessionLost;\n useEffect(() => {\n s.emitEvent = emit;\n return () => {\n s.emitEvent = () => {};\n };\n }, [s, emit]);\n useEffect(() => {\n start(s);\n return () => stop(s);\n }, [s]);\n return s;\n};\n\nexport { setContext, setTransport };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"session.js","names":["useEffect","useState","applyOps","StatewirePath","StatewireResponse","bindCommandsHost","CommandsInvocation","CommandsListener","Statewire","StatewireClient","createMemorySessionStore","StatewireSessionRecord","StatewireSessionStore","StatewireSessionStoreOptions","retryDelay","createMethodProxy","randomId","StatewireSendError","RemoteLink","StatewireRemoteProps","StatewireSubmitResult","StatewireWireCommand","PendingState","at","pendingMarked","failedAttempts","posted","Pending","seq","command","inv","state","resolve","payload","reject","error","Outcome","ok","failure","SendFailure","isResolved","isStateSettled","inFlight","Error","legalTransitions","Record","queued","trying","done","transition","s","Session","entry","next","outcome","prev","includes","settles","undefined","covers","lifecycle","onCovered","onSettle","type","pending","filter","p","carried","Exclude","newClientId","rejectedSend","Promise","result","catch","AttachState","StatewireSessionOptions","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
CHANGED
|
@@ -4,18 +4,6 @@ import { StatewireHost } from "../host.js";
|
|
|
4
4
|
/** Host-side accessor for a `useStatewireState` state object's source (`snapshot()` reads the committed state). */
|
|
5
5
|
declare const getStateHost: (state: object) => StatewireHost.StateHost | undefined;
|
|
6
6
|
declare const useStatewireState: <TState>(init: () => TState) => readonly [TState, StatewireHost.StateCtl<TState>];
|
|
7
|
-
/**
|
|
8
|
-
* `CommandExecution(executor)` runs the (possibly async) `executor({ ack })`
|
|
9
|
-
* immediately: its return value resolves the execution, a throw/rejection
|
|
10
|
-
* rejects it; `ack()` releases the client's ack watermark before the executor
|
|
11
|
-
* finishes. `CommandExecution.withResolvers()` returns `[execution,
|
|
12
|
-
* { ack, resolve, reject }]` instead — settle them from your own spawned work.
|
|
13
|
-
*/
|
|
14
|
-
declare const CommandExecution: (<T = void>(executor: (resolvers: {
|
|
15
|
-
ack: () => void;
|
|
16
|
-
}) => T | void | PromiseLike<T | void>) => StatewireHost.Execution<T>) & {
|
|
17
|
-
withResolvers: <T = void>() => [StatewireHost.Execution<T>, StatewireHost.ExecutionResolvers<T>];
|
|
18
|
-
};
|
|
19
7
|
type CommandsInvocation = {
|
|
20
8
|
readonly method: string;
|
|
21
9
|
readonly params: readonly unknown[];
|
|
@@ -52,12 +40,12 @@ type CommandsHost = {
|
|
|
52
40
|
};
|
|
53
41
|
declare const getCommandsHost: (commands: object) => CommandsHost | undefined;
|
|
54
42
|
declare const bindCommandsHost: (commands: object, host: CommandsHost) => void;
|
|
55
|
-
declare const useStatewireCommands: <THandlers extends Statewire.Commands>(commands: (THandlers & ThisType<StatewireHost.
|
|
43
|
+
declare const useStatewireCommands: <THandlers extends Statewire.Commands>(commands: (THandlers & ThisType<StatewireHost.Ctx>) | undefined | null | false) => Statewire.CommandsProxy<StatewireHost.HandledCommands<THandlers>>;
|
|
56
44
|
type EmitHost = {
|
|
57
45
|
setSink: (sink: ((event: unknown) => void) | null) => void;
|
|
58
46
|
};
|
|
59
47
|
declare const getEmitHost: (emit: object | undefined) => EmitHost | undefined;
|
|
60
48
|
declare const useStatewireEmit: <TEvent>() => ((event: TEvent) => void);
|
|
61
49
|
//#endregion
|
|
62
|
-
export {
|
|
50
|
+
export { CommandsHost, CommandsInvocation, CommandsListener, CommandsOutcome, CommandsSubmission, EmitHost, bindCommandsHost, getCommandsHost, getEmitHost, getStateHost, useStatewireCommands, useStatewireEmit, useStatewireState };
|
|
63
51
|
//# sourceMappingURL=host.d.ts.map
|
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
|
|
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;KAwEhC;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;cA4K5C,uBAAwB,kBAAkB,UAAU,UAC/D,WACK,YAAY,SAAS,cAAc,qCAIvC,UAAU,cAAc,cAAc,gBAAgB;KAqF7C;EACV,UAAU,QAAQ;;cAKP,cAAe,6BAA2B;cAG1C,mBAAoB,eAAa,OAAO"}
|
package/dist/host/host.js
CHANGED
|
@@ -69,64 +69,6 @@ const useStatewireState = (init) => {
|
|
|
69
69
|
}
|
|
70
70
|
return [value, cell.ctl];
|
|
71
71
|
};
|
|
72
|
-
const executionStates = /* @__PURE__ */ new WeakMap();
|
|
73
|
-
/**
|
|
74
|
-
* `CommandExecution(executor)` runs the (possibly async) `executor({ ack })`
|
|
75
|
-
* immediately: its return value resolves the execution, a throw/rejection
|
|
76
|
-
* rejects it; `ack()` releases the client's ack watermark before the executor
|
|
77
|
-
* finishes. `CommandExecution.withResolvers()` returns `[execution,
|
|
78
|
-
* { ack, resolve, reject }]` instead — settle them from your own spawned work.
|
|
79
|
-
*/
|
|
80
|
-
const CommandExecution = Object.assign((executor) => {
|
|
81
|
-
const state = {
|
|
82
|
-
acked: false,
|
|
83
|
-
settled: null,
|
|
84
|
-
bind: null
|
|
85
|
-
};
|
|
86
|
-
const execution = Object.freeze({});
|
|
87
|
-
executionStates.set(execution, state);
|
|
88
|
-
const settle = (settled) => {
|
|
89
|
-
if (state.settled) return;
|
|
90
|
-
state.settled = settled;
|
|
91
|
-
state.acked = true;
|
|
92
|
-
state.bind?.onSettle(settled);
|
|
93
|
-
};
|
|
94
|
-
const ack = () => {
|
|
95
|
-
if (state.acked) return;
|
|
96
|
-
state.acked = true;
|
|
97
|
-
state.bind?.onAck();
|
|
98
|
-
};
|
|
99
|
-
(async () => {
|
|
100
|
-
try {
|
|
101
|
-
settle({
|
|
102
|
-
type: "resolved",
|
|
103
|
-
value: await executor({ ack })
|
|
104
|
-
});
|
|
105
|
-
} catch (error) {
|
|
106
|
-
settle({
|
|
107
|
-
type: "rejected",
|
|
108
|
-
error
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
})();
|
|
112
|
-
return execution;
|
|
113
|
-
}, { withResolvers: () => {
|
|
114
|
-
let resolve;
|
|
115
|
-
let reject;
|
|
116
|
-
const promise = new Promise((res, rej) => {
|
|
117
|
-
resolve = res;
|
|
118
|
-
reject = rej;
|
|
119
|
-
});
|
|
120
|
-
let ack;
|
|
121
|
-
return [CommandExecution(({ ack: a }) => {
|
|
122
|
-
ack = a;
|
|
123
|
-
return promise;
|
|
124
|
-
}), {
|
|
125
|
-
ack: () => ack(),
|
|
126
|
-
resolve,
|
|
127
|
-
reject
|
|
128
|
-
}];
|
|
129
|
-
} });
|
|
130
72
|
const commandsHosts = /* @__PURE__ */ new WeakMap();
|
|
131
73
|
const getCommandsHost = (commands) => commandsHosts.get(commands);
|
|
132
74
|
const bindCommandsHost = (commands, host) => {
|
|
@@ -173,13 +115,8 @@ const settleInvocation = (cell, inv, call, settled) => {
|
|
|
173
115
|
call.reject(error);
|
|
174
116
|
}
|
|
175
117
|
};
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
watchStall(cell);
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
const call = cell.queue.shift();
|
|
182
|
-
if (!call) return;
|
|
118
|
+
const isThenable = (value) => typeof value === "object" && value !== null && typeof value.then === "function";
|
|
119
|
+
const dispatch = (cell, call) => {
|
|
183
120
|
const handler = cell.handlers[call.method];
|
|
184
121
|
if (handler === void 0) {
|
|
185
122
|
const error = new StatewireSendError({
|
|
@@ -191,7 +128,6 @@ const pump = (cell) => {
|
|
|
191
128
|
error
|
|
192
129
|
});
|
|
193
130
|
call.reject(error);
|
|
194
|
-
pump(cell);
|
|
195
131
|
return;
|
|
196
132
|
}
|
|
197
133
|
const inv = {
|
|
@@ -203,35 +139,48 @@ const pump = (cell) => {
|
|
|
203
139
|
...call.origin !== void 0 && { origin: call.origin },
|
|
204
140
|
...call.origin?.clientId !== void 0 && { clientId: call.origin.clientId }
|
|
205
141
|
};
|
|
206
|
-
cell.running = true;
|
|
207
142
|
cell.listener?.onStart(inv);
|
|
208
143
|
cell.listener?.onPosted?.(inv);
|
|
209
144
|
cell.listener?.onCovered?.(inv);
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
145
|
+
const ctx = {
|
|
146
|
+
caller: call.origin?.caller ?? LOCAL_CALLER,
|
|
147
|
+
ack: () => ackInvocation(cell, inv)
|
|
148
|
+
};
|
|
149
|
+
try {
|
|
150
|
+
const value = handler.call(ctx, ...call.params);
|
|
151
|
+
if (isThenable(value)) value.then((resolved) => settleInvocation(cell, inv, call, {
|
|
152
|
+
type: "resolved",
|
|
153
|
+
value: resolved
|
|
154
|
+
}), (error) => settleInvocation(cell, inv, call, {
|
|
155
|
+
type: "rejected",
|
|
156
|
+
error
|
|
157
|
+
}));
|
|
158
|
+
else settleInvocation(cell, inv, call, {
|
|
159
|
+
type: "resolved",
|
|
160
|
+
value
|
|
161
|
+
});
|
|
162
|
+
} catch (error) {
|
|
163
|
+
settleInvocation(cell, inv, call, {
|
|
164
|
+
type: "rejected",
|
|
165
|
+
error
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
const pump = (cell) => {
|
|
170
|
+
if (cell.running || !cell.handlers) {
|
|
171
|
+
watchStall(cell);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
cell.running = true;
|
|
175
|
+
try {
|
|
176
|
+
while (cell.handlers) {
|
|
177
|
+
const call = cell.queue.shift();
|
|
178
|
+
if (!call) break;
|
|
179
|
+
dispatch(cell, call);
|
|
233
180
|
}
|
|
234
|
-
}
|
|
181
|
+
} finally {
|
|
182
|
+
cell.running = false;
|
|
183
|
+
}
|
|
235
184
|
};
|
|
236
185
|
const watchStall = (cell) => {
|
|
237
186
|
if (!isDev() || cell.stallTimer !== null || cell.queue.length === 0) return;
|
|
@@ -333,6 +282,6 @@ function _temp() {
|
|
|
333
282
|
return emit;
|
|
334
283
|
}
|
|
335
284
|
//#endregion
|
|
336
|
-
export {
|
|
285
|
+
export { bindCommandsHost, getCommandsHost, getEmitHost, getStateHost, useStatewireCommands, useStatewireEmit, useStatewireState };
|
|
337
286
|
|
|
338
287
|
//# sourceMappingURL=host.js.map
|
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 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
|
+
{"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","Settled","type","error","CommandsInvocation","method","params","ts","seq","clientId","CommandsOutcome","payload","CommandsListener","onStart","inv","onPosted","onAck","onCovered","onSettle","outcome","CommandsSubmission","caller","Caller","CommandsHost","setListener","submit","call","Promise","commandsHosts","getCommandsHost","commands","bindCommandsHost","QueuedCall","resolve","reject","origin","ActiveInvocation","acked","CommandsCell","handlers","Record","args","queue","running","unmounted","stallTimer","ReturnType","setTimeout","STALL_WARN_MS","LOCAL_CALLER","Object","freeze","context","isConnected","toSendError","fate","cause","message","String","undefined","ackInvocation","settleInvocation","settled","isThenable","PromiseLike","then","dispatch","handler","ctx","Ctx","ack","this","resolved","pump","watchStall","shift","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 Settled =\n | { type: \"resolved\"; value: unknown }\n | { type: \"rejected\"; error: unknown };\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: Settled,\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 isThenable = (value: unknown): value is PromiseLike<unknown> =>\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { then?: unknown }).then === \"function\";\n\nconst dispatch = (cell: CommandsCell, call: QueuedCall) => {\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 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.listener?.onStart(inv);\n cell.listener?.onPosted?.(inv);\n cell.listener?.onCovered?.(inv);\n const ctx: StatewireHost.Ctx = {\n caller: call.origin?.caller ?? LOCAL_CALLER,\n ack: () => ackInvocation(cell, inv),\n };\n try {\n const value = (\n handler as (\n this: StatewireHost.Ctx,\n ...args: readonly unknown[]\n ) => unknown\n ).call(ctx, ...call.params);\n if (isThenable(value)) {\n value.then(\n (resolved) =>\n settleInvocation(cell, inv, call, {\n type: \"resolved\",\n value: resolved,\n }),\n (error: unknown) =>\n settleInvocation(cell, inv, call, { type: \"rejected\", error }),\n );\n } else {\n settleInvocation(cell, inv, call, { type: \"resolved\", value });\n }\n } catch (error) {\n settleInvocation(cell, inv, call, { type: \"rejected\", error });\n }\n};\n\n// Prefix-only advancement: each handler runs synchronously to its first\n// await, then the pump moves on; settlement rides the returned promise.\nconst pump = (cell: CommandsCell) => {\n if (cell.running || !cell.handlers) {\n watchStall(cell);\n return;\n }\n cell.running = true;\n try {\n while (cell.handlers) {\n const call = cell.queue.shift();\n if (!call) break;\n dispatch(cell, call);\n }\n } finally {\n cell.running = false;\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.Ctx>)\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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"}
|
package/dist/host.d.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { Statewire } from "./types.js";
|
|
2
2
|
import { ServerOp, append } from "./host/draft.js";
|
|
3
3
|
import { StatewireReject } from "./host/engine.js";
|
|
4
|
-
import {
|
|
4
|
+
import { getStateHost, useStatewireCommands, useStatewireEmit, useStatewireState } from "./host/host.js";
|
|
5
5
|
//#region src/host.d.ts
|
|
6
|
-
declare const executionResult: unique symbol;
|
|
7
|
-
type UnwrapExecution<V> = V extends StatewireHost.Execution<infer T> ? T : V;
|
|
8
6
|
declare namespace StatewireHost {
|
|
9
7
|
type StateCtl<TState = unknown> = {
|
|
10
8
|
/** Apply server-vocabulary ops (root replace = whole-state swap). */
|
|
@@ -18,21 +16,17 @@ declare namespace StatewireHost {
|
|
|
18
16
|
};
|
|
19
17
|
type StateSource = Statewire.StateSource;
|
|
20
18
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
19
|
+
* Per-invocation handle bound as `this` of a command handler: `caller` is
|
|
20
|
+
* the submitting client, `ack()` releases the client's optimistic overlay /
|
|
21
|
+
* ack watermark before the handler settles (idempotent, a no-op after
|
|
22
|
+
* settle). The command settles when the handler's returned promise does.
|
|
25
23
|
*/
|
|
26
|
-
type
|
|
27
|
-
readonly
|
|
28
|
-
|
|
29
|
-
type ExecutionResolvers<T = void> = {
|
|
30
|
-
ack: () => void;
|
|
31
|
-
resolve: (value: T) => void;
|
|
32
|
-
reject: (error: unknown) => void;
|
|
24
|
+
type Ctx = {
|
|
25
|
+
readonly caller: Caller;
|
|
26
|
+
readonly ack: () => void;
|
|
33
27
|
};
|
|
34
28
|
/**
|
|
35
|
-
* The submitting client
|
|
29
|
+
* The submitting client (`this.caller` in a command handler). Holdable
|
|
36
30
|
* across a long run: `clientId` is the client's `Statewire-Client-Id`
|
|
37
31
|
* (in-process dispatch has none — reading it throws), `context` reads the
|
|
38
32
|
* client's current ambient context live (null until the client sends one),
|
|
@@ -43,7 +37,7 @@ declare namespace StatewireHost {
|
|
|
43
37
|
readonly context: Record<string, unknown> | null;
|
|
44
38
|
readonly isConnected: boolean;
|
|
45
39
|
};
|
|
46
|
-
type HandledCommands<THandlers extends Statewire.Commands> = { [K in keyof THandlers]: (...args: Parameters<THandlers[K]>) =>
|
|
40
|
+
type HandledCommands<THandlers extends Statewire.Commands> = { [K in keyof THandlers]: (...args: Parameters<THandlers[K]>) => Awaited<ReturnType<THandlers[K]>>; };
|
|
47
41
|
type Fin = {
|
|
48
42
|
reason: "evicted" | "error" | "gone" | "superseded" | (string & {});
|
|
49
43
|
message?: string;
|
|
@@ -52,5 +46,5 @@ declare namespace StatewireHost {
|
|
|
52
46
|
};
|
|
53
47
|
}
|
|
54
48
|
//#endregion
|
|
55
|
-
export {
|
|
49
|
+
export { StatewireHost, StatewireReject, append, getStateHost, useStatewireCommands, useStatewireEmit, useStatewireState };
|
|
56
50
|
//# sourceMappingURL=host.d.ts.map
|
package/dist/host.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.d.ts","names":[],"sources":["../src/host.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"host.d.ts","names":[],"sources":["../src/host.ts"],"mappings":";;;;;kBAYiB;OACH,SAAS;;IAEnB,QAAQ,cAAc,UAAU;;IAEhC,gBAAgB;;OAGN,YAAY,UAAU;;IAEhC,UAAU,YAAY,IAAI;;OAGhB,cAAc,UAAU;;;;;;;OAQxB;aACD,QAAQ;aACR;;;;;;;;;OAUC;aACD;aACA,SAAS;aACT;;OAGC,gBAAgB,kBAAkB,UAAU,eACrD,WAAW,gBACP,MAAM,WAAW,UAAU,QAC3B,QAAQ,WAAW,UAAU;OAGxB;IACV;IACA;IACA;IACA"}
|
package/dist/host.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { StatewireReject } from "./host/engine.js";
|
|
2
2
|
import { append } from "./host/draft.js";
|
|
3
|
-
import {
|
|
4
|
-
export {
|
|
3
|
+
import { getStateHost, useStatewireCommands, useStatewireEmit, useStatewireState } from "./host/host.js";
|
|
4
|
+
export { StatewireReject, append, getStateHost, useStatewireCommands, useStatewireEmit, useStatewireState };
|
package/dist/util/statePath.js
CHANGED
|
@@ -7,12 +7,14 @@ const walkStatePath = (root, path, mode) => {
|
|
|
7
7
|
for (let depth = 0; depth < path.length; depth++) {
|
|
8
8
|
const k = path[depth];
|
|
9
9
|
const mustResolve = mode === "resolve" || mode === "parent" && depth < path.length - 1;
|
|
10
|
-
if (mode !== "read")
|
|
11
|
-
if (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
if (mode !== "read") {
|
|
11
|
+
if (typeof k === "number") {
|
|
12
|
+
if (!Array.isArray(node) || !Number.isInteger(k) || k < 0) return;
|
|
13
|
+
if (mustResolve && k >= node.length) return void 0;
|
|
14
|
+
} else {
|
|
15
|
+
if (isUnsafeKey(k, path[depth - 1]) || typeof node !== "object" || node === null || Array.isArray(node)) return;
|
|
16
|
+
if (mustResolve && !Object.hasOwn(node, k)) return void 0;
|
|
17
|
+
}
|
|
16
18
|
}
|
|
17
19
|
parent = node;
|
|
18
20
|
key = k;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statePath.js","names":["StatePath","isUnsafeKey","key","parent","WalkedPath","leaf","walkStatePath","root","path","mode","undefined","node","depth","length","k","mustResolve","Array","isArray","Number","isInteger","Object","hasOwn","Record","readAtPath"],"sources":["../../src/util/statePath.ts"],"sourcesContent":["export type StatePath = readonly (string | number)[];\n\nexport const isUnsafeKey = (\n key: string | number,\n parent: string | number | undefined,\n) => key === \"__proto__\" || (key === \"prototype\" && parent === \"constructor\");\n\nexport type WalkedPath = {\n /** Container of the final key; undefined when the path is empty. */\n parent: unknown;\n key: string | number;\n leaf: unknown;\n};\n\n// \"read\": lenient, never fails — missing steps yield an undefined leaf.\n// \"parent\": every step before the last must resolve; the leaf may not exist.\n// \"resolve\": every step including the last must resolve.\n// Strict modes require array+integer for number keys, plain object for string\n// keys, and reject unsafe (prototype-polluting) segments.\nexport const walkStatePath = (\n root: unknown,\n path: StatePath,\n mode: \"read\" | \"parent\" | \"resolve\",\n): WalkedPath | undefined => {\n let parent: unknown = undefined;\n let key: string | number = \"\";\n let node: unknown = root;\n for (let depth = 0; depth < path.length; depth++) {\n const k = path[depth]!;\n const mustResolve =\n mode === \"resolve\" || (mode === \"parent\" && depth < path.length - 1);\n if (mode !== \"read\") {\n if (typeof k === \"number\") {\n if (!Array.isArray(node) || !Number.isInteger(k) || k < 0) {\n return undefined;\n }\n if (mustResolve && k >= node.length) return undefined;\n } else {\n if (\n isUnsafeKey(k, path[depth - 1]) ||\n typeof node !== \"object\" ||\n node === null ||\n Array.isArray(node)\n ) {\n return undefined;\n }\n if (mustResolve && !Object.hasOwn(node, k)) return undefined;\n }\n }\n parent = node;\n key = k;\n node =\n typeof node === \"object\" && node !== null && Object.hasOwn(node, k)\n ? (node as Record<string | number, unknown>)[k]\n : undefined;\n }\n return { parent, key, leaf: node };\n};\n\nexport const readAtPath = (root: unknown, path: StatePath): unknown =>\n walkStatePath(root, path, \"read\")!.leaf;\n"],"mappings":";AAEA,MAAaC,eACXC,KACAC,WACGD,QAAQ,eAAgBA,QAAQ,eAAeC,WAAW;AAc/D,MAAaG,iBACXC,MACAC,MACAC,SAC2B;CAC3B,IAAIN,SAAkBO,KAAAA;CACtB,IAAIR,MAAuB;CAC3B,IAAIS,OAAgBJ;CACpB,KAAK,IAAIK,QAAQ,GAAGA,QAAQJ,KAAKK,QAAQD,SAAS;EAChD,MAAME,IAAIN,KAAKI;EACf,MAAMG,cACJN,SAAS,aAAcA,SAAS,YAAYG,QAAQJ,KAAKK,SAAS;EACpE,IAAIJ,SAAS,
|
|
1
|
+
{"version":3,"file":"statePath.js","names":["StatePath","isUnsafeKey","key","parent","WalkedPath","leaf","walkStatePath","root","path","mode","undefined","node","depth","length","k","mustResolve","Array","isArray","Number","isInteger","Object","hasOwn","Record","readAtPath"],"sources":["../../src/util/statePath.ts"],"sourcesContent":["export type StatePath = readonly (string | number)[];\n\nexport const isUnsafeKey = (\n key: string | number,\n parent: string | number | undefined,\n) => key === \"__proto__\" || (key === \"prototype\" && parent === \"constructor\");\n\nexport type WalkedPath = {\n /** Container of the final key; undefined when the path is empty. */\n parent: unknown;\n key: string | number;\n leaf: unknown;\n};\n\n// \"read\": lenient, never fails — missing steps yield an undefined leaf.\n// \"parent\": every step before the last must resolve; the leaf may not exist.\n// \"resolve\": every step including the last must resolve.\n// Strict modes require array+integer for number keys, plain object for string\n// keys, and reject unsafe (prototype-polluting) segments.\nexport const walkStatePath = (\n root: unknown,\n path: StatePath,\n mode: \"read\" | \"parent\" | \"resolve\",\n): WalkedPath | undefined => {\n let parent: unknown = undefined;\n let key: string | number = \"\";\n let node: unknown = root;\n for (let depth = 0; depth < path.length; depth++) {\n const k = path[depth]!;\n const mustResolve =\n mode === \"resolve\" || (mode === \"parent\" && depth < path.length - 1);\n if (mode !== \"read\") {\n if (typeof k === \"number\") {\n if (!Array.isArray(node) || !Number.isInteger(k) || k < 0) {\n return undefined;\n }\n if (mustResolve && k >= node.length) return undefined;\n } else {\n if (\n isUnsafeKey(k, path[depth - 1]) ||\n typeof node !== \"object\" ||\n node === null ||\n Array.isArray(node)\n ) {\n return undefined;\n }\n if (mustResolve && !Object.hasOwn(node, k)) return undefined;\n }\n }\n parent = node;\n key = k;\n node =\n typeof node === \"object\" && node !== null && Object.hasOwn(node, k)\n ? (node as Record<string | number, unknown>)[k]\n : undefined;\n }\n return { parent, key, leaf: node };\n};\n\nexport const readAtPath = (root: unknown, path: StatePath): unknown =>\n walkStatePath(root, path, \"read\")!.leaf;\n"],"mappings":";AAEA,MAAaC,eACXC,KACAC,WACGD,QAAQ,eAAgBA,QAAQ,eAAeC,WAAW;AAc/D,MAAaG,iBACXC,MACAC,MACAC,SAC2B;CAC3B,IAAIN,SAAkBO,KAAAA;CACtB,IAAIR,MAAuB;CAC3B,IAAIS,OAAgBJ;CACpB,KAAK,IAAIK,QAAQ,GAAGA,QAAQJ,KAAKK,QAAQD,SAAS;EAChD,MAAME,IAAIN,KAAKI;EACf,MAAMG,cACJN,SAAS,aAAcA,SAAS,YAAYG,QAAQJ,KAAKK,SAAS;EACpE,IAAIJ,SAAS,QAAQ;GACnB,IAAI,OAAOK,MAAM,UAAU;IACzB,IAAI,CAACE,MAAMC,QAAQN,IAAI,KAAK,CAACO,OAAOC,UAAUL,CAAC,KAAKA,IAAI,GACtD;IAEF,IAAIC,eAAeD,KAAKH,KAAKE,QAAQ,OAAOH,KAAAA;GAC9C,OAAO;IACL,IACET,YAAYa,GAAGN,KAAKI,QAAQ,EAAE,KAC9B,OAAOD,SAAS,YAChBA,SAAS,QACTK,MAAMC,QAAQN,IAAI,GAElB;IAEF,IAAII,eAAe,CAACK,OAAOC,OAAOV,MAAMG,CAAC,GAAG,OAAOJ,KAAAA;GACrD;EACF;EACAP,SAASQ;EACTT,MAAMY;EACNH,OACE,OAAOA,SAAS,YAAYA,SAAS,QAAQS,OAAOC,OAAOV,MAAMG,CAAC,IAC7DH,KAA0CG,KAC3CJ,KAAAA;CACR;CACA,OAAO;EAAEP;EAAQD;EAAKG,MAAMM;CAAK;AACnC;AAEA,MAAaY,cAAchB,MAAeC,SACxCF,cAAcC,MAAMC,MAAM,MAAM,CAAC,CAAEH"}
|
package/package.json
CHANGED
package/src/host/host.ts
CHANGED
|
@@ -102,82 +102,10 @@ export const useStatewireState = <TState>(
|
|
|
102
102
|
|
|
103
103
|
// ─── commands ───────────────────────────────────────────
|
|
104
104
|
|
|
105
|
-
type
|
|
105
|
+
type Settled =
|
|
106
106
|
| { type: "resolved"; value: unknown }
|
|
107
107
|
| { type: "rejected"; error: unknown };
|
|
108
108
|
|
|
109
|
-
type ExecutionBind = {
|
|
110
|
-
onAck: () => void;
|
|
111
|
-
onSettle: (settled: ExecutionSettled) => void;
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
type ExecutionState = {
|
|
115
|
-
acked: boolean;
|
|
116
|
-
settled: ExecutionSettled | null;
|
|
117
|
-
bind: ExecutionBind | null;
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const executionStates = new WeakMap<object, ExecutionState>();
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* `CommandExecution(executor)` runs the (possibly async) `executor({ ack })`
|
|
124
|
-
* immediately: its return value resolves the execution, a throw/rejection
|
|
125
|
-
* rejects it; `ack()` releases the client's ack watermark before the executor
|
|
126
|
-
* finishes. `CommandExecution.withResolvers()` returns `[execution,
|
|
127
|
-
* { ack, resolve, reject }]` instead — settle them from your own spawned work.
|
|
128
|
-
*/
|
|
129
|
-
export const CommandExecution = Object.assign(
|
|
130
|
-
<T = void>(
|
|
131
|
-
executor: (resolvers: {
|
|
132
|
-
ack: () => void;
|
|
133
|
-
}) => T | void | PromiseLike<T | void>,
|
|
134
|
-
): StatewireHost.Execution<T> => {
|
|
135
|
-
const state: ExecutionState = { acked: false, settled: null, bind: null };
|
|
136
|
-
const execution = Object.freeze(
|
|
137
|
-
{},
|
|
138
|
-
) as unknown as StatewireHost.Execution<T>;
|
|
139
|
-
executionStates.set(execution, state);
|
|
140
|
-
const settle = (settled: ExecutionSettled) => {
|
|
141
|
-
if (state.settled) return;
|
|
142
|
-
state.settled = settled;
|
|
143
|
-
state.acked = true;
|
|
144
|
-
state.bind?.onSettle(settled);
|
|
145
|
-
};
|
|
146
|
-
const ack = () => {
|
|
147
|
-
if (state.acked) return;
|
|
148
|
-
state.acked = true;
|
|
149
|
-
state.bind?.onAck();
|
|
150
|
-
};
|
|
151
|
-
void (async () => {
|
|
152
|
-
try {
|
|
153
|
-
settle({ type: "resolved", value: await executor({ ack }) });
|
|
154
|
-
} catch (error) {
|
|
155
|
-
settle({ type: "rejected", error });
|
|
156
|
-
}
|
|
157
|
-
})();
|
|
158
|
-
return execution;
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
withResolvers: <T = void>(): [
|
|
162
|
-
StatewireHost.Execution<T>,
|
|
163
|
-
StatewireHost.ExecutionResolvers<T>,
|
|
164
|
-
] => {
|
|
165
|
-
let resolve!: (value: T) => void;
|
|
166
|
-
let reject!: (error: unknown) => void;
|
|
167
|
-
const promise = new Promise<T>((res, rej) => {
|
|
168
|
-
resolve = res;
|
|
169
|
-
reject = rej;
|
|
170
|
-
});
|
|
171
|
-
let ack!: () => void;
|
|
172
|
-
const execution = CommandExecution<T>(({ ack: a }) => {
|
|
173
|
-
ack = a;
|
|
174
|
-
return promise;
|
|
175
|
-
});
|
|
176
|
-
return [execution, { ack: () => ack(), resolve, reject }];
|
|
177
|
-
},
|
|
178
|
-
},
|
|
179
|
-
);
|
|
180
|
-
|
|
181
109
|
export type CommandsInvocation = {
|
|
182
110
|
readonly method: string;
|
|
183
111
|
readonly params: readonly unknown[];
|
|
@@ -279,7 +207,7 @@ const settleInvocation = (
|
|
|
279
207
|
cell: CommandsCell,
|
|
280
208
|
inv: ActiveInvocation,
|
|
281
209
|
call: QueuedCall,
|
|
282
|
-
settled:
|
|
210
|
+
settled: Settled,
|
|
283
211
|
) => {
|
|
284
212
|
ackInvocation(cell, inv);
|
|
285
213
|
if (settled.type === "resolved") {
|
|
@@ -299,14 +227,13 @@ const settleInvocation = (
|
|
|
299
227
|
}
|
|
300
228
|
};
|
|
301
229
|
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const handler = cell.handlers[call.method];
|
|
230
|
+
const isThenable = (value: unknown): value is PromiseLike<unknown> =>
|
|
231
|
+
typeof value === "object" &&
|
|
232
|
+
value !== null &&
|
|
233
|
+
typeof (value as { then?: unknown }).then === "function";
|
|
234
|
+
|
|
235
|
+
const dispatch = (cell: CommandsCell, call: QueuedCall) => {
|
|
236
|
+
const handler = cell.handlers![call.method];
|
|
310
237
|
if (handler === undefined) {
|
|
311
238
|
const error = new StatewireSendError({
|
|
312
239
|
fate: "unknown-command",
|
|
@@ -314,7 +241,6 @@ const pump = (cell: CommandsCell) => {
|
|
|
314
241
|
});
|
|
315
242
|
call.origin?.onSettle?.({ type: "rejected", error });
|
|
316
243
|
call.reject(error);
|
|
317
|
-
pump(cell);
|
|
318
244
|
return;
|
|
319
245
|
}
|
|
320
246
|
const inv: ActiveInvocation = {
|
|
@@ -328,39 +254,55 @@ const pump = (cell: CommandsCell) => {
|
|
|
328
254
|
clientId: call.origin.clientId,
|
|
329
255
|
}),
|
|
330
256
|
};
|
|
331
|
-
cell.running = true;
|
|
332
257
|
cell.listener?.onStart(inv);
|
|
333
258
|
cell.listener?.onPosted?.(inv);
|
|
334
259
|
cell.listener?.onCovered?.(inv);
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
settleInvocation(cell, inv, call, { type: "rejected", error });
|
|
359
|
-
} finally {
|
|
360
|
-
cell.running = false;
|
|
361
|
-
pump(cell);
|
|
260
|
+
const ctx: StatewireHost.Ctx = {
|
|
261
|
+
caller: call.origin?.caller ?? LOCAL_CALLER,
|
|
262
|
+
ack: () => ackInvocation(cell, inv),
|
|
263
|
+
};
|
|
264
|
+
try {
|
|
265
|
+
const value = (
|
|
266
|
+
handler as (
|
|
267
|
+
this: StatewireHost.Ctx,
|
|
268
|
+
...args: readonly unknown[]
|
|
269
|
+
) => unknown
|
|
270
|
+
).call(ctx, ...call.params);
|
|
271
|
+
if (isThenable(value)) {
|
|
272
|
+
value.then(
|
|
273
|
+
(resolved) =>
|
|
274
|
+
settleInvocation(cell, inv, call, {
|
|
275
|
+
type: "resolved",
|
|
276
|
+
value: resolved,
|
|
277
|
+
}),
|
|
278
|
+
(error: unknown) =>
|
|
279
|
+
settleInvocation(cell, inv, call, { type: "rejected", error }),
|
|
280
|
+
);
|
|
281
|
+
} else {
|
|
282
|
+
settleInvocation(cell, inv, call, { type: "resolved", value });
|
|
362
283
|
}
|
|
363
|
-
}
|
|
284
|
+
} catch (error) {
|
|
285
|
+
settleInvocation(cell, inv, call, { type: "rejected", error });
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// Prefix-only advancement: each handler runs synchronously to its first
|
|
290
|
+
// await, then the pump moves on; settlement rides the returned promise.
|
|
291
|
+
const pump = (cell: CommandsCell) => {
|
|
292
|
+
if (cell.running || !cell.handlers) {
|
|
293
|
+
watchStall(cell);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
cell.running = true;
|
|
297
|
+
try {
|
|
298
|
+
while (cell.handlers) {
|
|
299
|
+
const call = cell.queue.shift();
|
|
300
|
+
if (!call) break;
|
|
301
|
+
dispatch(cell, call);
|
|
302
|
+
}
|
|
303
|
+
} finally {
|
|
304
|
+
cell.running = false;
|
|
305
|
+
}
|
|
364
306
|
};
|
|
365
307
|
|
|
366
308
|
const watchStall = (cell: CommandsCell) => {
|
|
@@ -378,7 +320,7 @@ const watchStall = (cell: CommandsCell) => {
|
|
|
378
320
|
|
|
379
321
|
export const useStatewireCommands = <THandlers extends Statewire.Commands>(
|
|
380
322
|
commands:
|
|
381
|
-
| (THandlers & ThisType<StatewireHost.
|
|
323
|
+
| (THandlers & ThisType<StatewireHost.Ctx>)
|
|
382
324
|
| undefined
|
|
383
325
|
| null
|
|
384
326
|
| false,
|
package/src/host.ts
CHANGED
|
@@ -4,17 +4,12 @@ import type { ServerOp } from "./host/draft";
|
|
|
4
4
|
export { StatewireReject } from "./host/engine";
|
|
5
5
|
export { append } from "./host/draft";
|
|
6
6
|
export {
|
|
7
|
-
CommandExecution,
|
|
8
7
|
getStateHost,
|
|
9
8
|
useStatewireState,
|
|
10
9
|
useStatewireCommands,
|
|
11
10
|
useStatewireEmit,
|
|
12
11
|
} from "./host/host";
|
|
13
12
|
|
|
14
|
-
declare const executionResult: unique symbol;
|
|
15
|
-
|
|
16
|
-
type UnwrapExecution<V> = V extends StatewireHost.Execution<infer T> ? T : V;
|
|
17
|
-
|
|
18
13
|
export namespace StatewireHost {
|
|
19
14
|
export type StateCtl<TState = unknown> = {
|
|
20
15
|
/** Apply server-vocabulary ops (root replace = whole-state swap). */
|
|
@@ -31,23 +26,18 @@ export namespace StatewireHost {
|
|
|
31
26
|
export type StateSource = Statewire.StateSource;
|
|
32
27
|
|
|
33
28
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
29
|
+
* Per-invocation handle bound as `this` of a command handler: `caller` is
|
|
30
|
+
* the submitting client, `ack()` releases the client's optimistic overlay /
|
|
31
|
+
* ack watermark before the handler settles (idempotent, a no-op after
|
|
32
|
+
* settle). The command settles when the handler's returned promise does.
|
|
38
33
|
*/
|
|
39
|
-
export type
|
|
40
|
-
readonly
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
export type ExecutionResolvers<T = void> = {
|
|
44
|
-
ack: () => void;
|
|
45
|
-
resolve: (value: T) => void;
|
|
46
|
-
reject: (error: unknown) => void;
|
|
34
|
+
export type Ctx = {
|
|
35
|
+
readonly caller: Caller;
|
|
36
|
+
readonly ack: () => void;
|
|
47
37
|
};
|
|
48
38
|
|
|
49
39
|
/**
|
|
50
|
-
* The submitting client
|
|
40
|
+
* The submitting client (`this.caller` in a command handler). Holdable
|
|
51
41
|
* across a long run: `clientId` is the client's `Statewire-Client-Id`
|
|
52
42
|
* (in-process dispatch has none — reading it throws), `context` reads the
|
|
53
43
|
* client's current ambient context live (null until the client sends one),
|
|
@@ -62,7 +52,7 @@ export namespace StatewireHost {
|
|
|
62
52
|
export type HandledCommands<THandlers extends Statewire.Commands> = {
|
|
63
53
|
[K in keyof THandlers]: (
|
|
64
54
|
...args: Parameters<THandlers[K]>
|
|
65
|
-
) =>
|
|
55
|
+
) => Awaited<ReturnType<THandlers[K]>>;
|
|
66
56
|
};
|
|
67
57
|
|
|
68
58
|
export type Fin = {
|