@volter/world-runtime 3.0.72 → 3.0.73
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/dist/src/infra.js +35 -3
- package/dist/src/native-postgres-backing.d.ts +5 -0
- package/dist/src/native-postgres-backing.js +31 -5
- package/dist/src/pglite-backing.js +3 -2
- package/package.json +4 -4
- package/src/infra.ts +31 -3
- package/src/native-postgres-backing.ts +24 -5
- package/src/pglite-backing.ts +3 -2
package/dist/src/infra.js
CHANGED
|
@@ -128,8 +128,8 @@ export async function runInfrastructure(phase, env, io, signal) {
|
|
|
128
128
|
// 1. VOLTER_WORLD_INFRA_BACKING=docker|pglite|native — explicit, for tests/operators;
|
|
129
129
|
// 2. a working container runtime — the compose path, byte-identical to before;
|
|
130
130
|
// 3. no container runtime + a definition of postgres and/or mongodb services —
|
|
131
|
-
// the containerless backing (
|
|
132
|
-
// MongoDB's own server for mongodb), announced loudly;
|
|
131
|
+
// the containerless backing (the machine's own PostgreSQL where it has one, else PGlite,
|
|
132
|
+
// for postgres — containerlessPostgres below; MongoDB's own server for mongodb), announced loudly;
|
|
133
133
|
// 4. otherwise the honest refusal naming what this machine cannot serve.
|
|
134
134
|
async function selectBacking() {
|
|
135
135
|
const forced = env.VOLTER_WORLD_INFRA_BACKING;
|
|
@@ -161,6 +161,33 @@ export async function runInfrastructure(phase, env, io, signal) {
|
|
|
161
161
|
const reason = classify(`${probe.stdout ?? ''}\n${probe.stderr ?? ''}`);
|
|
162
162
|
return reason === 'local execution capacity is unavailable' ? 'pglite' : 'docker';
|
|
163
163
|
}
|
|
164
|
+
// Without a container runtime a declared PostgreSQL is the machine's own wherever it has one, as MongoDB is its own
|
|
165
|
+
// mongod (architecture, managed infrastructure): retained data keeps the backing that made it, then an installed
|
|
166
|
+
// server selects native, then PGlite. A forced backing, several PostgreSQLs or an unservable kind beside it keep
|
|
167
|
+
// the selection made above.
|
|
168
|
+
async function containerlessPostgres(selected) {
|
|
169
|
+
if (selected !== 'pglite' || env.VOLTER_WORLD_INFRA_BACKING === 'pglite')
|
|
170
|
+
return selected;
|
|
171
|
+
const { unsupportedKinds } = await import("./pglite-backing.js");
|
|
172
|
+
const { nativePostgresTools } = await import("./native-postgres-backing.js");
|
|
173
|
+
const postgres = declared.filter(s => s.kind === 'postgres');
|
|
174
|
+
if (postgres.length !== 1 || unsupportedKinds(declared.filter(s => s.kind !== 'postgres' && !twinned.includes(s))).length)
|
|
175
|
+
return selected;
|
|
176
|
+
const native = existsSync(join(worldData, 'native-postgres-data'));
|
|
177
|
+
const pglite = existsSync(join(worldData, 'pglite-postgres-data'));
|
|
178
|
+
if (native && pglite) {
|
|
179
|
+
// teardown follows the recorded lifecycle, never startup's refusal
|
|
180
|
+
if (phase === 'down')
|
|
181
|
+
return existsSync(join(worldData, 'native-postgres.owner.json')) ? 'native' : 'pglite';
|
|
182
|
+
io.err('managed infrastructure: this World retains both native and PGlite PostgreSQL data; purge it before booting again\n');
|
|
183
|
+
return 1;
|
|
184
|
+
}
|
|
185
|
+
if (native)
|
|
186
|
+
return 'native';
|
|
187
|
+
if (pglite)
|
|
188
|
+
return 'pglite';
|
|
189
|
+
return nativePostgresTools(env) ? 'native' : 'pglite';
|
|
190
|
+
}
|
|
164
191
|
async function runPglite() {
|
|
165
192
|
const { unsupportedKinds, pgliteUp, pgliteStatus, pgliteDown } = await import("./pglite-backing.js");
|
|
166
193
|
const services = parseInfraDefinition(readFileSync(definition, 'utf8')).filter((s) => composed === null || composed.includes(s.kind));
|
|
@@ -175,6 +202,11 @@ export async function runInfrastructure(phase, env, io, signal) {
|
|
|
175
202
|
}
|
|
176
203
|
if (phase === 'up') {
|
|
177
204
|
io.out('managed infrastructure backing: containerless (no container runtime)\n');
|
|
205
|
+
if (services.some((s) => s.kind === 'postgres')) {
|
|
206
|
+
io.out('managed infrastructure: PostgreSQL is PGlite, one session every connection takes in turn; a read through '
|
|
207
|
+
+ 'the pool inside a transaction waits on itself until that transaction ends. A postgres server on PATH, or '
|
|
208
|
+
+ 'VOLTER_WORLD_POSTGRES_BIN, gives a fresh World independent sessions.\n');
|
|
209
|
+
}
|
|
178
210
|
try {
|
|
179
211
|
await pgliteUp(services, worldData, launch);
|
|
180
212
|
}
|
|
@@ -265,7 +297,7 @@ export async function runInfrastructure(phase, env, io, signal) {
|
|
|
265
297
|
io.out('managed infrastructure stopped\n');
|
|
266
298
|
return 0;
|
|
267
299
|
}
|
|
268
|
-
const backing = await selectBacking();
|
|
300
|
+
const backing = await containerlessPostgres(await selectBacking());
|
|
269
301
|
if (typeof backing === 'number')
|
|
270
302
|
return backing;
|
|
271
303
|
if (backing === 'pglite')
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { HostLaunch, InfraService } from './pglite-backing.js';
|
|
2
|
+
/** The PostgreSQL tool directory this machine offers, or undefined: `VOLTER_WORLD_POSTGRES_BIN` as named, else the
|
|
3
|
+
* directory of a `postgres` server on PATH, resolved through its links, holding the server's own tools beside it
|
|
4
|
+
* (libpq's client tools on PATH name no server). Read from executable files; nothing is launched or installed
|
|
5
|
+
* (architecture, managed infrastructure). */
|
|
6
|
+
export declare function nativePostgresTools(env: NodeJS.ProcessEnv): string | undefined;
|
|
2
7
|
export declare function nativePostgresUp(services: InfraService[], root: string, definition: string, launch: HostLaunch): Promise<void>;
|
|
3
8
|
export declare function nativePostgresStatus(root: string): Promise<boolean>;
|
|
4
9
|
export declare function nativePostgresDown(root: string): Promise<void>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// World-managed multi-session PostgreSQL. The declaration, data, process and teardown
|
|
2
2
|
// belong to the infrastructure service; a recipe never operates postgres itself.
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
|
-
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync } from 'node:fs';
|
|
4
|
+
import { accessSync, closeSync, constants, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from 'node:fs';
|
|
5
5
|
import { connect } from 'node:net';
|
|
6
|
-
import { join, resolve } from 'node:path';
|
|
6
|
+
import { delimiter, dirname, join, resolve } from 'node:path';
|
|
7
7
|
import { commandLifetime, COMMAND_GRACE_MS, CommandRetirementError } from "./command-lifetime.js";
|
|
8
8
|
import { survivingOwnedGroups } from "./process-groups.js";
|
|
9
9
|
import { publishProcessReceipt, readProcessReceipt, readSpawnReceipt, spawnReceiptPath, verifySpawnIdentity, signalOwnedProcess } from "./process-receipt.js";
|
|
@@ -220,15 +220,41 @@ function settings(definition) {
|
|
|
220
220
|
}
|
|
221
221
|
return { user, database };
|
|
222
222
|
}
|
|
223
|
+
const TOOLS = ['initdb', 'postgres', 'createdb', 'psql'];
|
|
224
|
+
const executable = (file) => {
|
|
225
|
+
try {
|
|
226
|
+
accessSync(file, constants.X_OK);
|
|
227
|
+
return statSync(file).isFile();
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
/** The PostgreSQL tool directory this machine offers, or undefined: `VOLTER_WORLD_POSTGRES_BIN` as named, else the
|
|
234
|
+
* directory of a `postgres` server on PATH, resolved through its links, holding the server's own tools beside it
|
|
235
|
+
* (libpq's client tools on PATH name no server). Read from executable files; nothing is launched or installed
|
|
236
|
+
* (architecture, managed infrastructure). */
|
|
237
|
+
export function nativePostgresTools(env) {
|
|
238
|
+
if (env.VOLTER_WORLD_POSTGRES_BIN)
|
|
239
|
+
return env.VOLTER_WORLD_POSTGRES_BIN;
|
|
240
|
+
for (const directory of (env.PATH ?? '').split(delimiter)) {
|
|
241
|
+
if (!directory || !executable(join(directory, 'postgres')))
|
|
242
|
+
continue;
|
|
243
|
+
const tools = dirname(realpathSync(join(directory, 'postgres')));
|
|
244
|
+
if (TOOLS.every(name => executable(join(tools, name))))
|
|
245
|
+
return tools;
|
|
246
|
+
}
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
223
249
|
export async function nativePostgresUp(services, root, definition, launch) {
|
|
224
250
|
if (services.length !== 1 || services[0].kind !== 'postgres')
|
|
225
251
|
throw new Error('native postgres requires one declared postgres service');
|
|
226
252
|
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
227
253
|
const env = { ...(launch.env ?? process.env), LC_ALL: launch.env?.LC_ALL || 'C' };
|
|
228
|
-
const binaries = env
|
|
254
|
+
const binaries = nativePostgresTools(env);
|
|
229
255
|
if (!binaries)
|
|
230
|
-
throw new Error('native postgres requires
|
|
231
|
-
for (const name of
|
|
256
|
+
throw new Error('native postgres requires an installed PostgreSQL: a postgres server on PATH, or VOLTER_WORLD_POSTGRES_BIN naming its tool directory');
|
|
257
|
+
for (const name of TOOLS) {
|
|
232
258
|
if (!existsSync(join(binaries, name)))
|
|
233
259
|
throw new Error(`native postgres tool is missing: ${join(binaries, name)}`);
|
|
234
260
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// The containerless backing for World-managed infrastructure, instead of docker
|
|
2
2
|
// compose: a POSTGRES service is a PGlite host (see pglite-host.mjs), a MONGODB
|
|
3
|
-
// service is MongoDB's own mongod (mongodb-backing.ts). MongoDB is always native;
|
|
4
|
-
// when no container runtime is available
|
|
3
|
+
// service is MongoDB's own mongod (mongodb-backing.ts). MongoDB is always native; PGlite serves Postgres
|
|
4
|
+
// when no container runtime is available and the machine has no PostgreSQL of its own (infra.ts,
|
|
5
|
+
// containerlessPostgres), or by VOLTER_WORLD_INFRA_BACKING=pglite. The
|
|
5
6
|
// world config, definition file, injected env, and declared-service contract are
|
|
6
7
|
// byte-identical either way — backing is private, exactly as the boundary comment
|
|
7
8
|
// in infra-cli.ts promises. Each service's bytes live under VOLTER_WORLD_DATA, so
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/world-runtime",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.73",
|
|
4
4
|
"description": "World configs for twins: boot named local runtimes, allocate ports, generate world.env/instance.json, and run apps against fake-key twin worlds.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"twin",
|
|
@@ -69,14 +69,14 @@
|
|
|
69
69
|
"node": ">=22.3"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
|
-
"@volter/world-core": "3.0.
|
|
73
|
-
"@volter/world-console": "3.0.
|
|
72
|
+
"@volter/world-core": "3.0.73",
|
|
73
|
+
"@volter/world-console": "3.0.68"
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"@electric-sql/pglite": "0.5.8",
|
|
77
77
|
"@electric-sql/pglite-pgvector": "0.0.9",
|
|
78
78
|
"@volter/world-access": "3.0.1",
|
|
79
|
-
"@volter/world-core": "3.0.
|
|
79
|
+
"@volter/world-core": "3.0.73",
|
|
80
80
|
"mongodb": "~6.20.0",
|
|
81
81
|
"pg-gateway": "0.3.0-beta.4",
|
|
82
82
|
"smol-toml": "^1.8.0",
|
package/src/infra.ts
CHANGED
|
@@ -115,8 +115,8 @@ export async function runInfrastructure(phase: string, env: NodeJS.ProcessEnv, i
|
|
|
115
115
|
// 1. VOLTER_WORLD_INFRA_BACKING=docker|pglite|native — explicit, for tests/operators;
|
|
116
116
|
// 2. a working container runtime — the compose path, byte-identical to before;
|
|
117
117
|
// 3. no container runtime + a definition of postgres and/or mongodb services —
|
|
118
|
-
// the containerless backing (
|
|
119
|
-
// MongoDB's own server for mongodb), announced loudly;
|
|
118
|
+
// the containerless backing (the machine's own PostgreSQL where it has one, else PGlite,
|
|
119
|
+
// for postgres — containerlessPostgres below; MongoDB's own server for mongodb), announced loudly;
|
|
120
120
|
// 4. otherwise the honest refusal naming what this machine cannot serve.
|
|
121
121
|
async function selectBacking(): Promise<'docker' | 'pglite' | 'native' | number> {
|
|
122
122
|
const forced = env.VOLTER_WORLD_INFRA_BACKING;
|
|
@@ -145,6 +145,29 @@ export async function runInfrastructure(phase: string, env: NodeJS.ProcessEnv, i
|
|
|
145
145
|
return reason === 'local execution capacity is unavailable' ? 'pglite' : 'docker';
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
// Without a container runtime a declared PostgreSQL is the machine's own wherever it has one, as MongoDB is its own
|
|
149
|
+
// mongod (architecture, managed infrastructure): retained data keeps the backing that made it, then an installed
|
|
150
|
+
// server selects native, then PGlite. A forced backing, several PostgreSQLs or an unservable kind beside it keep
|
|
151
|
+
// the selection made above.
|
|
152
|
+
async function containerlessPostgres(selected: 'docker' | 'pglite' | 'native' | number): Promise<'docker' | 'pglite' | 'native' | number> {
|
|
153
|
+
if (selected !== 'pglite' || env.VOLTER_WORLD_INFRA_BACKING === 'pglite') return selected;
|
|
154
|
+
const { unsupportedKinds } = await import('./pglite-backing.ts');
|
|
155
|
+
const { nativePostgresTools } = await import('./native-postgres-backing.ts');
|
|
156
|
+
const postgres = declared.filter(s => s.kind === 'postgres');
|
|
157
|
+
if (postgres.length !== 1 || unsupportedKinds(declared.filter(s => s.kind !== 'postgres' && !twinned.includes(s))).length) return selected;
|
|
158
|
+
const native = existsSync(join(worldData!, 'native-postgres-data'));
|
|
159
|
+
const pglite = existsSync(join(worldData!, 'pglite-postgres-data'));
|
|
160
|
+
if (native && pglite) {
|
|
161
|
+
// teardown follows the recorded lifecycle, never startup's refusal
|
|
162
|
+
if (phase === 'down') return existsSync(join(worldData!, 'native-postgres.owner.json')) ? 'native' : 'pglite';
|
|
163
|
+
io.err('managed infrastructure: this World retains both native and PGlite PostgreSQL data; purge it before booting again\n');
|
|
164
|
+
return 1;
|
|
165
|
+
}
|
|
166
|
+
if (native) return 'native';
|
|
167
|
+
if (pglite) return 'pglite';
|
|
168
|
+
return nativePostgresTools(env) ? 'native' : 'pglite';
|
|
169
|
+
}
|
|
170
|
+
|
|
148
171
|
async function runPglite(): Promise<number> {
|
|
149
172
|
const { unsupportedKinds, pgliteUp, pgliteStatus, pgliteDown } = await import('./pglite-backing.ts');
|
|
150
173
|
const services = parseInfraDefinition(readFileSync(definition, 'utf8')).filter((s) => composed === null || composed.includes(s.kind));
|
|
@@ -159,6 +182,11 @@ export async function runInfrastructure(phase: string, env: NodeJS.ProcessEnv, i
|
|
|
159
182
|
}
|
|
160
183
|
if (phase === 'up') {
|
|
161
184
|
io.out('managed infrastructure backing: containerless (no container runtime)\n');
|
|
185
|
+
if (services.some((s) => s.kind === 'postgres')) {
|
|
186
|
+
io.out('managed infrastructure: PostgreSQL is PGlite, one session every connection takes in turn; a read through '
|
|
187
|
+
+ 'the pool inside a transaction waits on itself until that transaction ends. A postgres server on PATH, or '
|
|
188
|
+
+ 'VOLTER_WORLD_POSTGRES_BIN, gives a fresh World independent sessions.\n');
|
|
189
|
+
}
|
|
162
190
|
try {
|
|
163
191
|
await pgliteUp(services, worldData!, launch);
|
|
164
192
|
} catch (error) {
|
|
@@ -240,7 +268,7 @@ export async function runInfrastructure(phase: string, env: NodeJS.ProcessEnv, i
|
|
|
240
268
|
return 0;
|
|
241
269
|
}
|
|
242
270
|
|
|
243
|
-
const backing = await selectBacking();
|
|
271
|
+
const backing = await containerlessPostgres(await selectBacking());
|
|
244
272
|
if (typeof backing === 'number') return backing;
|
|
245
273
|
if (backing === 'pglite') return runPglite();
|
|
246
274
|
if (backing === 'native') {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// World-managed multi-session PostgreSQL. The declaration, data, process and teardown
|
|
2
2
|
// belong to the infrastructure service; a recipe never operates postgres itself.
|
|
3
3
|
import { spawn, type ChildProcess } from 'node:child_process';
|
|
4
|
-
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync } from 'node:fs';
|
|
4
|
+
import { accessSync, closeSync, constants, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from 'node:fs';
|
|
5
5
|
import { connect } from 'node:net';
|
|
6
|
-
import { join, resolve } from 'node:path';
|
|
6
|
+
import { delimiter, dirname, join, resolve } from 'node:path';
|
|
7
7
|
import { commandLifetime, COMMAND_GRACE_MS, CommandRetirementError } from './command-lifetime.ts';
|
|
8
8
|
import { survivingOwnedGroups } from './process-groups.ts';
|
|
9
9
|
import { publishProcessReceipt, readProcessReceipt, readSpawnReceipt, spawnReceiptPath, verifySpawnIdentity, signalOwnedProcess, type SpawnIdentity } from './process-receipt.ts';
|
|
@@ -181,13 +181,32 @@ function settings(definition: string): { user: string; database: string } {
|
|
|
181
181
|
return { user, database };
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
const TOOLS = ['initdb', 'postgres', 'createdb', 'psql'];
|
|
185
|
+
const executable = (file: string): boolean => {
|
|
186
|
+
try { accessSync(file, constants.X_OK); return statSync(file).isFile(); } catch { return false; }
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/** The PostgreSQL tool directory this machine offers, or undefined: `VOLTER_WORLD_POSTGRES_BIN` as named, else the
|
|
190
|
+
* directory of a `postgres` server on PATH, resolved through its links, holding the server's own tools beside it
|
|
191
|
+
* (libpq's client tools on PATH name no server). Read from executable files; nothing is launched or installed
|
|
192
|
+
* (architecture, managed infrastructure). */
|
|
193
|
+
export function nativePostgresTools(env: NodeJS.ProcessEnv): string | undefined {
|
|
194
|
+
if (env.VOLTER_WORLD_POSTGRES_BIN) return env.VOLTER_WORLD_POSTGRES_BIN;
|
|
195
|
+
for (const directory of (env.PATH ?? '').split(delimiter)) {
|
|
196
|
+
if (!directory || !executable(join(directory, 'postgres'))) continue;
|
|
197
|
+
const tools = dirname(realpathSync(join(directory, 'postgres')));
|
|
198
|
+
if (TOOLS.every(name => executable(join(tools, name)))) return tools;
|
|
199
|
+
}
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
|
|
184
203
|
export async function nativePostgresUp(services: InfraService[], root: string, definition: string, launch: HostLaunch): Promise<void> {
|
|
185
204
|
if (services.length !== 1 || services[0]!.kind !== 'postgres') throw new Error('native postgres requires one declared postgres service');
|
|
186
205
|
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
187
206
|
const env: NodeJS.ProcessEnv = { ...(launch.env ?? process.env), LC_ALL: launch.env?.LC_ALL || 'C' };
|
|
188
|
-
const binaries = env
|
|
189
|
-
if (!binaries) throw new Error('native postgres requires
|
|
190
|
-
for (const name of
|
|
207
|
+
const binaries = nativePostgresTools(env);
|
|
208
|
+
if (!binaries) throw new Error('native postgres requires an installed PostgreSQL: a postgres server on PATH, or VOLTER_WORLD_POSTGRES_BIN naming its tool directory');
|
|
209
|
+
for (const name of TOOLS) {
|
|
191
210
|
if (!existsSync(join(binaries, name))) throw new Error(`native postgres tool is missing: ${join(binaries, name)}`);
|
|
192
211
|
}
|
|
193
212
|
const previous = ownership(root);
|
package/src/pglite-backing.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// The containerless backing for World-managed infrastructure, instead of docker
|
|
2
2
|
// compose: a POSTGRES service is a PGlite host (see pglite-host.mjs), a MONGODB
|
|
3
|
-
// service is MongoDB's own mongod (mongodb-backing.ts). MongoDB is always native;
|
|
4
|
-
// when no container runtime is available
|
|
3
|
+
// service is MongoDB's own mongod (mongodb-backing.ts). MongoDB is always native; PGlite serves Postgres
|
|
4
|
+
// when no container runtime is available and the machine has no PostgreSQL of its own (infra.ts,
|
|
5
|
+
// containerlessPostgres), or by VOLTER_WORLD_INFRA_BACKING=pglite. The
|
|
5
6
|
// world config, definition file, injected env, and declared-service contract are
|
|
6
7
|
// byte-identical either way — backing is private, exactly as the boundary comment
|
|
7
8
|
// in infra-cli.ts promises. Each service's bytes live under VOLTER_WORLD_DATA, so
|