@zod-utils/core 0.2.0 → 0.4.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 +52 -31
- package/dist/index.d.mts +119 -32
- package/dist/index.d.ts +119 -32
- package/dist/index.js +24 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +23 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@zod-utils/core)
|
|
4
4
|
[](https://www.npmjs.com/package/@zod-utils/core)
|
|
5
|
+
[](https://bundlephobia.com/package/@zod-utils/core)
|
|
5
6
|
[](https://opensource.org/licenses/MIT)
|
|
6
7
|
[](https://www.typescriptlang.org/)
|
|
7
8
|
[](https://github.com/thu-san/zod-utils/actions)
|
|
@@ -15,10 +16,14 @@ Pure TypeScript utilities for Zod schema manipulation and default extraction. No
|
|
|
15
16
|
npm install @zod-utils/core zod
|
|
16
17
|
```
|
|
17
18
|
|
|
19
|
+
## Related Packages
|
|
20
|
+
|
|
21
|
+
- **[@zod-utils/react-hook-form](https://www.npmjs.com/package/@zod-utils/react-hook-form)** - React Hook Form integration with automatic type transformation. Uses this package internally and re-exports all utilities for convenience.
|
|
22
|
+
|
|
18
23
|
## Features
|
|
19
24
|
|
|
20
25
|
- 🎯 **Extract defaults** - Get default values from Zod schemas
|
|
21
|
-
- ✅ **Check
|
|
26
|
+
- ✅ **Check validation requirements** - Determine if fields will error on empty input
|
|
22
27
|
- 🔧 **Schema utilities** - Unwrap and manipulate schema types
|
|
23
28
|
- 📦 **Zero dependencies** - Only requires Zod as a peer dependency
|
|
24
29
|
- 🌐 **Universal** - Works in Node.js, browsers, and any TypeScript project
|
|
@@ -67,48 +72,64 @@ const defaults = getSchemaDefaults(schema);
|
|
|
67
72
|
|
|
68
73
|
---
|
|
69
74
|
|
|
70
|
-
### `
|
|
75
|
+
### `requiresValidInput(field)`
|
|
76
|
+
|
|
77
|
+
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).
|
|
71
78
|
|
|
72
|
-
|
|
79
|
+
**Key insight:** Defaults are just initial values - they don't prevent validation errors if the user clears the field.
|
|
73
80
|
|
|
74
|
-
-
|
|
75
|
-
- `null` (via `.nullable()`)
|
|
76
|
-
- Empty string (plain `z.string()` without `.min(1)` or `.nonempty()`)
|
|
77
|
-
- Empty array (plain `z.array()` without `.min(1)` or `.nonempty()`)
|
|
81
|
+
**Real-world example:**
|
|
78
82
|
|
|
79
83
|
```typescript
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
// Marital status with default but validation rules
|
|
85
|
+
const maritalStatus = z.string().min(1).default('single');
|
|
86
|
+
|
|
87
|
+
// What happens in the form:
|
|
88
|
+
// 1. Initial: field shows "single" (from default)
|
|
89
|
+
// 2. User deletes the value → empty string
|
|
90
|
+
// 3. User submits form → validation fails (.min(1) rejects empty)
|
|
91
|
+
// 4. requiresValidInput(maritalStatus) → true (show *, show error)
|
|
92
|
+
```
|
|
82
93
|
|
|
83
|
-
|
|
84
|
-
const requiredString = z.string().min(1);
|
|
85
|
-
const nonemptyString = z.string().nonempty();
|
|
86
|
-
const requiredArray = z.array(z.string()).min(1);
|
|
87
|
-
const nonemptyArray = z.array(z.string()).nonempty();
|
|
94
|
+
**How it works:**
|
|
88
95
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
1. Removes `.default()` wrappers (defaults ≠ validation rules)
|
|
97
|
+
2. Tests if underlying schema accepts empty/invalid input:
|
|
98
|
+
- `undefined` (via `.optional()`)
|
|
99
|
+
- `null` (via `.nullable()`)
|
|
100
|
+
- Empty string (plain `z.string()`)
|
|
101
|
+
- Empty array (plain `z.array()`)
|
|
102
|
+
3. Returns `true` if validation will fail on empty input
|
|
103
|
+
|
|
104
|
+
**Examples:**
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { requiresValidInput } from "@zod-utils/core";
|
|
108
|
+
import { z } from "zod";
|
|
93
109
|
|
|
94
|
-
//
|
|
95
|
-
const
|
|
96
|
-
|
|
110
|
+
// User name - required, no default
|
|
111
|
+
const userName = z.string().min(1);
|
|
112
|
+
requiresValidInput(userName); // true - will error if empty
|
|
97
113
|
|
|
98
|
-
|
|
99
|
-
|
|
114
|
+
// Marital status - required WITH default
|
|
115
|
+
const maritalStatus = z.string().min(1).default('single');
|
|
116
|
+
requiresValidInput(maritalStatus); // true - will error if user clears it
|
|
100
117
|
|
|
101
|
-
//
|
|
102
|
-
const
|
|
103
|
-
|
|
118
|
+
// Age with default - requires valid input
|
|
119
|
+
const age = z.number().default(0);
|
|
120
|
+
requiresValidInput(age); // true - numbers reject empty strings
|
|
104
121
|
|
|
105
|
-
|
|
106
|
-
|
|
122
|
+
// Optional bio - doesn't require input
|
|
123
|
+
const bio = z.string().optional();
|
|
124
|
+
requiresValidInput(bio); // false - user can leave empty
|
|
107
125
|
|
|
108
|
-
//
|
|
109
|
-
const
|
|
126
|
+
// Notes with default but NO validation
|
|
127
|
+
const notes = z.string().default('N/A');
|
|
128
|
+
requiresValidInput(notes); // false - plain z.string() accepts empty
|
|
110
129
|
|
|
111
|
-
|
|
130
|
+
// Nullable middle name
|
|
131
|
+
const middleName = z.string().nullable();
|
|
132
|
+
requiresValidInput(middleName); // false - user can leave null
|
|
112
133
|
```
|
|
113
134
|
|
|
114
135
|
---
|
package/dist/index.d.mts
CHANGED
|
@@ -169,6 +169,78 @@ type Unwrappable = {
|
|
|
169
169
|
* @since 0.1.0
|
|
170
170
|
*/
|
|
171
171
|
declare function canUnwrap(field: z.ZodTypeAny): field is z.ZodTypeAny & Unwrappable;
|
|
172
|
+
/**
|
|
173
|
+
* Unwraps a ZodUnion type and returns the first field and all union options.
|
|
174
|
+
*
|
|
175
|
+
* This function extracts the individual type options from a union type.
|
|
176
|
+
* By default, it filters out `ZodNull` and `ZodUndefined` types, returning only
|
|
177
|
+
* the meaningful type options. You can disable this filtering to get all options.
|
|
178
|
+
*
|
|
179
|
+
* @template T - The Zod type to unwrap
|
|
180
|
+
* @param field - The Zod field (union or single type)
|
|
181
|
+
* @param options - Configuration options
|
|
182
|
+
* @param options.filterNullish - Whether to filter out null and undefined types (default: true)
|
|
183
|
+
* @returns Object with `field` (first option) and `union` (all options array)
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* Basic union unwrapping
|
|
187
|
+
* ```typescript
|
|
188
|
+
* const field = z.union([z.string(), z.number()]);
|
|
189
|
+
* const result = unwrapUnion(field);
|
|
190
|
+
* // Result: { field: z.string(), union: [z.string(), z.number()] }
|
|
191
|
+
* ```
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* Union with null (filtered by default)
|
|
195
|
+
* ```typescript
|
|
196
|
+
* const field = z.union([z.string(), z.null()]);
|
|
197
|
+
* const result = unwrapUnion(field);
|
|
198
|
+
* // Result: { field: z.string(), union: [z.string()] }
|
|
199
|
+
* ```
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* Union with null (keep all options)
|
|
203
|
+
* ```typescript
|
|
204
|
+
* const field = z.union([z.string(), z.null()]);
|
|
205
|
+
* const result = unwrapUnion(field, { filterNullish: false });
|
|
206
|
+
* // Result: { field: z.string(), union: [z.string(), z.null()] }
|
|
207
|
+
* ```
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* Non-union type (returns single field)
|
|
211
|
+
* ```typescript
|
|
212
|
+
* const field = z.string();
|
|
213
|
+
* const result = unwrapUnion(field);
|
|
214
|
+
* // Result: { field: z.string(), union: [z.string()] }
|
|
215
|
+
* ```
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* Nullable as union
|
|
219
|
+
* ```typescript
|
|
220
|
+
* const field = z.string().nullable(); // This is z.union([z.string(), z.null()])
|
|
221
|
+
* const result = unwrapUnion(field);
|
|
222
|
+
* // Result: { field: z.string(), union: [z.string()] } (null filtered out)
|
|
223
|
+
* ```
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* Using the first field for type checking
|
|
227
|
+
* ```typescript
|
|
228
|
+
* const field = z.union([z.string(), z.number()]);
|
|
229
|
+
* const { field: firstField, union } = unwrapUnion(field);
|
|
230
|
+
* if (firstField instanceof z.ZodString) {
|
|
231
|
+
* console.log('First type is string');
|
|
232
|
+
* }
|
|
233
|
+
* ```
|
|
234
|
+
*
|
|
235
|
+
* @see {@link getPrimitiveType} for unwrapping wrapper types
|
|
236
|
+
* @since 0.1.0
|
|
237
|
+
*/
|
|
238
|
+
declare function unwrapUnion<T extends z.ZodTypeAny>(field: T, options?: {
|
|
239
|
+
filterNullish?: boolean;
|
|
240
|
+
}): {
|
|
241
|
+
field: z.ZodTypeAny;
|
|
242
|
+
union: z.ZodTypeAny[];
|
|
243
|
+
};
|
|
172
244
|
/**
|
|
173
245
|
* Gets the underlying primitive type of a Zod field by recursively unwrapping wrapper types.
|
|
174
246
|
*
|
|
@@ -206,7 +278,7 @@ declare function canUnwrap(field: z.ZodTypeAny): field is z.ZodTypeAny & Unwrapp
|
|
|
206
278
|
* @see {@link canUnwrap} for checking if a field can be unwrapped
|
|
207
279
|
* @since 0.1.0
|
|
208
280
|
*/
|
|
209
|
-
declare const getPrimitiveType: <T extends z.
|
|
281
|
+
declare const getPrimitiveType: <T extends z.ZodType>(field: T) => z.ZodTypeAny;
|
|
210
282
|
type StripZodDefault<T> = T extends z.ZodDefault<infer Inner> ? StripZodDefault<Inner> : T extends z.ZodOptional<infer Inner> ? z.ZodOptional<StripZodDefault<Inner>> : T extends z.ZodNullable<infer Inner> ? z.ZodNullable<StripZodDefault<Inner>> : T;
|
|
211
283
|
/**
|
|
212
284
|
* Removes default values from a Zod field while preserving other wrapper types.
|
|
@@ -243,72 +315,87 @@ type StripZodDefault<T> = T extends z.ZodDefault<infer Inner> ? StripZodDefault<
|
|
|
243
315
|
* // Result: z.string().nullable()
|
|
244
316
|
* ```
|
|
245
317
|
*
|
|
246
|
-
* @see {@link
|
|
318
|
+
* @see {@link requiresValidInput} for usage with requirement checking
|
|
247
319
|
* @since 0.1.0
|
|
248
320
|
*/
|
|
249
321
|
declare function removeDefault<T extends z.ZodType>(field: T): StripZodDefault<T>;
|
|
250
322
|
/**
|
|
251
|
-
*
|
|
323
|
+
* Determines if a field will show validation errors when the user submits empty or invalid input.
|
|
324
|
+
*
|
|
325
|
+
* This is useful for form UIs to indicate which fields require valid user input (e.g., showing
|
|
326
|
+
* asterisks, validation states). The key insight: **defaults are just initial values** - they
|
|
327
|
+
* don't prevent validation errors if the user clears the field.
|
|
328
|
+
*
|
|
329
|
+
* **Real-world example:**
|
|
330
|
+
* ```typescript
|
|
331
|
+
* // Marital status field with default but validation rules
|
|
332
|
+
* const maritalStatus = z.string().min(1).default('single');
|
|
252
333
|
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
334
|
+
* // Initial: field shows "single" (from default)
|
|
335
|
+
* // User deletes the value → field is now empty string
|
|
336
|
+
* // User submits form → validation fails because .min(1) rejects empty strings
|
|
337
|
+
* // requiresValidInput(maritalStatus) → true (shows * indicator, validation error)
|
|
338
|
+
* ```
|
|
258
339
|
*
|
|
259
|
-
* **
|
|
260
|
-
*
|
|
340
|
+
* **How it works:**
|
|
341
|
+
* 1. Removes `.default()` wrappers (defaults are initial values, not validation rules)
|
|
342
|
+
* 2. Tests if the underlying schema accepts empty/invalid input:
|
|
343
|
+
* - `undefined` (via `.optional()`)
|
|
344
|
+
* - `null` (via `.nullable()`)
|
|
345
|
+
* - Empty string (plain `z.string()` without `.min(1)` or `.nonempty()`)
|
|
346
|
+
* - Empty array (plain `z.array()` without `.min(1)` or `.nonempty()`)
|
|
347
|
+
* 3. Returns `true` if validation will fail, `false` if empty input is accepted
|
|
261
348
|
*
|
|
262
349
|
* @template T - The Zod type to check
|
|
263
|
-
* @param field - The Zod field to check
|
|
264
|
-
* @returns True if the field
|
|
350
|
+
* @param field - The Zod field to check
|
|
351
|
+
* @returns True if the field will show validation errors on empty/invalid input, false otherwise
|
|
265
352
|
*
|
|
266
353
|
* @example
|
|
267
|
-
*
|
|
354
|
+
* User name field - required, no default
|
|
268
355
|
* ```typescript
|
|
269
|
-
* const
|
|
270
|
-
*
|
|
356
|
+
* const userName = z.string().min(1);
|
|
357
|
+
* requiresValidInput(userName); // true - will error if user submits empty
|
|
271
358
|
* ```
|
|
272
359
|
*
|
|
273
360
|
* @example
|
|
274
|
-
*
|
|
361
|
+
* Marital status - required WITH default
|
|
275
362
|
* ```typescript
|
|
276
|
-
* const
|
|
277
|
-
*
|
|
363
|
+
* const maritalStatus = z.string().min(1).default('single');
|
|
364
|
+
* requiresValidInput(maritalStatus); // true - will error if user clears and submits
|
|
278
365
|
* ```
|
|
279
366
|
*
|
|
280
367
|
* @example
|
|
281
|
-
*
|
|
368
|
+
* Age with default - requires valid input
|
|
282
369
|
* ```typescript
|
|
283
|
-
* const
|
|
284
|
-
*
|
|
370
|
+
* const age = z.number().default(0);
|
|
371
|
+
* requiresValidInput(age); // true - numbers reject empty strings
|
|
285
372
|
* ```
|
|
286
373
|
*
|
|
287
374
|
* @example
|
|
288
|
-
*
|
|
375
|
+
* Optional bio field - doesn't require input
|
|
289
376
|
* ```typescript
|
|
290
|
-
* const
|
|
291
|
-
*
|
|
377
|
+
* const bio = z.string().optional();
|
|
378
|
+
* requiresValidInput(bio); // false - user can leave empty
|
|
292
379
|
* ```
|
|
293
380
|
*
|
|
294
381
|
* @example
|
|
295
|
-
* String with
|
|
382
|
+
* String with default but NO validation - doesn't require input
|
|
296
383
|
* ```typescript
|
|
297
|
-
* const
|
|
298
|
-
*
|
|
384
|
+
* const notes = z.string().default('N/A');
|
|
385
|
+
* requiresValidInput(notes); // false - plain z.string() accepts empty strings
|
|
299
386
|
* ```
|
|
300
387
|
*
|
|
301
388
|
* @example
|
|
302
|
-
* Nullable field
|
|
389
|
+
* Nullable field - doesn't require input
|
|
303
390
|
* ```typescript
|
|
304
|
-
* const
|
|
305
|
-
*
|
|
391
|
+
* const middleName = z.string().nullable();
|
|
392
|
+
* requiresValidInput(middleName); // false - user can leave null
|
|
306
393
|
* ```
|
|
307
394
|
*
|
|
308
395
|
* @see {@link removeDefault} for understanding how defaults are handled
|
|
309
396
|
* @see {@link getPrimitiveType} for understanding type unwrapping
|
|
310
397
|
* @since 0.1.0
|
|
311
398
|
*/
|
|
312
|
-
declare const
|
|
399
|
+
declare const requiresValidInput: <T extends z.ZodType>(field: T) => boolean;
|
|
313
400
|
|
|
314
|
-
export { type Simplify, canUnwrap,
|
|
401
|
+
export { type Simplify, canUnwrap, extractDefault, getPrimitiveType, getSchemaDefaults, removeDefault, requiresValidInput, unwrapUnion };
|
package/dist/index.d.ts
CHANGED
|
@@ -169,6 +169,78 @@ type Unwrappable = {
|
|
|
169
169
|
* @since 0.1.0
|
|
170
170
|
*/
|
|
171
171
|
declare function canUnwrap(field: z.ZodTypeAny): field is z.ZodTypeAny & Unwrappable;
|
|
172
|
+
/**
|
|
173
|
+
* Unwraps a ZodUnion type and returns the first field and all union options.
|
|
174
|
+
*
|
|
175
|
+
* This function extracts the individual type options from a union type.
|
|
176
|
+
* By default, it filters out `ZodNull` and `ZodUndefined` types, returning only
|
|
177
|
+
* the meaningful type options. You can disable this filtering to get all options.
|
|
178
|
+
*
|
|
179
|
+
* @template T - The Zod type to unwrap
|
|
180
|
+
* @param field - The Zod field (union or single type)
|
|
181
|
+
* @param options - Configuration options
|
|
182
|
+
* @param options.filterNullish - Whether to filter out null and undefined types (default: true)
|
|
183
|
+
* @returns Object with `field` (first option) and `union` (all options array)
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* Basic union unwrapping
|
|
187
|
+
* ```typescript
|
|
188
|
+
* const field = z.union([z.string(), z.number()]);
|
|
189
|
+
* const result = unwrapUnion(field);
|
|
190
|
+
* // Result: { field: z.string(), union: [z.string(), z.number()] }
|
|
191
|
+
* ```
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* Union with null (filtered by default)
|
|
195
|
+
* ```typescript
|
|
196
|
+
* const field = z.union([z.string(), z.null()]);
|
|
197
|
+
* const result = unwrapUnion(field);
|
|
198
|
+
* // Result: { field: z.string(), union: [z.string()] }
|
|
199
|
+
* ```
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* Union with null (keep all options)
|
|
203
|
+
* ```typescript
|
|
204
|
+
* const field = z.union([z.string(), z.null()]);
|
|
205
|
+
* const result = unwrapUnion(field, { filterNullish: false });
|
|
206
|
+
* // Result: { field: z.string(), union: [z.string(), z.null()] }
|
|
207
|
+
* ```
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* Non-union type (returns single field)
|
|
211
|
+
* ```typescript
|
|
212
|
+
* const field = z.string();
|
|
213
|
+
* const result = unwrapUnion(field);
|
|
214
|
+
* // Result: { field: z.string(), union: [z.string()] }
|
|
215
|
+
* ```
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* Nullable as union
|
|
219
|
+
* ```typescript
|
|
220
|
+
* const field = z.string().nullable(); // This is z.union([z.string(), z.null()])
|
|
221
|
+
* const result = unwrapUnion(field);
|
|
222
|
+
* // Result: { field: z.string(), union: [z.string()] } (null filtered out)
|
|
223
|
+
* ```
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* Using the first field for type checking
|
|
227
|
+
* ```typescript
|
|
228
|
+
* const field = z.union([z.string(), z.number()]);
|
|
229
|
+
* const { field: firstField, union } = unwrapUnion(field);
|
|
230
|
+
* if (firstField instanceof z.ZodString) {
|
|
231
|
+
* console.log('First type is string');
|
|
232
|
+
* }
|
|
233
|
+
* ```
|
|
234
|
+
*
|
|
235
|
+
* @see {@link getPrimitiveType} for unwrapping wrapper types
|
|
236
|
+
* @since 0.1.0
|
|
237
|
+
*/
|
|
238
|
+
declare function unwrapUnion<T extends z.ZodTypeAny>(field: T, options?: {
|
|
239
|
+
filterNullish?: boolean;
|
|
240
|
+
}): {
|
|
241
|
+
field: z.ZodTypeAny;
|
|
242
|
+
union: z.ZodTypeAny[];
|
|
243
|
+
};
|
|
172
244
|
/**
|
|
173
245
|
* Gets the underlying primitive type of a Zod field by recursively unwrapping wrapper types.
|
|
174
246
|
*
|
|
@@ -206,7 +278,7 @@ declare function canUnwrap(field: z.ZodTypeAny): field is z.ZodTypeAny & Unwrapp
|
|
|
206
278
|
* @see {@link canUnwrap} for checking if a field can be unwrapped
|
|
207
279
|
* @since 0.1.0
|
|
208
280
|
*/
|
|
209
|
-
declare const getPrimitiveType: <T extends z.
|
|
281
|
+
declare const getPrimitiveType: <T extends z.ZodType>(field: T) => z.ZodTypeAny;
|
|
210
282
|
type StripZodDefault<T> = T extends z.ZodDefault<infer Inner> ? StripZodDefault<Inner> : T extends z.ZodOptional<infer Inner> ? z.ZodOptional<StripZodDefault<Inner>> : T extends z.ZodNullable<infer Inner> ? z.ZodNullable<StripZodDefault<Inner>> : T;
|
|
211
283
|
/**
|
|
212
284
|
* Removes default values from a Zod field while preserving other wrapper types.
|
|
@@ -243,72 +315,87 @@ type StripZodDefault<T> = T extends z.ZodDefault<infer Inner> ? StripZodDefault<
|
|
|
243
315
|
* // Result: z.string().nullable()
|
|
244
316
|
* ```
|
|
245
317
|
*
|
|
246
|
-
* @see {@link
|
|
318
|
+
* @see {@link requiresValidInput} for usage with requirement checking
|
|
247
319
|
* @since 0.1.0
|
|
248
320
|
*/
|
|
249
321
|
declare function removeDefault<T extends z.ZodType>(field: T): StripZodDefault<T>;
|
|
250
322
|
/**
|
|
251
|
-
*
|
|
323
|
+
* Determines if a field will show validation errors when the user submits empty or invalid input.
|
|
324
|
+
*
|
|
325
|
+
* This is useful for form UIs to indicate which fields require valid user input (e.g., showing
|
|
326
|
+
* asterisks, validation states). The key insight: **defaults are just initial values** - they
|
|
327
|
+
* don't prevent validation errors if the user clears the field.
|
|
328
|
+
*
|
|
329
|
+
* **Real-world example:**
|
|
330
|
+
* ```typescript
|
|
331
|
+
* // Marital status field with default but validation rules
|
|
332
|
+
* const maritalStatus = z.string().min(1).default('single');
|
|
252
333
|
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
334
|
+
* // Initial: field shows "single" (from default)
|
|
335
|
+
* // User deletes the value → field is now empty string
|
|
336
|
+
* // User submits form → validation fails because .min(1) rejects empty strings
|
|
337
|
+
* // requiresValidInput(maritalStatus) → true (shows * indicator, validation error)
|
|
338
|
+
* ```
|
|
258
339
|
*
|
|
259
|
-
* **
|
|
260
|
-
*
|
|
340
|
+
* **How it works:**
|
|
341
|
+
* 1. Removes `.default()` wrappers (defaults are initial values, not validation rules)
|
|
342
|
+
* 2. Tests if the underlying schema accepts empty/invalid input:
|
|
343
|
+
* - `undefined` (via `.optional()`)
|
|
344
|
+
* - `null` (via `.nullable()`)
|
|
345
|
+
* - Empty string (plain `z.string()` without `.min(1)` or `.nonempty()`)
|
|
346
|
+
* - Empty array (plain `z.array()` without `.min(1)` or `.nonempty()`)
|
|
347
|
+
* 3. Returns `true` if validation will fail, `false` if empty input is accepted
|
|
261
348
|
*
|
|
262
349
|
* @template T - The Zod type to check
|
|
263
|
-
* @param field - The Zod field to check
|
|
264
|
-
* @returns True if the field
|
|
350
|
+
* @param field - The Zod field to check
|
|
351
|
+
* @returns True if the field will show validation errors on empty/invalid input, false otherwise
|
|
265
352
|
*
|
|
266
353
|
* @example
|
|
267
|
-
*
|
|
354
|
+
* User name field - required, no default
|
|
268
355
|
* ```typescript
|
|
269
|
-
* const
|
|
270
|
-
*
|
|
356
|
+
* const userName = z.string().min(1);
|
|
357
|
+
* requiresValidInput(userName); // true - will error if user submits empty
|
|
271
358
|
* ```
|
|
272
359
|
*
|
|
273
360
|
* @example
|
|
274
|
-
*
|
|
361
|
+
* Marital status - required WITH default
|
|
275
362
|
* ```typescript
|
|
276
|
-
* const
|
|
277
|
-
*
|
|
363
|
+
* const maritalStatus = z.string().min(1).default('single');
|
|
364
|
+
* requiresValidInput(maritalStatus); // true - will error if user clears and submits
|
|
278
365
|
* ```
|
|
279
366
|
*
|
|
280
367
|
* @example
|
|
281
|
-
*
|
|
368
|
+
* Age with default - requires valid input
|
|
282
369
|
* ```typescript
|
|
283
|
-
* const
|
|
284
|
-
*
|
|
370
|
+
* const age = z.number().default(0);
|
|
371
|
+
* requiresValidInput(age); // true - numbers reject empty strings
|
|
285
372
|
* ```
|
|
286
373
|
*
|
|
287
374
|
* @example
|
|
288
|
-
*
|
|
375
|
+
* Optional bio field - doesn't require input
|
|
289
376
|
* ```typescript
|
|
290
|
-
* const
|
|
291
|
-
*
|
|
377
|
+
* const bio = z.string().optional();
|
|
378
|
+
* requiresValidInput(bio); // false - user can leave empty
|
|
292
379
|
* ```
|
|
293
380
|
*
|
|
294
381
|
* @example
|
|
295
|
-
* String with
|
|
382
|
+
* String with default but NO validation - doesn't require input
|
|
296
383
|
* ```typescript
|
|
297
|
-
* const
|
|
298
|
-
*
|
|
384
|
+
* const notes = z.string().default('N/A');
|
|
385
|
+
* requiresValidInput(notes); // false - plain z.string() accepts empty strings
|
|
299
386
|
* ```
|
|
300
387
|
*
|
|
301
388
|
* @example
|
|
302
|
-
* Nullable field
|
|
389
|
+
* Nullable field - doesn't require input
|
|
303
390
|
* ```typescript
|
|
304
|
-
* const
|
|
305
|
-
*
|
|
391
|
+
* const middleName = z.string().nullable();
|
|
392
|
+
* requiresValidInput(middleName); // false - user can leave null
|
|
306
393
|
* ```
|
|
307
394
|
*
|
|
308
395
|
* @see {@link removeDefault} for understanding how defaults are handled
|
|
309
396
|
* @see {@link getPrimitiveType} for understanding type unwrapping
|
|
310
397
|
* @since 0.1.0
|
|
311
398
|
*/
|
|
312
|
-
declare const
|
|
399
|
+
declare const requiresValidInput: <T extends z.ZodType>(field: T) => boolean;
|
|
313
400
|
|
|
314
|
-
export { type Simplify, canUnwrap,
|
|
401
|
+
export { type Simplify, canUnwrap, extractDefault, getPrimitiveType, getSchemaDefaults, removeDefault, requiresValidInput, unwrapUnion };
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,23 @@ var z__namespace = /*#__PURE__*/_interopNamespace(z);
|
|
|
26
26
|
function canUnwrap(field) {
|
|
27
27
|
return "unwrap" in field && typeof field.unwrap === "function";
|
|
28
28
|
}
|
|
29
|
+
function unwrapUnion(field, options = {}) {
|
|
30
|
+
const { filterNullish = true } = options;
|
|
31
|
+
if (field instanceof z__namespace.ZodUnion) {
|
|
32
|
+
const unionOptions = [...field.def.options];
|
|
33
|
+
const filteredOptions = filterNullish ? unionOptions.filter(
|
|
34
|
+
(option) => !(option instanceof z__namespace.ZodNull) && !(option instanceof z__namespace.ZodUndefined)
|
|
35
|
+
) : unionOptions;
|
|
36
|
+
return {
|
|
37
|
+
field: filteredOptions[0] || field,
|
|
38
|
+
union: filteredOptions
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
field,
|
|
43
|
+
union: [field]
|
|
44
|
+
};
|
|
45
|
+
}
|
|
29
46
|
var getPrimitiveType = (field) => {
|
|
30
47
|
if (field instanceof z__namespace.ZodArray) {
|
|
31
48
|
return field;
|
|
@@ -33,6 +50,9 @@ var getPrimitiveType = (field) => {
|
|
|
33
50
|
if (canUnwrap(field)) {
|
|
34
51
|
return getPrimitiveType(field.unwrap());
|
|
35
52
|
}
|
|
53
|
+
if (field instanceof z__namespace.ZodUnion) {
|
|
54
|
+
return getPrimitiveType(unwrapUnion(field).field);
|
|
55
|
+
}
|
|
36
56
|
return field;
|
|
37
57
|
};
|
|
38
58
|
function removeDefault(field) {
|
|
@@ -50,15 +70,12 @@ function removeDefault(field) {
|
|
|
50
70
|
}
|
|
51
71
|
return field;
|
|
52
72
|
}
|
|
53
|
-
var
|
|
54
|
-
const undefinedResult = field.safeParse(void 0).success;
|
|
55
|
-
if (undefinedResult) {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
73
|
+
var requiresValidInput = (field) => {
|
|
58
74
|
const defaultRemovedField = removeDefault(field);
|
|
59
75
|
if (!(defaultRemovedField instanceof z__namespace.ZodType)) {
|
|
60
76
|
return false;
|
|
61
77
|
}
|
|
78
|
+
const undefinedResult = defaultRemovedField.safeParse(void 0).success;
|
|
62
79
|
const nullResult = defaultRemovedField.safeParse(null).success;
|
|
63
80
|
const primitiveType = getPrimitiveType(defaultRemovedField);
|
|
64
81
|
const emptyStringResult = primitiveType.type === "string" && defaultRemovedField.safeParse("").success;
|
|
@@ -90,10 +107,11 @@ function getSchemaDefaults(schema) {
|
|
|
90
107
|
}
|
|
91
108
|
|
|
92
109
|
exports.canUnwrap = canUnwrap;
|
|
93
|
-
exports.checkIfFieldIsRequired = checkIfFieldIsRequired;
|
|
94
110
|
exports.extractDefault = extractDefault;
|
|
95
111
|
exports.getPrimitiveType = getPrimitiveType;
|
|
96
112
|
exports.getSchemaDefaults = getSchemaDefaults;
|
|
97
113
|
exports.removeDefault = removeDefault;
|
|
114
|
+
exports.requiresValidInput = requiresValidInput;
|
|
115
|
+
exports.unwrapUnion = unwrapUnion;
|
|
98
116
|
//# sourceMappingURL=index.js.map
|
|
99
117
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/schema.ts","../src/defaults.ts"],"names":["z","z2"],"mappings":"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","file":"index.js","sourcesContent":["import * as z from 'zod';\n\n/**\n * Type representing a Zod type that has an unwrap method\n */\ntype Unwrappable = { unwrap: () => z.ZodTypeAny };\n\n/**\n * Type guard to check if a Zod field can be unwrapped (has wrapper types like optional, nullable, default).\n *\n * This checks whether a Zod type has an `unwrap()` method, which is present on wrapper types\n * like `ZodOptional`, `ZodNullable`, `ZodDefault`, and others.\n *\n * @param field - The Zod field to check\n * @returns True if the field has an unwrap method, false otherwise\n *\n * @example\n * ```typescript\n * const optionalField = z.string().optional();\n * console.log(canUnwrap(optionalField)); // true\n *\n * const plainField = z.string();\n * console.log(canUnwrap(plainField)); // false\n * ```\n *\n * @since 0.1.0\n */\nexport function canUnwrap(\n field: z.ZodTypeAny,\n): field is z.ZodTypeAny & Unwrappable {\n return 'unwrap' in field && typeof field.unwrap === 'function';\n}\n\n/**\n * Gets the underlying primitive type of a Zod field by recursively unwrapping wrapper types.\n *\n * This function removes wrapper layers (optional, nullable, default) to reveal the base type.\n * **Important:** It stops at array types without unwrapping them, treating arrays as primitives.\n *\n * @template T - The Zod type to unwrap\n * @param field - The Zod field to unwrap\n * @returns The unwrapped primitive Zod type\n *\n * @example\n * Unwrapping to string primitive\n * ```typescript\n * const field = z.string().optional().nullable();\n * const primitive = getPrimitiveType(field);\n * // Result: z.string() (unwrapped all wrappers)\n * ```\n *\n * @example\n * Stopping at array type\n * ```typescript\n * const field = z.array(z.string()).optional();\n * const primitive = getPrimitiveType(field);\n * // Result: z.array(z.string()) (stops at array, doesn't unwrap it)\n * ```\n *\n * @example\n * Unwrapping defaults\n * ```typescript\n * const field = z.number().default(0).optional();\n * const primitive = getPrimitiveType(field);\n * // Result: z.number()\n * ```\n *\n * @see {@link canUnwrap} for checking if a field can be unwrapped\n * @since 0.1.0\n */\nexport const getPrimitiveType = <T extends z.ZodTypeAny>(\n field: T,\n): z.ZodTypeAny => {\n // Stop at arrays - don't unwrap them\n if (field instanceof z.ZodArray) {\n return field;\n }\n\n if (canUnwrap(field)) {\n return getPrimitiveType(field.unwrap());\n }\n\n return field;\n};\n\ntype StripZodDefault<T> = T extends z.ZodDefault<infer Inner>\n ? StripZodDefault<Inner>\n : T extends z.ZodOptional<infer Inner>\n ? z.ZodOptional<StripZodDefault<Inner>>\n : T extends z.ZodNullable<infer Inner>\n ? z.ZodNullable<StripZodDefault<Inner>>\n : T;\n\n/**\n * Removes default values from a Zod field while preserving other wrapper types.\n *\n * This function recursively removes `ZodDefault` wrappers from a field, while maintaining\n * `optional()` and `nullable()` wrappers. Useful for scenarios where you want to check\n * field requirements without considering default values.\n *\n * @template T - The Zod type to process\n * @param field - The Zod field to remove defaults from\n * @returns The field without defaults but with optional/nullable preserved\n *\n * @example\n * Removing simple default\n * ```typescript\n * const field = z.string().default('hello');\n * const withoutDefault = removeDefault(field);\n * // Result: z.string()\n * ```\n *\n * @example\n * Preserving optional wrapper\n * ```typescript\n * const field = z.string().default('hello').optional();\n * const withoutDefault = removeDefault(field);\n * // Result: z.string().optional()\n * ```\n *\n * @example\n * Nested defaults\n * ```typescript\n * const field = z.string().default('inner').nullable().default('outer');\n * const withoutDefault = removeDefault(field);\n * // Result: z.string().nullable()\n * ```\n *\n * @see {@link checkIfFieldIsRequired} for usage with requirement checking\n * @since 0.1.0\n */\nexport function removeDefault<T extends z.ZodType>(\n field: T,\n): StripZodDefault<T> {\n if (field instanceof z.ZodDefault) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return field.unwrap() as StripZodDefault<T>;\n }\n\n if ('innerType' in field.def && field.def.innerType instanceof z.ZodType) {\n const inner = removeDefault(field.def.innerType);\n // Reconstruct the wrapper with the modified inner type\n if (field instanceof z.ZodOptional) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return inner.optional() as unknown as StripZodDefault<T>;\n }\n if (field instanceof z.ZodNullable) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return inner.nullable() as unknown as StripZodDefault<T>;\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return field as StripZodDefault<T>;\n}\n\n/**\n * Checks if a Zod field is truly required by testing multiple acceptance criteria.\n *\n * A field is considered **not required** if it accepts any of the following:\n * - `undefined` (via `.optional()` or `.default()`)\n * - `null` (via `.nullable()`)\n * - Empty string (plain `z.string()` without `.min(1)` or `.nonempty()`)\n * - Empty array (plain `z.array()` without `.min(1)` or `.nonempty()`)\n *\n * **Note:** Fields with `.default()` are considered not required since they'll have a value\n * even if the user doesn't provide one.\n *\n * @template T - The Zod type to check\n * @param field - The Zod field to check for required status\n * @returns True if the field is required, false otherwise\n *\n * @example\n * Required field\n * ```typescript\n * const field = z.string().min(1);\n * console.log(checkIfFieldIsRequired(field)); // true\n * ```\n *\n * @example\n * Optional field (not required)\n * ```typescript\n * const field = z.string().optional();\n * console.log(checkIfFieldIsRequired(field)); // false\n * ```\n *\n * @example\n * Field with default (not required)\n * ```typescript\n * const field = z.string().default('hello');\n * console.log(checkIfFieldIsRequired(field)); // false\n * ```\n *\n * @example\n * String without min length (not required - accepts empty string)\n * ```typescript\n * const field = z.string();\n * console.log(checkIfFieldIsRequired(field)); // false\n * ```\n *\n * @example\n * String with nonempty (required)\n * ```typescript\n * const field = z.string().nonempty();\n * console.log(checkIfFieldIsRequired(field)); // true\n * ```\n *\n * @example\n * Nullable field (not required)\n * ```typescript\n * const field = z.number().nullable();\n * console.log(checkIfFieldIsRequired(field)); // false\n * ```\n *\n * @see {@link removeDefault} for understanding how defaults are handled\n * @see {@link getPrimitiveType} for understanding type unwrapping\n * @since 0.1.0\n */\nexport const checkIfFieldIsRequired = <T extends z.ZodType>(field: T) => {\n // First check the original field for undefined - this catches fields with defaults\n const undefinedResult = field.safeParse(undefined).success;\n if (undefinedResult) {\n return false;\n }\n\n const defaultRemovedField = removeDefault(field);\n\n if (!(defaultRemovedField instanceof z.ZodType)) {\n return false;\n }\n\n // Check if field accepts null (nullable)\n const nullResult = defaultRemovedField.safeParse(null).success;\n\n const primitiveType = getPrimitiveType(defaultRemovedField);\n\n const emptyStringResult =\n primitiveType.type === 'string' &&\n defaultRemovedField.safeParse('').success;\n\n const emptyArrayResult =\n primitiveType.type === 'array' && defaultRemovedField.safeParse([]).success;\n\n return (\n !undefinedResult && !nullResult && !emptyStringResult && !emptyArrayResult\n );\n};\n","import * as z from 'zod';\nimport { canUnwrap } from './schema';\nimport type { Simplify } from './types';\n\n/**\n * Extracts the default value from a Zod field, recursively unwrapping optional and nullable layers.\n *\n * This function traverses through wrapper types (like `ZodOptional`, `ZodNullable`) to find\n * the underlying `ZodDefault` and returns its default value. If no default is found, returns `undefined`.\n *\n * @template T - The Zod type to extract default from\n * @param field - The Zod field to extract default from\n * @returns The default value if present, undefined otherwise\n *\n * @example\n * Basic usage with default value\n * ```typescript\n * const field = z.string().default('hello');\n * const defaultValue = extractDefault(field);\n * // Result: 'hello'\n * ```\n *\n * @example\n * Unwrapping optional/nullable layers\n * ```typescript\n * const field = z.string().default('world').optional();\n * const defaultValue = extractDefault(field);\n * // Result: 'world' (unwraps optional to find default)\n * ```\n *\n * @example\n * Field without default\n * ```typescript\n * const field = z.string().optional();\n * const defaultValue = extractDefault(field);\n * // Result: undefined\n * ```\n *\n * @see {@link getSchemaDefaults} for extracting defaults from entire schemas\n * @since 0.1.0\n */\nexport function extractDefault<T extends z.ZodTypeAny>(\n field: T,\n): z.infer<T> | undefined {\n if (field instanceof z.ZodDefault) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return field.def.defaultValue as z.infer<T>;\n }\n\n if (canUnwrap(field)) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return extractDefault(field.unwrap()) as z.infer<T>;\n }\n\n return undefined;\n}\n\n/**\n * Extracts default values from a Zod object schema while skipping fields without defaults.\n *\n * This function recursively traverses the schema and collects all fields that have\n * explicit default values defined. Fields without defaults are excluded from the result.\n *\n * **Important:** Nested defaults are NOT extracted unless the parent object also has\n * an explicit `.default()`. This is by design to match Zod's default value behavior.\n *\n * @template T - The Zod object schema type\n * @param schema - The Zod object schema to extract defaults from\n * @returns A partial object containing only fields with default values\n *\n * @example\n * Basic usage\n * ```typescript\n * const schema = z.object({\n * name: z.string().default('John'),\n * age: z.number(), // no default - will be skipped\n * email: z.string().email().optional(),\n * });\n *\n * const defaults = getSchemaDefaults(schema);\n * // Result: { name: 'John' }\n * ```\n *\n * @example\n * Nested objects with defaults\n * ```typescript\n * const schema = z.object({\n * user: z.object({\n * name: z.string().default('Guest')\n * }).default({ name: 'Guest' }), // ✅ Extracted because parent has .default()\n *\n * settings: z.object({\n * theme: z.string().default('light')\n * }), // ❌ NOT extracted - parent has no .default()\n * });\n *\n * const defaults = getSchemaDefaults(schema);\n * // Result: { user: { name: 'Guest' } }\n * ```\n *\n * @example\n * Unwrapping optional/nullable fields\n * ```typescript\n * const schema = z.object({\n * title: z.string().default('Untitled').optional(),\n * count: z.number().default(0).nullable(),\n * });\n *\n * const defaults = getSchemaDefaults(schema);\n * // Result: { title: 'Untitled', count: 0 }\n * ```\n *\n * @see {@link extractDefault} for extracting defaults from individual fields\n * @since 0.1.0\n */\nexport function getSchemaDefaults<T extends z.ZodObject>(\n schema: T,\n): Simplify<Partial<z.infer<T>>> {\n const defaults: Record<string, unknown> = {};\n\n for (const key in schema.shape) {\n const field = schema.shape[key];\n if (!field) continue;\n\n // Check if this field has an explicit default value\n const defaultValue = extractDefault(field);\n if (defaultValue !== undefined) {\n defaults[key] = defaultValue;\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return defaults as Partial<z.infer<T>>;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/schema.ts","../src/defaults.ts"],"names":["z","z2"],"mappings":"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","file":"index.js","sourcesContent":["import * as z from 'zod';\n\n/**\n * Type representing a Zod type that has an unwrap method\n */\ntype Unwrappable = { unwrap: () => z.ZodTypeAny };\n\n/**\n * Type guard to check if a Zod field can be unwrapped (has wrapper types like optional, nullable, default).\n *\n * This checks whether a Zod type has an `unwrap()` method, which is present on wrapper types\n * like `ZodOptional`, `ZodNullable`, `ZodDefault`, and others.\n *\n * @param field - The Zod field to check\n * @returns True if the field has an unwrap method, false otherwise\n *\n * @example\n * ```typescript\n * const optionalField = z.string().optional();\n * console.log(canUnwrap(optionalField)); // true\n *\n * const plainField = z.string();\n * console.log(canUnwrap(plainField)); // false\n * ```\n *\n * @since 0.1.0\n */\nexport function canUnwrap(\n field: z.ZodTypeAny,\n): field is z.ZodTypeAny & Unwrappable {\n return 'unwrap' in field && typeof field.unwrap === 'function';\n}\n\n/**\n * Unwraps a ZodUnion type and returns the first field and all union options.\n *\n * This function extracts the individual type options from a union type.\n * By default, it filters out `ZodNull` and `ZodUndefined` types, returning only\n * the meaningful type options. You can disable this filtering to get all options.\n *\n * @template T - The Zod type to unwrap\n * @param field - The Zod field (union or single type)\n * @param options - Configuration options\n * @param options.filterNullish - Whether to filter out null and undefined types (default: true)\n * @returns Object with `field` (first option) and `union` (all options array)\n *\n * @example\n * Basic union unwrapping\n * ```typescript\n * const field = z.union([z.string(), z.number()]);\n * const result = unwrapUnion(field);\n * // Result: { field: z.string(), union: [z.string(), z.number()] }\n * ```\n *\n * @example\n * Union with null (filtered by default)\n * ```typescript\n * const field = z.union([z.string(), z.null()]);\n * const result = unwrapUnion(field);\n * // Result: { field: z.string(), union: [z.string()] }\n * ```\n *\n * @example\n * Union with null (keep all options)\n * ```typescript\n * const field = z.union([z.string(), z.null()]);\n * const result = unwrapUnion(field, { filterNullish: false });\n * // Result: { field: z.string(), union: [z.string(), z.null()] }\n * ```\n *\n * @example\n * Non-union type (returns single field)\n * ```typescript\n * const field = z.string();\n * const result = unwrapUnion(field);\n * // Result: { field: z.string(), union: [z.string()] }\n * ```\n *\n * @example\n * Nullable as union\n * ```typescript\n * const field = z.string().nullable(); // This is z.union([z.string(), z.null()])\n * const result = unwrapUnion(field);\n * // Result: { field: z.string(), union: [z.string()] } (null filtered out)\n * ```\n *\n * @example\n * Using the first field for type checking\n * ```typescript\n * const field = z.union([z.string(), z.number()]);\n * const { field: firstField, union } = unwrapUnion(field);\n * if (firstField instanceof z.ZodString) {\n * console.log('First type is string');\n * }\n * ```\n *\n * @see {@link getPrimitiveType} for unwrapping wrapper types\n * @since 0.1.0\n */\nexport function unwrapUnion<T extends z.ZodTypeAny>(\n field: T,\n options: { filterNullish?: boolean } = {},\n): { field: z.ZodTypeAny; union: z.ZodTypeAny[] } {\n const { filterNullish = true } = options;\n\n if (field instanceof z.ZodUnion) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n const unionOptions = [...field.def.options] as z.ZodTypeAny[];\n\n const filteredOptions = filterNullish\n ? unionOptions.filter(\n (option) =>\n !(option instanceof z.ZodNull) &&\n !(option instanceof z.ZodUndefined),\n )\n : unionOptions;\n\n return {\n field: filteredOptions[0] || field,\n union: filteredOptions,\n };\n }\n\n // If it's not a union, return the field itself\n return {\n field,\n union: [field],\n };\n}\n\n/**\n * Gets the underlying primitive type of a Zod field by recursively unwrapping wrapper types.\n *\n * This function removes wrapper layers (optional, nullable, default) to reveal the base type.\n * **Important:** It stops at array types without unwrapping them, treating arrays as primitives.\n *\n * @template T - The Zod type to unwrap\n * @param field - The Zod field to unwrap\n * @returns The unwrapped primitive Zod type\n *\n * @example\n * Unwrapping to string primitive\n * ```typescript\n * const field = z.string().optional().nullable();\n * const primitive = getPrimitiveType(field);\n * // Result: z.string() (unwrapped all wrappers)\n * ```\n *\n * @example\n * Stopping at array type\n * ```typescript\n * const field = z.array(z.string()).optional();\n * const primitive = getPrimitiveType(field);\n * // Result: z.array(z.string()) (stops at array, doesn't unwrap it)\n * ```\n *\n * @example\n * Unwrapping defaults\n * ```typescript\n * const field = z.number().default(0).optional();\n * const primitive = getPrimitiveType(field);\n * // Result: z.number()\n * ```\n *\n * @see {@link canUnwrap} for checking if a field can be unwrapped\n * @since 0.1.0\n */\nexport const getPrimitiveType = <T extends z.ZodType>(\n field: T,\n): z.ZodTypeAny => {\n // Stop at arrays - don't unwrap them\n if (field instanceof z.ZodArray) {\n return field;\n }\n\n if (canUnwrap(field)) {\n return getPrimitiveType(field.unwrap());\n }\n\n if (field instanceof z.ZodUnion) {\n return getPrimitiveType(unwrapUnion(field).field);\n }\n\n return field;\n};\n\ntype StripZodDefault<T> = T extends z.ZodDefault<infer Inner>\n ? StripZodDefault<Inner>\n : T extends z.ZodOptional<infer Inner>\n ? z.ZodOptional<StripZodDefault<Inner>>\n : T extends z.ZodNullable<infer Inner>\n ? z.ZodNullable<StripZodDefault<Inner>>\n : T;\n\n/**\n * Removes default values from a Zod field while preserving other wrapper types.\n *\n * This function recursively removes `ZodDefault` wrappers from a field, while maintaining\n * `optional()` and `nullable()` wrappers. Useful for scenarios where you want to check\n * field requirements without considering default values.\n *\n * @template T - The Zod type to process\n * @param field - The Zod field to remove defaults from\n * @returns The field without defaults but with optional/nullable preserved\n *\n * @example\n * Removing simple default\n * ```typescript\n * const field = z.string().default('hello');\n * const withoutDefault = removeDefault(field);\n * // Result: z.string()\n * ```\n *\n * @example\n * Preserving optional wrapper\n * ```typescript\n * const field = z.string().default('hello').optional();\n * const withoutDefault = removeDefault(field);\n * // Result: z.string().optional()\n * ```\n *\n * @example\n * Nested defaults\n * ```typescript\n * const field = z.string().default('inner').nullable().default('outer');\n * const withoutDefault = removeDefault(field);\n * // Result: z.string().nullable()\n * ```\n *\n * @see {@link requiresValidInput} for usage with requirement checking\n * @since 0.1.0\n */\nexport function removeDefault<T extends z.ZodType>(\n field: T,\n): StripZodDefault<T> {\n if (field instanceof z.ZodDefault) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return field.unwrap() as StripZodDefault<T>;\n }\n\n if ('innerType' in field.def && field.def.innerType instanceof z.ZodType) {\n const inner = removeDefault(field.def.innerType);\n // Reconstruct the wrapper with the modified inner type\n if (field instanceof z.ZodOptional) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return inner.optional() as unknown as StripZodDefault<T>;\n }\n if (field instanceof z.ZodNullable) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return inner.nullable() as unknown as StripZodDefault<T>;\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return field as StripZodDefault<T>;\n}\n\n/**\n * Determines if a field will show validation errors when the user submits empty or invalid input.\n *\n * This is useful for form UIs to indicate which fields require valid user input (e.g., showing\n * asterisks, validation states). The key insight: **defaults are just initial values** - they\n * don't prevent validation errors if the user clears the field.\n *\n * **Real-world example:**\n * ```typescript\n * // Marital status field with default but validation rules\n * const maritalStatus = z.string().min(1).default('single');\n *\n * // Initial: field shows \"single\" (from default)\n * // User deletes the value → field is now empty string\n * // User submits form → validation fails because .min(1) rejects empty strings\n * // requiresValidInput(maritalStatus) → true (shows * indicator, validation error)\n * ```\n *\n * **How it works:**\n * 1. Removes `.default()` wrappers (defaults are initial values, not validation rules)\n * 2. Tests if the underlying schema accepts empty/invalid input:\n * - `undefined` (via `.optional()`)\n * - `null` (via `.nullable()`)\n * - Empty string (plain `z.string()` without `.min(1)` or `.nonempty()`)\n * - Empty array (plain `z.array()` without `.min(1)` or `.nonempty()`)\n * 3. Returns `true` if validation will fail, `false` if empty input is accepted\n *\n * @template T - The Zod type to check\n * @param field - The Zod field to check\n * @returns True if the field will show validation errors on empty/invalid input, false otherwise\n *\n * @example\n * User name field - required, no default\n * ```typescript\n * const userName = z.string().min(1);\n * requiresValidInput(userName); // true - will error if user submits empty\n * ```\n *\n * @example\n * Marital status - required WITH default\n * ```typescript\n * const maritalStatus = z.string().min(1).default('single');\n * requiresValidInput(maritalStatus); // true - will error if user clears and submits\n * ```\n *\n * @example\n * Age with default - requires valid input\n * ```typescript\n * const age = z.number().default(0);\n * requiresValidInput(age); // true - numbers reject empty strings\n * ```\n *\n * @example\n * Optional bio field - doesn't require input\n * ```typescript\n * const bio = z.string().optional();\n * requiresValidInput(bio); // false - user can leave empty\n * ```\n *\n * @example\n * String with default but NO validation - doesn't require input\n * ```typescript\n * const notes = z.string().default('N/A');\n * requiresValidInput(notes); // false - plain z.string() accepts empty strings\n * ```\n *\n * @example\n * Nullable field - doesn't require input\n * ```typescript\n * const middleName = z.string().nullable();\n * requiresValidInput(middleName); // false - user can leave null\n * ```\n *\n * @see {@link removeDefault} for understanding how defaults are handled\n * @see {@link getPrimitiveType} for understanding type unwrapping\n * @since 0.1.0\n */\nexport const requiresValidInput = <T extends z.ZodType>(field: T) => {\n const defaultRemovedField = removeDefault(field);\n if (!(defaultRemovedField instanceof z.ZodType)) {\n return false;\n }\n\n const undefinedResult = defaultRemovedField.safeParse(undefined).success;\n\n // Check if field accepts null (nullable)\n const nullResult = defaultRemovedField.safeParse(null).success;\n\n const primitiveType = getPrimitiveType(defaultRemovedField);\n\n const emptyStringResult =\n primitiveType.type === 'string' &&\n defaultRemovedField.safeParse('').success;\n\n const emptyArrayResult =\n primitiveType.type === 'array' && defaultRemovedField.safeParse([]).success;\n\n return (\n !undefinedResult && !nullResult && !emptyStringResult && !emptyArrayResult\n );\n};\n","import * as z from 'zod';\nimport { canUnwrap } from './schema';\nimport type { Simplify } from './types';\n\n/**\n * Extracts the default value from a Zod field, recursively unwrapping optional and nullable layers.\n *\n * This function traverses through wrapper types (like `ZodOptional`, `ZodNullable`) to find\n * the underlying `ZodDefault` and returns its default value. If no default is found, returns `undefined`.\n *\n * @template T - The Zod type to extract default from\n * @param field - The Zod field to extract default from\n * @returns The default value if present, undefined otherwise\n *\n * @example\n * Basic usage with default value\n * ```typescript\n * const field = z.string().default('hello');\n * const defaultValue = extractDefault(field);\n * // Result: 'hello'\n * ```\n *\n * @example\n * Unwrapping optional/nullable layers\n * ```typescript\n * const field = z.string().default('world').optional();\n * const defaultValue = extractDefault(field);\n * // Result: 'world' (unwraps optional to find default)\n * ```\n *\n * @example\n * Field without default\n * ```typescript\n * const field = z.string().optional();\n * const defaultValue = extractDefault(field);\n * // Result: undefined\n * ```\n *\n * @see {@link getSchemaDefaults} for extracting defaults from entire schemas\n * @since 0.1.0\n */\nexport function extractDefault<T extends z.ZodTypeAny>(\n field: T,\n): z.infer<T> | undefined {\n if (field instanceof z.ZodDefault) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return field.def.defaultValue as z.infer<T>;\n }\n\n if (canUnwrap(field)) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return extractDefault(field.unwrap()) as z.infer<T>;\n }\n\n return undefined;\n}\n\n/**\n * Extracts default values from a Zod object schema while skipping fields without defaults.\n *\n * This function recursively traverses the schema and collects all fields that have\n * explicit default values defined. Fields without defaults are excluded from the result.\n *\n * **Important:** Nested defaults are NOT extracted unless the parent object also has\n * an explicit `.default()`. This is by design to match Zod's default value behavior.\n *\n * @template T - The Zod object schema type\n * @param schema - The Zod object schema to extract defaults from\n * @returns A partial object containing only fields with default values\n *\n * @example\n * Basic usage\n * ```typescript\n * const schema = z.object({\n * name: z.string().default('John'),\n * age: z.number(), // no default - will be skipped\n * email: z.string().email().optional(),\n * });\n *\n * const defaults = getSchemaDefaults(schema);\n * // Result: { name: 'John' }\n * ```\n *\n * @example\n * Nested objects with defaults\n * ```typescript\n * const schema = z.object({\n * user: z.object({\n * name: z.string().default('Guest')\n * }).default({ name: 'Guest' }), // ✅ Extracted because parent has .default()\n *\n * settings: z.object({\n * theme: z.string().default('light')\n * }), // ❌ NOT extracted - parent has no .default()\n * });\n *\n * const defaults = getSchemaDefaults(schema);\n * // Result: { user: { name: 'Guest' } }\n * ```\n *\n * @example\n * Unwrapping optional/nullable fields\n * ```typescript\n * const schema = z.object({\n * title: z.string().default('Untitled').optional(),\n * count: z.number().default(0).nullable(),\n * });\n *\n * const defaults = getSchemaDefaults(schema);\n * // Result: { title: 'Untitled', count: 0 }\n * ```\n *\n * @see {@link extractDefault} for extracting defaults from individual fields\n * @since 0.1.0\n */\nexport function getSchemaDefaults<T extends z.ZodObject>(\n schema: T,\n): Simplify<Partial<z.infer<T>>> {\n const defaults: Record<string, unknown> = {};\n\n for (const key in schema.shape) {\n const field = schema.shape[key];\n if (!field) continue;\n\n // Check if this field has an explicit default value\n const defaultValue = extractDefault(field);\n if (defaultValue !== undefined) {\n defaults[key] = defaultValue;\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return defaults as Partial<z.infer<T>>;\n}\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -4,6 +4,23 @@ import * as z from 'zod';
|
|
|
4
4
|
function canUnwrap(field) {
|
|
5
5
|
return "unwrap" in field && typeof field.unwrap === "function";
|
|
6
6
|
}
|
|
7
|
+
function unwrapUnion(field, options = {}) {
|
|
8
|
+
const { filterNullish = true } = options;
|
|
9
|
+
if (field instanceof z.ZodUnion) {
|
|
10
|
+
const unionOptions = [...field.def.options];
|
|
11
|
+
const filteredOptions = filterNullish ? unionOptions.filter(
|
|
12
|
+
(option) => !(option instanceof z.ZodNull) && !(option instanceof z.ZodUndefined)
|
|
13
|
+
) : unionOptions;
|
|
14
|
+
return {
|
|
15
|
+
field: filteredOptions[0] || field,
|
|
16
|
+
union: filteredOptions
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
field,
|
|
21
|
+
union: [field]
|
|
22
|
+
};
|
|
23
|
+
}
|
|
7
24
|
var getPrimitiveType = (field) => {
|
|
8
25
|
if (field instanceof z.ZodArray) {
|
|
9
26
|
return field;
|
|
@@ -11,6 +28,9 @@ var getPrimitiveType = (field) => {
|
|
|
11
28
|
if (canUnwrap(field)) {
|
|
12
29
|
return getPrimitiveType(field.unwrap());
|
|
13
30
|
}
|
|
31
|
+
if (field instanceof z.ZodUnion) {
|
|
32
|
+
return getPrimitiveType(unwrapUnion(field).field);
|
|
33
|
+
}
|
|
14
34
|
return field;
|
|
15
35
|
};
|
|
16
36
|
function removeDefault(field) {
|
|
@@ -28,15 +48,12 @@ function removeDefault(field) {
|
|
|
28
48
|
}
|
|
29
49
|
return field;
|
|
30
50
|
}
|
|
31
|
-
var
|
|
32
|
-
const undefinedResult = field.safeParse(void 0).success;
|
|
33
|
-
if (undefinedResult) {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
51
|
+
var requiresValidInput = (field) => {
|
|
36
52
|
const defaultRemovedField = removeDefault(field);
|
|
37
53
|
if (!(defaultRemovedField instanceof z.ZodType)) {
|
|
38
54
|
return false;
|
|
39
55
|
}
|
|
56
|
+
const undefinedResult = defaultRemovedField.safeParse(void 0).success;
|
|
40
57
|
const nullResult = defaultRemovedField.safeParse(null).success;
|
|
41
58
|
const primitiveType = getPrimitiveType(defaultRemovedField);
|
|
42
59
|
const emptyStringResult = primitiveType.type === "string" && defaultRemovedField.safeParse("").success;
|
|
@@ -67,6 +84,6 @@ function getSchemaDefaults(schema) {
|
|
|
67
84
|
return defaults;
|
|
68
85
|
}
|
|
69
86
|
|
|
70
|
-
export { canUnwrap,
|
|
87
|
+
export { canUnwrap, extractDefault, getPrimitiveType, getSchemaDefaults, removeDefault, requiresValidInput, unwrapUnion };
|
|
71
88
|
//# sourceMappingURL=index.mjs.map
|
|
72
89
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/schema.ts","../src/defaults.ts"],"names":["z2"],"mappings":"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","file":"index.mjs","sourcesContent":["import * as z from 'zod';\n\n/**\n * Type representing a Zod type that has an unwrap method\n */\ntype Unwrappable = { unwrap: () => z.ZodTypeAny };\n\n/**\n * Type guard to check if a Zod field can be unwrapped (has wrapper types like optional, nullable, default).\n *\n * This checks whether a Zod type has an `unwrap()` method, which is present on wrapper types\n * like `ZodOptional`, `ZodNullable`, `ZodDefault`, and others.\n *\n * @param field - The Zod field to check\n * @returns True if the field has an unwrap method, false otherwise\n *\n * @example\n * ```typescript\n * const optionalField = z.string().optional();\n * console.log(canUnwrap(optionalField)); // true\n *\n * const plainField = z.string();\n * console.log(canUnwrap(plainField)); // false\n * ```\n *\n * @since 0.1.0\n */\nexport function canUnwrap(\n field: z.ZodTypeAny,\n): field is z.ZodTypeAny & Unwrappable {\n return 'unwrap' in field && typeof field.unwrap === 'function';\n}\n\n/**\n * Gets the underlying primitive type of a Zod field by recursively unwrapping wrapper types.\n *\n * This function removes wrapper layers (optional, nullable, default) to reveal the base type.\n * **Important:** It stops at array types without unwrapping them, treating arrays as primitives.\n *\n * @template T - The Zod type to unwrap\n * @param field - The Zod field to unwrap\n * @returns The unwrapped primitive Zod type\n *\n * @example\n * Unwrapping to string primitive\n * ```typescript\n * const field = z.string().optional().nullable();\n * const primitive = getPrimitiveType(field);\n * // Result: z.string() (unwrapped all wrappers)\n * ```\n *\n * @example\n * Stopping at array type\n * ```typescript\n * const field = z.array(z.string()).optional();\n * const primitive = getPrimitiveType(field);\n * // Result: z.array(z.string()) (stops at array, doesn't unwrap it)\n * ```\n *\n * @example\n * Unwrapping defaults\n * ```typescript\n * const field = z.number().default(0).optional();\n * const primitive = getPrimitiveType(field);\n * // Result: z.number()\n * ```\n *\n * @see {@link canUnwrap} for checking if a field can be unwrapped\n * @since 0.1.0\n */\nexport const getPrimitiveType = <T extends z.ZodTypeAny>(\n field: T,\n): z.ZodTypeAny => {\n // Stop at arrays - don't unwrap them\n if (field instanceof z.ZodArray) {\n return field;\n }\n\n if (canUnwrap(field)) {\n return getPrimitiveType(field.unwrap());\n }\n\n return field;\n};\n\ntype StripZodDefault<T> = T extends z.ZodDefault<infer Inner>\n ? StripZodDefault<Inner>\n : T extends z.ZodOptional<infer Inner>\n ? z.ZodOptional<StripZodDefault<Inner>>\n : T extends z.ZodNullable<infer Inner>\n ? z.ZodNullable<StripZodDefault<Inner>>\n : T;\n\n/**\n * Removes default values from a Zod field while preserving other wrapper types.\n *\n * This function recursively removes `ZodDefault` wrappers from a field, while maintaining\n * `optional()` and `nullable()` wrappers. Useful for scenarios where you want to check\n * field requirements without considering default values.\n *\n * @template T - The Zod type to process\n * @param field - The Zod field to remove defaults from\n * @returns The field without defaults but with optional/nullable preserved\n *\n * @example\n * Removing simple default\n * ```typescript\n * const field = z.string().default('hello');\n * const withoutDefault = removeDefault(field);\n * // Result: z.string()\n * ```\n *\n * @example\n * Preserving optional wrapper\n * ```typescript\n * const field = z.string().default('hello').optional();\n * const withoutDefault = removeDefault(field);\n * // Result: z.string().optional()\n * ```\n *\n * @example\n * Nested defaults\n * ```typescript\n * const field = z.string().default('inner').nullable().default('outer');\n * const withoutDefault = removeDefault(field);\n * // Result: z.string().nullable()\n * ```\n *\n * @see {@link checkIfFieldIsRequired} for usage with requirement checking\n * @since 0.1.0\n */\nexport function removeDefault<T extends z.ZodType>(\n field: T,\n): StripZodDefault<T> {\n if (field instanceof z.ZodDefault) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return field.unwrap() as StripZodDefault<T>;\n }\n\n if ('innerType' in field.def && field.def.innerType instanceof z.ZodType) {\n const inner = removeDefault(field.def.innerType);\n // Reconstruct the wrapper with the modified inner type\n if (field instanceof z.ZodOptional) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return inner.optional() as unknown as StripZodDefault<T>;\n }\n if (field instanceof z.ZodNullable) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return inner.nullable() as unknown as StripZodDefault<T>;\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return field as StripZodDefault<T>;\n}\n\n/**\n * Checks if a Zod field is truly required by testing multiple acceptance criteria.\n *\n * A field is considered **not required** if it accepts any of the following:\n * - `undefined` (via `.optional()` or `.default()`)\n * - `null` (via `.nullable()`)\n * - Empty string (plain `z.string()` without `.min(1)` or `.nonempty()`)\n * - Empty array (plain `z.array()` without `.min(1)` or `.nonempty()`)\n *\n * **Note:** Fields with `.default()` are considered not required since they'll have a value\n * even if the user doesn't provide one.\n *\n * @template T - The Zod type to check\n * @param field - The Zod field to check for required status\n * @returns True if the field is required, false otherwise\n *\n * @example\n * Required field\n * ```typescript\n * const field = z.string().min(1);\n * console.log(checkIfFieldIsRequired(field)); // true\n * ```\n *\n * @example\n * Optional field (not required)\n * ```typescript\n * const field = z.string().optional();\n * console.log(checkIfFieldIsRequired(field)); // false\n * ```\n *\n * @example\n * Field with default (not required)\n * ```typescript\n * const field = z.string().default('hello');\n * console.log(checkIfFieldIsRequired(field)); // false\n * ```\n *\n * @example\n * String without min length (not required - accepts empty string)\n * ```typescript\n * const field = z.string();\n * console.log(checkIfFieldIsRequired(field)); // false\n * ```\n *\n * @example\n * String with nonempty (required)\n * ```typescript\n * const field = z.string().nonempty();\n * console.log(checkIfFieldIsRequired(field)); // true\n * ```\n *\n * @example\n * Nullable field (not required)\n * ```typescript\n * const field = z.number().nullable();\n * console.log(checkIfFieldIsRequired(field)); // false\n * ```\n *\n * @see {@link removeDefault} for understanding how defaults are handled\n * @see {@link getPrimitiveType} for understanding type unwrapping\n * @since 0.1.0\n */\nexport const checkIfFieldIsRequired = <T extends z.ZodType>(field: T) => {\n // First check the original field for undefined - this catches fields with defaults\n const undefinedResult = field.safeParse(undefined).success;\n if (undefinedResult) {\n return false;\n }\n\n const defaultRemovedField = removeDefault(field);\n\n if (!(defaultRemovedField instanceof z.ZodType)) {\n return false;\n }\n\n // Check if field accepts null (nullable)\n const nullResult = defaultRemovedField.safeParse(null).success;\n\n const primitiveType = getPrimitiveType(defaultRemovedField);\n\n const emptyStringResult =\n primitiveType.type === 'string' &&\n defaultRemovedField.safeParse('').success;\n\n const emptyArrayResult =\n primitiveType.type === 'array' && defaultRemovedField.safeParse([]).success;\n\n return (\n !undefinedResult && !nullResult && !emptyStringResult && !emptyArrayResult\n );\n};\n","import * as z from 'zod';\nimport { canUnwrap } from './schema';\nimport type { Simplify } from './types';\n\n/**\n * Extracts the default value from a Zod field, recursively unwrapping optional and nullable layers.\n *\n * This function traverses through wrapper types (like `ZodOptional`, `ZodNullable`) to find\n * the underlying `ZodDefault` and returns its default value. If no default is found, returns `undefined`.\n *\n * @template T - The Zod type to extract default from\n * @param field - The Zod field to extract default from\n * @returns The default value if present, undefined otherwise\n *\n * @example\n * Basic usage with default value\n * ```typescript\n * const field = z.string().default('hello');\n * const defaultValue = extractDefault(field);\n * // Result: 'hello'\n * ```\n *\n * @example\n * Unwrapping optional/nullable layers\n * ```typescript\n * const field = z.string().default('world').optional();\n * const defaultValue = extractDefault(field);\n * // Result: 'world' (unwraps optional to find default)\n * ```\n *\n * @example\n * Field without default\n * ```typescript\n * const field = z.string().optional();\n * const defaultValue = extractDefault(field);\n * // Result: undefined\n * ```\n *\n * @see {@link getSchemaDefaults} for extracting defaults from entire schemas\n * @since 0.1.0\n */\nexport function extractDefault<T extends z.ZodTypeAny>(\n field: T,\n): z.infer<T> | undefined {\n if (field instanceof z.ZodDefault) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return field.def.defaultValue as z.infer<T>;\n }\n\n if (canUnwrap(field)) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return extractDefault(field.unwrap()) as z.infer<T>;\n }\n\n return undefined;\n}\n\n/**\n * Extracts default values from a Zod object schema while skipping fields without defaults.\n *\n * This function recursively traverses the schema and collects all fields that have\n * explicit default values defined. Fields without defaults are excluded from the result.\n *\n * **Important:** Nested defaults are NOT extracted unless the parent object also has\n * an explicit `.default()`. This is by design to match Zod's default value behavior.\n *\n * @template T - The Zod object schema type\n * @param schema - The Zod object schema to extract defaults from\n * @returns A partial object containing only fields with default values\n *\n * @example\n * Basic usage\n * ```typescript\n * const schema = z.object({\n * name: z.string().default('John'),\n * age: z.number(), // no default - will be skipped\n * email: z.string().email().optional(),\n * });\n *\n * const defaults = getSchemaDefaults(schema);\n * // Result: { name: 'John' }\n * ```\n *\n * @example\n * Nested objects with defaults\n * ```typescript\n * const schema = z.object({\n * user: z.object({\n * name: z.string().default('Guest')\n * }).default({ name: 'Guest' }), // ✅ Extracted because parent has .default()\n *\n * settings: z.object({\n * theme: z.string().default('light')\n * }), // ❌ NOT extracted - parent has no .default()\n * });\n *\n * const defaults = getSchemaDefaults(schema);\n * // Result: { user: { name: 'Guest' } }\n * ```\n *\n * @example\n * Unwrapping optional/nullable fields\n * ```typescript\n * const schema = z.object({\n * title: z.string().default('Untitled').optional(),\n * count: z.number().default(0).nullable(),\n * });\n *\n * const defaults = getSchemaDefaults(schema);\n * // Result: { title: 'Untitled', count: 0 }\n * ```\n *\n * @see {@link extractDefault} for extracting defaults from individual fields\n * @since 0.1.0\n */\nexport function getSchemaDefaults<T extends z.ZodObject>(\n schema: T,\n): Simplify<Partial<z.infer<T>>> {\n const defaults: Record<string, unknown> = {};\n\n for (const key in schema.shape) {\n const field = schema.shape[key];\n if (!field) continue;\n\n // Check if this field has an explicit default value\n const defaultValue = extractDefault(field);\n if (defaultValue !== undefined) {\n defaults[key] = defaultValue;\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return defaults as Partial<z.infer<T>>;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/schema.ts","../src/defaults.ts"],"names":["z2"],"mappings":"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","file":"index.mjs","sourcesContent":["import * as z from 'zod';\n\n/**\n * Type representing a Zod type that has an unwrap method\n */\ntype Unwrappable = { unwrap: () => z.ZodTypeAny };\n\n/**\n * Type guard to check if a Zod field can be unwrapped (has wrapper types like optional, nullable, default).\n *\n * This checks whether a Zod type has an `unwrap()` method, which is present on wrapper types\n * like `ZodOptional`, `ZodNullable`, `ZodDefault`, and others.\n *\n * @param field - The Zod field to check\n * @returns True if the field has an unwrap method, false otherwise\n *\n * @example\n * ```typescript\n * const optionalField = z.string().optional();\n * console.log(canUnwrap(optionalField)); // true\n *\n * const plainField = z.string();\n * console.log(canUnwrap(plainField)); // false\n * ```\n *\n * @since 0.1.0\n */\nexport function canUnwrap(\n field: z.ZodTypeAny,\n): field is z.ZodTypeAny & Unwrappable {\n return 'unwrap' in field && typeof field.unwrap === 'function';\n}\n\n/**\n * Unwraps a ZodUnion type and returns the first field and all union options.\n *\n * This function extracts the individual type options from a union type.\n * By default, it filters out `ZodNull` and `ZodUndefined` types, returning only\n * the meaningful type options. You can disable this filtering to get all options.\n *\n * @template T - The Zod type to unwrap\n * @param field - The Zod field (union or single type)\n * @param options - Configuration options\n * @param options.filterNullish - Whether to filter out null and undefined types (default: true)\n * @returns Object with `field` (first option) and `union` (all options array)\n *\n * @example\n * Basic union unwrapping\n * ```typescript\n * const field = z.union([z.string(), z.number()]);\n * const result = unwrapUnion(field);\n * // Result: { field: z.string(), union: [z.string(), z.number()] }\n * ```\n *\n * @example\n * Union with null (filtered by default)\n * ```typescript\n * const field = z.union([z.string(), z.null()]);\n * const result = unwrapUnion(field);\n * // Result: { field: z.string(), union: [z.string()] }\n * ```\n *\n * @example\n * Union with null (keep all options)\n * ```typescript\n * const field = z.union([z.string(), z.null()]);\n * const result = unwrapUnion(field, { filterNullish: false });\n * // Result: { field: z.string(), union: [z.string(), z.null()] }\n * ```\n *\n * @example\n * Non-union type (returns single field)\n * ```typescript\n * const field = z.string();\n * const result = unwrapUnion(field);\n * // Result: { field: z.string(), union: [z.string()] }\n * ```\n *\n * @example\n * Nullable as union\n * ```typescript\n * const field = z.string().nullable(); // This is z.union([z.string(), z.null()])\n * const result = unwrapUnion(field);\n * // Result: { field: z.string(), union: [z.string()] } (null filtered out)\n * ```\n *\n * @example\n * Using the first field for type checking\n * ```typescript\n * const field = z.union([z.string(), z.number()]);\n * const { field: firstField, union } = unwrapUnion(field);\n * if (firstField instanceof z.ZodString) {\n * console.log('First type is string');\n * }\n * ```\n *\n * @see {@link getPrimitiveType} for unwrapping wrapper types\n * @since 0.1.0\n */\nexport function unwrapUnion<T extends z.ZodTypeAny>(\n field: T,\n options: { filterNullish?: boolean } = {},\n): { field: z.ZodTypeAny; union: z.ZodTypeAny[] } {\n const { filterNullish = true } = options;\n\n if (field instanceof z.ZodUnion) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n const unionOptions = [...field.def.options] as z.ZodTypeAny[];\n\n const filteredOptions = filterNullish\n ? unionOptions.filter(\n (option) =>\n !(option instanceof z.ZodNull) &&\n !(option instanceof z.ZodUndefined),\n )\n : unionOptions;\n\n return {\n field: filteredOptions[0] || field,\n union: filteredOptions,\n };\n }\n\n // If it's not a union, return the field itself\n return {\n field,\n union: [field],\n };\n}\n\n/**\n * Gets the underlying primitive type of a Zod field by recursively unwrapping wrapper types.\n *\n * This function removes wrapper layers (optional, nullable, default) to reveal the base type.\n * **Important:** It stops at array types without unwrapping them, treating arrays as primitives.\n *\n * @template T - The Zod type to unwrap\n * @param field - The Zod field to unwrap\n * @returns The unwrapped primitive Zod type\n *\n * @example\n * Unwrapping to string primitive\n * ```typescript\n * const field = z.string().optional().nullable();\n * const primitive = getPrimitiveType(field);\n * // Result: z.string() (unwrapped all wrappers)\n * ```\n *\n * @example\n * Stopping at array type\n * ```typescript\n * const field = z.array(z.string()).optional();\n * const primitive = getPrimitiveType(field);\n * // Result: z.array(z.string()) (stops at array, doesn't unwrap it)\n * ```\n *\n * @example\n * Unwrapping defaults\n * ```typescript\n * const field = z.number().default(0).optional();\n * const primitive = getPrimitiveType(field);\n * // Result: z.number()\n * ```\n *\n * @see {@link canUnwrap} for checking if a field can be unwrapped\n * @since 0.1.0\n */\nexport const getPrimitiveType = <T extends z.ZodType>(\n field: T,\n): z.ZodTypeAny => {\n // Stop at arrays - don't unwrap them\n if (field instanceof z.ZodArray) {\n return field;\n }\n\n if (canUnwrap(field)) {\n return getPrimitiveType(field.unwrap());\n }\n\n if (field instanceof z.ZodUnion) {\n return getPrimitiveType(unwrapUnion(field).field);\n }\n\n return field;\n};\n\ntype StripZodDefault<T> = T extends z.ZodDefault<infer Inner>\n ? StripZodDefault<Inner>\n : T extends z.ZodOptional<infer Inner>\n ? z.ZodOptional<StripZodDefault<Inner>>\n : T extends z.ZodNullable<infer Inner>\n ? z.ZodNullable<StripZodDefault<Inner>>\n : T;\n\n/**\n * Removes default values from a Zod field while preserving other wrapper types.\n *\n * This function recursively removes `ZodDefault` wrappers from a field, while maintaining\n * `optional()` and `nullable()` wrappers. Useful for scenarios where you want to check\n * field requirements without considering default values.\n *\n * @template T - The Zod type to process\n * @param field - The Zod field to remove defaults from\n * @returns The field without defaults but with optional/nullable preserved\n *\n * @example\n * Removing simple default\n * ```typescript\n * const field = z.string().default('hello');\n * const withoutDefault = removeDefault(field);\n * // Result: z.string()\n * ```\n *\n * @example\n * Preserving optional wrapper\n * ```typescript\n * const field = z.string().default('hello').optional();\n * const withoutDefault = removeDefault(field);\n * // Result: z.string().optional()\n * ```\n *\n * @example\n * Nested defaults\n * ```typescript\n * const field = z.string().default('inner').nullable().default('outer');\n * const withoutDefault = removeDefault(field);\n * // Result: z.string().nullable()\n * ```\n *\n * @see {@link requiresValidInput} for usage with requirement checking\n * @since 0.1.0\n */\nexport function removeDefault<T extends z.ZodType>(\n field: T,\n): StripZodDefault<T> {\n if (field instanceof z.ZodDefault) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return field.unwrap() as StripZodDefault<T>;\n }\n\n if ('innerType' in field.def && field.def.innerType instanceof z.ZodType) {\n const inner = removeDefault(field.def.innerType);\n // Reconstruct the wrapper with the modified inner type\n if (field instanceof z.ZodOptional) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return inner.optional() as unknown as StripZodDefault<T>;\n }\n if (field instanceof z.ZodNullable) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return inner.nullable() as unknown as StripZodDefault<T>;\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return field as StripZodDefault<T>;\n}\n\n/**\n * Determines if a field will show validation errors when the user submits empty or invalid input.\n *\n * This is useful for form UIs to indicate which fields require valid user input (e.g., showing\n * asterisks, validation states). The key insight: **defaults are just initial values** - they\n * don't prevent validation errors if the user clears the field.\n *\n * **Real-world example:**\n * ```typescript\n * // Marital status field with default but validation rules\n * const maritalStatus = z.string().min(1).default('single');\n *\n * // Initial: field shows \"single\" (from default)\n * // User deletes the value → field is now empty string\n * // User submits form → validation fails because .min(1) rejects empty strings\n * // requiresValidInput(maritalStatus) → true (shows * indicator, validation error)\n * ```\n *\n * **How it works:**\n * 1. Removes `.default()` wrappers (defaults are initial values, not validation rules)\n * 2. Tests if the underlying schema accepts empty/invalid input:\n * - `undefined` (via `.optional()`)\n * - `null` (via `.nullable()`)\n * - Empty string (plain `z.string()` without `.min(1)` or `.nonempty()`)\n * - Empty array (plain `z.array()` without `.min(1)` or `.nonempty()`)\n * 3. Returns `true` if validation will fail, `false` if empty input is accepted\n *\n * @template T - The Zod type to check\n * @param field - The Zod field to check\n * @returns True if the field will show validation errors on empty/invalid input, false otherwise\n *\n * @example\n * User name field - required, no default\n * ```typescript\n * const userName = z.string().min(1);\n * requiresValidInput(userName); // true - will error if user submits empty\n * ```\n *\n * @example\n * Marital status - required WITH default\n * ```typescript\n * const maritalStatus = z.string().min(1).default('single');\n * requiresValidInput(maritalStatus); // true - will error if user clears and submits\n * ```\n *\n * @example\n * Age with default - requires valid input\n * ```typescript\n * const age = z.number().default(0);\n * requiresValidInput(age); // true - numbers reject empty strings\n * ```\n *\n * @example\n * Optional bio field - doesn't require input\n * ```typescript\n * const bio = z.string().optional();\n * requiresValidInput(bio); // false - user can leave empty\n * ```\n *\n * @example\n * String with default but NO validation - doesn't require input\n * ```typescript\n * const notes = z.string().default('N/A');\n * requiresValidInput(notes); // false - plain z.string() accepts empty strings\n * ```\n *\n * @example\n * Nullable field - doesn't require input\n * ```typescript\n * const middleName = z.string().nullable();\n * requiresValidInput(middleName); // false - user can leave null\n * ```\n *\n * @see {@link removeDefault} for understanding how defaults are handled\n * @see {@link getPrimitiveType} for understanding type unwrapping\n * @since 0.1.0\n */\nexport const requiresValidInput = <T extends z.ZodType>(field: T) => {\n const defaultRemovedField = removeDefault(field);\n if (!(defaultRemovedField instanceof z.ZodType)) {\n return false;\n }\n\n const undefinedResult = defaultRemovedField.safeParse(undefined).success;\n\n // Check if field accepts null (nullable)\n const nullResult = defaultRemovedField.safeParse(null).success;\n\n const primitiveType = getPrimitiveType(defaultRemovedField);\n\n const emptyStringResult =\n primitiveType.type === 'string' &&\n defaultRemovedField.safeParse('').success;\n\n const emptyArrayResult =\n primitiveType.type === 'array' && defaultRemovedField.safeParse([]).success;\n\n return (\n !undefinedResult && !nullResult && !emptyStringResult && !emptyArrayResult\n );\n};\n","import * as z from 'zod';\nimport { canUnwrap } from './schema';\nimport type { Simplify } from './types';\n\n/**\n * Extracts the default value from a Zod field, recursively unwrapping optional and nullable layers.\n *\n * This function traverses through wrapper types (like `ZodOptional`, `ZodNullable`) to find\n * the underlying `ZodDefault` and returns its default value. If no default is found, returns `undefined`.\n *\n * @template T - The Zod type to extract default from\n * @param field - The Zod field to extract default from\n * @returns The default value if present, undefined otherwise\n *\n * @example\n * Basic usage with default value\n * ```typescript\n * const field = z.string().default('hello');\n * const defaultValue = extractDefault(field);\n * // Result: 'hello'\n * ```\n *\n * @example\n * Unwrapping optional/nullable layers\n * ```typescript\n * const field = z.string().default('world').optional();\n * const defaultValue = extractDefault(field);\n * // Result: 'world' (unwraps optional to find default)\n * ```\n *\n * @example\n * Field without default\n * ```typescript\n * const field = z.string().optional();\n * const defaultValue = extractDefault(field);\n * // Result: undefined\n * ```\n *\n * @see {@link getSchemaDefaults} for extracting defaults from entire schemas\n * @since 0.1.0\n */\nexport function extractDefault<T extends z.ZodTypeAny>(\n field: T,\n): z.infer<T> | undefined {\n if (field instanceof z.ZodDefault) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return field.def.defaultValue as z.infer<T>;\n }\n\n if (canUnwrap(field)) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return extractDefault(field.unwrap()) as z.infer<T>;\n }\n\n return undefined;\n}\n\n/**\n * Extracts default values from a Zod object schema while skipping fields without defaults.\n *\n * This function recursively traverses the schema and collects all fields that have\n * explicit default values defined. Fields without defaults are excluded from the result.\n *\n * **Important:** Nested defaults are NOT extracted unless the parent object also has\n * an explicit `.default()`. This is by design to match Zod's default value behavior.\n *\n * @template T - The Zod object schema type\n * @param schema - The Zod object schema to extract defaults from\n * @returns A partial object containing only fields with default values\n *\n * @example\n * Basic usage\n * ```typescript\n * const schema = z.object({\n * name: z.string().default('John'),\n * age: z.number(), // no default - will be skipped\n * email: z.string().email().optional(),\n * });\n *\n * const defaults = getSchemaDefaults(schema);\n * // Result: { name: 'John' }\n * ```\n *\n * @example\n * Nested objects with defaults\n * ```typescript\n * const schema = z.object({\n * user: z.object({\n * name: z.string().default('Guest')\n * }).default({ name: 'Guest' }), // ✅ Extracted because parent has .default()\n *\n * settings: z.object({\n * theme: z.string().default('light')\n * }), // ❌ NOT extracted - parent has no .default()\n * });\n *\n * const defaults = getSchemaDefaults(schema);\n * // Result: { user: { name: 'Guest' } }\n * ```\n *\n * @example\n * Unwrapping optional/nullable fields\n * ```typescript\n * const schema = z.object({\n * title: z.string().default('Untitled').optional(),\n * count: z.number().default(0).nullable(),\n * });\n *\n * const defaults = getSchemaDefaults(schema);\n * // Result: { title: 'Untitled', count: 0 }\n * ```\n *\n * @see {@link extractDefault} for extracting defaults from individual fields\n * @since 0.1.0\n */\nexport function getSchemaDefaults<T extends z.ZodObject>(\n schema: T,\n): Simplify<Partial<z.infer<T>>> {\n const defaults: Record<string, unknown> = {};\n\n for (const key in schema.shape) {\n const field = schema.shape[key];\n if (!field) continue;\n\n // Check if this field has an explicit default value\n const defaultValue = extractDefault(field);\n if (defaultValue !== undefined) {\n defaults[key] = defaultValue;\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return defaults as Partial<z.infer<T>>;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zod-utils/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Pure TypeScript utilities for Zod schema manipulation and default extraction",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
"test": "vitest run",
|
|
29
29
|
"test:watch": "vitest",
|
|
30
30
|
"test:coverage": "vitest run --coverage",
|
|
31
|
+
"bench": "vitest bench --run",
|
|
32
|
+
"bench:watch": "vitest bench",
|
|
31
33
|
"prepublishOnly": "npm run build"
|
|
32
34
|
},
|
|
33
35
|
"keywords": [
|