Helpers & Utilities
Helper methods control validation flow, compose schemas, and provide utility functions for working with results.
Flow Control
check(predicate, message?)
Runs a custom validation predicate or type guard.
Issue Code: 'check:failed'
Basic Validation
import { InferOutput } from '@valchecker/internal'
const positive = v.number()
.check(value => value > 0, 'Must be positive')
positive.execute(5) // { value: 5 }
positive.execute(-1) // { issues: [{ code: 'check:failed', message: 'Must be positive' }] }Return Values
The check function can return:
true: Validation passesfalse: Validation fails with default/custom messagestring: Validation fails with that string as the message
import { InferOutput } from '@valchecker/internal'
const schema = v.string()
.check((value) => {
if (value.length < 3)
return 'Too short'
if (value.length > 20)
return 'Too long'
return true
})Type Guards (Narrowing)
import { InferOutput } from '@valchecker/internal'
const isString = (value: unknown): value is string => typeof value === 'string'
const schema = v.unknown()
.check(isString)
type T = InferOutput<typeof schema> // stringCross-Property Validation
import { InferOutput } from '@valchecker/internal'
const passwordConfirm = v.object({
password: v.string()
.min(8),
confirmPassword: v.string(),
})
.check((obj) => {
return obj.password === obj.confirmPassword || 'Passwords must match'
})Async Checks
import { InferOutput } from '@valchecker/internal'
const uniqueEmail = v.string()
.check(async (value) => {
const exists = await db.users.exists({ email: value })
return !exists || 'Email already registered'
})
const result = await uniqueEmail.execute('test@example.com')fallback(getValue, message?)
Provides a fallback value when validation fails. The failure is caught and replaced with the fallback.
import { InferOutput } from '@valchecker/internal'
const safeNumber = v.number()
.min(0)
.fallback(() => 0)
safeNumber.execute(42) // { value: 42 }
safeNumber.execute(-5) // { value: 0 } (min failed, used fallback)
safeNumber.execute('invalid') // { value: 0 } (number failed, used fallback)Dynamic Fallbacks
import { InferOutput } from '@valchecker/internal'
const schema = v.string()
.parseJSON()
.fallback(() => ({ items: [], count: 0 }))
schema.execute('invalid json')
// { value: { items: [], count: 0 } }Default Values for Optional Fields
import { InferOutput } from '@valchecker/internal'
const config = v.object({
port: [v.number()
.fallback(() => 3000)],
host: [v.string()
.fallback(() => 'localhost')],
})
config.execute({})
// { value: { port: 3000, host: 'localhost' } }transform(fn, message?)
Transforms the value to a new type or shape.
import { InferOutput } from '@valchecker/internal'
const schema = v.string()
.transform(value => value.split(','))
schema.execute('a,b,c') // { value: ['a', 'b', 'c'] }
type T = InferOutput<typeof schema> // string[]See Transforms for detailed documentation.
Schema Composition
use(schema)
Delegates validation to another schema. Useful for reusing schemas and composing validations.
import { InferOutput } from '@valchecker/internal'
// Define reusable schemas
const stringSchema = v.string()
.toTrimmed()
.toLowercase()
const userSchema = v.object({
name: v.unknown()
.use(stringSchema),
email: [v.unknown()
.use(stringSchema)], // Optional
})
userSchema.execute({
name: ' ALICE ',
})
// { value: { name: 'alice', email: undefined } }Composing Unknown Input
import { InferOutput } from '@valchecker/internal'
const dataSchema = v.unknown()
.use(v.object({
type: v.literal('user'),
payload: v.object({
name: v.string(),
}),
}))as<T>()
Type assertion step for converting types without runtime transformation. Use with caution.
import { InferOutput } from '@valchecker/internal'
const schema = v.unknown()
.as<string>()
// This doesn't validate at runtime - it only changes the type
type T = InferOutput<typeof schema> // stringMessage Handling
Global Message Handler
Define a message resolver when creating the valchecker instance:
import { InferOutput } from '@valchecker/internal'
const v = createValchecker({
steps: allSteps,
message: ({ code, payload, path }) => {
// Use your i18n library
return i18n.t(`validation.${code}`, { ...payload, path: path.join('.') })
},
})Message Resolution Priority
- Per-step message (highest priority)
import { InferOutput } from '@valchecker/internal'
v.number()
.min(1, 'Quantity must be at least 1')- Global handler
import { InferOutput } from '@valchecker/internal'
createValchecker({ steps, message: handler })- Built-in fallback (lowest priority)
Dynamic Messages
import { InferOutput } from '@valchecker/internal'
const schema = v.number()
.min(10, ({ payload }) =>
`Value must be at least 10, got ${payload.value}`)Recursive Schemas
generic<T>(factory)
Creates recursive/self-referential schemas with proper typing.
import { InferOutput } from '@valchecker/internal'
interface TreeNode {
value: number
children?: TreeNode[]
}
const treeSchema = v.object({
value: v.number(),
children: [v.array(
v.generic<{ output: TreeNode }>(() => treeSchema)
)], // Optional array of tree nodes
})
const result = treeSchema.execute({
value: 1,
children: [
{ value: 2 },
{
value: 3,
children: [{ value: 4 }],
},
],
})Loose Variants
looseNumber(message?)
Coerces strings to numbers before validation.
import { InferOutput } from '@valchecker/internal'
const schema = v.looseNumber()
schema.execute('42') // { value: 42 }
schema.execute(42) // { value: 42 }
schema.execute('3.14') // { value: 3.14 }
schema.execute('abc') // { issues: [...] }looseObject(shape, message?)
Alias for object() that explicitly allows unknown keys.
import { InferOutput } from '@valchecker/internal'
const schema = v.looseObject({
name: v.string(),
})
schema.execute({ name: 'Alice', extra: 'allowed' })
// { value: { name: 'Alice' } }
// Note: unknown keys are stripped from outputWorking with Results
Result Type
import { InferOutput } from '@valchecker/internal'
type Result<T>
= | { value: T }
| { issues: Issue[] }
interface Issue {
code: string
message: string
path: PropertyKey[]
payload: unknown
}Handling Results
import { InferOutput } from '@valchecker/internal'
const result = schema.execute(input)
if ('value' in result) {
// Success: result.value is fully typed
console.log(result.value)
}
else {
// Failure: result.issues contains all errors
for (const issue of result.issues) {
console.log(`[${issue.code}] ${issue.path.join('.')}: ${issue.message}`)
}
}Creating a Parse Function
import { InferOutput } from '@valchecker/internal'
function parse<T>(schema: Schema<T>, data: unknown): T {
const result = schema.execute(data)
if ('issues' in result) {
throw new ValidationError(result.issues)
}
return result.value
}
class ValidationError extends Error {
constructor(public issues: Issue[]) {
super(`Validation failed: ${issues.map(i => i.message)
.join(', ')}`)
this.name = 'ValidationError'
}
}Safe Parse Pattern
import { InferOutput } from '@valchecker/internal'
function safeParse<T>(schema: Schema<T>, data: unknown): { success: true, data: T } | { success: false, error: Issue[] } {
const result = schema.execute(data)
if ('value' in result) {
return { success: true, data: result.value }
}
return { success: false, error: result.issues }
}Standard Schema Compliance
Valchecker implements Standard Schema V1, enabling interoperability:
import type { StandardSchema } from '@standard-schema/spec'
import { InferOutput } from '@valchecker/internal'
// All valchecker schemas are Standard Schema compatible
const userSchema: StandardSchema<User> = v.object({
name: v.string(),
email: v.string(),
})
// Use with any Standard Schema compatible library