@wildo-ai/zod-decorators 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/esm/__mocks__/mime.d.ts +12 -0
  2. package/dist/esm/__mocks__/mime.d.ts.map +1 -0
  3. package/dist/esm/__mocks__/mime.js +75 -0
  4. package/dist/esm/__mocks__/mime.js.map +1 -0
  5. package/dist/esm/commons.d.ts +26 -0
  6. package/dist/esm/commons.d.ts.map +1 -0
  7. package/dist/esm/commons.js +41 -0
  8. package/dist/esm/commons.js.map +1 -0
  9. package/dist/esm/currencies.shared.schemas.d.ts +208 -0
  10. package/dist/esm/currencies.shared.schemas.d.ts.map +1 -0
  11. package/dist/esm/currencies.shared.schemas.js +228 -0
  12. package/dist/esm/currencies.shared.schemas.js.map +1 -0
  13. package/dist/esm/files/agent-file-formats.d.ts +99 -0
  14. package/dist/esm/files/agent-file-formats.d.ts.map +1 -0
  15. package/dist/esm/files/agent-file-formats.js +163 -0
  16. package/dist/esm/files/agent-file-formats.js.map +1 -0
  17. package/dist/esm/files/extension-nature-map.d.ts +12 -0
  18. package/dist/esm/files/extension-nature-map.d.ts.map +1 -0
  19. package/dist/esm/files/extension-nature-map.js +63 -0
  20. package/dist/esm/files/extension-nature-map.js.map +1 -0
  21. package/dist/esm/files/file-extensions.enum.d.ts +58 -0
  22. package/dist/esm/files/file-extensions.enum.d.ts.map +1 -0
  23. package/dist/esm/files/file-extensions.enum.js +65 -0
  24. package/dist/esm/files/file-extensions.enum.js.map +1 -0
  25. package/dist/esm/files/mime-registry.d.ts +72 -0
  26. package/dist/esm/files/mime-registry.d.ts.map +1 -0
  27. package/dist/esm/files/mime-registry.js +174 -0
  28. package/dist/esm/files/mime-registry.js.map +1 -0
  29. package/dist/esm/files/unified-registry.d.ts +112 -0
  30. package/dist/esm/files/unified-registry.d.ts.map +1 -0
  31. package/dist/esm/files/unified-registry.js +190 -0
  32. package/dist/esm/files/unified-registry.js.map +1 -0
  33. package/dist/esm/index.d.ts +29 -0
  34. package/dist/esm/index.d.ts.map +1 -0
  35. package/dist/esm/index.js +36 -0
  36. package/dist/esm/index.js.map +1 -0
  37. package/dist/esm/testing/decorated-test-schemas.d.ts +27 -0
  38. package/dist/esm/testing/decorated-test-schemas.d.ts.map +1 -0
  39. package/dist/esm/testing/decorated-test-schemas.js +26 -0
  40. package/dist/esm/testing/decorated-test-schemas.js.map +1 -0
  41. package/dist/esm/testing.d.ts +6 -0
  42. package/dist/esm/testing.d.ts.map +1 -0
  43. package/dist/esm/testing.js +6 -0
  44. package/dist/esm/testing.js.map +1 -0
  45. package/dist/esm/zod-bigdecimal.d.ts +193 -0
  46. package/dist/esm/zod-bigdecimal.d.ts.map +1 -0
  47. package/dist/esm/zod-bigdecimal.js +274 -0
  48. package/dist/esm/zod-bigdecimal.js.map +1 -0
  49. package/dist/esm/zod-code.d.ts +95 -0
  50. package/dist/esm/zod-code.d.ts.map +1 -0
  51. package/dist/esm/zod-code.js +160 -0
  52. package/dist/esm/zod-code.js.map +1 -0
  53. package/dist/esm/zod-color.d.ts +8 -0
  54. package/dist/esm/zod-color.d.ts.map +1 -0
  55. package/dist/esm/zod-color.js +88 -0
  56. package/dist/esm/zod-color.js.map +1 -0
  57. package/dist/esm/zod-decorators.types.d.ts +2207 -0
  58. package/dist/esm/zod-decorators.types.d.ts.map +1 -0
  59. package/dist/esm/zod-decorators.types.js +771 -0
  60. package/dist/esm/zod-decorators.types.js.map +1 -0
  61. package/dist/esm/zod-duration.d.ts +128 -0
  62. package/dist/esm/zod-duration.d.ts.map +1 -0
  63. package/dist/esm/zod-duration.js +174 -0
  64. package/dist/esm/zod-duration.js.map +1 -0
  65. package/dist/esm/zod-file.d.ts +219 -0
  66. package/dist/esm/zod-file.d.ts.map +1 -0
  67. package/dist/esm/zod-file.js +469 -0
  68. package/dist/esm/zod-file.js.map +1 -0
  69. package/dist/esm/zod-money.d.ts +223 -0
  70. package/dist/esm/zod-money.d.ts.map +1 -0
  71. package/dist/esm/zod-money.js +293 -0
  72. package/dist/esm/zod-money.js.map +1 -0
  73. package/dist/esm/zod-phone.d.ts +75 -0
  74. package/dist/esm/zod-phone.d.ts.map +1 -0
  75. package/dist/esm/zod-phone.js +116 -0
  76. package/dist/esm/zod-phone.js.map +1 -0
  77. package/dist/esm/zod-range.d.ts +87 -0
  78. package/dist/esm/zod-range.d.ts.map +1 -0
  79. package/dist/esm/zod-range.js +138 -0
  80. package/dist/esm/zod-range.js.map +1 -0
  81. package/dist/esm/zod-utils.shared.d.ts +783 -0
  82. package/dist/esm/zod-utils.shared.d.ts.map +1 -0
  83. package/dist/esm/zod-utils.shared.js +2647 -0
  84. package/dist/esm/zod-utils.shared.js.map +1 -0
  85. package/dist/esm/zod-versioned.d.ts +150 -0
  86. package/dist/esm/zod-versioned.d.ts.map +1 -0
  87. package/dist/esm/zod-versioned.js +172 -0
  88. package/dist/esm/zod-versioned.js.map +1 -0
  89. package/dist/esm/zod.module-override.d.ts +413 -0
  90. package/dist/esm/zod.module-override.d.ts.map +1 -0
  91. package/dist/esm/zod.module-override.js +1093 -0
  92. package/dist/esm/zod.module-override.js.map +1 -0
  93. package/dist/tsconfig.build.tsbuildinfo +1 -0
  94. package/package.json +63 -0
