experimental-a2 0.16.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (406) hide show
  1. package/CHANGELOG.md +373 -0
  2. package/README.md +1 -1
  3. package/dist/{actor-ohPC-81x.d.ts → actor-Bjce9_5U.d.ts} +21 -11
  4. package/dist/actor-Bjce9_5U.d.ts.map +1 -0
  5. package/dist/actor-client.d.ts +2 -5
  6. package/dist/actor-client.d.ts.map +1 -1
  7. package/dist/actor-client.js +13 -4
  8. package/dist/actor-client.js.map +1 -1
  9. package/dist/actor-react.d.ts +5 -3
  10. package/dist/actor-react.d.ts.map +1 -1
  11. package/dist/actor-react.js +20 -10
  12. package/dist/actor-react.js.map +1 -1
  13. package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
  14. package/dist/actor-shared-t7w8xIKP.js.map +1 -0
  15. package/dist/actor.d.ts +1 -1
  16. package/dist/actor.js +42 -23
  17. package/dist/actor.js.map +1 -1
  18. package/dist/{ai-DgOBltJ_.js → ai-BpOGm-zB.js} +621 -216
  19. package/dist/ai-BpOGm-zB.js.map +1 -0
  20. package/dist/{ai-CrEf6p_W.d.ts → ai-DrUaMyRt.d.ts} +114 -100
  21. package/dist/ai-DrUaMyRt.d.ts.map +1 -0
  22. package/dist/ai-client.d.ts +27 -0
  23. package/dist/ai-client.d.ts.map +1 -0
  24. package/dist/ai-client.js +29 -0
  25. package/dist/ai-client.js.map +1 -0
  26. package/dist/ai-fx.browser.d.ts +1 -0
  27. package/dist/ai-fx.browser.js +6 -0
  28. package/dist/ai-fx.browser.js.map +1 -0
  29. package/dist/ai-fx.d.ts +10 -0
  30. package/dist/ai-fx.d.ts.map +1 -0
  31. package/dist/ai-fx.js +634 -0
  32. package/dist/ai-fx.js.map +1 -0
  33. package/dist/ai-harness-BuLh_EYi.js +273 -0
  34. package/dist/ai-harness-BuLh_EYi.js.map +1 -0
  35. package/dist/ai-harness-DvsPeJeB.d.ts +26 -0
  36. package/dist/ai-harness-DvsPeJeB.d.ts.map +1 -0
  37. package/dist/ai-pi.browser.d.ts +1 -0
  38. package/dist/ai-pi.browser.js +6 -0
  39. package/dist/ai-pi.browser.js.map +1 -0
  40. package/dist/ai-pi.d.ts +17 -0
  41. package/dist/ai-pi.d.ts.map +1 -0
  42. package/dist/ai-pi.js +904 -0
  43. package/dist/ai-pi.js.map +1 -0
  44. package/dist/ai-react.d.ts +96 -0
  45. package/dist/ai-react.d.ts.map +1 -0
  46. package/dist/ai-react.js +142 -0
  47. package/dist/ai-react.js.map +1 -0
  48. package/dist/ai-server.browser.js +2 -2
  49. package/dist/ai-server.browser.js.map +1 -1
  50. package/dist/ai-server.d.ts +109 -43
  51. package/dist/ai-server.d.ts.map +1 -1
  52. package/dist/ai-server.js +526 -283
  53. package/dist/ai-server.js.map +1 -1
  54. package/dist/ai.d.ts +2 -2
  55. package/dist/ai.js +2 -2
  56. package/dist/bounded-cache-CPHWF-8h.js +41 -0
  57. package/dist/bounded-cache-CPHWF-8h.js.map +1 -0
  58. package/dist/{client-BYzHjkwU.js → client-BReKqwZC.js} +447 -80
  59. package/dist/client-BReKqwZC.js.map +1 -0
  60. package/dist/{client-CzyacQpJ.d.ts → client-h_DKFAjj.d.ts} +14 -13
  61. package/dist/client-h_DKFAjj.d.ts.map +1 -0
  62. package/dist/client.d.ts +4 -2
  63. package/dist/client.js +1 -1
  64. package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
  65. package/dist/contract-BYKASlCV.js.map +1 -0
  66. package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
  67. package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
  68. package/dist/index.d.ts +6 -5
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +4 -3
  71. package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-Bx-c0pDP.d.ts} +7 -3
  72. package/dist/initial-snapshot-Bx-c0pDP.d.ts.map +1 -0
  73. package/dist/{internal-Dq2qYxou.js → internal-B2T3U4Rq.js} +29 -2
  74. package/dist/internal-B2T3U4Rq.js.map +1 -0
  75. package/dist/otel.d.ts +1 -1
  76. package/dist/react-BBXgHWFc.d.ts +86 -0
  77. package/dist/react-BBXgHWFc.d.ts.map +1 -0
  78. package/dist/react.d.ts +2 -122
  79. package/dist/react.js +107 -100
  80. package/dist/react.js.map +1 -1
  81. package/dist/reducer-BAXc8JyF.js +87 -0
  82. package/dist/reducer-BAXc8JyF.js.map +1 -0
  83. package/dist/{reducer-otzHjuJj.d.ts → reducer-CqA59Q6U.d.ts} +46 -30
  84. package/dist/reducer-CqA59Q6U.d.ts.map +1 -0
  85. package/dist/reducer-runtime-DQwz_J59.js +28 -0
  86. package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
  87. package/dist/scheduler-qstash.d.ts +3 -3
  88. package/dist/scheduler-qstash.d.ts.map +1 -1
  89. package/dist/scheduler-qstash.js +33 -10
  90. package/dist/scheduler-qstash.js.map +1 -1
  91. package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
  92. package/dist/scheduler-task-X1SToDjP.js.map +1 -0
  93. package/dist/scheduler-vercel.d.ts +4 -9
  94. package/dist/scheduler-vercel.d.ts.map +1 -1
  95. package/dist/scheduler-vercel.js +25 -44
  96. package/dist/scheduler-vercel.js.map +1 -1
  97. package/dist/{server-BD5ckxZb.js → server-Dw_k_7kO.js} +904 -219
  98. package/dist/server-Dw_k_7kO.js.map +1 -0
  99. package/dist/{server-DbD7IJVK.d.ts → server-En7cbi3v.d.ts} +116 -26
  100. package/dist/server-En7cbi3v.d.ts.map +1 -0
  101. package/dist/server.d.ts +4 -4
  102. package/dist/server.js +2 -2
  103. package/dist/session-clear-Bo-XTFEy.d.ts +12 -0
  104. package/dist/session-clear-Bo-XTFEy.d.ts.map +1 -0
  105. package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
  106. package/dist/session-clear-DGI4jTQ5.js.map +1 -0
  107. package/dist/{store-DyZM6fS5.d.ts → store-Djdv0bnZ.d.ts} +73 -3
  108. package/dist/store-Djdv0bnZ.d.ts.map +1 -0
  109. package/dist/store-N8PXxDAS.js.map +1 -1
  110. package/dist/store-memory.d.ts +1 -1
  111. package/dist/store-memory.d.ts.map +1 -1
  112. package/dist/store-memory.js +205 -66
  113. package/dist/store-memory.js.map +1 -1
  114. package/dist/{store-polling-CmxUbV93.js → store-polling-B5FcwJfo.js} +21 -4
  115. package/dist/store-polling-B5FcwJfo.js.map +1 -0
  116. package/dist/store-postgres.d.ts +1 -1
  117. package/dist/store-postgres.d.ts.map +1 -1
  118. package/dist/store-postgres.js +450 -108
  119. package/dist/store-postgres.js.map +1 -1
  120. package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-BAwS8RWn.js} +3 -3
  121. package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-BAwS8RWn.js.map} +1 -1
  122. package/dist/store-redis-http.d.ts +1 -1
  123. package/dist/store-redis-http.d.ts.map +1 -1
  124. package/dist/store-redis-http.js +293 -11
  125. package/dist/store-redis-http.js.map +1 -1
  126. package/dist/store-redis-notify-BRITti_o.js +2493 -0
  127. package/dist/store-redis-notify-BRITti_o.js.map +1 -0
  128. package/dist/store-redis.d.ts +1 -1
  129. package/dist/store-redis.d.ts.map +1 -1
  130. package/dist/store-redis.js +11 -11
  131. package/dist/store-redis.js.map +1 -1
  132. package/dist/store-sqlite.d.ts +1 -1
  133. package/dist/store-sqlite.d.ts.map +1 -1
  134. package/dist/store-sqlite.js +197 -33
  135. package/dist/store-sqlite.js.map +1 -1
  136. package/dist/stream-buffer-Cx7AAPGq.js +23 -0
  137. package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
  138. package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-Cyfqo6Qt.d.ts} +2 -2
  139. package/dist/telemetry-Cyfqo6Qt.d.ts.map +1 -0
  140. package/dist/{wire-BO5wWCb1.js → wire-rSUglYo8.js} +132 -10
  141. package/dist/wire-rSUglYo8.js.map +1 -0
  142. package/docs/01-quickstart.mdx +50 -84
  143. package/docs/{guides/07-examples.mdx → 02-examples.mdx} +10 -20
  144. package/docs/actors/01-introduction.mdx +48 -82
  145. package/docs/actors/02-concurrency.mdx +43 -70
  146. package/docs/actors/03-timers.mdx +28 -42
  147. package/docs/actors/04-routes.mdx +110 -137
  148. package/docs/actors/meta.ts +1 -1
  149. package/docs/agents/01-introduction.mdx +257 -0
  150. package/docs/agents/02-tools.mdx +247 -0
  151. package/docs/agents/03-conversations.mdx +538 -0
  152. package/docs/agents/04-harnesses.mdx +136 -0
  153. package/docs/agents/meta.ts +1 -0
  154. package/docs/client/01-react.mdx +294 -0
  155. package/docs/client/02-transports.mdx +86 -0
  156. package/docs/client/03-presence.mdx +224 -0
  157. package/docs/client/meta.ts +1 -0
  158. package/docs/contract/01-events.mdx +57 -0
  159. package/docs/contract/02-state.mdx +142 -0
  160. package/docs/contract/03-sessions.mdx +175 -0
  161. package/docs/index.mdx +112 -156
  162. package/docs/reference/01-core.mdx +139 -0
  163. package/docs/reference/02-server.mdx +855 -0
  164. package/docs/reference/03-client.mdx +301 -0
  165. package/docs/reference/04-ai.mdx +722 -0
  166. package/docs/reference/05-adapters.mdx +351 -0
  167. package/docs/reference/06-errors.mdx +138 -0
  168. package/docs/reference/meta.ts +1 -1
  169. package/docs/server/01-handlers.mdx +200 -0
  170. package/docs/server/02-timers.mdx +130 -0
  171. package/docs/server/03-cancellation.mdx +141 -0
  172. package/docs/server/04-routes.mdx +243 -0
  173. package/docs/server/05-production.mdx +322 -0
  174. package/docs/server/06-guarantees.mdx +123 -0
  175. package/examples/playground/DEPLOY.md +53 -7
  176. package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
  177. package/examples/playground/app/a0/[appId]/page.tsx +53 -0
  178. package/examples/playground/app/a0/a0.css +818 -0
  179. package/examples/playground/app/a0/app-list.tsx +218 -0
  180. package/examples/playground/app/a0/app-view.tsx +63 -0
  181. package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
  182. package/examples/playground/app/a0/apps/[appId]/route.ts +28 -0
  183. package/examples/playground/app/a0/apps.test.ts +128 -0
  184. package/examples/playground/app/a0/apps.ts +102 -0
  185. package/examples/playground/app/a0/chat.tsx +410 -0
  186. package/examples/playground/app/a0/columns.tsx +109 -0
  187. package/examples/playground/app/a0/context-menu.tsx +116 -0
  188. package/examples/playground/app/a0/cookies.ts +46 -0
  189. package/examples/playground/app/a0/crash-on-request.ts +72 -0
  190. package/examples/playground/app/a0/crashes/route.ts +3 -0
  191. package/examples/playground/app/a0/crashes.test.ts +33 -0
  192. package/examples/playground/app/a0/crashes.ts +28 -0
  193. package/examples/playground/app/a0/devtools.tsx +164 -0
  194. package/examples/playground/app/a0/events/route.ts +6 -0
  195. package/examples/playground/app/a0/flags.ts +30 -0
  196. package/examples/playground/app/a0/fx/events/route.ts +9 -0
  197. package/examples/playground/app/a0/fx/scheduler/route.ts +7 -0
  198. package/examples/playground/app/a0/fx/short/events/route.ts +11 -0
  199. package/examples/playground/app/a0/layout.tsx +38 -0
  200. package/examples/playground/app/a0/list/route.ts +10 -0
  201. package/examples/playground/app/a0/model.test.ts +73 -0
  202. package/examples/playground/app/a0/model.ts +95 -0
  203. package/examples/playground/app/a0/no-photon.cjs +4 -0
  204. package/examples/playground/app/a0/no-quickjs.cjs +3 -0
  205. package/examples/playground/app/a0/page.tsx +14 -0
  206. package/examples/playground/app/a0/pi/events/route.ts +9 -0
  207. package/examples/playground/app/a0/pi/scheduler/route.ts +7 -0
  208. package/examples/playground/app/a0/pi/short/events/route.ts +11 -0
  209. package/examples/playground/app/a0/preview-pane.tsx +145 -0
  210. package/examples/playground/app/a0/preview.test.ts +53 -0
  211. package/examples/playground/app/a0/preview.ts +26 -0
  212. package/examples/playground/app/a0/sandbox-log.ts +75 -0
  213. package/examples/playground/app/a0/sandbox-session.ts +152 -0
  214. package/examples/playground/app/a0/sandbox.ts +191 -0
  215. package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
  216. package/examples/playground/app/a0/scheduler/route.ts +7 -0
  217. package/examples/playground/app/a0/server.ts +213 -0
  218. package/examples/playground/app/a0/session.ts +59 -0
  219. package/examples/playground/app/a0/shell.tsx +158 -0
  220. package/examples/playground/app/a0/short/events/route.ts +9 -0
  221. package/examples/playground/app/a0/splitter.tsx +120 -0
  222. package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
  223. package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
  224. package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
  225. package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
  226. package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
  227. package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
  228. package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
  229. package/examples/playground/app/agent/events/route.ts +5 -2
  230. package/examples/playground/app/agent/ingress.test.ts +101 -0
  231. package/examples/playground/app/agent/model-input.test.tsx +58 -0
  232. package/examples/playground/app/agent/model-input.tsx +115 -0
  233. package/examples/playground/app/agent/model.test.ts +73 -0
  234. package/examples/playground/app/agent/model.ts +58 -26
  235. package/examples/playground/app/agent/new-agent-session.tsx +37 -19
  236. package/examples/playground/app/agent/page.tsx +4 -4
  237. package/examples/playground/app/agent/server.ts +19 -15
  238. package/examples/playground/app/agent/session.ts +5 -6
  239. package/examples/playground/app/agent/telemetry.test.ts +189 -0
  240. package/examples/playground/app/agent/telemetry.ts +30 -0
  241. package/examples/playground/app/agent/trace.test.ts +23 -0
  242. package/examples/playground/app/agent/trace.ts +18 -0
  243. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +12 -12
  244. package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
  245. package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
  246. package/examples/playground/app/canvas/model.ts +53 -53
  247. package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
  248. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
  249. package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
  250. package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
  251. package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
  252. package/examples/playground/app/chat/[chatId]/session.ts +2 -19
  253. package/examples/playground/app/chat/agent/route.ts +9 -0
  254. package/examples/playground/app/chat/model.test.ts +46 -119
  255. package/examples/playground/app/chat/model.ts +82 -128
  256. package/examples/playground/app/chat/scheduler/route.ts +2 -2
  257. package/examples/playground/app/chat/server.test.ts +112 -0
  258. package/examples/playground/app/chat/server.ts +113 -117
  259. package/examples/playground/app/components/activity-feed.tsx +30 -1
  260. package/examples/playground/app/components/activity-history.test.tsx +35 -0
  261. package/examples/playground/app/components/demo-icons.tsx +89 -0
  262. package/examples/playground/app/components/demos.tsx +33 -0
  263. package/examples/playground/app/components/fast-link.test.tsx +58 -0
  264. package/examples/playground/app/components/fast-link.tsx +55 -0
  265. package/examples/playground/app/components/site-header.tsx +96 -0
  266. package/examples/playground/app/counter/counter-client.tsx +3 -3
  267. package/examples/playground/app/counter/model.test.ts +3 -3
  268. package/examples/playground/app/counter/model.ts +11 -21
  269. package/examples/playground/app/counter/page.tsx +2 -15
  270. package/examples/playground/app/counter/session.ts +2 -2
  271. package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
  272. package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
  273. package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
  274. package/examples/playground/app/documents/[documentId]/session.ts +2 -2
  275. package/examples/playground/app/documents/model.ts +27 -27
  276. package/examples/playground/app/globals.css +420 -207
  277. package/examples/playground/app/initial-state.test.tsx +54 -0
  278. package/examples/playground/app/layout.tsx +7 -23
  279. package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
  280. package/examples/playground/app/orders/[orderId]/session.ts +2 -2
  281. package/examples/playground/app/orders/model.ts +49 -49
  282. package/examples/playground/app/page.tsx +13 -102
  283. package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
  284. package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
  285. package/examples/playground/app/recovery/model.ts +30 -30
  286. package/examples/playground/instrumentation.ts +5 -0
  287. package/examples/playground/next.config.ts +17 -5
  288. package/examples/playground/package.json +13 -2
  289. package/examples/playground/scripts/a0-template.ts +77 -0
  290. package/examples/playground/vercel.json +36 -0
  291. package/package.json +29 -3
  292. package/src/actor-client.ts +16 -4
  293. package/src/actor-react.ts +25 -11
  294. package/src/actor-shared.ts +21 -6
  295. package/src/actor.ts +76 -31
  296. package/src/ai-agent-actions.ts +219 -0
  297. package/src/ai-agent.ts +2517 -0
  298. package/src/ai-app-state.ts +91 -0
  299. package/src/ai-client-state.ts +6 -3
  300. package/src/ai-client.ts +62 -0
  301. package/src/ai-context-schema.ts +25 -5
  302. package/src/ai-context.ts +90 -40
  303. package/src/ai-control-server.ts +92 -72
  304. package/src/ai-coordinator.ts +23 -22
  305. package/src/ai-fx.browser.ts +5 -0
  306. package/src/ai-fx.ts +833 -0
  307. package/src/ai-harness.ts +518 -0
  308. package/src/ai-message-base.ts +209 -0
  309. package/src/ai-message-projection.ts +262 -64
  310. package/src/ai-model-metadata.ts +11 -6
  311. package/src/ai-pi.browser.ts +5 -0
  312. package/src/ai-pi.ts +1263 -0
  313. package/src/ai-progress-batches.ts +26 -0
  314. package/src/ai-projector.ts +95 -14
  315. package/src/ai-react.ts +146 -0
  316. package/src/ai-sdk-step.ts +2 -2
  317. package/src/ai-server.browser.ts +2 -2
  318. package/src/ai-server.ts +23 -1965
  319. package/src/ai-stored-state.ts +48 -163
  320. package/src/ai.ts +339 -155
  321. package/src/bootstrap-stream.ts +72 -0
  322. package/src/bounded-cache.ts +55 -0
  323. package/src/client-render-session.ts +49 -0
  324. package/src/client.ts +688 -151
  325. package/src/contract.ts +92 -42
  326. package/src/deterministic-id.ts +37 -0
  327. package/src/equal-json.ts +32 -0
  328. package/src/errors.ts +13 -1
  329. package/src/index.ts +5 -1
  330. package/src/initial-snapshot.ts +39 -0
  331. package/src/internal.ts +52 -0
  332. package/src/libfx.d.ts +34 -0
  333. package/src/postgres-notification-scope.ts +14 -3
  334. package/src/react.ts +171 -206
  335. package/src/redis-http-reply.ts +256 -0
  336. package/src/redis-http-stream.ts +73 -0
  337. package/src/redis-http-subscriptions.ts +13 -5
  338. package/src/reducer-runtime.ts +52 -0
  339. package/src/reducer.ts +93 -11
  340. package/src/scheduler-qstash.ts +49 -20
  341. package/src/scheduler-task.ts +34 -2
  342. package/src/scheduler-vercel.ts +32 -84
  343. package/src/server-fetch.ts +139 -35
  344. package/src/server.ts +1133 -255
  345. package/src/session-clear.ts +23 -0
  346. package/src/session-socket.ts +154 -49
  347. package/src/sse.ts +71 -4
  348. package/src/store-memory.ts +252 -75
  349. package/src/store-polling.ts +18 -4
  350. package/src/store-postgres.ts +541 -114
  351. package/src/store-redis-core.ts +1450 -493
  352. package/src/store-redis-http.ts +18 -2
  353. package/src/store-redis-notify.ts +262 -85
  354. package/src/store-redis.ts +19 -9
  355. package/src/store-sqlite.ts +319 -45
  356. package/src/store.ts +79 -1
  357. package/src/stream-buffer.ts +43 -0
  358. package/src/telemetry.ts +14 -1
  359. package/src/wire.ts +241 -13
  360. package/dist/actor-ohPC-81x.d.ts.map +0 -1
  361. package/dist/actor-shared-B5tJfzt-.js.map +0 -1
  362. package/dist/ai-CrEf6p_W.d.ts.map +0 -1
  363. package/dist/ai-DgOBltJ_.js.map +0 -1
  364. package/dist/client-BYzHjkwU.js.map +0 -1
  365. package/dist/client-CzyacQpJ.d.ts.map +0 -1
  366. package/dist/contract-CKRg_E4q.js.map +0 -1
  367. package/dist/errors-DCk6ch5n.js.map +0 -1
  368. package/dist/errors-DvhSXnxk.d.ts.map +0 -1
  369. package/dist/internal-Dq2qYxou.js.map +0 -1
  370. package/dist/react.d.ts.map +0 -1
  371. package/dist/reducer-DEMjEY_O.js +0 -29
  372. package/dist/reducer-DEMjEY_O.js.map +0 -1
  373. package/dist/reducer-otzHjuJj.d.ts.map +0 -1
  374. package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
  375. package/dist/server-BD5ckxZb.js.map +0 -1
  376. package/dist/server-DbD7IJVK.d.ts.map +0 -1
  377. package/dist/store-DyZM6fS5.d.ts.map +0 -1
  378. package/dist/store-polling-CmxUbV93.js.map +0 -1
  379. package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
  380. package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
  381. package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
  382. package/dist/validate-XKT4FSNn.js +0 -30
  383. package/dist/validate-XKT4FSNn.js.map +0 -1
  384. package/dist/wire-BO5wWCb1.js.map +0 -1
  385. package/docs/concepts/01-contracts.mdx +0 -142
  386. package/docs/concepts/02-handlers.mdx +0 -284
  387. package/docs/concepts/03-durability.mdx +0 -314
  388. package/docs/concepts/04-state.mdx +0 -209
  389. package/docs/guides/01-timers.mdx +0 -185
  390. package/docs/guides/02-cancellation.mdx +0 -161
  391. package/docs/guides/03-react.mdx +0 -384
  392. package/docs/guides/04-local-first.mdx +0 -88
  393. package/docs/guides/05-production.mdx +0 -521
  394. package/docs/guides/06-ai-agents.mdx +0 -1318
  395. package/docs/guides/08-application-data.mdx +0 -113
  396. package/docs/guides/09-presence.mdx +0 -266
  397. package/docs/guides/10-transports.mdx +0 -221
  398. package/docs/reference/01-api.mdx +0 -1896
  399. package/docs/reference/02-errors.mdx +0 -149
  400. package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
  401. package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
  402. package/examples/playground/app/agent/compaction-settings.ts +0 -49
  403. package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
  404. package/examples/playground/app/agent/compaction-timeline.ts +0 -208
  405. /package/docs/{concepts → contract}/meta.ts +0 -0
  406. /package/docs/{guides → server}/meta.ts +0 -0
