experimental-a2 0.20.0 → 0.22.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 (309) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +1 -1
  3. package/dist/{actor-C7b-1arn.d.ts → actor-CqibLgjQ.d.ts} +15 -7
  4. package/dist/actor-CqibLgjQ.d.ts.map +1 -0
  5. package/dist/actor-client.d.ts +1 -1
  6. package/dist/actor-client.js +2 -2
  7. package/dist/actor-react.d.ts +3 -3
  8. package/dist/actor-react.js +5 -5
  9. package/dist/{actor-shared-t7w8xIKP.js → actor-shared-DFET5q16.js} +3 -3
  10. package/dist/actor-shared-DFET5q16.js.map +1 -0
  11. package/dist/actor.d.ts +1 -1
  12. package/dist/actor.js +54 -43
  13. package/dist/actor.js.map +1 -1
  14. package/dist/{ai-Cz3hqVhk.d.ts → ai-DAu77CDr.d.ts} +8 -8
  15. package/dist/{ai-Cz3hqVhk.d.ts.map → ai-DAu77CDr.d.ts.map} +1 -1
  16. package/dist/{ai-mMiWul1T.js → ai-EEfKyc-z.js} +9 -9
  17. package/dist/ai-EEfKyc-z.js.map +1 -0
  18. package/dist/ai-client.d.ts +2 -2
  19. package/dist/ai-client.js +2 -2
  20. package/dist/ai-fx.d.ts +4 -2
  21. package/dist/ai-fx.d.ts.map +1 -1
  22. package/dist/ai-fx.js +214 -67
  23. package/dist/ai-fx.js.map +1 -1
  24. package/dist/{ai-harness-CV6rXbB3.d.ts → ai-harness-DTjkAr2T.d.ts} +2 -2
  25. package/dist/{ai-harness-CV6rXbB3.d.ts.map → ai-harness-DTjkAr2T.d.ts.map} +1 -1
  26. package/dist/{ai-harness-Clbg5ei7.js → ai-harness-dQeJaR1t.js} +7 -3
  27. package/dist/ai-harness-dQeJaR1t.js.map +1 -0
  28. package/dist/ai-pi.d.ts +1 -1
  29. package/dist/ai-pi.js +3 -3
  30. package/dist/ai-pi.js.map +1 -1
  31. package/dist/ai-react.d.ts +5 -5
  32. package/dist/ai-react.js +2 -2
  33. package/dist/ai-server.d.ts +14 -17
  34. package/dist/ai-server.d.ts.map +1 -1
  35. package/dist/ai-server.js +86 -73
  36. package/dist/ai-server.js.map +1 -1
  37. package/dist/ai.d.ts +2 -2
  38. package/dist/ai.js +2 -2
  39. package/dist/{client-BhHLoNKF.js → client-BQS4i_V9.js} +29 -6
  40. package/dist/client-BQS4i_V9.js.map +1 -0
  41. package/dist/{client-h_DKFAjj.d.ts → client-CUMbS_38.d.ts} +3 -3
  42. package/dist/{client-h_DKFAjj.d.ts.map → client-CUMbS_38.d.ts.map} +1 -1
  43. package/dist/client.d.ts +3 -3
  44. package/dist/client.js +1 -1
  45. package/dist/{contract-BYKASlCV.js → contract-CWukt01d.js} +2 -2
  46. package/dist/contract-CWukt01d.js.map +1 -0
  47. package/dist/index.d.ts +5 -5
  48. package/dist/index.js +3 -3
  49. package/dist/{initial-snapshot-Bx-c0pDP.d.ts → initial-snapshot-D3Lve43o.d.ts} +4 -4
  50. package/dist/initial-snapshot-D3Lve43o.d.ts.map +1 -0
  51. package/dist/{internal-CscSY7K_.js → internal-CvsrprnU.js} +40 -16
  52. package/dist/internal-CvsrprnU.js.map +1 -0
  53. package/dist/otel.d.ts +1 -1
  54. package/dist/otel.js +1 -1
  55. package/dist/otel.js.map +1 -1
  56. package/dist/{react-BBXgHWFc.d.ts → react-BoHvC3v9.d.ts} +4 -4
  57. package/dist/{react-BBXgHWFc.d.ts.map → react-BoHvC3v9.d.ts.map} +1 -1
  58. package/dist/react.d.ts +1 -1
  59. package/dist/react.js +1 -1
  60. package/dist/{reducer-CqA59Q6U.d.ts → reducer-CYPHsSmd.d.ts} +2 -2
  61. package/dist/{reducer-CqA59Q6U.d.ts.map → reducer-CYPHsSmd.d.ts.map} +1 -1
  62. package/dist/{reducer-BAXc8JyF.js → reducer-CjmvZSAb.js} +2 -2
  63. package/dist/{reducer-BAXc8JyF.js.map → reducer-CjmvZSAb.js.map} +1 -1
  64. package/dist/runtime-consumer-CIeL33wj.js +12 -0
  65. package/dist/runtime-consumer-CIeL33wj.js.map +1 -0
  66. package/dist/scheduler-qstash.d.ts +2 -2
  67. package/dist/scheduler-qstash.js +3 -3
  68. package/dist/scheduler-task-X1SToDjP.js.map +1 -1
  69. package/dist/scheduler-vercel.d.ts +2 -2
  70. package/dist/scheduler-vercel.js +3 -3
  71. package/dist/scheduler-vercel.js.map +1 -1
  72. package/dist/{server-qyb5aJJG.js → server-DtFrWxi3.js} +412 -116
  73. package/dist/server-DtFrWxi3.js.map +1 -0
  74. package/dist/{server-DYAATFuT.d.ts → server-qgez0Xny.d.ts} +108 -41
  75. package/dist/server-qgez0Xny.d.ts.map +1 -0
  76. package/dist/server.browser.js +1 -1
  77. package/dist/server.browser.js.map +1 -1
  78. package/dist/server.d.ts +4 -4
  79. package/dist/server.js +2 -2
  80. package/dist/{session-clear-C-QwTXV1.d.ts → session-clear-D1PFe7xl.d.ts} +2 -2
  81. package/dist/session-clear-D1PFe7xl.d.ts.map +1 -0
  82. package/dist/{session-clear-DGI4jTQ5.js → session-clear-wHd5GCpZ.js} +5 -4
  83. package/dist/session-clear-wHd5GCpZ.js.map +1 -0
  84. package/dist/store-D0MhBvg6.d.ts +532 -0
  85. package/dist/store-D0MhBvg6.d.ts.map +1 -0
  86. package/dist/store-N8PXxDAS.js.map +1 -1
  87. package/dist/store-codec-DwrWlgQx.js +65 -0
  88. package/dist/store-codec-DwrWlgQx.js.map +1 -0
  89. package/dist/store-memory.d.ts +1 -1
  90. package/dist/store-memory.d.ts.map +1 -1
  91. package/dist/store-memory.js +406 -225
  92. package/dist/store-memory.js.map +1 -1
  93. package/dist/{store-polling-7WJV0YWP.js → store-polling-BZt2Rf3D.js} +2 -2
  94. package/dist/{store-polling-7WJV0YWP.js.map → store-polling-BZt2Rf3D.js.map} +1 -1
  95. package/dist/store-postgres.d.ts +1 -1
  96. package/dist/store-postgres.d.ts.map +1 -1
  97. package/dist/store-postgres.js +1598 -587
  98. package/dist/store-postgres.js.map +1 -1
  99. package/dist/store-presence-polling-JuAy2Ck2.js +111 -0
  100. package/dist/store-presence-polling-JuAy2Ck2.js.map +1 -0
  101. package/dist/store-redis-http.d.ts +1 -1
  102. package/dist/store-redis-http.js +4 -4
  103. package/dist/store-redis-notify-CsJw_hfD.js +3060 -0
  104. package/dist/store-redis-notify-CsJw_hfD.js.map +1 -0
  105. package/dist/store-redis.d.ts +1 -1
  106. package/dist/store-redis.d.ts.map +1 -1
  107. package/dist/store-redis.js +4 -5
  108. package/dist/store-redis.js.map +1 -1
  109. package/dist/store-sqlite.d.ts +1 -1
  110. package/dist/store-sqlite.d.ts.map +1 -1
  111. package/dist/store-sqlite.js +527 -299
  112. package/dist/store-sqlite.js.map +1 -1
  113. package/dist/stream-buffer-DrTyiIuo.js +57 -0
  114. package/dist/stream-buffer-DrTyiIuo.js.map +1 -0
  115. package/dist/{telemetry-Cyfqo6Qt.d.ts → telemetry-CWwsh-Gx.d.ts} +3 -3
  116. package/dist/telemetry-CWwsh-Gx.d.ts.map +1 -0
  117. package/dist/{wire-BKxH1vbP.js → wire-CV-Z7wz-.js} +6 -5
  118. package/dist/wire-CV-Z7wz-.js.map +1 -0
  119. package/docs/01-quickstart.mdx +21 -15
  120. package/docs/02-examples.mdx +1 -1
  121. package/docs/actors/01-introduction.mdx +13 -2
  122. package/docs/actors/02-concurrency.mdx +1 -1
  123. package/docs/agents/01-introduction.mdx +1 -1
  124. package/docs/agents/02-tools.mdx +12 -11
  125. package/docs/agents/03-conversations.mdx +36 -25
  126. package/docs/agents/04-harnesses.mdx +14 -7
  127. package/docs/client/03-presence.mdx +18 -14
  128. package/docs/contract/01-events.mdx +2 -2
  129. package/docs/contract/02-state.mdx +18 -14
  130. package/docs/contract/03-sessions.mdx +32 -25
  131. package/docs/index.mdx +29 -24
  132. package/docs/reference/01-core.mdx +7 -6
  133. package/docs/reference/02-server.mdx +250 -191
  134. package/docs/reference/04-ai.mdx +33 -23
  135. package/docs/reference/05-telemetry.mdx +27 -13
  136. package/docs/reference/06-errors.mdx +25 -19
  137. package/docs/reference/meta.ts +1 -1
  138. package/docs/schedulers/01-overview.mdx +8 -8
  139. package/docs/schedulers/02-vercel-queues.mdx +2 -2
  140. package/docs/schedulers/03-qstash.mdx +4 -4
  141. package/docs/server/01-consumers.mdx +418 -0
  142. package/docs/server/02-timers.mdx +31 -21
  143. package/docs/server/03-cancellation.mdx +52 -44
  144. package/docs/server/04-routes.mdx +4 -1
  145. package/docs/server/05-sql-projections.mdx +391 -0
  146. package/docs/server/06-guarantees.mdx +61 -51
  147. package/docs/stores/01-overview.mdx +3 -3
  148. package/docs/stores/02-redis.mdx +24 -10
  149. package/docs/stores/03-redis-http.mdx +8 -6
  150. package/docs/stores/04-postgres.mdx +65 -14
  151. package/docs/stores/05-sqlite.mdx +5 -3
  152. package/docs/upgrades/01-overview.mdx +31 -0
  153. package/docs/upgrades/78-0-22.mdx +115 -0
  154. package/docs/upgrades/79-0-21.mdx +165 -0
  155. package/docs/upgrades/80-0-20.mdx +43 -0
  156. package/docs/upgrades/81-0-19.mdx +33 -0
  157. package/docs/upgrades/82-0-18.mdx +33 -0
  158. package/docs/upgrades/83-0-17.mdx +69 -0
  159. package/docs/upgrades/meta.ts +3 -0
  160. package/examples/playground/DEPLOY.md +39 -7
  161. package/examples/playground/app/a0/chat.test.tsx +2 -2
  162. package/examples/playground/app/a0/sandbox-log.ts +17 -15
  163. package/examples/playground/app/a0/sandbox.ts +58 -13
  164. package/examples/playground/app/a0/server.ts +4 -4
  165. package/examples/playground/app/a2slack/a2slack.css +1067 -0
  166. package/examples/playground/app/a2slack/c/[channel]/page.tsx +20 -0
  167. package/examples/playground/app/a2slack/consumers/agent.ts +63 -0
  168. package/examples/playground/app/a2slack/consumers/feed.ts +164 -0
  169. package/examples/playground/app/a2slack/consumers/notify.ts +116 -0
  170. package/examples/playground/app/a2slack/consumers/search.ts +37 -0
  171. package/examples/playground/app/a2slack/consumers/triage.ts +31 -0
  172. package/examples/playground/app/a2slack/consumers/welcome.ts +25 -0
  173. package/examples/playground/app/a2slack/db/a2.ts +209 -0
  174. package/examples/playground/app/a2slack/db/index.ts +73 -0
  175. package/examples/playground/app/a2slack/db/migrations/0000_extensions.sql +4 -0
  176. package/examples/playground/app/a2slack/db/migrations/0001_workspace.sql +74 -0
  177. package/examples/playground/app/a2slack/db/migrations/0002_a2.sql +215 -0
  178. package/examples/playground/app/a2slack/db/migrations/0003_a2_batch.sql +67 -0
  179. package/examples/playground/app/a2slack/db/migrations/0004_a2_session_event_ids.sql +2 -0
  180. package/examples/playground/app/a2slack/db/migrations/meta/0000_snapshot.json +18 -0
  181. package/examples/playground/app/a2slack/db/migrations/meta/0001_snapshot.json +500 -0
  182. package/examples/playground/app/a2slack/db/migrations/meta/0002_snapshot.json +1159 -0
  183. package/examples/playground/app/a2slack/db/migrations/meta/0003_snapshot.json +1159 -0
  184. package/examples/playground/app/a2slack/db/migrations/meta/0004_snapshot.json +1165 -0
  185. package/examples/playground/app/a2slack/db/migrations/meta/_journal.json +41 -0
  186. package/examples/playground/app/a2slack/db/queries.ts +263 -0
  187. package/examples/playground/app/a2slack/db/schema.ts +145 -0
  188. package/examples/playground/app/a2slack/drills/a2slack.drill.ts +708 -0
  189. package/examples/playground/app/a2slack/drills/harness.ts +244 -0
  190. package/examples/playground/app/a2slack/events/route.ts +36 -0
  191. package/examples/playground/app/a2slack/identity.ts +44 -0
  192. package/examples/playground/app/a2slack/inbox/route.ts +8 -0
  193. package/examples/playground/app/a2slack/lag/data/route.ts +8 -0
  194. package/examples/playground/app/a2slack/lag/page.tsx +101 -0
  195. package/examples/playground/app/a2slack/lag.ts +73 -0
  196. package/examples/playground/app/a2slack/layout.tsx +43 -0
  197. package/examples/playground/app/a2slack/messages/route.ts +17 -0
  198. package/examples/playground/app/a2slack/model.test.ts +208 -0
  199. package/examples/playground/app/a2slack/model.ts +369 -0
  200. package/examples/playground/app/a2slack/models.test.ts +72 -0
  201. package/examples/playground/app/a2slack/page.tsx +10 -0
  202. package/examples/playground/app/a2slack/scheduler/route.ts +5 -0
  203. package/examples/playground/app/a2slack/search/route.ts +40 -0
  204. package/examples/playground/app/a2slack/search-index.ts +108 -0
  205. package/examples/playground/app/a2slack/server.test.ts +315 -0
  206. package/examples/playground/app/a2slack/server.ts +55 -0
  207. package/examples/playground/app/a2slack/sessions.ts +50 -0
  208. package/examples/playground/app/a2slack/thread/route.ts +8 -0
  209. package/examples/playground/app/a2slack/ui/ChannelView.tsx +87 -0
  210. package/examples/playground/app/a2slack/ui/Composer.tsx +117 -0
  211. package/examples/playground/app/a2slack/ui/DotsShadow.tsx +42 -0
  212. package/examples/playground/app/a2slack/ui/Feed.tsx +157 -0
  213. package/examples/playground/app/a2slack/ui/InboxContext.tsx +63 -0
  214. package/examples/playground/app/a2slack/ui/InboxView.tsx +82 -0
  215. package/examples/playground/app/a2slack/ui/Menu.tsx +128 -0
  216. package/examples/playground/app/a2slack/ui/MessageRow.tsx +314 -0
  217. package/examples/playground/app/a2slack/ui/NameGate.tsx +35 -0
  218. package/examples/playground/app/a2slack/ui/SearchContext.tsx +146 -0
  219. package/examples/playground/app/a2slack/ui/SearchResults.tsx +97 -0
  220. package/examples/playground/app/a2slack/ui/Shell.tsx +34 -0
  221. package/examples/playground/app/a2slack/ui/Sidebar.tsx +199 -0
  222. package/examples/playground/app/a2slack/ui/ThreadPanel.tsx +134 -0
  223. package/examples/playground/app/a2slack/ui/useInbox.ts +71 -0
  224. package/examples/playground/app/agent/server.ts +3 -3
  225. package/examples/playground/app/chat/[chatId]/chat-client.tsx +1 -1
  226. package/examples/playground/app/chat/server.ts +45 -35
  227. package/examples/playground/app/components/demo-icons.tsx +7 -0
  228. package/examples/playground/app/components/demos.tsx +7 -0
  229. package/examples/playground/app/orders/[orderId]/order-client.tsx +2 -2
  230. package/examples/playground/app/orders/page.tsx +1 -1
  231. package/examples/playground/app/orders/server.ts +49 -30
  232. package/examples/playground/app/recovery/[recoveryId]/recovery-client.tsx +14 -15
  233. package/examples/playground/app/recovery/page.tsx +4 -4
  234. package/examples/playground/app/recovery/server.ts +23 -32
  235. package/examples/playground/drizzle.config.ts +14 -0
  236. package/examples/playground/lib/store.ts +1 -1
  237. package/examples/playground/next.config.ts +6 -1
  238. package/examples/playground/package.json +12 -1
  239. package/examples/playground/scripts/migrate.mjs +31 -0
  240. package/examples/playground/vercel.json +9 -0
  241. package/examples/playground/vitest.drills.config.ts +32 -0
  242. package/package.json +5 -2
  243. package/sql/postgres/drizzle-schema.ts +209 -0
  244. package/sql/postgres/schema.sql +227 -67
  245. package/src/actor-shared.ts +10 -2
  246. package/src/actor.ts +101 -71
  247. package/src/ai-agent.ts +162 -111
  248. package/src/ai-control-server.ts +8 -8
  249. package/src/ai-fx.ts +324 -82
  250. package/src/ai-harness.ts +17 -2
  251. package/src/ai-pi.ts +2 -2
  252. package/src/ai-server.ts +2 -2
  253. package/src/ai.ts +7 -7
  254. package/src/client.ts +19 -2
  255. package/src/contract.ts +1 -1
  256. package/src/deterministic-id.ts +27 -4
  257. package/src/errors.ts +8 -5
  258. package/src/idempotent-replay.ts +0 -1
  259. package/src/index.ts +1 -1
  260. package/src/internal.ts +57 -16
  261. package/src/otel.ts +1 -1
  262. package/src/postgres-schema.ts +98 -57
  263. package/src/presence-recovery.ts +18 -11
  264. package/src/presence-tombstones.ts +52 -0
  265. package/src/push-envelope.ts +20 -0
  266. package/src/runtime-consumer.ts +10 -0
  267. package/src/scheduler-task.ts +2 -2
  268. package/src/scheduler-vercel.ts +1 -1
  269. package/src/server.browser.ts +1 -1
  270. package/src/server.ts +819 -218
  271. package/src/session-socket.ts +1 -1
  272. package/src/store-codec.ts +66 -0
  273. package/src/store-memory.ts +604 -286
  274. package/src/store-postgres.ts +2006 -857
  275. package/src/store-presence-polling.ts +59 -34
  276. package/src/store-redis-core.ts +1961 -1333
  277. package/src/store-redis-notify.ts +76 -26
  278. package/src/store-redis.ts +2 -3
  279. package/src/store-sqlite.ts +915 -516
  280. package/src/store.ts +241 -102
  281. package/src/telemetry.ts +2 -1
  282. package/src/wire.ts +1 -0
  283. package/dist/actor-C7b-1arn.d.ts.map +0 -1
  284. package/dist/actor-shared-t7w8xIKP.js.map +0 -1
  285. package/dist/ai-harness-Clbg5ei7.js.map +0 -1
  286. package/dist/ai-mMiWul1T.js.map +0 -1
  287. package/dist/client-BhHLoNKF.js.map +0 -1
  288. package/dist/contract-BYKASlCV.js.map +0 -1
  289. package/dist/idempotent-replay-JdgMQRRm.js +0 -21
  290. package/dist/idempotent-replay-JdgMQRRm.js.map +0 -1
  291. package/dist/initial-snapshot-Bx-c0pDP.d.ts.map +0 -1
  292. package/dist/internal-CscSY7K_.js.map +0 -1
  293. package/dist/server-DYAATFuT.d.ts.map +0 -1
  294. package/dist/server-qyb5aJJG.js.map +0 -1
  295. package/dist/session-clear-C-QwTXV1.d.ts.map +0 -1
  296. package/dist/session-clear-DGI4jTQ5.js.map +0 -1
  297. package/dist/store-CyLUy86v.d.ts +0 -404
  298. package/dist/store-CyLUy86v.d.ts.map +0 -1
  299. package/dist/store-codec-DTG0Ftek.js +0 -10
  300. package/dist/store-codec-DTG0Ftek.js.map +0 -1
  301. package/dist/store-presence-polling-B1ixeqIS.js +0 -94
  302. package/dist/store-presence-polling-B1ixeqIS.js.map +0 -1
  303. package/dist/store-redis-notify-CM0AfLY0.js +0 -2544
  304. package/dist/store-redis-notify-CM0AfLY0.js.map +0 -1
  305. package/dist/stream-buffer-Cx7AAPGq.js +0 -23
  306. package/dist/stream-buffer-Cx7AAPGq.js.map +0 -1
  307. package/dist/telemetry-Cyfqo6Qt.d.ts.map +0 -1
  308. package/dist/wire-BKxH1vbP.js.map +0 -1
  309. package/docs/server/01-handlers.mdx +0 -227
