Core Concepts
Luq's design centers around three key principles: type-first validation, plugin architecture, and runtime safety.
Type-First Design
Luq builds validators from your existing TypeScript types. Your types remain the single source of truth.
// Define your TypeScript type as usual
type User = {
name: string;
age: number;
};
// Build a validator from the existing type
const validator = Builder()
.use(requiredPlugin)
.use(stringMinPlugin)
.for<User>() // Reference your existing type
.v('name', b => b.string.required())
.v('age', b => b.number.required())
.build();
Plugin Architecture
Validation methods come from plugins. Import only what you need for optimal bundle size.
import { Builder } from '@maroonedog/luq';
import { requiredPlugin } from '@maroonedog/luq/plugins/required';
import { stringMinPlugin } from '@maroonedog/luq/plugins/stringMin';
import { numberMinPlugin } from '@maroonedog/luq/plugins/numberMin';
// Only import what you need - tree-shaking friendly
const validator = Builder()
.use(requiredPlugin) // Adds .required() method
.use(stringMinPlugin) // Adds .min() for strings
.use(numberMinPlugin) // Adds .min() for numbers
.for<UserProfile>()
.v('name', b => b.string.required().min(2))
.v('age', b => b.number.required().min(18))
.build();
Validation & Results
Validators return Result objects for safe error handling.
const result = validator.validate({
name: 'John',
age: 25
});
if (result.isValid()) {
const data = result.unwrap();
// data is fully typed as UserProfile
} else {
result.errors.forEach(error => {
console.log(`${error.path}: ${error.message}`);
});
}
validate() vs parse()
- •
validate()
- Returns original values, validates structure - •
parse()
- Returns transformed values (when using transform plugins)
Field Path System
Access nested fields using dot notation. TypeScript provides autocomplete for valid paths.
type UserProfile = {
name: string;
address: {
street: string;
city: string;
};
tags: string[];
};
const validator = Builder()
.use(requiredPlugin)
.for<UserProfile>()
.v('name', b => b.string.required())
.v('address.street', b => b.string.required()) // Nested access
.v('address.city', b => b.string.required())
.v('tags', b => b.array.required())
.build();