Everything, in the order you need it.
A single-page reference for VLD 3.0.11. Every snippet below was executed against the published package before it was published to this page — if it is written here, it runs.
01
Installation
One dependency, zero of its own. VLD ships ESM and CJS builds with declaration files and no runtime dependency tree.
npm install @oxog/vld# oryarn add @oxog/vld# orpnpm add @oxog/vld# orbun add @oxog/vld# ordeno add npm:@oxog/vld02
Quick start
Define a schema, extract its type, parse safely. Thev.infertype always matches what the schema returns at runtime.
1import { v } from "@oxog/vld";2 3const userSchema = v.object({4 name: v.string().min(2).max(100),5 email: v.string().email(),6 age: v.number().int().positive().optional(),7 role: v.enum("admin", "user", "guest").default("user"),8 tags: v.array(v.string()).min(1),9});10 11// Extract the TypeScript type from the schema12type User = v.infer<typeof userSchema>;13 14// Never throws15const result = userSchema.safeParse(await request.json());16 17if (result.success) {18 console.log(result.data); // User19} else {20 console.error(result.error.issues);21}03
Primitives & formats
Every leaf validator is chainable and immutable — each call returns a new schema, so sharing a base schema is safe.
1v.string() v.number() v.int() v.int32()2v.bigint() v.boolean() v.date() v.symbol()3v.literal("x") v.enum("a", "b", "c")4v.any() v.unknown() v.null() v.undefined()5v.void() v.never()6 7// Constraints on strings8v.string()9 .min(3)10 .max(100)11 .email()12 .url()13 .uuid()14 .regex(/^[a-z0-9]+$/)15 .startsWith("https://")16 .endsWith(".json")17 .trim()18 .toLowerCase();1// Top-level format helpers2v.email() v.uuid() v.creditCard() v.jwt()3v.cuid() v.cuid2() v.nanoid() v.ulid()4v.ipv4() v.ipv6()5v.iso.date()6 7// Number constraints8v.number()9 .min(0)10 .max(100)11 .int()12 .positive()13 .negative()14 .nonnegative()15 .multipleOf(5)16 .finite()17 .safe();Verified against @oxog/vld@3.0.11: the README lists v.nullish() and v.uint8array(), but neither resolves in this release — v.nullish() throws on safeParse and v.uint8array is not a function. Use v.union(v.null(), v.undefined()) instead. They are left out above rather than documented as working.
04
Objects & collections
Object schemas are cheap to reshape. pick, omit, partial and extend return new schemas without touching the original.
1const profile = v.object({2 username: v.string().min(3),3 age: v.number().optional(),4});5 6profile.pick("username"); // keep one field7profile.omit("age"); // drop one field8profile.partial(); // all optional9profile.strict(); // reject unknown keys10profile.passthrough(); // keep unknown keys11profile.extend({ bio: v.string() });12 13// Arrays and friends14v.array(v.string()).min(1).max(10);15v.tuple(v.string(), v.number());16v.record(v.string(), v.number());17v.set(v.string());18v.map(v.string(), v.number());05
Composition
Discriminated unions short-circuit on the discriminant, which is why they are one of the fastest paths in the library. Refinements and transformations chain in any order.
1// Unions and intersections2v.union(v.string(), v.number());3v.intersection(schemaA, schemaB);4v.xor(schemaA, schemaB);5 6// Discriminated unions stay on the fast path7const eventSchema = v.discriminatedUnion(8 "type",9 v.object({ type: v.literal("click"), x: v.number(), y: v.number() }),10 v.object({ type: v.literal("scroll"), offset: v.number() }),11);12 13// Recursive schemas14type Tree = { id: string; children?: Tree[] };15let treeSchema: unknown;16treeSchema = v.lazy(() =>17 v.object({ id: v.string(), children: v.array(treeSchema).optional() }),18);19 20// Transform, refine, default, catch21v.string()22 .transform((value) => value.trim())23 .refine((value) => value.length >= 3, "Must be at least 3 characters")24 .default("fallback")25 .catch("catch-on-error");06
Error handling
Failures arrive as structured issues with code, path, expected and received. Four formatters cover forms, CLIs, UIs and Zod consumers.
1import { flattenError, prettifyError, treeifyError, toZodError } from "@oxog/vld";2 3const result = schema.safeParse(input);4 5if (!result.success) {6 // Ready for form libraries7 const { fieldErrors, formErrors } = flattenError(result.error);8 9 // Human-readable for CLIs and logs10 console.log(prettifyError(result.error));11 // ✖ Invalid field "name": String must be at least 2 characters12 // → at name13 14 // Nested structure for UI trees15 const tree = treeifyError(result.error);16 17 // Anything expecting a ZodError keeps working18 const zodError = toZodError(result.error);19 zodError.format();20 zodError.flatten();21}07
Localization
32 locales ship in the box. Note the ordering rule: messages are bound when a schema is built, so call setLocale() before constructing schemas — or rebuild them when the visitor changes language.
1import { setLocale, v } from "@oxog/vld";2 3setLocale("tr");4 5// Messages bind while the schema is constructed, so build schemas6// after switching locale (or rebuild them when the language changes).7const login = v.object({ password: v.string().min(8) });8 9login.safeParse({ password: "ab" });10// ✖ "password" alanı geçersiz: Metin en az 8 karakter olmalı11 12// Only ship the locales you need13import { setLocaleAsync, preloadLocales } from "@oxog/vld/locales/lazy";14 15await setLocaleAsync("tr");16await preloadLocales(["en", "de", "ja"]);08
Mini API
For bundle-critical runtimes, @oxog/vld/mini exposes bare functions with no class machinery.
1import { array, number, object, optional, string } from "@oxog/vld/mini";2 3const userSchema = object({4 name: string().min(2),5 age: optional(number().positive()),6 roles: array(string()),7});8 9userSchema.safeParse({ name: "Ada", roles: ["admin"] });09
Result pattern & codecs
A small functional layer for callers who would rather not throw, plus bidirectional codecs for moving between representations.
1import {2 Err,3 Ok,4 base64ToBytes,5 hexToBytes,6 isOk,7 jsonCodec,8 match,9 stringToNumber,10 tryCatch,11 unwrapOr,12} from "@oxog/vld";13 14const parsed = tryCatch(() => JSON.parse(rawInput));15 16const label = match(parsed, {17 ok: (data) => `Success: ${data.id}`,18 err: (error) => `Failed: ${error.message}`,19});20 21const safe = unwrapOr(parsed, { id: "unknown" });22const manual = isOk(parsed) ? Ok(parsed.value) : Err("invalid");23 24// Bidirectional codecs25stringToNumber.parse("42"); // 4226stringToNumber.encode(42); // "42"27 28const json = jsonCodec();29json.parse('{"id":1}');30json.encode({ id: 2 });31 32base64ToBytes.parse("SGVsbG8="); // Uint8Array33hexToBytes.parse("ff00");10
V2 mode
V2 stores one shared __def per instance and represents constraints as class instances instead of bound closures. That is where the memory win comes from.
1// Option 1 — drop-in for new code2import { vV2 as v } from "@oxog/vld";3 4const schema = v.object({5 email: v.string().email(),6 age: v.number().int().positive(),7});8 9// Option 2 — flip every factory at app start10import { v } from "@oxog/vld";11v.setV2Mode(true);12 13// Option 3 — opt in per call14const hot = v.stringV2().min(1).email();15const list = v.arrayV2(v.stringV2());16 17// V1 and V2 validators compose inside the same schema11
Subpath exports
Every entry point is declared in exports and checked per release, so deep imports are part of the contract rather than an accident.
@oxog/vld # root: v, vV2, z, codecs, result, plugins@oxog/vld/v4 # Zod 4 compatible surface@oxog/vld/v4/core # core primitives only@oxog/vld/v4-mini # Zod-compatible mini surface@oxog/vld/mini # tree-shakeable standalone functions@oxog/vld/locales # all 32 locale packs@oxog/vld/locales/lazy # async, per-locale loading@oxog/vld/codecs # codec helpers@oxog/vld/errors # error formatters@oxog/vld/kernel # plugin kernel12
Migration
From Zod it is the import line. From VLD 2.x, V3 is a non-breaking major — existing code keeps the V1 implementation until you opt in.
1// Before2import { z } from "zod";3 4// After — one line, nothing else5import { z } from "@oxog/vld";6 7// Prefer the explicit namespace? Both work.8import { v } from "@oxog/vld";9 10// Coming from VLD 2.x? V3 is a non-breaking major:11// - v.* still returns the V1 implementation by default12// - v.*V2() and vV2 are pure additions13// - v.setV2Mode(true) opts the whole app in at once14// - 259/259 Zod 4.6 exports verified per releaseThe project verifies parity with a npm run verify:zod check that walks every public Zod export, plus a behaviour sweep against the installed Zod. Run it in CI to make sure a Zod upgrade has not moved the target.