UNPKG

@dataql/node

Version:

DataQL core SDK for unified data management with MongoDB and GraphQL - Production Multi-Cloud Ready

393 lines (392 loc) 13.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PluginManager = void 0; const PluginEventEmitter_js_1 = require("./PluginEventEmitter.js"); const PluginLogger_js_1 = require("./PluginLogger.js"); const PluginUtils_js_1 = require("./PluginUtils.js"); /** * Core plugin manager implementation for DataQL * * Handles plugin registration, initialization, lifecycle management, * and provides the plugin ecosystem infrastructure. */ class PluginManager { constructor(dataql) { this.plugins = new Map(); this.pluginConfigs = new Map(); this.hooks = new Map(); this.middleware = []; this.extensions = new Map(); this.initialized = false; this.dataql = dataql; this.events = new PluginEventEmitter_js_1.PluginEventEmitter(); this.logger = new PluginLogger_js_1.PluginLoggerImpl("PluginManager"); this.utils = new PluginUtils_js_1.PluginUtilsImpl(); this.logger.info("DataQL Plugin Manager initialized"); } /** * Register a plugin with the manager */ async register(plugin, config = {}) { try { this.logger.info(`Registering plugin: ${plugin.id} (${plugin.type})`); // Validate plugin this.validatePlugin(plugin); // Check dependencies await this.checkDependencies(plugin); // Validate configuration if (plugin.configSchema) { this.utils.validateConfig(config, plugin.configSchema); } // Store plugin and config this.plugins.set(plugin.id, plugin); this.pluginConfigs.set(plugin.id, config); // Initialize plugin if manager is already initialized if (this.initialized) { await this.initializePlugin(plugin, config); } this.logger.info(`Plugin registered successfully: ${plugin.id}`); this.events.emit("pluginRegistered", { plugin, config }); } catch (error) { this.logger.error(`Failed to register plugin ${plugin.id}:`, error); throw new Error(`Plugin registration failed: ${error.message}`); } } /** * Unregister a plugin */ async unregister(pluginId) { try { const plugin = this.plugins.get(pluginId); if (!plugin) { throw new Error(`Plugin not found: ${pluginId}`); } this.logger.info(`Unregistering plugin: ${pluginId}`); // Cleanup plugin if (plugin.destroy) { await plugin.destroy(); } // Remove from collections this.plugins.delete(pluginId); this.pluginConfigs.delete(pluginId); // Remove hooks for (const [hook, handlers] of this.hooks.entries()) { this.hooks.set(hook, handlers.filter((h) => h.pluginId !== pluginId)); } // Remove middleware this.middleware = this.middleware.filter((m) => m.id !== pluginId); // Remove extensions this.extensions.delete(pluginId); this.logger.info(`Plugin unregistered successfully: ${pluginId}`); this.events.emit("pluginUnregistered", { pluginId }); } catch (error) { this.logger.error(`Failed to unregister plugin ${pluginId}:`, error); throw error; } } /** * Get a registered plugin */ getPlugin(pluginId) { return this.plugins.get(pluginId); } /** * Get all registered plugins */ getPlugins() { return Array.from(this.plugins.values()); } /** * Get plugins by type */ getPluginsByType(type) { return this.getPlugins().filter((plugin) => plugin.type === type); } /** * Check if plugin is registered */ hasPlugin(pluginId) { return this.plugins.has(pluginId); } /** * Initialize all registered plugins */ async initializeAll() { this.logger.info("Initializing all plugins..."); // Sort plugins by dependencies const sortedPlugins = this.sortPluginsByDependencies(); // Initialize plugins in dependency order for (const plugin of sortedPlugins) { const config = this.pluginConfigs.get(plugin.id) || {}; await this.initializePlugin(plugin, config); } this.initialized = true; this.logger.info(`Initialized ${sortedPlugins.length} plugins successfully`); this.events.emit("allPluginsInitialized", { count: sortedPlugins.length }); } /** * Destroy all plugins */ async destroyAll() { this.logger.info("Destroying all plugins..."); const plugins = Array.from(this.plugins.values()).reverse(); // Reverse order for (const plugin of plugins) { try { if (plugin.destroy) { await plugin.destroy(); } this.logger.debug(`Plugin destroyed: ${plugin.id}`); } catch (error) { this.logger.error(`Error destroying plugin ${plugin.id}:`, error); } } // Clear all collections this.plugins.clear(); this.pluginConfigs.clear(); this.hooks.clear(); this.middleware = []; this.extensions.clear(); this.initialized = false; this.logger.info("All plugins destroyed"); this.events.emit("allPluginsDestroyed", {}); } /** * Execute a hook with all registered handlers */ async executeHook(hook, data) { const handlers = this.hooks.get(hook) || []; if (handlers.length === 0) { return data; } let result = data; for (const handler of handlers) { try { const handlerResult = await handler(result, { hookName: hook, pluginId: handler.pluginId, dataql: this.dataql, metadata: {}, }); if (handlerResult !== undefined) { result = handlerResult; } } catch (error) { this.logger.error(`Hook handler error in ${hook}:`, error); // Continue with other handlers } } return result; } /** * Register a hook handler */ registerHook(hook, handler, pluginId) { if (!this.hooks.has(hook)) { this.hooks.set(hook, []); } // Add plugin ID to handler for cleanup handler.pluginId = pluginId; this.hooks.get(hook).push(handler); this.logger.debug(`Hook registered: ${hook} (plugin: ${pluginId})`); } /** * Process request through middleware */ async processRequest(request) { let result = request; // Sort middleware by order const sortedMiddleware = [...this.middleware].sort((a, b) => a.order - b.order); for (const middleware of sortedMiddleware) { if (middleware.processRequest) { try { result = await middleware.processRequest(result); } catch (error) { this.logger.error(`Middleware error in ${middleware.id}:`, error); if (middleware.handleError) { await middleware.handleError(error, { request: result }); } } } } return result; } /** * Process response through middleware */ async processResponse(response) { let result = response; // Process in reverse order for response const sortedMiddleware = [...this.middleware].sort((a, b) => b.order - a.order); for (const middleware of sortedMiddleware) { if (middleware.processResponse) { try { result = await middleware.processResponse(result); } catch (error) { this.logger.error(`Middleware error in ${middleware.id}:`, error); if (middleware.handleError) { await middleware.handleError(error, { response: result }); } } } } return result; } /** * Get extension methods */ getExtensions() { const allExtensions = {}; for (const [pluginId, extensions] of this.extensions) { Object.assign(allExtensions, extensions); } return allExtensions; } /** * Get plugin statistics */ getStats() { const stats = { totalPlugins: this.plugins.size, pluginsByType: {}, initialized: this.initialized, hooks: this.hooks.size, middleware: this.middleware.length, extensions: this.extensions.size, }; for (const plugin of this.plugins.values()) { stats.pluginsByType[plugin.type] = (stats.pluginsByType[plugin.type] || 0) + 1; } return stats; } /** * Private: Initialize a single plugin */ async initializePlugin(plugin, config) { try { this.logger.debug(`Initializing plugin: ${plugin.id}`); const context = { dataql: this.dataql, config, pluginManager: this, logger: this.logger.child({ plugin: plugin.id }), events: this.events, utils: this.utils, }; // Initialize plugin await plugin.initialize(context); // Register type-specific functionality await this.registerPluginFunctionality(plugin); this.logger.debug(`Plugin initialized successfully: ${plugin.id}`); } catch (error) { this.logger.error(`Failed to initialize plugin ${plugin.id}:`, error); throw error; } } /** * Private: Register plugin-specific functionality */ async registerPluginFunctionality(plugin) { switch (plugin.type) { case "middleware": const middleware = plugin; this.middleware.push(middleware); break; case "extension": const extension = plugin; if (extension.methods || extension.properties) { this.extensions.set(plugin.id, { ...extension.methods, ...extension.properties, }); } break; case "hook": const hookPlugin = plugin; if (hookPlugin.hooks) { for (const [hookName, handler] of Object.entries(hookPlugin.hooks)) { this.registerHook(hookName, handler, plugin.id); } } break; } } /** * Private: Validate plugin */ validatePlugin(plugin) { if (!plugin.id || typeof plugin.id !== "string") { throw new Error("Plugin must have a valid ID"); } if (!plugin.name || typeof plugin.name !== "string") { throw new Error("Plugin must have a valid name"); } if (!plugin.version || typeof plugin.version !== "string") { throw new Error("Plugin must have a valid version"); } if (!plugin.type) { throw new Error("Plugin must have a valid type"); } if (typeof plugin.initialize !== "function") { throw new Error("Plugin must have an initialize function"); } if (this.plugins.has(plugin.id)) { throw new Error(`Plugin already registered: ${plugin.id}`); } } /** * Private: Check plugin dependencies */ async checkDependencies(plugin) { if (!plugin.dependencies || plugin.dependencies.length === 0) { return; } for (const dependency of plugin.dependencies) { if (!this.plugins.has(dependency)) { throw new Error(`Missing dependency: ${dependency} (required by ${plugin.id})`); } } } /** * Private: Sort plugins by dependencies */ sortPluginsByDependencies() { const plugins = Array.from(this.plugins.values()); const sorted = []; const visited = new Set(); const visiting = new Set(); const visit = (plugin) => { if (visiting.has(plugin.id)) { throw new Error(`Circular dependency detected: ${plugin.id}`); } if (visited.has(plugin.id)) { return; } visiting.add(plugin.id); // Visit dependencies first if (plugin.dependencies) { for (const depId of plugin.dependencies) { const dep = this.plugins.get(depId); if (dep) { visit(dep); } } } visiting.delete(plugin.id); visited.add(plugin.id); sorted.push(plugin); }; for (const plugin of plugins) { visit(plugin); } return sorted; } } exports.PluginManager = PluginManager;