Fortify Schema: Interface-Style TypeScript Validation with Conditional Logic
A TypeScript validation library designed around familiar interface syntax and advanced conditional validation capabilities.
When building TypeScript applications that handle external data, validation becomes essential. Whether you're processing API requests, user forms, or configuration files, you need a way to ensure data integrity while maintaining type safety.
Fortify Schema addresses this need by providing a validation library that uses interface-like syntax familiar to TypeScript developers, combined with advanced conditional validation capabilities for complex business logic.
Core Design Principles
Interface-Native Syntax: Schema definitions use a string-based syntax that mirrors TypeScript interface declarations, making validation rules intuitive for TypeScript developers.
Conditional Validation: Built-in support for runtime conditional logic, allowing fields to be required, optional, or have different validation rules based on other data properties.
Type Integration: Complete TypeScript inference ensures validated data maintains proper typing without additional type assertions.
Basic Schema Definition
Here's how you define a schema in Fortify Schema:
import { Interface } from "fortify-schema";
const UserSchema = Interface({
id: "uuid",
email: "email",
name: "string(2,50)",
age: "number(18,120)?",
role: "admin|user|guest",
tags: "string[]?",
createdAt: "date"
});
// Validation with full type inference
const result = UserSchema.safeParse(userData);
if (result.success) {
// result.data is properly typed
console.log(result.data.email); // string
console.log(result.data.age); // number | undefined
}
Comprehensive Type Support
Fortify Schema supports a wide range of validation types:
const ComprehensiveSchema = Interface({
// Basic types
name: "string",
count: "number",
active: "boolean",
timestamp: "date",
// Constrained types
username: "string(3,20)", // 3-20 characters
score: "number(0,100)", // Range validation
// Format validation
email: "email",
website: "url",
phone: "phone",
userId: "uuid",
// Arrays with constraints
tags: "string[]", // Required array
scores: "number[]?", // Optional array
limitedTags: "string[](1,5)", // 1-5 items
// Union types
status: "active|inactive|pending",
priority: "low|medium|high",
// Literal values
version: "2.0",
type: "user",
// Optional fields
description: "\"string?\","
metadata: "any?"
});
Nested Object Validation
Complex nested structures are handled naturally:
const ProfileSchema = Interface({
user: {
id: "uuid",
email: "email",
profile: {
firstName: "string(1,50)",
lastName: "string(1,50)",
avatar: "url?",
settings: {
theme: "light|dark|auto",
language: "string(/^[a-z]{2}$/)",
notifications: {
email: "boolean",
push: "boolean",
sms: "boolean"
}
}
}
},
metadata: {
createdAt: "date",
lastUpdated: "date",
version: "number"
}
});
Conditional Validation Logic
The conditional validation system allows you to express complex business rules directly in schema definitions:
const ConditionalSchema = Interface({
// Context objects for conditional logic
config: "any?",
user: "any?",
// Core data
id: "uuid",
type: "individual|business",
// Conditional fields based on runtime properties
businessName: "when type=business *? string(2,100) : =null",
taxId: "when type=business *? string : =null",
// Conditional validation based on configuration
requiresApproval: "when config.strictMode.$exists() *? boolean : =false",
// Default values based on conditions
permissions: "when user.role=admin *? string[] : =[\"read\"]",
// Complex nested conditions
advancedFeatures: "when user.plan=premium && config.enableAdvanced.$exists() *? object : =null"
});
Runtime Validation Methods
The conditional system includes several runtime validation methods:
const RuntimeValidationSchema = Interface({
data: "any?",
// Property existence
hasProfile: "when data.profile.$exists() *? boolean : =false",
// Empty state checking
hasItems: "when data.items.$empty() *? boolean : =true",
// Null validation
isConfigured: "when data.config.$null() *? boolean : =false",
// String operations
hasKeyword: "when data.title.$contains(important) *? boolean : =false",
isPrefixed: "when data.code.$startsWith(PRE-) *? boolean : =false",
// Numeric range validation
inValidRange: "when data.score.$between(0,100) *? boolean : =false",
// Value inclusion
hasValidStatus: "when data.status.$in(active,pending,completed) *? boolean : =false"
});
Practical Application: API Request Validation
Here's a real-world example for validating API requests:
const CreateProductRequestSchema = Interface({
// Request context
user: "any?",
permissions: "any?",
// Product data
name: "string(1,200)",
description: "\"string(,2000)?\","
price: "number(0.01,999999.99)",
category: "electronics|books|clothing|home|sports",
// Inventory
initialStock: "number(0,)",
trackInventory: "boolean",
// Conditional validation based on user permissions
bulkDiscount: "when permissions.canSetBulkPricing.$exists() *? object? : =null",
// Admin-only fields
internalNotes: "when user.role=admin *? string? : =null",
costPrice: "when user.role=admin *? number(0,) : =null",
// Category-specific requirements
isbn: "when category=books *? string : =null",
size: "when category=clothing *? string : =null",
// Media uploads
images: "url[](1,10)",
documents: "when category=books *? url[] : =[]",
// Metadata
tags: "string[](0,20)",
status: "draft|active",
publishAt: "date?"
});
Schema Transformations
Fortify Schema provides utilities for creating schema variations:
const BaseUserSchema = Interface({
id: "uuid",
email: "email",
firstName: "string",
lastName: "string",
password: "string",
role: "user|admin",
createdAt: "date"
});
// Schema transformations
const PublicUserSchema = Mod.omit(BaseUserSchema, ["password"]);
const UpdateUserSchema = Mod.partial(Mod.omit(BaseUserSchema, ["id", "createdAt"]));
const AdminUserSchema = Mod.extend(BaseUserSchema, {
permissions: "string[]",
lastLogin: "date?",
adminNotes: "string?"
});
// Combining schemas
const UserWithProfileSchema = Mod.extend(BaseUserSchema, {
profile: {
avatar: "url?",
bio: "string(,500)?",
website: "url?",
location: "string?"
}
});
Error Handling and Debugging
Fortify Schema provides detailed error information for debugging:
const result = UserSchema.safeParse(invalidData);
if (!result.success) {
result.errors.forEach(error => {
console.log(`Field: ${error.path.join('.')}`);
console.log(`Error: ${error.message}`);
console.log(`Code: ${error.code}`);
console.log(`Expected: ${error.expected}`);
console.log(`Received: ${error.received}`);
});
}
Performance Considerations
Fortify Schema is designed for production use with several performance optimizations:
- Schema Compilation: Schemas are optimized at creation time rather than during validation
- Caching: Repeated validations benefit from intelligent caching of constraint checks
- Memory Efficiency: Minimal memory overhead per validation operation
- Early Termination: Validation stops on first error for faster feedback
Development Tooling
A VS Code extension provides enhanced development experience:
- Syntax highlighting for schema definitions
- IntelliSense with type and method completion
- Real-time validation with inline error detection
- Documentation on hover for validation types
Installation and Setup
npm install fortify-schema
Basic usage:
import { Interface } from "fortify-schema";
const schema = Interface({
email: "email",
name: "string(2,100)",
age: "number(18,)?"
});
const result = schema.safeParse(data);
Use Cases
Fortify Schema is well-suited for:
- API Request Validation: Complex request schemas with role-based field requirements
- Configuration Management: Validating application configuration with conditional settings
- Form Processing: Multi-step forms with conditional field requirements
- Data Pipeline Validation: Ensuring data integrity in processing workflows
- Business Rule Enforcement: Expressing complex validation logic in a readable format
Technical Architecture
The library provides several validation methods:
-
parse(data)
: Synchronous validation with exceptions -
safeParse(data)
: Safe validation returning result objects -
parseAsync(data)
: Asynchronous validation with promises -
safeParseAsync(data)
: Safe asynchronous validation
All methods maintain full TypeScript type inference, ensuring validated data is properly typed without additional assertions.
Fortify Schema offers TypeScript developers a validation solution that integrates naturally with existing codebases while providing advanced conditional validation capabilities. The interface-like syntax reduces the learning curve, while the conditional validation system handles complex business logic requirements.
Resources:
Have you worked with conditional validation requirements in your TypeScript projects? What approaches have you found most effective?
Top comments (1)
π€©π₯°