Quick Start
Get valchecker running in minutes and start validating runtime data with full TypeScript type safety.
Installation
pnpm add valchecker
# or
npm install valchecker
# or
yarn add valcheckerImport Strategy
Valchecker offers two ways to import validation steps:
Option 1: All Steps (Convenience)
Import allSteps to get every built-in validator. Best for prototyping, CLIs, or apps where bundle size isn't critical.
import { allSteps, createValchecker } from 'valchecker'
const v = createValchecker({ steps: allSteps })Option 2: Selective Imports (Tree-Shaking)
Import only the steps you need for maximum bundle optimization.
import { createValchecker, number, object, string } from 'valchecker'
const v = createValchecker({ steps: [string, number, object] })Recommended Approach
During development, use allSteps for convenience. Before production, analyze your usage and switch to selective imports for smaller bundles.
Your First Schema
Define a schema by chaining validation and transformation steps:
const userSchema = v.object({
name: v.string()
.toTrimmed(),
age: v.number()
.min(0),
email: v.string()
.toLowercase(),
})Every method call returns a new schema with the step appended. Schemas are immutable and can be safely reused.
Execute Validation
Valchecker provides two execution methods:
execute() - Always Async
Use execute() for async validation. It always returns a Promise:
const result = await userSchema.execute({
name: ' Alice ',
age: 25,
email: 'ALICE@EXAMPLE.COM',
})
if ('value' in result) {
console.log(result.value)
// => { name: 'Alice', age: 25, email: 'alice@example.com' }
}
else {
console.error(result.issues)
// Array of structured issues with codes, paths, and messages
}Understanding Results
Every validation returns a discriminated union result:
type ValidationResult<T>
= | { value: T }
| { issues: Issue[] }Working with Issues
Each issue contains structured information for debugging and user feedback:
interface Issue {
code: string // e.g., 'string:expected_string', 'min:expected_min'
path: PropertyKey[] // e.g., ['users', 0, 'email']
message: string // Human-readable message
payload: unknown // Issue-specific data
}Example of handling issues:
const result = await userSchema.execute({ name: '', age: -5, email: 'invalid' })
if ('issues' in result) {
for (const issue of result.issues) {
console.log(`[${issue.code}] ${issue.path.join('.')}: ${issue.message}`)
}
// [min:expected_min] name: Expected minimum value of 1
// [min:expected_min] age: Expected minimum value of 0
// [string:expected_string] email: Expected a string.
}Transform and Fallback
Chain transformations and provide fallback values for resilient pipelines:
const payloadSchema = v.unknown()
.parseJSON('Invalid JSON')
.fallback(() => ({ items: [] }))
.check(value => Array.isArray(value.items), 'items must be an array')
.use(
v.object({
items: v.array(
v.object({
id: v.string()
.toTrimmed(),
quantity: v.number()
.integer()
.min(1),
}),
)
.toFiltered(item => item.quantity > 0),
}),
)Transform Chain
Transforms update both the runtime value and the TypeScript type:
const schema = v.string()
.toTrimmed() // string → string (trimmed)
.transform(s => s.split(',')) // string → string[]
.transform(arr => arr.length) // string[] → number
// The output type will be inferred through the transforms
const result = await schema.execute('a,b,c')
// result.value is typed as number (length of the array)Fallback Values
fallback() catches validation failures and provides alternative values:
const schema = v.number()
.min(0)
.fallback(() => 0)
const result1 = await schema.execute(-5) // => { value: 0 }
const result2 = await schema.execute(10) // => { value: 10 }Async Validation
Mix async steps (database lookups, API calls) seamlessly:
const usernameSchema = v.string()
.toTrimmed()
.min(3)
.check(async (value) => {
const exists = await db.users.exists(value)
return exists ? 'Username already taken' : true
})
const result = await usernameSchema.execute('alice')Async Detection
When a pipeline contains async steps, run() returns a Promise. Use execute() if you want consistent async behavior.
Type Inference
Valchecker automatically infers output types through the entire pipeline:
import { InferOutput } from '@valchecker/internal'
const schema = v.object({
name: v.string(),
age: v.number(),
})
.transform(user => ({
...user,
isAdult: user.age >= 18,
}))
type User = InferOutput<typeof schema>
// { name: string; age: number; isAdult: boolean }
const result = await schema.execute({ name: 'Bob', age: 30 })
if ('value' in result) {
// result.value is fully typed
console.log(result.value.isAdult) // ✓ Type-safe
}Extracting Input and Output Types
Use TypeScript's utility types to extract types from schemas:
import { InferInput, InferOutput } from '@valchecker/internal'
const schema = v.object({
name: v.string()
.toTrimmed(),
tags: [v.array(v.string())], // Optional
})
type Input = InferInput<typeof schema>
// { name: string; tags?: string[] | undefined }
type Output = InferOutput<typeof schema>
// { name: string; tags: string[] | undefined }Standard Schema Compliance
Valchecker implements the Standard Schema V1 specification, enabling interoperability with Standard Schema compatible libraries:
import type { StandardSchema } from '@standard-schema/spec'
const userSchema = v.object({
name: v.string(),
email: v.string(),
})
// Works with any library that accepts StandardSchema
function validate<T>(schema: StandardSchema<T>, input: unknown): T {
const result = schema['~standard'].validate(input)
// ...
}Common Patterns
Optional Fields
const schema = v.object({
required: v.string(),
optional: [v.string()], // Optional with [] wrapper
withDefault: [v.string()
.fallback(() => 'default')], // Optional with default
})Union Types
import { InferOutput } from '@valchecker/internal'
const schema = v.union([
v.string(),
v.number(),
v.literal(null),
])
type T = InferOutput<typeof schema> // string | number | nullNested Objects
const addressSchema = v.object({
street: v.string(),
city: v.string(),
zip: v.string(),
})
const userSchema = v.object({
name: v.string(),
address: addressSchema,
billingAddress: [addressSchema], // Optional
})Next Steps
- Core Philosophy - Understand the step pipeline architecture
- Custom Steps - Create your own validation steps
- API Reference - Explore all available validation steps
- Examples - See real-world validation patterns