/** * Comprehensive Plugin System Example * * Demonstrates extensible request/response middleware */ import { createConfig, ElasticsearchConnectionManager, LogLevel, createPluginManager, createLoggingPlugin, createMetricsPlugin, createCachePlugin, createRateLimitPlugin, type Plugin, type PluginContext, type PluginResponse, } from '../../index.js'; async function main() { console.log('=== Plugin System Example ===\n'); // ============================================================================ // Step 1: Configuration // ============================================================================ console.log('Step 1: Configuring Elasticsearch connection...'); const config = createConfig() .fromEnv() .nodes(process.env.ELASTICSEARCH_URL || 'http://localhost:9200') .basicAuth( process.env.ELASTICSEARCH_USERNAME || 'elastic', process.env.ELASTICSEARCH_PASSWORD || 'changeme' ) .timeout(30000) .retries(3) .logLevel(LogLevel.INFO) .enableMetrics(true) .build(); // ============================================================================ // Step 2: Initialize Connection and Plugin Manager // ============================================================================ console.log('Step 2: Initializing connection and plugin manager...'); const connectionManager = ElasticsearchConnectionManager.getInstance(config); await connectionManager.initialize(); const pluginManager = createPluginManager({ enabled: true, maxHookDuration: 5000, continueOnError: true, collectStats: true, }); // Set the client for plugin initialization pluginManager.setClient(connectionManager.getClient()); console.log('✓ Connection and plugin manager initialized\n'); // ============================================================================ // Step 3: Register Built-in Plugins // ============================================================================ console.log('Step 3: Registering built-in plugins...'); // Logging plugin - logs all requests/responses await pluginManager.register( createLoggingPlugin({ logRequests: true, logResponses: true, logErrors: true, logRequestBody: true, logResponseBody: false, maxBodySize: 1024, }) ); // Metrics plugin - collects request metrics await pluginManager.register( createMetricsPlugin({ enabled: true, prefix: 'elasticsearch', recordDuration: true, recordSize: true, recordResponseSize: true, }) ); // Cache plugin - caches GET requests await pluginManager.register( createCachePlugin({ enabled: true, maxEntries: 100, defaultTTL: 60, methods: ['GET'], }) ); // Rate limit plugin - limits request rate await pluginManager.register( createRateLimitPlugin({ maxRequestsPerSecond: 10, burstSize: 5, waitForSlot: true, maxWaitTime: 5000, }) ); console.log('✓ Built-in plugins registered\n'); // ============================================================================ // Step 4: Create Custom Plugin // ============================================================================ console.log('Step 4: Creating and registering custom plugin...'); const customPlugin: Plugin = { name: 'request-id-injector', version: '1.0.0', priority: 5, // Execute very early beforeRequest: (context: PluginContext) => { // Add custom header to all requests if (!context.request.headers) { context.request.headers = {}; } context.request.headers['X-Custom-Request-ID'] = context.request.requestId; context.request.headers['X-Client-Version'] = '3.0.0'; console.log(` [Custom Plugin] Added headers to request ${context.request.requestId}`); return context; }, afterResponse: (context: PluginContext, response: PluginResponse) => { console.log( ` [Custom Plugin] Response received for ${context.request.requestId} with status ${response.statusCode}` ); return response; }, onError: (context) => { console.log( ` [Custom Plugin] Error occurred for ${context.request.requestId}: ${context.error.message}` ); // Don't handle error return null; }, }; await pluginManager.register(customPlugin); console.log('✓ Custom plugin registered\n'); // ============================================================================ // Step 5: Create Transformation Plugin // ============================================================================ console.log('Step 5: Creating transformation plugin...'); const transformPlugin: Plugin = { name: 'response-transformer', version: '1.0.0', priority: 80, // Execute late, after most plugins afterResponse: (context: PluginContext, response: PluginResponse) => { // Add metadata to all responses const transformedResponse = { ...response }; if (typeof transformedResponse.body === 'object' && transformedResponse.body !== null) { (transformedResponse.body as any)._metadata = { requestId: context.request.requestId, duration: Date.now() - context.request.startTime, timestamp: new Date().toISOString(), }; } console.log(` [Transform Plugin] Added metadata to response`); return transformedResponse; }, }; await pluginManager.register(transformPlugin); console.log('✓ Transformation plugin registered\n'); // ============================================================================ // Step 6: Demonstrate Plugin Execution // ============================================================================ console.log('Step 6: Demonstrating plugin execution...\n'); // Simulate a request context const mockContext: PluginContext = { client: connectionManager.getClient(), request: { method: 'GET', path: '/test-index/_search', body: { query: { match_all: {} } }, requestId: `req-${Date.now()}`, startTime: Date.now(), }, shared: new Map(), config: {}, }; // Execute beforeRequest hooks console.log(' Executing beforeRequest hooks...'); const modifiedContext = await pluginManager.executeBeforeRequest(mockContext); if (modifiedContext) { console.log(` ✓ Request context modified by ${pluginManager.getPlugins().length} plugins`); console.log(` Headers added:`, modifiedContext.request.headers); } else { console.log(' ✗ Request cancelled by plugin'); } // Simulate a response const mockResponse: PluginResponse = { body: { took: 5, hits: { total: { value: 0 }, hits: [], }, }, statusCode: 200, headers: {}, }; // Execute afterResponse hooks console.log('\n Executing afterResponse hooks...'); const modifiedResponse = await pluginManager.executeAfterResponse( modifiedContext!, mockResponse ); console.log(` ✓ Response modified by plugins`); console.log(` Metadata added:`, (modifiedResponse.body as any)._metadata); console.log(); // ============================================================================ // Step 7: Plugin Statistics // ============================================================================ console.log('Step 7: Plugin statistics...\n'); const stats = pluginManager.getStats(); for (const [pluginName, pluginStats] of stats) { console.log(`Plugin: ${pluginName}`); console.log(` beforeRequest calls: ${pluginStats.beforeRequestCalls}`); console.log(` afterResponse calls: ${pluginStats.afterResponseCalls}`); console.log(` onError calls: ${pluginStats.onErrorCalls}`); console.log( ` Avg beforeRequest duration: ${pluginStats.avgBeforeRequestDuration.toFixed(2)}ms` ); console.log( ` Avg afterResponse duration: ${pluginStats.avgAfterResponseDuration.toFixed(2)}ms` ); console.log(` Errors: ${pluginStats.errors}`); console.log(); } // ============================================================================ // Step 8: Plugin Priority Demonstration // ============================================================================ console.log('Step 8: Demonstrating plugin priority...\n'); const plugins = pluginManager.getPlugins(); const sortedPlugins = plugins.sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100)); console.log('Plugins in execution order (by priority):'); for (const plugin of sortedPlugins) { console.log(` ${plugin.priority ?? 100}: ${plugin.name}`); } console.log(); // ============================================================================ // Step 9: Dynamic Plugin Management // ============================================================================ console.log('Step 9: Dynamic plugin management...'); // Unregister a plugin console.log(' Unregistering cache plugin...'); await pluginManager.unregister('cache'); console.log(` ✓ Cache plugin unregistered (${pluginManager.getPlugins().length} remaining)`); // Register it again console.log(' Re-registering cache plugin...'); await pluginManager.register( createCachePlugin({ enabled: true, maxEntries: 50, defaultTTL: 30, }) ); console.log(` ✓ Cache plugin re-registered (${pluginManager.getPlugins().length} total)`); console.log(); // ============================================================================ // Step 10: Error Handling // ============================================================================ console.log('Step 10: Demonstrating error handling...\n'); const mockError = new Error('Connection timeout'); const errorContext = { ...mockContext, error: mockError, attempts: 1, }; console.log(' Executing onError hooks...'); const errorResponse = await pluginManager.executeOnError(errorContext); if (errorResponse) { console.log(' ✓ Error handled by plugin'); } else { console.log(' ✓ Error logged but not handled'); } console.log(); // ============================================================================ // Step 11: Creating a Plugin Factory // ============================================================================ console.log('Step 11: Creating reusable plugin factory...\n'); function createTimingPlugin(threshold: number = 1000): Plugin { return { name: `slow-request-detector-${threshold}`, version: '1.0.0', priority: 100, afterResponse: (context: PluginContext, response: PluginResponse) => { const duration = Date.now() - context.request.startTime; if (duration > threshold) { console.log( ` [Timing Plugin] SLOW REQUEST DETECTED: ${context.request.path} took ${duration}ms (threshold: ${threshold}ms)` ); } return response; }, }; } // Create and register timing plugin with custom threshold await pluginManager.register(createTimingPlugin(500)); console.log('✓ Timing plugin factory demonstrated\n'); // ============================================================================ // Step 12: Cleanup // ============================================================================ console.log('Step 12: Cleanup...'); // Clear statistics pluginManager.clearStats(); // Destroy all plugins await pluginManager.destroy(); await connectionManager.destroy(); console.log('✓ Cleanup complete\n'); console.log('=== Plugin System Example Complete ==='); console.log('\nKey Features Demonstrated:'); console.log(' ✓ Plugin registration and lifecycle'); console.log(' ✓ Built-in plugins (logging, metrics, cache, rate-limit)'); console.log(' ✓ Custom plugin creation'); console.log(' ✓ Request/response transformation'); console.log(' ✓ Plugin priority and execution order'); console.log(' ✓ Dynamic plugin management (register/unregister)'); console.log(' ✓ Error handling hooks'); console.log(' ✓ Plugin statistics collection'); console.log(' ✓ Plugin factories for reusable patterns'); console.log(' ✓ Shared context between plugins'); console.log(' ✓ Request cancellation (rate limiting)'); } // Run the example main().catch((error) => { console.error('Example failed:', error); process.exit(1); });