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.
Files changed (148) hide show
  1. package/README.md +14 -13
  2. package/dist/client/changes.d.ts +2 -2
  3. package/dist/client/changes.d.ts.map +1 -1
  4. package/dist/client/changes.js +3 -10
  5. package/dist/client/changes.js.map +1 -1
  6. package/dist/client/connection.d.ts +18 -39
  7. package/dist/client/connection.d.ts.map +1 -1
  8. package/dist/client/connection.js +61 -54
  9. package/dist/client/connection.js.map +1 -1
  10. package/dist/client/core.d.ts +7 -11
  11. package/dist/client/core.d.ts.map +1 -1
  12. package/dist/client/core.js +58 -55
  13. package/dist/client/core.js.map +1 -1
  14. package/dist/client/session.d.ts +8 -25
  15. package/dist/client/session.d.ts.map +1 -1
  16. package/dist/client/session.js +56 -36
  17. package/dist/client/session.js.map +1 -1
  18. package/dist/client/sessionStore.d.ts +1 -1
  19. package/dist/client/sessionStore.js.map +1 -1
  20. package/dist/client/timing.d.ts +2 -1
  21. package/dist/client/timing.d.ts.map +1 -1
  22. package/dist/client/timing.js +5 -1
  23. package/dist/client/timing.js.map +1 -1
  24. package/dist/host/adapter.d.ts +8 -0
  25. package/dist/host/adapter.d.ts.map +1 -0
  26. package/dist/host/adapter.js +23 -0
  27. package/dist/host/adapter.js.map +1 -0
  28. package/dist/host/draft.d.ts +21 -7
  29. package/dist/host/draft.d.ts.map +1 -1
  30. package/dist/host/draft.js +190 -103
  31. package/dist/host/draft.js.map +1 -1
  32. package/dist/host/host.d.ts +18 -35
  33. package/dist/host/host.d.ts.map +1 -1
  34. package/dist/host/host.js +81 -53
  35. package/dist/host/host.js.map +1 -1
  36. package/dist/host/socket.d.ts +14 -0
  37. package/dist/host/socket.d.ts.map +1 -0
  38. package/dist/host/socket.js +459 -0
  39. package/dist/host/socket.js.map +1 -0
  40. package/dist/host-internal.d.ts +27 -0
  41. package/dist/host-internal.d.ts.map +1 -0
  42. package/dist/host-internal.js +3 -0
  43. package/dist/host.d.ts +44 -0
  44. package/dist/host.d.ts.map +1 -0
  45. package/dist/host.js +4 -0
  46. package/dist/index.d.ts +5 -28
  47. package/dist/index.js +2 -3
  48. package/dist/statewire.d.ts +94 -10
  49. package/dist/statewire.d.ts.map +1 -1
  50. package/dist/statewire.js +23 -35
  51. package/dist/statewire.js.map +1 -1
  52. package/dist/transports/fake-sse.d.ts +12 -0
  53. package/dist/transports/fake-sse.d.ts.map +1 -0
  54. package/dist/transports/fake-sse.js +39 -0
  55. package/dist/transports/fake-sse.js.map +1 -0
  56. package/dist/transports/fixtures.d.ts +33 -0
  57. package/dist/transports/fixtures.d.ts.map +1 -0
  58. package/dist/transports/fixtures.js +50 -0
  59. package/dist/transports/fixtures.js.map +1 -0
  60. package/dist/transports/remote.d.ts +6 -13
  61. package/dist/transports/remote.d.ts.map +1 -1
  62. package/dist/transports/remote.js +5 -24
  63. package/dist/transports/remote.js.map +1 -1
  64. package/dist/transports/sse.d.ts +10 -20
  65. package/dist/transports/sse.d.ts.map +1 -1
  66. package/dist/transports/sse.js +42 -41
  67. package/dist/transports/sse.js.map +1 -1
  68. package/dist/transports/testing.d.ts +4 -3
  69. package/dist/transports/testing.d.ts.map +1 -1
  70. package/dist/transports/testing.js +19 -46
  71. package/dist/transports/testing.js.map +1 -1
  72. package/dist/transports/ws.d.ts +4 -13
  73. package/dist/transports/ws.d.ts.map +1 -1
  74. package/dist/transports/ws.js +57 -60
  75. package/dist/transports/ws.js.map +1 -1
  76. package/dist/types.d.ts +53 -100
  77. package/dist/types.d.ts.map +1 -1
  78. package/dist/types.js.map +1 -1
  79. package/dist/util/draftTraps.d.ts +6 -0
  80. package/dist/util/draftTraps.d.ts.map +1 -0
  81. package/dist/util/draftTraps.js +25 -0
  82. package/dist/util/draftTraps.js.map +1 -0
  83. package/dist/util/macrotask.d.ts +5 -0
  84. package/dist/util/macrotask.d.ts.map +1 -0
  85. package/dist/util/macrotask.js +25 -0
  86. package/dist/util/macrotask.js.map +1 -0
  87. package/dist/util/methodProxy.d.ts +5 -0
  88. package/dist/util/methodProxy.d.ts.map +1 -0
  89. package/dist/util/methodProxy.js +17 -0
  90. package/dist/util/methodProxy.js.map +1 -0
  91. package/dist/util/randomId.d.ts +5 -0
  92. package/dist/util/randomId.d.ts.map +1 -0
  93. package/dist/util/randomId.js +6 -0
  94. package/dist/util/randomId.js.map +1 -0
  95. package/dist/{client/optimistic.d.ts → util/recordMutations.d.ts} +2 -2
  96. package/dist/util/recordMutations.d.ts.map +1 -0
  97. package/dist/{client/optimistic.js → util/recordMutations.js} +5 -24
  98. package/dist/util/recordMutations.js.map +1 -0
  99. package/dist/util/statePath.d.ts +14 -0
  100. package/dist/util/statePath.d.ts.map +1 -0
  101. package/dist/util/statePath.js +31 -0
  102. package/dist/util/statePath.js.map +1 -0
  103. package/dist/wire/meta.d.ts +6 -1
  104. package/dist/wire/meta.d.ts.map +1 -1
  105. package/dist/wire/meta.js +30 -28
  106. package/dist/wire/meta.js.map +1 -1
  107. package/dist/wire/ops.d.ts +5 -26
  108. package/dist/wire/ops.d.ts.map +1 -1
  109. package/dist/wire/ops.js +67 -80
  110. package/dist/wire/ops.js.map +1 -1
  111. package/package.json +15 -11
  112. package/src/client/changes.ts +5 -15
  113. package/src/client/connection.ts +87 -113
  114. package/src/client/core.ts +57 -91
  115. package/src/client/session.ts +87 -70
  116. package/src/client/sessionStore.ts +1 -1
  117. package/src/client/timing.ts +6 -0
  118. package/src/host/adapter.ts +33 -0
  119. package/src/host/draft.ts +293 -125
  120. package/src/host/host.ts +107 -106
  121. package/src/host/socket.ts +542 -0
  122. package/src/host-internal.ts +28 -0
  123. package/src/host.ts +61 -0
  124. package/src/index.ts +2 -62
  125. package/src/statewire.ts +154 -61
  126. package/src/transports/fake-sse.ts +51 -0
  127. package/src/transports/fixtures.ts +60 -0
  128. package/src/transports/remote.ts +12 -54
  129. package/src/transports/sse.ts +68 -81
  130. package/src/transports/testing.ts +27 -59
  131. package/src/transports/ws.ts +76 -92
  132. package/src/types.ts +50 -131
  133. package/src/util/draftTraps.ts +32 -0
  134. package/src/util/macrotask.ts +25 -0
  135. package/src/util/methodProxy.ts +16 -0
  136. package/src/util/randomId.ts +4 -0
  137. package/src/{client/optimistic.ts → util/recordMutations.ts} +0 -0
  138. package/src/util/statePath.ts +61 -0
  139. package/src/wire/meta.ts +42 -47
  140. package/src/wire/ops.ts +107 -106
  141. package/LICENSE +0 -21
  142. package/dist/client/optimistic.d.ts.map +0 -1
  143. package/dist/client/optimistic.js.map +0 -1
  144. package/dist/index.d.ts.map +0 -1
  145. package/dist/server.d.ts +0 -15
  146. package/dist/server.d.ts.map +0 -1
  147. package/dist/server.js +0 -3
  148. 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":"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"}
@@ -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"}
@@ -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"}
@@ -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"}
@@ -1,14 +1,28 @@
1
- import { StatewireOp, StatewirePath } from "../wire/ops.js";
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<StatewireOp, {
4
+ type ServerOp = Exclude<Statewire.Op, {
4
5
  op: "event";
5
6
  }>;
6
7
  type DraftHost = {
7
- root: () => unknown;
8
- record: (op: ServerOp) => void;
8
+ snapshot: () => unknown;
9
+ record: (op: ServerOp, changedPath: StatewirePath) => void;
9
10
  };
10
- declare const frozen: (value: unknown) => unknown;
11
- declare const createStateDraft: <T>(host: DraftHost, path?: StatewirePath) => T;
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, createStateDraft, frozen };
27
+ export { DraftHost, ServerOp, StateDraft, append, createStateDraft };
14
28
  //# sourceMappingURL=draft.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"draft.d.ts","names":[],"sources":["../../src/host/draft.ts"],"mappings":";;KAEY,WAAW,QAAQ;EAAe;;KAElC;EACV;EACA,SAAS,IAAI;;cAGF,SAAU;cA6BV,mBAAoB,GAC/B,MAAM,WACN,OAAM,kBACL"}
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"}