experimental-a2 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. package/CHANGELOG.md +360 -0
  2. package/dist/{actor-ohPC-81x.d.ts → actor-DBbe5xeE.d.ts} +21 -11
  3. package/dist/actor-DBbe5xeE.d.ts.map +1 -0
  4. package/dist/actor-client.d.ts +2 -5
  5. package/dist/actor-client.d.ts.map +1 -1
  6. package/dist/actor-client.js +13 -4
  7. package/dist/actor-client.js.map +1 -1
  8. package/dist/actor-react.d.ts +5 -3
  9. package/dist/actor-react.d.ts.map +1 -1
  10. package/dist/actor-react.js +20 -10
  11. package/dist/actor-react.js.map +1 -1
  12. package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
  13. package/dist/actor-shared-t7w8xIKP.js.map +1 -0
  14. package/dist/actor.d.ts +1 -1
  15. package/dist/actor.js +42 -23
  16. package/dist/actor.js.map +1 -1
  17. package/dist/{ai-CrLYwNEx.js → ai-UrWfKeuv.js} +966 -541
  18. package/dist/ai-UrWfKeuv.js.map +1 -0
  19. package/dist/ai-client.d.ts +27 -0
  20. package/dist/ai-client.d.ts.map +1 -0
  21. package/dist/ai-client.js +29 -0
  22. package/dist/ai-client.js.map +1 -0
  23. package/dist/ai-react.d.ts +96 -0
  24. package/dist/ai-react.d.ts.map +1 -0
  25. package/dist/ai-react.js +142 -0
  26. package/dist/ai-react.js.map +1 -0
  27. package/dist/ai-server.browser.js +2 -2
  28. package/dist/ai-server.browser.js.map +1 -1
  29. package/dist/ai-server.d.ts +80 -36
  30. package/dist/ai-server.d.ts.map +1 -1
  31. package/dist/ai-server.js +466 -265
  32. package/dist/ai-server.js.map +1 -1
  33. package/dist/{ai-HJ9fHfYI.d.ts → ai-vpGtcaCB.d.ts} +115 -101
  34. package/dist/ai-vpGtcaCB.d.ts.map +1 -0
  35. package/dist/ai.d.ts +2 -2
  36. package/dist/ai.js +2 -2
  37. package/dist/{client-BYzHjkwU.js → client-COaiaDub.js} +447 -80
  38. package/dist/client-COaiaDub.js.map +1 -0
  39. package/dist/{client-CzyacQpJ.d.ts → client-qRnRUzBS.d.ts} +14 -13
  40. package/dist/client-qRnRUzBS.d.ts.map +1 -0
  41. package/dist/client.d.ts +4 -2
  42. package/dist/client.js +1 -1
  43. package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
  44. package/dist/contract-BYKASlCV.js.map +1 -0
  45. package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
  46. package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
  47. package/dist/index.d.ts +6 -5
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +4 -3
  50. package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-BrLso5x_.d.ts} +7 -3
  51. package/dist/initial-snapshot-BrLso5x_.d.ts.map +1 -0
  52. package/dist/{internal-Dq2qYxou.js → internal-CbVvEwad.js} +23 -2
  53. package/dist/internal-CbVvEwad.js.map +1 -0
  54. package/dist/otel.d.ts +1 -1
  55. package/dist/react-BbJ-g6Uu.d.ts +86 -0
  56. package/dist/react-BbJ-g6Uu.d.ts.map +1 -0
  57. package/dist/react.d.ts +2 -122
  58. package/dist/react.js +107 -100
  59. package/dist/react.js.map +1 -1
  60. package/dist/reducer-BAXc8JyF.js +87 -0
  61. package/dist/reducer-BAXc8JyF.js.map +1 -0
  62. package/dist/{reducer-otzHjuJj.d.ts → reducer-BYu6DT7h.d.ts} +45 -29
  63. package/dist/reducer-BYu6DT7h.d.ts.map +1 -0
  64. package/dist/reducer-runtime-DQwz_J59.js +28 -0
  65. package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
  66. package/dist/scheduler-qstash.d.ts +3 -3
  67. package/dist/scheduler-qstash.d.ts.map +1 -1
  68. package/dist/scheduler-qstash.js +33 -10
  69. package/dist/scheduler-qstash.js.map +1 -1
  70. package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
  71. package/dist/scheduler-task-X1SToDjP.js.map +1 -0
  72. package/dist/scheduler-vercel.d.ts +4 -9
  73. package/dist/scheduler-vercel.d.ts.map +1 -1
  74. package/dist/scheduler-vercel.js +25 -44
  75. package/dist/scheduler-vercel.js.map +1 -1
  76. package/dist/{server-DbD7IJVK.d.ts → server-CGM8WzRG.d.ts} +87 -26
  77. package/dist/server-CGM8WzRG.d.ts.map +1 -0
  78. package/dist/{server-BD5ckxZb.js → server-Rj64fNvX.js} +854 -202
  79. package/dist/server-Rj64fNvX.js.map +1 -0
  80. package/dist/server.d.ts +4 -4
  81. package/dist/server.js +2 -2
  82. package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
  83. package/dist/session-clear-DGI4jTQ5.js.map +1 -0
  84. package/dist/session-clear-Dshpuhff.d.ts +12 -0
  85. package/dist/session-clear-Dshpuhff.d.ts.map +1 -0
  86. package/dist/{store-DyZM6fS5.d.ts → store-Gpkq7WBo.d.ts} +44 -3
  87. package/dist/store-Gpkq7WBo.d.ts.map +1 -0
  88. package/dist/store-N8PXxDAS.js.map +1 -1
  89. package/dist/store-memory.d.ts +1 -1
  90. package/dist/store-memory.d.ts.map +1 -1
  91. package/dist/store-memory.js +182 -69
  92. package/dist/store-memory.js.map +1 -1
  93. package/dist/{store-polling-CmxUbV93.js → store-polling-4_y8lETm.js} +21 -4
  94. package/dist/store-polling-4_y8lETm.js.map +1 -0
  95. package/dist/store-postgres.d.ts +1 -1
  96. package/dist/store-postgres.d.ts.map +1 -1
  97. package/dist/store-postgres.js +399 -108
  98. package/dist/store-postgres.js.map +1 -1
  99. package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-Q1C-k2dd.js} +3 -3
  100. package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-Q1C-k2dd.js.map} +1 -1
  101. package/dist/store-redis-http.d.ts +1 -1
  102. package/dist/store-redis-http.d.ts.map +1 -1
  103. package/dist/store-redis-http.js +293 -11
  104. package/dist/store-redis-http.js.map +1 -1
  105. package/dist/store-redis-notify-tqWNnMRu.js +2374 -0
  106. package/dist/store-redis-notify-tqWNnMRu.js.map +1 -0
  107. package/dist/store-redis.d.ts +1 -1
  108. package/dist/store-redis.d.ts.map +1 -1
  109. package/dist/store-redis.js +11 -11
  110. package/dist/store-redis.js.map +1 -1
  111. package/dist/store-sqlite.d.ts +1 -1
  112. package/dist/store-sqlite.d.ts.map +1 -1
  113. package/dist/store-sqlite.js +166 -32
  114. package/dist/store-sqlite.js.map +1 -1
  115. package/dist/stream-buffer-Cx7AAPGq.js +23 -0
  116. package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
  117. package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-cZH-cibj.d.ts} +2 -2
  118. package/dist/telemetry-cZH-cibj.d.ts.map +1 -0
  119. package/dist/{wire-BO5wWCb1.js → wire-DjfHOvdW.js} +132 -10
  120. package/dist/wire-DjfHOvdW.js.map +1 -0
  121. package/docs/01-quickstart.mdx +2 -2
  122. package/docs/actors/01-introduction.mdx +4 -0
  123. package/docs/actors/04-routes.mdx +14 -1
  124. package/docs/concepts/01-contracts.mdx +95 -0
  125. package/docs/concepts/04-state.mdx +3 -3
  126. package/docs/guides/01-timers.mdx +22 -14
  127. package/docs/guides/02-cancellation.mdx +1 -1
  128. package/docs/guides/03-react.mdx +268 -328
  129. package/docs/guides/04-local-first.mdx +32 -73
  130. package/docs/guides/05-production.mdx +40 -27
  131. package/docs/guides/06-ai-agents.mdx +680 -580
  132. package/docs/guides/09-presence.mdx +3 -7
  133. package/docs/guides/10-transports.mdx +5 -5
  134. package/docs/index.mdx +10 -18
  135. package/docs/reference/01-api.mdx +662 -195
  136. package/docs/reference/02-errors.mdx +2 -1
  137. package/examples/playground/DEPLOY.md +52 -6
  138. package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
  139. package/examples/playground/app/a0/[appId]/page.tsx +48 -0
  140. package/examples/playground/app/a0/a0.css +818 -0
  141. package/examples/playground/app/a0/app-list.tsx +211 -0
  142. package/examples/playground/app/a0/app-view.tsx +55 -0
  143. package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
  144. package/examples/playground/app/a0/apps/[appId]/route.ts +23 -0
  145. package/examples/playground/app/a0/apps.test.ts +116 -0
  146. package/examples/playground/app/a0/apps.ts +87 -0
  147. package/examples/playground/app/a0/chat.tsx +364 -0
  148. package/examples/playground/app/a0/columns.tsx +109 -0
  149. package/examples/playground/app/a0/context-menu.tsx +116 -0
  150. package/examples/playground/app/a0/cookies.ts +46 -0
  151. package/examples/playground/app/a0/crash-on-request.ts +72 -0
  152. package/examples/playground/app/a0/crashes/route.ts +3 -0
  153. package/examples/playground/app/a0/crashes.test.ts +33 -0
  154. package/examples/playground/app/a0/crashes.ts +28 -0
  155. package/examples/playground/app/a0/devtools.tsx +164 -0
  156. package/examples/playground/app/a0/events/route.ts +6 -0
  157. package/examples/playground/app/a0/flags.ts +30 -0
  158. package/examples/playground/app/a0/layout.tsx +38 -0
  159. package/examples/playground/app/a0/list/route.ts +10 -0
  160. package/examples/playground/app/a0/model.test.ts +56 -0
  161. package/examples/playground/app/a0/model.ts +58 -0
  162. package/examples/playground/app/a0/page.tsx +14 -0
  163. package/examples/playground/app/a0/preview-pane.tsx +145 -0
  164. package/examples/playground/app/a0/preview.test.ts +53 -0
  165. package/examples/playground/app/a0/preview.ts +26 -0
  166. package/examples/playground/app/a0/sandbox-log.ts +69 -0
  167. package/examples/playground/app/a0/sandbox.ts +191 -0
  168. package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
  169. package/examples/playground/app/a0/scheduler/route.ts +7 -0
  170. package/examples/playground/app/a0/server.ts +158 -0
  171. package/examples/playground/app/a0/session.ts +47 -0
  172. package/examples/playground/app/a0/shell.tsx +150 -0
  173. package/examples/playground/app/a0/short/events/route.ts +9 -0
  174. package/examples/playground/app/a0/splitter.tsx +120 -0
  175. package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
  176. package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
  177. package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
  178. package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
  179. package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
  180. package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
  181. package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
  182. package/examples/playground/app/agent/events/route.ts +5 -2
  183. package/examples/playground/app/agent/ingress.test.ts +98 -0
  184. package/examples/playground/app/agent/model-input.test.tsx +58 -0
  185. package/examples/playground/app/agent/model-input.tsx +115 -0
  186. package/examples/playground/app/agent/model.test.ts +73 -0
  187. package/examples/playground/app/agent/model.ts +58 -26
  188. package/examples/playground/app/agent/new-agent-session.tsx +37 -19
  189. package/examples/playground/app/agent/page.tsx +4 -4
  190. package/examples/playground/app/agent/server.ts +15 -14
  191. package/examples/playground/app/agent/session.ts +5 -6
  192. package/examples/playground/app/agent/telemetry.test.ts +168 -0
  193. package/examples/playground/app/agent/telemetry.ts +30 -0
  194. package/examples/playground/app/agent/trace.test.ts +23 -0
  195. package/examples/playground/app/agent/trace.ts +18 -0
  196. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +11 -11
  197. package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
  198. package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
  199. package/examples/playground/app/canvas/model.ts +53 -53
  200. package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
  201. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
  202. package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
  203. package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
  204. package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
  205. package/examples/playground/app/chat/[chatId]/session.ts +2 -19
  206. package/examples/playground/app/chat/agent/route.ts +9 -0
  207. package/examples/playground/app/chat/model.test.ts +46 -119
  208. package/examples/playground/app/chat/model.ts +82 -128
  209. package/examples/playground/app/chat/scheduler/route.ts +2 -2
  210. package/examples/playground/app/chat/server.test.ts +108 -0
  211. package/examples/playground/app/chat/server.ts +69 -73
  212. package/examples/playground/app/components/activity-feed.tsx +30 -1
  213. package/examples/playground/app/components/activity-history.test.tsx +35 -0
  214. package/examples/playground/app/components/demo-icons.tsx +89 -0
  215. package/examples/playground/app/components/demos.tsx +33 -0
  216. package/examples/playground/app/components/fast-link.test.tsx +58 -0
  217. package/examples/playground/app/components/fast-link.tsx +55 -0
  218. package/examples/playground/app/components/site-header.tsx +96 -0
  219. package/examples/playground/app/counter/counter-client.tsx +3 -3
  220. package/examples/playground/app/counter/model.test.ts +3 -3
  221. package/examples/playground/app/counter/model.ts +11 -21
  222. package/examples/playground/app/counter/page.tsx +2 -15
  223. package/examples/playground/app/counter/session.ts +2 -2
  224. package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
  225. package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
  226. package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
  227. package/examples/playground/app/documents/[documentId]/session.ts +2 -2
  228. package/examples/playground/app/documents/model.ts +27 -27
  229. package/examples/playground/app/globals.css +420 -207
  230. package/examples/playground/app/initial-state.test.tsx +54 -0
  231. package/examples/playground/app/layout.tsx +7 -23
  232. package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
  233. package/examples/playground/app/orders/[orderId]/session.ts +2 -2
  234. package/examples/playground/app/orders/model.ts +49 -49
  235. package/examples/playground/app/page.tsx +13 -102
  236. package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
  237. package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
  238. package/examples/playground/app/recovery/model.ts +30 -30
  239. package/examples/playground/instrumentation.ts +5 -0
  240. package/examples/playground/package.json +10 -2
  241. package/examples/playground/scripts/a0-template.ts +77 -0
  242. package/examples/playground/vercel.json +18 -0
  243. package/package.json +3 -1
  244. package/src/actor-client.ts +16 -4
  245. package/src/actor-react.ts +25 -11
  246. package/src/actor-shared.ts +21 -6
  247. package/src/actor.ts +76 -31
  248. package/src/ai-agent-actions.ts +219 -0
  249. package/src/ai-agent.ts +2343 -0
  250. package/src/ai-app-state.ts +91 -0
  251. package/src/ai-client-state.ts +39 -2
  252. package/src/ai-client.ts +62 -0
  253. package/src/ai-context-schema.ts +25 -5
  254. package/src/ai-context.ts +90 -40
  255. package/src/ai-control-server.ts +94 -55
  256. package/src/ai-control.ts +2 -0
  257. package/src/ai-coordinator.ts +23 -22
  258. package/src/ai-message-base.ts +209 -0
  259. package/src/ai-message-projection.ts +382 -58
  260. package/src/ai-progress-batches.ts +26 -0
  261. package/src/ai-projector.ts +95 -14
  262. package/src/ai-react.ts +146 -0
  263. package/src/ai-server.browser.ts +2 -2
  264. package/src/ai-server.ts +20 -1951
  265. package/src/ai-stored-state.ts +48 -163
  266. package/src/ai.ts +343 -276
  267. package/src/bootstrap-stream.ts +72 -0
  268. package/src/bounded-cache.ts +55 -0
  269. package/src/client-render-session.ts +49 -0
  270. package/src/client.ts +688 -151
  271. package/src/contract.ts +92 -42
  272. package/src/deterministic-id.ts +37 -0
  273. package/src/equal-json.ts +32 -0
  274. package/src/errors.ts +13 -1
  275. package/src/index.ts +5 -1
  276. package/src/initial-snapshot.ts +39 -0
  277. package/src/internal.ts +45 -0
  278. package/src/postgres-notification-scope.ts +14 -3
  279. package/src/react.ts +171 -206
  280. package/src/redis-http-reply.ts +256 -0
  281. package/src/redis-http-stream.ts +73 -0
  282. package/src/redis-http-subscriptions.ts +13 -5
  283. package/src/reducer-runtime.ts +52 -0
  284. package/src/reducer.ts +92 -10
  285. package/src/scheduler-qstash.ts +49 -20
  286. package/src/scheduler-task.ts +34 -2
  287. package/src/scheduler-vercel.ts +32 -84
  288. package/src/server-fetch.ts +139 -35
  289. package/src/server.ts +957 -237
  290. package/src/session-clear.ts +23 -0
  291. package/src/session-socket.ts +154 -49
  292. package/src/sse.ts +71 -4
  293. package/src/store-memory.ts +220 -78
  294. package/src/store-polling.ts +18 -4
  295. package/src/store-postgres.ts +461 -114
  296. package/src/store-redis-core.ts +1260 -434
  297. package/src/store-redis-http.ts +18 -2
  298. package/src/store-redis-notify.ts +260 -85
  299. package/src/store-redis.ts +19 -9
  300. package/src/store-sqlite.ts +259 -45
  301. package/src/store.ts +46 -1
  302. package/src/stream-buffer.ts +43 -0
  303. package/src/telemetry.ts +13 -1
  304. package/src/wire.ts +241 -13
  305. package/dist/actor-ohPC-81x.d.ts.map +0 -1
  306. package/dist/actor-shared-B5tJfzt-.js.map +0 -1
  307. package/dist/ai-CrLYwNEx.js.map +0 -1
  308. package/dist/ai-HJ9fHfYI.d.ts.map +0 -1
  309. package/dist/client-BYzHjkwU.js.map +0 -1
  310. package/dist/client-CzyacQpJ.d.ts.map +0 -1
  311. package/dist/contract-CKRg_E4q.js.map +0 -1
  312. package/dist/errors-DCk6ch5n.js.map +0 -1
  313. package/dist/errors-DvhSXnxk.d.ts.map +0 -1
  314. package/dist/internal-Dq2qYxou.js.map +0 -1
  315. package/dist/react.d.ts.map +0 -1
  316. package/dist/reducer-DEMjEY_O.js +0 -29
  317. package/dist/reducer-DEMjEY_O.js.map +0 -1
  318. package/dist/reducer-otzHjuJj.d.ts.map +0 -1
  319. package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
  320. package/dist/server-BD5ckxZb.js.map +0 -1
  321. package/dist/server-DbD7IJVK.d.ts.map +0 -1
  322. package/dist/store-DyZM6fS5.d.ts.map +0 -1
  323. package/dist/store-polling-CmxUbV93.js.map +0 -1
  324. package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
  325. package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
  326. package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
  327. package/dist/validate-XKT4FSNn.js +0 -30
  328. package/dist/validate-XKT4FSNn.js.map +0 -1
  329. package/dist/wire-BO5wWCb1.js.map +0 -1
  330. package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
  331. package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
  332. package/examples/playground/app/agent/compaction-settings.ts +0 -49
  333. package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
  334. package/examples/playground/app/agent/compaction-timeline.ts +0 -208
