@aetherframework/database 1.1.1 → 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,351 @@
1
+ /**
2
+ * @license MIT
3
+ * Copyright (c) 2026-present AetherFramework Contributors.
4
+ * SPDX-License-Identifier: MIT
5
+ * @module @aetherframework/database/core/ConnectionManager
6
+ */
7
+
8
+ import { EventEmitter } from 'events';
9
+
10
+ /**
11
+ * Connection Manager - Manages database connections with pooling and retry logic
12
+ */
13
+ class ConnectionManager extends EventEmitter {
14
+ /**
15
+ * Create a new ConnectionManager instance
16
+ * @param {Object} driver - Database driver instance
17
+ * @param {Object} config - Connection configuration
18
+ */
19
+ constructor(driver, config) {
20
+ super();
21
+ this.driver = driver;
22
+ this.config = config;
23
+ this.connection = null;
24
+ this.pool = null;
25
+ this.isConnected = false;
26
+ this.retryCount = 0;
27
+ this.maxRetries = config.retry?.maxAttempts || 3;
28
+ this.retryDelay = config.retry?.delay || 1000;
29
+ this.retryBackoff = config.retry?.backoff !== false;
30
+ }
31
+
32
+ /**
33
+ * Connect to database
34
+ * @returns {Promise<ConnectionManager>} Connected instance
35
+ */
36
+ async connect() {
37
+ if (this.isConnected) {
38
+ return this;
39
+ }
40
+
41
+ try {
42
+ // Check if driver supports connection pooling
43
+ if (this.config.pool && this.driver.createPool) {
44
+ this.pool = await this.driver.createPool(this.config);
45
+ this.connection = this.pool;
46
+ } else {
47
+ this.connection = await this.driver.connect(this.config);
48
+ }
49
+
50
+ this.isConnected = true;
51
+ this.retryCount = 0;
52
+ this.emit('connected', { type: this.config.type, config: this.config });
53
+ return this;
54
+ } catch (error) {
55
+ this.emit('connection:error', { error, config: this.config });
56
+
57
+ // Retry logic
58
+ if (this.retryCount < this.maxRetries) {
59
+ this.retryCount++;
60
+ const delay = this.retryBackoff
61
+ ? this.retryDelay * Math.pow(2, this.retryCount - 1)
62
+ : this.retryDelay;
63
+
64
+ console.warn(`⚠️ Connection failed, retrying in ${delay}ms (attempt ${this.retryCount}/${this.maxRetries})`);
65
+
66
+ await new Promise(resolve => setTimeout(resolve, delay));
67
+ return this.connect();
68
+ }
69
+
70
+ throw new Error(`Failed to connect to ${this.config.type} database after ${this.maxRetries} attempts: ${error.message}`);
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Execute query
76
+ * @param {string} sql - SQL statement
77
+ * @param {Array} params - Query parameters
78
+ * @param {Object} options - Query options
79
+ * @returns {Promise<Object>} Query result
80
+ */
81
+ async query(sql, params = [], options = {}) {
82
+ if (!this.isConnected) {
83
+ await this.connect();
84
+ }
85
+
86
+ try {
87
+ const startTime = Date.now();
88
+ const result = await this.driver.query(this.connection, sql, params, options);
89
+ const duration = Date.now() - startTime;
90
+
91
+ this.emit('query:executed', {
92
+ sql,
93
+ params,
94
+ duration,
95
+ result,
96
+ options
97
+ });
98
+
99
+ return result;
100
+ } catch (error) {
101
+ this.emit('query:error', { sql, params, error, options });
102
+
103
+ // Check if connection is still alive
104
+ if (error.code === 'ECONNRESET' || error.code === 'PROTOCOL_CONNECTION_LOST') {
105
+ console.warn('⚠️ Connection lost, attempting to reconnect...');
106
+ this.isConnected = false;
107
+ await this.connect();
108
+ return this.query(sql, params, options);
109
+ }
110
+
111
+ throw error;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Execute transaction
117
+ * @param {Function} callback - Transaction callback
118
+ * @returns {Promise<*>} Transaction result
119
+ */
120
+ async transaction(callback) {
121
+ if (!this.isConnected) {
122
+ await this.connect();
123
+ }
124
+
125
+ if (!this.driver.beginTransaction) {
126
+ throw new Error(`Transaction not supported for ${this.config.type} driver`);
127
+ }
128
+
129
+ try {
130
+ await this.driver.beginTransaction(this.connection);
131
+ this.emit('transaction:begin');
132
+
133
+ const result = await callback({
134
+ query: (sql, params) => this.query(sql, params),
135
+ execute: (sql, params) => this.execute(sql, params)
136
+ });
137
+
138
+ await this.driver.commitTransaction(this.connection);
139
+ this.emit('transaction:commit', { result });
140
+
141
+ return result;
142
+ } catch (error) {
143
+ await this.driver.rollbackTransaction(this.connection);
144
+ this.emit('transaction:rollback', { error });
145
+ throw error;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Execute SQL statement (for INSERT, UPDATE, DELETE)
151
+ * @param {string} sql - SQL statement
152
+ * @param {Array} params - Query parameters
153
+ * @param {Object} options - Query options
154
+ * @returns {Promise<Object>} Execution result
155
+ */
156
+ async execute(sql, params = [], options = {}) {
157
+ if (!this.isConnected) {
158
+ await this.connect();
159
+ }
160
+
161
+ try {
162
+ const startTime = Date.now();
163
+ const result = await this.driver.execute(this.connection, sql, params, options);
164
+ const duration = Date.now() - startTime;
165
+
166
+ this.emit('execute:completed', {
167
+ sql,
168
+ params,
169
+ duration,
170
+ result,
171
+ options
172
+ });
173
+
174
+ return result;
175
+ } catch (error) {
176
+ this.emit('execute:error', { sql, params, error, options });
177
+ throw error;
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Close connection
183
+ * @returns {Promise<void>}
184
+ */
185
+ async close() {
186
+ if (!this.isConnected) {
187
+ return;
188
+ }
189
+
190
+ try {
191
+ if (this.pool && this.driver.closePool) {
192
+ await this.driver.closePool(this.pool);
193
+ } else if (this.driver.close) {
194
+ await this.driver.close(this.connection);
195
+ }
196
+
197
+ this.isConnected = false;
198
+ this.connection = null;
199
+ this.pool = null;
200
+ this.emit('closed');
201
+ } catch (error) {
202
+ this.emit('close:error', { error });
203
+ throw error;
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Get connection status
209
+ * @returns {Object} Connection status
210
+ */
211
+ getStatus() {
212
+ return {
213
+ type: this.config.type,
214
+ isConnected: this.isConnected,
215
+ retryCount: this.retryCount,
216
+ maxRetries: this.maxRetries,
217
+ hasPool: !!this.pool,
218
+ config: {
219
+ host: this.config.host,
220
+ port: this.config.port,
221
+ database: this.config.database,
222
+ user: this.config.user
223
+ }
224
+ };
225
+ }
226
+
227
+ /**
228
+ * Health check
229
+ * @returns {Promise<Object>} Health check result
230
+ */
231
+ async healthCheck() {
232
+ if (!this.isConnected) {
233
+ return {
234
+ status: 'disconnected',
235
+ message: 'Not connected to database',
236
+ timestamp: new Date().toISOString()
237
+ };
238
+ }
239
+
240
+ try {
241
+ const startTime = Date.now();
242
+
243
+ // Try a simple query to check connection health
244
+ const healthQuery = this.getHealthQuery();
245
+ await this.driver.query(this.connection, healthQuery.sql, healthQuery.params);
246
+ const duration = Date.now() - startTime;
247
+
248
+ return {
249
+ status: 'healthy',
250
+ type: this.config.type,
251
+ duration: `${duration}ms`,
252
+ timestamp: new Date().toISOString()
253
+ };
254
+ } catch (error) {
255
+ return {
256
+ status: 'unhealthy',
257
+ type: this.config.type,
258
+ error: error.message,
259
+ timestamp: new Date().toISOString()
260
+ };
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Get health check query based on database type
266
+ * @returns {Object} Health check query
267
+ */
268
+ getHealthQuery() {
269
+ const queries = {
270
+ mysql: { sql: 'SELECT 1', params: [] },
271
+ postgresql: { sql: 'SELECT 1', params: [] },
272
+ sqlite: { sql: 'SELECT 1', params: [] },
273
+ mongodb: { sql: 'db.stats()', params: [] },
274
+ redis: { sql: 'PING', params: [] },
275
+ mssql: { sql: 'SELECT 1', params: [] },
276
+ oracle: { sql: 'SELECT 1 FROM DUAL', params: [] }
277
+ };
278
+
279
+ return queries[this.config.type] || { sql: 'SELECT 1', params: [] };
280
+ }
281
+
282
+ /**
283
+ * Get connection pool stats (if using pooling)
284
+ * @returns {Object|null} Pool statistics
285
+ */
286
+ getPoolStats() {
287
+ if (!this.pool || !this.driver.getPoolStats) {
288
+ return null;
289
+ }
290
+
291
+ return this.driver.getPoolStats(this.pool);
292
+ }
293
+
294
+ /**
295
+ * Reconnect to database
296
+ * @returns {Promise<ConnectionManager>} Reconnected instance
297
+ */
298
+ async reconnect() {
299
+
300
+ try {
301
+ await this.close();
302
+ await this.connect();
303
+ return this;
304
+ } catch (error) {
305
+ this.emit('reconnect:error', { error });
306
+ throw error;
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Ping database connection
312
+ * @returns {Promise<boolean>} True if ping successful
313
+ */
314
+ async ping() {
315
+ if (!this.isConnected) {
316
+ return false;
317
+ }
318
+
319
+ try {
320
+ const health = await this.healthCheck();
321
+ return health.status === 'healthy';
322
+ } catch (error) {
323
+ return false;
324
+ }
325
+ }
326
+ getDriver() {
327
+ return this.driver;
328
+ }
329
+ getType() {
330
+ return this.type;
331
+ }
332
+ /**
333
+ * Get connection metrics
334
+ * @returns {Object} Connection metrics
335
+ */
336
+ getMetrics() {
337
+ return {
338
+ type: this.config.type,
339
+ isConnected: this.isConnected,
340
+ retryCount: this.retryCount,
341
+ connectionTime: this.connectionTime,
342
+ lastQueryTime: this.lastQueryTime,
343
+ totalQueries: this.totalQueries || 0,
344
+ failedQueries: this.failedQueries || 0,
345
+ poolStats: this.getPoolStats(),
346
+ timestamp: new Date().toISOString()
347
+ };
348
+ }
349
+ }
350
+
351
+ export default ConnectionManager;
@@ -0,0 +1,188 @@
1
+ /**
2
+ * @license MIT
3
+ * Copyright (c) 2026-present AetherFramework Contributors.
4
+ * SPDX-License-Identifier: MIT
5
+ * @module @aetherframework/database/core/DatabaseFactory
6
+ */
7
+
8
+ import mysqlDriver from '../drivers/mysql-driver.js';
9
+ import postgresDriver from '../drivers/postgres-driver.js';
10
+ import sqliteDriver from '../drivers/sqlite-driver.js';
11
+ import mongodbDriver from '../drivers/mongodb-driver.js';
12
+ import redisDriver from '../drivers/redis-driver.js';
13
+ import mssqlDriver from '../drivers/mssql-driver.js';
14
+ import oracleDriver from '../drivers/oracle-driver.js';
15
+
16
+ /**
17
+ * Database Factory - Core factory for creating database driver instances
18
+ * Supports both built-in and external drivers with plugin architecture
19
+ */
20
+ class DatabaseFactory {
21
+ constructor() {
22
+ this.drivers = new Map();
23
+ this.externalDrivers = new Map();
24
+ this.initializeBuiltInDrivers();
25
+ }
26
+
27
+ /**
28
+ * Initialize built-in database drivers
29
+ */
30
+ initializeBuiltInDrivers() {
31
+ const builtInDrivers = {
32
+ 'mysql': mysqlDriver,
33
+ 'mariadb': mysqlDriver,
34
+ 'postgresql': postgresDriver,
35
+ 'postgres': postgresDriver,
36
+ 'pg': postgresDriver,
37
+ 'sqlite': sqliteDriver,
38
+ 'sqlite3': sqliteDriver,
39
+ 'mongodb': mongodbDriver,
40
+ 'mongo': mongodbDriver,
41
+ 'redis': redisDriver,
42
+ 'mssql': mssqlDriver,
43
+ 'sqlserver': mssqlDriver,
44
+ 'oracle': oracleDriver
45
+ };
46
+
47
+ for (const [name, DriverClass] of Object.entries(builtInDrivers)) {
48
+ this.registerDriver(name, DriverClass);
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Register a new driver
54
+ * @param {string} name - Driver name
55
+ * @param {Class} DriverClass - Driver class
56
+ */
57
+ registerDriver(name, DriverClass) {
58
+ this.drivers.set(name.toLowerCase(), DriverClass);
59
+ }
60
+
61
+ /**
62
+ * Register external driver
63
+ * @param {string} name - Driver name
64
+ * @param {Class} DriverClass - Driver class
65
+ */
66
+ registerExternalDriver(name, DriverClass) {
67
+ this.externalDrivers.set(name.toLowerCase(), DriverClass);
68
+ }
69
+
70
+ /**
71
+ * Load driver dynamically
72
+ * @param {string} driverName - Driver name
73
+ * @returns {Promise<Class>} Driver class
74
+ */
75
+ async loadDriver(driverName) {
76
+ const normalizedName = driverName.toLowerCase();
77
+
78
+ // Check if driver is already loaded
79
+ if (this.drivers.has(normalizedName)) {
80
+ return this.drivers.get(normalizedName);
81
+ }
82
+
83
+ // Check if it's an external driver
84
+ if (this.externalDrivers.has(normalizedName)) {
85
+ return this.externalDrivers.get(normalizedName);
86
+ }
87
+
88
+ // Try to load from external drivers directory
89
+ try {
90
+ const driverModule = await import(`../drivers/${normalizedName}-driver.js`);
91
+ if (driverModule.default && typeof driverModule.default === 'function') {
92
+ this.registerDriver(normalizedName, driverModule.default);
93
+ return driverModule.default;
94
+ }
95
+ } catch (error) {
96
+ // Driver not found in drivers directory
97
+ }
98
+
99
+ throw new Error(`Unsupported database driver: ${driverName}`);
100
+ }
101
+
102
+ /**
103
+ * Create driver instance
104
+ * @param {string} driverName - Driver name
105
+ * @param {Object} config - Driver configuration
106
+ * @returns {Object} Driver instance
107
+ */
108
+ createDriver(driverName, config) {
109
+ const normalizedName = driverName.toLowerCase();
110
+
111
+ // Check built-in drivers first
112
+ if (this.drivers.has(normalizedName)) {
113
+ const DriverClass = this.drivers.get(normalizedName);
114
+ return new DriverClass(config);
115
+ }
116
+
117
+ // Check external drivers
118
+ if (this.externalDrivers.has(normalizedName)) {
119
+ const DriverClass = this.externalDrivers.get(normalizedName);
120
+ return new DriverClass(config);
121
+ }
122
+
123
+ throw new Error(`Driver not found: ${driverName}`);
124
+ }
125
+
126
+ /**
127
+ * Get available driver names
128
+ * @returns {Array} List of available driver names
129
+ */
130
+ getAvailableDrivers() {
131
+ const builtInDrivers = Array.from(this.drivers.keys());
132
+ const externalDrivers = Array.from(this.externalDrivers.keys());
133
+ return [...builtInDrivers, ...externalDrivers];
134
+ }
135
+
136
+ /**
137
+ * Check if driver exists
138
+ * @param {string} driverName - Driver name
139
+ * @returns {boolean} True if driver exists
140
+ */
141
+ hasDriver(driverName) {
142
+ const normalizedName = driverName.toLowerCase();
143
+ return this.drivers.has(normalizedName) || this.externalDrivers.has(normalizedName);
144
+ }
145
+
146
+ /**
147
+ * Remove driver
148
+ * @param {string} driverName - Driver name
149
+ */
150
+ removeDriver(driverName) {
151
+ const normalizedName = driverName.toLowerCase();
152
+ if (this.drivers.has(normalizedName)) {
153
+ this.drivers.delete(normalizedName);
154
+ }
155
+ if (this.externalDrivers.has(normalizedName)) {
156
+ this.externalDrivers.delete(normalizedName);
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Clear all drivers
162
+ */
163
+ clearDrivers() {
164
+ this.drivers.clear();
165
+ this.externalDrivers.clear();
166
+ }
167
+
168
+ /**
169
+ * Get driver info
170
+ * @param {string} driverName - Driver name
171
+ * @returns {Object} Driver information
172
+ */
173
+ getDriverInfo(driverName) {
174
+ const normalizedName = driverName.toLowerCase();
175
+ const isBuiltIn = this.drivers.has(normalizedName);
176
+ const isExternal = this.externalDrivers.has(normalizedName);
177
+
178
+ return {
179
+ name: driverName,
180
+ isBuiltIn,
181
+ isExternal,
182
+ exists: isBuiltIn || isExternal,
183
+ type: isBuiltIn ? 'built-in' : isExternal ? 'external' : 'not-found'
184
+ };
185
+ }
186
+ }
187
+
188
+ export default DatabaseFactory;