@geekmidas/testkit 1.0.2 → 1.0.4

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.
Files changed (102) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/{Factory-C6W78ulZ.d.mts → Factory-CVR3GdkW.d.mts} +2 -2
  3. package/dist/{Factory-C6W78ulZ.d.mts.map → Factory-CVR3GdkW.d.mts.map} +1 -1
  4. package/dist/{Factory-D28yjUj5.d.cts → Factory-Cwzho3c8.d.cts} +2 -2
  5. package/dist/{Factory-D28yjUj5.d.cts.map → Factory-Cwzho3c8.d.cts.map} +1 -1
  6. package/dist/Factory.d.cts +2 -2
  7. package/dist/Factory.d.mts +2 -2
  8. package/dist/{KyselyFactory-Cc2UmOJk.d.mts → KyselyFactory-DRlMv-WT.d.mts} +3 -3
  9. package/dist/{KyselyFactory-Cc2UmOJk.d.mts.map → KyselyFactory-DRlMv-WT.d.mts.map} +1 -1
  10. package/dist/{KyselyFactory-OUH03l2q.d.cts → KyselyFactory-hMcVtvJq.d.cts} +3 -3
  11. package/dist/{KyselyFactory-OUH03l2q.d.cts.map → KyselyFactory-hMcVtvJq.d.cts.map} +1 -1
  12. package/dist/KyselyFactory.d.cts +3 -3
  13. package/dist/KyselyFactory.d.mts +3 -3
  14. package/dist/{ObjectionFactory-B1WkcfZY.d.cts → ObjectionFactory-DW-qwnqO.d.cts} +3 -3
  15. package/dist/{ObjectionFactory-B1WkcfZY.d.cts.map → ObjectionFactory-DW-qwnqO.d.cts.map} +1 -1
  16. package/dist/{ObjectionFactory-BYnPr9ZP.d.mts → ObjectionFactory-DkJUf-uM.d.mts} +3 -3
  17. package/dist/{ObjectionFactory-BYnPr9ZP.d.mts.map → ObjectionFactory-DkJUf-uM.d.mts.map} +1 -1
  18. package/dist/ObjectionFactory.d.cts +3 -3
  19. package/dist/ObjectionFactory.d.mts +3 -3
  20. package/dist/{PostgresKyselyMigrator-B4pScubb.mjs → PostgresKyselyMigrator-BIKd7G5C.mjs} +16 -3
  21. package/dist/PostgresKyselyMigrator-BIKd7G5C.mjs.map +1 -0
  22. package/dist/{PostgresKyselyMigrator-CBltSOq5.d.cts → PostgresKyselyMigrator-CTNftoZK.d.cts} +16 -2
  23. package/dist/PostgresKyselyMigrator-CTNftoZK.d.cts.map +1 -0
  24. package/dist/{PostgresKyselyMigrator-C7ljZYvq.cjs → PostgresKyselyMigrator-CqNs3qX8.cjs} +16 -3
  25. package/dist/PostgresKyselyMigrator-CqNs3qX8.cjs.map +1 -0
  26. package/dist/{PostgresKyselyMigrator-DrVWncqd.d.mts → PostgresKyselyMigrator-x0uvWs3U.d.mts} +16 -2
  27. package/dist/PostgresKyselyMigrator-x0uvWs3U.d.mts.map +1 -0
  28. package/dist/PostgresKyselyMigrator.cjs +2 -2
  29. package/dist/PostgresKyselyMigrator.d.cts +2 -2
  30. package/dist/PostgresKyselyMigrator.d.mts +2 -2
  31. package/dist/PostgresKyselyMigrator.mjs +2 -2
  32. package/dist/{PostgresMigrator-Bres0U6E.d.cts → PostgresMigrator-C_QQ6q35.d.mts} +15 -2
  33. package/dist/PostgresMigrator-C_QQ6q35.d.mts.map +1 -0
  34. package/dist/{PostgresMigrator-S-YYosAC.d.mts → PostgresMigrator-CeYy-eHF.d.cts} +15 -2
  35. package/dist/PostgresMigrator-CeYy-eHF.d.cts.map +1 -0
  36. package/dist/{PostgresMigrator-DcP1o-T6.mjs → PostgresMigrator-DVAY04qN.mjs} +16 -2
  37. package/dist/PostgresMigrator-DVAY04qN.mjs.map +1 -0
  38. package/dist/{PostgresMigrator-CHiBYEg_.cjs → PostgresMigrator-M9jpzOvN.cjs} +16 -2
  39. package/dist/PostgresMigrator-M9jpzOvN.cjs.map +1 -0
  40. package/dist/PostgresMigrator.cjs +1 -1
  41. package/dist/PostgresMigrator.d.cts +1 -1
  42. package/dist/PostgresMigrator.d.mts +1 -1
  43. package/dist/PostgresMigrator.mjs +1 -1
  44. package/dist/{PostgresObjectionMigrator-CPfBAP7r.d.cts → PostgresObjectionMigrator-1j6YIB1c.d.mts} +2 -2
  45. package/dist/{PostgresObjectionMigrator-CPfBAP7r.d.cts.map → PostgresObjectionMigrator-1j6YIB1c.d.mts.map} +1 -1
  46. package/dist/{PostgresObjectionMigrator-DVEqB5tp.d.mts → PostgresObjectionMigrator-DHVC9h_P.d.cts} +2 -2
  47. package/dist/{PostgresObjectionMigrator-DVEqB5tp.d.mts.map → PostgresObjectionMigrator-DHVC9h_P.d.cts.map} +1 -1
  48. package/dist/{PostgresObjectionMigrator-BXLAVVwm.cjs → PostgresObjectionMigrator-DSaPhwjY.cjs} +2 -2
  49. package/dist/{PostgresObjectionMigrator-BXLAVVwm.cjs.map → PostgresObjectionMigrator-DSaPhwjY.cjs.map} +1 -1
  50. package/dist/{PostgresObjectionMigrator-BJ5X48U8.mjs → PostgresObjectionMigrator-DjPKdUbm.mjs} +2 -2
  51. package/dist/{PostgresObjectionMigrator-BJ5X48U8.mjs.map → PostgresObjectionMigrator-DjPKdUbm.mjs.map} +1 -1
  52. package/dist/PostgresObjectionMigrator.cjs +2 -2
  53. package/dist/PostgresObjectionMigrator.d.cts +2 -2
  54. package/dist/PostgresObjectionMigrator.d.mts +2 -2
  55. package/dist/PostgresObjectionMigrator.mjs +2 -2
  56. package/dist/better-auth.d.cts +2 -2
  57. package/dist/better-auth.d.mts +2 -2
  58. package/dist/{directory-CVrfTq1I.d.mts → directory-CYXmolu1.d.mts} +3 -3
  59. package/dist/{directory-CVrfTq1I.d.mts.map → directory-CYXmolu1.d.mts.map} +1 -1
  60. package/dist/{directory-DGOcVlKD.d.cts → directory-DAnMWi50.d.cts} +3 -3
  61. package/dist/{directory-DGOcVlKD.d.cts.map → directory-DAnMWi50.d.cts.map} +1 -1
  62. package/dist/{faker-CbYiF-8_.d.cts → faker-Dg3trU4a.d.cts} +3 -3
  63. package/dist/{faker-CbYiF-8_.d.cts.map → faker-Dg3trU4a.d.cts.map} +1 -1
  64. package/dist/{faker-D9gz7KjY.d.mts → faker-DsYCplsG.d.mts} +3 -3
  65. package/dist/{faker-D9gz7KjY.d.mts.map → faker-DsYCplsG.d.mts.map} +1 -1
  66. package/dist/faker.d.cts +1 -1
  67. package/dist/faker.d.mts +1 -1
  68. package/dist/initScript.cjs +95 -0
  69. package/dist/initScript.cjs.map +1 -0
  70. package/dist/initScript.d.cts +45 -0
  71. package/dist/initScript.d.cts.map +1 -0
  72. package/dist/initScript.d.mts +45 -0
  73. package/dist/initScript.d.mts.map +1 -0
  74. package/dist/initScript.mjs +93 -0
  75. package/dist/initScript.mjs.map +1 -0
  76. package/dist/kysely.cjs +2 -2
  77. package/dist/kysely.d.cts +5 -5
  78. package/dist/kysely.d.mts +5 -5
  79. package/dist/kysely.mjs +2 -2
  80. package/dist/objection.cjs +2 -2
  81. package/dist/objection.d.cts +5 -5
  82. package/dist/objection.d.mts +5 -5
  83. package/dist/objection.mjs +2 -2
  84. package/dist/os/directory.d.cts +1 -1
  85. package/dist/os/directory.d.mts +1 -1
  86. package/dist/os/index.d.cts +1 -1
  87. package/dist/os/index.d.mts +1 -1
  88. package/package.json +6 -1
  89. package/src/PostgresKyselyMigrator.ts +15 -1
  90. package/src/PostgresMigrator.ts +19 -2
  91. package/src/__tests__/PostgresKyselyMigrator.spec.ts +104 -0
  92. package/src/__tests__/PostgresMigrator.spec.ts +109 -0
  93. package/src/__tests__/initScript.spec.ts +308 -0
  94. package/src/initScript.ts +119 -0
  95. package/dist/PostgresKyselyMigrator-B4pScubb.mjs.map +0 -1
  96. package/dist/PostgresKyselyMigrator-C7ljZYvq.cjs.map +0 -1
  97. package/dist/PostgresKyselyMigrator-CBltSOq5.d.cts.map +0 -1
  98. package/dist/PostgresKyselyMigrator-DrVWncqd.d.mts.map +0 -1
  99. package/dist/PostgresMigrator-Bres0U6E.d.cts.map +0 -1
  100. package/dist/PostgresMigrator-CHiBYEg_.cjs.map +0 -1
  101. package/dist/PostgresMigrator-DcP1o-T6.mjs.map +0 -1
  102. package/dist/PostgresMigrator-S-YYosAC.d.mts.map +0 -1
