@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.
- package/dist/esm/__mocks__/mime.d.ts +12 -0
- package/dist/esm/__mocks__/mime.d.ts.map +1 -0
- package/dist/esm/__mocks__/mime.js +75 -0
- package/dist/esm/__mocks__/mime.js.map +1 -0
- package/dist/esm/commons.d.ts +26 -0
- package/dist/esm/commons.d.ts.map +1 -0
- package/dist/esm/commons.js +41 -0
- package/dist/esm/commons.js.map +1 -0
- package/dist/esm/currencies.shared.schemas.d.ts +208 -0
- package/dist/esm/currencies.shared.schemas.d.ts.map +1 -0
- package/dist/esm/currencies.shared.schemas.js +228 -0
- package/dist/esm/currencies.shared.schemas.js.map +1 -0
- package/dist/esm/files/agent-file-formats.d.ts +99 -0
- package/dist/esm/files/agent-file-formats.d.ts.map +1 -0
- package/dist/esm/files/agent-file-formats.js +163 -0
- package/dist/esm/files/agent-file-formats.js.map +1 -0
- package/dist/esm/files/extension-nature-map.d.ts +12 -0
- package/dist/esm/files/extension-nature-map.d.ts.map +1 -0
- package/dist/esm/files/extension-nature-map.js +63 -0
- package/dist/esm/files/extension-nature-map.js.map +1 -0
- package/dist/esm/files/file-extensions.enum.d.ts +58 -0
- package/dist/esm/files/file-extensions.enum.d.ts.map +1 -0
- package/dist/esm/files/file-extensions.enum.js +65 -0
- package/dist/esm/files/file-extensions.enum.js.map +1 -0
- package/dist/esm/files/mime-registry.d.ts +72 -0
- package/dist/esm/files/mime-registry.d.ts.map +1 -0
- package/dist/esm/files/mime-registry.js +174 -0
- package/dist/esm/files/mime-registry.js.map +1 -0
- package/dist/esm/files/unified-registry.d.ts +112 -0
- package/dist/esm/files/unified-registry.d.ts.map +1 -0
- package/dist/esm/files/unified-registry.js +190 -0
- package/dist/esm/files/unified-registry.js.map +1 -0
- package/dist/esm/index.d.ts +29 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +36 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/testing/decorated-test-schemas.d.ts +27 -0
- package/dist/esm/testing/decorated-test-schemas.d.ts.map +1 -0
- package/dist/esm/testing/decorated-test-schemas.js +26 -0
- package/dist/esm/testing/decorated-test-schemas.js.map +1 -0
- package/dist/esm/testing.d.ts +6 -0
- package/dist/esm/testing.d.ts.map +1 -0
- package/dist/esm/testing.js +6 -0
- package/dist/esm/testing.js.map +1 -0
- package/dist/esm/zod-bigdecimal.d.ts +193 -0
- package/dist/esm/zod-bigdecimal.d.ts.map +1 -0
- package/dist/esm/zod-bigdecimal.js +274 -0
- package/dist/esm/zod-bigdecimal.js.map +1 -0
- package/dist/esm/zod-code.d.ts +95 -0
- package/dist/esm/zod-code.d.ts.map +1 -0
- package/dist/esm/zod-code.js +160 -0
- package/dist/esm/zod-code.js.map +1 -0
- package/dist/esm/zod-color.d.ts +8 -0
- package/dist/esm/zod-color.d.ts.map +1 -0
- package/dist/esm/zod-color.js +88 -0
- package/dist/esm/zod-color.js.map +1 -0
- package/dist/esm/zod-decorators.types.d.ts +2207 -0
- package/dist/esm/zod-decorators.types.d.ts.map +1 -0
- package/dist/esm/zod-decorators.types.js +771 -0
- package/dist/esm/zod-decorators.types.js.map +1 -0
- package/dist/esm/zod-duration.d.ts +128 -0
- package/dist/esm/zod-duration.d.ts.map +1 -0
- package/dist/esm/zod-duration.js +174 -0
- package/dist/esm/zod-duration.js.map +1 -0
- package/dist/esm/zod-file.d.ts +219 -0
- package/dist/esm/zod-file.d.ts.map +1 -0
- package/dist/esm/zod-file.js +469 -0
- package/dist/esm/zod-file.js.map +1 -0
- package/dist/esm/zod-money.d.ts +223 -0
- package/dist/esm/zod-money.d.ts.map +1 -0
- package/dist/esm/zod-money.js +293 -0
- package/dist/esm/zod-money.js.map +1 -0
- package/dist/esm/zod-phone.d.ts +75 -0
- package/dist/esm/zod-phone.d.ts.map +1 -0
- package/dist/esm/zod-phone.js +116 -0
- package/dist/esm/zod-phone.js.map +1 -0
- package/dist/esm/zod-range.d.ts +87 -0
- package/dist/esm/zod-range.d.ts.map +1 -0
- package/dist/esm/zod-range.js +138 -0
- package/dist/esm/zod-range.js.map +1 -0
- package/dist/esm/zod-utils.shared.d.ts +783 -0
- package/dist/esm/zod-utils.shared.d.ts.map +1 -0
- package/dist/esm/zod-utils.shared.js +2647 -0
- package/dist/esm/zod-utils.shared.js.map +1 -0
- package/dist/esm/zod-versioned.d.ts +150 -0
- package/dist/esm/zod-versioned.d.ts.map +1 -0
- package/dist/esm/zod-versioned.js +172 -0
- package/dist/esm/zod-versioned.js.map +1 -0
- package/dist/esm/zod.module-override.d.ts +413 -0
- package/dist/esm/zod.module-override.d.ts.map +1 -0
- package/dist/esm/zod.module-override.js +1093 -0
- package/dist/esm/zod.module-override.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- 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
|