experimental-a2 0.16.0 → 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 (334) hide show
  1. package/CHANGELOG.md +360 -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-CrLYwNEx.js → ai-UrWfKeuv.js} +966 -541
  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 +466 -265
  32. package/dist/ai-server.js.map +1 -1
  33. package/dist/{ai-HJ9fHfYI.d.ts → ai-vpGtcaCB.d.ts} +115 -101
  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 +680 -580
  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 +662 -195
  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 +39 -2
  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 +94 -55
  256. package/src/ai-control.ts +2 -0
  257. package/src/ai-coordinator.ts +23 -22
  258. package/src/ai-message-base.ts +209 -0
  259. package/src/ai-message-projection.ts +382 -58
  260. package/src/ai-progress-batches.ts +26 -0
  261. package/src/ai-projector.ts +95 -14
  262. package/src/ai-react.ts +146 -0
  263. package/src/ai-server.browser.ts +2 -2
  264. package/src/ai-server.ts +20 -1951
  265. package/src/ai-stored-state.ts +48 -163
  266. package/src/ai.ts +343 -276
  267. package/src/bootstrap-stream.ts +72 -0
  268. package/src/bounded-cache.ts +55 -0
  269. package/src/client-render-session.ts +49 -0
  270. package/src/client.ts +688 -151
  271. package/src/contract.ts +92 -42
  272. package/src/deterministic-id.ts +37 -0
  273. package/src/equal-json.ts +32 -0
  274. package/src/errors.ts +13 -1
  275. package/src/index.ts +5 -1
  276. package/src/initial-snapshot.ts +39 -0
  277. package/src/internal.ts +45 -0
  278. package/src/postgres-notification-scope.ts +14 -3
  279. package/src/react.ts +171 -206
  280. package/src/redis-http-reply.ts +256 -0
  281. package/src/redis-http-stream.ts +73 -0
  282. package/src/redis-http-subscriptions.ts +13 -5
  283. package/src/reducer-runtime.ts +52 -0
  284. package/src/reducer.ts +92 -10
  285. package/src/scheduler-qstash.ts +49 -20
  286. package/src/scheduler-task.ts +34 -2
  287. package/src/scheduler-vercel.ts +32 -84
  288. package/src/server-fetch.ts +139 -35
  289. package/src/server.ts +957 -237
  290. package/src/session-clear.ts +23 -0
  291. package/src/session-socket.ts +154 -49
  292. package/src/sse.ts +71 -4
  293. package/src/store-memory.ts +220 -78
  294. package/src/store-polling.ts +18 -4
  295. package/src/store-postgres.ts +461 -114
  296. package/src/store-redis-core.ts +1260 -434
  297. package/src/store-redis-http.ts +18 -2
  298. package/src/store-redis-notify.ts +260 -85
  299. package/src/store-redis.ts +19 -9
  300. package/src/store-sqlite.ts +259 -45
  301. package/src/store.ts +46 -1
  302. package/src/stream-buffer.ts +43 -0
  303. package/src/telemetry.ts +13 -1
  304. package/src/wire.ts +241 -13
  305. package/dist/actor-ohPC-81x.d.ts.map +0 -1
  306. package/dist/actor-shared-B5tJfzt-.js.map +0 -1
  307. package/dist/ai-CrLYwNEx.js.map +0 -1
  308. package/dist/ai-HJ9fHfYI.d.ts.map +0 -1
  309. package/dist/client-BYzHjkwU.js.map +0 -1
  310. package/dist/client-CzyacQpJ.d.ts.map +0 -1
  311. package/dist/contract-CKRg_E4q.js.map +0 -1
  312. package/dist/errors-DCk6ch5n.js.map +0 -1
  313. package/dist/errors-DvhSXnxk.d.ts.map +0 -1
  314. package/dist/internal-Dq2qYxou.js.map +0 -1
  315. package/dist/react.d.ts.map +0 -1
  316. package/dist/reducer-DEMjEY_O.js +0 -29
  317. package/dist/reducer-DEMjEY_O.js.map +0 -1
  318. package/dist/reducer-otzHjuJj.d.ts.map +0 -1
  319. package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
  320. package/dist/server-BD5ckxZb.js.map +0 -1
  321. package/dist/server-DbD7IJVK.d.ts.map +0 -1
  322. package/dist/store-DyZM6fS5.d.ts.map +0 -1
  323. package/dist/store-polling-CmxUbV93.js.map +0 -1
  324. package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
  325. package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
  326. package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
  327. package/dist/validate-XKT4FSNn.js +0 -30
  328. package/dist/validate-XKT4FSNn.js.map +0 -1
  329. package/dist/wire-BO5wWCb1.js.map +0 -1
  330. package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
  331. package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
  332. package/examples/playground/app/agent/compaction-settings.ts +0 -49
  333. package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
  334. package/examples/playground/app/agent/compaction-timeline.ts +0 -208
@@ -1,7 +1,10 @@
1
1
  "use client";
2
- import { t as createClient } from "./client-BYzHjkwU.js";
2
+ import { n as isSessionCleared } from "./session-clear-DGI4jTQ5.js";
3
+ import { r as foldsClearMarkers } from "./reducer-BAXc8JyF.js";
4
+ import { n as internalContract } from "./contract-BYKASlCV.js";
5
+ import { t as createClient } from "./client-COaiaDub.js";
3
6
  import { useSession } from "./react.js";
4
- import { l as openPresenceDefs } from "./actor-shared-B5tJfzt-.js";
7
+ import { l as openPresenceDefs } from "./actor-shared-t7w8xIKP.js";
5
8
  import { createActorClient } from "./actor-client.js";
6
9
  import { useMemo } from "react";
7
10
  //#region src/actor-react.ts
@@ -43,10 +46,16 @@ const followerReducer = {
43
46
  name: "a2.actor.follower.v1",
44
47
  events: {},
45
48
  presence: openPresenceDefs,
46
- fold: (state, event) => event.type === "a2.actor.state" ? event.payload.state : state,
49
+ fold: foldsClearMarkers((state, event) => event.type === "a2.actor.state" || isSessionCleared(event) ? event.payload?.state : state),
47
50
  initialState: void 0,
48
51
  stateSchema: void 0
49
52
  };
53
+ const followerContract = internalContract({
54
+ name: "a2.actor.follower",
55
+ events: followerReducer.events,
56
+ presence: followerReducer.presence,
57
+ reducer: followerReducer
58
+ });
50
59
  const clients = /* @__PURE__ */ new Map();
51
60
  /** Use one actor instance: live state plus typed calls. No provider. */
