experimental-a2 0.17.0 → 0.18.1
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 +32 -0
- package/README.md +1 -1
- package/dist/{actor-DBbe5xeE.d.ts → actor-C7b-1arn.d.ts} +5 -5
- package/dist/{actor-DBbe5xeE.d.ts.map → actor-C7b-1arn.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-vpGtcaCB.d.ts → ai-BMLlI2Ff.d.ts} +3 -3
- package/dist/{ai-vpGtcaCB.d.ts.map → ai-BMLlI2Ff.d.ts.map} +1 -1
- package/dist/{ai-UrWfKeuv.js → ai-D6mGlO8j.js} +2 -2
- package/dist/{ai-UrWfKeuv.js.map → ai-D6mGlO8j.js.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 +692 -0
- package/dist/ai-fx.js.map +1 -0
- package/dist/ai-harness-B2suP2Qh.d.ts +26 -0
- package/dist/ai-harness-B2suP2Qh.d.ts.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-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 +950 -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 +113 -40
- package/dist/ai-server.js.map +1 -1
- package/dist/ai-tool-input-DmSqMUQi.js +40 -0
- package/dist/ai-tool-input-DmSqMUQi.js.map +1 -0
- 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-BhHLoNKF.js} +3 -3
- package/dist/{client-COaiaDub.js.map → client-BhHLoNKF.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-CscSY7K_.js} +11 -3
- package/dist/internal-CscSY7K_.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 +3 -3
- package/dist/scheduler-qstash.js.map +1 -1
- package/dist/scheduler-vercel.d.ts +7 -7
- package/dist/scheduler-vercel.d.ts.map +1 -1
- package/dist/scheduler-vercel.js +9 -10
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/{server-Rj64fNvX.js → server-D-Y4pqwu.js} +126 -67
- package/dist/server-D-Y4pqwu.js.map +1 -0
- package/dist/{server-CGM8WzRG.d.ts → server-DYAATFuT.d.ts} +42 -7
- package/dist/server-DYAATFuT.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-C-QwTXV1.d.ts} +2 -2
- package/dist/session-clear-C-QwTXV1.d.ts.map +1 -0
- package/dist/{store-Gpkq7WBo.d.ts → store-CyLUy86v.d.ts} +53 -5
- package/dist/store-CyLUy86v.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 +56 -11
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-4_y8lETm.js → store-polling-7WJV0YWP.js} +2 -2
- package/dist/{store-polling-4_y8lETm.js.map → store-polling-7WJV0YWP.js.map} +1 -1
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +106 -25
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-presence-polling-Q1C-k2dd.js → store-presence-polling-B1ixeqIS.js} +3 -3
- package/dist/{store-presence-polling-Q1C-k2dd.js.map → store-presence-polling-B1ixeqIS.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-CM0AfLY0.js} +271 -101
- package/dist/store-redis-notify-CM0AfLY0.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 +66 -21
- 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-BKxH1vbP.js} +2 -2
- package/dist/{wire-DjfHOvdW.js.map → wire-BKxH1vbP.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 +109 -145
- package/docs/reference/01-core.mdx +139 -0
- package/docs/reference/02-server.mdx +860 -0
- package/docs/reference/03-client.mdx +301 -0
- package/docs/reference/04-ai.mdx +731 -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 +142 -0
- package/docs/server/04-routes.mdx +243 -0
- package/docs/server/05-production.mdx +323 -0
- package/docs/server/06-guarantees.mdx +132 -0
- package/examples/playground/DEPLOY.md +3 -5
- package/examples/playground/app/a0/[appId]/page.tsx +12 -13
- 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/devtools.tsx +2 -8
- 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 +98 -54
- 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 +271 -90
- package/src/ai-fx.browser.ts +5 -0
- package/src/ai-fx.ts +912 -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 +1322 -0
- package/src/ai-sdk-step.ts +2 -2
- package/src/ai-server.ts +3 -1
- package/src/ai-tool-input.ts +45 -0
- package/src/internal.ts +13 -0
- package/src/libfx.d.ts +34 -0
- package/src/reducer.ts +1 -1
- package/src/scheduler-qstash.ts +5 -5
- package/src/scheduler-vercel.ts +14 -15
- package/src/server.ts +233 -29
- package/src/store-memory.ts +92 -16
- package/src/store-postgres.ts +156 -25
- package/src/store-redis-core.ts +322 -110
- package/src/store-redis-notify.ts +2 -0
- package/src/store-sqlite.ts +121 -28
- package/src/store.ts +55 -3
- 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
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Client
|
|
3
|
+
description: "`experimental-a2/client`, `experimental-a2/react`, and `experimental-a2/cache-indexeddb`."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## `experimental-a2/client`
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
createClient(options: {
|
|
10
|
+
contract: Contract // requires a public reducer
|
|
11
|
+
api: ClientApi
|
|
12
|
+
gcTime?: number // idle session lifetime; 5 minutes by default
|
|
13
|
+
participant?: string // default presence identity for every session
|
|
14
|
+
reconnect?: (failure: {
|
|
15
|
+
sessionId: string
|
|
16
|
+
error: A2ClientError | null
|
|
17
|
+
attempt: number
|
|
18
|
+
}) => number | false
|
|
19
|
+
}): A2Client
|
|
20
|
+
|
|
21
|
+
type ClientApi =
|
|
22
|
+
| SessionUrl // one route: GET SSE stream + POST push
|
|
23
|
+
| { type: 'http'; push: SessionUrl; stream: SessionUrl }
|
|
24
|
+
| { type: 'ws'; url: SessionUrl; multiplex?: false }
|
|
25
|
+
| { type: 'ws'; url: string; multiplex: true }
|
|
26
|
+
|
|
27
|
+
type SessionUrl = string | ((sessionId: string) => string)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The framework-agnostic session client `experimental-a2/react` is built on: the
|
|
31
|
+
SSE subscription with frontier resume and reconnection, the optimistic push
|
|
32
|
+
queue with ack and rollback, and the local fold. Use it from any framework, or
|
|
33
|
+
none.
|
|
34
|
+
|
|
35
|
+
**`reconnect`**
|
|
36
|
+
|
|
37
|
+
- Runs after a failed connection or a stream end, including a clean end with
|
|
38
|
+
`error: null`. Not for an explicit restart, an idle-stream refresh, or a
|
|
39
|
+
caller closing the stream.
|
|
40
|
+
- Returns the delay in ms before the next attempt (finite, non-negative, at
|
|
41
|
+
most 2,147,483,647), or `false` to pause. Synchronous; a throw or invalid
|
|
42
|
+
result pauses with an `A2ClientError`.
|
|
43
|
+
- `attempt` starts at 1, counts consecutive attempts per session, and resets
|
|
44
|
+
when the stream goes live or `session.reconnect()` restarts it. It is
|
|
45
|
+
separate from the lifetime `reconnects` count.
|
|
46
|
+
- Default: retry indefinitely, from 500 ms doubling up to five seconds.
|
|
47
|
+
- Paused: `connection.status` is `paused` with its last error. State,
|
|
48
|
+
hydration, optimistic writes, and leases stay intact. Neither a new lease nor
|
|
49
|
+
a push resumes it (push retries are independent); `session.reconnect()`
|
|
50
|
+
does, with a fresh attempt count. Releasing the last lease still closes the
|
|
51
|
+
stream; a later first lease starts a new connection. Resuming keeps the
|
|
52
|
+
stream's own position, even if hydration advanced state while paused.
|
|
53
|
+
|
|
54
|
+
**`SessionUrl` functions** run for each HTTP request or WebSocket connection;
|
|
55
|
+
A2 still adds its `sessionId` and read-bound query parameters. The function
|
|
56
|
+
does not authorize that ID: bind any path scope or URL token to
|
|
57
|
+
`operation.sessionId` in `authorize`, and keep URL tokens short-lived (URLs
|
|
58
|
+
reach infrastructure logs). A multiplexed WebSocket URL is a plain string. See
|
|
59
|
+
[Transports](/client/transports).
|
|
60
|
+
|
|
61
|
+
### `client.session(id, options?)`
|
|
62
|
+
|
|
63
|
+
`client.session(id, { initial?, initialEvents?, participant? })` returns a
|
|
64
|
+
handle:
|
|
65
|
+
|
|
66
|
+
| Member | Does |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `getSnapshot()`, `subscribe()` | the `useSyncExternalStore` contract; snapshots carry `state`, `events`, `index`, `history`, `hydrated`, and `connection`, as in [`useSession`](#usesessionoptions) |
|
|
69
|
+
| `push()`, `loadHistory()`, `reconnect()` | as in `useSession` |
|
|
70
|
+
| `connect()` | takes a stream lease and returns its release. Leases refcount per handle: the first connects, releasing the last closes, releasing twice is a no-op |
|
|
71
|
+
| `close()` | hard stop: drops every lease and closes now; a later `connect()` starts fresh |
|
|
72
|
+
| `setPresence()` | presence contracts only, as in `useSession`; snapshots then carry `presence` |
|
|
73
|
+
|
|
74
|
+
| Option | What it is |
|
|
75
|
+
| --- | --- |
|
|
76
|
+
| `initial` | an [`InitialSnapshot<S>`](#sessionprovider), as in React |
|
|
77
|
+
| `initialEvents` | earlier raw events for history UIs; they merge into `events` and never affect the folded state or stream frontier |
|
|
78
|
+
| `participant` | the identity `setPresence` sends under; overrides `createClient`'s |
|
|
79
|
+
|
|
80
|
+
- One `A2Client` returns the same live handle for repeated `session(id)`
|
|
81
|
+
calls. A later call with a further server-rendered frontier advances it
|
|
82
|
+
without dropping pending optimistic events; a stale render cannot move it
|
|
83
|
+
backward.
|
|
84
|
+
- Idle handles are evicted after `gcTime`. This memory layer keeps identity
|
|
85
|
+
across route transitions; `experimental-a2/cache-indexeddb` keeps the replica
|
|
86
|
+
across reloads.
|
|
87
|
+
- A pending `initial` Promise delays cold connection startup, including a
|
|
88
|
+
connection a push acquires, and starts no request of its own. Replacing the
|
|
89
|
+
source or `close()` invalidates its pending result and any deferred adoption
|
|
90
|
+
behind a write.
|
|
91
|
+
- Releasing the last lease does not cancel the caller's Promise; it can still
|
|
92
|
+
seed cached data without reopening transport. Idle eviction detaches A2's
|
|
93
|
+
listener. Repeating the same Promise in that cached runtime does not restart
|
|
94
|
+
cancelled work.
|
|
95
|
+
|
|
96
|
+
## `experimental-a2/react`
|
|
97
|
+
|
|
98
|
+
### `createReact(options)`
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
createReact(options: {
|
|
102
|
+
client: A2Client // bind a shared client: api + contract + session identity
|
|
103
|
+
}): { SessionProvider, useSession }
|
|
104
|
+
|
|
105
|
+
// or, bind the contract and supply api through the Provider:
|
|
106
|
+
createReact(options: {
|
|
107
|
+
contract: Contract // requires a public reducer
|
|
108
|
+
}): { SessionProvider, useSession }
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
A factory, like `createContext`: call it once in a `'use client'` module and
|
|
112
|
+
export the bound pair. Server components import `SessionProvider` from it as a
|
|
113
|
+
client reference; client components import `useSession` from the same file.
|
|
114
|
+
Bind a shared `client` when code outside the provider pushes before a route
|
|
115
|
+
transition and the destination should adopt the same optimistic session;
|
|
116
|
+
`{ contract }` suits provider-bound clients. See
|
|
117
|
+
[Bind React once](/client/react#bind-react-once).
|
|
118
|
+
|
|
119
|
+
### `<SessionProvider>`
|
|
120
|
+
|
|
121
|
+
| Prop | What it is |
|
|
122
|
+
| --------------- | ------------------------------------------------------ |
|
|
123
|
+
| `sessionId` | which session to subscribe to |
|
|
124
|
+
| `initial` | optional snapshot or Promise of a snapshot; `null` means bootstrap |
|
|
125
|
+
| `initialEvents` | optional earlier raw events for a history UI |
|
|
126
|
+
| `participant` | overrides the client's `participant`; one of the two is required to call `setPresence` |
|
|
127
|
+
|
|
128
|
+
- Supplies scope and optional initial data. Mounted enabled hooks own
|
|
129
|
+
connection leases and reconnect from the last streamed event.
|
|
130
|
+
- `participant` binds at the session's first resolution; later changes are
|
|
131
|
+
ignored for the session's runtime lifetime.
|
|
132
|
+
- `initial` is the complete state/index snapshot, plus `clearIndex` once the
|
|
133
|
+
session has been cleared. A new session streams from after `initial.index`,
|
|
134
|
+
including events appended between render and connection. Later snapshots
|
|
135
|
+
advance state but keep the resume position, so delayed events still enter
|
|
136
|
+
the feed without folding twice.
|
|
137
|
+
- `initialEvents` only preloads the raw `events` feed with entries before that
|
|
138
|
+
frontier; it never affects state hydration or stream resumption.
|
|
139
|
+
|
|
140
|
+
`InitialSnapshot<S>` is `StateSnapshot<S> | null | undefined |
|
|
141
|
+
Promise<StateSnapshot<S> | null | undefined>`, exported from `experimental-a2`
|
|
142
|
+
and `experimental-a2/client`. A cold session waits for a supplied Promise
|
|
143
|
+
before connecting. A snapshot seeds state; `null` or `undefined` selects
|
|
144
|
+
subscription bootstrap. A rejection pauses cold startup and reports through
|
|
145
|
+
`connection.error`; supply a new Promise, snapshot, or `null` to resume. A warm
|
|
146
|
+
session keeps its state and connection. The Provider never suspends. Keep
|
|
147
|
+
Promise identity stable. See
|
|
148
|
+
[Seed the first render](/client/react#seed-the-first-render).
|
|
149
|
+
|
|
150
|
+
### `useSession(options?)`
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
const { state, hydrated, push, reconnect, events, index, loadHistory, history, connection } = useSession()
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
| Option | Default | What it does |
|
|
157
|
+
| --- | --- | --- |
|
|
158
|
+
| `sessionId`, `client`, `participant`, `initial` | from the Provider and bound client | scope and seed |
|
|
159
|
+
| `enabled` | `true` | owns a connection lease. `false` defers automatic I/O: the hook observes cached state, other owners continue, and explicit pushes can still acquire a connection |
|
|
160
|
+
| `suspend` | `false` | `true` reads the `initial` Promise with React `use()`, through the nearest Suspense and Error boundaries. Otherwise a pending cold Promise renders the reducer seed. Automatic bootstrap never suspends, even when the Promise resolves to `null` or `undefined` |
|
|
161
|
+
|
|
162
|
+
| Member | What it is |
|
|
163
|
+
| --- | --- |
|
|
164
|
+
| `state` | the reducer's client state type. A supplied snapshot seeds the first render; otherwise `reducer.initialState` (through the client projection) shows until a snapshot replaces it atomically. Folds live events through the shared reducer |
|
|
165
|
+
| `hydrated` | `true` once an initial snapshot or bootstrap applies, even an empty state at index 0; `false` while only the reducer seed exists. Stays `true` through reconnects, disabled hooks, and a failed replacement preload. Not a sign of a live connection or fresh data. Shared with `session.getSnapshot()` |
|
|
166
|
+
| `events` | the raw feed: observed, seeded, or backscrolled. Starts after `initial.index` unless `initialEvents` or `loadHistory` add earlier entries |
|
|
167
|
+
| `index` | the latest server-confirmed state index, the `lastSeenIndex` for [cancellation](/server/cancellation) |
|
|
168
|
+
| `push`, `reconnect`, `loadHistory`, `history`, `connection` | below |
|
|
169
|
+
| `presence`, `setPresence` | presence contracts only; below |
|
|
170
|
+
|
|
171
|
+
Without a supplied snapshot, the server sends one first on the same
|
|
172
|
+
subscription; bootstrap errors and retries use `connection`.
|
|
173
|
+
|
|
174
|
+
**`loadHistory({ before?, limit? })`** fetches a bounded slice below the
|
|
175
|
+
frontier (same route, `gte`/`lte` query parameters) and resolves with the
|
|
176
|
+
events in that range.
|
|
177
|
+
|
|
178
|
+
- Merges into `events`: deduped, ordered, shared by every handle of the
|
|
179
|
+
session. Never touches `state` or the optimistic overlay.
|
|
180
|
+
- Defaults walk back 50 at a time from the oldest loaded event, also after a
|
|
181
|
+
newer snapshot hydrates. `before` is an exclusive upper bound for another
|
|
182
|
+
range.
|
|
183
|
+
- Calls serialize per session; loaded ranges are not refetched.
|
|
184
|
+
- `history` is `{ loading, complete, oldestLoaded }`. `complete`: the feed
|
|
185
|
+
reaches index 1, or the first event after the latest clear, or the log is
|
|
186
|
+
empty.
|
|
187
|
+
- The `ws` api has no history lane: `loadHistory` throws a `TypeError`.
|
|
188
|
+
|
|
189
|
+
**Clears** reset `state` to the reducer's initial state and drop `events` at or
|
|
190
|
+
below the marker. Unconfirmed pushes stay in the overlay and apply to the reset
|
|
191
|
+
state. A push acked below the marker was cleared with it: it leaves the overlay
|
|
192
|
+
and its `confirmed` resolves.
|
|
193
|
+
|
|
194
|
+
**`push`** appends optimistically: validated locally, rolled back on rejection,
|
|
195
|
+
retried for `STORE_UNAVAILABLE` and transport failures. Awaiting it gives the
|
|
196
|
+
server ack; its `confirmed` is a lazy promise for when the stream has delivered
|
|
197
|
+
the batch back:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
const t0 = performance.now()
|
|
201
|
+
const result = push({ type: 'created', payload })
|
|
202
|
+
await result // the server ack: push→ack latency
|
|
203
|
+
await result.confirmed // the stream round-trip: ack→stream latency
|
|
204
|
+
const roundtripMs = performance.now() - t0
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
- `confirmed` waits for the stream to deliver through the acked index. A
|
|
208
|
+
bootstrap snapshot, or a stream starting after a supplied initial index,
|
|
209
|
+
delivers through that index; hydration does not otherwise confirm an
|
|
210
|
+
unobserved ack.
|
|
211
|
+
- A push never waits for bootstrap, and an HTTP push never waits for a pending
|
|
212
|
+
initial Promise. A WebSocket push rides its connection, which opens once that
|
|
213
|
+
Promise settles.
|
|
214
|
+
- Overlapping calls on one session client update the view immediately and
|
|
215
|
+
enter the transport in invocation order, each waiting only for the previous
|
|
216
|
+
call's ack or final rejection. Other sessions and clients run concurrently
|
|
217
|
+
and converge on the log's durable order.
|
|
218
|
+
- A rejection rejects both promises with the same `A2ClientError`. `confirmed`
|
|
219
|
+
is materialized only when accessed, so ignoring it costs nothing.
|
|
220
|
+
|
|
221
|
+
**`reconnect`** (`session.reconnect(): void`) restarts a leased subscription
|
|
222
|
+
now from its current cursor, including one the policy paused. It keeps state,
|
|
223
|
+
pending writes, and leases, and does nothing without an active lease. It
|
|
224
|
+
requests a restart; it returns no promise for catch-up. Use it after a custom
|
|
225
|
+
REST mutation. A multiplexed WebSocket keeps its socket and other session
|
|
226
|
+
lanes; a single-session WebSocket replaces its socket.
|
|
227
|
+
|
|
228
|
+
**`connection`** is a discriminated union; `error` only exists while
|
|
229
|
+
disconnected:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
// in a client module:
|
|
233
|
+
import type { A2ClientError } from 'experimental-a2/client'
|
|
234
|
+
|
|
235
|
+
type Connection =
|
|
236
|
+
| { status: 'idle' }
|
|
237
|
+
| { status: 'connecting'; reconnects: number; error: A2ClientError | null }
|
|
238
|
+
| { status: 'paused'; reconnects: number; error: A2ClientError | null }
|
|
239
|
+
| { status: 'live'; reconnects: number }
|
|
240
|
+
| { status: 'closed' }
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
"Reconnecting…" is `status === 'connecting' && reconnects > 0`. `live` means
|
|
244
|
+
bytes are flowing: the server heartbeats the stream (`: ping` every 15s), and a
|
|
245
|
+
client that hears nothing for two beats aborts and reconnects.
|
|
246
|
+
|
|
247
|
+
**`presence`** is the replicated ephemeral map,
|
|
248
|
+
`Record<participantId, { [field]: { value, seen, at } }>`, including this
|
|
249
|
+
client. **`setPresence(values)`** is fire-and-forget:
|
|
250
|
+
|
|
251
|
+
- Validated locally and merged into the local map immediately.
|
|
252
|
+
- Coalesced on the wire to a fixed cadence: a leading send, then at most one
|
|
253
|
+
merged send per interval, later values winning per field.
|
|
254
|
+
- Stamped with the frontier at send time as `seen`.
|
|
255
|
+
- No ack, no `confirmed`, no retry. Parallel sends are safe: merges are
|
|
256
|
+
last-writer-wins by `at`, and stamps are monotonic per session (wall-seeded,
|
|
257
|
+
guarded so overlapping sends never invert).
|
|
258
|
+
|
|
259
|
+
Both exist only when the contract declares `presence`, typed from its schemas
|
|
260
|
+
through the reducer with no type arguments (the reducer never folds presence).
|
|
261
|
+
`setPresence` requires a `participant`, from the client or the provider. See
|
|
262
|
+
[Presence](/client/presence).
|
|
263
|
+
|
|
264
|
+
### `useSession(client, sessionId, options?)`
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
useSession(client: A2Client, sessionId: string, options?: {
|
|
268
|
+
participant?: string // overrides the client's participant
|
|
269
|
+
initial?: InitialSnapshot<S>
|
|
270
|
+
enabled?: boolean
|
|
271
|
+
suspend?: boolean
|
|
272
|
+
}): UseSessionResult
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
The provider-less hook, for components that reach sessions ad hoc (a sidebar
|
|
276
|
+
of channel sessions, a user session read from a menu). Same result as the
|
|
277
|
+
bound hook.
|
|
278
|
+
|
|
279
|
+
- Identity-mapped: every hook and provider mounting a session shares one
|
|
280
|
+
runtime. The stream is refcounted: the first mount connects, the last
|
|
281
|
+
unmount closes (StrictMode-safe).
|
|
282
|
+
- `initial` takes the result of `session.state(reducer)` or its Promise. Pass
|
|
283
|
+
it whole: its `clearIndex` lets a cached client drop history from before a
|
|
284
|
+
clear it never streamed (the automatic snapshot carries it too). Without it,
|
|
285
|
+
the subscription obtains a snapshot. For deliberate full replay, supply
|
|
286
|
+
`{ state: reducer.initialState, index: 0 }`.
|
|
287
|
+
- `enabled: false` defers automatic I/O, as above.
|
|
288
|
+
|
|
289
|
+
See [React](/client/react).
|
|
290
|
+
|
|
291
|
+
## `experimental-a2/cache-indexeddb`
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
indexedDb(): A2ClientCache
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
A browser-side cache for `SessionProvider`: the session's events (immutable;
|
|
298
|
+
writes are idempotent by index), a folded snapshot (invalidated by a reducer
|
|
299
|
+
rename), and the pending-push queue. `cache.clear(sessionId?)` is the logout
|
|
300
|
+
story. Create instances in client code; they're live objects, not serializable
|
|
301
|
+
props. See [Client lifetime](/client/react#client-lifetime).
|