@zod-utils/core 0.8.0 → 0.9.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/dist/index.d.mts CHANGED
@@ -51,8 +51,9 @@ type Simplify<T> = {
51
51
  * This function traverses through wrapper types (like `ZodOptional`, `ZodNullable`, `ZodUnion`) to find
52
52
  * the underlying `ZodDefault` and returns its default value. If no default is found, returns `undefined`.
53
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).
54
+ * **Union handling:** For union types, strips nullish types (null/undefined) first. If only one type
55
+ * remains after stripping, extracts the default from that type. If multiple non-nullish types remain,
56
+ * returns `undefined` (does not extract from any option).
56
57
  *
57
58
  * @template T - The Zod type to extract default from
58
59
  * @param field - The Zod field to extract default from
@@ -75,19 +76,19 @@ type Simplify<T> = {
75
76
  * ```
76
77
  *
77
78
  * @example
78
- * Union with default in first option
79
+ * Union with only nullish types stripped to single type
79
80
  * ```typescript
80
- * const field = z.union([z.string().default('hello'), z.number()]);
81
+ * const field = z.union([z.string().default('hello'), z.null()]);
81
82
  * const defaultValue = extractDefault(field);
82
- * // Result: 'hello' (extracts from first union option)
83
+ * // Result: 'hello' (null stripped, leaving only string)
83
84
  * ```
84
85
  *
85
86
  * @example
86
- * Union with default in second option
87
+ * Union with multiple non-nullish types
87
88
  * ```typescript
88
- * const field = z.union([z.string(), z.number().default(42)]);
89
+ * const field = z.union([z.string().default('hello'), z.number()]);
89
90
  * const defaultValue = extractDefault(field);
90
- * // Result: undefined (only checks first option)
91
+ * // Result: undefined (multiple non-nullish types - no default extracted)
91
92
  * ```
92
93
  *
93
94
  * @example
@@ -99,6 +100,7 @@ type Simplify<T> = {
99
100
  * ```
100
101
  *
101
102
  * @see {@link getSchemaDefaults} for extracting defaults from entire schemas
103
+ * @see {@link tryStripNullishOnly} for union nullish stripping logic
102
104
  * @since 0.1.0
103
105
  */
104
106
  declare function extractDefault<T extends z.ZodTypeAny>(field: T): z.infer<T> | undefined;
@@ -114,8 +116,8 @@ declare function extractDefault<T extends z.ZodTypeAny>(field: T): z.infer<T> |
114
116
  * **Component handling:** For form inputs without explicit defaults (like `z.string()` or `z.number()`),
115
117
  * use the `?? ''` pattern in your components: `<Input value={field.value ?? ''} />`
116
118
  *
117
- * @template T - The Zod object schema type
118
- * @param schema - The Zod object schema to extract defaults from
119
+ * @template TSchema - The Zod object schema type
120
+ * @param targetSchema - The Zod object schema to extract defaults from
119
121
  * @returns A partial object containing only fields with explicit default values
120
122
  *
121
123
  * @example
@@ -174,7 +176,12 @@ declare function extractDefault<T extends z.ZodTypeAny>(field: T): z.infer<T> |
174
176
  * @see {@link extractDefault} for extracting defaults from individual fields
175
177
  * @since 0.1.0
176
178
  */
177
- declare function getSchemaDefaults<T extends z.ZodObject>(schema: T): Simplify<Partial<z.infer<T>>>;
179
+ declare function getSchemaDefaults<TSchema extends z.ZodObject | z.ZodDiscriminatedUnion<Array<z.ZodObject>>, TObj extends z.infer<TSchema>, TDiscriminatorField extends keyof TObj = keyof TObj>(schema: TSchema, options?: {
180
+ discriminator?: {
181
+ field: TDiscriminatorField;
182
+ value: TObj[TDiscriminatorField];
183
+ };
184
+ }): Simplify<Partial<z.infer<TSchema>>>;
178
185
 
179
186
  /**
180
187
  * Type representing a Zod type that has an unwrap method
@@ -204,83 +211,53 @@ type Unwrappable = {
204
211
  */
205
212
  declare function canUnwrap(field: z.ZodTypeAny): field is z.ZodTypeAny & Unwrappable;
206
213
  /**
207
- * Unwraps a ZodUnion type and returns the first field and all union options.
214
+ * Attempts to strip nullish types from a union and return the single remaining type.
208
215
  *
209
- * This function extracts the individual type options from a union type.
210
- * By default, it filters out `ZodNull` and `ZodUndefined` types, returning only
211
- * the meaningful type options. You can disable this filtering to get all options.
216
+ * This function filters out `ZodNull` and `ZodUndefined` from union types. If exactly
217
+ * one type remains after filtering, it returns that unwrapped type. Otherwise, it returns
218
+ * `false` to indicate the union couldn't be simplified to a single type.
212
219
  *
213
- * @template T - The Zod type to unwrap
214
- * @param field - The Zod field (union or single type)
215
- * @param options - Configuration options
216
- * @param options.filterNullish - Whether to filter out null and undefined types (default: true)
217
- * @returns Object with `field` (first option) and `union` (all options array)
220
+ * @param field - The Zod field to process
221
+ * @returns The unwrapped type if only one remains, otherwise `false`
218
222
  *
219
223
  * @example
220
- * Basic union unwrapping
224
+ * Union with only nullish types filtered - returns single type
221
225
  * ```typescript
222
- * const field = z.union([z.string(), z.number()]);
223
- * const result = unwrapUnion(field);
224
- * // Result: { field: z.string(), union: [z.string(), z.number()] }
226
+ * const field = z.union([z.string(), z.null(), z.undefined()]);
227
+ * const result = tryStripNullishOnly(field);
228
+ * // Result: z.string() (unwrapped)
225
229
  * ```
226
230
  *
227
231
  * @example
228
- * Union with null (filtered by default)
232
+ * Union with multiple non-nullish types - returns false
229
233
  * ```typescript
230
- * const field = z.union([z.string(), z.null()]);
231
- * const result = unwrapUnion(field);
232
- * // Result: { field: z.string(), union: [z.string()] }
233
- * ```
234
- *
235
- * @example
236
- * Union with null (keep all options)
237
- * ```typescript
238
- * const field = z.union([z.string(), z.null()]);
239
- * const result = unwrapUnion(field, { filterNullish: false });
240
- * // Result: { field: z.string(), union: [z.string(), z.null()] }
234
+ * const field = z.union([z.string(), z.number()]);
235
+ * const result = tryStripNullishOnly(field);
236
+ * // Result: false (cannot simplify to single type)
241
237
  * ```
242
238
  *
243
239
  * @example
244
- * Non-union type (returns single field)
240
+ * Non-union type - returns false
245
241
  * ```typescript
246
242
  * const field = z.string();
247
- * const result = unwrapUnion(field);
248
- * // Result: { field: z.string(), union: [z.string()] }
249
- * ```
250
- *
251
- * @example
252
- * Nullable as union
253
- * ```typescript
254
- * const field = z.string().nullable(); // This is z.union([z.string(), z.null()])
255
- * const result = unwrapUnion(field);
256
- * // Result: { field: z.string(), union: [z.string()] } (null filtered out)
257
- * ```
258
- *
259
- * @example
260
- * Using the first field for type checking
261
- * ```typescript
262
- * const field = z.union([z.string(), z.number()]);
263
- * const { field: firstField, union } = unwrapUnion(field);
264
- * if (firstField instanceof z.ZodString) {
265
- * console.log('First type is string');
266
- * }
243
+ * const result = tryStripNullishOnly(field);
244
+ * // Result: false (not a union)
267
245
  * ```
268
246
  *
269
247
  * @see {@link getPrimitiveType} for unwrapping wrapper types
270
- * @since 0.1.0
248
+ * @since 0.5.0
271
249
  */
272
- declare function unwrapUnion<T extends z.ZodTypeAny>(field: T, options?: {
273
- filterNullish?: boolean;
274
- }): {
275
- field: z.ZodTypeAny;
276
- union: z.ZodTypeAny[];
277
- };
250
+ declare function tryStripNullishOnly(field: z.ZodTypeAny): z.ZodType | false;
278
251
  /**
279
252
  * Gets the underlying primitive type of a Zod field by recursively unwrapping wrapper types.
280
253
  *
281
254
  * This function removes wrapper layers (optional, nullable, default) to reveal the base type.
282
255
  * **Important:** It stops at array types without unwrapping them, treating arrays as primitives.
283
256
  *
257
+ * **Union handling:** For union types, strips nullish types (null/undefined) first. If only one
258
+ * type remains after stripping, unwraps to that type. If multiple non-nullish types remain,
259
+ * returns the union as-is (does not unwrap).
260
+ *
284
261
  * @template T - The Zod type to unwrap
285
262
  * @param field - The Zod field to unwrap
286
263
  * @returns The unwrapped primitive Zod type
@@ -309,7 +286,24 @@ declare function unwrapUnion<T extends z.ZodTypeAny>(field: T, options?: {
309
286
  * // Result: z.number()
310
287
  * ```
311
288
  *
289
+ * @example
290
+ * Union with only nullish types stripped to single type
291
+ * ```typescript
292
+ * const field = z.union([z.string(), z.null()]);
293
+ * const primitive = getPrimitiveType(field);
294
+ * // Result: z.string() (null stripped, leaving only string)
295
+ * ```
296
+ *
297
+ * @example
298
+ * Union with multiple non-nullish types
299
+ * ```typescript
300
+ * const field = z.union([z.string(), z.number()]);
301
+ * const primitive = getPrimitiveType(field);
302
+ * // Result: z.union([z.string(), z.number()]) (returned as-is)
303
+ * ```
304
+ *
312
305
  * @see {@link canUnwrap} for checking if a field can be unwrapped
306
+ * @see {@link tryStripNullishOnly} for union nullish stripping logic
313
307
  * @since 0.1.0
314
308
  */
315
309
  declare const getPrimitiveType: <T extends z.ZodType>(field: T) => z.ZodTypeAny;
@@ -521,5 +515,98 @@ type ZodUnionCheck = $ZodCheckLessThanDef | $ZodCheckGreaterThanDef | $ZodCheckM
521
515
  * @since 0.4.0
522
516
  */
523
517
  declare function getFieldChecks<T extends z.ZodTypeAny>(field: T): Array<ZodUnionCheck>;
518
+ /**
519
+ * Extracts a specific schema option from a discriminated union based on the discriminator field value.
520
+ *
521
+ * This function finds and returns the matching schema option from a `ZodDiscriminatedUnion` by
522
+ * comparing the discriminator field value. It's used internally by {@link getSchemaDefaults} to
523
+ * extract defaults from the correct schema variant in a discriminated union.
524
+ *
525
+ * **How it works:**
526
+ * 1. Iterates through all options in the discriminated union
527
+ * 2. For each option, checks if the discriminator field matches the provided value
528
+ * 3. Returns the first matching schema option, or `undefined` if no match found
529
+ *
530
+ * @template TSchema - The discriminated union schema type
531
+ * @template TObj - The inferred type of the schema
532
+ * @template TDiscriminatorField - The discriminator field key type
533
+ * @param params - Parameters object
534
+ * @param params.schema - The discriminated union schema to search
535
+ * @param params.discriminatorField - The discriminator field name (e.g., "mode", "type")
536
+ * @param params.discriminatorValue - The discriminator value to match (e.g., "create", "edit")
537
+ * @returns The matching schema option, or `undefined` if not found
538
+ *
539
+ * @example
540
+ * Basic discriminated union - create/edit mode
541
+ * ```typescript
542
+ * const userSchema = z.discriminatedUnion('mode', [
543
+ * z.object({
544
+ * mode: z.literal('create'),
545
+ * name: z.string(),
546
+ * age: z.number().optional(),
547
+ * }),
548
+ * z.object({
549
+ * mode: z.literal('edit'),
550
+ * id: z.number(),
551
+ * name: z.string().optional(),
552
+ * }),
553
+ * ]);
554
+ *
555
+ * // Extract the "create" schema
556
+ * const createSchema = extractDiscriminatedSchema({
557
+ * schema: userSchema,
558
+ * discriminatorField: 'mode',
559
+ * discriminatorValue: 'create',
560
+ * });
561
+ * // Result: z.object({ mode: z.literal('create'), name: z.string(), age: z.number().optional() })
562
+ *
563
+ * // Extract the "edit" schema
564
+ * const editSchema = extractDiscriminatedSchema({
565
+ * schema: userSchema,
566
+ * discriminatorField: 'mode',
567
+ * discriminatorValue: 'edit',
568
+ * });
569
+ * // Result: z.object({ mode: z.literal('edit'), id: z.number(), name: z.string().optional() })
570
+ * ```
571
+ *
572
+ * @example
573
+ * Type-based discrimination
574
+ * ```typescript
575
+ * const eventSchema = z.discriminatedUnion('type', [
576
+ * z.object({ type: z.literal('click'), x: z.number(), y: z.number() }),
577
+ * z.object({ type: z.literal('keypress'), key: z.string() }),
578
+ * ]);
579
+ *
580
+ * const clickSchema = extractDiscriminatedSchema({
581
+ * schema: eventSchema,
582
+ * discriminatorField: 'type',
583
+ * discriminatorValue: 'click',
584
+ * });
585
+ * // Result: z.object({ type: z.literal('click'), x: z.number(), y: z.number() })
586
+ * ```
587
+ *
588
+ * @example
589
+ * Invalid discriminator value
590
+ * ```typescript
591
+ * const schema = z.discriminatedUnion('mode', [
592
+ * z.object({ mode: z.literal('create'), name: z.string() }),
593
+ * ]);
594
+ *
595
+ * const result = extractDiscriminatedSchema({
596
+ * schema,
597
+ * discriminatorField: 'mode',
598
+ * discriminatorValue: 'invalid', // doesn't match any option
599
+ * });
600
+ * // Result: undefined
601
+ * ```
602
+ *
603
+ * @see {@link getSchemaDefaults} for usage with discriminated unions
604
+ * @since 0.6.0
605
+ */
606
+ declare const extractDiscriminatedSchema: <TSchema extends z.ZodDiscriminatedUnion<Array<z.ZodObject>>, TObj extends z.infer<TSchema>, TDiscriminatorField extends keyof TObj = keyof TObj>({ schema, discriminatorField, discriminatorValue, }: {
607
+ schema: TSchema;
608
+ discriminatorField: TDiscriminatorField;
609
+ discriminatorValue: TObj[TDiscriminatorField];
610
+ }) => z.ZodObject<z.core.$ZodLooseShape, z.core.$strip> | undefined;
524
611
 
525
- export { type Simplify, type ZodUnionCheck, canUnwrap, extractDefault, getFieldChecks, getPrimitiveType, getSchemaDefaults, removeDefault, requiresValidInput, unwrapUnion };
612
+ export { type Simplify, type ZodUnionCheck, canUnwrap, extractDefault, extractDiscriminatedSchema, getFieldChecks, getPrimitiveType, getSchemaDefaults, removeDefault, requiresValidInput, tryStripNullishOnly };
package/dist/index.d.ts CHANGED
@@ -51,8 +51,9 @@ type Simplify<T> = {
51
51
  * This function traverses through wrapper types (like `ZodOptional`, `ZodNullable`, `ZodUnion`) to find
52
52
  * the underlying `ZodDefault` and returns its default value. If no default is found, returns `undefined`.
53
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).
54
+ * **Union handling:** For union types, strips nullish types (null/undefined) first. If only one type
55
+ * remains after stripping, extracts the default from that type. If multiple non-nullish types remain,
56
+ * returns `undefined` (does not extract from any option).
56
57
  *
57
58
  * @template T - The Zod type to extract default from
58
59
  * @param field - The Zod field to extract default from
@@ -75,19 +76,19 @@ type Simplify<T> = {
75
76
  * ```
76
77
  *
77
78
  * @example
78
- * Union with default in first option
79
+ * Union with only nullish types stripped to single type
79
80
  * ```typescript
80
- * const field = z.union([z.string().default('hello'), z.number()]);
81
+ * const field = z.union([z.string().default('hello'), z.null()]);
81
82
  * const defaultValue = extractDefault(field);
82
- * // Result: 'hello' (extracts from first union option)
83
+ * // Result: 'hello' (null stripped, leaving only string)
83
84
  * ```
84
85
  *
85
86
  * @example
86
- * Union with default in second option
87
+ * Union with multiple non-nullish types
87
88
  * ```typescript
88
- * const field = z.union([z.string(), z.number().default(42)]);
89
+ * const field = z.union([z.string().default('hello'), z.number()]);
89
90
  * const defaultValue = extractDefault(field);
90
- * // Result: undefined (only checks first option)
91
+ * // Result: undefined (multiple non-nullish types - no default extracted)
91
92
  * ```
92
93
  *
93
94
  * @example
@@ -99,6 +100,7 @@ type Simplify<T> = {
99
100
  * ```
100
101
  *
101
102
  * @see {@link getSchemaDefaults} for extracting defaults from entire schemas
103
+ * @see {@link tryStripNullishOnly} for union nullish stripping logic
102
104
  * @since 0.1.0
103
105
  */
104
106
  declare function extractDefault<T extends z.ZodTypeAny>(field: T): z.infer<T> | undefined;
@@ -114,8 +116,8 @@ declare function extractDefault<T extends z.ZodTypeAny>(field: T): z.infer<T> |
114
116
  * **Component handling:** For form inputs without explicit defaults (like `z.string()` or `z.number()`),
115
117
  * use the `?? ''` pattern in your components: `<Input value={field.value ?? ''} />`
116
118
  *
117
- * @template T - The Zod object schema type
118
- * @param schema - The Zod object schema to extract defaults from
119
+ * @template TSchema - The Zod object schema type
120
+ * @param targetSchema - The Zod object schema to extract defaults from
119
121
  * @returns A partial object containing only fields with explicit default values
120
122
  *
121
123
  * @example
@@ -174,7 +176,12 @@ declare function extractDefault<T extends z.ZodTypeAny>(field: T): z.infer<T> |
174
176
  * @see {@link extractDefault} for extracting defaults from individual fields
175
177
  * @since 0.1.0
176
178
  */
177
- declare function getSchemaDefaults<T extends z.ZodObject>(schema: T): Simplify<Partial<z.infer<T>>>;
179
+ declare function getSchemaDefaults<TSchema extends z.ZodObject | z.ZodDiscriminatedUnion<Array<z.ZodObject>>, TObj extends z.infer<TSchema>, TDiscriminatorField extends keyof TObj = keyof TObj>(schema: TSchema, options?: {
180
+ discriminator?: {
181
+ field: TDiscriminatorField;
182
+ value: TObj[TDiscriminatorField];
183
+ };
184
+ }): Simplify<Partial<z.infer<TSchema>>>;
178
185
 
179
186
  /**
180
187
  * Type representing a Zod type that has an unwrap method
@@ -204,83 +211,53 @@ type Unwrappable = {
204
211
  */
205
212
  declare function canUnwrap(field: z.ZodTypeAny): field is z.ZodTypeAny & Unwrappable;
206
213
  /**
207
- * Unwraps a ZodUnion type and returns the first field and all union options.
214
+ * Attempts to strip nullish types from a union and return the single remaining type.
208
215
  *
209
- * This function extracts the individual type options from a union type.
210
- * By default, it filters out `ZodNull` and `ZodUndefined` types, returning only
211
- * the meaningful type options. You can disable this filtering to get all options.
216
+ * This function filters out `ZodNull` and `ZodUndefined` from union types. If exactly
217
+ * one type remains after filtering, it returns that unwrapped type. Otherwise, it returns
218
+ * `false` to indicate the union couldn't be simplified to a single type.
212
219
  *
213
- * @template T - The Zod type to unwrap
214
- * @param field - The Zod field (union or single type)
215
- * @param options - Configuration options
216
- * @param options.filterNullish - Whether to filter out null and undefined types (default: true)
217
- * @returns Object with `field` (first option) and `union` (all options array)
220
+ * @param field - The Zod field to process
221
+ * @returns The unwrapped type if only one remains, otherwise `false`
218
222
  *
219
223
  * @example
220
- * Basic union unwrapping
224
+ * Union with only nullish types filtered - returns single type
221
225
  * ```typescript
222
- * const field = z.union([z.string(), z.number()]);
223
- * const result = unwrapUnion(field);
224
- * // Result: { field: z.string(), union: [z.string(), z.number()] }
226
+ * const field = z.union([z.string(), z.null(), z.undefined()]);
227
+ * const result = tryStripNullishOnly(field);
228
+ * // Result: z.string() (unwrapped)
225
229
  * ```
226
230
  *
227
231
  * @example
228
- * Union with null (filtered by default)
232
+ * Union with multiple non-nullish types - returns false
229
233
  * ```typescript
230
- * const field = z.union([z.string(), z.null()]);
231
- * const result = unwrapUnion(field);
232
- * // Result: { field: z.string(), union: [z.string()] }
233
- * ```
234
- *
235
- * @example
236
- * Union with null (keep all options)
237
- * ```typescript
238
- * const field = z.union([z.string(), z.null()]);
239
- * const result = unwrapUnion(field, { filterNullish: false });
240
- * // Result: { field: z.string(), union: [z.string(), z.null()] }
234
+ * const field = z.union([z.string(), z.number()]);
235
+ * const result = tryStripNullishOnly(field);
236
+ * // Result: false (cannot simplify to single type)
241
237
  * ```
242
238
  *
243
239
  * @example
244
- * Non-union type (returns single field)
240
+ * Non-union type - returns false
245
241
  * ```typescript
246
242
  * const field = z.string();
247
- * const result = unwrapUnion(field);
248
- * // Result: { field: z.string(), union: [z.string()] }
249
- * ```
250
- *
251
- * @example
252
- * Nullable as union
253
- * ```typescript
254
- * const field = z.string().nullable(); // This is z.union([z.string(), z.null()])
255
- * const result = unwrapUnion(field);
256
- * // Result: { field: z.string(), union: [z.string()] } (null filtered out)
257
- * ```
258
- *
259
- * @example
260
- * Using the first field for type checking
261
- * ```typescript
262
- * const field = z.union([z.string(), z.number()]);
263
- * const { field: firstField, union } = unwrapUnion(field);
264
- * if (firstField instanceof z.ZodString) {
265
- * console.log('First type is string');
266
- * }
243
+ * const result = tryStripNullishOnly(field);
244
+ * // Result: false (not a union)
267
245
  * ```
268
246
  *
269
247
  * @see {@link getPrimitiveType} for unwrapping wrapper types
270
- * @since 0.1.0
248
+ * @since 0.5.0
271
249
  */
272
- declare function unwrapUnion<T extends z.ZodTypeAny>(field: T, options?: {
273
- filterNullish?: boolean;
274
- }): {
275
- field: z.ZodTypeAny;
276
- union: z.ZodTypeAny[];
277
- };
250
+ declare function tryStripNullishOnly(field: z.ZodTypeAny): z.ZodType | false;
278
251
  /**
279
252
  * Gets the underlying primitive type of a Zod field by recursively unwrapping wrapper types.
280
253
  *
281
254
  * This function removes wrapper layers (optional, nullable, default) to reveal the base type.
282
255
  * **Important:** It stops at array types without unwrapping them, treating arrays as primitives.
283
256
  *
257
+ * **Union handling:** For union types, strips nullish types (null/undefined) first. If only one
258
+ * type remains after stripping, unwraps to that type. If multiple non-nullish types remain,
259
+ * returns the union as-is (does not unwrap).
260
+ *
284
261
  * @template T - The Zod type to unwrap
285
262
  * @param field - The Zod field to unwrap
286
263
  * @returns The unwrapped primitive Zod type
@@ -309,7 +286,24 @@ declare function unwrapUnion<T extends z.ZodTypeAny>(field: T, options?: {
309
286
  * // Result: z.number()
310
287
  * ```
311
288
  *
289
+ * @example
290
+ * Union with only nullish types stripped to single type
291
+ * ```typescript
292
+ * const field = z.union([z.string(), z.null()]);
293
+ * const primitive = getPrimitiveType(field);
294
+ * // Result: z.string() (null stripped, leaving only string)
295
+ * ```
296
+ *
297
+ * @example
298
+ * Union with multiple non-nullish types
299
+ * ```typescript
300
+ * const field = z.union([z.string(), z.number()]);
301
+ * const primitive = getPrimitiveType(field);
302
+ * // Result: z.union([z.string(), z.number()]) (returned as-is)
303
+ * ```
304
+ *
312
305
  * @see {@link canUnwrap} for checking if a field can be unwrapped
306
+ * @see {@link tryStripNullishOnly} for union nullish stripping logic
313
307
  * @since 0.1.0
314
308
  */
315
309
  declare const getPrimitiveType: <T extends z.ZodType>(field: T) => z.ZodTypeAny;
@@ -521,5 +515,98 @@ type ZodUnionCheck = $ZodCheckLessThanDef | $ZodCheckGreaterThanDef | $ZodCheckM
521
515
  * @since 0.4.0
522
516
  */
523
517
  declare function getFieldChecks<T extends z.ZodTypeAny>(field: T): Array<ZodUnionCheck>;
518
+ /**
519
+ * Extracts a specific schema option from a discriminated union based on the discriminator field value.
520
+ *
521
+ * This function finds and returns the matching schema option from a `ZodDiscriminatedUnion` by
522
+ * comparing the discriminator field value. It's used internally by {@link getSchemaDefaults} to
523
+ * extract defaults from the correct schema variant in a discriminated union.
524
+ *
525
+ * **How it works:**
526
+ * 1. Iterates through all options in the discriminated union
527
+ * 2. For each option, checks if the discriminator field matches the provided value
528
+ * 3. Returns the first matching schema option, or `undefined` if no match found
529
+ *
530
+ * @template TSchema - The discriminated union schema type
531
+ * @template TObj - The inferred type of the schema
532
+ * @template TDiscriminatorField - The discriminator field key type
533
+ * @param params - Parameters object
534
+ * @param params.schema - The discriminated union schema to search
535
+ * @param params.discriminatorField - The discriminator field name (e.g., "mode", "type")
536
+ * @param params.discriminatorValue - The discriminator value to match (e.g., "create", "edit")
537
+ * @returns The matching schema option, or `undefined` if not found
538
+ *
539
+ * @example
540
+ * Basic discriminated union - create/edit mode
541
+ * ```typescript
542
+ * const userSchema = z.discriminatedUnion('mode', [
543
+ * z.object({
544
+ * mode: z.literal('create'),
545
+ * name: z.string(),
546
+ * age: z.number().optional(),
547
+ * }),
548
+ * z.object({
549
+ * mode: z.literal('edit'),
550
+ * id: z.number(),
551
+ * name: z.string().optional(),
552
+ * }),
553
+ * ]);
554
+ *
555
+ * // Extract the "create" schema
556
+ * const createSchema = extractDiscriminatedSchema({
557
+ * schema: userSchema,
558
+ * discriminatorField: 'mode',
559
+ * discriminatorValue: 'create',
560
+ * });
561
+ * // Result: z.object({ mode: z.literal('create'), name: z.string(), age: z.number().optional() })
562
+ *
563
+ * // Extract the "edit" schema
564
+ * const editSchema = extractDiscriminatedSchema({
565
+ * schema: userSchema,
566
+ * discriminatorField: 'mode',
567
+ * discriminatorValue: 'edit',
568
+ * });
569
+ * // Result: z.object({ mode: z.literal('edit'), id: z.number(), name: z.string().optional() })
570
+ * ```
571
+ *
572
+ * @example
573
+ * Type-based discrimination
574
+ * ```typescript
575
+ * const eventSchema = z.discriminatedUnion('type', [
576
+ * z.object({ type: z.literal('click'), x: z.number(), y: z.number() }),
577
+ * z.object({ type: z.literal('keypress'), key: z.string() }),
578
+ * ]);
579
+ *
580
+ * const clickSchema = extractDiscriminatedSchema({
581
+ * schema: eventSchema,
582
+ * discriminatorField: 'type',
583
+ * discriminatorValue: 'click',
584
+ * });
585
+ * // Result: z.object({ type: z.literal('click'), x: z.number(), y: z.number() })
586
+ * ```
587
+ *
588
+ * @example
589
+ * Invalid discriminator value
590
+ * ```typescript
591
+ * const schema = z.discriminatedUnion('mode', [
592
+ * z.object({ mode: z.literal('create'), name: z.string() }),
593
+ * ]);
594
+ *
595
+ * const result = extractDiscriminatedSchema({
596
+ * schema,
597
+ * discriminatorField: 'mode',
598
+ * discriminatorValue: 'invalid', // doesn't match any option
599
+ * });
600
+ * // Result: undefined
601
+ * ```
602
+ *
603
+ * @see {@link getSchemaDefaults} for usage with discriminated unions
604
+ * @since 0.6.0
605
+ */
606
+ declare const extractDiscriminatedSchema: <TSchema extends z.ZodDiscriminatedUnion<Array<z.ZodObject>>, TObj extends z.infer<TSchema>, TDiscriminatorField extends keyof TObj = keyof TObj>({ schema, discriminatorField, discriminatorValue, }: {
607
+ schema: TSchema;
608
+ discriminatorField: TDiscriminatorField;
609
+ discriminatorValue: TObj[TDiscriminatorField];
610
+ }) => z.ZodObject<z.core.$ZodLooseShape, z.core.$strip> | undefined;
524
611
 
525
- export { type Simplify, type ZodUnionCheck, canUnwrap, extractDefault, getFieldChecks, getPrimitiveType, getSchemaDefaults, removeDefault, requiresValidInput, unwrapUnion };
612
+ export { type Simplify, type ZodUnionCheck, canUnwrap, extractDefault, extractDiscriminatedSchema, getFieldChecks, getPrimitiveType, getSchemaDefaults, removeDefault, requiresValidInput, tryStripNullishOnly };
package/dist/index.js CHANGED
@@ -26,22 +26,18 @@ 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;
29
+ function tryStripNullishOnly(field) {
31
30
  if (field instanceof z__namespace.ZodUnion) {
32
31
  const unionOptions = [...field.def.options];
33
- const filteredOptions = filterNullish ? unionOptions.filter(
32
+ const filteredOptions = unionOptions.filter(
34
33
  (option) => !(option instanceof z__namespace.ZodNull) && !(option instanceof z__namespace.ZodUndefined)
35
- ) : unionOptions;
36
- return {
37
- field: filteredOptions[0] || field,
38
- union: filteredOptions
39
- };
34
+ );
35
+ const firstOption = filteredOptions[0];
36
+ if (firstOption && filteredOptions.length === 1) {
37
+ return firstOption;
38
+ }
40
39
  }
41
- return {
42
- field,
43
- union: [field]
44
- };
40
+ return false;
45
41
  }
46
42
  var getPrimitiveType = (field) => {
47
43
  if (field instanceof z__namespace.ZodArray) {
@@ -51,7 +47,11 @@ var getPrimitiveType = (field) => {
51
47
  return getPrimitiveType(field.unwrap());
52
48
  }
53
49
  if (field instanceof z__namespace.ZodUnion) {
54
- return getPrimitiveType(unwrapUnion(field).field);
50
+ const unwrapped = tryStripNullishOnly(field);
51
+ if (unwrapped !== false) {
52
+ return getPrimitiveType(unwrapped);
53
+ }
54
+ return field;
55
55
  }
56
56
  return field;
57
57
  };
@@ -87,6 +87,18 @@ function getFieldChecks(field) {
87
87
  const primitiveType = getPrimitiveType(field);
88
88
  return ((_a = primitiveType.def.checks) == null ? void 0 : _a.map((check) => check._zod.def)) || [];
89
89
  }
90
+ var extractDiscriminatedSchema = ({
91
+ schema,
92
+ discriminatorField,
93
+ discriminatorValue
94
+ }) => {
95
+ return schema.options.find((option) => {
96
+ const targetField = option.shape[String(discriminatorField)];
97
+ if (!targetField) return false;
98
+ const parseResult = targetField.safeParse(discriminatorValue);
99
+ return parseResult.success;
100
+ });
101
+ };
90
102
 
91
103
  // src/defaults.ts
92
104
  function extractDefault(field) {
@@ -97,18 +109,37 @@ function extractDefault(field) {
97
109
  return extractDefault(field.unwrap());
98
110
  }
99
111
  if (field instanceof z__namespace.ZodUnion) {
100
- return extractDefault(unwrapUnion(field).field);
112
+ const unwrapped = tryStripNullishOnly(field);
113
+ if (unwrapped !== false) {
114
+ return extractDefault(unwrapped);
115
+ }
116
+ return void 0;
101
117
  }
102
118
  return void 0;
103
119
  }
104
- function getSchemaDefaults(schema) {
120
+ function getSchemaDefaults(schema, options) {
121
+ let targetSchema;
122
+ if (schema instanceof z__namespace.ZodDiscriminatedUnion) {
123
+ if (options == null ? void 0 : options.discriminator) {
124
+ const { field, value } = options.discriminator;
125
+ targetSchema = extractDiscriminatedSchema({
126
+ schema,
127
+ discriminatorField: field,
128
+ discriminatorValue: value
129
+ });
130
+ }
131
+ } else {
132
+ targetSchema = schema;
133
+ }
105
134
  const defaults = {};
106
- for (const key in schema.shape) {
107
- const field = schema.shape[key];
108
- if (!field) continue;
109
- const defaultValue = extractDefault(field);
110
- if (defaultValue !== void 0) {
111
- defaults[key] = defaultValue;
135
+ if (targetSchema) {
136
+ for (const key in targetSchema.shape) {
137
+ const field = targetSchema.shape[key];
138
+ if (!field) continue;
139
+ const defaultValue = extractDefault(field);
140
+ if (defaultValue !== void 0) {
141
+ defaults[key] = defaultValue;
142
+ }
112
143
  }
113
144
  }
114
145
  return defaults;
@@ -116,11 +147,12 @@ function getSchemaDefaults(schema) {
116
147
 
117
148
  exports.canUnwrap = canUnwrap;
118
149
  exports.extractDefault = extractDefault;
150
+ exports.extractDiscriminatedSchema = extractDiscriminatedSchema;
119
151
  exports.getFieldChecks = getFieldChecks;
120
152
  exports.getPrimitiveType = getPrimitiveType;
121
153
  exports.getSchemaDefaults = getSchemaDefaults;
122
154
  exports.removeDefault = removeDefault;
123
155
  exports.requiresValidInput = requiresValidInput;
124
- exports.unwrapUnion = unwrapUnion;
156
+ exports.tryStripNullishOnly = tryStripNullishOnly;
125
157
  //# sourceMappingURL=index.js.map
126
158
  //# 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';\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, returning only fields with explicit `.default()`.\n *\n * This function traverses the schema and collects fields that have explicit default values.\n * 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 * **Component handling:** For form inputs without explicit defaults (like `z.string()` or `z.number()`),\n * use the `?? ''` pattern in your components: `<Input value={field.value ?? ''} />`\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 explicit default values\n *\n * @example\n * Basic usage - only explicit defaults\n * ```typescript\n * const schema = z.object({\n * name: z.string(), // no default → NOT included\n * age: z.number(), // no default → NOT included\n * role: z.string().default('user'), // explicit default → included\n * count: z.number().default(0), // explicit default → included\n * });\n *\n * const defaults = getSchemaDefaults(schema);\n * // Result: { role: 'user', count: 0 }\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 * name: z.string().optional(), // no default → NOT included\n * age: z.number().optional(), // no default → NOT included\n * });\n *\n * const defaults = getSchemaDefaults(schema);\n * // Result: { title: 'Untitled', count: 0 }\n * ```\n *\n * @example\n * Component usage for fields without defaults\n * ```typescript\n * // For string/number fields without defaults, handle in components:\n * <Input value={field.value ?? ''} />\n * <Input type=\"number\" value={field.value ?? ''} />\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 const defaultValue = extractDefault(field);\n if (defaultValue !== undefined) {\n defaults[key] = defaultValue;\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return defaults as Partial<z.infer<T>>;\n}\n"]}
1
+ {"version":3,"sources":["../src/schema.ts","../src/defaults.ts"],"names":["z","z2"],"mappings":"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","file":"index.js","sourcesContent":["import * as z from 'zod';\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 * Attempts to strip nullish types from a union and return the single remaining type.\n *\n * This function filters out `ZodNull` and `ZodUndefined` from union types. If exactly\n * one type remains after filtering, it returns that unwrapped type. Otherwise, it returns\n * `false` to indicate the union couldn't be simplified to a single type.\n *\n * @param field - The Zod field to process\n * @returns The unwrapped type if only one remains, otherwise `false`\n *\n * @example\n * Union with only nullish types filtered - returns single type\n * ```typescript\n * const field = z.union([z.string(), z.null(), z.undefined()]);\n * const result = tryStripNullishOnly(field);\n * // Result: z.string() (unwrapped)\n * ```\n *\n * @example\n * Union with multiple non-nullish types - returns false\n * ```typescript\n * const field = z.union([z.string(), z.number()]);\n * const result = tryStripNullishOnly(field);\n * // Result: false (cannot simplify to single type)\n * ```\n *\n * @example\n * Non-union type - returns false\n * ```typescript\n * const field = z.string();\n * const result = tryStripNullishOnly(field);\n * // Result: false (not a union)\n * ```\n *\n * @see {@link getPrimitiveType} for unwrapping wrapper types\n * @since 0.5.0\n */\nexport function tryStripNullishOnly(field: z.ZodTypeAny): z.ZodType | false {\n if (field instanceof z.ZodUnion) {\n const unionOptions = [...field.def.options];\n\n const filteredOptions = unionOptions.filter(\n (option): option is z.ZodType =>\n !(option instanceof z.ZodNull) && !(option instanceof z.ZodUndefined),\n );\n\n // If exactly one option remains, return it unwrapped\n const firstOption = filteredOptions[0];\n if (firstOption && filteredOptions.length === 1) {\n return firstOption;\n }\n }\n\n // Not a union, or couldn't simplify to single type\n return false;\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 * **Union handling:** For union types, strips nullish types (null/undefined) first. If only one\n * type remains after stripping, unwraps to that type. If multiple non-nullish types remain,\n * returns the union as-is (does not unwrap).\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 * @example\n * Union with only nullish types stripped to single type\n * ```typescript\n * const field = z.union([z.string(), z.null()]);\n * const primitive = getPrimitiveType(field);\n * // Result: z.string() (null stripped, leaving only string)\n * ```\n *\n * @example\n * Union with multiple non-nullish types\n * ```typescript\n * const field = z.union([z.string(), z.number()]);\n * const primitive = getPrimitiveType(field);\n * // Result: z.union([z.string(), z.number()]) (returned as-is)\n * ```\n *\n * @see {@link canUnwrap} for checking if a field can be unwrapped\n * @see {@link tryStripNullishOnly} for union nullish stripping logic\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 const unwrapped = tryStripNullishOnly(field);\n if (unwrapped !== false) {\n return getPrimitiveType(unwrapped);\n }\n\n // Multiple non-nullish types or all nullish - return union as-is\n return 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\n/**\n * Extracts a specific schema option from a discriminated union based on the discriminator field value.\n *\n * This function finds and returns the matching schema option from a `ZodDiscriminatedUnion` by\n * comparing the discriminator field value. It's used internally by {@link getSchemaDefaults} to\n * extract defaults from the correct schema variant in a discriminated union.\n *\n * **How it works:**\n * 1. Iterates through all options in the discriminated union\n * 2. For each option, checks if the discriminator field matches the provided value\n * 3. Returns the first matching schema option, or `undefined` if no match found\n *\n * @template TSchema - The discriminated union schema type\n * @template TObj - The inferred type of the schema\n * @template TDiscriminatorField - The discriminator field key type\n * @param params - Parameters object\n * @param params.schema - The discriminated union schema to search\n * @param params.discriminatorField - The discriminator field name (e.g., \"mode\", \"type\")\n * @param params.discriminatorValue - The discriminator value to match (e.g., \"create\", \"edit\")\n * @returns The matching schema option, or `undefined` if not found\n *\n * @example\n * Basic discriminated union - create/edit mode\n * ```typescript\n * const userSchema = z.discriminatedUnion('mode', [\n * z.object({\n * mode: z.literal('create'),\n * name: z.string(),\n * age: z.number().optional(),\n * }),\n * z.object({\n * mode: z.literal('edit'),\n * id: z.number(),\n * name: z.string().optional(),\n * }),\n * ]);\n *\n * // Extract the \"create\" schema\n * const createSchema = extractDiscriminatedSchema({\n * schema: userSchema,\n * discriminatorField: 'mode',\n * discriminatorValue: 'create',\n * });\n * // Result: z.object({ mode: z.literal('create'), name: z.string(), age: z.number().optional() })\n *\n * // Extract the \"edit\" schema\n * const editSchema = extractDiscriminatedSchema({\n * schema: userSchema,\n * discriminatorField: 'mode',\n * discriminatorValue: 'edit',\n * });\n * // Result: z.object({ mode: z.literal('edit'), id: z.number(), name: z.string().optional() })\n * ```\n *\n * @example\n * Type-based discrimination\n * ```typescript\n * const eventSchema = z.discriminatedUnion('type', [\n * z.object({ type: z.literal('click'), x: z.number(), y: z.number() }),\n * z.object({ type: z.literal('keypress'), key: z.string() }),\n * ]);\n *\n * const clickSchema = extractDiscriminatedSchema({\n * schema: eventSchema,\n * discriminatorField: 'type',\n * discriminatorValue: 'click',\n * });\n * // Result: z.object({ type: z.literal('click'), x: z.number(), y: z.number() })\n * ```\n *\n * @example\n * Invalid discriminator value\n * ```typescript\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string() }),\n * ]);\n *\n * const result = extractDiscriminatedSchema({\n * schema,\n * discriminatorField: 'mode',\n * discriminatorValue: 'invalid', // doesn't match any option\n * });\n * // Result: undefined\n * ```\n *\n * @see {@link getSchemaDefaults} for usage with discriminated unions\n * @since 0.6.0\n */\nexport const extractDiscriminatedSchema = <\n TSchema extends z.ZodDiscriminatedUnion<Array<z.ZodObject>>,\n TObj extends z.infer<TSchema>,\n TDiscriminatorField extends keyof TObj = keyof TObj,\n>({\n schema,\n discriminatorField,\n discriminatorValue,\n}: {\n schema: TSchema;\n discriminatorField: TDiscriminatorField;\n discriminatorValue: TObj[TDiscriminatorField];\n}) => {\n return schema.options.find((option) => {\n const targetField = option.shape[String(discriminatorField)];\n if (!targetField) return false;\n\n const parseResult = targetField.safeParse(discriminatorValue);\n return parseResult.success;\n });\n};\n","import * as z from 'zod';\nimport {\n canUnwrap,\n extractDiscriminatedSchema,\n tryStripNullishOnly,\n} 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, strips nullish types (null/undefined) first. If only one type\n * remains after stripping, extracts the default from that type. If multiple non-nullish types remain,\n * returns `undefined` (does not extract from any option).\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 only nullish types stripped to single type\n * ```typescript\n * const field = z.union([z.string().default('hello'), z.null()]);\n * const defaultValue = extractDefault(field);\n * // Result: 'hello' (null stripped, leaving only string)\n * ```\n *\n * @example\n * Union with multiple non-nullish types\n * ```typescript\n * const field = z.union([z.string().default('hello'), z.number()]);\n * const defaultValue = extractDefault(field);\n * // Result: undefined (multiple non-nullish types - no default extracted)\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 * @see {@link tryStripNullishOnly} for union nullish stripping logic\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 const unwrapped = tryStripNullishOnly(field);\n if (unwrapped !== false) {\n // Successfully unwrapped to single type\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return extractDefault(unwrapped) as z.infer<T>;\n }\n\n // Multiple non-nullish types or all nullish - no default\n return undefined;\n }\n\n return undefined;\n}\n\n/**\n * Extracts default values from a Zod object schema, returning only fields with explicit `.default()`.\n *\n * This function traverses the schema and collects fields that have explicit default values.\n * 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 * **Component handling:** For form inputs without explicit defaults (like `z.string()` or `z.number()`),\n * use the `?? ''` pattern in your components: `<Input value={field.value ?? ''} />`\n *\n * @template TSchema - The Zod object schema type\n * @param targetSchema - The Zod object schema to extract defaults from\n * @returns A partial object containing only fields with explicit default values\n *\n * @example\n * Basic usage - only explicit defaults\n * ```typescript\n * const schema = z.object({\n * name: z.string(), // no default → NOT included\n * age: z.number(), // no default → NOT included\n * role: z.string().default('user'), // explicit default → included\n * count: z.number().default(0), // explicit default → included\n * });\n *\n * const defaults = getSchemaDefaults(schema);\n * // Result: { role: 'user', count: 0 }\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 * name: z.string().optional(), // no default → NOT included\n * age: z.number().optional(), // no default → NOT included\n * });\n *\n * const defaults = getSchemaDefaults(schema);\n * // Result: { title: 'Untitled', count: 0 }\n * ```\n *\n * @example\n * Component usage for fields without defaults\n * ```typescript\n * // For string/number fields without defaults, handle in components:\n * <Input value={field.value ?? ''} />\n * <Input type=\"number\" value={field.value ?? ''} />\n * ```\n *\n * @see {@link extractDefault} for extracting defaults from individual fields\n * @since 0.1.0\n */\nexport function getSchemaDefaults<\n TSchema extends z.ZodObject | z.ZodDiscriminatedUnion<Array<z.ZodObject>>,\n TObj extends z.infer<TSchema>,\n TDiscriminatorField extends keyof TObj = keyof TObj,\n>(\n schema: TSchema,\n options?: {\n discriminator?: {\n field: TDiscriminatorField;\n value: TObj[TDiscriminatorField];\n };\n },\n): Simplify<Partial<z.infer<TSchema>>> {\n let targetSchema: z.ZodObject | undefined;\n if (schema instanceof z.ZodDiscriminatedUnion) {\n if (options?.discriminator) {\n const { field, value } = options.discriminator;\n\n targetSchema = extractDiscriminatedSchema({\n schema,\n discriminatorField: field,\n discriminatorValue: value,\n });\n }\n } else {\n targetSchema = schema;\n }\n\n const defaults: Record<string, unknown> = {};\n\n if (targetSchema) {\n for (const key in targetSchema.shape) {\n const field = targetSchema.shape[key];\n if (!field) continue;\n\n const defaultValue = extractDefault(field);\n if (defaultValue !== undefined) {\n defaults[key] = defaultValue;\n }\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return defaults as Partial<z.infer<TSchema>>;\n}\n"]}
package/dist/index.mjs CHANGED
@@ -4,22 +4,18 @@ 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;
7
+ function tryStripNullishOnly(field) {
9
8
  if (field instanceof z.ZodUnion) {
10
9
  const unionOptions = [...field.def.options];
11
- const filteredOptions = filterNullish ? unionOptions.filter(
10
+ const filteredOptions = unionOptions.filter(
12
11
  (option) => !(option instanceof z.ZodNull) && !(option instanceof z.ZodUndefined)
13
- ) : unionOptions;
14
- return {
15
- field: filteredOptions[0] || field,
16
- union: filteredOptions
17
- };
12
+ );
13
+ const firstOption = filteredOptions[0];
14
+ if (firstOption && filteredOptions.length === 1) {
15
+ return firstOption;
16
+ }
18
17
  }
19
- return {
20
- field,
21
- union: [field]
22
- };
18
+ return false;
23
19
  }
24
20
  var getPrimitiveType = (field) => {
25
21
  if (field instanceof z.ZodArray) {
@@ -29,7 +25,11 @@ var getPrimitiveType = (field) => {
29
25
  return getPrimitiveType(field.unwrap());
30
26
  }
31
27
  if (field instanceof z.ZodUnion) {
32
- return getPrimitiveType(unwrapUnion(field).field);
28
+ const unwrapped = tryStripNullishOnly(field);
29
+ if (unwrapped !== false) {
30
+ return getPrimitiveType(unwrapped);
31
+ }
32
+ return field;
33
33
  }
34
34
  return field;
35
35
  };
@@ -65,6 +65,18 @@ function getFieldChecks(field) {
65
65
  const primitiveType = getPrimitiveType(field);
66
66
  return ((_a = primitiveType.def.checks) == null ? void 0 : _a.map((check) => check._zod.def)) || [];
67
67
  }
68
+ var extractDiscriminatedSchema = ({
69
+ schema,
70
+ discriminatorField,
71
+ discriminatorValue
72
+ }) => {
73
+ return schema.options.find((option) => {
74
+ const targetField = option.shape[String(discriminatorField)];
75
+ if (!targetField) return false;
76
+ const parseResult = targetField.safeParse(discriminatorValue);
77
+ return parseResult.success;
78
+ });
79
+ };
68
80
 
69
81
  // src/defaults.ts
70
82
  function extractDefault(field) {
@@ -75,23 +87,42 @@ function extractDefault(field) {
75
87
  return extractDefault(field.unwrap());
76
88
  }
77
89
  if (field instanceof z.ZodUnion) {
78
- return extractDefault(unwrapUnion(field).field);
90
+ const unwrapped = tryStripNullishOnly(field);
91
+ if (unwrapped !== false) {
92
+ return extractDefault(unwrapped);
93
+ }
94
+ return void 0;
79
95
  }
80
96
  return void 0;
81
97
  }
82
- function getSchemaDefaults(schema) {
98
+ function getSchemaDefaults(schema, options) {
99
+ let targetSchema;
100
+ if (schema instanceof z.ZodDiscriminatedUnion) {
101
+ if (options == null ? void 0 : options.discriminator) {
102
+ const { field, value } = options.discriminator;
103
+ targetSchema = extractDiscriminatedSchema({
104
+ schema,
105
+ discriminatorField: field,
106
+ discriminatorValue: value
107
+ });
108
+ }
109
+ } else {
110
+ targetSchema = schema;
111
+ }
83
112
  const defaults = {};
84
- for (const key in schema.shape) {
85
- const field = schema.shape[key];
86
- if (!field) continue;
87
- const defaultValue = extractDefault(field);
88
- if (defaultValue !== void 0) {
89
- defaults[key] = defaultValue;
113
+ if (targetSchema) {
114
+ for (const key in targetSchema.shape) {
115
+ const field = targetSchema.shape[key];
116
+ if (!field) continue;
117
+ const defaultValue = extractDefault(field);
118
+ if (defaultValue !== void 0) {
119
+ defaults[key] = defaultValue;
120
+ }
90
121
  }
91
122
  }
92
123
  return defaults;
93
124
  }
94
125
 
95
- export { canUnwrap, extractDefault, getFieldChecks, getPrimitiveType, getSchemaDefaults, removeDefault, requiresValidInput, unwrapUnion };
126
+ export { canUnwrap, extractDefault, extractDiscriminatedSchema, getFieldChecks, getPrimitiveType, getSchemaDefaults, removeDefault, requiresValidInput, tryStripNullishOnly };
96
127
  //# sourceMappingURL=index.mjs.map
97
128
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/schema.ts","../src/defaults.ts"],"names":["z2"],"mappings":"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","file":"index.mjs","sourcesContent":["import * as z from 'zod';\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, returning only fields with explicit `.default()`.\n *\n * This function traverses the schema and collects fields that have explicit default values.\n * 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 * **Component handling:** For form inputs without explicit defaults (like `z.string()` or `z.number()`),\n * use the `?? ''` pattern in your components: `<Input value={field.value ?? ''} />`\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 explicit default values\n *\n * @example\n * Basic usage - only explicit defaults\n * ```typescript\n * const schema = z.object({\n * name: z.string(), // no default → NOT included\n * age: z.number(), // no default → NOT included\n * role: z.string().default('user'), // explicit default → included\n * count: z.number().default(0), // explicit default → included\n * });\n *\n * const defaults = getSchemaDefaults(schema);\n * // Result: { role: 'user', count: 0 }\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 * name: z.string().optional(), // no default → NOT included\n * age: z.number().optional(), // no default → NOT included\n * });\n *\n * const defaults = getSchemaDefaults(schema);\n * // Result: { title: 'Untitled', count: 0 }\n * ```\n *\n * @example\n * Component usage for fields without defaults\n * ```typescript\n * // For string/number fields without defaults, handle in components:\n * <Input value={field.value ?? ''} />\n * <Input type=\"number\" value={field.value ?? ''} />\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 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 * Attempts to strip nullish types from a union and return the single remaining type.\n *\n * This function filters out `ZodNull` and `ZodUndefined` from union types. If exactly\n * one type remains after filtering, it returns that unwrapped type. Otherwise, it returns\n * `false` to indicate the union couldn't be simplified to a single type.\n *\n * @param field - The Zod field to process\n * @returns The unwrapped type if only one remains, otherwise `false`\n *\n * @example\n * Union with only nullish types filtered - returns single type\n * ```typescript\n * const field = z.union([z.string(), z.null(), z.undefined()]);\n * const result = tryStripNullishOnly(field);\n * // Result: z.string() (unwrapped)\n * ```\n *\n * @example\n * Union with multiple non-nullish types - returns false\n * ```typescript\n * const field = z.union([z.string(), z.number()]);\n * const result = tryStripNullishOnly(field);\n * // Result: false (cannot simplify to single type)\n * ```\n *\n * @example\n * Non-union type - returns false\n * ```typescript\n * const field = z.string();\n * const result = tryStripNullishOnly(field);\n * // Result: false (not a union)\n * ```\n *\n * @see {@link getPrimitiveType} for unwrapping wrapper types\n * @since 0.5.0\n */\nexport function tryStripNullishOnly(field: z.ZodTypeAny): z.ZodType | false {\n if (field instanceof z.ZodUnion) {\n const unionOptions = [...field.def.options];\n\n const filteredOptions = unionOptions.filter(\n (option): option is z.ZodType =>\n !(option instanceof z.ZodNull) && !(option instanceof z.ZodUndefined),\n );\n\n // If exactly one option remains, return it unwrapped\n const firstOption = filteredOptions[0];\n if (firstOption && filteredOptions.length === 1) {\n return firstOption;\n }\n }\n\n // Not a union, or couldn't simplify to single type\n return false;\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 * **Union handling:** For union types, strips nullish types (null/undefined) first. If only one\n * type remains after stripping, unwraps to that type. If multiple non-nullish types remain,\n * returns the union as-is (does not unwrap).\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 * @example\n * Union with only nullish types stripped to single type\n * ```typescript\n * const field = z.union([z.string(), z.null()]);\n * const primitive = getPrimitiveType(field);\n * // Result: z.string() (null stripped, leaving only string)\n * ```\n *\n * @example\n * Union with multiple non-nullish types\n * ```typescript\n * const field = z.union([z.string(), z.number()]);\n * const primitive = getPrimitiveType(field);\n * // Result: z.union([z.string(), z.number()]) (returned as-is)\n * ```\n *\n * @see {@link canUnwrap} for checking if a field can be unwrapped\n * @see {@link tryStripNullishOnly} for union nullish stripping logic\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 const unwrapped = tryStripNullishOnly(field);\n if (unwrapped !== false) {\n return getPrimitiveType(unwrapped);\n }\n\n // Multiple non-nullish types or all nullish - return union as-is\n return 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\n/**\n * Extracts a specific schema option from a discriminated union based on the discriminator field value.\n *\n * This function finds and returns the matching schema option from a `ZodDiscriminatedUnion` by\n * comparing the discriminator field value. It's used internally by {@link getSchemaDefaults} to\n * extract defaults from the correct schema variant in a discriminated union.\n *\n * **How it works:**\n * 1. Iterates through all options in the discriminated union\n * 2. For each option, checks if the discriminator field matches the provided value\n * 3. Returns the first matching schema option, or `undefined` if no match found\n *\n * @template TSchema - The discriminated union schema type\n * @template TObj - The inferred type of the schema\n * @template TDiscriminatorField - The discriminator field key type\n * @param params - Parameters object\n * @param params.schema - The discriminated union schema to search\n * @param params.discriminatorField - The discriminator field name (e.g., \"mode\", \"type\")\n * @param params.discriminatorValue - The discriminator value to match (e.g., \"create\", \"edit\")\n * @returns The matching schema option, or `undefined` if not found\n *\n * @example\n * Basic discriminated union - create/edit mode\n * ```typescript\n * const userSchema = z.discriminatedUnion('mode', [\n * z.object({\n * mode: z.literal('create'),\n * name: z.string(),\n * age: z.number().optional(),\n * }),\n * z.object({\n * mode: z.literal('edit'),\n * id: z.number(),\n * name: z.string().optional(),\n * }),\n * ]);\n *\n * // Extract the \"create\" schema\n * const createSchema = extractDiscriminatedSchema({\n * schema: userSchema,\n * discriminatorField: 'mode',\n * discriminatorValue: 'create',\n * });\n * // Result: z.object({ mode: z.literal('create'), name: z.string(), age: z.number().optional() })\n *\n * // Extract the \"edit\" schema\n * const editSchema = extractDiscriminatedSchema({\n * schema: userSchema,\n * discriminatorField: 'mode',\n * discriminatorValue: 'edit',\n * });\n * // Result: z.object({ mode: z.literal('edit'), id: z.number(), name: z.string().optional() })\n * ```\n *\n * @example\n * Type-based discrimination\n * ```typescript\n * const eventSchema = z.discriminatedUnion('type', [\n * z.object({ type: z.literal('click'), x: z.number(), y: z.number() }),\n * z.object({ type: z.literal('keypress'), key: z.string() }),\n * ]);\n *\n * const clickSchema = extractDiscriminatedSchema({\n * schema: eventSchema,\n * discriminatorField: 'type',\n * discriminatorValue: 'click',\n * });\n * // Result: z.object({ type: z.literal('click'), x: z.number(), y: z.number() })\n * ```\n *\n * @example\n * Invalid discriminator value\n * ```typescript\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string() }),\n * ]);\n *\n * const result = extractDiscriminatedSchema({\n * schema,\n * discriminatorField: 'mode',\n * discriminatorValue: 'invalid', // doesn't match any option\n * });\n * // Result: undefined\n * ```\n *\n * @see {@link getSchemaDefaults} for usage with discriminated unions\n * @since 0.6.0\n */\nexport const extractDiscriminatedSchema = <\n TSchema extends z.ZodDiscriminatedUnion<Array<z.ZodObject>>,\n TObj extends z.infer<TSchema>,\n TDiscriminatorField extends keyof TObj = keyof TObj,\n>({\n schema,\n discriminatorField,\n discriminatorValue,\n}: {\n schema: TSchema;\n discriminatorField: TDiscriminatorField;\n discriminatorValue: TObj[TDiscriminatorField];\n}) => {\n return schema.options.find((option) => {\n const targetField = option.shape[String(discriminatorField)];\n if (!targetField) return false;\n\n const parseResult = targetField.safeParse(discriminatorValue);\n return parseResult.success;\n });\n};\n","import * as z from 'zod';\nimport {\n canUnwrap,\n extractDiscriminatedSchema,\n tryStripNullishOnly,\n} 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, strips nullish types (null/undefined) first. If only one type\n * remains after stripping, extracts the default from that type. If multiple non-nullish types remain,\n * returns `undefined` (does not extract from any option).\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 only nullish types stripped to single type\n * ```typescript\n * const field = z.union([z.string().default('hello'), z.null()]);\n * const defaultValue = extractDefault(field);\n * // Result: 'hello' (null stripped, leaving only string)\n * ```\n *\n * @example\n * Union with multiple non-nullish types\n * ```typescript\n * const field = z.union([z.string().default('hello'), z.number()]);\n * const defaultValue = extractDefault(field);\n * // Result: undefined (multiple non-nullish types - no default extracted)\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 * @see {@link tryStripNullishOnly} for union nullish stripping logic\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 const unwrapped = tryStripNullishOnly(field);\n if (unwrapped !== false) {\n // Successfully unwrapped to single type\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return extractDefault(unwrapped) as z.infer<T>;\n }\n\n // Multiple non-nullish types or all nullish - no default\n return undefined;\n }\n\n return undefined;\n}\n\n/**\n * Extracts default values from a Zod object schema, returning only fields with explicit `.default()`.\n *\n * This function traverses the schema and collects fields that have explicit default values.\n * 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 * **Component handling:** For form inputs without explicit defaults (like `z.string()` or `z.number()`),\n * use the `?? ''` pattern in your components: `<Input value={field.value ?? ''} />`\n *\n * @template TSchema - The Zod object schema type\n * @param targetSchema - The Zod object schema to extract defaults from\n * @returns A partial object containing only fields with explicit default values\n *\n * @example\n * Basic usage - only explicit defaults\n * ```typescript\n * const schema = z.object({\n * name: z.string(), // no default → NOT included\n * age: z.number(), // no default → NOT included\n * role: z.string().default('user'), // explicit default → included\n * count: z.number().default(0), // explicit default → included\n * });\n *\n * const defaults = getSchemaDefaults(schema);\n * // Result: { role: 'user', count: 0 }\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 * name: z.string().optional(), // no default → NOT included\n * age: z.number().optional(), // no default → NOT included\n * });\n *\n * const defaults = getSchemaDefaults(schema);\n * // Result: { title: 'Untitled', count: 0 }\n * ```\n *\n * @example\n * Component usage for fields without defaults\n * ```typescript\n * // For string/number fields without defaults, handle in components:\n * <Input value={field.value ?? ''} />\n * <Input type=\"number\" value={field.value ?? ''} />\n * ```\n *\n * @see {@link extractDefault} for extracting defaults from individual fields\n * @since 0.1.0\n */\nexport function getSchemaDefaults<\n TSchema extends z.ZodObject | z.ZodDiscriminatedUnion<Array<z.ZodObject>>,\n TObj extends z.infer<TSchema>,\n TDiscriminatorField extends keyof TObj = keyof TObj,\n>(\n schema: TSchema,\n options?: {\n discriminator?: {\n field: TDiscriminatorField;\n value: TObj[TDiscriminatorField];\n };\n },\n): Simplify<Partial<z.infer<TSchema>>> {\n let targetSchema: z.ZodObject | undefined;\n if (schema instanceof z.ZodDiscriminatedUnion) {\n if (options?.discriminator) {\n const { field, value } = options.discriminator;\n\n targetSchema = extractDiscriminatedSchema({\n schema,\n discriminatorField: field,\n discriminatorValue: value,\n });\n }\n } else {\n targetSchema = schema;\n }\n\n const defaults: Record<string, unknown> = {};\n\n if (targetSchema) {\n for (const key in targetSchema.shape) {\n const field = targetSchema.shape[key];\n if (!field) continue;\n\n const defaultValue = extractDefault(field);\n if (defaultValue !== undefined) {\n defaults[key] = defaultValue;\n }\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return defaults as Partial<z.infer<TSchema>>;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zod-utils/core",
3
- "version": "0.8.0",
3
+ "version": "0.9.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",