Zod, Valibot, ArkType
All three libraries implement Standard Schema v1, so all three work identically through
fromStandardSchema. QueryWeave has no preference and no default: pick on the merits of the
library, not on what this documentation happens to show first.
One rule, three spellings
Section titled “One rule, three spellings”A search term of at least two characters:
import { fromStandardSchema } from "@queryweave/standard-schema";import { defineQueryModel, param } from "@queryweave/core";import { z } from "zod";
const search = param .text() .refine(fromStandardSchema(z.string().min(2))) .optional();
const products = defineQueryModel({ search });import { fromStandardSchema } from "@queryweave/standard-schema";import { defineQueryModel, param } from "@queryweave/core";import * as v from "valibot";
const search = param .text() .refine(fromStandardSchema(v.pipe(v.string(), v.minLength(2)))) .optional();
const products = defineQueryModel({ search });import { fromStandardSchema } from "@queryweave/standard-schema";import { defineQueryModel, param } from "@queryweave/core";import { type } from "arktype";
const search = param .text() .refine(fromStandardSchema(type("string >= 2"))) .optional();
const products = defineQueryModel({ search });Whichever you choose, a rejected value produces the same thing:
const result = products.decode("?search=v");
result.issues[0]?.code; // "validation_failed"result.issues[0]?.key; // "search"result.ok && result.value.search; // undefined — optional presence recoveredA transform, three spellings
Section titled “A transform, three spellings”const products = defineQueryModel({ search: param.text().refine(fromStandardSchema(z.string().transform((value) => value.length))),});
const result = products.decode("?search=vue");if (result.ok) result.value.search; // 3, inferred as numberconst products = defineQueryModel({ search: param.text().refine( fromStandardSchema( v.pipe( v.string(), v.transform((value) => value.length), ), ), ),});
const result = products.decode("?search=vue");if (result.ok) result.value.search; // 3, inferred as numberconst products = defineQueryModel({ search: param.text().refine(fromStandardSchema(type("string").pipe((value) => value.length))),});
const result = products.decode("?search=vue");if (result.ok) result.value.search; // 3, inferred as numberIn all three cases the parameter’s value type becomes number, inferred from the schema’s output.
A model-level rule
Section titled “A model-level rule”const priceFilters = defineQueryModel( { minPrice: param.number({ min: 0 }), maxPrice: param.number({ min: 0 }) }, { refine: [ fromStandardSchema( z .object({ minPrice: z.number(), maxPrice: z.number() }) .refine((filters) => filters.minPrice <= filters.maxPrice), ), ], },);const priceFilters = defineQueryModel( { minPrice: param.number({ min: 0 }), maxPrice: param.number({ min: 0 }) }, { refine: [ fromStandardSchema( v.pipe( v.object({ minPrice: v.number(), maxPrice: v.number() }), v.check((filters) => filters.minPrice <= filters.maxPrice), ), ), ], },);const priceFilters = defineQueryModel( { minPrice: param.number({ min: 0 }), maxPrice: param.number({ min: 0 }) }, { refine: [ fromStandardSchema( type({ minPrice: "number", maxPrice: "number" }).narrow( (filters) => filters.minPrice <= filters.maxPrice, ), ), ], },);The URL and QueryWeave result are identical for all three versions:
const result = priceFilters.decode("?minPrice=200&maxPrice=100");
result.ok; // falseresult.issues[0]?.code; // "validation_failed"result.issues[0]?.key; // "$" — exported as modelIssueKeyWhat is verified
Section titled “What is verified”The repository’s compatibility suite runs a single table of expectations across all three vendors:
| Behavior | Verified |
|---|---|
| A valid value passes through unchanged | yes |
A rejected value produces validation_failed |
yes |
| Recovery follows the parameter’s presence | yes |
| A transform changes the inferred type | yes |
A model-level schema reports under $ |
yes |
| The vendor’s error type never escapes | yes |
That is the contract. If a fourth Standard Schema library satisfies it, it works — nothing about these three is special-cased.
Choosing
Section titled “Choosing”QueryWeave takes no position. The practical differences are the usual ones: Zod is the most widely known, Valibot is designed around tree-shaking, ArkType parses type-level syntax. All three produce identical QueryWeave issues, so this decision is reversible.