noormme 1.0.0 → 1.0.2
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/LICENSE +89 -21
- package/README.md +195 -578
- package/dist/cjs/cli/commands/analyze.js +4 -1
- package/dist/cjs/cli/commands/generate.js +48 -8
- package/dist/cjs/cli/commands/init.js +54 -14
- package/dist/cjs/cli/commands/inspect.js +10 -3
- package/dist/cjs/cli/commands/migrate.js +38 -2
- package/dist/cjs/cli/commands/optimize.js +4 -1
- package/dist/cjs/cli/commands/status.js +41 -5
- package/dist/cjs/cli/commands/watch.js +4 -1
- package/dist/cjs/cli/index.js +4 -1
- package/dist/cjs/dialect/database-introspector.js +16 -21
- package/dist/cjs/dialect/sqlite/sqlite-introspector.js +13 -24
- package/dist/cjs/dynamic/dynamic.d.ts +20 -0
- package/dist/cjs/dynamic/dynamic.js +25 -0
- package/dist/cjs/edge-runtime/edge-config.d.ts +125 -0
- package/dist/cjs/edge-runtime/edge-config.js +323 -0
- package/dist/cjs/errors/NoormError.d.ts +27 -4
- package/dist/cjs/errors/NoormError.js +142 -21
- package/dist/cjs/logging/logger.d.ts +7 -2
- package/dist/cjs/logging/logger.js +21 -7
- package/dist/cjs/noormme.d.ts +12 -8
- package/dist/cjs/noormme.js +133 -61
- package/dist/cjs/operation-node/column-node.js +4 -0
- package/dist/cjs/operation-node/identifier-node.js +4 -0
- package/dist/cjs/operation-node/table-node.js +7 -0
- package/dist/cjs/parser/reference-parser.js +5 -0
- package/dist/cjs/parser/table-parser.js +2 -0
- package/dist/cjs/performance/index.d.ts +44 -0
- package/dist/cjs/performance/index.js +64 -0
- package/dist/cjs/performance/query-optimizer.d.ts +134 -0
- package/dist/cjs/performance/query-optimizer.js +391 -0
- package/dist/cjs/performance/services/cache-service.d.ts +177 -0
- package/dist/cjs/performance/services/cache-service.js +415 -0
- package/dist/cjs/performance/services/connection-factory.d.ts +198 -0
- package/dist/cjs/performance/services/connection-factory.js +498 -0
- package/dist/cjs/performance/services/metrics-collector.d.ts +162 -0
- package/dist/cjs/performance/services/metrics-collector.js +406 -0
- package/dist/cjs/performance/utils/query-parser.d.ts +123 -0
- package/dist/cjs/performance/utils/query-parser.js +295 -0
- package/dist/cjs/raw-builder/sql.d.ts +73 -26
- package/dist/cjs/raw-builder/sql.js +9 -0
- package/dist/cjs/repository/repository-factory.d.ts +10 -42
- package/dist/cjs/repository/repository-factory.js +276 -394
- package/dist/cjs/schema/core/coordinators/schema-discovery.coordinator.js +6 -4
- package/dist/cjs/schema/core/factories/discovery-factory.js +5 -5
- package/dist/cjs/schema/core/utils/name-generator.js +34 -2
- package/dist/cjs/schema/core/utils/type-mapper.d.ts +19 -14
- package/dist/cjs/schema/core/utils/type-mapper.js +4 -7
- package/dist/cjs/schema/dialects/sqlite/discovery/sqlite-constraint-discovery.js +3 -2
- package/dist/cjs/schema/dialects/sqlite/sqlite-discovery.coordinator.d.ts +2 -0
- package/dist/cjs/schema/dialects/sqlite/sqlite-discovery.coordinator.js +19 -5
- package/dist/cjs/schema/test/dialect-capabilities.test.js +6 -0
- package/dist/cjs/schema/test/error-handling.test.js +52 -33
- package/dist/cjs/schema/test/integration.test.js +51 -5
- package/dist/cjs/schema/test/sqlite-discovery-coordinator.test.js +8 -4
- package/dist/cjs/sqlite-migration/index.js +35 -2
- package/dist/cjs/testing/test-utils.d.ts +5 -0
- package/dist/cjs/testing/test-utils.js +66 -6
- package/dist/cjs/types/index.d.ts +78 -13
- package/dist/cjs/types/index.js +23 -0
- package/dist/cjs/types/type-generator.d.ts +8 -0
- package/dist/cjs/types/type-generator.js +86 -17
- package/dist/cjs/util/safe-sql-helpers.d.ts +124 -0
- package/dist/cjs/util/safe-sql-helpers.js +221 -0
- package/dist/cjs/util/security-validator.d.ts +44 -0
- package/dist/cjs/util/security-validator.js +256 -0
- package/dist/cjs/util/security.d.ts +60 -0
- package/dist/cjs/util/security.js +137 -0
- package/dist/cjs/watch/schema-watcher.js +26 -7
- package/dist/esm/cli/commands/generate.js +10 -6
- package/dist/esm/cli/commands/init.js +15 -11
- package/dist/esm/cli/commands/inspect.js +6 -2
- package/dist/esm/cli/commands/status.js +3 -3
- package/dist/esm/dialect/database-introspector.js +16 -21
- package/dist/esm/dialect/sqlite/sqlite-introspector.js +13 -24
- package/dist/esm/dynamic/dynamic.d.ts +20 -0
- package/dist/esm/dynamic/dynamic.js +25 -0
- package/dist/esm/edge-runtime/edge-config.d.ts +125 -0
- package/dist/esm/edge-runtime/edge-config.js +281 -0
- package/dist/esm/errors/NoormError.d.ts +27 -4
- package/dist/esm/errors/NoormError.js +134 -18
- package/dist/esm/logging/logger.d.ts +7 -2
- package/dist/esm/logging/logger.js +21 -7
- package/dist/esm/noormme.d.ts +12 -8
- package/dist/esm/noormme.js +130 -61
- package/dist/esm/operation-node/column-node.js +4 -0
- package/dist/esm/operation-node/identifier-node.js +4 -0
- package/dist/esm/operation-node/table-node.js +7 -0
- package/dist/esm/parser/reference-parser.js +5 -0
- package/dist/esm/parser/table-parser.js +2 -0
- package/dist/esm/performance/index.d.ts +44 -0
- package/dist/esm/performance/index.js +48 -0
- package/dist/esm/performance/query-optimizer.d.ts +134 -0
- package/dist/esm/performance/query-optimizer.js +387 -0
- package/dist/esm/performance/services/cache-service.d.ts +177 -0
- package/dist/esm/performance/services/cache-service.js +410 -0
- package/dist/esm/performance/services/connection-factory.d.ts +198 -0
- package/dist/esm/performance/services/connection-factory.js +493 -0
- package/dist/esm/performance/services/metrics-collector.d.ts +162 -0
- package/dist/esm/performance/services/metrics-collector.js +402 -0
- package/dist/esm/performance/utils/query-parser.d.ts +123 -0
- package/dist/esm/performance/utils/query-parser.js +292 -0
- package/dist/esm/raw-builder/sql.d.ts +73 -26
- package/dist/esm/raw-builder/sql.js +9 -0
- package/dist/esm/repository/repository-factory.d.ts +10 -42
- package/dist/esm/repository/repository-factory.js +277 -395
- package/dist/esm/schema/core/coordinators/schema-discovery.coordinator.js +6 -4
- package/dist/esm/schema/core/factories/discovery-factory.js +5 -5
- package/dist/esm/schema/core/utils/name-generator.js +34 -2
- package/dist/esm/schema/core/utils/type-mapper.d.ts +19 -14
- package/dist/esm/schema/core/utils/type-mapper.js +4 -7
- package/dist/esm/schema/dialects/sqlite/discovery/sqlite-constraint-discovery.js +3 -2
- package/dist/esm/schema/dialects/sqlite/sqlite-discovery.coordinator.d.ts +2 -0
- package/dist/esm/schema/dialects/sqlite/sqlite-discovery.coordinator.js +19 -5
- package/dist/esm/schema/test/dialect-capabilities.test.js +6 -0
- package/dist/esm/schema/test/error-handling.test.js +52 -33
- package/dist/esm/schema/test/integration.test.js +18 -5
- package/dist/esm/schema/test/sqlite-discovery-coordinator.test.js +8 -4
- package/dist/esm/testing/test-utils.d.ts +5 -0
- package/dist/esm/testing/test-utils.js +66 -6
- package/dist/esm/types/index.d.ts +78 -13
- package/dist/esm/types/index.js +22 -1
- package/dist/esm/types/type-generator.d.ts +8 -0
- package/dist/esm/types/type-generator.js +86 -17
- package/dist/esm/util/safe-sql-helpers.d.ts +124 -0
- package/dist/esm/util/safe-sql-helpers.js +209 -0
- package/dist/esm/util/security-validator.d.ts +44 -0
- package/dist/esm/util/security-validator.js +246 -0
- package/dist/esm/util/security.d.ts +60 -0
- package/dist/esm/util/security.js +114 -0
- package/dist/esm/watch/schema-watcher.js +26 -7
- package/package.json +1 -1
- package/dist/cjs/performance/query-analyzer.d.ts +0 -89
- package/dist/cjs/performance/query-analyzer.js +0 -263
- package/dist/esm/performance/query-analyzer.d.ts +0 -89
- package/dist/esm/performance/query-analyzer.js +0 -260
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConnectionPoolManager = exports.ConnectionFactory = void 0;
|
|
4
|
+
exports.createConnectionPool = createConnectionPool;
|
|
5
|
+
const noormme_1 = require("../../noormme");
|
|
6
|
+
const logger_js_1 = require("../../logging/logger.js");
|
|
7
|
+
/**
|
|
8
|
+
* Factory for creating and managing database connections
|
|
9
|
+
*/
|
|
10
|
+
class ConnectionFactory {
|
|
11
|
+
connections = new Map();
|
|
12
|
+
stats;
|
|
13
|
+
logger;
|
|
14
|
+
constructor(logger) {
|
|
15
|
+
this.logger = logger || new logger_js_1.Logger('ConnectionFactory');
|
|
16
|
+
this.stats = {
|
|
17
|
+
totalCreated: 0,
|
|
18
|
+
totalDestroyed: 0,
|
|
19
|
+
activeConnections: 0,
|
|
20
|
+
failedCreations: 0,
|
|
21
|
+
averageCreationTime: 0,
|
|
22
|
+
averageValidationTime: 0
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create a new database connection
|
|
27
|
+
*/
|
|
28
|
+
async createConnection(config) {
|
|
29
|
+
const startTime = performance.now();
|
|
30
|
+
const id = config.id || this.generateConnectionId();
|
|
31
|
+
try {
|
|
32
|
+
this.logger.debug(`Creating connection ${id}`);
|
|
33
|
+
const db = new noormme_1.NOORMME(config);
|
|
34
|
+
await db.initialize();
|
|
35
|
+
const connection = {
|
|
36
|
+
id,
|
|
37
|
+
db,
|
|
38
|
+
createdAt: new Date(),
|
|
39
|
+
lastUsed: new Date(),
|
|
40
|
+
isActive: true,
|
|
41
|
+
inUse: false,
|
|
42
|
+
config
|
|
43
|
+
};
|
|
44
|
+
this.connections.set(id, connection);
|
|
45
|
+
this.updateStats('created', performance.now() - startTime);
|
|
46
|
+
this.logger.debug(`Created connection ${id} successfully`);
|
|
47
|
+
return connection;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
this.updateStats('failed', performance.now() - startTime);
|
|
51
|
+
this.logger.error(`Failed to create connection ${id}:`, error);
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Validate a connection
|
|
57
|
+
*/
|
|
58
|
+
async validateConnection(connection) {
|
|
59
|
+
const startTime = performance.now();
|
|
60
|
+
try {
|
|
61
|
+
await connection.db.execute('SELECT 1');
|
|
62
|
+
const responseTime = performance.now() - startTime;
|
|
63
|
+
this.updateStats('validated', responseTime);
|
|
64
|
+
return {
|
|
65
|
+
isValid: true,
|
|
66
|
+
responseTime
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
const responseTime = performance.now() - startTime;
|
|
71
|
+
this.updateStats('validated', responseTime);
|
|
72
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
73
|
+
return {
|
|
74
|
+
isValid: false,
|
|
75
|
+
error: errorMessage,
|
|
76
|
+
responseTime
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get connection by ID
|
|
82
|
+
*/
|
|
83
|
+
getConnection(id) {
|
|
84
|
+
return this.connections.get(id) || null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get all connections
|
|
88
|
+
*/
|
|
89
|
+
getAllConnections() {
|
|
90
|
+
return Array.from(this.connections.values());
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get active connections
|
|
94
|
+
*/
|
|
95
|
+
getActiveConnections() {
|
|
96
|
+
return Array.from(this.connections.values()).filter(conn => conn.isActive);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get idle connections
|
|
100
|
+
*/
|
|
101
|
+
getIdleConnections() {
|
|
102
|
+
return Array.from(this.connections.values()).filter(conn => conn.isActive && !conn.inUse);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Mark connection as in use
|
|
106
|
+
*/
|
|
107
|
+
markInUse(connection) {
|
|
108
|
+
connection.inUse = true;
|
|
109
|
+
connection.lastUsed = new Date();
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Mark connection as idle
|
|
113
|
+
*/
|
|
114
|
+
markIdle(connection) {
|
|
115
|
+
connection.inUse = false;
|
|
116
|
+
connection.lastUsed = new Date();
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Destroy a connection
|
|
120
|
+
*/
|
|
121
|
+
async destroyConnection(id) {
|
|
122
|
+
const connection = this.connections.get(id);
|
|
123
|
+
if (!connection) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
await connection.db.destroy();
|
|
128
|
+
connection.isActive = false;
|
|
129
|
+
this.connections.delete(id);
|
|
130
|
+
this.updateStats('destroyed', 0);
|
|
131
|
+
this.logger.debug(`Destroyed connection ${id}`);
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
this.logger.error(`Failed to destroy connection ${id}:`, error);
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Destroy all connections
|
|
141
|
+
*/
|
|
142
|
+
async destroyAllConnections() {
|
|
143
|
+
const destroyPromises = Array.from(this.connections.keys()).map(id => this.destroyConnection(id));
|
|
144
|
+
await Promise.all(destroyPromises);
|
|
145
|
+
this.logger.info('Destroyed all connections');
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Cleanup inactive connections
|
|
149
|
+
*/
|
|
150
|
+
async cleanupInactiveConnections(maxAge = 300000) {
|
|
151
|
+
const now = Date.now();
|
|
152
|
+
let cleaned = 0;
|
|
153
|
+
for (const [id, connection] of this.connections.entries()) {
|
|
154
|
+
if (!connection.inUse &&
|
|
155
|
+
now - connection.lastUsed.getTime() > maxAge) {
|
|
156
|
+
if (await this.destroyConnection(id)) {
|
|
157
|
+
cleaned++;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (cleaned > 0) {
|
|
162
|
+
this.logger.info(`Cleaned up ${cleaned} inactive connections`);
|
|
163
|
+
}
|
|
164
|
+
return cleaned;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Validate all connections
|
|
168
|
+
*/
|
|
169
|
+
async validateAllConnections() {
|
|
170
|
+
const validationPromises = Array.from(this.connections.values()).map(async (connection) => {
|
|
171
|
+
const result = await this.validateConnection(connection);
|
|
172
|
+
return {
|
|
173
|
+
id: connection.id,
|
|
174
|
+
isValid: result.isValid,
|
|
175
|
+
error: result.error,
|
|
176
|
+
responseTime: result.responseTime
|
|
177
|
+
};
|
|
178
|
+
});
|
|
179
|
+
return Promise.all(validationPromises);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get connection statistics
|
|
183
|
+
*/
|
|
184
|
+
getStats() {
|
|
185
|
+
return { ...this.stats };
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Get connection health status
|
|
189
|
+
*/
|
|
190
|
+
async getHealthStatus() {
|
|
191
|
+
const connections = this.getAllConnections();
|
|
192
|
+
const activeConnections = this.getActiveConnections();
|
|
193
|
+
const idleConnections = this.getIdleConnections();
|
|
194
|
+
const inUseConnections = connections.filter(c => c.inUse);
|
|
195
|
+
const issues = [];
|
|
196
|
+
// Check for failed creations
|
|
197
|
+
if (this.stats.failedCreations > 0) {
|
|
198
|
+
issues.push(`${this.stats.failedCreations} connection creation failures`);
|
|
199
|
+
}
|
|
200
|
+
// Check for slow validation times
|
|
201
|
+
if (this.stats.averageValidationTime > 1000) {
|
|
202
|
+
issues.push(`Slow connection validation: ${this.stats.averageValidationTime.toFixed(2)}ms average`);
|
|
203
|
+
}
|
|
204
|
+
// Check for high connection count
|
|
205
|
+
if (connections.length > 50) {
|
|
206
|
+
issues.push(`High connection count: ${connections.length}`);
|
|
207
|
+
}
|
|
208
|
+
let status = 'healthy';
|
|
209
|
+
if (issues.length > 0) {
|
|
210
|
+
status = issues.some(issue => issue.includes('failures')) ? 'critical' : 'warning';
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
status,
|
|
214
|
+
issues,
|
|
215
|
+
connections: {
|
|
216
|
+
total: connections.length,
|
|
217
|
+
active: activeConnections.length,
|
|
218
|
+
idle: idleConnections.length,
|
|
219
|
+
inUse: inUseConnections.length
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Generate unique connection ID
|
|
225
|
+
*/
|
|
226
|
+
generateConnectionId() {
|
|
227
|
+
return `conn_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Update statistics
|
|
231
|
+
*/
|
|
232
|
+
updateStats(operation, time) {
|
|
233
|
+
switch (operation) {
|
|
234
|
+
case 'created':
|
|
235
|
+
this.stats.totalCreated++;
|
|
236
|
+
this.stats.activeConnections++;
|
|
237
|
+
this.updateAverageTime('creation', time);
|
|
238
|
+
break;
|
|
239
|
+
case 'destroyed':
|
|
240
|
+
this.stats.totalDestroyed++;
|
|
241
|
+
this.stats.activeConnections = Math.max(0, this.stats.activeConnections - 1);
|
|
242
|
+
break;
|
|
243
|
+
case 'failed':
|
|
244
|
+
this.stats.failedCreations++;
|
|
245
|
+
break;
|
|
246
|
+
case 'validated':
|
|
247
|
+
this.updateAverageTime('validation', time);
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Update average time for operations
|
|
253
|
+
*/
|
|
254
|
+
updateAverageTime(operation, time) {
|
|
255
|
+
if (operation === 'creation') {
|
|
256
|
+
const total = this.stats.totalCreated;
|
|
257
|
+
this.stats.averageCreationTime =
|
|
258
|
+
(this.stats.averageCreationTime * (total - 1) + time) / total;
|
|
259
|
+
}
|
|
260
|
+
else if (operation === 'validation') {
|
|
261
|
+
// This is a simplified approach - in practice you'd track validation count separately
|
|
262
|
+
this.stats.averageValidationTime =
|
|
263
|
+
(this.stats.averageValidationTime + time) / 2;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
exports.ConnectionFactory = ConnectionFactory;
|
|
268
|
+
/**
|
|
269
|
+
* Connection pool manager using the factory
|
|
270
|
+
*/
|
|
271
|
+
class ConnectionPoolManager {
|
|
272
|
+
factory;
|
|
273
|
+
config;
|
|
274
|
+
poolConfig;
|
|
275
|
+
waitingQueue = [];
|
|
276
|
+
validationTimer;
|
|
277
|
+
isShuttingDown = false;
|
|
278
|
+
logger;
|
|
279
|
+
constructor(config, poolConfig = {}, logger) {
|
|
280
|
+
this.config = config;
|
|
281
|
+
this.logger = logger || new logger_js_1.Logger('ConnectionPoolManager');
|
|
282
|
+
this.factory = new ConnectionFactory(this.logger);
|
|
283
|
+
this.poolConfig = {
|
|
284
|
+
minConnections: 2,
|
|
285
|
+
maxConnections: 10,
|
|
286
|
+
acquireTimeout: 30000,
|
|
287
|
+
idleTimeout: 300000,
|
|
288
|
+
validationInterval: 60000,
|
|
289
|
+
...poolConfig
|
|
290
|
+
};
|
|
291
|
+
this.startValidationTimer();
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Initialize the connection pool
|
|
295
|
+
*/
|
|
296
|
+
async initialize() {
|
|
297
|
+
this.logger.info(`Initializing connection pool with ${this.poolConfig.minConnections} minimum connections`);
|
|
298
|
+
const initPromises = Array.from({ length: this.poolConfig.minConnections }, (_, i) => this.factory.createConnection({
|
|
299
|
+
...this.config,
|
|
300
|
+
id: `pool_conn_${i}`
|
|
301
|
+
}));
|
|
302
|
+
try {
|
|
303
|
+
await Promise.all(initPromises);
|
|
304
|
+
this.logger.info(`Connection pool initialized with ${this.factory.getAllConnections().length} connections`);
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
this.logger.error('Failed to initialize connection pool:', error);
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Acquire a connection from the pool
|
|
313
|
+
*/
|
|
314
|
+
async acquire() {
|
|
315
|
+
if (this.isShuttingDown) {
|
|
316
|
+
throw new Error('Connection pool is shutting down');
|
|
317
|
+
}
|
|
318
|
+
const startTime = performance.now();
|
|
319
|
+
try {
|
|
320
|
+
// Try to get an idle connection
|
|
321
|
+
const idleConnection = this.getIdleConnection();
|
|
322
|
+
if (idleConnection) {
|
|
323
|
+
this.factory.markInUse(idleConnection);
|
|
324
|
+
this.logger.debug(`Acquired connection ${idleConnection.id} from pool`);
|
|
325
|
+
return idleConnection;
|
|
326
|
+
}
|
|
327
|
+
// Try to create a new connection if under max limit
|
|
328
|
+
const currentConnections = this.factory.getAllConnections();
|
|
329
|
+
if (currentConnections.length < this.poolConfig.maxConnections) {
|
|
330
|
+
const newConnection = await this.factory.createConnection({
|
|
331
|
+
...this.config,
|
|
332
|
+
id: `pool_conn_${Date.now()}`
|
|
333
|
+
});
|
|
334
|
+
this.factory.markInUse(newConnection);
|
|
335
|
+
this.logger.debug(`Created and acquired new connection ${newConnection.id}`);
|
|
336
|
+
return newConnection;
|
|
337
|
+
}
|
|
338
|
+
// Wait for a connection to become available
|
|
339
|
+
return this.waitForConnection(startTime);
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
this.logger.error('Failed to acquire connection:', error);
|
|
343
|
+
throw error;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Release a connection back to the pool
|
|
348
|
+
*/
|
|
349
|
+
async release(connection) {
|
|
350
|
+
try {
|
|
351
|
+
// Validate connection before returning to pool
|
|
352
|
+
const validation = await this.factory.validateConnection(connection);
|
|
353
|
+
if (!validation.isValid) {
|
|
354
|
+
this.logger.warn(`Connection ${connection.id} failed validation, removing from pool`);
|
|
355
|
+
await this.factory.destroyConnection(connection.id);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
this.factory.markIdle(connection);
|
|
359
|
+
this.processWaitingQueue();
|
|
360
|
+
this.logger.debug(`Released connection ${connection.id} back to pool`);
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
this.logger.error(`Failed to release connection ${connection.id}:`, error);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Execute a function with a pooled connection
|
|
368
|
+
*/
|
|
369
|
+
async withConnection(fn) {
|
|
370
|
+
const connection = await this.acquire();
|
|
371
|
+
try {
|
|
372
|
+
return await fn(connection.db);
|
|
373
|
+
}
|
|
374
|
+
finally {
|
|
375
|
+
await this.release(connection);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Get pool statistics
|
|
380
|
+
*/
|
|
381
|
+
getStats() {
|
|
382
|
+
const factoryStats = this.factory.getStats();
|
|
383
|
+
const connections = this.factory.getAllConnections();
|
|
384
|
+
return {
|
|
385
|
+
...factoryStats,
|
|
386
|
+
poolSize: connections.length,
|
|
387
|
+
idleConnections: this.factory.getIdleConnections().length,
|
|
388
|
+
inUseConnections: connections.filter(c => c.inUse).length,
|
|
389
|
+
waitingQueue: this.waitingQueue.length
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Shutdown the connection pool
|
|
394
|
+
*/
|
|
395
|
+
async shutdown() {
|
|
396
|
+
this.logger.info('Shutting down connection pool...');
|
|
397
|
+
this.isShuttingDown = true;
|
|
398
|
+
// Stop validation timer
|
|
399
|
+
if (this.validationTimer) {
|
|
400
|
+
clearInterval(this.validationTimer);
|
|
401
|
+
}
|
|
402
|
+
// Reject all waiting requests
|
|
403
|
+
this.waitingQueue.forEach(({ reject }) => {
|
|
404
|
+
reject(new Error('Connection pool is shutting down'));
|
|
405
|
+
});
|
|
406
|
+
this.waitingQueue = [];
|
|
407
|
+
// Destroy all connections
|
|
408
|
+
await this.factory.destroyAllConnections();
|
|
409
|
+
this.logger.info('Connection pool shutdown complete');
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Get idle connection from pool
|
|
413
|
+
*/
|
|
414
|
+
getIdleConnection() {
|
|
415
|
+
const idleConnections = this.factory.getIdleConnections();
|
|
416
|
+
return idleConnections.length > 0 ? idleConnections[0] : null;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Wait for a connection to become available
|
|
420
|
+
*/
|
|
421
|
+
async waitForConnection(startTime) {
|
|
422
|
+
return new Promise((resolve, reject) => {
|
|
423
|
+
const timeout = setTimeout(() => {
|
|
424
|
+
const index = this.waitingQueue.findIndex(item => item.reject === reject);
|
|
425
|
+
if (index !== -1) {
|
|
426
|
+
this.waitingQueue.splice(index, 1);
|
|
427
|
+
}
|
|
428
|
+
reject(new Error(`Connection acquisition timeout after ${this.poolConfig.acquireTimeout}ms`));
|
|
429
|
+
}, this.poolConfig.acquireTimeout);
|
|
430
|
+
this.waitingQueue.push({
|
|
431
|
+
resolve: (connection) => {
|
|
432
|
+
clearTimeout(timeout);
|
|
433
|
+
resolve(connection);
|
|
434
|
+
},
|
|
435
|
+
reject: (error) => {
|
|
436
|
+
clearTimeout(timeout);
|
|
437
|
+
reject(error);
|
|
438
|
+
},
|
|
439
|
+
timestamp: Date.now()
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Process waiting queue when connections become available
|
|
445
|
+
*/
|
|
446
|
+
processWaitingQueue() {
|
|
447
|
+
while (this.waitingQueue.length > 0) {
|
|
448
|
+
const idleConnection = this.getIdleConnection();
|
|
449
|
+
if (!idleConnection)
|
|
450
|
+
break;
|
|
451
|
+
const waiter = this.waitingQueue.shift();
|
|
452
|
+
this.factory.markInUse(idleConnection);
|
|
453
|
+
waiter.resolve(idleConnection);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Start validation timer
|
|
458
|
+
*/
|
|
459
|
+
startValidationTimer() {
|
|
460
|
+
this.validationTimer = setInterval(async () => {
|
|
461
|
+
await this.validateConnections();
|
|
462
|
+
await this.cleanupIdleConnections();
|
|
463
|
+
}, this.poolConfig.validationInterval);
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Validate all connections in the pool
|
|
467
|
+
*/
|
|
468
|
+
async validateConnections() {
|
|
469
|
+
const validationPromises = this.factory.getIdleConnections().map(async (connection) => {
|
|
470
|
+
const validation = await this.factory.validateConnection(connection);
|
|
471
|
+
if (!validation.isValid) {
|
|
472
|
+
await this.factory.destroyConnection(connection.id);
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
await Promise.all(validationPromises);
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Cleanup idle connections that exceed the idle timeout
|
|
479
|
+
*/
|
|
480
|
+
async cleanupIdleConnections() {
|
|
481
|
+
const now = Date.now();
|
|
482
|
+
const idleConnections = this.factory.getIdleConnections();
|
|
483
|
+
const excessConnections = idleConnections.filter(conn => now - conn.lastUsed.getTime() > this.poolConfig.idleTimeout).slice(this.poolConfig.minConnections);
|
|
484
|
+
for (const connection of excessConnections) {
|
|
485
|
+
await this.factory.destroyConnection(connection.id);
|
|
486
|
+
}
|
|
487
|
+
if (excessConnections.length > 0) {
|
|
488
|
+
this.logger.debug(`Cleaned up ${excessConnections.length} idle connections`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
exports.ConnectionPoolManager = ConnectionPoolManager;
|
|
493
|
+
/**
|
|
494
|
+
* Factory function to create connection pool manager
|
|
495
|
+
*/
|
|
496
|
+
function createConnectionPool(config, poolConfig, logger) {
|
|
497
|
+
return new ConnectionPoolManager(config, poolConfig, logger);
|
|
498
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { Logger } from '../../logging/logger.js';
|
|
2
|
+
export interface QueryMetrics {
|
|
3
|
+
query: string;
|
|
4
|
+
executionTime: number;
|
|
5
|
+
timestamp: number;
|
|
6
|
+
table?: string;
|
|
7
|
+
operation?: string;
|
|
8
|
+
resultCount?: number;
|
|
9
|
+
error?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface PerformanceWarning {
|
|
12
|
+
type: 'n_plus_one' | 'missing_index' | 'slow_query' | 'full_table_scan' | 'large_result_set';
|
|
13
|
+
message: string;
|
|
14
|
+
suggestion: string;
|
|
15
|
+
query?: string;
|
|
16
|
+
table?: string;
|
|
17
|
+
severity: 'low' | 'medium' | 'high';
|
|
18
|
+
timestamp: number;
|
|
19
|
+
}
|
|
20
|
+
export interface MetricsConfig {
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
slowQueryThreshold: number;
|
|
23
|
+
nPlusOneDetection: boolean;
|
|
24
|
+
missingIndexDetection: boolean;
|
|
25
|
+
largeResultSetThreshold: number;
|
|
26
|
+
maxHistorySize: number;
|
|
27
|
+
warningRetentionDays: number;
|
|
28
|
+
}
|
|
29
|
+
export interface PerformanceStats {
|
|
30
|
+
totalQueries: number;
|
|
31
|
+
averageExecutionTime: number;
|
|
32
|
+
slowQueries: number;
|
|
33
|
+
errorRate: number;
|
|
34
|
+
warningCount: {
|
|
35
|
+
[key: string]: number;
|
|
36
|
+
};
|
|
37
|
+
topSlowQueries: Array<{
|
|
38
|
+
query: string;
|
|
39
|
+
averageTime: number;
|
|
40
|
+
count: number;
|
|
41
|
+
}>;
|
|
42
|
+
queryPatterns: Array<{
|
|
43
|
+
pattern: string;
|
|
44
|
+
frequency: number;
|
|
45
|
+
averageTime: number;
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Service for collecting and analyzing performance metrics
|
|
50
|
+
*/
|
|
51
|
+
export declare class MetricsCollector {
|
|
52
|
+
private queryHistory;
|
|
53
|
+
private warnings;
|
|
54
|
+
private recentQueries;
|
|
55
|
+
private config;
|
|
56
|
+
private logger;
|
|
57
|
+
private cleanupTimer?;
|
|
58
|
+
constructor(config?: Partial<MetricsConfig>, logger?: Logger);
|
|
59
|
+
/**
|
|
60
|
+
* Record a query execution
|
|
61
|
+
*/
|
|
62
|
+
recordQuery(query: string, executionTime: number, options?: {
|
|
63
|
+
table?: string;
|
|
64
|
+
operation?: string;
|
|
65
|
+
resultCount?: number;
|
|
66
|
+
error?: string;
|
|
67
|
+
}): void;
|
|
68
|
+
/**
|
|
69
|
+
* Get performance statistics
|
|
70
|
+
*/
|
|
71
|
+
getPerformanceStats(): PerformanceStats;
|
|
72
|
+
/**
|
|
73
|
+
* Get recent warnings
|
|
74
|
+
*/
|
|
75
|
+
getRecentWarnings(limit?: number): PerformanceWarning[];
|
|
76
|
+
/**
|
|
77
|
+
* Get warnings by type
|
|
78
|
+
*/
|
|
79
|
+
getWarningsByType(type: PerformanceWarning['type']): PerformanceWarning[];
|
|
80
|
+
/**
|
|
81
|
+
* Get query history
|
|
82
|
+
*/
|
|
83
|
+
getQueryHistory(limit?: number): QueryMetrics[];
|
|
84
|
+
/**
|
|
85
|
+
* Get slow queries
|
|
86
|
+
*/
|
|
87
|
+
getSlowQueries(threshold?: number): QueryMetrics[];
|
|
88
|
+
/**
|
|
89
|
+
* Get N+1 query patterns
|
|
90
|
+
*/
|
|
91
|
+
getNPlusOnePatterns(): Array<{
|
|
92
|
+
pattern: string;
|
|
93
|
+
count: number;
|
|
94
|
+
timeWindow: number;
|
|
95
|
+
severity: 'low' | 'medium' | 'high';
|
|
96
|
+
}>;
|
|
97
|
+
/**
|
|
98
|
+
* Clear all metrics
|
|
99
|
+
*/
|
|
100
|
+
clear(): void;
|
|
101
|
+
/**
|
|
102
|
+
* Export metrics data
|
|
103
|
+
*/
|
|
104
|
+
exportMetrics(): {
|
|
105
|
+
config: MetricsConfig;
|
|
106
|
+
stats: PerformanceStats;
|
|
107
|
+
warnings: PerformanceWarning[];
|
|
108
|
+
queries: QueryMetrics[];
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Shutdown metrics collector
|
|
112
|
+
*/
|
|
113
|
+
shutdown(): void;
|
|
114
|
+
/**
|
|
115
|
+
* Track recent query for pattern detection
|
|
116
|
+
*/
|
|
117
|
+
private trackRecentQuery;
|
|
118
|
+
/**
|
|
119
|
+
* Analyze query for performance issues
|
|
120
|
+
*/
|
|
121
|
+
private analyzeQuery;
|
|
122
|
+
/**
|
|
123
|
+
* Detect N+1 query patterns
|
|
124
|
+
*/
|
|
125
|
+
private detectNPlusOne;
|
|
126
|
+
/**
|
|
127
|
+
* Add warning to collection
|
|
128
|
+
*/
|
|
129
|
+
private addWarning;
|
|
130
|
+
/**
|
|
131
|
+
* Log performance warning
|
|
132
|
+
*/
|
|
133
|
+
private logWarning;
|
|
134
|
+
/**
|
|
135
|
+
* Get emoji for warning type
|
|
136
|
+
*/
|
|
137
|
+
private getWarningEmoji;
|
|
138
|
+
/**
|
|
139
|
+
* Normalize query for comparison
|
|
140
|
+
*/
|
|
141
|
+
private normalizeQuery;
|
|
142
|
+
/**
|
|
143
|
+
* Get top slow queries
|
|
144
|
+
*/
|
|
145
|
+
private getTopSlowQueries;
|
|
146
|
+
/**
|
|
147
|
+
* Get query patterns
|
|
148
|
+
*/
|
|
149
|
+
private getQueryPatterns;
|
|
150
|
+
/**
|
|
151
|
+
* Start cleanup timer
|
|
152
|
+
*/
|
|
153
|
+
private startCleanupTimer;
|
|
154
|
+
/**
|
|
155
|
+
* Cleanup old data
|
|
156
|
+
*/
|
|
157
|
+
private cleanup;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Factory function to create metrics collector
|
|
161
|
+
*/
|
|
162
|
+
export declare function createMetricsCollector(config?: Partial<MetricsConfig>, logger?: Logger): MetricsCollector;
|