@famgia/omnify-typescript 0.0.72 → 0.0.73
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/chunk-LHNMWILH.js +2221 -0
- package/dist/chunk-LHNMWILH.js.map +1 -0
- package/dist/{chunk-6I4O23X6.js → chunk-NFJACF72.js} +19 -11
- package/dist/chunk-NFJACF72.js.map +1 -0
- package/dist/index.cjs +18 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1 -1
- package/dist/plugin.cjs +18 -10
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-6I4O23X6.js.map +0 -1
|
@@ -0,0 +1,2221 @@
|
|
|
1
|
+
// src/interface-generator.ts
|
|
2
|
+
import { resolveLocalizedString } from "@famgia/omnify-types";
|
|
3
|
+
function toSnakeCase(str) {
|
|
4
|
+
return str.replace(/([A-Z])/g, "_$1").replace(/^_/, "").toLowerCase();
|
|
5
|
+
}
|
|
6
|
+
var TYPE_MAP = {
|
|
7
|
+
String: "string",
|
|
8
|
+
TinyInt: "number",
|
|
9
|
+
Int: "number",
|
|
10
|
+
BigInt: "number",
|
|
11
|
+
Float: "number",
|
|
12
|
+
Boolean: "boolean",
|
|
13
|
+
Text: "string",
|
|
14
|
+
MediumText: "string",
|
|
15
|
+
LongText: "string",
|
|
16
|
+
Date: "DateString",
|
|
17
|
+
Time: "string",
|
|
18
|
+
DateTime: "DateTimeString",
|
|
19
|
+
Timestamp: "DateTimeString",
|
|
20
|
+
Json: "unknown",
|
|
21
|
+
Email: "string",
|
|
22
|
+
Password: "string",
|
|
23
|
+
Enum: "string",
|
|
24
|
+
Select: "string",
|
|
25
|
+
Lookup: "number"
|
|
26
|
+
};
|
|
27
|
+
var FILE_INTERFACE_NAME = "File";
|
|
28
|
+
var PK_TYPE_MAP = {
|
|
29
|
+
Int: "number",
|
|
30
|
+
BigInt: "number",
|
|
31
|
+
Uuid: "string",
|
|
32
|
+
String: "string"
|
|
33
|
+
};
|
|
34
|
+
function resolveDisplayName(value, options = {}) {
|
|
35
|
+
if (value === void 0) {
|
|
36
|
+
return void 0;
|
|
37
|
+
}
|
|
38
|
+
return resolveLocalizedString(value, {
|
|
39
|
+
locale: options.locale,
|
|
40
|
+
config: options.localeConfig
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function toPropertyName(name) {
|
|
44
|
+
return name;
|
|
45
|
+
}
|
|
46
|
+
function toInterfaceName(schemaName) {
|
|
47
|
+
return schemaName;
|
|
48
|
+
}
|
|
49
|
+
function getPropertyType(property, _allSchemas) {
|
|
50
|
+
if (property.type === "File") {
|
|
51
|
+
const fileProp = property;
|
|
52
|
+
if (fileProp.multiple) {
|
|
53
|
+
return `${FILE_INTERFACE_NAME}[]`;
|
|
54
|
+
}
|
|
55
|
+
return `${FILE_INTERFACE_NAME} | null`;
|
|
56
|
+
}
|
|
57
|
+
if (property.type === "Association") {
|
|
58
|
+
const assocProp = property;
|
|
59
|
+
const targetName = assocProp.target ?? "unknown";
|
|
60
|
+
switch (assocProp.relation) {
|
|
61
|
+
// Standard relations
|
|
62
|
+
case "OneToOne":
|
|
63
|
+
case "ManyToOne":
|
|
64
|
+
return targetName;
|
|
65
|
+
case "OneToMany":
|
|
66
|
+
case "ManyToMany":
|
|
67
|
+
return `${targetName}[]`;
|
|
68
|
+
// Polymorphic relations
|
|
69
|
+
case "MorphTo":
|
|
70
|
+
if (assocProp.targets && assocProp.targets.length > 0) {
|
|
71
|
+
return assocProp.targets.join(" | ");
|
|
72
|
+
}
|
|
73
|
+
return "unknown";
|
|
74
|
+
case "MorphOne":
|
|
75
|
+
return targetName;
|
|
76
|
+
case "MorphMany":
|
|
77
|
+
case "MorphToMany":
|
|
78
|
+
case "MorphedByMany":
|
|
79
|
+
return `${targetName}[]`;
|
|
80
|
+
default:
|
|
81
|
+
return "unknown";
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (property.type === "EnumRef") {
|
|
85
|
+
const enumRefProp = property;
|
|
86
|
+
return enumRefProp.enum;
|
|
87
|
+
}
|
|
88
|
+
if (property.type === "Enum") {
|
|
89
|
+
const enumProp = property;
|
|
90
|
+
if (typeof enumProp.enum === "string") {
|
|
91
|
+
return enumProp.enum;
|
|
92
|
+
}
|
|
93
|
+
if (Array.isArray(enumProp.enum)) {
|
|
94
|
+
return enumProp.enum.map((v) => `'${v}'`).join(" | ");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (property.type === "Select") {
|
|
98
|
+
const selectProp = property;
|
|
99
|
+
if (selectProp.options && selectProp.options.length > 0) {
|
|
100
|
+
return selectProp.options.map((v) => `'${v}'`).join(" | ");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return TYPE_MAP[property.type] ?? "unknown";
|
|
104
|
+
}
|
|
105
|
+
function propertyToTSProperties(propertyName, property, allSchemas, options = {}) {
|
|
106
|
+
const baseProp = property;
|
|
107
|
+
const isReadonly = options.readonly ?? true;
|
|
108
|
+
const displayName = resolveDisplayName(baseProp.displayName, options);
|
|
109
|
+
if (options.customTypes) {
|
|
110
|
+
const customType = options.customTypes.get(property.type);
|
|
111
|
+
if (customType?.compound && customType.expand) {
|
|
112
|
+
const expandedProps = [];
|
|
113
|
+
for (const field of customType.expand) {
|
|
114
|
+
const fieldName = `${propertyName}_${toSnakeCase(field.suffix)}`;
|
|
115
|
+
const fieldOverride = baseProp.fields?.[field.suffix];
|
|
116
|
+
const isNullable = fieldOverride?.nullable ?? field.sql?.nullable ?? baseProp.nullable ?? false;
|
|
117
|
+
const tsType = field.typescript?.type ?? "string";
|
|
118
|
+
expandedProps.push({
|
|
119
|
+
name: fieldName,
|
|
120
|
+
type: tsType,
|
|
121
|
+
optional: isNullable,
|
|
122
|
+
readonly: isReadonly,
|
|
123
|
+
comment: `${displayName ?? propertyName} (${field.suffix})`
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (customType.accessors) {
|
|
127
|
+
for (const accessor of customType.accessors) {
|
|
128
|
+
const accessorName = `${propertyName}_${toSnakeCase(accessor.name)}`;
|
|
129
|
+
expandedProps.push({
|
|
130
|
+
name: accessorName,
|
|
131
|
+
type: "string | null",
|
|
132
|
+
optional: true,
|
|
133
|
+
readonly: isReadonly,
|
|
134
|
+
comment: `${displayName ?? propertyName} (computed)`
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return expandedProps;
|
|
139
|
+
}
|
|
140
|
+
if (customType && !customType.compound) {
|
|
141
|
+
const tsType = customType.typescript?.type ?? "string";
|
|
142
|
+
return [{
|
|
143
|
+
name: toPropertyName(propertyName),
|
|
144
|
+
type: tsType,
|
|
145
|
+
optional: baseProp.nullable ?? false,
|
|
146
|
+
readonly: isReadonly,
|
|
147
|
+
comment: displayName
|
|
148
|
+
}];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (property.type === "Association") {
|
|
152
|
+
const assocProp = property;
|
|
153
|
+
if (assocProp.relation === "MorphTo" && assocProp.targets && assocProp.targets.length > 0) {
|
|
154
|
+
const propBaseName = toPropertyName(propertyName);
|
|
155
|
+
const targetUnion = assocProp.targets.map((t) => `'${t}'`).join(" | ");
|
|
156
|
+
const relationUnion = assocProp.targets.join(" | ");
|
|
157
|
+
return [
|
|
158
|
+
{
|
|
159
|
+
name: `${propBaseName}Type`,
|
|
160
|
+
type: targetUnion,
|
|
161
|
+
optional: true,
|
|
162
|
+
// Polymorphic columns are nullable
|
|
163
|
+
readonly: isReadonly,
|
|
164
|
+
comment: `Polymorphic type for ${propertyName}`
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: `${propBaseName}Id`,
|
|
168
|
+
type: "number",
|
|
169
|
+
optional: true,
|
|
170
|
+
readonly: isReadonly,
|
|
171
|
+
comment: `Polymorphic ID for ${propertyName}`
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: propBaseName,
|
|
175
|
+
type: `${relationUnion} | null`,
|
|
176
|
+
optional: true,
|
|
177
|
+
readonly: isReadonly,
|
|
178
|
+
comment: displayName ?? `Polymorphic relation to ${assocProp.targets.join(", ")}`
|
|
179
|
+
}
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const type = getPropertyType(property, allSchemas);
|
|
184
|
+
return [{
|
|
185
|
+
name: toPropertyName(propertyName),
|
|
186
|
+
type,
|
|
187
|
+
optional: baseProp.nullable ?? false,
|
|
188
|
+
readonly: isReadonly,
|
|
189
|
+
comment: displayName
|
|
190
|
+
}];
|
|
191
|
+
}
|
|
192
|
+
function propertyToTSProperty(propertyName, property, allSchemas, options = {}) {
|
|
193
|
+
return propertyToTSProperties(propertyName, property, allSchemas, options)[0];
|
|
194
|
+
}
|
|
195
|
+
function extractTypeReferences(type, allSchemaNames) {
|
|
196
|
+
const primitives = /* @__PURE__ */ new Set(["string", "number", "boolean", "unknown", "null", "undefined", "void", "never", "any"]);
|
|
197
|
+
const refs = [];
|
|
198
|
+
const cleanType = type.replace(/\[\]/g, "").replace(/\s*\|\s*null/g, "");
|
|
199
|
+
const parts = cleanType.split(/\s*\|\s*/);
|
|
200
|
+
for (const part of parts) {
|
|
201
|
+
const trimmed = part.trim().replace(/^['"]|['"]$/g, "");
|
|
202
|
+
if (!primitives.has(trimmed) && allSchemaNames.has(trimmed)) {
|
|
203
|
+
refs.push(trimmed);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return refs;
|
|
207
|
+
}
|
|
208
|
+
function schemaToInterface(schema, allSchemas, options = {}) {
|
|
209
|
+
const properties = [];
|
|
210
|
+
const allSchemaNames = new Set(Object.keys(allSchemas).filter((name) => allSchemas[name].kind !== "enum"));
|
|
211
|
+
if (schema.options?.id !== false) {
|
|
212
|
+
const pkType = schema.options?.idType ?? "BigInt";
|
|
213
|
+
properties.push({
|
|
214
|
+
name: "id",
|
|
215
|
+
type: PK_TYPE_MAP[pkType] ?? "number",
|
|
216
|
+
optional: false,
|
|
217
|
+
readonly: options.readonly ?? true,
|
|
218
|
+
comment: "Primary key"
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
if (schema.properties) {
|
|
222
|
+
for (const [propName, property] of Object.entries(schema.properties)) {
|
|
223
|
+
properties.push(...propertyToTSProperties(propName, property, allSchemas, options));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (schema.options?.timestamps !== false) {
|
|
227
|
+
properties.push(
|
|
228
|
+
{
|
|
229
|
+
name: "created_at",
|
|
230
|
+
type: "DateTimeString",
|
|
231
|
+
optional: true,
|
|
232
|
+
readonly: options.readonly ?? true,
|
|
233
|
+
comment: "Creation timestamp"
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
name: "updated_at",
|
|
237
|
+
type: "DateTimeString",
|
|
238
|
+
optional: true,
|
|
239
|
+
readonly: options.readonly ?? true,
|
|
240
|
+
comment: "Last update timestamp"
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
if (schema.options?.softDelete) {
|
|
245
|
+
properties.push({
|
|
246
|
+
name: "deleted_at",
|
|
247
|
+
type: "DateTimeString",
|
|
248
|
+
optional: true,
|
|
249
|
+
readonly: options.readonly ?? true,
|
|
250
|
+
comment: "Soft delete timestamp"
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
const dependencySet = /* @__PURE__ */ new Set();
|
|
254
|
+
for (const prop of properties) {
|
|
255
|
+
for (const ref of extractTypeReferences(prop.type, allSchemaNames)) {
|
|
256
|
+
if (ref !== schema.name) {
|
|
257
|
+
dependencySet.add(ref);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const enumDependencySet = /* @__PURE__ */ new Set();
|
|
262
|
+
if (schema.properties) {
|
|
263
|
+
for (const property of Object.values(schema.properties)) {
|
|
264
|
+
if (property.type === "EnumRef") {
|
|
265
|
+
const enumRefProp = property;
|
|
266
|
+
if (enumRefProp.enum) {
|
|
267
|
+
enumDependencySet.add(enumRefProp.enum);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const schemaDisplayName = resolveDisplayName(schema.displayName, options);
|
|
273
|
+
return {
|
|
274
|
+
name: toInterfaceName(schema.name),
|
|
275
|
+
properties,
|
|
276
|
+
comment: schemaDisplayName ?? schema.name,
|
|
277
|
+
dependencies: dependencySet.size > 0 ? Array.from(dependencySet).sort() : void 0,
|
|
278
|
+
enumDependencies: enumDependencySet.size > 0 ? Array.from(enumDependencySet).sort() : void 0
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function formatProperty(property) {
|
|
282
|
+
const readonly = property.readonly ? "readonly " : "";
|
|
283
|
+
const optional = property.optional ? "?" : "";
|
|
284
|
+
const comment = property.comment ? ` /** ${property.comment} */
|
|
285
|
+
` : "";
|
|
286
|
+
return `${comment} ${readonly}${property.name}${optional}: ${property.type};`;
|
|
287
|
+
}
|
|
288
|
+
function formatInterface(iface) {
|
|
289
|
+
const comment = iface.comment ? `/**
|
|
290
|
+
* ${iface.comment}
|
|
291
|
+
*/
|
|
292
|
+
` : "";
|
|
293
|
+
const extendsClause = iface.extends && iface.extends.length > 0 ? ` extends ${iface.extends.join(", ")}` : "";
|
|
294
|
+
const properties = iface.properties.map(formatProperty).join("\n");
|
|
295
|
+
return `${comment}export interface ${iface.name}${extendsClause} {
|
|
296
|
+
${properties}
|
|
297
|
+
}`;
|
|
298
|
+
}
|
|
299
|
+
function generateInterfaces(schemas, options = {}) {
|
|
300
|
+
const interfaces = [];
|
|
301
|
+
for (const schema of Object.values(schemas)) {
|
|
302
|
+
if (schema.kind === "enum") {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (schema.options?.hidden === true) {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
interfaces.push(schemaToInterface(schema, schemas, options));
|
|
309
|
+
}
|
|
310
|
+
return interfaces;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/enum-generator.ts
|
|
314
|
+
import { resolveLocalizedString as resolveLocalizedString2 } from "@famgia/omnify-types";
|
|
315
|
+
function resolveDisplayName2(value, options = {}) {
|
|
316
|
+
if (value === void 0) {
|
|
317
|
+
return void 0;
|
|
318
|
+
}
|
|
319
|
+
return resolveLocalizedString2(value, {
|
|
320
|
+
locale: options.locale,
|
|
321
|
+
config: options.localeConfig
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
function toEnumMemberName(value) {
|
|
325
|
+
let result = value.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
326
|
+
if (/^\d/.test(result)) {
|
|
327
|
+
result = "_" + result;
|
|
328
|
+
}
|
|
329
|
+
return result;
|
|
330
|
+
}
|
|
331
|
+
function toEnumName(schemaName) {
|
|
332
|
+
return schemaName;
|
|
333
|
+
}
|
|
334
|
+
function parseEnumValue(value, options = {}) {
|
|
335
|
+
if (typeof value === "string") {
|
|
336
|
+
return {
|
|
337
|
+
name: toEnumMemberName(value),
|
|
338
|
+
value
|
|
339
|
+
// No label or extra - will fallback to value
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
let label;
|
|
343
|
+
if (value.label !== void 0) {
|
|
344
|
+
if (options.multiLocale && typeof value.label === "object") {
|
|
345
|
+
label = value.label;
|
|
346
|
+
} else {
|
|
347
|
+
label = resolveDisplayName2(value.label, options);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
name: toEnumMemberName(value.value),
|
|
352
|
+
value: value.value,
|
|
353
|
+
label,
|
|
354
|
+
extra: value.extra
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function schemaToEnum(schema, options = {}) {
|
|
358
|
+
if (schema.kind !== "enum" || !schema.values) {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
const values = schema.values.map(
|
|
362
|
+
(value) => parseEnumValue(value, options)
|
|
363
|
+
);
|
|
364
|
+
const displayName = resolveDisplayName2(schema.displayName, options);
|
|
365
|
+
return {
|
|
366
|
+
name: toEnumName(schema.name),
|
|
367
|
+
values,
|
|
368
|
+
comment: displayName ?? schema.name
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function generateEnums(schemas, options = {}) {
|
|
372
|
+
const enums = [];
|
|
373
|
+
for (const schema of Object.values(schemas)) {
|
|
374
|
+
if (schema.kind === "enum") {
|
|
375
|
+
const enumDef = schemaToEnum(schema, options);
|
|
376
|
+
if (enumDef) {
|
|
377
|
+
enums.push(enumDef);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return enums;
|
|
382
|
+
}
|
|
383
|
+
function pluginEnumToTSEnum(enumDef, options = {}) {
|
|
384
|
+
const values = enumDef.values.map((v) => {
|
|
385
|
+
let label;
|
|
386
|
+
if (v.label !== void 0) {
|
|
387
|
+
if (typeof v.label === "string") {
|
|
388
|
+
label = v.label;
|
|
389
|
+
} else if (options.multiLocale) {
|
|
390
|
+
label = v.label;
|
|
391
|
+
} else {
|
|
392
|
+
label = resolveDisplayName2(v.label, options);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return {
|
|
396
|
+
name: toEnumMemberName(v.value),
|
|
397
|
+
value: v.value,
|
|
398
|
+
label,
|
|
399
|
+
extra: v.extra
|
|
400
|
+
};
|
|
401
|
+
});
|
|
402
|
+
let comment;
|
|
403
|
+
if (enumDef.displayName !== void 0) {
|
|
404
|
+
if (typeof enumDef.displayName === "string") {
|
|
405
|
+
comment = enumDef.displayName;
|
|
406
|
+
} else {
|
|
407
|
+
comment = resolveDisplayName2(enumDef.displayName, options);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return {
|
|
411
|
+
name: enumDef.name,
|
|
412
|
+
values,
|
|
413
|
+
comment: comment ?? enumDef.name
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function generatePluginEnums(pluginEnums, options = {}) {
|
|
417
|
+
const enums = [];
|
|
418
|
+
for (const enumDef of pluginEnums.values()) {
|
|
419
|
+
enums.push(pluginEnumToTSEnum(enumDef, options));
|
|
420
|
+
}
|
|
421
|
+
return enums;
|
|
422
|
+
}
|
|
423
|
+
function isMultiLocaleLabel(label) {
|
|
424
|
+
return label !== void 0 && typeof label === "object";
|
|
425
|
+
}
|
|
426
|
+
function formatEnum(enumDef) {
|
|
427
|
+
const { name, values, comment } = enumDef;
|
|
428
|
+
const parts = [];
|
|
429
|
+
if (comment) {
|
|
430
|
+
parts.push(`/**
|
|
431
|
+
* ${comment}
|
|
432
|
+
*/
|
|
433
|
+
`);
|
|
434
|
+
}
|
|
435
|
+
const enumValues = values.map((v) => ` ${v.name} = '${v.value}',`).join("\n");
|
|
436
|
+
parts.push(`export enum ${name} {
|
|
437
|
+
${enumValues}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
`);
|
|
441
|
+
parts.push(`/** All ${name} values */
|
|
442
|
+
`);
|
|
443
|
+
parts.push(`export const ${name}Values = Object.values(${name}) as ${name}[];
|
|
444
|
+
|
|
445
|
+
`);
|
|
446
|
+
parts.push(`/** Type guard for ${name} */
|
|
447
|
+
`);
|
|
448
|
+
parts.push(`export function is${name}(value: unknown): value is ${name} {
|
|
449
|
+
`);
|
|
450
|
+
parts.push(` return ${name}Values.includes(value as ${name});
|
|
451
|
+
`);
|
|
452
|
+
parts.push(`}
|
|
453
|
+
|
|
454
|
+
`);
|
|
455
|
+
const hasLabels = values.some((v) => v.label !== void 0);
|
|
456
|
+
const hasMultiLocale = values.some((v) => isMultiLocaleLabel(v.label));
|
|
457
|
+
if (hasLabels) {
|
|
458
|
+
if (hasMultiLocale) {
|
|
459
|
+
const labelEntries = values.filter((v) => v.label !== void 0).map((v) => {
|
|
460
|
+
if (isMultiLocaleLabel(v.label)) {
|
|
461
|
+
const locales = Object.entries(v.label).map(([locale, text]) => `'${locale}': '${escapeString(text)}'`).join(", ");
|
|
462
|
+
return ` [${name}.${v.name}]: { ${locales} },`;
|
|
463
|
+
}
|
|
464
|
+
return ` [${name}.${v.name}]: { default: '${escapeString(String(v.label))}' },`;
|
|
465
|
+
}).join("\n");
|
|
466
|
+
parts.push(`const ${lowerFirst(name)}Labels: Partial<Record<${name}, Record<string, string>>> = {
|
|
467
|
+
${labelEntries}
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
`);
|
|
471
|
+
parts.push(`/** Get label for ${name} value with locale support */
|
|
472
|
+
`);
|
|
473
|
+
parts.push(`export function get${name}Label(value: ${name}, locale?: string): string {
|
|
474
|
+
`);
|
|
475
|
+
parts.push(` const labels = ${lowerFirst(name)}Labels[value];
|
|
476
|
+
`);
|
|
477
|
+
parts.push(` if (!labels) return value;
|
|
478
|
+
`);
|
|
479
|
+
parts.push(` if (locale && labels[locale]) return labels[locale];
|
|
480
|
+
`);
|
|
481
|
+
parts.push(` // Fallback: ja \u2192 en \u2192 first available
|
|
482
|
+
`);
|
|
483
|
+
parts.push(` return labels['ja'] ?? labels['en'] ?? Object.values(labels)[0] ?? value;
|
|
484
|
+
`);
|
|
485
|
+
parts.push(`}
|
|
486
|
+
|
|
487
|
+
`);
|
|
488
|
+
} else {
|
|
489
|
+
const labelEntries = values.filter((v) => v.label !== void 0).map((v) => ` [${name}.${v.name}]: '${escapeString(String(v.label))}',`).join("\n");
|
|
490
|
+
parts.push(`const ${lowerFirst(name)}Labels: Partial<Record<${name}, string>> = {
|
|
491
|
+
${labelEntries}
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
`);
|
|
495
|
+
parts.push(`/** Get label for ${name} value (fallback to value if no label) */
|
|
496
|
+
`);
|
|
497
|
+
parts.push(`export function get${name}Label(value: ${name}): string {
|
|
498
|
+
`);
|
|
499
|
+
parts.push(` return ${lowerFirst(name)}Labels[value] ?? value;
|
|
500
|
+
`);
|
|
501
|
+
parts.push(`}
|
|
502
|
+
|
|
503
|
+
`);
|
|
504
|
+
}
|
|
505
|
+
} else {
|
|
506
|
+
parts.push(`/** Get label for ${name} value (returns value as-is) */
|
|
507
|
+
`);
|
|
508
|
+
parts.push(`export function get${name}Label(value: ${name}): string {
|
|
509
|
+
`);
|
|
510
|
+
parts.push(` return value;
|
|
511
|
+
`);
|
|
512
|
+
parts.push(`}
|
|
513
|
+
|
|
514
|
+
`);
|
|
515
|
+
}
|
|
516
|
+
const hasExtra = values.some((v) => v.extra !== void 0);
|
|
517
|
+
if (hasExtra) {
|
|
518
|
+
const extraEntries = values.filter((v) => v.extra !== void 0).map((v) => ` [${name}.${v.name}]: ${JSON.stringify(v.extra)},`).join("\n");
|
|
519
|
+
parts.push(`const ${lowerFirst(name)}Extra: Partial<Record<${name}, Record<string, unknown>>> = {
|
|
520
|
+
${extraEntries}
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
`);
|
|
524
|
+
parts.push(`/** Get extra metadata for ${name} value (undefined if not defined) */
|
|
525
|
+
`);
|
|
526
|
+
parts.push(`export function get${name}Extra(value: ${name}): Record<string, unknown> | undefined {
|
|
527
|
+
`);
|
|
528
|
+
parts.push(` return ${lowerFirst(name)}Extra[value];
|
|
529
|
+
`);
|
|
530
|
+
parts.push(`}`);
|
|
531
|
+
} else {
|
|
532
|
+
parts.push(`/** Get extra metadata for ${name} value (undefined if not defined) */
|
|
533
|
+
`);
|
|
534
|
+
parts.push(`export function get${name}Extra(_value: ${name}): Record<string, unknown> | undefined {
|
|
535
|
+
`);
|
|
536
|
+
parts.push(` return undefined;
|
|
537
|
+
`);
|
|
538
|
+
parts.push(`}`);
|
|
539
|
+
}
|
|
540
|
+
return parts.join("");
|
|
541
|
+
}
|
|
542
|
+
function lowerFirst(str) {
|
|
543
|
+
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
544
|
+
}
|
|
545
|
+
function escapeString(str) {
|
|
546
|
+
return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
547
|
+
}
|
|
548
|
+
function enumToUnionType(enumDef) {
|
|
549
|
+
const type = enumDef.values.map((v) => `'${v.value}'`).join(" | ");
|
|
550
|
+
return {
|
|
551
|
+
name: enumDef.name,
|
|
552
|
+
type,
|
|
553
|
+
comment: enumDef.comment
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
function formatTypeAlias(alias) {
|
|
557
|
+
const { name, type, comment } = alias;
|
|
558
|
+
const parts = [];
|
|
559
|
+
if (comment) {
|
|
560
|
+
parts.push(`/**
|
|
561
|
+
* ${comment}
|
|
562
|
+
*/
|
|
563
|
+
`);
|
|
564
|
+
}
|
|
565
|
+
parts.push(`export type ${name} = ${type};
|
|
566
|
+
|
|
567
|
+
`);
|
|
568
|
+
const values = type.split(" | ").map((v) => v.trim());
|
|
569
|
+
parts.push(`/** All ${name} values */
|
|
570
|
+
`);
|
|
571
|
+
parts.push(`export const ${name}Values: ${name}[] = [${values.join(", ")}];
|
|
572
|
+
|
|
573
|
+
`);
|
|
574
|
+
parts.push(`/** Type guard for ${name} */
|
|
575
|
+
`);
|
|
576
|
+
parts.push(`export function is${name}(value: unknown): value is ${name} {
|
|
577
|
+
`);
|
|
578
|
+
parts.push(` return ${name}Values.includes(value as ${name});
|
|
579
|
+
`);
|
|
580
|
+
parts.push(`}
|
|
581
|
+
|
|
582
|
+
`);
|
|
583
|
+
parts.push(`/** Get label for ${name} value (returns value as-is) */
|
|
584
|
+
`);
|
|
585
|
+
parts.push(`export function get${name}Label(value: ${name}): string {
|
|
586
|
+
`);
|
|
587
|
+
parts.push(` return value;
|
|
588
|
+
`);
|
|
589
|
+
parts.push(`}
|
|
590
|
+
|
|
591
|
+
`);
|
|
592
|
+
parts.push(`/** Get extra metadata for ${name} value (always undefined for type aliases) */
|
|
593
|
+
`);
|
|
594
|
+
parts.push(`export function get${name}Extra(_value: ${name}): Record<string, unknown> | undefined {
|
|
595
|
+
`);
|
|
596
|
+
parts.push(` return undefined;
|
|
597
|
+
`);
|
|
598
|
+
parts.push(`}`);
|
|
599
|
+
return parts.join("");
|
|
600
|
+
}
|
|
601
|
+
function extractInlineEnums(schemas, options = {}) {
|
|
602
|
+
const typeAliases = [];
|
|
603
|
+
for (const schema of Object.values(schemas)) {
|
|
604
|
+
if (schema.kind === "enum" || !schema.properties) {
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
for (const [propName, property] of Object.entries(schema.properties)) {
|
|
608
|
+
if (property.type === "Enum") {
|
|
609
|
+
const enumProp = property;
|
|
610
|
+
if (Array.isArray(enumProp.enum) && enumProp.enum.length > 0) {
|
|
611
|
+
const typeName = `${schema.name}${propName.charAt(0).toUpperCase() + propName.slice(1)}`;
|
|
612
|
+
const values = enumProp.enum.map(
|
|
613
|
+
(v) => typeof v === "string" ? v : v.value
|
|
614
|
+
);
|
|
615
|
+
const displayName = resolveDisplayName2(enumProp.displayName, options);
|
|
616
|
+
typeAliases.push({
|
|
617
|
+
name: typeName,
|
|
618
|
+
type: values.map((v) => `'${v}'`).join(" | "),
|
|
619
|
+
comment: displayName ?? `${schema.name} ${propName} enum`
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (property.type === "Select") {
|
|
624
|
+
const selectProp = property;
|
|
625
|
+
if (selectProp.options && selectProp.options.length > 0) {
|
|
626
|
+
const typeName = `${schema.name}${propName.charAt(0).toUpperCase() + propName.slice(1)}`;
|
|
627
|
+
const displayName = resolveDisplayName2(selectProp.displayName, options);
|
|
628
|
+
typeAliases.push({
|
|
629
|
+
name: typeName,
|
|
630
|
+
type: selectProp.options.map((v) => `'${v}'`).join(" | "),
|
|
631
|
+
comment: displayName ?? `${schema.name} ${propName} options`
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return typeAliases;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// src/validation-templates.ts
|
|
641
|
+
var DEFAULT_VALIDATION_TEMPLATES = {
|
|
642
|
+
required: {
|
|
643
|
+
ja: "${displayName}\u306F\u5FC5\u9808\u3067\u3059",
|
|
644
|
+
en: "${displayName} is required",
|
|
645
|
+
vi: "${displayName} l\xE0 b\u1EAFt bu\u1ED9c",
|
|
646
|
+
ko: "${displayName}\uC740(\uB294) \uD544\uC218\uC785\uB2C8\uB2E4",
|
|
647
|
+
zh: "${displayName}\u4E3A\u5FC5\u586B\u9879"
|
|
648
|
+
},
|
|
649
|
+
minLength: {
|
|
650
|
+
ja: "${displayName}\u306F${min}\u6587\u5B57\u4EE5\u4E0A\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
651
|
+
en: "${displayName} must be at least ${min} characters",
|
|
652
|
+
vi: "${displayName} ph\u1EA3i c\xF3 \xEDt nh\u1EA5t ${min} k\xFD t\u1EF1",
|
|
653
|
+
ko: "${displayName}\uC740(\uB294) ${min}\uC790 \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4",
|
|
654
|
+
zh: "${displayName}\u81F3\u5C11\u9700\u8981${min}\u4E2A\u5B57\u7B26"
|
|
655
|
+
},
|
|
656
|
+
maxLength: {
|
|
657
|
+
ja: "${displayName}\u306F${max}\u6587\u5B57\u4EE5\u5185\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
658
|
+
en: "${displayName} must be at most ${max} characters",
|
|
659
|
+
vi: "${displayName} t\u1ED1i \u0111a ${max} k\xFD t\u1EF1",
|
|
660
|
+
ko: "${displayName}\uC740(\uB294) ${max}\uC790 \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4",
|
|
661
|
+
zh: "${displayName}\u4E0D\u80FD\u8D85\u8FC7${max}\u4E2A\u5B57\u7B26"
|
|
662
|
+
},
|
|
663
|
+
min: {
|
|
664
|
+
ja: "${displayName}\u306F${min}\u4EE5\u4E0A\u306E\u5024\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
665
|
+
en: "${displayName} must be at least ${min}",
|
|
666
|
+
vi: "${displayName} ph\u1EA3i l\u1EDBn h\u01A1n ho\u1EB7c b\u1EB1ng ${min}",
|
|
667
|
+
ko: "${displayName}\uC740(\uB294) ${min} \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4",
|
|
668
|
+
zh: "${displayName}\u4E0D\u80FD\u5C0F\u4E8E${min}"
|
|
669
|
+
},
|
|
670
|
+
max: {
|
|
671
|
+
ja: "${displayName}\u306F${max}\u4EE5\u4E0B\u306E\u5024\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
672
|
+
en: "${displayName} must be at most ${max}",
|
|
673
|
+
vi: "${displayName} ph\u1EA3i nh\u1ECF h\u01A1n ho\u1EB7c b\u1EB1ng ${max}",
|
|
674
|
+
ko: "${displayName}\uC740(\uB294) ${max} \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4",
|
|
675
|
+
zh: "${displayName}\u4E0D\u80FD\u5927\u4E8E${max}"
|
|
676
|
+
},
|
|
677
|
+
email: {
|
|
678
|
+
ja: "${displayName}\u306E\u5F62\u5F0F\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093",
|
|
679
|
+
en: "${displayName} is not a valid email address",
|
|
680
|
+
vi: "${displayName} kh\xF4ng ph\u1EA3i l\xE0 \u0111\u1ECBa ch\u1EC9 email h\u1EE3p l\u1EC7",
|
|
681
|
+
ko: "${displayName} \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
|
|
682
|
+
zh: "${displayName}\u4E0D\u662F\u6709\u6548\u7684\u90AE\u7BB1\u5730\u5740"
|
|
683
|
+
},
|
|
684
|
+
url: {
|
|
685
|
+
ja: "${displayName}\u306F\u6709\u52B9\u306AURL\u3067\u306F\u3042\u308A\u307E\u305B\u3093",
|
|
686
|
+
en: "${displayName} is not a valid URL",
|
|
687
|
+
vi: "${displayName} kh\xF4ng ph\u1EA3i l\xE0 URL h\u1EE3p l\u1EC7",
|
|
688
|
+
ko: "${displayName}\uC740(\uB294) \uC720\uD6A8\uD55C URL\uC774 \uC544\uB2D9\uB2C8\uB2E4",
|
|
689
|
+
zh: "${displayName}\u4E0D\u662F\u6709\u6548\u7684URL"
|
|
690
|
+
},
|
|
691
|
+
pattern: {
|
|
692
|
+
ja: "${displayName}\u306E\u5F62\u5F0F\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093",
|
|
693
|
+
en: "${displayName} format is invalid",
|
|
694
|
+
vi: "${displayName} kh\xF4ng \u0111\xFAng \u0111\u1ECBnh d\u1EA1ng",
|
|
695
|
+
ko: "${displayName} \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
|
|
696
|
+
zh: "${displayName}\u683C\u5F0F\u4E0D\u6B63\u786E"
|
|
697
|
+
},
|
|
698
|
+
enum: {
|
|
699
|
+
ja: "${displayName}\u306E\u5024\u304C\u7121\u52B9\u3067\u3059",
|
|
700
|
+
en: "${displayName} has an invalid value",
|
|
701
|
+
vi: "${displayName} c\xF3 gi\xE1 tr\u1ECB kh\xF4ng h\u1EE3p l\u1EC7",
|
|
702
|
+
ko: "${displayName} \uAC12\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
|
|
703
|
+
zh: "${displayName}\u7684\u503C\u65E0\u6548"
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
function mergeValidationTemplates(userTemplates) {
|
|
707
|
+
if (!userTemplates) {
|
|
708
|
+
return DEFAULT_VALIDATION_TEMPLATES;
|
|
709
|
+
}
|
|
710
|
+
const merged = {
|
|
711
|
+
required: { ...DEFAULT_VALIDATION_TEMPLATES.required },
|
|
712
|
+
minLength: { ...DEFAULT_VALIDATION_TEMPLATES.minLength },
|
|
713
|
+
maxLength: { ...DEFAULT_VALIDATION_TEMPLATES.maxLength },
|
|
714
|
+
min: { ...DEFAULT_VALIDATION_TEMPLATES.min },
|
|
715
|
+
max: { ...DEFAULT_VALIDATION_TEMPLATES.max },
|
|
716
|
+
email: { ...DEFAULT_VALIDATION_TEMPLATES.email },
|
|
717
|
+
url: { ...DEFAULT_VALIDATION_TEMPLATES.url },
|
|
718
|
+
pattern: { ...DEFAULT_VALIDATION_TEMPLATES.pattern },
|
|
719
|
+
enum: { ...DEFAULT_VALIDATION_TEMPLATES.enum }
|
|
720
|
+
};
|
|
721
|
+
for (const [key, value] of Object.entries(userTemplates)) {
|
|
722
|
+
if (value && key in merged) {
|
|
723
|
+
merged[key] = {
|
|
724
|
+
...merged[key],
|
|
725
|
+
...value
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return merged;
|
|
730
|
+
}
|
|
731
|
+
function formatValidationMessage(template, vars) {
|
|
732
|
+
let result = template;
|
|
733
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
734
|
+
result = result.replace(new RegExp(`\\$\\{${key}\\}`, "g"), String(value));
|
|
735
|
+
}
|
|
736
|
+
return result;
|
|
737
|
+
}
|
|
738
|
+
function getValidationMessages(templates, ruleType, locales, vars, fallbackLocale) {
|
|
739
|
+
const ruleTemplates = templates[ruleType];
|
|
740
|
+
const messages = {};
|
|
741
|
+
for (const locale of locales) {
|
|
742
|
+
const template = ruleTemplates[locale] ?? (fallbackLocale ? ruleTemplates[fallbackLocale] : void 0) ?? ruleTemplates["en"] ?? "";
|
|
743
|
+
messages[locale] = formatValidationMessage(template, vars);
|
|
744
|
+
}
|
|
745
|
+
return messages;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// src/rules-generator.ts
|
|
749
|
+
function getMultiLocaleDisplayName(value, locales, fallbackLocale, defaultValue) {
|
|
750
|
+
if (!value) {
|
|
751
|
+
const result2 = {};
|
|
752
|
+
for (const locale of locales) {
|
|
753
|
+
result2[locale] = defaultValue;
|
|
754
|
+
}
|
|
755
|
+
return result2;
|
|
756
|
+
}
|
|
757
|
+
if (typeof value === "string") {
|
|
758
|
+
const result2 = {};
|
|
759
|
+
for (const locale of locales) {
|
|
760
|
+
result2[locale] = value;
|
|
761
|
+
}
|
|
762
|
+
return result2;
|
|
763
|
+
}
|
|
764
|
+
const result = {};
|
|
765
|
+
for (const locale of locales) {
|
|
766
|
+
result[locale] = value[locale] ?? value[fallbackLocale] ?? value["en"] ?? defaultValue;
|
|
767
|
+
}
|
|
768
|
+
return result;
|
|
769
|
+
}
|
|
770
|
+
function generatePropertyRules(propName, property, displayName, locales, fallbackLocale, templates) {
|
|
771
|
+
const rules = [];
|
|
772
|
+
const propDef = property;
|
|
773
|
+
if (!propDef.nullable) {
|
|
774
|
+
rules.push({
|
|
775
|
+
required: true,
|
|
776
|
+
message: getValidationMessages(templates, "required", locales, { displayName: "${displayName}" }, fallbackLocale)
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
if (property.type === "Email") {
|
|
780
|
+
rules.push({
|
|
781
|
+
type: "email",
|
|
782
|
+
message: getValidationMessages(templates, "email", locales, { displayName: "${displayName}" }, fallbackLocale)
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
if (property.type === "String" || property.type === "Text" || property.type === "LongText") {
|
|
786
|
+
if (propDef.minLength) {
|
|
787
|
+
rules.push({
|
|
788
|
+
min: propDef.minLength,
|
|
789
|
+
message: getValidationMessages(templates, "minLength", locales, { displayName: "${displayName}", min: propDef.minLength }, fallbackLocale)
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
if (propDef.maxLength || propDef.length) {
|
|
793
|
+
const max = propDef.maxLength ?? propDef.length;
|
|
794
|
+
rules.push({
|
|
795
|
+
max,
|
|
796
|
+
message: getValidationMessages(templates, "maxLength", locales, { displayName: "${displayName}", max }, fallbackLocale)
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if (property.type === "Int" || property.type === "BigInt" || property.type === "Float") {
|
|
801
|
+
if (propDef.min !== void 0) {
|
|
802
|
+
rules.push({
|
|
803
|
+
type: property.type === "Float" ? "number" : "integer",
|
|
804
|
+
min: propDef.min,
|
|
805
|
+
message: getValidationMessages(templates, "min", locales, { displayName: "${displayName}", min: propDef.min }, fallbackLocale)
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
if (propDef.max !== void 0) {
|
|
809
|
+
rules.push({
|
|
810
|
+
type: property.type === "Float" ? "number" : "integer",
|
|
811
|
+
max: propDef.max,
|
|
812
|
+
message: getValidationMessages(templates, "max", locales, { displayName: "${displayName}", max: propDef.max }, fallbackLocale)
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
if (propDef.pattern) {
|
|
817
|
+
rules.push({
|
|
818
|
+
pattern: propDef.pattern,
|
|
819
|
+
message: getValidationMessages(templates, "pattern", locales, { displayName: "${displayName}" }, fallbackLocale)
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
for (const rule of rules) {
|
|
823
|
+
const newMessage = {};
|
|
824
|
+
for (const locale of locales) {
|
|
825
|
+
const msg = rule.message[locale];
|
|
826
|
+
if (msg) {
|
|
827
|
+
newMessage[locale] = msg.replace(/\$\{displayName\}/g, displayName[locale] ?? propName);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
rule.message = newMessage;
|
|
831
|
+
}
|
|
832
|
+
return rules;
|
|
833
|
+
}
|
|
834
|
+
function generateModelRules(schema, locales, fallbackLocale, templates) {
|
|
835
|
+
const modelDisplayName = getMultiLocaleDisplayName(
|
|
836
|
+
schema.displayName,
|
|
837
|
+
locales,
|
|
838
|
+
fallbackLocale,
|
|
839
|
+
schema.name
|
|
840
|
+
);
|
|
841
|
+
const properties = {};
|
|
842
|
+
if (schema.properties) {
|
|
843
|
+
for (const [propName, property] of Object.entries(schema.properties)) {
|
|
844
|
+
const propDef = property;
|
|
845
|
+
const displayName = getMultiLocaleDisplayName(
|
|
846
|
+
propDef.displayName,
|
|
847
|
+
locales,
|
|
848
|
+
fallbackLocale,
|
|
849
|
+
propName
|
|
850
|
+
);
|
|
851
|
+
properties[propName] = {
|
|
852
|
+
displayName,
|
|
853
|
+
rules: generatePropertyRules(propName, property, displayName, locales, fallbackLocale, templates)
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
return {
|
|
858
|
+
displayName: modelDisplayName,
|
|
859
|
+
properties
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
function formatRulesFile(schemaName, rules) {
|
|
863
|
+
const parts = [];
|
|
864
|
+
parts.push(`/**
|
|
865
|
+
* Auto-generated validation rules and metadata for ${schemaName}.
|
|
866
|
+
* DO NOT EDIT - This file is automatically generated and will be overwritten.
|
|
867
|
+
*/
|
|
868
|
+
|
|
869
|
+
import type { LocaleMap, ValidationRule } from '../common.js';
|
|
870
|
+
|
|
871
|
+
`);
|
|
872
|
+
parts.push(`/** Display name for ${schemaName} */
|
|
873
|
+
`);
|
|
874
|
+
parts.push(`export const ${schemaName}DisplayName: LocaleMap = ${JSON.stringify(rules.displayName, null, 2)};
|
|
875
|
+
|
|
876
|
+
`);
|
|
877
|
+
parts.push(`/** Property display names for ${schemaName} */
|
|
878
|
+
`);
|
|
879
|
+
parts.push(`export const ${schemaName}PropertyDisplayNames: Record<string, LocaleMap> = {
|
|
880
|
+
`);
|
|
881
|
+
for (const [propName, propRules] of Object.entries(rules.properties)) {
|
|
882
|
+
parts.push(` ${propName}: ${JSON.stringify(propRules.displayName)},
|
|
883
|
+
`);
|
|
884
|
+
}
|
|
885
|
+
parts.push(`};
|
|
886
|
+
|
|
887
|
+
`);
|
|
888
|
+
parts.push(`/** Validation rules for ${schemaName} (Ant Design compatible) */
|
|
889
|
+
`);
|
|
890
|
+
parts.push(`export const ${schemaName}Rules: Record<string, ValidationRule[]> = {
|
|
891
|
+
`);
|
|
892
|
+
for (const [propName, propRules] of Object.entries(rules.properties)) {
|
|
893
|
+
if (propRules.rules.length > 0) {
|
|
894
|
+
parts.push(` ${propName}: [
|
|
895
|
+
`);
|
|
896
|
+
for (const rule of propRules.rules) {
|
|
897
|
+
const ruleObj = {};
|
|
898
|
+
if (rule.required) ruleObj.required = true;
|
|
899
|
+
if (rule.type) ruleObj.type = `'${rule.type}'`;
|
|
900
|
+
if (rule.min !== void 0) ruleObj.min = rule.min;
|
|
901
|
+
if (rule.max !== void 0) ruleObj.max = rule.max;
|
|
902
|
+
if (rule.pattern) ruleObj.pattern = `/${rule.pattern}/`;
|
|
903
|
+
ruleObj.message = rule.message;
|
|
904
|
+
const ruleStr = Object.entries(ruleObj).map(([k, v]) => {
|
|
905
|
+
if (k === "type") return `${k}: ${v}`;
|
|
906
|
+
if (k === "pattern") return `${k}: ${v}`;
|
|
907
|
+
return `${k}: ${JSON.stringify(v)}`;
|
|
908
|
+
}).join(", ");
|
|
909
|
+
parts.push(` { ${ruleStr} },
|
|
910
|
+
`);
|
|
911
|
+
}
|
|
912
|
+
parts.push(` ],
|
|
913
|
+
`);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
parts.push(`};
|
|
917
|
+
|
|
918
|
+
`);
|
|
919
|
+
parts.push(`/** Get validation rules with messages for a specific locale */
|
|
920
|
+
`);
|
|
921
|
+
parts.push(`export function get${schemaName}Rules(locale: string): Record<string, Array<{ required?: boolean; type?: string; min?: number; max?: number; pattern?: RegExp; message: string }>> {
|
|
922
|
+
const result: Record<string, Array<{ required?: boolean; type?: string; min?: number; max?: number; pattern?: RegExp; message: string }>> = {};
|
|
923
|
+
for (const [prop, rules] of Object.entries(${schemaName}Rules)) {
|
|
924
|
+
result[prop] = rules.map(rule => ({
|
|
925
|
+
...rule,
|
|
926
|
+
message: rule.message[locale] ?? rule.message['en'] ?? '',
|
|
927
|
+
}));
|
|
928
|
+
}
|
|
929
|
+
return result;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
`);
|
|
933
|
+
parts.push(`/** Get display name for a specific locale */
|
|
934
|
+
`);
|
|
935
|
+
parts.push(`export function get${schemaName}DisplayName(locale: string): string {
|
|
936
|
+
return ${schemaName}DisplayName[locale] ?? ${schemaName}DisplayName['en'] ?? '${schemaName}';
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
`);
|
|
940
|
+
parts.push(`/** Get property display name for a specific locale */
|
|
941
|
+
`);
|
|
942
|
+
parts.push(`export function get${schemaName}PropertyDisplayName(property: string, locale: string): string {
|
|
943
|
+
const names = ${schemaName}PropertyDisplayNames[property];
|
|
944
|
+
return names?.[locale] ?? names?.['en'] ?? property;
|
|
945
|
+
}
|
|
946
|
+
`);
|
|
947
|
+
return parts.join("");
|
|
948
|
+
}
|
|
949
|
+
function generateRulesFiles(schemas, options = {}) {
|
|
950
|
+
const files = [];
|
|
951
|
+
const localeConfig = options.localeConfig;
|
|
952
|
+
const locales = [...localeConfig?.locales ?? ["en"]];
|
|
953
|
+
const fallbackLocale = localeConfig?.fallbackLocale ?? "en";
|
|
954
|
+
const templates = mergeValidationTemplates(options.validationTemplates);
|
|
955
|
+
for (const schema of Object.values(schemas)) {
|
|
956
|
+
if (schema.kind === "enum") continue;
|
|
957
|
+
if (schema.options?.hidden === true) continue;
|
|
958
|
+
const rules = generateModelRules(schema, locales, fallbackLocale, templates);
|
|
959
|
+
const content = formatRulesFile(schema.name, rules);
|
|
960
|
+
files.push({
|
|
961
|
+
filePath: `rules/${schema.name}.rules.ts`,
|
|
962
|
+
content,
|
|
963
|
+
types: [`${schema.name}Rules`, `${schema.name}DisplayName`],
|
|
964
|
+
overwrite: true
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
return files;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// src/zod-generator.ts
|
|
971
|
+
function getMultiLocaleDisplayName2(value, locales, fallbackLocale, defaultValue) {
|
|
972
|
+
if (!value) {
|
|
973
|
+
const result2 = {};
|
|
974
|
+
for (const locale of locales) {
|
|
975
|
+
result2[locale] = defaultValue;
|
|
976
|
+
}
|
|
977
|
+
return result2;
|
|
978
|
+
}
|
|
979
|
+
if (typeof value === "string") {
|
|
980
|
+
const result2 = {};
|
|
981
|
+
for (const locale of locales) {
|
|
982
|
+
result2[locale] = value;
|
|
983
|
+
}
|
|
984
|
+
return result2;
|
|
985
|
+
}
|
|
986
|
+
const result = {};
|
|
987
|
+
for (const locale of locales) {
|
|
988
|
+
result[locale] = value[locale] ?? value[fallbackLocale] ?? value["en"] ?? defaultValue;
|
|
989
|
+
}
|
|
990
|
+
return result;
|
|
991
|
+
}
|
|
992
|
+
function getZodSchemaForType(propDef, fieldName, customTypes) {
|
|
993
|
+
const def = propDef;
|
|
994
|
+
const isNullable = def.nullable ?? false;
|
|
995
|
+
let schema = "";
|
|
996
|
+
if (customTypes) {
|
|
997
|
+
const customType = customTypes.get(propDef.type);
|
|
998
|
+
if (customType && !customType.compound) {
|
|
999
|
+
const sqlType = customType.sql?.sqlType || "VARCHAR";
|
|
1000
|
+
schema = "z.string()";
|
|
1001
|
+
if (customType.sql?.length) {
|
|
1002
|
+
schema += `.max(${customType.sql.length})`;
|
|
1003
|
+
}
|
|
1004
|
+
if (isNullable) {
|
|
1005
|
+
schema += ".optional().nullable()";
|
|
1006
|
+
}
|
|
1007
|
+
return schema;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
switch (propDef.type) {
|
|
1011
|
+
case "String":
|
|
1012
|
+
case "Text":
|
|
1013
|
+
case "MediumText":
|
|
1014
|
+
case "LongText":
|
|
1015
|
+
case "Password":
|
|
1016
|
+
schema = "z.string()";
|
|
1017
|
+
if (!isNullable) {
|
|
1018
|
+
schema += ".min(1)";
|
|
1019
|
+
}
|
|
1020
|
+
if (def.maxLength || def.length) {
|
|
1021
|
+
schema += `.max(${def.maxLength ?? def.length})`;
|
|
1022
|
+
}
|
|
1023
|
+
if (def.minLength && def.minLength > 1) {
|
|
1024
|
+
schema = schema.replace(".min(1)", `.min(${def.minLength})`);
|
|
1025
|
+
}
|
|
1026
|
+
break;
|
|
1027
|
+
case "Email":
|
|
1028
|
+
schema = "z.string().email()";
|
|
1029
|
+
if (def.maxLength || def.length) {
|
|
1030
|
+
schema += `.max(${def.maxLength ?? def.length ?? 255})`;
|
|
1031
|
+
}
|
|
1032
|
+
break;
|
|
1033
|
+
case "TinyInt":
|
|
1034
|
+
case "Int":
|
|
1035
|
+
case "BigInt":
|
|
1036
|
+
schema = "z.number().int()";
|
|
1037
|
+
if (def.min !== void 0) {
|
|
1038
|
+
schema += `.gte(${def.min})`;
|
|
1039
|
+
}
|
|
1040
|
+
if (def.max !== void 0) {
|
|
1041
|
+
schema += `.lte(${def.max})`;
|
|
1042
|
+
}
|
|
1043
|
+
break;
|
|
1044
|
+
case "Float":
|
|
1045
|
+
schema = "z.number()";
|
|
1046
|
+
if (def.min !== void 0) {
|
|
1047
|
+
schema += `.gte(${def.min})`;
|
|
1048
|
+
}
|
|
1049
|
+
if (def.max !== void 0) {
|
|
1050
|
+
schema += `.lte(${def.max})`;
|
|
1051
|
+
}
|
|
1052
|
+
break;
|
|
1053
|
+
case "Boolean":
|
|
1054
|
+
schema = "z.boolean()";
|
|
1055
|
+
break;
|
|
1056
|
+
case "Date":
|
|
1057
|
+
schema = "z.string().date()";
|
|
1058
|
+
break;
|
|
1059
|
+
case "DateTime":
|
|
1060
|
+
case "Timestamp":
|
|
1061
|
+
schema = "z.string().datetime({ offset: true })";
|
|
1062
|
+
break;
|
|
1063
|
+
case "Time":
|
|
1064
|
+
schema = "z.string().time()";
|
|
1065
|
+
break;
|
|
1066
|
+
case "Json":
|
|
1067
|
+
schema = "z.unknown()";
|
|
1068
|
+
break;
|
|
1069
|
+
case "EnumRef":
|
|
1070
|
+
if (typeof def.enum === "string") {
|
|
1071
|
+
schema = `z.nativeEnum(${def.enum})`;
|
|
1072
|
+
} else {
|
|
1073
|
+
schema = "z.string()";
|
|
1074
|
+
}
|
|
1075
|
+
break;
|
|
1076
|
+
case "Enum":
|
|
1077
|
+
if (typeof def.enum === "string") {
|
|
1078
|
+
schema = `${def.enum}Schema`;
|
|
1079
|
+
} else if (Array.isArray(def.enum)) {
|
|
1080
|
+
const values = def.enum.map((v) => `'${v}'`).join(", ");
|
|
1081
|
+
schema = `z.enum([${values}])`;
|
|
1082
|
+
} else {
|
|
1083
|
+
schema = "z.string()";
|
|
1084
|
+
}
|
|
1085
|
+
break;
|
|
1086
|
+
case "Select":
|
|
1087
|
+
if (def.options && def.options.length > 0) {
|
|
1088
|
+
const values = def.options.map((v) => `'${v}'`).join(", ");
|
|
1089
|
+
schema = `z.enum([${values}])`;
|
|
1090
|
+
} else {
|
|
1091
|
+
schema = "z.string()";
|
|
1092
|
+
}
|
|
1093
|
+
break;
|
|
1094
|
+
case "Lookup":
|
|
1095
|
+
schema = "z.number().int().positive()";
|
|
1096
|
+
break;
|
|
1097
|
+
case "Association":
|
|
1098
|
+
return "";
|
|
1099
|
+
case "File":
|
|
1100
|
+
return "";
|
|
1101
|
+
default:
|
|
1102
|
+
schema = "z.string()";
|
|
1103
|
+
}
|
|
1104
|
+
if (isNullable && schema) {
|
|
1105
|
+
schema += ".optional().nullable()";
|
|
1106
|
+
}
|
|
1107
|
+
if (def.pattern && schema) {
|
|
1108
|
+
schema += `.regex(/${def.pattern}/)`;
|
|
1109
|
+
}
|
|
1110
|
+
return schema;
|
|
1111
|
+
}
|
|
1112
|
+
function generateCompoundTypeSchemas(propName, propDef, customType, options) {
|
|
1113
|
+
const schemas = [];
|
|
1114
|
+
const propFields = propDef.fields;
|
|
1115
|
+
const locales = options.localeConfig?.locales ?? ["en"];
|
|
1116
|
+
const fallbackLocale = options.localeConfig?.fallbackLocale ?? "en";
|
|
1117
|
+
if (!customType.expand) return schemas;
|
|
1118
|
+
for (const field of customType.expand) {
|
|
1119
|
+
const fieldName = `${toSnakeCase(propName)}_${toSnakeCase(field.suffix)}`;
|
|
1120
|
+
const fieldOverride = propFields?.[field.suffix];
|
|
1121
|
+
const isNullable = fieldOverride?.nullable ?? field.sql?.nullable ?? propDef.nullable ?? false;
|
|
1122
|
+
const pluginRules = field.rules;
|
|
1123
|
+
const overrideRules = fieldOverride?.rules;
|
|
1124
|
+
const length = fieldOverride?.length ?? overrideRules?.maxLength ?? pluginRules?.maxLength ?? field.sql?.length;
|
|
1125
|
+
const minLength = overrideRules?.minLength ?? pluginRules?.minLength;
|
|
1126
|
+
const pattern = overrideRules?.pattern ?? pluginRules?.pattern;
|
|
1127
|
+
const format = overrideRules?.format ?? pluginRules?.format;
|
|
1128
|
+
let schema = "z.string()";
|
|
1129
|
+
if (format === "email") {
|
|
1130
|
+
schema = "z.string().email()";
|
|
1131
|
+
} else if (format === "url") {
|
|
1132
|
+
schema = "z.string().url()";
|
|
1133
|
+
} else if (format === "phone") {
|
|
1134
|
+
schema = "z.string()";
|
|
1135
|
+
} else if (format === "postal_code") {
|
|
1136
|
+
schema = `z.string().regex(/^\\d{3}-?\\d{4}$/)`;
|
|
1137
|
+
}
|
|
1138
|
+
if (!isNullable) {
|
|
1139
|
+
const min = minLength ?? 1;
|
|
1140
|
+
schema += `.min(${min})`;
|
|
1141
|
+
} else if (minLength) {
|
|
1142
|
+
schema += `.min(${minLength})`;
|
|
1143
|
+
}
|
|
1144
|
+
if (length) {
|
|
1145
|
+
schema += `.max(${length})`;
|
|
1146
|
+
}
|
|
1147
|
+
if (pattern && !format) {
|
|
1148
|
+
schema += `.regex(/${pattern}/)`;
|
|
1149
|
+
}
|
|
1150
|
+
if (isNullable) {
|
|
1151
|
+
schema += ".optional().nullable()";
|
|
1152
|
+
}
|
|
1153
|
+
const propDisplayName = getMultiLocaleDisplayName2(
|
|
1154
|
+
propDef.displayName,
|
|
1155
|
+
locales,
|
|
1156
|
+
fallbackLocale,
|
|
1157
|
+
propName
|
|
1158
|
+
);
|
|
1159
|
+
schemas.push({
|
|
1160
|
+
fieldName,
|
|
1161
|
+
schema,
|
|
1162
|
+
inCreate: true,
|
|
1163
|
+
inUpdate: true,
|
|
1164
|
+
comment: `${propDisplayName["en"] ?? propName} (${field.suffix})`
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
return schemas;
|
|
1168
|
+
}
|
|
1169
|
+
function generateZodSchemas(schema, options) {
|
|
1170
|
+
const schemas = [];
|
|
1171
|
+
const customTypes = options.customTypes;
|
|
1172
|
+
if (!schema.properties) return schemas;
|
|
1173
|
+
for (const [propName, propDef] of Object.entries(schema.properties)) {
|
|
1174
|
+
if (customTypes) {
|
|
1175
|
+
const customType = customTypes.get(propDef.type);
|
|
1176
|
+
if (customType?.compound) {
|
|
1177
|
+
schemas.push(...generateCompoundTypeSchemas(propName, propDef, customType, options));
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
const zodSchema = getZodSchemaForType(propDef, propName, customTypes);
|
|
1182
|
+
if (!zodSchema) continue;
|
|
1183
|
+
const fieldName = toSnakeCase(propName);
|
|
1184
|
+
schemas.push({
|
|
1185
|
+
fieldName,
|
|
1186
|
+
schema: zodSchema,
|
|
1187
|
+
inCreate: true,
|
|
1188
|
+
inUpdate: true,
|
|
1189
|
+
comment: void 0
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
return schemas;
|
|
1193
|
+
}
|
|
1194
|
+
function generateDisplayNames(schema, options) {
|
|
1195
|
+
const locales = options.localeConfig?.locales ?? ["en"];
|
|
1196
|
+
const fallbackLocale = options.localeConfig?.fallbackLocale ?? "en";
|
|
1197
|
+
const customTypes = options.customTypes;
|
|
1198
|
+
const displayName = getMultiLocaleDisplayName2(
|
|
1199
|
+
schema.displayName,
|
|
1200
|
+
locales,
|
|
1201
|
+
fallbackLocale,
|
|
1202
|
+
schema.name
|
|
1203
|
+
);
|
|
1204
|
+
const propertyDisplayNames = {};
|
|
1205
|
+
const propertyPlaceholders = {};
|
|
1206
|
+
if (schema.properties) {
|
|
1207
|
+
for (const [propName, propDef] of Object.entries(schema.properties)) {
|
|
1208
|
+
const prop = propDef;
|
|
1209
|
+
const fieldName = toSnakeCase(propName);
|
|
1210
|
+
if (customTypes) {
|
|
1211
|
+
const customType = customTypes.get(propDef.type);
|
|
1212
|
+
if (customType?.compound && customType.expand) {
|
|
1213
|
+
if (prop.displayName) {
|
|
1214
|
+
propertyDisplayNames[fieldName] = getMultiLocaleDisplayName2(
|
|
1215
|
+
prop.displayName,
|
|
1216
|
+
locales,
|
|
1217
|
+
fallbackLocale,
|
|
1218
|
+
propName
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
for (const field of customType.expand) {
|
|
1222
|
+
const expandedFieldName = `${fieldName}_${toSnakeCase(field.suffix)}`;
|
|
1223
|
+
const fieldOverride = prop.fields?.[field.suffix];
|
|
1224
|
+
const labelSource = fieldOverride?.displayName ?? field.label;
|
|
1225
|
+
if (labelSource) {
|
|
1226
|
+
propertyDisplayNames[expandedFieldName] = getMultiLocaleDisplayName2(
|
|
1227
|
+
labelSource,
|
|
1228
|
+
locales,
|
|
1229
|
+
fallbackLocale,
|
|
1230
|
+
field.suffix
|
|
1231
|
+
);
|
|
1232
|
+
} else {
|
|
1233
|
+
propertyDisplayNames[expandedFieldName] = getMultiLocaleDisplayName2(
|
|
1234
|
+
prop.displayName,
|
|
1235
|
+
locales,
|
|
1236
|
+
fallbackLocale,
|
|
1237
|
+
propName
|
|
1238
|
+
);
|
|
1239
|
+
for (const locale of locales) {
|
|
1240
|
+
propertyDisplayNames[expandedFieldName] = {
|
|
1241
|
+
...propertyDisplayNames[expandedFieldName],
|
|
1242
|
+
[locale]: `${propertyDisplayNames[expandedFieldName][locale]} (${field.suffix})`
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
const placeholderSource = fieldOverride?.placeholder ?? field.placeholder;
|
|
1247
|
+
if (placeholderSource) {
|
|
1248
|
+
propertyPlaceholders[expandedFieldName] = getMultiLocaleDisplayName2(
|
|
1249
|
+
placeholderSource,
|
|
1250
|
+
locales,
|
|
1251
|
+
fallbackLocale,
|
|
1252
|
+
""
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
continue;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
propertyDisplayNames[fieldName] = getMultiLocaleDisplayName2(
|
|
1260
|
+
prop.displayName,
|
|
1261
|
+
locales,
|
|
1262
|
+
fallbackLocale,
|
|
1263
|
+
propName
|
|
1264
|
+
);
|
|
1265
|
+
if (prop.placeholder) {
|
|
1266
|
+
propertyPlaceholders[fieldName] = getMultiLocaleDisplayName2(
|
|
1267
|
+
prop.placeholder,
|
|
1268
|
+
locales,
|
|
1269
|
+
fallbackLocale,
|
|
1270
|
+
""
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
return { displayName, propertyDisplayNames, propertyPlaceholders };
|
|
1276
|
+
}
|
|
1277
|
+
function getExcludedFields(schema, customTypes) {
|
|
1278
|
+
const createExclude = /* @__PURE__ */ new Set();
|
|
1279
|
+
const updateExclude = /* @__PURE__ */ new Set();
|
|
1280
|
+
if (schema.options?.id !== false) {
|
|
1281
|
+
createExclude.add("id");
|
|
1282
|
+
updateExclude.add("id");
|
|
1283
|
+
}
|
|
1284
|
+
if (schema.options?.timestamps !== false) {
|
|
1285
|
+
createExclude.add("created_at");
|
|
1286
|
+
createExclude.add("updated_at");
|
|
1287
|
+
updateExclude.add("created_at");
|
|
1288
|
+
updateExclude.add("updated_at");
|
|
1289
|
+
}
|
|
1290
|
+
if (schema.options?.softDelete) {
|
|
1291
|
+
createExclude.add("deleted_at");
|
|
1292
|
+
updateExclude.add("deleted_at");
|
|
1293
|
+
}
|
|
1294
|
+
if (schema.properties) {
|
|
1295
|
+
if (schema.properties["emailVerifiedAt"] || schema.properties["email_verified_at"]) {
|
|
1296
|
+
createExclude.add("email_verified_at");
|
|
1297
|
+
updateExclude.add("email_verified_at");
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
if (schema.properties && customTypes) {
|
|
1301
|
+
for (const [propName, propDef] of Object.entries(schema.properties)) {
|
|
1302
|
+
const customType = customTypes.get(propDef.type);
|
|
1303
|
+
if (customType?.accessors) {
|
|
1304
|
+
for (const accessor of customType.accessors) {
|
|
1305
|
+
const fieldName = `${toSnakeCase(propName)}_${toSnakeCase(accessor.name)}`;
|
|
1306
|
+
createExclude.add(fieldName);
|
|
1307
|
+
updateExclude.add(fieldName);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
return { create: createExclude, update: updateExclude };
|
|
1313
|
+
}
|
|
1314
|
+
function formatZodSchemasSection(schemaName, zodSchemas, displayNames, excludedFields) {
|
|
1315
|
+
const parts = [];
|
|
1316
|
+
const lowerName = schemaName.charAt(0).toLowerCase() + schemaName.slice(1);
|
|
1317
|
+
parts.push(`// ============================================================================
|
|
1318
|
+
`);
|
|
1319
|
+
parts.push(`// I18n (Internationalization)
|
|
1320
|
+
`);
|
|
1321
|
+
parts.push(`// ============================================================================
|
|
1322
|
+
|
|
1323
|
+
`);
|
|
1324
|
+
parts.push(`/**
|
|
1325
|
+
`);
|
|
1326
|
+
parts.push(` * Unified i18n object for ${schemaName}
|
|
1327
|
+
`);
|
|
1328
|
+
parts.push(` * Contains model label and all field labels/placeholders
|
|
1329
|
+
`);
|
|
1330
|
+
parts.push(` */
|
|
1331
|
+
`);
|
|
1332
|
+
parts.push(`export const ${lowerName}I18n = {
|
|
1333
|
+
`);
|
|
1334
|
+
parts.push(` /** Model display name */
|
|
1335
|
+
`);
|
|
1336
|
+
parts.push(` label: ${JSON.stringify(displayNames.displayName)},
|
|
1337
|
+
`);
|
|
1338
|
+
parts.push(` /** Field labels and placeholders */
|
|
1339
|
+
`);
|
|
1340
|
+
parts.push(` fields: {
|
|
1341
|
+
`);
|
|
1342
|
+
for (const [propName, labelMap] of Object.entries(displayNames.propertyDisplayNames)) {
|
|
1343
|
+
const placeholderMap = displayNames.propertyPlaceholders[propName];
|
|
1344
|
+
parts.push(` ${propName}: {
|
|
1345
|
+
`);
|
|
1346
|
+
parts.push(` label: ${JSON.stringify(labelMap)},
|
|
1347
|
+
`);
|
|
1348
|
+
if (placeholderMap) {
|
|
1349
|
+
parts.push(` placeholder: ${JSON.stringify(placeholderMap)},
|
|
1350
|
+
`);
|
|
1351
|
+
}
|
|
1352
|
+
parts.push(` },
|
|
1353
|
+
`);
|
|
1354
|
+
}
|
|
1355
|
+
parts.push(` },
|
|
1356
|
+
`);
|
|
1357
|
+
parts.push(`} as const;
|
|
1358
|
+
|
|
1359
|
+
`);
|
|
1360
|
+
parts.push(`// ============================================================================
|
|
1361
|
+
`);
|
|
1362
|
+
parts.push(`// Zod Schemas
|
|
1363
|
+
`);
|
|
1364
|
+
parts.push(`// ============================================================================
|
|
1365
|
+
|
|
1366
|
+
`);
|
|
1367
|
+
parts.push(`/** Field schemas for ${schemaName} */
|
|
1368
|
+
`);
|
|
1369
|
+
parts.push(`export const base${schemaName}Schemas = {
|
|
1370
|
+
`);
|
|
1371
|
+
for (const prop of zodSchemas) {
|
|
1372
|
+
if (prop.comment) {
|
|
1373
|
+
parts.push(` /** ${prop.comment} */
|
|
1374
|
+
`);
|
|
1375
|
+
}
|
|
1376
|
+
parts.push(` ${prop.fieldName}: ${prop.schema},
|
|
1377
|
+
`);
|
|
1378
|
+
}
|
|
1379
|
+
parts.push(`} as const;
|
|
1380
|
+
|
|
1381
|
+
`);
|
|
1382
|
+
const createFields = zodSchemas.filter((p) => p.inCreate && !excludedFields.create.has(p.fieldName));
|
|
1383
|
+
parts.push(`/** Create schema for ${schemaName} (POST requests) */
|
|
1384
|
+
`);
|
|
1385
|
+
parts.push(`export const base${schemaName}CreateSchema = z.object({
|
|
1386
|
+
`);
|
|
1387
|
+
for (const prop of createFields) {
|
|
1388
|
+
parts.push(` ${prop.fieldName}: base${schemaName}Schemas.${prop.fieldName},
|
|
1389
|
+
`);
|
|
1390
|
+
}
|
|
1391
|
+
parts.push(`});
|
|
1392
|
+
|
|
1393
|
+
`);
|
|
1394
|
+
parts.push(`/** Update schema for ${schemaName} (PUT/PATCH requests) */
|
|
1395
|
+
`);
|
|
1396
|
+
parts.push(`export const base${schemaName}UpdateSchema = base${schemaName}CreateSchema.partial();
|
|
1397
|
+
|
|
1398
|
+
`);
|
|
1399
|
+
parts.push(`// ============================================================================
|
|
1400
|
+
`);
|
|
1401
|
+
parts.push(`// Inferred Types
|
|
1402
|
+
`);
|
|
1403
|
+
parts.push(`// ============================================================================
|
|
1404
|
+
|
|
1405
|
+
`);
|
|
1406
|
+
parts.push(`export type Base${schemaName}Create = z.infer<typeof base${schemaName}CreateSchema>;
|
|
1407
|
+
`);
|
|
1408
|
+
parts.push(`export type Base${schemaName}Update = z.infer<typeof base${schemaName}UpdateSchema>;
|
|
1409
|
+
|
|
1410
|
+
`);
|
|
1411
|
+
parts.push(`// ============================================================================
|
|
1412
|
+
`);
|
|
1413
|
+
parts.push(`// I18n Helper Functions
|
|
1414
|
+
`);
|
|
1415
|
+
parts.push(`// ============================================================================
|
|
1416
|
+
|
|
1417
|
+
`);
|
|
1418
|
+
parts.push(`/** Get model label for a specific locale */
|
|
1419
|
+
`);
|
|
1420
|
+
parts.push(`export function get${schemaName}Label(locale: string): string {
|
|
1421
|
+
`);
|
|
1422
|
+
parts.push(` return ${lowerName}I18n.label[locale as keyof typeof ${lowerName}I18n.label] ?? ${lowerName}I18n.label['en'] ?? '${schemaName}';
|
|
1423
|
+
`);
|
|
1424
|
+
parts.push(`}
|
|
1425
|
+
|
|
1426
|
+
`);
|
|
1427
|
+
parts.push(`/** Get field label for a specific locale */
|
|
1428
|
+
`);
|
|
1429
|
+
parts.push(`export function get${schemaName}FieldLabel(field: string, locale: string): string {
|
|
1430
|
+
`);
|
|
1431
|
+
parts.push(` const fieldI18n = ${lowerName}I18n.fields[field as keyof typeof ${lowerName}I18n.fields];
|
|
1432
|
+
`);
|
|
1433
|
+
parts.push(` if (!fieldI18n) return field;
|
|
1434
|
+
`);
|
|
1435
|
+
parts.push(` return fieldI18n.label[locale as keyof typeof fieldI18n.label] ?? fieldI18n.label['en'] ?? field;
|
|
1436
|
+
`);
|
|
1437
|
+
parts.push(`}
|
|
1438
|
+
|
|
1439
|
+
`);
|
|
1440
|
+
parts.push(`/** Get field placeholder for a specific locale */
|
|
1441
|
+
`);
|
|
1442
|
+
parts.push(`export function get${schemaName}FieldPlaceholder(field: string, locale: string): string {
|
|
1443
|
+
`);
|
|
1444
|
+
parts.push(` const fieldI18n = ${lowerName}I18n.fields[field as keyof typeof ${lowerName}I18n.fields];
|
|
1445
|
+
`);
|
|
1446
|
+
parts.push(` if (!fieldI18n || !('placeholder' in fieldI18n)) return '';
|
|
1447
|
+
`);
|
|
1448
|
+
parts.push(` const placeholder = fieldI18n.placeholder as Record<string, string>;
|
|
1449
|
+
`);
|
|
1450
|
+
parts.push(` return placeholder[locale] ?? placeholder['en'] ?? '';
|
|
1451
|
+
`);
|
|
1452
|
+
parts.push(`}
|
|
1453
|
+
`);
|
|
1454
|
+
return parts.join("");
|
|
1455
|
+
}
|
|
1456
|
+
function formatZodModelFile(schemaName, ext = "") {
|
|
1457
|
+
const lowerName = schemaName.charAt(0).toLowerCase() + schemaName.slice(1);
|
|
1458
|
+
return `/**
|
|
1459
|
+
* ${schemaName} Model
|
|
1460
|
+
*
|
|
1461
|
+
* This file extends the auto-generated base interface.
|
|
1462
|
+
* You can add custom methods, computed properties, or override types/schemas here.
|
|
1463
|
+
* This file will NOT be overwritten by the generator.
|
|
1464
|
+
*/
|
|
1465
|
+
|
|
1466
|
+
import { z } from 'zod';
|
|
1467
|
+
import type { ${schemaName} as ${schemaName}Base } from './base/${schemaName}${ext}';
|
|
1468
|
+
import {
|
|
1469
|
+
base${schemaName}Schemas,
|
|
1470
|
+
base${schemaName}CreateSchema,
|
|
1471
|
+
base${schemaName}UpdateSchema,
|
|
1472
|
+
${lowerName}I18n,
|
|
1473
|
+
get${schemaName}Label,
|
|
1474
|
+
get${schemaName}FieldLabel,
|
|
1475
|
+
get${schemaName}FieldPlaceholder,
|
|
1476
|
+
} from './base/${schemaName}${ext}';
|
|
1477
|
+
|
|
1478
|
+
// ============================================================================
|
|
1479
|
+
// Types (extend or re-export)
|
|
1480
|
+
// ============================================================================
|
|
1481
|
+
|
|
1482
|
+
export interface ${schemaName} extends ${schemaName}Base {
|
|
1483
|
+
// Add custom properties here
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
// ============================================================================
|
|
1487
|
+
// Schemas (extend or re-export)
|
|
1488
|
+
// ============================================================================
|
|
1489
|
+
|
|
1490
|
+
export const ${lowerName}Schemas = { ...base${schemaName}Schemas };
|
|
1491
|
+
export const ${lowerName}CreateSchema = base${schemaName}CreateSchema;
|
|
1492
|
+
export const ${lowerName}UpdateSchema = base${schemaName}UpdateSchema;
|
|
1493
|
+
|
|
1494
|
+
// ============================================================================
|
|
1495
|
+
// Types
|
|
1496
|
+
// ============================================================================
|
|
1497
|
+
|
|
1498
|
+
export type ${schemaName}Create = z.infer<typeof ${lowerName}CreateSchema>;
|
|
1499
|
+
export type ${schemaName}Update = z.infer<typeof ${lowerName}UpdateSchema>;
|
|
1500
|
+
|
|
1501
|
+
// Re-export i18n and helpers
|
|
1502
|
+
export {
|
|
1503
|
+
${lowerName}I18n,
|
|
1504
|
+
get${schemaName}Label,
|
|
1505
|
+
get${schemaName}FieldLabel,
|
|
1506
|
+
get${schemaName}FieldPlaceholder,
|
|
1507
|
+
};
|
|
1508
|
+
|
|
1509
|
+
// Re-export base type for internal use
|
|
1510
|
+
export type { ${schemaName}Base };
|
|
1511
|
+
`;
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
// src/generator.ts
|
|
1515
|
+
var DEFAULT_OPTIONS = {
|
|
1516
|
+
readonly: false,
|
|
1517
|
+
// Changed: interfaces should be mutable for forms/mutations
|
|
1518
|
+
strictNullChecks: true,
|
|
1519
|
+
generateZodSchemas: true,
|
|
1520
|
+
// Generate Zod schemas by default
|
|
1521
|
+
generateRules: false,
|
|
1522
|
+
// Legacy Ant Design rules (deprecated, ignored when generateZodSchemas=true)
|
|
1523
|
+
useJsExtension: false
|
|
1524
|
+
// Bundlers (Vite, webpack) don't need .js extension
|
|
1525
|
+
};
|
|
1526
|
+
function getImportExt(options) {
|
|
1527
|
+
return options.useJsExtension ? ".js" : "";
|
|
1528
|
+
}
|
|
1529
|
+
function generateBaseHeader() {
|
|
1530
|
+
return `/**
|
|
1531
|
+
* Auto-generated TypeScript types from Omnify schemas.
|
|
1532
|
+
* DO NOT EDIT - This file is automatically generated and will be overwritten.
|
|
1533
|
+
*/
|
|
1534
|
+
|
|
1535
|
+
`;
|
|
1536
|
+
}
|
|
1537
|
+
function generateModelHeader(schemaName) {
|
|
1538
|
+
return `/**
|
|
1539
|
+
* ${schemaName} Model
|
|
1540
|
+
*
|
|
1541
|
+
* This file extends the auto-generated base interface.
|
|
1542
|
+
* You can add custom methods, computed properties, or override types here.
|
|
1543
|
+
* This file will NOT be overwritten by the generator.
|
|
1544
|
+
*/
|
|
1545
|
+
|
|
1546
|
+
`;
|
|
1547
|
+
}
|
|
1548
|
+
function getComputedFields(schema, customTypes) {
|
|
1549
|
+
const computedFields = [];
|
|
1550
|
+
if (!schema.properties) return computedFields;
|
|
1551
|
+
for (const [propName, propDef] of Object.entries(schema.properties)) {
|
|
1552
|
+
const snakeName = toSnakeCase(propName);
|
|
1553
|
+
const customType = customTypes?.get(propDef.type);
|
|
1554
|
+
if (customType?.accessors) {
|
|
1555
|
+
for (const accessor of customType.accessors) {
|
|
1556
|
+
computedFields.push(`${snakeName}_${toSnakeCase(accessor.name)}`);
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
return computedFields;
|
|
1561
|
+
}
|
|
1562
|
+
function generateUtilityTypes(schemaName, schema, customTypes) {
|
|
1563
|
+
const parts = [];
|
|
1564
|
+
const excludeFields = [];
|
|
1565
|
+
if (schema.options?.id !== false) {
|
|
1566
|
+
excludeFields.push("'id'");
|
|
1567
|
+
}
|
|
1568
|
+
if (schema.options?.timestamps !== false) {
|
|
1569
|
+
excludeFields.push("'created_at'", "'updated_at'");
|
|
1570
|
+
}
|
|
1571
|
+
if (schema.options?.softDelete) {
|
|
1572
|
+
excludeFields.push("'deleted_at'");
|
|
1573
|
+
}
|
|
1574
|
+
if (schema.properties?.["emailVerifiedAt"] || schema.properties?.["email_verified_at"]) {
|
|
1575
|
+
excludeFields.push("'email_verified_at'");
|
|
1576
|
+
}
|
|
1577
|
+
const computedFields = getComputedFields(schema, customTypes);
|
|
1578
|
+
for (const field of computedFields) {
|
|
1579
|
+
excludeFields.push(`'${field}'`);
|
|
1580
|
+
}
|
|
1581
|
+
const omitType = excludeFields.length > 0 ? `Omit<${schemaName}, ${excludeFields.join(" | ")}>` : schemaName;
|
|
1582
|
+
parts.push(`
|
|
1583
|
+
/** For creating new ${schemaName} (POST requests) */`);
|
|
1584
|
+
parts.push(`
|
|
1585
|
+
export type ${schemaName}Create = ${omitType};
|
|
1586
|
+
`);
|
|
1587
|
+
parts.push(`
|
|
1588
|
+
/** For updating ${schemaName} (PUT/PATCH requests) */`);
|
|
1589
|
+
parts.push(`
|
|
1590
|
+
export type ${schemaName}Update = Partial<${schemaName}Create>;
|
|
1591
|
+
`);
|
|
1592
|
+
return parts.join("");
|
|
1593
|
+
}
|
|
1594
|
+
function needsDateTimeImports(iface) {
|
|
1595
|
+
let dateTime = false;
|
|
1596
|
+
let date = false;
|
|
1597
|
+
for (const prop of iface.properties) {
|
|
1598
|
+
if (prop.type === "DateTimeString" || prop.type.includes("DateTimeString")) {
|
|
1599
|
+
dateTime = true;
|
|
1600
|
+
}
|
|
1601
|
+
if (prop.type === "DateString" || prop.type.includes("DateString")) {
|
|
1602
|
+
date = true;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
return { dateTime, date };
|
|
1606
|
+
}
|
|
1607
|
+
function generateBaseInterfaceFile(schemaName, schemas, options) {
|
|
1608
|
+
const interfaces = generateInterfaces(schemas, options);
|
|
1609
|
+
const iface = interfaces.find((i) => i.name === schemaName);
|
|
1610
|
+
const schema = schemas[schemaName];
|
|
1611
|
+
if (!iface || !schema) {
|
|
1612
|
+
throw new Error(`Interface not found for schema: ${schemaName}`);
|
|
1613
|
+
}
|
|
1614
|
+
const parts = [generateBaseHeader()];
|
|
1615
|
+
if (options.generateZodSchemas) {
|
|
1616
|
+
parts.push(`import { z } from 'zod';
|
|
1617
|
+
`);
|
|
1618
|
+
}
|
|
1619
|
+
const dateImports = needsDateTimeImports(iface);
|
|
1620
|
+
const commonImports = [];
|
|
1621
|
+
if (dateImports.dateTime) commonImports.push("DateTimeString");
|
|
1622
|
+
if (dateImports.date) commonImports.push("DateString");
|
|
1623
|
+
const ext = getImportExt(options);
|
|
1624
|
+
if (commonImports.length > 0) {
|
|
1625
|
+
parts.push(`import type { ${commonImports.join(", ")} } from '../common${ext}';
|
|
1626
|
+
`);
|
|
1627
|
+
}
|
|
1628
|
+
if (iface.enumDependencies && iface.enumDependencies.length > 0) {
|
|
1629
|
+
const enumPrefix = options.enumImportPrefix ? `../${options.enumImportPrefix}` : "../enum";
|
|
1630
|
+
const pluginEnumNames = new Set(
|
|
1631
|
+
options.pluginEnums ? Array.from(options.pluginEnums.keys()) : []
|
|
1632
|
+
);
|
|
1633
|
+
for (const enumName of iface.enumDependencies) {
|
|
1634
|
+
const enumPath = pluginEnumNames.has(enumName) ? `${enumPrefix}/plugin/${enumName}${ext}` : `${enumPrefix}/${enumName}${ext}`;
|
|
1635
|
+
parts.push(`import { ${enumName} } from '${enumPath}';
|
|
1636
|
+
`);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
if (iface.dependencies && iface.dependencies.length > 0) {
|
|
1640
|
+
for (const dep of iface.dependencies) {
|
|
1641
|
+
parts.push(`import type { ${dep} } from './${dep}${ext}';
|
|
1642
|
+
`);
|
|
1643
|
+
}
|
|
1644
|
+
parts.push("\n");
|
|
1645
|
+
} else if (commonImports.length > 0 || options.generateZodSchemas || iface.enumDependencies && iface.enumDependencies.length > 0) {
|
|
1646
|
+
parts.push("\n");
|
|
1647
|
+
}
|
|
1648
|
+
parts.push(formatInterface(iface));
|
|
1649
|
+
parts.push("\n");
|
|
1650
|
+
if (options.generateZodSchemas) {
|
|
1651
|
+
const zodSchemas = generateZodSchemas(schema, options);
|
|
1652
|
+
const displayNames = generateDisplayNames(schema, options);
|
|
1653
|
+
const excludedFields = getExcludedFields(schema, options.customTypes);
|
|
1654
|
+
parts.push("\n");
|
|
1655
|
+
parts.push(formatZodSchemasSection(schemaName, zodSchemas, displayNames, excludedFields));
|
|
1656
|
+
} else {
|
|
1657
|
+
parts.push(generateUtilityTypes(schemaName, schema, options.customTypes));
|
|
1658
|
+
}
|
|
1659
|
+
return {
|
|
1660
|
+
filePath: `base/${schemaName}.ts`,
|
|
1661
|
+
content: parts.join(""),
|
|
1662
|
+
types: [schemaName, `${schemaName}Create`, `${schemaName}Update`],
|
|
1663
|
+
overwrite: true
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
function generateEnumFile(enumDef, isPluginEnum = false) {
|
|
1667
|
+
const parts = [generateBaseHeader()];
|
|
1668
|
+
parts.push(formatEnum(enumDef));
|
|
1669
|
+
parts.push("\n");
|
|
1670
|
+
const filePath = isPluginEnum ? `plugin/${enumDef.name}.ts` : `${enumDef.name}.ts`;
|
|
1671
|
+
return {
|
|
1672
|
+
filePath,
|
|
1673
|
+
content: parts.join(""),
|
|
1674
|
+
types: [enumDef.name],
|
|
1675
|
+
overwrite: true,
|
|
1676
|
+
category: "enum"
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
function generateTypeAliasFile(alias) {
|
|
1680
|
+
const parts = [generateBaseHeader()];
|
|
1681
|
+
parts.push(formatTypeAlias(alias));
|
|
1682
|
+
parts.push("\n");
|
|
1683
|
+
return {
|
|
1684
|
+
filePath: `${alias.name}.ts`,
|
|
1685
|
+
content: parts.join(""),
|
|
1686
|
+
types: [alias.name],
|
|
1687
|
+
overwrite: true,
|
|
1688
|
+
category: "enum"
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
function generateModelFile(schemaName, options) {
|
|
1692
|
+
if (options.generateZodSchemas) {
|
|
1693
|
+
return {
|
|
1694
|
+
filePath: `${schemaName}.ts`,
|
|
1695
|
+
content: formatZodModelFile(schemaName, getImportExt(options)),
|
|
1696
|
+
types: [schemaName],
|
|
1697
|
+
overwrite: false
|
|
1698
|
+
// Never overwrite user models
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
const parts = [generateModelHeader(schemaName)];
|
|
1702
|
+
const ext = getImportExt(options);
|
|
1703
|
+
parts.push(`import type { ${schemaName} as ${schemaName}Base } from './base/${schemaName}${ext}';
|
|
1704
|
+
|
|
1705
|
+
`);
|
|
1706
|
+
parts.push(`/**
|
|
1707
|
+
* ${schemaName} model interface.
|
|
1708
|
+
* Add custom properties or methods here.
|
|
1709
|
+
*/
|
|
1710
|
+
`);
|
|
1711
|
+
parts.push(`export interface ${schemaName} extends ${schemaName}Base {
|
|
1712
|
+
`);
|
|
1713
|
+
parts.push(` // Add custom properties here
|
|
1714
|
+
`);
|
|
1715
|
+
parts.push(`}
|
|
1716
|
+
|
|
1717
|
+
`);
|
|
1718
|
+
parts.push(`// Re-export base type for internal use
|
|
1719
|
+
`);
|
|
1720
|
+
parts.push(`export type { ${schemaName}Base };
|
|
1721
|
+
`);
|
|
1722
|
+
return {
|
|
1723
|
+
filePath: `${schemaName}.ts`,
|
|
1724
|
+
content: parts.join(""),
|
|
1725
|
+
types: [schemaName],
|
|
1726
|
+
overwrite: false
|
|
1727
|
+
// Never overwrite user models
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
var DEFAULT_VALIDATION_MESSAGES = {
|
|
1731
|
+
required: {
|
|
1732
|
+
en: "${displayName} is required",
|
|
1733
|
+
ja: "${displayName}\u306F\u5FC5\u9808\u3067\u3059",
|
|
1734
|
+
vi: "${displayName} l\xE0 b\u1EAFt bu\u1ED9c",
|
|
1735
|
+
ko: "${displayName}\uC740(\uB294) \uD544\uC218\uC785\uB2C8\uB2E4",
|
|
1736
|
+
"zh-CN": "${displayName}\u662F\u5FC5\u586B\u9879",
|
|
1737
|
+
"zh-TW": "${displayName}\u70BA\u5FC5\u586B\u6B04\u4F4D",
|
|
1738
|
+
th: "${displayName} \u0E08\u0E33\u0E40\u0E1B\u0E47\u0E19\u0E15\u0E49\u0E2D\u0E07\u0E01\u0E23\u0E2D\u0E01",
|
|
1739
|
+
es: "${displayName} es obligatorio"
|
|
1740
|
+
},
|
|
1741
|
+
minLength: {
|
|
1742
|
+
en: "${displayName} must be at least ${min} characters",
|
|
1743
|
+
ja: "${displayName}\u306F${min}\u6587\u5B57\u4EE5\u4E0A\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
1744
|
+
vi: "${displayName} ph\u1EA3i c\xF3 \xEDt nh\u1EA5t ${min} k\xFD t\u1EF1",
|
|
1745
|
+
ko: "${displayName}\uC740(\uB294) ${min}\uC790 \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4",
|
|
1746
|
+
"zh-CN": "${displayName}\u81F3\u5C11\u9700\u8981${min}\u4E2A\u5B57\u7B26",
|
|
1747
|
+
"zh-TW": "${displayName}\u81F3\u5C11\u9700\u8981${min}\u500B\u5B57\u5143",
|
|
1748
|
+
th: "${displayName} \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E2D\u0E22\u0E48\u0E32\u0E07\u0E19\u0E49\u0E2D\u0E22 ${min} \u0E15\u0E31\u0E27\u0E2D\u0E31\u0E01\u0E29\u0E23",
|
|
1749
|
+
es: "${displayName} debe tener al menos ${min} caracteres"
|
|
1750
|
+
},
|
|
1751
|
+
maxLength: {
|
|
1752
|
+
en: "${displayName} must be at most ${max} characters",
|
|
1753
|
+
ja: "${displayName}\u306F${max}\u6587\u5B57\u4EE5\u5185\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
1754
|
+
vi: "${displayName} kh\xF4ng \u0111\u01B0\u1EE3c qu\xE1 ${max} k\xFD t\u1EF1",
|
|
1755
|
+
ko: "${displayName}\uC740(\uB294) ${max}\uC790 \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4",
|
|
1756
|
+
"zh-CN": "${displayName}\u6700\u591A${max}\u4E2A\u5B57\u7B26",
|
|
1757
|
+
"zh-TW": "${displayName}\u6700\u591A${max}\u500B\u5B57\u5143",
|
|
1758
|
+
th: "${displayName} \u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${max} \u0E15\u0E31\u0E27\u0E2D\u0E31\u0E01\u0E29\u0E23",
|
|
1759
|
+
es: "${displayName} debe tener como m\xE1ximo ${max} caracteres"
|
|
1760
|
+
},
|
|
1761
|
+
min: {
|
|
1762
|
+
en: "${displayName} must be at least ${min}",
|
|
1763
|
+
ja: "${displayName}\u306F${min}\u4EE5\u4E0A\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
1764
|
+
vi: "${displayName} ph\u1EA3i l\u1EDBn h\u01A1n ho\u1EB7c b\u1EB1ng ${min}",
|
|
1765
|
+
ko: "${displayName}\uC740(\uB294) ${min} \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4",
|
|
1766
|
+
"zh-CN": "${displayName}\u5FC5\u987B\u5927\u4E8E\u7B49\u4E8E${min}",
|
|
1767
|
+
"zh-TW": "${displayName}\u5FC5\u9808\u5927\u65BC\u7B49\u65BC${min}",
|
|
1768
|
+
th: "${displayName} \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E32\u0E01\u0E01\u0E27\u0E48\u0E32\u0E2B\u0E23\u0E37\u0E2D\u0E40\u0E17\u0E48\u0E32\u0E01\u0E31\u0E1A ${min}",
|
|
1769
|
+
es: "${displayName} debe ser al menos ${min}"
|
|
1770
|
+
},
|
|
1771
|
+
max: {
|
|
1772
|
+
en: "${displayName} must be at most ${max}",
|
|
1773
|
+
ja: "${displayName}\u306F${max}\u4EE5\u4E0B\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
1774
|
+
vi: "${displayName} ph\u1EA3i nh\u1ECF h\u01A1n ho\u1EB7c b\u1EB1ng ${max}",
|
|
1775
|
+
ko: "${displayName}\uC740(\uB294) ${max} \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4",
|
|
1776
|
+
"zh-CN": "${displayName}\u5FC5\u987B\u5C0F\u4E8E\u7B49\u4E8E${max}",
|
|
1777
|
+
"zh-TW": "${displayName}\u5FC5\u9808\u5C0F\u65BC\u7B49\u65BC${max}",
|
|
1778
|
+
th: "${displayName} \u0E15\u0E49\u0E2D\u0E07\u0E19\u0E49\u0E2D\u0E22\u0E01\u0E27\u0E48\u0E32\u0E2B\u0E23\u0E37\u0E2D\u0E40\u0E17\u0E48\u0E32\u0E01\u0E31\u0E1A ${max}",
|
|
1779
|
+
es: "${displayName} debe ser como m\xE1ximo ${max}"
|
|
1780
|
+
},
|
|
1781
|
+
email: {
|
|
1782
|
+
en: "Please enter a valid email address",
|
|
1783
|
+
ja: "\u6709\u52B9\u306A\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
1784
|
+
vi: "Vui l\xF2ng nh\u1EADp \u0111\u1ECBa ch\u1EC9 email h\u1EE3p l\u1EC7",
|
|
1785
|
+
ko: "\uC720\uD6A8\uD55C \uC774\uBA54\uC77C \uC8FC\uC18C\uB97C \uC785\uB825\uD558\uC138\uC694",
|
|
1786
|
+
"zh-CN": "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u7535\u5B50\u90AE\u4EF6\u5730\u5740",
|
|
1787
|
+
"zh-TW": "\u8ACB\u8F38\u5165\u6709\u6548\u7684\u96FB\u5B50\u90F5\u4EF6\u5730\u5740",
|
|
1788
|
+
th: "\u0E01\u0E23\u0E38\u0E13\u0E32\u0E01\u0E23\u0E2D\u0E01\u0E2D\u0E35\u0E40\u0E21\u0E25\u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07",
|
|
1789
|
+
es: "Por favor, introduce una direcci\xF3n de correo electr\xF3nico v\xE1lida"
|
|
1790
|
+
},
|
|
1791
|
+
url: {
|
|
1792
|
+
en: "Please enter a valid URL",
|
|
1793
|
+
ja: "\u6709\u52B9\u306AURL\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
1794
|
+
vi: "Vui l\xF2ng nh\u1EADp URL h\u1EE3p l\u1EC7",
|
|
1795
|
+
ko: "\uC720\uD6A8\uD55C URL\uC744 \uC785\uB825\uD558\uC138\uC694",
|
|
1796
|
+
"zh-CN": "\u8BF7\u8F93\u5165\u6709\u6548\u7684URL",
|
|
1797
|
+
"zh-TW": "\u8ACB\u8F38\u5165\u6709\u6548\u7684\u7DB2\u5740",
|
|
1798
|
+
th: "\u0E01\u0E23\u0E38\u0E13\u0E32\u0E01\u0E23\u0E2D\u0E01 URL \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07",
|
|
1799
|
+
es: "Por favor, introduce una URL v\xE1lida"
|
|
1800
|
+
},
|
|
1801
|
+
pattern: {
|
|
1802
|
+
en: "${displayName} format is invalid",
|
|
1803
|
+
ja: "${displayName}\u306E\u5F62\u5F0F\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093",
|
|
1804
|
+
vi: "${displayName} kh\xF4ng \u0111\xFAng \u0111\u1ECBnh d\u1EA1ng",
|
|
1805
|
+
ko: "${displayName} \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
|
|
1806
|
+
"zh-CN": "${displayName}\u683C\u5F0F\u4E0D\u6B63\u786E",
|
|
1807
|
+
"zh-TW": "${displayName}\u683C\u5F0F\u4E0D\u6B63\u78BA",
|
|
1808
|
+
th: "\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A${displayName}\u0E44\u0E21\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07",
|
|
1809
|
+
es: "El formato de ${displayName} no es v\xE1lido"
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1812
|
+
function generateCommonFile(options) {
|
|
1813
|
+
const locales = options.localeConfig?.locales ?? ["ja", "en"];
|
|
1814
|
+
const localeUnion = locales.map((l) => `'${l}'`).join(" | ");
|
|
1815
|
+
const content = `${generateBaseHeader()}
|
|
1816
|
+
/**
|
|
1817
|
+
* Locale map for multi-language support.
|
|
1818
|
+
*/
|
|
1819
|
+
export interface LocaleMap {
|
|
1820
|
+
[locale: string]: string;
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
/**
|
|
1824
|
+
* Supported locales in this project.
|
|
1825
|
+
*/
|
|
1826
|
+
export type Locale = ${localeUnion};
|
|
1827
|
+
|
|
1828
|
+
/**
|
|
1829
|
+
* Validation rule with multi-locale messages.
|
|
1830
|
+
* Use get{Model}Rules(locale) to get Ant Design compatible rules with string messages.
|
|
1831
|
+
*/
|
|
1832
|
+
export interface ValidationRule {
|
|
1833
|
+
required?: boolean;
|
|
1834
|
+
type?: 'string' | 'number' | 'email' | 'url' | 'integer' | 'array' | 'object';
|
|
1835
|
+
min?: number;
|
|
1836
|
+
max?: number;
|
|
1837
|
+
len?: number;
|
|
1838
|
+
pattern?: RegExp;
|
|
1839
|
+
message: LocaleMap;
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
/**
|
|
1843
|
+
* ISO 8601 date-time string.
|
|
1844
|
+
*/
|
|
1845
|
+
export type DateTimeString = string;
|
|
1846
|
+
|
|
1847
|
+
/**
|
|
1848
|
+
* ISO 8601 date string (YYYY-MM-DD).
|
|
1849
|
+
*/
|
|
1850
|
+
export type DateString = string;
|
|
1851
|
+
`;
|
|
1852
|
+
return {
|
|
1853
|
+
filePath: "common.ts",
|
|
1854
|
+
content,
|
|
1855
|
+
types: ["LocaleMap", "Locale", "ValidationRule", "DateTimeString", "DateString"],
|
|
1856
|
+
overwrite: true
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
function generateI18nFile(options) {
|
|
1860
|
+
const locales = options.localeConfig?.locales ?? ["ja", "en"];
|
|
1861
|
+
const defaultLocale = options.localeConfig?.defaultLocale ?? "ja";
|
|
1862
|
+
const fallbackLocale = options.localeConfig?.fallbackLocale ?? "en";
|
|
1863
|
+
const userMessages = options.localeConfig?.messages ?? {};
|
|
1864
|
+
const mergedMessages = {};
|
|
1865
|
+
for (const [key, defaultMsgs] of Object.entries(DEFAULT_VALIDATION_MESSAGES)) {
|
|
1866
|
+
mergedMessages[key] = {};
|
|
1867
|
+
for (const locale of locales) {
|
|
1868
|
+
if (defaultMsgs[locale]) {
|
|
1869
|
+
mergedMessages[key][locale] = defaultMsgs[locale];
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
for (const [key, userMsgs] of Object.entries(userMessages)) {
|
|
1874
|
+
if (userMsgs) {
|
|
1875
|
+
if (!mergedMessages[key]) {
|
|
1876
|
+
mergedMessages[key] = {};
|
|
1877
|
+
}
|
|
1878
|
+
for (const [locale, msg] of Object.entries(userMsgs)) {
|
|
1879
|
+
mergedMessages[key][locale] = msg;
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
const messagesJson = JSON.stringify(mergedMessages, null, 2);
|
|
1884
|
+
const ext = getImportExt(options);
|
|
1885
|
+
const content = `${generateBaseHeader()}
|
|
1886
|
+
import type { LocaleMap } from './common${ext}';
|
|
1887
|
+
|
|
1888
|
+
/**
|
|
1889
|
+
* Default locale for this project.
|
|
1890
|
+
*/
|
|
1891
|
+
export const defaultLocale = '${defaultLocale}' as const;
|
|
1892
|
+
|
|
1893
|
+
/**
|
|
1894
|
+
* Fallback locale when requested locale is not found.
|
|
1895
|
+
*/
|
|
1896
|
+
export const fallbackLocale = '${fallbackLocale}' as const;
|
|
1897
|
+
|
|
1898
|
+
/**
|
|
1899
|
+
* Supported locales in this project.
|
|
1900
|
+
*/
|
|
1901
|
+
export const supportedLocales = ${JSON.stringify(locales)} as const;
|
|
1902
|
+
|
|
1903
|
+
/**
|
|
1904
|
+
* Validation messages for all supported locales.
|
|
1905
|
+
* Use getMessage(key, locale, params) to get formatted message.
|
|
1906
|
+
*/
|
|
1907
|
+
export const validationMessages = ${messagesJson} as const;
|
|
1908
|
+
|
|
1909
|
+
/**
|
|
1910
|
+
* Get validation message for a specific key and locale.
|
|
1911
|
+
* Supports template placeholders: \${displayName}, \${min}, \${max}, etc.
|
|
1912
|
+
*
|
|
1913
|
+
* @param key - Message key (e.g., 'required', 'minLength')
|
|
1914
|
+
* @param locale - Locale code (e.g., 'ja', 'en')
|
|
1915
|
+
* @param params - Template parameters to replace
|
|
1916
|
+
* @returns Formatted message string
|
|
1917
|
+
*
|
|
1918
|
+
* @example
|
|
1919
|
+
* getMessage('required', 'ja', { displayName: '\u6C0F\u540D' })
|
|
1920
|
+
* // => '\u6C0F\u540D\u306F\u5FC5\u9808\u3067\u3059'
|
|
1921
|
+
*/
|
|
1922
|
+
export function getMessage(
|
|
1923
|
+
key: string,
|
|
1924
|
+
locale: string,
|
|
1925
|
+
params: Record<string, string | number> = {}
|
|
1926
|
+
): string {
|
|
1927
|
+
const messages = validationMessages[key as keyof typeof validationMessages];
|
|
1928
|
+
if (!messages) return key;
|
|
1929
|
+
|
|
1930
|
+
let message = (messages as LocaleMap)[locale]
|
|
1931
|
+
?? (messages as LocaleMap)[fallbackLocale]
|
|
1932
|
+
?? (messages as LocaleMap)[defaultLocale]
|
|
1933
|
+
?? key;
|
|
1934
|
+
|
|
1935
|
+
// Replace template placeholders
|
|
1936
|
+
for (const [param, value] of Object.entries(params)) {
|
|
1937
|
+
message = message.replace(new RegExp(\`\\\\$\\{\${param}\\}\`, 'g'), String(value));
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
return message;
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
/**
|
|
1944
|
+
* Get all validation messages for a specific locale.
|
|
1945
|
+
*
|
|
1946
|
+
* @param locale - Locale code
|
|
1947
|
+
* @returns Object with all messages for the locale
|
|
1948
|
+
*/
|
|
1949
|
+
export function getMessages(locale: string): Record<string, string> {
|
|
1950
|
+
const result: Record<string, string> = {};
|
|
1951
|
+
for (const [key, messages] of Object.entries(validationMessages)) {
|
|
1952
|
+
result[key] = (messages as LocaleMap)[locale]
|
|
1953
|
+
?? (messages as LocaleMap)[fallbackLocale]
|
|
1954
|
+
?? (messages as LocaleMap)[defaultLocale]
|
|
1955
|
+
?? key;
|
|
1956
|
+
}
|
|
1957
|
+
return result;
|
|
1958
|
+
}
|
|
1959
|
+
`;
|
|
1960
|
+
return {
|
|
1961
|
+
filePath: "i18n.ts",
|
|
1962
|
+
content,
|
|
1963
|
+
types: ["validationMessages", "getMessage", "getMessages"],
|
|
1964
|
+
overwrite: true
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
function generateIndexFile(schemas, enums, pluginEnums, typeAliases, options) {
|
|
1968
|
+
const parts = [generateBaseHeader()];
|
|
1969
|
+
const ext = getImportExt(options);
|
|
1970
|
+
parts.push(`// Common Types
|
|
1971
|
+
`);
|
|
1972
|
+
parts.push(`export type { LocaleMap, Locale, ValidationRule, DateTimeString, DateString } from './common${ext}';
|
|
1973
|
+
|
|
1974
|
+
`);
|
|
1975
|
+
parts.push(`// i18n (Internationalization)
|
|
1976
|
+
`);
|
|
1977
|
+
parts.push(`export {
|
|
1978
|
+
`);
|
|
1979
|
+
parts.push(` defaultLocale,
|
|
1980
|
+
`);
|
|
1981
|
+
parts.push(` fallbackLocale,
|
|
1982
|
+
`);
|
|
1983
|
+
parts.push(` supportedLocales,
|
|
1984
|
+
`);
|
|
1985
|
+
parts.push(` validationMessages,
|
|
1986
|
+
`);
|
|
1987
|
+
parts.push(` getMessage,
|
|
1988
|
+
`);
|
|
1989
|
+
parts.push(` getMessages,
|
|
1990
|
+
`);
|
|
1991
|
+
parts.push(`} from './i18n${ext}';
|
|
1992
|
+
|
|
1993
|
+
`);
|
|
1994
|
+
const enumPrefix = options.enumImportPrefix ?? "./enum";
|
|
1995
|
+
if (enums.length > 0) {
|
|
1996
|
+
parts.push(`// Schema Enums
|
|
1997
|
+
`);
|
|
1998
|
+
for (const enumDef of enums) {
|
|
1999
|
+
parts.push(`export {
|
|
2000
|
+
`);
|
|
2001
|
+
parts.push(` ${enumDef.name},
|
|
2002
|
+
`);
|
|
2003
|
+
parts.push(` ${enumDef.name}Values,
|
|
2004
|
+
`);
|
|
2005
|
+
parts.push(` is${enumDef.name},
|
|
2006
|
+
`);
|
|
2007
|
+
parts.push(` get${enumDef.name}Label,
|
|
2008
|
+
`);
|
|
2009
|
+
parts.push(` get${enumDef.name}Extra,
|
|
2010
|
+
`);
|
|
2011
|
+
parts.push(`} from '${enumPrefix}/${enumDef.name}${ext}';
|
|
2012
|
+
`);
|
|
2013
|
+
}
|
|
2014
|
+
parts.push("\n");
|
|
2015
|
+
}
|
|
2016
|
+
if (pluginEnums.length > 0) {
|
|
2017
|
+
parts.push(`// Plugin Enums
|
|
2018
|
+
`);
|
|
2019
|
+
for (const enumDef of pluginEnums) {
|
|
2020
|
+
parts.push(`export {
|
|
2021
|
+
`);
|
|
2022
|
+
parts.push(` ${enumDef.name},
|
|
2023
|
+
`);
|
|
2024
|
+
parts.push(` ${enumDef.name}Values,
|
|
2025
|
+
`);
|
|
2026
|
+
parts.push(` is${enumDef.name},
|
|
2027
|
+
`);
|
|
2028
|
+
parts.push(` get${enumDef.name}Label,
|
|
2029
|
+
`);
|
|
2030
|
+
parts.push(` get${enumDef.name}Extra,
|
|
2031
|
+
`);
|
|
2032
|
+
parts.push(`} from '${enumPrefix}/plugin/${enumDef.name}${ext}';
|
|
2033
|
+
`);
|
|
2034
|
+
}
|
|
2035
|
+
parts.push("\n");
|
|
2036
|
+
}
|
|
2037
|
+
if (typeAliases.length > 0) {
|
|
2038
|
+
parts.push(`// Inline Enums (Type Aliases)
|
|
2039
|
+
`);
|
|
2040
|
+
for (const alias of typeAliases) {
|
|
2041
|
+
parts.push(`export {
|
|
2042
|
+
`);
|
|
2043
|
+
parts.push(` type ${alias.name},
|
|
2044
|
+
`);
|
|
2045
|
+
parts.push(` ${alias.name}Values,
|
|
2046
|
+
`);
|
|
2047
|
+
parts.push(` is${alias.name},
|
|
2048
|
+
`);
|
|
2049
|
+
parts.push(` get${alias.name}Label,
|
|
2050
|
+
`);
|
|
2051
|
+
parts.push(` get${alias.name}Extra,
|
|
2052
|
+
`);
|
|
2053
|
+
parts.push(`} from '${enumPrefix}/${alias.name}${ext}';
|
|
2054
|
+
`);
|
|
2055
|
+
}
|
|
2056
|
+
parts.push("\n");
|
|
2057
|
+
}
|
|
2058
|
+
if (options.generateZodSchemas) {
|
|
2059
|
+
parts.push(`// Models (with Zod schemas, i18n, and Create/Update types)
|
|
2060
|
+
`);
|
|
2061
|
+
for (const schema of Object.values(schemas)) {
|
|
2062
|
+
if (schema.kind === "enum") continue;
|
|
2063
|
+
if (schema.options?.hidden === true) continue;
|
|
2064
|
+
const lowerName = schema.name.charAt(0).toLowerCase() + schema.name.slice(1);
|
|
2065
|
+
parts.push(`export type { ${schema.name}, ${schema.name}Create, ${schema.name}Update } from './${schema.name}${ext}';
|
|
2066
|
+
`);
|
|
2067
|
+
parts.push(`export {
|
|
2068
|
+
`);
|
|
2069
|
+
parts.push(` ${lowerName}Schemas,
|
|
2070
|
+
`);
|
|
2071
|
+
parts.push(` ${lowerName}CreateSchema,
|
|
2072
|
+
`);
|
|
2073
|
+
parts.push(` ${lowerName}UpdateSchema,
|
|
2074
|
+
`);
|
|
2075
|
+
parts.push(` ${lowerName}I18n,
|
|
2076
|
+
`);
|
|
2077
|
+
parts.push(` get${schema.name}Label,
|
|
2078
|
+
`);
|
|
2079
|
+
parts.push(` get${schema.name}FieldLabel,
|
|
2080
|
+
`);
|
|
2081
|
+
parts.push(` get${schema.name}FieldPlaceholder,
|
|
2082
|
+
`);
|
|
2083
|
+
parts.push(`} from './${schema.name}${ext}';
|
|
2084
|
+
`);
|
|
2085
|
+
}
|
|
2086
|
+
} else {
|
|
2087
|
+
parts.push(`// Models (with Create/Update utility types)
|
|
2088
|
+
`);
|
|
2089
|
+
for (const schema of Object.values(schemas)) {
|
|
2090
|
+
if (schema.kind === "enum") continue;
|
|
2091
|
+
if (schema.options?.hidden === true) continue;
|
|
2092
|
+
parts.push(`export type { ${schema.name} } from './${schema.name}${ext}';
|
|
2093
|
+
`);
|
|
2094
|
+
parts.push(`export type { ${schema.name}Create, ${schema.name}Update } from './base/${schema.name}${ext}';
|
|
2095
|
+
`);
|
|
2096
|
+
}
|
|
2097
|
+
if (options.generateRules) {
|
|
2098
|
+
parts.push(`
|
|
2099
|
+
// Validation Rules
|
|
2100
|
+
`);
|
|
2101
|
+
for (const schema of Object.values(schemas)) {
|
|
2102
|
+
if (schema.kind === "enum") continue;
|
|
2103
|
+
if (schema.options?.hidden === true) continue;
|
|
2104
|
+
parts.push(`export {
|
|
2105
|
+
`);
|
|
2106
|
+
parts.push(` get${schema.name}Rules,
|
|
2107
|
+
`);
|
|
2108
|
+
parts.push(` get${schema.name}DisplayName,
|
|
2109
|
+
`);
|
|
2110
|
+
parts.push(` get${schema.name}PropertyDisplayName,
|
|
2111
|
+
`);
|
|
2112
|
+
parts.push(`} from './rules/${schema.name}.rules${ext}';
|
|
2113
|
+
`);
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
return {
|
|
2118
|
+
filePath: "index.ts",
|
|
2119
|
+
content: parts.join(""),
|
|
2120
|
+
types: [],
|
|
2121
|
+
overwrite: true
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
function generateTypeScript(schemas, options = {}) {
|
|
2125
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
2126
|
+
const files = [];
|
|
2127
|
+
const schemasByName = /* @__PURE__ */ new Map();
|
|
2128
|
+
for (const schema of Object.values(schemas)) {
|
|
2129
|
+
const paths = schemasByName.get(schema.name) ?? [];
|
|
2130
|
+
paths.push(schema.relativePath ?? schema.filePath);
|
|
2131
|
+
schemasByName.set(schema.name, paths);
|
|
2132
|
+
}
|
|
2133
|
+
const duplicateSchemas = Array.from(schemasByName.entries()).filter(([, paths]) => paths.length > 1);
|
|
2134
|
+
if (duplicateSchemas.length > 0) {
|
|
2135
|
+
const errors = duplicateSchemas.map(
|
|
2136
|
+
([name, paths]) => ` - "${name}" defined in: ${paths.join(", ")}`
|
|
2137
|
+
).join("\n");
|
|
2138
|
+
throw new Error(
|
|
2139
|
+
`Duplicate schema/enum names detected. Names must be globally unique:
|
|
2140
|
+
${errors}
|
|
2141
|
+
Hint: Rename to unique names like "Blog${duplicateSchemas[0][0]}" or "Shop${duplicateSchemas[0][0]}"`
|
|
2142
|
+
);
|
|
2143
|
+
}
|
|
2144
|
+
if (opts.pluginEnums && opts.pluginEnums.size > 0) {
|
|
2145
|
+
const conflicts = [];
|
|
2146
|
+
for (const schema of Object.values(schemas)) {
|
|
2147
|
+
if (schema.kind === "enum" && opts.pluginEnums.has(schema.name)) {
|
|
2148
|
+
conflicts.push(`"${schema.name}" (schema: ${schema.relativePath ?? schema.filePath}, plugin enum)`);
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
if (conflicts.length > 0) {
|
|
2152
|
+
throw new Error(
|
|
2153
|
+
`Schema enum conflicts with plugin enum:
|
|
2154
|
+
- ${conflicts.join("\n - ")}
|
|
2155
|
+
Hint: Rename your schema enum to avoid conflict with plugin-provided enums`
|
|
2156
|
+
);
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
const enums = generateEnums(schemas, opts);
|
|
2160
|
+
for (const enumDef of enums) {
|
|
2161
|
+
files.push(generateEnumFile(enumDef, false));
|
|
2162
|
+
}
|
|
2163
|
+
if (opts.pluginEnums && opts.pluginEnums.size > 0) {
|
|
2164
|
+
const pluginEnums = generatePluginEnums(opts.pluginEnums, opts);
|
|
2165
|
+
for (const enumDef of pluginEnums) {
|
|
2166
|
+
files.push(generateEnumFile(enumDef, true));
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
const typeAliases = extractInlineEnums(schemas, opts);
|
|
2170
|
+
for (const alias of typeAliases) {
|
|
2171
|
+
files.push(generateTypeAliasFile(alias));
|
|
2172
|
+
}
|
|
2173
|
+
for (const schema of Object.values(schemas)) {
|
|
2174
|
+
if (schema.kind === "enum") continue;
|
|
2175
|
+
if (schema.options?.hidden === true) continue;
|
|
2176
|
+
files.push(generateBaseInterfaceFile(schema.name, schemas, opts));
|
|
2177
|
+
}
|
|
2178
|
+
for (const schema of Object.values(schemas)) {
|
|
2179
|
+
if (schema.kind === "enum") continue;
|
|
2180
|
+
if (schema.options?.hidden === true) continue;
|
|
2181
|
+
files.push(generateModelFile(schema.name, opts));
|
|
2182
|
+
}
|
|
2183
|
+
if (!opts.generateZodSchemas && opts.generateRules) {
|
|
2184
|
+
const rulesFiles = generateRulesFiles(schemas, opts);
|
|
2185
|
+
files.push(...rulesFiles);
|
|
2186
|
+
}
|
|
2187
|
+
files.push(generateCommonFile(opts));
|
|
2188
|
+
files.push(generateI18nFile(opts));
|
|
2189
|
+
const pluginEnumsList = opts.pluginEnums ? generatePluginEnums(opts.pluginEnums, opts) : [];
|
|
2190
|
+
files.push(generateIndexFile(schemas, enums, pluginEnumsList, typeAliases, opts));
|
|
2191
|
+
return files;
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
export {
|
|
2195
|
+
toPropertyName,
|
|
2196
|
+
toInterfaceName,
|
|
2197
|
+
getPropertyType,
|
|
2198
|
+
propertyToTSProperty,
|
|
2199
|
+
schemaToInterface,
|
|
2200
|
+
formatProperty,
|
|
2201
|
+
formatInterface,
|
|
2202
|
+
generateInterfaces,
|
|
2203
|
+
toEnumMemberName,
|
|
2204
|
+
toEnumName,
|
|
2205
|
+
schemaToEnum,
|
|
2206
|
+
generateEnums,
|
|
2207
|
+
pluginEnumToTSEnum,
|
|
2208
|
+
generatePluginEnums,
|
|
2209
|
+
formatEnum,
|
|
2210
|
+
enumToUnionType,
|
|
2211
|
+
formatTypeAlias,
|
|
2212
|
+
extractInlineEnums,
|
|
2213
|
+
DEFAULT_VALIDATION_TEMPLATES,
|
|
2214
|
+
mergeValidationTemplates,
|
|
2215
|
+
formatValidationMessage,
|
|
2216
|
+
getValidationMessages,
|
|
2217
|
+
generateModelRules,
|
|
2218
|
+
generateRulesFiles,
|
|
2219
|
+
generateTypeScript
|
|
2220
|
+
};
|
|
2221
|
+
//# sourceMappingURL=chunk-LHNMWILH.js.map
|