npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pyreon/validation

v0.11.3

Published

Schema validation adapters for Pyreon forms (Zod, Valibot, ArkType)

Readme

@pyreon/validation

Schema adapters for @pyreon/form. Duck-typed interfaces for Zod, Valibot, and ArkType — no hard version coupling.

Install

bun add @pyreon/validation

Quick Start

import { z } from "zod"
import { useForm } from "@pyreon/form"
import { zodSchema } from "@pyreon/validation"

const schema = z.object({
  email: z.string().email(),
  age: z.number().min(13),
})

const form = useForm({
  initialValues: { email: "", age: 0 },
  schema: zodSchema(schema),
  onSubmit: async (values) => console.log(values),
})

Each adapter comes in two flavors: schema-level (validates the whole form) and field-level (validates a single field).

API

zodSchema(schema)

Create a form-level schema validator from a Zod schema. Uses safeParseAsync internally — supports both sync and async refinements. Duck-typed to work with Zod v3 and v4.

| Parameter | Type | Description | | --- | --- | --- | | schema | Zod schema | Any Zod object schema with safeParse/safeParseAsync |

Returns: SchemaValidateFn<TValues>

import { z } from "zod"
const form = useForm({
  initialValues: { email: "", password: "" },
  schema: zodSchema(z.object({
    email: z.string().email(),
    password: z.string().min(8),
  })),
  onSubmit: (values) => { ... },
})

zodField(schema)

Create a single-field validator from a Zod schema. Returns the first error message on failure.

| Parameter | Type | Description | | --- | --- | --- | | schema | Zod schema | Any Zod schema (string, number, etc.) |

Returns: ValidateFn<T>

const form = useForm({
  initialValues: { email: "" },
  validators: {
    email: zodField(z.string().email("Invalid email")),
  },
  onSubmit: (values) => { ... },
})

valibotSchema(schema, safeParseFn)

Create a form-level schema validator from a Valibot schema. Valibot uses standalone functions, so you must pass the parse function.

| Parameter | Type | Description | | --- | --- | --- | | schema | Valibot schema | Any Valibot object schema | | safeParseFn | Function | v.safeParse or v.safeParseAsync from valibot |

Returns: SchemaValidateFn<TValues>

import * as v from "valibot"
const form = useForm({
  initialValues: { email: "", password: "" },
  schema: valibotSchema(
    v.object({
      email: v.pipe(v.string(), v.email()),
      password: v.pipe(v.string(), v.minLength(8)),
    }),
    v.safeParseAsync,
  ),
  onSubmit: (values) => { ... },
})

valibotField(schema, safeParseFn)

Create a single-field validator from a Valibot schema.

| Parameter | Type | Description | | --- | --- | --- | | schema | Valibot schema | Any Valibot schema | | safeParseFn | Function | v.safeParse or v.safeParseAsync from valibot |

Returns: ValidateFn<T>

validators: {
  email: valibotField(v.pipe(v.string(), v.email("Invalid")), v.safeParseAsync),
}

arktypeSchema(schema)

Create a form-level schema validator from an ArkType schema. ArkType validation is synchronous.

| Parameter | Type | Description | | --- | --- | --- | | schema | ArkType Type | Any callable ArkType type |

Returns: SchemaValidateFn<TValues>

import { type } from "arktype"
const form = useForm({
  initialValues: { email: "", password: "" },
  schema: arktypeSchema(type({
    email: "string.email",
    password: "string >= 8",
  })),
  onSubmit: (values) => { ... },
})

arktypeField(schema)

Create a single-field validator from an ArkType schema.

| Parameter | Type | Description | | --- | --- | --- | | schema | ArkType Type | Any callable ArkType type |

Returns: ValidateFn<T>

validators: {
  email: arktypeField(type("string.email")),
}

issuesToRecord(issues)

Convert an array of ValidationIssue objects into a flat field-to-error record. First error per field wins. Useful for building custom adapters.

| Parameter | Type | Description | | --- | --- | --- | | issues | ValidationIssue[] | Array of { path: string, message: string } |

Returns: Partial<Record<keyof TValues, ValidationError>>

issuesToRecord([
  { path: "email", message: "Required" },
  { path: "email", message: "Invalid" },  // ignored — first wins
  { path: "age", message: "Too young" },
])
// => { email: "Required", age: "Too young" }

Patterns

Subpath Imports

Each adapter is available via subpath import to avoid bundling unused adapters:

import { zodSchema } from "@pyreon/validation/zod"
import { valibotSchema } from "@pyreon/validation/valibot"
import { arktypeSchema } from "@pyreon/validation/arktype"

Mixing Field and Schema Validators

Field-level validators run first. Schema errors only apply to fields that have no field-level error.

const form = useForm({
  initialValues: { email: "", password: "", confirmPassword: "" },
  validators: {
    email: zodField(z.string().email()),
  },
  schema: zodSchema(z.object({ ... }).refine(
    (data) => data.password === data.confirmPassword,
    { path: ["confirmPassword"], message: "Passwords must match" },
  )),
  onSubmit: (values) => { ... },
})

Types

| Type | Description | | --- | --- | | ValidationIssue | { path: string, message: string } — normalized issue | | SchemaAdapter<TSchema> | Generic schema adapter factory type | | FieldAdapter<TSchema> | Generic field adapter factory type | | SchemaValidateFn<TValues> | Re-exported from @pyreon/form | | ValidateFn<T> | Re-exported from @pyreon/form | | ValidationError | Re-exported from @pyreon/formstring \| undefined |

Gotchas

  • All adapters are duck-typed — they do not import types from Zod, Valibot, or ArkType. This means they work across major versions without breaking.
  • Zod and Valibot adapters use async parsing internally (safeParseAsync), so validation is always async even for sync schemas.
  • ArkType adapters are synchronous — ArkType does not support async validation.
  • When a validator throws, the error is caught and converted to a string error message rather than propagating.
  • For nested paths like address.city, the dot-separated path is used as the field key in the error record.