experimental-a2 0.6.0 → 0.7.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 +22 -0
- package/dist/ai-server.d.ts +4 -5
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +8 -7
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +334 -2
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +1 -1
- package/dist/client.d.ts +202 -2
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +1025 -1
- package/dist/client.js.map +1 -0
- package/dist/errors-BQuJpe82.js.map +1 -1
- package/dist/index.d.ts +22 -3
- package/dist/index.d.ts.map +1 -0
- package/dist/{internal-DstsI6Re.js → internal-DRXJ56EI.js} +5 -28
- package/dist/internal-DRXJ56EI.js.map +1 -0
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/scheduler-qstash.d.ts +3 -3
- package/dist/scheduler-qstash.js +4 -5
- package/dist/scheduler-qstash.js.map +1 -1
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.js +4 -4
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/{server-Duw6MVlB.js → server-286j79Mt.js} +708 -79
- package/dist/server-286j79Mt.js.map +1 -0
- package/dist/{server-DpvjhdoE.d.ts → server-DgXmORIq.d.ts} +67 -49
- package/dist/server-DgXmORIq.d.ts.map +1 -0
- package/dist/server.d.ts +3 -3
- package/dist/server.js +1 -1
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/{store-DysUkTH3.d.ts → store-flRz1OWh.d.ts} +2 -57
- package/dist/store-flRz1OWh.d.ts.map +1 -0
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.d.ts.map +1 -1
- package/dist/store-memory.js +1 -59
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-dSeLxzfb.js → store-polling-6DW7F1DT.js} +2 -2
- package/dist/{store-polling-dSeLxzfb.js.map → store-polling-6DW7F1DT.js.map} +1 -1
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.js +1 -81
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-redis-core-BFLwz0Wj.js → store-redis-core-DEYO8Ryv.js} +48 -134
- package/dist/store-redis-core-DEYO8Ryv.js.map +1 -0
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +2 -3
- package/dist/store-redis-http.js.map +1 -1
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.js +3 -4
- package/dist/store-redis.js.map +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.d.ts.map +1 -1
- package/dist/store-sqlite.js +1 -72
- package/dist/store-sqlite.js.map +1 -1
- package/dist/{wire-BFQmSJ-9.js → wire-B6te_wns.js} +4 -3
- package/dist/wire-B6te_wns.js.map +1 -0
- package/docs/guides/03-react.mdx +20 -28
- package/docs/guides/05-production.mdx +9 -11
- package/docs/guides/06-ai-agents.mdx +8 -14
- package/docs/guides/09-presence.mdx +14 -19
- package/docs/guides/10-transports.mdx +104 -86
- package/docs/reference/01-api.mdx +143 -277
- package/docs/reference/02-errors.mdx +5 -7
- package/package.json +1 -14
- package/src/ai-server.ts +9 -5
- package/src/client.ts +2 -2
- package/src/errors.ts +1 -0
- package/src/internal.ts +3 -62
- package/src/push-envelope.ts +24 -21
- package/src/scheduler-qstash.ts +3 -3
- package/src/scheduler-vercel.ts +2 -2
- package/src/server-fetch.ts +344 -0
- package/src/server.ts +73 -225
- package/src/session-socket.ts +36 -20
- package/src/sse.ts +2 -2
- package/src/store-memory.ts +0 -81
- package/src/store-postgres.ts +0 -100
- package/src/store-redis-core.ts +47 -211
- package/src/store-redis-http.ts +0 -1
- package/src/store-redis.ts +0 -1
- package/src/store-sqlite.ts +0 -119
- package/src/store.ts +0 -60
- package/src/wire.ts +2 -1
- package/dist/ai-D_PGS-JR.d.ts +0 -334
- package/dist/ai-D_PGS-JR.d.ts.map +0 -1
- package/dist/cli-B3VuxoDe.js +0 -599
- package/dist/cli-B3VuxoDe.js.map +0 -1
- package/dist/cli-bin.d.ts +0 -1
- package/dist/cli-bin.js +0 -7
- package/dist/cli-bin.js.map +0 -1
- package/dist/cli.d.ts +0 -20
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -2
- package/dist/client-CdMqi7mC.d.ts +0 -202
- package/dist/client-CdMqi7mC.d.ts.map +0 -1
- package/dist/client-Dj5d3SP_.js +0 -1026
- package/dist/client-Dj5d3SP_.js.map +0 -1
- package/dist/devtools-J_jZ2vQf.d.ts +0 -152
- package/dist/devtools-J_jZ2vQf.d.ts.map +0 -1
- package/dist/devtools-kJJaORn-.js +0 -340
- package/dist/devtools-kJJaORn-.js.map +0 -1
- package/dist/devtools-server.browser.d.ts +0 -1
- package/dist/devtools-server.browser.js +0 -6
- package/dist/devtools-server.browser.js.map +0 -1
- package/dist/devtools-server.d.ts +0 -23
- package/dist/devtools-server.d.ts.map +0 -1
- package/dist/devtools-server.js +0 -1270
- package/dist/devtools-server.js.map +0 -1
- package/dist/devtools.d.ts +0 -2
- package/dist/devtools.js +0 -2
- package/dist/errors-W6nwJ-fm.d.ts +0 -21
- package/dist/errors-W6nwJ-fm.d.ts.map +0 -1
- package/dist/http.d.ts +0 -151
- package/dist/http.d.ts.map +0 -1
- package/dist/http.js +0 -706
- package/dist/http.js.map +0 -1
- package/dist/inspection-DaxB5jM2.js +0 -13
- package/dist/inspection-DaxB5jM2.js.map +0 -1
- package/dist/internal-DstsI6Re.js.map +0 -1
- package/dist/platform-B4TnJtWu.js +0 -34
- package/dist/platform-B4TnJtWu.js.map +0 -1
- package/dist/server-DpvjhdoE.d.ts.map +0 -1
- package/dist/server-Duw6MVlB.js.map +0 -1
- package/dist/store-DysUkTH3.d.ts.map +0 -1
- package/dist/store-redis-core-BFLwz0Wj.js.map +0 -1
- package/dist/testing.browser.d.ts +0 -1
- package/dist/testing.browser.js +0 -6
- package/dist/testing.browser.js.map +0 -1
- package/dist/testing.d.ts +0 -32
- package/dist/testing.d.ts.map +0 -1
- package/dist/testing.js +0 -103
- package/dist/testing.js.map +0 -1
- package/dist/wire-BFQmSJ-9.js.map +0 -1
- package/docs/guides/07-devtools.mdx +0 -229
- package/src/cli-bin.ts +0 -5
- package/src/cli.ts +0 -1046
- package/src/devtools-app.ts +0 -989
- package/src/devtools-server.browser.ts +0 -5
- package/src/devtools-server.ts +0 -604
- package/src/devtools.ts +0 -716
- package/src/http.ts +0 -394
- package/src/inspection.ts +0 -39
- package/src/testing.browser.ts +0 -5
- package/src/testing.ts +0 -185
|
@@ -7,8 +7,8 @@ description: "One client, three wires: a single HTTP route, split routes for per
|
|
|
7
7
|
|
|
8
8
|
Everything the client does is transport-agnostic: optimistic pushes
|
|
9
9
|
with ack and `confirmed`, frontier-resumed live reads, reconnection
|
|
10
|
-
with backoff,
|
|
11
|
-
|
|
10
|
+
with backoff, and presence throttling. `api` picks the wire underneath
|
|
11
|
+
it:
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
14
|
// in a 'use client' session module:
|
|
@@ -18,73 +18,111 @@ api: { type: 'ws', url: '/api/order-events' }
|
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
- **The string** is the default and the recommendation: one route
|
|
21
|
-
serving GET (SSE stream) and POST (push)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
doesn't.
|
|
27
|
-
- **`ws`** rides everything over one WebSocket: streams come down it,
|
|
28
|
-
pushes and presence go up it. One socket carries every session of
|
|
29
|
-
the client; a page showing ten sessions holds one connection, not
|
|
30
|
-
ten. Use it when latency or per-message cost matters; at a presence
|
|
31
|
-
cadence of fifteen sends a second, each send is a socket frame
|
|
32
|
-
instead of a route invocation.
|
|
21
|
+
serving GET (SSE stream and history) and POST (push).
|
|
22
|
+
- **`http` split** serves the two verbs from separate routes. Use it
|
|
23
|
+
when platform duration limits differ per verb.
|
|
24
|
+
- **`ws`** rides streams, pushes, and presence over one multiplexed
|
|
25
|
+
WebSocket. One socket carries every session of the client.
|
|
33
26
|
|
|
34
27
|
A split socket is unrepresentable on purpose. The socket is one
|
|
35
|
-
connection in both directions
|
|
28
|
+
connection in both directions.
|
|
36
29
|
|
|
37
30
|
One wire is missing from `ws` by design: the history lane.
|
|
38
31
|
[`loadHistory`](/guides/react#the-client-component) is a bounded cold
|
|
39
|
-
read and rides plain HTTP
|
|
32
|
+
read and rides plain HTTP. On a `ws` API it throws a `TypeError`.
|
|
33
|
+
|
|
34
|
+
## The HTTP route
|
|
35
|
+
|
|
36
|
+
`server.fetch` is bound, so an unprotected route is two exports:
|
|
37
|
+
|
|
38
|
+
```ts app/api/public-order-events/route.ts
|
|
39
|
+
import { ordersServer } from '@/server/orders'
|
|
40
|
+
|
|
41
|
+
export const GET = ordersServer.fetch
|
|
42
|
+
export const POST = ordersServer.fetch
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
A plain GET streams one session over SSE. A GET with `gte` and `lte`
|
|
46
|
+
returns a history slice. POST parses the push envelope and appends its
|
|
47
|
+
events. If the contract declares presence, the same route streams and
|
|
48
|
+
accepts presence automatically.
|
|
49
|
+
|
|
50
|
+
Wrap the call when the route needs authentication or authorization:
|
|
51
|
+
|
|
52
|
+
```ts app/api/protected-order-events/route.ts
|
|
53
|
+
import { ordersServer } from '@/server/orders'
|
|
54
|
+
|
|
55
|
+
async function fetchOrders(request: Request): Promise<Response> {
|
|
56
|
+
// your authentication:
|
|
57
|
+
const user = { id: 'user-1' }
|
|
58
|
+
if (!user) return new Response(null, { status: 401 })
|
|
59
|
+
|
|
60
|
+
return ordersServer.fetch(request, {
|
|
61
|
+
authorize(operation) {
|
|
62
|
+
// your per-session and per-operation authorization:
|
|
63
|
+
// return canAccess(user, operation)
|
|
64
|
+
return true
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const GET = fetchOrders
|
|
70
|
+
export const POST = fetchOrders
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`authorize` runs after A2 parses the operation and before schema
|
|
74
|
+
validation or durable I/O. It receives `stream`, `history`, and `push`
|
|
75
|
+
operations. Push operations include parsed event names and payloads,
|
|
76
|
+
plus the complete presence patch when present. Return `false` to deny.
|
|
77
|
+
A2 answers `FORBIDDEN` with status 403. If the hook throws, A2 treats
|
|
78
|
+
that as an infrastructure failure and answers `STORE_UNAVAILABLE`.
|
|
79
|
+
|
|
80
|
+
The hook is per `fetch` call. Authentication stays in the route, where
|
|
81
|
+
request headers, cookies, and framework context are available. The
|
|
82
|
+
captured user can then authorize strongly typed A2 operations without
|
|
83
|
+
repeating request parsing.
|
|
40
84
|
|
|
41
85
|
## The WebSocket route
|
|
42
86
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
stay SSE, POST keeps working next to it. A `ws` client never calls
|
|
46
|
-
POST, an `http` client never upgrades; the transports are additive.
|
|
87
|
+
Pass the platform's upgrade capability on GET. POST can stay a direct
|
|
88
|
+
export for HTTP clients:
|
|
47
89
|
|
|
48
|
-
```ts app/api/order-events/route.ts
|
|
90
|
+
```ts app/api/socket-order-events/route.ts
|
|
49
91
|
import { experimental_upgradeWebSocket } from '@vercel/functions'
|
|
50
|
-
import { handle } from 'experimental-a2/http'
|
|
51
92
|
import { ordersServer } from '@/server/orders'
|
|
52
93
|
|
|
53
|
-
export const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}),
|
|
64
|
-
})
|
|
94
|
+
export const POST = ordersServer.fetch
|
|
95
|
+
|
|
96
|
+
export function GET(request: Request): Promise<Response> {
|
|
97
|
+
return ordersServer.fetch(request, {
|
|
98
|
+
upgradeWebSocket: (attach) =>
|
|
99
|
+
experimental_upgradeWebSocket(attach, {
|
|
100
|
+
maxPayload: 4 * 1024 * 1024,
|
|
101
|
+
}),
|
|
102
|
+
})
|
|
103
|
+
}
|
|
65
104
|
```
|
|
66
105
|
|
|
67
|
-
The socket is multiplexed
|
|
68
|
-
each resuming from its own frontier
|
|
69
|
-
`sessionId
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
`
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
fire-and-forget and
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
platform deadline, so clients reconnect on your schedule.
|
|
106
|
+
The socket is multiplexed. Subscribe frames open per-session lanes,
|
|
107
|
+
each resuming from its own frontier. Every down frame carries its
|
|
108
|
+
`sessionId`; pushes and presence route by it. One heartbeat and one
|
|
109
|
+
connection serve all of the client's sessions.
|
|
110
|
+
|
|
111
|
+
Authenticate the physical upgrade before calling `server.fetch`.
|
|
112
|
+
Then pass `authorize` to check every session subscribe and every push
|
|
113
|
+
or presence frame. There is no separate upgrade operation: request
|
|
114
|
+
policy belongs to the route, while A2 operation policy belongs to
|
|
115
|
+
`authorize`.
|
|
116
|
+
|
|
117
|
+
A denied subscribe receives an `unsubscribed` notice and leaves other
|
|
118
|
+
sessions connected. A denied event push receives a non-retryable
|
|
119
|
+
`FORBIDDEN` ack. Denied presence is silently dropped because presence
|
|
120
|
+
is fire-and-forget and repaints on the next update. Failed presence
|
|
121
|
+
authorization is dropped the same way and leaves the socket connected.
|
|
122
|
+
|
|
123
|
+
A2 reads the platform's ambient invocation deadline and closes the
|
|
124
|
+
socket cleanly before it. `waitUntil` and deadline discovery remain
|
|
125
|
+
platform capabilities used internally, not options on `server.fetch`.
|
|
88
126
|
|
|
89
127
|
## The client
|
|
90
128
|
|
|
@@ -104,37 +142,17 @@ export const { SessionProvider, useSession } = createReact({
|
|
|
104
142
|
```
|
|
105
143
|
|
|
106
144
|
Nothing else changes. `useSession` is identical, `push` returns the
|
|
107
|
-
same ack and `confirmed`, presence coalesces on the same cadence.
|
|
108
|
-
connection status,
|
|
109
|
-
|
|
110
|
-
are the same machinery above the wire seam.
|
|
145
|
+
same ack and `confirmed`, and presence coalesces on the same cadence.
|
|
146
|
+
The connection status, stall watchdog, and reconnection behavior are
|
|
147
|
+
shared above the wire seam.
|
|
111
148
|
|
|
112
149
|
## Lifecycle
|
|
113
150
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
replay (you get the original events back). Push retries wait for the
|
|
123
|
-
restored socket rather than a fixed clock, capped by the reconnect
|
|
124
|
-
backoff, so a push caught in a routine blip resolves the way it would
|
|
125
|
-
have over HTTP, where pushes never depended on the stream.
|
|
126
|
-
|
|
127
|
-
On Vercel, WebSocket connections pin to one function instance and new
|
|
128
|
-
connections may land anywhere. A2 never relies on instance affinity:
|
|
129
|
-
the log is the shared state, so two clients on two instances converge
|
|
130
|
-
through it, exactly as they do over HTTP.
|
|
131
|
-
|
|
132
|
-
## Choosing
|
|
133
|
-
|
|
134
|
-
SSE plus POST is the right default: simplest route, ordinary HTTP
|
|
135
|
-
semantics, and one auth surface. Reach for `ws` when the send rate
|
|
136
|
-
makes per-request overhead visible (live cursors, drag ghosts) or when
|
|
137
|
-
round-trip latency is the product. Reach for the `http` split only when
|
|
138
|
-
per-route duration configuration forces it. The protocol underneath is
|
|
139
|
-
the same in every case: the log replays, presence repaints, and the
|
|
140
|
-
wire is an implementation detail.
|
|
151
|
+
When a socket closes, the client reconnects with backoff, re-subscribes
|
|
152
|
+
every session at its own frontier, receives fresh presence snapshots,
|
|
153
|
+
and re-sends its own latest presence values. Event pushes keep their
|
|
154
|
+
client-generated ids, so retry remains idempotent.
|
|
155
|
+
|
|
156
|
+
SSE and WebSocket are transport choices, not different consistency
|
|
157
|
+
models. Both resume from the durable event frontier and treat presence
|
|
158
|
+
as ephemeral repaintable state.
|