@@ -1,9 +1,12 @@
1
- import { n as validateSync } from "./validate-XKT4FSNn.js";
2
- import { n as NonRetriableError, r as asStoreUnavailable, t as A2Error } from "./errors-DCk6ch5n.js";
3
- import { c as STREAM_TIMINGS, g as serverInternals, h as nullProtoRecord, i as POLL_TIMINGS, m as markSchedulerSendFailure, o as RESERVED_PARTICIPANT_IDS, s as SOCKET_TIMINGS, t as DRAIN_TIMINGS } from "./internal-Dq2qYxou.js";
4
- import { i as streamActivities, r as createStreamActivity, t as defaultSleep } from "./store-polling-CmxUbV93.js";
1
+ import { a as NonRetriableError, i as A2Error, n as isSessionCleared, o as asStoreUnavailable, r as sessionClearedError } from "./session-clear-DGI4jTQ5.js";
2
+ import { n as foldEvent, s as validateSync, t as cloneInitial } from "./reducer-BAXc8JyF.js";
3
+ import { n as encodeState, t as decodeState } from "./reducer-runtime-DQwz_J59.js";
4
+ import { _ as nullProtoRecord, c as STATE_CACHE, g as markSchedulerSendFailure, i as POLL_TIMINGS, l as STREAM_TIMINGS, m as failedPreviousAttempt, o as RESERVED_PARTICIPANT_IDS, s as SOCKET_TIMINGS, t as DRAIN_TIMINGS, v as serverInternals, y as stateTailReaders } from "./internal-B2T3U4Rq.js";
5
+ import { t as boundedCache } from "./bounded-cache-CPHWF-8h.js";
6
+ import { i as streamActivities, r as createStreamActivity, t as defaultSleep } from "./store-polling-B5FcwJfo.js";
7
+ import { i as takeBuffered, n as isSerializedEvent, r as parseSerializedEvent, t as bufferedEvents } from "./stream-buffer-Cx7AAPGq.js";
5
8
  import { r as platformWaitUntil, t as invocationDeadlineMs } from "./platform-B4TnJtWu.js";
