@famgia/omnify-typescript 0.0.25 → 0.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -20,17 +20,23 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ DEFAULT_VALIDATION_TEMPLATES: () => DEFAULT_VALIDATION_TEMPLATES,
23
24
  enumToUnionType: () => enumToUnionType,
24
25
  extractInlineEnums: () => extractInlineEnums,
25
26
  formatEnum: () => formatEnum,
26
27
  formatInterface: () => formatInterface,
27
28
  formatProperty: () => formatProperty,
28
29
  formatTypeAlias: () => formatTypeAlias,
30
+ formatValidationMessage: () => formatValidationMessage,
29
31
  generateEnums: () => generateEnums,
30
32
  generateInterfaces: () => generateInterfaces,
33
+ generateModelRules: () => generateModelRules,
34
+ generateRulesFiles: () => generateRulesFiles,
31
35
  generateTypeScript: () => generateTypeScript,
32
36
  generateTypeScriptFiles: () => generateTypeScript,
33
37
  getPropertyType: () => getPropertyType,
38
+ getValidationMessages: () => getValidationMessages,
39
+ mergeValidationTemplates: () => mergeValidationTemplates,
34
40
  propertyToTSProperty: () => propertyToTSProperty,
35
41
  schemaToEnum: () => schemaToEnum,
36
42
  schemaToInterface: () => schemaToInterface,
@@ -160,6 +166,18 @@ function propertyToTSProperties(propertyName, property, allSchemas, options = {}
160
166
  comment: `${displayName ?? propertyName} (${field.suffix})`
161
167
  });
162
168
  }
169
+ if (customType.accessors) {
170
+ for (const accessor of customType.accessors) {
171
+ const accessorName = `${propertyName}_${toSnakeCase(accessor.name)}`;
172
+ expandedProps.push({
173
+ name: accessorName,
174
+ type: "string | null",
175
+ optional: true,
176
+ readonly: isReadonly,
177
+ comment: `${displayName ?? propertyName} (computed)`
178
+ });
179
+ }
180
+ }
163
181
  return expandedProps;
164
182
  }
