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
|
@@ -5,11 +5,6 @@ description: "Schedule a typed event back to the same instance. Deadlines surviv
|
|
|
5
5
|
|
|
6
6
|
## A deadline is a message, delivered later
|
|
7
7
|
|
|
8
|
-
`ctx.schedule` mirrors `ctx.send`, one method per declared event, with
|
|
9
|
-
a timing argument: exactly one of `{ delay: '1h' }` or `{ at: date }`.
|
|
10
|
-
It hands the event to the configured scheduler, and delivery lands on
|
|
11
|
-
the instance's lane like any other message.
|
|
12
|
-
|
|
13
8
|
```ts escrow.ts
|
|
14
9
|
import { actor } from 'experimental-a2/actor'
|
|
15
10
|
import { vercelQueues } from 'experimental-a2/scheduler-vercel'
|
|
@@ -46,38 +41,29 @@ export const escrow = actor<Escrow>({
|
|
|
46
41
|
})
|
|
47
42
|
```
|
|
48
43
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
deploys in between
|
|
44
|
+
`ctx.schedule` mirrors `ctx.send`, one method per declared event, with
|
|
45
|
+
a timing argument: exactly one of `{ delay: '1h' }` or `{ at: date }`.
|
|
46
|
+
It requires the `scheduler` option. The deadline outlives the
|
|
47
|
+
invocation that armed it and any deploys in between, and delivery lands
|
|
48
|
+
on the instance's lane like any other message.
|
|
53
49
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
[Timers and delays](/guides/timers).
|
|
50
|
+
Adapters, delay limits, and delivery semantics are the
|
|
51
|
+
[session timers](/server/timers) underneath.
|
|
57
52
|
|
|
58
53
|
## Stale timers are no-ops
|
|
59
54
|
|
|
60
|
-
When `accept` wins
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
There is no cancellation API, and the log records that the timer fired.
|
|
66
|
-
Guarded delivery is the pattern: let the event land, read the state,
|
|
67
|
-
return when it no longer applies. The guard that makes finalizers
|
|
68
|
-
idempotent is the same guard that makes deadlines safe.
|
|
55
|
+
When `accept` wins, the release timer still fires an hour later, meets
|
|
56
|
+
the `held[offerId] === undefined` guard, and does nothing. When the
|
|
57
|
+
timer wins, a late `accept` meets the same guard. There is no
|
|
58
|
+
cancellation API, and the log records that the timer fired: let the
|
|
59
|
+
event land and return when it no longer applies.
|
|
69
60
|
|
|
70
61
|
## Identity and retries
|
|
71
62
|
|
|
72
63
|
A timer's name defaults to the target event's name, scoped to the
|
|
73
|
-
triggering message
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
append. Relative delays anchor to the triggering message's durable
|
|
77
|
-
`createdAt`, so re-runs resolve the same due time.
|
|
78
|
-
|
|
79
|
-
One handler run gets one timer per target event. To arm several toward
|
|
80
|
-
the same event, name them apart:
|
|
64
|
+
triggering message, so a re-run of a crashed handler addresses the same
|
|
65
|
+
timer instead of arming a second. One handler run gets one timer per
|
|
66
|
+
target event; to arm several toward the same event, name them apart:
|
|
81
67
|
|
|
82
68
|
```ts
|
|
83
69
|
// inside a handler:
|
|
@@ -87,24 +73,22 @@ await ctx.schedule.release(
|
|
|
87
73
|
)
|
|
88
74
|
```
|
|
89
75
|
|
|
76
|
+
Duplicate deliveries fold into one append, and relative delays anchor
|
|
77
|
+
to the triggering message's `createdAt`; see
|
|
78
|
+
[Identity and retries](/server/timers#identity-and-retries).
|
|
79
|
+
|
|
90
80
|
## Scheduling is immediate
|
|
91
81
|
|
|
92
82
|
:::warning
|
|
93
|
-
Unlike `ctx.send`, `ctx.schedule` is not buffered
|
|
94
|
-
provider, not in the log, so
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
but order handlers accordingly: guards and refusals first, `ctx.schedule`
|
|
99
|
-
last.
|
|
83
|
+
Unlike `ctx.send`, `ctx.schedule` is not buffered: a timer lives at
|
|
84
|
+
the provider, not in the log, so the call awaits provider acceptance
|
|
85
|
+
right where it happens. A handler that schedules and then refuses has
|
|
86
|
+
still armed the timer. Guarded delivery makes that safe, but order
|
|
87
|
+
handlers accordingly: guards and refusals first, `ctx.schedule` last.
|
|
100
88
|
:::
|
|
101
89
|
|
|
102
90
|
## From outside a handler
|
|
103
91
|
|
|
104
|
-
The typed surface lives on handler contexts. To schedule from ordinary
|
|
105
|
-
server code, use the instance's underlying session, which takes an
|
|
106
|
-
explicit name, timing, and event:
|
|
107
|
-
|
|
108
92
|
```ts
|
|
109
93
|
// anywhere on the server:
|
|
110
94
|
import { escrow } from '@/escrow'
|
|
@@ -116,5 +100,7 @@ await escrow.actor('offer-42').session.schedule(
|
|
|
116
100
|
)
|
|
117
101
|
```
|
|
118
102
|
|
|
119
|
-
|
|
120
|
-
|
|
103
|
+
Outside handler contexts, schedule through the instance's session with
|
|
104
|
+
an explicit name, timing, and event. Root scheduling semantics apply
|
|
105
|
+
(`{ at }` for retry-stable targets); see
|
|
106
|
+
[Schedule from ordinary server code](/server/timers#schedule-from-ordinary-server-code).
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Routes, clients, and React
|
|
3
|
-
description: "One route serves an instance: GET streams live state, POST answers calls. Authorize per operation, project per mount, and
|
|
3
|
+
description: "One route serves an instance: GET streams live state, POST answers calls. Authorize per operation, project per mount, and call it from any client or one React hook."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
## The actor for this page
|
|
7
7
|
|
|
8
|
-
A vault with one internal field, so the projection section below has
|
|
9
|
-
something to hide:
|
|
10
|
-
|
|
11
8
|
```ts vault.ts
|
|
12
9
|
import { NonRetriableError } from 'experimental-a2'
|
|
13
10
|
import { actor } from 'experimental-a2/actor'
|
|
@@ -47,9 +44,6 @@ export const vault = actor<Vault>({
|
|
|
47
44
|
|
|
48
45
|
## The route
|
|
49
46
|
|
|
50
|
-
`handle.fetch` serves one instance over HTTP. Your route authenticates
|
|
51
|
-
and picks the instance; the library serves the rest.
|
|
52
|
-
|
|
53
47
|
```ts app/vault/[vaultId]/route.ts
|
|
54
48
|
import { vault } from '@/vault'
|
|
55
49
|
|
|
@@ -66,47 +60,44 @@ export const GET = respond
|
|
|
66
60
|
export const POST = respond
|
|
67
61
|
```
|
|
68
62
|
|
|
69
|
-
`
|
|
70
|
-
|
|
71
|
-
with heartbeats. The stream carries state and nothing else: subscribers
|
|
72
|
-
see what the actor is, never other callers' inputs, and a refusal
|
|
73
|
-
answers only its caller. The full mailbox stays server-side.
|
|
63
|
+
`handle.fetch` serves one instance; your route authenticates and picks
|
|
64
|
+
which:
|
|
74
65
|
|
|
75
|
-
|
|
66
|
+
- **`GET`** streams state commits as server-sent events, resumed after
|
|
67
|
+
the `index` query parameter, with heartbeats. It carries state and
|
|
68
|
+
nothing else: subscribers never see other callers' inputs, and a
|
|
69
|
+
refusal answers only its caller.
|
|
70
|
+
- **`GET ?snapshot=1`** returns the current `{ state, index }`.
|
|
71
|
+
- **`POST`** makes a call and responds with its answer.
|
|
72
|
+
|
|
73
|
+
A `POST` body names a declared event:
|
|
76
74
|
|
|
77
75
|
```json
|
|
78
76
|
{ "event": "deposit", "input": { "amount": 100 }, "messageId": "m-1" }
|
|
79
77
|
```
|
|
80
78
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
treat its external effects as unknown, as after a timeout. The actor client
|
|
100
|
-
raises it as an `ActorRequestError` with status `410` and the A2 error as
|
|
101
|
-
its `cause`. Refusals keep `409`. A later message that reuses the id can't
|
|
79
|
+
It answers `{ state, index }`, `409` with the refusal's message, or
|
|
80
|
+
`400` for an undeclared event. The optional `messageId` makes retrying
|
|
81
|
+
the request idempotent. Snapshot and call responses use
|
|
82
|
+
`Cache-Control: private, no-store`.
|
|
83
|
+
|
|
84
|
+
### After a clear
|
|
85
|
+
|
|
86
|
+
After `handle.session.clear()`, snapshots, call answers, and stream
|
|
87
|
+
commits include `clearIndex`; keep it in any snapshot you pass to
|
|
88
|
+
`useActor`'s `initial`. The stream then sends the actor's initial state
|
|
89
|
+
(through `view`) as a reset, and the hook drops older history. A
|
|
90
|
+
delayed reset never replaces a newer snapshot.
|
|
91
|
+
|
|
92
|
+
A call cleared before its answer gets `410` with
|
|
93
|
+
`{ code: 'SESSION_CLEARED', error }`, which the actor client raises as
|
|
94
|
+
an `ActorRequestError` with status `410` and the A2 error as its
|
|
95
|
+
`cause`. The handler may already have run, so treat its effects as
|
|
96
|
+
unknown, as after a timeout. A later message reusing the id can't
|
|
102
97
|
answer the cleared call.
|
|
103
98
|
|
|
104
99
|
## Authorize
|
|
105
100
|
|
|
106
|
-
`authorize` is per-operation policy, checked before any read, write, or
|
|
107
|
-
subscription. Authentication (who is asking) stays in your route;
|
|
108
|
-
`authorize` decides whether this operation is allowed.
|
|
109
|
-
|
|
110
101
|
```ts
|
|
111
102
|
// app/vault/[vaultId]/route.ts, now with per-operation policy:
|
|
112
103
|
import { vault } from '@/vault'
|
|
@@ -123,19 +114,17 @@ export async function POST(
|
|
|
123
114
|
}
|
|
124
115
|
```
|
|
125
116
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
`
|
|
129
|
-
messageId? }` for calls. Returning `false` answers `403`.
|
|
117
|
+
Authentication stays in your route; `authorize` decides whether each
|
|
118
|
+
operation is allowed, before any read, write, or subscription.
|
|
119
|
+
Returning `false` answers `403`. The operation is one of:
|
|
130
120
|
|
|
131
|
-
|
|
121
|
+
- `{ type: 'state', id }` for snapshot reads
|
|
122
|
+
- `{ type: 'stream', id, startAfter }` for subscriptions
|
|
123
|
+
- `{ type: 'call', id, event, input, messageId? }` for calls
|
|
124
|
+
- `{ type: 'presence', id, participant, values }` for
|
|
125
|
+
[presence](#presence) announcements
|
|
132
126
|
|
|
133
|
-
|
|
134
|
-
fields. The definition stays audience-blind; audiences are a route
|
|
135
|
-
concern, like auth. A `view` is a request-time projection applied to
|
|
136
|
-
everything that mount serves: snapshots, every state frame on the stream,
|
|
137
|
-
and every call answer (they must agree, or the call lane would leak
|
|
138
|
-
what the stream hides).
|
|
127
|
+
## Views: what each mount shows
|
|
139
128
|
|
|
140
129
|
```ts
|
|
141
130
|
// app/vault/[vaultId]/public/route.ts, a projected mount of the same actor:
|
|
@@ -152,21 +141,23 @@ export async function GET(
|
|
|
152
141
|
}
|
|
153
142
|
```
|
|
154
143
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
144
|
+
A `view` projects everything its mount serves: snapshots, stream
|
|
145
|
+
frames, and call answers alike, so calls never leak what the stream
|
|
146
|
+
hides. Audiences are a route concern, like auth: an admin mount with no
|
|
147
|
+
view sees everything, and the mount above hides `audit`. For a
|
|
148
|
+
per-viewer view, close over your route's identity:
|
|
149
|
+
`view: (state) => ({ ...visible, mine: state.perUser[user.id] })`. View
|
|
150
|
+
output must be plain JSON, like state.
|
|
161
151
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
152
|
+
Clients of a projected mount pass its shape as the second type argument
|
|
153
|
+
(`createActorClient<typeof vault, PublicVault>` or
|
|
154
|
+
`useActor<typeof vault, PublicVault>`).
|
|
165
155
|
|
|
166
|
-
|
|
156
|
+
Views hide internal fields, not secrets. State commits are log rows,
|
|
157
|
+
and the log is forever: keep secrets outside, pass references, and
|
|
158
|
+
resolve them inside handlers.
|
|
167
159
|
|
|
168
|
-
|
|
169
|
-
lane. It works anywhere: browsers, other frameworks, other servers.
|
|
160
|
+
## Any client
|
|
170
161
|
|
|
171
162
|
```ts
|
|
172
163
|
// in the browser, or any client at all:
|
|
@@ -188,26 +179,20 @@ try {
|
|
|
188
179
|
}
|
|
189
180
|
```
|
|
190
181
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
callback receives successful state reads and call answers before their
|
|
198
|
-
promises resolve. It receives each answer, including older results from
|
|
199
|
-
idempotent retries; consumers compare indexes before replacing live state.
|
|
182
|
+
`createActorClient` is the typed call surface over the route's `POST`,
|
|
183
|
+
for browsers, other frameworks, and other servers. The `vault` import
|
|
184
|
+
is type-only, so the server module never enters a client bundle. `client.state()` reads a fresh snapshot. The optional
|
|
185
|
+
`onSnapshot` callback receives successful reads and call answers before
|
|
186
|
+
their promises resolve, including older results from idempotent
|
|
187
|
+
retries, so compare indexes before replacing live state.
|
|
200
188
|
|
|
201
|
-
`ActorRequestError` carries an HTTP `status` when available.
|
|
202
|
-
|
|
203
|
-
committed
|
|
204
|
-
original answer.
|
|
189
|
+
`ActorRequestError` carries an HTTP `status` when available. A network
|
|
190
|
+
failure, timeout, or server error doesn't establish whether a call
|
|
191
|
+
committed: retry with the same explicit `id` and input to recover its
|
|
192
|
+
original answer. The client never retries calls automatically.
|
|
205
193
|
|
|
206
194
|
## React
|
|
207
195
|
|
|
208
|
-
One hook. The server component hands down the first fold, the hook
|
|
209
|
-
keeps it live and exposes the same typed calls.
|
|
210
|
-
|
|
211
196
|
```tsx app/vault/[vaultId]/page.tsx
|
|
212
197
|
import { vault } from '@/vault'
|
|
213
198
|
import { VaultClient } from './vault-client'
|
|
@@ -256,56 +241,34 @@ export function VaultClient({
|
|
|
256
241
|
}
|
|
257
242
|
```
|
|
258
243
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
- **`call`**: the same typed
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
- **`
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
resolve. Snapshots only advance state: an older call answer or delayed
|
|
285
|
-
stream frame never replaces a newer state. React renders that update on
|
|
286
|
-
its normal schedule. A call still returns its own answer, even if a newer
|
|
287
|
-
state has already arrived. Refusals do not change state or trigger an
|
|
288
|
-
automatic refresh.
|
|
289
|
-
|
|
290
|
-
The stream tracks its own position for deduplication and reconnects.
|
|
291
|
-
Adopting a newer snapshot does not skip stream events: delayed commits
|
|
292
|
-
enter `events` without replacing newer state. Events from before the
|
|
293
|
-
initial snapshot are not loaded into this feed.
|
|
294
|
-
|
|
295
|
-
On a view-projected mount, pass the projected shape as the second type
|
|
296
|
-
argument (`useActor<typeof vault, PublicVault>`); `initial`, `state`,
|
|
297
|
-
and the call answers all carry it.
|
|
244
|
+
The server component hands down the first snapshot; `useActor` keeps it
|
|
245
|
+
live and returns:
|
|
246
|
+
|
|
247
|
+
- **`state`**: the live view (no reducers, schemas, or contracts in
|
|
248
|
+
browser code).
|
|
249
|
+
- **`call`**: the same typed calls as `createActorClient`, which the
|
|
250
|
+
hook wraps.
|
|
251
|
+
- **`index`**: the latest adopted state index.
|
|
252
|
+
- **`events`**: the state commits the stream observed after the initial
|
|
253
|
+
snapshot. A delayed commit still enters once, even when a call answer
|
|
254
|
+
or refresh already advanced `state`.
|
|
255
|
+
- **`refresh()`**: reads and adopts a fresh snapshot before resolving.
|
|
256
|
+
Use it after a refusal or when a suspended tab returns.
|
|
257
|
+
- **`connection`**: the same union as
|
|
258
|
+
[`useSession`](/client/react#events-and-history), heartbeat watchdog
|
|
259
|
+
included.
|
|
260
|
+
|
|
261
|
+
State is live, not optimistic: handlers decide on the server against
|
|
262
|
+
fresh state. For pending UI, await the call.
|
|
263
|
+
|
|
264
|
+
A successful call updates `state` before its promise resolves (React
|
|
265
|
+
renders on its normal schedule) and returns its own answer, even if
|
|
266
|
+
newer state already arrived. State only advances: an older answer or
|
|
267
|
+
delayed stream frame never replaces newer state. Refusals don't change
|
|
268
|
+
state or trigger a refresh.
|
|
298
269
|
|
|
299
270
|
## Presence
|
|
300
271
|
|
|
301
|
-
Who is here, live: cursors, names, viewer counts. Presence is
|
|
302
|
-
ephemeral audience state. It replicates to the instance's subscribers
|
|
303
|
-
and expires; it never enters the log, and handlers cannot see it (an
|
|
304
|
-
actor decides from its log, never from who is watching).
|
|
305
|
-
|
|
306
|
-
Declare the vocabulary in the protocol as types, and arm the wire with
|
|
307
|
-
`presence: true`:
|
|
308
|
-
|
|
309
272
|
```ts board.ts
|
|
310
273
|
import { actor } from 'experimental-a2/actor'
|
|
311
274
|
|
|
@@ -327,10 +290,12 @@ export const board = actor<Board>({
|
|
|
327
290
|
})
|
|
328
291
|
```
|
|
329
292
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
293
|
+
Declare [presence](/client/presence) fields as types in the protocol
|
|
294
|
+
and arm the wire with `presence: true`. Presence replicates to the
|
|
295
|
+
instance's subscribers and expires; it never enters the log, and
|
|
296
|
+
handlers can't see it. The route doesn't change: `GET` interleaves
|
|
297
|
+
presence with state commits, and `POST` accepts announcements. In
|
|
298
|
+
React, pass a `participant` identity and read the map:
|
|
334
299
|
|
|
335
300
|
```tsx app/board/[boardId]/board-client.tsx
|
|
336
301
|
'use client'
|
|
@@ -366,32 +331,27 @@ export function BoardClient({
|
|
|
366
331
|
}
|
|
367
332
|
```
|
|
368
333
|
|
|
369
|
-
`setPresence` is fire-and-forget: throttled
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
painted is render logic, decided against `at`.
|
|
334
|
+
`setPresence` is fire-and-forget: throttled, resent after a reconnect,
|
|
335
|
+
and `null` clears a field. The map holds each participant's latest
|
|
336
|
+
fields with their stamps; decide how long to paint a value from `at`.
|
|
373
337
|
|
|
374
|
-
Three guards
|
|
375
|
-
schema:
|
|
338
|
+
Three guards, none of them a schema:
|
|
376
339
|
|
|
377
340
|
- **Types**: the protocol types `setPresence` and the map, so honest
|
|
378
|
-
clients
|
|
379
|
-
- **The floor**:
|
|
380
|
-
|
|
381
|
-
-
|
|
382
|
-
`
|
|
383
|
-
|
|
384
|
-
user, or rate-limit here.
|
|
341
|
+
clients can't send the wrong shape.
|
|
342
|
+
- **The floor**: A2 rejects values that aren't plain JSON trees, values
|
|
343
|
+
over 8 KB, and object-plumbing field names.
|
|
344
|
+
- **[`authorize`](#authorize)**: every announcement arrives as a
|
|
345
|
+
`presence` operation before it is accepted. Clamp, verify the
|
|
346
|
+
participant matches the authenticated user, or rate-limit here.
|
|
385
347
|
|
|
386
|
-
|
|
387
|
-
|
|
348
|
+
No guard makes peer values trustworthy: presence is authored by whoever
|
|
349
|
+
holds a connection, so render it like user input.
|
|
388
350
|
|
|
389
351
|
## The session wire underneath
|
|
390
352
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
to its subscribers, inputs included, so reserve it for trusted
|
|
397
|
-
audiences.
|
|
353
|
+
For the raw session protocol (the full event feed, pushes, presence,
|
|
354
|
+
the `ws` transport), mount `def.server.fetch` as a
|
|
355
|
+
[session route](/server/routes) and use the core
|
|
356
|
+
[client](/client/react). That wire shows the whole mailbox, inputs
|
|
357
|
+
included, so reserve it for trusted audiences.
|
package/docs/actors/meta.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default { order:
|
|
1
|
+
export default { order: 6 }
|