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.
- package/CHANGELOG.md +21 -0
- package/README.md +1 -1
- package/dist/{actor-DBbe5xeE.d.ts → actor-Bjce9_5U.d.ts} +5 -5
- package/dist/{actor-DBbe5xeE.d.ts.map → actor-Bjce9_5U.d.ts.map} +1 -1
- package/dist/actor-client.d.ts +1 -1
- package/dist/actor-react.d.ts +3 -3
- package/dist/actor-react.js +1 -1
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +2 -2
- package/dist/{ai-UrWfKeuv.js → ai-BpOGm-zB.js} +2 -2
- package/dist/{ai-UrWfKeuv.js.map → ai-BpOGm-zB.js.map} +1 -1
- package/dist/{ai-vpGtcaCB.d.ts → ai-DrUaMyRt.d.ts} +3 -3
- package/dist/{ai-vpGtcaCB.d.ts.map → ai-DrUaMyRt.d.ts.map} +1 -1
- package/dist/ai-client.d.ts +2 -2
- package/dist/ai-client.js +2 -2
- package/dist/ai-fx.browser.d.ts +1 -0
- package/dist/ai-fx.browser.js +6 -0
- package/dist/ai-fx.browser.js.map +1 -0
- package/dist/ai-fx.d.ts +10 -0
- package/dist/ai-fx.d.ts.map +1 -0
- package/dist/ai-fx.js +634 -0
- package/dist/ai-fx.js.map +1 -0
- package/dist/ai-harness-BuLh_EYi.js +273 -0
- package/dist/ai-harness-BuLh_EYi.js.map +1 -0
- package/dist/ai-harness-DvsPeJeB.d.ts +26 -0
- package/dist/ai-harness-DvsPeJeB.d.ts.map +1 -0
- package/dist/ai-pi.browser.d.ts +1 -0
- package/dist/ai-pi.browser.js +6 -0
- package/dist/ai-pi.browser.js.map +1 -0
- package/dist/ai-pi.d.ts +17 -0
- package/dist/ai-pi.d.ts.map +1 -0
- package/dist/ai-pi.js +904 -0
- package/dist/ai-pi.js.map +1 -0
- package/dist/ai-react.d.ts +5 -5
- package/dist/ai-react.js +2 -2
- package/dist/ai-server.d.ts +47 -25
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +106 -37
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +1 -1
- package/dist/ai.js +1 -1
- package/dist/bounded-cache-CPHWF-8h.js +41 -0
- package/dist/bounded-cache-CPHWF-8h.js.map +1 -0
- package/dist/{client-COaiaDub.js → client-BReKqwZC.js} +3 -3
- package/dist/{client-COaiaDub.js.map → client-BReKqwZC.js.map} +1 -1
- package/dist/{client-qRnRUzBS.d.ts → client-h_DKFAjj.d.ts} +3 -3
- package/dist/{client-qRnRUzBS.d.ts.map → client-h_DKFAjj.d.ts.map} +1 -1
- package/dist/client.d.ts +3 -3
- package/dist/client.js +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/{initial-snapshot-BrLso5x_.d.ts → initial-snapshot-Bx-c0pDP.d.ts} +2 -2
- package/dist/{initial-snapshot-BrLso5x_.d.ts.map → initial-snapshot-Bx-c0pDP.d.ts.map} +1 -1
- package/dist/{internal-CbVvEwad.js → internal-B2T3U4Rq.js} +8 -2
- package/dist/internal-B2T3U4Rq.js.map +1 -0
- package/dist/otel.d.ts +1 -1
- package/dist/{react-BbJ-g6Uu.d.ts → react-BBXgHWFc.d.ts} +4 -4
- package/dist/{react-BbJ-g6Uu.d.ts.map → react-BBXgHWFc.d.ts.map} +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/reducer-BAXc8JyF.js.map +1 -1
- package/dist/{reducer-BYu6DT7h.d.ts → reducer-CqA59Q6U.d.ts} +2 -2
- package/dist/{reducer-BYu6DT7h.d.ts.map → reducer-CqA59Q6U.d.ts.map} +1 -1
- package/dist/scheduler-qstash.d.ts +2 -2
- package/dist/scheduler-qstash.js +2 -2
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.js +2 -2
- package/dist/{server-Rj64fNvX.js → server-Dw_k_7kO.js} +93 -60
- package/dist/server-Dw_k_7kO.js.map +1 -0
- package/dist/{server-CGM8WzRG.d.ts → server-En7cbi3v.d.ts} +36 -7
- package/dist/server-En7cbi3v.d.ts.map +1 -0
- package/dist/server.d.ts +4 -4
- package/dist/server.js +1 -1
- package/dist/{session-clear-Dshpuhff.d.ts → session-clear-Bo-XTFEy.d.ts} +2 -2
- package/dist/session-clear-Bo-XTFEy.d.ts.map +1 -0
- package/dist/{store-Gpkq7WBo.d.ts → store-Djdv0bnZ.d.ts} +31 -2
- package/dist/store-Djdv0bnZ.d.ts.map +1 -0
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.js +32 -6
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-4_y8lETm.js → store-polling-B5FcwJfo.js} +2 -2
- package/dist/{store-polling-4_y8lETm.js.map → store-polling-B5FcwJfo.js.map} +1 -1
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.js +54 -3
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-presence-polling-Q1C-k2dd.js → store-presence-polling-BAwS8RWn.js} +3 -3
- package/dist/{store-presence-polling-Q1C-k2dd.js.map → store-presence-polling-BAwS8RWn.js.map} +1 -1
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +3 -3
- package/dist/{store-redis-notify-tqWNnMRu.js → store-redis-notify-BRITti_o.js} +187 -68
- package/dist/store-redis-notify-BRITti_o.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.js +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.js +32 -2
- package/dist/store-sqlite.js.map +1 -1
- package/dist/{telemetry-cZH-cibj.d.ts → telemetry-Cyfqo6Qt.d.ts} +2 -2
- package/dist/telemetry-Cyfqo6Qt.d.ts.map +1 -0
- package/dist/{wire-DjfHOvdW.js → wire-rSUglYo8.js} +2 -2
- package/dist/{wire-DjfHOvdW.js.map → wire-rSUglYo8.js.map} +1 -1
- package/docs/01-quickstart.mdx +49 -83
- package/docs/{guides/07-examples.mdx → 02-examples.mdx} +10 -20
- package/docs/actors/01-introduction.mdx +48 -86
- package/docs/actors/02-concurrency.mdx +43 -70
- package/docs/actors/03-timers.mdx +28 -42
- package/docs/actors/04-routes.mdx +110 -150
- package/docs/actors/meta.ts +1 -1
- package/docs/agents/01-introduction.mdx +257 -0
- package/docs/agents/02-tools.mdx +247 -0
- package/docs/agents/03-conversations.mdx +538 -0
- package/docs/agents/04-harnesses.mdx +136 -0
- package/docs/agents/meta.ts +1 -0
- package/docs/client/01-react.mdx +294 -0
- package/docs/client/02-transports.mdx +86 -0
- package/docs/client/03-presence.mdx +224 -0
- package/docs/client/meta.ts +1 -0
- package/docs/contract/01-events.mdx +57 -0
- package/docs/contract/02-state.mdx +142 -0
- package/docs/contract/03-sessions.mdx +175 -0
- package/docs/index.mdx +108 -144
- package/docs/reference/01-core.mdx +139 -0
- package/docs/reference/02-server.mdx +855 -0
- package/docs/reference/03-client.mdx +301 -0
- package/docs/reference/04-ai.mdx +722 -0
- package/docs/reference/05-adapters.mdx +351 -0
- package/docs/reference/06-errors.mdx +138 -0
- package/docs/reference/meta.ts +1 -1
- package/docs/server/01-handlers.mdx +200 -0
- package/docs/server/02-timers.mdx +130 -0
- package/docs/server/03-cancellation.mdx +141 -0
- package/docs/server/04-routes.mdx +243 -0
- package/docs/server/05-production.mdx +322 -0
- package/docs/server/06-guarantees.mdx +123 -0
- package/examples/playground/DEPLOY.md +1 -1
- package/examples/playground/app/a0/[appId]/page.tsx +11 -6
- package/examples/playground/app/a0/app-list.tsx +9 -2
- package/examples/playground/app/a0/app-view.tsx +9 -1
- package/examples/playground/app/a0/apps/[appId]/route.ts +10 -5
- package/examples/playground/app/a0/apps.test.ts +12 -0
- package/examples/playground/app/a0/apps.ts +22 -7
- package/examples/playground/app/a0/chat.tsx +47 -1
- package/examples/playground/app/a0/fx/events/route.ts +9 -0
- package/examples/playground/app/a0/fx/scheduler/route.ts +7 -0
- package/examples/playground/app/a0/fx/short/events/route.ts +11 -0
- package/examples/playground/app/a0/model.test.ts +18 -1
- package/examples/playground/app/a0/model.ts +47 -10
- package/examples/playground/app/a0/no-photon.cjs +4 -0
- package/examples/playground/app/a0/no-quickjs.cjs +3 -0
- package/examples/playground/app/a0/pi/events/route.ts +9 -0
- package/examples/playground/app/a0/pi/scheduler/route.ts +7 -0
- package/examples/playground/app/a0/pi/short/events/route.ts +11 -0
- package/examples/playground/app/a0/sandbox-log.ts +10 -4
- package/examples/playground/app/a0/sandbox-session.ts +152 -0
- package/examples/playground/app/a0/scheduler/route.ts +2 -2
- package/examples/playground/app/a0/server.ts +100 -45
- package/examples/playground/app/a0/session.ts +17 -5
- package/examples/playground/app/a0/shell.tsx +11 -3
- package/examples/playground/app/agent/ingress.test.ts +6 -3
- package/examples/playground/app/agent/server.ts +9 -6
- package/examples/playground/app/agent/telemetry.test.ts +71 -50
- package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +1 -1
- package/examples/playground/app/chat/server.test.ts +30 -26
- package/examples/playground/app/chat/server.ts +46 -46
- package/examples/playground/next.config.ts +17 -5
- package/examples/playground/package.json +4 -1
- package/examples/playground/vercel.json +18 -0
- package/package.json +27 -3
- package/src/ai-agent.ts +261 -87
- package/src/ai-fx.browser.ts +5 -0
- package/src/ai-fx.ts +833 -0
- package/src/ai-harness.ts +518 -0
- package/src/ai-model-metadata.ts +11 -6
- package/src/ai-pi.browser.ts +5 -0
- package/src/ai-pi.ts +1263 -0
- package/src/ai-sdk-step.ts +2 -2
- package/src/ai-server.ts +3 -1
- package/src/internal.ts +7 -0
- package/src/libfx.d.ts +34 -0
- package/src/reducer.ts +1 -1
- package/src/server.ts +177 -19
- package/src/store-memory.ts +42 -7
- package/src/store-postgres.ts +80 -0
- package/src/store-redis-core.ts +210 -79
- package/src/store-redis-notify.ts +2 -0
- package/src/store-sqlite.ts +60 -0
- package/src/store.ts +33 -0
- package/src/telemetry.ts +1 -0
- package/dist/internal-CbVvEwad.js.map +0 -1
- package/dist/server-CGM8WzRG.d.ts.map +0 -1
- package/dist/server-Rj64fNvX.js.map +0 -1
- package/dist/session-clear-Dshpuhff.d.ts.map +0 -1
- package/dist/store-Gpkq7WBo.d.ts.map +0 -1
- package/dist/store-redis-notify-tqWNnMRu.js.map +0 -1
- package/dist/telemetry-cZH-cibj.d.ts.map +0 -1
- package/docs/concepts/01-contracts.mdx +0 -237
- package/docs/concepts/02-handlers.mdx +0 -284
- package/docs/concepts/03-durability.mdx +0 -314
- package/docs/concepts/04-state.mdx +0 -209
- package/docs/guides/01-timers.mdx +0 -193
- package/docs/guides/02-cancellation.mdx +0 -161
- package/docs/guides/03-react.mdx +0 -324
- package/docs/guides/04-local-first.mdx +0 -47
- package/docs/guides/05-production.mdx +0 -534
- package/docs/guides/06-ai-agents.mdx +0 -1408
- package/docs/guides/08-application-data.mdx +0 -113
- package/docs/guides/09-presence.mdx +0 -262
- package/docs/guides/10-transports.mdx +0 -221
- package/docs/reference/01-api.mdx +0 -2358
- package/docs/reference/02-errors.mdx +0 -150
- /package/docs/{concepts → contract}/meta.ts +0 -0
- /package/docs/{guides → server}/meta.ts +0 -0
package/docs/guides/03-react.mdx
DELETED
|
@@ -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.
|