experimental-a2 0.16.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (406) hide show
  1. package/CHANGELOG.md +373 -0
  2. package/README.md +1 -1
  3. package/dist/{actor-ohPC-81x.d.ts → actor-Bjce9_5U.d.ts} +21 -11
  4. package/dist/actor-Bjce9_5U.d.ts.map +1 -0
  5. package/dist/actor-client.d.ts +2 -5
  6. package/dist/actor-client.d.ts.map +1 -1
  7. package/dist/actor-client.js +13 -4
  8. package/dist/actor-client.js.map +1 -1
  9. package/dist/actor-react.d.ts +5 -3
  10. package/dist/actor-react.d.ts.map +1 -1
  11. package/dist/actor-react.js +20 -10
  12. package/dist/actor-react.js.map +1 -1
  13. package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
  14. package/dist/actor-shared-t7w8xIKP.js.map +1 -0
  15. package/dist/actor.d.ts +1 -1
  16. package/dist/actor.js +42 -23
  17. package/dist/actor.js.map +1 -1
  18. package/dist/{ai-DgOBltJ_.js → ai-BpOGm-zB.js} +621 -216
  19. package/dist/ai-BpOGm-zB.js.map +1 -0
  20. package/dist/{ai-CrEf6p_W.d.ts → ai-DrUaMyRt.d.ts} +114 -100
  21. package/dist/ai-DrUaMyRt.d.ts.map +1 -0
  22. package/dist/ai-client.d.ts +27 -0
  23. package/dist/ai-client.d.ts.map +1 -0
  24. package/dist/ai-client.js +29 -0
  25. package/dist/ai-client.js.map +1 -0
  26. package/dist/ai-fx.browser.d.ts +1 -0
  27. package/dist/ai-fx.browser.js +6 -0
  28. package/dist/ai-fx.browser.js.map +1 -0
  29. package/dist/ai-fx.d.ts +10 -0
  30. package/dist/ai-fx.d.ts.map +1 -0
  31. package/dist/ai-fx.js +634 -0
  32. package/dist/ai-fx.js.map +1 -0
  33. package/dist/ai-harness-BuLh_EYi.js +273 -0
  34. package/dist/ai-harness-BuLh_EYi.js.map +1 -0
  35. package/dist/ai-harness-DvsPeJeB.d.ts +26 -0
  36. package/dist/ai-harness-DvsPeJeB.d.ts.map +1 -0
  37. package/dist/ai-pi.browser.d.ts +1 -0
  38. package/dist/ai-pi.browser.js +6 -0
  39. package/dist/ai-pi.browser.js.map +1 -0
  40. package/dist/ai-pi.d.ts +17 -0
  41. package/dist/ai-pi.d.ts.map +1 -0
  42. package/dist/ai-pi.js +904 -0
  43. package/dist/ai-pi.js.map +1 -0
  44. package/dist/ai-react.d.ts +96 -0
  45. package/dist/ai-react.d.ts.map +1 -0
  46. package/dist/ai-react.js +142 -0
  47. package/dist/ai-react.js.map +1 -0
  48. package/dist/ai-server.browser.js +2 -2
  49. package/dist/ai-server.browser.js.map +1 -1
  50. package/dist/ai-server.d.ts +109 -43
  51. package/dist/ai-server.d.ts.map +1 -1
  52. package/dist/ai-server.js +526 -283
  53. package/dist/ai-server.js.map +1 -1
  54. package/dist/ai.d.ts +2 -2
  55. package/dist/ai.js +2 -2
  56. package/dist/bounded-cache-CPHWF-8h.js +41 -0
  57. package/dist/bounded-cache-CPHWF-8h.js.map +1 -0
  58. package/dist/{client-BYzHjkwU.js → client-BReKqwZC.js} +447 -80
  59. package/dist/client-BReKqwZC.js.map +1 -0
  60. package/dist/{client-CzyacQpJ.d.ts → client-h_DKFAjj.d.ts} +14 -13
  61. package/dist/client-h_DKFAjj.d.ts.map +1 -0
  62. package/dist/client.d.ts +4 -2
  63. package/dist/client.js +1 -1
  64. package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
  65. package/dist/contract-BYKASlCV.js.map +1 -0
  66. package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
  67. package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
  68. package/dist/index.d.ts +6 -5
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +4 -3
  71. package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-Bx-c0pDP.d.ts} +7 -3
  72. package/dist/initial-snapshot-Bx-c0pDP.d.ts.map +1 -0
  73. package/dist/{internal-Dq2qYxou.js → internal-B2T3U4Rq.js} +29 -2
  74. package/dist/internal-B2T3U4Rq.js.map +1 -0
  75. package/dist/otel.d.ts +1 -1
  76. package/dist/react-BBXgHWFc.d.ts +86 -0
  77. package/dist/react-BBXgHWFc.d.ts.map +1 -0
  78. package/dist/react.d.ts +2 -122
  79. package/dist/react.js +107 -100
  80. package/dist/react.js.map +1 -1
  81. package/dist/reducer-BAXc8JyF.js +87 -0
  82. package/dist/reducer-BAXc8JyF.js.map +1 -0
  83. package/dist/{reducer-otzHjuJj.d.ts → reducer-CqA59Q6U.d.ts} +46 -30
  84. package/dist/reducer-CqA59Q6U.d.ts.map +1 -0
  85. package/dist/reducer-runtime-DQwz_J59.js +28 -0
  86. package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
  87. package/dist/scheduler-qstash.d.ts +3 -3
  88. package/dist/scheduler-qstash.d.ts.map +1 -1
  89. package/dist/scheduler-qstash.js +33 -10
  90. package/dist/scheduler-qstash.js.map +1 -1
  91. package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
  92. package/dist/scheduler-task-X1SToDjP.js.map +1 -0
  93. package/dist/scheduler-vercel.d.ts +4 -9
  94. package/dist/scheduler-vercel.d.ts.map +1 -1
  95. package/dist/scheduler-vercel.js +25 -44
  96. package/dist/scheduler-vercel.js.map +1 -1
  97. package/dist/{server-BD5ckxZb.js → server-Dw_k_7kO.js} +904 -219
  98. package/dist/server-Dw_k_7kO.js.map +1 -0
  99. package/dist/{server-DbD7IJVK.d.ts → server-En7cbi3v.d.ts} +116 -26
  100. package/dist/server-En7cbi3v.d.ts.map +1 -0
  101. package/dist/server.d.ts +4 -4
  102. package/dist/server.js +2 -2
  103. package/dist/session-clear-Bo-XTFEy.d.ts +12 -0
  104. package/dist/session-clear-Bo-XTFEy.d.ts.map +1 -0
  105. package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
  106. package/dist/session-clear-DGI4jTQ5.js.map +1 -0
  107. package/dist/{store-DyZM6fS5.d.ts → store-Djdv0bnZ.d.ts} +73 -3
  108. package/dist/store-Djdv0bnZ.d.ts.map +1 -0
  109. package/dist/store-N8PXxDAS.js.map +1 -1
  110. package/dist/store-memory.d.ts +1 -1
  111. package/dist/store-memory.d.ts.map +1 -1
  112. package/dist/store-memory.js +205 -66
  113. package/dist/store-memory.js.map +1 -1
  114. package/dist/{store-polling-CmxUbV93.js → store-polling-B5FcwJfo.js} +21 -4
  115. package/dist/store-polling-B5FcwJfo.js.map +1 -0
  116. package/dist/store-postgres.d.ts +1 -1
  117. package/dist/store-postgres.d.ts.map +1 -1
  118. package/dist/store-postgres.js +450 -108
  119. package/dist/store-postgres.js.map +1 -1
  120. package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-BAwS8RWn.js} +3 -3
  121. package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-BAwS8RWn.js.map} +1 -1
  122. package/dist/store-redis-http.d.ts +1 -1
  123. package/dist/store-redis-http.d.ts.map +1 -1
  124. package/dist/store-redis-http.js +293 -11
  125. package/dist/store-redis-http.js.map +1 -1
  126. package/dist/store-redis-notify-BRITti_o.js +2493 -0
  127. package/dist/store-redis-notify-BRITti_o.js.map +1 -0
  128. package/dist/store-redis.d.ts +1 -1
  129. package/dist/store-redis.d.ts.map +1 -1
  130. package/dist/store-redis.js +11 -11
  131. package/dist/store-redis.js.map +1 -1
  132. package/dist/store-sqlite.d.ts +1 -1
  133. package/dist/store-sqlite.d.ts.map +1 -1
  134. package/dist/store-sqlite.js +197 -33
  135. package/dist/store-sqlite.js.map +1 -1
  136. package/dist/stream-buffer-Cx7AAPGq.js +23 -0
  137. package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
  138. package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-Cyfqo6Qt.d.ts} +2 -2
  139. package/dist/telemetry-Cyfqo6Qt.d.ts.map +1 -0
  140. package/dist/{wire-BO5wWCb1.js → wire-rSUglYo8.js} +132 -10
  141. package/dist/wire-rSUglYo8.js.map +1 -0
  142. package/docs/01-quickstart.mdx +50 -84
  143. package/docs/{guides/07-examples.mdx → 02-examples.mdx} +10 -20
  144. package/docs/actors/01-introduction.mdx +48 -82
  145. package/docs/actors/02-concurrency.mdx +43 -70
  146. package/docs/actors/03-timers.mdx +28 -42
  147. package/docs/actors/04-routes.mdx +110 -137
  148. package/docs/actors/meta.ts +1 -1
  149. package/docs/agents/01-introduction.mdx +257 -0
  150. package/docs/agents/02-tools.mdx +247 -0
  151. package/docs/agents/03-conversations.mdx +538 -0
  152. package/docs/agents/04-harnesses.mdx +136 -0
  153. package/docs/agents/meta.ts +1 -0
  154. package/docs/client/01-react.mdx +294 -0
  155. package/docs/client/02-transports.mdx +86 -0
  156. package/docs/client/03-presence.mdx +224 -0
  157. package/docs/client/meta.ts +1 -0
  158. package/docs/contract/01-events.mdx +57 -0
  159. package/docs/contract/02-state.mdx +142 -0
  160. package/docs/contract/03-sessions.mdx +175 -0
  161. package/docs/index.mdx +112 -156
  162. package/docs/reference/01-core.mdx +139 -0
  163. package/docs/reference/02-server.mdx +855 -0
  164. package/docs/reference/03-client.mdx +301 -0
  165. package/docs/reference/04-ai.mdx +722 -0
  166. package/docs/reference/05-adapters.mdx +351 -0
  167. package/docs/reference/06-errors.mdx +138 -0
  168. package/docs/reference/meta.ts +1 -1
  169. package/docs/server/01-handlers.mdx +200 -0
  170. package/docs/server/02-timers.mdx +130 -0
  171. package/docs/server/03-cancellation.mdx +141 -0
  172. package/docs/server/04-routes.mdx +243 -0
  173. package/docs/server/05-production.mdx +322 -0
  174. package/docs/server/06-guarantees.mdx +123 -0
  175. package/examples/playground/DEPLOY.md +53 -7
  176. package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
  177. package/examples/playground/app/a0/[appId]/page.tsx +53 -0
  178. package/examples/playground/app/a0/a0.css +818 -0
  179. package/examples/playground/app/a0/app-list.tsx +218 -0
  180. package/examples/playground/app/a0/app-view.tsx +63 -0
  181. package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
  182. package/examples/playground/app/a0/apps/[appId]/route.ts +28 -0
  183. package/examples/playground/app/a0/apps.test.ts +128 -0
  184. package/examples/playground/app/a0/apps.ts +102 -0
  185. package/examples/playground/app/a0/chat.tsx +410 -0
  186. package/examples/playground/app/a0/columns.tsx +109 -0
  187. package/examples/playground/app/a0/context-menu.tsx +116 -0
  188. package/examples/playground/app/a0/cookies.ts +46 -0
  189. package/examples/playground/app/a0/crash-on-request.ts +72 -0
  190. package/examples/playground/app/a0/crashes/route.ts +3 -0
  191. package/examples/playground/app/a0/crashes.test.ts +33 -0
  192. package/examples/playground/app/a0/crashes.ts +28 -0
  193. package/examples/playground/app/a0/devtools.tsx +164 -0
  194. package/examples/playground/app/a0/events/route.ts +6 -0
  195. package/examples/playground/app/a0/flags.ts +30 -0
  196. package/examples/playground/app/a0/fx/events/route.ts +9 -0
  197. package/examples/playground/app/a0/fx/scheduler/route.ts +7 -0
  198. package/examples/playground/app/a0/fx/short/events/route.ts +11 -0
  199. package/examples/playground/app/a0/layout.tsx +38 -0
  200. package/examples/playground/app/a0/list/route.ts +10 -0
  201. package/examples/playground/app/a0/model.test.ts +73 -0
  202. package/examples/playground/app/a0/model.ts +95 -0
  203. package/examples/playground/app/a0/no-photon.cjs +4 -0
  204. package/examples/playground/app/a0/no-quickjs.cjs +3 -0
  205. package/examples/playground/app/a0/page.tsx +14 -0
  206. package/examples/playground/app/a0/pi/events/route.ts +9 -0
  207. package/examples/playground/app/a0/pi/scheduler/route.ts +7 -0
  208. package/examples/playground/app/a0/pi/short/events/route.ts +11 -0
  209. package/examples/playground/app/a0/preview-pane.tsx +145 -0
  210. package/examples/playground/app/a0/preview.test.ts +53 -0
  211. package/examples/playground/app/a0/preview.ts +26 -0
  212. package/examples/playground/app/a0/sandbox-log.ts +75 -0
  213. package/examples/playground/app/a0/sandbox-session.ts +152 -0
  214. package/examples/playground/app/a0/sandbox.ts +191 -0
  215. package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
  216. package/examples/playground/app/a0/scheduler/route.ts +7 -0
  217. package/examples/playground/app/a0/server.ts +213 -0
  218. package/examples/playground/app/a0/session.ts +59 -0
  219. package/examples/playground/app/a0/shell.tsx +158 -0
  220. package/examples/playground/app/a0/short/events/route.ts +9 -0
  221. package/examples/playground/app/a0/splitter.tsx +120 -0
  222. package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
  223. package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
  224. package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
  225. package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
  226. package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
  227. package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
  228. package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
  229. package/examples/playground/app/agent/events/route.ts +5 -2
  230. package/examples/playground/app/agent/ingress.test.ts +101 -0
  231. package/examples/playground/app/agent/model-input.test.tsx +58 -0
  232. package/examples/playground/app/agent/model-input.tsx +115 -0
  233. package/examples/playground/app/agent/model.test.ts +73 -0
  234. package/examples/playground/app/agent/model.ts +58 -26
  235. package/examples/playground/app/agent/new-agent-session.tsx +37 -19
  236. package/examples/playground/app/agent/page.tsx +4 -4
  237. package/examples/playground/app/agent/server.ts +19 -15
  238. package/examples/playground/app/agent/session.ts +5 -6
  239. package/examples/playground/app/agent/telemetry.test.ts +189 -0
  240. package/examples/playground/app/agent/telemetry.ts +30 -0
  241. package/examples/playground/app/agent/trace.test.ts +23 -0
  242. package/examples/playground/app/agent/trace.ts +18 -0
  243. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +12 -12
  244. package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
  245. package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
  246. package/examples/playground/app/canvas/model.ts +53 -53
  247. package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
  248. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
  249. package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
  250. package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
  251. package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
  252. package/examples/playground/app/chat/[chatId]/session.ts +2 -19
  253. package/examples/playground/app/chat/agent/route.ts +9 -0
  254. package/examples/playground/app/chat/model.test.ts +46 -119
  255. package/examples/playground/app/chat/model.ts +82 -128
  256. package/examples/playground/app/chat/scheduler/route.ts +2 -2
  257. package/examples/playground/app/chat/server.test.ts +112 -0
  258. package/examples/playground/app/chat/server.ts +113 -117
  259. package/examples/playground/app/components/activity-feed.tsx +30 -1
  260. package/examples/playground/app/components/activity-history.test.tsx +35 -0
  261. package/examples/playground/app/components/demo-icons.tsx +89 -0
  262. package/examples/playground/app/components/demos.tsx +33 -0
  263. package/examples/playground/app/components/fast-link.test.tsx +58 -0
  264. package/examples/playground/app/components/fast-link.tsx +55 -0
  265. package/examples/playground/app/components/site-header.tsx +96 -0
  266. package/examples/playground/app/counter/counter-client.tsx +3 -3
  267. package/examples/playground/app/counter/model.test.ts +3 -3
  268. package/examples/playground/app/counter/model.ts +11 -21
  269. package/examples/playground/app/counter/page.tsx +2 -15
  270. package/examples/playground/app/counter/session.ts +2 -2
  271. package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
  272. package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
  273. package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
  274. package/examples/playground/app/documents/[documentId]/session.ts +2 -2
  275. package/examples/playground/app/documents/model.ts +27 -27
  276. package/examples/playground/app/globals.css +420 -207
  277. package/examples/playground/app/initial-state.test.tsx +54 -0
  278. package/examples/playground/app/layout.tsx +7 -23
  279. package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
  280. package/examples/playground/app/orders/[orderId]/session.ts +2 -2
  281. package/examples/playground/app/orders/model.ts +49 -49
  282. package/examples/playground/app/page.tsx +13 -102
  283. package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
  284. package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
  285. package/examples/playground/app/recovery/model.ts +30 -30
  286. package/examples/playground/instrumentation.ts +5 -0
  287. package/examples/playground/next.config.ts +17 -5
  288. package/examples/playground/package.json +13 -2
  289. package/examples/playground/scripts/a0-template.ts +77 -0
  290. package/examples/playground/vercel.json +36 -0
  291. package/package.json +29 -3
  292. package/src/actor-client.ts +16 -4
  293. package/src/actor-react.ts +25 -11
  294. package/src/actor-shared.ts +21 -6
  295. package/src/actor.ts +76 -31
  296. package/src/ai-agent-actions.ts +219 -0
  297. package/src/ai-agent.ts +2517 -0
  298. package/src/ai-app-state.ts +91 -0
  299. package/src/ai-client-state.ts +6 -3
  300. package/src/ai-client.ts +62 -0
  301. package/src/ai-context-schema.ts +25 -5
  302. package/src/ai-context.ts +90 -40
  303. package/src/ai-control-server.ts +92 -72
  304. package/src/ai-coordinator.ts +23 -22
  305. package/src/ai-fx.browser.ts +5 -0
  306. package/src/ai-fx.ts +833 -0
  307. package/src/ai-harness.ts +518 -0
  308. package/src/ai-message-base.ts +209 -0
  309. package/src/ai-message-projection.ts +262 -64
  310. package/src/ai-model-metadata.ts +11 -6
  311. package/src/ai-pi.browser.ts +5 -0
  312. package/src/ai-pi.ts +1263 -0
  313. package/src/ai-progress-batches.ts +26 -0
  314. package/src/ai-projector.ts +95 -14
  315. package/src/ai-react.ts +146 -0
  316. package/src/ai-sdk-step.ts +2 -2
  317. package/src/ai-server.browser.ts +2 -2
  318. package/src/ai-server.ts +23 -1965
  319. package/src/ai-stored-state.ts +48 -163
  320. package/src/ai.ts +339 -155
  321. package/src/bootstrap-stream.ts +72 -0
  322. package/src/bounded-cache.ts +55 -0
  323. package/src/client-render-session.ts +49 -0
  324. package/src/client.ts +688 -151
  325. package/src/contract.ts +92 -42
  326. package/src/deterministic-id.ts +37 -0
  327. package/src/equal-json.ts +32 -0
  328. package/src/errors.ts +13 -1
  329. package/src/index.ts +5 -1
  330. package/src/initial-snapshot.ts +39 -0
  331. package/src/internal.ts +52 -0
  332. package/src/libfx.d.ts +34 -0
  333. package/src/postgres-notification-scope.ts +14 -3
  334. package/src/react.ts +171 -206
  335. package/src/redis-http-reply.ts +256 -0
  336. package/src/redis-http-stream.ts +73 -0
  337. package/src/redis-http-subscriptions.ts +13 -5
  338. package/src/reducer-runtime.ts +52 -0
  339. package/src/reducer.ts +93 -11
  340. package/src/scheduler-qstash.ts +49 -20
  341. package/src/scheduler-task.ts +34 -2
  342. package/src/scheduler-vercel.ts +32 -84
  343. package/src/server-fetch.ts +139 -35
  344. package/src/server.ts +1133 -255
  345. package/src/session-clear.ts +23 -0
  346. package/src/session-socket.ts +154 -49
  347. package/src/sse.ts +71 -4
  348. package/src/store-memory.ts +252 -75
  349. package/src/store-polling.ts +18 -4
  350. package/src/store-postgres.ts +541 -114
  351. package/src/store-redis-core.ts +1450 -493
  352. package/src/store-redis-http.ts +18 -2
  353. package/src/store-redis-notify.ts +262 -85
  354. package/src/store-redis.ts +19 -9
  355. package/src/store-sqlite.ts +319 -45
  356. package/src/store.ts +79 -1
  357. package/src/stream-buffer.ts +43 -0
  358. package/src/telemetry.ts +14 -1
  359. package/src/wire.ts +241 -13
  360. package/dist/actor-ohPC-81x.d.ts.map +0 -1
  361. package/dist/actor-shared-B5tJfzt-.js.map +0 -1
  362. package/dist/ai-CrEf6p_W.d.ts.map +0 -1
  363. package/dist/ai-DgOBltJ_.js.map +0 -1
  364. package/dist/client-BYzHjkwU.js.map +0 -1
  365. package/dist/client-CzyacQpJ.d.ts.map +0 -1
  366. package/dist/contract-CKRg_E4q.js.map +0 -1
  367. package/dist/errors-DCk6ch5n.js.map +0 -1
  368. package/dist/errors-DvhSXnxk.d.ts.map +0 -1
  369. package/dist/internal-Dq2qYxou.js.map +0 -1
  370. package/dist/react.d.ts.map +0 -1
  371. package/dist/reducer-DEMjEY_O.js +0 -29
  372. package/dist/reducer-DEMjEY_O.js.map +0 -1
  373. package/dist/reducer-otzHjuJj.d.ts.map +0 -1
  374. package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
  375. package/dist/server-BD5ckxZb.js.map +0 -1
  376. package/dist/server-DbD7IJVK.d.ts.map +0 -1
  377. package/dist/store-DyZM6fS5.d.ts.map +0 -1
  378. package/dist/store-polling-CmxUbV93.js.map +0 -1
  379. package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
  380. package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
  381. package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
  382. package/dist/validate-XKT4FSNn.js +0 -30
  383. package/dist/validate-XKT4FSNn.js.map +0 -1
  384. package/dist/wire-BO5wWCb1.js.map +0 -1
  385. package/docs/concepts/01-contracts.mdx +0 -142
  386. package/docs/concepts/02-handlers.mdx +0 -284
  387. package/docs/concepts/03-durability.mdx +0 -314
  388. package/docs/concepts/04-state.mdx +0 -209
  389. package/docs/guides/01-timers.mdx +0 -185
  390. package/docs/guides/02-cancellation.mdx +0 -161
  391. package/docs/guides/03-react.mdx +0 -384
  392. package/docs/guides/04-local-first.mdx +0 -88
  393. package/docs/guides/05-production.mdx +0 -521
  394. package/docs/guides/06-ai-agents.mdx +0 -1318
  395. package/docs/guides/08-application-data.mdx +0 -113
  396. package/docs/guides/09-presence.mdx +0 -266
  397. package/docs/guides/10-transports.mdx +0 -221
  398. package/docs/reference/01-api.mdx +0 -1896
  399. package/docs/reference/02-errors.mdx +0 -149
  400. package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
  401. package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
  402. package/examples/playground/app/agent/compaction-settings.ts +0 -49
  403. package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
  404. package/examples/playground/app/agent/compaction-timeline.ts +0 -208
  405. /package/docs/{concepts → contract}/meta.ts +0 -0
  406. /package/docs/{guides → server}/meta.ts +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wire-rSUglYo8.js","names":[],"sources":["../src/wire.ts"],"sourcesContent":["/**\n * The wire format shared by server.fetch and experimental-a2/client\n * (browser side): events as JSON with ISO timestamps, and the A2Error\n * envelope. Internal module — both entry points re-export what's\n * public.\n */\n\nimport { A2Error, type A2ErrorCode, type A2ServerErrorCode } from './errors.ts'\nimport { nullProtoRecord } from './internal.ts'\nimport type { Event } from './store.ts'\nimport {\n isSerializedEvent,\n takeBuffered,\n type HeldEvent,\n type SerializedEvent,\n} from './stream-buffer.ts'\nimport type {\n PresenceMap,\n PresencePatch,\n PresenceSnapshot,\n} from './contract.ts'\n\nexport type WireEvent = {\n id: string\n type: string\n payload: unknown\n index: number\n sessionId: string\n /** ISO 8601 — revived to a Date on the client so reducers see the\n * same shape on both sides of the wire. */\n createdAt: string\n}\n\nexport function eventToWire(event: Event): WireEvent {\n return {\n id: event.id,\n type: event.type,\n payload: event.payload,\n index: event.index,\n sessionId: event.sessionId,\n createdAt: event.createdAt.toISOString(),\n }\n}\n\n// Consecutive frames usually repeat a stream's session, an event type, and\n// the timestamp of events appended together; each keeps its last encoding.\nconst lastEncoding = <T>(encode: (value: T) => string) => {\n let last: T | undefined\n let text = ''\n return (value: T): string => {\n if (value !== last) {\n text = encode(value)\n last = value\n }\n return text\n }\n}\nconst isoJson = lastEncoding((ms: number) =>\n JSON.stringify(new Date(ms).toISOString()),\n)\nconst sessionJson = lastEncoding((id: string) => JSON.stringify(id))\nconst typeJson = lastEncoding((type: string) => JSON.stringify(type))\n\n/**\n * `JSON.stringify(eventToWire(event))` for the parsed event, built around the\n * stored payload text instead of parsing and re-serializing it.\n */\nexport function serializedEventToWireJson(event: SerializedEvent): string {\n return `{\"id\":${JSON.stringify(event.id)},\"type\":${typeJson(event.type)},\"payload\":${event.payloadJson},\"index\":${event.index},\"sessionId\":${sessionJson(event.sessionId)},\"createdAt\":${isoJson(event.createdAtMs)}}`\n}\n\n/**\n * Wire version 2: what a client opts into with `wire=2` on the stream URL.\n * Live events travel in batches, without the `sessionId` the connection\n * already names, with `createdAt` as epoch milliseconds. Any other value,\n * or none, selects version 1.\n */\nexport type WireVersion = 1 | 2\n\nexport function wireVersion(value: string | null): WireVersion {\n return value === '2' ? 2 : 1\n}\n\n/**\n * Events already held by a stream join one write while it is shorter than\n * this many characters, so a write can exceed it by the event that crosses it.\n */\nexport const EVENT_WRITE_CHARS: number = 64 * 1024\n\n/** Stream items that are events, as opposed to snapshot or presence frames. */\nexport function isStreamEvent(item: object): item is HeldEvent {\n return !('kind' in item) && !('snapshot' in item) && !('participant' in item)\n}\n\n/**\n * `first` and the events `iterator` already holds, encoded, until the batch\n * reaches `EVENT_WRITE_CHARS`; `last` is the batch's final event.\n */\nexport function heldEventBatch(options: {\n first: HeldEvent\n iterator: object\n encode: (event: HeldEvent) => string\n}): { events: string[]; last: HeldEvent } {\n const { iterator, encode } = options\n let last = options.first\n const events = [encode(last)]\n let chars = events[0]!.length\n while (chars < EVENT_WRITE_CHARS) {\n const held = takeBuffered(iterator)\n if (!held) break\n const json = encode(held)\n events.push(json)\n chars += json.length + 1\n last = held\n }\n return { events, last }\n}\n\n/** A version 2 live event: `WireEvent` minus `sessionId`, ms timestamp. */\nexport type CompactWireEvent = {\n id: string\n type: string\n payload: unknown\n index: number\n createdAt: number\n}\n\n/** One version 2 event as JSON, splicing a held row's stored payload. */\nexport function compactEventJson(event: HeldEvent): string {\n if (!isSerializedEvent(event))\n return JSON.stringify({\n id: event.id,\n type: event.type,\n payload: event.payload,\n index: event.index,\n createdAt: event.createdAt.getTime(),\n })\n return `{\"id\":${JSON.stringify(event.id)},\"type\":${typeJson(event.type)},\"payload\":${event.payloadJson},\"index\":${event.index},\"createdAt\":${event.createdAtMs}}`\n}\n\nexport function isCompactWireEvent(value: unknown): value is CompactWireEvent {\n if (value === null || typeof value !== 'object') return false\n const v = value as Record<string, unknown>\n return (\n typeof v['id'] === 'string' &&\n typeof v['type'] === 'string' &&\n typeof v['index'] === 'number' &&\n typeof v['createdAt'] === 'number' &&\n Number.isFinite(v['createdAt'])\n )\n}\n\nexport function eventFromCompactWire(\n wire: CompactWireEvent,\n sessionId: string,\n): Event {\n return {\n id: wire.id,\n type: wire.type,\n payload: wire.payload,\n index: wire.index,\n sessionId,\n createdAt: new Date(wire.createdAt),\n }\n}\n\nexport function eventFromWire(wire: WireEvent): Event {\n return {\n id: wire.id,\n type: wire.type,\n payload: wire.payload,\n index: wire.index,\n sessionId: wire.sessionId,\n createdAt: new Date(wire.createdAt),\n }\n}\n\nexport function isWireEvent(value: unknown): value is WireEvent {\n if (value === null || typeof value !== 'object') return false\n const v = value as Record<string, unknown>\n return (\n typeof v['id'] === 'string' &&\n typeof v['type'] === 'string' &&\n typeof v['index'] === 'number' &&\n typeof v['sessionId'] === 'string' &&\n typeof v['createdAt'] === 'string'\n )\n}\n\n// ── the presence wire pair ───────────────────────────────────────────\n\n/** The `event: presence` SSE frame body: one patch as JSON. */\nexport type WirePresencePatch = {\n participant: string\n values: Record<string, unknown>\n seen: number\n /** ISO 8601 — revived to a Date on the client. */\n at: string\n}\n\n/**\n * The `event: presence-snapshot` SSE frame body: the in-memory\n * `PresenceSnapshot` shape verbatim, with each per-field `at`\n * serialized to ISO 8601.\n */\nexport type WirePresenceSnapshot = {\n snapshot: {\n [participant: string]: {\n [field: string]: { value: unknown; seen: number; at: string }\n }\n }\n}\n\nexport function presencePatchToWire(patch: PresencePatch): WirePresencePatch {\n return {\n participant: patch.participant,\n values: patch.values,\n seen: patch.seen,\n at: patch.at.toISOString(),\n }\n}\n\nexport function presencePatchFromWire(wire: WirePresencePatch): PresencePatch {\n return {\n participant: wire.participant,\n values: wire.values,\n seen: wire.seen,\n at: new Date(wire.at),\n }\n}\n\n// Participant and field keys come off the wire, so every object keyed\n// by them is built null-prototype — see `nullProtoRecord`.\nexport function presenceSnapshotToWire(\n snapshot: PresenceSnapshot,\n): WirePresenceSnapshot {\n const wire: WirePresenceSnapshot['snapshot'] = nullProtoRecord()\n for (const [participant, fields] of Object.entries(snapshot.snapshot)) {\n const wireFields: WirePresenceSnapshot['snapshot'][string] =\n nullProtoRecord()\n for (const [field, entry] of Object.entries(fields)) {\n if (entry === undefined) continue\n wireFields[field] = {\n value: entry.value,\n seen: entry.seen,\n at: entry.at.toISOString(),\n }\n }\n wire[participant] = wireFields\n }\n return { snapshot: wire }\n}\n\nexport function presenceSnapshotFromWire(\n wire: WirePresenceSnapshot,\n): PresenceSnapshot {\n const map: PresenceMap = nullProtoRecord()\n for (const [participant, fields] of Object.entries(wire.snapshot)) {\n const revived: PresenceMap[string] = nullProtoRecord()\n for (const [field, entry] of Object.entries(fields)) {\n revived[field] = {\n value: entry.value,\n seen: entry.seen,\n at: new Date(entry.at),\n }\n }\n map[participant] = revived\n }\n return { snapshot: map }\n}\n\nexport function isWirePresencePatch(\n value: unknown,\n): value is WirePresencePatch {\n if (value === null || typeof value !== 'object') return false\n const v = value as Record<string, unknown>\n return (\n typeof v['participant'] === 'string' &&\n v['values'] !== null &&\n typeof v['values'] === 'object' &&\n !Array.isArray(v['values']) &&\n typeof v['seen'] === 'number' &&\n typeof v['at'] === 'string'\n )\n}\n\nexport function isWirePresenceSnapshot(\n value: unknown,\n): value is WirePresenceSnapshot {\n if (value === null || typeof value !== 'object') return false\n const v = value as Record<string, unknown>\n return (\n v['snapshot'] !== null &&\n typeof v['snapshot'] === 'object' &&\n !Array.isArray(v['snapshot'])\n )\n}\n\n// ── the A2Error envelope ─────────────────────────────────────────────\n\nexport type WireError = {\n error: { code: A2ServerErrorCode; message: string; details?: unknown }\n}\n\nconst ERROR_STATUS: Record<A2ServerErrorCode, number> = {\n INVALID_PAYLOAD: 400,\n FORBIDDEN: 403,\n UNKNOWN_EVENT_TYPE: 400,\n PARTIAL_DUPLICATE_BATCH: 400,\n SUPERSEDED_ATTEMPT: 409,\n SESSION_CLEARED: 409,\n CLAIM_EXPIRED: 409,\n STORE_UNAVAILABLE: 503,\n STORE_NOT_CONFIGURED: 500,\n UNKNOWN_PRESENCE_FIELD: 400,\n PRESENCE_NOT_SUPPORTED: 500,\n}\n\nexport function errorStatus(code: A2ErrorCode): number {\n return ERROR_STATUS[isServerErrorCode(code) ? code : 'STORE_UNAVAILABLE']\n}\n\nexport function errorToWire(error: A2Error): WireError {\n const failure = asA2Error(error)\n const body: WireError = {\n error: {\n code: failure.code as A2ServerErrorCode,\n message: failure.message,\n },\n }\n if (failure.details !== undefined) body.error.details = failure.details\n return body\n}\n\n/** Wrap an arbitrary thrown value for the wire: known server codes pass through,\n * anything else becomes STORE_UNAVAILABLE — from the client's\n * perspective an unknown server failure is retryable-once, not a\n * protocol contract. */\nexport function asA2Error(error: unknown): A2Error {\n return error instanceof A2Error && isServerErrorCode(error.code)\n ? error\n : new A2Error('STORE_UNAVAILABLE', 'internal error', { cause: error })\n}\n\n/** Rebuild an A2Error from a wire body; null if the body isn't one. */\nexport function errorFromWire(body: unknown): A2Error | null {\n if (body === null || typeof body !== 'object') return null\n const err = (body as { error?: unknown }).error\n if (err === null || typeof err !== 'object') return null\n const { code, message, details } = err as Record<string, unknown>\n if (typeof code !== 'string' || !isServerErrorCode(code)) return null\n return new A2Error(code, String(message ?? code), {\n details,\n })\n}\n\nconst isServerErrorCode = (code: string): code is A2ServerErrorCode =>\n Object.hasOwn(ERROR_STATUS, code)\n\n// ── the ws frame layer ───────────────────────────────────────────────\n// The SSE lanes reframed for a socket (specs/a2-api.md §13): every\n// message is one JSON text frame, and a frame is its wire payload plus\n// a `kind` discriminant (plus `req` where a reply must correlate).\n// Unknown kinds are skipped by both sides — the same\n// forward-compatibility rule as named SSE frames.\n//\n// The multiplexed superset rides the same frames: `subscribe` /\n// `unsubscribe` up-frames open and close per-session lanes on one\n// socket, `sessionId` tags route everything else. Single-session\n// frames (no tags) keep parsing unchanged — a route opts into\n// multiplexing by choosing the multi-session server handler, never by\n// breaking the old protocol.\n\n/**\n * Server → client, parsed: a live-stream item, a push ack, a\n * subscription lifecycle notice, or the heartbeat. `parseSocketFrame`\n * yields these; frames of unknown kind (or ones failing their shape\n * guard) come back `null`. `sessionId` is present on frames from a\n * multiplexed socket and absent on a single-session one.\n */\nexport type StateSnapshotFrame = {\n kind: 'snapshot'\n sessionId: string\n name: string\n index: number\n state: unknown\n stateUndefined?: true\n /** The session's latest clear marker, when it has been cleared. */\n clearIndex?: number\n}\n\nexport function stateSnapshotToWire(\n frame: StateSnapshotFrame,\n): StateSnapshotFrame {\n return frame.state === undefined\n ? { ...frame, state: null, stateUndefined: true }\n : frame\n}\n\nexport function isStateSnapshotFrame(\n value: unknown,\n): value is StateSnapshotFrame {\n if (value === null || typeof value !== 'object') return false\n const frame = value as Record<string, unknown>\n return (\n frame['kind'] === 'snapshot' &&\n typeof frame['sessionId'] === 'string' &&\n typeof frame['name'] === 'string' &&\n frame['name'].length > 0 &&\n typeof frame['index'] === 'number' &&\n Number.isSafeInteger(frame['index']) &&\n frame['index'] >= 0 &&\n Object.hasOwn(frame, 'state') &&\n (!Object.hasOwn(frame, 'stateUndefined') ||\n (frame['stateUndefined'] === true && frame['state'] === null)) &&\n (!Object.hasOwn(frame, 'clearIndex') ||\n (typeof frame['clearIndex'] === 'number' &&\n Number.isSafeInteger(frame['clearIndex']) &&\n frame['clearIndex'] > 0 &&\n frame['clearIndex'] <= frame['index']))\n )\n}\n\nexport type SubscriptionStart =\n | { index: number; bootstrap?: never; reducer?: string }\n | { bootstrap: true; reducer: string; index?: never }\n\nexport type SocketDownFrame =\n | StateSnapshotFrame\n | { kind: 'event'; event: WireEvent }\n | { kind: 'events'; events: CompactWireEvent[]; sessionId?: string }\n | { kind: 'presence'; patch: WirePresencePatch; sessionId?: string }\n | {\n kind: 'presence-snapshot'\n snapshot: WirePresenceSnapshot\n sessionId?: string\n }\n | { kind: 'ack'; req: number; events: WireEvent[]; sessionId?: string }\n | { kind: 'ack'; req: number; error: A2Error; sessionId?: string }\n | { kind: 'subscribed'; sessionId: string; attempt?: number }\n | {\n kind: 'unsubscribed'\n sessionId: string\n attempt?: number\n reason?: string\n error?: A2Error\n }\n | { kind: 'ping' }\n\n/**\n * Client → server: the push envelope's two planes. `sessionId` is\n * implied by the socket on a single-session connection and required by\n * the multiplexed handler. Plain JSON on the wire; the server parses\n * and validates them through the same seams as `parsePushBody`.\n */\nexport type SocketPushFrame = {\n kind: 'push'\n /** Client-local ack correlator — opaque to the server. */\n req: number\n events: Array<{ type: string; payload: unknown; id?: string }>\n sessionId?: string\n}\n\nexport type SocketPresenceFrame = {\n kind: 'presence'\n participant: string\n values: Record<string, unknown>\n seen?: number\n at?: number\n sessionId?: string\n}\n\n/** One session lane in a subscribe frame. The server echoes `attempt`\n * on that lane's `subscribed`/`unsubscribed` answers, so a client that\n * re-subscribed before an answer arrived can tell the answers apart. */\nexport type SocketSubscribeEntry = {\n id: string\n attempt?: number\n} & SubscriptionStart\n\n/** Open per-session lanes on a multiplexed socket. `index` is each\n * session's exclusive resume frontier — `stream({ startAfter })`. */\nexport type SocketSubscribeFrame = {\n kind: 'subscribe'\n sessions: SocketSubscribeEntry[]\n}\n\nexport type SocketUnsubscribeFrame = {\n kind: 'unsubscribe'\n sessions: string[]\n}\n\nexport type SocketUpFrame =\n | SocketPushFrame\n | SocketPresenceFrame\n | SocketSubscribeFrame\n | SocketUnsubscribeFrame\n\nexport const SOCKET_PING_FRAME: string = JSON.stringify({ kind: 'ping' })\n\n/** One stream item as a socket frame — `sseResponse`'s framing over\n * the same codecs, with `kind` instead of an SSE event name. On a\n * multiplexed socket presence frames carry the `sessionId` tag; events\n * already carry theirs in the wire event. */\nexport function socketFrameFor(\n item: Event | PresencePatch | PresenceSnapshot | StateSnapshotFrame,\n sessionId?: string,\n): string {\n const tag = sessionId === undefined ? {} : { sessionId }\n if ('kind' in item) return JSON.stringify(stateSnapshotToWire(item))\n if ('snapshot' in item) {\n return JSON.stringify({\n kind: 'presence-snapshot',\n ...presenceSnapshotToWire(item),\n ...tag,\n })\n }\n if ('participant' in item) {\n return JSON.stringify({\n kind: 'presence',\n ...presencePatchToWire(item),\n ...tag,\n })\n }\n return JSON.stringify({ kind: 'event', ...eventToWire(item) })\n}\n\n/** A version 2 batch frame from compact event JSON; a multiplexed\n * socket names the batch's session once. */\nexport function socketEventsFor(events: string[], sessionId?: string): string {\n const tag =\n sessionId === undefined ? '' : `\"sessionId\":${JSON.stringify(sessionId)},`\n return `{\"kind\":\"events\",${tag}\"events\":[${events.join(',')}]}`\n}\n\nexport function socketAckFor(\n req: number,\n events: Event[],\n sessionId?: string,\n): string {\n return JSON.stringify({\n kind: 'ack',\n req,\n events: events.map(eventToWire),\n ...(sessionId === undefined ? {} : { sessionId }),\n })\n}\n\nexport function socketErrorAckFor(\n req: number,\n error: A2Error,\n sessionId?: string,\n): string {\n return JSON.stringify({\n kind: 'ack',\n req,\n ...errorToWire(error),\n ...(sessionId === undefined ? {} : { sessionId }),\n })\n}\n\nexport function socketSubscribedFor(options: {\n sessionId: string\n attempt?: number | undefined\n}): string {\n const { sessionId, attempt } = options\n return JSON.stringify({\n kind: 'subscribed',\n sessionId,\n ...(attempt === undefined ? {} : { attempt }),\n })\n}\n\n/** The subscription-over notice: with a `reason` the server rejected\n * or lost the session's stream, without one it ended cleanly. */\nexport function socketUnsubscribedFor(options: {\n sessionId: string\n attempt?: number | undefined\n reason?: string\n error?: A2Error\n}): string {\n const { sessionId, attempt, reason, error } = options\n return JSON.stringify({\n kind: 'unsubscribed',\n sessionId,\n ...(attempt === undefined ? {} : { attempt }),\n ...(reason === undefined ? {} : { reason }),\n ...(error === undefined ? {} : errorToWire(error)),\n })\n}\n\nconst isAttempt = (value: unknown): value is number =>\n typeof value === 'number' && Number.isSafeInteger(value) && value >= 0\n\nexport function parseSocketFrame(data: string): SocketDownFrame | null {\n let parsed: unknown\n try {\n parsed = JSON.parse(data)\n } catch {\n return null\n }\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return null\n }\n const frame = parsed as Record<string, unknown>\n switch (frame['kind']) {\n case 'snapshot':\n if (!isStateSnapshotFrame(parsed))\n throw new A2Error('INVALID_PAYLOAD', 'invalid bootstrap snapshot')\n return parsed\n case 'ping':\n return { kind: 'ping' }\n case 'event': {\n if (!isWireEvent(parsed)) return null\n const { id, type, payload, index, sessionId, createdAt } = parsed\n return {\n kind: 'event',\n event: { id, type, payload, index, sessionId, createdAt },\n }\n }\n case 'events': {\n // Dropping a batch would leave a gap the frontier then skips past.\n const events = frame['events']\n if (!Array.isArray(events) || !events.every(isCompactWireEvent))\n throw new A2Error('INVALID_PAYLOAD', 'invalid event batch')\n return { kind: 'events', events, ...sessionTag(frame) }\n }\n case 'presence': {\n if (!isWirePresencePatch(parsed)) return null\n const { participant, values, seen, at } = parsed\n return {\n kind: 'presence',\n patch: { participant, values, seen, at },\n ...sessionTag(frame),\n }\n }\n case 'presence-snapshot': {\n if (!isWirePresenceSnapshot(parsed)) return null\n return {\n kind: 'presence-snapshot',\n snapshot: { snapshot: parsed.snapshot },\n ...sessionTag(frame),\n }\n }\n case 'subscribed': {\n const { sessionId, attempt } = frame\n if (typeof sessionId !== 'string') return null\n if (attempt !== undefined && !isAttempt(attempt)) return null\n return {\n kind: 'subscribed',\n sessionId,\n ...(attempt === undefined ? {} : { attempt }),\n }\n }\n case 'unsubscribed': {\n const { sessionId, attempt } = frame\n if (typeof sessionId !== 'string') return null\n if (attempt !== undefined && !isAttempt(attempt)) return null\n const reason = frame['reason']\n const error =\n errorFromWire(parsed) ??\n ('error' in frame\n ? new A2Error(\n 'STORE_UNAVAILABLE',\n 'unintelligible subscription error',\n )\n : null)\n return {\n kind: 'unsubscribed',\n sessionId,\n ...(attempt === undefined ? {} : { attempt }),\n ...(typeof reason === 'string' ? { reason } : {}),\n ...(error === null ? {} : { error }),\n }\n }\n case 'ack': {\n const req = frame['req']\n if (typeof req !== 'number') return null\n const tag = sessionTag(frame)\n const events = frame['events']\n if (Array.isArray(events) && events.every(isWireEvent)) {\n return { kind: 'ack', req, events, ...tag }\n }\n const error = errorFromWire(parsed)\n if (error) return { kind: 'ack', req, error, ...tag }\n // A correlatable ack must never be dropped: its waiter would hang\n // forever behind a healthy socket (pings keep the watchdog fed).\n // An outcome this client cannot interpret — an error code from a\n // newer server, an event shape that fails a guard — degrades to\n // a lost ack: retryable, and the client-generated ids make the\n // retry idempotent even if the append actually committed.\n return {\n kind: 'ack',\n req,\n error: new A2Error('STORE_UNAVAILABLE', 'unintelligible ack'),\n ...tag,\n }\n }\n default:\n return null\n }\n}\n\nconst sessionTag = (frame: Record<string, unknown>): { sessionId?: string } => {\n const sessionId = frame['sessionId']\n return typeof sessionId === 'string' ? { sessionId } : {}\n}\n"],"mappings":"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"}