52
61
  function useActor(options) {
@@ -54,22 +63,23 @@ function useActor(options) {
54
63
  let client = clients.get(api);
55
64
  if (!client) {
56
65
  client = createClient({
57
- reducer: followerReducer,
66
+ contract: followerContract,
58
67
  api
59
68
  });
60
69
  clients.set(api, client);
61
70
  }
62
71
  const session = useSession(client, options.id, {
63
- hydrate: options.initial,
72
+ initial: options.initial,
64
73
  ...options.participant === void 0 ? {} : { participant: options.participant }
65
74
  });
66
75
  const { call, state: refresh } = useMemo(() => createActorClient({
67
76
  api,
68
- onSnapshot: ({ state, index }) => {
69
- client.session(options.id, {
70
- initialState: state,
71
- initialIndex: index
72
- });
77
+ onSnapshot: ({ state, index, clearIndex }) => {
78
+ client.session(options.id, { initial: {
79
+ state,
80
+ index,
81
+ clearIndex
82
+ } });
73
83
  }
74
84
  }), [
75
85
  api,
@@ -1 +1 @@
1
- {"version":3,"file":"actor-react.js","names":[],"sources":["../src/actor-react.ts"],"sourcesContent":["'use client'\n/**\n * experimental-a2/actor/react — use an actor from React: live state\n * plus typed calls, one hook. A thin wrapper: the state pump follows\n * the SSE that `handle.fetch` serves (frontier resume, reconnection,\n * heartbeat watchdog — both ends library code), and `call` is\n * `createActorClient`'s typed surface (the framework-agnostic\n * primitive for everything that isn't React).\n *\n * ```tsx\n * // server component\n * const initial = await vault.actor(id).state()\n * // client component — typed by the server module, erased at build\n * const { state, call, connection } = useActor<typeof vault>({\n * api: `/vault/${id}`, // your route: auth, then handle.fetch\n * id,\n * initial,\n * })\n * await call.withdraw({ amount: 60 }) // ActorRefusedError on refusal\n * ```\n *\n * The fold is library code (last write wins over state commits), so no\n * reducers, schemas, or contracts appear in browser code. Live, not\n * optimistic: handlers are server code deciding against serialized\n * fresh state, so a local guess would be wrong exactly when the actor\n * matters. On a view-projected mount, pass the projected shape as the\n * second type argument — `useActor<typeof vault, PublicVault>` — and\n * `initial`, `state`, and call answers all carry it.\n */\n\nimport { ACTOR_STATE_EVENT, openPresenceDefs } from './actor-shared.ts'\nimport type { ActorPresenceMap, ActorPresenceValues } from './actor-shared.ts'\nimport {\n createActorClient,\n type ActorClientCall,\n type ActorStateOf,\n type AnyActorDefinition,\n} from './actor-client.ts'\nimport { createClient } from './client.ts'\nimport type { A2Client, Connection } from './client.ts'\nimport type { ContractEvent, EventDefs, PresenceDefs } from './contract.ts'\nimport type { Reducer } from './reducer.ts'\nimport { useSession } from './react.ts'\nimport { useMemo } from 'react'\n\n/** The server-to-client handoff: `await def.actor(id).state()`. */\nexport type ActorSnapshot<S> = { state: S; index: number }\n\nexport type UseActorOptions<View> = {\n /** The route whose handler delegates to `handle.fetch` for this instance. */\n api: string\n /** The instance id. */\n id: string\n /** The server-rendered fold — the stream resumes after `index`. */\n initial: ActorSnapshot<View>\n /** This client's presence identity — required to call `setPresence`. */\n participant?: string\n}\n\n/** The definition's presence vocabulary, `never` when undeclared. */\ntype PresenceOf<D extends AnyActorDefinition> = D extends {\n protocol?: infer P\n}\n ? NonNullable<P> extends { presence: infer Values extends object }\n ? Values\n : never\n : never\n\nexport type UseActorResult<D extends AnyActorDefinition, View> = {\n /** The live view, advanced by call answers, refreshes, and stream commits. */\n state: View\n /** The latest adopted state index. */\n index: number\n connection: Connection\n /** Typed calls over POST — the response is the answer. */\n call: ActorClientCall<D, View>\n refresh: () => Promise<ActorSnapshot<View>>\n /** The state-commit feed this browser has observed. */\n events: ContractEvent<EventDefs>[]\n /**\n * The replicated presence map: participant → field → latest value.\n * Peer-authored — render values like user input. Empty (and\n * `setPresence` untyped-callable-with-nothing) when the protocol\n * declares no presence.\n */\n presence: ActorPresenceMap<PresenceOf<D>>\n /**\n * Announce this client's presence fields (throttled, resent on\n * reconnect; `null` clears a field). Requires `participant`.\n */\n setPresence: (values: ActorPresenceValues<PresenceOf<D>>) => void\n}\n\n/**\n * The follower fold, untyped on purpose: the state type comes from the\n * caller's `initial` handoff, and the fold never inspects state.\n * `initialState` is never rendered — `initial` is required, so every\n * session hydrates before it connects.\n */\nconst followerReducer: Reducer<EventDefs, unknown, PresenceDefs> = {\n name: 'a2.actor.follower.v1',\n events: {},\n // The open vocabulary ('*' catch-all): the browser cannot know a\n // server-defined field list, so the server stays the authority and\n // the protocol types honest callers at compile time.\n presence: openPresenceDefs,\n fold: (state, event) =>\n event.type === ACTOR_STATE_EVENT\n ? (event.payload as { state: unknown }).state\n : state,\n initialState: undefined,\n stateSchema: undefined,\n}\n\nconst clients = new Map<string, A2Client<EventDefs, unknown, PresenceDefs>>()\n\n/** Use one actor instance: live state plus typed calls. No provider. */\nexport function useActor<D extends AnyActorDefinition, View = ActorStateOf<D>>(\n options: UseActorOptions<View>,\n): UseActorResult<D, View> {\n const { api } = options\n let client = clients.get(api)\n if (!client) {\n client = createClient({ reducer: followerReducer, api })\n clients.set(api, client)\n }\n const session = useSession(client, options.id, {\n hydrate: options.initial,\n ...(options.participant === undefined\n ? {}\n : { participant: options.participant }),\n })\n const { call, state: refresh } = useMemo(\n () =>\n createActorClient<D, View>({\n api,\n onSnapshot: ({ state, index }) => {\n client.session(options.id, {\n initialState: state,\n initialIndex: index,\n })\n },\n }),\n [api, client, options.id],\n )\n return {\n state: session.state as View,\n index: session.index,\n connection: session.connection,\n call,\n refresh,\n events: session.events,\n presence: session.presence as ActorPresenceMap<PresenceOf<D>>,\n setPresence: session.setPresence as (\n values: ActorPresenceValues<PresenceOf<D>>,\n ) => void,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmGA,MAAM,kBAA6D;CACjE,MAAM;CACN,QAAQ,CAAC;CAIT,UAAU;CACV,OAAO,OAAO,UACZ,MAAM,SAAA,mBACD,MAAM,QAA+B,QACtC;CACN,cAAc,KAAA;CACd,aAAa,KAAA;AACf;AAEA,MAAM,0BAAU,IAAI,IAAwD;;AAG5E,SAAgB,SACd,SACyB;CACzB,MAAM,EAAE,QAAQ;CAChB,IAAI,SAAS,QAAQ,IAAI,GAAG;CAC5B,IAAI,CAAC,QAAQ;EACX,SAAS,aAAa;GAAE,SAAS;GAAiB;EAAI,CAAC;EACvD,QAAQ,IAAI,KAAK,MAAM;CACzB;CACA,MAAM,UAAU,WAAW,QAAQ,QAAQ,IAAI;EAC7C,SAAS,QAAQ;EACjB,GAAI,QAAQ,gBAAgB,KAAA,IACxB,CAAC,IACD,EAAE,aAAa,QAAQ,YAAY;CACzC,CAAC;CACD,MAAM,EAAE,MAAM,OAAO,YAAY,cAE7B,kBAA2B;EACzB;EACA,aAAa,EAAE,OAAO,YAAY;GAChC,OAAO,QAAQ,QAAQ,IAAI;IACzB,cAAc;IACd,cAAc;GAChB,CAAC;EACH;CACF,CAAC,GACH;EAAC;EAAK;EAAQ,QAAQ;CAAE,CAC1B;CACA,OAAO;EACL,OAAO,QAAQ;EACf,OAAO,QAAQ;EACf,YAAY,QAAQ;EACpB;EACA;EACA,QAAQ,QAAQ;EAChB,UAAU,QAAQ;EAClB,aAAa,QAAQ;CAGvB;AACF"}
1
+ {"version":3,"file":"actor-react.js","names":["contract"],"sources":["../src/actor-react.ts"],"sourcesContent":["'use client'\n/**\n * experimental-a2/actor/react — use an actor from React: live state\n * plus typed calls, one hook. A thin wrapper: the state pump follows\n * the SSE that `handle.fetch` serves (frontier resume, reconnection,\n * heartbeat watchdog — both ends library code), and `call` is\n * `createActorClient`'s typed surface (the framework-agnostic\n * primitive for everything that isn't React).\n *\n * ```tsx\n * // server component\n * const initial = await vault.actor(id).state()\n * // client component — typed by the server module, erased at build\n * const { state, call, connection } = useActor<typeof vault>({\n * api: `/vault/${id}`, // your route: auth, then handle.fetch\n * id,\n * initial,\n * })\n * await call.withdraw({ amount: 60 }) // ActorRefusedError on refusal\n * ```\n *\n * The fold is library code (last write wins over state commits), so no\n * reducers, schemas, or contracts appear in browser code. Live, not\n * optimistic: handlers are server code deciding against serialized\n * fresh state, so a local guess would be wrong exactly when the actor\n * matters. On a view-projected mount, pass the projected shape as the\n * second type argument — `useActor<typeof vault, PublicVault>` — and\n * `initial`, `state`, and call answers all carry it.\n */\n\nimport { ACTOR_STATE_EVENT, openPresenceDefs } from './actor-shared.ts'\nimport type { ActorPresenceMap, ActorPresenceValues } from './actor-shared.ts'\nimport {\n createActorClient,\n type ActorClientCall,\n type ActorStateOf,\n type AnyActorDefinition,\n} from './actor-client.ts'\nimport { createClient } from './client.ts'\nimport { foldsClearMarkers } from './reducer.ts'\nimport { isSessionCleared } from './session-clear.ts'\nimport type { A2Client, Connection } from './client.ts'\nimport type { ContractEvent, EventDefs, PresenceDefs } from './contract.ts'\nimport { internalContract as contract } from './contract.ts'\nimport type { Reducer } from './reducer.ts'\nimport { useSession } from './react.ts'\nimport { useMemo } from 'react'\n\n/** The server-to-client handoff: `await def.actor(id).state()`. */\nexport type ActorSnapshot<S> = {\n state: S\n index: number\n /** The latest clear marker, once the actor has been cleared. */\n clearIndex?: number\n}\n\nexport type UseActorOptions<View> = {\n /** The route whose handler delegates to `handle.fetch` for this instance. */\n api: string\n /** The instance id. */\n id: string\n /** The server-rendered fold — the stream resumes after `index`. */\n initial: ActorSnapshot<View>\n /** This client's presence identity — required to call `setPresence`. */\n participant?: string\n}\n\n/** The definition's presence vocabulary, `never` when undeclared. */\ntype PresenceOf<D extends AnyActorDefinition> = D extends {\n protocol?: infer P\n}\n ? NonNullable<P> extends { presence: infer Values extends object }\n ? Values\n : never\n : never\n\nexport type UseActorResult<D extends AnyActorDefinition, View> = {\n /** The live view, advanced by call answers, refreshes, and stream commits. */\n state: View\n /** The latest adopted state index. */\n index: number\n connection: Connection\n /** Typed calls over POST — the response is the answer. */\n call: ActorClientCall<D, View>\n refresh: () => Promise<ActorSnapshot<View>>\n /** The state-commit feed this browser has observed. */\n events: ContractEvent<EventDefs>[]\n /**\n * The replicated presence map: participant → field → latest value.\n * Peer-authored — render values like user input. Empty (and\n * `setPresence` untyped-callable-with-nothing) when the protocol\n * declares no presence.\n */\n presence: ActorPresenceMap<PresenceOf<D>>\n /**\n * Announce this client's presence fields (throttled, resent on\n * reconnect; `null` clears a field). Requires `participant`.\n */\n setPresence: (values: ActorPresenceValues<PresenceOf<D>>) => void\n}\n\n/**\n * The follower fold, untyped on purpose: the state type comes from the\n * caller's `initial` handoff, and the fold never inspects state.\n * `initialState` is never rendered — `initial` is required, so every\n * session hydrates before it connects.\n */\nconst followerReducer: Reducer<EventDefs, unknown, PresenceDefs> = {\n name: 'a2.actor.follower.v1',\n events: {},\n // The open vocabulary ('*' catch-all): the browser cannot know a\n // server-defined field list, so the server stays the authority and\n // the protocol types honest callers at compile time.\n presence: openPresenceDefs,\n // A clear marker from the actor route carries the actor's initial state.\n fold: foldsClearMarkers((state: unknown, event: ContractEvent<EventDefs>) =>\n event.type === ACTOR_STATE_EVENT || isSessionCleared(event)\n ? (event.payload as { state?: unknown } | null)?.state\n : state,\n ),\n initialState: undefined,\n stateSchema: undefined,\n}\n\nconst followerContract = contract({\n name: 'a2.actor.follower',\n events: followerReducer.events,\n presence: followerReducer.presence,\n reducer: followerReducer,\n})\n\nconst clients = new Map<string, A2Client<EventDefs, unknown, PresenceDefs>>()\n\n/** Use one actor instance: live state plus typed calls. No provider. */\nexport function useActor<D extends AnyActorDefinition, View = ActorStateOf<D>>(\n options: UseActorOptions<View>,\n): UseActorResult<D, View> {\n const { api } = options\n let client = clients.get(api)\n if (!client) {\n client = createClient({ contract: followerContract, api })\n clients.set(api, client)\n }\n const session = useSession(client, options.id, {\n initial: options.initial,\n ...(options.participant === undefined\n ? {}\n : { participant: options.participant }),\n })\n const { call, state: refresh } = useMemo(\n () =>\n createActorClient<D, View>({\n api,\n onSnapshot: ({ state, index, clearIndex }) => {\n client.session(options.id, { initial: { state, index, clearIndex } })\n },\n }),\n [api, client, options.id],\n )\n return {\n state: session.state as View,\n index: session.index,\n connection: session.connection,\n call,\n refresh,\n events: session.events,\n presence: session.presence as ActorPresenceMap<PresenceOf<D>>,\n setPresence: session.setPresence as (\n values: ActorPresenceValues<PresenceOf<D>>,\n ) => void,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2GA,MAAM,kBAA6D;CACjE,MAAM;CACN,QAAQ,CAAC;CAIT,UAAU;CAEV,MAAM,mBAAmB,OAAgB,UACvC,MAAM,SAAA,oBAA8B,iBAAiB,KAAK,IACrD,MAAM,SAAwC,QAC/C,KACN;CACA,cAAc,KAAA;CACd,aAAa,KAAA;AACf;AAEA,MAAM,mBAAmBA,iBAAS;CAChC,MAAM;CACN,QAAQ,gBAAgB;CACxB,UAAU,gBAAgB;CAC1B,SAAS;AACX,CAAC;AAED,MAAM,0BAAU,IAAI,IAAwD;;AAG5E,SAAgB,SACd,SACyB;CACzB,MAAM,EAAE,QAAQ;CAChB,IAAI,SAAS,QAAQ,IAAI,GAAG;CAC5B,IAAI,CAAC,QAAQ;EACX,SAAS,aAAa;GAAE,UAAU;GAAkB;EAAI,CAAC;EACzD,QAAQ,IAAI,KAAK,MAAM;CACzB;CACA,MAAM,UAAU,WAAW,QAAQ,QAAQ,IAAI;EAC7C,SAAS,QAAQ;EACjB,GAAI,QAAQ,gBAAgB,KAAA,IACxB,CAAC,IACD,EAAE,aAAa,QAAQ,YAAY;CACzC,CAAC;CACD,MAAM,EAAE,MAAM,OAAO,YAAY,cAE7B,kBAA2B;EACzB;EACA,aAAa,EAAE,OAAO,OAAO,iBAAiB;GAC5C,OAAO,QAAQ,QAAQ,IAAI,EAAE,SAAS;IAAE;IAAO;IAAO;GAAW,EAAE,CAAC;EACtE;CACF,CAAC,GACH;EAAC;EAAK;EAAQ,QAAQ;CAAE,CAC1B;CACA,OAAO;EACL,OAAO,QAAQ;EACf,OAAO,QAAQ;EACf,YAAY,QAAQ;EACpB;EACA;EACA,QAAQ,QAAQ;EAChB,UAAU,QAAQ;EAClB,aAAa,QAAQ;CAGvB;AACF"}
@@ -1,4 +1,5 @@
1
- import { t as contract } from "./contract-CKRg_E4q.js";
1
+ import { a as reducer } from "./reducer-BAXc8JyF.js";
2
+ import { n as internalContract } from "./contract-BYKASlCV.js";
2
3
  //#region src/actor-shared.ts
3
4
  /**
4
5
  * Internal shared vocabulary of the actor module — the reserved event
@@ -89,10 +90,13 @@ function actorStateSchema() {
89
90
  if (!isRecord(value) || typeof value["event"] !== "string" || typeof value["message"] !== "string") return issue(`invalid ${ACTOR_STATE_EVENT} payload`);
90
91
  const state = value["state"];
91
92
  if (!isJsonTree(state)) return issue("the actor state must be a plain JSON tree");
93
+ const clearIndex = value["clearIndex"];
94
+ if (clearIndex !== void 0 && (!Number.isSafeInteger(clearIndex) || clearIndex < 1)) return issue("the actor clearIndex must be a positive safe integer");
92
95
  return { value: {
93
96
  state,
94
97
  event: value["event"],
95
- message: value["message"]
98
+ message: value["message"],
99
+ ...clearIndex === void 0 ? {} : { clearIndex }
96
100
  } };
97
101
  });
98
102
  }
@@ -113,10 +117,11 @@ const actorFailedSchema = schema(ACTOR_FAILED_EVENT, (value) => {
113
117
  function actorReducer(options) {
114
118
  if (!isJsonTree(options.state)) throw new TypeError(`actor '${options.name}': the initial state must be a plain JSON tree`);
115
119
  const events = { [ACTOR_STATE_EVENT]: actorStateSchema() };
116
- return contract({
117
- name: options.name,
118
- events
119
- }).reducer({
120
+ return reducer({
121
+ contract: internalContract({
122
+ name: options.name,
123
+ events
124
+ }),
120
125
  name: ACTOR_REDUCER_NAME,
121
126
  initialState: options.state
122
127
  }).fold((state, event) => event.type === "a2.actor.state" ? event.payload.state : state);
@@ -124,4 +129,4 @@ function actorReducer(options) {
124
129
  //#endregion
125
130
  export { actorFailedSchema as a, isJsonTree as c, ActorRefusedError as i, openPresenceDefs as l, ACTOR_LANE as n, actorReducer as o, ACTOR_STATE_EVENT as r, actorStateSchema as s, ACTOR_FAILED_EVENT as t };
126
131
 
127
- //# sourceMappingURL=actor-shared-B5tJfzt-.js.map
132
+ //# sourceMappingURL=actor-shared-t7w8xIKP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actor-shared-t7w8xIKP.js","names":["createContract"],"sources":["../src/actor-shared.ts"],"sourcesContent":["import { reducer } from './reducer.ts'\n/**\n * Internal shared vocabulary of the actor module — the reserved event\n * envelopes, the protocol and context types, the state fold. Not an\n * entry point: the public surfaces are experimental-a2/actor (server),\n * /actor/client, and /actor/react.\n *\n * An actor is a durable mailbox with memory: one session per instance,\n * events processed one at a time (a lane) unless a handler opts out\n * with `concurrent: true`, state materialized as `a2.actor.state`\n * events committed atomically with each handler's completion.\n */\n\nimport { internalContract as createContract } from './contract.ts'\nimport type { Reducer } from './reducer.ts'\nimport type { ScheduleTiming } from './server.ts'\nimport type { StandardSchemaV1 } from './standard-schema.ts'\n\n/** Event type of a committed state change — one per completed serial handler. */\nexport const ACTOR_STATE_EVENT = 'a2.actor.state'\n/** Event type of a refusal — a handler that answered by throwing NonRetriableError. */\nexport const ACTOR_FAILED_EVENT = 'a2.actor.failed'\n/** The per-instance lane every serial handler shares — the state's write lock. */\nexport const ACTOR_LANE = 'a2.actor'\n/** Reducer identity — bump when the fold's meaning changes. */\nexport const ACTOR_REDUCER_NAME = 'a2.actor.v1'\n\n/**\n * A handler answered by throwing `NonRetriableError`: the event\n * settled, the state did not change. Isomorphic — the server raises it\n * from calls and `fetch()` serializes it as a 409; the client's `call`\n * proxy revives it from that response.\n */\nexport class ActorRefusedError extends Error {\n /** The refused event's name. */\n readonly event: string\n /** The refused event's message id. */\n readonly messageId: string\n constructor(event: string, messageId: string, message: string) {\n super(message)\n this.name = 'ActorRefusedError'\n this.event = event\n this.messageId = messageId\n }\n}\n\n/**\n * The declaration an actor is defined over: its state shape and its\n * event vocabulary, as types. Core A2 contracts are schemas because\n * the wire is untrusted; actor protocols are types because your\n * server is trusted — same contract-first design language, dialed to\n * the trust level.\n */\nexport type ActorProtocol = {\n state: object\n events: object\n /**\n * Optional third vocabulary: presence field → value type. Ephemeral\n * audience state (who is here, cursors), replicated to subscribers\n * and never stored in the log. Declaring it requires `presence:\n * true` in the actor options — the wire bit types cannot carry.\n */\n presence?: object\n}\n\n/** The protocol's presence vocabulary, `never` when undeclared. */\nexport type ActorPresenceOf<D extends ActorProtocol> = D extends {\n presence: infer P extends object\n}\n ? P\n : never\n\n/** A `setPresence` patch: changed fields, `null` clears one. */\nexport type ActorPresenceValues<P> = {\n [F in keyof P & string]?: P[F] | null\n}\n\n/**\n * The replicated presence map: participant → field → latest value,\n * last write wins per field. Values are peer-authored — render them\n * like user input.\n */\nexport type ActorPresenceMap<P> = {\n [participant: string]: {\n [F in keyof P & string]?: { value: P[F]; seen: number; at: Date }\n }\n}\n\n/**\n * The typed self-send surface: one method per declared event —\n * `ctx.send.transfer({ ref, amount })`. Buffered, not immediate: every\n * send requested during a handler commits atomically with that\n * handler's completion (a serial handler's state commit, a concurrent\n * handler's settlement), riding core's returned-events semantics — so\n * a reserve-and-trigger can never half-happen, and re-runs converge on\n * the same deterministic message ids.\n */\nexport type ActorSend<E> = {\n readonly [K in keyof E]: {} extends E[K]\n ? (input?: E[K]) => void\n : (input: E[K]) => void\n}\n\n/**\n * Timing for a scheduled event: exactly `{ delay: '5d' }` or\n * `{ at: Date }` (core §6 semantics — relative delays anchor to the\n * triggering message's durable `createdAt`, so re-runs resolve the\n * same due time). `name` overrides the timer's identity — it defaults\n * to the target event name, scoped to the triggering message, so one\n * handler run gets one timer per target event unless named apart.\n */\nexport type ActorScheduleOptions = ScheduleTiming & { name?: string }\n\n/**\n * The typed durable-timer surface: one method per declared event —\n * `ctx.schedule.refund({ ref: ctx.id }, { delay: '1h' })`. Unlike\n * `send`, scheduling is immediate, not buffered: it awaits provider\n * acceptance at call time (a timer is provider-side, not a log row),\n * so a handler that schedules and then refuses has still armed the\n * timer. That is safe by the guarded-delivery idiom — the stale timer\n * fires into an idempotent no-op — but it is the one exception to\n * \"refusals are total\". Requires a configured `scheduler`.\n */\nexport type ActorScheduleSend<E> = {\n readonly [K in keyof E]: {} extends E[K]\n ? (input: E[K] | undefined, options: ActorScheduleOptions) => Promise<void>\n : (input: E[K], options: ActorScheduleOptions) => Promise<void>\n}\n\n/** What a serial handler receives alongside its typed input. */\nexport type ActorContext<D extends ActorProtocol> = {\n /** Mutable draft, committed atomically with the handler's completion. */\n state: D['state']\n /** The message id — stable across re-runs; the idempotency key for external I/O. */\n id: string\n /** Durable 1-based dispatch ordinal of this message. */\n attempt: number\n /** The ordinary A2 handler signal — fires on claim expiry or supersession. */\n signal: AbortSignal\n /** Typed buffered self-send — committed atomically with the state commit. */\n send: ActorSend<D['events']>\n /** Typed durable timers — immediate provider handoff, see ActorScheduleSend. */\n schedule: ActorScheduleSend<D['events']>\n}\n\n/**\n * What a `concurrent: true` handler receives. No draft — concurrent\n * handlers run off the lane, in parallel, so state is a snapshot read\n * (honest about staleness) and mutations happen by sending events\n * whose serial handlers decide against fresh state.\n */\nexport type ActorConcurrentContext<D extends ActorProtocol> = {\n /** The message id — stable across re-runs; the idempotency key for external I/O. */\n id: string\n /** Durable 1-based dispatch ordinal of this message. */\n attempt: number\n /** The ordinary A2 handler signal — fires on claim expiry or supersession. */\n signal: AbortSignal\n /** Snapshot read — observational; the world moves while this runs. */\n state(): Promise<{ state: D['state']; index: number }>\n /** Typed buffered self-send — committed atomically with settlement. */\n send: ActorSend<D['events']>\n /** Typed durable timers — immediate provider handoff, see ActorScheduleSend. */\n schedule: ActorScheduleSend<D['events']>\n}\n\nexport type ActorStatePayload<S> = {\n state: S\n /** The event whose handler committed this state. */\n event: string\n /** The message (invocation event) id this state answers. */\n message: string\n /** The session's latest clear marker when this state was read. */\n clearIndex?: number\n}\n\nexport type ActorFailedPayload = {\n /** The message (invocation event) id this refusal answers. */\n message: string\n event: string\n error: string\n}\n\n/** The event vocabulary a client needs to follow an actor's state. */\nexport type ActorClientEventDefs<S> = {\n 'a2.actor.state': StandardSchemaV1<ActorStatePayload<S>>\n}\n\ntype SchemaResult<T> = StandardSchemaV1.Result<T>\n\nconst issue = <T>(message: string): SchemaResult<T> => ({\n issues: [{ message }],\n})\n\nconst schema = <T>(\n label: string,\n parse: (value: unknown) => SchemaResult<T>,\n): StandardSchemaV1<T> => ({\n '~standard': {\n version: 1,\n vendor: 'a2',\n validate(value) {\n try {\n return parse(value)\n } catch (error) {\n return issue(\n `${label}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n },\n },\n})\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/** True for plain JSON trees — what the log can store without loss. */\nexport function isJsonTree(\n value: unknown,\n seen: Set<object> = new Set(),\n): boolean {\n if (\n value === null ||\n typeof value === 'string' ||\n typeof value === 'boolean'\n ) {\n return true\n }\n if (typeof value === 'number') return Number.isFinite(value)\n if (typeof value !== 'object') return false\n if (seen.has(value)) return false\n seen.add(value)\n const valid = Array.isArray(value)\n ? value.every((item) => isJsonTree(item, seen))\n : Object.getPrototypeOf(value) === Object.prototype &&\n Object.values(value).every(\n (item) => item === undefined || isJsonTree(item, seen),\n )\n seen.delete(value)\n return valid\n}\n\n/** Per-field ceiling for presence values — presence is a cursor, not a document. */\nexport const PRESENCE_VALUE_MAX_BYTES = 8_192\n\n/**\n * The open presence vocabulary: the '*' catch-all validates any field\n * name against the JSON floor. The protocol types the vocabulary at\n * compile time; the route's `authorize` sees every set before\n * acceptance; renderers treat values as user input. Meaning has three\n * guards — this schema only owns the bytes.\n */\nexport const openPresenceDefs: Readonly<\n Record<string, StandardSchemaV1<unknown>>\n> = Object.freeze({\n '*': {\n '~standard': {\n version: 1,\n vendor: 'a2',\n validate: (value: unknown) => {\n if (!isJsonTree(value)) {\n return {\n issues: [{ message: 'presence values must be plain JSON trees' }],\n }\n }\n if (JSON.stringify(value).length > PRESENCE_VALUE_MAX_BYTES) {\n return {\n issues: [\n {\n message: `presence values are capped at ${PRESENCE_VALUE_MAX_BYTES} bytes`,\n },\n ],\n }\n }\n return { value }\n },\n },\n } satisfies StandardSchemaV1<unknown>,\n})\n\n/**\n * The `a2.actor.state` envelope: provenance plus the state, held to a\n * plain JSON tree (a `Date` or `Map` in state fails loudly at commit,\n * never silently coerces in the log).\n */\nexport function actorStateSchema<S>(): StandardSchemaV1<ActorStatePayload<S>> {\n return schema(ACTOR_STATE_EVENT, (value) => {\n if (\n !isRecord(value) ||\n typeof value['event'] !== 'string' ||\n typeof value['message'] !== 'string'\n ) {\n return issue(`invalid ${ACTOR_STATE_EVENT} payload`)\n }\n const state = value['state']\n if (!isJsonTree(state)) {\n return issue('the actor state must be a plain JSON tree')\n }\n const clearIndex = value['clearIndex']\n if (\n clearIndex !== undefined &&\n (!Number.isSafeInteger(clearIndex) || (clearIndex as number) < 1)\n ) {\n return issue('the actor clearIndex must be a positive safe integer')\n }\n return {\n value: {\n state: state as S,\n event: value['event'],\n message: value['message'],\n ...(clearIndex === undefined\n ? {}\n : { clearIndex: clearIndex as number }),\n },\n }\n })\n}\n\nexport const actorFailedSchema: StandardSchemaV1<ActorFailedPayload> = schema(\n ACTOR_FAILED_EVENT,\n (value) => {\n if (\n !isRecord(value) ||\n typeof value['message'] !== 'string' ||\n typeof value['event'] !== 'string' ||\n typeof value['error'] !== 'string'\n ) {\n return issue(`invalid ${ACTOR_FAILED_EVENT} payload`)\n }\n return {\n value: {\n message: value['message'],\n event: value['event'],\n error: value['error'],\n },\n }\n },\n)\n\nexport type ActorReducerOptions<S> = {\n /** The actor's name — must match the server-side definition. */\n name: string\n /** The initial state — a plain JSON tree, the fold's seed. */\n state: S\n}\n\n/**\n * The library fold over an actor's state commits — last write wins.\n * Pure library code: a browser bundle folds an actor's live state with\n * no user code, so the actor definition itself stays server-only\n * (import the server module's type for typing, this reducer for data).\n */\nexport function actorReducer<S>(\n options: ActorReducerOptions<S>,\n): Reducer<ActorClientEventDefs<S>, S> {\n if (!isJsonTree(options.state)) {\n throw new TypeError(\n `actor '${options.name}': the initial state must be a plain JSON tree`,\n )\n }\n const events: ActorClientEventDefs<S> = {\n [ACTOR_STATE_EVENT]: actorStateSchema(),\n }\n return reducer({\n contract: createContract({ name: options.name, events }),\n name: ACTOR_REDUCER_NAME,\n initialState: options.state,\n }).fold((state, event) =>\n event.type === ACTOR_STATE_EVENT ? event.payload.state : state,\n )\n}\n"],"mappings":"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"}
package/dist/actor.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as ActorProtocol, a as ActorHandle, b as ActorScheduleSend, c as ActorOptions, d as concurrent, f as ActorConcurrentContext, g as ActorPresenceValues, h as ActorPresenceOf, i as ActorFetchOptions, l as ActorSendOptions, m as ActorPresenceMap, n as ActorCallResult, o as ActorHandler, p as ActorContext, r as ActorDefinition, s as ActorOperation, t as ActorCallOptions, u as actor, v as ActorRefusedError, x as ActorSend, y as ActorScheduleOptions } from "./actor-ohPC-81x.js";
1
+ import { _ as ActorProtocol, a as ActorHandle, b as ActorScheduleSend, c as ActorOptions, d as concurrent, f as ActorConcurrentContext, g as ActorPresenceValues, h as ActorPresenceOf, i as ActorFetchOptions, l as ActorSendOptions, m as ActorPresenceMap, n as ActorCallResult, o as ActorHandler, p as ActorContext, r as ActorDefinition, s as ActorOperation, t as ActorCallOptions, u as actor, v as ActorRefusedError, x as ActorSend, y as ActorScheduleOptions } from "./actor-DBbe5xeE.js";
2
2
  export { ActorCallOptions, ActorCallResult, type ActorConcurrentContext, type ActorContext, ActorDefinition, ActorFetchOptions, ActorHandle, ActorHandler, ActorOperation, ActorOptions, type ActorPresenceMap, type ActorPresenceOf, type ActorPresenceValues, type ActorProtocol, ActorRefusedError, type ActorScheduleOptions, type ActorScheduleSend, type ActorSend, ActorSendOptions, actor, concurrent };
package/dist/actor.js CHANGED
@@ -1,7 +1,8 @@
1
- import { t as contract } from "./contract-CKRg_E4q.js";
2
- import { n as NonRetriableError, t as A2Error } from "./errors-DCk6ch5n.js";
3
- import { a as sseResponse, i as setServerFetchHooks, o as parsePresenceSibling, t as createServer } from "./server-BD5ckxZb.js";
4
- import { a as actorFailedSchema, c as isJsonTree, i as ActorRefusedError, l as openPresenceDefs, n as ACTOR_LANE, o as actorReducer, r as ACTOR_STATE_EVENT, s as actorStateSchema, t as ACTOR_FAILED_EVENT } from "./actor-shared-B5tJfzt-.js";
1
+ import { a as NonRetriableError, i as A2Error, n as isSessionCleared, r as sessionClearedError } from "./session-clear-DGI4jTQ5.js";
2
+ import { n as internalContract } from "./contract-BYKASlCV.js";
3
+ import { _ as serverInternals } from "./internal-CbVvEwad.js";
4
+ import { c as parsePresenceSibling, o as setServerFetchHooks, s as sseResponse, t as createServer } from "./server-Rj64fNvX.js";
5
+ import { a as actorFailedSchema, c as isJsonTree, i as ActorRefusedError, l as openPresenceDefs, n as ACTOR_LANE, o as actorReducer, r as ACTOR_STATE_EVENT, s as actorStateSchema, t as ACTOR_FAILED_EVENT } from "./actor-shared-t7w8xIKP.js";
5
6
  //#region src/actor.ts
6
7
  /**
7
8
  * experimental-a2/actor — durable actors over the log. Server-only
@@ -186,7 +187,7 @@ function actor(options) {
186
187
  handlers[eventName] = {
187
188
  lane: ACTOR_LANE,
188
189
  handler: async (ctx) => {
189
- const { state } = await ctx.session.state(serverReducer, { through: "latest" });
190
+ const { state, clearIndex } = await ctx.session.state(serverReducer, { through: "latest" });
190
191
  const draft = structuredClone(state);
191
192
  const sends = [];
192
193
  try {
@@ -213,7 +214,8 @@ function actor(options) {
213
214
  const payload = {
214
215
  state: draft,
215
216
  event: eventName,
216
- message: ctx.event.id
217
+ message: ctx.event.id,
218
+ ...clearIndex === void 0 ? {} : { clearIndex }
217
219
  };
218
220
  return [...sends, {
219
221
  type: ACTOR_STATE_EVENT,
@@ -223,16 +225,16 @@ function actor(options) {
223
225
  };
224
226
  }
225
227
  const presenceEnabled = options.presence === true;
226
- const contract$1 = presenceEnabled ? contract({
228
+ const contract = presenceEnabled ? internalContract({
227
229
  name,
228
230
  events,
229
231
  presence: openPresenceDefs
230
- }) : contract({
232
+ }) : internalContract({
231
233
  name,
232
234
  events
233
235
  });
234
236
  const server = createServer({
235
- contract: contract$1,
237
+ contract,
236
238
  handlers,
237
239
  ...options.store ? { store: options.store } : {},
238
240
  ...options.scheduler ? { scheduler: options.scheduler } : {},
@@ -241,6 +243,7 @@ function actor(options) {
241
243
  setServerFetchHooks(server, { validateIngress: ({ events: pushed }) => {
242
244
  for (const event of pushed) if (event.type.startsWith("a2.actor.")) throw new TypeError(`'${event.type}' is reserved — only a handler's completion can author it`);
243
245
  } });
246
+ const internals = serverInternals.get(server);
244
247
  const makeHandle = (id) => {
245
248
  const session = server.session(id);
246
249
  const sendEvent = async (eventName, input, sendOptions) => {
@@ -262,13 +265,15 @@ function actor(options) {
262
265
  let cursor = message.index + 1;
263
266
  for (;;) {
264
267
  await server.drain(id);
265
- const tail = await session.history({ gte: cursor });
268
+ const tail = await internals.historyWithMarkers(id, cursor);
266
269
  for (const event of tail) {
270
+ if (isSessionCleared(event)) throw sessionClearedError(`actor '${name}': '${eventName}' (message ${message.id}) was cleared before the call received its answer`);
267
271
  if (event.type === "a2.actor.state") {
268
272
  const payload = event.payload;
269
273
  if (payload.message === message.id) return {
270
274
  state: payload.state,
271
- index: event.index
275
+ index: event.index,
276
+ ...payload.clearIndex === void 0 ? {} : { clearIndex: payload.clearIndex }
272
277
  };
273
278
  }
274
279
  if (event.type === "a2.actor.failed") {
@@ -283,12 +288,21 @@ function actor(options) {
283
288
  }
284
289
  };
285
290
  const readState = async () => {
286
- const { state, index } = await session.state(serverReducer);
287
- return {
291
+ const { state, index, clearIndex } = await session.state(serverReducer);
292
+ return clearIndex === void 0 ? {
288
293
  state,
289
294
  index
295
+ } : {
296
+ state,
297
+ index,
298
+ clearIndex
290
299
  };
291
300
  };
301
+ /** A snapshot or call answer through the route's view. */
302
+ const projected = (result, view) => view ? {
303
+ ...result,
304
+ state: applyView(view, result.state)
305
+ } : result;
292
306
  const applyView = (view, state) => {
293
307
  const viewed = view(state);
294
308
  if (!isJsonTree(viewed)) throw new TypeError(`actor '${name}': the view returned a value that is not a plain JSON tree`);
@@ -305,6 +319,14 @@ function actor(options) {
305
319
  continue;
306
320
  }
307
321
  const event = item;
322
+ if (isSessionCleared(event)) {
323
+ const initial = structuredClone(options.state);
324
+ yield {
325
+ ...event,
326
+ payload: { state: view ? applyView(view, initial) : initial }
327
+ };
328
+ continue;
329
+ }
308
330
  if (event.type !== "a2.actor.state") continue;
309
331
  if (!view) {
310
332
  yield event;
@@ -329,11 +351,7 @@ function actor(options) {
329
351
  type: "state",
330
352
  id
331
353
  })) return forbidden();
332
- const result = await readState();
333
- return Response.json({
334
- state: fetchOptions?.view ? applyView(fetchOptions.view, result.state) : result.state,
335
- index: result.index
336
- }, { headers: { "cache-control": "private, no-store" } });
354
+ return Response.json(projected(await readState(), fetchOptions?.view), { headers: { "cache-control": "private, no-store" } });
337
355
  }
338
356
  const raw = Number(url.searchParams.get("index"));
339
357
  const startAfter = Number.isSafeInteger(raw) && raw >= 0 ? raw : 0;
@@ -395,16 +413,17 @@ function actor(options) {
395
413
  })) return forbidden();
396
414
  try {
397
415
  const result = await callEvent(eventName, input, messageId === void 0 ? void 0 : { id: messageId });
398
- return Response.json(fetchOptions?.view ? {
399
- state: applyView(fetchOptions.view, result.state),
400
- index: result.index
401
- } : result, { headers: { "cache-control": "private, no-store" } });
416
+ return Response.json(projected(result, fetchOptions?.view), { headers: { "cache-control": "private, no-store" } });
402
417
  } catch (error) {
403
418
  if (error instanceof ActorRefusedError) return Response.json({
404
419
  error: error.message,
405
420
  event: error.event,
406
421
  messageId: error.messageId
407
422
  }, { status: 409 });
423
+ if (error instanceof A2Error && error.code === "SESSION_CLEARED") return Response.json({
424
+ code: error.code,
425
+ error: error.message
426
+ }, { status: 410 });
408
427
  if (error instanceof A2Error && (error.code === "INVALID_PAYLOAD" || error.code === "UNKNOWN_EVENT_TYPE")) return Response.json({ error: error.message }, { status: 400 });
409
428
  throw error;
410
429
  }
@@ -436,7 +455,7 @@ function actor(options) {
436
455
  };
437
456
  return {
438
457
  name,
439
- contract: contract$1,
458
+ contract,
440
459
  server,
441
460
  reducer,
442
461
  actor: makeHandle
package/dist/actor.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"actor.js","names":["contract","createContract"],"sources":["../src/actor.ts"],"sourcesContent":["/**\n * experimental-a2/actor — durable actors over the log. Server-only\n * (handlers are server code); the browser condition resolves to a\n * throwing stub, and the browser surfaces are experimental-a2/actor/client\n * and experimental-a2/actor/react.\n *\n * An actor is defined over a protocol — its state shape and event\n * vocabulary, as types — and one complete `handlers` bag:\n *\n * ```ts\n * interface Counter {\n * state: { count: number }\n * events: { increment: object; add: { by: number } }\n * }\n * const counter = actor<Counter>({\n * name: 'counter',\n * state: { count: 0 },\n * handlers: {\n * increment: (ctx) => {\n * ctx.state.count++\n * },\n * add: (ctx, input) => {\n * ctx.state.count += input.by\n * },\n * },\n * })\n * ```\n *\n * Handlers are serial by default: one at a time per instance (a lane —\n * the state's write lock), `ctx.state` a mutable draft committed\n * atomically with completion. A handler holding slow I/O opts out of\n * the lane with `{ concurrent: true, handle }`: it runs in parallel,\n * reads state as a snapshot, and mutates by sending events whose\n * serial handlers decide against fresh state. `ctx.send.eventName(...)`\n * is typed by the protocol and buffered — committed atomically with\n * the handler's completion, so a reserve and its trigger can never\n * half-happen.\n *\n * A thrown `NonRetriableError` is an answer (settled, delivered to a\n * waiting caller); any other error retries like every A2 handler\n * failure; a crash re-runs against the same pre-state. Everything\n * compiles to contract + reducer + laned handlers + returned events —\n * no new store operations, no new wire.\n */\n\n// oxlint-disable no-await-in-loop -- a call awaits its answer: drain, read\n// the tail, repeat until the message settles. Sequential by nature.\n\nimport {\n ACTOR_FAILED_EVENT,\n ACTOR_LANE,\n ACTOR_STATE_EVENT,\n ActorRefusedError,\n actorFailedSchema,\n actorReducer,\n actorStateSchema,\n isJsonTree,\n openPresenceDefs,\n} from './actor-shared.ts'\nimport type {\n ActorClientEventDefs,\n ActorConcurrentContext,\n ActorContext,\n ActorFailedPayload,\n ActorPresenceOf,\n ActorProtocol,\n ActorScheduleOptions,\n ActorScheduleSend,\n ActorSend,\n ActorStatePayload,\n} from './actor-shared.ts'\nimport { parsePresenceSibling } from './push-envelope.ts'\nimport type { ParsedPushPresence } from './push-envelope.ts'\nimport { contract as createContract } from './contract.ts'\nimport type { PresencePatch, PresenceSnapshot } from './contract.ts'\nimport type { Contract, EventDefs } from './contract.ts'\nimport type { Reducer } from './reducer.ts'\nimport { A2Error, NonRetriableError } from './errors.ts'\nimport { createServer } from './server.ts'\nimport { setServerFetchHooks } from './server-fetch.ts'\nimport { sseResponse } from './sse.ts'\nimport type { A2Scheduler, A2Server, HandlerEntry, Session } from './server.ts'\nimport type { A2Store } from './store.ts'\nimport type { StandardSchemaV1 } from './standard-schema.ts'\nimport type { A2Telemetry } from './telemetry.ts'\n\nexport type {\n ActorConcurrentContext,\n ActorContext,\n ActorPresenceMap,\n ActorPresenceOf,\n ActorPresenceValues,\n ActorProtocol,\n ActorScheduleOptions,\n ActorScheduleSend,\n ActorSend,\n} from './actor-shared.ts'\nexport { ActorRefusedError } from './actor-shared.ts'\n\n/**\n * One event's handler: ordinary server code. Serial by default — read\n * `ctx.state`, perform I/O, mutate `ctx.state`, one atomic commit at\n * return. `{ concurrent: true, handle }` opts the handler out of the\n * lane for slow I/O: state becomes a snapshot read and mutations\n * happen by sending events. The input is typed by the protocol; the\n * wire hands handlers parsed JSON, so a wire-exposed event guards its\n * input in its first line (`schema.parse(input)` if you like schemas —\n * userland either way).\n */\nexport type ActorHandler<D extends ActorProtocol, K extends keyof D['events']> =\n | ((ctx: ActorContext<D>, input: D['events'][K]) => void | Promise<void>)\n | {\n concurrent: true\n handle: (\n ctx: ActorConcurrentContext<D>,\n input: D['events'][K],\n ) => void | Promise<void>\n }\n\n/**\n * Mark a handler concurrent — off the lane, in parallel with the lane\n * and with other concurrent handlers, `ctx.state()` a snapshot read.\n * Sugar for the structural `{ concurrent: true, handle }` form:\n *\n * ```ts\n * handlers: {\n * transfer: concurrent(async (ctx, input) => {\n * await bank.transfer(input, { idempotencyKey: input.ref })\n * ctx.send.settle({ ref: input.ref })\n * }),\n * }\n * ```\n */\nexport function concurrent<Ctx, I>(\n handle: (ctx: Ctx, input: I) => void | Promise<void>,\n): { concurrent: true; handle: (ctx: Ctx, input: I) => void | Promise<void> } {\n return { concurrent: true, handle }\n}\n\nexport type ActorOptions<D extends ActorProtocol> = {\n /** Identity — prefixes storage keys; one contract per actor definition. */\n name: string\n /** The initial state — checked against the protocol, a plain JSON tree. */\n state: D['state']\n /**\n * One handler per declared event — completeness and payload shapes\n * are compile-checked against the protocol.\n */\n handlers: { [K in keyof D['events']]: ActorHandler<D, K> }\n store?: A2Store\n scheduler?: A2Scheduler\n telemetry?: A2Telemetry\n} & ([ActorPresenceOf<D>] extends [never]\n ? { presence?: never }\n : {\n /**\n * The wire bit for the protocol's `presence` vocabulary — types\n * erase, so the runtime needs one value to arm the presence\n * lanes (frames on GET, envelopes on POST). Required exactly\n * when the protocol declares `presence`; the vocabulary itself\n * is typed there. Values are validated to the JSON floor and\n * pass through `authorize` as `{ type: 'presence' }` operations;\n * meaning stays with your renderers — treat values as user\n * input.\n */\n presence: true\n })\n\n/**\n * One HTTP operation `handle.fetch` is about to serve — the actor\n * mirror of core's `A2Operation`. `authorize` sees it before any\n * read, write, or subscription; returning `false` answers 403.\n */\nexport type ActorOperation =\n | { readonly type: 'state'; readonly id: string }\n | {\n readonly type: 'stream'\n readonly id: string\n readonly startAfter: number\n }\n | {\n readonly type: 'call'\n readonly id: string\n readonly event: string\n readonly input: unknown\n readonly messageId?: string\n }\n | {\n readonly type: 'presence'\n readonly id: string\n readonly participant: string\n readonly values: Readonly<Record<string, unknown>>\n }\n\nexport type ActorFetchOptions<D extends ActorProtocol> = {\n /** Per-operation policy — authentication happened in your route. */\n authorize?: (operation: ActorOperation) => boolean | Promise<boolean>\n /**\n * Request-time projection: what this mount's audience sees. Applied\n * to every state frame on the stream AND to call answers (the two\n * untrusted lanes must agree, or the call lane leaks what the\n * stream hides). The definition stays audience-agnostic — different\n * routes project the same actor differently, and per-viewer views\n * are just closures over the route's auth. Output is held to the\n * JSON-tree floor like everything else. Transport-agnostic: applied\n * at frame emission, so any wire the door speaks emits projected\n * frames. Absent, the full state ships (the trusted default).\n */\n view?: (state: D['state']) => unknown\n}\n\nexport type ActorSendOptions = {\n /** Explicit message id — makes retries of this send idempotent. */\n id?: string\n}\n\nexport type ActorCallOptions = ActorSendOptions & {\n /** How long to await the answer before rejecting the wait (default 30s). */\n timeoutMs?: number\n}\n\nexport type ActorCallResult<S> = {\n /** The actor's state after this message was processed. */\n state: S\n /** Log index of the state commit that answered this message. */\n index: number\n}\n\ntype CallMethod<\n D extends ActorProtocol,\n K extends keyof D['events'],\n> = {} extends D['events'][K]\n ? (\n input?: D['events'][K],\n options?: ActorCallOptions,\n ) => Promise<ActorCallResult<D['state']>>\n : (\n input: D['events'][K],\n options?: ActorCallOptions,\n ) => Promise<ActorCallResult<D['state']>>\n\ntype SendMethod<\n D extends ActorProtocol,\n K extends keyof D['events'],\n> = {} extends D['events'][K]\n ? (\n input?: D['events'][K],\n options?: ActorSendOptions,\n ) => Promise<{ id: string; index: number }>\n : (\n input: D['events'][K],\n options?: ActorSendOptions,\n ) => Promise<{ id: string; index: number }>\n\n/** One instance: typed calls and sends, reads, the managed door, the escape hatch. */\nexport type ActorHandle<D extends ActorProtocol> = {\n readonly id: string\n /**\n * Send the event and await its answer: the state after the handler\n * ran, or a rejection with `ActorRefusedError`. Serial events only —\n * concurrent handlers produce no answer to await; `send` them.\n */\n readonly call: { readonly [K in keyof D['events']]: CallMethod<D, K> }\n /** Cast: validate, append the event durably, return without waiting. */\n readonly send: { readonly [K in keyof D['events']]: SendMethod<D, K> }\n /** Snapshot read — never queues behind pending messages. */\n readonly state: () => Promise<ActorCallResult<D['state']>>\n /**\n * The managed door — the actor mirror of `server.fetch`, bound to\n * this instance. GET streams the state plane (`a2.actor.state`\n * commits only, `?index` resume, heartbeat, deadline rotation);\n * POST is the call lane `createActorClient` speaks (`{ event,\n * input, messageId? }` → the answer, 409 for a refusal). Your route\n * authenticates and picks the instance; `authorize` sees every\n * operation. Custom endpoints are route code around this call.\n */\n readonly fetch: (\n request: Request,\n options?: ActorFetchOptions<D>,\n ) => Promise<Response>\n /** Escape hatch: the raw A2 session (history, stream, schedule). */\n readonly session: Session<EventDefs>\n}\n\nexport type ActorDefinition<D extends ActorProtocol> = {\n readonly name: string\n /**\n * Type-only carrier of the protocol, so clients deriving from\n * `typeof def` can see the vocabularies (`experimental-a2/actor/react`\n * types `presence` from it). Never a runtime value.\n */\n readonly protocol?: D\n /** The assembled contract — declared events plus the reserved a2.actor.* pair. */\n readonly contract: Contract<EventDefs>\n /** The underlying A2 server — `server.fetch` mounts the full session wire. */\n readonly server: A2Server<EventDefs>\n /** The state fold — same identity as the client's follower fold. */\n readonly reducer: Reducer<ActorClientEventDefs<D['state']>, D['state']>\n actor(id: string): ActorHandle<D>\n}\n\n// Object-mechanics names, not surface names: events live in their own\n// bags, so nothing else is reserved.\nconst UNSAFE_EVENT_NAMES = new Set(['__proto__', 'constructor', 'prototype'])\n\nconst forbidden = (): Response =>\n Response.json({ error: 'the operation is not authorized' }, { status: 403 })\n\nconst CALL_TIMEOUT_MS = 30_000\nconst CALL_POLL_MS = 25\n\n/** Declared events accept any JSON tree; `undefined` normalizes to `{}`. */\nconst jsonInputSchema: StandardSchemaV1<unknown> = {\n '~standard': {\n version: 1,\n vendor: 'a2',\n validate(value) {\n if (value === undefined || value === null) return { value: {} }\n if (!isJsonTree(value)) {\n return {\n issues: [{ message: 'event input must be a plain JSON tree' }],\n }\n }\n return { value }\n },\n },\n}\n\nconst describeError = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\nconst sleep = (ms: number): Promise<void> =>\n new Promise((resolve) => setTimeout(resolve, ms))\n\ntype NormalizedHandler<D extends ActorProtocol> =\n | {\n kind: 'serial'\n handle: (ctx: ActorContext<D>, input: unknown) => void | Promise<void>\n }\n | {\n kind: 'concurrent'\n handle: (\n ctx: ActorConcurrentContext<D>,\n input: unknown,\n ) => void | Promise<void>\n }\n\n/**\n * Define and serve an actor over its protocol: a named, durable\n * instance-per-id with serialized state handlers, concurrent I/O\n * handlers, and typed self-sends.\n */\nexport function actor<D extends ActorProtocol>(\n options: ActorOptions<D>,\n): ActorDefinition<D> {\n const { name } = options\n\n const handlersByEvent = new Map<string, NormalizedHandler<D>>()\n const events: Record<string, StandardSchemaV1> = {\n [ACTOR_STATE_EVENT]: actorStateSchema(),\n [ACTOR_FAILED_EVENT]: actorFailedSchema,\n }\n for (const [eventName, def] of Object.entries(\n options.handlers as Record<string, ActorHandler<D, keyof D['events']>>,\n )) {\n if (UNSAFE_EVENT_NAMES.has(eventName)) {\n throw new TypeError(\n `actor '${name}': '${eventName}' collides with object plumbing and cannot name an event`,\n )\n }\n if (eventName.startsWith('a2.')) {\n throw new TypeError(\n `actor '${name}': event names starting with 'a2.' are reserved`,\n )\n }\n handlersByEvent.set(\n eventName,\n typeof def === 'function'\n ? {\n kind: 'serial',\n handle: def as (\n ctx: ActorContext<D>,\n input: unknown,\n ) => void | Promise<void>,\n }\n : {\n kind: 'concurrent',\n handle: def.handle as (\n ctx: ActorConcurrentContext<D>,\n input: unknown,\n ) => void | Promise<void>,\n },\n )\n events[eventName] = jsonInputSchema\n }\n\n const reducer = actorReducer({ name, state: options.state })\n // The server's contract carries every event type; the reducer only\n // folds state commits. Same fold, same identity — the widening is a\n // typing formality.\n const serverReducer = reducer as unknown as Reducer<EventDefs, D['state']>\n\n // The typed durable-timer surface: immediate handoff to the\n // configured scheduler through the handler session, so timer\n // identity is scoped to the triggering message and re-runs address\n // the same timer (core §6). Not buffered — see ActorScheduleSend.\n const makeSchedule = (\n schedule: (\n name: string,\n timing: { delay?: string; at?: Date },\n event: { type: string; payload: unknown },\n ) => Promise<void>,\n ): ActorScheduleSend<D['events']> => {\n const surface: Record<string, unknown> = {}\n for (const eventName of handlersByEvent.keys()) {\n Object.defineProperty(surface, eventName, {\n value: (input: unknown, scheduleOptions: ActorScheduleOptions) => {\n const { name: taskName, ...timing } = scheduleOptions\n return schedule(taskName ?? eventName, timing, {\n type: eventName,\n payload: input ?? {},\n })\n },\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n return surface as ActorScheduleSend<D['events']>\n }\n\n // The typed self-send surface: per-invocation buffers behind one\n // shared method record shape. defineProperty so event names like\n // 'constructor'-adjacent identifiers can never touch prototypes.\n const makeSend = (\n buffer: { type: string; payload: unknown }[],\n ): ActorSend<D['events']> => {\n const send: Record<string, unknown> = {}\n for (const eventName of handlersByEvent.keys()) {\n Object.defineProperty(send, eventName, {\n value: (input?: unknown) => {\n buffer.push({ type: eventName, payload: input ?? {} })\n },\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n return send as ActorSend<D['events']>\n }\n\n const handlers: Record<string, HandlerEntry<EventDefs>> = {}\n for (const [eventName, def] of handlersByEvent) {\n if (def.kind === 'concurrent') {\n // No lane: concurrent handlers run in parallel — with the lane\n // and with each other. They cannot write state (no draft to\n // commit), so no serialization is needed for correctness; slow\n // I/O lives here without blocking the actor.\n handlers[eventName] = async (ctx) => {\n const sends: { type: string; payload: unknown }[] = []\n try {\n await def.handle(\n {\n id: ctx.event.id,\n attempt: ctx.attempt,\n signal: ctx.signal,\n send: makeSend(sends),\n schedule: makeSchedule((taskName, timing, event) =>\n ctx.session.schedule(\n taskName,\n timing as Parameters<typeof ctx.session.schedule>[1],\n event,\n ),\n ),\n state: async () => {\n const { state, index } = await ctx.session.state(\n serverReducer,\n { through: 'latest' },\n )\n return { state, index }\n },\n },\n ctx.event.payload,\n )\n } catch (error) {\n if (!(error instanceof NonRetriableError)) throw error\n // A terminal failure is recorded for audit; nothing awaits a\n // concurrent handler, so the marker is history, not an\n // answer. Buffered sends are discarded — refusals are total,\n // so compensate by completing (send then return), never by\n // throwing.\n const payload: ActorFailedPayload = {\n message: ctx.event.id,\n event: eventName,\n error: describeError(error),\n }\n return { type: ACTOR_FAILED_EVENT, payload }\n }\n // Sends commit atomically with settlement; re-runs converge on\n // the same deterministic message ids.\n return sends\n }\n continue\n }\n handlers[eventName] = {\n // One lane for every serial handler of one instance: the next\n // message starts only after the previous one completed, and\n // completion commits the state event atomically —\n // read-modify-write on `ctx.state` is safe by exclusion, not by\n // liveness.\n lane: ACTOR_LANE,\n handler: async (ctx) => {\n // Explicitly 'latest', not the event-relative default: prior\n // lane messages commit *after* this message was appended, so\n // their state commits carry higher indexes than the trigger —\n // an event-relative read would miss them and lose updates.\n // 'latest' is deterministic here because the lane makes this\n // attempt the only possible writer while it runs.\n const { state } = await ctx.session.state(serverReducer, {\n through: 'latest',\n })\n const draft = structuredClone(state)\n const sends: { type: string; payload: unknown }[] = []\n try {\n await def.handle(\n {\n state: draft,\n id: ctx.event.id,\n attempt: ctx.attempt,\n signal: ctx.signal,\n send: makeSend(sends),\n schedule: makeSchedule((taskName, timing, event) =>\n ctx.session.schedule(\n taskName,\n timing as Parameters<typeof ctx.session.schedule>[1],\n event,\n ),\n ),\n },\n ctx.event.payload,\n )\n } catch (error) {\n // A NonRetriableError is an answer: settle the message,\n // record the refusal, deliver it to the waiting caller —\n // no retry, no failure budget spent (buffered sends are\n // discarded with the draft: refusals are total). Every\n // other throw is an ordinary A2 handler failure: rethrown\n // into core's retry machinery (backoff, ten-failure\n // budget, dead letter) — including surrendered lease\n // aborts, which core recognizes by identity.\n if (!(error instanceof NonRetriableError)) throw error\n const payload: ActorFailedPayload = {\n message: ctx.event.id,\n event: eventName,\n error: describeError(error),\n }\n return { type: ACTOR_FAILED_EVENT, payload }\n }\n // One returned batch: buffered sends plus the state commit —\n // committed atomically with completion, so a reserve and its\n // trigger can never half-happen. The state event stays the\n // outcome marker a waiting call resolves on. (Unconditional\n // for now; a dirty check is a compatible later optimization.)\n const payload: ActorStatePayload<D['state']> = {\n state: draft,\n event: eventName,\n message: ctx.event.id,\n }\n return [...sends, { type: ACTOR_STATE_EVENT, payload }]\n },\n }\n }\n\n const presenceEnabled = (options as { presence?: unknown }).presence === true\n // The '*' catch-all arms an open server-side vocabulary: the\n // protocol types field names at compile time, the floor validates\n // every value, and `authorize` sees every set. See openPresenceDefs.\n const contract = presenceEnabled\n ? createContract({ name, events, presence: openPresenceDefs })\n : createContract({ name, events })\n const server = createServer({\n contract,\n handlers,\n ...(options.store ? { store: options.store } : {}),\n ...(options.scheduler ? { scheduler: options.scheduler } : {}),\n ...(options.telemetry ? { telemetry: options.telemetry } : {}),\n })\n // Single-writer discipline on the underlying session wire too: if an\n // application mounts `def.server.fetch`, browsers may push declared\n // events, never authored state or failure records.\n setServerFetchHooks(server, {\n validateIngress: ({ events: pushed }) => {\n for (const event of pushed) {\n if (event.type.startsWith('a2.actor.')) {\n throw new TypeError(\n `'${event.type}' is reserved — only a handler's completion can author it`,\n )\n }\n }\n },\n })\n\n const makeHandle = (id: string): ActorHandle<D> => {\n const session = server.session(id)\n\n const sendEvent = async (\n eventName: string,\n input: unknown,\n sendOptions?: ActorSendOptions,\n ): Promise<{ id: string; index: number }> => {\n const [message] = await session.append({\n type: eventName,\n payload: input ?? {},\n ...(sendOptions?.id ? { id: sendOptions.id } : {}),\n })\n if (!message) throw new Error('append returned no event')\n return { id: message.id, index: message.index }\n }\n\n const callEvent = async (\n eventName: string,\n input: unknown,\n callOptions?: ActorCallOptions,\n ): Promise<ActorCallResult<D['state']>> => {\n if (handlersByEvent.get(eventName)?.kind === 'concurrent') {\n throw new TypeError(\n `actor '${name}': '${eventName}' is concurrent — it produces no answer to await; send it instead`,\n )\n }\n const message = await sendEvent(eventName, input, callOptions)\n const deadline = Date.now() + (callOptions?.timeoutMs ?? CALL_TIMEOUT_MS)\n let cursor = message.index + 1\n for (;;) {\n // Drain participates in processing (usually answering inline in\n // this invocation); claims arbitrate if another process runs.\n await server.drain(id)\n const tail = await session.history({ gte: cursor })\n for (const event of tail) {\n if (event.type === ACTOR_STATE_EVENT) {\n const payload = event.payload as ActorStatePayload<D['state']>\n if (payload.message === message.id) {\n return { state: payload.state, index: event.index }\n }\n }\n if (event.type === ACTOR_FAILED_EVENT) {\n const payload = event.payload as ActorFailedPayload\n if (payload.message === message.id) {\n throw new ActorRefusedError(eventName, message.id, payload.error)\n }\n }\n }\n const last = tail[tail.length - 1]\n if (last) cursor = last.index + 1\n if (Date.now() >= deadline) {\n throw new Error(\n `actor '${name}': timed out waiting for '${eventName}' (message ${message.id})`,\n )\n }\n await sleep(CALL_POLL_MS)\n }\n }\n\n const readState = async (): Promise<ActorCallResult<D['state']>> => {\n const { state, index } = await session.state(serverReducer)\n return { state, index }\n }\n\n const applyView = (\n view: (state: D['state']) => unknown,\n state: D['state'],\n ): unknown => {\n const viewed = view(state)\n if (!isJsonTree(viewed)) {\n throw new TypeError(\n `actor '${name}': the view returned a value that is not a plain JSON tree`,\n )\n }\n return viewed\n }\n\n const streamStates = async function* (\n startAfter: number,\n view?: (state: D['state']) => unknown,\n ) {\n // With presence armed, the session interleaves presence items\n // (snapshot, then patches) between log events. They are\n // ephemeral audience data, not actor state: they pass through\n // untouched — views project state, never peers — and they never\n // advance the resume frontier.\n const source: AsyncIterable<\n | Awaited<ReturnType<typeof session.history>>[number]\n | PresencePatch\n | PresenceSnapshot\n > = presenceEnabled\n ? (\n session as unknown as {\n stream(options: {\n startAfter: number\n presence: true\n }): AsyncIterable<PresencePatch | PresenceSnapshot>\n }\n ).stream({ startAfter, presence: true })\n : session.stream({ startAfter })\n for await (const item of source) {\n if (!('type' in item)) {\n yield item\n continue\n }\n const event = item\n // The state plane only: subscribers see what the actor is,\n // never other callers' inputs. Refusals answer only their\n // caller (the POST 409); the full mailbox stays server-side.\n if (event.type !== ACTOR_STATE_EVENT) continue\n if (!view) {\n yield event\n continue\n }\n const payload = event.payload as ActorStatePayload<D['state']>\n yield {\n ...event,\n payload: { ...payload, state: applyView(view, payload.state) },\n }\n }\n }\n\n // The managed door — the actor mirror of `server.fetch`: GET is\n // the state-plane SSE (`a2.actor.state` commits only, `?index`\n // resume), POST is the call lane (`{ event, input, messageId? }`\n // → the answer, 409 for a refusal). Authentication happens in the\n // surrounding route; per-operation policy in `authorize`.\n const handleFetch = async (\n request: Request,\n fetchOptions?: ActorFetchOptions<D>,\n ): Promise<Response> => {\n const authorized = async (operation: ActorOperation): Promise<boolean> =>\n (await fetchOptions?.authorize?.(Object.freeze(operation))) !== false\n\n if (request.method === 'GET') {\n const url = new URL(request.url)\n if (url.searchParams.has('snapshot')) {\n if (!(await authorized({ type: 'state', id }))) return forbidden()\n const result = await readState()\n return Response.json(\n {\n state: fetchOptions?.view\n ? applyView(fetchOptions.view, result.state)\n : result.state,\n index: result.index,\n },\n { headers: { 'cache-control': 'private, no-store' } },\n )\n }\n const raw = Number(url.searchParams.get('index'))\n const startAfter = Number.isSafeInteger(raw) && raw >= 0 ? raw : 0\n if (!(await authorized({ type: 'stream', id, startAfter }))) {\n return forbidden()\n }\n return sseResponse(streamStates(startAfter, fetchOptions?.view))\n }\n if (request.method !== 'POST') {\n return new Response(null, {\n status: 405,\n headers: { allow: 'GET, POST' },\n })\n }\n let body: unknown\n try {\n body = await request.json()\n } catch {\n return Response.json({ error: 'invalid JSON body' }, { status: 400 })\n }\n const record =\n typeof body === 'object' && body !== null\n ? (body as Record<string, unknown>)\n : {}\n if ('presence' in record) {\n // A presence envelope — what the follower client's\n // `setPresence` posts. Events never ride this lane: writes to\n // the actor are calls.\n if (!presenceEnabled) {\n return Response.json(\n { error: `actor '${name}' declares no presence` },\n { status: 400 },\n )\n }\n const pushedEvents = record['events']\n if (\n pushedEvents !== undefined &&\n (!Array.isArray(pushedEvents) || pushedEvents.length > 0)\n ) {\n return Response.json(\n { error: 'the actor wire takes calls, not event pushes' },\n { status: 400 },\n )\n }\n let patch: ParsedPushPresence\n try {\n const parsed = parsePresenceSibling(record['presence'])\n if (!parsed) throw new TypeError('presence must be an object')\n patch = parsed\n } catch (error) {\n return Response.json({ error: describeError(error) }, { status: 400 })\n }\n if (\n !(await authorized({\n type: 'presence',\n id,\n participant: patch.participant,\n values: patch.values,\n }))\n ) {\n return forbidden()\n }\n try {\n await (\n session as unknown as {\n setPresence(patch: ParsedPushPresence): Promise<void>\n }\n ).setPresence(patch)\n } catch (error) {\n if (\n error instanceof A2Error &&\n (error.code === 'INVALID_PAYLOAD' ||\n error.code === 'UNKNOWN_PRESENCE_FIELD')\n ) {\n return Response.json({ error: error.message }, { status: 400 })\n }\n throw error\n }\n return Response.json([])\n }\n const eventName = record['event']\n if (\n typeof eventName !== 'string' ||\n handlersByEvent.get(eventName)?.kind !== 'serial'\n ) {\n return Response.json({ error: 'unknown event' }, { status: 400 })\n }\n const messageId = record['messageId']\n if (messageId !== undefined && typeof messageId !== 'string') {\n return Response.json({ error: 'invalid messageId' }, { status: 400 })\n }\n const input = record['input']\n if (\n !(await authorized({\n type: 'call',\n id,\n event: eventName,\n input,\n ...(messageId === undefined ? {} : { messageId }),\n }))\n ) {\n return forbidden()\n }\n try {\n const result = await callEvent(\n eventName,\n input,\n messageId === undefined ? undefined : { id: messageId },\n )\n return Response.json(\n fetchOptions?.view\n ? {\n state: applyView(fetchOptions.view, result.state),\n index: result.index,\n }\n : result,\n { headers: { 'cache-control': 'private, no-store' } },\n )\n } catch (error) {\n if (error instanceof ActorRefusedError) {\n return Response.json(\n {\n error: error.message,\n event: error.event,\n messageId: error.messageId,\n },\n { status: 409 },\n )\n }\n if (\n error instanceof A2Error &&\n (error.code === 'INVALID_PAYLOAD' ||\n error.code === 'UNKNOWN_EVENT_TYPE')\n ) {\n return Response.json({ error: error.message }, { status: 400 })\n }\n throw error\n }\n }\n\n const call: Record<string, unknown> = {}\n const send: Record<string, unknown> = {}\n for (const eventName of handlersByEvent.keys()) {\n Object.defineProperty(call, eventName, {\n value: (input?: unknown, callOptions?: ActorCallOptions) =>\n callEvent(eventName, input, callOptions),\n enumerable: true,\n configurable: true,\n writable: false,\n })\n Object.defineProperty(send, eventName, {\n value: (input?: unknown, sendOptions?: ActorSendOptions) =>\n sendEvent(eventName, input, sendOptions),\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n\n const handle: ActorHandle<D> = {\n id,\n session,\n call: call as ActorHandle<D>['call'],\n send: send as ActorHandle<D>['send'],\n state: readState,\n fetch: handleFetch,\n }\n return handle\n }\n\n return {\n name,\n contract,\n server,\n reducer,\n actor: makeHandle,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqIA,SAAgB,WACd,QAC4E;CAC5E,OAAO;EAAE,YAAY;EAAM;CAAO;AACpC;AAsKA,MAAM,qCAAqB,IAAI,IAAI;CAAC;CAAa;CAAe;AAAW,CAAC;AAE5E,MAAM,kBACJ,SAAS,KAAK,EAAE,OAAO,kCAAkC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAE7E,MAAM,kBAAkB;AACxB,MAAM,eAAe;;AAGrB,MAAM,kBAA6C,EACjD,aAAa;CACX,SAAS;CACT,QAAQ;CACR,SAAS,OAAO;EACd,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,OAAO,EAAE,OAAO,CAAC,EAAE;EAC9D,IAAI,CAAC,WAAW,KAAK,GACnB,OAAO,EACL,QAAQ,CAAC,EAAE,SAAS,wCAAwC,CAAC,EAC/D;EAEF,OAAO,EAAE,MAAM;CACjB;AACF,EACF;AAEA,MAAM,iBAAiB,UACrB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAEvD,MAAM,SAAS,OACb,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;;;;;;AAoBlD,SAAgB,MACd,SACoB;CACpB,MAAM,EAAE,SAAS;CAEjB,MAAM,kCAAkB,IAAI,IAAkC;CAC9D,MAAM,SAA2C;GAC9C,oBAAoB,iBAAiB;GACrC,qBAAqB;CACxB;CACA,KAAK,MAAM,CAAC,WAAW,QAAQ,OAAO,QACpC,QAAQ,QACV,GAAG;EACD,IAAI,mBAAmB,IAAI,SAAS,GAClC,MAAM,IAAI,UACR,UAAU,KAAK,MAAM,UAAU,yDACjC;EAEF,IAAI,UAAU,WAAW,KAAK,GAC5B,MAAM,IAAI,UACR,UAAU,KAAK,gDACjB;EAEF,gBAAgB,IACd,WACA,OAAO,QAAQ,aACX;GACE,MAAM;GACN,QAAQ;EAIV,IACA;GACE,MAAM;GACN,QAAQ,IAAI;EAId,CACN;EACA,OAAO,aAAa;CACtB;CAEA,MAAM,UAAU,aAAa;EAAE;EAAM,OAAO,QAAQ;CAAM,CAAC;CAI3D,MAAM,gBAAgB;CAMtB,MAAM,gBACJ,aAKmC;EACnC,MAAM,UAAmC,CAAC;EAC1C,KAAK,MAAM,aAAa,gBAAgB,KAAK,GAC3C,OAAO,eAAe,SAAS,WAAW;GACxC,QAAQ,OAAgB,oBAA0C;IAChE,MAAM,EAAE,MAAM,UAAU,GAAG,WAAW;IACtC,OAAO,SAAS,YAAY,WAAW,QAAQ;KAC7C,MAAM;KACN,SAAS,SAAS,CAAC;IACrB,CAAC;GACH;GACA,YAAY;GACZ,cAAc;GACd,UAAU;EACZ,CAAC;EAEH,OAAO;CACT;CAKA,MAAM,YACJ,WAC2B;EAC3B,MAAM,OAAgC,CAAC;EACvC,KAAK,MAAM,aAAa,gBAAgB,KAAK,GAC3C,OAAO,eAAe,MAAM,WAAW;GACrC,QAAQ,UAAoB;IAC1B,OAAO,KAAK;KAAE,MAAM;KAAW,SAAS,SAAS,CAAC;IAAE,CAAC;GACvD;GACA,YAAY;GACZ,cAAc;GACd,UAAU;EACZ,CAAC;EAEH,OAAO;CACT;CAEA,MAAM,WAAoD,CAAC;CAC3D,KAAK,MAAM,CAAC,WAAW,QAAQ,iBAAiB;EAC9C,IAAI,IAAI,SAAS,cAAc;GAK7B,SAAS,aAAa,OAAO,QAAQ;IACnC,MAAM,QAA8C,CAAC;IACrD,IAAI;KACF,MAAM,IAAI,OACR;MACE,IAAI,IAAI,MAAM;MACd,SAAS,IAAI;MACb,QAAQ,IAAI;MACZ,MAAM,SAAS,KAAK;MACpB,UAAU,cAAc,UAAU,QAAQ,UACxC,IAAI,QAAQ,SACV,UACA,QACA,KACF,CACF;MACA,OAAO,YAAY;OACjB,MAAM,EAAE,OAAO,UAAU,MAAM,IAAI,QAAQ,MACzC,eACA,EAAE,SAAS,SAAS,CACtB;OACA,OAAO;QAAE;QAAO;OAAM;MACxB;KACF,GACA,IAAI,MAAM,OACZ;IACF,SAAS,OAAO;KACd,IAAI,EAAE,iBAAiB,oBAAoB,MAAM;KAMjD,MAAM,UAA8B;MAClC,SAAS,IAAI,MAAM;MACnB,OAAO;MACP,OAAO,cAAc,KAAK;KAC5B;KACA,OAAO;MAAE,MAAM;MAAoB;KAAQ;IAC7C;IAGA,OAAO;GACT;GACA;EACF;EACA,SAAS,aAAa;GAMpB,MAAM;GACN,SAAS,OAAO,QAAQ;IAOtB,MAAM,EAAE,UAAU,MAAM,IAAI,QAAQ,MAAM,eAAe,EACvD,SAAS,SACX,CAAC;IACD,MAAM,QAAQ,gBAAgB,KAAK;IACnC,MAAM,QAA8C,CAAC;IACrD,IAAI;KACF,MAAM,IAAI,OACR;MACE,OAAO;MACP,IAAI,IAAI,MAAM;MACd,SAAS,IAAI;MACb,QAAQ,IAAI;MACZ,MAAM,SAAS,KAAK;MACpB,UAAU,cAAc,UAAU,QAAQ,UACxC,IAAI,QAAQ,SACV,UACA,QACA,KACF,CACF;KACF,GACA,IAAI,MAAM,OACZ;IACF,SAAS,OAAO;KASd,IAAI,EAAE,iBAAiB,oBAAoB,MAAM;KACjD,MAAM,UAA8B;MAClC,SAAS,IAAI,MAAM;MACnB,OAAO;MACP,OAAO,cAAc,KAAK;KAC5B;KACA,OAAO;MAAE,MAAM;MAAoB;KAAQ;IAC7C;IAMA,MAAM,UAAyC;KAC7C,OAAO;KACP,OAAO;KACP,SAAS,IAAI,MAAM;IACrB;IACA,OAAO,CAAC,GAAG,OAAO;KAAE,MAAM;KAAmB;IAAQ,CAAC;GACxD;EACF;CACF;CAEA,MAAM,kBAAmB,QAAmC,aAAa;CAIzE,MAAMA,aAAW,kBACbC,SAAe;EAAE;EAAM;EAAQ,UAAU;CAAiB,CAAC,IAC3DA,SAAe;EAAE;EAAM;CAAO,CAAC;CACnC,MAAM,SAAS,aAAa;EAC1B,UAAA;EACA;EACA,GAAI,QAAQ,QAAQ,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;EAChD,GAAI,QAAQ,YAAY,EAAE,WAAW,QAAQ,UAAU,IAAI,CAAC;EAC5D,GAAI,QAAQ,YAAY,EAAE,WAAW,QAAQ,UAAU,IAAI,CAAC;CAC9D,CAAC;CAID,oBAAoB,QAAQ,EAC1B,kBAAkB,EAAE,QAAQ,aAAa;EACvC,KAAK,MAAM,SAAS,QAClB,IAAI,MAAM,KAAK,WAAW,WAAW,GACnC,MAAM,IAAI,UACR,IAAI,MAAM,KAAK,0DACjB;CAGN,EACF,CAAC;CAED,MAAM,cAAc,OAA+B;EACjD,MAAM,UAAU,OAAO,QAAQ,EAAE;EAEjC,MAAM,YAAY,OAChB,WACA,OACA,gBAC2C;GAC3C,MAAM,CAAC,WAAW,MAAM,QAAQ,OAAO;IACrC,MAAM;IACN,SAAS,SAAS,CAAC;IACnB,GAAI,aAAa,KAAK,EAAE,IAAI,YAAY,GAAG,IAAI,CAAC;GAClD,CAAC;GACD,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,0BAA0B;GACxD,OAAO;IAAE,IAAI,QAAQ;IAAI,OAAO,QAAQ;GAAM;EAChD;EAEA,MAAM,YAAY,OAChB,WACA,OACA,gBACyC;GACzC,IAAI,gBAAgB,IAAI,SAAS,CAAC,EAAE,SAAS,cAC3C,MAAM,IAAI,UACR,UAAU,KAAK,MAAM,UAAU,kEACjC;GAEF,MAAM,UAAU,MAAM,UAAU,WAAW,OAAO,WAAW;GAC7D,MAAM,WAAW,KAAK,IAAI,KAAK,aAAa,aAAa;GACzD,IAAI,SAAS,QAAQ,QAAQ;GAC7B,SAAS;IAGP,MAAM,OAAO,MAAM,EAAE;IACrB,MAAM,OAAO,MAAM,QAAQ,QAAQ,EAAE,KAAK,OAAO,CAAC;IAClD,KAAK,MAAM,SAAS,MAAM;KACxB,IAAI,MAAM,SAAA,kBAA4B;MACpC,MAAM,UAAU,MAAM;MACtB,IAAI,QAAQ,YAAY,QAAQ,IAC9B,OAAO;OAAE,OAAO,QAAQ;OAAO,OAAO,MAAM;MAAM;KAEtD;KACA,IAAI,MAAM,SAAA,mBAA6B;MACrC,MAAM,UAAU,MAAM;MACtB,IAAI,QAAQ,YAAY,QAAQ,IAC9B,MAAM,IAAI,kBAAkB,WAAW,QAAQ,IAAI,QAAQ,KAAK;KAEpE;IACF;IACA,MAAM,OAAO,KAAK,KAAK,SAAS;IAChC,IAAI,MAAM,SAAS,KAAK,QAAQ;IAChC,IAAI,KAAK,IAAI,KAAK,UAChB,MAAM,IAAI,MACR,UAAU,KAAK,4BAA4B,UAAU,aAAa,QAAQ,GAAG,EAC/E;IAEF,MAAM,MAAM,YAAY;GAC1B;EACF;EAEA,MAAM,YAAY,YAAkD;GAClE,MAAM,EAAE,OAAO,UAAU,MAAM,QAAQ,MAAM,aAAa;GAC1D,OAAO;IAAE;IAAO;GAAM;EACxB;EAEA,MAAM,aACJ,MACA,UACY;GACZ,MAAM,SAAS,KAAK,KAAK;GACzB,IAAI,CAAC,WAAW,MAAM,GACpB,MAAM,IAAI,UACR,UAAU,KAAK,2DACjB;GAEF,OAAO;EACT;EAEA,MAAM,eAAe,iBACnB,YACA,MACA;GAMA,MAAM,SAIF,kBAEE,QAMA,OAAO;IAAE;IAAY,UAAU;GAAK,CAAC,IACvC,QAAQ,OAAO,EAAE,WAAW,CAAC;GACjC,WAAW,MAAM,QAAQ,QAAQ;IAC/B,IAAI,EAAE,UAAU,OAAO;KACrB,MAAM;KACN;IACF;IACA,MAAM,QAAQ;IAId,IAAI,MAAM,SAAA,kBAA4B;IACtC,IAAI,CAAC,MAAM;KACT,MAAM;KACN;IACF;IACA,MAAM,UAAU,MAAM;IACtB,MAAM;KACJ,GAAG;KACH,SAAS;MAAE,GAAG;MAAS,OAAO,UAAU,MAAM,QAAQ,KAAK;KAAE;IAC/D;GACF;EACF;EAOA,MAAM,cAAc,OAClB,SACA,iBACsB;GACtB,MAAM,aAAa,OAAO,cACvB,MAAM,cAAc,YAAY,OAAO,OAAO,SAAS,CAAC,MAAO;GAElE,IAAI,QAAQ,WAAW,OAAO;IAC5B,MAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;IAC/B,IAAI,IAAI,aAAa,IAAI,UAAU,GAAG;KACpC,IAAI,CAAE,MAAM,WAAW;MAAE,MAAM;MAAS;KAAG,CAAC,GAAI,OAAO,UAAU;KACjE,MAAM,SAAS,MAAM,UAAU;KAC/B,OAAO,SAAS,KACd;MACE,OAAO,cAAc,OACjB,UAAU,aAAa,MAAM,OAAO,KAAK,IACzC,OAAO;MACX,OAAO,OAAO;KAChB,GACA,EAAE,SAAS,EAAE,iBAAiB,oBAAoB,EAAE,CACtD;IACF;IACA,MAAM,MAAM,OAAO,IAAI,aAAa,IAAI,OAAO,CAAC;IAChD,MAAM,aAAa,OAAO,cAAc,GAAG,KAAK,OAAO,IAAI,MAAM;IACjE,IAAI,CAAE,MAAM,WAAW;KAAE,MAAM;KAAU;KAAI;IAAW,CAAC,GACvD,OAAO,UAAU;IAEnB,OAAO,YAAY,aAAa,YAAY,cAAc,IAAI,CAAC;GACjE;GACA,IAAI,QAAQ,WAAW,QACrB,OAAO,IAAI,SAAS,MAAM;IACxB,QAAQ;IACR,SAAS,EAAE,OAAO,YAAY;GAChC,CAAC;GAEH,IAAI;GACJ,IAAI;IACF,OAAO,MAAM,QAAQ,KAAK;GAC5B,QAAQ;IACN,OAAO,SAAS,KAAK,EAAE,OAAO,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;GACtE;GACA,MAAM,SACJ,OAAO,SAAS,YAAY,SAAS,OAChC,OACD,CAAC;GACP,IAAI,cAAc,QAAQ;IAIxB,IAAI,CAAC,iBACH,OAAO,SAAS,KACd,EAAE,OAAO,UAAU,KAAK,wBAAwB,GAChD,EAAE,QAAQ,IAAI,CAChB;IAEF,MAAM,eAAe,OAAO;IAC5B,IACE,iBAAiB,KAAA,MAChB,CAAC,MAAM,QAAQ,YAAY,KAAK,aAAa,SAAS,IAEvD,OAAO,SAAS,KACd,EAAE,OAAO,+CAA+C,GACxD,EAAE,QAAQ,IAAI,CAChB;IAEF,IAAI;IACJ,IAAI;KACF,MAAM,SAAS,qBAAqB,OAAO,WAAW;KACtD,IAAI,CAAC,QAAQ,MAAM,IAAI,UAAU,4BAA4B;KAC7D,QAAQ;IACV,SAAS,OAAO;KACd,OAAO,SAAS,KAAK,EAAE,OAAO,cAAc,KAAK,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;IACvE;IACA,IACE,CAAE,MAAM,WAAW;KACjB,MAAM;KACN;KACA,aAAa,MAAM;KACnB,QAAQ,MAAM;IAChB,CAAC,GAED,OAAO,UAAU;IAEnB,IAAI;KACF,MACE,QAGA,YAAY,KAAK;IACrB,SAAS,OAAO;KACd,IACE,iBAAiB,YAChB,MAAM,SAAS,qBACd,MAAM,SAAS,2BAEjB,OAAO,SAAS,KAAK,EAAE,OAAO,MAAM,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;KAEhE,MAAM;IACR;IACA,OAAO,SAAS,KAAK,CAAC,CAAC;GACzB;GACA,MAAM,YAAY,OAAO;GACzB,IACE,OAAO,cAAc,YACrB,gBAAgB,IAAI,SAAS,CAAC,EAAE,SAAS,UAEzC,OAAO,SAAS,KAAK,EAAE,OAAO,gBAAgB,GAAG,EAAE,QAAQ,IAAI,CAAC;GAElE,MAAM,YAAY,OAAO;GACzB,IAAI,cAAc,KAAA,KAAa,OAAO,cAAc,UAClD,OAAO,SAAS,KAAK,EAAE,OAAO,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;GAEtE,MAAM,QAAQ,OAAO;GACrB,IACE,CAAE,MAAM,WAAW;IACjB,MAAM;IACN;IACA,OAAO;IACP;IACA,GAAI,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU;GACjD,CAAC,GAED,OAAO,UAAU;GAEnB,IAAI;IACF,MAAM,SAAS,MAAM,UACnB,WACA,OACA,cAAc,KAAA,IAAY,KAAA,IAAY,EAAE,IAAI,UAAU,CACxD;IACA,OAAO,SAAS,KACd,cAAc,OACV;KACE,OAAO,UAAU,aAAa,MAAM,OAAO,KAAK;KAChD,OAAO,OAAO;IAChB,IACA,QACJ,EAAE,SAAS,EAAE,iBAAiB,oBAAoB,EAAE,CACtD;GACF,SAAS,OAAO;IACd,IAAI,iBAAiB,mBACnB,OAAO,SAAS,KACd;KACE,OAAO,MAAM;KACb,OAAO,MAAM;KACb,WAAW,MAAM;IACnB,GACA,EAAE,QAAQ,IAAI,CAChB;IAEF,IACE,iBAAiB,YAChB,MAAM,SAAS,qBACd,MAAM,SAAS,uBAEjB,OAAO,SAAS,KAAK,EAAE,OAAO,MAAM,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;IAEhE,MAAM;GACR;EACF;EAEA,MAAM,OAAgC,CAAC;EACvC,MAAM,OAAgC,CAAC;EACvC,KAAK,MAAM,aAAa,gBAAgB,KAAK,GAAG;GAC9C,OAAO,eAAe,MAAM,WAAW;IACrC,QAAQ,OAAiB,gBACvB,UAAU,WAAW,OAAO,WAAW;IACzC,YAAY;IACZ,cAAc;IACd,UAAU;GACZ,CAAC;GACD,OAAO,eAAe,MAAM,WAAW;IACrC,QAAQ,OAAiB,gBACvB,UAAU,WAAW,OAAO,WAAW;IACzC,YAAY;IACZ,cAAc;IACd,UAAU;GACZ,CAAC;EACH;EAUA,OAAO;GAPL;GACA;GACM;GACA;GACN,OAAO;GACP,OAAO;EAEG;CACd;CAEA,OAAO;EACL;EACA,UAAA;EACA;EACA;EACA,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"actor.js","names":["createContract"],"sources":["../src/actor.ts"],"sourcesContent":["/**\n * experimental-a2/actor — durable actors over the log. Server-only\n * (handlers are server code); the browser condition resolves to a\n * throwing stub, and the browser surfaces are experimental-a2/actor/client\n * and experimental-a2/actor/react.\n *\n * An actor is defined over a protocol — its state shape and event\n * vocabulary, as types — and one complete `handlers` bag:\n *\n * ```ts\n * interface Counter {\n * state: { count: number }\n * events: { increment: object; add: { by: number } }\n * }\n * const counter = actor<Counter>({\n * name: 'counter',\n * state: { count: 0 },\n * handlers: {\n * increment: (ctx) => {\n * ctx.state.count++\n * },\n * add: (ctx, input) => {\n * ctx.state.count += input.by\n * },\n * },\n * })\n * ```\n *\n * Handlers are serial by default: one at a time per instance (a lane —\n * the state's write lock), `ctx.state` a mutable draft committed\n * atomically with completion. A handler holding slow I/O opts out of\n * the lane with `{ concurrent: true, handle }`: it runs in parallel,\n * reads state as a snapshot, and mutates by sending events whose\n * serial handlers decide against fresh state. `ctx.send.eventName(...)`\n * is typed by the protocol and buffered — committed atomically with\n * the handler's completion, so a reserve and its trigger can never\n * half-happen.\n *\n * A thrown `NonRetriableError` is an answer (settled, delivered to a\n * waiting caller); any other error retries like every A2 handler\n * failure; a crash re-runs against the same pre-state. Everything\n * compiles to contract + reducer + laned handlers + returned events —\n * no new store operations, no new wire.\n */\n\n// oxlint-disable no-await-in-loop -- a call awaits its answer: drain, read\n// the tail, repeat until the message settles. Sequential by nature.\n\nimport {\n ACTOR_FAILED_EVENT,\n ACTOR_LANE,\n ACTOR_STATE_EVENT,\n ActorRefusedError,\n actorFailedSchema,\n actorReducer,\n actorStateSchema,\n isJsonTree,\n openPresenceDefs,\n} from './actor-shared.ts'\nimport type {\n ActorClientEventDefs,\n ActorConcurrentContext,\n ActorContext,\n ActorFailedPayload,\n ActorPresenceOf,\n ActorProtocol,\n ActorScheduleOptions,\n ActorScheduleSend,\n ActorSend,\n ActorStatePayload,\n} from './actor-shared.ts'\nimport { parsePresenceSibling } from './push-envelope.ts'\nimport type { ParsedPushPresence } from './push-envelope.ts'\nimport { internalContract as createContract } from './contract.ts'\nimport type { PresencePatch, PresenceSnapshot } from './contract.ts'\nimport type { Contract, EventDefs } from './contract.ts'\nimport type { Reducer } from './reducer.ts'\nimport { A2Error, NonRetriableError } from './errors.ts'\nimport { serverInternals } from './internal.ts'\nimport { createServer } from './server.ts'\nimport { setServerFetchHooks } from './server-fetch.ts'\nimport { sseResponse } from './sse.ts'\nimport { isSessionCleared, sessionClearedError } from './session-clear.ts'\nimport type { SessionCleared } from './session-clear.ts'\nimport type { A2Scheduler, A2Server, HandlerEntry, Session } from './server.ts'\nimport type { A2Store } from './store.ts'\nimport type { StandardSchemaV1 } from './standard-schema.ts'\nimport type { A2Telemetry } from './telemetry.ts'\n\nexport type {\n ActorConcurrentContext,\n ActorContext,\n ActorPresenceMap,\n ActorPresenceOf,\n ActorPresenceValues,\n ActorProtocol,\n ActorScheduleOptions,\n ActorScheduleSend,\n ActorSend,\n} from './actor-shared.ts'\nexport { ActorRefusedError } from './actor-shared.ts'\n\n/**\n * One event's handler: ordinary server code. Serial by default — read\n * `ctx.state`, perform I/O, mutate `ctx.state`, one atomic commit at\n * return. `{ concurrent: true, handle }` opts the handler out of the\n * lane for slow I/O: state becomes a snapshot read and mutations\n * happen by sending events. The input is typed by the protocol; the\n * wire hands handlers parsed JSON, so a wire-exposed event guards its\n * input in its first line (`schema.parse(input)` if you like schemas —\n * userland either way).\n */\nexport type ActorHandler<D extends ActorProtocol, K extends keyof D['events']> =\n | ((ctx: ActorContext<D>, input: D['events'][K]) => void | Promise<void>)\n | {\n concurrent: true\n handle: (\n ctx: ActorConcurrentContext<D>,\n input: D['events'][K],\n ) => void | Promise<void>\n }\n\n/**\n * Mark a handler concurrent — off the lane, in parallel with the lane\n * and with other concurrent handlers, `ctx.state()` a snapshot read.\n * Sugar for the structural `{ concurrent: true, handle }` form:\n *\n * ```ts\n * handlers: {\n * transfer: concurrent(async (ctx, input) => {\n * await bank.transfer(input, { idempotencyKey: input.ref })\n * ctx.send.settle({ ref: input.ref })\n * }),\n * }\n * ```\n */\nexport function concurrent<Ctx, I>(\n handle: (ctx: Ctx, input: I) => void | Promise<void>,\n): { concurrent: true; handle: (ctx: Ctx, input: I) => void | Promise<void> } {\n return { concurrent: true, handle }\n}\n\nexport type ActorOptions<D extends ActorProtocol> = {\n /** Identity — prefixes storage keys; one contract per actor definition. */\n name: string\n /** The initial state — checked against the protocol, a plain JSON tree. */\n state: D['state']\n /**\n * One handler per declared event — completeness and payload shapes\n * are compile-checked against the protocol.\n */\n handlers: { [K in keyof D['events']]: ActorHandler<D, K> }\n store?: A2Store\n scheduler?: A2Scheduler\n telemetry?: A2Telemetry\n} & ([ActorPresenceOf<D>] extends [never]\n ? { presence?: never }\n : {\n /**\n * The wire bit for the protocol's `presence` vocabulary — types\n * erase, so the runtime needs one value to arm the presence\n * lanes (frames on GET, envelopes on POST). Required exactly\n * when the protocol declares `presence`; the vocabulary itself\n * is typed there. Values are validated to the JSON floor and\n * pass through `authorize` as `{ type: 'presence' }` operations;\n * meaning stays with your renderers — treat values as user\n * input.\n */\n presence: true\n })\n\n/**\n * One HTTP operation `handle.fetch` is about to serve — the actor\n * mirror of core's `A2Operation`. `authorize` sees it before any\n * read, write, or subscription; returning `false` answers 403.\n */\nexport type ActorOperation =\n | { readonly type: 'state'; readonly id: string }\n | {\n readonly type: 'stream'\n readonly id: string\n readonly startAfter: number\n }\n | {\n readonly type: 'call'\n readonly id: string\n readonly event: string\n readonly input: unknown\n readonly messageId?: string\n }\n | {\n readonly type: 'presence'\n readonly id: string\n readonly participant: string\n readonly values: Readonly<Record<string, unknown>>\n }\n\nexport type ActorFetchOptions<D extends ActorProtocol> = {\n /** Per-operation policy — authentication happened in your route. */\n authorize?: (operation: ActorOperation) => boolean | Promise<boolean>\n /**\n * Request-time projection: what this mount's audience sees. Applied\n * to every state frame on the stream AND to call answers (the two\n * untrusted lanes must agree, or the call lane leaks what the\n * stream hides). The definition stays audience-agnostic — different\n * routes project the same actor differently, and per-viewer views\n * are just closures over the route's auth. Output is held to the\n * JSON-tree floor like everything else. Transport-agnostic: applied\n * at frame emission, so any wire the door speaks emits projected\n * frames. Absent, the full state ships (the trusted default).\n */\n view?: (state: D['state']) => unknown\n}\n\nexport type ActorSendOptions = {\n /** Explicit message id — makes retries of this send idempotent. */\n id?: string\n}\n\nexport type ActorCallOptions = ActorSendOptions & {\n /** How long to await the answer before rejecting the wait (default 30s). */\n timeoutMs?: number\n}\n\nexport type ActorCallResult<S> = {\n /** The actor's state after this message was processed. */\n state: S\n /** Log index of the state commit that answered this message. */\n index: number\n /**\n * The clear marker this state's lifetime began at, once the actor has been\n * cleared. For a call answer it is the lifetime the handler read.\n */\n clearIndex?: number\n}\n\ntype CallMethod<\n D extends ActorProtocol,\n K extends keyof D['events'],\n> = {} extends D['events'][K]\n ? (\n input?: D['events'][K],\n options?: ActorCallOptions,\n ) => Promise<ActorCallResult<D['state']>>\n : (\n input: D['events'][K],\n options?: ActorCallOptions,\n ) => Promise<ActorCallResult<D['state']>>\n\ntype SendMethod<\n D extends ActorProtocol,\n K extends keyof D['events'],\n> = {} extends D['events'][K]\n ? (\n input?: D['events'][K],\n options?: ActorSendOptions,\n ) => Promise<{ id: string; index: number }>\n : (\n input: D['events'][K],\n options?: ActorSendOptions,\n ) => Promise<{ id: string; index: number }>\n\n/** One instance: typed calls and sends, reads, the managed door, the escape hatch. */\nexport type ActorHandle<D extends ActorProtocol> = {\n readonly id: string\n /**\n * Send the event and await its answer: the state after the handler\n * ran, or a rejection with `ActorRefusedError`. A clear before the call\n * receives its answer rejects with `A2Error('SESSION_CLEARED')`. Serial\n * events only — concurrent handlers produce no answer to await; `send`\n * them.\n */\n readonly call: { readonly [K in keyof D['events']]: CallMethod<D, K> }\n /** Cast: validate, append the event durably, return without waiting. */\n readonly send: { readonly [K in keyof D['events']]: SendMethod<D, K> }\n /** Snapshot read — never queues behind pending messages. */\n readonly state: () => Promise<ActorCallResult<D['state']>>\n /**\n * The managed door — the actor mirror of `server.fetch`, bound to\n * this instance. GET streams the state plane (`a2.actor.state`\n * commits and clear resets, `?index` resume, heartbeat, deadline\n * rotation); POST is the call lane `createActorClient` speaks\n * (`{ event, input, messageId? }` → the answer, 409 for a refusal,\n * 410 for a call cleared before its answer). Your route\n * authenticates and picks the instance; `authorize` sees every\n * operation. Custom endpoints are route code around this call.\n */\n readonly fetch: (\n request: Request,\n options?: ActorFetchOptions<D>,\n ) => Promise<Response>\n /** Escape hatch: the raw A2 session (history, stream, schedule). */\n readonly session: Session<EventDefs>\n}\n\nexport type ActorDefinition<D extends ActorProtocol> = {\n readonly name: string\n /**\n * Type-only carrier of the protocol, so clients deriving from\n * `typeof def` can see the vocabularies (`experimental-a2/actor/react`\n * types `presence` from it). Never a runtime value.\n */\n readonly protocol?: D\n /** The assembled contract — declared events plus the reserved a2.actor.* pair. */\n readonly contract: Contract<EventDefs>\n /** The underlying A2 server — `server.fetch` mounts the full session wire. */\n readonly server: A2Server<EventDefs>\n /** The state fold over commits; a clear resets it to the initial state. */\n readonly reducer: Reducer<ActorClientEventDefs<D['state']>, D['state']>\n actor(id: string): ActorHandle<D>\n}\n\n// Object-mechanics names, not surface names: events live in their own\n// bags, so nothing else is reserved.\nconst UNSAFE_EVENT_NAMES = new Set(['__proto__', 'constructor', 'prototype'])\n\nconst forbidden = (): Response =>\n Response.json({ error: 'the operation is not authorized' }, { status: 403 })\n\nconst CALL_TIMEOUT_MS = 30_000\nconst CALL_POLL_MS = 25\n\n/** Declared events accept any JSON tree; `undefined` normalizes to `{}`. */\nconst jsonInputSchema: StandardSchemaV1<unknown> = {\n '~standard': {\n version: 1,\n vendor: 'a2',\n validate(value) {\n if (value === undefined || value === null) return { value: {} }\n if (!isJsonTree(value)) {\n return {\n issues: [{ message: 'event input must be a plain JSON tree' }],\n }\n }\n return { value }\n },\n },\n}\n\nconst describeError = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\nconst sleep = (ms: number): Promise<void> =>\n new Promise((resolve) => setTimeout(resolve, ms))\n\ntype NormalizedHandler<D extends ActorProtocol> =\n | {\n kind: 'serial'\n handle: (ctx: ActorContext<D>, input: unknown) => void | Promise<void>\n }\n | {\n kind: 'concurrent'\n handle: (\n ctx: ActorConcurrentContext<D>,\n input: unknown,\n ) => void | Promise<void>\n }\n\n/**\n * Define and serve an actor over its protocol: a named, durable\n * instance-per-id with serialized state handlers, concurrent I/O\n * handlers, and typed self-sends.\n */\nexport function actor<D extends ActorProtocol>(\n options: ActorOptions<D>,\n): ActorDefinition<D> {\n const { name } = options\n\n const handlersByEvent = new Map<string, NormalizedHandler<D>>()\n const events: Record<string, StandardSchemaV1> = {\n [ACTOR_STATE_EVENT]: actorStateSchema(),\n [ACTOR_FAILED_EVENT]: actorFailedSchema,\n }\n for (const [eventName, def] of Object.entries(\n options.handlers as Record<string, ActorHandler<D, keyof D['events']>>,\n )) {\n if (UNSAFE_EVENT_NAMES.has(eventName)) {\n throw new TypeError(\n `actor '${name}': '${eventName}' collides with object plumbing and cannot name an event`,\n )\n }\n if (eventName.startsWith('a2.')) {\n throw new TypeError(\n `actor '${name}': event names starting with 'a2.' are reserved`,\n )\n }\n handlersByEvent.set(\n eventName,\n typeof def === 'function'\n ? {\n kind: 'serial',\n handle: def as (\n ctx: ActorContext<D>,\n input: unknown,\n ) => void | Promise<void>,\n }\n : {\n kind: 'concurrent',\n handle: def.handle as (\n ctx: ActorConcurrentContext<D>,\n input: unknown,\n ) => void | Promise<void>,\n },\n )\n events[eventName] = jsonInputSchema\n }\n\n const reducer = actorReducer({ name, state: options.state })\n // The server's contract carries every event type; the reducer only\n // folds state commits. Same fold, same identity — the widening is a\n // typing formality.\n const serverReducer = reducer as unknown as Reducer<EventDefs, D['state']>\n\n // The typed durable-timer surface: immediate handoff to the\n // configured scheduler through the handler session, so timer\n // identity is scoped to the triggering message and re-runs address\n // the same timer (core §6). Not buffered — see ActorScheduleSend.\n const makeSchedule = (\n schedule: (\n name: string,\n timing: { delay?: string; at?: Date },\n event: { type: string; payload: unknown },\n ) => Promise<void>,\n ): ActorScheduleSend<D['events']> => {\n const surface: Record<string, unknown> = {}\n for (const eventName of handlersByEvent.keys()) {\n Object.defineProperty(surface, eventName, {\n value: (input: unknown, scheduleOptions: ActorScheduleOptions) => {\n const { name: taskName, ...timing } = scheduleOptions\n return schedule(taskName ?? eventName, timing, {\n type: eventName,\n payload: input ?? {},\n })\n },\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n return surface as ActorScheduleSend<D['events']>\n }\n\n // The typed self-send surface: per-invocation buffers behind one\n // shared method record shape. defineProperty so event names like\n // 'constructor'-adjacent identifiers can never touch prototypes.\n const makeSend = (\n buffer: { type: string; payload: unknown }[],\n ): ActorSend<D['events']> => {\n const send: Record<string, unknown> = {}\n for (const eventName of handlersByEvent.keys()) {\n Object.defineProperty(send, eventName, {\n value: (input?: unknown) => {\n buffer.push({ type: eventName, payload: input ?? {} })\n },\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n return send as ActorSend<D['events']>\n }\n\n const handlers: Record<string, HandlerEntry<EventDefs>> = {}\n for (const [eventName, def] of handlersByEvent) {\n if (def.kind === 'concurrent') {\n // No lane: concurrent handlers run in parallel — with the lane\n // and with each other. They cannot write state (no draft to\n // commit), so no serialization is needed for correctness; slow\n // I/O lives here without blocking the actor.\n handlers[eventName] = async (ctx) => {\n const sends: { type: string; payload: unknown }[] = []\n try {\n await def.handle(\n {\n id: ctx.event.id,\n attempt: ctx.attempt,\n signal: ctx.signal,\n send: makeSend(sends),\n schedule: makeSchedule((taskName, timing, event) =>\n ctx.session.schedule(\n taskName,\n timing as Parameters<typeof ctx.session.schedule>[1],\n event,\n ),\n ),\n state: async () => {\n const { state, index } = await ctx.session.state(\n serverReducer,\n { through: 'latest' },\n )\n return { state, index }\n },\n },\n ctx.event.payload,\n )\n } catch (error) {\n if (!(error instanceof NonRetriableError)) throw error\n // A terminal failure is recorded for audit; nothing awaits a\n // concurrent handler, so the marker is history, not an\n // answer. Buffered sends are discarded — refusals are total,\n // so compensate by completing (send then return), never by\n // throwing.\n const payload: ActorFailedPayload = {\n message: ctx.event.id,\n event: eventName,\n error: describeError(error),\n }\n return { type: ACTOR_FAILED_EVENT, payload }\n }\n // Sends commit atomically with settlement; re-runs converge on\n // the same deterministic message ids.\n return sends\n }\n continue\n }\n handlers[eventName] = {\n // One lane for every serial handler of one instance: the next\n // message starts only after the previous one completed, and\n // completion commits the state event atomically —\n // read-modify-write on `ctx.state` is safe by exclusion, not by\n // liveness.\n lane: ACTOR_LANE,\n handler: async (ctx) => {\n // Explicitly 'latest', not the event-relative default: prior\n // lane messages commit *after* this message was appended, so\n // their state commits carry higher indexes than the trigger —\n // an event-relative read would miss them and lose updates.\n // 'latest' is deterministic here because the lane makes this\n // attempt the only possible writer while it runs.\n const { state, clearIndex } = await ctx.session.state(serverReducer, {\n through: 'latest',\n })\n const draft = structuredClone(state)\n const sends: { type: string; payload: unknown }[] = []\n try {\n await def.handle(\n {\n state: draft,\n id: ctx.event.id,\n attempt: ctx.attempt,\n signal: ctx.signal,\n send: makeSend(sends),\n schedule: makeSchedule((taskName, timing, event) =>\n ctx.session.schedule(\n taskName,\n timing as Parameters<typeof ctx.session.schedule>[1],\n event,\n ),\n ),\n },\n ctx.event.payload,\n )\n } catch (error) {\n // A NonRetriableError is an answer: settle the message,\n // record the refusal, deliver it to the waiting caller —\n // no retry, no failure budget spent (buffered sends are\n // discarded with the draft: refusals are total). Every\n // other throw is an ordinary A2 handler failure: rethrown\n // into core's retry machinery (backoff, ten-failure\n // budget, dead letter) — including surrendered lease\n // aborts, which core recognizes by identity.\n if (!(error instanceof NonRetriableError)) throw error\n const payload: ActorFailedPayload = {\n message: ctx.event.id,\n event: eventName,\n error: describeError(error),\n }\n return { type: ACTOR_FAILED_EVENT, payload }\n }\n // One returned batch: buffered sends plus the state commit —\n // committed atomically with completion, so a reserve and its\n // trigger can never half-happen. The state event stays the\n // outcome marker a waiting call resolves on. (Unconditional\n // for now; a dirty check is a compatible later optimization.)\n const payload: ActorStatePayload<D['state']> = {\n state: draft,\n event: eventName,\n message: ctx.event.id,\n ...(clearIndex === undefined ? {} : { clearIndex }),\n }\n return [...sends, { type: ACTOR_STATE_EVENT, payload }]\n },\n }\n }\n\n const presenceEnabled = (options as { presence?: unknown }).presence === true\n // The '*' catch-all arms an open server-side vocabulary: the\n // protocol types field names at compile time, the floor validates\n // every value, and `authorize` sees every set. See openPresenceDefs.\n const contract = presenceEnabled\n ? createContract({ name, events, presence: openPresenceDefs })\n : createContract({ name, events })\n const server = createServer({\n contract,\n handlers,\n ...(options.store ? { store: options.store } : {}),\n ...(options.scheduler ? { scheduler: options.scheduler } : {}),\n ...(options.telemetry ? { telemetry: options.telemetry } : {}),\n })\n // Single-writer discipline on the underlying session wire too: if an\n // application mounts `def.server.fetch`, browsers may push declared\n // events, never authored state or failure records.\n setServerFetchHooks(server, {\n validateIngress: ({ events: pushed }) => {\n for (const event of pushed) {\n if (event.type.startsWith('a2.actor.')) {\n throw new TypeError(\n `'${event.type}' is reserved — only a handler's completion can author it`,\n )\n }\n }\n },\n })\n\n const internals = serverInternals.get(server)!\n\n const makeHandle = (id: string): ActorHandle<D> => {\n const session = server.session(id)\n\n const sendEvent = async (\n eventName: string,\n input: unknown,\n sendOptions?: ActorSendOptions,\n ): Promise<{ id: string; index: number }> => {\n const [message] = await session.append({\n type: eventName,\n payload: input ?? {},\n ...(sendOptions?.id ? { id: sendOptions.id } : {}),\n })\n if (!message) throw new Error('append returned no event')\n return { id: message.id, index: message.index }\n }\n\n const callEvent = async (\n eventName: string,\n input: unknown,\n callOptions?: ActorCallOptions,\n ): Promise<ActorCallResult<D['state']>> => {\n if (handlersByEvent.get(eventName)?.kind === 'concurrent') {\n throw new TypeError(\n `actor '${name}': '${eventName}' is concurrent — it produces no answer to await; send it instead`,\n )\n }\n const message = await sendEvent(eventName, input, callOptions)\n const deadline = Date.now() + (callOptions?.timeoutMs ?? CALL_TIMEOUT_MS)\n let cursor = message.index + 1\n for (;;) {\n // Drain participates in processing (usually answering inline in\n // this invocation); claims arbitrate if another process runs.\n await server.drain(id)\n const tail = await internals.historyWithMarkers(id, cursor)\n for (const event of tail) {\n // Reads start at the latest marker, so a clear after the message\n // arrives before any answer from the next lifetime, including one\n // from a message that reuses this id.\n if (isSessionCleared(event)) {\n throw sessionClearedError(\n `actor '${name}': '${eventName}' (message ${message.id}) was cleared before the call received its answer`,\n )\n }\n if (event.type === ACTOR_STATE_EVENT) {\n const payload = event.payload as ActorStatePayload<D['state']>\n if (payload.message === message.id) {\n return {\n state: payload.state,\n index: event.index,\n ...(payload.clearIndex === undefined\n ? {}\n : { clearIndex: payload.clearIndex }),\n }\n }\n }\n if (event.type === ACTOR_FAILED_EVENT) {\n const payload = event.payload as ActorFailedPayload\n if (payload.message === message.id) {\n throw new ActorRefusedError(eventName, message.id, payload.error)\n }\n }\n }\n const last = tail[tail.length - 1]\n if (last) cursor = last.index + 1\n if (Date.now() >= deadline) {\n throw new Error(\n `actor '${name}': timed out waiting for '${eventName}' (message ${message.id})`,\n )\n }\n await sleep(CALL_POLL_MS)\n }\n }\n\n const readState = async (): Promise<ActorCallResult<D['state']>> => {\n const { state, index, clearIndex } = await session.state(serverReducer)\n return clearIndex === undefined\n ? { state, index }\n : { state, index, clearIndex }\n }\n\n /** A snapshot or call answer through the route's view. */\n const projected = (\n result: ActorCallResult<D['state']>,\n view: ((state: D['state']) => unknown) | undefined,\n ): ActorCallResult<unknown> =>\n view ? { ...result, state: applyView(view, result.state) } : result\n\n const applyView = (\n view: (state: D['state']) => unknown,\n state: D['state'],\n ): unknown => {\n const viewed = view(state)\n if (!isJsonTree(viewed)) {\n throw new TypeError(\n `actor '${name}': the view returned a value that is not a plain JSON tree`,\n )\n }\n return viewed\n }\n\n const streamStates = async function* (\n startAfter: number,\n view?: (state: D['state']) => unknown,\n ) {\n // With presence armed, the session interleaves presence items\n // (snapshot, then patches) between log events. They are\n // ephemeral audience data, not actor state: they pass through\n // untouched — views project state, never peers — and they never\n // advance the resume frontier.\n const source: AsyncIterable<\n | Awaited<ReturnType<typeof session.history>>[number]\n | SessionCleared\n | PresencePatch\n | PresenceSnapshot\n > = presenceEnabled\n ? (\n session as unknown as {\n stream(options: {\n startAfter: number\n presence: true\n }): AsyncIterable<PresencePatch | PresenceSnapshot>\n }\n ).stream({ startAfter, presence: true })\n : session.stream({ startAfter })\n for await (const item of source) {\n if (!('type' in item)) {\n yield item\n continue\n }\n const event = item\n if (isSessionCleared(event)) {\n // The follower has no seed of its own: the marker carries the\n // actor's initial state, projected like any commit.\n const initial = structuredClone(options.state)\n yield {\n ...event,\n payload: { state: view ? applyView(view, initial) : initial },\n }\n continue\n }\n // The state plane only: subscribers see what the actor is,\n // never other callers' inputs. Refusals answer only their\n // caller (the POST 409); the full mailbox stays server-side.\n if (event.type !== ACTOR_STATE_EVENT) continue\n if (!view) {\n yield event\n continue\n }\n const payload = event.payload as ActorStatePayload<D['state']>\n yield {\n ...event,\n payload: { ...payload, state: applyView(view, payload.state) },\n }\n }\n }\n\n // The managed door — the actor mirror of `server.fetch`: GET is\n // the state-plane SSE (`a2.actor.state` commits and clear resets,\n // `?index` resume), POST is the call lane (`{ event, input,\n // messageId? }` → the answer, 409 for a refusal, 410 for a cleared\n // call). Authentication happens in the surrounding route;\n // per-operation policy in `authorize`.\n const handleFetch = async (\n request: Request,\n fetchOptions?: ActorFetchOptions<D>,\n ): Promise<Response> => {\n const authorized = async (operation: ActorOperation): Promise<boolean> =>\n (await fetchOptions?.authorize?.(Object.freeze(operation))) !== false\n\n if (request.method === 'GET') {\n const url = new URL(request.url)\n if (url.searchParams.has('snapshot')) {\n if (!(await authorized({ type: 'state', id }))) return forbidden()\n return Response.json(\n projected(await readState(), fetchOptions?.view),\n {\n headers: { 'cache-control': 'private, no-store' },\n },\n )\n }\n const raw = Number(url.searchParams.get('index'))\n const startAfter = Number.isSafeInteger(raw) && raw >= 0 ? raw : 0\n if (!(await authorized({ type: 'stream', id, startAfter }))) {\n return forbidden()\n }\n return sseResponse(streamStates(startAfter, fetchOptions?.view))\n }\n if (request.method !== 'POST') {\n return new Response(null, {\n status: 405,\n headers: { allow: 'GET, POST' },\n })\n }\n let body: unknown\n try {\n body = await request.json()\n } catch {\n return Response.json({ error: 'invalid JSON body' }, { status: 400 })\n }\n const record =\n typeof body === 'object' && body !== null\n ? (body as Record<string, unknown>)\n : {}\n if ('presence' in record) {\n // A presence envelope — what the follower client's\n // `setPresence` posts. Events never ride this lane: writes to\n // the actor are calls.\n if (!presenceEnabled) {\n return Response.json(\n { error: `actor '${name}' declares no presence` },\n { status: 400 },\n )\n }\n const pushedEvents = record['events']\n if (\n pushedEvents !== undefined &&\n (!Array.isArray(pushedEvents) || pushedEvents.length > 0)\n ) {\n return Response.json(\n { error: 'the actor wire takes calls, not event pushes' },\n { status: 400 },\n )\n }\n let patch: ParsedPushPresence\n try {\n const parsed = parsePresenceSibling(record['presence'])\n if (!parsed) throw new TypeError('presence must be an object')\n patch = parsed\n } catch (error) {\n return Response.json({ error: describeError(error) }, { status: 400 })\n }\n if (\n !(await authorized({\n type: 'presence',\n id,\n participant: patch.participant,\n values: patch.values,\n }))\n ) {\n return forbidden()\n }\n try {\n await (\n session as unknown as {\n setPresence(patch: ParsedPushPresence): Promise<void>\n }\n ).setPresence(patch)\n } catch (error) {\n if (\n error instanceof A2Error &&\n (error.code === 'INVALID_PAYLOAD' ||\n error.code === 'UNKNOWN_PRESENCE_FIELD')\n ) {\n return Response.json({ error: error.message }, { status: 400 })\n }\n throw error\n }\n return Response.json([])\n }\n const eventName = record['event']\n if (\n typeof eventName !== 'string' ||\n handlersByEvent.get(eventName)?.kind !== 'serial'\n ) {\n return Response.json({ error: 'unknown event' }, { status: 400 })\n }\n const messageId = record['messageId']\n if (messageId !== undefined && typeof messageId !== 'string') {\n return Response.json({ error: 'invalid messageId' }, { status: 400 })\n }\n const input = record['input']\n if (\n !(await authorized({\n type: 'call',\n id,\n event: eventName,\n input,\n ...(messageId === undefined ? {} : { messageId }),\n }))\n ) {\n return forbidden()\n }\n try {\n const result = await callEvent(\n eventName,\n input,\n messageId === undefined ? undefined : { id: messageId },\n )\n return Response.json(projected(result, fetchOptions?.view), {\n headers: { 'cache-control': 'private, no-store' },\n })\n } catch (error) {\n if (error instanceof ActorRefusedError) {\n return Response.json(\n {\n error: error.message,\n event: error.event,\n messageId: error.messageId,\n },\n { status: 409 },\n )\n }\n if (error instanceof A2Error && error.code === 'SESSION_CLEARED') {\n return Response.json(\n { code: error.code, error: error.message },\n { status: 410 },\n )\n }\n if (\n error instanceof A2Error &&\n (error.code === 'INVALID_PAYLOAD' ||\n error.code === 'UNKNOWN_EVENT_TYPE')\n ) {\n return Response.json({ error: error.message }, { status: 400 })\n }\n throw error\n }\n }\n\n const call: Record<string, unknown> = {}\n const send: Record<string, unknown> = {}\n for (const eventName of handlersByEvent.keys()) {\n Object.defineProperty(call, eventName, {\n value: (input?: unknown, callOptions?: ActorCallOptions) =>\n callEvent(eventName, input, callOptions),\n enumerable: true,\n configurable: true,\n writable: false,\n })\n Object.defineProperty(send, eventName, {\n value: (input?: unknown, sendOptions?: ActorSendOptions) =>\n sendEvent(eventName, input, sendOptions),\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n\n const handle: ActorHandle<D> = {\n id,\n session,\n call: call as ActorHandle<D>['call'],\n send: send as ActorHandle<D>['send'],\n state: readState,\n fetch: handleFetch,\n }\n return handle\n }\n\n return {\n name,\n contract,\n server,\n reducer,\n actor: makeHandle,\n }\n}\n"],"mappings":"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"}