experimental-a2 0.16.1 → 0.18.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 (406) hide show
  1. package/CHANGELOG.md +373 -0
  2. package/README.md +1 -1
  3. package/dist/{actor-ohPC-81x.d.ts → actor-Bjce9_5U.d.ts} +21 -11
  4. package/dist/actor-Bjce9_5U.d.ts.map +1 -0
  5. package/dist/actor-client.d.ts +2 -5
  6. package/dist/actor-client.d.ts.map +1 -1
  7. package/dist/actor-client.js +13 -4
  8. package/dist/actor-client.js.map +1 -1
  9. package/dist/actor-react.d.ts +5 -3
  10. package/dist/actor-react.d.ts.map +1 -1
  11. package/dist/actor-react.js +20 -10
  12. package/dist/actor-react.js.map +1 -1
  13. package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
  14. package/dist/actor-shared-t7w8xIKP.js.map +1 -0
  15. package/dist/actor.d.ts +1 -1
  16. package/dist/actor.js +42 -23
  17. package/dist/actor.js.map +1 -1
  18. package/dist/{ai-DgOBltJ_.js → ai-BpOGm-zB.js} +621 -216
  19. package/dist/ai-BpOGm-zB.js.map +1 -0
  20. package/dist/{ai-CrEf6p_W.d.ts → ai-DrUaMyRt.d.ts} +114 -100
  21. package/dist/ai-DrUaMyRt.d.ts.map +1 -0
  22. package/dist/ai-client.d.ts +27 -0
  23. package/dist/ai-client.d.ts.map +1 -0
  24. package/dist/ai-client.js +29 -0
  25. package/dist/ai-client.js.map +1 -0
  26. package/dist/ai-fx.browser.d.ts +1 -0
  27. package/dist/ai-fx.browser.js +6 -0
  28. package/dist/ai-fx.browser.js.map +1 -0
  29. package/dist/ai-fx.d.ts +10 -0
  30. package/dist/ai-fx.d.ts.map +1 -0
  31. package/dist/ai-fx.js +634 -0
  32. package/dist/ai-fx.js.map +1 -0
  33. package/dist/ai-harness-BuLh_EYi.js +273 -0
  34. package/dist/ai-harness-BuLh_EYi.js.map +1 -0
  35. package/dist/ai-harness-DvsPeJeB.d.ts +26 -0
  36. package/dist/ai-harness-DvsPeJeB.d.ts.map +1 -0
  37. package/dist/ai-pi.browser.d.ts +1 -0
  38. package/dist/ai-pi.browser.js +6 -0
  39. package/dist/ai-pi.browser.js.map +1 -0
  40. package/dist/ai-pi.d.ts +17 -0
  41. package/dist/ai-pi.d.ts.map +1 -0
  42. package/dist/ai-pi.js +904 -0
  43. package/dist/ai-pi.js.map +1 -0
  44. package/dist/ai-react.d.ts +96 -0
  45. package/dist/ai-react.d.ts.map +1 -0
  46. package/dist/ai-react.js +142 -0
  47. package/dist/ai-react.js.map +1 -0
  48. package/dist/ai-server.browser.js +2 -2
  49. package/dist/ai-server.browser.js.map +1 -1
  50. package/dist/ai-server.d.ts +109 -43
  51. package/dist/ai-server.d.ts.map +1 -1
  52. package/dist/ai-server.js +526 -283
  53. package/dist/ai-server.js.map +1 -1
  54. package/dist/ai.d.ts +2 -2
  55. package/dist/ai.js +2 -2
  56. package/dist/bounded-cache-CPHWF-8h.js +41 -0
  57. package/dist/bounded-cache-CPHWF-8h.js.map +1 -0
  58. package/dist/{client-BYzHjkwU.js → client-BReKqwZC.js} +447 -80
  59. package/dist/client-BReKqwZC.js.map +1 -0
  60. package/dist/{client-CzyacQpJ.d.ts → client-h_DKFAjj.d.ts} +14 -13
  61. package/dist/client-h_DKFAjj.d.ts.map +1 -0
  62. package/dist/client.d.ts +4 -2
  63. package/dist/client.js +1 -1
  64. package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
  65. package/dist/contract-BYKASlCV.js.map +1 -0
  66. package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
  67. package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
  68. package/dist/index.d.ts +6 -5
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +4 -3
  71. package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-Bx-c0pDP.d.ts} +7 -3
  72. package/dist/initial-snapshot-Bx-c0pDP.d.ts.map +1 -0
  73. package/dist/{internal-Dq2qYxou.js → internal-B2T3U4Rq.js} +29 -2
  74. package/dist/internal-B2T3U4Rq.js.map +1 -0
  75. package/dist/otel.d.ts +1 -1
  76. package/dist/react-BBXgHWFc.d.ts +86 -0
  77. package/dist/react-BBXgHWFc.d.ts.map +1 -0
  78. package/dist/react.d.ts +2 -122
  79. package/dist/react.js +107 -100
  80. package/dist/react.js.map +1 -1
  81. package/dist/reducer-BAXc8JyF.js +87 -0
  82. package/dist/reducer-BAXc8JyF.js.map +1 -0
  83. package/dist/{reducer-otzHjuJj.d.ts → reducer-CqA59Q6U.d.ts} +46 -30
  84. package/dist/reducer-CqA59Q6U.d.ts.map +1 -0
  85. package/dist/reducer-runtime-DQwz_J59.js +28 -0
  86. package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
  87. package/dist/scheduler-qstash.d.ts +3 -3
  88. package/dist/scheduler-qstash.d.ts.map +1 -1
  89. package/dist/scheduler-qstash.js +33 -10
  90. package/dist/scheduler-qstash.js.map +1 -1
  91. package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
  92. package/dist/scheduler-task-X1SToDjP.js.map +1 -0
  93. package/dist/scheduler-vercel.d.ts +4 -9
  94. package/dist/scheduler-vercel.d.ts.map +1 -1
  95. package/dist/scheduler-vercel.js +25 -44
  96. package/dist/scheduler-vercel.js.map +1 -1
  97. package/dist/{server-BD5ckxZb.js → server-Dw_k_7kO.js} +904 -219
  98. package/dist/server-Dw_k_7kO.js.map +1 -0
  99. package/dist/{server-DbD7IJVK.d.ts → server-En7cbi3v.d.ts} +116 -26
  100. package/dist/server-En7cbi3v.d.ts.map +1 -0
  101. package/dist/server.d.ts +4 -4
  102. package/dist/server.js +2 -2
  103. package/dist/session-clear-Bo-XTFEy.d.ts +12 -0
  104. package/dist/session-clear-Bo-XTFEy.d.ts.map +1 -0
  105. package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
  106. package/dist/session-clear-DGI4jTQ5.js.map +1 -0
  107. package/dist/{store-DyZM6fS5.d.ts → store-Djdv0bnZ.d.ts} +73 -3
  108. package/dist/store-Djdv0bnZ.d.ts.map +1 -0
  109. package/dist/store-N8PXxDAS.js.map +1 -1
  110. package/dist/store-memory.d.ts +1 -1
  111. package/dist/store-memory.d.ts.map +1 -1
  112. package/dist/store-memory.js +205 -66
  113. package/dist/store-memory.js.map +1 -1
  114. package/dist/{store-polling-CmxUbV93.js → store-polling-B5FcwJfo.js} +21 -4
  115. package/dist/store-polling-B5FcwJfo.js.map +1 -0
  116. package/dist/store-postgres.d.ts +1 -1
  117. package/dist/store-postgres.d.ts.map +1 -1
  118. package/dist/store-postgres.js +450 -108
  119. package/dist/store-postgres.js.map +1 -1
  120. package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-BAwS8RWn.js} +3 -3
  121. package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-BAwS8RWn.js.map} +1 -1
  122. package/dist/store-redis-http.d.ts +1 -1
  123. package/dist/store-redis-http.d.ts.map +1 -1
  124. package/dist/store-redis-http.js +293 -11
  125. package/dist/store-redis-http.js.map +1 -1
  126. package/dist/store-redis-notify-BRITti_o.js +2493 -0
  127. package/dist/store-redis-notify-BRITti_o.js.map +1 -0
  128. package/dist/store-redis.d.ts +1 -1
  129. package/dist/store-redis.d.ts.map +1 -1
  130. package/dist/store-redis.js +11 -11
  131. package/dist/store-redis.js.map +1 -1
  132. package/dist/store-sqlite.d.ts +1 -1
  133. package/dist/store-sqlite.d.ts.map +1 -1
  134. package/dist/store-sqlite.js +197 -33
  135. package/dist/store-sqlite.js.map +1 -1
  136. package/dist/stream-buffer-Cx7AAPGq.js +23 -0
  137. package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
  138. package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-Cyfqo6Qt.d.ts} +2 -2
  139. package/dist/telemetry-Cyfqo6Qt.d.ts.map +1 -0
  140. package/dist/{wire-BO5wWCb1.js → wire-rSUglYo8.js} +132 -10
  141. package/dist/wire-rSUglYo8.js.map +1 -0
  142. package/docs/01-quickstart.mdx +50 -84
  143. package/docs/{guides/07-examples.mdx → 02-examples.mdx} +10 -20
  144. package/docs/actors/01-introduction.mdx +48 -82
  145. package/docs/actors/02-concurrency.mdx +43 -70
  146. package/docs/actors/03-timers.mdx +28 -42
  147. package/docs/actors/04-routes.mdx +110 -137
  148. package/docs/actors/meta.ts +1 -1
  149. package/docs/agents/01-introduction.mdx +257 -0
  150. package/docs/agents/02-tools.mdx +247 -0
  151. package/docs/agents/03-conversations.mdx +538 -0
  152. package/docs/agents/04-harnesses.mdx +136 -0
  153. package/docs/agents/meta.ts +1 -0
  154. package/docs/client/01-react.mdx +294 -0
  155. package/docs/client/02-transports.mdx +86 -0
  156. package/docs/client/03-presence.mdx +224 -0
  157. package/docs/client/meta.ts +1 -0
  158. package/docs/contract/01-events.mdx +57 -0
  159. package/docs/contract/02-state.mdx +142 -0
  160. package/docs/contract/03-sessions.mdx +175 -0
  161. package/docs/index.mdx +112 -156
  162. package/docs/reference/01-core.mdx +139 -0
  163. package/docs/reference/02-server.mdx +855 -0
  164. package/docs/reference/03-client.mdx +301 -0
  165. package/docs/reference/04-ai.mdx +722 -0
  166. package/docs/reference/05-adapters.mdx +351 -0
  167. package/docs/reference/06-errors.mdx +138 -0
  168. package/docs/reference/meta.ts +1 -1
  169. package/docs/server/01-handlers.mdx +200 -0
  170. package/docs/server/02-timers.mdx +130 -0
  171. package/docs/server/03-cancellation.mdx +141 -0
  172. package/docs/server/04-routes.mdx +243 -0
  173. package/docs/server/05-production.mdx +322 -0
  174. package/docs/server/06-guarantees.mdx +123 -0
  175. package/examples/playground/DEPLOY.md +53 -7
  176. package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
  177. package/examples/playground/app/a0/[appId]/page.tsx +53 -0
  178. package/examples/playground/app/a0/a0.css +818 -0
  179. package/examples/playground/app/a0/app-list.tsx +218 -0
  180. package/examples/playground/app/a0/app-view.tsx +63 -0
  181. package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
  182. package/examples/playground/app/a0/apps/[appId]/route.ts +28 -0
  183. package/examples/playground/app/a0/apps.test.ts +128 -0
  184. package/examples/playground/app/a0/apps.ts +102 -0
  185. package/examples/playground/app/a0/chat.tsx +410 -0
  186. package/examples/playground/app/a0/columns.tsx +109 -0
  187. package/examples/playground/app/a0/context-menu.tsx +116 -0
  188. package/examples/playground/app/a0/cookies.ts +46 -0
  189. package/examples/playground/app/a0/crash-on-request.ts +72 -0
  190. package/examples/playground/app/a0/crashes/route.ts +3 -0
  191. package/examples/playground/app/a0/crashes.test.ts +33 -0
  192. package/examples/playground/app/a0/crashes.ts +28 -0
  193. package/examples/playground/app/a0/devtools.tsx +164 -0
  194. package/examples/playground/app/a0/events/route.ts +6 -0
  195. package/examples/playground/app/a0/flags.ts +30 -0
  196. package/examples/playground/app/a0/fx/events/route.ts +9 -0
  197. package/examples/playground/app/a0/fx/scheduler/route.ts +7 -0
  198. package/examples/playground/app/a0/fx/short/events/route.ts +11 -0
  199. package/examples/playground/app/a0/layout.tsx +38 -0
  200. package/examples/playground/app/a0/list/route.ts +10 -0
  201. package/examples/playground/app/a0/model.test.ts +73 -0
  202. package/examples/playground/app/a0/model.ts +95 -0
  203. package/examples/playground/app/a0/no-photon.cjs +4 -0
  204. package/examples/playground/app/a0/no-quickjs.cjs +3 -0
  205. package/examples/playground/app/a0/page.tsx +14 -0
  206. package/examples/playground/app/a0/pi/events/route.ts +9 -0
  207. package/examples/playground/app/a0/pi/scheduler/route.ts +7 -0
  208. package/examples/playground/app/a0/pi/short/events/route.ts +11 -0
  209. package/examples/playground/app/a0/preview-pane.tsx +145 -0
  210. package/examples/playground/app/a0/preview.test.ts +53 -0
  211. package/examples/playground/app/a0/preview.ts +26 -0
  212. package/examples/playground/app/a0/sandbox-log.ts +75 -0
  213. package/examples/playground/app/a0/sandbox-session.ts +152 -0
  214. package/examples/playground/app/a0/sandbox.ts +191 -0
  215. package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
  216. package/examples/playground/app/a0/scheduler/route.ts +7 -0
  217. package/examples/playground/app/a0/server.ts +213 -0
  218. package/examples/playground/app/a0/session.ts +59 -0
  219. package/examples/playground/app/a0/shell.tsx +158 -0
  220. package/examples/playground/app/a0/short/events/route.ts +9 -0
  221. package/examples/playground/app/a0/splitter.tsx +120 -0
  222. package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
  223. package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
  224. package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
  225. package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
  226. package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
  227. package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
  228. package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
  229. package/examples/playground/app/agent/events/route.ts +5 -2
  230. package/examples/playground/app/agent/ingress.test.ts +101 -0
  231. package/examples/playground/app/agent/model-input.test.tsx +58 -0
  232. package/examples/playground/app/agent/model-input.tsx +115 -0
  233. package/examples/playground/app/agent/model.test.ts +73 -0
  234. package/examples/playground/app/agent/model.ts +58 -26
  235. package/examples/playground/app/agent/new-agent-session.tsx +37 -19
  236. package/examples/playground/app/agent/page.tsx +4 -4
  237. package/examples/playground/app/agent/server.ts +19 -15
  238. package/examples/playground/app/agent/session.ts +5 -6
  239. package/examples/playground/app/agent/telemetry.test.ts +189 -0
  240. package/examples/playground/app/agent/telemetry.ts +30 -0
  241. package/examples/playground/app/agent/trace.test.ts +23 -0
  242. package/examples/playground/app/agent/trace.ts +18 -0
  243. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +12 -12
  244. package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
  245. package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
  246. package/examples/playground/app/canvas/model.ts +53 -53
  247. package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
  248. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
  249. package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
  250. package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
  251. package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
  252. package/examples/playground/app/chat/[chatId]/session.ts +2 -19
  253. package/examples/playground/app/chat/agent/route.ts +9 -0
  254. package/examples/playground/app/chat/model.test.ts +46 -119
  255. package/examples/playground/app/chat/model.ts +82 -128
  256. package/examples/playground/app/chat/scheduler/route.ts +2 -2
  257. package/examples/playground/app/chat/server.test.ts +112 -0
  258. package/examples/playground/app/chat/server.ts +113 -117
  259. package/examples/playground/app/components/activity-feed.tsx +30 -1
  260. package/examples/playground/app/components/activity-history.test.tsx +35 -0
  261. package/examples/playground/app/components/demo-icons.tsx +89 -0
  262. package/examples/playground/app/components/demos.tsx +33 -0
  263. package/examples/playground/app/components/fast-link.test.tsx +58 -0
  264. package/examples/playground/app/components/fast-link.tsx +55 -0
  265. package/examples/playground/app/components/site-header.tsx +96 -0
  266. package/examples/playground/app/counter/counter-client.tsx +3 -3
  267. package/examples/playground/app/counter/model.test.ts +3 -3
  268. package/examples/playground/app/counter/model.ts +11 -21
  269. package/examples/playground/app/counter/page.tsx +2 -15
  270. package/examples/playground/app/counter/session.ts +2 -2
  271. package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
  272. package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
  273. package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
  274. package/examples/playground/app/documents/[documentId]/session.ts +2 -2
  275. package/examples/playground/app/documents/model.ts +27 -27
  276. package/examples/playground/app/globals.css +420 -207
  277. package/examples/playground/app/initial-state.test.tsx +54 -0
  278. package/examples/playground/app/layout.tsx +7 -23
  279. package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
  280. package/examples/playground/app/orders/[orderId]/session.ts +2 -2
  281. package/examples/playground/app/orders/model.ts +49 -49
  282. package/examples/playground/app/page.tsx +13 -102
  283. package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
  284. package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
  285. package/examples/playground/app/recovery/model.ts +30 -30
  286. package/examples/playground/instrumentation.ts +5 -0
  287. package/examples/playground/next.config.ts +17 -5
  288. package/examples/playground/package.json +13 -2
  289. package/examples/playground/scripts/a0-template.ts +77 -0
  290. package/examples/playground/vercel.json +36 -0
  291. package/package.json +29 -3
  292. package/src/actor-client.ts +16 -4
  293. package/src/actor-react.ts +25 -11
  294. package/src/actor-shared.ts +21 -6
  295. package/src/actor.ts +76 -31
  296. package/src/ai-agent-actions.ts +219 -0
  297. package/src/ai-agent.ts +2517 -0
  298. package/src/ai-app-state.ts +91 -0
  299. package/src/ai-client-state.ts +6 -3
  300. package/src/ai-client.ts +62 -0
  301. package/src/ai-context-schema.ts +25 -5
  302. package/src/ai-context.ts +90 -40
  303. package/src/ai-control-server.ts +92 -72
  304. package/src/ai-coordinator.ts +23 -22
  305. package/src/ai-fx.browser.ts +5 -0
  306. package/src/ai-fx.ts +833 -0
  307. package/src/ai-harness.ts +518 -0
  308. package/src/ai-message-base.ts +209 -0
  309. package/src/ai-message-projection.ts +262 -64
  310. package/src/ai-model-metadata.ts +11 -6
  311. package/src/ai-pi.browser.ts +5 -0
  312. package/src/ai-pi.ts +1263 -0
  313. package/src/ai-progress-batches.ts +26 -0
  314. package/src/ai-projector.ts +95 -14
  315. package/src/ai-react.ts +146 -0
  316. package/src/ai-sdk-step.ts +2 -2
  317. package/src/ai-server.browser.ts +2 -2
  318. package/src/ai-server.ts +23 -1965
  319. package/src/ai-stored-state.ts +48 -163
  320. package/src/ai.ts +339 -155
  321. package/src/bootstrap-stream.ts +72 -0
  322. package/src/bounded-cache.ts +55 -0
  323. package/src/client-render-session.ts +49 -0
  324. package/src/client.ts +688 -151
  325. package/src/contract.ts +92 -42
  326. package/src/deterministic-id.ts +37 -0
  327. package/src/equal-json.ts +32 -0
  328. package/src/errors.ts +13 -1
  329. package/src/index.ts +5 -1
  330. package/src/initial-snapshot.ts +39 -0
  331. package/src/internal.ts +52 -0
  332. package/src/libfx.d.ts +34 -0
  333. package/src/postgres-notification-scope.ts +14 -3
  334. package/src/react.ts +171 -206
  335. package/src/redis-http-reply.ts +256 -0
  336. package/src/redis-http-stream.ts +73 -0
  337. package/src/redis-http-subscriptions.ts +13 -5
  338. package/src/reducer-runtime.ts +52 -0
  339. package/src/reducer.ts +93 -11
  340. package/src/scheduler-qstash.ts +49 -20
  341. package/src/scheduler-task.ts +34 -2
  342. package/src/scheduler-vercel.ts +32 -84
  343. package/src/server-fetch.ts +139 -35
  344. package/src/server.ts +1133 -255
  345. package/src/session-clear.ts +23 -0
  346. package/src/session-socket.ts +154 -49
  347. package/src/sse.ts +71 -4
  348. package/src/store-memory.ts +252 -75
  349. package/src/store-polling.ts +18 -4
  350. package/src/store-postgres.ts +541 -114
  351. package/src/store-redis-core.ts +1450 -493
  352. package/src/store-redis-http.ts +18 -2
  353. package/src/store-redis-notify.ts +262 -85
  354. package/src/store-redis.ts +19 -9
  355. package/src/store-sqlite.ts +319 -45
  356. package/src/store.ts +79 -1
  357. package/src/stream-buffer.ts +43 -0
  358. package/src/telemetry.ts +14 -1
  359. package/src/wire.ts +241 -13
  360. package/dist/actor-ohPC-81x.d.ts.map +0 -1
  361. package/dist/actor-shared-B5tJfzt-.js.map +0 -1
  362. package/dist/ai-CrEf6p_W.d.ts.map +0 -1
  363. package/dist/ai-DgOBltJ_.js.map +0 -1
  364. package/dist/client-BYzHjkwU.js.map +0 -1
  365. package/dist/client-CzyacQpJ.d.ts.map +0 -1
  366. package/dist/contract-CKRg_E4q.js.map +0 -1
  367. package/dist/errors-DCk6ch5n.js.map +0 -1
  368. package/dist/errors-DvhSXnxk.d.ts.map +0 -1
  369. package/dist/internal-Dq2qYxou.js.map +0 -1
  370. package/dist/react.d.ts.map +0 -1
  371. package/dist/reducer-DEMjEY_O.js +0 -29
  372. package/dist/reducer-DEMjEY_O.js.map +0 -1
  373. package/dist/reducer-otzHjuJj.d.ts.map +0 -1
  374. package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
  375. package/dist/server-BD5ckxZb.js.map +0 -1
  376. package/dist/server-DbD7IJVK.d.ts.map +0 -1
  377. package/dist/store-DyZM6fS5.d.ts.map +0 -1
  378. package/dist/store-polling-CmxUbV93.js.map +0 -1
  379. package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
  380. package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
  381. package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
  382. package/dist/validate-XKT4FSNn.js +0 -30
  383. package/dist/validate-XKT4FSNn.js.map +0 -1
  384. package/dist/wire-BO5wWCb1.js.map +0 -1
  385. package/docs/concepts/01-contracts.mdx +0 -142
  386. package/docs/concepts/02-handlers.mdx +0 -284
  387. package/docs/concepts/03-durability.mdx +0 -314
  388. package/docs/concepts/04-state.mdx +0 -209
  389. package/docs/guides/01-timers.mdx +0 -185
  390. package/docs/guides/02-cancellation.mdx +0 -161
  391. package/docs/guides/03-react.mdx +0 -384
  392. package/docs/guides/04-local-first.mdx +0 -88
  393. package/docs/guides/05-production.mdx +0 -521
  394. package/docs/guides/06-ai-agents.mdx +0 -1318
  395. package/docs/guides/08-application-data.mdx +0 -113
  396. package/docs/guides/09-presence.mdx +0 -266
  397. package/docs/guides/10-transports.mdx +0 -221
  398. package/docs/reference/01-api.mdx +0 -1896
  399. package/docs/reference/02-errors.mdx +0 -149
  400. package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
  401. package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
  402. package/examples/playground/app/agent/compaction-settings.ts +0 -49
  403. package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
  404. package/examples/playground/app/agent/compaction-timeline.ts +0 -208
  405. /package/docs/{concepts → contract}/meta.ts +0 -0
  406. /package/docs/{guides → server}/meta.ts +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"store-sqlite.js","names":[],"sources":["../src/store-sqlite.ts"],"sourcesContent":["/**\n * experimental-a2/store-sqlite — sqlite store backend (the dev default, `.a2/dev.db`).\n *\n * Built on `node:sqlite` (Node ≥ 22.13) so it ships with zero\n * dependencies. Implements the A2Store interface; the conformance suite\n * in test/conformance is the executable contract.\n *\n * The spec's `index` column is stored as `idx` (a2-implementation.md\n * §11 — `index` collides with a reserved word) and mapped back at the\n * API boundary. Timestamps are epoch milliseconds written from the\n * injected clock — never SQL `now()` — so tests can time-travel against\n * real storage.\n */\n\nimport { mkdirSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { DatabaseSync } from 'node:sqlite'\nimport { A2Error } from './errors.ts'\nimport { idempotentReplay } from './idempotent-replay.ts'\nimport { decodeReturnedEventIds } from './store-codec.ts'\nimport { pollingStream } from './store-polling.ts'\nimport {\n RANDOM_IDS,\n SYSTEM_CLOCK,\n type A2Store,\n type Clock,\n type Event,\n type EventCause,\n type IdSource,\n type StoredEvent,\n} from './store.ts'\n\nexport type SqliteStoreOptions = {\n /** Database file path. Defaults to `.a2/dev.db`; `:memory:` works. */\n path?: string\n /** Injectable clock — every stored timestamp comes from here. */\n clock?: Clock\n /** Injectable id source for generated event ids. */\n ids?: IdSource\n}\n\nexport type SqliteStore = A2Store & {\n /** Close the underlying database handle. */\n close(): void\n}\n\nconst SCHEMA = `\ncreate table if not exists a2_events (\n session_id text not null,\n idx integer not null,\n event_type text not null,\n payload text not null,\n event_id text not null,\n created_at integer not null,\n cause text,\n lane text,\n lane_ready integer not null default 0,\n processed_at integer,\n processed_by_attempt integer,\n returned_event_ids text,\n first_claimed_at integer,\n last_claimed_at integer,\n attempt_count integer not null default 0,\n failure_count integer not null default 0,\n last_failed_at integer,\n last_failed_attempt integer,\n last_error text,\n failed_at integer,\n claim_holder text,\n claim_expires_at integer,\n primary key (session_id, idx)\n) strict;\n\ncreate unique index if not exists a2_events_event_id on a2_events (event_id);\ncreate index if not exists a2_events_unprocessed\n on a2_events (session_id, idx) where processed_at is null;\ncreate index if not exists a2_events_lane_pending\n on a2_events (session_id, lane, idx) where processed_at is null;\ncreate index if not exists a2_events_dispatch_ready\n on a2_events (session_id, idx)\n where lane_ready = 1 and processed_at is null and failed_at is null;\ncreate index if not exists a2_events_claim_expiry\n on a2_events (session_id, claim_expires_at)\n where lane_ready = 1 and processed_at is null and failed_at is null;\n\ncreate table if not exists a2_snapshots (\n session_id text not null,\n reducer_name text not null,\n up_to_index integer not null,\n state text not null,\n updated_at integer not null,\n primary key (session_id, reducer_name)\n) strict;\n\ncreate table if not exists a2_snapshot_history (\n session_id text not null,\n reducer_name text not null,\n up_to_index integer not null,\n state text not null,\n updated_at integer not null,\n primary key (session_id, reducer_name, up_to_index)\n) strict;\n\ncreate table if not exists a2_snapshot_pins (\n session_id text not null,\n event_index integer not null,\n reducer_name text not null,\n up_to_index integer not null,\n primary key (session_id, event_index, reducer_name, up_to_index)\n) strict;\n\ncreate index if not exists a2_snapshot_pins_checkpoint\n on a2_snapshot_pins (session_id, reducer_name, up_to_index);\n\ncreate table if not exists a2_presence (\n ns text not null,\n participant text not null,\n field text not null,\n value text not null,\n seen integer not null,\n at integer not null,\n expires_at integer not null,\n primary key (ns, participant, field)\n) strict;\n`\n\n/**\n * Switching journal modes takes an exclusive lock, and — unlike normal\n * statements — the switch can return SQLITE_BUSY without consulting the\n * busy handler while several connections race it (instances cold-booting\n * against one file). Bounded synchronous retry; the window is boot-only\n * and tiny. Found by the multi-process torture test.\n */\nconst setWalJournalMode = (db: DatabaseSync): void => {\n for (let attempt = 0; ; attempt += 1) {\n try {\n db.exec('pragma journal_mode = wal')\n return\n } catch (err) {\n const busy = (err as { errcode?: number }).errcode === 5\n if (!busy || attempt >= 100) throw err\n // Synchronous 5ms sleep — sqlite() is a sync constructor.\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5)\n }\n }\n}\n\nconst wrap = <T>(fn: () => T): T => {\n try {\n return fn()\n } catch (err) {\n if (err instanceof A2Error || err instanceof TypeError) throw err\n throw new A2Error('STORE_UNAVAILABLE', 'sqlite store operation failed', {\n cause: err,\n })\n }\n}\n\nconst toDate = (ms: number | bigint | null): Date | null =>\n ms === null ? null : new Date(Number(ms))\n\nconst toCause = (json: string | null): EventCause | null => {\n if (json === null) return null\n const value = JSON.parse(json) 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 toStored = (row: EventRow): StoredEvent => ({\n id: row.event_id,\n type: row.event_type,\n payload: JSON.parse(row.payload),\n index: Number(row.idx),\n sessionId: row.session_id,\n createdAt: new Date(Number(row.created_at)),\n cause: toCause(row.cause),\n lane: row.lane,\n processedAt: toDate(row.processed_at),\n processedByAttempt:\n row.processed_by_attempt === null ? null : Number(row.processed_by_attempt),\n returnedEventIds:\n row.returned_event_ids === null\n ? null\n : decodeReturnedEventIds(row.returned_event_ids),\n firstClaimedAt: toDate(row.first_claimed_at),\n lastClaimedAt: toDate(row.last_claimed_at),\n attemptCount: Number(row.attempt_count),\n failureCount: Number(row.failure_count),\n lastFailedAt: toDate(row.last_failed_at),\n lastFailedAttempt:\n row.last_failed_attempt === null ? null : Number(row.last_failed_attempt),\n lastError: row.last_error,\n failedAt: toDate(row.failed_at),\n claimHolder: row.claim_holder,\n claimExpiresAt: toDate(row.claim_expires_at),\n})\n\nconst toEvent = (row: EventRow): Event => ({\n id: row.event_id,\n type: row.event_type,\n payload: JSON.parse(row.payload),\n index: Number(row.idx),\n sessionId: row.session_id,\n createdAt: new Date(Number(row.created_at)),\n})\n\ntype EventRow = {\n session_id: string\n idx: number | bigint\n event_type: string\n payload: string\n event_id: string\n created_at: number | bigint\n cause: string | null\n lane: string | null\n processed_at: number | bigint | null\n processed_by_attempt: number | bigint | null\n returned_event_ids: string | null\n first_claimed_at: number | bigint | null\n last_claimed_at: number | bigint | null\n attempt_count: number | bigint\n failure_count: number | bigint\n last_failed_at: number | bigint | null\n last_failed_attempt: number | bigint | null\n last_error: string | null\n failed_at: number | bigint | null\n claim_holder: string | null\n claim_expires_at: number | bigint | null\n}\n\ntype PresenceDbRow = {\n participant: string\n field: string\n value: string\n seen: number | bigint\n at: number | bigint\n expires_at: number | bigint\n}\n\ntype StateReadRow = Partial<EventRow> & {\n row_kind: 'snapshot' | 'event'\n head_index: number | bigint | null\n snapshot_index: number | bigint | null\n snapshot_state: string | null\n}\n\ntype SnapshotHeadRow = {\n up_to_index: number | bigint\n state: string\n updated_at: number | bigint\n}\n\ntype AppendStatusRow = {\n max: number | bigint\n has_pending: number | bigint\n}\n\ntype ExistingEventRow = EventRow & {\n session_has_pending: number | bigint\n}\n\nexport function sqlite(options: SqliteStoreOptions = {}): SqliteStore {\n const path = options.path ?? '.a2/dev.db'\n const clock = options.clock ?? SYSTEM_CLOCK\n const generateId = options.ids ?? RANDOM_IDS\n\n if (path !== ':memory:') {\n // The dev-default backend only ever runs outside production, but\n // bundlers can't know that: without the ignore, Turbopack sees a\n // dynamic fs path and traces the entire project into the output.\n mkdirSync(dirname(resolve(/* turbopackIgnore: true */ path)), {\n recursive: true,\n })\n }\n const db = new DatabaseSync(path)\n // busy_timeout before anything that takes locks: concurrent\n // cold-opens (several instances booting at once) race the WAL switch\n // and schema creation.\n db.exec('pragma busy_timeout = 5000')\n setWalJournalMode(db)\n db.exec('pragma synchronous = normal')\n db.exec(SCHEMA)\n\n const insertEvent = db.prepare(\n `insert into a2_events\n (session_id, idx, event_type, payload, event_id, created_at,\n cause, lane, lane_ready, processed_at)\n select ?, ?, ?, ?, ?, ?, ?, ?,\n case\n when ? is null then 1\n when not exists (\n select 1 from a2_events\n where session_id = ? and lane = ? and processed_at is null\n ) then 1\n else 0\n end,\n ?`,\n )\n const promoteLaneHead = db.prepare(\n `update a2_events set lane_ready = 1\n where session_id = ?\n and idx = (\n select idx from a2_events\n where session_id = ? and lane = ? and processed_at is null\n order by idx limit 1\n )`,\n )\n const appendStatus = db.prepare(\n `select coalesce(\n (select max(idx) from a2_events where session_id = ?),\n 0\n ) as max,\n exists(\n select 1 from a2_events\n where session_id = ? and processed_at is null\n ) as has_pending`,\n )\n const upsertPresence = db.prepare(\n `insert into a2_presence (ns, participant, field, value, seen, at, expires_at)\n values (?, ?, ?, ?, ?, ?, ?)\n on conflict (ns, participant, field) do update set\n value = excluded.value,\n seen = excluded.seen,\n at = excluded.at,\n expires_at = excluded.expires_at\n where a2_presence.at <= excluded.at`,\n )\n const deletePresence = db.prepare(\n `delete from a2_presence\n where ns = ? and participant = ? and field = ? and at <= ?`,\n )\n const sweepPresence = db.prepare(\n 'delete from a2_presence where ns = ? and expires_at <= ?',\n )\n const selectPresence = db.prepare(\n `select participant, field, value, seen, at, expires_at\n from a2_presence\n where ns = ? and expires_at > ?\n order by participant, field`,\n )\n const selectByIds = (count: number) =>\n db.prepare(\n `select stored.*,\n exists(\n select 1 from a2_events pending\n where pending.session_id = ?\n and pending.processed_at is null\n ) as session_has_pending\n from a2_events stored\n where stored.event_id in (${Array.from({ length: count }, () => '?').join(', ')})\n order by stored.idx`,\n )\n\n const tx = <T>(fn: () => T): T => {\n db.exec('begin immediate')\n try {\n const result = fn()\n db.exec('commit')\n return result\n } catch (err) {\n try {\n db.exec('rollback')\n } catch {\n // Rollback can fail if the transaction never started; the\n // original error is what matters.\n }\n throw err\n }\n }\n\n return {\n async append(sessionId, events) {\n if (events.length === 0) {\n return wrap(() => {\n const status = appendStatus.get(\n sessionId,\n sessionId,\n ) as AppendStatusRow\n return {\n events: [],\n hasPending: Number(status.has_pending) === 1,\n }\n })\n }\n return wrap(() =>\n tx(() => {\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 if (suppliedIds.length > 0) {\n const existing = selectByIds(suppliedIds.length).all(\n sessionId,\n ...suppliedIds,\n ) as unknown as ExistingEventRow[]\n if (existing.length > 0) {\n const foreign = existing.find(\n (row) => row.session_id !== sessionId,\n )\n if (foreign) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n `event id '${foreign.event_id}' already exists in another session`,\n )\n }\n if (existing.length === events.length) {\n return {\n events: idempotentReplay(events, existing.map(toStored)),\n hasPending: Number(existing[0]!.session_has_pending) === 1,\n }\n }\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n `batch mixes ${existing.length} already-appended and ${events.length - existing.length} fresh events`,\n )\n }\n }\n\n // Attempt-currency fence: a fresh handler append commits only\n // while its causal attempt is still the parent's latest\n // (store.ts `append`). `begin immediate` serializes this read\n // against a concurrent claim.\n for (const e of events) {\n if (!e.cause) continue\n const parent = db\n .prepare(\n 'select attempt_count, failed_at from a2_events where session_id = ? and idx = ?',\n )\n .get(sessionId, e.cause.index) as\n { attempt_count: number | bigint; failed_at: unknown } | undefined\n if (!parent) {\n throw new TypeError(\n `no event at index ${e.cause.index} in session '${sessionId}'`,\n )\n }\n if (\n Number(parent.attempt_count) !== e.cause.attempt ||\n parent.failed_at !== null\n ) {\n throw new A2Error(\n 'SUPERSEDED_ATTEMPT',\n `attempt ${e.cause.attempt} no longer owns event ${e.cause.index} in session '${sessionId}'`,\n )\n }\n }\n\n const status = appendStatus.get(\n sessionId,\n sessionId,\n ) as AppendStatusRow\n const base = Number(status.max)\n const now = clock.now().getTime()\n const inserted: StoredEvent[] = events.map((e, i) => {\n const id = e.id ?? generateId()\n const index = base + 1 + i\n insertEvent.run(\n sessionId,\n index,\n e.type,\n JSON.stringify(e.payload) ?? 'null',\n id,\n now,\n e.cause ? JSON.stringify(e.cause) : null,\n e.lane ?? null,\n e.lane ?? null,\n sessionId,\n e.lane ?? null,\n e.settled ? now : null,\n )\n return {\n id,\n type: e.type,\n payload: structuredClone(e.payload),\n index,\n sessionId,\n createdAt: new Date(now),\n cause: e.cause ? { ...e.cause } : null,\n lane: e.lane ?? null,\n processedAt: e.settled ? new Date(now) : null,\n processedByAttempt: null,\n returnedEventIds: null,\n firstClaimedAt: null,\n lastClaimedAt: null,\n attemptCount: 0,\n failureCount: 0,\n lastFailedAt: null,\n lastFailedAttempt: null,\n lastError: null,\n failedAt: null,\n claimHolder: null,\n claimExpiresAt: null,\n }\n })\n return {\n events: inserted,\n hasPending:\n Number(status.has_pending) === 1 ||\n events.some((event) => event.settled !== true),\n }\n }),\n )\n },\n\n async read(sessionId, opts) {\n return wrap(() => {\n const conditions = ['session_id = ?']\n const params: (string | number)[] = [sessionId]\n if (opts?.afterIndex !== undefined) {\n conditions.push('idx > ?')\n params.push(opts.afterIndex)\n }\n if (opts?.throughIndex !== undefined) {\n conditions.push('idx <= ?')\n params.push(opts.throughIndex)\n }\n const rows = db\n .prepare(\n `select * from a2_events where ${conditions.join(' and ')} order by idx`,\n )\n .all(...params) as unknown as EventRow[]\n return rows.map(toStored)\n })\n },\n\n async claimAvailable({\n sessionId,\n holder,\n ttlMs,\n expiresAtMs,\n excludeIndexes = [],\n }) {\n return wrap(() =>\n tx(() => {\n const now = clock.now().getTime()\n const expiresAt = expiresAtMs ?? now + ttlMs\n const exclusion =\n excludeIndexes.length === 0\n ? ''\n : `and event.idx not in (${excludeIndexes.map(() => '?').join(', ')})`\n const eligible = db\n .prepare(\n `select event.idx\n from a2_events event\n where event.session_id = ?\n and event.processed_at is null\n and event.failed_at is null\n and event.lane_ready = 1\n and (event.claim_expires_at is null or event.claim_expires_at <= ?)\n ${exclusion}\n order by event.idx`,\n )\n .all(sessionId, now, ...excludeIndexes) as unknown as Array<{\n idx: number | bigint\n }>\n if (eligible.length > 0) {\n const update = db.prepare(\n `update a2_events set\n attempt_count = attempt_count + 1,\n first_claimed_at = coalesce(first_claimed_at, ?),\n last_claimed_at = ?,\n claim_holder = ?,\n claim_expires_at = ?\n where session_id = ? and idx = ?\n returning *`,\n )\n const events = eligible.map((row) => {\n const claimed = update.get(\n now,\n now,\n holder,\n expiresAt,\n sessionId,\n row.idx,\n ) as EventRow\n return toStored(claimed)\n })\n return { outcome: 'claimed', events }\n }\n\n const active = db\n .prepare(\n `select min(claim_expires_at) as retry_at\n from a2_events\n where session_id = ?\n and processed_at is null\n and failed_at is null\n and lane_ready = 1\n and claim_expires_at > ?`,\n )\n .get(sessionId, now) as {\n retry_at: number | bigint | null\n }\n return active.retry_at === null\n ? { outcome: 'settled' }\n : { outcome: 'busy', retryAt: new Date(Number(active.retry_at)) }\n }),\n )\n },\n\n async renewClaims({ sessionId, holder, claims, ttlMs, expiresAtMs }) {\n if (claims.length === 0) return { renewed: [], superseded: [] }\n return wrap(() =>\n tx(() => {\n const now = clock.now().getTime()\n const expiresAt = expiresAtMs ?? now + ttlMs\n const renewed: number[] = []\n const superseded: number[] = []\n const currentAttempt = db.prepare(\n 'select attempt_count from a2_events where session_id = ? and idx = ?',\n )\n const extend = db.prepare(\n `update a2_events set claim_expires_at = ?\n where session_id = ?\n and idx = ?\n and attempt_count = ?\n and processed_at is null\n and failed_at is null\n and claim_holder = ?\n and claim_expires_at > ?`,\n )\n for (const claim of claims) {\n const row = currentAttempt.get(sessionId, claim.index) as\n { attempt_count: number | bigint } | undefined\n if (!row) continue\n if (Number(row.attempt_count) > claim.attempt) {\n superseded.push(claim.index)\n continue\n }\n const updated = extend.run(\n expiresAt,\n sessionId,\n claim.index,\n claim.attempt,\n holder,\n now,\n )\n if (Number(updated.changes) === 1) renewed.push(claim.index)\n }\n return {\n renewed: renewed.toSorted((a, b) => a - b),\n superseded: superseded.toSorted((a, b) => a - b),\n }\n }),\n )\n },\n\n async completeAttempt({ sessionId, index, attempt, events }) {\n return wrap(() =>\n tx(() => {\n const parent = db\n .prepare('select * from a2_events where session_id = ? and idx = ?')\n .get(sessionId, index) as EventRow | undefined\n if (!parent) {\n throw new TypeError(\n `no event at index ${index} in session '${sessionId}'`,\n )\n }\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 'returned event batch contains the same event id more than once',\n )\n }\n\n if (parent.processed_at !== null) {\n if (Number(parent.processed_by_attempt) !== attempt) {\n return { outcome: 'superseded' }\n }\n const returnedIds =\n parent.returned_event_ids === null\n ? null\n : decodeReturnedEventIds(parent.returned_event_ids)\n if (\n returnedIds === null ||\n returnedIds.length !== ids.length ||\n returnedIds.some((id, offset) => id !== ids[offset])\n ) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'completed attempt does not match the returned event batch',\n )\n }\n const existing =\n ids.length === 0\n ? []\n : (selectByIds(ids.length).all(\n sessionId,\n ...ids,\n ) as unknown as ExistingEventRow[])\n const byId = new Map(existing.map((row) => [row.event_id, row]))\n const ordered = ids.map((id) => byId.get(id))\n if (\n ordered.some(\n (row) =>\n !row ||\n row.session_id !== sessionId ||\n row.cause === null ||\n toCause(row.cause)?.index !== index ||\n toCause(row.cause)?.attempt !== attempt,\n )\n ) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'returned event retry does not match the committed batch',\n )\n }\n return {\n outcome: 'completed',\n events: idempotentReplay(\n events,\n (ordered as ExistingEventRow[]).map(toStored),\n ),\n }\n }\n if (\n Number(parent.attempt_count) !== attempt ||\n parent.claim_holder === null ||\n parent.failed_at !== null\n ) {\n return { outcome: 'superseded' }\n }\n\n if (\n ids.length > 0 &&\n (selectByIds(ids.length).all(sessionId, ...ids) as unknown[])\n .length > 0\n ) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'returned event batch contains an already-appended event id',\n )\n }\n\n const status = appendStatus.get(\n sessionId,\n sessionId,\n ) as AppendStatusRow\n const now = clock.now().getTime()\n db.prepare(\n `update a2_events set\n processed_at = ?,\n processed_by_attempt = ?,\n returned_event_ids = ?,\n lane_ready = 0,\n claim_holder = null,\n claim_expires_at = null\n where session_id = ? and idx = ?`,\n ).run(now, attempt, JSON.stringify(ids), sessionId, index)\n db.prepare(\n 'delete from a2_snapshot_pins where session_id = ? and event_index = ?',\n ).run(sessionId, index)\n db.prepare(\n `delete from a2_snapshot_history\n where session_id = ?\n and not exists (\n select 1 from a2_snapshot_pins pin\n where pin.session_id = a2_snapshot_history.session_id\n and pin.reducer_name = a2_snapshot_history.reducer_name\n and pin.up_to_index = a2_snapshot_history.up_to_index\n )`,\n ).run(sessionId)\n if (parent.lane !== null) {\n promoteLaneHead.run(sessionId, sessionId, parent.lane)\n }\n const inserted = events.map((event, offset) => {\n const id = event.id\n const childIndex = Number(status.max) + offset + 1\n const cause = { index, attempt }\n insertEvent.run(\n sessionId,\n childIndex,\n event.type,\n JSON.stringify(event.payload) ?? 'null',\n id,\n now,\n JSON.stringify(cause),\n event.lane ?? null,\n event.lane ?? null,\n sessionId,\n event.lane ?? null,\n event.settled ? now : null,\n )\n return toStored(\n db\n .prepare(\n 'select * from a2_events where session_id = ? and idx = ?',\n )\n .get(sessionId, childIndex) as EventRow,\n )\n })\n return { outcome: 'completed', events: inserted }\n }),\n )\n },\n\n async failAttempt({ sessionId, index, attempt, error, maxFailures }) {\n return wrap(() =>\n tx(() => {\n const current = db\n .prepare('select * from a2_events where session_id = ? and idx = ?')\n .get(sessionId, index) as EventRow | undefined\n if (!current) {\n throw new TypeError(\n `no event at index ${index} in session '${sessionId}'`,\n )\n }\n const failureCount = Number(current.failure_count)\n if (\n current.processed_at !== null ||\n Number(current.attempt_count) !== attempt\n ) {\n return { outcome: 'superseded', failureCount }\n }\n if (current.failed_at !== null) {\n return { outcome: 'dead_lettered', failureCount }\n }\n if (\n current.claim_holder === null &&\n current.last_failed_attempt !== null &&\n Number(current.last_failed_attempt) === attempt\n ) {\n return { outcome: 'failed', failureCount }\n }\n if (current.claim_holder === null) {\n return { outcome: 'superseded', failureCount }\n }\n\n const now = clock.now().getTime()\n const nextFailureCount = failureCount + 1\n const deadLettered = nextFailureCount >= maxFailures\n db.prepare(\n `update a2_events set\n failure_count = ?,\n last_error = ?,\n last_failed_at = ?,\n last_failed_attempt = ?,\n failed_at = ?,\n claim_holder = null,\n claim_expires_at = null\n where session_id = ? and idx = ?`,\n ).run(\n nextFailureCount,\n error,\n now,\n attempt,\n deadLettered ? now : null,\n sessionId,\n index,\n )\n if (deadLettered) {\n db.prepare(\n 'delete from a2_snapshot_pins where session_id = ? and event_index = ?',\n ).run(sessionId, index)\n db.prepare(\n `delete from a2_snapshot_history\n where session_id = ?\n and not exists (\n select 1 from a2_snapshot_pins pin\n where pin.session_id = a2_snapshot_history.session_id\n and pin.reducer_name = a2_snapshot_history.reducer_name\n and pin.up_to_index = a2_snapshot_history.up_to_index\n )`,\n ).run(sessionId)\n }\n return {\n outcome: deadLettered ? 'dead_lettered' : 'failed',\n failureCount: nextFailureCount,\n }\n }),\n )\n },\n\n async readState(sessionId, reducerName, stateOptions) {\n return wrap(() => {\n const throughIndex = stateOptions?.throughIndex ?? null\n const snapshotThroughIndex =\n stateOptions?.snapshotThroughIndex ?? throughIndex\n const rows = db\n .prepare(\n `with head as materialized (\n select up_to_index, state, updated_at\n from a2_snapshots\n where session_id = ? and reducer_name = ?\n ), snapshot as materialized (\n select up_to_index, state from (\n select up_to_index, state from head\n where (? is null or up_to_index <= ?)\n union all\n select up_to_index, state\n from a2_snapshot_history\n where session_id = ? and reducer_name = ?\n and (? is null or up_to_index <= ?)\n ) order by up_to_index desc limit 1\n )\n select 0 as row_order, 'snapshot' as row_kind,\n (select up_to_index from head) as head_index,\n (select up_to_index from snapshot) as snapshot_index,\n (select state from snapshot) as snapshot_state,\n null as session_id, null as idx, null as event_type,\n null as payload, null as event_id, null as created_at,\n null as cause, null as lane, null as lane_ready,\n null as processed_at,\n null as processed_by_attempt, null as returned_event_ids,\n null as first_claimed_at,\n null as last_claimed_at, null as attempt_count,\n null as failure_count, null as last_failed_at,\n null as last_failed_attempt, null as last_error,\n null as failed_at, null as claim_holder,\n null as claim_expires_at\n union all\n select 1 as row_order, 'event' as row_kind,\n (select up_to_index from head) as head_index,\n null as snapshot_index, null as snapshot_state,\n event.*\n from a2_events as event\n where event.session_id = ?\n and event.idx > coalesce((select up_to_index from snapshot), 0)\n and (? is null or event.idx <= ?)\n order by row_order, idx`,\n )\n .all(\n sessionId,\n reducerName,\n snapshotThroughIndex,\n snapshotThroughIndex,\n sessionId,\n reducerName,\n snapshotThroughIndex,\n snapshotThroughIndex,\n sessionId,\n throughIndex,\n throughIndex,\n ) as unknown as StateReadRow[]\n const snapshot = rows.find((row) => row.row_kind === 'snapshot')\n return {\n headIndex:\n snapshot?.head_index === null || snapshot?.head_index === undefined\n ? null\n : Number(snapshot.head_index),\n snapshot:\n snapshot?.snapshot_index !== null &&\n snapshot?.snapshot_index !== undefined\n ? {\n index: Number(snapshot.snapshot_index),\n state: JSON.parse(snapshot.snapshot_state!),\n }\n : null,\n events: rows\n .filter(\n (row): row is StateReadRow & EventRow => row.row_kind === 'event',\n )\n .map(toEvent),\n }\n })\n },\n\n async putSnapshots(sessionId, reducerName, writes) {\n await wrap(() =>\n tx(() => {\n const selectHead = db.prepare(\n `select up_to_index, state, updated_at from a2_snapshots\n where session_id = ? and reducer_name = ?`,\n )\n const unfinished = db.prepare(\n `select 1 from a2_events\n where session_id = ? and idx = ?\n and processed_at is null and failed_at is null`,\n )\n const insertPin = db.prepare(\n `insert or ignore into a2_snapshot_pins\n (session_id, event_index, reducer_name, up_to_index)\n values (?, ?, ?, ?)`,\n )\n const hasPin = db.prepare(\n `select 1 from a2_snapshot_pins\n where session_id = ? and reducer_name = ? and up_to_index = ?\n limit 1`,\n )\n const putHistory = db.prepare(\n `insert into a2_snapshot_history\n (session_id, reducer_name, up_to_index, state, updated_at)\n values (?, ?, ?, ?, ?)\n on conflict (session_id, reducer_name, up_to_index) do update set\n state = excluded.state, updated_at = excluded.updated_at`,\n )\n const putHead = db.prepare(\n `insert into a2_snapshots\n (session_id, reducer_name, up_to_index, state, updated_at)\n values (?, ?, ?, ?, ?)\n on conflict (session_id, reducer_name) do update set\n up_to_index = excluded.up_to_index,\n state = excluded.state,\n updated_at = excluded.updated_at\n where excluded.up_to_index >= a2_snapshots.up_to_index`,\n )\n const deleteHistory = db.prepare(\n `delete from a2_snapshot_history\n where session_id = ? and reducer_name = ? and up_to_index = ?`,\n )\n for (const write of writes.toSorted((a, b) => a.index - b.index)) {\n for (const eventIndex of write.pinEventIndexes ?? []) {\n if (unfinished.get(sessionId, eventIndex)) {\n insertPin.run(sessionId, eventIndex, reducerName, write.index)\n }\n }\n const head = selectHead.get(sessionId, reducerName) as\n SnapshotHeadRow | undefined\n const now = clock.now().getTime()\n if (!head || write.index >= Number(head.up_to_index)) {\n if (\n head &&\n write.index > Number(head.up_to_index) &&\n hasPin.get(sessionId, reducerName, Number(head.up_to_index))\n ) {\n putHistory.run(\n sessionId,\n reducerName,\n Number(head.up_to_index),\n head.state,\n Number(head.updated_at),\n )\n }\n putHead.run(\n sessionId,\n reducerName,\n write.index,\n JSON.stringify(write.state) ?? 'null',\n now,\n )\n deleteHistory.run(sessionId, reducerName, write.index)\n } else if (\n write.index < Number(head.up_to_index) &&\n hasPin.get(sessionId, reducerName, write.index)\n ) {\n putHistory.run(\n sessionId,\n reducerName,\n write.index,\n JSON.stringify(write.state) ?? 'null',\n now,\n )\n }\n }\n }),\n )\n },\n\n presence: {\n async set(ns, participant, values, meta) {\n const entries = Object.entries(values)\n if (entries.length === 0) return\n wrap(() =>\n tx(() => {\n const atMs = meta.at.getTime()\n // Expiry anchors on the storage clock — the sender's `at`\n // orders writes but never anchors their lifetime.\n const expiresAtMs = clock.now().getTime() + meta.ttlMs\n for (const [field, value] of entries) {\n // Field-wise LWW: a stored row with a strictly newer `at`\n // survives; ties go to the incoming write (memory parity).\n if (value === null) {\n deletePresence.run(ns, participant, field, atMs)\n } else {\n upsertPresence.run(\n ns,\n participant,\n field,\n JSON.stringify(value) ?? 'null',\n meta.seen,\n atMs,\n expiresAtMs,\n )\n }\n }\n }),\n )\n },\n\n async read(ns) {\n return wrap(() => {\n const now = clock.now().getTime()\n // The sweep is opportunistic cleanup; the select's filter\n // alone keeps expired rows out of this result.\n sweepPresence.run(ns, now)\n const rows = selectPresence.all(ns, now) as unknown as PresenceDbRow[]\n return rows.map((row) => ({\n participant: row.participant,\n field: row.field,\n value: JSON.parse(row.value) as unknown,\n seen: Number(row.seen),\n at: new Date(Number(row.at)),\n expiresAt: new Date(Number(row.expires_at)),\n }))\n })\n },\n },\n\n stream(sessionId, opts) {\n const readAfter = (afterIndex: number): StoredEvent[] =>\n wrap(() => {\n const rows = db\n .prepare(\n 'select * from a2_events where session_id = ? and idx > ? order by idx',\n )\n .all(sessionId, afterIndex) as unknown as EventRow[]\n return rows.map(toStored)\n })\n return pollingStream(\n readAfter,\n opts?.startAfter !== undefined ? { startAfter: opts.startAfter } : {},\n )\n },\n\n close() {\n db.close()\n },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"store-sqlite.js","names":[],"sources":["../src/store-sqlite.ts"],"sourcesContent":["/**\n * experimental-a2/store-sqlite — sqlite store backend (the dev default, `.a2/dev.db`).\n *\n * Built on `node:sqlite` (Node ≥ 22.13) so it ships with zero\n * dependencies. Implements the A2Store interface; the conformance suite\n * in test/conformance is the executable contract.\n *\n * The spec's `index` column is stored as `idx` (a2-implementation.md\n * §11 — `index` collides with a reserved word) and mapped back at the\n * API boundary. Timestamps are epoch milliseconds written from the\n * injected clock — never SQL `now()` — so tests can time-travel against\n * real storage.\n */\n\nimport { mkdirSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { DatabaseSync } from 'node:sqlite'\nimport { A2Error, supersededAttemptError } from './errors.ts'\nimport { CLEAR_EVENT_TYPE, sessionClearedError } from './session-clear.ts'\nimport { idempotentReplay } from './idempotent-replay.ts'\nimport { decodeReturnedEventIds } from './store-codec.ts'\nimport { pollingStream } from './store-polling.ts'\nimport {\n RANDOM_IDS,\n SYSTEM_CLOCK,\n type A2Store,\n type Clock,\n type Event,\n type EventCause,\n type IdSource,\n type StoredEvent,\n} from './store.ts'\n\nexport type SqliteStoreOptions = {\n /** Database file path. Defaults to `.a2/dev.db`; `:memory:` works. */\n path?: string\n /** Injectable clock — every stored timestamp comes from here. */\n clock?: Clock\n /** Injectable id source for generated event ids. */\n ids?: IdSource\n}\n\nexport type SqliteStore = A2Store & {\n /** Close the underlying database handle. */\n close(): void\n}\n\nconst SCHEMA = `\ncreate table if not exists a2_events (\n session_id text not null,\n idx integer not null,\n event_type text not null,\n payload text not null,\n event_id text not null,\n created_at integer not null,\n cause text,\n lane text,\n lane_ready integer not null default 0,\n processed_at integer,\n processed_by_attempt integer,\n returned_event_ids text,\n first_claimed_at integer,\n last_claimed_at integer,\n attempt_count integer not null default 0,\n failure_count integer not null default 0,\n last_failed_at integer,\n last_failed_attempt integer,\n last_error text,\n failed_at integer,\n claim_holder text,\n claim_expires_at integer,\n primary key (session_id, idx)\n) strict;\n\ncreate unique index if not exists a2_events_event_id on a2_events (event_id);\ncreate index if not exists a2_events_unprocessed\n on a2_events (session_id, idx) where processed_at is null;\ncreate index if not exists a2_events_lane_pending\n on a2_events (session_id, lane, idx) where processed_at is null;\ncreate index if not exists a2_events_dispatch_ready\n on a2_events (session_id, idx)\n where lane_ready = 1 and processed_at is null and failed_at is null;\ncreate index if not exists a2_events_claim_expiry\n on a2_events (session_id, claim_expires_at)\n where lane_ready = 1 and processed_at is null and failed_at is null;\n\ncreate table if not exists a2_snapshots (\n session_id text not null,\n reducer_name text not null,\n up_to_index integer not null,\n state text not null,\n updated_at integer not null,\n primary key (session_id, reducer_name)\n) strict;\n\ncreate table if not exists a2_snapshot_history (\n session_id text not null,\n reducer_name text not null,\n up_to_index integer not null,\n state text not null,\n updated_at integer not null,\n primary key (session_id, reducer_name, up_to_index)\n) strict;\n\ncreate table if not exists a2_snapshot_pins (\n session_id text not null,\n event_index integer not null,\n reducer_name text not null,\n up_to_index integer not null,\n primary key (session_id, event_index, reducer_name, up_to_index)\n) strict;\n\ncreate index if not exists a2_snapshot_pins_checkpoint\n on a2_snapshot_pins (session_id, reducer_name, up_to_index);\n\ncreate table if not exists a2_clears (\n session_id text not null primary key,\n clear_index integer not null\n) strict;\n\ncreate table if not exists a2_presence (\n ns text not null,\n participant text not null,\n field text not null,\n value text not null,\n seen integer not null,\n at integer not null,\n expires_at integer not null,\n primary key (ns, participant, field)\n) strict;\n`\n\n/**\n * Switching journal modes takes an exclusive lock, and — unlike normal\n * statements — the switch can return SQLITE_BUSY without consulting the\n * busy handler while several connections race it (instances cold-booting\n * against one file). Bounded synchronous retry; the window is boot-only\n * and tiny. Found by the multi-process torture test.\n */\nconst setWalJournalMode = (db: DatabaseSync): void => {\n for (let attempt = 0; ; attempt += 1) {\n try {\n db.exec('pragma journal_mode = wal')\n return\n } catch (err) {\n const busy = (err as { errcode?: number }).errcode === 5\n if (!busy || attempt >= 100) throw err\n // Synchronous 5ms sleep — sqlite() is a sync constructor.\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5)\n }\n }\n}\n\nconst wrap = <T>(fn: () => T): T => {\n try {\n return fn()\n } catch (err) {\n if (err instanceof A2Error || err instanceof TypeError) throw err\n throw new A2Error('STORE_UNAVAILABLE', 'sqlite store operation failed', {\n cause: err,\n })\n }\n}\n\nconst toDate = (ms: number | bigint | null): Date | null =>\n ms === null ? null : new Date(Number(ms))\n\nconst toCause = (json: string | null): EventCause | null => {\n if (json === null) return null\n const value = JSON.parse(json) 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 toStored = (row: EventRow): StoredEvent => ({\n id: row.event_id,\n type: row.event_type,\n payload: JSON.parse(row.payload),\n index: Number(row.idx),\n sessionId: row.session_id,\n createdAt: new Date(Number(row.created_at)),\n cause: toCause(row.cause),\n lane: row.lane,\n processedAt: toDate(row.processed_at),\n processedByAttempt:\n row.processed_by_attempt === null ? null : Number(row.processed_by_attempt),\n returnedEventIds:\n row.returned_event_ids === null\n ? null\n : decodeReturnedEventIds(row.returned_event_ids),\n firstClaimedAt: toDate(row.first_claimed_at),\n lastClaimedAt: toDate(row.last_claimed_at),\n attemptCount: Number(row.attempt_count),\n failureCount: Number(row.failure_count),\n lastFailedAt: toDate(row.last_failed_at),\n lastFailedAttempt:\n row.last_failed_attempt === null ? null : Number(row.last_failed_attempt),\n lastError: row.last_error,\n failedAt: toDate(row.failed_at),\n claimHolder: row.claim_holder,\n claimExpiresAt: toDate(row.claim_expires_at),\n})\n\nconst toEvent = (row: EventRow): Event => ({\n id: row.event_id,\n type: row.event_type,\n payload: JSON.parse(row.payload),\n index: Number(row.idx),\n sessionId: row.session_id,\n createdAt: new Date(Number(row.created_at)),\n})\n\ntype EventRow = {\n session_id: string\n idx: number | bigint\n event_type: string\n payload: string\n event_id: string\n created_at: number | bigint\n cause: string | null\n lane: string | null\n processed_at: number | bigint | null\n processed_by_attempt: number | bigint | null\n returned_event_ids: string | null\n first_claimed_at: number | bigint | null\n last_claimed_at: number | bigint | null\n attempt_count: number | bigint\n failure_count: number | bigint\n last_failed_at: number | bigint | null\n last_failed_attempt: number | bigint | null\n last_error: string | null\n failed_at: number | bigint | null\n claim_holder: string | null\n claim_expires_at: number | bigint | null\n}\n\ntype PresenceDbRow = {\n participant: string\n field: string\n value: string\n seen: number | bigint\n at: number | bigint\n expires_at: number | bigint\n}\n\ntype StateReadRow = Partial<EventRow> & {\n row_kind: 'snapshot' | 'event'\n clear_index: number | bigint | null\n head_index: number | bigint | null\n snapshot_index: number | bigint | null\n snapshot_state: string | null\n}\n\ntype SnapshotHeadRow = {\n up_to_index: number | bigint\n state: string\n updated_at: number | bigint\n}\n\ntype AppendStatusRow = {\n max: number | bigint\n has_pending: number | bigint\n}\n\ntype ExistingEventRow = EventRow & {\n session_has_pending: number | bigint\n}\n\nconst RECLAIM_BATCH = 256\n\nexport function sqlite(options: SqliteStoreOptions = {}): SqliteStore {\n const path = options.path ?? '.a2/dev.db'\n const clock = options.clock ?? SYSTEM_CLOCK\n const generateId = options.ids ?? RANDOM_IDS\n\n if (path !== ':memory:') {\n // The dev-default backend only ever runs outside production, but\n // bundlers can't know that: without the ignore, Turbopack sees a\n // dynamic fs path and traces the entire project into the output.\n mkdirSync(dirname(resolve(/* turbopackIgnore: true */ path)), {\n recursive: true,\n })\n }\n const db = new DatabaseSync(path)\n // busy_timeout before anything that takes locks: concurrent\n // cold-opens (several instances booting at once) race the WAL switch\n // and schema creation.\n db.exec('pragma busy_timeout = 5000')\n setWalJournalMode(db)\n db.exec('pragma synchronous = normal')\n db.exec(SCHEMA)\n\n const insertEvent = db.prepare(\n `insert into a2_events\n (session_id, idx, event_type, payload, event_id, created_at,\n cause, lane, lane_ready, processed_at)\n select ?, ?, ?, ?, ?, ?, ?, ?,\n case\n when ? is null then 1\n when not exists (\n select 1 from a2_events\n where session_id = ? and lane = ? and processed_at is null\n and idx > ?\n ) then 1\n else 0\n end,\n ?`,\n )\n const promoteLaneHead = db.prepare(\n `update a2_events set lane_ready = 1\n where session_id = ?\n and idx = (\n select idx from a2_events\n where session_id = ? and lane = ? and processed_at is null\n and idx > ?\n order by idx limit 1\n )`,\n )\n const appendStatus = db.prepare(\n `select coalesce(\n (select max(idx) from a2_events where session_id = ?),\n 0\n ) as max,\n exists(\n select 1 from a2_events\n where session_id = ? and processed_at is null and idx > ?\n ) as has_pending`,\n )\n const upsertPresence = db.prepare(\n `insert into a2_presence (ns, participant, field, value, seen, at, expires_at)\n values (?, ?, ?, ?, ?, ?, ?)\n on conflict (ns, participant, field) do update set\n value = excluded.value,\n seen = excluded.seen,\n at = excluded.at,\n expires_at = excluded.expires_at\n where a2_presence.at <= excluded.at`,\n )\n const deletePresence = db.prepare(\n `delete from a2_presence\n where ns = ? and participant = ? and field = ? and at <= ?`,\n )\n const sweepPresence = db.prepare(\n 'delete from a2_presence where ns = ? and expires_at <= ?',\n )\n const selectPresence = db.prepare(\n `select participant, field, value, seen, at, expires_at\n from a2_presence\n where ns = ? and expires_at > ?\n order by participant, field`,\n )\n const selectByIds = (count: number) =>\n db.prepare(\n `select stored.*,\n exists(\n select 1 from a2_events pending\n where pending.session_id = ?\n and pending.processed_at is null\n and pending.idx > ?\n ) as session_has_pending\n from a2_events stored\n where stored.event_id in (${Array.from({ length: count }, () => '?').join(', ')})\n order by stored.idx`,\n )\n\n const selectClearIndex = db.prepare(\n 'select clear_index from a2_clears where session_id = ?',\n )\n const clearIndexOf = (sessionId: string): number =>\n Number(\n (\n selectClearIndex.get(sessionId) as\n { clear_index: number | bigint } | undefined\n )?.clear_index ?? 0,\n )\n const deleteEventById = db.prepare(\n 'delete from a2_events where session_id = ? and event_id = ?',\n )\n\n /**\n * The rows owning `ids`. This session's rows below its clear marker do\n * not own their ids: they are deleted here so the ids can be reused.\n */\n const liveOwnersOf = (\n sessionId: string,\n clearIndex: number,\n ids: readonly string[],\n ): ExistingEventRow[] => {\n if (ids.length === 0) return []\n const rows = selectByIds(ids.length).all(\n sessionId,\n clearIndex,\n ...ids,\n ) as unknown as ExistingEventRow[]\n return rows.filter((row) => {\n if (row.session_id !== sessionId || Number(row.idx) >= clearIndex)\n return true\n deleteEventById.run(sessionId, row.event_id)\n return false\n })\n }\n\n const selectCauseParent = db.prepare(\n 'select attempt_count, failed_at from a2_events where session_id = ? and idx = ?',\n )\n /** The attempt-currency fence shared by handler appends and clears. */\n const assertCurrentCause = ({\n sessionId,\n cause,\n clearIndex,\n }: {\n sessionId: string\n cause: EventCause\n clearIndex: number\n }): void => {\n if (cause.index <= clearIndex)\n throw supersededAttemptError(sessionId, cause)\n const parent = selectCauseParent.get(sessionId, cause.index) as\n { attempt_count: number | bigint; failed_at: unknown } | undefined\n if (!parent) {\n throw new TypeError(\n `no event at index ${cause.index} in session '${sessionId}'`,\n )\n }\n if (\n Number(parent.attempt_count) !== cause.attempt ||\n parent.failed_at !== null\n ) {\n throw supersededAttemptError(sessionId, cause)\n }\n }\n\n const tx = <T>(fn: () => T): T => {\n db.exec('begin immediate')\n try {\n const result = fn()\n db.exec('commit')\n return result\n } catch (err) {\n try {\n db.exec('rollback')\n } catch {\n // Rollback can fail if the transaction never started; the\n // original error is what matters.\n }\n throw err\n }\n }\n\n return {\n async append(sessionId, events, appendOptions) {\n if (events.length === 0) {\n return wrap(() => {\n const status = appendStatus.get(\n sessionId,\n sessionId,\n clearIndexOf(sessionId),\n ) as AppendStatusRow\n return {\n events: [],\n hasPending: Number(status.has_pending) === 1,\n }\n })\n }\n return wrap(() =>\n tx(() => {\n const clearIndex = clearIndexOf(sessionId)\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 if (suppliedIds.length > 0) {\n const existing = liveOwnersOf(sessionId, clearIndex, suppliedIds)\n if (existing.length > 0) {\n const foreign = existing.find(\n (row) => row.session_id !== sessionId,\n )\n if (foreign) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n `event id '${foreign.event_id}' already exists in another session`,\n )\n }\n if (existing.length === events.length) {\n return {\n events: idempotentReplay(events, existing.map(toStored)),\n hasPending: Number(existing[0]!.session_has_pending) === 1,\n }\n }\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n `batch mixes ${existing.length} already-appended and ${events.length - existing.length} fresh events`,\n )\n }\n }\n\n const originIndex = appendOptions?.originIndex\n if (originIndex !== undefined && originIndex <= clearIndex) {\n throw sessionClearedError(\n `scheduled work from event ${originIndex} was cleared in session '${sessionId}'`,\n )\n }\n // Attempt-currency fence: a fresh handler append commits only\n // while its causal attempt is still the parent's latest\n // (store.ts `append`). `begin immediate` serializes this read\n // against a concurrent claim.\n for (const e of events) {\n if (e.cause)\n assertCurrentCause({ sessionId, cause: e.cause, clearIndex })\n }\n\n const status = appendStatus.get(\n sessionId,\n sessionId,\n clearIndex,\n ) as AppendStatusRow\n const base = Number(status.max)\n const now = clock.now().getTime()\n const inserted: StoredEvent[] = events.map((e, i) => {\n const id = e.id ?? generateId()\n const index = base + 1 + i\n insertEvent.run(\n sessionId,\n index,\n e.type,\n JSON.stringify(e.payload) ?? 'null',\n id,\n now,\n e.cause ? JSON.stringify(e.cause) : null,\n e.lane ?? null,\n e.lane ?? null,\n sessionId,\n e.lane ?? null,\n clearIndex,\n e.settled ? now : null,\n )\n return {\n id,\n type: e.type,\n payload: structuredClone(e.payload),\n index,\n sessionId,\n createdAt: new Date(now),\n cause: e.cause ? { ...e.cause } : null,\n lane: e.lane ?? null,\n processedAt: e.settled ? new Date(now) : null,\n processedByAttempt: null,\n returnedEventIds: null,\n firstClaimedAt: null,\n lastClaimedAt: null,\n attemptCount: 0,\n failureCount: 0,\n lastFailedAt: null,\n lastFailedAttempt: null,\n lastError: null,\n failedAt: null,\n claimHolder: null,\n claimExpiresAt: null,\n }\n })\n return {\n events: inserted,\n hasPending:\n Number(status.has_pending) === 1 ||\n events.some((event) => event.settled !== true),\n }\n }),\n )\n },\n\n async read(sessionId, opts) {\n return wrap(() => {\n // Reads start at the latest clear marker.\n const conditions = [\n 'session_id = ?',\n 'idx >= coalesce((select clear_index from a2_clears where session_id = ?), 0)',\n ]\n const params: (string | number)[] = [sessionId, sessionId]\n if (opts?.afterIndex !== undefined) {\n conditions.push('idx > ?')\n params.push(opts.afterIndex)\n }\n if (opts?.throughIndex !== undefined) {\n conditions.push('idx <= ?')\n params.push(opts.throughIndex)\n }\n const rows = db\n .prepare(\n `select * from a2_events where ${conditions.join(' and ')} order by idx`,\n )\n .all(...params) as unknown as EventRow[]\n return rows.map(toStored)\n })\n },\n\n async claimAvailable({\n sessionId,\n holder,\n ttlMs,\n expiresAtMs,\n excludeIndexes = [],\n }) {\n return wrap(() =>\n tx(() => {\n const now = clock.now().getTime()\n const expiresAt = expiresAtMs ?? now + ttlMs\n const clearIndex = clearIndexOf(sessionId)\n const exclusion =\n excludeIndexes.length === 0\n ? ''\n : `and event.idx not in (${excludeIndexes.map(() => '?').join(', ')})`\n const eligible = db\n .prepare(\n `select event.idx\n from a2_events event\n where event.session_id = ?\n and event.processed_at is null\n and event.failed_at is null\n and event.lane_ready = 1\n and event.idx > ?\n and (event.claim_expires_at is null or event.claim_expires_at <= ?)\n ${exclusion}\n order by event.idx`,\n )\n .all(\n sessionId,\n clearIndex,\n now,\n ...excludeIndexes,\n ) as unknown as Array<{\n idx: number | bigint\n }>\n if (eligible.length > 0) {\n const update = db.prepare(\n `update a2_events set\n attempt_count = attempt_count + 1,\n first_claimed_at = coalesce(first_claimed_at, ?),\n last_claimed_at = ?,\n claim_holder = ?,\n claim_expires_at = ?\n where session_id = ? and idx = ?\n returning *`,\n )\n const events = eligible.map((row) => {\n const claimed = update.get(\n now,\n now,\n holder,\n expiresAt,\n sessionId,\n row.idx,\n ) as EventRow\n const stored = toStored(claimed)\n if (clearIndex > 0) stored.clearIndex = clearIndex\n return stored\n })\n return { outcome: 'claimed', events }\n }\n\n const active = db\n .prepare(\n `select min(claim_expires_at) as retry_at\n from a2_events\n where session_id = ?\n and processed_at is null\n and failed_at is null\n and lane_ready = 1\n and idx > ?\n and claim_expires_at > ?`,\n )\n .get(sessionId, clearIndex, now) as {\n retry_at: number | bigint | null\n }\n return active.retry_at === null\n ? { outcome: 'settled' }\n : { outcome: 'busy', retryAt: new Date(Number(active.retry_at)) }\n }),\n )\n },\n\n async renewClaims({ sessionId, holder, claims, ttlMs, expiresAtMs }) {\n if (claims.length === 0) return { renewed: [], superseded: [] }\n return wrap(() =>\n tx(() => {\n const now = clock.now().getTime()\n const expiresAt = expiresAtMs ?? now + ttlMs\n const renewed: number[] = []\n const superseded: number[] = []\n const currentAttempt = db.prepare(\n 'select attempt_count from a2_events where session_id = ? and idx = ?',\n )\n const extend = db.prepare(\n `update a2_events set claim_expires_at = ?\n where session_id = ?\n and idx = ?\n and attempt_count = ?\n and processed_at is null\n and failed_at is null\n and claim_holder = ?\n and claim_expires_at > ?`,\n )\n const clearIndex = clearIndexOf(sessionId)\n for (const claim of claims) {\n if (claim.index <= clearIndex) {\n superseded.push(claim.index)\n continue\n }\n const row = currentAttempt.get(sessionId, claim.index) as\n { attempt_count: number | bigint } | undefined\n if (!row) continue\n if (Number(row.attempt_count) > claim.attempt) {\n superseded.push(claim.index)\n continue\n }\n const updated = extend.run(\n expiresAt,\n sessionId,\n claim.index,\n claim.attempt,\n holder,\n now,\n )\n if (Number(updated.changes) === 1) renewed.push(claim.index)\n }\n return {\n renewed: renewed.toSorted((a, b) => a - b),\n superseded: superseded.toSorted((a, b) => a - b),\n }\n }),\n )\n },\n\n async completeAttempt({ sessionId, index, attempt, events }) {\n return wrap(() =>\n tx(() => {\n const clearIndex = clearIndexOf(sessionId)\n if (index <= clearIndex) return { outcome: 'superseded' }\n const parent = db\n .prepare('select * from a2_events where session_id = ? and idx = ?')\n .get(sessionId, index) as EventRow | undefined\n if (!parent) {\n throw new TypeError(\n `no event at index ${index} in session '${sessionId}'`,\n )\n }\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 'returned event batch contains the same event id more than once',\n )\n }\n\n if (parent.processed_at !== null) {\n if (Number(parent.processed_by_attempt) !== attempt) {\n return { outcome: 'superseded' }\n }\n const returnedIds =\n parent.returned_event_ids === null\n ? null\n : decodeReturnedEventIds(parent.returned_event_ids)\n if (\n returnedIds === null ||\n returnedIds.length !== ids.length ||\n returnedIds.some((id, offset) => id !== ids[offset])\n ) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'completed attempt does not match the returned event batch',\n )\n }\n const existing =\n ids.length === 0\n ? []\n : (selectByIds(ids.length).all(\n sessionId,\n clearIndex,\n ...ids,\n ) as unknown as ExistingEventRow[])\n const byId = new Map(existing.map((row) => [row.event_id, row]))\n const ordered = ids.map((id) => byId.get(id))\n if (\n ordered.some(\n (row) =>\n !row ||\n row.session_id !== sessionId ||\n row.cause === null ||\n toCause(row.cause)?.index !== index ||\n toCause(row.cause)?.attempt !== attempt,\n )\n ) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'returned event retry does not match the committed batch',\n )\n }\n return {\n outcome: 'completed',\n events: idempotentReplay(\n events,\n (ordered as ExistingEventRow[]).map(toStored),\n ),\n }\n }\n if (\n Number(parent.attempt_count) !== attempt ||\n parent.claim_holder === null ||\n parent.failed_at !== null\n ) {\n return { outcome: 'superseded' }\n }\n\n if (liveOwnersOf(sessionId, clearIndex, ids).length > 0) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'returned event batch contains an already-appended event id',\n )\n }\n\n const status = appendStatus.get(\n sessionId,\n sessionId,\n clearIndex,\n ) as AppendStatusRow\n const now = clock.now().getTime()\n db.prepare(\n `update a2_events set\n processed_at = ?,\n processed_by_attempt = ?,\n returned_event_ids = ?,\n lane_ready = 0,\n claim_holder = null,\n claim_expires_at = null\n where session_id = ? and idx = ?`,\n ).run(now, attempt, JSON.stringify(ids), sessionId, index)\n db.prepare(\n 'delete from a2_snapshot_pins where session_id = ? and event_index = ?',\n ).run(sessionId, index)\n db.prepare(\n `delete from a2_snapshot_history\n where session_id = ?\n and not exists (\n select 1 from a2_snapshot_pins pin\n where pin.session_id = a2_snapshot_history.session_id\n and pin.reducer_name = a2_snapshot_history.reducer_name\n and pin.up_to_index = a2_snapshot_history.up_to_index\n )`,\n ).run(sessionId)\n if (parent.lane !== null) {\n promoteLaneHead.run(sessionId, sessionId, parent.lane, clearIndex)\n }\n const inserted = events.map((event, offset) => {\n const id = event.id\n const childIndex = Number(status.max) + offset + 1\n const cause = { index, attempt }\n insertEvent.run(\n sessionId,\n childIndex,\n event.type,\n JSON.stringify(event.payload) ?? 'null',\n id,\n now,\n JSON.stringify(cause),\n event.lane ?? null,\n event.lane ?? null,\n sessionId,\n event.lane ?? null,\n clearIndex,\n event.settled ? now : null,\n )\n return toStored(\n db\n .prepare(\n 'select * from a2_events where session_id = ? and idx = ?',\n )\n .get(sessionId, childIndex) as EventRow,\n )\n })\n return { outcome: 'completed', events: inserted }\n }),\n )\n },\n\n async failAttempt({ sessionId, index, attempt, error, maxFailures }) {\n return wrap(() =>\n tx(() => {\n if (index <= clearIndexOf(sessionId)) {\n return { outcome: 'superseded', failureCount: 0 }\n }\n const current = db\n .prepare('select * from a2_events where session_id = ? and idx = ?')\n .get(sessionId, index) as EventRow | undefined\n if (!current) {\n throw new TypeError(\n `no event at index ${index} in session '${sessionId}'`,\n )\n }\n const failureCount = Number(current.failure_count)\n if (\n current.processed_at !== null ||\n Number(current.attempt_count) !== attempt\n ) {\n return { outcome: 'superseded', failureCount }\n }\n if (current.failed_at !== null) {\n return { outcome: 'dead_lettered', failureCount }\n }\n if (\n current.claim_holder === null &&\n current.last_failed_attempt !== null &&\n Number(current.last_failed_attempt) === attempt\n ) {\n return { outcome: 'failed', failureCount }\n }\n if (current.claim_holder === null) {\n return { outcome: 'superseded', failureCount }\n }\n\n const now = clock.now().getTime()\n const nextFailureCount = failureCount + 1\n const deadLettered = nextFailureCount >= maxFailures\n db.prepare(\n `update a2_events set\n failure_count = ?,\n last_error = ?,\n last_failed_at = ?,\n last_failed_attempt = ?,\n failed_at = ?,\n claim_holder = null,\n claim_expires_at = null\n where session_id = ? and idx = ?`,\n ).run(\n nextFailureCount,\n error,\n now,\n attempt,\n deadLettered ? now : null,\n sessionId,\n index,\n )\n if (deadLettered) {\n db.prepare(\n 'delete from a2_snapshot_pins where session_id = ? and event_index = ?',\n ).run(sessionId, index)\n db.prepare(\n `delete from a2_snapshot_history\n where session_id = ?\n and not exists (\n select 1 from a2_snapshot_pins pin\n where pin.session_id = a2_snapshot_history.session_id\n and pin.reducer_name = a2_snapshot_history.reducer_name\n and pin.up_to_index = a2_snapshot_history.up_to_index\n )`,\n ).run(sessionId)\n }\n return {\n outcome: deadLettered ? 'dead_lettered' : 'failed',\n failureCount: nextFailureCount,\n }\n }),\n )\n },\n\n async deadLetters(sessionId) {\n return wrap(() => {\n const rows = db\n .prepare(\n `select * from a2_events\n where session_id = ?\n and processed_at is null\n and failed_at is not null\n and idx > coalesce(\n (select clear_index from a2_clears where session_id = ?), 0\n )\n order by idx`,\n )\n .all(sessionId, sessionId) as unknown as EventRow[]\n return rows.map(toStored)\n })\n },\n\n async redrive({ sessionId, index, action }) {\n return wrap(() =>\n tx(() => {\n const clearIndex = clearIndexOf(sessionId)\n const row =\n index > clearIndex\n ? (db\n .prepare(\n 'select lane, processed_at, failed_at from a2_events where session_id = ? and idx = ?',\n )\n .get(sessionId, index) as\n | Pick<EventRow, 'lane' | 'processed_at' | 'failed_at'>\n | undefined)\n : undefined\n const redriven =\n row !== undefined &&\n row.processed_at === null &&\n row.failed_at !== null\n if (redriven && action === 'skip') {\n db.prepare(\n `update a2_events set processed_at = ?, lane_ready = 0\n where session_id = ? and idx = ?`,\n ).run(clock.now().getTime(), sessionId, index)\n if (row.lane !== null) {\n promoteLaneHead.run(sessionId, sessionId, row.lane, clearIndex)\n }\n } else if (redriven) {\n db.prepare(\n `update a2_events set failed_at = null, failure_count = 0\n where session_id = ? and idx = ?`,\n ).run(sessionId, index)\n }\n const status = appendStatus.get(\n sessionId,\n sessionId,\n clearIndex,\n ) as AppendStatusRow\n return { redriven, hasPending: Number(status.has_pending) === 1 }\n }),\n )\n },\n\n async readState(sessionId, reducerName, stateOptions) {\n return wrap(() => {\n const throughIndex = stateOptions?.throughIndex ?? null\n const snapshotThroughIndex =\n stateOptions?.snapshotThroughIndex ?? throughIndex\n const rows = db\n .prepare(\n `with boundary as materialized (\n select coalesce(\n (select clear_index from a2_clears where session_id = ?), 0\n ) as clear_index\n ), head as materialized (\n select up_to_index, state, updated_at\n from a2_snapshots\n where session_id = ? and reducer_name = ?\n and up_to_index >= (select clear_index from boundary)\n ), snapshot as materialized (\n select up_to_index, state from (\n select up_to_index, state from head\n where (? is null or up_to_index <= ?)\n union all\n select up_to_index, state\n from a2_snapshot_history\n where session_id = ? and reducer_name = ?\n and up_to_index >= (select clear_index from boundary)\n and (? is null or up_to_index <= ?)\n ) order by up_to_index desc limit 1\n )\n select 0 as row_order, 'snapshot' as row_kind,\n (select clear_index from boundary) as clear_index,\n (select up_to_index from head) as head_index,\n (select up_to_index from snapshot) as snapshot_index,\n (select state from snapshot) as snapshot_state,\n null as session_id, null as idx, null as event_type,\n null as payload, null as event_id, null as created_at,\n null as cause, null as lane, null as lane_ready,\n null as processed_at,\n null as processed_by_attempt, null as returned_event_ids,\n null as first_claimed_at,\n null as last_claimed_at, null as attempt_count,\n null as failure_count, null as last_failed_at,\n null as last_failed_attempt, null as last_error,\n null as failed_at, null as claim_holder,\n null as claim_expires_at\n union all\n select 1 as row_order, 'event' as row_kind,\n null as clear_index,\n (select up_to_index from head) as head_index,\n null as snapshot_index, null as snapshot_state,\n event.*\n from a2_events as event\n where event.session_id = ?\n and event.idx > max(\n coalesce((select up_to_index from snapshot), 0),\n (select clear_index from boundary) - 1\n )\n and (? is null or event.idx <= ?)\n order by row_order, idx`,\n )\n .all(\n sessionId,\n sessionId,\n reducerName,\n snapshotThroughIndex,\n snapshotThroughIndex,\n sessionId,\n reducerName,\n snapshotThroughIndex,\n snapshotThroughIndex,\n sessionId,\n throughIndex,\n throughIndex,\n ) as unknown as StateReadRow[]\n const snapshot = rows.find((row) => row.row_kind === 'snapshot')\n const clearIndex = Number(snapshot?.clear_index ?? 0)\n return {\n ...(clearIndex > 0 ? { clearIndex } : {}),\n headIndex:\n snapshot?.head_index === null || snapshot?.head_index === undefined\n ? null\n : Number(snapshot.head_index),\n snapshot:\n snapshot?.snapshot_index !== null &&\n snapshot?.snapshot_index !== undefined\n ? {\n index: Number(snapshot.snapshot_index),\n state: JSON.parse(snapshot.snapshot_state!),\n }\n : null,\n events: rows\n .filter(\n (row): row is StateReadRow & EventRow => row.row_kind === 'event',\n )\n .map(toEvent),\n }\n })\n },\n\n async putSnapshots(sessionId, reducerName, writes) {\n await wrap(() =>\n tx(() => {\n const selectHead = db.prepare(\n `select up_to_index, state, updated_at from a2_snapshots\n where session_id = ? and reducer_name = ?`,\n )\n const unfinished = db.prepare(\n `select 1 from a2_events\n where session_id = ? and idx = ? and idx > ?\n and processed_at is null and failed_at is null`,\n )\n const insertPin = db.prepare(\n `insert or ignore into a2_snapshot_pins\n (session_id, event_index, reducer_name, up_to_index)\n values (?, ?, ?, ?)`,\n )\n const hasPin = db.prepare(\n `select 1 from a2_snapshot_pins\n where session_id = ? and reducer_name = ? and up_to_index = ?\n limit 1`,\n )\n const putHistory = db.prepare(\n `insert into a2_snapshot_history\n (session_id, reducer_name, up_to_index, state, updated_at)\n values (?, ?, ?, ?, ?)\n on conflict (session_id, reducer_name, up_to_index) do update set\n state = excluded.state, updated_at = excluded.updated_at`,\n )\n const putHead = db.prepare(\n `insert into a2_snapshots\n (session_id, reducer_name, up_to_index, state, updated_at)\n values (?, ?, ?, ?, ?)\n on conflict (session_id, reducer_name) do update set\n up_to_index = excluded.up_to_index,\n state = excluded.state,\n updated_at = excluded.updated_at\n where excluded.up_to_index >= a2_snapshots.up_to_index`,\n )\n const deleteHistory = db.prepare(\n `delete from a2_snapshot_history\n where session_id = ? and reducer_name = ? and up_to_index = ?`,\n )\n const clearIndex = clearIndexOf(sessionId)\n for (const write of writes.toSorted((a, b) => a.index - b.index)) {\n // A fold from before the latest clear describes a lifetime that is gone.\n if (write.index < clearIndex) continue\n for (const eventIndex of write.pinEventIndexes ?? []) {\n if (unfinished.get(sessionId, eventIndex, clearIndex)) {\n insertPin.run(sessionId, eventIndex, reducerName, write.index)\n }\n }\n const head = selectHead.get(sessionId, reducerName) as\n SnapshotHeadRow | undefined\n const now = clock.now().getTime()\n if (!head || write.index >= Number(head.up_to_index)) {\n if (\n head &&\n write.index > Number(head.up_to_index) &&\n Number(head.up_to_index) >= clearIndex &&\n hasPin.get(sessionId, reducerName, Number(head.up_to_index))\n ) {\n putHistory.run(\n sessionId,\n reducerName,\n Number(head.up_to_index),\n head.state,\n Number(head.updated_at),\n )\n }\n putHead.run(\n sessionId,\n reducerName,\n write.index,\n JSON.stringify(write.state) ?? 'null',\n now,\n )\n deleteHistory.run(sessionId, reducerName, write.index)\n } else if (\n write.index < Number(head.up_to_index) &&\n hasPin.get(sessionId, reducerName, write.index)\n ) {\n putHistory.run(\n sessionId,\n reducerName,\n write.index,\n JSON.stringify(write.state) ?? 'null',\n now,\n )\n }\n }\n }),\n )\n },\n\n presence: {\n async set(ns, participant, values, meta) {\n const entries = Object.entries(values)\n if (entries.length === 0) return\n wrap(() =>\n tx(() => {\n const atMs = meta.at.getTime()\n // Expiry anchors on the storage clock — the sender's `at`\n // orders writes but never anchors their lifetime.\n const expiresAtMs = clock.now().getTime() + meta.ttlMs\n for (const [field, value] of entries) {\n // Field-wise LWW: a stored row with a strictly newer `at`\n // survives; ties go to the incoming write (memory parity).\n if (value === null) {\n deletePresence.run(ns, participant, field, atMs)\n } else {\n upsertPresence.run(\n ns,\n participant,\n field,\n JSON.stringify(value) ?? 'null',\n meta.seen,\n atMs,\n expiresAtMs,\n )\n }\n }\n }),\n )\n },\n\n async read(ns) {\n return wrap(() => {\n const now = clock.now().getTime()\n // The sweep is opportunistic cleanup; the select's filter\n // alone keeps expired rows out of this result.\n sweepPresence.run(ns, now)\n const rows = selectPresence.all(ns, now) as unknown as PresenceDbRow[]\n return rows.map((row) => ({\n participant: row.participant,\n field: row.field,\n value: JSON.parse(row.value) as unknown,\n seen: Number(row.seen),\n at: new Date(Number(row.at)),\n expiresAt: new Date(Number(row.expires_at)),\n }))\n })\n },\n },\n\n stream(sessionId, opts) {\n const readAfter = (afterIndex: number): StoredEvent[] =>\n wrap(() => {\n // A cursor below the latest marker resumes at it.\n const rows = db\n .prepare(\n `select * from a2_events\n where session_id = ? and idx > ?\n and idx >= coalesce((select clear_index from a2_clears where session_id = ?), 0)\n order by idx`,\n )\n .all(sessionId, afterIndex, sessionId) as unknown as EventRow[]\n return rows.map(toStored)\n })\n return pollingStream(\n readAfter,\n opts?.startAfter !== undefined ? { startAfter: opts.startAfter } : {},\n )\n },\n\n async clear({ sessionId, ifLastIndex, cause }) {\n return wrap(() =>\n tx(() => {\n const clearIndex = clearIndexOf(sessionId)\n if (cause) assertCurrentCause({ sessionId, cause, clearIndex })\n const head = Number(\n (\n appendStatus.get(\n sessionId,\n sessionId,\n clearIndex,\n ) as AppendStatusRow\n ).max,\n )\n if (ifLastIndex !== undefined && clearIndex === ifLastIndex + 1) {\n return { cleared: true, index: head, clearIndex }\n }\n if (ifLastIndex !== undefined && ifLastIndex !== head) {\n return { cleared: false, index: head, clearIndex }\n }\n if (head === 0 || clearIndex === head) {\n return { cleared: true, index: head, clearIndex }\n }\n const marker = head + 1\n const now = clock.now().getTime()\n insertEvent.run(\n sessionId,\n marker,\n CLEAR_EVENT_TYPE,\n 'null',\n generateId(),\n now,\n null,\n null,\n null,\n sessionId,\n null,\n clearIndex,\n now,\n )\n db.prepare(\n `insert into a2_clears (session_id, clear_index) values (?, ?)\n on conflict (session_id) do update set clear_index = excluded.clear_index`,\n ).run(sessionId, marker)\n return { cleared: true, index: marker, clearIndex: marker }\n }),\n )\n },\n\n async reclaim(sessionId) {\n return wrap(() =>\n tx(() => {\n const clearIndex = clearIndexOf(sessionId)\n if (clearIndex === 0) return { deleted: 0, done: true }\n const last = db\n .prepare(\n `select max(idx) as last from (\n select idx from a2_events\n where session_id = ? and idx < ?\n order by idx limit ?\n )`,\n )\n .get(sessionId, clearIndex, RECLAIM_BATCH) as {\n last: number | bigint | null\n }\n let deleted = 0\n if (last.last !== null) {\n const through = Number(last.last)\n deleted = Number(\n db\n .prepare(\n 'delete from a2_events where session_id = ? and idx <= ?',\n )\n .run(sessionId, through).changes,\n )\n db.prepare(\n 'delete from a2_snapshot_pins where session_id = ? and event_index <= ?',\n ).run(sessionId, through)\n }\n const done =\n db\n .prepare(\n 'select 1 from a2_events where session_id = ? and idx < ? limit 1',\n )\n .get(sessionId, clearIndex) === undefined\n // Writes serialize with the clear, so none lands below the marker later.\n if (done) {\n for (const table of [\n 'a2_snapshot_pins',\n 'a2_snapshot_history',\n 'a2_snapshots',\n ]) {\n db.prepare(\n `delete from ${table} where session_id = ? and up_to_index < ?`,\n ).run(sessionId, clearIndex)\n }\n }\n return { deleted, done }\n }),\n )\n },\n\n close() {\n db.close()\n },\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ //#region src/stream-buffer.ts
2
+ const isSerializedEvent = (event) => "payloadJson" in event;
3
+ const parseSerializedEvent = (event) => ({
4
+ id: event.id,
5
+ type: event.type,
6
+ payload: JSON.parse(event.payloadJson),
7
+ index: event.index,
8
+ sessionId: event.sessionId,
9
+ createdAt: new Date(event.createdAtMs)
10
+ });
11
+ /**
12
+ * The next event a stream iterator already holds, removed without
13
+ * awaiting its `next()`, which then continues after it; `undefined` when
14
+ * it holds none. Call only while no `next()` is pending. The SSE encoder
15
+ * writes these with the event `next()` just returned, so a buffered
16
+ * backlog leaves in few writes.
17
+ */
18
+ const bufferedEvents = /* @__PURE__ */ new WeakMap();
19
+ const takeBuffered = (iterator) => bufferedEvents.get(iterator)?.();
20
+ //#endregion
21
+ export { takeBuffered as i, isSerializedEvent as n, parseSerializedEvent as r, bufferedEvents as t };
22
+
23
+ //# sourceMappingURL=stream-buffer-Cx7AAPGq.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-buffer-Cx7AAPGq.js","names":[],"sources":["../src/stream-buffer.ts"],"sourcesContent":["import type { Event } from './store.ts'\n\n/**\n * An event as a store returned it, its payload still the JSON text A2's\n * store wrote with `JSON.stringify`. For payloads built from ordinary objects,\n * parsing and re-serializing that text yields the same text, so the SSE lane\n * can write it into a frame unchanged.\n */\nexport type SerializedEvent = {\n id: string\n type: string\n payloadJson: string\n index: number\n sessionId: string\n createdAtMs: number\n}\n\nexport type HeldEvent = Event | SerializedEvent\n\nexport const isSerializedEvent = (event: object): event is SerializedEvent =>\n 'payloadJson' in event\n\nexport const parseSerializedEvent = (event: SerializedEvent): Event => ({\n id: event.id,\n type: event.type,\n payload: JSON.parse(event.payloadJson),\n index: event.index,\n sessionId: event.sessionId,\n createdAt: new Date(event.createdAtMs),\n})\n\n/**\n * The next event a stream iterator already holds, removed without\n * awaiting its `next()`, which then continues after it; `undefined` when\n * it holds none. Call only while no `next()` is pending. The SSE encoder\n * writes these with the event `next()` just returned, so a buffered\n * backlog leaves in few writes.\n */\nexport const bufferedEvents: WeakMap<object, () => HeldEvent | undefined> =\n new WeakMap()\n\nexport const takeBuffered = (iterator: object): HeldEvent | undefined =>\n bufferedEvents.get(iterator)?.()\n"],"mappings":";AAmBA,MAAa,qBAAqB,UAChC,iBAAiB;AAEnB,MAAa,wBAAwB,WAAmC;CACtE,IAAI,MAAM;CACV,MAAM,MAAM;CACZ,SAAS,KAAK,MAAM,MAAM,WAAW;CACrC,OAAO,MAAM;CACb,WAAW,MAAM;CACjB,WAAW,IAAI,KAAK,MAAM,WAAW;AACvC;;;;;;;;AASA,MAAa,iCACX,IAAI,QAAQ;AAEd,MAAa,gBAAgB,aAC3B,eAAe,IAAI,QAAQ,CAAC,GAAG"}
@@ -8,7 +8,7 @@
8
8
  /** Attribute values a2 emits. */
9
9
  type A2AttributeValue = string | number | boolean;
10
10
  /** The spans a2 emits today. The catalogue grows with the surface. */
11
- type A2SpanName = "a2.append" | "a2.drain" | "a2.event" | "a2.state" | "a2.snapshot";
11
+ type A2SpanName = "a2.append" | "a2.drain" | "a2.event" | "a2.claim" | "a2.commit" | "a2.state" | "a2.snapshot" | "a2.redrive" | "a2.ai.control" | "a2.ai.ack" | "a2.ai.generate" | "a2.ai.context" | "a2.ai.compaction" | "a2.ai.tool";
12
12
  type A2SpanHandle = {
13
13
  /** Attach or update an attribute mid-span (e.g. the outcome). */
14
14
  setAttribute(key: string, value: A2AttributeValue): void;
@@ -30,4 +30,4 @@ type A2Telemetry = {
30
30
  };
31
31
  //#endregion
32
32
  export { A2Telemetry as i, A2SpanHandle as n, A2SpanName as r, A2AttributeValue as t };
33
- //# sourceMappingURL=telemetry-B5jzpy6y.d.ts.map
33
+ //# sourceMappingURL=telemetry-Cyfqo6Qt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-Cyfqo6Qt.d.ts","names":[],"sources":["../src/telemetry.ts"],"mappings":";;;;;;;;KAQY;;KAGA;KAgBA;;EAEV,aAAa,aAAa,OAAO;;;;;;EAMjC,YAAY;;KAGF;;;;;;;EAOV,KAAK,GACH,MAAM,YACN,YAAY,eAAe,mBAC3B,KAAK,MAAM,iBAAiB,QAAQ,KACnC,QAAQ"}
@@ -1,5 +1,6 @@
1
- import { t as A2Error } from "./errors-DCk6ch5n.js";
2
- import { h as nullProtoRecord } from "./internal-Dq2qYxou.js";
1
+ import { i as A2Error } from "./session-clear-DGI4jTQ5.js";
2
+ import { _ as nullProtoRecord } from "./internal-B2T3U4Rq.js";
3
+ import { i as takeBuffered, n as isSerializedEvent } from "./stream-buffer-Cx7AAPGq.js";
3
4
  //#region src/wire.ts
4
5
  /**
5
6
  * The wire format shared by server.fetch and experimental-a2/client
@@ -17,6 +18,87 @@ function eventToWire(event) {
17
18
  createdAt: event.createdAt.toISOString()
18
19
  };
19
20
  }
21
+ const lastEncoding = (encode) => {
22
+ let last;
23
+ let text = "";
24
+ return (value) => {
25
+ if (value !== last) {
26
+ text = encode(value);
27
+ last = value;
28
+ }
29
+ return text;
30
+ };
31
+ };
32
+ const isoJson = lastEncoding((ms) => JSON.stringify(new Date(ms).toISOString()));
33
+ const sessionJson = lastEncoding((id) => JSON.stringify(id));
34
+ const typeJson = lastEncoding((type) => JSON.stringify(type));
35
+ /**
36
+ * `JSON.stringify(eventToWire(event))` for the parsed event, built around the
37
+ * stored payload text instead of parsing and re-serializing it.
38
+ */
39
+ function serializedEventToWireJson(event) {
40
+ return `{"id":${JSON.stringify(event.id)},"type":${typeJson(event.type)},"payload":${event.payloadJson},"index":${event.index},"sessionId":${sessionJson(event.sessionId)},"createdAt":${isoJson(event.createdAtMs)}}`;
41
+ }
42
+ function wireVersion(value) {
43
+ return value === "2" ? 2 : 1;
44
+ }
45
+ /**
46
+ * Events already held by a stream join one write while it is shorter than
47
+ * this many characters, so a write can exceed it by the event that crosses it.
48
+ */
49
+ const EVENT_WRITE_CHARS = 65536;
50
+ /** Stream items that are events, as opposed to snapshot or presence frames. */
51
+ function isStreamEvent(item) {
52
+ return !("kind" in item) && !("snapshot" in item) && !("participant" in item);
53
+ }
54
+ /**
55
+ * `first` and the events `iterator` already holds, encoded, until the batch
56
+ * reaches `EVENT_WRITE_CHARS`; `last` is the batch's final event.
57
+ */
58
+ function heldEventBatch(options) {
59
+ const { iterator, encode } = options;
60
+ let last = options.first;
61
+ const events = [encode(last)];
62
+ let chars = events[0].length;
63
+ while (chars < EVENT_WRITE_CHARS) {
64
+ const held = takeBuffered(iterator);
65
+ if (!held) break;
66
+ const json = encode(held);
67
+ events.push(json);
68
+ chars += json.length + 1;
69
+ last = held;
70
+ }
71
+ return {
72
+ events,
73
+ last
74
+ };
75
+ }
76
+ /** One version 2 event as JSON, splicing a held row's stored payload. */
77
+ function compactEventJson(event) {
78
+ if (!isSerializedEvent(event)) return JSON.stringify({
79
+ id: event.id,
80
+ type: event.type,
81
+ payload: event.payload,
82
+ index: event.index,
83
+ createdAt: event.createdAt.getTime()
84
+ });
85
+ return `{"id":${JSON.stringify(event.id)},"type":${typeJson(event.type)},"payload":${event.payloadJson},"index":${event.index},"createdAt":${event.createdAtMs}}`;
86
+ }
87
+ function isCompactWireEvent(value) {
88
+ if (value === null || typeof value !== "object") return false;
89
+ const v = value;
90
+ return typeof v["id"] === "string" && typeof v["type"] === "string" && typeof v["index"] === "number" && typeof v["createdAt"] === "number" && Number.isFinite(v["createdAt"]);
91
+ }
92
+ function eventFromCompactWire(wire, sessionId) {
93
+ return {
94
+ id: wire.id,
95
+ type: wire.type,
96
+ payload: wire.payload,
97
+ index: wire.index,
98
+ sessionId,
99
+ createdAt: new Date(wire.createdAt)
100
+ };
101
+ }
20
102
  function eventFromWire(wire) {
21
103
  return {
22
104
  id: wire.id,
@@ -93,6 +175,7 @@ const ERROR_STATUS = {
93
175
  UNKNOWN_EVENT_TYPE: 400,
94
176
  PARTIAL_DUPLICATE_BATCH: 400,
95
177
  SUPERSEDED_ATTEMPT: 409,
178
+ SESSION_CLEARED: 409,
96
179
  CLAIM_EXPIRED: 409,
97
180
  STORE_UNAVAILABLE: 503,
98
181
  STORE_NOT_CONFIGURED: 500,
@@ -128,6 +211,18 @@ function errorFromWire(body) {
128
211
  return new A2Error(code, String(message ?? code), { details });
129
212
  }
130
213
  const isServerErrorCode = (code) => Object.hasOwn(ERROR_STATUS, code);
214
+ function stateSnapshotToWire(frame) {
215
+ return frame.state === void 0 ? {
216
+ ...frame,
217
+ state: null,
218
+ stateUndefined: true
219
+ } : frame;
220
+ }
221
+ function isStateSnapshotFrame(value) {
222
+ if (value === null || typeof value !== "object") return false;
223
+ const frame = value;
224
+ return frame["kind"] === "snapshot" && typeof frame["sessionId"] === "string" && typeof frame["name"] === "string" && frame["name"].length > 0 && typeof frame["index"] === "number" && Number.isSafeInteger(frame["index"]) && frame["index"] >= 0 && Object.hasOwn(frame, "state") && (!Object.hasOwn(frame, "stateUndefined") || frame["stateUndefined"] === true && frame["state"] === null) && (!Object.hasOwn(frame, "clearIndex") || typeof frame["clearIndex"] === "number" && Number.isSafeInteger(frame["clearIndex"]) && frame["clearIndex"] > 0 && frame["clearIndex"] <= frame["index"]);
225
+ }
131
226
  const SOCKET_PING_FRAME = JSON.stringify({ kind: "ping" });
132
227
  /** One stream item as a socket frame — `sseResponse`'s framing over
133
228
  * the same codecs, with `kind` instead of an SSE event name. On a
@@ -135,6 +230,7 @@ const SOCKET_PING_FRAME = JSON.stringify({ kind: "ping" });
135
230
  * already carry theirs in the wire event. */
136
231
  function socketFrameFor(item, sessionId) {
137
232
  const tag = sessionId === void 0 ? {} : { sessionId };
233
+ if ("kind" in item) return JSON.stringify(stateSnapshotToWire(item));
138
234
  if ("snapshot" in item) return JSON.stringify({
139
235
  kind: "presence-snapshot",
140
236
  ...presenceSnapshotToWire(item),
@@ -150,6 +246,11 @@ function socketFrameFor(item, sessionId) {
150
246
  ...eventToWire(item)
151
247
  });
152
248
  }
249
+ /** A version 2 batch frame from compact event JSON; a multiplexed
250
+ * socket names the batch's session once. */
251
+ function socketEventsFor(events, sessionId) {
252
+ return `{"kind":"events",${sessionId === void 0 ? "" : `"sessionId":${JSON.stringify(sessionId)},`}"events":[${events.join(",")}]}`;
253
+ }
153
254
  function socketAckFor(req, events, sessionId) {
154
255
  return JSON.stringify({
155
256
  kind: "ack",
@@ -166,22 +267,27 @@ function socketErrorAckFor(req, error, sessionId) {
166
267
  ...sessionId === void 0 ? {} : { sessionId }
167
268
  });
168
269
  }
169
- function socketSubscribedFor(sessionId) {
270
+ function socketSubscribedFor(options) {
271
+ const { sessionId, attempt } = options;
170
272
  return JSON.stringify({
171
273
  kind: "subscribed",
172
- sessionId
274
+ sessionId,
275
+ ...attempt === void 0 ? {} : { attempt }
173
276
  });
174
277
  }
175
278
  /** The subscription-over notice: with a `reason` the server rejected
176
279
  * or lost the session's stream, without one it ended cleanly. */
177
- function socketUnsubscribedFor(sessionId, reason, error) {
280
+ function socketUnsubscribedFor(options) {
281
+ const { sessionId, attempt, reason, error } = options;
178
282
  return JSON.stringify({
179
283
  kind: "unsubscribed",
180
284
  sessionId,
285
+ ...attempt === void 0 ? {} : { attempt },
181
286
  ...reason === void 0 ? {} : { reason },
182
287
  ...error === void 0 ? {} : errorToWire(error)
183
288
  });
184
289
  }
290
+ const isAttempt = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
185
291
  function parseSocketFrame(data) {
186
292
  let parsed;
187
293
  try {
@@ -192,6 +298,9 @@ function parseSocketFrame(data) {
192
298
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
193
299
  const frame = parsed;
194
300
  switch (frame["kind"]) {
301
+ case "snapshot":
302
+ if (!isStateSnapshotFrame(parsed)) throw new A2Error("INVALID_PAYLOAD", "invalid bootstrap snapshot");
303
+ return parsed;
195
304
  case "ping": return { kind: "ping" };
196
305
  case "event": {
197
306
  if (!isWireEvent(parsed)) return null;
@@ -208,6 +317,15 @@ function parseSocketFrame(data) {
208
317
  }
209
318
  };
210
319
  }
320
+ case "events": {
321
+ const events = frame["events"];
322
+ if (!Array.isArray(events) || !events.every(isCompactWireEvent)) throw new A2Error("INVALID_PAYLOAD", "invalid event batch");
323
+ return {
324
+ kind: "events",
325
+ events,
326
+ ...sessionTag(frame)
327
+ };
328
+ }
211
329
  case "presence": {
212
330
  if (!isWirePresencePatch(parsed)) return null;
213
331
  const { participant, values, seen, at } = parsed;
@@ -230,21 +348,25 @@ function parseSocketFrame(data) {
230
348
  ...sessionTag(frame)
231
349
  };
232
350
  case "subscribed": {
233
- const sessionId = frame["sessionId"];
351
+ const { sessionId, attempt } = frame;
234
352
  if (typeof sessionId !== "string") return null;
353
+ if (attempt !== void 0 && !isAttempt(attempt)) return null;
235
354
  return {
236
355
  kind: "subscribed",
237
- sessionId
356
+ sessionId,
357
+ ...attempt === void 0 ? {} : { attempt }
238
358
  };
239
359
  }
240
360
  case "unsubscribed": {
241
- const sessionId = frame["sessionId"];
361
+ const { sessionId, attempt } = frame;
242
362
  if (typeof sessionId !== "string") return null;
363
+ if (attempt !== void 0 && !isAttempt(attempt)) return null;
243
364
  const reason = frame["reason"];
244
365
  const error = errorFromWire(parsed) ?? ("error" in frame ? new A2Error("STORE_UNAVAILABLE", "unintelligible subscription error") : null);
245
366
  return {
246
367
  kind: "unsubscribed",
247
368
  sessionId,
369
+ ...attempt === void 0 ? {} : { attempt },
248
370
  ...typeof reason === "string" ? { reason } : {},
249
371
  ...error === null ? {} : { error }
250
372
  };
@@ -282,6 +404,6 @@ const sessionTag = (frame) => {
282
404
  return typeof sessionId === "string" ? { sessionId } : {};
283
405
  };
284
406
  //#endregion
285
- 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 };
407
+ export { stateSnapshotToWire as A, serializedEventToWireJson as C, socketFrameFor as D, socketEventsFor as E, socketSubscribedFor as O, presenceSnapshotToWire as S, socketErrorAckFor as T, isWirePresenceSnapshot as _, errorFromWire as a, presencePatchToWire as b, eventFromCompactWire as c, heldEventBatch as d, isCompactWireEvent as f, isWirePresencePatch as g, isWireEvent as h, compactEventJson as i, wireVersion as j, socketUnsubscribedFor as k, eventFromWire as l, isStreamEvent as m, SOCKET_PING_FRAME as n, errorStatus as o, isStateSnapshotFrame as p, asA2Error as r, errorToWire as s, EVENT_WRITE_CHARS as t, eventToWire as u, parseSocketFrame as v, socketAckFor as w, presenceSnapshotFromWire as x, presencePatchFromWire as y };
286
408
 
287
- //# sourceMappingURL=wire-BO5wWCb1.js.map
409
+ //# sourceMappingURL=wire-rSUglYo8.js.map