@@ -0,0 +1,2647 @@
1
+ import { z } from 'zod';
2
+ import { ForeignKeyDisplayMode, FieldSchemaType, FormFieldComponent, RangeType, SCHEMA_METADATA, ZodDecorator } from './zod-decorators.types.js';
3
+ import { z_range } from './zod-range.js';
4
+ import { addDecorators, getMetadataRecursive } from './zod.module-override.js';
5
+ // Type guard to check if a schema has Zod v4 internals
6
+ // Zod v4 can have two structures:
7
+ // - Legacy: schema._zod.def.type (older Zod v4 builds)
8
+ // - Current: schema.def.type with schema.type at root (newer Zod v4 builds)
9
+ export function hasZodInternals(schema) {
10
+ if (!schema || typeof schema !== 'object')
11
+ return false;
12
+ // First check for legacy structure: schema._zod.def.type
13
+ if ('_zod' in schema) {
14
+ const schemaObj = schema;
15
+ const zodProp = schemaObj._zod;
16
+ if (typeof zodProp === 'object' && zodProp !== null && 'def' in zodProp) {
17
+ const zodObj = zodProp;
18
+ const defProp = zodObj.def;
19
+ if (typeof defProp === 'object' && defProp !== null && 'type' in defProp) {
20
+ return true;
21
+ }
22
+ }
23
+ }
24
+ // Check for current structure: schema.def.type (with schema.type at root)
25
+ if ('def' in schema) {
26
+ const schemaObj = schema;
27
+ const defProp = schemaObj.def;
28
+ if (typeof defProp === 'object' && defProp !== null && 'type' in defProp) {
29
+ return true;
30
+ }
31
+ }
32
+ return false;
33
+ }
34
+ /**
35
+ * Check if a schema has a specific decorator.
36
+ * Note: getCustomFieldMetadata already handles recursive unwrapping for all wrapper types
37
+ * including pipe (from refine/superRefine), optional, nullable, default, etc.
38
+ */
39
+ export function hasDecorator(schema, decorator) {
40
+ const metadata = getCustomFieldMetadata(schema);
41
+ const value = metadata[decorator];
42
+ // Handle different value types properly
43
+ if (value !== undefined) {
44
+ // For strings: non-empty = has decorator, empty = no decorator (defensive guard)
45
+ if (typeof value === 'string') {
46
+ return value.length > 0;
47
+ }
48
+ // For numbers: any number including 0 = has decorator
49
+ if (typeof value === 'number') {
50
+ return true;
51
+ }
52
+ // For booleans: true = has decorator, false = no decorator
53
+ if (typeof value === 'boolean') {
54
+ return value;
55
+ }
56
+ if (typeof value === 'object' && value === null) {
57
+ return false;
58
+ }
59
+ // For other types: truthy = has decorator
60
+ return Boolean(value);
61
+ }
62
+ return false;
63
+ }
64
+ // =============================================================================
65
+ // SCHEMA UNWRAPPING
66
+ // =============================================================================
67
+ /**
68
+ * Unwrap optional, nullable, default, and other wrapper schemas
69
+ * to get the underlying base schema
70
+ */
71
+ export function unwrapSchema(schema) {
72
+ const type = getZodType(schema);
73
+ switch (type) {
74
+ case 'optional':
75
+ case 'nullable': {
76
+ const innerSchema = getZodDefSchemaProperty(schema, 'innerType');
77
+ return innerSchema ? unwrapSchema(innerSchema) : schema;
78
+ }
79
+ case 'default': {
80
+ const innerSchema = getZodDefSchemaProperty(schema, 'innerType');
81
+ return innerSchema ? unwrapSchema(innerSchema) : schema;
82
+ }
83
+ case 'pipe': {
84
+ const inSchema = getZodDefSchemaProperty(schema, 'in');
85
+ return inSchema ? unwrapSchema(inSchema) : schema;
86
+ }
87
+ case 'readonly': {
88
+ const innerSchema = getZodDefSchemaProperty(schema, 'innerType');
89
+ return innerSchema ? unwrapSchema(innerSchema) : schema;
90
+ }
91
+ case 'catch': {
92
+ const innerSchema = getZodDefSchemaProperty(schema, 'innerType');
93
+ return innerSchema ? unwrapSchema(innerSchema) : schema;
94
+ }
95
+ default:
96
+ return schema;
97
+ }
98
+ }
99
+ // =============================================================================
100
+ // ZOD TYPE DETECTION HELPERS
101
+ //
102
+ // Helper function to safely get the type from a Zod v4 schema
103
+ // Supports both structures:
104
+ // - Legacy: schema._zod.def.type
105
+ // - Current: schema.def.type (or schema.type at root)
106
+ // Type values are lowercase strings like "object", "array", "string", etc.
107
+ // =============================================================================
108
+ export function getZodType(schema) {
109
+ // Guard against null/undefined schemas
110
+ if (!schema || typeof schema !== 'object') {
111
+ return undefined;
112
+ }
113
+ const def = getZodDef(schema);
114
+ if (def?.type)
115
+ return def.type;
116
+ // Fallback: check schema.type at root level
117
+ if ('type' in schema && typeof schema.type === 'string') {
118
+ return schema.type;
119
+ }
120
+ return undefined;
121
+ }
122
+ /**
123
+ * Type guard to check if a schema has a specific type
124
+ * Uses Zod v4's type system where types are lowercase strings
125
+ */
126
+ export function hasZodType(schema, type) {
127
+ return getZodType(schema) === type;
128
+ }
129
+ export function isZodObject(schema) {
130
+ return hasZodType(schema, 'object');
131
+ }
132
+ export function isZodArray(schema) {
133
+ return hasZodType(schema, 'array');
134
+ }
135
+ export function isZodOptional(schema) {
136
+ return hasZodType(schema, 'optional');
137
+ }
138
+ export function isZodNullable(schema) {
139
+ return hasZodType(schema, 'nullable');
140
+ }
141
+ export function isZodDefault(schema) {
142
+ return hasZodType(schema, 'default');
143
+ }
144
+ export function isZodUnion(schema) {
145
+ return hasZodType(schema, 'union');
146
+ }
147
+ export function isZodIntersection(schema) {
148
+ return hasZodType(schema, 'intersection');
149
+ }
150
+ export function isZodLiteral(schema) {
151
+ return hasZodType(schema, 'literal');
152
+ }
153
+ export function isZodLazy(schema) {
154
+ return hasZodType(schema, 'lazy');
155
+ }
156
+ export function isZodTuple(schema) {
157
+ return hasZodType(schema, 'tuple');
158
+ }
159
+ export function isZodRecord(schema) {
160
+ return hasZodType(schema, 'record');
161
+ }
162
+ export function isZodSet(schema) {
163
+ return hasZodType(schema, 'set');
164
+ }
165
+ export function isZodPipe(schema) {
166
+ return hasZodType(schema, 'pipe');
167
+ }
168
+ export function isZodTransform(schema) {
169
+ const type = getZodType(schema);
170
+ if (type === 'transform')
171
+ return true;
172
+ // In Zod v4, `.transform()` returns a pipe schema whose output is a transform schema.
173
+ if (type === 'pipe') {
174
+ const outSchema = getZodDefSchemaProperty(schema, 'out');
175
+ return outSchema ? getZodType(outSchema) === 'transform' : false;
176
+ }
177
+ return false;
178
+ }
179
+ export function isZodCustom(schema) {
180
+ return hasZodType(schema, 'custom');
181
+ }
182
+ export function isArray(value) {
183
+ return Array.isArray(value);
184
+ }
185
+ export function isZodTypeAny(value) {
186
+ return value !== null &&
187
+ value !== undefined &&
188
+ typeof value === 'object' &&
189
+ hasZodInternals(value);
190
+ }
191
+ /**
192
+ * Check if a union is discriminated (has discriminator property)
193
+ * In Zod v4, discriminated unions have type: "union" with a discriminator property
194
+ */
195
+ export function isDiscriminatedUnion(schema) {
196
+ if (!isZodUnion(schema))
197
+ return false;
198
+ return getZodDefStringProperty(schema, 'discriminator') !== undefined;
199
+ }
200
+ /**
201
+ * Check if a schema is optional (can be undefined)
202
+ */
203
+ export function isOptional(schema) {
204
+ const type = getZodType(schema);
205
+ if (type === 'optional')
206
+ return true;
207
+ if (type === 'default')
208
+ return true; // Has default, so not required from user
209
+ if (type === 'nullable' || type === 'pipe' || type === 'readonly' || type === 'catch') {
210
+ const innerSchema = getZodDefSchemaProperty(schema, 'innerType') ?? getZodDefSchemaProperty(schema, 'in');
211
+ return innerSchema ? isOptional(innerSchema) : false;
212
+ }
213
+ return false;
214
+ }
215
+ /**
216
+ * Check if a schema has refine/superRefine transformations
217
+ * This triggers onChange validation mode
218
+ */
219
+ export function hasRefine(schema) {
220
+ const type = getZodType(schema);
221
+ // Check for pipe with refinement effect
222
+ if (type === 'pipe') {
223
+ const outSchema = getZodDefSchemaProperty(schema, 'out');
224
+ if (outSchema) {
225
+ const outType = getZodType(outSchema);
226
+ if (outType === 'custom') {
227
+ // Custom type in pipe output usually indicates refinement
228
+ return true;
229
+ }
230
+ }
231
+ const inSchema = getZodDefSchemaProperty(schema, 'in');
232
+ return inSchema ? hasRefine(inSchema) : false;
233
+ }
234
+ // Check for custom type (refine creates these)
235
+ if (type === 'custom') {
236
+ return true;
237
+ }
238
+ // Unwrap wrappers and check inner
239
+ if (type === 'optional' || type === 'nullable' || type === 'default' || type === 'readonly' || type === 'catch') {
240
+ const innerSchema = getZodDefSchemaProperty(schema, 'innerType');
241
+ return innerSchema ? hasRefine(innerSchema) : false;
242
+ }
243
+ return false;
244
+ }
245
+ // =============================================================================
246
+ // SCHEMA TYPE DETECTION
247
+ // =============================================================================
248
+ /**
249
+ * Get the schema type category for component selection.
250
+ *
251
+ * Important: RANGE schemas (created by z_range()) are detected by their
252
+ * SCHEMA_METADATA.rangeConfig before checking for object type. This allows range schemas
253
+ * to be rendered with appropriate range components instead of as composite objects.
254
+ */
255
+ export function getSchemaType(schema) {
256
+ // Check for range schema FIRST (before object check)
257
+ // Range schemas are z.object({from, to}) with SCHEMA_METADATA.rangeConfig
258
+ // isRangeSchema and getRangeType now handle unwrapping internally
259
+ if (isRangeSchema(schema)) {
260
+ // Return specialized range type based on the range's value type
261
+ const rangeType = getRangeType(schema);
262
+ switch (rangeType) {
263
+ case RangeType.NUMBER:
264
+ return FieldSchemaType.RANGE_NUMBER;
265
+ case RangeType.DATE:
266
+ return FieldSchemaType.RANGE_DATE;
267
+ case RangeType.DATETIME:
268
+ return FieldSchemaType.RANGE_DATETIME;
269
+ case RangeType.TIME:
270
+ return FieldSchemaType.RANGE_TIME;
271
+ case RangeType.BIGDECIMAL:
272
+ return FieldSchemaType.RANGE_BIGDECIMAL;
273
+ case RangeType.MONEY:
274
+ return FieldSchemaType.RANGE_MONEY;
275
+ default:
276
+ // z_range() only accepts valid RangeType values, so this should never happen
277
+ return FieldSchemaType.UNKNOWN;
278
+ }
279
+ }
280
+ // Check for money schemas (created by z_money())
281
+ // isMoneySchema now handles unwrapping internally
282
+ if (isMoneySchema(schema)) {
283
+ return FieldSchemaType.MONEY;
284
+ }
285
+ // Check for bigdecimal schemas (created by z_bigdecimal())
286
+ // isBigDecimalSchema now handles unwrapping internally
287
+ if (isBigDecimalSchema(schema)) {
288
+ return FieldSchemaType.BIGDECIMAL;
289
+ }
290
+ // Check for code schemas (created by z_code())
291
+ // isCodeSchema now handles unwrapping internally
292
+ if (isCodeSchema(schema)) {
293
+ return FieldSchemaType.CODE;
294
+ }
295
+ // Check for file schemas (created by z_file())
296
+ // isFileSchema now handles unwrapping internally
297
+ if (isFileSchema(schema)) {
298
+ return FieldSchemaType.FILE;
299
+ }
300
+ // Check for duration schemas (created by z_duration())
301
+ if (isDurationSchema(schema)) {
302
+ return FieldSchemaType.DURATION;
303
+ }
304
+ // Check for phone schemas (created by z_phone())
305
+ if (isPhoneSchema(schema)) {
306
+ return FieldSchemaType.PHONE;
307
+ }
308
+ // Check for versioned foreign key schemas (created by z_foreignKeyVersioned())
309
+ if (isVersionedForeignKeySchema(schema)) {
310
+ return FieldSchemaType.VERSIONED_FOREIGN_KEY;
311
+ }
312
+ // Check for versioned object schemas (created by versioned())
313
+ if (isVersionedObjectSchema(schema)) {
314
+ return FieldSchemaType.VERSIONED_OBJECT;
315
+ }
316
+ const unwrapped = unwrapSchema(schema);
317
+ const type = getZodType(unwrapped);
318
+ switch (type) {
319
+ case 'string': {
320
+ // Check for datetime/time formats (z.iso.datetime(), z.iso.time())
321
+ const format = getStringFormat(unwrapped);
322
+ if (format === 'datetime')
323
+ return FieldSchemaType.DATETIME;
324
+ if (format === 'time')
325
+ return FieldSchemaType.TIME;
326
+ return FieldSchemaType.STRING;
327
+ }
328
+ case 'number':
329
+ case 'int': return FieldSchemaType.NUMBER;
330
+ case 'bigint': return FieldSchemaType.BIGINT;
331
+ case 'boolean': return FieldSchemaType.BOOLEAN;
332
+ case 'enum':
333
+ return FieldSchemaType.ENUM;
334
+ case 'literal': {
335
+ // In Zod v4, literal values are stored in def.values as an array
336
+ const values = getZodDefProperty(unwrapped, 'values');
337
+ const literalValue = values?.[0];
338
+ if (literalValue === null || literalValue === undefined) {
339
+ return FieldSchemaType.UNKNOWN;
340
+ }
341
+ // Infer type based on the value type
342
+ switch (typeof literalValue) {
343
+ case 'string':
344
+ // String literals are treated as single-value enums (for select fields)
345
+ return FieldSchemaType.ENUM;
346
+ case 'number':
347
+ case 'bigint':
348
+ return FieldSchemaType.NUMBER;
349
+ case 'boolean':
350
+ return FieldSchemaType.BOOLEAN;
351
+ default:
352
+ return FieldSchemaType.UNKNOWN;
353
+ }
354
+ }
355
+ case 'date': return FieldSchemaType.DATE;
356
+ case 'object': return FieldSchemaType.OBJECT;
357
+ case 'array': return FieldSchemaType.ARRAY;
358
+ case 'set': return FieldSchemaType.SET;
359
+ case 'tuple': return FieldSchemaType.TUPLE;
360
+ case 'record': return FieldSchemaType.RECORD;
361
+ case 'map': return FieldSchemaType.MAP;
362
+ case 'union': {
363
+ // For unions, try to infer type from options
364
+ // If all options are enums, treat as ENUM (combined values)
365
+ // Otherwise, use the type of the first option
366
+ const options = getZodDefArrayProperty(unwrapped, 'options') ?? [];
367
+ if (options.length === 0)
368
+ return FieldSchemaType.UNKNOWN;
369
+ // Check if all options are enums or string literals (treat as combined enum)
370
+ const allEnumLike = options.every((opt) => {
371
+ if (!isZodTypeAny(opt))
372
+ return false;
373
+ const optType = getZodType(opt);
374
+ return optType === 'enum' || optType === 'literal';
375
+ });
376
+ if (allEnumLike) {
377
+ return FieldSchemaType.ENUM;
378
+ }
379
+ // Otherwise, infer from the first option
380
+ const firstOption = options[0];
381
+ if (isZodTypeAny(firstOption)) {
382
+ return getSchemaType(firstOption);
383
+ }
384
+ return FieldSchemaType.UNKNOWN;
385
+ }
386
+ default: return FieldSchemaType.UNKNOWN;
387
+ }
388
+ }
389
+ // =============================================================================
390
+ // ZOD HELPER GETTER
391
+ // =============================================================================
392
+ /**
393
+ * Get the type string from a Zod v4 schema's internal structure.
394
+ * Returns undefined if schema is not a valid Zod 4 schema.
395
+ */
396
+ function getZodTypeInternal(schema) {
397
+ if (!schema || typeof schema !== 'object')
398
+ return undefined;
399
+ const zodProp = schema._zod;
400
+ if (!zodProp || typeof zodProp !== 'object')
401
+ return undefined;
402
+ const def = zodProp.def;
403
+ if (!def || typeof def !== 'object')
404
+ return undefined;
405
+ return typeof def.type === 'string' ? def.type : undefined;
406
+ }
407
+ /**
408
+ * Get the shape of a Zod object schema.
409
+ *
410
+ * IMPORTANT: This function only returns valid Zod schema instances, not definitions.
411
+ * In Zod 4, `def.shape` contains JSON-serializable definitions, not schema instances
412
+ * with methods like safeParse.
413
+ *
414
+ * Prefer using getObjectShape() which handles unwrapping and validation.
415
+ */
416
+ export function getZodObjectShape(schema) {
417
+ if (!isZodObject(schema)) {
418
+ return {};
419
+ }
420
+ // Use the improved getObjectShape which validates schema instances
421
+ const shape = getObjectShape(schema);
422
+ return shape ?? {};
423
+ }
424
+ export function getZodDef(schema) {
425
+ if (!hasZodInternals(schema))
426
+ return undefined;
427
+ // First check legacy structure: schema._zod.def
428
+ if ('_zod' in schema) {
429
+ const zodProp = schema._zod;
430
+ if (typeof zodProp === 'object' && zodProp !== null && 'def' in zodProp) {
431
+ return zodProp.def;
432
+ }
433
+ }
434
+ // Use current structure: schema.def directly
435
+ return schema.def;
436
+ }
437
+ export function getZodDefSchemaProperty(schema, property) {
438
+ const def = getZodDef(schema);
439
+ const value = def?.[property];
440
+ return isZodTypeAny(value) ? value : undefined;
441
+ }
442
+ export function getZodDefArrayProperty(schema, property) {
443
+ const def = getZodDef(schema);
444
+ const value = def?.[property];
445
+ return isArray(value) ? value : undefined;
446
+ }
447
+ export function getZodDefStringProperty(schema, property) {
448
+ const def = getZodDef(schema);
449
+ const value = def?.[property];
450
+ return typeof value === 'string' ? value : undefined;
451
+ }
452
+ export function getZodDefFunctionProperty(schema, property) {
453
+ const def = getZodDef(schema);
454
+ const value = def?.[property];
455
+ if (typeof value === 'function') {
456
+ return value;
457
+ }
458
+ return undefined;
459
+ }
460
+ export function getZodDefProperty(schema, property) {
461
+ const def = getZodDef(schema);
462
+ return def?.[property];
463
+ }
464
+ /**
465
+ * Get the inner schema from a pipe type (for transforms/refinements)
466
+ * In Zod v4, pipe types have def.in and def.out
467
+ * For most cases, we want def.in (the input schema before transformation)
468
+ */
469
+ export function getPipeInputSchema(schema) {
470
+ if (!isZodPipe(schema))
471
+ return undefined;
472
+ return getZodDefSchemaProperty(schema, 'in');
473
+ }
474
+ /**
475
+ * Get all custom field metadata from a schema, traversing through wrapper types
476
+ * (optional, nullable, default, pipe, readonly, catch, array, effects).
477
+ * Always returns an object (never undefined) — empty `{}` if no metadata found.
478
+ *
479
+ * Delegates to the unified getMetadataRecursive from zod.module-override.ts.
480
+ */
481
+ export function getCustomFieldMetadata(schema) {
482
+ if (!schema) {
483
+ return {};
484
+ }
485
+ return getMetadataRecursive(schema) ?? {};
486
+ }
487
+ // =============================================================================
488
+ // GENERIC {primitive}UI CROSS-CUTTING READERS
489
+ // =============================================================================
490
+ /**
491
+ * All `*_UI` decorator keys. Used by cross-cutting readers to extract
492
+ * base params (`edit`, `display`) from whichever primitive UI decorator
493
+ * is present on a schema, without needing to know the concrete primitive.
494
+ */
495
+ const UI_DECORATOR_KEYS = [
496
+ ZodDecorator.BOOLEAN_UI, ZodDecorator.STRING_UI, ZodDecorator.NUMBER_UI,
497
+ ZodDecorator.ENUM_UI, ZodDecorator.DATE_UI, ZodDecorator.DATETIME_UI,
498
+ ZodDecorator.TIME_UI, ZodDecorator.MONEY_UI, ZodDecorator.CODE_UI,
499
+ ZodDecorator.FILE_UI, ZodDecorator.DURATION_UI, ZodDecorator.PHONE_UI,
500
+ ZodDecorator.BIGDECIMAL_UI, ZodDecorator.FOREIGN_KEY_UI,
501
+ ZodDecorator.ARRAY_UI, ZodDecorator.OBJECT_UI,
502
+ ];
503
+ /**
504
+ * Extract the edit-context base params from whichever `{primitive}UI` decorator
505
+ * is present on the schema. Works generically because all `*UIEditParams`
506
+ * extend `PrimitiveUIBaseEditParams`.
507
+ *
508
+ * Use this for cross-cutting concerns (validationMode, autosave, tooltip, etc.)
509
+ * where the consumer doesn't know the concrete primitive type.
510
+ */
511
+ export function getFieldUIEditConfig(schema) {
512
+ const metadata = getCustomFieldMetadata(schema);
513
+ for (const key of UI_DECORATOR_KEYS) {
514
+ const params = metadata[key];
515
+ if (params && typeof params === 'object' && 'edit' in params) {
516
+ return params.edit;
517
+ }
518
+ }
519
+ return undefined;
520
+ }
521
+ /**
522
+ * Extract the display-context base params from whichever `{primitive}UI` decorator
523
+ * is present on the schema. Works generically because all `*UIDisplayParams`
524
+ * extend `PrimitiveUIBaseDisplayParams`.
525
+ *
526
+ * Use this for cross-cutting concerns (tooltip, inlineEditable, etc.)
527
+ * where the consumer doesn't know the concrete primitive type.
528
+ */
529
+ export function getFieldUIDisplayConfig(schema) {
530
+ const metadata = getCustomFieldMetadata(schema);
531
+ for (const key of UI_DECORATOR_KEYS) {
532
+ const params = metadata[key];
533
+ if (params && typeof params === 'object' && 'display' in params) {
534
+ return params.display;
535
+ }
536
+ }
537
+ return undefined;
538
+ }
539
+ // =============================================================================
540
+ // TOOLTIP DECORATOR UTILITIES
541
+ // =============================================================================
542
+ /**
543
+ * Check if a field should show a tooltip in display mode.
544
+ * Reads from `{primitive}UI.display.tooltip` (boolean flag).
545
+ * The actual tooltip text comes from the i18n label system, NOT from the decorator.
546
+ */
547
+ export function hasTooltipForDisplay(schema) {
548
+ return getFieldUIDisplayConfig(schema)?.tooltip === true;
549
+ }
550
+ /**
551
+ * Check if a field should show a tooltip in edit mode.
552
+ * Reads from `{primitive}UI.edit.tooltip` (boolean flag).
553
+ * The actual tooltip text comes from the i18n label system, NOT from the decorator.
554
+ */
555
+ export function hasTooltipForEdit(schema) {
556
+ return getFieldUIEditConfig(schema)?.tooltip === true;
557
+ }
558
+ /**
559
+ * Check if a field is marked as draft-relaxed (validation is relaxed in draft mode).
560
+ *
561
+ * @param schema - Zod schema to check
562
+ * @returns true if the field is draft-relaxed
563
+ *
564
+ * @example
565
+ * if (isDraftRelaxed(schema) && isDraftMode) {
566
+ * // Skip strict validation for this field
567
+ * }
568
+ */
569
+ export function isDraftRelaxed(schema) {
570
+ const metadata = getCustomFieldMetadata(schema);
571
+ return metadata[ZodDecorator.IS_DRAFT_RELAXED] === true;
572
+ }
573
+ /**
574
+ * FIX-P5-H1: Get showWhen parameters from a field schema.
575
+ * Returns the conditional visibility configuration if present.
576
+ *
577
+ * @param schema - The field schema to get showWhen params from
578
+ * @returns ShowWhenParams if decorator was applied, undefined otherwise
579
+ *
580
+ * @example
581
+ * const showWhenParams = getShowWhenParams(fieldSchema);
582
+ * if (showWhenParams) {
583
+ * // Evaluate visibility based on conditions
584
+ * const isVisible = evaluateShowWhenConditions(showWhenParams, formValues);
585
+ * }
586
+ */
587
+ export function getShowWhenParams(schema) {
588
+ const metadata = getCustomFieldMetadata(schema);
589
+ return metadata[ZodDecorator.SHOW_WHEN];
590
+ }
591
+ /**
592
+ * Get string format from schema checks (email, url, etc.)
593
+ */
594
+ export function getStringFormat(schema) {
595
+ const unwrapped = unwrapSchema(schema);
596
+ if (getZodType(unwrapped) !== 'string') {
597
+ return null;
598
+ }
599
+ const checks = getZodDefArrayProperty(unwrapped, 'checks') ?? [];
600
+ for (const check of checks) {
601
+ if (typeof check !== 'object' || check === null)
602
+ continue;
603
+ const kind = check.kind;
604
+ switch (kind) {
605
+ case 'email': return 'email';
606
+ case 'url': return 'url';
607
+ case 'uuid': return 'uuid';
608
+ case 'cuid': return 'cuid';
609
+ case 'cuid2': return 'cuid2';
610
+ case 'ulid': return 'ulid';
611
+ case 'regex': return 'regex';
612
+ case 'datetime': return 'datetime';
613
+ case 'time': return 'time';
614
+ case 'ip': return 'ip';
615
+ }
616
+ }
617
+ return null;
618
+ }
619
+ /**
620
+ * Get min/max length constraints from string schema
621
+ */
622
+ export function getStringConstraints(schema) {
623
+ const unwrapped = unwrapSchema(schema);
624
+ if (getZodType(unwrapped) !== 'string') {
625
+ return {};
626
+ }
627
+ const checks = getZodDefArrayProperty(unwrapped, 'checks') ?? [];
628
+ const result = {};
629
+ for (const check of checks) {
630
+ if (typeof check !== 'object' || check === null)
631
+ continue;
632
+ const kind = check.kind;
633
+ const value = check.value;
634
+ if (typeof value !== 'number')
635
+ continue;
636
+ if (kind === 'min') {
637
+ result.minLength = value;
638
+ }
639
+ if (kind === 'max') {
640
+ result.maxLength = value;
641
+ }
642
+ if (kind === 'length') {
643
+ result.minLength = value;
644
+ result.maxLength = value;
645
+ }
646
+ }
647
+ return result;
648
+ }
649
+ // ------------------------------------------------------------
650
+ // NUMBER SCHEMA UTILITIES
651
+ // ------------------------------------------------------------
652
+ /**
653
+ * Get min/max value constraints from number schema
654
+ */
655
+ export function getNumberConstraints(schema) {
656
+ const unwrapped = unwrapSchema(schema);
657
+ const type = getZodType(unwrapped);
658
+ if (type !== 'number' && type !== 'int') {
659
+ return {};
660
+ }
661
+ const checks = getZodDefArrayProperty(unwrapped, 'checks') ?? [];
662
+ const result = {};
663
+ // int type in Zod v4
664
+ if (type === 'int') {
665
+ result.isInt = true;
666
+ result.step = 1;
667
+ }
668
+ for (const check of checks) {
669
+ if (typeof check !== 'object' || check === null)
670
+ continue;
671
+ const kind = check.kind;
672
+ const value = check.value;
673
+ if (kind === 'min' && typeof value === 'number') {
674
+ result.min = value;
675
+ }
676
+ if (kind === 'max' && typeof value === 'number') {
677
+ result.max = value;
678
+ }
679
+ if (kind === 'int') {
680
+ result.isInt = true;
681
+ result.step = 1;
682
+ }
683
+ if (kind === 'multipleOf' && typeof value === 'number') {
684
+ result.step = value;
685
+ }
686
+ }
687
+ return result;
688
+ }
689
+ // ------------------------------------------------------------
690
+ // ENUM SCHEMA UTILITIES
691
+ // ------------------------------------------------------------
692
+ /**
693
+ * Extract enum values from z.enum (Zod 4)
694
+ * In Zod 4, both z.enum() and z.enum() create schemas with type 'enum'
695
+ * - z.enum(['a', 'b', 'c']) - def.entries is an object map (e.g. { a: 'a', b: 'b' })
696
+ * - z.enum(TypeScriptEnum) / z.enum(TypeScriptEnum) - def.entries is the enum object map
697
+ */
698
+ export function getEnumValues(schema) {
699
+ const unwrapped = unwrapSchema(schema);
700
+ const type = getZodType(unwrapped);
701
+ // Handle string literal types (z.literal("value")) - return single value as array
702
+ // Only string literals are treated as enums (for select fields)
703
+ // In Zod v4, literal values are stored in def.values as an array
704
+ if (type === 'literal') {
705
+ const values = getZodDefProperty(unwrapped, 'values');
706
+ const literalValue = values?.[0];
707
+ // Only string literals are valid enum values
708
+ if (typeof literalValue === 'string') {
709
+ return [literalValue];
710
+ }
711
+ return [];
712
+ }
713
+ // Handle union types - combine enum values from all options
714
+ // This handles cases like z.union([z.enum(A), z.enum(B)])
715
+ if (type === 'union') {
716
+ const options = getZodDefArrayProperty(unwrapped, 'options') ?? [];
717
+ const allValues = [];
718
+ const seenValues = new Set();
719
+ for (const opt of options) {
720
+ if (isZodTypeAny(opt)) {
721
+ const optValues = getEnumValues(opt);
722
+ for (const val of optValues) {
723
+ if (!seenValues.has(val)) {
724
+ seenValues.add(val);
725
+ allValues.push(val);
726
+ }
727
+ }
728
+ }
729
+ }
730
+ return allValues;
731
+ }
732
+ if (type !== 'enum') {
733
+ return [];
734
+ }
735
+ // In Zod v4.1.x, enum values are stored in def.entries (object map).
736
+ const entries = getZodDefProperty(unwrapped, 'entries');
737
+ if (!entries || typeof entries !== 'object')
738
+ return [];
739
+ const values = [];
740
+ for (const key of Object.keys(entries)) {
741
+ const value = entries[key];
742
+ // For numeric enums, TypeScript creates reverse mappings: entries["0"] = "A", entries["A"] = 0
743
+ if (typeof value === 'string') {
744
+ // Skip reverse mapping values where key is numeric (e.g., "0" -> "A")
745
+ if (!Number.isNaN(Number(key)))
746
+ continue;
747
+ values.push(value);
748
+ }
749
+ else if (typeof value === 'number') {
750
+ values.push(String(value));
751
+ }
752
+ }
753
+ return values;
754
+ }
755
+ // ------------------------------------------------------------
756
+ // OBJECT SCHEMA UTILITIES
757
+ // ------------------------------------------------------------
758
+ /**
759
+ * Validates that an object contains actual Zod schema instances (not definitions).
760
+ * A valid Zod 4 schema instance has either:
761
+ * - _zod property (internal structure)
762
+ * - safeParse method (public API)
763
+ */
764
+ function isValidSchemaShape(shape) {
765
+ // Skip $-prefixed keys (Zod v4 internals like $id, $brand) when picking
766
+ // the representative entry to validate — their values are primitives, not schemas.
767
+ const keys = Object.keys(shape);
768
+ const firstUserKey = keys.find(k => !k.startsWith('$'));
769
+ if (!firstUserKey)
770
+ return keys.length === 0; // Only internal keys or empty → valid if empty
771
+ const firstValue = shape[firstUserKey];
772
+ if (!firstValue || typeof firstValue !== 'object')
773
+ return false;
774
+ const hasZodInternals = '_zod' in firstValue;
775
+ const hasSafeParse = 'safeParse' in firstValue && typeof firstValue.safeParse === 'function';
776
+ return hasZodInternals || hasSafeParse;
777
+ }
778
+ /**
779
+ * Strips Zod v4 internal properties (e.g. $id, $brand) from a shape record.
780
+ * These are runtime metadata, not user-defined fields, and leak into
781
+ * Object.entries() when iterating the raw .shape object.
782
+ *
783
+ * Uses warn-once to avoid log flooding (same pattern as _warnedStorageProviders in zod-file.ts).
784
+ * This is a low-level shared lib used by both frontend and backend — console.warn
785
+ * is the only available logging primitive here (no injected logger).
786
+ */
787
+ const _warnedInternalKeys = new Set();
788
+ function stripZodInternalKeys(shape) {
789
+ const keys = Object.keys(shape);
790
+ const internalKeys = keys.filter(k => k.startsWith('$'));
791
+ if (internalKeys.length === 0)
792
+ return shape;
793
+ const warnKey = internalKeys.sort().join(',');
794
+ if (!_warnedInternalKeys.has(warnKey)) {
795
+ _warnedInternalKeys.add(warnKey);
796
+ console.warn(`[getObjectShape] Stripping Zod internal keys from shape: [${internalKeys.join(', ')}]. ` +
797
+ `If a legitimate user-defined field starts with '$', this filter is incorrect. ` +
798
+ `Stripped keys had values: ${internalKeys.map(k => `${k}=${typeof shape[k] === 'object' ? 'object' : shape[k]}`).join(', ')}`);
799
+ }
800
+ const clean = {};
801
+ for (const k of keys) {
802
+ if (!k.startsWith('$'))
803
+ clean[k] = shape[k];
804
+ }
805
+ return clean;
806
+ }
807
+ /**
808
+ * Get the shape of an object schema.
809
+ *
810
+ * IMPORTANT: In Zod 4, `def.shape` contains schema DEFINITIONS (JSON-serializable),
811
+ * NOT schema instances with methods. This function validates that returned shapes
812
+ * contain actual Zod schema instances.
813
+ *
814
+ * If the schema is wrapped (e.g., with .default()), it will be unwrapped first.
815
+ */
816
+ export function getObjectShape(schema) {
817
+ const unwrapped = unwrapSchema(schema);
818
+ if (getZodType(unwrapped) !== 'object') {
819
+ return null;
820
+ }
821
+ // Try public .shape property - this should contain actual schema instances
822
+ if ('shape' in unwrapped && typeof unwrapped.shape === 'object' && unwrapped.shape !== null) {
823
+ const shape = unwrapped.shape;
824
+ // Validate that the shape contains actual Zod schemas, not definitions
825
+ if (isValidSchemaShape(shape)) {
826
+ return stripZodInternalKeys(shape);
827
+ }
828
+ // Shape exists but contains corrupted definitions - will be caught by fail-fast below
829
+ }
830
+ // Try accessing shape through _zod internals (Zod 4 structure)
831
+ // In some Zod 4 versions, _zod.def.shape might still contain actual schema instances
832
+ if ('_zod' in unwrapped) {
833
+ const zodInternals = unwrapped._zod;
834
+ if (zodInternals?.def?.shape && typeof zodInternals.def.shape === 'object') {
835
+ const shape = zodInternals.def.shape;
836
+ // Validate that it contains actual schema instances
837
+ if (isValidSchemaShape(shape)) {
838
+ return stripZodInternalKeys(shape);
839
+ }
840
+ }
841
+ }
842
+ // FAIL-FAST: Do NOT return corrupted schemas silently
843
+ // Throw with diagnostic info to identify the corruption source
844
+ if ('shape' in unwrapped && typeof unwrapped.shape === 'object' && unwrapped.shape !== null) {
845
+ const shape = unwrapped.shape;
846
+ const firstKey = Object.keys(shape)[0];
847
+ const firstValue = shape[firstKey];
848
+ const firstValueKeys = firstValue && typeof firstValue === 'object'
849
+ ? Object.keys(firstValue).slice(0, 10).join(', ')
850
+ : 'N/A';
851
+ throw new Error(`[getObjectShape] Schema shape contains corrupted objects (not valid Zod schemas). ` +
852
+ `First field "${firstKey}" is missing safeParse method. ` +
853
+ `Keys on corrupted object: [${firstValueKeys}]. ` +
854
+ `This usually means the schema was JSON.stringify/parsed or incorrectly cloned.`);
855
+ }
856
+ // Also check _zod.def.shape - same fail-fast approach
857
+ if ('_zod' in unwrapped) {
858
+ const zodInternals = unwrapped._zod;
859
+ if (zodInternals?.def?.shape && typeof zodInternals.def.shape === 'object') {
860
+ const shape = zodInternals.def.shape;
861
+ const firstKey = Object.keys(shape)[0];
862
+ const firstValue = shape[firstKey];
863
+ const firstValueKeys = firstValue && typeof firstValue === 'object'
864
+ ? Object.keys(firstValue).slice(0, 10).join(', ')
865
+ : 'N/A';
866
+ throw new Error(`[getObjectShape] _zod.def.shape contains corrupted objects (not valid Zod schemas). ` +
867
+ `First field "${firstKey}" is missing safeParse. ` +
868
+ `Keys: [${firstValueKeys}]. ` +
869
+ `This usually means the schema was JSON.stringify/parsed or incorrectly cloned.`);
870
+ }
871
+ }
872
+ return null;
873
+ }
874
+ /**
875
+ * Get the keys of an object schema shape
876
+ */
877
+ export function getObjectKeys(schema) {
878
+ const shape = getObjectShape(schema);
879
+ return shape ? Object.keys(shape) : [];
880
+ }
881
+ /**
882
+ * Extract all flattened paths from a Zod schema.
883
+ * Recursively traverses object schemas to build dot-notation paths (e.g., 'field.nestedField').
884
+ * Handles optional, nullable, array, and union types.
885
+ *
886
+ * @param zodSchema - The Zod schema to extract paths from
887
+ * @param prefix - Current path prefix (used for recursion)
888
+ * @param schemaDepthCounters - Map to track depth per schema instance (prevents infinite recursion)
889
+ * @param maxDepthPerSchema - Maximum depth to traverse per schema instance (default: 3)
890
+ * @returns Array of flattened dot-notation paths
891
+ */
892
+ export function extractFlattenedPathsFromZodSchema(zodSchema, prefix = '', schemaDepthCounters = new Map(), maxDepthPerSchema = 3) {
893
+ const paths = [];
894
+ if (!zodSchema) {
895
+ return paths;
896
+ }
897
+ // Check depth for this specific schema instance to prevent infinite recursion
898
+ const currentDepth = schemaDepthCounters.get(zodSchema) || 0;
899
+ if (currentDepth >= maxDepthPerSchema) {
900
+ return paths;
901
+ }
902
+ // Increment depth counter for this schema
903
+ schemaDepthCounters.set(zodSchema, currentDepth + 1);
904
+ try {
905
+ const zodType = getZodType(zodSchema);
906
+ switch (zodType) {
907
+ case 'object': {
908
+ // Extract shape from zod object using public API
909
+ const shape = getZodObjectShape(zodSchema);
910
+ Object.entries(shape).forEach(([fieldName, fieldSchema]) => {
911
+ const currentPath = prefix ? `${prefix}.${fieldName}` : fieldName;
912
+ // Add the current path
913
+ paths.push(currentPath);
914
+ // Recursively process nested objects
915
+ const nestedPaths = extractFlattenedPathsFromZodSchema(fieldSchema, currentPath, new Map(schemaDepthCounters) // Pass a copy to avoid shared state issues
916
+ );
917
+ paths.push(...nestedPaths);
918
+ });
919
+ break;
920
+ }
921
+ case 'optional': {
922
+ // Process the inner type for optional fields
923
+ const innerType = getZodDefSchemaProperty(zodSchema, 'innerType');
924
+ if (innerType) {
925
+ const innerPaths = extractFlattenedPathsFromZodSchema(innerType, prefix, new Map(schemaDepthCounters));
926
+ paths.push(...innerPaths);
927
+ }
928
+ break;
929
+ }
930
+ case 'nullable': {
931
+ // Process the inner type for nullable fields
932
+ const innerType = getZodDefSchemaProperty(zodSchema, 'innerType');
933
+ if (innerType) {
934
+ const innerPaths = extractFlattenedPathsFromZodSchema(innerType, prefix, new Map(schemaDepthCounters));
935
+ paths.push(...innerPaths);
936
+ }
937
+ break;
938
+ }
939
+ case 'array': {
940
+ // For arrays, extract paths from the element type
941
+ // This allows finding nested fields in array elements (e.g., "items.organizationId")
942
+ const element = getZodDefSchemaProperty(zodSchema, 'element');
943
+ if (element) {
944
+ const elementPaths = extractFlattenedPathsFromZodSchema(element, prefix, new Map(schemaDepthCounters));
945
+ paths.push(...elementPaths);
946
+ }
947
+ break;
948
+ }
949
+ case 'union': {
950
+ // For unions, try to extract paths from all possible schemas
951
+ const options = getZodDefArrayProperty(zodSchema, 'options') || [];
952
+ options.forEach((option) => {
953
+ if (option && typeof option === 'object' && hasZodInternals(option)) {
954
+ const unionPaths = extractFlattenedPathsFromZodSchema(option, prefix, new Map(schemaDepthCounters));
955
+ paths.push(...unionPaths);
956
+ }
957
+ });
958
+ break;
959
+ }
960
+ default: {
961
+ // For primitive types (string, number, boolean, etc.), no nested paths
962
+ break;
963
+ }
964
+ }
965
+ }
966
+ catch (error) {
967
+ // Silently handle errors - return paths collected so far
968
+ }
969
+ // Decrement depth counter when done processing this schema
970
+ schemaDepthCounters.set(zodSchema, currentDepth);
971
+ return paths;
972
+ }
973
+ // ------------------------------------------------------------
974
+ // ARRAY SCHEMA UTILITIES
975
+ // ------------------------------------------------------------
976
+ /**
977
+ * Get the item schema from an array schema
978
+ */
979
+ export function getArrayItemSchema(schema) {
980
+ const unwrapped = unwrapSchema(schema);
981
+ if (getZodType(unwrapped) !== 'array') {
982
+ return null;
983
+ }
984
+ const element = getZodDefSchemaProperty(unwrapped, 'element');
985
+ return element ?? null;
986
+ }
987
+ /**
988
+ * Get min/max constraints for array
989
+ */
990
+ export function getArrayConstraints(schema) {
991
+ const unwrapped = unwrapSchema(schema);
992
+ if (getZodType(unwrapped) !== 'array') {
993
+ return {};
994
+ }
995
+ const result = {};
996
+ const minLength = getZodDefProperty(unwrapped, 'minLength');
997
+ const maxLength = getZodDefProperty(unwrapped, 'maxLength');
998
+ if (minLength && typeof minLength === 'object' && 'value' in minLength) {
999
+ const value = minLength.value;
1000
+ if (typeof value === 'number')
1001
+ result.minLength = value;
1002
+ }
1003
+ if (maxLength && typeof maxLength === 'object' && 'value' in maxLength) {
1004
+ const value = maxLength.value;
1005
+ if (typeof value === 'number')
1006
+ result.maxLength = value;
1007
+ }
1008
+ return result;
1009
+ }
1010
+ // ------------------------------------------------------------
1011
+ // RECORD/MAP SCHEMA UTILITIES
1012
+ // ------------------------------------------------------------
1013
+ /**
1014
+ * Get the value schema from a record schema
1015
+ */
1016
+ export function getRecordValueSchema(schema) {
1017
+ const unwrapped = unwrapSchema(schema);
1018
+ if (getZodType(unwrapped) !== 'record') {
1019
+ return null;
1020
+ }
1021
+ const valueSchema = getZodDefSchemaProperty(unwrapped, 'valueType');
1022
+ return valueSchema ?? null;
1023
+ }
1024
+ /**
1025
+ * Get the key and value schemas from a map schema
1026
+ */
1027
+ export function getMapSchemas(schema) {
1028
+ const unwrapped = unwrapSchema(schema);
1029
+ if (getZodType(unwrapped) !== 'map') {
1030
+ return null;
1031
+ }
1032
+ const keySchema = getZodDefSchemaProperty(unwrapped, 'keyType');
1033
+ const valueSchema = getZodDefSchemaProperty(unwrapped, 'valueType');
1034
+ if (keySchema && valueSchema) {
1035
+ return { keySchema, valueSchema };
1036
+ }
1037
+ return null;
1038
+ }
1039
+ // ------------------------------------------------------------
1040
+ // DEFAULT VALUE UTILITIES
1041
+ // ------------------------------------------------------------
1042
+ /**
1043
+ * Get the default value from a schema if it has one
1044
+ */
1045
+ export function getDefaultValue(schema) {
1046
+ const type = getZodType(schema);
1047
+ if (type === 'default') {
1048
+ const defaultValue = getZodDefProperty(schema, 'defaultValue');
1049
+ if (typeof defaultValue === 'function') {
1050
+ return defaultValue();
1051
+ }
1052
+ return defaultValue;
1053
+ }
1054
+ if (type === 'optional' || type === 'nullable') {
1055
+ const innerSchema = getZodDefSchemaProperty(schema, 'innerType');
1056
+ return innerSchema ? getDefaultValue(innerSchema) : undefined;
1057
+ }
1058
+ return undefined;
1059
+ }
1060
+ // =============================================================================
1061
+ // DB related
1062
+ // =============================================================================
1063
+ export function isDBIndexed(schema) {
1064
+ return hasDecorator(schema, ZodDecorator.IS_DB_INDEXED);
1065
+ }
1066
+ /** @see isForeignKeyField — alias for backward compatibility */
1067
+ export function isForeignKey(schema) {
1068
+ return isForeignKeyField(schema);
1069
+ }
1070
+ /** @see isUniqueField — alias for backward compatibility */
1071
+ export function isUnique(schema) {
1072
+ return isUniqueField(schema);
1073
+ }
1074
+ /** @see getUniqueFieldParams — alias for backward compatibility */
1075
+ export function getUniqueParams(schema) {
1076
+ return getUniqueFieldParams(schema);
1077
+ }
1078
+ /**
1079
+ * Check if a field is a primary key.
1080
+ * Primary keys are automatically: excludeFromCreate, isHidden, isUnique, isDBIndexed
1081
+ * @see isPrimaryKeyField — alias for backward compatibility
1082
+ */
1083
+ export function isPrimaryKey(schema) {
1084
+ return isPrimaryKeyField(schema);
1085
+ }
1086
+ /**
1087
+ * Check if a field has a TTL (Time To Live) decorator.
1088
+ * TTL fields are used for MongoDB document expiration.
1089
+ */
1090
+ export function hasTtl(schema) {
1091
+ return hasDecorator(schema, ZodDecorator.TTL);
1092
+ }
1093
+ /**
1094
+ * Get TTL field params from a schema.
1095
+ * Returns undefined if the schema is not a TTL field.
1096
+ */
1097
+ export function getTtlParams(schema) {
1098
+ const metadata = getCustomFieldMetadata(schema);
1099
+ return metadata[ZodDecorator.TTL];
1100
+ }
1101
+ // =============================================================================
1102
+ // DECORATOR READER FUNCTIONS (using getMetadataRecursive — recursive unwrapping)
1103
+ // These handle .optional(), .nullable(), .default(), .refine(), array wrappers etc.
1104
+ // =============================================================================
1105
+ /**
1106
+ * Get the structural FK params (polymorphicVariants) from `IS_FOREIGN_KEY`.
1107
+ * Returns undefined if the schema is not a foreign key.
1108
+ */
1109
+ export function getForeignKeyParams(schema) {
1110
+ const metadata = getMetadataRecursive(schema);
1111
+ const value = metadata?.[ZodDecorator.IS_FOREIGN_KEY];
1112
+ if (value === undefined)
1113
+ return undefined;
1114
+ if (value === true)
1115
+ return {};
1116
+ return value;
1117
+ }
1118
+ /**
1119
+ * Get FK UI display params from `FOREIGN_KEY_UI.display`. Returns mode, linkable, displayPresetOverride.
1120
+ * Falls back to default `mode: LABEL` if the FK exists but has no explicit UI config.
1121
+ */
1122
+ export function getForeignKeyUIDisplay(schema) {
1123
+ if (!isForeignKeyField(schema))
1124
+ return undefined;
1125
+ const metadata = getCustomFieldMetadata(schema);
1126
+ const uiParams = metadata[ZodDecorator.FOREIGN_KEY_UI];
1127
+ const display = uiParams?.display;
1128
+ return { mode: ForeignKeyDisplayMode.LABEL, ...display };
1129
+ }
1130
+ /**
1131
+ * Get the polymorphic variants from a foreign key field.
1132
+ * Returns the array of PolymorphicFkVariant if the FK is polymorphic, or undefined.
1133
+ */
1134
+ export function getPolymorphicFkVariants(schema) {
1135
+ const params = getForeignKeyParams(schema);
1136
+ return params?.polymorphicVariants;
1137
+ }
1138
+ /**
1139
+ * Check if a schema field is a foreign key (recursive traversal through wrappers).
1140
+ */
1141
+ export function isForeignKeyField(schema) {
1142
+ const metadata = getMetadataRecursive(schema);
1143
+ return metadata?.[ZodDecorator.IS_FOREIGN_KEY] !== undefined;
1144
+ }
1145
+ /**
1146
+ * Check if a schema field has the isSummaryField decorator.
1147
+ */
1148
+ export function isSummaryField(schema) {
1149
+ const metadata = getMetadataRecursive(schema);
1150
+ return metadata?.[ZodDecorator.IS_SUMMARY_FIELD] !== undefined;
1151
+ }
1152
+ /**
1153
+ * Get the isSummaryField decorator parameters.
1154
+ * @returns SummaryFieldParams or default empty object if decorator is used without params
1155
+ */
1156
+ export function getSummaryFieldParams(schema) {
1157
+ const metadata = getMetadataRecursive(schema);
1158
+ const value = metadata?.[ZodDecorator.IS_SUMMARY_FIELD];
1159
+ if (value === undefined)
1160
+ return undefined;
1161
+ if (value === true)
1162
+ return {}; // Default params when decorator is used without params
1163
+ return value;
1164
+ }
1165
+ /**
1166
+ * Check if a schema field has the isBackendOnly decorator.
1167
+ */
1168
+ export function isBackendOnlyField(schema) {
1169
+ const metadata = getMetadataRecursive(schema);
1170
+ return metadata?.[ZodDecorator.IS_BACKEND_ONLY] !== undefined;
1171
+ }
1172
+ /**
1173
+ * Get the isBackendOnly decorator parameters.
1174
+ */
1175
+ export function getBackendOnlyParams(schema) {
1176
+ const metadata = getMetadataRecursive(schema);
1177
+ const value = metadata?.[ZodDecorator.IS_BACKEND_ONLY];
1178
+ if (value === undefined)
1179
+ return undefined;
1180
+ if (value === true)
1181
+ return {}; // Default params when decorator is used without params
1182
+ return value;
1183
+ }
1184
+ /**
1185
+ * Check if a field has auditAccess enabled via `.isBackendOnly({ auditAccess: true })`.
1186
+ * Fields with this flag generate FIELD_ACCESS audit events when included in API responses.
1187
+ */
1188
+ export function isAuditAccessField(schema) {
1189
+ return getBackendOnlyParams(schema)?.auditAccess === true;
1190
+ }
1191
+ /**
1192
+ * Check if a schema field has the isDiscriminator decorator.
1193
+ */
1194
+ export function isDiscriminatorField(schema) {
1195
+ const metadata = getMetadataRecursive(schema);
1196
+ return metadata?.[ZodDecorator.IS_DISCRIMINATOR] !== undefined;
1197
+ }
1198
+ /**
1199
+ * Get the isDiscriminator decorator parameters.
1200
+ */
1201
+ export function getDiscriminatorParams(schema) {
1202
+ const metadata = getMetadataRecursive(schema);
1203
+ const value = metadata?.[ZodDecorator.IS_DISCRIMINATOR];
1204
+ if (value === undefined)
1205
+ return undefined;
1206
+ if (value === true)
1207
+ return {}; // Default params when decorator is used without params
1208
+ return value;
1209
+ }
1210
+ /**
1211
+ * Check if a schema field has the excludeFromCreate decorator.
1212
+ */
1213
+ export function isExcludedFromCreate(schema) {
1214
+ const metadata = getMetadataRecursive(schema);
1215
+ return metadata?.[ZodDecorator.EXCLUDE_FROM_CREATE] !== undefined;
1216
+ }
1217
+ /**
1218
+ * Check if a schema field has the excludeFromUpdate decorator.
1219
+ */
1220
+ export function isExcludedFromUpdate(schema) {
1221
+ const metadata = getMetadataRecursive(schema);
1222
+ return metadata?.[ZodDecorator.EXCLUDE_FROM_UPDATE] !== undefined;
1223
+ }
1224
+ /**
1225
+ * Check if a schema field is hidden (recursive traversal through wrappers).
1226
+ */
1227
+ export function isHiddenField(schema) {
1228
+ const metadata = getMetadataRecursive(schema);
1229
+ return metadata?.[ZodDecorator.IS_HIDDEN] !== undefined;
1230
+ }
1231
+ /**
1232
+ * Get the isHidden decorator parameters.
1233
+ * Returns default params (hidden in both display and edit) when decorator is used without params.
1234
+ */
1235
+ export function getHiddenFieldParams(schema) {
1236
+ const metadata = getMetadataRecursive(schema);
1237
+ const value = metadata?.[ZodDecorator.IS_HIDDEN];
1238
+ if (value === undefined)
1239
+ return undefined;
1240
+ // Default: hidden in both display and edit mode when decorator is used without params
1241
+ if (value === true)
1242
+ return { display: true, edit: true };
1243
+ return value;
1244
+ }
1245
+ /**
1246
+ * Check if a schema field is not editable (recursive traversal through wrappers).
1247
+ */
1248
+ export function isNotEditableField(schema) {
1249
+ const metadata = getMetadataRecursive(schema);
1250
+ return metadata?.[ZodDecorator.IS_NOT_EDITABLE] !== undefined;
1251
+ }
1252
+ /**
1253
+ * Get the isNotEditable decorator parameters.
1254
+ */
1255
+ export function getNotEditableFieldParams(schema) {
1256
+ const metadata = getMetadataRecursive(schema);
1257
+ const value = metadata?.[ZodDecorator.IS_NOT_EDITABLE];
1258
+ if (value === undefined)
1259
+ return undefined;
1260
+ if (value === true)
1261
+ return {}; // Default params when decorator is used without params
1262
+ return value;
1263
+ }
1264
+ /**
1265
+ * Check if a schema field has the isUnique decorator (recursive traversal through wrappers).
1266
+ */
1267
+ export function isUniqueField(schema) {
1268
+ const metadata = getMetadataRecursive(schema);
1269
+ return metadata?.[ZodDecorator.IS_UNIQUE] !== undefined;
1270
+ }
1271
+ /**
1272
+ * Get the isUnique decorator parameters (recursive traversal through wrappers).
1273
+ * Returns default params { scope: 'global', sparse: true } when decorator is used without params.
1274
+ */
1275
+ export function getUniqueFieldParams(schema) {
1276
+ const metadata = getMetadataRecursive(schema);
1277
+ const value = metadata?.[ZodDecorator.IS_UNIQUE];
1278
+ if (value === undefined)
1279
+ return undefined;
1280
+ if (value === true)
1281
+ return { scope: 'global', sparse: true }; // Default params from original getUniqueParams
1282
+ return value;
1283
+ }
1284
+ /**
1285
+ * Check if a schema field has the isPrimaryKey decorator (recursive traversal through wrappers).
1286
+ */
1287
+ export function isPrimaryKeyField(schema) {
1288
+ const metadata = getMetadataRecursive(schema);
1289
+ return metadata?.[ZodDecorator.IS_PRIMARY_KEY] === true;
1290
+ }
1291
+ /**
1292
+ * Get all metadata for a schema field (non-recursive, direct access only).
1293
+ */
1294
+ export function getFieldMetadata(schema) {
1295
+ return schema[SCHEMA_METADATA];
1296
+ }
1297
+ /**
1298
+ * Get all metadata for a schema field, traversing through wrapper types.
1299
+ */
1300
+ export function getFieldMetadataRecursive(schema) {
1301
+ return getMetadataRecursive(schema);
1302
+ }
1303
+ // =============================================================================
1304
+ // RANGE SCHEMA HELPERS
1305
+ // =============================================================================
1306
+ /**
1307
+ * Check if a schema is a range schema (created by z_range()).
1308
+ * Detects by the presence of rangeConfig in SCHEMA_METADATA.
1309
+ * Checks both the schema directly and the unwrapped inner schema.
1310
+ */
1311
+ export function isRangeSchema(schema) {
1312
+ // Check the schema directly first
1313
+ const schemaMetadata = schema[SCHEMA_METADATA];
1314
+ if (schemaMetadata?.rangeConfig?.isRange === true) {
1315
+ return true;
1316
+ }
1317
+ // Also check the unwrapped schema (metadata might be on inner schema)
1318
+ const unwrapped = unwrapSchema(schema);
1319
+ if (unwrapped !== schema) {
1320
+ const unwrappedMetadata = unwrapped[SCHEMA_METADATA];
1321
+ return unwrappedMetadata?.rangeConfig?.isRange === true;
1322
+ }
1323
+ return false;
1324
+ }
1325
+ /**
1326
+ * Get the range metadata from a schema.
1327
+ * Returns undefined if not a range schema.
1328
+ * Checks both the schema directly and the unwrapped inner schema.
1329
+ */
1330
+ export function getRangeMetadata(schema) {
1331
+ // Check the schema directly first
1332
+ const schemaMetadata = schema[SCHEMA_METADATA];
1333
+ if (schemaMetadata?.rangeConfig?.isRange) {
1334
+ return schemaMetadata.rangeConfig;
1335
+ }
1336
+ // Also check the unwrapped schema
1337
+ const unwrapped = unwrapSchema(schema);
1338
+ if (unwrapped !== schema) {
1339
+ const unwrappedMetadata = unwrapped[SCHEMA_METADATA];
1340
+ if (unwrappedMetadata?.rangeConfig?.isRange) {
1341
+ return unwrappedMetadata.rangeConfig;
1342
+ }
1343
+ }
1344
+ return undefined;
1345
+ }
1346
+ /**
1347
+ * Get the range type from a schema.
1348
+ * Returns undefined if not a range schema.
1349
+ */
1350
+ export function getRangeType(schema) {
1351
+ return getRangeMetadata(schema)?.type;
1352
+ }
1353
+ // =============================================================================
1354
+ // CURRENCY SCHEMA HELPERS
1355
+ // =============================================================================
1356
+ /**
1357
+ * Check if a schema is a money schema (created by z_money()).
1358
+ * Detects by the presence of moneyConfig in SCHEMA_METADATA.
1359
+ * Checks both the schema directly and the unwrapped inner schema.
1360
+ */
1361
+ export function isMoneySchema(schema) {
1362
+ // Check the schema directly first
1363
+ const schemaMetadata = schema[SCHEMA_METADATA];
1364
+ if (schemaMetadata?.moneyConfig?.isMoney === true) {
1365
+ return true;
1366
+ }
1367
+ // Also check the unwrapped schema (metadata might be on inner schema)
1368
+ const unwrapped = unwrapSchema(schema);
1369
+ if (unwrapped !== schema) {
1370
+ const unwrappedMetadata = unwrapped[SCHEMA_METADATA];
1371
+ return unwrappedMetadata?.moneyConfig?.isMoney === true;
1372
+ }
1373
+ return false;
1374
+ }
1375
+ /**
1376
+ * Get the money metadata from a schema.
1377
+ * Returns undefined if not a money schema.
1378
+ * Checks both the schema directly and the unwrapped inner schema.
1379
+ */
1380
+ export function getMoneyMetadata(schema) {
1381
+ // Check the schema directly first
1382
+ const schemaMetadata = schema[SCHEMA_METADATA];
1383
+ if (schemaMetadata?.moneyConfig?.isMoney) {
1384
+ return schemaMetadata.moneyConfig;
1385
+ }
1386
+ // Also check the unwrapped schema
1387
+ const unwrapped = unwrapSchema(schema);
1388
+ if (unwrapped !== schema) {
1389
+ const unwrappedMetadata = unwrapped[SCHEMA_METADATA];
1390
+ if (unwrappedMetadata?.moneyConfig?.isMoney) {
1391
+ return unwrappedMetadata.moneyConfig;
1392
+ }
1393
+ }
1394
+ return undefined;
1395
+ }
1396
+ // =============================================================================
1397
+ // BIGDECIMAL SCHEMA HELPERS
1398
+ // =============================================================================
1399
+ /**
1400
+ * Check if a schema is a bigdecimal schema (created by z_bigdecimal()).
1401
+ * Detects by the presence of bigDecimalConfig in SCHEMA_METADATA.
1402
+ * Checks both the schema directly and the unwrapped inner schema.
1403
+ */
1404
+ export function isBigDecimalSchema(schema) {
1405
+ // Check the schema directly first
1406
+ const schemaMetadata = schema[SCHEMA_METADATA];
1407
+ if (schemaMetadata?.bigDecimalConfig?.isBigDecimal === true) {
1408
+ return true;
1409
+ }
1410
+ // Also check the unwrapped schema (metadata might be on inner schema)
1411
+ const unwrapped = unwrapSchema(schema);
1412
+ if (unwrapped !== schema) {
1413
+ const unwrappedMetadata = unwrapped[SCHEMA_METADATA];
1414
+ return unwrappedMetadata?.bigDecimalConfig?.isBigDecimal === true;
1415
+ }
1416
+ return false;
1417
+ }
1418
+ /**
1419
+ * Get the bigdecimal metadata from a schema.
1420
+ * Returns undefined if not a bigdecimal schema.
1421
+ * Checks both the schema directly and the unwrapped inner schema.
1422
+ */
1423
+ export function getBigDecimalMetadata(schema) {
1424
+ // Check the schema directly first
1425
+ const schemaMetadata = schema[SCHEMA_METADATA];
1426
+ if (schemaMetadata?.bigDecimalConfig?.isBigDecimal) {
1427
+ return schemaMetadata.bigDecimalConfig;
1428
+ }
1429
+ // Also check the unwrapped schema
1430
+ const unwrapped = unwrapSchema(schema);
1431
+ if (unwrapped !== schema) {
1432
+ const unwrappedMetadata = unwrapped[SCHEMA_METADATA];
1433
+ if (unwrappedMetadata?.bigDecimalConfig?.isBigDecimal) {
1434
+ return unwrappedMetadata.bigDecimalConfig;
1435
+ }
1436
+ }
1437
+ return undefined;
1438
+ }
1439
+ /**
1440
+ * Get bigdecimal constraints from schema metadata.
1441
+ * Returns min, max, precision from the bigdecimal schema configuration.
1442
+ */
1443
+ export function getBigDecimalConstraints(schema) {
1444
+ const metadata = getBigDecimalMetadata(schema);
1445
+ if (!metadata) {
1446
+ return {};
1447
+ }
1448
+ return {
1449
+ min: metadata.min,
1450
+ max: metadata.max,
1451
+ precision: metadata.precision,
1452
+ };
1453
+ }
1454
+ // =============================================================================
1455
+ // DURATION SCHEMA HELPERS
1456
+ // =============================================================================
1457
+ /**
1458
+ * Check if a schema is a duration schema (created by z_duration()).
1459
+ * Detects by the presence of durationConfig in SCHEMA_METADATA.
1460
+ * Checks both the schema directly and the unwrapped inner schema.
1461
+ */
1462
+ export function isDurationSchema(schema) {
1463
+ const schemaMetadata = schema[SCHEMA_METADATA];
1464
+ if (schemaMetadata?.durationConfig?.isDuration === true) {
1465
+ return true;
1466
+ }
1467
+ const unwrapped = unwrapSchema(schema);
1468
+ if (unwrapped !== schema) {
1469
+ const unwrappedMetadata = unwrapped[SCHEMA_METADATA];
1470
+ return unwrappedMetadata?.durationConfig?.isDuration === true;
1471
+ }
1472
+ return false;
1473
+ }
1474
+ /**
1475
+ * Get the duration metadata from a schema.
1476
+ * Returns undefined if not a duration schema.
1477
+ */
1478
+ export function getDurationMetadata(schema) {
1479
+ const schemaMetadata = schema[SCHEMA_METADATA];
1480
+ if (schemaMetadata?.durationConfig?.isDuration) {
1481
+ return schemaMetadata.durationConfig;
1482
+ }
1483
+ const unwrapped = unwrapSchema(schema);
1484
+ if (unwrapped !== schema) {
1485
+ const unwrappedMetadata = unwrapped[SCHEMA_METADATA];
1486
+ if (unwrappedMetadata?.durationConfig?.isDuration) {
1487
+ return unwrappedMetadata.durationConfig;
1488
+ }
1489
+ }
1490
+ return undefined;
1491
+ }
1492
+ /**
1493
+ * Get duration constraints from schema metadata.
1494
+ */
1495
+ export function getDurationConstraints(schema) {
1496
+ const metadata = getDurationMetadata(schema);
1497
+ if (!metadata) {
1498
+ return {};
1499
+ }
1500
+ return {
1501
+ minValue: metadata.minValue,
1502
+ maxValue: metadata.maxValue,
1503
+ allowedUnits: metadata.allowedUnits,
1504
+ };
1505
+ }
1506
+ /**
1507
+ * Check if a schema is a phone schema (created by z_phone()).
1508
+ * Detects by the presence of phoneConfig in SCHEMA_METADATA.
1509
+ * Checks both the schema directly and the unwrapped inner schema.
1510
+ */
1511
+ export function isPhoneSchema(schema) {
1512
+ const schemaMetadata = schema[SCHEMA_METADATA];
1513
+ if (schemaMetadata?.phoneConfig?.isPhone === true) {
1514
+ return true;
1515
+ }
1516
+ const unwrapped = unwrapSchema(schema);
1517
+ if (unwrapped !== schema) {
1518
+ const unwrappedMetadata = unwrapped[SCHEMA_METADATA];
1519
+ return unwrappedMetadata?.phoneConfig?.isPhone === true;
1520
+ }
1521
+ return false;
1522
+ }
1523
+ /**
1524
+ * Get the phone metadata from a schema.
1525
+ * Returns undefined if not a phone schema.
1526
+ */
1527
+ export function getPhoneMetadata(schema) {
1528
+ const schemaMetadata = schema[SCHEMA_METADATA];
1529
+ if (schemaMetadata?.phoneConfig?.isPhone) {
1530
+ return schemaMetadata.phoneConfig;
1531
+ }
1532
+ const unwrapped = unwrapSchema(schema);
1533
+ if (unwrapped !== schema) {
1534
+ const unwrappedMetadata = unwrapped[SCHEMA_METADATA];
1535
+ if (unwrappedMetadata?.phoneConfig?.isPhone) {
1536
+ return unwrappedMetadata.phoneConfig;
1537
+ }
1538
+ }
1539
+ return undefined;
1540
+ }
1541
+ /**
1542
+ * Check if a schema is a code schema created by z_code().
1543
+ * Checks both the schema directly and the unwrapped inner schema.
1544
+ */
1545
+ export function isCodeSchema(schema) {
1546
+ // Check the schema directly first
1547
+ const schemaMetadata = schema[SCHEMA_METADATA];
1548
+ if (schemaMetadata?.codeConfig?.isCode === true) {
1549
+ return true;
1550
+ }
1551
+ // Also check the unwrapped schema (metadata might be on inner schema)
1552
+ const unwrapped = unwrapSchema(schema);
1553
+ if (unwrapped !== schema) {
1554
+ const unwrappedMetadata = unwrapped[SCHEMA_METADATA];
1555
+ return unwrappedMetadata?.codeConfig?.isCode === true;
1556
+ }
1557
+ return false;
1558
+ }
1559
+ /**
1560
+ * Get code schema metadata if this is a code schema.
1561
+ * Checks both the schema directly and the unwrapped inner schema.
1562
+ */
1563
+ export function getCodeMetadata(schema) {
1564
+ // Check the schema directly first
1565
+ const schemaMetadata = schema[SCHEMA_METADATA];
1566
+ if (schemaMetadata?.codeConfig?.isCode) {
1567
+ return schemaMetadata.codeConfig;
1568
+ }
1569
+ // Also check the unwrapped schema
1570
+ const unwrapped = unwrapSchema(schema);
1571
+ if (unwrapped !== schema) {
1572
+ const unwrappedMetadata = unwrapped[SCHEMA_METADATA];
1573
+ if (unwrappedMetadata?.codeConfig?.isCode) {
1574
+ return unwrappedMetadata.codeConfig;
1575
+ }
1576
+ }
1577
+ return undefined;
1578
+ }
1579
+ /**
1580
+ * Check if a schema is a file schema created by z_file().
1581
+ * Checks both the schema directly and the unwrapped inner schema.
1582
+ */
1583
+ export function isFileSchema(schema) {
1584
+ // Check the schema directly first
1585
+ const schemaMetadata = schema[SCHEMA_METADATA];
1586
+ if (schemaMetadata?.fileConfig?.isFile === true) {
1587
+ return true;
1588
+ }
1589
+ // Also check the unwrapped schema (metadata might be on inner schema)
1590
+ const unwrapped = unwrapSchema(schema);
1591
+ if (unwrapped !== schema) {
1592
+ const unwrappedMetadata = unwrapped[SCHEMA_METADATA];
1593
+ return unwrappedMetadata?.fileConfig?.isFile === true;
1594
+ }
1595
+ return false;
1596
+ }
1597
+ /**
1598
+ * Get file schema metadata if this is a file schema.
1599
+ * Checks both the schema directly and the unwrapped inner schema.
1600
+ */
1601
+ export function getFileMetadata(schema) {
1602
+ // Check the schema directly first
1603
+ const schemaMetadata = schema[SCHEMA_METADATA];
1604
+ if (schemaMetadata?.fileConfig?.isFile) {
1605
+ return schemaMetadata.fileConfig;
1606
+ }
1607
+ // Also check the unwrapped schema
1608
+ const unwrapped = unwrapSchema(schema);
1609
+ if (unwrapped !== schema) {
1610
+ const unwrappedMetadata = unwrapped[SCHEMA_METADATA];
1611
+ if (unwrappedMetadata?.fileConfig?.isFile) {
1612
+ return unwrappedMetadata.fileConfig;
1613
+ }
1614
+ }
1615
+ return undefined;
1616
+ }
1617
+ // -----------------------------------------------------------------------------
1618
+ // VERSIONED SCHEMA HELPERS
1619
+ // -----------------------------------------------------------------------------
1620
+ /**
1621
+ * Check if a schema is a versioned foreign key schema created by z_foreignKeyVersioned().
1622
+ * Checks both the schema directly and the unwrapped inner schema.
1623
+ */
1624
+ export function isVersionedForeignKeySchema(schema) {
1625
+ // Check the schema directly first
1626
+ const schemaMetadata = schema[SCHEMA_METADATA];
1627
+ if (schemaMetadata?.versionedForeignKeyConfig?.isVersionedForeignKey === true) {
1628
+ return true;
1629
+ }
1630
+ // Also check the unwrapped schema (metadata might be on inner schema)
1631
+ const unwrapped = unwrapSchema(schema);
1632
+ if (unwrapped !== schema) {
1633
+ const unwrappedMetadata = unwrapped[SCHEMA_METADATA];
1634
+ return unwrappedMetadata?.versionedForeignKeyConfig?.isVersionedForeignKey === true;
1635
+ }
1636
+ return false;
1637
+ }
1638
+ /**
1639
+ * Get versioned foreign key schema metadata if this is a versioned foreign key schema.
1640
+ * Checks both the schema directly and the unwrapped inner schema.
1641
+ */
1642
+ export function getVersionedForeignKeyMetadata(schema) {
1643
+ // Check the schema directly first
1644
+ const schemaMetadata = schema[SCHEMA_METADATA];
1645
+ if (schemaMetadata?.versionedForeignKeyConfig?.isVersionedForeignKey) {
1646
+ return schemaMetadata.versionedForeignKeyConfig;
1647
+ }
1648
+ // Also check the unwrapped schema
1649
+ const unwrapped = unwrapSchema(schema);
1650
+ if (unwrapped !== schema) {
1651
+ const unwrappedMetadata = unwrapped[SCHEMA_METADATA];
1652
+ if (unwrappedMetadata?.versionedForeignKeyConfig?.isVersionedForeignKey) {
1653
+ return unwrappedMetadata.versionedForeignKeyConfig;
1654
+ }
1655
+ }
1656
+ return undefined;
1657
+ }
1658
+ /**
1659
+ * Check if a schema is a versioned object schema created by versioned().
1660
+ * Checks both the schema directly and the unwrapped inner schema.
1661
+ */
1662
+ export function isVersionedObjectSchema(schema) {
1663
+ // Check the schema directly first
1664
+ const schemaMetadata = schema[SCHEMA_METADATA];
1665
+ if (schemaMetadata?.versionedConfig?.isVersioned === true) {
1666
+ return true;
1667
+ }
1668
+ // Also check the unwrapped schema (metadata might be on inner schema)
1669
+ const unwrapped = unwrapSchema(schema);
1670
+ if (unwrapped !== schema) {
1671
+ const unwrappedMetadata = unwrapped[SCHEMA_METADATA];
1672
+ return unwrappedMetadata?.versionedConfig?.isVersioned === true;
1673
+ }
1674
+ return false;
1675
+ }
1676
+ /**
1677
+ * Get versioned schema metadata if this is a versioned object schema.
1678
+ * Checks both the schema directly and the unwrapped inner schema.
1679
+ */
1680
+ export function getVersionedObjectMetadata(schema) {
1681
+ // Check the schema directly first
1682
+ const schemaMetadata = schema[SCHEMA_METADATA];
1683
+ if (schemaMetadata?.versionedConfig?.isVersioned) {
1684
+ return schemaMetadata.versionedConfig;
1685
+ }
1686
+ // Also check the unwrapped schema
1687
+ const unwrapped = unwrapSchema(schema);
1688
+ if (unwrapped !== schema) {
1689
+ const unwrappedMetadata = unwrapped[SCHEMA_METADATA];
1690
+ if (unwrappedMetadata?.versionedConfig?.isVersioned) {
1691
+ return unwrappedMetadata.versionedConfig;
1692
+ }
1693
+ }
1694
+ return undefined;
1695
+ }
1696
+ /**
1697
+ * Check if the 'from' field is optional in a range schema.
1698
+ */
1699
+ export function isRangeFromOptional(schema) {
1700
+ return getRangeMetadata(schema)?.fromOptional;
1701
+ }
1702
+ /**
1703
+ * Check if the 'to' field is optional in a range schema.
1704
+ */
1705
+ export function isRangeToOptional(schema) {
1706
+ return getRangeMetadata(schema)?.toOptional;
1707
+ }
1708
+ /**
1709
+ * Get the min constraint from a range schema.
1710
+ */
1711
+ export function getRangeMin(schema) {
1712
+ return getRangeMetadata(schema)?.min;
1713
+ }
1714
+ /**
1715
+ * Get the max constraint from a range schema.
1716
+ */
1717
+ export function getRangeMax(schema) {
1718
+ return getRangeMetadata(schema)?.max;
1719
+ }
1720
+ // =============================================================================
1721
+ // FORM FIELD DECORATOR UTILITIES
1722
+ // =============================================================================
1723
+ /**
1724
+ * Get the form component from `{primitive}UI.edit.component`.
1725
+ * Returns undefined if no form component is specified.
1726
+ */
1727
+ export function getFormFieldComponent(schema) {
1728
+ return getFieldUIEditConfig(schema)?.component;
1729
+ }
1730
+ /**
1731
+ * Check if a field should be hidden based on the current context.
1732
+ * @param schema - The Zod schema to check
1733
+ * @param context - Optional context with mode ('display' | 'edit') and operation name
1734
+ */
1735
+ export function shouldHideField(schema, context) {
1736
+ if (!hasHiddenDecorator(schema)) {
1737
+ return false;
1738
+ }
1739
+ const hidden = getHiddenFieldMetadata(schema);
1740
+ // If hidden is true, always hide
1741
+ if (hidden === true) {
1742
+ return true;
1743
+ }
1744
+ // Check mode-specific hiding
1745
+ if (context?.mode === 'display' && hidden?.display) {
1746
+ return true;
1747
+ }
1748
+ if (context?.mode === 'edit' && hidden?.edit) {
1749
+ return true;
1750
+ }
1751
+ // Check operation-specific hiding
1752
+ if (context?.operation && hidden?.operations?.includes(context.operation)) {
1753
+ return true;
1754
+ }
1755
+ return false;
1756
+ }
1757
+ // -----------------------------------------------------------------------------
1758
+ // Form Component Check Functions (isFormXX)
1759
+ // -----------------------------------------------------------------------------
1760
+ export function isFormInput(schema) {
1761
+ return getFormFieldComponent(schema) === FormFieldComponent.INPUT;
1762
+ }
1763
+ export function isFormTextarea(schema) {
1764
+ return getFormFieldComponent(schema) === FormFieldComponent.TEXTAREA;
1765
+ }
1766
+ export function isFormSelect(schema) {
1767
+ return getFormFieldComponent(schema) === FormFieldComponent.SELECT;
1768
+ }
1769
+ export function isFormRadioGroup(schema) {
1770
+ return getFormFieldComponent(schema) === FormFieldComponent.RADIO_GROUP;
1771
+ }
1772
+ export function isFormCheckbox(schema) {
1773
+ return getFormFieldComponent(schema) === FormFieldComponent.CHECKBOX;
1774
+ }
1775
+ export function isFormCheckboxGroup(schema) {
1776
+ return getFormFieldComponent(schema) === FormFieldComponent.CHECKBOX_GROUP;
1777
+ }
1778
+ export function isFormSwitch(schema) {
1779
+ return getFormFieldComponent(schema) === FormFieldComponent.SWITCH;
1780
+ }
1781
+ export function isFormSlider(schema) {
1782
+ return getFormFieldComponent(schema) === FormFieldComponent.SLIDER;
1783
+ }
1784
+ export function isFormDate(schema) {
1785
+ return getFormFieldComponent(schema) === FormFieldComponent.DATE;
1786
+ }
1787
+ // =============================================================================
1788
+ // HIDDEN FIELD DECORATOR UTILITIES
1789
+ // =============================================================================
1790
+ /**
1791
+ * Get the hidden field metadata from schema decorator.
1792
+ * Returns undefined if no hidden decorator is set.
1793
+ * Returns true if field is hidden in all cases.
1794
+ * Returns HiddenFieldParams if conditional hiding is configured.
1795
+ * Note: getCustomFieldMetadata already handles recursive unwrapping for all wrapper types
1796
+ * including pipe (from refine/superRefine), optional, nullable, default, etc.
1797
+ */
1798
+ export function getHiddenFieldMetadata(schema) {
1799
+ const metadata = getCustomFieldMetadata(schema);
1800
+ return metadata[ZodDecorator.IS_HIDDEN];
1801
+ }
1802
+ /**
1803
+ * Check if a schema has a hidden decorator.
1804
+ */
1805
+ export function hasHiddenDecorator(schema) {
1806
+ return getHiddenFieldMetadata(schema) !== undefined;
1807
+ }
1808
+ /**
1809
+ * Check if a field should be hidden for display mode.
1810
+ * @param schema - The Zod schema to check
1811
+ * @returns true if the field should be hidden in display mode
1812
+ */
1813
+ export function isHiddenForDisplay(schema) {
1814
+ const hidden = getHiddenFieldMetadata(schema);
1815
+ if (hidden === undefined)
1816
+ return false;
1817
+ if (hidden === true)
1818
+ return true;
1819
+ return hidden.display === true;
1820
+ }
1821
+ /**
1822
+ * Check if a field should be hidden for edit mode.
1823
+ * @param schema - The Zod schema to check
1824
+ * @returns true if the field should be hidden in edit mode
1825
+ */
1826
+ export function isHiddenForEdit(schema) {
1827
+ const hidden = getHiddenFieldMetadata(schema);
1828
+ if (hidden === undefined)
1829
+ return false;
1830
+ if (hidden === true)
1831
+ return true;
1832
+ return hidden.edit === true;
1833
+ }
1834
+ /**
1835
+ * Check if a field should be hidden for a specific operation.
1836
+ * @param schema - The Zod schema to check
1837
+ * @param operation - The operation identifier (e.g., 'create', 'update')
1838
+ * @returns true if the field should be hidden for the given operation
1839
+ */
1840
+ export function isHiddenForOperation(schema, operation) {
1841
+ const hidden = getHiddenFieldMetadata(schema);
1842
+ if (hidden === undefined)
1843
+ return false;
1844
+ if (hidden === true)
1845
+ return true;
1846
+ return hidden.operations?.includes(operation) ?? false;
1847
+ }
1848
+ // =============================================================================
1849
+ // NOT EDITABLE FIELD DECORATOR UTILITIES
1850
+ // =============================================================================
1851
+ /**
1852
+ * Get the not editable field metadata from schema decorator.
1853
+ * Returns undefined if no isNotEditable decorator is set.
1854
+ * Returns true if field is not editable for all operations.
1855
+ * Returns NotEditableFieldParams if operation-specific.
1856
+ * Note: getCustomFieldMetadata already handles recursive unwrapping for all wrapper types
1857
+ * including pipe (from refine/superRefine), optional, nullable, default, etc.
1858
+ */
1859
+ export function getNotEditableFieldMetadata(schema) {
1860
+ const metadata = getCustomFieldMetadata(schema);
1861
+ return metadata[ZodDecorator.IS_NOT_EDITABLE];
1862
+ }
1863
+ /**
1864
+ * Check if a schema has a isNotEditable decorator.
1865
+ */
1866
+ export function hasNotEditableDecorator(schema) {
1867
+ return getNotEditableFieldMetadata(schema) !== undefined;
1868
+ }
1869
+ /**
1870
+ * Check if a field is not editable for a specific operation (or all operations).
1871
+ * @param schema - The Zod schema to check
1872
+ * @param operation - Optional operation identifier. If not provided, checks if not editable for all operations.
1873
+ * @returns true if the field is not editable
1874
+ */
1875
+ export function isNotEditableForOperation(schema, operation) {
1876
+ const notEditable = getNotEditableFieldMetadata(schema);
1877
+ if (notEditable === undefined)
1878
+ return false;
1879
+ // If true, not editable for all operations
1880
+ if (notEditable === true)
1881
+ return true;
1882
+ // If no operations specified in params, not editable for all operations
1883
+ if (!notEditable.operations || notEditable.operations.length === 0)
1884
+ return true;
1885
+ // If operation is provided, check if it matches
1886
+ if (operation) {
1887
+ return notEditable.operations.includes(operation);
1888
+ }
1889
+ // Operation not provided but params have specific operations - not editable only for those
1890
+ return false;
1891
+ }
1892
+ /**
1893
+ * Check if a schema has inline editable configuration.
1894
+ * Reads from `{primitive}UI.display.inlineEditable`.
1895
+ */
1896
+ export function hasInlineEditableDecorator(schema) {
1897
+ return getFieldUIDisplayConfig(schema)?.inlineEditable != null;
1898
+ }
1899
+ /**
1900
+ * Get the resolved inline editable params from `{primitive}UI.display.inlineEditable`.
1901
+ * Handles both `true` shorthand and full object config.
1902
+ */
1903
+ export function getInlineEditableParams(schema) {
1904
+ const config = getFieldUIDisplayConfig(schema)?.inlineEditable;
1905
+ if (config == null)
1906
+ return undefined;
1907
+ if (config === true) {
1908
+ return { mode: 'direct', saveOn: 'blur', showSaveButton: false, showCancelButton: true };
1909
+ }
1910
+ if (config === false)
1911
+ return undefined;
1912
+ const saveOn = config.saveOn ?? 'blur';
1913
+ const showSaveButton = config.showSaveButton ?? (saveOn === 'explicit');
1914
+ return {
1915
+ mode: config.mode ?? 'direct',
1916
+ saveOn,
1917
+ showSaveButton,
1918
+ showCancelButton: config.showCancelButton ?? true,
1919
+ };
1920
+ }
1921
+ // ============================================================================
1922
+ // ZOD .describe() UTILITY (NOT for UI descriptions — developer/AI/API docs only)
1923
+ // ============================================================================
1924
+ /**
1925
+ * Extract the `.describe()` text from a Zod schema, unwrapping optional/nullable/default wrappers.
1926
+ * Returns undefined if no `.describe("...")` is set.
1927
+ *
1928
+ * **IMPORTANT**: Zod `.describe("...")` is for developer documentation, AI prompting systems,
1929
+ * and API documentation generation. It is NOT used for UI field descriptions — those are
1930
+ * managed through `{primitive}UI.display.showDescription` + i18n system.
1931
+ *
1932
+ * In Zod 4, `.describe("...")` sets the `.description` property on the schema.
1933
+ * When `.optional()`, `.nullable()`, or `.default()` wrap the schema, the description
1934
+ * may live on the inner schema. We check both levels.
1935
+ */
1936
+ export function getZodDescription(schema) {
1937
+ // Check outer schema first
1938
+ if (schema.description)
1939
+ return schema.description;
1940
+ // Unwrap optional/nullable/default to check inner schema
1941
+ const inner = schema._zod?.innerType ?? schema.unwrap?.();
1942
+ if (inner?.description)
1943
+ return inner.description;
1944
+ return undefined;
1945
+ }
1946
+ // ============================================================================
1947
+ // FIELD DESCRIPTION HELPERS (opt-in i18n key generation, mirrors tooltip pattern)
1948
+ // ============================================================================
1949
+ /**
1950
+ * Check if a field should have a description i18n key generated for **display** mode.
1951
+ *
1952
+ * Reads from `{primitive}UI.display.showDescription` (boolean flag).
1953
+ * This follows the same opt-in pattern as tooltips: only generate i18n keys for
1954
+ * fields that explicitly declare intent via a decorator.
1955
+ *
1956
+ * **NOTE**: Zod `.describe("...")` is NOT an opt-in signal for UI descriptions.
1957
+ * `.describe()` serves a different purpose (developer docs, AI prompting, API docs).
1958
+ * UI field descriptions are purely decorator-driven + i18n-resolved.
1959
+ *
1960
+ * @param schema - The Zod schema to check
1961
+ * @returns true if a description i18n key should be generated for display mode
1962
+ */
1963
+ export function hasDescriptionForDisplay(schema) {
1964
+ return getFieldUIDisplayConfig(schema)?.showDescription === true;
1965
+ }
1966
+ /**
1967
+ * Check if a field should have a description i18n key generated for **edit** mode.
1968
+ *
1969
+ * Reads from `{primitive}UI.edit.showDescription` (boolean flag).
1970
+ * If a field declares it wants a description, both display AND edit i18n keys are generated
1971
+ * so translators can provide mode-appropriate text.
1972
+ *
1973
+ * @param schema - The Zod schema to check
1974
+ * @returns true if a description i18n key should be generated for edit mode
1975
+ */
1976
+ export function hasDescriptionForEdit(schema) {
1977
+ return getFieldUIEditConfig(schema)?.showDescription === true;
1978
+ }
1979
+ // ============================================================================
1980
+ // BOOLEAN DISPLAY POLICY HELPERS
1981
+ // ============================================================================
1982
+ /**
1983
+ * Check if a string schema should render as a password input.
1984
+ * Checks IS_PASSWORD structural marker first, then STRING_UI editMode fallback.
1985
+ */
1986
+ export function isPasswordField(schema) {
1987
+ const metadata = getCustomFieldMetadata(schema);
1988
+ if (metadata?.[ZodDecorator.IS_PASSWORD]) {
1989
+ return true;
1990
+ }
1991
+ const stringUI = getStringUI(schema);
1992
+ return stringUI?.edit?.editMode === 'password';
1993
+ }
1994
+ /**
1995
+ * Check if a string schema should render as an OTP input.
1996
+ * Checks IS_OTP structural marker first, then STRING_UI editMode fallback.
1997
+ */
1998
+ export function isOtpField(schema) {
1999
+ const metadata = getCustomFieldMetadata(schema);
2000
+ if (metadata?.[ZodDecorator.IS_OTP]) {
2001
+ return true;
2002
+ }
2003
+ const stringUI = getStringUI(schema);
2004
+ return stringUI?.edit?.editMode === 'otp';
2005
+ }
2006
+ /**
2007
+ * Check if a string schema should render as a color input.
2008
+ * Checks IS_COLOR structural marker first, then STRING_UI editMode fallback.
2009
+ */
2010
+ export function isColorField(schema) {
2011
+ const metadata = getCustomFieldMetadata(schema);
2012
+ if (metadata?.[ZodDecorator.IS_COLOR]) {
2013
+ return true;
2014
+ }
2015
+ const stringUI = getStringUI(schema);
2016
+ return stringUI?.edit?.editMode === 'color';
2017
+ }
2018
+ /**
2019
+ * Get OTP field params from a schema.
2020
+ * Returns the OTP params with defaults merged in if the decorator is present.
2021
+ * Defaults: maxLength=6
2022
+ */
2023
+ export function getOtpFieldParams(schema) {
2024
+ const metadata = getCustomFieldMetadata(schema);
2025
+ const otpParams = metadata?.[ZodDecorator.IS_OTP];
2026
+ if (!otpParams) {
2027
+ return undefined;
2028
+ }
2029
+ // If params is true (enabled without params), return defaults
2030
+ if (otpParams === true) {
2031
+ return { maxLength: 6 };
2032
+ }
2033
+ // Merge with defaults
2034
+ return {
2035
+ maxLength: 6,
2036
+ ...otpParams,
2037
+ };
2038
+ }
2039
+ /**
2040
+ * Get the system enum name from a schema's metadata.
2041
+ * Returns the name string (e.g. 'AvailableCurrency') if the .systemEnum() decorator is set,
2042
+ * or undefined if the field is not a system enum.
2043
+ */
2044
+ export function getSystemEnumName(schema) {
2045
+ const metadata = getCustomFieldMetadata(schema);
2046
+ return metadata[ZodDecorator.SYSTEM_ENUM];
2047
+ }
2048
+ //===============================================
2049
+ // SERIALIZATION (Zod 4)
2050
+ //===============================================
2051
+ /**
2052
+ * Serialize a Zod schema to a JSON-serializable format.
2053
+ * Uses Zod 4 internal structure (_zod.def.type).
2054
+ */
2055
+ export function serializeZodSchema(schema, seen = new Set()) {
2056
+ if (!schema)
2057
+ return null;
2058
+ // Avoid circular refs
2059
+ if (seen.has(schema)) {
2060
+ return { type: 'lazy' };
2061
+ }
2062
+ seen.add(schema);
2063
+ // Helper to add optional/nullable flags
2064
+ function withOptionals(base, optional, nullable) {
2065
+ return {
2066
+ ...base,
2067
+ ...(optional ? { optional: true } : {}),
2068
+ ...(nullable ? { nullable: true } : {})
2069
+ };
2070
+ }
2071
+ // Unwrap wrappers and track optional/nullable state (Zod 4 structure)
2072
+ const unwrapWithFlags = (s) => {
2073
+ let cur = s;
2074
+ let optional = false;
2075
+ let nullable = false;
2076
+ let type = getZodType(cur);
2077
+ // Iteratively unwrap wrappers
2078
+ while (type) {
2079
+ switch (type) {
2080
+ case 'optional':
2081
+ optional = true;
2082
+ cur = getZodDefSchemaProperty(cur, 'innerType') ?? cur;
2083
+ break;
2084
+ case 'nullable':
2085
+ nullable = true;
2086
+ cur = getZodDefSchemaProperty(cur, 'innerType') ?? cur;
2087
+ break;
2088
+ case 'default':
2089
+ cur = getZodDefSchemaProperty(cur, 'innerType') ?? cur;
2090
+ break;
2091
+ case 'pipe':
2092
+ // For pipe/effects, prefer the output schema for serialization
2093
+ cur = getZodDefSchemaProperty(cur, 'out') ?? getZodDefSchemaProperty(cur, 'in') ?? cur;
2094
+ break;
2095
+ case 'readonly':
2096
+ cur = getZodDefSchemaProperty(cur, 'innerType') ?? cur;
2097
+ break;
2098
+ case 'catch':
2099
+ cur = getZodDefSchemaProperty(cur, 'innerType') ?? cur;
2100
+ break;
2101
+ default:
2102
+ // Not a wrapper type, stop unwrapping
2103
+ return { inner: cur, optional, nullable };
2104
+ }
2105
+ type = getZodType(cur);
2106
+ }
2107
+ return { inner: cur, optional, nullable };
2108
+ };
2109
+ const { inner, optional, nullable } = unwrapWithFlags(schema);
2110
+ // Get custom field metadata from the ORIGINAL schema (not inner)
2111
+ // This ensures we find decorators at any wrapper level
2112
+ const metadata = getCustomFieldMetadata(schema);
2113
+ // Also check for range metadata (stored in SCHEMA_METADATA.rangeConfig)
2114
+ const rangeMetadata = getRangeMetadata(schema) ?? getRangeMetadata(inner);
2115
+ const withMeta = (o) => {
2116
+ const hasMeta = metadata && Object.keys(metadata).length > 0;
2117
+ const hasRangeMeta = rangeMetadata !== undefined;
2118
+ if (!hasMeta && !hasRangeMeta)
2119
+ return o;
2120
+ // Merge custom field metadata with range metadata if present
2121
+ const fullMetadata = {
2122
+ ...metadata,
2123
+ ...(hasRangeMeta ? { rangeType: rangeMetadata.type, rangeConfig: rangeMetadata } : {}),
2124
+ };
2125
+ return { ...o, metadata: fullMetadata };
2126
+ };
2127
+ // Get type from Zod 4 structure (_zod.def.type)
2128
+ const type = getZodType(inner);
2129
+ switch (type) {
2130
+ case 'string': {
2131
+ const description = inner.description;
2132
+ return withOptionals(withMeta({ type: 'string', ...(description ? { description } : {}) }), optional, nullable);
2133
+ }
2134
+ case 'number':
2135
+ case 'int':
2136
+ return withOptionals(withMeta({ type: 'number' }), optional, nullable);
2137
+ case 'bigint':
2138
+ return withOptionals(withMeta({ type: 'bigint' }), optional, nullable);
2139
+ case 'boolean':
2140
+ return withOptionals(withMeta({ type: 'boolean' }), optional, nullable);
2141
+ case 'date':
2142
+ return withOptionals(withMeta({ type: 'date' }), optional, nullable);
2143
+ case 'any':
2144
+ return withOptionals(withMeta({ type: 'any' }), optional, nullable);
2145
+ case 'unknown':
2146
+ return withOptionals(withMeta({ type: 'unknown' }), optional, nullable);
2147
+ case 'literal': {
2148
+ const value = getZodDefProperty(inner, 'value');
2149
+ return withOptionals(withMeta({ type: 'literal', value }), optional, nullable);
2150
+ }
2151
+ case 'enum': {
2152
+ // In Zod 4, both z.enum() and z.enum() use type 'enum'
2153
+ // Use the existing getEnumValues helper which handles both cases
2154
+ const values = getEnumValues(inner);
2155
+ return withOptionals(withMeta({ type: 'enum', values }), optional, nullable);
2156
+ }
2157
+ case 'array': {
2158
+ const element = getZodDefSchemaProperty(inner, 'element');
2159
+ return withOptionals(withMeta({
2160
+ type: 'array',
2161
+ element: serializeZodSchema(element, new Set(seen))
2162
+ }), optional, nullable);
2163
+ }
2164
+ case 'set': {
2165
+ const element = getZodDefSchemaProperty(inner, 'valueType');
2166
+ return withOptionals(withMeta({
2167
+ type: 'set',
2168
+ element: serializeZodSchema(element, new Set(seen))
2169
+ }), optional, nullable);
2170
+ }
2171
+ case 'tuple': {
2172
+ const items = getZodDefArrayProperty(inner, 'items') ?? [];
2173
+ return withOptionals(withMeta({
2174
+ type: 'tuple',
2175
+ items: items
2176
+ .filter((o) => isZodTypeAny(o))
2177
+ .map(o => serializeZodSchema(o, new Set(seen)))
2178
+ }), optional, nullable);
2179
+ }
2180
+ case 'record': {
2181
+ const keySchema = getZodDefSchemaProperty(inner, 'keyType');
2182
+ const valueSchema = getZodDefSchemaProperty(inner, 'valueType');
2183
+ return withOptionals(withMeta({
2184
+ type: 'record',
2185
+ key: serializeZodSchema(keySchema, new Set(seen)),
2186
+ value: serializeZodSchema(valueSchema, new Set(seen))
2187
+ }), optional, nullable);
2188
+ }
2189
+ case 'union': {
2190
+ const options = getZodDefArrayProperty(inner, 'options') ?? [];
2191
+ return withOptionals(withMeta({
2192
+ type: 'union',
2193
+ options: options
2194
+ .filter((o) => isZodTypeAny(o))
2195
+ .map(o => serializeZodSchema(o, new Set(seen)))
2196
+ }), optional, nullable);
2197
+ }
2198
+ case 'object': {
2199
+ // Use the existing getZodObjectShape helper (Zod 4 uses .shape property)
2200
+ const shape = getZodObjectShape(inner);
2201
+ const props = {};
2202
+ for (const [k, v] of Object.entries(shape)) {
2203
+ props[k] = serializeZodSchema(v, new Set(seen)) || { type: 'unknown' };
2204
+ }
2205
+ return withOptionals(withMeta({ type: 'object', properties: props }), optional, nullable);
2206
+ }
2207
+ case 'lazy':
2208
+ return withOptionals(withMeta({ type: 'lazy' }), optional, nullable);
2209
+ default:
2210
+ return withOptionals(withMeta({ type: 'unknown' }), optional, nullable);
2211
+ }
2212
+ }
2213
+ /**
2214
+ * Deserialize a SerializableZodSchema back into a Zod schema.
2215
+ * - Reconstructs approximate Zod types from the descriptor
2216
+ * - Re-applies optional/nullable wrappers
2217
+ * - Re-applies SCHEMA_METADATA as decorators when possible
2218
+ */
2219
+ export function deserializeZodSchema(desc) {
2220
+ if (!desc)
2221
+ return undefined;
2222
+ const applyOptionalNullable = (schema, d) => {
2223
+ let s = schema;
2224
+ if (d.optional)
2225
+ s = s.optional();
2226
+ if (d.nullable)
2227
+ s = s.nullable();
2228
+ return s;
2229
+ };
2230
+ const applyMetadata = (schema, metadata) => {
2231
+ if (!metadata || Object.keys(metadata).length === 0)
2232
+ return schema;
2233
+ const decorators = [];
2234
+ // Boolean decorators (no params)
2235
+ if (metadata[ZodDecorator.IS_DB_INDEXED]) {
2236
+ decorators.push(ZodDecorator.IS_DB_INDEXED);
2237
+ }
2238
+ if (metadata[ZodDecorator.IS_FOREIGN_KEY]) {
2239
+ decorators.push(ZodDecorator.IS_FOREIGN_KEY);
2240
+ }
2241
+ // Hidden decorator (HiddenFieldParams | true)
2242
+ const hidden = metadata[ZodDecorator.IS_HIDDEN];
2243
+ if (hidden !== undefined) {
2244
+ decorators.push({
2245
+ ref: ZodDecorator.IS_HIDDEN,
2246
+ params: hidden === true ? undefined : hidden
2247
+ });
2248
+ }
2249
+ // Not editable decorator (NotEditableFieldParams | true)
2250
+ const notEditable = metadata[ZodDecorator.IS_NOT_EDITABLE];
2251
+ if (notEditable !== undefined) {
2252
+ decorators.push({
2253
+ ref: ZodDecorator.IS_NOT_EDITABLE,
2254
+ params: notEditable === true ? undefined : notEditable
2255
+ });
2256
+ }
2257
+ // Array UI decorator (ArrayUIParams) — also carries absorbed isSortable config
2258
+ const arrayUI = metadata[ZodDecorator.ARRAY_UI];
2259
+ if (arrayUI !== undefined) {
2260
+ decorators.push({
2261
+ ref: ZodDecorator.ARRAY_UI,
2262
+ params: arrayUI
2263
+ });
2264
+ }
2265
+ // Object UI decorator (ObjectUIParams)
2266
+ const objectUI = metadata[ZodDecorator.OBJECT_UI];
2267
+ if (objectUI !== undefined) {
2268
+ decorators.push({
2269
+ ref: ZodDecorator.OBJECT_UI,
2270
+ params: objectUI
2271
+ });
2272
+ }
2273
+ return (decorators.length > 0 ? addDecorators(schema, decorators) : schema);
2274
+ };
2275
+ const build = (d) => {
2276
+ switch (d.type) {
2277
+ case 'string': {
2278
+ let s = z.string();
2279
+ // Note: description is used for API docs; set if present
2280
+ if (d.description)
2281
+ s = s.describe(d.description);
2282
+ s = applyMetadata(s, d.metadata);
2283
+ return applyOptionalNullable(s, d);
2284
+ }
2285
+ case 'number': {
2286
+ const s = applyMetadata(z.number(), d.metadata);
2287
+ return applyOptionalNullable(s, d);
2288
+ }
2289
+ case 'bigint': {
2290
+ const s = applyMetadata(z.bigint(), d.metadata);
2291
+ return applyOptionalNullable(s, d);
2292
+ }
2293
+ case 'boolean': {
2294
+ const s = applyMetadata(z.boolean(), d.metadata);
2295
+ return applyOptionalNullable(s, d);
2296
+ }
2297
+ case 'date': {
2298
+ const s = applyMetadata(z.date(), d.metadata);
2299
+ return applyOptionalNullable(s, d);
2300
+ }
2301
+ case 'any': {
2302
+ const s = applyMetadata(z.any(), d.metadata);
2303
+ return applyOptionalNullable(s, d);
2304
+ }
2305
+ case 'unknown': {
2306
+ const s = applyMetadata(z.unknown(), d.metadata);
2307
+ return applyOptionalNullable(s, d);
2308
+ }
2309
+ case 'literal': {
2310
+ const s = applyMetadata(z.literal(d.value), d.metadata);
2311
+ return applyOptionalNullable(s, d);
2312
+ }
2313
+ case 'enum': {
2314
+ const values = d.values;
2315
+ // If values are missing/empty, fall back to string (cannot build z.enum([]))
2316
+ const base = values && values.length > 0
2317
+ ? z.enum(values)
2318
+ : z.string();
2319
+ const s = applyMetadata(base, d.metadata);
2320
+ return applyOptionalNullable(s, d);
2321
+ }
2322
+ case 'array': {
2323
+ const el = build(d.element);
2324
+ const s = applyMetadata(z.array(el), d.metadata);
2325
+ return applyOptionalNullable(s, d);
2326
+ }
2327
+ case 'set': {
2328
+ const el = build(d.element);
2329
+ const s = applyMetadata(z.set(el), d.metadata);
2330
+ return applyOptionalNullable(s, d);
2331
+ }
2332
+ case 'tuple': {
2333
+ const items = d.items.map(build);
2334
+ const s = applyMetadata(items.length >= 1
2335
+ ? z.tuple(items)
2336
+ : z.tuple([z.any()]), d.metadata);
2337
+ return applyOptionalNullable(s, d);
2338
+ }
2339
+ case 'record': {
2340
+ const val = build(d.value);
2341
+ // Zod records only support string keys; fall back to string keys for non-string keys
2342
+ const keyDesc = d.key;
2343
+ const keySchema = keyDesc && keyDesc.type === 'string' ? z.string() : z.string();
2344
+ const s = applyMetadata(z.record(keySchema, val), d.metadata);
2345
+ return applyOptionalNullable(s, d);
2346
+ }
2347
+ case 'union': {
2348
+ const options = d.options.map(build);
2349
+ const s = applyMetadata(options.length >= 2 ? z.union(options) : (options[0] || z.any()), d.metadata);
2350
+ return applyOptionalNullable(s, d);
2351
+ }
2352
+ case 'object': {
2353
+ const metadata = d.metadata;
2354
+ const rangeConfig = metadata?.rangeConfig;
2355
+ // Check if this is a range schema (has rangeConfig metadata)
2356
+ if (rangeConfig && rangeConfig.type) {
2357
+ const rangeSchema = z_range(rangeConfig.type, {
2358
+ fromOptional: rangeConfig.fromOptional,
2359
+ toOptional: rangeConfig.toOptional,
2360
+ min: rangeConfig.min,
2361
+ max: rangeConfig.max,
2362
+ });
2363
+ // Apply other decorators (excluding range-specific ones that are already applied)
2364
+ const metadataWithoutRange = { ...metadata };
2365
+ delete metadataWithoutRange.rangeType;
2366
+ delete metadataWithoutRange.rangeConfig;
2367
+ const s = applyMetadata(rangeSchema, metadataWithoutRange);
2368
+ return applyOptionalNullable(s, d);
2369
+ }
2370
+ // Regular object schema
2371
+ const props = d.properties;
2372
+ const shape = {};
2373
+ for (const [k, v] of Object.entries(props || {})) {
2374
+ shape[k] = build(v) || z.unknown();
2375
+ }
2376
+ const s = applyMetadata(z.object(shape), metadata);
2377
+ return applyOptionalNullable(s, d);
2378
+ }
2379
+ case 'lazy': {
2380
+ // Cannot reconstruct true lazy structure; fallback
2381
+ const s = applyMetadata(z.any(), d.metadata);
2382
+ return applyOptionalNullable(s, d);
2383
+ }
2384
+ default: {
2385
+ const s = applyMetadata(z.unknown(), d.metadata);
2386
+ return applyOptionalNullable(s, d);
2387
+ }
2388
+ }
2389
+ };
2390
+ return build(desc);
2391
+ }
2392
+ // =============================================================================
2393
+ // VIRTUAL FIELD UTILITIES
2394
+ // =============================================================================
2395
+ /**
2396
+ * Check if a schema is marked as a virtual field.
2397
+ * Virtual fields are computed at runtime and not stored in the database.
2398
+ */
2399
+ export function isVirtualField(schema) {
2400
+ return hasDecorator(schema, ZodDecorator.IS_VIRTUAL);
2401
+ }
2402
+ /**
2403
+ * Get the virtual field parameters from a schema.
2404
+ * Returns undefined if the field is not virtual.
2405
+ */
2406
+ export function getVirtualFieldParams(schema) {
2407
+ const metadata = getCustomFieldMetadata(schema);
2408
+ const virtualMetadata = metadata[ZodDecorator.IS_VIRTUAL];
2409
+ if (!virtualMetadata)
2410
+ return undefined;
2411
+ return virtualMetadata;
2412
+ }
2413
+ /**
2414
+ * Get the dependencies declared for a virtual field.
2415
+ * Returns an empty array if no dependencies are declared or the field is not virtual.
2416
+ */
2417
+ export function getVirtualFieldDependencies(schema) {
2418
+ const params = getVirtualFieldParams(schema);
2419
+ return params?.dependencies ?? [];
2420
+ }
2421
+ /**
2422
+ * Check if a virtual field needs object context (parents/children) for computation.
2423
+ */
2424
+ export function virtualFieldNeedsObjectContext(schema) {
2425
+ const params = getVirtualFieldParams(schema);
2426
+ return params?.needsObjectContext ?? false;
2427
+ }
2428
+ /**
2429
+ * Check if a virtual field needs user context for computation.
2430
+ */
2431
+ export function virtualFieldNeedsUserContext(schema) {
2432
+ const params = getVirtualFieldParams(schema);
2433
+ return params?.needsUserContext ?? false;
2434
+ }
2435
+ /**
2436
+ * Check if a field should be excluded from form validation based on its decorators.
2437
+ */
2438
+ export function shouldExcludeFieldFromForm(fieldSchema, operationType) {
2439
+ // Exclude virtual fields (computed at runtime)
2440
+ if (hasDecorator(fieldSchema, ZodDecorator.IS_VIRTUAL)) {
2441
+ return true;
2442
+ }
2443
+ // Exclude backend-only fields (never sent to frontend)
2444
+ if (hasDecorator(fieldSchema, ZodDecorator.IS_BACKEND_ONLY)) {
2445
+ return true;
2446
+ }
2447
+ // Exclude hidden fields (but respect edit: false param which means visible in edit mode)
2448
+ const hiddenMetadata = getHiddenFieldMetadata(fieldSchema);
2449
+ if (hiddenMetadata !== undefined) {
2450
+ // If hiddenMetadata is true (no params), field is hidden everywhere
2451
+ if (hiddenMetadata === true) {
2452
+ return true;
2453
+ }
2454
+ // If hiddenMetadata has params, check the edit flag
2455
+ // edit: false means "NOT hidden in edit mode" (visible in forms)
2456
+ // edit: true or undefined means "hidden in edit mode"
2457
+ if (hiddenMetadata.edit !== false) {
2458
+ return true;
2459
+ }
2460
+ // edit: false - field is visible in forms, don't exclude
2461
+ }
2462
+ // Exclude excludeFromCreate fields for CREATE operations
2463
+ if (operationType === 'CREATE' && hasDecorator(fieldSchema, ZodDecorator.EXCLUDE_FROM_CREATE)) {
2464
+ return true;
2465
+ }
2466
+ // Exclude excludeFromUpdate fields for UPDATE operations
2467
+ if (operationType === 'UPDATE' && hasDecorator(fieldSchema, ZodDecorator.EXCLUDE_FROM_UPDATE)) {
2468
+ return true;
2469
+ }
2470
+ return false;
2471
+ }
2472
+ /**
2473
+ * Check if a field has isDraftRelaxed decorator.
2474
+ */
2475
+ export function isDraftRelaxedField(fieldSchema) {
2476
+ return hasDecorator(fieldSchema, ZodDecorator.IS_DRAFT_RELAXED);
2477
+ }
2478
+ /**
2479
+ * Prepare a schema for form validation by filtering out fields that shouldn't be validated
2480
+ * and adjusting optionality based on draft mode.
2481
+ *
2482
+ * This function:
2483
+ * - Removes virtual fields
2484
+ * - Removes backend-only fields
2485
+ * - Removes hidden fields
2486
+ * - Removes excludeFromCreate fields (for CREATE operations)
2487
+ * - Removes excludeFromUpdate fields (for UPDATE operations)
2488
+ * - Makes isDraftRelaxed fields optional when in draft mode
2489
+ *
2490
+ * **WARNING: Schema-level refinements (.refine(), .superRefine()) are NOT carried over.**
2491
+ * The returned schema is a new z.object() with the filtered shape — any cross-field
2492
+ * validation defined via refinements on the original schema will be silently lost.
2493
+ * This is intentional for draft mode (draft = relaxed validation), but callers using
2494
+ * this outside of draft mode should be aware that cross-field rules won't apply.
2495
+ * For non-draft form validation, use the original schema (e.g., operation.requestDto) directly.
2496
+ *
2497
+ * @param schema - The original object schema
2498
+ * @param options - Preparation options
2499
+ * @returns A new schema suitable for form validation (without schema-level refinements)
2500
+ */
2501
+ export function prepareFormSchema(schema, options) {
2502
+ const { operationType, isDraftMode = false, operationLike } = options;
2503
+ const effectiveOperationType = operationLike ?? operationType;
2504
+ // Get the shape of the object schema
2505
+ const shape = schema.shape;
2506
+ const newShape = {};
2507
+ // Process each field
2508
+ for (const [fieldName, fieldSchema] of Object.entries(shape)) {
2509
+ // Skip fields that should be excluded
2510
+ if (shouldExcludeFieldFromForm(fieldSchema, effectiveOperationType)) {
2511
+ continue;
2512
+ }
2513
+ // If in draft mode and field is draft-relaxed, make it optional
2514
+ if (isDraftMode && isDraftRelaxedField(fieldSchema)) {
2515
+ // Wrap in optional if not already optional
2516
+ if (!isOptional(fieldSchema)) {
2517
+ newShape[fieldName] = fieldSchema.optional();
2518
+ }
2519
+ else {
2520
+ newShape[fieldName] = fieldSchema;
2521
+ }
2522
+ }
2523
+ else {
2524
+ newShape[fieldName] = fieldSchema;
2525
+ }
2526
+ }
2527
+ // Create new object schema with filtered shape
2528
+ const newSchema = z.object(newShape);
2529
+ const schemaAsAny = schema;
2530
+ const originalMetadata = schemaAsAny[SCHEMA_METADATA];
2531
+ if (originalMetadata && typeof originalMetadata === 'object') {
2532
+ const newSchemaAsAny = newSchema;
2533
+ newSchemaAsAny[SCHEMA_METADATA] = { ...originalMetadata };
2534
+ }
2535
+ // Note: We don't copy schema-level refinements in draft mode to avoid cross-field validation
2536
+ // For non-draft mode, refinements should ideally be copied but that requires deeper schema introspection
2537
+ return newSchema;
2538
+ }
2539
+ /**
2540
+ * Check if a schema field is marked as ephemeral (transient response-only data).
2541
+ * Ephemeral fields are shown once (e.g., API keys upon creation) and not persisted in the UI.
2542
+ */
2543
+ export function isEphemeralField(schema) {
2544
+ return hasDecorator(schema, ZodDecorator.IS_EPHEMERAL);
2545
+ }
2546
+ // =============================================================================
2547
+ // Unified {primitive}UI Reader Functions
2548
+ // =============================================================================
2549
+ // Return the full nested structure: { display?, summaryOverride?, edit? }.
2550
+ // Consumers access context-specific params via e.g. getStringUI(s)?.edit?.editMode.
2551
+ export function getBooleanUI(schema) {
2552
+ return getCustomFieldMetadata(schema)[ZodDecorator.BOOLEAN_UI];
2553
+ }
2554
+ export function hasBooleanUI(schema) {
2555
+ return hasDecorator(schema, ZodDecorator.BOOLEAN_UI);
2556
+ }
2557
+ export function getStringUI(schema) {
2558
+ return getCustomFieldMetadata(schema)[ZodDecorator.STRING_UI];
2559
+ }
2560
+ export function hasStringUI(schema) {
2561
+ return hasDecorator(schema, ZodDecorator.STRING_UI);
2562
+ }
2563
+ export function getNumberUI(schema) {
2564
+ return getCustomFieldMetadata(schema)[ZodDecorator.NUMBER_UI];
2565
+ }
2566
+ export function hasNumberUI(schema) {
2567
+ return hasDecorator(schema, ZodDecorator.NUMBER_UI);
2568
+ }
2569
+ export function getEnumUI(schema) {
2570
+ return getCustomFieldMetadata(schema)[ZodDecorator.ENUM_UI];
2571
+ }
2572
+ export function hasEnumUI(schema) {
2573
+ return hasDecorator(schema, ZodDecorator.ENUM_UI);
2574
+ }
2575
+ export function getDateUI(schema) {
2576
+ return getCustomFieldMetadata(schema)[ZodDecorator.DATE_UI];
2577
+ }
2578
+ export function hasDateUI(schema) {
2579
+ return hasDecorator(schema, ZodDecorator.DATE_UI);
2580
+ }
2581
+ export function getDateTimeUI(schema) {
2582
+ return getCustomFieldMetadata(schema)[ZodDecorator.DATETIME_UI];
2583
+ }
2584
+ export function hasDateTimeUI(schema) {
2585
+ return hasDecorator(schema, ZodDecorator.DATETIME_UI);
2586
+ }
2587
+ export function getTimeUI(schema) {
2588
+ return getCustomFieldMetadata(schema)[ZodDecorator.TIME_UI];
2589
+ }
2590
+ export function hasTimeUI(schema) {
2591
+ return hasDecorator(schema, ZodDecorator.TIME_UI);
2592
+ }
2593
+ export function getMoneyUI(schema) {
2594
+ return getCustomFieldMetadata(schema)[ZodDecorator.MONEY_UI];
2595
+ }
2596
+ export function hasMoneyUI(schema) {
2597
+ return hasDecorator(schema, ZodDecorator.MONEY_UI);
2598
+ }
2599
+ export function getCodeUI(schema) {
2600
+ return getCustomFieldMetadata(schema)[ZodDecorator.CODE_UI];
2601
+ }
2602
+ export function hasCodeUI(schema) {
2603
+ return hasDecorator(schema, ZodDecorator.CODE_UI);
2604
+ }
2605
+ export function getFileUI(schema) {
2606
+ return getCustomFieldMetadata(schema)[ZodDecorator.FILE_UI];
2607
+ }
2608
+ export function hasFileUI(schema) {
2609
+ return hasDecorator(schema, ZodDecorator.FILE_UI);
2610
+ }
2611
+ export function getDurationUI(schema) {
2612
+ return getCustomFieldMetadata(schema)[ZodDecorator.DURATION_UI];
2613
+ }
2614
+ export function hasDurationUI(schema) {
2615
+ return hasDecorator(schema, ZodDecorator.DURATION_UI);
2616
+ }
2617
+ export function getPhoneUI(schema) {
2618
+ return getCustomFieldMetadata(schema)[ZodDecorator.PHONE_UI];
2619
+ }
2620
+ export function hasPhoneUI(schema) {
2621
+ return hasDecorator(schema, ZodDecorator.PHONE_UI);
2622
+ }
2623
+ export function getBigDecimalUI(schema) {
2624
+ return getCustomFieldMetadata(schema)[ZodDecorator.BIGDECIMAL_UI];
2625
+ }
2626
+ export function hasBigDecimalUI(schema) {
2627
+ return hasDecorator(schema, ZodDecorator.BIGDECIMAL_UI);
2628
+ }
2629
+ export function getForeignKeyUI(schema) {
2630
+ return getCustomFieldMetadata(schema)[ZodDecorator.FOREIGN_KEY_UI];
2631
+ }
2632
+ export function hasForeignKeyUI(schema) {
2633
+ return hasDecorator(schema, ZodDecorator.FOREIGN_KEY_UI);
2634
+ }
2635
+ export function getArrayUI(schema) {
2636
+ return getCustomFieldMetadata(schema)[ZodDecorator.ARRAY_UI];
2637
+ }
2638
+ export function hasArrayUI(schema) {
2639
+ return hasDecorator(schema, ZodDecorator.ARRAY_UI);
2640
+ }
2641
+ export function getObjectUI(schema) {
2642
+ return getCustomFieldMetadata(schema)[ZodDecorator.OBJECT_UI];
2643
+ }
2644
+ export function hasObjectUI(schema) {
2645
+ return hasDecorator(schema, ZodDecorator.OBJECT_UI);
2646
+ }
2647
+ //# sourceMappingURL=zod-utils.shared.js.map