@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
JavaScript
"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;