@@ -29,7 +29,14 @@ const RESERVED_PARTICIPANT_IDS = /* @__PURE__ */ new Set([
29
29
  ]);
30
30
  const serverInternals = /* @__PURE__ */ new WeakMap();
31
31
  /**
32
- * Handler contexts whose previous attempt ended in a failure the handler
32
+ * Consumers A2's own runtimes define: named under `a2.`, with child ids that
33
+ * leave the name out, since their types are disjoint. Defined through
34
+ * `runtimeConsumer` (runtime-consumer.ts), which the server module installs.
35
+ */
36
+ const runtimeConsumers = /* @__PURE__ */ new WeakSet();
37
+ const runtimeConsumerFactory = {};
38
+ /**
39
+ * Consumer contexts whose previous attempt ended in a failure the consumer
33
40
  * threw, as opposed to a claim that lapsed (a crash, a timeout, a frozen
34
41
  * process). Only the attempt's own outcome is known, not how far it got.
35
42
  */
@@ -135,6 +142,18 @@ const DRAIN_TIMINGS = {
135
142
  failureRetryMaxMs: 3e4
136
143
  };
137
144
  /**
145
+ * How many kernels a harness keeps, per agent, for the next step of a session
146
+ * in its process. Mutable only as a white-box test seam; production always
147
+ * uses the default.
148
+ */
149
+ const HARNESS_PARKING = { maxKernels: 32 };
150
+ /**
151
+ * Per server: called when a drain of a session ends with nothing left to run
152
+ * for it in this process, so the layer above can let go of what it kept for
153
+ * that session's next step.
154
+ */
155
+ const drainSettledHooks = /* @__PURE__ */ new WeakMap();
156
+ /**
138
157
  * Stream liveness knobs — the server-side SSE heartbeat and the
139
158
  * client-side stall watchdog. Paired by design: the client declares a
140
159
  * connection dead after roughly two missed heartbeats. Mutable only as
@@ -152,13 +171,19 @@ const POLL_TIMINGS = {
152
171
  idleCeilingMs: 2e3
153
172
  };
154
173
  /**
155
- * Safety-read cadence for the notify-driven redis live stream. A
156
- * parked feed re-reads the store at this interval even without a
157
- * pub/sub wake-up, so a lost notification (subscriber reconnect gap,
158
- * dropped PUBLISH) costs latency, never an event. Mutable only as a
159
- * white-box test seam.
174
+ * Timings for the notify-driven redis live stream. A parked feed re-reads
175
+ * the store every `safetyReadMs` even without a pub/sub wake-up, so a lost
176
+ * notification (subscriber reconnect gap, dropped PUBLISH) costs latency,
177
+ * never an event. Writers publish after their scripts, so wake-ups from
178
+ * concurrent writers can arrive out of order: a feed holds a gap for
179
+ * `reorderMs` before catching up with a read, and a write waits at most
180
+ * `publishMs` for its PUBLISH. Mutable only as a white-box test seam.
160
181
  */
161
- const NOTIFY_TIMINGS = { safetyReadMs: 1e4 };
182
+ const NOTIFY_TIMINGS = {
183
+ safetyReadMs: 1e4,
184
+ reorderMs: 50,
185
+ publishMs: 1e3
186
+ };
162
187
  /**
163
188
  * Bounds for each server instance's in-memory reducer-state cache. Sizes are
164
189
  * encoded JSON lengths, re-measured every `measureEvery` folded events, so
@@ -203,15 +228,14 @@ const SOCKET_TIMINGS = {
203
228
  disconnectWaterMarkBytes: 8388608
204
229
  };
205
230
  /**
206
- * Client presence send cadence — a leading send immediately, then at
207
- * most one merged send per interval (~15Hz). Owned, not configurable
208
- * (decision #36's rule): coupling the cadence to the POST ack tied
209
- * remote cursor smoothness to RTT, and the ack serves no correctness
210
- * purpose on a fire-and-forget LWW plane. Mutable only as a white-box
211
- * test seam.
231
+ * Presence timings. Owned, not configurable (decision #36's rule).
232
+ * Mutable only as a white-box test seam.
212
233
  */
213
- const PRESENCE_TIMINGS = { sendIntervalMs: 66 };
234
+ const PRESENCE_TIMINGS = {
235
+ sendIntervalMs: 66,
236
+ tombstoneMs: 6e4
237
+ };
214
238
  //#endregion
215
- export { nullProtoRecord as _, PRESENCE_TIMINGS as a, STATE_CACHE as c, assertSchedulerTargets as d, classifySchedulerSendFailure as f, markSchedulerSendFailure as g, installAmbientToolScopeStorage as h, POLL_TIMINGS as i, STREAM_TIMINGS as l, failedPreviousAttempt as m, MAX_DATE_MS as n, RESERVED_PARTICIPANT_IDS as o, consumeSchedulerSendFailure as p, NOTIFY_TIMINGS as r, SOCKET_TIMINGS as s, DRAIN_TIMINGS as t, ambientToolScopeStorage as u, serverInternals as v, stateTailReaders as y };
239
+ export { stateTailReaders as C, serverInternals as S, installAmbientToolScopeStorage as _, POLL_TIMINGS as a, runtimeConsumerFactory as b, SOCKET_TIMINGS as c, ambientToolScopeStorage as d, assertSchedulerTargets as f, failedPreviousAttempt as g, drainSettledHooks as h, NOTIFY_TIMINGS as i, STATE_CACHE as l, consumeSchedulerSendFailure as m, HARNESS_PARKING as n, PRESENCE_TIMINGS as o, classifySchedulerSendFailure as p, MAX_DATE_MS as r, RESERVED_PARTICIPANT_IDS as s, DRAIN_TIMINGS as t, STREAM_TIMINGS as u, markSchedulerSendFailure as v, runtimeConsumers as x, nullProtoRecord as y };
216
240
 
