experimental-a2 0.19.0 → 0.21.1

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 (320) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +1 -1
  3. package/dist/{actor-C7b-1arn.d.ts → actor-HnGmrOZZ.d.ts} +5 -5
  4. package/dist/{actor-C7b-1arn.d.ts.map → actor-HnGmrOZZ.d.ts.map} +1 -1
  5. package/dist/actor-client.d.ts +1 -1
  6. package/dist/actor-client.js +2 -2
  7. package/dist/actor-react.d.ts +3 -3
  8. package/dist/actor-react.js +5 -5
  9. package/dist/{actor-shared-t7w8xIKP.js → actor-shared-DFET5q16.js} +3 -3
  10. package/dist/{actor-shared-t7w8xIKP.js.map → actor-shared-DFET5q16.js.map} +1 -1
  11. package/dist/actor.d.ts +1 -1
  12. package/dist/actor.js +54 -43
  13. package/dist/actor.js.map +1 -1
  14. package/dist/{ai-Cz3hqVhk.d.ts → ai-CvEnqVfo.d.ts} +8 -8
  15. package/dist/{ai-Cz3hqVhk.d.ts.map → ai-CvEnqVfo.d.ts.map} +1 -1
  16. package/dist/{ai-mMiWul1T.js → ai-CzE672Q3.js} +9 -9
  17. package/dist/ai-CzE672Q3.js.map +1 -0
  18. package/dist/ai-client.d.ts +2 -2
  19. package/dist/ai-client.js +2 -2
  20. package/dist/ai-fx.d.ts +1 -1
  21. package/dist/ai-fx.d.ts.map +1 -1
  22. package/dist/ai-fx.js +36 -9
  23. package/dist/ai-fx.js.map +1 -1
  24. package/dist/{ai-harness-CV6rXbB3.d.ts → ai-harness-Bfk-Lycn.d.ts} +2 -2
  25. package/dist/ai-harness-Bfk-Lycn.d.ts.map +1 -0
  26. package/dist/{ai-harness-BuuC-gJ3.js → ai-harness-dQeJaR1t.js} +17 -4
  27. package/dist/ai-harness-dQeJaR1t.js.map +1 -0
  28. package/dist/ai-pi.d.ts +1 -1
  29. package/dist/ai-pi.js +4 -4
  30. package/dist/ai-pi.js.map +1 -1
  31. package/dist/ai-react.d.ts +5 -5
  32. package/dist/ai-react.js +2 -2
  33. package/dist/ai-server.d.ts +14 -17
  34. package/dist/ai-server.d.ts.map +1 -1
  35. package/dist/ai-server.js +84 -75
  36. package/dist/ai-server.js.map +1 -1
  37. package/dist/ai.d.ts +2 -2
  38. package/dist/ai.js +2 -2
  39. package/dist/{client-BhHLoNKF.js → client-9IeTsiAs.js} +5 -5
  40. package/dist/{client-BhHLoNKF.js.map → client-9IeTsiAs.js.map} +1 -1
  41. package/dist/{client-h_DKFAjj.d.ts → client-CUMbS_38.d.ts} +3 -3
  42. package/dist/{client-h_DKFAjj.d.ts.map → client-CUMbS_38.d.ts.map} +1 -1
  43. package/dist/client.d.ts +3 -3
  44. package/dist/client.js +1 -1
  45. package/dist/{contract-BYKASlCV.js → contract-CWukt01d.js} +2 -2
  46. package/dist/contract-CWukt01d.js.map +1 -0
  47. package/dist/index.d.ts +5 -5
  48. package/dist/index.js +3 -3
  49. package/dist/{initial-snapshot-Bx-c0pDP.d.ts → initial-snapshot-D3Lve43o.d.ts} +4 -4
  50. package/dist/initial-snapshot-D3Lve43o.d.ts.map +1 -0
  51. package/dist/{internal-CscSY7K_.js → internal-Bc4PvfHk.js} +10 -3
  52. package/dist/internal-Bc4PvfHk.js.map +1 -0
  53. package/dist/otel.d.ts +1 -1
  54. package/dist/otel.js +1 -1
  55. package/dist/otel.js.map +1 -1
  56. package/dist/{react-BBXgHWFc.d.ts → react-BoHvC3v9.d.ts} +4 -4
  57. package/dist/{react-BBXgHWFc.d.ts.map → react-BoHvC3v9.d.ts.map} +1 -1
  58. package/dist/react.d.ts +1 -1
  59. package/dist/react.js +1 -1
  60. package/dist/{reducer-CqA59Q6U.d.ts → reducer-CYPHsSmd.d.ts} +2 -2
  61. package/dist/{reducer-CqA59Q6U.d.ts.map → reducer-CYPHsSmd.d.ts.map} +1 -1
  62. package/dist/{reducer-BAXc8JyF.js → reducer-CjmvZSAb.js} +2 -2
  63. package/dist/{reducer-BAXc8JyF.js.map → reducer-CjmvZSAb.js.map} +1 -1
  64. package/dist/runtime-consumer-BWmht60z.js +12 -0
  65. package/dist/runtime-consumer-BWmht60z.js.map +1 -0
  66. package/dist/scheduler-qstash.d.ts +2 -2
  67. package/dist/scheduler-qstash.d.ts.map +1 -1
  68. package/dist/scheduler-qstash.js +7 -7
  69. package/dist/scheduler-qstash.js.map +1 -1
  70. package/dist/scheduler-task-X1SToDjP.js.map +1 -1
  71. package/dist/scheduler-vercel.d.ts +2 -2
  72. package/dist/scheduler-vercel.d.ts.map +1 -1
  73. package/dist/scheduler-vercel.js +5 -4
  74. package/dist/scheduler-vercel.js.map +1 -1
  75. package/dist/{server-DYAATFuT.d.ts → server-BshFZ0eL.d.ts} +108 -41
  76. package/dist/server-BshFZ0eL.d.ts.map +1 -0
  77. package/dist/{server-D-Y4pqwu.js → server-DvIB8kj5.js} +423 -135
  78. package/dist/server-DvIB8kj5.js.map +1 -0
  79. package/dist/server.browser.js +1 -1
  80. package/dist/server.browser.js.map +1 -1
  81. package/dist/server.d.ts +4 -4
  82. package/dist/server.js +2 -2
  83. package/dist/{session-clear-C-QwTXV1.d.ts → session-clear-Bk_29eEw.d.ts} +2 -2
  84. package/dist/session-clear-Bk_29eEw.d.ts.map +1 -0
  85. package/dist/{session-clear-DGI4jTQ5.js → session-clear-wHd5GCpZ.js} +5 -4
  86. package/dist/session-clear-wHd5GCpZ.js.map +1 -0
  87. package/dist/store-N8PXxDAS.js.map +1 -1
  88. package/dist/store-Z2qjw50n.d.ts +523 -0
  89. package/dist/store-Z2qjw50n.d.ts.map +1 -0
  90. package/dist/store-codec-DwrWlgQx.js +65 -0
  91. package/dist/store-codec-DwrWlgQx.js.map +1 -0
  92. package/dist/store-memory.d.ts +1 -1
  93. package/dist/store-memory.d.ts.map +1 -1
  94. package/dist/store-memory.js +393 -210
  95. package/dist/store-memory.js.map +1 -1
  96. package/dist/{store-polling-7WJV0YWP.js → store-polling-DYNyxCev.js} +2 -2
  97. package/dist/{store-polling-7WJV0YWP.js.map → store-polling-DYNyxCev.js.map} +1 -1
  98. package/dist/store-postgres.d.ts +1 -1
  99. package/dist/store-postgres.d.ts.map +1 -1
  100. package/dist/store-postgres.js +1595 -673
  101. package/dist/store-postgres.js.map +1 -1
  102. package/dist/{store-presence-polling-B1ixeqIS.js → store-presence-polling-CzQ0wdtM.js} +3 -3
  103. package/dist/{store-presence-polling-B1ixeqIS.js.map → store-presence-polling-CzQ0wdtM.js.map} +1 -1
  104. package/dist/store-redis-http.d.ts +1 -1
  105. package/dist/store-redis-http.js +4 -4
  106. package/dist/store-redis-notify-IiVdlYHc.js +2919 -0
  107. package/dist/store-redis-notify-IiVdlYHc.js.map +1 -0
  108. package/dist/store-redis.d.ts +1 -1
  109. package/dist/store-redis.js +2 -2
  110. package/dist/store-sqlite.d.ts +1 -1
  111. package/dist/store-sqlite.d.ts.map +1 -1
  112. package/dist/store-sqlite.js +517 -287
  113. package/dist/store-sqlite.js.map +1 -1
  114. package/dist/{telemetry-Cyfqo6Qt.d.ts → telemetry-CWwsh-Gx.d.ts} +3 -3
  115. package/dist/telemetry-CWwsh-Gx.d.ts.map +1 -0
  116. package/dist/{wire-BKxH1vbP.js → wire-CZGqNZRx.js} +5 -4
  117. package/dist/wire-CZGqNZRx.js.map +1 -0
  118. package/docs/01-quickstart.mdx +28 -19
  119. package/docs/02-examples.mdx +1 -1
  120. package/docs/actors/01-introduction.mdx +9 -1
  121. package/docs/actors/02-concurrency.mdx +1 -1
  122. package/docs/actors/meta.ts +1 -1
  123. package/docs/agents/01-introduction.mdx +1 -1
  124. package/docs/agents/02-tools.mdx +13 -12
  125. package/docs/agents/03-conversations.mdx +31 -21
  126. package/docs/agents/04-harnesses.mdx +7 -3
  127. package/docs/agents/meta.ts +1 -1
  128. package/docs/client/03-presence.mdx +18 -14
  129. package/docs/client/meta.ts +1 -1
  130. package/docs/contract/01-events.mdx +2 -2
  131. package/docs/contract/02-state.mdx +18 -14
  132. package/docs/contract/03-sessions.mdx +32 -25
  133. package/docs/index.mdx +31 -26
  134. package/docs/reference/01-core.mdx +12 -12
  135. package/docs/reference/02-server.mdx +247 -192
  136. package/docs/reference/04-ai.mdx +29 -20
  137. package/docs/reference/05-telemetry.mdx +146 -0
  138. package/docs/reference/06-errors.mdx +24 -18
  139. package/docs/reference/meta.ts +1 -1
  140. package/docs/schedulers/01-overview.mdx +112 -0
  141. package/docs/schedulers/02-vercel-queues.mdx +87 -0
  142. package/docs/schedulers/03-qstash.mdx +123 -0
  143. package/docs/schedulers/meta.ts +1 -0
  144. package/docs/server/01-consumers.mdx +415 -0
  145. package/docs/server/02-timers.mdx +33 -23
  146. package/docs/server/03-cancellation.mdx +52 -44
  147. package/docs/server/04-routes.mdx +4 -1
  148. package/docs/server/05-sql-projections.mdx +391 -0
  149. package/docs/server/06-guarantees.mdx +60 -50
  150. package/docs/stores/01-overview.mdx +37 -0
  151. package/docs/stores/02-redis.mdx +57 -0
  152. package/docs/stores/03-redis-http.mdx +56 -0
  153. package/docs/stores/04-postgres.mdx +172 -0
  154. package/docs/stores/05-sqlite.mdx +36 -0
  155. package/docs/stores/06-memory.mdx +22 -0
  156. package/docs/stores/meta.ts +1 -0
  157. package/docs/upgrades/01-overview.mdx +30 -0
  158. package/docs/upgrades/79-0-21.mdx +165 -0
  159. package/docs/upgrades/80-0-20.mdx +43 -0
  160. package/docs/upgrades/81-0-19.mdx +33 -0
  161. package/docs/upgrades/82-0-18.mdx +33 -0
  162. package/docs/upgrades/83-0-17.mdx +69 -0
  163. package/docs/upgrades/meta.ts +3 -0
  164. package/examples/playground/DEPLOY.md +48 -11
  165. package/examples/playground/app/a0/apps/[appId]/route.ts +6 -10
  166. package/examples/playground/app/a0/chat.test.tsx +421 -330
  167. package/examples/playground/app/a0/sandbox-log.ts +17 -15
  168. package/examples/playground/app/a0/server.ts +4 -4
  169. package/examples/playground/app/a0/shell.tsx +1 -1
  170. package/examples/playground/app/a0/uploads/route.test.ts +59 -0
  171. package/examples/playground/app/a2slack/a2slack.css +1067 -0
  172. package/examples/playground/app/a2slack/c/[channel]/page.tsx +20 -0
  173. package/examples/playground/app/a2slack/consumers/agent.ts +63 -0
  174. package/examples/playground/app/a2slack/consumers/feed.ts +164 -0
  175. package/examples/playground/app/a2slack/consumers/notify.ts +116 -0
  176. package/examples/playground/app/a2slack/consumers/search.ts +37 -0
  177. package/examples/playground/app/a2slack/consumers/triage.ts +31 -0
  178. package/examples/playground/app/a2slack/consumers/welcome.ts +25 -0
  179. package/examples/playground/app/a2slack/db/a2.ts +206 -0
  180. package/examples/playground/app/a2slack/db/index.ts +73 -0
  181. package/examples/playground/app/a2slack/db/migrations/0000_extensions.sql +4 -0
  182. package/examples/playground/app/a2slack/db/migrations/0001_workspace.sql +74 -0
  183. package/examples/playground/app/a2slack/db/migrations/0002_a2.sql +215 -0
  184. package/examples/playground/app/a2slack/db/migrations/0003_a2_batch.sql +67 -0
  185. package/examples/playground/app/a2slack/db/migrations/meta/0000_snapshot.json +18 -0
  186. package/examples/playground/app/a2slack/db/migrations/meta/0001_snapshot.json +500 -0
  187. package/examples/playground/app/a2slack/db/migrations/meta/0002_snapshot.json +1159 -0
  188. package/examples/playground/app/a2slack/db/migrations/meta/0003_snapshot.json +1159 -0
  189. package/examples/playground/app/a2slack/db/migrations/meta/_journal.json +34 -0
  190. package/examples/playground/app/a2slack/db/queries.ts +263 -0
  191. package/examples/playground/app/a2slack/db/schema.ts +145 -0
  192. package/examples/playground/app/a2slack/drills/a2slack.drill.ts +708 -0
  193. package/examples/playground/app/a2slack/drills/harness.ts +244 -0
  194. package/examples/playground/app/a2slack/events/route.ts +36 -0
  195. package/examples/playground/app/a2slack/identity.ts +44 -0
  196. package/examples/playground/app/a2slack/inbox/route.ts +8 -0
  197. package/examples/playground/app/a2slack/lag/data/route.ts +8 -0
  198. package/examples/playground/app/a2slack/lag/page.tsx +101 -0
  199. package/examples/playground/app/a2slack/lag.ts +73 -0
  200. package/examples/playground/app/a2slack/layout.tsx +43 -0
  201. package/examples/playground/app/a2slack/messages/route.ts +17 -0
  202. package/examples/playground/app/a2slack/model.test.ts +208 -0
  203. package/examples/playground/app/a2slack/model.ts +369 -0
  204. package/examples/playground/app/a2slack/models.test.ts +72 -0
  205. package/examples/playground/app/a2slack/page.tsx +10 -0
  206. package/examples/playground/app/a2slack/scheduler/route.ts +5 -0
  207. package/examples/playground/app/a2slack/search/route.ts +40 -0
  208. package/examples/playground/app/a2slack/search-index.ts +108 -0
  209. package/examples/playground/app/a2slack/server.test.ts +315 -0
  210. package/examples/playground/app/a2slack/server.ts +55 -0
  211. package/examples/playground/app/a2slack/sessions.ts +50 -0
  212. package/examples/playground/app/a2slack/thread/route.ts +8 -0
  213. package/examples/playground/app/a2slack/ui/ChannelView.tsx +87 -0
  214. package/examples/playground/app/a2slack/ui/Composer.tsx +117 -0
  215. package/examples/playground/app/a2slack/ui/DotsShadow.tsx +42 -0
  216. package/examples/playground/app/a2slack/ui/Feed.tsx +157 -0
  217. package/examples/playground/app/a2slack/ui/InboxContext.tsx +63 -0
  218. package/examples/playground/app/a2slack/ui/InboxView.tsx +82 -0
  219. package/examples/playground/app/a2slack/ui/Menu.tsx +128 -0
  220. package/examples/playground/app/a2slack/ui/MessageRow.tsx +314 -0
  221. package/examples/playground/app/a2slack/ui/NameGate.tsx +35 -0
  222. package/examples/playground/app/a2slack/ui/SearchContext.tsx +146 -0
  223. package/examples/playground/app/a2slack/ui/SearchResults.tsx +97 -0
  224. package/examples/playground/app/a2slack/ui/Shell.tsx +34 -0
  225. package/examples/playground/app/a2slack/ui/Sidebar.tsx +199 -0
  226. package/examples/playground/app/a2slack/ui/ThreadPanel.tsx +134 -0
  227. package/examples/playground/app/a2slack/ui/useInbox.ts +71 -0
  228. package/examples/playground/app/agent/[agentId]/agent-queue.test.tsx +116 -90
  229. package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +107 -76
  230. package/examples/playground/app/agent/ingress.test.ts +41 -17
  231. package/examples/playground/app/agent/model-input.test.tsx +92 -43
  232. package/examples/playground/app/agent/server.ts +3 -3
  233. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.test.tsx +2 -2
  234. package/examples/playground/app/chat/[chatId]/chat-client.tsx +1 -1
  235. package/examples/playground/app/chat/server.test.ts +38 -65
  236. package/examples/playground/app/chat/server.ts +45 -35
  237. package/examples/playground/app/components/activity-history.test.tsx +60 -18
  238. package/examples/playground/app/components/demo-icons.tsx +7 -0
  239. package/examples/playground/app/components/demos.tsx +7 -0
  240. package/examples/playground/app/components/fast-link.test.tsx +3 -5
  241. package/examples/playground/app/initial-state.test.tsx +86 -45
  242. package/examples/playground/app/orders/[orderId]/order-client.tsx +2 -2
  243. package/examples/playground/app/orders/page.tsx +1 -1
  244. package/examples/playground/app/orders/server.ts +49 -30
  245. package/examples/playground/app/recovery/[recoveryId]/recovery-client.tsx +14 -15
  246. package/examples/playground/app/recovery/page.tsx +4 -4
  247. package/examples/playground/app/recovery/server.ts +23 -32
  248. package/examples/playground/drizzle.config.ts +14 -0
  249. package/examples/playground/next.config.ts +6 -1
  250. package/examples/playground/package.json +13 -1
  251. package/examples/playground/scripts/migrate.mjs +31 -0
  252. package/examples/playground/test/agent.ts +35 -0
  253. package/examples/playground/test/model.ts +149 -0
  254. package/examples/playground/test/serve.ts +184 -0
  255. package/examples/playground/test/server-only.ts +1 -0
  256. package/examples/playground/vercel.json +9 -0
  257. package/examples/playground/vitest.config.ts +27 -0
  258. package/examples/playground/vitest.drills.config.ts +32 -0
  259. package/package.json +7 -2
  260. package/sql/postgres/drizzle-schema.ts +206 -0
  261. package/sql/postgres/notifications.sql +24 -0
  262. package/sql/postgres/schema.sql +276 -0
  263. package/src/actor.ts +101 -71
  264. package/src/ai-agent.ts +154 -115
  265. package/src/ai-control-server.ts +8 -8
  266. package/src/ai-fx.ts +41 -9
  267. package/src/ai-harness.ts +28 -2
  268. package/src/ai-pi.ts +2 -2
  269. package/src/ai-server.ts +2 -2
  270. package/src/ai.ts +7 -7
  271. package/src/contract.ts +1 -1
  272. package/src/deterministic-id.ts +26 -3
  273. package/src/errors.ts +9 -6
  274. package/src/idempotent-replay.ts +0 -1
  275. package/src/index.ts +1 -1
  276. package/src/internal.ts +17 -6
  277. package/src/otel.ts +1 -1
  278. package/src/postgres-notifications.ts +0 -22
  279. package/src/postgres-resources.ts +6 -9
  280. package/src/postgres-schema.ts +121 -0
  281. package/src/push-envelope.ts +20 -0
  282. package/src/runtime-consumer.ts +10 -0
  283. package/src/scheduler-qstash.ts +12 -5
  284. package/src/scheduler-task.ts +2 -2
  285. package/src/scheduler-vercel.ts +10 -4
  286. package/src/server.browser.ts +1 -1
  287. package/src/server.ts +839 -238
  288. package/src/session-socket.ts +1 -1
  289. package/src/store-codec.ts +66 -0
  290. package/src/store-memory.ts +581 -255
  291. package/src/store-postgres.ts +1984 -943
  292. package/src/store-redis-core.ts +1624 -1152
  293. package/src/store-redis-notify.ts +2 -0
  294. package/src/store-sqlite.ts +891 -486
  295. package/src/store.ts +227 -97
  296. package/src/telemetry.ts +2 -1
  297. package/src/wire.ts +1 -0
  298. package/dist/ai-harness-BuuC-gJ3.js.map +0 -1
  299. package/dist/ai-harness-CV6rXbB3.d.ts.map +0 -1
  300. package/dist/ai-mMiWul1T.js.map +0 -1
  301. package/dist/contract-BYKASlCV.js.map +0 -1
  302. package/dist/idempotent-replay-JdgMQRRm.js +0 -21
  303. package/dist/idempotent-replay-JdgMQRRm.js.map +0 -1
  304. package/dist/initial-snapshot-Bx-c0pDP.d.ts.map +0 -1
  305. package/dist/internal-CscSY7K_.js.map +0 -1
  306. package/dist/server-D-Y4pqwu.js.map +0 -1
  307. package/dist/server-DYAATFuT.d.ts.map +0 -1
  308. package/dist/session-clear-C-QwTXV1.d.ts.map +0 -1
  309. package/dist/session-clear-DGI4jTQ5.js.map +0 -1
  310. package/dist/store-CyLUy86v.d.ts +0 -404
  311. package/dist/store-CyLUy86v.d.ts.map +0 -1
  312. package/dist/store-codec-DTG0Ftek.js +0 -10
  313. package/dist/store-codec-DTG0Ftek.js.map +0 -1
  314. package/dist/store-redis-notify-CM0AfLY0.js +0 -2544
  315. package/dist/store-redis-notify-CM0AfLY0.js.map +0 -1
  316. package/dist/telemetry-Cyfqo6Qt.d.ts.map +0 -1
  317. package/dist/wire-BKxH1vbP.js.map +0 -1
  318. package/docs/reference/05-adapters.mdx +0 -351
  319. package/docs/server/01-handlers.mdx +0 -200
  320. package/docs/server/05-production.mdx +0 -323
