@pyreon/validation — API Reference
Generated from
validation'ssrc/manifest.ts— the same source that powersllms.txtand MCPget_api. Do not edit this page by hand; edit the manifest. For the conceptual guide, see validation.
The stack-wide validation gate — the library-agnostic contract every Pyreon data package (@pyreon/form, @pyreon/store, @pyreon/state-tree, @pyreon/feature) consumes. Owns the validation contract types (ValidationError / ValidateFn / SchemaValidateFn) and the Standard Schema bridge, with ZERO pyreon dependencies (the consumers depend on it, not the reverse). Accept a RAW Standard Schema (Zod 3.24+, Valibot 1+, ArkType 2+, Effect Schema, @pyreon/validate s) directly — no wrapper, no cast — via standardSchemaToValidator, or use the duck-typed zodSchema / valibotSchema / arktypeSchema adapters (which add the _infer brand + a sync parse for schema-driven state). Zero dependencies — schema libraries are detected by duck-typing the ~standard contract, with no peer-dependency records declared.
Features
Universal, library-agnostic validation gate — owns the contract types (ValidationError / ValidateFn / SchemaValidateFn) with ZERO pyreon deps
Standard Schema bridge — pass a raw Zod / Valibot / ArkType / Effect /
sschema directly, no adapter (standardSchemaToValidator / isStandardSchema)Sync fast-path bridge — a sync schema validates without a Promise/microtask per call; wrapper tax bench-proven ≈0ns over the raw library (bench:validation, correctness-gated, per-op process isolation)
zodSchema / zodField — duck-typed Zod adapter (works with v3 and v4)
valibotSchema / valibotField — Valibot standalone-function-style adapter
arktypeSchema / arktypeField — ArkType sync adapter
InferSchema<S> — resolve inferred field types from a Pyreon adapter OR a raw Standard Schema
Schema-driven-state helpers — extractParseFn / formatIssues power @pyreon/store + @pyreon/state-tree
issuesToRecord utility for building custom adapters
Complete example
A full, end-to-end usage of the package:
import { useForm } from '@pyreon/form'
import { z } from 'zod'
// Universal gate: pass a RAW Standard Schema directly — no adapter, no cast.
// Zod ≥3.24 / Valibot ≥1 / ArkType ≥2 / Effect Schema / @pyreon/validate 's'
// all expose '~standard', so the form bridges it via standardSchemaToValidator.
const schema = z.object({
email: z.string().email(),
age: z.number().min(18),
})
const form = useForm({
initialValues: { email: '', age: 0 },
schema, // raw schema — the gate adapts it
onSubmit: (values) => save(values),
})
// Or use the typed adapter when you want the _infer brand + sync parse
// (schema-driven @pyreon/store / @pyreon/state-tree need the coerced value):
import { zodSchema, zodField } from '@pyreon/validation'
const form2 = useForm({
initialValues: { username: '', bio: '' },
validators: {
username: zodField(z.string().min(3).max(20)), // per-field
bio: zodField(z.string().max(500)),
},
schema: zodSchema(z.object({ username: z.string(), bio: z.string() })),
onSubmit: (values) => save(values),
})
// Valibot adapter — standalone-function style (pass v.safeParse explicitly)
import { valibotSchema } from '@pyreon/validation'
import * as v from 'valibot'
const vSchema = v.object({ email: v.pipe(v.string(), v.email()) })
const form3 = useForm({
initialValues: { email: '' },
schema: valibotSchema(vSchema, v.safeParse),
onSubmit: (values) => save(values),
})
// Bridge a raw schema by hand when you need the validator standalone:
import { standardSchemaToValidator } from '@pyreon/validation'
const validate = standardSchemaToValidator(schema)
const errors = validate({ email: 'x', age: 5 }) // sync schema → plain record (no await needed)
// => { email: 'Invalid email', age: 'Too small: ...' }Exports
| Symbol | Kind | Summary |
|---|---|---|
zodSchema | function | Create a typed whole-form schema adapter from a Zod schema. |
zodField | function | Create a per-field validator from a Zod schema. |
valibotSchema | function | Create a typed whole-form schema adapter from a Valibot schema. |
valibotField | function | Create a per-field validator from a Valibot schema. |
arktypeSchema | function | Create a typed whole-form schema adapter from an ArkType type. |
arktypeField | function | Create a per-field validator from an ArkType type. |
standardSchemaToValidator | function | Convert a RAW Standard Schema (any library exposing ~standard — Zod 3.24+, Valibot 1+, ArkType 2+, Effect Schema, `@py |
isStandardSchema | function | Runtime type guard — detect a Standard Schema-compliant schema by its ~standard property (an object carrying a `valida |
isPyreonAdapter | function | Runtime type guard — detect a Pyreon TypedSchemaAdapter (Tier A.1) by its _infer brand plus a callable parse. |
wrapStandardSchema | function | Convert a Standard Schema into a synchronous parser returning SchemaParseResult<T> (`{ ok: true, value } | { ok: false |
extractParseFn | function | The primary schema-driven entry point for @pyreon/store + @pyreon/state-tree: accept EITHER a Pyreon TypedSchemaAdap |
formatIssues | function | Format normalized schema issues into a readable multi-line [Pyreon] Schema validation failed (<op>): ... message. |
issuesToRecord | function | Collapse an array of normalized ValidationIssue ({ path, message }) into a flat field→error record — the shape `@pyr |
TypedSchemaAdapter | type | The object every zodSchema() / valibotSchema() / arktypeSchema() returns. |
InferSchema | type | Extract the inferred output type from EITHER a Pyreon TypedSchemaAdapter (reads _infer, Tier A.1) OR a raw Standard Sc |
SchemaValidateFn | type | The whole-object validator contract — maps a values object to a per-key error record (sync or async). |
ValidateFn | type | The single-field validator contract — receives the field value, all current values (for cross-field checks), and an opti |
ValidationError | type | A single field's error value — the message string, or undefined for "no error". |
StandardSchemaLike | type | The Standard Schema (https://standardschema.dev) shape @pyreon/validation owns so any consumer can accept a raw schema |
ValidationIssue | type | The normalized issue shape every adapter produces before calling issuesToRecord — a dot-separated field path (`addre |
API
zodSchema function
<TValues>(schema: ZodSchema<TValues>) => TypedSchemaAdapter<TValues>Create a typed whole-form schema adapter from a Zod schema. Duck-typed against .safeParse() / .safeParseAsync() so it works with Zod v3 and v4 without version checks. Returns a TypedSchemaAdapter ({ _infer, validator, parse }) that useForm({ schema }), schema-driven defineStore, and model accept — _infer carries the inferred field types for compile-time field-name checking; parse gives store / state-tree the coerced value. Since Zod ≥3.24 is Standard-Schema-compliant you may also skip this wrapper and pass the raw z.object(...) directly.
Example
const schema = z.object({ email: z.string().email(), age: z.number().min(18) })
const form = useForm({
initialValues: { email: '', age: 0 },
schema: zodSchema(schema),
onSubmit: (values) => save(values),
})Common mistakes
Passing both zodSchema AND a per-field validator for the same field — both run; a schema error can override the field error on that key
Using zodSchema with a non-object schema (z.string()) — form/store schemas must validate an object shape matching initialValues
Assuming the return is a plain function — it is a TypedSchemaAdapter object ({ _infer, validator, parse }); the form reads
.validatorfor you
See also: zodField · standardSchemaToValidator · TypedSchemaAdapter
zodField function
<T>(schema: ZodSchema<T>) => ValidateFn<T>Create a per-field validator from a Zod schema. Returns a ValidateFn compatible with useForm({ validators: { fieldName: zodField(z.string().email()) } }). Uses safeParseAsync so sync AND async refinements work; returns the first issue message on failure, undefined on success. Use when individual fields have independent rules that don't need cross-field context.
Example
const form = useForm({
initialValues: { username: '' },
validators: { username: zodField(z.string().min(3).max(20)) },
onSubmit: (values) => save(values),
})See also: zodSchema · valibotField · ValidateFn
valibotSchema function
<TValues>(schema: unknown, safeParse: Function) => TypedSchemaAdapter<TValues>Create a typed whole-form schema adapter from a Valibot schema. Requires passing the safeParse (or safeParseAsync) function explicitly — Valibot uses standalone functions, not methods, so this keeps the adapter independent of Valibot's internal module structure across versions. Returns a TypedSchemaAdapter ({ _infer, validator, parse }); the sync parse path needs the SYNC v.safeParse. Valibot ≥1 is also Standard-Schema-compliant, so a raw v.object(...) can be passed directly instead.
Example
import * as v from 'valibot'
const schema = v.object({ email: v.pipe(v.string(), v.email()) })
const form = useForm({
initialValues: { email: '' },
schema: valibotSchema(schema, v.safeParse),
onSubmit: (values) => save(values),
})Common mistakes
Forgetting to pass v.safeParse as the second argument — the adapter cannot call safeParse without it (Valibot uses standalone functions)
Passing v.safeParseAsync when using the sync
parsepath (schema-driven store) — the parser then returns a Promise and store rejects it at defineStore-time
See also: valibotField · zodSchema · standardSchemaToValidator
valibotField function
<T>(schema: unknown, safeParse: Function) => ValidateFn<T>Create a per-field validator from a Valibot schema. Same standalone-function style as valibotSchema — pass v.safeParse (or v.safeParseAsync) explicitly. Returns the first issue message on failure, undefined on success.
Example
validators: { email: valibotField(v.pipe(v.string(), v.email()), v.safeParseAsync) }See also: valibotSchema · zodField
arktypeSchema function
<TValues>(schema: (data: unknown) => unknown) => TypedSchemaAdapter<TValues>Create a typed whole-form schema adapter from an ArkType type. Accepts any callable — ArkType schemas are invoked directly, no ArkType import required. Synchronous only (ArkType has no async surface); the non-error result IS the coerced value, so the parse path is native. Returns a TypedSchemaAdapter ({ _infer, validator, parse }); errors are read from the returned ArkErrors array. ArkType ≥2 is Standard-Schema-compliant, so a raw type(...) can be passed directly instead.
Example
import { type } from 'arktype'
const schema = type({ email: 'string.email', age: 'number > 18' })
const form = useForm({
initialValues: { email: '', age: 0 },
schema: arktypeSchema(schema),
onSubmit: (values) => save(values),
})Common mistakes
Expecting async validation — the ArkType adapter is synchronous; wrap async logic in a per-field validator function instead
See also: arktypeField · zodSchema · standardSchemaToValidator
arktypeField function
<T>(schema: (data: unknown) => unknown) => ValidateFn<T>Create a per-field validator from an ArkType type. Synchronous, like arktypeSchema. Returns the first ArkType error message on failure, undefined on success.
Example
validators: { age: arktypeField(type('number > 18')) }See also: arktypeSchema · zodField
standardSchemaToValidator function
<TValues>(schema: StandardSchemaLike) => SchemaValidateFn<TValues>Convert a RAW Standard Schema (any library exposing ~standard — Zod 3.24+, Valibot 1+, ArkType 2+, Effect Schema, @pyreon/validate s) into a whole-object SchemaValidateFn — (values) => per-key error record. This is the bridge that lets a consumer accept a raw schema with no zodSchema() wrapper and no cast: useForm({ schema: z.object(...) }). Issue paths flatten to dot-strings (address.city); first message per path wins. SYNC FAST-PATH: a synchronously-validating schema (zod/valibot/arktype sync trees, s) returns the error record DIRECTLY — no Promise allocation, no microtask hop per call (the keystroke path in @pyreon/form validateOn); a genuinely async schema still returns a Promise the caller awaits. Wrapper tax bench-proven ≈0ns over the raw library call (bun run bench:validation).
Example
import { z } from 'zod'
import { standardSchemaToValidator } from '@pyreon/validation'
const schema = z.object({ email: z.string().email(), age: z.number().min(18) })
const validate = standardSchemaToValidator(schema)
const errors = validate({ email: 'x', age: 5 }) // sync schema → plain record (no await needed)
// => { email: 'Invalid email', age: 'Too small: ...' }Common mistakes
Passing a Pyreon adapter (the result of zodSchema()) instead of the RAW schema — the adapter is already a validator; pass z.object(...) directly, or use adapter.validator
Expecting a synchronous return — the produced validator is always async (returns a Promise); always await it
Assuming a non-object schema works — the output is a per-key record keyed on the top-level fields, so the schema must describe an object shape
See also: isStandardSchema · zodSchema · InferSchema
isStandardSchema function
(value: unknown) => value is StandardSchemaLike<unknown>Runtime type guard — detect a Standard Schema-compliant schema by its ~standard property (an object carrying a validate function). Accepts a value whose typeof is object OR function — ArkType schemas are CALLABLE (type("string")(input) validates) yet still carry ~standard, so a callable is a valid Standard Schema; a plain function WITHOUT ~standard is still rejected. Used by the universal gate to decide whether a schema option is a raw Standard Schema (→ standardSchemaToValidator) vs a Pyreon TypedSchemaAdapter (→ isPyreonAdapter) vs a plain validator function. Zero library imports — pure duck-typing, so it never breaks on a validator-library major bump.
Example
import { isStandardSchema, standardSchemaToValidator } from '@pyreon/validation'
if (isStandardSchema(schema)) {
const validate = standardSchemaToValidator(schema)
}Common mistakes
Using it to detect a Pyreon adapter — those brand with
_infer, not~standard; use isPyreonAdapter for that tierAssuming it only matches objects — ArkType schemas are functions; the guard accepts a callable carrying
~standard(a bare object-only guard silently rejected raw ArkType everywhere)
See also: isPyreonAdapter · standardSchemaToValidator
isPyreonAdapter function
(value: unknown) => value is PyreonAdapterShape<Record<string, unknown>>Runtime type guard — detect a Pyreon TypedSchemaAdapter (Tier A.1) by its _infer brand plus a callable parse. The counterpart to isStandardSchema in the two-tier detection that schema-driven consumers (@pyreon/store, @pyreon/state-tree) use to accept EITHER a zodSchema()-style adapter OR a raw Standard Schema (Tier A.2).
Example
import { isPyreonAdapter } from '@pyreon/validation'
if (isPyreonAdapter(schema)) {
const result = schema.parse!(value) // sync coerced parse
}Common mistakes
Assuming any adapter passes — all three built-in adapters ship
parseso they do, but a validator-only shape ({ _infer, validator } with no parse) does NOT
See also: isStandardSchema · extractParseFn
wrapStandardSchema function
<T>(schema: StandardSchemaShape<unknown>) => (value: unknown) => SchemaParseResult<T>Convert a Standard Schema into a synchronous parser returning SchemaParseResult<T> ({ ok: true, value } | { ok: false, issues }). Unlike standardSchemaToValidator (which returns per-key ERRORS for forms), this returns the coerced VALUE on success — what @pyreon/store / @pyreon/state-tree need. Surfaces async validation as a Promise return so callers can detect and reject async-only schemas. @internal — most consumers go through extractParseFn.
Example
import { wrapStandardSchema } from '@pyreon/validation'
const parse = wrapStandardSchema(schema)
const r = parse(input)
if (r instanceof Promise) throw new Error('async schema unsupported')
if (r.ok) use(r.value)Common mistakes
Treating it like standardSchemaToValidator — this returns a coerced-value ParseResult, not a form error record
Not probing for a Promise return — an async Standard Schema returns a Promise, which is not a valid sync ParseResult
See also: extractParseFn · standardSchemaToValidator
extractParseFn function
<T>(schema: unknown) => (value: unknown) => SchemaParseResult<T>The primary schema-driven entry point for @pyreon/store + @pyreon/state-tree: accept EITHER a Pyreon TypedSchemaAdapter (uses its sync parse) OR a raw Standard Schema (wraps via wrapStandardSchema) and return one uniform sync parser. Throws a [Pyreon]-prefixed error at construction if the value is neither shape, or if a Tier-A.1 adapter is missing its parse. Callers should probe the first call for a Promise (async-only schema) and reject it.
Example
import { extractParseFn, formatIssues } from '@pyreon/validation'
const parse = extractParseFn(userSchema)
const r = parse(initial)
if (r instanceof Promise) throw new Error('[Pyreon] async schemas unsupported')
if (!r.ok) throw new Error(formatIssues(r.issues, 'init'))
const value = r.value // parsed + coercedCommon mistakes
Passing an async-only schema (objectAsync / safeParseAsync-only) — extractParseFn constructs fine but the returned parser resolves a Promise; detect and reject it
Passing a @pyreon/form-only validator ({ _infer, validator } with no parse) — throws at construction; schema-driven state needs the coerced value, not just errors
See also: wrapStandardSchema · isPyreonAdapter · formatIssues
formatIssues function
(issues: SchemaIssue[], op: string) => stringFormat normalized schema issues into a readable multi-line [Pyreon] Schema validation failed (<op>): ... message. Truncates after 5 issues with an "and N more" suffix. op is a free-form label for the failing operation (init, set, patch, create, $set, ...). Used by schema-driven store / state-tree to throw clear errors on an invalid write.
Example
import { formatIssues } from '@pyreon/validation'
throw new Error(formatIssues([{ path: 'email', message: 'Invalid' }], 'set'))
// [Pyreon] Schema validation failed (set):
// - email: InvalidSee also: extractParseFn · issuesToRecord
issuesToRecord function
<TValues>(issues: ValidationIssue[]) => Partial<Record<keyof TValues, ValidationError>>Collapse an array of normalized ValidationIssue ({ path, message }) into a flat field→error record — the shape @pyreon/form consumes. First message per path wins; nested dot-paths (address.city) become the record key verbatim (the adapter is responsible for producing the dot-string). The building block every custom adapter ends with.
Example
import { issuesToRecord } from '@pyreon/validation'
issuesToRecord([
{ path: 'email', message: 'Required' },
{ path: 'email', message: 'Invalid' }, // dropped — first wins
])
// => { email: 'Required' }Common mistakes
Expecting the LAST message to win for a repeated path — the FIRST wins; order your issues most-important-first
Feeding native library paths (arrays / objects) directly — normalize to a dot-string path in the ValidationIssue first
See also: formatIssues · zodSchema
TypedSchemaAdapter type
interface TypedSchemaAdapter<TValues> { readonly _infer: TValues; readonly validator: SchemaValidateFn<TValues>; readonly parse?: (value: unknown) => ParseResult<TValues> }The object every zodSchema() / valibotSchema() / arktypeSchema() returns. _infer is a compile-time-only brand carrying the inferred field types (never read at runtime — it is undefined as any); validator is the whole-form error function @pyreon/form runs; parse is the optional sync coerced-value parser @pyreon/store / @pyreon/state-tree need (all three built-in adapters ship it).
Example
import { zodSchema } from '@pyreon/validation'
const adapter = zodSchema(z.object({ id: z.string() }))
adapter.validator({ id: 5 }) // => { id: 'Expected string' }
adapter.parse!({ id: 'x' }) // => { ok: true, value: { id: 'x' } }Common mistakes
Calling the adapter like a function — it is an object; the form reads
.validatorfor you, store reads.parseRelying on
_inferat runtime — it isundefined as any, a type brand only
See also: zodSchema · SchemaValidateFn · InferSchema
InferSchema type
type InferSchema<S> = S["_infer"] /* Tier A.1 */ | S["~standard"]["types"]["output"] /* Tier A.2 */ | Record<string, unknown>Extract the inferred output type from EITHER a Pyreon TypedSchemaAdapter (reads _infer, Tier A.1) OR a raw Standard Schema (reads ~standard.types.output, Tier A.2). Falls back to Record<string, unknown> for unknown shapes (never collapses to never). Powers the strict typing in @pyreon/store + @pyreon/state-tree so a raw z.object(...) passed directly infers its exact field types.
Example
import type { InferSchema } from '@pyreon/validation'
import { z } from 'zod'
const schema = z.object({ id: z.string(), n: z.number() })
type Values = InferSchema<typeof schema> // { id: string; n: number }Common mistakes
Expecting inference when a raw schema omits its
~standard.typesphantom — the spec makestypes?optional; real libraries emit it, but a hand-rolled ~standard without it falls back to Record<string, unknown>
See also: TypedSchemaAdapter · StandardSchemaLike
SchemaValidateFn type
type SchemaValidateFn<TValues> = (values: TValues) => Partial<Record<keyof TValues, ValidationError>> | Promise<Partial<Record<keyof TValues, ValidationError>>>The whole-object validator contract — maps a values object to a per-key error record (sync or async). What every schema adapter (zod / valibot / arktype / Standard Schema) produces and what @pyreon/form + @pyreon/store consume. OWNED by @pyreon/validation (the library-agnostic gate); @pyreon/form re-exports it for back-compat.
Example
import type { SchemaValidateFn } from '@pyreon/validation'
const validate: SchemaValidateFn<{ email: string }> = (values) =>
values.email.includes('@') ? {} : { email: 'Invalid email' }Common mistakes
Returning a full record with every key present — return ONLY errored keys; an empty object {} means "valid"
See also: ValidateFn · standardSchemaToValidator
ValidateFn type
type ValidateFn<T, TValues = Record<string, unknown>> = (value: T, allValues: TValues, signal?: AbortSignal) => ValidationError | Promise<ValidationError>The single-field validator contract — receives the field value, all current values (for cross-field checks), and an optional AbortSignal (cancellation, e.g. when a form unmounts). Returns an error string or undefined (sync or async). OWNED by @pyreon/validation; @pyreon/form re-exports it.
Example
import type { ValidateFn } from '@pyreon/validation'
const confirm: ValidateFn<string, { password: string }> = (value, all) =>
value === all.password ? undefined : 'Passwords must match'Common mistakes
Returning a falsy non-undefined value ("" / false / 0) to mean "valid" — return
undefined; an empty string is technically an error message
See also: SchemaValidateFn · ValidationError
ValidationError type
type ValidationError = string | undefinedA single field's error value — the message string, or undefined for "no error". The atomic unit of every error record and validator return in the stack. OWNED by @pyreon/validation; re-exported by @pyreon/form so import { ValidationError } from '@pyreon/form' still works.
Example
import type { ValidationError } from '@pyreon/validation'
const err: ValidationError = isValid ? undefined : 'Required'See also: ValidateFn · SchemaValidateFn
StandardSchemaLike type
interface StandardSchemaLike<Output = unknown> { readonly "~standard": { readonly types?: { readonly output: Output }; readonly validate: (value: unknown) => StandardSchemaResult | Promise<StandardSchemaResult> } }The Standard Schema (https://standardschema.dev) shape @pyreon/validation owns so any consumer can accept a raw schema with no adapter and no cast — the ~standard property Zod ≥3.24 / Valibot ≥1 / ArkType ≥2 / Effect Schema / @pyreon/validate s all expose. standardSchemaToValidator takes this type; useForm({ schema }) accepts it directly.
Example
import type { StandardSchemaLike } from '@pyreon/validation'
import { standardSchemaToValidator } from '@pyreon/validation'
function adapt<T extends Record<string, unknown>>(s: StandardSchemaLike) {
return standardSchemaToValidator<T>(s)
}Common mistakes
Confusing it with StandardSchemaShape — near-identical duck-types; standardSchemaToValidator takes StandardSchemaLike, wrapStandardSchema / isStandardSchema use StandardSchemaShape
See also: standardSchemaToValidator · isStandardSchema
ValidationIssue type
interface ValidationIssue { path: string; message: string }The normalized issue shape every adapter produces before calling issuesToRecord — a dot-separated field path (address.city) plus a human-readable message. The common denominator that lets any library's errors flow into @pyreon/form's flat record. Aliased as SchemaIssue for the store / state-tree surface.
Example
import type { ValidationIssue } from '@pyreon/validation'
const issues: ValidationIssue[] = [{ path: 'address.city', message: 'Required' }]See also: issuesToRecord · formatIssues
Package-level notes
Note: Zero dependencies — no peer-dependency records at all: schema libraries are detected by duck-typing the
~standard(Standard Schema) contract. Install only the one you use — the adapters are tree-shaken per import path (@pyreon/validation/zod,@pyreon/validation/valibot,@pyreon/validation/arktype).
Raw Standard Schema needs no wrapper: Zod 3.24+, Valibot 1+, ArkType 2+, Effect Schema, and
@pyreon/validate'ssall expose~standard, so you can pass the schema DIRECTLY (useForm({ schema: z.object(...) })) — the gate bridges it viastandardSchemaToValidator. Reach forzodSchema()/valibotSchema()/arktypeSchema()when you want the_infer-branded typed adapter, the syncparsepath (schema-driven@pyreon/store/@pyreon/state-tree), or a library that is not Standard-Schema-compliant.
Contract types live here now:
ValidationError/ValidateFn/SchemaValidateFnare OWNED by@pyreon/validation(the gate has ZERO pyreon deps;@pyreon/form/@pyreon/store/@pyreon/state-tree/@pyreon/featuredepend on it).@pyreon/formre-exports them, soimport { ValidationError } from '@pyreon/form'still works.
Valibot standalone functions: Valibot uses standalone functions (not methods), so
valibotSchemaandvalibotFieldrequire passingv.safeParseas an explicit argument. This is by design to avoid internal coupling to Valibot's module structure.
Duck typing: Every adapter AND the Standard Schema bridge duck-type at runtime — they never
importfrom Zod / Valibot / ArkType. Major version bumps in any validator library do not break the gate.