experimental-a2 0.16.1 → 0.17.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 (333) hide show
  1. package/CHANGELOG.md +352 -0
  2. package/dist/{actor-ohPC-81x.d.ts → actor-DBbe5xeE.d.ts} +21 -11
  3. package/dist/actor-DBbe5xeE.d.ts.map +1 -0
  4. package/dist/actor-client.d.ts +2 -5
  5. package/dist/actor-client.d.ts.map +1 -1
  6. package/dist/actor-client.js +13 -4
  7. package/dist/actor-client.js.map +1 -1
  8. package/dist/actor-react.d.ts +5 -3
  9. package/dist/actor-react.d.ts.map +1 -1
  10. package/dist/actor-react.js +20 -10
  11. package/dist/actor-react.js.map +1 -1
  12. package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
  13. package/dist/actor-shared-t7w8xIKP.js.map +1 -0
  14. package/dist/actor.d.ts +1 -1
  15. package/dist/actor.js +42 -23
  16. package/dist/actor.js.map +1 -1
  17. package/dist/{ai-DgOBltJ_.js → ai-UrWfKeuv.js} +621 -216
  18. package/dist/ai-UrWfKeuv.js.map +1 -0
  19. package/dist/ai-client.d.ts +27 -0
  20. package/dist/ai-client.d.ts.map +1 -0
  21. package/dist/ai-client.js +29 -0
  22. package/dist/ai-client.js.map +1 -0
  23. package/dist/ai-react.d.ts +96 -0
  24. package/dist/ai-react.d.ts.map +1 -0
  25. package/dist/ai-react.js +142 -0
  26. package/dist/ai-react.js.map +1 -0
  27. package/dist/ai-server.browser.js +2 -2
  28. package/dist/ai-server.browser.js.map +1 -1
  29. package/dist/ai-server.d.ts +80 -36
  30. package/dist/ai-server.d.ts.map +1 -1
  31. package/dist/ai-server.js +432 -258
  32. package/dist/ai-server.js.map +1 -1
  33. package/dist/{ai-CrEf6p_W.d.ts → ai-vpGtcaCB.d.ts} +114 -100
  34. package/dist/ai-vpGtcaCB.d.ts.map +1 -0
  35. package/dist/ai.d.ts +2 -2
  36. package/dist/ai.js +2 -2
  37. package/dist/{client-BYzHjkwU.js → client-COaiaDub.js} +447 -80
  38. package/dist/client-COaiaDub.js.map +1 -0
  39. package/dist/{client-CzyacQpJ.d.ts → client-qRnRUzBS.d.ts} +14 -13
  40. package/dist/client-qRnRUzBS.d.ts.map +1 -0
  41. package/dist/client.d.ts +4 -2
  42. package/dist/client.js +1 -1
  43. package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
  44. package/dist/contract-BYKASlCV.js.map +1 -0
  45. package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
  46. package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
  47. package/dist/index.d.ts +6 -5
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +4 -3
  50. package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-BrLso5x_.d.ts} +7 -3
  51. package/dist/initial-snapshot-BrLso5x_.d.ts.map +1 -0
  52. package/dist/{internal-Dq2qYxou.js → internal-CbVvEwad.js} +23 -2
  53. package/dist/internal-CbVvEwad.js.map +1 -0
  54. package/dist/otel.d.ts +1 -1
  55. package/dist/react-BbJ-g6Uu.d.ts +86 -0
  56. package/dist/react-BbJ-g6Uu.d.ts.map +1 -0
  57. package/dist/react.d.ts +2 -122
  58. package/dist/react.js +107 -100
  59. package/dist/react.js.map +1 -1
  60. package/dist/reducer-BAXc8JyF.js +87 -0
  61. package/dist/reducer-BAXc8JyF.js.map +1 -0
  62. package/dist/{reducer-otzHjuJj.d.ts → reducer-BYu6DT7h.d.ts} +45 -29
  63. package/dist/reducer-BYu6DT7h.d.ts.map +1 -0
  64. package/dist/reducer-runtime-DQwz_J59.js +28 -0
  65. package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
  66. package/dist/scheduler-qstash.d.ts +3 -3
  67. package/dist/scheduler-qstash.d.ts.map +1 -1
  68. package/dist/scheduler-qstash.js +33 -10
  69. package/dist/scheduler-qstash.js.map +1 -1
  70. package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
  71. package/dist/scheduler-task-X1SToDjP.js.map +1 -0
  72. package/dist/scheduler-vercel.d.ts +4 -9
  73. package/dist/scheduler-vercel.d.ts.map +1 -1
  74. package/dist/scheduler-vercel.js +25 -44
  75. package/dist/scheduler-vercel.js.map +1 -1
  76. package/dist/{server-DbD7IJVK.d.ts → server-CGM8WzRG.d.ts} +87 -26
  77. package/dist/server-CGM8WzRG.d.ts.map +1 -0
  78. package/dist/{server-BD5ckxZb.js → server-Rj64fNvX.js} +854 -202
  79. package/dist/server-Rj64fNvX.js.map +1 -0
  80. package/dist/server.d.ts +4 -4
  81. package/dist/server.js +2 -2
  82. package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
  83. package/dist/session-clear-DGI4jTQ5.js.map +1 -0
  84. package/dist/session-clear-Dshpuhff.d.ts +12 -0
  85. package/dist/session-clear-Dshpuhff.d.ts.map +1 -0
  86. package/dist/{store-DyZM6fS5.d.ts → store-Gpkq7WBo.d.ts} +44 -3
  87. package/dist/store-Gpkq7WBo.d.ts.map +1 -0
  88. package/dist/store-N8PXxDAS.js.map +1 -1
  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 +182 -69
  92. package/dist/store-memory.js.map +1 -1
  93. package/dist/{store-polling-CmxUbV93.js → store-polling-4_y8lETm.js} +21 -4
  94. package/dist/store-polling-4_y8lETm.js.map +1 -0
  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 +399 -108
  98. package/dist/store-postgres.js.map +1 -1
  99. package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-Q1C-k2dd.js} +3 -3
  100. package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-Q1C-k2dd.js.map} +1 -1
  101. package/dist/store-redis-http.d.ts +1 -1
  102. package/dist/store-redis-http.d.ts.map +1 -1
  103. package/dist/store-redis-http.js +293 -11
  104. package/dist/store-redis-http.js.map +1 -1
  105. package/dist/store-redis-notify-tqWNnMRu.js +2374 -0
  106. package/dist/store-redis-notify-tqWNnMRu.js.map +1 -0
  107. package/dist/store-redis.d.ts +1 -1
  108. package/dist/store-redis.d.ts.map +1 -1
  109. package/dist/store-redis.js +11 -11
  110. package/dist/store-redis.js.map +1 -1
  111. package/dist/store-sqlite.d.ts +1 -1
  112. package/dist/store-sqlite.d.ts.map +1 -1
  113. package/dist/store-sqlite.js +166 -32
  114. package/dist/store-sqlite.js.map +1 -1
  115. package/dist/stream-buffer-Cx7AAPGq.js +23 -0
  116. package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
  117. package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-cZH-cibj.d.ts} +2 -2
  118. package/dist/telemetry-cZH-cibj.d.ts.map +1 -0
  119. package/dist/{wire-BO5wWCb1.js → wire-DjfHOvdW.js} +132 -10
  120. package/dist/wire-DjfHOvdW.js.map +1 -0
  121. package/docs/01-quickstart.mdx +2 -2
  122. package/docs/actors/01-introduction.mdx +4 -0
  123. package/docs/actors/04-routes.mdx +14 -1
  124. package/docs/concepts/01-contracts.mdx +95 -0
  125. package/docs/concepts/04-state.mdx +3 -3
  126. package/docs/guides/01-timers.mdx +22 -14
  127. package/docs/guides/02-cancellation.mdx +1 -1
  128. package/docs/guides/03-react.mdx +268 -328
  129. package/docs/guides/04-local-first.mdx +32 -73
  130. package/docs/guides/05-production.mdx +40 -27
  131. package/docs/guides/06-ai-agents.mdx +671 -581
  132. package/docs/guides/09-presence.mdx +3 -7
  133. package/docs/guides/10-transports.mdx +5 -5
  134. package/docs/index.mdx +10 -18
  135. package/docs/reference/01-api.mdx +656 -194
  136. package/docs/reference/02-errors.mdx +2 -1
  137. package/examples/playground/DEPLOY.md +52 -6
  138. package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
  139. package/examples/playground/app/a0/[appId]/page.tsx +48 -0
  140. package/examples/playground/app/a0/a0.css +818 -0
  141. package/examples/playground/app/a0/app-list.tsx +211 -0
  142. package/examples/playground/app/a0/app-view.tsx +55 -0
  143. package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
  144. package/examples/playground/app/a0/apps/[appId]/route.ts +23 -0
  145. package/examples/playground/app/a0/apps.test.ts +116 -0
  146. package/examples/playground/app/a0/apps.ts +87 -0
  147. package/examples/playground/app/a0/chat.tsx +364 -0
  148. package/examples/playground/app/a0/columns.tsx +109 -0
  149. package/examples/playground/app/a0/context-menu.tsx +116 -0
  150. package/examples/playground/app/a0/cookies.ts +46 -0
  151. package/examples/playground/app/a0/crash-on-request.ts +72 -0
  152. package/examples/playground/app/a0/crashes/route.ts +3 -0
  153. package/examples/playground/app/a0/crashes.test.ts +33 -0
  154. package/examples/playground/app/a0/crashes.ts +28 -0
  155. package/examples/playground/app/a0/devtools.tsx +164 -0
  156. package/examples/playground/app/a0/events/route.ts +6 -0
  157. package/examples/playground/app/a0/flags.ts +30 -0
  158. package/examples/playground/app/a0/layout.tsx +38 -0
  159. package/examples/playground/app/a0/list/route.ts +10 -0
  160. package/examples/playground/app/a0/model.test.ts +56 -0
  161. package/examples/playground/app/a0/model.ts +58 -0
  162. package/examples/playground/app/a0/page.tsx +14 -0
  163. package/examples/playground/app/a0/preview-pane.tsx +145 -0
  164. package/examples/playground/app/a0/preview.test.ts +53 -0
  165. package/examples/playground/app/a0/preview.ts +26 -0
  166. package/examples/playground/app/a0/sandbox-log.ts +69 -0
  167. package/examples/playground/app/a0/sandbox.ts +191 -0
  168. package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
  169. package/examples/playground/app/a0/scheduler/route.ts +7 -0
  170. package/examples/playground/app/a0/server.ts +158 -0
  171. package/examples/playground/app/a0/session.ts +47 -0
  172. package/examples/playground/app/a0/shell.tsx +150 -0
  173. package/examples/playground/app/a0/short/events/route.ts +9 -0
  174. package/examples/playground/app/a0/splitter.tsx +120 -0
  175. package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
  176. package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
  177. package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
  178. package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
  179. package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
  180. package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
  181. package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
  182. package/examples/playground/app/agent/events/route.ts +5 -2
  183. package/examples/playground/app/agent/ingress.test.ts +98 -0
  184. package/examples/playground/app/agent/model-input.test.tsx +58 -0
  185. package/examples/playground/app/agent/model-input.tsx +115 -0
  186. package/examples/playground/app/agent/model.test.ts +73 -0
  187. package/examples/playground/app/agent/model.ts +58 -26
  188. package/examples/playground/app/agent/new-agent-session.tsx +37 -19
  189. package/examples/playground/app/agent/page.tsx +4 -4
  190. package/examples/playground/app/agent/server.ts +15 -14
  191. package/examples/playground/app/agent/session.ts +5 -6
  192. package/examples/playground/app/agent/telemetry.test.ts +168 -0
  193. package/examples/playground/app/agent/telemetry.ts +30 -0
  194. package/examples/playground/app/agent/trace.test.ts +23 -0
  195. package/examples/playground/app/agent/trace.ts +18 -0
  196. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +11 -11
  197. package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
  198. package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
  199. package/examples/playground/app/canvas/model.ts +53 -53
  200. package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
  201. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
  202. package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
  203. package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
  204. package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
  205. package/examples/playground/app/chat/[chatId]/session.ts +2 -19
  206. package/examples/playground/app/chat/agent/route.ts +9 -0
  207. package/examples/playground/app/chat/model.test.ts +46 -119
  208. package/examples/playground/app/chat/model.ts +82 -128
  209. package/examples/playground/app/chat/scheduler/route.ts +2 -2
  210. package/examples/playground/app/chat/server.test.ts +108 -0
  211. package/examples/playground/app/chat/server.ts +69 -73
  212. package/examples/playground/app/components/activity-feed.tsx +30 -1
  213. package/examples/playground/app/components/activity-history.test.tsx +35 -0
  214. package/examples/playground/app/components/demo-icons.tsx +89 -0
  215. package/examples/playground/app/components/demos.tsx +33 -0
  216. package/examples/playground/app/components/fast-link.test.tsx +58 -0
  217. package/examples/playground/app/components/fast-link.tsx +55 -0
  218. package/examples/playground/app/components/site-header.tsx +96 -0
  219. package/examples/playground/app/counter/counter-client.tsx +3 -3
  220. package/examples/playground/app/counter/model.test.ts +3 -3
  221. package/examples/playground/app/counter/model.ts +11 -21
  222. package/examples/playground/app/counter/page.tsx +2 -15
  223. package/examples/playground/app/counter/session.ts +2 -2
  224. package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
  225. package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
  226. package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
  227. package/examples/playground/app/documents/[documentId]/session.ts +2 -2
  228. package/examples/playground/app/documents/model.ts +27 -27
  229. package/examples/playground/app/globals.css +420 -207
  230. package/examples/playground/app/initial-state.test.tsx +54 -0
  231. package/examples/playground/app/layout.tsx +7 -23
  232. package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
  233. package/examples/playground/app/orders/[orderId]/session.ts +2 -2
  234. package/examples/playground/app/orders/model.ts +49 -49
  235. package/examples/playground/app/page.tsx +13 -102
  236. package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
  237. package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
  238. package/examples/playground/app/recovery/model.ts +30 -30
  239. package/examples/playground/instrumentation.ts +5 -0
  240. package/examples/playground/package.json +10 -2
  241. package/examples/playground/scripts/a0-template.ts +77 -0
  242. package/examples/playground/vercel.json +18 -0
  243. package/package.json +3 -1
  244. package/src/actor-client.ts +16 -4
  245. package/src/actor-react.ts +25 -11
  246. package/src/actor-shared.ts +21 -6
  247. package/src/actor.ts +76 -31
  248. package/src/ai-agent-actions.ts +219 -0
  249. package/src/ai-agent.ts +2343 -0
  250. package/src/ai-app-state.ts +91 -0
  251. package/src/ai-client-state.ts +6 -3
  252. package/src/ai-client.ts +62 -0
  253. package/src/ai-context-schema.ts +25 -5
  254. package/src/ai-context.ts +90 -40
  255. package/src/ai-control-server.ts +92 -72
  256. package/src/ai-coordinator.ts +23 -22
  257. package/src/ai-message-base.ts +209 -0
  258. package/src/ai-message-projection.ts +262 -64
  259. package/src/ai-progress-batches.ts +26 -0
  260. package/src/ai-projector.ts +95 -14
  261. package/src/ai-react.ts +146 -0
  262. package/src/ai-server.browser.ts +2 -2
  263. package/src/ai-server.ts +20 -1964
  264. package/src/ai-stored-state.ts +48 -163
  265. package/src/ai.ts +339 -155
  266. package/src/bootstrap-stream.ts +72 -0
  267. package/src/bounded-cache.ts +55 -0
  268. package/src/client-render-session.ts +49 -0
  269. package/src/client.ts +688 -151
  270. package/src/contract.ts +92 -42
  271. package/src/deterministic-id.ts +37 -0
  272. package/src/equal-json.ts +32 -0
  273. package/src/errors.ts +13 -1
  274. package/src/index.ts +5 -1
  275. package/src/initial-snapshot.ts +39 -0
  276. package/src/internal.ts +45 -0
  277. package/src/postgres-notification-scope.ts +14 -3
  278. package/src/react.ts +171 -206
  279. package/src/redis-http-reply.ts +256 -0
  280. package/src/redis-http-stream.ts +73 -0
  281. package/src/redis-http-subscriptions.ts +13 -5
  282. package/src/reducer-runtime.ts +52 -0
  283. package/src/reducer.ts +92 -10
  284. package/src/scheduler-qstash.ts +49 -20
  285. package/src/scheduler-task.ts +34 -2
  286. package/src/scheduler-vercel.ts +32 -84
  287. package/src/server-fetch.ts +139 -35
  288. package/src/server.ts +957 -237
  289. package/src/session-clear.ts +23 -0
  290. package/src/session-socket.ts +154 -49
  291. package/src/sse.ts +71 -4
  292. package/src/store-memory.ts +220 -78
  293. package/src/store-polling.ts +18 -4
  294. package/src/store-postgres.ts +461 -114
  295. package/src/store-redis-core.ts +1260 -434
  296. package/src/store-redis-http.ts +18 -2
  297. package/src/store-redis-notify.ts +260 -85
  298. package/src/store-redis.ts +19 -9
  299. package/src/store-sqlite.ts +259 -45
  300. package/src/store.ts +46 -1
  301. package/src/stream-buffer.ts +43 -0
  302. package/src/telemetry.ts +13 -1
  303. package/src/wire.ts +241 -13
  304. package/dist/actor-ohPC-81x.d.ts.map +0 -1
  305. package/dist/actor-shared-B5tJfzt-.js.map +0 -1
  306. package/dist/ai-CrEf6p_W.d.ts.map +0 -1
  307. package/dist/ai-DgOBltJ_.js.map +0 -1
  308. package/dist/client-BYzHjkwU.js.map +0 -1
  309. package/dist/client-CzyacQpJ.d.ts.map +0 -1
  310. package/dist/contract-CKRg_E4q.js.map +0 -1
  311. package/dist/errors-DCk6ch5n.js.map +0 -1
  312. package/dist/errors-DvhSXnxk.d.ts.map +0 -1
  313. package/dist/internal-Dq2qYxou.js.map +0 -1
  314. package/dist/react.d.ts.map +0 -1
  315. package/dist/reducer-DEMjEY_O.js +0 -29
  316. package/dist/reducer-DEMjEY_O.js.map +0 -1
  317. package/dist/reducer-otzHjuJj.d.ts.map +0 -1
  318. package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
  319. package/dist/server-BD5ckxZb.js.map +0 -1
  320. package/dist/server-DbD7IJVK.d.ts.map +0 -1
  321. package/dist/store-DyZM6fS5.d.ts.map +0 -1
  322. package/dist/store-polling-CmxUbV93.js.map +0 -1
  323. package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
  324. package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
  325. package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
  326. package/dist/validate-XKT4FSNn.js +0 -30
  327. package/dist/validate-XKT4FSNn.js.map +0 -1
  328. package/dist/wire-BO5wWCb1.js.map +0 -1
  329. package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
  330. package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
  331. package/examples/playground/app/agent/compaction-settings.ts +0 -49
  332. package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
  333. package/examples/playground/app/agent/compaction-timeline.ts +0 -208
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","names":[],"sources":["../src/react.ts"],"sourcesContent":["'use client'\n/**\n * experimental-a2/react — React bindings over experimental-a2/client.\n *\n * `createReact({ client })` is a factory (like createContext): call it\n * once in a `'use client'` module with a shared A2 client and export the\n * bound pair. Server components import `SessionProvider` from that module\n * as a client reference; client components import `useSession` from the\n * same file. The shared client also makes imperative pushes and provider\n * renders resolve the same live session. Everything is typed by the\n * reducer value — no type arguments, and nothing here ever touches the\n * machine module.\n *\n * The standalone `useSession(client, sessionId, options?)` is the\n * provider-less primitive for client-first apps: identity-mapped\n * handles, one leased stream per session, and an app-owned hydration\n * input — the hook holds the stream until the app hands it a\n * `{ state, index }` fold.\n */\n\nimport {\n createContext,\n createElement,\n useContext,\n useEffect,\n useMemo,\n useSyncExternalStore,\n type ReactElement,\n type ReactNode,\n} from 'react'\nimport {\n createClient,\n type A2Client,\n type Connection,\n type HistoryState,\n type LoadHistoryOptions,\n type PushResult,\n type SessionClient,\n type SessionClientPresence,\n type SessionSnapshot,\n} from './client.ts'\nimport type {\n AppendInput,\n ContractEvent,\n EventDefs,\n PresenceDefs,\n PresenceMap,\n PresencePatch,\n WithPresence,\n} from './contract.ts'\nimport type { Reducer } from './reducer.ts'\n\nexport type UseSessionOptions<S> = {\n /** Overrides the client's `participant` for this session — for apps\n * whose identity is only known at mount time. Bound once per\n * session handle. */\n participant?: string\n /**\n * The server fold this session mounts from: the `{ state, index }`\n * pair `session.state(reducer)` returns, fetched by the app through\n * its own route. Atomic on purpose — the fold and its frontier\n * travel together or not at all. While `undefined` (the app's fetch\n * has not landed), the hook holds the stream; when the value\n * arrives, the session hydrates through the ordinary re-hydration\n * seam and the stream connects at that frontier. To fold from the log's start\n * deliberately, hand the fold's true starting point:\n * `{ state: reducer.initialState, index: 0 }` — state at index 0 is\n * the reducer's seed by definition, so the explicit replay needs no\n * special vocabulary. There is no pending/ready echo on the result:\n * whether the fold has been handed over is this option — the\n * caller's own input — so the data layer's loading state IS the\n * pending state.\n */\n hydrate?: { state: S; index: number } | undefined\n}\n\nexport type SessionProviderProps<D extends EventDefs, S> = {\n /** Which session to subscribe to. */\n sessionId: string\n /** Base path of the route exposing GET (stream) and POST (push). */\n api: string\n /** The server-rendered fold — the first paint, no client JS needed. */\n initialState: S\n /** The fold's frontier — the stream resumes exactly there. */\n initialIndex: number\n /** Server-rendered event history through `initialIndex`. */\n initialEvents?: ContractEvent<D>[]\n /** Overrides the client's `participant` for this session — for apps\n * whose identity is only known at mount time. */\n participant?: string\n children?: ReactNode\n}\n\nexport type BoundSessionProviderProps<D extends EventDefs, S> = Omit<\n SessionProviderProps<D, S>,\n 'api'\n>\n\n/**\n * The presence members of the hook result — intersected in via\n * `WithPresence`, so they exist exactly when the contract declares\n * presence fields (the reducer is the client's typed handle on it).\n */\nexport type UseSessionPresence<P extends PresenceDefs> = {\n /** The replicated presence map, this client included (local echo). */\n presence: PresenceMap<P>\n /**\n * Fire-and-forget: validated locally, echoed into the map\n * immediately, coalesced on the wire to a fixed cadence (a leading\n * send immediately, then at most one merged send per interval;\n * later values win per field), stamped with the frontier at send\n * time as `seen`. Requires the provider's `participant`.\n */\n setPresence: (values: PresencePatch<P>['values']) => void\n}\n\nexport type UseSessionResult<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n> = WithPresence<P, UseSessionPresence<P>> & {\n /** The live view, folded through the shared reducer. */\n state: S\n /** The raw event feed — observed, seeded, or backscrolled. */\n events: ContractEvent<D>[]\n /** The stream frontier — `lastSeenIndex` for cancellation. */\n index: number\n /**\n * The connection, as a discriminated union — `error` exists only\n * while disconnected; \"reconnecting…\" is\n * `status === 'connecting' && reconnects > 0`.\n */\n connection: Connection\n /**\n * Typed optimistic append. Awaiting it gives the server ack;\n * `.confirmed` resolves when the live stream has delivered the batch\n * back. Rejects with the server's A2Error codes.\n */\n push: (...events: AppendInput<D>[]) => PushResult<D>\n /** Restart the live subscription without resetting its state or cursor. */\n reconnect: () => void\n /**\n * Backscroll: fetch a bounded slice of older events into `events`.\n * Defaults page backward 50 at a time from the oldest loaded event.\n * Display data only — `state` and the optimistic overlay never move.\n */\n loadHistory: (options?: LoadHistoryOptions) => Promise<ContractEvent<D>[]>\n /** Backscroll progress: `complete` means the feed reaches index 1. */\n history: HistoryState\n}\n\nexport type A2React<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n V = S,\n> = {\n SessionProvider: (props: SessionProviderProps<D, S>) => ReactElement\n useSession: () => UseSessionResult<D, V, P>\n}\n\nexport type BoundA2React<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n V = S,\n> = {\n SessionProvider: (props: BoundSessionProviderProps<D, S>) => ReactElement\n useSession: () => UseSessionResult<D, V, P>\n}\n\nexport function createReact<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n V = S,\n>(options: { client: A2Client<D, S, P, V> }): BoundA2React<D, S, P, V>\nexport function createReact<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n V = S,\n>(options: { reducer: Reducer<D, S, P, V> }): A2React<D, S, P, V>\nexport function createReact<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n V = S,\n>(\n options: { client: A2Client<D, S, P, V> } | { reducer: Reducer<D, S, P, V> },\n): BoundA2React<D, S, P, V> | A2React<D, S, P, V> {\n const Context = createContext<SessionClient<D, S, P, V> | null>(null)\n const resolveSession: (\n sessionId: string,\n api: string | undefined,\n initialState: S,\n initialIndex: number,\n initialEvents: ContractEvent<D>[] | undefined,\n participant: string | undefined,\n ) => SessionClient<D, S, P, V> =\n 'client' in options\n ? (\n sessionId,\n _api,\n initialState,\n initialIndex,\n initialEvents,\n participant,\n ) =>\n options.client.session(sessionId, {\n initialState,\n initialIndex,\n ...(initialEvents === undefined ? {} : { initialEvents }),\n ...(participant === undefined ? {} : { participant }),\n })\n : (() => {\n const clients = new Map<string, A2Client<D, S, P, V>>()\n return (\n sessionId: string,\n api: string | undefined,\n initialState: S,\n initialIndex: number,\n initialEvents: ContractEvent<D>[] | undefined,\n participant: string | undefined,\n ) => {\n if (api === undefined) {\n throw new TypeError(\n 'SessionProvider requires api when createReact binds a reducer',\n )\n }\n let client = clients.get(api)\n if (!client) {\n client = createClient({ reducer: options.reducer, api })\n clients.set(api, client)\n }\n return client.session(sessionId, {\n initialState,\n initialIndex,\n ...(initialEvents === undefined ? {} : { initialEvents }),\n ...(participant === undefined ? {} : { participant }),\n })\n }\n })()\n\n function SessionProvider(\n props: SessionProviderProps<D, S> | BoundSessionProviderProps<D, S>,\n ): ReactElement {\n const {\n sessionId,\n initialState,\n initialIndex,\n initialEvents,\n participant,\n } = props\n const api = 'api' in props ? props.api : undefined\n const client = useMemo(\n () =>\n resolveSession(\n sessionId,\n api,\n initialState,\n initialIndex,\n initialEvents,\n participant,\n ),\n [sessionId, api, initialState, initialIndex, initialEvents, participant],\n )\n useSessionLifecycle(client, true)\n return createElement(Context.Provider, { value: client }, props.children)\n }\n\n function useBoundSession(): UseSessionResult<D, V, P> {\n const client = useContext(Context)\n if (!client) {\n throw new Error(\n 'useSession must be rendered inside its matching SessionProvider',\n )\n }\n return useSessionResult(client)\n }\n\n return { SessionProvider, useSession: useBoundSession }\n}\n\n// ── the shared session lifecycle ───────────────────────────────\n\n/**\n * Hold a stream lease while mounted and `live` — the client refcounts\n * leases per handle, so however many providers and hooks mount one\n * session, the first connects and the last release closes\n * (StrictMode-safe: the lease is retaken on remount). A standalone\n * hook without a `hydrate` value passes `live: false` and holds the\n * stream — connecting an unhydrated session would replay the whole\n * log, and the library never picks the expensive path silently.\n */\nfunction useSessionLifecycle<D extends EventDefs, S, P extends PresenceDefs, V>(\n session: SessionClient<D, S, P, V>,\n live: boolean,\n): void {\n useEffect(() => {\n if (!live) return\n return session.connect()\n }, [session, live])\n}\n\nfunction useSessionResult<D extends EventDefs, S, P extends PresenceDefs, V>(\n session: SessionClient<D, S, P, V>,\n): UseSessionResult<D, V, P> {\n const snapshot: SessionSnapshot<D, V, P> = useSyncExternalStore(\n session.subscribe,\n session.getSnapshot,\n session.getSnapshot,\n )\n return useMemo(() => {\n // Runtime mirror of the WithPresence surface: the client attaches\n // setPresence (and the snapshot carries presence) exactly when the\n // reducer declares presence fields — no runtime-inert members.\n const { setPresence } = session as Partial<SessionClientPresence<P>>\n const { presence } = snapshot as Partial<{ presence: PresenceMap<P> }>\n return {\n state: snapshot.state,\n events: snapshot.events,\n index: snapshot.index,\n connection: snapshot.connection,\n push: session.push,\n reconnect: session.reconnect,\n loadHistory: session.loadHistory,\n history: snapshot.history,\n ...(setPresence && presence ? { presence, setPresence } : {}),\n } as UseSessionResult<D, V, P>\n }, [snapshot, session])\n}\n\n/**\n * The standalone, provider-less hook — for apps whose components reach\n * sessions ad hoc (a sidebar of channel sessions, a user session read\n * from a menu). Takes the shared client and a session id; the handle\n * is identity-mapped, so every hook and provider mounting the same\n * session shares one runtime and one leased stream. Hydration is\n * app-owned: pass `hydrate` when your fetch lands; until then the\n * stream is held, and your data layer's loading state is the pending\n * state.\n */\nexport function useSession<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n V = S,\n>(\n client: A2Client<D, S, P, V>,\n sessionId: string,\n options?: UseSessionOptions<S>,\n): UseSessionResult<D, V, P> {\n const { participant, hydrate } = options ?? {}\n const session = useMemo(\n () =>\n client.session(sessionId, {\n ...(hydrate === undefined\n ? {}\n : { initialState: hydrate.state, initialIndex: hydrate.index }),\n ...(participant === undefined ? {} : { participant }),\n }),\n [client, sessionId, hydrate, participant],\n )\n useSessionLifecycle(session, hydrate !== undefined)\n return useSessionResult(session)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAuLA,SAAgB,YAMd,SACgD;CAChD,MAAM,UAAU,cAAgD,IAAI;CACpE,MAAM,iBAQJ,YAAY,WAEN,WACA,MACA,cACA,cACA,eACA,gBAEA,QAAQ,OAAO,QAAQ,WAAW;EAChC;EACA;EACA,GAAI,kBAAkB,KAAA,IAAY,CAAC,IAAI,EAAE,cAAc;EACvD,GAAI,gBAAgB,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY;CACrD,CAAC,WACI;EACL,MAAM,0BAAU,IAAI,IAAkC;EACtD,QACE,WACA,KACA,cACA,cACA,eACA,gBACG;GACH,IAAI,QAAQ,KAAA,GACV,MAAM,IAAI,UACR,+DACF;GAEF,IAAI,SAAS,QAAQ,IAAI,GAAG;GAC5B,IAAI,CAAC,QAAQ;IACX,SAAS,aAAa;KAAE,SAAS,QAAQ;KAAS;IAAI,CAAC;IACvD,QAAQ,IAAI,KAAK,MAAM;GACzB;GACA,OAAO,OAAO,QAAQ,WAAW;IAC/B;IACA;IACA,GAAI,kBAAkB,KAAA,IAAY,CAAC,IAAI,EAAE,cAAc;IACvD,GAAI,gBAAgB,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY;GACrD,CAAC;EACH;CACF,EAAA,CAAG;CAET,SAAS,gBACP,OACc;EACd,MAAM,EACJ,WACA,cACA,cACA,eACA,gBACE;EACJ,MAAM,MAAM,SAAS,QAAQ,MAAM,MAAM,KAAA;EACzC,MAAM,SAAS,cAEX,eACE,WACA,KACA,cACA,cACA,eACA,WACF,GACF;GAAC;GAAW;GAAK;GAAc;GAAc;GAAe;EAAW,CACzE;EACA,oBAAoB,QAAQ,IAAI;EAChC,OAAO,cAAc,QAAQ,UAAU,EAAE,OAAO,OAAO,GAAG,MAAM,QAAQ;CAC1E;CAEA,SAAS,kBAA6C;EACpD,MAAM,SAAS,WAAW,OAAO;EACjC,IAAI,CAAC,QACH,MAAM,IAAI,MACR,iEACF;EAEF,OAAO,iBAAiB,MAAM;CAChC;CAEA,OAAO;EAAE;EAAiB,YAAY;CAAgB;AACxD;;;;;;;;;;AAaA,SAAS,oBACP,SACA,MACM;CACN,gBAAgB;EACd,IAAI,CAAC,MAAM;EACX,OAAO,QAAQ,QAAQ;CACzB,GAAG,CAAC,SAAS,IAAI,CAAC;AACpB;AAEA,SAAS,iBACP,SAC2B;CAC3B,MAAM,WAAqC,qBACzC,QAAQ,WACR,QAAQ,aACR,QAAQ,WACV;CACA,OAAO,cAAc;EAInB,MAAM,EAAE,gBAAgB;EACxB,MAAM,EAAE,aAAa;EACrB,OAAO;GACL,OAAO,SAAS;GAChB,QAAQ,SAAS;GACjB,OAAO,SAAS;GAChB,YAAY,SAAS;GACrB,MAAM,QAAQ;GACd,WAAW,QAAQ;GACnB,aAAa,QAAQ;GACrB,SAAS,SAAS;GAClB,GAAI,eAAe,WAAW;IAAE;IAAU;GAAY,IAAI,CAAC;EAC7D;CACF,GAAG,CAAC,UAAU,OAAO,CAAC;AACxB;;;;;;;;;;;AAYA,SAAgB,WAMd,QACA,WACA,SAC2B;CAC3B,MAAM,EAAE,aAAa,YAAY,WAAW,CAAC;CAC7C,MAAM,UAAU,cAEZ,OAAO,QAAQ,WAAW;EACxB,GAAI,YAAY,KAAA,IACZ,CAAC,IACD;GAAE,cAAc,QAAQ;GAAO,cAAc,QAAQ;EAAM;EAC/D,GAAI,gBAAgB,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY;CACrD,CAAC,GACH;EAAC;EAAQ;EAAW;EAAS;CAAW,CAC1C;CACA,oBAAoB,SAAS,YAAY,KAAA,CAAS;CAClD,OAAO,iBAAiB,OAAO;AACjC"}
1
+ {"version":3,"file":"react.js","names":[],"sources":["../src/react.ts"],"sourcesContent":["'use client'\nimport {\n createContext,\n createElement,\n useContext,\n use,\n useEffect,\n useMemo,\n useSyncExternalStore,\n type ReactElement,\n type ReactNode,\n} from 'react'\nimport {\n createClient,\n type A2Client,\n type Connection,\n type HistoryState,\n type LoadHistoryOptions,\n type PushResult,\n type SessionClient,\n type SessionClientPresence,\n type SessionOptions,\n} from './client.ts'\nimport type {\n AppendInput,\n Contract,\n ContractEvent,\n EventDefs,\n PresenceDefs,\n PresenceMap,\n PresencePatch,\n WithPresence,\n} from './contract.ts'\nimport type { Reducer } from './reducer.ts'\nimport {\n createRenderSession,\n resolveInitialForReact,\n} from './client-render-session.ts'\nimport { isInitialPromise, type InitialSnapshot } from './initial-snapshot.ts'\n\nexport type UseSessionOptions<S> = {\n participant?: string\n initial?: InitialSnapshot<S>\n enabled?: boolean\n suspend?: boolean\n}\n\nexport type SessionProviderProps<D extends EventDefs, S> = {\n sessionId: string\n api: string\n initial?: InitialSnapshot<S>\n initialEvents?: ContractEvent<D>[]\n participant?: string\n children?: ReactNode\n}\n\nexport type BoundSessionProviderProps<D extends EventDefs, S> = Omit<\n SessionProviderProps<D, S>,\n 'api'\n>\n\nexport type UseSessionPresence<P extends PresenceDefs> = {\n /** The replicated presence map, this client included (local echo). */\n presence: PresenceMap<P>\n /**\n * Fire-and-forget: validated locally, echoed into the map\n * immediately, coalesced on the wire to a fixed cadence (a leading\n * send immediately, then at most one merged send per interval;\n * later values win per field), stamped with the frontier at send\n * time as `seen`. Requires the provider's `participant`.\n */\n setPresence: (values: PresencePatch<P>['values']) => void\n}\n\nexport type UseSessionResult<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n> = WithPresence<P, UseSessionPresence<P>> & {\n /** The live view, folded through the shared reducer. */\n state: S\n /** A state/index snapshot has been applied; independent of connection status. */\n hydrated: boolean\n /** The raw event feed — observed, seeded, or backscrolled. */\n events: ContractEvent<D>[]\n /** The stream frontier — `lastSeenIndex` for cancellation. */\n index: number\n /**\n * The connection, as a discriminated union — `error` exists only\n * while disconnected; \"reconnecting…\" is\n * `status === 'connecting' && reconnects > 0`.\n */\n connection: Connection\n /**\n * Typed optimistic append. Awaiting it gives the server ack;\n * `.confirmed` resolves when the live stream has delivered the batch\n * back. Rejects with the server's A2Error codes.\n */\n push: (...events: AppendInput<D>[]) => PushResult<D>\n /** Restart the live subscription without resetting its state or cursor. */\n reconnect: () => void\n /**\n * Backscroll: fetch a bounded slice of older events into `events`.\n * Defaults page backward 50 at a time from the oldest loaded event.\n * Display data only — `state` and the optimistic overlay never move.\n */\n loadHistory: (options?: LoadHistoryOptions) => Promise<ContractEvent<D>[]>\n /** Backscroll progress: `complete` means the feed reaches index 1. */\n history: HistoryState\n}\n\nexport type BoundSessionOptions<\n D extends EventDefs,\n S,\n P extends PresenceDefs,\n> = UseSessionOptions<S> & {\n sessionId?: string\n client?: A2Client<D, S, P>\n}\n\nexport type A2React<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n> = {\n SessionProvider: (props: SessionProviderProps<D, S>) => ReactElement\n useSession: (\n options?: BoundSessionOptions<D, S, P>,\n ) => UseSessionResult<D, S, P>\n}\n\nexport type BoundA2React<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n> = {\n SessionProvider: (props: BoundSessionProviderProps<D, S>) => ReactElement\n useSession: (\n options?: BoundSessionOptions<D, S, P>,\n ) => UseSessionResult<D, S, P>\n}\n\nexport function createReact<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n>(options: { client: A2Client<D, S, P> }): BoundA2React<D, S, P>\nexport function createReact<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n>(options: { contract: Contract<D, P, Reducer<D, S, P>> }): A2React<D, S, P>\nexport function createReact<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n>(\n options:\n | { client: A2Client<D, S, P> }\n | { contract: Contract<D, P, Reducer<D, S, P>> },\n): BoundA2React<D, S, P> | A2React<D, S, P> {\n type Scope = {\n client: A2Client<D, S, P>\n sessionId: string\n settings: SessionOptions<D, S>\n }\n const Context = createContext<Scope | null>(null)\n const clients = new Map<string, A2Client<D, S, P>>()\n const resolveClient = (api: string | undefined): A2Client<D, S, P> => {\n if ('client' in options) return options.client\n if (api === undefined)\n throw new TypeError(\n 'SessionProvider requires api when createReact binds a contract',\n )\n let client = clients.get(api)\n if (!client) {\n client = createClient({ contract: options.contract, api })\n clients.set(api, client)\n }\n return client\n }\n\n function SessionProvider(\n props: SessionProviderProps<D, S> | BoundSessionProviderProps<D, S>,\n ): ReactElement {\n const { sessionId, initial, initialEvents, participant } = props\n const api = 'api' in props ? props.api : undefined\n const scope = useMemo(\n () => ({\n client: resolveClient(api),\n sessionId,\n settings: {\n ...(initial === undefined ? {} : { initial }),\n ...(initialEvents === undefined ? {} : { initialEvents }),\n ...(participant === undefined ? {} : { participant }),\n },\n }),\n [sessionId, api, initial, initialEvents, participant],\n )\n return createElement(Context.Provider, { value: scope }, props.children)\n }\n\n function useBoundSession(\n overrides: BoundSessionOptions<D, S, P> = {},\n ): UseSessionResult<D, S, P> {\n const scope = useContext(Context)\n const client =\n overrides.client ??\n scope?.client ??\n ('client' in options ? options.client : undefined)\n const sessionId = overrides.sessionId ?? scope?.sessionId\n if (!client || sessionId === undefined)\n throw new TypeError(\n 'provide client and sessionId through SessionProvider or hook options',\n )\n const inherits = client === scope?.client && sessionId === scope?.sessionId\n const inherited = inherits ? scope.settings : undefined\n const { initial, participant } = overrides\n const settings = useMemo(\n () => ({\n ...inherited,\n ...(initial === undefined ? {} : { initial }),\n ...(participant === undefined ? {} : { participant }),\n }),\n [inherited, initial, participant],\n )\n return useLiveSession({\n client,\n sessionId,\n settings,\n enabled: overrides.enabled ?? true,\n suspend: overrides.suspend ?? false,\n })\n }\n\n return { SessionProvider, useSession: useBoundSession }\n}\n\nfunction useLiveSession<\n D extends EventDefs,\n S,\n P extends PresenceDefs,\n>(options: {\n client: A2Client<D, S, P>\n sessionId: string\n settings: SessionOptions<D, S>\n enabled: boolean\n suspend: boolean\n}): UseSessionResult<D, S, P> {\n const { client, sessionId, settings, enabled, suspend } = options\n const browserSession = useMemo(() => {\n if (typeof window === 'undefined') {\n if (isInitialPromise(settings.initial))\n void settings.initial.catch(() => {})\n return undefined\n }\n return client.session(sessionId, settings)\n }, [client, sessionId, settings])\n const promise = isInitialPromise(settings.initial)\n ? settings.initial\n : undefined\n const resolved = promise && suspend ? use(promise) : undefined\n const renderInitial = promise ? resolved : settings.initial\n const renderSession = useMemo(() => {\n let session: SessionClient<D, S, P> | undefined\n return () => {\n session ??= createRenderSession({\n client,\n sessionId,\n settings: { ...settings, initial: renderInitial },\n })\n return session\n }\n }, [client, sessionId, settings, renderInitial])\n const session = browserSession ?? renderSession()\n if (browserSession && promise && suspend)\n resolveInitialForReact({\n client,\n session: browserSession,\n source: promise,\n value: resolved,\n })\n const serverSnapshot = useMemo(\n () => () => renderSession().getSnapshot(),\n [renderSession],\n )\n const snapshot = useSyncExternalStore(\n session.subscribe,\n session.getSnapshot,\n serverSnapshot,\n )\n useEffect(() => {\n if (enabled) return session.connect()\n }, [session, enabled])\n return useMemo(() => {\n const { setPresence } = session as Partial<SessionClientPresence<P>>\n const { presence } = snapshot as Partial<{ presence: PresenceMap<P> }>\n return {\n ...snapshot,\n push: session.push,\n reconnect: session.reconnect,\n loadHistory: session.loadHistory,\n ...(setPresence && presence ? { presence, setPresence } : {}),\n } as UseSessionResult<D, S, P>\n }, [session, snapshot])\n}\n\nexport function useSession<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n>(\n client: A2Client<D, S, P>,\n sessionId: string,\n options: UseSessionOptions<NoInfer<S>> = {},\n): UseSessionResult<D, S, P> {\n const { initial, participant } = options\n const settings = useMemo(\n () => ({\n ...(initial === undefined ? {} : { initial }),\n ...(participant === undefined ? {} : { participant }),\n }),\n [initial, participant],\n )\n return useLiveSession({\n client,\n sessionId,\n settings,\n enabled: options.enabled ?? true,\n suspend: options.suspend ?? false,\n })\n}\n"],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ import { n as isSessionCleared } from "./session-clear-DGI4jTQ5.js";
2
+ //#region src/validate.ts
3
+ /**
4
+ * Run a Standard Schema validator, requiring a synchronous result.
5
+ * Validators must be synchronous (specs/a2-api.md §1) — an async one is
6
+ * a definition bug, rejected at definition time where possible and here
7
+ * as a backstop. `what` names the schema in error messages, e.g.
8
+ * "event 'created'" or "the stateSchema".
9
+ */
10
+ function validateSync(schema, value, what) {
11
+ const result = schema["~standard"].validate(value);
12
+ if (result instanceof Promise) throw new TypeError(`the schema for ${what} is asynchronous — a2 schemas must validate synchronously`);
13
+ return result;
14
+ }
15
+ /**
16
+ * Probe a schema to reject always-async validators early. The probe
17
+ * value is deliberately garbage — we only care whether the result is a
18
+ * Promise, not whether it passes.
19
+ */
20
+ function assertSyncSchema(schema, what) {
21
+ const probe = Symbol("a2.schema-probe");
22
+ const result = schema["~standard"].validate(probe);
23
+ if (result instanceof Promise) {
24
+ result.catch(() => {});
25
+ throw new TypeError(`the schema for ${what} is asynchronous — a2 schemas must validate synchronously`);
26
+ }
27
+ }
28
+ //#endregion
29
+ //#region src/reducer.ts
30
+ /**
31
+ * A fresh copy of a reducer's seed, so a fold that mutates in place
32
+ * cannot corrupt the shared `initialState`. Non-cloneable seeds
33
+ * (functions, class instances, proxies) fall back to the original.
34
+ */
35
+ function cloneInitial(initial) {
36
+ try {
37
+ return structuredClone(initial);
38
+ } catch {
39
+ return initial;
40
+ }
41
+ }
42
+ const markerFolds = /* @__PURE__ */ new WeakSet();
43
+ /**
44
+ * Mark a fold that receives clear markers itself instead of resetting to its
45
+ * `initialState`. The actor follower has no seed of its own; its stream carries
46
+ * the reset state in the marker.
47
+ */
48
+ function foldsClearMarkers(fold) {
49
+ markerFolds.add(fold);
50
+ return fold;
51
+ }
52
+ /** One fold step; a clear marker resets to the seed on server and client alike. */
53
+ function foldEvent(source, state, event) {
54
+ if (!isSessionCleared(event)) return source.fold(state, event);
55
+ return markerFolds.has(source.fold) ? source.fold(state, event) : cloneInitial(source.initialState);
56
+ }
57
+ /** Internal — shared validation for inline and standalone reducer definitions. */
58
+ function makeReducerBuilder(events, presence, options) {
59
+ const { name } = options;
60
+ if (typeof name !== "string" || name.length === 0) throw new TypeError("reducer name must be a non-empty string — it identifies the reducer and keys cached snapshots");
61
+ let initialState = options.initialState;
62
+ if (options.stateSchema) {
63
+ const result = validateSync(options.stateSchema, options.initialState, "the stateSchema");
64
+ if (result.issues) throw new TypeError(`reducer initialState does not match its stateSchema: ${result.issues.map((issue) => issue.message).join("; ")}`);
65
+ initialState = result.value;
66
+ const restored = validateSync(options.stateSchema, initialState, "the stateSchema");
67
+ if (restored.issues || JSON.stringify(restored.value) !== JSON.stringify(initialState)) throw new TypeError("stateSchema must accept and preserve its own output so snapshots can be restored");
68
+ }
69
+ return { fold(fold) {
70
+ if (typeof fold !== "function") throw new TypeError("reducer fold must be a function");
71
+ return {
72
+ name,
73
+ events,
74
+ presence,
75
+ fold,
76
+ initialState,
77
+ stateSchema: options.stateSchema
78
+ };
79
+ } };
80
+ }
81
+ function reducer(options) {
82
+ return makeReducerBuilder(options.contract.events, options.contract.presence, options);
83
+ }
84
+ //#endregion
85
+ export { reducer as a, makeReducerBuilder as i, foldEvent as n, assertSyncSchema as o, foldsClearMarkers as r, validateSync as s, cloneInitial as t };
86
+
87
+ //# sourceMappingURL=reducer-BAXc8JyF.js.map
@@ -0,0 +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/concepts/04-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"}
@@ -139,7 +139,7 @@ type ReducerOptions<Seed, S> = {
139
139
  */
140
140
  stateSchema?: StandardSchemaV1<Seed, S>;
141
141
  };
142
- type Contract<D extends EventDefs = EventDefs, P extends PresenceDefs = Record<never, never>> = {
142
+ type Contract<D extends EventDefs = EventDefs, P extends PresenceDefs = Record<never, never>, R = RegisteredReducer<D, P> | undefined> = {
143
143
  /** Identity — prefixes storage keys and queue messages. */
144
144
  readonly name: string;
145
145
  /** The vocabulary. */
@@ -148,41 +148,50 @@ type Contract<D extends EventDefs = EventDefs, P extends PresenceDefs = Record<n
148
148
  readonly presence: Readonly<P>;
149
149
  /** Preserve literal types for a handler-returned event batch. */
150
150
  readonly batch: EventBatchFactory<D>;
151
- /**
152
- * Derive a view from this contract. The reducer's `name` is its
153
- * identity and snapshot cache key: rename it when the fold's logic
154
- * changes. Two steps by design: this call fixes the state and event
155
- * types, so `.fold()` receives fully concrete types — literal unions
156
- * in your state survive, with zero annotations. (A single call can't
157
- * do this: TypeScript never contextually types the returns of a
158
- * context-sensitive argument against generics inferred in the same
159
- * call.)
160
- */
161
- reducer<Seed, S = Seed>(options: ReducerOptions<Seed, S>): ReducerBuilder<D, S, P>;
151
+ readonly reducer: R;
162
152
  };
163
153
  /**
164
154
  * Define a contract: a name plus the events it understands. Validators
165
155
  * must be synchronous — async ones are rejected here, at definition
166
156
  * time. The result is a plain, importable, isomorphic value.
167
157
  */
168
- declare function contract<D extends EventDefs, P extends PresenceDefs = Record<never, never>>(options: {
169
- /** Identity — prefixes storage keys and queue messages. */
158
+ type ContractOptions<D extends EventDefs, P extends PresenceDefs> = {
170
159
  name: string;
171
- /** The vocabulary: event name → payload schema (Standard Schema). */
172
160
  events: D;
173
- /**
174
- * Optional second vocabulary: presence field → value schema.
175
- * Per-participant ephemeral state, replicated last-writer-wins and
176
- * never stored in the log — reducers cannot see it.
177
- */
178
161
  presence?: P;
179
- }): Contract<D, P>;
162
+ };
163
+ declare function contract<D extends EventDefs, P extends PresenceDefs = Record<never, never>, Schema extends StandardSchemaV1 = StandardSchemaV1>(options: ContractOptions<D, P> & {
164
+ reducer: {
165
+ name: string;
166
+ initialState: StandardSchemaV1.InferInput<NoInfer<Schema>>;
167
+ stateSchema: Schema;
168
+ fold: (state: StandardSchemaV1.InferOutput<Schema>, event: ContractEvent<NoInfer<D>>) => StandardSchemaV1.InferOutput<Schema>;
169
+ };
170
+ }): Contract<D, P, Reducer<D, StandardSchemaV1.InferOutput<Schema>, P>>;
171
+ declare function contract<D extends EventDefs, P extends PresenceDefs = Record<never, never>, S = unknown>(options: ContractOptions<D, P> & {
172
+ reducer: {
173
+ name: string;
174
+ initialState: S;
175
+ fold: (state: NoInfer<S>, event: ContractEvent<NoInfer<D>>) => NoInfer<S>;
176
+ };
177
+ }): Contract<D, P, Reducer<D, S, P>>;
178
+ declare function contract<D extends EventDefs, P extends PresenceDefs = Record<never, never>, R extends RegisteredReducer<D, P> = RegisteredReducer<D, P>>(options: ContractOptions<D, P> & {
179
+ reducer: R;
180
+ }): Contract<D, P, R>;
181
+ declare function contract<D extends EventDefs, P extends PresenceDefs = Record<never, never>>(options: ContractOptions<D, P> & {
182
+ reducer?: undefined;
183
+ }): Contract<D, P, undefined>;
180
184
  //#endregion
181
185
  //#region src/reducer.d.ts
182
- declare const clientStateProjection: unique symbol;
186
+ type StateSnapshot<S> = {
187
+ state: S;
188
+ index: number;
189
+ /** The session's latest clear marker, once it has been cleared. */
190
+ clearIndex?: number | undefined;
191
+ };
183
192
  /**
184
193
  * A pure fold over a session's events — `(state, event) => state` —
185
- * derived from a contract via `contract.reducer(options).fold(fn)`.
194
+ * published inline in a contract or built with `reducer(options).fold(fn)`.
186
195
  *
187
196
  * The name is the reducer's identity. Cached folds (server snapshots,
188
197
  * the browser cache) are keyed by it, which makes it the invalidation
@@ -190,7 +199,7 @@ declare const clientStateProjection: unique symbol;
190
199
  * the old name are ignored and the next read refolds from raw events.
191
200
  * See docs/concepts/04-state.mdx.
192
201
  */
193
- type Reducer<D extends EventDefs = EventDefs, S = unknown, P extends PresenceDefs = Record<never, never>, V = S> = {
202
+ type Reducer<D extends EventDefs = EventDefs, S = unknown, P extends PresenceDefs = Record<never, never>> = {
194
203
  /** Identity and cache key. Rename when the fold's logic changes. */
195
204
  readonly name: string;
196
205
  /** The contract's vocabulary — types every event in the fold. */
@@ -203,8 +212,6 @@ type Reducer<D extends EventDefs = EventDefs, S = unknown, P extends PresenceDef
203
212
  readonly presence: Readonly<P>;
204
213
  readonly fold: (state: S, event: ContractEvent<D>) => S;
205
214
  readonly initialState: S;
206
- /** Internal read-side projection. Never used as a fold input or persisted. */
207
- readonly [clientStateProjection]?: (state: S) => V;
208
215
  /**
209
216
  * When present: `initialState` was validated against it at
210
217
  * definition, and any cached fold a2 rehydrates (server snapshots,
@@ -213,10 +220,19 @@ type Reducer<D extends EventDefs = EventDefs, S = unknown, P extends PresenceDef
213
220
  */
214
221
  readonly stateSchema: StandardSchemaV1<unknown, S> | undefined;
215
222
  };
216
- /** The second step of `contract.reducer` — see the method's doc. */
223
+ /** The second step of `reducer` — see the method's doc. */
217
224
  type ReducerBuilder<D extends EventDefs, S, P extends PresenceDefs = Record<never, never>> = {
218
225
  fold(fold: (state: S, event: ContractEvent<D>) => S): Reducer<D, S, P>;
219
226
  };
227
+ type RegisteredReducer<D extends EventDefs, P extends PresenceDefs> = Omit<Reducer<D, unknown, P>, "fold"> & {
228
+ readonly fold: (state: never, event: never) => unknown;
229
+ };
230
+ declare function reducer<D extends EventDefs, P extends PresenceDefs, Seed, S = Seed>(options: ReducerOptions<Seed, S> & {
231
+ contract: {
232
+ events: Readonly<D>;
233
+ presence: Readonly<P>;
234
+ };
235
+ }): ReducerBuilder<D, S, P>;
220
236
  //#endregion
221
- export { ContractEvent as a, PresenceDefs as c, PresenceSnapshot as d, ReducerOptions as f, StandardSchemaV1 as h, Contract as i, PresenceMap as l, contract as m, ReducerBuilder as n, EventBatchFactory as o, WithPresence as p, AppendInput as r, EventDefs as s, Reducer as t, PresencePatch as u };
222
- //# sourceMappingURL=reducer-otzHjuJj.d.ts.map
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-BYu6DT7h.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reducer-BYu6DT7h.d.ts","names":[],"sources":["../src/standard-schema.ts","../src/contract.ts","../src/reducer.ts"],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ //#region src/reducer-runtime.ts
2
+ const runtimeKey = Symbol("a2.reducerRuntime");
3
+ function withReducerRuntime(options) {
4
+ const { reducer, ...runtime } = options;
5
+ return {
6
+ ...reducer,
7
+ [runtimeKey]: {
8
+ ...reducer[runtimeKey],
9
+ ...runtime
10
+ }
11
+ };
12
+ }
13
+ function encodeState(options) {
14
+ const storage = options.reducer[runtimeKey]?.storage;
15
+ return storage ? storage.encode(options.state) : options.state;
16
+ }
17
+ function decodeState(options) {
18
+ const storage = options.reducer[runtimeKey]?.storage;
19
+ return storage ? storage.decode(options.state) : options.state;
20
+ }
21
+ function projectClientState(options) {
22
+ const project = options.reducer[runtimeKey]?.project;
23
+ return project ? project(options.state) : options.state;
24
+ }
25
+ //#endregion
26
+ export { withReducerRuntime as i, encodeState as n, projectClientState as r, decodeState as t };
27
+
28
+ //# sourceMappingURL=reducer-runtime-DQwz_J59.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reducer-runtime-DQwz_J59.js","names":[],"sources":["../src/reducer-runtime.ts"],"sourcesContent":["const runtimeKey: unique symbol = Symbol('a2.reducerRuntime')\n\ntype StateStorage<S> = {\n encode(state: S): unknown\n decode(stored: unknown): S\n}\n\ntype ReducerRuntime<S> = {\n storage?: StateStorage<S>\n project?: (state: S) => S\n}\n\ntype Carrier = { [runtimeKey]?: ReducerRuntime<unknown> }\n\nexport function withReducerRuntime<R extends object, S>(options: {\n reducer: R\n storage?: StateStorage<S>\n project?: (state: S) => S\n}): R {\n const { reducer, ...runtime } = options\n return {\n ...reducer,\n [runtimeKey]: {\n ...(reducer as Carrier)[runtimeKey],\n ...runtime,\n },\n }\n}\n\nexport function encodeState(options: {\n reducer: object\n state: unknown\n}): unknown {\n const storage = (options.reducer as Carrier)[runtimeKey]?.storage\n return storage ? storage.encode(options.state) : options.state\n}\n\nexport function decodeState(options: {\n reducer: object\n state: unknown\n}): unknown {\n const storage = (options.reducer as Carrier)[runtimeKey]?.storage\n return storage ? storage.decode(options.state) : options.state\n}\n\nexport function projectClientState<S>(options: {\n reducer: object\n state: S\n}): S {\n const project = (options.reducer as Carrier)[runtimeKey]?.project\n return project ? (project(options.state) as S) : options.state\n}\n"],"mappings":";AAAA,MAAM,aAA4B,OAAO,mBAAmB;AAc5D,SAAgB,mBAAwC,SAIlD;CACJ,MAAM,EAAE,SAAS,GAAG,YAAY;CAChC,OAAO;EACL,GAAG;GACF,aAAa;GACZ,GAAI,QAAoB;GACxB,GAAG;EACL;CACF;AACF;AAEA,SAAgB,YAAY,SAGhB;CACV,MAAM,UAAW,QAAQ,QAAoB,WAAW,EAAE;CAC1D,OAAO,UAAU,QAAQ,OAAO,QAAQ,KAAK,IAAI,QAAQ;AAC3D;AAEA,SAAgB,YAAY,SAGhB;CACV,MAAM,UAAW,QAAQ,QAAoB,WAAW,EAAE;CAC1D,OAAO,UAAU,QAAQ,OAAO,QAAQ,KAAK,IAAI,QAAQ;AAC3D;AAEA,SAAgB,mBAAsB,SAGhC;CACJ,MAAM,UAAW,QAAQ,QAAoB,WAAW,EAAE;CAC1D,OAAO,UAAW,QAAQ,QAAQ,KAAK,IAAU,QAAQ;AAC3D"}
@@ -1,11 +1,11 @@
1
- import { r as Clock } from "./store-DyZM6fS5.js";
2
- import { E as SchedulerTask, t as A2Scheduler } from "./server-DbD7IJVK.js";
1
+ import { r as Clock } from "./store-Gpkq7WBo.js";
2
+ import { A as SchedulerTask, t as A2Scheduler } from "./server-CGM8WzRG.js";
3
3
  //#region src/scheduler-qstash.d.ts
4
4
  type QStashStoreOutage = {
5
5
  version: 1;
6
6
  /** Zero is the first successor created for a store outage. */
7
7
  generation: number;
8
- /** The first failing drain task's due time. Append identity lives on the task. */
8
+ /** The first failing drain task's due time. Append and clear identity lives on the task. */
9
9
  rootDueAt?: number;
10
10
  };
11
11
  type QStashMessage = SchedulerTask & {
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler-qstash.d.ts","names":[],"sources":["../src/scheduler-qstash.ts"],"mappings":";;;KAmCK;EACH;;EAEA;;EAEA;;KAGG,gBAAgB;;EAEnB,YAAY;;KAGF;EACV;EACA,MAAM;;EAEN,UAAU,SAAS;;EAEnB;IACE;;;EAGF;EACA;;EAEA;;;KAIU,6BAA6B,KACvC;EAGA,UAAU;EACV;IACE;;;;KAKQ;EACV,YAAY,SAAS,6BAA6B;;KAGxC;EACV;;EAEA;;EAEA;EACA;;;KAIU;EACV,QAAQ,SAAS,uBAAuB;EACxC,OAAO,SAAS,sBAAsB;;KAG5B;;EAEV;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA,UAAU,SAAS;;EAEnB,SAAS;;EAET,YAAY;;EAEZ,QAAQ;;iBA4eM,OAAO,UAAS,yBAA8B"}
1
+ {"version":3,"file":"scheduler-qstash.d.ts","names":[],"sources":["../src/scheduler-qstash.ts"],"mappings":";;;KAqCK;EACH;;EAEA;;EAEA;;KAGG,gBAAgB;;EAEnB,YAAY;;KAGF;EACV;EACA,MAAM;;EAEN,UAAU,SAAS;;EAEnB;IACE;;;EAGF;EACA;;EAEA;;;KAIU,6BAA6B,KACvC;EAGA,UAAU;EACV;IACE;;;;KAKQ;EACV,YAAY,SAAS,6BAA6B;;KAGxC;EACV;;EAEA;;EAEA;EACA;;;KAIU;EACV,QAAQ,SAAS,uBAAuB;EACxC,OAAO,SAAS,sBAAsB;;KAG5B;;EAEV;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA,UAAU,SAAS;;EAEnB,SAAS;;EAET,YAAY;;EAEZ,QAAQ;;iBA4eM,OAAO,UAAS,yBAA8B"}
@@ -1,17 +1,18 @@
1
- import { t as A2Error } from "./errors-DCk6ch5n.js";
2
- import { d as classifySchedulerSendFailure, g as serverInternals, t as DRAIN_TIMINGS, u as assertSchedulerTargets } from "./internal-Dq2qYxou.js";
1
+ import { i as A2Error } from "./session-clear-DGI4jTQ5.js";
2
+ import { _ as serverInternals, d as assertSchedulerTargets, f as classifySchedulerSendFailure, t as DRAIN_TIMINGS } from "./internal-CbVvEwad.js";
3
+ import { n as deliverSchedulerAppend, r as deliverSchedulerClear } from "./server-Rj64fNvX.js";
3
4
  import { n as platformVercelOidcToken } from "./platform-B4TnJtWu.js";
4
- import { n as deliverSchedulerAppend } from "./server-BD5ckxZb.js";
5
5
  import { t as retryableLazy } from "./retryable-lazy-DZWmHpii.js";
6
6
  import { n as SYSTEM_CLOCK } from "./store-N8PXxDAS.js";
7
- import { t as normalizeSchedulerTask } from "./scheduler-task-BpzhPnRS.js";
7
+ import { t as normalizeSchedulerTask } from "./scheduler-task-X1SToDjP.js";
8
8
  //#region src/scheduler-qstash.ts
9
9
  /**
10
10
  * experimental-a2/scheduler-qstash — HTTP scheduler over Upstash QStash.
11
11
  *
12
12
  * QStash publishes one signed HTTP delivery per scheduler task. Drain tasks
13
13
  * use an absolute, whole-second `notBefore` and a fixed-size deduplication id;
14
- * append tasks hash their stable task id with the callback URL.
14
+ * append and clear tasks hash their stable task id with the callback URL, each
15
+ * under its own version tag.
15
16
  * Store-outage successors carry a signed private generation and back off to
16
17
  * one day without deriving their deduplication identity from the local clock.
17
18
  * `handler(...servers)` verifies the signature over the raw request body
@@ -308,6 +309,14 @@ function qstash(options = {}) {
308
309
  const clock = options.clock ?? SYSTEM_CLOCK;
309
310
  const transport = retryableLazy(() => options.transport ? Promise.resolve(options.transport) : qstashTransport(options, developmentMode)).get;
310
311
  const storeOutageDeduplicationId = (task, outage) => {
312
+ if (task.kind === "clear") return digestId([
313
+ "a2:scheduler-qstash:store-outage:clear:v1",
314
+ url,
315
+ task.contract,
316
+ task.sessionId,
317
+ task.id,
318
+ outage.generation
319
+ ]);
311
320
  if (task.kind === "append") return digestId([
312
321
  "a2:scheduler-qstash:store-outage:append:v1",
313
322
  url,
@@ -339,7 +348,16 @@ function qstash(options = {}) {
339
348
  contract: task.contract,
340
349
  sessionId: task.sessionId,
341
350
  dueAt,
342
- events: task.events
351
+ events: task.events,
352
+ ...task.originIndex === void 0 ? {} : { originIndex: task.originIndex }
353
+ } : task.kind === "clear" ? {
354
+ version: 1,
355
+ kind: "clear",
356
+ id: task.id,
357
+ contract: task.contract,
358
+ sessionId: task.sessionId,
359
+ dueAt,
360
+ ifLastIndex: task.ifLastIndex
343
361
  } : {
344
362
  version: 1,
345
363
  kind: "drain",
@@ -353,7 +371,7 @@ function qstash(options = {}) {
353
371
  };
354
372
  const sizeCheckedMessage = outage === void 0 ? {
355
373
  ...normalizedTask,
356
- _a2QStash: normalizedTask.kind === "append" ? {
374
+ _a2QStash: normalizedTask.kind !== "drain" ? {
357
375
  version: 1,
358
376
  generation: Number.MAX_SAFE_INTEGER
359
377
  } : {
@@ -373,6 +391,10 @@ function qstash(options = {}) {
373
391
  "a2:scheduler-qstash:append:v1",
374
392
  url,
375
393
  task.id
394
+ ]) : task.kind === "clear" ? await digestId([
395
+ "a2:scheduler-qstash:clear:v1",
396
+ url,
397
+ task.id
376
398
  ]) : await digestId([
377
399
  "a2:scheduler-qstash:v1",
378
400
  url,
@@ -401,7 +423,7 @@ function qstash(options = {}) {
401
423
  const delayMs = Math.min(STORE_OUTAGE_MAX_DELAY_MS, STORE_OUTAGE_BASE_DELAY_MS * 4 ** Math.min(generation, 6));
402
424
  const nowMs = clock.now().getTime();
403
425
  const dueAt = Math.ceil((nowMs + delayMs) / 1e3) * 1e3;
404
- const nextOutage = task.kind === "append" ? {
426
+ const nextOutage = task.kind !== "drain" ? {
405
427
  version: 1,
406
428
  generation
407
429
  } : {
@@ -450,9 +472,10 @@ function qstash(options = {}) {
450
472
  const server = byContract.get(task.contract);
451
473
  if (!server) throw new Error(`a2 scheduler: no server for contract '${task.contract}' — pass it to scheduler.handler(...)`);
452
474
  const internals = serverInternals.get(server);
453
- if (task.kind === "append") {
475
+ if (task.kind !== "drain") {
454
476
  try {
455
- await deliverSchedulerAppend(server, task);
477
+ if (task.kind === "append") await deliverSchedulerAppend(server, task);
478
+ else await deliverSchedulerClear(server, task);
456
479
  } catch (error) {
457
480
  if (!storeUnavailable(error)) throw error;
458
481
  await sendStoreOutageSuccessor(task, outage);