@loomcore/api 0.1.135 → 0.1.136

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 (31) hide show
  1. package/dist/__tests__/postgres.test-database.d.ts +1 -1
  2. package/dist/__tests__/postgres.test-database.js +28 -30
  3. package/dist/databases/models/database.d.ts +2 -2
  4. package/dist/databases/postgres/commands/postgres-batch-update.command.d.ts +2 -2
  5. package/dist/databases/postgres/commands/postgres-batch-update.command.js +22 -7
  6. package/dist/databases/postgres/commands/postgres-create-many.command.d.ts +2 -2
  7. package/dist/databases/postgres/commands/postgres-create.command.d.ts +2 -2
  8. package/dist/databases/postgres/commands/postgres-delete-by-id.command.d.ts +2 -2
  9. package/dist/databases/postgres/commands/postgres-delete-many.command.d.ts +2 -2
  10. package/dist/databases/postgres/commands/postgres-full-update-by-id.command.d.ts +2 -2
  11. package/dist/databases/postgres/commands/postgres-partial-update-by-id.command.d.ts +2 -2
  12. package/dist/databases/postgres/commands/postgres-update.command.d.ts +2 -2
  13. package/dist/databases/postgres/index.d.ts +1 -0
  14. package/dist/databases/postgres/index.js +1 -0
  15. package/dist/databases/postgres/migrations/postgres-initial-schema.js +29 -13
  16. package/dist/databases/postgres/postgres-connection.d.ts +2 -0
  17. package/dist/databases/postgres/postgres-connection.js +1 -0
  18. package/dist/databases/postgres/postgres.database.d.ts +3 -3
  19. package/dist/databases/postgres/postgres.database.js +18 -18
  20. package/dist/databases/postgres/queries/postgres-find-one.query.d.ts +2 -2
  21. package/dist/databases/postgres/queries/postgres-find.query.d.ts +2 -2
  22. package/dist/databases/postgres/queries/postgres-get-all.query.d.ts +2 -2
  23. package/dist/databases/postgres/queries/postgres-get-by-id.query.d.ts +2 -2
  24. package/dist/databases/postgres/queries/postgres-get-count.query.d.ts +2 -2
  25. package/dist/databases/postgres/queries/postgres-get.query.d.ts +2 -2
  26. package/dist/databases/postgres/utils/build-count-query.d.ts +2 -2
  27. package/dist/databases/postgres/utils/build-select-clause.d.ts +3 -3
  28. package/dist/databases/postgres/utils/does-table-exist.util.d.ts +2 -2
  29. package/dist/utils/express.utils.d.ts +2 -1
  30. package/dist/utils/express.utils.js +21 -5
  31. package/package.json +1 -1
