embedded-postgres-node 0.0.0-stage → 0.1.0-alpha.2
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 +21 -0
- package/README.md +113 -2
- package/dist/cjs/binary.d.ts +24 -0
- package/dist/cjs/binary.js +168 -0
- package/dist/cjs/binary.js.map +1 -0
- package/dist/cjs/errors.d.ts +9 -0
- package/dist/cjs/errors.js +35 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/index.d.ts +7 -0
- package/dist/cjs/index.js +13 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/lifecycle.d.ts +21 -0
- package/dist/cjs/lifecycle.js +209 -0
- package/dist/cjs/lifecycle.js.map +1 -0
- package/dist/cjs/options.d.ts +46 -0
- package/dist/cjs/options.js +79 -0
- package/dist/cjs/options.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/protocol.d.ts +22 -0
- package/dist/cjs/protocol.js +70 -0
- package/dist/cjs/protocol.js.map +1 -0
- package/dist/esm/binary.d.ts +24 -0
- package/dist/esm/binary.js +162 -0
- package/dist/esm/binary.js.map +1 -0
- package/dist/esm/errors.d.ts +9 -0
- package/dist/esm/errors.js +29 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/index.d.ts +7 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/lifecycle.d.ts +21 -0
- package/dist/esm/lifecycle.js +205 -0
- package/dist/esm/lifecycle.js.map +1 -0
- package/dist/esm/options.d.ts +46 -0
- package/dist/esm/options.js +76 -0
- package/dist/esm/options.js.map +1 -0
- package/dist/esm/protocol.d.ts +22 -0
- package/dist/esm/protocol.js +66 -0
- package/dist/esm/protocol.js.map +1 -0
- package/docs/api.md +73 -0
- package/docs/binaries.md +50 -0
- package/docs/integration.md +39 -0
- package/docs/releasing.md +41 -0
- package/docs/validation.md +94 -0
- package/examples/jest.config.cjs +1 -0
- package/examples/jest.test.cjs +22 -0
- package/examples/migrations.mjs +17 -0
- package/examples/node-test.mjs +22 -0
- package/examples/options.mjs +6 -0
- package/examples/scoped.mjs +21 -0
- package/examples/vitest.config.mjs +2 -0
- package/examples/vitest.test.mjs +21 -0
- package/package.json +73 -4
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { PostgresError, positiveTimeout } from './errors.js';
|
|
4
|
+
const reserved = new Set(['json', 'parent-stdin', 'config', 'state-file', 'password', 'port', 'database', 'username', 'start-timeout', 'stop-timeout', 'data-dir', 'work-dir', 'cache-dir', 'binaries', 'postgres-version', 'user', 'set', 'help']);
|
|
5
|
+
export function prepareOptions(options) {
|
|
6
|
+
const start = positiveTimeout(options.startTimeoutMs ?? 120_000, 'startTimeoutMs');
|
|
7
|
+
const stop = positiveTimeout(options.stopTimeoutMs ?? 10_000, 'stopTimeoutMs');
|
|
8
|
+
if (stop > 60_000)
|
|
9
|
+
throw new PostgresError('CONFIG', 'stopTimeoutMs must not exceed the CLI maximum of 60000');
|
|
10
|
+
const startupDeadlineMs = positiveTimeout(options.startupDeadlineMs ?? start + 30_000, 'startupDeadlineMs');
|
|
11
|
+
const shutdownDeadlineMs = positiveTimeout(options.shutdownDeadlineMs ?? stop + 5_000, 'shutdownDeadlineMs');
|
|
12
|
+
if (shutdownDeadlineMs <= stop + 2_000)
|
|
13
|
+
throw new PostgresError('CONFIG', 'shutdownDeadlineMs must exceed stopTimeoutMs + 2000');
|
|
14
|
+
const port = options.port ?? 0;
|
|
15
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535)
|
|
16
|
+
throw new PostgresError('CONFIG', 'port must be an integer between 0 and 65535');
|
|
17
|
+
if (options.storage?.type === 'persistent' && (!options.storage.dataDir || !options.password || !options.username || !options.database)) {
|
|
18
|
+
throw new PostgresError('CONFIG', 'Persistent storage requires dataDir and explicit database, username and password');
|
|
19
|
+
}
|
|
20
|
+
if (options.password === '')
|
|
21
|
+
throw new PostgresError('CONFIG', 'password must not be empty');
|
|
22
|
+
const password = options.password ?? randomBytes(24).toString('base64url');
|
|
23
|
+
const args = ['run', '--json', '--parent-stdin', `--port=${port}`, `--start-timeout=${start}ms`, `--stop-timeout=${stop}ms`];
|
|
24
|
+
const add = (name, value) => {
|
|
25
|
+
if (value !== undefined) {
|
|
26
|
+
if (value.includes('\0'))
|
|
27
|
+
throw new PostgresError('CONFIG', 'CLI options cannot contain NUL characters');
|
|
28
|
+
args.push(`--${name}=${value}`);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
add('database', options.database);
|
|
32
|
+
add('username', options.username);
|
|
33
|
+
add('postgres-version', options.postgresVersion);
|
|
34
|
+
add('cache-dir', options.cacheDir === undefined ? undefined : resolve(options.cacheDir));
|
|
35
|
+
add('binaries', options.binaries === undefined ? undefined : resolve(options.binaries));
|
|
36
|
+
add('work-dir', options.storage?.workDir === undefined ? undefined : resolve(options.storage.workDir));
|
|
37
|
+
if (options.storage?.type === 'persistent')
|
|
38
|
+
add('data-dir', resolve(options.storage.dataDir));
|
|
39
|
+
if (options.runAs) {
|
|
40
|
+
for (const id of [options.runAs.uid, options.runAs.gid]) {
|
|
41
|
+
if (!Number.isInteger(id) || id < 0 || id >= 4_294_967_295)
|
|
42
|
+
throw new PostgresError('CONFIG', 'runAs IDs must be unsigned integers below 4294967295');
|
|
43
|
+
}
|
|
44
|
+
if (options.runAs.uid === 0)
|
|
45
|
+
throw new PostgresError('CONFIG', 'runAs.uid must be nonzero');
|
|
46
|
+
add('user', `${options.runAs.uid}:${options.runAs.gid}`);
|
|
47
|
+
}
|
|
48
|
+
for (const [name, value] of Object.entries(options.parameters ?? {})) {
|
|
49
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_.]*$/.test(name))
|
|
50
|
+
throw new PostgresError('CONFIG', 'Invalid PostgreSQL parameter name');
|
|
51
|
+
add('set', `${name}=${value}`);
|
|
52
|
+
}
|
|
53
|
+
for (const [name, value] of Object.entries(options.cliOptions ?? {})) {
|
|
54
|
+
if (!/^[a-z][a-z0-9-]*$/.test(name) || reserved.has(name))
|
|
55
|
+
throw new PostgresError('CONFIG', 'Invalid or reserved advanced CLI option');
|
|
56
|
+
for (const item of Array.isArray(value) ? value : [value])
|
|
57
|
+
add(name, String(item));
|
|
58
|
+
}
|
|
59
|
+
const env = {};
|
|
60
|
+
for (const [name, value] of Object.entries(process.env)) {
|
|
61
|
+
// Do not allow ambient CLI configuration to turn disposable tests persistent.
|
|
62
|
+
if (!name.toUpperCase().startsWith('EP_'))
|
|
63
|
+
env[name] = value;
|
|
64
|
+
}
|
|
65
|
+
for (const [name, value] of Object.entries(options.env ?? {})) {
|
|
66
|
+
if (name.toUpperCase().startsWith('EP_') || name.includes('=') || name.includes('\0') || value.includes('\0')) {
|
|
67
|
+
throw new PostgresError('CONFIG', 'Invalid or reserved child environment variable');
|
|
68
|
+
}
|
|
69
|
+
env[name] = value;
|
|
70
|
+
}
|
|
71
|
+
if (password.includes('\0'))
|
|
72
|
+
throw new PostgresError('CONFIG', 'password cannot contain NUL characters');
|
|
73
|
+
env.EP_PASSWORD = password;
|
|
74
|
+
return { args, env, password, startupDeadlineMs, shutdownDeadlineMs };
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/options.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { resolve } from 'node:path';\nimport { PostgresError, positiveTimeout } from './errors.js';\nimport type { CliSource } from './binary.js';\n\nexport interface PostgresOptions {\n /** Explicit executable/release; otherwise EMBEDDED_POSTGRES_CLI or the package's pinned release. */\n cli?: CliSource;\n postgresVersion?: string;\n database?: string;\n username?: string;\n password?: string;\n /** Zero (the default) lets PostgreSQL select an available port. */\n port?: number;\n cacheDir?: string;\n binaries?: string;\n storage?: { type: 'disposable'; workDir?: string } | { type: 'persistent'; dataDir: string; workDir?: string };\n runAs?: { uid: number; gid: number };\n parameters?: Readonly<Record<string, string>>;\n startTimeoutMs?: number;\n stopTimeoutMs?: number;\n /** Outer startup deadline, including executable resolution. Defaults to startTimeoutMs + 30s. */\n startupDeadlineMs?: number;\n /** Must exceed stopTimeoutMs + 2s. Defaults to stopTimeoutMs + 5s. */\n shutdownDeadlineMs?: number;\n /** Lifetime cancellation: abort before or after readiness requests cleanup. */\n signal?: AbortSignal;\n /** Additional child environment. EP_* keys are reserved. */\n env?: Readonly<Record<string, string>>;\n /** CLI flag names without --. Core, credential, config and lifecycle flags are reserved. */\n cliOptions?: Readonly<Record<string, string | number | boolean | readonly string[]>>;\n}\n\nexport interface PreparedOptions {\n args: string[];\n env: NodeJS.ProcessEnv;\n password: string;\n startupDeadlineMs: number;\n shutdownDeadlineMs: number;\n}\n\nconst reserved = new Set(['json', 'parent-stdin', 'config', 'state-file', 'password', 'port', 'database', 'username', 'start-timeout', 'stop-timeout', 'data-dir', 'work-dir', 'cache-dir', 'binaries', 'postgres-version', 'user', 'set', 'help']);\n\nexport function prepareOptions(options: PostgresOptions): PreparedOptions {\n const start = positiveTimeout(options.startTimeoutMs ?? 120_000, 'startTimeoutMs');\n const stop = positiveTimeout(options.stopTimeoutMs ?? 10_000, 'stopTimeoutMs');\n if (stop > 60_000) throw new PostgresError('CONFIG', 'stopTimeoutMs must not exceed the CLI maximum of 60000');\n const startupDeadlineMs = positiveTimeout(options.startupDeadlineMs ?? start + 30_000, 'startupDeadlineMs');\n const shutdownDeadlineMs = positiveTimeout(options.shutdownDeadlineMs ?? stop + 5_000, 'shutdownDeadlineMs');\n if (shutdownDeadlineMs <= stop + 2_000) throw new PostgresError('CONFIG', 'shutdownDeadlineMs must exceed stopTimeoutMs + 2000');\n const port = options.port ?? 0;\n if (!Number.isInteger(port) || port < 0 || port > 65535) throw new PostgresError('CONFIG', 'port must be an integer between 0 and 65535');\n if (options.storage?.type === 'persistent' && (!options.storage.dataDir || !options.password || !options.username || !options.database)) {\n throw new PostgresError('CONFIG', 'Persistent storage requires dataDir and explicit database, username and password');\n }\n if (options.password === '') throw new PostgresError('CONFIG', 'password must not be empty');\n const password = options.password ?? randomBytes(24).toString('base64url');\n const args = ['run', '--json', '--parent-stdin', `--port=${port}`, `--start-timeout=${start}ms`, `--stop-timeout=${stop}ms`];\n const add = (name: string, value: string | undefined) => {\n if (value !== undefined) {\n if (value.includes('\\0')) throw new PostgresError('CONFIG', 'CLI options cannot contain NUL characters');\n args.push(`--${name}=${value}`);\n }\n };\n add('database', options.database);\n add('username', options.username);\n add('postgres-version', options.postgresVersion);\n add('cache-dir', options.cacheDir === undefined ? undefined : resolve(options.cacheDir));\n add('binaries', options.binaries === undefined ? undefined : resolve(options.binaries));\n add('work-dir', options.storage?.workDir === undefined ? undefined : resolve(options.storage.workDir));\n if (options.storage?.type === 'persistent') add('data-dir', resolve(options.storage.dataDir));\n if (options.runAs) {\n for (const id of [options.runAs.uid, options.runAs.gid]) {\n if (!Number.isInteger(id) || id < 0 || id >= 4_294_967_295) throw new PostgresError('CONFIG', 'runAs IDs must be unsigned integers below 4294967295');\n }\n if (options.runAs.uid === 0) throw new PostgresError('CONFIG', 'runAs.uid must be nonzero');\n add('user', `${options.runAs.uid}:${options.runAs.gid}`);\n }\n for (const [name, value] of Object.entries(options.parameters ?? {})) {\n if (!/^[a-zA-Z_][a-zA-Z0-9_.]*$/.test(name)) throw new PostgresError('CONFIG', 'Invalid PostgreSQL parameter name');\n add('set', `${name}=${value}`);\n }\n for (const [name, value] of Object.entries(options.cliOptions ?? {})) {\n if (!/^[a-z][a-z0-9-]*$/.test(name) || reserved.has(name)) throw new PostgresError('CONFIG', 'Invalid or reserved advanced CLI option');\n for (const item of Array.isArray(value) ? value : [value]) add(name, String(item));\n }\n const env: NodeJS.ProcessEnv = {};\n for (const [name, value] of Object.entries(process.env)) {\n // Do not allow ambient CLI configuration to turn disposable tests persistent.\n if (!name.toUpperCase().startsWith('EP_')) env[name] = value;\n }\n for (const [name, value] of Object.entries(options.env ?? {})) {\n if (name.toUpperCase().startsWith('EP_') || name.includes('=') || name.includes('\\0') || value.includes('\\0')) {\n throw new PostgresError('CONFIG', 'Invalid or reserved child environment variable');\n }\n env[name] = value;\n }\n if (password.includes('\\0')) throw new PostgresError('CONFIG', 'password cannot contain NUL characters');\n env.EP_PASSWORD = password;\n return { args, env, password, startupDeadlineMs, shutdownDeadlineMs };\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type ProtocolEvent = {
|
|
2
|
+
protocol: 1;
|
|
3
|
+
event: 'ready';
|
|
4
|
+
connection_url: string;
|
|
5
|
+
port: number;
|
|
6
|
+
} | {
|
|
7
|
+
protocol: 1;
|
|
8
|
+
event: 'stopped';
|
|
9
|
+
} | {
|
|
10
|
+
protocol: 1;
|
|
11
|
+
event: 'error';
|
|
12
|
+
error: string;
|
|
13
|
+
};
|
|
14
|
+
/** Incremental JSON-lines parser. Never include raw protocol in diagnostics. */
|
|
15
|
+
export declare class ProtocolParser {
|
|
16
|
+
private readonly onEvent;
|
|
17
|
+
private pending;
|
|
18
|
+
constructor(onEvent: (event: ProtocolEvent) => void);
|
|
19
|
+
push(chunk: string): void;
|
|
20
|
+
finish(): void;
|
|
21
|
+
private line;
|
|
22
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { PostgresError } from './errors.js';
|
|
2
|
+
const limit = 64 * 1024;
|
|
3
|
+
/** Incremental JSON-lines parser. Never include raw protocol in diagnostics. */
|
|
4
|
+
export class ProtocolParser {
|
|
5
|
+
onEvent;
|
|
6
|
+
pending = '';
|
|
7
|
+
constructor(onEvent) {
|
|
8
|
+
this.onEvent = onEvent;
|
|
9
|
+
}
|
|
10
|
+
push(chunk) {
|
|
11
|
+
this.pending += chunk;
|
|
12
|
+
let newline;
|
|
13
|
+
while ((newline = this.pending.indexOf('\n')) !== -1) {
|
|
14
|
+
const line = this.pending.slice(0, newline);
|
|
15
|
+
this.pending = this.pending.slice(newline + 1);
|
|
16
|
+
this.line(line);
|
|
17
|
+
}
|
|
18
|
+
if (this.pending.length > limit)
|
|
19
|
+
throw new PostgresError('PROTOCOL', 'CLI protocol line exceeds 64 KiB');
|
|
20
|
+
}
|
|
21
|
+
finish() {
|
|
22
|
+
if (this.pending)
|
|
23
|
+
this.line(this.pending);
|
|
24
|
+
this.pending = '';
|
|
25
|
+
}
|
|
26
|
+
line(line) {
|
|
27
|
+
if (line.length > limit)
|
|
28
|
+
throw new PostgresError('PROTOCOL', 'CLI protocol line exceeds 64 KiB');
|
|
29
|
+
if (!line.trim())
|
|
30
|
+
return;
|
|
31
|
+
let value;
|
|
32
|
+
try {
|
|
33
|
+
value = JSON.parse(line);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new PostgresError('PROTOCOL', 'CLI emitted invalid JSON');
|
|
37
|
+
}
|
|
38
|
+
if (!value || typeof value !== 'object' || !('protocol' in value) || value.protocol !== 1 || !('event' in value)) {
|
|
39
|
+
throw new PostgresError('PROTOCOL', 'CLI must speak protocol 1');
|
|
40
|
+
}
|
|
41
|
+
const event = value;
|
|
42
|
+
if (event.event === 'ready') {
|
|
43
|
+
if (!Number.isInteger(event.port) || event.port < 1 || event.port > 65535 || typeof event.connection_url !== 'string') {
|
|
44
|
+
throw new PostgresError('PROTOCOL', 'Invalid CLI ready event');
|
|
45
|
+
}
|
|
46
|
+
let url;
|
|
47
|
+
try {
|
|
48
|
+
url = new URL(event.connection_url);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
throw new PostgresError('PROTOCOL', 'Invalid CLI connection URL');
|
|
52
|
+
}
|
|
53
|
+
if (!['postgres:', 'postgresql:'].includes(url.protocol))
|
|
54
|
+
throw new PostgresError('PROTOCOL', 'Invalid CLI connection URL scheme');
|
|
55
|
+
}
|
|
56
|
+
else if (event.event === 'error') {
|
|
57
|
+
if (typeof event.error !== 'string')
|
|
58
|
+
throw new PostgresError('PROTOCOL', 'Invalid CLI error event');
|
|
59
|
+
}
|
|
60
|
+
else if (event.event !== 'stopped') {
|
|
61
|
+
throw new PostgresError('PROTOCOL', 'Unknown CLI protocol event');
|
|
62
|
+
}
|
|
63
|
+
this.onEvent(value);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/protocol.ts"],"names":[],"mappings":"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","sourcesContent":["import { PostgresError } from './errors.js';\n\nexport type ProtocolEvent =\n | { protocol: 1; event: 'ready'; connection_url: string; port: number }\n | { protocol: 1; event: 'stopped' }\n | { protocol: 1; event: 'error'; error: string };\n\nconst limit = 64 * 1024;\n\n/** Incremental JSON-lines parser. Never include raw protocol in diagnostics. */\nexport class ProtocolParser {\n private pending = '';\n constructor(private readonly onEvent: (event: ProtocolEvent) => void) {}\n\n push(chunk: string): void {\n this.pending += chunk;\n let newline: number;\n while ((newline = this.pending.indexOf('\\n')) !== -1) {\n const line = this.pending.slice(0, newline);\n this.pending = this.pending.slice(newline + 1);\n this.line(line);\n }\n if (this.pending.length > limit) throw new PostgresError('PROTOCOL', 'CLI protocol line exceeds 64 KiB');\n }\n\n finish(): void {\n if (this.pending) this.line(this.pending);\n this.pending = '';\n }\n\n private line(line: string): void {\n if (line.length > limit) throw new PostgresError('PROTOCOL', 'CLI protocol line exceeds 64 KiB');\n if (!line.trim()) return;\n let value: unknown;\n try { value = JSON.parse(line); } catch { throw new PostgresError('PROTOCOL', 'CLI emitted invalid JSON'); }\n if (!value || typeof value !== 'object' || !('protocol' in value) || value.protocol !== 1 || !('event' in value)) {\n throw new PostgresError('PROTOCOL', 'CLI must speak protocol 1');\n }\n const event = value as Record<string, unknown>;\n if (event.event === 'ready') {\n if (!Number.isInteger(event.port) || (event.port as number) < 1 || (event.port as number) > 65535 || typeof event.connection_url !== 'string') {\n throw new PostgresError('PROTOCOL', 'Invalid CLI ready event');\n }\n let url: URL;\n try { url = new URL(event.connection_url); } catch { throw new PostgresError('PROTOCOL', 'Invalid CLI connection URL'); }\n if (!['postgres:', 'postgresql:'].includes(url.protocol)) throw new PostgresError('PROTOCOL', 'Invalid CLI connection URL scheme');\n } else if (event.event === 'error') {\n if (typeof event.error !== 'string') throw new PostgresError('PROTOCOL', 'Invalid CLI error event');\n } else if (event.event !== 'stopped') {\n throw new PostgresError('PROTOCOL', 'Unknown CLI protocol event');\n }\n this.onEvent(value as ProtocolEvent);\n }\n}\n"]}
|
package/docs/api.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# API
|
|
2
|
+
|
|
3
|
+
Import from `embedded-postgres-node` with ESM `import` or CommonJS `require`. Both export the same API and corresponding `.d.ts` files.
|
|
4
|
+
|
|
5
|
+
## Lifecycle
|
|
6
|
+
|
|
7
|
+
`startPostgres(options?: PostgresOptions): Promise<PostgresInstance>` resolves only after the CLI reports authenticated readiness. Rejection waits for cleanup or the shutdown deadline. With no CLI configuration, it uses `EMBEDDED_POSTGRES_CLI` when set, otherwise downloads the package's checksum-pinned release. There is no implicit PATH lookup or latest-release lookup.
|
|
8
|
+
|
|
9
|
+
`withPostgres<T>(options, callback): Promise<T>` starts, awaits the callback, and stops in a `finally` block. Both callback and cleanup errors are retained in an `AggregateError`. The callback should await migration/setup work and close its drivers/pools before returning. User hooks are ordinary JavaScript functions, not executable CLI configuration.
|
|
10
|
+
|
|
11
|
+
An instance exposes:
|
|
12
|
+
|
|
13
|
+
| Member | Contract |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `connectionUrl: string` | Credential returned by CLI readiness; hidden from ordinary inspection and JSON serialization |
|
|
16
|
+
| `port: number` | Actual selected port |
|
|
17
|
+
| `stop(): Promise<void>` | Idempotent; all calls share one promise; rejects on startup/runtime/shutdown failure |
|
|
18
|
+
| `[Symbol.asyncDispose]()` | Same lifecycle as `stop()` |
|
|
19
|
+
| `closed: Promise<ExitResult>` | Always resolves: `{ exitCode, signal, stopped, error? }`; `error` is a `PostgresError` |
|
|
20
|
+
|
|
21
|
+
`closed.stopped` records the protocol event, not an inferred process state. A successful `stop()` requires that event plus exit code zero. Forced teardown returns an error, even if a late stopped event arrives. After a process/pipe shutdown stall, `exitCode` and `signal` may be null; cleanup cannot be confirmed.
|
|
22
|
+
|
|
23
|
+
## Options
|
|
24
|
+
|
|
25
|
+
| Option | Default / behavior |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| `cli` | Explicit `{ path: string }` or download options `{ release?, cacheDir?, offline?, downloadTimeoutMs? }`; otherwise `EMBEDDED_POSTGRES_CLI`, then `DEFAULT_CLI_RELEASE` |
|
|
28
|
+
| `postgresVersion` | CLI's pinned default PostgreSQL distribution |
|
|
29
|
+
| `database`, `username` | CLI defaults (`postgres`) |
|
|
30
|
+
| `password` | Random 192-bit password, sent through `EP_PASSWORD`, never argv |
|
|
31
|
+
| `port` | `0`, dynamic selection; otherwise integer 1–65535 |
|
|
32
|
+
| `cacheDir` | CLI PostgreSQL binary cache; separate from the Node CLI executable cache |
|
|
33
|
+
| `binaries` | Optional complete custom distribution path containing `bin/` |
|
|
34
|
+
| `storage` | Omitted or `{ type: 'disposable', workDir? }`; persistent form is `{ type: 'persistent', dataDir, workDir? }` |
|
|
35
|
+
| `runAs` | Optional Unix `{ uid, gid }`; existing nonzero UID, integer IDs below 4294967295; root callers also require nonzero GID |
|
|
36
|
+
| `parameters` | `Record<string, string>` mapped to repeated `--set=name=value` |
|
|
37
|
+
| `startTimeoutMs` | `120000`, CLI startup timeout |
|
|
38
|
+
| `stopTimeoutMs` | `10000`, CLI shutdown grace; maximum `60000` |
|
|
39
|
+
| `startupDeadlineMs` | `startTimeoutMs + 30000`, from before executable resolution through readiness |
|
|
40
|
+
| `shutdownDeadlineMs` | `stopTimeoutMs + 5000`; must be strictly greater than `stopTimeoutMs + 2000` |
|
|
41
|
+
| `signal` | Optional lifetime `AbortSignal`, including after readiness |
|
|
42
|
+
| `env` | Additional environment strings; `EP_*` keys are reserved |
|
|
43
|
+
| `cliOptions` | Advanced flag names without `--`, with string/number/boolean or arrays of strings |
|
|
44
|
+
|
|
45
|
+
All millisecond durations are positive integer Node timer values (up to 2147483647). An outer startup timeout can be shorter than CLI startup: it triggers independent bounded cleanup and is therefore not a total wall-clock deadline for the returned promise. Startup rejection can take the startup deadline plus shutdown deadline plus two seconds for forced pipe closure. As with any JavaScript timer, a blocked event loop delays deadlines.
|
|
46
|
+
|
|
47
|
+
Persistent mode requires nonempty explicit credentials and a nonempty data path. Runtime validation remains necessary even with TypeScript because JavaScript callers and filesystem states are dynamic.
|
|
48
|
+
|
|
49
|
+
Advanced flags cannot override `json`, `parent-stdin`, `config`, `state-file`, `password`, `port`, `database`, `username`, `start-timeout`, `stop-timeout`, `data-dir`, `work-dir`, `cache-dir`, `binaries`, `postgres-version`, `user`, `set`, or `help`. Arguments are passed directly without a shell, using `--name=value` so values starting with dashes remain values. Put PostgreSQL settings in `parameters`; put passwords in `password`.
|
|
50
|
+
|
|
51
|
+
## Executable resolution
|
|
52
|
+
|
|
53
|
+
`resolveCli(source?: CliSource, signal?: AbortSignal): Promise<string>` resolves a local executable or downloads a checksum-verified release before starting tests. Omitted `source` checks `EMBEDDED_POSTGRES_CLI`, then uses the package pin. An explicit options object takes precedence over the environment; omitting its `release` uses the package pin while allowing cache/offline settings.
|
|
54
|
+
|
|
55
|
+
`DEFAULT_CLI_RELEASE: Readonly<CliRelease>` exposes the reviewed `v2.0.0-alpha.1` version and all six SHA-256 pins. Both the object and its checksum map are frozen. `cliPlatform()` returns one of `linux-amd64`, `linux-arm64`, `darwin-amd64`, `darwin-arm64`, `windows-amd64`, `windows-arm64`. See [binary acquisition](binaries.md).
|
|
56
|
+
|
|
57
|
+
## Errors
|
|
58
|
+
|
|
59
|
+
`PostgresError extends Error` has `code` and `stderr` fields. Codes:
|
|
60
|
+
|
|
61
|
+
| Code | Meaning |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| `CONFIG` | Invalid options, release tag or checksum configuration |
|
|
64
|
+
| `BINARY` | Local executable or verified download/cache unavailable |
|
|
65
|
+
| `SPAWN` | The OS could not start the CLI |
|
|
66
|
+
| `PROTOCOL` | Malformed/oversized JSON, wrong protocol, invalid event or order |
|
|
67
|
+
| `STARTUP` | CLI reported startup failure |
|
|
68
|
+
| `TIMEOUT` | Executable download or outer startup deadline |
|
|
69
|
+
| `ABORTED` | Caller cancelled the database lifetime |
|
|
70
|
+
| `EXIT` | CLI runtime failure, unexpected exit/stop or missing clean shutdown |
|
|
71
|
+
| `SHUTDOWN` | Outer shutdown deadline expired; owner was forcibly terminated |
|
|
72
|
+
|
|
73
|
+
No automatic diagnostic logging or global handlers are installed. Error causes from the OS/network are intentionally not retained because they can contain credentials or private URLs. `stderr` is at most 64 KiB of characters before redaction and can grow slightly when masking short secrets. Protocol lines are limited to 64 KiB of decoded characters; an oversized event stops parsing and begins teardown.
|
package/docs/binaries.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# CLI and PostgreSQL binaries
|
|
2
|
+
|
|
3
|
+
These are separate acquisitions:
|
|
4
|
+
|
|
5
|
+
1. The Node package resolves the embedded-postgres **CLI executable**.
|
|
6
|
+
2. The CLI resolves a native **PostgreSQL distribution** using its own pinned manifest, shared cache and supervisor.
|
|
7
|
+
|
|
8
|
+
`startPostgres()` downloads the reviewed [CLI v2.0.0-alpha.1 release](https://github.com/fergusstrange/embedded-postgres/releases/tag/v2.0.0-alpha.1) when needed. Its six SHA-256 pins are embedded in the package and exported as the frozen `DEFAULT_CLI_RELEASE` object. Every asset was downloaded and checked against the published `checksums.txt` before these pins were added. The release tag points to upstream commit `1aa5666cb1b70f044eb74c717e3e0a6ef19cc415`.
|
|
9
|
+
|
|
10
|
+
The selection order is an explicit `cli` option, then `EMBEDDED_POSTGRES_CLI`, then the package's release pin. An explicit download-options object, including `{}`, selects release acquisition even if the environment names a local CLI. It may omit `release` to retain the package default. Downloads happen during resolution/startup, never during npm installation or import. Consumers need no Go toolchain.
|
|
11
|
+
|
|
12
|
+
## Prefetch and offline use
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { resolveCli, startPostgres } from 'embedded-postgres-node';
|
|
16
|
+
|
|
17
|
+
const cli = { cacheDir: '/path/to/cli-cache' };
|
|
18
|
+
await resolveCli(cli); // Optional prefetch for a subsequent offline test run.
|
|
19
|
+
const database = await startPostgres({ cli: { ...cli, offline: true } });
|
|
20
|
+
try { /* tests */ } finally { await database.stop(); }
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This example prefetches only the CLI; PostgreSQL must also be cached or supplied through `binaries` for a completely offline run (see below).
|
|
24
|
+
|
|
25
|
+
## Custom releases and mirrors
|
|
26
|
+
|
|
27
|
+
To select another reviewed v2 release, pass `cli: { release }`. `CliRelease` contains an exact `version` and a `checksums` map from platform to 64-character SHA-256 digest. Copy those hashes from that release's reviewed `checksums.txt` and include every platform your team uses. Runtime resolution never fetches checksums or selects a newer version automatically. Updating this package's default requires a source change and a new npm version.
|
|
28
|
+
|
|
29
|
+
`baseUrl`, when omitted, is `https://github.com/fergusstrange/embedded-postgres/releases/download/`. A mirror must serve `BASE/VERSION/ASSET` with bytes matching the trusted pin. To mirror the default release, use `cli: { release: { ...DEFAULT_CLI_RELEASE, baseUrl: 'https://your-mirror.example/cli/' } }` after importing `DEFAULT_CLI_RELEASE`.
|
|
30
|
+
|
|
31
|
+
For development builds, pass `cli: { path: '/absolute/path/to/embedded-postgres' }` or set `EMBEDDED_POSTGRES_CLI`. Local executables are trusted directly. The optional integration source build uses `.github/upstream.json`, pinned to the published release's commit. Only this development route needs Go.
|
|
32
|
+
|
|
33
|
+
| Node target | Pin key | Upstream asset |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| linux x64 | linux-amd64 | embedded-postgres_linux_amd64 |
|
|
36
|
+
| linux arm64 | linux-arm64 | embedded-postgres_linux_arm64 |
|
|
37
|
+
| darwin x64 | darwin-amd64 | embedded-postgres_darwin_amd64 |
|
|
38
|
+
| darwin arm64 | darwin-arm64 | embedded-postgres_darwin_arm64 |
|
|
39
|
+
| win32 x64 | windows-amd64 | embedded-postgres_windows_amd64.exe |
|
|
40
|
+
| win32 arm64 | windows-arm64 | embedded-postgres_windows_arm64.exe |
|
|
41
|
+
|
|
42
|
+
Windows ARM64 maps to the native Go CLI, which selects x64 PostgreSQL under OS emulation.
|
|
43
|
+
|
|
44
|
+
Downloads have a 120-second default deadline (`downloadTimeoutMs` overrides), a 128 MiB size ceiling, at most five redirects, streamed SHA-256 validation and atomic cache installation. HTTPS is required; HTTP is allowed for loopback test mirrors only. Signed CDN redirect query strings are supported. Base URLs cannot contain credentials, query parameters or fragments. A mismatch is never executed or cached as an installation.
|
|
45
|
+
|
|
46
|
+
The default CLI cache is `~/.cache/embedded-postgres-node/VERSION/DIGEST/ASSET`. Files use mode 0700 where Unix permissions apply. Concurrent installs are safe because they contain identical verified bytes; partial downloads have unique temporary names and are removed on ordinary failure/cancellation. Each cache hit is hashed again. Offline mode refuses missing/corrupted entries. Online mode can replace corrupted entries with verified bytes. The caller owns cache pruning; do not prune executables used by running tests. An abruptly killed process may leave a `.tmp` file, which is never treated as a verified executable.
|
|
47
|
+
|
|
48
|
+
Use a private, trusted cache directory. On Windows, privacy is governed by the parent directory ACL. Hash validation protects downloaded/cached content against accidental or remote corruption; it does not defend against a hostile local process replacing files between validation and execution. A caller-supplied local executable is trusted and is not checksummed.
|
|
49
|
+
|
|
50
|
+
`cli.offline` concerns only the CLI executable. To prohibit PostgreSQL downloads too, use `cliOptions: { offline: true }` with a populated PostgreSQL cache, or supply `binaries`. Use the CLI's `prefetch` command to prepare its PostgreSQL cache ahead of time.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Test-runner integration and migration
|
|
2
|
+
|
|
3
|
+
Choose one database per test for isolation, or one per suite when startup cost matters. With a shared server, allocate a schema/database per parallel test or use an application-appropriate transaction strategy. This library does not reset application state or wrap SQL transactions.
|
|
4
|
+
|
|
5
|
+
## Suite lifecycle
|
|
6
|
+
|
|
7
|
+
The checked-in examples run real SQL in `node:test`, Vitest and Jest. Each uses its runner's suite-level setup/teardown, an explicit startup allowance of 180 seconds, and pool-before-server cleanup. They can be copied into your project after installing your chosen driver. `npm run test:examples` runs all examples locally with the default pinned release, or an optional `EMBEDDED_POSTGRES_CLI` override.
|
|
8
|
+
|
|
9
|
+
Runner hook timeouts must exceed wrapper startup plus shutdown allowances. On a cold or slow network, prefetch CLI/PostgreSQL distributions ahead of the suite or increase both the wrapper and runner timeouts. Tests should not depend on downloads fitting into a runner's common 5-second default.
|
|
10
|
+
|
|
11
|
+
Use `before`/`after` for node:test, `beforeAll`/`afterAll` for Vitest or Jest. When registering teardown separately, ensure it handles setup that failed partway through. The examples use optional handles and nested `try/finally`, so pool cleanup failure still attempts server shutdown.
|
|
12
|
+
|
|
13
|
+
Runner-wide setup in a separate worker/process must keep its owner process and stdin pipe alive until teardown. Passing only a connection URL out of a short-lived setup process causes intentional cleanup when that owner exits. Prefer per-suite ownership or use the Go CLI's separate authenticated `start`/`stop` workflow directly if you explicitly need a daemon. The Node API owns foreground `run` processes only.
|
|
14
|
+
|
|
15
|
+
## Migration from manually managed PostgreSQL
|
|
16
|
+
|
|
17
|
+
1. Replace fixed ports with `startPostgres()` and `database.connectionUrl`.
|
|
18
|
+
2. Pass that URL directly to your existing driver instead of logging it or writing a shared credential file.
|
|
19
|
+
3. Run your existing migration/seed function after startup. The ready event means PostgreSQL is authenticated and the requested database exists; it does not mean your application schema is migrated.
|
|
20
|
+
4. Close pools, streams and migration handles before `await database.stop()`.
|
|
21
|
+
5. Remove persistent test data paths unless persistence is an explicit test requirement. Disposable private clusters are the default.
|
|
22
|
+
|
|
23
|
+
There is no automatic API compatibility claim with unrelated npm packages named `embedded-postgres`. This is a new CLI-based library, not a drop-in major upgrade of another Node package.
|
|
24
|
+
|
|
25
|
+
## Migrations and extensions
|
|
26
|
+
|
|
27
|
+
[The migration example](../examples/migrations.mjs) demonstrates a user-owned async hook inside `withPostgres`. Call your preferred framework's migration function there and await its completion. Throwing from the hook still tears down the server. Keep transaction semantics in the migration framework.
|
|
28
|
+
|
|
29
|
+
For pgvector, PostGIS, AGE or TimescaleDB, supply a compatible custom distribution through `binaries`, including extension libraries and control/SQL files. Configure preload settings with `parameters` before startup, then enable extensions through your driver after readiness. The Node package does not build, download or add core dependencies for extensions. Never patch a shared CLI-owned PostgreSQL cache in place.
|
|
30
|
+
|
|
31
|
+
## RunAs and containers
|
|
32
|
+
|
|
33
|
+
A normal non-root caller uses its own identity. A root caller must explicitly supply both a nonzero Unix UID and nonzero GID via `runAs`; an omitted group is never inferred. A non-root caller may retain its own GID 0; an unprivileged caller cannot choose another identity. Caller-owned workspace/socket parents and persistent paths must be accessible to that account. Windows does not support Unix UID/GID selection. Consult the upstream [non-root guide](https://github.com/fergusstrange/embedded-postgres/blob/v2.0.0-alpha.1/docs/non-root.md) for native runtime libraries and ownership behavior.
|
|
34
|
+
|
|
35
|
+
## Signals and errors
|
|
36
|
+
|
|
37
|
+
The package installs no SIGINT/SIGTERM/exit handlers. Normal framework teardown should await `stop()`. Applications that install their own signal handlers should allow this asynchronous cleanup to finish before exiting. `process.on('exit')` cannot await a promise. `SIGKILL` cannot be handled; pipe closure delegates cleanup to the surviving CLI/supervisor, subject to the limits in the README.
|
|
38
|
+
|
|
39
|
+
An optional `AbortSignal` covers the entire lifetime. If you only want to limit startup, use `startupDeadlineMs`, rather than a signal that your runner may abort before its teardown hooks close pools. `closed` resolves with runtime failure details even when no caller is currently awaiting `stop()`.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Release preparation
|
|
2
|
+
|
|
3
|
+
The public source repository is [fergusstrange/embedded-postgres-node](https://github.com/fergusstrange/embedded-postgres-node), and the npm package is owned by `fergusstrange`. The first library release is `0.1.0-alpha.2` under the `next` dist-tag. Releasing requires the owner's explicit authorization; pushes run CI without publishing. The registration history below records the earlier setup-only placeholder and draft. The `v0.1.0-alpha.1` tag remains unchanged after its publish command failed before uploading a package.
|
|
4
|
+
|
|
5
|
+
## Before a release
|
|
6
|
+
|
|
7
|
+
1. npm ownership is established: `embedded-postgres-node` is owned by `fergusstrange`. Enable account 2FA before approving staged releases or configuring protected publishing. Verify the account with `npm whoami`; do not commit npm credentials.
|
|
8
|
+
2. The owner authorized creation of the public source repository, and package.json now includes its actual `repository`, `homepage` and `bugs` URLs. Keep npm provenance tied to this repository.
|
|
9
|
+
3. Review the API, MIT license, README status and native prerequisites. The default CLI now pins the published `v2.0.0-alpha.1` assets with reviewed checksums. Run `npm run check:release` to verify real downloads, SQL and offline reuse through the installed package. For future pin updates, independently verify all six assets against upstream checksums and update `.github/upstream.json` to the corresponding release commit.
|
|
10
|
+
4. Run the full native CI matrix. Local macOS results are not substitutes for Linux/Windows results.
|
|
11
|
+
5. Configure an `npm` GitHub environment with required reviewers and tag restrictions. Configure npm's trusted publisher for the authorized repository, `release.yml`, and environment `npm`. Keep `NPM_PUBLISH_ENABLED` unset until publication is authorized and these protections are verified. Confirm the installed npm version satisfies the current [trusted publishing requirements](https://docs.npmjs.com/trusted-publishers/).
|
|
12
|
+
|
|
13
|
+
## Preparing a review artifact
|
|
14
|
+
|
|
15
|
+
Update the package version and lockfile together, review the diff, and create an immutable tag `vVERSION`. Run `Review and publish package` with that existing tag and `publish: false`; also select the same tag in GitHub's **Use workflow from** selector (or `gh workflow run --ref TAG`). The dispatch ref and commit must match the selected tag so npm provenance binds to the tested source. The workflow resolves the tag once to a commit, checks it matches package.json, and reruns all twelve CI jobs for that commit. It then packs and smoke-tests the tarball and uploads `reviewed-npm-package`.
|
|
16
|
+
|
|
17
|
+
Locally, `npm run check:package` produces the same `.local/*.tgz` shape and validates contents, runtime dependency absence, ESM/CommonJS startup and declaration consumers. The packaging job also dry-runs publication with the same explicit tarball path, access and dist-tag options as the publish job. No install-time download or Go build is permitted in the npm package. Review the exact tarball before enabling publication.
|
|
18
|
+
|
|
19
|
+
## Publishing an approved artifact
|
|
20
|
+
|
|
21
|
+
After explicit owner authorization, rerun the workflow with `publish: true` and `NPM_PUBLISH_ENABLED=true`. The protected `npm` environment is the final human gate. The publish job consumes the exact tarball from the successful packaging job; it does not rebuild from a moving branch. It uses npm OIDC trusted publishing and provenance, without a stored publishing token.
|
|
22
|
+
|
|
23
|
+
This initial prerelease has `publishConfig.tag: next`, and both workflow commands explicitly pass `--tag next` because publishing a tarball must not rely on its embedded publish configuration. For a stable release, deliberately review changing both to `latest`; changing only the numeric version does not promote the dist-tag. Prefix relative tarball paths with `./` so npm cannot interpret them as GitHub shorthand. Confirm package ownership and first-publication setup with the current npm UI/CLI before attempting the first publish; the workflow does not bypass that onboarding.
|
|
24
|
+
|
|
25
|
+
The workflow validates the presence of real repository metadata and stays disabled unless both the dispatch input and repository variable opt in. These are supplemental controls; GitHub environment protection must be configured by the repository owner. Do not assume naming an environment automatically creates a required-reviewer policy.
|
|
26
|
+
|
|
27
|
+
After publication, verify npm metadata, integrity and provenance, then install the published version into a clean consumer project. Do not overwrite existing versions or move release tags.
|
|
28
|
+
|
|
29
|
+
## npm registration record — 2026-10-06
|
|
30
|
+
|
|
31
|
+
The account owner requested npm setup, and registration used `npm stage publish` with npm 11.15.0 (invoked temporarily; the global npm installation was unchanged). [npm staged publishing](https://docs.npmjs.com/staged-publishing/) creates a public placeholder while keeping the submitted version's code unavailable until approval.
|
|
32
|
+
|
|
33
|
+
- Public package: `embedded-postgres-node`, owner `fergusstrange`.
|
|
34
|
+
- Public placeholder: `0.0.0-stage`; current `latest` points to this npm-generated placeholder.
|
|
35
|
+
- Setup draft: `0.0.0-setup.1`, tag `setup`.
|
|
36
|
+
- Stage ID: `6e550c38-6fbe-439d-bd5b-501539e2f24b`.
|
|
37
|
+
- Planned product prerelease remains `0.1.0-alpha.1`, tag `next`; that version was not staged or published.
|
|
38
|
+
|
|
39
|
+
The setup draft is a snapshot of the locally verified package with only setup version/publishing metadata changed. It was uploaded locally without provenance and should not be approved as the product release. Keep it pending or reject it later after the owner enables 2FA; ordinary publication of a different version remains possible while a draft is pending. The actual product release should come from the reviewed CI tarball with provenance.
|
|
40
|
+
|
|
41
|
+
Inspect the draft with `npm exec --yes --package=npm@11.15.0 -- npm stage view 6e550c38-6fbe-439d-bd5b-501539e2f24b`. The public GitHub repository now exists; trusted publishing still needs configuration. The initial `npm trust list` readback returned registry HTTP 403; no trusted publisher was configured during setup. Do not approve this setup draft merely to make the package settings accessible; ownership already exists.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Validation record — 2026-10-05 to 2026-10-07
|
|
2
|
+
|
|
3
|
+
The dated sections below are historical snapshots. Later sections record subsequent validation and repository setup; earlier release, npm and repository blockers have since changed.
|
|
4
|
+
|
|
5
|
+
Local implementation and packaging validation is complete on macOS ARM64. The Go sibling checkout stayed clean at `356e19e765e005f058501d25f9ff2b893298f53e`. No changes were made to that checkout, no remote was created for this project, and no package was published.
|
|
6
|
+
|
|
7
|
+
## Verified locally
|
|
8
|
+
|
|
9
|
+
| Check | Node 22.23.3 | Node 24.12.0 |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| Protocol, lifecycle, options and download unit/failure tests | 89 passed | 89 passed |
|
|
12
|
+
| Real PostgreSQL lifecycle integration tests | 8 passed | 8 passed |
|
|
13
|
+
| node:test suite example | Passed | Passed |
|
|
14
|
+
| Vitest 5.0.3 suite example | Passed | Passed |
|
|
15
|
+
| Jest 30.5.2 CommonJS suite example | Passed | Passed |
|
|
16
|
+
| Explicit/scoped ownership and migration examples | Passed | Passed |
|
|
17
|
+
| Installed tarball ESM/CommonJS lifecycle and declaration consumers | Passed | Passed |
|
|
18
|
+
| Line / statement coverage | 98.40% | 98.40% |
|
|
19
|
+
| Function coverage | 100% | 100% |
|
|
20
|
+
| Branch coverage | 96.40% | 96.39% |
|
|
21
|
+
|
|
22
|
+
Coverage uses c8 and source maps, includes every production implementation file, and excludes only the logic-free export barrel. Every implementation file exceeds 90% for lines, statements, functions and branches. The gate is enforced **per file**, not just on the aggregate. Uncovered paths include the last-resort inherited-pipe reaping fallback and an OS-dependent concurrent cache rename failure.
|
|
23
|
+
|
|
24
|
+
Real integration used PostgreSQL 18.6.0 and the native Go CLI built from the pinned source with Go 1.26.3. Tests verify SQL, separate ports and isolated clusters, persistent restart with preserved data, cleanup after startup/configuration failure, occupied-port handling, cancellation, normal Node parent exit and forced Node parent termination. Cleanup assertions check both port closure and removal of disposable workspaces. The persistent lease file is intentionally retained by the CLI.
|
|
25
|
+
|
|
26
|
+
Binary download fixtures verify exact platform mapping, checksum rejection, cache revalidation, offline behavior, concurrent installation, bounded downloads, cancellation/deadlines, filesystem failures, signed-CDN redirects and rejection of unsafe redirect destinations. They do not pretend a v2 release is already published.
|
|
27
|
+
|
|
28
|
+
`actionlint v1.7.12` passed for both GitHub Actions workflows. The package-content check installs the generated tarball into an isolated consumer project, starts/stops the protocol fixture through both exports, checks TypeScript `.mts` and `.cts` consumers, confirms zero runtime dependencies and restricts included paths and size.
|
|
29
|
+
|
|
30
|
+
## Decisions and remaining release prerequisites
|
|
31
|
+
|
|
32
|
+
- Use the CLI protocol boundary; no Node native addon or consumer Go toolchain.
|
|
33
|
+
- Support currently maintained LTS lines 22 and 24. Revisit Node 26 when it enters LTS.
|
|
34
|
+
- Keep persistent storage explicit, preserve CLI shutdown grace, and report unconfirmed forced cleanup as a failure.
|
|
35
|
+
- Require a local executable now or caller-reviewed exact release/hash pins; do not guess an unpublished upstream asset.
|
|
36
|
+
- Keep migrations, extension activation and pool cleanup in user-owned JavaScript hooks.
|
|
37
|
+
- Proposed npm name `embedded-postgres-node` returned public-registry 404 on 2026-10-05. This is not a name reservation or publication authorization.
|
|
38
|
+
- The twelve-job native CI matrix is prepared, but **has not run remotely**. Linux, Windows and the other architecture results must be obtained after an authorized repository is created. Upstream Go results are not wrapper results.
|
|
39
|
+
- Add actual public repository metadata, establish npm ownership/trusted publishing, configure required environment reviewers and authorize publication before enabling the release workflow. Publication is off by default; pushes never publish.
|
|
40
|
+
|
|
41
|
+
See [release preparation](releasing.md). Local ignored logs and tarballs are under `.local/`; coverage output is under `coverage/`. Those are generated artifacts and are not committed.
|
|
42
|
+
|
|
43
|
+
## Merged upstream follow-up — 2026-10-06
|
|
44
|
+
|
|
45
|
+
PR #171 merged as `0483a84e6bc989f4cd6893ab852d56fb9756dbcf`. The Node integration pin now uses that merge commit. The local Go checkout at `a879e93bbae7ebde9461c0ee950046ea15099e7a` has the identical Git source tree (`1780db9a0c2c9cd6414ba30f7204a1c75298c1f5`), verified against the GitHub API. A CLI rebuilt from those matching local sources passed all eight real PostgreSQL integration tests on Node 24.12.0/macOS ARM64. The Go checkout was not changed. Documentation now reflects the merged requirement that root RunAs callers provide both a nonzero UID and a nonzero GID.
|
|
46
|
+
|
|
47
|
+
At this check, the merged Go workflow's native tests, supported-version tests, Alpine, static/security checks and coverage all passed. Its [release job](https://github.com/fergusstrange/embedded-postgres/actions/runs/37439342355/job/112191654690) failed while looking up `releases/tags/v2.0.0-alpha.1` (HTTP 404). The corresponding draft release contained no assets. The wrapper therefore still has no safe published default CLI pin; the upstream upload/publication must complete first.
|
|
48
|
+
|
|
49
|
+
The proposed Node repository is not yet present under `fergusstrange/embedded-postgres-node`, the npm name still returns 404, and the local npm CLI reports `ENEEDAUTH`. Remaining work is to pin published CLI assets/checksums and test the ordinary consumer download path, create the authorized public Node repository and run its full twelve-job CI matrix, configure first-release npm authentication and trusted publishing, then publish the reviewed `0.1.0-alpha.1` tarball with the `next` dist-tag and verify installation from npm.
|
|
50
|
+
|
|
51
|
+
## npm setup follow-up — 2026-10-06
|
|
52
|
+
|
|
53
|
+
The owner logged into npm as `fergusstrange` and authorized package setup. The installed npm 11.13.0 lacked staging, so npm 11.15.0 was invoked temporarily without changing the global installation. The packed library was rebuilt and passed its package-content, ESM/CommonJS lifecycle and TypeScript consumer checks before a setup-only snapshot was staged.
|
|
54
|
+
|
|
55
|
+
The registry confirms `embedded-postgres-node` is public and maintained by `fergusstrange`, with only npm's generated `0.0.0-stage` placeholder publicly available. `0.0.0-setup.1` is staged under ID `6e550c38-6fbe-439d-bd5b-501539e2f24b`; the product version `0.1.0-alpha.1` remains unused. No library code was approved for public release. Account 2FA was disabled at setup and still needs owner configuration before release approvals. The public GitHub repository and trusted publishing setup remain outstanding.
|
|
56
|
+
|
|
57
|
+
## Published CLI default — 2026-10-06
|
|
58
|
+
|
|
59
|
+
Upstream [v2.0.0-alpha.1](https://github.com/fergusstrange/embedded-postgres/releases/tag/v2.0.0-alpha.1) was published at 09:31 UTC from commit `1aa5666cb1b70f044eb74c717e3e0a6ef19cc415`. All six CLI assets were downloaded independently, hashed, and verified against the release's `checksums.txt`. Those exact hashes now form `DEFAULT_CLI_RELEASE`; the source integration pin matches the release commit. Runtime resolution uses this fixed release when no explicit source or environment override is supplied. Both exported release metadata and its checksum map are frozen.
|
|
60
|
+
|
|
61
|
+
The following checks passed on macOS ARM64 with the published CLI:
|
|
62
|
+
|
|
63
|
+
| Check | Node 22.23.3 | Node 24.12.0 |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| Unit/failure tests, including default-release checksum rejection | 90 passed | 90 passed |
|
|
66
|
+
| Real PostgreSQL lifecycle integration tests | 8 passed | 8 passed |
|
|
67
|
+
| Installed tarball ESM/CommonJS lifecycle and TypeScript consumers | Passed | Passed |
|
|
68
|
+
| Fresh CLI and PostgreSQL downloads, real SQL and cleanup (ESM) | Passed | Passed |
|
|
69
|
+
| Offline CLI/PostgreSQL cache reuse, real SQL and cleanup (CommonJS) | Passed | Passed |
|
|
70
|
+
| Line / statement coverage | 98.44% | 98.44% |
|
|
71
|
+
| Function coverage | 100% | 100% |
|
|
72
|
+
| Branch coverage | 96.40% | 96.39% |
|
|
73
|
+
|
|
74
|
+
All production implementation files still exceed 90% for every coverage metric. The installed consumer starts with empty CLI and PostgreSQL cache directories and selects the package's default release without caller-provided hashes. It checks SQL, the stopped event, a successful child exit, closed ports and removed disposable workspaces. ESM and CommonJS use the installed tarball, not the source tree. No Go build runs on this consumer path.
|
|
75
|
+
|
|
76
|
+
All node:test, Vitest, Jest, scoped/explicit cleanup and migration examples passed on Node 24 with `EMBEDDED_POSTGRES_CLI`, `EP_TEST_BINARIES` and `EP_TEST_VERSION` unset, exercising automatic default resolution. `actionlint v1.7.12` passed for both workflows. CI now runs `npm run check:release` in all twelve jobs in addition to its source-based integration checks. Remote Linux/Windows and other-architecture results remain pending; verifying their downloaded hashes does not substitute for executing them.
|
|
77
|
+
|
|
78
|
+
Logs use the `.local/pinned-release-` prefix. No Go source was changed, no GitHub remote was created and no npm library release was published in this step. The remaining work is public source repository creation, the twelve-job native CI run, npm trusted-publisher setup, and the reviewed `0.1.0-alpha.1` publication under `next`.
|
|
79
|
+
|
|
80
|
+
## Public repository setup — 2026-10-06
|
|
81
|
+
|
|
82
|
+
The owner authorized the next step: public repository creation and the full CI matrix. [fergusstrange/embedded-postgres-node](https://github.com/fergusstrange/embedded-postgres-node) now exists, and package.json points to its actual source, README and issue URLs. Remote execution results are recorded in the [CI workflow runs](https://github.com/fergusstrange/embedded-postgres-node/actions/workflows/ci.yml), separately from the local evidence above. Publication is still gated; this repository setup does not publish an npm library version.
|
|
83
|
+
|
|
84
|
+
The [first native run](https://github.com/fergusstrange/embedded-postgres-node/actions/runs/37446665926) passed all eight Linux/macOS jobs. Windows exposed test-harness portability problems: drive-letter preload paths were not module URLs, npm's Windows command shell preserved single quotes around coverage patterns, and Node does not close standard descriptors on Windows. The harness now passes `file:` URLs to `--import` and uses double-quoted coverage globs. The EOF regression test injects a reader EOF while a real child remains alive waiting for stdin; it verifies that the library initiates shutdown and reports the missing stopped event. The spawn mock is confined to its own test process. Post-readiness failure tests have a timeout and cleanup hook so fixture regressions cannot stall the entire job. Runtime library behavior and coverage thresholds are unchanged; subsequent CI runs validate these fixes on Windows.
|
|
85
|
+
|
|
86
|
+
The default branch and CI push trigger use `master`, as requested by the owner.
|
|
87
|
+
|
|
88
|
+
Subsequent Windows runs passed all 90 unit tests and the installed-package fresh-download/offline checks. Source-integration archive extraction now uses relative paths because Git Bash's GNU tar interprets drive-letter archive paths as remote hosts. Real integration then exposed a runtime issue in both parent-death cases: Node's Windows job object kills ordinary child processes immediately when Node exits, preventing CLI cleanup. The wrapper now launches the Windows CLI with `detached: true` while retaining its process reference and ownership pipes. This lets the CLI observe stdin EOF and finish cleanup after normal or forced Node termination. The existing real parent-death tests cover both port closure and disposable-workspace removal; no assertions are skipped or relaxed.
|
|
89
|
+
|
|
90
|
+
## Completed native matrix and release preparation — 2026-10-07
|
|
91
|
+
|
|
92
|
+
Commit `640966c3fdbe218a087edc7886ae2255310a10f1` passed [all twelve native CI jobs](https://github.com/fergusstrange/embedded-postgres-node/actions/runs/37450528359): Node 22 and 24 on Linux, macOS and Windows, each on x64 and ARM64. Every job passed 90 unit/failure tests, per-file coverage thresholds, installed-tarball ESM/CommonJS and declaration checks, fresh published CLI/PostgreSQL downloads and offline reuse, all eight real lifecycle tests, and the runner/migration examples. Both Windows parent-death cases passed with port and workspace cleanup verified.
|
|
93
|
+
|
|
94
|
+
The owner authorized the first npm alpha release. The release preparation updates installation documentation; runtime code and dependency versions remain those validated above. The tag-based release workflow repeats the native matrix and package checks before publication, which requires approval in the protected `npm` environment.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = { testEnvironment: 'node', testMatch: ['**/examples/jest.test.cjs'], maxWorkers: 1 };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const { startPostgres } = require('embedded-postgres-node');
|
|
2
|
+
const { Pool } = require('pg');
|
|
3
|
+
|
|
4
|
+
describe('repository (one database per suite)', () => {
|
|
5
|
+
let database;
|
|
6
|
+
let pool;
|
|
7
|
+
beforeAll(async () => {
|
|
8
|
+
database = await startPostgres({
|
|
9
|
+
...(process.env.EP_TEST_BINARIES ? { binaries: process.env.EP_TEST_BINARIES } : {}),
|
|
10
|
+
...(process.env.EP_TEST_VERSION ? { postgresVersion: process.env.EP_TEST_VERSION } : {}),
|
|
11
|
+
});
|
|
12
|
+
pool = new Pool({ connectionString: database.connectionUrl });
|
|
13
|
+
await pool.query('CREATE TABLE widgets (id integer PRIMARY KEY)');
|
|
14
|
+
}, 180000);
|
|
15
|
+
afterAll(async () => {
|
|
16
|
+
try { await pool?.end(); }
|
|
17
|
+
finally { await database?.stop(); }
|
|
18
|
+
}, 20000);
|
|
19
|
+
test('queries the database', async () => {
|
|
20
|
+
expect((await pool.query('SELECT 42 AS answer')).rows[0].answer).toBe(42);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import pg from 'pg';
|
|
3
|
+
import { withPostgres } from 'embedded-postgres-node';
|
|
4
|
+
import { options } from './options.mjs';
|
|
5
|
+
|
|
6
|
+
// Your driver and migration framework are application dependencies.
|
|
7
|
+
await withPostgres(options, async database => {
|
|
8
|
+
const pool = new pg.Pool({ connectionString: database.connectionUrl });
|
|
9
|
+
try {
|
|
10
|
+
// Replace this with your framework's awaited migration hook.
|
|
11
|
+
await pool.query('CREATE TABLE accounts (id integer PRIMARY KEY, name text NOT NULL)');
|
|
12
|
+
await pool.query('INSERT INTO accounts VALUES ($1, $2)', [1, 'Ada']);
|
|
13
|
+
assert.equal((await pool.query('SELECT name FROM accounts WHERE id = 1')).rows[0].name, 'Ada');
|
|
14
|
+
// Extension libraries must already exist in the selected distribution.
|
|
15
|
+
// await pool.query('CREATE EXTENSION IF NOT EXISTS vector');
|
|
16
|
+
} finally { await pool.end(); }
|
|
17
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { after, before, describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import pg from 'pg';
|
|
4
|
+
import { startPostgres } from 'embedded-postgres-node';
|
|
5
|
+
import { options } from './options.mjs';
|
|
6
|
+
|
|
7
|
+
describe('repository (one database per suite)', () => {
|
|
8
|
+
let database;
|
|
9
|
+
let pool;
|
|
10
|
+
before(async () => {
|
|
11
|
+
database = await startPostgres(options);
|
|
12
|
+
pool = new pg.Pool({ connectionString: database.connectionUrl });
|
|
13
|
+
await pool.query('CREATE TABLE widgets (id integer PRIMARY KEY)');
|
|
14
|
+
}, { timeout: 180000 });
|
|
15
|
+
after(async () => {
|
|
16
|
+
try { await pool?.end(); }
|
|
17
|
+
finally { await database?.stop(); }
|
|
18
|
+
});
|
|
19
|
+
it('queries the database', async () => {
|
|
20
|
+
assert.equal((await pool.query('SELECT 42 AS answer')).rows[0].answer, 42);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Development convenience for these runnable examples. CLI selection defaults
|
|
2
|
+
// to the package's verified release; EMBEDDED_POSTGRES_CLI can override it.
|
|
3
|
+
export const options = {
|
|
4
|
+
...(process.env.EP_TEST_BINARIES ? { binaries: process.env.EP_TEST_BINARIES } : {}),
|
|
5
|
+
...(process.env.EP_TEST_VERSION ? { postgresVersion: process.env.EP_TEST_VERSION } : {}),
|
|
6
|
+
};
|