@volter/world-runtime 3.0.144 → 3.0.146
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/cli-commands.js +13 -2
- package/dist/src/covers.d.ts +21 -1
- package/dist/src/covers.js +46 -4
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +1 -1
- package/dist/src/infra.js +42 -8
- package/dist/src/init.d.ts +4 -0
- package/dist/src/init.js +20 -2
- package/dist/src/native-postgres-backing.d.ts +15 -1
- package/dist/src/native-postgres-backing.js +178 -7
- package/dist/src/package-manifest.d.ts +7 -0
- package/dist/src/package-manifest.js +29 -0
- package/dist/src/pglite-backing.d.ts +85 -0
- package/dist/src/pglite-backing.js +249 -33
- package/dist/src/process-groups.mjs +25 -4
- package/dist/src/process-signal.mjs +63 -14
- package/dist/src/project-inspect.d.ts +9 -0
- package/dist/src/project-inspect.js +31 -8
- package/dist/src/runtime.js +23 -2
- package/dist/src/schema.d.ts +68 -0
- package/dist/src/schema.js +70 -4
- package/dist/src/up-cli.js +21 -1
- package/package.json +4 -4
- package/src/cli-commands.ts +10 -2
- package/src/covers.ts +53 -5
- package/src/index.ts +4 -0
- package/src/infra.ts +40 -8
- package/src/init.ts +22 -1
- package/src/native-postgres-backing.ts +175 -8
- package/src/package-manifest.ts +31 -0
- package/src/pglite-backing.ts +227 -22
- package/src/process-groups.mjs +25 -4
- package/src/process-signal.mjs +63 -14
- package/src/project-inspect.ts +38 -1
- package/src/runtime.ts +15 -1
- package/src/schema.ts +99 -5
- package/src/up-cli.ts +16 -1
|
@@ -1,14 +1,17 @@
|
|
|
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 {
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { accessSync, closeSync, constants, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
5
6
|
import { connect } from 'node:net';
|
|
7
|
+
import { runtimeManifest } from "./package-manifest.js";
|
|
6
8
|
import { delimiter, dirname, join, resolve } from 'node:path';
|
|
7
9
|
import { commandLifetime, COMMAND_GRACE_MS, CommandRetirementError } from "./command-lifetime.js";
|
|
8
10
|
import { survivingOwnedGroups } from "./process-groups.js";
|
|
9
11
|
import { publishProcessReceipt, readProcessReceipt, readSpawnReceipt, spawnReceiptPath, verifySpawnIdentity, signalOwnedProcess } from "./process-receipt.js";
|
|
10
12
|
import { signalChild, childIdentity } from './process-signal.mjs';
|
|
11
13
|
import { errorMessage } from "./error-message.js";
|
|
14
|
+
import { PREPARED_DIRECTORY, databasePrepareOf, expectedMigration, forgetStaleMigration, keepFailureEvidence, runDatabasePrepare, takePreparedCluster } from "./pglite-backing.js";
|
|
12
15
|
const starts = new Map();
|
|
13
16
|
const wait = (ms) => new Promise(done => setTimeout(done, ms));
|
|
14
17
|
const recordPath = (root) => join(root, 'native-postgres.owner.json');
|
|
@@ -126,6 +129,20 @@ async function listening(port, deadline, signal) {
|
|
|
126
129
|
abort();
|
|
127
130
|
});
|
|
128
131
|
}
|
|
132
|
+
/** The last lines of a child's stream for an error, labelled, or nothing when it said nothing. */
|
|
133
|
+
function streamTail(label, text, lines = 40) {
|
|
134
|
+
const tail = text.trim().split('\n').slice(-lines).join('\n');
|
|
135
|
+
return tail ? `\n${label}:\n${tail}` : '';
|
|
136
|
+
}
|
|
137
|
+
/** The postmaster's log as an error carries it: its last lines. A failed preparation also keeps the whole file (nativePrepare). */
|
|
138
|
+
function logTail(log) {
|
|
139
|
+
try {
|
|
140
|
+
return streamTail(`log ${log}`, readFileSync(log, 'utf8'));
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return '';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
129
146
|
/** Every helper joins exit and its private group, including spawn errors and abort. */
|
|
130
147
|
async function tool(command, args, env, deadline, root, signal) {
|
|
131
148
|
signal?.throwIfAborted();
|
|
@@ -170,8 +187,10 @@ async function tool(command, args, env, deadline, root, signal) {
|
|
|
170
187
|
throw new Error(`native postgres readiness deadline elapsed during ${command}`);
|
|
171
188
|
if (spawnError)
|
|
172
189
|
throw spawnError;
|
|
190
|
+
// Both streams, labelled: a tool's last words on stdout say how far it got (initdb's bootstrap steps) and are
|
|
191
|
+
// otherwise lost with the process.
|
|
173
192
|
if (result.code !== 0 || lifetime.forwarded)
|
|
174
|
-
throw new Error(`${command} exited ${result.code}/${result.signal}
|
|
193
|
+
throw new Error(`${command} exited ${result.code}/${result.signal}${streamTail('stderr', errorOutput)}${streamTail('stdout', output)}`);
|
|
175
194
|
return output;
|
|
176
195
|
}
|
|
177
196
|
catch (error) {
|
|
@@ -246,6 +265,141 @@ export function nativePostgresTools(env) {
|
|
|
246
265
|
}
|
|
247
266
|
return undefined;
|
|
248
267
|
}
|
|
268
|
+
/** initdb's arguments after `-D`, the same at up and at prepare, and so in the stamp. */
|
|
269
|
+
function initdbArguments(user) {
|
|
270
|
+
return [`--username=${user}`, '--auth=trust', '--no-locale', '--encoding=UTF8'];
|
|
271
|
+
}
|
|
272
|
+
/** Where a World's prepared native cluster lives, beside its world.json, and its stamp (ADR 0018's layout). */
|
|
273
|
+
export function nativePreparedCluster(worldConfig) {
|
|
274
|
+
const directory = join(dirname(worldConfig), PREPARED_DIRECTORY, 'native-postgres-data');
|
|
275
|
+
return { directory, stamp: `${directory}.json` };
|
|
276
|
+
}
|
|
277
|
+
function nativeStamp(tools, user, database, digests, migration) {
|
|
278
|
+
const manifest = runtimeManifest();
|
|
279
|
+
const programs = {};
|
|
280
|
+
for (const name of ['postgres', 'initdb']) {
|
|
281
|
+
const file = programFile(join(tools, name));
|
|
282
|
+
programs[name] = { bytes: statSync(file).size,
|
|
283
|
+
...(digests ? { sha256: createHash('sha256').update(readFileSync(file)).digest('hex') } : {}) };
|
|
284
|
+
}
|
|
285
|
+
return { runtime: manifest.version ?? 'unversioned', backing: 'native', programs, initdb: initdbArguments(user), user, database,
|
|
286
|
+
...(migration ? { migration } : {}) };
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* The file a command runs: itself, or, for a host shim that runs another build of the program (an image's wasm32
|
|
290
|
+
* PostgreSQL at its image build: browser-substrate wali-host-program.ts), the program it declares on a
|
|
291
|
+
* `# program: <file>` line in its first 1 KiB. The stamp is about the program that makes the cluster, not its launcher.
|
|
292
|
+
*/
|
|
293
|
+
function programFile(command) {
|
|
294
|
+
const file = realpathSync(command);
|
|
295
|
+
// Its first 1 KiB only: read whole, postgres and initdb crossed the tab's file system at every up for this line.
|
|
296
|
+
const bytes = Buffer.alloc(1024);
|
|
297
|
+
const fd = openSync(file, 'r');
|
|
298
|
+
let length;
|
|
299
|
+
try {
|
|
300
|
+
length = readSync(fd, bytes, 0, bytes.length, 0);
|
|
301
|
+
}
|
|
302
|
+
finally {
|
|
303
|
+
closeSync(fd);
|
|
304
|
+
}
|
|
305
|
+
const head = bytes.subarray(0, length).toString('latin1');
|
|
306
|
+
return head.startsWith('#!') ? /^# program: (.+)$/m.exec(head)?.[1]?.trim() || file : file;
|
|
307
|
+
}
|
|
308
|
+
/** Whether a stamp names this runtime, these programs' sizes, these initdb arguments, this identity and this migration
|
|
309
|
+
* (up's check): a migrated cluster is taken only for the step and input digests it was migrated from, an unmigrated one
|
|
310
|
+
* only by a World that declares no step. */
|
|
311
|
+
function nativeStampMatches(text, expected) {
|
|
312
|
+
let stamp;
|
|
313
|
+
try {
|
|
314
|
+
stamp = JSON.parse(text);
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
return stamp.runtime === expected.runtime && stamp.backing === 'native' && stamp.user === expected.user
|
|
320
|
+
&& stamp.database === expected.database && JSON.stringify(stamp.initdb) === JSON.stringify(expected.initdb)
|
|
321
|
+
&& Object.keys(expected.programs).every(name => stamp.programs?.[name]?.bytes === expected.programs[name].bytes)
|
|
322
|
+
&& JSON.stringify(stamp.migration) === JSON.stringify(expected.migration);
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* `volter-world-infra prepare` for a World whose PostgreSQL is native (architecture, managed infrastructure; ADR
|
|
326
|
+
* 0018's phase): this lifecycle's own steps once, into `<World dir>/.volter-prepared/native-postgres-data`. initdb with
|
|
327
|
+
* up's arguments, the postmaster on the World's declared loopback port, the declared database, and a clean SIGINT stop (its
|
|
328
|
+
* shutdown checkpoint; no postmaster.pid left), then the stamp. Where the World declares runtime.database.prepare, the
|
|
329
|
+
* step runs against that postmaster before its stop, and a step that fails leaves no cluster. The image build runs it with the programs the tab
|
|
330
|
+
* runs, under the tab's kernel, so the cluster is the one a first boot there would write.
|
|
331
|
+
*/
|
|
332
|
+
export async function nativePrepare(service, definition, worldConfig, launch = {}) {
|
|
333
|
+
if (service.kind !== 'postgres')
|
|
334
|
+
throw new Error('only a declared PostgreSQL is prepared');
|
|
335
|
+
const env = { ...(launch.env ?? process.env) };
|
|
336
|
+
// The preparation makes the cluster; it never takes one.
|
|
337
|
+
delete env.VOLTER_WORLD_CONFIG;
|
|
338
|
+
const tools = nativePostgresTools(env);
|
|
339
|
+
if (!tools)
|
|
340
|
+
throw new Error('native prepare requires the PostgreSQL programs: a postgres server on PATH, or VOLTER_WORLD_POSTGRES_BIN');
|
|
341
|
+
const { directory, stamp } = nativePreparedCluster(worldConfig);
|
|
342
|
+
const root = dirname(directory);
|
|
343
|
+
mkdirSync(root, { recursive: true });
|
|
344
|
+
if (ownership(root).present)
|
|
345
|
+
await nativePostgresDown(root);
|
|
346
|
+
rmSync(directory, { recursive: true, force: true });
|
|
347
|
+
rmSync(stamp, { force: true });
|
|
348
|
+
const { user, database } = settings(definition);
|
|
349
|
+
// On the World's declared port, the one up serves: the image build's sandbox admits exactly that loopback endpoint
|
|
350
|
+
// (browser-substrate world-cluster.ts), and a port already held is refused by up's own check.
|
|
351
|
+
// A preparation that fails keeps the postmaster's WHOLE log outside this directory, which its caller may remove,
|
|
352
|
+
// and says where (pglite-backing.ts keepFailureEvidence). The log is removed only once the cluster is prepared.
|
|
353
|
+
const log = join(root, 'native-postgres.log');
|
|
354
|
+
const failed = (error) => {
|
|
355
|
+
const kept = existsSync(log) ? keepFailureEvidence('native-postgres-log', { file: log }, env) : '';
|
|
356
|
+
if (error instanceof Error) {
|
|
357
|
+
error.message += kept;
|
|
358
|
+
return error;
|
|
359
|
+
}
|
|
360
|
+
return new Error(`${String(error)}${kept}`);
|
|
361
|
+
};
|
|
362
|
+
try {
|
|
363
|
+
await nativePostgresUp([service], root, definition, { ...launch, env });
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
throw failed(error);
|
|
367
|
+
}
|
|
368
|
+
// The app's own migration, where the World declares one, against the postmaster this lifecycle has up: the same step
|
|
369
|
+
// the PGlite backing runs against its host. Its failure waits for the stop below, then removes the cluster.
|
|
370
|
+
const step = databasePrepareOf(worldConfig);
|
|
371
|
+
let migration;
|
|
372
|
+
let stepFailure;
|
|
373
|
+
if (step) {
|
|
374
|
+
try {
|
|
375
|
+
migration = await runDatabasePrepare(step, worldConfig, { ...launch, env });
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
stepFailure = { error };
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// The clean stop: SIGINT is the postmaster's fast shutdown, which writes its checkpoint and removes postmaster.pid.
|
|
382
|
+
// Its wait is the lifecycle's own readiness budget, not a new limit; down then clears the receipts.
|
|
383
|
+
const identity = readSpawnReceipt(recordPath(root)).value;
|
|
384
|
+
if (identity) {
|
|
385
|
+
await signalOwnedProcess(identity, 'SIGINT', true);
|
|
386
|
+
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
387
|
+
while (liveGroup(identity.pid) && Date.now() < deadline)
|
|
388
|
+
await wait(100);
|
|
389
|
+
}
|
|
390
|
+
await nativePostgresDown(root);
|
|
391
|
+
if (stepFailure) {
|
|
392
|
+
rmSync(directory, { recursive: true, force: true });
|
|
393
|
+
throw failed(stepFailure.error);
|
|
394
|
+
}
|
|
395
|
+
if (!existsSync(join(directory, 'PG_VERSION')) || existsSync(join(directory, 'postmaster.pid'))) {
|
|
396
|
+
rmSync(directory, { recursive: true, force: true });
|
|
397
|
+
throw failed(new Error('native prepare ended without a cleanly stopped cluster'));
|
|
398
|
+
}
|
|
399
|
+
rmSync(log, { force: true });
|
|
400
|
+
writeFileSync(stamp, `${JSON.stringify(nativeStamp(tools, user, database, true, migration))}\n`);
|
|
401
|
+
return directory;
|
|
402
|
+
}
|
|
249
403
|
export async function nativePostgresUp(services, root, definition, launch) {
|
|
250
404
|
if (services.length !== 1 || services[0].kind !== 'postgres')
|
|
251
405
|
throw new Error('native postgres requires one declared postgres service');
|
|
@@ -275,10 +429,27 @@ export async function nativePostgresUp(services, root, definition, launch) {
|
|
|
275
429
|
if (await listening(service.hostPort, deadline, launch.signal))
|
|
276
430
|
throw new Error(`native postgres port ${service.hostPort} is already occupied`);
|
|
277
431
|
const data = resolve(root, 'native-postgres-data');
|
|
278
|
-
mkdirSync(data, { recursive: true, mode: 0o700 });
|
|
279
432
|
const { user, database } = settings(definition);
|
|
433
|
+
// An empty data directory takes the World's prepared cluster when its stamp matches (ADR 0018's placement; the
|
|
434
|
+
// cluster was made by these same programs at image build). initdb then finds PG_VERSION, and the database exists.
|
|
435
|
+
forgetStaleMigration(data);
|
|
436
|
+
const prepared = env.VOLTER_WORLD_CONFIG ? nativePreparedCluster(env.VOLTER_WORLD_CONFIG) : undefined;
|
|
437
|
+
// The check reads two files' metadata and the stamp; it runs only when a prepared cluster is there to take.
|
|
438
|
+
if (prepared && existsSync(join(prepared.directory, 'PG_VERSION')) && !existsSync(join(data, 'PG_VERSION'))) {
|
|
439
|
+
// A migrated cluster is the app's only for the step inputs it was migrated from (pglite-backing.ts, expectedMigration).
|
|
440
|
+
const wanted = expectedMigration(env.VOLTER_WORLD_CONFIG, env, prepared.directory);
|
|
441
|
+
if ('note' in wanted)
|
|
442
|
+
launch.note?.(wanted.note);
|
|
443
|
+
else {
|
|
444
|
+
const expected = nativeStamp(binaries, user, database, false, wanted.migration);
|
|
445
|
+
const placed = takePreparedCluster(prepared, text => nativeStampMatches(text, expected), data, 'other PostgreSQL programs, initdb arguments, identity or migration', wanted.migration);
|
|
446
|
+
if (placed.note)
|
|
447
|
+
launch.note?.(placed.note);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
mkdirSync(data, { recursive: true, mode: 0o700 });
|
|
280
451
|
if (!existsSync(join(data, 'PG_VERSION'))) {
|
|
281
|
-
await tool(join(binaries, 'initdb'), ['-D', data,
|
|
452
|
+
await tool(join(binaries, 'initdb'), ['-D', data, ...initdbArguments(user)], env, deadline, root, launch.signal);
|
|
282
453
|
}
|
|
283
454
|
launch.signal?.throwIfAborted();
|
|
284
455
|
const binary = join(binaries, 'postgres');
|
|
@@ -321,9 +492,9 @@ export async function nativePostgresUp(services, root, definition, launch) {
|
|
|
321
492
|
if (started.error)
|
|
322
493
|
throw started.error;
|
|
323
494
|
if (child.exitCode !== null || child.signalCode !== null || started.closed)
|
|
324
|
-
throw new Error(`native postgres exited before readiness; see ${log}`);
|
|
495
|
+
throw new Error(`native postgres exited before readiness; see ${log}${logTail(log)}`);
|
|
325
496
|
if (Date.now() >= deadline)
|
|
326
|
-
throw new Error(`native postgres did not become ready; see ${log}`);
|
|
497
|
+
throw new Error(`native postgres did not become ready; see ${log}${logTail(log)}`);
|
|
327
498
|
if (existsSync(join(data, 'postmaster.pid'))) {
|
|
328
499
|
verifyOwner(current);
|
|
329
500
|
const lines = readFileSync(join(data, 'postmaster.pid'), 'utf8').split('\n');
|
|
@@ -340,7 +511,7 @@ export async function nativePostgresUp(services, root, definition, launch) {
|
|
|
340
511
|
}
|
|
341
512
|
launch.signal?.throwIfAborted();
|
|
342
513
|
if (child.exitCode !== null || child.signalCode !== null)
|
|
343
|
-
throw new Error(`native postgres exited during readiness; see ${log}`);
|
|
514
|
+
throw new Error(`native postgres exited during readiness; see ${log}${logTail(log)}`);
|
|
344
515
|
rmSync(startingPath(root));
|
|
345
516
|
child.unref();
|
|
346
517
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// THIS PACKAGE'S OWN MANIFEST, found by its name walking up from this module. One finder for every module of the
|
|
2
|
+
// runtime that needs it (a prepared cluster's stamp records the runtime's version and its PGlite's).
|
|
3
|
+
//
|
|
4
|
+
// A fixed hop is wrong in one of the two places the runtime runs from: src/ in the repository is one directory below
|
|
5
|
+
// the package root, dist/src/ in the published package is two, so `..` from a published module is dist/, which
|
|
6
|
+
// holds no package.json. That mistake was made twice and fixed once: the PGlite backing walked up, the native
|
|
7
|
+
// backing still hopped, and a native preparation failed after its migration had run (Cal run 28: "ENOENT ...
|
|
8
|
+
// node_modules/@volter/world-runtime/dist/package.json").
|
|
9
|
+
//
|
|
10
|
+
// Nothing runs at import: a module that builds a URL from import.meta.url when it loads fails to load in workerd,
|
|
11
|
+
// which bundles the runtime into the hosted World.
|
|
12
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
13
|
+
import { dirname, join } from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
const NAME = '@volter/world-runtime';
|
|
16
|
+
/** The runtime's package.json, from wherever this module runs. */
|
|
17
|
+
export function runtimeManifest() {
|
|
18
|
+
const from = fileURLToPath(new URL('.', import.meta.url));
|
|
19
|
+
for (let dir = from;; dir = dirname(dir)) {
|
|
20
|
+
const candidate = join(dir, 'package.json');
|
|
21
|
+
if (existsSync(candidate)) {
|
|
22
|
+
const manifest = JSON.parse(readFileSync(candidate, 'utf8'));
|
|
23
|
+
if (manifest.name === NAME)
|
|
24
|
+
return manifest;
|
|
25
|
+
}
|
|
26
|
+
if (dirname(dir) === dir)
|
|
27
|
+
throw new Error(`no ${NAME} package.json above ${from}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type WorldDatabasePrepare } from './schema.js';
|
|
1
2
|
export interface InfraService {
|
|
2
3
|
kind: string;
|
|
3
4
|
hostPort: number;
|
|
@@ -42,8 +43,92 @@ export declare function preparedCluster(worldConfig: string): {
|
|
|
42
43
|
directory: string;
|
|
43
44
|
stamp: string;
|
|
44
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* What a prepared cluster must have been made with: this runtime (whose pglite-host.mjs is the open path that made
|
|
48
|
+
* it), its exact PGlite pin (initdb's encoding and locale are PGlite's defaults: the host passes no initdb
|
|
49
|
+
* parameters), and the declared user and database. The password is not in the cluster: the role is created without
|
|
50
|
+
* one (LOGIN SUPERUSER) and the host's gateway checks the declared password on each connection, so a World with
|
|
51
|
+
* another password takes the same cluster and authenticates against its own. Extensions are loaded into the module
|
|
52
|
+
* at each open, never created in the cluster by the open path.
|
|
53
|
+
*/
|
|
54
|
+
/** A prepared cluster's migration, as its stamp records it: the step's command and the digest of each of its inputs. */
|
|
55
|
+
export interface PreparedMigration {
|
|
56
|
+
command: string[];
|
|
57
|
+
inputs: Record<string, string>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The SHA-256 of each of a migration step's inputs, under its cwd in the app's tree: a file's bytes; a directory's
|
|
61
|
+
* sorted relative paths, each with its type, its mode and its bytes (a link, its target). What the image build records
|
|
62
|
+
* for an image, and what `up` hashes only for a World run outside one.
|
|
63
|
+
*/
|
|
64
|
+
export declare function migrationInputDigests(cwd: string, inputs: readonly string[]): Record<string, string>;
|
|
65
|
+
/** The World's declared migration step for its prepared database, if any (runtime.database.prepare). */
|
|
66
|
+
export declare function databasePrepareOf(worldConfig: string): WorldDatabasePrepare | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* The World's declared step (runtime.database.prepare) run once against a prepared cluster its backing is serving on
|
|
69
|
+
* the World's port: the declared command in the app's tree, with the image's environment and the World's values over
|
|
70
|
+
* it. The build host's own PATH finds the command's tools (the image's PATH names its own paths). Its failure throws,
|
|
71
|
+
* with its output; its answer is what the cluster's stamp records.
|
|
72
|
+
*/
|
|
73
|
+
/**
|
|
74
|
+
* A FAILED PREPARATION KEEPS ITS WHOLE EVIDENCE. An error carries a tail, and the preparation's own directory is
|
|
75
|
+
* its caller's to remove (an image build prepares in a staging directory it retires when the preparation fails), so
|
|
76
|
+
* the tail was all that survived: a postmaster's first lines, its shared-memory setup among them, were gone with the
|
|
77
|
+
* directory (Cal runs 23 and 24). The whole text or file is written under the preparation's temporary directory
|
|
78
|
+
* (TMPDIR, which a failed build retains; the system's otherwise), outside the World's directory, and the sentence
|
|
79
|
+
* naming it is added to the error. Never on success, and a copy that fails says so instead of hiding the failure.
|
|
80
|
+
*/
|
|
81
|
+
export declare function keepFailureEvidence(name: string, evidence: string | {
|
|
82
|
+
file: string;
|
|
83
|
+
}, env: NodeJS.ProcessEnv): string;
|
|
84
|
+
export declare function runDatabasePrepare(step: WorldDatabasePrepare, worldConfig: string, launch: HostLaunch): Promise<PreparedMigration>;
|
|
45
85
|
/** Run the host's open path once into the World's prepared directory; replaces an earlier preparation. */
|
|
46
86
|
export declare function pglitePrepare(service: InfraService, worldConfig: string, launch?: HostLaunch): Promise<string>;
|
|
87
|
+
/**
|
|
88
|
+
* The migration a prepared cluster must have been made with for `up` to take it, on either backing: none when the
|
|
89
|
+
* World declares no step; else the step's command and its inputs' digests, as the app's image recorded them
|
|
90
|
+
* (VOLTER_WORLD_APP_DIGESTS, no read of the tree) or, for a World run outside an image, as its tree has them. With
|
|
91
|
+
* neither given a migrated cluster is not taken, and the note says so.
|
|
92
|
+
*/
|
|
93
|
+
export declare function expectedMigration(worldConfig: string, env: NodeJS.ProcessEnv, directory: string): {
|
|
94
|
+
migration?: PreparedMigration;
|
|
95
|
+
} | {
|
|
96
|
+
note: string;
|
|
97
|
+
};
|
|
98
|
+
/** A data directory with no cluster carries no migration: a record left by data since removed is dropped. */
|
|
99
|
+
export declare function forgetStaleMigration(serviceData: string): void;
|
|
100
|
+
/**
|
|
101
|
+
* The declared step's command when the LIVE database carries the migration the app's image expects, else undefined
|
|
102
|
+
* (architecture, managed infrastructure). A fact of the data, not of this boot's placement: kept data is still
|
|
103
|
+
* migrated on a later boot, and kept data under an image whose step inputs have changed is older than the image.
|
|
104
|
+
*/
|
|
105
|
+
export declare function liveDatabaseMigration(worldConfig: string, env: NodeJS.ProcessEnv, serviceData: string): string[] | undefined;
|
|
106
|
+
/**
|
|
107
|
+
* The declared step's command when the World declares one and the live database does NOT carry the migration the
|
|
108
|
+
* image expects: data kept from an earlier image, or a cluster that was initialized here because the prepared one
|
|
109
|
+
* was not taken. Undefined with no step, no cluster yet, or a migrated one.
|
|
110
|
+
*/
|
|
111
|
+
export declare function liveDatabaseBehind(worldConfig: string, env: NodeJS.ProcessEnv, serviceData: string): string[] | undefined;
|
|
112
|
+
/**
|
|
113
|
+
* Whether this is a development World whose app may migrate its database when it starts
|
|
114
|
+
* (runtime.development.migrateDatabaseAtRuntime; schema.ts WorldDevelopment says whose the setting is).
|
|
115
|
+
*/
|
|
116
|
+
export declare function developmentMigratesAtRuntime(worldConfig: string): boolean;
|
|
117
|
+
/** What a development World says each time it is brought up or built into an image, so its use is never silent. */
|
|
118
|
+
export declare const DEVELOPMENT_MIGRATION_NOTICE = "development World: its database migrates at run time; it is not prepared";
|
|
119
|
+
/**
|
|
120
|
+
* The one placement of a prepared cluster, for every backing that prepares one (PGlite here, native PostgreSQL in
|
|
121
|
+
* native-postgres-backing.ts): an empty `serviceData` takes `prepared.directory` by rename when the stamp beside it
|
|
122
|
+
* is exactly `expected`; a mismatch is named, never taken. A migrated cluster's `migration` is recorded beside the data
|
|
123
|
+
* it becomes (liveDatabaseMigration).
|
|
124
|
+
*/
|
|
125
|
+
export declare function takePreparedCluster(prepared: {
|
|
126
|
+
directory: string;
|
|
127
|
+
stamp: string;
|
|
128
|
+
}, expected: string | ((stamp: string) => boolean), serviceData: string, mismatch: string, migration?: PreparedMigration): {
|
|
129
|
+
placed: boolean;
|
|
130
|
+
note?: string;
|
|
131
|
+
};
|
|
47
132
|
export declare function pgliteUp(services: InfraService[], dataDir: string, launch?: HostLaunch): Promise<void>;
|
|
48
133
|
export declare function pgliteStatus(services: InfraService[], dataDir: string): Promise<{
|
|
49
134
|
ok: boolean;
|