Features
Shutdown
The component that starts a server should own its shutdown, while the core API supports explicit cross-process cleanup when ownership must be recovered.
Stop the Returned Server
Call stop() from the same scope that called startPostgres:
const postgres = await startPostgres({ dataDir: '.postgres' })
try {
await runApplication(postgres.connectionString)
} finally {
await postgres.stop()
}
Calling stop() more than once is safe. Startup failures after spawn also
attempt to stop the child before rejecting.
The managed child first receives SIGINT. If it remains alive after
stopTimeoutMs, it receives SIGTERM and gets one more timeout interval:
const postgres = await startPostgres({
dataDir: '.postgres',
stopTimeoutMs: 10_000,
})
Use Explicit Resource Management
Runtimes with explicit resource management can bind shutdown to a lexical scope:
await using postgres = await startPostgres({
dataDir: '.postgres',
})
await runApplication(postgres.connectionString)
Leaving the scope calls the same idempotent stop() operation.
Stop by Data Directory
The core API can stop a server from a different process by reading
postmaster.pid:
import { stopPostgresDataDir } from 'local-postgres/core'
await stopPostgresDataDir({
dataDir: '.postgres',
mode: 'fast',
timeoutMs: 10_000,
})
Delayed cleanup jobs should capture the original server PID and pass it as
expectedPid:
await stopPostgresDataDir({
dataDir: '.postgres',
expectedPid: postgres.pid,
})
If postmaster.pid identifies a different process, the call returns without
signaling it. This prevents an older job from stopping a newer server that
reused the same data directory.
When the expected postmaster is already dead on macOS, shutdown can remove only the exact, unattached System V segment recorded by that postmaster. See Shared Memory Troubleshooting for the verification contract and recovery limits.
| Mode | PostgreSQL signal | Intent |
|---|---|---|
smart |
SIGTERM |
Wait for clients to disconnect. |
fast |
SIGINT |
Roll back active work and disconnect clients; the default. |
immediate |
SIGQUIT |
Stop without normal shutdown processing. |
Use this only when the caller intentionally owns that cluster. Startup itself never adopts or stops an existing live server.
Wait for Idle Connections
Cross-process shutdown can wait for connection counts before signaling:
await stopPostgresDataDir({
dataDir: '.postgres',
listen: { type: 'tcp', host: '127.0.0.1', port: 54329 },
waitForIdle: {
database: 'app_dev',
minConnections: 0,
timeoutMs: 30_000,
},
})
listen is required when waitForIdle is enabled. See
Core Lifecycle API for the lower-level ownership model.