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
@@ -7,9 +7,10 @@
7
7
  */
8
8
 
9
9
  import type { PresencePatch } from './contract.ts'
10
- import { A2Error } from './errors.ts'
10
+ import { A2Error, supersededAttemptError } from './errors.ts'
11
11
  import { idempotentReplay } from './idempotent-replay.ts'
12
12
  import { nullProtoRecord } from './internal.ts'
13
+ import { CLEAR_EVENT_TYPE, sessionClearedError } from './session-clear.ts'
13
14
  import {
14
15
  RANDOM_IDS,
15
16
  SYSTEM_CLOCK,
@@ -68,6 +69,20 @@ type SessionDispatch = {
68
69
  lanes: Map<string, LaneQueue>
69
70
  }
70
71
 
72
+ /** Contiguous rows; `reclaim` removes from the front below `clearIndex`. */
73
+ type SessionLog = {
74
+ rows: Row[]
75
+ /** Index of the event before `rows[0]`. */
76
+ offset: number
77
+ /** Latest clear marker, or 0. */
78
+ clearIndex: number
79
+ }
80
+
81
+ const RECLAIM_BATCH = 256
82
+
83
+ const headOf = (log: SessionLog | undefined): number =>
84
+ log ? log.offset + log.rows.length : 0
85
+
71
86
  const toStored = (row: Row): StoredEvent => ({
72
87
  id: row.id,
73
88
  type: row.type,
@@ -111,7 +126,7 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
111
126
  const clock = options.clock ?? SYSTEM_CLOCK
112
127
  const generateId = options.ids ?? RANDOM_IDS
113
128
 
114
- const sessions = new Map<string, Row[]>()
129
+ const sessions = new Map<string, SessionLog>()
115
130
  const dispatch = new Map<string, SessionDispatch>()
116
131
  /** Global unique index on event ids, like the SQL schema's. */
117
132
  const byEventId = new Map<string, Row>()
@@ -145,13 +160,31 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
145
160
  }
146
161
  }
147
162
 
148
- const rowsOf = (sessionId: string): Row[] => {
149
- let rows = sessions.get(sessionId)
150
- if (!rows) {
151
- rows = []
152
- sessions.set(sessionId, rows)
163
+ const logOf = (sessionId: string): SessionLog => {
164
+ let log = sessions.get(sessionId)
165
+ if (!log) {
166
+ log = { rows: [], offset: 0, clearIndex: 0 }
167
+ sessions.set(sessionId, log)
153
168
  }
154
- return rows
169
+ return log
170
+ }
171
+
172
+ const rowAt = (sessionId: string, index: number): Row | undefined => {
173
+ const log = sessions.get(sessionId)
174
+ return log && index > log.offset
175
+ ? log.rows[index - log.offset - 1]
176
+ : undefined
177
+ }
178
+
179
+ const clearIndexOf = (sessionId: string): number =>
180
+ sessions.get(sessionId)?.clearIndex ?? 0
181
+
182
+ /** The row owning `id`, ignoring this session's rows below its clear. */
183
+ const ownerOf = (sessionId: string, id: string): Row | undefined => {
184
+ const row = byEventId.get(id)
185
+ return row?.sessionId === sessionId && row.index < clearIndexOf(sessionId)
186
+ ? undefined
187
+ : row
155
188
  }
156
189
 
157
190
  const checkpointKey = (
@@ -217,7 +250,7 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
217
250
  }
218
251
 
219
252
  const find = (sessionId: string, index: number): Row => {
220
- const row = sessions.get(sessionId)?.[index - 1]
253
+ const row = rowAt(sessionId, index)
221
254
  if (!row) {
222
255
  throw new TypeError(
223
256
  `no event at index ${index} in session '${sessionId}'`,
@@ -226,13 +259,29 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
226
259
  return row
227
260
  }
228
261
 
262
+ const hasPending = (sessionId: string): boolean => {
263
+ const log = sessions.get(sessionId)
264
+ return (log?.rows ?? []).some(
265
+ (row) => row.index > log!.clearIndex && row.processedAt === null,
266
+ )
267
+ }
268
+
229
269
  const appendResult = (sessionId: string, events: StoredEvent[]) => ({
230
270
  events,
231
- hasPending: (sessions.get(sessionId) ?? []).some(
232
- (row) => row.processedAt === null,
233
- ),
271
+ hasPending: hasPending(sessionId),
234
272
  })
235
273
 
274
+ /** The attempt-currency fence shared by handler appends and clears. */
275
+ const assertCurrentCause = (sessionId: string, cause: EventCause): void => {
276
+ if (cause.index <= clearIndexOf(sessionId)) {
277
+ throw supersededAttemptError(sessionId, cause)
278
+ }
279
+ const parent = find(sessionId, cause.index)
280
+ if (parent.attemptCount !== cause.attempt || parent.failedAt !== null) {
281
+ throw supersededAttemptError(sessionId, cause)
282
+ }
283
+ }
284
+
236
285
  const claim = (
237
286
  row: Row,
238
287
  holder: string,
@@ -263,7 +312,7 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
263
312
  )
264
313
  }
265
314
  const existing = supplied
266
- .map((candidate) => byEventId.get(candidate.id))
315
+ .map((candidate) => ownerOf(sessionId, candidate.id))
267
316
  .filter((row): row is Row => row !== undefined)
268
317
  const foreign = existing.find((row) => row.sessionId !== sessionId)
269
318
  if (foreign) {
@@ -281,8 +330,8 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
281
330
  now: Date,
282
331
  cause?: EventCause,
283
332
  ): Row[] => {
284
- const rows = rowsOf(sessionId)
285
- const base = rows.length === 0 ? 0 : rows[rows.length - 1]!.index
333
+ const log = logOf(sessionId)
334
+ const base = headOf(log)
286
335
  const inserted = events.map((candidate, offset): Row => ({
287
336
  id: candidate.id ?? generateId(),
288
337
  type: candidate.type,
@@ -311,7 +360,7 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
311
360
  failedAt: null,
312
361
  }))
313
362
  for (const row of inserted) {
314
- if (byEventId.has(row.id)) {
363
+ if (ownerOf(sessionId, row.id)) {
315
364
  throw new A2Error(
316
365
  'PARTIAL_DUPLICATE_BATCH',
317
366
  `event id '${row.id}' already exists`,
@@ -319,13 +368,31 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
319
368
  }
320
369
  }
321
370
  for (const row of inserted) {
322
- rows.push(row)
371
+ log.rows.push(row)
323
372
  byEventId.set(row.id, row)
324
373
  enqueue(sessionId, row)
325
374
  }
326
375
  return inserted
327
376
  }
328
377
 
378
+ /** Every cached fold of this session below its clear, head or historical. */
379
+ const dropCheckpointsBelow = (
380
+ sessionId: string,
381
+ clearIndex: number,
382
+ ): void => {
383
+ const prefix = `${sessionId}\u0000`
384
+ for (const [key, snapshot] of snapshots) {
385
+ if (!key.startsWith(prefix) || snapshot.index >= clearIndex) continue
386
+ snapshots.delete(key)
387
+ snapshotPinCounts.delete(`${key}\u0000${snapshot.index}`)
388
+ }
389
+ for (const [key, snapshot] of historicalSnapshots) {
390
+ if (!key.startsWith(prefix) || snapshot.index >= clearIndex) continue
391
+ historicalSnapshots.delete(key)
392
+ snapshotPinCounts.delete(key)
393
+ }
394
+ }
395
+
329
396
  const readStateOf = (
330
397
  sessionId: string,
331
398
  reducerName: string,
@@ -333,8 +400,10 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
333
400
  snapshotThroughIndex?: number,
334
401
  ): StoreStateRead => {
335
402
  const key = snapshotKey(sessionId, reducerName)
403
+ const clearIndex = clearIndexOf(sessionId)
336
404
  const snapshotFrontier = snapshotThroughIndex ?? throughIndex
337
- const head = snapshots.get(key)
405
+ const stored = snapshots.get(key)
406
+ const head = stored && stored.index >= clearIndex ? stored : undefined
338
407
  let candidate =
339
408
  head && (snapshotFrontier === undefined || head.index <= snapshotFrontier)
340
409
  ? head
@@ -344,6 +413,7 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
344
413
  for (const [historicalKey, snapshot] of historicalSnapshots) {
345
414
  if (
346
415
  historicalKey.startsWith(prefix) &&
416
+ snapshot.index >= clearIndex &&
347
417
  snapshot.index <= snapshotFrontier &&
348
418
  (!candidate || snapshot.index > candidate.index)
349
419
  ) {
@@ -354,22 +424,23 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
354
424
  const snapshot = candidate
355
425
  ? { index: candidate.index, state: structuredClone(candidate.state) }
356
426
  : null
357
- const afterIndex = snapshot?.index ?? 0
427
+ const afterIndex = Math.max(snapshot?.index ?? 0, clearIndex - 1)
358
428
  return {
359
429
  headIndex: head?.index ?? null,
360
430
  snapshot,
361
- events: (sessions.get(sessionId) ?? [])
431
+ events: (sessions.get(sessionId)?.rows ?? [])
362
432
  .filter(
363
433
  (row) =>
364
434
  row.index > afterIndex &&
365
435
  (throughIndex === undefined || row.index <= throughIndex),
366
436
  )
367
437
  .map(toEvent),
438
+ ...(clearIndex > 0 ? { clearIndex } : {}),
368
439
  }
369
440
  }
370
441
 
371
442
  return {
372
- async append(sessionId, events) {
443
+ async append(sessionId, events, appendOptions) {
373
444
  if (events.length === 0) return appendResult(sessionId, [])
374
445
 
375
446
  // Batch-scoped idempotency (a2-implementation.md §3): all ids
@@ -389,26 +460,16 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
389
460
  )
390
461
  }
391
462
 
463
+ const originIndex = appendOptions?.originIndex
464
+ if (originIndex !== undefined && originIndex <= clearIndexOf(sessionId)) {
465
+ throw sessionClearedError(
466
+ `scheduled work from event ${originIndex} was cleared in session '${sessionId}'`,
467
+ )
468
+ }
392
469
  // Attempt-currency fence: a fresh handler append commits only while
393
470
  // its causal attempt is still the parent's latest (store.ts `append`).
394
- const rows = sessions.get(sessionId) ?? []
395
471
  for (const event of events) {
396
- if (!event.cause) continue
397
- const parent = rows[event.cause.index - 1]
398
- if (!parent) {
399
- throw new TypeError(
400
- `no event at index ${event.cause.index} in session '${sessionId}'`,
401
- )
402
- }
403
- if (
404
- parent.attemptCount !== event.cause.attempt ||
405
- parent.failedAt !== null
406
- ) {
407
- throw new A2Error(
408
- 'SUPERSEDED_ATTEMPT',
409
- `attempt ${event.cause.attempt} no longer owns event ${event.cause.index} in session '${sessionId}'`,
410
- )
411
- }
472
+ if (event.cause) assertCurrentCause(sessionId, event.cause)
412
473
  }
413
474
 
414
475
  const now = clock.now()
@@ -417,11 +478,67 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
417
478
  return appendResult(sessionId, inserted.map(toStored))
418
479
  },
419
480
 
481
+ async clear({ sessionId, ifLastIndex, cause }) {
482
+ if (cause) assertCurrentCause(sessionId, cause)
483
+ const log = sessions.get(sessionId)
484
+ const head = headOf(log)
485
+ const clearIndex = log?.clearIndex ?? 0
486
+ if (ifLastIndex !== undefined && clearIndex === ifLastIndex + 1) {
487
+ return { cleared: true, index: head, clearIndex }
488
+ }
489
+ if (ifLastIndex !== undefined && ifLastIndex !== head) {
490
+ return { cleared: false, index: head, clearIndex }
491
+ }
492
+ if (head === 0 || clearIndex === head) {
493
+ return { cleared: true, index: head, clearIndex }
494
+ }
495
+ const [marker] = insert(
496
+ sessionId,
497
+ [{ type: CLEAR_EVENT_TYPE, payload: null, settled: true }],
498
+ clock.now(),
499
+ )
500
+ logOf(sessionId).clearIndex = marker!.index
501
+ // Every row is at or below the marker, so no dispatch state survives.
502
+ dispatch.delete(sessionId)
503
+ notifyStreams(sessionId, [marker!])
504
+ return { cleared: true, index: marker!.index, clearIndex: marker!.index }
505
+ },
506
+
507
+ async reclaim(sessionId) {
508
+ const log = sessions.get(sessionId)
509
+ if (!log || log.offset >= log.clearIndex - 1) {
510
+ return { deleted: 0, done: true }
511
+ }
512
+ const removed = log.rows.splice(
513
+ 0,
514
+ Math.min(RECLAIM_BATCH, log.clearIndex - 1 - log.offset),
515
+ )
516
+ log.offset += removed.length
517
+ for (const row of removed) {
518
+ // A reused id belongs to its new row now.
519
+ if (byEventId.get(row.id) === row) byEventId.delete(row.id)
520
+ releaseSnapshotPins(sessionId, row.index)
521
+ }
522
+ const done = log.offset === log.clearIndex - 1
523
+ // Writes below the marker are dropped, so one sweep at the end suffices.
524
+ if (done) dropCheckpointsBelow(sessionId, log.clearIndex)
525
+ return { deleted: removed.length, done }
526
+ },
527
+
420
528
  async read(sessionId, opts) {
421
- const rows = sessions.get(sessionId) ?? []
422
- const start = Math.max(0, opts?.afterIndex ?? 0)
423
- const end = Math.max(0, opts?.throughIndex ?? rows.length)
424
- return rows.slice(start, end).map(toStored)
529
+ const log = sessions.get(sessionId)
530
+ if (!log) return []
531
+ const head = headOf(log)
532
+ const after = Math.max(
533
+ opts?.afterIndex ?? 0,
534
+ log.clearIndex - 1,
535
+ log.offset,
536
+ )
537
+ const through = Math.min(opts?.throughIndex ?? head, head)
538
+ if (through <= after) return []
539
+ return log.rows
540
+ .slice(after - log.offset, through - log.offset)
541
+ .map(toStored)
425
542
  },
426
543
 
427
544
  async claimAvailable({
@@ -453,21 +570,32 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
453
570
  return retryAt ? { outcome: 'busy', retryAt } : { outcome: 'settled' }
454
571
  }
455
572
  eligible.sort((a, b) => a.index - b.index)
573
+ const clearIndex = clearIndexOf(sessionId)
456
574
  return {
457
575
  outcome: 'claimed',
458
- events: eligible.map((row) => claim(row, holder, claimedAt, expiresAt)),
576
+ events: eligible.map((row) => {
577
+ const claimed = claim(row, holder, claimedAt, expiresAt)
578
+ if (clearIndex > 0) claimed.clearIndex = clearIndex
579
+ return claimed
580
+ }),
459
581
  }
460
582
  },
461
583
 
462
584
  async renewClaims({ sessionId, holder, claims, ttlMs, expiresAtMs }) {
463
585
  const now = clock.now()
464
586
  const expiresAt = new Date(expiresAtMs ?? now.getTime() + ttlMs)
465
- const requested = new Map(claims.map((c) => [c.index, c.attempt]))
587
+ const clearIndex = clearIndexOf(sessionId)
466
588
  const renewed: number[] = []
467
589
  const superseded: number[] = []
468
- for (const row of sessions.get(sessionId) ?? []) {
469
- const attempt = requested.get(row.index)
470
- if (attempt === undefined) continue
590
+ for (const { index, attempt } of claims.toSorted(
591
+ (a, b) => a.index - b.index,
592
+ )) {
593
+ if (index <= clearIndex) {
594
+ superseded.push(index)
595
+ continue
596
+ }
597
+ const row = rowAt(sessionId, index)
598
+ if (!row) continue
471
599
  if (row.attemptCount > attempt) {
472
600
  superseded.push(row.index)
473
601
  continue
@@ -487,6 +615,7 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
487
615
  },
488
616
 
489
617
  async completeAttempt({ sessionId, index, attempt, events }) {
618
+ if (index <= clearIndexOf(sessionId)) return { outcome: 'superseded' }
490
619
  const parent = find(sessionId, index)
491
620
  const cause = { index, attempt }
492
621
  if (parent.processedAt !== null) {
@@ -553,6 +682,9 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
553
682
  },
554
683
 
555
684
  async failAttempt({ sessionId, index, attempt, error, maxFailures }) {
685
+ if (index <= clearIndexOf(sessionId)) {
686
+ return { outcome: 'superseded', failureCount: 0 }
687
+ }
556
688
  const row = find(sessionId, index)
557
689
  if (
558
690
  row.processedAt ||
@@ -589,6 +721,39 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
589
721
  return { outcome: 'failed', failureCount: row.failureCount }
590
722
  },
591
723
 
724
+ async deadLetters(sessionId) {
725
+ const log = sessions.get(sessionId)
726
+ return (log?.rows ?? [])
727
+ .filter(
728
+ (row) =>
729
+ row.index > log!.clearIndex &&
730
+ row.processedAt === null &&
731
+ row.failedAt !== null,
732
+ )
733
+ .map(toStored)
734
+ },
735
+
736
+ async redrive({ sessionId, index, action }) {
737
+ const row =
738
+ index > clearIndexOf(sessionId) ? rowAt(sessionId, index) : undefined
739
+ if (!row || row.processedAt !== null || row.failedAt === null) {
740
+ return { redriven: false, hasPending: hasPending(sessionId) }
741
+ }
742
+ if (action === 'skip') {
743
+ row.processedAt = clock.now()
744
+ settle(sessionId, row)
745
+ } else {
746
+ row.failedAt = null
747
+ row.failureCount = 0
748
+ const state = dispatchOf(sessionId)
749
+ const queue = row.lane === null ? undefined : state.lanes.get(row.lane)
750
+ if (row.lane === null || queue?.rows[queue.head] === row) {
751
+ state.ready.set(row.index, row)
752
+ }
753
+ }
754
+ return { redriven: true, hasPending: hasPending(sessionId) }
755
+ },
756
+
592
757
  async readState(sessionId, reducerName, stateOptions) {
593
758
  return readStateOf(
594
759
  sessionId,
@@ -611,11 +776,18 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
611
776
 
612
777
  async putSnapshots(sessionId, reducerName, writes) {
613
778
  const key = snapshotKey(sessionId, reducerName)
779
+ const clearIndex = clearIndexOf(sessionId)
614
780
  for (const write of writes.toSorted((a, b) => a.index - b.index)) {
781
+ if (write.index < clearIndex) continue
615
782
  const checkpoint = checkpointKey(sessionId, reducerName, write.index)
616
783
  for (const eventIndex of write.pinEventIndexes ?? []) {
617
- const event = sessions.get(sessionId)?.[eventIndex - 1]
618
- if (!event || event.processedAt !== null || event.failedAt !== null) {
784
+ const event = rowAt(sessionId, eventIndex)
785
+ if (
786
+ !event ||
787
+ event.index <= clearIndex ||
788
+ event.processedAt !== null ||
789
+ event.failedAt !== null
790
+ ) {
619
791
  continue
620
792
  }
621
793
  const pinKey = eventPinKey(sessionId, eventIndex)
@@ -635,7 +807,11 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
635
807
 
636
808
  const current = snapshots.get(key)
637
809
  if (!current || write.index >= current.index) {
638
- if (current && write.index > current.index) {
810
+ if (
811
+ current &&
812
+ write.index > current.index &&
813
+ current.index >= clearIndex
814
+ ) {
639
815
  const currentCheckpoint = checkpointKey(
640
816
  sessionId,
641
817
  reducerName,
@@ -769,13 +945,11 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
769
945
  const startAfter = opts?.startAfter ?? 0
770
946
  return {
771
947
  [Symbol.asyncIterator](): AsyncIterator<Event> {
772
- let last = startAfter
773
- const buffer: Row[] = []
948
+ let last = Math.max(startAfter, clearIndexOf(sessionId) - 1)
774
949
  let wake: (() => void) | null = null
775
950
  let closed = false
776
951
 
777
- const onRow = (row: Row): void => {
778
- buffer.push(row)
952
+ const onRow = (): void => {
779
953
  wake?.()
780
954
  }
781
955
  let subs = streamSubscribers.get(sessionId)
@@ -784,37 +958,40 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
784
958
  streamSubscribers.set(sessionId, subs)
785
959
  }
786
960
  subs.add(onRow)
787
- // Seed with history — synchronously, in the same tick as the
788
- // subscription, so nothing can slip between the two.
789
- const existing = sessions.get(sessionId) ?? []
790
- buffer.unshift(...existing.filter((row) => row.index > startAfter))
791
-
792
- const unsubscribe = (): void => {
961
+ const stop = (): void => {
962
+ if (closed) return
963
+ closed = true
793
964
  subs.delete(onRow)
794
965
  if (subs.size === 0) streamSubscribers.delete(sessionId)
966
+ wake?.()
795
967
  }
796
968
 
797
969
  return {
798
970
  async next(): Promise<IteratorResult<Event>> {
799
- for (;;) {
800
- while (buffer.length > 0) {
801
- const row = buffer.shift()!
802
- if (row.index <= last) continue
803
- last = row.index
804
- return { value: toStored(row), done: false }
971
+ try {
972
+ for (;;) {
973
+ if (closed) return { value: undefined, done: true }
974
+ const log = sessions.get(sessionId)
975
+ // Rows reclaimed behind a running stream are skipped.
976
+ const row = log?.rows[Math.max(0, last - log.offset)]
977
+ if (row) {
978
+ const value = toStored(row)
979
+ last = row.index
980
+ return { value, done: false }
981
+ }
982
+ // oxlint-disable-next-line no-await-in-loop -- wait-for-wake
983
+ await new Promise<void>((resolve) => {
984
+ wake = resolve
985
+ })
986
+ wake = null
805
987
  }
806
- if (closed) return { value: undefined, done: true }
807
- // oxlint-disable-next-line no-await-in-loop -- wait-for-wake
808
- await new Promise<void>((resolve) => {
809
- wake = resolve
810
- })
811
- wake = null
988
+ } catch (error) {
989
+ stop()
990
+ throw error
812
991
  }
813
992
  },
814
993
  async return(): Promise<IteratorResult<Event>> {
815
- closed = true
816
- unsubscribe()
817
- wake?.()
994
+ stop()
818
995
  return { value: undefined, done: true }
819
996
  },
820
997
  }
@@ -66,7 +66,8 @@ export function pollingStream(
66
66
  return {
67
67
  [Symbol.asyncIterator](): AsyncIterator<Event> {
68
68
  let last = startAfter
69
- let buffer: Event[] = []
69
+ let buffer: Array<Event | undefined> = []
70
+ let position = 0
70
71
  let closed = false
71
72
  let pending: Sleeper | null = null
72
73
  let interval = floor()
@@ -83,18 +84,29 @@ export function pollingStream(
83
84
  for (;;) {
84
85
  if (closed) return { value: undefined, done: true }
85
86
  try {
86
- // oxlint-disable-next-line no-await-in-loop -- poll loop
87
- if (buffer.length === 0) buffer = await readAfter(last)
87
+ if (position === buffer.length) {
88
+ // oxlint-disable-next-line no-await-in-loop -- poll loop
89
+ const rows = await readAfter(last)
90
+ if (closed) return { value: undefined, done: true }
91
+ buffer = rows
92
+ position = 0
93
+ }
88
94
  } catch (error) {
89
95
  if (closed) return { value: undefined, done: true }
90
96
  throw error
91
97
  }
92
98
  if (closed) return { value: undefined, done: true }
93
- const row = buffer.shift()
99
+ const row = buffer[position]
94
100
  if (row) {
95
101
  // Activity: tighten back to the floor.
96
102
  activity.activate()
97
103
  last = row.index
104
+ buffer[position] = undefined
105
+ position += 1
106
+ if (position === buffer.length) {
107
+ buffer = []
108
+ position = 0
109
+ }
98
110
  return { value: row, done: false }
99
111
  }
100
112
  // Empty poll: wait, then back off toward the idle ceiling.
@@ -111,6 +123,8 @@ export function pollingStream(
111
123
  },
112
124
  async return(): Promise<IteratorResult<Event>> {
113
125
  closed = true
126
+ buffer = []
127
+ position = 0
114
128
  pending?.cancel()
115
129
  return { value: undefined, done: true }
116
130
  },