@orval/core 8.6.2 → 8.8.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
@@ -17,6 +17,11 @@ import { Parser } from "acorn";
17
17
  import { build } from "esbuild";
18
18
 
19
19
  //#region src/types.ts
20
+ const SupportedFormatter = {
21
+ PRETTIER: "prettier",
22
+ BIOME: "biome",
23
+ OXFMT: "oxfmt"
24
+ };
20
25
  const PropertySortOrder = {
21
26
  ALPHABETICAL: "Alphabetical",
22
27
  SPECIFICATION: "Specification"
@@ -281,37 +286,19 @@ function compareVersions(firstVersion, secondVersions, operator = ">=") {
281
286
 
282
287
  //#endregion
283
288
  //#region src/utils/content-type.ts
289
+ const binaryApplicationTypes = new Set(["application/octet-stream", "application/pdf"]);
284
290
  /**
285
- * Determine if a content type is binary (vs text-based).
291
+ * Determine if a content type is binary.
292
+ * Only known binary types return true. Unknown types default to false (non-binary)
293
+ * so that schema type information is preserved rather than being overridden with Blob.
286
294
  */
287
295
  function isBinaryContentType(contentType) {
288
- if (contentType === "application/octet-stream") return true;
289
- if (contentType.startsWith("image/")) return true;
290
- if (contentType.startsWith("audio/")) return true;
291
- if (contentType.startsWith("video/")) return true;
292
- if (contentType.startsWith("font/")) return true;
293
- if (contentType.startsWith("text/")) return false;
294
- if ([
295
- "+json",
296
- "-json",
297
- "+xml",
298
- "-xml",
299
- "+yaml",
300
- "-yaml",
301
- "+rss",
302
- "-rss",
303
- "+csv",
304
- "-csv"
305
- ].some((suffix) => contentType.includes(suffix))) return false;
306
- return !new Set([
307
- "application/json",
308
- "application/xml",
309
- "application/yaml",
310
- "application/x-www-form-urlencoded",
311
- "application/javascript",
312
- "application/ecmascript",
313
- "application/graphql"
314
- ]).has(contentType);
296
+ const baseType = contentType.split(";")[0].trim();
297
+ if (baseType.startsWith("image/")) return true;
298
+ if (baseType.startsWith("audio/")) return true;
299
+ if (baseType.startsWith("video/")) return true;
300
+ if (baseType.startsWith("font/")) return true;
301
+ return binaryApplicationTypes.has(baseType);
315
302
  }
316
303
  /**
317
304
  * Determine if a form-data field should be treated as a file (binary or text).
@@ -566,6 +553,17 @@ function getIsBodyVerb(verb) {
566
553
  //#endregion
567
554
  //#region src/utils/logger.ts
568
555
  const log = console.log;
556
+ let _warningCount = 0;
557
+ function logWarning(message) {
558
+ _warningCount++;
559
+ log(styleText("yellow", message));
560
+ }
561
+ function getWarningCount() {
562
+ return _warningCount;
563
+ }
564
+ function resetWarnings() {
565
+ _warningCount = 0;
566
+ }
569
567
  let _verbose = false;
570
568
  function setVerbose(v) {
571
569
  _verbose = v;
@@ -595,7 +593,7 @@ function logError(err, tag) {
595
593
  ].filter(Boolean).join(" ")));
596
594
  }
597
595
  function mismatchArgsMessage(mismatchArgs) {
598
- log(styleText("yellow", `${mismatchArgs.join(", ")} ${mismatchArgs.length === 1 ? "is" : "are"} not defined in your configuration!`));
596
+ logWarning(`${mismatchArgs.join(", ")} ${mismatchArgs.length === 1 ? "is" : "are"} not defined in your configuration!`);
599
597
  }
600
598
  function createSuccessMessage(backend) {
601
599
  log(`🎉 ${backend ? `${styleText("green", backend)} - ` : ""}Your OpenAPI spec has been converted into ready to use orval!`);
@@ -1389,7 +1387,8 @@ function resolveValue({ schema, name, context, formDataContext }) {
1389
1387
  }
1390
1388
  }).hasReadonlyProps;
1391
1389
  const isAnyOfNullable = schemaObject.anyOf?.some((anyOfItem) => !isReference(anyOfItem) && (anyOfItem.type === "null" || Array.isArray(anyOfItem.type) && anyOfItem.type.includes("null")));
1392
- const nullable = Array.isArray(schemaObject.type) && schemaObject.type.includes("null") || schemaObject.nullable === true || isAnyOfNullable ? " | null" : "";
1390
+ const schemaType = schemaObject.type;
1391
+ const nullable = Array.isArray(schemaType) && schemaType.includes("null") || schemaObject.nullable === true || isAnyOfNullable ? " | null" : "";
1393
1392
  return {
1394
1393
  value: resolvedImport.name + nullable,
1395
1394
  imports: [{
@@ -1595,6 +1594,8 @@ const getSchemaAnyOf = (s) => s.anyOf;
1595
1594
  const getSchemaItems = (s) => s.items;
1596
1595
  const getSchemaRequired = (s) => s.required;
1597
1596
  const getSchemaProperties = (s) => s.properties;
1597
+ const resolveSchemaRef = (schema, context) => resolveRef(schema, context);
1598
+ const resolveResponseOrRequestRef = (schema, context) => resolveRef(schema, context);
1598
1599
  const formDataContentTypes = new Set(["multipart/form-data"]);
1599
1600
  const formUrlEncodedContentTypes = new Set(["application/x-www-form-urlencoded"]);
1600
1601
  function getResReqContentTypes({ mediaType, propName, context, isFormData, contentType }) {
@@ -1618,7 +1619,7 @@ function getResReqContentTypes({ mediaType, propName, context, isFormData, conte
1618
1619
  function getResReqTypes(responsesOrRequests, name, context, defaultType = "unknown", uniqueKey = (item) => item.value) {
1619
1620
  return uniqueBy(responsesOrRequests.filter(([, res]) => Boolean(res)).map(([key, res]) => {
1620
1621
  if (isReference(res)) {
1621
- const { schema: bodySchema, imports: [{ name, schemaName }] } = resolveRef(res, context);
1622
+ const { schema: bodySchema, imports: [{ name, schemaName }] } = resolveResponseOrRequestRef(res, context);
1622
1623
  const firstEntry = Object.entries(bodySchema.content ?? {}).at(0);
1623
1624
  if (!firstEntry) return [{
1624
1625
  value: name,
@@ -1706,7 +1707,7 @@ function getResReqTypes(responsesOrRequests, name, context, defaultType = "unkno
1706
1707
  if (propName && arr.length > 1) propName = propName + pascal(getNumberWord(index + 1));
1707
1708
  let effectivePropName = propName;
1708
1709
  if (mediaType.schema && isReference(mediaType.schema)) {
1709
- const { imports } = resolveRef(mediaType.schema, context);
1710
+ const { imports } = resolveSchemaRef(mediaType.schema, context);
1710
1711
  if (imports[0]?.name) effectivePropName = imports[0].name;
1711
1712
  }
1712
1713
  const isFormData = formDataContentTypes.has(contentType);
@@ -1836,14 +1837,14 @@ function getDefaultContentType(contentTypes) {
1836
1837
  return contentTypes[0];
1837
1838
  }
1838
1839
  function getFormDataAdditionalImports({ schemaObject, context }) {
1839
- const { schema } = resolveRef(schemaObject, context);
1840
+ const { schema } = resolveSchemaRef(schemaObject, context);
1840
1841
  if (schema.type !== "object") return [];
1841
1842
  const combinedSchemas = getSchemaOneOf(schema) ?? getSchemaAnyOf(schema);
1842
1843
  if (!combinedSchemas) return [];
1843
- return combinedSchemas.map((subSchema) => resolveRef(subSchema, context).imports[0]).filter(Boolean);
1844
+ return combinedSchemas.map((subSchema) => resolveSchemaRef(subSchema, context).imports[0]).filter(Boolean);
1844
1845
  }
1845
1846
  function getSchemaFormDataAndUrlEncoded({ name, schemaObject, context, isRequestBodyOptional, isUrlEncoded, isRef, encoding }) {
1846
- const { schema, imports } = resolveRef(schemaObject, context);
1847
+ const { schema, imports } = resolveSchemaRef(schemaObject, context);
1847
1848
  const propName = camel(!isRef && isReference(schemaObject) ? imports[0].name : name);
1848
1849
  const additionalImports = [];
1849
1850
  const variableName = isUrlEncoded ? "formUrlEncoded" : "formData";
@@ -1853,7 +1854,7 @@ function getSchemaFormDataAndUrlEncoded({ name, schemaObject, context, isRequest
1853
1854
  if (combinedSchemas) {
1854
1855
  const shouldCast = !!getSchemaOneOf(schema) || !!getSchemaAnyOf(schema);
1855
1856
  const combinedSchemasFormData = combinedSchemas.map((subSchema) => {
1856
- const { schema: combinedSchema, imports } = resolveRef(subSchema, context);
1857
+ const { schema: combinedSchema, imports } = resolveSchemaRef(subSchema, context);
1857
1858
  let newPropName = propName;
1858
1859
  let newPropDefinition = "";
1859
1860
  if (shouldCast && imports[0]) {
@@ -1889,7 +1890,7 @@ function getSchemaFormDataAndUrlEncoded({ name, schemaObject, context, isRequest
1889
1890
  let valueStr = "value";
1890
1891
  const schemaItems = getSchemaItems(schema);
1891
1892
  if (schemaItems) {
1892
- const { schema: itemSchema } = resolveRef(schemaItems, context);
1893
+ const { schema: itemSchema } = resolveSchemaRef(schemaItems, context);
1893
1894
  if (itemSchema.type === "object" || itemSchema.type === "array") valueStr = "JSON.stringify(value)";
1894
1895
  else if (itemSchema.type === "number" || itemSchema.type === "integer" || itemSchema.type === "boolean") valueStr = "value.toString()";
1895
1896
  }
@@ -1902,7 +1903,7 @@ function resolveSchemaPropertiesToFormData({ schema, variableName, propName, con
1902
1903
  let formDataValues = "";
1903
1904
  const schemaProps = getSchemaProperties(schema) ?? {};
1904
1905
  for (const [key, value] of Object.entries(schemaProps)) {
1905
- const { schema: property } = resolveRef(value, context);
1906
+ const { schema: property } = resolveSchemaRef(value, context);
1906
1907
  if (property.readOnly) continue;
1907
1908
  let formDataValue = "";
1908
1909
  const partContentType = (depth === 0 ? encoding?.[key] : void 0)?.contentType;
@@ -1929,7 +1930,7 @@ function resolveSchemaPropertiesToFormData({ schema, variableName, propName, con
1929
1930
  let hasNonPrimitiveChild = false;
1930
1931
  const propertyItems = getSchemaItems(property);
1931
1932
  if (propertyItems) {
1932
- const { schema: itemSchema } = resolveRef(propertyItems, context);
1933
+ const { schema: itemSchema } = resolveSchemaRef(propertyItems, context);
1933
1934
  if (itemSchema.type === "object" || itemSchema.type === "array") if (context.output.override.formData.arrayHandling === FormDataArrayHandling.EXPLODE) {
1934
1935
  hasNonPrimitiveChild = true;
1935
1936
  const resolvedValue = resolveSchemaPropertiesToFormData({
@@ -1997,8 +1998,7 @@ function resolveSchemaPropertiesToFormData({ schema, variableName, propName, con
1997
1998
 
1998
1999
  //#endregion
1999
2000
  //#region src/getters/body.ts
2000
- function getBody({ requestBody, operationName, context, contentType }) {
2001
- const filteredBodyTypes = filterByContentType(getResReqTypes([[context.output.override.components.requestBodies.suffix, requestBody]], operationName, context), contentType);
2001
+ function buildBody(filteredBodyTypes, requestBody, operationName, context) {
2002
2002
  const imports = filteredBodyTypes.flatMap(({ imports }) => imports);
2003
2003
  const schemas = filteredBodyTypes.flatMap(({ schemas }) => schemas);
2004
2004
  const definition = filteredBodyTypes.map(({ value }) => value).join(" | ");
@@ -2036,6 +2036,40 @@ function getBody({ requestBody, operationName, context, contentType }) {
2036
2036
  }
2037
2037
  };
2038
2038
  }
2039
+ function getBody({ requestBody, operationName, context, contentType }) {
2040
+ return buildBody(filterByContentType(getResReqTypes([[context.output.override.components.requestBodies.suffix, requestBody]], operationName, context), contentType), requestBody, operationName, context);
2041
+ }
2042
+ /**
2043
+ * Returns per-content-type bodies when `splitByContentType` is enabled.
2044
+ * Each entry includes a `contentTypeSuffix` for generating distinct function names.
2045
+ */
2046
+ function getBodiesByContentType({ requestBody, operationName, context, contentType }) {
2047
+ const filteredBodyTypes = filterByContentType(getResReqTypes([[context.output.override.components.requestBodies.suffix, requestBody]], operationName, context, void 0, (item) => `${item.value}::${item.contentType}`), contentType);
2048
+ if (filteredBodyTypes.length <= 1) return [{
2049
+ ...buildBody(filteredBodyTypes, requestBody, operationName, context),
2050
+ contentTypeSuffix: ""
2051
+ }];
2052
+ return filteredBodyTypes.map((bodyType) => {
2053
+ const suffix = getContentTypeSuffix(bodyType.contentType);
2054
+ return {
2055
+ ...buildBody([bodyType], requestBody, operationName, context),
2056
+ contentTypeSuffix: suffix
2057
+ };
2058
+ });
2059
+ }
2060
+ const CONTENT_TYPE_SUFFIX_MAP = {
2061
+ "application/json": "Json",
2062
+ "multipart/form-data": "FormData",
2063
+ "application/x-www-form-urlencoded": "UrlEncoded",
2064
+ "text/plain": "Text",
2065
+ "application/xml": "Xml",
2066
+ "text/xml": "Xml",
2067
+ "application/octet-stream": "Blob"
2068
+ };
2069
+ function getContentTypeSuffix(contentType) {
2070
+ if (CONTENT_TYPE_SUFFIX_MAP[contentType]) return CONTENT_TYPE_SUFFIX_MAP[contentType];
2071
+ return (contentType.split("/")[1] ?? contentType).split(/[-+.]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
2072
+ }
2039
2073
 
2040
2074
  //#endregion
2041
2075
  //#region src/getters/imports.ts
@@ -2069,17 +2103,18 @@ function getKey(key) {
2069
2103
  //#region src/getters/object.ts
2070
2104
  /**
2071
2105
  * Extract enum values from propertyNames schema (OpenAPI 3.1)
2072
- * Returns undefined if propertyNames doesn't have an enum
2106
+ * Handles both `enum` and `const` (treated as a single-element enum)
2107
+ * Returns undefined if propertyNames has neither
2073
2108
  */
2074
2109
  function getPropertyNamesEnum(item) {
2075
- if ("propertyNames" in item && item.propertyNames && "enum" in item.propertyNames) {
2076
- const propertyNames = item.propertyNames;
2077
- if (Array.isArray(propertyNames.enum)) return propertyNames.enum.filter((val) => isString(val));
2078
- }
2110
+ if (!("propertyNames" in item) || !item.propertyNames) return;
2111
+ const propertyNames = item.propertyNames;
2112
+ if (Array.isArray(propertyNames.enum)) return propertyNames.enum.filter((val) => isString(val));
2113
+ if (isString(propertyNames.const)) return [propertyNames.const];
2079
2114
  }
2080
2115
  /**
2081
- * Generate index signature key type based on propertyNames enum
2082
- * Returns union type string like "'foo' | 'bar'" or 'string' if no enum
2116
+ * Generate index signature key type based on propertyNames enum or const
2117
+ * Returns union type string like "'foo' | 'bar'", "'x'", or 'string' if neither
2083
2118
  */
2084
2119
  function getIndexSignatureKey(item) {
2085
2120
  const enumValues = getPropertyNamesEnum(item);
@@ -2405,7 +2440,9 @@ function getScalar({ item, name, context, formDataContext }) {
2405
2440
  };
2406
2441
  }
2407
2442
  case "boolean": {
2408
- let value = "boolean" + nullable;
2443
+ let value = "boolean";
2444
+ if (enumItems && !(enumItems.includes(true) && enumItems.includes(false))) value = enumItems.map((enumItem) => `${enumItem}`).join(" | ");
2445
+ value += nullable;
2409
2446
  if (schemaConst !== void 0) value = schemaConst;
2410
2447
  return {
2411
2448
  value,
@@ -2739,12 +2776,21 @@ function resolveDiscriminators(schemas, context) {
2739
2776
  hasProperty: false,
2740
2777
  value: void 0
2741
2778
  };
2742
- const mergedEnumValues = [...((enumProperty.hasProperty && Array.isArray(enumProperty.value) ? enumProperty.value : void 0) ?? []).filter((value) => value !== mappingKey), mappingKey];
2779
+ const enumValues = enumProperty.hasProperty && Array.isArray(enumProperty.value) ? enumProperty.value : void 0;
2780
+ const propertyType = schemaProperty?.type ?? "string";
2781
+ let typedMappingKey = mappingKey;
2782
+ if (propertyType === "boolean") typedMappingKey = mappingKey === "true";
2783
+ else if (propertyType === "number" || propertyType === "integer") {
2784
+ const parsed = Number(mappingKey);
2785
+ if (!Number.isNaN(parsed)) typedMappingKey = parsed;
2786
+ }
2787
+ const mergedEnumValues = [...(enumValues ?? []).filter((value) => value !== typedMappingKey), typedMappingKey];
2743
2788
  const mergedProperty = {
2744
2789
  ...schemaProperty,
2745
- type: "string",
2790
+ type: propertyType,
2746
2791
  enum: mergedEnumValues
2747
2792
  };
2793
+ delete mergedProperty.const;
2748
2794
  subTypeSchema.properties = {
2749
2795
  ...subTypeSchema.properties,
2750
2796
  [propertyName]: mergedProperty
@@ -2777,8 +2823,10 @@ function getParameters({ parameters, context }) {
2777
2823
  header: []
2778
2824
  };
2779
2825
  for (const p of parameters) if (isReference(p)) {
2780
- const { schema: parameter, imports } = resolveRef(p, context);
2781
- if (parameter.in === "path" || parameter.in === "query" || parameter.in === "header") result[parameter.in].push({
2826
+ const { schema, imports } = resolveRef(p, context);
2827
+ const parameter = schema;
2828
+ const location = parameter.in;
2829
+ if (location === "path" || location === "query" || location === "header") result[location].push({
2782
2830
  parameter,
2783
2831
  imports
2784
2832
  });
@@ -3028,7 +3076,7 @@ function getResponse({ responses, operationName, context, contentType }) {
3028
3076
  success: success || (defaultType ?? "unknown"),
3029
3077
  errors: errors || (defaultType ?? "unknown")
3030
3078
  },
3031
- isBlob: success === "Blob",
3079
+ isBlob: groupedByStatus.success.some((t) => !!t.contentType && isBinaryContentType(t.contentType)),
3032
3080
  types: groupedByStatus,
3033
3081
  contentTypes,
3034
3082
  schemas,
@@ -3038,7 +3086,18 @@ function getResponse({ responses, operationName, context, contentType }) {
3038
3086
 
3039
3087
  //#endregion
3040
3088
  //#region src/getters/route.ts
3041
- const TEMPLATE_TAG_IN_PATH_REGEX = /\/([\w]+)(?:\$\{)/g;
3089
+ function isBaseUrlRuntime(baseUrl) {
3090
+ return isObject(baseUrl) && "runtime" in baseUrl && typeof baseUrl.runtime === "string";
3091
+ }
3092
+ /**
3093
+ * Wraps a runtime expression for generated URL template literals.
3094
+ * Pass the expression only (e.g. `process.env.API_BASE_URL`), not a `${...}` fragment.
3095
+ */
3096
+ function runtimeExpressionToUrlPrefix(expression) {
3097
+ const t = expression.trim();
3098
+ if (!t) return "";
3099
+ return "${" + t + "}";
3100
+ }
3042
3101
  const hasParam = (path) => /[^{]*{[\w*_-]*}.*/.test(path);
3043
3102
  const getRoutePath = (path) => {
3044
3103
  const matches = /([^{]*){?([\w*_-]*)}?(.*)/.exec(path);
@@ -3068,6 +3127,7 @@ function getFullRoute(route, servers, baseUrl) {
3068
3127
  const getBaseUrl = () => {
3069
3128
  if (!baseUrl) return "";
3070
3129
  if (isString(baseUrl)) return baseUrl;
3130
+ if (isBaseUrlRuntime(baseUrl)) return runtimeExpressionToUrlPrefix(baseUrl.runtime);
3071
3131
  if (baseUrl.getBaseUrlFromSpecification) {
3072
3132
  if (!servers) throw new Error("Orval is configured to use baseUrl from the specifications 'servers' field, but there exist no servers in the specification.");
3073
3133
  const server = servers.at(Math.min(baseUrl.index ?? 0, servers.length - 1));
@@ -3095,8 +3155,22 @@ function getFullRoute(route, servers, baseUrl) {
3095
3155
  }
3096
3156
  return fullRoute;
3097
3157
  }
3158
+ /**
3159
+ * Returns `GeneratorImport` entries for {@link BaseUrlRuntime.imports} when `baseUrl` is a runtime config.
3160
+ */
3161
+ function getBaseUrlRuntimeImports(baseUrl) {
3162
+ if (!baseUrl) return [];
3163
+ if (!isBaseUrlRuntime(baseUrl)) return [];
3164
+ return baseUrl.imports ?? [];
3165
+ }
3098
3166
  function getRouteAsArray(route) {
3099
- return route.replaceAll(TEMPLATE_TAG_IN_PATH_REGEX, "/$1/${").split("/").filter((i) => i !== "").map((i) => i.includes("${") ? i.replace(TEMPLATE_TAG_REGEX, "$1") : `'${i}'`).join(",").replace(",,", "");
3167
+ return route.split("/").filter((i) => i !== "").flatMap((segment) => {
3168
+ if (!segment.includes("${")) return [`'${segment}'`];
3169
+ return segment.split(/(\$\{.+?\})/g).filter(Boolean).map((part) => {
3170
+ const match = /^\$\{(.+?)\}$/.exec(part);
3171
+ return match ? match[1] : `'${part}'`;
3172
+ });
3173
+ }).join(",");
3100
3174
  }
3101
3175
 
3102
3176
  //#endregion
@@ -3435,7 +3509,7 @@ function removeComments(file) {
3435
3509
  const getAngularFilteredParamsExpression = (paramsExpression, requiredNullableParamKeys = [], preserveRequiredNullables = false) => {
3436
3510
  const filteredParamValueType = `string | number | boolean${preserveRequiredNullables ? " | null" : ""} | Array<string | number | boolean>`;
3437
3511
  const preserveNullableBranch = preserveRequiredNullables ? ` } else if (value === null && requiredNullableParamKeys.has(key)) {
3438
- filteredParams[key] = value;
3512
+ filteredParams[key] = null;
3439
3513
  ` : "";
3440
3514
  return `(() => {
3441
3515
  const requiredNullableParamKeys = new Set<string>(${JSON.stringify(requiredNullableParamKeys)});
@@ -3505,7 +3579,7 @@ function filterParams(
3505
3579
  value === null &&
3506
3580
  requiredNullableKeys.has(key)
3507
3581
  ) {
3508
- filteredParams[key] = value;
3582
+ filteredParams[key] = null;
3509
3583
  } else if (
3510
3584
  value != null &&
3511
3585
  (typeof value === 'string' ||
@@ -3855,43 +3929,15 @@ function generateSchemaDefinitions(schemaName, schema, context, suffix) {
3855
3929
 
3856
3930
  //#endregion
3857
3931
  //#region src/generators/verbs-options.ts
3858
- async function generateVerbOptions({ verb, output, operation, route, pathRoute, verbParameters = [], context }) {
3859
- const { responses, requestBody, parameters: operationParameters, tags: rawTags, deprecated: rawDeprecated, description: rawDescription, summary: rawSummary } = operation;
3860
- const tags = rawTags ?? [];
3861
- const deprecated = rawDeprecated;
3862
- const description = rawDescription;
3863
- const summary = rawSummary;
3864
- const operationId = getOperationId(operation, route, verb);
3865
- const overrideOperation = output.override.operations[operationId];
3866
- let overrideTag = {};
3867
- for (const [tag, options] of Object.entries(output.override.tags)) if (tags.includes(tag) && options) overrideTag = mergeDeep(overrideTag, options);
3868
- const override = mergeDeep(mergeDeep(output.override, overrideTag), overrideOperation ?? {});
3869
- const overrideOperationName = overrideOperation?.operationName ?? output.override.operationName;
3870
- const operationName = overrideOperationName ? overrideOperationName(operation, route, verb) : sanitize(camel(operationId), { es5keyword: true });
3932
+ async function buildVerbOption({ verb, output, operation, route, pathRoute, verbParameters = [], context, body, operationName, operationId, override, tags, deprecated, description, summary }) {
3871
3933
  const response = getResponse({
3872
- responses: responses ?? {},
3934
+ responses: operation.responses ?? {},
3873
3935
  operationName,
3874
3936
  context,
3875
3937
  contentType: override.contentType
3876
3938
  });
3877
- const body = requestBody ? getBody({
3878
- requestBody,
3879
- operationName,
3880
- context,
3881
- contentType: override.contentType
3882
- }) : {
3883
- originalSchema: {},
3884
- definition: "",
3885
- implementation: "",
3886
- imports: [],
3887
- schemas: [],
3888
- formData: "",
3889
- formUrlEncoded: "",
3890
- contentType: "",
3891
- isOptional: false
3892
- };
3893
3939
  const parameters = getParameters({
3894
- parameters: [...verbParameters, ...operationParameters ?? []],
3940
+ parameters: [...verbParameters, ...operation.parameters ?? []],
3895
3941
  context
3896
3942
  });
3897
3943
  const queryParams = getQueryParams({
@@ -3980,6 +4026,83 @@ async function generateVerbOptions({ verb, output, operation, route, pathRoute,
3980
4026
  const transformer = await dynamicImport(override.transformer, context.workspace);
3981
4027
  return transformer ? transformer(verbOption) : verbOption;
3982
4028
  }
4029
+ async function generateVerbOptions({ verb, output, operation, route, pathRoute, verbParameters = [], context }) {
4030
+ const { requestBody, tags: rawTags, deprecated: rawDeprecated, description: rawDescription, summary: rawSummary } = operation;
4031
+ const tags = rawTags ?? [];
4032
+ const deprecated = rawDeprecated;
4033
+ const description = rawDescription;
4034
+ const summary = rawSummary;
4035
+ const operationId = getOperationId(operation, route, verb);
4036
+ const overrideOperation = output.override.operations[operationId];
4037
+ let overrideTag = {};
4038
+ for (const [tag, options] of Object.entries(output.override.tags)) if (tags.includes(tag) && options) overrideTag = mergeDeep(overrideTag, options);
4039
+ const override = mergeDeep(mergeDeep(output.override, overrideTag), overrideOperation ?? {});
4040
+ const overrideOperationName = overrideOperation?.operationName ?? output.override.operationName;
4041
+ const operationName = overrideOperationName ? overrideOperationName(operation, route, verb) : sanitize(camel(operationId), { es5keyword: true });
4042
+ if (override.splitByContentType && requestBody) {
4043
+ const bodies = getBodiesByContentType({
4044
+ requestBody,
4045
+ operationName,
4046
+ context,
4047
+ contentType: override.contentType
4048
+ });
4049
+ const results = [];
4050
+ for (const bodyEntry of bodies) {
4051
+ const { contentTypeSuffix, ...body } = bodyEntry;
4052
+ const verbOption = await buildVerbOption({
4053
+ verb,
4054
+ output,
4055
+ operation,
4056
+ route,
4057
+ pathRoute,
4058
+ verbParameters,
4059
+ context,
4060
+ body,
4061
+ operationName: contentTypeSuffix ? `${operationName}With${contentTypeSuffix}` : operationName,
4062
+ operationId,
4063
+ override,
4064
+ tags,
4065
+ deprecated,
4066
+ description,
4067
+ summary
4068
+ });
4069
+ results.push(verbOption);
4070
+ }
4071
+ return results;
4072
+ }
4073
+ return [await buildVerbOption({
4074
+ verb,
4075
+ output,
4076
+ operation,
4077
+ route,
4078
+ pathRoute,
4079
+ verbParameters,
4080
+ context,
4081
+ body: requestBody ? getBody({
4082
+ requestBody,
4083
+ operationName,
4084
+ context,
4085
+ contentType: override.contentType
4086
+ }) : {
4087
+ originalSchema: {},
4088
+ definition: "",
4089
+ implementation: "",
4090
+ imports: [],
4091
+ schemas: [],
4092
+ formData: "",
4093
+ formUrlEncoded: "",
4094
+ contentType: "",
4095
+ isOptional: false
4096
+ },
4097
+ operationName,
4098
+ operationId,
4099
+ override,
4100
+ tags,
4101
+ deprecated,
4102
+ description,
4103
+ summary
4104
+ })];
4105
+ }
3983
4106
  function generateVerbsOptions({ verbs, input, output, route, pathRoute, context }) {
3984
4107
  return asyncReduce(_filteredVerbs(verbs, input.filters), async (acc, [verb, operation]) => {
3985
4108
  if (isVerb(verb)) {
@@ -3992,7 +4115,7 @@ function generateVerbsOptions({ verbs, input, output, route, pathRoute, context
3992
4115
  operation,
3993
4116
  context
3994
4117
  });
3995
- acc.push(verbOptions);
4118
+ acc.push(...verbOptions);
3996
4119
  }
3997
4120
  return acc;
3998
4121
  }, []);
@@ -4124,7 +4247,7 @@ function normalizeCanonicalImportPaths(schemas, canonicalPathMap, canonicalNameM
4124
4247
  const canonicalByPath = canonicalPathMap.get(resolvedImportKey);
4125
4248
  const canonical = canonicalByName ?? canonicalByPath;
4126
4249
  if (!canonical?.importPath) return imp;
4127
- const importPath = removeFileExtension(relativeSafe(schemaPath, canonical.importPath.replaceAll("\\", "/")), fileExtension);
4250
+ const importPath = removeTSExtension(relativeSafe(schemaPath, canonical.importPath.replaceAll("\\", "/")));
4128
4251
  return {
4129
4252
  ...imp,
4130
4253
  importPath
@@ -4147,8 +4270,8 @@ function mergeSchemaGroup(schemas) {
4147
4270
  function resolveImportKey(schemaPath, importPath, fileExtension) {
4148
4271
  return join(schemaPath, `${importPath}${fileExtension}`).toLowerCase().replaceAll("\\", "/");
4149
4272
  }
4150
- function removeFileExtension(path, fileExtension) {
4151
- return path.endsWith(fileExtension) ? path.slice(0, path.length - fileExtension.length) : path;
4273
+ function removeTSExtension(path) {
4274
+ return path.endsWith(".ts") ? path.slice(0, -3) : path;
4152
4275
  }
4153
4276
  function getSchema({ schema: { imports, model }, header, namingConvention = NamingConvention.CAMEL_CASE }) {
4154
4277
  let file = header;
@@ -4210,7 +4333,7 @@ async function writeSchemas({ schemaPath, schemas, target, namingConvention, fil
4210
4333
  });
4211
4334
  }
4212
4335
  if (indexFiles) {
4213
- const schemaFilePath = path.join(schemaPath, `index${fileExtension}`);
4336
+ const schemaFilePath = path.join(schemaPath, `index.ts`);
4214
4337
  await fs$1.ensureFile(schemaFilePath);
4215
4338
  const ext = fileExtension.endsWith(".ts") ? fileExtension.slice(0, -3) : fileExtension;
4216
4339
  const conventionNamesSet = new Set(Object.values(schemaGroups).map((group) => conventionName(group[0].name, namingConvention)));
@@ -4229,7 +4352,7 @@ function generateImportsForBuilder(output, imports, relativeSchemasPath) {
4229
4352
  let schemaImports;
4230
4353
  if (output.indexFiles) schemaImports = isZodSchemaOutput ? [{
4231
4354
  exports: imports.filter((i) => !i.importPath),
4232
- dependency: joinSafe(relativeSchemasPath, "index.zod")
4355
+ dependency: relativeSchemasPath
4233
4356
  }] : [{
4234
4357
  exports: imports.filter((i) => !i.importPath),
4235
4358
  dependency: relativeSchemasPath
@@ -4376,7 +4499,7 @@ interface TypedResponse<T> extends Response {
4376
4499
 
4377
4500
  //#endregion
4378
4501
  //#region src/writers/single-mode.ts
4379
- async function writeSingleMode({ builder, output, projectName, header, needSchema }) {
4502
+ async function writeSingleMode({ builder, output, projectName, header, needSchema, generateSchemasInline }) {
4380
4503
  try {
4381
4504
  const { path } = getFileInfo(output.target, {
4382
4505
  backupFilename: conventionName(builder.info.title ?? "filename", output.namingConvention),
@@ -4438,7 +4561,7 @@ async function writeSingleMode({ builder, output, projectName, header, needSchem
4438
4561
  data += getTypedResponse();
4439
4562
  data += "\n";
4440
4563
  }
4441
- if (!output.schemas && needSchema) data += generateModelsInline(builder.schemas);
4564
+ if (!output.schemas && needSchema) data += generateSchemasInline ? generateSchemasInline() : generateModelsInline(builder.schemas);
4442
4565
  data += `${implementation.trim()}\n`;
4443
4566
  if (output.mock) {
4444
4567
  data += "\n\n";
@@ -4454,7 +4577,7 @@ async function writeSingleMode({ builder, output, projectName, header, needSchem
4454
4577
 
4455
4578
  //#endregion
4456
4579
  //#region src/writers/split-mode.ts
4457
- async function writeSplitMode({ builder, output, projectName, header, needSchema }) {
4580
+ async function writeSplitMode({ builder, output, projectName, header, needSchema, generateSchemasInline }) {
4458
4581
  try {
4459
4582
  const { path: targetPath, filename, dirname, extension } = getFileInfo(output.target, {
4460
4583
  backupFilename: conventionName(builder.info.title ?? "filename", output.namingConvention),
@@ -4463,7 +4586,7 @@ async function writeSplitMode({ builder, output, projectName, header, needSchema
4463
4586
  const { imports, implementation, implementationMock, importsMock, mutators, clientMutators, formData, formUrlEncoded, paramsSerializer, fetchReviver } = generateTarget(builder, output);
4464
4587
  let implementationData = header;
4465
4588
  let mockData = header;
4466
- const relativeSchemasPath = output.schemas ? getRelativeImportPath(targetPath, getFileInfo(isString(output.schemas) ? output.schemas : output.schemas.path, { extension: output.fileExtension }).dirname) : "./" + filename + ".schemas";
4589
+ const relativeSchemasPath = output.schemas ? getRelativeImportPath(targetPath, getFileInfo(isString(output.schemas) ? output.schemas : output.schemas.path, { extension: output.fileExtension }).dirname) : "./" + filename + ".schemas" + extension.replace(/\.ts$/, "");
4467
4590
  const isAllowSyntheticDefaultImports = isSyntheticDefaultImportsAllow(output.tsconfig);
4468
4591
  const importsForBuilder = generateImportsForBuilder(output, imports, relativeSchemasPath);
4469
4592
  implementationData += builder.imports({
@@ -4489,7 +4612,7 @@ async function writeSplitMode({ builder, output, projectName, header, needSchema
4489
4612
  options: isFunction(output.mock) ? void 0 : output.mock
4490
4613
  });
4491
4614
  const schemasPath = output.schemas ? void 0 : path.join(dirname, filename + ".schemas" + extension);
4492
- if (schemasPath && needSchema) await writeGeneratedFile(schemasPath, header + generateModelsInline(builder.schemas));
4615
+ if (schemasPath && needSchema) await writeGeneratedFile(schemasPath, generateSchemasInline ? header + generateSchemasInline() : header + generateModelsInline(builder.schemas));
4493
4616
  if (mutators) implementationData += generateMutatorImports({
4494
4617
  mutators,
4495
4618
  implementation
@@ -4641,7 +4764,7 @@ function generateTargetForTags(builder, options) {
4641
4764
 
4642
4765
  //#endregion
4643
4766
  //#region src/writers/split-tags-mode.ts
4644
- async function writeSplitTagsMode({ builder, output, projectName, header, needSchema }) {
4767
+ async function writeSplitTagsMode({ builder, output, projectName, header, needSchema, generateSchemasInline }) {
4645
4768
  const { filename, dirname, extension } = getFileInfo(output.target, {
4646
4769
  backupFilename: conventionName(builder.info.title ?? "filename", output.namingConvention),
4647
4770
  extension: output.fileExtension
@@ -4658,8 +4781,25 @@ async function writeSplitTagsMode({ builder, output, projectName, header, needSc
4658
4781
  let implementationData = header;
4659
4782
  let mockData = header;
4660
4783
  const importerPath = path.join(dirname, tag, tag + extension);
4661
- const relativeSchemasPath = output.schemas ? getRelativeImportPath(importerPath, getFileInfo(isString(output.schemas) ? output.schemas : output.schemas.path, { extension: output.fileExtension }).dirname) : "../" + filename + ".schemas";
4662
- const importsForBuilder = generateImportsForBuilder(output, imports, relativeSchemasPath);
4784
+ const relativeSchemasPath = output.schemas ? getRelativeImportPath(importerPath, getFileInfo(isString(output.schemas) ? output.schemas : output.schemas.path, { extension: output.fileExtension }).dirname) : "../" + filename + ".schemas" + extension.replace(/\.ts$/, "");
4785
+ const tagNames = new Set(tagEntries.map(([t]) => t));
4786
+ const serviceSuffix = OutputClient.ANGULAR === output.client ? ".service" : "";
4787
+ const importsForBuilder = generateImportsForBuilder(output, imports.map((imp) => {
4788
+ if (!imp.importPath) return imp;
4789
+ if (!imp.importPath.startsWith(".")) return imp;
4790
+ const resolvedPath = path.resolve(dirname, imp.importPath);
4791
+ const targetBasename = path.basename(resolvedPath);
4792
+ let targetFile;
4793
+ if (tagNames.has(targetBasename)) {
4794
+ const tagFilename = targetBasename + serviceSuffix + extension;
4795
+ targetFile = path.join(resolvedPath, tagFilename);
4796
+ } else targetFile = resolvedPath + extension;
4797
+ const adjustedPath = getRelativeImportPath(importerPath, targetFile);
4798
+ return {
4799
+ ...imp,
4800
+ importPath: adjustedPath
4801
+ };
4802
+ }), relativeSchemasPath);
4663
4803
  implementationData += builder.imports({
4664
4804
  client: output.client,
4665
4805
  implementation,
@@ -4683,7 +4823,7 @@ async function writeSplitTagsMode({ builder, output, projectName, header, needSc
4683
4823
  options: isFunction(output.mock) ? void 0 : output.mock
4684
4824
  });
4685
4825
  const schemasPath = output.schemas ? void 0 : path.join(dirname, filename + ".schemas" + extension);
4686
- if (schemasPath && needSchema) await writeGeneratedFile(schemasPath, header + generateModelsInline(builder.schemas));
4826
+ if (schemasPath && needSchema) await writeGeneratedFile(schemasPath, generateSchemasInline ? header + generateSchemasInline() : header + generateModelsInline(builder.schemas));
4687
4827
  if (mutators) implementationData += generateMutatorImports({
4688
4828
  mutators,
4689
4829
  implementation,
@@ -4745,7 +4885,7 @@ async function writeSplitTagsMode({ builder, output, projectName, header, needSc
4745
4885
 
4746
4886
  //#endregion
4747
4887
  //#region src/writers/tags-mode.ts
4748
- async function writeTagsMode({ builder, output, projectName, header, needSchema }) {
4888
+ async function writeTagsMode({ builder, output, projectName, header, needSchema, generateSchemasInline }) {
4749
4889
  const { path: targetPath, filename, dirname, extension } = getFileInfo(output.target, {
4750
4890
  backupFilename: conventionName(builder.info.title ?? "filename", output.namingConvention),
4751
4891
  extension: output.fileExtension
@@ -4756,7 +4896,7 @@ async function writeTagsMode({ builder, output, projectName, header, needSchema
4756
4896
  try {
4757
4897
  const { imports, implementation, implementationMock, importsMock, mutators, clientMutators, formData, formUrlEncoded, fetchReviver, paramsSerializer } = target;
4758
4898
  let data = header;
4759
- const schemasPathRelative = output.schemas ? getRelativeImportPath(targetPath, getFileInfo(isString(output.schemas) ? output.schemas : output.schemas.path, { extension: output.fileExtension }).dirname) : "./" + filename + ".schemas";
4899
+ const schemasPathRelative = output.schemas ? getRelativeImportPath(targetPath, getFileInfo(isString(output.schemas) ? output.schemas : output.schemas.path, { extension: output.fileExtension }).dirname) : "./" + filename + ".schemas" + extension.replace(/\.ts$/, "");
4760
4900
  const normalizedImports = imports.filter((imp) => {
4761
4901
  const searchWords = [imp.alias, imp.name].filter((part) => Boolean(part?.length)).map((part) => escapeRegExp(part)).join("|");
4762
4902
  if (!searchWords) return false;
@@ -4793,7 +4933,7 @@ async function writeTagsMode({ builder, output, projectName, header, needSchema
4793
4933
  });
4794
4934
  }
4795
4935
  const schemasPath = output.schemas ? void 0 : path.join(dirname, filename + ".schemas" + extension);
4796
- if (schemasPath && needSchema) await writeGeneratedFile(schemasPath, header + generateModelsInline(builder.schemas));
4936
+ if (schemasPath && needSchema) await writeGeneratedFile(schemasPath, generateSchemasInline ? header + generateSchemasInline() : header + generateModelsInline(builder.schemas));
4797
4937
  if (mutators) data += generateMutatorImports({
4798
4938
  mutators,
4799
4939
  implementation
@@ -4827,5 +4967,5 @@ async function writeTagsMode({ builder, output, projectName, header, needSchema
4827
4967
  }
4828
4968
 
4829
4969
  //#endregion
4830
- export { BODY_TYPE_NAME, EnumGeneration, ErrorWithTag, FormDataArrayHandling, GetterPropType, LogLevels, NamingConvention, OutputClient, OutputHttpClient, OutputMockType, OutputMode, PropertySortOrder, RefComponentSuffix, SchemaType, TEMPLATE_TAG_REGEX, URL_REGEX, VERBS_WITH_BODY, Verbs, _filteredVerbs, addDependency, asyncReduce, camel, combineSchemas, compareVersions, conventionName, count, createDebugger, createLogger, createSuccessMessage, createTypeAliasIfNeeded, dedupeUnionType, dynamicImport, escape, escapeRegExp, filterByContentType, 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, getBody, getCombinedEnumValue, getDefaultContentType, getEnum, getEnumDescriptions, getEnumImplementation, getEnumNames, getEnumUnionFromSchema, getExtension, getFileInfo, getFormDataFieldFileType, getFullRoute, getIsBodyVerb, getKey, getMockFileExtensionByTypeName, getNumberWord, getObject, getOperationId, getOrvalGeneratedTypes, getParameters, getParams, getParamsInPath, getPropertySafe, getProps, getQueryParams, getRefInfo, getResReqTypes, getResponse, getResponseTypeCategory, getRoute, getRouteAsArray, getScalar, getSuccessResponseType, getTypedResponse, isBinaryContentType, isBoolean, isDirectory, isFunction, isModule, isNullish, isNumber, isNumeric, isObject, isReference, isSchema, isString, isStringLike, isSyntheticDefaultImportsAllow, isUrl, isVerb, isVerbose, jsDoc, jsStringEscape, kebab, keyValuePairsToJsDoc, log, logError, logVerbose, mergeDeep, mismatchArgsMessage, pascal, removeFilesAndEmptyFolders, resolveDiscriminators, resolveExampleRefs, resolveInstalledVersion, resolveInstalledVersions, resolveObject, resolveRef, resolveValue, sanitize, setVerbose, snake, sortByPriority, splitSchemasByType, startMessage, stringify, toObjectString, path_exports as upath, upper, writeModelInline, writeModelsInline, writeSchema, writeSchemas, writeSingleMode, writeSplitMode, writeSplitTagsMode, writeTagsMode };
4970
+ export { BODY_TYPE_NAME, EnumGeneration, ErrorWithTag, FormDataArrayHandling, GetterPropType, LogLevels, NamingConvention, OutputClient, OutputHttpClient, OutputMockType, OutputMode, PropertySortOrder, RefComponentSuffix, SchemaType, SupportedFormatter, TEMPLATE_TAG_REGEX, URL_REGEX, VERBS_WITH_BODY, Verbs, _filteredVerbs, addDependency, asyncReduce, camel, combineSchemas, compareVersions, conventionName, count, createDebugger, createLogger, createSuccessMessage, createTypeAliasIfNeeded, dedupeUnionType, dynamicImport, escape, escapeRegExp, filterByContentType, 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, 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, isDirectory, isFunction, isModule, isNullish, isNumber, isNumeric, isObject, isReference, isSchema, isString, isStringLike, isSyntheticDefaultImportsAllow, isUrl, isVerb, isVerbose, jsDoc, jsStringEscape, kebab, keyValuePairsToJsDoc, log, logError, logVerbose, logWarning, 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, writeModelInline, writeModelsInline, writeSchema, writeSchemas, writeSingleMode, writeSplitMode, writeSplitTagsMode, writeTagsMode };
4831
4971
  //# sourceMappingURL=index.mjs.map