Comprehensive Validation library
npm install @warlock.js/seal> Cast validation seals on your schemas to protect your data
A powerful, type-safe validation library for TypeScript with an intuitive API and framework-agnostic design.
``bash`
npm install @warlock.js/seal
`bash`
yarn add @warlock.js/seal
`bash`
pnpm add @warlock.js/seal
`typescript
import { v, type Infer } from "@warlock.js/seal";
// Define your validation schema (cast a seal)
const userSeal = v.object({
name: v.string().required().min(3),
email: v.string().email().required(),
age: v.int().min(18),
status: v.string().in(["active", "inactive"]),
});
// Extract TypeScript type automatically
type User = Infer
// Result: { name: string; email: string; age: number; status: string }
// Validate data
const result = await v.validate(userSeal, userData);
if (result.isValid) {
console.log("Data is sealed! ā
", result.data);
} else {
console.log("Seal broken! ā", result.errors);
// [
// { error: "The name must be at least 3 characters", path: "name" },
// { error: "The email must be a valid email address", path: "email" }
// ]
}
`
Seal uses a unique three-layer architecture that separates concerns:
``
Input Data
ā
š§ MUTATORS (Prep for Validation)
- Normalize data before validation
- Examples: trim(), lowercase(), toStartOfDay()
- Run BEFORE validation rules
ā
ā
VALIDATORS (Check Constraints)
- Validate against rules
- Examples: email(), min(), after()
- Return errors if validation fails
ā
šØ TRANSFORMERS (Format Output)
- Format validated data for output
- Examples: toISOString(), toFormat()
- Run AFTER validation passes
ā
Output Data
`typescript
const schema = v
.date()
.toStartOfDay() // š§ Mutator: normalize to 00:00:00
.after("2024-01-01") // ā
Validator: check Date object
.toISOString(); // šØ Transformer: output as ISO string
const result = await v.validate(schema, "2024-06-15 14:30:00");
// result.data = "2024-06-15T00:00:00.000Z"
`
Why this matters:
- Mutators prepare data for validation (no more string comparison issues!)
- Validators check constraints on normalized data
- Transformers format output without affecting validation
---
Automatically extract TypeScript types from your schemas:
`typescript
const schema = v.object({
name: v.string().required(),
age: v.int(),
tags: v.array(v.string()),
});
type User = Infer
// { name: string; age: number; tags: string[] }
`
Readable, chainable methods:
`typescript
v.string().required().email().min(5).max(100);
v.int().min(0).max(100).positive();
v.array(v.string()).minLength(1).unique();
`
Apply different validators based on other field values:
`typescript
const schema = v.object({
type: v.string().required().in(["admin", "user"]),
role: v.string().when("type", {
is: {
admin: v.string().required().in(["super", "moderator"]),
user: v.string().required().in(["member", "guest"]),
},
}),
});
// If type is "admin", role must be "super" or "moderator"
// If type is "user", role must be "member" or "guest"
`
Compare fields against each other (global or sibling scope):
`typescript
const schema = v.object({
password: v.string().required().min(8),
confirmPassword: v.string().required().sameAs("password"), // Compare with password field
startDate: v.date().required(),
endDate: v.date().required().after("startDate"), // Compare with startDate field
});
`
Add your own validation logic:
`typescript`
v.string().refine(async value => {
const exists = await checkUsername(value);
if (exists) return "Username already taken";
});
Normalize input and format output:
`typescript
// String mutators
v.string()
.trim() // Remove whitespace
.lowercase() // Convert to lowercase
.email() // Validate email
.toJSON(); // Transform to JSON string
// Date mutators & transformers
v.date()
.toStartOfDay() // Normalize to midnight
.after("2024-01-01")
.toISOString(); // Output as ISO string
`
---
For complete documentation, visit: https://warlock.js.org/seal
| Validator | Purpose | Example |
| ------------- | ------------------ | -------------------------------- |
| v.string() | String validation | v.string().email().min(3) |v.int()
| | Integer validation | v.int().min(0).max(100) |v.float()
| | Float validation | v.float().positive() |v.number()
| | Number validation | v.number().min(0) |v.boolean()
| | Boolean validation | v.boolean().accepted() |v.date()
| | Date validation | v.date().after(new Date()) |v.array()
| | Array validation | v.array(v.string()).min(1) |v.object()
| | Object validation | v.object({ name: v.string() }) |v.scalar()
| | Scalar values | v.scalar().in([1, "2", true]) |
Available on all validators:
| Method | Purpose |
| -------------------------- | -------------------------------- |
| .required() | Value must be present |.optional()
| | Value is optional |.forbidden()
| | Value must not be present |.equals(value)
| | Must equal specific value |.default(value)
| | Set default value |.allowsEmpty()
| | Skip validation if empty |.when(field, conditions)
| | Conditional validation |.omit()
| | Validate but exclude from output |.refine(callback)
| | Custom validation logic |
---
`typescript`
const registerSchema = v.object({
email: v.string().required().email(),
password: v.string().required().min(8).strongPassword(),
confirmPassword: v.string().required().sameAs("password"),
age: v.int().required().min(18).max(120),
terms: v.boolean().required().accepted(),
});
`typescript
const formSchema = v.object({
accountType: v.string().required().in(["personal", "business"]),
// Required only if accountType is "business"
companyName: v.string().requiredIf("accountType", { is: "business" }),
// Conditional validation
taxId: v.string().when("accountType", {
is: {
business: v
.string()
.required()
.pattern(/^[0-9]{9}$/),
personal: v.string().forbidden(),
},
}),
});
`
`typescript
const bookingSchema = v.object({
checkIn: v.date().required().afterToday(),
checkOut: v
.date()
.required()
.after("checkIn") // Compare with checkIn field
.withinDays(30), // Max 30 days from checkIn
});
`
`typescript
const tagsSchema = v
.array(v.string())
.required()
.minLength(1)
.maxLength(10)
.unique();
const usersSchema = v
.array(
v.object({
name: v.string().required(),
email: v.string().email(),
})
)
.minLength(1);
`
---
For Warlock.js projects, framework-specific validators are available:
`typescript
import { v } from "@warlock.js/core/v";
const schema = v.object({
email: v.string().email().unique(User), // Database validation
avatar: v.file().image().maxSize(5000000), // File upload validation
uploadId: v.string().uploadable(), // Upload hash validation
});
`
---
š® Magical Context
Warlocks use seals to protect and verify. Your validation schemas are seals that protect your data integrity.
š» Programming Context
A "seal of approval" - data that passes validation is sealed and verified.
⨠Developer Experience
Clean, intuitive API that feels natural:
`typescript``
const seal = v.object({ ... }); // Cast a seal
await v.validate(seal, data); // Verify with the seal
---
Seal is designed around three principles:
1. Type Safety First - TypeScript support is not an afterthought
2. Framework Agnostic - Works anywhere JavaScript runs
3. Intuitive API - If it feels right, it probably works
---
For complete documentation, visit: https://warlock.js.org/seal
The documentation includes:
- š Getting Started Guide
- šÆ Core Concepts
- š Validator Reference
- š All Validation Rules
- š Plugin System
- šØ Custom Rules
---
MIT
---
See main Warlock.js repository
---
Cast your seals and protect your data! š®āØ