217
- //# sourceMappingURL=internal-CscSY7K_.js.map
241
+ //# sourceMappingURL=internal-CvsrprnU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-CvsrprnU.js","names":[],"sources":["../src/internal.ts"],"sourcesContent":["/**\n * Private seams shared by a2 entry points and a2's own tests. Not exported\n * from the package. Nothing here is public API.\n */\n\nimport type { ScheduledEvent } from './scheduler-task.ts'\nimport type { DrainableServer } from './server.ts'\nimport type { Event } from './store.ts'\n\n/** `failed`: a scheduler drain threw after arming its successor. */\nexport type DrainOutcome = 'busy' | 'stalled' | 'settled' | 'failed'\n\n/**\n * A null-prototype record for maps keyed by caller-controlled strings\n * (participant ids, presence field names). On a normal object,\n * assigning to '__proto__' rewrites the prototype instead of creating\n * an own property — the prototype-pollution class. Null-prototype\n * objects still satisfy Record types and JSON.stringify cleanly.\n */\nexport function nullProtoRecord<T extends object>(): T {\n return Object.create(null) as T\n}\n\n/**\n * ECMA-262's maximum Date value (±8.64e15 ms). A presence stamp past\n * it becomes an Invalid Date whose toISOString throws — one hostile\n * stamp would poison every subscriber's snapshot frame — so both\n * seams bound `at` to the representable range.\n */\nexport const MAX_DATE_MS = 8_640_000_000_000_000\n\n/**\n * Participant ids that collide with Object.prototype plumbing.\n * Null-prototype construction already defuses them, but they are\n * rejected at both seams (`parsePushBody`, `setPresence`) so a hostile\n * id never propagates into userland maps either.\n */\nexport const RESERVED_PARTICIPANT_IDS: ReadonlySet<string> = new Set([\n '__proto__',\n 'constructor',\n 'prototype',\n])\n\nexport type DrainResult = {\n settled: boolean\n outcome: DrainOutcome\n /** Existing successor arm a scheduler callback may join before acking. */\n schedulerArm?: Promise<void>\n}\n\nexport type ServerInternals = {\n /**\n * Await every in-flight background drain this machine has scheduled,\n * including ones scheduled while waiting. Lets tests flush the\n * fire-and-forget inline path deterministically — no sleeps.\n */\n settle(): Promise<void>\n /** The public `server.drain()` result deliberately remains `{ settled }`. */\n schedulerDrain(\n sessionId: string,\n opts?: { watchdogDueAt?: number },\n ): Promise<DrainResult>\n /** Deliver a scheduler-owned delayed append through the normal root path. */\n schedulerAppend(\n sessionId: string,\n events: readonly ScheduledEvent[],\n originIndex?: number,\n ): Promise<void>\n /** Deliver a scheduler-owned conditional clear, deletion included. */\n schedulerClear(sessionId: string, ifLastIndex: number): Promise<void>\n /** `session.history` with clear markers kept; reads start at the latest one. */\n historyWithMarkers(sessionId: string, gte: number): Promise<Event[]>\n}\n\nexport const serverInternals: WeakMap<object, ServerInternals> = new WeakMap()\n\n/**\n * Consumers A2's own runtimes define: named under `a2.`, with child ids that\n * leave the name out, since their types are disjoint. Defined through\n * `runtimeConsumer` (runtime-consumer.ts), which the server module installs.\n */\nexport const runtimeConsumers: WeakSet<object> = new WeakSet()\n\nexport const runtimeConsumerFactory: { define?: (options: never) => unknown } =\n {}\n\n/**\n * Consumer contexts whose previous attempt ended in a failure the consumer\n * threw, as opposed to a claim that lapsed (a crash, a timeout, a frozen\n * process). Only the attempt's own outcome is known, not how far it got.\n */\nexport const failedPreviousAttempt: WeakSet<object> = new WeakSet()\n\nconst objectLike = (value: unknown): value is object =>\n (typeof value === 'object' && value !== null) || typeof value === 'function'\n\n/** The ambient value delivered to `consumerContext` during a tool execution. */\nexport type AmbientToolScope = {\n /** Executing server identity; a test scope from `runWithConsumerContext` has none. */\n contract?: { readonly name: string }\n context: unknown\n}\n\n/**\n * Structural AsyncLocalStorage surface for the ambient tool scope. The\n * instance is created by the server-only AI entry point (the only place\n * `node:async_hooks` may be imported) and shared through a global\n * symbol slot, so `consumerContext` in the isomorphic AI entry point and\n * independently bundled A2 copies all read the same scope.\n */\nexport type AmbientToolScopeStorage = {\n getStore(): AmbientToolScope | undefined\n run<R>(store: AmbientToolScope, callback: () => R): R\n}\n\nconst TOOL_SCOPE_STORAGE = Symbol.for('experimental-a2.ai.tool-scope')\n\nexport function ambientToolScopeStorage(): AmbientToolScopeStorage | undefined {\n const value = Reflect.get(globalThis, TOOL_SCOPE_STORAGE)\n if (\n !objectLike(value) ||\n typeof Reflect.get(value, 'getStore') !== 'function' ||\n typeof Reflect.get(value, 'run') !== 'function'\n ) {\n return undefined\n }\n return value as AmbientToolScopeStorage\n}\n\nexport function installAmbientToolScopeStorage(\n create: () => AmbientToolScopeStorage,\n): AmbientToolScopeStorage {\n const existing = ambientToolScopeStorage()\n if (existing !== undefined) return existing\n const storage = create()\n Reflect.set(globalThis, TOOL_SCOPE_STORAGE, storage)\n return storage\n}\n\nexport type SchedulerSendFailureDisposition = 'retryable' | 'terminal'\n\ntype SchedulerSendFailureMarker = {\n readonly version: 1\n readonly pending: SchedulerSendFailureDisposition[]\n readonly active: SchedulerSendFailureDisposition[]\n}\n\nconst SCHEDULER_SEND_FAILURE = Symbol.for(\n 'experimental-a2.scheduler.send-failure',\n)\n\nconst schedulerSendFailureMarker = (\n value: unknown,\n): SchedulerSendFailureMarker | undefined => {\n if (!objectLike(value)) return undefined\n const marker = Reflect.get(value, SCHEDULER_SEND_FAILURE)\n if (\n !objectLike(marker) ||\n Reflect.get(marker, 'version') !== 1 ||\n !Array.isArray(Reflect.get(marker, 'pending')) ||\n !Array.isArray(Reflect.get(marker, 'active'))\n ) {\n return undefined\n }\n return marker as SchedulerSendFailureMarker\n}\n\nconst schedulerFailureObject = (error: unknown): object => {\n if (\n objectLike(error) &&\n (schedulerSendFailureMarker(error) !== undefined ||\n Object.isExtensible(error))\n ) {\n return error\n }\n return new Error(String(error), { cause: error })\n}\n\nconst schedulerSendFailureState = (\n error: object,\n): SchedulerSendFailureMarker => {\n const existing = schedulerSendFailureMarker(error)\n if (existing !== undefined) return existing\n const marker: SchedulerSendFailureMarker = {\n version: 1,\n pending: [],\n active: [],\n }\n Object.defineProperty(error, SCHEDULER_SEND_FAILURE, {\n value: marker,\n enumerable: false,\n configurable: true,\n writable: false,\n })\n return marker\n}\n\nconst clearSchedulerSendFailureState = (\n error: object,\n marker: SchedulerSendFailureMarker,\n): void => {\n if (marker.pending.length === 0 && marker.active.length === 0) {\n Reflect.deleteProperty(error, SCHEDULER_SEND_FAILURE)\n }\n}\n\n/**\n * Classify an adapter error before throwing it. Unclassified adapter failures\n * are retryable because the caller cannot know whether a remote send landed.\n */\nexport function classifySchedulerSendFailure(\n error: unknown,\n disposition: SchedulerSendFailureDisposition,\n): unknown {\n const classified = schedulerFailureObject(error)\n schedulerSendFailureState(classified).pending.push(disposition)\n return classified\n}\n\n/** Consume a pending adapter classification and count this rejected send. */\nexport function markSchedulerSendFailure(error: unknown): unknown {\n const marked = schedulerFailureObject(error)\n const marker = schedulerSendFailureState(marked)\n marker.active.push(marker.pending.pop() ?? 'retryable')\n return marked\n}\n\nexport function consumeSchedulerSendFailure(\n error: unknown,\n): SchedulerSendFailureDisposition | undefined {\n if (!objectLike(error)) return undefined\n const marker = schedulerSendFailureMarker(error)\n const disposition = marker?.active.pop()\n if (marker !== undefined) clearSchedulerSendFailureState(error, marker)\n return disposition\n}\n\nconst isDrainableServer = (value: unknown): value is DrainableServer => {\n if (value === null || typeof value !== 'object') return false\n const contract = Reflect.get(value, 'contract')\n return (\n contract !== null &&\n typeof contract === 'object' &&\n typeof Reflect.get(contract, 'name') === 'string' &&\n typeof Reflect.get(value, 'drain') === 'function'\n )\n}\n\n/**\n * Validate the server list captured by a scheduler route at construction time.\n * The seam stays structural so custom scheduler integrations can supply their\n * own drainable targets.\n */\nexport function assertSchedulerTargets(\n servers: readonly unknown[],\n): asserts servers is readonly DrainableServer[] {\n const contracts = new Set<string>()\n for (const target of servers) {\n if (!isDrainableServer(target)) {\n throw new TypeError('a2 scheduler: expected a server from createServer()')\n }\n const name = target.contract.name\n if (contracts.has(name)) {\n throw new TypeError(\n `a2 scheduler: duplicate server contract '${name}' in one handler`,\n )\n }\n contracts.add(name)\n }\n}\n\n/**\n * Drain timing knobs. Mutable only as a white-box test seam. Claim\n * heartbeats run on real timers, so tests shrink these to keep suites\n * fast. Production always uses the defaults.\n */\nexport const DRAIN_TIMINGS: {\n /** How long a claim lives without renewal. */\n claimTtlMs: number\n /** Heartbeat cadence while a consumer is running. */\n claimHeartbeatMs: number\n /** Base delay before retrying a failed renewal while the lease lives. */\n renewRetryMs: number\n /** Delay between a claim's expiry and its scheduler watchdog. */\n watchdogGraceMs: number\n /** Maximum time a top-level append waits for its initial scheduler arm. */\n schedulerArmTimeoutMs: number\n /** First retry delay after a caught failure, doubling per failure. */\n failureRetryBaseMs: number\n /** Ceiling of the caught-failure retry delay. */\n failureRetryMaxMs: number\n} = {\n claimTtlMs: 5_000,\n claimHeartbeatMs: 2_000,\n renewRetryMs: 400,\n watchdogGraceMs: 1_000,\n schedulerArmTimeoutMs: 2_000,\n failureRetryBaseMs: 1_000,\n failureRetryMaxMs: 30_000,\n}\n\n/**\n * How many kernels a harness keeps, per agent, for the next step of a session\n * in its process. Mutable only as a white-box test seam; production always\n * uses the default.\n */\nexport const HARNESS_PARKING: { maxKernels: number } = { maxKernels: 32 }\n\n/**\n * Per server: called when a drain of a session ends with nothing left to run\n * for it in this process, so the layer above can let go of what it kept for\n * that session's next step.\n */\nexport const drainSettledHooks: WeakMap<object, (sessionId: string) => void> =\n new WeakMap()\n\n/**\n * Stream liveness knobs — the server-side SSE heartbeat and the\n * client-side stall watchdog. Paired by design: the client declares a\n * connection dead after roughly two missed heartbeats. Mutable only as\n * a white-box test seam; production always uses the defaults.\n */\n/**\n * Poll cadence for the poll-based store streams (sqlite, postgres) —\n * owned, not configurable: adaptive polling removed the operator's\n * reason to tune it (the floor keeps active streams smooth; the\n * ceiling bounds idle cost). Mutable only as a white-box test seam so\n * suites don't wait out real idle gaps.\n */\nexport const POLL_TIMINGS: {\n /** Gap between polls while a session is producing events. */\n activeFloorMs: number\n /** The idle ceiling — the longest gap between polls. */\n idleCeilingMs: number\n} = {\n activeFloorMs: 25,\n idleCeilingMs: 2_000,\n}\n\n/**\n * Timings for the notify-driven redis live stream. A parked feed re-reads\n * the store every `safetyReadMs` even without a pub/sub wake-up, so a lost\n * notification (subscriber reconnect gap, dropped PUBLISH) costs latency,\n * never an event. Writers publish after their scripts, so wake-ups from\n * concurrent writers can arrive out of order: a feed holds a gap for\n * `reorderMs` before catching up with a read, and a write waits at most\n * `publishMs` for its PUBLISH. Mutable only as a white-box test seam.\n */\nexport const NOTIFY_TIMINGS: {\n safetyReadMs: number\n reorderMs: number\n publishMs: number\n} = {\n safetyReadMs: 10_000,\n reorderMs: 50,\n publishMs: 1_000,\n}\n\n/**\n * Bounds for each server instance's in-memory reducer-state cache. Sizes are\n * encoded JSON lengths, re-measured every `measureEvery` folded events, so\n * the byte budget is approximate. Mutable only as a white-box test seam.\n */\nexport const STATE_CACHE: {\n maxEntries: number\n maxBytes: number\n /** Events a cached state may advance before it writes a snapshot. */\n snapshotEvery: number\n /** Events a warm read may fold; longer tails read the stored snapshot. */\n maxTail: number\n /** Events folded between re-measuring a cached state's encoded size. */\n measureEvery: number\n} = {\n maxEntries: 1_000,\n maxBytes: 32 * 1024 * 1024,\n snapshotEvery: 128,\n maxTail: 255,\n measureEvery: 16,\n}\n\n/**\n * Warm state-read tails for stores that can serve one in a single request:\n * events from `fromIndex` (inclusive) through `throughIndex` or the newest\n * event, or null when that needs more than one request. Like every store\n * read, a tail starts no lower than the latest clear marker. Keyed by the exact\n * store object, so a wrapper built by spreading or proxying a store reads\n * through its own `readHistory` instead.\n */\nexport const stateTailReaders: WeakMap<\n object,\n (\n sessionId: string,\n fromIndex: number,\n throughIndex?: number,\n ) => Promise<Event[] | null>\n> = new WeakMap()\n\nexport const STREAM_TIMINGS: {\n /** Cadence of `: ping` comment frames from `sseResponse`. */\n sseHeartbeatMs: number\n /** How far ahead of an invocation deadline an SSE response closes. */\n sseDeadlineGraceMs: number\n /** Client: silence longer than this means the connection is dead. */\n stallTimeoutMs: number\n /** Client reconnect backoff: first retry delay, doubling per attempt. */\n reconnectBaseMs: number\n /** Client reconnect backoff ceiling. */\n reconnectMaxMs: number\n} = {\n sseHeartbeatMs: 15_000,\n sseDeadlineGraceMs: 1_000,\n stallTimeoutMs: 35_000,\n reconnectBaseMs: 500,\n reconnectMaxMs: 5_000,\n}\n\n/**\n * WebSocket down-pump backpressure — `sessionsSocket` parks its pull\n * loop while the socket reports more buffered bytes than the\n * high-water mark and re-checks on the poll cadence, so a slow reader\n * applies backpressure to the store subscription instead of buffering\n * without bound in the socket. Acks and heartbeats stay exempt: they\n * are small and correctness-relevant. Mutable only as a white-box test\n * seam.\n */\nexport const SOCKET_TIMINGS: {\n highWaterMarkBytes: number\n resumePollMs: number\n /** Up-lane bound: concurrent push/presence handlers per socket. POST\n * gets per-request platform limits for free; the socket must bring\n * its own. Over the cap, pushes are answered with a retryable error\n * ack and presence frames are shed (that plane repaints). */\n maxInFlightMessages: number\n /** Hard ceiling on `bufferedAmount`: past it the peer is a\n * non-reader (acks and pings are park-exempt, so only a peer that\n * never reads can reach it) and the socket closes 1008. */\n disconnectWaterMarkBytes: number\n} = {\n highWaterMarkBytes: 1_048_576,\n resumePollMs: 25,\n maxInFlightMessages: 32,\n disconnectWaterMarkBytes: 8_388_608,\n}\n\n/**\n * Presence timings. Owned, not configurable (decision #36's rule).\n * Mutable only as a white-box test seam.\n */\nexport const PRESENCE_TIMINGS: {\n /**\n * Client send cadence — a leading send immediately, then at most one\n * merged send per interval (~15Hz). Coupling the cadence to the POST\n * ack tied remote cursor smoothness to RTT, and the ack serves no\n * correctness purpose on a fire-and-forget LWW plane.\n */\n sendIntervalMs: number\n /**\n * How long, in local receipt time, a presence applier keeps a deleted\n * field's stamp so an older set delivered after the delete still\n * loses. Reorders span milliseconds; the bound keeps a long-lived\n * client with churning participants from accumulating tombstones.\n */\n tombstoneMs: number\n} = {\n sendIntervalMs: 66,\n tombstoneMs: 60_000,\n}\n"],"mappings":"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"}
package/dist/otel.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { i as A2Telemetry } from "./telemetry-Cyfqo6Qt.js";
1
+ import { i as A2Telemetry } from "./telemetry-CWwsh-Gx.js";
2
2
  import { Tracer } from "@opentelemetry/api";
3
3
  //#region src/otel.d.ts
