BREAKING CHANGE(core): Refactor to v3: introduce modular core/domain architecture, plugin system, observability and strict TypeScript configuration; remove legacy classes
This commit is contained in:
401
ts/examples/plugins/plugin-example.ts
Normal file
401
ts/examples/plugins/plugin-example.ts
Normal file
@@ -0,0 +1,401 @@
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
Reference in New Issue
Block a user