statewire 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -13
- package/dist/client/changes.d.ts +2 -2
- package/dist/client/changes.d.ts.map +1 -1
- package/dist/client/changes.js +3 -10
- package/dist/client/changes.js.map +1 -1
- package/dist/client/connection.d.ts +18 -39
- package/dist/client/connection.d.ts.map +1 -1
- package/dist/client/connection.js +61 -54
- package/dist/client/connection.js.map +1 -1
- package/dist/client/core.d.ts +7 -11
- package/dist/client/core.d.ts.map +1 -1
- package/dist/client/core.js +58 -55
- package/dist/client/core.js.map +1 -1
- package/dist/client/session.d.ts +8 -25
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +56 -36
- package/dist/client/session.js.map +1 -1
- package/dist/client/sessionStore.d.ts +1 -1
- package/dist/client/sessionStore.js.map +1 -1
- package/dist/client/timing.d.ts +2 -1
- package/dist/client/timing.d.ts.map +1 -1
- package/dist/client/timing.js +5 -1
- package/dist/client/timing.js.map +1 -1
- package/dist/host/adapter.d.ts +8 -0
- package/dist/host/adapter.d.ts.map +1 -0
- package/dist/host/adapter.js +23 -0
- package/dist/host/adapter.js.map +1 -0
- package/dist/host/draft.d.ts +21 -7
- package/dist/host/draft.d.ts.map +1 -1
- package/dist/host/draft.js +190 -103
- package/dist/host/draft.js.map +1 -1
- package/dist/host/host.d.ts +18 -35
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js +81 -53
- package/dist/host/host.js.map +1 -1
- package/dist/host/socket.d.ts +14 -0
- package/dist/host/socket.d.ts.map +1 -0
- package/dist/host/socket.js +459 -0
- package/dist/host/socket.js.map +1 -0
- package/dist/host-internal.d.ts +27 -0
- package/dist/host-internal.d.ts.map +1 -0
- package/dist/host-internal.js +3 -0
- package/dist/host.d.ts +44 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +4 -0
- package/dist/index.d.ts +5 -28
- package/dist/index.js +2 -3
- package/dist/statewire.d.ts +94 -10
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js +23 -35
- package/dist/statewire.js.map +1 -1
- package/dist/transports/fake-sse.d.ts +12 -0
- package/dist/transports/fake-sse.d.ts.map +1 -0
- package/dist/transports/fake-sse.js +39 -0
- package/dist/transports/fake-sse.js.map +1 -0
- package/dist/transports/fixtures.d.ts +33 -0
- package/dist/transports/fixtures.d.ts.map +1 -0
- package/dist/transports/fixtures.js +50 -0
- package/dist/transports/fixtures.js.map +1 -0
- package/dist/transports/remote.d.ts +6 -13
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/transports/remote.js +5 -24
- package/dist/transports/remote.js.map +1 -1
- package/dist/transports/sse.d.ts +10 -20
- package/dist/transports/sse.d.ts.map +1 -1
- package/dist/transports/sse.js +42 -41
- package/dist/transports/sse.js.map +1 -1
- package/dist/transports/testing.d.ts +4 -3
- package/dist/transports/testing.d.ts.map +1 -1
- package/dist/transports/testing.js +19 -46
- package/dist/transports/testing.js.map +1 -1
- package/dist/transports/ws.d.ts +4 -13
- package/dist/transports/ws.d.ts.map +1 -1
- package/dist/transports/ws.js +57 -60
- package/dist/transports/ws.js.map +1 -1
- package/dist/types.d.ts +53 -100
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/util/draftTraps.d.ts +6 -0
- package/dist/util/draftTraps.d.ts.map +1 -0
- package/dist/util/draftTraps.js +25 -0
- package/dist/util/draftTraps.js.map +1 -0
- package/dist/util/macrotask.d.ts +5 -0
- package/dist/util/macrotask.d.ts.map +1 -0
- package/dist/util/macrotask.js +25 -0
- package/dist/util/macrotask.js.map +1 -0
- package/dist/util/methodProxy.d.ts +5 -0
- package/dist/util/methodProxy.d.ts.map +1 -0
- package/dist/util/methodProxy.js +17 -0
- package/dist/util/methodProxy.js.map +1 -0
- package/dist/util/randomId.d.ts +5 -0
- package/dist/util/randomId.d.ts.map +1 -0
- package/dist/util/randomId.js +6 -0
- package/dist/util/randomId.js.map +1 -0
- package/dist/{client/optimistic.d.ts → util/recordMutations.d.ts} +2 -2
- package/dist/util/recordMutations.d.ts.map +1 -0
- package/dist/{client/optimistic.js → util/recordMutations.js} +5 -24
- package/dist/util/recordMutations.js.map +1 -0
- package/dist/util/statePath.d.ts +14 -0
- package/dist/util/statePath.d.ts.map +1 -0
- package/dist/util/statePath.js +31 -0
- package/dist/util/statePath.js.map +1 -0
- package/dist/wire/meta.d.ts +6 -1
- package/dist/wire/meta.d.ts.map +1 -1
- package/dist/wire/meta.js +30 -28
- package/dist/wire/meta.js.map +1 -1
- package/dist/wire/ops.d.ts +5 -26
- package/dist/wire/ops.d.ts.map +1 -1
- package/dist/wire/ops.js +67 -80
- package/dist/wire/ops.js.map +1 -1
- package/package.json +15 -11
- package/src/client/changes.ts +5 -15
- package/src/client/connection.ts +87 -113
- package/src/client/core.ts +57 -91
- package/src/client/session.ts +87 -70
- package/src/client/sessionStore.ts +1 -1
- package/src/client/timing.ts +6 -0
- package/src/host/adapter.ts +33 -0
- package/src/host/draft.ts +293 -125
- package/src/host/host.ts +107 -106
- package/src/host/socket.ts +542 -0
- package/src/host-internal.ts +28 -0
- package/src/host.ts +61 -0
- package/src/index.ts +2 -62
- package/src/statewire.ts +154 -61
- package/src/transports/fake-sse.ts +51 -0
- package/src/transports/fixtures.ts +60 -0
- package/src/transports/remote.ts +12 -54
- package/src/transports/sse.ts +68 -81
- package/src/transports/testing.ts +27 -59
- package/src/transports/ws.ts +76 -92
- package/src/types.ts +50 -131
- package/src/util/draftTraps.ts +32 -0
- package/src/util/macrotask.ts +25 -0
- package/src/util/methodProxy.ts +16 -0
- package/src/util/randomId.ts +4 -0
- package/src/{client/optimistic.ts → util/recordMutations.ts} +0 -0
- package/src/util/statePath.ts +61 -0
- package/src/wire/meta.ts +42 -47
- package/src/wire/ops.ts +107 -106
- package/LICENSE +0 -21
- package/dist/client/optimistic.d.ts.map +0 -1
- package/dist/client/optimistic.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/server.d.ts +0 -15
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js +0 -3
- package/src/server.ts +0 -32
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.js","names":["applyOps","StatewirePath","StatewireResponse","bindCommandsHost","CommandsInvocation","CommandsListener","StatewireStateSource","createMemorySessionStore","StatewireSessionRecord","StatewireSessionStore","backoff","StatewireSendError","SendFailure","StatewireSendFailure","RemoteLink","StatewireSubmitResult","StatewireWireCommand","SessionOptions","sessionStore","PendingState","at","pendingMarked","failedAttempts","posted","Pending","seq","command","inv","state","resolve","payload","reject","error","Outcome","ok","failure","isResolved","isStateSettled","legalTransitions","Record","queued","trying","done","transition","s","Session","entry","next","outcome","prev","includes","Error","settles","undefined","covers","lifecycle","onCovered","onSettle","type","pending","filter","p","carried","Exclude","newClientId","Date","now","toString","Math","random","slice","rejectedSend","Promise","result","catch","AttachState","record","recordPersisted","store","lastAck","canonical","lastPublished","changed","source","attach","attachEpoch","running","pumping","transport","waiters","notify","emitEvent","event","send","commands","params","createCommandsProxy","memo","Map","Proxy","get","_target","method","fn","set","createSession","options","stored","load","clientId","nextSeq","root","takeChanged","setOnChange","listener","sendCommand","setListener","publish","wake","waiter","waitForWake","r","push","settleAll","detach","verdict","cause","evicted","unmount","gone","superseded","const","batch","detachFailure","fate","cascades","some","evict","save","noteAcked","onAck","spliceAcked","settleVoid","protocolError","message","applyResponse","response","find","code","ingestOps","ops","applied","changedPaths","events","publishIngested","handleSnapshot","lastSeq","connection","reconnect","pump","handleOps","res","ack","horizon","max","handleFinish","reason","isFinished","sendable","epoch","retryAfterMs","delay","setTimeout","onPosted","console","start","stop","setTransport","ts","onStart"],"sources":["../../src/client/session.ts"],"sourcesContent":["import { 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 { StatewireStateSource } from \"../types\";\nimport { createMemorySessionStore } from \"./sessionStore\";\nimport type {\n StatewireSessionRecord,\n StatewireSessionStore,\n} from \"./sessionStore\";\nimport { backoff } from \"./timing\";\nimport { StatewireSendError } from \"../types\";\nimport type { SendFailure as StatewireSendFailure } from \"../types\";\nimport type {\n RemoteLink,\n StatewireSubmitResult,\n StatewireWireCommand,\n} from \"../transports/sse\";\n\nexport type SessionOptions = {\n sessionStore?: StatewireSessionStore | undefined;\n};\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 readonly inv: CommandsInvocation;\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: StatewireSendFailure };\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\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) s.lifecycle?.onCovered?.(entry.inv);\n if (settles && outcome !== undefined) {\n if (outcome.ok) {\n entry.resolve(outcome.payload);\n s.lifecycle?.onSettle(entry.inv, {\n type: \"accepted\",\n payload: outcome.payload,\n });\n } else {\n entry.reject(new StatewireSendError(outcome.failure));\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 = () =>\n `c-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n\nconst rejectedSend = (failure: StatewireSendFailure): Promise<unknown> => {\n const result = Promise.reject<unknown>(new StatewireSendError(failure));\n result.catch(() => {});\n return result;\n};\n\ntype AttachState = \"detached\" | \"attached\" | \"finished\";\n\ntype Session = {\n record: StatewireSessionRecord;\n recordPersisted: boolean;\n store: StatewireSessionStore;\n lastAck: number;\n pending: Pending[];\n canonical: unknown;\n lastPublished: unknown;\n changed: StatewirePath[];\n source: StatewireStateSource;\n attach: AttachState;\n attachEpoch: number;\n running: boolean;\n pumping: boolean;\n transport: RemoteLink | null;\n waiters: (() => void)[];\n lifecycle: CommandsListener | null;\n notify: () => void;\n emitEvent: (event: unknown) => void;\n send: (command: StatewireWireCommand) => Promise<unknown>;\n commands: Record<string, (...params: unknown[]) => Promise<unknown>>;\n};\n\nconst createCommandsProxy = (\n send: (command: StatewireWireCommand) => Promise<unknown>,\n): Session[\"commands\"] => {\n const memo = new Map<string, (...params: unknown[]) => Promise<unknown>>();\n return new Proxy({} as Session[\"commands\"], {\n get: (_target, method) => {\n if (typeof method !== \"string\") return undefined;\n let fn = memo.get(method);\n if (fn === undefined) {\n fn = (...params: unknown[]) => send({ method, params });\n memo.set(method, fn);\n }\n return fn;\n },\n });\n};\n\nconst createSession = (options: SessionOptions): 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 recordPersisted: stored !== null,\n store,\n lastAck: -1,\n pending: [],\n canonical: undefined,\n lastPublished: undefined,\n changed: [],\n source: {\n root: () => s.canonical,\n takeChanged: () => {\n const changed = s.changed;\n s.changed = [];\n return changed;\n },\n setOnChange: (listener) => {\n s.notify = listener ?? (() => {});\n },\n },\n attach: \"detached\",\n attachEpoch: 0,\n running: false,\n pumping: false,\n transport: null,\n waiters: [],\n lifecycle: null,\n notify: () => {},\n emitEvent: () => {},\n send: (command) => sendCommand(s, command),\n commands: createCommandsProxy((command) => s.send(command)),\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 settleAll = (\n s: Session,\n detach: \"evicted\" | \"unmount\" | \"gone\" | \"superseded\",\n verdict?: {\n entry: Pending;\n failure: StatewireSendFailure;\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): StatewireSendFailure => {\n const state = entry.state;\n if (state.at === \"awaiting-result\")\n return { fate: \"result-unavailable\", cause };\n if (state.at === \"queued\") {\n const cascades = batch.some(\n (p) => p.seq < entry.seq && p.state.at !== \"queued\",\n );\n return { fate: \"aborted\", cause: cascades ? \"cascade\" : cause };\n }\n return { fate: \"lost\", cause };\n };\n for (const entry of [...batch]) {\n const failure =\n entry === verdict?.entry ? verdict.failure : detachFailure(entry);\n transition(s, entry, { at: \"done\" }, { ok: false, failure });\n }\n};\n\nconst evict = (\n s: Session,\n verdict?: {\n entry: Pending;\n failure: StatewireSendFailure;\n },\n) => {\n settleAll(s, \"evicted\", verdict);\n s.record = { clientId: newClientId(), nextSeq: 1 };\n s.store.save(s.record);\n s.attach = \"detached\";\n 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 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: response.message ?? \"\",\n }\n : {\n fate: \"rejected\",\n cause: \"server\",\n message: response.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 = (\n s: Session,\n events: { event: unknown; state: unknown }[],\n) => {\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 === \"ended\" || 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 = entry.state;\n if (state.at === \"queued\" || state.at === \"done\") continue;\n transition(s, entry, {\n ...state,\n failedAttempts: state.failedAttempts + 1,\n });\n const { retryAfterMs } = error as { retryAfterMs?: unknown };\n const delay =\n typeof retryAfterMs === \"number\"\n ? retryAfterMs\n : backoff(state.failedAttempts + 1);\n await new Promise((r) => setTimeout(r, delay));\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 s.lifecycle?.onPosted?.(entry.inv);\n if (response !== undefined) {\n applyResponse(s, response);\n publish(s);\n }\n } else if (result.type === \"rejected\") {\n const { message, payload } = result;\n transition(\n s,\n entry,\n { at: \"done\" },\n {\n ok: false,\n failure: {\n fate: \"rejected\",\n cause: \"server\",\n message,\n ...(payload !== undefined && { payload }),\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 = entry.state;\n const failure: StatewireSendFailure =\n state.at === \"awaiting-result\"\n ? { fate: \"result-unavailable\", cause: \"session-lost\" }\n : state.at === \"queued\" || state.at === \"done\"\n ? { fate: \"aborted\", cause: \"session-lost\" }\n : {\n fate: state.failedAttempts > 0 ? \"lost\" : \"aborted\",\n cause: \"session-lost\",\n };\n evict(s, { entry, failure });\n s.transport?.connection.reconnect();\n }\n }\n }\n } finally {\n s.pumping = false;\n }\n};\n\nconst start = (s: Session) => {\n if (!s.recordPersisted) {\n s.recordPersisted = true;\n s.store.save(s.record);\n }\n s.running = true;\n void pump(s);\n};\n\nconst stop = (s: Session) => {\n s.running = false;\n wake(s);\n settleAll(s, \"unmount\");\n};\n\nconst setTransport = (s: Session, transport: RemoteLink) => {\n if (s.transport === transport) return;\n s.transport = transport;\n wake(s);\n};\n\nconst sendCommand = (\n s: Session,\n command: StatewireWireCommand,\n): Promise<unknown> => {\n if (!s.running) {\n return rejectedSend({ fate: \"aborted\", cause: \"unmount\" });\n }\n if (isFinished(s)) {\n return rejectedSend({ fate: \"aborted\", cause: \"stream-gone\" });\n }\n const seq = s.record.nextSeq;\n s.record = { ...s.record, nextSeq: seq + 1 };\n s.store.save(s.record);\n const entry: Pending = {\n seq,\n command,\n inv: {\n method: command.method,\n params: command.params,\n ts: Date.now(),\n seq,\n clientId: s.record.clientId,\n },\n state: { at: \"queued\", pendingMarked: false },\n resolve: () => {},\n reject: () => {},\n };\n const result = new Promise<unknown>((resolve, reject) => {\n entry.resolve = resolve;\n entry.reject = reject;\n });\n result.catch(() => {});\n s.pending.push(entry);\n s.lifecycle?.onStart(entry.inv);\n void pump(s);\n return result;\n};\n\nexport {\n createSession,\n handleSnapshot,\n handleFinish,\n handleOps,\n setTransport,\n start,\n stop,\n};\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","record","ctx","recordPersisted","store","lastAck","canonical","lastPublished","changed","source","StateSource","attach","attachEpoch","running","pumping","transport","waiters","notify","emitEvent","event","send","commands","params","createSession","options","sessionStore","stored","load","clientId","nextSeq","onSnapshot","args","handleSnapshot","onOps","handleOps","onFinish","handleFinish","snapshot","takeChanged","setOnChange","listener","sendCommand","method","setListener","publish","wake","waiter","waitForWake","r","push","sleepUntilWake","delayMs","clearTimeout","timer","setTimeout","w","settleAll","detach","verdict","cause","evicted","unmount","gone","superseded","const","batch","detachFailure","fate","cascades","some","evict","save","noteAcked","onAck","spliceAcked","settleVoid","protocolError","message","applyResponse","response","find","code","ingestOps","ops","applied","changedPaths","events","publishIngested","lastSeq","connection","reconnect","pump","res","ack","horizon","Math","max","reason","isFinished","sendable","epoch","onPosted","console","start","stop","setTransport","ts","Date","now","onStart","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 readonly inv: CommandsInvocation;\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) s.lifecycle?.onCovered?.(entry.inv);\n if (settles && outcome !== undefined) {\n if (outcome.ok) {\n entry.resolve(outcome.payload);\n s.lifecycle?.onSettle(entry.inv, {\n type: \"accepted\",\n payload: outcome.payload,\n });\n } else {\n entry.reject(new StatewireSendError(outcome.failure));\n s.lifecycle?.onSettle(entry.inv, {\n type: \"rejected\",\n error: new StatewireSendError(outcome.failure),\n });\n }\n }\n if (next.at === \"done\") {\n s.pending = s.pending.filter((p) => p !== entry);\n }\n};\n\nconst carried = (\n state: Exclude<PendingState, { at: \"done\" }>,\n): { posted: boolean; failedAttempts: number } =>\n state.at === \"queued\"\n ? { posted: false, failedAttempts: 0 }\n : state.at === \"trying\" || state.at === \"posted\"\n ? { posted: state.at === \"posted\", failedAttempts: state.failedAttempts }\n : { posted: state.posted, failedAttempts: state.failedAttempts };\n\nconst newClientId = () => `c-${randomId()}`;\n\nconst rejectedSend = (\n failure: StatewireClient.SendFailure,\n): Promise<unknown> => {\n const result = Promise.reject<unknown>(new StatewireSendError(failure));\n result.catch(() => {});\n return result;\n};\n\ntype AttachState = \"detached\" | \"attached\" | \"finished\";\n\ntype Session = {\n record: StatewireSessionRecord;\n ctx: StatewireRemoteProps;\n recordPersisted: boolean;\n store: StatewireSessionStore;\n lastAck: number;\n pending: Pending[];\n canonical: unknown;\n lastPublished: unknown;\n changed: StatewirePath[];\n source: Statewire.StateSource;\n attach: AttachState;\n attachEpoch: number;\n running: boolean;\n pumping: boolean;\n transport: RemoteLink | null;\n waiters: (() => void)[];\n lifecycle: CommandsListener | null;\n notify: () => void;\n emitEvent: (event: unknown) => void;\n send: (command: StatewireWireCommand) => Promise<unknown>;\n commands: Record<string, (...params: unknown[]) => Promise<unknown>>;\n};\n\nconst createSession = (options: StatewireSessionStoreOptions): Session => {\n const store = options.sessionStore ?? createMemorySessionStore();\n const stored = store.load();\n const record = stored ?? { clientId: newClientId(), nextSeq: 1 };\n const s: Session = {\n record,\n ctx: {\n get clientId() {\n return s.record.clientId;\n },\n onSnapshot: (args) => handleSnapshot(s, args),\n onOps: (args) => handleOps(s, args),\n onFinish: (args) => handleFinish(s, args),\n },\n recordPersisted: stored !== null,\n store,\n lastAck: -1,\n pending: [],\n canonical: undefined,\n lastPublished: undefined,\n changed: [],\n source: {\n snapshot: () => s.canonical,\n takeChanged: () => {\n const changed = s.changed;\n s.changed = [];\n return changed;\n },\n setOnChange: (listener) => {\n s.notify = listener ?? (() => {});\n },\n },\n attach: \"detached\",\n attachEpoch: 0,\n running: false,\n pumping: false,\n transport: null,\n waiters: [],\n lifecycle: null,\n notify: () => {},\n emitEvent: () => {},\n send: (command) => sendCommand(s, command),\n commands: createMethodProxy((method, params) => s.send({ method, params })),\n };\n bindCommandsHost(s.commands, {\n setListener: (listener) => {\n s.lifecycle = listener;\n },\n });\n return s;\n};\n\nconst publish = (s: Session) => {\n if (s.canonical === s.lastPublished) return;\n s.lastPublished = s.canonical;\n s.notify();\n};\n\nconst wake = (s: Session) => {\n const waiters = s.waiters;\n s.waiters = [];\n for (const waiter of waiters) waiter();\n};\n\nconst waitForWake = (s: Session) => new Promise<void>((r) => s.waiters.push(r));\n\nconst sleepUntilWake = (s: Session, delayMs: number) =>\n new Promise<void>((resolve) => {\n const waiter = () => {\n clearTimeout(timer);\n resolve();\n };\n const timer = setTimeout(() => {\n s.waiters = s.waiters.filter((w) => w !== waiter);\n resolve();\n }, delayMs);\n s.waiters.push(waiter);\n });\n\nconst settleAll = (\n s: Session,\n detach: \"evicted\" | \"unmount\" | \"gone\" | \"superseded\",\n verdict?: {\n entry: Pending;\n failure: StatewireClient.SendFailure;\n },\n) => {\n const cause = (\n {\n evicted: \"session-lost\",\n unmount: \"unmount\",\n gone: \"stream-gone\",\n superseded: \"superseded\",\n } as const\n )[detach];\n const batch = s.pending;\n const detachFailure = (entry: Pending): StatewireClient.SendFailure => {\n const state = entry.state;\n if (state.at === \"awaiting-result\")\n return { fate: \"result-unavailable\", cause };\n if (state.at === \"queued\") {\n const cascades = batch.some(\n (p) => p.seq < entry.seq && p.state.at !== \"queued\",\n );\n return { fate: \"aborted\", cause: cascades ? \"cascade\" : cause };\n }\n return { fate: \"lost\", cause };\n };\n for (const entry of [...batch]) {\n const failure =\n entry === verdict?.entry ? verdict.failure : detachFailure(entry);\n transition(s, entry, { at: \"done\" }, { ok: false, failure });\n }\n};\n\nconst evict = (\n s: Session,\n verdict?: {\n entry: Pending;\n failure: StatewireClient.SendFailure;\n },\n) => {\n settleAll(s, \"evicted\", verdict);\n s.record = { clientId: newClientId(), nextSeq: 1 };\n s.store.save(s.record);\n s.attach = \"detached\";\n 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 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 s.lifecycle?.onPosted?.(entry.inv);\n if (response !== undefined) {\n applyResponse(s, response);\n publish(s);\n }\n } else if (result.type === \"rejected\") {\n transition(\n s,\n entry,\n { at: \"done\" },\n {\n ok: false,\n failure: {\n fate: \"rejected\",\n cause: \"server\",\n message: result.message,\n },\n },\n );\n publish(s);\n } else if (result.type === \"stale-lease\") {\n if (epoch === s.attachEpoch) await waitForWake(s);\n } else if (result.type === \"unknown-client\") {\n // Eviction is never decided on the POST leg: re-attach first and let\n // the snapshot's lastSeq deliver the verdict. An answered attempt\n // proves the server never admitted this seq, so a clean entry requeues.\n if (epoch === s.attachEpoch) {\n const state = entry.state;\n if (state.at === \"trying\" && state.failedAttempts === 0) {\n transition(s, entry, {\n at: \"queued\",\n pendingMarked: state.pendingMarked,\n });\n }\n s.transport?.connection.reconnect();\n while (\n s.running &&\n !isFinished(s) &&\n epoch === s.attachEpoch &&\n clientId === s.record.clientId\n ) {\n await waitForWake(s);\n }\n }\n } else if (result.type === \"seq-gap\") {\n if (clientId === s.record.clientId) {\n console.error(\n `statewire: server reports a seq gap for seq ${entry.seq}; ` +\n \"treating the session as corrupt and rotating the client id\",\n );\n // Every attempt answered means the server never saw it; a thrown\n // attempt may have left the machine unanswered, so the verdict is \"lost\".\n const state = inFlight(entry.state);\n const failure: StatewireClient.SendFailure =\n state.at === \"awaiting-result\"\n ? { fate: \"result-unavailable\", cause: \"session-lost\" }\n : {\n fate: state.failedAttempts > 0 ? \"lost\" : \"aborted\",\n cause: \"session-lost\",\n };\n evict(s, { entry, failure });\n s.transport?.connection.reconnect();\n }\n }\n }\n } finally {\n s.pumping = false;\n }\n};\n\nconst start = (s: Session) => {\n if (!s.recordPersisted) {\n s.recordPersisted = true;\n s.store.save(s.record);\n }\n s.running = true;\n void pump(s);\n};\n\nconst stop = (s: Session) => {\n s.running = false;\n wake(s);\n settleAll(s, \"unmount\");\n};\n\nconst setTransport = (s: Session, transport: RemoteLink) => {\n if (s.transport === transport) return;\n s.transport = transport;\n wake(s);\n};\n\nconst sendCommand = (\n s: Session,\n command: StatewireWireCommand,\n): Promise<unknown> => {\n if (!s.running) {\n return rejectedSend({ fate: \"aborted\", cause: \"unmount\" });\n }\n if (isFinished(s)) {\n return rejectedSend({ fate: \"aborted\", cause: \"stream-gone\" });\n }\n const seq = s.record.nextSeq;\n s.record = { ...s.record, nextSeq: seq + 1 };\n s.store.save(s.record);\n const entry: Pending = {\n seq,\n command,\n inv: {\n method: command.method,\n params: command.params,\n ts: Date.now(),\n seq,\n clientId: s.record.clientId,\n },\n state: { at: \"queued\", pendingMarked: false },\n resolve: () => {},\n reject: () => {},\n };\n const result = new Promise<unknown>((resolve, reject) => {\n entry.resolve = resolve;\n entry.reject = reject;\n });\n result.catch(() => {});\n s.pending.push(entry);\n s.lifecycle?.onStart(entry.inv);\n void pump(s);\n return result;\n};\n\nexport const useStatewireSession = (\n options: StatewireSessionStoreOptions,\n emit: (event: unknown) => void,\n): Session => {\n const [s] = useState(() => createSession(options));\n useEffect(() => {\n s.emitEvent = emit;\n return () => {\n s.emitEvent = () => {};\n };\n }, [s, emit]);\n useEffect(() => {\n start(s);\n return () => stop(s);\n }, [s]);\n return s;\n};\n\nexport { setTransport };\n"],"mappings":";;;;;;;;;AAoDA,MAAMwC,cAAcT,UAClBA,MAAMR,OAAO,kBAAkBQ,MAAMR,OAAO;AAE9C,MAAMkB,kBAAkBV,UACtBA,MAAMR,OAAO,qBAAqBQ,MAAMR,OAAO;AAIjD,MAAMmB,YAAYX,UAAwB;CACxC,IAAIA,MAAMR,OAAO,YAAYQ,MAAMR,OAAO,QACxC,MAAM,IAAIoB,MAAM,cAAcZ,MAAMR,GAAE,6BAA8B;CAEtE,OAAOQ;AACT;AAEA,MAAMa,mBAAqE;CACzEE,QAAQ;EAAC;EAAU;EAAU;EAAmB;EAAgB;CAAM;CAEtEC,QAAQ;EACN;EACA;EACA;EACA;EACA;EACA;CAAM;CAERrB,QAAQ;EAAC;EAAU;EAAU;EAAmB;EAAgB;CAAM;CACtE,mBAAmB,CAAC,mBAAmB,MAAM;CAC7C,gBAAgB,CAAC,gBAAgB,MAAM;CACvCsB,MAAM,CAAA;AACR;AAEA,MAAMC,cACJC,GACAE,OACAC,MACAC,YACG;CACH,MAAMC,OAAOH,MAAMrB;CACnB,IAAI,CAACa,iBAAiBW,KAAKhC,GAAG,CAACiC,SAASH,KAAK9B,EAAE,GAC7C,MAAM,IAAIoB,MACR,yCAAyCY,KAAKhC,GAAE,MAAO8B,KAAK9B,IAC9D;CAEF,MAAMkC,UAAU,CAACjB,WAAWe,IAAI,KAAKf,WAAWa,IAAI;CACpD,IAAII,WAAWH,YAAYI,KAAAA,GACzB,MAAM,IAAIf,MACR,kCAAkCY,KAAKhC,GAAE,MAAO8B,KAAK9B,GAAE,oBACzD;CAEF,MAAMoC,SAAS,CAAClB,eAAec,IAAI,KAAKd,eAAeY,IAAI;CAC3DD,MAAMrB,QAAQsB;CACd,IAAIM,QAAQT,EAAEU,WAAWC,YAAYT,MAAMtB,GAAG;CAC9C,IAAI2B,WAAWH,YAAYI,KAAAA,GACzB,IAAIJ,QAAQjB,IAAI;EACde,MAAMpB,QAAQsB,QAAQrB,OAAO;EAC7BiB,EAAEU,WAAWE,SAASV,MAAMtB,KAAK;GAC/BiC,MAAM;GACN9B,SAASqB,QAAQrB;EACnB,CAAC;CACH,OAAO;EACLmB,MAAMlB,OAAO,IAAIjB,mBAAmBqC,QAAQhB,OAAO,CAAC;EACpDY,EAAEU,WAAWE,SAASV,MAAMtB,KAAK;GAC/BiC,MAAM;GACN5B,OAAO,IAAIlB,mBAAmBqC,QAAQhB,OAAO;EAC/C,CAAC;CACH;CAEF,IAAIe,KAAK9B,OAAO,QACd2B,EAAEc,UAAUd,EAAEc,QAAQC,QAAQC,MAAMA,MAAMd,KAAK;AAEnD;AAEA,MAAMe,WACJpC,UAEAA,MAAMR,OAAO,WACT;CAAEG,QAAQ;CAAOD,gBAAgB;AAAE,IACnCM,MAAMR,OAAO,YAAYQ,MAAMR,OAAO,WACpC;CAAEG,QAAQK,MAAMR,OAAO;CAAUE,gBAAgBM,MAAMN;AAAe,IACtE;CAAEC,QAAQK,MAAML;CAAQD,gBAAgBM,MAAMN;AAAe;AAErE,MAAM4C,oBAAoB,KAAKrD,SAAS;AAExC,MAAMsD,gBACJhC,YACqB;CACrB,MAAMkC,SAASD,QAAQrC,OAAgB,IAAIjB,mBAAmBqB,OAAO,CAAC;CACtEkC,OAAOC,YAAY,CAAC,CAAC;CACrB,OAAOD;AACT;AA4BA,MAAMyB,iBAAiBC,YAAmD;CACxE,MAAMpB,QAAQoB,QAAQC,gBAAgBzF,yBAAyB;CAC/D,MAAM0F,SAAStB,MAAMuB,KAAK;CAE1B,MAAMnD,IAAa;EACjByB,QAFayB,UAAU;GAAEE,UAAUjC,YAAY;GAAGkC,SAAS;EAAE;EAG7D3B,KAAK;GACH,IAAI0B,WAAW;IACb,OAAOpD,EAAEyB,OAAO2B;GAClB;GACAE,aAAaC,SAASC,eAAexD,GAAGuD,IAAI;GAC5CE,QAAQF,SAASG,UAAU1D,GAAGuD,IAAI;GAClCI,WAAWJ,SAASK,aAAa5D,GAAGuD,IAAI;EAC1C;EACA5B,iBAAiBuB,WAAW;EAC5BtB;EACAC,SAAS;EACTf,SAAS,CAAA;EACTgB,WAAWtB,KAAAA;EACXuB,eAAevB,KAAAA;EACfwB,SAAS,CAAA;EACTC,QAAQ;GACN4B,gBAAgB7D,EAAE8B;GAClBgC,mBAAmB;IACjB,MAAM9B,UAAUhC,EAAEgC;IAClBhC,EAAEgC,UAAU,CAAA;IACZ,OAAOA;GACT;GACA+B,cAAcC,aAAa;IACzBhE,EAAEyC,SAASuB,mBAAmB,CAAC;GACjC;EACF;EACA7B,QAAQ;EACRC,aAAa;EACbC,SAAS;EACTC,SAAS;EACTC,WAAW;EACXC,SAAS,CAAA;EACT9B,WAAW;EACX+B,cAAc,CAAC;EACfC,iBAAiB,CAAC;EAClBE,OAAOjE,YAAYsF,YAAYjE,GAAGrB,OAAO;EACzCkE,UAAUhF,mBAAmBqG,QAAQpB,WAAW9C,EAAE4C,KAAK;GAAEsB;GAAQpB;EAAO,CAAC,CAAC;CAC5E;CACA3F,iBAAiB6C,EAAE6C,UAAU,EAC3BsB,cAAcH,aAAa;EACzBhE,EAAEU,YAAYsD;CAChB,EACF,CAAC;CACD,OAAOhE;AACT;AAEA,MAAMoE,WAAWpE,MAAe;CAC9B,IAAIA,EAAE8B,cAAc9B,EAAE+B,eAAe;CACrC/B,EAAE+B,gBAAgB/B,EAAE8B;CACpB9B,EAAEyC,OAAO;AACX;AAEA,MAAM4B,QAAQrE,MAAe;CAC3B,MAAMwC,UAAUxC,EAAEwC;CAClBxC,EAAEwC,UAAU,CAAA;CACZ,KAAK,MAAM8B,UAAU9B,SAAS8B,OAAO;AACvC;AAEA,MAAMC,eAAevE,MAAe,IAAIqB,SAAemD,MAAMxE,EAAEwC,QAAQiC,KAAKD,CAAC,CAAC;AAE9E,MAAME,kBAAkB1E,GAAY2E,YAClC,IAAItD,SAAevC,YAAY;CAC7B,MAAMwF,eAAe;EACnBM,aAAaC,KAAK;EAClB/F,QAAQ;CACV;CACA,MAAM+F,QAAQC,iBAAiB;EAC7B9E,EAAEwC,UAAUxC,EAAEwC,QAAQzB,QAAQgE,MAAMA,MAAMT,MAAM;EAChDxF,QAAQ;CACV,GAAG6F,OAAO;CACV3E,EAAEwC,QAAQiC,KAAKH,MAAM;AACvB,CAAC;AAEH,MAAMU,aACJhF,GACAiF,QACAC,YAIG;CACH,MAAMC,QACJ;EACEC,SAAS;EACTC,SAAS;EACTC,MAAM;EACNC,YAAY;CACd,EACAN;CACF,MAAMQ,QAAQzF,EAAEc;CAChB,MAAM4E,iBAAiBxF,UAAgD;EACrE,MAAMrB,QAAQqB,MAAMrB;EACpB,IAAIA,MAAMR,OAAO,mBACf,OAAO;GAAEsH,MAAM;GAAsBR;EAAM;EAC7C,IAAItG,MAAMR,OAAO,UAIf,OAAO;GAAEsH,MAAM;GAAWR,OAHTM,MAAMI,MACpB7E,MAAMA,EAAEtC,MAAMwB,MAAMxB,OAAOsC,EAAEnC,MAAMR,OAAO,QAEZuH,IAAW,YAAYT;EAAM;EAEhE,OAAO;GAAEQ,MAAM;GAAQR;EAAM;CAC/B;CACA,KAAK,MAAMjF,SAAS,CAAC,GAAGuF,KAAK,GAAG;EAC9B,MAAMrG,UACJc,UAAUgF,SAAShF,QAAQgF,QAAQ9F,UAAUsG,cAAcxF,KAAK;EAClEH,WAAWC,GAAGE,OAAO,EAAE7B,IAAI,OAAO,GAAG;GAAEc,IAAI;GAAOC;EAAQ,CAAC;CAC7D;AACF;AAEA,MAAM0G,SACJ9F,GACAkF,YAIG;CACHF,UAAUhF,GAAG,WAAWkF,OAAO;CAC/BlF,EAAEyB,SAAS;EAAE2B,UAAUjC,YAAY;EAAGkC,SAAS;CAAE;CACjDrD,EAAE4B,MAAMmE,KAAK/F,EAAEyB,MAAM;CACrBzB,EAAEmC,SAAS;CACXiC,QAAQpE,CAAC;CACTqE,KAAKrE,CAAC;AACR;AAEA,MAAMgG,aAAahG,GAAYtB,QAAgB;CAC7C,KAAK,MAAMwB,SAASF,EAAEc,SAAS;EAC7B,IAAIZ,MAAMxB,MAAMA,OAAOa,eAAeW,MAAMrB,KAAK,GAAG;EACpDmB,EAAEU,WAAWuF,MAAM/F,MAAMtB,GAAG;CAC9B;AACF;AAEA,MAAMsH,eAAelG,GAAYtB,KAAayH,eAAwB;CACpE,KAAK,MAAMjG,SAAS,CAAC,GAAGF,EAAEc,OAAO,GAAG;EAClC,MAAMjC,QAAQqB,MAAMrB;EACpB,IAAIqB,MAAMxB,MAAMA,OAAOa,eAAeV,KAAK,GAAG;EAC9C,IAAIA,MAAMR,OAAO,gBACf0B,WAAWC,GAAGE,OAAO,EAAE7B,IAAI,OAAO,CAAC;OAC9B,IAAI8H,cAAc,CAACtH,MAAMP,eAC9ByB,WAAWC,GAAGE,OAAO,EAAE7B,IAAI,OAAO,GAAG;GAAEc,IAAI;GAAMJ,SAASyB,KAAAA;EAAU,CAAC;OAErET,WAAWC,GAAGE,OAAO;GAAE7B,IAAI;GAAmB,GAAG4C,QAAQpC,KAAK;EAAE,CAAC;CAErE;AACF;AAEA,MAAMuH,iBAAiBC,YAA2B;CAChD,MAAM,IAAI5G,MAAM,8BAA8B4G,SAAS;AACzD;AAEA,MAAMC,iBAAiBtG,GAAYuG,aAAgC;CACjE,MAAMrG,QAAQF,EAAEc,QAAQ0F,MAAMxF,MAAMA,EAAEtC,QAAQ6H,SAAS7H,GAAG;CAC1D,IAAI,CAACwB,OAAO;CACZ,MAAMrB,QAAQqB,MAAMrB;CACpB,IAAIA,MAAMR,OAAO,QAAQ;CACzB,QAAQkI,SAAS1F,MAAjB;EACE,KAAK;GACH,IACEhC,MAAMR,OAAO,YACbQ,MAAMR,OAAO,YACbQ,MAAMR,OAAO,UAEb0B,WAAWC,GAAGE,OAAO;IAAE,GAAGrB;IAAOP,eAAe;GAAK,CAAC;GAExD;EACF,KAAK;GACH,IAAIO,MAAMR,OAAO,gBAAgB;GACjC0B,WACEC,GACAE,OACArB,MAAMR,OAAO,oBACT,EAAEA,IAAI,OAAO,IACb;IAAEA,IAAI;IAAgB,GAAG4C,QAAQpC,KAAK;GAAE,GAC5C;IAAEM,IAAI;IAAMJ,SAASwH,SAASxH;GAAQ,CACxC;GACA;EACF,KAAK;GACHgB,WACEC,GACAE,OACA,EAAE7B,IAAI,OAAO,GACb;IACEc,IAAI;IACJC,SACEmH,SAASE,SAAS,oBACd;KAAEd,MAAM;KAAmBR,OAAO;IAAS,IAC3CoB,SAASE,SAAS,mBAChB;KACEd,MAAM;KACNR,OAAO;KACPkB,SACEE,SAASF,WACTD,cAAc,mCAAmC;IACrD,IACA;KACET,MAAM;KACNR,OAAO;KACPkB,SACEE,SAASF,WACTD,cAAc,mCAAmC;KACnD,GAAIG,SAASxH,YAAYyB,KAAAA,KAAa,EACpCzB,SAASwH,SAASxH,QACpB;IACF;GACV,CACF;GACA;EACF,KAAK;EACL,KAAK;GACH,IAAIF,MAAMR,OAAO,gBAAgB;GACjC0B,WACEC,GACAE,OACArB,MAAMR,OAAO,oBACT,EAAEA,IAAI,OAAO,IACb;IAAEA,IAAI;IAAgB,GAAG4C,QAAQpC,KAAK;GAAE,GAC5C;IAAEM,IAAI;IAAOC,SAAS;KAAEuG,MAAMY,SAAS1F;KAAMsE,OAAO;IAAS;GAAE,CACjE;GACA;CACJ;AACF;AAEA,MAAMuB,aAAa1G,GAAY2G,QAAmB;CAChD,MAAMC,UAAU5J,SAASgD,EAAE8B,WAAW6E,GAAG;CACzC3G,EAAE8B,YAAY8E,QAAQ/H;CACtBmB,EAAEgC,QAAQyC,KAAK,GAAGmC,QAAQC,YAAY;CACtC,OAAOD,QAAQE;AACjB;AAEA,MAAMC,mBAAmB/G,GAAY8G,WAAsB;CACzD1C,QAAQpE,CAAC;CACT,KAAK,MAAM2C,SAASmE,QAAQ9G,EAAE0C,UAAUC,KAAK;CAC7C0B,KAAKrE,CAAC;AACR;AAEA,MAAMwD,kBACJxD,GACA,EAAE2G,KAAKK,cACJ;CACH,IAAIA,YAAY,MAAMhH,EAAEmC,WAAW,YAAY;EAC7C2D,MAAM9F,CAAC;EACPA,EAAEuC,WAAW0E,WAAWC,UAAU;EAClCH,gBAAgB/G,GAAG0G,UAAU1G,GAAG2G,GAAG,CAAC;EACpC;CACF;CACA,MAAMG,SAASJ,UAAU1G,GAAG2G,GAAG;CAC/B3G,EAAEmC,SAAS;CACXnC,EAAEoC,eAAe;CAGjB4D,UAAUhG,GAAGgH,OAAO;CACpBd,YAAYlG,GAAGgH,SAAS,KAAK;CAC7BhH,EAAE6B,UAAUmF;CACZ,KAAK,MAAM9G,SAASF,EAAEc,SAAS;EAC7B,MAAMjC,QAAQqB,MAAMrB;EACpB,IAAIqB,MAAMxB,OAAOsI,SAAS;GACxB,IAAInI,MAAMR,OAAO,qBAAqBQ,MAAML,QAC1CuB,WAAWC,GAAGE,OAAO;IAAE,GAAGrB;IAAOL,QAAQ;GAAM,CAAC;GAElD,IAAIK,MAAMR,OAAO,mBAAmB,KAAU2B,CAAC;EACjD,OAAO,IAAInB,MAAMR,OAAO,UAAU;GAEhC0B,WAAWC,GAAGE,OAAO;IACnB7B,IAAI;IACJC,eAAeO,MAAMP;IACrBC,gBAAgBM,MAAMN;GACxB,CAAC;GACD,KAAUyB,CAAC;EACb,OAAO,IAAInB,MAAMR,OAAO,kBAAkBQ,MAAML,QAAQ;GACtDuB,WAAWC,GAAGE,OAAO;IAAE,GAAGrB;IAAOL,QAAQ;GAAM,CAAC;GAChD,KAAUwB,CAAC;EACb;CACF;CACA+G,gBAAgB/G,GAAG8G,MAAM;AAC3B;AAEA,MAAMpD,aACJ1D,GACA,EACE2G,KACAS,KACAC,UAEC;CACH,MAAMC,UAAUD,QAAQ7G,KAAAA,IAAYR,EAAE6B,UAAU0F,KAAKC,IAAIxH,EAAE6B,SAASwF,GAAG;CACvE,IAAID,QAAQ5G,KAAAA,GACL;OAAA,MAAM+F,YAAYa,KACrB,IAAIb,SAAS7H,MAAM4I,SACjBlB,cAAc,6BAA6B;CAAA;CAIjD,MAAMU,SAASJ,UAAU1G,GAAG2G,GAAG;CAC/B,IAAIU,QAAQ7G,KAAAA,GAAWwF,UAAUhG,GAAGqH,GAAG;CACvC,IAAID,QAAQ5G,KAAAA,GAAW,KAAK,MAAM+F,YAAYa,KAAKd,cAActG,GAAGuG,QAAQ;CAC5E,IAAIc,QAAQ7G,KAAAA,GAAW;EACrB0F,YAAYlG,GAAGqH,KAAK,IAAI;EACxBrH,EAAE6B,UAAU0F,KAAKC,IAAIxH,EAAE6B,SAASwF,GAAG;CACrC;CACAN,gBAAgB/G,GAAG8G,MAAM;AAC3B;AAEA,MAAMlD,gBAAgB5D,GAAY,EAAEyH,aAAiC;CACnE,IAAIA,WAAW,aAAaA,WAAW,SAAS;CAChDzH,EAAEmC,SAAS;CACX6C,UAAUhF,GAAGyH,WAAW,eAAe,eAAe,MAAM;CAC5DrD,QAAQpE,CAAC;CACTqE,KAAKrE,CAAC;AACR;AAEA,MAAM0H,cAAc1H,MAAeA,EAAEmC,WAAW;AAEhD,MAAMwF,YAAY3G,MAChBA,EAAEnC,MAAMR,OAAO,YACf2C,EAAEnC,MAAMR,OAAO,aACb2C,EAAEnC,MAAMR,OAAO,qBAAqB2C,EAAEnC,MAAMR,OAAO,mBACnD,CAAC2C,EAAEnC,MAAML;AAEb,MAAM2I,OAAO,OAAOnH,MAAe;CACjC,IAAIA,EAAEsC,SAAS;CACftC,EAAEsC,UAAU;CACZ,IAAI;EACF,OAAOtC,EAAEqC,WAAW,CAACqF,WAAW1H,CAAC,GAAG;GAClC,MAAME,QAAQF,EAAEc,QAAQ0F,KAAKmB,QAAQ;GACrC,IAAI,CAACzH,OAAO;GACZ,MAAMqC,YAAYvC,EAAEuC;GACpB,IAAIvC,EAAEmC,WAAW,cAAc,CAACI,WAAW;IACzC,MAAMgC,YAAYvE,CAAC;IACnB;GACF;GACA,MAAMoD,WAAWpD,EAAEyB,OAAO2B;GAC1B,MAAMwE,QAAQ5H,EAAEoC;GAChB,IAAIlC,MAAMrB,MAAMR,OAAO,UACrB0B,WAAWC,GAAGE,OAAO;IACnB7B,IAAI;IACJC,eAAe4B,MAAMrB,MAAMP;IAC3BC,gBAAgB;GAClB,CAAC;GAEH,IAAI+C;GACJ,IAAI;IACFA,SAAS,MAAMiB,UAAUK,KAAK;KAC5BlE,KAAKwB,MAAMxB;KACXC,SAASuB,MAAMvB;IACjB,CAAC;GACH,SAASM,OAAO;IACd,IAAI,CAACe,EAAEqC,WAAWqF,WAAW1H,CAAC,GAAG;IACjC,IAAI,CAACA,EAAEc,QAAQR,SAASJ,KAAK,GAAG;IAChC,MAAMrB,QAAQW,SAASU,MAAMrB,KAAK;IAClCkB,WAAWC,GAAGE,OAAO;KACnB,GAAGrB;KACHN,gBAAgBM,MAAMN,iBAAiB;IACzC,CAAC;IACD,MAAMmG,eAAe1E,GAAGpC,WAAWqB,OAAOJ,MAAMN,iBAAiB,CAAC,CAAC;IACnE;GACF;GACA,IAAI,CAACyB,EAAEqC,SAAS;GAChB,IAAI,CAACrC,EAAEc,QAAQR,SAASJ,KAAK,GAAG;GAChC,IAAIoB,OAAOT,SAAS,WAAW;IAC7B,MAAM0F,WAAWjF,OAAOiF;IACxB,MAAM1H,QAAQqB,MAAMrB;IACpB,IAAIA,MAAMR,OAAO,YAAYQ,MAAMR,OAAO,UACxC0B,WAAWC,GAAGE,OAAO;KACnB7B,IAAI;KACJC,eAAeO,MAAMP;KACrBC,gBAAgBM,MAAMN;IACxB,CAAC;SACI,IACLM,MAAMR,OAAO,qBACbQ,MAAMR,OAAO,gBAEb0B,WAAWC,GAAGE,OAAO;KAAE,GAAGrB;KAAOL,QAAQ;IAAK,CAAC;IAEjDwB,EAAEU,WAAWmH,WAAW3H,MAAMtB,GAAG;IACjC,IAAI2H,aAAa/F,KAAAA,GAAW;KAC1B8F,cAActG,GAAGuG,QAAQ;KACzBnC,QAAQpE,CAAC;IACX;GACF,OAAO,IAAIsB,OAAOT,SAAS,YAAY;IACrCd,WACEC,GACAE,OACA,EAAE7B,IAAI,OAAO,GACb;KACEc,IAAI;KACJC,SAAS;MACPuG,MAAM;MACNR,OAAO;MACPkB,SAAS/E,OAAO+E;KAClB;IACF,CACF;IACAjC,QAAQpE,CAAC;GACX,OAAO,IAAIsB,OAAOT,SAAS,eACrB+G;QAAAA,UAAU5H,EAAEoC,aAAa,MAAMmC,YAAYvE,CAAC;GAAA,OAC3C,IAAIsB,OAAOT,SAAS,kBAIrB+G;QAAAA,UAAU5H,EAAEoC,aAAa;KAC3B,MAAMvD,QAAQqB,MAAMrB;KACpB,IAAIA,MAAMR,OAAO,YAAYQ,MAAMN,mBAAmB,GACpDwB,WAAWC,GAAGE,OAAO;MACnB7B,IAAI;MACJC,eAAeO,MAAMP;KACvB,CAAC;KAEH0B,EAAEuC,WAAW0E,WAAWC,UAAU;KAClC,OACElH,EAAEqC,WACF,CAACqF,WAAW1H,CAAC,KACb4H,UAAU5H,EAAEoC,eACZgB,aAAapD,EAAEyB,OAAO2B,UAEtB,MAAMmB,YAAYvE,CAAC;IAEvB;UACK,IAAIsB,OAAOT,SAAS,WACrBuC;QAAAA,aAAapD,EAAEyB,OAAO2B,UAAU;KAClC0E,QAAQ7I,MACN,+CAA+CiB,MAAMxB,IAAG,6DAE1D;KAGA,MAAMG,QAAQW,SAASU,MAAMrB,KAAK;KAClC,MAAMO,UACJP,MAAMR,OAAO,oBACT;MAAEsH,MAAM;MAAsBR,OAAO;KAAe,IACpD;MACEQ,MAAM9G,MAAMN,iBAAiB,IAAI,SAAS;MAC1C4G,OAAO;KACT;KACNW,MAAM9F,GAAG;MAAEE;MAAOd;KAAQ,CAAC;KAC3BY,EAAEuC,WAAW0E,WAAWC,UAAU;IACpC;;EAEJ;CACF,UAAU;EACRlH,EAAEsC,UAAU;CACd;AACF;AAEA,MAAMyF,SAAS/H,MAAe;CAC5B,IAAI,CAACA,EAAE2B,iBAAiB;EACtB3B,EAAE2B,kBAAkB;EACpB3B,EAAE4B,MAAMmE,KAAK/F,EAAEyB,MAAM;CACvB;CACAzB,EAAEqC,UAAU;CACZ,KAAUrC,CAAC;AACb;AAEA,MAAMgI,QAAQhI,MAAe;CAC3BA,EAAEqC,UAAU;CACZgC,KAAKrE,CAAC;CACNgF,UAAUhF,GAAG,SAAS;AACxB;AAEA,MAAMiI,gBAAgBjI,GAAYuC,cAA0B;CAC1D,IAAIvC,EAAEuC,cAAcA,WAAW;CAC/BvC,EAAEuC,YAAYA;CACd8B,KAAKrE,CAAC;AACR;AAEA,MAAMiE,eACJjE,GACArB,YACqB;CACrB,IAAI,CAACqB,EAAEqC,SACL,OAAOjB,aAAa;EAAEuE,MAAM;EAAWR,OAAO;CAAU,CAAC;CAE3D,IAAIuC,WAAW1H,CAAC,GACd,OAAOoB,aAAa;EAAEuE,MAAM;EAAWR,OAAO;CAAc,CAAC;CAE/D,MAAMzG,MAAMsB,EAAEyB,OAAO4B;CACrBrD,EAAEyB,SAAS;EAAE,GAAGzB,EAAEyB;EAAQ4B,SAAS3E,MAAM;CAAE;CAC3CsB,EAAE4B,MAAMmE,KAAK/F,EAAEyB,MAAM;CACrB,MAAMvB,QAAiB;EACrBxB;EACAC;EACAC,KAAK;GACHsF,QAAQvF,QAAQuF;GAChBpB,QAAQnE,QAAQmE;GAChBoF,IAAIC,KAAKC,IAAI;GACb1J;GACA0E,UAAUpD,EAAEyB,OAAO2B;EACrB;EACAvE,OAAO;GAAER,IAAI;GAAUC,eAAe;EAAM;EAC5CQ,eAAe,CAAC;EAChBE,cAAc,CAAC;CACjB;CACA,MAAMsC,SAAS,IAAID,SAAkBvC,SAASE,WAAW;EACvDkB,MAAMpB,UAAUA;EAChBoB,MAAMlB,SAASA;CACjB,CAAC;CACDsC,OAAOC,YAAY,CAAC,CAAC;CACrBvB,EAAEc,QAAQ2D,KAAKvE,KAAK;CACpBF,EAAEU,WAAW2H,QAAQnI,MAAMtB,GAAG;CAC9B,KAAUoB,CAAC;CACX,OAAOsB;AACT;AAEA,MAAagH,uBACXtF,SACAuF,SACY;CACZ,MAAM,CAACvI,KAAKjD,eAAegG,cAAcC,OAAO,CAAC;CACjDlG,gBAAgB;EACdkD,EAAE0C,YAAY6F;EACd,aAAa;GACXvI,EAAE0C,kBAAkB,CAAC;EACvB;CACF,GAAG,CAAC1C,GAAGuI,IAAI,CAAC;CACZzL,gBAAgB;EACdiL,MAAM/H,CAAC;EACP,aAAagI,KAAKhI,CAAC;CACrB,GAAG,CAACA,CAAC,CAAC;CACN,OAAOA;AACT"}
|
|
@@ -8,7 +8,7 @@ type StatewireSessionStore = {
|
|
|
8
8
|
save: (record: StatewireSessionRecord) => void;
|
|
9
9
|
};
|
|
10
10
|
type StatewireSessionStoreOptions = {
|
|
11
|
-
sessionStore?: StatewireSessionStore;
|
|
11
|
+
sessionStore?: StatewireSessionStore | undefined;
|
|
12
12
|
};
|
|
13
13
|
declare const createMemorySessionStore: () => StatewireSessionStore;
|
|
14
14
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessionStore.js","names":["StatewireSessionRecord","clientId","nextSeq","StatewireSessionStore","load","save","record","StatewireSessionStoreOptions","sessionStore","createMemorySessionStore","next"],"sources":["../../src/client/sessionStore.ts"],"sourcesContent":["export type StatewireSessionRecord = {\n clientId: string;\n nextSeq: number;\n};\n\nexport type StatewireSessionStore = {\n load: () => StatewireSessionRecord | null;\n save: (record: StatewireSessionRecord) => void;\n};\n\nexport type StatewireSessionStoreOptions = {\n sessionStore?: StatewireSessionStore;\n};\n\nexport const createMemorySessionStore = (): StatewireSessionStore => {\n let record: StatewireSessionRecord | null = null;\n return {\n load: () => record,\n save: (next) => {\n record = next;\n },\n };\n};\n"],"mappings":";AAcA,MAAaS,iCAAwD;CACnE,IAAIH,SAAwC;CAC5C,OAAO;EACLF,YAAYE;EACZD,OAAOK,SAAS;GACdJ,SAASI;EACX;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"sessionStore.js","names":["StatewireSessionRecord","clientId","nextSeq","StatewireSessionStore","load","save","record","StatewireSessionStoreOptions","sessionStore","createMemorySessionStore","next"],"sources":["../../src/client/sessionStore.ts"],"sourcesContent":["export type StatewireSessionRecord = {\n clientId: string;\n nextSeq: number;\n};\n\nexport type StatewireSessionStore = {\n load: () => StatewireSessionRecord | null;\n save: (record: StatewireSessionRecord) => void;\n};\n\nexport type StatewireSessionStoreOptions = {\n sessionStore?: StatewireSessionStore | undefined;\n};\n\nexport const createMemorySessionStore = (): StatewireSessionStore => {\n let record: StatewireSessionRecord | null = null;\n return {\n load: () => record,\n save: (next) => {\n record = next;\n },\n };\n};\n"],"mappings":";AAcA,MAAaS,iCAAwD;CACnE,IAAIH,SAAwC;CAC5C,OAAO;EACLF,YAAYE;EACZD,OAAOK,SAAS;GACdJ,SAASI;EACX;CACF;AACF"}
|
package/dist/client/timing.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ declare const timing: {
|
|
|
5
5
|
heartbeatTimeoutMs: number;
|
|
6
6
|
};
|
|
7
7
|
declare const backoff: (attempt: number) => number;
|
|
8
|
+
declare const retryDelay: (error: unknown, attempt: number) => number;
|
|
8
9
|
//#endregion
|
|
9
|
-
export { backoff, timing };
|
|
10
|
+
export { backoff, retryDelay, timing };
|
|
10
11
|
//# sourceMappingURL=timing.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timing.d.ts","names":[],"sources":["../../src/client/timing.ts"],"mappings":";cAAa;;;;;cAMA,UAAW"}
|
|
1
|
+
{"version":3,"file":"timing.d.ts","names":[],"sources":["../../src/client/timing.ts"],"mappings":";cAAa;;;;;cAMA,UAAW;cAGX,aAAc,gBAAgB"}
|
package/dist/client/timing.js
CHANGED
|
@@ -5,7 +5,11 @@ const timing = {
|
|
|
5
5
|
heartbeatTimeoutMs: 3e4
|
|
6
6
|
};
|
|
7
7
|
const backoff = (attempt) => Math.min(timing.backoffBaseMs * 2 ** (attempt - 1), timing.backoffMaxMs);
|
|
8
|
+
const retryDelay = (error, attempt) => {
|
|
9
|
+
const retryAfterMs = error?.retryAfterMs;
|
|
10
|
+
return typeof retryAfterMs === "number" ? retryAfterMs : backoff(attempt);
|
|
11
|
+
};
|
|
8
12
|
//#endregion
|
|
9
|
-
export { backoff, timing };
|
|
13
|
+
export { backoff, retryDelay, timing };
|
|
10
14
|
|
|
11
15
|
//# sourceMappingURL=timing.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timing.js","names":["timing","backoffBaseMs","backoffMaxMs","heartbeatTimeoutMs","backoff","attempt","Math","min"],"sources":["../../src/client/timing.ts"],"sourcesContent":["export const timing = {\n backoffBaseMs: 250,\n backoffMaxMs: 30_000,\n heartbeatTimeoutMs: 30_000,\n};\n\nexport const backoff = (attempt: number) =>\n Math.min(timing.backoffBaseMs * 2 ** (attempt - 1), timing.backoffMaxMs);\n"],"mappings":";AAAA,MAAaA,SAAS;CACpBC,eAAe;CACfC,cAAc;CACdC,oBAAoB;AACtB;AAEA,MAAaC,WAAWC,YACtBC,KAAKC,IAAIP,OAAOC,gBAAgB,MAAMI,UAAU,IAAIL,OAAOE,YAAY"}
|
|
1
|
+
{"version":3,"file":"timing.js","names":["timing","backoffBaseMs","backoffMaxMs","heartbeatTimeoutMs","backoff","attempt","Math","min","retryDelay","error","retryAfterMs"],"sources":["../../src/client/timing.ts"],"sourcesContent":["export const timing = {\n backoffBaseMs: 250,\n backoffMaxMs: 30_000,\n heartbeatTimeoutMs: 30_000,\n};\n\nexport const backoff = (attempt: number) =>\n Math.min(timing.backoffBaseMs * 2 ** (attempt - 1), timing.backoffMaxMs);\n\nexport const retryDelay = (error: unknown, attempt: number): number => {\n const retryAfterMs = (error as { retryAfterMs?: unknown } | undefined)\n ?.retryAfterMs;\n return typeof retryAfterMs === \"number\" ? retryAfterMs : backoff(attempt);\n};\n"],"mappings":";AAAA,MAAaA,SAAS;CACpBC,eAAe;CACfC,cAAc;CACdC,oBAAoB;AACtB;AAEA,MAAaC,WAAWC,YACtBC,KAAKC,IAAIP,OAAOC,gBAAgB,MAAMI,UAAU,IAAIL,OAAOE,YAAY;AAEzE,MAAaM,cAAcC,OAAgBJ,YAA4B;CACrE,MAAMK,eAAgBD,OAClBC;CACJ,OAAO,OAAOA,iBAAiB,WAAWA,eAAeN,QAAQC,OAAO;AAC1E"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { StatewireHostInternal } from "../host-internal.js";
|
|
2
|
+
//#region src/host/adapter.d.ts
|
|
3
|
+
declare const CLIENT_QUERY_PARAM = "client";
|
|
4
|
+
declare const clientIdFromUrl: (url: string | null | undefined) => string;
|
|
5
|
+
declare const textFrameOrClose: (socket: Pick<StatewireHostInternal.Socket, "close">, data: unknown) => string | null;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { CLIENT_QUERY_PARAM, clientIdFromUrl, textFrameOrClose };
|
|
8
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","names":[],"sources":["../../src/host/adapter.ts"],"mappings":";;cAEa;cAEA,kBAAmB;cAqBnB,mBACX,QAAQ,KAAK,sBAAsB,kBACnC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/host/adapter.ts
|
|
2
|
+
const CLIENT_QUERY_PARAM = "client";
|
|
3
|
+
const clientIdFromUrl = (url) => {
|
|
4
|
+
if (!url) throw new Error("statewire: cannot resolve client id: missing url");
|
|
5
|
+
let parsed;
|
|
6
|
+
try {
|
|
7
|
+
parsed = new URL(url, "http://statewire");
|
|
8
|
+
} catch {
|
|
9
|
+
throw new Error(`statewire: cannot resolve client id: unparseable url "${url}"`);
|
|
10
|
+
}
|
|
11
|
+
const clientId = parsed.searchParams.get(CLIENT_QUERY_PARAM);
|
|
12
|
+
if (!clientId) throw new Error(`statewire: url is missing the "${CLIENT_QUERY_PARAM}" query parameter`);
|
|
13
|
+
return clientId;
|
|
14
|
+
};
|
|
15
|
+
const textFrameOrClose = (socket, data) => {
|
|
16
|
+
if (typeof data === "string") return data;
|
|
17
|
+
socket.close(1008, "text frames only");
|
|
18
|
+
return null;
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
export { CLIENT_QUERY_PARAM, clientIdFromUrl, textFrameOrClose };
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","names":["StatewireHostInternal","CLIENT_QUERY_PARAM","clientIdFromUrl","url","Error","parsed","URL","clientId","searchParams","get","textFrameOrClose","socket","Pick","Socket","data","close"],"sources":["../../src/host/adapter.ts"],"sourcesContent":["import type { StatewireHostInternal } from \"../host-internal\";\n\nexport const CLIENT_QUERY_PARAM = \"client\";\n\nexport const clientIdFromUrl = (url: string | null | undefined): string => {\n if (!url) {\n throw new Error(\"statewire: cannot resolve client id: missing url\");\n }\n let parsed: URL;\n try {\n parsed = new URL(url, \"http://statewire\");\n } catch {\n throw new Error(\n `statewire: cannot resolve client id: unparseable url \"${url}\"`,\n );\n }\n const clientId = parsed.searchParams.get(CLIENT_QUERY_PARAM);\n if (!clientId) {\n throw new Error(\n `statewire: url is missing the \"${CLIENT_QUERY_PARAM}\" query parameter`,\n );\n }\n return clientId;\n};\n\nexport const textFrameOrClose = (\n socket: Pick<StatewireHostInternal.Socket, \"close\">,\n data: unknown,\n): string | null => {\n if (typeof data === \"string\") return data;\n socket.close(1008, \"text frames only\");\n return null;\n};\n"],"mappings":";AAEA,MAAaC,qBAAqB;AAElC,MAAaC,mBAAmBC,QAA2C;CACzE,IAAI,CAACA,KACH,MAAM,IAAIC,MAAM,kDAAkD;CAEpE,IAAIC;CACJ,IAAI;EACFA,SAAS,IAAIC,IAAIH,KAAK,kBAAkB;CAC1C,QAAQ;EACN,MAAM,IAAIC,MACR,yDAAyDD,IAAG,EAC9D;CACF;CACA,MAAMI,WAAWF,OAAOG,aAAaC,IAAIR,kBAAkB;CAC3D,IAAI,CAACM,UACH,MAAM,IAAIH,MACR,kCAAkCH,mBAAkB,kBACtD;CAEF,OAAOM;AACT;AAEA,MAAaG,oBACXC,QACAG,SACkB;CAClB,IAAI,OAAOA,SAAS,UAAU,OAAOA;CACrCH,OAAOI,MAAM,MAAM,kBAAkB;CACrC,OAAO;AACT"}
|
package/dist/host/draft.d.ts
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Statewire } from "../types.js";
|
|
2
|
+
import { StatewirePath } from "../wire/ops.js";
|
|
2
3
|
//#region src/host/draft.d.ts
|
|
3
|
-
type ServerOp = Exclude<
|
|
4
|
+
type ServerOp = Exclude<Statewire.Op, {
|
|
4
5
|
op: "event";
|
|
5
6
|
}>;
|
|
6
7
|
type DraftHost = {
|
|
7
|
-
|
|
8
|
-
record: (op: ServerOp) => void;
|
|
8
|
+
snapshot: () => unknown;
|
|
9
|
+
record: (op: ServerOp, changedPath: StatewirePath) => void;
|
|
9
10
|
};
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
type StateDraft<T> = {
|
|
12
|
+
proxy: T;
|
|
13
|
+
/** Seal pending writes and return the committed state. */
|
|
14
|
+
flush: () => unknown;
|
|
15
|
+
/** Committed state was replaced externally; cached nodes re-resolve. */
|
|
16
|
+
invalidate: () => void;
|
|
17
|
+
};
|
|
18
|
+
declare const createStateDraft: <T>(host: DraftHost) => StateDraft<T>;
|
|
19
|
+
/**
|
|
20
|
+
* Append `suffix` to the string at `parent[key]` on a statewire state draft,
|
|
21
|
+
* recording a single end-offset text-insert `add` op instead of a full
|
|
22
|
+
* `replace` — use it on token-streaming hot paths. Throws when the current
|
|
23
|
+
* value is not a string.
|
|
24
|
+
*/
|
|
25
|
+
declare const append: <TKey extends string | number>(parent: { readonly [P in TKey]: string; }, key: TKey, suffix: string) => void;
|
|
12
26
|
//#endregion
|
|
13
|
-
export { DraftHost, ServerOp,
|
|
27
|
+
export { DraftHost, ServerOp, StateDraft, append, createStateDraft };
|
|
14
28
|
//# sourceMappingURL=draft.d.ts.map
|
package/dist/host/draft.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"draft.d.ts","names":[],"sources":["../../src/host/draft.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"draft.d.ts","names":[],"sources":["../../src/host/draft.ts"],"mappings":";;;KAMY,WAAW,QAAQ,UAAU;EAAM;;KAEnC;EACV;EACA,SAAS,IAAI,UAAU,aAAa;;KAG1B,WAAW;EACrB,OAAO;;EAEP;;EAEA;;cA6BW,mBAAoB,GAAG,MAAM,cAAY,WAAW;;;;;;;cAsQpD,SAAU,8BACrB,oBAAoB,KAAK,kBACzB,KAAK,MACL"}
|