@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 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 (pglite-backing.ts: PGlite for postgres, the
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.VOLTER_WORLD_POSTGRES_BIN;
254
+ const binaries = nativePostgresTools(env);
229
255
  if (!binaries)
230
- throw new Error('native postgres requires VOLTER_WORLD_POSTGRES_BIN (an installed PostgreSQL tool directory)');
231
- for (const name of ['initdb', 'postgres', 'createdb', 'psql']) {
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; Postgres is selected
4
- // when no container runtime is available (or by VOLTER_WORLD_INFRA_BACKING=pglite). The
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.72",
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.72",
73
- "@volter/world-console": "3.0.67"
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.72",
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 (pglite-backing.ts: PGlite for postgres, the
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.VOLTER_WORLD_POSTGRES_BIN;
189
- if (!binaries) throw new Error('native postgres requires VOLTER_WORLD_POSTGRES_BIN (an installed PostgreSQL tool directory)');
190
- for (const name of ['initdb', 'postgres', 'createdb', 'psql']) {
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);
@@ -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; Postgres is selected
4
- // when no container runtime is available (or by VOLTER_WORLD_INFRA_BACKING=pglite). The
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