@ng-forge/openapi-generator 0.9.0-next.9 → 0.10.0-next.1

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
  }
@@ -581,11 +623,17 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
581
623
  if (prop.schema.default !== void 0) {
582
624
  field.value = prop.schema.default;
583
625
  }
584
- if (!ngForgeType && finalType === typeResult.fieldType && typeResult.props && Object.keys(typeResult.props).length > 0) {
626
+ if (!ngForgeType && finalType === typeResult.fieldType && typeResult.props && Object.keys(typeResult.props).length > 0 && !CONTAINER_FIELD_TYPES.has(finalType)) {
585
627
  field.props = typeResult.props;
586
628
  }
587
629
  if (prop.schema.description) {
588
- field.props = { ...field.props, hint: prop.schema.description };
630
+ if (CONTAINER_FIELD_TYPES.has(finalType)) {
631
+ logger.verbose(
632
+ `Field '${fieldPath}': '${finalType}' container fields do not accept props \u2014 dropped description hint. Add a sibling 'text' field manually if you want to render this description.`
633
+ );
634
+ } else {
635
+ field.props = { ...field.props, hint: prop.schema.description };
636
+ }
589
637
  }
590
638
  if (prop.schema.enum) {
591
639
  const filteredEnum = prop.schema.enum.filter((v) => v !== null);
@@ -606,7 +654,15 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
606
654
  }
607
655
  }
608
656
  if (validators.length > 0) {
609
- field.validators = validators;
657
+ if (CONTAINER_FIELD_TYPES.has(finalType)) {
658
+ const droppedTypes = validators.map((v) => v.type).join(", ");
659
+ const recovery = finalType === "array" ? ` Use \`minLength: 1\` on the array or add validators to the template field.` : ` Add validators to individual child fields instead.`;
660
+ logger.verbose(
661
+ `Field '${fieldPath}': '${finalType}' container fields do not accept field-level validators \u2014 dropped: ${droppedTypes}.${recovery}`
662
+ );
663
+ } else {
664
+ field.validators = validators;
665
+ }
610
666
  }
