@zod-utils/core 3.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 +1092 -315
- package/dist/index.d.mts +20 -20
- package/dist/index.d.ts +20 -20
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4 -3
- package/dist/index.mjs.map +1 -1
- 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 }
|
|
89
261
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
//
|
|
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' }
|
|
267
|
+
|
|
268
|
+
// Without discriminator, returns empty object
|
|
269
|
+
getSchemaDefaults(formSchema);
|
|
270
|
+
// {}
|
|
95
271
|
```
|
|
96
272
|
|
|
97
|
-
|
|
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 }
|
|
286
|
+
```
|
|
287
|
+
|
|
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
|
|
362
|
+
|
|
363
|
+
// Optional arrays don't require input
|
|
364
|
+
requiresValidInput(z.array(z.string()).optional()); // false
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
#### Object Fields
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
// Objects require some structure
|
|
371
|
+
requiresValidInput(z.object({ name: z.string() })); // true
|
|
372
|
+
|
|
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
|
|
383
|
+
|
|
384
|
+
// Any and unknown accept everything
|
|
385
|
+
requiresValidInput(z.any()); // false
|
|
386
|
+
requiresValidInput(z.unknown()); // false
|
|
387
|
+
|
|
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
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
### `extractDefaultValue(field)`
|
|
428
|
+
|
|
429
|
+
Extract the default value from a Zod field. Recursively unwraps optional/nullable/union/transform layers.
|
|
430
|
+
|
|
431
|
+
#### Basic Usage
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
import { extractDefaultValue } from "@zod-utils/core";
|
|
435
|
+
import { z } from "zod";
|
|
436
|
+
|
|
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
|
|
116
465
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
466
|
+
```typescript
|
|
467
|
+
// Extracts input default, not output
|
|
468
|
+
const schema = z
|
|
469
|
+
.string()
|
|
470
|
+
.default("hello")
|
|
471
|
+
.transform((val) => val.toUpperCase());
|
|
120
472
|
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
requiresValidInput(age); // true - numbers reject empty strings
|
|
473
|
+
extractDefaultValue(schema); // 'hello' (not 'HELLO')
|
|
474
|
+
```
|
|
124
475
|
|
|
125
|
-
|
|
126
|
-
const bio = z.string().optional();
|
|
127
|
-
requiresValidInput(bio); // false - user can leave empty
|
|
476
|
+
#### With Unions
|
|
128
477
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
|
132
482
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
requiresValidInput(middleName); // false - user can leave null
|
|
483
|
+
extractDefaultValue(z.union([z.string().default("hello"), z.null()]));
|
|
484
|
+
// 'hello' - union with nullish types extracts from first option
|
|
136
485
|
```
|
|
137
486
|
|
|
138
487
|
---
|
|
139
488
|
|
|
140
489
|
### `getPrimitiveType(field)`
|
|
141
490
|
|
|
142
|
-
Get the primitive type of a Zod field by unwrapping optional/nullable/transform wrappers.
|
|
143
|
-
Stops at arrays without unwrapping them.
|
|
491
|
+
Get the primitive type of a Zod field by unwrapping optional/nullable/default/transform wrappers. Stops at arrays without unwrapping them.
|
|
144
492
|
|
|
145
|
-
|
|
493
|
+
#### Basic Usage
|
|
146
494
|
|
|
147
495
|
```typescript
|
|
148
496
|
import { getPrimitiveType } from "@zod-utils/core";
|
|
149
497
|
import { z } from "zod";
|
|
150
498
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
//
|
|
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
|
+
```
|
|
507
|
+
|
|
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
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
#### With Transforms
|
|
517
|
+
|
|
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
|
|
154
528
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
|
158
533
|
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// Returns the underlying ZodString (unwraps the transform)
|
|
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
|
|
163
537
|
```
|
|
164
538
|
|
|
165
539
|
---
|
|
166
540
|
|
|
167
541
|
### `removeDefault(field)`
|
|
168
542
|
|
|
169
|
-
Remove default values from a Zod field.
|
|
543
|
+
Remove default values from a Zod field while preserving optional/nullable wrappers.
|
|
170
544
|
|
|
171
545
|
```typescript
|
|
172
546
|
import { removeDefault } from "@zod-utils/core";
|
|
173
547
|
import { z } from "zod";
|
|
174
548
|
|
|
549
|
+
// Remove default
|
|
175
550
|
const withDefault = z.string().default("hello");
|
|
176
551
|
const withoutDefault = removeDefault(withDefault);
|
|
177
552
|
|
|
178
553
|
withDefault.parse(undefined); // 'hello'
|
|
179
|
-
withoutDefault.parse(undefined); // throws
|
|
554
|
+
withoutDefault.parse(undefined); // throws ZodError
|
|
555
|
+
|
|
556
|
+
// Preserves optional/nullable
|
|
557
|
+
const complex = z.string().default("test").optional().nullable();
|
|
558
|
+
const removed = removeDefault(complex);
|
|
559
|
+
|
|
560
|
+
removed.parse(undefined); // undefined (optional still works)
|
|
561
|
+
removed.parse(null); // null (nullable still works)
|
|
562
|
+
removed.parse("hello"); // 'hello'
|
|
563
|
+
|
|
564
|
+
// Returns same schema if no default
|
|
565
|
+
const plain = z.string();
|
|
566
|
+
removeDefault(plain) === plain; // true
|
|
180
567
|
```
|
|
181
568
|
|
|
182
569
|
---
|
|
183
570
|
|
|
184
|
-
### `
|
|
185
|
-
|
|
186
|
-
Extract the default value from a Zod field (recursively unwraps optional/nullable/union/transform layers).
|
|
571
|
+
### `getFieldChecks(field)`
|
|
187
572
|
|
|
188
|
-
|
|
573
|
+
Extract all validation check definitions from a Zod schema field. Returns Zod's raw check definition objects.
|
|
189
574
|
|
|
190
|
-
|
|
575
|
+
#### String Validations
|
|
191
576
|
|
|
192
577
|
```typescript
|
|
193
|
-
import {
|
|
578
|
+
import { getFieldChecks } from "@zod-utils/core";
|
|
194
579
|
import { z } from "zod";
|
|
195
580
|
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
581
|
+
// Length constraints
|
|
582
|
+
getFieldChecks(z.string().min(3));
|
|
583
|
+
// [{ check: 'min_length', minimum: 3, ... }]
|
|
199
584
|
|
|
200
|
-
|
|
201
|
-
|
|
585
|
+
getFieldChecks(z.string().max(100));
|
|
586
|
+
// [{ check: 'max_length', maximum: 100, ... }]
|
|
202
587
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
588
|
+
getFieldChecks(z.string().min(3).max(20));
|
|
589
|
+
// [
|
|
590
|
+
// { check: 'min_length', minimum: 3, ... },
|
|
591
|
+
// { check: 'max_length', maximum: 20, ... }
|
|
592
|
+
// ]
|
|
206
593
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
extractDefaultValue(unionField2); // undefined
|
|
594
|
+
getFieldChecks(z.string().length(10));
|
|
595
|
+
// [{ check: 'length_equals', length: 10, ... }]
|
|
210
596
|
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
597
|
+
// Format validations
|
|
598
|
+
getFieldChecks(z.string().email());
|
|
599
|
+
// [{ check: 'string_format', format: 'email', ... }]
|
|
214
600
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
601
|
+
getFieldChecks(z.string().url());
|
|
602
|
+
// [{ check: 'string_format', format: 'url', ... }]
|
|
603
|
+
|
|
604
|
+
getFieldChecks(z.string().uuid());
|
|
605
|
+
// [{ check: 'string_format', format: 'uuid', ... }]
|
|
218
606
|
```
|
|
219
607
|
|
|
220
|
-
|
|
608
|
+
#### Number Validations
|
|
221
609
|
|
|
222
|
-
|
|
610
|
+
```typescript
|
|
611
|
+
getFieldChecks(z.number().min(18));
|
|
612
|
+
// [{ check: 'greater_than', value: 18, inclusive: true, ... }]
|
|
223
613
|
|
|
224
|
-
|
|
614
|
+
getFieldChecks(z.number().max(120));
|
|
615
|
+
// [{ check: 'less_than', value: 120, inclusive: true, ... }]
|
|
225
616
|
|
|
226
|
-
|
|
617
|
+
getFieldChecks(z.number().min(0).max(100));
|
|
618
|
+
// [
|
|
619
|
+
// { check: 'greater_than', value: 0, inclusive: true, ... },
|
|
620
|
+
// { check: 'less_than', value: 100, inclusive: true, ... }
|
|
621
|
+
// ]
|
|
227
622
|
|
|
228
|
-
|
|
229
|
-
|
|
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`
|
|
623
|
+
getFieldChecks(z.number().gt(0)); // exclusive >
|
|
624
|
+
// [{ check: 'greater_than', value: 0, inclusive: false, ... }]
|
|
235
625
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
626
|
+
getFieldChecks(z.number().lt(100)); // exclusive <
|
|
627
|
+
// [{ check: 'less_than', value: 100, inclusive: false, ... }]
|
|
628
|
+
```
|
|
239
629
|
|
|
240
|
-
|
|
241
|
-
const username = z.string().min(3).max(20);
|
|
242
|
-
const checks = getFieldChecks(username);
|
|
243
|
-
// [
|
|
244
|
-
// { check: 'min_length', minimum: 3, when: [Function], ... },
|
|
245
|
-
// { check: 'max_length', maximum: 20, when: [Function], ... }
|
|
246
|
-
// ]
|
|
630
|
+
#### Array Validations
|
|
247
631
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
// [
|
|
252
|
-
// { check: 'greater_than', value: 18, inclusive: true, ... },
|
|
253
|
-
// { check: 'less_than', value: 120, inclusive: true, ... }
|
|
254
|
-
// ]
|
|
632
|
+
```typescript
|
|
633
|
+
getFieldChecks(z.array(z.string()).min(1));
|
|
634
|
+
// [{ check: 'min_length', minimum: 1, ... }]
|
|
255
635
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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");
|
|
268
651
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
652
|
+
getFieldChecks(z.date().min(minDate));
|
|
653
|
+
// [{ check: 'greater_than', value: Date, inclusive: true, ... }]
|
|
654
|
+
|
|
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
|
|
666
|
+
|
|
667
|
+
```typescript
|
|
668
|
+
// Automatically unwraps optional/nullable/default
|
|
669
|
+
getFieldChecks(z.string().min(3).max(20).optional());
|
|
670
|
+
// [{ check: 'min_length', ... }, { check: 'max_length', ... }]
|
|
276
671
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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', ... }]
|
|
280
682
|
```
|
|
281
683
|
|
|
282
|
-
|
|
684
|
+
#### No Constraints
|
|
283
685
|
|
|
284
686
|
```typescript
|
|
285
|
-
|
|
687
|
+
// Returns empty array when no constraints
|
|
688
|
+
getFieldChecks(z.string()); // []
|
|
689
|
+
getFieldChecks(z.number()); // []
|
|
690
|
+
getFieldChecks(z.boolean()); // []
|
|
691
|
+
getFieldChecks(z.date()); // []
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
#### Using Checks in UI
|
|
695
|
+
|
|
696
|
+
```typescript
|
|
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,195 +759,242 @@ 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
|
+
}),
|
|
353
847
|
});
|
|
354
|
-
// Returns: ZodString
|
|
355
848
|
|
|
356
|
-
|
|
357
|
-
|
|
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
|
+
),
|
|
865
|
+
});
|
|
866
|
+
|
|
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
|
|
901
|
+
|
|
902
|
+
// Without discriminator, returns undefined
|
|
903
|
+
extractFieldFromSchema({
|
|
904
|
+
schema: formSchema,
|
|
905
|
+
name: "name",
|
|
906
|
+
}); // undefined
|
|
907
|
+
```
|
|
387
908
|
|
|
388
|
-
|
|
389
|
-
|
|
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)
|
|
919
|
+
// Extracts from input type (before transform)
|
|
920
|
+
extractFieldFromSchema({ schema, name: "name" }); // ZodString
|
|
921
|
+
extractFieldFromSchema({ schema, name: "age" }); // ZodNumber
|
|
401
922
|
```
|
|
402
923
|
|
|
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
924
|
---
|
|
406
925
|
|
|
407
926
|
### `extendWithMeta(field, transform)`
|
|
408
927
|
|
|
409
928
|
Extends a Zod field with a transformation while preserving its metadata.
|
|
410
929
|
|
|
411
|
-
This is useful when you want to add validations or transformations to a field but keep the original metadata (like `translationKey`) intact.
|
|
412
|
-
|
|
413
930
|
```typescript
|
|
414
931
|
import { extendWithMeta } from "@zod-utils/core";
|
|
415
932
|
import { z } from "zod";
|
|
416
933
|
|
|
417
934
|
// Base field with metadata
|
|
418
|
-
const baseField = z.string().meta({ translationKey:
|
|
935
|
+
const baseField = z.string().meta({ translationKey: "user.field.name" });
|
|
419
936
|
|
|
420
937
|
// Extend with validation while keeping metadata
|
|
421
938
|
const extendedField = extendWithMeta(baseField, (f) => f.min(3).max(100));
|
|
422
939
|
|
|
423
940
|
extendedField.meta(); // { translationKey: 'user.field.name' }
|
|
424
|
-
|
|
425
|
-
//
|
|
426
|
-
extendedField.parse('ab'); // throws - too short
|
|
427
|
-
extendedField.parse('abc'); // 'abc' - valid
|
|
941
|
+
extendedField.parse("ab"); // throws - too short
|
|
942
|
+
extendedField.parse("abc"); // 'abc' - valid
|
|
428
943
|
```
|
|
429
944
|
|
|
430
|
-
|
|
945
|
+
#### Use Case: Shared Field Definitions with i18n
|
|
431
946
|
|
|
432
947
|
```typescript
|
|
433
948
|
// Shared field definitions with i18n metadata
|
|
434
949
|
const fields = {
|
|
435
|
-
name: z.string().meta({ translationKey:
|
|
436
|
-
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" }),
|
|
437
953
|
};
|
|
438
954
|
|
|
439
955
|
// Create form uses base fields with additional constraints
|
|
440
956
|
const createFormSchema = z.object({
|
|
441
957
|
name: extendWithMeta(fields.name, (f) => f.min(3).max(50)),
|
|
442
958
|
email: extendWithMeta(fields.email, (f) => f.min(5)),
|
|
959
|
+
age: extendWithMeta(fields.age, (f) => f.min(18).max(120)),
|
|
443
960
|
});
|
|
444
961
|
|
|
445
962
|
// Edit form uses same fields with different constraints
|
|
446
963
|
const editFormSchema = z.object({
|
|
447
964
|
name: extendWithMeta(fields.name, (f) => f.optional()),
|
|
448
965
|
email: fields.email, // no extension needed
|
|
966
|
+
age: extendWithMeta(fields.age, (f) => f.optional()),
|
|
449
967
|
});
|
|
450
968
|
```
|
|
451
969
|
|
|
452
|
-
**Note:** If the original field has no metadata, the transformed field is returned as-is without calling `.meta()`.
|
|
453
|
-
|
|
454
970
|
---
|
|
455
971
|
|
|
456
|
-
### `
|
|
972
|
+
### `toFieldSelector(props)`
|
|
457
973
|
|
|
458
|
-
|
|
974
|
+
Extracts a `FieldSelector` from props containing schema, name, and optional discriminator. Encapsulates type assertion so callers don't need eslint-disable.
|
|
459
975
|
|
|
460
976
|
```typescript
|
|
461
|
-
import {
|
|
462
|
-
import { z } from "zod";
|
|
977
|
+
import { toFieldSelector } from "@zod-utils/core";
|
|
463
978
|
|
|
464
|
-
|
|
465
|
-
const formSchema = z.discriminatedUnion('mode', [
|
|
466
|
-
z.object({
|
|
467
|
-
mode: z.literal('create'),
|
|
468
|
-
name: z.string(),
|
|
469
|
-
age: z.number().default(18),
|
|
470
|
-
}),
|
|
471
|
-
z.object({
|
|
472
|
-
mode: z.literal('edit'),
|
|
473
|
-
id: z.number().default(1),
|
|
474
|
-
name: z.string().optional(),
|
|
475
|
-
bio: z.string().default('bio goes here'),
|
|
476
|
-
}),
|
|
477
|
-
]);
|
|
979
|
+
const schema = z.object({ name: z.string(), age: z.number() });
|
|
478
980
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
discriminator: {
|
|
482
|
-
key: 'mode',
|
|
483
|
-
value: 'create',
|
|
484
|
-
},
|
|
485
|
-
});
|
|
486
|
-
// Returns: { age: 18 }
|
|
981
|
+
const selector = toFieldSelector({ schema, name: "name" });
|
|
982
|
+
// { schema, name: 'name' }
|
|
487
983
|
|
|
488
|
-
//
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
},
|
|
494
|
-
});
|
|
495
|
-
// 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
|
+
]);
|
|
496
989
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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' } }
|
|
500
996
|
```
|
|
501
997
|
|
|
502
|
-
**Discriminator types:** The discriminator `value` can be a string, number, or boolean literal that matches the discriminator field type.
|
|
503
|
-
|
|
504
998
|
---
|
|
505
999
|
|
|
506
1000
|
## Type Utilities
|
|
@@ -519,40 +1013,67 @@ type Simple = Simplify<Complex>;
|
|
|
519
1013
|
|
|
520
1014
|
---
|
|
521
1015
|
|
|
522
|
-
### `
|
|
1016
|
+
### `Paths<T, FilterType?, Strict?>`
|
|
1017
|
+
|
|
1018
|
+
Generate dot-notation paths from any type. Low-level utility for building path strings.
|
|
523
1019
|
|
|
524
|
-
|
|
1020
|
+
#### Basic Usage
|
|
525
1021
|
|
|
526
1022
|
```typescript
|
|
527
|
-
import type {
|
|
528
|
-
import { z } from "zod";
|
|
1023
|
+
import type { Paths } from "@zod-utils/core";
|
|
529
1024
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
1025
|
+
type User = {
|
|
1026
|
+
name: string;
|
|
1027
|
+
age: number;
|
|
1028
|
+
profile: {
|
|
1029
|
+
bio: string;
|
|
1030
|
+
avatar: string;
|
|
1031
|
+
};
|
|
1032
|
+
tags: string[];
|
|
1033
|
+
};
|
|
534
1034
|
|
|
535
|
-
|
|
536
|
-
|
|
1035
|
+
// All paths
|
|
1036
|
+
type AllPaths = Paths<User>;
|
|
1037
|
+
// "name" | "age" | "profile" | "profile.bio" | "profile.avatar" | "tags" | "tags.0"
|
|
1038
|
+
```
|
|
537
1039
|
|
|
538
|
-
|
|
539
|
-
|
|
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"
|
|
540
1068
|
```
|
|
541
1069
|
|
|
542
1070
|
---
|
|
543
1071
|
|
|
544
1072
|
### `ValidPaths<TSchema, TDiscriminatorKey?, TDiscriminatorValue?, TFilterType?, TStrict?>`
|
|
545
1073
|
|
|
546
|
-
Generates valid dot-notation paths for a schema, with optional type filtering and discriminated union support.
|
|
1074
|
+
Generates valid dot-notation paths for a Zod schema, with optional type filtering and discriminated union support.
|
|
547
1075
|
|
|
548
|
-
|
|
549
|
-
- `TSchema` - The Zod schema type
|
|
550
|
-
- `TDiscriminatorKey` - Discriminator key for discriminated unions (default: `never`)
|
|
551
|
-
- `TDiscriminatorValue` - Discriminator value to filter variant (default: `never`)
|
|
552
|
-
- `TFilterType` - Type to filter paths by (default: `unknown` = all paths)
|
|
553
|
-
- `TStrict` - Strict mode for type matching (default: `true`)
|
|
554
|
-
|
|
555
|
-
**Basic usage:**
|
|
1076
|
+
#### Basic Usage
|
|
556
1077
|
|
|
557
1078
|
```typescript
|
|
558
1079
|
import type { ValidPaths } from "@zod-utils/core";
|
|
@@ -565,20 +1086,24 @@ const schema = z.object({
|
|
|
565
1086
|
active: z.boolean(),
|
|
566
1087
|
});
|
|
567
1088
|
|
|
568
|
-
//
|
|
1089
|
+
// All paths
|
|
569
1090
|
type AllPaths = ValidPaths<typeof schema>;
|
|
570
1091
|
// "name" | "age" | "email" | "active"
|
|
1092
|
+
```
|
|
1093
|
+
|
|
1094
|
+
#### Filter by Type
|
|
571
1095
|
|
|
572
|
-
|
|
1096
|
+
```typescript
|
|
1097
|
+
// Only string fields
|
|
573
1098
|
type StringPaths = ValidPaths<typeof schema, never, never, string>;
|
|
574
1099
|
// "name"
|
|
575
1100
|
|
|
576
1101
|
// Non-strict mode - includes optional string fields
|
|
577
|
-
type
|
|
1102
|
+
type LooseStringPaths = ValidPaths<typeof schema, never, never, string, false>;
|
|
578
1103
|
// "name" | "email"
|
|
579
1104
|
```
|
|
580
1105
|
|
|
581
|
-
|
|
1106
|
+
#### With Discriminated Unions
|
|
582
1107
|
|
|
583
1108
|
```typescript
|
|
584
1109
|
const formSchema = z.discriminatedUnion("mode", [
|
|
@@ -586,55 +1111,300 @@ const formSchema = z.discriminatedUnion("mode", [
|
|
|
586
1111
|
z.object({ mode: z.literal("edit"), id: z.number(), title: z.string() }),
|
|
587
1112
|
]);
|
|
588
1113
|
|
|
589
|
-
//
|
|
1114
|
+
// All paths for 'create' variant
|
|
590
1115
|
type CreatePaths = ValidPaths<typeof formSchema, "mode", "create">;
|
|
591
1116
|
// "mode" | "name" | "age"
|
|
592
1117
|
|
|
593
|
-
//
|
|
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
|
|
594
1123
|
type EditNumberPaths = ValidPaths<typeof formSchema, "mode", "edit", number>;
|
|
595
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"
|
|
596
1169
|
```
|
|
597
1170
|
|
|
598
|
-
|
|
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
|
|
1340
|
+
```
|
|
1341
|
+
|
|
1342
|
+
#### Optional and Nullable Fields with Strict Mode
|
|
599
1343
|
|
|
600
1344
|
```typescript
|
|
601
1345
|
const schema = z.object({
|
|
602
1346
|
required: z.string(),
|
|
603
1347
|
optional: z.string().optional(),
|
|
604
1348
|
nullable: z.string().nullable(),
|
|
1349
|
+
optionalNullable: z.string().optional().nullable(),
|
|
1350
|
+
withDefault: z.string().default("hello"),
|
|
605
1351
|
});
|
|
606
1352
|
|
|
607
|
-
// Strict mode (default) - exact type
|
|
608
|
-
type
|
|
609
|
-
// "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
|
|
610
1357
|
|
|
611
|
-
// Non-strict mode - includes
|
|
612
|
-
type
|
|
613
|
-
// "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"
|
|
614
1361
|
```
|
|
615
1362
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
### `Paths<T, FilterType?, Strict?>`
|
|
619
|
-
|
|
620
|
-
Low-level type utility for generating dot-notation paths from any type (not schema-specific).
|
|
1363
|
+
#### Complex Discriminated Union with Nested Arrays
|
|
621
1364
|
|
|
622
1365
|
```typescript
|
|
623
|
-
|
|
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
|
+
]);
|
|
624
1394
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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"
|
|
630
1400
|
|
|
631
|
-
//
|
|
632
|
-
type
|
|
633
|
-
// "
|
|
1401
|
+
// Digital order string paths
|
|
1402
|
+
type DigitalStringPaths = ValidPaths<typeof orderSchema, "type", "digital", string>;
|
|
1403
|
+
// "items.0.licenseKey" | "items.0.downloadUrl" | "email"
|
|
634
1404
|
|
|
635
|
-
//
|
|
636
|
-
type
|
|
637
|
-
// "
|
|
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"
|
|
638
1408
|
```
|
|
639
1409
|
|
|
640
1410
|
---
|
|
@@ -663,54 +1433,61 @@ type FormParams = FieldSelector<typeof formSchema, "name", "mode", "create">;
|
|
|
663
1433
|
|
|
664
1434
|
---
|
|
665
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
|
+
|
|
666
1458
|
## Migration Guide
|
|
667
1459
|
|
|
668
1460
|
### Migrating to v3.0.0
|
|
669
1461
|
|
|
670
1462
|
#### `ValidPathsOfType` removed → Use `ValidPaths` with type filtering
|
|
671
1463
|
|
|
672
|
-
The `ValidPathsOfType` type has been removed and consolidated into `ValidPaths` with a new `TFilterType` parameter.
|
|
673
|
-
|
|
674
|
-
**Before (v2.x):**
|
|
675
1464
|
```typescript
|
|
1465
|
+
// Before (v2.x)
|
|
676
1466
|
import type { ValidPathsOfType } from "@zod-utils/core";
|
|
677
|
-
|
|
678
|
-
// Get string field paths
|
|
679
1467
|
type StringPaths = ValidPathsOfType<typeof schema, string>;
|
|
680
|
-
|
|
681
|
-
// With discriminated union
|
|
682
1468
|
type EditNumberPaths = ValidPathsOfType<typeof schema, number, "mode", "edit">;
|
|
683
|
-
```
|
|
684
1469
|
|
|
685
|
-
|
|
686
|
-
```typescript
|
|
1470
|
+
// After (v3.x)
|
|
687
1471
|
import type { ValidPaths } from "@zod-utils/core";
|
|
688
|
-
|
|
689
|
-
// Get string field paths - use 4th type parameter
|
|
690
1472
|
type StringPaths = ValidPaths<typeof schema, never, never, string>;
|
|
691
|
-
|
|
692
|
-
// With discriminated union - TFilterType is now 4th parameter
|
|
693
1473
|
type EditNumberPaths = ValidPaths<typeof schema, "mode", "edit", number>;
|
|
694
1474
|
```
|
|
695
1475
|
|
|
696
|
-
|
|
1476
|
+
### Migrating to v4.0.0
|
|
697
1477
|
|
|
698
|
-
|
|
1478
|
+
#### `mergeFieldSelectorProps` renamed → Use `toFieldSelector`
|
|
699
1479
|
|
|
700
|
-
**Before (v2.x):**
|
|
701
1480
|
```typescript
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
//
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
// New: Non-strict mode
|
|
713
|
-
type StringPaths = Paths<User, string, false>;
|
|
1481
|
+
// Before (v3.x)
|
|
1482
|
+
import { mergeFieldSelectorProps } from "@zod-utils/core";
|
|
1483
|
+
const selectorProps = mergeFieldSelectorProps(
|
|
1484
|
+
{ schema },
|
|
1485
|
+
{ name, discriminator }
|
|
1486
|
+
);
|
|
1487
|
+
|
|
1488
|
+
// After (v4.x)
|
|
1489
|
+
import { toFieldSelector } from "@zod-utils/core";
|
|
1490
|
+
const selectorProps = toFieldSelector({ schema, name, discriminator });
|
|
714
1491
|
```
|
|
715
1492
|
|
|
716
1493
|
---
|