@@ -1,13 +1,12 @@
1
1
  ---
2
2
  title: Quickstart
3
- description: Define one lifecycle, implement its reactions, and make your first durable append in about five minutes.
3
+ description: Define a contract, react to its events, and make your first durable append in five minutes.
4
4
  ---
5
5
 
6
6
  ## What you'll build
7
7
 
8
- The smallest useful lifecycle: an order that notifies a shop when it arrives.
9
- One contract, one session, one handler, two routes, and at the end a log you
10
- can read back.
8
+ An order that notifies a shop when it arrives: one contract, one handler, two
9
+ routes, and a log you can read back.
11
10
 
12
11
  ## 1. Install
13
12
 
@@ -17,10 +16,7 @@ npm i experimental-a2 zod
17
16
 
18
17
  ## 2. Define a contract
19
18
 
20
- A contract is a name plus the events it understands: each key an event
21
- name, each value a schema. Zod here, though any
22
- [Standard Schema](https://standardschema.dev) validator works, and
23
- that's the contract's complete declaration.
19
+ A contract is a name plus the events it understands, each with a schema:
24
20
 
25
21
  ```ts server/orders.ts
26
22
  import { z } from 'zod'
@@ -35,21 +31,10 @@ export const orders = a2.contract({
35
31
  })
36
32
  ```
37
33
 
38
- The contract is a plain, importable, isomorphic value. The server
39
- implements it below, the reducer derives from it in step 6, and when a
40
- browser enters the picture it types the client too.
41
-
42
- There's no database setup in development: A2 stores events in SQLite at
43
- `.a2/dev.db`, so state survives restarts. Tests get an in-memory store.
44
- Production wants an explicit backend; see
45
- [Going to production](/guides/production).
46
-
47
34
  ## 3. Implement it
48
35
 
49
- Handlers run where the contract is served. `createServer` binds the
50
- vocabulary to storage and reactions; each handler gets the triggering
51
- event and can append what happens next. Same file, below the contract
52
- (they can split later, when a browser needs the contract alone):
36
+ `createServer` binds the contract to storage and handlers. Same file, below
37
+ the contract:
53
38
 
54
39
  ```ts server/orders.ts
55
40
  import { createServer } from 'experimental-a2/server'
@@ -66,24 +51,19 @@ export const ordersServer = createServer({
66
51
  })
67
52
  ```
68
53
 
69
- All handlers are declared at construction: one table of reactions, so a
70
- handler can never be silently missing because the module that registered
71
- it wasn't imported.
72
-
73
- Two things worth noticing:
54
+ - Handlers can run more than once (at-least-once delivery), so make external
55
+ calls safe to repeat. `event.id` is stable across re-runs: use it as the
56
+ idempotency key.
57
+ - The returned event commits atomically with handler success. If the handler
58
+ throws or the process dies first, `shop.notified` doesn't appear. Return one
59
+ event or an array.
74
60
 
75
- - The side effect takes an idempotency key. Handlers can run more than
76
- once (that's what at-least-once delivery means), so external calls
77
- should be safe to repeat. `event.id` is stable across re-runs. Use it.
78
- - The returned event is committed atomically with successful handler
79
- completion. If the handler throws or the process dies first,
80
- `shop.notified` does not appear. A handler can return one event or an array.
61
+ Development needs no database setup: events go to SQLite at `.a2/dev.db` and
62
+ survive restarts. Tests get an in-memory store. Production needs an explicit
63
+ store; see [Production](/server/production).
81
64
 
82
65
  ## 4. Append the first event
83
66
 
84
- `append` is the entire public surface for moving a session forward. Call it
85
- from a route:
86
-
87
67
  ```ts app/api/orders/route.ts
88
68
  import { ordersServer } from '@/server/orders'
89
69
 
@@ -99,11 +79,8 @@ export async function POST(req: Request) {
99
79
  }
100
80
  ```
101
81
 
102
- A session is one instance of the contract. `ordersServer.session('order-42')`
103
- is order 42's lifecycle: the unit A2 recovers, folds, and streams.
104
- Creating the handle does no I/O, and the id is yours to choose.
105
-
106
- Try it:
82
+ `session(orderId)` is one [session](/contract/sessions), one instance of the
83
+ contract. The id is yours, and creating the handle does no I/O.
107
84
 
108
85
  ```bash
109
86
  curl -X POST localhost:3000/api/orders \
@@ -111,18 +88,15 @@ curl -X POST localhost:3000/api/orders \
111
88
  -d '{ "orderId": "order-42", "shopId": "shop-7", "items": ["flat white"] }'
112
89
  ```
113
90
 
114
- The payload is validated against the schema before anything is written;
115
- send garbage and you get `INVALID_PAYLOAD` back, with the issues.
91
+ The payload is validated before anything is written; a bad one fails with
92
+ `INVALID_PAYLOAD` and the issues.
116
93
 
117
- When the append returns, the event is durably in the log and A2 has started the
94
+ When `append` returns, the event is durably in the log and A2 has started the
118
95
  `created` handler inline. In production, a queue-backed scheduler wakes pending
119
- work after a failure or process death. See [Durability](/concepts/durability)
120
- for the exact contract.
96
+ work after a failure or process death. See [Guarantees](/server/guarantees).
121
97
 
122
98
  ## 5. Read the log back
123
99
 
124
- Add a route that returns the session's history:
125
-
126
100
  ```ts app/api/orders/[orderId]/route.ts
127
101
  import { ordersServer } from '@/server/orders'
128
102
 
@@ -147,41 +121,35 @@ curl localhost:3000/api/orders/order-42
147
121
  ]
148
122
  ```
149
123
 
150
- (Trimmed for brevity; each event also carries `id`, `sessionId`, and
151
- `createdAt`.)
152
-
153
- That second event is the payoff. You appended one event; the handler
154
- returned the next; the log shows the whole story, in order. This is what
155
- debugging looks like in A2: you read.
124
+ Each event also carries `id`, `sessionId`, and `createdAt` (trimmed here). You
125
+ appended one event and the handler returned the second. Debugging in A2 is
126
+ reading the log.
156
127
 
157
128
  ## 6. Fold state out of the log
158
129
 
159
- Raw session history is truth, but UIs usually want a view. Define a reducer
160
- once:
130
+ UIs usually want a view, not raw history. Define a reducer once:
161
131
 
162
132
  ```ts reducer.ts
133
+ import { reducer } from 'experimental-a2'
163
134
  import { orders } from '@/server/orders'
164
135
 
165
- export const ordersReducer = orders
166
- .reducer({ name: 'order-status', initialState: { status: 'new' } })
167
- .fold((state, event) => {
168
- switch (event.type) {
169
- case 'created':
170
- return { ...state, status: 'created' }
171
- case 'shop.notified':
172
- return { ...state, status: 'notified' }
173
- default:
174
- return state
175
- }
176
- })
136
+ export const ordersReducer = reducer({
137
+ contract: orders,
138
+ name: 'order-status',
139
+ initialState: { status: 'new' },
140
+ }).fold((state, event) => {
141
+ switch (event.type) {
142
+ case 'created':
143
+ return { ...state, status: 'created' }
144
+ case 'shop.notified':
145
+ return { ...state, status: 'notified' }
146
+ default:
147
+ return state
148
+ }
149
+ })
177
150
  ```
178
151
 
179
- A reducer is *derived from* the contract, in two steps by design: the
180
- first call fixes the state and event types, so `.fold()` receives fully
181
- concrete ones. No type arguments, no annotations, and literal unions in
182
- your state survive the fold.
183
-
184
- And fold anywhere you need the current picture:
152
+ Then fold wherever you need the current picture:
185
153
 
186
154
  ```ts
187
155
  // anywhere on the server:
@@ -192,22 +160,20 @@ const { state } = await ordersServer.session('order-42').state(ordersReducer)
192
160
  // state → { status: 'notified' }
193
161
  ```
194
162
 
195
- The session's state is not a second record to update. It is a fold over the
196
- log. More in [Reading state](/concepts/state).
163
+ State isn't a second record to update. It's a fold over the log; see
164
+ [State](/contract/state).
197
165
 
198
166
  ## That's it
199
167
 
200
- You have a durable lifecycle, implemented: a validated write path, a handler,
201
- a log you can read, and a view folded from it.
202
- Everything else in these docs is the same two moves, append an event and
203
- react to it, applied to timers, cancellation, live UIs, and production
204
- infrastructure.
168
+ You have a validated write path, a handler, a log you can read, and a view
169
+ folded from it. The rest of these docs apply the same two moves, append an
170
+ event and react to it, to timers, cancellation, live UIs, and production.
205
171
 
206
172
  <CardGroup cols={2}>
207
- <Card title="Handlers" href="/concepts/handlers" icon="file">
208
- The context object, chaining, ordering, and re-runs.
173
+ <Card title="Handlers" href="/server/handlers" icon="file">
174
+ Context, chaining, and re-runs.
209
175
  </Card>
210
- <Card title="Going to production" href="/guides/production" icon="rocket">
211
- Postgres, a queue-backed scheduler, and dead-letter resolution.
176
+ <Card title="Production" href="/server/production" icon="rocket">
177
+ A store, a scheduler, dead letters.
212
178
  </Card>
213
179
  </CardGroup>
@@ -1,19 +1,10 @@
1
1
  ---
2
2
  title: Complete examples
3
- description: Read or copy the complete Next.js playground that ships inside the experimental-a2 package.
3
+ description: Every experimental-a2 install includes a complete Next.js playground to copy, run, and read.
4
4
  ---
5
5
 
6
6
  ## Shipped with the package
7
7
 
8
- Every `experimental-a2` installation includes a complete Next.js playground at
9
- `node_modules/experimental-a2/examples/playground`.
10
-
11
- The playground is not a collection of isolated snippets. Each feature owns its
12
- contract, reducer, server, route handlers, client session, and scheduler when it
13
- needs one. It uses only public package entry points.
14
-
15
- Copy it into a new directory to run or modify it:
16
-
17
8
  ```sh
18
9
  cp -R node_modules/experimental-a2/examples/playground ./a2-playground
19
10
  cd a2-playground
@@ -21,12 +12,13 @@ pnpm install
21
12
  pnpm dev
22
13
  ```
23
14
 
24
- The copied `package.json` is standalone. Its A2 dependency is pinned to the
25
- version that supplied the example.
15
+ The playground lives at `node_modules/experimental-a2/examples/playground`.
16
+ Each feature owns its contract, reducer, server, route handlers, client
17
+ session, and scheduler when it needs one, using only public entry points.
26
18
 
27
- The snapshot also includes an `AGENTS.md` file. Coding agents that support
28
- repository instructions discover the example's structure as soon as they work
29
- inside the copied directory.
19
+ The copy is standalone: its `package.json` pins A2 to the version that
20
+ shipped it, and its `AGENTS.md` describes the structure to coding agents that
21
+ work inside the directory.
30
22
 
31
23
  ## Find the right example
32
24
 
@@ -49,8 +41,6 @@ When A2 is installed locally, read these package directories in this order:
49
41
  3. `node_modules/experimental-a2/src` only when implementation details are
50
42
  relevant.
51
43
 
52
- Do not infer application patterns from `dist`. The playground is the reference
53
- for how the public pieces fit together in a real Next.js application.
54
-
55
- The npm snapshot is generated from `apps/playground` in the A2 repository when
56
- the package is published, so the runnable app remains the canonical source.
44
+ Don't infer application patterns from `dist`. The playground, generated from
45
+ `apps/playground` in the A2 repository at publish time, is the reference for
46
+ how the public pieces fit together.
@@ -1,28 +1,22 @@
1
1
  ---
2
2
  title: Introduction
3
- description: "A durable object built on the log: one named instance, typed state, and handlers that process one message at a time."
3
+ description: "Typed state behind a mailbox. Each instance processes one message at a time, and each handler ends in one atomic commit."
4
4
  ---
5
5
 
6
6
  ## The model
7
7
 
8
- An actor is state behind a mailbox. You address an instance by id, send
9
- it typed events, and its handlers process them one at a time: read
10
- state, decide, mutate, return. Each handler ends in one atomic commit.
11
- Between messages the instance is rows in your store, not a process:
12
- nothing to keep alive, no locks to take, no lost updates to debug.
8
+ An actor is state behind a mailbox: you send an instance typed events,
9
+ and its handlers process them one at a time. Between messages an
10
+ instance is rows in your store, not a process. Every instance is an
11
+ ordinary A2 session, so durability, retries, history, and devtools hold
12
+ for actors too.
13
13
 
14
- Every instance is an ordinary A2 session underneath, so everything the
15
- log gives a session (durability, retries, history, devtools) holds for
16
- actors too. The module is three entry points: `experimental-a2/actor`
17
- on the server, `experimental-a2/actor/client` anywhere, and
14
+ Entry points: `experimental-a2/actor` on the server,
15
+ `experimental-a2/actor/client` anywhere, and
18
16
  `experimental-a2/actor/react` for the hook.
19
17
 
20
18
  ## Define one
21
19
 
22
- A protocol declares the state shape and the events with their inputs.
23
- Types, not schemas: handlers are trusted server code, and the compiler
24
- holds them to the protocol. Pass it explicitly.
25
-
26
20
  ```ts vault.ts
27
21
  import { NonRetriableError } from 'experimental-a2'
28
22
  import { actor } from 'experimental-a2/actor'
@@ -52,28 +46,22 @@ export const vault = actor<Vault>({
52
46
  })
53
47
  ```
54
48
 
55
- The `handlers` record must cover every declared event: a missing
56
- handler, an extra one, or a wrong input shape fails to compile. Inputs
57
- arrive typed; `ctx.state` is a mutable draft of the instance's state,
58
- committed atomically when the handler returns. `name` is identity (it
59
- prefixes storage keys), and `state` seeds new instances.
49
+ Pass the protocol explicitly as the type argument. It declares types,
50
+ not schemas: handlers are trusted server code, and a missing handler,
51
+ an extra one, or a wrong input shape fails to compile. `ctx.state` is a
52
+ mutable draft, committed atomically when the handler returns. `name` is
53
+ identity (it prefixes storage keys), and `state` seeds new instances.
60
54
 
61
- Types are compile-time claims. When an event can arrive from the wire,
62
- guard its input in the handler's first lines and throw
63
- `NonRetriableError` on bad shapes (bring a schema library and `.parse`
64
- if you prefer; it is userland either way).
55
+ When an event can arrive from the wire, guard its input in the
56
+ handler's first lines and throw `NonRetriableError` on bad shapes.
65
57
 
66
- `actor` also accepts the `store`, `scheduler`, and `telemetry` options
67
- `createServer` takes. Without a store it uses the same development
68
- default; see [Going to production](/guides/production). A protocol can
69
- declare one more vocabulary, `presence`, for live audience state; see
70
- [Presence](/actors/routes#presence).
58
+ `actor` also accepts `createServer`'s `store`, `scheduler`, and
59
+ `telemetry` options, with the same development default store; see
60
+ [Production](/server/production). A protocol can also declare
61
+ [`presence`](/actors/routes#presence).
71
62
 
72
63
  ## Call it
73
64
 
74
- `call` sends the event and awaits its answer: the state after the
75
- handler ran, and the log index of that commit.
76
-
77
65
  ```ts
78
66
  // anywhere on the server:
79
67
  import { vault } from '@/vault'
@@ -84,18 +72,14 @@ const { state, index } = await savings.call.deposit({ amount: 100 })
84
72
  // state reflects this deposit, and every message before it
85
73
  ```
86
74
 
87
- `vault.actor(id)` is a handle, not a resource; nothing exists until the
88
- first message. Concurrent calls from any number of serverless
75
+ `call` awaits the answer: the state after the handler ran and the log
76
+ index of that commit. `vault.actor(id)` is a handle; nothing exists
77
+ until the first message. Concurrent calls from any number of
89
78
  invocations serialize on the instance's lane, and each answer reflects
90
- its own message. That serialization is the feature:
91
- [Concurrency](/actors/concurrency) is the full story.
79
+ its own message; see [Concurrency](/actors/concurrency).
92
80
 
93
81
  ### Refusals
94
82
 
95
- A handler says no by throwing `NonRetriableError`. That refusal is an
96
- answer, not a failure: nothing commits, nothing retries, and the caller
97
- gets the message.
98
-
99
83
  ```ts
100
84
  // anywhere on the server:
101
85
  import { ActorRefusedError } from 'experimental-a2/actor'
@@ -110,24 +94,23 @@ try {
110
94
  }
111
95
  ```
112
96
 
113
- Three kinds of failure, three behaviors:
97
+ Throwing `NonRetriableError` refuses the message. A refusal is an
98
+ answer, and it is all or nothing: no state change, no buffered sends,
99
+ no retry. The message settles, and the caller receives
100
+ `ActorRefusedError`.
114
101
 
115
- - **`NonRetriableError`** is a refusal, and a refusal is all or
116
- nothing: no state change, no buffered sends. The message is settled,
117
- and the caller receives `ActorRefusedError`.
118
- - **Any other throw** is an infrastructure failure. A2 retries the
119
- message with the usual budget and backoff; the instance's lane waits,
120
- which is deliberate (correctness over liveness). See
102
+ - **Any other throw** retries the message with the usual budget and
103
+ backoff while the instance's lane waits; see
121
104
  [Errors](/reference/errors).
122
105
  - **A crash** re-runs the handler from the pre-message state. `ctx.id`
123
106
  is the message id, stable across re-runs: use it as the idempotency
124
107
  key for external I/O.
108
+ - **A clear** by `handle.session.clear()` before the answer rejects the
109
+ call with an `A2Error` whose code is `SESSION_CLEARED`. The handler
110
+ may already have run. Over HTTP it's a `410`.
125
111
 
126
112
  ## Send, don't wait
127
113
 
128
- `send` validates, appends the event durably, and returns without
129
- waiting for the handler.
130
-
131
114
  ```ts
132
115
  // anywhere on the server:
133
116
  import { vault } from '@/vault'
@@ -136,9 +119,9 @@ const { id } = await vault.actor('savings').send.deposit({ amount: 25 })
136
119
  // durably queued; the handler runs on the instance's lane
137
120
  ```
138
121
 
139
- Same mailbox, same ordering; the only difference is that nobody waits
140
- for the answer. Pass `{ id }` in the options to make a retried send
141
- idempotent.
122
+ `send` validates and appends the event durably without waiting for the
123
+ handler: same mailbox, same ordering. Pass `{ id }` in the options to
124
+ make a retried send idempotent.
142
125
 
143
126
  ## Read without queueing
144
127
 
@@ -149,41 +132,24 @@ import { vault } from '@/vault'
149
132
  const { state } = await vault.actor('savings').state()
150
133
  ```
151
134
 
152
- A snapshot read. It never queues behind pending messages, and a new
135
+ A snapshot read: it never queues behind pending messages, and a new
153
136
  instance reads as its seed state.
154
137
 
155
138
  ## State is remembered, not recomputed
156
139
 
157
- Every commit appends a state event to the instance's log, and reads
158
- fold those by last write wins. History is never reinterpreted: you can
159
- rewrite a handler freely without changing what any past commit means.
160
- (This is the opposite trade from [reducers](/concepts/state), which
161
- recompute views from events; actor state is the recorded decision
162
- itself.)
140
+ Every commit appends a state event, and reads fold them by last write
141
+ wins. Unlike [reducers](/contract/state), nothing is recomputed, so
142
+ rewriting a handler never changes what a past commit means. State must
143
+ be plain JSON: a `Date`, a `Map`, or `NaN` fails the commit loudly.
163
144
 
164
- Because state lives in the log, it holds to the same floor as any
165
- payload: plain JSON trees. A `Date`, a `Map`, or `NaN` fails the commit
166
- loudly instead of being silently coerced.
167
-
168
- Two consequences worth designing for:
169
-
170
- - **Old instances meet new code.** After a deploy, state written by the
171
- previous version flows into the new handlers. Default new fields
172
- defensively (`ctx.state.pending ??= {}` as a first line) instead of
173
- assuming the latest shape.
174
- - **The seed applies to new instances only.** Changing `state` in the
175
- definition does not migrate existing instances; they keep their
176
- remembered state.
145
+ - **Old instances meet new code.** After a deploy, default new fields
146
+ defensively (`ctx.state.pending ??= {}` as a first line).
147
+ - **The seed applies to new instances only.** Changing `state` does not
148
+ migrate existing instances.
177
149
 
178
150
  ## The log is still there
179
151
 
180
- An actor is a layer over an ordinary A2 session, not a wall around
181
- one. `handle.session` is that session (history, streams, scheduling),
182
- the definition
183
- exposes its assembled `contract`, `server`, and `reducer`, and the full
184
- mailbox is visible in devtools. Event names starting with `a2.` are
185
- reserved for the actor's own bookkeeping.
186
-
187
- Next: [Concurrency](/actors/concurrency) for the execution
188
- model, [Deadlines and timers](/actors/timers) for scheduled events, and
189
- [Routes, clients, and React](/actors/routes) for the wire.
152
+ `handle.session` is the instance's underlying session (history,
153
+ streams, scheduling). The definition exposes its assembled `contract`,
154
+ `server`, and `reducer`, and devtools show the full mailbox. Event
155
+ names starting with `a2.` are reserved for the actor's own bookkeeping.