@nylorun/runtime 0.10.0-beta → 0.11.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 +46 -0
- package/README.md +53 -51
- package/dist/adapters/execution/restate.d.ts +130 -0
- package/dist/adapters/execution/restate.js +442 -0
- package/dist/adapters/sandbox/virtual.js +1 -1
- package/dist/adapters/streams/s2.d.ts +18 -0
- package/dist/adapters/streams/s2.js +361 -0
- package/dist/core/executors.d.ts +4 -1
- package/dist/core/executors.js +20 -1
- package/dist/core/flow-host.d.ts +93 -132
- package/dist/core/flow-host.js +282 -547
- package/dist/core/sandbox-routes.d.ts +8 -3
- package/dist/core/sandbox-routes.js +10 -10
- package/dist/execution/memory.d.ts +54 -0
- package/dist/execution/memory.js +217 -0
- package/dist/execution/types.d.ts +125 -0
- package/dist/execution/types.js +52 -0
- package/dist/host/config-for.d.ts +2 -1
- package/dist/host/config-for.js +3 -21
- package/dist/host/config.d.ts +0 -8
- package/dist/host/create-host.d.ts +36 -1
- package/dist/host/create-host.js +48 -24
- package/dist/host/execution.d.ts +61 -0
- package/dist/host/execution.js +69 -0
- package/dist/host/http.d.ts +4 -0
- package/dist/host/http.js +6 -0
- package/dist/host/index.d.ts +3 -1
- package/dist/host/index.js +2 -0
- package/dist/host/main.js +106 -47
- package/dist/host/stack-config.d.ts +72 -0
- package/dist/host/stack-config.js +193 -0
- package/dist/index.d.ts +1 -1
- package/dist/infra/database.d.ts +16 -0
- package/dist/infra/database.js +31 -0
- package/dist/infra/execution.d.ts +39 -0
- package/dist/infra/execution.js +68 -0
- package/dist/infra/index.d.ts +42 -0
- package/dist/infra/index.js +56 -0
- package/dist/infra/readiness.d.ts +33 -0
- package/dist/infra/readiness.js +53 -0
- package/dist/infra/streams.d.ts +17 -0
- package/dist/infra/streams.js +33 -0
- package/dist/infra/workers.d.ts +39 -0
- package/dist/infra/workers.js +31 -0
- package/dist/model/pi-model.d.ts +2 -2
- package/dist/model/pi-model.js +5 -5
- package/dist/sandbox/manager.d.ts +26 -8
- package/dist/sandbox/manager.js +95 -47
- package/dist/sandbox/select.d.ts +1 -1
- package/dist/sandbox/select.js +4 -5
- package/dist/sandbox/types.d.ts +2 -2
- package/dist/store/canonical.d.ts +5 -0
- package/dist/store/canonical.js +17 -0
- package/dist/store/cursor.d.ts +9 -0
- package/dist/store/cursor.js +18 -0
- package/dist/store/memory.d.ts +57 -0
- package/dist/store/memory.js +643 -0
- package/dist/store/ownership.d.ts +19 -0
- package/dist/store/ownership.js +33 -0
- package/dist/store/postgres/connect.d.ts +25 -0
- package/dist/store/postgres/connect.js +25 -0
- package/dist/store/postgres/locking.d.ts +16 -0
- package/dist/store/postgres/locking.js +22 -0
- package/dist/store/postgres/migrations/001_initial.d.ts +27 -0
- package/dist/store/postgres/migrations/001_initial.js +196 -0
- package/dist/store/postgres/migrations/index.d.ts +49 -0
- package/dist/store/postgres/migrations/index.js +75 -0
- package/dist/store/postgres/names.d.ts +11 -0
- package/dist/store/postgres/names.js +42 -0
- package/dist/store/postgres/store.d.ts +11 -0
- package/dist/store/postgres/store.js +790 -0
- package/dist/store/postgres/tenants.d.ts +90 -0
- package/dist/store/postgres/tenants.js +207 -0
- package/dist/store/types.d.ts +440 -0
- package/dist/store/types.js +10 -0
- package/dist/streams/basin.d.ts +23 -0
- package/dist/streams/basin.js +51 -0
- package/dist/streams/memory.d.ts +25 -0
- package/dist/streams/memory.js +124 -0
- package/dist/streams/relay.d.ts +37 -0
- package/dist/streams/relay.js +254 -0
- package/dist/streams/types.d.ts +115 -0
- package/dist/streams/types.js +86 -0
- package/dist/tenant/actions.d.ts +153 -0
- package/dist/tenant/actions.js +191 -0
- package/dist/tenant/advance.d.ts +15 -0
- package/dist/tenant/advance.js +439 -0
- package/dist/tenant/auth.d.ts +11 -0
- package/dist/tenant/auth.js +34 -0
- package/dist/tenant/commands.d.ts +3 -0
- package/dist/tenant/commands.js +384 -0
- package/dist/tenant/context.d.ts +140 -0
- package/dist/tenant/context.js +40 -0
- package/dist/tenant/effects.d.ts +20 -0
- package/dist/tenant/effects.js +539 -0
- package/dist/tenant/envelope.d.ts +0 -3
- package/dist/tenant/envelope.js +2 -34
- package/dist/tenant/ephemeral.d.ts +15 -4
- package/dist/tenant/ephemeral.js +106 -38
- package/dist/tenant/http.d.ts +32 -0
- package/dist/tenant/http.js +51 -0
- package/dist/tenant/live.d.ts +92 -0
- package/dist/tenant/live.js +290 -0
- package/dist/tenant/model-setting.d.ts +16 -0
- package/dist/tenant/model-setting.js +12 -0
- package/dist/tenant/module.d.ts +29 -8
- package/dist/tenant/module.js +162 -158
- package/dist/tenant/paths.d.ts +12 -8
- package/dist/tenant/paths.js +38 -12
- package/dist/tenant/principals.d.ts +9 -13
- package/dist/tenant/principals.js +31 -38
- package/dist/tenant/quarantine-error.d.ts +1 -1
- package/dist/tenant/quarantine-error.js +2 -8
- package/dist/tenant/quarantine.d.ts +0 -8
- package/dist/tenant/quarantine.js +8 -30
- package/dist/tenant/reset.d.ts +7 -6
- package/dist/tenant/reset.js +7 -51
- package/dist/tenant/routes-tenant.d.ts +12 -0
- package/dist/tenant/routes-tenant.js +126 -0
- package/dist/tenant/routes.d.ts +9 -0
- package/dist/tenant/routes.js +142 -0
- package/dist/tenant/runtime.d.ts +59 -58
- package/dist/tenant/runtime.js +243 -2201
- package/dist/tenant/scheduler.d.ts +25 -0
- package/dist/tenant/scheduler.js +84 -0
- package/dist/tenant/session.d.ts +60 -0
- package/dist/tenant/session.js +94 -0
- package/dist/tenant/sessions.d.ts +34 -0
- package/dist/tenant/sessions.js +149 -0
- package/dist/tenant/status.d.ts +13 -4
- package/dist/tenant/status.js +76 -41
- package/dist/tenant/store-memory.d.ts +9 -12
- package/dist/tenant/store-memory.js +79 -68
- package/dist/tenant/store-pg.d.ts +15 -0
- package/dist/tenant/store-pg.js +140 -0
- package/dist/tenant/streams.d.ts +126 -0
- package/dist/tenant/streams.js +419 -0
- package/dist/tenant/sweep.d.ts +15 -0
- package/dist/tenant/sweep.js +122 -0
- package/dist/tenant/types.d.ts +95 -15
- package/dist/tenant/types.js +23 -1
- package/dist/tenant/worker.d.ts +84 -0
- package/dist/tenant/worker.js +189 -0
- package/dist/vault/service.d.ts +37 -31
- package/dist/vault/service.js +439 -359
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +13 -13
- package/dist/adapters/sandbox/microsandbox.d.ts +0 -2
- package/dist/adapters/sandbox/microsandbox.js +0 -238
- package/dist/core/store.d.ts +0 -32
- package/dist/core/store.js +0 -143
- package/dist/launcher/commands.d.ts +0 -24
- package/dist/launcher/commands.js +0 -260
- package/dist/launcher/ensure-config.d.ts +0 -16
- package/dist/launcher/ensure-config.js +0 -79
- package/dist/launcher/errors.d.ts +0 -12
- package/dist/launcher/errors.js +0 -22
- package/dist/launcher/host-config.d.ts +0 -17
- package/dist/launcher/host-config.js +0 -64
- package/dist/launcher/host-state.d.ts +0 -17
- package/dist/launcher/host-state.js +0 -61
- package/dist/launcher/lifecycle.d.ts +0 -66
- package/dist/launcher/lifecycle.js +0 -407
- package/dist/launcher/locks.d.ts +0 -26
- package/dist/launcher/locks.js +0 -117
- package/dist/launcher/logs.d.ts +0 -18
- package/dist/launcher/logs.js +0 -117
- package/dist/launcher/main.d.ts +0 -2
- package/dist/launcher/main.js +0 -66
- package/dist/launcher/output.d.ts +0 -11
- package/dist/launcher/output.js +0 -68
- package/dist/launcher/paths.d.ts +0 -20
- package/dist/launcher/paths.js +0 -36
- package/dist/launcher/probe.d.ts +0 -23
- package/dist/launcher/probe.js +0 -72
- package/dist/launcher/protocol.d.ts +0 -62
- package/dist/launcher/protocol.js +0 -1
- package/dist/launcher/schema-guard.d.ts +0 -6
- package/dist/launcher/schema-guard.js +0 -36
- package/dist/launcher/spawn.d.ts +0 -22
- package/dist/launcher/spawn.js +0 -85
- package/dist/launcher/status.d.ts +0 -6
- package/dist/launcher/status.js +0 -65
- package/dist/tenant/migration.d.ts +0 -16
- package/dist/tenant/migration.js +0 -116
- package/dist/tenant/schema.d.ts +0 -23
- package/dist/tenant/schema.js +0 -151
- package/dist/tenant/store-fs.d.ts +0 -16
- package/dist/tenant/store-fs.js +0 -205
package/dist/host/create-host.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { createServer, } from "node:http";
|
|
2
2
|
import { createHash, timingSafeEqual } from "node:crypto";
|
|
3
|
-
import { unlinkSync, existsSync } from "node:fs";
|
|
4
3
|
import { checkCompatibility, HOST_PROTOCOL, isTenantId, PROTOCOL_HEADER, TENANT_HEADER, } from "@nylorun/core/compatibility";
|
|
5
4
|
import { AdminStatusSchema, CreateTenantRequestSchema, } from "@nylorun/core/contracts";
|
|
6
|
-
import { hostPaths } from "../tenant/paths.js";
|
|
7
5
|
import { TenantBusyError, TenantConflictError, } from "../tenant/quarantine.js";
|
|
6
|
+
import { TenantNotFoundError, } from "../tenant/types.js";
|
|
8
7
|
import { EXIT_NON_LOOPBACK, EXIT_PORT_IN_USE, HostListenError, isAllowedRequestHost, isJsonContentType, isLoopbackHost, readBearer, readJsonBody, redactRoutePath, requestHasBody, sendJson, sendOpaqueNotFound, sendProtocolRejected, sendRejected, } from "./http.js";
|
|
9
8
|
import { RUNTIME_VERSION } from "../version.js";
|
|
10
9
|
function hashUtf8(value) {
|
|
@@ -41,15 +40,17 @@ function protocolAccepted(raw) {
|
|
|
41
40
|
export function createHost(options) {
|
|
42
41
|
const { hostRoot, module, config, credentials, logger, coreVersion, } = options;
|
|
43
42
|
const pid = options.pid ?? process.pid;
|
|
44
|
-
const
|
|
43
|
+
const containerListen = options.listen;
|
|
44
|
+
const bindHost = containerListen?.host ?? config.host;
|
|
45
|
+
const bindPort = containerListen?.port ?? config.port;
|
|
45
46
|
let server;
|
|
46
47
|
let url = "";
|
|
47
|
-
let listenPort =
|
|
48
|
+
let listenPort = bindPort;
|
|
48
49
|
let closing = false;
|
|
49
50
|
let closePromise;
|
|
50
51
|
const adminStatusBody = async () => {
|
|
51
52
|
const tenants = await module.list();
|
|
52
|
-
const aggregate = module.summarize();
|
|
53
|
+
const aggregate = await module.summarize();
|
|
53
54
|
return AdminStatusSchema.parse({
|
|
54
55
|
service: "nylorun-runtime",
|
|
55
56
|
version: RUNTIME_VERSION,
|
|
@@ -62,7 +63,7 @@ export function createHost(options) {
|
|
|
62
63
|
aggregate,
|
|
63
64
|
host: {
|
|
64
65
|
hostId: config.hostId,
|
|
65
|
-
url,
|
|
66
|
+
url: options.publicUrl ?? url,
|
|
66
67
|
pid,
|
|
67
68
|
},
|
|
68
69
|
});
|
|
@@ -92,6 +93,9 @@ export function createHost(options) {
|
|
|
92
93
|
principalId: body.principalId,
|
|
93
94
|
credentialHash: body.credentialHash,
|
|
94
95
|
idempotencyKey: body.idempotencyKey,
|
|
96
|
+
...(body.studioCredentialHash
|
|
97
|
+
? { studioCredentialHash: body.studioCredentialHash }
|
|
98
|
+
: {}),
|
|
95
99
|
});
|
|
96
100
|
return sendJson(response, result.created ? 201 : 200, result.envelope);
|
|
97
101
|
}
|
|
@@ -132,6 +136,8 @@ export function createHost(options) {
|
|
|
132
136
|
return;
|
|
133
137
|
}
|
|
134
138
|
catch (error) {
|
|
139
|
+
if (error instanceof TenantNotFoundError)
|
|
140
|
+
return sendOpaqueNotFound(response);
|
|
135
141
|
const code = error.code;
|
|
136
142
|
if (error instanceof TenantBusyError ||
|
|
137
143
|
error.name === "TenantBusyError" ||
|
|
@@ -173,6 +179,9 @@ export function createHost(options) {
|
|
|
173
179
|
port: listenPort,
|
|
174
180
|
host: config.host,
|
|
175
181
|
allowNonLoopback: config.allowNonLoopback,
|
|
182
|
+
...(containerListen
|
|
183
|
+
? { allowedHosts: containerListen.allowedHosts }
|
|
184
|
+
: {}),
|
|
176
185
|
})) {
|
|
177
186
|
sendRejected(response, 421, "host_rejected", "Host header is not an allowed loopback or configured address");
|
|
178
187
|
statusCode = 421;
|
|
@@ -209,11 +218,12 @@ export function createHost(options) {
|
|
|
209
218
|
if (pathname === "/ready") {
|
|
210
219
|
const listener = Boolean(server?.listening);
|
|
211
220
|
const discovery = module.started;
|
|
212
|
-
const
|
|
221
|
+
const infra = await options.readiness?.();
|
|
222
|
+
const ready = listener && discovery && !closing && (infra?.ok ?? true);
|
|
213
223
|
sendJson(response, ready ? 200 : 503, {
|
|
214
224
|
status: ready ? "ready" : "not_ready",
|
|
215
225
|
service: "nylorun-runtime",
|
|
216
|
-
checks: { listener, discovery },
|
|
226
|
+
checks: { listener, discovery, ...infra?.checks },
|
|
217
227
|
});
|
|
218
228
|
statusCode = ready ? 200 : 503;
|
|
219
229
|
return;
|
|
@@ -262,7 +272,7 @@ export function createHost(options) {
|
|
|
262
272
|
statusCode = 426;
|
|
263
273
|
return;
|
|
264
274
|
}
|
|
265
|
-
const resolution = module.resolve(tenantId);
|
|
275
|
+
const resolution = await module.resolve(tenantId);
|
|
266
276
|
if (resolution.kind !== "open") {
|
|
267
277
|
if (resolution.kind === "quarantined") {
|
|
268
278
|
logger.warn("tenant_quarantined", {
|
|
@@ -328,7 +338,11 @@ export function createHost(options) {
|
|
|
328
338
|
async function listen() {
|
|
329
339
|
if (server)
|
|
330
340
|
throw new Error("Already listening");
|
|
331
|
-
|
|
341
|
+
// Container mode binds a non-loopback address behind an explicit Host
|
|
342
|
+
// allowlist; otherwise only loopback unless host.json allows more.
|
|
343
|
+
if (!containerListen &&
|
|
344
|
+
!config.allowNonLoopback &&
|
|
345
|
+
!isLoopbackHost(config.host)) {
|
|
332
346
|
throw new HostListenError(`Refusing to bind non-loopback host ${config.host} without allowNonLoopback`, EXIT_NON_LOOPBACK);
|
|
333
347
|
}
|
|
334
348
|
server = createServer((req, res) => {
|
|
@@ -337,17 +351,17 @@ export function createHost(options) {
|
|
|
337
351
|
await new Promise((resolve, reject) => {
|
|
338
352
|
server.once("error", (error) => {
|
|
339
353
|
if (error.code === "EADDRINUSE") {
|
|
340
|
-
reject(new HostListenError(`Port ${
|
|
354
|
+
reject(new HostListenError(`Port ${bindPort} on ${bindHost} is already in use`, EXIT_PORT_IN_USE, error));
|
|
341
355
|
return;
|
|
342
356
|
}
|
|
343
357
|
reject(error);
|
|
344
358
|
});
|
|
345
|
-
server.listen(
|
|
359
|
+
server.listen(bindPort, bindHost, resolve);
|
|
346
360
|
});
|
|
347
361
|
const address = server.address();
|
|
348
362
|
listenPort =
|
|
349
|
-
typeof address === "object" && address ? address.port :
|
|
350
|
-
url = `http://${
|
|
363
|
+
typeof address === "object" && address ? address.port : bindPort;
|
|
364
|
+
url = `http://${bindHost}:${listenPort}`;
|
|
351
365
|
await module.start();
|
|
352
366
|
logger.info("host_listening", {
|
|
353
367
|
hostId: config.hostId,
|
|
@@ -355,6 +369,21 @@ export function createHost(options) {
|
|
|
355
369
|
pid,
|
|
356
370
|
});
|
|
357
371
|
}
|
|
372
|
+
const step = async (name, run) => {
|
|
373
|
+
try {
|
|
374
|
+
await run?.();
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
logger.error("host_shutdown_step_failed", {
|
|
378
|
+
step: name,
|
|
379
|
+
error: error instanceof Error ? error.message : String(error),
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
let settleClosed;
|
|
384
|
+
const closed = new Promise((resolve) => {
|
|
385
|
+
settleClosed = resolve;
|
|
386
|
+
});
|
|
358
387
|
async function close() {
|
|
359
388
|
if (closePromise)
|
|
360
389
|
return closePromise;
|
|
@@ -368,21 +397,16 @@ export function createHost(options) {
|
|
|
368
397
|
});
|
|
369
398
|
server = undefined;
|
|
370
399
|
}
|
|
371
|
-
await
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
}
|
|
376
|
-
catch {
|
|
377
|
-
/* best-effort */
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
})();
|
|
400
|
+
await step("beforeTenants", options.shutdown?.beforeTenants);
|
|
401
|
+
await step("tenants", () => module.close());
|
|
402
|
+
await step("afterTenants", options.shutdown?.afterTenants);
|
|
403
|
+
})().finally(settleClosed);
|
|
381
404
|
return closePromise;
|
|
382
405
|
}
|
|
383
406
|
return {
|
|
384
407
|
listen,
|
|
385
408
|
close,
|
|
409
|
+
closed,
|
|
386
410
|
get url() {
|
|
387
411
|
return url;
|
|
388
412
|
},
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Host's Durable Session Execution (architecture §6, §12.3, §14.8): one per process,
|
|
3
|
+
* shared by every Tenant the Host opens.
|
|
4
|
+
*
|
|
5
|
+
* - `tenantExecution` is what the Host passes to each Tenant it opens
|
|
6
|
+
* (`TenantOpenHooks.execution`): the execution, the `TenantWorkers` registry its handlers
|
|
7
|
+
* dispatch through (with `resolve` opening a Tenant on demand when an invocation arrives for
|
|
8
|
+
* one that is not open), and the Tenant status hook for stuck invocations.
|
|
9
|
+
* - `start` serves the Worker endpoint for `--role worker|all` (`infra/workers.ts`) and does
|
|
10
|
+
* nothing for `api`, whose wakes, timers and sweep arming go through Restate's ingress.
|
|
11
|
+
* - `armAll` re-arms every Tenant's sweep at startup. Arming is idempotent, so it is safe on
|
|
12
|
+
* every start, and it is what recovers from lost Restate state: the sweep re-wakes `runnable`
|
|
13
|
+
* sessions without a live owner (§14.8).
|
|
14
|
+
* - `disarm` stops a deleted Tenant's sweep.
|
|
15
|
+
*
|
|
16
|
+
* **Order at startup.** Call `start` before opening any Tenant. Opening a Tenant arms its
|
|
17
|
+
* sweep through Restate's ingress, which answers 404 until a Worker has registered the
|
|
18
|
+
* services, so the open would fail. An api-role process relies on a Worker having registered
|
|
19
|
+
* them first.
|
|
20
|
+
*
|
|
21
|
+
* **One Worker URL.** `start` registers the Worker endpoint (`NYLORUN_WORKER_URL`) with
|
|
22
|
+
* Restate once. Restate sends new invocations to the latest registered deployment, so
|
|
23
|
+
* several Workers that each register their own URL do not share load: the last one to start
|
|
24
|
+
* takes every new invocation. The V1 stack runs a single `runtime` container with
|
|
25
|
+
* `--role all`. A deployment with several Worker processes (Helm, Cloud) must advertise one
|
|
26
|
+
* load-balanced `NYLORUN_WORKER_URL` shared by all of them.
|
|
27
|
+
*/
|
|
28
|
+
import type { DurableExecution } from "../execution/types.js";
|
|
29
|
+
import type { Logger } from "../tenant/types.js";
|
|
30
|
+
import { type TenantExecution, type TenantWorker } from "../tenant/worker.js";
|
|
31
|
+
import type { RuntimeRole } from "./stack-config.js";
|
|
32
|
+
export interface CreateHostExecutionOptions {
|
|
33
|
+
execution: DurableExecution;
|
|
34
|
+
role: RuntimeRole;
|
|
35
|
+
/** Finds or opens a Tenant an invocation arrived for that is not open on this process. */
|
|
36
|
+
resolve: (tenantId: string) => Promise<TenantWorker | undefined>;
|
|
37
|
+
logger?: Logger;
|
|
38
|
+
/**
|
|
39
|
+
* How long one advance may run before its signal aborts and it settles
|
|
40
|
+
* (`tenant/worker.ts`). Default 50 minutes, below Restate's timeouts.
|
|
41
|
+
*/
|
|
42
|
+
advanceDeadlineMs?: number;
|
|
43
|
+
/** How long an aborted advance has to return before it is abandoned. Default 30 s. */
|
|
44
|
+
advanceGraceMs?: number;
|
|
45
|
+
}
|
|
46
|
+
export interface HostExecution {
|
|
47
|
+
/** Pass to every Tenant the Host opens (`TenantOpenHooks.execution`). */
|
|
48
|
+
readonly tenantExecution: TenantExecution;
|
|
49
|
+
/** Serves the Worker endpoint for the worker and all roles. Idempotent. */
|
|
50
|
+
start(): Promise<void>;
|
|
51
|
+
/** Stops serving: aborts running advances and waits for them. Idempotent. */
|
|
52
|
+
stop(): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Arms every listed Tenant's sweep (idempotent). Tries them all; rejects with an
|
|
55
|
+
* `AggregateError` of the failures after logging each.
|
|
56
|
+
*/
|
|
57
|
+
armAll(tenantIds: readonly string[]): Promise<void>;
|
|
58
|
+
/** Stops re-arming a deleted Tenant's sweep. */
|
|
59
|
+
disarm(tenantId: string): Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
export declare function createHostExecution(options: CreateHostExecutionOptions): HostExecution;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { startWorker, stopWorker } from "../infra/workers.js";
|
|
2
|
+
import { TenantWorkers, } from "../tenant/worker.js";
|
|
3
|
+
/** How many sweep arms `armAll` sends at once. */
|
|
4
|
+
const ARM_CONCURRENCY = 16;
|
|
5
|
+
export function createHostExecution(options) {
|
|
6
|
+
const { execution, role, logger } = options;
|
|
7
|
+
const workers = new TenantWorkers({
|
|
8
|
+
resolve: options.resolve,
|
|
9
|
+
...(options.advanceDeadlineMs !== undefined
|
|
10
|
+
? { advanceDeadlineMs: options.advanceDeadlineMs }
|
|
11
|
+
: {}),
|
|
12
|
+
...(options.advanceGraceMs !== undefined
|
|
13
|
+
? { advanceGraceMs: options.advanceGraceMs }
|
|
14
|
+
: {}),
|
|
15
|
+
...(logger ? { logger } : {}),
|
|
16
|
+
});
|
|
17
|
+
const tenantExecution = {
|
|
18
|
+
execution,
|
|
19
|
+
workers,
|
|
20
|
+
...(execution.stuckInvocations
|
|
21
|
+
? {
|
|
22
|
+
stuckInvocations: (tenantId) => execution.stuckInvocations(tenantId),
|
|
23
|
+
}
|
|
24
|
+
: {}),
|
|
25
|
+
};
|
|
26
|
+
let started;
|
|
27
|
+
return {
|
|
28
|
+
tenantExecution,
|
|
29
|
+
async start() {
|
|
30
|
+
started ??= startWorker({ role, execution, handlers: workers.handlers });
|
|
31
|
+
try {
|
|
32
|
+
await started;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
started = undefined;
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
async stop() {
|
|
40
|
+
const handle = await started?.catch(() => undefined);
|
|
41
|
+
if (handle)
|
|
42
|
+
await stopWorker(handle);
|
|
43
|
+
},
|
|
44
|
+
async armAll(tenantIds) {
|
|
45
|
+
const queue = [...new Set(tenantIds)];
|
|
46
|
+
const failures = [];
|
|
47
|
+
const next = async () => {
|
|
48
|
+
for (let id = queue.shift(); id !== undefined; id = queue.shift()) {
|
|
49
|
+
try {
|
|
50
|
+
await execution.armSweep(id);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
failures.push(error);
|
|
54
|
+
logger?.warn("tenant sweep arm failed", {
|
|
55
|
+
tenantId: id,
|
|
56
|
+
message: error instanceof Error ? error.message : String(error),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
await Promise.all(Array.from({ length: Math.min(ARM_CONCURRENCY, queue.length) }, next));
|
|
62
|
+
if (failures.length > 0)
|
|
63
|
+
throw new AggregateError(failures, `Arming ${failures.length} Tenant sweep(s) failed`);
|
|
64
|
+
},
|
|
65
|
+
async disarm(tenantId) {
|
|
66
|
+
await execution.disarmSweep(tenantId);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
package/dist/host/http.d.ts
CHANGED
|
@@ -19,11 +19,15 @@ export declare function sendProtocolRejected(response: ServerResponse, protocol?
|
|
|
19
19
|
/**
|
|
20
20
|
* D§11 Host allowlist: loopback forms with the listening port, plus the
|
|
21
21
|
* configured host when `allowNonLoopback` is set.
|
|
22
|
+
*
|
|
23
|
+
* An explicit `allowedHosts` list (container mode) replaces that rule: only
|
|
24
|
+
* those exact `name:port` values are accepted.
|
|
22
25
|
*/
|
|
23
26
|
export declare function isAllowedRequestHost(hostHeader: string | undefined, options: {
|
|
24
27
|
port: number;
|
|
25
28
|
host: string;
|
|
26
29
|
allowNonLoopback?: boolean;
|
|
30
|
+
allowedHosts?: readonly string[];
|
|
27
31
|
}): boolean;
|
|
28
32
|
export declare function isJsonContentType(value: string | undefined): boolean;
|
|
29
33
|
/** True when the request carries a body (Content-Length > 0 or chunked). */
|
package/dist/host/http.js
CHANGED
|
@@ -48,11 +48,17 @@ export function sendProtocolRejected(response, protocol = HOST_PROTOCOL) {
|
|
|
48
48
|
/**
|
|
49
49
|
* D§11 Host allowlist: loopback forms with the listening port, plus the
|
|
50
50
|
* configured host when `allowNonLoopback` is set.
|
|
51
|
+
*
|
|
52
|
+
* An explicit `allowedHosts` list (container mode) replaces that rule: only
|
|
53
|
+
* those exact `name:port` values are accepted.
|
|
51
54
|
*/
|
|
52
55
|
export function isAllowedRequestHost(hostHeader, options) {
|
|
53
56
|
if (hostHeader === undefined)
|
|
54
57
|
return false;
|
|
55
58
|
const normalized = hostHeader.trim().toLowerCase();
|
|
59
|
+
if (options.allowedHosts !== undefined) {
|
|
60
|
+
return options.allowedHosts.some((entry) => entry.trim().toLowerCase() === normalized);
|
|
61
|
+
}
|
|
56
62
|
const port = String(options.port);
|
|
57
63
|
const allowed = new Set([
|
|
58
64
|
`127.0.0.1:${port}`,
|
package/dist/host/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export type { HostConfigFile, HostCredentialsFile,
|
|
1
|
+
export type { HostConfigFile, HostCredentialsFile, } from "./config.js";
|
|
2
2
|
export { baselineEnvironment, hostProcessEnvironment, tenantChildEnvironment, } from "./environment.js";
|
|
3
3
|
export { createHost, adminKeyMatches, type CreateHostOptions, type HostServer } from "./create-host.js";
|
|
4
4
|
export { createHostLogger } from "./logger.js";
|
|
5
5
|
export { configForFactory } from "./config-for.js";
|
|
6
|
+
export { createHostExecution, type CreateHostExecutionOptions, type HostExecution, } from "./execution.js";
|
|
7
|
+
export { parseStackConfig, StackConfigError, type ContainerListen, type RuntimeRole, type StackConfig, type StackEndpoints, } from "./stack-config.js";
|
|
6
8
|
export { EXIT_PORT_IN_USE, EXIT_NON_LOOPBACK, HostListenError, OPAQUE_NOT_FOUND, } from "./http.js";
|
package/dist/host/index.js
CHANGED
|
@@ -2,4 +2,6 @@ export { baselineEnvironment, hostProcessEnvironment, tenantChildEnvironment, }
|
|
|
2
2
|
export { createHost, adminKeyMatches } from "./create-host.js";
|
|
3
3
|
export { createHostLogger } from "./logger.js";
|
|
4
4
|
export { configForFactory } from "./config-for.js";
|
|
5
|
+
export { createHostExecution, } from "./execution.js";
|
|
6
|
+
export { parseStackConfig, StackConfigError, } from "./stack-config.js";
|
|
5
7
|
export { EXIT_PORT_IN_USE, EXIT_NON_LOOPBACK, HostListenError, OPAQUE_NOT_FOUND, } from "./http.js";
|
package/dist/host/main.js
CHANGED
|
@@ -1,27 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runtime Host process entry (`@nylorun/runtime/server`).
|
|
3
3
|
*
|
|
4
|
-
* Reads
|
|
5
|
-
*
|
|
4
|
+
* Reads `NYLORUN_HOME` (falls back to `~/.nylorun`), and hands an environment
|
|
5
|
+
* snapshot and argv to `parseStackConfig` (container listen mode, `--role`,
|
|
6
|
+
* stack endpoints). Absolute Host root is resolved once and passed down.
|
|
7
|
+
*
|
|
8
|
+
* Composition: `createInfra` builds the Postgres pool, Durable Session
|
|
9
|
+
* Execution and Durable Streams from the endpoints; `createHostExecution`
|
|
10
|
+
* shares one execution across the Tenants; the Tenant store is Postgres
|
|
11
|
+
* (`NYLORUN_DATABASE_URL`, required); `/ready` reports the infrastructure
|
|
12
|
+
* checks. See the startup order in `main()`. Tests compose a Host without this
|
|
13
|
+
* entry, with `createHost` and an injected Tenant module.
|
|
6
14
|
*/
|
|
7
|
-
import { readFileSync,
|
|
15
|
+
import { readFileSync, mkdirSync, existsSync } from "node:fs";
|
|
8
16
|
import { createRequire } from "node:module";
|
|
9
17
|
import { homedir } from "node:os";
|
|
10
18
|
import { resolve } from "node:path";
|
|
11
|
-
import {
|
|
12
|
-
import { hostPaths } from "../tenant/paths.js";
|
|
19
|
+
import { hostPaths, trashSqliteTenants } from "../tenant/paths.js";
|
|
13
20
|
import { createTenantModule } from "../tenant/module.js";
|
|
14
|
-
import {
|
|
21
|
+
import { createPostgresTenantStore } from "../tenant/store-pg.js";
|
|
15
22
|
import { openTenantRuntime } from "../tenant/runtime.js";
|
|
16
|
-
import {
|
|
17
|
-
import { migrateTenantDatabase } from "../tenant/schema.js";
|
|
23
|
+
import { createTenantStreams, deleteTenantStreams } from "../tenant/streams.js";
|
|
18
24
|
import { configForFactory } from "./config-for.js";
|
|
19
25
|
import { createHost } from "./create-host.js";
|
|
20
|
-
import {
|
|
26
|
+
import { createHostExecution } from "./execution.js";
|
|
27
|
+
import { baselineEnvironment } from "./environment.js";
|
|
21
28
|
import { HostListenError, EXIT_PORT_IN_USE, EXIT_NON_LOOPBACK, } from "./http.js";
|
|
22
29
|
import { createHostLogger } from "./logger.js";
|
|
23
|
-
import {
|
|
24
|
-
|
|
30
|
+
import { describeEndpoints, parseStackConfig } from "./stack-config.js";
|
|
31
|
+
import { createExecution, createInfra } from "../infra/index.js";
|
|
25
32
|
const nodeRequire = createRequire(import.meta.url);
|
|
26
33
|
function coreVersion() {
|
|
27
34
|
try {
|
|
@@ -40,51 +47,89 @@ function resolveHostRoot() {
|
|
|
40
47
|
return resolve(root);
|
|
41
48
|
}
|
|
42
49
|
export async function main() {
|
|
50
|
+
const stack = parseStackConfig(process.env, process.argv.slice(2));
|
|
43
51
|
const hostRoot = resolveHostRoot();
|
|
44
52
|
const paths = hostPaths(hostRoot);
|
|
45
53
|
mkdirSync(paths.home, { recursive: true });
|
|
46
54
|
mkdirSync(paths.tmp, { recursive: true });
|
|
47
55
|
mkdirSync(paths.tenants, { recursive: true });
|
|
48
56
|
if (!existsSync(paths.config)) {
|
|
49
|
-
throw new Error(`Missing host.json at ${paths.config}; run \`nylorun
|
|
57
|
+
throw new Error(`Missing host.json at ${paths.config}; run \`nylorun start\` first`);
|
|
50
58
|
}
|
|
51
59
|
if (!existsSync(paths.credentials)) {
|
|
52
|
-
throw new Error(`Missing host-credentials.json at ${paths.credentials}; run \`nylorun
|
|
60
|
+
throw new Error(`Missing host-credentials.json at ${paths.credentials}; run \`nylorun start\` first`);
|
|
61
|
+
}
|
|
62
|
+
// Tenants are Postgres schemas. Only a Host outside a container (a local
|
|
63
|
+
// development Host) may run without S2, on in-process streams.
|
|
64
|
+
if (!stack.endpoints.databaseUrl) {
|
|
65
|
+
throw new Error("NYLORUN_DATABASE_URL is required: the Postgres URL of the Session Store (`nylorun start` sets it)");
|
|
66
|
+
}
|
|
67
|
+
if (stack.listen && !stack.endpoints.s2Endpoint) {
|
|
68
|
+
throw new Error("NYLORUN_S2_ENDPOINT is required in container mode: the S2 endpoint of Durable Streams (`nylorun start` sets it)");
|
|
53
69
|
}
|
|
54
70
|
const config = loadJson(paths.config);
|
|
55
71
|
const credentials = loadJson(paths.credentials);
|
|
56
72
|
const logger = createHostLogger();
|
|
73
|
+
logger.info("host_stack_config", {
|
|
74
|
+
role: stack.role,
|
|
75
|
+
mode: stack.listen ? "container" : "local",
|
|
76
|
+
endpoints: describeEndpoints(stack.endpoints),
|
|
77
|
+
});
|
|
78
|
+
// Tenants from the SQLite Runtime are not migrated: move them out of the way.
|
|
79
|
+
trashSqliteTenants(hostRoot, logger);
|
|
80
|
+
const infra = createInfra(stack, { logger });
|
|
81
|
+
const database = infra.database;
|
|
82
|
+
if (!database)
|
|
83
|
+
throw new Error("NYLORUN_DATABASE_URL did not yield a Postgres pool");
|
|
57
84
|
const baseline = baselineEnvironment(process.env);
|
|
58
|
-
|
|
59
|
-
//
|
|
60
|
-
// ephemeral mode so fixture models are allowed (Tenants D10).
|
|
61
|
-
const useFixture = process.env.NYLORUN_DEV_MODEL?.trim() === "fixture";
|
|
85
|
+
// A Tenant that should answer with the fixture model carries the Tenant
|
|
86
|
+
// setting (`tenant/model-setting.ts`); the Host has no fixture mode.
|
|
62
87
|
const configFor = configForFactory({
|
|
63
88
|
hostRoot,
|
|
64
89
|
hostConfig: config,
|
|
65
90
|
logger,
|
|
66
91
|
baseline,
|
|
67
|
-
...(useFixture
|
|
68
|
-
? { mode: "ephemeral", model: { kind: "fixture" } }
|
|
69
|
-
: {}),
|
|
70
92
|
});
|
|
71
|
-
|
|
72
|
-
|
|
93
|
+
// One Durable Session Execution for every Tenant this process opens: Restate
|
|
94
|
+
// when its endpoints are set, else the in-process memory execution. An
|
|
95
|
+
// invocation for a Tenant that is not open here opens it on demand.
|
|
96
|
+
const hostExecution = createHostExecution({
|
|
97
|
+
execution: infra.execution ?? createExecution(stack),
|
|
98
|
+
role: stack.role,
|
|
99
|
+
resolve: (tenantId) => module.worker(tenantId),
|
|
100
|
+
logger,
|
|
101
|
+
});
|
|
102
|
+
// Durable Streams: S2 when configured (always in container mode). Without
|
|
103
|
+
// them (a local Host only) each Tenant keeps in-process streams, whose history
|
|
104
|
+
// does not survive a restart.
|
|
105
|
+
const streams = infra.streams;
|
|
106
|
+
const store = createPostgresTenantStore({
|
|
73
107
|
hostRoot,
|
|
74
|
-
|
|
108
|
+
sql: database,
|
|
75
109
|
configFor,
|
|
76
110
|
logger,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
111
|
+
openRuntime: (tenantConfig, opened) => openTenantRuntime(tenantConfig, {
|
|
112
|
+
execution: hostExecution.tenantExecution,
|
|
113
|
+
...(streams ? { streams } : {}),
|
|
114
|
+
...opened,
|
|
115
|
+
}),
|
|
81
116
|
});
|
|
82
117
|
const module = createTenantModule({
|
|
83
|
-
hostRoot,
|
|
84
118
|
store,
|
|
85
|
-
openRuntime,
|
|
86
|
-
configFor,
|
|
87
119
|
logger,
|
|
120
|
+
// A new Tenant's basin is created with it (opening it repairs a failure);
|
|
121
|
+
// a deleted Tenant's sweep stops re-arming and its basin goes.
|
|
122
|
+
...(streams
|
|
123
|
+
? { onCreated: (tenantId) => createTenantStreams(streams, tenantId) }
|
|
124
|
+
: {}),
|
|
125
|
+
onDeleted: async (tenantId) => {
|
|
126
|
+
const failed = (await Promise.allSettled([
|
|
127
|
+
hostExecution.disarm(tenantId),
|
|
128
|
+
...(streams ? [deleteTenantStreams(streams, tenantId)] : []),
|
|
129
|
+
])).flatMap((result) => (result.status === "rejected" ? [result.reason] : []));
|
|
130
|
+
if (failed.length > 0)
|
|
131
|
+
throw new AggregateError(failed, "Deleted Tenant cleanup failed");
|
|
132
|
+
},
|
|
88
133
|
});
|
|
89
134
|
const options = {
|
|
90
135
|
hostRoot,
|
|
@@ -93,12 +138,32 @@ export async function main() {
|
|
|
93
138
|
credentials,
|
|
94
139
|
logger,
|
|
95
140
|
coreVersion: coreVersion(),
|
|
141
|
+
...(stack.listen ? { listen: stack.listen } : {}),
|
|
142
|
+
...(stack.publicUrl ? { publicUrl: stack.publicUrl } : {}),
|
|
143
|
+
...(infra.readiness ? { readiness: infra.readiness } : {}),
|
|
144
|
+
// SIGTERM and POST /v1/admin/host/shutdown both close the Host this way:
|
|
145
|
+
// stop the Worker, close the Tenants, then end the infrastructure clients.
|
|
146
|
+
shutdown: {
|
|
147
|
+
beforeTenants: () => hostExecution.stop(),
|
|
148
|
+
afterTenants: () => infra.close(),
|
|
149
|
+
},
|
|
96
150
|
};
|
|
97
151
|
const host = createHost(options);
|
|
152
|
+
// Startup order. The Worker starts first: opening a Tenant arms its sweep
|
|
153
|
+
// through Restate's ingress, which answers 404 until a Worker has registered
|
|
154
|
+
// the services, so no Tenant may open before `start` (the listener opens
|
|
155
|
+
// Tenants on demand). An api-role process serves no Worker endpoint, so it
|
|
156
|
+
// can open Tenants only once some Worker process has registered. Container
|
|
157
|
+
// mode runs one `--role all` process, which registers here. Then the
|
|
158
|
+
// listener starts (and marks discovery done), and every listed Tenant's
|
|
159
|
+
// sweep is re-armed, which recovers wakes lost with Restate's state (§14.8).
|
|
98
160
|
try {
|
|
161
|
+
await hostExecution.start();
|
|
99
162
|
await host.listen();
|
|
100
163
|
}
|
|
101
164
|
catch (error) {
|
|
165
|
+
await hostExecution.stop().catch(() => undefined);
|
|
166
|
+
await infra.close();
|
|
102
167
|
if (error instanceof HostListenError) {
|
|
103
168
|
logger.error("listen_failed", {
|
|
104
169
|
message: error.message,
|
|
@@ -109,26 +174,20 @@ export async function main() {
|
|
|
109
174
|
}
|
|
110
175
|
throw error;
|
|
111
176
|
}
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
177
|
+
const tenants = await module.list();
|
|
178
|
+
await hostExecution
|
|
179
|
+
.armAll(tenants.filter((t) => t.state === "open").map((t) => t.id))
|
|
180
|
+
.catch((error) => logger.warn("tenant_sweeps_not_armed", {
|
|
181
|
+
message: error instanceof Error ? error.message : String(error),
|
|
182
|
+
}));
|
|
183
|
+
logger.info("host_ready", {
|
|
117
184
|
url: host.url,
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
mode: 0o600,
|
|
185
|
+
hostId: config.hostId,
|
|
186
|
+
tenants: tenants.length,
|
|
121
187
|
});
|
|
122
|
-
|
|
123
|
-
process.send?.({ type: "ready", url: host.url });
|
|
124
|
-
let closing;
|
|
125
|
-
const shutdown = () => {
|
|
126
|
-
closing ??= host.close().then(() => {
|
|
127
|
-
process.exit(0);
|
|
128
|
-
});
|
|
129
|
-
};
|
|
188
|
+
void host.closed.then(() => process.exit(0));
|
|
130
189
|
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
131
|
-
process.on(signal,
|
|
190
|
+
process.on(signal, () => void host.close());
|
|
132
191
|
}
|
|
133
192
|
}
|
|
134
193
|
void main().catch((error) => {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stack configuration for the Runtime process, parsed from an environment
|
|
3
|
+
* snapshot and argv. Pure: `host/main.ts` passes the process environment and
|
|
4
|
+
* arguments; nothing here reads ambient state.
|
|
5
|
+
*
|
|
6
|
+
* Two listen modes:
|
|
7
|
+
* - **local** (no `NYLORUN_LISTEN_*` / `NYLORUN_ALLOWED_HOSTS`): the Host binds
|
|
8
|
+
* what `host.json` names, loopback only unless `allowNonLoopback`.
|
|
9
|
+
* - **container**: the Host binds `NYLORUN_LISTEN_HOST:NYLORUN_LISTEN_PORT`
|
|
10
|
+
* (default `0.0.0.0:4000`) and accepts only the `Host` headers in
|
|
11
|
+
* `NYLORUN_ALLOWED_HOSTS`, plus the loopback forms of the listen port for
|
|
12
|
+
* probes from inside the container.
|
|
13
|
+
*
|
|
14
|
+
* The Postgres, Restate and S2 endpoints are parsed and validated here;
|
|
15
|
+
* `infra/*` builds the clients from them.
|
|
16
|
+
*/
|
|
17
|
+
export type RuntimeRole = "api" | "worker" | "all";
|
|
18
|
+
export declare const RUNTIME_ROLES: readonly RuntimeRole[];
|
|
19
|
+
export declare const DEFAULT_CONTAINER_LISTEN_HOST = "0.0.0.0";
|
|
20
|
+
export declare const DEFAULT_CONTAINER_LISTEN_PORT = 4000;
|
|
21
|
+
export interface ContainerListen {
|
|
22
|
+
/** Bind address, e.g. `0.0.0.0`. */
|
|
23
|
+
host: string;
|
|
24
|
+
port: number;
|
|
25
|
+
/**
|
|
26
|
+
* Exact `Host` header values accepted (lowercase `name:port`). Replaces the
|
|
27
|
+
* loopback-only rule.
|
|
28
|
+
*/
|
|
29
|
+
allowedHosts: readonly string[];
|
|
30
|
+
}
|
|
31
|
+
export interface StackEndpoints {
|
|
32
|
+
databaseUrl?: string;
|
|
33
|
+
restateIngressUrl?: string;
|
|
34
|
+
restateAdminUrl?: string;
|
|
35
|
+
workerUrl?: string;
|
|
36
|
+
s2Endpoint?: string;
|
|
37
|
+
s2Token?: string;
|
|
38
|
+
workspaceStoreUrl?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Restate request-identity public keys (`publickeyv1_...`) the Worker
|
|
41
|
+
* endpoint accepts, from `NYLORUN_RESTATE_IDENTITY_KEY` (comma-separated
|
|
42
|
+
* during a rotation). Unset means the endpoint accepts unsigned requests.
|
|
43
|
+
*/
|
|
44
|
+
restateIdentityKeys?: string[];
|
|
45
|
+
}
|
|
46
|
+
export interface StackConfig {
|
|
47
|
+
role: RuntimeRole;
|
|
48
|
+
/** Present in container mode; absent means bind what host.json names. */
|
|
49
|
+
listen?: ContainerListen;
|
|
50
|
+
endpoints: StackEndpoints;
|
|
51
|
+
/**
|
|
52
|
+
* The URL clients use to reach this Host (`NYLORUN_PUBLIC_URL`), reported by
|
|
53
|
+
* `/v1/admin/status`. In container mode the bind address (`0.0.0.0:4000`)
|
|
54
|
+
* means nothing outside the container.
|
|
55
|
+
*/
|
|
56
|
+
publicUrl?: string;
|
|
57
|
+
}
|
|
58
|
+
export declare class StackConfigError extends Error {
|
|
59
|
+
constructor(message: string);
|
|
60
|
+
}
|
|
61
|
+
type EnvSnapshot = Readonly<Record<string, string | undefined>>;
|
|
62
|
+
export declare function parseRole(argv: readonly string[]): RuntimeRole;
|
|
63
|
+
/**
|
|
64
|
+
* Normalize one `Host` header allowlist entry to lowercase `name:port`.
|
|
65
|
+
* IPv6 literals must be bracketed (`[::1]:4000`).
|
|
66
|
+
*/
|
|
67
|
+
export declare function normalizeAllowedHost(name: string, entry: string): string;
|
|
68
|
+
/** Parse the stack configuration; throws `StackConfigError` naming the variable. */
|
|
69
|
+
export declare function parseStackConfig(env: EnvSnapshot, argv: readonly string[]): StackConfig;
|
|
70
|
+
/** Endpoint summary for logs: which endpoints are set, never their values. */
|
|
71
|
+
export declare function describeEndpoints(endpoints: StackEndpoints): string[];
|
|
72
|
+
export {};
|