experimental-a2 0.16.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (406) hide show
  1. package/CHANGELOG.md +373 -0
  2. package/README.md +1 -1
  3. package/dist/{actor-ohPC-81x.d.ts → actor-Bjce9_5U.d.ts} +21 -11
  4. package/dist/actor-Bjce9_5U.d.ts.map +1 -0
  5. package/dist/actor-client.d.ts +2 -5
  6. package/dist/actor-client.d.ts.map +1 -1
  7. package/dist/actor-client.js +13 -4
  8. package/dist/actor-client.js.map +1 -1
  9. package/dist/actor-react.d.ts +5 -3
  10. package/dist/actor-react.d.ts.map +1 -1
  11. package/dist/actor-react.js +20 -10
  12. package/dist/actor-react.js.map +1 -1
  13. package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
  14. package/dist/actor-shared-t7w8xIKP.js.map +1 -0
  15. package/dist/actor.d.ts +1 -1
  16. package/dist/actor.js +42 -23
  17. package/dist/actor.js.map +1 -1
  18. package/dist/{ai-DgOBltJ_.js → ai-BpOGm-zB.js} +621 -216
  19. package/dist/ai-BpOGm-zB.js.map +1 -0
  20. package/dist/{ai-CrEf6p_W.d.ts → ai-DrUaMyRt.d.ts} +114 -100
  21. package/dist/ai-DrUaMyRt.d.ts.map +1 -0
  22. package/dist/ai-client.d.ts +27 -0
  23. package/dist/ai-client.d.ts.map +1 -0
  24. package/dist/ai-client.js +29 -0
  25. package/dist/ai-client.js.map +1 -0
  26. package/dist/ai-fx.browser.d.ts +1 -0
  27. package/dist/ai-fx.browser.js +6 -0
  28. package/dist/ai-fx.browser.js.map +1 -0
  29. package/dist/ai-fx.d.ts +10 -0
  30. package/dist/ai-fx.d.ts.map +1 -0
  31. package/dist/ai-fx.js +634 -0
  32. package/dist/ai-fx.js.map +1 -0
  33. package/dist/ai-harness-BuLh_EYi.js +273 -0
  34. package/dist/ai-harness-BuLh_EYi.js.map +1 -0
  35. package/dist/ai-harness-DvsPeJeB.d.ts +26 -0
  36. package/dist/ai-harness-DvsPeJeB.d.ts.map +1 -0
  37. package/dist/ai-pi.browser.d.ts +1 -0
  38. package/dist/ai-pi.browser.js +6 -0
  39. package/dist/ai-pi.browser.js.map +1 -0
  40. package/dist/ai-pi.d.ts +17 -0
  41. package/dist/ai-pi.d.ts.map +1 -0
  42. package/dist/ai-pi.js +904 -0
  43. package/dist/ai-pi.js.map +1 -0
  44. package/dist/ai-react.d.ts +96 -0
  45. package/dist/ai-react.d.ts.map +1 -0
  46. package/dist/ai-react.js +142 -0
  47. package/dist/ai-react.js.map +1 -0
  48. package/dist/ai-server.browser.js +2 -2
  49. package/dist/ai-server.browser.js.map +1 -1
  50. package/dist/ai-server.d.ts +109 -43
  51. package/dist/ai-server.d.ts.map +1 -1
  52. package/dist/ai-server.js +526 -283
  53. package/dist/ai-server.js.map +1 -1
  54. package/dist/ai.d.ts +2 -2
  55. package/dist/ai.js +2 -2
  56. package/dist/bounded-cache-CPHWF-8h.js +41 -0
  57. package/dist/bounded-cache-CPHWF-8h.js.map +1 -0
  58. package/dist/{client-BYzHjkwU.js → client-BReKqwZC.js} +447 -80
  59. package/dist/client-BReKqwZC.js.map +1 -0
  60. package/dist/{client-CzyacQpJ.d.ts → client-h_DKFAjj.d.ts} +14 -13
  61. package/dist/client-h_DKFAjj.d.ts.map +1 -0
  62. package/dist/client.d.ts +4 -2
  63. package/dist/client.js +1 -1
  64. package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
  65. package/dist/contract-BYKASlCV.js.map +1 -0
  66. package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
  67. package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
  68. package/dist/index.d.ts +6 -5
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +4 -3
  71. package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-Bx-c0pDP.d.ts} +7 -3
  72. package/dist/initial-snapshot-Bx-c0pDP.d.ts.map +1 -0
  73. package/dist/{internal-Dq2qYxou.js → internal-B2T3U4Rq.js} +29 -2
  74. package/dist/internal-B2T3U4Rq.js.map +1 -0
  75. package/dist/otel.d.ts +1 -1
  76. package/dist/react-BBXgHWFc.d.ts +86 -0
  77. package/dist/react-BBXgHWFc.d.ts.map +1 -0
  78. package/dist/react.d.ts +2 -122
  79. package/dist/react.js +107 -100
  80. package/dist/react.js.map +1 -1
  81. package/dist/reducer-BAXc8JyF.js +87 -0
  82. package/dist/reducer-BAXc8JyF.js.map +1 -0
  83. package/dist/{reducer-otzHjuJj.d.ts → reducer-CqA59Q6U.d.ts} +46 -30
  84. package/dist/reducer-CqA59Q6U.d.ts.map +1 -0
  85. package/dist/reducer-runtime-DQwz_J59.js +28 -0
  86. package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
  87. package/dist/scheduler-qstash.d.ts +3 -3
  88. package/dist/scheduler-qstash.d.ts.map +1 -1
  89. package/dist/scheduler-qstash.js +33 -10
  90. package/dist/scheduler-qstash.js.map +1 -1
  91. package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
  92. package/dist/scheduler-task-X1SToDjP.js.map +1 -0
  93. package/dist/scheduler-vercel.d.ts +4 -9
  94. package/dist/scheduler-vercel.d.ts.map +1 -1
  95. package/dist/scheduler-vercel.js +25 -44
  96. package/dist/scheduler-vercel.js.map +1 -1
  97. package/dist/{server-BD5ckxZb.js → server-Dw_k_7kO.js} +904 -219
  98. package/dist/server-Dw_k_7kO.js.map +1 -0
  99. package/dist/{server-DbD7IJVK.d.ts → server-En7cbi3v.d.ts} +116 -26
  100. package/dist/server-En7cbi3v.d.ts.map +1 -0
  101. package/dist/server.d.ts +4 -4
  102. package/dist/server.js +2 -2
  103. package/dist/session-clear-Bo-XTFEy.d.ts +12 -0
  104. package/dist/session-clear-Bo-XTFEy.d.ts.map +1 -0
  105. package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
  106. package/dist/session-clear-DGI4jTQ5.js.map +1 -0
  107. package/dist/{store-DyZM6fS5.d.ts → store-Djdv0bnZ.d.ts} +73 -3
  108. package/dist/store-Djdv0bnZ.d.ts.map +1 -0
  109. package/dist/store-N8PXxDAS.js.map +1 -1
  110. package/dist/store-memory.d.ts +1 -1
  111. package/dist/store-memory.d.ts.map +1 -1
  112. package/dist/store-memory.js +205 -66
  113. package/dist/store-memory.js.map +1 -1
  114. package/dist/{store-polling-CmxUbV93.js → store-polling-B5FcwJfo.js} +21 -4
  115. package/dist/store-polling-B5FcwJfo.js.map +1 -0
  116. package/dist/store-postgres.d.ts +1 -1
  117. package/dist/store-postgres.d.ts.map +1 -1
  118. package/dist/store-postgres.js +450 -108
  119. package/dist/store-postgres.js.map +1 -1
  120. package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-BAwS8RWn.js} +3 -3
  121. package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-BAwS8RWn.js.map} +1 -1
  122. package/dist/store-redis-http.d.ts +1 -1
  123. package/dist/store-redis-http.d.ts.map +1 -1
  124. package/dist/store-redis-http.js +293 -11
  125. package/dist/store-redis-http.js.map +1 -1
  126. package/dist/store-redis-notify-BRITti_o.js +2493 -0
  127. package/dist/store-redis-notify-BRITti_o.js.map +1 -0
  128. package/dist/store-redis.d.ts +1 -1
  129. package/dist/store-redis.d.ts.map +1 -1
  130. package/dist/store-redis.js +11 -11
  131. package/dist/store-redis.js.map +1 -1
  132. package/dist/store-sqlite.d.ts +1 -1
  133. package/dist/store-sqlite.d.ts.map +1 -1
  134. package/dist/store-sqlite.js +197 -33
  135. package/dist/store-sqlite.js.map +1 -1
  136. package/dist/stream-buffer-Cx7AAPGq.js +23 -0
  137. package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
  138. package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-Cyfqo6Qt.d.ts} +2 -2
  139. package/dist/telemetry-Cyfqo6Qt.d.ts.map +1 -0
  140. package/dist/{wire-BO5wWCb1.js → wire-rSUglYo8.js} +132 -10
  141. package/dist/wire-rSUglYo8.js.map +1 -0
  142. package/docs/01-quickstart.mdx +50 -84
  143. package/docs/{guides/07-examples.mdx → 02-examples.mdx} +10 -20
  144. package/docs/actors/01-introduction.mdx +48 -82
  145. package/docs/actors/02-concurrency.mdx +43 -70
  146. package/docs/actors/03-timers.mdx +28 -42
  147. package/docs/actors/04-routes.mdx +110 -137
  148. package/docs/actors/meta.ts +1 -1
  149. package/docs/agents/01-introduction.mdx +257 -0
  150. package/docs/agents/02-tools.mdx +247 -0
  151. package/docs/agents/03-conversations.mdx +538 -0
  152. package/docs/agents/04-harnesses.mdx +136 -0
  153. package/docs/agents/meta.ts +1 -0
  154. package/docs/client/01-react.mdx +294 -0
  155. package/docs/client/02-transports.mdx +86 -0
  156. package/docs/client/03-presence.mdx +224 -0
  157. package/docs/client/meta.ts +1 -0
  158. package/docs/contract/01-events.mdx +57 -0
  159. package/docs/contract/02-state.mdx +142 -0
  160. package/docs/contract/03-sessions.mdx +175 -0
  161. package/docs/index.mdx +112 -156
  162. package/docs/reference/01-core.mdx +139 -0
  163. package/docs/reference/02-server.mdx +855 -0
  164. package/docs/reference/03-client.mdx +301 -0
  165. package/docs/reference/04-ai.mdx +722 -0
  166. package/docs/reference/05-adapters.mdx +351 -0
  167. package/docs/reference/06-errors.mdx +138 -0
  168. package/docs/reference/meta.ts +1 -1
  169. package/docs/server/01-handlers.mdx +200 -0
  170. package/docs/server/02-timers.mdx +130 -0
  171. package/docs/server/03-cancellation.mdx +141 -0
  172. package/docs/server/04-routes.mdx +243 -0
  173. package/docs/server/05-production.mdx +322 -0
  174. package/docs/server/06-guarantees.mdx +123 -0
  175. package/examples/playground/DEPLOY.md +53 -7
  176. package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
  177. package/examples/playground/app/a0/[appId]/page.tsx +53 -0
  178. package/examples/playground/app/a0/a0.css +818 -0
  179. package/examples/playground/app/a0/app-list.tsx +218 -0
  180. package/examples/playground/app/a0/app-view.tsx +63 -0
  181. package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
  182. package/examples/playground/app/a0/apps/[appId]/route.ts +28 -0
  183. package/examples/playground/app/a0/apps.test.ts +128 -0
  184. package/examples/playground/app/a0/apps.ts +102 -0
  185. package/examples/playground/app/a0/chat.tsx +410 -0
  186. package/examples/playground/app/a0/columns.tsx +109 -0
  187. package/examples/playground/app/a0/context-menu.tsx +116 -0
  188. package/examples/playground/app/a0/cookies.ts +46 -0
  189. package/examples/playground/app/a0/crash-on-request.ts +72 -0
  190. package/examples/playground/app/a0/crashes/route.ts +3 -0
  191. package/examples/playground/app/a0/crashes.test.ts +33 -0
  192. package/examples/playground/app/a0/crashes.ts +28 -0
  193. package/examples/playground/app/a0/devtools.tsx +164 -0
  194. package/examples/playground/app/a0/events/route.ts +6 -0
  195. package/examples/playground/app/a0/flags.ts +30 -0
  196. package/examples/playground/app/a0/fx/events/route.ts +9 -0
  197. package/examples/playground/app/a0/fx/scheduler/route.ts +7 -0
  198. package/examples/playground/app/a0/fx/short/events/route.ts +11 -0
  199. package/examples/playground/app/a0/layout.tsx +38 -0
  200. package/examples/playground/app/a0/list/route.ts +10 -0
  201. package/examples/playground/app/a0/model.test.ts +73 -0
  202. package/examples/playground/app/a0/model.ts +95 -0
  203. package/examples/playground/app/a0/no-photon.cjs +4 -0
  204. package/examples/playground/app/a0/no-quickjs.cjs +3 -0
  205. package/examples/playground/app/a0/page.tsx +14 -0
  206. package/examples/playground/app/a0/pi/events/route.ts +9 -0
  207. package/examples/playground/app/a0/pi/scheduler/route.ts +7 -0
  208. package/examples/playground/app/a0/pi/short/events/route.ts +11 -0
  209. package/examples/playground/app/a0/preview-pane.tsx +145 -0
  210. package/examples/playground/app/a0/preview.test.ts +53 -0
  211. package/examples/playground/app/a0/preview.ts +26 -0
  212. package/examples/playground/app/a0/sandbox-log.ts +75 -0
  213. package/examples/playground/app/a0/sandbox-session.ts +152 -0
  214. package/examples/playground/app/a0/sandbox.ts +191 -0
  215. package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
  216. package/examples/playground/app/a0/scheduler/route.ts +7 -0
  217. package/examples/playground/app/a0/server.ts +213 -0
  218. package/examples/playground/app/a0/session.ts +59 -0
  219. package/examples/playground/app/a0/shell.tsx +158 -0
  220. package/examples/playground/app/a0/short/events/route.ts +9 -0
  221. package/examples/playground/app/a0/splitter.tsx +120 -0
  222. package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
  223. package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
  224. package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
  225. package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
  226. package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
  227. package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
  228. package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
  229. package/examples/playground/app/agent/events/route.ts +5 -2
  230. package/examples/playground/app/agent/ingress.test.ts +101 -0
  231. package/examples/playground/app/agent/model-input.test.tsx +58 -0
  232. package/examples/playground/app/agent/model-input.tsx +115 -0
  233. package/examples/playground/app/agent/model.test.ts +73 -0
  234. package/examples/playground/app/agent/model.ts +58 -26
  235. package/examples/playground/app/agent/new-agent-session.tsx +37 -19
  236. package/examples/playground/app/agent/page.tsx +4 -4
  237. package/examples/playground/app/agent/server.ts +19 -15
  238. package/examples/playground/app/agent/session.ts +5 -6
  239. package/examples/playground/app/agent/telemetry.test.ts +189 -0
  240. package/examples/playground/app/agent/telemetry.ts +30 -0
  241. package/examples/playground/app/agent/trace.test.ts +23 -0
  242. package/examples/playground/app/agent/trace.ts +18 -0
  243. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +12 -12
  244. package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
  245. package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
  246. package/examples/playground/app/canvas/model.ts +53 -53
  247. package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
  248. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
  249. package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
  250. package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
  251. package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
  252. package/examples/playground/app/chat/[chatId]/session.ts +2 -19
  253. package/examples/playground/app/chat/agent/route.ts +9 -0
  254. package/examples/playground/app/chat/model.test.ts +46 -119
  255. package/examples/playground/app/chat/model.ts +82 -128
  256. package/examples/playground/app/chat/scheduler/route.ts +2 -2
  257. package/examples/playground/app/chat/server.test.ts +112 -0
  258. package/examples/playground/app/chat/server.ts +113 -117
  259. package/examples/playground/app/components/activity-feed.tsx +30 -1
  260. package/examples/playground/app/components/activity-history.test.tsx +35 -0
  261. package/examples/playground/app/components/demo-icons.tsx +89 -0
  262. package/examples/playground/app/components/demos.tsx +33 -0
  263. package/examples/playground/app/components/fast-link.test.tsx +58 -0
  264. package/examples/playground/app/components/fast-link.tsx +55 -0
  265. package/examples/playground/app/components/site-header.tsx +96 -0
  266. package/examples/playground/app/counter/counter-client.tsx +3 -3
  267. package/examples/playground/app/counter/model.test.ts +3 -3
  268. package/examples/playground/app/counter/model.ts +11 -21
  269. package/examples/playground/app/counter/page.tsx +2 -15
  270. package/examples/playground/app/counter/session.ts +2 -2
  271. package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
  272. package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
  273. package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
  274. package/examples/playground/app/documents/[documentId]/session.ts +2 -2
  275. package/examples/playground/app/documents/model.ts +27 -27
  276. package/examples/playground/app/globals.css +420 -207
  277. package/examples/playground/app/initial-state.test.tsx +54 -0
  278. package/examples/playground/app/layout.tsx +7 -23
  279. package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
  280. package/examples/playground/app/orders/[orderId]/session.ts +2 -2
  281. package/examples/playground/app/orders/model.ts +49 -49
  282. package/examples/playground/app/page.tsx +13 -102
  283. package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
  284. package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
  285. package/examples/playground/app/recovery/model.ts +30 -30
  286. package/examples/playground/instrumentation.ts +5 -0
  287. package/examples/playground/next.config.ts +17 -5
  288. package/examples/playground/package.json +13 -2
  289. package/examples/playground/scripts/a0-template.ts +77 -0
  290. package/examples/playground/vercel.json +36 -0
  291. package/package.json +29 -3
  292. package/src/actor-client.ts +16 -4
  293. package/src/actor-react.ts +25 -11
  294. package/src/actor-shared.ts +21 -6
  295. package/src/actor.ts +76 -31
  296. package/src/ai-agent-actions.ts +219 -0
  297. package/src/ai-agent.ts +2517 -0
  298. package/src/ai-app-state.ts +91 -0
  299. package/src/ai-client-state.ts +6 -3
  300. package/src/ai-client.ts +62 -0
  301. package/src/ai-context-schema.ts +25 -5
  302. package/src/ai-context.ts +90 -40
  303. package/src/ai-control-server.ts +92 -72
  304. package/src/ai-coordinator.ts +23 -22
  305. package/src/ai-fx.browser.ts +5 -0
  306. package/src/ai-fx.ts +833 -0
  307. package/src/ai-harness.ts +518 -0
  308. package/src/ai-message-base.ts +209 -0
  309. package/src/ai-message-projection.ts +262 -64
  310. package/src/ai-model-metadata.ts +11 -6
  311. package/src/ai-pi.browser.ts +5 -0
  312. package/src/ai-pi.ts +1263 -0
  313. package/src/ai-progress-batches.ts +26 -0
  314. package/src/ai-projector.ts +95 -14
  315. package/src/ai-react.ts +146 -0
  316. package/src/ai-sdk-step.ts +2 -2
  317. package/src/ai-server.browser.ts +2 -2
  318. package/src/ai-server.ts +23 -1965
  319. package/src/ai-stored-state.ts +48 -163
  320. package/src/ai.ts +339 -155
  321. package/src/bootstrap-stream.ts +72 -0
  322. package/src/bounded-cache.ts +55 -0
  323. package/src/client-render-session.ts +49 -0
  324. package/src/client.ts +688 -151
  325. package/src/contract.ts +92 -42
  326. package/src/deterministic-id.ts +37 -0
  327. package/src/equal-json.ts +32 -0
  328. package/src/errors.ts +13 -1
  329. package/src/index.ts +5 -1
  330. package/src/initial-snapshot.ts +39 -0
  331. package/src/internal.ts +52 -0
  332. package/src/libfx.d.ts +34 -0
  333. package/src/postgres-notification-scope.ts +14 -3
  334. package/src/react.ts +171 -206
  335. package/src/redis-http-reply.ts +256 -0
  336. package/src/redis-http-stream.ts +73 -0
  337. package/src/redis-http-subscriptions.ts +13 -5
  338. package/src/reducer-runtime.ts +52 -0
  339. package/src/reducer.ts +93 -11
  340. package/src/scheduler-qstash.ts +49 -20
  341. package/src/scheduler-task.ts +34 -2
  342. package/src/scheduler-vercel.ts +32 -84
  343. package/src/server-fetch.ts +139 -35
  344. package/src/server.ts +1133 -255
  345. package/src/session-clear.ts +23 -0
  346. package/src/session-socket.ts +154 -49
  347. package/src/sse.ts +71 -4
  348. package/src/store-memory.ts +252 -75
  349. package/src/store-polling.ts +18 -4
  350. package/src/store-postgres.ts +541 -114
  351. package/src/store-redis-core.ts +1450 -493
  352. package/src/store-redis-http.ts +18 -2
  353. package/src/store-redis-notify.ts +262 -85
  354. package/src/store-redis.ts +19 -9
  355. package/src/store-sqlite.ts +319 -45
  356. package/src/store.ts +79 -1
  357. package/src/stream-buffer.ts +43 -0
  358. package/src/telemetry.ts +14 -1
  359. package/src/wire.ts +241 -13
  360. package/dist/actor-ohPC-81x.d.ts.map +0 -1
  361. package/dist/actor-shared-B5tJfzt-.js.map +0 -1
  362. package/dist/ai-CrEf6p_W.d.ts.map +0 -1
  363. package/dist/ai-DgOBltJ_.js.map +0 -1
  364. package/dist/client-BYzHjkwU.js.map +0 -1
  365. package/dist/client-CzyacQpJ.d.ts.map +0 -1
  366. package/dist/contract-CKRg_E4q.js.map +0 -1
  367. package/dist/errors-DCk6ch5n.js.map +0 -1
  368. package/dist/errors-DvhSXnxk.d.ts.map +0 -1
  369. package/dist/internal-Dq2qYxou.js.map +0 -1
  370. package/dist/react.d.ts.map +0 -1
  371. package/dist/reducer-DEMjEY_O.js +0 -29
  372. package/dist/reducer-DEMjEY_O.js.map +0 -1
  373. package/dist/reducer-otzHjuJj.d.ts.map +0 -1
  374. package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
  375. package/dist/server-BD5ckxZb.js.map +0 -1
  376. package/dist/server-DbD7IJVK.d.ts.map +0 -1
  377. package/dist/store-DyZM6fS5.d.ts.map +0 -1
  378. package/dist/store-polling-CmxUbV93.js.map +0 -1
  379. package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
  380. package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
  381. package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
  382. package/dist/validate-XKT4FSNn.js +0 -30
  383. package/dist/validate-XKT4FSNn.js.map +0 -1
  384. package/dist/wire-BO5wWCb1.js.map +0 -1
  385. package/docs/concepts/01-contracts.mdx +0 -142
  386. package/docs/concepts/02-handlers.mdx +0 -284
  387. package/docs/concepts/03-durability.mdx +0 -314
  388. package/docs/concepts/04-state.mdx +0 -209
  389. package/docs/guides/01-timers.mdx +0 -185
  390. package/docs/guides/02-cancellation.mdx +0 -161
  391. package/docs/guides/03-react.mdx +0 -384
  392. package/docs/guides/04-local-first.mdx +0 -88
  393. package/docs/guides/05-production.mdx +0 -521
  394. package/docs/guides/06-ai-agents.mdx +0 -1318
  395. package/docs/guides/08-application-data.mdx +0 -113
  396. package/docs/guides/09-presence.mdx +0 -266
  397. package/docs/guides/10-transports.mdx +0 -221
  398. package/docs/reference/01-api.mdx +0 -1896
  399. package/docs/reference/02-errors.mdx +0 -149
  400. package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
  401. package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
  402. package/examples/playground/app/agent/compaction-settings.ts +0 -49
  403. package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
  404. package/examples/playground/app/agent/compaction-timeline.ts +0 -208
  405. /package/docs/{concepts → contract}/meta.ts +0 -0
  406. /package/docs/{guides → server}/meta.ts +0 -0
