Compose validators by chaining steps:
import {
ComposedValSan,
TrimSanitizer,
LowercaseSanitizer,
LengthValidator,
AlphaNumericValidator,
} from 'valsan';
class UsernameValSan extends ComposedValSan<string, string> {
constructor() {
super([
new TrimSanitizer(),
new LowercaseSanitizer(),
new LengthValidator({ minLength: 3, maxLength: 60 }),
new AlphaNumericValidator()
]);
}
}- Keep steps simple: Each step should do one thing well
- Order matters: Place normalization steps first, validation next
- Reuse components: Build a library of simple validators to compose
- Name your compositions: Create named classes instead of inline compositions
- Test components individually: Unit test each step before composing
- Use options for flexibility: Pass options to steps for configurability
You can pass options from the composed validator down to individual child steps. This is perfect for creating configurable validators where parent options control child behavior:
interface EmailAddressOptions extends ComposedValSanOptions {
maxLength?: number;
minLength?: number;
}
class EmailAddressValSan extends ComposedValSan<string, string> {
constructor(options: EmailAddressOptions = {}) {
super([
new TrimSanitizer(),
new LengthValidator({
minLength: options.minLength ?? 3,
maxLength: options.maxLength ?? 255
}),
new LowercaseSanitizer(),
new EmailValidator(),
], options);
}
}
// Usage examples
const standard = new EmailAddressValSan({ maxLength: 254 });
const strict = new EmailAddressValSan({ minLength: 8, maxLength: 50 });
await standard.run('user@example.com'); // ✅ passes
await strict.run('a@b.c'); // ❌ fails minLength