165
183
  if (customType && !customType.compound) {
@@ -217,8 +235,22 @@ function propertyToTSProperties(propertyName, property, allSchemas, options = {}
217
235
  function propertyToTSProperty(propertyName, property, allSchemas, options = {}) {
218
236
  return propertyToTSProperties(propertyName, property, allSchemas, options)[0];
219
237
  }
238
+ function extractTypeReferences(type, allSchemaNames) {
239
+ const primitives = /* @__PURE__ */ new Set(["string", "number", "boolean", "unknown", "null", "undefined", "void", "never", "any"]);
240
+ const refs = [];
241
+ const cleanType = type.replace(/\[\]/g, "").replace(/\s*\|\s*null/g, "");
242
+ const parts = cleanType.split(/\s*\|\s*/);
243
+ for (const part of parts) {
244
+ const trimmed = part.trim().replace(/^['"]|['"]$/g, "");
245
+ if (!primitives.has(trimmed) && allSchemaNames.has(trimmed)) {
246
+ refs.push(trimmed);
247
+ }
248
+ }
249
+ return refs;
250
+ }
220
251
  function schemaToInterface(schema, allSchemas, options = {}) {
221
252
  const properties = [];
253
+ const allSchemaNames = new Set(Object.keys(allSchemas).filter((name) => allSchemas[name].kind !== "enum"));
222
254
  if (schema.options?.id !== false) {
223
255
  const pkType = schema.options?.idType ?? "BigInt";
224
256
  properties.push({
@@ -261,11 +293,20 @@ function schemaToInterface(schema, allSchemas, options = {}) {
261
293
  comment: "Soft delete timestamp"
262
294
  });
263
295
  }
296
+ const dependencySet = /* @__PURE__ */ new Set();
297
+ for (const prop of properties) {
298
+ for (const ref of extractTypeReferences(prop.type, allSchemaNames)) {
299
+ if (ref !== schema.name) {
300
+ dependencySet.add(ref);
301
+ }
302
+ }
303
+ }
264
304
  const schemaDisplayName = resolveDisplayName(schema.displayName, options);
265
305
  return {
266
306
  name: toInterfaceName(schema.name),
267
307
  properties,
268
- comment: schemaDisplayName ?? schema.name
308
+ comment: schemaDisplayName ?? schema.name,
309
+ dependencies: dependencySet.size > 0 ? Array.from(dependencySet).sort() : void 0
269
310
  };
270
311
  }
271
312
  function formatProperty(property) {
@@ -322,11 +363,18 @@ function parseEnumValue(value, options = {}) {
322
363
  // No label or extra - will fallback to value
323
364
  };
324
365
  }
325
- const resolvedLabel = resolveDisplayName2(value.label, options);
366
+ let label;
367
+ if (value.label !== void 0) {
368
+ if (options.multiLocale && typeof value.label === "object") {
369
+ label = value.label;
370
+ } else {
371
+ label = resolveDisplayName2(value.label, options);
372
+ }
373
+ }
326
374
  return {
327
375
  name: toEnumMemberName(value.value),
328
376
  value: value.value,
329
- label: resolvedLabel,
377
+ label,
330
378
  extra: value.extra
331
379
  };
332
380
  }
@@ -356,6 +404,9 @@ function generateEnums(schemas, options = {}) {
356
404
  }
357
405
  return enums;
358
406
  }
407
+ function isMultiLocaleLabel(label) {
408
+ return label !== void 0 && typeof label === "object";
409
+ }
359
410
  function formatEnum(enumDef) {
360
411
  const { name, values, comment } = enumDef;
361
412
  const parts = [];
@@ -386,28 +437,64 @@ ${enumValues}
386
437
 
387
438
  `);
388
439
  const hasLabels = values.some((v) => v.label !== void 0);
440
+ const hasMultiLocale = values.some((v) => isMultiLocaleLabel(v.label));
389
441
  if (hasLabels) {
390
- const labelEntries = values.filter((v) => v.label !== void 0).map((v) => ` [${name}.${v.name}]: '${v.label}',`).join("\n");
391
- parts.push(`const ${lowerFirst(name)}Labels: Partial<Record<${name}, string>> = {
442
+ if (hasMultiLocale) {
443
+ const labelEntries = values.filter((v) => v.label !== void 0).map((v) => {
444
+ if (isMultiLocaleLabel(v.label)) {
445
+ const locales = Object.entries(v.label).map(([locale, text]) => `${locale}: '${text}'`).join(", ");
446
+ return ` [${name}.${v.name}]: { ${locales} },`;
447
+ }
448
+ return ` [${name}.${v.name}]: { default: '${v.label}' },`;
449
+ }).join("\n");
450
+ parts.push(`const ${lowerFirst(name)}Labels: Partial<Record<${name}, Record<string, string>>> = {
392
451
  ${labelEntries}
393
452
  };
394
453
 
395
454
  `);
396
- }
397
- parts.push(`/** Get label for ${name} value (fallback to value if no label) */
455
+ parts.push(`/** Get label for ${name} value with locale support */
398
456
  `);
399
- parts.push(`export function get${name}Label(value: ${name}): string {
457
+ parts.push(`export function get${name}Label(value: ${name}, locale?: string): string {
400
458
  `);
401
- if (hasLabels) {
402
- parts.push(` return ${lowerFirst(name)}Labels[value] ?? value;
459
+ parts.push(` const labels = ${lowerFirst(name)}Labels[value];
460
+ `);
461
+ parts.push(` if (!labels) return value;
403
462
  `);
463
+ parts.push(` if (locale && labels[locale]) return labels[locale];
464
+ `);
465
+ parts.push(` return Object.values(labels)[0] ?? value;
466
+ `);
467
+ parts.push(`}
468
+
469
+ `);
470
+ } else {
471
+ const labelEntries = values.filter((v) => v.label !== void 0).map((v) => ` [${name}.${v.name}]: '${v.label}',`).join("\n");
472
+ parts.push(`const ${lowerFirst(name)}Labels: Partial<Record<${name}, string>> = {
473
+ ${labelEntries}
474
+ };
475
+
476
+ `);
477
+ parts.push(`/** Get label for ${name} value (fallback to value if no label) */
478
+ `);
479
+ parts.push(`export function get${name}Label(value: ${name}): string {
480
+ `);
481
+ parts.push(` return ${lowerFirst(name)}Labels[value] ?? value;
482
+ `);
483
+ parts.push(`}
484
+
485
+ `);
486
+ }
404
487
  } else {
488
+ parts.push(`/** Get label for ${name} value (returns value as-is) */
489
+ `);
490
+ parts.push(`export function get${name}Label(value: ${name}): string {
491
+ `);
405
492
  parts.push(` return value;
406
493
  `);
407
- }
408
- parts.push(`}
494
+ parts.push(`}
409
495
 
410
496
  `);
497
+ }
411
498
  const hasExtra = values.some((v) => v.extra !== void 0);
412
499
  if (hasExtra) {
413
500
  const extraEntries = values.filter((v) => v.extra !== void 0).map((v) => ` [${name}.${v.name}]: ${JSON.stringify(v.extra)},`).join("\n");
@@ -529,6 +616,347 @@ function extractInlineEnums(schemas, options = {}) {
529
616
  return typeAliases;
530
617
  }
531
618
 
619
+ // src/validation-templates.ts
620
+ var DEFAULT_VALIDATION_TEMPLATES = {
621
+ required: {
622
+ ja: "${displayName}\u306F\u5FC5\u9808\u3067\u3059",
623
+ en: "${displayName} is required",
624
+ vi: "${displayName} l\xE0 b\u1EAFt bu\u1ED9c",
625
+ ko: "${displayName}\uC740(\uB294) \uD544\uC218\uC785\uB2C8\uB2E4",
626
+ zh: "${displayName}\u4E3A\u5FC5\u586B\u9879"
627
+ },
628
+ minLength: {
629
+ ja: "${displayName}\u306F${min}\u6587\u5B57\u4EE5\u4E0A\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
630
+ en: "${displayName} must be at least ${min} characters",
631
+ vi: "${displayName} ph\u1EA3i c\xF3 \xEDt nh\u1EA5t ${min} k\xFD t\u1EF1",
632
+ ko: "${displayName}\uC740(\uB294) ${min}\uC790 \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4",
633
+ zh: "${displayName}\u81F3\u5C11\u9700\u8981${min}\u4E2A\u5B57\u7B26"
634
+ },
635
+ maxLength: {
636
+ ja: "${displayName}\u306F${max}\u6587\u5B57\u4EE5\u5185\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
637
+ en: "${displayName} must be at most ${max} characters",
638
+ vi: "${displayName} t\u1ED1i \u0111a ${max} k\xFD t\u1EF1",
639
+ ko: "${displayName}\uC740(\uB294) ${max}\uC790 \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4",
640
+ zh: "${displayName}\u4E0D\u80FD\u8D85\u8FC7${max}\u4E2A\u5B57\u7B26"
641
+ },
642
+ min: {
643
+ ja: "${displayName}\u306F${min}\u4EE5\u4E0A\u306E\u5024\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
644
+ en: "${displayName} must be at least ${min}",
645
+ vi: "${displayName} ph\u1EA3i l\u1EDBn h\u01A1n ho\u1EB7c b\u1EB1ng ${min}",
646
+ ko: "${displayName}\uC740(\uB294) ${min} \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4",
647
+ zh: "${displayName}\u4E0D\u80FD\u5C0F\u4E8E${min}"
648
+ },
649
+ max: {
650
+ ja: "${displayName}\u306F${max}\u4EE5\u4E0B\u306E\u5024\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
651
+ en: "${displayName} must be at most ${max}",
652
+ vi: "${displayName} ph\u1EA3i nh\u1ECF h\u01A1n ho\u1EB7c b\u1EB1ng ${max}",
653
+ ko: "${displayName}\uC740(\uB294) ${max} \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4",
654
+ zh: "${displayName}\u4E0D\u80FD\u5927\u4E8E${max}"
655
+ },
656
+ email: {
657
+ ja: "${displayName}\u306E\u5F62\u5F0F\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093",
658
+ en: "${displayName} is not a valid email address",
659
+ vi: "${displayName} kh\xF4ng ph\u1EA3i l\xE0 \u0111\u1ECBa ch\u1EC9 email h\u1EE3p l\u1EC7",
660
+ ko: "${displayName} \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
661
+ zh: "${displayName}\u4E0D\u662F\u6709\u6548\u7684\u90AE\u7BB1\u5730\u5740"
662
+ },
663
+ url: {
664
+ ja: "${displayName}\u306F\u6709\u52B9\u306AURL\u3067\u306F\u3042\u308A\u307E\u305B\u3093",
665
+ en: "${displayName} is not a valid URL",
666
+ vi: "${displayName} kh\xF4ng ph\u1EA3i l\xE0 URL h\u1EE3p l\u1EC7",
667
+ ko: "${displayName}\uC740(\uB294) \uC720\uD6A8\uD55C URL\uC774 \uC544\uB2D9\uB2C8\uB2E4",
668
+ zh: "${displayName}\u4E0D\u662F\u6709\u6548\u7684URL"
669
+ },
670
+ pattern: {
671
+ ja: "${displayName}\u306E\u5F62\u5F0F\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093",
672
+ en: "${displayName} format is invalid",
673
+ vi: "${displayName} kh\xF4ng \u0111\xFAng \u0111\u1ECBnh d\u1EA1ng",
674
+ ko: "${displayName} \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
675
+ zh: "${displayName}\u683C\u5F0F\u4E0D\u6B63\u786E"
676
+ },
677
+ enum: {
678
+ ja: "${displayName}\u306E\u5024\u304C\u7121\u52B9\u3067\u3059",
679
+ en: "${displayName} has an invalid value",
680
+ vi: "${displayName} c\xF3 gi\xE1 tr\u1ECB kh\xF4ng h\u1EE3p l\u1EC7",
681
+ ko: "${displayName} \uAC12\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
682
+ zh: "${displayName}\u7684\u503C\u65E0\u6548"
683
+ }
684
+ };
685
+ function mergeValidationTemplates(userTemplates) {
686
+ if (!userTemplates) {
687
+ return DEFAULT_VALIDATION_TEMPLATES;
688
+ }
689
+ const merged = {
690
+ required: { ...DEFAULT_VALIDATION_TEMPLATES.required },
691
+ minLength: { ...DEFAULT_VALIDATION_TEMPLATES.minLength },
692
+ maxLength: { ...DEFAULT_VALIDATION_TEMPLATES.maxLength },
693
+ min: { ...DEFAULT_VALIDATION_TEMPLATES.min },
694
+ max: { ...DEFAULT_VALIDATION_TEMPLATES.max },
695
+ email: { ...DEFAULT_VALIDATION_TEMPLATES.email },
696
+ url: { ...DEFAULT_VALIDATION_TEMPLATES.url },
697
+ pattern: { ...DEFAULT_VALIDATION_TEMPLATES.pattern },
698
+ enum: { ...DEFAULT_VALIDATION_TEMPLATES.enum }
699
+ };
700
+ for (const [key, value] of Object.entries(userTemplates)) {
701
+ if (value && key in merged) {
702
+ merged[key] = {
703
+ ...merged[key],
704
+ ...value
705
+ };
706
+ }
707
+ }
708
+ return merged;
709
+ }
710
+ function formatValidationMessage(template, vars) {
711
+ let result = template;
712
+ for (const [key, value] of Object.entries(vars)) {
713
+ result = result.replace(new RegExp(`\\$\\{${key}\\}`, "g"), String(value));
714
+ }
715
+ return result;
716
+ }
717
+ function getValidationMessages(templates, ruleType, locales, vars, fallbackLocale) {
718
+ const ruleTemplates = templates[ruleType];
719
+ const messages = {};
720
+ for (const locale of locales) {
721
+ const template = ruleTemplates[locale] ?? (fallbackLocale ? ruleTemplates[fallbackLocale] : void 0) ?? ruleTemplates["en"] ?? "";
722
+ messages[locale] = formatValidationMessage(template, vars);
723
+ }
724
+ return messages;
725
+ }
726
+
727
+ // src/rules-generator.ts
728
+ function getMultiLocaleDisplayName(value, locales, fallbackLocale, defaultValue) {
729
+ if (!value) {
730
+ const result2 = {};
731
+ for (const locale of locales) {
732
+ result2[locale] = defaultValue;
733
+ }
734
+ return result2;
735
+ }
736
+ if (typeof value === "string") {
737
+ const result2 = {};
738
+ for (const locale of locales) {
739
+ result2[locale] = value;
740
+ }
741
+ return result2;
742
+ }
743
+ const result = {};
744
+ for (const locale of locales) {
745
+ result[locale] = value[locale] ?? value[fallbackLocale] ?? value["en"] ?? defaultValue;
746
+ }
747
+ return result;
748
+ }
749
+ function generatePropertyRules(propName, property, displayName, locales, fallbackLocale, templates) {
750
+ const rules = [];
751
+ const propDef = property;
752
+ if (!propDef.nullable) {
753
+ rules.push({
754
+ required: true,
755
+ message: getValidationMessages(templates, "required", locales, { displayName: "${displayName}" }, fallbackLocale)
756
+ });
757
+ }
758
+ if (property.type === "Email") {
759
+ rules.push({
760
+ type: "email",
761
+ message: getValidationMessages(templates, "email", locales, { displayName: "${displayName}" }, fallbackLocale)
762
+ });
763
+ }
764
+ if (property.type === "String" || property.type === "Text" || property.type === "LongText") {
765
+ if (propDef.minLength) {
766
+ rules.push({
767
+ min: propDef.minLength,
768
+ message: getValidationMessages(templates, "minLength", locales, { displayName: "${displayName}", min: propDef.minLength }, fallbackLocale)
769
+ });
770
+ }
771
+ if (propDef.maxLength || propDef.length) {
772
+ const max = propDef.maxLength ?? propDef.length;
773
+ rules.push({
774
+ max,
775
+ message: getValidationMessages(templates, "maxLength", locales, { displayName: "${displayName}", max }, fallbackLocale)
776
+ });
777
+ }
778
+ }
779
+ if (property.type === "Int" || property.type === "BigInt" || property.type === "Float") {
780
+ if (propDef.min !== void 0) {
781
+ rules.push({
782
+ type: property.type === "Float" ? "number" : "integer",
783
+ min: propDef.min,
784
+ message: getValidationMessages(templates, "min", locales, { displayName: "${displayName}", min: propDef.min }, fallbackLocale)
785
+ });
786
+ }
787
+ if (propDef.max !== void 0) {
788
+ rules.push({
789
+ type: property.type === "Float" ? "number" : "integer",
790
+ max: propDef.max,
791
+ message: getValidationMessages(templates, "max", locales, { displayName: "${displayName}", max: propDef.max }, fallbackLocale)
792
+ });
793
+ }
794
+ }
795
+ if (propDef.pattern) {
796
+ rules.push({
797
+ pattern: propDef.pattern,
798
+ message: getValidationMessages(templates, "pattern", locales, { displayName: "${displayName}" }, fallbackLocale)
799
+ });
800
+ }
801
+ for (const rule of rules) {
802
+ const newMessage = {};
803
+ for (const locale of locales) {
804
+ const msg = rule.message[locale];
805
+ if (msg) {
806
+ newMessage[locale] = msg.replace(/\$\{displayName\}/g, displayName[locale] ?? propName);
807
+ }
808
+ }
809
+ rule.message = newMessage;
810
+ }
811
+ return rules;
812
+ }
813
+ function generateModelRules(schema, locales, fallbackLocale, templates) {
814
+ const modelDisplayName = getMultiLocaleDisplayName(
815
+ schema.displayName,
816
+ locales,
817
+ fallbackLocale,
818
+ schema.name
819
+ );
820
+ const properties = {};
821
+ if (schema.properties) {
822
+ for (const [propName, property] of Object.entries(schema.properties)) {
823
+ const propDef = property;
824
+ const displayName = getMultiLocaleDisplayName(
825
+ propDef.displayName,
826
+ locales,
827
+ fallbackLocale,
828
+ propName
829
+ );
830
+ properties[propName] = {
831
+ displayName,
832
+ rules: generatePropertyRules(propName, property, displayName, locales, fallbackLocale, templates)
833
+ };
834
+ }
835
+ }
836
+ return {
837
+ displayName: modelDisplayName,
838
+ properties
839
+ };
840
+ }
841
+ function formatRulesFile(schemaName, rules) {
842
+ const parts = [];
843
+ parts.push(`/**
844
+ * Auto-generated validation rules and metadata for ${schemaName}.
845
+ * DO NOT EDIT - This file is automatically generated and will be overwritten.
846
+ */
847
+
848
+ `);
849
+ parts.push(`export interface LocaleMap { [locale: string]: string; }
850
+
851
+ `);
852
+ parts.push(`export interface ValidationRule {
853
+ required?: boolean;
854
+ type?: 'string' | 'number' | 'email' | 'url' | 'integer';
855
+ min?: number;
856
+ max?: number;
857
+ len?: number;
858
+ pattern?: RegExp;
859
+ message: LocaleMap;
860
+ }
861
+
862
+ `);
863
+ parts.push(`/** Display name for ${schemaName} */
864
+ `);
865
+ parts.push(`export const ${schemaName}DisplayName: LocaleMap = ${JSON.stringify(rules.displayName, null, 2)};
866
+
867
+ `);
868
+ parts.push(`/** Property display names for ${schemaName} */
869
+ `);
870
+ parts.push(`export const ${schemaName}PropertyDisplayNames: Record<string, LocaleMap> = {
871
+ `);
872
+ for (const [propName, propRules] of Object.entries(rules.properties)) {
873
+ parts.push(` ${propName}: ${JSON.stringify(propRules.displayName)},
874
+ `);
875
+ }
876
+ parts.push(`};
877
+
878
+ `);
879
+ parts.push(`/** Validation rules for ${schemaName} (Ant Design compatible) */
880
+ `);
881
+ parts.push(`export const ${schemaName}Rules: Record<string, ValidationRule[]> = {
882
+ `);
883
+ for (const [propName, propRules] of Object.entries(rules.properties)) {
884
+ if (propRules.rules.length > 0) {
885
+ parts.push(` ${propName}: [
886
+ `);
887
+ for (const rule of propRules.rules) {
888
+ const ruleObj = {};
889
+ if (rule.required) ruleObj.required = true;
890
+ if (rule.type) ruleObj.type = `'${rule.type}'`;
891
+ if (rule.min !== void 0) ruleObj.min = rule.min;
892
+ if (rule.max !== void 0) ruleObj.max = rule.max;
893
+ if (rule.pattern) ruleObj.pattern = `/${rule.pattern}/`;
894
+ ruleObj.message = rule.message;
895
+ const ruleStr = Object.entries(ruleObj).map(([k, v]) => {
896
+ if (k === "type") return `${k}: ${v}`;
897
+ if (k === "pattern") return `${k}: ${v}`;
898
+ return `${k}: ${JSON.stringify(v)}`;
899
+ }).join(", ");
900
+ parts.push(` { ${ruleStr} },
901
+ `);
902
+ }
903
+ parts.push(` ],
904
+ `);
905
+ }
906
+ }
907
+ parts.push(`};
908
+
909
+ `);
910
+ parts.push(`/** Get validation rules with messages for a specific locale */
911
+ `);
912
+ parts.push(`export function get${schemaName}Rules(locale: string): Record<string, Array<{ required?: boolean; type?: string; min?: number; max?: number; pattern?: RegExp; message: string }>> {
913
+ const result: Record<string, Array<{ required?: boolean; type?: string; min?: number; max?: number; pattern?: RegExp; message: string }>> = {};
914
+ for (const [prop, rules] of Object.entries(${schemaName}Rules)) {
915
+ result[prop] = rules.map(rule => ({
916
+ ...rule,
917
+ message: rule.message[locale] ?? rule.message['en'] ?? '',
918
+ }));
919
+ }
920
+ return result;
921
+ }
922
+
923
+ `);
924
+ parts.push(`/** Get display name for a specific locale */
925
+ `);
926
+ parts.push(`export function get${schemaName}DisplayName(locale: string): string {
927
+ return ${schemaName}DisplayName[locale] ?? ${schemaName}DisplayName['en'] ?? '${schemaName}';
928
+ }
929
+
930
+ `);
931
+ parts.push(`/** Get property display name for a specific locale */
932
+ `);
933
+ parts.push(`export function get${schemaName}PropertyDisplayName(property: string, locale: string): string {
934
+ const names = ${schemaName}PropertyDisplayNames[property];
935
+ return names?.[locale] ?? names?.['en'] ?? property;
936
+ }
937
+ `);
938
+ return parts.join("");
939
+ }
940
+ function generateRulesFiles(schemas, options = {}) {
941
+ const files = [];
942
+ const localeConfig = options.localeConfig;
943
+ const locales = [...localeConfig?.locales ?? ["en"]];
944
+ const fallbackLocale = localeConfig?.fallbackLocale ?? "en";
945
+ const templates = mergeValidationTemplates(options.validationTemplates);
946
+ for (const schema of Object.values(schemas)) {
947
+ if (schema.kind === "enum") continue;
948
+ const rules = generateModelRules(schema, locales, fallbackLocale, templates);
949
+ const content = formatRulesFile(schema.name, rules);
950
+ files.push({
951
+ filePath: `rules/${schema.name}.rules.ts`,
952
+ content,
953
+ types: [`${schema.name}Rules`, `${schema.name}DisplayName`],
954
+ overwrite: true
955
+ });
956
+ }
957
+ return files;
958
+ }
959
+
532
960
  // src/generator.ts
533
961
  var DEFAULT_OPTIONS = {
534
962
  readonly: true,
@@ -560,6 +988,13 @@ function generateBaseInterfaceFile(schemaName, schemas, options) {
560
988
  throw new Error(`Interface not found for schema: ${schemaName}`);
561
989
  }
562
990
  const parts = [generateBaseHeader()];
991
+ if (iface.dependencies && iface.dependencies.length > 0) {
992
+ for (const dep of iface.dependencies) {
993
+ parts.push(`import type { ${dep} } from './${dep}.js';
994
+ `);
995
+ }
996
+ parts.push("\n");
997
+ }
563
998
  parts.push(formatInterface(iface));
564
999
  parts.push("\n");
565
1000
  return {
@@ -676,11 +1111,11 @@ function generateIndexFile(schemas, enums, typeAliases) {
676
1111
  function generateTypeScript(schemas, options = {}) {
677
1112
  const opts = { ...DEFAULT_OPTIONS, ...options };
678
1113
  const files = [];
679
- const enums = generateEnums(schemas);
1114
+ const enums = generateEnums(schemas, opts);
680
1115
  for (const enumDef of enums) {
681
1116
  files.push(generateEnumFile(enumDef));
682
1117
  }
683
- const typeAliases = extractInlineEnums(schemas);
1118
+ const typeAliases = extractInlineEnums(schemas, opts);
684
1119
  for (const alias of typeAliases) {
685
1120
  files.push(generateTypeAliasFile(alias));
686
1121
  }
@@ -692,22 +1127,32 @@ function generateTypeScript(schemas, options = {}) {
692
1127
  if (schema.kind === "enum") continue;
693
1128
  files.push(generateModelFile(schema.name));
694
1129
  }
1130
+ if (opts.generateRules) {
1131
+ const rulesFiles = generateRulesFiles(schemas, opts);
1132
+ files.push(...rulesFiles);
1133
+ }
695
1134
  files.push(generateIndexFile(schemas, enums, typeAliases));
696
1135
  return files;
697
1136
  }
698
1137
  // Annotate the CommonJS export names for ESM import in node:
699
1138
  0 && (module.exports = {
1139
+ DEFAULT_VALIDATION_TEMPLATES,
700
1140
  enumToUnionType,
701
1141
  extractInlineEnums,
702
1142
  formatEnum,
703
1143
  formatInterface,
704
1144
  formatProperty,
705
1145
  formatTypeAlias,
1146
+ formatValidationMessage,
706
1147
  generateEnums,
707
1148
  generateInterfaces,
1149
+ generateModelRules,
1150
+ generateRulesFiles,
708
1151
  generateTypeScript,
709
1152
  generateTypeScriptFiles,
710
1153
  getPropertyType,
1154
+ getValidationMessages,
1155
+ mergeValidationTemplates,
711
1156
  propertyToTSProperty,
712
1157
  schemaToEnum,
713
1158
  schemaToInterface,