6
- import { _ as socketErrorAckFor, a as errorToWire, b as socketUnsubscribedFor, g as socketAckFor, h as presenceSnapshotToWire, i as errorStatus, n as asA2Error, p as presencePatchToWire, s as eventToWire, t as SOCKET_PING_FRAME, v as socketFrameFor, y as socketSubscribedFor } from "./wire-BO5wWCb1.js";
9
+ import { A as stateSnapshotToWire, C as serializedEventToWireJson, D as socketFrameFor, E as socketEventsFor, O as socketSubscribedFor, S as presenceSnapshotToWire, T as socketErrorAckFor, b as presencePatchToWire, d as heldEventBatch, i as compactEventJson, j as wireVersion, k as socketUnsubscribedFor, m as isStreamEvent, n as SOCKET_PING_FRAME, o as errorStatus, r as asA2Error, s as errorToWire, t as EVENT_WRITE_CHARS, u as eventToWire, w as socketAckFor } from "./wire-rSUglYo8.js";
7
10
  import { t as retryableLazy } from "./retryable-lazy-DZWmHpii.js";
8
11
  //#region src/push-envelope.ts
9
12
  /**
@@ -191,25 +194,43 @@ function socketShell(socket, release, onFrame, deadlineAt) {
191
194
  track
192
195
  };
193
196
  }
194
- const socketSubscription = (target, startAfter, presence) => ({
195
- target,
196
- iterator: (presence ? target.stream({
197
- startAfter,
198
- presence: true
199
- }) : target.stream({ startAfter }))[Symbol.asyncIterator](),
200
- stopped: false
201
- });
197
+ const socketSubscription = (options) => {
198
+ const { target, startAfter, presence, attempt } = options;
199
+ return {
200
+ target,
201
+ iterator: (presence ? target.stream({
202
+ startAfter,
203
+ presence: true
204
+ }) : target.stream({ startAfter }))[Symbol.asyncIterator](),
205
+ stopped: false,
206
+ ...attempt === void 0 ? {} : { attempt }
207
+ };
208
+ };
202
209
  const stopSubscription = (subscription) => {
203
210
  subscription.stopped = true;
204
211
  subscription.iterator.return?.()?.catch(() => {});
205
212
  };
206
- const pumpSubscription = async (shell, subscription, frameFor) => {
213
+ /**
214
+ * Sends a subscription's items until it ends. With `batch`, an event and
215
+ * the events its iterator already holds go out as one frame.
216
+ */
217
+ const pumpSubscription = async (options) => {
218
+ const { shell, subscription, frameFor, batch } = options;
207
219
  for (;;) {
208
220
  while (!shell.torn() && !subscription.stopped && shell.buffered() > SOCKET_TIMINGS.highWaterMarkBytes) await new Promise((wake) => setTimeout(wake, SOCKET_TIMINGS.resumePollMs));
209
221
  if (shell.torn() || subscription.stopped) return;
210
222
  const { value, done } = await subscription.iterator.next();
211
223
  if (shell.torn() || subscription.stopped || done) return;
212
- shell.send(frameFor(value));
224
+ if (!batch || !isStreamEvent(value)) {
225
+ shell.send(frameFor(value));
226
+ continue;
227
+ }
228
+ const { events } = heldEventBatch({
229
+ first: value,
230
+ iterator: subscription.iterator,
231
+ encode: compactEventJson
232
+ });
233
+ shell.send(batch(events));
213
234
  }
214
235
  };
215
236
  const handleSocketPush = async (shell, target, sessionId, frame, req, ackSessionId, gatePush, activate) => {
@@ -255,7 +276,11 @@ const applySocketPresence = async (shell, target, sessionId, frame, gatePush) =>
255
276
  };
