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,141 @@
/**
* Metrics Plugin
*
* Automatically collects metrics for requests and responses
*/
import { defaultMetricsCollector } from '../../observability/metrics.js';
import type { Plugin, PluginContext, PluginResponse, MetricsPluginConfig } from '../types.js';
/**
* Default configuration
*/
const DEFAULT_CONFIG: Required<MetricsPluginConfig> = {
enabled: true,
prefix: 'elasticsearch',
recordDuration: true,
recordSize: true,
recordResponseSize: true,
};
/**
* Create metrics plugin
*/
export function createMetricsPlugin(config: MetricsPluginConfig = {}): Plugin {
const pluginConfig = { ...DEFAULT_CONFIG, ...config };
const metrics = defaultMetricsCollector;
return {
name: 'metrics',
version: '1.0.0',
priority: 20, // Execute early, after logging
beforeRequest: (context: PluginContext) => {
if (!pluginConfig.enabled) {
return context;
}
// Record request counter
metrics.recordCounter(`${pluginConfig.prefix}.requests`, 1, {
method: context.request.method,
path: extractIndexFromPath(context.request.path),
});
// Record request size if enabled
if (pluginConfig.recordSize && context.request.body) {
const size = Buffer.byteLength(JSON.stringify(context.request.body), 'utf8');
metrics.recordHistogram(`${pluginConfig.prefix}.request.size`, size, {
method: context.request.method,
});
}
return context;
},
afterResponse: <T>(context: PluginContext, response: PluginResponse<T>) => {
if (!pluginConfig.enabled) {
return response;
}
const duration = Date.now() - context.request.startTime;
// Record request duration if enabled
if (pluginConfig.recordDuration) {
metrics.recordHistogram(`${pluginConfig.prefix}.request.duration`, duration, {
method: context.request.method,
path: extractIndexFromPath(context.request.path),
status: response.statusCode.toString(),
});
}
// Record response size if enabled
if (pluginConfig.recordResponseSize && response.body) {
const size = Buffer.byteLength(JSON.stringify(response.body), 'utf8');
metrics.recordHistogram(`${pluginConfig.prefix}.response.size`, size, {
method: context.request.method,
status: response.statusCode.toString(),
});
}
// Record success/failure
const success = response.statusCode >= 200 && response.statusCode < 300;
metrics.recordCounter(
`${pluginConfig.prefix}.requests.${success ? 'success' : 'failure'}`,
1,
{
method: context.request.method,
status: response.statusCode.toString(),
}
);
return response;
},
onError: (context) => {
if (!pluginConfig.enabled) {
return null;
}
const duration = Date.now() - context.request.startTime;
// Record error
metrics.recordCounter(`${pluginConfig.prefix}.errors`, 1, {
method: context.request.method,
path: extractIndexFromPath(context.request.path),
error: context.error.name,
});
// Record error duration
if (pluginConfig.recordDuration) {
metrics.recordHistogram(`${pluginConfig.prefix}.error.duration`, duration, {
method: context.request.method,
error: context.error.name,
});
}
// Don't handle error
return null;
},
};
}
/**
* Extract index name from path
*/
function extractIndexFromPath(path: string): string {
// Remove leading slash
const cleanPath = path.startsWith('/') ? path.slice(1) : path;
// Split by slash and get first segment
const segments = cleanPath.split('/');
// Common patterns:
// /{index}/_search
// /{index}/_doc/{id}
// /_cat/indices
if (segments[0].startsWith('_')) {
return segments[0]; // API endpoint like _cat, _search
}
return segments[0] || 'unknown';
}