@@ -1,113 +0,0 @@
1
- ---
2
- title: A2 and your database
3
- description: Use A2 for things with a lifecycle. Keep collections, relationships, and queries in your application database.
4
- ---
5
-
6
- ## Start with ownership
7
-
8
- A2 does not replace your application database. It synchronizes one known
9
- session at a time: its events, its derived state, and optimistic writes from
10
- the browser. It does not synchronize tables or database queries.
11
-
12
- Use your database for the records and relationships that the rest of the
13
- application queries.
14
-
15
- Decide which system owns each fact:
16
-
17
- - If A2 owns it, append an event and derive the session's state with a reducer.
18
- - If your database owns it, write it with the database's normal transaction.
19
-
20
- Do not make both paths authoritative for the same fact.
21
-
22
- ## What belongs in A2
23
-
24
- A2 fits something with a lifecycle: an order moving through fulfillment, an
25
- agent producing a response, an approval waiting for a decision, or an import
26
- that can fail and resume.
27
-
28
- A **contract** names the events in one kind of lifecycle. A **session** is one
29
- instance of it:
30
-
31
- ```text
32
- orders / order-42
33
- orders / order-43
34
- agents / run-abc
35
- ```
36
-
37
- The session is the boundary A2 logs, recovers, folds, and streams. Put events
38
- in the same session when they form one history or view. Handler execution is
39
- concurrent unless events share a lane.
40
-
41
- A contract is not a table. A session is not a table either. One contract
42
- usually serves many sessions, and one session may describe changes that touch
43
- several records elsewhere in an application.
44
-
45
- ## What belongs in the database
46
-
47
- Use your application database for collections and relationships: accounts,
48
- projects, issue lists, labels, permissions, search, joins, pagination, and
49
- reporting. Use its transactions for constraints that must hold across several
50
- rows before a request returns.
51
-
52
- The Postgres adapter in `experimental-a2/store-postgres` stores A2's own event logs. It does not
53
- watch your application tables, turn their changes into events, or combine an
54
- A2 append with your SQL transaction. The A2 log and your tables may share one
55
- Postgres database, but they remain separate data models and transaction
56
- boundaries.
57
-
58
- ## An issue tracker
59
-
60
- An issue tracker is mostly a collection and query product. Postgres should
61
- usually own its core data:
62
-
63
- | Part | Owner |
64
- | --- | --- |
65
- | issues, projects, labels, comments | Postgres |
66
- | permissions, board filters, search | Postgres |
67
- | an AI investigation attached to an issue | one A2 session |
68
- | an import or automation run | one A2 session |
69
-
70
- The A2 session can store the investigation's messages, progress, tool calls,
71
- approval, and result. It refers to the issue id, but it does not become a
72
- second owner of the issue.
73
-
74
- An individual issue also has a lifecycle, so A2 can own it when the issue's
75
- ordered history and live state are the center of the product. That choice has
76
- a cost: boards and search span many sessions and need another query model. If
77
- those collection views are the center of the product, let the database lead.
78
-
79
- Do not put an entire workspace in one session just to make cross-issue queries
80
- possible. Every event joins the same growing log and every client follows it.
81
-
82
- ## Database writes from handlers
83
-
84
- Handlers may call your database like any other side effect. They are
85
- at-least-once, so make the write idempotent with `event.id`. The event is
86
- durable before the handler runs, and the SQL write may happen later or be
87
- retried.
88
-
89
- That is a good fit for a reaction to a lifecycle event. It is not one atomic
90
- write across A2 and your tables. If a relational change must commit before the
91
- request succeeds, make that change through the database's write path.
92
-
93
- ## Cross-session indexes
94
-
95
- Sometimes an A2-owned lifecycle needs a small database index, such as a list of
96
- active imports. A handler can maintain that index. Treat it as an advanced
97
- integration:
98
-
99
- - it is eventually consistent with the session logs;
100
- - writes must be idempotent;
101
- - A2 assigns log order within one session, not across sessions;
102
- - A2 does not expose one API that scans and replays every session to rebuild
103
- the index.
104
-
105
- Back up and migrate that index like application data. If search, joins, and
106
- cross-session reporting are central rather than incidental, the database
107
- should probably own that domain.
108
-
109
- ## The short version
110
-
111
- Use A2 when people open something by id and watch it progress. Use your
112
- database when people list, filter, join, and edit many records. In an
113
- application that needs both, split ownership along that line.
@@ -1,266 +0,0 @@
1
- ---
2
- title: Presence
3
- description: "Ephemeral participant state on a durable session: cursors, typing, live progress. Presence repaints; the log replays."
4
- ---
5
-
6
- ## Two planes, one session
7
-
8
- Everything in the log is a fact: stored forever, folded by reducers,
9
- replayed on demand. Presence is not a fact. "Her cursor is at (312, 88)"
10
- has a useful lifetime of about two seconds, no replay value, and no
11
- durability requirement; a lost update is repainted by the next one.
12
-
13
- The test that separates the planes:
14
-
15
- > Will anyone, human or reducer, care about this datum after the moment
16
- > passes? Yes: event, in the log. No: presence.
17
-
18
- Messages, document updates, cancellations: events. Cursors, selections,
19
- typing, "who's online", a handler's live progress: presence.
20
-
21
- A2 gives presence its own plane on the session you already have. Same
22
- contract, same route, same auth, same stream. Each participant owns a
23
- small ephemeral state; A2 replicates the latest value of each field to
24
- everyone subscribed and stores none of it in the log. Reducers never see
25
- it, `history()` never returns it, and the browser cache never persists
26
- it. One invariant covers the whole design:
27
-
28
- > **Presence repaints; the log replays.** Anything that decides (a fold,
29
- > a retry, an abort match) reads the log. Presence only hurries pixels.
30
-
31
- ## Declare it in the contract
32
-
33
- `presence` is a second vocabulary next to `events`: each key is a field
34
- of one participant's ephemeral state, each value a schema for it.
35
-
36
- ```ts contracts.ts
37
- import { z } from 'zod'
38
- import * as a2 from 'experimental-a2'
39
-
40
- export const canvas = a2.contract({
41
- name: 'canvas',
42
- events: {
43
- 'shape.created': z.object({
44
- id: z.string(),
45
- kind: z.enum(['rect', 'ellipse']),
46
- x: z.number(),
47
- y: z.number(),
48
- }),
49
- 'shape.moved': z.object({ id: z.string(), x: z.number(), y: z.number() }),
50
- },
51
- presence: {
52
- cursor: z.object({ x: z.number(), y: z.number() }),
53
- dragging: z.object({ shapeId: z.string(), x: z.number(), y: z.number() }),
54
- },
55
- })
56
- ```
57
-
58
- The field is the unit of replication: last writer wins per participant
59
- per field. Setting `cursor` does not touch `dragging`. Fields are absent
60
- until a participant sets them, and clear when set to `null` or when
61
- their backend TTL lapses (a closed tab disappears by silence, no
62
- explicit leave required).
63
-
64
- ## The route
65
-
66
- The same `server.fetch` route as [Live UI](/guides/react). Presence is
67
- inferred from the contract. A contract with presence fields streams
68
- presence patches with events, starting with a snapshot of the current
69
- map. Its push body also accepts an optional `presence` sibling to
70
- `events`, forwarded to `setPresence`.
71
-
72
- ```ts app/api/canvas-events/route.ts
73
- import { canvasServer } from '@/server/canvas'
74
-
75
- export const GET = canvasServer.fetch
76
- export const POST = canvasServer.fetch
77
- ```
78
-
79
- Wrap `server.fetch` when the route needs authentication. Pass
80
- `authorize` to check the typed A2 operation before field validation or
81
- I/O. A push operation carries the complete presence patch, including
82
- its caller-supplied participant id. The same hook runs for HTTP and
83
- WebSocket presence.
84
-
85
- `setPresence` validates each field against the contract, then
86
- broadcasts. No append transaction, no dispatch, no scheduler arm, no log
87
- row. A bad field throws `INVALID_PAYLOAD`; an unknown field throws
88
- `UNKNOWN_PRESENCE_FIELD`; nothing is broadcast on either. A
89
- presence-only push acks `[]`.
90
-
91
- ## The browser
92
-
93
- Presence needs a `participant` id (yours to mint: a user id, a tab
94
- nonce, the playground's guest name). State it once on
95
- `createClient({ participant })` when the app knows it at module scope,
96
- or pass it to the provider, whose `participant` prop overrides the
97
- client's. The hook grows two members. The
98
- session module is the usual pair from
99
- [Live UI](/guides/react#the-session-module), bound to a reducer that
100
- carries the canvas contract:
101
-
102
- ```ts app/canvas/[canvasId]/session.ts
103
- 'use client'
104
- import { createClient } from 'experimental-a2/client'
105
- import { createReact } from 'experimental-a2/react'
106
- import { canvas } from '@/contracts'
107
-
108
- // a minimal scene fold; the real one lives with your reducers
109
- const sceneReducer = canvas
110
- .reducer({ name: 'scene', initialState: {} })
111
- .fold((state) => state)
112
-
113
- export const canvasClient = createClient({
114
- reducer: sceneReducer,
115
- api: '/api/canvas-events',
116
- })
117
- export const { SessionProvider, useSession } = createReact({
118
- client: canvasClient,
119
- })
120
- ```
121
-
122
- ```tsx app/canvas/[canvasId]/canvas-client.tsx
123
- 'use client'
124
- import { useSession } from './session'
125
-
126
- export function CanvasClient() {
127
- const { presence, setPresence } = useSession()
128
-
129
- return (
130
- <div
131
- onPointerMove={(e) => setPresence({ cursor: { x: e.clientX, y: e.clientY } })}
132
- >
133
- {Object.entries(presence).map(([participant, fields]) =>
134
- // render-time expiry: a cursor silent for a few seconds is gone
135
- fields.cursor && Date.now() - fields.cursor.at.getTime() < 5_000 ? (
136
- <Cursor key={participant} {...fields.cursor.value} />
137
- ) : null,
138
- )}
139
- </div>
140
- )
141
- }
142
-
143
- // your cursor sprite:
144
- // function Cursor({ x, y }: { x: number; y: number }) { ... }
145
- const Cursor = (props: { x: number; y: number }) => null
146
- ```
147
-
148
- - **`setPresence(values)`** is fire-and-forget: validated locally,
149
- merged into the local map immediately, coalesced on the wire to a
150
- fixed cadence. The first set sends immediately; within the interval,
151
- later values merge field by field (latest wins, `null` included) and
152
- one trailing send carries the merged batch, so a 60Hz pointer becomes
153
- about fifteen sends per second and losing one is repainted by the
154
- next. Sends may overlap in flight; that is safe because merges are
155
- last-writer-wins by `at` per field. No ack, no `confirmed`, no retry,
156
- no optimistic rollback: there is nothing to roll back, because
157
- nothing was ever true.
158
- - **`presence`** is the replicated map,
159
- `Record<participantId, { [field]: { value, seen, at } }>`, including
160
- your own participant. `at` is the sender's stamp for when the value
161
- was set, and the last-writer-wins comparator; the client stamps its
162
- sends monotonically, so overlapping sends can never invert. How long
163
- a cursor stays painted is view logic, decided at render time. Wall
164
- clocks never enter folds, and presence is not folded state.
165
-
166
- Set a field to `null` to clear it (pointer leaves the canvas, blur,
167
- unmount). Everything else expires by TTL when a tab goes silent: 60
168
- seconds by default, set `createServer({ presence: { ttlMs } })` to
169
- change it. Expiry counts on the backend's clock, not the sender's
170
- stamp.
171
-
172
- Both members are typed from the contract's `presence` map, through the
173
- reducer, with no type arguments: field names, payloads, and `null`
174
- clears all autocomplete. On a contract without `presence` the members
175
- do not exist, so reaching for them is a compile error, not a silent
176
- no-op.
177
-
178
- ## Reconciling the planes: `seen`
179
-
180
- Every presence value carries `seen`, the sender's stream frontier at the
181
- moment it was set. The client stamps it automatically. This is what lets
182
- the two planes agree on an order without wall clocks.
183
-
184
- The classic race: a participant drags a shape (presence), drops it
185
- (event), and a stale `dragging` patch arrives after the durable
186
- `shape.moved`. Without a shared coordinate system the shape flickers
187
- backwards. With `seen`, superseding is one render-time rule:
188
-
189
- ```ts
190
- // in render code: a ghost is dead once the log has moved past it.
191
- // show fields.dragging only if
192
- // fields.dragging.seen >= state.shapes[fields.dragging.value.shapeId].movedAt
193
- // where movedAt is the index your reducer recorded for the last shape.moved
194
- ```
195
-
196
- The durable event at index 214 beats any presence stamped 213 or lower,
197
- deterministically. A2 carries the stamp and exposes the frontiers; the
198
- comparison is yours, in view code, next to the pixels it protects.
199
-
200
- ## Handlers broadcast too
201
-
202
- `ctx.session.setPresence` is the same session operation. A handler
203
- grinding through work can publish live progress without writing
204
- non-facts to the log:
205
-
206
- ```ts server/canvas.ts
207
- import { createServer } from 'experimental-a2/server'
208
- import { canvas } from '@/contracts'
209
-
210
- export const canvasServer = createServer({
211
- contract: canvas,
212
- handlers: {
213
- 'shape.created': async (ctx) => {
214
- // long work; broadcast progress as it goes:
215
- // await ctx.session.setPresence({
216
- // participant: 'system:thumbnails',
217
- // values: { progress: { step: 2, of: 5 } },
218
- // })
219
- },
220
- },
221
- })
222
- ```
223
-
224
- The participant id is the handler's choice; a `system:` prefix keeps
225
- robots and humans apart in the map. Append the durable outcome as an
226
- event when the work completes; the progress was never a fact, the result
227
- is.
228
-
229
- ## What presence never does
230
-
231
- - Never appears in `history()`, `state()`, or a reducer's input type.
232
- The separation is structural: presence fields are not events, so a
233
- fold cannot reach them even by accident.
234
- - Never arms the scheduler, takes a claim, or retries. A crashed presence
235
- update is a non-event, literally.
236
- - Never enters the IndexedDB cache. Cache the log, never the moment.
237
- - Never blocks an append. The planes share a route, not a transaction.
238
-
239
- ## Backends
240
-
241
- Presence needs a broadcast primitive, and backends differ. Declaring
242
- `presence` in a contract requires a store with the presence capability;
243
- `createServer` throws `PRESENCE_NOT_SUPPORTED` at construction
244
- otherwise. All built-in stores have it, at two tiers:
245
-
246
- | Backend | Delivery |
247
- | --- | --- |
248
- | `store-redis` | push: TTL'd hash plus pub/sub patches |
249
- | `store-redis-http` | push over HTTP subscriptions; adaptive polling when unsupported |
250
- | `store-memory` | in-process, immediate |
251
- | `store-postgres` | native notifications with `listenConnectionString`; adaptive polling by default |
252
- | `store-sqlite` | degraded: bounded TTL'd rows, read on the live feed's poll cadence |
253
-
254
- Degraded means later, not lost while watched: presence-only traffic
255
- uses an adaptive re-read cadence, starting at 25ms and backing off to two
256
- seconds while unchanged. New presence patches or durable events restore the
257
- fast cadence. An idle remote viewer may wait up to two seconds plus query
258
- latency for the first change. Redis Pub/Sub does not proactively broadcast
259
- TTL expiry. Silent departures keep their last values in connected clients'
260
- maps until a snapshot reconciles them, so use the render-time expiry above.
261
- Native Postgres subscriptions reconcile missing or expired fields on their
262
- ten-second safety reads. Cursors want
263
- the push tier; typing indicators and progress read fine on either. The presence rows are
264
- bounded per session and participant, so this is not log growth in
265
- disguise, but on metered backends every patch is still a network
266
- operation. Presence makes ephemeral state correct, not free.
@@ -1,221 +0,0 @@
1
- ---
2
- title: Transports
3
- description: "One client, three wires: a single HTTP route, split routes for per-route timeouts, or one WebSocket for both directions."
4
- ---
5
-
6
- ## One wire seam, same client
7
-
8
- Everything the client does is transport-agnostic: optimistic pushes
9
- with ack and `confirmed`, frontier-resumed live reads, reconnection
10
- with backoff, and presence throttling. `api` picks the wire underneath
11
- it:
12
-
13
- ```ts
14
- // in a 'use client' session module:
15
- api: '/api/order-events'
16
- api: (sessionId) => `/api/orders/${encodeURIComponent(sessionId)}/events`
17
- api: { type: 'http', push: '/api/order-push', stream: '/api/order-stream' }
18
- api: { type: 'ws', url: '/api/order-events' }
19
- ```
20
-
21
- - **One route** is the default and the recommendation. Pass a fixed URL or a
22
- session URL function. It serves GET (SSE stream and history) and POST (push).
23
- - **`http` split** serves the two verbs from separate routes. Use it
24
- when platform duration limits differ per verb.
25
- - **`ws`** rides streams, pushes, and presence over WebSocket. It opens
26
- one socket per session. Set `multiplex: true` on the client and
27
- `multiplexWebSocket: true` on the route to share one socket across the
28
- client's sessions.
29
-
30
- A split socket is unrepresentable on purpose. The socket is one
31
- connection in both directions.
32
-
33
- Every session-scoped URL may instead be a `(sessionId) => string` function.
34
- A2 resolves it for each request or reconnect, then adds its standard query
35
- parameters. This supports paths scoped to a session. Encode the ID when placing
36
- it in a path segment. The function chooses a route, not authority: if a path or
37
- connection token is scoped to one session, `authorize` must compare that scope
38
- to `operation.sessionId`. Bind URL tokens to the same session and keep them
39
- short-lived because URLs can appear in infrastructure logs. Multiplexed
40
- WebSockets take one plain URL because the connection carries more than one
41
- session.
42
-
43
- One wire is missing from `ws` by design: the history lane.
44
- [`loadHistory`](/guides/react#the-client-component) is a bounded cold
45
- read and rides plain HTTP. On a `ws` API it throws a `TypeError`.
46
-
47
- ## The HTTP route
48
-
49
- `server.fetch` is bound, so an unprotected route is two exports:
50
-
51
- ```ts app/api/public-order-events/route.ts
52
- import { ordersServer } from '@/server/orders'
53
-
54
- export const GET = ordersServer.fetch
55
- export const POST = ordersServer.fetch
56
- ```
57
-
58
- A plain GET streams one session over SSE. A GET with `gte` and `lte`
59
- returns a history slice. POST parses the push envelope and appends its
60
- events. If the contract declares presence, the same route streams and
61
- accepts presence automatically.
62
-
63
- Wrap the call when the route needs authentication or authorization:
64
-
65
- ```ts app/api/protected-order-events/route.ts
66
- import { ordersServer } from '@/server/orders'
67
-
68
- async function fetchOrders(request: Request): Promise<Response> {
69
- // your authentication:
70
- const user = { id: 'user-1' }
71
- if (!user) return new Response(null, { status: 401 })
72
-
73
- return ordersServer.fetch(request, {
74
- authorize(operation) {
75
- // your per-session and per-operation authorization:
76
- // return canAccess(user, operation)
77
- return true
78
- },
79
- })
80
- }
81
-
82
- export const GET = fetchOrders
83
- export const POST = fetchOrders
84
- ```
85
-
86
- `authorize` runs after A2 parses the operation and before schema
87
- validation or durable I/O. It receives `stream`, `history`, and `push`
88
- operations. Push operations include parsed event names and payloads,
89
- plus the complete presence patch when present. Return `false` to deny.
90
- A2 answers `FORBIDDEN` with status 403. If the hook throws, A2 treats
91
- that as an infrastructure failure and answers `STORE_UNAVAILABLE`.
92
-
93
- The hook is per `fetch` call. Authentication stays in the route, where
94
- request headers, cookies, and framework context are available. The
95
- captured user can then authorize strongly typed A2 operations without
96
- repeating request parsing.
97
-
98
- ## The WebSocket route
99
-
100
- Pass the platform's upgrade capability on GET. POST can stay a direct
101
- export for HTTP clients:
102
-
103
- ```ts app/api/socket-order-events/route.ts
104
- import { experimental_upgradeWebSocket } from '@vercel/functions'
105
- import { ordersServer } from '@/server/orders'
106
-
107
- export const POST = ordersServer.fetch
108
-
109
- export function GET(request: Request): Promise<Response> {
110
- return ordersServer.fetch(request, {
111
- upgradeWebSocket: (attach) =>
112
- experimental_upgradeWebSocket(attach, {
113
- maxPayload: 4 * 1024 * 1024,
114
- }),
115
- })
116
- }
117
- ```
118
-
119
- The socket is bound to the `sessionId` in its upgrade URL. The route
120
- authorizes that session before upgrading, and frames cannot select a
121
- different session afterward. Each connected session has its own socket.
122
-
123
- Authenticate the physical upgrade before calling `server.fetch`. For browser
124
- sockets authenticated by cookies, validate the request's `Origin` too. A
125
- WebSocket handshake does not use a CORS preflight.
126
- Then pass `authorize` to check the session stream and every push or
127
- presence frame. There is no separate upgrade operation: request policy
128
- belongs to the route, while A2 operation policy belongs to `authorize`.
129
-
130
- To multiplex, opt in on both sides:
131
-
132
- ```ts app/api/multiplexed-order-events/route.ts
133
- import { experimental_upgradeWebSocket } from '@vercel/functions'
134
- import { ordersServer } from '@/server/orders'
135
-
136
- export function GET(request: Request): Promise<Response> {
137
- return ordersServer.fetch(request, {
138
- multiplexWebSocket: true,
139
- upgradeWebSocket: (attach) =>
140
- experimental_upgradeWebSocket(attach, {
141
- maxPayload: 4 * 1024 * 1024,
142
- }),
143
- })
144
- }
145
- ```
146
-
147
- ```ts app/orders/multiplexed-session.ts
148
- 'use client'
149
- import { createClient } from 'experimental-a2/client'
150
- import { ordersReducer } from '@/reducer'
151
-
152
- export const ordersClient = createClient({
153
- reducer: ordersReducer,
154
- api: {
155
- type: 'ws',
156
- url: '/api/multiplexed-order-events',
157
- multiplex: true,
158
- },
159
- })
160
- ```
161
-
162
- Only enable multiplexing when the request's authentication context may
163
- open more than one session and `authorize` checks each supplied session
164
- ID. Subscribe frames open the lanes after the physical upgrade.
165
-
166
- On a multiplexed socket, a denied subscribe receives an `unsubscribed`
167
- notice with a structured `FORBIDDEN` error and leaves other sessions
168
- connected. A failed stream or thrown authorization check carries
169
- `STORE_UNAVAILABLE`. The client exposes these as `A2ClientError` instances
170
- to its reconnection policy. On either socket shape, a
171
- denied event push receives a non-retryable `FORBIDDEN` ack. Denied
172
- presence is silently dropped because presence is fire-and-forget and
173
- repaints on the next update.
174
-
175
- A2 reads the platform's ambient invocation deadline and closes the
176
- socket cleanly before it. `waitUntil` and deadline discovery remain
177
- platform capabilities used internally, not options on `server.fetch`.
178
-
179
- ## The client
180
-
181
- ```ts app/orders/[orderId]/session.ts
182
- 'use client'
183
- import { createClient } from 'experimental-a2/client'
184
- import { createReact } from 'experimental-a2/react'
185
- import { ordersReducer } from '@/reducer'
186
-
187
- export const ordersClient = createClient({
188
- reducer: ordersReducer,
189
- api: { type: 'ws', url: '/api/order-events' },
190
- })
191
- export const { SessionProvider, useSession } = createReact({
192
- client: ordersClient,
193
- })
194
- ```
195
-
196
- Nothing else changes. `useSession` is identical, `push` returns the
197
- same ack and `confirmed`, and presence coalesces on the same cadence.
198
- The connection status, stall watchdog, and reconnection behavior are
199
- shared above the wire seam.
200
-
201
- ## Lifecycle
202
-
203
- When a socket closes, the client follows its reconnection policy at the current
204
- frontier, receives a fresh presence snapshot, and re-sends its own latest
205
- presence values. A multiplexed socket re-subscribes every active session
206
- at its own frontier. Event pushes keep their client-generated ids, so
207
- retry remains idempotent.
208
-
209
- Configure `createClient({ reconnect })` to choose a delay or pause by
210
- returning `false`. A paused session keeps its state and leases; call
211
- `session.reconnect()` to resume it. The default policy reconnects with
212
- backoff indefinitely. A clean close also runs the policy, with `error: null`.
213
-
214
- Pre-upgrade authorization remains in the HTTP route. Browser WebSockets do
215
- not expose a rejected upgrade's HTTP status or body, so those failures remain
216
- opaque `CONNECTION_FAILED` errors. Structured subscription errors are
217
- available after a multiplexed socket has opened.
218
-
219
- SSE and WebSocket are transport choices, not different consistency
220
- models. Both resume from the durable event frontier and treat presence
221
- as ephemeral repaintable state.