ormbridge 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -22
- package/dist/cliRunner.js +20 -2
- package/dist/cliRunner.js.map +1 -1
- package/dist/config.d.ts +6 -1
- package/dist/config.js +17 -0
- package/dist/config.js.map +1 -1
- package/dist/emitters/jpa.d.ts +17 -2
- package/dist/emitters/jpa.js +55 -7
- package/dist/emitters/jpa.js.map +1 -1
- package/dist/emitters/jpaKeyClass.d.ts +18 -0
- package/dist/emitters/jpaKeyClass.js +88 -0
- package/dist/emitters/jpaKeyClass.js.map +1 -0
- package/dist/emitters/jpaKeys.d.ts +1 -7
- package/dist/emitters/jpaKeys.js +0 -43
- package/dist/emitters/jpaKeys.js.map +1 -1
- package/dist/emitters/jpaRelations.d.ts +5 -1
- package/dist/emitters/jpaRelations.js +7 -1
- package/dist/emitters/jpaRelations.js.map +1 -1
- package/dist/emitters/jpaRender.d.ts +5 -1
- package/dist/emitters/jpaRender.js +11 -4
- package/dist/emitters/jpaRender.js.map +1 -1
- package/dist/emitters/jpaTypes.d.ts +9 -3
- package/dist/emitters/jpaTypes.js +8 -0
- package/dist/emitters/jpaTypes.js.map +1 -1
- package/dist/emitters/sequelize.d.ts +16 -3
- package/dist/emitters/sequelize.js +270 -45
- package/dist/emitters/sequelize.js.map +1 -1
- package/dist/emitters/sequelize7.d.ts +68 -0
- package/dist/emitters/sequelize7.js +199 -0
- package/dist/emitters/sequelize7.js.map +1 -0
- package/dist/emitters/sequelizeText.d.ts +12 -0
- package/dist/emitters/sequelizeText.js +37 -0
- package/dist/emitters/sequelizeText.js.map +1 -0
- package/dist/formats.d.ts +13 -2
- package/dist/formats.js +17 -6
- package/dist/formats.js.map +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/io.js +105 -26
- package/dist/io.js.map +1 -1
- package/dist/parsers/efcore.js +60 -3
- package/dist/parsers/efcore.js.map +1 -1
- package/dist/parsers/efcoreAnnotations.js +1 -1
- package/dist/parsers/efcoreAnnotations.js.map +1 -1
- package/dist/parsers/efcoreBuild.d.ts +1 -0
- package/dist/parsers/efcoreBuild.js +15 -4
- package/dist/parsers/efcoreBuild.js.map +1 -1
- package/dist/parsers/efcoreEntities.js +90 -22
- package/dist/parsers/efcoreEntities.js.map +1 -1
- package/dist/parsers/efcoreMigrationHistory.d.ts +17 -0
- package/dist/parsers/efcoreMigrationHistory.js +105 -0
- package/dist/parsers/efcoreMigrationHistory.js.map +1 -0
- package/dist/parsers/efcoreMigrationReplay.d.ts +25 -0
- package/dist/parsers/efcoreMigrationReplay.js +790 -0
- package/dist/parsers/efcoreMigrationReplay.js.map +1 -0
- package/dist/parsers/efcoreMigrationSchema.d.ts +14 -0
- package/dist/parsers/efcoreMigrationSchema.js +550 -0
- package/dist/parsers/efcoreMigrationSchema.js.map +1 -0
- package/dist/parsers/efcoreMigrationState.d.ts +82 -0
- package/dist/parsers/efcoreMigrationState.js +104 -0
- package/dist/parsers/efcoreMigrationState.js.map +1 -0
- package/dist/parsers/efcoreModel.d.ts +18 -2
- package/dist/parsers/efcoreOwned.d.ts +12 -0
- package/dist/parsers/efcoreOwned.js +139 -0
- package/dist/parsers/efcoreOwned.js.map +1 -0
- package/dist/parsers/efcoreSnapshot.d.ts +85 -0
- package/dist/parsers/efcoreSnapshot.js +791 -0
- package/dist/parsers/efcoreSnapshot.js.map +1 -0
- package/dist/parsers/efcoreSteps.js +28 -3
- package/dist/parsers/efcoreSteps.js.map +1 -1
- package/dist/parsers/jpaBuild.js +2 -1
- package/dist/parsers/jpaBuild.js.map +1 -1
- package/dist/parsers/jpaClasses.js +7 -1
- package/dist/parsers/jpaClasses.js.map +1 -1
- package/dist/parsers/jpaColumns.d.ts +4 -1
- package/dist/parsers/jpaColumns.js +8 -2
- package/dist/parsers/jpaColumns.js.map +1 -1
- package/dist/parsers/jpaEntities.js +15 -0
- package/dist/parsers/jpaEntities.js.map +1 -1
- package/dist/parsers/jpaModel.d.ts +6 -0
- package/dist/parsers/sequelize.js +37 -10
- package/dist/parsers/sequelize.js.map +1 -1
- package/dist/parsers/sequelizeAssociations.js +13 -2
- package/dist/parsers/sequelizeAssociations.js.map +1 -1
- package/dist/parsers/sequelizeCollect.d.ts +7 -2
- package/dist/parsers/sequelizeCollect.js +120 -9
- package/dist/parsers/sequelizeCollect.js.map +1 -1
- package/dist/parsers/sequelizeMigrationState.d.ts +75 -0
- package/dist/parsers/sequelizeMigrationState.js +330 -0
- package/dist/parsers/sequelizeMigrationState.js.map +1 -0
- package/dist/parsers/sequelizeMigrations.d.ts +14 -0
- package/dist/parsers/sequelizeMigrations.js +1123 -0
- package/dist/parsers/sequelizeMigrations.js.map +1 -0
- package/dist/parsers/sequelizeModel.d.ts +19 -2
- package/dist/parsers/sqlDdl.js +1306 -65
- package/dist/parsers/sqlDdl.js.map +1 -1
- package/dist/parsers/sqlMigrationOrder.d.ts +8 -0
- package/dist/parsers/sqlMigrationOrder.js +110 -0
- package/dist/parsers/sqlMigrationOrder.js.map +1 -0
- package/dist/parsers/sqlSyntax.d.ts +6 -0
- package/dist/parsers/sqlSyntax.js +15 -2
- package/dist/parsers/sqlSyntax.js.map +1 -1
- package/dist/parsers/zod.d.ts +11 -0
- package/dist/parsers/zod.js +796 -0
- package/dist/parsers/zod.js.map +1 -0
- package/dist/parsers/zodFields.d.ts +73 -0
- package/dist/parsers/zodFields.js +681 -0
- package/dist/parsers/zodFields.js.map +1 -0
- package/dist/parsers/zodSyntax.d.ts +72 -0
- package/dist/parsers/zodSyntax.js +347 -0
- package/dist/parsers/zodSyntax.js.map +1 -0
- package/dist/styles.d.ts +3 -3
- package/dist/styles.js +4 -4
- package/dist/styles.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
import { functionResult, isFunctionNode, literalOf, objectMembers, stripWrappers, unwindChain, } from './zodSyntax.js';
|
|
2
|
+
const MAX_DEPTH = 24;
|
|
3
|
+
const STRING_FORMAT_BASES = {
|
|
4
|
+
uuid: 'uuid',
|
|
5
|
+
guid: 'plain',
|
|
6
|
+
uuidv4: 'uuid',
|
|
7
|
+
uuidv6: 'uuid',
|
|
8
|
+
uuidv7: 'uuid',
|
|
9
|
+
email: 'plain',
|
|
10
|
+
url: 'plain',
|
|
11
|
+
httpUrl: 'plain',
|
|
12
|
+
emoji: 'plain',
|
|
13
|
+
nanoid: 'plain',
|
|
14
|
+
cuid: 'plain',
|
|
15
|
+
cuid2: 'plain',
|
|
16
|
+
ulid: 'plain',
|
|
17
|
+
xid: 'plain',
|
|
18
|
+
ksuid: 'plain',
|
|
19
|
+
jwt: 'plain',
|
|
20
|
+
hostname: 'plain',
|
|
21
|
+
e164: 'plain',
|
|
22
|
+
cidrv4: 'plain',
|
|
23
|
+
cidrv6: 'plain',
|
|
24
|
+
ipv4: 'ipAddress',
|
|
25
|
+
ipv6: 'ipAddress',
|
|
26
|
+
base64: 'bytes',
|
|
27
|
+
base64url: 'plain',
|
|
28
|
+
hex: 'plain',
|
|
29
|
+
'iso.datetime': 'dateTime',
|
|
30
|
+
'iso.date': 'date',
|
|
31
|
+
'iso.time': 'time',
|
|
32
|
+
'iso.duration': 'duration',
|
|
33
|
+
};
|
|
34
|
+
/** Format methods of a Zod 3 / Zod 4 string (`z.string().uuid()`). */
|
|
35
|
+
const STRING_FORMAT_STEPS = {
|
|
36
|
+
uuid: 'uuid',
|
|
37
|
+
guid: 'plain',
|
|
38
|
+
email: 'plain',
|
|
39
|
+
url: 'plain',
|
|
40
|
+
emoji: 'plain',
|
|
41
|
+
nanoid: 'plain',
|
|
42
|
+
cuid: 'plain',
|
|
43
|
+
cuid2: 'plain',
|
|
44
|
+
ulid: 'plain',
|
|
45
|
+
xid: 'plain',
|
|
46
|
+
ksuid: 'plain',
|
|
47
|
+
jwt: 'plain',
|
|
48
|
+
e164: 'plain',
|
|
49
|
+
cidr: 'plain',
|
|
50
|
+
ip: 'ipAddress',
|
|
51
|
+
ipv4: 'ipAddress',
|
|
52
|
+
ipv6: 'ipAddress',
|
|
53
|
+
base64: 'bytes',
|
|
54
|
+
base64url: 'plain',
|
|
55
|
+
datetime: 'dateTime',
|
|
56
|
+
date: 'date',
|
|
57
|
+
time: 'time',
|
|
58
|
+
duration: 'duration',
|
|
59
|
+
};
|
|
60
|
+
const JSON_BASES = new Set([
|
|
61
|
+
'any',
|
|
62
|
+
'unknown',
|
|
63
|
+
'json',
|
|
64
|
+
'record',
|
|
65
|
+
'partialRecord',
|
|
66
|
+
'looseRecord',
|
|
67
|
+
'map',
|
|
68
|
+
]);
|
|
69
|
+
/** Constructors that describe no stored value: they become json, with a warning. */
|
|
70
|
+
const NO_COLUMN_BASES = new Set([
|
|
71
|
+
'custom',
|
|
72
|
+
'instanceof',
|
|
73
|
+
'function',
|
|
74
|
+
'promise',
|
|
75
|
+
'symbol',
|
|
76
|
+
'void',
|
|
77
|
+
'never',
|
|
78
|
+
'nan',
|
|
79
|
+
'codec',
|
|
80
|
+
]);
|
|
81
|
+
export const OBJECT_BASES = new Set([
|
|
82
|
+
'object',
|
|
83
|
+
'strictObject',
|
|
84
|
+
'looseObject',
|
|
85
|
+
'interface',
|
|
86
|
+
'strictInterface',
|
|
87
|
+
]);
|
|
88
|
+
/** Wrappers written as functions (`z.optional(inner)`): the schema is the first argument. */
|
|
89
|
+
const WRAPPER_BASES = new Set([
|
|
90
|
+
'optional',
|
|
91
|
+
'nullable',
|
|
92
|
+
'nullish',
|
|
93
|
+
'readonly',
|
|
94
|
+
'lazy',
|
|
95
|
+
'nonoptional',
|
|
96
|
+
'brand',
|
|
97
|
+
'catch',
|
|
98
|
+
'default',
|
|
99
|
+
'prefault',
|
|
100
|
+
]);
|
|
101
|
+
/** Modifiers that change nothing the shared model stores and are not worth mentioning. */
|
|
102
|
+
const SILENT_STEPS = new Set(['describe', 'meta']);
|
|
103
|
+
const DECIMAL_UNSIZED = '/^-?\\d+(\\.\\d+)?$/';
|
|
104
|
+
const BIGINT_PATTERN = '/^-?\\d+$/';
|
|
105
|
+
const DECIMAL_SIZED = /^\/\^-\?\\d\{1,(\d+)\}(?:\(\\\.\\d\{1,(\d+)\}\)\?)?\$\/$/;
|
|
106
|
+
function emptyField(type) {
|
|
107
|
+
return {
|
|
108
|
+
type,
|
|
109
|
+
arrayDepth: 0,
|
|
110
|
+
optional: false,
|
|
111
|
+
nullable: false,
|
|
112
|
+
hasDefault: false,
|
|
113
|
+
dropped: [],
|
|
114
|
+
problems: [],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function jsonField(problem) {
|
|
118
|
+
const field = emptyField('json');
|
|
119
|
+
if (problem !== undefined) {
|
|
120
|
+
field.problems.push(problem);
|
|
121
|
+
}
|
|
122
|
+
return field;
|
|
123
|
+
}
|
|
124
|
+
function stepText(step) {
|
|
125
|
+
const first = step.args[0];
|
|
126
|
+
const text = first === undefined || isFunctionNode(stripWrappers(first))
|
|
127
|
+
? ''
|
|
128
|
+
: first.text;
|
|
129
|
+
return `.${step.name}(${text.length > 24 || text.includes('\n') ? '' : text})`;
|
|
130
|
+
}
|
|
131
|
+
function isTextual(field) {
|
|
132
|
+
return ((field.type === 'string' || field.type === 'text') &&
|
|
133
|
+
field.enumName === undefined &&
|
|
134
|
+
field.arrayDepth === 0 &&
|
|
135
|
+
field.relation === undefined);
|
|
136
|
+
}
|
|
137
|
+
const TYPE_LABELS = {
|
|
138
|
+
text: 'string',
|
|
139
|
+
string: 'string',
|
|
140
|
+
uuid: 'string',
|
|
141
|
+
int: 'number',
|
|
142
|
+
float: 'number',
|
|
143
|
+
bigInt: 'bigint',
|
|
144
|
+
boolean: 'boolean',
|
|
145
|
+
date: 'Date',
|
|
146
|
+
dateTime: 'Date',
|
|
147
|
+
json: 'unknown',
|
|
148
|
+
};
|
|
149
|
+
/** A short name for a resolved type, used when a union of unlike types falls back to json. */
|
|
150
|
+
function describeField(field) {
|
|
151
|
+
const base = field.relation !== undefined
|
|
152
|
+
? field.relation.model
|
|
153
|
+
: (field.enumName ?? TYPE_LABELS[field.type] ?? field.type);
|
|
154
|
+
return `${base}${'[]'.repeat(field.arrayDepth)}`;
|
|
155
|
+
}
|
|
156
|
+
function sameShape(first, second) {
|
|
157
|
+
return (first.type === second.type &&
|
|
158
|
+
first.enumName === second.enumName &&
|
|
159
|
+
first.arrayDepth === second.arrayDepth &&
|
|
160
|
+
first.relation?.model === second.relation?.model &&
|
|
161
|
+
first.relation?.many === second.relation?.many);
|
|
162
|
+
}
|
|
163
|
+
function mergeNotes(target, source) {
|
|
164
|
+
target.dropped.push(...source.dropped);
|
|
165
|
+
target.problems.push(...source.problems);
|
|
166
|
+
}
|
|
167
|
+
/** Resolves the Zod expression at `node` (a property value). */
|
|
168
|
+
export function resolveFieldNode(scope, node) {
|
|
169
|
+
return resolveNode(scope, node, 0);
|
|
170
|
+
}
|
|
171
|
+
function resolveNode(scope, node, depth) {
|
|
172
|
+
const chain = unwindChain(scope.file, node);
|
|
173
|
+
if (chain === undefined) {
|
|
174
|
+
return jsonField(`the value \`${truncate(node.text)}\` is not a Zod schema this reader understands; it was kept as json.`);
|
|
175
|
+
}
|
|
176
|
+
return resolveChain(scope, chain, depth);
|
|
177
|
+
}
|
|
178
|
+
function truncate(text) {
|
|
179
|
+
const flat = text.replace(/\s+/g, ' ');
|
|
180
|
+
return flat.length > 60 ? `${flat.slice(0, 57)}...` : flat;
|
|
181
|
+
}
|
|
182
|
+
export function resolveChain(scope, chain, depth) {
|
|
183
|
+
if (depth > MAX_DEPTH) {
|
|
184
|
+
return jsonField('the schema is nested too deeply; it was kept as json.');
|
|
185
|
+
}
|
|
186
|
+
let field = chain.base === undefined
|
|
187
|
+
? resolveReference(scope, chain.ref ?? '', depth)
|
|
188
|
+
: resolveBase(scope, chain.base.name, chain.base.args, depth);
|
|
189
|
+
for (const step of chain.steps) {
|
|
190
|
+
field = applyStep(scope, field, step, depth);
|
|
191
|
+
}
|
|
192
|
+
return field;
|
|
193
|
+
}
|
|
194
|
+
function resolveReference(scope, name, depth) {
|
|
195
|
+
const { host } = scope;
|
|
196
|
+
switch (host.kindOf(name)) {
|
|
197
|
+
case 'model': {
|
|
198
|
+
const field = emptyField('json');
|
|
199
|
+
field.relation = { model: host.modelNameOf(name) ?? name, many: false };
|
|
200
|
+
return field;
|
|
201
|
+
}
|
|
202
|
+
case 'enum': {
|
|
203
|
+
const field = emptyField('string');
|
|
204
|
+
const enumName = host.enumNameOf(name);
|
|
205
|
+
if (enumName === undefined) {
|
|
206
|
+
return jsonField(`the enum "${name}" could not be read; kept as json.`);
|
|
207
|
+
}
|
|
208
|
+
field.enumName = enumName;
|
|
209
|
+
return field;
|
|
210
|
+
}
|
|
211
|
+
case 'alias': {
|
|
212
|
+
const alias = host.aliasOf(name);
|
|
213
|
+
if (alias === undefined) {
|
|
214
|
+
return jsonField(`"${name}" could not be read; it was kept as json.`);
|
|
215
|
+
}
|
|
216
|
+
return resolveNode({ ...scope, file: alias.file }, alias.node, depth + 1);
|
|
217
|
+
}
|
|
218
|
+
case 'values':
|
|
219
|
+
return jsonField(`"${name}" is a list of values, not a Zod schema; it was kept as json.`);
|
|
220
|
+
default:
|
|
221
|
+
return jsonField(`"${name}" is not declared in the input files, so its type is unknown; it was kept as json. Pass the file that declares it too.`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
// Constructors
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
function resolveBase(scope, name, args, depth) {
|
|
228
|
+
const first = args[0];
|
|
229
|
+
switch (name) {
|
|
230
|
+
case 'string':
|
|
231
|
+
case 'coerce.string':
|
|
232
|
+
case 'templateLiteral':
|
|
233
|
+
return emptyField('text');
|
|
234
|
+
case 'number':
|
|
235
|
+
case 'coerce.number':
|
|
236
|
+
case 'float32':
|
|
237
|
+
case 'float64':
|
|
238
|
+
return emptyField('float');
|
|
239
|
+
case 'int':
|
|
240
|
+
case 'int32':
|
|
241
|
+
case 'uint32':
|
|
242
|
+
return emptyField('int');
|
|
243
|
+
case 'bigint':
|
|
244
|
+
case 'coerce.bigint':
|
|
245
|
+
case 'int64':
|
|
246
|
+
case 'uint64':
|
|
247
|
+
return emptyField('bigInt');
|
|
248
|
+
case 'boolean':
|
|
249
|
+
case 'coerce.boolean':
|
|
250
|
+
case 'stringbool':
|
|
251
|
+
return emptyField('boolean');
|
|
252
|
+
case 'date':
|
|
253
|
+
case 'coerce.date':
|
|
254
|
+
return emptyField('dateTime');
|
|
255
|
+
case 'file':
|
|
256
|
+
return emptyField('bytes');
|
|
257
|
+
case 'null': {
|
|
258
|
+
const field = jsonField();
|
|
259
|
+
field.onlyNull = true;
|
|
260
|
+
field.nullable = true;
|
|
261
|
+
return field;
|
|
262
|
+
}
|
|
263
|
+
case 'undefined': {
|
|
264
|
+
const field = jsonField();
|
|
265
|
+
field.onlyUndefined = true;
|
|
266
|
+
field.optional = true;
|
|
267
|
+
return field;
|
|
268
|
+
}
|
|
269
|
+
case 'enum':
|
|
270
|
+
case 'nativeEnum':
|
|
271
|
+
return resolveEnum(scope, name, first);
|
|
272
|
+
case 'literal':
|
|
273
|
+
return resolveLiteral(scope, first);
|
|
274
|
+
case 'array':
|
|
275
|
+
case 'set':
|
|
276
|
+
return wrapArray(first === undefined
|
|
277
|
+
? jsonField(`z.${name}() has no element schema; kept as json.`)
|
|
278
|
+
: resolveNode(scope, first, depth + 1));
|
|
279
|
+
case 'union':
|
|
280
|
+
case 'xor':
|
|
281
|
+
return resolveUnion(scope, first, depth);
|
|
282
|
+
case 'preprocess':
|
|
283
|
+
return withDropped(args[1] === undefined
|
|
284
|
+
? jsonField('z.preprocess() has no schema; kept as json.')
|
|
285
|
+
: resolveNode(scope, args[1], depth + 1), '.preprocess()');
|
|
286
|
+
case 'pipe':
|
|
287
|
+
return withDropped(first === undefined
|
|
288
|
+
? jsonField('z.pipe() has no schema; kept as json.')
|
|
289
|
+
: resolveNode(scope, first, depth + 1), '.pipe()');
|
|
290
|
+
default:
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
if (name in STRING_FORMAT_BASES) {
|
|
294
|
+
return stringFormat(name, STRING_FORMAT_BASES[name] ?? 'plain', true);
|
|
295
|
+
}
|
|
296
|
+
if (WRAPPER_BASES.has(name)) {
|
|
297
|
+
return resolveWrapper(scope, name, args, depth);
|
|
298
|
+
}
|
|
299
|
+
if (JSON_BASES.has(name)) {
|
|
300
|
+
return emptyField('json');
|
|
301
|
+
}
|
|
302
|
+
if (OBJECT_BASES.has(name)) {
|
|
303
|
+
return jsonField('a nested z.object() is kept as one json column (no model is made for an unnamed object).');
|
|
304
|
+
}
|
|
305
|
+
if (name === 'tuple') {
|
|
306
|
+
return jsonField('a z.tuple() is kept as one json column.');
|
|
307
|
+
}
|
|
308
|
+
if (name === 'intersection') {
|
|
309
|
+
return jsonField('a z.intersection() is kept as one json column.');
|
|
310
|
+
}
|
|
311
|
+
if (name === 'discriminatedUnion') {
|
|
312
|
+
return jsonField('a z.discriminatedUnion() is kept as one json column.');
|
|
313
|
+
}
|
|
314
|
+
if (NO_COLUMN_BASES.has(name)) {
|
|
315
|
+
return jsonField(`z.${name}() has no column type; it was kept as json.`);
|
|
316
|
+
}
|
|
317
|
+
return jsonField(`z.${name}() is not a Zod constructor this reader knows; it was kept as json.`);
|
|
318
|
+
}
|
|
319
|
+
function withDropped(field, text) {
|
|
320
|
+
field.dropped.push(text);
|
|
321
|
+
return field;
|
|
322
|
+
}
|
|
323
|
+
function stringFormat(name, target, asConstructor) {
|
|
324
|
+
const field = emptyField(target === 'plain' ? 'text' : target);
|
|
325
|
+
if (target === 'plain') {
|
|
326
|
+
field.dropped.push(asConstructor ? `z.${name}()` : `.${name}()`);
|
|
327
|
+
}
|
|
328
|
+
return field;
|
|
329
|
+
}
|
|
330
|
+
function resolveWrapper(scope, name, args, depth) {
|
|
331
|
+
const first = args[0];
|
|
332
|
+
if (first === undefined) {
|
|
333
|
+
return jsonField(`z.${name}() has no schema; kept as json.`);
|
|
334
|
+
}
|
|
335
|
+
if (name === 'lazy' && isFunctionNode(first)) {
|
|
336
|
+
const inner = functionResult(first);
|
|
337
|
+
return inner === undefined
|
|
338
|
+
? jsonField('z.lazy() returns something other than one schema; kept as json.')
|
|
339
|
+
: resolveNode(scope, inner, depth + 1);
|
|
340
|
+
}
|
|
341
|
+
return applyStep(scope, resolveNode(scope, first, depth + 1), { name, args: args.slice(1) }, depth);
|
|
342
|
+
}
|
|
343
|
+
function resolveEnum(scope, name, argument) {
|
|
344
|
+
if (argument === undefined) {
|
|
345
|
+
return jsonField(`z.${name}() has no values; kept as json.`);
|
|
346
|
+
}
|
|
347
|
+
const source = stripWrappers(argument);
|
|
348
|
+
let enumValues;
|
|
349
|
+
if (source.type === 'array') {
|
|
350
|
+
const values = [];
|
|
351
|
+
for (const item of source.namedChildren) {
|
|
352
|
+
const literal = literalOf(item);
|
|
353
|
+
if (typeof literal === 'string') {
|
|
354
|
+
values.push({ name: literal, dbValue: literal });
|
|
355
|
+
}
|
|
356
|
+
else if (item.type !== 'comment') {
|
|
357
|
+
return jsonField(`z.${name}() lists a value that is not a string literal; kept as json.`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
enumValues = { values, numeric: false };
|
|
361
|
+
}
|
|
362
|
+
else if (source.type === 'object') {
|
|
363
|
+
enumValues = objectEnumValues(source);
|
|
364
|
+
}
|
|
365
|
+
else if (source.type === 'identifier') {
|
|
366
|
+
enumValues = scope.host.valuesOf(source.text);
|
|
367
|
+
if (enumValues === undefined) {
|
|
368
|
+
return jsonField(`z.${name}(${source.text}) refers to a list of values that is not declared in the input files; kept as json.`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (enumValues?.numeric === true) {
|
|
372
|
+
const field = emptyField('int');
|
|
373
|
+
field.dropped.push(`numeric enum z.${name}(${truncate(argument.text)})`);
|
|
374
|
+
return field;
|
|
375
|
+
}
|
|
376
|
+
if (enumValues === undefined || enumValues.values.length === 0) {
|
|
377
|
+
return jsonField(`z.${name}(${truncate(argument.text)}) has values that could not be read statically; kept as json.`);
|
|
378
|
+
}
|
|
379
|
+
const field = emptyField('string');
|
|
380
|
+
field.enumName = scope.host.inlineEnum(scope.model, scope.key, enumValues.values);
|
|
381
|
+
return field;
|
|
382
|
+
}
|
|
383
|
+
/** Reads `{ A: 'a', B: 'b' }` as enum members (name -> stored value). */
|
|
384
|
+
export function objectEnumValues(objectNode) {
|
|
385
|
+
const values = [];
|
|
386
|
+
let numeric = false;
|
|
387
|
+
for (const member of objectMembers(objectNode).members) {
|
|
388
|
+
if (member.kind !== 'property') {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
const literal = literalOf(member.node);
|
|
392
|
+
if (typeof literal === 'string') {
|
|
393
|
+
values.push({ name: member.key, dbValue: literal });
|
|
394
|
+
}
|
|
395
|
+
else if (typeof literal === 'number') {
|
|
396
|
+
numeric = true;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return { values: numeric ? [] : values, numeric };
|
|
400
|
+
}
|
|
401
|
+
function resolveLiteral(scope, argument) {
|
|
402
|
+
const text = argument === undefined ? '' : truncate(argument.text);
|
|
403
|
+
if (argument === undefined) {
|
|
404
|
+
return jsonField('z.literal() has no value; kept as json.');
|
|
405
|
+
}
|
|
406
|
+
if (stripWrappers(argument).type === 'array') {
|
|
407
|
+
// Zod 4: z.literal(['a', 'b']) is a union of literals.
|
|
408
|
+
const items = stripWrappers(argument).namedChildren.filter((child) => child.type !== 'comment');
|
|
409
|
+
return combineOptions(scope, items.map((item) => resolveLiteral(scope, item)));
|
|
410
|
+
}
|
|
411
|
+
const literal = literalOf(argument);
|
|
412
|
+
let field;
|
|
413
|
+
if (typeof literal === 'string') {
|
|
414
|
+
field = emptyField('text');
|
|
415
|
+
field.literalString = literal;
|
|
416
|
+
}
|
|
417
|
+
else if (typeof literal === 'number') {
|
|
418
|
+
field = emptyField(Number.isInteger(literal) ? 'int' : 'float');
|
|
419
|
+
}
|
|
420
|
+
else if (typeof literal === 'boolean') {
|
|
421
|
+
field = emptyField('boolean');
|
|
422
|
+
}
|
|
423
|
+
else if (literal === null) {
|
|
424
|
+
field = jsonField();
|
|
425
|
+
field.onlyNull = true;
|
|
426
|
+
return field;
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
return jsonField(`z.literal(${text}) is not a plain literal; kept as json.`);
|
|
430
|
+
}
|
|
431
|
+
field.dropped.push(`z.literal(${text})`);
|
|
432
|
+
return field;
|
|
433
|
+
}
|
|
434
|
+
function wrapArray(inner) {
|
|
435
|
+
const field = inner;
|
|
436
|
+
if (inner.optional || inner.nullable) {
|
|
437
|
+
field.dropped.push('nullable array elements');
|
|
438
|
+
field.optional = false;
|
|
439
|
+
field.nullable = false;
|
|
440
|
+
}
|
|
441
|
+
field.hasDefault = false;
|
|
442
|
+
delete field.default;
|
|
443
|
+
if (field.relation !== undefined) {
|
|
444
|
+
field.relation = { ...field.relation, many: true };
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
field.arrayDepth += 1;
|
|
448
|
+
}
|
|
449
|
+
return field;
|
|
450
|
+
}
|
|
451
|
+
function resolveUnion(scope, options, depth) {
|
|
452
|
+
const list = options === undefined ? undefined : stripWrappers(options);
|
|
453
|
+
if (list === undefined || list.type !== 'array') {
|
|
454
|
+
return jsonField('a z.union() without an inline list of schemas is kept as json.');
|
|
455
|
+
}
|
|
456
|
+
const resolved = list.namedChildren
|
|
457
|
+
.filter((child) => child.type !== 'comment')
|
|
458
|
+
.map((child) => resolveNode(scope, child, depth + 1));
|
|
459
|
+
return combineOptions(scope, resolved);
|
|
460
|
+
}
|
|
461
|
+
/** Merges the options of a union: nulls make it nullable, string literals make an enum, equal types collapse. */
|
|
462
|
+
function combineOptions(scope, options) {
|
|
463
|
+
const nullable = options.some((option) => option.onlyNull === true);
|
|
464
|
+
const optional = options.some((option) => option.onlyUndefined === true);
|
|
465
|
+
const rest = options.filter((option) => option.onlyNull !== true && option.onlyUndefined !== true);
|
|
466
|
+
let result;
|
|
467
|
+
const first = rest[0];
|
|
468
|
+
if (first === undefined) {
|
|
469
|
+
result = jsonField();
|
|
470
|
+
}
|
|
471
|
+
else if (rest.length === 1) {
|
|
472
|
+
result = first;
|
|
473
|
+
}
|
|
474
|
+
else if (rest.every((option) => option.literalString !== undefined)) {
|
|
475
|
+
result = emptyField('string');
|
|
476
|
+
result.enumName = scope.host.inlineEnum(scope.model, scope.key, rest.map((option) => ({
|
|
477
|
+
name: option.literalString ?? '',
|
|
478
|
+
dbValue: option.literalString ?? '',
|
|
479
|
+
})));
|
|
480
|
+
}
|
|
481
|
+
else if (rest.every((option) => sameShape(first, option))) {
|
|
482
|
+
result = first;
|
|
483
|
+
for (const option of rest.slice(1)) {
|
|
484
|
+
mergeNotes(result, option);
|
|
485
|
+
if (option.maxLength !== result.maxLength) {
|
|
486
|
+
delete result.maxLength;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
result = jsonField(`a union of unlike types (${rest.map(describeField).join(' | ')}) is kept as one json column.`);
|
|
492
|
+
for (const option of rest) {
|
|
493
|
+
result.dropped.push(...option.dropped);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
if (nullable) {
|
|
497
|
+
result.nullable = true;
|
|
498
|
+
}
|
|
499
|
+
if (optional) {
|
|
500
|
+
result.optional = true;
|
|
501
|
+
}
|
|
502
|
+
return result;
|
|
503
|
+
}
|
|
504
|
+
// ---------------------------------------------------------------------------
|
|
505
|
+
// Steps
|
|
506
|
+
// ---------------------------------------------------------------------------
|
|
507
|
+
function numberArgument(step) {
|
|
508
|
+
const first = step.args[0];
|
|
509
|
+
const literal = first === undefined ? undefined : literalOf(first);
|
|
510
|
+
return typeof literal === 'number' ? literal : undefined;
|
|
511
|
+
}
|
|
512
|
+
function applyStep(scope, field, step, depth) {
|
|
513
|
+
switch (step.name) {
|
|
514
|
+
case 'optional':
|
|
515
|
+
field.optional = true;
|
|
516
|
+
return field;
|
|
517
|
+
case 'nullable':
|
|
518
|
+
field.nullable = true;
|
|
519
|
+
return field;
|
|
520
|
+
case 'nullish':
|
|
521
|
+
field.optional = true;
|
|
522
|
+
field.nullable = true;
|
|
523
|
+
return field;
|
|
524
|
+
case 'nonoptional':
|
|
525
|
+
case 'required':
|
|
526
|
+
field.optional = false;
|
|
527
|
+
return field;
|
|
528
|
+
case 'default':
|
|
529
|
+
case 'prefault':
|
|
530
|
+
case 'catch':
|
|
531
|
+
applyDefault(scope, field, step);
|
|
532
|
+
return field;
|
|
533
|
+
case 'array':
|
|
534
|
+
return wrapArray(field);
|
|
535
|
+
case 'or':
|
|
536
|
+
return applyUnion(scope, field, step, depth);
|
|
537
|
+
case 'and':
|
|
538
|
+
field.problems.push('a .and() intersection is kept as one json column.');
|
|
539
|
+
field.type = 'json';
|
|
540
|
+
delete field.maxLength;
|
|
541
|
+
delete field.enumName;
|
|
542
|
+
delete field.relation;
|
|
543
|
+
field.arrayDepth = 0;
|
|
544
|
+
return field;
|
|
545
|
+
case 'int':
|
|
546
|
+
if (field.type === 'float' && field.arrayDepth === 0) {
|
|
547
|
+
field.type = 'int';
|
|
548
|
+
}
|
|
549
|
+
return field;
|
|
550
|
+
case 'max':
|
|
551
|
+
case 'length': {
|
|
552
|
+
const limit = numberArgument(step);
|
|
553
|
+
if (isTextual(field) && limit !== undefined) {
|
|
554
|
+
field.maxLength = limit;
|
|
555
|
+
if (step.name === 'length') {
|
|
556
|
+
field.dropped.push(`${stepText(step)} (kept as the maximum length)`);
|
|
557
|
+
}
|
|
558
|
+
return field;
|
|
559
|
+
}
|
|
560
|
+
field.dropped.push(stepText(step));
|
|
561
|
+
return field;
|
|
562
|
+
}
|
|
563
|
+
case 'regex':
|
|
564
|
+
applyRegex(field, step);
|
|
565
|
+
return field;
|
|
566
|
+
default:
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
if (isTextual(field) && step.name in STRING_FORMAT_STEPS) {
|
|
570
|
+
const target = STRING_FORMAT_STEPS[step.name] ?? 'plain';
|
|
571
|
+
if (target === 'plain') {
|
|
572
|
+
field.dropped.push(stepText(step));
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
field.type = target;
|
|
576
|
+
delete field.maxLength;
|
|
577
|
+
}
|
|
578
|
+
return field;
|
|
579
|
+
}
|
|
580
|
+
if (!SILENT_STEPS.has(step.name)) {
|
|
581
|
+
field.dropped.push(stepText(step));
|
|
582
|
+
}
|
|
583
|
+
return field;
|
|
584
|
+
}
|
|
585
|
+
function applyRegex(field, step) {
|
|
586
|
+
const pattern = step.args[0]?.text ?? '';
|
|
587
|
+
if (isTextual(field) && field.maxLength === undefined) {
|
|
588
|
+
if (pattern === BIGINT_PATTERN) {
|
|
589
|
+
field.type = 'bigInt';
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (pattern === DECIMAL_UNSIZED) {
|
|
593
|
+
field.type = 'decimal';
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
const sized = DECIMAL_SIZED.exec(pattern);
|
|
597
|
+
if (sized !== null) {
|
|
598
|
+
const integerDigits = Number(sized[1]);
|
|
599
|
+
const places = sized[2] === undefined ? 0 : Number(sized[2]);
|
|
600
|
+
field.type = 'decimal';
|
|
601
|
+
field.maxDigits = integerDigits + places;
|
|
602
|
+
field.decimalPlaces = places;
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
field.dropped.push('.regex()');
|
|
607
|
+
}
|
|
608
|
+
function applyUnion(scope, field, step, depth) {
|
|
609
|
+
const other = step.args[0];
|
|
610
|
+
return other === undefined
|
|
611
|
+
? field
|
|
612
|
+
: combineOptions(scope, [field, resolveNode(scope, other, depth + 1)]);
|
|
613
|
+
}
|
|
614
|
+
function applyDefault(scope, field, step) {
|
|
615
|
+
field.hasDefault = true;
|
|
616
|
+
if (step.name === 'catch') {
|
|
617
|
+
field.dropped.push('.catch() (read as the default)');
|
|
618
|
+
}
|
|
619
|
+
const argument = step.args[0];
|
|
620
|
+
if (argument === undefined) {
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
const value = defaultOf(scope, field, argument);
|
|
624
|
+
if (value === 'none') {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (value === undefined) {
|
|
628
|
+
field.dropped.push(stepText({ name: 'default', args: step.args }));
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
field.default = value;
|
|
632
|
+
}
|
|
633
|
+
/** Reads a default value; undefined when it is not a value the shared model can store, "none" for a null default. */
|
|
634
|
+
function defaultOf(scope, field, argument) {
|
|
635
|
+
const current = stripWrappers(argument);
|
|
636
|
+
if (isFunctionNode(current)) {
|
|
637
|
+
const result = functionResult(current);
|
|
638
|
+
const text = result?.text.replace(/\s+/g, '') ?? '';
|
|
639
|
+
if ((field.type === 'dateTime' || field.type === 'date') &&
|
|
640
|
+
/^(newDate\(\)|Date\.now\(\))$/.test(text)) {
|
|
641
|
+
return { kind: 'now' };
|
|
642
|
+
}
|
|
643
|
+
if (field.type === 'uuid' && /randomUUID\(\)$/.test(text)) {
|
|
644
|
+
return { kind: 'uuid' };
|
|
645
|
+
}
|
|
646
|
+
return result !== undefined && field.arrayDepth === 0
|
|
647
|
+
? structuredDefault(field, result)
|
|
648
|
+
: undefined;
|
|
649
|
+
}
|
|
650
|
+
const literal = literalOf(current);
|
|
651
|
+
if (literal === null) {
|
|
652
|
+
return 'none';
|
|
653
|
+
}
|
|
654
|
+
if (typeof literal === 'string' && field.enumName !== undefined) {
|
|
655
|
+
const member = scope.host.enumMemberName(field.enumName, literal);
|
|
656
|
+
return member === undefined
|
|
657
|
+
? undefined
|
|
658
|
+
: { kind: 'enumValue', value: member };
|
|
659
|
+
}
|
|
660
|
+
if (literal !== undefined) {
|
|
661
|
+
return field.arrayDepth === 0
|
|
662
|
+
? { kind: 'literal', value: literal }
|
|
663
|
+
: undefined;
|
|
664
|
+
}
|
|
665
|
+
return field.arrayDepth === 0 ? structuredDefault(field, current) : undefined;
|
|
666
|
+
}
|
|
667
|
+
/** `{}` and `[]` as the default of a json column are stored as their text. */
|
|
668
|
+
function structuredDefault(field, node) {
|
|
669
|
+
const current = stripWrappers(node);
|
|
670
|
+
if (field.type !== 'json') {
|
|
671
|
+
return undefined;
|
|
672
|
+
}
|
|
673
|
+
if (current.type === 'object' && current.namedChildCount === 0) {
|
|
674
|
+
return { kind: 'literal', value: '{}' };
|
|
675
|
+
}
|
|
676
|
+
if (current.type === 'array' && current.namedChildCount === 0) {
|
|
677
|
+
return { kind: 'literal', value: '[]' };
|
|
678
|
+
}
|
|
679
|
+
return undefined;
|
|
680
|
+
}
|
|
681
|
+
//# sourceMappingURL=zodFields.js.map
|