### Install Dependencies with pnpm Source: https://github.com/damir-manapov/hands-on-plugin-system-for-back/blob/main/README.md Installs all necessary project dependencies using the pnpm package manager. This is a prerequisite for running the development server or building the project. ```bash pnpm install ``` -------------------------------- ### Start Development Server with pnpm Source: https://github.com/damir-manapov/hands-on-plugin-system-for-back/blob/main/README.md Launches the development server, enabling features like hot-reloading for rapid development cycles. This command is typically used during the active development phase of the plugin system. ```bash pnpm dev ``` -------------------------------- ### Programmatic Plugin Management with PluginManager (TypeScript) Source: https://github.com/damir-manapov/hands-on-plugin-system-for-back/blob/main/HOT_RELOAD.md Demonstrates how to use the PluginManager class to load, unload, reload, get, and execute plugins programmatically. It requires the PluginManager to be imported and instantiated. ```typescript import { PluginManager } from "./core/plugin-manager.js"; const pluginManager = new PluginManager(); // Load a plugin await pluginManager.loadPlugin("/path/to/plugin.js"); // Unload a plugin await pluginManager.unloadPlugin("plugin-name"); // Reload a plugin await pluginManager.reloadPlugin("plugin-name"); // Load all plugins from a directory await pluginManager.loadPluginsFromDirectory("./plugins"); // Get a plugin const plugin = pluginManager.getPlugin("plugin-name"); // Execute a plugin const result = await plugin?.execute({ data: "test" }); ``` -------------------------------- ### Retrieve and Execute a Specific Loaded Plugin Source: https://context7.com/damir-manapov/hands-on-plugin-system-for-back/llms.txt Fetches a plugin instance by its unique name from the PluginManager. Once retrieved, it allows direct interaction with the plugin, including executing its methods if available. This example also demonstrates how to retrieve all loaded plugins or just their names. ```javascript import { PluginManager } from './core/plugin-manager.js'; const manager = new PluginManager(); await manager.loadPluginsFromDirectory('./plugins'); // Get a specific plugin const authPlugin = manager.getPlugin('auth-plugin'); if (authPlugin) { console.log('Found:', authPlugin.metadata.name); // Execute the plugin if (authPlugin.execute) { const result = await authPlugin.execute({ action: 'authenticate', credentials: { username: 'user', password: 'pass' } }); console.log('Auth result:', result); } } else { console.log('Plugin not found'); } // Alternative: get all plugins const allPlugins = manager.getAllPlugins(); console.log(`Total plugins: ${allPlugins.length}`); // Or just get plugin names const names = manager.getPluginNames(); console.log('Plugin names:', names); ``` -------------------------------- ### Event-Driven Communication - Inter-Plugin Events (JavaScript) Source: https://context7.com/damir-manapov/hands-on-plugin-system-for-back/llms.txt Illustrates how plugins communicate indirectly through an event bus, promoting loose coupling. Includes examples of a logger plugin subscribing to events, a user management plugin emitting 'user:created' events after database operations, and an email plugin reacting to 'user:created' events to send welcome emails. ```javascript // Example 1: Event emitter plugin (logger) export default { metadata: { name: 'logger-plugin', version: '1.0.0', dependencies: [] }, async initialize(context) { // Subscribe to all important events context.eventBus.on('user:created', (data) => { console.log('[AUDIT] User created:', data.userId, data.email); }); context.eventBus.on('user:deleted', (data) => { console.log('[AUDIT] User deleted:', data.userId); }); context.eventBus.on('error', (error) => { console.error('[ERROR]', error.message, error.stack); }); } }; // Example 2: User management plugin export default { metadata: { name: 'user-management', version: '1.0.0', dependencies: ['database-plugin'] }, context: null, async initialize(context) { this.context = context; }, async execute(input) { const { action, data } = input; if (action === 'createUser') { const db = this.context.getDependency('database-plugin'); // Create user const userId = Date.now(); await db.execute({ action: 'insert', table: 'users', id: userId, data: { ...data, createdAt: new Date() } }); // Emit event - logger and other plugins can react this.context.eventBus.emit('user:created', { userId, email: data.email, timestamp: new Date() }); return { success: true, userId }; } } }; // Example 3: Email plugin reacts to events export default { metadata: { name: 'email-plugin', version: '1.0.0', dependencies: [] }, async initialize(context) { // React to user creation context.eventBus.on('user:created', async (data) => { console.log(`[email-plugin] Sending welcome email to ${data.email}`); // await sendEmail(data.email, 'Welcome!', 'Thanks for joining...'); // Emit completion event context.eventBus.emit('email:sent', { to: data.email, type: 'welcome', timestamp: new Date() }); }); } }; ``` -------------------------------- ### Prevent Use-After-Unload with Context Invalidation in JavaScript Source: https://context7.com/damir-manapov/hands-on-plugin-system-for-back/llms.txt Illustrates how a plugin can manage its internal state and context, and how the system invalidates this context upon plugin unload. The example shows a plugin using `setInterval` that would throw an error if the context is accessed after unload, demonstrating the importance of proper cleanup. ```javascript // Example plugin that stores context export default { metadata: { name: 'stateful-plugin', version: '1.0.0' }, context: null, intervalId: null, async initialize(context) { this.context = context; // Start a background task this.intervalId = setInterval(() => { try { // This will throw if plugin is unloaded this.context.eventBus.emit('heartbeat', { plugin: 'stateful-plugin', timestamp: Date.now() }); } catch (error) { // Context invalidated - plugin was unloaded console.error('Plugin context invalidated:', error.message); if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = null; } } }, 1000); }, async cleanup() { // Proper cleanup prevents the error if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = null; } } }; // Manager usage const manager = new PluginManager(); await manager.loadPlugin('./plugins/stateful-plugin.js'); // Wait a bit await new Promise(resolve => setTimeout(resolve, 3000)); // Unload - context becomes invalid await manager.unloadPlugin('stateful-plugin'); // If cleanup() didn't clear the interval, next tick would throw: // "Plugin 'stateful-plugin' has been unloaded. Context is no longer valid." ``` -------------------------------- ### Build Project with pnpm Source: https://github.com/damir-manapov/hands-on-plugin-system-for-back/blob/main/README.md Compiles the project, including the plugin system and any associated code, into a deployable format. This command is used to prepare the application for production or distribution. ```bash pnpm build ``` -------------------------------- ### Run Tests with pnpm Source: https://github.com/damir-manapov/hands-on-plugin-system-for-back/blob/main/README.md Executes the test suite for the plugin system and the backend application. This ensures the stability and correctness of the implemented features and plugin interactions. ```bash pnpm test ``` -------------------------------- ### Creating a Basic Plugin (JavaScript) Source: https://github.com/damir-manapov/hands-on-plugin-system-for-back/blob/main/HOT_RELOAD.md Defines the structure for a custom plugin, including metadata and lifecycle hooks like initialize, cleanup, and execute. This script should be saved as a .js or .mjs file. ```javascript export default { metadata: { name: "my-plugin", version: "1.0.0", description: "My custom plugin", }, async initialize() { console.log("Plugin initialized"); }, async cleanup() { console.log("Plugin cleaned up"); }, async execute(input) { return `Processed: ${input}`; }, }; ``` -------------------------------- ### Creating a Plugin (JavaScript) Source: https://github.com/damir-manapov/hands-on-plugin-system-for-back/blob/main/RUNTIME_PLUGINS.md Defines the structure and lifecycle methods for a custom plugin. Includes metadata, initialization, cleanup, and execution logic. Demonstrates dependency declaration and usage of the plugin context for event bus and dependency access. ```javascript export default { metadata: { name: "my-plugin", version: "1.0.0", description: "My custom plugin", dependencies: ["other-plugin"], // Declare dependencies here }, context: null, async initialize(context) { console.log("Plugin initialized"); // Store context for later use if needed // Engine automatically invalidates context on unload - no manual cleanup needed this.context = context; // Access declared dependencies const otherPlugin = context.getDependency("other-plugin"); if (otherPlugin) { console.log("Found dependency:", otherPlugin.metadata.name); } // Get all dependencies const allDeps = context.getDependencies(); console.log("All dependencies:", Array.from(allDeps.keys())); // Subscribe to events from other plugins context.eventBus.on("user:created", (data) => { console.log("User created:", data); }); // Emit an event context.eventBus.emit("plugin:ready", { name: "my-plugin", timestamp: Date.now(), }); }, async cleanup() { // Engine automatically handles: // - Event listener cleanup // - Context invalidation // - Dependency cleanup // Only implement cleanup() if you need custom cleanup logic console.log("Plugin cleaned up"); }, async execute(input) { // Emit an event during execution // Context is automatically invalidated if plugin was unloaded if (this.context) { try { this.context.eventBus.emit("data:processed", { plugin: "my-plugin", input, }); } catch { // Context may be invalidated if plugin was unloaded console.warn("Cannot emit event - plugin may be unloaded"); } } return `Processed: ${input}`; }, }; ``` -------------------------------- ### Complete Application Lifecycle Management in JavaScript Source: https://context7.com/damir-manapov/hands-on-plugin-system-for-back/llms.txt Demonstrates the full lifecycle of a plugin system, including initialization, loading plugins from a directory, event handling, plugin execution, and graceful shutdown using SIGINT. It utilizes a PluginManager for managing plugins and their dependencies. ```javascript import { PluginManager } from './core/plugin-manager.js'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); async function main() { const manager = new PluginManager(); // Setup event handlers manager.on('pluginLoaded', (plugin) => { console.log(`✓ Loaded: ${plugin.metadata.name} v${plugin.metadata.version}`); if (plugin.metadata.dependencies?.length) { console.log(` Dependencies: ${plugin.metadata.dependencies.join(', ')}`); } }); manager.on('pluginUnloaded', (pluginName) => { console.log(`✓ Unloaded: ${pluginName}`); }); manager.on('pluginError', (error) => { console.error(`✗ Plugin error: ${error.message}`); }); try { // Load all plugins from directory const pluginsDir = join(__dirname, 'plugins'); console.log(`Loading plugins from: ${pluginsDir}\n`); await manager.loadPluginsFromDirectory(pluginsDir); console.log('\n--- All plugins loaded ---\n'); // List loaded plugins const plugins = manager.getAllPlugins(); console.log(`Active plugins (${plugins.length}):`); plugins.forEach(p => { console.log(` - ${p.metadata.name} v${p.metadata.version}`); }); // Execute specific plugin const plugin = manager.getPlugin('example-plugin-2'); if (plugin?.execute) { console.log('\nExecuting example-plugin-2...'); const result = await plugin.execute({ value: 42 }); console.log('Result:', result); } } catch (error) { console.error('\nFatal error:', error.message); process.exit(1); } // Graceful shutdown process.on('SIGINT', async () => { console.log('\n\nShutting down...'); try { await manager.unloadAll(); console.log('All plugins unloaded'); process.exit(0); } catch (error) { console.error('Shutdown error:', error.message); process.exit(1); } }); console.log('\n--- Application running (Ctrl+C to exit) ---'); } main().catch(console.error); ``` -------------------------------- ### Plugin with Dependencies - Consuming Other Plugins (JavaScript) Source: https://context7.com/damir-manapov/hands-on-plugin-system-for-back/llms.txt Demonstrates a plugin ('api-handler') that declares dependencies on other plugins ('database-plugin', 'auth-plugin'). It accesses these dependencies via the injected context to perform actions like verifying user authentication and querying a database. It also listens for and emits events related to its operations. ```javascript export default { metadata: { name: 'api-handler', version: '1.0.0', description: 'Handles HTTP API requests', dependencies: ['database-plugin', 'auth-plugin'] }, context: null, async initialize(context) { console.log('[api-handler] Initializing...'); this.context = context; // Access declared dependencies const dbPlugin = context.getDependency('database-plugin'); const authPlugin = context.getDependency('auth-plugin'); if (!dbPlugin || !authPlugin) { throw new Error('Required dependencies not available'); } // Subscribe to database ready event context.eventBus.on('database:ready', ({ tables }) => { console.log('[api-handler] Database ready with tables:', tables); }); // Subscribe to incoming requests context.eventBus.on('http:request', async (request) => { try { // Authenticate first const authResult = await authPlugin.execute({ action: 'verify', token: request.headers.authorization }); if (!authResult.valid) { context.eventBus.emit('http:response', { requestId: request.id, status: 401, body: { error: 'Unauthorized' } }); return; } // Query database const data = await dbPlugin.execute({ action: 'query', table: 'users', id: request.params.userId }); // Send response context.eventBus.emit('http:response', { requestId: request.id, status: 200, body: data }); } catch (error) { context.eventBus.emit('http:response', { requestId: request.id, status: 500, body: { error: error.message } }); } }); console.log('[api-handler] Ready to handle requests'); }, async cleanup() { console.log('[api-handler] Shutting down...'); } }; ``` -------------------------------- ### PluginManager.loadPlugin - Load a Plugin from File Path Source: https://context7.com/damir-manapov/hands-on-plugin-system-for-back/llms.txt Dynamically imports and initializes a plugin from a file system path. It validates metadata, resolves dependencies, and injects the plugin context. Error handling for missing or circular dependencies is included. ```APIDOC ## PluginManager.loadPlugin ### Description Dynamically imports and initializes a plugin from a file system path, validating its metadata, resolving dependencies, and injecting the plugin context. ### Method `loadPlugin(filePath: string)` ### Endpoint N/A (This is a class method) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```javascript import { PluginManager } from './core/plugin-manager.js'; const manager = new PluginManager(); // Listen for load events manager.on('pluginLoaded', (plugin) => { console.log(`\u2713 Loaded: ${plugin.metadata.name} v${plugin.metadata.version}`); }); // Handle errors manager.on('pluginError', (error) => { console.error('Plugin error:', error.message); }); try { // Load a single plugin const plugin = await manager.loadPlugin('./plugins/my-plugin.js'); console.log('Plugin loaded:', plugin.metadata.name); // Execute plugin if it has an execute method if (plugin.execute) { const result = await plugin.execute({ userId: 123 }); console.log('Execution result:', result); } } catch (error) { if (error.name === 'DependencyNotFoundError') { console.error('Missing dependency:', error.dependency); } else if (error.name === 'CircularDependencyError') { console.error('Circular dependency chain:', error.dependencyChain); } else { console.error('Load failed:', error.message); } } ``` ### Response #### Success Response (200) - **plugin** (object) - The loaded plugin object, including its metadata and methods. #### Response Example ```json { "metadata": { "name": "my-plugin", "version": "1.0.0" }, "execute": "[function]" } ``` ### Error Handling - **DependencyNotFoundError**: Thrown when a required plugin dependency is not found. - **CircularDependencyError**: Thrown when a circular dependency is detected between plugins. - **Load Failed**: Generic error for other loading issues. ``` -------------------------------- ### Execute All Project Checks Source: https://github.com/damir-manapov/hands-on-plugin-system-for-back/blob/main/README.md Runs a comprehensive set of checks, likely including linting, formatting, and potentially custom validation scripts, to ensure code quality and project integrity. This script is part of the project's quality assurance process. ```bash ./all-checks.sh ``` -------------------------------- ### Load Plugin from File Path with PluginManager Source: https://context7.com/damir-manapov/hands-on-plugin-system-for-back/llms.txt Dynamically imports and initializes a plugin from a file system path using PluginManager.loadPlugin. It validates plugin metadata, resolves dependencies, injects context, and listens for 'pluginLoaded' and 'pluginError' events. Handles specific errors like 'DependencyNotFoundError' and 'CircularDependencyError'. ```javascript import { PluginManager } from './core/plugin-manager.js'; const manager = new PluginManager(); // Listen for load events manager.on('pluginLoaded', (plugin) => { console.log(`✓ Loaded: ${plugin.metadata.name} v${plugin.metadata.version}`); }); // Handle errors manager.on('pluginError', (error) => { console.error('Plugin error:', error.message); }); try { // Load a single plugin const plugin = await manager.loadPlugin('./plugins/my-plugin.js'); console.log('Plugin loaded:', plugin.metadata.name); // Execute plugin if it has an execute method if (plugin.execute) { const result = await plugin.execute({ userId: 123 }); console.log('Execution result:', result); } } catch (error) { if (error.name === 'DependencyNotFoundError') { console.error('Missing dependency:', error.dependency); } else if (error.name === 'CircularDependencyError') { console.error('Circular dependency chain:', error.dependencyChain); } else { console.error('Load failed:', error.message); } } ``` -------------------------------- ### Bulk Load Plugins from Directory with Dependency Resolution Source: https://context7.com/damir-manapov/hands-on-plugin-system-for-back/llms.txt Scans a specified directory for JavaScript plugin files (.js/.mjs). It automatically resolves and loads plugins in the correct dependency order. This function relies on the PluginManager class and emits 'pluginLoaded' events. It handles dependency resolution errors and general directory load failures. ```javascript import { PluginManager } from './core/plugin-manager.js'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const manager = new PluginManager(); const pluginsDir = join(__dirname, 'plugins'); // Track loaded plugins manager.on('pluginLoaded', (plugin) => { console.log(`✓ ${plugin.metadata.name} (deps: ${plugin.metadata.dependencies?.join(', ') || 'none'})`); }); try { // Load all plugins from directory // System automatically orders by dependencies await manager.loadPluginsFromDirectory(pluginsDir); // List all loaded plugins const pluginNames = manager.getPluginNames(); console.log('\nLoaded plugins:', pluginNames); // Get details of all plugins const allPlugins = manager.getAllPlugins(); allPlugins.forEach(p => { console.log(`- ${p.metadata.name} v${p.metadata.version}`); }); } catch (error) { if (error.name === 'DependencyResolutionError') { console.error('Cannot resolve dependencies:', error.message); } else { console.error('Directory load failed:', error.message); } } ``` -------------------------------- ### Plugin Event Handling with PluginManager (TypeScript) Source: https://github.com/damir-manapov/hands-on-plugin-system-for-back/blob/main/HOT_RELOAD.md Shows how to subscribe to and handle events emitted by the PluginManager, such as 'pluginLoaded', 'pluginUnloaded', and 'pluginError'. This allows for reacting to changes in the plugin lifecycle. ```typescript pluginManager.on("pluginLoaded", (plugin) => { console.log(`Plugin loaded: ${plugin.metadata.name}`); }); pluginManager.on("pluginUnloaded", (metadata) => { console.log(`Plugin unloaded: ${metadata.name}`); }); pluginManager.on("pluginError", (error, metadata) => { console.error(`Error in plugin ${metadata?.name}:`, error); }); ``` -------------------------------- ### Plugin Structure: Metadata, Lifecycle, and Execution Source: https://context7.com/damir-manapov/hands-on-plugin-system-for-back/llms.txt Defines the standard structure for a plugin module. A plugin is a JavaScript module exporting a default object containing essential metadata (name, version, description, dependencies), optional lifecycle hooks (initialize, cleanup), and an execute method for performing its primary function. It can also interact with a provided context, including an event bus. ```javascript // plugins/database-plugin.js export default { metadata: { name: 'database-plugin', version: '1.0.0', description: 'Provides database access to other plugins', dependencies: [] // No dependencies }, db: null, context: null, async initialize(context) { console.log('[database-plugin] Initializing...'); this.context = context; // Setup database connection this.db = { users: new Map(), query: async (table, id) => { return this.db[table].get(id); }, insert: async (table, id, data) => { this.db[table].set(id, data); context.eventBus.emit('db:insert', { table, id, data }); } }; // Subscribe to events from other plugins context.eventBus.on('db:query', async ({ table, id, responseChannel }) => { const result = await this.db.query(table, id); context.eventBus.emit(responseChannel, result); }); // Announce ready context.eventBus.emit('database:ready', { tables: ['users'] }); }, async cleanup() { console.log('[database-plugin] Cleaning up...'); this.db = null; // Event listeners automatically cleaned up }, async execute(input) { const { action, table, id, data } = input; if (action === 'query') { return await this.db.query(table, id); } else if (action === 'insert') { return await this.db.insert(table, id, data); } throw new Error(`Unknown action: ${action}`); } }; ``` -------------------------------- ### Handle Plugin Errors Safely in JavaScript Source: https://context7.com/damir-manapov/hands-on-plugin-system-for-back/llms.txt Demonstrates how to safely load plugins and handle various plugin-specific errors using a switch statement. It imports custom error types from './index.js' and returns a structured result object indicating success or failure with detailed error information. ```javascript import { PluginManager, PluginNotFoundError, InvalidPluginFormatError, DependencyNotFoundError, CircularDependencyError, SelfDependencyError, UndeclaredDependencyError, PluginLoadError, PluginUnloadError, DependencyResolutionError } from './index.js'; const manager = new PluginManager(); async function loadPluginSafely(path) { try { const plugin = await manager.loadPlugin(path); return { success: true, plugin }; } catch (error) { switch (error.name) { case 'InvalidPluginFormatError': return { success: false, error: 'Plugin file must export default object with metadata', details: error.message }; case 'DependencyNotFoundError': return { success: false, error: `Missing dependency: ${error.dependency}`, plugin: error.pluginName, required: error.dependency }; case 'CircularDependencyError': return { success: false, error: 'Circular dependency detected', chain: error.dependencyChain, message: error.message }; case 'SelfDependencyError': return { success: false, error: 'Plugin cannot depend on itself', plugin: error.pluginName }; case 'PluginLoadError': return { success: false, error: 'Failed to load plugin', plugin: error.pluginName, cause: error.cause?.message }; default: return { success: false, error: 'Unknown error', message: error.message }; } } } // Usage const result = await loadPluginSafely('./plugins/my-plugin.js'); if (!result.success) { console.error('Load failed:', result.error); if (result.chain) { console.error('Dependency chain:', result.chain.join(' -> ')); } } ``` -------------------------------- ### Inter-Plugin Event Communication (JavaScript) Source: https://github.com/damir-manapov/hands-on-plugin-system-for-back/blob/main/RUNTIME_PLUGINS.md Plugins can communicate using an event bus accessible via `context.eventBus`. Use `on` to subscribe to events and `emit` to trigger them. Event listeners are automatically removed upon plugin cleanup. Supports wildcard subscriptions for event patterns. ```javascript export default { metadata: { name: "event-emitter-plugin", version: "1.0.0", }, context: null, async initialize(context) { this.context = context; // Subscribe to events context.eventBus.on("task:started", (data) => { console.log("Task started:", data); // Process the event and emit a response context.eventBus.emit("task:acknowledged", { plugin: "event-emitter-plugin", taskId: data.taskId, }); }); // Subscribe to events from a specific pattern context.eventBus.on("data:*", (data) => { console.log("Data event received:", data); }); }, async cleanup() { this.context = null; }, }; ``` -------------------------------- ### PluginManager.reloadPlugin - Hot Reload a Plugin Source: https://context7.com/damir-manapov/hands-on-plugin-system-for-back/llms.txt Performs a hot reload of a plugin by first unloading it and then immediately reloading it. This is useful for applying code changes without restarting the entire application. ```APIDOC ## PluginManager.reloadPlugin ### Description Hot reloads a plugin by first unloading it and then immediately reloading it. This is useful for applying changes to plugin code without restarting the application. Note that Node.js module caching might require cache-busting techniques for true hot reloading. ### Method `reloadPlugin(pluginName: string)` ### Endpoint N/A (This is a class method) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```javascript import { PluginManager } from './core/plugin-manager.js'; const manager = new PluginManager(); const pluginPath = './plugins/dynamic-plugin.js'; // Initial load await manager.loadPlugin(pluginPath); console.log('Plugin loaded'); // Simulate plugin file modification // (In real scenario, user would edit the plugin file) try { // Reload to pick up changes const reloadedPlugin = await manager.reloadPlugin('dynamic-plugin'); console.log('Plugin reloaded:', reloadedPlugin.metadata.version); // Note: Node.js caches ES modules, so true hot reload requires // cache-busting techniques like appending query parameters } catch (error) { console.error('Reload failed:', error.message); } ``` ### Response #### Success Response (200) - **reloadedPlugin** (object) - The reloaded plugin object. #### Response Example ```json { "metadata": { "name": "dynamic-plugin", "version": "1.1.0" }, "execute": "[function]" } ``` ### Error Handling - **PluginNotFoundError**: Thrown if the plugin to be reloaded is not found. - **Reload Failed**: Generic error for issues during the unload/load sequence. ``` -------------------------------- ### PluginManager.unloadPlugin - Unload a Running Plugin Source: https://context7.com/damir-manapov/hands-on-plugin-system-for-back/llms.txt Calls the plugin's cleanup hook, automatically removes event listeners, invalidates the plugin context, and removes the plugin from memory. Supports unloading by plugin name. ```APIDOC ## PluginManager.unloadPlugin ### Description Unloads a running plugin by calling its cleanup hook, automatically removing event listeners, invalidating the plugin context, and removing it from memory. This operation can be performed by plugin name. ### Method `unloadPlugin(pluginName: string)` ### Endpoint N/A (This is a class method) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```javascript import { PluginManager } from './core/plugin-manager.js'; const manager = new PluginManager(); // Load a plugin first await manager.loadPlugin('./plugins/example-plugin.js'); // Listen for unload events manager.on('pluginUnloaded', (pluginName) => { console.log(`\u2713 Unloaded: ${pluginName}`); }); try { // Unload by name await manager.unloadPlugin('example-plugin'); console.log('Plugin unloaded successfully'); // Verify it's gone const plugin = manager.getPlugin('example-plugin'); console.log('Plugin still exists:', plugin !== undefined); // false } catch (error) { if (error.name === 'PluginNotFoundError') { console.error('Plugin not found:', error.pluginName); } else { console.error('Unload failed:', error.message); } } ``` ### Response #### Success Response (200) Indicates successful unloading of the plugin. #### Response Example ```json { "message": "Plugin 'example-plugin' unloaded successfully." } ``` ### Error Handling - **PluginNotFoundError**: Thrown when the specified plugin name does not exist or is not loaded. ``` -------------------------------- ### Declare and Access Plugin Dependencies (JavaScript) Source: https://github.com/damir-manapov/hands-on-plugin-system-for-back/blob/main/RUNTIME_PLUGINS.md Plugins declare their dependencies in `metadata.dependencies`. The `context.getDependency` method is used to access these declared dependencies. Undeclared dependencies will cause an error. All declared dependencies are available as a Map via `context.getDependencies()`. ```javascript export default { metadata: { name: "consumer-plugin", version: "1.0.0", dependencies: ["provider-plugin-1", "provider-plugin-2"], // Required dependencies }, async initialize(context) { // Only declared dependencies are accessible const provider1 = context.getDependency("provider-plugin-1"); // ✅ OK const provider2 = context.getDependency("provider-plugin-2"); // ✅ OK const unknown = context.getDependency("unknown-plugin"); // ❌ Throws error // Get all dependencies as a Map const deps = context.getDependencies(); // deps.get("provider-plugin-1") === provider1 }, }; ``` -------------------------------- ### Hot Reload a Plugin with PluginManager Source: https://context7.com/damir-manapov/hands-on-plugin-system-for-back/llms.txt Performs a hot reload of a plugin by first unloading and then reloading it in sequence using PluginManager.reloadPlugin. This is useful for applying code changes without restarting the application. Note that Node.js ES module caching might require cache-busting techniques for true hot reloading. ```javascript import { PluginManager } from './core/plugin-manager.js'; const manager = new PluginManager(); const pluginPath = './plugins/dynamic-plugin.js'; // Initial load await manager.loadPlugin(pluginPath); console.log('Plugin loaded'); // Simulate plugin file modification // (In real scenario, user would edit the plugin file) try { // Reload to pick up changes const reloadedPlugin = await manager.reloadPlugin('dynamic-plugin'); console.log('Plugin reloaded:', reloadedPlugin.metadata.version); // Note: Node.js caches ES modules, so true hot reload requires // cache-busting techniques like appending query parameters } catch (error) { console.error('Reload failed:', error.message); } ``` -------------------------------- ### Unload a Running Plugin with PluginManager Source: https://context7.com/damir-manapov/hands-on-plugin-system-for-back/llms.txt Removes a running plugin from the system using PluginManager.unloadPlugin. This process includes calling the plugin's cleanup hook, removing event listeners, invalidating the plugin context, and removing it from memory. It listens for 'pluginUnloaded' events and handles 'PluginNotFoundError'. ```javascript import { PluginManager } from './core/plugin-manager.js'; const manager = new PluginManager(); // Load a plugin first await manager.loadPlugin('./plugins/example-plugin.js'); // Listen for unload events manager.on('pluginUnloaded', (pluginName) => { console.log(`✓ Unloaded: ${pluginName}`); }); try { // Unload by name await manager.unloadPlugin('example-plugin'); console.log('Plugin unloaded successfully'); // Verify it's gone const plugin = manager.getPlugin('example-plugin'); console.log('Plugin still exists:', plugin !== undefined); // false } catch (error) { if (error.name === 'PluginNotFoundError') { console.error('Plugin not found:', error.pluginName); } else { console.error('Unload failed:', error.message); } } ```