@nylorun/runtime 0.13.0-beta → 0.14.0-beta
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 +330 -0
- package/README.md +69 -15
- package/dist/adapters/execution/restate.d.ts +7 -2
- package/dist/adapters/execution/restate.js +38 -8
- package/dist/adapters/replication/pgoutput.d.ts +16 -0
- package/dist/adapters/replication/pgoutput.js +169 -0
- package/dist/adapters/streams/s2.js +37 -20
- package/dist/admin-openapi.json +1071 -0
- package/dist/api/a2a/card.d.ts +15 -0
- package/dist/api/a2a/card.js +24 -0
- package/dist/api/a2a/endpoint.d.ts +8 -0
- package/dist/api/a2a/endpoint.js +81 -0
- package/dist/api/a2a/protocol.d.ts +144 -0
- package/dist/api/a2a/protocol.js +188 -0
- package/dist/api/a2a/routes.d.ts +19 -0
- package/dist/api/a2a/routes.js +289 -0
- package/dist/api/a2a/tasks.d.ts +50 -0
- package/dist/api/a2a/tasks.js +186 -0
- package/dist/api/ag-ui/endpoint.d.ts +8 -0
- package/dist/api/ag-ui/endpoint.js +126 -0
- package/dist/api/ag-ui/history.d.ts +7 -0
- package/dist/api/ag-ui/history.js +60 -0
- package/dist/api/ag-ui/routes.d.ts +32 -0
- package/dist/api/ag-ui/routes.js +305 -0
- package/dist/api/ag-ui/session-id.d.ts +1 -0
- package/dist/api/ag-ui/session-id.js +12 -0
- package/dist/api/ag-ui/sse.d.ts +9 -0
- package/dist/api/ag-ui/sse.js +7 -0
- package/dist/api/ag-ui/translate.d.ts +38 -0
- package/dist/api/ag-ui/translate.js +222 -0
- package/dist/api/components.d.ts +7825 -0
- package/dist/api/components.js +83 -0
- package/dist/api/http/app.d.ts +18 -0
- package/dist/api/http/app.js +58 -0
- package/dist/api/http/body.d.ts +4 -0
- package/dist/api/http/body.js +37 -0
- package/dist/api/http/define.d.ts +39 -0
- package/dist/api/http/define.js +130 -0
- package/dist/api/http/respond.d.ts +9 -0
- package/dist/api/http/respond.js +44 -0
- package/dist/api/http/routes/access.d.ts +24 -0
- package/dist/api/http/routes/access.js +259 -0
- package/dist/api/http/routes/actions.d.ts +8 -0
- package/dist/api/http/routes/actions.js +76 -0
- package/dist/api/http/routes/endpoints.d.ts +7 -0
- package/dist/api/http/routes/endpoints.js +100 -0
- package/dist/api/http/routes/sessions.d.ts +3 -0
- package/dist/api/http/routes/sessions.js +205 -0
- package/dist/api/http/routes/tenant.d.ts +10 -0
- package/dist/api/http/routes/tenant.js +172 -0
- package/dist/api/http/routes/vaults.d.ts +8 -0
- package/dist/api/http/routes/vaults.js +167 -0
- package/dist/api/openapi.d.ts +19 -0
- package/dist/api/openapi.js +138 -0
- package/dist/api/route.d.ts +12 -0
- package/dist/api/route.js +4 -0
- package/dist/core/bearer.d.ts +8 -0
- package/dist/core/bearer.js +11 -0
- package/dist/core/flow-host.d.ts +7 -15
- package/dist/core/flow-host.js +16 -41
- package/dist/core/sandbox-routes.d.ts +12 -7
- package/dist/core/sandbox-routes.js +24 -25
- package/dist/execution/memory.d.ts +8 -5
- package/dist/execution/memory.js +33 -13
- package/dist/execution/types.d.ts +38 -6
- package/dist/execution/types.js +12 -3
- package/dist/host/admin-api.d.ts +22 -0
- package/dist/host/admin-api.js +178 -0
- package/dist/host/app.d.ts +34 -0
- package/dist/host/app.js +185 -0
- package/dist/host/config-for.d.ts +2 -0
- package/dist/host/config-for.js +1 -0
- package/dist/host/config.d.ts +11 -0
- package/dist/host/cors.d.ts +27 -0
- package/dist/host/cors.js +64 -0
- package/dist/host/create-host.d.ts +31 -3
- package/dist/host/create-host.js +168 -327
- package/dist/host/http.d.ts +14 -3
- package/dist/host/http.js +64 -23
- package/dist/host/main.js +55 -4
- package/dist/host/stack-config.d.ts +20 -0
- package/dist/host/stack-config.js +55 -0
- package/dist/mcp/pool.d.ts +19 -1
- package/dist/mcp/pool.js +60 -10
- package/dist/model/catalog.d.ts +2 -10
- package/dist/model/classify.d.ts +13 -0
- package/dist/model/classify.js +61 -0
- package/dist/model/models.d.ts +10 -1
- package/dist/model/models.js +14 -6
- package/dist/model/pi-model.d.ts +17 -2
- package/dist/model/pi-model.js +366 -222
- package/dist/openapi.json +25696 -0
- package/dist/sandbox/manager.d.ts +13 -1
- package/dist/sandbox/manager.js +8 -11
- package/dist/sandbox/resolve.d.ts +33 -0
- package/dist/sandbox/resolve.js +81 -0
- package/dist/sandbox/session-sandbox.d.ts +11 -0
- package/dist/sandbox/session-sandbox.js +51 -0
- package/dist/sandbox/share.d.ts +14 -2
- package/dist/sandbox/share.js +33 -3
- package/dist/sandbox/tenant-config.d.ts +37 -0
- package/dist/sandbox/tenant-config.js +51 -0
- package/dist/sandbox/tools.d.ts +7 -9
- package/dist/sandbox/tools.js +11 -3
- package/dist/sandbox/types.d.ts +8 -2
- package/dist/store/event.d.ts +19 -0
- package/dist/store/event.js +49 -0
- package/dist/store/memory.d.ts +20 -8
- package/dist/store/memory.js +265 -100
- package/dist/store/postgres/connect.d.ts +13 -0
- package/dist/store/postgres/connect.js +5 -0
- package/dist/store/postgres/migrations/003_subject_tokens.d.ts +8 -0
- package/dist/store/postgres/migrations/003_subject_tokens.js +42 -0
- package/dist/store/postgres/migrations/004_publishable_keys.d.ts +6 -0
- package/dist/store/postgres/migrations/004_publishable_keys.js +18 -0
- package/dist/store/postgres/migrations/005_action_endpoints.d.ts +6 -0
- package/dist/store/postgres/migrations/005_action_endpoints.js +32 -0
- package/dist/store/postgres/migrations/006_drop_executors.d.ts +11 -0
- package/dist/store/postgres/migrations/006_drop_executors.js +32 -0
- package/dist/store/postgres/migrations/007_event_time.d.ts +7 -0
- package/dist/store/postgres/migrations/007_event_time.js +14 -0
- package/dist/store/postgres/migrations/008_durable_streams.d.ts +13 -0
- package/dist/store/postgres/migrations/008_durable_streams.js +35 -0
- package/dist/store/postgres/migrations/index.d.ts +5 -1
- package/dist/store/postgres/migrations/index.js +22 -2
- package/dist/store/postgres/migrations/shared/index.d.ts +31 -0
- package/dist/store/postgres/migrations/shared/index.js +92 -0
- package/dist/store/postgres/record.d.ts +11 -0
- package/dist/store/postgres/record.js +64 -0
- package/dist/store/postgres/store.js +337 -95
- package/dist/store/postgres/tenants.js +14 -0
- package/dist/store/types.d.ts +176 -81
- package/dist/streams/basin.d.ts +18 -1
- package/dist/streams/basin.js +34 -2
- package/dist/streams/control.d.ts +18 -0
- package/dist/streams/control.js +29 -0
- package/dist/streams/memory.d.ts +3 -1
- package/dist/streams/memory.js +14 -8
- package/dist/streams/relay/core.d.ts +35 -0
- package/dist/streams/relay/core.js +366 -0
- package/dist/streams/relay/memory.d.ts +69 -0
- package/dist/streams/relay/memory.js +176 -0
- package/dist/streams/relay/types.d.ts +77 -0
- package/dist/streams/relay/types.js +7 -0
- package/dist/streams/types.d.ts +66 -36
- package/dist/streams/types.js +18 -53
- package/dist/tenant/access-policy.d.ts +30 -0
- package/dist/tenant/access-policy.js +52 -0
- package/dist/tenant/actions.d.ts +10 -149
- package/dist/tenant/actions.js +5 -167
- package/dist/tenant/advance.js +32 -0
- package/dist/tenant/auth.d.ts +19 -17
- package/dist/tenant/auth.js +90 -93
- package/dist/tenant/browser.d.ts +15 -0
- package/dist/tenant/browser.js +39 -0
- package/dist/tenant/commands.d.ts +18 -2
- package/dist/tenant/commands.js +107 -113
- package/dist/tenant/context.d.ts +75 -18
- package/dist/tenant/context.js +14 -10
- package/dist/tenant/delivery-token.d.ts +41 -0
- package/dist/tenant/delivery-token.js +105 -0
- package/dist/tenant/delivery.d.ts +62 -0
- package/dist/tenant/delivery.js +476 -0
- package/dist/tenant/effects.d.ts +5 -0
- package/dist/tenant/effects.js +78 -37
- package/dist/tenant/endpoints.d.ts +67 -0
- package/dist/tenant/endpoints.js +90 -0
- package/dist/tenant/ephemeral.d.ts +6 -0
- package/dist/tenant/ephemeral.js +60 -36
- package/dist/tenant/http.d.ts +8 -9
- package/dist/tenant/http.js +13 -27
- package/dist/tenant/jwt.d.ts +16 -0
- package/dist/tenant/jwt.js +50 -0
- package/dist/tenant/module.d.ts +3 -0
- package/dist/tenant/module.js +6 -12
- package/dist/tenant/outbound.d.ts +44 -0
- package/dist/tenant/outbound.js +127 -0
- package/dist/tenant/pool.d.ts +0 -2
- package/dist/tenant/pool.js +0 -18
- package/dist/tenant/reset.d.ts +0 -4
- package/dist/tenant/reset.js +0 -5
- package/dist/tenant/runtime.d.ts +10 -20
- package/dist/tenant/runtime.js +59 -42
- package/dist/tenant/scheduler.d.ts +7 -2
- package/dist/tenant/scheduler.js +7 -3
- package/dist/tenant/session-streams.d.ts +125 -0
- package/dist/tenant/session-streams.js +399 -0
- package/dist/tenant/sessions.d.ts +28 -4
- package/dist/tenant/sessions.js +118 -24
- package/dist/tenant/signing-keys.d.ts +55 -0
- package/dist/tenant/signing-keys.js +172 -0
- package/dist/tenant/slim.d.ts +8 -0
- package/dist/tenant/slim.js +21 -0
- package/dist/tenant/status.d.ts +0 -3
- package/dist/tenant/status.js +19 -12
- package/dist/tenant/streams.d.ts +60 -67
- package/dist/tenant/streams.js +140 -208
- package/dist/tenant/subject-limits.d.ts +16 -0
- package/dist/tenant/subject-limits.js +45 -0
- package/dist/tenant/sweep.d.ts +1 -10
- package/dist/tenant/sweep.js +29 -69
- package/dist/tenant/tokens.d.ts +14 -0
- package/dist/tenant/tokens.js +178 -0
- package/dist/tenant/transcript.d.ts +48 -5
- package/dist/tenant/transcript.js +58 -1
- package/dist/tenant/types.d.ts +29 -7
- package/dist/tenant/worker.d.ts +3 -1
- package/dist/tenant/worker.js +9 -3
- package/dist/vault/service.d.ts +18 -2
- package/dist/vault/service.js +57 -13
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +20 -8
- package/dist/core/executors.d.ts +0 -44
- package/dist/core/executors.js +0 -95
- package/dist/streams/relay.d.ts +0 -37
- package/dist/streams/relay.js +0 -254
- package/dist/tenant/live.d.ts +0 -92
- package/dist/tenant/live.js +0 -290
- package/dist/tenant/routes-tenant.d.ts +0 -12
- package/dist/tenant/routes-tenant.js +0 -137
- package/dist/tenant/routes.d.ts +0 -9
- package/dist/tenant/routes.js +0 -151
package/dist/store/types.d.ts
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
*
|
|
10
10
|
* 1. **One transaction per `tx` call, READ COMMITTED or stronger.** Nothing a
|
|
11
11
|
* transaction wrote is visible to others before it commits, and nothing is
|
|
12
|
-
* kept when `fn` throws: document writes, event sequences,
|
|
13
|
-
* `afterCommit` callbacks
|
|
14
|
-
*
|
|
12
|
+
* kept when `fn` throws: document writes, event sequences, record rows,
|
|
13
|
+
* and `afterCommit` callbacks are all discarded, and `tx` rejects with the
|
|
14
|
+
* error `fn` threw.
|
|
15
15
|
* 2. **Session-scoped writes lock the session row first.** `lockSession` takes
|
|
16
16
|
* a row lock (`SELECT … FOR UPDATE`) held until the transaction ends. Effect
|
|
17
17
|
* intent and outcome, Action transitions, checkpoint settlement and event
|
|
@@ -21,18 +21,17 @@
|
|
|
21
21
|
* 3. **Per-session event sequence.** `event` allocates the session's next
|
|
22
22
|
* sequence under the session lock, starting at 0, without gaps across
|
|
23
23
|
* committed transactions. The sequence is also the S2 sequence number of the
|
|
24
|
-
* event in the session's stream (`sessions/<id
|
|
25
|
-
* `streams/
|
|
26
|
-
* (see `store/cursor.ts`).
|
|
24
|
+
* event in the session's stream (`sessions/<id>` in the Tenant's basin
|
|
25
|
+
* generation, `streams/basin.ts`), and the cursor is
|
|
26
|
+
* `base64url("<sessionId>:<seq>")` (see `store/cursor.ts`).
|
|
27
27
|
* 4. **No external I/O inside `fn`.** No model, tool, MCP, sandbox, Restate or
|
|
28
|
-
* S2 call, and no `fetch`, runs inside a transaction. Wakes go
|
|
29
|
-
* `afterCommit`,
|
|
30
|
-
*
|
|
28
|
+
* S2 call, and no `fetch`, runs inside a transaction. Wakes and deliveries go
|
|
29
|
+
* through `afterCommit`, and events are delivered to commit listeners after
|
|
30
|
+
* commit (seam rule 1 and 2).
|
|
31
31
|
* 5. **No nested transactions.** Calling `store.tx` from inside `fn` rejects.
|
|
32
32
|
* A `Tx` must not be used after its `tx` call settles.
|
|
33
33
|
* 6. **Post-commit order.** After a commit, the store first calls every commit
|
|
34
|
-
* listener once with the transaction's events (in allocation order)
|
|
35
|
-
* `signalWork` flag, then runs the `afterCommit` callbacks in registration
|
|
34
|
+
* listener once with the transaction's events (in allocation order), then runs the `afterCommit` callbacks in registration
|
|
36
35
|
* order and awaits them. Listener and callback failures are reported to the
|
|
37
36
|
* store's error hook; they never reject `tx`, because the commit stands.
|
|
38
37
|
* Recovery from a lost post-commit step is the Tenant sweep's job.
|
|
@@ -46,14 +45,17 @@
|
|
|
46
45
|
* ## Typed queries, no scans
|
|
47
46
|
*
|
|
48
47
|
* There is no generic table scan. Every read the Runtime needs is a typed
|
|
49
|
-
* method on `Tx` (`sessionsWithStatus`, `
|
|
48
|
+
* method on `Tx` (`sessionsWithStatus`, `expiredDeliveries`, `pendingActions`,
|
|
50
49
|
* `effectsForTurn`, `linkedSessions`, `counts`, …) that an implementation can
|
|
51
|
-
* back with an index, and principals,
|
|
50
|
+
* back with an index, and principals, endpoints, vaults and Tenant settings
|
|
52
51
|
* have their own methods rather than raw SQL outside the store. Session history
|
|
53
|
-
* is not read from the store:
|
|
54
|
-
*
|
|
52
|
+
* is not read from the store: every event is written to the record (Postgres
|
|
53
|
+
* `nylorun_streams.session_events`) in its transaction, the stream relay
|
|
54
|
+
* (`streams/relay/`) feeds Durable Streams from it, and history and SSE read
|
|
55
|
+
* them there (`tenant/session-streams.ts`).
|
|
55
56
|
*/
|
|
56
|
-
import type {
|
|
57
|
+
import type { RecordReader } from "../streams/relay/types.js";
|
|
58
|
+
import type { Action, EventPayload, EventType, LiveEvent, SessionEventOf } from "@nylorun/core/contracts";
|
|
57
59
|
import type { HostEffect } from "@nylorun/harness/run";
|
|
58
60
|
/** Tables holding JSON documents keyed by `id`. */
|
|
59
61
|
export type DocTable = "definitions" | "sessions" | "commands" | "checkpoints" | "effects" | "actions" | "sandboxes" | "links";
|
|
@@ -87,18 +89,13 @@ export interface SessionDoc {
|
|
|
87
89
|
agentId: string;
|
|
88
90
|
status: SessionStatus | (string & {});
|
|
89
91
|
activeTurnId: string | null;
|
|
90
|
-
/**
|
|
91
|
-
* Names the session's event stream (`sessions/<id>/<incarnation>`, `streams/types.ts`).
|
|
92
|
-
* Set when the session is created; `event` reports it with each event (`Commit`).
|
|
93
|
-
*/
|
|
94
|
-
streamIncarnation?: string;
|
|
95
92
|
}
|
|
96
93
|
/** The fields of an effect document the store indexes (`request.sessionId`, `request.turnId`, `request.kind`, `status`). */
|
|
97
94
|
export interface EffectDoc {
|
|
98
95
|
request: HostEffect;
|
|
99
96
|
status: EffectStatus | (string & {});
|
|
100
97
|
}
|
|
101
|
-
/** Actions are stored as the wire `Action`. Indexed: `sessionId`, `turnId`, `agentId`, `status`, `kind`, `
|
|
98
|
+
/** Actions are stored as the wire `Action`. Indexed: `sessionId`, `turnId`, `agentId`, `status`, `kind`, `deadlineAt`. */
|
|
102
99
|
export type ActionDoc = Action;
|
|
103
100
|
/** A workflow → agent session link, keyed by the linked agent session id. Indexed: `workflowSessionId`. */
|
|
104
101
|
export interface LinkDoc {
|
|
@@ -125,42 +122,70 @@ export interface LinkedSession<S extends SessionDoc = SessionDoc> {
|
|
|
125
122
|
link: LinkDoc;
|
|
126
123
|
session: StoredSession<S>;
|
|
127
124
|
}
|
|
128
|
-
/** One committed-but-not-yet-relayed event (§12.4). Deleted once S2 has it. */
|
|
129
|
-
export interface OutboxRow {
|
|
130
|
-
sessionId: string;
|
|
131
|
-
seq: number;
|
|
132
|
-
event: LiveEvent;
|
|
133
|
-
}
|
|
134
|
-
export interface OutboxStats {
|
|
135
|
-
/** Unrelayed events. */
|
|
136
|
-
depth: number;
|
|
137
|
-
/** ISO `createdAt` of the oldest unrelayed event, or null when the outbox is empty. */
|
|
138
|
-
oldestCreatedAt: string | null;
|
|
139
|
-
}
|
|
140
125
|
/** What one commit produced, delivered to commit listeners after commit. */
|
|
141
126
|
export interface Commit {
|
|
142
127
|
/** Events written by the transaction, in allocation order. */
|
|
143
128
|
readonly events: readonly LiveEvent[];
|
|
144
129
|
/**
|
|
145
|
-
* The
|
|
146
|
-
*
|
|
147
|
-
* each event to that incarnation's stream.
|
|
130
|
+
* The basin generation of each event, aligned with `events`: the stream relay appends it to
|
|
131
|
+
* `sessions/<id>` in that generation's basin.
|
|
148
132
|
*/
|
|
149
|
-
readonly
|
|
150
|
-
|
|
151
|
-
|
|
133
|
+
readonly generations: readonly number[];
|
|
134
|
+
}
|
|
135
|
+
/** A Tenant's basin generations (Durable Streams §8.1). */
|
|
136
|
+
export interface BasinGenerations {
|
|
137
|
+
/** The basin session streams are written to and read from. */
|
|
138
|
+
current: number;
|
|
139
|
+
/** Earlier generations whose basins are still to be deleted. */
|
|
140
|
+
retired: number[];
|
|
152
141
|
}
|
|
153
142
|
export type CommitListener = (commit: Commit) => void;
|
|
154
|
-
/**
|
|
155
|
-
|
|
143
|
+
/**
|
|
144
|
+
* An Action endpoint: the URL the Runtime delivers one agent's Actions to, and what recent
|
|
145
|
+
* deliveries and the last ping say about it.
|
|
146
|
+
*/
|
|
147
|
+
export interface EndpointRow {
|
|
156
148
|
agentId: string;
|
|
157
|
-
|
|
149
|
+
url: string;
|
|
158
150
|
implementationVersion: string;
|
|
159
151
|
manifestHash?: string;
|
|
152
|
+
timeoutMs: number;
|
|
153
|
+
maxConcurrent: number;
|
|
154
|
+
/** Application principal that registered it, when known. */
|
|
160
155
|
principalId?: string;
|
|
156
|
+
lastDeliveryAt?: string;
|
|
157
|
+
lastSuccessAt?: string;
|
|
158
|
+
lastErrorCode?: string;
|
|
159
|
+
lastErrorMessage?: string;
|
|
160
|
+
consecutiveFailures: number;
|
|
161
|
+
/** What the endpoint reported serving on the last ping. */
|
|
162
|
+
servedImplementationVersion?: string;
|
|
163
|
+
servedManifestHash?: string;
|
|
161
164
|
createdAt: string;
|
|
162
165
|
updatedAt: string;
|
|
163
166
|
}
|
|
167
|
+
/** The registration part of an endpoint, as `putEndpoint` writes it. */
|
|
168
|
+
export type EndpointRegistrationRow = Pick<EndpointRow, "agentId" | "url" | "implementationVersion" | "manifestHash" | "timeoutMs" | "maxConcurrent" | "principalId" | "updatedAt">;
|
|
169
|
+
/** One observation about an endpoint (`recordEndpointHealth`). */
|
|
170
|
+
export type EndpointHealthUpdate =
|
|
171
|
+
/** A delivery was answered. */
|
|
172
|
+
{
|
|
173
|
+
kind: "success";
|
|
174
|
+
at: string;
|
|
175
|
+
}
|
|
176
|
+
/** A delivery failed: not reached, refused, or lost. */
|
|
177
|
+
| {
|
|
178
|
+
kind: "failure";
|
|
179
|
+
at: string;
|
|
180
|
+
code: string;
|
|
181
|
+
message: string;
|
|
182
|
+
}
|
|
183
|
+
/** A ping was answered with what the endpoint serves. */
|
|
184
|
+
| {
|
|
185
|
+
kind: "served";
|
|
186
|
+
implementationVersion: string;
|
|
187
|
+
manifestHash?: string;
|
|
188
|
+
};
|
|
164
189
|
export interface PrincipalRow {
|
|
165
190
|
id: string;
|
|
166
191
|
role: "application" | (string & {});
|
|
@@ -184,6 +209,34 @@ export interface SealedSecret {
|
|
|
184
209
|
ciphertext: Uint8Array;
|
|
185
210
|
wrappedDek: Uint8Array;
|
|
186
211
|
}
|
|
212
|
+
/** A Tenant signing key (subject tokens). The private key is sealed; the public JWK is not. */
|
|
213
|
+
export interface SigningKeyRow extends SealedSecret {
|
|
214
|
+
id: string;
|
|
215
|
+
state: "standby" | "current" | "previous" | "revoked";
|
|
216
|
+
alg: "ES256";
|
|
217
|
+
/** JSON text of the public JWK. */
|
|
218
|
+
publicJwk: string;
|
|
219
|
+
createdAt: string;
|
|
220
|
+
activatedAt: string | null;
|
|
221
|
+
retiredAt: string | null;
|
|
222
|
+
revokedAt: string | null;
|
|
223
|
+
}
|
|
224
|
+
/** A publishable key (Host feature `browser-access`). Public by design. */
|
|
225
|
+
export interface PublishableKeyRow {
|
|
226
|
+
id: string;
|
|
227
|
+
key: string;
|
|
228
|
+
name: string;
|
|
229
|
+
/** JSON array of allowed origins. */
|
|
230
|
+
originsJson: string;
|
|
231
|
+
createdAt: string;
|
|
232
|
+
revokedAt: string | null;
|
|
233
|
+
}
|
|
234
|
+
/** A subject's turn bucket (subject limits): tokens left and when they were last refilled. */
|
|
235
|
+
export interface SubjectUsageRow {
|
|
236
|
+
subject: string;
|
|
237
|
+
turnTokens: number;
|
|
238
|
+
refilledAt: string;
|
|
239
|
+
}
|
|
187
240
|
export interface VaultCredentialRow extends SealedSecret {
|
|
188
241
|
id: string;
|
|
189
242
|
vaultId: string;
|
|
@@ -229,7 +282,7 @@ export interface StoreCounts {
|
|
|
229
282
|
sessions: number;
|
|
230
283
|
/** Sessions `running` or `runnable`. */
|
|
231
284
|
runningSessions: number;
|
|
232
|
-
/** Actions `pending` or `
|
|
285
|
+
/** Actions `pending` or `delivering`. */
|
|
233
286
|
pendingActions: number;
|
|
234
287
|
/** Effects `uncertain`. */
|
|
235
288
|
uncertainEffects: number;
|
|
@@ -263,10 +316,12 @@ export interface SessionStore {
|
|
|
263
316
|
tx<T>(fn: (t: Tx) => Promise<T>): Promise<T>;
|
|
264
317
|
/**
|
|
265
318
|
* Registers a listener called once per committed transaction that wrote
|
|
266
|
-
* events
|
|
267
|
-
* relay (`streams
|
|
319
|
+
* events. Returns an unsubscribe function. A Tenant without the Host's stream
|
|
320
|
+
* relay relays its own commits from here (`tenant/streams.ts`).
|
|
268
321
|
*/
|
|
269
322
|
onCommit(listener: CommitListener): () => void;
|
|
323
|
+
/** Reads the record back, for the stream relay's refills and reconciliation. */
|
|
324
|
+
record(): RecordReader;
|
|
270
325
|
/** Reachability and schema check for Tenant status and `/ready`. Never throws. */
|
|
271
326
|
health(): Promise<StoreHealth>;
|
|
272
327
|
close(): Promise<void>;
|
|
@@ -279,7 +334,7 @@ export interface StoreHealth {
|
|
|
279
334
|
/** Options every implementation accepts. */
|
|
280
335
|
export interface SessionStoreOptions {
|
|
281
336
|
tenantId: string;
|
|
282
|
-
/** Clock for event `
|
|
337
|
+
/** Clock for event `time`. Defaults to `() => new Date()`. */
|
|
283
338
|
now?: () => Date;
|
|
284
339
|
/** Receives listener and `afterCommit` failures, which never reject `tx`. */
|
|
285
340
|
onError?: (error: unknown) => void;
|
|
@@ -290,9 +345,9 @@ export interface Tx {
|
|
|
290
345
|
/** Insert or replace. For `sessions`, ownership fields in `body` are ignored. */
|
|
291
346
|
put(table: DocTable, id: string, body: unknown): Promise<void>;
|
|
292
347
|
/**
|
|
293
|
-
* Deletes a document. Deleting a session
|
|
294
|
-
* session
|
|
295
|
-
*
|
|
348
|
+
* Deletes a document. Deleting a session keeps its record rows and log head
|
|
349
|
+
* (per-session record deletion is deferred, Durable Streams §15), so a session
|
|
350
|
+
* created again with the same id continues its log and stream.
|
|
296
351
|
*/
|
|
297
352
|
delete(table: DocTable, id: string): Promise<void>;
|
|
298
353
|
/**
|
|
@@ -313,14 +368,15 @@ export interface Tx {
|
|
|
313
368
|
lockSession<T extends SessionDoc = SessionDoc>(id: string): Promise<StoredSession<T> | undefined>;
|
|
314
369
|
/**
|
|
315
370
|
* Allocates the session's next sequence (from 0) under the session lock,
|
|
316
|
-
*
|
|
317
|
-
*
|
|
371
|
+
* builds the event on the `nylorun.event/2` envelope, checks it against the
|
|
372
|
+
* event catalog (`InvalidEventError` when it does not match), writes it to
|
|
373
|
+
* the record in the Tenant's current basin generation and buffers it for
|
|
374
|
+
* commit listeners. Rejects when the session does not exist. The returned
|
|
375
|
+
* cursor is final.
|
|
318
376
|
*/
|
|
319
|
-
event(sessionId: string, turnId: string | null, type:
|
|
377
|
+
event<T extends EventType>(sessionId: string, turnId: string | null, type: T, payload: EventPayload<T>): Promise<SessionEventOf<T>>;
|
|
320
378
|
/** Runs `fn` after a successful commit (never on rollback). Used for wakes. */
|
|
321
379
|
afterCommit(fn: () => void | Promise<void>): void;
|
|
322
|
-
/** Signals `work_available` to executors after a successful commit. */
|
|
323
|
-
signalWork(): void;
|
|
324
380
|
/**
|
|
325
381
|
* Locks the session and takes ownership when it has no owner or the owner's
|
|
326
382
|
* lease ended at or before `now`; then increments `epoch` and sets the lease
|
|
@@ -354,10 +410,14 @@ export interface Tx {
|
|
|
354
410
|
}): Promise<StoredSession<T>[]>;
|
|
355
411
|
listDefinitions<T extends DefinitionDoc = DefinitionDoc>(): Promise<T[]>;
|
|
356
412
|
listSandboxes<T extends SandboxDoc = SandboxDoc>(): Promise<T[]>;
|
|
357
|
-
/**
|
|
358
|
-
expiredClaims(now: Date, limit: number): Promise<ActionDoc[]>;
|
|
359
|
-
/** `pending` actions offered to one agent's executor. */
|
|
413
|
+
/** One agent's `pending` actions, delivered when its endpoint is registered. */
|
|
360
414
|
pendingActions(agentId: string): Promise<ActionDoc[]>;
|
|
415
|
+
/** How many of one agent's actions are `delivering` (Action endpoints). */
|
|
416
|
+
deliveringCount(agentId: string): Promise<number>;
|
|
417
|
+
/** `pending` actions of agents that have an Action endpoint, by id. */
|
|
418
|
+
pendingActionsWithEndpoint(limit: number): Promise<ActionDoc[]>;
|
|
419
|
+
/** `delivering` actions whose deadline is at or before `now`, earliest first. */
|
|
420
|
+
expiredDeliveries(now: Date, limit: number): Promise<ActionDoc[]>;
|
|
361
421
|
actionsForSession(sessionId: string, filter?: SessionActionFilter): Promise<ActionDoc[]>;
|
|
362
422
|
actionsWithStatus(statuses: readonly ActionStatus[], filter?: {
|
|
363
423
|
kinds?: readonly ActionKind[];
|
|
@@ -372,27 +432,19 @@ export interface Tx {
|
|
|
372
432
|
/** Links of one workflow session joined to their existing agent sessions, ordered by agent session id. */
|
|
373
433
|
linkedSessions<S extends SessionDoc = SessionDoc>(workflowSessionId: string): Promise<LinkedSession<S>[]>;
|
|
374
434
|
counts(): Promise<StoreCounts>;
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
435
|
+
basinGenerations(): Promise<BasinGenerations>;
|
|
436
|
+
/** Forgets a retired generation once its basin is deleted. */
|
|
437
|
+
forgetRetiredGeneration(generation: number): Promise<void>;
|
|
438
|
+
listEndpoints(): Promise<EndpointRow[]>;
|
|
439
|
+
getEndpoint(agentId: string): Promise<EndpointRow | undefined>;
|
|
379
440
|
/**
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
* (null: none), in one statement: a relay that appended to an abandoned incarnation's
|
|
383
|
-
* stream never deletes the rows of a session created again with the same id.
|
|
441
|
+
* Inserts or updates by `agentId`, keeping `createdAt`. Health is kept, except that a new
|
|
442
|
+
* `url` starts with none.
|
|
384
443
|
*/
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
getExecutor(agentId: string): Promise<ExecutorRow | undefined>;
|
|
390
|
-
/**
|
|
391
|
-
* Inserts or updates by `agentId`, keeping `createdAt` on update. Rejects
|
|
392
|
-
* when another agent already uses `tokenHash`.
|
|
393
|
-
*/
|
|
394
|
-
putExecutor(row: Omit<ExecutorRow, "createdAt">): Promise<void>;
|
|
395
|
-
deleteExecutor(agentId: string): Promise<void>;
|
|
444
|
+
putEndpoint(row: EndpointRegistrationRow): Promise<void>;
|
|
445
|
+
deleteEndpoint(agentId: string): Promise<void>;
|
|
446
|
+
/** Records one observation. Nothing happens when the endpoint does not exist. */
|
|
447
|
+
recordEndpointHealth(agentId: string, update: EndpointHealthUpdate): Promise<void>;
|
|
396
448
|
/** Rejects when the id or token hash already exists. */
|
|
397
449
|
insertPrincipal(row: PrincipalRow): Promise<void>;
|
|
398
450
|
principalByTokenHash(tokenHash: string): Promise<PrincipalRow | undefined>;
|
|
@@ -428,15 +480,58 @@ export interface Tx {
|
|
|
428
480
|
getVaultIdempotency(id: string): Promise<VaultIdempotencyRow | undefined>;
|
|
429
481
|
/** Rejects when the id exists. */
|
|
430
482
|
insertVaultIdempotency(row: VaultIdempotencyRow): Promise<void>;
|
|
483
|
+
/**
|
|
484
|
+
* Serializes signing key changes: held until the transaction ends. Take it before reading
|
|
485
|
+
* the keys a change depends on, so concurrent first uses or rotations do not collide.
|
|
486
|
+
*/
|
|
487
|
+
lockSigningKeys(): Promise<void>;
|
|
488
|
+
/** Rejects on a duplicate id, or a second key in `standby`, `current` or `previous`. */
|
|
489
|
+
insertSigningKey(row: SigningKeyRow): Promise<void>;
|
|
490
|
+
signingKey(id: string): Promise<SigningKeyRow | undefined>;
|
|
491
|
+
/** Keys in the given states (all when omitted), ordered by `createdAt`, then id. */
|
|
492
|
+
signingKeys(states?: readonly SigningKeyRow["state"][]): Promise<SigningKeyRow[]>;
|
|
493
|
+
/**
|
|
494
|
+
* Moves a key from `from` to `to` and stamps the matching time (`activatedAt` for
|
|
495
|
+
* `current`, `retiredAt` for `previous`, `revokedAt` for `revoked`). Returns false when the
|
|
496
|
+
* key is not in `from`.
|
|
497
|
+
*/
|
|
498
|
+
setSigningKeyState(id: string, from: SigningKeyRow["state"], to: SigningKeyRow["state"], at: string): Promise<boolean>;
|
|
499
|
+
countSigningKeys(): Promise<number>;
|
|
500
|
+
/** The subject's revocation epoch; 0 when it was never revoked. */
|
|
501
|
+
subjectEpoch(subject: string): Promise<number>;
|
|
502
|
+
/** The epochs of several subjects; subjects never revoked are absent. */
|
|
503
|
+
subjectEpochs(subjects: readonly string[]): Promise<Map<string, number>>;
|
|
504
|
+
/** Adds one to the subject's epoch and returns the new value. */
|
|
505
|
+
bumpSubjectEpoch(subject: string, at: string): Promise<number>;
|
|
506
|
+
/**
|
|
507
|
+
* The subject's turn bucket, created from `initial` when missing, locked until the
|
|
508
|
+
* transaction ends so concurrent commands of one subject serialize.
|
|
509
|
+
*/
|
|
510
|
+
lockSubjectUsage(initial: SubjectUsageRow): Promise<SubjectUsageRow>;
|
|
511
|
+
putSubjectUsage(row: SubjectUsageRow): Promise<void>;
|
|
512
|
+
/** How many sessions of `ownerUserId` are in one of `statuses`. */
|
|
513
|
+
countOwnerSessions(ownerUserId: string, statuses: readonly string[]): Promise<number>;
|
|
514
|
+
/** Rejects on a duplicate id, key or name. */
|
|
515
|
+
insertPublishableKey(row: PublishableKeyRow): Promise<void>;
|
|
516
|
+
publishableKeyByKey(key: string): Promise<PublishableKeyRow | undefined>;
|
|
517
|
+
publishableKey(id: string): Promise<PublishableKeyRow | undefined>;
|
|
518
|
+
/** All keys, revoked ones included, ordered by `createdAt`, then id. */
|
|
519
|
+
publishableKeys(): Promise<PublishableKeyRow[]>;
|
|
520
|
+
/** Returns false when the key does not exist. */
|
|
521
|
+
updatePublishableKey(id: string, patch: Partial<Pick<PublishableKeyRow, "originsJson" | "revokedAt">>): Promise<boolean>;
|
|
431
522
|
getSetting(key: string): Promise<string | undefined>;
|
|
432
523
|
putSetting(key: string, value: string): Promise<void>;
|
|
433
524
|
/**
|
|
434
525
|
* Deletes Tenant state by scope, in this transaction:
|
|
435
|
-
* - `sessions`: sessions, commands, checkpoints, effects, actions, links
|
|
526
|
+
* - `sessions`: sessions, commands, checkpoints, effects, actions, links, subject turn
|
|
527
|
+
* buckets and the Tenant's record rows and log heads. The Tenant moves to the next basin
|
|
528
|
+
* generation and the current one is retired, so session ids it frees start again in an
|
|
529
|
+
* empty basin;
|
|
436
530
|
* - `sandboxes`: sandbox records;
|
|
437
|
-
* - `all`: both, plus definitions,
|
|
438
|
-
* credentials. The host vault, principals,
|
|
439
|
-
*
|
|
531
|
+
* - `all`: both, plus definitions, Action endpoints and user vaults with their
|
|
532
|
+
* credentials. The host vault, principals, signing keys, subject epochs, publishable
|
|
533
|
+
* keys, settings,
|
|
534
|
+
* audit and vault idempotency rows stay.
|
|
440
535
|
*/
|
|
441
536
|
reset(scope: ResetScope): Promise<void>;
|
|
442
537
|
}
|
package/dist/streams/basin.d.ts
CHANGED
|
@@ -20,4 +20,21 @@
|
|
|
20
20
|
*/
|
|
21
21
|
export declare const MAX_BASIN_PREFIX_LENGTH = 16;
|
|
22
22
|
export declare function validateBasinPrefix(prefix: string): string;
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* The basin of one generation of a Tenant (Durable Streams §8.1). A Tenant reset moves the
|
|
25
|
+
* Tenant to a new generation, so a session id it frees starts again in an empty basin.
|
|
26
|
+
* `DurableStreams` methods take this key where they take a Tenant id; generation 0 is the
|
|
27
|
+
* Tenant id itself, so existing basins keep their names.
|
|
28
|
+
*/
|
|
29
|
+
export declare function basinOf(tenantId: string, generation: number): string;
|
|
30
|
+
/** The Tenant id and generation of a basin key from `basinOf`. */
|
|
31
|
+
export declare function parseBasin(basin: string): {
|
|
32
|
+
tenantId: string;
|
|
33
|
+
generation: number;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* The S2 basin of a Tenant, or of one of its generations (`basinOf`). Generation `g` > 0 of
|
|
37
|
+
* a canonical Tenant appends `-<g mod 1296 in base36>`, at most 48 characters with a
|
|
38
|
+
* 16-character prefix; other ids fold the generation into their hash.
|
|
39
|
+
*/
|
|
40
|
+
export declare function tenantBasinName(basin: string, prefix?: string): string;
|
package/dist/streams/basin.js
CHANGED
|
@@ -28,13 +28,42 @@ export function validateBasinPrefix(prefix) {
|
|
|
28
28
|
throw new Error(`S2 basin prefix must be at most ${MAX_BASIN_PREFIX_LENGTH} lowercase letters, digits or hyphens, starting with a letter or digit`);
|
|
29
29
|
return prefix;
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
/**
|
|
32
|
+
* The basin of one generation of a Tenant (Durable Streams §8.1). A Tenant reset moves the
|
|
33
|
+
* Tenant to a new generation, so a session id it frees starts again in an empty basin.
|
|
34
|
+
* `DurableStreams` methods take this key where they take a Tenant id; generation 0 is the
|
|
35
|
+
* Tenant id itself, so existing basins keep their names.
|
|
36
|
+
*/
|
|
37
|
+
export function basinOf(tenantId, generation) {
|
|
38
|
+
if (!Number.isInteger(generation) || generation < 0)
|
|
39
|
+
throw new Error("A basin generation is a non-negative integer");
|
|
40
|
+
return generation === 0 ? tenantId : `${tenantId}${GENERATION_SEPARATOR}${generation}`;
|
|
41
|
+
}
|
|
42
|
+
/** The Tenant id and generation of a basin key from `basinOf`. */
|
|
43
|
+
export function parseBasin(basin) {
|
|
44
|
+
const at = basin.lastIndexOf(GENERATION_SEPARATOR);
|
|
45
|
+
const generation = at < 0 ? NaN : Number(basin.slice(at + 1));
|
|
46
|
+
return Number.isInteger(generation) && generation > 0
|
|
47
|
+
? { tenantId: basin.slice(0, at), generation }
|
|
48
|
+
: { tenantId: basin, generation: 0 };
|
|
49
|
+
}
|
|
50
|
+
/** Never in a Tenant id (`TENANT_ID_PATTERN`). */
|
|
51
|
+
const GENERATION_SEPARATOR = "#";
|
|
52
|
+
/**
|
|
53
|
+
* The S2 basin of a Tenant, or of one of its generations (`basinOf`). Generation `g` > 0 of
|
|
54
|
+
* a canonical Tenant appends `-<g mod 1296 in base36>`, at most 48 characters with a
|
|
55
|
+
* 16-character prefix; other ids fold the generation into their hash.
|
|
56
|
+
*/
|
|
57
|
+
export function tenantBasinName(basin, prefix = "") {
|
|
32
58
|
validateBasinPrefix(prefix);
|
|
59
|
+
const { tenantId, generation } = parseBasin(basin);
|
|
33
60
|
if (!tenantId)
|
|
34
61
|
throw new Error("tenantId is required");
|
|
35
62
|
let name;
|
|
36
63
|
if (TENANT_ID_PATTERN.test(tenantId)) {
|
|
37
64
|
name = `${prefix}tn-${tenantId.slice(3)}`;
|
|
65
|
+
if (generation > 0)
|
|
66
|
+
name += `-${(generation % 1296).toString(36)}`;
|
|
38
67
|
}
|
|
39
68
|
else {
|
|
40
69
|
const slug = tenantId
|
|
@@ -42,7 +71,10 @@ export function tenantBasinName(tenantId, prefix = "") {
|
|
|
42
71
|
.replace(/[^a-z0-9]+/g, "-")
|
|
43
72
|
.slice(0, 12)
|
|
44
73
|
.replace(/^-+|-+$/g, "");
|
|
45
|
-
const hash = createHash("sha256")
|
|
74
|
+
const hash = createHash("sha256")
|
|
75
|
+
.update(generation > 0 ? basin : tenantId)
|
|
76
|
+
.digest("hex")
|
|
77
|
+
.slice(0, 16);
|
|
46
78
|
name = `${prefix}x-${slug ? `${slug}-` : ""}${hash}`;
|
|
47
79
|
}
|
|
48
80
|
if (!BASIN_NAME.test(name))
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signals on a basin's `tenant/control` stream (Durable Streams §8). They are appended
|
|
3
|
+
* directly, never recorded: a lost signal costs latency, never correctness (the epoch fence
|
|
4
|
+
* stops a cancelled turn's effects; readers also check their sessions periodically).
|
|
5
|
+
*/
|
|
6
|
+
import { type DurableStreams } from "./types.js";
|
|
7
|
+
/** Appends a `session.cancel` signal: the process running the session's advance aborts it. */
|
|
8
|
+
export declare function signalCancel(streams: DurableStreams, basin: string, sessionId: string): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Appends a `sessions.reset` signal to the old generation's basin: every process with the
|
|
11
|
+
* Tenant open moves its readers to `generation` and ends the streams of deleted sessions.
|
|
12
|
+
*/
|
|
13
|
+
export declare function signalSessionsReset(streams: DurableStreams, basin: string, generation: number): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Appends a `subject.revoked` signal: every process with the Tenant open ends the subject's
|
|
16
|
+
* streams opened with a token older than `epoch`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function signalSubjectRevoked(streams: DurableStreams, basin: string, subject: string, epoch: number): Promise<void>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signals on a basin's `tenant/control` stream (Durable Streams §8). They are appended
|
|
3
|
+
* directly, never recorded: a lost signal costs latency, never correctness (the epoch fence
|
|
4
|
+
* stops a cancelled turn's effects; readers also check their sessions periodically).
|
|
5
|
+
*/
|
|
6
|
+
import { CONTROL_STREAM } from "./types.js";
|
|
7
|
+
/** Appends a `session.cancel` signal: the process running the session's advance aborts it. */
|
|
8
|
+
export async function signalCancel(streams, basin, sessionId) {
|
|
9
|
+
if (!sessionId)
|
|
10
|
+
throw new Error("sessionId is required");
|
|
11
|
+
const signal = { type: "session.cancel", sessionId };
|
|
12
|
+
await streams.append(basin, CONTROL_STREAM, [signal]);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Appends a `sessions.reset` signal to the old generation's basin: every process with the
|
|
16
|
+
* Tenant open moves its readers to `generation` and ends the streams of deleted sessions.
|
|
17
|
+
*/
|
|
18
|
+
export async function signalSessionsReset(streams, basin, generation) {
|
|
19
|
+
const signal = { type: "sessions.reset", generation };
|
|
20
|
+
await streams.append(basin, CONTROL_STREAM, [signal]);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Appends a `subject.revoked` signal: every process with the Tenant open ends the subject's
|
|
24
|
+
* streams opened with a token older than `epoch`.
|
|
25
|
+
*/
|
|
26
|
+
export async function signalSubjectRevoked(streams, basin, subject, epoch) {
|
|
27
|
+
const signal = { type: "subject.revoked", subject, epoch };
|
|
28
|
+
await streams.append(basin, CONTROL_STREAM, [signal]);
|
|
29
|
+
}
|
package/dist/streams/memory.d.ts
CHANGED
|
@@ -14,7 +14,9 @@ export declare class MemoryStreams implements DurableStreams {
|
|
|
14
14
|
read<T = unknown>(tenantId: string, stream: string, fromSeq: number, options?: ReadOptions): AsyncIterable<StreamRecord<T>>;
|
|
15
15
|
tail(tenantId: string, stream: string): Promise<number>;
|
|
16
16
|
ensureTenant(tenantId: string): Promise<void>;
|
|
17
|
-
deleteTenant(tenantId: string
|
|
17
|
+
deleteTenant(tenantId: string, options?: {
|
|
18
|
+
allGenerations?: boolean;
|
|
19
|
+
}): Promise<void>;
|
|
18
20
|
deleteStream(tenantId: string, stream: string): Promise<void>;
|
|
19
21
|
listStreams(tenantId: string, prefix: string): Promise<string[]>;
|
|
20
22
|
close(): Promise<void>;
|
package/dist/streams/memory.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseBasin } from "./basin.js";
|
|
1
2
|
/**
|
|
2
3
|
* In-memory `DurableStreams` for unit tests and a local development Host.
|
|
3
4
|
* Not durable: history does not survive a restart. Keeps the seam's
|
|
@@ -71,14 +72,19 @@ export class MemoryStreams {
|
|
|
71
72
|
if (!this.tenants.has(tenantId))
|
|
72
73
|
this.tenants.set(tenantId, new Map());
|
|
73
74
|
}
|
|
74
|
-
async deleteTenant(tenantId) {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
async deleteTenant(tenantId, options = {}) {
|
|
76
|
+
const basins = options.allGenerations
|
|
77
|
+
? [...this.tenants.keys()].filter((key) => parseBasin(key).tenantId === tenantId)
|
|
78
|
+
: [tenantId];
|
|
79
|
+
for (const basin of basins) {
|
|
80
|
+
const streams = this.tenants.get(basin);
|
|
81
|
+
this.tenants.delete(basin);
|
|
82
|
+
for (const stream of streams?.keys() ?? [])
|
|
83
|
+
this.notify(basin, stream);
|
|
84
|
+
for (const key of this.waiters.keys())
|
|
85
|
+
if (key.startsWith(`${basin}\u0000`))
|
|
86
|
+
this.notifyKey(key);
|
|
87
|
+
}
|
|
82
88
|
}
|
|
83
89
|
async deleteStream(tenantId, stream) {
|
|
84
90
|
this.tenants.get(tenantId)?.delete(stream);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type DurableStreams } from "../types.js";
|
|
2
|
+
import type { ChangeSource, RecordReader } from "./types.js";
|
|
3
|
+
export interface StreamRelayOptions {
|
|
4
|
+
source: ChangeSource;
|
|
5
|
+
record: RecordReader;
|
|
6
|
+
streams: DurableStreams;
|
|
7
|
+
/** Relay events: lag, errors, reconciliations. */
|
|
8
|
+
log?: (message: string, fields?: Record<string, unknown>) => void;
|
|
9
|
+
/** Records per append (S2 allows 1000). */
|
|
10
|
+
maxBatchRecords?: number;
|
|
11
|
+
/** Bytes per append (S2 allows 1 MiB). */
|
|
12
|
+
maxBatchBytes?: number;
|
|
13
|
+
/** Sessions reconciled at once. */
|
|
14
|
+
reconcileConcurrency?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface StreamRelayStatus {
|
|
17
|
+
/** This process holds the source (the replication slot). */
|
|
18
|
+
active: boolean;
|
|
19
|
+
/** Committed transactions not yet fully in S2. */
|
|
20
|
+
pendingTxs: number;
|
|
21
|
+
/** Rows queued for S2. */
|
|
22
|
+
pendingRows: number;
|
|
23
|
+
/** The last position acknowledged to the source. */
|
|
24
|
+
confirmed: string | null;
|
|
25
|
+
reconciliations: number;
|
|
26
|
+
lastError: string | null;
|
|
27
|
+
}
|
|
28
|
+
export interface StreamRelay {
|
|
29
|
+
start(): void;
|
|
30
|
+
stop(): Promise<void>;
|
|
31
|
+
status(): StreamRelayStatus;
|
|
32
|
+
/** Resolves once nothing is pending (tests and shutdown). */
|
|
33
|
+
idle(): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export declare function createStreamRelay(options: StreamRelayOptions): StreamRelay;
|