clhq-postgres-module 1.1.0-alpha.154 → 1.1.0-alpha.155

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.
@@ -0,0 +1,30 @@
1
+ import { DataSource } from 'typeorm';
2
+ export interface DatabaseConfig {
3
+ maxConnections?: number;
4
+ acquireTimeoutMillis?: number;
5
+ idleTimeoutMillis?: number;
6
+ connectionTimeoutMillis?: number;
7
+ queryTimeout?: number;
8
+ statementTimeout?: number;
9
+ }
10
+ export interface ConnectionHealth {
11
+ isHealthy: boolean;
12
+ responseTime: number;
13
+ error?: string;
14
+ timestamp: Date;
15
+ }
16
+ export interface ConnectionMetrics {
17
+ totalConnections: number;
18
+ activeConnections: number;
19
+ idleConnections: number;
20
+ waitingClients: number;
21
+ lastHealthCheck: Date;
22
+ }
23
+ export declare function checkConnectionHealth(dataSource: DataSource, timeoutMs?: number): Promise<ConnectionHealth>;
24
+ export declare function getConnectionMetrics(dataSource: DataSource): Promise<ConnectionMetrics>;
25
+ export declare function recoverConnection(dataSource: DataSource, entities: any[], config?: DatabaseConfig, maxRetries?: number): Promise<boolean>;
26
+ export declare function createDataSource(entities: any[], config?: DatabaseConfig): Promise<DataSource>;
27
+ export declare function closeDataSource(dataSource: DataSource): Promise<void>;
28
+ export declare function withDatabase<T>(entities: any[], operation: (dataSource: DataSource) => Promise<T>, config?: DatabaseConfig): Promise<T>;
29
+ export declare function withDatabaseResilient<T>(entities: any[], operation: (dataSource: DataSource) => Promise<T>, config?: DatabaseConfig, maxRetries?: number): Promise<T>;
30
+ export declare function logConnectionStatus(dataSource: DataSource, label?: string): void;
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkConnectionHealth = checkConnectionHealth;
4
+ exports.getConnectionMetrics = getConnectionMetrics;
5
+ exports.recoverConnection = recoverConnection;
6
+ exports.createDataSource = createDataSource;
7
+ exports.closeDataSource = closeDataSource;
8
+ exports.withDatabase = withDatabase;
9
+ exports.withDatabaseResilient = withDatabaseResilient;
10
+ exports.logConnectionStatus = logConnectionStatus;
11
+ const typeorm_1 = require("typeorm");
12
+ const DEFAULT_CONFIG = {
13
+ maxConnections: 1,
14
+ acquireTimeoutMillis: 30000,
15
+ idleTimeoutMillis: 30000,
16
+ connectionTimeoutMillis: 10000,
17
+ queryTimeout: 30000,
18
+ statementTimeout: 30000,
19
+ };
20
+ async function checkConnectionHealth(dataSource, timeoutMs = 5000) {
21
+ const startTime = Date.now();
22
+ const timestamp = new Date();
23
+ try {
24
+ if (!dataSource || !dataSource.isInitialized) {
25
+ return {
26
+ isHealthy: false,
27
+ responseTime: Date.now() - startTime,
28
+ error: 'DataSource not initialized',
29
+ timestamp,
30
+ };
31
+ }
32
+ await Promise.race([
33
+ dataSource.query('SELECT 1 as health_check'),
34
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Health check timeout')), timeoutMs)),
35
+ ]);
36
+ return {
37
+ isHealthy: true,
38
+ responseTime: Date.now() - startTime,
39
+ timestamp,
40
+ };
41
+ }
42
+ catch (error) {
43
+ return {
44
+ isHealthy: false,
45
+ responseTime: Date.now() - startTime,
46
+ error: error instanceof Error ? error.message : String(error),
47
+ timestamp,
48
+ };
49
+ }
50
+ }
51
+ async function getConnectionMetrics(dataSource) {
52
+ const metrics = {
53
+ totalConnections: 0,
54
+ activeConnections: 0,
55
+ idleConnections: 0,
56
+ waitingClients: 0,
57
+ lastHealthCheck: new Date(),
58
+ };
59
+ if (!dataSource || !dataSource.isInitialized) {
60
+ return metrics;
61
+ }
62
+ metrics.totalConnections = dataSource.isInitialized ? 1 : 0;
63
+ metrics.activeConnections = dataSource.isInitialized ? 1 : 0;
64
+ return metrics;
65
+ }
66
+ async function recoverConnection(dataSource, entities, config = DEFAULT_CONFIG, maxRetries = 3) {
67
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
68
+ try {
69
+ console.log(`[DB] Recovery attempt ${attempt}/${maxRetries}`);
70
+ if (dataSource.isInitialized) {
71
+ await closeDataSource(dataSource);
72
+ }
73
+ const newDataSource = await createDataSource(entities, config);
74
+ const health = await checkConnectionHealth(newDataSource);
75
+ if (health.isHealthy) {
76
+ console.log(`[DB] Connection recovered successfully on attempt ${attempt}`);
77
+ Object.assign(dataSource, newDataSource);
78
+ return true;
79
+ }
80
+ else {
81
+ console.warn(`[DB] Recovery attempt ${attempt} failed health check:`, health.error);
82
+ await closeDataSource(newDataSource);
83
+ }
84
+ }
85
+ catch (error) {
86
+ console.error(`[DB] Recovery attempt ${attempt} failed:`, error);
87
+ }
88
+ }
89
+ console.error(`[DB] Connection recovery failed after ${maxRetries} attempts`);
90
+ return false;
91
+ }
92
+ async function createDataSource(entities, config = DEFAULT_CONFIG) {
93
+ const databaseUrl = process.env.PG_SESSION_POOLER_URL;
94
+ if (!databaseUrl) {
95
+ throw new Error('PG_SESSION_POOLER_URL environment variable is required');
96
+ }
97
+ const dataSource = new typeorm_1.DataSource({
98
+ type: 'postgres',
99
+ url: databaseUrl,
100
+ entities,
101
+ synchronize: false,
102
+ ssl: { rejectUnauthorized: false },
103
+ extra: {
104
+ max: config.maxConnections,
105
+ min: 0,
106
+ acquireTimeoutMillis: config.acquireTimeoutMillis,
107
+ idleTimeoutMillis: config.idleTimeoutMillis,
108
+ evictCheckIntervalMillis: 10000,
109
+ handleTimeoutMillis: 5000,
110
+ reapIntervalMillis: 1000,
111
+ createTimeoutMillis: config.acquireTimeoutMillis,
112
+ connectionTimeoutMillis: config.connectionTimeoutMillis,
113
+ query_timeout: config.queryTimeout,
114
+ statement_timeout: config.statementTimeout,
115
+ testOnBorrow: true,
116
+ testOnReturn: true,
117
+ testOnIdle: true,
118
+ },
119
+ });
120
+ const startTime = Date.now();
121
+ await dataSource.initialize();
122
+ const initTime = Date.now() - startTime;
123
+ console.log(`[DB] Connection initialized in ${initTime}ms with config:`, {
124
+ maxConnections: config.maxConnections,
125
+ url: databaseUrl.replace(/:\/\/.*@/, '://***:***@'),
126
+ });
127
+ return dataSource;
128
+ }
129
+ async function closeDataSource(dataSource) {
130
+ if (!dataSource || !dataSource.isInitialized) {
131
+ console.warn('[DB] Attempted to close uninitialized or null DataSource');
132
+ return;
133
+ }
134
+ try {
135
+ const startTime = Date.now();
136
+ await dataSource.destroy();
137
+ const closeTime = Date.now() - startTime;
138
+ console.log(`[DB] Connection closed in ${closeTime}ms`);
139
+ }
140
+ catch (error) {
141
+ console.error('[DB] Error closing connection:', error);
142
+ throw error;
143
+ }
144
+ }
145
+ async function withDatabase(entities, operation, config) {
146
+ const dataSource = await createDataSource(entities, config);
147
+ try {
148
+ const result = await operation(dataSource);
149
+ console.log('[DB] Operation completed successfully');
150
+ return result;
151
+ }
152
+ catch (error) {
153
+ console.error('[DB] Operation failed:', error);
154
+ throw error;
155
+ }
156
+ finally {
157
+ await closeDataSource(dataSource);
158
+ }
159
+ }
160
+ async function withDatabaseResilient(entities, operation, config, maxRetries = 2) {
161
+ let lastError;
162
+ for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
163
+ try {
164
+ return await withDatabase(entities, operation, config);
165
+ }
166
+ catch (error) {
167
+ lastError = error;
168
+ console.error(`[DB] Operation failed on attempt ${attempt}:`, error);
169
+ if (attempt <= maxRetries) {
170
+ console.log(`[DB] Retrying operation (attempt ${attempt + 1}/${maxRetries + 1})`);
171
+ await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
172
+ }
173
+ }
174
+ }
175
+ console.error(`[DB] Operation failed after ${maxRetries + 1} attempts`);
176
+ throw lastError;
177
+ }
178
+ function logConnectionStatus(dataSource, label = 'DB') {
179
+ if (!dataSource || !dataSource.isInitialized) {
180
+ console.log(`[${label}] DataSource not initialized`);
181
+ return;
182
+ }
183
+ console.log(`[${label}] Connection status: initialized=${dataSource.isInitialized}`);
184
+ }
package/dist/index.d.ts CHANGED
@@ -2,3 +2,4 @@ export * from './entities';
2
2
  export * from './repositories';
