@zod-utils/core 4.0.0 → 5.0.0
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/README.md +1082 -341
- package/dist/index.d.mts +10 -8
- package/dist/index.d.ts +10 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -22,20 +22,137 @@ npm install @zod-utils/core zod
|
|
|
22
22
|
|
|
23
23
|
## Features
|
|
24
24
|
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
25
|
+
- **Extract defaults** - Get default values from Zod schemas
|
|
26
|
+
- **Check validation requirements** - Determine if fields will error on empty input
|
|
27
|
+
- **Extract validation checks** - Get all validation constraints (min/max, formats, patterns, etc.)
|
|
28
|
+
- **Schema utilities** - Unwrap and manipulate schema types
|
|
29
|
+
- **Zero dependencies** - Only requires Zod as a peer dependency
|
|
30
|
+
- **Universal** - Works in Node.js, browsers, and any TypeScript project
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
- [@zod-utils/core](#zod-utilscore)
|
|
35
|
+
- [Installation](#installation)
|
|
36
|
+
- [Related Packages](#related-packages)
|
|
37
|
+
- [Features](#features)
|
|
38
|
+
- [Quick Start](#quick-start)
|
|
39
|
+
- [API Reference](#api-reference)
|
|
40
|
+
- [`getSchemaDefaults(schema, options?)`](#getschemadefaultsschema-options)
|
|
41
|
+
- [Basic Example](#basic-example)
|
|
42
|
+
- [With Optional and Nullable Fields](#with-optional-and-nullable-fields)
|
|
43
|
+
- [With Function Defaults](#with-function-defaults)
|
|
44
|
+
- [Nested Objects - Important Behavior](#nested-objects---important-behavior)
|
|
45
|
+
- [With Discriminated Unions](#with-discriminated-unions)
|
|
46
|
+
- [With Schema Transforms](#with-schema-transforms)
|
|
47
|
+
- [`requiresValidInput(field)`](#requiresvalidinputfield)
|
|
48
|
+
- [How It Works](#how-it-works)
|
|
49
|
+
- [String Fields](#string-fields)
|
|
50
|
+
- [Number Fields](#number-fields)
|
|
51
|
+
- [Boolean Fields](#boolean-fields)
|
|
52
|
+
- [Array Fields](#array-fields)
|
|
53
|
+
- [Object Fields](#object-fields)
|
|
54
|
+
- [Other Types](#other-types)
|
|
55
|
+
- [Real-World Form Example](#real-world-form-example)
|
|
56
|
+
- [`extractDefaultValue(field)`](#extractdefaultvaluefield)
|
|
57
|
+
- [Basic Usage](#basic-usage)
|
|
58
|
+
- [With Wrappers](#with-wrappers)
|
|
59
|
+
- [With Function Defaults](#with-function-defaults-1)
|
|
60
|
+
- [With Transforms](#with-transforms)
|
|
61
|
+
- [With Unions](#with-unions)
|
|
62
|
+
- [`getPrimitiveType(field)`](#getprimitivetypefield)
|
|
63
|
+
- [Basic Usage](#basic-usage-1)
|
|
64
|
+
- [Stops at Arrays](#stops-at-arrays)
|
|
65
|
+
- [With Transforms](#with-transforms-1)
|
|
66
|
+
- [With Unions](#with-unions-1)
|
|
67
|
+
- [`removeDefault(field)`](#removedefaultfield)
|
|
68
|
+
- [`getFieldChecks(field)`](#getfieldchecksfield)
|
|
69
|
+
- [String Validations](#string-validations)
|
|
70
|
+
- [Number Validations](#number-validations)
|
|
71
|
+
- [Array Validations](#array-validations)
|
|
72
|
+
- [Date Validations](#date-validations)
|
|
73
|
+
- [Unwrapping Behavior](#unwrapping-behavior)
|
|
74
|
+
- [No Constraints](#no-constraints)
|
|
75
|
+
- [Using Checks in UI](#using-checks-in-ui)
|
|
76
|
+
- [`extractDiscriminatedSchema(props)`](#extractdiscriminatedschemaprops)
|
|
77
|
+
- [With Different Discriminator Types](#with-different-discriminator-types)
|
|
78
|
+
- [`extractFieldFromSchema(props)`](#extractfieldfromschemaprops)
|
|
79
|
+
- [Basic Usage](#basic-usage-2)
|
|
80
|
+
- [Nested Paths](#nested-paths)
|
|
81
|
+
- [Array Element Access](#array-element-access)
|
|
82
|
+
- [With Discriminated Unions](#with-discriminated-unions-1)
|
|
83
|
+
- [With Transforms](#with-transforms-2)
|
|
84
|
+
- [`extendWithMeta(field, transform)`](#extendwithmetafield-transform)
|
|
85
|
+
- [Use Case: Shared Field Definitions with i18n](#use-case-shared-field-definitions-with-i18n)
|
|
86
|
+
- [`toFieldSelector(props)`](#tofieldselectorprops)
|
|
87
|
+
- [Type Utilities](#type-utilities)
|
|
88
|
+
- [`Simplify<T>`](#simplifyt)
|
|
89
|
+
- [`Paths<T, FilterType?, Strict?>`](#pathst-filtertype-strict)
|
|
90
|
+
- [Basic Usage](#basic-usage-3)
|
|
91
|
+
- [Filter by Type](#filter-by-type)
|
|
92
|
+
- [Strict vs Non-Strict Mode](#strict-vs-non-strict-mode)
|
|
93
|
+
- [`ValidPaths<TSchema, TDiscriminatorKey?, TDiscriminatorValue?, TFilterType?, TStrict?>`](#validpathstschema-tdiscriminatorkey-tdiscriminatorvalue-tfiltertype-tstrict)
|
|
94
|
+
- [Basic Usage](#basic-usage-4)
|
|
95
|
+
- [Filter by Type](#filter-by-type-1)
|
|
96
|
+
- [With Discriminated Unions](#with-discriminated-unions-2)
|
|
97
|
+
- [`FieldSelector<TSchema, TName, TDiscriminatorKey?, TDiscriminatorValue?, TFilterType?, TStrict?>`](#fieldselectortschema-tname-tdiscriminatorkey-tdiscriminatorvalue-tfiltertype-tstrict)
|
|
98
|
+
- [`DiscriminatedInput<TSchema, TDiscriminatorKey, TDiscriminatorValue>`](#discriminatedinputtschema-tdiscriminatorkey-tdiscriminatorvalue)
|
|
99
|
+
- [Migration Guide](#migration-guide)
|
|
100
|
+
- [Migrating to v3.0.0](#migrating-to-v300)
|
|
101
|
+
- [`ValidPathsOfType` removed → Use `ValidPaths` with type filtering](#validpathsoftype-removed--use-validpaths-with-type-filtering)
|
|
102
|
+
- [Migrating to v4.0.0](#migrating-to-v400)
|
|
103
|
+
- [`mergeFieldSelectorProps` renamed → Use `toFieldSelector`](#mergefieldselectorprops-renamed--use-tofieldselector)
|
|
104
|
+
- [License](#license)
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Quick Start
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { z } from "zod";
|
|
112
|
+
import {
|
|
113
|
+
getSchemaDefaults,
|
|
114
|
+
requiresValidInput,
|
|
115
|
+
extractDefaultValue,
|
|
116
|
+
getPrimitiveType,
|
|
117
|
+
getFieldChecks,
|
|
118
|
+
} from "@zod-utils/core";
|
|
119
|
+
|
|
120
|
+
// Define your schema
|
|
121
|
+
const userSchema = z.object({
|
|
122
|
+
name: z.string().min(1),
|
|
123
|
+
email: z.string().email(),
|
|
124
|
+
age: z.number().min(18).max(120).default(25),
|
|
125
|
+
bio: z.string().optional(),
|
|
126
|
+
tags: z.array(z.string()).default([]),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Extract defaults for form initialization
|
|
130
|
+
const defaults = getSchemaDefaults(userSchema);
|
|
131
|
+
// { age: 25, tags: [] }
|
|
132
|
+
|
|
133
|
+
// Check which fields require valid input (for showing * in forms)
|
|
134
|
+
requiresValidInput(userSchema.shape.name); // true - has min(1)
|
|
135
|
+
requiresValidInput(userSchema.shape.email); // true - email validation
|
|
136
|
+
requiresValidInput(userSchema.shape.age); // true - number type
|
|
137
|
+
requiresValidInput(userSchema.shape.bio); // false - optional
|
|
138
|
+
|
|
139
|
+
// Get validation constraints for UI hints
|
|
140
|
+
getFieldChecks(userSchema.shape.age);
|
|
141
|
+
// [
|
|
142
|
+
// { check: 'greater_than', value: 18, inclusive: true },
|
|
143
|
+
// { check: 'less_than', value: 120, inclusive: true }
|
|
144
|
+
// ]
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
31
148
|
|
|
32
149
|
## API Reference
|
|
33
150
|
|
|
34
|
-
### `getSchemaDefaults(schema)`
|
|
151
|
+
### `getSchemaDefaults(schema, options?)`
|
|
35
152
|
|
|
36
153
|
Extract all default values from a Zod object schema. Only extracts fields that explicitly have `.default()` on them.
|
|
37
154
|
|
|
38
|
-
|
|
155
|
+
#### Basic Example
|
|
39
156
|
|
|
40
157
|
```typescript
|
|
41
158
|
import { getSchemaDefaults } from "@zod-utils/core";
|
|
@@ -44,13 +161,8 @@ import { z } from "zod";
|
|
|
44
161
|
const schema = z.object({
|
|
45
162
|
name: z.string().default("John Doe"),
|
|
46
163
|
age: z.number().default(25),
|
|
47
|
-
email: z.string().email(), // no default -
|
|
48
|
-
|
|
49
|
-
.object({
|
|
50
|
-
theme: z.string().default("light"),
|
|
51
|
-
notifications: z.boolean().default(true),
|
|
52
|
-
})
|
|
53
|
-
.default({}), // must have explicit .default() to be extracted
|
|
164
|
+
email: z.string().email(), // no default - NOT included
|
|
165
|
+
active: z.boolean().default(true),
|
|
54
166
|
tags: z.array(z.string()).default([]),
|
|
55
167
|
});
|
|
56
168
|
|
|
@@ -58,236 +170,571 @@ const defaults = getSchemaDefaults(schema);
|
|
|
58
170
|
// {
|
|
59
171
|
// name: 'John Doe',
|
|
60
172
|
// age: 25,
|
|
61
|
-
//
|
|
173
|
+
// active: true,
|
|
62
174
|
// tags: []
|
|
63
175
|
// }
|
|
64
176
|
```
|
|
65
177
|
|
|
66
|
-
|
|
178
|
+
#### With Optional and Nullable Fields
|
|
67
179
|
|
|
68
|
-
|
|
180
|
+
```typescript
|
|
181
|
+
const schema = z.object({
|
|
182
|
+
// Optional with default - included
|
|
183
|
+
nickname: z.string().default("Anonymous").optional(),
|
|
184
|
+
// Nullable with default - included
|
|
185
|
+
title: z.string().default("Mr.").nullable(),
|
|
186
|
+
// Optional without default - NOT included
|
|
187
|
+
middleName: z.string().optional(),
|
|
188
|
+
// Nullable without default - NOT included
|
|
189
|
+
suffix: z.string().nullable(),
|
|
190
|
+
});
|
|
69
191
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
- Objects with defaults: `.object({...}).default({})`
|
|
74
|
-
- Skips fields without explicit defaults
|
|
192
|
+
const defaults = getSchemaDefaults(schema);
|
|
193
|
+
// { nickname: 'Anonymous', title: 'Mr.' }
|
|
194
|
+
```
|
|
75
195
|
|
|
76
|
-
|
|
196
|
+
#### With Function Defaults
|
|
77
197
|
|
|
78
|
-
|
|
198
|
+
```typescript
|
|
199
|
+
const schema = z.object({
|
|
200
|
+
id: z.string().default(() => crypto.randomUUID()),
|
|
201
|
+
createdAt: z.number().default(() => Date.now()),
|
|
202
|
+
});
|
|
79
203
|
|
|
80
|
-
|
|
204
|
+
const defaults = getSchemaDefaults(schema);
|
|
205
|
+
// { id: 'a1b2c3...', createdAt: 1703980800000 }
|
|
206
|
+
// Functions are called at extraction time
|
|
207
|
+
```
|
|
81
208
|
|
|
82
|
-
|
|
209
|
+
#### Nested Objects - Important Behavior
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
// Nested defaults are NOT extracted unless parent has .default()
|
|
213
|
+
const schema = z.object({
|
|
214
|
+
name: z.string().default("John"),
|
|
215
|
+
settings: z.object({
|
|
216
|
+
theme: z.string().default("light"), // Has default
|
|
217
|
+
notifications: z.boolean().default(true), // Has default
|
|
218
|
+
}), // Parent has NO .default() - entire object skipped!
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
getSchemaDefaults(schema);
|
|
222
|
+
// { name: 'John' }
|
|
223
|
+
// settings is NOT included because parent object has no .default()
|
|
224
|
+
|
|
225
|
+
// To include nested defaults, add .default() to parent:
|
|
226
|
+
const schemaWithNestedDefaults = z.object({
|
|
227
|
+
name: z.string().default("John"),
|
|
228
|
+
settings: z
|
|
229
|
+
.object({
|
|
230
|
+
theme: z.string().default("light"),
|
|
231
|
+
notifications: z.boolean().default(true),
|
|
232
|
+
})
|
|
233
|
+
.default({ theme: "light", notifications: true }), // Parent has .default()
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
getSchemaDefaults(schemaWithNestedDefaults);
|
|
237
|
+
// { name: 'John', settings: { theme: 'light', notifications: true } }
|
|
238
|
+
```
|
|
83
239
|
|
|
84
|
-
|
|
240
|
+
#### With Discriminated Unions
|
|
85
241
|
|
|
86
242
|
```typescript
|
|
87
|
-
|
|
88
|
-
|
|
243
|
+
const formSchema = z.discriminatedUnion("mode", [
|
|
244
|
+
z.object({
|
|
245
|
+
mode: z.literal("create"),
|
|
246
|
+
name: z.string(),
|
|
247
|
+
age: z.number().default(18),
|
|
248
|
+
}),
|
|
249
|
+
z.object({
|
|
250
|
+
mode: z.literal("edit"),
|
|
251
|
+
id: z.number().default(1),
|
|
252
|
+
bio: z.string().default("bio goes here"),
|
|
253
|
+
}),
|
|
254
|
+
]);
|
|
255
|
+
|
|
256
|
+
// Get defaults for 'create' mode
|
|
257
|
+
const createDefaults = getSchemaDefaults(formSchema, {
|
|
258
|
+
discriminator: { key: "mode", value: "create" },
|
|
259
|
+
});
|
|
260
|
+
// { age: 18 }
|
|
261
|
+
|
|
262
|
+
// Get defaults for 'edit' mode
|
|
263
|
+
const editDefaults = getSchemaDefaults(formSchema, {
|
|
264
|
+
discriminator: { key: "mode", value: "edit" },
|
|
265
|
+
});
|
|
266
|
+
// { id: 1, bio: 'bio goes here' }
|
|
89
267
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
268
|
+
// Without discriminator, returns empty object
|
|
269
|
+
getSchemaDefaults(formSchema);
|
|
270
|
+
// {}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
#### With Schema Transforms
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
const schema = z
|
|
277
|
+
.object({
|
|
278
|
+
name: z.string().default("John"),
|
|
279
|
+
age: z.number().default(25),
|
|
280
|
+
})
|
|
281
|
+
.transform((data) => ({ ...data, computed: true }));
|
|
282
|
+
|
|
283
|
+
// Extracts defaults from the INPUT type (before transform)
|
|
284
|
+
getSchemaDefaults(schema);
|
|
285
|
+
// { name: 'John', age: 25 }
|
|
95
286
|
```
|
|
96
287
|
|
|
97
|
-
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
### `requiresValidInput(field)`
|
|
291
|
+
|
|
292
|
+
Determines if a field will show validation errors when the user submits empty or invalid input. Useful for form UIs to show which fields need valid user input (asterisks, validation indicators).
|
|
293
|
+
|
|
294
|
+
**Key insight:** Defaults are just initial values - they don't prevent validation errors if the user clears the field.
|
|
295
|
+
|
|
296
|
+
#### How It Works
|
|
98
297
|
|
|
99
298
|
1. Removes `.default()` wrappers (defaults ≠ validation rules)
|
|
100
299
|
2. Tests if underlying schema accepts empty/invalid input:
|
|
101
300
|
- `undefined` (via `.optional()`)
|
|
102
301
|
- `null` (via `.nullable()`)
|
|
103
|
-
- Empty string (plain `z.string()`)
|
|
104
|
-
- Empty array (plain `z.array()`)
|
|
302
|
+
- Empty string (plain `z.string()` without `.min(1)` or `.nonempty()`)
|
|
303
|
+
- Empty array (plain `z.array()` without `.min(1)` or `.nonempty()`)
|
|
105
304
|
3. Returns `true` if validation will fail on empty input
|
|
106
305
|
|
|
107
|
-
|
|
306
|
+
#### String Fields
|
|
108
307
|
|
|
109
308
|
```typescript
|
|
110
309
|
import { requiresValidInput } from "@zod-utils/core";
|
|
111
310
|
import { z } from "zod";
|
|
112
311
|
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
requiresValidInput(
|
|
312
|
+
// Plain string accepts empty string ""
|
|
313
|
+
requiresValidInput(z.string()); // false
|
|
314
|
+
requiresValidInput(z.string().optional()); // false
|
|
315
|
+
requiresValidInput(z.string().nullable()); // false
|
|
316
|
+
|
|
317
|
+
// String with min(1) requires non-empty
|
|
318
|
+
requiresValidInput(z.string().min(1)); // true
|
|
319
|
+
requiresValidInput(z.string().nonempty()); // true
|
|
320
|
+
|
|
321
|
+
// Email validation rejects empty string
|
|
322
|
+
requiresValidInput(z.string().email()); // true
|
|
323
|
+
|
|
324
|
+
// Default doesn't change validation requirement!
|
|
325
|
+
requiresValidInput(z.string().default("hello")); // false - plain string
|
|
326
|
+
requiresValidInput(z.string().min(1).default("hello")); // true - has min(1)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
#### Number Fields
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
// Numbers always require valid input (empty string fails)
|
|
333
|
+
requiresValidInput(z.number()); // true
|
|
334
|
+
requiresValidInput(z.number().default(0)); // true - default doesn't help
|
|
335
|
+
|
|
336
|
+
// Optional numbers don't require input
|
|
337
|
+
requiresValidInput(z.number().optional()); // false
|
|
338
|
+
requiresValidInput(z.number().nullable()); // false
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
#### Boolean Fields
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
// Booleans require true/false value
|
|
345
|
+
requiresValidInput(z.boolean()); // true
|
|
346
|
+
requiresValidInput(z.boolean().default(false)); // true
|
|
347
|
+
|
|
348
|
+
// Optional booleans don't require input
|
|
349
|
+
requiresValidInput(z.boolean().optional()); // false
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
#### Array Fields
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
// Plain arrays accept empty []
|
|
356
|
+
requiresValidInput(z.array(z.string())); // false
|
|
357
|
+
requiresValidInput(z.array(z.string()).default([])); // false
|
|
358
|
+
|
|
359
|
+
// Arrays with min(1) require at least one item
|
|
360
|
+
requiresValidInput(z.array(z.string()).min(1)); // true
|
|
361
|
+
requiresValidInput(z.array(z.string()).nonempty()); // true
|
|
116
362
|
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
363
|
+
// Optional arrays don't require input
|
|
364
|
+
requiresValidInput(z.array(z.string()).optional()); // false
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
#### Object Fields
|
|
120
368
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
requiresValidInput(
|
|
369
|
+
```typescript
|
|
370
|
+
// Objects require some structure
|
|
371
|
+
requiresValidInput(z.object({ name: z.string() })); // true
|
|
124
372
|
|
|
125
|
-
// Optional
|
|
126
|
-
|
|
127
|
-
|
|
373
|
+
// Optional objects don't require input
|
|
374
|
+
requiresValidInput(z.object({ name: z.string() }).optional()); // false
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
#### Other Types
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
// Enums and literals require exact match
|
|
381
|
+
requiresValidInput(z.enum(["a", "b", "c"])); // true
|
|
382
|
+
requiresValidInput(z.literal("test")); // true
|
|
128
383
|
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
requiresValidInput(
|
|
384
|
+
// Any and unknown accept everything
|
|
385
|
+
requiresValidInput(z.any()); // false
|
|
386
|
+
requiresValidInput(z.unknown()); // false
|
|
132
387
|
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
388
|
+
// Never rejects everything
|
|
389
|
+
requiresValidInput(z.never()); // true
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
#### Real-World Form Example
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
const userFormSchema = z.object({
|
|
396
|
+
// Required - show asterisk
|
|
397
|
+
firstName: z.string().min(1), // requiresValidInput: true
|
|
398
|
+
lastName: z.string().min(1), // requiresValidInput: true
|
|
399
|
+
email: z.string().email(), // requiresValidInput: true
|
|
400
|
+
age: z.number().min(18), // requiresValidInput: true
|
|
401
|
+
|
|
402
|
+
// Optional - no asterisk
|
|
403
|
+
middleName: z.string().optional(), // requiresValidInput: false
|
|
404
|
+
nickname: z.string(), // requiresValidInput: false (empty allowed)
|
|
405
|
+
bio: z.string().optional(), // requiresValidInput: false
|
|
406
|
+
|
|
407
|
+
// Has default but still requires valid input if user clears
|
|
408
|
+
role: z.string().min(1).default("user"), // requiresValidInput: true
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// Use in form UI
|
|
412
|
+
function FormField({ name, schema }: { name: string; schema: z.ZodType }) {
|
|
413
|
+
const field = schema.shape[name];
|
|
414
|
+
const isRequired = requiresValidInput(field);
|
|
415
|
+
|
|
416
|
+
return (
|
|
417
|
+
<label>
|
|
418
|
+
{name} {isRequired && <span className="text-red-500">*</span>}
|
|
419
|
+
<input name={name} />
|
|
420
|
+
</label>
|
|
421
|
+
);
|
|
422
|
+
}
|
|
136
423
|
```
|
|
137
424
|
|
|
138
425
|
---
|
|
139
426
|
|
|
140
|
-
### `
|
|
427
|
+
### `extractDefaultValue(field)`
|
|
141
428
|
|
|
142
|
-
|
|
143
|
-
Stops at arrays without unwrapping them.
|
|
429
|
+
Extract the default value from a Zod field. Recursively unwraps optional/nullable/union/transform layers.
|
|
144
430
|
|
|
145
|
-
|
|
431
|
+
#### Basic Usage
|
|
146
432
|
|
|
147
433
|
```typescript
|
|
148
|
-
import {
|
|
434
|
+
import { extractDefaultValue } from "@zod-utils/core";
|
|
149
435
|
import { z } from "zod";
|
|
150
436
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
//
|
|
437
|
+
// Simple defaults
|
|
438
|
+
extractDefaultValue(z.string().default("hello")); // 'hello'
|
|
439
|
+
extractDefaultValue(z.number().default(42)); // 42
|
|
440
|
+
extractDefaultValue(z.boolean().default(true)); // true
|
|
441
|
+
|
|
442
|
+
// No default returns undefined
|
|
443
|
+
extractDefaultValue(z.string()); // undefined
|
|
444
|
+
extractDefaultValue(z.string().optional()); // undefined
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
#### With Wrappers
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
// Unwraps optional/nullable to find default
|
|
451
|
+
extractDefaultValue(z.string().default("hello").optional()); // 'hello'
|
|
452
|
+
extractDefaultValue(z.string().default("hello").nullable()); // 'hello'
|
|
453
|
+
extractDefaultValue(z.string().default("hello").optional().nullable()); // 'hello'
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
#### With Function Defaults
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
// Functions are called to get the value
|
|
460
|
+
extractDefaultValue(z.string().default(() => "dynamic")); // 'dynamic'
|
|
461
|
+
extractDefaultValue(z.number().default(() => Date.now())); // 1703980800000
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
#### With Transforms
|
|
154
465
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
466
|
+
```typescript
|
|
467
|
+
// Extracts input default, not output
|
|
468
|
+
const schema = z
|
|
469
|
+
.string()
|
|
470
|
+
.default("hello")
|
|
471
|
+
.transform((val) => val.toUpperCase());
|
|
158
472
|
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
473
|
+
extractDefaultValue(schema); // 'hello' (not 'HELLO')
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
#### With Unions
|
|
477
|
+
|
|
478
|
+
```typescript
|
|
479
|
+
// Only checks first option in union
|
|
480
|
+
extractDefaultValue(z.union([z.string().default("hello"), z.number()]));
|
|
481
|
+
// undefined - unions with multiple non-nullish types return undefined
|
|
482
|
+
|
|
483
|
+
extractDefaultValue(z.union([z.string().default("hello"), z.null()]));
|
|
484
|
+
// 'hello' - union with nullish types extracts from first option
|
|
163
485
|
```
|
|
164
486
|
|
|
165
487
|
---
|
|
166
488
|
|
|
167
|
-
### `
|
|
489
|
+
### `getPrimitiveType(field)`
|
|
490
|
+
|
|
491
|
+
Get the primitive type of a Zod field by unwrapping optional/nullable/default/transform wrappers. Stops at arrays without unwrapping them.
|
|
168
492
|
|
|
169
|
-
|
|
493
|
+
#### Basic Usage
|
|
170
494
|
|
|
171
495
|
```typescript
|
|
172
|
-
import {
|
|
496
|
+
import { getPrimitiveType } from "@zod-utils/core";
|
|
173
497
|
import { z } from "zod";
|
|
174
498
|
|
|
175
|
-
|
|
176
|
-
|
|
499
|
+
// Unwraps to get underlying type
|
|
500
|
+
getPrimitiveType(z.string()); // ZodString
|
|
501
|
+
getPrimitiveType(z.string().optional()); // ZodString
|
|
502
|
+
getPrimitiveType(z.string().nullable()); // ZodString
|
|
503
|
+
getPrimitiveType(z.string().default("test")); // ZodString
|
|
504
|
+
getPrimitiveType(z.string().optional().nullable()); // ZodString
|
|
505
|
+
getPrimitiveType(z.string().default("x").optional().nullable()); // ZodString
|
|
506
|
+
```
|
|
177
507
|
|
|
178
|
-
|
|
179
|
-
|
|
508
|
+
#### Stops at Arrays
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
// Arrays are returned as-is (not unwrapped to element type)
|
|
512
|
+
getPrimitiveType(z.array(z.string())); // ZodArray
|
|
513
|
+
getPrimitiveType(z.array(z.string()).optional()); // ZodArray
|
|
180
514
|
```
|
|
181
515
|
|
|
182
|
-
|
|
516
|
+
#### With Transforms
|
|
183
517
|
|
|
184
|
-
|
|
518
|
+
```typescript
|
|
519
|
+
// Unwraps transform to get input type
|
|
520
|
+
getPrimitiveType(z.string().transform((val) => val.toUpperCase())); // ZodString
|
|
521
|
+
|
|
522
|
+
getPrimitiveType(
|
|
523
|
+
z.object({ name: z.string() }).transform((data) => ({ ...data, id: 1 }))
|
|
524
|
+
); // ZodObject
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
#### With Unions
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
// Union with only nullish types - extracts the non-nullish type
|
|
531
|
+
getPrimitiveType(z.union([z.string(), z.null()])); // ZodString
|
|
532
|
+
getPrimitiveType(z.union([z.number(), z.undefined()])); // ZodNumber
|
|
185
533
|
|
|
186
|
-
|
|
534
|
+
// Union with multiple non-nullish types - returns union as-is
|
|
535
|
+
getPrimitiveType(z.union([z.string(), z.number()])); // ZodUnion
|
|
536
|
+
getPrimitiveType(z.union([z.literal("a"), z.literal("b")])); // ZodUnion
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
---
|
|
187
540
|
|
|
188
|
-
|
|
541
|
+
### `removeDefault(field)`
|
|
189
542
|
|
|
190
|
-
|
|
543
|
+
Remove default values from a Zod field while preserving optional/nullable wrappers.
|
|
191
544
|
|
|
192
545
|
```typescript
|
|
193
|
-
import {
|
|
546
|
+
import { removeDefault } from "@zod-utils/core";
|
|
194
547
|
import { z } from "zod";
|
|
195
548
|
|
|
196
|
-
//
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const noDefault = z.string();
|
|
201
|
-
extractDefaultValue(noDefault); // undefined
|
|
549
|
+
// Remove default
|
|
550
|
+
const withDefault = z.string().default("hello");
|
|
551
|
+
const withoutDefault = removeDefault(withDefault);
|
|
202
552
|
|
|
203
|
-
//
|
|
204
|
-
|
|
205
|
-
extractDefaultValue(unionField); // 'hello'
|
|
553
|
+
withDefault.parse(undefined); // 'hello'
|
|
554
|
+
withoutDefault.parse(undefined); // throws ZodError
|
|
206
555
|
|
|
207
|
-
//
|
|
208
|
-
const
|
|
209
|
-
|
|
556
|
+
// Preserves optional/nullable
|
|
557
|
+
const complex = z.string().default("test").optional().nullable();
|
|
558
|
+
const removed = removeDefault(complex);
|
|
210
559
|
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
560
|
+
removed.parse(undefined); // undefined (optional still works)
|
|
561
|
+
removed.parse(null); // null (nullable still works)
|
|
562
|
+
removed.parse("hello"); // 'hello'
|
|
214
563
|
|
|
215
|
-
//
|
|
216
|
-
const
|
|
217
|
-
|
|
564
|
+
// Returns same schema if no default
|
|
565
|
+
const plain = z.string();
|
|
566
|
+
removeDefault(plain) === plain; // true
|
|
218
567
|
```
|
|
219
568
|
|
|
220
569
|
---
|
|
221
570
|
|
|
222
571
|
### `getFieldChecks(field)`
|
|
223
572
|
|
|
224
|
-
Extract all validation check definitions from a Zod schema field. Returns Zod's raw check definition objects
|
|
573
|
+
Extract all validation check definitions from a Zod schema field. Returns Zod's raw check definition objects.
|
|
225
574
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
**Supported check types:** Returns any of 21 check types:
|
|
229
|
-
- **Length checks**: `min_length`, `max_length`, `length_equals` (strings, arrays)
|
|
230
|
-
- **Size checks**: `min_size`, `max_size`, `size_equals` (files, sets, maps)
|
|
231
|
-
- **Numeric checks**: `greater_than`, `less_than`, `multiple_of`
|
|
232
|
-
- **Format checks**: `number_format`, `bigint_format`, `string_format` (email, url, uuid, etc.)
|
|
233
|
-
- **String pattern checks**: `regex`, `lowercase`, `uppercase`, `includes`, `starts_with`, `ends_with`
|
|
234
|
-
- **Other checks**: `property`, `mime_type`, `overwrite`
|
|
575
|
+
#### String Validations
|
|
235
576
|
|
|
236
577
|
```typescript
|
|
237
578
|
import { getFieldChecks } from "@zod-utils/core";
|
|
238
579
|
import { z } from "zod";
|
|
239
580
|
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
|
|
581
|
+
// Length constraints
|
|
582
|
+
getFieldChecks(z.string().min(3));
|
|
583
|
+
// [{ check: 'min_length', minimum: 3, ... }]
|
|
584
|
+
|
|
585
|
+
getFieldChecks(z.string().max(100));
|
|
586
|
+
// [{ check: 'max_length', maximum: 100, ... }]
|
|
587
|
+
|
|
588
|
+
getFieldChecks(z.string().min(3).max(20));
|
|
243
589
|
// [
|
|
244
|
-
// { check: 'min_length', minimum: 3,
|
|
245
|
-
// { check: 'max_length', maximum: 20,
|
|
590
|
+
// { check: 'min_length', minimum: 3, ... },
|
|
591
|
+
// { check: 'max_length', maximum: 20, ... }
|
|
246
592
|
// ]
|
|
247
593
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
594
|
+
getFieldChecks(z.string().length(10));
|
|
595
|
+
// [{ check: 'length_equals', length: 10, ... }]
|
|
596
|
+
|
|
597
|
+
// Format validations
|
|
598
|
+
getFieldChecks(z.string().email());
|
|
599
|
+
// [{ check: 'string_format', format: 'email', ... }]
|
|
600
|
+
|
|
601
|
+
getFieldChecks(z.string().url());
|
|
602
|
+
// [{ check: 'string_format', format: 'url', ... }]
|
|
603
|
+
|
|
604
|
+
getFieldChecks(z.string().uuid());
|
|
605
|
+
// [{ check: 'string_format', format: 'uuid', ... }]
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
#### Number Validations
|
|
609
|
+
|
|
610
|
+
```typescript
|
|
611
|
+
getFieldChecks(z.number().min(18));
|
|
612
|
+
// [{ check: 'greater_than', value: 18, inclusive: true, ... }]
|
|
613
|
+
|
|
614
|
+
getFieldChecks(z.number().max(120));
|
|
615
|
+
// [{ check: 'less_than', value: 120, inclusive: true, ... }]
|
|
616
|
+
|
|
617
|
+
getFieldChecks(z.number().min(0).max(100));
|
|
251
618
|
// [
|
|
252
|
-
// { check: 'greater_than', value:
|
|
253
|
-
// { check: 'less_than', value:
|
|
619
|
+
// { check: 'greater_than', value: 0, inclusive: true, ... },
|
|
620
|
+
// { check: 'less_than', value: 100, inclusive: true, ... }
|
|
254
621
|
// ]
|
|
255
622
|
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
|
|
623
|
+
getFieldChecks(z.number().gt(0)); // exclusive >
|
|
624
|
+
// [{ check: 'greater_than', value: 0, inclusive: false, ... }]
|
|
625
|
+
|
|
626
|
+
getFieldChecks(z.number().lt(100)); // exclusive <
|
|
627
|
+
// [{ check: 'less_than', value: 100, inclusive: false, ... }]
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
#### Array Validations
|
|
631
|
+
|
|
632
|
+
```typescript
|
|
633
|
+
getFieldChecks(z.array(z.string()).min(1));
|
|
634
|
+
// [{ check: 'min_length', minimum: 1, ... }]
|
|
635
|
+
|
|
636
|
+
getFieldChecks(z.array(z.string()).max(10));
|
|
637
|
+
// [{ check: 'max_length', maximum: 10, ... }]
|
|
638
|
+
|
|
639
|
+
getFieldChecks(z.array(z.string()).min(1).max(5));
|
|
259
640
|
// [
|
|
260
641
|
// { check: 'min_length', minimum: 1, ... },
|
|
261
642
|
// { check: 'max_length', maximum: 5, ... }
|
|
262
643
|
// ]
|
|
644
|
+
```
|
|
263
645
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
646
|
+
#### Date Validations
|
|
647
|
+
|
|
648
|
+
```typescript
|
|
649
|
+
const minDate = new Date("2024-01-01");
|
|
650
|
+
const maxDate = new Date("2024-12-31");
|
|
651
|
+
|
|
652
|
+
getFieldChecks(z.date().min(minDate));
|
|
653
|
+
// [{ check: 'greater_than', value: Date, inclusive: true, ... }]
|
|
268
654
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
655
|
+
getFieldChecks(z.date().max(maxDate));
|
|
656
|
+
// [{ check: 'less_than', value: Date, inclusive: true, ... }]
|
|
657
|
+
|
|
658
|
+
getFieldChecks(z.date().min(minDate).max(maxDate));
|
|
272
659
|
// [
|
|
273
|
-
// { check: '
|
|
274
|
-
// { check: '
|
|
660
|
+
// { check: 'greater_than', value: Date, inclusive: true, ... },
|
|
661
|
+
// { check: 'less_than', value: Date, inclusive: true, ... }
|
|
275
662
|
// ]
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
#### Unwrapping Behavior
|
|
276
666
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
getFieldChecks(
|
|
667
|
+
```typescript
|
|
668
|
+
// Automatically unwraps optional/nullable/default
|
|
669
|
+
getFieldChecks(z.string().min(3).max(20).optional());
|
|
670
|
+
// [{ check: 'min_length', ... }, { check: 'max_length', ... }]
|
|
671
|
+
|
|
672
|
+
getFieldChecks(z.number().min(0).max(100).nullable());
|
|
673
|
+
// [{ check: 'greater_than', ... }, { check: 'less_than', ... }]
|
|
674
|
+
|
|
675
|
+
getFieldChecks(z.string().min(5).max(50).default("test"));
|
|
676
|
+
// [{ check: 'min_length', ... }, { check: 'max_length', ... }]
|
|
677
|
+
|
|
678
|
+
getFieldChecks(
|
|
679
|
+
z.string().min(10).max(500).optional().nullable().default("test")
|
|
680
|
+
);
|
|
681
|
+
// [{ check: 'min_length', ... }, { check: 'max_length', ... }]
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
#### No Constraints
|
|
685
|
+
|
|
686
|
+
```typescript
|
|
687
|
+
// Returns empty array when no constraints
|
|
688
|
+
getFieldChecks(z.string()); // []
|
|
689
|
+
getFieldChecks(z.number()); // []
|
|
690
|
+
getFieldChecks(z.boolean()); // []
|
|
691
|
+
getFieldChecks(z.date()); // []
|
|
280
692
|
```
|
|
281
693
|
|
|
282
|
-
|
|
694
|
+
#### Using Checks in UI
|
|
283
695
|
|
|
284
696
|
```typescript
|
|
285
|
-
|
|
697
|
+
function getInputProps(field: z.ZodType) {
|
|
698
|
+
const checks = getFieldChecks(field);
|
|
699
|
+
const props: Record<string, unknown> = {};
|
|
700
|
+
|
|
701
|
+
for (const check of checks) {
|
|
702
|
+
switch (check.check) {
|
|
703
|
+
case "min_length":
|
|
704
|
+
props.minLength = check.minimum;
|
|
705
|
+
break;
|
|
706
|
+
case "max_length":
|
|
707
|
+
props.maxLength = check.maximum;
|
|
708
|
+
break;
|
|
709
|
+
case "greater_than":
|
|
710
|
+
props.min = check.value;
|
|
711
|
+
break;
|
|
712
|
+
case "less_than":
|
|
713
|
+
props.max = check.value;
|
|
714
|
+
break;
|
|
715
|
+
case "string_format":
|
|
716
|
+
if (check.format === "email") props.type = "email";
|
|
717
|
+
if (check.format === "url") props.type = "url";
|
|
718
|
+
break;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
return props;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Usage
|
|
726
|
+
const usernameField = z.string().min(3).max(20);
|
|
727
|
+
getInputProps(usernameField);
|
|
728
|
+
// { minLength: 3, maxLength: 20 }
|
|
729
|
+
|
|
730
|
+
const ageField = z.number().min(18).max(120);
|
|
731
|
+
getInputProps(ageField);
|
|
732
|
+
// { min: 18, max: 120 }
|
|
286
733
|
```
|
|
287
734
|
|
|
288
735
|
---
|
|
289
736
|
|
|
290
|
-
### `extractDiscriminatedSchema(
|
|
737
|
+
### `extractDiscriminatedSchema(props)`
|
|
291
738
|
|
|
292
739
|
Extract a specific variant from a discriminated union schema based on the discriminator field and value.
|
|
293
740
|
|
|
@@ -295,14 +742,14 @@ Extract a specific variant from a discriminated union schema based on the discri
|
|
|
295
742
|
import { extractDiscriminatedSchema } from "@zod-utils/core";
|
|
296
743
|
import { z } from "zod";
|
|
297
744
|
|
|
298
|
-
const userSchema = z.discriminatedUnion(
|
|
745
|
+
const userSchema = z.discriminatedUnion("mode", [
|
|
299
746
|
z.object({
|
|
300
|
-
mode: z.literal(
|
|
747
|
+
mode: z.literal("create"),
|
|
301
748
|
name: z.string(),
|
|
302
749
|
age: z.number().optional(),
|
|
303
750
|
}),
|
|
304
751
|
z.object({
|
|
305
|
-
mode: z.literal(
|
|
752
|
+
mode: z.literal("edit"),
|
|
306
753
|
id: z.number(),
|
|
307
754
|
name: z.string().optional(),
|
|
308
755
|
bio: z.string().optional(),
|
|
@@ -312,107 +759,166 @@ const userSchema = z.discriminatedUnion('mode', [
|
|
|
312
759
|
// Extract the 'create' variant
|
|
313
760
|
const createSchema = extractDiscriminatedSchema({
|
|
314
761
|
schema: userSchema,
|
|
315
|
-
key:
|
|
316
|
-
value:
|
|
762
|
+
key: "mode",
|
|
763
|
+
value: "create",
|
|
317
764
|
});
|
|
318
765
|
// Returns: z.ZodObject with { mode, name, age }
|
|
319
766
|
|
|
320
767
|
// Extract the 'edit' variant
|
|
321
768
|
const editSchema = extractDiscriminatedSchema({
|
|
322
769
|
schema: userSchema,
|
|
323
|
-
key:
|
|
324
|
-
value:
|
|
770
|
+
key: "mode",
|
|
771
|
+
value: "edit",
|
|
325
772
|
});
|
|
326
773
|
// Returns: z.ZodObject with { mode, id, name, bio }
|
|
774
|
+
|
|
775
|
+
// Invalid value returns undefined
|
|
776
|
+
const invalid = extractDiscriminatedSchema({
|
|
777
|
+
schema: userSchema,
|
|
778
|
+
key: "mode",
|
|
779
|
+
value: "invalid" as any,
|
|
780
|
+
});
|
|
781
|
+
// Returns: undefined
|
|
327
782
|
```
|
|
328
783
|
|
|
329
|
-
|
|
784
|
+
#### With Different Discriminator Types
|
|
785
|
+
|
|
786
|
+
```typescript
|
|
787
|
+
// Boolean discriminator
|
|
788
|
+
const responseSchema = z.discriminatedUnion("success", [
|
|
789
|
+
z.object({ success: z.literal(true), data: z.string() }),
|
|
790
|
+
z.object({ success: z.literal(false), error: z.string() }),
|
|
791
|
+
]);
|
|
792
|
+
|
|
793
|
+
extractDiscriminatedSchema({
|
|
794
|
+
schema: responseSchema,
|
|
795
|
+
key: "success",
|
|
796
|
+
value: true,
|
|
797
|
+
});
|
|
798
|
+
// Returns: z.ZodObject with { success, data }
|
|
799
|
+
|
|
800
|
+
// Numeric discriminator
|
|
801
|
+
const statusSchema = z.discriminatedUnion("code", [
|
|
802
|
+
z.object({ code: z.literal(200), message: z.string() }),
|
|
803
|
+
z.object({ code: z.literal(404), error: z.string() }),
|
|
804
|
+
]);
|
|
805
|
+
|
|
806
|
+
extractDiscriminatedSchema({
|
|
807
|
+
schema: statusSchema,
|
|
808
|
+
key: "code",
|
|
809
|
+
value: 200,
|
|
810
|
+
});
|
|
811
|
+
// Returns: z.ZodObject with { code, message }
|
|
812
|
+
```
|
|
330
813
|
|
|
331
814
|
---
|
|
332
815
|
|
|
333
|
-
### `extractFieldFromSchema(
|
|
816
|
+
### `extractFieldFromSchema(props)`
|
|
334
817
|
|
|
335
|
-
Extract a single field from a Zod object or discriminated union schema.
|
|
818
|
+
Extract a single field from a Zod object or discriminated union schema. Supports dot-notation paths for nested fields.
|
|
336
819
|
|
|
337
|
-
|
|
820
|
+
#### Basic Usage
|
|
338
821
|
|
|
339
822
|
```typescript
|
|
340
823
|
import { extractFieldFromSchema } from "@zod-utils/core";
|
|
341
824
|
import { z } from "zod";
|
|
342
825
|
|
|
343
|
-
// Simple object schema
|
|
344
826
|
const userSchema = z.object({
|
|
345
827
|
name: z.string(),
|
|
346
828
|
age: z.number(),
|
|
347
829
|
email: z.string().email(),
|
|
348
830
|
});
|
|
349
831
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
832
|
+
extractFieldFromSchema({ schema: userSchema, name: "name" }); // ZodString
|
|
833
|
+
extractFieldFromSchema({ schema: userSchema, name: "age" }); // ZodNumber
|
|
834
|
+
extractFieldFromSchema({ schema: userSchema, name: "email" }); // ZodString
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
#### Nested Paths
|
|
838
|
+
|
|
839
|
+
```typescript
|
|
840
|
+
const schema = z.object({
|
|
841
|
+
user: z.object({
|
|
842
|
+
profile: z.object({
|
|
843
|
+
name: z.string(),
|
|
844
|
+
age: z.number(),
|
|
845
|
+
}),
|
|
846
|
+
}),
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
extractFieldFromSchema({ schema, name: "user" }); // ZodObject
|
|
850
|
+
extractFieldFromSchema({ schema, name: "user.profile" }); // ZodObject
|
|
851
|
+
extractFieldFromSchema({ schema, name: "user.profile.name" }); // ZodString
|
|
852
|
+
extractFieldFromSchema({ schema, name: "user.profile.age" }); // ZodNumber
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
#### Array Element Access
|
|
856
|
+
|
|
857
|
+
```typescript
|
|
858
|
+
const schema = z.object({
|
|
859
|
+
addresses: z.array(
|
|
860
|
+
z.object({
|
|
861
|
+
street: z.string(),
|
|
862
|
+
city: z.string(),
|
|
863
|
+
})
|
|
864
|
+
),
|
|
353
865
|
});
|
|
354
|
-
// Returns: ZodString
|
|
355
866
|
|
|
356
|
-
//
|
|
357
|
-
|
|
867
|
+
// Use numeric index to access array elements
|
|
868
|
+
extractFieldFromSchema({ schema, name: "addresses.0.street" }); // ZodString
|
|
869
|
+
extractFieldFromSchema({ schema, name: "addresses.0.city" }); // ZodString
|
|
870
|
+
extractFieldFromSchema({ schema, name: "addresses.99.street" }); // ZodString (any index works)
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
#### With Discriminated Unions
|
|
874
|
+
|
|
875
|
+
```typescript
|
|
876
|
+
const formSchema = z.discriminatedUnion("mode", [
|
|
358
877
|
z.object({
|
|
359
|
-
mode: z.literal(
|
|
878
|
+
mode: z.literal("create"),
|
|
360
879
|
name: z.string(),
|
|
361
880
|
age: z.number().optional(),
|
|
362
881
|
}),
|
|
363
882
|
z.object({
|
|
364
|
-
mode: z.literal(
|
|
883
|
+
mode: z.literal("edit"),
|
|
365
884
|
id: z.number(),
|
|
366
885
|
name: z.string().optional(),
|
|
367
886
|
}),
|
|
368
887
|
]);
|
|
369
888
|
|
|
370
|
-
//
|
|
371
|
-
|
|
889
|
+
// Must provide discriminator for discriminated unions
|
|
890
|
+
extractFieldFromSchema({
|
|
372
891
|
schema: formSchema,
|
|
373
|
-
name:
|
|
374
|
-
discriminator: {
|
|
375
|
-
|
|
376
|
-
value: 'edit',
|
|
377
|
-
},
|
|
378
|
-
});
|
|
379
|
-
// Returns: ZodNumber
|
|
892
|
+
name: "name",
|
|
893
|
+
discriminator: { key: "mode", value: "create" },
|
|
894
|
+
}); // ZodString
|
|
380
895
|
|
|
381
|
-
|
|
382
|
-
const fieldWithoutDiscriminator = extractFieldFromSchema({
|
|
896
|
+
extractFieldFromSchema({
|
|
383
897
|
schema: formSchema,
|
|
384
|
-
name:
|
|
385
|
-
}
|
|
386
|
-
//
|
|
898
|
+
name: "id",
|
|
899
|
+
discriminator: { key: "mode", value: "edit" },
|
|
900
|
+
}); // ZodNumber
|
|
387
901
|
|
|
388
|
-
//
|
|
389
|
-
|
|
902
|
+
// Without discriminator, returns undefined
|
|
903
|
+
extractFieldFromSchema({
|
|
904
|
+
schema: formSchema,
|
|
905
|
+
name: "name",
|
|
906
|
+
}); // undefined
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
#### With Transforms
|
|
910
|
+
|
|
911
|
+
```typescript
|
|
912
|
+
const schema = z
|
|
390
913
|
.object({
|
|
391
914
|
name: z.string(),
|
|
392
915
|
age: z.number(),
|
|
393
916
|
})
|
|
394
917
|
.transform((data) => ({ ...data, computed: true }));
|
|
395
918
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
});
|
|
400
|
-
// Returns: ZodString (from the input type, not affected by transform)
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
**Discriminated union support:** When extracting fields from discriminated unions, you must provide the `discriminator` option with `key` and `value` to specify which variant to use.
|
|
404
|
-
|
|
405
|
-
---
|
|
406
|
-
|
|
407
|
-
### `toFieldSelector(props)`
|
|
408
|
-
|
|
409
|
-
Extracts a `FieldSelector` from props containing schema, name, and optional discriminator. Encapsulates type assertion so callers don't need eslint-disable.
|
|
410
|
-
|
|
411
|
-
```typescript
|
|
412
|
-
import { toFieldSelector } from "@zod-utils/core";
|
|
413
|
-
|
|
414
|
-
const selectorProps = toFieldSelector({ schema, name: 'fieldName', discriminator });
|
|
415
|
-
// Use selectorProps for extractFieldFromSchema, etc.
|
|
919
|
+
// Extracts from input type (before transform)
|
|
920
|
+
extractFieldFromSchema({ schema, name: "name" }); // ZodString
|
|
921
|
+
extractFieldFromSchema({ schema, name: "age" }); // ZodNumber
|
|
416
922
|
```
|
|
417
923
|
|
|
418
924
|
---
|
|
@@ -421,99 +927,74 @@ const selectorProps = toFieldSelector({ schema, name: 'fieldName', discriminator
|
|
|
421
927
|
|
|
422
928
|
Extends a Zod field with a transformation while preserving its metadata.
|
|
423
929
|
|
|
424
|
-
This is useful when you want to add validations or transformations to a field but keep the original metadata (like `translationKey`) intact.
|
|
425
|
-
|
|
426
930
|
```typescript
|
|
427
931
|
import { extendWithMeta } from "@zod-utils/core";
|
|
428
932
|
import { z } from "zod";
|
|
429
933
|
|
|
430
934
|
// Base field with metadata
|
|
431
|
-
const baseField = z.string().meta({ translationKey:
|
|
935
|
+
const baseField = z.string().meta({ translationKey: "user.field.name" });
|
|
432
936
|
|
|
433
937
|
// Extend with validation while keeping metadata
|
|
434
938
|
const extendedField = extendWithMeta(baseField, (f) => f.min(3).max(100));
|
|
435
939
|
|
|
436
940
|
extendedField.meta(); // { translationKey: 'user.field.name' }
|
|
437
|
-
|
|
438
|
-
//
|
|
439
|
-
extendedField.parse('ab'); // throws - too short
|
|
440
|
-
extendedField.parse('abc'); // 'abc' - valid
|
|
941
|
+
extendedField.parse("ab"); // throws - too short
|
|
942
|
+
extendedField.parse("abc"); // 'abc' - valid
|
|
441
943
|
```
|
|
442
944
|
|
|
443
|
-
|
|
945
|
+
#### Use Case: Shared Field Definitions with i18n
|
|
444
946
|
|
|
445
947
|
```typescript
|
|
446
948
|
// Shared field definitions with i18n metadata
|
|
447
949
|
const fields = {
|
|
448
|
-
name: z.string().meta({ translationKey:
|
|
449
|
-
email: z.string().email().meta({ translationKey:
|
|
950
|
+
name: z.string().meta({ translationKey: "user.field.name" }),
|
|
951
|
+
email: z.string().email().meta({ translationKey: "user.field.email" }),
|
|
952
|
+
age: z.number().meta({ translationKey: "user.field.age" }),
|
|
450
953
|
};
|
|
451
954
|
|
|
452
955
|
// Create form uses base fields with additional constraints
|
|
453
956
|
const createFormSchema = z.object({
|
|
454
957
|
name: extendWithMeta(fields.name, (f) => f.min(3).max(50)),
|
|
455
958
|
email: extendWithMeta(fields.email, (f) => f.min(5)),
|
|
959
|
+
age: extendWithMeta(fields.age, (f) => f.min(18).max(120)),
|
|
456
960
|
});
|
|
457
961
|
|
|
458
962
|
// Edit form uses same fields with different constraints
|
|
459
963
|
const editFormSchema = z.object({
|
|
460
964
|
name: extendWithMeta(fields.name, (f) => f.optional()),
|
|
461
965
|
email: fields.email, // no extension needed
|
|
966
|
+
age: extendWithMeta(fields.age, (f) => f.optional()),
|
|
462
967
|
});
|
|
463
968
|
```
|
|
464
969
|
|
|
465
|
-
**Note:** If the original field has no metadata, the transformed field is returned as-is without calling `.meta()`.
|
|
466
|
-
|
|
467
970
|
---
|
|
468
971
|
|
|
469
|
-
### `
|
|
972
|
+
### `toFieldSelector(props)`
|
|
470
973
|
|
|
471
|
-
|
|
974
|
+
Extracts a `FieldSelector` from props containing schema, name, and optional discriminator. Encapsulates type assertion so callers don't need eslint-disable.
|
|
472
975
|
|
|
473
976
|
```typescript
|
|
474
|
-
import {
|
|
475
|
-
import { z } from "zod";
|
|
977
|
+
import { toFieldSelector } from "@zod-utils/core";
|
|
476
978
|
|
|
477
|
-
|
|
478
|
-
const formSchema = z.discriminatedUnion('mode', [
|
|
479
|
-
z.object({
|
|
480
|
-
mode: z.literal('create'),
|
|
481
|
-
name: z.string(),
|
|
482
|
-
age: z.number().default(18),
|
|
483
|
-
}),
|
|
484
|
-
z.object({
|
|
485
|
-
mode: z.literal('edit'),
|
|
486
|
-
id: z.number().default(1),
|
|
487
|
-
name: z.string().optional(),
|
|
488
|
-
bio: z.string().default('bio goes here'),
|
|
489
|
-
}),
|
|
490
|
-
]);
|
|
979
|
+
const schema = z.object({ name: z.string(), age: z.number() });
|
|
491
980
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
discriminator: {
|
|
495
|
-
key: 'mode',
|
|
496
|
-
value: 'create',
|
|
497
|
-
},
|
|
498
|
-
});
|
|
499
|
-
// Returns: { age: 18 }
|
|
981
|
+
const selector = toFieldSelector({ schema, name: "name" });
|
|
982
|
+
// { schema, name: 'name' }
|
|
500
983
|
|
|
501
|
-
//
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
},
|
|
507
|
-
});
|
|
508
|
-
// Returns: { id: 1, bio: 'bio goes here' }
|
|
984
|
+
// With discriminated union
|
|
985
|
+
const unionSchema = z.discriminatedUnion("mode", [
|
|
986
|
+
z.object({ mode: z.literal("create"), title: z.string() }),
|
|
987
|
+
z.object({ mode: z.literal("edit"), id: z.number() }),
|
|
988
|
+
]);
|
|
509
989
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
990
|
+
const unionSelector = toFieldSelector({
|
|
991
|
+
schema: unionSchema,
|
|
992
|
+
name: "title",
|
|
993
|
+
discriminator: { key: "mode", value: "create" },
|
|
994
|
+
});
|
|
995
|
+
// { schema: unionSchema, name: 'title', discriminator: { key: 'mode', value: 'create' } }
|
|
513
996
|
```
|
|
514
997
|
|
|
515
|
-
**Discriminator types:** The discriminator `value` can be a string, number, or boolean literal that matches the discriminator field type.
|
|
516
|
-
|
|
517
998
|
---
|
|
518
999
|
|
|
519
1000
|
## Type Utilities
|
|
@@ -532,40 +1013,67 @@ type Simple = Simplify<Complex>;
|
|
|
532
1013
|
|
|
533
1014
|
---
|
|
534
1015
|
|
|
535
|
-
### `
|
|
1016
|
+
### `Paths<T, FilterType?, Strict?>`
|
|
536
1017
|
|
|
537
|
-
|
|
1018
|
+
Generate dot-notation paths from any type. Low-level utility for building path strings.
|
|
1019
|
+
|
|
1020
|
+
#### Basic Usage
|
|
538
1021
|
|
|
539
1022
|
```typescript
|
|
540
|
-
import type {
|
|
541
|
-
import { z } from "zod";
|
|
1023
|
+
import type { Paths } from "@zod-utils/core";
|
|
542
1024
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
1025
|
+
type User = {
|
|
1026
|
+
name: string;
|
|
1027
|
+
age: number;
|
|
1028
|
+
profile: {
|
|
1029
|
+
bio: string;
|
|
1030
|
+
avatar: string;
|
|
1031
|
+
};
|
|
1032
|
+
tags: string[];
|
|
1033
|
+
};
|
|
547
1034
|
|
|
548
|
-
|
|
549
|
-
|
|
1035
|
+
// All paths
|
|
1036
|
+
type AllPaths = Paths<User>;
|
|
1037
|
+
// "name" | "age" | "profile" | "profile.bio" | "profile.avatar" | "tags" | "tags.0"
|
|
1038
|
+
```
|
|
550
1039
|
|
|
551
|
-
|
|
552
|
-
|
|
1040
|
+
#### Filter by Type
|
|
1041
|
+
|
|
1042
|
+
```typescript
|
|
1043
|
+
// Only string paths
|
|
1044
|
+
type StringPaths = Paths<User, string>;
|
|
1045
|
+
// "name" | "profile.bio" | "profile.avatar"
|
|
1046
|
+
|
|
1047
|
+
// Only number paths
|
|
1048
|
+
type NumberPaths = Paths<User, number>;
|
|
1049
|
+
// "age"
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
#### Strict vs Non-Strict Mode
|
|
1053
|
+
|
|
1054
|
+
```typescript
|
|
1055
|
+
type Schema = {
|
|
1056
|
+
required: string;
|
|
1057
|
+
optional?: string;
|
|
1058
|
+
nullable: string | null;
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1061
|
+
// Strict mode (default) - exact type matching
|
|
1062
|
+
type StrictPaths = Paths<Schema, string>;
|
|
1063
|
+
// "required" - only exact string type
|
|
1064
|
+
|
|
1065
|
+
// Non-strict mode - includes optional/nullable
|
|
1066
|
+
type LoosePaths = Paths<Schema, string, false>;
|
|
1067
|
+
// "required" | "optional" | "nullable"
|
|
553
1068
|
```
|
|
554
1069
|
|
|
555
1070
|
---
|
|
556
1071
|
|
|
557
1072
|
### `ValidPaths<TSchema, TDiscriminatorKey?, TDiscriminatorValue?, TFilterType?, TStrict?>`
|
|
558
1073
|
|
|
559
|
-
Generates valid dot-notation paths for a schema, with optional type filtering and discriminated union support.
|
|
560
|
-
|
|
561
|
-
**Parameters:**
|
|
562
|
-
- `TSchema` - The Zod schema type
|
|
563
|
-
- `TDiscriminatorKey` - Discriminator key for discriminated unions (default: `never`)
|
|
564
|
-
- `TDiscriminatorValue` - Discriminator value to filter variant (default: `never`)
|
|
565
|
-
- `TFilterType` - Type to filter paths by (default: `unknown` = all paths)
|
|
566
|
-
- `TStrict` - Strict mode for type matching (default: `true`)
|
|
1074
|
+
Generates valid dot-notation paths for a Zod schema, with optional type filtering and discriminated union support.
|
|
567
1075
|
|
|
568
|
-
|
|
1076
|
+
#### Basic Usage
|
|
569
1077
|
|
|
570
1078
|
```typescript
|
|
571
1079
|
import type { ValidPaths } from "@zod-utils/core";
|
|
@@ -578,20 +1086,24 @@ const schema = z.object({
|
|
|
578
1086
|
active: z.boolean(),
|
|
579
1087
|
});
|
|
580
1088
|
|
|
581
|
-
//
|
|
1089
|
+
// All paths
|
|
582
1090
|
type AllPaths = ValidPaths<typeof schema>;
|
|
583
1091
|
// "name" | "age" | "email" | "active"
|
|
1092
|
+
```
|
|
584
1093
|
|
|
585
|
-
|
|
1094
|
+
#### Filter by Type
|
|
1095
|
+
|
|
1096
|
+
```typescript
|
|
1097
|
+
// Only string fields
|
|
586
1098
|
type StringPaths = ValidPaths<typeof schema, never, never, string>;
|
|
587
1099
|
// "name"
|
|
588
1100
|
|
|
589
1101
|
// Non-strict mode - includes optional string fields
|
|
590
|
-
type
|
|
1102
|
+
type LooseStringPaths = ValidPaths<typeof schema, never, never, string, false>;
|
|
591
1103
|
// "name" | "email"
|
|
592
1104
|
```
|
|
593
1105
|
|
|
594
|
-
|
|
1106
|
+
#### With Discriminated Unions
|
|
595
1107
|
|
|
596
1108
|
```typescript
|
|
597
1109
|
const formSchema = z.discriminatedUnion("mode", [
|
|
@@ -599,55 +1111,300 @@ const formSchema = z.discriminatedUnion("mode", [
|
|
|
599
1111
|
z.object({ mode: z.literal("edit"), id: z.number(), title: z.string() }),
|
|
600
1112
|
]);
|
|
601
1113
|
|
|
602
|
-
//
|
|
1114
|
+
// All paths for 'create' variant
|
|
603
1115
|
type CreatePaths = ValidPaths<typeof formSchema, "mode", "create">;
|
|
604
1116
|
// "mode" | "name" | "age"
|
|
605
1117
|
|
|
606
|
-
//
|
|
1118
|
+
// All paths for 'edit' variant
|
|
1119
|
+
type EditPaths = ValidPaths<typeof formSchema, "mode", "edit">;
|
|
1120
|
+
// "mode" | "id" | "title"
|
|
1121
|
+
|
|
1122
|
+
// Number paths for 'edit' variant
|
|
607
1123
|
type EditNumberPaths = ValidPaths<typeof formSchema, "mode", "edit", number>;
|
|
608
1124
|
// "id"
|
|
1125
|
+
|
|
1126
|
+
// String paths for 'create' variant (non-strict to include optional)
|
|
1127
|
+
type CreateStringPaths = ValidPaths<typeof formSchema, "mode", "create", string, false>;
|
|
1128
|
+
// "name"
|
|
1129
|
+
```
|
|
1130
|
+
|
|
1131
|
+
#### Nested Objects
|
|
1132
|
+
|
|
1133
|
+
```typescript
|
|
1134
|
+
const schema = z.object({
|
|
1135
|
+
user: z.object({
|
|
1136
|
+
profile: z.object({
|
|
1137
|
+
firstName: z.string(),
|
|
1138
|
+
lastName: z.string(),
|
|
1139
|
+
age: z.number(),
|
|
1140
|
+
}),
|
|
1141
|
+
settings: z.object({
|
|
1142
|
+
theme: z.string(),
|
|
1143
|
+
notifications: z.boolean(),
|
|
1144
|
+
}),
|
|
1145
|
+
}),
|
|
1146
|
+
metadata: z.object({
|
|
1147
|
+
createdAt: z.date(),
|
|
1148
|
+
updatedAt: z.date(),
|
|
1149
|
+
}),
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
// All paths including nested
|
|
1153
|
+
type AllPaths = ValidPaths<typeof schema>;
|
|
1154
|
+
// "user" | "user.profile" | "user.profile.firstName" | "user.profile.lastName" |
|
|
1155
|
+
// "user.profile.age" | "user.settings" | "user.settings.theme" |
|
|
1156
|
+
// "user.settings.notifications" | "metadata" | "metadata.createdAt" | "metadata.updatedAt"
|
|
1157
|
+
|
|
1158
|
+
// Only string paths (deeply nested)
|
|
1159
|
+
type StringPaths = ValidPaths<typeof schema, never, never, string>;
|
|
1160
|
+
// "user.profile.firstName" | "user.profile.lastName" | "user.settings.theme"
|
|
1161
|
+
|
|
1162
|
+
// Only boolean paths
|
|
1163
|
+
type BooleanPaths = ValidPaths<typeof schema, never, never, boolean>;
|
|
1164
|
+
// "user.settings.notifications"
|
|
1165
|
+
|
|
1166
|
+
// Only Date paths
|
|
1167
|
+
type DatePaths = ValidPaths<typeof schema, never, never, Date>;
|
|
1168
|
+
// "metadata.createdAt" | "metadata.updatedAt"
|
|
1169
|
+
```
|
|
1170
|
+
|
|
1171
|
+
#### Arrays and Array Elements
|
|
1172
|
+
|
|
1173
|
+
```typescript
|
|
1174
|
+
const schema = z.object({
|
|
1175
|
+
tags: z.array(z.string()),
|
|
1176
|
+
scores: z.array(z.number()),
|
|
1177
|
+
users: z.array(
|
|
1178
|
+
z.object({
|
|
1179
|
+
id: z.number(),
|
|
1180
|
+
name: z.string(),
|
|
1181
|
+
email: z.string().optional(),
|
|
1182
|
+
})
|
|
1183
|
+
),
|
|
1184
|
+
matrix: z.array(z.array(z.number())),
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
// All paths - arrays use numeric index syntax
|
|
1188
|
+
type AllPaths = ValidPaths<typeof schema>;
|
|
1189
|
+
// "tags" | "tags.0" | "scores" | "scores.0" |
|
|
1190
|
+
// "users" | "users.0" | "users.0.id" | "users.0.name" | "users.0.email" |
|
|
1191
|
+
// "matrix" | "matrix.0" | "matrix.0.0"
|
|
1192
|
+
|
|
1193
|
+
// Array element paths only (filter by element type)
|
|
1194
|
+
type StringArrayElements = ValidPaths<typeof schema, never, never, string>;
|
|
1195
|
+
// "tags.0" | "users.0.name"
|
|
1196
|
+
|
|
1197
|
+
// Number array elements
|
|
1198
|
+
type NumberArrayElements = ValidPaths<typeof schema, never, never, number>;
|
|
1199
|
+
// "scores.0" | "users.0.id" | "matrix.0.0"
|
|
1200
|
+
|
|
1201
|
+
// The "0" is a placeholder - works with any numeric index at runtime
|
|
1202
|
+
// These are all valid at runtime: "users.0.name", "users.1.name", "users.99.name"
|
|
1203
|
+
```
|
|
1204
|
+
|
|
1205
|
+
#### Optional Nested Objects and Arrays
|
|
1206
|
+
|
|
1207
|
+
```typescript
|
|
1208
|
+
const schema = z.object({
|
|
1209
|
+
// Required nested object
|
|
1210
|
+
profile: z.object({
|
|
1211
|
+
name: z.string(),
|
|
1212
|
+
age: z.number(),
|
|
1213
|
+
}),
|
|
1214
|
+
// Optional nested object
|
|
1215
|
+
settings: z
|
|
1216
|
+
.object({
|
|
1217
|
+
theme: z.string(),
|
|
1218
|
+
language: z.string(),
|
|
1219
|
+
})
|
|
1220
|
+
.optional(),
|
|
1221
|
+
// Nullable nested object
|
|
1222
|
+
metadata: z
|
|
1223
|
+
.object({
|
|
1224
|
+
source: z.string(),
|
|
1225
|
+
version: z.number(),
|
|
1226
|
+
})
|
|
1227
|
+
.nullable(),
|
|
1228
|
+
// Required array
|
|
1229
|
+
tags: z.array(z.string()),
|
|
1230
|
+
// Optional array
|
|
1231
|
+
scores: z.array(z.number()).optional(),
|
|
1232
|
+
// Nullable array of objects
|
|
1233
|
+
comments: z
|
|
1234
|
+
.array(
|
|
1235
|
+
z.object({
|
|
1236
|
+
author: z.string(),
|
|
1237
|
+
text: z.string(),
|
|
1238
|
+
})
|
|
1239
|
+
)
|
|
1240
|
+
.nullable(),
|
|
1241
|
+
});
|
|
1242
|
+
|
|
1243
|
+
// All paths (no filter) - includes everything
|
|
1244
|
+
type AllPaths = ValidPaths<typeof schema>;
|
|
1245
|
+
// "profile" | "profile.name" | "profile.age" |
|
|
1246
|
+
// "settings" | "settings.theme" | "settings.language" |
|
|
1247
|
+
// "metadata" | "metadata.source" | "metadata.version" |
|
|
1248
|
+
// "tags" | "tags.0" |
|
|
1249
|
+
// "scores" | "scores.0" |
|
|
1250
|
+
// "comments" | "comments.0" | "comments.0.author" | "comments.0.text"
|
|
1251
|
+
|
|
1252
|
+
// Strict mode - paths through optional/nullable parents are BLOCKED
|
|
1253
|
+
type StrictStringPaths = ValidPaths<typeof schema, never, never, string>;
|
|
1254
|
+
// "profile.name" | "tags.0"
|
|
1255
|
+
// Note: settings.theme, metadata.source, comments.0.author are excluded
|
|
1256
|
+
// because their parent objects are optional/nullable
|
|
1257
|
+
|
|
1258
|
+
// Non-strict mode - paths through optional/nullable parents are ALLOWED
|
|
1259
|
+
type LooseStringPaths = ValidPaths<typeof schema, never, never, string, false>;
|
|
1260
|
+
// "profile.name" | "settings.theme" | "settings.language" |
|
|
1261
|
+
// "metadata.source" | "tags.0" | "comments.0.author" | "comments.0.text"
|
|
1262
|
+
|
|
1263
|
+
// Strict number paths
|
|
1264
|
+
type StrictNumberPaths = ValidPaths<typeof schema, never, never, number>;
|
|
1265
|
+
// "profile.age"
|
|
1266
|
+
// Note: metadata.version and scores.0 are excluded (optional/nullable parents)
|
|
1267
|
+
|
|
1268
|
+
// Non-strict number paths
|
|
1269
|
+
type LooseNumberPaths = ValidPaths<typeof schema, never, never, number, false>;
|
|
1270
|
+
// "profile.age" | "metadata.version" | "scores.0"
|
|
1271
|
+
```
|
|
1272
|
+
|
|
1273
|
+
#### When to Use Strict vs Non-Strict Mode
|
|
1274
|
+
|
|
1275
|
+
```typescript
|
|
1276
|
+
// Strict mode (default): Use when you need GUARANTEED non-null values
|
|
1277
|
+
// - Form fields that must always exist
|
|
1278
|
+
// - Required input components
|
|
1279
|
+
// - Direct property access without null checks
|
|
1280
|
+
|
|
1281
|
+
// Non-strict mode: Use when you handle optional/nullable at runtime
|
|
1282
|
+
// - Conditional form fields
|
|
1283
|
+
// - Fields that may or may not be rendered
|
|
1284
|
+
// - When you already check for existence before accessing
|
|
1285
|
+
```
|
|
1286
|
+
|
|
1287
|
+
#### Practical Example: Type-Safe Form Field Component
|
|
1288
|
+
|
|
1289
|
+
```typescript
|
|
1290
|
+
const userFormSchema = z.object({
|
|
1291
|
+
personal: z.object({
|
|
1292
|
+
firstName: z.string().min(1),
|
|
1293
|
+
lastName: z.string().min(1),
|
|
1294
|
+
age: z.number().min(18),
|
|
1295
|
+
}),
|
|
1296
|
+
contact: z.object({
|
|
1297
|
+
email: z.string().email(),
|
|
1298
|
+
phone: z.string().optional(),
|
|
1299
|
+
}),
|
|
1300
|
+
addresses: z.array(
|
|
1301
|
+
z.object({
|
|
1302
|
+
street: z.string(),
|
|
1303
|
+
city: z.string(),
|
|
1304
|
+
zipCode: z.string(),
|
|
1305
|
+
})
|
|
1306
|
+
),
|
|
1307
|
+
});
|
|
1308
|
+
|
|
1309
|
+
// Type-safe field component that only accepts valid paths
|
|
1310
|
+
function TextField<TName extends ValidPaths<typeof userFormSchema, never, never, string>>({
|
|
1311
|
+
name,
|
|
1312
|
+
}: {
|
|
1313
|
+
name: TName;
|
|
1314
|
+
}) {
|
|
1315
|
+
// Implementation
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// Usage - TypeScript provides autocomplete for all string paths:
|
|
1319
|
+
<TextField name="personal.firstName" /> // ✅ Valid
|
|
1320
|
+
<TextField name="personal.lastName" /> // ✅ Valid
|
|
1321
|
+
<TextField name="contact.email" /> // ✅ Valid
|
|
1322
|
+
<TextField name="addresses.0.street" /> // ✅ Valid
|
|
1323
|
+
<TextField name="addresses.0.city" /> // ✅ Valid
|
|
1324
|
+
|
|
1325
|
+
// TypeScript errors:
|
|
1326
|
+
<TextField name="personal.age" /> // ❌ Error: age is number, not string
|
|
1327
|
+
<TextField name="invalid.path" /> // ❌ Error: path doesn't exist
|
|
1328
|
+
|
|
1329
|
+
// Number field component
|
|
1330
|
+
function NumberField<TName extends ValidPaths<typeof userFormSchema, never, never, number>>({
|
|
1331
|
+
name,
|
|
1332
|
+
}: {
|
|
1333
|
+
name: TName;
|
|
1334
|
+
}) {
|
|
1335
|
+
// Implementation
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
<NumberField name="personal.age" /> // ✅ Valid - age is number
|
|
1339
|
+
<NumberField name="personal.firstName" /> // ❌ Error: firstName is string
|
|
609
1340
|
```
|
|
610
1341
|
|
|
611
|
-
|
|
1342
|
+
#### Optional and Nullable Fields with Strict Mode
|
|
612
1343
|
|
|
613
1344
|
```typescript
|
|
614
1345
|
const schema = z.object({
|
|
615
1346
|
required: z.string(),
|
|
616
1347
|
optional: z.string().optional(),
|
|
617
1348
|
nullable: z.string().nullable(),
|
|
1349
|
+
optionalNullable: z.string().optional().nullable(),
|
|
1350
|
+
withDefault: z.string().default("hello"),
|
|
618
1351
|
});
|
|
619
1352
|
|
|
620
|
-
// Strict mode (default) - exact type
|
|
621
|
-
type
|
|
622
|
-
// "required"
|
|
1353
|
+
// Strict mode (default) - only exact type matches
|
|
1354
|
+
type StrictStringPaths = ValidPaths<typeof schema, never, never, string>;
|
|
1355
|
+
// "required" | "withDefault"
|
|
1356
|
+
// Note: optional/nullable are excluded because their type is string | undefined/null
|
|
623
1357
|
|
|
624
|
-
// Non-strict mode - includes
|
|
625
|
-
type
|
|
626
|
-
// "required" | "optional" | "nullable"
|
|
1358
|
+
// Non-strict mode - includes optional and nullable fields
|
|
1359
|
+
type LooseStringPaths = ValidPaths<typeof schema, never, never, string, false>;
|
|
1360
|
+
// "required" | "optional" | "nullable" | "optionalNullable" | "withDefault"
|
|
627
1361
|
```
|
|
628
1362
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
### `Paths<T, FilterType?, Strict?>`
|
|
632
|
-
|
|
633
|
-
Low-level type utility for generating dot-notation paths from any type (not schema-specific).
|
|
1363
|
+
#### Complex Discriminated Union with Nested Arrays
|
|
634
1364
|
|
|
635
1365
|
```typescript
|
|
636
|
-
|
|
1366
|
+
const orderSchema = z.discriminatedUnion("type", [
|
|
1367
|
+
z.object({
|
|
1368
|
+
type: z.literal("physical"),
|
|
1369
|
+
items: z.array(
|
|
1370
|
+
z.object({
|
|
1371
|
+
productId: z.number(),
|
|
1372
|
+
quantity: z.number(),
|
|
1373
|
+
price: z.number(),
|
|
1374
|
+
})
|
|
1375
|
+
),
|
|
1376
|
+
shippingAddress: z.object({
|
|
1377
|
+
street: z.string(),
|
|
1378
|
+
city: z.string(),
|
|
1379
|
+
country: z.string(),
|
|
1380
|
+
}),
|
|
1381
|
+
}),
|
|
1382
|
+
z.object({
|
|
1383
|
+
type: z.literal("digital"),
|
|
1384
|
+
items: z.array(
|
|
1385
|
+
z.object({
|
|
1386
|
+
productId: z.number(),
|
|
1387
|
+
licenseKey: z.string(),
|
|
1388
|
+
downloadUrl: z.string(),
|
|
1389
|
+
})
|
|
1390
|
+
),
|
|
1391
|
+
email: z.string().email(),
|
|
1392
|
+
}),
|
|
1393
|
+
]);
|
|
637
1394
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
1395
|
+
// Physical order paths
|
|
1396
|
+
type PhysicalPaths = ValidPaths<typeof orderSchema, "type", "physical">;
|
|
1397
|
+
// "type" | "items" | "items.0" | "items.0.productId" | "items.0.quantity" |
|
|
1398
|
+
// "items.0.price" | "shippingAddress" | "shippingAddress.street" |
|
|
1399
|
+
// "shippingAddress.city" | "shippingAddress.country"
|
|
643
1400
|
|
|
644
|
-
//
|
|
645
|
-
type
|
|
646
|
-
// "
|
|
1401
|
+
// Digital order string paths
|
|
1402
|
+
type DigitalStringPaths = ValidPaths<typeof orderSchema, "type", "digital", string>;
|
|
1403
|
+
// "items.0.licenseKey" | "items.0.downloadUrl" | "email"
|
|
647
1404
|
|
|
648
|
-
//
|
|
649
|
-
type
|
|
650
|
-
// "
|
|
1405
|
+
// Physical order number paths (for numeric inputs)
|
|
1406
|
+
type PhysicalNumberPaths = ValidPaths<typeof orderSchema, "type", "physical", number>;
|
|
1407
|
+
// "items.0.productId" | "items.0.quantity" | "items.0.price"
|
|
651
1408
|
```
|
|
652
1409
|
|
|
653
1410
|
---
|
|
@@ -676,76 +1433,60 @@ type FormParams = FieldSelector<typeof formSchema, "name", "mode", "create">;
|
|
|
676
1433
|
|
|
677
1434
|
---
|
|
678
1435
|
|
|
1436
|
+
### `DiscriminatedInput<TSchema, TDiscriminatorKey, TDiscriminatorValue>`
|
|
1437
|
+
|
|
1438
|
+
Extracts the input type from a discriminated union variant.
|
|
1439
|
+
|
|
1440
|
+
```typescript
|
|
1441
|
+
import type { DiscriminatedInput } from "@zod-utils/core";
|
|
1442
|
+
import { z } from "zod";
|
|
1443
|
+
|
|
1444
|
+
const schema = z.discriminatedUnion("mode", [
|
|
1445
|
+
z.object({ mode: z.literal("create"), name: z.string() }),
|
|
1446
|
+
z.object({ mode: z.literal("edit"), id: z.number() }),
|
|
1447
|
+
]);
|
|
1448
|
+
|
|
1449
|
+
type CreateInput = DiscriminatedInput<typeof schema, "mode", "create">;
|
|
1450
|
+
// { mode: 'create'; name: string }
|
|
1451
|
+
|
|
1452
|
+
type EditInput = DiscriminatedInput<typeof schema, "mode", "edit">;
|
|
1453
|
+
// { mode: 'edit'; id: number }
|
|
1454
|
+
```
|
|
1455
|
+
|
|
1456
|
+
---
|
|
1457
|
+
|
|
679
1458
|
## Migration Guide
|
|
680
1459
|
|
|
681
1460
|
### Migrating to v3.0.0
|
|
682
1461
|
|
|
683
1462
|
#### `ValidPathsOfType` removed → Use `ValidPaths` with type filtering
|
|
684
1463
|
|
|
685
|
-
The `ValidPathsOfType` type has been removed and consolidated into `ValidPaths` with a new `TFilterType` parameter.
|
|
686
|
-
|
|
687
|
-
**Before (v2.x):**
|
|
688
1464
|
```typescript
|
|
1465
|
+
// Before (v2.x)
|
|
689
1466
|
import type { ValidPathsOfType } from "@zod-utils/core";
|
|
690
|
-
|
|
691
|
-
// Get string field paths
|
|
692
1467
|
type StringPaths = ValidPathsOfType<typeof schema, string>;
|
|
693
|
-
|
|
694
|
-
// With discriminated union
|
|
695
1468
|
type EditNumberPaths = ValidPathsOfType<typeof schema, number, "mode", "edit">;
|
|
696
|
-
```
|
|
697
1469
|
|
|
698
|
-
|
|
699
|
-
```typescript
|
|
1470
|
+
// After (v3.x)
|
|
700
1471
|
import type { ValidPaths } from "@zod-utils/core";
|
|
701
|
-
|
|
702
|
-
// Get string field paths - use 4th type parameter
|
|
703
1472
|
type StringPaths = ValidPaths<typeof schema, never, never, string>;
|
|
704
|
-
|
|
705
|
-
// With discriminated union - TFilterType is now 4th parameter
|
|
706
1473
|
type EditNumberPaths = ValidPaths<typeof schema, "mode", "edit", number>;
|
|
707
1474
|
```
|
|
708
1475
|
|
|
709
|
-
#### `Paths<T>` signature changed
|
|
710
|
-
|
|
711
|
-
The `Paths` type now accepts optional `FilterType` and `Strict` parameters.
|
|
712
|
-
|
|
713
|
-
**Before (v2.x):**
|
|
714
|
-
```typescript
|
|
715
|
-
type AllPaths = Paths<User>; // Still works the same
|
|
716
|
-
```
|
|
717
|
-
|
|
718
|
-
**After (v3.x):**
|
|
719
|
-
```typescript
|
|
720
|
-
type AllPaths = Paths<User>; // Works the same (backward compatible)
|
|
721
|
-
|
|
722
|
-
// New: Filter by type
|
|
723
|
-
type StringPaths = Paths<User, string>;
|
|
724
|
-
|
|
725
|
-
// New: Non-strict mode
|
|
726
|
-
type StringPaths = Paths<User, string, false>;
|
|
727
|
-
```
|
|
728
|
-
|
|
729
1476
|
### Migrating to v4.0.0
|
|
730
1477
|
|
|
731
1478
|
#### `mergeFieldSelectorProps` renamed → Use `toFieldSelector`
|
|
732
1479
|
|
|
733
|
-
The function has been renamed and simplified to accept a single props object.
|
|
734
|
-
|
|
735
|
-
**Before (v3.x):**
|
|
736
1480
|
```typescript
|
|
1481
|
+
// Before (v3.x)
|
|
737
1482
|
import { mergeFieldSelectorProps } from "@zod-utils/core";
|
|
738
|
-
|
|
739
1483
|
const selectorProps = mergeFieldSelectorProps(
|
|
740
1484
|
{ schema },
|
|
741
1485
|
{ name, discriminator }
|
|
742
1486
|
);
|
|
743
|
-
```
|
|
744
1487
|
|
|
745
|
-
|
|
746
|
-
```typescript
|
|
1488
|
+
// After (v4.x)
|
|
747
1489
|
import { toFieldSelector } from "@zod-utils/core";
|
|
748
|
-
|
|
749
1490
|
const selectorProps = toFieldSelector({ schema, name, discriminator });
|
|
750
1491
|
```
|
|
751
1492
|
|