pg-here 0.1.0 → 0.1.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/README.md +5 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +109 -7
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -47,6 +47,11 @@ await pgHere.stop();
|
|
|
47
47
|
`databaseConnectionString` points to your target DB (`my_app` above).
|
|
48
48
|
If the DB does not exist yet, `createDatabaseIfMissing: true` creates it on startup.
|
|
49
49
|
Set `postgresVersion` if you want to pin/select a specific PostgreSQL version.
|
|
50
|
+
By default, `startPgHere()` installs SIGINT/SIGTERM shutdown hooks that stop Postgres when
|
|
51
|
+
your process exits, and `stop()` preserves data (no cluster cleanup/delete).
|
|
52
|
+
Use `await pgHere.cleanup()` only when you explicitly want full resource cleanup.
|
|
53
|
+
`pg_stat_statements` is enabled automatically (`shared_preload_libraries` + extension creation).
|
|
54
|
+
Set `enablePgStatStatements: false` to opt out.
|
|
50
55
|
|
|
51
56
|
### CLI Options
|
|
52
57
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PostgresInstance } from "pg-embedded";
|
|
2
|
+
export type PgHereShutdownSignal = "SIGINT" | "SIGTERM" | "SIGHUP";
|
|
2
3
|
export interface PgHereOptions {
|
|
3
4
|
projectDir?: string;
|
|
4
5
|
dataDir?: string;
|
|
@@ -11,16 +12,25 @@ export interface PgHereOptions {
|
|
|
11
12
|
persistent?: boolean;
|
|
12
13
|
database?: string;
|
|
13
14
|
createDatabaseIfMissing?: boolean;
|
|
15
|
+
registerProcessShutdownHandlers?: boolean;
|
|
16
|
+
shutdownSignals?: PgHereShutdownSignal[];
|
|
17
|
+
cleanupOnShutdown?: boolean;
|
|
18
|
+
enablePgStatStatements?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface StopPgHereOptions {
|
|
21
|
+
cleanup?: boolean;
|
|
14
22
|
}
|
|
15
23
|
export interface PgHereHandle {
|
|
16
24
|
instance: PostgresInstance;
|
|
17
25
|
connectionString: string;
|
|
18
26
|
databaseConnectionString: string;
|
|
19
27
|
database: string;
|
|
20
|
-
stop: () => Promise<void>;
|
|
28
|
+
stop: (options?: StopPgHereOptions) => Promise<void>;
|
|
29
|
+
cleanup: () => Promise<void>;
|
|
21
30
|
ensureDatabase: (databaseName?: string) => Promise<boolean>;
|
|
31
|
+
removeShutdownHooks: () => void;
|
|
22
32
|
}
|
|
23
33
|
export declare function createPgHereInstance(options?: PgHereOptions): PostgresInstance;
|
|
24
34
|
export declare function ensurePgHereDatabase(instance: PostgresInstance, databaseName: string): Promise<boolean>;
|
|
25
|
-
export declare function stopPgHere(instance: PostgresInstance): Promise<void>;
|
|
35
|
+
export declare function stopPgHere(instance: PostgresInstance, options?: StopPgHereOptions): Promise<void>;
|
|
26
36
|
export declare function startPgHere(options?: PgHereOptions): Promise<PgHereHandle>;
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { join, resolve } from "node:path";
|
|
2
2
|
import { PostgresInstance } from "pg-embedded";
|
|
3
|
+
import { Client } from "pg";
|
|
3
4
|
const DEFAULT_USERNAME = "postgres";
|
|
4
5
|
const DEFAULT_PASSWORD = "postgres";
|
|
5
6
|
const DEFAULT_PORT = 55432;
|
|
6
7
|
const DEFAULT_DATABASE = "postgres";
|
|
8
|
+
const DEFAULT_SHUTDOWN_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
9
|
+
const PG_STAT_STATEMENTS_EXTENSION = "pg_stat_statements";
|
|
7
10
|
export function createPgHereInstance(options = {}) {
|
|
8
11
|
const root = resolve(options.projectDir ?? process.cwd());
|
|
9
12
|
const dataDir = resolve(options.dataDir ?? join(root, "pg_local", "data"));
|
|
@@ -30,18 +33,20 @@ export async function ensurePgHereDatabase(instance, databaseName) {
|
|
|
30
33
|
await instance.createDatabase(databaseName);
|
|
31
34
|
return true;
|
|
32
35
|
}
|
|
33
|
-
export async function stopPgHere(instance) {
|
|
36
|
+
export async function stopPgHere(instance, options = {}) {
|
|
34
37
|
try {
|
|
35
38
|
await instance.stop();
|
|
36
39
|
}
|
|
37
40
|
catch {
|
|
38
41
|
// no-op
|
|
39
42
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
if (options.cleanup) {
|
|
44
|
+
try {
|
|
45
|
+
await instance.cleanup();
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// no-op
|
|
49
|
+
}
|
|
45
50
|
}
|
|
46
51
|
}
|
|
47
52
|
export async function startPgHere(options = {}) {
|
|
@@ -52,15 +57,33 @@ export async function startPgHere(options = {}) {
|
|
|
52
57
|
if (shouldCreate) {
|
|
53
58
|
await ensurePgHereDatabase(instance, database);
|
|
54
59
|
}
|
|
60
|
+
if (options.enablePgStatStatements ?? true) {
|
|
61
|
+
await ensurePgStatStatements(instance, database);
|
|
62
|
+
}
|
|
63
|
+
const defaultCleanupOnShutdown = options.cleanupOnShutdown ?? false;
|
|
55
64
|
const connectionString = instance.connectionInfo.connectionString;
|
|
56
65
|
const databaseConnectionString = setConnectionDatabase(connectionString, database);
|
|
66
|
+
let removeShutdownHooks = () => { };
|
|
67
|
+
const stopHandle = async (stopOptions = {}) => {
|
|
68
|
+
removeShutdownHooks();
|
|
69
|
+
const cleanup = stopOptions.cleanup ?? defaultCleanupOnShutdown;
|
|
70
|
+
await stopPgHere(instance, { cleanup });
|
|
71
|
+
};
|
|
72
|
+
if (options.registerProcessShutdownHandlers ?? true) {
|
|
73
|
+
removeShutdownHooks = registerPgHereShutdownHandlers({
|
|
74
|
+
stop: async () => stopHandle({ cleanup: defaultCleanupOnShutdown }),
|
|
75
|
+
signals: options.shutdownSignals ?? DEFAULT_SHUTDOWN_SIGNALS,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
57
78
|
return {
|
|
58
79
|
instance,
|
|
59
80
|
connectionString,
|
|
60
81
|
databaseConnectionString,
|
|
61
82
|
database,
|
|
62
|
-
stop:
|
|
83
|
+
stop: stopHandle,
|
|
84
|
+
cleanup: async () => stopHandle({ cleanup: true }),
|
|
63
85
|
ensureDatabase: async (databaseName = database) => ensurePgHereDatabase(instance, databaseName),
|
|
86
|
+
removeShutdownHooks,
|
|
64
87
|
};
|
|
65
88
|
}
|
|
66
89
|
function setConnectionDatabase(connectionString, database) {
|
|
@@ -68,3 +91,82 @@ function setConnectionDatabase(connectionString, database) {
|
|
|
68
91
|
connectionUrl.pathname = `/${database}`;
|
|
69
92
|
return connectionUrl.toString();
|
|
70
93
|
}
|
|
94
|
+
async function ensurePgStatStatements(instance, database) {
|
|
95
|
+
await ensureSharedPreloadLibrary(instance, PG_STAT_STATEMENTS_EXTENSION);
|
|
96
|
+
await ensureExtension(instance, database, PG_STAT_STATEMENTS_EXTENSION);
|
|
97
|
+
}
|
|
98
|
+
async function ensureSharedPreloadLibrary(instance, libraryName) {
|
|
99
|
+
const adminConnection = setConnectionDatabase(instance.connectionInfo.connectionString, DEFAULT_DATABASE);
|
|
100
|
+
const client = new Client({ connectionString: adminConnection });
|
|
101
|
+
try {
|
|
102
|
+
await client.connect();
|
|
103
|
+
const result = await client.query("show shared_preload_libraries");
|
|
104
|
+
const rawLibraries = String(result.rows[0]?.shared_preload_libraries ?? "");
|
|
105
|
+
const libraries = parsePreloadLibraries(rawLibraries);
|
|
106
|
+
if (libraries.includes(libraryName)) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const nextLibraries = [...libraries, libraryName];
|
|
110
|
+
const librariesValue = escapeSqlLiteral(nextLibraries.join(","));
|
|
111
|
+
await client.query(`ALTER SYSTEM SET shared_preload_libraries = '${librariesValue}'`);
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
await client.end().catch(() => { });
|
|
115
|
+
}
|
|
116
|
+
await instance.stop().catch(() => { });
|
|
117
|
+
await instance.start();
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
async function ensureExtension(instance, database, extensionName) {
|
|
121
|
+
const connectionString = setConnectionDatabase(instance.connectionInfo.connectionString, database);
|
|
122
|
+
const client = new Client({ connectionString });
|
|
123
|
+
try {
|
|
124
|
+
await client.connect();
|
|
125
|
+
await client.query(`CREATE EXTENSION IF NOT EXISTS ${quoteIdentifier(extensionName)}`);
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
await client.end().catch(() => { });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function parsePreloadLibraries(value) {
|
|
132
|
+
if (!value) {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
return value
|
|
136
|
+
.split(",")
|
|
137
|
+
.map((library) => library.trim())
|
|
138
|
+
.filter(Boolean);
|
|
139
|
+
}
|
|
140
|
+
function quoteIdentifier(identifier) {
|
|
141
|
+
return `"${identifier.replaceAll('"', '""')}"`;
|
|
142
|
+
}
|
|
143
|
+
function escapeSqlLiteral(value) {
|
|
144
|
+
return value.replaceAll("'", "''");
|
|
145
|
+
}
|
|
146
|
+
function registerPgHereShutdownHandlers({ stop, signals, }) {
|
|
147
|
+
let isStopping = false;
|
|
148
|
+
const uniqueSignals = [...new Set(signals)];
|
|
149
|
+
const handlers = uniqueSignals.map((signal) => {
|
|
150
|
+
const handler = () => {
|
|
151
|
+
if (isStopping) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
isStopping = true;
|
|
155
|
+
void (async () => {
|
|
156
|
+
try {
|
|
157
|
+
await stop();
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
process.exit(0);
|
|
161
|
+
}
|
|
162
|
+
})();
|
|
163
|
+
};
|
|
164
|
+
process.on(signal, handler);
|
|
165
|
+
return { signal, handler };
|
|
166
|
+
});
|
|
167
|
+
return () => {
|
|
168
|
+
for (const { signal, handler } of handlers) {
|
|
169
|
+
process.off(signal, handler);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pg-here",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Per-project embedded PostgreSQL with programmatic startup and auto DB creation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/bun": "latest",
|
|
52
|
+
"@types/pg": "^8.15.6",
|
|
52
53
|
"typescript": "^5.9.2"
|
|
53
54
|
},
|
|
54
55
|
"dependencies": {
|