@pyreon/validation 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-present Vit Bokisch
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,223 @@
1
+ # @pyreon/validation
2
+
3
+ Schema adapters for `@pyreon/form`. Duck-typed interfaces for Zod, Valibot, and ArkType — no hard version coupling.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ bun add @pyreon/validation
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```ts
14
+ import { z } from "zod"
15
+ import { useForm } from "@pyreon/form"
16
+ import { zodSchema } from "@pyreon/validation"
17
+
18
+ const schema = z.object({
19
+ email: z.string().email(),
20
+ age: z.number().min(13),
21
+ })
22
+
23
+ const form = useForm({
24
+ initialValues: { email: "", age: 0 },
25
+ schema: zodSchema(schema),
26
+ onSubmit: async (values) => console.log(values),
27
+ })
28
+ ```
29
+
30
+ Each adapter comes in two flavors: **schema-level** (validates the whole form) and **field-level** (validates a single field).
31
+
32
+ ## API
33
+
34
+ ### `zodSchema(schema)`
35
+
36
+ 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.
37
+
38
+ | Parameter | Type | Description |
39
+ | --- | --- | --- |
40
+ | `schema` | Zod schema | Any Zod object schema with `safeParse`/`safeParseAsync` |
41
+
42
+ **Returns:** `SchemaValidateFn<TValues>`
43
+
44
+ ```ts
45
+ import { z } from "zod"
46
+ const form = useForm({
47
+ initialValues: { email: "", password: "" },
48
+ schema: zodSchema(z.object({
49
+ email: z.string().email(),
50
+ password: z.string().min(8),
51
+ })),
52
+ onSubmit: (values) => { ... },
53
+ })
54
+ ```
55
+
56
+ ### `zodField(schema)`
57
+
58
+ Create a single-field validator from a Zod schema. Returns the first error message on failure.
59
+
60
+ | Parameter | Type | Description |
61
+ | --- | --- | --- |
62
+ | `schema` | Zod schema | Any Zod schema (string, number, etc.) |
63
+
64
+ **Returns:** `ValidateFn<T>`
65
+
66
+ ```ts
67
+ const form = useForm({
68
+ initialValues: { email: "" },
69
+ validators: {
70
+ email: zodField(z.string().email("Invalid email")),
71
+ },
72
+ onSubmit: (values) => { ... },
73
+ })
74
+ ```
75
+
76
+ ### `valibotSchema(schema, safeParseFn)`
77
+
78
+ Create a form-level schema validator from a Valibot schema. Valibot uses standalone functions, so you must pass the parse function.
79
+
80
+ | Parameter | Type | Description |
81
+ | --- | --- | --- |
82
+ | `schema` | Valibot schema | Any Valibot object schema |
83
+ | `safeParseFn` | `Function` | `v.safeParse` or `v.safeParseAsync` from valibot |
84
+
85
+ **Returns:** `SchemaValidateFn<TValues>`
86
+
87
+ ```ts
88
+ import * as v from "valibot"
89
+ const form = useForm({
90
+ initialValues: { email: "", password: "" },
91
+ schema: valibotSchema(
92
+ v.object({
93
+ email: v.pipe(v.string(), v.email()),
94
+ password: v.pipe(v.string(), v.minLength(8)),
95
+ }),
96
+ v.safeParseAsync,
97
+ ),
98
+ onSubmit: (values) => { ... },
99
+ })
100
+ ```
101
+
102
+ ### `valibotField(schema, safeParseFn)`
103
+
104
+ Create a single-field validator from a Valibot schema.
105
+
106
+ | Parameter | Type | Description |
107
+ | --- | --- | --- |
108
+ | `schema` | Valibot schema | Any Valibot schema |
109
+ | `safeParseFn` | `Function` | `v.safeParse` or `v.safeParseAsync` from valibot |
110
+
111
+ **Returns:** `ValidateFn<T>`
112
+
113
+ ```ts
114
+ validators: {
115
+ email: valibotField(v.pipe(v.string(), v.email("Invalid")), v.safeParseAsync),
116
+ }
117
+ ```
118
+
119
+ ### `arktypeSchema(schema)`
120
+
121
+ Create a form-level schema validator from an ArkType schema. ArkType validation is synchronous.
122
+
123
+ | Parameter | Type | Description |
124
+ | --- | --- | --- |
125
+ | `schema` | ArkType `Type` | Any callable ArkType type |
126
+
127
+ **Returns:** `SchemaValidateFn<TValues>`
128
+
129
+ ```ts
130
+ import { type } from "arktype"
131
+ const form = useForm({
132
+ initialValues: { email: "", password: "" },
133
+ schema: arktypeSchema(type({
134
+ email: "string.email",
135
+ password: "string >= 8",
136
+ })),
137
+ onSubmit: (values) => { ... },
138
+ })
139
+ ```
140
+
141
+ ### `arktypeField(schema)`
142
+
143
+ Create a single-field validator from an ArkType schema.
144
+
145
+ | Parameter | Type | Description |
146
+ | --- | --- | --- |
147
+ | `schema` | ArkType `Type` | Any callable ArkType type |
148
+
149
+ **Returns:** `ValidateFn<T>`
150
+
151
+ ```ts
152
+ validators: {
153
+ email: arktypeField(type("string.email")),
154
+ }
155
+ ```
156
+
157
+ ### `issuesToRecord(issues)`
158
+
159
+ Convert an array of `ValidationIssue` objects into a flat field-to-error record. First error per field wins. Useful for building custom adapters.
160
+
161
+ | Parameter | Type | Description |
162
+ | --- | --- | --- |
163
+ | `issues` | `ValidationIssue[]` | Array of `{ path: string, message: string }` |
164
+
165
+ **Returns:** `Partial<Record<keyof TValues, ValidationError>>`
166
+
167
+ ```ts
168
+ issuesToRecord([
169
+ { path: "email", message: "Required" },
170
+ { path: "email", message: "Invalid" }, // ignored — first wins
171
+ { path: "age", message: "Too young" },
172
+ ])
173
+ // => { email: "Required", age: "Too young" }
174
+ ```
175
+
176
+ ## Patterns
177
+
178
+ ### Subpath Imports
179
+
180
+ Each adapter is available via subpath import to avoid bundling unused adapters:
181
+
182
+ ```ts
183
+ import { zodSchema } from "@pyreon/validation/zod"
184
+ import { valibotSchema } from "@pyreon/validation/valibot"
185
+ import { arktypeSchema } from "@pyreon/validation/arktype"
186
+ ```
187
+
188
+ ### Mixing Field and Schema Validators
189
+
190
+ Field-level validators run first. Schema errors only apply to fields that have no field-level error.
191
+
192
+ ```ts
193
+ const form = useForm({
194
+ initialValues: { email: "", password: "", confirmPassword: "" },
195
+ validators: {
196
+ email: zodField(z.string().email()),
197
+ },
198
+ schema: zodSchema(z.object({ ... }).refine(
199
+ (data) => data.password === data.confirmPassword,
200
+ { path: ["confirmPassword"], message: "Passwords must match" },
201
+ )),
202
+ onSubmit: (values) => { ... },
203
+ })
204
+ ```
205
+
206
+ ## Types
207
+
208
+ | Type | Description |
209
+ | --- | --- |
210
+ | `ValidationIssue` | `{ path: string, message: string }` — normalized issue |
211
+ | `SchemaAdapter<TSchema>` | Generic schema adapter factory type |
212
+ | `FieldAdapter<TSchema>` | Generic field adapter factory type |
213
+ | `SchemaValidateFn<TValues>` | Re-exported from `@pyreon/form` |
214
+ | `ValidateFn<T>` | Re-exported from `@pyreon/form` |
215
+ | `ValidationError` | Re-exported from `@pyreon/form` — `string \| undefined` |
216
+
217
+ ## Gotchas
218
+
219
+ - All adapters are duck-typed — they do not import types from Zod, Valibot, or ArkType. This means they work across major versions without breaking.
220
+ - Zod and Valibot adapters use async parsing internally (`safeParseAsync`), so validation is always async even for sync schemas.
221
+ - ArkType adapters are synchronous — ArkType does not support async validation.
222
+ - When a validator throws, the error is caught and converted to a string error message rather than propagating.
223
+ - For nested paths like `address.city`, the dot-separated path is used as the field key in the error record.