4
4
  type OtelOptions = {
package/dist/otel.js CHANGED
@@ -6,7 +6,7 @@ import { SpanStatusCode, trace } from "@opentelemetry/api";
6
6
  * Peer-depends on `@opentelemetry/api` (optional). Spans nest through
7
7
  * the active context, so with a context manager registered (any real
8
8
  * OTel setup, e.g. `@vercel/otel`) the happy path lands in one trace:
9
- * request → a2.append → a2.drain → a2.event → the handler's own spans.
9
+ * request → a2.append → a2.drain → a2.event → the consumer's own spans.
10
10
  */
11
11
  const fail = (span, error) => {
12
12
  span.recordException(error instanceof Error ? error : new Error(String(error)));
package/dist/otel.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"otel.js","names":[],"sources":["../src/otel.ts"],"sourcesContent":["/**\n * experimental-a2/otel — OpenTelemetry adapter for the A2Telemetry interface.\n *\n * Peer-depends on `@opentelemetry/api` (optional). Spans nest through\n * the active context, so with a context manager registered (any real\n * OTel setup, e.g. `@vercel/otel`) the happy path lands in one trace:\n * request → a2.append → a2.drain → a2.event → the handler's own spans.\n */\n\nimport {\n SpanStatusCode,\n trace,\n type Span,\n type Tracer,\n} from '@opentelemetry/api'\nimport type { A2Telemetry } from './telemetry.ts'\n\nexport type OtelOptions = {\n /** Bring your own tracer; defaults to `trace.getTracer(tracerName)`. */\n tracer?: Tracer\n /** Tracer name for the global provider. Default `'a2'`. */\n tracerName?: string\n}\n\nconst fail = (span: Span, error: unknown): void => {\n span.recordException(\n error instanceof Error ? error : new Error(String(error)),\n )\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n })\n}\n\nexport function otel(options: OtelOptions = {}): A2Telemetry {\n const tracer = options.tracer ?? trace.getTracer(options.tracerName ?? 'a2')\n\n return {\n span(name, attributes, fn) {\n return tracer.startActiveSpan(name, { attributes }, async (span) => {\n try {\n return await fn({\n setAttribute: (key, value) => {\n span.setAttribute(key, value)\n },\n recordError: (error) => {\n fail(span, error)\n },\n })\n } catch (error) {\n fail(span, error)\n throw error\n } finally {\n span.end()\n }\n })\n },\n }\n}\n"],"mappings":";;;;;;;;;;AAwBA,MAAM,QAAQ,MAAY,UAAyB;CACjD,KAAK,gBACH,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAC1D;CACA,KAAK,UAAU;EACb,MAAM,eAAe;EACrB,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;CAChE,CAAC;AACH;AAEA,SAAgB,KAAK,UAAuB,CAAC,GAAgB;CAC3D,MAAM,SAAS,QAAQ,UAAU,MAAM,UAAU,QAAQ,cAAc,IAAI;CAE3E,OAAO,EACL,KAAK,MAAM,YAAY,IAAI;EACzB,OAAO,OAAO,gBAAgB,MAAM,EAAE,WAAW,GAAG,OAAO,SAAS;GAClE,IAAI;IACF,OAAO,MAAM,GAAG;KACd,eAAe,KAAK,UAAU;MAC5B,KAAK,aAAa,KAAK,KAAK;KAC9B;KACA,cAAc,UAAU;MACtB,KAAK,MAAM,KAAK;KAClB;IACF,CAAC;GACH,SAAS,OAAO;IACd,KAAK,MAAM,KAAK;IAChB,MAAM;GACR,UAAU;IACR,KAAK,IAAI;GACX;EACF,CAAC;CACH,EACF;AACF"}
1
+ {"version":3,"file":"otel.js","names":[],"sources":["../src/otel.ts"],"sourcesContent":["/**\n * experimental-a2/otel — OpenTelemetry adapter for the A2Telemetry interface.\n *\n * Peer-depends on `@opentelemetry/api` (optional). Spans nest through\n * the active context, so with a context manager registered (any real\n * OTel setup, e.g. `@vercel/otel`) the happy path lands in one trace:\n * request → a2.append → a2.drain → a2.event → the consumer's own spans.\n */\n\nimport {\n SpanStatusCode,\n trace,\n type Span,\n type Tracer,\n} from '@opentelemetry/api'\nimport type { A2Telemetry } from './telemetry.ts'\n\nexport type OtelOptions = {\n /** Bring your own tracer; defaults to `trace.getTracer(tracerName)`. */\n tracer?: Tracer\n /** Tracer name for the global provider. Default `'a2'`. */\n tracerName?: string\n}\n\nconst fail = (span: Span, error: unknown): void => {\n span.recordException(\n error instanceof Error ? error : new Error(String(error)),\n )\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n })\n}\n\nexport function otel(options: OtelOptions = {}): A2Telemetry {\n const tracer = options.tracer ?? trace.getTracer(options.tracerName ?? 'a2')\n\n return {\n span(name, attributes, fn) {\n return tracer.startActiveSpan(name, { attributes }, async (span) => {\n try {\n return await fn({\n setAttribute: (key, value) => {\n span.setAttribute(key, value)\n },\n recordError: (error) => {\n fail(span, error)\n },\n })\n } catch (error) {\n fail(span, error)\n throw error\n } finally {\n span.end()\n }\n })\n },\n }\n}\n"],"mappings":";;;;;;;;;;AAwBA,MAAM,QAAQ,MAAY,UAAyB;CACjD,KAAK,gBACH,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAC1D;CACA,KAAK,UAAU;EACb,MAAM,eAAe;EACrB,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;CAChE,CAAC;AACH;AAEA,SAAgB,KAAK,UAAuB,CAAC,GAAgB;CAC3D,MAAM,SAAS,QAAQ,UAAU,MAAM,UAAU,QAAQ,cAAc,IAAI;CAE3E,OAAO,EACL,KAAK,MAAM,YAAY,IAAI;EACzB,OAAO,OAAO,gBAAgB,MAAM,EAAE,WAAW,GAAG,OAAO,SAAS;GAClE,IAAI;IACF,OAAO,MAAM,GAAG;KACd,eAAe,KAAK,UAAU;MAC5B,KAAK,aAAa,KAAK,KAAK;KAC9B;KACA,cAAc,UAAU;MACtB,KAAK,MAAM,KAAK;KAClB;IACF,CAAC;GACH,SAAS,OAAO;IACd,KAAK,MAAM,KAAK;IAChB,MAAM;GACR,UAAU;IACR,KAAK,IAAI;GACX;EACF,CAAC;CACH,EACF;AACF"}
@@ -1,6 +1,6 @@
1
- import { c as ContractEvent, d as PresenceDefs, f as PresenceMap, g as WithPresence, o as AppendInput, p as PresencePatch, s as Contract, t as Reducer, u as EventDefs } from "./reducer-CqA59Q6U.js";
2
- import { t as InitialSnapshot } from "./initial-snapshot-Bx-c0pDP.js";
3
- import { a as Connection, c as HistoryState, l as LoadHistoryOptions, t as A2Client, u as PushResult } from "./client-h_DKFAjj.js";
1
+ import { c as ContractEvent, d as PresenceDefs, f as PresenceMap, g as WithPresence, o as AppendInput, p as PresencePatch, s as Contract, t as Reducer, u as EventDefs } from "./reducer-CYPHsSmd.js";
2
+ import { t as InitialSnapshot } from "./initial-snapshot-D3Lve43o.js";
3
+ import { a as Connection, c as HistoryState, l as LoadHistoryOptions, t as A2Client, u as PushResult } from "./client-CUMbS_38.js";
4
4
  import { ReactElement, ReactNode } from "react";
5
5
  //#region src/react.d.ts
6
6
  type UseSessionOptions<S> = {
@@ -83,4 +83,4 @@ declare function createReact<D extends EventDefs, S, P extends PresenceDefs = Re
83
83
  declare function useSession<D extends EventDefs, S, P extends PresenceDefs = Record<never, never>>(client: A2Client<D, S, P>, sessionId: string, options?: UseSessionOptions<NoInfer<S>>): UseSessionResult<D, S, P>;
84
84
  //#endregion
85
85
  export { SessionProviderProps as a, UseSessionResult as c, BoundSessionProviderProps as i, createReact as l, BoundA2React as n, UseSessionOptions as o, BoundSessionOptions as r, UseSessionPresence as s, A2React as t, useSession as u };
86
- //# sourceMappingURL=react-BBXgHWFc.d.ts.map
86
+ //# sourceMappingURL=react-BoHvC3v9.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"react-BBXgHWFc.d.ts","names":[],"sources":["../src/react.ts"],"mappings":";;;;;KAwCY,kBAAkB;EAC5B;EACA,UAAU,gBAAgB;EAC1B;EACA;;KAGU,qBAAqB,UAAU,WAAW;EACpD;EACA;EACA,UAAU,gBAAgB;EAC1B,gBAAgB,cAAc;EAC9B;EACA,WAAW;;KAGD,0BAA0B,UAAU,WAAW,KAAK,KAC9D,qBAAqB,GAAG;KAId,mBAAmB,UAAU;;EAEvC,UAAU,YAAY;;;;;;;;EAQtB,cAAc,QAAQ,cAAc;;KAG1B,iBACV,UAAU,WACV,GACA,UAAU,eAAe,wBACvB,aAAa,GAAG,mBAAmB;;EAErC,OAAO;;EAEP;;EAEA,QAAQ,cAAc;;EAEtB;;;;;;EAMA,YAAY;;;;;;EAMZ,UAAU,QAAQ,YAAY,SAAS,WAAW;;EAElD;;;;;;EAMA,cAAc,UAAU,uBAAuB,QAAQ,cAAc;;EAErE,SAAS;;KAGC,oBACV,UAAU,WACV,GACA,UAAU,gBACR,kBAAkB;EACpB;EACA,SAAS,SAAS,GAAG,GAAG;;KAGd,QACV,UAAU,WACV,GACA,UAAU,eAAe;EAEzB,kBAAkB,OAAO,qBAAqB,GAAG,OAAO;EACxD,aACE,UAAU,oBAAoB,GAAG,GAAG,OACjC,iBAAiB,GAAG,GAAG;;KAGlB,aACV,UAAU,WACV,GACA,UAAU,eAAe;EAEzB,kBAAkB,OAAO,0BAA0B,GAAG,OAAO;EAC7D,aACE,UAAU,oBAAoB,GAAG,GAAG,OACjC,iBAAiB,GAAG,GAAG;;iBAGd,YACd,UAAU,WACV,GACA,UAAU,eAAe,sBACzB;EAAW,QAAQ,SAAS,GAAG,GAAG;IAAO,aAAa,GAAG,GAAG;iBAC9C,YACd,UAAU,WACV,GACA,UAAU,eAAe,sBACzB;EAAW,UAAU,SAAS,GAAG,GAAG,QAAQ,GAAG,GAAG;IAAQ,QAAQ,GAAG,GAAG;iBA4J1D,WACd,UAAU,WACV,GACA,UAAU,eAAe,sBAEzB,QAAQ,SAAS,GAAG,GAAG,IACvB,mBACA,UAAS,kBAAkB,QAAQ,MAClC,iBAAiB,GAAG,GAAG"}
1
+ {"version":3,"file":"react-BoHvC3v9.d.ts","names":[],"sources":["../src/react.ts"],"mappings":";;;;;KAwCY,kBAAkB;EAC5B;EACA,UAAU,gBAAgB;EAC1B;EACA;;KAGU,qBAAqB,UAAU,WAAW;EACpD;EACA;EACA,UAAU,gBAAgB;EAC1B,gBAAgB,cAAc;EAC9B;EACA,WAAW;;KAGD,0BAA0B,UAAU,WAAW,KAAK,KAC9D,qBAAqB,GAAG;KAId,mBAAmB,UAAU;;EAEvC,UAAU,YAAY;;;;;;;;EAQtB,cAAc,QAAQ,cAAc;;KAG1B,iBACV,UAAU,WACV,GACA,UAAU,eAAe,wBACvB,aAAa,GAAG,mBAAmB;;EAErC,OAAO;;EAEP;;EAEA,QAAQ,cAAc;;EAEtB;;;;;;EAMA,YAAY;;;;;;EAMZ,UAAU,QAAQ,YAAY,SAAS,WAAW;;EAElD;;;;;;EAMA,cAAc,UAAU,uBAAuB,QAAQ,cAAc;;EAErE,SAAS;;KAGC,oBACV,UAAU,WACV,GACA,UAAU,gBACR,kBAAkB;EACpB;EACA,SAAS,SAAS,GAAG,GAAG;;KAGd,QACV,UAAU,WACV,GACA,UAAU,eAAe;EAEzB,kBAAkB,OAAO,qBAAqB,GAAG,OAAO;EACxD,aACE,UAAU,oBAAoB,GAAG,GAAG,OACjC,iBAAiB,GAAG,GAAG;;KAGlB,aACV,UAAU,WACV,GACA,UAAU,eAAe;EAEzB,kBAAkB,OAAO,0BAA0B,GAAG,OAAO;EAC7D,aACE,UAAU,oBAAoB,GAAG,GAAG,OACjC,iBAAiB,GAAG,GAAG;;iBAGd,YACd,UAAU,WACV,GACA,UAAU,eAAe,sBACzB;EAAW,QAAQ,SAAS,GAAG,GAAG;IAAO,aAAa,GAAG,GAAG;iBAC9C,YACd,UAAU,WACV,GACA,UAAU,eAAe,sBACzB;EAAW,UAAU,SAAS,GAAG,GAAG,QAAQ,GAAG,GAAG;IAAQ,QAAQ,GAAG,GAAG;iBA4J1D,WACd,UAAU,WACV,GACA,UAAU,eAAe,sBAEzB,QAAQ,SAAS,GAAG,GAAG,IACvB,mBACA,UAAS,kBAAkB,QAAQ,MAClC,iBAAiB,GAAG,GAAG"}
package/dist/react.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as SessionProviderProps, c as UseSessionResult, i as BoundSessionProviderProps, l as createReact, n as BoundA2React, o as UseSessionOptions, r as BoundSessionOptions, s as UseSessionPresence, t as A2React, u as useSession } from "./react-BBXgHWFc.js";
1
+ import { a as SessionProviderProps, c as UseSessionResult, i as BoundSessionProviderProps, l as createReact, n as BoundA2React, o as UseSessionOptions, r as BoundSessionOptions, s as UseSessionPresence, t as A2React, u as useSession } from "./react-BoHvC3v9.js";
2
2
  export { A2React, BoundA2React, BoundSessionOptions, BoundSessionProviderProps, SessionProviderProps, UseSessionOptions, UseSessionPresence, UseSessionResult, createReact, useSession };
package/dist/react.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { i as isInitialPromise, n as createRenderSession, r as resolveInitialForReact, t as createClient } from "./client-BhHLoNKF.js";
2
+ import { i as isInitialPromise, n as createRenderSession, r as resolveInitialForReact, t as createClient } from "./client-BQS4i_V9.js";
3
3
  import { createContext, createElement, use, useContext, useEffect, useMemo, useSyncExternalStore } from "react";
4
4
  //#region src/react.ts
5
5
  function createReact(options) {
@@ -146,7 +146,7 @@ type Contract<D extends EventDefs = EventDefs, P extends PresenceDefs = Record<n
146
146
  readonly events: Readonly<D>;
147
147
  /** The presence vocabulary — empty unless declared. */
148
148
  readonly presence: Readonly<P>;
149
- /** Preserve literal types for a handler-returned event batch. */
149
+ /** Preserve literal types for a consumer-returned event batch. */
150
150
  readonly batch: EventBatchFactory<D>;
151
151
  readonly reducer: R;
152
152
  };
@@ -235,4 +235,4 @@ declare function reducer<D extends EventDefs, P extends PresenceDefs, Seed, S =
235
235
  }): ReducerBuilder<D, S, P>;
236
236
  //#endregion
237
237
  export { contract as _, reducer as a, ContractEvent as c, PresenceDefs as d, PresenceMap as f, WithPresence as g, ReducerOptions as h, StateSnapshot as i, EventBatchFactory as l, PresenceSnapshot as m, ReducerBuilder as n, AppendInput as o, PresencePatch as p, RegisteredReducer as r, Contract as s, Reducer as t, EventDefs as u, StandardSchemaV1 as v };
