experimental-a2 0.2.0 → 0.3.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 +13 -0
- package/dist/ai-server.js +1 -1
- package/dist/client.js +1 -1
- package/dist/http.js +1 -1
- package/dist/{internal-D6wNxTck.js → internal-gCd5qMry.js} +9 -1
- package/dist/{log-polling-6COoN60V.js → log-polling-DZ1MiKLg.js} +3 -2
- package/dist/log-postgres.js +1 -1
- package/dist/log-redis-core-CyJ5L8yR.js +836 -0
- package/dist/log-redis-http.d.ts +21 -0
- package/dist/log-redis-http.js +62 -0
- package/dist/log-redis.d.ts +10 -4
- package/dist/log-redis.js +165 -828
- package/dist/log-sqlite.js +1 -1
- package/dist/recovery-vercel.js +1 -1
- package/dist/{server-DJgD2YWP.js → server-BcLa4RFL.js} +1 -1
- package/dist/server.js +1 -1
- package/docs/01-quickstart.mdx +1 -2
- package/docs/concepts/01-contracts.mdx +3 -4
- package/docs/concepts/03-durability.mdx +6 -9
- package/docs/guides/01-timers.mdx +4 -9
- package/docs/guides/05-production.mdx +14 -2
- package/docs/index.mdx +7 -35
- package/docs/reference/01-api.mdx +1 -0
- package/package.json +2 -1
package/dist/log-sqlite.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as A2Error } from "./errors-BJRMd-h6.js";
|
|
2
2
|
import { t as idempotentReplay } from "./idempotent-replay-BMyHrP0L.js";
|
|
3
3
|
import { n as SYSTEM_CLOCK, t as RANDOM_IDS } from "./log-yJbXUf72.js";
|
|
4
|
-
import {
|
|
4
|
+
import { n as pollingStream } from "./log-polling-DZ1MiKLg.js";
|
|
5
5
|
import { mkdirSync } from "node:fs";
|
|
6
6
|
import { dirname, resolve } from "node:path";
|
|
7
7
|
import { DatabaseSync } from "node:sqlite";
|
package/dist/recovery-vercel.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as serverInternals, t as DRAIN_TIMINGS } from "./internal-gCd5qMry.js";
|
|
2
2
|
import { t as retryableLazy } from "./retryable-lazy-DZWmHpii.js";
|
|
3
3
|
import { n as SYSTEM_CLOCK } from "./log-yJbXUf72.js";
|
|
4
4
|
//#region src/recovery-vercel.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as validateSync } from "./validate-XKT4FSNn.js";
|
|
2
2
|
import { n as asLogUnavailable, t as A2Error } from "./errors-BJRMd-h6.js";
|
|
3
|
-
import {
|
|
3
|
+
import { a as serverInternals, t as DRAIN_TIMINGS } from "./internal-gCd5qMry.js";
|
|
4
4
|
import { n as serverInspection } from "./inspection-E7qbD0Xj.js";
|
|
5
5
|
import { t as retryableLazy } from "./retryable-lazy-DZWmHpii.js";
|
|
6
6
|
//#region src/deterministic-id.ts
|
package/dist/server.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as createServer } from "./server-
|
|
1
|
+
import { t as createServer } from "./server-BcLa4RFL.js";
|
|
2
2
|
export { createServer };
|
package/docs/01-quickstart.mdx
CHANGED
|
@@ -20,8 +20,7 @@ npm i experimental-a2 zod
|
|
|
20
20
|
A contract is a name plus the events it understands: each key an event
|
|
21
21
|
name, each value a schema. Zod here, though any
|
|
22
22
|
[Standard Schema](https://standardschema.dev) validator works, and
|
|
23
|
-
that's the
|
|
24
|
-
table.
|
|
23
|
+
that's the contract's complete declaration.
|
|
25
24
|
|
|
26
25
|
```ts server/orders.ts
|
|
27
26
|
import { z } from 'zod'
|
|
@@ -41,10 +41,9 @@ schemas and their type helpers. The server implements it,
|
|
|
41
41
|
[reducers](/concepts/state) derive from it, and the browser types its pushes
|
|
42
42
|
off it. One artifact, shared by every side of the wire.
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
things look like right now
|
|
47
|
-
contract just names the vocabulary.
|
|
44
|
+
The contract names the complete vocabulary. What happens on each event is
|
|
45
|
+
defined where the contract is served. [Reducers](/concepts/state) define what
|
|
46
|
+
things look like right now.
|
|
48
47
|
|
|
49
48
|
:::tip
|
|
50
49
|
Events are facts, so name them in past tense: `created`, `expired`,
|
|
@@ -43,10 +43,9 @@ processed marker says what finished. The watchdog says when to look again.
|
|
|
43
43
|
+-------------+ +-------------------+
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
A queue message
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
dispatch handlers.
|
|
46
|
+
A queue message identifies a session. Every wakeup runs the same drain, and
|
|
47
|
+
the log decides what remains. Recovery and explicit `server.drain()` inspect
|
|
48
|
+
the full session. Reads never dispatch handlers.
|
|
50
49
|
|
|
51
50
|
:::note[Recovery is not an event]
|
|
52
51
|
A2 does not append `recovered` or `continued`. Recovery retries the same event.
|
|
@@ -239,11 +238,9 @@ Four eight-second handlers returned one after another do not fit together in a
|
|
|
239
238
|
| t29 | Handler 3 finishes; handler 4 starts | Events 1 through 3 processed |
|
|
240
239
|
| t37 | Handler 4 finishes | Session settled |
|
|
241
240
|
|
|
242
|
-
The retry
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
cannot checkpoint arbitrary async code. Split the work into smaller events or
|
|
246
|
-
increase the function duration.
|
|
241
|
+
The retry starts handler 3 again from its entry point. Keep each handler within
|
|
242
|
+
a fresh invocation. Split longer work into smaller events or increase the
|
|
243
|
+
function duration.
|
|
247
244
|
|
|
248
245
|
## Crash around a returned batch
|
|
249
246
|
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Timers and delays
|
|
3
|
-
description:
|
|
3
|
+
description: A delayed action is an event delivered later by anything that can make an HTTP call.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
##
|
|
6
|
+
## Schedule delayed events
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
happen in five days, then in five days, something should append an event.
|
|
11
|
-
That's scheduling.
|
|
12
|
-
|
|
13
|
-
The scheduled thing is data (a session id and an event), not a suspended
|
|
14
|
-
function. No closure has to survive the gap.
|
|
8
|
+
If something should happen in five days, schedule an HTTP call that appends
|
|
9
|
+
the event then. The scheduler stores the session id and event until delivery.
|
|
15
10
|
|
|
16
11
|
## Schedule an event
|
|
17
12
|
|
|
@@ -40,6 +40,19 @@ box; transaction-mode poolers included, which is exactly why polling
|
|
|
40
40
|
is the default. `pg` is an optional peer dependency; pass
|
|
41
41
|
`connectionString`, or inject your own pool as `client`.
|
|
42
42
|
|
|
43
|
+
Prefer Redis? `redis({ url })` from `experimental-a2/log-redis` stores each
|
|
44
|
+
session as a Redis Stream and streams push-natively: writes to watched
|
|
45
|
+
sessions publish a disposable wake-up (sessions nobody watches cost no extra
|
|
46
|
+
command), one shared subscriber connection per process serves every connected
|
|
47
|
+
viewer, and a safety re-read covers a lost wake-up within ten seconds.
|
|
48
|
+
Connections scale with your processes, not with your audience.
|
|
49
|
+
`ioredis` is an optional peer dependency; pass `url`, or inject a client.
|
|
50
|
+
Works on single instances and non-cluster providers such as Upstash, where
|
|
51
|
+
durability is on by default. When only a REST API is available,
|
|
52
|
+
`redisHttp({ url, token })` from `experimental-a2/log-redis-http` speaks the
|
|
53
|
+
same storage over `fetch`, holds no connections at all, and polls on the same
|
|
54
|
+
adaptive cadence as Postgres.
|
|
55
|
+
|
|
43
56
|
This configures storage for A2's session logs. It does not connect A2 to your
|
|
44
57
|
application tables or make them part of the append transaction. See
|
|
45
58
|
[A2 and your database](/guides/application-data) for that boundary.
|
|
@@ -115,8 +128,7 @@ so they do not create an independent stream of watchdog callbacks.
|
|
|
115
128
|
Events appended or returned by handlers ride their current execution window
|
|
116
129
|
and add no recovery operation of their own.
|
|
117
130
|
|
|
118
|
-
|
|
119
|
-
the recovery state, and the log is the only thing it consults.
|
|
131
|
+
The queue message carries the recovery state, and recovery consults the log.
|
|
120
132
|
|
|
121
133
|
### Dispatch in a fresh invocation
|
|
122
134
|
|
package/docs/index.mdx
CHANGED
|
@@ -14,8 +14,8 @@ Handlers react to them, and usually return the next one. The browser follows
|
|
|
14
14
|
the same log and folds the same state as the server. That's the whole model;
|
|
15
15
|
the rest of this page is it happening.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
Events record facts, reducers compute the current view, and handlers perform
|
|
18
|
+
the reactions.
|
|
19
19
|
|
|
20
20
|
## Define a contract
|
|
21
21
|
|
|
@@ -39,8 +39,7 @@ export const orders = a2.contract({
|
|
|
39
39
|
|
|
40
40
|
## React to events
|
|
41
41
|
|
|
42
|
-
Handlers are plain async functions
|
|
43
|
-
wrappers around side effects. Each one reacts to a fact and usually
|
|
42
|
+
Handlers are plain async functions. Each one reacts to a fact and usually
|
|
44
43
|
returns the next one.
|
|
45
44
|
|
|
46
45
|
```ts server/orders.ts
|
|
@@ -178,32 +177,6 @@ twenty lines; [Live UI](/guides/react) wires it end to end.
|
|
|
178
177
|
|
|
179
178
|
## FAQ
|
|
180
179
|
|
|
181
|
-
<details>
|
|
182
|
-
<summary>Why not a workflow engine?</summary>
|
|
183
|
-
|
|
184
|
-
Workflow engines replay your code from the top on every wake-up. So the
|
|
185
|
-
code has to be deterministic, so every side effect gets wrapped in a step
|
|
186
|
-
function, and `sleep()` becomes something magical instead of something
|
|
187
|
-
you'd never call in a serverless function.
|
|
188
|
-
|
|
189
|
-
A2's answer is older and simpler: write everything down. Every meaningful
|
|
190
|
-
thing that happens is an event in a log. Handlers are stateless functions
|
|
191
|
-
that react to one event at a time. State isn't stored. It's computed, by
|
|
192
|
-
folding over the log whenever you need it. There's no orchestrator to
|
|
193
|
-
operate.
|
|
194
|
-
|
|
195
|
-
</details>
|
|
196
|
-
|
|
197
|
-
<details>
|
|
198
|
-
<summary>Isn't this just event sourcing?</summary>
|
|
199
|
-
|
|
200
|
-
It's the useful core of it. A log of facts, state as a fold: the idea is
|
|
201
|
-
decades old, and it's a good one. A2 cuts the ceremony that made it a big
|
|
202
|
-
commitment. No command bus, no projection cluster, no upcasting
|
|
203
|
-
framework. A contract, a log, handlers, reducers.
|
|
204
|
-
|
|
205
|
-
</details>
|
|
206
|
-
|
|
207
180
|
<details>
|
|
208
181
|
<summary>Can I use A2 without handlers?</summary>
|
|
209
182
|
|
|
@@ -217,12 +190,11 @@ See [Events without handlers](/concepts/handlers#events-without-handlers).
|
|
|
217
190
|
</details>
|
|
218
191
|
|
|
219
192
|
<details>
|
|
220
|
-
<summary>How do I
|
|
193
|
+
<summary>How do I schedule an event for later?</summary>
|
|
221
194
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
event, not a suspended function. See [Timers](/guides/timers).
|
|
195
|
+
Anything that can deliver an HTTP call later (QStash, a cron, a payment
|
|
196
|
+
provider's webhook) can hit a route that appends. Schedule the session id and
|
|
197
|
+
event as the request payload. See [Timers](/guides/timers).
|
|
226
198
|
|
|
227
199
|
</details>
|
|
228
200
|
|
|
@@ -860,6 +860,7 @@ dashboard returns 501 for those logs.
|
|
|
860
860
|
| `experimental-a2/http` | route-side transport helpers | none |
|
|
861
861
|
| `experimental-a2/log-postgres` | `postgres`: Postgres log backend | `pg` (or inject a client) |
|
|
862
862
|
| `experimental-a2/log-redis` | `redis`: Redis Streams log backend, push-native streaming | `ioredis` (or inject a client) |
|
|
863
|
+
| `experimental-a2/log-redis-http` | `redisHttp`: the same Redis log over provider REST APIs (Upstash) | none |
|
|
863
864
|
| `experimental-a2/log-sqlite` | SQLite log backend | none |
|
|
864
865
|
| `experimental-a2/log-memory` | in-memory log backend | none |
|
|
865
866
|
| `experimental-a2/recovery-vercel` | `vercelQueues` recovery | `@vercel/queue` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "experimental-a2",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Durable sync and reactions for things with a lifecycle: one event log, derived state, and live client per session.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"./log-sqlite": "./dist/log-sqlite.js",
|
|
41
41
|
"./log-postgres": "./dist/log-postgres.js",
|
|
42
42
|
"./log-redis": "./dist/log-redis.js",
|
|
43
|
+
"./log-redis-http": "./dist/log-redis-http.js",
|
|
43
44
|
"./recovery-vercel": "./dist/recovery-vercel.js",
|
|
44
45
|
"./cache-indexeddb": "./dist/cache-indexeddb.js",
|
|
45
46
|
"./otel": "./dist/otel.js",
|