@@ -3,7 +3,7 @@ import { IDatabase } from '../databases/models/index.js';
3
3
  export declare class TestPostgresDatabase implements ITestDatabase {
4
4
  private database;
5
5
  private postgresClient;
6
- private postgresPool;
6
+ private migrationPool;
7
7
  private initPromise;
8
8
  init(): Promise<IDatabase>;
9
9
  getRandomId(): string;
@@ -1,6 +1,6 @@
1
1
  import testUtils from './common-test.utils.js';
2
2
  import { newDb } from "pg-mem";
3
- import { Client, Pool } from 'pg';
3
+ import { Pool } from 'pg';
4
4
  import { PostgresDatabase } from '../databases/postgres/postgres.database.js';
5
5
  import { config } from '../config/base-api-config.js';
6
6
  import { runInitialSchemaMigrations, runTestSchemaMigrations } from '../databases/postgres/migrations/__tests__/test-migration-helper.js';
@@ -8,7 +8,7 @@ const USE_REAL_POSTGRES = process.env.USE_REAL_POSTGRES === 'true';
8
8
  export class TestPostgresDatabase {
9
9
  database = null;
10
10
  postgresClient = null;
11
- postgresPool = null;
11
+ migrationPool = null;
12
12
  initPromise = null;
13
13
  async init() {
14
14
  if (this.initPromise) {
@@ -22,18 +22,21 @@ export class TestPostgresDatabase {
22
22
  }
23
23
  async _performInit() {
24
24
  if (!this.database) {
25
- let postgresClient;
25
+ let connection;
26
26
  let pool;
27
27
  if (USE_REAL_POSTGRES) {
28
28
  const connectionString = `postgresql://test-user:test-password@localhost:5444/test-db`;
29
- postgresClient = new Client({
29
+ pool = new Pool({
30
30
  connectionString,
31
31
  connectionTimeoutMillis: 5000,
32
32
  });
33
+ this.migrationPool = pool;
33
34
  try {
34
- await postgresClient.connect();
35
+ await pool.query('SELECT now(), current_database()');
35
36
  }
36
37
  catch (error) {
38
+ await pool.end();
39
+ this.migrationPool = null;
37
40
  const errorMessage = error.message || String(error);
38
41
  const isPermissionError = errorMessage.includes('permission denied') || errorMessage.includes('operation not permitted');
39
42
  if (isPermissionError) {
@@ -49,20 +52,18 @@ export class TestPostgresDatabase {
49
52
  `View container logs: npm run test:db:logs\n` +
50
53
  `Connection error: ${errorMessage}`);
51
54
  }
52
- pool = new Pool({
53
- connectionString,
54
- connectionTimeoutMillis: 5000,
55
- });
55
+ connection = pool;
56
56
  }
57
57
  else {
58
58
  const { Client } = newDb().adapters.createPg();
59
- postgresClient = new Client();
60
- await postgresClient.connect();
61
- pool = postgresClient;
59
+ const pgMemClient = new Client();
60
+ await pgMemClient.connect();
61
+ pool = pgMemClient;
62
+ connection = pgMemClient;
63
+ this.migrationPool = pool;
62
64
  }
63
- this.postgresPool = pool;
64
- this.database = new PostgresDatabase(postgresClient);
65
- this.postgresClient = postgresClient;
65
+ this.database = new PostgresDatabase(connection);
66
+ this.postgresClient = connection;
66
67
  const { initializeSystemUserContext, isSystemUserContextInitialized } = await import('@loomcore/common/models');
67
68
  if (!isSystemUserContextInitialized()) {
68
69
  initializeSystemUserContext(config.email?.systemEmailAddress || 'system@test.com', undefined);
@@ -70,14 +71,14 @@ export class TestPostgresDatabase {
70
71
  await runInitialSchemaMigrations(pool, config);
71
72
  await runTestSchemaMigrations(pool, config);
72
73
  testUtils.initialize(this.database);
73
- await this.createIndexes(postgresClient);
74
+ await this.createIndexes(connection);
74
75
  await testUtils.createMetaOrg();
75
76
  }
76
77
  return this.database;
77
78
  }
78
- async createIndexes(client) {
79
+ async createIndexes(connection) {
79
80
  try {
80
- await client.query(`
81
+ await connection.query(`
81
82
  CREATE INDEX IF NOT EXISTS email_index ON users (LOWER(email));
82
83
  CREATE UNIQUE INDEX IF NOT EXISTS email_unique_index ON users (LOWER(email));
83
84
  `);
@@ -97,7 +98,7 @@ export class TestPostgresDatabase {
97
98
  AND "table_type" = 'BASE TABLE'
98
99
  `);
99
100
  if (USE_REAL_POSTGRES) {
100
- await this.postgresClient?.query(`TRUNCATE TABLE ${result.rows.map(row => `"${row.table_name}"`).join(', ')} RESTART IDENTITY CASCADE`);
101
+ await this.postgresClient.query(`TRUNCATE TABLE ${result.rows.map(row => `"${row.table_name}"`).join(', ')} RESTART IDENTITY CASCADE`);
101
102
  }
102
103
  else {
103
104
  result.rows.forEach(async (row) => {
@@ -111,21 +112,18 @@ export class TestPostgresDatabase {
111
112
  }
112
113
  if (this.postgresClient) {
113
114
  try {
114
- await this.postgresClient.end();
115
- }
116
- catch (error) {
117
- console.warn('Error closing PostgreSQL client:', error);
118
- }
119
- }
120
- if (this.postgresPool) {
121
- try {
122
- await this.postgresPool.end();
115
+ if (this.postgresClient instanceof Pool) {
116
+ await this.postgresClient.end();
117
+ }
118
+ else {
119
+ await this.postgresClient.end();
120
+ }
123
121
  }
124
122
  catch (error) {
125
- console.warn('Error closing PostgreSQL pool:', error);
123
+ console.warn('Error closing PostgreSQL connection:', error);
126
124
  }
127
- this.postgresPool = null;
128
125
  }
126
+ this.migrationPool = null;
129
127
  this.initPromise = null;
130
128
  this.database = null;
131
129
  this.postgresClient = null;
@@ -1,3 +1,3 @@
1
1
  import { Db } from 'mongodb';
2
- import { Client } from 'pg';
3
- export type Database = Db | Client;
2
+ import type { Client, Pool } from 'pg';
3
+ export type Database = Db | Client | Pool;
@@ -1,4 +1,4 @@
1
- import { Client } from 'pg';
1
+ import type { PostgresConnection } from '../postgres-connection.js';
2
2
  import { Operation } from "../../operations/operation.js";
3
3
  import { IEntity, IQueryOptions } from '@loomcore/common/models';
4
- export declare function batchUpdate<T extends IEntity>(client: Client, entities: Partial<T>[], operations: Operation[], queryObject: IQueryOptions, pluralResourceName: string): Promise<T[]>;
4
+ export declare function batchUpdate<T extends IEntity>(connection: PostgresConnection, entities: Partial<T>[], operations: Operation[], queryObject: IQueryOptions, pluralResourceName: string): Promise<T[]>;
@@ -1,3 +1,4 @@
1
+ import { Pool } from 'pg';
1
2
  import { LeftJoin } from "../../operations/left-join.operation.js";
2
3
  import { InnerJoin } from "../../operations/inner-join.operation.js";
3
4
  import { LeftJoinMany } from "../../operations/left-join-many.operation.js";
@@ -7,7 +8,7 @@ import { buildSelectClause } from '../utils/build-select-clause.js';
7
8
  import { transformJoinResults } from '../utils/transform-join-results.js';
8
9
  import { columnsAndValuesFromEntity } from '../utils/columns-and-values-from-entity.js';
9
10
  import { buildWhereClause } from '../utils/build-where-clause.js';
10
- export async function batchUpdate(client, entities, operations, queryObject, pluralResourceName) {
11
+ export async function batchUpdate(connection, entities, operations, queryObject, pluralResourceName) {
11
12
  if (!entities || entities.length === 0) {
12
13
  return [];
13
14
  }
@@ -19,8 +20,17 @@ export async function batchUpdate(client, entities, operations, queryObject, plu
19
20
  entityIds.push(entity._id);
20
21
  }
21
22
  queryObject.filters = queryObject.filters || {};
23
+ let session;
24
+ let releaseSession = false;
25
+ if (connection instanceof Pool) {
26
+ session = await connection.connect();
27
+ releaseSession = true;
28
+ }
29
+ else {
30
+ session = connection;
31
+ }
22
32
  try {
23
- await client.query('BEGIN');
33
+ await session.query('BEGIN');
24
34
  for (const entity of entities) {
25
35
  const { _id, ...updateData } = entity;
26
36
  if (Object.keys(updateData).length === 0) {
@@ -35,31 +45,36 @@ export async function batchUpdate(client, entities, operations, queryObject, plu
35
45
  SET ${setClause}
36
46
  ${whereClause}
37
47
  `;
38
- await client.query(query, values);
48
+ await session.query(query, values);
39
49
  }
40
- await client.query('COMMIT');
50
+ await session.query('COMMIT');
41
51
  const joinClauses = buildJoinClauses(operations, pluralResourceName);
42
52
  const hasJoins = operations.some(op => op instanceof LeftJoin || op instanceof InnerJoin || op instanceof LeftJoinMany);
43
53
  const tablePrefix = hasJoins ? pluralResourceName : undefined;
44
54
  queryObject.filters._id = { in: entityIds };
45
55
  const { whereClause, values } = buildWhereClause(queryObject, [], tablePrefix);
46
56
  const selectClause = hasJoins
47
- ? await buildSelectClause(client, pluralResourceName, operations)
57
+ ? await buildSelectClause(session, pluralResourceName, operations)
48
58
  : '*';
49
59
  const selectQuery = `
50
60
  SELECT ${selectClause} FROM "${pluralResourceName}" ${joinClauses}
51
61
  ${whereClause}
52
62
  `;
53
- const result = await client.query(selectQuery, values);
63
+ const result = await session.query(selectQuery, values);
54
64
  return hasJoins
55
65
  ? transformJoinResults(result.rows, operations)
56
66
  : result.rows;
57
67
  }
58
68
  catch (err) {
59
- await client.query('ROLLBACK');
69
+ await session.query('ROLLBACK');
60
70
  if (err.code === '23505') {
61
71
  throw new BadRequestError(`One or more ${pluralResourceName} have duplicate key violations`);
62
72
  }
63
73
  throw new BadRequestError(`Error updating ${pluralResourceName}: ${err.message}`);
64
74
  }
75
+ finally {
76
+ if (releaseSession) {
77
+ session.release();
78
+ }
79
+ }
65
80
  }
@@ -1,7 +1,7 @@
1
- import { Client } from 'pg';
1
+ import type { PostgresConnection } from '../postgres-connection.js';
2
2
  import { IEntity } from '@loomcore/common/models';
3
3
  import type { AppIdType } from '@loomcore/common/types';
4
- export declare function createMany<T extends IEntity>(client: Client, pluralResourceName: string, entities: Partial<T>[]): Promise<{
4
+ export declare function createMany<T extends IEntity>(client: PostgresConnection, pluralResourceName: string, entities: Partial<T>[]): Promise<{
5
5
  insertedIds: AppIdType[];
6
6
  entities: T[];
7
7
  }>;
@@ -1,7 +1,7 @@
1
- import { Client } from 'pg';
1
+ import type { PostgresConnection } from '../postgres-connection.js';
2
2
  import { IEntity } from "@loomcore/common/models";
3
3
  import type { AppIdType } from "@loomcore/common/types";
4
- export declare function create<T extends IEntity>(client: Client, pluralResourceName: string, entity: Partial<T>): Promise<{
4
+ export declare function create<T extends IEntity>(client: PostgresConnection, pluralResourceName: string, entity: Partial<T>): Promise<{
5
5
  insertedId: AppIdType;
6
6
  entity: T;
7
7
  }>;
@@ -1,4 +1,4 @@
1
- import { Client } from 'pg';
1
+ import type { PostgresConnection } from '../postgres-connection.js';
2
2
  import { DeleteResult } from "../../models/delete-result.js";
3
3
  import type { AppIdType } from '@loomcore/common/types';
4
- export declare function deleteById(client: Client, id: AppIdType, pluralResourceName: string): Promise<DeleteResult>;
4
+ export declare function deleteById(client: PostgresConnection, id: AppIdType, pluralResourceName: string): Promise<DeleteResult>;
@@ -1,4 +1,4 @@
1
- import { Client } from 'pg';
1
+ import type { PostgresConnection } from '../postgres-connection.js';
2
2
  import { IQueryOptions } from "@loomcore/common/models";
3
3
  import { DeleteResult } from "../../models/delete-result.js";
4
- export declare function deleteMany(client: Client, queryObject: IQueryOptions, pluralResourceName: string): Promise<DeleteResult>;
4
+ export declare function deleteMany(client: PostgresConnection, queryObject: IQueryOptions, pluralResourceName: string): Promise<DeleteResult>;
@@ -1,5 +1,5 @@
1
- import { Client } from 'pg';
1
+ import type { PostgresConnection } from '../postgres-connection.js';
2
2
  import { Operation } from "../../operations/operation.js";
3
3
  import { IEntity } from '@loomcore/common/models';
4
4
  import type { AppIdType } from '@loomcore/common/types';
5
- export declare function fullUpdateById<T extends IEntity>(client: Client, operations: Operation[], id: AppIdType, entity: Partial<T>, pluralResourceName: string): Promise<T>;
5
+ export declare function fullUpdateById<T extends IEntity>(client: PostgresConnection, operations: Operation[], id: AppIdType, entity: Partial<T>, pluralResourceName: string): Promise<T>;
@@ -1,5 +1,5 @@
1
- import { Client } from 'pg';
1
+ import type { PostgresConnection } from '../postgres-connection.js';
2
2
  import { Operation } from "../../operations/operation.js";
3
3
  import { IEntity } from '@loomcore/common/models';
4
4
  import type { AppIdType } from '@loomcore/common/types';
5
- export declare function partialUpdateById<T extends IEntity>(client: Client, operations: Operation[], id: AppIdType, entity: Partial<T>, pluralResourceName: string): Promise<T>;
5
+ export declare function partialUpdateById<T extends IEntity>(client: PostgresConnection, operations: Operation[], id: AppIdType, entity: Partial<T>, pluralResourceName: string): Promise<T>;
@@ -1,5 +1,5 @@
1
- import { Client } from 'pg';
1
+ import type { PostgresConnection } from '../postgres-connection.js';
2
2
  import { IQueryOptions } from "@loomcore/common/models";
3
3
  import { Operation } from "../../operations/operation.js";
4
4
  import { IEntity } from '@loomcore/common/models';
5
- export declare function update<T extends IEntity>(client: Client, queryObject: IQueryOptions, entity: Partial<T>, operations: Operation[], pluralResourceName: string): Promise<T[]>;
5
+ export declare function update<T extends IEntity>(client: PostgresConnection, queryObject: IQueryOptions, entity: Partial<T>, operations: Operation[], pluralResourceName: string): Promise<T[]>;
@@ -1,3 +1,4 @@
1
+ export * from './postgres-connection.js';
1
2
  export * from './postgres.database.js';
2
3
  export * from './migrations/index.js';
3
4
  export * from './utils/index.js';
@@ -1,3 +1,4 @@
1
+ export * from './postgres-connection.js';
1
2
  export * from './postgres.database.js';
2
3
  export * from './migrations/index.js';
3
4
  export * from './utils/index.js';
@@ -9,9 +9,25 @@ export const getPostgresInitialSchema = (dbConfig) => {
9
9
  throw new Error('Multi-tenant configuration is enabled but multi-tenant configuration is not provided');
10
10
  }
11
11
  const isAuthEnabled = dbConfig.app.isAuthEnabled;
12
+ const dbName = dbConfig.database.name.replace(/"/g, '""');
13
+ migrations.push({
14
+ name: '00000000000001_system-configurations',
15
+ up: async ({ context: pool }) => {
16
+ if (dbConfig.env === 'test') {
17
+ return;
18
+ }
19
+ await pool.query(`ALTER DATABASE "${dbName}" SET statement_timeout = '60s'`);
20
+ },
21
+ down: async ({ context: pool }) => {
22
+ if (dbConfig.env === 'test') {
23
+ return;
24
+ }
25
+ await pool.query(`ALTER DATABASE "${dbName}" RESET statement_timeout`);
26
+ }
27
+ });
12
28
  if (isMultiTenant) {
13
29
  migrations.push({
14
- name: '00000000000001_schema-organizations',
30
+ name: '00000000000002_schema-organizations',
15
31
  up: async ({ context: pool }) => {
16
32
  await pool.query(`
17
33
  CREATE TABLE IF NOT EXISTS "organizations" (
@@ -38,7 +54,7 @@ export const getPostgresInitialSchema = (dbConfig) => {
38
54
  }
39
55
  if (isAuthEnabled)
40
56
  migrations.push({
41
- name: '00000000000002_schema-persons',
57
+ name: '00000000000003_schema-persons',
42
58
  up: async ({ context: pool }) => {
43
59
  const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
44
60
  const personsUniqueConstraints = isMultiTenant
@@ -76,7 +92,7 @@ export const getPostgresInitialSchema = (dbConfig) => {
76
92
  });
77
93
  if (isAuthEnabled)
78
94
  migrations.push({
79
- name: '00000000000003_schema-users',
95
+ name: '00000000000004_schema-users',
80
96
  up: async ({ context: pool }) => {
81
97
  const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
82
98
  let uniqueConstraint = isMultiTenant
@@ -111,7 +127,7 @@ export const getPostgresInitialSchema = (dbConfig) => {
111
127
  });
112
128
  if (isAuthEnabled)
113
129
  migrations.push({
114
- name: '00000000000004_schema-refresh-tokens',
130
+ name: '00000000000005_schema-refresh-tokens',
115
131
  up: async ({ context: pool }) => {
116
132
  const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
117
133
  await pool.query(`
@@ -134,7 +150,7 @@ export const getPostgresInitialSchema = (dbConfig) => {
134
150
  });
135
151
  if (isAuthEnabled)
136
152
  migrations.push({
137
- name: '00000000000005_schema-password-reset-tokens',
153
+ name: '00000000000006_schema-password-reset-tokens',
138
154
  up: async ({ context: pool }) => {
139
155
  const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
140
156
  const uniqueConstraint = isMultiTenant
@@ -163,7 +179,7 @@ export const getPostgresInitialSchema = (dbConfig) => {
163
179
  });
164
180
  if (isAuthEnabled)
165
181
  migrations.push({
166
- name: '00000000000006_schema-roles',
182
+ name: '00000000000007_schema-roles',
167
183
  up: async ({ context: pool }) => {
168
184
  const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
169
185
  const uniqueConstraint = isMultiTenant
@@ -185,7 +201,7 @@ export const getPostgresInitialSchema = (dbConfig) => {
185
201
  });
186
202
  if (isAuthEnabled)
187
203
  migrations.push({
188
- name: '00000000000007_schema-user-roles',
204
+ name: '00000000000008_schema-user-roles',
189
205
  up: async ({ context: pool }) => {
190
206
  const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
191
207
  const uniqueConstraint = isMultiTenant
@@ -215,7 +231,7 @@ export const getPostgresInitialSchema = (dbConfig) => {
215
231
  });
216
232
  if (isAuthEnabled)
217
233
  migrations.push({
218
- name: '00000000000008_schema-features',
234
+ name: '00000000000009_schema-features',
219
235
  up: async ({ context: pool }) => {
220
236
  const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
221
237
  const uniqueConstraint = isMultiTenant
@@ -237,7 +253,7 @@ export const getPostgresInitialSchema = (dbConfig) => {
237
253
  });
238
254
  if (isAuthEnabled)
239
255
  migrations.push({
240
- name: '00000000000009_schema-authorizations',
256
+ name: '00000000000010_schema-authorizations',
241
257
  up: async ({ context: pool }) => {
242
258
  const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
243
259
  const uniqueConstraint = isMultiTenant
@@ -270,7 +286,7 @@ export const getPostgresInitialSchema = (dbConfig) => {
270
286
  });
271
287
  if (isMultiTenant) {
272
288
  migrations.push({
273
- name: '00000000000010_data-meta-org',
289
+ name: '00000000000011_data-meta-org',
274
290
  up: async ({ context: pool }) => {
275
291
  const result = await pool.query(`
276
292
  INSERT INTO "organizations" ("name", "code", "status", "is_meta_org", "_created", "_createdBy", "_updated", "_updatedBy")
@@ -289,11 +305,11 @@ export const getPostgresInitialSchema = (dbConfig) => {
289
305
  }
290
306
  if (isAuthEnabled && dbConfig.adminUser) {
291
307
  migrations.push({
292
- name: '00000000000011_data-admin-user',
308
+ name: '00000000000012_data-admin-user',
293
309
  up: async ({ context: pool }) => {
294
310
  if (!isSystemUserContextInitialized()) {
295
311
  const errorMessage = isMultiTenant
296
- ? 'SystemUserContext has not been initialized. The meta-org migration (00000000000010_data-meta-org) should have run before this migration. ' +
312
+ ? 'SystemUserContext has not been initialized. The meta-org migration (00000000000011_data-meta-org) should have run before this migration. ' +
297
313
  'Please ensure metaOrgName and metaOrgCode are provided in your dbConfig.'
298
314
  : 'BUG: SystemUserContext has not been initialized. For non-multi-tenant setups, SystemUserContext should be initialized before migrations run.';
299
315
  console.error('❌ Migration Error:', errorMessage);
@@ -335,7 +351,7 @@ export const getPostgresInitialSchema = (dbConfig) => {
335
351
  }
336
352
  if (isAuthEnabled && dbConfig.adminUser) {
337
353
  migrations.push({
338
- name: '00000000000012_data-admin-authorizations',
354
+ name: '00000000000013_data-admin-authorizations',
339
355
  up: async ({ context: pool }) => {
340
356
  const client = await pool.connect();
341
357
  try {
@@ -0,0 +1,2 @@
1
+ import type { Client, Pool, PoolClient } from 'pg';
2
+ export type PostgresConnection = Client | Pool | PoolClient;
@@ -0,0 +1 @@
1
+ export {};
@@ -3,10 +3,10 @@ import type { AppIdType } from "@loomcore/common/types";
3
3
  import { TSchema } from "@sinclair/typebox";
4
4
  import { DeleteResult, IDatabase } from "../models/index.js";
5
5
  import { Operation } from "../operations/operation.js";
6
- import { Client } from 'pg';
6
+ import type { PostgresConnection } from './postgres-connection.js';
7
7
  export declare class PostgresDatabase implements IDatabase {
8
- private client;
9
- constructor(client: Client);
8
+ private connection;
9
+ constructor(connection: PostgresConnection);
10
10
  preProcessEntity<T extends IEntity>(entity: Partial<T>, modelSpec: TSchema): Partial<T>;
11
11
  postProcessEntity<T extends IEntity>(entity: T, modelSpec: TSchema): T;
12
12
  getAll<T extends IEntity>(operations: Operation[], pluralResourceName: string): Promise<T[]>;
@@ -15,9 +15,9 @@ import { getCount as getCountQuery } from "./queries/postgres-get-count.query.js
15
15
  import { convertNullToUndefined } from "./utils/convert-null-to-undefined.util.js";
16
16
  import { convertKeysToSnakeCase, convertKeysToCamelCase } from "./utils/convert-keys.util.js";
17
17
  export class PostgresDatabase {
18
- client;
19
- constructor(client) {
20
- this.client = client;
18
+ connection;
19
+ constructor(connection) {
20
+ this.connection = connection;
21
21
  }
22
22
  preProcessEntity(entity, modelSpec) {
23
23
  return convertKeysToSnakeCase(entity);
@@ -27,46 +27,46 @@ export class PostgresDatabase {
27
27
  return convertKeysToCamelCase(withNullsConverted);
28
28
  }
29
29
  async getAll(operations, pluralResourceName) {
30
- return getAllQuery(this.client, operations, pluralResourceName);
30
+ return getAllQuery(this.connection, operations, pluralResourceName);
31
31
  }
32
32
  async get(operations, queryOptions, modelSpec, pluralResourceName) {
33
- return getQuery(this.client, operations, queryOptions, pluralResourceName);
33
+ return getQuery(this.connection, operations, queryOptions, pluralResourceName);
34
34
  }
35
35
  async getById(operations, queryObject, id, pluralResourceName) {
36
- return getByIdQuery(this.client, operations, queryObject, id, pluralResourceName);
36
+ return getByIdQuery(this.connection, operations, queryObject, id, pluralResourceName);
37
37
  }
38
38
  async getCount(pluralResourceName) {
39
- return getCountQuery(this.client, pluralResourceName);
39
+ return getCountQuery(this.connection, pluralResourceName);
40
40
  }
41
41
  async create(entity, pluralResourceName) {
42
- return createCommand(this.client, pluralResourceName, entity);
42
+ return createCommand(this.connection, pluralResourceName, entity);
43
43
  }
44
44
  async createMany(entities, pluralResourceName) {
45
- return createManyCommand(this.client, pluralResourceName, entities);
45
+ return createManyCommand(this.connection, pluralResourceName, entities);
46
46
  }
47
47
  async batchUpdate(entities, operations, queryObject, pluralResourceName) {
48
- return batchUpdateCommand(this.client, entities, operations, queryObject, pluralResourceName);
48
+ return batchUpdateCommand(this.connection, entities, operations, queryObject, pluralResourceName);
49
49
  }
50
50
  async fullUpdateById(operations, id, entity, pluralResourceName) {
51
- return fullUpdateByIdCommand(this.client, operations, id, entity, pluralResourceName);
51
+ return fullUpdateByIdCommand(this.connection, operations, id, entity, pluralResourceName);
52
52
  }
53
53
  async partialUpdateById(operations, id, entity, pluralResourceName) {
54
- return partialUpdateByIdCommand(this.client, operations, id, entity, pluralResourceName);
54
+ return partialUpdateByIdCommand(this.connection, operations, id, entity, pluralResourceName);
55
55
  }
56
56
  async update(queryObject, entity, operations, pluralResourceName) {
57
- return updateCommand(this.client, queryObject, entity, operations, pluralResourceName);
57
+ return updateCommand(this.connection, queryObject, entity, operations, pluralResourceName);
58
58
  }
59
59
  async deleteById(id, pluralResourceName) {
60
- return deleteByIdCommand(this.client, id, pluralResourceName);
60
+ return deleteByIdCommand(this.connection, id, pluralResourceName);
61
61
  }
62
62
  async deleteMany(queryObject, pluralResourceName) {
63
- return deleteManyCommand(this.client, queryObject, pluralResourceName);
63
+ return deleteManyCommand(this.connection, queryObject, pluralResourceName);
64
64
  }
65
65
  async find(queryObject, pluralResourceName) {
66
- return findQuery(this.client, queryObject, pluralResourceName);
66
+ return findQuery(this.connection, queryObject, pluralResourceName);
67
67
  }
68
68
  async findOne(queryObject, pluralResourceName) {
69
- return findOneQuery(this.client, queryObject, pluralResourceName);
69
+ return findOneQuery(this.connection, queryObject, pluralResourceName);
70
70
  }
71
71
  async getUserAuthorizations(userId, orgId) {
72
72
  const now = new Date();
@@ -93,7 +93,7 @@ export class PostgresDatabase {
93
93
  query += ` AND ur."_orgId" = $3 AND r."_orgId" = $3 AND a."_orgId" = $3 AND f."_orgId" = $3`;
94
94
  values.push(orgId);
95
95
  }
96
- const result = await this.client.query(query, values);
96
+ const result = await this.connection.query(query, values);
97
97
  const authorizations = [];
98
98
  for (const row of result.rows) {
99
99
  const userId = row.userId;
@@ -1,3 +1,3 @@
1
- import { Client } from 'pg';
1
+ import type { PostgresConnection } from '../postgres-connection.js';
2
2
  import { IQueryOptions } from "@loomcore/common/models";
3
- export declare function findOne<T>(client: Client, queryObject: IQueryOptions, pluralResourceName: string): Promise<T | null>;
3
+ export declare function findOne<T>(client: PostgresConnection, queryObject: IQueryOptions, pluralResourceName: string): Promise<T | null>;
@@ -1,3 +1,3 @@
1
- import { Client } from 'pg';
1
+ import type { PostgresConnection } from '../postgres-connection.js';
2
2
  import { IQueryOptions } from "@loomcore/common/models";
3
- export declare function find<T>(client: Client, queryObject: IQueryOptions, pluralResourceName: string): Promise<T[]>;
3
+ export declare function find<T>(client: PostgresConnection, queryObject: IQueryOptions, pluralResourceName: string): Promise<T[]>;
@@ -1,3 +1,3 @@
1
- import { Client } from 'pg';
1
+ import type { PostgresConnection } from '../postgres-connection.js';
2
2
  import { Operation } from "../../operations/operation.js";
3
- export declare function getAll<T>(client: Client, operations: Operation[], pluralResourceName: string): Promise<T[]>;
3
+ export declare function getAll<T>(client: PostgresConnection, operations: Operation[], pluralResourceName: string): Promise<T[]>;
@@ -1,5 +1,5 @@
1
- import { Client } from 'pg';
1
+ import type { PostgresConnection } from '../postgres-connection.js';
2
2
  import { Operation } from "../../operations/operation.js";
3
3
  import { IQueryOptions } from '@loomcore/common/models';
4
4
  import type { AppIdType } from '@loomcore/common/types';
5
- export declare function getById<T>(client: Client, operations: Operation[], queryObject: IQueryOptions, id: AppIdType, pluralResourceName: string): Promise<T | null>;
5
+ export declare function getById<T>(client: PostgresConnection, operations: Operation[], queryObject: IQueryOptions, id: AppIdType, pluralResourceName: string): Promise<T | null>;
@@ -1,2 +1,2 @@
1
- import { Client } from 'pg';
2
- export declare function getCount(client: Client, pluralResourceName: string): Promise<number>;
1
+ import type { PostgresConnection } from '../postgres-connection.js';
2
+ export declare function getCount(client: PostgresConnection, pluralResourceName: string): Promise<number>;
@@ -1,4 +1,4 @@
1
- import { Client } from 'pg';
1
+ import type { PostgresConnection } from '../postgres-connection.js';
2
2
  import { IQueryOptions, IPagedResult } from "@loomcore/common/models";
3
3
  import { Operation } from "../../operations/operation.js";
4
- export declare function get<T>(client: Client, operations: Operation[], queryOptions: IQueryOptions, pluralResourceName: string): Promise<IPagedResult<T>>;
4
+ export declare function get<T>(client: PostgresConnection, operations: Operation[], queryOptions: IQueryOptions, pluralResourceName: string): Promise<IPagedResult<T>>;
@@ -1,3 +1,3 @@
1
- import { Client } from 'pg';
1
+ import type { PostgresConnection } from '../postgres-connection.js';
2
2
  import { IQueryOptions } from "@loomcore/common/models";
3
- export declare function executeCountQuery(client: Client, pluralResourceName: string, queryOptions?: IQueryOptions): Promise<number>;
3
+ export declare function executeCountQuery(client: PostgresConnection, pluralResourceName: string, queryOptions?: IQueryOptions): Promise<number>;
@@ -1,4 +1,4 @@
1
- import { Client } from 'pg';
1
+ import type { PostgresConnection } from '../postgres-connection.js';
2
2
  import { Operation } from '../../operations/operation.js';
3
- export declare function getTableColumns(client: Client, tableName: string): Promise<string[]>;
4
- export declare function buildSelectClause(client: Client, mainTableName: string, operations: Operation[]): Promise<string>;
3
+ export declare function getTableColumns(client: PostgresConnection, tableName: string): Promise<string[]>;
4
+ export declare function buildSelectClause(client: PostgresConnection, mainTableName: string, operations: Operation[]): Promise<string>;
@@ -1,2 +1,2 @@
1
- import { Client } from "pg";
2
- export declare function doesTableExist(client: Client, tableName: string): Promise<boolean>;
1
+ import type { PostgresConnection } from "../postgres-connection.js";
2
+ export declare function doesTableExist(client: PostgresConnection, tableName: string): Promise<boolean>;
@@ -1,11 +1,12 @@
1
1
  import { Application } from 'express';
2
2
  import { MongoClient } from "mongodb";
3
+ import type { Client, Pool } from "pg";
3
4
  import { Server } from "http";
4
5
  import { IBaseApiConfig } from "../models/index.js";
5
6
  import { IDatabase } from '../databases/models/index.js';
6
7
  type RouteSetupFunction = (app: Application, database: IDatabase, config: IBaseApiConfig) => void;
7
8
  declare function setupExpressApp(database: IDatabase, config: IBaseApiConfig, setupRoutes: RouteSetupFunction): Application;
8
- declare function performGracefulShutdown(event: any, mongoClient: MongoClient | null, externalServer: Server | null, internalServer: Server | null): void;
9
+ declare function performGracefulShutdown(event: any, mongoClient: MongoClient | null, externalServer: Server | null, internalServer: Server | null, postgres?: Pool | Client | null): void;
9
10
  export declare const expressUtils: {
10
11
  setupExpressApp: typeof setupExpressApp;
11
12
  performGracefulShutdown: typeof performGracefulShutdown;
@@ -31,7 +31,7 @@ function setupExpressApp(database, config, setupRoutes) {
31
31
  app.use(errorHandler);
32
32
  return app;
33
33
  }
34
- function performGracefulShutdown(event, mongoClient, externalServer, internalServer) {
34
+ function performGracefulShutdown(event, mongoClient, externalServer, internalServer, postgres = null) {
35
35
  const closeMongoConnection = async () => {
36
36
  if (mongoClient) {
37
37
  console.log('closing mongodb connection');
@@ -44,6 +44,19 @@ function performGracefulShutdown(event, mongoClient, externalServer, internalSer
44
44
  }
45
45
  }
46
46
  };
47
+ const closePostgres = async () => {
48
+ if (!postgres) {
49
+ return;
50
+ }
51
+ console.log('closing PostgreSQL pool');
52
+ try {
53
+ await postgres.end();
54
+ console.log('PostgreSQL pool closed successfully');
55
+ }
56
+ catch (err) {
57
+ console.error('Error closing PostgreSQL pool:', err);
58
+ }
59
+ };
47
60
  const shutdownServers = new Promise((resolve) => {
48
61
  let serversClosedCount = 0;
49
62
  const totalServers = (externalServer ? 1 : 0) + (internalServer ? 1 : 0);
@@ -76,16 +89,19 @@ function performGracefulShutdown(event, mongoClient, externalServer, internalSer
76
89
  });
77
90
  }
78
91
  setTimeout(() => {
79
- console.log('Server shutdown timeout reached, proceeding with MongoDB cleanup');
92
+ console.log('Server shutdown timeout reached, proceeding with database cleanup');
80
93
  resolve();
81
94
  }, 5000);
82
95
  });
96
+ const closeAllDatabases = async () => {
97
+ await Promise.all([closeMongoConnection(), closePostgres()]);
98
+ };
83
99
  Promise.race([
84
- shutdownServers.then(() => closeMongoConnection()),
100
+ shutdownServers.then(() => closeAllDatabases()),
85
101
  new Promise(resolve => {
86
102
  setTimeout(async () => {
87
- console.log('Ensuring MongoDB connection is closed before exit');
88
- await closeMongoConnection();
103
+ console.log('Ensuring database connections are closed before exit');
104
+ await closeAllDatabases();
89
105
  resolve();
90
106
  }, 6000);
91
107
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomcore/api",
3
- "version": "0.1.135",
3
+ "version": "0.1.136",
4
4
  "private": false,
5
5
  "description": "Loom Core Api - An opinionated Node.js api using Typescript, Express, and MongoDb or PostgreSQL",
6
6
  "scripts": {