experimental-a2 0.0.0 → 0.2.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 +43 -0
- package/dist/ai-server.browser.js +2 -2
- package/dist/ai-server.d.ts +19 -7
- package/dist/ai-server.js +730 -96
- package/dist/ai.d.ts +32 -11
- package/dist/ai.js +253 -75
- package/dist/client.d.ts +1 -1
- package/dist/client.js +4 -4
- package/dist/{contract-B0kAXoaL.js → contract-CG_adnu_.js} +2 -1
- package/dist/{contract-DL8btVd9.d.ts → contract-C_3dIIEU.d.ts} +4 -1
- package/dist/devtools-server.browser.js +2 -2
- package/dist/devtools-server.js +1 -1
- package/dist/http.d.ts +1 -1
- package/dist/http.js +4 -3
- package/dist/idempotent-replay-BMyHrP0L.js +19 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/{internal-Dm8Ejnud.js → internal-D6wNxTck.js} +3 -3
- package/dist/{log-Dg1I8NRr.d.ts → log-ldf5g8Cx.d.ts} +74 -56
- package/dist/log-memory.d.ts +1 -1
- package/dist/log-memory.js +173 -96
- package/dist/{log-polling-RO7kclzR.js → log-polling-6COoN60V.js} +1 -1
- package/dist/log-postgres.d.ts +1 -1
- package/dist/log-postgres.js +235 -192
- package/dist/log-redis.d.ts +1 -1
- package/dist/log-redis.js +453 -263
- package/dist/log-sqlite.d.ts +1 -1
- package/dist/log-sqlite.js +216 -127
- package/dist/otel.d.ts +1 -1
- package/dist/otel.js +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/recovery-vercel.d.ts +2 -2
- package/dist/recovery-vercel.js +9 -10
- package/dist/server-DJgD2YWP.js +877 -0
- package/dist/server.browser.js +4 -4
- package/dist/server.d.ts +46 -27
- package/dist/server.js +1 -1
- package/dist/{telemetry-C78al20p.d.ts → telemetry-Cso0qyHQ.d.ts} +1 -1
- package/dist/{wire-2QpU1EtJ.js → wire-BVsgR8o9.js} +1 -1
- package/docs/01-quickstart.mdx +7 -7
- package/docs/concepts/01-contracts.mdx +22 -22
- package/docs/concepts/02-handlers.mdx +223 -89
- package/docs/concepts/03-durability.mdx +199 -112
- package/docs/concepts/04-state.mdx +27 -1
- package/docs/guides/01-timers.mdx +4 -4
- package/docs/guides/02-cancellation.mdx +32 -4
- package/docs/guides/05-production.mdx +61 -27
- package/docs/guides/06-ai-agents.mdx +151 -70
- package/docs/guides/07-devtools.mdx +6 -3
- package/docs/guides/08-application-data.mdx +5 -6
- package/docs/index.mdx +30 -14
- package/docs/reference/01-api.mdx +305 -70
- package/package.json +31 -31
- package/dist/server-DYsnKTTy.js +0 -780
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
//#region src/devtools-server.browser.ts
|
|
2
|
-
/** a2/devtools/server owns log access and is server-only. */
|
|
3
|
-
throw new Error("a2/devtools/server is server-only — mount its handler in a server route and open that URL in the browser.");
|
|
2
|
+
/** experimental-a2/devtools/server owns log access and is server-only. */
|
|
3
|
+
throw new Error("experimental-a2/devtools/server is server-only — mount its handler in a server route and open that URL in the browser.");
|
|
4
4
|
//#endregion
|
package/dist/devtools-server.js
CHANGED
|
@@ -819,7 +819,7 @@ if (bootstrap) {
|
|
|
819
819
|
//#endregion
|
|
820
820
|
//#region src/devtools-server.ts
|
|
821
821
|
/**
|
|
822
|
-
* a2/devtools/server — a read-only dashboard over A2's durable data.
|
|
822
|
+
* experimental-a2/devtools/server — a read-only dashboard over A2's durable data.
|
|
823
823
|
* One Request handler serves the application, its private browser bundle,
|
|
824
824
|
* JSON inspection endpoints, and live SSE invalidations.
|
|
825
825
|
*/
|
package/dist/http.d.ts
CHANGED
package/dist/http.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { t as A2Error } from "./errors-BJRMd-h6.js";
|
|
2
|
-
import { r as STREAM_TIMINGS } from "./internal-
|
|
3
|
-
import { a as eventToWire, n as errorStatus, r as errorToWire, t as errorFromWire } from "./wire-
|
|
2
|
+
import { r as STREAM_TIMINGS } from "./internal-D6wNxTck.js";
|
|
3
|
+
import { a as eventToWire, n as errorStatus, r as errorToWire, t as errorFromWire } from "./wire-BVsgR8o9.js";
|
|
4
4
|
//#region src/http.ts
|
|
5
5
|
/**
|
|
6
|
-
* a2/http — route-side transport helpers (specs/a2-api.md §9–10).
|
|
6
|
+
* experimental-a2/http — route-side transport helpers (specs/a2-api.md §9–10).
|
|
7
7
|
*
|
|
8
8
|
* `sseResponse` pipes a `session.stream()` iterable into a server-sent
|
|
9
9
|
* events Response; `parsePushBody` validates the push envelope;
|
|
@@ -39,6 +39,7 @@ async function parsePushBody(req) {
|
|
|
39
39
|
payload
|
|
40
40
|
};
|
|
41
41
|
if (id !== void 0) out.id = id;
|
|
42
|
+
Object.defineProperty(out, "~a2.pushed", { value: true });
|
|
42
43
|
return out;
|
|
43
44
|
})
|
|
44
45
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { t as A2Error } from "./errors-BJRMd-h6.js";
|
|
2
|
+
//#region src/idempotent-replay.ts
|
|
3
|
+
const canonicalJson = (value) => JSON.stringify(value, (_key, candidate) => {
|
|
4
|
+
if (candidate === null || typeof candidate !== "object" || Array.isArray(candidate)) return candidate;
|
|
5
|
+
return Object.fromEntries(Object.entries(candidate).toSorted(([left], [right]) => left.localeCompare(right)));
|
|
6
|
+
}) ?? "null";
|
|
7
|
+
const idempotentReplay = (events, existing) => {
|
|
8
|
+
const byId = new Map(existing.map((event) => [event.id, event]));
|
|
9
|
+
const ordered = events.map((event) => event.id === void 0 ? void 0 : byId.get(event.id));
|
|
10
|
+
const firstIndex = ordered[0]?.index;
|
|
11
|
+
const batchSize = events[0]?.cause?.batchSize;
|
|
12
|
+
if (firstIndex === void 0 || batchSize !== void 0 && batchSize !== events.length || ordered.some((stored, index) => {
|
|
13
|
+
const event = events[index];
|
|
14
|
+
return stored === void 0 || stored.index !== firstIndex + index || stored.type !== event.type || stored.lane !== (event.lane ?? null) || stored.cause?.batchSize !== event.cause?.batchSize || canonicalJson(stored.payload) !== canonicalJson(event.payload);
|
|
15
|
+
})) throw new A2Error("PARTIAL_DUPLICATE_BATCH", "already-appended events do not match the retried batch");
|
|
16
|
+
return ordered;
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
export { idempotentReplay as t };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { a as Event } from "./log-
|
|
3
|
-
import { i as A2Telemetry, n as A2SpanHandle, r as A2SpanName, t as A2AttributeValue } from "./telemetry-
|
|
1
|
+
import { a as EventDefs, c as Reducer, i as EventBatchFactory, l as ReducerBuilder, n as Contract, o as ReducerOptions, r as ContractEvent, s as contract, t as AppendInput, u as StandardSchemaV1 } from "./contract-C_3dIIEU.js";
|
|
2
|
+
import { a as Event } from "./log-ldf5g8Cx.js";
|
|
3
|
+
import { i as A2Telemetry, n as A2SpanHandle, r as A2SpanName, t as A2AttributeValue } from "./telemetry-Cso0qyHQ.js";
|
|
4
4
|
import { n as A2ErrorCode, t as A2Error } from "./errors-xL_JTXsY.js";
|
|
5
|
-
export { type A2AttributeValue, A2Error, type A2ErrorCode, type A2SpanHandle, type A2SpanName, type A2Telemetry, type AppendInput, type Contract, type ContractEvent, type Event, type EventDefs, type Reducer, type ReducerBuilder, type ReducerOptions, type StandardSchemaV1, contract };
|
|
5
|
+
export { type A2AttributeValue, A2Error, type A2ErrorCode, type A2SpanHandle, type A2SpanName, type A2Telemetry, type AppendInput, type Contract, type ContractEvent, type Event, type EventBatchFactory, type EventDefs, type Reducer, type ReducerBuilder, type ReducerOptions, type StandardSchemaV1, contract };
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
//#region src/internal.ts
|
|
2
2
|
const serverInternals = /* @__PURE__ */ new WeakMap();
|
|
3
3
|
/**
|
|
4
|
-
* Drain timing knobs. Mutable only as a white-box test seam
|
|
4
|
+
* Drain timing knobs. Mutable only as a white-box test seam. Claim
|
|
5
5
|
* heartbeats run on real timers, so tests shrink these to keep suites
|
|
6
6
|
* fast. Production always uses the defaults.
|
|
7
7
|
*/
|
|
8
8
|
const DRAIN_TIMINGS = {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
claimTtlMs: 5e3,
|
|
10
|
+
claimHeartbeatMs: 2e3,
|
|
11
11
|
recoveryGraceMs: 1e3,
|
|
12
12
|
recoveryArmTimeoutMs: 2e3
|
|
13
13
|
};
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* The A2Log interface — the storage contract every log backend
|
|
4
4
|
* implements. See specs/a2-implementation.md §2–3.
|
|
5
5
|
*
|
|
6
|
-
* This is the whole storage contract: append, read,
|
|
7
|
-
*
|
|
6
|
+
* This is the whole storage contract: append, read, dispatch claims,
|
|
7
|
+
* failure markers, snapshots, and the live stream. Recovery
|
|
8
8
|
* needs nothing extra — the armed queue message is its own state, and
|
|
9
9
|
* the log is the only thing it consults.
|
|
10
10
|
*/
|
|
@@ -22,25 +22,35 @@ type Event = {
|
|
|
22
22
|
type EventCause = {
|
|
23
23
|
index: number;
|
|
24
24
|
attempt: number;
|
|
25
|
+
/** Size of one named handler append, used to reject truncated retries. */
|
|
26
|
+
batchSize?: number;
|
|
25
27
|
};
|
|
26
28
|
/**
|
|
27
29
|
* What the log stores: immutable event history, including its causal edge,
|
|
28
|
-
* plus derived
|
|
30
|
+
* plus derived dispatch and failure bookkeeping. The bookkeeping is disposable;
|
|
29
31
|
* the event and its cause are not.
|
|
30
32
|
*/
|
|
31
33
|
type StoredEvent = Event & {
|
|
32
|
-
/** Same-session handler dispatch that appended this event; null means root
|
|
34
|
+
/** Same-session handler dispatch that appended this event; null means root. */
|
|
33
35
|
cause: EventCause | null;
|
|
34
|
-
/**
|
|
36
|
+
/** Session-scoped serial execution key resolved when the event is appended. */
|
|
37
|
+
lane: string | null;
|
|
38
|
+
/** Adapter clock time recorded by append settlement, completion, or manual skip; null while pending. */
|
|
35
39
|
processedAt: Date | null;
|
|
36
|
-
/** Dispatch attempt that completed this event; null
|
|
40
|
+
/** Dispatch attempt that completed this event; null without dispatch or while pending. */
|
|
37
41
|
processedByAttempt: number | null;
|
|
42
|
+
/** Exact ordered child ids atomically returned by the completing attempt. */
|
|
43
|
+
returnedEventIds: string[] | null;
|
|
38
44
|
/** Adapter clock time recorded by the first durable dispatch claim. */
|
|
39
45
|
firstClaimedAt: Date | null;
|
|
40
46
|
/** Adapter clock time recorded by the most recent durable dispatch claim. */
|
|
41
47
|
lastClaimedAt: Date | null;
|
|
42
48
|
/** Durable dispatch claims, including claims abandoned by hard kills. */
|
|
43
49
|
attemptCount: number;
|
|
50
|
+
/** Current dispatch holder; null when the event is not claimed. */
|
|
51
|
+
claimHolder: string | null;
|
|
52
|
+
/** Adapter clock expiry for the current dispatch claim. */
|
|
53
|
+
claimExpiresAt: Date | null;
|
|
44
54
|
/** Caught handler failures. This alone drives dead-lettering. */
|
|
45
55
|
failureCount: number;
|
|
46
56
|
/** Adapter clock time recorded by the most recent caught handler failure. */
|
|
@@ -89,7 +99,7 @@ type StoredSessionPage = {
|
|
|
89
99
|
/**
|
|
90
100
|
* Optional read-only administration implemented by A2's shipped logs.
|
|
91
101
|
* It is separate from the correctness contract so custom logs do not
|
|
92
|
-
* need to expose storage-wide discovery merely to
|
|
102
|
+
* need to expose storage-wide discovery merely to process events.
|
|
93
103
|
*/
|
|
94
104
|
type A2LogInspection = {
|
|
95
105
|
listSessions(options: {
|
|
@@ -100,17 +110,21 @@ type A2LogInspection = {
|
|
|
100
110
|
}): Promise<StoredSessionPage>;
|
|
101
111
|
listSnapshots(sessionId: string): Promise<StoredSnapshot[]>;
|
|
102
112
|
};
|
|
103
|
-
/** The result of atomically claiming
|
|
104
|
-
type
|
|
113
|
+
/** The result of atomically claiming every currently eligible event. */
|
|
114
|
+
type LogClaimAvailableResult = {
|
|
105
115
|
outcome: "claimed";
|
|
106
|
-
|
|
116
|
+
events: StoredEvent[];
|
|
107
117
|
} | {
|
|
108
118
|
outcome: "busy";
|
|
119
|
+
retryAt: Date;
|
|
109
120
|
} | {
|
|
110
121
|
outcome: "settled";
|
|
111
122
|
};
|
|
112
123
|
/** A completion may lose to a newer claim or an earlier completion. */
|
|
113
|
-
type
|
|
124
|
+
type CompleteAttemptResult = {
|
|
125
|
+
outcome: "completed";
|
|
126
|
+
events: StoredEvent[];
|
|
127
|
+
} | {
|
|
114
128
|
outcome: "superseded";
|
|
115
129
|
};
|
|
116
130
|
/** The result of atomically recording a caught handler failure. */
|
|
@@ -124,11 +138,25 @@ type AppendEvent = {
|
|
|
124
138
|
payload: unknown;
|
|
125
139
|
/** Caller-supplied idempotency key; generated when absent. */
|
|
126
140
|
id?: string;
|
|
127
|
-
/** Internal causal edge supplied atomically by `
|
|
141
|
+
/** Internal causal edge supplied atomically by handler `session.append`. */
|
|
128
142
|
cause?: EventCause;
|
|
143
|
+
/** Session-scoped serial execution key resolved before persistence. */
|
|
144
|
+
lane?: string;
|
|
145
|
+
/** Internal hint: settle this event in the append transaction; no handler is registered. */
|
|
146
|
+
settled?: true;
|
|
147
|
+
};
|
|
148
|
+
/** A handler-returned event with the deterministic id recovery requires. */
|
|
149
|
+
type ReturnedEvent = AppendEvent & {
|
|
150
|
+
id: string;
|
|
151
|
+
};
|
|
152
|
+
/** The rows written by an append and the session's pending state. */
|
|
153
|
+
type LogAppendResult = {
|
|
154
|
+
events: StoredEvent[];
|
|
155
|
+
/** Whether the session contains an event without a completion marker. */
|
|
156
|
+
hasPending: boolean;
|
|
129
157
|
};
|
|
130
158
|
/**
|
|
131
|
-
* An injectable clock. Adapters take one so tests can drive
|
|
159
|
+
* An injectable clock. Adapters take one so tests can drive claim
|
|
132
160
|
* expiry, failure timestamps, and (later) stuck-session detection
|
|
133
161
|
* deterministically — against real storage, no mocking.
|
|
134
162
|
*/
|
|
@@ -146,43 +174,53 @@ interface A2Log {
|
|
|
146
174
|
* lost — return the existing rows as success. If only *some* ids
|
|
147
175
|
* exist, the caller mixed an already-sent batch with fresh events —
|
|
148
176
|
* always a caller bug — so throw `A2Error('PARTIAL_DUPLICATE_BATCH')`.
|
|
177
|
+
* Events carrying `settled: true` get `processedAt` in this same atomic
|
|
178
|
+
* operation, with no dispatch claim or `processedByAttempt`. The result's
|
|
179
|
+
* `hasPending` reflects the whole session in the same atomic operation,
|
|
180
|
+
* including older events and idempotent retries.
|
|
149
181
|
*/
|
|
150
|
-
append(sessionId: string, events: AppendEvent[]): Promise<
|
|
182
|
+
append(sessionId: string, events: AppendEvent[]): Promise<LogAppendResult>;
|
|
151
183
|
/** Events for one session, oldest first. */
|
|
152
184
|
read(sessionId: string, opts?: {
|
|
153
185
|
afterIndex?: number;
|
|
154
|
-
unprocessedOnly?: boolean;
|
|
155
186
|
}): Promise<StoredEvent[]>;
|
|
156
187
|
/**
|
|
157
|
-
* Atomically
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
188
|
+
* Atomically claims every eligible pending event. Unlaned events are all
|
|
189
|
+
* independently eligible. Within a lane, only the lowest-index unfinished
|
|
190
|
+
* event is eligible. A live claim produces `busy` only when it is the sole
|
|
191
|
+
* remaining obstacle to actionable work. Claimed events are returned in log
|
|
192
|
+
* order. Excluded rows remain lane barriers.
|
|
161
193
|
*/
|
|
162
|
-
|
|
194
|
+
claimAvailable(options: {
|
|
163
195
|
sessionId: string;
|
|
164
196
|
holder: string;
|
|
165
197
|
ttlMs: number;
|
|
166
198
|
expiresAtMs?: number;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}): Promise<LogClaimResult>;
|
|
199
|
+
excludeIndexes?: readonly number[];
|
|
200
|
+
}): Promise<LogClaimAvailableResult>;
|
|
170
201
|
/**
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
* the same adapter clock value.
|
|
202
|
+
* Renews the listed live claims still owned by `holder`. Missing, expired,
|
|
203
|
+
* completed, failed, or superseded claims are omitted from the result. The
|
|
204
|
+
* renewed indexes are returned in log order.
|
|
175
205
|
*/
|
|
176
|
-
|
|
206
|
+
renewClaims(options: {
|
|
177
207
|
sessionId: string;
|
|
178
208
|
holder: string;
|
|
179
|
-
|
|
209
|
+
indexes: number[];
|
|
210
|
+
ttlMs: number;
|
|
211
|
+
expiresAtMs?: number;
|
|
212
|
+
}): Promise<number[]>;
|
|
213
|
+
/**
|
|
214
|
+
* Atomically completes one current attempt and appends its returned events.
|
|
215
|
+
* Retrying a committed completion with the same attempt and deterministic
|
|
216
|
+
* child ids returns the existing children. A stale attempt never appends.
|
|
217
|
+
*/
|
|
218
|
+
completeAttempt(options: {
|
|
219
|
+
sessionId: string;
|
|
220
|
+
index: number;
|
|
180
221
|
attempt: number;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}): Promise<LogHandoffResult>;
|
|
184
|
-
/** The drain-completion marker and drain idempotency check. */
|
|
185
|
-
markProcessed(sessionId: string, index: number): Promise<void>;
|
|
222
|
+
events: ReturnedEvent[];
|
|
223
|
+
}): Promise<CompleteAttemptResult>;
|
|
186
224
|
/**
|
|
187
225
|
* Atomically records a caught failure for one claimed attempt. A stale
|
|
188
226
|
* attempt cannot poison a processed event or a newer dispatch. Accepted
|
|
@@ -195,8 +233,6 @@ interface A2Log {
|
|
|
195
233
|
error: string;
|
|
196
234
|
maxFailures: number;
|
|
197
235
|
}): Promise<FailAttemptResult>;
|
|
198
|
-
/** Dead-letters the event. */
|
|
199
|
-
markFailed(sessionId: string, index: number): Promise<void>;
|
|
200
236
|
/**
|
|
201
237
|
* Reads a reducer snapshot and its event tail as one consistent adapter
|
|
202
238
|
* operation. On a cache miss, `snapshot` is null and `events` is the full
|
|
@@ -210,26 +246,8 @@ interface A2Log {
|
|
|
210
246
|
* (`where up_to_index < excluded.up_to_index`).
|
|
211
247
|
*/
|
|
212
248
|
putSnapshot(sessionId: string, reducerName: string, index: number, state: unknown): Promise<void>;
|
|
213
|
-
/** Read-only operational data used by `a2/devtools/server`. */
|
|
249
|
+
/** Read-only operational data used by `experimental-a2/devtools/server`. */
|
|
214
250
|
inspect?: A2LogInspection;
|
|
215
|
-
/**
|
|
216
|
-
* One lease per session — serializes processing order for a session,
|
|
217
|
-
* not individual events. TTL-bounded, never held indefinitely.
|
|
218
|
-
* Re-acquiring with the same holder renews the TTL.
|
|
219
|
-
*/
|
|
220
|
-
lease: {
|
|
221
|
-
acquire(options: {
|
|
222
|
-
sessionId: string;
|
|
223
|
-
holder: string;
|
|
224
|
-
ttlMs: number;
|
|
225
|
-
/** Optional absolute expiry used for platform-deadline lease windows. */
|
|
226
|
-
expiresAtMs?: number;
|
|
227
|
-
}): Promise<boolean>;
|
|
228
|
-
release(options: {
|
|
229
|
-
sessionId: string;
|
|
230
|
-
holder: string;
|
|
231
|
-
}): Promise<void>;
|
|
232
|
-
};
|
|
233
251
|
/**
|
|
234
252
|
* A live feed of one session's events, starting after `startAt`
|
|
235
253
|
* (exclusive). Transport is the backend's choice — in-process pub/sub,
|
|
@@ -242,4 +260,4 @@ interface A2Log {
|
|
|
242
260
|
}): AsyncIterable<Event>;
|
|
243
261
|
}
|
|
244
262
|
//#endregion
|
|
245
|
-
export { Event as a, IdSource as c, LogStateRead as d, StoredEvent as f, StoredSnapshot as h, Clock as i,
|
|
263
|
+
export { Event as a, IdSource as c, LogStateRead as d, StoredEvent as f, StoredSnapshot as h, Clock as i, LogAppendResult as l, StoredSessionSummary as m, A2LogInspection as n, EventCause as o, StoredSessionPage as p, AppendEvent as r, FailAttemptResult as s, A2Log as t, LogClaimAvailableResult as u };
|
package/dist/log-memory.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as IdSource, i as Clock, t as A2Log } from "./log-
|
|
1
|
+
import { c as IdSource, i as Clock, t as A2Log } from "./log-ldf5g8Cx.js";
|
|
2
2
|
//#region src/log-memory.d.ts
|
|
3
3
|
type MemoryLogOptions = {
|
|
4
4
|
/** Injectable clock — every stored timestamp comes from here. */
|