238
- //# sourceMappingURL=reducer-CqA59Q6U.d.ts.map
238
+ //# sourceMappingURL=reducer-CYPHsSmd.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reducer-CqA59Q6U.d.ts","names":[],"sources":["../src/standard-schema.ts","../src/contract.ts","../src/reducer.ts"],"mappings":"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"}
1
+ {"version":3,"file":"reducer-CYPHsSmd.d.ts","names":[],"sources":["../src/standard-schema.ts","../src/contract.ts","../src/reducer.ts"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { n as isSessionCleared } from "./session-clear-DGI4jTQ5.js";
1
+ import { n as isSessionCleared } from "./session-clear-wHd5GCpZ.js";
2
2
  //#region src/validate.ts
3
3
  /**
4
4
  * Run a Standard Schema validator, requiring a synchronous result.
@@ -84,4 +84,4 @@ function reducer(options) {
84
84
  //#endregion
85
85
  export { reducer as a, makeReducerBuilder as i, foldEvent as n, assertSyncSchema as o, foldsClearMarkers as r, validateSync as s, cloneInitial as t };
86
86
 
87
- //# sourceMappingURL=reducer-BAXc8JyF.js.map
87
+ //# sourceMappingURL=reducer-CjmvZSAb.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"reducer-BAXc8JyF.js","names":[],"sources":["../src/validate.ts","../src/reducer.ts"],"sourcesContent":["import type { StandardSchemaV1 } from './standard-schema.ts'\n\n/**\n * Run a Standard Schema validator, requiring a synchronous result.\n * Validators must be synchronous (specs/a2-api.md §1) — an async one is\n * a definition bug, rejected at definition time where possible and here\n * as a backstop. `what` names the schema in error messages, e.g.\n * \"event 'created'\" or \"the stateSchema\".\n */\nexport function validateSync<Input, Output>(\n schema: StandardSchemaV1<Input, Output>,\n value: unknown,\n what: string,\n): StandardSchemaV1.Result<Output> {\n const result = schema['~standard'].validate(value)\n if (result instanceof Promise) {\n throw new TypeError(\n `the schema for ${what} is asynchronous — a2 schemas must validate synchronously`,\n )\n }\n return result\n}\n\n/**\n * Probe a schema to reject always-async validators early. The probe\n * value is deliberately garbage — we only care whether the result is a\n * Promise, not whether it passes.\n */\nexport function assertSyncSchema(schema: StandardSchemaV1, what: string): void {\n const probe = Symbol('a2.schema-probe')\n const result = schema['~standard'].validate(probe)\n if (result instanceof Promise) {\n // Swallow the eventual rejection — the probe input was garbage.\n result.catch(() => {})\n throw new TypeError(\n `the schema for ${what} is asynchronous — a2 schemas must validate synchronously`,\n )\n }\n}\n","import type {\n ContractEvent,\n EventDefs,\n PresenceDefs,\n ReducerOptions,\n} from './contract.ts'\nimport { isSessionCleared, type SessionCleared } from './session-clear.ts'\nimport type { StandardSchemaV1 } from './standard-schema.ts'\nimport { validateSync } from './validate.ts'\n\nexport type StateSnapshot<S> = {\n state: S\n index: number\n /** The session's latest clear marker, once it has been cleared. */\n clearIndex?: number | undefined\n}\n\n/**\n * A fresh copy of a reducer's seed, so a fold that mutates in place\n * cannot corrupt the shared `initialState`. Non-cloneable seeds\n * (functions, class instances, proxies) fall back to the original.\n */\nexport function cloneInitial<S>(initial: S): S {\n try {\n return structuredClone(initial)\n } catch {\n return initial\n }\n}\n\nconst markerFolds = new WeakSet<object>()\n\n/**\n * Mark a fold that receives clear markers itself instead of resetting to its\n * `initialState`. The actor follower has no seed of its own; its stream carries\n * the reset state in the marker.\n */\nexport function foldsClearMarkers<\n F extends (state: never, event: never) => unknown,\n>(fold: F): F {\n markerFolds.add(fold)\n return fold\n}\n\n/** One fold step; a clear marker resets to the seed on server and client alike. */\nexport function foldEvent<D extends EventDefs, S>(\n source: Pick<Reducer<D, S>, 'fold' | 'initialState'>,\n state: S,\n event: ContractEvent<D> | SessionCleared,\n): S {\n if (!isSessionCleared(event)) return source.fold(state, event)\n return markerFolds.has(source.fold)\n ? source.fold(state, event as unknown as ContractEvent<D>)\n : cloneInitial(source.initialState)\n}\n\n/**\n * A pure fold over a session's events — `(state, event) => state` —\n * published inline in a contract or built with `reducer(options).fold(fn)`.\n *\n * The name is the reducer's identity. Cached folds (server snapshots,\n * the browser cache) are keyed by it, which makes it the invalidation\n * knob too: changed the fold's logic? Change the name. Snapshots under\n * the old name are ignored and the next read refolds from raw events.\n * See docs/contract/02-state.mdx.\n */\nexport type Reducer<\n D extends EventDefs = EventDefs,\n S = unknown,\n P extends PresenceDefs = Record<never, never>,\n> = {\n /** Identity and cache key. Rename when the fold's logic changes. */\n readonly name: string\n /** The contract's vocabulary — types every event in the fold. */\n readonly events: Readonly<D>\n /**\n * The contract's presence vocabulary — carried because the reducer\n * is the client's typed handle on the contract. The fold never sees\n * presence.\n */\n readonly presence: Readonly<P>\n readonly fold: (state: S, event: ContractEvent<D>) => S\n readonly initialState: S\n /**\n * When present: `initialState` was validated against it at\n * definition, and any cached fold a2 rehydrates (server snapshots,\n * the browser cache) is checked against it — failing snapshots are\n * discarded and refolded, catching shape drift a stale name can't.\n */\n readonly stateSchema: StandardSchemaV1<unknown, S> | undefined\n}\n\n/** The second step of `reducer` — see the method's doc. */\nexport type ReducerBuilder<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n> = {\n fold(fold: (state: S, event: ContractEvent<D>) => S): Reducer<D, S, P>\n}\n\n/** Internal — shared validation for inline and standalone reducer definitions. */\nexport function makeReducerBuilder<\n D extends EventDefs,\n P extends PresenceDefs,\n Seed,\n S,\n>(\n events: Readonly<D>,\n presence: Readonly<P>,\n options: {\n name: string\n initialState: Seed\n stateSchema?: StandardSchemaV1<Seed, S>\n },\n): ReducerBuilder<D, S, P> {\n const { name } = options\n if (typeof name !== 'string' || name.length === 0) {\n throw new TypeError(\n 'reducer name must be a non-empty string — it identifies the reducer and keys cached snapshots',\n )\n }\n\n // Without a schema, S = Seed (the type parameter default), so the\n // seed is the state. With one, the seed is the schema's input and the\n // validated output (defaults applied) becomes the fold's actual seed.\n let initialState = options.initialState as unknown as S\n if (options.stateSchema) {\n const result = validateSync(\n options.stateSchema,\n options.initialState,\n 'the stateSchema',\n )\n if (result.issues) {\n throw new TypeError(\n `reducer initialState does not match its stateSchema: ${result.issues\n .map((issue) => issue.message)\n .join('; ')}`,\n )\n }\n initialState = result.value as S\n const restored = validateSync(\n options.stateSchema,\n initialState,\n 'the stateSchema',\n )\n if (\n restored.issues ||\n JSON.stringify(restored.value) !== JSON.stringify(initialState)\n ) {\n throw new TypeError(\n 'stateSchema must accept and preserve its own output so snapshots can be restored',\n )\n }\n }\n\n return {\n fold(fold) {\n if (typeof fold !== 'function') {\n throw new TypeError('reducer fold must be a function')\n }\n return {\n name,\n events,\n presence,\n fold,\n initialState,\n stateSchema: options.stateSchema as\n StandardSchemaV1<unknown, S> | undefined,\n }\n },\n }\n}\n\nexport type RegisteredReducer<\n D extends EventDefs,\n P extends PresenceDefs,\n> = Omit<Reducer<D, unknown, P>, 'fold'> & {\n readonly fold: (state: never, event: never) => unknown\n}\n\nexport function reducer<\n D extends EventDefs,\n P extends PresenceDefs,\n Seed,\n S = Seed,\n>(\n options: ReducerOptions<Seed, S> & {\n contract: { events: Readonly<D>; presence: Readonly<P> }\n },\n): ReducerBuilder<D, S, P> {\n return makeReducerBuilder(\n options.contract.events,\n options.contract.presence,\n options,\n )\n}\n"],"mappings":";;;;;;;;;AASA,SAAgB,aACd,QACA,OACA,MACiC;CACjC,MAAM,SAAS,OAAO,YAAY,CAAC,SAAS,KAAK;CACjD,IAAI,kBAAkB,SACpB,MAAM,IAAI,UACR,kBAAkB,KAAK,0DACzB;CAEF,OAAO;AACT;;;;;;AAOA,SAAgB,iBAAiB,QAA0B,MAAoB;CAC7E,MAAM,QAAQ,OAAO,iBAAiB;CACtC,MAAM,SAAS,OAAO,YAAY,CAAC,SAAS,KAAK;CACjD,IAAI,kBAAkB,SAAS;EAE7B,OAAO,YAAY,CAAC,CAAC;EACrB,MAAM,IAAI,UACR,kBAAkB,KAAK,0DACzB;CACF;AACF;;;;;;;;AChBA,SAAgB,aAAgB,SAAe;CAC7C,IAAI;EACF,OAAO,gBAAgB,OAAO;CAChC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,MAAM,8BAAc,IAAI,QAAgB;;;;;;AAOxC,SAAgB,kBAEd,MAAY;CACZ,YAAY,IAAI,IAAI;CACpB,OAAO;AACT;;AAGA,SAAgB,UACd,QACA,OACA,OACG;CACH,IAAI,CAAC,iBAAiB,KAAK,GAAG,OAAO,OAAO,KAAK,OAAO,KAAK;CAC7D,OAAO,YAAY,IAAI,OAAO,IAAI,IAC9B,OAAO,KAAK,OAAO,KAAoC,IACvD,aAAa,OAAO,YAAY;AACtC;;AAgDA,SAAgB,mBAMd,QACA,UACA,SAKyB;CACzB,MAAM,EAAE,SAAS;CACjB,IAAI,OAAO,SAAS,YAAY,KAAK,WAAW,GAC9C,MAAM,IAAI,UACR,+FACF;CAMF,IAAI,eAAe,QAAQ;CAC3B,IAAI,QAAQ,aAAa;EACvB,MAAM,SAAS,aACb,QAAQ,aACR,QAAQ,cACR,iBACF;EACA,IAAI,OAAO,QACT,MAAM,IAAI,UACR,wDAAwD,OAAO,OAC5D,KAAK,UAAU,MAAM,OAAO,CAAC,CAC7B,KAAK,IAAI,GACd;EAEF,eAAe,OAAO;EACtB,MAAM,WAAW,aACf,QAAQ,aACR,cACA,iBACF;EACA,IACE,SAAS,UACT,KAAK,UAAU,SAAS,KAAK,MAAM,KAAK,UAAU,YAAY,GAE9D,MAAM,IAAI,UACR,kFACF;CAEJ;CAEA,OAAO,EACL,KAAK,MAAM;EACT,IAAI,OAAO,SAAS,YAClB,MAAM,IAAI,UAAU,iCAAiC;EAEvD,OAAO;GACL;GACA;GACA;GACA;GACA;GACA,aAAa,QAAQ;EAEvB;CACF,EACF;AACF;AASA,SAAgB,QAMd,SAGyB;CACzB,OAAO,mBACL,QAAQ,SAAS,QACjB,QAAQ,SAAS,UACjB,OACF;AACF"}
1
+ {"version":3,"file":"reducer-CjmvZSAb.js","names":[],"sources":["../src/validate.ts","../src/reducer.ts"],"sourcesContent":["import type { StandardSchemaV1 } from './standard-schema.ts'\n\n/**\n * Run a Standard Schema validator, requiring a synchronous result.\n * Validators must be synchronous (specs/a2-api.md §1) — an async one is\n * a definition bug, rejected at definition time where possible and here\n * as a backstop. `what` names the schema in error messages, e.g.\n * \"event 'created'\" or \"the stateSchema\".\n */\nexport function validateSync<Input, Output>(\n schema: StandardSchemaV1<Input, Output>,\n value: unknown,\n what: string,\n): StandardSchemaV1.Result<Output> {\n const result = schema['~standard'].validate(value)\n if (result instanceof Promise) {\n throw new TypeError(\n `the schema for ${what} is asynchronous — a2 schemas must validate synchronously`,\n )\n }\n return result\n}\n\n/**\n * Probe a schema to reject always-async validators early. The probe\n * value is deliberately garbage — we only care whether the result is a\n * Promise, not whether it passes.\n */\nexport function assertSyncSchema(schema: StandardSchemaV1, what: string): void {\n const probe = Symbol('a2.schema-probe')\n const result = schema['~standard'].validate(probe)\n if (result instanceof Promise) {\n // Swallow the eventual rejection — the probe input was garbage.\n result.catch(() => {})\n throw new TypeError(\n `the schema for ${what} is asynchronous — a2 schemas must validate synchronously`,\n )\n }\n}\n","import type {\n ContractEvent,\n EventDefs,\n PresenceDefs,\n ReducerOptions,\n} from './contract.ts'\nimport { isSessionCleared, type SessionCleared } from './session-clear.ts'\nimport type { StandardSchemaV1 } from './standard-schema.ts'\nimport { validateSync } from './validate.ts'\n\nexport type StateSnapshot<S> = {\n state: S\n index: number\n /** The session's latest clear marker, once it has been cleared. */\n clearIndex?: number | undefined\n}\n\n/**\n * A fresh copy of a reducer's seed, so a fold that mutates in place\n * cannot corrupt the shared `initialState`. Non-cloneable seeds\n * (functions, class instances, proxies) fall back to the original.\n */\nexport function cloneInitial<S>(initial: S): S {\n try {\n return structuredClone(initial)\n } catch {\n return initial\n }\n}\n\nconst markerFolds = new WeakSet<object>()\n\n/**\n * Mark a fold that receives clear markers itself instead of resetting to its\n * `initialState`. The actor follower has no seed of its own; its stream carries\n * the reset state in the marker.\n */\nexport function foldsClearMarkers<\n F extends (state: never, event: never) => unknown,\n>(fold: F): F {\n markerFolds.add(fold)\n return fold\n}\n\n/** One fold step; a clear marker resets to the seed on server and client alike. */\nexport function foldEvent<D extends EventDefs, S>(\n source: Pick<Reducer<D, S>, 'fold' | 'initialState'>,\n state: S,\n event: ContractEvent<D> | SessionCleared,\n): S {\n if (!isSessionCleared(event)) return source.fold(state, event)\n return markerFolds.has(source.fold)\n ? source.fold(state, event as unknown as ContractEvent<D>)\n : cloneInitial(source.initialState)\n}\n\n/**\n * A pure fold over a session's events — `(state, event) => state` —\n * published inline in a contract or built with `reducer(options).fold(fn)`.\n *\n * The name is the reducer's identity. Cached folds (server snapshots,\n * the browser cache) are keyed by it, which makes it the invalidation\n * knob too: changed the fold's logic? Change the name. Snapshots under\n * the old name are ignored and the next read refolds from raw events.\n * See docs/contract/02-state.mdx.\n */\nexport type Reducer<\n D extends EventDefs = EventDefs,\n S = unknown,\n P extends PresenceDefs = Record<never, never>,\n> = {\n /** Identity and cache key. Rename when the fold's logic changes. */\n readonly name: string\n /** The contract's vocabulary — types every event in the fold. */\n readonly events: Readonly<D>\n /**\n * The contract's presence vocabulary — carried because the reducer\n * is the client's typed handle on the contract. The fold never sees\n * presence.\n */\n readonly presence: Readonly<P>\n readonly fold: (state: S, event: ContractEvent<D>) => S\n readonly initialState: S\n /**\n * When present: `initialState` was validated against it at\n * definition, and any cached fold a2 rehydrates (server snapshots,\n * the browser cache) is checked against it — failing snapshots are\n * discarded and refolded, catching shape drift a stale name can't.\n */\n readonly stateSchema: StandardSchemaV1<unknown, S> | undefined\n}\n\n/** The second step of `reducer` — see the method's doc. */\nexport type ReducerBuilder<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n> = {\n fold(fold: (state: S, event: ContractEvent<D>) => S): Reducer<D, S, P>\n}\n\n/** Internal — shared validation for inline and standalone reducer definitions. */\nexport function makeReducerBuilder<\n D extends EventDefs,\n P extends PresenceDefs,\n Seed,\n S,\n>(\n events: Readonly<D>,\n presence: Readonly<P>,\n options: {\n name: string\n initialState: Seed\n stateSchema?: StandardSchemaV1<Seed, S>\n },\n): ReducerBuilder<D, S, P> {\n const { name } = options\n if (typeof name !== 'string' || name.length === 0) {\n throw new TypeError(\n 'reducer name must be a non-empty string — it identifies the reducer and keys cached snapshots',\n )\n }\n\n // Without a schema, S = Seed (the type parameter default), so the\n // seed is the state. With one, the seed is the schema's input and the\n // validated output (defaults applied) becomes the fold's actual seed.\n let initialState = options.initialState as unknown as S\n if (options.stateSchema) {\n const result = validateSync(\n options.stateSchema,\n options.initialState,\n 'the stateSchema',\n )\n if (result.issues) {\n throw new TypeError(\n `reducer initialState does not match its stateSchema: ${result.issues\n .map((issue) => issue.message)\n .join('; ')}`,\n )\n }\n initialState = result.value as S\n const restored = validateSync(\n options.stateSchema,\n initialState,\n 'the stateSchema',\n )\n if (\n restored.issues ||\n JSON.stringify(restored.value) !== JSON.stringify(initialState)\n ) {\n throw new TypeError(\n 'stateSchema must accept and preserve its own output so snapshots can be restored',\n )\n }\n }\n\n return {\n fold(fold) {\n if (typeof fold !== 'function') {\n throw new TypeError('reducer fold must be a function')\n }\n return {\n name,\n events,\n presence,\n fold,\n initialState,\n stateSchema: options.stateSchema as\n StandardSchemaV1<unknown, S> | undefined,\n }\n },\n }\n}\n\nexport type RegisteredReducer<\n D extends EventDefs,\n P extends PresenceDefs,\n> = Omit<Reducer<D, unknown, P>, 'fold'> & {\n readonly fold: (state: never, event: never) => unknown\n}\n\nexport function reducer<\n D extends EventDefs,\n P extends PresenceDefs,\n Seed,\n S = Seed,\n>(\n options: ReducerOptions<Seed, S> & {\n contract: { events: Readonly<D>; presence: Readonly<P> }\n },\n): ReducerBuilder<D, S, P> {\n return makeReducerBuilder(\n options.contract.events,\n options.contract.presence,\n options,\n )\n}\n"],"mappings":";;;;;;;;;AASA,SAAgB,aACd,QACA,OACA,MACiC;CACjC,MAAM,SAAS,OAAO,YAAY,CAAC,SAAS,KAAK;CACjD,IAAI,kBAAkB,SACpB,MAAM,IAAI,UACR,kBAAkB,KAAK,0DACzB;CAEF,OAAO;AACT;;;;;;AAOA,SAAgB,iBAAiB,QAA0B,MAAoB;CAC7E,MAAM,QAAQ,OAAO,iBAAiB;CACtC,MAAM,SAAS,OAAO,YAAY,CAAC,SAAS,KAAK;CACjD,IAAI,kBAAkB,SAAS;EAE7B,OAAO,YAAY,CAAC,CAAC;EACrB,MAAM,IAAI,UACR,kBAAkB,KAAK,0DACzB;CACF;AACF;;;;;;;;AChBA,SAAgB,aAAgB,SAAe;CAC7C,IAAI;EACF,OAAO,gBAAgB,OAAO;CAChC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,MAAM,8BAAc,IAAI,QAAgB;;;;;;AAOxC,SAAgB,kBAEd,MAAY;CACZ,YAAY,IAAI,IAAI;CACpB,OAAO;AACT;;AAGA,SAAgB,UACd,QACA,OACA,OACG;CACH,IAAI,CAAC,iBAAiB,KAAK,GAAG,OAAO,OAAO,KAAK,OAAO,KAAK;CAC7D,OAAO,YAAY,IAAI,OAAO,IAAI,IAC9B,OAAO,KAAK,OAAO,KAAoC,IACvD,aAAa,OAAO,YAAY;AACtC;;AAgDA,SAAgB,mBAMd,QACA,UACA,SAKyB;CACzB,MAAM,EAAE,SAAS;CACjB,IAAI,OAAO,SAAS,YAAY,KAAK,WAAW,GAC9C,MAAM,IAAI,UACR,+FACF;CAMF,IAAI,eAAe,QAAQ;CAC3B,IAAI,QAAQ,aAAa;EACvB,MAAM,SAAS,aACb,QAAQ,aACR,QAAQ,cACR,iBACF;EACA,IAAI,OAAO,QACT,MAAM,IAAI,UACR,wDAAwD,OAAO,OAC5D,KAAK,UAAU,MAAM,OAAO,CAAC,CAC7B,KAAK,IAAI,GACd;EAEF,eAAe,OAAO;EACtB,MAAM,WAAW,aACf,QAAQ,aACR,cACA,iBACF;EACA,IACE,SAAS,UACT,KAAK,UAAU,SAAS,KAAK,MAAM,KAAK,UAAU,YAAY,GAE9D,MAAM,IAAI,UACR,kFACF;CAEJ;CAEA,OAAO,EACL,KAAK,MAAM;EACT,IAAI,OAAO,SAAS,YAClB,MAAM,IAAI,UAAU,iCAAiC;EAEvD,OAAO;GACL;GACA;GACA;GACA;GACA;GACA,aAAa,QAAQ;EAEvB;CACF,EACF;AACF;AASA,SAAgB,QAMd,SAGyB;CACzB,OAAO,mBACL,QAAQ,SAAS,QACjB,QAAQ,SAAS,UACjB,OACF;AACF"}
@@ -0,0 +1,12 @@
1
+ import { b as runtimeConsumerFactory } from "./internal-CvsrprnU.js";
2
+ //#region src/runtime-consumer.ts
3
+ /**
4
+ * `consumer()` for A2's own runtimes (actors, the AI runtime): their names
5
+ * sit under the reserved `a2.` prefix, and their child ids leave the name
6
+ * out, as the handler tables they replaced had. Not a public entry point.
7
+ */
8
+ const runtimeConsumer = ((options) => runtimeConsumerFactory.define(options));
9
+ //#endregion
10
+ export { runtimeConsumer as t };
11
+
12
+ //# sourceMappingURL=runtime-consumer-CIeL33wj.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-consumer-CIeL33wj.js","names":[],"sources":["../src/runtime-consumer.ts"],"sourcesContent":["/**\n * `consumer()` for A2's own runtimes (actors, the AI runtime): their names\n * sit under the reserved `a2.` prefix, and their child ids leave the name\n * out, as the handler tables they replaced had. Not a public entry point.\n */\nimport { runtimeConsumerFactory } from './internal.ts'\nimport type { consumer } from './server.ts'\n\nexport const runtimeConsumer = ((options: never) =>\n runtimeConsumerFactory.define!(options)) as typeof consumer\n"],"mappings":";;;;;;;AAQA,MAAa,oBAAoB,YAC/B,uBAAuB,OAAQ,OAAO"}
@@ -1,5 +1,5 @@
1
- import { r as Clock } from "./store-CyLUy86v.js";
2
- import { P as SchedulerTask, t as A2Scheduler } from "./server-DYAATFuT.js";
1
+ import { r as Clock } from "./store-D0MhBvg6.js";
2
+ import { B as SchedulerTask, t as A2Scheduler } from "./server-qgez0Xny.js";
3
3
  //#region src/scheduler-qstash.d.ts
4
4
  type QStashStoreOutage = {
5
5
  version: 1;
@@ -1,7 +1,7 @@
1
- import { i as A2Error } from "./session-clear-DGI4jTQ5.js";
2
- import { d as assertSchedulerTargets, f as classifySchedulerSendFailure, t as DRAIN_TIMINGS, v as serverInternals } from "./internal-CscSY7K_.js";
1
+ import { i as A2Error } from "./session-clear-wHd5GCpZ.js";
2
+ import { S as serverInternals, f as assertSchedulerTargets, p as classifySchedulerSendFailure, t as DRAIN_TIMINGS } from "./internal-CvsrprnU.js";
3
3
  import { n as platformVercelOidcToken } from "./platform-B4TnJtWu.js";
4
- import { n as deliverSchedulerAppend, r as deliverSchedulerClear } from "./server-qyb5aJJG.js";
4
+ import { i as deliverSchedulerClear, r as deliverSchedulerAppend } from "./server-DtFrWxi3.js";
5
5
  import { t as retryableLazy } from "./retryable-lazy-DZWmHpii.js";
6
6
  import { n as SYSTEM_CLOCK } from "./store-N8PXxDAS.js";
7
7
  import { t as normalizeSchedulerTask } from "./scheduler-task-X1SToDjP.js";
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler-task-X1SToDjP.js","names":[],"sources":["../src/scheduler-task.ts"],"sourcesContent":["/** One event carried by a delayed append task. */\nexport type ScheduledEvent = {\n id: string\n type: string\n payload: unknown\n}\n\n/** A durable wake-up for pending handler work. */\nexport type SchedulerDrainTask = {\n version: 1\n kind: 'drain'\n contract: string\n sessionId: string\n /** Earliest useful delivery as epoch milliseconds. */\n dueAt: number\n}\n\n/** A durable delayed append. */\nexport type SchedulerAppendTask = {\n version: 1\n kind: 'append'\n /** Stable identity for provider-level deduplication. */\n id: string\n contract: string\n sessionId: string\n /** Earliest useful delivery as epoch milliseconds. */\n dueAt: number\n events: readonly ScheduledEvent[]\n /** The scheduling handler's event; delivery skips once it is cleared. */\n originIndex?: number\n}\n\n/** A durable conditional clear. */\nexport type SchedulerClearTask = {\n version: 1\n kind: 'clear'\n /** Stable identity for provider-level deduplication. */\n id: string\n contract: string\n sessionId: string\n /** Earliest useful delivery as epoch milliseconds. */\n dueAt: number\n /** Clear only while this is still the head. */\n ifLastIndex: number\n}\n\n/** The versioned message protocol shared by scheduler adapters. */\nexport type SchedulerTask =\n SchedulerDrainTask | SchedulerAppendTask | SchedulerClearTask\n\nconst recordOf = (value: unknown): Record<string, unknown> | null =>\n value !== null && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null\n\nconst validName = (value: unknown): value is string =>\n typeof value === 'string' && value.length > 0\n\nconst validDueAt = (value: unknown): value is number =>\n typeof value === 'number' && Number.isInteger(value) && value % 1_000 === 0\n\nconst validIndex = (value: unknown): value is number =>\n typeof value === 'number' && Number.isSafeInteger(value) && value >= 0\n\nconst baseTask = (\n record: Record<string, unknown>,\n): { contract: string; sessionId: string } | null =>\n Object.hasOwn(record, 'contract') &&\n Object.hasOwn(record, 'sessionId') &&\n validName(record['contract']) &&\n validName(record['sessionId'])\n ? { contract: record['contract'], sessionId: record['sessionId'] }\n : null\n\n/**\n * Validate one delivered task.\n * Adapters call this only after their transport authentication has succeeded.\n */\nexport function normalizeSchedulerTask(value: unknown): SchedulerTask | null {\n const record = recordOf(value)\n if (!record) return null\n const base = baseTask(record)\n if (!base) return null\n\n if (record['version'] !== 1) return null\n if (record['kind'] === 'drain') {\n const dueAt = record['dueAt']\n if (!validDueAt(dueAt)) return null\n return { version: 1, kind: 'drain', ...base, dueAt }\n }\n if (record['kind'] === 'clear') {\n const { dueAt, id, ifLastIndex } = record\n if (!validName(id) || !validDueAt(dueAt) || !validIndex(ifLastIndex))\n return null\n return { version: 1, kind: 'clear', id, ...base, dueAt, ifLastIndex }\n }\n if (record['kind'] !== 'append') return null\n\n const { dueAt, events, id, originIndex } = record\n if (!validName(id) || !validDueAt(dueAt)) return null\n if (\n originIndex !== undefined &&\n (!validIndex(originIndex) || originIndex === 0)\n )\n return null\n if (!Array.isArray(events) || events.length === 0) return null\n const parsedEvents: ScheduledEvent[] = []\n for (const event of events) {\n const item = recordOf(event)\n if (\n !item ||\n typeof item['id'] !== 'string' ||\n typeof item['type'] !== 'string' ||\n !Object.hasOwn(item, 'payload')\n ) {\n return null\n }\n parsedEvents.push({\n id: item['id'],\n type: item['type'],\n payload: item['payload'],\n })\n }\n if (\n new Set(parsedEvents.map((event) => event.id)).size !== parsedEvents.length\n ) {\n return null\n }\n return {\n version: 1,\n kind: 'append',\n id,\n ...base,\n dueAt,\n events: parsedEvents,\n ...(originIndex === undefined ? {} : { originIndex }),\n }\n}\n"],"mappings":";AAkDA,MAAM,YAAY,UAChB,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAC9D,QACD;AAEN,MAAM,aAAa,UACjB,OAAO,UAAU,YAAY,MAAM,SAAS;AAE9C,MAAM,cAAc,UAClB,OAAO,UAAU,YAAY,OAAO,UAAU,KAAK,KAAK,QAAQ,QAAU;AAE5E,MAAM,cAAc,UAClB,OAAO,UAAU,YAAY,OAAO,cAAc,KAAK,KAAK,SAAS;AAEvE,MAAM,YACJ,WAEA,OAAO,OAAO,QAAQ,UAAU,KAChC,OAAO,OAAO,QAAQ,WAAW,KACjC,UAAU,OAAO,WAAW,KAC5B,UAAU,OAAO,YAAY,IACzB;CAAE,UAAU,OAAO;CAAa,WAAW,OAAO;AAAa,IAC/D;;;;;AAMN,SAAgB,uBAAuB,OAAsC;CAC3E,MAAM,SAAS,SAAS,KAAK;CAC7B,IAAI,CAAC,QAAQ,OAAO;CACpB,MAAM,OAAO,SAAS,MAAM;CAC5B,IAAI,CAAC,MAAM,OAAO;CAElB,IAAI,OAAO,eAAe,GAAG,OAAO;CACpC,IAAI,OAAO,YAAY,SAAS;EAC9B,MAAM,QAAQ,OAAO;EACrB,IAAI,CAAC,WAAW,KAAK,GAAG,OAAO;EAC/B,OAAO;GAAE,SAAS;GAAG,MAAM;GAAS,GAAG;GAAM;EAAM;CACrD;CACA,IAAI,OAAO,YAAY,SAAS;EAC9B,MAAM,EAAE,OAAO,IAAI,gBAAgB;EACnC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,WAAW,KAAK,KAAK,CAAC,WAAW,WAAW,GACjE,OAAO;EACT,OAAO;GAAE,SAAS;GAAG,MAAM;GAAS;GAAI,GAAG;GAAM;GAAO;EAAY;CACtE;CACA,IAAI,OAAO,YAAY,UAAU,OAAO;CAExC,MAAM,EAAE,OAAO,QAAQ,IAAI,gBAAgB;CAC3C,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,WAAW,KAAK,GAAG,OAAO;CACjD,IACE,gBAAgB,KAAA,MACf,CAAC,WAAW,WAAW,KAAK,gBAAgB,IAE7C,OAAO;CACT,IAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,OAAO,WAAW,GAAG,OAAO;CAC1D,MAAM,eAAiC,CAAC;CACxC,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,OAAO,SAAS,KAAK;EAC3B,IACE,CAAC,QACD,OAAO,KAAK,UAAU,YACtB,OAAO,KAAK,YAAY,YACxB,CAAC,OAAO,OAAO,MAAM,SAAS,GAE9B,OAAO;EAET,aAAa,KAAK;GAChB,IAAI,KAAK;GACT,MAAM,KAAK;GACX,SAAS,KAAK;EAChB,CAAC;CACH;CACA,IACE,IAAI,IAAI,aAAa,KAAK,UAAU,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,aAAa,QAErE,OAAO;CAET,OAAO;EACL,SAAS;EACT,MAAM;EACN;EACA,GAAG;EACH;EACA,QAAQ;EACR,GAAI,gBAAgB,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY;CACrD;AACF"}
1
+ {"version":3,"file":"scheduler-task-X1SToDjP.js","names":[],"sources":["../src/scheduler-task.ts"],"sourcesContent":["/** One event carried by a delayed append task. */\nexport type ScheduledEvent = {\n id: string\n type: string\n payload: unknown\n}\n\n/** A durable wake-up for pending consumer work. */\nexport type SchedulerDrainTask = {\n version: 1\n kind: 'drain'\n contract: string\n sessionId: string\n /** Earliest useful delivery as epoch milliseconds. */\n dueAt: number\n}\n\n/** A durable delayed append. */\nexport type SchedulerAppendTask = {\n version: 1\n kind: 'append'\n /** Stable identity for provider-level deduplication. */\n id: string\n contract: string\n sessionId: string\n /** Earliest useful delivery as epoch milliseconds. */\n dueAt: number\n events: readonly ScheduledEvent[]\n /** The scheduling consumer's event; delivery skips once it is cleared. */\n originIndex?: number\n}\n\n/** A durable conditional clear. */\nexport type SchedulerClearTask = {\n version: 1\n kind: 'clear'\n /** Stable identity for provider-level deduplication. */\n id: string\n contract: string\n sessionId: string\n /** Earliest useful delivery as epoch milliseconds. */\n dueAt: number\n /** Clear only while this is still the head. */\n ifLastIndex: number\n}\n\n/** The versioned message protocol shared by scheduler adapters. */\nexport type SchedulerTask =\n SchedulerDrainTask | SchedulerAppendTask | SchedulerClearTask\n\nconst recordOf = (value: unknown): Record<string, unknown> | null =>\n value !== null && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null\n\nconst validName = (value: unknown): value is string =>\n typeof value === 'string' && value.length > 0\n\nconst validDueAt = (value: unknown): value is number =>\n typeof value === 'number' && Number.isInteger(value) && value % 1_000 === 0\n\nconst validIndex = (value: unknown): value is number =>\n typeof value === 'number' && Number.isSafeInteger(value) && value >= 0\n\nconst baseTask = (\n record: Record<string, unknown>,\n): { contract: string; sessionId: string } | null =>\n Object.hasOwn(record, 'contract') &&\n Object.hasOwn(record, 'sessionId') &&\n validName(record['contract']) &&\n validName(record['sessionId'])\n ? { contract: record['contract'], sessionId: record['sessionId'] }\n : null\n\n/**\n * Validate one delivered task.\n * Adapters call this only after their transport authentication has succeeded.\n */\nexport function normalizeSchedulerTask(value: unknown): SchedulerTask | null {\n const record = recordOf(value)\n if (!record) return null\n const base = baseTask(record)\n if (!base) return null\n\n if (record['version'] !== 1) return null\n if (record['kind'] === 'drain') {\n const dueAt = record['dueAt']\n if (!validDueAt(dueAt)) return null\n return { version: 1, kind: 'drain', ...base, dueAt }\n }\n if (record['kind'] === 'clear') {\n const { dueAt, id, ifLastIndex } = record\n if (!validName(id) || !validDueAt(dueAt) || !validIndex(ifLastIndex))\n return null\n return { version: 1, kind: 'clear', id, ...base, dueAt, ifLastIndex }\n }\n if (record['kind'] !== 'append') return null\n\n const { dueAt, events, id, originIndex } = record\n if (!validName(id) || !validDueAt(dueAt)) return null\n if (\n originIndex !== undefined &&\n (!validIndex(originIndex) || originIndex === 0)\n )\n return null\n if (!Array.isArray(events) || events.length === 0) return null\n const parsedEvents: ScheduledEvent[] = []\n for (const event of events) {\n const item = recordOf(event)\n if (\n !item ||\n typeof item['id'] !== 'string' ||\n typeof item['type'] !== 'string' ||\n !Object.hasOwn(item, 'payload')\n ) {\n return null\n }\n parsedEvents.push({\n id: item['id'],\n type: item['type'],\n payload: item['payload'],\n })\n }\n if (\n new Set(parsedEvents.map((event) => event.id)).size !== parsedEvents.length\n ) {\n return null\n }\n return {\n version: 1,\n kind: 'append',\n id,\n ...base,\n dueAt,\n events: parsedEvents,\n ...(originIndex === undefined ? {} : { originIndex }),\n }\n}\n"],"mappings":";AAkDA,MAAM,YAAY,UAChB,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAC9D,QACD;AAEN,MAAM,aAAa,UACjB,OAAO,UAAU,YAAY,MAAM,SAAS;AAE9C,MAAM,cAAc,UAClB,OAAO,UAAU,YAAY,OAAO,UAAU,KAAK,KAAK,QAAQ,QAAU;AAE5E,MAAM,cAAc,UAClB,OAAO,UAAU,YAAY,OAAO,cAAc,KAAK,KAAK,SAAS;AAEvE,MAAM,YACJ,WAEA,OAAO,OAAO,QAAQ,UAAU,KAChC,OAAO,OAAO,QAAQ,WAAW,KACjC,UAAU,OAAO,WAAW,KAC5B,UAAU,OAAO,YAAY,IACzB;CAAE,UAAU,OAAO;CAAa,WAAW,OAAO;AAAa,IAC/D;;;;;AAMN,SAAgB,uBAAuB,OAAsC;CAC3E,MAAM,SAAS,SAAS,KAAK;CAC7B,IAAI,CAAC,QAAQ,OAAO;CACpB,MAAM,OAAO,SAAS,MAAM;CAC5B,IAAI,CAAC,MAAM,OAAO;CAElB,IAAI,OAAO,eAAe,GAAG,OAAO;CACpC,IAAI,OAAO,YAAY,SAAS;EAC9B,MAAM,QAAQ,OAAO;EACrB,IAAI,CAAC,WAAW,KAAK,GAAG,OAAO;EAC/B,OAAO;GAAE,SAAS;GAAG,MAAM;GAAS,GAAG;GAAM;EAAM;CACrD;CACA,IAAI,OAAO,YAAY,SAAS;EAC9B,MAAM,EAAE,OAAO,IAAI,gBAAgB;EACnC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,WAAW,KAAK,KAAK,CAAC,WAAW,WAAW,GACjE,OAAO;EACT,OAAO;GAAE,SAAS;GAAG,MAAM;GAAS;GAAI,GAAG;GAAM;GAAO;EAAY;CACtE;CACA,IAAI,OAAO,YAAY,UAAU,OAAO;CAExC,MAAM,EAAE,OAAO,QAAQ,IAAI,gBAAgB;CAC3C,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,WAAW,KAAK,GAAG,OAAO;CACjD,IACE,gBAAgB,KAAA,MACf,CAAC,WAAW,WAAW,KAAK,gBAAgB,IAE7C,OAAO;CACT,IAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,OAAO,WAAW,GAAG,OAAO;CAC1D,MAAM,eAAiC,CAAC;CACxC,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,OAAO,SAAS,KAAK;EAC3B,IACE,CAAC,QACD,OAAO,KAAK,UAAU,YACtB,OAAO,KAAK,YAAY,YACxB,CAAC,OAAO,OAAO,MAAM,SAAS,GAE9B,OAAO;EAET,aAAa,KAAK;GAChB,IAAI,KAAK;GACT,MAAM,KAAK;GACX,SAAS,KAAK;EAChB,CAAC;CACH;CACA,IACE,IAAI,IAAI,aAAa,KAAK,UAAU,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,aAAa,QAErE,OAAO;CAET,OAAO;EACL,SAAS;EACT,MAAM;EACN;EACA,GAAG;EACH;EACA,QAAQ;EACR,GAAI,gBAAgB,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY;CACrD;AACF"}
@@ -1,5 +1,5 @@
1
- import { r as Clock } from "./store-CyLUy86v.js";
2
- import { P as SchedulerTask, t as A2Scheduler } from "./server-DYAATFuT.js";
1
+ import { r as Clock } from "./store-D0MhBvg6.js";
2
+ import { B as SchedulerTask, t as A2Scheduler } from "./server-qgez0Xny.js";
3
3
  //#region src/scheduler-vercel.d.ts
4
4
  /**
5
5
  * The "not settled yet" signal for a drain whose outcome is unknown — an
@@ -1,5 +1,5 @@
1
- import { d as assertSchedulerTargets, f as classifySchedulerSendFailure, t as DRAIN_TIMINGS, v as serverInternals } from "./internal-CscSY7K_.js";
2
- import { n as deliverSchedulerAppend, r as deliverSchedulerClear } from "./server-qyb5aJJG.js";
1
+ import { S as serverInternals, f as assertSchedulerTargets, p as classifySchedulerSendFailure, t as DRAIN_TIMINGS } from "./internal-CvsrprnU.js";
2
+ import { i as deliverSchedulerClear, r as deliverSchedulerAppend } from "./server-DtFrWxi3.js";
3
3
  import { t as retryableLazy } from "./retryable-lazy-DZWmHpii.js";
4
4
  import { n as SYSTEM_CLOCK } from "./store-N8PXxDAS.js";
5
5
  import { t as normalizeSchedulerTask } from "./scheduler-task-X1SToDjP.js";
@@ -21,7 +21,7 @@ import { createHash } from "node:crypto";
21
21
  * the configured deployment scope for sending and receiving.
22
22
  * - `handler(...servers)` is the delivery route: look up the server for
23
23
  * the contract named in the message and drain it. Settled drains ack;
24
- * contention, handler failure, and a drain that throws after arming its
24
+ * contention, consumer failure, and a drain that throws after arming its
25
25
  * successor all ack once a later watchdog is durable.
26
26
  *
27
27
  * `@vercel/queue` is an optional peer dependency, imported lazily. The
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler-vercel.js","names":[],"sources":["../src/scheduler-vercel.ts"],"sourcesContent":["/**\n * experimental-a2/scheduler-vercel — queue-backed scheduler over Vercel Queues.\n *\n * `vercelQueues()` returns an `A2Scheduler` (a2-implementation.md §9):\n *\n * - drain tasks round the requested watchdog time up to a one-second slot.\n * One message per `(topic, contract, session, dueAt)` coalesces appends,\n * claim renewals and racing callbacks. `DuplicateMessageError` is\n * success because that due-time slot is already durable.\n * - append tasks hash their stable task id with the topic for provider\n * deduplication. Their six-day maximum delay leaves a full day for\n * delivery and retries inside the provider's seven-day retention.\n * Clear tasks follow the same identity and delay rules. All tasks use\n * the configured deployment scope for sending and receiving.\n * - `handler(...servers)` is the delivery route: look up the server for\n * the contract named in the message and drain it. Settled drains ack;\n * contention, handler failure, and a drain that throws after arming its\n * successor all ack once a later watchdog is durable.\n *\n * `@vercel/queue` is an optional peer dependency, imported lazily. The\n * `transport` option is the injection seam — tests run a real little\n * in-memory queue through the same interface.\n */\n\nimport { createHash } from 'node:crypto'\nimport { SYSTEM_CLOCK, type Clock } from './store.ts'\nimport {\n assertSchedulerTargets,\n classifySchedulerSendFailure,\n DRAIN_TIMINGS,\n serverInternals,\n} from './internal.ts'\nimport { retryableLazy } from './retryable-lazy.ts'\nimport {\n normalizeSchedulerTask,\n type SchedulerDrainTask,\n type SchedulerTask,\n} from './scheduler-task.ts'\nimport {\n deliverSchedulerAppend,\n deliverSchedulerClear,\n type A2Scheduler,\n type DrainableServer,\n} from './server.ts'\n\n/**\n * The \"not settled yet\" signal for a drain whose outcome is unknown — an\n * expected state, not a transport failure. The default transport classifies\n * it in `@vercel/queue`'s `retry` option and reschedules the delivery quietly\n * (`{ afterSeconds }` → visibility change → 200 response, no error log)\n * instead of letting it surface as a thrown 500. Busy and stalled deliveries\n * use a fresh watchdog instead. Custom transports should treat this class the\n * same way when their protocol has a polite reschedule.\n */\nexport class DrainUnsettled extends Error {}\n\nexport type QueueDeliveryMetadata = {\n messageId: string\n deliveryCount: number\n}\n\n/** What this module needs from a Vercel Queues-compatible transport. */\nexport type QueueTransport = {\n send(\n topic: string,\n message: SchedulerTask,\n options?: {\n delaySeconds?: number\n idempotencyKey?: string\n retentionSeconds?: number\n },\n ): Promise<unknown>\n /**\n * Wrap the delivery handler into a route. The handler throws\n * `DrainUnsettled` when the session needs redelivery — transports\n * with a polite reschedule (visibility change) should use it there\n * instead of surfacing an error; anything else thrown is a genuine\n * failure and should stay loud.\n */\n handleCallback(\n handler: (\n message: unknown,\n metadata: QueueDeliveryMetadata,\n ) => Promise<void>,\n ): (req: Request) => Promise<Response>\n /** Is this send error the idempotency-key collision? (= success) */\n isDuplicate(error: unknown): boolean\n}\n\nexport type VercelQueuesOptions = {\n /** Queue topic matching `[A-Za-z0-9_-]+`. Default `'a2'`. */\n topic?: string\n /** Queue SDK deployment scope for all tasks. Default: VERCEL_DEPLOYMENT_ID; null opts out. */\n deploymentId?: string | null\n /** Injectable transport (tests). Default: `@vercel/queue`. */\n transport?: QueueTransport\n /** Injectable clock for due-time calculations (tests). */\n clock?: Clock\n}\n\n/** Vercel Queues rejects shorter visibility changes. */\nconst MIN_VISIBILITY_SECONDS = 30\n\n/** Vercel Queues' maximum message retention; its default is a day. */\nconst MAX_RETENTION_SECONDS = 7 * 24 * 60 * 60\n\nconst TOPIC_PATTERN = /^[A-Za-z0-9_-]+$/\nconst RETRYABLE_HTTP_CLIENT_STATUSES = new Set([408, 409, 425, 429])\n/** The SDK's send path maps these statuses to name-only error instances. */\nconst TERMINAL_VERCEL_SEND_ERROR_NAMES = new Set([\n 'BadRequestError',\n 'UnauthorizedError',\n 'ForbiddenError',\n])\n\nconst recordOf = (value: unknown): Record<string, unknown> | null =>\n value !== null && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null\n\nconst httpStatusOf = (error: unknown): number | undefined => {\n try {\n const record = recordOf(error)\n if (!record) return undefined\n const response = recordOf(record['response'])\n const statuses = [\n record['status'],\n record['statusCode'],\n response?.['status'],\n ].filter(\n (value): value is number =>\n typeof value === 'number' &&\n Number.isInteger(value) &&\n value >= 100 &&\n value <= 599,\n )\n const [status] = statuses\n if (status === undefined) return undefined\n return statuses.every((candidate) => candidate === status)\n ? status\n : undefined\n } catch {\n return undefined\n }\n}\n\nconst errorNameOf = (error: unknown): string | undefined => {\n try {\n const name = recordOf(error)?.['name']\n return typeof name === 'string' ? name : undefined\n } catch {\n return undefined\n }\n}\n\nconst classifyVercelSendFailure = (error: unknown): unknown => {\n const status = httpStatusOf(error)\n const terminal =\n status === undefined\n ? TERMINAL_VERCEL_SEND_ERROR_NAMES.has(errorNameOf(error) ?? '')\n : status >= 400 &&\n status < 500 &&\n !RETRYABLE_HTTP_CLIENT_STATUSES.has(status)\n return classifySchedulerSendFailure(\n error,\n terminal ? 'terminal' : 'retryable',\n )\n}\n\nconst digestId = (parts: readonly string[]): string =>\n `a2_${createHash('sha256').update(JSON.stringify(parts)).digest('hex')}`\n\n/** Base of the unsettled redelivery backoff, doubled per delivery. */\nconst UNSETTLED_BACKOFF_BASE_SECONDS = 5\n\n/** Redelivery backoff for retry states: base, 2×, 4×… bounded. */\nconst unsettledBackoffSeconds = (deliveryCount: number): number =>\n Math.min(\n Math.max(\n UNSETTLED_BACKOFF_BASE_SECONDS * 2 ** Math.max(deliveryCount - 1, 0),\n MIN_VISIBILITY_SECONDS,\n ),\n 60,\n )\n\nconst vercelQueueTransport = async (\n clientOptions: Pick<VercelQueuesOptions, 'deploymentId'>,\n): Promise<QueueTransport> => {\n const queue = await import('@vercel/queue').catch(() => {\n throw classifySchedulerSendFailure(\n new Error(\n \"experimental-a2/scheduler-vercel needs the '@vercel/queue' package (optional peer dependency) — install it, or inject a transport\",\n ),\n 'terminal',\n )\n })\n const client = new queue.QueueClient(clientOptions)\n return {\n send: (topic, message, options) => client.send(topic, message, options),\n handleCallback: (handler) =>\n client.handleCallback(handler, {\n // A hard-killed callback normally has a fresh claim watchdog. If\n // its successor send also failed, make the old unacked delivery's\n // fallback as prompt as Vercel Queues permits.\n visibilityTimeoutSeconds: MIN_VISIBILITY_SECONDS,\n // Not-settled-yet is an expected state: reschedule the (still\n // unacked) delivery via a visibility change — 200, no error\n // log. Everything else returns undefined → the SDK's default\n // throw path, which is loud on purpose.\n retry: (error, metadata) =>\n error instanceof DrainUnsettled\n ? {\n afterSeconds: unsettledBackoffSeconds(metadata.deliveryCount),\n }\n : undefined,\n }) as (req: Request) => Promise<Response>,\n isDuplicate: (error) => error instanceof queue.DuplicateMessageError,\n }\n}\n\nexport function vercelQueues(options: VercelQueuesOptions = {}): A2Scheduler {\n const topic = options.topic ?? 'a2'\n if (typeof topic !== 'string' || !TOPIC_PATTERN.test(topic)) {\n throw new TypeError('vercelQueues() topic must match [A-Za-z0-9_-]+')\n }\n const clock = options.clock ?? SYSTEM_CLOCK\n const clientOptions =\n options.deploymentId === undefined\n ? {}\n : { deploymentId: options.deploymentId }\n\n // Lazy: constructing the scheduler value does no I/O and works without\n // the peer dep until the first task or delivery.\n const transport = retryableLazy(() =>\n options.transport\n ? Promise.resolve(options.transport)\n : vercelQueueTransport(clientOptions),\n ).get\n\n const sendTask = async (task: SchedulerTask): Promise<void> => {\n const nowMs = clock.now().getTime()\n const requestedDueAt = task.dueAt\n if (!Number.isFinite(requestedDueAt)) {\n throw classifySchedulerSendFailure(\n new TypeError('vercel scheduler dueAt must be a finite number'),\n 'terminal',\n )\n }\n const dueAt =\n requestedDueAt <= nowMs\n ? Math.floor(nowMs / 1_000) * 1_000\n : Math.ceil(requestedDueAt / 1_000) * 1_000\n const deliveryTargetAt = task.kind === 'drain' ? dueAt : requestedDueAt\n const deliveryDelaySeconds = Math.max(\n 0,\n Math.ceil((deliveryTargetAt - nowMs) / 1_000),\n )\n const message: SchedulerTask = { ...task, dueAt }\n const idempotencyKey =\n task.kind === 'drain'\n ? digestId([\n 'a2:scheduler-vercel:drain:v1',\n topic,\n task.contract,\n task.sessionId,\n String(dueAt),\n ])\n : task.kind === 'append'\n ? digestId(['a2:scheduler-vercel:append:v1', topic, task.id])\n : digestId(['a2:scheduler-vercel:clear:v1', topic, task.id])\n const t = await transport()\n try {\n await t.send(topic, message, {\n delaySeconds: deliveryDelaySeconds,\n idempotencyKey,\n retentionSeconds: MAX_RETENTION_SECONDS,\n })\n } catch (err) {\n if (t.isDuplicate(err)) return\n throw classifyVercelSendFailure(err)\n }\n }\n\n const successorDueAt = (message: SchedulerDrainTask): number => {\n const minimumDueAt =\n clock.now().getTime() +\n DRAIN_TIMINGS.claimTtlMs +\n DRAIN_TIMINGS.watchdogGraceMs\n const beats = Math.max(\n 1,\n Math.ceil(\n (minimumDueAt - message.dueAt) / DRAIN_TIMINGS.claimHeartbeatMs,\n ),\n )\n return message.dueAt + beats * DRAIN_TIMINGS.claimHeartbeatMs\n }\n\n return {\n async schedule(task) {\n await sendTask(task)\n },\n\n handler(...servers: DrainableServer[]) {\n assertSchedulerTargets(servers)\n const byContract = new Map<string, DrainableServer>()\n for (const server of servers) {\n byContract.set(server.contract.name, server)\n }\n return async (req: Request): Promise<Response> => {\n const t = await transport()\n return t.handleCallback(async (delivered) => {\n const task = normalizeSchedulerTask(delivered)\n if (!task) {\n throw new TypeError('a2 scheduler received an invalid task')\n }\n const server = byContract.get(task.contract)\n if (!server) {\n // A message for a contract this route doesn't serve is a\n // wiring bug — throw so it redelivers and stays visible in\n // queue observability instead of vanishing on an ack.\n throw new Error(\n `a2 scheduler: no server for contract '${task.contract}' — pass it to scheduler.handler(...)`,\n )\n }\n const internals = serverInternals.get(server)\n if (task.kind === 'append') {\n await deliverSchedulerAppend(server, task)\n return\n }\n if (task.kind === 'clear') {\n await deliverSchedulerClear(server, task)\n return\n }\n\n const nextDueAt = successorDueAt(task)\n const result = internals\n ? await internals.schedulerDrain(task.sessionId, {\n watchdogDueAt: nextDueAt,\n })\n : {\n ...(await server.drain(task.sessionId)),\n outcome: 'unknown' as const,\n }\n if (result.settled) return\n\n if (\n result.outcome === 'busy' ||\n result.outcome === 'stalled' ||\n result.outcome === 'failed'\n ) {\n // The live owner arms its own claim windows, the store holds\n // a failed event until its retry time, and a drain that failed\n // after claiming armed its successor. Ensure a later slot here\n // as well before relinquishing this durable delivery. If the queue is down, the\n // throw leaves the current message unacked for visibility-timeout\n // redelivery.\n if (result.schedulerArm) await result.schedulerArm\n else {\n await sendTask({\n version: 1,\n kind: 'drain',\n contract: task.contract,\n sessionId: task.sessionId,\n dueAt: nextDueAt,\n })\n }\n return\n }\n\n // A drain whose outcome is unknown keeps the current durable message\n // and uses the transport retry path.\n throw new DrainUnsettled(\n `a2 scheduler: session '${task.sessionId}' of '${task.contract}' not settled yet (${result.outcome}) — rescheduled for redelivery`,\n )\n })(req)\n }\n },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"scheduler-vercel.js","names":[],"sources":["../src/scheduler-vercel.ts"],"sourcesContent":["/**\n * experimental-a2/scheduler-vercel — queue-backed scheduler over Vercel Queues.\n *\n * `vercelQueues()` returns an `A2Scheduler` (a2-implementation.md §9):\n *\n * - drain tasks round the requested watchdog time up to a one-second slot.\n * One message per `(topic, contract, session, dueAt)` coalesces appends,\n * claim renewals and racing callbacks. `DuplicateMessageError` is\n * success because that due-time slot is already durable.\n * - append tasks hash their stable task id with the topic for provider\n * deduplication. Their six-day maximum delay leaves a full day for\n * delivery and retries inside the provider's seven-day retention.\n * Clear tasks follow the same identity and delay rules. All tasks use\n * the configured deployment scope for sending and receiving.\n * - `handler(...servers)` is the delivery route: look up the server for\n * the contract named in the message and drain it. Settled drains ack;\n * contention, consumer failure, and a drain that throws after arming its\n * successor all ack once a later watchdog is durable.\n *\n * `@vercel/queue` is an optional peer dependency, imported lazily. The\n * `transport` option is the injection seam — tests run a real little\n * in-memory queue through the same interface.\n */\n\nimport { createHash } from 'node:crypto'\nimport { SYSTEM_CLOCK, type Clock } from './store.ts'\nimport {\n assertSchedulerTargets,\n classifySchedulerSendFailure,\n DRAIN_TIMINGS,\n serverInternals,\n} from './internal.ts'\nimport { retryableLazy } from './retryable-lazy.ts'\nimport {\n normalizeSchedulerTask,\n type SchedulerDrainTask,\n type SchedulerTask,\n} from './scheduler-task.ts'\nimport {\n deliverSchedulerAppend,\n deliverSchedulerClear,\n type A2Scheduler,\n type DrainableServer,\n} from './server.ts'\n\n/**\n * The \"not settled yet\" signal for a drain whose outcome is unknown — an\n * expected state, not a transport failure. The default transport classifies\n * it in `@vercel/queue`'s `retry` option and reschedules the delivery quietly\n * (`{ afterSeconds }` → visibility change → 200 response, no error log)\n * instead of letting it surface as a thrown 500. Busy and stalled deliveries\n * use a fresh watchdog instead. Custom transports should treat this class the\n * same way when their protocol has a polite reschedule.\n */\nexport class DrainUnsettled extends Error {}\n\nexport type QueueDeliveryMetadata = {\n messageId: string\n deliveryCount: number\n}\n\n/** What this module needs from a Vercel Queues-compatible transport. */\nexport type QueueTransport = {\n send(\n topic: string,\n message: SchedulerTask,\n options?: {\n delaySeconds?: number\n idempotencyKey?: string\n retentionSeconds?: number\n },\n ): Promise<unknown>\n /**\n * Wrap the delivery handler into a route. The handler throws\n * `DrainUnsettled` when the session needs redelivery — transports\n * with a polite reschedule (visibility change) should use it there\n * instead of surfacing an error; anything else thrown is a genuine\n * failure and should stay loud.\n */\n handleCallback(\n handler: (\n message: unknown,\n metadata: QueueDeliveryMetadata,\n ) => Promise<void>,\n ): (req: Request) => Promise<Response>\n /** Is this send error the idempotency-key collision? (= success) */\n isDuplicate(error: unknown): boolean\n}\n\nexport type VercelQueuesOptions = {\n /** Queue topic matching `[A-Za-z0-9_-]+`. Default `'a2'`. */\n topic?: string\n /** Queue SDK deployment scope for all tasks. Default: VERCEL_DEPLOYMENT_ID; null opts out. */\n deploymentId?: string | null\n /** Injectable transport (tests). Default: `@vercel/queue`. */\n transport?: QueueTransport\n /** Injectable clock for due-time calculations (tests). */\n clock?: Clock\n}\n\n/** Vercel Queues rejects shorter visibility changes. */\nconst MIN_VISIBILITY_SECONDS = 30\n\n/** Vercel Queues' maximum message retention; its default is a day. */\nconst MAX_RETENTION_SECONDS = 7 * 24 * 60 * 60\n\nconst TOPIC_PATTERN = /^[A-Za-z0-9_-]+$/\nconst RETRYABLE_HTTP_CLIENT_STATUSES = new Set([408, 409, 425, 429])\n/** The SDK's send path maps these statuses to name-only error instances. */\nconst TERMINAL_VERCEL_SEND_ERROR_NAMES = new Set([\n 'BadRequestError',\n 'UnauthorizedError',\n 'ForbiddenError',\n])\n\nconst recordOf = (value: unknown): Record<string, unknown> | null =>\n value !== null && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null\n\nconst httpStatusOf = (error: unknown): number | undefined => {\n try {\n const record = recordOf(error)\n if (!record) return undefined\n const response = recordOf(record['response'])\n const statuses = [\n record['status'],\n record['statusCode'],\n response?.['status'],\n ].filter(\n (value): value is number =>\n typeof value === 'number' &&\n Number.isInteger(value) &&\n value >= 100 &&\n value <= 599,\n )\n const [status] = statuses\n if (status === undefined) return undefined\n return statuses.every((candidate) => candidate === status)\n ? status\n : undefined\n } catch {\n return undefined\n }\n}\n\nconst errorNameOf = (error: unknown): string | undefined => {\n try {\n const name = recordOf(error)?.['name']\n return typeof name === 'string' ? name : undefined\n } catch {\n return undefined\n }\n}\n\nconst classifyVercelSendFailure = (error: unknown): unknown => {\n const status = httpStatusOf(error)\n const terminal =\n status === undefined\n ? TERMINAL_VERCEL_SEND_ERROR_NAMES.has(errorNameOf(error) ?? '')\n : status >= 400 &&\n status < 500 &&\n !RETRYABLE_HTTP_CLIENT_STATUSES.has(status)\n return classifySchedulerSendFailure(\n error,\n terminal ? 'terminal' : 'retryable',\n )\n}\n\nconst digestId = (parts: readonly string[]): string =>\n `a2_${createHash('sha256').update(JSON.stringify(parts)).digest('hex')}`\n\n/** Base of the unsettled redelivery backoff, doubled per delivery. */\nconst UNSETTLED_BACKOFF_BASE_SECONDS = 5\n\n/** Redelivery backoff for retry states: base, 2×, 4×… bounded. */\nconst unsettledBackoffSeconds = (deliveryCount: number): number =>\n Math.min(\n Math.max(\n UNSETTLED_BACKOFF_BASE_SECONDS * 2 ** Math.max(deliveryCount - 1, 0),\n MIN_VISIBILITY_SECONDS,\n ),\n 60,\n )\n\nconst vercelQueueTransport = async (\n clientOptions: Pick<VercelQueuesOptions, 'deploymentId'>,\n): Promise<QueueTransport> => {\n const queue = await import('@vercel/queue').catch(() => {\n throw classifySchedulerSendFailure(\n new Error(\n \"experimental-a2/scheduler-vercel needs the '@vercel/queue' package (optional peer dependency) — install it, or inject a transport\",\n ),\n 'terminal',\n )\n })\n const client = new queue.QueueClient(clientOptions)\n return {\n send: (topic, message, options) => client.send(topic, message, options),\n handleCallback: (handler) =>\n client.handleCallback(handler, {\n // A hard-killed callback normally has a fresh claim watchdog. If\n // its successor send also failed, make the old unacked delivery's\n // fallback as prompt as Vercel Queues permits.\n visibilityTimeoutSeconds: MIN_VISIBILITY_SECONDS,\n // Not-settled-yet is an expected state: reschedule the (still\n // unacked) delivery via a visibility change — 200, no error\n // log. Everything else returns undefined → the SDK's default\n // throw path, which is loud on purpose.\n retry: (error, metadata) =>\n error instanceof DrainUnsettled\n ? {\n afterSeconds: unsettledBackoffSeconds(metadata.deliveryCount),\n }\n : undefined,\n }) as (req: Request) => Promise<Response>,\n isDuplicate: (error) => error instanceof queue.DuplicateMessageError,\n }\n}\n\nexport function vercelQueues(options: VercelQueuesOptions = {}): A2Scheduler {\n const topic = options.topic ?? 'a2'\n if (typeof topic !== 'string' || !TOPIC_PATTERN.test(topic)) {\n throw new TypeError('vercelQueues() topic must match [A-Za-z0-9_-]+')\n }\n const clock = options.clock ?? SYSTEM_CLOCK\n const clientOptions =\n options.deploymentId === undefined\n ? {}\n : { deploymentId: options.deploymentId }\n\n // Lazy: constructing the scheduler value does no I/O and works without\n // the peer dep until the first task or delivery.\n const transport = retryableLazy(() =>\n options.transport\n ? Promise.resolve(options.transport)\n : vercelQueueTransport(clientOptions),\n ).get\n\n const sendTask = async (task: SchedulerTask): Promise<void> => {\n const nowMs = clock.now().getTime()\n const requestedDueAt = task.dueAt\n if (!Number.isFinite(requestedDueAt)) {\n throw classifySchedulerSendFailure(\n new TypeError('vercel scheduler dueAt must be a finite number'),\n 'terminal',\n )\n }\n const dueAt =\n requestedDueAt <= nowMs\n ? Math.floor(nowMs / 1_000) * 1_000\n : Math.ceil(requestedDueAt / 1_000) * 1_000\n const deliveryTargetAt = task.kind === 'drain' ? dueAt : requestedDueAt\n const deliveryDelaySeconds = Math.max(\n 0,\n Math.ceil((deliveryTargetAt - nowMs) / 1_000),\n )\n const message: SchedulerTask = { ...task, dueAt }\n const idempotencyKey =\n task.kind === 'drain'\n ? digestId([\n 'a2:scheduler-vercel:drain:v1',\n topic,\n task.contract,\n task.sessionId,\n String(dueAt),\n ])\n : task.kind === 'append'\n ? digestId(['a2:scheduler-vercel:append:v1', topic, task.id])\n : digestId(['a2:scheduler-vercel:clear:v1', topic, task.id])\n const t = await transport()\n try {\n await t.send(topic, message, {\n delaySeconds: deliveryDelaySeconds,\n idempotencyKey,\n retentionSeconds: MAX_RETENTION_SECONDS,\n })\n } catch (err) {\n if (t.isDuplicate(err)) return\n throw classifyVercelSendFailure(err)\n }\n }\n\n const successorDueAt = (message: SchedulerDrainTask): number => {\n const minimumDueAt =\n clock.now().getTime() +\n DRAIN_TIMINGS.claimTtlMs +\n DRAIN_TIMINGS.watchdogGraceMs\n const beats = Math.max(\n 1,\n Math.ceil(\n (minimumDueAt - message.dueAt) / DRAIN_TIMINGS.claimHeartbeatMs,\n ),\n )\n return message.dueAt + beats * DRAIN_TIMINGS.claimHeartbeatMs\n }\n\n return {\n async schedule(task) {\n await sendTask(task)\n },\n\n handler(...servers: DrainableServer[]) {\n assertSchedulerTargets(servers)\n const byContract = new Map<string, DrainableServer>()\n for (const server of servers) {\n byContract.set(server.contract.name, server)\n }\n return async (req: Request): Promise<Response> => {\n const t = await transport()\n return t.handleCallback(async (delivered) => {\n const task = normalizeSchedulerTask(delivered)\n if (!task) {\n throw new TypeError('a2 scheduler received an invalid task')\n }\n const server = byContract.get(task.contract)\n if (!server) {\n // A message for a contract this route doesn't serve is a\n // wiring bug — throw so it redelivers and stays visible in\n // queue observability instead of vanishing on an ack.\n throw new Error(\n `a2 scheduler: no server for contract '${task.contract}' — pass it to scheduler.handler(...)`,\n )\n }\n const internals = serverInternals.get(server)\n if (task.kind === 'append') {\n await deliverSchedulerAppend(server, task)\n return\n }\n if (task.kind === 'clear') {\n await deliverSchedulerClear(server, task)\n return\n }\n\n const nextDueAt = successorDueAt(task)\n const result = internals\n ? await internals.schedulerDrain(task.sessionId, {\n watchdogDueAt: nextDueAt,\n })\n : {\n ...(await server.drain(task.sessionId)),\n outcome: 'unknown' as const,\n }\n if (result.settled) return\n\n if (\n result.outcome === 'busy' ||\n result.outcome === 'stalled' ||\n result.outcome === 'failed'\n ) {\n // The live owner arms its own claim windows, the store holds\n // a failed event until its retry time, and a drain that failed\n // after claiming armed its successor. Ensure a later slot here\n // as well before relinquishing this durable delivery. If the queue is down, the\n // throw leaves the current message unacked for visibility-timeout\n // redelivery.\n if (result.schedulerArm) await result.schedulerArm\n else {\n await sendTask({\n version: 1,\n kind: 'drain',\n contract: task.contract,\n sessionId: task.sessionId,\n dueAt: nextDueAt,\n })\n }\n return\n }\n\n // A drain whose outcome is unknown keeps the current durable message\n // and uses the transport retry path.\n throw new DrainUnsettled(\n `a2 scheduler: session '${task.sessionId}' of '${task.contract}' not settled yet (${result.outcome}) — rescheduled for redelivery`,\n )\n })(req)\n }\n },\n }\n}\n"],"mappings":"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"}