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.

719 lines (650 loc) 21.1 kB
/** * Intelligent Schema Pre-compilation System * * This system pre-compiles schemas into optimized validation functions * to eliminate runtime parsing overhead and dramatically improve performance. */ import { SchemaValidationResult, ValidationError } from "../../types/types"; import { SchemaFieldType } from "../../types/SchemaValidator.type"; import { CompiledValidator, OptimizationStrategy } from "../../types/scompiler"; import { ValidationHelpers } from "../mode/interfaces/validators/ValidationHelpers"; import { ConstraintParser } from "../mode/interfaces/validators/ConstraintParser"; import { OptimizedUnionValidator } from "../mode/interfaces/validators/UnionCache"; import { ErrorHandler, } from "../mode/interfaces/errors/ErrorHandler"; import { ErrorCode } from "../mode/interfaces/errors/types/errors.type"; export class SchemaCompiler { private static compiledCache = new Map<string, CompiledValidator>(); private static optimizationStrategies: OptimizationStrategy[] = []; private static maxCacheSize = 2000; private static compilationStats = { hits: 0, misses: 0, compilations: 0, optimizations: 0, }; /** * Initialize optimization strategies */ static { this.registerOptimizationStrategies(); } /** * Compile a schema definition into an optimized validator */ static compileSchema( schemaDefinition: Record<string, SchemaFieldType>, options: any = {} ): CompiledValidator { const cacheKey = this.generateCacheKey(schemaDefinition, options); // Check cache first const cached = this.compiledCache.get(cacheKey); if (cached) { this.compilationStats.hits++; return cached; } this.compilationStats.misses++; this.compilationStats.compilations++; // Analyze schema complexity const metadata = this.analyzeSchemaComplexity(schemaDefinition); // Generate optimized validator const validator = this.generateOptimizedValidator( schemaDefinition, metadata, options ); // Apply optimization strategies const optimizedValidator = this.applyOptimizations(validator); // Cache the result this.compiledCache.set(cacheKey, optimizedValidator); this.cleanupCache(); return optimizedValidator; } /** * Analyze schema complexity to determine optimization strategy */ private static analyzeSchemaComplexity( schema: Record<string, SchemaFieldType> ): CompiledValidator["metadata"] { let fieldCount = 0; let nestingLevel = 0; let hasUnions = false; let hasArrays = false; let hasConditionals = false; const analyzeField = (field: SchemaFieldType, depth: number = 0): void => { fieldCount++; nestingLevel = Math.max(nestingLevel, depth); if (typeof field === "string") { if (field.includes("|")) hasUnions = true; if (field.includes("[]")) hasArrays = true; if (field.includes("when")) hasConditionals = true; } else if (typeof field === "object" && field !== null) { if ("union" in field) hasUnions = true; else { // Nested object Object.values(field).forEach((nestedField) => analyzeField(nestedField, depth + 1) ); } } }; Object.values(schema).forEach((field) => analyzeField(field)); const complexity = this.calculateComplexityScore({ fieldCount, nestingLevel, hasUnions, hasArrays, hasConditionals, }); return { complexity, fieldCount, nestingLevel, hasUnions, hasArrays, hasConditionals, }; } /** * Calculate complexity score for optimization decisions */ private static calculateComplexityScore( metadata: Omit<CompiledValidator["metadata"], "complexity"> ): number { let score = metadata.fieldCount; score += metadata.nestingLevel * 3; // Nesting is expensive score += metadata.hasUnions ? 2 : 0; score += metadata.hasArrays ? 2 : 0; score += metadata.hasConditionals ? 5 : 0; // Conditionals are most expensive return score; } /** * Generate optimized validator function */ private static generateOptimizedValidator( schema: Record<string, SchemaFieldType>, metadata: CompiledValidator["metadata"], options: any ): CompiledValidator { // Generate validation function based on complexity const validate = this.createValidationFunction(schema, metadata, options); return { validate, metadata, cacheKey: this.generateCacheKey(schema, options), compiledAt: Date.now(), }; } /** * Create optimized validation function */ private static createValidationFunction( schema: Record<string, SchemaFieldType>, metadata: CompiledValidator["metadata"], options: any ): (value: any) => SchemaValidationResult { // For simple schemas, use fast path if (metadata.complexity < 10) { return this.createSimpleValidator(schema, options); } // For complex schemas, use optimized path if (metadata.nestingLevel > 2) { return this.createNestedValidator(schema, options); } // Default optimized validator return this.createStandardValidator(schema, options); } /** * Fast path for simple schemas */ private static createSimpleValidator( schema: Record<string, SchemaFieldType>, options: any ): (value: any) => SchemaValidationResult { // Pre-compile all field validators const fieldValidators = new Map< string, (value: any) => SchemaValidationResult >(); for (const [key, fieldType] of Object.entries(schema)) { fieldValidators.set(key, this.compileFieldValidator(fieldType, options)); } return (value: any): SchemaValidationResult => { if (typeof value !== "object" || value === null) { return { success: false, errors: [ErrorHandler.createTypeError([], "object", value)], warnings: [], data: value, }; } const errors: ValidationError[] = []; const validatedData: any = {}; // Fast validation loop for (const [key, validator] of fieldValidators) { const result = validator(value[key]); if (!result.success) { errors.push(...result.errors); } else { validatedData[key] = result.data; } } return { success: errors.length === 0, errors, warnings: [], data: validatedData, }; }; } /** * Optimized validator for nested schemas */ private static createNestedValidator( schema: Record<string, SchemaFieldType>, options: any ): (value: any) => SchemaValidationResult { // Pre-compile nested structure const compiledStructure = this.preCompileNestedStructure(schema, options); return (value: any): SchemaValidationResult => { return this.validateWithCompiledStructure(value, compiledStructure); }; } /** * Standard optimized validator */ private static createStandardValidator( schema: Record<string, SchemaFieldType>, options: any ): (value: any) => SchemaValidationResult { // Use lazy initialization to avoid circular dependency let InterfaceSchema: any = null; let tempSchema: any = null; return (value: any) => { if (!InterfaceSchema) { // Lazy load InterfaceSchema to avoid circular dependency InterfaceSchema = require("../mode/interfaces/InterfaceSchema").InterfaceSchema; tempSchema = new InterfaceSchema(schema, { ...options, skipOptimization: true, }); } return tempSchema.safeParse(value); }; } /** * Compile individual field validator with real validation logic */ private static compileFieldValidator( fieldType: SchemaFieldType, options: any ): (value: any) => SchemaValidationResult { if (typeof fieldType === "string") { // Check for conditional syntax first if (fieldType.includes("when ") && fieldType.includes(" *? ")) { // This is a conditional field - delegate to InterfaceSchema // Use lazy loading to avoid circular dependency let InterfaceSchema: any = null; let tempSchema: any = null; const initializeSchema = () => { if (!InterfaceSchema) { InterfaceSchema = require("../mode/interfaces/InterfaceSchema").InterfaceSchema; tempSchema = new InterfaceSchema( { temp: fieldType }, { ...options, skipOptimization: true, } ); } }; return (value: any, fullData?: any) => { // Initialize schema on first use initializeSchema(); // For conditional fields, we need the full data context for runtime methods const dataToValidate = fullData ? { temp: value, ...fullData } : { temp: value }; const result = tempSchema.safeParse(dataToValidate); return { success: result.success, errors: result.errors.map((err: string) => err.replace("temp: ", "") ), warnings: result.warnings.map((warn: string) => warn.replace("temp: ", "") ), data: result.success ? result.data?.temp : undefined, }; }; } // Pre-parse the field type for optimization // ValidationHelpers and ConstraintParser are already imported at the top const parsed = ConstraintParser.parseConstraints(fieldType); const { type, constraints, optional } = parsed; // Return optimized validator based on type if (type.includes("|")) { // Union type - use fixed validation that handles both type and literal unions return (value: any): SchemaValidationResult => { if (value === undefined && optional) { return { success: true, errors: [], warnings: [], data: options.default, }; } // Use the fixed union validation from ValidationHelpers return ValidationHelpers.validateUnionType(type, value); }; } if (type === "string") { // Optimized string validator return (value: any, fullData?: any): SchemaValidationResult => { if (value === undefined && optional) { return { success: true, errors: [], warnings: [], data: options.default, }; } if (typeof value !== "string") { return { success: false, errors: [ErrorHandler.createTypeError([], "string", value)], warnings: [], data: value, }; } if (constraints.minLength && value.length < constraints.minLength) { return { success: false, errors: [ ErrorHandler.createStringError( [], `String too short (min: ${constraints.minLength})`, value, ErrorCode.STRING_TOO_SHORT ), ], warnings: [], data: value, }; } if (constraints.maxLength && value.length > constraints.maxLength) { return { success: false, errors: [ ErrorHandler.createStringError( [], `String too long (max: ${constraints.maxLength})`, value, ErrorCode.STRING_TOO_LONG ), ], warnings: [], data: value, }; } return { success: true, errors: [], warnings: [], data: value }; }; } if (type === "number" || type === "int") { // Optimized number validator return (value: any, fullData?: any): SchemaValidationResult => { if (value === undefined && optional) { return { success: true, errors: [], warnings: [], data: options.default, }; } if (typeof value !== "number" || isNaN(value)) { return { success: false, errors: [ErrorHandler.createTypeError([], "number", value)], warnings: [], data: value, }; } if (type === "int" && !Number.isInteger(value)) { return { success: false, errors: [ErrorHandler.createTypeError([], "integer", value)], warnings: [], data: value, }; } if (constraints.min !== undefined && value < constraints.min) { return { success: false, errors: [ ErrorHandler.createNumberError( [], `Number too small (min: ${constraints.min})`, value, ErrorCode.NUMBER_TOO_SMALL ), ], warnings: [], data: value, }; } if (constraints.max !== undefined && value > constraints.max) { return { success: false, errors: [ ErrorHandler.createNumberError( [], `Number too large (max: ${constraints.max})`, value, ErrorCode.NUMBER_TOO_LARGE ), ], warnings: [], data: value, }; } return { success: true, errors: [], warnings: [], data: value }; }; } // Fallback to ValidationHelpers for complex types return (value: any, fullData?: any): SchemaValidationResult => { return ValidationHelpers.routeTypeValidation( type, value, options, constraints ); }; } // Fallback for non-string field types return (value: any, fullData?: any): SchemaValidationResult => { return { success: true, errors: [], warnings: [], data: value }; }; } /** * Pre-compile nested structure for fast traversal */ private static preCompileNestedStructure(schema: any, options: any): any { const compiledStructure: any = { fields: new Map(), validators: new Map(), paths: [], isFlat: true, }; const compileLevel = (obj: any, path: string[] = []): void => { for (const [key, value] of Object.entries(obj)) { const currentPath = [...path, key]; const pathKey = currentPath.join("."); compiledStructure.paths.push(pathKey); if (typeof value === "string") { // Compile field validator const validator = this.compileFieldValidator(value, options); compiledStructure.validators.set(pathKey, validator); compiledStructure.fields.set(pathKey, { type: "primitive", validator, }); } else if ( typeof value === "object" && value !== null && !Array.isArray(value) ) { // Nested object compiledStructure.isFlat = false; compiledStructure.fields.set(pathKey, { type: "object", schema: value, }); compileLevel(value, currentPath); } else if (Array.isArray(value) && value.length === 1) { // Array of objects compiledStructure.fields.set(pathKey, { type: "array", itemSchema: value[0], }); if (typeof value[0] === "object") { compileLevel(value[0], [...currentPath, "[]"]); } } } }; compileLevel(schema); return compiledStructure; } /** * Validate using pre-compiled structure */ private static validateWithCompiledStructure( value: any, structure: any ): SchemaValidationResult { if (typeof value !== "object" || value === null) { return { success: false, errors: [ErrorHandler.createTypeError([], "object", value)], warnings: [], data: value, }; } const errors: ValidationError[] = []; const validatedData: any = {}; // Fast path for flat structures if (structure.isFlat) { for (const [pathKey, validator] of structure.validators) { const fieldKey = pathKey.split(".").pop(); const fieldValue = value[fieldKey!]; const result = validator(fieldValue, value); // Pass full data context if (!result.success) { errors.push(...result.errors); } else if (result.data !== undefined) { validatedData[fieldKey!] = result.data; } } } else { // Complex nested validation for (const path of structure.paths) { const pathParts = path.split("."); const fieldValue = this.getValueByPath(value, pathParts); const field = structure.fields.get(path); if (field.type === "primitive") { const result = field.validator(fieldValue, value); // Pass full data context if (!result.success) { errors.push(...result.errors); } else { this.setValueByPath(validatedData, pathParts, result.data); } } } } return { success: errors.length === 0, errors, warnings: [], data: errors.length === 0 ? validatedData : undefined, }; } /** * Get value by path array */ private static getValueByPath(obj: any, path: string[]): any { let current = obj; for (const key of path) { if (current === null || current === undefined) return undefined; current = current[key]; } return current; } /** * Set value by path array */ private static setValueByPath(obj: any, path: string[], value: any): void { let current = obj; for (let i = 0; i < path.length - 1; i++) { const key = path[i]; if (!(key in current)) { current[key] = {}; } current = current[key]; } current[path[path.length - 1]] = value; } /** * Apply optimization strategies based on schema characteristics */ private static applyOptimizations( validator: CompiledValidator ): CompiledValidator { let optimized = validator; for (const strategy of this.optimizationStrategies) { if (strategy.condition(validator.metadata)) { optimized = strategy.optimize(optimized); this.compilationStats.optimizations++; } } return optimized; } /** * Register optimization strategies */ private static registerOptimizationStrategies(): void { // Union optimization strategy this.optimizationStrategies.push({ name: "union-caching", condition: (metadata) => metadata.hasUnions, optimize: (validator) => { // Apply union caching optimization return validator; }, }); // Array optimization strategy this.optimizationStrategies.push({ name: "array-batching", condition: (metadata) => metadata.hasArrays, optimize: (validator) => { // Apply array batching optimization return validator; }, }); // Nested object optimization this.optimizationStrategies.push({ name: "nested-flattening", condition: (metadata) => metadata.nestingLevel > 2, optimize: (validator) => { // Apply nested object flattening return validator; }, }); } /** * Generate cache key for schema */ private static generateCacheKey(schema: any, options: any): string { return JSON.stringify({ schema, options }); } /** * Cleanup cache when it gets too large */ private static cleanupCache(): void { if (this.compiledCache.size > this.maxCacheSize) { // Remove oldest entries const entries = Array.from(this.compiledCache.entries()); entries.sort((a, b) => a[1].compiledAt - b[1].compiledAt); const toRemove = entries.length - this.maxCacheSize; for (let i = 0; i < toRemove; i++) { this.compiledCache.delete(entries[i][0]); } } } /** * Get compilation statistics */ static getStats() { const hitRate = this.compilationStats.hits / (this.compilationStats.hits + this.compilationStats.misses); return { ...this.compilationStats, hitRate: hitRate || 0, cacheSize: this.compiledCache.size, }; } /** * Clear all caches */ static clearCache(): void { this.compiledCache.clear(); this.compilationStats = { hits: 0, misses: 0, compilations: 0, optimizations: 0, }; } }