@ng-forge/openapi-generator 0.9.0-next.12 → 0.9.0-next.14

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.
@@ -71,52 +71,60 @@ function formatEndpointLabel(endpoint) {
71
71
  }
72
72
 
73
73
  // packages/openapi-generator/src/parser/schema-walker.ts
74
- function walkSchema(schema, requiredFields = []) {
74
+ function walkSchema(schema, requiredFields = [], seen = /* @__PURE__ */ new WeakSet()) {
75
75
  const warnings = [];
76
- if (schema.allOf) {
77
- return walkAllOf(schema.allOf, requiredFields, warnings);
78
- }
79
- if (schema.oneOf && schema.discriminator) {
80
- return walkOneOfWithDiscriminator(
81
- schema.oneOf,
82
- schema.discriminator,
83
- requiredFields,
84
- warnings
85
- );
86
- }
87
- if (schema.oneOf && !schema.discriminator) {
88
- warnings.push("oneOf without discriminator is not supported \u2014 add a discriminator to generate conditional form groups");
89
- return { properties: [], warnings };
90
- }
91
- if (schema.anyOf) {
92
- warnings.push("anyOf schemas are not supported and were skipped");
93
- return { properties: [], warnings };
76
+ if (seen.has(schema)) {
77
+ return { properties: [], warnings: ["Circular schema reference detected during allOf merge \u2014 nested schema skipped"] };
94
78
  }
95
- if ("if" in schema) {
96
- warnings.push("if/then/else schemas are not supported and were skipped");
97
- return { properties: [], warnings };
98
- }
99
- if (schema.additionalProperties && schema.additionalProperties !== false) {
100
- warnings.push("additionalProperties are not supported and were skipped");
101
- }
102
- const properties = [];
103
- const required = new Set(schema.required ?? requiredFields);
104
- for (const [name, propSchema] of Object.entries(schema.properties ?? {})) {
105
- if (isReferenceObject(propSchema)) continue;
106
- properties.push({
107
- name,
108
- schema: propSchema,
109
- required: required.has(name)
110
- });
79
+ seen.add(schema);
80
+ try {
81
+ if (schema.allOf) {
82
+ return walkAllOf(schema.allOf, requiredFields, warnings, seen);
83
+ }
84
+ if (schema.oneOf && schema.discriminator) {
85
+ return walkOneOfWithDiscriminator(
86
+ schema.oneOf,
87
+ schema.discriminator,
88
+ requiredFields,
89
+ warnings
90
+ );
91
+ }
92
+ if (schema.oneOf && !schema.discriminator) {
93
+ warnings.push("oneOf without discriminator is not supported \u2014 add a discriminator to generate conditional form groups");
94
+ return { properties: [], warnings };
95
+ }
96
+ if (schema.anyOf) {
97
+ warnings.push("anyOf schemas are not supported and were skipped");
98
+ return { properties: [], warnings };
99
+ }
100
+ if ("if" in schema) {
101
+ warnings.push("if/then/else schemas are not supported and were skipped");
102
+ return { properties: [], warnings };
103
+ }
104
+ if (schema.additionalProperties && schema.additionalProperties !== false) {
105
+ warnings.push("additionalProperties are not supported and were skipped");
106
+ }
107
+ const properties = [];
108
+ const required = new Set(schema.required ?? requiredFields);
109
+ for (const [name, propSchema] of Object.entries(schema.properties ?? {})) {
110
+ if (isReferenceObject(propSchema)) continue;
111
+ properties.push({
112
+ name,
113
+ schema: propSchema,
114
+ required: required.has(name)
115
+ });
116
+ }
117
+ return { properties, warnings };
118
+ } finally {
119
+ seen.delete(schema);
111
120
  }
112
- return { properties, warnings };
113
121
  }
114
- function walkAllOf(schemas, requiredFields, warnings) {
122
+ function walkAllOf(schemas, requiredFields, warnings, seen) {
115
123
  const mergedProperties = /* @__PURE__ */ new Map();
116
124
  const allRequired = new Set(requiredFields);
117
125
  for (const schema of schemas) {
118
126
  if (isReferenceObject(schema)) continue;
119
- const walked = walkSchema(schema, []);
127
+ const walked = walkSchema(schema, [], seen);
120
128
  warnings.push(...walked.warnings);
121
129
  for (const prop of walked.properties) {
122
130
  mergedProperties.set(prop.name, prop);
@@ -453,7 +461,17 @@ var logger = {
453
461
  };
454
462
 
455
463
  // packages/openapi-generator/src/mapper/schema-to-fields.ts
456
- var NULLABLE_SUPPORTED_FIELD_TYPES = /* @__PURE__ */ new Set(["input", "textarea", "select", "radio", "multi-checkbox", "slider", "datepicker"]);
464
+ var NULLABLE_SUPPORTED_FIELD_TYPES = /* @__PURE__ */ new Set([
465
+ "input",
466
+ "textarea",
467
+ "select",
468
+ "radio",
469
+ "multi-checkbox",
470
+ "slider",
471
+ "datepicker",
472
+ "checkbox",
473
+ "toggle"
474
+ ]);
457
475
  var CONTAINER_FIELD_TYPES = /* @__PURE__ */ new Set(["group", "array", "row", "page", "container"]);
458
476
  function singularize(label) {
459
477
  if (label.length >= 4 && label.endsWith("s") && !label.endsWith("ss")) {
@@ -462,54 +480,76 @@ function singularize(label) {
462
480
  return label;
463
481
  }
464
482
  function mapSchemaToFields(schema, requiredFields, options = {}) {
465
- const walked = walkSchema(schema, requiredFields);
466
- const ambiguousFields = [];
467
- const warnings = [...walked.warnings];
468
- if (walked.discriminator) {
469
- const discConfig = mapDiscriminator(walked.discriminator);
470
- const fields2 = [discConfig.discriminatorField];
471
- for (const group of discConfig.conditionalGroups) {
472
- const variantSchema = walked.discriminator.mapping[group.discriminatorValue];
473
- if (variantSchema) {
474
- const variantResult = mapSchemaToFields(variantSchema, variantSchema.required ?? [], {
475
- ...options,
476
- schemaName: options.schemaName ? `${options.schemaName}.${group.discriminatorValue}` : group.discriminatorValue
477
- });
478
- const variantFields = variantResult.fields.filter((f) => f.key !== walked.discriminator.propertyName);
479
- ambiguousFields.push(...variantResult.ambiguousFields);
480
- warnings.push(...variantResult.warnings);
481
- if (variantFields.length > 0) {
482
- fields2.push({
483
- key: `${group.discriminatorValue}Variant`,
484
- type: "group",
485
- fields: variantFields,
486
- logic: [
487
- {
488
- type: "hidden",
489
- condition: {
490
- type: "fieldValue",
491
- fieldPath: walked.discriminator.propertyName,
492
- operator: "notEquals",
493
- value: group.discriminatorValue
483
+ return mapSchemaToFieldsInternal(schema, requiredFields, options, /* @__PURE__ */ new WeakSet());
484
+ }
485
+ function mapSchemaToFieldsInternal(schema, requiredFields, options, seen) {
486
+ if (seen.has(schema)) {
487
+ return {
488
+ fields: [],
489
+ ambiguousFields: [],
490
+ warnings: [
491
+ `Circular schema reference at '${options.schemaName ?? "<root>"}' \u2014 nested fields skipped. Use x-ng-forge-type to override how this field renders.`
492
+ ]
493
+ };
494
+ }
495
+ seen.add(schema);
496
+ try {
497
+ const walked = walkSchema(schema, requiredFields);
498
+ const ambiguousFields = [];
499
+ const warnings = [...walked.warnings];
500
+ if (walked.discriminator) {
501
+ const discConfig = mapDiscriminator(walked.discriminator);
502
+ const fields2 = [discConfig.discriminatorField];
503
+ for (const group of discConfig.conditionalGroups) {
504
+ const variantSchema = walked.discriminator.mapping[group.discriminatorValue];
505
+ if (variantSchema) {
506
+ const variantResult = mapSchemaToFieldsInternal(
507
+ variantSchema,
508
+ variantSchema.required ?? [],
509
+ {
510
+ ...options,
511
+ schemaName: options.schemaName ? `${options.schemaName}.${group.discriminatorValue}` : group.discriminatorValue
512
+ },
513
+ seen
514
+ );
515
+ const variantFields = variantResult.fields.filter((f) => f.key !== walked.discriminator.propertyName);
516
+ ambiguousFields.push(...variantResult.ambiguousFields);
517
+ warnings.push(...variantResult.warnings);
518
+ if (variantFields.length > 0) {
519
+ fields2.push({
520
+ key: `${group.discriminatorValue}Variant`,
521
+ type: "group",
522
+ fields: variantFields,
523
+ logic: [
524
+ {
525
+ type: "hidden",
526
+ condition: {
527
+ type: "fieldValue",
528
+ fieldPath: walked.discriminator.propertyName,
529
+ operator: "notEquals",
530
+ value: group.discriminatorValue
531
+ }
494
532
  }
495
- }
496
- ]
497
- });
533
+ ]
534
+ });
535
+ }
498
536
  }
499
537
  }
538
+ return { fields: fields2, ambiguousFields, warnings };
500
539
  }
501
- return { fields: fields2, ambiguousFields, warnings };
502
- }
503
- const fields = [];
504
- for (const prop of walked.properties) {
505
- const field = mapPropertyToField(prop, options, ambiguousFields, warnings);
506
- if (field) {
507
- fields.push(field);
540
+ const fields = [];
541
+ for (const prop of walked.properties) {
542
+ const field = mapPropertyToField(prop, options, seen, ambiguousFields, warnings);
543
+ if (field) {
544
+ fields.push(field);
545
+ }
508
546
  }
547
+ return { fields, ambiguousFields, warnings };
548
+ } finally {
549
+ seen.delete(schema);
509
550
  }
510
- return { fields, ambiguousFields, warnings };
511
551
  }
512
- function mapPropertyToField(prop, options, ambiguousFields, warnings) {
552
+ function mapPropertyToField(prop, options, seen, ambiguousFields, warnings) {
513
553
  if (prop.schema["deprecated"]) {
514
554
  warnings.push(`Property '${prop.name}' is deprecated and was skipped`);
515
555
  return void 0;
@@ -517,10 +557,12 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
517
557
  if (prop.schema.oneOf && prop.schema["discriminator"]) {
518
558
  const schemaPrefix2 = options.schemaName ?? "";
519
559
  const nestedSchemaName = schemaPrefix2 ? `${schemaPrefix2}.${prop.name}` : prop.name;
520
- const innerResult = mapSchemaToFields(prop.schema, prop.schema.required ?? [], {
521
- ...options,
522
- schemaName: nestedSchemaName
523
- });
560
+ const innerResult = mapSchemaToFieldsInternal(
561
+ prop.schema,
562
+ prop.schema.required ?? [],
563
+ { ...options, schemaName: nestedSchemaName },
564
+ seen
565
+ );
524
566
  ambiguousFields.push(...innerResult.ambiguousFields);
525
567
  warnings.push(...innerResult.warnings);
526
568
  return {
@@ -550,19 +592,19 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
550
592
  }
551
593
  }
552
594
  const validators = mapSchemaToValidators(prop.schema, prop.required);
595
+ const field = {
596
+ key: prop.name,
597
+ type: finalType
598
+ };
553
599
  const arraySchema = prop.schema;
554
- if (typeResult.fieldType === "array") {
600
+ if (finalType === "array") {
555
601
  if (arraySchema["minItems"] !== void 0) {
556
- validators.push({ type: "minLength", value: arraySchema["minItems"] });
602
+ field.minLength = arraySchema["minItems"];
557
603
  }
558
604
  if (arraySchema["maxItems"] !== void 0) {
559
- validators.push({ type: "maxLength", value: arraySchema["maxItems"] });
605
+ field.maxLength = arraySchema["maxItems"];
560
606
  }
561
607
  }
562
- const field = {
563
- key: prop.name,
564
- type: finalType
565
- };
566
608
  if (!CONTAINER_FIELD_TYPES.has(finalType)) {
567
609
  field.label = prop.schema["title"] ?? toLabel(prop.name);
568
610
  }
@@ -606,7 +648,15 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
606
648
  }
607
649
  }
608
650
  if (validators.length > 0) {
609
- field.validators = validators;
651
+ if (CONTAINER_FIELD_TYPES.has(finalType)) {
652
+ const droppedTypes = validators.map((v) => v.type).join(", ");
653
+ const recovery = finalType === "array" ? ` Use \`minLength: 1\` on the array or add validators to the template field.` : ` Add validators to individual child fields instead.`;
654
+ logger.verbose(
655
+ `Field '${fieldPath}': '${finalType}' container fields do not accept field-level validators \u2014 dropped: ${droppedTypes}.${recovery}`
656
+ );
657
+ } else {
658
+ field.validators = validators;
659
+ }
610
660
  }
611
661
  if (options.editable === false) {
612
662
  field.disabled = true;
@@ -633,14 +683,14 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
633
683
  const nestedSchemaName = schemaPrefix ? `${schemaPrefix}.${prop.name}` : prop.name;
634
684
  const nestedOptions = { ...options, schemaName: nestedSchemaName };
635
685
  if (typeResult.fieldType === "group" && prop.schema.properties) {
636
- const innerResult = mapSchemaToFields(prop.schema, prop.schema.required ?? [], nestedOptions);
686
+ const innerResult = mapSchemaToFieldsInternal(prop.schema, prop.schema.required ?? [], nestedOptions, seen);
637
687
  field.fields = innerResult.fields;
638
688
  ambiguousFields.push(...innerResult.ambiguousFields);
639
689
  } else if (typeResult.fieldType === "array" && prop.schema["items"]) {
640
690
  const items = prop.schema["items"];
641
691
  const fieldLabel = singularize(toLabel(prop.name));
642
692
  if (items.type === "object" || items.properties) {
643
- const innerResult = mapSchemaToFields(items, items.required ?? [], nestedOptions);
693
+ const innerResult = mapSchemaToFieldsInternal(items, items.required ?? [], nestedOptions, seen);
644
694
  field.template = innerResult.fields;
645
695
  ambiguousFields.push(...innerResult.ambiguousFields);
646
696
  warnings.push(...innerResult.warnings);
@@ -740,6 +790,12 @@ function generateFieldLines(field, indent) {
740
790
  }
741
791
  lines.push(`${indent} ],`);
742
792
  }
793
+ if (field.minLength !== void 0) {
794
+ lines.push(`${indent} minLength: ${field.minLength},`);
795
+ }
796
+ if (field.maxLength !== void 0) {
797
+ lines.push(`${indent} maxLength: ${field.maxLength},`);
798
+ }
743
799
  if (field.disabled) {
744
800
  lines.push(`${indent} disabled: true,`);
745
801
  }
@@ -828,15 +884,21 @@ function generateInterface(schema, options) {
828
884
  const interfaceName = toInterfaceName(options.method, options.path, options.operationId);
829
885
  const lines = [];
830
886
  const nestedInterfaces = [];
831
- const { properties, required } = resolveSchemaProperties(schema);
832
- lines.push(`export interface ${interfaceName} {`);
833
- for (const [name, propSchema] of properties) {
834
- if (isReferenceObject(propSchema)) continue;
835
- const optional = required.has(name) ? "" : "?";
836
- const tsType = schemaToTsType(name, propSchema, interfaceName, nestedInterfaces);
837
- lines.push(` ${name}${optional}: ${tsType};`);
887
+ const seen = /* @__PURE__ */ new WeakSet();
888
+ seen.add(schema);
889
+ try {
890
+ const { properties, required } = resolveSchemaProperties(schema, seen);
891
+ lines.push(`export interface ${interfaceName} {`);
892
+ for (const [name, propSchema] of properties) {
893
+ if (isReferenceObject(propSchema)) continue;
894
+ const optional = required.has(name) ? "" : "?";
895
+ const tsType = schemaToTsType(name, propSchema, interfaceName, nestedInterfaces, seen);
896
+ lines.push(` ${name}${optional}: ${tsType};`);
897
+ }
898
+ lines.push(`}`);
899
+ } finally {
900
+ seen.delete(schema);
838
901
  }
839
- lines.push(`}`);
840
902
  if (nestedInterfaces.length > 0) {
841
903
  return `// @generated by @ng-forge/openapi-generator
842
904
  ` + [...nestedInterfaces, "", ...lines, ""].join("\n");
@@ -845,18 +907,24 @@ function generateInterface(schema, options) {
845
907
  return `// @generated by @ng-forge/openapi-generator
846
908
  ` + lines.join("\n");
847
909
  }
848
- function resolveSchemaProperties(schema) {
910
+ function resolveSchemaProperties(schema, seen) {
849
911
  if (schema.allOf) {
850
912
  const merged = /* @__PURE__ */ new Map();
851
913
  const allRequired = new Set(schema.required ?? []);
852
914
  for (const sub of schema.allOf) {
853
915
  if (isReferenceObject(sub)) continue;
854
- const resolved = resolveSchemaProperties(sub);
855
- for (const [name, propSchema] of resolved.properties) {
856
- merged.set(name, propSchema);
857
- }
858
- for (const r of resolved.required) {
859
- allRequired.add(r);
916
+ if (seen.has(sub)) continue;
917
+ seen.add(sub);
918
+ try {
919
+ const resolved = resolveSchemaProperties(sub, seen);
920
+ for (const [name, propSchema] of resolved.properties) {
921
+ merged.set(name, propSchema);
922
+ }
923
+ for (const r of resolved.required) {
924
+ allRequired.add(r);
925
+ }
926
+ } finally {
927
+ seen.delete(sub);
860
928
  }
861
929
  }
862
930
  return { properties: Array.from(merged.entries()), required: allRequired };
@@ -868,7 +936,14 @@ function resolveSchemaProperties(schema) {
868
936
  const discriminatorValues = [];
869
937
  for (const variant of schema.oneOf) {
870
938
  if (isReferenceObject(variant)) continue;
871
- const resolved = resolveSchemaProperties(variant);
939
+ if (seen.has(variant)) continue;
940
+ seen.add(variant);
941
+ let resolved;
942
+ try {
943
+ resolved = resolveSchemaProperties(variant, seen);
944
+ } finally {
945
+ seen.delete(variant);
946
+ }
872
947
  for (const [name, propSchema] of resolved.properties) {
873
948
  if (name === discriminatorName) {
874
949
  if (propSchema.enum) {
@@ -914,78 +989,86 @@ function resolveSchemaProperties(schema) {
914
989
  }
915
990
  return { properties, required };
916
991
  }
917
- function schemaToTsType(propertyName, schema, parentName, nestedInterfaces) {
918
- const rawType = schema["type"];
919
- let type;
920
- let nullableFrom31Type = false;
921
- if (Array.isArray(rawType)) {
922
- const nonNull = rawType.filter((t) => t !== "null");
923
- nullableFrom31Type = nonNull.length !== rawType.length;
924
- type = nonNull[0];
925
- } else {
926
- type = rawType;
927
- }
928
- const isNullable = schema["nullable"] === true || nullableFrom31Type;
929
- const enumValues = schema.enum ? schema.enum.filter((v) => v !== null) : void 0;
930
- if (enumValues && enumValues.length > 0) {
931
- const enumUnion = enumValues.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ");
932
- return isNullable ? `${enumUnion} | null` : enumUnion;
933
- }
934
- if (isNullable) {
935
- const baseSchema = { ...schema, nullable: void 0, type };
936
- const baseType = schemaToTsType(propertyName, baseSchema, parentName, nestedInterfaces);
937
- return `${baseType} | null`;
938
- }
939
- const unionSchemas = schema.oneOf ?? schema.anyOf;
940
- if (unionSchemas) {
941
- const disc = schema["discriminator"];
942
- const mappingKeys = disc?.mapping ? Object.keys(disc.mapping) : void 0;
943
- const types = unionSchemas.filter((s) => !isReferenceObject(s)).map((s, i) => {
944
- if ((s.type === "object" || s.properties) && mappingKeys) {
945
- const variantName = mappingKeys[i] ? toPascalCase(mappingKeys[i]) : `Variant${i + 1}`;
946
- const nestedName = `${parentName}${toPascalCase(propertyName)}${variantName}`;
947
- const nestedInterface = generateNestedInterface(nestedName, s);
992
+ function schemaToTsType(propertyName, schema, parentName, nestedInterfaces, seen) {
993
+ if (seen.has(schema)) {
994
+ return "unknown";
995
+ }
996
+ seen.add(schema);
997
+ try {
998
+ const rawType = schema["type"];
999
+ let type;
1000
+ let nullableFrom31Type = false;
1001
+ if (Array.isArray(rawType)) {
1002
+ const nonNull = rawType.filter((t) => t !== "null");
1003
+ nullableFrom31Type = nonNull.length !== rawType.length;
1004
+ type = nonNull[0];
1005
+ } else {
1006
+ type = rawType;
1007
+ }
1008
+ const isNullable = schema["nullable"] === true || nullableFrom31Type;
1009
+ const enumValues = schema.enum ? schema.enum.filter((v) => v !== null) : void 0;
1010
+ if (enumValues && enumValues.length > 0) {
1011
+ const enumUnion = enumValues.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ");
1012
+ return isNullable ? `${enumUnion} | null` : enumUnion;
1013
+ }
1014
+ if (isNullable) {
1015
+ const baseSchema = { ...schema, nullable: void 0, type };
1016
+ const baseType = schemaToTsType(propertyName, baseSchema, parentName, nestedInterfaces, seen);
1017
+ return `${baseType} | null`;
1018
+ }
1019
+ const unionSchemas = schema.oneOf ?? schema.anyOf;
1020
+ if (unionSchemas) {
1021
+ const disc = schema["discriminator"];
1022
+ const mappingKeys = disc?.mapping ? Object.keys(disc.mapping) : void 0;
1023
+ const types = unionSchemas.filter((s) => !isReferenceObject(s)).map((s, i) => {
1024
+ if ((s.type === "object" || s.properties) && mappingKeys) {
1025
+ const variantName = mappingKeys[i] ? toPascalCase(mappingKeys[i]) : `Variant${i + 1}`;
1026
+ const nestedName = `${parentName}${toPascalCase(propertyName)}${variantName}`;
1027
+ const nestedInterface = generateNestedInterface(nestedName, s, seen);
1028
+ nestedInterfaces.push(nestedInterface);
1029
+ return nestedName;
1030
+ }
1031
+ return schemaToTsType(propertyName, s, parentName, nestedInterfaces, seen);
1032
+ });
1033
+ return types.join(" | ");
1034
+ }
1035
+ if (schema.allOf) {
1036
+ const types = schema.allOf.filter((s) => !isReferenceObject(s)).map((s) => {
1037
+ const t = schemaToTsType(propertyName, s, parentName, nestedInterfaces, seen);
1038
+ return t.includes(" | ") ? `(${t})` : t;
1039
+ });
1040
+ return types.join(" & ");
1041
+ }
1042
+ if (type === "null") return "null";
1043
+ if (type === "string") return "string";
1044
+ if (type === "integer" || type === "number") return "number";
1045
+ if (type === "boolean") return "boolean";
1046
+ const arrayItems = schema["items"];
1047
+ if (type === "array" && arrayItems) {
1048
+ const items = arrayItems;
1049
+ if (items.type === "object" || items.properties) {
1050
+ const nestedName = `${parentName}${toPascalCase(propertyName)}Item`;
1051
+ const nestedInterface = generateNestedInterface(nestedName, items, seen);
948
1052
  nestedInterfaces.push(nestedInterface);
949
- return nestedName;
1053
+ return `${nestedName}[]`;
950
1054
  }
951
- return schemaToTsType(propertyName, s, parentName, nestedInterfaces);
952
- });
953
- return types.join(" | ");
954
- }
955
- if (schema.allOf) {
956
- const types = schema.allOf.filter((s) => !isReferenceObject(s)).map((s) => {
957
- const t = schemaToTsType(propertyName, s, parentName, nestedInterfaces);
958
- return t.includes(" | ") ? `(${t})` : t;
959
- });
960
- return types.join(" & ");
961
- }
962
- if (type === "null") return "null";
963
- if (type === "string") return "string";
964
- if (type === "integer" || type === "number") return "number";
965
- if (type === "boolean") return "boolean";
966
- const arrayItems = schema["items"];
967
- if (type === "array" && arrayItems) {
968
- const items = arrayItems;
969
- if (items.type === "object" || items.properties) {
970
- const nestedName = `${parentName}${toPascalCase(propertyName)}Item`;
971
- const nestedInterface = generateNestedInterface(nestedName, items);
972
- nestedInterfaces.push(nestedInterface);
973
- return `${nestedName}[]`;
1055
+ if (items.enum) {
1056
+ return `(${items.enum.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ")})[]`;
1057
+ }
1058
+ return `${schemaToTsType(propertyName, items, parentName, nestedInterfaces, seen)}[]`;
974
1059
  }
975
- if (items.enum) {
976
- return `(${items.enum.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ")})[]`;
1060
+ if (type === "object" || schema.properties) {
1061
+ const nestedName = `${parentName}${toPascalCase(propertyName)}`;
1062
+ const nestedInterface = generateNestedInterface(nestedName, schema, seen);
1063
+ nestedInterfaces.push(nestedInterface);
1064
+ return nestedName;
977
1065
  }
978
- return `${schemaToTsType(propertyName, items, parentName, nestedInterfaces)}[]`;
979
- }
980
- if (type === "object" || schema.properties) {
981
- const nestedName = `${parentName}${toPascalCase(propertyName)}`;
982
- const nestedInterface = generateNestedInterface(nestedName, schema);
983
- nestedInterfaces.push(nestedInterface);
984
- return nestedName;
1066
+ return "unknown";
1067
+ } finally {
1068
+ seen.delete(schema);
985
1069
  }
986
- return "unknown";
987
1070
  }
988
- function generateNestedInterface(name, schema) {
1071
+ function generateNestedInterface(name, schema, seen) {
989
1072
  const lines = [];
990
1073
  const nestedInterfaces = [];
991
1074
  const required = new Set(schema.required ?? []);
@@ -993,7 +1076,7 @@ function generateNestedInterface(name, schema) {
993
1076
  for (const [propName, propSchema] of Object.entries(schema.properties ?? {})) {
994
1077
  if (isReferenceObject(propSchema)) continue;
995
1078
  const optional = required.has(propName) ? "" : "?";
996
- const tsType = schemaToTsType(propName, propSchema, name, nestedInterfaces);
1079
+ const tsType = schemaToTsType(propName, propSchema, name, nestedInterfaces, seen);
997
1080
  lines.push(` ${propName}${optional}: ${tsType};`);
998
1081
  }
999
1082
  lines.push(`}`);
@@ -1465,6 +1548,12 @@ run().catch((error) => {
1465
1548
  if (error.message.includes("Only OpenAPI 3.x")) {
1466
1549
  logger.error("Only OpenAPI 3.x specifications are supported. Swagger 2.0 specs must be converted first.");
1467
1550
  logger.info("Tip: Use https://converter.swagger.io to convert Swagger 2.0 to OpenAPI 3.0");
1551
+ } else if (error instanceof RangeError && /stack|recursion/i.test(error.message)) {
1552
+ logger.error("Schema generation failed: stack overflow during schema traversal.");
1553
+ logger.info("Tip: this usually means your spec contains a circular $ref chain (e.g. A \u2192 B \u2192 A).");
1554
+ logger.info(
1555
+ " Identify the cycle and either flatten it or annotate the back-edge with x-ng-forge-type to control how it renders."
1556
+ );
1468
1557
  } else {
1469
1558
  logger.error(error.message);
1470
1559
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ng-forge/openapi-generator",
3
- "version": "0.9.0-next.12",
3
+ "version": "0.9.0-next.14",
4
4
  "description": "Generate @ng-forge/dynamic-forms configurations from OpenAPI specs",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -45,9 +45,9 @@
45
45
  "sideEffects": false,
46
46
  "dependencies": {
47
47
  "@apidevtools/swagger-parser": "^10.1.1",
48
- "commander": "^13.1.0",
48
+ "commander": "^14.0.3",
49
49
  "@inquirer/prompts": "^8.3.0",
50
- "chokidar": "^4.0.3",
50
+ "chokidar": "^5.0.0",
51
51
  "chalk": "^5.4.1",
52
52
  "openapi-types": "^12.1.3"
53
53
  }
@@ -1 +1 @@
1
- {"version":3,"file":"interface-generator.d.ts","sourceRoot":"","sources":["../../../../../packages/openapi-generator/src/generator/interface-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAI5D,KAAK,YAAY,GAAG,SAAS,CAAC,YAAY,GAAG,WAAW,CAAC,YAAY,CAAC;AAEtE,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,yBAAyB,GAAG,MAAM,CA0BlG"}
1
+ {"version":3,"file":"interface-generator.d.ts","sourceRoot":"","sources":["../../../../../packages/openapi-generator/src/generator/interface-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAI5D,KAAK,YAAY,GAAG,SAAS,CAAC,YAAY,GAAG,WAAW,CAAC,YAAY,CAAC;AAEtE,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,yBAAyB,GAAG,MAAM,CAkClG"}
package/src/index.js CHANGED
@@ -63,52 +63,60 @@ function formatEndpointLabel(endpoint) {
63
63
  }
64
64
 
65
65
  // packages/openapi-generator/src/parser/schema-walker.ts
66
- function walkSchema(schema, requiredFields = []) {
66
+ function walkSchema(schema, requiredFields = [], seen = /* @__PURE__ */ new WeakSet()) {
67
67
  const warnings = [];
68
- if (schema.allOf) {
69
- return walkAllOf(schema.allOf, requiredFields, warnings);
70
- }
71
- if (schema.oneOf && schema.discriminator) {
72
- return walkOneOfWithDiscriminator(
73
- schema.oneOf,
74
- schema.discriminator,
75
- requiredFields,
76
- warnings
77
- );
78
- }
79
- if (schema.oneOf && !schema.discriminator) {
80
- warnings.push("oneOf without discriminator is not supported \u2014 add a discriminator to generate conditional form groups");
81
- return { properties: [], warnings };
82
- }
83
- if (schema.anyOf) {
84
- warnings.push("anyOf schemas are not supported and were skipped");
85
- return { properties: [], warnings };
68
+ if (seen.has(schema)) {
69
+ return { properties: [], warnings: ["Circular schema reference detected during allOf merge \u2014 nested schema skipped"] };
86
70
  }
87
- if ("if" in schema) {
88
- warnings.push("if/then/else schemas are not supported and were skipped");
89
- return { properties: [], warnings };
90
- }
91
- if (schema.additionalProperties && schema.additionalProperties !== false) {
92
- warnings.push("additionalProperties are not supported and were skipped");
93
- }
94
- const properties = [];
95
- const required = new Set(schema.required ?? requiredFields);
96
- for (const [name, propSchema] of Object.entries(schema.properties ?? {})) {
97
- if (isReferenceObject(propSchema)) continue;
98
- properties.push({
99
- name,
100
- schema: propSchema,
101
- required: required.has(name)
102
- });
71
+ seen.add(schema);
72
+ try {
73
+ if (schema.allOf) {
74
+ return walkAllOf(schema.allOf, requiredFields, warnings, seen);
75
+ }
76
+ if (schema.oneOf && schema.discriminator) {
77
+ return walkOneOfWithDiscriminator(
78
+ schema.oneOf,
79
+ schema.discriminator,
80
+ requiredFields,
81
+ warnings
82
+ );
83
+ }
84
+ if (schema.oneOf && !schema.discriminator) {
85
+ warnings.push("oneOf without discriminator is not supported \u2014 add a discriminator to generate conditional form groups");
86
+ return { properties: [], warnings };
87
+ }
88
+ if (schema.anyOf) {
89
+ warnings.push("anyOf schemas are not supported and were skipped");
90
+ return { properties: [], warnings };
91
+ }
92
+ if ("if" in schema) {
93
+ warnings.push("if/then/else schemas are not supported and were skipped");
94
+ return { properties: [], warnings };
95
+ }
96
+ if (schema.additionalProperties && schema.additionalProperties !== false) {
97
+ warnings.push("additionalProperties are not supported and were skipped");
98
+ }
99
+ const properties = [];
100
+ const required = new Set(schema.required ?? requiredFields);
101
+ for (const [name, propSchema] of Object.entries(schema.properties ?? {})) {
102
+ if (isReferenceObject(propSchema)) continue;
103
+ properties.push({
104
+ name,
105
+ schema: propSchema,
106
+ required: required.has(name)
107
+ });
108
+ }
109
+ return { properties, warnings };
110
+ } finally {
111
+ seen.delete(schema);
103
112
  }
104
- return { properties, warnings };
105
113
  }
106
- function walkAllOf(schemas, requiredFields, warnings) {
114
+ function walkAllOf(schemas, requiredFields, warnings, seen) {
107
115
  const mergedProperties = /* @__PURE__ */ new Map();
108
116
  const allRequired = new Set(requiredFields);
109
117
  for (const schema of schemas) {
110
118
  if (isReferenceObject(schema)) continue;
111
- const walked = walkSchema(schema, []);
119
+ const walked = walkSchema(schema, [], seen);
112
120
  warnings.push(...walked.warnings);
113
121
  for (const prop of walked.properties) {
114
122
  mergedProperties.set(prop.name, prop);
@@ -445,7 +453,17 @@ var logger = {
445
453
  };
446
454
 
447
455
  // packages/openapi-generator/src/mapper/schema-to-fields.ts
448
- var NULLABLE_SUPPORTED_FIELD_TYPES = /* @__PURE__ */ new Set(["input", "textarea", "select", "radio", "multi-checkbox", "slider", "datepicker"]);
456
+ var NULLABLE_SUPPORTED_FIELD_TYPES = /* @__PURE__ */ new Set([
457
+ "input",
458
+ "textarea",
459
+ "select",
460
+ "radio",
461
+ "multi-checkbox",
462
+ "slider",
463
+ "datepicker",
464
+ "checkbox",
465
+ "toggle"
466
+ ]);
449
467
  var CONTAINER_FIELD_TYPES = /* @__PURE__ */ new Set(["group", "array", "row", "page", "container"]);
450
468
  function singularize(label) {
451
469
  if (label.length >= 4 && label.endsWith("s") && !label.endsWith("ss")) {
@@ -454,54 +472,76 @@ function singularize(label) {
454
472
  return label;
455
473
  }
456
474
  function mapSchemaToFields(schema, requiredFields, options = {}) {
457
- const walked = walkSchema(schema, requiredFields);
458
- const ambiguousFields = [];
459
- const warnings = [...walked.warnings];
460
- if (walked.discriminator) {
461
- const discConfig = mapDiscriminator(walked.discriminator);
462
- const fields2 = [discConfig.discriminatorField];
463
- for (const group of discConfig.conditionalGroups) {
464
- const variantSchema = walked.discriminator.mapping[group.discriminatorValue];
465
- if (variantSchema) {
466
- const variantResult = mapSchemaToFields(variantSchema, variantSchema.required ?? [], {
467
- ...options,
468
- schemaName: options.schemaName ? `${options.schemaName}.${group.discriminatorValue}` : group.discriminatorValue
469
- });
470
- const variantFields = variantResult.fields.filter((f) => f.key !== walked.discriminator.propertyName);
471
- ambiguousFields.push(...variantResult.ambiguousFields);
472
- warnings.push(...variantResult.warnings);
473
- if (variantFields.length > 0) {
474
- fields2.push({
475
- key: `${group.discriminatorValue}Variant`,
476
- type: "group",
477
- fields: variantFields,
478
- logic: [
479
- {
480
- type: "hidden",
481
- condition: {
482
- type: "fieldValue",
483
- fieldPath: walked.discriminator.propertyName,
484
- operator: "notEquals",
485
- value: group.discriminatorValue
475
+ return mapSchemaToFieldsInternal(schema, requiredFields, options, /* @__PURE__ */ new WeakSet());
476
+ }
477
+ function mapSchemaToFieldsInternal(schema, requiredFields, options, seen) {
478
+ if (seen.has(schema)) {
479
+ return {
480
+ fields: [],
481
+ ambiguousFields: [],
482
+ warnings: [
483
+ `Circular schema reference at '${options.schemaName ?? "<root>"}' \u2014 nested fields skipped. Use x-ng-forge-type to override how this field renders.`
484
+ ]
485
+ };
486
+ }
487
+ seen.add(schema);
488
+ try {
489
+ const walked = walkSchema(schema, requiredFields);
490
+ const ambiguousFields = [];
491
+ const warnings = [...walked.warnings];
492
+ if (walked.discriminator) {
493
+ const discConfig = mapDiscriminator(walked.discriminator);
494
+ const fields2 = [discConfig.discriminatorField];
495
+ for (const group of discConfig.conditionalGroups) {
496
+ const variantSchema = walked.discriminator.mapping[group.discriminatorValue];
497
+ if (variantSchema) {
498
+ const variantResult = mapSchemaToFieldsInternal(
499
+ variantSchema,
500
+ variantSchema.required ?? [],
501
+ {
502
+ ...options,
503
+ schemaName: options.schemaName ? `${options.schemaName}.${group.discriminatorValue}` : group.discriminatorValue
504
+ },
505
+ seen
506
+ );
507
+ const variantFields = variantResult.fields.filter((f) => f.key !== walked.discriminator.propertyName);
508
+ ambiguousFields.push(...variantResult.ambiguousFields);
509
+ warnings.push(...variantResult.warnings);
510
+ if (variantFields.length > 0) {
511
+ fields2.push({
512
+ key: `${group.discriminatorValue}Variant`,
513
+ type: "group",
514
+ fields: variantFields,
515
+ logic: [
516
+ {
517
+ type: "hidden",
518
+ condition: {
519
+ type: "fieldValue",
520
+ fieldPath: walked.discriminator.propertyName,
521
+ operator: "notEquals",
522
+ value: group.discriminatorValue
523
+ }
486
524
  }
487
- }
488
- ]
489
- });
525
+ ]
526
+ });
527
+ }
490
528
  }
491
529
  }
530
+ return { fields: fields2, ambiguousFields, warnings };
492
531
  }
493
- return { fields: fields2, ambiguousFields, warnings };
494
- }
495
- const fields = [];
496
- for (const prop of walked.properties) {
497
- const field = mapPropertyToField(prop, options, ambiguousFields, warnings);
498
- if (field) {
499
- fields.push(field);
532
+ const fields = [];
533
+ for (const prop of walked.properties) {
534
+ const field = mapPropertyToField(prop, options, seen, ambiguousFields, warnings);
535
+ if (field) {
536
+ fields.push(field);
537
+ }
500
538
  }
539
+ return { fields, ambiguousFields, warnings };
540
+ } finally {
541
+ seen.delete(schema);
501
542
  }
502
- return { fields, ambiguousFields, warnings };
503
543
  }
504
- function mapPropertyToField(prop, options, ambiguousFields, warnings) {
544
+ function mapPropertyToField(prop, options, seen, ambiguousFields, warnings) {
505
545
  if (prop.schema["deprecated"]) {
506
546
  warnings.push(`Property '${prop.name}' is deprecated and was skipped`);
507
547
  return void 0;
@@ -509,10 +549,12 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
509
549
  if (prop.schema.oneOf && prop.schema["discriminator"]) {
510
550
  const schemaPrefix2 = options.schemaName ?? "";
511
551
  const nestedSchemaName = schemaPrefix2 ? `${schemaPrefix2}.${prop.name}` : prop.name;
512
- const innerResult = mapSchemaToFields(prop.schema, prop.schema.required ?? [], {
513
- ...options,
514
- schemaName: nestedSchemaName
515
- });
552
+ const innerResult = mapSchemaToFieldsInternal(
553
+ prop.schema,
554
+ prop.schema.required ?? [],
555
+ { ...options, schemaName: nestedSchemaName },
556
+ seen
557
+ );
516
558
  ambiguousFields.push(...innerResult.ambiguousFields);
517
559
  warnings.push(...innerResult.warnings);
518
560
  return {
@@ -542,19 +584,19 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
542
584
  }
543
585
  }
544
586
  const validators = mapSchemaToValidators(prop.schema, prop.required);
587
+ const field = {
588
+ key: prop.name,
589
+ type: finalType
590
+ };
545
591
  const arraySchema = prop.schema;
546
- if (typeResult.fieldType === "array") {
592
+ if (finalType === "array") {
547
593
  if (arraySchema["minItems"] !== void 0) {
548
- validators.push({ type: "minLength", value: arraySchema["minItems"] });
594
+ field.minLength = arraySchema["minItems"];
549
595
  }
550
596
  if (arraySchema["maxItems"] !== void 0) {
551
- validators.push({ type: "maxLength", value: arraySchema["maxItems"] });
597
+ field.maxLength = arraySchema["maxItems"];
552
598
  }
553
599
  }
554
- const field = {
555
- key: prop.name,
556
- type: finalType
557
- };
558
600
  if (!CONTAINER_FIELD_TYPES.has(finalType)) {
559
601
  field.label = prop.schema["title"] ?? toLabel(prop.name);
560
602
  }
@@ -598,7 +640,15 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
598
640
  }
599
641
  }
600
642
  if (validators.length > 0) {
601
- field.validators = validators;
643
+ if (CONTAINER_FIELD_TYPES.has(finalType)) {
644
+ const droppedTypes = validators.map((v) => v.type).join(", ");
645
+ const recovery = finalType === "array" ? ` Use \`minLength: 1\` on the array or add validators to the template field.` : ` Add validators to individual child fields instead.`;
646
+ logger.verbose(
647
+ `Field '${fieldPath}': '${finalType}' container fields do not accept field-level validators \u2014 dropped: ${droppedTypes}.${recovery}`
648
+ );
649
+ } else {
650
+ field.validators = validators;
651
+ }
602
652
  }
603
653
  if (options.editable === false) {
604
654
  field.disabled = true;
@@ -625,14 +675,14 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
625
675
  const nestedSchemaName = schemaPrefix ? `${schemaPrefix}.${prop.name}` : prop.name;
626
676
  const nestedOptions = { ...options, schemaName: nestedSchemaName };
627
677
  if (typeResult.fieldType === "group" && prop.schema.properties) {
628
- const innerResult = mapSchemaToFields(prop.schema, prop.schema.required ?? [], nestedOptions);
678
+ const innerResult = mapSchemaToFieldsInternal(prop.schema, prop.schema.required ?? [], nestedOptions, seen);
629
679
  field.fields = innerResult.fields;
630
680
  ambiguousFields.push(...innerResult.ambiguousFields);
631
681
  } else if (typeResult.fieldType === "array" && prop.schema["items"]) {
632
682
  const items = prop.schema["items"];
633
683
  const fieldLabel = singularize(toLabel(prop.name));
634
684
  if (items.type === "object" || items.properties) {
635
- const innerResult = mapSchemaToFields(items, items.required ?? [], nestedOptions);
685
+ const innerResult = mapSchemaToFieldsInternal(items, items.required ?? [], nestedOptions, seen);
636
686
  field.template = innerResult.fields;
637
687
  ambiguousFields.push(...innerResult.ambiguousFields);
638
688
  warnings.push(...innerResult.warnings);
@@ -732,6 +782,12 @@ function generateFieldLines(field, indent) {
732
782
  }
733
783
  lines.push(`${indent} ],`);
734
784
  }
785
+ if (field.minLength !== void 0) {
786
+ lines.push(`${indent} minLength: ${field.minLength},`);
787
+ }
788
+ if (field.maxLength !== void 0) {
789
+ lines.push(`${indent} maxLength: ${field.maxLength},`);
790
+ }
735
791
  if (field.disabled) {
736
792
  lines.push(`${indent} disabled: true,`);
737
793
  }
@@ -820,15 +876,21 @@ function generateInterface(schema, options) {
820
876
  const interfaceName = toInterfaceName(options.method, options.path, options.operationId);
821
877
  const lines = [];
822
878
  const nestedInterfaces = [];
823
- const { properties, required } = resolveSchemaProperties(schema);
824
- lines.push(`export interface ${interfaceName} {`);
825
- for (const [name, propSchema] of properties) {
826
- if (isReferenceObject(propSchema)) continue;
827
- const optional = required.has(name) ? "" : "?";
828
- const tsType = schemaToTsType(name, propSchema, interfaceName, nestedInterfaces);
829
- lines.push(` ${name}${optional}: ${tsType};`);
879
+ const seen = /* @__PURE__ */ new WeakSet();
880
+ seen.add(schema);
881
+ try {
882
+ const { properties, required } = resolveSchemaProperties(schema, seen);
883
+ lines.push(`export interface ${interfaceName} {`);
884
+ for (const [name, propSchema] of properties) {
885
+ if (isReferenceObject(propSchema)) continue;
886
+ const optional = required.has(name) ? "" : "?";
887
+ const tsType = schemaToTsType(name, propSchema, interfaceName, nestedInterfaces, seen);
888
+ lines.push(` ${name}${optional}: ${tsType};`);
889
+ }
890
+ lines.push(`}`);
891
+ } finally {
892
+ seen.delete(schema);
830
893
  }
831
- lines.push(`}`);
832
894
  if (nestedInterfaces.length > 0) {
833
895
  return `// @generated by @ng-forge/openapi-generator
834
896
  ` + [...nestedInterfaces, "", ...lines, ""].join("\n");
@@ -837,18 +899,24 @@ function generateInterface(schema, options) {
837
899
  return `// @generated by @ng-forge/openapi-generator
838
900
  ` + lines.join("\n");
839
901
  }
840
- function resolveSchemaProperties(schema) {
902
+ function resolveSchemaProperties(schema, seen) {
841
903
  if (schema.allOf) {
842
904
  const merged = /* @__PURE__ */ new Map();
843
905
  const allRequired = new Set(schema.required ?? []);
844
906
  for (const sub of schema.allOf) {
845
907
  if (isReferenceObject(sub)) continue;
846
- const resolved = resolveSchemaProperties(sub);
847
- for (const [name, propSchema] of resolved.properties) {
848
- merged.set(name, propSchema);
849
- }
850
- for (const r of resolved.required) {
851
- allRequired.add(r);
908
+ if (seen.has(sub)) continue;
909
+ seen.add(sub);
910
+ try {
911
+ const resolved = resolveSchemaProperties(sub, seen);
912
+ for (const [name, propSchema] of resolved.properties) {
913
+ merged.set(name, propSchema);
914
+ }
915
+ for (const r of resolved.required) {
916
+ allRequired.add(r);
917
+ }
918
+ } finally {
919
+ seen.delete(sub);
852
920
  }
853
921
  }
854
922
  return { properties: Array.from(merged.entries()), required: allRequired };
@@ -860,7 +928,14 @@ function resolveSchemaProperties(schema) {
860
928
  const discriminatorValues = [];
861
929
  for (const variant of schema.oneOf) {
862
930
  if (isReferenceObject(variant)) continue;
863
- const resolved = resolveSchemaProperties(variant);
931
+ if (seen.has(variant)) continue;
932
+ seen.add(variant);
933
+ let resolved;
934
+ try {
935
+ resolved = resolveSchemaProperties(variant, seen);
936
+ } finally {
937
+ seen.delete(variant);
938
+ }
864
939
  for (const [name, propSchema] of resolved.properties) {
865
940
  if (name === discriminatorName) {
866
941
  if (propSchema.enum) {
@@ -906,78 +981,86 @@ function resolveSchemaProperties(schema) {
906
981
  }
907
982
  return { properties, required };
908
983
  }
909
- function schemaToTsType(propertyName, schema, parentName, nestedInterfaces) {
910
- const rawType = schema["type"];
911
- let type;
912
- let nullableFrom31Type = false;
913
- if (Array.isArray(rawType)) {
914
- const nonNull = rawType.filter((t) => t !== "null");
915
- nullableFrom31Type = nonNull.length !== rawType.length;
916
- type = nonNull[0];
917
- } else {
918
- type = rawType;
919
- }
920
- const isNullable = schema["nullable"] === true || nullableFrom31Type;
921
- const enumValues = schema.enum ? schema.enum.filter((v) => v !== null) : void 0;
922
- if (enumValues && enumValues.length > 0) {
923
- const enumUnion = enumValues.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ");
924
- return isNullable ? `${enumUnion} | null` : enumUnion;
925
- }
926
- if (isNullable) {
927
- const baseSchema = { ...schema, nullable: void 0, type };
928
- const baseType = schemaToTsType(propertyName, baseSchema, parentName, nestedInterfaces);
929
- return `${baseType} | null`;
930
- }
931
- const unionSchemas = schema.oneOf ?? schema.anyOf;
932
- if (unionSchemas) {
933
- const disc = schema["discriminator"];
934
- const mappingKeys = disc?.mapping ? Object.keys(disc.mapping) : void 0;
935
- const types = unionSchemas.filter((s) => !isReferenceObject(s)).map((s, i) => {
936
- if ((s.type === "object" || s.properties) && mappingKeys) {
937
- const variantName = mappingKeys[i] ? toPascalCase(mappingKeys[i]) : `Variant${i + 1}`;
938
- const nestedName = `${parentName}${toPascalCase(propertyName)}${variantName}`;
939
- const nestedInterface = generateNestedInterface(nestedName, s);
984
+ function schemaToTsType(propertyName, schema, parentName, nestedInterfaces, seen) {
985
+ if (seen.has(schema)) {
986
+ return "unknown";
987
+ }
988
+ seen.add(schema);
989
+ try {
990
+ const rawType = schema["type"];
991
+ let type;
992
+ let nullableFrom31Type = false;
993
+ if (Array.isArray(rawType)) {
994
+ const nonNull = rawType.filter((t) => t !== "null");
995
+ nullableFrom31Type = nonNull.length !== rawType.length;
996
+ type = nonNull[0];
997
+ } else {
998
+ type = rawType;
999
+ }
1000
+ const isNullable = schema["nullable"] === true || nullableFrom31Type;
1001
+ const enumValues = schema.enum ? schema.enum.filter((v) => v !== null) : void 0;
1002
+ if (enumValues && enumValues.length > 0) {
1003
+ const enumUnion = enumValues.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ");
1004
+ return isNullable ? `${enumUnion} | null` : enumUnion;
1005
+ }
1006
+ if (isNullable) {
1007
+ const baseSchema = { ...schema, nullable: void 0, type };
1008
+ const baseType = schemaToTsType(propertyName, baseSchema, parentName, nestedInterfaces, seen);
1009
+ return `${baseType} | null`;
1010
+ }
1011
+ const unionSchemas = schema.oneOf ?? schema.anyOf;
1012
+ if (unionSchemas) {
1013
+ const disc = schema["discriminator"];
1014
+ const mappingKeys = disc?.mapping ? Object.keys(disc.mapping) : void 0;
1015
+ const types = unionSchemas.filter((s) => !isReferenceObject(s)).map((s, i) => {
1016
+ if ((s.type === "object" || s.properties) && mappingKeys) {
1017
+ const variantName = mappingKeys[i] ? toPascalCase(mappingKeys[i]) : `Variant${i + 1}`;
1018
+ const nestedName = `${parentName}${toPascalCase(propertyName)}${variantName}`;
1019
+ const nestedInterface = generateNestedInterface(nestedName, s, seen);
1020
+ nestedInterfaces.push(nestedInterface);
1021
+ return nestedName;
1022
+ }
1023
+ return schemaToTsType(propertyName, s, parentName, nestedInterfaces, seen);
1024
+ });
1025
+ return types.join(" | ");
1026
+ }
1027
+ if (schema.allOf) {
1028
+ const types = schema.allOf.filter((s) => !isReferenceObject(s)).map((s) => {
1029
+ const t = schemaToTsType(propertyName, s, parentName, nestedInterfaces, seen);
1030
+ return t.includes(" | ") ? `(${t})` : t;
1031
+ });
1032
+ return types.join(" & ");
1033
+ }
1034
+ if (type === "null") return "null";
1035
+ if (type === "string") return "string";
1036
+ if (type === "integer" || type === "number") return "number";
1037
+ if (type === "boolean") return "boolean";
1038
+ const arrayItems = schema["items"];
1039
+ if (type === "array" && arrayItems) {
1040
+ const items = arrayItems;
1041
+ if (items.type === "object" || items.properties) {
1042
+ const nestedName = `${parentName}${toPascalCase(propertyName)}Item`;
1043
+ const nestedInterface = generateNestedInterface(nestedName, items, seen);
940
1044
  nestedInterfaces.push(nestedInterface);
941
- return nestedName;
1045
+ return `${nestedName}[]`;
942
1046
  }
943
- return schemaToTsType(propertyName, s, parentName, nestedInterfaces);
944
- });
945
- return types.join(" | ");
946
- }
947
- if (schema.allOf) {
948
- const types = schema.allOf.filter((s) => !isReferenceObject(s)).map((s) => {
949
- const t = schemaToTsType(propertyName, s, parentName, nestedInterfaces);
950
- return t.includes(" | ") ? `(${t})` : t;
951
- });
952
- return types.join(" & ");
953
- }
954
- if (type === "null") return "null";
955
- if (type === "string") return "string";
956
- if (type === "integer" || type === "number") return "number";
957
- if (type === "boolean") return "boolean";
958
- const arrayItems = schema["items"];
959
- if (type === "array" && arrayItems) {
960
- const items = arrayItems;
961
- if (items.type === "object" || items.properties) {
962
- const nestedName = `${parentName}${toPascalCase(propertyName)}Item`;
963
- const nestedInterface = generateNestedInterface(nestedName, items);
964
- nestedInterfaces.push(nestedInterface);
965
- return `${nestedName}[]`;
1047
+ if (items.enum) {
1048
+ return `(${items.enum.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ")})[]`;
1049
+ }
1050
+ return `${schemaToTsType(propertyName, items, parentName, nestedInterfaces, seen)}[]`;
966
1051
  }
967
- if (items.enum) {
968
- return `(${items.enum.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ")})[]`;
1052
+ if (type === "object" || schema.properties) {
1053
+ const nestedName = `${parentName}${toPascalCase(propertyName)}`;
1054
+ const nestedInterface = generateNestedInterface(nestedName, schema, seen);
1055
+ nestedInterfaces.push(nestedInterface);
1056
+ return nestedName;
969
1057
  }
970
- return `${schemaToTsType(propertyName, items, parentName, nestedInterfaces)}[]`;
971
- }
972
- if (type === "object" || schema.properties) {
973
- const nestedName = `${parentName}${toPascalCase(propertyName)}`;
974
- const nestedInterface = generateNestedInterface(nestedName, schema);
975
- nestedInterfaces.push(nestedInterface);
976
- return nestedName;
1058
+ return "unknown";
1059
+ } finally {
1060
+ seen.delete(schema);
977
1061
  }
978
- return "unknown";
979
1062
  }
980
- function generateNestedInterface(name, schema) {
1063
+ function generateNestedInterface(name, schema, seen) {
981
1064
  const lines = [];
982
1065
  const nestedInterfaces = [];
983
1066
  const required = new Set(schema.required ?? []);
@@ -985,7 +1068,7 @@ function generateNestedInterface(name, schema) {
985
1068
  for (const [propName, propSchema] of Object.entries(schema.properties ?? {})) {
986
1069
  if (isReferenceObject(propSchema)) continue;
987
1070
  const optional = required.has(propName) ? "" : "?";
988
- const tsType = schemaToTsType(propName, propSchema, name, nestedInterfaces);
1071
+ const tsType = schemaToTsType(propName, propSchema, name, nestedInterfaces, seen);
989
1072
  lines.push(` ${propName}${optional}: ${tsType};`);
990
1073
  }
991
1074
  lines.push(`}`);
@@ -29,6 +29,8 @@ export interface FieldConfig {
29
29
  props?: Record<string, unknown>;
30
30
  } | false;
31
31
  logic?: LogicConfig[];
32
+ minLength?: number;
33
+ maxLength?: number;
32
34
  }
33
35
  export interface MappingResult {
34
36
  fields: FieldConfig[];
@@ -1 +1 @@
1
- {"version":3,"file":"schema-to-fields.d.ts","sourceRoot":"","sources":["../../../../../packages/openapi-generator/src/mapper/schema-to-fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,4BAA4B,CAAC;AAG/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAiC9D,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,WAAW,GAAG,WAAW,EAAE,CAAC;IACvC,SAAS,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,GAAG,KAAK,CAAC;IACvE,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,GAAG,KAAK,CAAC;IAC1E,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,cAAmB,GAAG,aAAa,CA0D7H"}
1
+ {"version":3,"file":"schema-to-fields.d.ts","sourceRoot":"","sources":["../../../../../packages/openapi-generator/src/mapper/schema-to-fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,4BAA4B,CAAC;AAG/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AA4C9D,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,WAAW,GAAG,WAAW,EAAE,CAAC;IACvC,SAAS,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,GAAG,KAAK,CAAC;IACvE,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,GAAG,KAAK,CAAC;IAC1E,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;IAItB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,cAAmB,GAAG,aAAa,CAI7H"}
@@ -13,5 +13,5 @@ export interface WalkedSchema {
13
13
  };
14
14
  warnings: string[];
15
15
  }
16
- export declare function walkSchema(schema: SchemaObject, requiredFields?: string[]): WalkedSchema;
16
+ export declare function walkSchema(schema: SchemaObject, requiredFields?: string[], seen?: WeakSet<SchemaObject>): WalkedSchema;
17
17
  //# sourceMappingURL=schema-walker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema-walker.d.ts","sourceRoot":"","sources":["../../../../../packages/openapi-generator/src/parser/schema-walker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5D,MAAM,MAAM,YAAY,GAAG,SAAS,CAAC,YAAY,GAAG,WAAW,CAAC,YAAY,CAAC;AAE7E,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,aAAa,CAAC,EAAE;QACd,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;KACvC,CAAC;IACF,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,EAAE,cAAc,GAAE,MAAM,EAAO,GAAG,YAAY,CAuD5F"}
1
+ {"version":3,"file":"schema-walker.d.ts","sourceRoot":"","sources":["../../../../../packages/openapi-generator/src/parser/schema-walker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5D,MAAM,MAAM,YAAY,GAAG,SAAS,CAAC,YAAY,GAAG,WAAW,CAAC,YAAY,CAAC;AAE7E,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,aAAa,CAAC,EAAE;QACd,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;KACvC,CAAC;IACF,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,EAAE,cAAc,GAAE,MAAM,EAAO,EAAE,IAAI,GAAE,OAAO,CAAC,YAAY,CAAiB,GAAG,YAAY,CAoEzI"}