@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,543 @@
1
+ /**
2
+ * @license MIT
3
+ * Copyright (c) 2026-present AetherFramework Contributors.
4
+ * SPDX-License-Identifier: MIT
5
+ * @module @aetherframework/database/plugin/DistributedPlugin
6
+ */
7
+ import { BasePlugin } from "./BasePlugin.js";
8
+
9
+ /**
10
+ * Distributed feature plugin for QueryBuilder
11
+ * Provides distributed system capabilities: connection pool management, read-write splitting, service discovery integration
12
+ *
13
+ * @class DistributedPlugin
14
+ * @extends {BasePlugin}
15
+ * @description Plugin for handling distributed database operations including connection pooling,
16
+ * read-write splitting, shard routing, distributed locking, and cloud-native configuration
17
+ */
18
+ export class DistributedPlugin extends BasePlugin {
19
+ /**
20
+ * Plugin name identifier
21
+ * @type {string}
22
+ * @static
23
+ */
24
+ static name = 'distributed';
25
+
26
+ /**
27
+ * Supported distributed features
28
+ * @type {string[]}
29
+ * @static
30
+ */
31
+ static supportedFeatures = ['connectionPool', 'readWriteSplit', 'shardRouting', 'distributedLock', 'configCenter'];
32
+
33
+ /**
34
+ * Create a distributed plugin instance
35
+ * @param {Object} queryBuilder - QueryBuilder instance to attach to
36
+ * @param {Object} options - Plugin configuration options
37
+ * @param {Object} [options.connectionPool=null] - External connection pool instance
38
+ * @param {Object} [options.readWriteSplit=null] - Read-write split configuration
39
+ * @param {Object} [options.shardRouting=null] - Shard routing configuration
40
+ * @param {Object} [options.distributedLock=null] - Distributed lock client (Redis/ZooKeeper)
41
+ * @param {Object} [options.configCenter=null] - Cloud native config center client
42
+ */
43
+ constructor(queryBuilder, options = {}) {
44
+ super(queryBuilder);
45
+ this.pluginName = "DistributedPlugin";
46
+ this.connectionPool = options.connectionPool || null;
47
+ this.readWriteSplit = options.readWriteSplit || null;
48
+ this.shardRouting = options.shardRouting || null;
49
+ this.distributedLockClient = options.distributedLock || null;
50
+ this.configCenter = options.configCenter || null;
51
+ this.primaryConnection = null;
52
+ this.replicaConnections = [];
53
+ this._roundRobinIndex = 0;
54
+ }
55
+
56
+ /**
57
+ * Register plugin methods to QueryBuilder
58
+ * @protected
59
+ * @override
60
+ */
61
+ _registerMethods() {
62
+ // Register distributed methods to QueryBuilder
63
+ this.methods = {
64
+ executeWithPool: this.executeWithPool.bind(this),
65
+ executeWithDistributedLock: this.executeWithDistributedLock.bind(this),
66
+ routeToShard: this.routeToShard.bind(this),
67
+ getReadConnection: this.getReadConnection.bind(this),
68
+ isReadOnlyQuery: this.isReadOnlyQuery.bind(this),
69
+ getDistributedHealthStatus: this.getHealthStatus.bind(this),
70
+ updateDistributedConfig: this.updateConfig.bind(this)
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Register hooks for the plugin
76
+ * @protected
77
+ * @override
78
+ */
79
+ _registerHooks() {
80
+ // Register query execution hooks for automatic read-write splitting
81
+ this.registerHook('beforeExecute', async (sql, bindings) => {
82
+ // Automatically route read-only queries to replica connections
83
+ if (this.readWriteSplit && this.isReadOnlyQuery()) {
84
+ const readConn = this.getReadConnection();
85
+ if (readConn && this.queryBuilder.connection !== readConn) {
86
+ const oldConn = this.queryBuilder.connection;
87
+ this.queryBuilder.connection = readConn;
88
+ return () => {
89
+ // Cleanup hook: restore original connection
90
+ this.queryBuilder.connection = oldConn;
91
+ };
92
+ }
93
+ }
94
+ return null;
95
+ }, { priority: 100 });
96
+
97
+ // Register after execution hook for metrics collection
98
+ this.registerHook('afterExecute', async (result) => {
99
+ if (this.configCenter) {
100
+ await this._recordMetrics(result);
101
+ }
102
+ return result;
103
+ }, { priority: 50 });
104
+ }
105
+
106
+ /**
107
+ * Register middlewares for the plugin
108
+ * @protected
109
+ * @override
110
+ */
111
+ _registerMiddlewares() {
112
+ // Register connection pool middleware
113
+ this.registerMiddleware('connection', async (context, next) => {
114
+ if (this.connectionPool && context.usePool !== false) {
115
+ const connection = await this.connectionPool.getConnection();
116
+ try {
117
+ context.connection = connection;
118
+ const result = await next();
119
+ return result;
120
+ } finally {
121
+ this.connectionPool.release(connection);
122
+ }
123
+ }
124
+ return await next();
125
+ }, { priority: 100 });
126
+ }
127
+
128
+ /**
129
+ * Initialize the plugin
130
+ * @async
131
+ * @returns {Promise<void>}
132
+ * @override
133
+ */
134
+ async init() {
135
+ await super.init();
136
+
137
+ if (this.connectionPool) {
138
+ this._setupConnectionPool();
139
+ }
140
+
141
+ if (this.readWriteSplit) {
142
+ this._setupReadWriteSplit();
143
+ }
144
+
145
+ if (this.configCenter) {
146
+ this._watchConfigChanges();
147
+ }
148
+
149
+
150
+ }
151
+
152
+ /**
153
+ * Use connection pool to get connection for execution
154
+ * @async
155
+ * @param {QueryBuilder} qb - Query builder instance
156
+ * @returns {Promise<any>} Query execution result
157
+ */
158
+ async executeWithPool(qb) {
159
+ const connection = await this.connectionPool.getConnection();
160
+ try {
161
+ qb.connection = connection;
162
+ const result = await qb.execute();
163
+ return result;
164
+ } finally {
165
+ this.connectionPool.release(connection);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Execute query with distributed lock protection
171
+ * @async
172
+ * @param {string} lockKey - Unique lock key
173
+ * @param {number} [timeout=5000] - Lock acquire timeout in milliseconds
174
+ * @returns {Promise<any>} Query execution result
175
+ * @throws {Error} If distributed lock client not configured
176
+ */
177
+ async executeWithDistributedLock(lockKey, timeout = 5000) {
178
+ if (!this.distributedLockClient) {
179
+ throw new Error('Distributed lock client not configured. Please initialize plugin with distributedLock option.');
180
+ }
181
+
182
+ const lock = await this.distributedLockClient.acquire(lockKey, timeout);
183
+ try {
184
+ return await this.queryBuilder.execute();
185
+ } finally {
186
+ await lock.release();
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Route query to specific shard by shard key
192
+ * @param {string|number} shardKey - Sharding key value
193
+ * @returns {QueryBuilder} Current query builder instance with correct connection
194
+ * @throws {Error} If shard routing not configured
195
+ */
196
+ routeToShard(shardKey) {
197
+ if (!this.shardRouting) {
198
+ throw new Error('Shard routing not configured. Please initialize plugin with shardRouting option.');
199
+ }
200
+
201
+ const shard = this._calculateShard(shardKey);
202
+ this.queryBuilder.connection = shard.connection;
203
+ this.queryBuilder.tableName = shard.getTableName(this.queryBuilder.originalTableName, shardKey);
204
+ return this.queryBuilder;
205
+ }
206
+
207
+ /**
208
+ * Get read connection based on read-write split strategy
209
+ * @returns {Object} Read connection
210
+ */
211
+ getReadConnection() {
212
+ if (!this.readWriteSplit || this.replicaConnections.length === 0) {
213
+ return this.primaryConnection;
214
+ }
215
+
216
+ return this._selectReplicaByStrategy();
217
+ }
218
+
219
+ /**
220
+ * Check if current query is read-only operation
221
+ * @returns {boolean} True if query is read-only
222
+ */
223
+ isReadOnlyQuery() {
224
+ const queryType = this.queryBuilder.query.type;
225
+ return ['select', 'exists', 'count'].includes(queryType);
226
+ }
227
+
228
+ /**
229
+ * Setup connection pool integration
230
+ * @returns {void}
231
+ * @private
232
+ */
233
+ _setupConnectionPool() {
234
+ // Validate connection pool interface
235
+ if (typeof this.connectionPool.getConnection !== 'function' ||
236
+ typeof this.connectionPool.release !== 'function') {
237
+ throw new Error('Connection pool must implement getConnection and release methods');
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Setup read-write split connections
243
+ * @returns {void}
244
+ * @private
245
+ */
246
+ _setupReadWriteSplit() {
247
+ this.primaryConnection = this.readWriteSplit.primary;
248
+ this.replicaConnections = this.readWriteSplit.replicas || [];
249
+ }
250
+
251
+ /**
252
+ * Watch dynamic configuration changes from config center
253
+ * @returns {void}
254
+ * @private
255
+ */
256
+ _watchConfigChanges() {
257
+ if (!this.configCenter || !this.configCenter.watch) {
258
+ return;
259
+ }
260
+
261
+ // Watch pool size changes
262
+ this.configCenter.watch('database.pool.size', (newSize) => {
263
+ if (this.connectionPool && this.connectionPool.resize) {
264
+ this.connectionPool.resize(newSize);
265
+ }
266
+ });
267
+
268
+ // Watch replica node changes
269
+ this.configCenter.watch('database.readWriteSplit.replicas', (newReplicas) => {
270
+ this.replicaConnections = newReplicas;
271
+ });
272
+ }
273
+
274
+ /**
275
+ * Calculate target shard by shard key using consistent hashing
276
+ * @param {string|number} shardKey - Sharding key
277
+ * @returns {Object} Target shard configuration
278
+ * @private
279
+ */
280
+ _calculateShard(shardKey) {
281
+ const shards = this.shardRouting.shards;
282
+ const hash = this._hashString(String(shardKey));
283
+ const index = hash % shards.length;
284
+ return shards[index];
285
+ }
286
+
287
+ /**
288
+ * Select replica connection based on load balancing strategy
289
+ * @returns {Object} Selected replica connection
290
+ * @private
291
+ */
292
+ _selectReplicaByStrategy() {
293
+ const strategy = this.readWriteSplit.strategy || 'round-robin';
294
+
295
+ switch (strategy) {
296
+ case 'round-robin':
297
+ const index = this._roundRobinIndex++ % this.replicaConnections.length;
298
+ return this.replicaConnections[index];
299
+
300
+ case 'random':
301
+ const randomIndex = Math.floor(Math.random() * this.replicaConnections.length);
302
+ return this.replicaConnections[randomIndex];
303
+
304
+ case 'least-connected':
305
+ return this.replicaConnections
306
+ .sort((a, b) => a.activeConnections - b.activeConnections);
307
+
308
+ default:
309
+ return this.replicaConnections;
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Simple string hash function for consistent hashing
315
+ * @param {string} str - Input string
316
+ * @returns {number} Hash value
317
+ * @private
318
+ */
319
+ _hashString(str) {
320
+ let hash = 0;
321
+ for (let i = 0; i < str.length; i++) {
322
+ hash = ((hash << 5) - hash) + str.charCodeAt(i);
323
+ hash |= 0; // Convert to 32bit integer
324
+ }
325
+ return Math.abs(hash);
326
+ }
327
+
328
+ /**
329
+ * Record metrics to config center
330
+ * @param {Object} result - Query execution result
331
+ * @returns {Promise<void>}
332
+ * @private
333
+ */
334
+ async _recordMetrics(result) {
335
+ if (!this.configCenter || !this.configCenter.recordMetric) {
336
+ return;
337
+ }
338
+
339
+ const metrics = {
340
+ timestamp: Date.now(),
341
+ queryType: this.queryBuilder.query.type,
342
+ shardKey: this.shardRouting ? 'calculated' : 'none',
343
+ readWrite: this.readWriteSplit ? 'split' : 'single',
344
+ connectionPool: !!this.connectionPool
345
+ };
346
+
347
+ await this.configCenter.recordMetric('distributed_query', metrics);
348
+ }
349
+
350
+ /**
351
+ * Update plugin configuration dynamically
352
+ * @param {Object} newConfig - New configuration
353
+ * @returns {Promise<void>}
354
+ */
355
+ async updateConfig(newConfig) {
356
+ // Cleanup old configuration
357
+ await this.cleanup();
358
+
359
+ // Update configuration
360
+ Object.assign(this.config, newConfig);
361
+
362
+ if (newConfig.connectionPool) {
363
+ this.connectionPool = newConfig.connectionPool;
364
+ }
365
+
366
+ if (newConfig.readWriteSplit) {
367
+ this.readWriteSplit = newConfig.readWriteSplit;
368
+ this._setupReadWriteSplit();
369
+ }
370
+
371
+ if (newConfig.shardRouting) {
372
+ this.shardRouting = newConfig.shardRouting;
373
+ }
374
+
375
+ if (newConfig.distributedLock) {
376
+ this.distributedLockClient = newConfig.distributedLock;
377
+ }
378
+
379
+ if (newConfig.configCenter) {
380
+ this.configCenter = newConfig.configCenter;
381
+ this._watchConfigChanges();
382
+ }
383
+
384
+ // Re-initialize
385
+ await this.init();
386
+ }
387
+
388
+ /**
389
+ * Get plugin health status for cloud native monitoring
390
+ * @returns {Promise<Object>} Health status object
391
+ */
392
+ async getHealthStatus() {
393
+ const status = {
394
+ healthy: true,
395
+ features: {
396
+ connectionPool: !!this.connectionPool,
397
+ readWriteSplit: !!this.readWriteSplit,
398
+ shardRouting: !!this.shardRouting,
399
+ distributedLock: !!this.distributedLockClient,
400
+ configCenter: !!this.configCenter
401
+ },
402
+ timestamp: Date.now()
403
+ };
404
+
405
+ if (this.connectionPool) {
406
+ try {
407
+ status.poolStats = await this.connectionPool.getStats();
408
+ } catch (error) {
409
+ status.poolStats = { error: error.message };
410
+ status.healthy = false;
411
+ }
412
+ }
413
+
414
+ return status;
415
+ }
416
+
417
+ /**
418
+ * Cleanup resources when plugin is destroyed
419
+ * @returns {Promise<void>}
420
+ * @override
421
+ */
422
+ async cleanup() {
423
+ // Cleanup config center watchers
424
+ if (this.configCenter && this.configCenter.unwatch) {
425
+ this.configCenter.unwatch('database.pool.size');
426
+ this.configCenter.unwatch('database.readWriteSplit.replicas');
427
+ }
428
+
429
+ // Cleanup connection pool
430
+ if (this.connectionPool) {
431
+ await this.connectionPool.end();
432
+ }
433
+
434
+ // Call parent cleanup
435
+ await super.cleanup();
436
+
437
+
438
+ }
439
+
440
+ /**
441
+ * Get plugin metadata
442
+ * @returns {Object} Plugin metadata
443
+ * @override
444
+ */
445
+ getMetadata() {
446
+ const baseMetadata = super.getMetadata();
447
+ return {
448
+ ...baseMetadata,
449
+ name: DistributedPlugin.name,
450
+ description: 'Distributed database operations plugin with connection pooling, read-write splitting, sharding, and distributed locking',
451
+ version: '1.0.0',
452
+ features: DistributedPlugin.supportedFeatures,
453
+ dependencies: ['BasePlugin']
454
+ };
455
+ }
456
+
457
+ /**
458
+ * Validate plugin configuration
459
+ * @param {Object} config - Plugin configuration
460
+ * @returns {Object} Validation result
461
+ * @override
462
+ */
463
+ validateConfig(config) {
464
+ const baseValidation = super.validateConfig(config);
465
+ const errors = [...baseValidation.errors];
466
+ const warnings = [...baseValidation.warnings];
467
+
468
+ // Validate connection pool configuration
469
+ if (config.connectionPool) {
470
+ if (typeof config.connectionPool.getConnection !== 'function') {
471
+ errors.push('connectionPool must have getConnection method');
472
+ }
473
+ if (typeof config.connectionPool.release !== 'function') {
474
+ errors.push('connectionPool must have release method');
475
+ }
476
+ }
477
+
478
+ // Validate read-write split configuration
479
+ if (config.readWriteSplit) {
480
+ if (!config.readWriteSplit.primary) {
481
+ errors.push('readWriteSplit must have primary connection');
482
+ }
483
+ if (!Array.isArray(config.readWriteSplit.replicas)) {
484
+ warnings.push('readWriteSplit.replicas should be an array');
485
+ }
486
+ }
487
+
488
+ // Validate sharding configuration
489
+ if (config.shardRouting) {
490
+ if (!Array.isArray(config.shardRouting.shards) || config.shardRouting.shards.length === 0) {
491
+ errors.push('shardRouting.shards must be a non-empty array');
492
+ }
493
+ }
494
+
495
+ // Validate distributed lock configuration
496
+ if (config.distributedLock) {
497
+ if (typeof config.distributedLock.acquire !== 'function') {
498
+ errors.push('distributedLock must have acquire method');
499
+ }
500
+ }
501
+
502
+ return {
503
+ valid: errors.length === 0,
504
+ errors,
505
+ warnings
506
+ };
507
+ }
508
+
509
+ /**
510
+ * Get plugin configuration
511
+ * @returns {Object} Plugin configuration
512
+ * @override
513
+ */
514
+ getConfig() {
515
+ const baseConfig = super.getConfig();
516
+ return {
517
+ ...baseConfig,
518
+ connectionPool: !!this.connectionPool,
519
+ readWriteSplit: !!this.readWriteSplit,
520
+ shardRouting: !!this.shardRouting,
521
+ distributedLock: !!this.distributedLockClient,
522
+ configCenter: !!this.configCenter
523
+ };
524
+ }
525
+
526
+ /**
527
+ * Get plugin status
528
+ * @returns {Object} Plugin status information
529
+ * @override
530
+ */
531
+ getStatus() {
532
+ const baseStatus = super.getStatus();
533
+ return {
534
+ ...baseStatus,
535
+ connectionPool: !!this.connectionPool,
536
+ readWriteSplit: !!this.readWriteSplit,
537
+ replicaCount: this.replicaConnections.length,
538
+ shardRouting: !!this.shardRouting,
539
+ distributedLock: !!this.distributedLockClient,
540
+ configCenter: !!this.configCenter
541
+ };
542
+ }
543
+ }