@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.
- package/dist/__tests__/postgres.test-database.d.ts +1 -1
- package/dist/__tests__/postgres.test-database.js +28 -30
- package/dist/databases/models/database.d.ts +2 -2
- package/dist/databases/postgres/commands/postgres-batch-update.command.d.ts +2 -2
- package/dist/databases/postgres/commands/postgres-batch-update.command.js +22 -7
- package/dist/databases/postgres/commands/postgres-create-many.command.d.ts +2 -2
- package/dist/databases/postgres/commands/postgres-create.command.d.ts +2 -2
- package/dist/databases/postgres/commands/postgres-delete-by-id.command.d.ts +2 -2
- package/dist/databases/postgres/commands/postgres-delete-many.command.d.ts +2 -2
- package/dist/databases/postgres/commands/postgres-full-update-by-id.command.d.ts +2 -2
- package/dist/databases/postgres/commands/postgres-partial-update-by-id.command.d.ts +2 -2
- package/dist/databases/postgres/commands/postgres-update.command.d.ts +2 -2
- package/dist/databases/postgres/index.d.ts +1 -0
- package/dist/databases/postgres/index.js +1 -0
- package/dist/databases/postgres/migrations/postgres-initial-schema.js +29 -13
- package/dist/databases/postgres/postgres-connection.d.ts +2 -0
- package/dist/databases/postgres/postgres-connection.js +1 -0
- package/dist/databases/postgres/postgres.database.d.ts +3 -3
- package/dist/databases/postgres/postgres.database.js +18 -18
- package/dist/databases/postgres/queries/postgres-find-one.query.d.ts +2 -2
- package/dist/databases/postgres/queries/postgres-find.query.d.ts +2 -2
- package/dist/databases/postgres/queries/postgres-get-all.query.d.ts +2 -2
- package/dist/databases/postgres/queries/postgres-get-by-id.query.d.ts +2 -2
- package/dist/databases/postgres/queries/postgres-get-count.query.d.ts +2 -2
- package/dist/databases/postgres/queries/postgres-get.query.d.ts +2 -2
- package/dist/databases/postgres/utils/build-count-query.d.ts +2 -2
- package/dist/databases/postgres/utils/build-select-clause.d.ts +3 -3
- package/dist/databases/postgres/utils/does-table-exist.util.d.ts +2 -2
- package/dist/utils/express.utils.d.ts +2 -1
- package/dist/utils/express.utils.js +21 -5
- 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
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
29
|
+
pool = new Pool({
|
|
30
30
|
connectionString,
|
|
31
31
|
connectionTimeoutMillis: 5000,
|
|
32
32
|
});
|
|
33
|
+
this.migrationPool = pool;
|
|
33
34
|
try {
|
|
34
|
-
await
|
|
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
|
-
|
|
53
|
-
connectionString,
|
|
54
|
-
connectionTimeoutMillis: 5000,
|
|
55
|
-
});
|
|
55
|
+
connection = pool;
|
|
56
56
|
}
|
|
57
57
|
else {
|
|
58
58
|
const { Client } = newDb().adapters.createPg();
|
|
59
|
-
|
|
60
|
-
await
|
|
61
|
-
pool =
|
|
59
|
+
const pgMemClient = new Client();
|
|
60
|
+
await pgMemClient.connect();
|
|
61
|
+
pool = pgMemClient;
|
|
62
|
+
connection = pgMemClient;
|
|
63
|
+
this.migrationPool = pool;
|
|
62
64
|
}
|
|
63
|
-
this.
|
|
64
|
-
this.
|
|
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(
|
|
74
|
+
await this.createIndexes(connection);
|
|
74
75
|
await testUtils.createMetaOrg();
|
|
75
76
|
}
|
|
76
77
|
return this.database;
|
|
77
78
|
}
|
|
78
|
-
async createIndexes(
|
|
79
|
+
async createIndexes(connection) {
|
|
79
80
|
try {
|
|
80
|
-
await
|
|
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
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
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 {
|
|
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>(
|
|
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(
|
|
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
|
|
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
|
|
48
|
+
await session.query(query, values);
|
|
39
49
|
}
|
|
40
|
-
await
|
|
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(
|
|
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
|
|
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
|
|
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 {
|
|
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:
|
|
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 {
|
|
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:
|
|
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 {
|
|
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:
|
|
4
|
+
export declare function deleteById(client: PostgresConnection, id: AppIdType, pluralResourceName: string): Promise<DeleteResult>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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:
|
|
4
|
+
export declare function deleteMany(client: PostgresConnection, queryObject: IQueryOptions, pluralResourceName: string): Promise<DeleteResult>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
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:
|
|
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 {
|
|
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:
|
|
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 {
|
|
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:
|
|
5
|
+
export declare function update<T extends IEntity>(client: PostgresConnection, queryObject: IQueryOptions, entity: Partial<T>, operations: Operation[], pluralResourceName: string): Promise<T[]>;
|
|
@@ -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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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 (
|
|
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: '
|
|
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 @@
|
|
|
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 {
|
|
6
|
+
import type { PostgresConnection } from './postgres-connection.js';
|
|
7
7
|
export declare class PostgresDatabase implements IDatabase {
|
|
8
|
-
private
|
|
9
|
-
constructor(
|
|
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
|
-
|
|
19
|
-
constructor(
|
|
20
|
-
this.
|
|
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.
|
|
30
|
+
return getAllQuery(this.connection, operations, pluralResourceName);
|
|
31
31
|
}
|
|
32
32
|
async get(operations, queryOptions, modelSpec, pluralResourceName) {
|
|
33
|
-
return getQuery(this.
|
|
33
|
+
return getQuery(this.connection, operations, queryOptions, pluralResourceName);
|
|
34
34
|
}
|
|
35
35
|
async getById(operations, queryObject, id, pluralResourceName) {
|
|
36
|
-
return getByIdQuery(this.
|
|
36
|
+
return getByIdQuery(this.connection, operations, queryObject, id, pluralResourceName);
|
|
37
37
|
}
|
|
38
38
|
async getCount(pluralResourceName) {
|
|
39
|
-
return getCountQuery(this.
|
|
39
|
+
return getCountQuery(this.connection, pluralResourceName);
|
|
40
40
|
}
|
|
41
41
|
async create(entity, pluralResourceName) {
|
|
42
|
-
return createCommand(this.
|
|
42
|
+
return createCommand(this.connection, pluralResourceName, entity);
|
|
43
43
|
}
|
|
44
44
|
async createMany(entities, pluralResourceName) {
|
|
45
|
-
return createManyCommand(this.
|
|
45
|
+
return createManyCommand(this.connection, pluralResourceName, entities);
|
|
46
46
|
}
|
|
47
47
|
async batchUpdate(entities, operations, queryObject, pluralResourceName) {
|
|
48
|
-
return batchUpdateCommand(this.
|
|
48
|
+
return batchUpdateCommand(this.connection, entities, operations, queryObject, pluralResourceName);
|
|
49
49
|
}
|
|
50
50
|
async fullUpdateById(operations, id, entity, pluralResourceName) {
|
|
51
|
-
return fullUpdateByIdCommand(this.
|
|
51
|
+
return fullUpdateByIdCommand(this.connection, operations, id, entity, pluralResourceName);
|
|
52
52
|
}
|
|
53
53
|
async partialUpdateById(operations, id, entity, pluralResourceName) {
|
|
54
|
-
return partialUpdateByIdCommand(this.
|
|
54
|
+
return partialUpdateByIdCommand(this.connection, operations, id, entity, pluralResourceName);
|
|
55
55
|
}
|
|
56
56
|
async update(queryObject, entity, operations, pluralResourceName) {
|
|
57
|
-
return updateCommand(this.
|
|
57
|
+
return updateCommand(this.connection, queryObject, entity, operations, pluralResourceName);
|
|
58
58
|
}
|
|
59
59
|
async deleteById(id, pluralResourceName) {
|
|
60
|
-
return deleteByIdCommand(this.
|
|
60
|
+
return deleteByIdCommand(this.connection, id, pluralResourceName);
|
|
61
61
|
}
|
|
62
62
|
async deleteMany(queryObject, pluralResourceName) {
|
|
63
|
-
return deleteManyCommand(this.
|
|
63
|
+
return deleteManyCommand(this.connection, queryObject, pluralResourceName);
|
|
64
64
|
}
|
|
65
65
|
async find(queryObject, pluralResourceName) {
|
|
66
|
-
return findQuery(this.
|
|
66
|
+
return findQuery(this.connection, queryObject, pluralResourceName);
|
|
67
67
|
}
|
|
68
68
|
async findOne(queryObject, pluralResourceName) {
|
|
69
|
-
return findOneQuery(this.
|
|
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.
|
|
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 {
|
|
1
|
+
import type { PostgresConnection } from '../postgres-connection.js';
|
|
2
2
|
import { IQueryOptions } from "@loomcore/common/models";
|
|
3
|
-
export declare function findOne<T>(client:
|
|
3
|
+
export declare function findOne<T>(client: PostgresConnection, queryObject: IQueryOptions, pluralResourceName: string): Promise<T | null>;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { PostgresConnection } from '../postgres-connection.js';
|
|
2
2
|
import { IQueryOptions } from "@loomcore/common/models";
|
|
3
|
-
export declare function find<T>(client:
|
|
3
|
+
export declare function find<T>(client: PostgresConnection, queryObject: IQueryOptions, pluralResourceName: string): Promise<T[]>;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { PostgresConnection } from '../postgres-connection.js';
|
|
2
2
|
import { Operation } from "../../operations/operation.js";
|
|
3
|
-
export declare function getAll<T>(client:
|
|
3
|
+
export declare function getAll<T>(client: PostgresConnection, operations: Operation[], pluralResourceName: string): Promise<T[]>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
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:
|
|
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 {
|
|
2
|
-
export declare function getCount(client:
|
|
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 {
|
|
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:
|
|
4
|
+
export declare function get<T>(client: PostgresConnection, operations: Operation[], queryOptions: IQueryOptions, pluralResourceName: string): Promise<IPagedResult<T>>;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { PostgresConnection } from '../postgres-connection.js';
|
|
2
2
|
import { IQueryOptions } from "@loomcore/common/models";
|
|
3
|
-
export declare function executeCountQuery(client:
|
|
3
|
+
export declare function executeCountQuery(client: PostgresConnection, pluralResourceName: string, queryOptions?: IQueryOptions): Promise<number>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { PostgresConnection } from '../postgres-connection.js';
|
|
2
2
|
import { Operation } from '../../operations/operation.js';
|
|
3
|
-
export declare function getTableColumns(client:
|
|
4
|
-
export declare function buildSelectClause(client:
|
|
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 {
|
|
2
|
-
export declare function doesTableExist(client:
|
|
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
|
|
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(() =>
|
|
100
|
+
shutdownServers.then(() => closeAllDatabases()),
|
|
85
101
|
new Promise(resolve => {
|
|
86
102
|
setTimeout(async () => {
|
|
87
|
-
console.log('Ensuring
|
|
88
|
-
await
|
|
103
|
+
console.log('Ensuring database connections are closed before exit');
|
|
104
|
+
await closeAllDatabases();
|
|
89
105
|
resolve();
|
|
90
106
|
}, 6000);
|
|
91
107
|
})
|