@volter/world-runtime 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/dist/known-external-services.json +1108 -0
- package/dist/src/ancestry.d.ts +2 -0
- package/dist/src/ancestry.js +42 -0
- package/dist/src/app-url.d.ts +47 -0
- package/dist/src/app-url.js +239 -0
- package/dist/src/attach.d.ts +48 -0
- package/dist/src/attach.js +87 -0
- package/dist/src/branch.d.ts +20 -0
- package/dist/src/branch.js +65 -0
- package/dist/src/browser-proxy-cli.d.ts +2 -0
- package/dist/src/browser-proxy-cli.js +41 -0
- package/dist/src/ca-trust.d.ts +5 -0
- package/dist/src/ca-trust.js +64 -0
- package/dist/src/catalog.d.ts +31 -0
- package/dist/src/catalog.js +148 -0
- package/dist/src/changeset.d.ts +142 -0
- package/dist/src/changeset.js +570 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +1262 -0
- package/dist/src/command-lifetime.d.ts +15 -0
- package/dist/src/command-lifetime.js +98 -0
- package/dist/src/configs.d.ts +18 -0
- package/dist/src/configs.js +119 -0
- package/dist/src/console-apart.d.ts +38 -0
- package/dist/src/console-apart.js +107 -0
- package/dist/src/consumers.d.ts +46 -0
- package/dist/src/consumers.js +200 -0
- package/dist/src/covers.d.ts +183 -0
- package/dist/src/covers.js +800 -0
- package/dist/src/fixture-env.d.ts +42 -0
- package/dist/src/fixture-env.js +221 -0
- package/dist/src/host-cli.d.ts +2 -0
- package/dist/src/host-cli.js +92 -0
- package/dist/src/host-fault-fixture.d.ts +32 -0
- package/dist/src/host-fault-fixture.js +100 -0
- package/dist/src/host-worker.d.ts +1 -0
- package/dist/src/host-worker.js +23 -0
- package/dist/src/host.d.ts +38 -0
- package/dist/src/host.js +135 -0
- package/dist/src/index.d.ts +48 -0
- package/dist/src/index.js +35 -0
- package/dist/src/infra-cli.d.ts +2 -0
- package/dist/src/infra-cli.js +136 -0
- package/dist/src/init.d.ts +227 -0
- package/dist/src/init.js +1117 -0
- package/dist/src/inject-map.d.ts +34 -0
- package/dist/src/inject-map.js +56 -0
- package/dist/src/lifecycle-record.d.ts +47 -0
- package/dist/src/lifecycle-record.js +196 -0
- package/dist/src/origin.d.ts +31 -0
- package/dist/src/origin.js +139 -0
- package/dist/src/pack-facts.d.ts +75 -0
- package/dist/src/pack-facts.js +98 -0
- package/dist/src/pglite-backing.d.ts +21 -0
- package/dist/src/pglite-backing.js +158 -0
- package/dist/src/pglite-host.mjs +147 -0
- package/dist/src/placeholder.d.ts +20 -0
- package/dist/src/placeholder.js +100 -0
- package/dist/src/prerequisites.d.ts +21 -0
- package/dist/src/prerequisites.js +49 -0
- package/dist/src/process-groups.d.ts +4 -0
- package/dist/src/process-groups.js +49 -0
- package/dist/src/project-inspect.d.ts +109 -0
- package/dist/src/project-inspect.js +827 -0
- package/dist/src/proxy-daemon.d.ts +2 -0
- package/dist/src/proxy-daemon.js +18 -0
- package/dist/src/redirect-proxy.d.ts +105 -0
- package/dist/src/redirect-proxy.js +665 -0
- package/dist/src/reflect.d.ts +74 -0
- package/dist/src/reflect.js +392 -0
- package/dist/src/resources.d.ts +26 -0
- package/dist/src/resources.js +22 -0
- package/dist/src/root.d.ts +114 -0
- package/dist/src/root.js +312 -0
- package/dist/src/run-task-worker.d.ts +1 -0
- package/dist/src/run-task-worker.js +38 -0
- package/dist/src/run-task.d.ts +18 -0
- package/dist/src/run-task.js +48 -0
- package/dist/src/runtime-test-support.d.ts +59 -0
- package/dist/src/runtime-test-support.js +205 -0
- package/dist/src/runtime.d.ts +256 -0
- package/dist/src/runtime.js +3502 -0
- package/dist/src/schema.d.ts +449 -0
- package/dist/src/schema.js +605 -0
- package/dist/src/serve.d.ts +30 -0
- package/dist/src/serve.js +82 -0
- package/dist/src/served-world.d.ts +194 -0
- package/dist/src/served-world.js +986 -0
- package/dist/src/service-exit.d.ts +46 -0
- package/dist/src/service-exit.js +195 -0
- package/dist/src/service-recorder.d.ts +1 -0
- package/dist/src/service-recorder.js +121 -0
- package/dist/src/sibling.d.ts +1 -0
- package/dist/src/sibling.js +9 -0
- package/dist/src/signals.d.ts +1 -0
- package/dist/src/signals.js +11 -0
- package/dist/src/storage-capacity.d.ts +8 -0
- package/dist/src/storage-capacity.js +61 -0
- package/dist/src/tail.d.ts +30 -0
- package/dist/src/tail.js +160 -0
- package/dist/src/tcp-port.d.ts +2 -0
- package/dist/src/tcp-port.js +36 -0
- package/dist/src/up-task-worker.d.ts +1 -0
- package/dist/src/up-task-worker.js +61 -0
- package/dist/src/up-task.d.ts +17 -0
- package/dist/src/up-task.js +49 -0
- package/dist/src/websocket-relay.d.ts +3 -0
- package/dist/src/websocket-relay.js +40 -0
- package/known-external-services.json +1108 -0
- package/package.json +83 -0
- package/src/ancestry.ts +36 -0
- package/src/app-url.ts +253 -0
- package/src/attach.ts +117 -0
- package/src/branch.ts +63 -0
- package/src/browser-proxy-cli.ts +44 -0
- package/src/ca-trust.ts +57 -0
- package/src/catalog.ts +156 -0
- package/src/changeset.ts +627 -0
- package/src/cli.ts +1111 -0
- package/src/command-lifetime.ts +79 -0
- package/src/configs.ts +110 -0
- package/src/console-apart.ts +90 -0
- package/src/consumers.ts +185 -0
- package/src/covers.ts +934 -0
- package/src/fixture-env.ts +230 -0
- package/src/host-cli.ts +90 -0
- package/src/host-worker.ts +23 -0
- package/src/host.ts +169 -0
- package/src/index.ts +171 -0
- package/src/infra-cli.ts +133 -0
- package/src/init.ts +1316 -0
- package/src/inject-map.ts +72 -0
- package/src/lifecycle-record.ts +168 -0
- package/src/origin.ts +134 -0
- package/src/pack-facts.ts +128 -0
- package/src/pglite-backing.ts +141 -0
- package/src/pglite-host.mjs +147 -0
- package/src/placeholder.ts +89 -0
- package/src/prerequisites.ts +66 -0
- package/src/process-groups.ts +33 -0
- package/src/project-inspect.ts +770 -0
- package/src/proxy-daemon.ts +21 -0
- package/src/redirect-proxy.ts +684 -0
- package/src/reflect.ts +440 -0
- package/src/resources.ts +22 -0
- package/src/root.ts +290 -0
- package/src/run-task-worker.ts +27 -0
- package/src/run-task.ts +44 -0
- package/src/runtime-test-support.ts +208 -0
- package/src/runtime.ts +3357 -0
- package/src/schema.ts +922 -0
- package/src/serve.ts +102 -0
- package/src/served-world.ts +812 -0
- package/src/service-exit.ts +175 -0
- package/src/service-recorder.ts +89 -0
- package/src/sibling.ts +10 -0
- package/src/signals.ts +10 -0
- package/src/storage-capacity.ts +60 -0
- package/src/tail.ts +205 -0
- package/src/tcp-port.ts +35 -0
- package/src/up-task-worker.ts +40 -0
- package/src/up-task.ts +45 -0
- package/src/websocket-relay.ts +32 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createServer } from 'node:net';
|
|
2
|
+
/** Select a port and exclude it from later selections. The probe closes before returning. */
|
|
3
|
+
export async function allocatePort(excluded = new Set()) {
|
|
4
|
+
for (let attempt = 0; attempt < 32; attempt++) {
|
|
5
|
+
let port;
|
|
6
|
+
try {
|
|
7
|
+
port = await probePort();
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
if (error.code === 'EADDRINUSE')
|
|
11
|
+
continue;
|
|
12
|
+
throw error;
|
|
13
|
+
}
|
|
14
|
+
if (excluded.has(port))
|
|
15
|
+
continue;
|
|
16
|
+
excluded.add(port);
|
|
17
|
+
return port;
|
|
18
|
+
}
|
|
19
|
+
throw new Error('Could not allocate a TCP port after 32 attempts');
|
|
20
|
+
}
|
|
21
|
+
function probePort() {
|
|
22
|
+
return new Promise((resolvePort, reject) => {
|
|
23
|
+
const server = createServer();
|
|
24
|
+
server.once('error', reject);
|
|
25
|
+
// Match serveHttp's default scope; a loopback-only probe can miss a wildcard listener.
|
|
26
|
+
server.listen(0, '0.0.0.0', () => {
|
|
27
|
+
const address = server.address();
|
|
28
|
+
if (!address || typeof address === 'string') {
|
|
29
|
+
server.close(() => reject(new Error('Could not allocate a TCP port')));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const port = address.port;
|
|
33
|
+
server.close((error) => error ? reject(error) : resolvePort(port));
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { recordWorldLifecycleEvent } from "./lifecycle-record.js";
|
|
2
|
+
import { upWorld } from "./runtime.js";
|
|
3
|
+
import { loadWorldConfig } from "./configs.js";
|
|
4
|
+
const worldNameOf = (configId, root) => { try {
|
|
5
|
+
return loadWorldConfig(configId, root).config.id;
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return configId;
|
|
9
|
+
} };
|
|
10
|
+
// The boot's owner (./up-task.ts). Only the caller's explicit cancel aborts the boot; a lost caller does not, and the
|
|
11
|
+
// boot's outcome, which the caller can no longer print, goes to the lifecycle log.
|
|
12
|
+
if (!process.send)
|
|
13
|
+
throw new Error('World boot owner requires its private lifetime channel');
|
|
14
|
+
const abort = new AbortController();
|
|
15
|
+
let started = false;
|
|
16
|
+
let callerLost = false;
|
|
17
|
+
process.on('disconnect', () => {
|
|
18
|
+
callerLost = true;
|
|
19
|
+
if (!started)
|
|
20
|
+
process.exit(0);
|
|
21
|
+
});
|
|
22
|
+
// A dead caller may also close the output reader. Reporting must not kill the boot.
|
|
23
|
+
process.stdout.on('error', () => { });
|
|
24
|
+
process.stderr.on('error', () => { });
|
|
25
|
+
process.on('message', async (message) => {
|
|
26
|
+
const request = message;
|
|
27
|
+
if (request.type === 'cancel') {
|
|
28
|
+
abort.abort(request.signal);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (request.type !== 'start' || started)
|
|
32
|
+
return;
|
|
33
|
+
started = true;
|
|
34
|
+
let reply;
|
|
35
|
+
try {
|
|
36
|
+
reply = { instance: await upWorld(request.configId, request.options, abort.signal) };
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
reply = { error: error instanceof Error ? error.message : String(error) };
|
|
40
|
+
}
|
|
41
|
+
if (callerLost && 'instance' in reply) {
|
|
42
|
+
try {
|
|
43
|
+
recordWorldLifecycleEvent(reply.instance.root, reply.instance.name, 'boot completed after its caller went away; the World is up');
|
|
44
|
+
}
|
|
45
|
+
catch { /* the World is up regardless */ }
|
|
46
|
+
}
|
|
47
|
+
if (callerLost && 'error' in reply) {
|
|
48
|
+
// The caller that would have printed the reason is gone; the lifecycle log keeps it (upWorld also records its own
|
|
49
|
+
// boot failure there, but a failure before the lifecycle claim would otherwise leave nothing).
|
|
50
|
+
try {
|
|
51
|
+
recordWorldLifecycleEvent(request.options.root ?? process.cwd(), request.options.name ?? worldNameOf(request.configId, request.options.root ?? process.cwd()), `boot failed after its caller went away: ${reply.error}`);
|
|
52
|
+
}
|
|
53
|
+
catch { /* nowhere left to write it */ }
|
|
54
|
+
}
|
|
55
|
+
if ('error' in reply && !process.connected)
|
|
56
|
+
process.stderr.write(`${reply.error}\n`);
|
|
57
|
+
if (process.connected)
|
|
58
|
+
process.send(reply, () => process.exit('error' in reply ? 1 : 0));
|
|
59
|
+
else
|
|
60
|
+
process.exit('error' in reply ? 1 : 0);
|
|
61
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { UpWorldOptions } from './runtime.js';
|
|
2
|
+
import type { WorldInstance } from './schema.js';
|
|
3
|
+
export type UpTaskRequest = {
|
|
4
|
+
type: 'start';
|
|
5
|
+
configId: string;
|
|
6
|
+
options: UpWorldOptions;
|
|
7
|
+
};
|
|
8
|
+
export type UpTaskReply = {
|
|
9
|
+
instance: WorldInstance;
|
|
10
|
+
} | {
|
|
11
|
+
error: string;
|
|
12
|
+
};
|
|
13
|
+
/** `up` from the CLI: the boot runs in an owner process outside the caller's process group (the shape of `run`'s
|
|
14
|
+
* task owner, ./run-task.ts), so the boot does not depend on the caller's terminal. The caller's Ctrl-C or SIGTERM
|
|
15
|
+
* cancels the boot, which rolls back; losing the caller (its terminal closed, the caller killed) does not: a World
|
|
16
|
+
* is not the caller's to keep, so the boot completes and its outcome is written to the lifecycle log. */
|
|
17
|
+
export declare function superviseWorldUp(configId: string, options: UpWorldOptions): Promise<WorldInstance>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { offSignal } from "./signals.js";
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { siblingScript } from "./sibling.js";
|
|
5
|
+
/** `up` from the CLI: the boot runs in an owner process outside the caller's process group (the shape of `run`'s
|
|
6
|
+
* task owner, ./run-task.ts), so the boot does not depend on the caller's terminal. The caller's Ctrl-C or SIGTERM
|
|
7
|
+
* cancels the boot, which rolls back; losing the caller (its terminal closed, the caller killed) does not: a World
|
|
8
|
+
* is not the caller's to keep, so the boot completes and its outcome is written to the lifecycle log. */
|
|
9
|
+
export function superviseWorldUp(configId, options) {
|
|
10
|
+
return new Promise((done, reject) => {
|
|
11
|
+
const child = spawn(process.execPath, [siblingScript(import.meta.url, 'up-task-worker')], {
|
|
12
|
+
cwd: process.cwd(), env: process.env, detached: process.platform !== 'win32',
|
|
13
|
+
stdio: ['ignore', 'inherit', 'inherit', 'ipc'], serialization: 'json',
|
|
14
|
+
});
|
|
15
|
+
let reply;
|
|
16
|
+
let failure;
|
|
17
|
+
const cancel = (signal) => {
|
|
18
|
+
if (child.connected)
|
|
19
|
+
child.send({ type: 'cancel', signal }, error => { if (error)
|
|
20
|
+
failure ??= error; });
|
|
21
|
+
};
|
|
22
|
+
const onInt = () => cancel('SIGINT');
|
|
23
|
+
const onTerm = () => cancel('SIGTERM');
|
|
24
|
+
process.on('SIGINT', onInt);
|
|
25
|
+
process.on('SIGTERM', onTerm);
|
|
26
|
+
child.on('error', error => { failure = error; });
|
|
27
|
+
child.on('message', message => { reply = message; });
|
|
28
|
+
child.once('spawn', () => {
|
|
29
|
+
const request = { type: 'start', configId, options: { ...options, root: resolve(options.root ?? process.cwd()) } };
|
|
30
|
+
child.send(request, error => { if (error) {
|
|
31
|
+
failure = error;
|
|
32
|
+
if (child.connected)
|
|
33
|
+
child.disconnect();
|
|
34
|
+
} });
|
|
35
|
+
});
|
|
36
|
+
child.once('close', (code, signal) => {
|
|
37
|
+
offSignal('SIGINT', onInt);
|
|
38
|
+
offSignal('SIGTERM', onTerm);
|
|
39
|
+
if (failure)
|
|
40
|
+
reject(failure);
|
|
41
|
+
else if (reply && 'error' in reply)
|
|
42
|
+
reject(new Error(reply.error));
|
|
43
|
+
else if (reply && code === 0)
|
|
44
|
+
done(reply.instance);
|
|
45
|
+
else
|
|
46
|
+
reject(new Error(`World boot owner exited without a result (${signal ?? code}); inspect \`volter-world status\` and the lifecycle log`));
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ServeHttpOptions } from '@volter/world-core';
|
|
2
|
+
/** A message relay for the World's TLS fronts. Transport state is connection-local. */
|
|
3
|
+
export declare function websocketRelay(targetFor: (request: Request) => string | undefined): NonNullable<ServeHttpOptions['upgrade']>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** A message relay for the World's TLS fronts. Transport state is connection-local. */
|
|
2
|
+
export function websocketRelay(targetFor) {
|
|
3
|
+
const sessions = new WeakMap();
|
|
4
|
+
return {
|
|
5
|
+
accepts: request => targetFor(request) !== undefined,
|
|
6
|
+
open(peer, request) {
|
|
7
|
+
const target = targetFor(request);
|
|
8
|
+
if (!target) {
|
|
9
|
+
peer.close(1011, 'no twin for request');
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const upstream = new WebSocket(target);
|
|
13
|
+
upstream.binaryType = 'arraybuffer';
|
|
14
|
+
const session = { upstream, pending: [] };
|
|
15
|
+
sessions.set(peer, session);
|
|
16
|
+
upstream.onopen = () => { for (const message of session.pending)
|
|
17
|
+
upstream.send(message); session.pending.length = 0; };
|
|
18
|
+
upstream.onmessage = event => { peer.send(typeof event.data === 'string' ? event.data : new Uint8Array(event.data)); };
|
|
19
|
+
upstream.onclose = event => { peer.close(event.code === 1006 ? 1011 : event.code, event.reason); };
|
|
20
|
+
upstream.onerror = () => { peer.close(1011, 'twin websocket error'); };
|
|
21
|
+
},
|
|
22
|
+
message(peer, data) {
|
|
23
|
+
const session = sessions.get(peer);
|
|
24
|
+
if (!session)
|
|
25
|
+
return;
|
|
26
|
+
if (session.upstream.readyState === WebSocket.OPEN)
|
|
27
|
+
session.upstream.send(data);
|
|
28
|
+
else
|
|
29
|
+
session.pending.push(data);
|
|
30
|
+
},
|
|
31
|
+
close(peer) {
|
|
32
|
+
const session = sessions.get(peer);
|
|
33
|
+
sessions.delete(peer);
|
|
34
|
+
if (session) {
|
|
35
|
+
session.pending.length = 0;
|
|
36
|
+
session.upstream.close();
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|