@ahmedhegazee/nestjs-telescope
Version:
Advanced observability and monitoring solution for NestJS applications with ML-powered analytics, enterprise features, and production-ready scaling
270 lines • 11.8 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var TelescopeConfigValidator_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TelescopeConfigValidator = void 0;
const common_1 = require("@nestjs/common");
let TelescopeConfigValidator = TelescopeConfigValidator_1 = class TelescopeConfigValidator {
constructor() {
this.logger = new common_1.Logger(TelescopeConfigValidator_1.name);
this.defaults = {
enabled: true,
storage: {
driver: 'memory',
batch: {
enabled: true,
size: 100,
flushInterval: 5000
}
},
devtools: {
enabled: true,
bridge: {
resilience: {
circuitBreakerEnabled: true,
fallbackEnabled: true,
maxRetries: 3,
retryDelayMs: 1000,
healthCheckIntervalMs: 30000
}
}
},
features: {
realTimeUpdates: true,
dashboard: true,
metrics: true
}
};
this.supportedDrivers = ['memory', 'file', 'database', 'redis'];
this.requiredFields = ['enabled', 'storage', 'devtools'];
}
validate(config) {
const errors = [];
const warnings = [];
try {
this.validateRequiredFields(config, errors);
this.validateStorageConfig(config.storage, errors, warnings);
this.validateDevToolsConfig(config.devtools, errors, warnings);
this.validateFeaturesConfig(config.features, errors, warnings);
this.validateWatchersConfig(config.watchers, errors, warnings);
this.validatePerformanceConfig(config, errors, warnings);
this.validateSecurityConfig(config, errors, warnings);
return {
isValid: errors.length === 0,
errors,
warnings
};
}
catch (error) {
this.logger.error('Configuration validation failed:', error);
return {
isValid: false,
errors: [`Validation failed: ${error.message}`],
warnings
};
}
}
applyDefaults(config) {
const mergedConfig = this.deepMerge(this.defaults, config);
this.logger.debug('Configuration after applying defaults:', JSON.stringify(mergedConfig, null, 2));
return mergedConfig;
}
validateAndApplyDefaults(config) {
const validation = this.validate(config);
if (!validation.isValid) {
throw new common_1.BadRequestException(`Configuration validation failed: ${validation.errors.join(', ')}`);
}
if (validation.warnings.length > 0) {
this.logger.warn('Configuration warnings:', validation.warnings);
}
return this.applyDefaults(config);
}
validateRequiredFields(config, errors) {
for (const field of this.requiredFields) {
if (!(field in config)) {
errors.push(`Missing required field: ${field}`);
}
}
}
validateStorageConfig(storage, errors, warnings) {
if (!storage) {
errors.push('Storage configuration is required');
return;
}
if (storage.driver && !this.supportedDrivers.includes(storage.driver)) {
errors.push(`Unsupported storage driver: ${storage.driver}. Supported: ${this.supportedDrivers.join(', ')}`);
}
if (storage.batch) {
if (storage.batch.size !== undefined) {
if (typeof storage.batch.size !== 'number' || storage.batch.size <= 0) {
errors.push('Batch size must be a positive number');
}
else if (storage.batch.size > 1000) {
warnings.push('Large batch size may impact performance');
}
}
if (storage.batch.flushInterval !== undefined) {
if (typeof storage.batch.flushInterval !== 'number' || storage.batch.flushInterval <= 0) {
errors.push('Batch flush interval must be a positive number');
}
else if (storage.batch.flushInterval < 1000) {
warnings.push('Short flush interval may impact performance');
}
}
}
if (storage.driver === 'file') {
if (storage.file && !storage.file.directory) {
errors.push('File storage requires directory configuration');
}
}
if (storage.driver === 'database') {
if (storage.database && !storage.database.connection) {
errors.push('Database storage requires connection configuration');
}
}
if (storage.driver === 'redis') {
if (storage.redis && !storage.redis.host) {
errors.push('Redis storage requires host configuration');
}
}
}
validateDevToolsConfig(devtools, errors, warnings) {
if (!devtools) {
errors.push('DevTools configuration is required');
return;
}
if (devtools.bridge) {
if (devtools.bridge.resilience) {
const resilience = devtools.bridge.resilience;
if (resilience.maxRetries !== undefined) {
if (typeof resilience.maxRetries !== 'number' || resilience.maxRetries < 0) {
errors.push('Max retries must be a non-negative number');
}
else if (resilience.maxRetries > 10) {
warnings.push('High retry count may cause delays');
}
}
if (resilience.retryDelayMs !== undefined) {
if (typeof resilience.retryDelayMs !== 'number' || resilience.retryDelayMs < 0) {
errors.push('Retry delay must be a non-negative number');
}
}
if (resilience.healthCheckIntervalMs !== undefined) {
if (typeof resilience.healthCheckIntervalMs !== 'number' || resilience.healthCheckIntervalMs < 1000) {
errors.push('Health check interval must be at least 1000ms');
}
}
}
}
}
validateFeaturesConfig(features, errors, warnings) {
if (!features) {
return;
}
const booleanFields = ['realTimeUpdates', 'dashboard', 'metrics'];
for (const field of booleanFields) {
if (features[field] !== undefined && typeof features[field] !== 'boolean') {
errors.push(`Feature ${field} must be a boolean`);
}
}
if (features.dashboard === false && features.realTimeUpdates === true) {
warnings.push('Real-time updates enabled but dashboard disabled');
}
}
validateWatchersConfig(watchers, errors, warnings) {
if (!watchers) {
return;
}
const validWatchers = ['request', 'query', 'exception', 'job', 'cache'];
for (const [watcherName, watcherConfig] of Object.entries(watchers)) {
if (!validWatchers.includes(watcherName)) {
warnings.push(`Unknown watcher: ${watcherName}`);
}
if (typeof watcherConfig === 'object' && watcherConfig !== null) {
this.validateWatcherConfig(watcherName, watcherConfig, errors, warnings);
}
}
}
validateWatcherConfig(name, config, errors, warnings) {
if (config.enabled !== undefined && typeof config.enabled !== 'boolean') {
errors.push(`Watcher ${name} enabled must be a boolean`);
}
if (config.sampling !== undefined) {
if (typeof config.sampling !== 'number' || config.sampling < 0 || config.sampling > 100) {
errors.push(`Watcher ${name} sampling must be a number between 0 and 100`);
}
}
if (name === 'request') {
if (config.excludePaths && !Array.isArray(config.excludePaths)) {
errors.push('Request watcher excludePaths must be an array');
}
}
if (name === 'query') {
if (config.slowQueryThreshold !== undefined) {
if (typeof config.slowQueryThreshold !== 'number' || config.slowQueryThreshold <= 0) {
errors.push('Query watcher slowQueryThreshold must be a positive number');
}
}
}
}
validatePerformanceConfig(config, errors, warnings) {
if (config.storage?.batch?.size > 500) {
warnings.push('Large batch size may cause memory issues');
}
if (config.storage?.batch?.flushInterval < 1000) {
warnings.push('Short flush interval may cause high CPU usage');
}
if (config.devtools?.bridge?.resilience?.maxRetries > 5) {
warnings.push('High retry count may cause long delays');
}
if (config.features?.realTimeUpdates === true && config.storage?.batch?.flushInterval > 10000) {
warnings.push('Real-time updates with long flush interval may cause delays');
}
}
validateSecurityConfig(config, errors, warnings) {
if (config.storage?.driver === 'file' && config.storage?.file?.directory?.startsWith('/')) {
warnings.push('Using absolute path for file storage may have security implications');
}
if (config.features?.dashboard === true && !config.auth) {
warnings.push('Dashboard enabled without authentication configuration');
}
if (this.containsSensitiveData(config)) {
errors.push('Configuration contains sensitive data that should be in environment variables');
}
}
containsSensitiveData(obj) {
const sensitiveKeys = ['password', 'secret', 'key', 'token', 'apikey'];
const json = JSON.stringify(obj).toLowerCase();
return sensitiveKeys.some(key => json.includes(key));
}
deepMerge(target, source) {
const result = { ...target };
for (const key in source) {
if (source.hasOwnProperty(key)) {
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
result[key] = this.deepMerge(result[key] || {}, source[key]);
}
else {
result[key] = source[key];
}
}
}
return result;
}
getDefaults() {
return { ...this.defaults };
}
getSupportedDrivers() {
return [...this.supportedDrivers];
}
};
exports.TelescopeConfigValidator = TelescopeConfigValidator;
exports.TelescopeConfigValidator = TelescopeConfigValidator = TelescopeConfigValidator_1 = __decorate([
(0, common_1.Injectable)()
], TelescopeConfigValidator);
//# sourceMappingURL=telescope-config.validator.js.map