UNPKG

fortify-schema

Version:

A modern TypeScript validation library designed around familiar interface syntax and powerful conditional validation. Experience schema validation that feels natural to TypeScript developers while unlocking advanced runtime validation capabilities.

398 lines (337 loc) 10.4 kB
import { ValidationStats } from "../../../../types/extension.type"; import { SchemaInterface } from "../../../../types/SchemaValidator.type"; import { ValidationEngine } from "../../mods"; /** * Enhanced Stream Validator with full EventEmitter-like interface * Supports all standard stream methods (.on, .emit, .pipe, etc.) * Synchronized with InterfaceSchema modules */ export class StreamValidator { private validListeners: Array<(data: any) => void> = []; private invalidListeners: Array< (data: any, errors: Record<string, string[]>) => void > = []; private statsListeners: Array<(stats: ValidationStats) => void> = []; // Enhanced event system private eventListeners: Map<string, Array<(...args: any[]) => void>> = new Map(); private onceListeners: Map<string, Array<(...args: any[]) => void>> = new Map(); // Stream control private isPaused: boolean = false; private isDestroyed: boolean = false; private dataQueue: any[] = []; // Data transformation pipeline private transformers: Array<(data: any) => any> = []; private filters: Array<(data: any) => boolean> = []; private mappers: Array<(data: any) => any> = []; private stats: ValidationStats = { totalValidated: 0, validCount: 0, invalidCount: 0, errorRate: 0, startTime: new Date(), }; constructor(private schema: SchemaInterface) {} // ================================================================= // CORE EVENT EMITTER METHODS // ================================================================= /** * Generic event listener (EventEmitter-like interface) */ on(event: string, listener: (...args: any[]) => void): this { if (this.isDestroyed) throw new Error("StreamValidator is destroyed"); if (!this.eventListeners.has(event)) { this.eventListeners.set(event, []); } this.eventListeners.get(event)!.push(listener); return this; } /** * One-time event listener */ once(event: string, listener: (...args: any[]) => void): this { if (this.isDestroyed) throw new Error("StreamValidator is destroyed"); if (!this.onceListeners.has(event)) { this.onceListeners.set(event, []); } this.onceListeners.get(event)!.push(listener); return this; } /** * Remove event listener */ off(event: string, listener?: (...args: any[]) => void): this { if (listener) { // Remove specific listener const listeners = this.eventListeners.get(event); if (listeners) { const index = listeners.indexOf(listener); if (index > -1) listeners.splice(index, 1); } const onceListeners = this.onceListeners.get(event); if (onceListeners) { const index = onceListeners.indexOf(listener); if (index > -1) onceListeners.splice(index, 1); } } else { // Remove all listeners for event this.eventListeners.delete(event); this.onceListeners.delete(event); } return this; } /** * Emit event to all listeners */ emit(event: string, ...args: any[]): boolean { if (this.isDestroyed) return false; let hasListeners = false; // Regular listeners const listeners = this.eventListeners.get(event); if (listeners && listeners.length > 0) { hasListeners = true; listeners.forEach((listener) => { try { listener(...args); } catch (error) { this.emit("error", error); } }); } // Once listeners const onceListeners = this.onceListeners.get(event); if (onceListeners && onceListeners.length > 0) { hasListeners = true; onceListeners.forEach((listener) => { try { listener(...args); } catch (error) { this.emit("error", error); } }); this.onceListeners.delete(event); // Clear once listeners } return hasListeners; } // ================================================================= // ENHANCED VALIDATION WITH STREAM CONTROL // ================================================================= /** * Enhanced validate method with stream control and InterfaceSchema sync */ validate(data: any): void { if (this.isDestroyed) { this.emit("error", new Error("Cannot validate on destroyed stream")); return; } if (this.isPaused) { this.dataQueue.push(data); this.emit("queued", data); return; } this._processData(data); } private _processData(data: any): void { this.stats.totalValidated++; this.emit("data", data); try { // Apply data transformation pipeline let processedData = this._applyTransformations(data); // Apply filters if (!this._passesFilters(processedData)) { this.emit("filtered", processedData); return; } // FIXED: Use InterfaceSchema.safeParse for proper validation sync let validationResult; if (typeof this.schema.safeParse === "function") { // Use InterfaceSchema validation const result = this.schema.safeParse(processedData); validationResult = { isValid: result.success, errors: result.success ? {} : this._formatErrors(result.errors), }; } else { // Fallback to ValidationEngine validationResult = ValidationEngine.validateObject( this.schema, processedData ); } if (validationResult.isValid) { this.stats.validCount++; this.validListeners.forEach((listener) => listener(processedData)); this.emit("valid", processedData); } else { this.stats.invalidCount++; this.invalidListeners.forEach((listener) => listener(processedData, validationResult.errors) ); this.emit("invalid", processedData, validationResult.errors); } this.updateStats(); this.emit("validated", processedData, validationResult); } catch (error) { this.emit("error", error); } } private _formatErrors(errors: any[]): Record<string, string[]> { const formatted: Record<string, string[]> = {}; errors.forEach((error) => { const field = error.path || "unknown"; if (!formatted[field]) formatted[field] = []; formatted[field].push(error.message || error.toString()); }); return formatted; } private _applyTransformations(data: any): any { let result = data; for (const transformer of this.transformers) { result = transformer(result); } for (const mapper of this.mappers) { result = mapper(result); } return result; } private _passesFilters(data: any): boolean { return this.filters.every((filter) => filter(data)); } /** * Listen for valid data */ onValid(listener: (data: any) => void): void { this.validListeners.push(listener); } /** * Listen for invalid data */ onInvalid( listener: (data: any, errors: Record<string, string[]>) => void ): void { this.invalidListeners.push(listener); } /** * Listen for validation statistics */ onStats(listener: (stats: ValidationStats) => void): void { this.statsListeners.push(listener); } /** * Get current validation statistics */ getStats(): ValidationStats { return { ...this.stats }; } private updateStats(): void { this.stats.errorRate = this.stats.totalValidated > 0 ? this.stats.invalidCount / this.stats.totalValidated : 0; this.statsListeners.forEach((listener) => listener(this.stats)); } // ================================================================= // DATA TRANSFORMATION METHODS // ================================================================= /** * Add data transformer to pipeline */ transform(transformer: (data: any) => any): this { if (this.isDestroyed) throw new Error("StreamValidator is destroyed"); this.transformers.push(transformer); return this; } /** * Add data filter to pipeline */ filter(predicate: (data: any) => boolean): this { if (this.isDestroyed) throw new Error("StreamValidator is destroyed"); this.filters.push(predicate); return this; } /** * Add data mapper to pipeline */ map(mapper: (data: any) => any): this { if (this.isDestroyed) throw new Error("StreamValidator is destroyed"); this.mappers.push(mapper); return this; } /** * Pipe data to another stream validator */ pipe(destination: StreamValidator) { if (this.isDestroyed) throw new Error("StreamValidator is destroyed"); this.on("valid", (data) => { destination.validate(data); }); this.on("error", (error) => { destination.emit("error", error); }); return destination; } // ================================================================= // STREAM CONTROL METHODS // ================================================================= /** * Pause the stream (queue incoming data) */ pause(): this { this.isPaused = true; this.emit("pause"); return this; } /** * Resume the stream (process queued data) */ resume(): this { if (this.isDestroyed) throw new Error("StreamValidator is destroyed"); this.isPaused = false; this.emit("resume"); // Process queued data const queuedData = [...this.dataQueue]; this.dataQueue = []; queuedData.forEach((data) => this._processData(data)); return this; } /** * Destroy the stream (cleanup and prevent further use) */ destroy(): this { if (this.isDestroyed) return this; this.isDestroyed = true; this.isPaused = false; // Clear all data this.dataQueue = []; this.transformers = []; this.filters = []; this.mappers = []; // Clear all listeners this.validListeners = []; this.invalidListeners = []; this.statsListeners = []; this.eventListeners.clear(); this.onceListeners.clear(); this.emit("destroy"); return this; } /** * Check if stream is destroyed */ get destroyed(): boolean { return this.isDestroyed; } /** * Check if stream is paused */ get paused(): boolean { return this.isPaused; } /** * Get queue length */ get queueLength(): number { return this.dataQueue.length; } }