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
@@ -15,7 +15,8 @@
15
15
  import { mkdirSync } from 'node:fs'
16
16
  import { dirname, resolve } from 'node:path'
17
17
  import { DatabaseSync } from 'node:sqlite'
18
- import { A2Error } from './errors.ts'
18
+ import { A2Error, supersededAttemptError } from './errors.ts'
19
+ import { CLEAR_EVENT_TYPE, sessionClearedError } from './session-clear.ts'
19
20
  import { idempotentReplay } from './idempotent-replay.ts'
20
21
  import { decodeReturnedEventIds } from './store-codec.ts'
21
22
  import { pollingStream } from './store-polling.ts'
@@ -112,6 +113,11 @@ create table if not exists a2_snapshot_pins (
112
113
  create index if not exists a2_snapshot_pins_checkpoint
113
114
  on a2_snapshot_pins (session_id, reducer_name, up_to_index);
114
115
 
116
+ create table if not exists a2_clears (
117
+ session_id text not null primary key,
118
+ clear_index integer not null
119
+ ) strict;
120
+
115
121
  create table if not exists a2_presence (
116
122
  ns text not null,
117
123
  participant text not null,
@@ -262,6 +268,7 @@ type PresenceDbRow = {
262
268
 
263
269
  type StateReadRow = Partial<EventRow> & {
264
270
  row_kind: 'snapshot' | 'event'
271
+ clear_index: number | bigint | null
265
272
  head_index: number | bigint | null
266
273
  snapshot_index: number | bigint | null
267
274
  snapshot_state: string | null
@@ -282,6 +289,8 @@ type ExistingEventRow = EventRow & {
282
289
  session_has_pending: number | bigint
283
290
  }
284
291
 
292
+ const RECLAIM_BATCH = 256
293
+
285
294
  export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
286
295
  const path = options.path ?? '.a2/dev.db'
287
296
  const clock = options.clock ?? SYSTEM_CLOCK
@@ -314,6 +323,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
314
323
  when not exists (
315
324
  select 1 from a2_events
316
325
  where session_id = ? and lane = ? and processed_at is null
326
+ and idx > ?
317
327
  ) then 1
318
328
  else 0
319
329
  end,
@@ -325,6 +335,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
325
335
  and idx = (
326
336
  select idx from a2_events
327
337
  where session_id = ? and lane = ? and processed_at is null
338
+ and idx > ?
328
339
  order by idx limit 1
329
340
  )`,
330
341
  )
@@ -335,7 +346,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
335
346
  ) as max,
336
347
  exists(
337
348
  select 1 from a2_events
338
- where session_id = ? and processed_at is null
349
+ where session_id = ? and processed_at is null and idx > ?
339
350
  ) as has_pending`,
340
351
  )
341
352
  const upsertPresence = db.prepare(
@@ -368,12 +379,80 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
368
379
  select 1 from a2_events pending
369
380
  where pending.session_id = ?
370
381
  and pending.processed_at is null
382
+ and pending.idx > ?
371
383
  ) as session_has_pending
372
384
  from a2_events stored
373
385
  where stored.event_id in (${Array.from({ length: count }, () => '?').join(', ')})
374
386
  order by stored.idx`,
375
387
  )
376
388
 
389
+ const selectClearIndex = db.prepare(
390
+ 'select clear_index from a2_clears where session_id = ?',
391
+ )
392
+ const clearIndexOf = (sessionId: string): number =>
393
+ Number(
394
+ (
395
+ selectClearIndex.get(sessionId) as
396
+ { clear_index: number | bigint } | undefined
397
+ )?.clear_index ?? 0,
398
+ )
399
+ const deleteEventById = db.prepare(
400
+ 'delete from a2_events where session_id = ? and event_id = ?',
401
+ )
402
+
403
+ /**
404
+ * The rows owning `ids`. This session's rows below its clear marker do
405
+ * not own their ids: they are deleted here so the ids can be reused.
406
+ */
407
+ const liveOwnersOf = (
408
+ sessionId: string,
409
+ clearIndex: number,
410
+ ids: readonly string[],
411
+ ): ExistingEventRow[] => {
412
+ if (ids.length === 0) return []
413
+ const rows = selectByIds(ids.length).all(
414
+ sessionId,
415
+ clearIndex,
416
+ ...ids,
417
+ ) as unknown as ExistingEventRow[]
418
+ return rows.filter((row) => {
419
+ if (row.session_id !== sessionId || Number(row.idx) >= clearIndex)
420
+ return true
421
+ deleteEventById.run(sessionId, row.event_id)
422
+ return false
423
+ })
424
+ }
425
+
426
+ const selectCauseParent = db.prepare(
427
+ 'select attempt_count, failed_at from a2_events where session_id = ? and idx = ?',
428
+ )
429
+ /** The attempt-currency fence shared by handler appends and clears. */
430
+ const assertCurrentCause = ({
431
+ sessionId,
432
+ cause,
433
+ clearIndex,
434
+ }: {
435
+ sessionId: string
436
+ cause: EventCause
437
+ clearIndex: number
438
+ }): void => {
439
+ if (cause.index <= clearIndex)
440
+ throw supersededAttemptError(sessionId, cause)
441
+ const parent = selectCauseParent.get(sessionId, cause.index) as
442
+ { attempt_count: number | bigint; failed_at: unknown } | undefined
443
+ if (!parent) {
444
+ throw new TypeError(
445
+ `no event at index ${cause.index} in session '${sessionId}'`,
446
+ )
447
+ }
448
+ if (
449
+ Number(parent.attempt_count) !== cause.attempt ||
450
+ parent.failed_at !== null
451
+ ) {
452
+ throw supersededAttemptError(sessionId, cause)
453
+ }
454
+ }
455
+
377
456
  const tx = <T>(fn: () => T): T => {
378
457
  db.exec('begin immediate')
379
458
  try {
@@ -392,12 +471,13 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
392
471
  }
393
472
 
394
473
  return {
395
- async append(sessionId, events) {
474
+ async append(sessionId, events, appendOptions) {
396
475
  if (events.length === 0) {
397
476
  return wrap(() => {
398
477
  const status = appendStatus.get(
399
478
  sessionId,
400
479
  sessionId,
480
+ clearIndexOf(sessionId),
401
481
  ) as AppendStatusRow
402
482
  return {
403
483
  events: [],
@@ -407,6 +487,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
407
487
  }
408
488
  return wrap(() =>
409
489
  tx(() => {
490
+ const clearIndex = clearIndexOf(sessionId)
410
491
  const supplied = events.filter((e) => e.id !== undefined)
411
492
  const suppliedIds = supplied.map((e) => e.id as string)
412
493
  if (new Set(suppliedIds).size !== suppliedIds.length) {
@@ -416,10 +497,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
416
497
  )
417
498
  }
418
499
  if (suppliedIds.length > 0) {
419
- const existing = selectByIds(suppliedIds.length).all(
420
- sessionId,
421
- ...suppliedIds,
422
- ) as unknown as ExistingEventRow[]
500
+ const existing = liveOwnersOf(sessionId, clearIndex, suppliedIds)
423
501
  if (existing.length > 0) {
424
502
  const foreign = existing.find(
425
503
  (row) => row.session_id !== sessionId,
@@ -443,37 +521,25 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
443
521
  }
444
522
  }
445
523
 
524
+ const originIndex = appendOptions?.originIndex
525
+ if (originIndex !== undefined && originIndex <= clearIndex) {
526
+ throw sessionClearedError(
527
+ `scheduled work from event ${originIndex} was cleared in session '${sessionId}'`,
528
+ )
529
+ }
446
530
  // Attempt-currency fence: a fresh handler append commits only
447
531
  // while its causal attempt is still the parent's latest
448
532
  // (store.ts `append`). `begin immediate` serializes this read
449
533
  // against a concurrent claim.
450
534
  for (const e of events) {
451
- if (!e.cause) continue
452
- const parent = db
453
- .prepare(
454
- 'select attempt_count, failed_at from a2_events where session_id = ? and idx = ?',
455
- )
456
- .get(sessionId, e.cause.index) as
457
- { attempt_count: number | bigint; failed_at: unknown } | undefined
458
- if (!parent) {
459
- throw new TypeError(
460
- `no event at index ${e.cause.index} in session '${sessionId}'`,
461
- )
462
- }
463
- if (
464
- Number(parent.attempt_count) !== e.cause.attempt ||
465
- parent.failed_at !== null
466
- ) {
467
- throw new A2Error(
468
- 'SUPERSEDED_ATTEMPT',
469
- `attempt ${e.cause.attempt} no longer owns event ${e.cause.index} in session '${sessionId}'`,
470
- )
471
- }
535
+ if (e.cause)
536
+ assertCurrentCause({ sessionId, cause: e.cause, clearIndex })
472
537
  }
473
538
 
474
539
  const status = appendStatus.get(
475
540
  sessionId,
476
541
  sessionId,
542
+ clearIndex,
477
543
  ) as AppendStatusRow
478
544
  const base = Number(status.max)
479
545
  const now = clock.now().getTime()
@@ -492,6 +558,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
492
558
  e.lane ?? null,
493
559
  sessionId,
494
560
  e.lane ?? null,
561
+ clearIndex,
495
562
  e.settled ? now : null,
496
563
  )
497
564
  return {
@@ -530,8 +597,12 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
530
597
 
531
598
  async read(sessionId, opts) {
532
599
  return wrap(() => {
533
- const conditions = ['session_id = ?']
534
- const params: (string | number)[] = [sessionId]
600
+ // Reads start at the latest clear marker.
601
+ const conditions = [
602
+ 'session_id = ?',
603
+ 'idx >= coalesce((select clear_index from a2_clears where session_id = ?), 0)',
604
+ ]
605
+ const params: (string | number)[] = [sessionId, sessionId]
535
606
  if (opts?.afterIndex !== undefined) {
536
607
  conditions.push('idx > ?')
537
608
  params.push(opts.afterIndex)
@@ -560,6 +631,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
560
631
  tx(() => {
561
632
  const now = clock.now().getTime()
562
633
  const expiresAt = expiresAtMs ?? now + ttlMs
634
+ const clearIndex = clearIndexOf(sessionId)
563
635
  const exclusion =
564
636
  excludeIndexes.length === 0
565
637
  ? ''
@@ -572,11 +644,17 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
572
644
  and event.processed_at is null
573
645
  and event.failed_at is null
574
646
  and event.lane_ready = 1
647
+ and event.idx > ?
575
648
  and (event.claim_expires_at is null or event.claim_expires_at <= ?)
576
649
  ${exclusion}
577
650
  order by event.idx`,
578
651
  )
579
- .all(sessionId, now, ...excludeIndexes) as unknown as Array<{
652
+ .all(
653
+ sessionId,
654
+ clearIndex,
655
+ now,
656
+ ...excludeIndexes,
657
+ ) as unknown as Array<{
580
658
  idx: number | bigint
581
659
  }>
582
660
  if (eligible.length > 0) {
@@ -599,7 +677,9 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
599
677
  sessionId,
600
678
  row.idx,
601
679
  ) as EventRow
602
- return toStored(claimed)
680
+ const stored = toStored(claimed)
681
+ if (clearIndex > 0) stored.clearIndex = clearIndex
682
+ return stored
603
683
  })
604
684
  return { outcome: 'claimed', events }
605
685
  }
