pgsql-test 2.19.0 → 2.20.0

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 CHANGED
@@ -685,9 +685,14 @@ Common issues and solutions for pgpm, PostgreSQL, and testing.
685
685
 
686
686
  ## Related Constructive Tooling
687
687
 
688
+ ### 📦 Package Management
689
+
690
+ * [pgpm](https://github.com/constructive-io/constructive/tree/main/pgpm/pgpm): **🖥️ PostgreSQL Package Manager** for modular Postgres development. Works with database workspaces, scaffolding, migrations, seeding, and installing database packages.
691
+
688
692
  ### 🧪 Testing
689
693
 
690
694
  * [pgsql-test](https://github.com/constructive-io/constructive/tree/main/postgres/pgsql-test): **📊 Isolated testing environments** with per-test transaction rollbacks—ideal for integration tests, complex migrations, and RLS simulation.
695
+ * [pgsql-seed](https://github.com/constructive-io/constructive/tree/main/postgres/pgsql-seed): **🌱 PostgreSQL seeding utilities** for CSV, JSON, SQL data loading, and pgpm deployment.
691
696
  * [supabase-test](https://github.com/constructive-io/constructive/tree/main/postgres/supabase-test): **🧪 Supabase-native test harness** preconfigured for the local Supabase stack—per-test rollbacks, JWT/role context helpers, and CI/GitHub Actions ready.
692
697
  * [graphile-test](https://github.com/constructive-io/constructive/tree/main/graphile/graphile-test): **🔐 Authentication mocking** for Graphile-focused test helpers and emulating row-level security contexts.
693
698
  * [pg-query-context](https://github.com/constructive-io/constructive/tree/main/postgres/pg-query-context): **🔒 Session context injection** to add session-local context (e.g., `SET LOCAL`) into queries—ideal for setting `role`, `jwt.claims`, and other session settings.
@@ -701,28 +706,6 @@ Common issues and solutions for pgpm, PostgreSQL, and testing.
701
706
  * [@pgsql/types](https://www.npmjs.com/package/@pgsql/types): **📝 Type definitions** for PostgreSQL AST nodes in TypeScript.
702
707
  * [@pgsql/utils](https://www.npmjs.com/package/@pgsql/utils): **🛠️ AST utilities** for constructing and transforming PostgreSQL syntax trees.
703
708
 
704
- ### 🚀 API & Dev Tools
705
-
706
- * [@constructive-io/graphql-server](https://github.com/constructive-io/constructive/tree/main/graphql/server): **⚡ Express-based API server** powered by PostGraphile to expose a secure, scalable GraphQL API over your Postgres database.
707
- * [@constructive-io/graphql-explorer](https://github.com/constructive-io/constructive/tree/main/graphql/explorer): **🔎 Visual API explorer** with GraphiQL for browsing across all databases and schemas—useful for debugging, documentation, and API prototyping.
708
-
709
- ### 🔁 Streaming & Uploads
710
-
711
- * [etag-hash](https://github.com/constructive-io/constructive/tree/main/streaming/etag-hash): **🏷️ S3-compatible ETags** created by streaming and hashing file uploads in chunks.
712
- * [etag-stream](https://github.com/constructive-io/constructive/tree/main/streaming/etag-stream): **🔄 ETag computation** via Node stream transformer during upload or transfer.
713
- * [uuid-hash](https://github.com/constructive-io/constructive/tree/main/streaming/uuid-hash): **🆔 Deterministic UUIDs** generated from hashed content, great for deduplication and asset referencing.
714
- * [uuid-stream](https://github.com/constructive-io/constructive/tree/main/streaming/uuid-stream): **🌊 Streaming UUID generation** based on piped file content—ideal for upload pipelines.
715
- * [@constructive-io/s3-streamer](https://github.com/constructive-io/constructive/tree/main/streaming/s3-streamer): **📤 Direct S3 streaming** for large files with support for metadata injection and content validation.
716
- * [@constructive-io/upload-names](https://github.com/constructive-io/constructive/tree/main/streaming/upload-names): **📂 Collision-resistant filenames** utility for structured and unique file names for uploads.
717
-
718
- ### 🧰 CLI & Codegen
719
-
720
- * [pgpm](https://github.com/constructive-io/constructive/tree/main/pgpm/pgpm): **🖥️ PostgreSQL Package Manager** for modular Postgres development. Works with database workspaces, scaffolding, migrations, seeding, and installing database packages.
721
- * [@constructive-io/cli](https://github.com/constructive-io/constructive/tree/main/packages/cli): **🖥️ Command-line toolkit** for managing Constructive projects—supports database scaffolding, migrations, seeding, code generation, and automation.
722
- * [@constructive-io/graphql-codegen](https://github.com/constructive-io/constructive/tree/main/graphql/codegen): **✨ GraphQL code generation** (types, operations, SDK) from schema/endpoint introspection.
723
- * [@constructive-io/query-builder](https://github.com/constructive-io/constructive/tree/main/packages/query-builder): **🏗️ SQL constructor** providing a robust TypeScript-based query builder for dynamic generation of `SELECT`, `INSERT`, `UPDATE`, `DELETE`, and stored procedure calls—supports advanced SQL features like `JOIN`, `GROUP BY`, and schema-qualified queries.
724
- * [@constructive-io/graphql-query](https://github.com/constructive-io/constructive/tree/main/graphql/query): **🧩 Fluent GraphQL builder** for PostGraphile schemas. ⚡ Schema-aware via introspection, 🧩 composable and ergonomic for building deeply nested queries.
725
-
726
709
  ## Credits
727
710
 
728
711
  **🛠 Built by the [Constructive](https://constructive.io) team — creators of modular Postgres tooling for secure, composable backends. If you like our work, contribute on [GitHub](https://github.com/constructive-io).**
package/admin.d.ts CHANGED
@@ -1,26 +1,8 @@
1
+ import { DbAdmin as BaseDbAdmin } from 'pgsql-client';
1
2
  import { PgTestConnectionOptions } from '@pgpmjs/types';
2
3
  import { PgConfig } from 'pg-env';
3
4
  import { SeedAdapter } from './seed/types';
4
- export declare class DbAdmin {
5
- private config;
6
- private verbose;
7
- private roleConfig?;
5
+ export declare class DbAdmin extends BaseDbAdmin {
8
6
  constructor(config: PgConfig, verbose?: boolean, roleConfig?: PgTestConnectionOptions);
9
- private getEnv;
10
- private run;
11
- private safeDropDb;
12
- drop(dbName?: string): void;
13
- dropTemplate(dbName: string): void;
14
- create(dbName?: string): void;
15
- createFromTemplate(template: string, dbName?: string): void;
16
- installExtensions(extensions: string[] | string, dbName?: string): void;
17
- connectionString(dbName?: string): string;
18
- createTemplateFromBase(base: string, template: string): void;
19
- cleanupTemplate(template: string): void;
20
- grantRole(role: string, user: string, dbName?: string): Promise<void>;
21
- grantConnect(role: string, dbName?: string): Promise<void>;
22
- createUserRole(user: string, password: string, dbName: string, useLocksForRoles?: boolean): Promise<void>;
23
- loadSql(file: string, dbName: string): void;
24
- streamSql(sql: string, dbName: string): Promise<void>;
25
7
  createSeededTemplate(templateName: string, adapter: SeedAdapter): Promise<void>;
26
8
  }
package/admin.js CHANGED
@@ -1,131 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DbAdmin = void 0;
4
- const core_1 = require("@pgpmjs/core");
5
- const logger_1 = require("@pgpmjs/logger");
6
- const child_process_1 = require("child_process");
7
- const fs_1 = require("fs");
8
- const pg_env_1 = require("pg-env");
9
- const roles_1 = require("./roles");
10
- const stream_1 = require("./stream");
11
- const log = new logger_1.Logger('db-admin');
12
- class DbAdmin {
13
- config;
14
- verbose;
15
- roleConfig;
4
+ const pgsql_client_1 = require("pgsql-client");
5
+ // Extend DbAdmin from pgsql-client with test-specific methods
6
+ class DbAdmin extends pgsql_client_1.DbAdmin {
16
7
  constructor(config, verbose = false, roleConfig) {
17
- this.config = config;
18
- this.verbose = verbose;
19
- this.roleConfig = roleConfig;
20
- this.config = (0, pg_env_1.getPgEnvOptions)(config);
21
- }
22
- getEnv() {
23
- return {
24
- PGHOST: this.config.host,
25
- PGPORT: String(this.config.port),
26
- PGUSER: this.config.user,
27
- PGPASSWORD: this.config.password
28
- };
29
- }
30
- run(command) {
31
- try {
32
- (0, child_process_1.execSync)(command, {
33
- stdio: this.verbose ? 'inherit' : 'pipe',
34
- env: {
35
- ...process.env,
36
- ...this.getEnv()
37
- }
38
- });
39
- if (this.verbose)
40
- log.success(`Executed: ${command}`);
41
- }
42
- catch (err) {
43
- log.error(`Command failed: ${command}`);
44
- if (this.verbose)
45
- log.error(err.message);
46
- throw err;
47
- }
48
- }
49
- safeDropDb(name) {
50
- try {
51
- this.run(`dropdb "${name}"`);
52
- }
53
- catch (err) {
54
- if (!err.message.includes('does not exist')) {
55
- log.warn(`Could not drop database ${name}: ${err.message}`);
56
- }
57
- }
58
- }
59
- drop(dbName) {
60
- this.safeDropDb(dbName ?? this.config.database);
61
- }
62
- dropTemplate(dbName) {
63
- this.run(`psql -c "UPDATE pg_database SET datistemplate='false' WHERE datname='${dbName}';"`);
64
- this.drop(dbName);
65
- }
66
- create(dbName) {
67
- const db = dbName ?? this.config.database;
68
- this.run(`createdb -U ${this.config.user} -h ${this.config.host} -p ${this.config.port} "${db}"`);
69
- }
70
- createFromTemplate(template, dbName) {
71
- const db = dbName ?? this.config.database;
72
- this.run(`createdb -U ${this.config.user} -h ${this.config.host} -p ${this.config.port} -e "${db}" -T "${template}"`);
73
- }
74
- installExtensions(extensions, dbName) {
75
- const db = dbName ?? this.config.database;
76
- const extList = typeof extensions === 'string' ? extensions.split(',') : extensions;
77
- for (const extension of extList) {
78
- this.run(`psql --dbname "${db}" -c 'CREATE EXTENSION IF NOT EXISTS "${extension}" CASCADE;'`);
79
- }
80
- }
81
- connectionString(dbName) {
82
- const { user, password, host, port } = this.config;
83
- const db = dbName ?? this.config.database;
84
- return `postgres://${user}:${password}@${host}:${port}/${db}`;
85
- }
86
- createTemplateFromBase(base, template) {
87
- this.run(`createdb -T "${base}" "${template}"`);
88
- this.run(`psql -c "UPDATE pg_database SET datistemplate = true WHERE datname = '${template}';"`);
89
- }
90
- cleanupTemplate(template) {
91
- try {
92
- this.run(`psql -c "UPDATE pg_database SET datistemplate = false WHERE datname = '${template}'"`);
93
- }
94
- catch {
95
- log.warn(`Skipping failed UPDATE of datistemplate for ${template}`);
96
- }
97
- this.safeDropDb(template);
98
- }
99
- async grantRole(role, user, dbName) {
100
- const db = dbName ?? this.config.database;
101
- const sql = (0, core_1.generateGrantRoleSQL)(role, user);
102
- await this.streamSql(sql, db);
103
- }
104
- async grantConnect(role, dbName) {
105
- const db = dbName ?? this.config.database;
106
- const sql = `GRANT CONNECT ON DATABASE "${db}" TO ${role};`;
107
- await this.streamSql(sql, db);
108
- }
109
- // ONLY granting admin role for testing purposes, normally the db connection for apps won't have admin role
110
- // DO NOT USE THIS FOR PRODUCTION
111
- async createUserRole(user, password, dbName, useLocksForRoles = false) {
112
- const anonRole = (0, roles_1.getRoleName)('anonymous', this.roleConfig);
113
- const authRole = (0, roles_1.getRoleName)('authenticated', this.roleConfig);
114
- const adminRole = (0, roles_1.getRoleName)('administrator', this.roleConfig);
115
- const sql = (0, core_1.generateCreateUserWithGrantsSQL)(user, password, [anonRole, authRole, adminRole], useLocksForRoles);
116
- await this.streamSql(sql, dbName);
117
- }
118
- loadSql(file, dbName) {
119
- if (!(0, fs_1.existsSync)(file)) {
120
- throw new Error(`Missing SQL file: ${file}`);
121
- }
122
- this.run(`psql -f ${file} ${dbName}`);
123
- }
124
- async streamSql(sql, dbName) {
125
- await (0, stream_1.streamSql)({
126
- ...this.config,
127
- database: dbName
128
- }, sql);
8
+ super(config, verbose, roleConfig);
129
9
  }
130
10
  async createSeededTemplate(templateName, adapter) {
131
11
  const seedDb = this.config.database;
package/connect.d.ts CHANGED
@@ -9,11 +9,15 @@ export interface GetConnectionOpts {
9
9
  pg?: Partial<PgConfig>;
10
10
  db?: Partial<PgTestConnectionOptions>;
11
11
  }
12
+ export interface TeardownOptions {
13
+ /** If true, keeps the database after closing connections (default: false) */
14
+ keepDb?: boolean;
15
+ }
12
16
  export interface GetConnectionResult {
13
17
  pg: PgTestClient;
14
18
  db: PgTestClient;
15
19
  admin: DbAdmin;
16
- teardown: () => Promise<void>;
20
+ teardown: (opts?: TeardownOptions) => Promise<void>;
17
21
  manager: PgTestConnector;
18
22
  }
19
23
  export declare const getConnections: (cn?: GetConnectionOpts, seedAdapters?: SeedAdapter[]) => Promise<GetConnectionResult>;
package/connect.js CHANGED
@@ -5,9 +5,9 @@ const env_1 = require("@pgpmjs/env");
5
5
  const crypto_1 = require("crypto");
6
6
  const pg_cache_1 = require("pg-cache");
7
7
  const pg_env_1 = require("pg-env");
8
+ const pgsql_client_1 = require("pgsql-client");
8
9
  const admin_1 = require("./admin");
9
10
  const manager_1 = require("./manager");
10
- const roles_1 = require("./roles");
11
11
  const seed_1 = require("./seed");
12
12
  let manager;
13
13
  const getPgRootAdmin = (config, connOpts = {}) => {
@@ -54,13 +54,15 @@ const getConnections = async (cn = {}, seedAdapters = [seed_1.seed.pgpm()]) => {
54
54
  manager = manager_1.PgTestConnector.getInstance(config);
55
55
  const pg = manager.getClient(config);
56
56
  let teardownPromise = null;
57
- const teardown = async () => {
57
+ let teardownOpts = {};
58
+ const teardown = async (opts = {}) => {
59
+ teardownOpts = opts;
58
60
  if (teardownPromise)
59
61
  return teardownPromise;
60
62
  teardownPromise = (async () => {
61
63
  manager.beginTeardown();
62
64
  await (0, pg_cache_1.teardownPgPools)();
63
- await manager.closeAll();
65
+ await manager.closeAll({ keepDb: teardownOpts.keepDb });
64
66
  })();
65
67
  return teardownPromise;
66
68
  };
@@ -89,7 +91,7 @@ const getConnections = async (cn = {}, seedAdapters = [seed_1.seed.pgpm()]) => {
89
91
  auth: connOpts.auth,
90
92
  roles: connOpts.roles
91
93
  });
92
- db.setContext({ role: (0, roles_1.getDefaultRole)(connOpts) });
94
+ db.setContext({ role: (0, pgsql_client_1.getDefaultRole)(connOpts) });
93
95
  return { pg, db, teardown, manager, admin };
94
96
  };
95
97
  exports.getConnections = getConnections;
package/esm/admin.js CHANGED
@@ -1,128 +1,8 @@
1
- import { generateCreateUserWithGrantsSQL, generateGrantRoleSQL } from '@pgpmjs/core';
2
- import { Logger } from '@pgpmjs/logger';
3
- import { execSync } from 'child_process';
4
- import { existsSync } from 'fs';
5
- import { getPgEnvOptions } from 'pg-env';
6
- import { getRoleName } from './roles';
7
- import { streamSql as stream } from './stream';
8
- const log = new Logger('db-admin');
9
- export class DbAdmin {
10
- config;
11
- verbose;
12
- roleConfig;
1
+ import { DbAdmin as BaseDbAdmin } from 'pgsql-client';
2
+ // Extend DbAdmin from pgsql-client with test-specific methods
3
+ export class DbAdmin extends BaseDbAdmin {
13
4
  constructor(config, verbose = false, roleConfig) {
14
- this.config = config;
15
- this.verbose = verbose;
16
- this.roleConfig = roleConfig;
17
- this.config = getPgEnvOptions(config);
18
- }
19
- getEnv() {
20
- return {
21
- PGHOST: this.config.host,
22
- PGPORT: String(this.config.port),
23
- PGUSER: this.config.user,
24
- PGPASSWORD: this.config.password
25
- };
26
- }
27
- run(command) {
28
- try {
29
- execSync(command, {
30
- stdio: this.verbose ? 'inherit' : 'pipe',
31
- env: {
32
- ...process.env,
33
- ...this.getEnv()
34
- }
35
- });
36
- if (this.verbose)
37
- log.success(`Executed: ${command}`);
38
- }
39
- catch (err) {
40
- log.error(`Command failed: ${command}`);
41
- if (this.verbose)
42
- log.error(err.message);
43
- throw err;
44
- }
45
- }
46
- safeDropDb(name) {
47
- try {
48
- this.run(`dropdb "${name}"`);
49
- }
50
- catch (err) {
51
- if (!err.message.includes('does not exist')) {
52
- log.warn(`Could not drop database ${name}: ${err.message}`);
53
- }
54
- }
55
- }
56
- drop(dbName) {
57
- this.safeDropDb(dbName ?? this.config.database);
58
- }
59
- dropTemplate(dbName) {
60
- this.run(`psql -c "UPDATE pg_database SET datistemplate='false' WHERE datname='${dbName}';"`);
61
- this.drop(dbName);
62
- }
63
- create(dbName) {
64
- const db = dbName ?? this.config.database;
65
- this.run(`createdb -U ${this.config.user} -h ${this.config.host} -p ${this.config.port} "${db}"`);
66
- }
67
- createFromTemplate(template, dbName) {
68
- const db = dbName ?? this.config.database;
69
- this.run(`createdb -U ${this.config.user} -h ${this.config.host} -p ${this.config.port} -e "${db}" -T "${template}"`);
70
- }
71
- installExtensions(extensions, dbName) {
72
- const db = dbName ?? this.config.database;
73
- const extList = typeof extensions === 'string' ? extensions.split(',') : extensions;
74
- for (const extension of extList) {
75
- this.run(`psql --dbname "${db}" -c 'CREATE EXTENSION IF NOT EXISTS "${extension}" CASCADE;'`);
76
- }
77
- }
78
- connectionString(dbName) {
79
- const { user, password, host, port } = this.config;
80
- const db = dbName ?? this.config.database;
81
- return `postgres://${user}:${password}@${host}:${port}/${db}`;
82
- }
83
- createTemplateFromBase(base, template) {
84
- this.run(`createdb -T "${base}" "${template}"`);
85
- this.run(`psql -c "UPDATE pg_database SET datistemplate = true WHERE datname = '${template}';"`);
86
- }
87
- cleanupTemplate(template) {
88
- try {
89
- this.run(`psql -c "UPDATE pg_database SET datistemplate = false WHERE datname = '${template}'"`);
90
- }
91
- catch {
92
- log.warn(`Skipping failed UPDATE of datistemplate for ${template}`);
93
- }
94
- this.safeDropDb(template);
95
- }
96
- async grantRole(role, user, dbName) {
97
- const db = dbName ?? this.config.database;
98
- const sql = generateGrantRoleSQL(role, user);
99
- await this.streamSql(sql, db);
100
- }
101
- async grantConnect(role, dbName) {
102
- const db = dbName ?? this.config.database;
103
- const sql = `GRANT CONNECT ON DATABASE "${db}" TO ${role};`;
104
- await this.streamSql(sql, db);
105
- }
106
- // ONLY granting admin role for testing purposes, normally the db connection for apps won't have admin role
107
- // DO NOT USE THIS FOR PRODUCTION
108
- async createUserRole(user, password, dbName, useLocksForRoles = false) {
109
- const anonRole = getRoleName('anonymous', this.roleConfig);
110
- const authRole = getRoleName('authenticated', this.roleConfig);
111
- const adminRole = getRoleName('administrator', this.roleConfig);
112
- const sql = generateCreateUserWithGrantsSQL(user, password, [anonRole, authRole, adminRole], useLocksForRoles);
113
- await this.streamSql(sql, dbName);
114
- }
115
- loadSql(file, dbName) {
116
- if (!existsSync(file)) {
117
- throw new Error(`Missing SQL file: ${file}`);
118
- }
119
- this.run(`psql -f ${file} ${dbName}`);
120
- }
121
- async streamSql(sql, dbName) {
122
- await stream({
123
- ...this.config,
124
- database: dbName
125
- }, sql);
5
+ super(config, verbose, roleConfig);
126
6
  }
127
7
  async createSeededTemplate(templateName, adapter) {
128
8
  const seedDb = this.config.database;
package/esm/connect.js CHANGED
@@ -2,9 +2,9 @@ import { getConnEnvOptions } from '@pgpmjs/env';
2
2
  import { randomUUID } from 'crypto';
3
3
  import { teardownPgPools } from 'pg-cache';
4
4
  import { getPgEnvOptions, } from 'pg-env';
5
+ import { getDefaultRole } from 'pgsql-client';
5
6
  import { DbAdmin } from './admin';
6
7
  import { PgTestConnector } from './manager';
7
- import { getDefaultRole } from './roles';
8
8
  import { seed } from './seed';
9
9
  let manager;
10
10
  export const getPgRootAdmin = (config, connOpts = {}) => {
@@ -50,13 +50,15 @@ export const getConnections = async (cn = {}, seedAdapters = [seed.pgpm()]) => {
50
50
  manager = PgTestConnector.getInstance(config);
51
51
  const pg = manager.getClient(config);
52
52
  let teardownPromise = null;
53
- const teardown = async () => {
53
+ let teardownOpts = {};
54
+ const teardown = async (opts = {}) => {
55
+ teardownOpts = opts;
54
56
  if (teardownPromise)
55
57
  return teardownPromise;
56
58
  teardownPromise = (async () => {
57
59
  manager.beginTeardown();
58
60
  await teardownPgPools();
59
- await manager.closeAll();
61
+ await manager.closeAll({ keepDb: teardownOpts.keepDb });
60
62
  })();
61
63
  return teardownPromise;
62
64
  };
package/esm/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  export * from './admin';
2
2
  export * from './connect';
3
3
  export * from './manager';
4
- export * from './roles';
5
4
  export * from './seed';
6
5
  export * from './test-client';
7
6
  export { snapshot } from './utils';
package/esm/manager.js CHANGED
@@ -84,7 +84,8 @@ export class PgTestConnector {
84
84
  log.info(`🔌 New PgTestClient connected to ${config.database}`);
85
85
  return client;
86
86
  }
87
- async closeAll() {
87
+ async closeAll(opts = {}) {
88
+ const { keepDb = false } = opts;
88
89
  this.beginTeardown();
89
90
  await this.awaitPendingConnects();
90
91
  log.info('🧹 Closing all PgTestClients...');
@@ -104,20 +105,28 @@ export class PgTestConnector {
104
105
  end(pool);
105
106
  }
106
107
  this.pgPools.clear();
107
- log.info('🗑️ Dropping seen databases...');
108
- await Promise.all(Array.from(this.seenDbConfigs.values()).map(async (config) => {
109
- try {
110
- const rootPg = getPgEnvOptions(this.config);
111
- const admin = new DbAdmin({ ...config, user: rootPg.user, password: rootPg.password }, this.verbose);
112
- admin.drop();
113
- log.warn(`🧨 Dropped database: ${config.database}`);
114
- }
115
- catch (err) {
116
- log.error(`❌ Failed to drop database ${config.database}:`, err);
117
- }
118
- }));
108
+ if (keepDb) {
109
+ log.info('📦 Keeping databases (keepDb=true)...');
110
+ const dbNames = Array.from(this.seenDbConfigs.values()).map(c => c.database);
111
+ log.info(`📦 Preserved databases: ${dbNames.join(', ')}`);
112
+ }
113
+ else {
114
+ log.info('🗑️ Dropping seen databases...');
115
+ await Promise.all(Array.from(this.seenDbConfigs.values()).map(async (config) => {
116
+ try {
117
+ const rootPg = getPgEnvOptions(this.config);
118
+ const admin = new DbAdmin({ ...config, user: rootPg.user, password: rootPg.password }, this.verbose);
119
+ admin.drop();
120
+ log.warn(`🧨 Dropped database: ${config.database}`);
121
+ }
122
+ catch (err) {
123
+ log.error(`❌ Failed to drop database ${config.database}:`, err);
124
+ }
125
+ }));
126
+ }
119
127
  this.seenDbConfigs.clear();
120
- log.success('✅ All PgTestClients closed, pools disposed, databases dropped.');
128
+ const action = keepDb ? 'preserved' : 'dropped';
129
+ log.success(`✅ All PgTestClients closed, pools disposed, databases ${action}.`);
121
130
  this.pendingConnects.clear();
122
131
  this.shuttingDown = false;
123
132
  }
@@ -1,60 +1,11 @@
1
- import { Client } from 'pg';
2
- import { getRoleName } from './roles';
3
- import { generateContextStatements } from './context-utils';
1
+ import { PgClient } from 'pgsql-client';
4
2
  import { insertJsonMap } from 'pgsql-seed';
5
3
  import { loadCsvMap } from 'pgsql-seed';
6
4
  import { loadSqlFiles } from 'pgsql-seed';
7
5
  import { deployPgpm } from 'pgsql-seed';
8
- export class PgTestClient {
9
- config;
10
- client;
11
- opts;
12
- ctxStmts = '';
13
- contextSettings = {};
14
- _ended = false;
15
- connectPromise = null;
6
+ export class PgTestClient extends PgClient {
16
7
  constructor(config, opts = {}) {
17
- this.opts = opts;
18
- this.config = config;
19
- this.client = new Client({
20
- host: this.config.host,
21
- port: this.config.port,
22
- database: this.config.database,
23
- user: this.config.user,
24
- password: this.config.password
25
- });
26
- if (!opts.deferConnect) {
27
- this.connectPromise = this.client.connect();
28
- if (opts.trackConnect)
29
- opts.trackConnect(this.connectPromise);
30
- }
31
- }
32
- async ensureConnected() {
33
- if (this.connectPromise) {
34
- try {
35
- await this.connectPromise;
36
- }
37
- catch { }
38
- }
39
- }
40
- async close() {
41
- if (!this._ended) {
42
- this._ended = true;
43
- await this.ensureConnected();
44
- this.client.end();
45
- }
46
- }
47
- async begin() {
48
- await this.client.query('BEGIN;');
49
- }
50
- async savepoint(name = 'lqlsavepoint') {
51
- await this.client.query(`SAVEPOINT "${name}";`);
52
- }
53
- async rollback(name = 'lqlsavepoint') {
54
- await this.client.query(`ROLLBACK TO SAVEPOINT "${name}";`);
55
- }
56
- async commit() {
57
- await this.client.query('COMMIT;');
8
+ super(config, opts);
58
9
  }
59
10
  async beforeEach() {
60
11
  await this.begin();
@@ -64,23 +15,6 @@ export class PgTestClient {
64
15
  await this.rollback();
65
16
  await this.commit();
66
17
  }
67
- setContext(ctx) {
68
- Object.assign(this.contextSettings, ctx);
69
- this.ctxStmts = generateContextStatements(this.contextSettings);
70
- }
71
- /**
72
- * Set authentication context for the current session.
73
- * Configures role and user ID using cascading defaults from options → opts.auth → RoleMapping.
74
- */
75
- auth(options = {}) {
76
- const role = options.role ?? this.opts.auth?.role ?? getRoleName('authenticated', this.opts);
77
- const userIdKey = options.userIdKey ?? this.opts.auth?.userIdKey ?? 'jwt.claims.user_id';
78
- const userId = options.userId ?? this.opts.auth?.userId ?? null;
79
- this.setContext({
80
- role,
81
- [userIdKey]: userId !== null ? String(userId) : null
82
- });
83
- }
84
18
  /**
85
19
  * Commit current transaction to make data visible to other connections, then start fresh transaction.
86
20
  * Maintains test isolation by creating a savepoint and reapplying session context.
@@ -91,60 +25,6 @@ export class PgTestClient {
91
25
  await this.savepoint(); // keep rollback harness
92
26
  await this.ctxQuery(); // reapply all setContext()
93
27
  }
94
- /**
95
- * Clear all session context variables and reset to default anonymous role.
96
- */
97
- clearContext() {
98
- const defaultRole = getRoleName('anonymous', this.opts);
99
- const nulledSettings = {};
100
- Object.keys(this.contextSettings).forEach(key => {
101
- nulledSettings[key] = null;
102
- });
103
- nulledSettings.role = defaultRole;
104
- this.ctxStmts = generateContextStatements(nulledSettings);
105
- this.contextSettings = { role: defaultRole };
106
- }
107
- async any(query, values) {
108
- const result = await this.query(query, values);
109
- return result.rows;
110
- }
111
- async one(query, values) {
112
- const rows = await this.any(query, values);
113
- if (rows.length !== 1) {
114
- throw new Error('Expected exactly one result');
115
- }
116
- return rows[0];
117
- }
118
- async oneOrNone(query, values) {
119
- const rows = await this.any(query, values);
120
- return rows[0] || null;
121
- }
122
- async many(query, values) {
123
- const rows = await this.any(query, values);
124
- if (rows.length === 0)
125
- throw new Error('Expected many rows, got none');
126
- return rows;
127
- }
128
- async manyOrNone(query, values) {
129
- return this.any(query, values);
130
- }
131
- async none(query, values) {
132
- await this.query(query, values);
133
- }
134
- async result(query, values) {
135
- return this.query(query, values);
136
- }
137
- async ctxQuery() {
138
- if (this.ctxStmts) {
139
- await this.client.query(this.ctxStmts);
140
- }
141
- }
142
- // NOTE: all queries should call ctxQuery() before executing the query
143
- async query(query, values) {
144
- await this.ctxQuery();
145
- const result = await this.client.query(query, values);
146
- return result;
147
- }
148
28
  async loadJson(data) {
149
29
  await this.ctxQuery();
150
30
  await insertJsonMap(this.client, data);
package/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  export * from './admin';
2
2
  export * from './connect';
3
3
  export * from './manager';
4
- export * from './roles';
5
4
  export * from './seed';
6
5
  export * from './test-client';
7
6
  export { snapshot } from './utils';
package/index.js CHANGED
@@ -18,7 +18,6 @@ exports.snapshot = void 0;
18
18
  __exportStar(require("./admin"), exports);
19
19
  __exportStar(require("./connect"), exports);
20
20
  __exportStar(require("./manager"), exports);
21
- __exportStar(require("./roles"), exports);
22
21
  __exportStar(require("./seed"), exports);
23
22
  __exportStar(require("./test-client"), exports);
24
23
  var utils_1 = require("./utils");
package/manager.d.ts CHANGED
@@ -19,7 +19,9 @@ export declare class PgTestConnector {
19
19
  private awaitPendingConnects;
20
20
  getPool(config: PgConfig): Pool;
21
21
  getClient(config: PgConfig, opts?: Partial<PgTestClientOpts>): PgTestClient;
22
- closeAll(): Promise<void>;
22
+ closeAll(opts?: {
23
+ keepDb?: boolean;
24
+ }): Promise<void>;
23
25
  close(): void;
24
26
  drop(config: PgConfig): void;
25
27
  kill(client: PgTestClient): Promise<void>;