@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,968 @@
1
+ /**
2
+ * @license MIT
3
+ * Copyright (c) 2026-present AetherFramework Contributors.
4
+ * SPDX-License-Identifier: MIT
5
+ * @module @aetherframework/database/core/PluginManager
6
+ */
7
+
8
+ import { EventEmitter } from "events";
9
+
10
+ /**
11
+ * PluginManager - Manages QueryBuilder plugin system
12
+ * Provides plugin registration, loading, unloading, and event management
13
+ */
14
+ export class PluginManager extends EventEmitter {
15
+ constructor(config = {}) {
16
+ super();
17
+ this.config = config;
18
+ this.plugins = new Map(); // Store registered plugin classes
19
+ this.pluginInstances = new Map(); // Store plugin instances
20
+ this.methods = new Map(); // Store plugin methods
21
+ this.hooks = new Map(); // Store hook functions
22
+ this.middlewares = new Map(); // Store middleware functions
23
+ this.initialized = false;
24
+ }
25
+
26
+ /**
27
+ * Initialize plugin manager
28
+ * @param {Object} config - Plugin manager configuration
29
+ * @returns {PluginManager} PluginManager instance
30
+ */
31
+ initialize(config = {}) {
32
+ if (this.initialized) return this;
33
+
34
+ this.config = { ...this.config, ...config };
35
+ this.initialized = true;
36
+
37
+ this.emit("initialized", { config: this.config });
38
+
39
+
40
+ return this;
41
+ }
42
+
43
+ /**
44
+ * Register a plugin
45
+ * @param {string} name - Plugin name
46
+ * @param {Function} PluginClass - Plugin class constructor
47
+ * @param {Object} options - Plugin options
48
+ * @returns {PluginManager} PluginManager instance
49
+ */
50
+ register(name, PluginClass, options = {}) {
51
+ if (this.plugins.has(name)) {
52
+ throw new Error(`Plugin "${name}" is already registered`);
53
+ }
54
+
55
+ // Validate plugin class
56
+ if (!PluginClass || typeof PluginClass !== "function") {
57
+ throw new Error(`Plugin "${name}" must be a class constructor`);
58
+ }
59
+
60
+ // Check if plugin extends BasePlugin
61
+ const pluginProto = PluginClass.prototype;
62
+ if (!pluginProto || typeof pluginProto._registerMethods !== "function") {
63
+ console.warn(`Plugin "${name}" may not extend BasePlugin properly`);
64
+ }
65
+
66
+ // Store plugin class and options
67
+ this.plugins.set(name, {
68
+ class: PluginClass,
69
+ options: {
70
+ name,
71
+ enabled: options.enabled !== false,
72
+ priority: options.priority || 0,
73
+ dependencies: options.dependencies || [],
74
+ config: options.config || {},
75
+ registeredAt: new Date(),
76
+ },
77
+ });
78
+
79
+ this.emit("plugin:registered", { name, PluginClass, options });
80
+
81
+
82
+ return this;
83
+ }
84
+
85
+ /**
86
+ * Enable a plugin
87
+ * @param {string} name - Plugin name
88
+ * @param {Object} options - Plugin options
89
+ * @returns {PluginManager} PluginManager instance
90
+ */
91
+ enable(name, options = {}) {
92
+ const pluginInfo = this.plugins.get(name);
93
+ if (!pluginInfo) {
94
+ throw new Error(`Plugin "${name}" not found`);
95
+ }
96
+
97
+ if (pluginInfo.options.enabled) {
98
+ return this;
99
+ }
100
+
101
+ // Check dependencies
102
+ this._checkDependencies(name);
103
+
104
+ pluginInfo.options.enabled = true;
105
+ pluginInfo.options.config = { ...pluginInfo.options.config, ...options };
106
+
107
+ this.emit("plugin:enabled", { name, options: pluginInfo.options });
108
+
109
+
110
+ return this;
111
+ }
112
+
113
+ /**
114
+ * Disable a plugin
115
+ * @param {string} name - Plugin name
116
+ * @returns {PluginManager} PluginManager instance
117
+ */
118
+ disable(name) {
119
+ const pluginInfo = this.plugins.get(name);
120
+ if (!pluginInfo) {
121
+ throw new Error(`Plugin "${name}" not found`);
122
+ }
123
+
124
+ if (!pluginInfo.options.enabled) {
125
+ return this;
126
+ }
127
+
128
+ pluginInfo.options.enabled = false;
129
+
130
+ // Cleanup plugin instance if it exists
131
+ if (this.pluginInstances.has(name)) {
132
+ const instance = this.pluginInstances.get(name);
133
+ if (instance && typeof instance.cleanup === "function") {
134
+ instance.cleanup();
135
+ }
136
+ this.pluginInstances.delete(name);
137
+ }
138
+
139
+ this.emit("plugin:disabled", { name });
140
+
141
+
142
+ return this;
143
+ }
144
+
145
+ /**
146
+ * Unregister a plugin
147
+ * @param {string} name - Plugin name
148
+ * @returns {PluginManager} PluginManager instance
149
+ */
150
+ unregister(name) {
151
+ const pluginInfo = this.plugins.get(name);
152
+ if (!pluginInfo) {
153
+ return this;
154
+ }
155
+
156
+ // Check if other plugins depend on this plugin
157
+ for (const [otherName, otherPlugin] of this.plugins) {
158
+ if (otherName !== name && otherPlugin.options.dependencies.includes(name)) {
159
+ throw new Error(
160
+ `Cannot unregister plugin "${name}" because "${otherName}" depends on it`
161
+ );
162
+ }
163
+ }
164
+
165
+ // Disable first
166
+ this.disable(name);
167
+
168
+ // Remove from registry
169
+ this.plugins.delete(name);
170
+
171
+ this.emit("plugin:unregistered", { name });
172
+
173
+
174
+ return this;
175
+ }
176
+
177
+ /**
178
+ * Check plugin dependencies
179
+ * @param {string} pluginName - Plugin name
180
+ * @private
181
+ */
182
+ _checkDependencies(pluginName) {
183
+ const pluginInfo = this.plugins.get(pluginName);
184
+ if (!pluginInfo) return;
185
+
186
+ const visited = new Set();
187
+ const stack = new Set();
188
+ const missing = [];
189
+ const circular = [];
190
+
191
+ const check = (name, path = []) => {
192
+ if (visited.has(name)) return;
193
+
194
+ if (stack.has(name)) {
195
+ circular.push([...path, name]);
196
+ return;
197
+ }
198
+
199
+ const plugin = this.plugins.get(name);
200
+ if (!plugin) {
201
+ missing.push(name);
202
+ return;
203
+ }
204
+
205
+ stack.add(name);
206
+ path.push(name);
207
+
208
+ for (const dep of plugin.options.dependencies) {
209
+ check(dep, [...path]);
210
+ }
211
+
212
+ stack.delete(name);
213
+ visited.add(name);
214
+ };
215
+
216
+ check(pluginName);
217
+
218
+ if (missing.length > 0) {
219
+ throw new Error(
220
+ `Plugin "${pluginName}" missing dependencies: ${missing.join(", ")}`
221
+ );
222
+ }
223
+
224
+ if (circular.length > 0) {
225
+ throw new Error(
226
+ `Plugin "${pluginName}" has circular dependencies: ${circular
227
+ .map((path) => path.join(" -> "))
228
+ .join(", ")}`
229
+ );
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Get plugin class
235
+ * @param {string} name - Plugin name
236
+ * @returns {Function|null} Plugin class or null
237
+ */
238
+ get(name) {
239
+ const pluginInfo = this.plugins.get(name);
240
+ return pluginInfo ? pluginInfo.class : null;
241
+ }
242
+
243
+ /**
244
+ * Get or create plugin instance for a specific QueryBuilder
245
+ * @param {string} name - Plugin name
246
+ * @param {QueryBuilder} queryBuilder - QueryBuilder instance
247
+ * @returns {BasePlugin|null} Plugin instance or null
248
+ */
249
+ getInstance(name, queryBuilder) {
250
+ // Create a unique key for this plugin instance (plugin name + queryBuilder reference)
251
+ const instanceKey = `${name}_${queryBuilder._instanceId || Date.now()}`;
252
+
253
+ if (this.pluginInstances.has(instanceKey)) {
254
+ return this.pluginInstances.get(instanceKey);
255
+ }
256
+
257
+ const PluginClass = this.get(name);
258
+ if (!PluginClass) return null;
259
+
260
+ const instance = new PluginClass(queryBuilder);
261
+ this.pluginInstances.set(instanceKey, instance);
262
+ return instance;
263
+ }
264
+
265
+ /**
266
+ * Check if plugin exists
267
+ * @param {string} name - Plugin name
268
+ * @returns {boolean} True if plugin exists
269
+ */
270
+ has(name) {
271
+ return this.plugins.has(name);
272
+ }
273
+
274
+ /**
275
+ * Check if plugin is enabled
276
+ * @param {string} name - Plugin name
277
+ * @returns {boolean} True if plugin is enabled
278
+ */
279
+ isEnabled(name) {
280
+ const pluginInfo = this.plugins.get(name);
281
+ return pluginInfo ? pluginInfo.options.enabled : false;
282
+ }
283
+
284
+ /**
285
+ * Get all registered plugins
286
+ * @returns {Array} All registered plugins
287
+ */
288
+ getAll() {
289
+ return Array.from(this.plugins.values()).map((info) => ({
290
+ name: info.options.name,
291
+ class: info.class,
292
+ options: info.options,
293
+ }));
294
+ }
295
+
296
+ /**
297
+ * Get enabled plugins
298
+ * @returns {Array} Enabled plugins
299
+ */
300
+ getEnabled() {
301
+ return Array.from(this.plugins.values())
302
+ .filter((info) => info.options.enabled)
303
+ .map((info) => info.class);
304
+ }
305
+
306
+ /**
307
+ * Get enabled plugin names mapping
308
+ * @returns {Object} Enabled plugins name mapping
309
+ */
310
+ getEnabledPlugins() {
311
+ const enabledPlugins = {};
312
+ for (const [name, info] of this.plugins) {
313
+ if (info.options.enabled) {
314
+ enabledPlugins[name] = info.class;
315
+ }
316
+ }
317
+ return enabledPlugins;
318
+ }
319
+
320
+ /**
321
+ * Get plugin information
322
+ * @param {string} name - Plugin name
323
+ * @returns {Object|null} Plugin information or null
324
+ */
325
+ getPluginInfo(name) {
326
+ const pluginInfo = this.plugins.get(name);
327
+ if (!pluginInfo) return null;
328
+
329
+ const PluginClass = pluginInfo.class;
330
+ const proto = PluginClass.prototype;
331
+ const methods = [];
332
+
333
+ // Get all methods from prototype
334
+ for (const key of Object.getOwnPropertyNames(proto)) {
335
+ if (key !== "constructor" && typeof proto[key] === "function") {
336
+ methods.push(key);
337
+ }
338
+ }
339
+
340
+ return {
341
+ name: pluginInfo.options.name,
342
+ version: pluginInfo.options.version || "1.0.0",
343
+ enabled: pluginInfo.options.enabled,
344
+ priority: pluginInfo.options.priority,
345
+ dependencies: pluginInfo.options.dependencies,
346
+ config: pluginInfo.options.config,
347
+ registeredAt: pluginInfo.options.registeredAt,
348
+ methods: methods,
349
+ };
350
+ }
351
+
352
+ /**
353
+ * Initialize all plugins for a specific QueryBuilder
354
+ * @param {QueryBuilder} queryBuilder - QueryBuilder instance
355
+ * @returns {Promise<void>}
356
+ */
357
+ async initializePluginsForQueryBuilder(queryBuilder) {
358
+ if (!this.initialized) {
359
+ throw new Error("PluginManager must be initialized first");
360
+ }
361
+
362
+ if (!queryBuilder) {
363
+ throw new Error("QueryBuilder is required for plugin initialization");
364
+ }
365
+
366
+ const enabledPlugins = this.getEnabled();
367
+
368
+ for (const PluginClass of enabledPlugins) {
369
+ try {
370
+ // Create plugin instance with QueryBuilder
371
+ const pluginInstance = new PluginClass(queryBuilder);
372
+
373
+ // Initialize the plugin
374
+ await pluginInstance.init();
375
+
376
+ // Store plugin instance with unique key
377
+ const pluginName = pluginInstance.pluginName || PluginClass.name;
378
+ const instanceKey = `${pluginName}_${queryBuilder._instanceId || Date.now()}`;
379
+ this.pluginInstances.set(instanceKey, pluginInstance);
380
+
381
+ } catch (error) {
382
+
383
+ throw error;
384
+ }
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Initialize all registered plugins (without QueryBuilder)
390
+ * This method only registers plugins, doesn't create instances
391
+ * @returns {Promise<void>}
392
+ */
393
+ async initializeAll() {
394
+ if (!this.initialized) {
395
+ throw new Error("PluginManager must be initialized first");
396
+ }
397
+
398
+ const enabledPlugins = this.getEnabled();
399
+
400
+ for (const PluginClass of enabledPlugins) {
401
+ try {
402
+ // Just log registration, don't create instances
403
+ const pluginName = PluginClass.prototype.pluginName || PluginClass.name;
404
+ } catch (error) {
405
+ throw error;
406
+ }
407
+ }
408
+ }
409
+ /**
410
+ * Get plugin configuration
411
+ * @param {string} pluginName - Plugin name
412
+ * @returns {Object|null} Plugin configuration or null if not found
413
+ */
414
+ getPluginConfig(pluginName) {
415
+ const plugin = this.plugins.get(pluginName);
416
+ return plugin ? plugin.config : null;
417
+ }
418
+ /**
419
+ * Register method to QueryBuilder
420
+ * @param {string} name - Method name
421
+ * @param {Function} method - Method function
422
+ * @param {Object} options - Method options
423
+ * @returns {PluginManager} PluginManager instance
424
+ */
425
+ registerMethod(name, method, options = {}) {
426
+ if (typeof method !== "function") {
427
+ throw new Error(`Method "${name}" must be a function`);
428
+ }
429
+
430
+ // Check if method already exists
431
+ if (this.methods.has(name)) {
432
+ if (options.override) {
433
+ console.warn(`Method "${name}" will be overridden`);
434
+ } else {
435
+ throw new Error(`Method "${name}" already exists`);
436
+ }
437
+ }
438
+
439
+ // Store method
440
+ this.methods.set(name, {
441
+ method,
442
+ plugin: options.plugin,
443
+ description: options.description,
444
+ addedAt: new Date(),
445
+ });
446
+
447
+ this.emit("method:registered", { name, method, options });
448
+ return this;
449
+ }
450
+
451
+ /**
452
+ * Remove method
453
+ * @param {string} name - Method name
454
+ * @returns {PluginManager} PluginManager instance
455
+ */
456
+ unregisterMethod(name) {
457
+ const methodInfo = this.methods.get(name);
458
+ if (!methodInfo) {
459
+ return this;
460
+ }
461
+
462
+ // Remove from storage
463
+ this.methods.delete(name);
464
+
465
+ this.emit("method:unregistered", { name, methodInfo });
466
+ return this;
467
+ }
468
+
469
+ /**
470
+ * Register hook
471
+ * @param {string} event - Event name
472
+ * @param {Function} handler - Handler function
473
+ * @param {Object} options - Hook options
474
+ * @returns {PluginManager} PluginManager instance
475
+ */
476
+ registerHook(event, handler, options = {}) {
477
+ if (typeof handler !== "function") {
478
+ throw new Error(`Hook handler "${event}" must be a function`);
479
+ }
480
+
481
+ if (!this.hooks.has(event)) {
482
+ this.hooks.set(event, []);
483
+ }
484
+
485
+ const hook = {
486
+ handler,
487
+ plugin: options.plugin,
488
+ priority: options.priority || 0,
489
+ once: options.once || false,
490
+ addedAt: new Date(),
491
+ };
492
+
493
+ // Insert sorted by priority
494
+ const hooks = this.hooks.get(event);
495
+ const index = hooks.findIndex((h) => h.priority < hook.priority);
496
+ if (index === -1) {
497
+ hooks.push(hook);
498
+ } else {
499
+ hooks.splice(index, 0, hook);
500
+ }
501
+
502
+ this.emit("hook:registered", { event, hook, options });
503
+ return this;
504
+ }
505
+
506
+ /**
507
+ * Trigger hook
508
+ * @param {string} event - Event name
509
+ * @param {...any} args - Arguments
510
+ * @returns {Promise<Array>} Results from all hooks
511
+ */
512
+ async triggerHook(event, ...args) {
513
+ if (!this.hooks.has(event)) {
514
+ return [];
515
+ }
516
+
517
+ const hooks = this.hooks.get(event);
518
+ const results = [];
519
+
520
+ for (const hook of hooks) {
521
+ try {
522
+ const result = await hook.handler(...args);
523
+ results.push(result);
524
+
525
+ // If it's a one-time hook, remove it
526
+ if (hook.once) {
527
+ this.unregisterHook(event, hook.handler);
528
+ }
529
+ } catch (error) {
530
+ console.error(`Hook "${event}" execution error:`, error);
531
+ this.emit("hook:error", { event, hook, error, args });
532
+ }
533
+ }
534
+
535
+ this.emit("hook:triggered", { event, results, args });
536
+ return results;
537
+ }
538
+
539
+ /**
540
+ * Remove hook
541
+ * @param {string} event - Event name
542
+ * @param {Function} handler - Handler function
543
+ * @returns {PluginManager} PluginManager instance
544
+ */
545
+ unregisterHook(event, handler) {
546
+ if (!this.hooks.has(event)) {
547
+ return this;
548
+ }
549
+
550
+ const hooks = this.hooks.get(event);
551
+ const index = hooks.findIndex((h) => h.handler === handler);
552
+
553
+ if (index !== -1) {
554
+ const removed = hooks.splice(index, 1);
555
+ this.emit("hook:unregistered", { event, hook: removed });
556
+ }
557
+
558
+ if (hooks.length === 0) {
559
+ this.hooks.delete(event);
560
+ }
561
+
562
+ return this;
563
+ }
564
+
565
+ /**
566
+ * Register middleware
567
+ * @param {string} type - Middleware type
568
+ * @param {Function} middleware - Middleware function
569
+ * @param {Object} options - Middleware options
570
+ * @returns {PluginManager} PluginManager instance
571
+ */
572
+ registerMiddleware(type, middleware, options = {}) {
573
+ if (typeof middleware !== "function") {
574
+ throw new Error(`Middleware "${type}" must be a function`);
575
+ }
576
+
577
+ if (!this.middlewares.has(type)) {
578
+ this.middlewares.set(type, []);
579
+ }
580
+
581
+ const mw = {
582
+ middleware,
583
+ plugin: options.plugin,
584
+ priority: options.priority || 0,
585
+ addedAt: new Date(),
586
+ };
587
+
588
+ // Insert sorted by priority
589
+ const middlewares = this.middlewares.get(type);
590
+ const index = middlewares.findIndex((m) => m.priority < mw.priority);
591
+ if (index === -1) {
592
+ middlewares.push(mw);
593
+ } else {
594
+ middlewares.splice(index, 0, mw);
595
+ }
596
+
597
+ this.emit("middleware:registered", { type, middleware: mw, options });
598
+ return this;
599
+ }
600
+
601
+ /**
602
+ * Execute middleware
603
+ * @param {string} type - Middleware type
604
+ * @param {*} context - Context object
605
+ * @param {...any} args - Additional arguments
606
+ * @returns {Promise<*>} Result after middleware processing
607
+ */
608
+ async executeMiddleware(type, context, ...args) {
609
+ if (!this.middlewares.has(type)) {
610
+ return context;
611
+ }
612
+
613
+ const middlewares = this.middlewares.get(type);
614
+ let currentIndex = 0;
615
+
616
+ const next = async () => {
617
+ if (currentIndex >= middlewares.length) {
618
+ return context;
619
+ }
620
+
621
+ const mw = middlewares[currentIndex++];
622
+ try {
623
+ return await mw.middleware(context, next, ...args);
624
+ } catch (error) {
625
+ console.error(`Middleware "${type}" execution error:`, error);
626
+ this.emit("middleware:error", { type, middleware: mw, error, args });
627
+ throw error;
628
+ }
629
+ };
630
+
631
+ const result = await next();
632
+ this.emit("middleware:executed", { type, result, args });
633
+ return result;
634
+ }
635
+
636
+ /**
637
+ * Remove middleware
638
+ * @param {string} type - Middleware type
639
+ * @param {Function} middleware - Middleware function
640
+ * @returns {PluginManager} PluginManager instance
641
+ */
642
+ unregisterMiddleware(type, middleware) {
643
+ if (!this.middlewares.has(type)) {
644
+ return this;
645
+ }
646
+
647
+ const middlewares = this.middlewares.get(type);
648
+ const index = middlewares.findIndex((m) => m.middleware === middleware);
649
+
650
+ if (index !== -1) {
651
+ const removed = middlewares.splice(index, 1);
652
+ this.emit("middleware:unregistered", { type, middleware: removed });
653
+ }
654
+
655
+ if (middlewares.length === 0) {
656
+ this.middlewares.delete(type);
657
+ }
658
+
659
+ return this;
660
+ }
661
+
662
+ /**
663
+ * Load plugin configuration
664
+ * @param {Object} config - Plugin configuration
665
+ * @returns {PluginManager} PluginManager instance
666
+ */
667
+ loadConfig(config) {
668
+ if (!config || typeof config !== "object") {
669
+ return this;
670
+ }
671
+
672
+ // Load plugin configuration
673
+ if (config.plugins) {
674
+ for (const [name, pluginConfig] of Object.entries(config.plugins)) {
675
+ if (pluginConfig.enabled !== undefined) {
676
+ const plugin = this.plugins.get(name);
677
+ if (plugin) {
678
+ if (pluginConfig.enabled) {
679
+ this.enable(name);
680
+ } else {
681
+ this.disable(name);
682
+ }
683
+ }
684
+ }
685
+
686
+ // Update plugin configuration
687
+ if (pluginConfig.config && this.plugins.has(name)) {
688
+ const plugin = this.plugins.get(name);
689
+ plugin.options.config = {
690
+ ...plugin.options.config,
691
+ ...pluginConfig.config,
692
+ };
693
+ }
694
+ }
695
+ }
696
+
697
+ this.emit("config:loaded", { config });
698
+ return this;
699
+ }
700
+
701
+ /**
702
+ * Save plugin configuration
703
+ * @returns {Object} Plugin configuration
704
+ */
705
+ saveConfig() {
706
+ const config = {
707
+ plugins: {},
708
+ };
709
+
710
+ for (const [name, plugin] of this.plugins) {
711
+ config.plugins[name] = {
712
+ enabled: plugin.options.enabled,
713
+ config: plugin.options.config,
714
+ version: plugin.options.version,
715
+ priority: plugin.options.priority,
716
+ };
717
+ }
718
+
719
+ return config;
720
+ }
721
+
722
+ /**
723
+ * Hot reload plugin
724
+ * @param {string} name - Plugin name
725
+ * @param {Function} newPluginClass - New plugin class
726
+ * @returns {PluginManager} PluginManager instance
727
+ */
728
+ hotReload(name, newPluginClass) {
729
+ const oldPlugin = this.plugins.get(name);
730
+ if (!oldPlugin) {
731
+ throw new Error(`Plugin "${name}" not found`);
732
+ }
733
+
734
+ const wasEnabled = oldPlugin.options.enabled;
735
+
736
+ // Disable old plugin
737
+ if (wasEnabled) {
738
+ this.disable(name);
739
+ }
740
+
741
+ // Unregister old plugin
742
+ this.unregister(name);
743
+
744
+ // Register new plugin
745
+ this.register(name, newPluginClass, {
746
+ version: newPluginClass.version || oldPlugin.options.version,
747
+ enabled: wasEnabled,
748
+ priority: oldPlugin.options.priority,
749
+ dependencies: oldPlugin.options.dependencies,
750
+ config: oldPlugin.options.config,
751
+ });
752
+
753
+ // Enable new plugin
754
+ if (wasEnabled) {
755
+ this.enable(name);
756
+ }
757
+
758
+ this.emit("plugin:reloaded", { name, oldPlugin, newPluginClass });
759
+ return this;
760
+ }
761
+
762
+ /**
763
+ * Check plugin dependencies
764
+ * @param {string} name - Plugin name
765
+ * @returns {Object} Dependency check result
766
+ */
767
+ checkDependencies(name) {
768
+ const plugin = this.plugins.get(name);
769
+ if (!plugin) {
770
+ return { valid: false, missing: [name], circular: [] };
771
+ }
772
+
773
+ const visited = new Set();
774
+ const stack = new Set();
775
+ const missing = [];
776
+ const circular = [];
777
+
778
+ const check = (pluginName, path = []) => {
779
+ if (visited.has(pluginName)) {
780
+ return;
781
+ }
782
+
783
+ if (stack.has(pluginName)) {
784
+ circular.push([...path, pluginName]);
785
+ return;
786
+ }
787
+
788
+ const depPlugin = this.plugins.get(pluginName);
789
+ if (!depPlugin) {
790
+ missing.push(pluginName);
791
+ return;
792
+ }
793
+
794
+ stack.add(pluginName);
795
+ path.push(pluginName);
796
+
797
+ for (const dep of depPlugin.options.dependencies) {
798
+ check(dep, [...path]);
799
+ }
800
+
801
+ stack.delete(pluginName);
802
+ visited.add(pluginName);
803
+ };
804
+
805
+ check(name);
806
+
807
+ return {
808
+ valid: missing.length === 0 && circular.length === 0,
809
+ missing,
810
+ circular,
811
+ dependencies: plugin.options.dependencies,
812
+ };
813
+ }
814
+
815
+ /**
816
+ * Get plugin status
817
+ * @returns {Object} Plugin status
818
+ */
819
+ getStatus() {
820
+ const status = {
821
+ initialized: this.initialized,
822
+ totalPlugins: this.plugins.size,
823
+ enabledPlugins: Array.from(this.plugins.values()).filter(p => p.options.enabled).length,
824
+ registeredMethods: this.methods.size,
825
+ registeredHooks: Array.from(this.hooks.keys()).length,
826
+ registeredMiddlewares: Array.from(this.middlewares.keys()).length,
827
+ plugins: [],
828
+ };
829
+
830
+ for (const [name, plugin] of this.plugins) {
831
+ const PluginClass = plugin.class;
832
+ const proto = PluginClass.prototype;
833
+ const methods = [];
834
+
835
+ for (const key of Object.getOwnPropertyNames(proto)) {
836
+ if (key !== "constructor" && typeof proto[key] === "function") {
837
+ methods.push(key);
838
+ }
839
+ }
840
+
841
+ status.plugins.push({
842
+ name,
843
+ enabled: plugin.options.enabled,
844
+ version: plugin.options.version || "1.0.0",
845
+ priority: plugin.options.priority,
846
+ dependencies: plugin.options.dependencies,
847
+ methods: methods.length,
848
+ });
849
+ }
850
+
851
+ return status;
852
+ }
853
+
854
+ /**
855
+ * Cleanup plugin system
856
+ */
857
+ cleanup() {
858
+ // Disable all plugins
859
+ for (const [name, plugin] of this.plugins) {
860
+ if (plugin.options.enabled) {
861
+ this.disable(name);
862
+ }
863
+ }
864
+
865
+ // Clear all collections
866
+ this.plugins.clear();
867
+ this.pluginInstances.clear();
868
+ this.methods.clear();
869
+ this.hooks.clear();
870
+ this.middlewares.clear();
871
+
872
+ this.initialized = false;
873
+ this.emit("cleaned");
874
+ }
875
+
876
+ /**
877
+ * Batch register plugins
878
+ * @param {Array} plugins - Array of plugin configurations
879
+ * @returns {PluginManager} PluginManager instance
880
+ */
881
+ registerAll(plugins) {
882
+ if (!Array.isArray(plugins)) {
883
+ throw new Error("Plugins must be an array");
884
+ }
885
+
886
+ for (const plugin of plugins) {
887
+ if (!plugin.name || !plugin.class) {
888
+ throw new Error("Plugin must contain name and class properties");
889
+ }
890
+
891
+ this.register(plugin.name, plugin.class, plugin.options || {});
892
+ }
893
+
894
+ return this;
895
+ }
896
+
897
+ /**
898
+ * Get plugins sorted by priority
899
+ * @returns {Array} Sorted plugins list
900
+ */
901
+ getPluginsByPriority() {
902
+ return Array.from(this.plugins.values()).sort(
903
+ (a, b) => b.options.priority - a.options.priority
904
+ );
905
+ }
906
+
907
+ /**
908
+ * Get plugin dependency graph
909
+ * @returns {Object} Dependency graph
910
+ */
911
+ getDependencyGraph() {
912
+ const graph = {
913
+ nodes: [],
914
+ edges: [],
915
+ };
916
+
917
+ for (const [name, plugin] of this.plugins) {
918
+ graph.nodes.push({
919
+ id: name,
920
+ label: name,
921
+ enabled: plugin.options.enabled,
922
+ version: plugin.options.version || "1.0.0",
923
+ });
924
+
925
+ for (const dep of plugin.options.dependencies) {
926
+ graph.edges.push({
927
+ from: name,
928
+ to: dep,
929
+ type: "depends_on",
930
+ });
931
+ }
932
+ }
933
+
934
+ return graph;
935
+ }
936
+
937
+ /**
938
+ * Validate plugin compatibility
939
+ * @param {Function} PluginClass - Plugin class
940
+ * @returns {Object} Compatibility check result
941
+ */
942
+ validatePlugin(PluginClass) {
943
+ const errors = [];
944
+ const warnings = [];
945
+
946
+ // Check required methods
947
+ const requiredMethods = ["_registerMethods"];
948
+ for (const method of requiredMethods) {
949
+ if (typeof PluginClass.prototype[method] !== "function") {
950
+ errors.push(`Missing required method: ${method}`);
951
+ }
952
+ }
953
+
954
+ // Check plugin name
955
+ if (!PluginClass.prototype.pluginName || typeof PluginClass.prototype.pluginName !== "string") {
956
+ warnings.push("Plugin should have a unique pluginName property");
957
+ }
958
+
959
+ return {
960
+ valid: errors.length === 0,
961
+ errors,
962
+ warnings,
963
+ };
964
+ }
965
+ }
966
+
967
+ // Default export
968
+ export default PluginManager;