@@ -612,9 +692,10 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
612
692
  and processed_at is null
613
693
  and failed_at is null
614
694
  and lane_ready = 1
695
+ and idx > ?
615
696
  and claim_expires_at > ?`,
616
697
  )
617
- .get(sessionId, now) as {
698
+ .get(sessionId, clearIndex, now) as {
618
699
  retry_at: number | bigint | null
619
700
  }
620
701
  return active.retry_at === null
@@ -645,7 +726,12 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
645
726
  and claim_holder = ?
646
727
  and claim_expires_at > ?`,
647
728
  )
729
+ const clearIndex = clearIndexOf(sessionId)
648
730
  for (const claim of claims) {
731
+ if (claim.index <= clearIndex) {
732
+ superseded.push(claim.index)
733
+ continue
734
+ }
649
735
  const row = currentAttempt.get(sessionId, claim.index) as
650
736
  { attempt_count: number | bigint } | undefined
651
737
  if (!row) continue
@@ -674,6 +760,8 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
674
760
  async completeAttempt({ sessionId, index, attempt, events }) {
675
761
  return wrap(() =>
676
762
  tx(() => {
763
+ const clearIndex = clearIndexOf(sessionId)
764
+ if (index <= clearIndex) return { outcome: 'superseded' }
677
765
  const parent = db
678
766
  .prepare('select * from a2_events where session_id = ? and idx = ?')
679
767
  .get(sessionId, index) as EventRow | undefined
@@ -713,6 +801,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
713
801
  ? []
714
802
  : (selectByIds(ids.length).all(
715
803
  sessionId,
804
+ clearIndex,
716
805
  ...ids,
717
806
  ) as unknown as ExistingEventRow[])
718
807
  const byId = new Map(existing.map((row) => [row.event_id, row]))
@@ -748,11 +837,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
748
837
  return { outcome: 'superseded' }
749
838
  }
750
839
 
751
- if (
752
- ids.length > 0 &&
753
- (selectByIds(ids.length).all(sessionId, ...ids) as unknown[])
754
- .length > 0
755
- ) {
840
+ if (liveOwnersOf(sessionId, clearIndex, ids).length > 0) {
756
841
  throw new A2Error(
757
842
  'PARTIAL_DUPLICATE_BATCH',
758
843
  'returned event batch contains an already-appended event id',
@@ -762,6 +847,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
762
847
  const status = appendStatus.get(
763
848
  sessionId,
764
849
  sessionId,
850
+ clearIndex,
765
851
  ) as AppendStatusRow
766
852
  const now = clock.now().getTime()
767
853
  db.prepare(
@@ -788,7 +874,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
788
874
  )`,
789
875
  ).run(sessionId)
790
876
  if (parent.lane !== null) {
791
- promoteLaneHead.run(sessionId, sessionId, parent.lane)
877
+ promoteLaneHead.run(sessionId, sessionId, parent.lane, clearIndex)
792
878
  }
793
879
  const inserted = events.map((event, offset) => {
794
880
  const id = event.id
@@ -806,6 +892,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
806
892
  event.lane ?? null,
807
893
  sessionId,
808
894
  event.lane ?? null,
895
+ clearIndex,
809
896
  event.settled ? now : null,
810
897
  )
811
898
  return toStored(
@@ -824,6 +911,9 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
824
911
  async failAttempt({ sessionId, index, attempt, error, maxFailures }) {
825
912
  return wrap(() =>
826
913
  tx(() => {
914
+ if (index <= clearIndexOf(sessionId)) {
915
+ return { outcome: 'superseded', failureCount: 0 }
916
+ }
827
917
  const current = db
828
918
  .prepare('select * from a2_events where session_id = ? and idx = ?')
829
919
  .get(sessionId, index) as EventRow | undefined
@@ -898,6 +988,66 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
898
988
  )
899
989
  },
900
990
 
991
+ async deadLetters(sessionId) {
992
+ return wrap(() => {
993
+ const rows = db
994
+ .prepare(
995
+ `select * from a2_events
996
+ where session_id = ?
997
+ and processed_at is null
998
+ and failed_at is not null
999
+ and idx > coalesce(
1000
+ (select clear_index from a2_clears where session_id = ?), 0
1001
+ )
1002
+ order by idx`,
1003
+ )
1004
+ .all(sessionId, sessionId) as unknown as EventRow[]
1005
+ return rows.map(toStored)
1006
+ })
1007
+ },
1008
+
1009
+ async redrive({ sessionId, index, action }) {
1010
+ return wrap(() =>
1011
+ tx(() => {
1012
+ const clearIndex = clearIndexOf(sessionId)
1013
+ const row =
1014
+ index > clearIndex
1015
+ ? (db
1016
+ .prepare(
1017
+ 'select lane, processed_at, failed_at from a2_events where session_id = ? and idx = ?',
1018
+ )
1019
+ .get(sessionId, index) as
1020
+ | Pick<EventRow, 'lane' | 'processed_at' | 'failed_at'>
1021
+ | undefined)
1022
+ : undefined
1023
+ const redriven =
1024
+ row !== undefined &&
1025
+ row.processed_at === null &&
1026
+ row.failed_at !== null
1027
+ if (redriven && action === 'skip') {
1028
+ db.prepare(
1029
+ `update a2_events set processed_at = ?, lane_ready = 0
1030
+ where session_id = ? and idx = ?`,
1031
+ ).run(clock.now().getTime(), sessionId, index)
1032
+ if (row.lane !== null) {
1033
+ promoteLaneHead.run(sessionId, sessionId, row.lane, clearIndex)
1034
+ }
1035
+ } else if (redriven) {
1036
+ db.prepare(
1037
+ `update a2_events set failed_at = null, failure_count = 0
1038
+ where session_id = ? and idx = ?`,
1039
+ ).run(sessionId, index)
1040
+ }
1041
+ const status = appendStatus.get(
1042
+ sessionId,
1043
+ sessionId,
1044
+ clearIndex,
1045
+ ) as AppendStatusRow
1046
+ return { redriven, hasPending: Number(status.has_pending) === 1 }
1047
+ }),
1048
+ )
1049
+ },
1050
+
901
1051
  async readState(sessionId, reducerName, stateOptions) {
902
1052
  return wrap(() => {
903
1053
  const throughIndex = stateOptions?.throughIndex ?? null
@@ -905,10 +1055,15 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
905
1055
  stateOptions?.snapshotThroughIndex ?? throughIndex
906
1056
  const rows = db
907
1057
  .prepare(
908
- `with head as materialized (
1058
+ `with boundary as materialized (
1059
+ select coalesce(
1060
+ (select clear_index from a2_clears where session_id = ?), 0
1061
+ ) as clear_index
1062
+ ), head as materialized (
909
1063
  select up_to_index, state, updated_at
910
1064
  from a2_snapshots
911
1065
  where session_id = ? and reducer_name = ?
1066
+ and up_to_index >= (select clear_index from boundary)
912
1067
  ), snapshot as materialized (
913
1068
  select up_to_index, state from (
914
1069
  select up_to_index, state from head
@@ -917,10 +1072,12 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
917
1072
  select up_to_index, state
918
1073
  from a2_snapshot_history
919
1074
  where session_id = ? and reducer_name = ?
1075
+ and up_to_index >= (select clear_index from boundary)
920
1076
  and (? is null or up_to_index <= ?)
921
1077
  ) order by up_to_index desc limit 1
922
1078
  )
923
1079
  select 0 as row_order, 'snapshot' as row_kind,
1080
+ (select clear_index from boundary) as clear_index,
924
1081
  (select up_to_index from head) as head_index,
925
1082
  (select up_to_index from snapshot) as snapshot_index,
926
1083
  (select state from snapshot) as snapshot_state,
@@ -937,16 +1094,21 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
937
1094
  null as claim_expires_at
938
1095
  union all
939
1096
  select 1 as row_order, 'event' as row_kind,
1097
+ null as clear_index,
940
1098
  (select up_to_index from head) as head_index,
941
1099
  null as snapshot_index, null as snapshot_state,
942
1100
  event.*
943
1101
  from a2_events as event
944
1102
  where event.session_id = ?
945
- and event.idx > coalesce((select up_to_index from snapshot), 0)
1103
+ and event.idx > max(
1104
+ coalesce((select up_to_index from snapshot), 0),
1105
+ (select clear_index from boundary) - 1
1106
+ )
946
1107
  and (? is null or event.idx <= ?)
947
1108
  order by row_order, idx`,
948
1109
  )
949
1110
  .all(
1111
+ sessionId,
950
1112
  sessionId,
951
1113
  reducerName,
952
1114
  snapshotThroughIndex,
@@ -960,7 +1122,9 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
960
1122
  throughIndex,
961
1123
  ) as unknown as StateReadRow[]
962
1124
  const snapshot = rows.find((row) => row.row_kind === 'snapshot')
1125
+ const clearIndex = Number(snapshot?.clear_index ?? 0)
963
1126
  return {
1127
+ ...(clearIndex > 0 ? { clearIndex } : {}),
964
1128
  headIndex:
965
1129
  snapshot?.head_index === null || snapshot?.head_index === undefined
966
1130
  ? null
@@ -991,7 +1155,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
991
1155
  )
992
1156
  const unfinished = db.prepare(
993
1157
  `select 1 from a2_events
994
- where session_id = ? and idx = ?
1158
+ where session_id = ? and idx = ? and idx > ?
995
1159
  and processed_at is null and failed_at is null`,
996
1160
  )
997
1161
  const insertPin = db.prepare(
@@ -1025,9 +1189,12 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
1025
1189
  `delete from a2_snapshot_history
1026
1190
  where session_id = ? and reducer_name = ? and up_to_index = ?`,
1027
1191
  )
1192
+ const clearIndex = clearIndexOf(sessionId)
1028
1193
  for (const write of writes.toSorted((a, b) => a.index - b.index)) {
1194
+ // A fold from before the latest clear describes a lifetime that is gone.
1195
+ if (write.index < clearIndex) continue
1029
1196
  for (const eventIndex of write.pinEventIndexes ?? []) {
1030
- if (unfinished.get(sessionId, eventIndex)) {
1197
+ if (unfinished.get(sessionId, eventIndex, clearIndex)) {
1031
1198
  insertPin.run(sessionId, eventIndex, reducerName, write.index)
1032
1199
  }
1033
1200
  }
@@ -1038,6 +1205,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
1038
1205
  if (
1039
1206
  head &&
1040
1207
  write.index > Number(head.up_to_index) &&
1208
+ Number(head.up_to_index) >= clearIndex &&
1041
1209
  hasPin.get(sessionId, reducerName, Number(head.up_to_index))
1042
1210
  ) {
1043
1211
  putHistory.run(
@@ -1126,11 +1294,15 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
1126
1294
  stream(sessionId, opts) {
1127
1295
  const readAfter = (afterIndex: number): StoredEvent[] =>
1128
1296
  wrap(() => {
1297
+ // A cursor below the latest marker resumes at it.
1129
1298
  const rows = db
1130
1299
  .prepare(
1131
- 'select * from a2_events where session_id = ? and idx > ? order by idx',
1300
+ `select * from a2_events
1301
+ where session_id = ? and idx > ?
1302
+ and idx >= coalesce((select clear_index from a2_clears where session_id = ?), 0)
1303
+ order by idx`,
1132
1304
  )
1133
- .all(sessionId, afterIndex) as unknown as EventRow[]
1305
+ .all(sessionId, afterIndex, sessionId) as unknown as EventRow[]
1134
1306
  return rows.map(toStored)
1135
1307
  })
1136
1308
  return pollingStream(
@@ -1139,6 +1311,108 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
1139
1311
  )
1140
1312
  },
1141
1313
 
1314
+ async clear({ sessionId, ifLastIndex, cause }) {
1315
+ return wrap(() =>
1316
+ tx(() => {
1317
+ const clearIndex = clearIndexOf(sessionId)
1318
+ if (cause) assertCurrentCause({ sessionId, cause, clearIndex })
1319
+ const head = Number(
1320
+ (
1321
+ appendStatus.get(
1322
+ sessionId,
1323
+ sessionId,
1324
+ clearIndex,
1325
+ ) as AppendStatusRow
1326
+ ).max,
1327
+ )
1328
+ if (ifLastIndex !== undefined && clearIndex === ifLastIndex + 1) {
1329
+ return { cleared: true, index: head, clearIndex }
1330
+ }
1331
+ if (ifLastIndex !== undefined && ifLastIndex !== head) {
1332
+ return { cleared: false, index: head, clearIndex }
1333
+ }
1334
+ if (head === 0 || clearIndex === head) {
1335
+ return { cleared: true, index: head, clearIndex }
1336
+ }
1337
+ const marker = head + 1
1338
+ const now = clock.now().getTime()
1339
+ insertEvent.run(
1340
+ sessionId,
1341
+ marker,
1342
+ CLEAR_EVENT_TYPE,
1343
+ 'null',
1344
+ generateId(),
1345
+ now,
1346
+ null,
1347
+ null,
1348
+ null,
1349
+ sessionId,
1350
+ null,
1351
+ clearIndex,
1352
+ now,
1353
+ )
1354
+ db.prepare(
1355
+ `insert into a2_clears (session_id, clear_index) values (?, ?)
1356
+ on conflict (session_id) do update set clear_index = excluded.clear_index`,
1357
+ ).run(sessionId, marker)
1358
+ return { cleared: true, index: marker, clearIndex: marker }
1359
+ }),
1360
+ )
1361
+ },
1362
+
1363
+ async reclaim(sessionId) {
1364
+ return wrap(() =>
1365
+ tx(() => {
1366
+ const clearIndex = clearIndexOf(sessionId)
1367
+ if (clearIndex === 0) return { deleted: 0, done: true }
1368
+ const last = db
1369
+ .prepare(
1370
+ `select max(idx) as last from (
1371
+ select idx from a2_events
1372
+ where session_id = ? and idx < ?
1373
+ order by idx limit ?
1374
+ )`,
1375
+ )
1376
+ .get(sessionId, clearIndex, RECLAIM_BATCH) as {
1377
+ last: number | bigint | null
1378
+ }
1379
+ let deleted = 0
1380
+ if (last.last !== null) {
1381
+ const through = Number(last.last)
1382
+ deleted = Number(
1383
+ db
1384
+ .prepare(
1385
+ 'delete from a2_events where session_id = ? and idx <= ?',
1386
+ )
1387
+ .run(sessionId, through).changes,
1388
+ )
1389
+ db.prepare(
1390
+ 'delete from a2_snapshot_pins where session_id = ? and event_index <= ?',
1391
+ ).run(sessionId, through)
1392
+ }
1393
+ const done =
1394
+ db
1395
+ .prepare(
1396
+ 'select 1 from a2_events where session_id = ? and idx < ? limit 1',
1397
+ )
1398
+ .get(sessionId, clearIndex) === undefined
1399
+ // Writes serialize with the clear, so none lands below the marker later.
1400
+ if (done) {
1401
+ for (const table of [
1402
+ 'a2_snapshot_pins',
1403
+ 'a2_snapshot_history',
1404
+ 'a2_snapshots',
1405
+ ]) {
1406
+ db.prepare(
1407
+ `delete from ${table} where session_id = ? and up_to_index < ?`,
1408
+ ).run(sessionId, clearIndex)
1409
+ }
1410
+ }
1411
+ return { deleted, done }
1412
+ }),
1413
+ )
1414
+ },
1415
+
1142
1416
  close() {
1143
1417
  db.close()
1144
1418
  },