3
3
  export * from './repositories/repository.module';
4
4
  export * from './postgres.module';
5
+ export * from './database-utils';
package/dist/index.js CHANGED
@@ -18,3 +18,4 @@ __exportStar(require("./entities"), exports);
18
18
  __exportStar(require("./repositories"), exports);
19
19
  __exportStar(require("./repositories/repository.module"), exports);
20
20
  __exportStar(require("./postgres.module"), exports);
21
+ __exportStar(require("./database-utils"), exports);
@@ -52,6 +52,11 @@ const allEntities = [
52
52
  entities_1.Scene,
53
53
  entities_1.Item,
54
54
  entities_1.ProjectShare,
55
+ entities_1.AssetMetadata,
56
+ entities_1.ProjectMetadata,
57
+ entities_1.UserContext,
58
+ entities_1.AiEmbedding,
59
+ entities_1.MetadataActivityLog,
55
60
  ];
56
61
  let PostgresModule = class PostgresModule {
57
62
  };
@@ -65,6 +70,19 @@ exports.PostgresModule = PostgresModule = __decorate([
65
70
  entities: allEntities,
66
71
  synchronize: false,
67
72
  logging: false,
73
+ extra: {
74
+ max: 5,
75
+ min: 0,
76
+ acquireTimeoutMillis: 30000,
77
+ idleTimeoutMillis: 30000,
78
+ evictCheckIntervalMillis: 10000,
79
+ handleTimeoutMillis: 5000,
80
+ reapIntervalMillis: 1000,
81
+ createTimeoutMillis: 30000,
82
+ testOnBorrow: true,
83
+ testOnReturn: true,
84
+ testOnIdle: true,
85
+ },
68
86
  ssl: process.env.NODE_ENV === 'test' ||
69
87
  process.env.USE_TEST_CONTAINERS === 'true'
70
88
  ? false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clhq-postgres-module",
3
- "version": "1.1.0-alpha.154",
3
+ "version": "1.1.0-alpha.155",
4
4
  "description": "PostgreSQL module using TypeORM for Clippy",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",