256
277
  function sessionSocket(sessionId, session, socket, options) {
257
278
  const target = session;
258
- const subscription = socketSubscription(target, options?.startAfter ?? 0, options?.presence === true);
279
+ const subscription = socketSubscription({
280
+ target,
281
+ startAfter: options?.startAfter ?? 0,
282
+ presence: options?.presence === true
283
+ });
259
284
  const handleFrame = (frame) => {
260
285
  switch (frame["kind"]) {
261
286
  case "push": {
@@ -286,20 +311,41 @@ function sessionSocket(sessionId, session, socket, options) {
286
311
  }
287
312
  };
288
313
  const shell = socketShell(socket, () => stopSubscription(subscription), handleFrame, options?.deadline);
289
- pumpSubscription(shell, subscription, socketFrameFor).then(() => shell.shutdown(1e3), () => shell.shutdown(1011, "stream failed"));
314
+ pumpSubscription({
315
+ shell,
316
+ subscription,
317
+ frameFor: socketFrameFor,
318
+ ...options?.wire === 2 ? { batch: (events) => socketEventsFor(events) } : {}
319
+ }).then(() => shell.shutdown(1e3), () => shell.shutdown(1011, "stream failed"));
290
320
  }
291
321
  const parseSubscribeEntries = (value) => {
292
322
  if (!Array.isArray(value) || value.length === 0) return null;
293
323
  const entries = [];
294
324
  for (const entry of value) {
295
325
  if (entry === null || typeof entry !== "object") return null;
296
- const { id, index } = entry;
326
+ const { id, attempt, index, bootstrap, reducer } = entry;
297
327
  if (typeof id !== "string" || id.length === 0) return null;
298
- if (typeof index !== "number" || !Number.isInteger(index) || index < 0) return null;
299
- entries.push({
328
+ if (attempt !== void 0 && (typeof attempt !== "number" || !Number.isSafeInteger(attempt) || attempt < 0)) return null;
329
+ if (reducer !== void 0 && (typeof reducer !== "string" || reducer.length === 0)) return null;
330
+ const lane = {
300
331
  id,
301
- index
302
- });
332
+ ...attempt === void 0 ? {} : { attempt }
333
+ };
334
+ if (bootstrap === true) {
335
+ if (index !== void 0 || typeof reducer !== "string") return null;
336
+ entries.push({
337
+ ...lane,
338
+ bootstrap: true,
339
+ reducer
340
+ });
341
+ } else {
342
+ if (bootstrap !== void 0 || typeof index !== "number" || !Number.isSafeInteger(index) || index < 0) return null;
343
+ entries.push({
344
+ ...lane,
345
+ index,
346
+ ...reducer === void 0 ? {} : { reducer }
347
+ });
348
+ }
303
349
  }
304
350
  return entries;
305
351
  };
@@ -322,18 +368,34 @@ function sessionsSocket(resolve, socket, options) {
322
368
  if (subscriptions.get(id) === subscription) subscriptions.delete(id);
323
369
  };
324
370
  const runSubscription = async (id, subscription) => {
371
+ const { attempt } = subscription;
325
372
  try {
326
- await pumpSubscription(shell, subscription, (item) => socketFrameFor(item, id));
373
+ await pumpSubscription({
374
+ shell,
375
+ subscription,
376
+ frameFor: (item) => socketFrameFor(item, id),
377
+ ...options?.wire === 2 ? { batch: (events) => socketEventsFor(events, id) } : {}
378
+ });
327
379
  if (shell.torn() || subscription.stopped) return;
328
380
  retire(id, subscription);
329
- shell.send(socketUnsubscribedFor(id));
381
+ shell.send(socketUnsubscribedFor({
382
+ sessionId: id,
383
+ attempt
384
+ }));
330
385
  } catch (error) {
331
386
  if (shell.torn() || subscription.stopped) return;
332
387
  retire(id, subscription);
333
- shell.send(socketUnsubscribedFor(id, "stream failed", asA2Error(error)));
388
+ shell.send(socketUnsubscribedFor({
389
+ sessionId: id,
390
+ attempt,
391
+ reason: "stream failed",
392
+ error: asA2Error(error)
393
+ }));
334
394
  }
335
395
  };
336
- const startSubscription = async (id, index) => {
396
+ const startSubscription = async (entry) => {
397
+ const { id, attempt } = entry;
398
+ const index = entry.index ?? 0;
337
399
  const existing = subscriptions.get(id);
338
400
  if (existing) {
339
401
  stopSubscription(existing);
@@ -341,19 +403,37 @@ function sessionsSocket(resolve, socket, options) {
341
403
  }
342
404
  let session;
343
405
  try {
344
- session = await resolve(id, index);
406
+ session = await resolve(id, index, entry);
345
407
  } catch (error) {
346
- if (!shell.torn()) shell.send(socketUnsubscribedFor(id, "stream failed", asA2Error(error)));
408
+ if (!shell.torn()) shell.send(socketUnsubscribedFor({
409
+ sessionId: id,
410
+ attempt,
411
+ reason: "stream failed",
412
+ error: asA2Error(error)
413
+ }));
347
414
  return;
348
415
  }
349
416
  if (shell.torn()) return;
350
417
  if (session === null) {
351
- shell.send(socketUnsubscribedFor(id, "subscribe rejected", new A2Error("FORBIDDEN", "the subscription is not authorized")));
418
+ shell.send(socketUnsubscribedFor({
419
+ sessionId: id,
420
+ attempt,
421
+ reason: "subscribe rejected",
422
+ error: new A2Error("FORBIDDEN", "the subscription is not authorized")
423
+ }));
352
424
  return;
353
425
  }
354
- const subscription = socketSubscription(session, index, presence);
426
+ const subscription = socketSubscription({
427
+ target: session,
428
+ startAfter: index,
429
+ presence,
430
+ attempt
431
+ });
355
432
  subscriptions.set(id, subscription);
356
- shell.send(socketSubscribedFor(id));
433
+ shell.send(socketSubscribedFor({
434
+ sessionId: id,
435
+ attempt
436
+ }));
357
437
  runSubscription(id, subscription);
358
438
  };
359
439
  let controlTail = Promise.resolve();
@@ -384,7 +464,7 @@ function sessionsSocket(resolve, socket, options) {
384
464
  shell.malformedFrame("malformed subscribe frame");
385
465
  return;
386
466
  }
387
- for (const entry of entries) control(() => startSubscription(entry.id, entry.index));
467
+ for (const entry of entries) control(() => startSubscription(entry));
388
468
  return;
389
469
  }
390
470
  case "unsubscribe": {
@@ -449,7 +529,10 @@ function sessionsSocket(resolve, socket, options) {
449
529
  */
450
530
  /**
451
531
  * Pipe a live event iterable into an SSE `Response`. Each event is one
452
- * frame — `id:` carries the event-log index, `data:` the JSON event. A
532
+ * frame — `id:` carries the event-log index, `data:` the JSON event.
533
+ * Frames for events the iterable already holds are written together. With
534
+ * `wire: 2` those events share one frame instead: `data:` is a JSON array of
535
+ * `CompactWireEvent`s and `id:` the last one's index. A
453
536
  * `stream({ presence: true })` iterable also yields presence items,
454
537
  * which ride as named frames old clients skip: `event:
455
538
  * presence-snapshot` first (the map, ISO dates), then `event: presence`
@@ -463,7 +546,8 @@ function sessionsSocket(resolve, socket, options) {
463
546
  * closes cleanly one second before it so the client reconnects without
464
547
  * a platform-timeout failure.
465
548
  */
466
- function sseResponse(iterable) {
549
+ function sseResponse(iterable, options = {}) {
550
+ const write = options.wire === 2 ? batchedFrames : eventFrames;
467
551
  const iterator = iterable[Symbol.asyncIterator]();
468
552
  const encoder = new TextEncoder();
469
553
  let heartbeat;
@@ -529,7 +613,7 @@ function sseResponse(iterable) {
529
613
  controller.close();
530
614
  return;
531
615
  }
532
- controller.enqueue(encoder.encode(frameFor(value)));
616
+ controller.enqueue(encoder.encode(write(value, iterator)));
533
617
  } catch (error) {
534
618
  if (closed) return;
535
619
  closed = true;
@@ -554,12 +638,114 @@ function sseResponse(iterable) {
554
638
  }
555
639
  });
556
640
  }
641
+ /** Version 1: one frame per event, then each event the iterator holds. */
642
+ function eventFrames(first, iterator) {
643
+ let frames = frameFor(first);
644
+ while (frames.length < EVENT_WRITE_CHARS) {
645
+ const event = takeBuffered(iterator);
646
+ if (!event) break;
647
+ frames += frameFor(event);
648
+ }
649
+ return frames;
650
+ }
651
+ /**
652
+ * Version 2: events this write carries share one frame, a JSON array whose
653
+ * `id:` is its last index. Held events are always events, so only `first`
654
+ * can be a snapshot or presence frame.
655
+ */
656
+ function batchedFrames(first, iterator) {
657
+ const lead = isStreamEvent(first) ? "" : frameFor(first);
658
+ const head = isStreamEvent(first) ? first : takeBuffered(iterator);
659
+ if (!head) return lead;
660
+ const { events, last } = heldEventBatch({
661
+ first: head,
662
+ iterator,
663
+ encode: compactJsonLine
664
+ });
665
+ return `${lead}id: ${last.index}\ndata: [${events.join(",")}]\n\n`;
666
+ }
667
+ const compactJsonLine = (event) => isSerializedEvent(event) && /[\r\n]/.test(event.payloadJson) ? compactEventJson(parseSerializedEvent(event)) : compactEventJson(event);
557
668
  function frameFor(item) {
669
+ if ("kind" in item) return `event: a2-snapshot\ndata: ${JSON.stringify(stateSnapshotToWire(item))}\n\n`;
558
670
  if ("snapshot" in item) return `event: presence-snapshot\ndata: ${JSON.stringify(presenceSnapshotToWire(item))}\n\n`;
559
671
  if ("participant" in item) return `event: presence\ndata: ${JSON.stringify(presencePatchToWire(item))}\n\n`;
672
+ if (isSerializedEvent(item)) return /[\r\n]/.test(item.payloadJson) ? `id: ${item.index}\ndata: ${JSON.stringify(eventToWire(parseSerializedEvent(item)))}\n\n` : `id: ${item.index}\ndata: ${serializedEventToWireJson(item)}\n\n`;
560
673
  return `id: ${item.index}\ndata: ${JSON.stringify(eventToWire(item))}\n\n`;
561
674
  }
562
675
  //#endregion
676
+ //#region src/bootstrap-stream.ts
677
+ function bootstrapStream(options) {
678
+ return { [Symbol.asyncIterator]() {
679
+ let closed = false;
680
+ let index;
681
+ let source;
682
+ let stopActivity;
683
+ const abort = new AbortController();
684
+ const activity = createStreamActivity(() => {
685
+ if (source) streamActivities.get(source)?.activate();
686
+ });
687
+ const iterator = {
688
+ async next() {
689
+ if (closed) return {
690
+ done: true,
691
+ value: void 0
692
+ };
693
+ if (index === void 0) {
694
+ let cancel;
695
+ const cancelled = new Promise((resolve) => {
696
+ cancel = () => resolve(void 0);
697
+ abort.signal.addEventListener("abort", cancel, { once: true });
698
+ });
699
+ try {
700
+ const snapshot = await Promise.race([options.snapshot(), cancelled]);
701
+ if (closed || snapshot === void 0) return {
702
+ done: true,
703
+ value: void 0
704
+ };
705
+ index = snapshot.index;
706
+ return {
707
+ done: false,
708
+ value: snapshot
709
+ };
710
+ } finally {
711
+ if (cancel) abort.signal.removeEventListener("abort", cancel);
712
+ }
713
+ }
714
+ if (!source) {
715
+ source = options.stream(index)[Symbol.asyncIterator]();
716
+ const sourceActivity = streamActivities.get(source);
717
+ if (sourceActivity) {
718
+ activity.setIdle(sourceActivity.idle);
719
+ stopActivity = sourceActivity.subscribe(activity.setIdle);
720
+ }
721
+ }
722
+ const result = await source.next();
723
+ return closed ? {
724
+ done: true,
725
+ value: void 0
726
+ } : result;
727
+ },
728
+ async return() {
729
+ if (closed) return {
730
+ done: true,
731
+ value: void 0
732
+ };
733
+ closed = true;
734
+ abort.abort();
735
+ stopActivity?.();
736
+ await source?.return?.();
737
+ return {
738
+ done: true,
739
+ value: void 0
740
+ };
741
+ }
742
+ };
743
+ streamActivities.set(iterator, activity);
744
+ bufferedEvents.set(iterator, () => closed || !source ? void 0 : takeBuffered(source));
745
+ return iterator;
746
+ } };
747
+ }
748
+ //#endregion
563
749
  //#region src/server-fetch.ts
564
750
  const serverFetchHooks = /* @__PURE__ */ new WeakMap();
565
751
  function setServerFetchHooks(server, hooks) {
@@ -581,6 +767,23 @@ const parseResumeIndex = (raw) => {
581
767
  if (!Number.isSafeInteger(index) || index < 0) throw new A2Error("INVALID_PAYLOAD", "stream index must be a non-negative safe integer");
582
768
  return index;
583
769
  };
770
+ const parseSubscription = (params) => {
771
+ const reducer = params.get("reducer");
772
+ const bootstrap = params.get("bootstrap");
773
+ const index = params.get("index");
774
+ if (reducer === "") throw new A2Error("INVALID_PAYLOAD", "empty reducer name");
775
+ if (bootstrap !== null) {
776
+ if (bootstrap !== "1" || index !== null || reducer === null) throw new A2Error("INVALID_PAYLOAD", "bootstrap requires a reducer and no index");
777
+ return {
778
+ bootstrap: true,
779
+ reducer
780
+ };
781
+ }
782
+ return {
783
+ index: parseResumeIndex(index),
784
+ ...reducer === null ? {} : { reducer }
785
+ };
786
+ };
584
787
  const errorResponse = (error) => {
585
788
  const a2error = asA2Error(error);
586
789
  return Response.json(errorToWire(a2error), { status: errorStatus(a2error.code) });
@@ -630,17 +833,54 @@ function createServerFetch(server) {
630
833
  return asA2Error(error);
631
834
  }
632
835
  };
633
- const attachMultiplexed = (socket) => {
836
+ const selectedSession = (sessionId, selection) => {
837
+ const reducer = server.contract.reducer;
838
+ if (selection.reducer !== void 0 && reducer !== void 0 && reducer.name !== selection.reducer) throw new A2Error("INVALID_PAYLOAD", "client reducer does not match the public contract reducer");
839
+ const target = server.session(sessionId);
840
+ const streamTarget = target;
841
+ if (!selection.bootstrap) return streamTarget;
842
+ if (!reducer) throw new A2Error("INVALID_PAYLOAD", "contract has no public reducer");
843
+ return {
844
+ ...streamTarget,
845
+ stream: (streamOptions) => bootstrapStream({
846
+ snapshot: async () => {
847
+ const initial = await target.state(reducer);
848
+ return {
849
+ kind: "snapshot",
850
+ sessionId,
851
+ name: reducer.name,
852
+ index: initial.index,
853
+ state: encodeState({
854
+ reducer,
855
+ state: initial.state
856
+ }),
857
+ ...initial.clearIndex === void 0 ? {} : { clearIndex: initial.clearIndex }
858
+ };
859
+ },
860
+ stream: (index) => {
861
+ const bounds = {
862
+ startAfter: index,
863
+ presence: streamOptions.presence === true
864
+ };
865
+ return target.stream(bounds);
866
+ }
867
+ })
868
+ };
869
+ };
870
+ const attachMultiplexed = (wire) => (socket) => {
634
871
  const deadline = invocationDeadlineMs();
635
- sessionsSocket(async (sessionId, startAfter) => {
636
- return await authorized({
872
+ sessionsSocket(async (sessionId, startAfter, selection = { index: startAfter }) => {
873
+ const operation = {
637
874
  type: "stream",
638
875
  sessionId,
639
876
  startAfter,
877
+ ...selection.bootstrap ? { bootstrap: true } : {},
640
878
  transport: "websocket"
641
- }) ? server.session(sessionId) : null;
879
+ };
880
+ return await authorized(operation) ? selectedSession(sessionId, selection) : null;
642
881
  }, socket, {
643
882
  presence,
883
+ wire,
644
884
  ...deadline === null ? {} : { deadline },
645
885
  gatePush: gateSocketPush
646
886
  });
@@ -649,23 +889,27 @@ function createServerFetch(server) {
649
889
  if (request.method === "GET") {
650
890
  if (request.headers.get("upgrade")?.toLowerCase() === "websocket") {
651
891
  if (options?.upgradeWebSocket === void 0) return new Response("WebSocket upgrade requested, but server.fetch() has no upgrade implementation", { status: 426 });
652
- if (options.multiplexWebSocket === true) return await options.upgradeWebSocket(attachMultiplexed);
653
892
  const { searchParams } = new URL(request.url);
893
+ const wire = wireVersion(searchParams.get("wire"));
894
+ if (options.multiplexWebSocket === true) return await options.upgradeWebSocket(attachMultiplexed(wire));
654
895
  const sessionId = searchParams.get("sessionId");
655
896
  if (sessionId === null || sessionId.length === 0) throw new A2Error("INVALID_PAYLOAD", "missing sessionId");
656
- const startAfter = parseResumeIndex(searchParams.get("index"));
897
+ const selection = parseSubscription(searchParams);
898
+ const startAfter = selection.index ?? 0;
657
899
  await authorize({
658
900
  type: "stream",
659
901
  sessionId,
660
902
  startAfter,
903
+ ...selection.bootstrap ? { bootstrap: true } : {},
661
904
  transport: "websocket"
662
905
  });
663
- const target = server.session(sessionId);
906
+ const target = selectedSession(sessionId, selection);
664
907
  const attach = (socket) => {
665
908
  const deadline = invocationDeadlineMs();
666
909
  sessionSocket(sessionId, target, socket, {
667
910
  startAfter,
668
911
  presence,
912
+ wire,
669
913
  ...deadline === null ? {} : { deadline },
670
914
  gatePush: gateSocketPush
671
915
  });
@@ -685,22 +929,28 @@ function createServerFetch(server) {
685
929
  ...bounds,
686
930
  transport: "http"
687
931
  });
688
- const events = await server.session(sessionId).history(bounds);
689
- const covered = events.length === bounds.lte - bounds.gte + 1;
690
- return Response.json(events.map(eventToWire), { headers: { "a2-history-covered": String(covered) } });
932
+ const { events, clearIndex } = await server.historyPage(sessionId, bounds);
933
+ const first = Math.max(bounds.gte, (clearIndex ?? 0) + 1);
934
+ const covered = events.length === Math.max(0, bounds.lte - first + 1);
935
+ return Response.json(events.map(eventToWire), { headers: {
936
+ "a2-history-covered": String(covered),
937
+ ...clearIndex === void 0 ? {} : { "a2-clear-index": String(clearIndex) }
938
+ } });
691
939
  }
692
- const startAfter = parseResumeIndex(searchParams.get("index"));
940
+ const selection = parseSubscription(searchParams);
941
+ const startAfter = selection.index ?? 0;
693
942
  await authorize({
694
943
  type: "stream",
695
944
  sessionId,
696
945
  startAfter,
946
+ ...selection.bootstrap ? { bootstrap: true } : {},
697
947
  transport: "http"
698
948
  });
699
- const target = server.session(sessionId);
949
+ const target = selectedSession(sessionId, selection);
700
950
  return sseResponse(presence ? target.stream({
701
951
  startAfter,
702
952
  presence: true
703
- }) : target.stream({ startAfter }));
953
+ }) : target.stream({ startAfter }), { wire: wireVersion(searchParams.get("wire")) });
704
954
  }
705
955
  if (request.method === "POST") {
706
956
  const body = await parsePushBody(request);
@@ -746,6 +996,7 @@ const APPEND_NAMESPACE = "a2:handler-append:v2";
746
996
  const RETURN_NAMESPACE = "a2:handler-return:v1";
747
997
  const SCHEDULE_NAMESPACE = "a2:schedule:v1";
748
998
  const SCHEDULED_EVENT_NAMESPACE = "a2:scheduled-event:v1";
999
+ const SCHEDULE_CLEAR_NAMESPACE = "a2:schedule-clear:v1";
749
1000
  async function deterministicIdFromInput(input) {
750
1001
  const digest = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(input));
751
1002
  const bytes = new Uint8Array(digest).slice(0, 16);
@@ -773,13 +1024,31 @@ async function deterministicReturnedEventId(triggerEventId, itemOrdinal) {
773
1024
  itemOrdinal
774
1025
  ]);
775
1026
  }
776
- async function deterministicScheduleId(contract, sessionId, triggerEventId, name) {
1027
+ /**
1028
+ * The id of a scheduled append. `clearIndex` is the lifetime the trigger was
1029
+ * claimed in; it joins the input only when positive, so a trigger id reused
1030
+ * after a clear schedules a distinct task while sessions never cleared keep
1031
+ * the ids they always had.
1032
+ */
1033
+ async function deterministicScheduleId(contract, sessionId, triggerEventId, name, clearIndex = 0) {
777
1034
  return deterministicIdFromInput(JSON.stringify([
778
1035
  SCHEDULE_NAMESPACE,
779
1036
  contract,
780
1037
  sessionId,
781
1038
  triggerEventId,
782
- name
1039
+ name,
1040
+ ...clearIndex > 0 ? [clearIndex] : []
1041
+ ]));
1042
+ }
1043
+ async function deterministicScheduleClearId({ contract, sessionId, triggerEventId, clearIndex, name, ifLastIndex }) {
1044
+ return deterministicIdFromInput(JSON.stringify([
1045
+ SCHEDULE_CLEAR_NAMESPACE,
1046
+ contract,
1047
+ sessionId,
1048
+ triggerEventId,
1049
+ clearIndex,
1050
+ name,
1051
+ ifLastIndex
783
1052
  ]));
784
1053
  }
785
1054
  async function deterministicScheduledEventId(scheduleId, itemOrdinal) {
@@ -812,14 +1081,42 @@ const CONSOLE_TELEMETRY = { span: (name, attributes, fn) => fn({
812
1081
  */
813
1082
  async function deliverSchedulerAppend(server, task) {
814
1083
  if (server.contract.name !== task.contract) throw new TypeError(`a2 scheduler: append task for contract '${task.contract}' cannot be delivered to '${server.contract.name}'`);
1084
+ const { originIndex } = task;
1085
+ if (originIndex !== void 0 && (!Number.isSafeInteger(originIndex) || originIndex < 1)) throw new TypeError("a2 scheduler: append task originIndex must be a positive safe integer");
815
1086
  const internals = serverInternals.get(server);
816
1087
  if (!internals) throw new TypeError(`a2 scheduler: server for contract '${task.contract}' cannot receive scheduled appends`);
817
- await internals.schedulerAppend(task.sessionId, task.events);
1088
+ await internals.schedulerAppend(task.sessionId, task.events, originIndex);
1089
+ }
1090
+ /**
1091
+ * Deliver one authenticated scheduler clear: a conditional
1092
+ * `clear({ ifLastIndex })` that resolves after its deletion finishes, so a
1093
+ * provider retry resumes an interrupted deletion. Custom scheduler adapters
1094
+ * call this after validating their transport envelope; application code uses
1095
+ * `session.schedule.clear`.
1096
+ */
1097
+ async function deliverSchedulerClear(server, task) {
1098
+ if (server.contract.name !== task.contract) throw new TypeError(`a2 scheduler: clear task for contract '${task.contract}' cannot be delivered to '${server.contract.name}'`);
1099
+ if (task.kind !== "clear" || !Number.isSafeInteger(task.ifLastIndex) || task.ifLastIndex < 0) throw new TypeError("a2 scheduler: a clear task needs kind \"clear\" and a non-negative safe integer ifLastIndex");
1100
+ const internals = serverInternals.get(server);
1101
+ if (!internals) throw new TypeError(`a2 scheduler: server for contract '${task.contract}' cannot receive scheduled clears`);
1102
+ await internals.schedulerClear(task.sessionId, task.ifLastIndex);
818
1103
  }
819
1104
  function handler(entry) {
820
1105
  return typeof entry === "function" ? { handler: entry } : entry;
821
1106
  }
822
1107
  const MAX_FAILURES = 10;
1108
+ /** Abort a lost attempt's handler and release its drain slot. */
1109
+ function endSuperseded(entry, message) {
1110
+ entry.supersede.abort(new A2Error("SUPERSEDED_ATTEMPT", message));
1111
+ entry.fence({
1112
+ status: "superseded",
1113
+ opensLane: false,
1114
+ appendsEvents: false
1115
+ });
1116
+ }
1117
+ /** Pause between clear-deletion batches that found only locked rows. */
1118
+ const RECLAIM_STALL_MS = 20;
1119
+ const RECLAIM_STALL_LIMIT = 50;
823
1120
  /** Default: how long a presence value survives without a refreshing set. */
824
1121
  const PRESENCE_TTL_MS = 6e4;
825
1122
  function createDrainSignal() {
@@ -901,6 +1198,19 @@ function currentSchedulerSlot(now) {
901
1198
  */
902
1199
  const NS = "";
903
1200
  const devDefaultStore = retryableLazy(() => import("./store-sqlite.js").then((m) => m.sqlite()));
1201
+ /** Waits for a scheduler arm for at most `schedulerArmTimeoutMs`. */
1202
+ async function joinSchedulerArm(arm) {
1203
+ let timeout = null;
1204
+ try {
1205
+ await Promise.race([arm, new Promise((_, reject) => {
1206
+ timeout = setTimeout(() => {
1207
+ reject(/* @__PURE__ */ new Error(`a2 scheduler arm timed out after ${DRAIN_TIMINGS.schedulerArmTimeoutMs}ms`));
1208
+ }, DRAIN_TIMINGS.schedulerArmTimeoutMs);
1209
+ })]);
1210
+ } finally {
1211
+ if (timeout) clearTimeout(timeout);
1212
+ }
1213
+ }
904
1214
  function environment() {
905
1215
  const env = typeof process === "undefined" ? void 0 : process.env?.["NODE_ENV"];
906
1216
  if (env === "test") return "test";
@@ -927,6 +1237,17 @@ function foldPresenceRows(rows) {
927
1237
  };
928
1238
  return map;
929
1239
  }
1240
+ const addSnapshotWrite = (writes, index, state, pinEventIndex) => {
1241
+ let write = writes.get(index);
1242
+ if (!write) {
1243
+ write = {
1244
+ state: cloneInitial(state),
1245
+ pinEventIndexes: /* @__PURE__ */ new Set()
1246
+ };
1247
+ writes.set(index, write);
1248
+ }
1249
+ if (pinEventIndex !== void 0) write.pinEventIndexes.add(pinEventIndex);
1250
+ };
930
1251
  /** Implement a contract: bind its vocabulary to storage and reactions. */
931
1252
  function createServer(options) {
932
1253
  const serverContract = options?.contract;
@@ -1107,7 +1428,7 @@ function createServer(options) {
1107
1428
  * the drain it schedules is created in the append's active context —
1108
1429
  * with a real tracer, causal work remains connected.
1109
1430
  */
1110
- const appendCore = (sessionId, events, source, mode, onAppended, cause, generatedIds, requireDurableInitialArm = false) => telemetry.span("a2.append", {
1431
+ const appendCore = (sessionId, events, source, mode, onAppended, cause, generatedIds, requireDurableInitialArm = false, originIndex) => telemetry.span("a2.append", {
1111
1432
  "a2.contract": name,
1112
1433
  "a2.session_id": sessionId,
1113
1434
  "a2.append.source": source,
@@ -1126,9 +1447,9 @@ function createServer(options) {
1126
1447
  let rows;
1127
1448
  let shouldHealSession;
1128
1449
  try {
1129
- const result = await store.append(nsId(sessionId), validated);
1450
+ const result = await store.append(nsId(sessionId), validated, originIndex === void 0 ? void 0 : { originIndex });
1130
1451
  rows = result.events;
1131
- shouldHealSession = result.hasPending;
1452
+ shouldHealSession = result.hasPending && !(source === "handler" && validated.every((event) => event.settled === true));
1132
1453
  } catch (err) {
1133
1454
  throw asStoreUnavailable(err);
1134
1455
  }
@@ -1138,21 +1459,12 @@ function createServer(options) {
1138
1459
  const dispatchArm = shouldHealSession && mode === "dispatch" ? startSchedulerArm(sessionId, currentSchedulerSlot(Date.now())) : null;
1139
1460
  if (shouldHealSession && mode === "inline") onAppended(appended, initialWatchdogAt);
1140
1461
  if (dispatchArm) await dispatchArm;
1141
- if (initialArm) {
1142
- let timeout = null;
1143
- try {
1144
- await Promise.race([initialArm, new Promise((_, reject) => {
1145
- timeout = setTimeout(() => {
1146
- reject(/* @__PURE__ */ new Error(`a2 scheduler arm timed out after ${DRAIN_TIMINGS.schedulerArmTimeoutMs}ms`));
1147
- }, DRAIN_TIMINGS.schedulerArmTimeoutMs);
1148
- })]);
1149
- } catch (err) {
1150
- span.setAttribute("a2.append.armed", false);
1151
- span.recordError(err);
1152
- if (requireDurableInitialArm) throw err;
1153
- } finally {
1154
- if (timeout) clearTimeout(timeout);
1155
- }
1462
+ if (initialArm) try {
1463
+ await joinSchedulerArm(initialArm);
1464
+ } catch (err) {
1465
+ span.setAttribute("a2.append.armed", false);
1466
+ span.recordError(err);
1467
+ if (requireDurableInitialArm) throw err;
1156
1468
  }
1157
1469
  return appended;
1158
1470
  });
@@ -1204,113 +1516,263 @@ function createServer(options) {
1204
1516
  }
1205
1517
  return plans;
1206
1518
  };
1519
+ const persistSnapshots = ({ store, reducer, sessionId, snapshots }) => {
1520
+ const persistence = Promise.resolve().then(() => telemetry.span("a2.snapshot", {
1521
+ "a2.contract": name,
1522
+ "a2.session_id": sessionId,
1523
+ "a2.snapshot.reducer": reducer.name,
1524
+ "a2.snapshot.index": snapshots.at(-1).index,
1525
+ "a2.snapshot.count": snapshots.length
1526
+ }, async (span) => {
1527
+ try {
1528
+ await store.putSnapshots(nsId(sessionId), reducer.name, snapshots);
1529
+ } catch (error) {
1530
+ try {
1531
+ span.recordError(error);
1532
+ } catch (telemetryError) {
1533
+ throw new AggregateError([error, telemetryError], "Snapshot write and error reporting failed", { cause: telemetryError });
1534
+ }
1535
+ }
1536
+ })).catch((error) => {
1537
+ console.error("[a2] error in a2.snapshot telemetry", {
1538
+ sessionId,
1539
+ reducer: reducer.name,
1540
+ index: snapshots.at(-1).index
1541
+ }, error);
1542
+ });
1543
+ track(persistence);
1544
+ platformWaitUntil(persistence);
1545
+ };
1546
+ const stateCache = boundedCache(STATE_CACHE);
1547
+ const cacheState = (key, next) => {
1548
+ const current = stateCache.get(key);
1549
+ if (current?.reducer === next.reducer && (current.index > next.index || current.index === next.index && current.persistedIndex >= next.persistedIndex)) return;
1550
+ stateCache.set(key, next, next.bytes);
1551
+ };
1552
+ const writeSnapshots = ({ store, reducer, sessionId, writes }) => {
1553
+ const snapshots = [...writes].toSorted(([a], [b]) => a - b).map(([index, write]) => {
1554
+ const snapshot = {
1555
+ index,
1556
+ state: encodeState({
1557
+ reducer,
1558
+ state: write.state
1559
+ })
1560
+ };
1561
+ if (write.pinEventIndexes.size > 0) snapshot.pinEventIndexes = [...write.pinEventIndexes];
1562
+ return snapshot;
1563
+ });
1564
+ if (snapshots.length > 0) persistSnapshots({
1565
+ store,
1566
+ reducer,
1567
+ sessionId,
1568
+ snapshots
1569
+ });
1570
+ return snapshots;
1571
+ };
1572
+ /**
1573
+ * Folds `rows` from `position` in bound order, resolving each entry with a
1574
+ * clone once the rows through its bound are folded. `visit` sees each
1575
+ * entry's state before later rows are folded into it.
1576
+ */
1577
+ const foldEntries = ({ reducer, entries, rows, position, state, index, clearIndex, snapshot, visit }) => {
1578
+ const start = position;
1579
+ const ordered = entries.toSorted((a, b) => (a.throughIndex ?? Number.POSITIVE_INFINITY) - (b.throughIndex ?? Number.POSITIVE_INFINITY));
1580
+ for (const entry of ordered) {
1581
+ const target = entry.throughIndex ?? Number.POSITIVE_INFINITY;
1582
+ while (position < rows.length && rows[position].index <= target) {
1583
+ const row = rows[position];
1584
+ state = foldEvent(reducer, state, toPublic(row));
1585
+ index = row.index;
1586
+ position += 1;
1587
+ }
1588
+ if (entry.triggerIndex !== void 0 && entry.triggerIndex <= clearIndex || entry.throughIndex !== void 0 && entry.throughIndex > 0 && entry.throughIndex < clearIndex) {
1589
+ entry.reject(sessionClearedError(`session '${entry.sessionId}' was cleared at index ${clearIndex}`));
1590
+ continue;
1591
+ }
1592
+ entry.span.setAttribute("a2.state.snapshot", snapshot);
1593
+ entry.span.setAttribute("a2.state.folded", position - start);
1594
+ entry.span.setAttribute("a2.state.index", index);
1595
+ visit(entry, state, index, position - start);
1596
+ entry.resolve({
1597
+ state: cloneInitial(state),
1598
+ index,
1599
+ ...clearIndex > 0 && index >= clearIndex ? { clearIndex } : {}
1600
+ });
1601
+ }
1602
+ return {
1603
+ state,
1604
+ index,
1605
+ position
1606
+ };
1607
+ };
1608
+ /**
1609
+ * Events from `fromIndex` (inclusive) through `throughIndex` or the newest
1610
+ * event, or null when the tail is too long to be worth folding in memory.
1611
+ */
1612
+ const readCachedTail = async (store, sessionId, fromIndex, throughIndex) => {
1613
+ const readTail = stateTailReaders.get(store);
1614
+ if (readTail) try {
1615
+ return await readTail(nsId(sessionId), fromIndex, throughIndex);
1616
+ } catch (err) {
1617
+ throw asStoreUnavailable(err);
1618
+ }
1619
+ const limit = fromIndex + STATE_CACHE.maxTail;
1620
+ const capped = throughIndex === void 0 || throughIndex > limit;
1621
+ const rows = await readHistory(sessionId, {
1622
+ gte: fromIndex,
1623
+ lte: capped ? limit : throughIndex
1624
+ });
1625
+ return capped && rows.at(-1)?.index === limit ? null : rows;
1626
+ };
1627
+ /**
1628
+ * Serves a plan from the cached state when every read is at or past the
1629
+ * cached index. A read bounded at the cached index by an event whose id
1630
+ * matches the cached one (a handler's own trigger) needs no store request;
1631
+ * any other read fetches the tail from the cached event onward and checks
1632
+ * that event's id. Returns false when the store must answer instead.
1633
+ */
1634
+ const foldCachedPlan = async (store, reducer, plan) => {
1635
+ const key = stateCacheKey(plan.sessionId, reducer.name);
1636
+ const cached = stateCache.get(key);
1637
+ if (cached?.reducer !== reducer) return false;
1638
+ if (plan.entries.some((entry) => entry.throughIndex !== void 0 && entry.throughIndex < cached.index)) return false;
1639
+ let rows = [];
1640
+ if (plan.entries.some((entry) => entry.throughIndex !== cached.index || entry.throughEventId !== cached.lastId)) {
1641
+ const tail = await readCachedTail(store, plan.sessionId, cached.index, plan.throughIndex);
1642
+ if (!tail) return false;
1643
+ if (tail[0]?.index !== cached.index || tail[0].id !== cached.lastId || tail.some((row, position) => position > 0 && isSessionCleared(row))) {
1644
+ stateCache.delete(key);
1645
+ return false;
1646
+ }
1647
+ rows = tail;
1648
+ }
1649
+ const writes = /* @__PURE__ */ new Map();
1650
+ const folded = foldEntries({
1651
+ reducer,
1652
+ entries: plan.entries,
1653
+ rows,
1654
+ position: 1,
1655
+ state: rows.length > 1 ? cloneInitial(cached.state) : cached.state,
1656
+ index: cached.index,
1657
+ clearIndex: cached.clearIndex,
1658
+ snapshot: "memory",
1659
+ visit: (entry, state, index) => {
1660
+ if (entry.pinEventIndex !== void 0) addSnapshotWrite(writes, index, state, entry.pinEventIndex);
1661
+ }
1662
+ });
1663
+ const added = Math.max(0, folded.position - 1);
1664
+ const rewritesHead = folded.index - cached.persistedIndex >= STATE_CACHE.snapshotEvery;
1665
+ if (rewritesHead && !writes.has(folded.index)) writes.set(folded.index, {
1666
+ state: folded.state,
1667
+ pinEventIndexes: /* @__PURE__ */ new Set()
1668
+ });
1669
+ const snapshots = writeSnapshots({
1670
+ store,
1671
+ reducer,
1672
+ sessionId: plan.sessionId,
1673
+ writes
1674
+ });
1675
+ const measure = rewritesHead || cached.unmeasured + added >= STATE_CACHE.measureEvery;
1676
+ let grown = 0;
1677
+ if (!measure) for (let position = 1; position < folded.position; position += 1) grown += JSON.stringify(rows[position].payload)?.length ?? 0;
1678
+ cacheState(key, {
1679
+ reducer,
1680
+ index: folded.index,
1681
+ lastId: added > 0 ? rows[folded.position - 1].id : cached.lastId,
1682
+ state: folded.state,
1683
+ bytes: measure ? measuredBytes(reducer, folded.state, folded.index, snapshots) : cached.bytes + grown,
1684
+ unmeasured: measure ? 0 : cached.unmeasured + added,
1685
+ persistedIndex: Math.max(cached.persistedIndex, snapshots.at(-1)?.index ?? 0),
1686
+ clearIndex: cached.clearIndex
1687
+ });
1688
+ return true;
1689
+ };
1207
1690
  const foldStatePlan = async (store, reducer, plan, stateRead) => {
1208
1691
  let state = cloneInitial(reducer.initialState);
1209
1692
  let index = 0;
1210
1693
  let snapshotOutcome = "miss";
1211
1694
  const snap = stateRead.snapshot;
1212
1695
  let rows = stateRead.events;
1696
+ let clearIndex = stateRead.clearIndex ?? 0;
1213
1697
  if (snap) {
1214
- if (reducer.stateSchema) {
1215
- const result = validateSync(reducer.stateSchema, snap.state, "the stateSchema");
1698
+ let decoded;
1699
+ try {
1700
+ decoded = decodeState({
1701
+ reducer,
1702
+ state: snap.state
1703
+ });
1704
+ } catch {
1705
+ snapshotOutcome = "rejected";
1706
+ }
1707
+ if (snapshotOutcome !== "rejected" && reducer.stateSchema) {
1708
+ const result = validateSync(reducer.stateSchema, decoded, "the stateSchema");
1216
1709
  if (result.issues) snapshotOutcome = "rejected";
1217
1710
  else {
1218
1711
  state = result.value;
1219
1712
  index = snap.index;
1220
1713
  snapshotOutcome = "hit";
1221
1714
  }
1222
- } else {
1223
- state = snap.state;
1715
+ } else if (snapshotOutcome !== "rejected") {
1716
+ state = decoded;
1224
1717
  index = snap.index;
1225
1718
  snapshotOutcome = "hit";
1226
1719
  }
1227
1720
  }
1228
- if (snapshotOutcome === "rejected") try {
1229
- rows = await store.read(nsId(plan.sessionId), plan.throughIndex === void 0 ? void 0 : { throughIndex: plan.throughIndex });
1230
- } catch (err) {
1231
- throw asStoreUnavailable(err);
1232
- }
1233
- const ordered = plan.entries.toSorted((a, b) => (a.throughIndex ?? Number.POSITIVE_INFINITY) - (b.throughIndex ?? Number.POSITIVE_INFINITY));
1234
- const writes = /* @__PURE__ */ new Map();
1235
- let rowPosition = 0;
1236
- for (const entry of ordered) {
1237
- const target = entry.throughIndex ?? Number.POSITIVE_INFINITY;
1238
- while (rowPosition < rows.length && rows[rowPosition].index <= target) {
1239
- const row = rows[rowPosition];
1240
- state = reducer.fold(state, toPublic(row));
1241
- index = row.index;
1242
- rowPosition += 1;
1243
- }
1244
- const resultState = cloneInitial(state);
1245
- const folded = rowPosition;
1246
- entry.span.setAttribute("a2.state.snapshot", snapshotOutcome);
1247
- entry.span.setAttribute("a2.state.folded", folded);
1248
- entry.span.setAttribute("a2.state.index", index);
1249
- if (index > 0 && (folded > 0 || entry.pinEventIndex !== void 0)) {
1250
- const advancesHead = stateRead.headIndex === null || index > stateRead.headIndex;
1251
- const repairsHead = snapshotOutcome === "rejected" && index === stateRead.headIndex;
1252
- if (!advancesHead && !repairsHead && entry.pinEventIndex === void 0) {
1253
- entry.resolve({
1254
- state: resultState,
1255
- index
1256
- });
1257
- continue;
1258
- }
1259
- let write = writes.get(index);
1260
- if (!write) {
1261
- write = {
1262
- state: cloneInitial(resultState),
1263
- pinEventIndexes: /* @__PURE__ */ new Set()
1264
- };
1265
- writes.set(index, write);
1266
- }
1267
- if (entry.pinEventIndex !== void 0) write.pinEventIndexes.add(entry.pinEventIndex);
1721
+ if (snapshotOutcome === "rejected") {
1722
+ try {
1723
+ rows = await store.read(nsId(plan.sessionId), plan.throughIndex === void 0 ? void 0 : { throughIndex: plan.throughIndex });
1724
+ } catch (err) {
1725
+ throw asStoreUnavailable(err);
1268
1726
  }
1269
- entry.resolve({
1270
- state: resultState,
1271
- index
1272
- });
1727
+ if (rows[0] && isSessionCleared(rows[0])) clearIndex = Math.max(clearIndex, rows[0].index);
1273
1728
  }
1274
- if (writes.size > 0) {
1275
- const snapshots = [];
1276
- for (const [snapshotIndex, write] of writes) {
1277
- const snapshot = {
1278
- index: snapshotIndex,
1279
- state: write.state
1280
- };
1281
- if (write.pinEventIndexes.size > 0) snapshot.pinEventIndexes = [...write.pinEventIndexes];
1282
- snapshots.push(snapshot);
1729
+ const writes = /* @__PURE__ */ new Map();
1730
+ const folded = foldEntries({
1731
+ reducer,
1732
+ entries: plan.entries,
1733
+ rows,
1734
+ position: 0,
1735
+ state,
1736
+ index,
1737
+ clearIndex,
1738
+ snapshot: snapshotOutcome,
1739
+ visit: (entry, entryState, entryIndex, count) => {
1740
+ if (entryIndex === 0) return;
1741
+ if (count === 0 && entry.pinEventIndex === void 0) return;
1742
+ const advancesHead = stateRead.headIndex === null || entryIndex > stateRead.headIndex;
1743
+ const repairsHead = snapshotOutcome === "rejected" && entryIndex === stateRead.headIndex;
1744
+ if (!advancesHead && !repairsHead && entry.pinEventIndex === void 0) return;
1745
+ addSnapshotWrite(writes, entryIndex, entryState, entry.pinEventIndex);
1283
1746
  }
1284
- const persistence = Promise.resolve().then(() => telemetry.span("a2.snapshot", {
1285
- "a2.contract": name,
1286
- "a2.session_id": plan.sessionId,
1287
- "a2.snapshot.reducer": reducer.name,
1288
- "a2.snapshot.index": snapshots.at(-1).index,
1289
- "a2.snapshot.count": snapshots.length
1290
- }, async (span) => {
1291
- try {
1292
- await store.putSnapshots(nsId(plan.sessionId), reducer.name, snapshots);
1293
- } catch (error) {
1294
- try {
1295
- span.recordError(error);
1296
- } catch (telemetryError) {
1297
- throw new AggregateError([error, telemetryError], "Snapshot write and error reporting failed", { cause: telemetryError });
1298
- }
1299
- }
1300
- })).catch((error) => {
1301
- console.error("[a2] error in a2.snapshot telemetry", {
1302
- sessionId: plan.sessionId,
1303
- reducer: reducer.name,
1304
- index: snapshots.at(-1).index
1305
- }, error);
1306
- });
1307
- track(persistence);
1308
- platformWaitUntil(persistence);
1309
- }
1747
+ });
1748
+ const snapshots = writeSnapshots({
1749
+ store,
1750
+ reducer,
1751
+ sessionId: plan.sessionId,
1752
+ writes
1753
+ });
1754
+ if (folded.position > 0 && STATE_CACHE.maxEntries > 0) cacheState(stateCacheKey(plan.sessionId, reducer.name), {
1755
+ reducer,
1756
+ index: folded.index,
1757
+ lastId: rows[folded.position - 1].id,
1758
+ state: folded.state,
1759
+ bytes: measuredBytes(reducer, folded.state, folded.index, snapshots),
1760
+ unmeasured: 0,
1761
+ persistedIndex: Math.min(Math.max(stateRead.headIndex ?? 0, ...writes.keys()), folded.index),
1762
+ clearIndex
1763
+ });
1310
1764
  };
1311
1765
  const pendingStateReads = /* @__PURE__ */ new Map();
1312
1766
  const flushStateReads = async (store, reducer, batch) => {
1313
- const plans = planStateReads(batch);
1767
+ const plans = [];
1768
+ await Promise.all(planStateReads(batch).map(async (plan) => {
1769
+ try {
1770
+ if (!await foldCachedPlan(store, reducer, plan)) plans.push(plan);
1771
+ } catch (error) {
1772
+ for (const entry of plan.entries) entry.reject(error);
1773
+ }
1774
+ }));
1775
+ if (plans.length === 0) return;
1314
1776
  let reads;
1315
1777
  try {
1316
1778
  reads = store.readStates ? await store.readStates(plans.map((plan) => ({
@@ -1321,7 +1783,7 @@ function createServer(options) {
1321
1783
  if (reads.length !== plans.length) throw new TypeError("store.readStates must return one result per request");
1322
1784
  } catch (error) {
1323
1785
  const failure = asStoreUnavailable(error);
1324
- for (const entry of batch) entry.reject(failure);
1786
+ for (const plan of plans) for (const entry of plan.entries) entry.reject(failure);
1325
1787
  return;
1326
1788
  }
1327
1789
  await Promise.all(plans.map(async (plan, position) => {
@@ -1332,7 +1794,7 @@ function createServer(options) {
1332
1794
  }
1333
1795
  }));
1334
1796
  };
1335
- const enqueueStateRead = (store, sessionId, reducer, throughIndex, pinEventIndex, span) => new Promise((resolve, reject) => {
1797
+ const enqueueStateRead = (store, sessionId, reducer, bounds, span) => new Promise((resolve, reject) => {
1336
1798
  const reducerName = reducer.name;
1337
1799
  let batch = pendingStateReads.get(reducerName);
1338
1800
  if (!batch) {
@@ -1346,25 +1808,25 @@ function createServer(options) {
1346
1808
  }
1347
1809
  batch.push({
1348
1810
  sessionId,
1349
- ...throughIndex === void 0 ? {} : { throughIndex },
1350
- ...pinEventIndex === void 0 ? {} : { pinEventIndex },
1811
+ ...bounds,
1351
1812
  span,
1352
1813
  resolve,
1353
1814
  reject
1354
1815
  });
1355
1816
  });
1356
- const readState = async (sessionId, reducer, throughIndex, pinEventIndex) => {
1817
+ const readState = async (sessionId, reducer, bounds) => {
1357
1818
  const store = await resolveStore();
1358
1819
  return telemetry.span("a2.state", {
1359
1820
  "a2.contract": name,
1360
1821
  "a2.session_id": sessionId,
1361
1822
  "a2.state.reducer": reducer.name
1362
- }, (span) => enqueueStateRead(store, sessionId, reducer, throughIndex, pinEventIndex, span));
1823
+ }, (span) => enqueueStateRead(store, sessionId, reducer, bounds, span));
1363
1824
  };
1364
1825
  const makeSchedule = (sessionId, trigger) => {
1365
1826
  const triggerEventId = trigger?.id ?? null;
1366
1827
  const triggerCreatedAt = trigger?.createdAt.getTime();
1367
- return async (scheduleName, timing, ...events) => {
1828
+ const clearIndex = trigger?.clearIndex ?? 0;
1829
+ const schedule = async (scheduleName, timing, ...events) => {
1368
1830
  if (!scheduler) throw new TypeError("session.schedule requires a configured scheduler adapter");
1369
1831
  assertScheduleName(scheduleName);
1370
1832
  if (events.length === 0) throw new TypeError("schedule requires at least one event");
@@ -1379,7 +1841,7 @@ function createServer(options) {
1379
1841
  id: event.id
1380
1842
  });
1381
1843
  const validated = validateEvents(sessionId, structuredClone(snapshottedEvents), false);
1382
- const scheduleId = await deterministicScheduleId(name, sessionId, triggerEventId, scheduleName);
1844
+ const scheduleId = await deterministicScheduleId(name, sessionId, triggerEventId, scheduleName, clearIndex);
1383
1845
  const scheduledEvents = await Promise.all(validated.map(async (event, item) => ({
1384
1846
  id: event.id ?? await deterministicScheduledEventId(scheduleId, item),
1385
1847
  type: event.type,
@@ -1394,12 +1856,45 @@ function createServer(options) {
1394
1856
  contract: name,
1395
1857
  sessionId,
1396
1858
  dueAt,
1397
- events: scheduledEvents
1859
+ events: scheduledEvents,
1860
+ ...trigger ? { originIndex: trigger.index } : {}
1398
1861
  });
1399
1862
  } catch (error) {
1400
1863
  throw markSchedulerSendFailure(error);
1401
1864
  }
1402
1865
  };
1866
+ const clear = async (scheduleName, clearOptions) => {
1867
+ if (!scheduler) throw new TypeError("session.schedule.clear requires a configured scheduler adapter");
1868
+ assertScheduleName(scheduleName);
1869
+ if (clearOptions === null || typeof clearOptions !== "object") throw new TypeError("schedule.clear requires an ifLastIndex");
1870
+ const { ifLastIndex, ...timing } = clearOptions;
1871
+ assertClearIndex(ifLastIndex);
1872
+ const dueAt = scheduleDueAt(timing, triggerCreatedAt ?? Date.now());
1873
+ const store = await resolveStore();
1874
+ if (!store.clear || !store.reclaim) throw new TypeError("session.schedule.clear requires a store that supports clearing");
1875
+ const id = await deterministicScheduleClearId({
1876
+ contract: name,
1877
+ sessionId,
1878
+ triggerEventId,
1879
+ clearIndex,
1880
+ name: scheduleName,
1881
+ ifLastIndex
1882
+ });
1883
+ try {
1884
+ await scheduler.schedule({
1885
+ version: 1,
1886
+ kind: "clear",
1887
+ id,
1888
+ contract: name,
1889
+ sessionId,
1890
+ dueAt,
1891
+ ifLastIndex
1892
+ });
1893
+ } catch (error) {
1894
+ throw markSchedulerSendFailure(error);
1895
+ }
1896
+ };
1897
+ return Object.assign(schedule, { clear });
1403
1898
  };
1404
1899
  const toSessionEvent = (id, event) => ({
1405
1900
  id: event.id,
@@ -1459,6 +1954,13 @@ function createServer(options) {
1459
1954
  }
1460
1955
  };
1461
1956
  streamActivities.set(stream, activity);
1957
+ bufferedEvents.set(stream, () => {
1958
+ const event = closed || !inner ? void 0 : takeBuffered(inner);
1959
+ if (!event) return void 0;
1960
+ if (!isSerializedEvent(event)) return toSessionEvent(id, event);
1961
+ event.sessionId = id;
1962
+ return event;
1963
+ });
1462
1964
  return stream;
1463
1965
  } };
1464
1966
  };
@@ -1514,6 +2016,7 @@ function createServer(options) {
1514
2016
  const ns = nsId(id);
1515
2017
  let closed = false;
1516
2018
  let events;
2019
+ let pendingEvent = null;
1517
2020
  let unsubscribe;
1518
2021
  let sleeper = null;
1519
2022
  let wakeQueue = null;
@@ -1655,7 +2158,6 @@ function createServer(options) {
1655
2158
  }
1656
2159
  return null;
1657
2160
  };
1658
- let pendingEvent = null;
1659
2161
  try {
1660
2162
  unsubscribe = api.subscribe?.(ns, enqueue);
1661
2163
  events = store.stream(ns, { startAfter })[Symbol.asyncIterator]();
@@ -1745,18 +2247,144 @@ function createServer(options) {
1745
2247
  }
1746
2248
  };
1747
2249
  streamActivities.set(stream, activity);
2250
+ bufferedEvents.set(stream, () => {
2251
+ if (closed || !events || pendingEvent) return void 0;
2252
+ const event = takeBuffered(events);
2253
+ if (!event) return void 0;
2254
+ if (!isSerializedEvent(event)) return toSessionEvent(id, event);
2255
+ event.sessionId = id;
2256
+ return event;
2257
+ });
1748
2258
  return stream;
1749
2259
  } };
1750
2260
  };
1751
- const makeSession = (id, append, trigger) => ({
2261
+ const reclaimAll = async (store, ns) => {
2262
+ for (let stalls = 0;;) {
2263
+ let batch;
2264
+ try {
2265
+ batch = await store.reclaim(ns);
2266
+ } catch (err) {
2267
+ throw asStoreUnavailable(err);
2268
+ }
2269
+ if (batch.done) return;
2270
+ if (batch.deleted > 0) {
2271
+ stalls = 0;
2272
+ continue;
2273
+ }
2274
+ stalls += 1;
2275
+ if (stalls === RECLAIM_STALL_LIMIT) throw new A2Error("STORE_UNAVAILABLE", `deleting the cleared history of session '${stripNs(ns)}' made no progress`);
2276
+ await defaultSleep(RECLAIM_STALL_MS).promise;
2277
+ }
2278
+ };
2279
+ const clearSession = async (sessionId, clearOptions, attempt) => {
2280
+ const ifLastIndex = clearOptions?.ifLastIndex;
2281
+ if (ifLastIndex !== void 0) assertClearIndex(ifLastIndex);
2282
+ const store = await resolveStore();
2283
+ if (!store.clear || !store.reclaim) throw new TypeError("session.clear requires a store that supports clearing");
2284
+ const ns = nsId(sessionId);
2285
+ let result;
2286
+ try {
2287
+ result = await store.clear({
2288
+ sessionId: ns,
2289
+ ...ifLastIndex === void 0 ? {} : { ifLastIndex },
2290
+ ...attempt ? { cause: attempt.cause } : {}
2291
+ });
2292
+ } catch (err) {
2293
+ throw asStoreUnavailable(err);
2294
+ }
2295
+ if (result.cleared) {
2296
+ const cachePrefix = stateCacheKey(sessionId, "");
2297
+ stateCache.deleteWhere((key) => key.startsWith(cachePrefix));
2298
+ }
2299
+ const deletion = reclaimAll(store, ns);
2300
+ if (attempt) {
2301
+ if (attempt.cause.index <= result.clearIndex) attempt.end();
2302
+ const kept = deletion.catch(() => void 0);
2303
+ track(kept);
2304
+ platformWaitUntil(kept);
2305
+ }
2306
+ await deletion;
2307
+ return {
2308
+ cleared: result.cleared,
2309
+ index: result.index
2310
+ };
2311
+ };
2312
+ const redrivingStore = async (method) => {
2313
+ const store = await resolveStore();
2314
+ if (!store.deadLetters || !store.redrive) throw new TypeError(`session.${method} requires a store that supports redriving dead letters`);
2315
+ return store;
2316
+ };
2317
+ const listDeadLetters = async (sessionId) => {
2318
+ const store = await redrivingStore("deadLetters");
2319
+ let rows;
2320
+ try {
2321
+ rows = await store.deadLetters(nsId(sessionId));
2322
+ } catch (err) {
2323
+ throw asStoreUnavailable(err);
2324
+ }
2325
+ return rows.flatMap((row) => row.failedAt === null ? [] : [{
2326
+ ...toPublic(row),
2327
+ lane: row.lane,
2328
+ attemptCount: row.attemptCount,
2329
+ failureCount: row.failureCount,
2330
+ lastError: row.lastError,
2331
+ failedAt: row.failedAt
2332
+ }]);
2333
+ };
2334
+ const redriveEvent = async (sessionId, index, redriveOptions) => {
2335
+ assertRedriveIndex(index);
2336
+ const action = redriveOptions?.action;
2337
+ if (action !== "retry" && action !== "skip") throw new TypeError("redrive requires action 'retry' or 'skip'");
2338
+ return telemetry.span("a2.redrive", {
2339
+ "a2.contract": name,
2340
+ "a2.session_id": sessionId,
2341
+ "a2.redrive.index": index,
2342
+ "a2.redrive.action": action
2343
+ }, async (span) => {
2344
+ const store = await redrivingStore("redrive");
2345
+ let result;
2346
+ try {
2347
+ result = await store.redrive({
2348
+ sessionId: nsId(sessionId),
2349
+ index,
2350
+ action
2351
+ });
2352
+ } catch (err) {
2353
+ throw asStoreUnavailable(err);
2354
+ }
2355
+ span.setAttribute("a2.redrive.redriven", result.redriven);
2356
+ if (result.hasPending) {
2357
+ const watchdogAt = scheduler ? schedulerSlot(nextClaimWindow().watchdogAtMs) : void 0;
2358
+ const arm = watchdogAt === void 0 ? null : startSchedulerArm(sessionId, watchdogAt);
2359
+ scheduleDrain(sessionId, watchdogAt);
2360
+ if (arm) try {
2361
+ await joinSchedulerArm(arm);
2362
+ } catch (err) {
2363
+ span.setAttribute("a2.redrive.armed", false);
2364
+ span.recordError(err);
2365
+ }
2366
+ }
2367
+ return { redriven: result.redriven };
2368
+ });
2369
+ };
2370
+ const makeSession = (id, append, trigger, attempt) => ({
1752
2371
  id,
1753
2372
  append,
1754
2373
  schedule: makeSchedule(id, trigger),
1755
- history: async (bounds) => (await readHistory(id, bounds)).map(toPublic),
1756
- state: (reducer, stateOptions) => {
2374
+ clear: (clearOptions) => clearSession(id, clearOptions, attempt),
2375
+ history: async (bounds) => (await readHistory(id, bounds)).filter((row) => !isSessionCleared(row)).map(toPublic),
2376
+ state: (reducer = serverContract.reducer, stateOptions) => {
2377
+ if (!reducer) throw new A2Error("INVALID_PAYLOAD", "contract has no public reducer");
1757
2378
  const through = stateOptions?.through ?? trigger?.index;
1758
2379
  if (through !== void 0 && through !== "latest") assertStateIndex(through);
1759
- return readState(id, reducer, through === "latest" ? void 0 : through, trigger && through !== "latest" ? trigger.index : void 0);
2380
+ const bounds = {};
2381
+ if (through !== void 0 && through !== "latest") {
2382
+ bounds.throughIndex = through;
2383
+ if (trigger) bounds.pinEventIndex = trigger.index;
2384
+ if (through === trigger?.index && !attempt?.signal.aborted) bounds.throughEventId = trigger.id;
2385
+ }
2386
+ if (trigger) bounds.triggerIndex = trigger.index;
2387
+ return readState(id, reducer, bounds);
1760
2388
  },
1761
2389
  stream: (opts) => {
1762
2390
  const startAfter = opts?.startAfter ?? 0;
@@ -1768,7 +2396,7 @@ function createServer(options) {
1768
2396
  presence: () => readPresenceMap(id)
1769
2397
  } : {}
1770
2398
  });
1771
- const makeCtx = (trigger, attempt, signal) => {
2399
+ const makeCtx = (trigger, attempt, signal, endAttempt) => {
1772
2400
  const publicSessionId = stripNs(trigger.sessionId);
1773
2401
  const append = async (appendName, ...events) => {
1774
2402
  assertAppendName(appendName);
@@ -1786,12 +2414,21 @@ function createServer(options) {
1786
2414
  attempt
1787
2415
  }, generatedIds);
1788
2416
  };
1789
- return {
2417
+ const ctx = {
1790
2418
  event: toPublic(trigger),
1791
2419
  attempt,
1792
- session: makeSession(publicSessionId, append, trigger),
2420
+ session: makeSession(publicSessionId, append, trigger, {
2421
+ cause: {
2422
+ index: trigger.index,
2423
+ attempt
2424
+ },
2425
+ end: endAttempt,
2426
+ signal
2427
+ }),
1793
2428
  signal
1794
2429
  };
2430
+ if (attempt > 1 && trigger.lastFailedAttempt === attempt - 1) failedPreviousAttempt.add(ctx);
2431
+ return ctx;
1795
2432
  };
1796
2433
  const abortHubs = /* @__PURE__ */ new Map();
1797
2434
  const createAbortHub = (store, ns, startAfter) => {
@@ -1988,12 +2625,7 @@ function createServer(options) {
1988
2625
  for (const index of result.superseded) {
1989
2626
  const entry = active.get(index);
1990
2627
  if (!entry) continue;
1991
- entry.supersede.abort(new A2Error("SUPERSEDED_ATTEMPT", `a newer attempt owns event ${index} in session '${sessionId}'`));
1992
- entry.fence({
1993
- status: "superseded",
1994
- opensLane: false,
1995
- appendsEvents: false
1996
- });
2628
+ endSuperseded(entry, `a newer attempt owns event ${index} in session '${sessionId}'`);
1997
2629
  }
1998
2630
  if (window.deadlineCapped) stopRenewing = true;
1999
2631
  scheduleLapse();
@@ -2007,7 +2639,7 @@ function createServer(options) {
2007
2639
  renewal = tracked;
2008
2640
  return tracked;
2009
2641
  };
2010
- const runEvent = async (event, supersedeSignal) => {
2642
+ const runEvent = async (event, supersedeSignal, endAttempt) => {
2011
2643
  const registration = handlers.get(event.type);
2012
2644
  return telemetry.span("a2.event", {
2013
2645
  "a2.contract": name,
@@ -2027,7 +2659,7 @@ function createServer(options) {
2027
2659
  subscription = await subscribeAbort(store, ns, event, registration.abortOn, event.attemptCount);
2028
2660
  handlerSignal = AbortSignal.any([supersedeSignal, subscription.signal]);
2029
2661
  }
2030
- const operation = registration?.handler(makeCtx(event, event.attemptCount, handlerSignal));
2662
+ const operation = registration?.handler(makeCtx(event, event.attemptCount, handlerSignal, endAttempt));
2031
2663
  const result = operation ? await (subscription ? subscription.monitor(operation) : operation) : void 0;
2032
2664
  if (subscription) {
2033
2665
  await subscription.close();
@@ -2044,12 +2676,22 @@ function createServer(options) {
2044
2676
  };
2045
2677
  }
2046
2678
  eventSpan.recordError(err);
2047
- const failure = await store.failAttempt({
2048
- sessionId: ns,
2049
- index: event.index,
2050
- attempt: event.attemptCount,
2051
- error: describeError(err),
2052
- maxFailures: err instanceof NonRetriableError ? 1 : MAX_FAILURES
2679
+ const failure = await telemetry.span("a2.commit", {
2680
+ "a2.contract": name,
2681
+ "a2.session_id": sessionId,
2682
+ "a2.commit.operation": "fail",
2683
+ "a2.commit.index": event.index,
2684
+ "a2.commit.attempt": event.attemptCount
2685
+ }, async (commitSpan) => {
2686
+ const result = await store.failAttempt({
2687
+ sessionId: ns,
2688
+ index: event.index,
2689
+ attempt: event.attemptCount,
2690
+ error: describeError(err),
2691
+ maxFailures: err instanceof NonRetriableError ? 1 : MAX_FAILURES
2692
+ });
2693
+ commitSpan.setAttribute("a2.commit.outcome", result.outcome);
2694
+ return result;
2053
2695
  });
2054
2696
  eventSpan.setAttribute("a2.event.outcome", failure.outcome);
2055
2697
  return {
@@ -2061,11 +2703,21 @@ function createServer(options) {
2061
2703
  await subscription?.close();
2062
2704
  }
2063
2705
  await renewal;
2064
- const completion = await store.completeAttempt({
2065
- sessionId: ns,
2066
- index: event.index,
2067
- attempt: event.attemptCount,
2068
- events: returned
2706
+ const completion = await telemetry.span("a2.commit", {
2707
+ "a2.contract": name,
2708
+ "a2.session_id": sessionId,
2709
+ "a2.commit.operation": "complete",
2710
+ "a2.commit.index": event.index,
2711
+ "a2.commit.attempt": event.attemptCount
2712
+ }, async (commitSpan) => {
2713
+ const result = await store.completeAttempt({
2714
+ sessionId: ns,
2715
+ index: event.index,
2716
+ attempt: event.attemptCount,
2717
+ events: returned
2718
+ });
2719
+ commitSpan.setAttribute("a2.commit.outcome", result.outcome);
2720
+ return result;
2069
2721
  });
2070
2722
  eventSpan.setAttribute("a2.event.outcome", completion.outcome === "completed" ? "processed" : completion.outcome);
2071
2723
  if (completion.outcome === "superseded") return {
@@ -2084,21 +2736,18 @@ function createServer(options) {
2084
2736
  let claimAgain = false;
2085
2737
  const start = (event, expiresAtMs) => {
2086
2738
  const previous = active.get(event.index);
2087
- if (previous) {
2088
- previous.supersede.abort(new A2Error("SUPERSEDED_ATTEMPT", `a newer attempt owns event ${event.index} in session '${sessionId}'`));
2089
- previous.fence({
2090
- status: "superseded",
2091
- opensLane: false,
2092
- appendsEvents: false
2093
- });
2094
- }
2739
+ if (previous) endSuperseded(previous, `a newer attempt owns event ${event.index} in session '${sessionId}'`);
2095
2740
  const supersede = new AbortController();
2096
2741
  let fence;
2097
2742
  const fenced = new Promise((resolve) => {
2098
2743
  fence = resolve;
2099
2744
  });
2745
+ const endAttempt = () => endSuperseded({
2746
+ supersede,
2747
+ fence
2748
+ }, `event ${event.index} in session '${sessionId}' was cleared`);
2100
2749
  let entry;
2101
- const execution = Promise.race([runEvent(event, supersede.signal), fenced]).then((eventOutcome) => {
2750
+ const execution = Promise.race([runEvent(event, supersede.signal, endAttempt), fenced]).then((eventOutcome) => {
2102
2751
  if (active.get(event.index) === entry) active.delete(event.index);
2103
2752
  if (eventOutcome.status === "processed") processed += 1;
2104
2753
  if (eventOutcome.status === "failed") {
@@ -2131,12 +2780,20 @@ function createServer(options) {
2131
2780
  let claim;
2132
2781
  if (shouldClaim) {
2133
2782
  const window = nextClaimWindow();
2134
- claim = await store.claimAvailable({
2135
- sessionId: ns,
2136
- holder,
2137
- ttlMs: window.ttlMs,
2138
- ...window.deadlineCapped ? { expiresAtMs: window.expiresAtMs } : {},
2139
- ...excluded.size === 0 ? {} : { excludeIndexes: [...excluded] }
2783
+ claim = await telemetry.span("a2.claim", {
2784
+ "a2.contract": name,
2785
+ "a2.session_id": sessionId
2786
+ }, async (claimSpan) => {
2787
+ const result = await store.claimAvailable({
2788
+ sessionId: ns,
2789
+ holder,
2790
+ ttlMs: window.ttlMs,
2791
+ ...window.deadlineCapped ? { expiresAtMs: window.expiresAtMs } : {},
2792
+ ...excluded.size === 0 ? {} : { excludeIndexes: [...excluded] }
2793
+ });
2794
+ claimSpan.setAttribute("a2.claim.outcome", result.outcome);
2795
+ claimSpan.setAttribute("a2.claim.count", result.outcome === "claimed" ? result.events.length : 0);
2796
+ return result;
2140
2797
  });
2141
2798
  if (claim.outcome === "claimed") {
2142
2799
  requestWatchdog(window.watchdogAtMs);
@@ -2195,21 +2852,34 @@ function createServer(options) {
2195
2852
  ...schedulerArm ? { schedulerArm } : {}
2196
2853
  };
2197
2854
  });
2198
- const appendExternal = async (sessionId, mode, events, requireDurableInitialArm = false) => {
2199
- return appendCore(sessionId, events, "external", mode, (_rows, watchdogDueAt) => scheduleDrain(sessionId, watchdogDueAt), void 0, void 0, requireDurableInitialArm);
2855
+ const appendExternal = async (sessionId, mode, events, requireDurableInitialArm = false, originIndex) => {
2856
+ return appendCore(sessionId, events, "external", mode, (_rows, watchdogDueAt) => scheduleDrain(sessionId, watchdogDueAt), void 0, void 0, requireDurableInitialArm, originIndex);
2200
2857
  };
2201
2858
  const session = (id) => {
2202
2859
  assertSessionId(id);
2203
2860
  const appendInline = async (...events) => appendExternal(id, "inline", events);
2204
2861
  const appendDispatch = async (...events) => appendExternal(id, "dispatch", events);
2205
2862
  const append = Object.assign(appendInline, { dispatch: appendDispatch });
2206
- return makeSession(id, append, null);
2863
+ return Object.assign(makeSession(id, append, null), {
2864
+ deadLetters: () => listDeadLetters(id),
2865
+ redrive: (index, redriveOptions) => redriveEvent(id, index, redriveOptions)
2866
+ });
2867
+ };
2868
+ const historyPage = async (sessionId, bounds) => {
2869
+ assertSessionId(sessionId);
2870
+ const rows = await readHistory(sessionId, bounds);
2871
+ const marker = rows[0] && isSessionCleared(rows[0]) ? rows[0] : void 0;
2872
+ return {
2873
+ events: (marker ? rows.slice(1) : rows).map(toPublic),
2874
+ ...marker ? { clearIndex: marker.index } : {}
2875
+ };
2207
2876
  };
2208
2877
  const self = {
2209
2878
  contract: serverContract,
2210
2879
  fetch: createServerFetch({
2211
2880
  contract: serverContract,
2212
- session
2881
+ session,
2882
+ historyPage
2213
2883
  }),
2214
2884
  session,
2215
2885
  async drain(sessionId) {
@@ -2226,10 +2896,21 @@ function createServer(options) {
2226
2896
  assertSessionId(sessionId);
2227
2897
  return drainSession(sessionId, opts?.watchdogDueAt);
2228
2898
  },
2229
- schedulerAppend: async (sessionId, events) => {
2899
+ schedulerAppend: async (sessionId, events, originIndex) => {
2230
2900
  if (!scheduler) throw new TypeError("scheduled append delivery requires a configured scheduler adapter");
2231
2901
  assertSessionId(sessionId);
2232
- await appendExternal(sessionId, "inline", [...events], true);
2902
+ try {
2903
+ await appendExternal(sessionId, "inline", [...events], true, originIndex);
2904
+ } catch (error) {
2905
+ if (originIndex !== void 0 && error instanceof A2Error && error.code === "SESSION_CLEARED") return;
2906
+ throw error;
2907
+ }
2908
+ },
2909
+ historyWithMarkers: async (sessionId, gte) => (await readHistory(sessionId, { gte })).map(toPublic),
2910
+ schedulerClear: async (sessionId, ifLastIndex) => {
2911
+ assertSessionId(sessionId);
2912
+ assertClearIndex(ifLastIndex);
2913
+ await clearSession(sessionId, { ifLastIndex }, void 0);
2233
2914
  }
2234
2915
  });
2235
2916
  return self;
@@ -2247,6 +2928,12 @@ function assertStreamIndex(value) {
2247
2928
  function assertStateIndex(value) {
2248
2929
  if (!Number.isSafeInteger(value) || value < 0) throw new TypeError("state.through must be a non-negative safe integer");
2249
2930
  }
2931
+ function assertRedriveIndex(value) {
2932
+ if (!Number.isSafeInteger(value) || value < 1) throw new TypeError("redrive requires a positive safe-integer index");
2933
+ }
2934
+ function assertClearIndex(value) {
2935
+ if (!Number.isSafeInteger(value) || value < 0) throw new TypeError("ifLastIndex must be a non-negative safe integer");
2936
+ }
2250
2937
  function assertAppendName(name) {
2251
2938
  if (typeof name !== "string" || name.length === 0) throw new TypeError("handler append name must be a non-empty string");
2252
2939
  }
@@ -2328,14 +3015,12 @@ function isJsonValue(value, ancestors) {
2328
3015
  ancestors.delete(value);
2329
3016
  }
2330
3017
  }
2331
- function cloneInitial(initial) {
2332
- try {
2333
- return structuredClone(initial);
2334
- } catch {
2335
- return initial;
2336
- }
2337
- }
3018
+ const measuredBytes = (reducer, state, index, snapshots) => JSON.stringify(snapshots.find((snapshot) => snapshot.index === index)?.state ?? encodeState({
3019
+ reducer,
3020
+ state
3021
+ }))?.length ?? 0;
3022
+ const stateCacheKey = (sessionId, reducerName) => `${sessionId}\u0000${reducerName}`;
2338
3023
  //#endregion
2339
- export { sseResponse as a, setServerFetchHooks as i, deliverSchedulerAppend as n, parsePresenceSibling as o, handler as r, createServer as t };
3024
+ export { CONSOLE_TELEMETRY as a, parsePresenceSibling as c, handler as i, deliverSchedulerAppend as n, setServerFetchHooks as o, deliverSchedulerClear as r, sseResponse as s, createServer as t };
2340
3025
 
2341
- //# sourceMappingURL=server-BD5ckxZb.js.map
3026
+ //# sourceMappingURL=server-Dw_k_7kO.js.map