experimental-a2 0.2.0 → 0.4.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 +169 -0
- package/dist/ai-B4YhEnfw.d.ts +333 -0
- package/dist/ai-server.d.ts +48 -9
- package/dist/ai-server.js +121 -49
- package/dist/ai.d.ts +2 -303
- package/dist/ai.js +231 -86
- package/dist/cli-B3VuxoDe.js +597 -0
- package/dist/cli-bin.d.ts +1 -0
- package/dist/cli-bin.js +5 -0
- package/dist/cli.d.ts +19 -0
- package/dist/cli.js +2 -0
- package/dist/client-BrfDXQ8A.d.ts +155 -0
- package/dist/client-Bt4tAKi9.js +798 -0
- package/dist/client.d.ts +2 -90
- package/dist/client.js +1 -409
- package/dist/{contract-CG_adnu_.js → contract-48bUMgcL.js} +10 -2
- package/dist/{contract-C_3dIIEU.d.ts → contract-jIfaR085.d.ts} +62 -8
- package/dist/devtools-J_jZ2vQf.d.ts +151 -0
- package/dist/devtools-kJJaORn-.js +338 -0
- package/dist/devtools-server.browser.js +1 -1
- package/dist/devtools-server.d.ts +2 -2
- package/dist/devtools-server.js +224 -43
- package/dist/devtools.d.ts +2 -0
- package/dist/devtools.js +2 -0
- package/dist/{errors-BJRMd-h6.js → errors-BQuJpe82.js} +4 -4
- package/dist/{errors-xL_JTXsY.d.ts → errors-W6nwJ-fm.d.ts} +1 -1
- package/dist/http.d.ts +71 -13
- package/dist/http.js +302 -41
- package/dist/{idempotent-replay-BMyHrP0L.js → idempotent-replay-DuqEkYA7.js} +2 -2
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -2
- package/dist/{inspection-E7qbD0Xj.js → inspection-DaxB5jM2.js} +2 -1
- package/dist/internal-aEotMzu_.js +209 -0
- package/dist/otel.d.ts +1 -1
- package/dist/platform-B4TnJtWu.js +32 -0
- package/dist/react.d.ts +37 -14
- package/dist/react.js +26 -15
- package/dist/scheduler-qstash.d.ts +78 -0
- package/dist/scheduler-qstash.js +499 -0
- package/dist/scheduler-task-BpzhPnRS.js +54 -0
- package/dist/{recovery-vercel.d.ts → scheduler-vercel.d.ts} +17 -24
- package/dist/scheduler-vercel.js +226 -0
- package/dist/server-CcNnFnoW.js +1405 -0
- package/dist/server-YtPq7hjw.d.ts +260 -0
- package/dist/server.d.ts +4 -155
- package/dist/server.js +2 -2
- package/dist/{log-ldf5g8Cx.d.ts → store-C3sNAaBT.d.ts} +111 -35
- package/dist/{log-yJbXUf72.js → store-N8PXxDAS.js} +1 -1
- package/dist/store-codec-DTG0Ftek.js +8 -0
- package/dist/store-memory.d.ts +11 -0
- package/dist/{log-memory.js → store-memory.js} +127 -24
- package/dist/{log-polling-6COoN60V.js → store-polling-DgrrAE3d.js} +7 -6
- package/dist/{log-postgres.d.ts → store-postgres.d.ts} +6 -6
- package/dist/{log-postgres.js → store-postgres.js} +158 -24
- package/dist/{log-redis.js → store-redis-core-DWqx3F47.js} +294 -156
- package/dist/store-redis-http.d.ts +21 -0
- package/dist/store-redis-http.js +70 -0
- package/dist/store-redis.d.ts +37 -0
- package/dist/store-redis.js +298 -0
- package/dist/{log-sqlite.d.ts → store-sqlite.d.ts} +6 -6
- package/dist/{log-sqlite.js → store-sqlite.js} +116 -22
- package/dist/{telemetry-Cso0qyHQ.d.ts → telemetry-BjYHTfh2.d.ts} +1 -1
- package/dist/testing.browser.d.ts +1 -0
- package/dist/testing.browser.js +4 -0
- package/dist/testing.d.ts +31 -0
- package/dist/testing.js +101 -0
- package/dist/wire-DCUZBUlT.js +222 -0
- package/docs/01-quickstart.mdx +4 -5
- package/docs/concepts/01-contracts.mdx +21 -17
- package/docs/concepts/02-handlers.mdx +7 -7
- package/docs/concepts/03-durability.mdx +26 -29
- package/docs/concepts/04-state.mdx +18 -21
- package/docs/guides/01-timers.mdx +154 -54
- package/docs/guides/02-cancellation.mdx +30 -4
- package/docs/guides/03-react.mdx +20 -21
- package/docs/guides/04-local-first.mdx +1 -1
- package/docs/guides/05-production.mdx +321 -60
- package/docs/guides/06-ai-agents.mdx +249 -49
- package/docs/guides/07-devtools.mdx +137 -12
- package/docs/guides/08-application-data.mdx +1 -1
- package/docs/guides/09-presence.mdx +284 -0
- package/docs/guides/10-transports.mdx +131 -0
- package/docs/index.mdx +22 -46
- package/docs/reference/01-api.mdx +751 -102
- package/docs/reference/02-errors.mdx +10 -5
- package/package.json +23 -6
- package/dist/internal-D6wNxTck.js +0 -36
- package/dist/log-memory.d.ts +0 -11
- package/dist/log-redis.d.ts +0 -31
- package/dist/recovery-vercel.js +0 -119
- package/dist/server-DJgD2YWP.js +0 -877
- package/dist/wire-BVsgR8o9.js +0 -62
|
@@ -36,11 +36,15 @@ export async function POST(req: Request) {
|
|
|
36
36
|
| ------------------------- | ------------------------------------------------------------------ | --------- |
|
|
37
37
|
| `INVALID_PAYLOAD` | a payload fails its schema; nothing was written, and `details` carries the issues ([Standard Schema](https://standardschema.dev) format) | no |
|
|
38
38
|
| `UNKNOWN_EVENT_TYPE` | an event type isn't in the contract's `events` map | no |
|
|
39
|
+
| `UNKNOWN_PRESENCE_FIELD` | a presence field isn't in the contract's `presence` map | no |
|
|
40
|
+
| `PRESENCE_NOT_SUPPORTED` | the contract declares `presence` but the configured store has no presence capability (thrown at construction; for environment-resolved stores, deferred to the first store use) | no |
|
|
39
41
|
| `PARTIAL_DUPLICATE_BATCH` | a batch mixed already-appended and fresh events | no |
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
42
|
+
| `SUPERSEDED_ATTEMPT` | a handler append from an attempt a recovery claim has replaced; nothing was written, the successor owns the event. Also the abort reason on that handler's `ctx.signal` | no |
|
|
43
|
+
| `CLAIM_EXPIRED` | never thrown; only the `ctx.signal` abort reason when the attempt's lease lapsed without renewal (assumed loss, decided locally, not proof) | no |
|
|
44
|
+
| `STORE_UNAVAILABLE` | the store backend failed; nothing was written, original error as `cause` | **yes** |
|
|
45
|
+
| `STORE_NOT_CONFIGURED` | production boot with no `store` configured (during `next build` page collection, deferred to first use) | no |
|
|
42
46
|
|
|
43
|
-
Retryability is derivable from the code. `
|
|
47
|
+
Retryability is derivable from the code. `STORE_UNAVAILABLE` is the only one
|
|
44
48
|
worth retrying; the rest are deterministic caller bugs, and retrying them
|
|
45
49
|
is wasted work.
|
|
46
50
|
|
|
@@ -69,9 +73,10 @@ The push route serializes an `A2Error` as:
|
|
|
69
73
|
```
|
|
70
74
|
|
|
71
75
|
with a mapped status: `INVALID_PAYLOAD`, `UNKNOWN_EVENT_TYPE`, and
|
|
72
|
-
`PARTIAL_DUPLICATE_BATCH` are 400; `
|
|
76
|
+
`PARTIAL_DUPLICATE_BATCH` are 400; `SUPERSEDED_ATTEMPT` is 409;
|
|
77
|
+
`STORE_UNAVAILABLE` is 503.
|
|
73
78
|
|
|
74
79
|
The client's `push` deserializes the body back into an `A2Error`, so client
|
|
75
80
|
and server code branch on identical codes. `push` auto-retries only
|
|
76
|
-
`
|
|
81
|
+
`STORE_UNAVAILABLE`. The serializer pair ships in `experimental-a2/http`, alongside
|
|
77
82
|
`parsePushBody` and `sseResponse`.
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "experimental-a2",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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",
|
|
7
|
+
"bin": {
|
|
8
|
+
"a2": "./dist/cli-bin.js"
|
|
9
|
+
},
|
|
7
10
|
"sideEffects": false,
|
|
8
11
|
"engines": {
|
|
9
12
|
"node": ">=22.13"
|
|
@@ -36,21 +39,29 @@
|
|
|
36
39
|
"default": "./dist/ai-server.js"
|
|
37
40
|
},
|
|
38
41
|
"./http": "./dist/http.js",
|
|
39
|
-
"./
|
|
40
|
-
"./
|
|
41
|
-
"./
|
|
42
|
-
"./
|
|
43
|
-
"./
|
|
42
|
+
"./store-memory": "./dist/store-memory.js",
|
|
43
|
+
"./store-sqlite": "./dist/store-sqlite.js",
|
|
44
|
+
"./store-postgres": "./dist/store-postgres.js",
|
|
45
|
+
"./store-redis": "./dist/store-redis.js",
|
|
46
|
+
"./store-redis-http": "./dist/store-redis-http.js",
|
|
47
|
+
"./scheduler-qstash": "./dist/scheduler-qstash.js",
|
|
48
|
+
"./scheduler-vercel": "./dist/scheduler-vercel.js",
|
|
44
49
|
"./cache-indexeddb": "./dist/cache-indexeddb.js",
|
|
45
50
|
"./otel": "./dist/otel.js",
|
|
51
|
+
"./devtools": "./dist/devtools.js",
|
|
46
52
|
"./devtools/server": {
|
|
47
53
|
"browser": "./dist/devtools-server.browser.js",
|
|
48
54
|
"default": "./dist/devtools-server.js"
|
|
49
55
|
},
|
|
56
|
+
"./testing": {
|
|
57
|
+
"browser": "./dist/testing.browser.js",
|
|
58
|
+
"default": "./dist/testing.js"
|
|
59
|
+
},
|
|
50
60
|
"./package.json": "./package.json"
|
|
51
61
|
},
|
|
52
62
|
"peerDependencies": {
|
|
53
63
|
"@opentelemetry/api": "^1.9.0",
|
|
64
|
+
"@upstash/qstash": "^2.11.0",
|
|
54
65
|
"@vercel/queue": "*",
|
|
55
66
|
"ai": "^7.0.0",
|
|
56
67
|
"ioredis": "^5.4.0",
|
|
@@ -61,6 +72,9 @@
|
|
|
61
72
|
"@opentelemetry/api": {
|
|
62
73
|
"optional": true
|
|
63
74
|
},
|
|
75
|
+
"@upstash/qstash": {
|
|
76
|
+
"optional": true
|
|
77
|
+
},
|
|
64
78
|
"@vercel/queue": {
|
|
65
79
|
"optional": true
|
|
66
80
|
},
|
|
@@ -79,12 +93,15 @@
|
|
|
79
93
|
},
|
|
80
94
|
"devDependencies": {
|
|
81
95
|
"@electric-sql/pglite": "^0.3.14",
|
|
96
|
+
"@upstash/qstash": "^2.11.3",
|
|
82
97
|
"ai": "^7.0.58",
|
|
83
98
|
"ioredis": "^5.9.0",
|
|
84
99
|
"@opentelemetry/api": "^1.9.0",
|
|
85
100
|
"@types/pg": "^8.15.0",
|
|
101
|
+
"@types/ws": "^8.18.1",
|
|
86
102
|
"@vercel/queue": "^0.2.0",
|
|
87
103
|
"pg": "^8.16.0",
|
|
104
|
+
"ws": "^8.21.3",
|
|
88
105
|
"@testing-library/react": "^16.1.0",
|
|
89
106
|
"@types/react": "^19.0.0",
|
|
90
107
|
"esbuild": "^0.25.0",
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
//#region src/internal.ts
|
|
2
|
-
const serverInternals = /* @__PURE__ */ new WeakMap();
|
|
3
|
-
/**
|
|
4
|
-
* Drain timing knobs. Mutable only as a white-box test seam. Claim
|
|
5
|
-
* heartbeats run on real timers, so tests shrink these to keep suites
|
|
6
|
-
* fast. Production always uses the defaults.
|
|
7
|
-
*/
|
|
8
|
-
const DRAIN_TIMINGS = {
|
|
9
|
-
claimTtlMs: 5e3,
|
|
10
|
-
claimHeartbeatMs: 2e3,
|
|
11
|
-
recoveryGraceMs: 1e3,
|
|
12
|
-
recoveryArmTimeoutMs: 2e3
|
|
13
|
-
};
|
|
14
|
-
/**
|
|
15
|
-
* Stream liveness knobs — the server-side SSE heartbeat and the
|
|
16
|
-
* client-side stall watchdog. Paired by design: the client declares a
|
|
17
|
-
* connection dead after roughly two missed heartbeats. Mutable only as
|
|
18
|
-
* a white-box test seam; production always uses the defaults.
|
|
19
|
-
*/
|
|
20
|
-
/**
|
|
21
|
-
* Poll cadence for the poll-based log streams (sqlite, postgres) —
|
|
22
|
-
* owned, not configurable: adaptive polling removed the operator's
|
|
23
|
-
* reason to tune it (the floor keeps active streams smooth; the
|
|
24
|
-
* ceiling bounds idle cost). Mutable only as a white-box test seam so
|
|
25
|
-
* suites don't wait out real idle gaps.
|
|
26
|
-
*/
|
|
27
|
-
const POLL_TIMINGS = {
|
|
28
|
-
activeFloorMs: 25,
|
|
29
|
-
idleCeilingMs: 250
|
|
30
|
-
};
|
|
31
|
-
const STREAM_TIMINGS = {
|
|
32
|
-
sseHeartbeatMs: 15e3,
|
|
33
|
-
stallTimeoutMs: 35e3
|
|
34
|
-
};
|
|
35
|
-
//#endregion
|
|
36
|
-
export { serverInternals as i, POLL_TIMINGS as n, STREAM_TIMINGS as r, DRAIN_TIMINGS as t };
|
package/dist/log-memory.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { c as IdSource, i as Clock, t as A2Log } from "./log-ldf5g8Cx.js";
|
|
2
|
-
//#region src/log-memory.d.ts
|
|
3
|
-
type MemoryLogOptions = {
|
|
4
|
-
/** Injectable clock — every stored timestamp comes from here. */
|
|
5
|
-
clock?: Clock;
|
|
6
|
-
/** Injectable id source for generated event ids. */
|
|
7
|
-
ids?: IdSource;
|
|
8
|
-
};
|
|
9
|
-
declare function memory(options?: MemoryLogOptions): A2Log;
|
|
10
|
-
//#endregion
|
|
11
|
-
export { MemoryLogOptions, memory };
|
package/dist/log-redis.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { c as IdSource, i as Clock, t as A2Log } from "./log-ldf5g8Cx.js";
|
|
2
|
-
//#region src/log-redis.d.ts
|
|
3
|
-
/**
|
|
4
|
-
* The minimal client this backend needs — `ioredis` matches it
|
|
5
|
-
* structurally. `call` issues any command; `duplicate` opens the
|
|
6
|
-
* dedicated connection each blocking live feed holds.
|
|
7
|
-
*/
|
|
8
|
-
type RedisConnection = {
|
|
9
|
-
call(command: string, ...args: Array<string | number>): Promise<unknown>;
|
|
10
|
-
duplicate(): RedisConnection;
|
|
11
|
-
disconnect(): void;
|
|
12
|
-
};
|
|
13
|
-
type RedisLogOptions = {
|
|
14
|
-
/** Creates an `ioredis` client lazily (optional peer dep `ioredis`). */
|
|
15
|
-
url?: string | undefined;
|
|
16
|
-
/** Bring your own client — anything `call`/`duplicate`/`disconnect`. */
|
|
17
|
-
client?: RedisConnection;
|
|
18
|
-
/** Key prefix — isolates multiple apps on one Redis. Default `'a2'`. */
|
|
19
|
-
keyPrefix?: string;
|
|
20
|
-
/** Injectable clock — every stored timestamp comes from here. */
|
|
21
|
-
clock?: Clock;
|
|
22
|
-
/** Injectable id source for generated event ids. */
|
|
23
|
-
ids?: IdSource;
|
|
24
|
-
};
|
|
25
|
-
type RedisLog = A2Log & {
|
|
26
|
-
/** Disconnect the client and every live stream connection. */
|
|
27
|
-
close(): Promise<void>;
|
|
28
|
-
};
|
|
29
|
-
declare function redis(options?: RedisLogOptions): RedisLog;
|
|
30
|
-
//#endregion
|
|
31
|
-
export { RedisConnection, RedisLog, RedisLogOptions, redis };
|
package/dist/recovery-vercel.js
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { i as serverInternals, t as DRAIN_TIMINGS } from "./internal-D6wNxTck.js";
|
|
2
|
-
import { t as retryableLazy } from "./retryable-lazy-DZWmHpii.js";
|
|
3
|
-
import { n as SYSTEM_CLOCK } from "./log-yJbXUf72.js";
|
|
4
|
-
//#region src/recovery-vercel.ts
|
|
5
|
-
/**
|
|
6
|
-
* experimental-a2/recovery-vercel — queue-backed recovery over Vercel Queues.
|
|
7
|
-
*
|
|
8
|
-
* `vercelQueues()` returns an `A2Recovery` (a2-implementation.md §9):
|
|
9
|
-
*
|
|
10
|
-
* - `arm` rounds the requested watchdog time up to a one-second slot.
|
|
11
|
-
* One message per `(contract, session, dueAt)` coalesces appends,
|
|
12
|
-
* claim renewals and racing callbacks. `DuplicateMessageError` is
|
|
13
|
-
* success because that due-time slot is already durable.
|
|
14
|
-
* - `handler(...servers)` is the delivery route: look up the server for
|
|
15
|
-
* the contract named in the message and drain it. Settled drains ack;
|
|
16
|
-
* contention arms a later watchdog before acking; handler failure keeps
|
|
17
|
-
* the current message and redelivers with backoff.
|
|
18
|
-
*
|
|
19
|
-
* `@vercel/queue` is an optional peer dependency, imported lazily. The
|
|
20
|
-
* `transport` option is the injection seam — tests run a real little
|
|
21
|
-
* in-memory queue through the same interface.
|
|
22
|
-
*/
|
|
23
|
-
/**
|
|
24
|
-
* The "not settled yet" signal for a stalled handler — an expected state,
|
|
25
|
-
* not a transport failure. The default transport classifies it in
|
|
26
|
-
* `@vercel/queue`'s `retry` option and reschedules the delivery quietly
|
|
27
|
-
* (`{ afterSeconds }` → visibility change → 200 response, no error log)
|
|
28
|
-
* instead of letting it surface as a thrown 500. Busy deliveries use a
|
|
29
|
-
* fresh watchdog instead. Custom transports should treat this class the
|
|
30
|
-
* same way when their protocol has a polite reschedule.
|
|
31
|
-
*/
|
|
32
|
-
var RecoveryUnsettled = class extends Error {};
|
|
33
|
-
/** Vercel Queues rejects shorter visibility changes. */
|
|
34
|
-
const MIN_VISIBILITY_SECONDS = 30;
|
|
35
|
-
/** Redelivery backoff for retry states: base, 2×, 4×… bounded. */
|
|
36
|
-
const unsettledBackoffSeconds = (delaySeconds, deliveryCount) => Math.min(Math.max(delaySeconds * 2 ** Math.max(deliveryCount - 1, 0), MIN_VISIBILITY_SECONDS), 60);
|
|
37
|
-
const vercelQueueTransport = async (delaySeconds) => {
|
|
38
|
-
const queue = await import("@vercel/queue").catch(() => {
|
|
39
|
-
throw new Error("experimental-a2/recovery-vercel needs the '@vercel/queue' package (optional peer dependency) — install it, or inject a transport");
|
|
40
|
-
});
|
|
41
|
-
return {
|
|
42
|
-
send: (topic, message, options) => queue.send(topic, message, options),
|
|
43
|
-
handleCallback: (handler) => queue.handleCallback(handler, {
|
|
44
|
-
visibilityTimeoutSeconds: MIN_VISIBILITY_SECONDS,
|
|
45
|
-
retry: (error, metadata) => error instanceof RecoveryUnsettled ? { afterSeconds: unsettledBackoffSeconds(delaySeconds, metadata.deliveryCount) } : void 0
|
|
46
|
-
}),
|
|
47
|
-
isDuplicate: (error) => error instanceof queue.DuplicateMessageError
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
function vercelQueues(options = {}) {
|
|
51
|
-
const topic = options.topic ?? "a2";
|
|
52
|
-
const delaySeconds = options.delaySeconds ?? 5;
|
|
53
|
-
const clock = options.clock ?? SYSTEM_CLOCK;
|
|
54
|
-
const transport = retryableLazy(() => options.transport ? Promise.resolve(options.transport) : vercelQueueTransport(delaySeconds)).get;
|
|
55
|
-
const sendWatchdog = async (opts) => {
|
|
56
|
-
const t = await transport();
|
|
57
|
-
const nowMs = clock.now().getTime();
|
|
58
|
-
const requestedDueAt = opts.dueAt ?? nowMs + delaySeconds * 1e3;
|
|
59
|
-
const dueAt = Math.ceil(requestedDueAt / 1e3) * 1e3;
|
|
60
|
-
const recoveryId = `${opts.contract}:${opts.sessionId}:watchdog:${dueAt}`;
|
|
61
|
-
try {
|
|
62
|
-
await t.send(topic, {
|
|
63
|
-
contract: opts.contract,
|
|
64
|
-
sessionId: opts.sessionId,
|
|
65
|
-
dueAt
|
|
66
|
-
}, {
|
|
67
|
-
delaySeconds: Math.max(0, Math.ceil((dueAt - nowMs) / 1e3)),
|
|
68
|
-
idempotencyKey: recoveryId
|
|
69
|
-
});
|
|
70
|
-
} catch (err) {
|
|
71
|
-
if (t.isDuplicate(err)) return;
|
|
72
|
-
throw err;
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
const successorDueAt = (message) => {
|
|
76
|
-
const minimumDueAt = clock.now().getTime() + DRAIN_TIMINGS.claimTtlMs + DRAIN_TIMINGS.recoveryGraceMs;
|
|
77
|
-
if (message.dueAt === void 0) return minimumDueAt;
|
|
78
|
-
const beats = Math.max(1, Math.ceil((minimumDueAt - message.dueAt) / DRAIN_TIMINGS.claimHeartbeatMs));
|
|
79
|
-
return message.dueAt + beats * DRAIN_TIMINGS.claimHeartbeatMs;
|
|
80
|
-
};
|
|
81
|
-
return {
|
|
82
|
-
async arm({ contract, sessionId, dueAt }) {
|
|
83
|
-
await sendWatchdog({
|
|
84
|
-
contract,
|
|
85
|
-
sessionId,
|
|
86
|
-
...dueAt !== void 0 ? { dueAt } : {}
|
|
87
|
-
});
|
|
88
|
-
},
|
|
89
|
-
handler(...servers) {
|
|
90
|
-
const byContract = /* @__PURE__ */ new Map();
|
|
91
|
-
for (const server of servers) byContract.set(server.contract.name, server);
|
|
92
|
-
return async (req) => {
|
|
93
|
-
return (await transport()).handleCallback(async (message) => {
|
|
94
|
-
const server = byContract.get(message.contract);
|
|
95
|
-
if (!server) throw new Error(`a2 recovery: no server for contract '${message.contract}' — pass it to recovery.handler(...)`);
|
|
96
|
-
const internals = serverInternals.get(server);
|
|
97
|
-
const nextDueAt = successorDueAt(message);
|
|
98
|
-
const result = internals ? await internals.recoveryDrain(message.sessionId, { recoveryDueAt: nextDueAt }) : {
|
|
99
|
-
...await server.drain(message.sessionId),
|
|
100
|
-
outcome: "unknown"
|
|
101
|
-
};
|
|
102
|
-
if (result.settled) return;
|
|
103
|
-
if (result.outcome === "busy") {
|
|
104
|
-
if (result.recoveryArm) await result.recoveryArm;
|
|
105
|
-
else await sendWatchdog({
|
|
106
|
-
contract: message.contract,
|
|
107
|
-
sessionId: message.sessionId,
|
|
108
|
-
dueAt: nextDueAt
|
|
109
|
-
});
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
throw new RecoveryUnsettled(`a2 recovery: session '${message.sessionId}' of '${message.contract}' not settled yet (${result.outcome}) — rescheduled for redelivery`);
|
|
113
|
-
})(req);
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
//#endregion
|
|
119
|
-
export { RecoveryUnsettled, vercelQueues };
|