experimental-a2 0.17.0 → 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 (211) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +1 -1
  3. package/dist/{actor-DBbe5xeE.d.ts → actor-Bjce9_5U.d.ts} +5 -5
  4. package/dist/{actor-DBbe5xeE.d.ts.map → actor-Bjce9_5U.d.ts.map} +1 -1
  5. package/dist/actor-client.d.ts +1 -1
  6. package/dist/actor-react.d.ts +3 -3
  7. package/dist/actor-react.js +1 -1
  8. package/dist/actor.d.ts +1 -1
  9. package/dist/actor.js +2 -2
  10. package/dist/{ai-UrWfKeuv.js → ai-BpOGm-zB.js} +2 -2
  11. package/dist/{ai-UrWfKeuv.js.map → ai-BpOGm-zB.js.map} +1 -1
  12. package/dist/{ai-vpGtcaCB.d.ts → ai-DrUaMyRt.d.ts} +3 -3
  13. package/dist/{ai-vpGtcaCB.d.ts.map → ai-DrUaMyRt.d.ts.map} +1 -1
  14. package/dist/ai-client.d.ts +2 -2
  15. package/dist/ai-client.js +2 -2
  16. package/dist/ai-fx.browser.d.ts +1 -0
  17. package/dist/ai-fx.browser.js +6 -0
  18. package/dist/ai-fx.browser.js.map +1 -0
  19. package/dist/ai-fx.d.ts +10 -0
  20. package/dist/ai-fx.d.ts.map +1 -0
  21. package/dist/ai-fx.js +634 -0
  22. package/dist/ai-fx.js.map +1 -0
  23. package/dist/ai-harness-BuLh_EYi.js +273 -0
  24. package/dist/ai-harness-BuLh_EYi.js.map +1 -0
  25. package/dist/ai-harness-DvsPeJeB.d.ts +26 -0
  26. package/dist/ai-harness-DvsPeJeB.d.ts.map +1 -0
  27. package/dist/ai-pi.browser.d.ts +1 -0
  28. package/dist/ai-pi.browser.js +6 -0
  29. package/dist/ai-pi.browser.js.map +1 -0
  30. package/dist/ai-pi.d.ts +17 -0
  31. package/dist/ai-pi.d.ts.map +1 -0
  32. package/dist/ai-pi.js +904 -0
  33. package/dist/ai-pi.js.map +1 -0
  34. package/dist/ai-react.d.ts +5 -5
  35. package/dist/ai-react.js +2 -2
  36. package/dist/ai-server.d.ts +47 -25
  37. package/dist/ai-server.d.ts.map +1 -1
  38. package/dist/ai-server.js +106 -37
  39. package/dist/ai-server.js.map +1 -1
  40. package/dist/ai.d.ts +1 -1
  41. package/dist/ai.js +1 -1
  42. package/dist/bounded-cache-CPHWF-8h.js +41 -0
  43. package/dist/bounded-cache-CPHWF-8h.js.map +1 -0
  44. package/dist/{client-COaiaDub.js → client-BReKqwZC.js} +3 -3
  45. package/dist/{client-COaiaDub.js.map → client-BReKqwZC.js.map} +1 -1
  46. package/dist/{client-qRnRUzBS.d.ts → client-h_DKFAjj.d.ts} +3 -3
  47. package/dist/{client-qRnRUzBS.d.ts.map → client-h_DKFAjj.d.ts.map} +1 -1
  48. package/dist/client.d.ts +3 -3
  49. package/dist/client.js +1 -1
  50. package/dist/index.d.ts +5 -5
  51. package/dist/{initial-snapshot-BrLso5x_.d.ts → initial-snapshot-Bx-c0pDP.d.ts} +2 -2
  52. package/dist/{initial-snapshot-BrLso5x_.d.ts.map → initial-snapshot-Bx-c0pDP.d.ts.map} +1 -1
  53. package/dist/{internal-CbVvEwad.js → internal-B2T3U4Rq.js} +8 -2
  54. package/dist/internal-B2T3U4Rq.js.map +1 -0
  55. package/dist/otel.d.ts +1 -1
  56. package/dist/{react-BbJ-g6Uu.d.ts → react-BBXgHWFc.d.ts} +4 -4
  57. package/dist/{react-BbJ-g6Uu.d.ts.map → react-BBXgHWFc.d.ts.map} +1 -1
  58. package/dist/react.d.ts +1 -1
  59. package/dist/react.js +1 -1
  60. package/dist/reducer-BAXc8JyF.js.map +1 -1
  61. package/dist/{reducer-BYu6DT7h.d.ts → reducer-CqA59Q6U.d.ts} +2 -2
  62. package/dist/{reducer-BYu6DT7h.d.ts.map → reducer-CqA59Q6U.d.ts.map} +1 -1
  63. package/dist/scheduler-qstash.d.ts +2 -2
  64. package/dist/scheduler-qstash.js +2 -2
  65. package/dist/scheduler-vercel.d.ts +2 -2
  66. package/dist/scheduler-vercel.js +2 -2
  67. package/dist/{server-Rj64fNvX.js → server-Dw_k_7kO.js} +93 -60
  68. package/dist/server-Dw_k_7kO.js.map +1 -0
  69. package/dist/{server-CGM8WzRG.d.ts → server-En7cbi3v.d.ts} +36 -7
  70. package/dist/server-En7cbi3v.d.ts.map +1 -0
  71. package/dist/server.d.ts +4 -4
  72. package/dist/server.js +1 -1
  73. package/dist/{session-clear-Dshpuhff.d.ts → session-clear-Bo-XTFEy.d.ts} +2 -2
  74. package/dist/session-clear-Bo-XTFEy.d.ts.map +1 -0
  75. package/dist/{store-Gpkq7WBo.d.ts → store-Djdv0bnZ.d.ts} +31 -2
  76. package/dist/store-Djdv0bnZ.d.ts.map +1 -0
  77. package/dist/store-N8PXxDAS.js.map +1 -1
  78. package/dist/store-memory.d.ts +1 -1
  79. package/dist/store-memory.js +32 -6
  80. package/dist/store-memory.js.map +1 -1
  81. package/dist/{store-polling-4_y8lETm.js → store-polling-B5FcwJfo.js} +2 -2
  82. package/dist/{store-polling-4_y8lETm.js.map → store-polling-B5FcwJfo.js.map} +1 -1
  83. package/dist/store-postgres.d.ts +1 -1
  84. package/dist/store-postgres.js +54 -3
  85. package/dist/store-postgres.js.map +1 -1
  86. package/dist/{store-presence-polling-Q1C-k2dd.js → store-presence-polling-BAwS8RWn.js} +3 -3
  87. package/dist/{store-presence-polling-Q1C-k2dd.js.map → store-presence-polling-BAwS8RWn.js.map} +1 -1
  88. package/dist/store-redis-http.d.ts +1 -1
  89. package/dist/store-redis-http.js +3 -3
  90. package/dist/{store-redis-notify-tqWNnMRu.js → store-redis-notify-BRITti_o.js} +187 -68
  91. package/dist/store-redis-notify-BRITti_o.js.map +1 -0
  92. package/dist/store-redis.d.ts +1 -1
  93. package/dist/store-redis.js +1 -1
  94. package/dist/store-sqlite.d.ts +1 -1
  95. package/dist/store-sqlite.js +32 -2
  96. package/dist/store-sqlite.js.map +1 -1
  97. package/dist/{telemetry-cZH-cibj.d.ts → telemetry-Cyfqo6Qt.d.ts} +2 -2
  98. package/dist/telemetry-Cyfqo6Qt.d.ts.map +1 -0
  99. package/dist/{wire-DjfHOvdW.js → wire-rSUglYo8.js} +2 -2
  100. package/dist/{wire-DjfHOvdW.js.map → wire-rSUglYo8.js.map} +1 -1
  101. package/docs/01-quickstart.mdx +49 -83
  102. package/docs/{guides/07-examples.mdx → 02-examples.mdx} +10 -20
  103. package/docs/actors/01-introduction.mdx +48 -86
  104. package/docs/actors/02-concurrency.mdx +43 -70
  105. package/docs/actors/03-timers.mdx +28 -42
  106. package/docs/actors/04-routes.mdx +110 -150
  107. package/docs/actors/meta.ts +1 -1
  108. package/docs/agents/01-introduction.mdx +257 -0
  109. package/docs/agents/02-tools.mdx +247 -0
  110. package/docs/agents/03-conversations.mdx +538 -0
  111. package/docs/agents/04-harnesses.mdx +136 -0
  112. package/docs/agents/meta.ts +1 -0
  113. package/docs/client/01-react.mdx +294 -0
  114. package/docs/client/02-transports.mdx +86 -0
  115. package/docs/client/03-presence.mdx +224 -0
  116. package/docs/client/meta.ts +1 -0
  117. package/docs/contract/01-events.mdx +57 -0
  118. package/docs/contract/02-state.mdx +142 -0
  119. package/docs/contract/03-sessions.mdx +175 -0
  120. package/docs/index.mdx +108 -144
  121. package/docs/reference/01-core.mdx +139 -0
  122. package/docs/reference/02-server.mdx +855 -0
  123. package/docs/reference/03-client.mdx +301 -0
  124. package/docs/reference/04-ai.mdx +722 -0
  125. package/docs/reference/05-adapters.mdx +351 -0
  126. package/docs/reference/06-errors.mdx +138 -0
  127. package/docs/reference/meta.ts +1 -1
  128. package/docs/server/01-handlers.mdx +200 -0
  129. package/docs/server/02-timers.mdx +130 -0
  130. package/docs/server/03-cancellation.mdx +141 -0
  131. package/docs/server/04-routes.mdx +243 -0
  132. package/docs/server/05-production.mdx +322 -0
  133. package/docs/server/06-guarantees.mdx +123 -0
  134. package/examples/playground/DEPLOY.md +1 -1
  135. package/examples/playground/app/a0/[appId]/page.tsx +11 -6
  136. package/examples/playground/app/a0/app-list.tsx +9 -2
  137. package/examples/playground/app/a0/app-view.tsx +9 -1
  138. package/examples/playground/app/a0/apps/[appId]/route.ts +10 -5
  139. package/examples/playground/app/a0/apps.test.ts +12 -0
  140. package/examples/playground/app/a0/apps.ts +22 -7
  141. package/examples/playground/app/a0/chat.tsx +47 -1
  142. package/examples/playground/app/a0/fx/events/route.ts +9 -0
  143. package/examples/playground/app/a0/fx/scheduler/route.ts +7 -0
  144. package/examples/playground/app/a0/fx/short/events/route.ts +11 -0
  145. package/examples/playground/app/a0/model.test.ts +18 -1
  146. package/examples/playground/app/a0/model.ts +47 -10
  147. package/examples/playground/app/a0/no-photon.cjs +4 -0
  148. package/examples/playground/app/a0/no-quickjs.cjs +3 -0
  149. package/examples/playground/app/a0/pi/events/route.ts +9 -0
  150. package/examples/playground/app/a0/pi/scheduler/route.ts +7 -0
  151. package/examples/playground/app/a0/pi/short/events/route.ts +11 -0
  152. package/examples/playground/app/a0/sandbox-log.ts +10 -4
  153. package/examples/playground/app/a0/sandbox-session.ts +152 -0
  154. package/examples/playground/app/a0/scheduler/route.ts +2 -2
  155. package/examples/playground/app/a0/server.ts +100 -45
  156. package/examples/playground/app/a0/session.ts +17 -5
  157. package/examples/playground/app/a0/shell.tsx +11 -3
  158. package/examples/playground/app/agent/ingress.test.ts +6 -3
  159. package/examples/playground/app/agent/server.ts +9 -6
  160. package/examples/playground/app/agent/telemetry.test.ts +71 -50
  161. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +1 -1
  162. package/examples/playground/app/chat/server.test.ts +30 -26
  163. package/examples/playground/app/chat/server.ts +46 -46
  164. package/examples/playground/next.config.ts +17 -5
  165. package/examples/playground/package.json +4 -1
  166. package/examples/playground/vercel.json +18 -0
  167. package/package.json +27 -3
  168. package/src/ai-agent.ts +261 -87
  169. package/src/ai-fx.browser.ts +5 -0
  170. package/src/ai-fx.ts +833 -0
  171. package/src/ai-harness.ts +518 -0
  172. package/src/ai-model-metadata.ts +11 -6
  173. package/src/ai-pi.browser.ts +5 -0
  174. package/src/ai-pi.ts +1263 -0
  175. package/src/ai-sdk-step.ts +2 -2
  176. package/src/ai-server.ts +3 -1
  177. package/src/internal.ts +7 -0
  178. package/src/libfx.d.ts +34 -0
  179. package/src/reducer.ts +1 -1
  180. package/src/server.ts +177 -19
  181. package/src/store-memory.ts +42 -7
  182. package/src/store-postgres.ts +80 -0
  183. package/src/store-redis-core.ts +210 -79
  184. package/src/store-redis-notify.ts +2 -0
  185. package/src/store-sqlite.ts +60 -0
  186. package/src/store.ts +33 -0
  187. package/src/telemetry.ts +1 -0
  188. package/dist/internal-CbVvEwad.js.map +0 -1
  189. package/dist/server-CGM8WzRG.d.ts.map +0 -1
  190. package/dist/server-Rj64fNvX.js.map +0 -1
  191. package/dist/session-clear-Dshpuhff.d.ts.map +0 -1
  192. package/dist/store-Gpkq7WBo.d.ts.map +0 -1
  193. package/dist/store-redis-notify-tqWNnMRu.js.map +0 -1
  194. package/dist/telemetry-cZH-cibj.d.ts.map +0 -1
  195. package/docs/concepts/01-contracts.mdx +0 -237
  196. package/docs/concepts/02-handlers.mdx +0 -284
  197. package/docs/concepts/03-durability.mdx +0 -314
  198. package/docs/concepts/04-state.mdx +0 -209
  199. package/docs/guides/01-timers.mdx +0 -193
  200. package/docs/guides/02-cancellation.mdx +0 -161
  201. package/docs/guides/03-react.mdx +0 -324
  202. package/docs/guides/04-local-first.mdx +0 -47
  203. package/docs/guides/05-production.mdx +0 -534
  204. package/docs/guides/06-ai-agents.mdx +0 -1408
  205. package/docs/guides/08-application-data.mdx +0 -113
  206. package/docs/guides/09-presence.mdx +0 -262
  207. package/docs/guides/10-transports.mdx +0 -221
  208. package/docs/reference/01-api.mdx +0 -2358
  209. package/docs/reference/02-errors.mdx +0 -150
  210. /package/docs/{concepts → contract}/meta.ts +0 -0
  211. /package/docs/{guides → server}/meta.ts +0 -0
