Runtime-first validation with zero guesswork
A modular ESM-only TypeScript validation library with immutable fluent steps, transformed-output inference, structured non-empty issues, sync/maybe-async execution, Standard Schema V1, and selective tree-shakable plugin registration.
- Node.js 22 or newer
- ESM; CommonJS may use dynamic
import('valchecker')
pnpm add valchecker
# or
npm install valcheckerimport { v } from 'valchecker'
const userSchema = v.object({
name: v.string().toTrimmed().isNotEmpty(),
email: v.string().toLowercase().isEmail(),
age: v.looseNumber().isFinite().isInteger().isAtLeast(0),
nickname: [v.string()],
})
const result = await userSchema.execute({
name: ' Alice ',
email: 'ALICE@EXAMPLE.COM',
age: '25',
})
if (v.isSuccess(result))
console.log(result.value)
else
console.error(result.issues)Every fluent method returns a new reusable schema. One-element tuples mark object fields optional and materialize undefined when absent.
The default v includes every built-in step. Bundle-sensitive applications can register only what they use:
import { createValchecker, isAtLeast, isFinite, number } from 'valchecker'
const v = createValchecker({ steps: [number, isFinite, isAtLeast] })allSteps is available for custom complete instances and is derived from runtime-marked public plugin exports rather than a duplicate static list.
- Initial schemas use nouns:
string(),number(),object(),looseBoolean(). - Built-in validations use
isXxx()and preserve successful values. - Concrete transformations use
toXxx()and change representation. - Generic escape hatches remain
check()andtransform().
number() accepts every JavaScript number, including NaN and infinities. Add isFinite() when finite values are required. A named validation enforces only its stated condition; for example, isAtLeast(0) accepts positive infinity.
Loose primitives accept the primitive or the corresponding TypeScript-template-compatible string representation and normalize the output. They do not perform unrestricted JavaScript coercion.
Native conversion steps delegate to JavaScript:
v.string().toNumber() // Number(value)
v.unknown().toBoolean() // Boolean(value)
v.string().toBigint() // BigInt(value)Use explicit policy steps such as toSafeNumber() and toMappedBoolean() when narrower semantics are required.
object()omits unknown output properties.strictObject()rejects unknown enumerable own string and symbol keys.looseObject()preserves unknown own properties.- Arrays, tuples, Sets, Maps, and records validate and transform nested values.
union()returns the first successful branch.variant()dispatches directly from an own discriminator.intersection()composes compatible branch outputs.
Set and Map schemas preserve insertion order and reject duplicate transformed items or keys rather than silently losing data.
Use use() for schema delegation and fallback() for documented recovery. fallback() recovers validation and operation failures only; internal issues are fatal.
A synchronous pipeline returns directly. A callback-driven schema may return a promise only when asynchronous work is reached; an earlier failure can remain synchronous. Awaiting either mode is safe. Append .toAsync() when every call must return a native promise.
type ExecutionResult<Value, Issue>
= | { value: Value }
| { issues: [Issue, ...Issue[]] }
interface Issue {
code: string
category: 'validation' | 'operation' | 'internal'
payload: unknown
message: string
path: PropertyKey[]
context?: Array<{ type: string, [key: string]: unknown }>
}Use codes, categories, paths, context, and payloads for machine behavior rather than parsing messages.
Message priority is step message, nearest enclosing structure, outer structures, originating instance global resolver, step default, then "Invalid value.".
import type { InferInput, InferOutput } from 'valchecker'
type Input = InferInput<typeof userSchema>
type Output = InferOutput<typeof userSchema>| Package | Purpose |
|---|---|
valchecker |
application API, default v, built-ins and helpers |
@valchecker/all-steps |
complete runtime-marked plugin collection |
@valchecker/internal |
semver-covered advanced types and plugin author API |
Every schema exposes ~standard for Standard Schema V1 integrations. Public exports are recorded in api-surface.json.
- Quick Start
- Valchecker 1.0 Contract
- Migrating to 1.0
- Custom Steps
- API Reference
- Complete migration guide
- Support policy
- Contributing
- Release process
pnpm install --frozen-lockfile
pnpm verifypnpm verify is the complete gate and runs the same commands as CI. See
Contributing before opening a pull request.