package/src/react.ts CHANGED
@@ -1,27 +1,9 @@
1
1
  'use client'
2
- /**
3
- * experimental-a2/react — React bindings over experimental-a2/client.
4
- *
5
- * `createReact({ client })` is a factory (like createContext): call it
6
- * once in a `'use client'` module with a shared A2 client and export the
7
- * bound pair. Server components import `SessionProvider` from that module
8
- * as a client reference; client components import `useSession` from the
9
- * same file. The shared client also makes imperative pushes and provider
10
- * renders resolve the same live session. Everything is typed by the
11
- * reducer value — no type arguments, and nothing here ever touches the
12
- * machine module.
13
- *
14
- * The standalone `useSession(client, sessionId, options?)` is the
15
- * provider-less primitive for client-first apps: identity-mapped
16
- * handles, one leased stream per session, and an app-owned hydration
17
- * input — the hook holds the stream until the app hands it a
18
- * `{ state, index }` fold.
19
- */
20
-
21
2
  import {
22
3
  createContext,
23
4
  createElement,
24
5
  useContext,
6
+ use,
25
7
  useEffect,
26
8
  useMemo,
27
9
  useSyncExternalStore,
@@ -37,10 +19,11 @@ import {
37
19
  type PushResult,
38
20
  type SessionClient,
39
21
  type SessionClientPresence,
40
- type SessionSnapshot,
22
+ type SessionOptions,
41
23
  } from './client.ts'
42
24
  import type {
43
25
  AppendInput,
26
+ Contract,
44
27
  ContractEvent,
45
28
  EventDefs,
46
29
  PresenceDefs,
@@ -49,44 +32,24 @@ import type {
49
32
  WithPresence,
50
33
  } from './contract.ts'
51
34
  import type { Reducer } from './reducer.ts'
35
+ import {
36
+ createRenderSession,
37
+ resolveInitialForReact,
38
+ } from './client-render-session.ts'
39
+ import { isInitialPromise, type InitialSnapshot } from './initial-snapshot.ts'
52
40
 
53
41
  export type UseSessionOptions<S> = {
54
- /** Overrides the client's `participant` for this session — for apps
55
- * whose identity is only known at mount time. Bound once per
56
- * session handle. */
57
42
  participant?: string
58
- /**
59
- * The server fold this session mounts from: the `{ state, index }`
60
- * pair `session.state(reducer)` returns, fetched by the app through
61
- * its own route. Atomic on purpose — the fold and its frontier
62
- * travel together or not at all. While `undefined` (the app's fetch
63
- * has not landed), the hook holds the stream; when the value
64
- * arrives, the session hydrates through the ordinary re-hydration
65
- * seam and the stream connects at that frontier. To fold from the log's start
66
- * deliberately, hand the fold's true starting point:
67
- * `{ state: reducer.initialState, index: 0 }` — state at index 0 is
68
- * the reducer's seed by definition, so the explicit replay needs no
69
- * special vocabulary. There is no pending/ready echo on the result:
70
- * whether the fold has been handed over is this option — the
71
- * caller's own input — so the data layer's loading state IS the
72
- * pending state.
73
- */
74
- hydrate?: { state: S; index: number } | undefined
43
+ initial?: InitialSnapshot<S>
44
+ enabled?: boolean
45
+ suspend?: boolean
75
46
  }
76
47
 
77
48
  export type SessionProviderProps<D extends EventDefs, S> = {
78
- /** Which session to subscribe to. */
79
49
  sessionId: string
80
- /** Base path of the route exposing GET (stream) and POST (push). */
81
50
  api: string
82
- /** The server-rendered fold — the first paint, no client JS needed. */
83
- initialState: S
84
- /** The fold's frontier — the stream resumes exactly there. */
85
- initialIndex: number
86
- /** Server-rendered event history through `initialIndex`. */
51
+ initial?: InitialSnapshot<S>
87
52
  initialEvents?: ContractEvent<D>[]
88
- /** Overrides the client's `participant` for this session — for apps
89
- * whose identity is only known at mount time. */
90
53
  participant?: string
91
54
  children?: ReactNode
92
55
  }
@@ -96,11 +59,6 @@ export type BoundSessionProviderProps<D extends EventDefs, S> = Omit<
96
59
  'api'
97
60
  >
98
61
 
99
- /**
100
- * The presence members of the hook result — intersected in via
101
- * `WithPresence`, so they exist exactly when the contract declares
102
- * presence fields (the reducer is the client's typed handle on it).
103
- */
104
62
  export type UseSessionPresence<P extends PresenceDefs> = {
105
63
  /** The replicated presence map, this client included (local echo). */
106
64
  presence: PresenceMap<P>
@@ -121,6 +79,8 @@ export type UseSessionResult<
121
79
  > = WithPresence<P, UseSessionPresence<P>> & {
122
80
  /** The live view, folded through the shared reducer. */
123
81
  state: S
82
+ /** A state/index snapshot has been applied; independent of connection status. */
83
+ hydrated: boolean
124
84
  /** The raw event feed — observed, seeded, or backscrolled. */
125
85
  events: ContractEvent<D>[]
126
86
  /** The stream frontier — `lastSeenIndex` for cancellation. */
@@ -149,219 +109,224 @@ export type UseSessionResult<
149
109
  history: HistoryState
150
110
  }
151
111
 
112
+ export type BoundSessionOptions<
113
+ D extends EventDefs,
114
+ S,
115
+ P extends PresenceDefs,
116
+ > = UseSessionOptions<S> & {
117
+ sessionId?: string
118
+ client?: A2Client<D, S, P>
119
+ }
120
+
152
121
  export type A2React<
153
122
  D extends EventDefs,
154
123
  S,
155
124
  P extends PresenceDefs = Record<never, never>,
156
- V = S,
157
125
  > = {
158
126
  SessionProvider: (props: SessionProviderProps<D, S>) => ReactElement
159
- useSession: () => UseSessionResult<D, V, P>
127
+ useSession: (
128
+ options?: BoundSessionOptions<D, S, P>,
129
+ ) => UseSessionResult<D, S, P>
160
130
  }
161
131
 
162
132
  export type BoundA2React<
163
133
  D extends EventDefs,
164
134
  S,
165
135
  P extends PresenceDefs = Record<never, never>,
166
- V = S,
167
136
  > = {
168
137
  SessionProvider: (props: BoundSessionProviderProps<D, S>) => ReactElement
169
- useSession: () => UseSessionResult<D, V, P>
138
+ useSession: (
139
+ options?: BoundSessionOptions<D, S, P>,
140
+ ) => UseSessionResult<D, S, P>
170
141
  }
171
142
 
172
143
  export function createReact<
173
144
  D extends EventDefs,
174
145
  S,
175
146
  P extends PresenceDefs = Record<never, never>,
176
- V = S,
177
- >(options: { client: A2Client<D, S, P, V> }): BoundA2React<D, S, P, V>
147
+ >(options: { client: A2Client<D, S, P> }): BoundA2React<D, S, P>
178
148
  export function createReact<
179
149
  D extends EventDefs,
180
150
  S,
181
151
  P extends PresenceDefs = Record<never, never>,
182
- V = S,
183
- >(options: { reducer: Reducer<D, S, P, V> }): A2React<D, S, P, V>
152
+ >(options: { contract: Contract<D, P, Reducer<D, S, P>> }): A2React<D, S, P>
184
153
  export function createReact<
185
154
  D extends EventDefs,
186
155
  S,
187
156
  P extends PresenceDefs = Record<never, never>,
188
- V = S,
189
157
  >(
190
- options: { client: A2Client<D, S, P, V> } | { reducer: Reducer<D, S, P, V> },
191
- ): BoundA2React<D, S, P, V> | A2React<D, S, P, V> {
192
- const Context = createContext<SessionClient<D, S, P, V> | null>(null)
193
- const resolveSession: (
194
- sessionId: string,
195
- api: string | undefined,
196
- initialState: S,
197
- initialIndex: number,
198
- initialEvents: ContractEvent<D>[] | undefined,
199
- participant: string | undefined,
200
- ) => SessionClient<D, S, P, V> =
201
- 'client' in options
202
- ? (
203
- sessionId,
204
- _api,
205
- initialState,
206
- initialIndex,
207
- initialEvents,
208
- participant,
209
- ) =>
210
- options.client.session(sessionId, {
211
- initialState,
212
- initialIndex,
213
- ...(initialEvents === undefined ? {} : { initialEvents }),
214
- ...(participant === undefined ? {} : { participant }),
215
- })
216
- : (() => {
217
- const clients = new Map<string, A2Client<D, S, P, V>>()
218
- return (
219
- sessionId: string,
220
- api: string | undefined,
221
- initialState: S,
222
- initialIndex: number,
223
- initialEvents: ContractEvent<D>[] | undefined,
224
- participant: string | undefined,
225
- ) => {
226
- if (api === undefined) {
227
- throw new TypeError(
228
- 'SessionProvider requires api when createReact binds a reducer',
229
- )
230
- }
231
- let client = clients.get(api)
232
- if (!client) {
233
- client = createClient({ reducer: options.reducer, api })
234
- clients.set(api, client)
235
- }
236
- return client.session(sessionId, {
237
- initialState,
238
- initialIndex,
239
- ...(initialEvents === undefined ? {} : { initialEvents }),
240
- ...(participant === undefined ? {} : { participant }),
241
- })
242
- }
243
- })()
158
+ options:
159
+ | { client: A2Client<D, S, P> }
160
+ | { contract: Contract<D, P, Reducer<D, S, P>> },
161
+ ): BoundA2React<D, S, P> | A2React<D, S, P> {
162
+ type Scope = {
163
+ client: A2Client<D, S, P>
164
+ sessionId: string
165
+ settings: SessionOptions<D, S>
166
+ }
167
+ const Context = createContext<Scope | null>(null)
168
+ const clients = new Map<string, A2Client<D, S, P>>()
169
+ const resolveClient = (api: string | undefined): A2Client<D, S, P> => {
170
+ if ('client' in options) return options.client
171
+ if (api === undefined)
172
+ throw new TypeError(
173
+ 'SessionProvider requires api when createReact binds a contract',
174
+ )
175
+ let client = clients.get(api)
176
+ if (!client) {
177
+ client = createClient({ contract: options.contract, api })
178
+ clients.set(api, client)
179
+ }
180
+ return client
181
+ }
244
182
 
245
183
  function SessionProvider(
246
184
  props: SessionProviderProps<D, S> | BoundSessionProviderProps<D, S>,
247
185
  ): ReactElement {
248
- const {
249
- sessionId,
250
- initialState,
251
- initialIndex,
252
- initialEvents,
253
- participant,
254
- } = props
186
+ const { sessionId, initial, initialEvents, participant } = props
255
187
  const api = 'api' in props ? props.api : undefined
256
- const client = useMemo(
257
- () =>
258
- resolveSession(
259
- sessionId,
260
- api,
261
- initialState,
262
- initialIndex,
263
- initialEvents,
264
- participant,
265
- ),
266
- [sessionId, api, initialState, initialIndex, initialEvents, participant],
188
+ const scope = useMemo(
189
+ () => ({
190
+ client: resolveClient(api),
191
+ sessionId,
192
+ settings: {
193
+ ...(initial === undefined ? {} : { initial }),
194
+ ...(initialEvents === undefined ? {} : { initialEvents }),
195
+ ...(participant === undefined ? {} : { participant }),
196
+ },
197
+ }),
198
+ [sessionId, api, initial, initialEvents, participant],
267
199
  )
268
- useSessionLifecycle(client, true)
269
- return createElement(Context.Provider, { value: client }, props.children)
200
+ return createElement(Context.Provider, { value: scope }, props.children)
270
201
  }
271
202
 
272
- function useBoundSession(): UseSessionResult<D, V, P> {
273
- const client = useContext(Context)
274
- if (!client) {
275
- throw new Error(
276
- 'useSession must be rendered inside its matching SessionProvider',
203
+ function useBoundSession(
204
+ overrides: BoundSessionOptions<D, S, P> = {},
205
+ ): UseSessionResult<D, S, P> {
206
+ const scope = useContext(Context)
207
+ const client =
208
+ overrides.client ??
209
+ scope?.client ??
210
+ ('client' in options ? options.client : undefined)
211
+ const sessionId = overrides.sessionId ?? scope?.sessionId
212
+ if (!client || sessionId === undefined)
213
+ throw new TypeError(
214
+ 'provide client and sessionId through SessionProvider or hook options',
277
215
  )
278
- }
279
- return useSessionResult(client)
216
+ const inherits = client === scope?.client && sessionId === scope?.sessionId
217
+ const inherited = inherits ? scope.settings : undefined
218
+ const { initial, participant } = overrides
219
+ const settings = useMemo(
220
+ () => ({
221
+ ...inherited,
222
+ ...(initial === undefined ? {} : { initial }),
223
+ ...(participant === undefined ? {} : { participant }),
224
+ }),
225
+ [inherited, initial, participant],
226
+ )
227
+ return useLiveSession({
228
+ client,
229
+ sessionId,
230
+ settings,
231
+ enabled: overrides.enabled ?? true,
232
+ suspend: overrides.suspend ?? false,
233
+ })
280
234
  }
281
235
 
282
236
  return { SessionProvider, useSession: useBoundSession }
283
237
  }
284
238
 
285
- // ── the shared session lifecycle ───────────────────────────────
286
-
287
- /**
288
- * Hold a stream lease while mounted and `live` — the client refcounts
289
- * leases per handle, so however many providers and hooks mount one
290
- * session, the first connects and the last release closes
291
- * (StrictMode-safe: the lease is retaken on remount). A standalone
292
- * hook without a `hydrate` value passes `live: false` and holds the
293
- * stream — connecting an unhydrated session would replay the whole
294
- * log, and the library never picks the expensive path silently.
295
- */
296
- function useSessionLifecycle<D extends EventDefs, S, P extends PresenceDefs, V>(
297
- session: SessionClient<D, S, P, V>,
298
- live: boolean,
299
- ): void {
300
- useEffect(() => {
301
- if (!live) return
302
- return session.connect()
303
- }, [session, live])
304
- }
305
-
306
- function useSessionResult<D extends EventDefs, S, P extends PresenceDefs, V>(
307
- session: SessionClient<D, S, P, V>,
308
- ): UseSessionResult<D, V, P> {
309
- const snapshot: SessionSnapshot<D, V, P> = useSyncExternalStore(
239
+ function useLiveSession<
240
+ D extends EventDefs,
241
+ S,
242
+ P extends PresenceDefs,
243
+ >(options: {
244
+ client: A2Client<D, S, P>
245
+ sessionId: string
246
+ settings: SessionOptions<D, S>
247
+ enabled: boolean
248
+ suspend: boolean
249
+ }): UseSessionResult<D, S, P> {
250
+ const { client, sessionId, settings, enabled, suspend } = options
251
+ const browserSession = useMemo(() => {
252
+ if (typeof window === 'undefined') {
253
+ if (isInitialPromise(settings.initial))
254
+ void settings.initial.catch(() => {})
255
+ return undefined
256
+ }
257
+ return client.session(sessionId, settings)
258
+ }, [client, sessionId, settings])
259
+ const promise = isInitialPromise(settings.initial)
260
+ ? settings.initial
261
+ : undefined
262
+ const resolved = promise && suspend ? use(promise) : undefined
263
+ const renderInitial = promise ? resolved : settings.initial
264
+ const renderSession = useMemo(() => {
265
+ let session: SessionClient<D, S, P> | undefined
266
+ return () => {
267
+ session ??= createRenderSession({
268
+ client,
269
+ sessionId,
270
+ settings: { ...settings, initial: renderInitial },
271
+ })
272
+ return session
273
+ }
274
+ }, [client, sessionId, settings, renderInitial])
275
+ const session = browserSession ?? renderSession()
276
+ if (browserSession && promise && suspend)
277
+ resolveInitialForReact({
278
+ client,
279
+ session: browserSession,
280
+ source: promise,
281
+ value: resolved,
282
+ })
283
+ const serverSnapshot = useMemo(
284
+ () => () => renderSession().getSnapshot(),
285
+ [renderSession],
286
+ )
287
+ const snapshot = useSyncExternalStore(
310
288
  session.subscribe,
311
289
  session.getSnapshot,
312
- session.getSnapshot,
290
+ serverSnapshot,
313
291
  )
292
+ useEffect(() => {
293
+ if (enabled) return session.connect()
294
+ }, [session, enabled])
314
295
  return useMemo(() => {
315
- // Runtime mirror of the WithPresence surface: the client attaches
316
- // setPresence (and the snapshot carries presence) exactly when the
317
- // reducer declares presence fields — no runtime-inert members.
318
296
  const { setPresence } = session as Partial<SessionClientPresence<P>>
319
297
  const { presence } = snapshot as Partial<{ presence: PresenceMap<P> }>
320
298
  return {
321
- state: snapshot.state,
322
- events: snapshot.events,
323
- index: snapshot.index,
324
- connection: snapshot.connection,
299
+ ...snapshot,
325
300
  push: session.push,
326
301
  reconnect: session.reconnect,
327
302
  loadHistory: session.loadHistory,
328
- history: snapshot.history,
329
303
  ...(setPresence && presence ? { presence, setPresence } : {}),
330
- } as UseSessionResult<D, V, P>
331
- }, [snapshot, session])
304
+ } as UseSessionResult<D, S, P>
305
+ }, [session, snapshot])
332
306
  }
333
307
 
334
- /**
335
- * The standalone, provider-less hook — for apps whose components reach
336
- * sessions ad hoc (a sidebar of channel sessions, a user session read
337
- * from a menu). Takes the shared client and a session id; the handle
338
- * is identity-mapped, so every hook and provider mounting the same
339
- * session shares one runtime and one leased stream. Hydration is
340
- * app-owned: pass `hydrate` when your fetch lands; until then the
341
- * stream is held, and your data layer's loading state is the pending
342
- * state.
343
- */
344
308
  export function useSession<
345
309
  D extends EventDefs,
346
310
  S,
347
311
  P extends PresenceDefs = Record<never, never>,
348
- V = S,
349
312
  >(
350
- client: A2Client<D, S, P, V>,
313
+ client: A2Client<D, S, P>,
351
314
  sessionId: string,
352
- options?: UseSessionOptions<S>,
353
- ): UseSessionResult<D, V, P> {
354
- const { participant, hydrate } = options ?? {}
355
- const session = useMemo(
356
- () =>
357
- client.session(sessionId, {
358
- ...(hydrate === undefined
359
- ? {}
360
- : { initialState: hydrate.state, initialIndex: hydrate.index }),
361
- ...(participant === undefined ? {} : { participant }),
362
- }),
363
- [client, sessionId, hydrate, participant],
315
+ options: UseSessionOptions<NoInfer<S>> = {},
316
+ ): UseSessionResult<D, S, P> {
317
+ const { initial, participant } = options
318
+ const settings = useMemo(
319
+ () => ({
320
+ ...(initial === undefined ? {} : { initial }),
321
+ ...(participant === undefined ? {} : { participant }),
322
+ }),
323
+ [initial, participant],
364
324
  )
365
- useSessionLifecycle(session, hydrate !== undefined)
366
- return useSessionResult(session)
325
+ return useLiveSession({
326
+ client,
327
+ sessionId,
328
+ settings,
329
+ enabled: options.enabled ?? true,
330
+ suspend: options.suspend ?? false,
331
+ })
367
332
  }