611
667
  if (options.editable === false) {
612
668
  field.disabled = true;
@@ -633,14 +689,14 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
633
689
  const nestedSchemaName = schemaPrefix ? `${schemaPrefix}.${prop.name}` : prop.name;
634
690
  const nestedOptions = { ...options, schemaName: nestedSchemaName };
635
691
  if (typeResult.fieldType === "group" && prop.schema.properties) {
636
- const innerResult = mapSchemaToFields(prop.schema, prop.schema.required ?? [], nestedOptions);
692
+ const innerResult = mapSchemaToFieldsInternal(prop.schema, prop.schema.required ?? [], nestedOptions, seen);
637
693
  field.fields = innerResult.fields;
638
694
  ambiguousFields.push(...innerResult.ambiguousFields);
639
695
  } else if (typeResult.fieldType === "array" && prop.schema["items"]) {
640
696
  const items = prop.schema["items"];
641
697
  const fieldLabel = singularize(toLabel(prop.name));
642
698
  if (items.type === "object" || items.properties) {
643
- const innerResult = mapSchemaToFields(items, items.required ?? [], nestedOptions);
699
+ const innerResult = mapSchemaToFieldsInternal(items, items.required ?? [], nestedOptions, seen);
644
700
  field.template = innerResult.fields;
645
701
  ambiguousFields.push(...innerResult.ambiguousFields);
646
702
  warnings.push(...innerResult.warnings);
@@ -740,6 +796,12 @@ function generateFieldLines(field, indent) {
740
796
  }
741
797
  lines.push(`${indent} ],`);
742
798
  }
799
+ if (field.minLength !== void 0) {
800
+ lines.push(`${indent} minLength: ${field.minLength},`);
801
+ }
802
+ if (field.maxLength !== void 0) {
803
+ lines.push(`${indent} maxLength: ${field.maxLength},`);
804
+ }
743
805
  if (field.disabled) {
744
806
  lines.push(`${indent} disabled: true,`);
745
807
  }
@@ -828,15 +890,21 @@ function generateInterface(schema, options) {
828
890
  const interfaceName = toInterfaceName(options.method, options.path, options.operationId);
829
891
  const lines = [];
830
892
  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};`);
893
+ const seen = /* @__PURE__ */ new WeakSet();
894
+ seen.add(schema);
895
+ try {
896
+ const { properties, required } = resolveSchemaProperties(schema, seen);
897
+ lines.push(`export interface ${interfaceName} {`);
898
+ for (const [name, propSchema] of properties) {
899
+ if (isReferenceObject(propSchema)) continue;
900
+ const optional = required.has(name) ? "" : "?";
901
+ const tsType = schemaToTsType(name, propSchema, interfaceName, nestedInterfaces, seen);
902
+ lines.push(` ${name}${optional}: ${tsType};`);
903
+ }
904
+ lines.push(`}`);
905
+ } finally {
906
+ seen.delete(schema);
838
907
  }
839
- lines.push(`}`);
840
908
  if (nestedInterfaces.length > 0) {
841
909
  return `// @generated by @ng-forge/openapi-generator
842
910
  ` + [...nestedInterfaces, "", ...lines, ""].join("\n");
@@ -845,18 +913,24 @@ function generateInterface(schema, options) {
845
913
  return `// @generated by @ng-forge/openapi-generator
846
914
  ` + lines.join("\n");
847
915
  }
848
- function resolveSchemaProperties(schema) {
916
+ function resolveSchemaProperties(schema, seen) {
849
917
  if (schema.allOf) {
850
918
  const merged = /* @__PURE__ */ new Map();
851
919
  const allRequired = new Set(schema.required ?? []);
852
920
  for (const sub of schema.allOf) {
853
921
  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);
922
+ if (seen.has(sub)) continue;
923
+ seen.add(sub);
924
+ try {
925
+ const resolved = resolveSchemaProperties(sub, seen);
926
+ for (const [name, propSchema] of resolved.properties) {
927
+ merged.set(name, propSchema);
928
+ }
929
+ for (const r of resolved.required) {
930
+ allRequired.add(r);
931
+ }
932
+ } finally {
933
+ seen.delete(sub);
860
934
  }
861
935
  }
862
936
  return { properties: Array.from(merged.entries()), required: allRequired };
@@ -868,7 +942,14 @@ function resolveSchemaProperties(schema) {
868
942
  const discriminatorValues = [];
869
943
  for (const variant of schema.oneOf) {
870
944
  if (isReferenceObject(variant)) continue;
871
- const resolved = resolveSchemaProperties(variant);
945
+ if (seen.has(variant)) continue;
946
+ seen.add(variant);
947
+ let resolved;
948
+ try {
949
+ resolved = resolveSchemaProperties(variant, seen);
950
+ } finally {
951
+ seen.delete(variant);
952
+ }
872
953
  for (const [name, propSchema] of resolved.properties) {
873
954
  if (name === discriminatorName) {
874
955
  if (propSchema.enum) {
@@ -914,78 +995,86 @@ function resolveSchemaProperties(schema) {
914
995
  }
915
996
  return { properties, required };
916
997
  }
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);
998
+ function schemaToTsType(propertyName, schema, parentName, nestedInterfaces, seen) {
999
+ if (seen.has(schema)) {
1000
+ return "unknown";
1001
+ }
1002
+ seen.add(schema);
1003
+ try {
1004
+ const rawType = schema["type"];
1005
+ let type;
1006
+ let nullableFrom31Type = false;
1007
+ if (Array.isArray(rawType)) {
1008
+ const nonNull = rawType.filter((t) => t !== "null");
1009
+ nullableFrom31Type = nonNull.length !== rawType.length;
1010
+ type = nonNull[0];
1011
+ } else {
1012
+ type = rawType;
1013
+ }
1014
+ const isNullable = schema["nullable"] === true || nullableFrom31Type;
1015
+ const enumValues = schema.enum ? schema.enum.filter((v) => v !== null) : void 0;
1016
+ if (enumValues && enumValues.length > 0) {
1017
+ const enumUnion = enumValues.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ");
1018
+ return isNullable ? `${enumUnion} | null` : enumUnion;
1019
+ }
1020
+ if (isNullable) {
1021
+ const baseSchema = { ...schema, nullable: void 0, type };
1022
+ const baseType = schemaToTsType(propertyName, baseSchema, parentName, nestedInterfaces, seen);
1023
+ return `${baseType} | null`;
1024
+ }
1025
+ const unionSchemas = schema.oneOf ?? schema.anyOf;
1026
+ if (unionSchemas) {
1027
+ const disc = schema["discriminator"];
1028
+ const mappingKeys = disc?.mapping ? Object.keys(disc.mapping) : void 0;
1029
+ const types = unionSchemas.filter((s) => !isReferenceObject(s)).map((s, i) => {
1030
+ if ((s.type === "object" || s.properties) && mappingKeys) {
1031
+ const variantName = mappingKeys[i] ? toPascalCase(mappingKeys[i]) : `Variant${i + 1}`;
1032
+ const nestedName = `${parentName}${toPascalCase(propertyName)}${variantName}`;
1033
+ const nestedInterface = generateNestedInterface(nestedName, s, seen);
1034
+ nestedInterfaces.push(nestedInterface);
1035
+ return nestedName;
1036
+ }
1037
+ return schemaToTsType(propertyName, s, parentName, nestedInterfaces, seen);
1038
+ });
1039
+ return types.join(" | ");
1040
+ }
1041
+ if (schema.allOf) {
1042
+ const types = schema.allOf.filter((s) => !isReferenceObject(s)).map((s) => {
1043
+ const t = schemaToTsType(propertyName, s, parentName, nestedInterfaces, seen);
1044
+ return t.includes(" | ") ? `(${t})` : t;
1045
+ });
1046
+ return types.join(" & ");
1047
+ }
1048
+ if (type === "null") return "null";
1049
+ if (type === "string") return "string";
1050
+ if (type === "integer" || type === "number") return "number";
1051
+ if (type === "boolean") return "boolean";
1052
+ const arrayItems = schema["items"];
1053
+ if (type === "array" && arrayItems) {
1054
+ const items = arrayItems;
1055
+ if (items.type === "object" || items.properties) {
1056
+ const nestedName = `${parentName}${toPascalCase(propertyName)}Item`;
1057
+ const nestedInterface = generateNestedInterface(nestedName, items, seen);
948
1058
  nestedInterfaces.push(nestedInterface);
949
- return nestedName;
1059
+ return `${nestedName}[]`;
950
1060
  }
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}[]`;
1061
+ if (items.enum) {
1062
+ return `(${items.enum.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ")})[]`;
1063
+ }
1064
+ return `${schemaToTsType(propertyName, items, parentName, nestedInterfaces, seen)}[]`;
974
1065
  }
