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
text/typescript
/**
* 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,
};
}
}