@@ -1,324 +0,0 @@
1
- ---
2
- title: Live UI with React
3
- description: "Start from a snapshot, fold new events, and push optimistic writes through one shared session."
4
- ---
5
-
6
- ## One session, one state
7
-
8
- A contract can publish one reducer. The client starts from a snapshot of
9
- that state and folds subsequent events locally. Initial data from a server
10
- component is optional. Without it, the server sends a snapshot as the first
11
- data frame of the subscription, then streams events after its index.
12
-
13
- The same connection carries bootstrap and live events, over SSE or WebSocket.
14
- Events appended while the snapshot is read remain in the durable log and
15
- are delivered after its index. Reconnecting resumes after the last delivered
16
- position without fetching another snapshot.
17
-
18
- This is session sync. A client follows a known session ID. Keep collection
19
- queries across sessions in your [application database](/guides/application-data).
20
-
21
- ## Publish the state
22
-
23
- The reducer lives with the event vocabulary, in a shared module:
24
-
25
- ```ts model.ts
26
- import { contract } from 'experimental-a2'
27
- import { z } from 'zod'
28
-
29
- export const counter = contract({
30
- name: 'counter',
31
- events: { added: z.number() },
32
- reducer: {
33
- name: 'count.v1',
34
- initialState: 0,
35
- fold: (state, event) => state + event.payload,
36
- },
37
- })
38
- ```
39
-
40
- The reducer name identifies its snapshot format. Change it when the fold's
41
- meaning or stored shape changes. The server and client must use the same
42
- version. A fold returning a discriminated union may need `as const` on a
43
- returned literal or an explicit return type. The standalone `reducer`
44
- builder fixes the state type before its `.fold()` call.
45
-
46
- Additional server-side reducers can be read with `session.state(reducer)`;
47
- those reads do not publish another client state.
48
-
49
- ```ts server/counter.ts
50
- import { createServer } from 'experimental-a2/server'
51
- import { counter } from '../model'
52
-
53
- export const counterServer = createServer({ contract: counter })
54
- ```
55
-
56
- ```ts app/api/counter/route.ts
57
- import { counterServer } from '../../../server/counter'
58
-
59
- export const GET = counterServer.fetch
60
- export const POST = counterServer.fetch
61
- ```
62
-
63
- `GET` serves the subscription and bounded history reads. `POST` appends
64
- writes. Wrap `server.fetch` with its `authorize` option for application
65
- checks. Bootstrap is authorized as a stream operation with `bootstrap: true`
66
- before reading state.
67
-
68
- ## Bind React once
69
-
70
- The factory binds the client's types into the Provider and hook. It does
71
- not create a separate connection pool.
72
-
73
- ```tsx app/counter/session.ts
74
- 'use client'
75
- import { createClient } from 'experimental-a2/client'
76
- import { createReact } from 'experimental-a2/react'
77
- import { counter } from '../../model'
78
-
79
- export const counterClient = createClient({
80
- contract: counter,
81
- api: '/api/counter',
82
- })
83
-
84
- export const { SessionProvider, useSession } = createReact({
85
- client: counterClient,
86
- })
87
- ```
88
-
89
- ```tsx app/counter/counter.tsx
90
- 'use client'
91
- import { useSession } from './session'
92
-
93
- export function Counter() {
94
- const { state, connection, push } = useSession()
95
- const error = 'error' in connection ? connection.error : null
96
-
97
- return (
98
- <>
99
- <button onClick={() => void push({ type: 'added', payload: 1 })}>
100
- Count: {state}
101
- </button>
102
- {error && <p role="alert">{error.message}</p>}
103
- </>
104
- )
105
- }
106
- ```
107
-
108
- `state` has the reducer's client state type. A supplied snapshot seeds the
109
- first render; otherwise the client displays `reducer.initialState` (through
110
- the reducer's client projection when present). The connection snapshot
111
- replaces that seed atomically, then new events update it. The seed can look
112
- empty while an existing session loads. Supply a server snapshot to render
113
- its current state immediately.
114
-
115
- `connection` stays `connecting` until bootstrap completes. Failures use its
116
- existing error and retry policy. A slow snapshot can keep loading while the
117
- connection sends heartbeats. The ordinary stream stall watchdog handles a
118
- silent connection. A reconnect preserves the last state.
119
-
120
- A push made during bootstrap is sent right away; it does not wait for the
121
- snapshot. Its optimistic update appears once the snapshot is applied. If
122
- the push was already sent when the snapshot arrives and has no
123
- acknowledgement yet, it stays hidden until the acknowledgement arrives,
124
- because the snapshot may already contain it. Pushes queued behind it stay
125
- hidden with it, so the view never skips a write. As usual, the push promise
126
- resolves on the server acknowledgement; `.confirmed` waits for delivery
127
- through the stream, and the bootstrap snapshot counts as delivery through
128
- its index.
129
-
130
- ## Without server rendering
131
-
132
- A Provider only needs the session ID. It supplies scope and optional data;
133
- mounted hooks own connection leases.
134
-
135
- ```tsx app/counter/page.tsx
136
- import { Counter } from './counter'
137
- import { SessionProvider } from './session'
138
-
139
- export default function Page() {
140
- return (
141
- <SessionProvider sessionId="shared">
142
- <Counter />
143
- </SessionProvider>
144
- )
145
- }
146
- ```
147
-
148
- You can also omit the Provider. The bound hook already knows its client:
149
-
150
- ```tsx app/counter/standalone.tsx
151
- 'use client'
152
- import { useSession } from './session'
153
-
154
- export function StandaloneCounter({ sessionId }: { sessionId: string }) {
155
- const { state, push } = useSession({ sessionId })
156
- return (
157
- <button
158
- onClick={() => void push({ type: 'added', payload: 1 })}
159
- >
160
- {state}
161
- </button>
162
- )
163
- }
164
- ```
165
-
166
- The direct `useSession(client, sessionId, options?)` export is available
167
- when you do not use the factory. It has the same bootstrap and connection
168
- ownership behavior.
169
-
170
- ## Seed the first render
171
-
172
- Server rendering can skip the bootstrap read. Supply the state and its
173
- index together:
174
-
175
- ```tsx app/seeded-counter/page.tsx
176
- import { counterServer } from '../../server/counter'
177
- import { Counter } from '../counter/counter'
178
- import { SessionProvider } from '../counter/session'
179
-
180
- export default async function Page() {
181
- const initial = await counterServer.session('shared').state()
182
- return (
183
- <SessionProvider
184
- sessionId="shared"
185
- initial={initial}
186
- >
187
- <Counter />
188
- </SessionProvider>
189
- )
190
- }
191
- ```
192
-
193
- Index 10 means this state already includes events through 10. The stream
194
- starts after 10. Index 0 is valid when paired with the reducer's initial
195
- state; that explicitly requests replay from the beginning. An index by
196
- itself cannot describe a reducer state.
197
-
198
- A standalone hook can receive the same pair as `initial`. Omitting
199
- it enables automatic bootstrap. For an asynchronous server read, pass its
200
- Promise as described below.
201
-
202
- SSR uses render-local snapshots. One request's seed does not modify another
203
- request's state, and browser hydration first renders the supplied snapshot
204
- even when the shared browser cache has already advanced.
205
-
206
- `hydrated` distinguishes the reducer seed from a known state/index snapshot.
207
- It is `false` until a supplied snapshot or subscription bootstrap initializes
208
- the session, including while a cold preload is pending or rejected. A snapshot
209
- at index 0 counts. Once initialized, it stays `true` through disconnections,
210
- reconnects, and disabled readers while the session retains its state. A new
211
- session without a seed starts at `false` again.
212
-
213
- Use `hydrated` when a decision needs initialized state, such as distinguishing
214
- an empty queue from one that has not loaded. `connection` describes the
215
- transport independently. A hydrated snapshot may be stale and does not imply
216
- permission to write. Imperative code reads the same flag from
217
- `client.session(id).getSnapshot().hydrated`.
218
-
219
- ## Start a preload on the server
220
-
221
- `initial` also accepts a stable Promise of a snapshot. A pending preload
222
- blocks only cold startup: A2 waits before opening its subscription, displaying
223
- `reducer.initialState` without suspending the UI by default. A snapshot result
224
- replaces that seed and skips the
225
- bootstrap read. A `null` or `undefined` result uses ordinary subscription
226
- bootstrap. No additional request is started to resolve the Promise.
227
-
228
- ```tsx app/preloaded-counter/page.tsx
229
- import { Suspense } from 'react'
230
- import { counterServer } from '../../server/counter'
231
- import { SessionProvider } from '../counter/session'
232
- import { PreloadedCounter } from './counter'
233
-
234
- export default function Page() {
235
- const initial = counterServer.session('shared').state()
236
- return (
237
- <SessionProvider sessionId="shared" initial={initial}>
238
- <h1>Counter</h1>
239
- <Suspense fallback={<p>Loading counter…</p>}>
240
- <PreloadedCounter />
241
- </Suspense>
242
- </SessionProvider>
243
- )
244
- }
245
- ```
246
-
247
- ```tsx app/preloaded-counter/counter.tsx
248
- 'use client'
249
- import { useSession } from '../counter/session'
250
-
251
- export function PreloadedCounter() {
252
- const { state, push } = useSession({ suspend: true })
253
- return (
254
- <button
255
- onClick={() => void push({ type: 'added', payload: 1 })}
256
- >
257
- {state}
258
- </button>
259
- )
260
- }
261
- ```
262
-
263
- A rejected cold preload pauses startup and reports its error through
264
- `connection`. A2 cannot retry an externally created Promise: supply a new
265
- Promise, a snapshot, or `null` to continue. The caller can catch a preload
266
- failure and return `null` when bootstrap fallback is appropriate.
267
-
268
- A warm session keeps its state and connection while another initial Promise
269
- is pending. A newer successful snapshot follows the normal hydration rules;
270
- a rejected late preload does not disrupt a working session. Replaced sources
271
- and hard close invalidate pending results, including deferred hydration.
272
- Unmounting releases that hook's connection lease; it does not cancel the
273
- caller's fetch. Within a cached session, repeating the same Promise does not
274
- restart cancelled work.
275
-
276
- Each hook can opt into React Suspense with `useSession({ suspend: true })`.
277
- It reads the supplied Promise with React `use()`. The nearest Suspense and
278
- Error boundaries handle its pending and rejected states. The Provider itself
279
- does not suspend. Without a Promise, or when it resolves to no snapshot,
280
- ordinary subscription bootstrap displays the reducer seed without suspending.
281
- `enabled` continues to control connection ownership separately.
282
-
283
- A non-suspending preload renders the reducer seed on the server. Await the
284
- snapshot upstream or use the suspending hook within an appropriate boundary
285
- to render the loaded session in the server response. Keep
286
- Promise identity stable across client renders; do not create it inside the
287
- component that consumes it.
288
-
289
- ## Connections and activation
290
-
291
- Hooks using the same client instance and session ID share one runtime,
292
- connection, state, and optimistic queue. Providers do not determine deduplication.
293
- Another client instance is isolated, even when its URL matches.
294
-
295
- `useSession({ enabled: false })` observes cached or initial data without
296
- acquiring an automatic connection lease. The default is `true`. Disabling
297
- or unmounting a hook releases only its own lease. Another enabled consumer
298
- continues normally. Explicit pushes still work and acquire a connection
299
- when needed; disabling is not an authorization boundary.
300
-
301
- The bound hook accepts `sessionId`, `client`, `participant`, `initial`,
302
- `enabled`, and `suspend` options. Initial data and participant defaults are inherited
303
- only when the resolved client and session ID match the Provider's scope.
304
-
305
- ## Events and history
306
-
307
- `useSession()` returns `state`, `events`, `index`, `connection`, `push`,
308
- `reconnect`, `loadHistory`, and `history`. Presence contracts also return
309
- `presence` and `setPresence`.
310
-
311
- `events` contains supplied history, loaded history, received events, and
312
- pending optimistic events. A bootstrap snapshot does not invent historical
313
- rows. Without `initialEvents`, the array starts empty and receives events
314
- after the snapshot's index.
315
-
316
- `loadHistory({ before, limit })` reads older log entries into that array.
317
- Omit the options to load up to 50 events before the oldest loaded event.
318
- History reads do not change state or rewind the live cursor. `history`
319
- reports loading, the oldest loaded index, and whether the beginning is
320
- loaded. History reads currently require the HTTP transport.
321
-
322
- The array is an in-memory collection, not a paginated projection. Long-lived
323
- sessions can accumulate many events; a state snapshot bounds replay work,
324
- not the size of state or retained history.
@@ -1,47 +0,0 @@
1
- ---
2
- title: Client lifetime
3
- description: "Share an in-memory session, control idle eviction, and resume its event stream."
4
- ---
5
-
6
- ## Reuse a client
7
-
8
- Keep the client in a module so components share session identity, state,
9
- raw events, pending writes, and connection ownership:
10
-
11
- ```ts app/session-client.ts
12
- 'use client'
13
- import { createClient } from 'experimental-a2/client'
14
- import { orders } from '@/contracts'
15
-
16
- export const ordersClient = createClient({
17
- contract: orders,
18
- api: '/api/order-events',
19
- gcTime: 5 * 60 * 1_000,
20
- })
21
- ```
22
-
23
- `gcTime` is the idle eviction delay, five minutes by default. Active
24
- connections, listeners, and pending work keep the runtime alive. Another
25
- client instance has a separate cache even when it uses the same URL.
26
-
27
- ## Startup and reconnect
28
-
29
- A new unseeded session receives a snapshot on its event subscription.
30
- A supplied state/index pair skips that read. A cached session keeps its
31
- state and resumes after its delivered event position. See
32
- [Live UI](/guides/react).
33
-
34
- State snapshots bound replay work, not the size of the reducer state or
35
- raw event collection. Choose reducer contents that fit the application.
36
-
37
- ## Persistence
38
-
39
- This cache is in memory. Reloading the page discards it. A2 does not ship
40
- an IndexedDB adapter or a persistent offline write queue. The
41
- `experimental-a2/cache-indexeddb` entry point currently has no implementation.
42
-
43
- Pushes have bounded retries and optimistic rollback when they fail.
44
- Application-owned persistence can restore a state/index pair through the
45
- normal hydration options. Only restore data belonging to the same session
46
- and reducer version, and keep authentication and retained-data policy in
47
- the application.