402 lines
12 KiB
TypeScript
402 lines
12 KiB
TypeScript
/**
|
|
* 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: <T>(context: PluginContext, response: PluginResponse<T>) => {
|
|
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: <T>(context: PluginContext, response: PluginResponse<T>) => {
|
|
// 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: <T>(context: PluginContext, response: PluginResponse<T>) => {
|
|
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);
|
|
});
|