@orval/core 8.12.2 → 8.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -143,6 +143,16 @@ const TEMPLATE_TAG_REGEX = /\${(.+?)}/g;
143
143
  function isReference(obj) {
144
144
  return !isNullish$1(obj) && Object.hasOwn(obj, "$ref");
145
145
  }
146
+ /**
147
+ * Discriminator helper for {@link OpenApiDynamicReferenceObject}.
148
+ *
149
+ * Returns `true` when `obj` has a `$dynamicRef` string property,
150
+ * indicating it is an OpenAPI 3.1 dynamic reference rather than a
151
+ * static `$ref`.
152
+ */
153
+ function isDynamicReference(obj) {
154
+ return !isNullish$1(obj) && Object.hasOwn(obj, "$dynamicRef") && typeof obj.$dynamicRef === "string";
155
+ }
146
156
  function isDirectory(pathValue) {
147
157
  return !nodePath.extname(pathValue);
148
158
  }
@@ -741,13 +751,21 @@ function count(str = "", key) {
741
751
  var path_exports = /* @__PURE__ */ __exportAll({
742
752
  getRelativeImportPath: () => getRelativeImportPath,
743
753
  getSchemaFileName: () => getSchemaFileName,
754
+ isAbsolute: () => isAbsolute,
744
755
  join: () => join,
745
756
  joinSafe: () => joinSafe,
746
757
  normalizeSafe: () => normalizeSafe,
747
758
  relativeSafe: () => relativeSafe,
759
+ resolve: () => resolve,
748
760
  separator: () => "/",
749
761
  toUnix: () => toUnix
750
762
  });
763
+ function isAbsolute(value) {
764
+ return nodePath.isAbsolute(value);
765
+ }
766
+ function resolve(...args) {
767
+ return toUnix(nodePath.resolve(...args));
768
+ }
751
769
  function toUnix(value) {
752
770
  value = value.replaceAll("\\", "/");
753
771
  value = value.replaceAll(/(?<!^)\/+/g, "/");
@@ -1357,1591 +1375,1996 @@ function getRefInfo($ref, context) {
1357
1375
  refPaths
1358
1376
  };
1359
1377
  }
1360
- //#endregion
1361
- //#region src/resolvers/ref.ts
1362
- const REF_NOT_FOUND_PREFIX = "Oops... 🍻. Ref not found";
1363
1378
  /**
1364
- * Recursively resolves a `$ref` in an OpenAPI document, following
1365
- * nested schema refs and collecting imports along the way.
1366
- *
1367
- * Handles OpenAPI 3.0 `nullable` and 3.1 type-array hints on direct refs.
1379
+ * Extracts the anchor name from a fragment-only `$dynamicRef` (e.g. `#category` `category`).
1368
1380
  *
1369
- * @see https://spec.openapis.org/oas/v3.0.3#reference-object
1370
- * @see https://spec.openapis.org/oas/v3.1.0#reference-object
1381
+ * Returns `undefined` for external-document `$dynamicRef` values (e.g. `other.json#anchor`)
1382
+ * which are not supported.
1371
1383
  */
1372
- function resolveRef(schema, context, imports = []) {
1373
- const refPath = "$ref" in schema ? schema.$ref : void 0;
1374
- const nestedSchema = "schema" in schema ? schema.schema : void 0;
1375
- if (isObject(nestedSchema) && isReference(nestedSchema) && typeof nestedSchema.$ref === "string") {
1376
- const resolvedRef = resolveRef(nestedSchema, context, imports);
1377
- if ("examples" in schema) {
1378
- const schemaWithExamples = schema;
1379
- schemaWithExamples.examples = resolveExampleRefs(schemaWithExamples.examples, context);
1380
- }
1381
- if ("examples" in resolvedRef.schema) {
1382
- const resolvedWithExamples = resolvedRef.schema;
1383
- resolvedWithExamples.examples = resolveExampleRefs(resolvedWithExamples.examples, context);
1384
- }
1385
- return {
1386
- schema: {
1387
- ...schema,
1388
- schema: resolvedRef.schema
1389
- },
1390
- imports: resolvedRef.imports
1391
- };
1392
- }
1393
- if (isDereferenced(schema)) {
1394
- if ("examples" in schema) {
1395
- const schemaWithExamples = schema;
1396
- schemaWithExamples.examples = resolveExampleRefs(schemaWithExamples.examples, context);
1397
- }
1384
+ function getDynamicAnchorName(dynamicRef) {
1385
+ if (!dynamicRef.startsWith("#") || dynamicRef.length <= 1) return;
1386
+ return dynamicRef.slice(1);
1387
+ }
1388
+ //#endregion
1389
+ //#region src/getters/imports.ts
1390
+ function getAliasedImports({ name, resolvedValue, context }) {
1391
+ return context.output.schemas && resolvedValue.isRef ? resolvedValue.imports.map((imp) => {
1392
+ if (!needCreateImportAlias({
1393
+ name,
1394
+ imp
1395
+ })) return imp;
1398
1396
  return {
1399
- schema,
1400
- imports
1397
+ ...imp,
1398
+ alias: `__${imp.name}`
1401
1399
  };
1402
- }
1403
- if (!refPath) throw new Error(`${REF_NOT_FOUND_PREFIX}: missing $ref`);
1404
- const { currentSchema, refInfo: { name, originalName } } = getSchema$1(schema, context);
1405
- if (!currentSchema) throw new Error(`${REF_NOT_FOUND_PREFIX}: ${refPath}`);
1406
- return resolveRef(currentSchema, { ...context }, [...imports, {
1407
- name,
1408
- schemaName: originalName
1409
- }]);
1400
+ }) : resolvedValue.imports;
1410
1401
  }
1411
- /**
1412
- * Looks up a schema by its `$ref` path in the spec, applying suffix resolution.
1413
- *
1414
- * Preserves OpenAPI 3.0 `nullable` and 3.1 type-array (`["object", "null"]`)
1415
- * hints from the referencing schema onto the resolved target.
1416
- *
1417
- * @see https://spec.openapis.org/oas/v3.0.3#fixed-fields-18 (nullable)
1418
- * @see https://spec.openapis.org/oas/v3.1.0#schema-object (type as array)
1419
- */
1420
- function getSchema$1(schema, context) {
1421
- if (!schema.$ref) throw new Error(`${REF_NOT_FOUND_PREFIX}: missing $ref`);
1422
- const refInfo = getRefInfo(schema.$ref, context);
1423
- const { refPaths } = refInfo;
1424
- const schemaByRefPaths = Array.isArray(refPaths) ? prop(context.spec, ...refPaths) : void 0;
1425
- if (isObject(schemaByRefPaths) && isReference(schemaByRefPaths)) return getSchema$1(schemaByRefPaths, context);
1426
- let currentSchema = schemaByRefPaths;
1427
- if (isObject(currentSchema) && "nullable" in schema) {
1428
- const nullable = schema.nullable;
1429
- currentSchema = {
1430
- ...currentSchema,
1431
- nullable
1432
- };
1433
- }
1434
- if (isObject(currentSchema) && "type" in schema && Array.isArray(schema.type)) {
1435
- const type = schema.type;
1436
- currentSchema = {
1437
- ...currentSchema,
1438
- type
1439
- };
1440
- }
1441
- return {
1442
- currentSchema,
1443
- refInfo
1444
- };
1402
+ function needCreateImportAlias({ imp, name }) {
1403
+ return !imp.alias && imp.name === name;
1445
1404
  }
1446
- /** Recursively resolves `$ref` entries in an examples array or record. */
1447
- function resolveExampleRefs(examples, context) {
1448
- if (!examples) return;
1449
- return Array.isArray(examples) ? examples.map((example) => {
1450
- if (isObject(example) && isReference(example)) {
1451
- const { schema } = resolveRef(example, context);
1452
- return schema.value;
1453
- }
1454
- return example;
1455
- }) : (() => {
1456
- const result = {};
1457
- for (const [key, example] of Object.entries(examples)) result[key] = isObject(example) && isReference(example) ? resolveRef(example, context).schema.value : example;
1458
- return result;
1459
- })();
1405
+ function getImportAliasForRefOrValue({ context, imports, resolvedValue }) {
1406
+ if (!context.output.schemas || !resolvedValue.isRef) return resolvedValue.value;
1407
+ return imports.find((imp) => imp.name === resolvedValue.value)?.alias ?? resolvedValue.value;
1460
1408
  }
1461
1409
  //#endregion
1462
- //#region src/resolvers/value.ts
1463
- function resolveValue({ schema, name, context, formDataContext }) {
1464
- if (isReference(schema)) {
1465
- const refValue = schema.$ref;
1466
- const { schema: schemaObject, imports } = resolveRef(schema, context);
1467
- if (refValue && !isComponentRef(refValue)) {
1468
- if (context.parents?.includes(refValue)) return {
1469
- value: "unknown",
1470
- imports: [],
1471
- schemas: [],
1472
- type: "unknown",
1473
- isEnum: false,
1474
- originalSchema: schemaObject,
1475
- hasReadonlyProps: false,
1476
- isRef: false,
1477
- dependencies: []
1478
- };
1479
- return {
1480
- ...getScalar({
1481
- item: schemaObject,
1482
- name,
1483
- context: {
1484
- ...context,
1485
- parents: [...context.parents ?? [], refValue]
1486
- },
1487
- formDataContext
1488
- }),
1489
- originalSchema: schemaObject,
1490
- isRef: false
1491
- };
1410
+ //#region src/getters/combine.ts
1411
+ const mergeableAllOfKeys = new Set([
1412
+ "type",
1413
+ "properties",
1414
+ "required"
1415
+ ]);
1416
+ function isMergeableAllOfObject(schema) {
1417
+ if (isNullish$1(schema.properties)) return false;
1418
+ if (schema.allOf || schema.anyOf || schema.oneOf) return false;
1419
+ if (!isNullish$1(schema.type) && schema.type !== "object") return false;
1420
+ return Object.keys(schema).every((key) => mergeableAllOfKeys.has(key));
1421
+ }
1422
+ function normalizeAllOfSchema(schema) {
1423
+ const schemaAllOf = schema.allOf;
1424
+ if (!schemaAllOf) return schema;
1425
+ let didMerge = false;
1426
+ const schemaProperties = schema.properties;
1427
+ const schemaRequired = schema.required;
1428
+ const mergedProperties = schemaProperties ? { ...schemaProperties } : {};
1429
+ const mergedRequired = new Set(schemaRequired);
1430
+ const remainingAllOf = [];
1431
+ for (const subSchema of schemaAllOf) {
1432
+ if (isSchema(subSchema) && isMergeableAllOfObject(subSchema)) {
1433
+ didMerge = true;
1434
+ if (subSchema.properties) Object.assign(mergedProperties, subSchema.properties);
1435
+ const subRequired = subSchema.required;
1436
+ if (subRequired) for (const prop of subRequired) mergedRequired.add(prop);
1437
+ continue;
1492
1438
  }
1493
- const resolvedImport = imports[0];
1494
- let hasReadonlyProps = false;
1495
- const refName = resolvedImport.name;
1496
- if (!context.parents?.includes(refName)) hasReadonlyProps = getScalar({
1497
- item: schemaObject,
1498
- name: refName,
1499
- context: {
1500
- ...context,
1501
- parents: [...context.parents ?? [], refName]
1502
- }
1503
- }).hasReadonlyProps;
1504
- const isAnyOfNullable = schemaObject.anyOf?.some((anyOfItem) => !isReference(anyOfItem) && (anyOfItem.type === "null" || Array.isArray(anyOfItem.type) && anyOfItem.type.includes("null")));
1505
- const schemaType = schemaObject.type;
1506
- const nullable = Array.isArray(schemaType) && schemaType.includes("null") || schemaObject.nullable === true || isAnyOfNullable ? " | null" : "";
1507
- return {
1508
- value: resolvedImport.name + nullable,
1509
- imports: [{
1510
- name: resolvedImport.name,
1511
- schemaName: resolvedImport.schemaName
1512
- }],
1513
- type: schemaObject.type ?? "object",
1514
- schemas: [],
1515
- isEnum: !!schemaObject.enum,
1516
- originalSchema: schemaObject,
1517
- hasReadonlyProps,
1518
- isRef: true,
1519
- dependencies: [resolvedImport.name]
1520
- };
1439
+ remainingAllOf.push(subSchema);
1521
1440
  }
1441
+ if (!didMerge || remainingAllOf.length === 0) return schema;
1522
1442
  return {
1523
- ...getScalar({
1524
- item: schema,
1525
- name,
1526
- context,
1527
- formDataContext
1528
- }),
1529
- originalSchema: schema,
1530
- isRef: false
1531
- };
1532
- }
1533
- //#endregion
1534
- //#region src/resolvers/object.ts
1535
- /**
1536
- * Wraps inline object type in a type alias.
1537
- * E.g. `{ foo: string }` → value becomes `FooBody`, schema gets `export type FooBody = { foo: string };`
1538
- */
1539
- function createTypeAliasIfNeeded({ resolvedValue, propName, context }) {
1540
- if (!propName) return;
1541
- if (resolvedValue.isEnum || resolvedValue.type !== "object") return;
1542
- const aliasPattern = context.output.override.aliasCombinedTypes ? String.raw`{|&|\|` : "{";
1543
- if (!new RegExp(aliasPattern).test(resolvedValue.value)) return;
1544
- const { originalSchema } = resolvedValue;
1545
- const doc = jsDoc(originalSchema);
1546
- const isConstant = "const" in originalSchema;
1547
- const constantIsString = "type" in originalSchema && (originalSchema.type === "string" || Array.isArray(originalSchema.type) && originalSchema.type.includes("string"));
1548
- const model = isConstant ? `${doc}export const ${propName} = ${constantIsString ? `'${originalSchema.const}'` : originalSchema.const} as const;\n` : `${doc}export type ${propName} = ${resolvedValue.value};\n`;
1549
- return {
1550
- value: propName,
1551
- imports: [{
1552
- name: propName,
1553
- isConstant
1554
- }],
1555
- schemas: [...resolvedValue.schemas, {
1556
- name: propName,
1557
- model,
1558
- imports: resolvedValue.imports,
1559
- dependencies: resolvedValue.dependencies
1560
- }],
1561
- isEnum: false,
1562
- type: "object",
1563
- isRef: resolvedValue.isRef,
1564
- hasReadonlyProps: resolvedValue.hasReadonlyProps,
1565
- dependencies: resolvedValue.dependencies
1443
+ ...schema,
1444
+ ...Object.keys(mergedProperties).length > 0 && { properties: mergedProperties },
1445
+ ...mergedRequired.size > 0 && { required: [...mergedRequired] },
1446
+ ...remainingAllOf.length > 0 && { allOf: remainingAllOf }
1566
1447
  };
1567
1448
  }
1568
- function resolveObjectOriginal({ schema, propName, combined = false, context, formDataContext }) {
1569
- const resolvedValue = resolveValue({
1570
- schema,
1571
- name: propName,
1572
- context,
1573
- formDataContext
1574
- });
1575
- const aliased = createTypeAliasIfNeeded({
1576
- resolvedValue,
1577
- propName,
1578
- context
1579
- });
1580
- if (aliased) return {
1581
- ...aliased,
1582
- originalSchema: resolvedValue.originalSchema
1449
+ function combineValues({ resolvedData, resolvedValue, separator, context, parentSchema }) {
1450
+ if (resolvedData.isEnum.every(Boolean)) return `${resolvedData.values.join(` | `)}${resolvedValue ? ` | ${resolvedValue.value}` : ""}`;
1451
+ if (separator === "allOf") {
1452
+ let resolvedDataValue = resolvedData.values.map((v) => v.includes(" | ") ? `(${v})` : v).join(` & `);
1453
+ if (resolvedData.originalSchema.length > 0 && resolvedValue) {
1454
+ const discriminatedPropertySchemas = resolvedData.originalSchema.filter((s) => {
1455
+ const disc = s?.discriminator;
1456
+ return disc && resolvedValue.value.includes(` ${disc.propertyName}:`);
1457
+ });
1458
+ if (discriminatedPropertySchemas.length > 0) resolvedDataValue = `Omit<${resolvedDataValue}, '${discriminatedPropertySchemas.map((s) => s.discriminator?.propertyName).join("' | '")}'>`;
1459
+ }
1460
+ const resolvedValueStr = resolvedValue?.value.includes(" | ") ? `(${resolvedValue.value})` : resolvedValue?.value;
1461
+ const joined = `${resolvedDataValue}${resolvedValue ? ` & ${resolvedValueStr}` : ""}`;
1462
+ const overrideRequiredProperties = resolvedData.requiredProperties.filter((prop) => !resolvedData.originalSchema.some((schema) => {
1463
+ const props = schema?.properties;
1464
+ const req = schema?.required;
1465
+ return props?.[prop] && req?.includes(prop);
1466
+ }) && !(() => {
1467
+ const parentProps = parentSchema?.properties;
1468
+ const parentReq = parentSchema?.required;
1469
+ return !!(parentProps?.[prop] && parentReq?.includes(prop));
1470
+ })());
1471
+ if (overrideRequiredProperties.length > 0) return `${joined} & Required<Pick<${joined}, '${overrideRequiredProperties.join("' | '")}'>>`;
1472
+ return joined;
1473
+ }
1474
+ let values = resolvedData.values;
1475
+ if (resolvedData.allProperties.length && context.output.unionAddMissingProperties) {
1476
+ values = [];
1477
+ for (let i = 0; i < resolvedData.values.length; i += 1) {
1478
+ const subSchema = resolvedData.originalSchema[i];
1479
+ if (subSchema?.type !== "object" || !subSchema.properties) {
1480
+ values.push(resolvedData.values[i]);
1481
+ continue;
1482
+ }
1483
+ const subSchemaProps = subSchema.properties;
1484
+ const missingProperties = unique(resolvedData.allProperties.filter((p) => !Object.keys(subSchemaProps).includes(p)));
1485
+ values.push(`${resolvedData.values[i]}${missingProperties.length > 0 ? ` & {${missingProperties.map((p) => `${p}?: never`).join("; ")}}` : ""}`);
1486
+ }
1487
+ }
1488
+ if (resolvedValue) return `(${values.join(` & ${resolvedValue.value}) | (`)} & ${resolvedValue.value})`;
1489
+ return values.join(" | ");
1490
+ }
1491
+ function combineSchemas({ name, schema, separator, context, nullable, formDataContext }) {
1492
+ const normalizedSchema = separator === "allOf" && !context.output.override.aliasCombinedTypes && !schema.oneOf && !schema.anyOf ? normalizeAllOfSchema(schema) : schema;
1493
+ const items = normalizedSchema[separator] ?? [];
1494
+ const resolvedData = {
1495
+ values: [],
1496
+ imports: [],
1497
+ schemas: [],
1498
+ isEnum: [],
1499
+ isRef: [],
1500
+ types: [],
1501
+ dependencies: [],
1502
+ originalSchema: [],
1503
+ allProperties: [],
1504
+ hasReadonlyProps: false,
1505
+ example: schema.example,
1506
+ examples: resolveExampleRefs(schema.examples, context),
1507
+ requiredProperties: separator === "allOf" ? schema.required ?? [] : []
1583
1508
  };
1584
- if (propName && resolvedValue.isEnum && !combined && !resolvedValue.isRef) {
1585
- const doc = jsDoc(resolvedValue.originalSchema);
1586
- const enumValue = getEnum(resolvedValue.value, propName, getEnumNames(resolvedValue.originalSchema), context.output.override.enumGenerationType, getEnumDescriptions(resolvedValue.originalSchema), context.output.override.namingConvention.enum);
1509
+ for (const subSchema of items) {
1510
+ let propName;
1511
+ if (context.output.override.aliasCombinedTypes) {
1512
+ propName = name ? name + pascal(separator) : void 0;
1513
+ if (propName && resolvedData.schemas.length > 0) propName = propName + pascal(getNumberWord(resolvedData.schemas.length + 1));
1514
+ }
1515
+ if (separator === "allOf" && isSchema(subSchema) && subSchema.required) resolvedData.requiredProperties.push(...subSchema.required);
1516
+ const resolvedValue = resolveObject({
1517
+ schema: subSchema,
1518
+ propName,
1519
+ combined: true,
1520
+ context,
1521
+ formDataContext
1522
+ });
1523
+ const aliasedImports = getAliasedImports({
1524
+ context,
1525
+ name,
1526
+ resolvedValue
1527
+ });
1528
+ const value = getImportAliasForRefOrValue({
1529
+ context,
1530
+ resolvedValue,
1531
+ imports: aliasedImports
1532
+ });
1533
+ resolvedData.values.push(value);
1534
+ resolvedData.imports.push(...aliasedImports);
1535
+ resolvedData.schemas.push(...resolvedValue.schemas);
1536
+ resolvedData.dependencies.push(...resolvedValue.dependencies);
1537
+ resolvedData.isEnum.push(resolvedValue.isEnum);
1538
+ resolvedData.types.push(resolvedValue.type);
1539
+ resolvedData.isRef.push(resolvedValue.isRef);
1540
+ resolvedData.originalSchema.push(resolvedValue.originalSchema);
1541
+ if (resolvedValue.hasReadonlyProps) resolvedData.hasReadonlyProps = true;
1542
+ const originalProps = resolvedValue.originalSchema.properties;
1543
+ if (resolvedValue.type === "object" && originalProps) resolvedData.allProperties.push(...Object.keys(originalProps));
1544
+ }
1545
+ const isAllEnums = resolvedData.isEnum.every(Boolean);
1546
+ const isNullableEnumComposition = (separator === "anyOf" || separator === "oneOf") && !isAllEnums && resolvedData.isEnum.some(Boolean) && resolvedData.isEnum.every((isEnum, index) => isEnum && !resolvedData.isRef[index] || resolvedData.types[index] === "null");
1547
+ if (isAllEnums && name && items.length > 1 && context.output.override.enumGenerationType !== EnumGeneration.UNION) {
1548
+ const { value: combinedEnumValue, valueImports, hasNull } = getCombinedEnumValue(resolvedData.values.map((value, index) => ({
1549
+ value,
1550
+ isRef: resolvedData.isRef[index],
1551
+ schema: resolvedData.originalSchema[index]
1552
+ })));
1553
+ const newEnum = `export const ${pascal(name)} = ${combinedEnumValue}`;
1554
+ const valueImportSet = new Set(valueImports);
1555
+ const typeSuffix = `${nullable}${hasNull && !nullable.includes("null") ? " | null" : ""}`;
1587
1556
  return {
1588
- value: propName,
1589
- imports: [{ name: propName }],
1590
- schemas: [...resolvedValue.schemas, {
1591
- name: propName,
1592
- model: doc + enumValue,
1593
- imports: resolvedValue.imports,
1594
- dependencies: resolvedValue.dependencies
1557
+ value: `typeof ${pascal(name)}[keyof typeof ${pascal(name)}]${typeSuffix}`,
1558
+ imports: [{ name: pascal(name) }],
1559
+ schemas: [...resolvedData.schemas, {
1560
+ imports: resolvedData.imports.filter((toImport) => valueImportSet.has(toImport.alias ?? toImport.name)).map((toImport) => ({
1561
+ ...toImport,
1562
+ values: true
1563
+ })),
1564
+ model: newEnum,
1565
+ name
1595
1566
  }],
1596
1567
  isEnum: false,
1597
- type: "enum",
1598
- originalSchema: resolvedValue.originalSchema,
1599
- isRef: resolvedValue.isRef,
1600
- hasReadonlyProps: resolvedValue.hasReadonlyProps,
1601
- dependencies: [...resolvedValue.dependencies, propName]
1568
+ type: "object",
1569
+ isRef: false,
1570
+ hasReadonlyProps: resolvedData.hasReadonlyProps,
1571
+ dependencies: resolvedData.dependencies,
1572
+ example: schema.example,
1573
+ examples: resolveExampleRefs(schema.examples, context)
1602
1574
  };
1603
1575
  }
1604
- return resolvedValue;
1605
- }
1606
- const resolveObjectCacheMap = /* @__PURE__ */ new Map();
1607
- function resolveObject({ schema, propName, combined = false, context, formDataContext }) {
1608
- const hashKey = JSON.stringify({
1609
- schema,
1610
- propName,
1611
- combined,
1612
- projectName: context.projectName ?? context.output.target,
1613
- formDataContext
1614
- });
1615
- if (resolveObjectCacheMap.has(hashKey)) return resolveObjectCacheMap.get(hashKey);
1616
- const result = resolveObjectOriginal({
1617
- schema,
1618
- propName,
1619
- combined,
1576
+ let resolvedValue;
1577
+ const normalizedProperties = normalizedSchema.properties;
1578
+ const schemaOneOf = schema.oneOf;
1579
+ const schemaAnyOf = schema.anyOf;
1580
+ if (normalizedProperties) resolvedValue = getScalar({
1581
+ item: Object.fromEntries(Object.entries(normalizedSchema).filter(([key]) => key !== separator)),
1582
+ name,
1620
1583
  context,
1621
1584
  formDataContext
1622
1585
  });
1623
- resolveObjectCacheMap.set(hashKey, result);
1624
- return result;
1586
+ else if (separator === "allOf" && (schemaOneOf || schemaAnyOf)) {
1587
+ const siblingCombiner = schemaOneOf ? "oneOf" : "anyOf";
1588
+ const siblingSchemas = schemaOneOf ?? schemaAnyOf;
1589
+ resolvedValue = combineSchemas({
1590
+ schema: { [siblingCombiner]: siblingSchemas },
1591
+ name,
1592
+ separator: siblingCombiner,
1593
+ context,
1594
+ nullable: ""
1595
+ });
1596
+ }
1597
+ return {
1598
+ value: dedupeUnionType(combineValues({
1599
+ resolvedData,
1600
+ separator,
1601
+ resolvedValue,
1602
+ context,
1603
+ parentSchema: normalizedSchema
1604
+ }) + nullable),
1605
+ imports: resolvedValue ? [...resolvedData.imports, ...resolvedValue.imports] : resolvedData.imports,
1606
+ schemas: resolvedValue ? [...resolvedData.schemas, ...resolvedValue.schemas] : resolvedData.schemas,
1607
+ dependencies: resolvedValue ? [...resolvedData.dependencies, ...resolvedValue.dependencies] : resolvedData.dependencies,
1608
+ isEnum: isNullableEnumComposition,
1609
+ type: "object",
1610
+ isRef: false,
1611
+ hasReadonlyProps: resolvedData.hasReadonlyProps || (resolvedValue?.hasReadonlyProps ?? false),
1612
+ example: schema.example,
1613
+ examples: resolveExampleRefs(schema.examples, context)
1614
+ };
1625
1615
  }
1626
1616
  //#endregion
1627
- //#region src/getters/array.ts
1617
+ //#region src/getters/keys.ts
1618
+ function getKey(key) {
1619
+ return keyword.isIdentifierNameES5(key) ? key : `'${key}'`;
1620
+ }
1621
+ //#endregion
1622
+ //#region src/getters/object.ts
1623
+ function getPropertyNamesEnumKeyType(item) {
1624
+ if (!("propertyNames" in item) || !item.propertyNames) return;
1625
+ const propertyNames = item.propertyNames;
1626
+ if (Array.isArray(propertyNames.enum)) {
1627
+ const enumValues = propertyNames.enum.filter((val) => isString(val));
1628
+ if (enumValues.length > 0) return {
1629
+ value: enumValues.map((val) => `'${escape(val)}'`).join(" | "),
1630
+ imports: [],
1631
+ dependencies: []
1632
+ };
1633
+ }
1634
+ if (isString(propertyNames.const)) return {
1635
+ value: `'${escape(propertyNames.const)}'`,
1636
+ imports: [],
1637
+ dependencies: []
1638
+ };
1639
+ }
1628
1640
  /**
1629
- * Return the output type from an array
1641
+ * Resolve a narrowed key type from OpenAPI 3.1 propertyNames.
1642
+ * Supports inline enum/const and $ref string enums.
1643
+ */
1644
+ function getPropertyNamesKeyType(item, context) {
1645
+ const inlineKeyType = getPropertyNamesEnumKeyType(item);
1646
+ if (inlineKeyType) return inlineKeyType;
1647
+ const propertyNames = item.propertyNames;
1648
+ if (!propertyNames || !isReference(propertyNames)) return;
1649
+ const resolvedValue = resolveValue({
1650
+ schema: propertyNames,
1651
+ context
1652
+ });
1653
+ const resolvedConst = resolvedValue.originalSchema.const;
1654
+ const isStringConst = resolvedValue.type === "string" && isString(resolvedConst);
1655
+ if (!resolvedValue.isEnum && !isStringConst) return;
1656
+ return {
1657
+ value: resolvedValue.value,
1658
+ imports: resolvedValue.imports,
1659
+ dependencies: resolvedValue.dependencies
1660
+ };
1661
+ }
1662
+ /**
1663
+ * Generate index signature key type based on propertyNames enum or const
1664
+ * Returns union type string like "'foo' | 'bar'", "'x'", or 'string' if neither
1665
+ */
1666
+ function getIndexSignatureKey(item) {
1667
+ return getPropertyNamesEnumKeyType(item)?.value ?? "string";
1668
+ }
1669
+ function getPropertyNamesRecordType(item, valueType, context) {
1670
+ const keyType = getPropertyNamesKeyType(item, context);
1671
+ if (!keyType) return;
1672
+ return {
1673
+ ...keyType,
1674
+ value: `Partial<Record<${keyType.value}, ${valueType}>>`
1675
+ };
1676
+ }
1677
+ /**
1678
+ * Return the output type from an object
1630
1679
  *
1631
- * @param item item with type === "array"
1680
+ * @param item item with type === "object"
1632
1681
  */
1633
- function getArray({ schema, name, context, formDataContext }) {
1634
- const schemaPrefixItems = schema.prefixItems;
1635
- const schemaItems = schema.items;
1636
- const schemaExample = schema.example;
1637
- const schemaExamples = schema.examples;
1638
- const itemSuffix = context.output.override.components.schemas.itemSuffix;
1639
- if (schemaPrefixItems) {
1640
- const resolvedObjects = schemaPrefixItems.map((item, index) => resolveObject({
1641
- schema: item,
1642
- propName: name ? name + itemSuffix + String(index) : void 0,
1643
- context
1644
- }));
1645
- if (schemaItems) {
1646
- const additional = resolveObject({
1647
- schema: schemaItems,
1648
- propName: name ? name + itemSuffix + "Additional" : void 0,
1649
- context
1650
- });
1651
- resolvedObjects.push({
1652
- ...additional,
1653
- value: `...${additional.value}[]`
1654
- });
1655
- }
1682
+ function getObject({ item, name, context, nullable, formDataContext }) {
1683
+ if (isReference(item)) {
1684
+ const { name } = getRefInfo(item.$ref, context);
1656
1685
  return {
1657
- type: "array",
1686
+ value: name + nullable,
1687
+ imports: [{ name }],
1688
+ schemas: [],
1658
1689
  isEnum: false,
1659
- isRef: false,
1660
- value: `[${resolvedObjects.map((o) => o.value).join(", ")}]`,
1661
- imports: resolvedObjects.flatMap((o) => o.imports),
1662
- schemas: resolvedObjects.flatMap((o) => o.schemas),
1663
- dependencies: resolvedObjects.flatMap((o) => o.dependencies),
1664
- hasReadonlyProps: resolvedObjects.some((o) => o.hasReadonlyProps),
1665
- example: schemaExample,
1666
- examples: resolveExampleRefs(schemaExamples, context)
1690
+ type: "object",
1691
+ isRef: true,
1692
+ hasReadonlyProps: item.readOnly ?? false,
1693
+ dependencies: [name],
1694
+ example: item.example,
1695
+ examples: resolveExampleRefs(item.examples, context)
1667
1696
  };
1668
1697
  }
1669
- if (schemaItems) {
1670
- const resolvedObject = resolveObject({
1671
- schema: schemaItems,
1672
- propName: name ? name + itemSuffix : void 0,
1698
+ const schemaItem = item;
1699
+ const itemAllOf = schemaItem.allOf;
1700
+ const itemOneOf = schemaItem.oneOf;
1701
+ const itemAnyOf = schemaItem.anyOf;
1702
+ const itemType = schemaItem.type;
1703
+ if (itemAllOf || itemOneOf || itemAnyOf) return combineSchemas({
1704
+ schema: schemaItem,
1705
+ name,
1706
+ separator: itemAllOf ? "allOf" : itemOneOf ? "oneOf" : "anyOf",
1707
+ context,
1708
+ nullable,
1709
+ formDataContext
1710
+ });
1711
+ if (Array.isArray(itemType)) {
1712
+ const typeArray = itemType;
1713
+ const baseItem = schemaItem;
1714
+ return combineSchemas({
1715
+ schema: { anyOf: typeArray.map((type) => ({
1716
+ ...baseItem,
1717
+ type
1718
+ })) },
1719
+ name,
1720
+ separator: "anyOf",
1673
1721
  context,
1674
- formDataContext
1722
+ nullable
1675
1723
  });
1676
- return {
1677
- value: `${schema.readOnly === true && !context.output.override.suppressReadonlyModifier ? "readonly " : ""}${resolvedObject.value.includes("|") || resolvedObject.value.includes("&") ? `(${resolvedObject.value})[]` : `${resolvedObject.value}[]`}`,
1678
- imports: resolvedObject.imports,
1679
- schemas: resolvedObject.schemas,
1680
- dependencies: resolvedObject.dependencies,
1724
+ }
1725
+ const itemProperties = schemaItem.properties;
1726
+ if (itemProperties && Object.entries(itemProperties).length > 0) {
1727
+ const entries = Object.entries(itemProperties);
1728
+ if (context.output.propertySortOrder === PropertySortOrder.ALPHABETICAL) entries.sort((a, b) => {
1729
+ return a[0].localeCompare(b[0], "en", { numeric: true });
1730
+ });
1731
+ const acc = {
1732
+ imports: [],
1733
+ schemas: [],
1734
+ value: "",
1681
1735
  isEnum: false,
1682
- type: "array",
1736
+ type: "object",
1683
1737
  isRef: false,
1684
- hasReadonlyProps: resolvedObject.hasReadonlyProps,
1685
- example: schemaExample,
1686
- examples: resolveExampleRefs(schemaExamples, context)
1738
+ hasReadonlyProps: false,
1739
+ useTypeAlias: false,
1740
+ dependencies: [],
1741
+ example: schemaItem.example,
1742
+ examples: resolveExampleRefs(schemaItem.examples, context)
1687
1743
  };
1688
- } else if (compareVersions(context.spec.openapi ?? "3.0.0", "3.1", ">=")) return {
1689
- value: "unknown[]",
1690
- imports: [],
1691
- schemas: [],
1692
- dependencies: [],
1693
- isEnum: false,
1694
- type: "array",
1695
- isRef: false,
1696
- hasReadonlyProps: false
1697
- };
1698
- else throw new Error(`All arrays must have an \`items\` key defined (name=${name}, schema=${JSON.stringify(schema)})`);
1699
- }
1700
- //#endregion
1701
- //#region src/getters/res-req-types.ts
1702
- const getSchemaType$1 = (s) => s.type;
1703
- const getSchemaCombined = (s) => s.oneOf ?? s.anyOf ?? s.allOf;
1704
- const getSchemaOneOf = (s) => s.oneOf;
1705
- const getSchemaAnyOf = (s) => s.anyOf;
1706
- const getSchemaItems = (s) => s.items;
1707
- const getSchemaRequired = (s) => s.required;
1708
- const getSchemaProperties = (s) => s.properties;
1709
- const resolveSchemaRef = (schema, context) => resolveRef(schema, context);
1710
- const resolveResponseOrRequestRef = (schema, context) => resolveRef(schema, context);
1711
- const formDataContentTypes = new Set(["multipart/form-data"]);
1712
- const formUrlEncodedContentTypes = new Set(["application/x-www-form-urlencoded"]);
1713
- function getResReqContentTypes({ mediaType, propName, context, isFormData, contentType }) {
1714
- if (!mediaType.schema) return;
1715
- const isFormUrlEncoded = formUrlEncodedContentTypes.has(contentType);
1716
- const formDataContext = isFormData ? {
1717
- atPart: false,
1718
- encoding: mediaType.encoding ?? {}
1719
- } : isFormUrlEncoded ? {
1720
- atPart: false,
1721
- encoding: mediaType.encoding ?? {},
1722
- urlEncoded: true
1723
- } : void 0;
1724
- const resolvedObject = resolveObject({
1725
- schema: mediaType.schema,
1726
- propName,
1727
- context,
1728
- formDataContext
1729
- });
1730
- if (!isFormData && isBinaryContentType(contentType)) return {
1731
- ...resolvedObject,
1732
- value: "Blob"
1733
- };
1734
- return resolvedObject;
1735
- }
1736
- function getResReqTypes(responsesOrRequests, name, context, defaultType = "unknown", uniqueKey = (item) => item.value) {
1737
- return uniqueBy(responsesOrRequests.filter(([, res]) => Boolean(res)).map(([key, res]) => {
1738
- if (isReference(res)) {
1739
- const { schema: bodySchema, imports: [{ name, schemaName }] } = resolveResponseOrRequestRef(res, context);
1740
- const firstEntry = Object.entries(bodySchema.content ?? {}).at(0);
1741
- if (!firstEntry) return [{
1742
- value: name,
1743
- imports: [{
1744
- name,
1745
- schemaName
1746
- }],
1747
- schemas: [],
1748
- type: "unknown",
1749
- isEnum: false,
1750
- isRef: true,
1751
- hasReadonlyProps: false,
1752
- dependencies: [name],
1753
- originalSchema: void 0,
1754
- example: void 0,
1755
- examples: void 0,
1756
- key,
1757
- contentType: ""
1758
- }];
1759
- const [contentType, mediaType] = firstEntry;
1760
- const isFormData = formDataContentTypes.has(contentType);
1761
- const isFormUrlEncoded = formUrlEncodedContentTypes.has(contentType);
1762
- if (!isFormData && !isFormUrlEncoded || !mediaType.schema) return [{
1763
- value: name,
1764
- imports: [{
1765
- name,
1766
- schemaName
1767
- }],
1768
- schemas: [],
1769
- type: "unknown",
1770
- isEnum: false,
1771
- isRef: true,
1772
- hasReadonlyProps: false,
1773
- dependencies: [name],
1774
- originalSchema: mediaType.schema,
1775
- example: mediaType.example,
1776
- examples: resolveExampleRefs(mediaType.examples, context),
1777
- key,
1778
- contentType
1779
- }];
1780
- const formData = isFormData ? getSchemaFormDataAndUrlEncoded({
1781
- name,
1782
- schemaObject: mediaType.schema,
1744
+ const itemRequired = schemaItem.required;
1745
+ for (const [index, [key, schema]] of entries.entries()) {
1746
+ const isRequired = (Array.isArray(itemRequired) ? itemRequired : []).includes(key);
1747
+ let propName = "";
1748
+ if (name) {
1749
+ const isKeyStartWithUnderscore = key.startsWith("_");
1750
+ propName += pascal(`${isKeyStartWithUnderscore ? "_" : ""}${name}_${key}`);
1751
+ }
1752
+ const allSpecSchemas = context.spec.components?.schemas ?? {};
1753
+ if (Object.keys(allSpecSchemas).some((schemaName) => pascal(schemaName) === propName)) propName = propName + "Property";
1754
+ const propertyFormDataContext = formDataContext && !formDataContext.atPart ? {
1755
+ atPart: true,
1756
+ partContentType: formDataContext.encoding[key]?.contentType,
1757
+ urlEncoded: formDataContext.urlEncoded
1758
+ } : void 0;
1759
+ const resolvedValue = resolveObject({
1760
+ schema,
1761
+ propName,
1783
1762
  context,
1784
- isRequestBodyOptional: bodySchema.required !== true,
1785
- isRef: true,
1786
- encoding: mediaType.encoding
1787
- }) : void 0;
1788
- const formUrlEncoded = isFormUrlEncoded ? getSchemaFormDataAndUrlEncoded({
1763
+ formDataContext: propertyFormDataContext
1764
+ });
1765
+ const isReadOnly = Boolean(schemaItem.readOnly) || Boolean(schema.readOnly);
1766
+ if (!index) acc.value += "{";
1767
+ const doc = jsDoc(schema, true, context);
1768
+ const propertyDoc = doc ? `${doc.trimEnd().split("\n").map((line) => ` ${line}`).join("\n")}\n` : "";
1769
+ if (isReadOnly || resolvedValue.hasReadonlyProps) acc.hasReadonlyProps = true;
1770
+ const constValue = "const" in schema ? schema.const : void 0;
1771
+ const hasConst = constValue !== void 0;
1772
+ let constLiteral;
1773
+ if (!hasConst) constLiteral = void 0;
1774
+ else if (isString(constValue)) constLiteral = `'${escape(constValue)}'`;
1775
+ else constLiteral = JSON.stringify(constValue);
1776
+ const needsValueImport = hasConst && (resolvedValue.isEnum || resolvedValue.type === "enum");
1777
+ const usedResolvedValue = !hasConst || needsValueImport;
1778
+ const aliasedImports = needsValueImport ? resolvedValue.imports.map((imp) => ({
1779
+ ...imp,
1780
+ isConstant: true
1781
+ })) : hasConst ? [] : getAliasedImports({
1789
1782
  name,
1790
- schemaObject: mediaType.schema,
1791
1783
  context,
1792
- isRequestBodyOptional: bodySchema.required !== true,
1793
- isUrlEncoded: true,
1794
- isRef: true,
1795
- encoding: mediaType.encoding
1796
- }) : void 0;
1797
- const additionalImports = getFormDataAdditionalImports({
1798
- schemaObject: mediaType.schema,
1799
- context
1784
+ resolvedValue
1800
1785
  });
1801
- return [{
1802
- value: name,
1803
- imports: [{
1804
- name,
1805
- schemaName
1806
- }, ...additionalImports],
1807
- schemas: [],
1808
- type: "unknown",
1809
- isEnum: false,
1810
- hasReadonlyProps: false,
1811
- dependencies: [name],
1812
- formData,
1813
- formUrlEncoded,
1814
- isRef: true,
1815
- originalSchema: mediaType.schema,
1816
- example: mediaType.example,
1817
- examples: resolveExampleRefs(mediaType.examples, context),
1818
- key,
1819
- contentType
1820
- }];
1821
- }
1822
- if (res.content) return Object.entries(res.content).map(([contentType, mediaType], index, arr) => {
1823
- let propName = key ? pascal(name) + pascal(key) : void 0;
1824
- if (propName && arr.length > 1) propName = propName + pascal(getNumberWord(index + 1));
1825
- const isFormData = formDataContentTypes.has(contentType);
1826
- const isFormUrlEncoded = formUrlEncodedContentTypes.has(contentType);
1827
- let effectivePropName = propName;
1828
- if (mediaType.schema && isReference(mediaType.schema)) {
1829
- const { imports } = resolveSchemaRef(mediaType.schema, context);
1830
- if (imports[0]?.name) effectivePropName = imports[0].name;
1831
- } else if ((isFormData || isFormUrlEncoded) && mediaType.schema) {
1832
- const combinedRefs = getSchemaOneOf(mediaType.schema) ?? getSchemaAnyOf(mediaType.schema);
1833
- if (combinedRefs) {
1834
- const names = [];
1835
- for (const ref of combinedRefs) {
1836
- if (!isReference(ref)) continue;
1837
- const refName = resolveSchemaRef(ref, context).imports[0]?.name;
1838
- if (refName) names.push(refName);
1839
- }
1840
- if (names.length > 0) effectivePropName = names.join("");
1841
- }
1842
- }
1843
- const resolvedValue = getResReqContentTypes({
1844
- mediaType,
1845
- propName: effectivePropName,
1786
+ if (aliasedImports.length > 0) acc.imports.push(...aliasedImports);
1787
+ const alias = getImportAliasForRefOrValue({
1846
1788
  context,
1847
- isFormData,
1848
- contentType
1789
+ resolvedValue,
1790
+ imports: aliasedImports
1849
1791
  });
1850
- if (!resolvedValue) {
1851
- if (isBinaryContentType(contentType)) return {
1852
- value: "Blob",
1853
- imports: [],
1854
- schemas: [],
1855
- type: "Blob",
1856
- isEnum: false,
1857
- key,
1858
- isRef: false,
1859
- hasReadonlyProps: false,
1860
- contentType
1861
- };
1862
- return;
1792
+ const propValue = needsValueImport ? alias : constLiteral ?? alias;
1793
+ const finalPropValue = isRequired ? propValue : context.output.override.useNullForOptional === true ? `${propValue} | null` : propValue;
1794
+ acc.value += `\n${propertyDoc}${isReadOnly && !context.output.override.suppressReadonlyModifier ? " readonly " : " "}${getKey(key)}${isRequired ? "" : "?"}: ${finalPropValue};`;
1795
+ if (usedResolvedValue) {
1796
+ acc.schemas.push(...resolvedValue.schemas);
1797
+ acc.dependencies.push(...resolvedValue.dependencies);
1863
1798
  }
1864
- if (!isFormData && !isFormUrlEncoded || !effectivePropName || !mediaType.schema) return {
1865
- ...resolvedValue,
1866
- imports: resolvedValue.imports,
1867
- dependencies: resolvedValue.dependencies,
1868
- contentType,
1869
- example: mediaType.example,
1870
- examples: resolveExampleRefs(mediaType.examples, context)
1799
+ if (entries.length - 1 === index) {
1800
+ const additionalProps = schemaItem.additionalProperties;
1801
+ if (additionalProps) if (additionalProps === true) {
1802
+ const recordType = getPropertyNamesRecordType(schemaItem, "unknown", context);
1803
+ if (recordType) {
1804
+ acc.value += "\n}";
1805
+ acc.value += ` & ${recordType.value}`;
1806
+ acc.useTypeAlias = true;
1807
+ acc.imports.push(...recordType.imports);
1808
+ acc.dependencies.push(...recordType.dependencies);
1809
+ } else {
1810
+ const keyType = getIndexSignatureKey(schemaItem);
1811
+ acc.value += `\n [key: ${keyType}]: unknown;\n }`;
1812
+ }
1813
+ } else {
1814
+ const resolvedValue = resolveValue({
1815
+ schema: additionalProps,
1816
+ name,
1817
+ context
1818
+ });
1819
+ const recordType = getPropertyNamesRecordType(schemaItem, resolvedValue.value, context);
1820
+ if (recordType) {
1821
+ acc.value += "\n}";
1822
+ acc.value += ` & ${recordType.value}`;
1823
+ acc.useTypeAlias = true;
1824
+ acc.imports.push(...recordType.imports);
1825
+ acc.dependencies.push(...recordType.dependencies);
1826
+ } else {
1827
+ const keyType = getIndexSignatureKey(schemaItem);
1828
+ acc.value += `\n [key: ${keyType}]: ${resolvedValue.value};\n}`;
1829
+ }
1830
+ acc.imports.push(...resolvedValue.imports);
1831
+ acc.schemas.push(...resolvedValue.schemas);
1832
+ acc.dependencies.push(...resolvedValue.dependencies);
1833
+ }
1834
+ else acc.value += "\n}";
1835
+ acc.value += nullable;
1836
+ }
1837
+ }
1838
+ return acc;
1839
+ }
1840
+ const outerAdditionalProps = schemaItem.additionalProperties;
1841
+ const readOnlyFlag = schemaItem.readOnly;
1842
+ if (outerAdditionalProps) {
1843
+ if (outerAdditionalProps === true) {
1844
+ const recordType = getPropertyNamesRecordType(schemaItem, "unknown", context);
1845
+ if (recordType) return {
1846
+ value: recordType.value + nullable,
1847
+ imports: recordType.imports,
1848
+ schemas: [],
1849
+ isEnum: false,
1850
+ type: "object",
1851
+ isRef: false,
1852
+ hasReadonlyProps: readOnlyFlag ?? false,
1853
+ useTypeAlias: true,
1854
+ dependencies: recordType.dependencies
1871
1855
  };
1872
- const formData = isFormData ? getSchemaFormDataAndUrlEncoded({
1873
- name: effectivePropName,
1874
- schemaObject: mediaType.schema,
1875
- context,
1876
- isRequestBodyOptional: res.required !== true,
1877
- isRef: true,
1878
- encoding: mediaType.encoding
1879
- }) : void 0;
1880
- const formUrlEncoded = isFormUrlEncoded ? getSchemaFormDataAndUrlEncoded({
1881
- name: effectivePropName,
1882
- schemaObject: mediaType.schema,
1883
- context,
1884
- isUrlEncoded: true,
1885
- isRequestBodyOptional: res.required !== true,
1886
- isRef: true,
1887
- encoding: mediaType.encoding
1888
- }) : void 0;
1889
- const additionalImports = getFormDataAdditionalImports({
1890
- schemaObject: mediaType.schema,
1891
- context
1892
- });
1893
1856
  return {
1894
- ...resolvedValue,
1895
- imports: [...resolvedValue.imports, ...additionalImports],
1896
- formData,
1897
- formUrlEncoded,
1898
- contentType,
1899
- example: mediaType.example,
1900
- examples: resolveExampleRefs(mediaType.examples, context)
1857
+ value: `{ [key: ${getIndexSignatureKey(schemaItem)}]: unknown }` + nullable,
1858
+ imports: [],
1859
+ schemas: [],
1860
+ isEnum: false,
1861
+ type: "object",
1862
+ isRef: false,
1863
+ hasReadonlyProps: readOnlyFlag ?? false,
1864
+ useTypeAlias: false,
1865
+ dependencies: []
1901
1866
  };
1902
- }).filter(Boolean).map((x) => ({
1903
- ...x,
1904
- key
1905
- }));
1906
- const swaggerSchema = "schema" in res ? res.schema : void 0;
1907
- if (swaggerSchema) return [{
1908
- ...resolveObject({
1909
- schema: swaggerSchema,
1910
- propName: key ? pascal(name) + pascal(key) : void 0,
1911
- context
1912
- }),
1913
- contentType: "application/json",
1914
- key
1915
- }];
1916
- return [{
1917
- value: defaultType,
1867
+ }
1868
+ const resolvedValue = resolveValue({
1869
+ schema: outerAdditionalProps,
1870
+ name,
1871
+ context
1872
+ });
1873
+ const recordType = getPropertyNamesRecordType(schemaItem, resolvedValue.value, context);
1874
+ if (recordType) return {
1875
+ value: recordType.value + nullable,
1876
+ imports: [...recordType.imports, ...resolvedValue.imports],
1877
+ schemas: resolvedValue.schemas,
1878
+ isEnum: false,
1879
+ type: "object",
1880
+ isRef: false,
1881
+ hasReadonlyProps: resolvedValue.hasReadonlyProps,
1882
+ useTypeAlias: true,
1883
+ dependencies: [...recordType.dependencies, ...resolvedValue.dependencies]
1884
+ };
1885
+ return {
1886
+ value: `{[key: ${getIndexSignatureKey(schemaItem)}]: ${resolvedValue.value}}` + nullable,
1887
+ imports: resolvedValue.imports,
1888
+ schemas: resolvedValue.schemas,
1889
+ isEnum: false,
1890
+ type: "object",
1891
+ isRef: false,
1892
+ hasReadonlyProps: resolvedValue.hasReadonlyProps,
1893
+ useTypeAlias: false,
1894
+ dependencies: resolvedValue.dependencies
1895
+ };
1896
+ }
1897
+ const constValue = schemaItem.const;
1898
+ if (constValue !== void 0) {
1899
+ let type;
1900
+ if (Array.isArray(constValue)) type = "array";
1901
+ else if (constValue === null) type = "null";
1902
+ else if (typeof constValue === "string") type = "string";
1903
+ else if (typeof constValue === "number") type = "number";
1904
+ else if (typeof constValue === "boolean") type = "boolean";
1905
+ else type = "object";
1906
+ return {
1907
+ value: typeof constValue === "string" ? `'${escape(constValue)}'` : JSON.stringify(constValue),
1918
1908
  imports: [],
1919
1909
  schemas: [],
1920
- type: defaultType,
1921
1910
  isEnum: false,
1922
- dependencies: [],
1923
- key,
1911
+ type,
1924
1912
  isRef: false,
1925
- hasReadonlyProps: false,
1926
- contentType: "application/json"
1927
- }];
1928
- }).flat(), uniqueKey);
1913
+ hasReadonlyProps: readOnlyFlag ?? false,
1914
+ dependencies: []
1915
+ };
1916
+ }
1917
+ const keyType = itemType === "object" ? getIndexSignatureKey(schemaItem) : "string";
1918
+ const recordType = getPropertyNamesRecordType(schemaItem, "unknown", context);
1919
+ if (itemType === "object" && recordType) return {
1920
+ value: recordType.value + nullable,
1921
+ imports: recordType.imports,
1922
+ schemas: [],
1923
+ isEnum: false,
1924
+ type: "object",
1925
+ isRef: false,
1926
+ hasReadonlyProps: readOnlyFlag ?? false,
1927
+ useTypeAlias: true,
1928
+ dependencies: recordType.dependencies
1929
+ };
1930
+ return {
1931
+ value: (itemType === "object" ? `{ [key: ${keyType}]: unknown }` : "unknown") + nullable,
1932
+ imports: [],
1933
+ schemas: [],
1934
+ isEnum: false,
1935
+ type: "object",
1936
+ isRef: false,
1937
+ hasReadonlyProps: readOnlyFlag ?? false,
1938
+ useTypeAlias: false,
1939
+ dependencies: []
1940
+ };
1929
1941
  }
1942
+ //#endregion
1943
+ //#region src/getters/scalar.ts
1930
1944
  /**
1931
- * Determine the responseType option based on success content types only.
1932
- * This avoids error-response content types influencing the responseType.
1933
- */
1934
- function getSuccessResponseType(response) {
1935
- const successContentTypes = response.types.success.map((t) => t.contentType).filter(Boolean);
1936
- if (response.isBlob) return "blob";
1937
- const hasJsonResponse = successContentTypes.some((contentType) => contentType.includes("json") || contentType.includes("+json"));
1938
- const hasTextResponse = successContentTypes.some((contentType) => contentType.startsWith("text/") || contentType.includes("xml"));
1939
- if (!hasJsonResponse && hasTextResponse) return "text";
1940
- }
1941
- /**
1942
- * Determine the response type category for a given content type.
1943
- * Used to set the correct responseType option in HTTP clients.
1945
+ * Returns true when a schema describes a raw binary string scalar — i.e. one
1946
+ * that getScalar's `case 'string':` branch would coerce to `Blob` outside a
1947
+ * url-encoded context (see the formDataContext.urlEncoded gate below). Shared
1948
+ * with resolveValue so the component-`$ref` urlEncoded short-circuit and the
1949
+ * inline scalar path stay in lockstep when new binary shapes are added
1950
+ * (#1624 / #3395 / #2410).
1944
1951
  *
1945
- * @param contentType - The MIME content type (e.g., 'application/json', 'text/plain')
1946
- * @returns The response type category to use for parsing
1952
+ * Accepts OAS 3.1 nullable unions (`type: ['string', 'null']`) since getScalar
1953
+ * normalizes those into `case 'string':` before invoking this predicate.
1947
1954
  */
1948
- function getResponseTypeCategory(contentType) {
1949
- if (isBinaryContentType(contentType)) return "blob";
1950
- if (contentType === "application/json" || contentType.includes("+json") || contentType.includes("-json")) return "json";
1951
- return "text";
1955
+ function isBinaryScalarSchema(schema) {
1956
+ const schemaType = schema.type;
1957
+ if (!(schemaType === "string" || isArray(schemaType) && schemaType.includes("string") && schemaType.every((type) => type === "string" || type === "null"))) return false;
1958
+ if (schema.format === "binary") return true;
1959
+ const contentMediaType = schema.contentMediaType;
1960
+ const contentEncoding = schema.contentEncoding;
1961
+ return contentMediaType === "application/octet-stream" && !contentEncoding;
1952
1962
  }
1953
1963
  /**
1954
- * Get the default content type from a list of content types.
1955
- * Priority: application/json > any JSON-like type > first in list
1964
+ * Return the typescript equivalent of open-api data type
1956
1965
  *
1957
- * @param contentTypes - Array of content types from OpenAPI spec
1958
- * @returns The default content type to use
1966
+ * @param item
1967
+ * @ref https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.1.md#data-types
1959
1968
  */
1960
- function getDefaultContentType(contentTypes) {
1961
- if (contentTypes.length === 0) return "application/json";
1962
- if (contentTypes.includes("application/json")) return "application/json";
1963
- const jsonType = contentTypes.find((ct) => ct.includes("+json") || ct.includes("-json"));
1964
- if (jsonType) return jsonType;
1965
- return contentTypes[0];
1966
- }
1967
- function getFormDataAdditionalImports({ schemaObject, context }) {
1968
- const { schema } = resolveSchemaRef(schemaObject, context);
1969
- if (schema.type !== "object") return [];
1970
- const combinedSchemas = getSchemaOneOf(schema) ?? getSchemaAnyOf(schema);
1971
- if (!combinedSchemas) return [];
1972
- return combinedSchemas.map((subSchema) => resolveSchemaRef(subSchema, context).imports[0]).filter(Boolean);
1973
- }
1974
- function getSchemaFormDataAndUrlEncoded({ name, schemaObject, context, isRequestBodyOptional, isUrlEncoded, isRef, encoding }) {
1975
- const { schema, imports } = resolveSchemaRef(schemaObject, context);
1976
- const propName = camel(!isRef && isReference(schemaObject) ? imports[0].name : name);
1977
- const variableName = isUrlEncoded ? "formUrlEncoded" : "formData";
1978
- let form = isUrlEncoded ? `const ${variableName} = new URLSearchParams();\n` : `const ${variableName} = new FormData();\n`;
1979
- const combinedSchemas = getSchemaCombined(schema);
1980
- if (schema.type === "object" || schema.type === void 0 && combinedSchemas) {
1981
- if (combinedSchemas) if (!!getSchemaOneOf(schema) || !!getSchemaAnyOf(schema)) {
1982
- const directProperties = getSchemaProperties(schema);
1983
- const directKeys = directProperties ? Object.entries(directProperties).filter(([, value]) => !resolveSchemaRef(value, context).schema.readOnly).map(([key]) => key) : [];
1984
- const skipLine = directKeys.length > 0 ? ` if ([${directKeys.map((k) => JSON.stringify(k)).join(", ")}].includes(key)) return;\n` : "";
1985
- form += `Object.entries(${propName} ?? {}).forEach(([key, value]) => {\n`;
1986
- form += skipLine;
1987
- form += ` if (value !== undefined && value !== null) {\n`;
1988
- if (isUrlEncoded) {
1989
- form += ` if (Array.isArray(value)) {\n`;
1990
- form += ` value.forEach(v => {\n`;
1991
- form += ` ${variableName}.append(key, typeof v === 'object' ? JSON.stringify(v) : String(v));\n`;
1992
- form += ` });\n`;
1993
- form += ` } else if (typeof value === 'object') {\n`;
1994
- form += ` ${variableName}.append(key, JSON.stringify(value));\n`;
1995
- form += ` } else {\n`;
1996
- form += ` ${variableName}.append(key, String(value));\n`;
1997
- form += ` }\n`;
1998
- } else {
1999
- form += ` if ((typeof File !== 'undefined' && value instanceof File) || value instanceof Blob) {\n`;
2000
- form += ` ${variableName}.append(key, value);\n`;
2001
- form += ` } else if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {\n`;
2002
- form += ` ${variableName}.append(key, new Blob([Uint8Array.from(value)]));\n`;
2003
- form += ` } else if (Array.isArray(value)) {\n`;
2004
- form += ` value.forEach(v => {\n`;
2005
- form += ` if ((typeof File !== 'undefined' && v instanceof File) || v instanceof Blob) {\n`;
2006
- form += ` ${variableName}.append(key, v);\n`;
2007
- form += ` } else if (typeof Buffer !== 'undefined' && Buffer.isBuffer(v)) {\n`;
2008
- form += ` ${variableName}.append(key, new Blob([Uint8Array.from(v)]));\n`;
2009
- form += ` } else {\n`;
2010
- form += ` ${variableName}.append(key, typeof v === 'object' ? JSON.stringify(v) : String(v));\n`;
2011
- form += ` }\n`;
2012
- form += ` });\n`;
2013
- form += ` } else if (typeof value === 'object') {\n`;
2014
- form += ` ${variableName}.append(key, JSON.stringify(value));\n`;
2015
- form += ` } else {\n`;
2016
- form += ` ${variableName}.append(key, String(value));\n`;
2017
- form += ` }\n`;
1969
+ function getScalar({ item, name, context, formDataContext }) {
1970
+ const schemaEnum = item.enum;
1971
+ const schemaType = item.type;
1972
+ const schemaReadOnly = item.readOnly;
1973
+ const schemaExample = item.example;
1974
+ const schemaExamples = item.examples;
1975
+ const schemaConst = item.const;
1976
+ const schemaFormat = item.format;
1977
+ const schemaNullable = item.nullable;
1978
+ const nullable = isArray(schemaType) && schemaType.includes("null") || schemaNullable === true ? " | null" : "";
1979
+ const enumItems = schemaEnum?.filter((enumItem) => enumItem !== null);
1980
+ let itemType = schemaType;
1981
+ if (!itemType && item.items) {
1982
+ item.type = "array";
1983
+ itemType = "array";
1984
+ }
1985
+ if (isArray(schemaType) && schemaType.includes("null")) {
1986
+ const typesWithoutNull = schemaType.filter((x) => x !== "null");
1987
+ itemType = typesWithoutNull.length === 1 ? typesWithoutNull[0] : typesWithoutNull;
1988
+ }
1989
+ switch (itemType) {
1990
+ case "number":
1991
+ case "integer": {
1992
+ let value = context.output.override.useBigInt && (schemaFormat === "int64" || schemaFormat === "uint64") ? "bigint" : "number";
1993
+ let isEnum = false;
1994
+ if (enumItems) {
1995
+ value = enumItems.map((enumItem) => `${enumItem}`).join(" | ");
1996
+ isEnum = true;
2018
1997
  }
2019
- form += ` }\n`;
2020
- form += `});\n`;
2021
- } else {
2022
- const combinedSchemasFormData = combinedSchemas.map((subSchema) => {
2023
- const { schema: combinedSchema } = resolveSchemaRef(subSchema, context);
2024
- return resolveSchemaPropertiesToFormData({
2025
- schema: combinedSchema,
2026
- variableName,
2027
- propName,
2028
- context,
2029
- isRequestBodyOptional,
2030
- encoding
2031
- });
2032
- }).filter(Boolean).join("\n");
2033
- form += combinedSchemasFormData;
1998
+ value += nullable;
1999
+ if (schemaConst !== void 0) value = schemaConst;
2000
+ return {
2001
+ value,
2002
+ isEnum,
2003
+ type: "number",
2004
+ schemas: [],
2005
+ imports: [],
2006
+ isRef: false,
2007
+ hasReadonlyProps: schemaReadOnly ?? false,
2008
+ dependencies: [],
2009
+ example: schemaExample,
2010
+ examples: resolveExampleRefs(schemaExamples, context)
2011
+ };
2034
2012
  }
2035
- if (schema.properties) {
2036
- const formDataValues = resolveSchemaPropertiesToFormData({
2037
- schema,
2038
- variableName,
2039
- propName,
2013
+ case "boolean": {
2014
+ let value = "boolean";
2015
+ if (enumItems && !(enumItems.includes(true) && enumItems.includes(false))) value = enumItems.map((enumItem) => `${enumItem}`).join(" | ");
2016
+ value += nullable;
2017
+ if (schemaConst !== void 0) value = schemaConst;
2018
+ return {
2019
+ value,
2020
+ type: "boolean",
2021
+ isEnum: false,
2022
+ schemas: [],
2023
+ imports: [],
2024
+ isRef: false,
2025
+ hasReadonlyProps: schemaReadOnly ?? false,
2026
+ dependencies: [],
2027
+ example: schemaExample,
2028
+ examples: resolveExampleRefs(schemaExamples, context)
2029
+ };
2030
+ }
2031
+ case "array": {
2032
+ const { value, ...rest } = getArray({
2033
+ schema: item,
2034
+ name,
2040
2035
  context,
2041
- isRequestBodyOptional,
2042
- encoding
2036
+ formDataContext
2043
2037
  });
2044
- form += formDataValues;
2045
- }
2046
- return form;
2047
- }
2048
- if (schema.type === "array") {
2049
- let valueStr = "value";
2050
- const schemaItems = getSchemaItems(schema);
2051
- if (schemaItems) {
2052
- const { schema: itemSchema } = resolveSchemaRef(schemaItems, context);
2053
- if (itemSchema.type === "object" || itemSchema.type === "array") valueStr = "JSON.stringify(value)";
2054
- else if (itemSchema.type === "number" || itemSchema.type === "integer" || itemSchema.type === "boolean") valueStr = "value.toString()";
2038
+ return {
2039
+ value: value + nullable,
2040
+ ...rest,
2041
+ dependencies: rest.dependencies
2042
+ };
2055
2043
  }
2056
- return `${form}${propName}.forEach(value => ${variableName}.append('data', ${valueStr}))\n`;
2057
- }
2058
- if (schema.type === "number" || schema.type === "integer" || schema.type === "boolean") return `${form}${variableName}.append('data', ${propName}.toString())\n`;
2059
- return `${form}${variableName}.append('data', ${propName})\n`;
2060
- }
2061
- function resolveSchemaPropertiesToFormData({ schema, variableName, propName, context, isRequestBodyOptional, keyPrefix = "", depth = 0, encoding }) {
2062
- let formDataValues = "";
2063
- const isUrlEncoded = variableName === "formUrlEncoded";
2064
- const schemaProps = getSchemaProperties(schema) ?? {};
2065
- for (const [key, value] of Object.entries(schemaProps)) {
2066
- const { schema: property } = resolveSchemaRef(value, context);
2067
- if (property.readOnly) continue;
2068
- let formDataValue = "";
2069
- const partContentType = (depth === 0 ? encoding?.[key] : void 0)?.contentType;
2070
- const formattedKeyPrefix = isRequestBodyOptional ? keyword.isIdentifierNameES5(key) ? "?" : "?." : "";
2071
- const formattedKey = keyword.isIdentifierNameES5(key) ? `.${key}` : `['${key}']`;
2072
- const valueKey = `${propName}${formattedKeyPrefix}${formattedKey}`;
2073
- const nonOptionalValueKey = `${propName}${formattedKey}`;
2074
- const fileType = getFormDataFieldFileType(property, partContentType);
2075
- const effectiveContentType = partContentType ?? property.contentMediaType;
2076
- if (isUrlEncoded && (fileType || property.format === "binary")) formDataValue = `${variableName}.append(\`${keyPrefix}${key}\`, ${nonOptionalValueKey});\n`;
2077
- else if (fileType === "binary" || property.format === "binary") formDataValue = `${variableName}.append(\`${keyPrefix}${key}\`, ${nonOptionalValueKey});\n`;
2078
- else if (fileType === "text") formDataValue = `${variableName}.append(\`${keyPrefix}${key}\`, ${nonOptionalValueKey} instanceof Blob ? ${nonOptionalValueKey} : new Blob([${nonOptionalValueKey}], { type: '${effectiveContentType}' }));\n`;
2079
- else if (property.type === "object" || Array.isArray(property.type) && property.type.includes("object")) formDataValue = context.output.override.formData.arrayHandling === FormDataArrayHandling.EXPLODE ? resolveSchemaPropertiesToFormData({
2080
- schema: property,
2081
- variableName,
2082
- propName: nonOptionalValueKey,
2083
- context,
2084
- isRequestBodyOptional,
2085
- keyPrefix: `${keyPrefix}${key}.`,
2086
- depth: depth + 1,
2087
- encoding
2088
- }) : `${variableName}.append(\`${keyPrefix}${key}\`, JSON.stringify(${nonOptionalValueKey}));\n`;
2089
- else if (property.type === "array" || Array.isArray(property.type) && property.type.includes("array")) {
2090
- let valueStr = "value";
2091
- let hasNonPrimitiveChild = false;
2092
- const propertyItems = getSchemaItems(property);
2093
- if (propertyItems) {
2094
- const { schema: itemSchema } = resolveSchemaRef(propertyItems, context);
2095
- if (itemSchema.type === "object" || itemSchema.type === "array") if (context.output.override.formData.arrayHandling === FormDataArrayHandling.EXPLODE) {
2096
- hasNonPrimitiveChild = true;
2097
- const resolvedValue = resolveSchemaPropertiesToFormData({
2098
- schema: itemSchema,
2099
- variableName,
2100
- propName: "value",
2101
- context,
2102
- isRequestBodyOptional,
2103
- keyPrefix: `${keyPrefix}${key}[\${index${depth > 0 ? depth : ""}}].`,
2104
- depth: depth + 1
2105
- });
2106
- formDataValue = `${valueKey}.forEach((value, index${depth > 0 ? depth : ""}) => {
2107
- ${resolvedValue}});\n`;
2108
- } else valueStr = "JSON.stringify(value)";
2109
- else {
2110
- const itemType = getSchemaType$1(itemSchema);
2111
- if (itemType === "number" || Array.isArray(itemType) && itemType.includes("number") || itemType === "integer" || Array.isArray(itemType) && itemType.includes("integer") || itemType === "boolean" || Array.isArray(itemType) && itemType.includes("boolean")) valueStr = "value.toString()";
2112
- }
2044
+ case "string": {
2045
+ let value = "string";
2046
+ let isEnum = false;
2047
+ if (enumItems) {
2048
+ value = enumItems.map((enumItem) => isString(enumItem) ? `'${escape(enumItem)}'` : `${enumItem}`).filter(Boolean).join(` | `);
2049
+ isEnum = true;
2113
2050
  }
2114
- if (context.output.override.formData.arrayHandling === FormDataArrayHandling.EXPLODE) {
2115
- if (!hasNonPrimitiveChild) formDataValue = `${valueKey}.forEach((value, index${depth > 0 ? depth : ""}) => ${variableName}.append(\`${keyPrefix}${key}[\${index${depth > 0 ? depth : ""}}]\`, ${valueStr}));\n`;
2116
- } else formDataValue = `${valueKey}.forEach(value => ${variableName}.append(\`${keyPrefix}${key}${context.output.override.formData.arrayHandling === FormDataArrayHandling.SERIALIZE_WITH_BRACKETS ? "[]" : ""}\`, ${valueStr}));\n`;
2117
- } else if ((() => {
2118
- const propType = getSchemaType$1(property);
2119
- return propType === "number" || Array.isArray(propType) && propType.includes("number") || propType === "integer" || Array.isArray(propType) && propType.includes("integer") || propType === "boolean" || Array.isArray(propType) && propType.includes("boolean");
2120
- })()) formDataValue = `${variableName}.append(\`${keyPrefix}${key}\`, ${nonOptionalValueKey}.toString())\n`;
2121
- else formDataValue = `${variableName}.append(\`${keyPrefix}${key}\`, ${nonOptionalValueKey});\n`;
2122
- let existSubSchemaNullable = false;
2123
- const combine = getSchemaCombined(property);
2124
- if (combine) {
2125
- const subSchemas = combine.map((c) => resolveObject({
2126
- schema: c,
2127
- combined: true,
2128
- context
2129
- }));
2130
- if (subSchemas.some((subSchema) => {
2131
- return [
2132
- "number",
2133
- "integer",
2134
- "boolean"
2135
- ].includes(subSchema.type);
2136
- })) formDataValue = `${variableName}.append(\`${key}\`, ${nonOptionalValueKey}.toString())\n`;
2137
- if (subSchemas.some((subSchema) => {
2138
- return subSchema.type === "null";
2139
- })) existSubSchemaNullable = true;
2140
- }
2141
- const isRequired = getSchemaRequired(schema)?.includes(key) && !isRequestBodyOptional;
2142
- const propType = getSchemaType$1(property);
2143
- if (property.nullable || Array.isArray(propType) && propType.includes("null") || existSubSchemaNullable) {
2144
- if (isRequired) {
2145
- formDataValues += `if(${valueKey} !== null) {\n ${formDataValue} }\n`;
2146
- continue;
2051
+ if (!formDataContext?.urlEncoded) {
2052
+ if (schemaFormat === "binary") value = "Blob";
2053
+ else if (formDataContext?.atPart) {
2054
+ const fileType = getFormDataFieldFileType(item, formDataContext.partContentType);
2055
+ if (fileType) value = fileType === "binary" ? "Blob" : "Blob | string";
2056
+ } else if (isBinaryScalarSchema(item)) value = "Blob";
2147
2057
  }
2148
- formDataValues += `if(${valueKey} !== undefined && ${nonOptionalValueKey} !== null) {\n ${formDataValue} }\n`;
2149
- continue;
2058
+ if (context.output.override.useDates && (schemaFormat === "date" || schemaFormat === "date-time")) value = "Date";
2059
+ value += nullable;
2060
+ if (schemaConst) value = `'${schemaConst}'`;
2061
+ return {
2062
+ value,
2063
+ isEnum,
2064
+ type: "string",
2065
+ imports: [],
2066
+ schemas: [],
2067
+ isRef: false,
2068
+ hasReadonlyProps: schemaReadOnly ?? false,
2069
+ dependencies: [],
2070
+ example: schemaExample,
2071
+ examples: resolveExampleRefs(schemaExamples, context)
2072
+ };
2150
2073
  }
2151
- if (isRequired) {
2152
- formDataValues += formDataValue;
2153
- continue;
2074
+ case "null": {
2075
+ const itemAllOf = item.allOf;
2076
+ const itemOneOf = item.oneOf;
2077
+ const itemAnyOf = item.anyOf;
2078
+ let separator;
2079
+ if (itemAllOf?.length) separator = "allOf";
2080
+ else if (itemOneOf?.length) separator = "oneOf";
2081
+ else if (itemAnyOf?.length) separator = "anyOf";
2082
+ if (separator) return combineSchemas({
2083
+ schema: Object.fromEntries(Object.entries(item).filter(([key]) => key !== "type")),
2084
+ name,
2085
+ separator,
2086
+ context,
2087
+ nullable: nullable || " | null",
2088
+ formDataContext
2089
+ });
2090
+ return {
2091
+ value: "null",
2092
+ isEnum: false,
2093
+ type: "null",
2094
+ imports: [],
2095
+ schemas: [],
2096
+ isRef: false,
2097
+ hasReadonlyProps: schemaReadOnly ?? false,
2098
+ dependencies: []
2099
+ };
2100
+ }
2101
+ default: {
2102
+ if (isArray(itemType)) return combineSchemas({
2103
+ schema: { anyOf: itemType.map((type) => Object.assign({}, item, { type })) },
2104
+ name,
2105
+ separator: "anyOf",
2106
+ context,
2107
+ nullable
2108
+ });
2109
+ if (enumItems) return {
2110
+ value: enumItems.map((enumItem) => isString(enumItem) ? `'${escape(enumItem)}'` : String(enumItem)).filter(Boolean).join(` | `) + nullable,
2111
+ isEnum: true,
2112
+ type: "string",
2113
+ imports: [],
2114
+ schemas: [],
2115
+ isRef: false,
2116
+ hasReadonlyProps: schemaReadOnly ?? false,
2117
+ dependencies: [],
2118
+ example: schemaExample,
2119
+ examples: resolveExampleRefs(schemaExamples, context)
2120
+ };
2121
+ const hasCombiners = item.allOf ?? item.anyOf ?? item.oneOf;
2122
+ const { value, ...rest } = getObject({
2123
+ item,
2124
+ name,
2125
+ context,
2126
+ nullable,
2127
+ formDataContext: formDataContext?.atPart === false || formDataContext?.atPart && hasCombiners ? formDataContext : void 0
2128
+ });
2129
+ return {
2130
+ value,
2131
+ ...rest
2132
+ };
2154
2133
  }
2155
- formDataValues += `if(${valueKey} !== undefined) {\n ${formDataValue} }\n`;
2156
2134
  }
2157
- return formDataValues;
2158
2135
  }
2159
2136
  //#endregion
2160
- //#region src/getters/body.ts
2161
- function buildBody(filteredBodyTypes, requestBody, operationName, context) {
2162
- const imports = filteredBodyTypes.flatMap(({ imports }) => imports);
2163
- const schemas = filteredBodyTypes.flatMap(({ schemas }) => schemas);
2164
- const definition = filteredBodyTypes.map(({ value }) => value).join(" | ");
2165
- const nonReadonlyDefinition = filteredBodyTypes.some((x) => x.hasReadonlyProps) && definition && context.output.override.preserveReadonlyRequestBodies !== "preserve" ? `NonReadonly<${definition}>` : definition;
2166
- let implementation = generalJSTypesWithArray.includes(definition.toLowerCase()) || filteredBodyTypes.length > 1 ? camel(operationName) + context.output.override.components.requestBodies.suffix : camel(definition);
2167
- let isOptional = false;
2168
- if (implementation) {
2169
- implementation = sanitize(implementation, {
2170
- underscore: "_",
2171
- whitespace: "_",
2172
- dash: true,
2173
- es5keyword: true,
2174
- es5IdentifierName: true
2175
- });
2176
- if (isReference(requestBody)) {
2177
- const { schema: bodySchema } = resolveRef(requestBody, context);
2178
- isOptional = bodySchema.required !== true;
2179
- } else isOptional = requestBody.required !== true;
2180
- }
2181
- return {
2182
- originalSchema: requestBody,
2183
- definition: nonReadonlyDefinition,
2184
- implementation,
2185
- imports,
2186
- schemas,
2187
- isOptional,
2188
- ...filteredBodyTypes.length === 1 ? {
2189
- formData: filteredBodyTypes[0].formData,
2190
- formUrlEncoded: filteredBodyTypes[0].formUrlEncoded,
2191
- contentType: filteredBodyTypes[0].contentType
2192
- } : {
2193
- formData: "",
2194
- formUrlEncoded: "",
2195
- contentType: ""
2196
- }
2197
- };
2137
+ //#region src/resolvers/ref.ts
2138
+ /** Convert a `$dynamicAnchor` name to a valid TypeScript generic parameter identifier. */
2139
+ function dynamicAnchorToParamName(anchor) {
2140
+ return sanitize(anchor, {
2141
+ underscore: "_",
2142
+ whitespace: "_",
2143
+ dash: "_",
2144
+ es5keyword: true,
2145
+ es5IdentifierName: true
2146
+ });
2198
2147
  }
2199
- function getBody({ requestBody, operationName, context, contentType }) {
2200
- return buildBody(filterByContentType(getResReqTypes([[context.output.override.components.requestBodies.suffix, requestBody]], operationName, context), contentType), requestBody, operationName, context);
2148
+ function dynamicAnchorsToUniqueParamNames(anchors) {
2149
+ const result = /* @__PURE__ */ new Map();
2150
+ const usedNames = /* @__PURE__ */ new Map();
2151
+ for (const anchor of anchors) {
2152
+ const base = dynamicAnchorToParamName(anchor);
2153
+ const count = usedNames.get(base) ?? 0;
2154
+ usedNames.set(base, count + 1);
2155
+ const paramName = count === 0 ? base : `${base}${count + 1}`;
2156
+ result.set(anchor, paramName);
2157
+ }
2158
+ return result;
2201
2159
  }
2160
+ const REF_NOT_FOUND_PREFIX = "Oops... 🍻. Ref not found";
2202
2161
  /**
2203
- * Returns per-content-type bodies when `splitByContentType` is enabled.
2204
- * Each entry includes a `contentTypeSuffix` for generating distinct function names.
2162
+ * Recursively resolves a `$ref` in an OpenAPI document, following
2163
+ * nested schema refs and collecting imports along the way.
2164
+ *
2165
+ * Handles OpenAPI 3.0 `nullable` and 3.1 type-array hints on direct refs.
2166
+ *
2167
+ * @see https://spec.openapis.org/oas/v3.0.3#reference-object
2168
+ * @see https://spec.openapis.org/oas/v3.1.0#reference-object
2205
2169
  */
2206
- function getBodiesByContentType({ requestBody, operationName, context, contentType }) {
2207
- const filteredBodyTypes = filterByContentType(getResReqTypes([[context.output.override.components.requestBodies.suffix, requestBody]], operationName, context, void 0, (item) => `${item.value}::${item.contentType}`), contentType);
2208
- if (filteredBodyTypes.length <= 1) return [{
2209
- ...buildBody(filteredBodyTypes, requestBody, operationName, context),
2210
- contentTypeSuffix: ""
2211
- }];
2212
- return filteredBodyTypes.map((bodyType) => {
2213
- const suffix = getContentTypeSuffix(bodyType.contentType);
2170
+ function resolveRef(schema, context, imports = []) {
2171
+ const refPath = "$ref" in schema ? schema.$ref : void 0;
2172
+ const nestedSchema = "schema" in schema ? schema.schema : void 0;
2173
+ if (isObject(nestedSchema) && isReference(nestedSchema) && typeof nestedSchema.$ref === "string") {
2174
+ const resolvedRef = resolveRef(nestedSchema, context, imports);
2175
+ if ("examples" in schema) {
2176
+ const schemaWithExamples = schema;
2177
+ schemaWithExamples.examples = resolveExampleRefs(schemaWithExamples.examples, context);
2178
+ }
2179
+ if ("examples" in resolvedRef.schema) {
2180
+ const resolvedWithExamples = resolvedRef.schema;
2181
+ resolvedWithExamples.examples = resolveExampleRefs(resolvedWithExamples.examples, context);
2182
+ }
2214
2183
  return {
2215
- ...buildBody([bodyType], requestBody, operationName, context),
2216
- contentTypeSuffix: suffix
2184
+ schema: {
2185
+ ...schema,
2186
+ schema: resolvedRef.schema
2187
+ },
2188
+ imports: resolvedRef.imports
2217
2189
  };
2218
- });
2219
- }
2220
- const CONTENT_TYPE_SUFFIX_MAP = {
2221
- "application/json": "Json",
2222
- "multipart/form-data": "FormData",
2223
- "application/x-www-form-urlencoded": "UrlEncoded",
2224
- "text/plain": "Text",
2225
- "application/xml": "Xml",
2226
- "text/xml": "Xml",
2227
- "application/octet-stream": "Blob"
2228
- };
2229
- function getContentTypeSuffix(contentType) {
2230
- if (CONTENT_TYPE_SUFFIX_MAP[contentType]) return CONTENT_TYPE_SUFFIX_MAP[contentType];
2231
- return (contentType.split("/")[1] ?? contentType).split(/[-+.]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
2232
- }
2233
- //#endregion
2234
- //#region src/getters/imports.ts
2235
- function getAliasedImports({ name, resolvedValue, context }) {
2236
- return context.output.schemas && resolvedValue.isRef ? resolvedValue.imports.map((imp) => {
2237
- if (!needCreateImportAlias({
2238
- name,
2239
- imp
2240
- })) return imp;
2190
+ }
2191
+ if (isDereferenced(schema)) {
2192
+ if ("examples" in schema) {
2193
+ const schemaWithExamples = schema;
2194
+ schemaWithExamples.examples = resolveExampleRefs(schemaWithExamples.examples, context);
2195
+ }
2241
2196
  return {
2242
- ...imp,
2243
- alias: `__${imp.name}`
2197
+ schema,
2198
+ imports
2244
2199
  };
2245
- }) : resolvedValue.imports;
2246
- }
2247
- function needCreateImportAlias({ imp, name }) {
2248
- return !imp.alias && imp.name === name;
2249
- }
2250
- function getImportAliasForRefOrValue({ context, imports, resolvedValue }) {
2251
- if (!context.output.schemas || !resolvedValue.isRef) return resolvedValue.value;
2252
- return imports.find((imp) => imp.name === resolvedValue.value)?.alias ?? resolvedValue.value;
2253
- }
2254
- //#endregion
2255
- //#region src/getters/keys.ts
2256
- function getKey(key) {
2257
- return keyword.isIdentifierNameES5(key) ? key : `'${key}'`;
2200
+ }
2201
+ if (!refPath) throw new Error(`${REF_NOT_FOUND_PREFIX}: missing $ref`);
2202
+ const { currentSchema, refInfo: { name, originalName } } = getSchema$1(schema, context);
2203
+ if (!currentSchema) throw new Error(`${REF_NOT_FOUND_PREFIX}: ${refPath}`);
2204
+ return resolveRef(currentSchema, { ...context }, [...imports, {
2205
+ name,
2206
+ schemaName: originalName
2207
+ }]);
2258
2208
  }
2259
- //#endregion
2260
- //#region src/getters/object.ts
2261
- function getPropertyNamesEnumKeyType(item) {
2262
- if (!("propertyNames" in item) || !item.propertyNames) return;
2263
- const propertyNames = item.propertyNames;
2264
- if (Array.isArray(propertyNames.enum)) {
2265
- const enumValues = propertyNames.enum.filter((val) => isString(val));
2266
- if (enumValues.length > 0) return {
2267
- value: enumValues.map((val) => `'${escape(val)}'`).join(" | "),
2268
- imports: [],
2269
- dependencies: []
2270
- };
2209
+ /** Check whether a schema reference has at least one `$defs` entry with both `$dynamicAnchor` and `$ref`. */
2210
+ function isBoundAlias(schema) {
2211
+ const defs = schema.$defs;
2212
+ if (!defs || typeof defs !== "object") return false;
2213
+ for (const defSchema of Object.values(defs)) {
2214
+ if (!defSchema || typeof defSchema !== "object") continue;
2215
+ const rec = defSchema;
2216
+ if (typeof rec.$dynamicAnchor === "string" && typeof rec.$ref === "string") return true;
2271
2217
  }
2272
- if (isString(propertyNames.const)) return {
2273
- value: `'${escape(propertyNames.const)}'`,
2274
- imports: [],
2275
- dependencies: []
2276
- };
2218
+ return false;
2277
2219
  }
2278
2220
  /**
2279
- * Resolve a narrowed key type from OpenAPI 3.1 propertyNames.
2280
- * Supports inline enum/const and $ref string enums.
2221
+ * Extract bound-alias information from a schema that references a generic template
2222
+ * and binds `$dynamicAnchor` entries to concrete types via `$defs`.
2281
2223
  */
2282
- function getPropertyNamesKeyType(item, context) {
2283
- const inlineKeyType = getPropertyNamesEnumKeyType(item);
2284
- if (inlineKeyType) return inlineKeyType;
2285
- const propertyNames = item.propertyNames;
2286
- if (!propertyNames || !isReference(propertyNames)) return;
2287
- const resolvedValue = resolveValue({
2288
- schema: propertyNames,
2289
- context
2290
- });
2291
- const resolvedConst = resolvedValue.originalSchema.const;
2292
- const isStringConst = resolvedValue.type === "string" && isString(resolvedConst);
2293
- if (!resolvedValue.isEnum && !isStringConst) return;
2224
+ function extractBoundAliasInfo(schema, context) {
2225
+ let bindingElement;
2226
+ let extraSchemas;
2227
+ if (isReference(schema) && isBoundAlias(schema)) bindingElement = schema;
2228
+ else {
2229
+ const allOf = schema.allOf;
2230
+ if (Array.isArray(allOf)) for (let i = 0; i < allOf.length; i++) {
2231
+ const element = allOf[i];
2232
+ if (isReference(element) && isBoundAlias(element)) {
2233
+ bindingElement = element;
2234
+ extraSchemas = allOf.filter((_, j) => j !== i);
2235
+ break;
2236
+ }
2237
+ }
2238
+ }
2239
+ if (!bindingElement) return void 0;
2240
+ const defs = bindingElement.$defs;
2241
+ if (!defs || typeof defs !== "object") return void 0;
2242
+ const bindingByAnchor = /* @__PURE__ */ new Map();
2243
+ for (const defSchema of Object.values(defs)) {
2244
+ if (!defSchema || typeof defSchema !== "object") continue;
2245
+ const rec = defSchema;
2246
+ if (rec.$dynamicAnchor === void 0) continue;
2247
+ const ref = rec.$ref;
2248
+ if (!ref || !isComponentRef(ref)) continue;
2249
+ const anchor = rec.$dynamicAnchor;
2250
+ const { name, originalName } = getRefInfo(ref, context);
2251
+ bindingByAnchor.set(anchor, {
2252
+ typeName: name,
2253
+ originalName
2254
+ });
2255
+ }
2256
+ if (bindingByAnchor.size === 0) return void 0;
2257
+ const refPath = bindingElement.$ref;
2258
+ if (typeof refPath !== "string") return void 0;
2259
+ const { name: genericName, refPaths: templateRefPaths } = getRefInfo(refPath, context);
2260
+ const templateDefs = (templateRefPaths ? prop(context.spec, ...templateRefPaths) : void 0)?.$defs;
2261
+ const typeArgs = [];
2262
+ const genericParams = [];
2263
+ const imports = [];
2264
+ if (templateDefs && typeof templateDefs === "object") {
2265
+ const templateAnchors = [];
2266
+ for (const defSchema of Object.values(templateDefs)) {
2267
+ if (!defSchema || typeof defSchema !== "object") continue;
2268
+ const rec = defSchema;
2269
+ if (rec.$dynamicAnchor === void 0 || rec.$ref !== void 0) continue;
2270
+ templateAnchors.push(rec.$dynamicAnchor);
2271
+ }
2272
+ const uniqueNames = dynamicAnchorsToUniqueParamNames(templateAnchors);
2273
+ for (const anchor of templateAnchors) {
2274
+ const binding = bindingByAnchor.get(anchor);
2275
+ if (binding) {
2276
+ typeArgs.push(binding.typeName);
2277
+ imports.push({
2278
+ name: binding.typeName,
2279
+ schemaName: binding.originalName
2280
+ });
2281
+ } else {
2282
+ const paramName = uniqueNames.get(anchor) ?? dynamicAnchorToParamName(anchor);
2283
+ typeArgs.push(paramName);
2284
+ genericParams.push(paramName);
2285
+ }
2286
+ }
2287
+ }
2288
+ if (typeArgs.length === 0) for (const { typeName, originalName } of bindingByAnchor.values()) {
2289
+ typeArgs.push(typeName);
2290
+ imports.push({
2291
+ name: typeName,
2292
+ schemaName: originalName
2293
+ });
2294
+ }
2294
2295
  return {
2295
- value: resolvedValue.value,
2296
- imports: resolvedValue.imports,
2297
- dependencies: resolvedValue.dependencies
2296
+ genericName,
2297
+ genericParams,
2298
+ typeArgs,
2299
+ imports,
2300
+ extraSchemas
2298
2301
  };
2299
2302
  }
2303
+ function getSchema$1(schema, context) {
2304
+ if (!schema.$ref) throw new Error(`${REF_NOT_FOUND_PREFIX}: missing $ref`);
2305
+ const refInfo = getRefInfo(schema.$ref, context);
2306
+ const { refPaths } = refInfo;
2307
+ const schemaByRefPaths = Array.isArray(refPaths) ? prop(context.spec, ...refPaths) : void 0;
2308
+ if (isObject(schemaByRefPaths) && isReference(schemaByRefPaths)) return getSchema$1(schemaByRefPaths, context);
2309
+ let currentSchema = schemaByRefPaths;
2310
+ if (isObject(currentSchema) && "nullable" in schema) {
2311
+ const nullable = schema.nullable;
2312
+ currentSchema = {
2313
+ ...currentSchema,
2314
+ nullable
2315
+ };
2316
+ }
2317
+ if (isObject(currentSchema) && "type" in schema && Array.isArray(schema.type)) {
2318
+ const type = schema.type;
2319
+ currentSchema = {
2320
+ ...currentSchema,
2321
+ type
2322
+ };
2323
+ }
2324
+ return {
2325
+ currentSchema,
2326
+ refInfo
2327
+ };
2328
+ }
2329
+ function encodeJsonPointerSegment(segment) {
2330
+ return segment.replaceAll("~", "~0").replaceAll("/", "~1");
2331
+ }
2300
2332
  /**
2301
- * Generate index signature key type based on propertyNames enum or const
2302
- * Returns union type string like "'foo' | 'bar'", "'x'", or 'string' if neither
2333
+ * Build the dynamic scope for a schema: maps `$dynamicAnchor` names to concrete
2334
+ * type entries for self-referential resolution, `$defs` bindings, and sibling anchors.
2303
2335
  */
2304
- function getIndexSignatureKey(item) {
2305
- return getPropertyNamesEnumKeyType(item)?.value ?? "string";
2336
+ function buildDynamicScope(schemaName, schema, context) {
2337
+ const scope = {};
2338
+ const getSchemaScopeEntry = (name) => {
2339
+ const refInfo = getRefInfo(`#/components/schemas/${encodeJsonPointerSegment(name)}`, context);
2340
+ return {
2341
+ name: refInfo.name,
2342
+ schemaName: refInfo.originalName
2343
+ };
2344
+ };
2345
+ const schemaRecord = schema;
2346
+ if (typeof schemaRecord.$dynamicAnchor === "string") scope[schemaRecord.$dynamicAnchor] = getSchemaScopeEntry(schemaName);
2347
+ const defs = schemaRecord.$defs;
2348
+ if (defs && typeof defs === "object") {
2349
+ const unboundAnchors = [];
2350
+ for (const [, defSchema] of Object.entries(defs)) {
2351
+ if (!defSchema || typeof defSchema !== "object") continue;
2352
+ const defRecord = defSchema;
2353
+ if (typeof defRecord.$dynamicAnchor === "string") {
2354
+ const anchorName = defRecord.$dynamicAnchor;
2355
+ const refInDef = defSchema.$ref;
2356
+ if (refInDef?.startsWith("#/components/schemas/")) {
2357
+ const { name, originalName } = getRefInfo(refInDef, context);
2358
+ scope[anchorName] = {
2359
+ name,
2360
+ schemaName: originalName
2361
+ };
2362
+ } else if (!refInDef) unboundAnchors.push(anchorName);
2363
+ }
2364
+ }
2365
+ if (unboundAnchors.length > 0) {
2366
+ const uniqueNames = dynamicAnchorsToUniqueParamNames(unboundAnchors);
2367
+ for (const anchor of unboundAnchors) {
2368
+ const paramName = uniqueNames.get(anchor);
2369
+ if (paramName === void 0) continue;
2370
+ scope[anchor] = {
2371
+ name: paramName,
2372
+ schemaName: paramName,
2373
+ isParameter: true
2374
+ };
2375
+ }
2376
+ }
2377
+ }
2378
+ return scope;
2306
2379
  }
2307
- function getPropertyNamesRecordType(item, valueType, context) {
2308
- const keyType = getPropertyNamesKeyType(item, context);
2309
- if (!keyType) return;
2380
+ /**
2381
+ * Resolve a `$dynamicRef` anchor to its concrete type using the current dynamic scope.
2382
+ * Returns `{ schema: {}, resolvedTypeName: 'unknown' }` when no scope override exists.
2383
+ */
2384
+ function resolveDynamicRef(anchorName, context, imports = []) {
2385
+ let scopeEntry = (context.dynamicScope ?? {})[anchorName];
2386
+ if (!scopeEntry) {
2387
+ const schemas = context.spec.components?.schemas;
2388
+ if (schemas && typeof schemas === "object") for (const [schemaName, schemaObj] of Object.entries(schemas)) {
2389
+ if (!schemaObj || typeof schemaObj !== "object") continue;
2390
+ if (schemaObj.$dynamicAnchor === anchorName) {
2391
+ const refInfo = getRefInfo(`#/components/schemas/${encodeJsonPointerSegment(schemaName)}`, context);
2392
+ scopeEntry = {
2393
+ name: refInfo.name,
2394
+ schemaName: refInfo.originalName
2395
+ };
2396
+ break;
2397
+ }
2398
+ }
2399
+ }
2400
+ if (!scopeEntry) return {
2401
+ schema: {},
2402
+ imports,
2403
+ resolvedTypeName: "unknown"
2404
+ };
2405
+ if (scopeEntry.isParameter) return {
2406
+ schema: {},
2407
+ imports,
2408
+ resolvedTypeName: scopeEntry.name
2409
+ };
2410
+ const resolvedTypeName = scopeEntry.name;
2411
+ const schemaRef = `#/components/schemas/${encodeJsonPointerSegment(scopeEntry.schemaName)}`;
2412
+ try {
2413
+ const { schema: resolvedSchema, imports: resolvedImports } = resolveRef({ $ref: schemaRef }, context, imports);
2414
+ return {
2415
+ schema: resolvedSchema,
2416
+ imports: resolvedImports,
2417
+ resolvedTypeName
2418
+ };
2419
+ } catch {
2420
+ return {
2421
+ schema: {},
2422
+ imports,
2423
+ resolvedTypeName: "unknown"
2424
+ };
2425
+ }
2426
+ }
2427
+ /** Recursively resolves `$ref` entries in an examples array or record. */
2428
+ function resolveExampleRefs(examples, context) {
2429
+ if (!examples) return;
2430
+ return Array.isArray(examples) ? examples.map((example) => {
2431
+ if (isObject(example) && isReference(example)) {
2432
+ const { schema } = resolveRef(example, context);
2433
+ return schema.value;
2434
+ }
2435
+ return example;
2436
+ }) : (() => {
2437
+ const result = {};
2438
+ for (const [key, example] of Object.entries(examples)) result[key] = isObject(example) && isReference(example) ? resolveRef(example, context).schema.value : example;
2439
+ return result;
2440
+ })();
2441
+ }
2442
+ //#endregion
2443
+ //#region src/resolvers/value.ts
2444
+ const schemaArrayKeys = [
2445
+ "allOf",
2446
+ "anyOf",
2447
+ "oneOf",
2448
+ "prefixItems"
2449
+ ];
2450
+ const schemaObjectKeys = [
2451
+ "additionalProperties",
2452
+ "contains",
2453
+ "else",
2454
+ "if",
2455
+ "items",
2456
+ "not",
2457
+ "propertyNames",
2458
+ "then",
2459
+ "unevaluatedItems",
2460
+ "unevaluatedProperties"
2461
+ ];
2462
+ const schemaMapKeys = [
2463
+ "$defs",
2464
+ "dependentSchemas",
2465
+ "patternProperties",
2466
+ "properties"
2467
+ ];
2468
+ /**
2469
+ * Recursively walks a schema value and returns `true` if any nested
2470
+ * `$dynamicRef` resolves — via the current `context.dynamicScope` — to a
2471
+ * schema *other* than `refName`.
2472
+ *
2473
+ * Used by `resolveValue` to decide whether a `$ref`'d schema must be
2474
+ * instantiated with its bound type arguments rather than referenced by name.
2475
+ *
2476
+ * @param value - The schema node (or sub-node) to inspect.
2477
+ * @param context - Current resolution context, including the dynamic scope.
2478
+ * @param refName - The resolved name of the enclosing `$ref` schema; dynamic
2479
+ * refs that resolve to this same name are considered
2480
+ * self-references and do not count as "scope-affected".
2481
+ * @param seen - Cycle-guard; tracks already-visited objects.
2482
+ */
2483
+ function hasScopeAffectedDynamicRef(value, context, refName, seen = /* @__PURE__ */ new WeakSet()) {
2484
+ if (!value || typeof value !== "object") return false;
2485
+ if (!context.dynamicScope || Object.keys(context.dynamicScope).length === 0) return false;
2486
+ if (seen.has(value)) return false;
2487
+ seen.add(value);
2488
+ if (isDynamicReference(value) && value.$dynamicRef.startsWith("#")) {
2489
+ const anchorName = getDynamicAnchorName(value.$dynamicRef);
2490
+ if (anchorName) {
2491
+ const scopeEntry = context.dynamicScope[anchorName];
2492
+ if (scopeEntry && scopeEntry.name !== refName) return true;
2493
+ }
2494
+ }
2495
+ const schema = value;
2496
+ for (const key of schemaArrayKeys) {
2497
+ const items = schema[key];
2498
+ if (Array.isArray(items) && items.some((item) => hasScopeAffectedDynamicRef(item, context, refName, seen))) return true;
2499
+ }
2500
+ for (const key of schemaObjectKeys) if (hasScopeAffectedDynamicRef(schema[key], context, refName, seen)) return true;
2501
+ for (const key of schemaMapKeys) {
2502
+ const schemaMap = schema[key];
2503
+ if (schemaMap && typeof schemaMap === "object" && Object.values(schemaMap).some((item) => hasScopeAffectedDynamicRef(item, context, refName, seen))) return true;
2504
+ }
2505
+ return false;
2506
+ }
2507
+ function makeUnknownValue(originalSchema) {
2310
2508
  return {
2311
- ...keyType,
2312
- value: `Partial<Record<${keyType.value}, ${valueType}>>`
2509
+ value: "unknown",
2510
+ imports: [],
2511
+ type: "unknown",
2512
+ isEnum: false,
2513
+ schemas: [],
2514
+ isRef: false,
2515
+ hasReadonlyProps: false,
2516
+ originalSchema,
2517
+ dependencies: []
2313
2518
  };
2314
2519
  }
2315
2520
  /**
2316
- * Return the output type from an object
2521
+ * Resolves an OpenAPI schema or reference object to a {@link ResolverValue}
2522
+ * that carries the TypeScript type string, required imports, and metadata.
2317
2523
  *
2318
- * @param item item with type === "object"
2524
+ * Handles all schema forms in priority order:
2525
+ * 1. **Bound generic alias** — a `$ref` with `$defs` overrides; emits an
2526
+ * instantiated generic expression such as `Paginated<User>`.
2527
+ * 2. **Component `$ref`** — a named `$ref` pointing to `#/components/…`;
2528
+ * emits the schema name as a reference import.
2529
+ * 3. **Non-component `$ref`** — an anonymous or path-level ref; inlines the
2530
+ * resolved schema via {@link getScalar} (cycle-safe).
2531
+ * 4. **`$dynamicRef`** — resolved via the active dynamic scope; falls back to
2532
+ * `unknown` when the anchor is absent or the ref is a bare `#`.
2533
+ * 5. **Plain schema** — delegates to {@link getScalar} for all other cases
2534
+ * (primitives, objects, arrays, enums, …).
2319
2535
  */
2320
- function getObject({ item, name, context, nullable, formDataContext }) {
2321
- if (isReference(item)) {
2322
- const { name } = getRefInfo(item.$ref, context);
2536
+ function resolveValue({ schema, name, context, formDataContext }) {
2537
+ if (isReference(schema)) {
2538
+ const alias = extractBoundAliasInfo(schema, context);
2539
+ if (alias) {
2540
+ const value = `${alias.genericName}<${alias.typeArgs.join(", ")}>`;
2541
+ const allImports = [{
2542
+ name: alias.genericName,
2543
+ schemaName: alias.genericName
2544
+ }, ...alias.imports];
2545
+ return {
2546
+ value,
2547
+ imports: allImports,
2548
+ type: "object",
2549
+ schemas: [],
2550
+ isEnum: false,
2551
+ originalSchema: schema,
2552
+ hasReadonlyProps: false,
2553
+ isRef: true,
2554
+ dependencies: allImports.map((i) => i.name)
2555
+ };
2556
+ }
2557
+ const refValue = schema.$ref;
2558
+ const { schema: schemaObject, imports } = resolveRef(schema, context);
2559
+ if (refValue && !isComponentRef(refValue)) {
2560
+ if (context.parents?.includes(refValue)) return {
2561
+ value: "unknown",
2562
+ imports: [],
2563
+ schemas: [],
2564
+ type: "unknown",
2565
+ isEnum: false,
2566
+ originalSchema: schemaObject,
2567
+ hasReadonlyProps: false,
2568
+ isRef: false,
2569
+ dependencies: []
2570
+ };
2571
+ return {
2572
+ ...getScalar({
2573
+ item: schemaObject,
2574
+ name,
2575
+ context: {
2576
+ ...context,
2577
+ parents: [...context.parents ?? [], refValue]
2578
+ },
2579
+ formDataContext
2580
+ }),
2581
+ originalSchema: schemaObject,
2582
+ isRef: false
2583
+ };
2584
+ }
2585
+ if (formDataContext?.urlEncoded && isBinaryScalarSchema(schemaObject)) return {
2586
+ ...getScalar({
2587
+ item: schemaObject,
2588
+ name,
2589
+ context,
2590
+ formDataContext
2591
+ }),
2592
+ originalSchema: schemaObject,
2593
+ isRef: false
2594
+ };
2595
+ const resolvedImport = imports[0];
2596
+ let hasReadonlyProps = false;
2597
+ const refName = resolvedImport.name;
2598
+ if (!context.parents?.includes(refName) && hasScopeAffectedDynamicRef(schemaObject, context, refName)) return {
2599
+ ...getScalar({
2600
+ item: schemaObject,
2601
+ name: name ?? refName,
2602
+ context: {
2603
+ ...context,
2604
+ parents: [...context.parents ?? [], refName]
2605
+ },
2606
+ formDataContext
2607
+ }),
2608
+ originalSchema: schemaObject,
2609
+ isRef: false
2610
+ };
2611
+ if (!context.parents?.includes(refName)) hasReadonlyProps = getScalar({
2612
+ item: schemaObject,
2613
+ name: refName,
2614
+ context: {
2615
+ ...context,
2616
+ parents: [...context.parents ?? [], refName]
2617
+ }
2618
+ }).hasReadonlyProps;
2619
+ const isAnyOfNullable = schemaObject.anyOf?.some((anyOfItem) => !isReference(anyOfItem) && (anyOfItem.type === "null" || Array.isArray(anyOfItem.type) && anyOfItem.type.includes("null")));
2620
+ const schemaType = schemaObject.type;
2621
+ const nullable = Array.isArray(schemaType) && schemaType.includes("null") || schemaObject.nullable === true || isAnyOfNullable ? " | null" : "";
2323
2622
  return {
2324
- value: name + nullable,
2325
- imports: [{ name }],
2623
+ value: resolvedImport.name + nullable,
2624
+ imports: [{
2625
+ name: resolvedImport.name,
2626
+ schemaName: resolvedImport.schemaName
2627
+ }],
2628
+ type: schemaObject.type ?? "object",
2326
2629
  schemas: [],
2327
- isEnum: false,
2630
+ isEnum: !!schemaObject.enum,
2631
+ originalSchema: schemaObject,
2632
+ hasReadonlyProps,
2633
+ isRef: true,
2634
+ dependencies: [resolvedImport.name]
2635
+ };
2636
+ }
2637
+ if (isDynamicReference(schema)) {
2638
+ const dynamicRef = schema.$dynamicRef;
2639
+ if (!dynamicRef.startsWith("#")) return makeUnknownValue(schema);
2640
+ const anchorName = getDynamicAnchorName(dynamicRef);
2641
+ if (!anchorName) return makeUnknownValue(schema);
2642
+ const { imports: resolvedImports, resolvedTypeName } = resolveDynamicRef(anchorName, context);
2643
+ if (resolvedTypeName === "unknown") return makeUnknownValue(schema);
2644
+ return {
2645
+ value: resolvedTypeName,
2646
+ imports: resolvedImports,
2328
2647
  type: "object",
2648
+ isEnum: false,
2649
+ schemas: [],
2329
2650
  isRef: true,
2330
- hasReadonlyProps: item.readOnly ?? false,
2331
- dependencies: [name],
2332
- example: item.example,
2333
- examples: resolveExampleRefs(item.examples, context)
2651
+ hasReadonlyProps: false,
2652
+ originalSchema: schema,
2653
+ dependencies: [resolvedTypeName]
2334
2654
  };
2335
2655
  }
2336
- const schemaItem = item;
2337
- const itemAllOf = schemaItem.allOf;
2338
- const itemOneOf = schemaItem.oneOf;
2339
- const itemAnyOf = schemaItem.anyOf;
2340
- const itemType = schemaItem.type;
2341
- if (itemAllOf || itemOneOf || itemAnyOf) return combineSchemas({
2342
- schema: schemaItem,
2343
- name,
2344
- separator: itemAllOf ? "allOf" : itemOneOf ? "oneOf" : "anyOf",
2345
- context,
2346
- nullable,
2347
- formDataContext
2348
- });
2349
- if (Array.isArray(itemType)) {
2350
- const typeArray = itemType;
2351
- const baseItem = schemaItem;
2352
- return combineSchemas({
2353
- schema: { anyOf: typeArray.map((type) => ({
2354
- ...baseItem,
2355
- type
2356
- })) },
2656
+ return {
2657
+ ...getScalar({
2658
+ item: schema,
2357
2659
  name,
2358
- separator: "anyOf",
2359
2660
  context,
2360
- nullable
2361
- });
2362
- }
2363
- const itemProperties = schemaItem.properties;
2364
- if (itemProperties && Object.entries(itemProperties).length > 0) {
2365
- const entries = Object.entries(itemProperties);
2366
- if (context.output.propertySortOrder === PropertySortOrder.ALPHABETICAL) entries.sort((a, b) => {
2367
- return a[0].localeCompare(b[0], "en", { numeric: true });
2661
+ formDataContext
2662
+ }),
2663
+ originalSchema: schema,
2664
+ isRef: false
2665
+ };
2666
+ }
2667
+ //#endregion
2668
+ //#region src/resolvers/object.ts
2669
+ /**
2670
+ * Wraps inline object type in a type alias.
2671
+ * E.g. `{ foo: string }` → value becomes `FooBody`, schema gets `export type FooBody = { foo: string };`
2672
+ */
2673
+ function createTypeAliasIfNeeded({ resolvedValue, propName, context }) {
2674
+ if (!propName) return;
2675
+ if (resolvedValue.isEnum || resolvedValue.type !== "object") return;
2676
+ const aliasPattern = context.output.override.aliasCombinedTypes ? String.raw`{|&|\|` : "{";
2677
+ if (!new RegExp(aliasPattern).test(resolvedValue.value)) return;
2678
+ const { originalSchema } = resolvedValue;
2679
+ const doc = jsDoc(originalSchema);
2680
+ const isConstant = "const" in originalSchema;
2681
+ const constantIsString = "type" in originalSchema && (originalSchema.type === "string" || Array.isArray(originalSchema.type) && originalSchema.type.includes("string"));
2682
+ const model = isConstant ? `${doc}export const ${propName} = ${constantIsString ? `'${originalSchema.const}'` : originalSchema.const} as const;\n` : `${doc}export type ${propName} = ${resolvedValue.value};\n`;
2683
+ return {
2684
+ value: propName,
2685
+ imports: [{
2686
+ name: propName,
2687
+ isConstant
2688
+ }],
2689
+ schemas: [...resolvedValue.schemas, {
2690
+ name: propName,
2691
+ model,
2692
+ imports: resolvedValue.imports,
2693
+ dependencies: resolvedValue.dependencies
2694
+ }],
2695
+ isEnum: false,
2696
+ type: "object",
2697
+ isRef: resolvedValue.isRef,
2698
+ hasReadonlyProps: resolvedValue.hasReadonlyProps,
2699
+ dependencies: resolvedValue.dependencies
2700
+ };
2701
+ }
2702
+ function resolveObjectOriginal({ schema, propName, combined = false, context, formDataContext }) {
2703
+ const resolvedValue = resolveValue({
2704
+ schema,
2705
+ name: propName,
2706
+ context,
2707
+ formDataContext
2708
+ });
2709
+ const aliased = createTypeAliasIfNeeded({
2710
+ resolvedValue,
2711
+ propName,
2712
+ context
2713
+ });
2714
+ if (aliased) return {
2715
+ ...aliased,
2716
+ originalSchema: resolvedValue.originalSchema
2717
+ };
2718
+ if (propName && resolvedValue.isEnum && !combined && !resolvedValue.isRef) {
2719
+ const doc = jsDoc(resolvedValue.originalSchema);
2720
+ const enumValue = getEnum(resolvedValue.value, propName, getEnumNames(resolvedValue.originalSchema), context.output.override.enumGenerationType, getEnumDescriptions(resolvedValue.originalSchema), context.output.override.namingConvention.enum);
2721
+ return {
2722
+ value: propName,
2723
+ imports: [{ name: propName }],
2724
+ schemas: [...resolvedValue.schemas, {
2725
+ name: propName,
2726
+ model: doc + enumValue,
2727
+ imports: resolvedValue.imports,
2728
+ dependencies: resolvedValue.dependencies
2729
+ }],
2730
+ isEnum: false,
2731
+ type: "enum",
2732
+ originalSchema: resolvedValue.originalSchema,
2733
+ isRef: resolvedValue.isRef,
2734
+ hasReadonlyProps: resolvedValue.hasReadonlyProps,
2735
+ dependencies: [...resolvedValue.dependencies, propName]
2736
+ };
2737
+ }
2738
+ return resolvedValue;
2739
+ }
2740
+ const resolveObjectCacheMap = /* @__PURE__ */ new Map();
2741
+ function resolveObject({ schema, propName, combined = false, context, formDataContext }) {
2742
+ const hashKey = JSON.stringify({
2743
+ schema,
2744
+ propName,
2745
+ combined,
2746
+ projectName: context.projectName ?? context.output.target,
2747
+ formDataContext,
2748
+ dynamicScope: context.dynamicScope
2749
+ });
2750
+ if (resolveObjectCacheMap.has(hashKey)) return resolveObjectCacheMap.get(hashKey);
2751
+ const result = resolveObjectOriginal({
2752
+ schema,
2753
+ propName,
2754
+ combined,
2755
+ context,
2756
+ formDataContext
2757
+ });
2758
+ resolveObjectCacheMap.set(hashKey, result);
2759
+ return result;
2760
+ }
2761
+ //#endregion
2762
+ //#region src/getters/array.ts
2763
+ /**
2764
+ * Return the output type from an array
2765
+ *
2766
+ * @param item item with type === "array"
2767
+ */
2768
+ function getArray({ schema, name, context, formDataContext }) {
2769
+ const schemaPrefixItems = schema.prefixItems;
2770
+ const schemaItems = schema.items;
2771
+ const schemaExample = schema.example;
2772
+ const schemaExamples = schema.examples;
2773
+ const itemSuffix = context.output.override.components.schemas.itemSuffix;
2774
+ if (schemaPrefixItems) {
2775
+ const resolvedObjects = schemaPrefixItems.map((item, index) => resolveObject({
2776
+ schema: item,
2777
+ propName: name ? name + itemSuffix + String(index) : void 0,
2778
+ context
2779
+ }));
2780
+ if (schemaItems) {
2781
+ const additional = resolveObject({
2782
+ schema: schemaItems,
2783
+ propName: name ? name + itemSuffix + "Additional" : void 0,
2784
+ context
2785
+ });
2786
+ resolvedObjects.push({
2787
+ ...additional,
2788
+ value: `...${additional.value}[]`
2789
+ });
2790
+ }
2791
+ return {
2792
+ type: "array",
2793
+ isEnum: false,
2794
+ isRef: false,
2795
+ value: `[${resolvedObjects.map((o) => o.value).join(", ")}]`,
2796
+ imports: resolvedObjects.flatMap((o) => o.imports),
2797
+ schemas: resolvedObjects.flatMap((o) => o.schemas),
2798
+ dependencies: resolvedObjects.flatMap((o) => o.dependencies),
2799
+ hasReadonlyProps: resolvedObjects.some((o) => o.hasReadonlyProps),
2800
+ example: schemaExample,
2801
+ examples: resolveExampleRefs(schemaExamples, context)
2802
+ };
2803
+ }
2804
+ if (schemaItems) {
2805
+ const resolvedObject = resolveObject({
2806
+ schema: schemaItems,
2807
+ propName: name ? name + itemSuffix : void 0,
2808
+ context,
2809
+ formDataContext
2368
2810
  });
2369
- const acc = {
2370
- imports: [],
2371
- schemas: [],
2372
- value: "",
2811
+ return {
2812
+ value: `${schema.readOnly === true && !context.output.override.suppressReadonlyModifier ? "readonly " : ""}${resolvedObject.value.includes("|") || resolvedObject.value.includes("&") ? `(${resolvedObject.value})[]` : `${resolvedObject.value}[]`}`,
2813
+ imports: resolvedObject.imports,
2814
+ schemas: resolvedObject.schemas,
2815
+ dependencies: resolvedObject.dependencies,
2373
2816
  isEnum: false,
2374
- type: "object",
2817
+ type: "array",
2375
2818
  isRef: false,
2376
- hasReadonlyProps: false,
2377
- useTypeAlias: false,
2378
- dependencies: [],
2379
- example: schemaItem.example,
2380
- examples: resolveExampleRefs(schemaItem.examples, context)
2819
+ hasReadonlyProps: resolvedObject.hasReadonlyProps,
2820
+ example: schemaExample,
2821
+ examples: resolveExampleRefs(schemaExamples, context)
2381
2822
  };
2382
- const itemRequired = schemaItem.required;
2383
- for (const [index, [key, schema]] of entries.entries()) {
2384
- const isRequired = (Array.isArray(itemRequired) ? itemRequired : []).includes(key);
2385
- let propName = "";
2386
- if (name) {
2387
- const isKeyStartWithUnderscore = key.startsWith("_");
2388
- propName += pascal(`${isKeyStartWithUnderscore ? "_" : ""}${name}_${key}`);
2389
- }
2390
- const allSpecSchemas = context.spec.components?.schemas ?? {};
2391
- if (Object.keys(allSpecSchemas).some((schemaName) => pascal(schemaName) === propName)) propName = propName + "Property";
2392
- const propertyFormDataContext = formDataContext && !formDataContext.atPart ? {
2393
- atPart: true,
2394
- partContentType: formDataContext.encoding[key]?.contentType,
2395
- urlEncoded: formDataContext.urlEncoded
2396
- } : void 0;
2397
- const resolvedValue = resolveObject({
2398
- schema,
2399
- propName,
2400
- context,
2401
- formDataContext: propertyFormDataContext
2402
- });
2403
- const isReadOnly = Boolean(schemaItem.readOnly) || Boolean(schema.readOnly);
2404
- if (!index) acc.value += "{";
2405
- const doc = jsDoc(schema, true, context);
2406
- const propertyDoc = doc ? `${doc.trimEnd().split("\n").map((line) => ` ${line}`).join("\n")}\n` : "";
2407
- if (isReadOnly || resolvedValue.hasReadonlyProps) acc.hasReadonlyProps = true;
2408
- const constValue = "const" in schema ? schema.const : void 0;
2409
- const hasConst = constValue !== void 0;
2410
- let constLiteral;
2411
- if (!hasConst) constLiteral = void 0;
2412
- else if (isString(constValue)) constLiteral = `'${escape(constValue)}'`;
2413
- else constLiteral = JSON.stringify(constValue);
2414
- const needsValueImport = hasConst && (resolvedValue.isEnum || resolvedValue.type === "enum");
2415
- const usedResolvedValue = !hasConst || needsValueImport;
2416
- const aliasedImports = needsValueImport ? resolvedValue.imports.map((imp) => ({
2417
- ...imp,
2418
- isConstant: true
2419
- })) : hasConst ? [] : getAliasedImports({
2823
+ } else if (compareVersions(context.spec.openapi ?? "3.0.0", "3.1", ">=")) return {
2824
+ value: "unknown[]",
2825
+ imports: [],
2826
+ schemas: [],
2827
+ dependencies: [],
2828
+ isEnum: false,
2829
+ type: "array",
2830
+ isRef: false,
2831
+ hasReadonlyProps: false
2832
+ };
2833
+ else throw new Error(`All arrays must have an \`items\` key defined (name=${name}, schema=${JSON.stringify(schema)})`);
2834
+ }
2835
+ //#endregion
2836
+ //#region src/getters/res-req-types.ts
2837
+ const getSchemaType$1 = (s) => s.type;
2838
+ const getSchemaCombined = (s) => s.oneOf ?? s.anyOf ?? s.allOf;
2839
+ const getSchemaOneOf = (s) => s.oneOf;
2840
+ const getSchemaAnyOf = (s) => s.anyOf;
2841
+ const getSchemaItems = (s) => s.items;
2842
+ const getSchemaRequired = (s) => s.required;
2843
+ const getSchemaProperties = (s) => s.properties;
2844
+ const resolveSchemaRef = (schema, context) => resolveRef(schema, context);
2845
+ const resolveResponseOrRequestRef = (schema, context) => resolveRef(schema, context);
2846
+ const formDataContentTypes = new Set(["multipart/form-data"]);
2847
+ const formUrlEncodedContentTypes = new Set(["application/x-www-form-urlencoded"]);
2848
+ function getResReqContentTypes({ mediaType, propName, context, isFormData, contentType }) {
2849
+ if (!mediaType.schema) return;
2850
+ const isFormUrlEncoded = formUrlEncodedContentTypes.has(contentType);
2851
+ const formDataContext = isFormData ? {
2852
+ atPart: false,
2853
+ encoding: mediaType.encoding ?? {}
2854
+ } : isFormUrlEncoded ? {
2855
+ atPart: false,
2856
+ encoding: mediaType.encoding ?? {},
2857
+ urlEncoded: true
2858
+ } : void 0;
2859
+ const resolvedObject = resolveObject({
2860
+ schema: mediaType.schema,
2861
+ propName,
2862
+ context,
2863
+ formDataContext
2864
+ });
2865
+ if (!isFormData && isBinaryContentType(contentType)) return {
2866
+ ...resolvedObject,
2867
+ value: "Blob"
2868
+ };
2869
+ return resolvedObject;
2870
+ }
2871
+ function getResReqTypes(responsesOrRequests, name, context, defaultType = "unknown", uniqueKey = (item) => item.value) {
2872
+ return uniqueBy(responsesOrRequests.filter(([, res]) => Boolean(res)).map(([key, res]) => {
2873
+ if (isReference(res)) {
2874
+ const { schema: bodySchema, imports: [{ name, schemaName }] } = resolveResponseOrRequestRef(res, context);
2875
+ const firstEntry = Object.entries(bodySchema.content ?? {}).at(0);
2876
+ if (!firstEntry) return [{
2877
+ value: name,
2878
+ imports: [{
2879
+ name,
2880
+ schemaName
2881
+ }],
2882
+ schemas: [],
2883
+ type: "unknown",
2884
+ isEnum: false,
2885
+ isRef: true,
2886
+ hasReadonlyProps: false,
2887
+ dependencies: [name],
2888
+ originalSchema: void 0,
2889
+ example: void 0,
2890
+ examples: void 0,
2891
+ key,
2892
+ contentType: ""
2893
+ }];
2894
+ const [contentType, mediaType] = firstEntry;
2895
+ const isFormData = formDataContentTypes.has(contentType);
2896
+ const isFormUrlEncoded = formUrlEncodedContentTypes.has(contentType);
2897
+ if (!isFormData && !isFormUrlEncoded || !mediaType.schema) return [{
2898
+ value: name,
2899
+ imports: [{
2900
+ name,
2901
+ schemaName
2902
+ }],
2903
+ schemas: [],
2904
+ type: "unknown",
2905
+ isEnum: false,
2906
+ isRef: true,
2907
+ hasReadonlyProps: false,
2908
+ dependencies: [name],
2909
+ originalSchema: mediaType.schema,
2910
+ example: mediaType.example,
2911
+ examples: resolveExampleRefs(mediaType.examples, context),
2912
+ key,
2913
+ contentType
2914
+ }];
2915
+ const formData = isFormData ? getSchemaFormDataAndUrlEncoded({
2420
2916
  name,
2917
+ schemaObject: mediaType.schema,
2421
2918
  context,
2422
- resolvedValue
2423
- });
2424
- if (aliasedImports.length > 0) acc.imports.push(...aliasedImports);
2425
- const alias = getImportAliasForRefOrValue({
2919
+ isRequestBodyOptional: bodySchema.required !== true,
2920
+ isRef: true,
2921
+ encoding: mediaType.encoding
2922
+ }) : void 0;
2923
+ const formUrlEncoded = isFormUrlEncoded ? getSchemaFormDataAndUrlEncoded({
2924
+ name,
2925
+ schemaObject: mediaType.schema,
2426
2926
  context,
2427
- resolvedValue,
2428
- imports: aliasedImports
2927
+ isRequestBodyOptional: bodySchema.required !== true,
2928
+ isUrlEncoded: true,
2929
+ isRef: true,
2930
+ encoding: mediaType.encoding
2931
+ }) : void 0;
2932
+ const additionalImports = getFormDataAdditionalImports({
2933
+ schemaObject: mediaType.schema,
2934
+ context
2429
2935
  });
2430
- const propValue = needsValueImport ? alias : constLiteral ?? alias;
2431
- const finalPropValue = isRequired ? propValue : context.output.override.useNullForOptional === true ? `${propValue} | null` : propValue;
2432
- acc.value += `\n${propertyDoc}${isReadOnly && !context.output.override.suppressReadonlyModifier ? " readonly " : " "}${getKey(key)}${isRequired ? "" : "?"}: ${finalPropValue};`;
2433
- if (usedResolvedValue) {
2434
- acc.schemas.push(...resolvedValue.schemas);
2435
- acc.dependencies.push(...resolvedValue.dependencies);
2436
- }
2437
- if (entries.length - 1 === index) {
2438
- const additionalProps = schemaItem.additionalProperties;
2439
- if (additionalProps) if (additionalProps === true) {
2440
- const recordType = getPropertyNamesRecordType(schemaItem, "unknown", context);
2441
- if (recordType) {
2442
- acc.value += "\n}";
2443
- acc.value += ` & ${recordType.value}`;
2444
- acc.useTypeAlias = true;
2445
- acc.imports.push(...recordType.imports);
2446
- acc.dependencies.push(...recordType.dependencies);
2447
- } else {
2448
- const keyType = getIndexSignatureKey(schemaItem);
2449
- acc.value += `\n [key: ${keyType}]: unknown;\n }`;
2450
- }
2451
- } else {
2452
- const resolvedValue = resolveValue({
2453
- schema: additionalProps,
2454
- name,
2455
- context
2456
- });
2457
- const recordType = getPropertyNamesRecordType(schemaItem, resolvedValue.value, context);
2458
- if (recordType) {
2459
- acc.value += "\n}";
2460
- acc.value += ` & ${recordType.value}`;
2461
- acc.useTypeAlias = true;
2462
- acc.imports.push(...recordType.imports);
2463
- acc.dependencies.push(...recordType.dependencies);
2464
- } else {
2465
- const keyType = getIndexSignatureKey(schemaItem);
2466
- acc.value += `\n [key: ${keyType}]: ${resolvedValue.value};\n}`;
2936
+ return [{
2937
+ value: name,
2938
+ imports: [{
2939
+ name,
2940
+ schemaName
2941
+ }, ...additionalImports],
2942
+ schemas: [],
2943
+ type: "unknown",
2944
+ isEnum: false,
2945
+ hasReadonlyProps: false,
2946
+ dependencies: [name],
2947
+ formData,
2948
+ formUrlEncoded,
2949
+ isRef: true,
2950
+ originalSchema: mediaType.schema,
2951
+ example: mediaType.example,
2952
+ examples: resolveExampleRefs(mediaType.examples, context),
2953
+ key,
2954
+ contentType
2955
+ }];
2956
+ }
2957
+ if (res.content) return Object.entries(res.content).map(([contentType, mediaType], index, arr) => {
2958
+ let propName = key ? pascal(name) + pascal(key) : void 0;
2959
+ if (propName && arr.length > 1) propName = propName + pascal(getNumberWord(index + 1));
2960
+ const isFormData = formDataContentTypes.has(contentType);
2961
+ const isFormUrlEncoded = formUrlEncodedContentTypes.has(contentType);
2962
+ let effectivePropName = propName;
2963
+ if (mediaType.schema && isReference(mediaType.schema)) {
2964
+ const { imports } = resolveSchemaRef(mediaType.schema, context);
2965
+ if (imports[0]?.name) effectivePropName = imports[0].name;
2966
+ } else if ((isFormData || isFormUrlEncoded) && mediaType.schema) {
2967
+ const combinedRefs = getSchemaOneOf(mediaType.schema) ?? getSchemaAnyOf(mediaType.schema);
2968
+ if (combinedRefs) {
2969
+ const names = [];
2970
+ for (const ref of combinedRefs) {
2971
+ if (!isReference(ref)) continue;
2972
+ const refName = resolveSchemaRef(ref, context).imports[0]?.name;
2973
+ if (refName) names.push(refName);
2467
2974
  }
2468
- acc.imports.push(...resolvedValue.imports);
2469
- acc.schemas.push(...resolvedValue.schemas);
2470
- acc.dependencies.push(...resolvedValue.dependencies);
2975
+ if (names.length > 0) effectivePropName = names.join("");
2471
2976
  }
2472
- else acc.value += "\n}";
2473
- acc.value += nullable;
2474
2977
  }
2475
- }
2476
- return acc;
2477
- }
2478
- const outerAdditionalProps = schemaItem.additionalProperties;
2479
- const readOnlyFlag = schemaItem.readOnly;
2480
- if (outerAdditionalProps) {
2481
- if (outerAdditionalProps === true) {
2482
- const recordType = getPropertyNamesRecordType(schemaItem, "unknown", context);
2483
- if (recordType) return {
2484
- value: recordType.value + nullable,
2485
- imports: recordType.imports,
2486
- schemas: [],
2487
- isEnum: false,
2488
- type: "object",
2489
- isRef: false,
2490
- hasReadonlyProps: readOnlyFlag ?? false,
2491
- useTypeAlias: true,
2492
- dependencies: recordType.dependencies
2978
+ const resolvedValue = getResReqContentTypes({
2979
+ mediaType,
2980
+ propName: effectivePropName,
2981
+ context,
2982
+ isFormData,
2983
+ contentType
2984
+ });
2985
+ if (!resolvedValue) {
2986
+ if (isBinaryContentType(contentType)) return {
2987
+ value: "Blob",
2988
+ imports: [],
2989
+ schemas: [],
2990
+ type: "Blob",
2991
+ isEnum: false,
2992
+ key,
2993
+ isRef: false,
2994
+ hasReadonlyProps: false,
2995
+ contentType
2996
+ };
2997
+ return;
2998
+ }
2999
+ if (!isFormData && !isFormUrlEncoded || !effectivePropName || !mediaType.schema) return {
3000
+ ...resolvedValue,
3001
+ imports: resolvedValue.imports,
3002
+ dependencies: resolvedValue.dependencies,
3003
+ contentType,
3004
+ example: mediaType.example,
3005
+ examples: resolveExampleRefs(mediaType.examples, context)
2493
3006
  };
3007
+ const formData = isFormData ? getSchemaFormDataAndUrlEncoded({
3008
+ name: effectivePropName,
3009
+ schemaObject: mediaType.schema,
3010
+ context,
3011
+ isRequestBodyOptional: res.required !== true,
3012
+ isRef: true,
3013
+ encoding: mediaType.encoding
3014
+ }) : void 0;
3015
+ const formUrlEncoded = isFormUrlEncoded ? getSchemaFormDataAndUrlEncoded({
3016
+ name: effectivePropName,
3017
+ schemaObject: mediaType.schema,
3018
+ context,
3019
+ isUrlEncoded: true,
3020
+ isRequestBodyOptional: res.required !== true,
3021
+ isRef: true,
3022
+ encoding: mediaType.encoding
3023
+ }) : void 0;
3024
+ const additionalImports = getFormDataAdditionalImports({
3025
+ schemaObject: mediaType.schema,
3026
+ context
3027
+ });
2494
3028
  return {
2495
- value: `{ [key: ${getIndexSignatureKey(schemaItem)}]: unknown }` + nullable,
2496
- imports: [],
2497
- schemas: [],
2498
- isEnum: false,
2499
- type: "object",
2500
- isRef: false,
2501
- hasReadonlyProps: readOnlyFlag ?? false,
2502
- useTypeAlias: false,
2503
- dependencies: []
3029
+ ...resolvedValue,
3030
+ imports: [...resolvedValue.imports, ...additionalImports],
3031
+ formData,
3032
+ formUrlEncoded,
3033
+ contentType,
3034
+ example: mediaType.example,
3035
+ examples: resolveExampleRefs(mediaType.examples, context)
2504
3036
  };
2505
- }
2506
- const resolvedValue = resolveValue({
2507
- schema: outerAdditionalProps,
2508
- name,
2509
- context
2510
- });
2511
- const recordType = getPropertyNamesRecordType(schemaItem, resolvedValue.value, context);
2512
- if (recordType) return {
2513
- value: recordType.value + nullable,
2514
- imports: [...recordType.imports, ...resolvedValue.imports],
2515
- schemas: resolvedValue.schemas,
2516
- isEnum: false,
2517
- type: "object",
2518
- isRef: false,
2519
- hasReadonlyProps: resolvedValue.hasReadonlyProps,
2520
- useTypeAlias: true,
2521
- dependencies: [...recordType.dependencies, ...resolvedValue.dependencies]
2522
- };
2523
- return {
2524
- value: `{[key: ${getIndexSignatureKey(schemaItem)}]: ${resolvedValue.value}}` + nullable,
2525
- imports: resolvedValue.imports,
2526
- schemas: resolvedValue.schemas,
2527
- isEnum: false,
2528
- type: "object",
2529
- isRef: false,
2530
- hasReadonlyProps: resolvedValue.hasReadonlyProps,
2531
- useTypeAlias: false,
2532
- dependencies: resolvedValue.dependencies
2533
- };
2534
- }
2535
- const constValue = schemaItem.const;
2536
- if (constValue !== void 0) {
2537
- let type;
2538
- if (Array.isArray(constValue)) type = "array";
2539
- else if (constValue === null) type = "null";
2540
- else if (typeof constValue === "string") type = "string";
2541
- else if (typeof constValue === "number") type = "number";
2542
- else if (typeof constValue === "boolean") type = "boolean";
2543
- else type = "object";
2544
- return {
2545
- value: typeof constValue === "string" ? `'${escape(constValue)}'` : JSON.stringify(constValue),
3037
+ }).filter(Boolean).map((x) => ({
3038
+ ...x,
3039
+ key
3040
+ }));
3041
+ const swaggerSchema = "schema" in res ? res.schema : void 0;
3042
+ if (swaggerSchema) return [{
3043
+ ...resolveObject({
3044
+ schema: swaggerSchema,
3045
+ propName: key ? pascal(name) + pascal(key) : void 0,
3046
+ context
3047
+ }),
3048
+ contentType: "application/json",
3049
+ key
3050
+ }];
3051
+ return [{
3052
+ value: defaultType,
2546
3053
  imports: [],
2547
3054
  schemas: [],
3055
+ type: defaultType,
2548
3056
  isEnum: false,
2549
- type,
3057
+ dependencies: [],
3058
+ key,
2550
3059
  isRef: false,
2551
- hasReadonlyProps: readOnlyFlag ?? false,
2552
- dependencies: []
2553
- };
2554
- }
2555
- const keyType = itemType === "object" ? getIndexSignatureKey(schemaItem) : "string";
2556
- const recordType = getPropertyNamesRecordType(schemaItem, "unknown", context);
2557
- if (itemType === "object" && recordType) return {
2558
- value: recordType.value + nullable,
2559
- imports: recordType.imports,
2560
- schemas: [],
2561
- isEnum: false,
2562
- type: "object",
2563
- isRef: false,
2564
- hasReadonlyProps: readOnlyFlag ?? false,
2565
- useTypeAlias: true,
2566
- dependencies: recordType.dependencies
2567
- };
2568
- return {
2569
- value: (itemType === "object" ? `{ [key: ${keyType}]: unknown }` : "unknown") + nullable,
2570
- imports: [],
2571
- schemas: [],
2572
- isEnum: false,
2573
- type: "object",
2574
- isRef: false,
2575
- hasReadonlyProps: readOnlyFlag ?? false,
2576
- useTypeAlias: false,
2577
- dependencies: []
2578
- };
3060
+ hasReadonlyProps: false,
3061
+ contentType: "application/json"
3062
+ }];
3063
+ }).flat(), uniqueKey);
2579
3064
  }
2580
- //#endregion
2581
- //#region src/getters/scalar.ts
2582
3065
  /**
2583
- * Return the typescript equivalent of open-api data type
3066
+ * Determine the responseType option based on success content types only.
3067
+ * This avoids error-response content types influencing the responseType.
3068
+ */
3069
+ function getSuccessResponseType(response) {
3070
+ const successContentTypes = response.types.success.map((t) => t.contentType).filter(Boolean);
3071
+ if (response.isBlob) return "blob";
3072
+ const hasJsonResponse = successContentTypes.some((contentType) => contentType.includes("json") || contentType.includes("+json"));
3073
+ const hasTextResponse = successContentTypes.some((contentType) => contentType.startsWith("text/") || contentType.includes("xml"));
3074
+ if (!hasJsonResponse && hasTextResponse) return "text";
3075
+ }
3076
+ /**
3077
+ * Determine the response type category for a given content type.
3078
+ * Used to set the correct responseType option in HTTP clients.
2584
3079
  *
2585
- * @param item
2586
- * @ref https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.1.md#data-types
3080
+ * @param contentType - The MIME content type (e.g., 'application/json', 'text/plain')
3081
+ * @returns The response type category to use for parsing
2587
3082
  */
2588
- function getScalar({ item, name, context, formDataContext }) {
2589
- const schemaEnum = item.enum;
2590
- const schemaType = item.type;
2591
- const schemaReadOnly = item.readOnly;
2592
- const schemaExample = item.example;
2593
- const schemaExamples = item.examples;
2594
- const schemaConst = item.const;
2595
- const schemaFormat = item.format;
2596
- const schemaNullable = item.nullable;
2597
- const schemaContentMediaType = item.contentMediaType;
2598
- const schemaContentEncoding = item.contentEncoding;
2599
- const nullable = isArray(schemaType) && schemaType.includes("null") || schemaNullable === true ? " | null" : "";
2600
- const enumItems = schemaEnum?.filter((enumItem) => enumItem !== null);
2601
- let itemType = schemaType;
2602
- if (!itemType && item.items) {
2603
- item.type = "array";
2604
- itemType = "array";
2605
- }
2606
- if (isArray(schemaType) && schemaType.includes("null")) {
2607
- const typesWithoutNull = schemaType.filter((x) => x !== "null");
2608
- itemType = typesWithoutNull.length === 1 ? typesWithoutNull[0] : typesWithoutNull;
2609
- }
2610
- switch (itemType) {
2611
- case "number":
2612
- case "integer": {
2613
- let value = context.output.override.useBigInt && (schemaFormat === "int64" || schemaFormat === "uint64") ? "bigint" : "number";
2614
- let isEnum = false;
2615
- if (enumItems) {
2616
- value = enumItems.map((enumItem) => `${enumItem}`).join(" | ");
2617
- isEnum = true;
2618
- }
2619
- value += nullable;
2620
- if (schemaConst !== void 0) value = schemaConst;
2621
- return {
2622
- value,
2623
- isEnum,
2624
- type: "number",
2625
- schemas: [],
2626
- imports: [],
2627
- isRef: false,
2628
- hasReadonlyProps: schemaReadOnly ?? false,
2629
- dependencies: [],
2630
- example: schemaExample,
2631
- examples: resolveExampleRefs(schemaExamples, context)
2632
- };
2633
- }
2634
- case "boolean": {
2635
- let value = "boolean";
2636
- if (enumItems && !(enumItems.includes(true) && enumItems.includes(false))) value = enumItems.map((enumItem) => `${enumItem}`).join(" | ");
2637
- value += nullable;
2638
- if (schemaConst !== void 0) value = schemaConst;
2639
- return {
2640
- value,
2641
- type: "boolean",
2642
- isEnum: false,
2643
- schemas: [],
2644
- imports: [],
2645
- isRef: false,
2646
- hasReadonlyProps: schemaReadOnly ?? false,
2647
- dependencies: [],
2648
- example: schemaExample,
2649
- examples: resolveExampleRefs(schemaExamples, context)
2650
- };
2651
- }
2652
- case "array": {
2653
- const { value, ...rest } = getArray({
2654
- schema: item,
2655
- name,
2656
- context,
2657
- formDataContext
2658
- });
2659
- return {
2660
- value: value + nullable,
2661
- ...rest,
2662
- dependencies: rest.dependencies
2663
- };
2664
- }
2665
- case "string": {
2666
- let value = "string";
2667
- let isEnum = false;
2668
- if (enumItems) {
2669
- value = enumItems.map((enumItem) => isString(enumItem) ? `'${escape(enumItem)}'` : `${enumItem}`).filter(Boolean).join(` | `);
2670
- isEnum = true;
2671
- }
2672
- if (!formDataContext?.urlEncoded) {
2673
- if (schemaFormat === "binary") value = "Blob";
2674
- else if (formDataContext?.atPart) {
2675
- const fileType = getFormDataFieldFileType(item, formDataContext.partContentType);
2676
- if (fileType) value = fileType === "binary" ? "Blob" : "Blob | string";
2677
- } else if (schemaContentMediaType === "application/octet-stream" && !schemaContentEncoding) value = "Blob";
3083
+ function getResponseTypeCategory(contentType) {
3084
+ if (isBinaryContentType(contentType)) return "blob";
3085
+ if (contentType === "application/json" || contentType.includes("+json") || contentType.includes("-json")) return "json";
3086
+ return "text";
3087
+ }
3088
+ /**
3089
+ * Get the default content type from a list of content types.
3090
+ * Priority: application/json > any JSON-like type > first in list
3091
+ *
3092
+ * @param contentTypes - Array of content types from OpenAPI spec
3093
+ * @returns The default content type to use
3094
+ */
3095
+ function getDefaultContentType(contentTypes) {
3096
+ if (contentTypes.length === 0) return "application/json";
3097
+ if (contentTypes.includes("application/json")) return "application/json";
3098
+ const jsonType = contentTypes.find((ct) => ct.includes("+json") || ct.includes("-json"));
3099
+ if (jsonType) return jsonType;
3100
+ return contentTypes[0];
3101
+ }
3102
+ function getFormDataAdditionalImports({ schemaObject, context }) {
3103
+ const { schema } = resolveSchemaRef(schemaObject, context);
3104
+ if (schema.type !== "object") return [];
3105
+ const combinedSchemas = getSchemaOneOf(schema) ?? getSchemaAnyOf(schema);
3106
+ if (!combinedSchemas) return [];
3107
+ return combinedSchemas.map((subSchema) => resolveSchemaRef(subSchema, context).imports[0]).filter(Boolean);
3108
+ }
3109
+ function getSchemaFormDataAndUrlEncoded({ name, schemaObject, context, isRequestBodyOptional, isUrlEncoded, isRef, encoding }) {
3110
+ const { schema, imports } = resolveSchemaRef(schemaObject, context);
3111
+ const propName = camel(!isRef && isReference(schemaObject) ? imports[0].name : name);
3112
+ const variableName = isUrlEncoded ? "formUrlEncoded" : "formData";
3113
+ let form = isUrlEncoded ? `const ${variableName} = new URLSearchParams();\n` : `const ${variableName} = new FormData();\n`;
3114
+ const combinedSchemas = getSchemaCombined(schema);
3115
+ if (schema.type === "object" || schema.type === void 0 && combinedSchemas) {
3116
+ if (combinedSchemas) if (!!getSchemaOneOf(schema) || !!getSchemaAnyOf(schema)) {
3117
+ const directProperties = getSchemaProperties(schema);
3118
+ const directKeys = directProperties ? Object.entries(directProperties).filter(([, value]) => !resolveSchemaRef(value, context).schema.readOnly).map(([key]) => key) : [];
3119
+ const skipLine = directKeys.length > 0 ? ` if ([${directKeys.map((k) => JSON.stringify(k)).join(", ")}].includes(key)) return;\n` : "";
3120
+ form += `Object.entries(${propName} ?? {}).forEach(([key, value]) => {\n`;
3121
+ form += skipLine;
3122
+ form += ` if (value !== undefined && value !== null) {\n`;
3123
+ if (isUrlEncoded) {
3124
+ form += ` if (Array.isArray(value)) {\n`;
3125
+ form += ` value.forEach(v => {\n`;
3126
+ form += ` ${variableName}.append(key, typeof v === 'object' ? JSON.stringify(v) : String(v));\n`;
3127
+ form += ` });\n`;
3128
+ form += ` } else if (typeof value === 'object') {\n`;
3129
+ form += ` ${variableName}.append(key, JSON.stringify(value));\n`;
3130
+ form += ` } else {\n`;
3131
+ form += ` ${variableName}.append(key, String(value));\n`;
3132
+ form += ` }\n`;
3133
+ } else {
3134
+ form += ` if ((typeof File !== 'undefined' && value instanceof File) || value instanceof Blob) {\n`;
3135
+ form += ` ${variableName}.append(key, value);\n`;
3136
+ form += ` } else if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {\n`;
3137
+ form += ` ${variableName}.append(key, new Blob([Uint8Array.from(value)]));\n`;
3138
+ form += ` } else if (Array.isArray(value)) {\n`;
3139
+ form += ` value.forEach(v => {\n`;
3140
+ form += ` if ((typeof File !== 'undefined' && v instanceof File) || v instanceof Blob) {\n`;
3141
+ form += ` ${variableName}.append(key, v);\n`;
3142
+ form += ` } else if (typeof Buffer !== 'undefined' && Buffer.isBuffer(v)) {\n`;
3143
+ form += ` ${variableName}.append(key, new Blob([Uint8Array.from(v)]));\n`;
3144
+ form += ` } else {\n`;
3145
+ form += ` ${variableName}.append(key, typeof v === 'object' ? JSON.stringify(v) : String(v));\n`;
3146
+ form += ` }\n`;
3147
+ form += ` });\n`;
3148
+ form += ` } else if (typeof value === 'object') {\n`;
3149
+ form += ` ${variableName}.append(key, JSON.stringify(value));\n`;
3150
+ form += ` } else {\n`;
3151
+ form += ` ${variableName}.append(key, String(value));\n`;
3152
+ form += ` }\n`;
2678
3153
  }
2679
- if (context.output.override.useDates && (schemaFormat === "date" || schemaFormat === "date-time")) value = "Date";
2680
- value += nullable;
2681
- if (schemaConst) value = `'${schemaConst}'`;
2682
- return {
2683
- value,
2684
- isEnum,
2685
- type: "string",
2686
- imports: [],
2687
- schemas: [],
2688
- isRef: false,
2689
- hasReadonlyProps: schemaReadOnly ?? false,
2690
- dependencies: [],
2691
- example: schemaExample,
2692
- examples: resolveExampleRefs(schemaExamples, context)
2693
- };
3154
+ form += ` }\n`;
3155
+ form += `});\n`;
3156
+ } else {
3157
+ const combinedSchemasFormData = combinedSchemas.map((subSchema) => {
3158
+ const { schema: combinedSchema } = resolveSchemaRef(subSchema, context);
3159
+ return resolveSchemaPropertiesToFormData({
3160
+ schema: combinedSchema,
3161
+ variableName,
3162
+ propName,
3163
+ context,
3164
+ isRequestBodyOptional,
3165
+ encoding
3166
+ });
3167
+ }).filter(Boolean).join("\n");
3168
+ form += combinedSchemasFormData;
2694
3169
  }
2695
- case "null": return {
2696
- value: "null",
2697
- isEnum: false,
2698
- type: "null",
2699
- imports: [],
2700
- schemas: [],
2701
- isRef: false,
2702
- hasReadonlyProps: schemaReadOnly ?? false,
2703
- dependencies: []
2704
- };
2705
- default: {
2706
- if (isArray(itemType)) return combineSchemas({
2707
- schema: { anyOf: itemType.map((type) => Object.assign({}, item, { type })) },
2708
- name,
2709
- separator: "anyOf",
2710
- context,
2711
- nullable
2712
- });
2713
- if (enumItems) return {
2714
- value: enumItems.map((enumItem) => isString(enumItem) ? `'${escape(enumItem)}'` : String(enumItem)).filter(Boolean).join(` | `) + nullable,
2715
- isEnum: true,
2716
- type: "string",
2717
- imports: [],
2718
- schemas: [],
2719
- isRef: false,
2720
- hasReadonlyProps: schemaReadOnly ?? false,
2721
- dependencies: [],
2722
- example: schemaExample,
2723
- examples: resolveExampleRefs(schemaExamples, context)
2724
- };
2725
- const hasCombiners = item.allOf ?? item.anyOf ?? item.oneOf;
2726
- const { value, ...rest } = getObject({
2727
- item,
2728
- name,
3170
+ if (schema.properties) {
3171
+ const formDataValues = resolveSchemaPropertiesToFormData({
3172
+ schema,
3173
+ variableName,
3174
+ propName,
2729
3175
  context,
2730
- nullable,
2731
- formDataContext: formDataContext?.atPart === false || formDataContext?.atPart && hasCombiners ? formDataContext : void 0
2732
- });
2733
- return {
2734
- value,
2735
- ...rest
2736
- };
2737
- }
2738
- }
2739
- }
2740
- //#endregion
2741
- //#region src/getters/combine.ts
2742
- const mergeableAllOfKeys = new Set([
2743
- "type",
2744
- "properties",
2745
- "required"
2746
- ]);
2747
- function isMergeableAllOfObject(schema) {
2748
- if (isNullish$1(schema.properties)) return false;
2749
- if (schema.allOf || schema.anyOf || schema.oneOf) return false;
2750
- if (!isNullish$1(schema.type) && schema.type !== "object") return false;
2751
- return Object.keys(schema).every((key) => mergeableAllOfKeys.has(key));
2752
- }
2753
- function normalizeAllOfSchema(schema) {
2754
- const schemaAllOf = schema.allOf;
2755
- if (!schemaAllOf) return schema;
2756
- let didMerge = false;
2757
- const schemaProperties = schema.properties;
2758
- const schemaRequired = schema.required;
2759
- const mergedProperties = schemaProperties ? { ...schemaProperties } : {};
2760
- const mergedRequired = new Set(schemaRequired);
2761
- const remainingAllOf = [];
2762
- for (const subSchema of schemaAllOf) {
2763
- if (isSchema(subSchema) && isMergeableAllOfObject(subSchema)) {
2764
- didMerge = true;
2765
- if (subSchema.properties) Object.assign(mergedProperties, subSchema.properties);
2766
- const subRequired = subSchema.required;
2767
- if (subRequired) for (const prop of subRequired) mergedRequired.add(prop);
2768
- continue;
2769
- }
2770
- remainingAllOf.push(subSchema);
2771
- }
2772
- if (!didMerge || remainingAllOf.length === 0) return schema;
2773
- return {
2774
- ...schema,
2775
- ...Object.keys(mergedProperties).length > 0 && { properties: mergedProperties },
2776
- ...mergedRequired.size > 0 && { required: [...mergedRequired] },
2777
- ...remainingAllOf.length > 0 && { allOf: remainingAllOf }
2778
- };
2779
- }
2780
- function combineValues({ resolvedData, resolvedValue, separator, context, parentSchema }) {
2781
- if (resolvedData.isEnum.every(Boolean)) return `${resolvedData.values.join(` | `)}${resolvedValue ? ` | ${resolvedValue.value}` : ""}`;
2782
- if (separator === "allOf") {
2783
- let resolvedDataValue = resolvedData.values.map((v) => v.includes(" | ") ? `(${v})` : v).join(` & `);
2784
- if (resolvedData.originalSchema.length > 0 && resolvedValue) {
2785
- const discriminatedPropertySchemas = resolvedData.originalSchema.filter((s) => {
2786
- const disc = s?.discriminator;
2787
- return disc && resolvedValue.value.includes(` ${disc.propertyName}:`);
3176
+ isRequestBodyOptional,
3177
+ encoding
2788
3178
  });
2789
- if (discriminatedPropertySchemas.length > 0) resolvedDataValue = `Omit<${resolvedDataValue}, '${discriminatedPropertySchemas.map((s) => s.discriminator?.propertyName).join("' | '")}'>`;
3179
+ form += formDataValues;
2790
3180
  }
2791
- const resolvedValueStr = resolvedValue?.value.includes(" | ") ? `(${resolvedValue.value})` : resolvedValue?.value;
2792
- const joined = `${resolvedDataValue}${resolvedValue ? ` & ${resolvedValueStr}` : ""}`;
2793
- const overrideRequiredProperties = resolvedData.requiredProperties.filter((prop) => !resolvedData.originalSchema.some((schema) => {
2794
- const props = schema?.properties;
2795
- const req = schema?.required;
2796
- return props?.[prop] && req?.includes(prop);
2797
- }) && !(() => {
2798
- const parentProps = parentSchema?.properties;
2799
- const parentReq = parentSchema?.required;
2800
- return !!(parentProps?.[prop] && parentReq?.includes(prop));
2801
- })());
2802
- if (overrideRequiredProperties.length > 0) return `${joined} & Required<Pick<${joined}, '${overrideRequiredProperties.join("' | '")}'>>`;
2803
- return joined;
3181
+ return form;
2804
3182
  }
2805
- let values = resolvedData.values;
2806
- if (resolvedData.allProperties.length && context.output.unionAddMissingProperties) {
2807
- values = [];
2808
- for (let i = 0; i < resolvedData.values.length; i += 1) {
2809
- const subSchema = resolvedData.originalSchema[i];
2810
- if (subSchema?.type !== "object" || !subSchema.properties) {
2811
- values.push(resolvedData.values[i]);
2812
- continue;
2813
- }
2814
- const subSchemaProps = subSchema.properties;
2815
- const missingProperties = unique(resolvedData.allProperties.filter((p) => !Object.keys(subSchemaProps).includes(p)));
2816
- values.push(`${resolvedData.values[i]}${missingProperties.length > 0 ? ` & {${missingProperties.map((p) => `${p}?: never`).join("; ")}}` : ""}`);
3183
+ if (schema.type === "array") {
3184
+ let valueStr = "value";
3185
+ const schemaItems = getSchemaItems(schema);
3186
+ if (schemaItems) {
3187
+ const { schema: itemSchema } = resolveSchemaRef(schemaItems, context);
3188
+ if (itemSchema.type === "object" || itemSchema.type === "array") valueStr = "JSON.stringify(value)";
3189
+ else if (itemSchema.type === "number" || itemSchema.type === "integer" || itemSchema.type === "boolean") valueStr = "value.toString()";
2817
3190
  }
3191
+ return `${form}${propName}.forEach(value => ${variableName}.append('data', ${valueStr}))\n`;
2818
3192
  }
2819
- if (resolvedValue) return `(${values.join(` & ${resolvedValue.value}) | (`)} & ${resolvedValue.value})`;
2820
- return values.join(" | ");
3193
+ if (schema.type === "number" || schema.type === "integer" || schema.type === "boolean") return `${form}${variableName}.append('data', ${propName}.toString())\n`;
3194
+ return `${form}${variableName}.append('data', ${propName})\n`;
2821
3195
  }
2822
- function combineSchemas({ name, schema, separator, context, nullable, formDataContext }) {
2823
- const normalizedSchema = separator === "allOf" && !context.output.override.aliasCombinedTypes && !schema.oneOf && !schema.anyOf ? normalizeAllOfSchema(schema) : schema;
2824
- const items = normalizedSchema[separator] ?? [];
2825
- const resolvedData = {
2826
- values: [],
2827
- imports: [],
2828
- schemas: [],
2829
- isEnum: [],
2830
- isRef: [],
2831
- types: [],
2832
- dependencies: [],
2833
- originalSchema: [],
2834
- allProperties: [],
2835
- hasReadonlyProps: false,
2836
- example: schema.example,
2837
- examples: resolveExampleRefs(schema.examples, context),
2838
- requiredProperties: separator === "allOf" ? schema.required ?? [] : []
2839
- };
2840
- for (const subSchema of items) {
2841
- let propName;
2842
- if (context.output.override.aliasCombinedTypes) {
2843
- propName = name ? name + pascal(separator) : void 0;
2844
- if (propName && resolvedData.schemas.length > 0) propName = propName + pascal(getNumberWord(resolvedData.schemas.length + 1));
2845
- }
2846
- if (separator === "allOf" && isSchema(subSchema) && subSchema.required) resolvedData.requiredProperties.push(...subSchema.required);
2847
- const resolvedValue = resolveObject({
2848
- schema: subSchema,
2849
- propName,
2850
- combined: true,
2851
- context,
2852
- formDataContext
2853
- });
2854
- const aliasedImports = getAliasedImports({
2855
- context,
2856
- name,
2857
- resolvedValue
2858
- });
2859
- const value = getImportAliasForRefOrValue({
3196
+ function resolveSchemaPropertiesToFormData({ schema, variableName, propName, context, isRequestBodyOptional, keyPrefix = "", depth = 0, encoding }) {
3197
+ let formDataValues = "";
3198
+ const isUrlEncoded = variableName === "formUrlEncoded";
3199
+ const schemaProps = getSchemaProperties(schema) ?? {};
3200
+ for (const [key, value] of Object.entries(schemaProps)) {
3201
+ const { schema: property } = resolveSchemaRef(value, context);
3202
+ if (property.readOnly) continue;
3203
+ let formDataValue = "";
3204
+ const partContentType = (depth === 0 ? encoding?.[key] : void 0)?.contentType;
3205
+ const formattedKeyPrefix = isRequestBodyOptional ? keyword.isIdentifierNameES5(key) ? "?" : "?." : "";
3206
+ const formattedKey = keyword.isIdentifierNameES5(key) ? `.${key}` : `['${key}']`;
3207
+ const valueKey = `${propName}${formattedKeyPrefix}${formattedKey}`;
3208
+ const nonOptionalValueKey = `${propName}${formattedKey}`;
3209
+ const fileType = getFormDataFieldFileType(property, partContentType);
3210
+ const effectiveContentType = partContentType ?? property.contentMediaType;
3211
+ if (isUrlEncoded && (fileType || property.format === "binary")) formDataValue = `${variableName}.append(\`${keyPrefix}${key}\`, ${nonOptionalValueKey});\n`;
3212
+ else if (fileType === "binary" || property.format === "binary") formDataValue = `${variableName}.append(\`${keyPrefix}${key}\`, ${nonOptionalValueKey});\n`;
3213
+ else if (fileType === "text") formDataValue = `${variableName}.append(\`${keyPrefix}${key}\`, ${nonOptionalValueKey} instanceof Blob ? ${nonOptionalValueKey} : new Blob([${nonOptionalValueKey}], { type: '${effectiveContentType}' }));\n`;
3214
+ else if (property.type === "object" || Array.isArray(property.type) && property.type.includes("object")) formDataValue = context.output.override.formData.arrayHandling === FormDataArrayHandling.EXPLODE ? resolveSchemaPropertiesToFormData({
3215
+ schema: property,
3216
+ variableName,
3217
+ propName: nonOptionalValueKey,
2860
3218
  context,
2861
- resolvedValue,
2862
- imports: aliasedImports
2863
- });
2864
- resolvedData.values.push(value);
2865
- resolvedData.imports.push(...aliasedImports);
2866
- resolvedData.schemas.push(...resolvedValue.schemas);
2867
- resolvedData.dependencies.push(...resolvedValue.dependencies);
2868
- resolvedData.isEnum.push(resolvedValue.isEnum);
2869
- resolvedData.types.push(resolvedValue.type);
2870
- resolvedData.isRef.push(resolvedValue.isRef);
2871
- resolvedData.originalSchema.push(resolvedValue.originalSchema);
2872
- if (resolvedValue.hasReadonlyProps) resolvedData.hasReadonlyProps = true;
2873
- const originalProps = resolvedValue.originalSchema.properties;
2874
- if (resolvedValue.type === "object" && originalProps) resolvedData.allProperties.push(...Object.keys(originalProps));
2875
- }
2876
- if (resolvedData.isEnum.every(Boolean) && name && items.length > 1 && context.output.override.enumGenerationType !== EnumGeneration.UNION) {
2877
- const { value: combinedEnumValue, valueImports, hasNull } = getCombinedEnumValue(resolvedData.values.map((value, index) => ({
2878
- value,
2879
- isRef: resolvedData.isRef[index],
2880
- schema: resolvedData.originalSchema[index]
2881
- })));
2882
- const newEnum = `export const ${pascal(name)} = ${combinedEnumValue}`;
2883
- const valueImportSet = new Set(valueImports);
2884
- const typeSuffix = `${nullable}${hasNull && !nullable.includes("null") ? " | null" : ""}`;
2885
- return {
2886
- value: `typeof ${pascal(name)}[keyof typeof ${pascal(name)}]${typeSuffix}`,
2887
- imports: [{ name: pascal(name) }],
2888
- schemas: [...resolvedData.schemas, {
2889
- imports: resolvedData.imports.filter((toImport) => valueImportSet.has(toImport.alias ?? toImport.name)).map((toImport) => ({
2890
- ...toImport,
2891
- values: true
2892
- })),
2893
- model: newEnum,
2894
- name
2895
- }],
2896
- isEnum: false,
2897
- type: "object",
2898
- isRef: false,
2899
- hasReadonlyProps: resolvedData.hasReadonlyProps,
2900
- dependencies: resolvedData.dependencies,
2901
- example: schema.example,
2902
- examples: resolveExampleRefs(schema.examples, context)
2903
- };
3219
+ isRequestBodyOptional,
3220
+ keyPrefix: `${keyPrefix}${key}.`,
3221
+ depth: depth + 1,
3222
+ encoding
3223
+ }) : `${variableName}.append(\`${keyPrefix}${key}\`, JSON.stringify(${nonOptionalValueKey}));\n`;
3224
+ else if (property.type === "array" || Array.isArray(property.type) && property.type.includes("array")) {
3225
+ let valueStr = "value";
3226
+ let hasNonPrimitiveChild = false;
3227
+ const propertyItems = getSchemaItems(property);
3228
+ if (propertyItems) {
3229
+ const { schema: itemSchema } = resolveSchemaRef(propertyItems, context);
3230
+ if (itemSchema.type === "object" || itemSchema.type === "array") if (context.output.override.formData.arrayHandling === FormDataArrayHandling.EXPLODE) {
3231
+ hasNonPrimitiveChild = true;
3232
+ const resolvedValue = resolveSchemaPropertiesToFormData({
3233
+ schema: itemSchema,
3234
+ variableName,
3235
+ propName: "value",
3236
+ context,
3237
+ isRequestBodyOptional,
3238
+ keyPrefix: `${keyPrefix}${key}[\${index${depth > 0 ? depth : ""}}].`,
3239
+ depth: depth + 1
3240
+ });
3241
+ formDataValue = `${valueKey}.forEach((value, index${depth > 0 ? depth : ""}) => {
3242
+ ${resolvedValue}});\n`;
3243
+ } else valueStr = "JSON.stringify(value)";
3244
+ else {
3245
+ const itemType = getSchemaType$1(itemSchema);
3246
+ if (itemType === "number" || Array.isArray(itemType) && itemType.includes("number") || itemType === "integer" || Array.isArray(itemType) && itemType.includes("integer") || itemType === "boolean" || Array.isArray(itemType) && itemType.includes("boolean")) valueStr = "value.toString()";
3247
+ }
3248
+ }
3249
+ if (context.output.override.formData.arrayHandling === FormDataArrayHandling.EXPLODE) {
3250
+ if (!hasNonPrimitiveChild) formDataValue = `${valueKey}.forEach((value, index${depth > 0 ? depth : ""}) => ${variableName}.append(\`${keyPrefix}${key}[\${index${depth > 0 ? depth : ""}}]\`, ${valueStr}));\n`;
3251
+ } else formDataValue = `${valueKey}.forEach(value => ${variableName}.append(\`${keyPrefix}${key}${context.output.override.formData.arrayHandling === FormDataArrayHandling.SERIALIZE_WITH_BRACKETS ? "[]" : ""}\`, ${valueStr}));\n`;
3252
+ } else if ((() => {
3253
+ const propType = getSchemaType$1(property);
3254
+ return propType === "number" || Array.isArray(propType) && propType.includes("number") || propType === "integer" || Array.isArray(propType) && propType.includes("integer") || propType === "boolean" || Array.isArray(propType) && propType.includes("boolean");
3255
+ })()) formDataValue = `${variableName}.append(\`${keyPrefix}${key}\`, ${nonOptionalValueKey}.toString())\n`;
3256
+ else formDataValue = `${variableName}.append(\`${keyPrefix}${key}\`, ${nonOptionalValueKey});\n`;
3257
+ let existSubSchemaNullable = false;
3258
+ const combine = getSchemaCombined(property);
3259
+ if (combine) {
3260
+ const subSchemas = combine.map((c) => resolveObject({
3261
+ schema: c,
3262
+ combined: true,
3263
+ context
3264
+ }));
3265
+ if (subSchemas.some((subSchema) => {
3266
+ return [
3267
+ "number",
3268
+ "integer",
3269
+ "boolean"
3270
+ ].includes(subSchema.type);
3271
+ })) formDataValue = `${variableName}.append(\`${key}\`, ${nonOptionalValueKey}.toString())\n`;
3272
+ if (subSchemas.some((subSchema) => {
3273
+ return subSchema.type === "null";
3274
+ })) existSubSchemaNullable = true;
3275
+ }
3276
+ const isRequired = getSchemaRequired(schema)?.includes(key) && !isRequestBodyOptional;
3277
+ const propType = getSchemaType$1(property);
3278
+ if (property.nullable || Array.isArray(propType) && propType.includes("null") || existSubSchemaNullable) {
3279
+ if (isRequired) {
3280
+ formDataValues += `if(${valueKey} !== null) {\n ${formDataValue} }\n`;
3281
+ continue;
3282
+ }
3283
+ formDataValues += `if(${valueKey} !== undefined && ${nonOptionalValueKey} !== null) {\n ${formDataValue} }\n`;
3284
+ continue;
3285
+ }
3286
+ if (isRequired) {
3287
+ formDataValues += formDataValue;
3288
+ continue;
3289
+ }
3290
+ formDataValues += `if(${valueKey} !== undefined) {\n ${formDataValue} }\n`;
2904
3291
  }
2905
- let resolvedValue;
2906
- const normalizedProperties = normalizedSchema.properties;
2907
- const schemaOneOf = schema.oneOf;
2908
- const schemaAnyOf = schema.anyOf;
2909
- if (normalizedProperties) resolvedValue = getScalar({
2910
- item: Object.fromEntries(Object.entries(normalizedSchema).filter(([key]) => key !== separator)),
2911
- name,
2912
- context,
2913
- formDataContext
2914
- });
2915
- else if (separator === "allOf" && (schemaOneOf || schemaAnyOf)) {
2916
- const siblingCombiner = schemaOneOf ? "oneOf" : "anyOf";
2917
- const siblingSchemas = schemaOneOf ?? schemaAnyOf;
2918
- resolvedValue = combineSchemas({
2919
- schema: { [siblingCombiner]: siblingSchemas },
2920
- name,
2921
- separator: siblingCombiner,
2922
- context,
2923
- nullable: ""
3292
+ return formDataValues;
3293
+ }
3294
+ //#endregion
3295
+ //#region src/getters/body.ts
3296
+ function buildBody(filteredBodyTypes, requestBody, operationName, context) {
3297
+ const imports = filteredBodyTypes.flatMap(({ imports }) => imports);
3298
+ const schemas = filteredBodyTypes.flatMap(({ schemas }) => schemas);
3299
+ const definition = filteredBodyTypes.map(({ value }) => value).join(" | ");
3300
+ const nonReadonlyDefinition = filteredBodyTypes.some((x) => x.hasReadonlyProps) && definition && context.output.override.preserveReadonlyRequestBodies !== "preserve" ? `NonReadonly<${definition}>` : definition;
3301
+ let implementation = generalJSTypesWithArray.includes(definition.toLowerCase()) || filteredBodyTypes.length > 1 ? camel(operationName) + context.output.override.components.requestBodies.suffix : camel(definition);
3302
+ let isOptional = false;
3303
+ if (implementation) {
3304
+ implementation = sanitize(implementation, {
3305
+ underscore: "_",
3306
+ whitespace: "_",
3307
+ dash: true,
3308
+ es5keyword: true,
3309
+ es5IdentifierName: true
2924
3310
  });
3311
+ if (isReference(requestBody)) {
3312
+ const { schema: bodySchema } = resolveRef(requestBody, context);
3313
+ isOptional = bodySchema.required !== true;
3314
+ } else isOptional = requestBody.required !== true;
2925
3315
  }
2926
3316
  return {
2927
- value: dedupeUnionType(combineValues({
2928
- resolvedData,
2929
- separator,
2930
- resolvedValue,
2931
- context,
2932
- parentSchema: normalizedSchema
2933
- }) + nullable),
2934
- imports: resolvedValue ? [...resolvedData.imports, ...resolvedValue.imports] : resolvedData.imports,
2935
- schemas: resolvedValue ? [...resolvedData.schemas, ...resolvedValue.schemas] : resolvedData.schemas,
2936
- dependencies: resolvedValue ? [...resolvedData.dependencies, ...resolvedValue.dependencies] : resolvedData.dependencies,
2937
- isEnum: false,
2938
- type: "object",
2939
- isRef: false,
2940
- hasReadonlyProps: resolvedData.hasReadonlyProps || (resolvedValue?.hasReadonlyProps ?? false),
2941
- example: schema.example,
2942
- examples: resolveExampleRefs(schema.examples, context)
3317
+ originalSchema: requestBody,
3318
+ definition: nonReadonlyDefinition,
3319
+ implementation,
3320
+ imports,
3321
+ schemas,
3322
+ isOptional,
3323
+ ...filteredBodyTypes.length === 1 ? {
3324
+ formData: filteredBodyTypes[0].formData,
3325
+ formUrlEncoded: filteredBodyTypes[0].formUrlEncoded,
3326
+ contentType: filteredBodyTypes[0].contentType
3327
+ } : {
3328
+ formData: "",
3329
+ formUrlEncoded: "",
3330
+ contentType: ""
3331
+ }
2943
3332
  };
2944
3333
  }
3334
+ function getBody({ requestBody, operationName, context, contentType }) {
3335
+ return buildBody(filterByContentType(getResReqTypes([[context.output.override.components.requestBodies.suffix, requestBody]], operationName, context), contentType), requestBody, operationName, context);
3336
+ }
3337
+ /**
3338
+ * Returns per-content-type bodies when `splitByContentType` is enabled.
3339
+ * Each entry includes a `contentTypeSuffix` for generating distinct function names.
3340
+ */
3341
+ function getBodiesByContentType({ requestBody, operationName, context, contentType }) {
3342
+ const filteredBodyTypes = filterByContentType(getResReqTypes([[context.output.override.components.requestBodies.suffix, requestBody]], operationName, context, void 0, (item) => `${item.value}::${item.contentType}`), contentType);
3343
+ if (filteredBodyTypes.length <= 1) return [{
3344
+ ...buildBody(filteredBodyTypes, requestBody, operationName, context),
3345
+ contentTypeSuffix: ""
3346
+ }];
3347
+ return filteredBodyTypes.map((bodyType) => {
3348
+ const suffix = getContentTypeSuffix(bodyType.contentType);
3349
+ return {
3350
+ ...buildBody([bodyType], requestBody, operationName, context),
3351
+ contentTypeSuffix: suffix
3352
+ };
3353
+ });
3354
+ }
3355
+ const CONTENT_TYPE_SUFFIX_MAP = {
3356
+ "application/json": "Json",
3357
+ "multipart/form-data": "FormData",
3358
+ "application/x-www-form-urlencoded": "UrlEncoded",
3359
+ "text/plain": "Text",
3360
+ "application/xml": "Xml",
3361
+ "text/xml": "Xml",
3362
+ "application/octet-stream": "Blob"
3363
+ };
3364
+ function getContentTypeSuffix(contentType) {
3365
+ if (CONTENT_TYPE_SUFFIX_MAP[contentType]) return CONTENT_TYPE_SUFFIX_MAP[contentType];
3366
+ return (contentType.split("/")[1] ?? contentType).split(/[-+.]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
3367
+ }
2945
3368
  //#endregion
2946
3369
  //#region src/getters/discriminators.ts
2947
3370
  function resolveDiscriminators(schemas, context) {
@@ -2991,6 +3414,61 @@ function resolveDiscriminators(schemas, context) {
2991
3414
  }
2992
3415
  }
2993
3416
  }
3417
+ for (const [parentName, parentSchema] of Object.entries(transformedSchemas)) {
3418
+ if (isBoolean$1(parentSchema)) continue;
3419
+ if (!parentSchema.oneOf || !parentSchema.discriminator?.mapping) continue;
3420
+ const { mapping, propertyName } = parentSchema.discriminator;
3421
+ if (!propertyName) continue;
3422
+ const parentProperties = parentSchema.properties;
3423
+ const parentRequired = parentSchema.required;
3424
+ const inheritableProps = {};
3425
+ if (parentProperties) {
3426
+ for (const [key, value] of Object.entries(parentProperties)) if (key !== propertyName) inheritableProps[key] = value;
3427
+ }
3428
+ const inheritableRequired = parentRequired?.filter((key) => key !== propertyName);
3429
+ const hasInheritableProps = Object.keys(inheritableProps).length > 0;
3430
+ for (const mappingValue of Object.values(mapping)) {
3431
+ let variantSchema;
3432
+ try {
3433
+ const { originalName } = getRefInfo(mappingValue, context);
3434
+ variantSchema = transformedSchemas[pascal(originalName)] ?? transformedSchemas[originalName];
3435
+ } catch {
3436
+ variantSchema = transformedSchemas[mappingValue];
3437
+ }
3438
+ if (!variantSchema || isBoolean$1(variantSchema)) continue;
3439
+ const variantAllOf = variantSchema.allOf;
3440
+ if (!variantAllOf) continue;
3441
+ const rewritten = [];
3442
+ for (const item of variantAllOf) {
3443
+ if (!isReference(item) || !item.$ref) {
3444
+ rewritten.push(item);
3445
+ continue;
3446
+ }
3447
+ let refOriginalName;
3448
+ try {
3449
+ refOriginalName = getRefInfo(item.$ref, context).originalName;
3450
+ } catch {
3451
+ refOriginalName = void 0;
3452
+ }
3453
+ if (!(refOriginalName === parentName || refOriginalName !== void 0 && pascal(refOriginalName) === pascal(parentName))) {
3454
+ rewritten.push(item);
3455
+ continue;
3456
+ }
3457
+ const inlinedParent = { ...parentSchema };
3458
+ delete inlinedParent.oneOf;
3459
+ delete inlinedParent.discriminator;
3460
+ delete inlinedParent.allOf;
3461
+ delete inlinedParent.anyOf;
3462
+ if (hasInheritableProps) inlinedParent.properties = { ...inheritableProps };
3463
+ else delete inlinedParent.properties;
3464
+ if (inheritableRequired && inheritableRequired.length > 0) inlinedParent.required = [...inheritableRequired];
3465
+ else delete inlinedParent.required;
3466
+ if (Object.keys(inlinedParent).filter((key) => key !== "type").length > 0) rewritten.push(inlinedParent);
3467
+ }
3468
+ if (rewritten.length === 0) delete variantSchema.allOf;
3469
+ else variantSchema.allOf = rewritten;
3470
+ }
3471
+ }
2994
3472
  return transformedSchemas;
2995
3473
  }
2996
3474
  //#endregion
@@ -3243,7 +3721,8 @@ function getQueryParamsTypes(queryParams, operationName, context) {
3243
3721
  };
3244
3722
  if (resolvedValue.isEnum && !resolvedValue.isRef) {
3245
3723
  const enumName = queryName;
3246
- const enumValue = getEnum(resolvedValue.value, enumName, getEnumNames(resolvedValue.originalSchema), context.output.override.enumGenerationType, getEnumDescriptions(resolvedValue.originalSchema), context.output.override.namingConvention.enum);
3724
+ const parameterAsSchema = parameter;
3725
+ const enumValue = getEnum(resolvedValue.value, enumName, getEnumNames(resolvedValue.originalSchema) ?? getEnumNames(parameterAsSchema), context.output.override.enumGenerationType, getEnumDescriptions(resolvedValue.originalSchema) ?? getEnumDescriptions(parameterAsSchema), context.output.override.namingConvention.enum);
3247
3726
  return {
3248
3727
  name,
3249
3728
  required,
@@ -3285,6 +3764,7 @@ function getQueryParams({ queryParams, operationName, context, suffix = "params"
3285
3764
  },
3286
3765
  deps: schemas,
3287
3766
  isOptional: allOptional,
3767
+ paramNames: types.map(({ name }) => name),
3288
3768
  requiredNullableKeys,
3289
3769
  ...nonPrimitiveKeys.length > 0 ? { nonPrimitiveKeys } : {}
3290
3770
  };
@@ -3443,6 +3923,231 @@ function generateComponentDefinition(responses = {}, context, suffix) {
3443
3923
  return generatorSchemas;
3444
3924
  }
3445
3925
  //#endregion
3926
+ //#region src/generators/factory.ts
3927
+ const circularRefCache = /* @__PURE__ */ new WeakMap();
3928
+ function getSchemasPath(context) {
3929
+ const { schemas, target } = context.output;
3930
+ if (schemas) return normalizeSafe(isString(schemas) ? schemas : schemas.path);
3931
+ const { dirname, filename } = getFileInfo(target);
3932
+ return joinSafe(dirname, filename + ".schemas");
3933
+ }
3934
+ function getSchemaImportPath(refName, context) {
3935
+ if (context.output.factoryMethods?.mode === "single") return;
3936
+ let outputDir = context.output.factoryMethods?.outputDirectory;
3937
+ let schemasPath = getSchemasPath(context);
3938
+ if (context.output.workspace) {
3939
+ if (outputDir && !isAbsolute(outputDir)) outputDir = resolve(context.output.workspace, outputDir);
3940
+ if (schemasPath && !isAbsolute(schemasPath)) schemasPath = resolve(context.output.workspace, schemasPath);
3941
+ }
3942
+ return joinSafe(outputDir ? relativeSafe(outputDir, schemasPath) : "./", conventionName(refName, context.output.namingConvention));
3943
+ }
3944
+ function isReference$1(schema) {
3945
+ return "$ref" in schema;
3946
+ }
3947
+ function getResolvedRef(schema, context) {
3948
+ return resolveRef(schema, context);
3949
+ }
3950
+ function getProperties(schema) {
3951
+ return schema.properties ?? {};
3952
+ }
3953
+ function getItems(schema) {
3954
+ return schema.items;
3955
+ }
3956
+ function getAdditionalProperties(schema) {
3957
+ return schema.additionalProperties;
3958
+ }
3959
+ function getSchemas(schemas) {
3960
+ return schemas;
3961
+ }
3962
+ function getExtendedProps(schema) {
3963
+ const extended = schema;
3964
+ return {
3965
+ constValue: extended.const,
3966
+ prefixItems: extended.prefixItems,
3967
+ minItems: extended.minItems
3968
+ };
3969
+ }
3970
+ function generateFactory(schema, name, context) {
3971
+ if (!canGenerateSchema(schema) || !context.output.factoryMethods) return void 0;
3972
+ const { functionNamePrefix, mode } = context.output.factoryMethods;
3973
+ const factoryName = `${functionNamePrefix}${pascal(name)}`;
3974
+ const imports = [];
3975
+ const payload = buildPayload(schema, context, [name], imports);
3976
+ if (mode !== "single") {
3977
+ const schemaImportPath = getSchemaImportPath(name, context);
3978
+ imports.push({
3979
+ name,
3980
+ importPath: schemaImportPath
3981
+ });
3982
+ }
3983
+ return {
3984
+ model: `export function ${factoryName}(): ${name} {\n return ${payload};\n}\n`,
3985
+ imports
3986
+ };
3987
+ }
3988
+ function canGenerateSchema(schema) {
3989
+ return schema.type === "object" || schema.type === "array" || !!schema.properties || !!schema.allOf || !!schema.oneOf || !!schema.anyOf || !!schema.items || !!schema.enum;
3990
+ }
3991
+ function hasCircularReference(target, sourceName, context, visited = /* @__PURE__ */ new Set()) {
3992
+ if (isReference$1(target)) {
3993
+ const { imports, schema } = getResolvedRef(target, context);
3994
+ const refName = imports[0]?.name;
3995
+ if (refName === sourceName) return true;
3996
+ if (refName && visited.has(refName)) return false;
3997
+ if (refName) visited.add(refName);
3998
+ let cache = circularRefCache.get(context);
3999
+ if (!cache) {
4000
+ cache = /* @__PURE__ */ new Map();
4001
+ circularRefCache.set(context, cache);
4002
+ }
4003
+ const cacheKey = refName ? `${sourceName}::${refName}` : void 0;
4004
+ if (cacheKey) {
4005
+ const cached = cache.get(cacheKey);
4006
+ if (cached !== void 0) return cached;
4007
+ }
4008
+ const result = hasCircularReference(schema, sourceName, context, visited);
4009
+ if (cacheKey) cache.set(cacheKey, result);
4010
+ return result;
4011
+ }
4012
+ const check = (schemas) => schemas?.some((s) => hasCircularReference(s, sourceName, context, visited)) ?? false;
4013
+ const items = getItems(target);
4014
+ const additionalProperties = getAdditionalProperties(target);
4015
+ return check(getSchemas(target.allOf)) || check(getSchemas(target.oneOf)) || check(getSchemas(target.anyOf)) || Object.values(getProperties(target)).some((s) => hasCircularReference(s, sourceName, context, visited)) || !!items && hasCircularReference(items, sourceName, context, visited) || typeof additionalProperties === "object" && hasCircularReference(additionalProperties, sourceName, context, visited);
4016
+ }
4017
+ function buildPayload(target, context, parents, imports) {
4018
+ if (isReference$1(target)) return buildRefPayload(target, context, parents, imports);
4019
+ const schema = target;
4020
+ if (schema.allOf) return buildAllOfPayload(getSchemas(schema.allOf) ?? [], context, parents, imports);
4021
+ if (schema.oneOf) return buildFirstOfPayload(getSchemas(schema.oneOf) ?? [], context, parents, imports);
4022
+ if (schema.anyOf) return buildFirstOfPayload(getSchemas(schema.anyOf) ?? [], context, parents, imports);
4023
+ const { constValue } = getExtendedProps(schema);
4024
+ if (constValue !== void 0) return formatValue(constValue);
4025
+ if (schema.default !== void 0) return buildDefaultPayload(schema, context);
4026
+ const schemaType = inferSchemaType(schema);
4027
+ if (schemaType === "object" || schema.properties) return buildObjectPayload(schema, context, parents, imports);
4028
+ if (schemaType === "array") return buildArrayPayload(schema, context, parents, imports);
4029
+ return buildPrimitivePayload(schema, schemaType, context);
4030
+ }
4031
+ function buildRefPayload(schema, context, parents, imports) {
4032
+ const { schema: resolved, imports: refImports } = getResolvedRef(schema, context);
4033
+ const refName = refImports[0]?.name;
4034
+ if (!refName) return "{}";
4035
+ if (parents.includes(refName) || hasCircularReference(resolved, parents[0], context)) {
4036
+ imports.push({
4037
+ name: refName,
4038
+ importPath: getSchemaImportPath(refName, context)
4039
+ });
4040
+ return `{} as ${refName}`;
4041
+ }
4042
+ const { functionNamePrefix = "create", mode = "single" } = context.output.factoryMethods ?? {};
4043
+ const refFactoryName = `${functionNamePrefix}${pascal(refName)}`;
4044
+ if (mode !== "single-split") {
4045
+ const importPath = resolveImportPath(mode, refName, context);
4046
+ imports.push({
4047
+ name: refFactoryName,
4048
+ importPath,
4049
+ isConstant: true
4050
+ });
4051
+ }
4052
+ imports.push({
4053
+ name: refName,
4054
+ importPath: getSchemaImportPath(refName, context)
4055
+ });
4056
+ return `${refFactoryName}()`;
4057
+ }
4058
+ function resolveImportPath(mode, refName, context) {
4059
+ const baseName = conventionName(refName, context.output.namingConvention);
4060
+ switch (mode) {
4061
+ case "split": return `./${baseName}.factory`;
4062
+ case "single-split": return `./${conventionName("factoryMethods", context.output.namingConvention)}`;
4063
+ case "single": return `./${baseName}`;
4064
+ }
4065
+ }
4066
+ function buildAllOfPayload(allOf, context, parents, imports) {
4067
+ const payloads = allOf.map((s) => buildPayload(s, context, parents, imports));
4068
+ return payloads.length > 0 ? `Object.assign({}, ${payloads.join(", ")})` : "{}";
4069
+ }
4070
+ function buildFirstOfPayload(schemas, context, parents, imports) {
4071
+ const first = schemas[0];
4072
+ return first ? buildPayload(first, context, parents, imports) : "{}";
4073
+ }
4074
+ function buildObjectPayload(schema, context, parents, imports) {
4075
+ const { includeOptionalProperty = false } = context.output.factoryMethods ?? {};
4076
+ const props = getProperties(schema);
4077
+ const requiredProps = schema.required ?? [];
4078
+ const entries = Object.entries(props);
4079
+ if (context.output.propertySortOrder === PropertySortOrder.ALPHABETICAL) entries.sort(([a], [b]) => a.localeCompare(b));
4080
+ const includeOptional = includeOptionalProperty;
4081
+ const lines = [];
4082
+ for (const [key, prop] of entries) {
4083
+ const isRequired = requiredProps.includes(key);
4084
+ const resolved = isReference$1(prop) ? getResolvedRef(prop, context).schema : prop;
4085
+ const isReadOnly = !!prop.readOnly || !!resolved.readOnly;
4086
+ const isWriteOnly = !!prop.writeOnly || !!resolved.writeOnly;
4087
+ if (!isRequired) {
4088
+ if (isReadOnly) continue;
4089
+ if (!isWriteOnly && !includeOptional) continue;
4090
+ }
4091
+ const payload = buildPayload(prop, context, parents, imports);
4092
+ const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
4093
+ lines.push(`${safeKey}: ${payload}`);
4094
+ }
4095
+ return `{\n ${lines.join(",\n ")}\n }`;
4096
+ }
4097
+ function buildArrayPayload(schema, context, parents, imports) {
4098
+ const { prefixItems, minItems } = getExtendedProps(schema);
4099
+ const items = getItems(schema);
4100
+ if (prefixItems && prefixItems.length > 0) return `[${prefixItems.map((item) => buildPayload(item, context, parents, imports)).join(", ")}]`;
4101
+ if (minItems && items) {
4102
+ const MAX_MIN_ITEMS = 50;
4103
+ if (minItems > MAX_MIN_ITEMS) logWarning(`Warning: minItems is ${minItems}, capping at ${MAX_MIN_ITEMS} to prevent massive payload.`);
4104
+ const count = Math.min(minItems, MAX_MIN_ITEMS);
4105
+ const itemPayload = buildPayload(items, context, parents, imports);
4106
+ return `[${Array.from({ length: count }).fill(itemPayload).join(", ")}]`;
4107
+ }
4108
+ return "[]";
4109
+ }
4110
+ function inferSchemaType(schema) {
4111
+ let type = schema.type;
4112
+ if (Array.isArray(type)) {
4113
+ const nonNull = type.filter((t) => t !== "null");
4114
+ type = nonNull.length > 0 ? nonNull[0] : "null";
4115
+ }
4116
+ if (!type && schema.items) return "array";
4117
+ if (!type && schema.enum) {
4118
+ const first = schema.enum[0];
4119
+ if (typeof first === "number") return "number";
4120
+ if (typeof first === "boolean") return "boolean";
4121
+ return "string";
4122
+ }
4123
+ return type;
4124
+ }
4125
+ function buildDefaultPayload(schema, context) {
4126
+ if (context.output.override.useDates && typeof schema.default === "string" && (schema.format === "date" || schema.format === "date-time")) return `new Date('${schema.default}')`;
4127
+ return formatValue(schema.default);
4128
+ }
4129
+ function buildPrimitivePayload(schema, schemaType, context) {
4130
+ if (schemaType === "null") return "null";
4131
+ const enumValues = schema.enum;
4132
+ if (schemaType === "boolean") return enumValues && enumValues.length > 0 ? String(enumValues[0]) : "false";
4133
+ if (schemaType === "number" || schemaType === "integer") return enumValues && enumValues.length > 0 ? String(enumValues[0]) : "0";
4134
+ if (schemaType === "string") {
4135
+ if (enumValues && enumValues.length > 0) {
4136
+ const first = enumValues[0];
4137
+ return typeof first === "string" ? JSON.stringify(first) : String(first);
4138
+ }
4139
+ if (schema.format === "date" || schema.format === "date-time") return context.output.override.useDates ? "new Date(0)" : `'${(/* @__PURE__ */ new Date(0)).toISOString()}'`;
4140
+ return "''";
4141
+ }
4142
+ return "undefined as unknown";
4143
+ }
4144
+ function formatValue(val) {
4145
+ if (val === null) return "null";
4146
+ if (typeof val === "string") return JSON.stringify(val);
4147
+ if (typeof val === "object") return JSON.stringify(val);
4148
+ return String(val);
4149
+ }
4150
+ //#endregion
3446
4151
  //#region src/generators/imports.ts
3447
4152
  function generateImports({ imports, namingConvention = NamingConvention.CAMEL_CASE, importExtension = "" }) {
3448
4153
  if (imports.length === 0) return "";
@@ -4133,7 +4838,7 @@ function generateParameterDefinition(parameters = {}, context, suffix) {
4133
4838
  * @param name interface name
4134
4839
  * @param schema
4135
4840
  */
4136
- function generateInterface({ name, schema, context }) {
4841
+ function generateInterface({ name, schema, context, genericParams }) {
4137
4842
  const scalar = getScalar({
4138
4843
  item: schema,
4139
4844
  name,
@@ -4141,6 +4846,7 @@ function generateInterface({ name, schema, context }) {
4141
4846
  });
4142
4847
  const isEmptyObject = scalar.value === "{}";
4143
4848
  const shouldUseTypeAlias = context.output.override.useTypeOverInterfaces ?? scalar.useTypeAlias;
4849
+ const genericSuffix = genericParams && genericParams.length > 0 ? `<${genericParams.join(", ")}>` : "";
4144
4850
  let model = "";
4145
4851
  model += jsDoc(schema);
4146
4852
  if (isEmptyObject) model += "// eslint-disable-next-line @typescript-eslint/no-empty-interface\n";
@@ -4148,12 +4854,12 @@ function generateInterface({ name, schema, context }) {
4148
4854
  const properties = schema.properties;
4149
4855
  if (properties && Object.values(properties).length > 0 && Object.values(properties).every((item) => "const" in item)) {
4150
4856
  const mappedScalarValue = scalar.value.replaceAll(";", ",").replaceAll("?:", ":");
4151
- model += `export const ${name}Value = ${mappedScalarValue} as const;\nexport type ${name} = typeof ${name}Value;\n`;
4857
+ model += `export const ${name}Value = ${mappedScalarValue} as const;\nexport type ${name}${genericSuffix} = typeof ${name}Value;\n`;
4152
4858
  } else {
4153
4859
  const blankInterfaceValue = scalar.value === "unknown" ? "{}" : scalar.value;
4154
- model += `export interface ${name} ${blankInterfaceValue}\n`;
4860
+ model += `export interface ${name}${genericSuffix} ${blankInterfaceValue}\n`;
4155
4861
  }
4156
- } else model += `export type ${name} = ${scalar.value};\n`;
4862
+ } else model += `export type ${name}${genericSuffix} = ${scalar.value};\n`;
4157
4863
  const externalModulesImportsOnly = scalar.imports.filter((importName) => importName.alias ? importName.alias !== name : importName.name !== name);
4158
4864
  return [...scalar.schemas, {
4159
4865
  name,
@@ -4187,6 +4893,17 @@ function generateSchemasDefinition(schemas = {}, context, suffix, filters) {
4187
4893
  const normalizedName = conventionName(schema.name, context.output.namingConvention);
4188
4894
  if (!seenNames.has(normalizedName)) {
4189
4895
  seenNames.add(normalizedName);
4896
+ if (context.output.factoryMethods && schema.schema) {
4897
+ const factoryData = generateFactory(schema.schema, schema.name, context);
4898
+ if (factoryData) if (context.output.factoryMethods.mode === "single") {
4899
+ schema.model += `\n${factoryData.model}`;
4900
+ for (const imp of factoryData.imports) if (!schema.imports.some((existing) => existing.name === imp.name)) schema.imports.push(imp);
4901
+ } else {
4902
+ schema.factory = factoryData.model;
4903
+ schema.factoryImports = factoryData.imports;
4904
+ schema.factoryMode = context.output.factoryMethods.mode;
4905
+ }
4906
+ }
4190
4907
  deduplicatedModels.push(schema);
4191
4908
  }
4192
4909
  }
@@ -4231,6 +4948,21 @@ function shouldCreateInterface(schema) {
4231
4948
  const isNullable = isArray(schema.type) && schema.type.includes("null");
4232
4949
  return (!schema.type || schema.type === "object") && !schema.allOf && !schema.oneOf && !schema.anyOf && isDereferenced(schema) && !schema.enum && !isNullable;
4233
4950
  }
4951
+ function collectGenericParams(schema) {
4952
+ const defs = schema.$defs;
4953
+ if (!defs || typeof defs !== "object") return [];
4954
+ const anchors = [];
4955
+ for (const defSchema of Object.values(defs)) {
4956
+ if (!defSchema || typeof defSchema !== "object") continue;
4957
+ const rec = defSchema;
4958
+ if (rec.$dynamicAnchor !== void 0 && rec.$ref === void 0) anchors.push(rec.$dynamicAnchor);
4959
+ }
4960
+ const uniqueNames = dynamicAnchorsToUniqueParamNames(anchors);
4961
+ return anchors.map((anchor) => ({
4962
+ anchorName: anchor,
4963
+ paramName: uniqueNames.get(anchor) ?? dynamicAnchorToParamName(anchor)
4964
+ }));
4965
+ }
4234
4966
  function generateSchemaDefinitions(schemaName, schema, context, suffix) {
4235
4967
  const sanitizedSchemaName = sanitize(`${pascal(schemaName)}${suffix}`, {
4236
4968
  underscore: "_",
@@ -4245,22 +4977,82 @@ function generateSchemaDefinitions(schemaName, schema, context, suffix) {
4245
4977
  imports: [],
4246
4978
  schema
4247
4979
  }];
4980
+ const alias = extractBoundAliasInfo(schema, context);
4981
+ if (alias) {
4982
+ const genericParams = alias.genericParams.map((paramName) => ({
4983
+ anchorName: paramName,
4984
+ paramName
4985
+ }));
4986
+ const genericSuffix = genericParams.length > 0 ? `<${genericParams.map((p) => p.paramName).join(", ")}>` : "";
4987
+ const typeArgsStr = alias.typeArgs.join(", ");
4988
+ const genericPart = `${alias.genericName}<${typeArgsStr}>`;
4989
+ const schemaType = schema.type;
4990
+ const nullable = Array.isArray(schemaType) && schemaType.includes("null") || schema.nullable === true ? " | null" : "";
4991
+ let model;
4992
+ const allImports = [{
4993
+ name: alias.genericName,
4994
+ schemaName: alias.genericName
4995
+ }, ...alias.imports];
4996
+ if (alias.extraSchemas && alias.extraSchemas.length > 0) {
4997
+ const aliasScopedContext = {
4998
+ ...context,
4999
+ dynamicScope: buildDynamicScope(schemaName, schema, context)
5000
+ };
5001
+ const subSchemas = [];
5002
+ model = `export type ${sanitizedSchemaName}${genericSuffix} = (${[genericPart, ...alias.extraSchemas.map((extraSchema) => {
5003
+ const resolved = resolveValue({
5004
+ schema: extraSchema,
5005
+ name: sanitizedSchemaName,
5006
+ context: aliasScopedContext
5007
+ });
5008
+ for (const imp of resolved.imports) {
5009
+ const impSchemaName = imp.schemaName ?? imp.name;
5010
+ if (!allImports.some((a) => a.name === imp.name && a.schemaName === impSchemaName)) allImports.push({
5011
+ name: imp.name,
5012
+ schemaName: impSchemaName
5013
+ });
5014
+ }
5015
+ for (const sub of resolved.schemas) if (sub.name !== sanitizedSchemaName) subSchemas.push(sub);
5016
+ return resolved.value;
5017
+ })].join(" & ")})${nullable};\n`;
5018
+ return [...subSchemas, {
5019
+ name: sanitizedSchemaName,
5020
+ model,
5021
+ imports: allImports,
5022
+ dependencies: allImports.map((i) => i.name),
5023
+ schema
5024
+ }];
5025
+ } else model = `export type ${sanitizedSchemaName}${genericSuffix} = ${genericPart}${nullable};\n`;
5026
+ return [{
5027
+ name: sanitizedSchemaName,
5028
+ model,
5029
+ imports: allImports,
5030
+ dependencies: allImports.map((i) => i.name),
5031
+ schema
5032
+ }];
5033
+ }
5034
+ const scopedContext = isBoolean(schema) ? context : {
5035
+ ...context,
5036
+ dynamicScope: buildDynamicScope(schemaName, schema, context)
5037
+ };
5038
+ const genericParams = collectGenericParams(schema);
4248
5039
  if (shouldCreateInterface(schema)) return generateInterface({
4249
5040
  name: sanitizedSchemaName,
4250
5041
  schema,
4251
- context
5042
+ context: scopedContext,
5043
+ genericParams: genericParams.length > 0 ? genericParams.map((p) => p.paramName) : void 0
4252
5044
  });
4253
5045
  const resolvedValue = resolveValue({
4254
5046
  schema,
4255
5047
  name: sanitizedSchemaName,
4256
- context
5048
+ context: scopedContext
4257
5049
  });
4258
5050
  let output = "";
4259
5051
  let imports = resolvedValue.imports;
4260
5052
  output += jsDoc(schema);
4261
5053
  if (resolvedValue.isEnum && !resolvedValue.isRef) output += getEnum(resolvedValue.value, sanitizedSchemaName, getEnumNames(resolvedValue.originalSchema), context.output.override.enumGenerationType, getEnumDescriptions(resolvedValue.originalSchema), context.output.override.namingConvention.enum);
4262
5054
  else if (sanitizedSchemaName === resolvedValue.value && resolvedValue.isRef) {
4263
- const { schema: referredSchema } = resolveRef(schema, context);
5055
+ const { schema: referredSchema } = resolveRef(schema, scopedContext);
4264
5056
  if (!shouldCreateInterface(referredSchema)) {
4265
5057
  const imp = resolvedValue.imports.find((imp) => imp.name === sanitizedSchemaName);
4266
5058
  if (imp) {
@@ -4281,7 +5073,8 @@ function generateSchemaDefinitions(schemaName, schema, context, suffix) {
4281
5073
  resolvedValue.dependencies.push(...schema.dependencies ?? []);
4282
5074
  return false;
4283
5075
  });
4284
- output += `export type ${sanitizedSchemaName} = ${resolvedValue.value};\n`;
5076
+ const genericSuffix = genericParams.length > 0 ? `<${genericParams.map((p) => p.paramName).join(", ")}>` : "";
5077
+ output += `export type ${sanitizedSchemaName}${genericSuffix} = ${resolvedValue.value};\n`;
4285
5078
  }
4286
5079
  return [...resolvedValue.schemas, {
4287
5080
  name: sanitizedSchemaName,
@@ -4593,33 +5386,54 @@ function getCanonicalMap(schemaGroups, schemaPath, namingConvention, fileExtensi
4593
5386
  canonicalNameMap
4594
5387
  };
4595
5388
  }
4596
- function normalizeCanonicalImportPaths(schemas, canonicalPathMap, canonicalNameMap, schemaPath, namingConvention, fileExtension, tsconfig) {
5389
+ function normalizeCanonicalImportPaths(schemas, canonicalPathMap, canonicalNameMap, schemaPath, namingConvention, fileExtension, tsconfig, factoryOutputDirectory) {
4597
5390
  const importExtension = getImportExtension(fileExtension, tsconfig);
4598
- for (const schema of schemas) schema.imports = schema.imports.map((imp) => {
4599
- const canonicalByName = canonicalNameMap.get(imp.name);
4600
- const resolvedImportKey = resolveImportKey(schemaPath, imp.importPath ?? `./${conventionName(imp.name, namingConvention)}`, fileExtension);
4601
- const canonicalByPath = canonicalPathMap.get(resolvedImportKey);
4602
- const canonical = canonicalByName ?? canonicalByPath;
4603
- if (!canonical?.importPath) return imp;
4604
- const relative = relativeSafe(schemaPath, canonical.importPath.replaceAll("\\", "/"));
4605
- const importPath = `${relative.endsWith(fileExtension) ? relative.slice(0, -fileExtension.length) : relative.replace(/\.ts$/, "")}${importExtension}`;
4606
- return {
4607
- ...imp,
4608
- importPath
4609
- };
4610
- });
5391
+ const factoryDir = factoryOutputDirectory ?? schemaPath;
5392
+ for (const schema of schemas) {
5393
+ schema.imports = schema.imports.map((imp) => {
5394
+ const canonicalByName = canonicalNameMap.get(imp.name);
5395
+ const resolvedImportKey = resolveImportKey(schemaPath, imp.importPath ?? `./${conventionName(imp.name, namingConvention)}`, fileExtension);
5396
+ const canonicalByPath = canonicalPathMap.get(resolvedImportKey);
5397
+ const canonical = canonicalByName ?? canonicalByPath;
5398
+ if (!canonical?.importPath) return imp;
5399
+ const relative = relativeSafe(schemaPath, canonical.importPath.replaceAll("\\", "/"));
5400
+ const importPath = `${relative.endsWith(fileExtension) ? relative.slice(0, -fileExtension.length) : relative.replace(/\.ts$/, "")}${importExtension}`;
5401
+ return {
5402
+ ...imp,
5403
+ importPath
5404
+ };
5405
+ });
5406
+ if (schema.factoryImports) schema.factoryImports = schema.factoryImports.map((imp) => {
5407
+ const canonicalByName = canonicalNameMap.get(imp.name);
5408
+ const resolvedImportKey = resolveImportKey(factoryDir, imp.importPath ?? `./${conventionName(imp.name, namingConvention)}`, fileExtension);
5409
+ const canonicalByPath = canonicalPathMap.get(resolvedImportKey);
5410
+ const canonical = canonicalByName ?? canonicalByPath;
5411
+ if (!canonical?.importPath) return imp;
5412
+ const relative = relativeSafe(factoryDir, canonical.importPath.replaceAll("\\", "/"));
5413
+ const importPath = `${relative.endsWith(fileExtension) ? relative.slice(0, -fileExtension.length) : relative.replace(/\.ts$/, "")}${importExtension}`;
5414
+ return {
5415
+ ...imp,
5416
+ importPath
5417
+ };
5418
+ });
5419
+ }
4611
5420
  }
4612
5421
  function mergeSchemaGroup(schemas) {
4613
5422
  const baseSchemaName = schemas[0].name;
4614
5423
  const baseSchema = schemas[0].schema;
4615
5424
  const mergedImports = [...new Map(schemas.flatMap((schema) => schema.imports).map((imp) => [JSON.stringify(imp), imp])).values()];
4616
5425
  const mergedDependencies = [...new Set(schemas.flatMap((schema) => schema.dependencies ?? []))];
5426
+ const mergedFactory = schemas.map((s) => s.factory).filter(Boolean).join("\n");
5427
+ const mergedFactoryImports = [...new Map(schemas.flatMap((schema) => schema.factoryImports ?? []).map((imp) => [JSON.stringify(imp), imp])).values()];
4617
5428
  return {
4618
5429
  name: baseSchemaName,
4619
5430
  schema: baseSchema,
4620
5431
  model: schemas.map((schema) => schema.model).join("\n"),
4621
5432
  imports: mergedImports,
4622
- dependencies: mergedDependencies
5433
+ dependencies: mergedDependencies,
5434
+ factory: mergedFactory || void 0,
5435
+ factoryImports: mergedFactoryImports,
5436
+ factoryMode: schemas[0].factoryMode
4623
5437
  };
4624
5438
  }
4625
5439
  function resolveImportKey(schemaPath, importPath, fileExtension) {
@@ -4661,10 +5475,39 @@ async function writeSchema({ path, schema, target, namingConvention, fileExtensi
4661
5475
  throw new Error(`Oups... 🍻. An Error occurred while writing schema ${name} => ${String(error)}`, { cause: error });
4662
5476
  }
4663
5477
  }
4664
- async function writeSchemas({ schemaPath, schemas, target, namingConvention, fileExtension, header, indexFiles, tsconfig }) {
5478
+ async function emitFactoryForSchema(schema, namingConvention, header, factoryDir, fileExtension, helpers) {
5479
+ if (schema.factory && schema.factoryMode) {
5480
+ const mode = schema.factoryMode;
5481
+ if (mode === "split") {
5482
+ const factoryName = `${conventionName(schema.name, namingConvention)}.factory`;
5483
+ helpers.separateFactoryNames.push(factoryName);
5484
+ const factoryFile = `${header}\n${generateImports({
5485
+ imports: schema.factoryImports ?? [],
5486
+ namingConvention
5487
+ })}\n\n${schema.factory}`;
5488
+ await writeGeneratedFile(getPath(factoryDir, factoryName, fileExtension), factoryFile);
5489
+ } else if (mode === "single-split") {
5490
+ helpers.isCombined.value = true;
5491
+ helpers.combinedFactoryContent.value += `${schema.factory}\n`;
5492
+ helpers.combinedFactoryImports.push(...schema.factoryImports ?? []);
5493
+ }
5494
+ }
5495
+ }
5496
+ async function writeSchemas({ schemaPath, schemas, target, namingConvention, fileExtension, header, indexFiles, tsconfig, factoryOutputDirectory }) {
4665
5497
  const schemaGroups = getSchemaGroups(schemaPath, schemas, namingConvention, fileExtension);
4666
5498
  const { canonicalPathMap, canonicalNameMap } = getCanonicalMap(schemaGroups, schemaPath, namingConvention, fileExtension);
4667
- normalizeCanonicalImportPaths(schemas, canonicalPathMap, canonicalNameMap, schemaPath, namingConvention, fileExtension, tsconfig);
5499
+ normalizeCanonicalImportPaths(schemas, canonicalPathMap, canonicalNameMap, schemaPath, namingConvention, fileExtension, tsconfig, factoryOutputDirectory);
5500
+ const factoryDir = factoryOutputDirectory ?? schemaPath;
5501
+ const combinedFactoryContent = { value: "" };
5502
+ const combinedFactoryImports = [];
5503
+ const isCombined = { value: false };
5504
+ const separateFactoryNames = [];
5505
+ const factoryHelpers = {
5506
+ separateFactoryNames,
5507
+ combinedFactoryContent,
5508
+ combinedFactoryImports,
5509
+ isCombined
5510
+ };
4668
5511
  for (const groupSchemas of Object.values(schemaGroups)) {
4669
5512
  if (groupSchemas.length === 1) {
4670
5513
  await writeSchema({
@@ -4676,17 +5519,29 @@ async function writeSchemas({ schemaPath, schemas, target, namingConvention, fil
4676
5519
  header,
4677
5520
  tsconfig
4678
5521
  });
5522
+ const singleSchema = groupSchemas[0];
5523
+ await emitFactoryForSchema(singleSchema, namingConvention, header, factoryDir, fileExtension, factoryHelpers);
4679
5524
  continue;
4680
5525
  }
5526
+ const mergedSchema = mergeSchemaGroup(groupSchemas);
4681
5527
  await writeSchema({
4682
5528
  path: schemaPath,
4683
- schema: mergeSchemaGroup(groupSchemas),
5529
+ schema: mergedSchema,
4684
5530
  target,
4685
5531
  namingConvention,
4686
5532
  fileExtension,
4687
5533
  header,
4688
5534
  tsconfig
4689
5535
  });
5536
+ await emitFactoryForSchema(mergedSchema, namingConvention, header, factoryDir, fileExtension, factoryHelpers);
5537
+ }
5538
+ if (isCombined.value) {
5539
+ const factoryFileName = conventionName("factoryMethods", namingConvention);
5540
+ const factoryFile = `${header}\n${generateImports({
5541
+ imports: combinedFactoryImports,
5542
+ namingConvention
5543
+ })}\n\n${combinedFactoryContent.value}`;
5544
+ await writeGeneratedFile(getPath(factoryDir, factoryFileName, fileExtension), factoryFile);
4690
5545
  }
4691
5546
  if (indexFiles) {
4692
5547
  const schemaFilePath = nodePath.join(schemaPath, `index.ts`);
@@ -4694,7 +5549,25 @@ async function writeSchemas({ schemaPath, schemas, target, namingConvention, fil
4694
5549
  const ext = getImportExtension(fileExtension, tsconfig);
4695
5550
  const conventionNamesSet = new Set(Object.values(schemaGroups).map((group) => conventionName(group[0].name, namingConvention)));
4696
5551
  try {
4697
- await writeGeneratedFile(schemaFilePath, `${header}\n${[...conventionNamesSet].map((schemaName) => `export * from './${schemaName}${ext}';`).toSorted((a, b) => a.localeCompare(b, "en", { numeric: true })).join("\n")}\n`);
5552
+ const currentExports = [...conventionNamesSet].map((schemaName) => `export * from './${schemaName}${ext}';`);
5553
+ if (factoryOutputDirectory && normalizeSafe(factoryOutputDirectory) !== normalizeSafe(schemaPath) && (isCombined.value || separateFactoryNames.length > 0)) {
5554
+ const factoryIndexFilePath = nodePath.join(factoryOutputDirectory, `index.ts`);
5555
+ await fs$1.ensureFile(factoryIndexFilePath);
5556
+ const factoryExports = [];
5557
+ if (isCombined.value) {
5558
+ const factoryFileName = conventionName("factoryMethods", namingConvention);
5559
+ factoryExports.push(`export * from './${factoryFileName}${ext}';`);
5560
+ }
5561
+ for (const fName of separateFactoryNames) factoryExports.push(`export * from './${fName}${ext}';`);
5562
+ await writeGeneratedFile(factoryIndexFilePath, `${header}\n${factoryExports.join("\n")}\n`);
5563
+ } else {
5564
+ if (isCombined.value) {
5565
+ const factoryFileName = conventionName("factoryMethods", namingConvention);
5566
+ currentExports.push(`export * from './${factoryFileName}${ext}';`);
5567
+ }
5568
+ for (const fName of separateFactoryNames) currentExports.push(`export * from './${fName}${ext}';`);
5569
+ }
5570
+ await writeGeneratedFile(schemaFilePath, `${header}\n${[...new Set(currentExports)].toSorted((a, b) => a.localeCompare(b, "en", { numeric: true })).join("\n")}\n`);
4698
5571
  } catch (error) {
4699
5572
  throw new Error(`Oups... 🍻. An Error occurred while writing schema index file ${schemaFilePath} => ${String(error)}`, { cause: error });
4700
5573
  }
@@ -4704,6 +5577,13 @@ async function writeSchemas({ schemaPath, schemas, target, namingConvention, fil
4704
5577
  //#region src/writers/generate-imports-for-builder.ts
4705
5578
  function generateImportsForBuilder(output, imports, relativeSchemasPath) {
4706
5579
  const isZodSchemaOutput = isObject(output.schemas) && output.schemas.type === "zod";
5580
+ const schemaFactoryImports = imports.filter((i) => i.schemaFactory);
5581
+ const schemaFactoryImportExtension = getImportExtension(output.fileExtension, output.tsconfig);
5582
+ const schemaFactoryDeps = schemaFactoryImports.length > 0 ? [{
5583
+ exports: uniqueBy(schemaFactoryImports, (entry) => `${entry.name}|${entry.alias ?? ""}`),
5584
+ dependency: joinSafe(relativeSchemasPath, `index.faker${schemaFactoryImportExtension}`)
5585
+ }] : [];
5586
+ imports = imports.filter((i) => !i.schemaFactory);
4707
5587
  let schemaImports;
4708
5588
  if (output.indexFiles) schemaImports = isZodSchemaOutput ? [{
4709
5589
  exports: imports.filter((i) => !i.importPath),
@@ -4730,7 +5610,11 @@ function generateImportsForBuilder(output, imports, relativeSchemasPath) {
4730
5610
  dependency: i.importPath
4731
5611
  };
4732
5612
  });
4733
- return [...schemaImports, ...otherImports];
5613
+ return [
5614
+ ...schemaImports,
5615
+ ...schemaFactoryDeps,
5616
+ ...otherImports
5617
+ ];
4734
5618
  }
4735
5619
  //#endregion
4736
5620
  //#region src/writers/mock-outputs.ts
@@ -5448,6 +6332,6 @@ async function writeTagsMode({ builder, output, projectName, header, needSchema,
5448
6332
  }))).flat();
5449
6333
  }
5450
6334
  //#endregion
5451
- export { BODY_TYPE_NAME, DefaultTag, EnumGeneration, ErrorWithTag, FormDataArrayHandling, GetterPropType, LogLevels, NAMED_COMPONENT_SECTIONS, NamingConvention, OutputClient, OutputHttpClient, OutputMockType, OutputMode, PropertySortOrder, RefComponentSuffix, SchemaType, SupportedFormatter, TEMPLATE_TAG_REGEX, URL_REGEX, VERBS_WITH_BODY, Verbs, addDependency, asyncReduce, buildAngularParamsFilterExpression, camel, collectReferencedComponents, combineSchemas, compareVersions, conventionName, count, createDebugger, createLogger, createSuccessMessage, createTypeAliasIfNeeded, dedupeUnionType, dynamicImport, escape, escapeRegExp, filterByContentType, filteredVerbs, fixCrossDirectoryImports, fixRegularSchemaImports, generalJSTypes, generalJSTypesWithArray, generateAxiosOptions, generateBodyMutatorConfig, generateBodyOptions, generateComponentDefinition, generateDependencyImports, generateFormDataAndUrlEncodedFunction, generateImports, generateModelInline, generateModelsInline, generateMutator, generateMutatorConfig, generateMutatorImports, generateMutatorRequestOptions, generateOptions, generateParameterDefinition, generateQueryParamsAxiosConfig, generateSchemasDefinition, generateTarget, generateTargetForTags, generateVerbImports, generateVerbOptions, generateVerbsOptions, getAngularFilteredParamsCallExpression, getAngularFilteredParamsExpression, getAngularFilteredParamsHelperBody, getArray, getBaseUrlRuntimeImports, getBodiesByContentType, getBody, getCombinedEnumValue, getDefaultContentType, getEnum, getEnumDescriptions, getEnumImplementation, getEnumNames, getEnumUnionFromSchema, getExtension, getFileInfo, getFormDataFieldFileType, getFullRoute, getImportExtension, getIsBodyVerb, getKey, getMockFileExtensionByTypeName, getNumberWord, getObject, getOperationId, getOrvalGeneratedTypes, getParameters, getParams, getParamsInPath, getPropertySafe, getProps, getQueryParams, getRefInfo, getResReqTypes, getResponse, getResponseTypeCategory, getRoute, getRouteAsArray, getScalar, getSuccessResponseType, getTypedResponse, getWarningCount, isBinaryContentType, isBoolean, isComponentRef, isDirectory, isFakerMock, isFunction, isModule, isMswMock, isNullish, isNumber, isNumeric, isObject, isReference, isSchema, isString, isStringLike, isSyntheticDefaultImportsAllow, isUrl, isVerb, isVerbose, jsDoc, jsStringEscape, kebab, keyValuePairsToJsDoc, log, logError, logVerbose, logWarning, makeRouteSafe, mergeDeep, mismatchArgsMessage, pascal, removeFilesAndEmptyFolders, resetWarnings, resolveDiscriminators, resolveExampleRefs, resolveInstalledVersion, resolveInstalledVersions, resolveObject, resolveRef, resolveValue, sanitize, setVerbose, snake, sortByPriority, splitSchemasByType, startMessage, stringify, toObjectString, path_exports as upath, upper, wrapRouteParameters, writeModelInline, writeModelsInline, writeSchema, writeSchemas, writeSingleMode, writeSplitMode, writeSplitTagsMode, writeTagsMode };
6335
+ export { BODY_TYPE_NAME, DefaultTag, EnumGeneration, ErrorWithTag, FormDataArrayHandling, GetterPropType, LogLevels, NAMED_COMPONENT_SECTIONS, NamingConvention, OutputClient, OutputHttpClient, OutputMockType, OutputMode, PropertySortOrder, RefComponentSuffix, SchemaType, SupportedFormatter, TEMPLATE_TAG_REGEX, URL_REGEX, VERBS_WITH_BODY, Verbs, addDependency, asyncReduce, buildAngularParamsFilterExpression, buildDynamicScope, camel, collectReferencedComponents, combineSchemas, compareVersions, conventionName, count, createDebugger, createLogger, createSuccessMessage, createTypeAliasIfNeeded, dedupeUnionType, dynamicAnchorToParamName, dynamicAnchorsToUniqueParamNames, dynamicImport, escape, escapeRegExp, extractBoundAliasInfo, filterByContentType, filteredVerbs, fixCrossDirectoryImports, fixRegularSchemaImports, generalJSTypes, generalJSTypesWithArray, generateAxiosOptions, generateBodyMutatorConfig, generateBodyOptions, generateComponentDefinition, generateDependencyImports, generateFactory, generateFormDataAndUrlEncodedFunction, generateImports, generateModelInline, generateModelsInline, generateMutator, generateMutatorConfig, generateMutatorImports, generateMutatorRequestOptions, generateOptions, generateParameterDefinition, generateQueryParamsAxiosConfig, generateSchemasDefinition, generateTarget, generateTargetForTags, generateVerbImports, generateVerbOptions, generateVerbsOptions, getAngularFilteredParamsCallExpression, getAngularFilteredParamsExpression, getAngularFilteredParamsHelperBody, getArray, getBaseUrlRuntimeImports, getBodiesByContentType, getBody, getCombinedEnumValue, getDefaultContentType, getDynamicAnchorName, getEnum, getEnumDescriptions, getEnumImplementation, getEnumNames, getEnumUnionFromSchema, getExtension, getFileInfo, getFormDataFieldFileType, getFullRoute, getImportExtension, getIsBodyVerb, getKey, getMockFileExtensionByTypeName, getNumberWord, getObject, getOperationId, getOrvalGeneratedTypes, getParameters, getParams, getParamsInPath, getPropertySafe, getProps, getQueryParams, getRefInfo, getResReqTypes, getResponse, getResponseTypeCategory, getRoute, getRouteAsArray, getScalar, getSuccessResponseType, getTypedResponse, getWarningCount, isBinaryContentType, isBinaryScalarSchema, isBoolean, isComponentRef, isDirectory, isDynamicReference, isFakerMock, isFunction, isModule, isMswMock, isNullish, isNumber, isNumeric, isObject, isReference, isSchema, isString, isStringLike, isSyntheticDefaultImportsAllow, isUrl, isVerb, isVerbose, jsDoc, jsStringEscape, kebab, keyValuePairsToJsDoc, log, logError, logVerbose, logWarning, makeRouteSafe, mergeDeep, mismatchArgsMessage, pascal, removeFilesAndEmptyFolders, resetWarnings, resolveDiscriminators, resolveDynamicRef, resolveExampleRefs, resolveInstalledVersion, resolveInstalledVersions, resolveObject, resolveRef, resolveValue, sanitize, setVerbose, snake, sortByPriority, splitSchemasByType, startMessage, stringify, toObjectString, path_exports as upath, upper, wrapRouteParameters, writeGeneratedFile, writeModelInline, writeModelsInline, writeSchema, writeSchemas, writeSingleMode, writeSplitMode, writeSplitTagsMode, writeTagsMode };
5452
6336
 
5453
6337
  //# sourceMappingURL=index.mjs.map