@zod-utils/core 0.2.0 → 0.5.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 CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@zod-utils/core.svg)](https://www.npmjs.com/package/@zod-utils/core)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/@zod-utils/core.svg)](https://www.npmjs.com/package/@zod-utils/core)
5
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/@zod-utils/core)](https://bundlephobia.com/package/@zod-utils/core)
5
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
7
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue.svg)](https://www.typescriptlang.org/)
7
8
  [![CI](https://github.com/thu-san/zod-utils/workflows/CI/badge.svg)](https://github.com/thu-san/zod-utils/actions)
@@ -15,10 +16,15 @@ 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 required fields** - Determine if fields are required
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.)
22
28
  - 🔧 **Schema utilities** - Unwrap and manipulate schema types
23
29
  - 📦 **Zero dependencies** - Only requires Zod as a peer dependency
24
30
  - 🌐 **Universal** - Works in Node.js, browsers, and any TypeScript project
@@ -67,48 +73,64 @@ const defaults = getSchemaDefaults(schema);
67
73
 
68
74
  ---
69
75
 
70
- ### `checkIfFieldIsRequired(field)`
76
+ ### `requiresValidInput(field)`
71
77
 
72
- Check if a Zod field is required. Returns `false` if the field accepts any of:
78
+ 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).
73
79
 
74
- - `undefined` (via `.optional()` or `.default()`)
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()`)
80
+ **Key insight:** Defaults are just initial values - they don't prevent validation errors if the user clears the field.
81
+
82
+ **Real-world example:**
78
83
 
79
84
  ```typescript
80
- import { checkIfFieldIsRequired } from "@zod-utils/core";
81
- import { z } from "zod";
85
+ // Marital status with default but validation rules
86
+ const maritalStatus = z.string().min(1).default('single');
87
+
88
+ // What happens in the form:
89
+ // 1. Initial: field shows "single" (from default)
90
+ // 2. User deletes the value → empty string
91
+ // 3. User submits form → validation fails (.min(1) rejects empty)
92
+ // 4. requiresValidInput(maritalStatus) → true (show *, show error)
93
+ ```
94
+
95
+ **How it works:**
96
+
97
+ 1. Removes `.default()` wrappers (defaults ≠ validation rules)
98
+ 2. Tests if underlying schema accepts empty/invalid input:
99
+ - `undefined` (via `.optional()`)
100
+ - `null` (via `.nullable()`)
101
+ - Empty string (plain `z.string()`)
102
+ - Empty array (plain `z.array()`)
103
+ 3. Returns `true` if validation will fail on empty input
82
104
 
83
- // Required fields - return true
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();
105
+ **Examples:**
88
106
 
89
- checkIfFieldIsRequired(requiredString); // true
90
- checkIfFieldIsRequired(nonemptyString); // true
91
- checkIfFieldIsRequired(requiredArray); // true
92
- checkIfFieldIsRequired(nonemptyArray); // true
107
+ ```typescript
108
+ import { requiresValidInput } from "@zod-utils/core";
109
+ import { z } from "zod";
93
110
 
94
- // Fields accepting undefined - return false
95
- const optionalField = z.string().optional();
96
- const fieldWithDefault = z.string().default("hello");
111
+ // User name - required, no default
112
+ const userName = z.string().min(1);
113
+ requiresValidInput(userName); // true - will error if empty
97
114
 
98
- checkIfFieldIsRequired(optionalField); // false
99
- checkIfFieldIsRequired(fieldWithDefault); // false
115
+ // Marital status - required WITH default
116
+ const maritalStatus = z.string().min(1).default('single');
117
+ requiresValidInput(maritalStatus); // true - will error if user clears it
100
118
 
101
- // Fields accepting empty values - return false
102
- const emptyStringAllowed = z.string();
103
- const emptyArrayAllowed = z.array(z.string());
119
+ // Age with default - requires valid input
120
+ const age = z.number().default(0);
121
+ requiresValidInput(age); // true - numbers reject empty strings
104
122
 
105
- checkIfFieldIsRequired(emptyStringAllowed); // false
106
- checkIfFieldIsRequired(emptyArrayAllowed); // false
123
+ // Optional bio - doesn't require input
124
+ const bio = z.string().optional();
125
+ requiresValidInput(bio); // false - user can leave empty
107
126
 
108
- // Fields accepting null - return false
109
- const nullableField = z.string().nullable();
127
+ // Notes with default but NO validation
128
+ const notes = z.string().default('N/A');
129
+ requiresValidInput(notes); // false - plain z.string() accepts empty
110
130
 
111
- checkIfFieldIsRequired(nullableField); // false
131
+ // Nullable middle name
132
+ const middleName = z.string().nullable();
133
+ requiresValidInput(middleName); // false - user can leave null
112
134
  ```
113
135
 
114
136
  ---
@@ -152,17 +174,100 @@ withoutDefault.parse(undefined); // throws error
152
174
 
153
175
  ### `extractDefault(field)`
154
176
 
155
- Extract the default value from a Zod field (recursively unwraps optional/nullable).
177
+ Extract the default value from a Zod field (recursively unwraps optional/nullable/union layers).
178
+
179
+ **Union handling:** For union types, extracts the default from the first option. If the first option has no default, returns `undefined` (defaults in other union options are not checked).
156
180
 
157
181
  ```typescript
158
182
  import { extractDefault } from "@zod-utils/core";
159
183
  import { z } from "zod";
160
184
 
185
+ // Basic usage
161
186
  const field = z.string().optional().default("hello");
162
187
  extractDefault(field); // 'hello'
163
188
 
164
189
  const noDefault = z.string();
165
190
  extractDefault(noDefault); // undefined
191
+
192
+ // Union with default in first option
193
+ const unionField = z.union([z.string().default('hello'), z.number()]);
194
+ extractDefault(unionField); // 'hello'
195
+
196
+ // Union with default in second option (only checks first)
197
+ const unionField2 = z.union([z.string(), z.number().default(42)]);
198
+ extractDefault(unionField2); // undefined
199
+
200
+ // Union wrapped in optional
201
+ const wrappedUnion = z.union([z.string().default('test'), z.number()]).optional();
202
+ extractDefault(wrappedUnion); // 'test'
203
+ ```
204
+
205
+ ---
206
+
207
+ ### `getFieldChecks(field)`
208
+
209
+ Extract all validation check definitions from a Zod schema field. Returns Zod's raw check definition objects directly, including all properties like `check`, `minimum`, `maximum`, `value`, `inclusive`, `format`, `pattern`, etc.
210
+
211
+ **Automatically unwraps:** optional, nullable, and default layers. For unions, checks only the first option.
212
+
213
+ **Supported check types:** Returns any of 21 check types:
214
+ - **Length checks**: `min_length`, `max_length`, `length_equals` (strings, arrays)
215
+ - **Size checks**: `min_size`, `max_size`, `size_equals` (files, sets, maps)
216
+ - **Numeric checks**: `greater_than`, `less_than`, `multiple_of`
217
+ - **Format checks**: `number_format`, `bigint_format`, `string_format` (email, url, uuid, etc.)
218
+ - **String pattern checks**: `regex`, `lowercase`, `uppercase`, `includes`, `starts_with`, `ends_with`
219
+ - **Other checks**: `property`, `mime_type`, `overwrite`
220
+
221
+ ```typescript
222
+ import { getFieldChecks } from "@zod-utils/core";
223
+ import { z } from "zod";
224
+
225
+ // String with length constraints
226
+ const username = z.string().min(3).max(20);
227
+ const checks = getFieldChecks(username);
228
+ // [
229
+ // { check: 'min_length', minimum: 3, when: [Function], ... },
230
+ // { check: 'max_length', maximum: 20, when: [Function], ... }
231
+ // ]
232
+
233
+ // Number with range constraints
234
+ const age = z.number().min(18).max(120);
235
+ const checks = getFieldChecks(age);
236
+ // [
237
+ // { check: 'greater_than', value: 18, inclusive: true, ... },
238
+ // { check: 'less_than', value: 120, inclusive: true, ... }
239
+ // ]
240
+
241
+ // Array with item count constraints
242
+ const tags = z.array(z.string()).min(1).max(5);
243
+ const checks = getFieldChecks(tags);
244
+ // [
245
+ // { check: 'min_length', minimum: 1, ... },
246
+ // { check: 'max_length', maximum: 5, ... }
247
+ // ]
248
+
249
+ // String with format validation
250
+ const email = z.string().email();
251
+ const checks = getFieldChecks(email);
252
+ // [{ check: 'string_format', format: 'email', ... }]
253
+
254
+ // Unwrapping optional/nullable/default layers
255
+ const bio = z.string().min(10).max(500).optional();
256
+ const checks = getFieldChecks(bio);
257
+ // [
258
+ // { check: 'min_length', minimum: 10, ... },
259
+ // { check: 'max_length', maximum: 500, ... }
260
+ // ]
261
+
262
+ // No checks
263
+ const plainString = z.string();
264
+ getFieldChecks(plainString); // []
265
+ ```
266
+
267
+ **Type:** The return type is `ZodUnionCheck[]`, a union of all 21 Zod check definition types. You can also import the `ZodUnionCheck` type:
268
+
269
+ ```typescript
270
+ import { getFieldChecks, type ZodUnionCheck } from "@zod-utils/core";
166
271
  ```
167
272
 
168
273
  ---
package/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as z from 'zod';
2
+ import { $ZodCheckLessThanDef, $ZodCheckGreaterThanDef, $ZodCheckMultipleOfDef, $ZodCheckNumberFormatDef, $ZodCheckBigIntFormatDef, $ZodCheckMaxSizeDef, $ZodCheckMinSizeDef, $ZodCheckSizeEqualsDef, $ZodCheckMaxLengthDef, $ZodCheckMinLengthDef, $ZodCheckLengthEqualsDef, $ZodCheckStringFormatDef, $ZodCheckRegexDef, $ZodCheckLowerCaseDef, $ZodCheckUpperCaseDef, $ZodCheckIncludesDef, $ZodCheckStartsWithDef, $ZodCheckEndsWithDef, $ZodCheckPropertyDef, $ZodCheckMimeTypeDef, $ZodCheckOverwriteDef } from 'zod/v4/core';
2
3
 
3
4
  /**
4
5
  * Simplifies complex TypeScript types for better IDE hover tooltips and error messages.
@@ -45,11 +46,14 @@ type Simplify<T> = {
45
46
  } & {};
46
47
 
47
48
  /**
48
- * Extracts the default value from a Zod field, recursively unwrapping optional and nullable layers.
49
+ * Extracts the default value from a Zod field, recursively unwrapping optional, nullable, and union layers.
49
50
  *
50
- * This function traverses through wrapper types (like `ZodOptional`, `ZodNullable`) to find
51
+ * This function traverses through wrapper types (like `ZodOptional`, `ZodNullable`, `ZodUnion`) to find
51
52
  * the underlying `ZodDefault` and returns its default value. If no default is found, returns `undefined`.
52
53
  *
54
+ * **Union handling:** For union types, extracts the default from the first option. If the first option
55
+ * has no default, returns `undefined` (defaults in other union options are not checked).
56
+ *
53
57
  * @template T - The Zod type to extract default from
54
58
  * @param field - The Zod field to extract default from
55
59
  * @returns The default value if present, undefined otherwise
@@ -71,6 +75,22 @@ type Simplify<T> = {
71
75
  * ```
72
76
  *
73
77
  * @example
78
+ * Union with default in first option
79
+ * ```typescript
80
+ * const field = z.union([z.string().default('hello'), z.number()]);
81
+ * const defaultValue = extractDefault(field);
82
+ * // Result: 'hello' (extracts from first union option)
83
+ * ```
84
+ *
85
+ * @example
86
+ * Union with default in second option
87
+ * ```typescript
88
+ * const field = z.union([z.string(), z.number().default(42)]);
89
+ * const defaultValue = extractDefault(field);
90
+ * // Result: undefined (only checks first option)
91
+ * ```
92
+ *
93
+ * @example
74
94
  * Field without default
75
95
  * ```typescript
76
96
  * const field = z.string().optional();
@@ -169,6 +189,78 @@ type Unwrappable = {
169
189
  * @since 0.1.0
170
190
  */
171
191
  declare function canUnwrap(field: z.ZodTypeAny): field is z.ZodTypeAny & Unwrappable;
192
+ /**
193
+ * Unwraps a ZodUnion type and returns the first field and all union options.
194
+ *
195
+ * This function extracts the individual type options from a union type.
196
+ * By default, it filters out `ZodNull` and `ZodUndefined` types, returning only
197
+ * the meaningful type options. You can disable this filtering to get all options.
198
+ *
199
+ * @template T - The Zod type to unwrap
200
+ * @param field - The Zod field (union or single type)
201
+ * @param options - Configuration options
202
+ * @param options.filterNullish - Whether to filter out null and undefined types (default: true)
203
+ * @returns Object with `field` (first option) and `union` (all options array)
204
+ *
205
+ * @example
206
+ * Basic union unwrapping
207
+ * ```typescript
208
+ * const field = z.union([z.string(), z.number()]);
209
+ * const result = unwrapUnion(field);
210
+ * // Result: { field: z.string(), union: [z.string(), z.number()] }
211
+ * ```
212
+ *
213
+ * @example
214
+ * Union with null (filtered by default)
215
+ * ```typescript
216
+ * const field = z.union([z.string(), z.null()]);
217
+ * const result = unwrapUnion(field);
218
+ * // Result: { field: z.string(), union: [z.string()] }
219
+ * ```
220
+ *
221
+ * @example
222
+ * Union with null (keep all options)
223
+ * ```typescript
224
+ * const field = z.union([z.string(), z.null()]);
225
+ * const result = unwrapUnion(field, { filterNullish: false });
226
+ * // Result: { field: z.string(), union: [z.string(), z.null()] }
227
+ * ```
228
+ *
229
+ * @example
230
+ * Non-union type (returns single field)
231
+ * ```typescript
232
+ * const field = z.string();
233
+ * const result = unwrapUnion(field);
234
+ * // Result: { field: z.string(), union: [z.string()] }
235
+ * ```
236
+ *
237
+ * @example
238
+ * Nullable as union
239
+ * ```typescript
240
+ * const field = z.string().nullable(); // This is z.union([z.string(), z.null()])
241
+ * const result = unwrapUnion(field);
242
+ * // Result: { field: z.string(), union: [z.string()] } (null filtered out)
243
+ * ```
244
+ *
245
+ * @example
246
+ * Using the first field for type checking
247
+ * ```typescript
248
+ * const field = z.union([z.string(), z.number()]);
249
+ * const { field: firstField, union } = unwrapUnion(field);
250
+ * if (firstField instanceof z.ZodString) {
251
+ * console.log('First type is string');
252
+ * }
253
+ * ```
254
+ *
255
+ * @see {@link getPrimitiveType} for unwrapping wrapper types
256
+ * @since 0.1.0
257
+ */
258
+ declare function unwrapUnion<T extends z.ZodTypeAny>(field: T, options?: {
259
+ filterNullish?: boolean;
260
+ }): {
261
+ field: z.ZodTypeAny;
262
+ union: z.ZodTypeAny[];
263
+ };
172
264
  /**
173
265
  * Gets the underlying primitive type of a Zod field by recursively unwrapping wrapper types.
174
266
  *
@@ -206,7 +298,7 @@ declare function canUnwrap(field: z.ZodTypeAny): field is z.ZodTypeAny & Unwrapp
206
298
  * @see {@link canUnwrap} for checking if a field can be unwrapped
207
299
  * @since 0.1.0
208
300
  */
209
- declare const getPrimitiveType: <T extends z.ZodTypeAny>(field: T) => z.ZodTypeAny;
301
+ declare const getPrimitiveType: <T extends z.ZodType>(field: T) => z.ZodTypeAny;
210
302
  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
303
  /**
212
304
  * Removes default values from a Zod field while preserving other wrapper types.
@@ -243,72 +335,177 @@ type StripZodDefault<T> = T extends z.ZodDefault<infer Inner> ? StripZodDefault<
243
335
  * // Result: z.string().nullable()
244
336
  * ```
245
337
  *
246
- * @see {@link checkIfFieldIsRequired} for usage with requirement checking
338
+ * @see {@link requiresValidInput} for usage with requirement checking
247
339
  * @since 0.1.0
248
340
  */
249
341
  declare function removeDefault<T extends z.ZodType>(field: T): StripZodDefault<T>;
250
342
  /**
251
- * Checks if a Zod field is truly required by testing multiple acceptance criteria.
343
+ * Determines if a field will show validation errors when the user submits empty or invalid input.
344
+ *
345
+ * This is useful for form UIs to indicate which fields require valid user input (e.g., showing
346
+ * asterisks, validation states). The key insight: **defaults are just initial values** - they
347
+ * don't prevent validation errors if the user clears the field.
252
348
  *
253
- * A field is considered **not required** if it accepts any of the following:
254
- * - `undefined` (via `.optional()` or `.default()`)
255
- * - `null` (via `.nullable()`)
256
- * - Empty string (plain `z.string()` without `.min(1)` or `.nonempty()`)
257
- * - Empty array (plain `z.array()` without `.min(1)` or `.nonempty()`)
349
+ * **Real-world example:**
350
+ * ```typescript
351
+ * // Marital status field with default but validation rules
352
+ * const maritalStatus = z.string().min(1).default('single');
353
+ *
354
+ * // Initial: field shows "single" (from default)
355
+ * // User deletes the value → field is now empty string
356
+ * // User submits form → validation fails because .min(1) rejects empty strings
357
+ * // requiresValidInput(maritalStatus) → true (shows * indicator, validation error)
358
+ * ```
258
359
  *
259
- * **Note:** Fields with `.default()` are considered not required since they'll have a value
260
- * even if the user doesn't provide one.
360
+ * **How it works:**
361
+ * 1. Removes `.default()` wrappers (defaults are initial values, not validation rules)
362
+ * 2. Tests if the underlying schema accepts empty/invalid input:
363
+ * - `undefined` (via `.optional()`)
364
+ * - `null` (via `.nullable()`)
365
+ * - Empty string (plain `z.string()` without `.min(1)` or `.nonempty()`)
366
+ * - Empty array (plain `z.array()` without `.min(1)` or `.nonempty()`)
367
+ * 3. Returns `true` if validation will fail, `false` if empty input is accepted
261
368
  *
262
369
  * @template T - The Zod type to check
263
- * @param field - The Zod field to check for required status
264
- * @returns True if the field is required, false otherwise
370
+ * @param field - The Zod field to check
371
+ * @returns True if the field will show validation errors on empty/invalid input, false otherwise
265
372
  *
266
373
  * @example
267
- * Required field
374
+ * User name field - required, no default
268
375
  * ```typescript
269
- * const field = z.string().min(1);
270
- * console.log(checkIfFieldIsRequired(field)); // true
376
+ * const userName = z.string().min(1);
377
+ * requiresValidInput(userName); // true - will error if user submits empty
271
378
  * ```
272
379
  *
273
380
  * @example
274
- * Optional field (not required)
381
+ * Marital status - required WITH default
275
382
  * ```typescript
276
- * const field = z.string().optional();
277
- * console.log(checkIfFieldIsRequired(field)); // false
383
+ * const maritalStatus = z.string().min(1).default('single');
384
+ * requiresValidInput(maritalStatus); // true - will error if user clears and submits
278
385
  * ```
279
386
  *
280
387
  * @example
281
- * Field with default (not required)
388
+ * Age with default - requires valid input
282
389
  * ```typescript
283
- * const field = z.string().default('hello');
284
- * console.log(checkIfFieldIsRequired(field)); // false
390
+ * const age = z.number().default(0);
391
+ * requiresValidInput(age); // true - numbers reject empty strings
285
392
  * ```
286
393
  *
287
394
  * @example
288
- * String without min length (not required - accepts empty string)
395
+ * Optional bio field - doesn't require input
289
396
  * ```typescript
290
- * const field = z.string();
291
- * console.log(checkIfFieldIsRequired(field)); // false
397
+ * const bio = z.string().optional();
398
+ * requiresValidInput(bio); // false - user can leave empty
292
399
  * ```
293
400
  *
294
401
  * @example
295
- * String with nonempty (required)
402
+ * String with default but NO validation - doesn't require input
296
403
  * ```typescript
297
- * const field = z.string().nonempty();
298
- * console.log(checkIfFieldIsRequired(field)); // true
404
+ * const notes = z.string().default('N/A');
405
+ * requiresValidInput(notes); // false - plain z.string() accepts empty strings
299
406
  * ```
300
407
  *
301
408
  * @example
302
- * Nullable field (not required)
409
+ * Nullable field - doesn't require input
303
410
  * ```typescript
304
- * const field = z.number().nullable();
305
- * console.log(checkIfFieldIsRequired(field)); // false
411
+ * const middleName = z.string().nullable();
412
+ * requiresValidInput(middleName); // false - user can leave null
306
413
  * ```
307
414
  *
308
415
  * @see {@link removeDefault} for understanding how defaults are handled
309
416
  * @see {@link getPrimitiveType} for understanding type unwrapping
310
417
  * @since 0.1.0
311
418
  */
312
- declare const checkIfFieldIsRequired: <T extends z.ZodType>(field: T) => boolean;
419
+ declare const requiresValidInput: <T extends z.ZodType>(field: T) => boolean;
420
+ /**
421
+ * Union type of all Zod check definition types.
422
+ *
423
+ * Includes all validation check types supported by Zod v4:
424
+ * - **Length checks**: `min_length`, `max_length`, `length_equals` (strings, arrays)
425
+ * - **Size checks**: `min_size`, `max_size`, `size_equals` (files, sets, maps)
426
+ * - **Numeric checks**: `greater_than`, `less_than`, `multiple_of`
427
+ * - **Format checks**: `number_format` (int32, float64, etc.), `bigint_format`, `string_format` (email, url, uuid, etc.)
428
+ * - **String pattern checks**: `regex`, `lowercase`, `uppercase`, `includes`, `starts_with`, `ends_with`
429
+ * - **Other checks**: `property`, `mime_type`, `overwrite`
430
+ *
431
+ * @since 0.4.0
432
+ */
433
+ type ZodUnionCheck = $ZodCheckLessThanDef | $ZodCheckGreaterThanDef | $ZodCheckMultipleOfDef | $ZodCheckNumberFormatDef | $ZodCheckBigIntFormatDef | $ZodCheckMaxSizeDef | $ZodCheckMinSizeDef | $ZodCheckSizeEqualsDef | $ZodCheckMaxLengthDef | $ZodCheckMinLengthDef | $ZodCheckLengthEqualsDef | $ZodCheckStringFormatDef | $ZodCheckRegexDef | $ZodCheckLowerCaseDef | $ZodCheckUpperCaseDef | $ZodCheckIncludesDef | $ZodCheckStartsWithDef | $ZodCheckEndsWithDef | $ZodCheckPropertyDef | $ZodCheckMimeTypeDef | $ZodCheckOverwriteDef;
434
+ /**
435
+ * Extracts all validation check definitions from a Zod schema field.
436
+ *
437
+ * This function analyzes a Zod field and returns all check definitions as defined
438
+ * by Zod's internal structure. Returns Zod's raw check definition objects directly,
439
+ * including all properties like `check`, `minimum`, `maximum`, `value`, `inclusive`,
440
+ * `format`, `pattern`, etc.
441
+ *
442
+ * **Unwrapping behavior:** Automatically unwraps optional, nullable, and default layers.
443
+ * For unions, checks only the first option (same as other schema utilities).
444
+ *
445
+ * **Supported check types:** Returns any of the 21 check types defined in {@link ZodUnionCheck},
446
+ * including length, size, numeric range, format validation, string patterns, and more.
447
+ *
448
+ * @template T - The Zod type to extract checks from
449
+ * @param field - The Zod field to analyze
450
+ * @returns Array of Zod check definition objects (see {@link ZodUnionCheck})
451
+ *
452
+ * @example
453
+ * String with length constraints
454
+ * ```typescript
455
+ * const username = z.string().min(3).max(20);
456
+ * const checks = getFieldChecks(username);
457
+ * // [
458
+ * // { check: 'min_length', minimum: 3, when: [Function], ... },
459
+ * // { check: 'max_length', maximum: 20, when: [Function], ... }
460
+ * // ]
461
+ * ```
462
+ *
463
+ * @example
464
+ * Number with range constraints
465
+ * ```typescript
466
+ * const age = z.number().min(18).max(120);
467
+ * const checks = getFieldChecks(age);
468
+ * // [
469
+ * // { check: 'greater_than', value: 18, inclusive: true, when: [Function], ... },
470
+ * // { check: 'less_than', value: 120, inclusive: true, when: [Function], ... }
471
+ * // ]
472
+ * ```
473
+ *
474
+ * @example
475
+ * Array with item count constraints
476
+ * ```typescript
477
+ * const tags = z.array(z.string()).min(1).max(5);
478
+ * const checks = getFieldChecks(tags);
479
+ * // [
480
+ * // { check: 'min_length', minimum: 1, ... },
481
+ * // { check: 'max_length', maximum: 5, ... }
482
+ * // ]
483
+ * ```
484
+ *
485
+ * @example
486
+ * String with format validation
487
+ * ```typescript
488
+ * const email = z.string().email();
489
+ * const checks = getFieldChecks(email);
490
+ * // [
491
+ * // { check: 'string_format', format: 'email', ... }
492
+ * // ]
493
+ * ```
494
+ *
495
+ * @example
496
+ * Unwrapping optional/nullable/default layers
497
+ * ```typescript
498
+ * const bio = z.string().min(10).max(500).optional();
499
+ * const checks = getFieldChecks(bio);
500
+ * // [
501
+ * // { check: 'min_length', minimum: 10, ... },
502
+ * // { check: 'max_length', maximum: 500, ... }
503
+ * // ]
504
+ * ```
505
+ *
506
+ * @see {@link ZodUnionCheck} for all supported check types
507
+ * @since 0.4.0
508
+ */
509
+ declare function getFieldChecks<T extends z.ZodTypeAny>(field: T): Array<ZodUnionCheck>;
313
510
 
314
- export { type Simplify, canUnwrap, checkIfFieldIsRequired, extractDefault, getPrimitiveType, getSchemaDefaults, removeDefault };
511
+ export { type Simplify, type ZodUnionCheck, canUnwrap, extractDefault, getFieldChecks, getPrimitiveType, getSchemaDefaults, removeDefault, requiresValidInput, unwrapUnion };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as z from 'zod';
2
+ import { $ZodCheckLessThanDef, $ZodCheckGreaterThanDef, $ZodCheckMultipleOfDef, $ZodCheckNumberFormatDef, $ZodCheckBigIntFormatDef, $ZodCheckMaxSizeDef, $ZodCheckMinSizeDef, $ZodCheckSizeEqualsDef, $ZodCheckMaxLengthDef, $ZodCheckMinLengthDef, $ZodCheckLengthEqualsDef, $ZodCheckStringFormatDef, $ZodCheckRegexDef, $ZodCheckLowerCaseDef, $ZodCheckUpperCaseDef, $ZodCheckIncludesDef, $ZodCheckStartsWithDef, $ZodCheckEndsWithDef, $ZodCheckPropertyDef, $ZodCheckMimeTypeDef, $ZodCheckOverwriteDef } from 'zod/v4/core';
2
3
 
3
4
  /**
4
5
  * Simplifies complex TypeScript types for better IDE hover tooltips and error messages.
@@ -45,11 +46,14 @@ type Simplify<T> = {
45
46
  } & {};
46
47
 
47
48
  /**
48
- * Extracts the default value from a Zod field, recursively unwrapping optional and nullable layers.
49
+ * Extracts the default value from a Zod field, recursively unwrapping optional, nullable, and union layers.
49
50
  *
50
- * This function traverses through wrapper types (like `ZodOptional`, `ZodNullable`) to find
51
+ * This function traverses through wrapper types (like `ZodOptional`, `ZodNullable`, `ZodUnion`) to find
51
52
  * the underlying `ZodDefault` and returns its default value. If no default is found, returns `undefined`.
52
53
  *
54
+ * **Union handling:** For union types, extracts the default from the first option. If the first option
55
+ * has no default, returns `undefined` (defaults in other union options are not checked).
56
+ *
53
57
  * @template T - The Zod type to extract default from
54
58
  * @param field - The Zod field to extract default from
55
59
  * @returns The default value if present, undefined otherwise
@@ -71,6 +75,22 @@ type Simplify<T> = {
71
75
  * ```
72
76
  *
73
77
  * @example
78
+ * Union with default in first option
79
+ * ```typescript
80
+ * const field = z.union([z.string().default('hello'), z.number()]);
81
+ * const defaultValue = extractDefault(field);
82
+ * // Result: 'hello' (extracts from first union option)
83
+ * ```
84
+ *
85
+ * @example
86
+ * Union with default in second option
87
+ * ```typescript
88
+ * const field = z.union([z.string(), z.number().default(42)]);
89
+ * const defaultValue = extractDefault(field);
90
+ * // Result: undefined (only checks first option)
91
+ * ```
92
+ *
93
+ * @example
74
94
  * Field without default
75
95
  * ```typescript
76
96
  * const field = z.string().optional();
@@ -169,6 +189,78 @@ type Unwrappable = {
169
189
  * @since 0.1.0
170
190
  */
171
191
  declare function canUnwrap(field: z.ZodTypeAny): field is z.ZodTypeAny & Unwrappable;
192
+ /**
193
+ * Unwraps a ZodUnion type and returns the first field and all union options.
194
+ *
195
+ * This function extracts the individual type options from a union type.
196
+ * By default, it filters out `ZodNull` and `ZodUndefined` types, returning only
197
+ * the meaningful type options. You can disable this filtering to get all options.
198
+ *
199
+ * @template T - The Zod type to unwrap
200
+ * @param field - The Zod field (union or single type)
201
+ * @param options - Configuration options
202
+ * @param options.filterNullish - Whether to filter out null and undefined types (default: true)
203
+ * @returns Object with `field` (first option) and `union` (all options array)
204
+ *
205
+ * @example
206
+ * Basic union unwrapping
207
+ * ```typescript
208
+ * const field = z.union([z.string(), z.number()]);
209
+ * const result = unwrapUnion(field);
210
+ * // Result: { field: z.string(), union: [z.string(), z.number()] }
211
+ * ```
212
+ *
213
+ * @example
214
+ * Union with null (filtered by default)
215
+ * ```typescript
216
+ * const field = z.union([z.string(), z.null()]);
217
+ * const result = unwrapUnion(field);
218
+ * // Result: { field: z.string(), union: [z.string()] }
219
+ * ```
220
+ *
221
+ * @example
222
+ * Union with null (keep all options)
223
+ * ```typescript
224
+ * const field = z.union([z.string(), z.null()]);
225
+ * const result = unwrapUnion(field, { filterNullish: false });
226
+ * // Result: { field: z.string(), union: [z.string(), z.null()] }
227
+ * ```
228
+ *
229
+ * @example
230
+ * Non-union type (returns single field)
231
+ * ```typescript
232
+ * const field = z.string();
233
+ * const result = unwrapUnion(field);
234
+ * // Result: { field: z.string(), union: [z.string()] }
235
+ * ```
236
+ *
237
+ * @example
238
+ * Nullable as union
239
+ * ```typescript
240
+ * const field = z.string().nullable(); // This is z.union([z.string(), z.null()])
241
+ * const result = unwrapUnion(field);
242
+ * // Result: { field: z.string(), union: [z.string()] } (null filtered out)
243
+ * ```
244
+ *
245
+ * @example
246
+ * Using the first field for type checking
247
+ * ```typescript
248
+ * const field = z.union([z.string(), z.number()]);
249
+ * const { field: firstField, union } = unwrapUnion(field);
250
+ * if (firstField instanceof z.ZodString) {
251
+ * console.log('First type is string');
252
+ * }
253
+ * ```
254
+ *
255
+ * @see {@link getPrimitiveType} for unwrapping wrapper types
256
+ * @since 0.1.0
257
+ */
258
+ declare function unwrapUnion<T extends z.ZodTypeAny>(field: T, options?: {
259
+ filterNullish?: boolean;
260
+ }): {
261
+ field: z.ZodTypeAny;
262
+ union: z.ZodTypeAny[];
263
+ };
172
264
  /**
173
265
  * Gets the underlying primitive type of a Zod field by recursively unwrapping wrapper types.
174
266
  *
@@ -206,7 +298,7 @@ declare function canUnwrap(field: z.ZodTypeAny): field is z.ZodTypeAny & Unwrapp
206
298
  * @see {@link canUnwrap} for checking if a field can be unwrapped
207
299
  * @since 0.1.0
208
300
  */
209
- declare const getPrimitiveType: <T extends z.ZodTypeAny>(field: T) => z.ZodTypeAny;
301
+ declare const getPrimitiveType: <T extends z.ZodType>(field: T) => z.ZodTypeAny;
210
302
  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
303
  /**
212
304
  * Removes default values from a Zod field while preserving other wrapper types.
@@ -243,72 +335,177 @@ type StripZodDefault<T> = T extends z.ZodDefault<infer Inner> ? StripZodDefault<
243
335
  * // Result: z.string().nullable()
244
336
  * ```
245
337
  *
246
- * @see {@link checkIfFieldIsRequired} for usage with requirement checking
338
+ * @see {@link requiresValidInput} for usage with requirement checking
247
339
  * @since 0.1.0
248
340
  */
249
341
  declare function removeDefault<T extends z.ZodType>(field: T): StripZodDefault<T>;
250
342
  /**
251
- * Checks if a Zod field is truly required by testing multiple acceptance criteria.
343
+ * Determines if a field will show validation errors when the user submits empty or invalid input.
344
+ *
345
+ * This is useful for form UIs to indicate which fields require valid user input (e.g., showing
346
+ * asterisks, validation states). The key insight: **defaults are just initial values** - they
347
+ * don't prevent validation errors if the user clears the field.
252
348
  *
253
- * A field is considered **not required** if it accepts any of the following:
254
- * - `undefined` (via `.optional()` or `.default()`)
255
- * - `null` (via `.nullable()`)
256
- * - Empty string (plain `z.string()` without `.min(1)` or `.nonempty()`)
257
- * - Empty array (plain `z.array()` without `.min(1)` or `.nonempty()`)
349
+ * **Real-world example:**
350
+ * ```typescript
351
+ * // Marital status field with default but validation rules
352
+ * const maritalStatus = z.string().min(1).default('single');
353
+ *
354
+ * // Initial: field shows "single" (from default)
355
+ * // User deletes the value → field is now empty string
356
+ * // User submits form → validation fails because .min(1) rejects empty strings
357
+ * // requiresValidInput(maritalStatus) → true (shows * indicator, validation error)
358
+ * ```
258
359
  *
259
- * **Note:** Fields with `.default()` are considered not required since they'll have a value
260
- * even if the user doesn't provide one.
360
+ * **How it works:**
361
+ * 1. Removes `.default()` wrappers (defaults are initial values, not validation rules)
362
+ * 2. Tests if the underlying schema accepts empty/invalid input:
363
+ * - `undefined` (via `.optional()`)
364
+ * - `null` (via `.nullable()`)
365
+ * - Empty string (plain `z.string()` without `.min(1)` or `.nonempty()`)
366
+ * - Empty array (plain `z.array()` without `.min(1)` or `.nonempty()`)
367
+ * 3. Returns `true` if validation will fail, `false` if empty input is accepted
261
368
  *
262
369
  * @template T - The Zod type to check
263
- * @param field - The Zod field to check for required status
264
- * @returns True if the field is required, false otherwise
370
+ * @param field - The Zod field to check
371
+ * @returns True if the field will show validation errors on empty/invalid input, false otherwise
265
372
  *
266
373
  * @example
267
- * Required field
374
+ * User name field - required, no default
268
375
  * ```typescript
269
- * const field = z.string().min(1);
270
- * console.log(checkIfFieldIsRequired(field)); // true
376
+ * const userName = z.string().min(1);
377
+ * requiresValidInput(userName); // true - will error if user submits empty
271
378
  * ```
272
379
  *
273
380
  * @example
274
- * Optional field (not required)
381
+ * Marital status - required WITH default
275
382
  * ```typescript
276
- * const field = z.string().optional();
277
- * console.log(checkIfFieldIsRequired(field)); // false
383
+ * const maritalStatus = z.string().min(1).default('single');
384
+ * requiresValidInput(maritalStatus); // true - will error if user clears and submits
278
385
  * ```
279
386
  *
280
387
  * @example
281
- * Field with default (not required)
388
+ * Age with default - requires valid input
282
389
  * ```typescript
283
- * const field = z.string().default('hello');
284
- * console.log(checkIfFieldIsRequired(field)); // false
390
+ * const age = z.number().default(0);
391
+ * requiresValidInput(age); // true - numbers reject empty strings
285
392
  * ```
286
393
  *
287
394
  * @example
288
- * String without min length (not required - accepts empty string)
395
+ * Optional bio field - doesn't require input
289
396
  * ```typescript
290
- * const field = z.string();
291
- * console.log(checkIfFieldIsRequired(field)); // false
397
+ * const bio = z.string().optional();
398
+ * requiresValidInput(bio); // false - user can leave empty
292
399
  * ```
293
400
  *
294
401
  * @example
295
- * String with nonempty (required)
402
+ * String with default but NO validation - doesn't require input
296
403
  * ```typescript
297
- * const field = z.string().nonempty();
298
- * console.log(checkIfFieldIsRequired(field)); // true
404
+ * const notes = z.string().default('N/A');
405
+ * requiresValidInput(notes); // false - plain z.string() accepts empty strings
299
406
  * ```
300
407
  *
301
408
  * @example
302
- * Nullable field (not required)
409
+ * Nullable field - doesn't require input
303
410
  * ```typescript
304
- * const field = z.number().nullable();
305
- * console.log(checkIfFieldIsRequired(field)); // false
411
+ * const middleName = z.string().nullable();
412
+ * requiresValidInput(middleName); // false - user can leave null
306
413
  * ```
307
414
  *
308
415
  * @see {@link removeDefault} for understanding how defaults are handled
309
416
  * @see {@link getPrimitiveType} for understanding type unwrapping
310
417
  * @since 0.1.0
311
418
  */
312
- declare const checkIfFieldIsRequired: <T extends z.ZodType>(field: T) => boolean;
419
+ declare const requiresValidInput: <T extends z.ZodType>(field: T) => boolean;
420
+ /**
421
+ * Union type of all Zod check definition types.
422
+ *
423
+ * Includes all validation check types supported by Zod v4:
424
+ * - **Length checks**: `min_length`, `max_length`, `length_equals` (strings, arrays)
425
+ * - **Size checks**: `min_size`, `max_size`, `size_equals` (files, sets, maps)
426
+ * - **Numeric checks**: `greater_than`, `less_than`, `multiple_of`
427
+ * - **Format checks**: `number_format` (int32, float64, etc.), `bigint_format`, `string_format` (email, url, uuid, etc.)
428
+ * - **String pattern checks**: `regex`, `lowercase`, `uppercase`, `includes`, `starts_with`, `ends_with`
429
+ * - **Other checks**: `property`, `mime_type`, `overwrite`
430
+ *
431
+ * @since 0.4.0
432
+ */
433
+ type ZodUnionCheck = $ZodCheckLessThanDef | $ZodCheckGreaterThanDef | $ZodCheckMultipleOfDef | $ZodCheckNumberFormatDef | $ZodCheckBigIntFormatDef | $ZodCheckMaxSizeDef | $ZodCheckMinSizeDef | $ZodCheckSizeEqualsDef | $ZodCheckMaxLengthDef | $ZodCheckMinLengthDef | $ZodCheckLengthEqualsDef | $ZodCheckStringFormatDef | $ZodCheckRegexDef | $ZodCheckLowerCaseDef | $ZodCheckUpperCaseDef | $ZodCheckIncludesDef | $ZodCheckStartsWithDef | $ZodCheckEndsWithDef | $ZodCheckPropertyDef | $ZodCheckMimeTypeDef | $ZodCheckOverwriteDef;
434
+ /**
435
+ * Extracts all validation check definitions from a Zod schema field.
436
+ *
437
+ * This function analyzes a Zod field and returns all check definitions as defined
438
+ * by Zod's internal structure. Returns Zod's raw check definition objects directly,
439
+ * including all properties like `check`, `minimum`, `maximum`, `value`, `inclusive`,
440
+ * `format`, `pattern`, etc.
441
+ *
442
+ * **Unwrapping behavior:** Automatically unwraps optional, nullable, and default layers.
443
+ * For unions, checks only the first option (same as other schema utilities).
444
+ *
445
+ * **Supported check types:** Returns any of the 21 check types defined in {@link ZodUnionCheck},
446
+ * including length, size, numeric range, format validation, string patterns, and more.
447
+ *
448
+ * @template T - The Zod type to extract checks from
449
+ * @param field - The Zod field to analyze
450
+ * @returns Array of Zod check definition objects (see {@link ZodUnionCheck})
451
+ *
452
+ * @example
453
+ * String with length constraints
454
+ * ```typescript
455
+ * const username = z.string().min(3).max(20);
456
+ * const checks = getFieldChecks(username);
457
+ * // [
458
+ * // { check: 'min_length', minimum: 3, when: [Function], ... },
459
+ * // { check: 'max_length', maximum: 20, when: [Function], ... }
460
+ * // ]
461
+ * ```
462
+ *
463
+ * @example
464
+ * Number with range constraints
465
+ * ```typescript
466
+ * const age = z.number().min(18).max(120);
467
+ * const checks = getFieldChecks(age);
468
+ * // [
469
+ * // { check: 'greater_than', value: 18, inclusive: true, when: [Function], ... },
470
+ * // { check: 'less_than', value: 120, inclusive: true, when: [Function], ... }
471
+ * // ]
472
+ * ```
473
+ *
474
+ * @example
475
+ * Array with item count constraints
476
+ * ```typescript
477
+ * const tags = z.array(z.string()).min(1).max(5);
478
+ * const checks = getFieldChecks(tags);
479
+ * // [
480
+ * // { check: 'min_length', minimum: 1, ... },
481
+ * // { check: 'max_length', maximum: 5, ... }
482
+ * // ]
483
+ * ```
484
+ *
485
+ * @example
486
+ * String with format validation
487
+ * ```typescript
488
+ * const email = z.string().email();
489
+ * const checks = getFieldChecks(email);
490
+ * // [
491
+ * // { check: 'string_format', format: 'email', ... }
492
+ * // ]
493
+ * ```
494
+ *
495
+ * @example
496
+ * Unwrapping optional/nullable/default layers
497
+ * ```typescript
498
+ * const bio = z.string().min(10).max(500).optional();
499
+ * const checks = getFieldChecks(bio);
500
+ * // [
501
+ * // { check: 'min_length', minimum: 10, ... },
502
+ * // { check: 'max_length', maximum: 500, ... }
503
+ * // ]
504
+ * ```
505
+ *
506
+ * @see {@link ZodUnionCheck} for all supported check types
507
+ * @since 0.4.0
508
+ */
509
+ declare function getFieldChecks<T extends z.ZodTypeAny>(field: T): Array<ZodUnionCheck>;
313
510
 
314
- export { type Simplify, canUnwrap, checkIfFieldIsRequired, extractDefault, getPrimitiveType, getSchemaDefaults, removeDefault };
511
+ export { type Simplify, type ZodUnionCheck, canUnwrap, extractDefault, getFieldChecks, 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,21 +70,23 @@ function removeDefault(field) {
50
70
  }
51
71
  return field;
52
72
  }
53
- var checkIfFieldIsRequired = (field) => {
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;
65
82
  const emptyArrayResult = primitiveType.type === "array" && defaultRemovedField.safeParse([]).success;
66
83
  return !undefinedResult && !nullResult && !emptyStringResult && !emptyArrayResult;
67
84
  };
85
+ function getFieldChecks(field) {
86
+ var _a;
87
+ const primitiveType = getPrimitiveType(field);
88
+ return ((_a = primitiveType.def.checks) == null ? void 0 : _a.map((check) => check._zod.def)) || [];
89
+ }
68
90
 
69
91
  // src/defaults.ts
70
92
  function extractDefault(field) {
@@ -74,6 +96,9 @@ function extractDefault(field) {
74
96
  if (canUnwrap(field)) {
75
97
  return extractDefault(field.unwrap());
76
98
  }
99
+ if (field instanceof z__namespace.ZodUnion) {
100
+ return extractDefault(unwrapUnion(field).field);
101
+ }
77
102
  return void 0;
78
103
  }
79
104
  function getSchemaDefaults(schema) {
@@ -90,10 +115,12 @@ function getSchemaDefaults(schema) {
90
115
  }
91
116
 
92
117
  exports.canUnwrap = canUnwrap;
93
- exports.checkIfFieldIsRequired = checkIfFieldIsRequired;
94
118
  exports.extractDefault = extractDefault;
119
+ exports.getFieldChecks = getFieldChecks;
95
120
  exports.getPrimitiveType = getPrimitiveType;
96
121
  exports.getSchemaDefaults = getSchemaDefaults;
97
122
  exports.removeDefault = removeDefault;
123
+ exports.requiresValidInput = requiresValidInput;
124
+ exports.unwrapUnion = unwrapUnion;
98
125
  //# sourceMappingURL=index.js.map
99
126
  //# 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":";;;;;;;;;;;;;;;;;;;;;;;;;AAkDO,SAAS,UACd,KAAA,EACqC;AACrC,EAAA,OAAO,QAAA,IAAY,KAAA,IAAS,OAAO,KAAA,CAAM,MAAA,KAAW,UAAA;AACtD;AAoEO,SAAS,WAAA,CACd,KAAA,EACA,OAAA,GAAuC,EAAC,EACQ;AAChD,EAAA,MAAM,EAAE,aAAA,GAAgB,IAAA,EAAK,GAAI,OAAA;AAEjC,EAAA,IAAI,iBAAmBA,YAAA,CAAA,QAAA,EAAU;AAE/B,IAAA,MAAM,YAAA,GAAe,CAAC,GAAG,KAAA,CAAM,IAAI,OAAO,CAAA;AAE1C,IAAA,MAAM,eAAA,GAAkB,gBACpB,YAAA,CAAa,MAAA;AAAA,MACX,CAAC,MAAA,KACC,EAAE,MAAA,YAAoBA,YAAA,CAAA,OAAA,CAAA,IACtB,EAAE,MAAA,YAAoBA,YAAA,CAAA,YAAA;AAAA,KAC1B,GACA,YAAA;AAEJ,IAAA,OAAO;AAAA,MACL,KAAA,EAAO,eAAA,CAAgB,CAAC,CAAA,IAAK,KAAA;AAAA,MAC7B,KAAA,EAAO;AAAA,KACT;AAAA,EACF;AAGA,EAAA,OAAO;AAAA,IACL,KAAA;AAAA,IACA,KAAA,EAAO,CAAC,KAAK;AAAA,GACf;AACF;AAuCO,IAAM,gBAAA,GAAmB,CAC9B,KAAA,KACiB;AAEjB,EAAA,IAAI,iBAAmBA,YAAA,CAAA,QAAA,EAAU;AAC/B,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,IAAI,SAAA,CAAU,KAAK,CAAA,EAAG;AACpB,IAAA,OAAO,gBAAA,CAAiB,KAAA,CAAM,MAAA,EAAQ,CAAA;AAAA,EACxC;AAEA,EAAA,IAAI,iBAAmBA,YAAA,CAAA,QAAA,EAAU;AAC/B,IAAA,OAAO,gBAAA,CAAiB,WAAA,CAAY,KAAK,CAAA,CAAE,KAAK,CAAA;AAAA,EAClD;AAEA,EAAA,OAAO,KAAA;AACT;AAgDO,SAAS,cACd,KAAA,EACoB;AACpB,EAAA,IAAI,iBAAmBA,YAAA,CAAA,UAAA,EAAY;AAEjC,IAAA,OAAO,MAAM,MAAA,EAAO;AAAA,EACtB;AAEA,EAAA,IAAI,eAAe,KAAA,CAAM,GAAA,IAAO,KAAA,CAAM,GAAA,CAAI,qBAAuBA,YAAA,CAAA,OAAA,EAAS;AACxE,IAAA,MAAM,KAAA,GAAQ,aAAA,CAAc,KAAA,CAAM,GAAA,CAAI,SAAS,CAAA;AAE/C,IAAA,IAAI,iBAAmBA,YAAA,CAAA,WAAA,EAAa;AAElC,MAAA,OAAO,MAAM,QAAA,EAAS;AAAA,IACxB;AACA,IAAA,IAAI,iBAAmBA,YAAA,CAAA,WAAA,EAAa;AAElC,MAAA,OAAO,MAAM,QAAA,EAAS;AAAA,IACxB;AAAA,EACF;AAGA,EAAA,OAAO,KAAA;AACT;AA+EO,IAAM,kBAAA,GAAqB,CAAsB,KAAA,KAAa;AACnE,EAAA,MAAM,mBAAA,GAAsB,cAAc,KAAK,CAAA;AAC/C,EAAA,IAAI,EAAE,+BAAiCA,YAAA,CAAA,OAAA,CAAA,EAAU;AAC/C,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,MAAM,eAAA,GAAkB,mBAAA,CAAoB,SAAA,CAAU,MAAS,CAAA,CAAE,OAAA;AAGjE,EAAA,MAAM,UAAA,GAAa,mBAAA,CAAoB,SAAA,CAAU,IAAI,CAAA,CAAE,OAAA;AAEvD,EAAA,MAAM,aAAA,GAAgB,iBAAiB,mBAAmB,CAAA;AAE1D,EAAA,MAAM,oBACJ,aAAA,CAAc,IAAA,KAAS,YACvB,mBAAA,CAAoB,SAAA,CAAU,EAAE,CAAA,CAAE,OAAA;AAEpC,EAAA,MAAM,gBAAA,GACJ,cAAc,IAAA,KAAS,OAAA,IAAW,oBAAoB,SAAA,CAAU,EAAE,CAAA,CAAE,OAAA;AAEtE,EAAA,OACE,CAAC,eAAA,IAAmB,CAAC,UAAA,IAAc,CAAC,qBAAqB,CAAC,gBAAA;AAE9D;AAiHO,SAAS,eACd,KAAA,EACsB;AA/exB,EAAA,IAAA,EAAA;AAgfE,EAAA,MAAM,aAAA,GAAgB,iBAAiB,KAAK,CAAA;AAE5C,EAAA,OAAA,CAAA,CAAQ,EAAA,GAAA,aAAA,CAAc,GAAA,CAAI,MAAA,KAAlB,IAAA,GAAA,MAAA,GAAA,EAAA,CAA0B,GAAA,CAAI,CAAC,KAAA,KAAU,KAAA,CAAM,IAAA,CAAK,GAAA,CAAA,KAC1D,EAAC;AACL;;;ACxbO,SAAS,eACd,KAAA,EACwB;AACxB,EAAA,IAAI,iBAAmBC,YAAA,CAAA,UAAA,EAAY;AAEjC,IAAA,OAAO,MAAM,GAAA,CAAI,YAAA;AAAA,EACnB;AAEA,EAAA,IAAI,SAAA,CAAU,KAAK,CAAA,EAAG;AAEpB,IAAA,OAAO,cAAA,CAAe,KAAA,CAAM,MAAA,EAAQ,CAAA;AAAA,EACtC;AAEA,EAAA,IAAI,iBAAmBA,YAAA,CAAA,QAAA,EAAU;AAE/B,IAAA,OAAO,cAAA,CAAe,WAAA,CAAY,KAAK,CAAA,CAAE,KAAK,CAAA;AAAA,EAChD;AAEA,EAAA,OAAO,MAAA;AACT;AA4DO,SAAS,kBACd,MAAA,EAC+B;AAC/B,EAAA,MAAM,WAAoC,EAAC;AAE3C,EAAA,KAAA,MAAW,GAAA,IAAO,OAAO,KAAA,EAAO;AAC9B,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,GAAG,CAAA;AAC9B,IAAA,IAAI,CAAC,KAAA,EAAO;AAGZ,IAAA,MAAM,YAAA,GAAe,eAAe,KAAK,CAAA;AACzC,IAAA,IAAI,iBAAiB,MAAA,EAAW;AAC9B,MAAA,QAAA,CAAS,GAAG,CAAA,GAAI,YAAA;AAAA,IAClB;AAAA,EACF;AAGA,EAAA,OAAO,QAAA;AACT","file":"index.js","sourcesContent":["import * as z from 'zod';\nimport type {\n $ZodCheckBigIntFormatDef,\n $ZodCheckEndsWithDef,\n $ZodCheckGreaterThanDef,\n $ZodCheckIncludesDef,\n $ZodCheckLengthEqualsDef,\n $ZodCheckLessThanDef,\n $ZodCheckLowerCaseDef,\n $ZodCheckMaxLengthDef,\n $ZodCheckMaxSizeDef,\n $ZodCheckMimeTypeDef,\n $ZodCheckMinLengthDef,\n $ZodCheckMinSizeDef,\n $ZodCheckMultipleOfDef,\n $ZodCheckNumberFormatDef,\n $ZodCheckOverwriteDef,\n $ZodCheckPropertyDef,\n $ZodCheckRegexDef,\n $ZodCheckSizeEqualsDef,\n $ZodCheckStartsWithDef,\n $ZodCheckStringFormatDef,\n $ZodCheckUpperCaseDef,\n} from 'zod/v4/core';\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\n/**\n * Union type of all Zod check definition types.\n *\n * Includes all validation check types supported by Zod v4:\n * - **Length checks**: `min_length`, `max_length`, `length_equals` (strings, arrays)\n * - **Size checks**: `min_size`, `max_size`, `size_equals` (files, sets, maps)\n * - **Numeric checks**: `greater_than`, `less_than`, `multiple_of`\n * - **Format checks**: `number_format` (int32, float64, etc.), `bigint_format`, `string_format` (email, url, uuid, etc.)\n * - **String pattern checks**: `regex`, `lowercase`, `uppercase`, `includes`, `starts_with`, `ends_with`\n * - **Other checks**: `property`, `mime_type`, `overwrite`\n *\n * @since 0.4.0\n */\nexport type ZodUnionCheck =\n | $ZodCheckLessThanDef\n | $ZodCheckGreaterThanDef\n | $ZodCheckMultipleOfDef\n | $ZodCheckNumberFormatDef\n | $ZodCheckBigIntFormatDef\n | $ZodCheckMaxSizeDef\n | $ZodCheckMinSizeDef\n | $ZodCheckSizeEqualsDef\n | $ZodCheckMaxLengthDef\n | $ZodCheckMinLengthDef\n | $ZodCheckLengthEqualsDef\n | $ZodCheckStringFormatDef\n | $ZodCheckRegexDef\n | $ZodCheckLowerCaseDef\n | $ZodCheckUpperCaseDef\n | $ZodCheckIncludesDef\n | $ZodCheckStartsWithDef\n | $ZodCheckEndsWithDef\n | $ZodCheckPropertyDef\n | $ZodCheckMimeTypeDef\n | $ZodCheckOverwriteDef;\n\n/**\n * Extracts all validation check definitions from a Zod schema field.\n *\n * This function analyzes a Zod field and returns all check definitions as defined\n * by Zod's internal structure. Returns Zod's raw check definition objects directly,\n * including all properties like `check`, `minimum`, `maximum`, `value`, `inclusive`,\n * `format`, `pattern`, etc.\n *\n * **Unwrapping behavior:** Automatically unwraps optional, nullable, and default layers.\n * For unions, checks only the first option (same as other schema utilities).\n *\n * **Supported check types:** Returns any of the 21 check types defined in {@link ZodUnionCheck},\n * including length, size, numeric range, format validation, string patterns, and more.\n *\n * @template T - The Zod type to extract checks from\n * @param field - The Zod field to analyze\n * @returns Array of Zod check definition objects (see {@link ZodUnionCheck})\n *\n * @example\n * String with length constraints\n * ```typescript\n * const username = z.string().min(3).max(20);\n * const checks = getFieldChecks(username);\n * // [\n * // { check: 'min_length', minimum: 3, when: [Function], ... },\n * // { check: 'max_length', maximum: 20, when: [Function], ... }\n * // ]\n * ```\n *\n * @example\n * Number with range constraints\n * ```typescript\n * const age = z.number().min(18).max(120);\n * const checks = getFieldChecks(age);\n * // [\n * // { check: 'greater_than', value: 18, inclusive: true, when: [Function], ... },\n * // { check: 'less_than', value: 120, inclusive: true, when: [Function], ... }\n * // ]\n * ```\n *\n * @example\n * Array with item count constraints\n * ```typescript\n * const tags = z.array(z.string()).min(1).max(5);\n * const checks = getFieldChecks(tags);\n * // [\n * // { check: 'min_length', minimum: 1, ... },\n * // { check: 'max_length', maximum: 5, ... }\n * // ]\n * ```\n *\n * @example\n * String with format validation\n * ```typescript\n * const email = z.string().email();\n * const checks = getFieldChecks(email);\n * // [\n * // { check: 'string_format', format: 'email', ... }\n * // ]\n * ```\n *\n * @example\n * Unwrapping optional/nullable/default layers\n * ```typescript\n * const bio = z.string().min(10).max(500).optional();\n * const checks = getFieldChecks(bio);\n * // [\n * // { check: 'min_length', minimum: 10, ... },\n * // { check: 'max_length', maximum: 500, ... }\n * // ]\n * ```\n *\n * @see {@link ZodUnionCheck} for all supported check types\n * @since 0.4.0\n */\nexport function getFieldChecks<T extends z.ZodTypeAny>(\n field: T,\n): Array<ZodUnionCheck> {\n const primitiveType = getPrimitiveType(field);\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return (primitiveType.def.checks?.map((check) => check._zod.def) ||\n []) as Array<ZodUnionCheck>;\n}\n","import * as z from 'zod';\nimport { canUnwrap, unwrapUnion } from './schema';\nimport type { Simplify } from './types';\n\n/**\n * Extracts the default value from a Zod field, recursively unwrapping optional, nullable, and union layers.\n *\n * This function traverses through wrapper types (like `ZodOptional`, `ZodNullable`, `ZodUnion`) to find\n * the underlying `ZodDefault` and returns its default value. If no default is found, returns `undefined`.\n *\n * **Union handling:** For union types, extracts the default from the first option. If the first option\n * has no default, returns `undefined` (defaults in other union options are not checked).\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 * Union with default in first option\n * ```typescript\n * const field = z.union([z.string().default('hello'), z.number()]);\n * const defaultValue = extractDefault(field);\n * // Result: 'hello' (extracts from first union option)\n * ```\n *\n * @example\n * Union with default in second option\n * ```typescript\n * const field = z.union([z.string(), z.number().default(42)]);\n * const defaultValue = extractDefault(field);\n * // Result: undefined (only checks first option)\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 if (field instanceof z.ZodUnion) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return extractDefault(unwrapUnion(field).field) 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,21 +48,23 @@ function removeDefault(field) {
28
48
  }
29
49
  return field;
30
50
  }
31
- var checkIfFieldIsRequired = (field) => {
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;
43
60
  const emptyArrayResult = primitiveType.type === "array" && defaultRemovedField.safeParse([]).success;
44
61
  return !undefinedResult && !nullResult && !emptyStringResult && !emptyArrayResult;
45
62
  };
63
+ function getFieldChecks(field) {
64
+ var _a;
65
+ const primitiveType = getPrimitiveType(field);
66
+ return ((_a = primitiveType.def.checks) == null ? void 0 : _a.map((check) => check._zod.def)) || [];
67
+ }
46
68
 
47
69
  // src/defaults.ts
48
70
  function extractDefault(field) {
@@ -52,6 +74,9 @@ function extractDefault(field) {
52
74
  if (canUnwrap(field)) {
53
75
  return extractDefault(field.unwrap());
54
76
  }
77
+ if (field instanceof z.ZodUnion) {
78
+ return extractDefault(unwrapUnion(field).field);
79
+ }
55
80
  return void 0;
56
81
  }
57
82
  function getSchemaDefaults(schema) {
@@ -67,6 +92,6 @@ function getSchemaDefaults(schema) {
67
92
  return defaults;
68
93
  }
69
94
 
70
- export { canUnwrap, checkIfFieldIsRequired, extractDefault, getPrimitiveType, getSchemaDefaults, removeDefault };
95
+ export { canUnwrap, extractDefault, getFieldChecks, getPrimitiveType, getSchemaDefaults, removeDefault, requiresValidInput, unwrapUnion };
71
96
  //# sourceMappingURL=index.mjs.map
72
97
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/schema.ts","../src/defaults.ts"],"names":["z2"],"mappings":";;;AA2BO,SAAS,UACd,KAAA,EACqC;AACrC,EAAA,OAAO,QAAA,IAAY,KAAA,IAAS,OAAO,KAAA,CAAM,MAAA,KAAW,UAAA;AACtD;AAuCO,IAAM,gBAAA,GAAmB,CAC9B,KAAA,KACiB;AAEjB,EAAA,IAAI,iBAAmB,CAAA,CAAA,QAAA,EAAU;AAC/B,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,IAAI,SAAA,CAAU,KAAK,CAAA,EAAG;AACpB,IAAA,OAAO,gBAAA,CAAiB,KAAA,CAAM,MAAA,EAAQ,CAAA;AAAA,EACxC;AAEA,EAAA,OAAO,KAAA;AACT;AAgDO,SAAS,cACd,KAAA,EACoB;AACpB,EAAA,IAAI,iBAAmB,CAAA,CAAA,UAAA,EAAY;AAEjC,IAAA,OAAO,MAAM,MAAA,EAAO;AAAA,EACtB;AAEA,EAAA,IAAI,eAAe,KAAA,CAAM,GAAA,IAAO,KAAA,CAAM,GAAA,CAAI,qBAAuB,CAAA,CAAA,OAAA,EAAS;AACxE,IAAA,MAAM,KAAA,GAAQ,aAAA,CAAc,KAAA,CAAM,GAAA,CAAI,SAAS,CAAA;AAE/C,IAAA,IAAI,iBAAmB,CAAA,CAAA,WAAA,EAAa;AAElC,MAAA,OAAO,MAAM,QAAA,EAAS;AAAA,IACxB;AACA,IAAA,IAAI,iBAAmB,CAAA,CAAA,WAAA,EAAa;AAElC,MAAA,OAAO,MAAM,QAAA,EAAS;AAAA,IACxB;AAAA,EACF;AAGA,EAAA,OAAO,KAAA;AACT;AAgEO,IAAM,sBAAA,GAAyB,CAAsB,KAAA,KAAa;AAEvE,EAAA,MAAM,eAAA,GAAkB,KAAA,CAAM,SAAA,CAAU,MAAS,CAAA,CAAE,OAAA;AACnD,EAAA,IAAI,eAAA,EAAiB;AACnB,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,MAAM,mBAAA,GAAsB,cAAc,KAAK,CAAA;AAE/C,EAAA,IAAI,EAAE,+BAAiC,CAAA,CAAA,OAAA,CAAA,EAAU;AAC/C,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,MAAM,UAAA,GAAa,mBAAA,CAAoB,SAAA,CAAU,IAAI,CAAA,CAAE,OAAA;AAEvD,EAAA,MAAM,aAAA,GAAgB,iBAAiB,mBAAmB,CAAA;AAE1D,EAAA,MAAM,oBACJ,aAAA,CAAc,IAAA,KAAS,YACvB,mBAAA,CAAoB,SAAA,CAAU,EAAE,CAAA,CAAE,OAAA;AAEpC,EAAA,MAAM,gBAAA,GACJ,cAAc,IAAA,KAAS,OAAA,IAAW,oBAAoB,SAAA,CAAU,EAAE,CAAA,CAAE,OAAA;AAEtE,EAAA,OACE,CAAC,eAAA,IAAmB,CAAC,UAAA,IAAc,CAAC,qBAAqB,CAAC,gBAAA;AAE9D;;;AC7MO,SAAS,eACd,KAAA,EACwB;AACxB,EAAA,IAAI,iBAAmBA,CAAA,CAAA,UAAA,EAAY;AAEjC,IAAA,OAAO,MAAM,GAAA,CAAI,YAAA;AAAA,EACnB;AAEA,EAAA,IAAI,SAAA,CAAU,KAAK,CAAA,EAAG;AAEpB,IAAA,OAAO,cAAA,CAAe,KAAA,CAAM,MAAA,EAAQ,CAAA;AAAA,EACtC;AAEA,EAAA,OAAO,MAAA;AACT;AA4DO,SAAS,kBACd,MAAA,EAC+B;AAC/B,EAAA,MAAM,WAAoC,EAAC;AAE3C,EAAA,KAAA,MAAW,GAAA,IAAO,OAAO,KAAA,EAAO;AAC9B,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,GAAG,CAAA;AAC9B,IAAA,IAAI,CAAC,KAAA,EAAO;AAGZ,IAAA,MAAM,YAAA,GAAe,eAAe,KAAK,CAAA;AACzC,IAAA,IAAI,iBAAiB,MAAA,EAAW;AAC9B,MAAA,QAAA,CAAS,GAAG,CAAA,GAAI,YAAA;AAAA,IAClB;AAAA,EACF;AAGA,EAAA,OAAO,QAAA;AACT","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';\nimport type {\n $ZodCheckBigIntFormatDef,\n $ZodCheckEndsWithDef,\n $ZodCheckGreaterThanDef,\n $ZodCheckIncludesDef,\n $ZodCheckLengthEqualsDef,\n $ZodCheckLessThanDef,\n $ZodCheckLowerCaseDef,\n $ZodCheckMaxLengthDef,\n $ZodCheckMaxSizeDef,\n $ZodCheckMimeTypeDef,\n $ZodCheckMinLengthDef,\n $ZodCheckMinSizeDef,\n $ZodCheckMultipleOfDef,\n $ZodCheckNumberFormatDef,\n $ZodCheckOverwriteDef,\n $ZodCheckPropertyDef,\n $ZodCheckRegexDef,\n $ZodCheckSizeEqualsDef,\n $ZodCheckStartsWithDef,\n $ZodCheckStringFormatDef,\n $ZodCheckUpperCaseDef,\n} from 'zod/v4/core';\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\n/**\n * Union type of all Zod check definition types.\n *\n * Includes all validation check types supported by Zod v4:\n * - **Length checks**: `min_length`, `max_length`, `length_equals` (strings, arrays)\n * - **Size checks**: `min_size`, `max_size`, `size_equals` (files, sets, maps)\n * - **Numeric checks**: `greater_than`, `less_than`, `multiple_of`\n * - **Format checks**: `number_format` (int32, float64, etc.), `bigint_format`, `string_format` (email, url, uuid, etc.)\n * - **String pattern checks**: `regex`, `lowercase`, `uppercase`, `includes`, `starts_with`, `ends_with`\n * - **Other checks**: `property`, `mime_type`, `overwrite`\n *\n * @since 0.4.0\n */\nexport type ZodUnionCheck =\n | $ZodCheckLessThanDef\n | $ZodCheckGreaterThanDef\n | $ZodCheckMultipleOfDef\n | $ZodCheckNumberFormatDef\n | $ZodCheckBigIntFormatDef\n | $ZodCheckMaxSizeDef\n | $ZodCheckMinSizeDef\n | $ZodCheckSizeEqualsDef\n | $ZodCheckMaxLengthDef\n | $ZodCheckMinLengthDef\n | $ZodCheckLengthEqualsDef\n | $ZodCheckStringFormatDef\n | $ZodCheckRegexDef\n | $ZodCheckLowerCaseDef\n | $ZodCheckUpperCaseDef\n | $ZodCheckIncludesDef\n | $ZodCheckStartsWithDef\n | $ZodCheckEndsWithDef\n | $ZodCheckPropertyDef\n | $ZodCheckMimeTypeDef\n | $ZodCheckOverwriteDef;\n\n/**\n * Extracts all validation check definitions from a Zod schema field.\n *\n * This function analyzes a Zod field and returns all check definitions as defined\n * by Zod's internal structure. Returns Zod's raw check definition objects directly,\n * including all properties like `check`, `minimum`, `maximum`, `value`, `inclusive`,\n * `format`, `pattern`, etc.\n *\n * **Unwrapping behavior:** Automatically unwraps optional, nullable, and default layers.\n * For unions, checks only the first option (same as other schema utilities).\n *\n * **Supported check types:** Returns any of the 21 check types defined in {@link ZodUnionCheck},\n * including length, size, numeric range, format validation, string patterns, and more.\n *\n * @template T - The Zod type to extract checks from\n * @param field - The Zod field to analyze\n * @returns Array of Zod check definition objects (see {@link ZodUnionCheck})\n *\n * @example\n * String with length constraints\n * ```typescript\n * const username = z.string().min(3).max(20);\n * const checks = getFieldChecks(username);\n * // [\n * // { check: 'min_length', minimum: 3, when: [Function], ... },\n * // { check: 'max_length', maximum: 20, when: [Function], ... }\n * // ]\n * ```\n *\n * @example\n * Number with range constraints\n * ```typescript\n * const age = z.number().min(18).max(120);\n * const checks = getFieldChecks(age);\n * // [\n * // { check: 'greater_than', value: 18, inclusive: true, when: [Function], ... },\n * // { check: 'less_than', value: 120, inclusive: true, when: [Function], ... }\n * // ]\n * ```\n *\n * @example\n * Array with item count constraints\n * ```typescript\n * const tags = z.array(z.string()).min(1).max(5);\n * const checks = getFieldChecks(tags);\n * // [\n * // { check: 'min_length', minimum: 1, ... },\n * // { check: 'max_length', maximum: 5, ... }\n * // ]\n * ```\n *\n * @example\n * String with format validation\n * ```typescript\n * const email = z.string().email();\n * const checks = getFieldChecks(email);\n * // [\n * // { check: 'string_format', format: 'email', ... }\n * // ]\n * ```\n *\n * @example\n * Unwrapping optional/nullable/default layers\n * ```typescript\n * const bio = z.string().min(10).max(500).optional();\n * const checks = getFieldChecks(bio);\n * // [\n * // { check: 'min_length', minimum: 10, ... },\n * // { check: 'max_length', maximum: 500, ... }\n * // ]\n * ```\n *\n * @see {@link ZodUnionCheck} for all supported check types\n * @since 0.4.0\n */\nexport function getFieldChecks<T extends z.ZodTypeAny>(\n field: T,\n): Array<ZodUnionCheck> {\n const primitiveType = getPrimitiveType(field);\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return (primitiveType.def.checks?.map((check) => check._zod.def) ||\n []) as Array<ZodUnionCheck>;\n}\n","import * as z from 'zod';\nimport { canUnwrap, unwrapUnion } from './schema';\nimport type { Simplify } from './types';\n\n/**\n * Extracts the default value from a Zod field, recursively unwrapping optional, nullable, and union layers.\n *\n * This function traverses through wrapper types (like `ZodOptional`, `ZodNullable`, `ZodUnion`) to find\n * the underlying `ZodDefault` and returns its default value. If no default is found, returns `undefined`.\n *\n * **Union handling:** For union types, extracts the default from the first option. If the first option\n * has no default, returns `undefined` (defaults in other union options are not checked).\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 * Union with default in first option\n * ```typescript\n * const field = z.union([z.string().default('hello'), z.number()]);\n * const defaultValue = extractDefault(field);\n * // Result: 'hello' (extracts from first union option)\n * ```\n *\n * @example\n * Union with default in second option\n * ```typescript\n * const field = z.union([z.string(), z.number().default(42)]);\n * const defaultValue = extractDefault(field);\n * // Result: undefined (only checks first option)\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 if (field instanceof z.ZodUnion) {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return extractDefault(unwrapUnion(field).field) 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.2.0",
3
+ "version": "0.5.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": [