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:
2025-11-29 18:32:00 +00:00
parent 53673e37cb
commit 7e89b6ebf5
68 changed files with 17020 additions and 720 deletions

View 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);
});