Reference
stage-pg
Functions
assertInstanceRelativeDirectory
export function assertInstanceRelativeDirectory(instanceRoot: string, value: string, label: string): void;
š assertInstanceRelativeDirectory on GitHub
assertPrivateFile
export function assertPrivateFile(path: string): Promise<void>;
š assertPrivateFile on GitHub
buildCloudflaredSpec
export function buildCloudflaredSpec(config: ResolvedStagePgConfig, executable?: string): ProcessSpec;
š buildCloudflaredSpec on GitHub
buildTailscaleServeCommand
export function buildTailscaleServeCommand(config: Pick<ResolvedStagePgConfig, 'originAddress' | 'port' | 'tailscaleAdmins'>, executable?: string): TailscaleServeCommand;
š buildTailscaleServeCommand on GitHub
canTransition
export function canTransition(from: LifecycleState, to: LifecycleState): boolean;
createConfig
export function createConfig(options: InitConfigOptions, port: number): StagePgConfig;
createSecrets
export function createSecrets(): InstanceSecrets;
dispatchCli
export function dispatchCli(args: readonly string[], dependencies?: CliDependencies): Promise<number>;
formatCommand
export function formatCommand(command: string, args: readonly string[], secrets?: readonly string[]): string;
getCloudflareWorkersOrigin
export function getCloudflareWorkersOrigin(config: Pick<ResolvedStagePgConfig, 'originAddress' | 'port'>): CloudflareWorkersOrigin;
š getCloudflareWorkersOrigin on GitHub
getInstancePaths
export function getInstancePaths(instance: string): InstancePaths;
š getInstancePaths on GitHub
getTailscaleServeContract
Describes the externally provisioned Tailscale Serve TCP forwarder. stage-pg deliberately does not execute this command or probe Serve status.
export function getTailscaleServeContract(config: Pick<ResolvedStagePgConfig, 'originAddress' | 'port' | 'tailscaleAdmins'>): TailscaleServeContract;
š getTailscaleServeContract on GitHub
isPortAvailable
export function isPortAvailable(port: number, host?: string): Promise<boolean>;
š isPortAvailable on GitHub
parseConfig
export function parseConfig(value: unknown): StagePgConfig;
pruneLocalArtifacts
export function pruneLocalArtifacts(directory: string, keepLocal: number, protectedDump?: string): Promise<string[]>;
š pruneLocalArtifacts on GitHub
redactArguments
export function redactArguments(args: readonly string[], secrets?: readonly string[]): string[];
š redactArguments on GitHub
redactText
export function redactText(value: string, secrets?: readonly string[]): string;
resolveConfigPaths
export function resolveConfigPaths(config: StagePgConfig, instanceRoot: string): ResolvedStagePgConfig;
š resolveConfigPaths on GitHub
runChecked
export function runChecked(runner: ProcessRunner, spec: ProcessSpec): Promise<ProcessResult>;
runCli
export function runCli(args?: readonly string[], dependencies?: CliDependencies): Promise<number>;
successfulResult
export function successfulResult(command: string, args?: readonly string[]): ProcessResult;
š successfulResult on GitHub
transitionState
export function transitionState(state: InstanceState, lifecycle: LifecycleState, patch?: StatePatch): InstanceState;
š transitionState on GitHub
validateCloudflareTokenFile
export function validateCloudflareTokenFile(tokenFile: string): Promise<void>;
š validateCloudflareTokenFile on GitHub
validateCloudflareWorkersConfig
export function validateCloudflareWorkersConfig(value: unknown, originAddress?: typeof DEFAULT_ORIGIN_ADDRESS): asserts value is CloudflareWorkersConfig;
š validateCloudflareWorkersConfig on GitHub
validateConfig
export function validateConfig(value: unknown): asserts value is StagePgConfig;
validateSecrets
export function validateSecrets(value: unknown): asserts value is InstanceSecrets;
š validateSecrets on GitHub
validateState
export function validateState(value: unknown): asserts value is InstanceState;
validateTailscaleAdminsConfig
export function validateTailscaleAdminsConfig(value: unknown): asserts value is TailscaleAdminsConfig;
š validateTailscaleAdminsConfig on GitHub
validateTlsConfig
export function validateTlsConfig(value: unknown, label?: string): asserts value is PostgresTlsConfig;
š validateTlsConfig on GitHub
writeJsonAtomic
export function writeJsonAtomic(file: string, value: unknown, mode: number): Promise<void>;
š writeJsonAtomic on GitHub
Classes
BackupCoordinator
export class BackupCoordinator {
private readonly runner;
private readonly discovery;
private readonly suppliedTools?;
private readonly storeFactory;
private readonly secretStoreFactory;
private readonly uploader;
private readonly now;
constructor(options?: BackupCoordinatorOptions);
run(instance: string): Promise<void>;
backup(instance: string): Promise<BackupResult>;
private getTools;
private dumpSpec;
private updateBackupState;
}
š BackupCoordinator on GitHub
BackupUploadError
export class BackupUploadError extends Error {
readonly artifact: BackupArtifact;
readonly cause: unknown;
constructor(artifact: BackupArtifact, cause: unknown);
}
š BackupUploadError on GitHub
ChildProcessRunner
export class ChildProcessRunner implements ProcessRunner {
private readonly environment;
private readonly redactions;
private readonly onDiagnostic?;
constructor(options?: ChildProcessRunnerOptions);
run(spec: ProcessSpec): Promise<ProcessResult>;
spawn(spec: ProcessSpec): ManagedProcess;
}
š ChildProcessRunner on GitHub
CloudflareTunnel
Owns only the local cloudflared child process. Tunnel and Workers VPC resources are configured outside stage-pg.
export class CloudflareTunnel implements CloudflareLifecycle {
private readonly runner;
private readonly discovery;
private readonly suppliedExecutable?;
private resolvedExecutable?;
constructor(options?: CloudflareTunnelOptions);
validate(config: ResolvedStagePgConfig): Promise<void>;
start(config: ResolvedStagePgConfig): Promise<ManagedProcess | undefined>;
private getExecutable;
}
š CloudflareTunnel on GitHub
ExecutableDiscovery
export class ExecutableDiscovery {
private readonly environment;
private readonly runner?;
constructor(options?: {
environment?: NodeJS.ProcessEnv;
runner?: ProcessRunner;
});
find(name: string, preferredDirectory?: string): Promise<DiscoveredExecutable>;
findOptional(name: string, preferredDirectory?: string): Promise<DiscoveredExecutable | undefined>;
discoverPostgresTools(preferredDirectory?: string): Promise<PostgresTools>;
private pathCandidates;
private readVersion;
}
š ExecutableDiscovery on GitHub
FakeManagedProcess
class FakeManagedProcess implements ManagedProcess {
readonly pid: undefined;
readonly displayCommand: string;
private readonly result;
private killedWith;
constructor(displayCommand: string, result: ProcessResult | Promise<ProcessResult>);
wait(): Promise<ProcessResult>;
kill(signal?: NodeJS.Signals): void;
get signal(): NodeJS.Signals | undefined;
}
š FakeManagedProcess on GitHub
FakeProcessRunner
export class FakeProcessRunner implements ProcessRunner {
readonly calls: ProcessSpec[];
private readonly responses;
private readonly spawned;
setResponse(command: string, result: ProcessResult | (() => ProcessResult | Promise<ProcessResult>)): void;
run(spec: ProcessSpec): Promise<ProcessResult>;
spawn(spec: ProcessSpec): FakeManagedProcess;
get lastSpawned(): FakeManagedProcess | undefined;
private responseFor;
}
š FakeProcessRunner on GitHub
InstanceStore
export class InstanceStore {
readonly paths: InstancePaths;
constructor(instance: string | InstancePaths);
ensureLayout(): Promise<void>;
ensureFresh(): Promise<void>;
hasConfig(): Promise<boolean>;
readConfig(): Promise<StagePgConfig>;
writeConfig(config: StagePgConfig): Promise<void>;
readResolvedConfig(): Promise<ResolvedStagePgConfig>;
readState(): Promise<InstanceState | undefined>;
readRequiredState(): Promise<InstanceState>;
writeState(state: InstanceState): Promise<void>;
createInitialState(port: number): Promise<InstanceState>;
updateState(update: (state: InstanceState) => InstanceState): Promise<InstanceState>;
}
LifecycleCoordinator
export class LifecycleCoordinator {
private readonly portAllocator;
private readonly postgres;
private readonly cloudflare;
private readonly storeFactory;
private readonly secretStoreFactory;
private readonly now;
constructor(options?: LifecycleCoordinatorOptions);
init(instance: string, options: InitConfigOptions): Promise<InstanceState>;
run(instance: string, options?: RunOptions): Promise<InstanceState>;
private markFailed;
private releasePort;
}
š LifecycleCoordinator on GitHub
LocalPostgres
export class LocalPostgres implements PostgresLifecycle {
private readonly runner;
private readonly discovery;
private readonly suppliedTools?;
private readonly bootstrapRole;
private readonly startupTimeoutMs;
private readonly pollIntervalMs;
constructor(options?: LocalPostgresOptions);
initialize(config: ResolvedStagePgConfig, secrets: InstanceSecrets): Promise<void>;
start(config: ResolvedStagePgConfig): Promise<PostgresHandle>;
stop(config: ResolvedStagePgConfig, mode?: PostgresStopMode): Promise<void>;
isReady(config: ResolvedStagePgConfig): Promise<boolean>;
stopWithPgCtl(config: ResolvedStagePgConfig, tools: PostgresTools, mode: PostgresStopMode): Promise<void>;
private getTools;
private startWithPgCtl;
private waitUntilReady;
private createRolesAndDatabase;
private verifyRole;
private writePostgresqlConfig;
private writePgHba;
private pgCtlSpec;
private psqlSpec;
}
LocalSecretStore
export class LocalSecretStore implements SecretStore {
private readonly paths;
constructor(paths: Pick<InstancePaths, 'secretsFile'>);
read(): Promise<InstanceSecrets>;
write(secrets: InstanceSecrets): Promise<void>;
}
š LocalSecretStore on GitHub
PersistentPortAllocator
export class PersistentPortAllocator implements PortAllocator {
private readonly registryFile;
private readonly probe;
private readonly lockTimeoutMs;
constructor(options?: PortAllocatorOptions);
allocate(instance: string, range?: PortRange): Promise<number>;
reserve(instance: string, port: number): Promise<void>;
assertAvailable(instance: string, port: number): Promise<void>;
release(instance: string): Promise<void>;
private isReservedByAnother;
private withRegistry;
private readRegistry;
}
š PersistentPortAllocator on GitHub
PortCollisionError
export class PortCollisionError extends Error {
readonly code = "PORT_COLLISION";
readonly port: number;
constructor(port: number, message?: string);
}
š PortCollisionError on GitHub
ProcessError
export class ProcessError extends Error {
readonly result: ProcessResult;
constructor(result: ProcessResult);
}
RestoreCoordinator
export class RestoreCoordinator {
private readonly runner;
private readonly discovery;
private readonly suppliedTools?;
private readonly storeFactory;
private readonly secretStoreFactory;
private readonly now;
constructor(options?: BackupCoordinatorOptions);
run(instance: string, backup: string): Promise<void>;
restore(instance: string, backup: string): Promise<RestoreResult>;
private getTools;
private assertCanCreateDatabase;
private findRestoreDatabase;
private assertDatabaseOwner;
private psqlSpec;
private pgRestoreSpec;
}
š RestoreCoordinator on GitHub
S3CompatibleBackupUploader
export class S3CompatibleBackupUploader implements BackupUploader {
private readonly environment;
private readonly fetcher;
private readonly now;
constructor(options?: {
environment?: NodeJS.ProcessEnv;
fetch?: typeof fetch;
now?: () => Date;
});
upload(artifact: BackupArtifact, destination: S3BackupConfig): Promise<void>;
private putObject;
}
š S3CompatibleBackupUploader on GitHub
TailscaleAdapter
export class TailscaleAdapter {
getServeContract(config: Pick<ResolvedStagePgConfig, 'originAddress' | 'port' | 'tailscaleAdmins'>): TailscaleServeContract;
buildServeCommand(config: Pick<ResolvedStagePgConfig, 'originAddress' | 'port' | 'tailscaleAdmins'>, executable?: string): TailscaleServeCommand;
}
š TailscaleAdapter on GitHub
Constants
BACKUP_FORMAT
export const BACKUP_FORMAT: "custom";
BACKUP_MANIFEST_SCHEMA_VERSION
export const BACKUP_MANIFEST_SCHEMA_VERSION: 1;
š BACKUP_MANIFEST_SCHEMA_VERSION on GitHub
CONFIG_SCHEMA_VERSION
export const CONFIG_SCHEMA_VERSION: 1;
š CONFIG_SCHEMA_VERSION on GitHub
DEFAULT_ADMIN_ROLE
export const DEFAULT_ADMIN_ROLE = "stage_admin";
š DEFAULT_ADMIN_ROLE on GitHub
DEFAULT_APP_ROLE
export const DEFAULT_APP_ROLE = "stage_app";
š DEFAULT_APP_ROLE on GitHub
DEFAULT_DATABASE
export const DEFAULT_DATABASE = "staging";
š DEFAULT_DATABASE on GitHub
DEFAULT_ORIGIN_ADDRESS
export const DEFAULT_ORIGIN_ADDRESS = "127.0.0.1";
š DEFAULT_ORIGIN_ADDRESS on GitHub
DEFAULT_PORT_RANGE
export const DEFAULT_PORT_RANGE: {
readonly start: 55432;
readonly end: 55531;
};
š DEFAULT_PORT_RANGE on GitHub
POSTGRES_EXECUTABLES
export const POSTGRES_EXECUTABLES: readonly ["initdb", "postgres", "pg_ctl", "psql", "pg_dump", "pg_restore"];
š POSTGRES_EXECUTABLES on GitHub
USAGE
export const USAGE = "Usage:\n stage-pg init <instance> --tls-cert <path> --tls-key <path> [options]\n stage-pg run <instance>\n stage-pg backup <instance>\n stage-pg restore <instance> <backup>\n\nInit options:\n --tls-cert <path>\n --tls-key <path>\n --tls-ca <path>\n --postgres-bin-dir <path>\n --postgres-major <number>\n --database <name>\n --app-role <name>\n --admin-role <name>\n --cloudflare-token-file <path>\n";
Types
BackupArtifact
export interface BackupArtifact {
directory: string;
dumpPath: string;
manifestPath: string;
checksumPath: string;
manifest: BackupManifest;
}
BackupCoordinatorOptions
export interface BackupCoordinatorOptions {
runner?: ProcessRunner;
discovery?: ExecutableDiscovery;
tools?: PostgresTools;
store?: (instance: string) => InstanceStore;
secretStore?: (paths: Pick<InstancePaths, 'secretsFile'>) => SecretStore;
secretStoreFactory?: (paths: Pick<InstancePaths, 'secretsFile'>) => SecretStore;
uploader?: BackupUploader;
now?: () => string;
}
š BackupCoordinatorOptions on GitHub
BackupHandler
export interface BackupHandler {
run(instance: string): Promise<void>;
}
BackupManifest
export interface BackupManifest {
schemaVersion: typeof BACKUP_MANIFEST_SCHEMA_VERSION;
instanceId: string;
database: string;
postgresMajor: number;
format: typeof BACKUP_FORMAT;
filename: string;
size: number;
sha256: string;
createdAt: string;
upload: {
status: BackupUploadStatus;
error?: string;
};
}
BackupResult
export interface BackupResult {
artifact: BackupArtifact;
uploadStatus: BackupUploadStatus;
}
BackupUploader
export interface BackupUploader {
upload(artifact: BackupArtifact, destination: S3BackupConfig): Promise<void>;
}
BackupUploadStatus
export type BackupUploadStatus = 'not-configured' | 'pending' | 'uploaded' | 'failed';
š BackupUploadStatus on GitHub
ChildProcessRunnerOptions
export interface ChildProcessRunnerOptions {
environment?: NodeJS.ProcessEnv;
redactions?: readonly string[];
onDiagnostic?: (message: string) => void;
}
š ChildProcessRunnerOptions on GitHub
CliDependencies
export interface CliDependencies {
lifecycle?: CliLifecycle;
backup?: BackupHandler;
restore?: RestoreHandler;
stdout?: CliWriter;
stderr?: CliWriter;
signal?: AbortSignal;
}
š CliDependencies on GitHub
CliLifecycle
export interface CliLifecycle {
init(instance: string, options: InitConfigOptions): Promise<unknown>;
run(instance: string, options?: RunOptions): Promise<unknown>;
}
CliWriter
export interface CliWriter {
write(value: string): unknown;
}
CloudflareLifecycle
export interface CloudflareLifecycle {
validate?(config: ResolvedStagePgConfig): Promise<void>;
start(config: ResolvedStagePgConfig): Promise<ManagedProcess | undefined>;
}
š CloudflareLifecycle on GitHub
CloudflareTunnelOptions
export interface CloudflareTunnelOptions {
runner?: ProcessRunner;
discovery?: ExecutableDiscovery;
executable?: string;
}
Properties
executableAn explicit path is useful for tests and host-specific installations.
š CloudflareTunnelOptions on GitHub
CloudflareWorkersConfig
export interface CloudflareWorkersConfig {
originAddress: typeof DEFAULT_ORIGIN_ADDRESS;
tokenFile?: string;
}
Properties
tokenFileA path only; the token contents never belong in public configuration.
š CloudflareWorkersConfig on GitHub
CloudflareWorkersOrigin
export interface CloudflareWorkersOrigin {
address: string;
port: number;
target: string;
}
š CloudflareWorkersOrigin on GitHub
DiscoveredExecutable
export interface DiscoveredExecutable {
name: string;
path: string;
}
š DiscoveredExecutable on GitHub
InitConfigOptions
export interface InitConfigOptions {
postgresMajor?: number;
database?: string;
appRole?: string;
adminRole?: string;
postgresBinDir?: string;
tls: PostgresTlsConfig;
cloudflareTokenFile?: string;
}
š InitConfigOptions on GitHub
InstancePaths
export interface InstancePaths {
root: string;
configFile: string;
secretsFile: string;
stateFile: string;
dataDirectory: string;
backupsDirectory: string;
}
InstanceSecrets
export interface InstanceSecrets {
appPassword: string;
adminPassword: string;
}
š InstanceSecrets on GitHub
InstanceState
export interface InstanceState {
schemaVersion: 1;
instanceId: string;
lifecycle: LifecycleState;
port: number;
lastStartedAt?: string;
lastStoppedAt?: string;
lastError?: string;
postgresPid?: number;
lastBackup?: LastBackupState;
}
LastBackupState
export interface LastBackupState {
completedAt: string;
filename: string;
sha256: string;
uploadStatus: BackupUploadStatus;
uploadError?: string;
}
š LastBackupState on GitHub
LifecycleCoordinatorOptions
export interface LifecycleCoordinatorOptions {
portAllocator?: PortAllocator;
postgres?: PostgresLifecycle;
cloudflare?: CloudflareLifecycle;
store?: (instance: string) => InstanceStore;
secretStore?: (paths: Pick<InstancePaths, 'secretsFile'>) => SecretStore;
now?: () => string;
}
š LifecycleCoordinatorOptions on GitHub
LifecycleState
export type LifecycleState = 'uninitialized' | 'initializing' | 'initialized' | 'starting' | 'running' | 'stopping' | 'failed';
LocalPostgresOptions
export interface LocalPostgresOptions {
runner?: ProcessRunner;
discovery?: ExecutableDiscovery;
tools?: PostgresTools;
bootstrapRole?: string;
startupTimeoutMs?: number;
pollIntervalMs?: number;
}
š LocalPostgresOptions on GitHub
ManagedProcess
export interface ManagedProcess {
readonly pid: number | undefined;
readonly displayCommand: string;
wait(): Promise<ProcessResult>;
kill(signal?: NodeJS.Signals): void;
}
PortAllocator
export interface PortAllocator {
allocate(instance: string, range?: PortRange): Promise<number>;
reserve(instance: string, port: number): Promise<void>;
assertAvailable(instance: string, port: number): Promise<void>;
release?(instance: string): Promise<void>;
}
PortAllocatorOptions
export interface PortAllocatorOptions {
registryFile?: string;
probe?: (port: number) => Promise<boolean>;
lockTimeoutMs?: number;
}
š PortAllocatorOptions on GitHub
PortRange
export interface PortRange {
start: number;
end: number;
}
PostgresHandle
export interface PostgresHandle extends ManagedProcess {
stop(): Promise<void>;
}
PostgresLifecycle
export interface PostgresLifecycle {
initialize(config: ResolvedStagePgConfig, secrets: InstanceSecrets): Promise<void>;
start(config: ResolvedStagePgConfig): Promise<PostgresHandle>;
stop(config: ResolvedStagePgConfig, mode?: PostgresStopMode): Promise<void>;
isReady(config: ResolvedStagePgConfig): Promise<boolean>;
}
š PostgresLifecycle on GitHub
PostgresStopMode
export type PostgresStopMode = 'smart' | 'fast' | 'immediate';
š PostgresStopMode on GitHub
PostgresTlsConfig
export interface PostgresTlsConfig {
certFile: string;
keyFile: string;
caFile?: string;
}
Properties
certFileAn externally provisioned PostgreSQL server certificate.keyFileAn externally provisioned PostgreSQL server private key.caFileOptional CA bundle used by PostgreSQL for certificate verification.
š PostgresTlsConfig on GitHub
PostgresTools
export interface PostgresTools {
initdb: DiscoveredExecutable;
postgres: DiscoveredExecutable;
pgCtl: DiscoveredExecutable;
psql: DiscoveredExecutable;
pgDump: DiscoveredExecutable;
pgRestore: DiscoveredExecutable;
majorVersion: number;
}
ProcessResult
export interface ProcessResult {
command: string;
args: string[];
exitCode: number | null;
signal: NodeJS.Signals | null;
stdout: string;
stderr: string;
durationMs: number;
}
ProcessRunner
export interface ProcessRunner {
run(spec: ProcessSpec): Promise<ProcessResult>;
spawn(spec: ProcessSpec): ManagedProcess;
}
ProcessSpec
export interface ProcessSpec {
command: string;
args?: readonly string[];
cwd?: string;
env?: NodeJS.ProcessEnv;
input?: string;
redactions?: readonly string[];
timeoutMs?: number;
}
ResolvedStagePgConfig
export interface ResolvedStagePgConfig extends Omit<StagePgConfig, 'postgres' | 'cloudflareWorkers' | 'backup'> {
postgres: Omit<StagePgConfig['postgres'], 'dataDirectory' | 'binDir' | 'tls'> & {
dataDirectory: string;
binDir?: string;
tls: PostgresTlsConfig;
};
cloudflareWorkers: Omit<StagePgConfig['cloudflareWorkers'], 'tokenFile'> & {
tokenFile?: string;
};
backup: Omit<StagePgConfig['backup'], 'directory'> & {
directory: string;
};
}
š ResolvedStagePgConfig on GitHub
RestoreHandler
export interface RestoreHandler {
run(instance: string, backup: string): Promise<void>;
}
RestoreResult
export interface RestoreResult {
database: string;
manifest: BackupManifest;
dumpPath: string;
}
RunOptions
export interface RunOptions {
signal?: AbortSignal;
}
S3BackupConfig
export interface S3BackupConfig {
bucket: string;
prefix: string;
endpoint?: string;
region: string;
forcePathStyle?: boolean;
}
SecretStore
export interface SecretStore {
read(): Promise<InstanceSecrets>;
write(secrets: InstanceSecrets): Promise<void>;
}
StagePgConfig
export interface StagePgConfig {
schemaVersion: typeof CONFIG_SCHEMA_VERSION;
postgresMajor: number;
database: string;
appRole: string;
adminRole: string;
port: number;
originAddress: typeof DEFAULT_ORIGIN_ADDRESS;
postgres: {
dataDirectory: string;
binDir?: string;
tls: PostgresTlsConfig;
};
cloudflareWorkers: CloudflareWorkersConfig;
tailscaleAdmins: TailscaleAdminsConfig;
backup: {
intervalMinutes: number;
directory: string;
keepLocal: number;
s3?: S3BackupConfig;
};
}
StatePatch
export type StatePatch = Partial<Pick<InstanceState, 'lastStartedAt' | 'lastStoppedAt' | 'lastError' | 'postgresPid'>>;
TailscaleAdminsConfig
export interface TailscaleAdminsConfig {
mode: 'serve-tcp';
}
š TailscaleAdminsConfig on GitHub
TailscaleServeCommand
export interface TailscaleServeCommand {
command: string;
args: readonly string[];
}
š TailscaleServeCommand on GitHub
TailscaleServeContract
export interface TailscaleServeContract {
mode: TailscaleAdminsConfig['mode'];
listenPort: number;
targetAddress: string;
targetPort: number;
target: string;
}