experimental-a2 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/AGENTS.md +11 -0
  2. package/CHANGELOG.md +36 -0
  3. package/README.md +29 -0
  4. package/dist/actor-client.d.ts +46 -0
  5. package/dist/actor-client.d.ts.map +1 -0
  6. package/dist/actor-client.js +54 -0
  7. package/dist/actor-client.js.map +1 -0
  8. package/dist/actor-react.d.ts +54 -0
  9. package/dist/actor-react.d.ts.map +1 -0
  10. package/dist/actor-react.js +79 -0
  11. package/dist/actor-react.js.map +1 -0
  12. package/dist/actor-shared-BACubf4x.d.ts +136 -0
  13. package/dist/actor-shared-BACubf4x.d.ts.map +1 -0
  14. package/dist/actor-shared-DI7J5upy.js +127 -0
  15. package/dist/actor-shared-DI7J5upy.js.map +1 -0
  16. package/dist/actor.browser.d.ts +1 -0
  17. package/dist/actor.browser.js +13 -0
  18. package/dist/actor.browser.js.map +1 -0
  19. package/dist/actor.d.ts +176 -0
  20. package/dist/actor.d.ts.map +1 -0
  21. package/dist/actor.js +437 -0
  22. package/dist/actor.js.map +1 -0
  23. package/dist/ai-server.d.ts +2 -2
  24. package/dist/ai-server.js +2 -2
  25. package/dist/ai.d.ts +2 -2
  26. package/dist/client.d.ts +1 -1
  27. package/dist/client.d.ts.map +1 -1
  28. package/dist/client.js +4 -4
  29. package/dist/client.js.map +1 -1
  30. package/dist/{errors-BQuJpe82.js → errors-DCk6ch5n.js} +16 -2
  31. package/dist/{errors-BQuJpe82.js.map → errors-DCk6ch5n.js.map} +1 -1
  32. package/dist/{idempotent-replay-DuqEkYA7.js → idempotent-replay-DVOlyYbx.js} +2 -2
  33. package/dist/{idempotent-replay-DuqEkYA7.js.map → idempotent-replay-DVOlyYbx.js.map} +1 -1
  34. package/dist/index.d.ts +16 -3
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +2 -2
  37. package/dist/react.d.ts +1 -1
  38. package/dist/{contract-jIfaR085.d.ts → reducer-DJKWm3cp.d.ts} +39 -39
  39. package/dist/reducer-DJKWm3cp.d.ts.map +1 -0
  40. package/dist/scheduler-qstash.d.ts +2 -2
  41. package/dist/scheduler-qstash.js +2 -2
  42. package/dist/scheduler-vercel.d.ts +2 -2
  43. package/dist/scheduler-vercel.js +1 -1
  44. package/dist/{server-B2XNevQA.js → server-CBET-jSz.js} +6 -6
  45. package/dist/server-CBET-jSz.js.map +1 -0
  46. package/dist/{server-DjPhHnbI.d.ts → server-CKY3_lbw.d.ts} +3 -3
  47. package/dist/{server-DjPhHnbI.d.ts.map → server-CKY3_lbw.d.ts.map} +1 -1
  48. package/dist/server.d.ts +3 -3
  49. package/dist/server.js +1 -1
  50. package/dist/{store-RJO35BMj.d.ts → store-DGHeBtIQ.d.ts} +2 -2
  51. package/dist/{store-RJO35BMj.d.ts.map → store-DGHeBtIQ.d.ts.map} +1 -1
  52. package/dist/store-memory.d.ts +1 -1
  53. package/dist/store-memory.js +2 -2
  54. package/dist/store-postgres.d.ts +1 -1
  55. package/dist/store-postgres.js +2 -2
  56. package/dist/{store-redis-core-DT01r4GZ.js → store-redis-core-z-ykbyMg.js} +3 -3
  57. package/dist/{store-redis-core-DT01r4GZ.js.map → store-redis-core-z-ykbyMg.js.map} +1 -1
  58. package/dist/store-redis-http.d.ts +1 -1
  59. package/dist/store-redis-http.js +2 -2
  60. package/dist/store-redis.d.ts +1 -1
  61. package/dist/store-redis.js +2 -2
  62. package/dist/store-sqlite.d.ts +1 -1
  63. package/dist/store-sqlite.js +2 -2
  64. package/dist/{wire-B6te_wns.js → wire--yji6mO3.js} +2 -2
  65. package/dist/{wire-B6te_wns.js.map → wire--yji6mO3.js.map} +1 -1
  66. package/docs/actors/01-introduction.mdx +189 -0
  67. package/docs/actors/02-concurrency.mdx +154 -0
  68. package/docs/actors/03-timers.mdx +120 -0
  69. package/docs/actors/04-routes.mdx +352 -0
  70. package/docs/actors/meta.ts +1 -0
  71. package/docs/concepts/meta.ts +1 -0
  72. package/docs/guides/07-examples.mdx +56 -0
  73. package/docs/guides/meta.ts +1 -0
  74. package/docs/index.mdx +16 -0
  75. package/docs/reference/02-errors.mdx +33 -0
  76. package/docs/reference/meta.ts +1 -0
  77. package/examples/README.md +15 -0
  78. package/examples/playground/AGENTS.md +11 -0
  79. package/examples/playground/DEPLOY.md +106 -0
  80. package/examples/playground/README.md +19 -0
  81. package/examples/playground/activity-feed.test.ts +10 -0
  82. package/examples/playground/app/agent/[agentId]/agent-client.tsx +376 -0
  83. package/examples/playground/app/agent/[agentId]/page.tsx +29 -0
  84. package/examples/playground/app/agent/events/route.ts +4 -0
  85. package/examples/playground/app/agent/model.ts +3 -0
  86. package/examples/playground/app/agent/new-agent-session.tsx +98 -0
  87. package/examples/playground/app/agent/page.tsx +25 -0
  88. package/examples/playground/app/agent/scheduler/route.ts +5 -0
  89. package/examples/playground/app/agent/server.ts +153 -0
  90. package/examples/playground/app/agent/session.ts +13 -0
  91. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +682 -0
  92. package/examples/playground/app/canvas/[canvasId]/canvas-replay.test.ts +68 -0
  93. package/examples/playground/app/canvas/[canvasId]/canvas-replay.ts +19 -0
  94. package/examples/playground/app/canvas/[canvasId]/page.tsx +22 -0
  95. package/examples/playground/app/canvas/[canvasId]/session.ts +19 -0
  96. package/examples/playground/app/canvas/events/route.ts +13 -0
  97. package/examples/playground/app/canvas/model.ts +94 -0
  98. package/examples/playground/app/canvas/open-canvas.tsx +40 -0
  99. package/examples/playground/app/canvas/page.tsx +20 -0
  100. package/examples/playground/app/canvas/server.ts +9 -0
  101. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.test.tsx +118 -0
  102. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +316 -0
  103. package/examples/playground/app/chat/[chatId]/chat-client.tsx +922 -0
  104. package/examples/playground/app/chat/[chatId]/chat-view.test.ts +152 -0
  105. package/examples/playground/app/chat/[chatId]/chat-view.ts +101 -0
  106. package/examples/playground/app/chat/[chatId]/composer.test.ts +44 -0
  107. package/examples/playground/app/chat/[chatId]/composer.ts +30 -0
  108. package/examples/playground/app/chat/[chatId]/page.tsx +30 -0
  109. package/examples/playground/app/chat/[chatId]/session.ts +7 -0
  110. package/examples/playground/app/chat/events/route.ts +7 -0
  111. package/examples/playground/app/chat/model.test.ts +155 -0
  112. package/examples/playground/app/chat/model.ts +310 -0
  113. package/examples/playground/app/chat/new-conversation.tsx +16 -0
  114. package/examples/playground/app/chat/page.tsx +25 -0
  115. package/examples/playground/app/chat/scheduler/route.ts +5 -0
  116. package/examples/playground/app/chat/server.ts +184 -0
  117. package/examples/playground/app/components/activity-feed.tsx +54 -0
  118. package/examples/playground/app/components/connection-pill.tsx +29 -0
  119. package/examples/playground/app/counter/counter-client.tsx +72 -0
  120. package/examples/playground/app/counter/events/route.ts +4 -0
  121. package/examples/playground/app/counter/model.test.ts +36 -0
  122. package/examples/playground/app/counter/model.ts +31 -0
  123. package/examples/playground/app/counter/page.tsx +24 -0
  124. package/examples/playground/app/counter/server.ts +9 -0
  125. package/examples/playground/app/counter/session.ts +13 -0
  126. package/examples/playground/app/documents/[documentId]/code-editor.tsx +80 -0
  127. package/examples/playground/app/documents/[documentId]/document-client.tsx +525 -0
  128. package/examples/playground/app/documents/[documentId]/page.tsx +23 -0
  129. package/examples/playground/app/documents/[documentId]/session.ts +7 -0
  130. package/examples/playground/app/documents/events/route.ts +4 -0
  131. package/examples/playground/app/documents/model.ts +55 -0
  132. package/examples/playground/app/documents/open-document.tsx +40 -0
  133. package/examples/playground/app/documents/page.tsx +22 -0
  134. package/examples/playground/app/documents/server.ts +9 -0
  135. package/examples/playground/app/globals.css +2078 -0
  136. package/examples/playground/app/layout.tsx +44 -0
  137. package/examples/playground/app/orders/[orderId]/order-client.tsx +140 -0
  138. package/examples/playground/app/orders/[orderId]/page.tsx +29 -0
  139. package/examples/playground/app/orders/[orderId]/session.ts +11 -0
  140. package/examples/playground/app/orders/create/route.ts +30 -0
  141. package/examples/playground/app/orders/events/route.ts +4 -0
  142. package/examples/playground/app/orders/model.ts +79 -0
  143. package/examples/playground/app/orders/new-order-form.tsx +98 -0
  144. package/examples/playground/app/orders/page.tsx +22 -0
  145. package/examples/playground/app/orders/scheduler/route.ts +5 -0
  146. package/examples/playground/app/orders/server.ts +50 -0
  147. package/examples/playground/app/page.tsx +111 -0
  148. package/examples/playground/app/recovery/[recoveryId]/page.tsx +31 -0
  149. package/examples/playground/app/recovery/[recoveryId]/recovery-client.tsx +144 -0
  150. package/examples/playground/app/recovery/[recoveryId]/session.ts +7 -0
  151. package/examples/playground/app/recovery/events/route.ts +3 -0
  152. package/examples/playground/app/recovery/model.ts +55 -0
  153. package/examples/playground/app/recovery/new-recovery-session.tsx +20 -0
  154. package/examples/playground/app/recovery/page.tsx +22 -0
  155. package/examples/playground/app/recovery/scheduler/route.ts +7 -0
  156. package/examples/playground/app/recovery/server.ts +53 -0
  157. package/examples/playground/app/recovery/start/route.ts +41 -0
  158. package/examples/playground/app/vault/[vaultId]/route.ts +19 -0
  159. package/examples/playground/app/vault/page.tsx +12 -0
  160. package/examples/playground/app/vault/server.ts +9 -0
  161. package/examples/playground/app/vault/vault-client.tsx +124 -0
  162. package/examples/playground/app/vault/vault.test.ts +147 -0
  163. package/examples/playground/app/vault/vault.ts +119 -0
  164. package/examples/playground/css.d.ts +4 -0
  165. package/examples/playground/lib/store.ts +15 -0
  166. package/examples/playground/next-env.d.ts +5 -0
  167. package/examples/playground/next.config.ts +10 -0
  168. package/examples/playground/package.json +46 -0
  169. package/examples/playground/tsconfig.json +37 -0
  170. package/examples/playground/vercel.json +40 -0
  171. package/package.json +11 -2
  172. package/src/actor-client.ts +132 -0
  173. package/src/actor-react.ts +143 -0
  174. package/src/actor-shared.ts +356 -0
  175. package/src/actor.browser.ts +12 -0
  176. package/src/actor.ts +914 -0
  177. package/src/client.ts +9 -1
  178. package/src/errors.ts +15 -0
  179. package/src/index.ts +1 -1
  180. package/src/server.ts +13 -3
  181. package/dist/contract-jIfaR085.d.ts.map +0 -1
  182. package/dist/server-B2XNevQA.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"store-redis-core-DT01r4GZ.js","names":[],"sources":["../src/store-redis-core.ts"],"sourcesContent":["/**\n * The shared Redis store core — every operation except `stream` and\n * `close`, expressed over one injected `call` function so both\n * transports reuse it: `store-redis` (Redis protocol via `ioredis`) and\n * `store-redis-http` (provider REST APIs).\n *\n * The stream IS the store: each event is a stream entry whose ID is its\n * dense a2 index (`${index}-0`), so `XRANGE` is the catch-up read.\n * Mutable drain/failure bookkeeping lives in a side hash (stream\n * entries are immutable); every write path is one atomic Lua script.\n * The conformance suite in test/conformance is the executable\n * contract, run against a real spawned `redis-server`.\n *\n * Timestamps and claim expiry come from the injected clock so tests\n * can time-travel against a real server. Cluster mode is out: the\n * atomic scripts span keys.\n */\n\nimport type { PresencePatch } from './contract.ts'\nimport { A2Error } from './errors.ts'\nimport { idempotentReplay } from './idempotent-replay.ts'\nimport { nullProtoRecord } from './internal.ts'\nimport { decodeReturnedEventIds } from './store-codec.ts'\nimport type {\n A2Store,\n Clock,\n CompleteAttemptResult,\n Event,\n EventCause,\n IdSource,\n StoreClaimAvailableResult,\n PresenceRow,\n StoredEvent,\n} from './store.ts'\n\n/** Issues any Redis command; the seam both transports implement. */\nexport type RedisCall = (\n command: string,\n ...args: Array<string | number>\n) => Promise<unknown>\n\nexport type RedisStoreCoreOptions = {\n call: RedisCall\n clock: Clock\n ids: IdSource\n keyPrefix: string\n /**\n * Disposable wake-up fired after a write appends new events to a\n * watched session — `count` is the session's new event count. The\n * watched check runs inside the write script, so unwatched sessions\n * cost no wake-up command at all. Losing one is harmless: live\n * feeds re-read on a safety cadence, so notifications only affect\n * latency, never delivery.\n */\n notify?: (sessionId: string, count: number) => void\n}\n\nexport type RedisStoreCore = Omit<A2Store, 'stream' | 'presence'> & {\n /**\n * The storage half of the presence capability, shared by both\n * transports. `set` returns the applied patch — null when every\n * field lost the LWW race — so a push-capable transport broadcasts\n * exactly what `read` now reflects; delivery is its business.\n */\n presence: {\n set(\n ns: string,\n participant: string,\n values: Record<string, unknown | null>,\n meta: { seen: number; at: Date; ttlMs: number },\n ): Promise<PresencePatch | null>\n read(ns: string): Promise<PresenceRow[]>\n }\n /** XRANGE + meta merge — the full-row catch-up read. */\n readRange(\n sessionId: string,\n afterIndex: number,\n throughIndex?: number,\n ): Promise<StoredEvent[]>\n /** XRANGE only — the light catch-up read live feeds use. */\n readEvents(sessionId: string, afterIndex: number): Promise<Event[]>\n /**\n * Declare the session watched for `ttlMs` — writes publish wake-ups\n * only while this presence marker lives. Live feeds refresh it\n * ahead of expiry; an expired marker degrades latency to the safety\n * cadence, never delivery.\n */\n markWatched(sessionId: string, ttlMs: number): Promise<void>\n}\n\nconst READ_PAGE_SIZE = 256\n\n// ── Lua scripts (each runs atomically) ───────────────────────────────\n\n/**\n * KEYS: store stream, counter, global id hash, metadata, pending, ready,\n * watched.\n * ARGV: sessionId, nowMs, n, then per event:\n * id, type, payloadJson, causeJson, lane, settled.\n * Returns ['empty', hasPending] | ['ok', firstIndex, hasPending, watched] |\n * ['dup', hasPending, ...indexes] | ['partial', count] |\n * ['foreign', eventId] | ['no_parent', idx] | ['superseded', idx].\n */\nconst APPEND_LUA = `\nlocal function lane_field(lane, suffix)\n return '__lane:' .. tostring(string.len(lane)) .. ':' .. lane .. ':' .. suffix\nend\nlocal function enqueue(field, idx, lane)\n redis.call('ZADD', KEYS[5], idx, field)\n if lane == '' then\n redis.call('ZADD', KEYS[6], idx, field)\n return\n end\n local tail_key = lane_field(lane, 'tail')\n local tail = redis.call('HGET', KEYS[4], tail_key)\n if tail then\n redis.call('HSET', KEYS[4], tail .. ':next', field)\n else\n redis.call('HSET', KEYS[4], lane_field(lane, 'head'), field)\n redis.call('ZADD', KEYS[6], idx, field)\n end\n redis.call('HSET', KEYS[4], tail_key, field)\nend\nlocal n = tonumber(ARGV[3])\nlocal base = tonumber(redis.call('GET', KEYS[2]) or '0')\nlocal has_pending = redis.call('ZCARD', KEYS[5]) > 0 and '1' or '0'\nif n == 0 then return { 'empty', has_pending } end\nlocal dups = {}\nfor i = 1, n do\n local id = ARGV[3 + (i - 1) * 6 + 1]\n local existing = redis.call('HGET', KEYS[3], id)\n if existing then\n local rec = cjson.decode(existing)\n if rec[1] ~= ARGV[1] then return { 'foreign', id } end\n dups[#dups + 1] = tostring(rec[2])\n end\nend\nif #dups == n then\n local res = { 'dup', has_pending }\n for i = 1, #dups do res[#res + 1] = dups[i] end\n return res\nend\nif #dups > 0 then return { 'partial', tostring(#dups) } end\nfor i = 1, n do\n local cause = ARGV[3 + (i - 1) * 6 + 4]\n if cause ~= '' then\n local parsed = cjson.decode(cause)\n local pidx = tonumber(parsed.index)\n if pidx < 1 or pidx > base then\n return { 'no_parent', tostring(parsed.index) }\n end\n local field = tostring(pidx)\n local dispatches = tonumber(redis.call('HGET', KEYS[4], field .. ':d') or '0')\n if dispatches ~= tonumber(parsed.attempt) or\n redis.call('HEXISTS', KEYS[4], field .. ':f') == 1 then\n return { 'superseded', field }\n end\n end\nend\nfor i = 1, n do\n local id = ARGV[3 + (i - 1) * 6 + 1]\n local typ = ARGV[3 + (i - 1) * 6 + 2]\n local payload = ARGV[3 + (i - 1) * 6 + 3]\n local cause = ARGV[3 + (i - 1) * 6 + 4]\n local lane = ARGV[3 + (i - 1) * 6 + 5]\n local settled = ARGV[3 + (i - 1) * 6 + 6]\n local idx = base + i\n redis.call('XADD', KEYS[1], tostring(idx) .. '-0',\n 'id', id, 'type', typ, 'payload', payload, 'created_at', ARGV[2],\n 'cause', cause, 'lane', lane)\n redis.call('HSET', KEYS[3], id, cjson.encode({ ARGV[1], idx }))\n redis.call('HSET', KEYS[4], tostring(idx) .. ':l', lane)\n if settled == '1' then\n redis.call('HSET', KEYS[4], tostring(idx) .. ':p', ARGV[2])\n else\n enqueue(tostring(idx), idx, lane)\n end\nend\nredis.call('SET', KEYS[2], tostring(base + n))\nhas_pending = redis.call('ZCARD', KEYS[5]) > 0 and '1' or '0'\nreturn { 'ok', tostring(base + 1), has_pending,\n tostring(redis.call('EXISTS', KEYS[7])) }\n`\n\n/**\n * KEYS: counter, meta, pending, ready, event snapshot pins, snapshot pin counts.\n * ARGV: idx, attempt, error, maxFailures, nowMs.\n */\nconst FAIL_ATTEMPT_LUA = `\nlocal function release_snapshot_pins()\n local refs = redis.call('SMEMBERS', KEYS[5])\n for _, ref in ipairs(refs) do\n local count = redis.call('HINCRBY', KEYS[6], ref, -1)\n if count <= 0 then\n redis.call('HDEL', KEYS[6], ref)\n local checkpoint = cjson.decode(ref)\n redis.call('ZREMRANGEBYSCORE', checkpoint[1], checkpoint[2], checkpoint[2])\n end\n end\n redis.call('DEL', KEYS[5])\n if redis.call('HLEN', KEYS[6]) == 0 then redis.call('DEL', KEYS[6]) end\nend\nlocal function lane_field(lane, suffix)\n return '__lane:' .. tostring(string.len(lane)) .. ':' .. lane .. ':' .. suffix\nend\nlocal function settle(field)\n redis.call('ZREM', KEYS[3], field)\n redis.call('ZREM', KEYS[4], field)\n local lane = redis.call('HGET', KEYS[2], field .. ':l') or ''\n if lane == '' then return end\n local head_key = lane_field(lane, 'head')\n if redis.call('HGET', KEYS[2], head_key) ~= field then return end\n local next_field = redis.call('HGET', KEYS[2], field .. ':next')\n while next_field and redis.call('HEXISTS', KEYS[2], next_field .. ':p') == 1 do\n next_field = redis.call('HGET', KEYS[2], next_field .. ':next')\n end\n if next_field then\n redis.call('HSET', KEYS[2], head_key, next_field)\n if redis.call('HEXISTS', KEYS[2], next_field .. ':f') == 0 then\n redis.call('ZADD', KEYS[4], tonumber(next_field), next_field)\n end\n else\n redis.call('HDEL', KEYS[2], head_key, lane_field(lane, 'tail'))\n end\nend\nlocal idx = tonumber(ARGV[1])\nif idx < 1 or idx > tonumber(redis.call('GET', KEYS[1]) or '0') then\n return { 'missing', '0' }\nend\nlocal field = ARGV[1]\nlocal failures = tonumber(redis.call('HGET', KEYS[2], field .. ':a') or '0')\nlocal dispatches = tonumber(redis.call('HGET', KEYS[2], field .. ':d') or '0')\nif redis.call('HEXISTS', KEYS[2], field .. ':p') == 1 or dispatches ~= tonumber(ARGV[2]) then\n return { 'superseded', tostring(failures) }\nend\nif redis.call('HEXISTS', KEYS[2], field .. ':f') == 1 then\n return { 'dead_lettered', tostring(failures) }\nend\nif redis.call('HEXISTS', KEYS[2], field .. ':lfa') == 1 and\n tonumber(redis.call('HGET', KEYS[2], field .. ':lfa')) == tonumber(ARGV[2]) and\n redis.call('HEXISTS', KEYS[2], field .. ':ch') == 0 then\n return { 'failed', tostring(failures) }\nend\nif redis.call('HEXISTS', KEYS[2], field .. ':ch') == 0 then\n return { 'superseded', tostring(failures) }\nend\nfailures = redis.call('HINCRBY', KEYS[2], field .. ':a', 1)\nredis.call('HSET', KEYS[2],\n field .. ':e', ARGV[3],\n field .. ':lf', ARGV[5],\n field .. ':lfa', ARGV[2])\nredis.call('HDEL', KEYS[2], field .. ':ch', field .. ':ce')\nif failures >= tonumber(ARGV[4]) then\n redis.call('HSET', KEYS[2], field .. ':f', ARGV[5])\n redis.call('ZREM', KEYS[4], field)\n release_snapshot_pins()\n return { 'dead_lettered', tostring(failures) }\nend\nreturn { 'failed', tostring(failures) }\n`\n\n/**\n * KEYS: store, counter, meta, pending, ready.\n * ARGV: holder, nowMs, expiresAtMs, excludedCount, excluded indexes.\n */\nconst CLAIM_AVAILABLE_LUA = `\nif redis.call('ZCARD', KEYS[4]) == 0 then return { 'settled' } end\nlocal ready = redis.call('ZRANGE', KEYS[5], 0, -1)\nlocal excluded = {}\nfor i = 1, tonumber(ARGV[4]) do excluded[ARGV[4 + i]] = true end\nlocal claimed = {}\nlocal retry_at = nil\nfor _, field in ipairs(ready) do\n if not excluded[field] then\n local expires = tonumber(redis.call('HGET', KEYS[3], field .. ':ce') or '0')\n if expires > tonumber(ARGV[2]) then\n if not retry_at or expires < retry_at then retry_at = expires end\n else\n local entries = redis.call('XRANGE', KEYS[1], field .. '-0', field .. '-0')\n if #entries == 0 then return { 'missing', field } end\n local attempt = redis.call('HINCRBY', KEYS[3], field .. ':d', 1)\n redis.call('HSETNX', KEYS[3], field .. ':fc', ARGV[2])\n redis.call('HSET', KEYS[3],\n field .. ':lc', ARGV[2],\n field .. ':ch', ARGV[1],\n field .. ':ce', ARGV[3])\n claimed[#claimed + 1] = {\n tostring(attempt),\n redis.call('HGET', KEYS[3], field .. ':a') or '0',\n redis.call('HGET', KEYS[3], field .. ':e') or false,\n redis.call('HGET', KEYS[3], field .. ':fc') or false,\n ARGV[2],\n redis.call('HGET', KEYS[3], field .. ':lf') or false,\n redis.call('HGET', KEYS[3], field .. ':lfa') or false,\n ARGV[1],\n ARGV[3],\n entries[1]\n }\n end\n end\nend\nif #claimed > 0 then\n local result = { 'claimed', tostring(#claimed) }\n for _, claim in ipairs(claimed) do\n for _, value in ipairs(claim) do result[#result + 1] = value end\n end\n return result\nend\nif retry_at then return { 'busy', tostring(retry_at) } end\nreturn { 'settled' }\n`\n\n/**\n * KEYS: counter, meta.\n * ARGV: holder, nowMs, expiresAtMs, n, then per claim: index, attempt.\n * Returns [renewedCount, ...renewed, ...superseded].\n */\nconst RENEW_CLAIMS_LUA = `\nlocal total = tonumber(redis.call('GET', KEYS[1]) or '0')\nlocal renewed = {}\nlocal superseded = {}\nfor i = 1, tonumber(ARGV[4]) do\n local field = ARGV[4 + (i - 1) * 2 + 1]\n local attempt = tonumber(ARGV[4 + (i - 1) * 2 + 2])\n local idx = tonumber(field)\n if idx >= 1 and idx <= total then\n local dispatches = tonumber(redis.call('HGET', KEYS[2], field .. ':d') or '0')\n if dispatches > attempt then\n superseded[#superseded + 1] = field\n else\n local expires = tonumber(redis.call('HGET', KEYS[2], field .. ':ce') or '0')\n if tonumber(ARGV[3]) > tonumber(ARGV[2]) and\n redis.call('HEXISTS', KEYS[2], field .. ':p') == 0 and\n redis.call('HEXISTS', KEYS[2], field .. ':f') == 0 and\n redis.call('HGET', KEYS[2], field .. ':ch') == ARGV[1] and\n expires > tonumber(ARGV[2]) then\n redis.call('HSET', KEYS[2], field .. ':ce', ARGV[3])\n renewed[#renewed + 1] = field\n end\n end\n end\nend\nlocal result = { tostring(#renewed) }\nfor i = 1, #renewed do result[#result + 1] = renewed[i] end\nfor i = 1, #superseded do result[#result + 1] = superseded[i] end\nreturn result\n`\n\n/**\n * KEYS: store, counter, ids, meta, pending, ready, watched,\n * event snapshot pins, snapshot pin counts.\n * ARGV: sessionId, nowMs, parent index, attempt, n, then per child:\n * id, type, payloadJson, lane, settled.\n */\nconst COMPLETE_ATTEMPT_LUA = `\nlocal function release_snapshot_pins()\n local refs = redis.call('SMEMBERS', KEYS[8])\n for _, ref in ipairs(refs) do\n local count = redis.call('HINCRBY', KEYS[9], ref, -1)\n if count <= 0 then\n redis.call('HDEL', KEYS[9], ref)\n local checkpoint = cjson.decode(ref)\n redis.call('ZREMRANGEBYSCORE', checkpoint[1], checkpoint[2], checkpoint[2])\n end\n end\n redis.call('DEL', KEYS[8])\n if redis.call('HLEN', KEYS[9]) == 0 then redis.call('DEL', KEYS[9]) end\nend\nlocal function lane_field(lane, suffix)\n return '__lane:' .. tostring(string.len(lane)) .. ':' .. lane .. ':' .. suffix\nend\nlocal function settle(field)\n redis.call('ZREM', KEYS[5], field)\n redis.call('ZREM', KEYS[6], field)\n local lane = redis.call('HGET', KEYS[4], field .. ':l') or ''\n if lane == '' then return end\n local head_key = lane_field(lane, 'head')\n if redis.call('HGET', KEYS[4], head_key) ~= field then return end\n local next_field = redis.call('HGET', KEYS[4], field .. ':next')\n while next_field and redis.call('HEXISTS', KEYS[4], next_field .. ':p') == 1 do\n next_field = redis.call('HGET', KEYS[4], next_field .. ':next')\n end\n if next_field then\n redis.call('HSET', KEYS[4], head_key, next_field)\n if redis.call('HEXISTS', KEYS[4], next_field .. ':f') == 0 then\n redis.call('ZADD', KEYS[6], tonumber(next_field), next_field)\n end\n else\n redis.call('HDEL', KEYS[4], head_key, lane_field(lane, 'tail'))\n end\nend\nlocal function enqueue(field, idx, lane)\n redis.call('ZADD', KEYS[5], idx, field)\n if lane == '' then\n redis.call('ZADD', KEYS[6], idx, field)\n return\n end\n local tail_key = lane_field(lane, 'tail')\n local tail = redis.call('HGET', KEYS[4], tail_key)\n if tail then\n redis.call('HSET', KEYS[4], tail .. ':next', field)\n else\n redis.call('HSET', KEYS[4], lane_field(lane, 'head'), field)\n redis.call('ZADD', KEYS[6], idx, field)\n end\n redis.call('HSET', KEYS[4], tail_key, field)\nend\nlocal parent = ARGV[3]\nlocal attempt = tonumber(ARGV[4])\nlocal n = tonumber(ARGV[5])\nlocal total = tonumber(redis.call('GET', KEYS[2]) or '0')\nif tonumber(parent) < 1 or tonumber(parent) > total then return { 'missing' } end\nlocal dispatches = tonumber(redis.call('HGET', KEYS[4], parent .. ':d') or '0')\nlocal processed = redis.call('HGET', KEYS[4], parent .. ':p')\nlocal processed_attempt = tonumber(redis.call('HGET', KEYS[4], parent .. ':pa') or '0')\nif processed then\n if processed_attempt ~= attempt then return { 'superseded' } end\n local raw_recorded_ids = redis.call('HGET', KEYS[4], parent .. ':ri')\n if not raw_recorded_ids then return { 'partial' } end\n local recorded_ids = cjson.decode(raw_recorded_ids)\n if #recorded_ids ~= n then return { 'partial' } end\n local indexes = {}\n for i = 1, n do\n local id = ARGV[5 + (i - 1) * 5 + 1]\n if recorded_ids[i] ~= id then return { 'partial' } end\n local existing = redis.call('HGET', KEYS[3], id)\n if not existing then return { 'partial' } end\n local rec = cjson.decode(existing)\n if rec[1] ~= ARGV[1] then return { 'partial' } end\n local entries = redis.call('XRANGE', KEYS[1], tostring(rec[2]) .. '-0', tostring(rec[2]) .. '-0')\n if #entries == 0 then return { 'partial' } end\n local values = entries[1][2]\n local cause = nil\n for j = 1, #values, 2 do\n if values[j] == 'cause' then cause = values[j + 1] break end\n end\n if not cause or cause == '' then return { 'partial' } end\n local decoded = cjson.decode(cause)\n if tonumber(decoded['index']) ~= tonumber(parent) or\n tonumber(decoded['attempt']) ~= attempt then return { 'partial' } end\n indexes[#indexes + 1] = tostring(rec[2])\n end\n local result = { 'duplicate' }\n for _, idx in ipairs(indexes) do result[#result + 1] = idx end\n return result\nend\nif dispatches ~= attempt then return { 'superseded' } end\nif redis.call('HEXISTS', KEYS[4], parent .. ':ch') == 0 or\n redis.call('HEXISTS', KEYS[4], parent .. ':f') == 1 or\n tonumber(redis.call('HGET', KEYS[4], parent .. ':lfa') or '0') == attempt then\n return { 'superseded' }\nend\nfor i = 1, n do\n local id = ARGV[5 + (i - 1) * 5 + 1]\n if redis.call('HEXISTS', KEYS[3], id) == 1 then return { 'partial' } end\nend\nlocal returned_ids = {}\nfor i = 1, n do returned_ids[i] = ARGV[5 + (i - 1) * 5 + 1] end\nlocal returned_json = '[]'\nif n > 0 then returned_json = cjson.encode(returned_ids) end\nredis.call('HSET', KEYS[4],\n parent .. ':p', ARGV[2],\n parent .. ':pa', ARGV[4],\n parent .. ':ri', returned_json)\nredis.call('HDEL', KEYS[4], parent .. ':ch', parent .. ':ce')\nsettle(parent)\nrelease_snapshot_pins()\nlocal cause = cjson.encode({ index = tonumber(parent), attempt = attempt })\nfor i = 1, n do\n local id = ARGV[5 + (i - 1) * 5 + 1]\n local typ = ARGV[5 + (i - 1) * 5 + 2]\n local payload = ARGV[5 + (i - 1) * 5 + 3]\n local lane = ARGV[5 + (i - 1) * 5 + 4]\n local settled = ARGV[5 + (i - 1) * 5 + 5]\n local idx = total + i\n redis.call('XADD', KEYS[1], tostring(idx) .. '-0',\n 'id', id, 'type', typ, 'payload', payload, 'created_at', ARGV[2],\n 'cause', cause, 'lane', lane)\n redis.call('HSET', KEYS[3], id, cjson.encode({ ARGV[1], idx }))\n redis.call('HSET', KEYS[4], tostring(idx) .. ':l', lane)\n if settled == '1' then\n redis.call('HSET', KEYS[4], tostring(idx) .. ':p', ARGV[2])\n else\n enqueue(tostring(idx), idx, lane)\n end\nend\nredis.call('SET', KEYS[2], tostring(total + n))\nreturn { 'ok', tostring(total + 1),\n tostring(redis.call('EXISTS', KEYS[7])) }\n`\n\n/**\n * KEYS: head, history, event counter, event meta, pin counts.\n * ARGV: nowMs, event pin key prefix, write count, then per write:\n * index, snapshot JSON, pin count, pin event indexes.\n */\nconst PUT_SNAPSHOTS_LUA = `\nlocal total = tonumber(redis.call('GET', KEYS[3]) or '0')\nlocal writes = {}\nlocal offset = 4\nfor i = 1, tonumber(ARGV[3]) do\n local index = tonumber(ARGV[offset])\n local snapshot = ARGV[offset + 1]\n local pin_count = tonumber(ARGV[offset + 2])\n local ref = cjson.encode({ KEYS[2], tostring(index) })\n for p = 1, pin_count do\n local event_index = tonumber(ARGV[offset + 2 + p])\n if event_index >= 1 and event_index <= total and\n redis.call('HEXISTS', KEYS[4], tostring(event_index) .. ':p') == 0 and\n redis.call('HEXISTS', KEYS[4], tostring(event_index) .. ':f') == 0 then\n local pin_key = ARGV[2] .. tostring(event_index)\n if redis.call('SADD', pin_key, ref) == 1 then\n redis.call('HINCRBY', KEYS[5], ref, 1)\n end\n end\n end\n writes[#writes + 1] = { index = index, snapshot = snapshot, ref = ref }\n offset = offset + 3 + pin_count\nend\n\nlocal head = redis.call('GET', KEYS[1])\nlocal head_index = nil\nif head then head_index = tonumber(cjson.decode(head)['index']) end\nlocal latest = writes[#writes]\nlocal writes_head = not head_index or latest.index >= head_index\nlocal advances = not head_index or latest.index > head_index\nlocal final_head_index = advances and latest.index or head_index\n\nif advances and head and tonumber(redis.call('HGET', KEYS[5], cjson.encode({ KEYS[2], tostring(head_index) })) or '0') > 0 then\n redis.call('ZREMRANGEBYSCORE', KEYS[2], head_index, head_index)\n redis.call('ZADD', KEYS[2], head_index, head)\nend\n\nfor _, write in ipairs(writes) do\n if write.index < final_head_index then\n redis.call('ZREMRANGEBYSCORE', KEYS[2], write.index, write.index)\n if tonumber(redis.call('HGET', KEYS[5], write.ref) or '0') > 0 then\n redis.call('ZADD', KEYS[2], write.index, write.snapshot)\n end\n end\nend\n\nif writes_head then\n redis.call('SET', KEYS[1], latest.snapshot)\n redis.call('ZREMRANGEBYSCORE', KEYS[2], latest.index, latest.index)\nend\nreturn 1\n`\n\n/**\n * KEYS: presence hash. ARGV: atMs, ttlMs, n, then per entry:\n * hashField, valueJson ('' deletes). Field-wise LWW by the sender's\n * `at`: a stored entry with a strictly newer stamp survives; ties go\n * to the incoming write (memory-tier parity). Returns the applied\n * 1-based entry positions. The whole-key PEXPIRE is the coarse bound,\n * sound because core sends one uniform ttlMs; the per-entry `e` stamp\n * is the fine bound (the injected storage clock at the applied set\n * plus ttlMs — never the sender stamp), pruned on read against the\n * same clock.\n */\nconst PRESENCE_SET_LUA = `\nlocal at = tonumber(ARGV[1])\nlocal applied = {}\nfor i = 1, tonumber(ARGV[3]) do\n local field = ARGV[3 + (i - 1) * 2 + 1]\n local value = ARGV[3 + (i - 1) * 2 + 2]\n local existing = redis.call('HGET', KEYS[1], field)\n if not existing or tonumber(cjson.decode(existing)['a']) <= at then\n if value == '' then\n redis.call('HDEL', KEYS[1], field)\n else\n redis.call('HSET', KEYS[1], field, value)\n end\n applied[#applied + 1] = tostring(i)\n end\nend\nif #applied > 0 then redis.call('PEXPIRE', KEYS[1], ARGV[2]) end\nreturn applied\n`\n\n/**\n * KEYS: presence hash. ARGV: nowMs (the injected clock, so tests can\n * time-travel expiry against a real server). Returns the live\n * [field, value, ...] pairs; entries at or past their fine-bound\n * expiry are HDELed in the same atomic step, so a stale reader can\n * never sweep a concurrently refreshed field.\n */\nconst PRESENCE_READ_LUA = `\nlocal now = tonumber(ARGV[1])\nlocal rows = redis.call('HGETALL', KEYS[1])\nlocal live = {}\nfor i = 1, #rows, 2 do\n if tonumber(cjson.decode(rows[i + 1])['e']) <= now then\n redis.call('HDEL', KEYS[1], rows[i])\n else\n live[#live + 1] = rows[i]\n live[#live + 1] = rows[i + 1]\n end\nend\nreturn live\n`\n\n/**\n * KEYS: head, history, event counter. ARGV: snapshot frontier or ''.\n * Returns one atomic multiversion-cache-plus-log-frontier read.\n */\nconst READ_STATE_LUA = `\nlocal head = redis.call('GET', KEYS[1])\nlocal cap = ARGV[1]\nlocal snapshot = nil\nif head and (cap == '' or tonumber(cjson.decode(head)['index']) <= tonumber(cap)) then\n snapshot = head\nend\nlocal upper = cap == '' and '+inf' or cap\nlocal historical = redis.call('ZREVRANGEBYSCORE', KEYS[2], upper, '-inf', 'LIMIT', 0, 1)\nif #historical > 0 then\n if not snapshot or tonumber(cjson.decode(historical[1])['index']) > tonumber(cjson.decode(snapshot)['index']) then\n snapshot = historical[1]\n end\nend\nlocal frontier = redis.call('GET', KEYS[3]) or '0'\nreturn { head or '', snapshot or '', frontier }\n`\n\n// ── wire helpers ─────────────────────────────────────────────────────\n\ntype StreamEntry = [id: string, fields: string[]]\n\nconst fieldMap = (fields: string[]): Record<string, string> => {\n const map: Record<string, string> = {}\n for (let i = 0; i + 1 < fields.length; i += 2) {\n map[fields[i]!] = fields[i + 1]!\n }\n return map\n}\n\nconst STORED_EVENT_META_SUFFIXES = [\n 'p',\n 'pa',\n 'ri',\n 'ch',\n 'ce',\n 'f',\n 'fc',\n 'lc',\n 'lf',\n 'lfa',\n 'd',\n 'a',\n 'e',\n] as const\n\nconst entryIndex = (entryId: string): number =>\n Number(entryId.slice(0, entryId.indexOf('-')))\n\nconst toEvent = (sessionId: string, entry: StreamEntry): Event => {\n const fields = fieldMap(entry[1])\n return {\n id: fields['id']!,\n type: fields['type']!,\n payload: JSON.parse(fields['payload']!),\n index: entryIndex(entry[0]),\n sessionId,\n createdAt: new Date(Number(fields['created_at'])),\n }\n}\n\nconst json = (value: unknown): string => JSON.stringify(value) ?? 'null'\n\nconst toCause = (raw: string | undefined): EventCause | null => {\n if (raw === '' || raw === undefined) return null\n const value = JSON.parse(raw) as {\n index?: unknown\n attempt?: unknown\n batchSize?: unknown\n }\n if (\n !Number.isInteger(value.index) ||\n Number(value.index) < 1 ||\n !Number.isInteger(value.attempt) ||\n Number(value.attempt) < 1\n ) {\n throw new TypeError('stored event has an invalid cause')\n }\n if (\n value.batchSize !== undefined &&\n (!Number.isInteger(value.batchSize) || Number(value.batchSize) < 1)\n ) {\n throw new TypeError('stored event has an invalid cause')\n }\n return {\n index: Number(value.index),\n attempt: Number(value.attempt),\n ...(value.batchSize === undefined\n ? {}\n : { batchSize: Number(value.batchSize) }),\n }\n}\n\nconst toClaimAvailableResult = (\n sessionId: string,\n reply: unknown[],\n): StoreClaimAvailableResult => {\n const outcome = String(reply[0])\n if (outcome === 'claimed') {\n const count = Number(reply[1])\n const events: StoredEvent[] = []\n for (let i = 0; i < count; i++) {\n const offset = 2 + i * 10\n const entry = reply[offset + 9] as StreamEntry\n const event = toEvent(sessionId, entry)\n const fields = fieldMap(entry[1])\n events.push(\n Object.assign(event, {\n cause: toCause(fields['cause']),\n lane: fields['lane'] === '' ? null : fields['lane']!,\n processedAt: null,\n processedByAttempt: null,\n returnedEventIds: null,\n firstClaimedAt:\n reply[offset + 3] === null\n ? null\n : new Date(Number(reply[offset + 3])),\n lastClaimedAt: new Date(Number(reply[offset + 4])),\n attemptCount: Number(reply[offset]),\n claimHolder: String(reply[offset + 7]),\n claimExpiresAt: new Date(Number(reply[offset + 8])),\n failureCount: Number(reply[offset + 1]),\n lastFailedAt:\n reply[offset + 5] === null\n ? null\n : new Date(Number(reply[offset + 5])),\n lastFailedAttempt:\n reply[offset + 6] === null ? null : Number(reply[offset + 6]),\n lastError:\n reply[offset + 2] === null ? null : String(reply[offset + 2]),\n failedAt: null,\n }),\n )\n }\n return {\n outcome: 'claimed',\n events,\n }\n }\n if (outcome === 'busy') {\n return { outcome, retryAt: new Date(Number(reply[1])) }\n }\n if (outcome === 'settled') return { outcome }\n throw new TypeError(\n `claim referenced a missing event in session '${sessionId}'`,\n )\n}\n\nconst wrap = async <T>(fn: () => Promise<T>): Promise<T> => {\n try {\n return await fn()\n } catch (err) {\n if (err instanceof A2Error || err instanceof TypeError) throw err\n throw new A2Error('STORE_UNAVAILABLE', 'redis store operation failed', {\n cause: err,\n })\n }\n}\n\nexport function createRedisStoreCore(\n options: RedisStoreCoreOptions,\n): RedisStoreCore {\n const { call, clock, notify } = options\n const generateId = options.ids\n const prefix = options.keyPrefix\n\n const storeKey = (s: string): string => `${prefix}:${s}:log`\n const countKey = (s: string): string => `${prefix}:${s}:count`\n const metaKey = (s: string): string => `${prefix}:${s}:meta`\n const pendingKey = (s: string): string => `${prefix}:${s}:pending`\n const readyKey = (s: string): string => `${prefix}:${s}:ready`\n const snapKey = (s: string, r: string): string => `${prefix}:${s}:snap:${r}`\n const snapHistoryKey = (s: string, r: string): string =>\n `${prefix}:${s}:snap-history:${r}`\n const snapshotEventPinsKey = (s: string, index: number): string =>\n `${prefix}:${s}:snapshot-pins:${index}`\n const snapshotEventPinsPrefix = (s: string): string =>\n `${prefix}:${s}:snapshot-pins:`\n const snapshotPinCountsKey = (s: string): string =>\n `${prefix}:${s}:snapshot-pin-counts`\n const watchedKey = (s: string): string => `${prefix}:${s}:watched`\n const presenceKey = (s: string): string => `${prefix}:${s}:presence`\n const idsKey = `${prefix}:ids`\n\n const evalScript = (\n script: string,\n keys: string[],\n args: Array<string | number>,\n ): Promise<unknown> => call('EVAL', script, keys.length, ...keys, ...args)\n\n const readFrontier = async (sessionId: string): Promise<number> => {\n const frontier = Number((await call('GET', countKey(sessionId))) ?? 0)\n if (!Number.isSafeInteger(frontier) || frontier < 0) {\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n `redis event count for session '${sessionId}' is invalid`,\n )\n }\n return frontier\n }\n\n const readRangePage = async (\n sessionId: string,\n afterIndex: number,\n throughIndex: number,\n limit: number,\n ): Promise<StoredEvent[]> => {\n if (throughIndex <= afterIndex) return []\n const entries = (await call(\n 'XRANGE',\n storeKey(sessionId),\n `${afterIndex + 1}-0`,\n `${throughIndex}-0`,\n 'COUNT',\n limit,\n )) as StreamEntry[]\n if (entries.length === 0) return []\n\n const metaFields = entries.flatMap((entry) => {\n const index = entryIndex(entry[0])\n return STORED_EVENT_META_SUFFIXES.map((suffix) => `${index}:${suffix}`)\n })\n const metaValues =\n ((await call('HMGET', metaKey(sessionId), ...metaFields)) as Array<\n string | null\n > | null) ?? []\n const meta: Record<string, string> = {}\n for (let i = 0; i < metaFields.length; i += 1) {\n const value = metaValues[i]\n if (value !== null && value !== undefined) {\n meta[metaFields[i]!] = value\n }\n }\n return entries.map((entry) => {\n const event = toEvent(sessionId, entry)\n const idx = event.index\n const fields = fieldMap(entry[1])\n const processedAt = meta[`${idx}:p`]\n const processedByAttempt = meta[`${idx}:pa`]\n const returnedEventIds = meta[`${idx}:ri`]\n const claimHolder = meta[`${idx}:ch`]\n const claimExpiresAt = meta[`${idx}:ce`]\n const failedAt = meta[`${idx}:f`]\n const firstClaimedAt = meta[`${idx}:fc`]\n const lastClaimedAt = meta[`${idx}:lc`]\n const lastFailedAt = meta[`${idx}:lf`]\n const lastFailedAttempt = meta[`${idx}:lfa`]\n return Object.assign(event, {\n cause: toCause(fields['cause']),\n lane: fields['lane'] === '' ? null : fields['lane']!,\n processedAt:\n processedAt === undefined ? null : new Date(Number(processedAt)),\n processedByAttempt:\n processedByAttempt === undefined ? null : Number(processedByAttempt),\n returnedEventIds:\n returnedEventIds === undefined\n ? null\n : decodeReturnedEventIds(returnedEventIds),\n firstClaimedAt:\n firstClaimedAt === undefined\n ? null\n : new Date(Number(firstClaimedAt)),\n lastClaimedAt:\n lastClaimedAt === undefined ? null : new Date(Number(lastClaimedAt)),\n attemptCount: Number(meta[`${idx}:d`] ?? 0),\n claimHolder: claimHolder ?? null,\n claimExpiresAt:\n claimExpiresAt === undefined\n ? null\n : new Date(Number(claimExpiresAt)),\n failureCount: Number(meta[`${idx}:a`] ?? 0),\n lastFailedAt:\n lastFailedAt === undefined ? null : new Date(Number(lastFailedAt)),\n lastFailedAttempt:\n lastFailedAttempt === undefined ? null : Number(lastFailedAttempt),\n lastError: meta[`${idx}:e`] ?? null,\n failedAt: failedAt === undefined ? null : new Date(Number(failedAt)),\n })\n })\n }\n\n const readEventRange = async (\n sessionId: string,\n afterIndex: number,\n throughIndex: number,\n ): Promise<Event[]> => {\n if (throughIndex <= afterIndex) return []\n const events: Event[] = []\n let nextIndex = afterIndex + 1\n while (nextIndex <= throughIndex) {\n // oxlint-disable-next-line no-await-in-loop -- each page advances the dense frozen range\n const entries = (await call(\n 'XRANGE',\n storeKey(sessionId),\n `${nextIndex}-0`,\n `${throughIndex}-0`,\n 'COUNT',\n READ_PAGE_SIZE,\n )) as StreamEntry[]\n if (entries.length === 0) {\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n `redis event range for session '${sessionId}' ended before index ${throughIndex}`,\n )\n }\n for (let position = 0; position < entries.length; position += 1) {\n const index = entryIndex(entries[position]![0])\n if (index !== nextIndex + position || index > throughIndex) {\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n `redis event range for session '${sessionId}' is not contiguous`,\n )\n }\n }\n events.push(...entries.map((entry) => toEvent(sessionId, entry)))\n nextIndex = events.at(-1)!.index + 1\n }\n return events\n }\n\n /** Bounded XRANGE pages restore the full-read contract on capped providers. */\n const readRange = async (\n sessionId: string,\n afterIndex: number,\n throughIndex?: number,\n ): Promise<StoredEvent[]> => {\n if (throughIndex !== undefined && throughIndex <= afterIndex) return []\n const currentFrontier = await readFrontier(sessionId)\n const frontier = Math.min(throughIndex ?? currentFrontier, currentFrontier)\n if (frontier <= afterIndex) return []\n\n const rows: StoredEvent[] = []\n let nextIndex = afterIndex + 1\n while (nextIndex <= frontier) {\n // oxlint-disable-next-line no-await-in-loop -- each page advances the dense frozen range\n const page = await readRangePage(\n sessionId,\n nextIndex - 1,\n frontier,\n READ_PAGE_SIZE,\n )\n if (page.length === 0) {\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n `redis event range for session '${sessionId}' ended before index ${frontier}`,\n )\n }\n for (let position = 0; position < page.length; position += 1) {\n if (page[position]!.index !== nextIndex + position) {\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n `redis event range for session '${sessionId}' is not contiguous`,\n )\n }\n }\n const lastIndex = page.at(-1)!.index\n if (lastIndex < nextIndex || lastIndex > frontier) {\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n `redis event range for session '${sessionId}' did not advance`,\n )\n }\n rows.push(...page)\n nextIndex = lastIndex + 1\n }\n return rows\n }\n\n return {\n async append(sessionId, events) {\n return wrap(async () => {\n const supplied = events.filter((e) => e.id !== undefined)\n const suppliedIds = supplied.map((e) => e.id as string)\n if (new Set(suppliedIds).size !== suppliedIds.length) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'batch contains the same event id more than once',\n )\n }\n const now = clock.now().getTime()\n const withIds = events.map((e) => ({\n id: e.id ?? generateId(),\n type: e.type,\n payload: e.payload,\n cause: e.cause ? { ...e.cause } : null,\n lane: e.lane ?? null,\n settled: e.settled === true,\n }))\n const args: Array<string | number> = [\n sessionId,\n now,\n withIds.length,\n ...withIds.flatMap((e) => [\n e.id,\n e.type,\n json(e.payload),\n e.cause ? json(e.cause) : '',\n e.lane ?? '',\n e.settled ? '1' : '0',\n ]),\n ]\n const reply = (await evalScript(\n APPEND_LUA,\n [\n storeKey(sessionId),\n countKey(sessionId),\n idsKey,\n metaKey(sessionId),\n pendingKey(sessionId),\n readyKey(sessionId),\n watchedKey(sessionId),\n ],\n args,\n )) as string[]\n\n const [verdict] = reply\n if (verdict === 'empty') {\n return { events: [], hasPending: reply[1] === '1' }\n }\n if (verdict === 'foreign') {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n `event id '${reply[1]}' already exists in another session`,\n )\n }\n if (verdict === 'no_parent') {\n throw new TypeError(\n `no event at index ${reply[1]} in session '${sessionId}'`,\n )\n }\n if (verdict === 'superseded') {\n throw new A2Error(\n 'SUPERSEDED_ATTEMPT',\n `a newer attempt owns event ${reply[1]} in session '${sessionId}'`,\n )\n }\n if (verdict === 'partial') {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n `batch mixes ${reply[1]} already-appended and ${events.length - Number(reply[1])} fresh events`,\n )\n }\n if (verdict === 'dup') {\n // A retry of a committed batch whose ack was lost — return\n // the original rows, ordered by index like every adapter.\n const hasPending = reply[1] === '1'\n const wanted = new Set(reply.slice(2).map(Number))\n const min = Math.min(...wanted)\n const rows = await readRange(sessionId, min - 1)\n const existing = rows.filter((row) => wanted.has(row.index))\n return {\n events: idempotentReplay(events, existing),\n hasPending,\n }\n }\n const base = Number(reply[1]) - 1\n if (reply[3] === '1') notify?.(sessionId, base + withIds.length)\n return {\n events: withIds.map((e, i) => ({\n id: e.id,\n type: e.type,\n payload: structuredClone(e.payload),\n index: base + 1 + i,\n sessionId,\n createdAt: new Date(now),\n cause: e.cause,\n lane: e.lane,\n processedAt: e.settled ? new Date(now) : null,\n processedByAttempt: null,\n returnedEventIds: null,\n firstClaimedAt: null,\n lastClaimedAt: null,\n attemptCount: 0,\n claimHolder: null,\n claimExpiresAt: null,\n failureCount: 0,\n lastFailedAt: null,\n lastFailedAttempt: null,\n lastError: null,\n failedAt: null,\n })),\n hasPending: reply[2] === '1',\n }\n })\n },\n\n async read(sessionId, opts) {\n return wrap(async () => {\n const rows = await readRange(\n sessionId,\n opts?.afterIndex ?? 0,\n opts?.throughIndex,\n )\n return rows\n })\n },\n\n async claimAvailable({\n sessionId,\n holder,\n ttlMs,\n expiresAtMs,\n excludeIndexes,\n }) {\n return wrap(async () => {\n const now = clock.now().getTime()\n const expiresAt = expiresAtMs ?? now + ttlMs\n const excluded = excludeIndexes ?? []\n const reply = (await evalScript(\n CLAIM_AVAILABLE_LUA,\n [\n storeKey(sessionId),\n countKey(sessionId),\n metaKey(sessionId),\n pendingKey(sessionId),\n readyKey(sessionId),\n ],\n [holder, now, expiresAt, excluded.length, ...excluded],\n )) as unknown[]\n return toClaimAvailableResult(sessionId, reply)\n })\n },\n\n async renewClaims({ sessionId, holder, claims, ttlMs, expiresAtMs }) {\n return wrap(async () => {\n const now = clock.now().getTime()\n const expiresAt = expiresAtMs ?? now + ttlMs\n const reply = (await evalScript(\n RENEW_CLAIMS_LUA,\n [countKey(sessionId), metaKey(sessionId)],\n [\n holder,\n now,\n expiresAt,\n claims.length,\n ...claims.flatMap((claim) => [claim.index, claim.attempt]),\n ],\n )) as string[]\n const renewedCount = Number(reply[0])\n const values = reply.slice(1).map(Number)\n return {\n renewed: values.slice(0, renewedCount).toSorted((a, b) => a - b),\n superseded: values.slice(renewedCount).toSorted((a, b) => a - b),\n }\n })\n },\n\n async completeAttempt({ sessionId, index, attempt, events }) {\n return wrap(async () => {\n const ids = events.map((event) => event.id)\n if (new Set(ids).size !== ids.length) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'batch contains the same event id more than once',\n )\n }\n const now = clock.now().getTime()\n const reply = (await evalScript(\n COMPLETE_ATTEMPT_LUA,\n [\n storeKey(sessionId),\n countKey(sessionId),\n idsKey,\n metaKey(sessionId),\n pendingKey(sessionId),\n readyKey(sessionId),\n watchedKey(sessionId),\n snapshotEventPinsKey(sessionId, index),\n snapshotPinCountsKey(sessionId),\n ],\n [\n sessionId,\n now,\n index,\n attempt,\n events.length,\n ...events.flatMap((event) => [\n event.id,\n event.type,\n json(event.payload),\n event.lane ?? '',\n event.settled === true ? '1' : '0',\n ]),\n ],\n )) as string[]\n const outcome = reply[0]\n if (outcome === 'missing') {\n throw new TypeError(\n `no event at index ${index} in session '${sessionId}'`,\n )\n }\n if (outcome === 'partial') {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'returned event ids do not match the completed attempt',\n )\n }\n if (outcome === 'superseded') return { outcome }\n\n const cause = { index, attempt }\n if (outcome === 'ok') {\n const base = Number(reply[1]) - 1\n if (events.length > 0 && reply[2] === '1') {\n notify?.(sessionId, base + events.length)\n }\n return {\n outcome: 'completed',\n events: events.map((event, i) => ({\n id: event.id,\n type: event.type,\n payload: structuredClone(event.payload),\n index: base + 1 + i,\n sessionId,\n createdAt: new Date(now),\n cause,\n lane: event.lane ?? null,\n processedAt: event.settled === true ? new Date(now) : null,\n processedByAttempt: null,\n returnedEventIds: null,\n firstClaimedAt: null,\n lastClaimedAt: null,\n attemptCount: 0,\n claimHolder: null,\n claimExpiresAt: null,\n failureCount: 0,\n lastFailedAt: null,\n lastFailedAttempt: null,\n lastError: null,\n failedAt: null,\n })),\n } satisfies CompleteAttemptResult\n }\n if (outcome === 'duplicate') {\n if (events.length === 0) {\n return { outcome: 'completed', events: [] }\n }\n const wanted = new Set(reply.slice(1).map(Number))\n const min = Math.min(...wanted)\n const rows = await readRange(sessionId, min - 1)\n const byIndex = new Map(rows.map((row) => [row.index, row]))\n const existing = reply\n .slice(1)\n .map((value) => byIndex.get(Number(value))!)\n return {\n outcome: 'completed',\n events: idempotentReplay(events, existing),\n }\n }\n throw new TypeError(`unexpected completion outcome '${outcome}'`)\n })\n },\n\n async failAttempt({ sessionId, index, attempt, error, maxFailures }) {\n return wrap(async () => {\n const reply = (await evalScript(\n FAIL_ATTEMPT_LUA,\n [\n countKey(sessionId),\n metaKey(sessionId),\n pendingKey(sessionId),\n readyKey(sessionId),\n snapshotEventPinsKey(sessionId, index),\n snapshotPinCountsKey(sessionId),\n ],\n [index, attempt, error, maxFailures, clock.now().getTime()],\n )) as string[]\n const outcome = reply[0]\n if (outcome === 'missing') {\n throw new TypeError(\n `no event at index ${index} in session '${sessionId}'`,\n )\n }\n return {\n outcome: outcome as 'failed' | 'dead_lettered' | 'superseded',\n failureCount: Number(reply[1]),\n }\n })\n },\n\n async readState(sessionId, reducerName, stateOptions) {\n return wrap(async () => {\n const reply = (await evalScript(\n READ_STATE_LUA,\n [\n snapKey(sessionId, reducerName),\n snapHistoryKey(sessionId, reducerName),\n countKey(sessionId),\n ],\n [\n stateOptions?.snapshotThroughIndex ??\n stateOptions?.throughIndex ??\n '',\n ],\n )) as [string, string, string]\n const rawHead = reply[0]\n const raw = reply[1]\n const head =\n rawHead === ''\n ? null\n : (JSON.parse(rawHead) as { index: number; state: unknown })\n const parsed =\n raw === ''\n ? null\n : (JSON.parse(raw) as { index: number; state: unknown })\n const currentFrontier = Number(reply[2])\n if (\n !Number.isSafeInteger(currentFrontier) ||\n currentFrontier < 0 ||\n (head !== null &&\n (!Number.isSafeInteger(head.index) || head.index < 0)) ||\n (parsed !== null &&\n (!Number.isSafeInteger(parsed.index) || parsed.index < 0))\n ) {\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n `redis state frontier for session '${sessionId}' is invalid`,\n )\n }\n const frontier = Math.min(\n currentFrontier,\n stateOptions?.throughIndex ?? currentFrontier,\n )\n const snapshot = parsed\n const events = await readEventRange(\n sessionId,\n snapshot?.index ?? 0,\n frontier,\n )\n return {\n headIndex: head?.index ?? null,\n snapshot: snapshot\n ? { index: snapshot.index, state: snapshot.state }\n : null,\n events,\n }\n })\n },\n\n async putSnapshots(sessionId, reducerName, writes) {\n const byIndex = new Map<\n number,\n { index: number; state: unknown; pinEventIndexes: Set<number> }\n >()\n for (const write of writes) {\n const existing = byIndex.get(write.index)\n if (existing) {\n existing.state = write.state\n for (const eventIndex of write.pinEventIndexes ?? []) {\n existing.pinEventIndexes.add(eventIndex)\n }\n } else {\n byIndex.set(write.index, {\n index: write.index,\n state: write.state,\n pinEventIndexes: new Set(write.pinEventIndexes ?? []),\n })\n }\n }\n const normalized = [...byIndex.values()].toSorted(\n (a, b) => a.index - b.index,\n )\n if (normalized.length === 0) return\n await wrap(async () => {\n const args: Array<string | number> = [\n clock.now().getTime(),\n snapshotEventPinsPrefix(sessionId),\n normalized.length,\n ]\n for (const write of normalized) {\n args.push(\n write.index,\n json({\n index: write.index,\n state: write.state,\n updatedAt: clock.now().getTime(),\n }),\n write.pinEventIndexes.size,\n ...write.pinEventIndexes,\n )\n }\n await evalScript(\n PUT_SNAPSHOTS_LUA,\n [\n snapKey(sessionId, reducerName),\n snapHistoryKey(sessionId, reducerName),\n countKey(sessionId),\n metaKey(sessionId),\n snapshotPinCountsKey(sessionId),\n ],\n args,\n )\n })\n },\n\n presence: {\n async set(ns, participant, values, meta) {\n return wrap(async () => {\n const entries = Object.entries(values)\n if (entries.length === 0) return null\n const atMs = meta.at.getTime()\n // Expiry anchors on the injected storage clock — the sender's\n // `a` stamp orders writes, the `e` stamp bounds their\n // lifetime, and the two clocks never mix.\n const expiresAtMs = clock.now().getTime() + meta.ttlMs\n // The hash field is JSON `[participant, field]`: both are\n // caller strings, so no separator character is\n // collision-safe — the JSON array is.\n const applied = (await evalScript(\n PRESENCE_SET_LUA,\n [presenceKey(ns)],\n [\n atMs,\n meta.ttlMs,\n entries.length,\n ...entries.flatMap(([field, value]) => [\n JSON.stringify([participant, field]),\n value === null\n ? ''\n : json({\n v: value,\n s: meta.seen,\n a: atMs,\n e: expiresAtMs,\n }),\n ]),\n ],\n )) as string[]\n if (applied.length === 0) return null\n // Field-keyed and caller-named — null-prototype, like every\n // presence map (see `nullProtoRecord`).\n const appliedValues: Record<string, unknown> = nullProtoRecord()\n for (const position of applied) {\n const [field, value] = entries[Number(position) - 1]!\n appliedValues[field] = structuredClone(value)\n }\n return {\n participant,\n values: appliedValues,\n seen: meta.seen,\n at: new Date(atMs),\n }\n })\n },\n\n async read(ns) {\n return wrap(async () => {\n const reply = (await evalScript(\n PRESENCE_READ_LUA,\n [presenceKey(ns)],\n [clock.now().getTime()],\n )) as string[]\n const rows: PresenceRow[] = []\n for (let i = 0; i + 1 < reply.length; i += 2) {\n const [participant, field] = JSON.parse(reply[i]!) as [\n string,\n string,\n ]\n const entry = JSON.parse(reply[i + 1]!) as {\n v: unknown\n s: number\n a: number\n e: number\n }\n rows.push({\n participant,\n field,\n value: entry.v,\n seen: entry.s,\n at: new Date(entry.a),\n expiresAt: new Date(entry.e),\n })\n }\n return rows\n })\n },\n },\n\n async readRange(sessionId, afterIndex, throughIndex) {\n return wrap(() => readRange(sessionId, afterIndex, throughIndex))\n },\n\n async readEvents(sessionId, afterIndex) {\n return wrap(async () => {\n const entries = (await call(\n 'XRANGE',\n storeKey(sessionId),\n `${afterIndex + 1}-0`,\n '+',\n )) as StreamEntry[]\n return entries.map((entry) => toEvent(sessionId, entry))\n })\n },\n\n async markWatched(sessionId, ttlMs) {\n await wrap(() => call('SET', watchedKey(sessionId), '1', 'PX', ttlMs))\n },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"store-redis-core-z-ykbyMg.js","names":[],"sources":["../src/store-redis-core.ts"],"sourcesContent":["/**\n * The shared Redis store core — every operation except `stream` and\n * `close`, expressed over one injected `call` function so both\n * transports reuse it: `store-redis` (Redis protocol via `ioredis`) and\n * `store-redis-http` (provider REST APIs).\n *\n * The stream IS the store: each event is a stream entry whose ID is its\n * dense a2 index (`${index}-0`), so `XRANGE` is the catch-up read.\n * Mutable drain/failure bookkeeping lives in a side hash (stream\n * entries are immutable); every write path is one atomic Lua script.\n * The conformance suite in test/conformance is the executable\n * contract, run against a real spawned `redis-server`.\n *\n * Timestamps and claim expiry come from the injected clock so tests\n * can time-travel against a real server. Cluster mode is out: the\n * atomic scripts span keys.\n */\n\nimport type { PresencePatch } from './contract.ts'\nimport { A2Error } from './errors.ts'\nimport { idempotentReplay } from './idempotent-replay.ts'\nimport { nullProtoRecord } from './internal.ts'\nimport { decodeReturnedEventIds } from './store-codec.ts'\nimport type {\n A2Store,\n Clock,\n CompleteAttemptResult,\n Event,\n EventCause,\n IdSource,\n StoreClaimAvailableResult,\n PresenceRow,\n StoredEvent,\n} from './store.ts'\n\n/** Issues any Redis command; the seam both transports implement. */\nexport type RedisCall = (\n command: string,\n ...args: Array<string | number>\n) => Promise<unknown>\n\nexport type RedisStoreCoreOptions = {\n call: RedisCall\n clock: Clock\n ids: IdSource\n keyPrefix: string\n /**\n * Disposable wake-up fired after a write appends new events to a\n * watched session — `count` is the session's new event count. The\n * watched check runs inside the write script, so unwatched sessions\n * cost no wake-up command at all. Losing one is harmless: live\n * feeds re-read on a safety cadence, so notifications only affect\n * latency, never delivery.\n */\n notify?: (sessionId: string, count: number) => void\n}\n\nexport type RedisStoreCore = Omit<A2Store, 'stream' | 'presence'> & {\n /**\n * The storage half of the presence capability, shared by both\n * transports. `set` returns the applied patch — null when every\n * field lost the LWW race — so a push-capable transport broadcasts\n * exactly what `read` now reflects; delivery is its business.\n */\n presence: {\n set(\n ns: string,\n participant: string,\n values: Record<string, unknown | null>,\n meta: { seen: number; at: Date; ttlMs: number },\n ): Promise<PresencePatch | null>\n read(ns: string): Promise<PresenceRow[]>\n }\n /** XRANGE + meta merge — the full-row catch-up read. */\n readRange(\n sessionId: string,\n afterIndex: number,\n throughIndex?: number,\n ): Promise<StoredEvent[]>\n /** XRANGE only — the light catch-up read live feeds use. */\n readEvents(sessionId: string, afterIndex: number): Promise<Event[]>\n /**\n * Declare the session watched for `ttlMs` — writes publish wake-ups\n * only while this presence marker lives. Live feeds refresh it\n * ahead of expiry; an expired marker degrades latency to the safety\n * cadence, never delivery.\n */\n markWatched(sessionId: string, ttlMs: number): Promise<void>\n}\n\nconst READ_PAGE_SIZE = 256\n\n// ── Lua scripts (each runs atomically) ───────────────────────────────\n\n/**\n * KEYS: store stream, counter, global id hash, metadata, pending, ready,\n * watched.\n * ARGV: sessionId, nowMs, n, then per event:\n * id, type, payloadJson, causeJson, lane, settled.\n * Returns ['empty', hasPending] | ['ok', firstIndex, hasPending, watched] |\n * ['dup', hasPending, ...indexes] | ['partial', count] |\n * ['foreign', eventId] | ['no_parent', idx] | ['superseded', idx].\n */\nconst APPEND_LUA = `\nlocal function lane_field(lane, suffix)\n return '__lane:' .. tostring(string.len(lane)) .. ':' .. lane .. ':' .. suffix\nend\nlocal function enqueue(field, idx, lane)\n redis.call('ZADD', KEYS[5], idx, field)\n if lane == '' then\n redis.call('ZADD', KEYS[6], idx, field)\n return\n end\n local tail_key = lane_field(lane, 'tail')\n local tail = redis.call('HGET', KEYS[4], tail_key)\n if tail then\n redis.call('HSET', KEYS[4], tail .. ':next', field)\n else\n redis.call('HSET', KEYS[4], lane_field(lane, 'head'), field)\n redis.call('ZADD', KEYS[6], idx, field)\n end\n redis.call('HSET', KEYS[4], tail_key, field)\nend\nlocal n = tonumber(ARGV[3])\nlocal base = tonumber(redis.call('GET', KEYS[2]) or '0')\nlocal has_pending = redis.call('ZCARD', KEYS[5]) > 0 and '1' or '0'\nif n == 0 then return { 'empty', has_pending } end\nlocal dups = {}\nfor i = 1, n do\n local id = ARGV[3 + (i - 1) * 6 + 1]\n local existing = redis.call('HGET', KEYS[3], id)\n if existing then\n local rec = cjson.decode(existing)\n if rec[1] ~= ARGV[1] then return { 'foreign', id } end\n dups[#dups + 1] = tostring(rec[2])\n end\nend\nif #dups == n then\n local res = { 'dup', has_pending }\n for i = 1, #dups do res[#res + 1] = dups[i] end\n return res\nend\nif #dups > 0 then return { 'partial', tostring(#dups) } end\nfor i = 1, n do\n local cause = ARGV[3 + (i - 1) * 6 + 4]\n if cause ~= '' then\n local parsed = cjson.decode(cause)\n local pidx = tonumber(parsed.index)\n if pidx < 1 or pidx > base then\n return { 'no_parent', tostring(parsed.index) }\n end\n local field = tostring(pidx)\n local dispatches = tonumber(redis.call('HGET', KEYS[4], field .. ':d') or '0')\n if dispatches ~= tonumber(parsed.attempt) or\n redis.call('HEXISTS', KEYS[4], field .. ':f') == 1 then\n return { 'superseded', field }\n end\n end\nend\nfor i = 1, n do\n local id = ARGV[3 + (i - 1) * 6 + 1]\n local typ = ARGV[3 + (i - 1) * 6 + 2]\n local payload = ARGV[3 + (i - 1) * 6 + 3]\n local cause = ARGV[3 + (i - 1) * 6 + 4]\n local lane = ARGV[3 + (i - 1) * 6 + 5]\n local settled = ARGV[3 + (i - 1) * 6 + 6]\n local idx = base + i\n redis.call('XADD', KEYS[1], tostring(idx) .. '-0',\n 'id', id, 'type', typ, 'payload', payload, 'created_at', ARGV[2],\n 'cause', cause, 'lane', lane)\n redis.call('HSET', KEYS[3], id, cjson.encode({ ARGV[1], idx }))\n redis.call('HSET', KEYS[4], tostring(idx) .. ':l', lane)\n if settled == '1' then\n redis.call('HSET', KEYS[4], tostring(idx) .. ':p', ARGV[2])\n else\n enqueue(tostring(idx), idx, lane)\n end\nend\nredis.call('SET', KEYS[2], tostring(base + n))\nhas_pending = redis.call('ZCARD', KEYS[5]) > 0 and '1' or '0'\nreturn { 'ok', tostring(base + 1), has_pending,\n tostring(redis.call('EXISTS', KEYS[7])) }\n`\n\n/**\n * KEYS: counter, meta, pending, ready, event snapshot pins, snapshot pin counts.\n * ARGV: idx, attempt, error, maxFailures, nowMs.\n */\nconst FAIL_ATTEMPT_LUA = `\nlocal function release_snapshot_pins()\n local refs = redis.call('SMEMBERS', KEYS[5])\n for _, ref in ipairs(refs) do\n local count = redis.call('HINCRBY', KEYS[6], ref, -1)\n if count <= 0 then\n redis.call('HDEL', KEYS[6], ref)\n local checkpoint = cjson.decode(ref)\n redis.call('ZREMRANGEBYSCORE', checkpoint[1], checkpoint[2], checkpoint[2])\n end\n end\n redis.call('DEL', KEYS[5])\n if redis.call('HLEN', KEYS[6]) == 0 then redis.call('DEL', KEYS[6]) end\nend\nlocal function lane_field(lane, suffix)\n return '__lane:' .. tostring(string.len(lane)) .. ':' .. lane .. ':' .. suffix\nend\nlocal function settle(field)\n redis.call('ZREM', KEYS[3], field)\n redis.call('ZREM', KEYS[4], field)\n local lane = redis.call('HGET', KEYS[2], field .. ':l') or ''\n if lane == '' then return end\n local head_key = lane_field(lane, 'head')\n if redis.call('HGET', KEYS[2], head_key) ~= field then return end\n local next_field = redis.call('HGET', KEYS[2], field .. ':next')\n while next_field and redis.call('HEXISTS', KEYS[2], next_field .. ':p') == 1 do\n next_field = redis.call('HGET', KEYS[2], next_field .. ':next')\n end\n if next_field then\n redis.call('HSET', KEYS[2], head_key, next_field)\n if redis.call('HEXISTS', KEYS[2], next_field .. ':f') == 0 then\n redis.call('ZADD', KEYS[4], tonumber(next_field), next_field)\n end\n else\n redis.call('HDEL', KEYS[2], head_key, lane_field(lane, 'tail'))\n end\nend\nlocal idx = tonumber(ARGV[1])\nif idx < 1 or idx > tonumber(redis.call('GET', KEYS[1]) or '0') then\n return { 'missing', '0' }\nend\nlocal field = ARGV[1]\nlocal failures = tonumber(redis.call('HGET', KEYS[2], field .. ':a') or '0')\nlocal dispatches = tonumber(redis.call('HGET', KEYS[2], field .. ':d') or '0')\nif redis.call('HEXISTS', KEYS[2], field .. ':p') == 1 or dispatches ~= tonumber(ARGV[2]) then\n return { 'superseded', tostring(failures) }\nend\nif redis.call('HEXISTS', KEYS[2], field .. ':f') == 1 then\n return { 'dead_lettered', tostring(failures) }\nend\nif redis.call('HEXISTS', KEYS[2], field .. ':lfa') == 1 and\n tonumber(redis.call('HGET', KEYS[2], field .. ':lfa')) == tonumber(ARGV[2]) and\n redis.call('HEXISTS', KEYS[2], field .. ':ch') == 0 then\n return { 'failed', tostring(failures) }\nend\nif redis.call('HEXISTS', KEYS[2], field .. ':ch') == 0 then\n return { 'superseded', tostring(failures) }\nend\nfailures = redis.call('HINCRBY', KEYS[2], field .. ':a', 1)\nredis.call('HSET', KEYS[2],\n field .. ':e', ARGV[3],\n field .. ':lf', ARGV[5],\n field .. ':lfa', ARGV[2])\nredis.call('HDEL', KEYS[2], field .. ':ch', field .. ':ce')\nif failures >= tonumber(ARGV[4]) then\n redis.call('HSET', KEYS[2], field .. ':f', ARGV[5])\n redis.call('ZREM', KEYS[4], field)\n release_snapshot_pins()\n return { 'dead_lettered', tostring(failures) }\nend\nreturn { 'failed', tostring(failures) }\n`\n\n/**\n * KEYS: store, counter, meta, pending, ready.\n * ARGV: holder, nowMs, expiresAtMs, excludedCount, excluded indexes.\n */\nconst CLAIM_AVAILABLE_LUA = `\nif redis.call('ZCARD', KEYS[4]) == 0 then return { 'settled' } end\nlocal ready = redis.call('ZRANGE', KEYS[5], 0, -1)\nlocal excluded = {}\nfor i = 1, tonumber(ARGV[4]) do excluded[ARGV[4 + i]] = true end\nlocal claimed = {}\nlocal retry_at = nil\nfor _, field in ipairs(ready) do\n if not excluded[field] then\n local expires = tonumber(redis.call('HGET', KEYS[3], field .. ':ce') or '0')\n if expires > tonumber(ARGV[2]) then\n if not retry_at or expires < retry_at then retry_at = expires end\n else\n local entries = redis.call('XRANGE', KEYS[1], field .. '-0', field .. '-0')\n if #entries == 0 then return { 'missing', field } end\n local attempt = redis.call('HINCRBY', KEYS[3], field .. ':d', 1)\n redis.call('HSETNX', KEYS[3], field .. ':fc', ARGV[2])\n redis.call('HSET', KEYS[3],\n field .. ':lc', ARGV[2],\n field .. ':ch', ARGV[1],\n field .. ':ce', ARGV[3])\n claimed[#claimed + 1] = {\n tostring(attempt),\n redis.call('HGET', KEYS[3], field .. ':a') or '0',\n redis.call('HGET', KEYS[3], field .. ':e') or false,\n redis.call('HGET', KEYS[3], field .. ':fc') or false,\n ARGV[2],\n redis.call('HGET', KEYS[3], field .. ':lf') or false,\n redis.call('HGET', KEYS[3], field .. ':lfa') or false,\n ARGV[1],\n ARGV[3],\n entries[1]\n }\n end\n end\nend\nif #claimed > 0 then\n local result = { 'claimed', tostring(#claimed) }\n for _, claim in ipairs(claimed) do\n for _, value in ipairs(claim) do result[#result + 1] = value end\n end\n return result\nend\nif retry_at then return { 'busy', tostring(retry_at) } end\nreturn { 'settled' }\n`\n\n/**\n * KEYS: counter, meta.\n * ARGV: holder, nowMs, expiresAtMs, n, then per claim: index, attempt.\n * Returns [renewedCount, ...renewed, ...superseded].\n */\nconst RENEW_CLAIMS_LUA = `\nlocal total = tonumber(redis.call('GET', KEYS[1]) or '0')\nlocal renewed = {}\nlocal superseded = {}\nfor i = 1, tonumber(ARGV[4]) do\n local field = ARGV[4 + (i - 1) * 2 + 1]\n local attempt = tonumber(ARGV[4 + (i - 1) * 2 + 2])\n local idx = tonumber(field)\n if idx >= 1 and idx <= total then\n local dispatches = tonumber(redis.call('HGET', KEYS[2], field .. ':d') or '0')\n if dispatches > attempt then\n superseded[#superseded + 1] = field\n else\n local expires = tonumber(redis.call('HGET', KEYS[2], field .. ':ce') or '0')\n if tonumber(ARGV[3]) > tonumber(ARGV[2]) and\n redis.call('HEXISTS', KEYS[2], field .. ':p') == 0 and\n redis.call('HEXISTS', KEYS[2], field .. ':f') == 0 and\n redis.call('HGET', KEYS[2], field .. ':ch') == ARGV[1] and\n expires > tonumber(ARGV[2]) then\n redis.call('HSET', KEYS[2], field .. ':ce', ARGV[3])\n renewed[#renewed + 1] = field\n end\n end\n end\nend\nlocal result = { tostring(#renewed) }\nfor i = 1, #renewed do result[#result + 1] = renewed[i] end\nfor i = 1, #superseded do result[#result + 1] = superseded[i] end\nreturn result\n`\n\n/**\n * KEYS: store, counter, ids, meta, pending, ready, watched,\n * event snapshot pins, snapshot pin counts.\n * ARGV: sessionId, nowMs, parent index, attempt, n, then per child:\n * id, type, payloadJson, lane, settled.\n */\nconst COMPLETE_ATTEMPT_LUA = `\nlocal function release_snapshot_pins()\n local refs = redis.call('SMEMBERS', KEYS[8])\n for _, ref in ipairs(refs) do\n local count = redis.call('HINCRBY', KEYS[9], ref, -1)\n if count <= 0 then\n redis.call('HDEL', KEYS[9], ref)\n local checkpoint = cjson.decode(ref)\n redis.call('ZREMRANGEBYSCORE', checkpoint[1], checkpoint[2], checkpoint[2])\n end\n end\n redis.call('DEL', KEYS[8])\n if redis.call('HLEN', KEYS[9]) == 0 then redis.call('DEL', KEYS[9]) end\nend\nlocal function lane_field(lane, suffix)\n return '__lane:' .. tostring(string.len(lane)) .. ':' .. lane .. ':' .. suffix\nend\nlocal function settle(field)\n redis.call('ZREM', KEYS[5], field)\n redis.call('ZREM', KEYS[6], field)\n local lane = redis.call('HGET', KEYS[4], field .. ':l') or ''\n if lane == '' then return end\n local head_key = lane_field(lane, 'head')\n if redis.call('HGET', KEYS[4], head_key) ~= field then return end\n local next_field = redis.call('HGET', KEYS[4], field .. ':next')\n while next_field and redis.call('HEXISTS', KEYS[4], next_field .. ':p') == 1 do\n next_field = redis.call('HGET', KEYS[4], next_field .. ':next')\n end\n if next_field then\n redis.call('HSET', KEYS[4], head_key, next_field)\n if redis.call('HEXISTS', KEYS[4], next_field .. ':f') == 0 then\n redis.call('ZADD', KEYS[6], tonumber(next_field), next_field)\n end\n else\n redis.call('HDEL', KEYS[4], head_key, lane_field(lane, 'tail'))\n end\nend\nlocal function enqueue(field, idx, lane)\n redis.call('ZADD', KEYS[5], idx, field)\n if lane == '' then\n redis.call('ZADD', KEYS[6], idx, field)\n return\n end\n local tail_key = lane_field(lane, 'tail')\n local tail = redis.call('HGET', KEYS[4], tail_key)\n if tail then\n redis.call('HSET', KEYS[4], tail .. ':next', field)\n else\n redis.call('HSET', KEYS[4], lane_field(lane, 'head'), field)\n redis.call('ZADD', KEYS[6], idx, field)\n end\n redis.call('HSET', KEYS[4], tail_key, field)\nend\nlocal parent = ARGV[3]\nlocal attempt = tonumber(ARGV[4])\nlocal n = tonumber(ARGV[5])\nlocal total = tonumber(redis.call('GET', KEYS[2]) or '0')\nif tonumber(parent) < 1 or tonumber(parent) > total then return { 'missing' } end\nlocal dispatches = tonumber(redis.call('HGET', KEYS[4], parent .. ':d') or '0')\nlocal processed = redis.call('HGET', KEYS[4], parent .. ':p')\nlocal processed_attempt = tonumber(redis.call('HGET', KEYS[4], parent .. ':pa') or '0')\nif processed then\n if processed_attempt ~= attempt then return { 'superseded' } end\n local raw_recorded_ids = redis.call('HGET', KEYS[4], parent .. ':ri')\n if not raw_recorded_ids then return { 'partial' } end\n local recorded_ids = cjson.decode(raw_recorded_ids)\n if #recorded_ids ~= n then return { 'partial' } end\n local indexes = {}\n for i = 1, n do\n local id = ARGV[5 + (i - 1) * 5 + 1]\n if recorded_ids[i] ~= id then return { 'partial' } end\n local existing = redis.call('HGET', KEYS[3], id)\n if not existing then return { 'partial' } end\n local rec = cjson.decode(existing)\n if rec[1] ~= ARGV[1] then return { 'partial' } end\n local entries = redis.call('XRANGE', KEYS[1], tostring(rec[2]) .. '-0', tostring(rec[2]) .. '-0')\n if #entries == 0 then return { 'partial' } end\n local values = entries[1][2]\n local cause = nil\n for j = 1, #values, 2 do\n if values[j] == 'cause' then cause = values[j + 1] break end\n end\n if not cause or cause == '' then return { 'partial' } end\n local decoded = cjson.decode(cause)\n if tonumber(decoded['index']) ~= tonumber(parent) or\n tonumber(decoded['attempt']) ~= attempt then return { 'partial' } end\n indexes[#indexes + 1] = tostring(rec[2])\n end\n local result = { 'duplicate' }\n for _, idx in ipairs(indexes) do result[#result + 1] = idx end\n return result\nend\nif dispatches ~= attempt then return { 'superseded' } end\nif redis.call('HEXISTS', KEYS[4], parent .. ':ch') == 0 or\n redis.call('HEXISTS', KEYS[4], parent .. ':f') == 1 or\n tonumber(redis.call('HGET', KEYS[4], parent .. ':lfa') or '0') == attempt then\n return { 'superseded' }\nend\nfor i = 1, n do\n local id = ARGV[5 + (i - 1) * 5 + 1]\n if redis.call('HEXISTS', KEYS[3], id) == 1 then return { 'partial' } end\nend\nlocal returned_ids = {}\nfor i = 1, n do returned_ids[i] = ARGV[5 + (i - 1) * 5 + 1] end\nlocal returned_json = '[]'\nif n > 0 then returned_json = cjson.encode(returned_ids) end\nredis.call('HSET', KEYS[4],\n parent .. ':p', ARGV[2],\n parent .. ':pa', ARGV[4],\n parent .. ':ri', returned_json)\nredis.call('HDEL', KEYS[4], parent .. ':ch', parent .. ':ce')\nsettle(parent)\nrelease_snapshot_pins()\nlocal cause = cjson.encode({ index = tonumber(parent), attempt = attempt })\nfor i = 1, n do\n local id = ARGV[5 + (i - 1) * 5 + 1]\n local typ = ARGV[5 + (i - 1) * 5 + 2]\n local payload = ARGV[5 + (i - 1) * 5 + 3]\n local lane = ARGV[5 + (i - 1) * 5 + 4]\n local settled = ARGV[5 + (i - 1) * 5 + 5]\n local idx = total + i\n redis.call('XADD', KEYS[1], tostring(idx) .. '-0',\n 'id', id, 'type', typ, 'payload', payload, 'created_at', ARGV[2],\n 'cause', cause, 'lane', lane)\n redis.call('HSET', KEYS[3], id, cjson.encode({ ARGV[1], idx }))\n redis.call('HSET', KEYS[4], tostring(idx) .. ':l', lane)\n if settled == '1' then\n redis.call('HSET', KEYS[4], tostring(idx) .. ':p', ARGV[2])\n else\n enqueue(tostring(idx), idx, lane)\n end\nend\nredis.call('SET', KEYS[2], tostring(total + n))\nreturn { 'ok', tostring(total + 1),\n tostring(redis.call('EXISTS', KEYS[7])) }\n`\n\n/**\n * KEYS: head, history, event counter, event meta, pin counts.\n * ARGV: nowMs, event pin key prefix, write count, then per write:\n * index, snapshot JSON, pin count, pin event indexes.\n */\nconst PUT_SNAPSHOTS_LUA = `\nlocal total = tonumber(redis.call('GET', KEYS[3]) or '0')\nlocal writes = {}\nlocal offset = 4\nfor i = 1, tonumber(ARGV[3]) do\n local index = tonumber(ARGV[offset])\n local snapshot = ARGV[offset + 1]\n local pin_count = tonumber(ARGV[offset + 2])\n local ref = cjson.encode({ KEYS[2], tostring(index) })\n for p = 1, pin_count do\n local event_index = tonumber(ARGV[offset + 2 + p])\n if event_index >= 1 and event_index <= total and\n redis.call('HEXISTS', KEYS[4], tostring(event_index) .. ':p') == 0 and\n redis.call('HEXISTS', KEYS[4], tostring(event_index) .. ':f') == 0 then\n local pin_key = ARGV[2] .. tostring(event_index)\n if redis.call('SADD', pin_key, ref) == 1 then\n redis.call('HINCRBY', KEYS[5], ref, 1)\n end\n end\n end\n writes[#writes + 1] = { index = index, snapshot = snapshot, ref = ref }\n offset = offset + 3 + pin_count\nend\n\nlocal head = redis.call('GET', KEYS[1])\nlocal head_index = nil\nif head then head_index = tonumber(cjson.decode(head)['index']) end\nlocal latest = writes[#writes]\nlocal writes_head = not head_index or latest.index >= head_index\nlocal advances = not head_index or latest.index > head_index\nlocal final_head_index = advances and latest.index or head_index\n\nif advances and head and tonumber(redis.call('HGET', KEYS[5], cjson.encode({ KEYS[2], tostring(head_index) })) or '0') > 0 then\n redis.call('ZREMRANGEBYSCORE', KEYS[2], head_index, head_index)\n redis.call('ZADD', KEYS[2], head_index, head)\nend\n\nfor _, write in ipairs(writes) do\n if write.index < final_head_index then\n redis.call('ZREMRANGEBYSCORE', KEYS[2], write.index, write.index)\n if tonumber(redis.call('HGET', KEYS[5], write.ref) or '0') > 0 then\n redis.call('ZADD', KEYS[2], write.index, write.snapshot)\n end\n end\nend\n\nif writes_head then\n redis.call('SET', KEYS[1], latest.snapshot)\n redis.call('ZREMRANGEBYSCORE', KEYS[2], latest.index, latest.index)\nend\nreturn 1\n`\n\n/**\n * KEYS: presence hash. ARGV: atMs, ttlMs, n, then per entry:\n * hashField, valueJson ('' deletes). Field-wise LWW by the sender's\n * `at`: a stored entry with a strictly newer stamp survives; ties go\n * to the incoming write (memory-tier parity). Returns the applied\n * 1-based entry positions. The whole-key PEXPIRE is the coarse bound,\n * sound because core sends one uniform ttlMs; the per-entry `e` stamp\n * is the fine bound (the injected storage clock at the applied set\n * plus ttlMs — never the sender stamp), pruned on read against the\n * same clock.\n */\nconst PRESENCE_SET_LUA = `\nlocal at = tonumber(ARGV[1])\nlocal applied = {}\nfor i = 1, tonumber(ARGV[3]) do\n local field = ARGV[3 + (i - 1) * 2 + 1]\n local value = ARGV[3 + (i - 1) * 2 + 2]\n local existing = redis.call('HGET', KEYS[1], field)\n if not existing or tonumber(cjson.decode(existing)['a']) <= at then\n if value == '' then\n redis.call('HDEL', KEYS[1], field)\n else\n redis.call('HSET', KEYS[1], field, value)\n end\n applied[#applied + 1] = tostring(i)\n end\nend\nif #applied > 0 then redis.call('PEXPIRE', KEYS[1], ARGV[2]) end\nreturn applied\n`\n\n/**\n * KEYS: presence hash. ARGV: nowMs (the injected clock, so tests can\n * time-travel expiry against a real server). Returns the live\n * [field, value, ...] pairs; entries at or past their fine-bound\n * expiry are HDELed in the same atomic step, so a stale reader can\n * never sweep a concurrently refreshed field.\n */\nconst PRESENCE_READ_LUA = `\nlocal now = tonumber(ARGV[1])\nlocal rows = redis.call('HGETALL', KEYS[1])\nlocal live = {}\nfor i = 1, #rows, 2 do\n if tonumber(cjson.decode(rows[i + 1])['e']) <= now then\n redis.call('HDEL', KEYS[1], rows[i])\n else\n live[#live + 1] = rows[i]\n live[#live + 1] = rows[i + 1]\n end\nend\nreturn live\n`\n\n/**\n * KEYS: head, history, event counter. ARGV: snapshot frontier or ''.\n * Returns one atomic multiversion-cache-plus-log-frontier read.\n */\nconst READ_STATE_LUA = `\nlocal head = redis.call('GET', KEYS[1])\nlocal cap = ARGV[1]\nlocal snapshot = nil\nif head and (cap == '' or tonumber(cjson.decode(head)['index']) <= tonumber(cap)) then\n snapshot = head\nend\nlocal upper = cap == '' and '+inf' or cap\nlocal historical = redis.call('ZREVRANGEBYSCORE', KEYS[2], upper, '-inf', 'LIMIT', 0, 1)\nif #historical > 0 then\n if not snapshot or tonumber(cjson.decode(historical[1])['index']) > tonumber(cjson.decode(snapshot)['index']) then\n snapshot = historical[1]\n end\nend\nlocal frontier = redis.call('GET', KEYS[3]) or '0'\nreturn { head or '', snapshot or '', frontier }\n`\n\n// ── wire helpers ─────────────────────────────────────────────────────\n\ntype StreamEntry = [id: string, fields: string[]]\n\nconst fieldMap = (fields: string[]): Record<string, string> => {\n const map: Record<string, string> = {}\n for (let i = 0; i + 1 < fields.length; i += 2) {\n map[fields[i]!] = fields[i + 1]!\n }\n return map\n}\n\nconst STORED_EVENT_META_SUFFIXES = [\n 'p',\n 'pa',\n 'ri',\n 'ch',\n 'ce',\n 'f',\n 'fc',\n 'lc',\n 'lf',\n 'lfa',\n 'd',\n 'a',\n 'e',\n] as const\n\nconst entryIndex = (entryId: string): number =>\n Number(entryId.slice(0, entryId.indexOf('-')))\n\nconst toEvent = (sessionId: string, entry: StreamEntry): Event => {\n const fields = fieldMap(entry[1])\n return {\n id: fields['id']!,\n type: fields['type']!,\n payload: JSON.parse(fields['payload']!),\n index: entryIndex(entry[0]),\n sessionId,\n createdAt: new Date(Number(fields['created_at'])),\n }\n}\n\nconst json = (value: unknown): string => JSON.stringify(value) ?? 'null'\n\nconst toCause = (raw: string | undefined): EventCause | null => {\n if (raw === '' || raw === undefined) return null\n const value = JSON.parse(raw) as {\n index?: unknown\n attempt?: unknown\n batchSize?: unknown\n }\n if (\n !Number.isInteger(value.index) ||\n Number(value.index) < 1 ||\n !Number.isInteger(value.attempt) ||\n Number(value.attempt) < 1\n ) {\n throw new TypeError('stored event has an invalid cause')\n }\n if (\n value.batchSize !== undefined &&\n (!Number.isInteger(value.batchSize) || Number(value.batchSize) < 1)\n ) {\n throw new TypeError('stored event has an invalid cause')\n }\n return {\n index: Number(value.index),\n attempt: Number(value.attempt),\n ...(value.batchSize === undefined\n ? {}\n : { batchSize: Number(value.batchSize) }),\n }\n}\n\nconst toClaimAvailableResult = (\n sessionId: string,\n reply: unknown[],\n): StoreClaimAvailableResult => {\n const outcome = String(reply[0])\n if (outcome === 'claimed') {\n const count = Number(reply[1])\n const events: StoredEvent[] = []\n for (let i = 0; i < count; i++) {\n const offset = 2 + i * 10\n const entry = reply[offset + 9] as StreamEntry\n const event = toEvent(sessionId, entry)\n const fields = fieldMap(entry[1])\n events.push(\n Object.assign(event, {\n cause: toCause(fields['cause']),\n lane: fields['lane'] === '' ? null : fields['lane']!,\n processedAt: null,\n processedByAttempt: null,\n returnedEventIds: null,\n firstClaimedAt:\n reply[offset + 3] === null\n ? null\n : new Date(Number(reply[offset + 3])),\n lastClaimedAt: new Date(Number(reply[offset + 4])),\n attemptCount: Number(reply[offset]),\n claimHolder: String(reply[offset + 7]),\n claimExpiresAt: new Date(Number(reply[offset + 8])),\n failureCount: Number(reply[offset + 1]),\n lastFailedAt:\n reply[offset + 5] === null\n ? null\n : new Date(Number(reply[offset + 5])),\n lastFailedAttempt:\n reply[offset + 6] === null ? null : Number(reply[offset + 6]),\n lastError:\n reply[offset + 2] === null ? null : String(reply[offset + 2]),\n failedAt: null,\n }),\n )\n }\n return {\n outcome: 'claimed',\n events,\n }\n }\n if (outcome === 'busy') {\n return { outcome, retryAt: new Date(Number(reply[1])) }\n }\n if (outcome === 'settled') return { outcome }\n throw new TypeError(\n `claim referenced a missing event in session '${sessionId}'`,\n )\n}\n\nconst wrap = async <T>(fn: () => Promise<T>): Promise<T> => {\n try {\n return await fn()\n } catch (err) {\n if (err instanceof A2Error || err instanceof TypeError) throw err\n throw new A2Error('STORE_UNAVAILABLE', 'redis store operation failed', {\n cause: err,\n })\n }\n}\n\nexport function createRedisStoreCore(\n options: RedisStoreCoreOptions,\n): RedisStoreCore {\n const { call, clock, notify } = options\n const generateId = options.ids\n const prefix = options.keyPrefix\n\n const storeKey = (s: string): string => `${prefix}:${s}:log`\n const countKey = (s: string): string => `${prefix}:${s}:count`\n const metaKey = (s: string): string => `${prefix}:${s}:meta`\n const pendingKey = (s: string): string => `${prefix}:${s}:pending`\n const readyKey = (s: string): string => `${prefix}:${s}:ready`\n const snapKey = (s: string, r: string): string => `${prefix}:${s}:snap:${r}`\n const snapHistoryKey = (s: string, r: string): string =>\n `${prefix}:${s}:snap-history:${r}`\n const snapshotEventPinsKey = (s: string, index: number): string =>\n `${prefix}:${s}:snapshot-pins:${index}`\n const snapshotEventPinsPrefix = (s: string): string =>\n `${prefix}:${s}:snapshot-pins:`\n const snapshotPinCountsKey = (s: string): string =>\n `${prefix}:${s}:snapshot-pin-counts`\n const watchedKey = (s: string): string => `${prefix}:${s}:watched`\n const presenceKey = (s: string): string => `${prefix}:${s}:presence`\n const idsKey = `${prefix}:ids`\n\n const evalScript = (\n script: string,\n keys: string[],\n args: Array<string | number>,\n ): Promise<unknown> => call('EVAL', script, keys.length, ...keys, ...args)\n\n const readFrontier = async (sessionId: string): Promise<number> => {\n const frontier = Number((await call('GET', countKey(sessionId))) ?? 0)\n if (!Number.isSafeInteger(frontier) || frontier < 0) {\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n `redis event count for session '${sessionId}' is invalid`,\n )\n }\n return frontier\n }\n\n const readRangePage = async (\n sessionId: string,\n afterIndex: number,\n throughIndex: number,\n limit: number,\n ): Promise<StoredEvent[]> => {\n if (throughIndex <= afterIndex) return []\n const entries = (await call(\n 'XRANGE',\n storeKey(sessionId),\n `${afterIndex + 1}-0`,\n `${throughIndex}-0`,\n 'COUNT',\n limit,\n )) as StreamEntry[]\n if (entries.length === 0) return []\n\n const metaFields = entries.flatMap((entry) => {\n const index = entryIndex(entry[0])\n return STORED_EVENT_META_SUFFIXES.map((suffix) => `${index}:${suffix}`)\n })\n const metaValues =\n ((await call('HMGET', metaKey(sessionId), ...metaFields)) as Array<\n string | null\n > | null) ?? []\n const meta: Record<string, string> = {}\n for (let i = 0; i < metaFields.length; i += 1) {\n const value = metaValues[i]\n if (value !== null && value !== undefined) {\n meta[metaFields[i]!] = value\n }\n }\n return entries.map((entry) => {\n const event = toEvent(sessionId, entry)\n const idx = event.index\n const fields = fieldMap(entry[1])\n const processedAt = meta[`${idx}:p`]\n const processedByAttempt = meta[`${idx}:pa`]\n const returnedEventIds = meta[`${idx}:ri`]\n const claimHolder = meta[`${idx}:ch`]\n const claimExpiresAt = meta[`${idx}:ce`]\n const failedAt = meta[`${idx}:f`]\n const firstClaimedAt = meta[`${idx}:fc`]\n const lastClaimedAt = meta[`${idx}:lc`]\n const lastFailedAt = meta[`${idx}:lf`]\n const lastFailedAttempt = meta[`${idx}:lfa`]\n return Object.assign(event, {\n cause: toCause(fields['cause']),\n lane: fields['lane'] === '' ? null : fields['lane']!,\n processedAt:\n processedAt === undefined ? null : new Date(Number(processedAt)),\n processedByAttempt:\n processedByAttempt === undefined ? null : Number(processedByAttempt),\n returnedEventIds:\n returnedEventIds === undefined\n ? null\n : decodeReturnedEventIds(returnedEventIds),\n firstClaimedAt:\n firstClaimedAt === undefined\n ? null\n : new Date(Number(firstClaimedAt)),\n lastClaimedAt:\n lastClaimedAt === undefined ? null : new Date(Number(lastClaimedAt)),\n attemptCount: Number(meta[`${idx}:d`] ?? 0),\n claimHolder: claimHolder ?? null,\n claimExpiresAt:\n claimExpiresAt === undefined\n ? null\n : new Date(Number(claimExpiresAt)),\n failureCount: Number(meta[`${idx}:a`] ?? 0),\n lastFailedAt:\n lastFailedAt === undefined ? null : new Date(Number(lastFailedAt)),\n lastFailedAttempt:\n lastFailedAttempt === undefined ? null : Number(lastFailedAttempt),\n lastError: meta[`${idx}:e`] ?? null,\n failedAt: failedAt === undefined ? null : new Date(Number(failedAt)),\n })\n })\n }\n\n const readEventRange = async (\n sessionId: string,\n afterIndex: number,\n throughIndex: number,\n ): Promise<Event[]> => {\n if (throughIndex <= afterIndex) return []\n const events: Event[] = []\n let nextIndex = afterIndex + 1\n while (nextIndex <= throughIndex) {\n // oxlint-disable-next-line no-await-in-loop -- each page advances the dense frozen range\n const entries = (await call(\n 'XRANGE',\n storeKey(sessionId),\n `${nextIndex}-0`,\n `${throughIndex}-0`,\n 'COUNT',\n READ_PAGE_SIZE,\n )) as StreamEntry[]\n if (entries.length === 0) {\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n `redis event range for session '${sessionId}' ended before index ${throughIndex}`,\n )\n }\n for (let position = 0; position < entries.length; position += 1) {\n const index = entryIndex(entries[position]![0])\n if (index !== nextIndex + position || index > throughIndex) {\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n `redis event range for session '${sessionId}' is not contiguous`,\n )\n }\n }\n events.push(...entries.map((entry) => toEvent(sessionId, entry)))\n nextIndex = events.at(-1)!.index + 1\n }\n return events\n }\n\n /** Bounded XRANGE pages restore the full-read contract on capped providers. */\n const readRange = async (\n sessionId: string,\n afterIndex: number,\n throughIndex?: number,\n ): Promise<StoredEvent[]> => {\n if (throughIndex !== undefined && throughIndex <= afterIndex) return []\n const currentFrontier = await readFrontier(sessionId)\n const frontier = Math.min(throughIndex ?? currentFrontier, currentFrontier)\n if (frontier <= afterIndex) return []\n\n const rows: StoredEvent[] = []\n let nextIndex = afterIndex + 1\n while (nextIndex <= frontier) {\n // oxlint-disable-next-line no-await-in-loop -- each page advances the dense frozen range\n const page = await readRangePage(\n sessionId,\n nextIndex - 1,\n frontier,\n READ_PAGE_SIZE,\n )\n if (page.length === 0) {\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n `redis event range for session '${sessionId}' ended before index ${frontier}`,\n )\n }\n for (let position = 0; position < page.length; position += 1) {\n if (page[position]!.index !== nextIndex + position) {\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n `redis event range for session '${sessionId}' is not contiguous`,\n )\n }\n }\n const lastIndex = page.at(-1)!.index\n if (lastIndex < nextIndex || lastIndex > frontier) {\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n `redis event range for session '${sessionId}' did not advance`,\n )\n }\n rows.push(...page)\n nextIndex = lastIndex + 1\n }\n return rows\n }\n\n return {\n async append(sessionId, events) {\n return wrap(async () => {\n const supplied = events.filter((e) => e.id !== undefined)\n const suppliedIds = supplied.map((e) => e.id as string)\n if (new Set(suppliedIds).size !== suppliedIds.length) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'batch contains the same event id more than once',\n )\n }\n const now = clock.now().getTime()\n const withIds = events.map((e) => ({\n id: e.id ?? generateId(),\n type: e.type,\n payload: e.payload,\n cause: e.cause ? { ...e.cause } : null,\n lane: e.lane ?? null,\n settled: e.settled === true,\n }))\n const args: Array<string | number> = [\n sessionId,\n now,\n withIds.length,\n ...withIds.flatMap((e) => [\n e.id,\n e.type,\n json(e.payload),\n e.cause ? json(e.cause) : '',\n e.lane ?? '',\n e.settled ? '1' : '0',\n ]),\n ]\n const reply = (await evalScript(\n APPEND_LUA,\n [\n storeKey(sessionId),\n countKey(sessionId),\n idsKey,\n metaKey(sessionId),\n pendingKey(sessionId),\n readyKey(sessionId),\n watchedKey(sessionId),\n ],\n args,\n )) as string[]\n\n const [verdict] = reply\n if (verdict === 'empty') {\n return { events: [], hasPending: reply[1] === '1' }\n }\n if (verdict === 'foreign') {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n `event id '${reply[1]}' already exists in another session`,\n )\n }\n if (verdict === 'no_parent') {\n throw new TypeError(\n `no event at index ${reply[1]} in session '${sessionId}'`,\n )\n }\n if (verdict === 'superseded') {\n throw new A2Error(\n 'SUPERSEDED_ATTEMPT',\n `a newer attempt owns event ${reply[1]} in session '${sessionId}'`,\n )\n }\n if (verdict === 'partial') {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n `batch mixes ${reply[1]} already-appended and ${events.length - Number(reply[1])} fresh events`,\n )\n }\n if (verdict === 'dup') {\n // A retry of a committed batch whose ack was lost — return\n // the original rows, ordered by index like every adapter.\n const hasPending = reply[1] === '1'\n const wanted = new Set(reply.slice(2).map(Number))\n const min = Math.min(...wanted)\n const rows = await readRange(sessionId, min - 1)\n const existing = rows.filter((row) => wanted.has(row.index))\n return {\n events: idempotentReplay(events, existing),\n hasPending,\n }\n }\n const base = Number(reply[1]) - 1\n if (reply[3] === '1') notify?.(sessionId, base + withIds.length)\n return {\n events: withIds.map((e, i) => ({\n id: e.id,\n type: e.type,\n payload: structuredClone(e.payload),\n index: base + 1 + i,\n sessionId,\n createdAt: new Date(now),\n cause: e.cause,\n lane: e.lane,\n processedAt: e.settled ? new Date(now) : null,\n processedByAttempt: null,\n returnedEventIds: null,\n firstClaimedAt: null,\n lastClaimedAt: null,\n attemptCount: 0,\n claimHolder: null,\n claimExpiresAt: null,\n failureCount: 0,\n lastFailedAt: null,\n lastFailedAttempt: null,\n lastError: null,\n failedAt: null,\n })),\n hasPending: reply[2] === '1',\n }\n })\n },\n\n async read(sessionId, opts) {\n return wrap(async () => {\n const rows = await readRange(\n sessionId,\n opts?.afterIndex ?? 0,\n opts?.throughIndex,\n )\n return rows\n })\n },\n\n async claimAvailable({\n sessionId,\n holder,\n ttlMs,\n expiresAtMs,\n excludeIndexes,\n }) {\n return wrap(async () => {\n const now = clock.now().getTime()\n const expiresAt = expiresAtMs ?? now + ttlMs\n const excluded = excludeIndexes ?? []\n const reply = (await evalScript(\n CLAIM_AVAILABLE_LUA,\n [\n storeKey(sessionId),\n countKey(sessionId),\n metaKey(sessionId),\n pendingKey(sessionId),\n readyKey(sessionId),\n ],\n [holder, now, expiresAt, excluded.length, ...excluded],\n )) as unknown[]\n return toClaimAvailableResult(sessionId, reply)\n })\n },\n\n async renewClaims({ sessionId, holder, claims, ttlMs, expiresAtMs }) {\n return wrap(async () => {\n const now = clock.now().getTime()\n const expiresAt = expiresAtMs ?? now + ttlMs\n const reply = (await evalScript(\n RENEW_CLAIMS_LUA,\n [countKey(sessionId), metaKey(sessionId)],\n [\n holder,\n now,\n expiresAt,\n claims.length,\n ...claims.flatMap((claim) => [claim.index, claim.attempt]),\n ],\n )) as string[]\n const renewedCount = Number(reply[0])\n const values = reply.slice(1).map(Number)\n return {\n renewed: values.slice(0, renewedCount).toSorted((a, b) => a - b),\n superseded: values.slice(renewedCount).toSorted((a, b) => a - b),\n }\n })\n },\n\n async completeAttempt({ sessionId, index, attempt, events }) {\n return wrap(async () => {\n const ids = events.map((event) => event.id)\n if (new Set(ids).size !== ids.length) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'batch contains the same event id more than once',\n )\n }\n const now = clock.now().getTime()\n const reply = (await evalScript(\n COMPLETE_ATTEMPT_LUA,\n [\n storeKey(sessionId),\n countKey(sessionId),\n idsKey,\n metaKey(sessionId),\n pendingKey(sessionId),\n readyKey(sessionId),\n watchedKey(sessionId),\n snapshotEventPinsKey(sessionId, index),\n snapshotPinCountsKey(sessionId),\n ],\n [\n sessionId,\n now,\n index,\n attempt,\n events.length,\n ...events.flatMap((event) => [\n event.id,\n event.type,\n json(event.payload),\n event.lane ?? '',\n event.settled === true ? '1' : '0',\n ]),\n ],\n )) as string[]\n const outcome = reply[0]\n if (outcome === 'missing') {\n throw new TypeError(\n `no event at index ${index} in session '${sessionId}'`,\n )\n }\n if (outcome === 'partial') {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'returned event ids do not match the completed attempt',\n )\n }\n if (outcome === 'superseded') return { outcome }\n\n const cause = { index, attempt }\n if (outcome === 'ok') {\n const base = Number(reply[1]) - 1\n if (events.length > 0 && reply[2] === '1') {\n notify?.(sessionId, base + events.length)\n }\n return {\n outcome: 'completed',\n events: events.map((event, i) => ({\n id: event.id,\n type: event.type,\n payload: structuredClone(event.payload),\n index: base + 1 + i,\n sessionId,\n createdAt: new Date(now),\n cause,\n lane: event.lane ?? null,\n processedAt: event.settled === true ? new Date(now) : null,\n processedByAttempt: null,\n returnedEventIds: null,\n firstClaimedAt: null,\n lastClaimedAt: null,\n attemptCount: 0,\n claimHolder: null,\n claimExpiresAt: null,\n failureCount: 0,\n lastFailedAt: null,\n lastFailedAttempt: null,\n lastError: null,\n failedAt: null,\n })),\n } satisfies CompleteAttemptResult\n }\n if (outcome === 'duplicate') {\n if (events.length === 0) {\n return { outcome: 'completed', events: [] }\n }\n const wanted = new Set(reply.slice(1).map(Number))\n const min = Math.min(...wanted)\n const rows = await readRange(sessionId, min - 1)\n const byIndex = new Map(rows.map((row) => [row.index, row]))\n const existing = reply\n .slice(1)\n .map((value) => byIndex.get(Number(value))!)\n return {\n outcome: 'completed',\n events: idempotentReplay(events, existing),\n }\n }\n throw new TypeError(`unexpected completion outcome '${outcome}'`)\n })\n },\n\n async failAttempt({ sessionId, index, attempt, error, maxFailures }) {\n return wrap(async () => {\n const reply = (await evalScript(\n FAIL_ATTEMPT_LUA,\n [\n countKey(sessionId),\n metaKey(sessionId),\n pendingKey(sessionId),\n readyKey(sessionId),\n snapshotEventPinsKey(sessionId, index),\n snapshotPinCountsKey(sessionId),\n ],\n [index, attempt, error, maxFailures, clock.now().getTime()],\n )) as string[]\n const outcome = reply[0]\n if (outcome === 'missing') {\n throw new TypeError(\n `no event at index ${index} in session '${sessionId}'`,\n )\n }\n return {\n outcome: outcome as 'failed' | 'dead_lettered' | 'superseded',\n failureCount: Number(reply[1]),\n }\n })\n },\n\n async readState(sessionId, reducerName, stateOptions) {\n return wrap(async () => {\n const reply = (await evalScript(\n READ_STATE_LUA,\n [\n snapKey(sessionId, reducerName),\n snapHistoryKey(sessionId, reducerName),\n countKey(sessionId),\n ],\n [\n stateOptions?.snapshotThroughIndex ??\n stateOptions?.throughIndex ??\n '',\n ],\n )) as [string, string, string]\n const rawHead = reply[0]\n const raw = reply[1]\n const head =\n rawHead === ''\n ? null\n : (JSON.parse(rawHead) as { index: number; state: unknown })\n const parsed =\n raw === ''\n ? null\n : (JSON.parse(raw) as { index: number; state: unknown })\n const currentFrontier = Number(reply[2])\n if (\n !Number.isSafeInteger(currentFrontier) ||\n currentFrontier < 0 ||\n (head !== null &&\n (!Number.isSafeInteger(head.index) || head.index < 0)) ||\n (parsed !== null &&\n (!Number.isSafeInteger(parsed.index) || parsed.index < 0))\n ) {\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n `redis state frontier for session '${sessionId}' is invalid`,\n )\n }\n const frontier = Math.min(\n currentFrontier,\n stateOptions?.throughIndex ?? currentFrontier,\n )\n const snapshot = parsed\n const events = await readEventRange(\n sessionId,\n snapshot?.index ?? 0,\n frontier,\n )\n return {\n headIndex: head?.index ?? null,\n snapshot: snapshot\n ? { index: snapshot.index, state: snapshot.state }\n : null,\n events,\n }\n })\n },\n\n async putSnapshots(sessionId, reducerName, writes) {\n const byIndex = new Map<\n number,\n { index: number; state: unknown; pinEventIndexes: Set<number> }\n >()\n for (const write of writes) {\n const existing = byIndex.get(write.index)\n if (existing) {\n existing.state = write.state\n for (const eventIndex of write.pinEventIndexes ?? []) {\n existing.pinEventIndexes.add(eventIndex)\n }\n } else {\n byIndex.set(write.index, {\n index: write.index,\n state: write.state,\n pinEventIndexes: new Set(write.pinEventIndexes ?? []),\n })\n }\n }\n const normalized = [...byIndex.values()].toSorted(\n (a, b) => a.index - b.index,\n )\n if (normalized.length === 0) return\n await wrap(async () => {\n const args: Array<string | number> = [\n clock.now().getTime(),\n snapshotEventPinsPrefix(sessionId),\n normalized.length,\n ]\n for (const write of normalized) {\n args.push(\n write.index,\n json({\n index: write.index,\n state: write.state,\n updatedAt: clock.now().getTime(),\n }),\n write.pinEventIndexes.size,\n ...write.pinEventIndexes,\n )\n }\n await evalScript(\n PUT_SNAPSHOTS_LUA,\n [\n snapKey(sessionId, reducerName),\n snapHistoryKey(sessionId, reducerName),\n countKey(sessionId),\n metaKey(sessionId),\n snapshotPinCountsKey(sessionId),\n ],\n args,\n )\n })\n },\n\n presence: {\n async set(ns, participant, values, meta) {\n return wrap(async () => {\n const entries = Object.entries(values)\n if (entries.length === 0) return null\n const atMs = meta.at.getTime()\n // Expiry anchors on the injected storage clock — the sender's\n // `a` stamp orders writes, the `e` stamp bounds their\n // lifetime, and the two clocks never mix.\n const expiresAtMs = clock.now().getTime() + meta.ttlMs\n // The hash field is JSON `[participant, field]`: both are\n // caller strings, so no separator character is\n // collision-safe — the JSON array is.\n const applied = (await evalScript(\n PRESENCE_SET_LUA,\n [presenceKey(ns)],\n [\n atMs,\n meta.ttlMs,\n entries.length,\n ...entries.flatMap(([field, value]) => [\n JSON.stringify([participant, field]),\n value === null\n ? ''\n : json({\n v: value,\n s: meta.seen,\n a: atMs,\n e: expiresAtMs,\n }),\n ]),\n ],\n )) as string[]\n if (applied.length === 0) return null\n // Field-keyed and caller-named — null-prototype, like every\n // presence map (see `nullProtoRecord`).\n const appliedValues: Record<string, unknown> = nullProtoRecord()\n for (const position of applied) {\n const [field, value] = entries[Number(position) - 1]!\n appliedValues[field] = structuredClone(value)\n }\n return {\n participant,\n values: appliedValues,\n seen: meta.seen,\n at: new Date(atMs),\n }\n })\n },\n\n async read(ns) {\n return wrap(async () => {\n const reply = (await evalScript(\n PRESENCE_READ_LUA,\n [presenceKey(ns)],\n [clock.now().getTime()],\n )) as string[]\n const rows: PresenceRow[] = []\n for (let i = 0; i + 1 < reply.length; i += 2) {\n const [participant, field] = JSON.parse(reply[i]!) as [\n string,\n string,\n ]\n const entry = JSON.parse(reply[i + 1]!) as {\n v: unknown\n s: number\n a: number\n e: number\n }\n rows.push({\n participant,\n field,\n value: entry.v,\n seen: entry.s,\n at: new Date(entry.a),\n expiresAt: new Date(entry.e),\n })\n }\n return rows\n })\n },\n },\n\n async readRange(sessionId, afterIndex, throughIndex) {\n return wrap(() => readRange(sessionId, afterIndex, throughIndex))\n },\n\n async readEvents(sessionId, afterIndex) {\n return wrap(async () => {\n const entries = (await call(\n 'XRANGE',\n storeKey(sessionId),\n `${afterIndex + 1}-0`,\n '+',\n )) as StreamEntry[]\n return entries.map((entry) => toEvent(sessionId, entry))\n })\n },\n\n async markWatched(sessionId, ttlMs) {\n await wrap(() => call('SET', watchedKey(sessionId), '1', 'PX', ttlMs))\n },\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { r as Clock, s as IdSource, t as A2Store } from "./store-RJO35BMj.js";
1
+ import { r as Clock, s as IdSource, t as A2Store } from "./store-DGHeBtIQ.js";
2
2
  //#region src/store-redis-http.d.ts
3
3
  type RedisHttpStoreOptions = {
4
4
  /** REST endpoint, e.g. `process.env.UPSTASH_REDIS_REST_URL`. */
@@ -1,7 +1,7 @@
1
- import { t as A2Error } from "./errors-BQuJpe82.js";
1
+ import { t as A2Error } from "./errors-DCk6ch5n.js";
2
2
  import { n as pollingStream } from "./store-polling-6DW7F1DT.js";
3
3
  import { n as SYSTEM_CLOCK, t as RANDOM_IDS } from "./store-N8PXxDAS.js";
4
- import { t as createRedisStoreCore } from "./store-redis-core-DT01r4GZ.js";
4
+ import { t as createRedisStoreCore } from "./store-redis-core-z-ykbyMg.js";
5
5
  //#region src/store-redis-http.ts
6
6
  /**
7
7
  * experimental-a2/store-redis-http — the same Redis Streams store over a
@@ -1,4 +1,4 @@
1
- import { r as Clock, s as IdSource, t as A2Store } from "./store-RJO35BMj.js";
1
+ import { r as Clock, s as IdSource, t as A2Store } from "./store-DGHeBtIQ.js";
2
2
  //#region src/store-redis.d.ts
3
3
  /**
4
4
  * The minimal client this backend needs — `ioredis` matches it
@@ -1,9 +1,9 @@
1
- import { t as A2Error } from "./errors-BQuJpe82.js";
1
+ import { t as A2Error } from "./errors-DCk6ch5n.js";
2
2
  import { r as NOTIFY_TIMINGS } from "./internal-DRXJ56EI.js";
3
3
  import { t as defaultSleep } from "./store-polling-6DW7F1DT.js";
4
4
  import { t as retryableLazy } from "./retryable-lazy-DZWmHpii.js";
5
5
  import { n as SYSTEM_CLOCK, t as RANDOM_IDS } from "./store-N8PXxDAS.js";
6
- import { t as createRedisStoreCore } from "./store-redis-core-DT01r4GZ.js";
6
+ import { t as createRedisStoreCore } from "./store-redis-core-z-ykbyMg.js";
7
7
  //#region src/store-redis.ts
8
8
  function redis(options = {}) {
9
9
  const clock = options.clock ?? SYSTEM_CLOCK;
@@ -1,4 +1,4 @@
1
- import { r as Clock, s as IdSource, t as A2Store } from "./store-RJO35BMj.js";
1
+ import { r as Clock, s as IdSource, t as A2Store } from "./store-DGHeBtIQ.js";
2
2
  //#region src/store-sqlite.d.ts
3
3
  type SqliteStoreOptions = {
4
4
  /** Database file path. Defaults to `.a2/dev.db`; `:memory:` works. */
@@ -1,6 +1,6 @@
1
- import { t as A2Error } from "./errors-BQuJpe82.js";
1
+ import { t as A2Error } from "./errors-DCk6ch5n.js";
2
2
  import { n as pollingStream } from "./store-polling-6DW7F1DT.js";
3
- import { t as idempotentReplay } from "./idempotent-replay-DuqEkYA7.js";
3
+ import { t as idempotentReplay } from "./idempotent-replay-DVOlyYbx.js";
4
4
  import { n as SYSTEM_CLOCK, t as RANDOM_IDS } from "./store-N8PXxDAS.js";
5
5
  import { t as decodeReturnedEventIds } from "./store-codec-DTG0Ftek.js";
6
6
  import { mkdirSync } from "node:fs";
@@ -1,4 +1,4 @@
1
- import { t as A2Error } from "./errors-BQuJpe82.js";
1
+ import { t as A2Error } from "./errors-DCk6ch5n.js";
2
2
  import { h as nullProtoRecord } from "./internal-DRXJ56EI.js";
3
3
  //#region src/wire.ts
4
4
  /**
@@ -282,4 +282,4 @@ const sessionTag = (frame) => {
282
282
  //#endregion
283
283
  export { socketErrorAckFor as _, errorToWire as a, socketUnsubscribedFor as b, isWireEvent as c, parseSocketFrame as d, presencePatchFromWire as f, socketAckFor as g, presenceSnapshotToWire as h, errorStatus as i, isWirePresencePatch as l, presenceSnapshotFromWire as m, asA2Error as n, eventFromWire as o, presencePatchToWire as p, errorFromWire as r, eventToWire as s, SOCKET_PING_FRAME as t, isWirePresenceSnapshot as u, socketFrameFor as v, socketSubscribedFor as y };
284
284
 
285
- //# sourceMappingURL=wire-B6te_wns.js.map
285
+ //# sourceMappingURL=wire--yji6mO3.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"wire-B6te_wns.js","names":[],"sources":["../src/wire.ts"],"sourcesContent":["/**\n * The wire format shared by server.fetch and experimental-a2/client\n * (browser side): events as JSON with ISO timestamps, and the A2Error\n * envelope. Internal module — both entry points re-export what's\n * public.\n */\n\nimport { A2Error, type A2ErrorCode } from './errors.ts'\nimport { nullProtoRecord } from './internal.ts'\nimport type { Event } from './store.ts'\nimport type {\n PresenceMap,\n PresencePatch,\n PresenceSnapshot,\n} from './contract.ts'\n\nexport type WireEvent = {\n id: string\n type: string\n payload: unknown\n index: number\n sessionId: string\n /** ISO 8601 — revived to a Date on the client so reducers see the\n * same shape on both sides of the wire. */\n createdAt: string\n}\n\nexport function eventToWire(event: Event): WireEvent {\n return {\n id: event.id,\n type: event.type,\n payload: event.payload,\n index: event.index,\n sessionId: event.sessionId,\n createdAt: event.createdAt.toISOString(),\n }\n}\n\nexport function eventFromWire(wire: WireEvent): Event {\n return {\n id: wire.id,\n type: wire.type,\n payload: wire.payload,\n index: wire.index,\n sessionId: wire.sessionId,\n createdAt: new Date(wire.createdAt),\n }\n}\n\nexport function isWireEvent(value: unknown): value is WireEvent {\n if (value === null || typeof value !== 'object') return false\n const v = value as Record<string, unknown>\n return (\n typeof v['id'] === 'string' &&\n typeof v['type'] === 'string' &&\n typeof v['index'] === 'number' &&\n typeof v['sessionId'] === 'string' &&\n typeof v['createdAt'] === 'string'\n )\n}\n\n// ── the presence wire pair ───────────────────────────────────────────\n\n/** The `event: presence` SSE frame body: one patch as JSON. */\nexport type WirePresencePatch = {\n participant: string\n values: Record<string, unknown>\n seen: number\n /** ISO 8601 — revived to a Date on the client. */\n at: string\n}\n\n/**\n * The `event: presence-snapshot` SSE frame body: the in-memory\n * `PresenceSnapshot` shape verbatim, with each per-field `at`\n * serialized to ISO 8601.\n */\nexport type WirePresenceSnapshot = {\n snapshot: {\n [participant: string]: {\n [field: string]: { value: unknown; seen: number; at: string }\n }\n }\n}\n\nexport function presencePatchToWire(patch: PresencePatch): WirePresencePatch {\n return {\n participant: patch.participant,\n values: patch.values,\n seen: patch.seen,\n at: patch.at.toISOString(),\n }\n}\n\nexport function presencePatchFromWire(wire: WirePresencePatch): PresencePatch {\n return {\n participant: wire.participant,\n values: wire.values,\n seen: wire.seen,\n at: new Date(wire.at),\n }\n}\n\n// Participant and field keys come off the wire, so every object keyed\n// by them is built null-prototype — see `nullProtoRecord`.\nexport function presenceSnapshotToWire(\n snapshot: PresenceSnapshot,\n): WirePresenceSnapshot {\n const wire: WirePresenceSnapshot['snapshot'] = nullProtoRecord()\n for (const [participant, fields] of Object.entries(snapshot.snapshot)) {\n const wireFields: WirePresenceSnapshot['snapshot'][string] =\n nullProtoRecord()\n for (const [field, entry] of Object.entries(fields)) {\n if (entry === undefined) continue\n wireFields[field] = {\n value: entry.value,\n seen: entry.seen,\n at: entry.at.toISOString(),\n }\n }\n wire[participant] = wireFields\n }\n return { snapshot: wire }\n}\n\nexport function presenceSnapshotFromWire(\n wire: WirePresenceSnapshot,\n): PresenceSnapshot {\n const map: PresenceMap = nullProtoRecord()\n for (const [participant, fields] of Object.entries(wire.snapshot)) {\n const revived: PresenceMap[string] = nullProtoRecord()\n for (const [field, entry] of Object.entries(fields)) {\n revived[field] = {\n value: entry.value,\n seen: entry.seen,\n at: new Date(entry.at),\n }\n }\n map[participant] = revived\n }\n return { snapshot: map }\n}\n\nexport function isWirePresencePatch(\n value: unknown,\n): value is WirePresencePatch {\n if (value === null || typeof value !== 'object') return false\n const v = value as Record<string, unknown>\n return (\n typeof v['participant'] === 'string' &&\n v['values'] !== null &&\n typeof v['values'] === 'object' &&\n !Array.isArray(v['values']) &&\n typeof v['seen'] === 'number' &&\n typeof v['at'] === 'string'\n )\n}\n\nexport function isWirePresenceSnapshot(\n value: unknown,\n): value is WirePresenceSnapshot {\n if (value === null || typeof value !== 'object') return false\n const v = value as Record<string, unknown>\n return (\n v['snapshot'] !== null &&\n typeof v['snapshot'] === 'object' &&\n !Array.isArray(v['snapshot'])\n )\n}\n\n// ── the A2Error envelope ─────────────────────────────────────────────\n\nexport type WireError = {\n error: { code: A2ErrorCode; message: string; details?: unknown }\n}\n\nconst ERROR_STATUS: Record<A2ErrorCode, number> = {\n INVALID_PAYLOAD: 400,\n FORBIDDEN: 403,\n UNKNOWN_EVENT_TYPE: 400,\n PARTIAL_DUPLICATE_BATCH: 400,\n SUPERSEDED_ATTEMPT: 409,\n CLAIM_EXPIRED: 409,\n STORE_UNAVAILABLE: 503,\n STORE_NOT_CONFIGURED: 500,\n UNKNOWN_PRESENCE_FIELD: 400,\n PRESENCE_NOT_SUPPORTED: 500,\n}\n\nexport function errorStatus(code: A2ErrorCode): number {\n return ERROR_STATUS[code]\n}\n\nexport function errorToWire(error: A2Error): WireError {\n const body: WireError = {\n error: { code: error.code, message: error.message },\n }\n if (error.details !== undefined) body.error.details = error.details\n return body\n}\n\n/** Wrap an arbitrary thrown value for the wire: A2Errors pass through,\n * anything else becomes STORE_UNAVAILABLE — from the client's\n * perspective an unknown server failure is retryable-once, not a\n * protocol contract. */\nexport function asA2Error(error: unknown): A2Error {\n return error instanceof A2Error\n ? error\n : new A2Error('STORE_UNAVAILABLE', 'internal error', { cause: error })\n}\n\n/** Rebuild an A2Error from a wire body; null if the body isn't one. */\nexport function errorFromWire(body: unknown): A2Error | null {\n if (body === null || typeof body !== 'object') return null\n const err = (body as { error?: unknown }).error\n if (err === null || typeof err !== 'object') return null\n const { code, message, details } = err as Record<string, unknown>\n if (typeof code !== 'string' || !Object.hasOwn(ERROR_STATUS, code))\n return null\n return new A2Error(code as A2ErrorCode, String(message ?? code), {\n details,\n })\n}\n\n// ── the ws frame layer ───────────────────────────────────────────────\n// The SSE lanes reframed for a socket (specs/a2-api.md §13): every\n// message is one JSON text frame, and a frame is its wire payload plus\n// a `kind` discriminant (plus `req` where a reply must correlate).\n// Unknown kinds are skipped by both sides — the same\n// forward-compatibility rule as named SSE frames.\n//\n// The multiplexed superset rides the same frames: `subscribe` /\n// `unsubscribe` up-frames open and close per-session lanes on one\n// socket, `sessionId` tags route everything else. Single-session\n// frames (no tags) keep parsing unchanged — a route opts into\n// multiplexing by choosing the multi-session server handler, never by\n// breaking the old protocol.\n\n/**\n * Server → client, parsed: a live-stream item, a push ack, a\n * subscription lifecycle notice, or the heartbeat. `parseSocketFrame`\n * yields these; frames of unknown kind (or ones failing their shape\n * guard) come back `null`. `sessionId` is present on frames from a\n * multiplexed socket and absent on a single-session one.\n */\nexport type SocketDownFrame =\n | { kind: 'event'; event: WireEvent }\n | { kind: 'presence'; patch: WirePresencePatch; sessionId?: string }\n | {\n kind: 'presence-snapshot'\n snapshot: WirePresenceSnapshot\n sessionId?: string\n }\n | { kind: 'ack'; req: number; events: WireEvent[]; sessionId?: string }\n | { kind: 'ack'; req: number; error: A2Error; sessionId?: string }\n | { kind: 'subscribed'; sessionId: string }\n | { kind: 'unsubscribed'; sessionId: string; reason?: string }\n | { kind: 'ping' }\n\n/**\n * Client → server: the push envelope's two planes. `sessionId` is\n * implied by the socket on a single-session connection and required by\n * the multiplexed handler. Plain JSON on the wire; the server parses\n * and validates them through the same seams as `parsePushBody`.\n */\nexport type SocketPushFrame = {\n kind: 'push'\n /** Client-local ack correlator — opaque to the server. */\n req: number\n events: Array<{ type: string; payload: unknown; id?: string }>\n sessionId?: string\n}\n\nexport type SocketPresenceFrame = {\n kind: 'presence'\n participant: string\n values: Record<string, unknown>\n seen?: number\n at?: number\n sessionId?: string\n}\n\n/** Open per-session lanes on a multiplexed socket. `index` is each\n * session's exclusive resume frontier — `stream({ startAfter })`. */\nexport type SocketSubscribeFrame = {\n kind: 'subscribe'\n sessions: Array<{ id: string; index: number }>\n}\n\nexport type SocketUnsubscribeFrame = {\n kind: 'unsubscribe'\n sessions: string[]\n}\n\nexport type SocketUpFrame =\n | SocketPushFrame\n | SocketPresenceFrame\n | SocketSubscribeFrame\n | SocketUnsubscribeFrame\n\nexport const SOCKET_PING_FRAME: string = JSON.stringify({ kind: 'ping' })\n\n/** One stream item as a socket frame — `sseResponse`'s framing over\n * the same codecs, with `kind` instead of an SSE event name. On a\n * multiplexed socket presence frames carry the `sessionId` tag; events\n * already carry theirs in the wire event. */\nexport function socketFrameFor(\n item: Event | PresencePatch | PresenceSnapshot,\n sessionId?: string,\n): string {\n const tag = sessionId === undefined ? {} : { sessionId }\n if ('snapshot' in item) {\n return JSON.stringify({\n kind: 'presence-snapshot',\n ...presenceSnapshotToWire(item),\n ...tag,\n })\n }\n if ('participant' in item) {\n return JSON.stringify({\n kind: 'presence',\n ...presencePatchToWire(item),\n ...tag,\n })\n }\n return JSON.stringify({ kind: 'event', ...eventToWire(item) })\n}\n\nexport function socketAckFor(\n req: number,\n events: Event[],\n sessionId?: string,\n): string {\n return JSON.stringify({\n kind: 'ack',\n req,\n events: events.map(eventToWire),\n ...(sessionId === undefined ? {} : { sessionId }),\n })\n}\n\nexport function socketErrorAckFor(\n req: number,\n error: A2Error,\n sessionId?: string,\n): string {\n return JSON.stringify({\n kind: 'ack',\n req,\n ...errorToWire(error),\n ...(sessionId === undefined ? {} : { sessionId }),\n })\n}\n\nexport function socketSubscribedFor(sessionId: string): string {\n return JSON.stringify({ kind: 'subscribed', sessionId })\n}\n\n/** The subscription-over notice: with a `reason` the server rejected\n * or lost the session's stream, without one it ended cleanly. */\nexport function socketUnsubscribedFor(\n sessionId: string,\n reason?: string,\n): string {\n return JSON.stringify({\n kind: 'unsubscribed',\n sessionId,\n ...(reason === undefined ? {} : { reason }),\n })\n}\n\nexport function parseSocketFrame(data: string): SocketDownFrame | null {\n let parsed: unknown\n try {\n parsed = JSON.parse(data)\n } catch {\n return null\n }\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return null\n }\n const frame = parsed as Record<string, unknown>\n switch (frame['kind']) {\n case 'ping':\n return { kind: 'ping' }\n case 'event': {\n if (!isWireEvent(parsed)) return null\n const { id, type, payload, index, sessionId, createdAt } = parsed\n return {\n kind: 'event',\n event: { id, type, payload, index, sessionId, createdAt },\n }\n }\n case 'presence': {\n if (!isWirePresencePatch(parsed)) return null\n const { participant, values, seen, at } = parsed\n return {\n kind: 'presence',\n patch: { participant, values, seen, at },\n ...sessionTag(frame),\n }\n }\n case 'presence-snapshot': {\n if (!isWirePresenceSnapshot(parsed)) return null\n return {\n kind: 'presence-snapshot',\n snapshot: { snapshot: parsed.snapshot },\n ...sessionTag(frame),\n }\n }\n case 'subscribed': {\n const sessionId = frame['sessionId']\n if (typeof sessionId !== 'string') return null\n return { kind: 'subscribed', sessionId }\n }\n case 'unsubscribed': {\n const sessionId = frame['sessionId']\n if (typeof sessionId !== 'string') return null\n const reason = frame['reason']\n return typeof reason === 'string'\n ? { kind: 'unsubscribed', sessionId, reason }\n : { kind: 'unsubscribed', sessionId }\n }\n case 'ack': {\n const req = frame['req']\n if (typeof req !== 'number') return null\n const tag = sessionTag(frame)\n const events = frame['events']\n if (Array.isArray(events) && events.every(isWireEvent)) {\n return { kind: 'ack', req, events, ...tag }\n }\n const error = errorFromWire(parsed)\n if (error) return { kind: 'ack', req, error, ...tag }\n // A correlatable ack must never be dropped: its waiter would hang\n // forever behind a healthy socket (pings keep the watchdog fed).\n // An outcome this client cannot interpret — an error code from a\n // newer server, an event shape that fails a guard — degrades to\n // a lost ack: retryable, and the client-generated ids make the\n // retry idempotent even if the append actually committed.\n return {\n kind: 'ack',\n req,\n error: new A2Error('STORE_UNAVAILABLE', 'unintelligible ack'),\n ...tag,\n }\n }\n default:\n return null\n }\n}\n\nconst sessionTag = (frame: Record<string, unknown>): { sessionId?: string } => {\n const sessionId = frame['sessionId']\n return typeof sessionId === 'string' ? { sessionId } : {}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"wire--yji6mO3.js","names":[],"sources":["../src/wire.ts"],"sourcesContent":["/**\n * The wire format shared by server.fetch and experimental-a2/client\n * (browser side): events as JSON with ISO timestamps, and the A2Error\n * envelope. Internal module — both entry points re-export what's\n * public.\n */\n\nimport { A2Error, type A2ErrorCode } from './errors.ts'\nimport { nullProtoRecord } from './internal.ts'\nimport type { Event } from './store.ts'\nimport type {\n PresenceMap,\n PresencePatch,\n PresenceSnapshot,\n} from './contract.ts'\n\nexport type WireEvent = {\n id: string\n type: string\n payload: unknown\n index: number\n sessionId: string\n /** ISO 8601 — revived to a Date on the client so reducers see the\n * same shape on both sides of the wire. */\n createdAt: string\n}\n\nexport function eventToWire(event: Event): WireEvent {\n return {\n id: event.id,\n type: event.type,\n payload: event.payload,\n index: event.index,\n sessionId: event.sessionId,\n createdAt: event.createdAt.toISOString(),\n }\n}\n\nexport function eventFromWire(wire: WireEvent): Event {\n return {\n id: wire.id,\n type: wire.type,\n payload: wire.payload,\n index: wire.index,\n sessionId: wire.sessionId,\n createdAt: new Date(wire.createdAt),\n }\n}\n\nexport function isWireEvent(value: unknown): value is WireEvent {\n if (value === null || typeof value !== 'object') return false\n const v = value as Record<string, unknown>\n return (\n typeof v['id'] === 'string' &&\n typeof v['type'] === 'string' &&\n typeof v['index'] === 'number' &&\n typeof v['sessionId'] === 'string' &&\n typeof v['createdAt'] === 'string'\n )\n}\n\n// ── the presence wire pair ───────────────────────────────────────────\n\n/** The `event: presence` SSE frame body: one patch as JSON. */\nexport type WirePresencePatch = {\n participant: string\n values: Record<string, unknown>\n seen: number\n /** ISO 8601 — revived to a Date on the client. */\n at: string\n}\n\n/**\n * The `event: presence-snapshot` SSE frame body: the in-memory\n * `PresenceSnapshot` shape verbatim, with each per-field `at`\n * serialized to ISO 8601.\n */\nexport type WirePresenceSnapshot = {\n snapshot: {\n [participant: string]: {\n [field: string]: { value: unknown; seen: number; at: string }\n }\n }\n}\n\nexport function presencePatchToWire(patch: PresencePatch): WirePresencePatch {\n return {\n participant: patch.participant,\n values: patch.values,\n seen: patch.seen,\n at: patch.at.toISOString(),\n }\n}\n\nexport function presencePatchFromWire(wire: WirePresencePatch): PresencePatch {\n return {\n participant: wire.participant,\n values: wire.values,\n seen: wire.seen,\n at: new Date(wire.at),\n }\n}\n\n// Participant and field keys come off the wire, so every object keyed\n// by them is built null-prototype — see `nullProtoRecord`.\nexport function presenceSnapshotToWire(\n snapshot: PresenceSnapshot,\n): WirePresenceSnapshot {\n const wire: WirePresenceSnapshot['snapshot'] = nullProtoRecord()\n for (const [participant, fields] of Object.entries(snapshot.snapshot)) {\n const wireFields: WirePresenceSnapshot['snapshot'][string] =\n nullProtoRecord()\n for (const [field, entry] of Object.entries(fields)) {\n if (entry === undefined) continue\n wireFields[field] = {\n value: entry.value,\n seen: entry.seen,\n at: entry.at.toISOString(),\n }\n }\n wire[participant] = wireFields\n }\n return { snapshot: wire }\n}\n\nexport function presenceSnapshotFromWire(\n wire: WirePresenceSnapshot,\n): PresenceSnapshot {\n const map: PresenceMap = nullProtoRecord()\n for (const [participant, fields] of Object.entries(wire.snapshot)) {\n const revived: PresenceMap[string] = nullProtoRecord()\n for (const [field, entry] of Object.entries(fields)) {\n revived[field] = {\n value: entry.value,\n seen: entry.seen,\n at: new Date(entry.at),\n }\n }\n map[participant] = revived\n }\n return { snapshot: map }\n}\n\nexport function isWirePresencePatch(\n value: unknown,\n): value is WirePresencePatch {\n if (value === null || typeof value !== 'object') return false\n const v = value as Record<string, unknown>\n return (\n typeof v['participant'] === 'string' &&\n v['values'] !== null &&\n typeof v['values'] === 'object' &&\n !Array.isArray(v['values']) &&\n typeof v['seen'] === 'number' &&\n typeof v['at'] === 'string'\n )\n}\n\nexport function isWirePresenceSnapshot(\n value: unknown,\n): value is WirePresenceSnapshot {\n if (value === null || typeof value !== 'object') return false\n const v = value as Record<string, unknown>\n return (\n v['snapshot'] !== null &&\n typeof v['snapshot'] === 'object' &&\n !Array.isArray(v['snapshot'])\n )\n}\n\n// ── the A2Error envelope ─────────────────────────────────────────────\n\nexport type WireError = {\n error: { code: A2ErrorCode; message: string; details?: unknown }\n}\n\nconst ERROR_STATUS: Record<A2ErrorCode, number> = {\n INVALID_PAYLOAD: 400,\n FORBIDDEN: 403,\n UNKNOWN_EVENT_TYPE: 400,\n PARTIAL_DUPLICATE_BATCH: 400,\n SUPERSEDED_ATTEMPT: 409,\n CLAIM_EXPIRED: 409,\n STORE_UNAVAILABLE: 503,\n STORE_NOT_CONFIGURED: 500,\n UNKNOWN_PRESENCE_FIELD: 400,\n PRESENCE_NOT_SUPPORTED: 500,\n}\n\nexport function errorStatus(code: A2ErrorCode): number {\n return ERROR_STATUS[code]\n}\n\nexport function errorToWire(error: A2Error): WireError {\n const body: WireError = {\n error: { code: error.code, message: error.message },\n }\n if (error.details !== undefined) body.error.details = error.details\n return body\n}\n\n/** Wrap an arbitrary thrown value for the wire: A2Errors pass through,\n * anything else becomes STORE_UNAVAILABLE — from the client's\n * perspective an unknown server failure is retryable-once, not a\n * protocol contract. */\nexport function asA2Error(error: unknown): A2Error {\n return error instanceof A2Error\n ? error\n : new A2Error('STORE_UNAVAILABLE', 'internal error', { cause: error })\n}\n\n/** Rebuild an A2Error from a wire body; null if the body isn't one. */\nexport function errorFromWire(body: unknown): A2Error | null {\n if (body === null || typeof body !== 'object') return null\n const err = (body as { error?: unknown }).error\n if (err === null || typeof err !== 'object') return null\n const { code, message, details } = err as Record<string, unknown>\n if (typeof code !== 'string' || !Object.hasOwn(ERROR_STATUS, code))\n return null\n return new A2Error(code as A2ErrorCode, String(message ?? code), {\n details,\n })\n}\n\n// ── the ws frame layer ───────────────────────────────────────────────\n// The SSE lanes reframed for a socket (specs/a2-api.md §13): every\n// message is one JSON text frame, and a frame is its wire payload plus\n// a `kind` discriminant (plus `req` where a reply must correlate).\n// Unknown kinds are skipped by both sides — the same\n// forward-compatibility rule as named SSE frames.\n//\n// The multiplexed superset rides the same frames: `subscribe` /\n// `unsubscribe` up-frames open and close per-session lanes on one\n// socket, `sessionId` tags route everything else. Single-session\n// frames (no tags) keep parsing unchanged — a route opts into\n// multiplexing by choosing the multi-session server handler, never by\n// breaking the old protocol.\n\n/**\n * Server → client, parsed: a live-stream item, a push ack, a\n * subscription lifecycle notice, or the heartbeat. `parseSocketFrame`\n * yields these; frames of unknown kind (or ones failing their shape\n * guard) come back `null`. `sessionId` is present on frames from a\n * multiplexed socket and absent on a single-session one.\n */\nexport type SocketDownFrame =\n | { kind: 'event'; event: WireEvent }\n | { kind: 'presence'; patch: WirePresencePatch; sessionId?: string }\n | {\n kind: 'presence-snapshot'\n snapshot: WirePresenceSnapshot\n sessionId?: string\n }\n | { kind: 'ack'; req: number; events: WireEvent[]; sessionId?: string }\n | { kind: 'ack'; req: number; error: A2Error; sessionId?: string }\n | { kind: 'subscribed'; sessionId: string }\n | { kind: 'unsubscribed'; sessionId: string; reason?: string }\n | { kind: 'ping' }\n\n/**\n * Client → server: the push envelope's two planes. `sessionId` is\n * implied by the socket on a single-session connection and required by\n * the multiplexed handler. Plain JSON on the wire; the server parses\n * and validates them through the same seams as `parsePushBody`.\n */\nexport type SocketPushFrame = {\n kind: 'push'\n /** Client-local ack correlator — opaque to the server. */\n req: number\n events: Array<{ type: string; payload: unknown; id?: string }>\n sessionId?: string\n}\n\nexport type SocketPresenceFrame = {\n kind: 'presence'\n participant: string\n values: Record<string, unknown>\n seen?: number\n at?: number\n sessionId?: string\n}\n\n/** Open per-session lanes on a multiplexed socket. `index` is each\n * session's exclusive resume frontier — `stream({ startAfter })`. */\nexport type SocketSubscribeFrame = {\n kind: 'subscribe'\n sessions: Array<{ id: string; index: number }>\n}\n\nexport type SocketUnsubscribeFrame = {\n kind: 'unsubscribe'\n sessions: string[]\n}\n\nexport type SocketUpFrame =\n | SocketPushFrame\n | SocketPresenceFrame\n | SocketSubscribeFrame\n | SocketUnsubscribeFrame\n\nexport const SOCKET_PING_FRAME: string = JSON.stringify({ kind: 'ping' })\n\n/** One stream item as a socket frame — `sseResponse`'s framing over\n * the same codecs, with `kind` instead of an SSE event name. On a\n * multiplexed socket presence frames carry the `sessionId` tag; events\n * already carry theirs in the wire event. */\nexport function socketFrameFor(\n item: Event | PresencePatch | PresenceSnapshot,\n sessionId?: string,\n): string {\n const tag = sessionId === undefined ? {} : { sessionId }\n if ('snapshot' in item) {\n return JSON.stringify({\n kind: 'presence-snapshot',\n ...presenceSnapshotToWire(item),\n ...tag,\n })\n }\n if ('participant' in item) {\n return JSON.stringify({\n kind: 'presence',\n ...presencePatchToWire(item),\n ...tag,\n })\n }\n return JSON.stringify({ kind: 'event', ...eventToWire(item) })\n}\n\nexport function socketAckFor(\n req: number,\n events: Event[],\n sessionId?: string,\n): string {\n return JSON.stringify({\n kind: 'ack',\n req,\n events: events.map(eventToWire),\n ...(sessionId === undefined ? {} : { sessionId }),\n })\n}\n\nexport function socketErrorAckFor(\n req: number,\n error: A2Error,\n sessionId?: string,\n): string {\n return JSON.stringify({\n kind: 'ack',\n req,\n ...errorToWire(error),\n ...(sessionId === undefined ? {} : { sessionId }),\n })\n}\n\nexport function socketSubscribedFor(sessionId: string): string {\n return JSON.stringify({ kind: 'subscribed', sessionId })\n}\n\n/** The subscription-over notice: with a `reason` the server rejected\n * or lost the session's stream, without one it ended cleanly. */\nexport function socketUnsubscribedFor(\n sessionId: string,\n reason?: string,\n): string {\n return JSON.stringify({\n kind: 'unsubscribed',\n sessionId,\n ...(reason === undefined ? {} : { reason }),\n })\n}\n\nexport function parseSocketFrame(data: string): SocketDownFrame | null {\n let parsed: unknown\n try {\n parsed = JSON.parse(data)\n } catch {\n return null\n }\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return null\n }\n const frame = parsed as Record<string, unknown>\n switch (frame['kind']) {\n case 'ping':\n return { kind: 'ping' }\n case 'event': {\n if (!isWireEvent(parsed)) return null\n const { id, type, payload, index, sessionId, createdAt } = parsed\n return {\n kind: 'event',\n event: { id, type, payload, index, sessionId, createdAt },\n }\n }\n case 'presence': {\n if (!isWirePresencePatch(parsed)) return null\n const { participant, values, seen, at } = parsed\n return {\n kind: 'presence',\n patch: { participant, values, seen, at },\n ...sessionTag(frame),\n }\n }\n case 'presence-snapshot': {\n if (!isWirePresenceSnapshot(parsed)) return null\n return {\n kind: 'presence-snapshot',\n snapshot: { snapshot: parsed.snapshot },\n ...sessionTag(frame),\n }\n }\n case 'subscribed': {\n const sessionId = frame['sessionId']\n if (typeof sessionId !== 'string') return null\n return { kind: 'subscribed', sessionId }\n }\n case 'unsubscribed': {\n const sessionId = frame['sessionId']\n if (typeof sessionId !== 'string') return null\n const reason = frame['reason']\n return typeof reason === 'string'\n ? { kind: 'unsubscribed', sessionId, reason }\n : { kind: 'unsubscribed', sessionId }\n }\n case 'ack': {\n const req = frame['req']\n if (typeof req !== 'number') return null\n const tag = sessionTag(frame)\n const events = frame['events']\n if (Array.isArray(events) && events.every(isWireEvent)) {\n return { kind: 'ack', req, events, ...tag }\n }\n const error = errorFromWire(parsed)\n if (error) return { kind: 'ack', req, error, ...tag }\n // A correlatable ack must never be dropped: its waiter would hang\n // forever behind a healthy socket (pings keep the watchdog fed).\n // An outcome this client cannot interpret — an error code from a\n // newer server, an event shape that fails a guard — degrades to\n // a lost ack: retryable, and the client-generated ids make the\n // retry idempotent even if the append actually committed.\n return {\n kind: 'ack',\n req,\n error: new A2Error('STORE_UNAVAILABLE', 'unintelligible ack'),\n ...tag,\n }\n }\n default:\n return null\n }\n}\n\nconst sessionTag = (frame: Record<string, unknown>): { sessionId?: string } => {\n const sessionId = frame['sessionId']\n return typeof sessionId === 'string' ? { sessionId } : {}\n}\n"],"mappings":"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"}