@aetherframework/database 1.1.0 → 1.1.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.
Files changed (47) hide show
  1. package/examples/mysql-test-pressure.js +1530 -0
  2. package/examples/test-direct.js +116 -0
  3. package/examples/transaction_example.js +127 -0
  4. package/package.json +3 -1
  5. package/src/DatabaseManager.js +565 -0
  6. package/src/core/ConnectionManager.js +351 -0
  7. package/src/core/DatabaseFactory.js +188 -0
  8. package/src/core/MongoQueryBuilder.js +576 -0
  9. package/src/core/PluginManager.js +968 -0
  10. package/src/core/QueryBuilder.js +4394 -0
  11. package/src/core/TransactionManager.js +40 -0
  12. package/src/drivers/clickhouse-driver.js +272 -0
  13. package/src/drivers/index.js +273 -0
  14. package/src/drivers/mongodb-driver.js +87 -0
  15. package/src/drivers/mssql-driver.js +117 -0
  16. package/src/drivers/mysql-driver.js +169 -0
  17. package/src/drivers/oracle-driver.js +101 -0
  18. package/src/drivers/postgres-driver.js +234 -0
  19. package/src/drivers/redis-driver.js +52 -0
  20. package/src/drivers/sqlite-driver.js +67 -0
  21. package/src/middleware/connection-pool.js +455 -0
  22. package/src/middleware/performance-monitor.js +652 -0
  23. package/src/middleware/query-cache.js +500 -0
  24. package/src/middleware/query-logger.js +262 -0
  25. package/src/plugins/AuditPlugin.js +447 -0
  26. package/src/plugins/BasePlugin.js +418 -0
  27. package/src/plugins/BatchOperationPlugin.js +165 -0
  28. package/src/plugins/CachePlugin.js +407 -0
  29. package/src/plugins/CtePlugin.js +523 -0
  30. package/src/plugins/DistributedPlugin.js +543 -0
  31. package/src/plugins/EncryptionPlugin.js +211 -0
  32. package/src/plugins/FullTextSearchPlugin.js +164 -0
  33. package/src/plugins/GeospatialPlugin.js +219 -0
  34. package/src/plugins/GraphQLPlugin.js +162 -0
  35. package/src/plugins/HookPlugin.js +211 -0
  36. package/src/plugins/JsonPlugin.js +366 -0
  37. package/src/plugins/OptimisticLockPlugin.js +374 -0
  38. package/src/plugins/PerformancePlugin.js +175 -0
  39. package/src/plugins/ResiliencePlugin.js +114 -0
  40. package/src/plugins/ShardingPlugin.js +227 -0
  41. package/src/plugins/SoftDeletePlugin.js +258 -0
  42. package/src/plugins/SyncPlugin.js +373 -0
  43. package/src/plugins/VersioningPlugin.js +314 -0
  44. package/src/plugins/WindowFunctionPlugin.js +343 -0
  45. package/src/utils/config-loader.js +632 -0
  46. package/src/utils/error-handler.js +724 -0
  47. package/src/utils/migration-runner.js +1066 -0
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @license MIT
3
+ * Copyright (c) 2026-present AetherFramework Contributors.
4
+ * SPDX-License-Identifier: MIT
5
+ * @module @aetherframework/database/drivers/sqlite-driver
6
+ */
7
+ import sqlite3 from 'sqlite3';
8
+ import { open } from 'sqlite';
9
+
10
+ class SQLiteDriver {
11
+ constructor(config) {
12
+ this.config = config;
13
+ this.db = null;
14
+ }
15
+
16
+ async connect(config) {
17
+ const db = await open({
18
+ filename: config.database === ':memory:' ? ':memory:' : config.database,
19
+ driver: sqlite3.Database,
20
+ mode: config.mode === 'memory' ? sqlite3.OPEN_MEMORY : sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE
21
+ });
22
+ this.db = db;
23
+ return db;
24
+ }
25
+
26
+ async query(connection, sql, params = []) {
27
+ const rows = await connection.all(sql, params);
28
+ const info = await connection.run(sql, params);
29
+ return {
30
+ rows,
31
+ rowCount: rows.length,
32
+ lastID: info.lastID,
33
+ changes: info.changes
34
+ };
35
+ }
36
+
37
+ async execute(connection, sql, params = []) {
38
+ const result = await connection.run(sql, params);
39
+ return {
40
+ lastID: result.lastID,
41
+ changes: result.changes
42
+ };
43
+ }
44
+
45
+ async beginTransaction(connection) {
46
+ await connection.run('BEGIN TRANSACTION');
47
+ }
48
+
49
+ async commitTransaction(connection) {
50
+ await connection.run('COMMIT');
51
+ }
52
+
53
+ async rollbackTransaction(connection) {
54
+ await connection.run('ROLLBACK');
55
+ }
56
+
57
+ async close(connection) {
58
+ await connection.close();
59
+ }
60
+
61
+ async healthCheck(connection) {
62
+ const result = await connection.get('SELECT 1 as health');
63
+ return result.health === 1;
64
+ }
65
+ }
66
+
67
+ export default SQLiteDriver;
@@ -0,0 +1,455 @@
1
+ /**
2
+ * @license MIT
3
+ * Copyright (c) 2026-present AetherFramework Contributors.
4
+ * SPDX-License-Identifier: MIT
5
+ * @module @aetherframework/database/middleware/connection-pool
6
+ */
7
+ import { EventEmitter } from 'events';
8
+
9
+ class ConnectionPoolMiddleware extends EventEmitter {
10
+ constructor(options = {}) {
11
+ super();
12
+ this.options = {
13
+ maxConnections: options.maxConnections || 10,
14
+ minConnections: options.minConnections || 2,
15
+ idleTimeout: options.idleTimeout || 30000, // ms
16
+ acquireTimeout: options.acquireTimeout || 10000, // ms
17
+ evictionRunInterval: options.evictionRunInterval || 60000, // ms
18
+ testOnBorrow: options.testOnBorrow !== false,
19
+ testOnReturn: options.testOnReturn !== false,
20
+ ...options
21
+ };
22
+
23
+ this.pool = new Map();
24
+ this.activeConnections = new Set();
25
+ this.waitingQueue = [];
26
+ this.stats = {
27
+ totalConnections: 0,
28
+ activeConnections: 0,
29
+ idleConnections: 0,
30
+ waitingClients: 0,
31
+ connectionCreations: 0,
32
+ connectionDestructions: 0,
33
+ connectionTimeouts: 0,
34
+ connectionErrors: 0
35
+ };
36
+
37
+ this.startEvictionTimer();
38
+ }
39
+
40
+ /**
41
+ * Get a connection from pool
42
+ * @param {string} connectionName - Connection name
43
+ * @param {Function} createConnection - Function to create new connection
44
+ * @returns {Promise<Object>} Database connection
45
+ */
46
+ async getConnection(connectionName, createConnection) {
47
+ const poolKey = connectionName;
48
+
49
+ // Check if we have idle connections
50
+ if (this.pool.has(poolKey) && this.pool.get(poolKey).length > 0) {
51
+ const connections = this.pool.get(poolKey);
52
+ const connection = connections.pop();
53
+
54
+ // Test connection if enabled
55
+ if (this.options.testOnBorrow) {
56
+ try {
57
+ await this.testConnection(connection);
58
+ } catch (error) {
59
+ this.stats.connectionErrors++;
60
+ this.emit('connection-test-failed', { connectionName, error });
61
+ // Remove bad connection and create new one
62
+ this.stats.connectionDestructions++;
63
+ return this.createNewConnection(connectionName, createConnection);
64
+ }
65
+ }
66
+
67
+ this.activeConnections.add(connection);
68
+ this.stats.activeConnections++;
69
+ this.stats.idleConnections--;
70
+
71
+ this.emit('connection-acquired', {
72
+ connectionName,
73
+ fromPool: true,
74
+ activeConnections: this.stats.activeConnections,
75
+ idleConnections: this.stats.idleConnections
76
+ });
77
+
78
+ return connection;
79
+ }
80
+
81
+ // Check if we can create new connection
82
+ if (this.stats.totalConnections < this.options.maxConnections) {
83
+ const connection = await this.createNewConnection(connectionName, createConnection);
84
+ this.activeConnections.add(connection);
85
+ this.stats.activeConnections++;
86
+
87
+ this.emit('connection-created', {
88
+ connectionName,
89
+ activeConnections: this.stats.activeConnections,
90
+ totalConnections: this.stats.totalConnections
91
+ });
92
+
93
+ return connection;
94
+ }
95
+
96
+ // Wait for connection to become available
97
+ return new Promise((resolve, reject) => {
98
+ const waitStartTime = Date.now();
99
+ const timeoutId = setTimeout(() => {
100
+ const index = this.waitingQueue.findIndex(item => item.resolve === resolve);
101
+ if (index !== -1) {
102
+ this.waitingQueue.splice(index, 1);
103
+ this.stats.waitingClients--;
104
+ this.stats.connectionTimeouts++;
105
+
106
+ this.emit('connection-timeout', {
107
+ connectionName,
108
+ waitTime: Date.now() - waitStartTime,
109
+ waitingClients: this.stats.waitingClients
110
+ });
111
+
112
+ reject(new Error(`Connection pool timeout after ${this.options.acquireTimeout}ms`));
113
+ }
114
+ }, this.options.acquireTimeout);
115
+
116
+ this.waitingQueue.push({
117
+ connectionName,
118
+ resolve: async () => {
119
+ clearTimeout(timeoutId);
120
+ try {
121
+ const connection = await this.getConnection(connectionName, createConnection);
122
+ resolve(connection);
123
+ } catch (error) {
124
+ reject(error);
125
+ }
126
+ },
127
+ reject,
128
+ timestamp: Date.now()
129
+ });
130
+
131
+ this.stats.waitingClients++;
132
+ this.emit('connection-waiting', {
133
+ connectionName,
134
+ waitingClients: this.stats.waitingClients
135
+ });
136
+ });
137
+ }
138
+
139
+ /**
140
+ * Release connection back to pool
141
+ * @param {string} connectionName - Connection name
142
+ * @param {Object} connection - Database connection
143
+ */
144
+ async releaseConnection(connectionName, connection) {
145
+ if (!this.activeConnections.has(connection)) {
146
+ this.emit('connection-not-active', { connectionName });
147
+ return;
148
+ }
149
+
150
+ // Test connection if enabled
151
+ if (this.options.testOnReturn) {
152
+ try {
153
+ await this.testConnection(connection);
154
+ } catch (error) {
155
+ this.stats.connectionErrors++;
156
+ this.emit('connection-test-failed', { connectionName, error });
157
+ // Destroy bad connection
158
+ await this.destroyConnection(connectionName, connection);
159
+ this.activeConnections.delete(connection);
160
+ this.stats.activeConnections--;
161
+ this.stats.totalConnections--;
162
+ return;
163
+ }
164
+ }
165
+
166
+ // Add connection back to pool
167
+ if (!this.pool.has(connectionName)) {
168
+ this.pool.set(connectionName, []);
169
+ }
170
+
171
+ this.pool.get(connectionName).push({
172
+ connection,
173
+ lastUsed: Date.now()
174
+ });
175
+
176
+ this.activeConnections.delete(connection);
177
+ this.stats.activeConnections--;
178
+ this.stats.idleConnections++;
179
+
180
+ // Check waiting queue
181
+ if (this.waitingQueue.length > 0) {
182
+ const nextRequest = this.waitingQueue.shift();
183
+ this.stats.waitingClients--;
184
+ nextRequest.resolve();
185
+ }
186
+
187
+ this.emit('connection-released', {
188
+ connectionName,
189
+ activeConnections: this.stats.activeConnections,
190
+ idleConnections: this.stats.idleConnections,
191
+ waitingClients: this.stats.waitingClients
192
+ });
193
+ }
194
+
195
+ /**
196
+ * Create new connection
197
+ * @param {string} connectionName - Connection name
198
+ * @param {Function} createConnection - Function to create new connection
199
+ * @returns {Promise<Object>} New connection
200
+ */
201
+ async createNewConnection(connectionName, createConnection) {
202
+ try {
203
+ const connection = await createConnection();
204
+ this.stats.totalConnections++;
205
+ this.stats.connectionCreations++;
206
+
207
+ this.emit('connection-created', {
208
+ connectionName,
209
+ activeConnections: this.stats.activeConnections,
210
+ totalConnections: this.stats.totalConnections
211
+ });
212
+
213
+ return connection;
214
+ } catch (error) {
215
+ this.stats.connectionErrors++;
216
+ this.emit('connection-creation-error', { connectionName, error });
217
+ throw error;
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Destroy connection
223
+ * @param {string} connectionName - Connection name
224
+ * @param {Object} connection - Database connection
225
+ */
226
+ async destroyConnection(connectionName, connection) {
227
+ try {
228
+ if (typeof connection.close === 'function') {
229
+ await connection.close();
230
+ } else if (typeof connection.end === 'function') {
231
+ await connection.end();
232
+ } else if (typeof connection.destroy === 'function') {
233
+ connection.destroy();
234
+ }
235
+
236
+ this.stats.connectionDestructions++;
237
+ this.emit('connection-destroyed', { connectionName });
238
+ } catch (error) {
239
+ this.stats.connectionErrors++;
240
+ this.emit('connection-destruction-error', { connectionName, error });
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Test connection health
246
+ * @param {Object} connection - Database connection
247
+ * @returns {Promise<boolean>} True if connection is healthy
248
+ */
249
+ async testConnection(connection) {
250
+ // Try to execute a simple query to test connection
251
+ if (typeof connection.query === 'function') {
252
+ try {
253
+ await connection.query('SELECT 1');
254
+ return true;
255
+ } catch (error) {
256
+ return false;
257
+ }
258
+ }
259
+
260
+ // For connections without query method, assume they're healthy
261
+ return true;
262
+ }
263
+
264
+ /**
265
+ * Start eviction timer
266
+ */
267
+ startEvictionTimer() {
268
+ setInterval(() => {
269
+ this.evictIdleConnections();
270
+ }, this.options.evictionRunInterval);
271
+ }
272
+
273
+ /**
274
+ * Evict idle connections
275
+ */
276
+ async evictIdleConnections() {
277
+ const now = Date.now();
278
+
279
+ for (const [connectionName, connections] of this.pool.entries()) {
280
+ const activeConnections = [];
281
+ const idleConnections = [];
282
+ const toDestroy = [];
283
+
284
+ for (const connInfo of connections) {
285
+ const idleTime = now - connInfo.lastUsed;
286
+
287
+ if (idleTime > this.options.idleTimeout) {
288
+ toDestroy.push(connInfo);
289
+ } else if (this.activeConnections.has(connInfo.connection)) {
290
+ activeConnections.push(connInfo);
291
+ } else {
292
+ idleConnections.push(connInfo);
293
+ }
294
+ }
295
+
296
+ // Destroy idle connections that exceed idle timeout
297
+ for (const connInfo of toDestroy) {
298
+ await this.destroyConnection(connectionName, connInfo.connection);
299
+ }
300
+
301
+ // Update pool with remaining connections
302
+ this.pool.set(connectionName, idleConnections);
303
+ this.stats.idleConnections = idleConnections.length;
304
+ this.stats.activeConnections = activeConnections.length;
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Get pool statistics
310
+ * @returns {Object} Pool statistics
311
+ */
312
+ getStats() {
313
+ const poolStats = {};
314
+ for (const [connectionName, connections] of this.pool.entries()) {
315
+ poolStats[connectionName] = {
316
+ idleConnections: connections.length,
317
+ activeConnections: Array.from(this.activeConnections).filter(conn =>
318
+ this.getConnectionName(conn) === connectionName
319
+ ).length
320
+ };
321
+ }
322
+
323
+ return {
324
+ ...this.stats,
325
+ poolStats,
326
+ waitingQueueSize: this.waitingQueue.length,
327
+ evictionRunInterval: this.options.evictionRunInterval,
328
+ idleTimeout: this.options.idleTimeout,
329
+ acquireTimeout: this.options.acquireTimeout,
330
+ maxConnections: this.options.maxConnections,
331
+ minConnections: this.options.minConnections,
332
+ testOnBorrow: this.options.testOnBorrow,
333
+ testOnReturn: this.options.testOnReturn,
334
+ timestamp: new Date().toISOString()
335
+ };
336
+ }
337
+
338
+ /**
339
+ * Get connection name from connection object
340
+ * @param {Object} connection - Database connection
341
+ * @returns {string} Connection name
342
+ */
343
+ getConnectionName(connection) {
344
+ for (const [name, connections] of this.pool.entries()) {
345
+ if (connections.some(connInfo => connInfo.connection === connection)) {
346
+ return name;
347
+ }
348
+ }
349
+ return 'unknown';
350
+ }
351
+
352
+ /**
353
+ * Close all connections in pool
354
+ * @returns {Promise<void>}
355
+ */
356
+ async closeAll() {
357
+ const closePromises = [];
358
+
359
+ // Close all idle connections
360
+ for (const [connectionName, connections] of this.pool.entries()) {
361
+ for (const connInfo of connections) {
362
+ closePromises.push(this.destroyConnection(connectionName, connInfo.connection));
363
+ }
364
+ }
365
+
366
+ // Close all active connections
367
+ for (const connection of this.activeConnections) {
368
+ const connectionName = this.getConnectionName(connection);
369
+ closePromises.push(this.destroyConnection(connectionName, connection));
370
+ }
371
+
372
+ // Clear waiting queue
373
+ for (const request of this.waitingQueue) {
374
+ request.reject(new Error('Connection pool closed'));
375
+ }
376
+ this.waitingQueue = [];
377
+
378
+ await Promise.allSettled(closePromises);
379
+
380
+ this.pool.clear();
381
+ this.activeConnections.clear();
382
+ this.stats = {
383
+ totalConnections: 0,
384
+ activeConnections: 0,
385
+ idleConnections: 0,
386
+ waitingClients: 0,
387
+ connectionCreations: 0,
388
+ connectionDestructions: 0,
389
+ connectionTimeouts: 0,
390
+ connectionErrors: 0
391
+ };
392
+
393
+ this.emit('pool-closed');
394
+ }
395
+
396
+ /**
397
+ * Drain pool (stop accepting new connections)
398
+ * @returns {Promise<void>}
399
+ */
400
+ async drain() {
401
+ this.options.maxConnections = 0;
402
+
403
+ // Wait for all active connections to be released
404
+ while (this.activeConnections.size > 0) {
405
+ await new Promise(resolve => setTimeout(resolve, 100));
406
+ }
407
+
408
+ // Close all idle connections
409
+ await this.closeAll();
410
+
411
+ this.emit('pool-drained');
412
+ }
413
+
414
+ /**
415
+ * Check if pool is healthy
416
+ * @returns {Object} Health status
417
+ */
418
+ getHealth() {
419
+ const stats = this.getStats();
420
+ const isHealthy =
421
+ stats.connectionErrors < 10 &&
422
+ stats.connectionTimeouts < 5 &&
423
+ stats.waitingQueueSize < 20;
424
+
425
+ return {
426
+ healthy: isHealthy,
427
+ status: isHealthy ? 'healthy' : 'unhealthy',
428
+ issues: !isHealthy ? [
429
+ ...(stats.connectionErrors >= 10 ? ['Too many connection errors'] : []),
430
+ ...(stats.connectionTimeouts >= 5 ? ['Too many connection timeouts'] : []),
431
+ ...(stats.waitingQueueSize >= 20 ? ['Too many waiting clients'] : [])
432
+ ] : [],
433
+ stats
434
+ };
435
+ }
436
+
437
+ /**
438
+ * Reset pool statistics
439
+ */
440
+ resetStats() {
441
+ this.stats = {
442
+ totalConnections: this.stats.totalConnections,
443
+ activeConnections: this.stats.activeConnections,
444
+ idleConnections: this.stats.idleConnections,
445
+ waitingClients: this.stats.waitingClients,
446
+ connectionCreations: 0,
447
+ connectionDestructions: 0,
448
+ connectionTimeouts: 0,
449
+ connectionErrors: 0
450
+ };
451
+ this.emit('stats-reset');
452
+ }
453
+ }
454
+
455
+ export default ConnectionPoolMiddleware;