@@ -0,0 +1,308 @@
1
+ import { writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import pg from 'pg';
5
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
6
+ import { parseInitScript, runInitScript } from '../initScript';
7
+
8
+ describe('parseInitScript', () => {
9
+ it('should extract SQL from EOSQL heredoc blocks', () => {
10
+ const script = `#!/bin/bash
11
+ set -e
12
+
13
+ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
14
+ CREATE USER api WITH PASSWORD '$API_DB_PASSWORD';
15
+ GRANT ALL ON SCHEMA public TO api;
16
+ EOSQL
17
+ `;
18
+
19
+ const blocks = parseInitScript(script, {
20
+ POSTGRES_USER: 'app',
21
+ POSTGRES_DB: 'mydb',
22
+ API_DB_PASSWORD: 'secret123',
23
+ });
24
+
25
+ expect(blocks).toHaveLength(1);
26
+ expect(blocks[0]).toContain("CREATE USER api WITH PASSWORD 'secret123'");
27
+ expect(blocks[0]).toContain('GRANT ALL ON SCHEMA public TO api');
28
+ });
29
+
30
+ it('should extract multiple heredoc blocks', () => {
31
+ const script = `#!/bin/bash
32
+ set -e
33
+
34
+ psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
35
+ CREATE USER api WITH PASSWORD '$API_DB_PASSWORD';
36
+ EOSQL
37
+
38
+ psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
39
+ CREATE USER auth WITH PASSWORD '$AUTH_DB_PASSWORD';
40
+ CREATE SCHEMA auth AUTHORIZATION auth;
41
+ EOSQL
42
+
43
+ echo "Done!"
44
+ `;
45
+
46
+ const blocks = parseInitScript(script, {
47
+ POSTGRES_USER: 'app',
48
+ POSTGRES_DB: 'mydb',
49
+ API_DB_PASSWORD: 'apipass',
50
+ AUTH_DB_PASSWORD: 'authpass',
51
+ });
52
+
53
+ expect(blocks).toHaveLength(2);
54
+ expect(blocks[0]).toContain("CREATE USER api WITH PASSWORD 'apipass'");
55
+ expect(blocks[1]).toContain("CREATE USER auth WITH PASSWORD 'authpass'");
56
+ expect(blocks[1]).toContain('CREATE SCHEMA auth AUTHORIZATION auth');
57
+ });
58
+
59
+ it('should substitute $VAR_NAME syntax', () => {
60
+ const script = `psql <<-EOSQL
61
+ CREATE USER $APP_USER WITH PASSWORD '$APP_PASSWORD';
62
+ EOSQL`;
63
+
64
+ const blocks = parseInitScript(script, {
65
+ APP_USER: 'myuser',
66
+ APP_PASSWORD: 'mypass',
67
+ });
68
+
69
+ expect(blocks[0]).toContain("CREATE USER myuser WITH PASSWORD 'mypass'");
70
+ });
71
+
72
+ it('should substitute ${VAR_NAME} syntax', () => {
73
+ const script = `psql <<-EOSQL
74
+ CREATE USER \${APP_USER} WITH PASSWORD '\${APP_PASSWORD}';
75
+ EOSQL`;
76
+
77
+ const blocks = parseInitScript(script, {
78
+ APP_USER: 'myuser',
79
+ APP_PASSWORD: 'mypass',
80
+ });
81
+
82
+ expect(blocks[0]).toContain("CREATE USER myuser WITH PASSWORD 'mypass'");
83
+ });
84
+
85
+ it('should replace missing vars with empty string', () => {
86
+ const script = `psql <<-EOSQL
87
+ CREATE USER api WITH PASSWORD '$MISSING_VAR';
88
+ EOSQL`;
89
+
90
+ const blocks = parseInitScript(script, {});
91
+
92
+ expect(blocks[0]).toContain("CREATE USER api WITH PASSWORD ''");
93
+ });
94
+
95
+ it('should return empty array for script with no heredocs', () => {
96
+ const script = `#!/bin/bash
97
+ echo "Hello world"
98
+ `;
99
+
100
+ const blocks = parseInitScript(script, {});
101
+
102
+ expect(blocks).toEqual([]);
103
+ });
104
+
105
+ it('should handle the full generated init.sh format', () => {
106
+ const script = `#!/bin/bash
107
+ set -e
108
+
109
+ # Auto-generated PostgreSQL init script
110
+ # Creates per-app users with separate schemas in a single database
111
+ # - api: uses public schema
112
+ # - auth: uses auth schema (search_path=auth)
113
+
114
+ # Create api user (uses public schema)
115
+ echo "Creating user api..."
116
+ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
117
+ CREATE USER api WITH PASSWORD '$API_DB_PASSWORD';
118
+ GRANT ALL ON SCHEMA public TO api;
119
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO api;
120
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO api;
121
+ EOSQL
122
+
123
+
124
+ # Create auth user with dedicated schema
125
+ echo "Creating user auth with schema auth..."
126
+ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
127
+ CREATE USER auth WITH PASSWORD '$AUTH_DB_PASSWORD';
128
+ CREATE SCHEMA auth AUTHORIZATION auth;
129
+ ALTER USER auth SET search_path TO auth;
130
+ GRANT USAGE ON SCHEMA auth TO auth;
131
+ GRANT ALL ON ALL TABLES IN SCHEMA auth TO auth;
132
+ GRANT ALL ON ALL SEQUENCES IN SCHEMA auth TO auth;
133
+ ALTER DEFAULT PRIVILEGES IN SCHEMA auth GRANT ALL ON TABLES TO auth;
134
+ ALTER DEFAULT PRIVILEGES IN SCHEMA auth GRANT ALL ON SEQUENCES TO auth;
135
+ EOSQL
136
+
137
+ echo "Database initialization complete!"
138
+ `;
139
+
140
+ const blocks = parseInitScript(script, {
141
+ POSTGRES_USER: 'app',
142
+ POSTGRES_DB: 'residentman_dev_test',
143
+ API_DB_PASSWORD: 'apipass123',
144
+ AUTH_DB_PASSWORD: 'authpass456',
145
+ });
146
+
147
+ expect(blocks).toHaveLength(2);
148
+
149
+ // API block
150
+ expect(blocks[0]).toContain("CREATE USER api WITH PASSWORD 'apipass123'");
151
+ expect(blocks[0]).toContain('GRANT ALL ON SCHEMA public TO api');
152
+ expect(blocks[0]).toContain('ALTER DEFAULT PRIVILEGES IN SCHEMA public');
153
+
154
+ // Auth block
155
+ expect(blocks[1]).toContain("CREATE USER auth WITH PASSWORD 'authpass456'");
156
+ expect(blocks[1]).toContain('CREATE SCHEMA auth AUTHORIZATION auth');
157
+ expect(blocks[1]).toContain('ALTER USER auth SET search_path TO auth');
158
+ });
159
+ });
160
+
161
+ const PG_CONFIG = {
162
+ host: 'localhost',
163
+ port: 5432,
164
+ user: 'geekmidas',
165
+ password: 'geekmidas',
166
+ };
167
+
168
+ /**
169
+ * Helper to run queries against the postgres admin database.
170
+ */
171
+ async function adminQuery(...queries: string[]): Promise<void> {
172
+ const client = new pg.Client({ ...PG_CONFIG, database: 'postgres' });
173
+ try {
174
+ await client.connect();
175
+ for (const sql of queries) {
176
+ await client.query(sql);
177
+ }
178
+ } finally {
179
+ await client.end();
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Force-drop a role by reassigning owned objects and revoking privileges first.
185
+ */
186
+ async function forceDropRole(role: string): Promise<void> {
187
+ // Find all databases where this role might own objects
188
+ const client = new pg.Client({ ...PG_CONFIG, database: 'postgres' });
189
+ try {
190
+ await client.connect();
191
+ const result = await client.query(
192
+ "SELECT datname FROM pg_database WHERE datistemplate = false AND datname != 'postgres'",
193
+ );
194
+ for (const row of result.rows) {
195
+ const dbClient = new pg.Client({
196
+ ...PG_CONFIG,
197
+ database: row.datname,
198
+ });
199
+ try {
200
+ await dbClient.connect();
201
+ await dbClient.query(`REASSIGN OWNED BY ${role} TO ${PG_CONFIG.user}`);
202
+ await dbClient.query(`DROP OWNED BY ${role}`);
203
+ } catch {
204
+ // Role might not exist in this database, ignore
205
+ } finally {
206
+ await dbClient.end();
207
+ }
208
+ }
209
+ await client.query(`DROP ROLE IF EXISTS ${role}`);
210
+ } finally {
211
+ await client.end();
212
+ }
213
+ }
214
+
215
+ describe('runInitScript', () => {
216
+ const dbName = `test_init_script_${Date.now()}`;
217
+ const dbUrl = `postgresql://${PG_CONFIG.user}:${PG_CONFIG.password}@${PG_CONFIG.host}:${PG_CONFIG.port}/${dbName}`;
218
+
219
+ beforeAll(async () => {
220
+ // Clean up stale state from previous failed runs
221
+ await forceDropRole('test_api');
222
+ await forceDropRole('test_auth');
223
+ await adminQuery(
224
+ `DROP DATABASE IF EXISTS "${dbName}"`,
225
+ `CREATE DATABASE "${dbName}"`,
226
+ );
227
+ });
228
+
229
+ afterAll(async () => {
230
+ // Drop the database first (removes object dependencies), then roles
231
+ await adminQuery(`DROP DATABASE IF EXISTS "${dbName}"`);
232
+ await forceDropRole('test_api');
233
+ await forceDropRole('test_auth');
234
+ });
235
+
236
+ it('should create users and schemas from init script', async () => {
237
+ // Write a test init script to a temp file
238
+ const scriptPath = join(tmpdir(), `init-${Date.now()}.sh`);
239
+ writeFileSync(
240
+ scriptPath,
241
+ `#!/bin/bash
242
+ set -e
243
+
244
+ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
245
+ CREATE USER test_api WITH PASSWORD '$API_DB_PASSWORD';
246
+ GRANT ALL ON SCHEMA public TO test_api;
247
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO test_api;
248
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO test_api;
249
+ EOSQL
250
+
251
+ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
252
+ CREATE USER test_auth WITH PASSWORD '$AUTH_DB_PASSWORD';
253
+ CREATE SCHEMA IF NOT EXISTS test_auth AUTHORIZATION test_auth;
254
+ ALTER USER test_auth SET search_path TO test_auth;
255
+ GRANT USAGE ON SCHEMA test_auth TO test_auth;
256
+ GRANT ALL ON ALL TABLES IN SCHEMA test_auth TO test_auth;
257
+ GRANT ALL ON ALL SEQUENCES IN SCHEMA test_auth TO test_auth;
258
+ ALTER DEFAULT PRIVILEGES IN SCHEMA test_auth GRANT ALL ON TABLES TO test_auth;
259
+ ALTER DEFAULT PRIVILEGES IN SCHEMA test_auth GRANT ALL ON SEQUENCES TO test_auth;
260
+ EOSQL
261
+
262
+ echo "Done!"
263
+ `,
264
+ );
265
+
266
+ // Run the init script against the test database
267
+ await runInitScript(scriptPath, dbUrl, {
268
+ POSTGRES_USER: PG_CONFIG.user,
269
+ POSTGRES_DB: dbName,
270
+ API_DB_PASSWORD: 'apipass',
271
+ AUTH_DB_PASSWORD: 'authpass',
272
+ });
273
+
274
+ // Verify: connect as test_api and check access to public schema
275
+ const apiClient = new pg.Client({
276
+ ...PG_CONFIG,
277
+ user: 'test_api',
278
+ password: 'apipass',
279
+ database: dbName,
280
+ });
281
+ await apiClient.connect();
282
+ await apiClient.query(
283
+ 'CREATE TABLE IF NOT EXISTS api_test_table (id serial PRIMARY KEY)',
284
+ );
285
+ const apiResult = await apiClient.query(
286
+ "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'api_test_table'",
287
+ );
288
+ expect(apiResult.rowCount).toBe(1);
289
+ await apiClient.end();
290
+
291
+ // Verify: connect as test_auth and check dedicated schema
292
+ const authClient = new pg.Client({
293
+ ...PG_CONFIG,
294
+ user: 'test_auth',
295
+ password: 'authpass',
296
+ database: dbName,
297
+ });
298
+ await authClient.connect();
299
+ const schemaResult = await authClient.query(
300
+ "SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'test_auth'",
301
+ );
302
+ expect(schemaResult.rowCount).toBe(1);
303
+ await authClient.query(
304
+ 'CREATE TABLE IF NOT EXISTS test_auth.auth_test_table (id serial PRIMARY KEY)',
305
+ );
306
+ await authClient.end();
307
+ });
308
+ });
@@ -0,0 +1,119 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import pg from 'pg';
3
+
4
+ const { Client } = pg;
5
+
6
+ /**
7
+ * Parse a shell init script (like docker/postgres/init.sh) and extract
8
+ * SQL blocks from heredoc sections (<<-EOSQL ... EOSQL).
9
+ *
10
+ * @param content - The shell script content
11
+ * @param env - Environment variables to substitute ($VAR_NAME references)
12
+ * @returns Array of SQL strings ready to execute
13
+ * @internal Exported for testing
14
+ */
15
+ export function parseInitScript(
16
+ content: string,
17
+ env: Record<string, string>,
18
+ ): string[] {
19
+ const blocks: string[] = [];
20
+ const lines = content.split('\n');
21
+ let inHeredoc = false;
22
+ let currentBlock: string[] = [];
23
+
24
+ for (const line of lines) {
25
+ if (inHeredoc) {
26
+ // Check for heredoc terminator (EOSQL at start of line, with optional leading whitespace)
27
+ if (/^\s*EOSQL\s*$/.test(line)) {
28
+ const sql = substituteEnvVars(currentBlock.join('\n'), env);
29
+ blocks.push(sql);
30
+ currentBlock = [];
31
+ inHeredoc = false;
32
+ } else {
33
+ currentBlock.push(line);
34
+ }
35
+ } else if (
36
+ line.includes('<<-EOSQL') ||
37
+ line.includes('<< EOSQL') ||
38
+ line.includes('<<EOSQL')
39
+ ) {
40
+ inHeredoc = true;
41
+ currentBlock = [];
42
+ }
43
+ }
44
+
45
+ return blocks;
46
+ }
47
+
48
+ /**
49
+ * Replace shell variable references ($VAR_NAME and ${VAR_NAME})
50
+ * with values from the provided env object.
51
+ */
52
+ function substituteEnvVars(sql: string, env: Record<string, string>): string {
53
+ // Replace ${VAR_NAME} syntax
54
+ let result = sql.replace(/\$\{(\w+)\}/g, (_, name) => env[name] ?? '');
55
+ // Replace $VAR_NAME syntax (word boundary after)
56
+ result = result.replace(/\$(\w+)/g, (_, name) => env[name] ?? '');
57
+ return result;
58
+ }
59
+
60
+ /**
61
+ * Read a postgres init script, parse out the SQL blocks,
62
+ * substitute environment variables, and execute against a database.
63
+ *
64
+ * This is intended to run `docker/postgres/init.sh` against a test database
65
+ * so that per-app users and schemas are created (matching what Docker does
66
+ * on first volume initialization).
67
+ *
68
+ * Uses `CREATE ... IF NOT EXISTS` and `DO $$ ... END $$` wrappers where
69
+ * needed so the script is idempotent.
70
+ *
71
+ * @param scriptPath - Path to the init.sh file
72
+ * @param databaseUrl - PostgreSQL connection URL (should point to the test database)
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * // In your globalSetup.ts
77
+ * import { runInitScript } from '@geekmidas/testkit/postgres';
78
+ * import { Credentials } from '@geekmidas/envkit/credentials';
79
+ *
80
+ * const cleanup = await migrator.start();
81
+ *
82
+ * // Create per-app users in the test database
83
+ * await runInitScript('docker/postgres/init.sh', Credentials.DATABASE_URL, {
84
+ * ...process.env,
85
+ * ...Credentials,
86
+ * });
87
+ * ```
88
+ */
89
+ export async function runInitScript(
90
+ scriptPath: string,
91
+ databaseUrl: string,
92
+ env?: Record<string, string>,
93
+ ): Promise<void> {
94
+ const content = readFileSync(scriptPath, 'utf-8');
95
+ const resolvedEnv = env ?? ({ ...process.env } as Record<string, string>);
96
+ const blocks = parseInitScript(content, resolvedEnv);
97
+
98
+ if (blocks.length === 0) {
99
+ return;
100
+ }
101
+
102
+ const url = new URL(databaseUrl);
103
+ const client = new Client({
104
+ user: url.username,
105
+ password: decodeURIComponent(url.password),
106
+ host: url.hostname,
107
+ port: parseInt(url.port, 10),
108
+ database: url.pathname.slice(1),
109
+ });
110
+
111
+ try {
112
+ await client.connect();
113
+ for (const sql of blocks) {
114
+ await client.query(sql);
115
+ }
116
+ } finally {
117
+ await client.end();
118
+ }
119
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"PostgresKyselyMigrator-B4pScubb.mjs","names":["options: {\n\t\t\turi: string;\n\t\t\tdb: Kysely<any>;\n\t\t\tprovider: MigrationProvider;\n\t\t}"],"sources":["../src/PostgresKyselyMigrator.ts"],"sourcesContent":["import { type Kysely, type MigrationProvider, Migrator } from 'kysely';\nimport { PostgresMigrator } from './PostgresMigrator';\n\n/**\n * Default logger instance for migration operations.\n */\nconst logger = console;\n\n/**\n * PostgreSQL migrator implementation for Kysely ORM.\n * Extends PostgresMigrator to provide Kysely-specific migration functionality.\n * Automatically creates test databases and applies migrations for testing environments.\n *\n * @example\n * ```typescript\n * import { FileMigrationProvider } from 'kysely';\n * import { PostgresKyselyMigrator } from '@geekmidas/testkit';\n *\n * // Create migration provider\n * const provider = new FileMigrationProvider({\n * fs: require('fs'),\n * path: require('path'),\n * migrationFolder: path.join(__dirname, 'migrations')\n * });\n *\n * // Create Kysely instance\n * const db = new Kysely<Database>({\n * dialect: new PostgresDialect({\n * pool: new Pool({ connectionString: uri })\n * })\n * });\n *\n * // Create and use migrator\n * const migrator = new PostgresKyselyMigrator({\n * uri: 'postgresql://localhost:5432/test_db',\n * db,\n * provider\n * });\n *\n * const cleanup = await migrator.start();\n * // Run tests...\n * await cleanup();\n * ```\n */\nexport class PostgresKyselyMigrator extends PostgresMigrator {\n\t/**\n\t * Creates a new PostgresKyselyMigrator instance.\n\t *\n\t * @param options - Configuration options\n\t * @param options.uri - PostgreSQL connection URI\n\t * @param options.db - Kysely database instance\n\t * @param options.provider - Migration provider for locating migration files\n\t */\n\tconstructor(\n\t\tprivate options: {\n\t\t\turi: string;\n\t\t\tdb: Kysely<any>;\n\t\t\tprovider: MigrationProvider;\n\t\t},\n\t) {\n\t\tsuper(options.uri);\n\t}\n\n\t/**\n\t * Executes Kysely migrations to the latest version.\n\t * Implements the abstract migrate() method from PostgresMigrator.\n\t *\n\t * @throws Error if migrations fail to apply\n\t * @returns Promise that resolves when all migrations are applied\n\t */\n\tasync migrate(): Promise<void> {\n\t\tconst migrator = new Migrator({\n\t\t\tdb: this.options.db,\n\t\t\tprovider: this.options.provider,\n\t\t});\n\t\tconst migrations = await migrator.migrateToLatest();\n\n\t\tif (migrations.error) {\n\t\t\tlogger.error(migrations.error, `Failed to apply migrations`);\n\t\t\tthrow migrations.error;\n\t\t}\n\n\t\tawait this.options.db.destroy();\n\n\t\tlogger.log(`Applied ${migrations.results?.length} migrations successfully`);\n\t}\n}\n"],"mappings":";;;;;;;AAMA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCf,IAAa,yBAAb,cAA4C,iBAAiB;;;;;;;;;CAS5D,YACSA,SAKP;AACD,QAAM,QAAQ,IAAI;EANV;CAOR;;;;;;;;CASD,MAAM,UAAyB;EAC9B,MAAM,WAAW,IAAI,SAAS;GAC7B,IAAI,KAAK,QAAQ;GACjB,UAAU,KAAK,QAAQ;EACvB;EACD,MAAM,aAAa,MAAM,SAAS,iBAAiB;AAEnD,MAAI,WAAW,OAAO;AACrB,UAAO,MAAM,WAAW,QAAQ,4BAA4B;AAC5D,SAAM,WAAW;EACjB;AAED,QAAM,KAAK,QAAQ,GAAG,SAAS;AAE/B,SAAO,KAAK,UAAU,WAAW,SAAS,OAAO,0BAA0B;CAC3E;AACD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"PostgresKyselyMigrator-C7ljZYvq.cjs","names":["PostgresMigrator","options: {\n\t\t\turi: string;\n\t\t\tdb: Kysely<any>;\n\t\t\tprovider: MigrationProvider;\n\t\t}","Migrator"],"sources":["../src/PostgresKyselyMigrator.ts"],"sourcesContent":["import { type Kysely, type MigrationProvider, Migrator } from 'kysely';\nimport { PostgresMigrator } from './PostgresMigrator';\n\n/**\n * Default logger instance for migration operations.\n */\nconst logger = console;\n\n/**\n * PostgreSQL migrator implementation for Kysely ORM.\n * Extends PostgresMigrator to provide Kysely-specific migration functionality.\n * Automatically creates test databases and applies migrations for testing environments.\n *\n * @example\n * ```typescript\n * import { FileMigrationProvider } from 'kysely';\n * import { PostgresKyselyMigrator } from '@geekmidas/testkit';\n *\n * // Create migration provider\n * const provider = new FileMigrationProvider({\n * fs: require('fs'),\n * path: require('path'),\n * migrationFolder: path.join(__dirname, 'migrations')\n * });\n *\n * // Create Kysely instance\n * const db = new Kysely<Database>({\n * dialect: new PostgresDialect({\n * pool: new Pool({ connectionString: uri })\n * })\n * });\n *\n * // Create and use migrator\n * const migrator = new PostgresKyselyMigrator({\n * uri: 'postgresql://localhost:5432/test_db',\n * db,\n * provider\n * });\n *\n * const cleanup = await migrator.start();\n * // Run tests...\n * await cleanup();\n * ```\n */\nexport class PostgresKyselyMigrator extends PostgresMigrator {\n\t/**\n\t * Creates a new PostgresKyselyMigrator instance.\n\t *\n\t * @param options - Configuration options\n\t * @param options.uri - PostgreSQL connection URI\n\t * @param options.db - Kysely database instance\n\t * @param options.provider - Migration provider for locating migration files\n\t */\n\tconstructor(\n\t\tprivate options: {\n\t\t\turi: string;\n\t\t\tdb: Kysely<any>;\n\t\t\tprovider: MigrationProvider;\n\t\t},\n\t) {\n\t\tsuper(options.uri);\n\t}\n\n\t/**\n\t * Executes Kysely migrations to the latest version.\n\t * Implements the abstract migrate() method from PostgresMigrator.\n\t *\n\t * @throws Error if migrations fail to apply\n\t * @returns Promise that resolves when all migrations are applied\n\t */\n\tasync migrate(): Promise<void> {\n\t\tconst migrator = new Migrator({\n\t\t\tdb: this.options.db,\n\t\t\tprovider: this.options.provider,\n\t\t});\n\t\tconst migrations = await migrator.migrateToLatest();\n\n\t\tif (migrations.error) {\n\t\t\tlogger.error(migrations.error, `Failed to apply migrations`);\n\t\t\tthrow migrations.error;\n\t\t}\n\n\t\tawait this.options.db.destroy();\n\n\t\tlogger.log(`Applied ${migrations.results?.length} migrations successfully`);\n\t}\n}\n"],"mappings":";;;;;;;;AAMA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCf,IAAa,yBAAb,cAA4CA,0CAAiB;;;;;;;;;CAS5D,YACSC,SAKP;AACD,QAAM,QAAQ,IAAI;EANV;CAOR;;;;;;;;CASD,MAAM,UAAyB;EAC9B,MAAM,WAAW,IAAIC,gBAAS;GAC7B,IAAI,KAAK,QAAQ;GACjB,UAAU,KAAK,QAAQ;EACvB;EACD,MAAM,aAAa,MAAM,SAAS,iBAAiB;AAEnD,MAAI,WAAW,OAAO;AACrB,UAAO,MAAM,WAAW,QAAQ,4BAA4B;AAC5D,SAAM,WAAW;EACjB;AAED,QAAM,KAAK,QAAQ,GAAG,SAAS;AAE/B,SAAO,KAAK,UAAU,WAAW,SAAS,OAAO,0BAA0B;CAC3E;AACD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"PostgresKyselyMigrator-CBltSOq5.d.cts","names":[],"sources":["../src/PostgresKyselyMigrator.ts"],"sourcesContent":[],"mappings":";;;;;;;AA4CA;;;;;;AAA4D;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAA/C,sBAAA,SAA+B,gBAAA;;;;;;;;;;;;QAYrC;cACM;;;;;;;;;aAaK"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"PostgresKyselyMigrator-DrVWncqd.d.mts","names":[],"sources":["../src/PostgresKyselyMigrator.ts"],"sourcesContent":[],"mappings":";;;;;;;AA4CA;;;;;;AAA4D;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAA/C,sBAAA,SAA+B,gBAAA;;;;;;;;;;;;QAYrC;cACM;;;;;;;;;aAaK"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"PostgresMigrator-Bres0U6E.d.cts","names":[],"sources":["../src/PostgresMigrator.ts"],"sourcesContent":[],"mappings":";;AAiEA;;;;;AA8FY;;;;;;;;;;;;;;;;;;uBA9FU,gBAAA;;;;;;;;;;;;;;sBAcD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAgFT,cAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"PostgresMigrator-CHiBYEg_.cjs","names":["uri: string","error: any"],"sources":["../src/PostgresMigrator.ts"],"sourcesContent":["import pg from 'pg';\n\nconst { Client } = pg;\n\n/**\n * Creates a PostgreSQL client connected to the 'postgres' database.\n * Extracts connection details from the provided URI.\n *\n * @param uri - PostgreSQL connection URI\n * @returns Object containing the target database name and client instance\n *\n * @example\n * ```typescript\n * const { database, db } = await setupClient('postgresql://user:pass@localhost:5432/mydb');\n * // database = 'mydb'\n * // db = Client instance connected to 'postgres' database\n * ```\n */\nasync function setupClient(uri: string) {\n\tconst url = new URL(uri);\n\n\tconst db = new Client({\n\t\tuser: url.username,\n\t\tpassword: url.password,\n\t\thost: url.hostname,\n\t\tport: parseInt(url.port, 10),\n\t\tdatabase: 'postgres',\n\t});\n\n\tlet database = url.pathname.slice(1);\n\tif (database.includes('?')) {\n\t\tdatabase = database.substring(0, database.indexOf('?'));\n\t}\n\treturn { database, db };\n}\n\n/**\n * Default logger instance for migration operations.\n */\nconst logger = console;\n\n/**\n * Abstract base class for PostgreSQL database migration utilities.\n * Provides database creation, migration, and cleanup functionality for testing.\n * Subclasses must implement the migrate() method to define migration logic.\n *\n * @example\n * ```typescript\n * class MyMigrator extends PostgresMigrator {\n * async migrate(): Promise<void> {\n * // Run your migrations here\n * await this.runMigrations();\n * }\n * }\n *\n * // Use in tests\n * const migrator = new MyMigrator('postgresql://localhost:5432/test_db');\n * const cleanup = await migrator.start();\n *\n * // Run tests...\n *\n * // Clean up\n * await cleanup();\n * ```\n */\nexport abstract class PostgresMigrator {\n\t/**\n\t * Creates a new PostgresMigrator instance.\n\t *\n\t * @param uri - PostgreSQL connection URI\n\t */\n\tconstructor(private uri: string) {}\n\n\t/**\n\t * Abstract method to be implemented by subclasses.\n\t * Should contain the migration logic for setting up database schema.\n\t *\n\t * @returns Promise that resolves when migrations are complete\n\t */\n\tabstract migrate(): Promise<void>;\n\n\t/**\n\t * Creates a PostgreSQL database if it doesn't already exist.\n\t * Connects to the 'postgres' database to check and create the target database.\n\t *\n\t * @param uri - PostgreSQL connection URI\n\t * @returns Object indicating whether the database already existed\n\t * @private\n\t */\n\tprivate static async create(\n\t\turi: string,\n\t): Promise<{ alreadyExisted: boolean }> {\n\t\tconst { database, db } = await setupClient(uri);\n\t\ttry {\n\t\t\tawait db.connect();\n\t\t\tconst result = await db.query(\n\t\t\t\t`SELECT * FROM pg_catalog.pg_database WHERE datname = '${database}'`,\n\t\t\t);\n\n\t\t\tif (result.rowCount === 0) {\n\t\t\t\ttry {\n\t\t\t\t\tawait db.query(`CREATE DATABASE \"${database}\"`);\n\t\t\t\t} catch (error: any) {\n\t\t\t\t\t// 42P04 = duplicate_database — another process created it between our check and create\n\t\t\t\t\tif (error?.code === '42P04') {\n\t\t\t\t\t\treturn { alreadyExisted: true };\n\t\t\t\t\t}\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\talreadyExisted: result.rowCount ? result.rowCount > 0 : false,\n\t\t\t};\n\t\t} finally {\n\t\t\tawait db.end();\n\t\t}\n\t}\n\n\t/**\n\t * Drops a PostgreSQL database.\n\t * Used for cleanup after tests are complete.\n\t *\n\t * @param uri - PostgreSQL connection URI\n\t * @throws Error if database cannot be dropped\n\t * @private\n\t */\n\tprivate static async drop(uri: string): Promise<void> {\n\t\tconst { database, db } = await setupClient(uri);\n\t\ttry {\n\t\t\tawait db.connect();\n\t\t\tawait db.query(`DROP DATABASE \"${database}\"`);\n\t\t} finally {\n\t\t\tawait db.end();\n\t\t}\n\t}\n\n\t/**\n\t * Starts the migration process by creating the database and running migrations.\n\t * Returns a cleanup function that will drop the database when called.\n\t *\n\t * @returns Async cleanup function that drops the created database\n\t *\n\t * @example\n\t * ```typescript\n\t * const migrator = new MyMigrator('postgresql://localhost:5432/test_db');\n\t *\n\t * // Start migrations and get cleanup function\n\t * const cleanup = await migrator.start();\n\t *\n\t * try {\n\t * // Run your tests here\n\t * await runTests();\n\t * } finally {\n\t * // Always clean up\n\t * await cleanup();\n\t * }\n\t * ```\n\t */\n\tasync start() {\n\t\tconst { database, db } = await setupClient(this.uri);\n\t\ttry {\n\t\t\tawait PostgresMigrator.create(this.uri);\n\t\t\t// Implement migration logic here\n\t\t\tawait this.migrate();\n\t\t\tlogger.log(`Migrating database: ${database}`);\n\t\t\t// Example: await db.query('CREATE TABLE example (id SERIAL PRIMARY KEY)');\n\t\t} finally {\n\t\t\tawait db.end();\n\t\t}\n\n\t\treturn async () => {\n\t\t\tawait PostgresMigrator.drop(this.uri);\n\t\t};\n\t}\n}\n"],"mappings":";;;;AAEA,MAAM,EAAE,QAAQ,GAAG;;;;;;;;;;;;;;;AAgBnB,eAAe,YAAYA,KAAa;CACvC,MAAM,MAAM,IAAI,IAAI;CAEpB,MAAM,KAAK,IAAI,OAAO;EACrB,MAAM,IAAI;EACV,UAAU,IAAI;EACd,MAAM,IAAI;EACV,MAAM,SAAS,IAAI,MAAM,GAAG;EAC5B,UAAU;CACV;CAED,IAAI,WAAW,IAAI,SAAS,MAAM,EAAE;AACpC,KAAI,SAAS,SAAS,IAAI,CACzB,YAAW,SAAS,UAAU,GAAG,SAAS,QAAQ,IAAI,CAAC;AAExD,QAAO;EAAE;EAAU;CAAI;AACvB;;;;AAKD,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;AA0Bf,IAAsB,mBAAtB,MAAsB,iBAAiB;;;;;;CAMtC,YAAoBA,KAAa;EAAb;CAAe;;;;;;;;;CAkBnC,aAAqB,OACpBA,KACuC;EACvC,MAAM,EAAE,UAAU,IAAI,GAAG,MAAM,YAAY,IAAI;AAC/C,MAAI;AACH,SAAM,GAAG,SAAS;GAClB,MAAM,SAAS,MAAM,GAAG,OACtB,wDAAwD,SAAS,GAClE;AAED,OAAI,OAAO,aAAa,EACvB,KAAI;AACH,UAAM,GAAG,OAAO,mBAAmB,SAAS,GAAG;GAC/C,SAAQC,OAAY;AAEpB,QAAI,OAAO,SAAS,QACnB,QAAO,EAAE,gBAAgB,KAAM;AAEhC,UAAM;GACN;AAGF,UAAO,EACN,gBAAgB,OAAO,WAAW,OAAO,WAAW,IAAI,MACxD;EACD,UAAS;AACT,SAAM,GAAG,KAAK;EACd;CACD;;;;;;;;;CAUD,aAAqB,KAAKD,KAA4B;EACrD,MAAM,EAAE,UAAU,IAAI,GAAG,MAAM,YAAY,IAAI;AAC/C,MAAI;AACH,SAAM,GAAG,SAAS;AAClB,SAAM,GAAG,OAAO,iBAAiB,SAAS,GAAG;EAC7C,UAAS;AACT,SAAM,GAAG,KAAK;EACd;CACD;;;;;;;;;;;;;;;;;;;;;;;CAwBD,MAAM,QAAQ;EACb,MAAM,EAAE,UAAU,IAAI,GAAG,MAAM,YAAY,KAAK,IAAI;AACpD,MAAI;AACH,SAAM,iBAAiB,OAAO,KAAK,IAAI;AAEvC,SAAM,KAAK,SAAS;AACpB,UAAO,KAAK,sBAAsB,SAAS,EAAE;EAE7C,UAAS;AACT,SAAM,GAAG,KAAK;EACd;AAED,SAAO,YAAY;AAClB,SAAM,iBAAiB,KAAK,KAAK,IAAI;EACrC;CACD;AACD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"PostgresMigrator-DcP1o-T6.mjs","names":["uri: string","error: any"],"sources":["../src/PostgresMigrator.ts"],"sourcesContent":["import pg from 'pg';\n\nconst { Client } = pg;\n\n/**\n * Creates a PostgreSQL client connected to the 'postgres' database.\n * Extracts connection details from the provided URI.\n *\n * @param uri - PostgreSQL connection URI\n * @returns Object containing the target database name and client instance\n *\n * @example\n * ```typescript\n * const { database, db } = await setupClient('postgresql://user:pass@localhost:5432/mydb');\n * // database = 'mydb'\n * // db = Client instance connected to 'postgres' database\n * ```\n */\nasync function setupClient(uri: string) {\n\tconst url = new URL(uri);\n\n\tconst db = new Client({\n\t\tuser: url.username,\n\t\tpassword: url.password,\n\t\thost: url.hostname,\n\t\tport: parseInt(url.port, 10),\n\t\tdatabase: 'postgres',\n\t});\n\n\tlet database = url.pathname.slice(1);\n\tif (database.includes('?')) {\n\t\tdatabase = database.substring(0, database.indexOf('?'));\n\t}\n\treturn { database, db };\n}\n\n/**\n * Default logger instance for migration operations.\n */\nconst logger = console;\n\n/**\n * Abstract base class for PostgreSQL database migration utilities.\n * Provides database creation, migration, and cleanup functionality for testing.\n * Subclasses must implement the migrate() method to define migration logic.\n *\n * @example\n * ```typescript\n * class MyMigrator extends PostgresMigrator {\n * async migrate(): Promise<void> {\n * // Run your migrations here\n * await this.runMigrations();\n * }\n * }\n *\n * // Use in tests\n * const migrator = new MyMigrator('postgresql://localhost:5432/test_db');\n * const cleanup = await migrator.start();\n *\n * // Run tests...\n *\n * // Clean up\n * await cleanup();\n * ```\n */\nexport abstract class PostgresMigrator {\n\t/**\n\t * Creates a new PostgresMigrator instance.\n\t *\n\t * @param uri - PostgreSQL connection URI\n\t */\n\tconstructor(private uri: string) {}\n\n\t/**\n\t * Abstract method to be implemented by subclasses.\n\t * Should contain the migration logic for setting up database schema.\n\t *\n\t * @returns Promise that resolves when migrations are complete\n\t */\n\tabstract migrate(): Promise<void>;\n\n\t/**\n\t * Creates a PostgreSQL database if it doesn't already exist.\n\t * Connects to the 'postgres' database to check and create the target database.\n\t *\n\t * @param uri - PostgreSQL connection URI\n\t * @returns Object indicating whether the database already existed\n\t * @private\n\t */\n\tprivate static async create(\n\t\turi: string,\n\t): Promise<{ alreadyExisted: boolean }> {\n\t\tconst { database, db } = await setupClient(uri);\n\t\ttry {\n\t\t\tawait db.connect();\n\t\t\tconst result = await db.query(\n\t\t\t\t`SELECT * FROM pg_catalog.pg_database WHERE datname = '${database}'`,\n\t\t\t);\n\n\t\t\tif (result.rowCount === 0) {\n\t\t\t\ttry {\n\t\t\t\t\tawait db.query(`CREATE DATABASE \"${database}\"`);\n\t\t\t\t} catch (error: any) {\n\t\t\t\t\t// 42P04 = duplicate_database — another process created it between our check and create\n\t\t\t\t\tif (error?.code === '42P04') {\n\t\t\t\t\t\treturn { alreadyExisted: true };\n\t\t\t\t\t}\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\talreadyExisted: result.rowCount ? result.rowCount > 0 : false,\n\t\t\t};\n\t\t} finally {\n\t\t\tawait db.end();\n\t\t}\n\t}\n\n\t/**\n\t * Drops a PostgreSQL database.\n\t * Used for cleanup after tests are complete.\n\t *\n\t * @param uri - PostgreSQL connection URI\n\t * @throws Error if database cannot be dropped\n\t * @private\n\t */\n\tprivate static async drop(uri: string): Promise<void> {\n\t\tconst { database, db } = await setupClient(uri);\n\t\ttry {\n\t\t\tawait db.connect();\n\t\t\tawait db.query(`DROP DATABASE \"${database}\"`);\n\t\t} finally {\n\t\t\tawait db.end();\n\t\t}\n\t}\n\n\t/**\n\t * Starts the migration process by creating the database and running migrations.\n\t * Returns a cleanup function that will drop the database when called.\n\t *\n\t * @returns Async cleanup function that drops the created database\n\t *\n\t * @example\n\t * ```typescript\n\t * const migrator = new MyMigrator('postgresql://localhost:5432/test_db');\n\t *\n\t * // Start migrations and get cleanup function\n\t * const cleanup = await migrator.start();\n\t *\n\t * try {\n\t * // Run your tests here\n\t * await runTests();\n\t * } finally {\n\t * // Always clean up\n\t * await cleanup();\n\t * }\n\t * ```\n\t */\n\tasync start() {\n\t\tconst { database, db } = await setupClient(this.uri);\n\t\ttry {\n\t\t\tawait PostgresMigrator.create(this.uri);\n\t\t\t// Implement migration logic here\n\t\t\tawait this.migrate();\n\t\t\tlogger.log(`Migrating database: ${database}`);\n\t\t\t// Example: await db.query('CREATE TABLE example (id SERIAL PRIMARY KEY)');\n\t\t} finally {\n\t\t\tawait db.end();\n\t\t}\n\n\t\treturn async () => {\n\t\t\tawait PostgresMigrator.drop(this.uri);\n\t\t};\n\t}\n}\n"],"mappings":";;;AAEA,MAAM,EAAE,QAAQ,GAAG;;;;;;;;;;;;;;;AAgBnB,eAAe,YAAYA,KAAa;CACvC,MAAM,MAAM,IAAI,IAAI;CAEpB,MAAM,KAAK,IAAI,OAAO;EACrB,MAAM,IAAI;EACV,UAAU,IAAI;EACd,MAAM,IAAI;EACV,MAAM,SAAS,IAAI,MAAM,GAAG;EAC5B,UAAU;CACV;CAED,IAAI,WAAW,IAAI,SAAS,MAAM,EAAE;AACpC,KAAI,SAAS,SAAS,IAAI,CACzB,YAAW,SAAS,UAAU,GAAG,SAAS,QAAQ,IAAI,CAAC;AAExD,QAAO;EAAE;EAAU;CAAI;AACvB;;;;AAKD,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;AA0Bf,IAAsB,mBAAtB,MAAsB,iBAAiB;;;;;;CAMtC,YAAoBA,KAAa;EAAb;CAAe;;;;;;;;;CAkBnC,aAAqB,OACpBA,KACuC;EACvC,MAAM,EAAE,UAAU,IAAI,GAAG,MAAM,YAAY,IAAI;AAC/C,MAAI;AACH,SAAM,GAAG,SAAS;GAClB,MAAM,SAAS,MAAM,GAAG,OACtB,wDAAwD,SAAS,GAClE;AAED,OAAI,OAAO,aAAa,EACvB,KAAI;AACH,UAAM,GAAG,OAAO,mBAAmB,SAAS,GAAG;GAC/C,SAAQC,OAAY;AAEpB,QAAI,OAAO,SAAS,QACnB,QAAO,EAAE,gBAAgB,KAAM;AAEhC,UAAM;GACN;AAGF,UAAO,EACN,gBAAgB,OAAO,WAAW,OAAO,WAAW,IAAI,MACxD;EACD,UAAS;AACT,SAAM,GAAG,KAAK;EACd;CACD;;;;;;;;;CAUD,aAAqB,KAAKD,KAA4B;EACrD,MAAM,EAAE,UAAU,IAAI,GAAG,MAAM,YAAY,IAAI;AAC/C,MAAI;AACH,SAAM,GAAG,SAAS;AAClB,SAAM,GAAG,OAAO,iBAAiB,SAAS,GAAG;EAC7C,UAAS;AACT,SAAM,GAAG,KAAK;EACd;CACD;;;;;;;;;;;;;;;;;;;;;;;CAwBD,MAAM,QAAQ;EACb,MAAM,EAAE,UAAU,IAAI,GAAG,MAAM,YAAY,KAAK,IAAI;AACpD,MAAI;AACH,SAAM,iBAAiB,OAAO,KAAK,IAAI;AAEvC,SAAM,KAAK,SAAS;AACpB,UAAO,KAAK,sBAAsB,SAAS,EAAE;EAE7C,UAAS;AACT,SAAM,GAAG,KAAK;EACd;AAED,SAAO,YAAY;AAClB,SAAM,iBAAiB,KAAK,KAAK,IAAI;EACrC;CACD;AACD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"PostgresMigrator-S-YYosAC.d.mts","names":[],"sources":["../src/PostgresMigrator.ts"],"sourcesContent":[],"mappings":";;AAiEA;;;;;AA8FY;;;;;;;;;;;;;;;;;;uBA9FU,gBAAA;;;;;;;;;;;;;;sBAcD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAgFT,cAAA"}