975
- if (items.enum) {
976
- return `(${items.enum.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ")})[]`;
1066
+ if (type === "object" || schema.properties) {
1067
+ const nestedName = `${parentName}${toPascalCase(propertyName)}`;
1068
+ const nestedInterface = generateNestedInterface(nestedName, schema, seen);
1069
+ nestedInterfaces.push(nestedInterface);
1070
+ return nestedName;
977
1071
  }
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;
1072
+ return "unknown";
1073
+ } finally {
1074
+ seen.delete(schema);
985
1075
  }
986
- return "unknown";
987
1076
  }
988
- function generateNestedInterface(name, schema) {
1077
+ function generateNestedInterface(name, schema, seen) {
989
1078
  const lines = [];
990
1079
  const nestedInterfaces = [];
991
1080
  const required = new Set(schema.required ?? []);
@@ -993,7 +1082,7 @@ function generateNestedInterface(name, schema) {
993
1082
  for (const [propName, propSchema] of Object.entries(schema.properties ?? {})) {
994
1083
  if (isReferenceObject(propSchema)) continue;
995
1084
  const optional = required.has(propName) ? "" : "?";
996
- const tsType = schemaToTsType(propName, propSchema, name, nestedInterfaces);
1085
+ const tsType = schemaToTsType(propName, propSchema, name, nestedInterfaces, seen);
997
1086
  lines.push(` ${propName}${optional}: ${tsType};`);
998
1087
  }
999
1088
  lines.push(`}`);
@@ -1465,6 +1554,12 @@ run().catch((error) => {
1465
1554
  if (error.message.includes("Only OpenAPI 3.x")) {
1466
1555
  logger.error("Only OpenAPI 3.x specifications are supported. Swagger 2.0 specs must be converted first.");
1467
1556
  logger.info("Tip: Use https://converter.swagger.io to convert Swagger 2.0 to OpenAPI 3.0");
1557
+ } else if (error instanceof RangeError && /stack|recursion/i.test(error.message)) {
1558
+ logger.error("Schema generation failed: stack overflow during schema traversal.");
1559
+ logger.info("Tip: this usually means your spec contains a circular $ref chain (e.g. A \u2192 B \u2192 A).");
1560
+ logger.info(
1561
+ " Identify the cycle and either flatten it or annotate the back-edge with x-ng-forge-type to control how it renders."
1562
+ );
1468
1563
  } else {
1469
1564
  logger.error(error.message);
1470
1565
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ng-forge/openapi-generator",
3
- "version": "0.9.0-next.9",
3
+ "version": "0.10.0-next.1",
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
  }
@@ -573,11 +615,17 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
573
615
  if (prop.schema.default !== void 0) {
574
616
  field.value = prop.schema.default;
575
617
  }
576
- if (!ngForgeType && finalType === typeResult.fieldType && typeResult.props && Object.keys(typeResult.props).length > 0) {
618
+ if (!ngForgeType && finalType === typeResult.fieldType && typeResult.props && Object.keys(typeResult.props).length > 0 && !CONTAINER_FIELD_TYPES.has(finalType)) {
577
619
  field.props = typeResult.props;
578
620
  }
579
621
  if (prop.schema.description) {
580
- field.props = { ...field.props, hint: prop.schema.description };
622
+ if (CONTAINER_FIELD_TYPES.has(finalType)) {
623
+ logger.verbose(
624
+ `Field '${fieldPath}': '${finalType}' container fields do not accept props \u2014 dropped description hint. Add a sibling 'text' field manually if you want to render this description.`
625
+ );
626
+ } else {
627
+ field.props = { ...field.props, hint: prop.schema.description };
628
+ }
581
629
  }
582
630
  if (prop.schema.enum) {
583
631
  const filteredEnum = prop.schema.enum.filter((v) => v !== null);
@@ -598,7 +646,15 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
598
646
  }
599
647
  }
600
648
  if (validators.length > 0) {
601
- field.validators = validators;
649
+ if (CONTAINER_FIELD_TYPES.has(finalType)) {
650
+ const droppedTypes = validators.map((v) => v.type).join(", ");
651
+ const recovery = finalType === "array" ? ` Use \`minLength: 1\` on the array or add validators to the template field.` : ` Add validators to individual child fields instead.`;
652
+ logger.verbose(
653
+ `Field '${fieldPath}': '${finalType}' container fields do not accept field-level validators \u2014 dropped: ${droppedTypes}.${recovery}`
654
+ );
655
+ } else {
656
+ field.validators = validators;
657
+ }
602
658
  }
603
659
  if (options.editable === false) {
604
660
  field.disabled = true;
@@ -625,14 +681,14 @@ function mapPropertyToField(prop, options, ambiguousFields, warnings) {
625
681
  const nestedSchemaName = schemaPrefix ? `${schemaPrefix}.${prop.name}` : prop.name;
626
682
  const nestedOptions = { ...options, schemaName: nestedSchemaName };
627
683
  if (typeResult.fieldType === "group" && prop.schema.properties) {
628
- const innerResult = mapSchemaToFields(prop.schema, prop.schema.required ?? [], nestedOptions);
684
+ const innerResult = mapSchemaToFieldsInternal(prop.schema, prop.schema.required ?? [], nestedOptions, seen);
629
685
  field.fields = innerResult.fields;
630
686
  ambiguousFields.push(...innerResult.ambiguousFields);
631
687
  } else if (typeResult.fieldType === "array" && prop.schema["items"]) {
632
688
  const items = prop.schema["items"];
633
689
  const fieldLabel = singularize(toLabel(prop.name));
634
690
  if (items.type === "object" || items.properties) {
635
- const innerResult = mapSchemaToFields(items, items.required ?? [], nestedOptions);
691
+ const innerResult = mapSchemaToFieldsInternal(items, items.required ?? [], nestedOptions, seen);
636
692
  field.template = innerResult.fields;
637
693
  ambiguousFields.push(...innerResult.ambiguousFields);
638
694
  warnings.push(...innerResult.warnings);
@@ -732,6 +788,12 @@ function generateFieldLines(field, indent) {
732
788
  }
733
789
  lines.push(`${indent} ],`);
734
790
  }
791
+ if (field.minLength !== void 0) {
792
+ lines.push(`${indent} minLength: ${field.minLength},`);
793
+ }
794
+ if (field.maxLength !== void 0) {
795
+ lines.push(`${indent} maxLength: ${field.maxLength},`);
796
+ }
735
797
  if (field.disabled) {
736
798
  lines.push(`${indent} disabled: true,`);
737
799
  }
@@ -820,15 +882,21 @@ function generateInterface(schema, options) {
820
882
  const interfaceName = toInterfaceName(options.method, options.path, options.operationId);
821
883
  const lines = [];
822
884
  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};`);
885
+ const seen = /* @__PURE__ */ new WeakSet();
886
+ seen.add(schema);
887
+ try {
888
+ const { properties, required } = resolveSchemaProperties(schema, seen);
889
+ lines.push(`export interface ${interfaceName} {`);
890
+ for (const [name, propSchema] of properties) {
891
+ if (isReferenceObject(propSchema)) continue;
892
+ const optional = required.has(name) ? "" : "?";
893
+ const tsType = schemaToTsType(name, propSchema, interfaceName, nestedInterfaces, seen);
894
+ lines.push(` ${name}${optional}: ${tsType};`);
895
+ }
896
+ lines.push(`}`);
897
+ } finally {
898
+ seen.delete(schema);
830
899
  }
831
- lines.push(`}`);
832
900
  if (nestedInterfaces.length > 0) {
833
901
  return `// @generated by @ng-forge/openapi-generator
834
902
  ` + [...nestedInterfaces, "", ...lines, ""].join("\n");
@@ -837,18 +905,24 @@ function generateInterface(schema, options) {
837
905
  return `// @generated by @ng-forge/openapi-generator
838
906
  ` + lines.join("\n");
839
907
  }
840
- function resolveSchemaProperties(schema) {
908
+ function resolveSchemaProperties(schema, seen) {
841
909
  if (schema.allOf) {
842
910
  const merged = /* @__PURE__ */ new Map();
843
911
  const allRequired = new Set(schema.required ?? []);
844
912
  for (const sub of schema.allOf) {
845
913
  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);
914
+ if (seen.has(sub)) continue;
915
+ seen.add(sub);
916
+ try {
917
+ const resolved = resolveSchemaProperties(sub, seen);
918
+ for (const [name, propSchema] of resolved.properties) {
919
+ merged.set(name, propSchema);
920
+ }
921
+ for (const r of resolved.required) {
922
+ allRequired.add(r);
923
+ }
924
+ } finally {
925
+ seen.delete(sub);
852
926
  }
853
927
  }
854
928
  return { properties: Array.from(merged.entries()), required: allRequired };
@@ -860,7 +934,14 @@ function resolveSchemaProperties(schema) {
860
934
  const discriminatorValues = [];
861
935
  for (const variant of schema.oneOf) {
862
936
  if (isReferenceObject(variant)) continue;
863
- const resolved = resolveSchemaProperties(variant);
937
+ if (seen.has(variant)) continue;
938
+ seen.add(variant);
939
+ let resolved;
940
+ try {
941
+ resolved = resolveSchemaProperties(variant, seen);
942
+ } finally {
943
+ seen.delete(variant);
944
+ }
864
945
  for (const [name, propSchema] of resolved.properties) {
865
946
  if (name === discriminatorName) {
866
947
  if (propSchema.enum) {
@@ -906,78 +987,86 @@ function resolveSchemaProperties(schema) {
906
987
  }
907
988
  return { properties, required };
908
989
  }
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);
990
+ function schemaToTsType(propertyName, schema, parentName, nestedInterfaces, seen) {
991
+ if (seen.has(schema)) {
992
+ return "unknown";
993
+ }
994
+ seen.add(schema);
995
+ try {
996
+ const rawType = schema["type"];
997
+ let type;
998
+ let nullableFrom31Type = false;
999
+ if (Array.isArray(rawType)) {
1000
+ const nonNull = rawType.filter((t) => t !== "null");
1001
+ nullableFrom31Type = nonNull.length !== rawType.length;
1002
+ type = nonNull[0];
1003
+ } else {
1004
+ type = rawType;
1005
+ }
1006
+ const isNullable = schema["nullable"] === true || nullableFrom31Type;
1007
+ const enumValues = schema.enum ? schema.enum.filter((v) => v !== null) : void 0;
1008
+ if (enumValues && enumValues.length > 0) {
1009
+ const enumUnion = enumValues.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ");
1010
+ return isNullable ? `${enumUnion} | null` : enumUnion;
1011
+ }
1012
+ if (isNullable) {
1013
+ const baseSchema = { ...schema, nullable: void 0, type };
1014
+ const baseType = schemaToTsType(propertyName, baseSchema, parentName, nestedInterfaces, seen);
1015
+ return `${baseType} | null`;
1016
+ }
1017
+ const unionSchemas = schema.oneOf ?? schema.anyOf;
1018
+ if (unionSchemas) {
1019
+ const disc = schema["discriminator"];
1020
+ const mappingKeys = disc?.mapping ? Object.keys(disc.mapping) : void 0;
1021
+ const types = unionSchemas.filter((s) => !isReferenceObject(s)).map((s, i) => {
1022
+ if ((s.type === "object" || s.properties) && mappingKeys) {
1023
+ const variantName = mappingKeys[i] ? toPascalCase(mappingKeys[i]) : `Variant${i + 1}`;
1024
+ const nestedName = `${parentName}${toPascalCase(propertyName)}${variantName}`;
1025
+ const nestedInterface = generateNestedInterface(nestedName, s, seen);
1026
+ nestedInterfaces.push(nestedInterface);
1027
+ return nestedName;
1028
+ }
1029
+ return schemaToTsType(propertyName, s, parentName, nestedInterfaces, seen);
1030
+ });
1031
+ return types.join(" | ");
1032
+ }
1033
+ if (schema.allOf) {
1034
+ const types = schema.allOf.filter((s) => !isReferenceObject(s)).map((s) => {
1035
+ const t = schemaToTsType(propertyName, s, parentName, nestedInterfaces, seen);
1036
+ return t.includes(" | ") ? `(${t})` : t;
1037
+ });
1038
+ return types.join(" & ");
1039
+ }
1040
+ if (type === "null") return "null";
1041
+ if (type === "string") return "string";
1042
+ if (type === "integer" || type === "number") return "number";
1043
+ if (type === "boolean") return "boolean";
1044
+ const arrayItems = schema["items"];
1045
+ if (type === "array" && arrayItems) {
1046
+ const items = arrayItems;
1047
+ if (items.type === "object" || items.properties) {
1048
+ const nestedName = `${parentName}${toPascalCase(propertyName)}Item`;
1049
+ const nestedInterface = generateNestedInterface(nestedName, items, seen);
940
1050
  nestedInterfaces.push(nestedInterface);
941
- return nestedName;
1051
+ return `${nestedName}[]`;
942
1052
  }
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}[]`;
1053
+ if (items.enum) {
1054
+ return `(${items.enum.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ")})[]`;
1055
+ }
1056
+ return `${schemaToTsType(propertyName, items, parentName, nestedInterfaces, seen)}[]`;
966
1057
  }
967
- if (items.enum) {
968
- return `(${items.enum.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ")})[]`;
1058
+ if (type === "object" || schema.properties) {
1059
+ const nestedName = `${parentName}${toPascalCase(propertyName)}`;
1060
+ const nestedInterface = generateNestedInterface(nestedName, schema, seen);
1061
+ nestedInterfaces.push(nestedInterface);
1062
+ return nestedName;
969
1063
  }
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;
1064
+ return "unknown";
1065
+ } finally {
1066
+ seen.delete(schema);
977
1067
  }
978
- return "unknown";
979
1068
  }
980
- function generateNestedInterface(name, schema) {
1069
+ function generateNestedInterface(name, schema, seen) {
981
1070
  const lines = [];
982
1071
  const nestedInterfaces = [];
983
1072
  const required = new Set(schema.required ?? []);
@@ -985,7 +1074,7 @@ function generateNestedInterface(name, schema) {
985
1074
  for (const [propName, propSchema] of Object.entries(schema.properties ?? {})) {
986
1075
  if (isReferenceObject(propSchema)) continue;
987
1076
  const optional = required.has(propName) ? "" : "?";
988
- const tsType = schemaToTsType(propName, propSchema, name, nestedInterfaces);
1077
+ const tsType = schemaToTsType(propName, propSchema, name, nestedInterfaces, seen);
989
1078
  lines.push(` ${propName}${optional}: ${tsType};`);
990
1079
  }
991
1080
  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"}