@nylorun/runtime 0.13.0-beta → 0.15.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 +434 -0
- package/README.md +137 -41
- package/dist/adapters/execution/restate.d.ts +7 -2
- package/dist/adapters/execution/restate.js +40 -10
- package/dist/adapters/replication/pgoutput.d.ts +21 -0
- package/dist/adapters/replication/pgoutput.js +170 -0
- package/dist/adapters/streams/s2.js +37 -20
- package/dist/admin-openapi.json +570 -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 +8065 -0
- package/dist/api/components.js +82 -0
- package/dist/api/gate/keys.d.ts +14 -0
- package/dist/api/gate/keys.js +59 -0
- package/dist/api/gate/routes.d.ts +34 -0
- package/dist/api/gate/routes.js +235 -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 +122 -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 +266 -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 +11 -0
- package/dist/api/http/routes/tenant.js +244 -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 +137 -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/gates/contract.d.ts +51 -0
- package/dist/gates/contract.js +37 -0
- package/dist/gates/handler.d.ts +21 -0
- package/dist/gates/handler.js +55 -0
- package/dist/gates/http-client.d.ts +12 -0
- package/dist/gates/http-client.js +160 -0
- package/dist/gates/in-process.d.ts +32 -0
- package/dist/gates/in-process.js +42 -0
- package/dist/gates/inflight.d.ts +39 -0
- package/dist/gates/inflight.js +70 -0
- package/dist/gates/mcp-handler.d.ts +35 -0
- package/dist/gates/mcp-handler.js +142 -0
- package/dist/gates/meter.d.ts +24 -0
- package/dist/gates/meter.js +165 -0
- package/dist/gates/model-gate.d.ts +45 -0
- package/dist/gates/model-gate.js +1 -0
- package/dist/gates/tenant-vaults.d.ts +64 -0
- package/dist/gates/tenant-vaults.js +94 -0
- package/dist/gates/tool-calls.d.ts +51 -0
- package/dist/gates/tool-calls.js +64 -0
- package/dist/gates/tool-client.d.ts +12 -0
- package/dist/gates/tool-client.js +201 -0
- package/dist/gates/tool-contract.d.ts +116 -0
- package/dist/gates/tool-contract.js +64 -0
- package/dist/gates/tool-gate.d.ts +45 -0
- package/dist/gates/tool-gate.js +10 -0
- package/dist/host/admin-api.d.ts +23 -0
- package/dist/host/admin-api.js +74 -0
- package/dist/host/app.d.ts +34 -0
- package/dist/host/app.js +181 -0
- package/dist/host/config-for.d.ts +3 -1
- package/dist/host/config-for.js +3 -2
- 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 +36 -8
- package/dist/host/create-host.js +161 -330
- package/dist/host/execution.d.ts +20 -18
- package/dist/host/execution.js +2 -2
- package/dist/host/gates.d.ts +58 -0
- package/dist/host/gates.js +127 -0
- package/dist/host/http.d.ts +20 -4
- package/dist/host/http.js +81 -23
- package/dist/host/index.d.ts +1 -1
- package/dist/host/main.js +189 -58
- package/dist/host/stack-config.d.ts +91 -19
- package/dist/host/stack-config.js +246 -17
- package/dist/infra/database.d.ts +2 -2
- package/dist/infra/execution.d.ts +5 -5
- package/dist/infra/execution.js +8 -6
- package/dist/infra/index.d.ts +1 -1
- package/dist/infra/workers.d.ts +17 -17
- package/dist/infra/workers.js +9 -9
- package/dist/keys/client.d.ts +9 -0
- package/dist/keys/client.js +70 -0
- package/dist/keys/contract.d.ts +24 -0
- package/dist/keys/contract.js +12 -0
- package/dist/keys/keys.d.ts +41 -0
- package/dist/keys/keys.js +50 -0
- package/dist/mcp/connect.d.ts +43 -3
- package/dist/mcp/connect.js +12 -5
- package/dist/mcp/pool.d.ts +39 -3
- package/dist/mcp/pool.js +74 -11
- 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 +25889 -0
- package/dist/record/append.d.ts +51 -0
- package/dist/record/append.js +20 -0
- package/dist/record/envelope.d.ts +19 -0
- package/dist/record/envelope.js +49 -0
- package/dist/record/index.d.ts +11 -0
- package/dist/record/index.js +11 -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/postgres/connect.d.ts +25 -9
- package/dist/store/postgres/connect.js +27 -10
- package/dist/store/postgres/db.d.ts +16 -0
- package/dist/store/postgres/db.js +28 -0
- package/dist/store/postgres/drizzle/0000_foundation.sql +22 -0
- package/dist/store/postgres/drizzle/0001_baseline.sql +255 -0
- package/dist/store/postgres/drizzle/0002_stream_relay.sql +4 -0
- package/dist/store/postgres/drizzle/0003_tool_crossings.sql +9 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +34 -0
- package/dist/store/postgres/migrate.d.ts +68 -0
- package/dist/store/postgres/migrate.js +183 -0
- package/dist/store/postgres/record-writer.d.ts +4 -0
- package/dist/store/postgres/record-writer.js +34 -0
- package/dist/store/postgres/record.d.ts +5 -0
- package/dist/store/postgres/record.js +60 -0
- package/dist/store/postgres/schema.d.ts +3096 -0
- package/dist/store/postgres/schema.js +363 -0
- package/dist/store/postgres/store.d.ts +2 -4
- package/dist/store/postgres/store.js +590 -434
- package/dist/store/postgres/tenant.d.ts +56 -0
- package/dist/store/postgres/tenant.js +154 -0
- package/dist/store/types.d.ts +183 -132
- package/dist/store/types.js +0 -1
- 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 +33 -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/types.d.ts +78 -0
- package/dist/streams/relay/types.js +8 -0
- package/dist/streams/types.d.ts +71 -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.d.ts +7 -2
- package/dist/tenant/advance.js +125 -14
- 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/cause.d.ts +25 -0
- package/dist/tenant/cause.js +45 -0
- package/dist/tenant/commands.d.ts +18 -2
- package/dist/tenant/commands.js +111 -115
- package/dist/tenant/context.d.ts +101 -21
- package/dist/tenant/context.js +14 -10
- package/dist/tenant/delivery-token.d.ts +41 -0
- package/dist/tenant/delivery-token.js +104 -0
- package/dist/tenant/delivery.d.ts +62 -0
- package/dist/tenant/delivery.js +481 -0
- package/dist/tenant/effects.d.ts +20 -1
- package/dist/tenant/effects.js +159 -42
- package/dist/tenant/endpoints.d.ts +67 -0
- package/dist/tenant/endpoints.js +90 -0
- package/dist/tenant/ephemeral.d.ts +22 -7
- package/dist/tenant/ephemeral.js +83 -125
- package/dist/tenant/history.d.ts +64 -0
- package/dist/tenant/history.js +120 -0
- 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/model-setting.d.ts +2 -2
- package/dist/tenant/module.d.ts +17 -28
- package/dist/tenant/module.js +133 -279
- package/dist/tenant/outbound.d.ts +44 -0
- package/dist/tenant/outbound.js +127 -0
- package/dist/tenant/paths.d.ts +11 -16
- package/dist/tenant/paths.js +24 -68
- package/dist/tenant/pool.d.ts +1 -3
- package/dist/tenant/pool.js +1 -19
- package/dist/tenant/principals.d.ts +23 -11
- package/dist/tenant/principals.js +61 -49
- package/dist/tenant/reset.d.ts +0 -4
- package/dist/tenant/reset.js +0 -5
- package/dist/tenant/runtime.d.ts +28 -20
- package/dist/tenant/runtime.js +95 -64
- package/dist/tenant/scheduler.d.ts +10 -3
- package/dist/tenant/scheduler.js +9 -5
- package/dist/tenant/session-streams.d.ts +127 -0
- package/dist/tenant/session-streams.js +404 -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 +3 -3
- package/dist/tenant/status.js +20 -13
- package/dist/tenant/store-pg.d.ts +9 -6
- package/dist/tenant/store-pg.js +60 -125
- package/dist/tenant/streams.d.ts +62 -85
- package/dist/tenant/streams.js +143 -226
- 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 +180 -0
- package/dist/tenant/transcript.d.ts +48 -5
- package/dist/tenant/transcript.js +58 -1
- package/dist/tenant/types.d.ts +69 -98
- package/dist/tenant/types.js +4 -15
- package/dist/tenant/worker.d.ts +3 -1
- package/dist/tenant/worker.js +13 -7
- package/dist/vault/host-model.d.ts +31 -0
- package/dist/vault/host-model.js +57 -0
- package/dist/vault/service.d.ts +44 -17
- package/dist/vault/service.js +126 -121
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +25 -9
- package/dist/core/executors.d.ts +0 -44
- package/dist/core/executors.js +0 -95
- package/dist/store/memory.d.ts +0 -57
- package/dist/store/memory.js +0 -645
- package/dist/store/postgres/migrations/001_initial.d.ts +0 -27
- package/dist/store/postgres/migrations/001_initial.js +0 -196
- package/dist/store/postgres/migrations/002_session_owner.d.ts +0 -6
- package/dist/store/postgres/migrations/002_session_owner.js +0 -13
- package/dist/store/postgres/migrations/index.d.ts +0 -49
- package/dist/store/postgres/migrations/index.js +0 -76
- package/dist/store/postgres/names.d.ts +0 -11
- package/dist/store/postgres/names.js +0 -42
- package/dist/store/postgres/tenants.d.ts +0 -90
- package/dist/store/postgres/tenants.js +0 -214
- package/dist/streams/relay.d.ts +0 -37
- package/dist/streams/relay.js +0 -254
- package/dist/tenant/envelope.d.ts +0 -7
- package/dist/tenant/envelope.js +0 -22
- package/dist/tenant/live.d.ts +0 -92
- package/dist/tenant/live.js +0 -290
- package/dist/tenant/quarantine-error.d.ts +0 -6
- package/dist/tenant/quarantine-error.js +0 -9
- package/dist/tenant/quarantine.d.ts +0 -27
- package/dist/tenant/quarantine.js +0 -72
- 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/tenant/store-memory.d.ts +0 -16
- package/dist/tenant/store-memory.js +0 -111
- /package/dist/{store → record}/cursor.d.ts +0 -0
- /package/dist/{store → record}/cursor.js +0 -0
package/dist/tenant/module.d.ts
CHANGED
|
@@ -1,38 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import type { StreamRelayStatus } from "@nylorun/core/contracts";
|
|
2
|
+
import { TenantUnavailableError, type Logger, type TenantHandle, type TenantModule, type TenantOpener } from "./types.js";
|
|
3
3
|
export interface CreateTenantModuleOptions {
|
|
4
|
-
store
|
|
5
|
-
|
|
6
|
-
hostRoot?: string;
|
|
7
|
-
/** Unused: the store opens Tenants. Accepted so callers can pass the store's options. */
|
|
8
|
-
openRuntime?: OpenTenantRuntime;
|
|
9
|
-
/** Unused; see `openRuntime`. */
|
|
10
|
-
configFor?: (tenantId: string) => TenantConfig;
|
|
4
|
+
/** Opens the Host's Tenant (`store-pg.ts`). */
|
|
5
|
+
open: TenantOpener;
|
|
11
6
|
logger: Logger;
|
|
12
|
-
/** Bound on
|
|
7
|
+
/** Bound on opening the Tenant; a slower open fails with `open-timeout`. */
|
|
13
8
|
openTimeoutMs?: number;
|
|
14
9
|
/**
|
|
15
|
-
* Runs
|
|
16
|
-
*
|
|
10
|
+
* Runs once the Tenant is open, at start or on a later retry: the Host starts what needs the
|
|
11
|
+
* Tenant's id (its stream relay). A failure is logged; the Tenant stays open.
|
|
17
12
|
*/
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
* Durable Streams basin, its armed sweep). A failure is logged; the Tenant stays deleted.
|
|
22
|
-
*/
|
|
23
|
-
onDeleted?: (tenantId: string) => Promise<void>;
|
|
13
|
+
onOpen?: (handle: TenantHandle) => void | Promise<void>;
|
|
14
|
+
/** This process's stream relay, for the Host aggregate (a Host with S2 runs one). */
|
|
15
|
+
relayStatus?: () => Promise<StreamRelayStatus>;
|
|
24
16
|
}
|
|
25
17
|
/**
|
|
26
|
-
* Deep Tenant module (§8):
|
|
27
|
-
* injected `TenantStore`.
|
|
18
|
+
* Deep Tenant module (§8): the Host's one Tenant over an injected opener (tenancy.md §5).
|
|
28
19
|
*
|
|
29
|
-
*
|
|
30
|
-
* Tenant (
|
|
31
|
-
* the
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* tries again. A Tenant being deleted resolves as not found, so a late request or Worker
|
|
35
|
-
* call cannot reopen it while its storage is removed.
|
|
20
|
+
* `start` opens it once, even when requests or Worker calls ask at the same time. An open that
|
|
21
|
+
* fails for a reason in the Tenant (a `TenantOpenError`, or a timeout) is kept as the Tenant's
|
|
22
|
+
* cause: the Host is not ready, `/v1/admin/status` names it, requests get the opaque 404.
|
|
23
|
+
* One that fails outside the Tenant (`TenantUnavailableError`) is retried in the background
|
|
24
|
+
* with backoff, and by the next request, which gets 503 meanwhile.
|
|
36
25
|
*/
|
|
37
26
|
export declare function createTenantModule(options: CreateTenantModuleOptions): TenantModule;
|
|
38
|
-
export {
|
|
27
|
+
export { TenantUnavailableError };
|
package/dist/tenant/module.js
CHANGED
|
@@ -1,322 +1,176 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { POSTGRES_SCHEMA_VERSION } from "../store/postgres/migrations/index.js";
|
|
3
|
-
import { envelopeNow } from "./envelope.js";
|
|
1
|
+
import { openError, TenantOpenError } from "./cause.js";
|
|
4
2
|
import { TimeoutError, withTimeout } from "./pool.js";
|
|
5
|
-
import {
|
|
6
|
-
import { TenantNotFoundError, TenantUnavailableError, } from "./types.js";
|
|
3
|
+
import { TenantUnavailableError, } from "./types.js";
|
|
7
4
|
const OPEN_TIMEOUT_MS = 30_000;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
summary.pendingActions > 0);
|
|
12
|
-
}
|
|
13
|
-
function toAdmin(id, entry, envelope) {
|
|
14
|
-
if (entry?.kind === "open") {
|
|
15
|
-
return {
|
|
16
|
-
id,
|
|
17
|
-
name: entry.handle.envelope.name,
|
|
18
|
-
state: "open",
|
|
19
|
-
envelope: entry.handle.envelope,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
return {
|
|
23
|
-
id,
|
|
24
|
-
name: envelope?.name ?? null,
|
|
25
|
-
state: entry ? "quarantined" : "open",
|
|
26
|
-
envelope,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
/** The quarantine an open failure carries, from either quarantine error type. */
|
|
30
|
-
function quarantineOf(error) {
|
|
31
|
-
const carried = error?.quarantine;
|
|
32
|
-
if (carried && typeof carried === "object" && "code" in carried)
|
|
33
|
-
return carried;
|
|
34
|
-
return asQuarantine(error);
|
|
35
|
-
}
|
|
5
|
+
/** Retries of an open that failed outside the Tenant: from 1 s, doubling, at most 30 s apart. */
|
|
6
|
+
const RETRY_FIRST_MS = 1_000;
|
|
7
|
+
const RETRY_MAX_MS = 30_000;
|
|
36
8
|
/**
|
|
37
|
-
* Deep Tenant module (§8):
|
|
38
|
-
* injected `TenantStore`.
|
|
9
|
+
* Deep Tenant module (§8): the Host's one Tenant over an injected opener (tenancy.md §5).
|
|
39
10
|
*
|
|
40
|
-
*
|
|
41
|
-
* Tenant (
|
|
42
|
-
* the
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* tries again. A Tenant being deleted resolves as not found, so a late request or Worker
|
|
46
|
-
* call cannot reopen it while its storage is removed.
|
|
11
|
+
* `start` opens it once, even when requests or Worker calls ask at the same time. An open that
|
|
12
|
+
* fails for a reason in the Tenant (a `TenantOpenError`, or a timeout) is kept as the Tenant's
|
|
13
|
+
* cause: the Host is not ready, `/v1/admin/status` names it, requests get the opaque 404.
|
|
14
|
+
* One that fails outside the Tenant (`TenantUnavailableError`) is retried in the background
|
|
15
|
+
* with backoff, and by the next request, which gets 503 meanwhile.
|
|
47
16
|
*/
|
|
48
17
|
export function createTenantModule(options) {
|
|
49
|
-
const {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
let
|
|
54
|
-
|
|
55
|
-
function
|
|
56
|
-
const entry = { kind: "quarantined", quarantine: q };
|
|
57
|
-
entries.set(id, entry);
|
|
58
|
-
logger.warn("tenant quarantined", {
|
|
59
|
-
tenantId: id,
|
|
60
|
-
code: q.code,
|
|
61
|
-
repair: q.repair,
|
|
62
|
-
});
|
|
63
|
-
return entry;
|
|
64
|
-
}
|
|
65
|
-
async function openEntry(id) {
|
|
18
|
+
const { logger, openTimeoutMs = OPEN_TIMEOUT_MS } = options;
|
|
19
|
+
let state = { kind: "idle" };
|
|
20
|
+
let opening;
|
|
21
|
+
let retry;
|
|
22
|
+
let nextDelayMs = RETRY_FIRST_MS;
|
|
23
|
+
const current = () => state;
|
|
24
|
+
async function openOnce() {
|
|
66
25
|
let handle;
|
|
67
26
|
try {
|
|
68
|
-
handle = await withTimeout(
|
|
27
|
+
handle = await withTimeout(options.open(), openTimeoutMs, (late) => {
|
|
69
28
|
void late.then((h) => h.close()).catch(() => undefined);
|
|
70
29
|
});
|
|
71
30
|
}
|
|
72
31
|
catch (error) {
|
|
73
|
-
if (
|
|
74
|
-
return
|
|
75
|
-
if (closed || deleting.has(id))
|
|
76
|
-
return undefined;
|
|
32
|
+
if (current().kind === "closed")
|
|
33
|
+
return;
|
|
77
34
|
if (error instanceof TenantUnavailableError) {
|
|
78
|
-
|
|
35
|
+
state = { kind: "unavailable", error };
|
|
79
36
|
logger.warn("tenant unavailable", {
|
|
80
|
-
tenantId: id,
|
|
81
37
|
message: error.cause instanceof Error ? error.cause.message : String(error.cause),
|
|
38
|
+
retryMs: nextDelayMs,
|
|
82
39
|
});
|
|
83
|
-
|
|
40
|
+
scheduleRetry();
|
|
41
|
+
return;
|
|
84
42
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
43
|
+
const failure = error instanceof TenantOpenError
|
|
44
|
+
? error
|
|
45
|
+
: error instanceof TimeoutError
|
|
46
|
+
? openError("open-timeout", `Opening the Tenant timed out after ${openTimeoutMs}ms`)
|
|
47
|
+
: openError("open-failed", error instanceof Error ? error.message : "Opening the Tenant failed");
|
|
48
|
+
const failed = {
|
|
49
|
+
kind: "failed",
|
|
50
|
+
cause: failure.toCause(),
|
|
51
|
+
envelope: failure.envelope ?? null,
|
|
52
|
+
};
|
|
53
|
+
state = failed;
|
|
54
|
+
logger.error("tenant not opened", {
|
|
55
|
+
...(failed.envelope ? { tenantId: failed.envelope.id } : {}),
|
|
56
|
+
code: failed.cause.code,
|
|
57
|
+
message: failed.cause.message,
|
|
58
|
+
repair: failed.cause.repair,
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
91
61
|
}
|
|
92
|
-
if (
|
|
62
|
+
if (current().kind === "closed") {
|
|
93
63
|
await handle.close().catch(() => undefined);
|
|
94
|
-
return
|
|
64
|
+
return;
|
|
95
65
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
async function load(id) {
|
|
102
|
-
if (deleting.has(id))
|
|
103
|
-
return undefined;
|
|
104
|
-
const cached = entries.get(id);
|
|
105
|
-
if (cached)
|
|
106
|
-
return cached;
|
|
107
|
-
if (closed)
|
|
108
|
-
return undefined;
|
|
109
|
-
let pending = opening.get(id);
|
|
110
|
-
if (!pending) {
|
|
111
|
-
pending = openEntry(id).finally(() => opening.delete(id));
|
|
112
|
-
opening.set(id, pending);
|
|
66
|
+
state = { kind: "open", handle };
|
|
67
|
+
nextDelayMs = RETRY_FIRST_MS;
|
|
68
|
+
logger.info("tenant opened", { tenantId: handle.envelope.id });
|
|
69
|
+
try {
|
|
70
|
+
await options.onOpen?.(handle);
|
|
113
71
|
}
|
|
114
|
-
|
|
72
|
+
catch (error) {
|
|
73
|
+
logger.warn("tenant open step failed", {
|
|
74
|
+
tenantId: handle.envelope.id,
|
|
75
|
+
message: error instanceof Error ? error.message : String(error),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Opens the Tenant unless it is open, failed or closed; one open at a time. */
|
|
80
|
+
function load() {
|
|
81
|
+
const { kind } = current();
|
|
82
|
+
if (kind !== "idle" && kind !== "unavailable")
|
|
83
|
+
return Promise.resolve();
|
|
84
|
+
clearTimeout(retry);
|
|
85
|
+
retry = undefined;
|
|
86
|
+
opening ??= openOnce().finally(() => {
|
|
87
|
+
opening = undefined;
|
|
88
|
+
});
|
|
89
|
+
return opening;
|
|
90
|
+
}
|
|
91
|
+
function scheduleRetry() {
|
|
92
|
+
if (retry)
|
|
93
|
+
return;
|
|
94
|
+
const delayMs = nextDelayMs;
|
|
95
|
+
nextDelayMs = Math.min(delayMs * 2, RETRY_MAX_MS);
|
|
96
|
+
retry = setTimeout(() => {
|
|
97
|
+
retry = undefined;
|
|
98
|
+
void load();
|
|
99
|
+
}, delayMs);
|
|
100
|
+
retry.unref();
|
|
115
101
|
}
|
|
116
|
-
const openEntries = () => [...entries.values()].filter((e) => e.kind === "open");
|
|
117
102
|
const module = {
|
|
118
|
-
get
|
|
119
|
-
return
|
|
103
|
+
get ready() {
|
|
104
|
+
return current().kind === "open";
|
|
120
105
|
},
|
|
121
106
|
async start() {
|
|
122
|
-
if (closed)
|
|
107
|
+
if (current().kind === "closed")
|
|
123
108
|
throw new Error("Tenant module is closed");
|
|
124
|
-
|
|
125
|
-
started = true;
|
|
126
|
-
logger.info("tenant module started", { tenants: ids.length });
|
|
109
|
+
await load();
|
|
127
110
|
},
|
|
128
|
-
async resolve(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
111
|
+
async resolve() {
|
|
112
|
+
await load();
|
|
113
|
+
const now = current();
|
|
114
|
+
switch (now.kind) {
|
|
115
|
+
case "open":
|
|
116
|
+
return { kind: "open", handle: now.handle };
|
|
117
|
+
case "failed":
|
|
118
|
+
return { kind: "unavailable", cause: now.cause };
|
|
119
|
+
case "unavailable":
|
|
120
|
+
throw now.error;
|
|
121
|
+
default:
|
|
122
|
+
return { kind: "unavailable" };
|
|
123
|
+
}
|
|
137
124
|
},
|
|
138
125
|
async worker(id) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
126
|
+
await load();
|
|
127
|
+
const now = current();
|
|
128
|
+
return now.kind === "open" && now.handle.envelope.id === id
|
|
129
|
+
? now.handle.worker
|
|
130
|
+
: undefined;
|
|
143
131
|
},
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
credentialHash: input.credentialHash,
|
|
151
|
-
idempotencyKey: input.idempotencyKey,
|
|
152
|
-
...(input.studioCredentialHash
|
|
153
|
-
? { studioCredentialHash: input.studioCredentialHash }
|
|
154
|
-
: {}),
|
|
155
|
-
...(input.derivedPrincipals?.length
|
|
156
|
-
? { derivedPrincipals: input.derivedPrincipals }
|
|
157
|
-
: {}),
|
|
158
|
-
};
|
|
159
|
-
// The store records its own schema version.
|
|
160
|
-
const envelope = envelopeNow({
|
|
161
|
-
id: input.tenantId,
|
|
162
|
-
name: input.name,
|
|
163
|
-
schemaVersion: POSTGRES_SCHEMA_VERSION,
|
|
164
|
-
});
|
|
165
|
-
let outcome;
|
|
166
|
-
try {
|
|
167
|
-
outcome = await store.create(envelope, bootstrap);
|
|
168
|
-
}
|
|
169
|
-
catch (error) {
|
|
170
|
-
await store.removePartial(input.tenantId).catch(() => undefined);
|
|
171
|
-
throw error;
|
|
172
|
-
}
|
|
173
|
-
if (outcome === "exists") {
|
|
174
|
-
const matches = await store.bootstrapMatches(input.tenantId, bootstrap);
|
|
175
|
-
if (!matches)
|
|
176
|
-
throw new TenantConflictError();
|
|
177
|
-
const existing = await store.readEnvelope(input.tenantId);
|
|
178
|
-
await load(input.tenantId).catch(() => undefined);
|
|
179
|
-
return { envelope: existing, created: false };
|
|
180
|
-
}
|
|
181
|
-
await options.onCreated?.(input.tenantId).catch((error) => logger.warn("tenant create step failed; open repairs it", {
|
|
182
|
-
tenantId: input.tenantId,
|
|
183
|
-
message: error instanceof Error ? error.message : String(error),
|
|
184
|
-
}));
|
|
185
|
-
const opened = await load(input.tenantId).catch(() => undefined);
|
|
186
|
-
if (opened?.kind === "open") {
|
|
187
|
-
return { envelope: opened.handle.envelope, created: true };
|
|
188
|
-
}
|
|
189
|
-
// Created but failed to open: report the stored envelope.
|
|
190
|
-
return {
|
|
191
|
-
envelope: await store
|
|
192
|
-
.readEnvelope(input.tenantId)
|
|
193
|
-
.catch(() => envelope),
|
|
194
|
-
created: true,
|
|
195
|
-
};
|
|
196
|
-
},
|
|
197
|
-
async list() {
|
|
198
|
-
const ids = new Set(await store.enumerate());
|
|
199
|
-
for (const id of entries.keys())
|
|
200
|
-
ids.add(id);
|
|
201
|
-
const result = [];
|
|
202
|
-
for (const id of [...ids].sort()) {
|
|
203
|
-
if (!isTenantId(id) || deleting.has(id))
|
|
204
|
-
continue;
|
|
205
|
-
let entry = entries.get(id);
|
|
206
|
-
if (entry?.kind === "open") {
|
|
207
|
-
result.push(toAdmin(id, entry, null));
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
let envelope = null;
|
|
211
|
-
try {
|
|
212
|
-
envelope = await store.readEnvelope(id);
|
|
213
|
-
if (envelope.id !== id) {
|
|
214
|
-
entry = {
|
|
215
|
-
kind: "quarantined",
|
|
216
|
-
quarantine: quarantine("envelope-invalid", `Tenant envelope id ${envelope.id} does not match ${id}`, { tenantId: id }).toQuarantine(),
|
|
217
|
-
};
|
|
218
|
-
envelope = null;
|
|
219
|
-
}
|
|
132
|
+
tenant() {
|
|
133
|
+
const now = current();
|
|
134
|
+
switch (now.kind) {
|
|
135
|
+
case "open": {
|
|
136
|
+
const { envelope } = now.handle;
|
|
137
|
+
return { id: envelope.id, name: envelope.name, state: "open", envelope };
|
|
220
138
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
entry ??= {
|
|
229
|
-
kind: "quarantined",
|
|
230
|
-
quarantine: quarantineOf(error) ??
|
|
231
|
-
quarantine("envelope-invalid", "Tenant envelope is unreadable", {
|
|
232
|
-
tenantId: id,
|
|
233
|
-
}).toQuarantine(),
|
|
139
|
+
case "failed":
|
|
140
|
+
return {
|
|
141
|
+
id: now.envelope?.id ?? null,
|
|
142
|
+
name: now.envelope?.name ?? null,
|
|
143
|
+
state: "unavailable",
|
|
144
|
+
envelope: now.envelope,
|
|
145
|
+
cause: now.cause,
|
|
234
146
|
};
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
return result;
|
|
239
|
-
},
|
|
240
|
-
async status(id) {
|
|
241
|
-
if (!isTenantId(id))
|
|
242
|
-
return undefined;
|
|
243
|
-
const entry = await load(id);
|
|
244
|
-
if (!entry)
|
|
245
|
-
return undefined;
|
|
246
|
-
if (entry.kind === "open")
|
|
247
|
-
return toAdmin(id, entry, null);
|
|
248
|
-
const envelope = await store.readEnvelope(id).catch(() => null);
|
|
249
|
-
return { ...toAdmin(id, entry, envelope), quarantine: entry.quarantine };
|
|
250
|
-
},
|
|
251
|
-
async delete(id, activeWork) {
|
|
252
|
-
if (!isTenantId(id))
|
|
253
|
-
throw new Error(`Invalid tenant id: ${id}`);
|
|
254
|
-
if (deleting.has(id))
|
|
255
|
-
throw new TenantBusyError("Tenant is being deleted");
|
|
256
|
-
const entry = await load(id);
|
|
257
|
-
if (!entry)
|
|
258
|
-
throw new TenantNotFoundError(id);
|
|
259
|
-
if (entry.kind === "open") {
|
|
260
|
-
const summary = await entry.handle.summary();
|
|
261
|
-
if (activeWork === "refuse" && hasLiveWork(summary)) {
|
|
262
|
-
throw new TenantBusyError();
|
|
263
|
-
}
|
|
264
|
-
if (activeWork === "drain" || activeWork === "cancel") {
|
|
265
|
-
await entry.handle.drain(activeWork);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
deleting.add(id);
|
|
269
|
-
try {
|
|
270
|
-
entries.delete(id);
|
|
271
|
-
// Sandbox prefix cleanup is owned by TenantHandle.close().
|
|
272
|
-
if (entry.kind === "open")
|
|
273
|
-
await entry.handle.close();
|
|
274
|
-
await store.trash(id, new Date());
|
|
275
|
-
}
|
|
276
|
-
finally {
|
|
277
|
-
deleting.delete(id);
|
|
147
|
+
default:
|
|
148
|
+
return { id: null, name: null, state: "unavailable", envelope: null };
|
|
278
149
|
}
|
|
279
|
-
logger.info("tenant deleted", { tenantId: id, activeWork });
|
|
280
|
-
await options.onDeleted?.(id).catch((error) => logger.warn("tenant delete cleanup failed", {
|
|
281
|
-
tenantId: id,
|
|
282
|
-
message: error instanceof Error ? error.message : String(error),
|
|
283
|
-
}));
|
|
284
150
|
},
|
|
285
151
|
async summarize() {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
let uncertainEffects = 0;
|
|
290
|
-
let outboxDepth;
|
|
291
|
-
let relayLagMs;
|
|
292
|
-
for (const s of await Promise.all(openEntries().map((e) => e.handle.summary()))) {
|
|
293
|
-
runningSessions += s.runningSessions;
|
|
294
|
-
connectedExecutors += s.connectedExecutors;
|
|
295
|
-
pendingActions += s.pendingActions;
|
|
296
|
-
uncertainEffects += s.uncertainEffects;
|
|
297
|
-
if (s.outboxDepth !== undefined)
|
|
298
|
-
outboxDepth = (outboxDepth ?? 0) + s.outboxDepth;
|
|
299
|
-
if (s.relayLagMs !== undefined)
|
|
300
|
-
relayLagMs = Math.max(relayLagMs ?? 0, s.relayLagMs);
|
|
301
|
-
}
|
|
152
|
+
const now = current();
|
|
153
|
+
const summary = now.kind === "open" ? await now.handle.summary() : undefined;
|
|
154
|
+
const relay = await options.relayStatus?.().catch(() => undefined);
|
|
302
155
|
return {
|
|
303
|
-
runningSessions,
|
|
304
|
-
|
|
305
|
-
pendingActions,
|
|
306
|
-
uncertainEffects,
|
|
307
|
-
...(
|
|
308
|
-
...(relayLagMs !== undefined ? { relayLagMs } : {}),
|
|
156
|
+
runningSessions: summary?.runningSessions ?? 0,
|
|
157
|
+
inFlightDeliveries: summary?.inFlightDeliveries ?? 0,
|
|
158
|
+
pendingActions: summary?.pendingActions ?? 0,
|
|
159
|
+
uncertainEffects: summary?.uncertainEffects ?? 0,
|
|
160
|
+
...(relay ? { relay } : {}),
|
|
309
161
|
};
|
|
310
162
|
},
|
|
311
163
|
async close() {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
await
|
|
164
|
+
const previous = current();
|
|
165
|
+
state = { kind: "closed" };
|
|
166
|
+
clearTimeout(retry);
|
|
167
|
+
retry = undefined;
|
|
168
|
+
// An open in progress sees `closed` and closes what it opened.
|
|
169
|
+
await opening?.catch(() => undefined);
|
|
170
|
+
if (previous.kind === "open")
|
|
171
|
+
await previous.handle.close().catch(() => undefined);
|
|
318
172
|
},
|
|
319
173
|
};
|
|
320
174
|
return module;
|
|
321
175
|
}
|
|
322
|
-
export {
|
|
176
|
+
export { TenantUnavailableError };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type IncomingHttpHeaders } from "node:http";
|
|
2
|
+
/** How the Host lets the Runtime call developer URLs (Host settings, `TenantConfig.delivery`). */
|
|
3
|
+
export interface OutboundPolicy {
|
|
4
|
+
/** `http` URLs as well as `https`. Default true (OSS). */
|
|
5
|
+
allowHttp?: boolean;
|
|
6
|
+
/** Private, loopback and link-local addresses. Default `allow` (OSS); Cloud refuses. */
|
|
7
|
+
privateAddresses?: "allow" | "refuse";
|
|
8
|
+
/**
|
|
9
|
+
* `docker-host`: the Runtime runs in the local stack's container, so `localhost`,
|
|
10
|
+
* `127.0.0.1` and `[::1]` mean the machine that runs Docker (`host.docker.internal`).
|
|
11
|
+
*/
|
|
12
|
+
loopback?: "docker-host";
|
|
13
|
+
}
|
|
14
|
+
export type OutboundResult = {
|
|
15
|
+
kind: "response";
|
|
16
|
+
status: number;
|
|
17
|
+
headers: IncomingHttpHeaders;
|
|
18
|
+
body: Buffer;
|
|
19
|
+
}
|
|
20
|
+
/** The answer was larger than `maxResponseBytes`. */
|
|
21
|
+
| {
|
|
22
|
+
kind: "too_large";
|
|
23
|
+
status: number;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "not_sent";
|
|
26
|
+
code: string;
|
|
27
|
+
message: string;
|
|
28
|
+
} | {
|
|
29
|
+
kind: "lost";
|
|
30
|
+
code: string;
|
|
31
|
+
message: string;
|
|
32
|
+
};
|
|
33
|
+
/** The largest answer read from an endpoint. */
|
|
34
|
+
export declare const MAX_RESPONSE_BYTES: number;
|
|
35
|
+
/** True when `address` is private, loopback, link-local or unspecified (IPv4-mapped included). */
|
|
36
|
+
export declare function isPrivateAddress(address: string): boolean;
|
|
37
|
+
/** Why the Host refuses `url`, or `undefined` when it may be called. */
|
|
38
|
+
export declare function refusal(url: URL, policy: OutboundPolicy): string | undefined;
|
|
39
|
+
/** POSTs `body` to `url` under `policy`. Never throws; `signal` aborts the request. */
|
|
40
|
+
export declare function post(target: string, body: string, headers: Record<string, string>, options: {
|
|
41
|
+
policy: OutboundPolicy;
|
|
42
|
+
signal: AbortSignal;
|
|
43
|
+
maxResponseBytes?: number;
|
|
44
|
+
}): Promise<OutboundResult>;
|