@@ -1,10 +1,23 @@
1
- import { i as A2Error, r as sessionClearedError, s as supersededAttemptError, t as CLEAR_EVENT_TYPE } from "./session-clear-DGI4jTQ5.js";
2
- import { _ as nullProtoRecord } from "./internal-CscSY7K_.js";
3
- import { t as idempotentReplay } from "./idempotent-replay-JdgMQRRm.js";
1
+ import { i as A2Error, r as sessionClearedError, s as supersededAttemptError, t as CLEAR_EVENT_TYPE } from "./session-clear-wHd5GCpZ.js";
2
+ import { _ as nullProtoRecord } from "./internal-Bc4PvfHk.js";
3
+ import { a as idempotentReplay, i as idleLogCursor } from "./store-codec-DwrWlgQx.js";
4
4
  import { n as SYSTEM_CLOCK, t as RANDOM_IDS } from "./store-N8PXxDAS.js";
5
5
  //#region src/store-memory.ts
6
6
  const RECLAIM_BATCH = 256;
7
7
  const headOf = (log) => log ? log.offset + log.rows.length : 0;
8
+ const freshDispatch = () => ({
9
+ firstClaimedAt: null,
10
+ lastClaimedAt: null,
11
+ attemptCount: 0,
12
+ claimHolder: null,
13
+ claimExpiresAt: null,
14
+ failureCount: 0,
15
+ lastFailedAt: null,
16
+ lastFailedAttempt: null,
17
+ lastError: null,
18
+ failedAt: null
19
+ });
20
+ const date = (value) => value ? new Date(value) : null;
8
21
  const toStored = (row) => ({
9
22
  id: row.id,
10
23
  type: row.type,
@@ -12,21 +25,7 @@ const toStored = (row) => ({
12
25
  index: row.index,
13
26
  sessionId: row.sessionId,
14
27
  createdAt: new Date(row.createdAt),
15
- cause: row.cause ? { ...row.cause } : null,
16
- lane: row.lane,
17
- processedAt: row.processedAt ? new Date(row.processedAt) : null,
18
- processedByAttempt: row.processedByAttempt,
19
- returnedEventIds: row.returnedEventIds ? [...row.returnedEventIds] : null,
20
- firstClaimedAt: row.firstClaimedAt ? new Date(row.firstClaimedAt) : null,
21
- lastClaimedAt: row.lastClaimedAt ? new Date(row.lastClaimedAt) : null,
22
- attemptCount: row.attemptCount,
23
- claimHolder: row.claimHolder,
24
- claimExpiresAt: row.claimExpiresAt ? new Date(row.claimExpiresAt) : null,
25
- failureCount: row.failureCount,
26
- lastFailedAt: row.lastFailedAt ? new Date(row.lastFailedAt) : null,
27
- lastFailedAttempt: row.lastFailedAttempt,
28
- lastError: row.lastError,
29
- failedAt: row.failedAt ? new Date(row.failedAt) : null
28
+ cause: row.cause ? { ...row.cause } : null
30
29
  });
31
30
  const toEvent = (row) => ({
32
31
  id: row.id,
@@ -36,13 +35,63 @@ const toEvent = (row) => ({
36
35
  sessionId: row.sessionId,
37
36
  createdAt: new Date(row.createdAt)
38
37
  });
38
+ const toHead = (cursor, row) => ({
39
+ ...toStored(row),
40
+ consumer: cursor.consumer,
41
+ lane: cursor.lane,
42
+ firstClaimedAt: date(cursor.dispatch.firstClaimedAt),
43
+ lastClaimedAt: date(cursor.dispatch.lastClaimedAt),
44
+ attemptCount: cursor.dispatch.attemptCount,
45
+ claimHolder: cursor.dispatch.claimHolder,
46
+ claimExpiresAt: date(cursor.dispatch.claimExpiresAt),
47
+ failureCount: cursor.dispatch.failureCount,
48
+ lastFailedAt: date(cursor.dispatch.lastFailedAt),
49
+ lastFailedAttempt: cursor.dispatch.lastFailedAttempt,
50
+ lastError: cursor.dispatch.lastError,
51
+ failedAt: date(cursor.dispatch.failedAt)
52
+ });
53
+ const toCursor = (cursor) => ({
54
+ consumer: cursor.consumer,
55
+ lane: cursor.lane,
56
+ position: cursor.position,
57
+ head: cursor.head,
58
+ firstClaimedAt: date(cursor.dispatch.firstClaimedAt),
59
+ lastClaimedAt: date(cursor.dispatch.lastClaimedAt),
60
+ attemptCount: cursor.dispatch.attemptCount,
61
+ claimHolder: cursor.dispatch.claimHolder,
62
+ claimExpiresAt: date(cursor.dispatch.claimExpiresAt),
63
+ failureCount: cursor.dispatch.failureCount,
64
+ lastFailedAt: date(cursor.dispatch.lastFailedAt),
65
+ lastFailedAttempt: cursor.dispatch.lastFailedAttempt,
66
+ lastError: cursor.dispatch.lastError,
67
+ failedAt: date(cursor.dispatch.failedAt)
68
+ });
69
+ const compareText = (a, b) => a < b ? -1 : a > b ? 1 : 0;
70
+ /** Consumer, then lane, with concurrent cursors last by head. */
71
+ const byCursor = (a, b) => compareText(a.consumer, b.consumer) || (a.lane === null ? b.lane === null ? (a.head ?? 0) - (b.head ?? 0) : 1 : b.lane === null ? -1 : compareText(a.lane, b.lane));
72
+ const cursorKey = (consumer, lane, index) => lane === null ? `${consumer}\u0000#${index}` : `${consumer}\u0000L${lane}`;
73
+ const takes = (types, type) => type !== "a2.cleared" && (types === null || types.includes(type));
74
+ const byHead = (a, b) => a.index - b.index || compareText(a.consumer, b.consumer);
39
75
  const snapshotKey = (sessionId, reducerName) => `${sessionId}\u0000${reducerName}`;
40
- const eventPinKey = (sessionId, index) => `${sessionId}\u0000${index}`;
76
+ const headPinKey = (sessionId, ref) => `${sessionId}\u0000${cursorKey(ref.consumer, ref.lane, ref.index)}\u0000${ref.index}`;
77
+ /** Whether a cursor belongs to a consumer the caller serves; omitted, every one. */
78
+ const servedBy = (consumers) => {
79
+ if (!consumers) return () => true;
80
+ const names = new Set(consumers.map((consumer) => consumer.name));
81
+ return (cursor) => names.has(cursor.consumer);
82
+ };
83
+ /** A cursor's current head. */
84
+ const refOf = (cursor) => ({
85
+ consumer: cursor.consumer,
86
+ lane: cursor.lane,
87
+ index: cursor.head
88
+ });
41
89
  function memory(options = {}) {
42
90
  const clock = options.clock ?? SYSTEM_CLOCK;
43
91
  const generateId = options.ids ?? RANDOM_IDS;
44
92
  const sessions = /* @__PURE__ */ new Map();
45
- const dispatch = /* @__PURE__ */ new Map();
93
+ /** Session id → cursor key → cursor. */
94
+ const cursors = /* @__PURE__ */ new Map();
46
95
  /** Global unique index on event ids, like the SQL schema's. */
47
96
  const byEventId = /* @__PURE__ */ new Map();
48
97
  /** Keyed by `${sessionId}\u0000${reducerName}` — a pure cache. */
@@ -81,9 +130,18 @@ function memory(options = {}) {
81
130
  const row = byEventId.get(id);
82
131
  return row?.sessionId === sessionId && row.index < clearIndexOf(sessionId) ? void 0 : row;
83
132
  };
133
+ const cursorsOf = (sessionId) => {
134
+ let map = cursors.get(sessionId);
135
+ if (!map) {
136
+ map = /* @__PURE__ */ new Map();
137
+ cursors.set(sessionId, map);
138
+ }
139
+ return map;
140
+ };
141
+ const cursorAt = (sessionId, ref) => cursors.get(sessionId)?.get(cursorKey(ref.consumer, ref.lane, ref.index));
84
142
  const checkpointKey = (sessionId, reducerName, index) => `${snapshotKey(sessionId, reducerName)}\u0000${index}`;
85
- const releaseSnapshotPins = (sessionId, eventIndex) => {
86
- const pinKey = eventPinKey(sessionId, eventIndex);
143
+ const releaseSnapshotPins = (sessionId, ref) => {
144
+ const pinKey = headPinKey(sessionId, ref);
87
145
  const checkpoints = snapshotPins.get(pinKey);
88
146
  if (!checkpoints) return;
89
147
  for (const key of checkpoints) {
@@ -95,87 +153,62 @@ function memory(options = {}) {
95
153
  }
96
154
  snapshotPins.delete(pinKey);
97
155
  };
98
- const dispatchOf = (sessionId) => {
99
- let state = dispatch.get(sessionId);
100
- if (!state) {
101
- state = {
102
- ready: /* @__PURE__ */ new Map(),
103
- lanes: /* @__PURE__ */ new Map()
104
- };
105
- dispatch.set(sessionId, state);
106
- }
107
- return state;
108
- };
109
- const enqueue = (sessionId, row) => {
110
- if (row.processedAt !== null) return;
111
- const state = dispatchOf(sessionId);
112
- if (row.lane === null) {
113
- state.ready.set(row.index, row);
114
- return;
115
- }
116
- let queue = state.lanes.get(row.lane);
117
- if (!queue) {
118
- queue = {
119
- rows: [],
120
- head: 0
121
- };
122
- state.lanes.set(row.lane, queue);
123
- }
124
- queue.rows.push(row);
125
- if (queue.rows.length === 1) state.ready.set(row.index, row);
126
- };
127
- const settle = (sessionId, row) => {
128
- const state = dispatchOf(sessionId);
129
- state.ready.delete(row.index);
130
- if (row.lane === null) return;
131
- const queue = state.lanes.get(row.lane);
132
- if (queue.rows[queue.head] !== row) return;
133
- queue.head += 1;
134
- if (queue.head === queue.rows.length) {
135
- state.lanes.delete(row.lane);
136
- return;
156
+ /** The next event after `after` that a log cursor takes, or null. */
157
+ const nextLogHead = (sessionId, after, types) => {
158
+ const log = sessions.get(sessionId);
159
+ if (!log) return null;
160
+ for (let offset = Math.max(0, after - log.offset); offset < log.rows.length; offset += 1) {
161
+ const row = log.rows[offset];
162
+ if (takes(types, row.type)) return row.index;
137
163
  }
138
- const next = queue.rows[queue.head];
139
- if (next.failedAt === null) state.ready.set(next.index, next);
164
+ return null;
140
165
  };
141
- const find = (sessionId, index) => {
142
- const row = rowAt(sessionId, index);
143
- if (!row) throw new TypeError(`no event at index ${index} in session '${sessionId}'`);
144
- return row;
166
+ /** Moves a cursor past its head: the next head starts with fresh bookkeeping. */
167
+ const advance = (sessionId, cursor) => {
168
+ const done = cursor.head;
169
+ releaseSnapshotPins(sessionId, refOf(cursor));
170
+ cursor.position = done;
171
+ cursor.dispatch = freshDispatch();
172
+ if (cursor.kind === "event") cursor.head = null;
173
+ else if (cursor.kind === "lane") {
174
+ cursor.queued += 1;
175
+ cursor.head = cursor.queue[cursor.queued] ?? null;
176
+ } else cursor.head = nextLogHead(sessionId, done, cursor.types);
145
177
  };
146
- const hasPending = (sessionId) => {
147
- const log = sessions.get(sessionId);
148
- return (log?.rows ?? []).some((row) => row.index > log.clearIndex && row.processedAt === null);
178
+ const hasPending = (sessionId, consumers) => {
179
+ const served = servedBy(consumers);
180
+ for (const cursor of cursors.get(sessionId)?.values() ?? []) if (cursor.head !== null && cursor.dispatch.failedAt === null && served(cursor)) return true;
181
+ return false;
149
182
  };
150
- const appendResult = (sessionId, events) => ({
151
- events,
152
- hasPending: hasPending(sessionId)
153
- });
154
- /** The attempt-currency fence shared by handler appends and clears. */
183
+ /** The attempt-currency fence shared by consumer appends and clears. */
155
184
  const assertCurrentCause = (sessionId, cause) => {
156
185
  if (cause.index <= clearIndexOf(sessionId)) throw supersededAttemptError(sessionId, cause);
157
- const parent = find(sessionId, cause.index);
158
- if (parent.attemptCount !== cause.attempt || parent.failedAt !== null) throw supersededAttemptError(sessionId, cause);
186
+ const cursor = cursorAt(sessionId, cause);
187
+ if (!cursor || cursor.head !== cause.index || cursor.dispatch.attemptCount !== cause.attempt || cursor.dispatch.failedAt !== null) throw supersededAttemptError(sessionId, cause);
159
188
  };
160
- const claim = ({ sessionId, row, holder, now, expiresAt, maxFailures }) => {
161
- if (row.claimHolder !== null) {
162
- row.failureCount += 1;
163
- if (maxFailures !== void 0 && row.failureCount >= maxFailures) {
164
- row.attemptCount += 1;
165
- row.failedAt = new Date(now);
166
- row.claimHolder = null;
167
- row.claimExpiresAt = null;
168
- dispatchOf(sessionId).ready.delete(row.index);
169
- releaseSnapshotPins(sessionId, row.index);
170
- return toStored(row);
189
+ const deadLetter = (sessionId, cursor, now) => {
190
+ cursor.dispatch.failedAt = new Date(now);
191
+ releaseSnapshotPins(sessionId, refOf(cursor));
192
+ };
193
+ const claim = ({ sessionId, cursor, holder, now, expiresAt, maxFailures }) => {
194
+ const dispatch = cursor.dispatch;
195
+ const row = rowAt(sessionId, cursor.head);
196
+ if (dispatch.claimHolder !== null) {
197
+ dispatch.failureCount += 1;
198
+ if (maxFailures !== void 0 && dispatch.failureCount >= maxFailures) {
199
+ dispatch.attemptCount += 1;
200
+ dispatch.claimHolder = null;
201
+ dispatch.claimExpiresAt = null;
202
+ deadLetter(sessionId, cursor, now);
203
+ return toHead(cursor, row);
171
204
  }
172
205
  }
173
- row.attemptCount += 1;
174
- row.firstClaimedAt ??= new Date(now);
175
- row.lastClaimedAt = new Date(now);
176
- row.claimHolder = holder;
177
- row.claimExpiresAt = new Date(expiresAt);
178
- return toStored(row);
206
+ dispatch.attemptCount += 1;
207
+ dispatch.firstClaimedAt ??= new Date(now);
208
+ dispatch.lastClaimedAt = new Date(now);
209
+ dispatch.claimHolder = holder;
210
+ dispatch.claimExpiresAt = new Date(expiresAt);
211
+ return toHead(cursor, row);
179
212
  };
180
213
  const checkBatchIds = (sessionId, events) => {
181
214
  const supplied = events.filter((candidate) => candidate.id !== void 0);
@@ -185,7 +218,86 @@ function memory(options = {}) {
185
218
  if (foreign) throw new A2Error("PARTIAL_DUPLICATE_BATCH", `event id '${foreign.id}' already exists in another session`);
186
219
  return existing;
187
220
  };
188
- const insert = (sessionId, events, now, cause) => {
221
+ /** Records the types a declared log consumer takes on its cursor. */
222
+ const refreshLogTypes = (sessionId, consumers) => {
223
+ const map = cursors.get(sessionId);
224
+ if (!map) return;
225
+ for (const consumer of consumers ?? []) {
226
+ if (!consumer.log) continue;
227
+ const cursor = map.get(cursorKey(consumer.name, consumer.log.lane, 0));
228
+ if (cursor?.kind === "log") cursor.types = consumer.log.types;
229
+ }
230
+ };
231
+ /** Starts the cursor work an inserted batch carries. */
232
+ const enqueue = (sessionId, rows, events, base, consumers) => {
233
+ const map = cursorsOf(sessionId);
234
+ rows.forEach((row, offset) => {
235
+ for (const target of events[offset].targets ?? []) {
236
+ const key = cursorKey(target.consumer, target.lane, row.index);
237
+ if (target.lane === null) {
238
+ map.set(key, {
239
+ consumer: target.consumer,
240
+ lane: null,
241
+ kind: "event",
242
+ position: row.index - 1,
243
+ head: row.index,
244
+ dispatch: freshDispatch(),
245
+ queue: [],
246
+ queued: 0,
247
+ completions: /* @__PURE__ */ new Map(),
248
+ types: null
249
+ });
250
+ continue;
251
+ }
252
+ let cursor = map.get(key);
253
+ if (!cursor) {
254
+ cursor = {
255
+ consumer: target.consumer,
256
+ lane: target.lane,
257
+ kind: "lane",
258
+ position: clearIndexOf(sessionId),
259
+ head: null,
260
+ dispatch: freshDispatch(),
261
+ queue: [],
262
+ queued: 0,
263
+ completions: /* @__PURE__ */ new Map(),
264
+ types: null
265
+ };
266
+ map.set(key, cursor);
267
+ }
268
+ cursor.queue.push(row.index);
269
+ cursor.head ??= row.index;
270
+ }
271
+ });
272
+ const clearIndex = clearIndexOf(sessionId);
273
+ for (const consumer of consumers ?? []) {
274
+ const log = consumer.log;
275
+ if (!log) continue;
276
+ const first = rows.find((row) => takes(log.types, row.type));
277
+ if (!first) continue;
278
+ const key = cursorKey(consumer.name, log.lane, 0);
279
+ const cursor = map.get(key);
280
+ if (!cursor) {
281
+ const position = log.backfill ? clearIndex : base;
282
+ map.set(key, {
283
+ consumer: consumer.name,
284
+ lane: log.lane,
285
+ kind: "log",
286
+ position,
287
+ head: log.backfill ? nextLogHead(sessionId, position, log.types) : first.index,
288
+ dispatch: freshDispatch(),
289
+ queue: [],
290
+ queued: 0,
291
+ completions: /* @__PURE__ */ new Map(),
292
+ types: log.types
293
+ });
294
+ } else {
295
+ cursor.types = log.types;
296
+ cursor.head ??= nextLogHead(sessionId, cursor.position, log.types);
297
+ }
298
+ }
299
+ };
300
+ const insert = (sessionId, events, now, consumers, cause) => {
189
301
  const log = logOf(sessionId);
190
302
  const base = headOf(log);
191
303
  const inserted = events.map((candidate, offset) => ({
@@ -195,28 +307,14 @@ function memory(options = {}) {
195
307
  index: base + 1 + offset,
196
308
  sessionId,
197
309
  createdAt: new Date(now),
198
- cause: cause ? { ...cause } : candidate.cause ? { ...candidate.cause } : null,
199
- lane: candidate.lane ?? null,
200
- processedAt: candidate.settled ? new Date(now) : null,
201
- processedByAttempt: null,
202
- returnedEventIds: null,
203
- firstClaimedAt: null,
204
- lastClaimedAt: null,
205
- attemptCount: 0,
206
- claimHolder: null,
207
- claimExpiresAt: null,
208
- failureCount: 0,
209
- lastFailedAt: null,
210
- lastFailedAttempt: null,
211
- lastError: null,
212
- failedAt: null
310
+ cause: cause ? { ...cause } : candidate.cause ? { ...candidate.cause } : null
213
311
  }));
214
312
  for (const row of inserted) if (ownerOf(sessionId, row.id)) throw new A2Error("PARTIAL_DUPLICATE_BATCH", `event id '${row.id}' already exists`);
215
313
  for (const row of inserted) {
216
314
  log.rows.push(row);
217
315
  byEventId.set(row.id, row);
218
- enqueue(sessionId, row);
219
316
  }
317
+ enqueue(sessionId, inserted, events, base, consumers);
220
318
  return inserted;
221
319
  };
222
320
  /** Every cached fold of this session below its clear, head or historical. */
@@ -256,21 +354,35 @@ function memory(options = {}) {
256
354
  ...clearIndex > 0 ? { clearIndex } : {}
257
355
  };
258
356
  };
357
+ /** The dead-lettered head a redrive names, or undefined. */
358
+ const deadHeadOf = (sessionId, consumer, index) => {
359
+ if (index <= clearIndexOf(sessionId)) return void 0;
360
+ for (const cursor of cursors.get(sessionId)?.values() ?? []) if (cursor.consumer === consumer && cursor.head === index && cursor.dispatch.failedAt !== null) return cursor;
361
+ };
259
362
  return {
260
363
  async append(sessionId, events, appendOptions) {
261
- if (events.length === 0) return appendResult(sessionId, []);
364
+ const consumers = appendOptions?.consumers;
365
+ if (events.length === 0) return {
366
+ events: [],
367
+ hasPending: hasPending(sessionId, consumers)
368
+ };
262
369
  const existing = checkBatchIds(sessionId, events);
263
370
  if (existing.length > 0) {
264
- if (existing.length === events.length) return appendResult(sessionId, idempotentReplay(events, existing.map(toStored)));
371
+ if (existing.length === events.length) return {
372
+ events: idempotentReplay(events, existing.map(toStored)),
373
+ hasPending: hasPending(sessionId, consumers)
374
+ };
265
375
  throw new A2Error("PARTIAL_DUPLICATE_BATCH", `batch mixes ${existing.length} already-appended and ${events.length - existing.length} fresh events`);
266
376
  }
267
377
  const originIndex = appendOptions?.originIndex;
268
378
  if (originIndex !== void 0 && originIndex <= clearIndexOf(sessionId)) throw sessionClearedError(`scheduled work from event ${originIndex} was cleared in session '${sessionId}'`);
269
379
  for (const event of events) if (event.cause) assertCurrentCause(sessionId, event.cause);
270
- const now = clock.now();
271
- const inserted = insert(sessionId, events, now);
380
+ const inserted = insert(sessionId, events, clock.now(), consumers);
272
381
  notifyStreams(sessionId, inserted);
273
- return appendResult(sessionId, inserted.map(toStored));
382
+ return {
383
+ events: inserted.map(toStored),
384
+ hasPending: hasPending(sessionId, consumers)
385
+ };
274
386
  },
275
387
  async clear({ sessionId, ifLastIndex, cause }) {
276
388
  if (cause) assertCurrentCause(sessionId, cause);
@@ -294,11 +406,21 @@ function memory(options = {}) {
294
406
  };
295
407
  const [marker] = insert(sessionId, [{
296
408
  type: CLEAR_EVENT_TYPE,
297
- payload: null,
298
- settled: true
299
- }], clock.now());
409
+ payload: null
410
+ }], clock.now(), void 0);
300
411
  logOf(sessionId).clearIndex = marker.index;
301
- dispatch.delete(sessionId);
412
+ const map = cursors.get(sessionId);
413
+ for (const [key, cursor] of map ?? []) {
414
+ if (cursor.head !== null) releaseSnapshotPins(sessionId, refOf(cursor));
415
+ if (cursor.kind !== "log") {
416
+ map.delete(key);
417
+ continue;
418
+ }
419
+ cursor.position = marker.index;
420
+ cursor.head = null;
421
+ cursor.dispatch = freshDispatch();
422
+ cursor.completions.clear();
423
+ }
302
424
  notifyStreams(sessionId, [marker]);
303
425
  return {
304
426
  cleared: true,
@@ -314,10 +436,7 @@ function memory(options = {}) {
314
436
  };
315
437
  const removed = log.rows.splice(0, Math.min(RECLAIM_BATCH, log.clearIndex - 1 - log.offset));
316
438
  log.offset += removed.length;
317
- for (const row of removed) {
318
- if (byEventId.get(row.id) === row) byEventId.delete(row.id);
319
- releaseSnapshotPins(sessionId, row.index);
320
- }
439
+ for (const row of removed) if (byEventId.get(row.id) === row) byEventId.delete(row.id);
321
440
  const done = log.offset === log.clearIndex - 1;
322
441
  if (done) dropCheckpointsBelow(sessionId, log.clearIndex);
323
442
  return {
@@ -325,6 +444,43 @@ function memory(options = {}) {
325
444
  done
326
445
  };
327
446
  },
447
+ async wakeLog({ sessionId, consumer }) {
448
+ const { log } = consumer;
449
+ const key = cursorKey(consumer.name, log.lane, 0);
450
+ const head = headOf(sessions.get(sessionId));
451
+ let cursor = cursors.get(sessionId)?.get(key);
452
+ if (!cursor) {
453
+ const marker = clearIndexOf(sessionId);
454
+ if (head === marker) return {
455
+ cursor: idleLogCursor(consumer.name, log.lane, marker),
456
+ head
457
+ };
458
+ const position = log.backfill ? marker : head;
459
+ cursor = {
460
+ consumer: consumer.name,
461
+ lane: log.lane,
462
+ kind: "log",
463
+ position,
464
+ head: log.backfill ? nextLogHead(sessionId, position, log.types) : null,
465
+ dispatch: freshDispatch(),
466
+ queue: [],
467
+ queued: 0,
468
+ completions: /* @__PURE__ */ new Map(),
469
+ types: log.types
470
+ };
471
+ cursorsOf(sessionId).set(key, cursor);
472
+ } else {
473
+ cursor.types = log.types;
474
+ cursor.head ??= nextLogHead(sessionId, cursor.position, log.types);
475
+ }
476
+ return {
477
+ cursor: toCursor(cursor),
478
+ head
479
+ };
480
+ },
481
+ async cursors(sessionId) {
482
+ return [...cursors.get(sessionId)?.values() ?? []].filter((cursor) => cursor.kind !== "event" || cursor.head !== null).map(toCursor).toSorted(byCursor);
483
+ },
328
484
  async read(sessionId, opts) {
329
485
  const log = sessions.get(sessionId);
330
486
  if (!log) return [];
@@ -334,41 +490,44 @@ function memory(options = {}) {
334
490
  if (through <= after) return [];
335
491
  return log.rows.slice(after - log.offset, through - log.offset).map(toStored);
336
492
  },
337
- async claimAvailable({ sessionId, holder, ttlMs, expiresAtMs, excludeIndexes, maxFailures }) {
493
+ async claimAvailable({ sessionId, holder, ttlMs, expiresAtMs, exclude, consumers, maxFailures, through }) {
494
+ refreshLogTypes(sessionId, consumers);
338
495
  const claimedAt = clock.now();
339
496
  const now = claimedAt.getTime();
340
497
  const expiresAt = new Date(expiresAtMs ?? now + ttlMs);
341
- const excluded = new Set(excludeIndexes ?? []);
498
+ const excluded = new Set((exclude ?? []).map((ref) => cursorKey(ref.consumer, ref.lane, ref.index)));
499
+ const served = servedBy(consumers);
342
500
  const eligible = [];
343
501
  let retryAt = null;
344
- for (const row of dispatchOf(sessionId).ready.values()) {
345
- if (row.failedAt !== null || excluded.has(row.index)) continue;
346
- if (row.claimExpiresAt && row.claimExpiresAt.getTime() > now) {
347
- if (!retryAt || row.claimExpiresAt < retryAt) retryAt = new Date(row.claimExpiresAt);
502
+ for (const [key, cursor] of cursors.get(sessionId) ?? []) {
503
+ if (cursor.head === null || through !== void 0 && cursor.head > through || cursor.dispatch.failedAt !== null || !served(cursor) || excluded.has(key)) continue;
504
+ const expiry = cursor.dispatch.claimExpiresAt;
505
+ if (expiry && expiry.getTime() > now) {
506
+ if (!retryAt || expiry < retryAt) retryAt = new Date(expiry);
348
507
  continue;
349
508
  }
350
- eligible.push(row);
509
+ eligible.push(cursor);
351
510
  }
352
511
  if (eligible.length === 0) return retryAt ? {
353
512
  outcome: "busy",
354
513
  retryAt
355
514
  } : { outcome: "settled" };
356
- eligible.sort((a, b) => a.index - b.index);
357
515
  const clearIndex = clearIndexOf(sessionId);
358
516
  return {
359
517
  outcome: "claimed",
360
- events: eligible.map((row) => {
361
- const claimed = claim({
518
+ events: eligible.map((cursor) => {
519
+ const head = claim({
362
520
  sessionId,
363
- row,
521
+ cursor,
364
522
  holder,
365
523
  now: claimedAt,
366
524
  expiresAt,
367
525
  maxFailures
368
526
  });
369
- if (clearIndex > 0) claimed.clearIndex = clearIndex;
370
- return claimed;
371
- })
527
+ if (clearIndex > 0) head.clearIndex = clearIndex;
528
+ return head;
529
+ }).toSorted(byHead),
530
+ head: headOf(sessions.get(sessionId))
372
531
  };
373
532
  },
374
533
  async renewClaims({ sessionId, holder, claims, ttlMs, expiresAtMs }) {
@@ -377,20 +536,23 @@ function memory(options = {}) {
377
536
  const clearIndex = clearIndexOf(sessionId);
378
537
  const renewed = [];
379
538
  const superseded = [];
380
- for (const { index, attempt } of claims.toSorted((a, b) => a.index - b.index)) {
381
- if (index <= clearIndex) {
382
- superseded.push(index);
539
+ const ordered = claims.toSorted((a, b) => a.index - b.index || compareText(a.consumer, b.consumer));
540
+ for (const { attempt, ...ref } of ordered) {
541
+ if (ref.index <= clearIndex) {
542
+ superseded.push(ref);
383
543
  continue;
384
544
  }
385
- const row = rowAt(sessionId, index);
386
- if (!row) continue;
387
- if (row.attemptCount > attempt) {
388
- superseded.push(row.index);
545
+ const cursor = cursorAt(sessionId, ref);
546
+ if (!cursor) continue;
547
+ const dispatch = cursor.dispatch;
548
+ if (cursor.head !== ref.index && cursor.position < ref.index) continue;
549
+ if (cursor.head !== ref.index || dispatch.attemptCount > attempt) {
550
+ superseded.push(ref);
389
551
  continue;
390
552
  }
391
- if (row.processedAt === null && row.failedAt === null && row.claimHolder === holder && row.claimExpiresAt !== null && row.claimExpiresAt > now) {
392
- row.claimExpiresAt = new Date(expiresAt);
393
- renewed.push(row.index);
553
+ if (dispatch.failedAt === null && dispatch.claimHolder === holder && dispatch.claimExpiresAt !== null && dispatch.claimExpiresAt > now) {
554
+ dispatch.claimExpiresAt = new Date(expiresAt);
555
+ renewed.push(ref);
394
556
  }
395
557
  }
396
558
  return {
@@ -398,103 +560,124 @@ function memory(options = {}) {
398
560
  superseded
399
561
  };
400
562
  },
401
- async completeAttempt({ sessionId, index, attempt, events }) {
563
+ async completeAttempt({ sessionId, consumer, lane, index, attempt, events, consumers }) {
402
564
  if (index <= clearIndexOf(sessionId)) return { outcome: "superseded" };
403
- const parent = find(sessionId, index);
404
- const cause = {
405
- index,
406
- attempt
407
- };
408
- if (parent.processedAt !== null) {
409
- if (parent.processedByAttempt !== attempt) return { outcome: "superseded" };
565
+ const cursor = cursorAt(sessionId, {
566
+ consumer,
567
+ lane,
568
+ index
569
+ });
570
+ if (!cursor) {
571
+ if (!rowAt(sessionId, index)) throw new TypeError(`no event at index ${index} in session '${sessionId}'`);
572
+ return { outcome: "superseded" };
573
+ }
574
+ refreshLogTypes(sessionId, consumers);
575
+ if (cursor.head !== index) {
576
+ const completion = cursor.completions.get(index);
577
+ if (!completion || completion.attempt !== attempt) return { outcome: "superseded" };
410
578
  const requestedIds = events.map((event) => event.id);
411
- if (parent.returnedEventIds === null || parent.returnedEventIds.length !== requestedIds.length || parent.returnedEventIds.some((id, offset) => id !== requestedIds[offset])) throw new A2Error("PARTIAL_DUPLICATE_BATCH", "completed attempt does not match the returned event batch");
579
+ if (completion.returnedEventIds.length !== requestedIds.length || completion.returnedEventIds.some((id, offset) => id !== requestedIds[offset])) throw new A2Error("PARTIAL_DUPLICATE_BATCH", "completed attempt does not match the returned event batch");
412
580
  const existing = checkBatchIds(sessionId, events);
413
- if (existing.length !== events.length || existing.some((row) => row.cause?.index !== index || row.cause.attempt !== attempt)) throw new A2Error("PARTIAL_DUPLICATE_BATCH", "completed attempt does not match the returned event batch");
581
+ if (existing.length !== events.length || existing.some((row) => row.cause?.consumer !== consumer || row.cause.lane !== lane || row.cause.index !== index || row.cause.attempt !== attempt)) throw new A2Error("PARTIAL_DUPLICATE_BATCH", "completed attempt does not match the returned event batch");
414
582
  return {
415
583
  outcome: "completed",
416
- events: idempotentReplay(events, existing.map(toStored))
584
+ events: events.length === 0 ? [] : idempotentReplay(events, existing.map(toStored))
417
585
  };
418
586
  }
419
- if (parent.attemptCount !== attempt || parent.failedAt !== null || parent.claimHolder === null) return { outcome: "superseded" };
587
+ const dispatch = cursor.dispatch;
588
+ if (dispatch.attemptCount !== attempt || dispatch.failedAt !== null || dispatch.claimHolder === null) return { outcome: "superseded" };
420
589
  if (checkBatchIds(sessionId, events).length > 0) throw new A2Error("PARTIAL_DUPLICATE_BATCH", "returned event batch contains already-appended ids");
421
- const now = clock.now();
422
- const inserted = insert(sessionId, events, now, cause);
423
- parent.processedAt = new Date(now);
424
- parent.processedByAttempt = attempt;
425
- parent.returnedEventIds = events.map((event) => event.id);
426
- parent.claimHolder = null;
427
- parent.claimExpiresAt = null;
428
- settle(sessionId, parent);
429
- releaseSnapshotPins(sessionId, index);
590
+ const inserted = insert(sessionId, events, clock.now(), consumers, {
591
+ consumer,
592
+ lane,
593
+ index,
594
+ attempt
595
+ });
596
+ if (cursor.kind === "log") cursor.completions.clear();
597
+ cursor.completions.set(index, {
598
+ attempt,
599
+ returnedEventIds: events.map((event) => event.id)
600
+ });
601
+ advance(sessionId, cursor);
430
602
  notifyStreams(sessionId, inserted);
431
603
  return {
432
604
  outcome: "completed",
433
605
  events: inserted.map(toStored)
434
606
  };
435
607
  },
436
- async failAttempt({ sessionId, index, attempt, error, maxFailures, retryAfterMs }) {
608
+ async failAttempt({ sessionId, consumer, lane, index, attempt, error, maxFailures, retryAfterMs }) {
437
609
  if (index <= clearIndexOf(sessionId)) return {
438
610
  outcome: "superseded",
439
611
  failureCount: 0
440
612
  };
441
- const row = find(sessionId, index);
442
- if (row.processedAt || row.attemptCount !== attempt || row.claimHolder === null && row.lastFailedAttempt !== attempt) return {
613
+ const cursor = cursorAt(sessionId, {
614
+ consumer,
615
+ lane,
616
+ index
617
+ });
618
+ if (!cursor) {
619
+ if (!rowAt(sessionId, index)) throw new TypeError(`no event at index ${index} in session '${sessionId}'`);
620
+ return {
621
+ outcome: "superseded",
622
+ failureCount: 0
623
+ };
624
+ }
625
+ const dispatch = cursor.dispatch;
626
+ if (cursor.head !== index || dispatch.attemptCount !== attempt || dispatch.claimHolder === null && dispatch.lastFailedAttempt !== attempt) return {
443
627
  outcome: "superseded",
444
- failureCount: row.failureCount
628
+ failureCount: cursor.head === index ? dispatch.failureCount : 0
445
629
  };
446
- if (row.lastFailedAttempt === attempt && row.claimHolder === null) return {
447
- outcome: row.failedAt ? "dead_lettered" : "failed",
448
- failureCount: row.failureCount
630
+ if (dispatch.lastFailedAttempt === attempt && dispatch.claimHolder === null) return {
631
+ outcome: dispatch.failedAt ? "dead_lettered" : "failed",
632
+ failureCount: dispatch.failureCount
449
633
  };
450
- if (row.failedAt) return {
634
+ if (dispatch.failedAt) return {
451
635
  outcome: "dead_lettered",
452
- failureCount: row.failureCount
636
+ failureCount: dispatch.failureCount
453
637
  };
454
- row.failureCount += 1;
455
- row.lastError = error;
638
+ dispatch.failureCount += 1;
639
+ dispatch.lastError = error;
456
640
  const failedAt = clock.now();
457
- row.lastFailedAt = new Date(failedAt);
458
- row.lastFailedAttempt = attempt;
459
- row.claimHolder = null;
460
- row.claimExpiresAt = retryAfterMs === void 0 || row.failureCount >= maxFailures ? null : new Date(failedAt.getTime() + retryAfterMs);
461
- if (row.failureCount >= maxFailures) {
462
- row.failedAt = new Date(failedAt);
463
- dispatchOf(sessionId).ready.delete(row.index);
464
- releaseSnapshotPins(sessionId, index);
641
+ dispatch.lastFailedAt = new Date(failedAt);
642
+ dispatch.lastFailedAttempt = attempt;
643
+ dispatch.claimHolder = null;
644
+ dispatch.claimExpiresAt = retryAfterMs === void 0 || dispatch.failureCount >= maxFailures ? null : new Date(failedAt.getTime() + retryAfterMs);
645
+ if (dispatch.failureCount >= maxFailures) {
646
+ deadLetter(sessionId, cursor, failedAt);
465
647
  return {
466
648
  outcome: "dead_lettered",
467
- failureCount: row.failureCount
649
+ failureCount: dispatch.failureCount
468
650
  };
469
651
  }
470
652
  return {
471
653
  outcome: "failed",
472
- failureCount: row.failureCount
654
+ failureCount: dispatch.failureCount
473
655
  };
474
656
  },
475
657
  async deadLetters(sessionId) {
476
- const log = sessions.get(sessionId);
477
- return (log?.rows ?? []).filter((row) => row.index > log.clearIndex && row.processedAt === null && row.failedAt !== null).map(toStored);
658
+ const clearIndex = clearIndexOf(sessionId);
659
+ const dead = [];
660
+ for (const cursor of cursors.get(sessionId)?.values() ?? []) {
661
+ if (cursor.head === null || cursor.head <= clearIndex || cursor.dispatch.failedAt === null) continue;
662
+ dead.push(toHead(cursor, rowAt(sessionId, cursor.head)));
663
+ }
664
+ return dead.toSorted(byHead);
478
665
  },
479
- async redrive({ sessionId, index, action }) {
480
- const row = index > clearIndexOf(sessionId) ? rowAt(sessionId, index) : void 0;
481
- if (!row || row.processedAt !== null || row.failedAt === null) return {
666
+ async redrive({ sessionId, consumer, index, action, consumers }) {
667
+ const cursor = deadHeadOf(sessionId, consumer, index);
668
+ if (!cursor) return {
482
669
  redriven: false,
483
- hasPending: hasPending(sessionId)
670
+ hasPending: hasPending(sessionId, consumers)
484
671
  };
485
- if (action === "skip") {
486
- row.processedAt = clock.now();
487
- settle(sessionId, row);
488
- } else {
489
- row.failedAt = null;
490
- row.failureCount = 0;
491
- const state = dispatchOf(sessionId);
492
- const queue = row.lane === null ? void 0 : state.lanes.get(row.lane);
493
- if (row.lane === null || queue?.rows[queue.head] === row) state.ready.set(row.index, row);
672
+ refreshLogTypes(sessionId, consumers);
673
+ if (action === "skip") advance(sessionId, cursor);
674
+ else {
675
+ cursor.dispatch.failedAt = null;
676
+ cursor.dispatch.failureCount = 0;
494
677
  }
495
678
  return {
496
679
  redriven: true,
497
- hasPending: hasPending(sessionId)
680
+ hasPending: hasPending(sessionId, consumers)
498
681
  };
499
682
  },
500
683
  async readState(sessionId, reducerName, stateOptions) {
@@ -509,10 +692,10 @@ function memory(options = {}) {
509
692
  for (const write of writes.toSorted((a, b) => a.index - b.index)) {
510
693
  if (write.index < clearIndex) continue;
511
694
  const checkpoint = checkpointKey(sessionId, reducerName, write.index);
512
- for (const eventIndex of write.pinEventIndexes ?? []) {
513
- const event = rowAt(sessionId, eventIndex);
514
- if (!event || event.index <= clearIndex || event.processedAt !== null || event.failedAt !== null) continue;
515
- const pinKey = eventPinKey(sessionId, eventIndex);
695
+ for (const pin of write.pins ?? []) {
696
+ const cursor = cursorAt(sessionId, pin);
697
+ if (!cursor || pin.index <= clearIndex || cursor.head !== pin.index || cursor.dispatch.failedAt !== null) continue;
698
+ const pinKey = headPinKey(sessionId, pin);
516
699
  let pinned = snapshotPins.get(pinKey);
517
700
  if (!pinned) {
518
701
  pinned = /* @__PURE__ */ new Set();