TypeScript Validation Made Simple
Use your existing TypeScript types to build validators. No schema rewriting, just add validation rules to the types you already have.
npm install @maroonedog/luq@alpha
See It In Action
Traditional Schema-Based Approach
// Define schema (types are generated)
const UserSchema = z.object({
name: z.string().min(3),
email: z.string().email(),
age: z.number().min(18),
role: z.enum(['admin', 'user']),
settings: z.object({
notifications: z.boolean(),
theme: z.enum(['light', 'dark'])
})
});
// Extract type from schema
type User = z.infer<typeof UserSchema>;
// Custom business logic
const schema = UserSchema.refine(
(data) => {
// Inline custom validation
if (data.role === 'admin' && data.age < 21) {
return false;
}
return true;
},
{ message: 'Admins must be 21 or older' }
);
// Validate
const result = schema.safeParse(userData);
Luq Type-First Approach
// Use existing TypeScript types
type User = {
name: string;
email: string;
age: number;
role: 'admin' | 'user';
settings: {
notifications: boolean;
theme: 'light' | 'dark';
};
};
// Create reusable business logic plugin
const adminAgePlugin = plugin({
name: 'adminAge',
methodName: 'adminAge',
allowedTypes: ['number'] as const,
category: 'standard',
impl: (options?: ValidationOptions) => ({
check: (value: any, context: any) => {
const parent = context?.parent;
return parent?.role !== 'admin' || value >= 21;
},
code: 'admin_age',
getErrorMessage: () => 'Admins must be 21 or older',
params: []
})
});
// Build validator with plugins
const validator = Builder()
.use(requiredPlugin)
.use(stringMinPlugin)
.use(stringEmailPlugin)
.use(numberMinPlugin)
.use(adminAgePlugin)
.for<User>()
.v('name', b => b.string.required().min(3))
.v('email', b => b.string.required().email())
.v('age', b => b.number.required().min(18).adminAge())
.v('role', b => b.required())
.build();
// Validate with full type safety
const result = validator.validate(userData);
How It Works
Three simple steps to add validation to your existing TypeScript types
Use Your Types
Keep your existing TypeScript interfaces exactly as they are
Add Validation
Build validators using the plugins you need
Validate Data
Get type-safe results with detailed error messages
✓ Your existing code
// Keep your types as-is
interface User {
name: string;
email: string;
age: number;
}
// Your existing function
function saveUser(user: User) {
// No validation... 😬
return api.post('/users', user);
}
+ Add validation with Luq
// Same interface, no changes needed
interface User {
name: string;
email: string;
age: number;
}
// Build validator from your type
const validator = Builder()
.use(requiredPlugin)
.use(stringEmailPlugin)
.use(numberMinPlugin)
.for<User>()
.v("name", b => b.string.required())
.v("email", b => b.string.required().email())
.v("age", b => b.number.required().min(13))
.build();
function saveUser(userData: unknown) {
const result = validator.validate(userData);
if (result.isValid()) {
const user = result.unwrap(); // Type: User
return api.post('/users', user);
} else {
throw new Error(result.errors[0].message);
}
}
No Rewriting
Use your existing TypeScript interfaces. No need to convert to schemas or learn new syntax.
Type Safe
Full TypeScript integration. IDE autocomplete works perfectly with your field names and types.
Small Bundle
Import only the validation rules you need. Tree-shaking ensures minimal bundle size.
Why Choose Luq?
Built for teams who want validation without the complexity
Works With Your Existing Types
No need to rewrite your interfaces or convert to schemas. Luq works with the TypeScript types you already have.
// Your existing interface
interface User {
name: string;
email: string;
age: number;
}
// Just add validation
.for<User>() // Full type safety
Type-Safe Builder Pattern
IntelliSense shows only the methods available for each field type. String fields get string methods, numbers get number methods.
// IDE autocomplete works perfectly
.v("name", b => b.string.required().min(2))
.v("age", b => b.number.required().min(13))
.v("email", b => b.string.required().email())
Small Bundle Size
Import only the validation plugins you need. Unused plugins are automatically tree-shaken from your bundle.
// Only import what you use
import { requiredPlugin } from '@maroonedog/luq/plugins/required';
import { stringEmailPlugin } from '@maroonedog/luq/plugins/stringEmail';
// Final bundle: ~19KB gzipped
Cross-Field Validation
Validate fields that depend on other fields. Compare passwords, check conditional requirements, and more.
// Password confirmation
.v("confirmPassword", b =>
b.string.required().compareField("password")
)
// Conditional validation
.v("phone", b =>
b.string.requiredIf((data) => data.contactMethod === "phone")
)
Ready to Start Using Type-Safe Validation?
Start using Luq today and experience type-safe, plugin-based validation