@orval/core 8.5.3 → 8.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -6,6 +6,7 @@ import nodePath from "node:path";
6
6
  import { compare } from "compare-versions";
7
7
  import debug from "debug";
8
8
  import { pathToFileURL } from "node:url";
9
+ import { createJiti } from "jiti";
9
10
  import fs, { existsSync, readFileSync } from "node:fs";
10
11
  import { globby } from "globby";
11
12
  import readline from "node:readline";
@@ -245,10 +246,10 @@ function camel(s = "") {
245
246
  const camelString = decap(pascal(s), isStartWithUnderscore ? 1 : 0);
246
247
  return isStartWithUnderscore ? `_${camelString}` : camelString;
247
248
  }
248
- function snake(s) {
249
+ function snake(s = "") {
249
250
  return lower(s, "_", true);
250
251
  }
251
- function kebab(s) {
252
+ function kebab(s = "") {
252
253
  return lower(s, "-", true);
253
254
  }
254
255
  function upper(s, fillWith, isDeapostrophe) {
@@ -457,12 +458,26 @@ function keyValuePairsToJsDoc(keyValues) {
457
458
 
458
459
  //#endregion
459
460
  //#region src/utils/dynamic-import.ts
461
+ const TS_MODULE_EXTENSIONS = new Set([
462
+ ".ts",
463
+ ".mts",
464
+ ".cts",
465
+ ".tsx",
466
+ ".jsx"
467
+ ]);
460
468
  async function dynamicImport(toImport, from = process.cwd(), takeDefault = true) {
461
469
  if (!toImport) return toImport;
462
470
  try {
463
471
  if (isString(toImport)) {
464
- const fileUrl = pathToFileURL(nodePath.resolve(from, toImport));
465
- const data = nodePath.extname(fileUrl.href) === ".json" ? await import(fileUrl.href, { with: { type: "json" } }) : await import(fileUrl.href);
472
+ const filePath = nodePath.resolve(from, toImport);
473
+ const extension = nodePath.extname(filePath);
474
+ if (TS_MODULE_EXTENSIONS.has(extension)) {
475
+ const data = await createJiti(from, { interopDefault: true }).import(filePath);
476
+ if (takeDefault && (isObject(data) || isModule(data)) && data.default) return data.default;
477
+ return data;
478
+ }
479
+ const fileUrl = pathToFileURL(filePath);
480
+ const data = extension === ".json" ? await import(fileUrl.href, { with: { type: "json" } }) : await import(fileUrl.href);
466
481
  if (takeDefault && (isObject(data) || isModule(data)) && data.default) return data.default;
467
482
  return data;
468
483
  }
@@ -817,7 +832,7 @@ const sortByPriority = (arr) => arr.toSorted((a, b) => {
817
832
  * Handles strings, numbers, booleans, functions, arrays, and objects.
818
833
  *
819
834
  * @param data - The data to stringify. Can be a string, array, object, number, boolean, function, null, or undefined.
820
- * @returns A string representation of the data, or undefined if data is null or undefined.
835
+ * @returns A string representation of the data, `null` for null, or undefined if data is undefined.
821
836
  * @example
822
837
  * stringify('hello') // returns "'hello'"
823
838
  * stringify(42) // returns "42"
@@ -825,7 +840,8 @@ const sortByPriority = (arr) => arr.toSorted((a, b) => {
825
840
  * stringify({ a: 1, b: 'test' }) // returns "{ a: 1, b: 'test', }"
826
841
  */
827
842
  function stringify(data) {
828
- if (isNullish$1(data)) return;
843
+ if (data === void 0) return;
844
+ if (data === null) return "null";
829
845
  if (isString(data)) return `'${data.replaceAll("'", String.raw`\'`)}'`;
830
846
  if (isNumber(data) || isBoolean(data) || isFunction(data)) return String(data);
831
847
  if (Array.isArray(data)) return `[${data.map((item) => stringify(item)).join(", ")}]`;
@@ -933,6 +949,18 @@ function escape(str, char = "'") {
933
949
  return str?.replaceAll(char, `\\${char}`);
934
950
  }
935
951
  /**
952
+ * Escapes regular expression metacharacters in a string so it can be safely
953
+ * embedded inside a RegExp pattern.
954
+ *
955
+ * @param value - The raw string value to escape for regex usage.
956
+ * @returns The escaped string.
957
+ * @example
958
+ * escapeRegExp('foo$bar') // returns 'foo\\$bar'
959
+ */
960
+ function escapeRegExp(value) {
961
+ return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
962
+ }
963
+ /**
936
964
  * Escape all characters not included in SingleStringCharacters and
937
965
  * DoubleStringCharacters on
938
966
  * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4
@@ -998,6 +1026,38 @@ function isSyntheticDefaultImportsAllow(config) {
998
1026
 
999
1027
  //#endregion
1000
1028
  //#region src/getters/enum.ts
1029
+ /**
1030
+ * Map of special characters to semantic word replacements.
1031
+ *
1032
+ * Applied before naming convention transforms (PascalCase, camelCase, …) so
1033
+ * that characters which would otherwise be stripped still contribute a unique
1034
+ * segment to the generated key. Without this, values like "created_at" and
1035
+ * "-created_at" both PascalCase to "CreatedAt", silently overwriting one
1036
+ * another in the generated const/enum object.
1037
+ *
1038
+ * Only characters that appear as leading/trailing modifiers in real-world
1039
+ * OpenAPI enums are mapped — the list is intentionally conservative to avoid
1040
+ * changing output for schemas that don't hit collisions.
1041
+ */
1042
+ const ENUM_SPECIAL_CHARACTER_MAP = {
1043
+ "-": "minus",
1044
+ "+": "plus"
1045
+ };
1046
+ /**
1047
+ * Replace special characters with semantic words (plus an underscore separator)
1048
+ * so that naming convention transforms (PascalCase, etc.) produce unique keys.
1049
+ *
1050
+ * The trailing underscore acts as a word boundary so that PascalCase treats the
1051
+ * replacement as a separate word: "-created_at" → "minus_created_at" → "MinusCreatedAt".
1052
+ */
1053
+ function replaceSpecialCharacters(key) {
1054
+ let result = "";
1055
+ for (const char of key) {
1056
+ const replacement = ENUM_SPECIAL_CHARACTER_MAP[char];
1057
+ result += replacement ? replacement + "_" : char;
1058
+ }
1059
+ return result;
1060
+ }
1001
1061
  function getEnumNames(schemaObject) {
1002
1062
  const names = schemaObject?.["x-enumNames"] ?? schemaObject?.["x-enumnames"] ?? schemaObject?.["x-enum-varnames"];
1003
1063
  if (!names) return;
@@ -1025,9 +1085,33 @@ const getTypeConstEnum = (value, enumName, names, descriptions, enumNamingConven
1025
1085
  enumValue += `export const ${enumName} = {\n${implementation}} as const;\n`;
1026
1086
  return enumValue;
1027
1087
  };
1088
+ /**
1089
+ * Derive the object/enum key for a single enum value.
1090
+ *
1091
+ * Handles numeric prefixes, sanitization, and optional naming convention
1092
+ * transforms. When `disambiguate` is true, special characters (-/+) are
1093
+ * replaced with semantic words before the convention transform to prevent
1094
+ * key collisions.
1095
+ */
1096
+ function deriveEnumKey(val, enumNamingConvention, disambiguate = false) {
1097
+ let key = val.startsWith("'") ? val.slice(1, -1) : val;
1098
+ if (isNumeric(key)) key = toNumberKey(key);
1099
+ if (key.length > 1) key = sanitize(key, {
1100
+ whitespace: "_",
1101
+ underscore: true,
1102
+ dash: true,
1103
+ special: true
1104
+ });
1105
+ if (enumNamingConvention) {
1106
+ if (disambiguate) key = replaceSpecialCharacters(key);
1107
+ key = conventionName(key, enumNamingConvention);
1108
+ }
1109
+ return key;
1110
+ }
1028
1111
  function getEnumImplementation(value, names, descriptions, enumNamingConvention) {
1029
1112
  if (value === "") return "";
1030
1113
  const uniqueValues = [...new Set(value.split(" | "))];
1114
+ const disambiguate = !!enumNamingConvention && new Set(uniqueValues.map((v) => deriveEnumKey(v, enumNamingConvention))).size < uniqueValues.length;
1031
1115
  let result = "";
1032
1116
  for (const [index, val] of uniqueValues.entries()) {
1033
1117
  const name = names?.[index];
@@ -1037,15 +1121,7 @@ function getEnumImplementation(value, names, descriptions, enumNamingConvention)
1037
1121
  result += comment + ` ${keyword.isIdentifierNameES5(name) ? name : `'${name}'`}: ${val},\n`;
1038
1122
  continue;
1039
1123
  }
1040
- let key = val.startsWith("'") ? val.slice(1, -1) : val;
1041
- if (isNumeric(key)) key = toNumberKey(key);
1042
- if (key.length > 1) key = sanitize(key, {
1043
- whitespace: "_",
1044
- underscore: true,
1045
- dash: true,
1046
- special: true
1047
- });
1048
- if (enumNamingConvention) key = conventionName(key, enumNamingConvention);
1124
+ const key = deriveEnumKey(val, enumNamingConvention, disambiguate);
1049
1125
  result += comment + ` ${keyword.isIdentifierNameES5(key) ? key : `'${key}'`}: ${val},\n`;
1050
1126
  }
1051
1127
  return result;
@@ -1056,6 +1132,7 @@ const getNativeEnum = (value, enumName, names, enumNamingConvention) => {
1056
1132
  const getNativeEnumItems = (value, names, enumNamingConvention) => {
1057
1133
  if (value === "") return "";
1058
1134
  const uniqueValues = [...new Set(value.split(" | "))];
1135
+ const disambiguate = !!enumNamingConvention && new Set(uniqueValues.map((v) => deriveEnumKey(v, enumNamingConvention))).size < uniqueValues.length;
1059
1136
  let result = "";
1060
1137
  for (const [index, val] of uniqueValues.entries()) {
1061
1138
  const name = names?.[index];
@@ -1063,15 +1140,7 @@ const getNativeEnumItems = (value, names, enumNamingConvention) => {
1063
1140
  result += ` ${keyword.isIdentifierNameES5(name) ? name : `'${name}'`}= ${val},\n`;
1064
1141
  continue;
1065
1142
  }
1066
- let key = val.startsWith("'") ? val.slice(1, -1) : val;
1067
- if (isNumeric(key)) key = toNumberKey(key);
1068
- if (key.length > 1) key = sanitize(key, {
1069
- whitespace: "_",
1070
- underscore: true,
1071
- dash: true,
1072
- special: true
1073
- });
1074
- if (enumNamingConvention) key = conventionName(key, enumNamingConvention);
1143
+ const key = deriveEnumKey(val, enumNamingConvention, disambiguate);
1075
1144
  result += ` ${keyword.isIdentifierNameES5(key) ? key : `'${key}'`}= ${val},\n`;
1076
1145
  }
1077
1146
  return result;
@@ -1178,7 +1247,7 @@ function getRefInfo($ref, context) {
1178
1247
  return firstLevel[paths[1]]?.suffix ?? "";
1179
1248
  };
1180
1249
  const suffix = getOverrideSuffix(context.output.override, refPaths);
1181
- const originalName = ref ? refPaths[refPaths.length - 1] : getSchemaFileName(pathname);
1250
+ const originalName = ref ? refPaths.at(-1) ?? "" : getSchemaFileName(pathname);
1182
1251
  if (!pathname) return {
1183
1252
  name: sanitize(pascal(originalName) + suffix, {
1184
1253
  es5keyword: true,
@@ -1265,10 +1334,9 @@ function getSchema$1(schema, context) {
1265
1334
  if (!schema.$ref) throw new Error(`${REF_NOT_FOUND_PREFIX}: missing $ref`);
1266
1335
  const refInfo = getRefInfo(schema.$ref, context);
1267
1336
  const { refPaths } = refInfo;
1268
- let schemaByRefPaths = Array.isArray(refPaths) ? prop(context.spec, ...refPaths) : void 0;
1269
- schemaByRefPaths ??= context.spec;
1270
- if (isReference(schemaByRefPaths)) return getSchema$1(schemaByRefPaths, context);
1271
- let currentSchema = schemaByRefPaths || context.spec;
1337
+ const schemaByRefPaths = Array.isArray(refPaths) ? prop(context.spec, ...refPaths) : void 0;
1338
+ if (isObject(schemaByRefPaths) && isReference(schemaByRefPaths)) return getSchema$1(schemaByRefPaths, context);
1339
+ let currentSchema = schemaByRefPaths;
1272
1340
  if (isObject(currentSchema) && "nullable" in schema) {
1273
1341
  const nullable = schema.nullable;
1274
1342
  currentSchema = {
@@ -1505,7 +1573,7 @@ function getArray({ schema, name, context, formDataContext }) {
1505
1573
  example: schemaExample,
1506
1574
  examples: resolveExampleRefs(schemaExamples, context)
1507
1575
  };
1508
- } else if (compareVersions(context.spec.openapi, "3.1", ">=")) return {
1576
+ } else if (compareVersions(context.spec.openapi ?? "3.0.0", "3.1", ">=")) return {
1509
1577
  value: "unknown[]",
1510
1578
  imports: [],
1511
1579
  schemas: [],
@@ -1563,11 +1631,12 @@ function getResReqTypes(responsesOrRequests, name, context, defaultType = "unkno
1563
1631
  isEnum: false,
1564
1632
  isRef: true,
1565
1633
  hasReadonlyProps: false,
1634
+ dependencies: [name],
1566
1635
  originalSchema: void 0,
1567
1636
  example: void 0,
1568
1637
  examples: void 0,
1569
1638
  key,
1570
- contentType: void 0
1639
+ contentType: ""
1571
1640
  }];
1572
1641
  const [contentType, mediaType] = firstEntry;
1573
1642
  const isFormData = formDataContentTypes.has(contentType);
@@ -1583,6 +1652,7 @@ function getResReqTypes(responsesOrRequests, name, context, defaultType = "unkno
1583
1652
  isEnum: false,
1584
1653
  isRef: true,
1585
1654
  hasReadonlyProps: false,
1655
+ dependencies: [name],
1586
1656
  originalSchema: mediaType.schema,
1587
1657
  example: mediaType.example,
1588
1658
  examples: resolveExampleRefs(mediaType.examples, context),
@@ -1620,6 +1690,7 @@ function getResReqTypes(responsesOrRequests, name, context, defaultType = "unkno
1620
1690
  type: "unknown",
1621
1691
  isEnum: false,
1622
1692
  hasReadonlyProps: false,
1693
+ dependencies: [name],
1623
1694
  formData,
1624
1695
  formUrlEncoded,
1625
1696
  isRef: true,
@@ -1664,6 +1735,7 @@ function getResReqTypes(responsesOrRequests, name, context, defaultType = "unkno
1664
1735
  if (!isFormData && !isFormUrlEncoded || !effectivePropName || !mediaType.schema) return {
1665
1736
  ...resolvedValue,
1666
1737
  imports: resolvedValue.imports,
1738
+ dependencies: resolvedValue.dependencies,
1667
1739
  contentType,
1668
1740
  example: mediaType.example,
1669
1741
  examples: resolveExampleRefs(mediaType.examples, context)
@@ -1718,6 +1790,7 @@ function getResReqTypes(responsesOrRequests, name, context, defaultType = "unkno
1718
1790
  schemas: [],
1719
1791
  type: defaultType,
1720
1792
  isEnum: false,
1793
+ dependencies: [],
1721
1794
  key,
1722
1795
  isRef: false,
1723
1796
  hasReadonlyProps: false,
@@ -1929,7 +2002,7 @@ function getBody({ requestBody, operationName, context, contentType }) {
1929
2002
  const imports = filteredBodyTypes.flatMap(({ imports }) => imports);
1930
2003
  const schemas = filteredBodyTypes.flatMap(({ schemas }) => schemas);
1931
2004
  const definition = filteredBodyTypes.map(({ value }) => value).join(" | ");
1932
- const nonReadonlyDefinition = filteredBodyTypes.some((x) => x.hasReadonlyProps) && definition ? `NonReadonly<${definition}>` : definition;
2005
+ const nonReadonlyDefinition = filteredBodyTypes.some((x) => x.hasReadonlyProps) && definition && context.output.override.preserveReadonlyRequestBodies !== "preserve" ? `NonReadonly<${definition}>` : definition;
1933
2006
  let implementation = generalJSTypesWithArray.includes(definition.toLowerCase()) || filteredBodyTypes.length > 1 ? camel(operationName) + context.output.override.components.requestBodies.suffix : camel(definition);
1934
2007
  let isOptional = false;
1935
2008
  if (implementation) {
@@ -2039,17 +2112,22 @@ function getObject({ item, name, context, nullable, formDataContext }) {
2039
2112
  examples: resolveExampleRefs(item.examples, context)
2040
2113
  };
2041
2114
  }
2042
- if (item.allOf || item.oneOf || item.anyOf) return combineSchemas({
2043
- schema: item,
2115
+ const schemaItem = item;
2116
+ const itemAllOf = schemaItem.allOf;
2117
+ const itemOneOf = schemaItem.oneOf;
2118
+ const itemAnyOf = schemaItem.anyOf;
2119
+ const itemType = schemaItem.type;
2120
+ if (itemAllOf || itemOneOf || itemAnyOf) return combineSchemas({
2121
+ schema: schemaItem,
2044
2122
  name,
2045
- separator: item.allOf ? "allOf" : item.oneOf ? "oneOf" : "anyOf",
2123
+ separator: itemAllOf ? "allOf" : itemOneOf ? "oneOf" : "anyOf",
2046
2124
  context,
2047
2125
  nullable,
2048
2126
  formDataContext
2049
2127
  });
2050
- if (Array.isArray(item.type)) {
2051
- const typeArray = item.type;
2052
- const baseItem = item;
2128
+ if (Array.isArray(itemType)) {
2129
+ const typeArray = itemType;
2130
+ const baseItem = schemaItem;
2053
2131
  return combineSchemas({
2054
2132
  schema: { anyOf: typeArray.map((type) => ({
2055
2133
  ...baseItem,
@@ -2061,7 +2139,7 @@ function getObject({ item, name, context, nullable, formDataContext }) {
2061
2139
  nullable
2062
2140
  });
2063
2141
  }
2064
- const itemProperties = item.properties;
2142
+ const itemProperties = schemaItem.properties;
2065
2143
  if (itemProperties && Object.entries(itemProperties).length > 0) {
2066
2144
  const entries = Object.entries(itemProperties);
2067
2145
  if (context.output.propertySortOrder === PropertySortOrder.ALPHABETICAL) entries.sort((a, b) => {
@@ -2074,14 +2152,13 @@ function getObject({ item, name, context, nullable, formDataContext }) {
2074
2152
  isEnum: false,
2075
2153
  type: "object",
2076
2154
  isRef: false,
2077
- schema: {},
2078
2155
  hasReadonlyProps: false,
2079
2156
  useTypeAlias: false,
2080
2157
  dependencies: [],
2081
- example: item.example,
2082
- examples: resolveExampleRefs(item.examples, context)
2158
+ example: schemaItem.example,
2159
+ examples: resolveExampleRefs(schemaItem.examples, context)
2083
2160
  };
2084
- const itemRequired = item.required;
2161
+ const itemRequired = schemaItem.required;
2085
2162
  for (const [index, [key, schema]] of entries.entries()) {
2086
2163
  const isRequired = (Array.isArray(itemRequired) ? itemRequired : []).includes(key);
2087
2164
  let propName = "";
@@ -2101,10 +2178,11 @@ function getObject({ item, name, context, nullable, formDataContext }) {
2101
2178
  context,
2102
2179
  formDataContext: propertyFormDataContext
2103
2180
  });
2104
- const isReadOnly = item.readOnly ?? schema.readOnly;
2181
+ const isReadOnly = Boolean(schemaItem.readOnly) || Boolean(schema.readOnly);
2105
2182
  if (!index) acc.value += "{";
2106
2183
  const doc = jsDoc(schema, true, context);
2107
- if (isReadOnly ?? false) acc.hasReadonlyProps = true;
2184
+ const propertyDoc = doc ? `${doc.trimEnd().split("\n").map((line) => ` ${line}`).join("\n")}\n` : "";
2185
+ if (isReadOnly) acc.hasReadonlyProps = true;
2108
2186
  const constValue = "const" in schema ? schema.const : void 0;
2109
2187
  const hasConst = constValue !== void 0;
2110
2188
  let constLiteral;
@@ -2112,6 +2190,7 @@ function getObject({ item, name, context, nullable, formDataContext }) {
2112
2190
  else if (isString(constValue)) constLiteral = `'${escape(constValue)}'`;
2113
2191
  else constLiteral = JSON.stringify(constValue);
2114
2192
  const needsValueImport = hasConst && (resolvedValue.isEnum || resolvedValue.type === "enum");
2193
+ const usedResolvedValue = !hasConst || needsValueImport;
2115
2194
  const aliasedImports = needsValueImport ? resolvedValue.imports.map((imp) => ({
2116
2195
  ...imp,
2117
2196
  isConstant: true
@@ -2128,19 +2207,21 @@ function getObject({ item, name, context, nullable, formDataContext }) {
2128
2207
  });
2129
2208
  const propValue = needsValueImport ? alias : constLiteral ?? alias;
2130
2209
  const finalPropValue = isRequired ? propValue : context.output.override.useNullForOptional === true ? `${propValue} | null` : propValue;
2131
- acc.value += `\n ${doc ? `${doc} ` : ""}${isReadOnly && !context.output.override.suppressReadonlyModifier ? "readonly " : ""}${getKey(key)}${isRequired ? "" : "?"}: ${finalPropValue};`;
2132
- acc.schemas.push(...resolvedValue.schemas);
2133
- acc.dependencies.push(...resolvedValue.dependencies);
2210
+ acc.value += `\n${propertyDoc}${isReadOnly && !context.output.override.suppressReadonlyModifier ? " readonly " : " "}${getKey(key)}${isRequired ? "" : "?"}: ${finalPropValue};`;
2211
+ if (usedResolvedValue) {
2212
+ acc.schemas.push(...resolvedValue.schemas);
2213
+ acc.dependencies.push(...resolvedValue.dependencies);
2214
+ }
2134
2215
  if (entries.length - 1 === index) {
2135
- const additionalProps = item.additionalProperties;
2136
- if (additionalProps) if (isBoolean(additionalProps)) {
2137
- const recordType = getPropertyNamesRecordType(item, "unknown");
2216
+ const additionalProps = schemaItem.additionalProperties;
2217
+ if (additionalProps) if (additionalProps === true) {
2218
+ const recordType = getPropertyNamesRecordType(schemaItem, "unknown");
2138
2219
  if (recordType) {
2139
2220
  acc.value += "\n}";
2140
2221
  acc.value += ` & ${recordType}`;
2141
2222
  acc.useTypeAlias = true;
2142
2223
  } else {
2143
- const keyType = getIndexSignatureKey(item);
2224
+ const keyType = getIndexSignatureKey(schemaItem);
2144
2225
  acc.value += `\n [key: ${keyType}]: unknown;\n }`;
2145
2226
  }
2146
2227
  } else {
@@ -2149,13 +2230,13 @@ function getObject({ item, name, context, nullable, formDataContext }) {
2149
2230
  name,
2150
2231
  context
2151
2232
  });
2152
- const recordType = getPropertyNamesRecordType(item, resolvedValue.value);
2233
+ const recordType = getPropertyNamesRecordType(schemaItem, resolvedValue.value);
2153
2234
  if (recordType) {
2154
2235
  acc.value += "\n}";
2155
2236
  acc.value += ` & ${recordType}`;
2156
2237
  acc.useTypeAlias = true;
2157
2238
  } else {
2158
- const keyType = getIndexSignatureKey(item);
2239
+ const keyType = getIndexSignatureKey(schemaItem);
2159
2240
  acc.value += `\n [key: ${keyType}]: ${resolvedValue.value};\n}`;
2160
2241
  }
2161
2242
  acc.dependencies.push(...resolvedValue.dependencies);
@@ -2166,11 +2247,11 @@ function getObject({ item, name, context, nullable, formDataContext }) {
2166
2247
  }
2167
2248
  return acc;
2168
2249
  }
2169
- const outerAdditionalProps = item.additionalProperties;
2170
- const readOnlyFlag = item.readOnly;
2250
+ const outerAdditionalProps = schemaItem.additionalProperties;
2251
+ const readOnlyFlag = schemaItem.readOnly;
2171
2252
  if (outerAdditionalProps) {
2172
- if (isBoolean(outerAdditionalProps)) {
2173
- const recordType = getPropertyNamesRecordType(item, "unknown");
2253
+ if (outerAdditionalProps === true) {
2254
+ const recordType = getPropertyNamesRecordType(schemaItem, "unknown");
2174
2255
  if (recordType) return {
2175
2256
  value: recordType + nullable,
2176
2257
  imports: [],
@@ -2183,7 +2264,7 @@ function getObject({ item, name, context, nullable, formDataContext }) {
2183
2264
  dependencies: []
2184
2265
  };
2185
2266
  return {
2186
- value: `{ [key: ${getIndexSignatureKey(item)}]: unknown }` + nullable,
2267
+ value: `{ [key: ${getIndexSignatureKey(schemaItem)}]: unknown }` + nullable,
2187
2268
  imports: [],
2188
2269
  schemas: [],
2189
2270
  isEnum: false,
@@ -2199,7 +2280,7 @@ function getObject({ item, name, context, nullable, formDataContext }) {
2199
2280
  name,
2200
2281
  context
2201
2282
  });
2202
- const recordType = getPropertyNamesRecordType(item, resolvedValue.value);
2283
+ const recordType = getPropertyNamesRecordType(schemaItem, resolvedValue.value);
2203
2284
  if (recordType) return {
2204
2285
  value: recordType + nullable,
2205
2286
  imports: resolvedValue.imports,
@@ -2212,7 +2293,7 @@ function getObject({ item, name, context, nullable, formDataContext }) {
2212
2293
  dependencies: resolvedValue.dependencies
2213
2294
  };
2214
2295
  return {
2215
- value: `{[key: ${getIndexSignatureKey(item)}]: ${resolvedValue.value}}` + nullable,
2296
+ value: `{[key: ${getIndexSignatureKey(schemaItem)}]: ${resolvedValue.value}}` + nullable,
2216
2297
  imports: resolvedValue.imports,
2217
2298
  schemas: resolvedValue.schemas,
2218
2299
  isEnum: false,
@@ -2223,20 +2304,29 @@ function getObject({ item, name, context, nullable, formDataContext }) {
2223
2304
  dependencies: resolvedValue.dependencies
2224
2305
  };
2225
2306
  }
2226
- const constValue = item.const;
2227
- if (constValue) return {
2228
- value: `'${constValue}'`,
2229
- imports: [],
2230
- schemas: [],
2231
- isEnum: false,
2232
- type: "string",
2233
- isRef: false,
2234
- hasReadonlyProps: readOnlyFlag ?? false,
2235
- dependencies: []
2236
- };
2237
- const keyType = item.type === "object" ? getIndexSignatureKey(item) : "string";
2238
- const recordType = getPropertyNamesRecordType(item, "unknown");
2239
- if (item.type === "object" && recordType) return {
2307
+ const constValue = schemaItem.const;
2308
+ if (constValue !== void 0) {
2309
+ let type;
2310
+ if (Array.isArray(constValue)) type = "array";
2311
+ else if (constValue === null) type = "null";
2312
+ else if (typeof constValue === "string") type = "string";
2313
+ else if (typeof constValue === "number") type = "number";
2314
+ else if (typeof constValue === "boolean") type = "boolean";
2315
+ else type = "object";
2316
+ return {
2317
+ value: typeof constValue === "string" ? `'${escape(constValue)}'` : JSON.stringify(constValue),
2318
+ imports: [],
2319
+ schemas: [],
2320
+ isEnum: false,
2321
+ type,
2322
+ isRef: false,
2323
+ hasReadonlyProps: readOnlyFlag ?? false,
2324
+ dependencies: []
2325
+ };
2326
+ }
2327
+ const keyType = itemType === "object" ? getIndexSignatureKey(schemaItem) : "string";
2328
+ const recordType = getPropertyNamesRecordType(schemaItem, "unknown");
2329
+ if (itemType === "object" && recordType) return {
2240
2330
  value: recordType + nullable,
2241
2331
  imports: [],
2242
2332
  schemas: [],
@@ -2248,7 +2338,7 @@ function getObject({ item, name, context, nullable, formDataContext }) {
2248
2338
  dependencies: []
2249
2339
  };
2250
2340
  return {
2251
- value: (item.type === "object" ? `{ [key: ${keyType}]: unknown }` : "unknown") + nullable,
2341
+ value: (itemType === "object" ? `{ [key: ${keyType}]: unknown }` : "unknown") + nullable,
2252
2342
  imports: [],
2253
2343
  schemas: [],
2254
2344
  isEnum: false,
@@ -2436,7 +2526,7 @@ function normalizeAllOfSchema(schema) {
2436
2526
  let didMerge = false;
2437
2527
  const schemaProperties = schema.properties;
2438
2528
  const schemaRequired = schema.required;
2439
- const mergedProperties = { ...schemaProperties };
2529
+ const mergedProperties = schemaProperties ? { ...schemaProperties } : {};
2440
2530
  const mergedRequired = new Set(schemaRequired);
2441
2531
  const remainingAllOf = [];
2442
2532
  for (const subSchema of schemaAllOf) {
@@ -2583,15 +2673,18 @@ function combineSchemas({ name, schema, separator, context, nullable, formDataCo
2583
2673
  };
2584
2674
  }
2585
2675
  let resolvedValue;
2586
- if (normalizedSchema.properties) resolvedValue = getScalar({
2676
+ const normalizedProperties = normalizedSchema.properties;
2677
+ const schemaOneOf = schema.oneOf;
2678
+ const schemaAnyOf = schema.anyOf;
2679
+ if (normalizedProperties) resolvedValue = getScalar({
2587
2680
  item: Object.fromEntries(Object.entries(normalizedSchema).filter(([key]) => key !== separator)),
2588
2681
  name,
2589
2682
  context,
2590
2683
  formDataContext
2591
2684
  });
2592
- else if (separator === "allOf" && (schema.oneOf || schema.anyOf)) {
2593
- const siblingCombiner = schema.oneOf ? "oneOf" : "anyOf";
2594
- const siblingSchemas = schema[siblingCombiner];
2685
+ else if (separator === "allOf" && (schemaOneOf || schemaAnyOf)) {
2686
+ const siblingCombiner = schemaOneOf ? "oneOf" : "anyOf";
2687
+ const siblingSchemas = schemaOneOf ?? schemaAnyOf;
2595
2688
  resolvedValue = combineSchemas({
2596
2689
  schema: { [siblingCombiner]: siblingSchemas },
2597
2690
  name,
@@ -2785,8 +2878,8 @@ function getProps({ body, queryParams, params, operationName, headers, context }
2785
2878
  const parameterTypeName = `${pascal(operationName)}PathParameters`;
2786
2879
  const name = "pathParams";
2787
2880
  const namedParametersTypeDefinition = `export type ${parameterTypeName} = {\n ${params.map((property) => property.definition).join(",\n ")},\n }`;
2788
- const isOptional = context.output.optionsParamRequired || params.every((param) => param.default);
2789
- const implementation = `{ ${params.map((property) => property.default ? `${property.name} = ${property.default}` : property.name).join(", ")} }: ${parameterTypeName}${isOptional ? " = {}" : ""}`;
2881
+ const isOptional = context.output.optionsParamRequired || params.every((param) => param.default !== void 0);
2882
+ const implementation = `{ ${params.map((property) => property.default === void 0 ? property.name : `${property.name} = ${stringify(property.default)}`).join(", ")} }: ${parameterTypeName}${isOptional ? " = {}" : ""}`;
2790
2883
  const destructured = `{ ${params.map((property) => property.name).join(", ")} }`;
2791
2884
  paramGetterProps = [{
2792
2885
  type: GetterPropType.NAMED_PATH_PARAMS,
@@ -2951,13 +3044,15 @@ const getRoutePath = (path) => {
2951
3044
  const matches = /([^{]*){?([\w*_-]*)}?(.*)/.exec(path);
2952
3045
  if (!matches?.length) return path;
2953
3046
  const prev = matches[1];
2954
- const param = sanitize(camel(matches[2]), {
3047
+ const rawParam = matches[2];
3048
+ const rest = matches[3];
3049
+ const param = sanitize(camel(rawParam), {
2955
3050
  es5keyword: true,
2956
3051
  underscore: true,
2957
3052
  dash: true,
2958
3053
  dot: true
2959
3054
  });
2960
- const next = hasParam(matches[3]) ? getRoutePath(matches[3]) : matches[3];
3055
+ const next = hasParam(rest) ? getRoutePath(rest) : rest;
2961
3056
  return hasParam(path) ? `${prev}\${${param}}${next}` : `${prev}${param}${next}`;
2962
3057
  };
2963
3058
  function getRoute(route) {
@@ -2977,13 +3072,14 @@ function getFullRoute(route, servers, baseUrl) {
2977
3072
  if (!servers) throw new Error("Orval is configured to use baseUrl from the specifications 'servers' field, but there exist no servers in the specification.");
2978
3073
  const server = servers.at(Math.min(baseUrl.index ?? 0, servers.length - 1));
2979
3074
  if (!server) return "";
2980
- if (!server.variables) return server.url;
2981
- let url = server.url;
3075
+ const serverUrl = server.url ?? "";
3076
+ if (!server.variables) return serverUrl;
3077
+ let url = serverUrl;
2982
3078
  const variables = baseUrl.variables;
2983
3079
  for (const variableKey of Object.keys(server.variables)) {
2984
3080
  const variable = server.variables[variableKey];
2985
3081
  if (variables?.[variableKey]) {
2986
- if (variable.enum && !variable.enum.some((e) => e == variables[variableKey])) throw new Error(`Invalid variable value '${variables[variableKey]}' for variable '${variableKey}' when resolving ${server.url}. Valid values are: ${variable.enum.join(", ")}.`);
3082
+ if (variable.enum && !variable.enum.some((e) => e == variables[variableKey])) throw new Error(`Invalid variable value '${variables[variableKey]}' for variable '${variableKey}' when resolving ${serverUrl}. Valid values are: ${variable.enum.join(", ")}.`);
2987
3083
  url = url.replaceAll(`{${variableKey}}`, variables[variableKey]);
2988
3084
  } else url = url.replaceAll(`{${variableKey}}`, String(variable.default));
2989
3085
  }
@@ -3083,7 +3179,8 @@ function generateDependency({ deps, isAllowSyntheticDefaultImports, dependency,
3083
3179
  }
3084
3180
  function addDependency({ implementation, exports, dependency, projectName, isAllowSyntheticDefaultImports }) {
3085
3181
  const toAdds = exports.filter((e) => {
3086
- const searchWords = [e.alias, e.name].filter((p) => p?.length).join("|");
3182
+ const searchWords = [e.alias, e.name].filter((p) => Boolean(p?.length)).map((part) => escapeRegExp(part)).join("|");
3183
+ if (!searchWords) return false;
3087
3184
  const pattern = new RegExp(String.raw`\b(${searchWords})\b`, "g");
3088
3185
  return implementation.match(pattern);
3089
3186
  });
@@ -3126,8 +3223,7 @@ function addDependency({ implementation, exports, dependency, projectName, isAll
3126
3223
  }).join("\n") + "\n";
3127
3224
  }
3128
3225
  function getLibName(code) {
3129
- const splitString = code.split(" from ");
3130
- return splitString[splitString.length - 1].split(";")[0].trim();
3226
+ return (code.split(" from ").at(-1) ?? "").split(";")[0].trim();
3131
3227
  }
3132
3228
  function generateDependencyImports(implementation, imports, projectName, hasSchemaDir, isAllowSyntheticDefaultImports) {
3133
3229
  const dependencies = imports.map((dep) => addDependency({
@@ -3172,7 +3268,7 @@ function generateModelInline(acc, model) {
3172
3268
  return acc + `${model}\n`;
3173
3269
  }
3174
3270
  function generateModelsInline(obj) {
3175
- const schemas = Object.values(obj).flat();
3271
+ const schemas = Array.isArray(obj) ? obj : Object.values(obj).flat();
3176
3272
  let result = "";
3177
3273
  for (const { model } of schemas) result = generateModelInline(result, model);
3178
3274
  return result;
@@ -3336,9 +3432,14 @@ function removeComments(file) {
3336
3432
  * (e.g. observe-mode branches), prefer getAngularFilteredParamsCallExpression +
3337
3433
  * getAngularFilteredParamsHelperBody instead.
3338
3434
  */
3339
- const getAngularFilteredParamsExpression = (paramsExpression, requiredNullableParamKeys = []) => `(() => {
3435
+ const getAngularFilteredParamsExpression = (paramsExpression, requiredNullableParamKeys = [], preserveRequiredNullables = false) => {
3436
+ const filteredParamValueType = `string | number | boolean${preserveRequiredNullables ? " | null" : ""} | Array<string | number | boolean>`;
3437
+ const preserveNullableBranch = preserveRequiredNullables ? ` } else if (value === null && requiredNullableParamKeys.has(key)) {
3438
+ filteredParams[key] = value;
3439
+ ` : "";
3440
+ return `(() => {
3340
3441
  const requiredNullableParamKeys = new Set<string>(${JSON.stringify(requiredNullableParamKeys)});
3341
- const filteredParams = {} as Record<string, string | number | boolean | null | Array<string | number | boolean>>;
3442
+ const filteredParams: Record<string, ${filteredParamValueType}> = {};
3342
3443
  for (const [key, value] of Object.entries(${paramsExpression})) {
3343
3444
  if (Array.isArray(value)) {
3344
3445
  const filtered = value.filter(
@@ -3347,33 +3448,46 @@ const getAngularFilteredParamsExpression = (paramsExpression, requiredNullablePa
3347
3448
  (typeof item === 'string' ||
3348
3449
  typeof item === 'number' ||
3349
3450
  typeof item === 'boolean'),
3350
- ) as Array<string | number | boolean>;
3451
+ ) as Array<string | number | boolean>;
3351
3452
  if (filtered.length) {
3352
3453
  filteredParams[key] = filtered;
3353
3454
  }
3354
- } else if (value === null && requiredNullableParamKeys.has(key)) {
3355
- filteredParams[key] = value;
3356
- } else if (
3455
+ ${preserveNullableBranch} } else if (
3357
3456
  value != null &&
3358
3457
  (typeof value === 'string' ||
3359
3458
  typeof value === 'number' ||
3360
3459
  typeof value === 'boolean')
3361
3460
  ) {
3362
- filteredParams[key] = value as string | number | boolean;
3461
+ filteredParams[key] = value;
3363
3462
  }
3364
3463
  }
3365
- return filteredParams as unknown as Record<string, string | number | boolean | Array<string | number | boolean>>;
3464
+ return filteredParams;
3366
3465
  })()`;
3466
+ };
3367
3467
  /**
3368
3468
  * Returns the body of a standalone `filterParams` helper function
3369
3469
  * to be emitted once in the generated file header, replacing the
3370
3470
  * inline IIFE that was previously duplicated in every method.
3371
3471
  */
3372
- const getAngularFilteredParamsHelperBody = () => `function filterParams(
3472
+ const getAngularFilteredParamsHelperBody = () => `type AngularHttpParamValue = string | number | boolean | Array<string | number | boolean>;
3473
+ type AngularHttpParamValueWithNullable = AngularHttpParamValue | null;
3474
+
3475
+ function filterParams(
3476
+ params: Record<string, unknown>,
3477
+ requiredNullableKeys?: ReadonlySet<string>,
3478
+ preserveRequiredNullables?: false,
3479
+ ): Record<string, AngularHttpParamValue>;
3480
+ function filterParams(
3373
3481
  params: Record<string, unknown>,
3374
- requiredNullableKeys: Set<string> = new Set(),
3375
- ): Record<string, string | number | boolean | Array<string | number | boolean>> {
3376
- const filteredParams: Record<string, string | number | boolean | null | Array<string | number | boolean>> = {};
3482
+ requiredNullableKeys: ReadonlySet<string> | undefined,
3483
+ preserveRequiredNullables: true,
3484
+ ): Record<string, AngularHttpParamValueWithNullable>;
3485
+ function filterParams(
3486
+ params: Record<string, unknown>,
3487
+ requiredNullableKeys: ReadonlySet<string> = new Set(),
3488
+ preserveRequiredNullables = false,
3489
+ ): Record<string, AngularHttpParamValueWithNullable> {
3490
+ const filteredParams: Record<string, AngularHttpParamValueWithNullable> = {};
3377
3491
  for (const [key, value] of Object.entries(params)) {
3378
3492
  if (Array.isArray(value)) {
3379
3493
  const filtered = value.filter(
@@ -3386,7 +3500,11 @@ const getAngularFilteredParamsHelperBody = () => `function filterParams(
3386
3500
  if (filtered.length) {
3387
3501
  filteredParams[key] = filtered;
3388
3502
  }
3389
- } else if (value === null && requiredNullableKeys.has(key)) {
3503
+ } else if (
3504
+ preserveRequiredNullables &&
3505
+ value === null &&
3506
+ requiredNullableKeys.has(key)
3507
+ ) {
3390
3508
  filteredParams[key] = value;
3391
3509
  } else if (
3392
3510
  value != null &&
@@ -3394,15 +3512,15 @@ const getAngularFilteredParamsHelperBody = () => `function filterParams(
3394
3512
  typeof value === 'number' ||
3395
3513
  typeof value === 'boolean')
3396
3514
  ) {
3397
- filteredParams[key] = value as string | number | boolean;
3515
+ filteredParams[key] = value;
3398
3516
  }
3399
3517
  }
3400
- return filteredParams as Record<string, string | number | boolean | Array<string | number | boolean>>;
3518
+ return filteredParams;
3401
3519
  }`;
3402
3520
  /**
3403
3521
  * Returns a call expression to the `filterParams` helper function.
3404
3522
  */
3405
- const getAngularFilteredParamsCallExpression = (paramsExpression, requiredNullableParamKeys = []) => `filterParams(${paramsExpression}, new Set<string>(${JSON.stringify(requiredNullableParamKeys)}))`;
3523
+ const getAngularFilteredParamsCallExpression = (paramsExpression, requiredNullableParamKeys = [], preserveRequiredNullables = false) => `filterParams(${paramsExpression}, new Set<string>(${JSON.stringify(requiredNullableParamKeys)})${preserveRequiredNullables ? ", true" : ""})`;
3406
3524
  function generateBodyOptions(body, isFormData, isFormUrlEncoded) {
3407
3525
  if (isFormData && body.formData) return "\n formData,";
3408
3526
  if (isFormUrlEncoded && body.formUrlEncoded) return "\n formUrlEncoded,";
@@ -3424,7 +3542,7 @@ function generateAxiosOptions({ response, isExactOptionalPropertyTypes, angularO
3424
3542
  let value = "";
3425
3543
  if (!isRequestOptions) {
3426
3544
  if (queryParams) if (isAngular) {
3427
- const iifeExpr = getAngularFilteredParamsExpression("params ?? {}", requiredNullableQueryParamKeys);
3545
+ const iifeExpr = getAngularFilteredParamsExpression("params ?? {}", requiredNullableQueryParamKeys, !!paramsSerializer);
3428
3546
  value += paramsSerializer ? `\n params: ${paramsSerializer.name}(${iifeExpr}),` : `\n params: ${iifeExpr},`;
3429
3547
  } else value += "\n params,";
3430
3548
  if (headers) value += "\n headers,";
@@ -3441,7 +3559,7 @@ function generateAxiosOptions({ response, isExactOptionalPropertyTypes, angularO
3441
3559
  if (queryParams) if (isVue) value += "\n params: {...unref(params), ...options?.params},";
3442
3560
  else if (isAngular && angularParamsRef) value += `\n params: ${angularParamsRef},`;
3443
3561
  else if (isAngular && paramsSerializer) {
3444
- const callExpr = getAngularFilteredParamsCallExpression("{...params, ...options?.params}", requiredNullableQueryParamKeys);
3562
+ const callExpr = getAngularFilteredParamsCallExpression("{...params, ...options?.params}", requiredNullableQueryParamKeys, true);
3445
3563
  value += `\n params: ${paramsSerializer.name}(${callExpr}),`;
3446
3564
  } else if (isAngular) value += `\n params: ${getAngularFilteredParamsCallExpression("{...params, ...options?.params}", requiredNullableQueryParamKeys)},`;
3447
3565
  else value += "\n params: {...params, ...options?.params},";
@@ -3889,6 +4007,20 @@ function _filteredVerbs(verbs, filters) {
3889
4007
  });
3890
4008
  }
3891
4009
 
4010
+ //#endregion
4011
+ //#region src/writers/file.ts
4012
+ const TRAILING_WHITESPACE_RE = /[^\S\r\n]+$/gm;
4013
+ /**
4014
+ * Write generated code to a file, stripping trailing whitespace from each line.
4015
+ *
4016
+ * Template literals in code generators can produce lines with only whitespace
4017
+ * when conditional expressions evaluate to empty strings. This function
4018
+ * ensures the output is always clean regardless of generator implementation.
4019
+ */
4020
+ async function writeGeneratedFile(filePath, content) {
4021
+ await fs$1.outputFile(filePath, content.replaceAll(TRAILING_WHITESPACE_RE, ""));
4022
+ }
4023
+
3892
4024
  //#endregion
3893
4025
  //#region src/writers/schemas.ts
3894
4026
  /**
@@ -4018,11 +4150,10 @@ function resolveImportKey(schemaPath, importPath, fileExtension) {
4018
4150
  function removeFileExtension(path, fileExtension) {
4019
4151
  return path.endsWith(fileExtension) ? path.slice(0, path.length - fileExtension.length) : path;
4020
4152
  }
4021
- function getSchema({ schema: { imports, model }, target, header, namingConvention = NamingConvention.CAMEL_CASE }) {
4153
+ function getSchema({ schema: { imports, model }, header, namingConvention = NamingConvention.CAMEL_CASE }) {
4022
4154
  let file = header;
4023
4155
  file += generateImports({
4024
4156
  imports: imports.filter((imp) => !model.includes(`type ${imp.alias ?? imp.name} =`) && !model.includes(`interface ${imp.alias ?? imp.name} {`)),
4025
- target,
4026
4157
  namingConvention
4027
4158
  });
4028
4159
  file += imports.length > 0 ? "\n\n" : "\n";
@@ -4043,7 +4174,7 @@ function writeModelsInline(array) {
4043
4174
  async function writeSchema({ path, schema, target, namingConvention, fileExtension, header }) {
4044
4175
  const name = conventionName(schema.name, namingConvention);
4045
4176
  try {
4046
- await fs$1.outputFile(getPath(path, name, fileExtension), getSchema({
4177
+ await writeGeneratedFile(getPath(path, name, fileExtension), getSchema({
4047
4178
  schema,
4048
4179
  target,
4049
4180
  header,
@@ -4084,14 +4215,7 @@ async function writeSchemas({ schemaPath, schemas, target, namingConvention, fil
4084
4215
  const ext = fileExtension.endsWith(".ts") ? fileExtension.slice(0, -3) : fileExtension;
4085
4216
  const conventionNamesSet = new Set(Object.values(schemaGroups).map((group) => conventionName(group[0].name, namingConvention)));
4086
4217
  try {
4087
- const currentExports = [...conventionNamesSet].map((schemaName) => `export * from './${schemaName}${ext}';`).toSorted((a, b) => a.localeCompare(b));
4088
- const existingExports = (await fs$1.readFile(schemaFilePath, "utf8")).match(/export\s+\*\s+from\s+['"][^'"]+['"]/g)?.map((statement) => {
4089
- const match = /export\s+\*\s+from\s+['"]([^'"]+)['"]/.exec(statement);
4090
- if (!match) return;
4091
- return `export * from '${match[1]}';`;
4092
- }).filter(Boolean) ?? [];
4093
- const fileContent = `${header}\n${[...new Set([...existingExports, ...currentExports])].toSorted((a, b) => a.localeCompare(b)).join("\n")}`;
4094
- await fs$1.writeFile(schemaFilePath, fileContent, { encoding: "utf8" });
4218
+ await writeGeneratedFile(schemaFilePath, `${header}\n${[...conventionNamesSet].map((schemaName) => `export * from './${schemaName}${ext}';`).toSorted((a, b) => a.localeCompare(b)).join("\n")}\n`);
4095
4219
  } catch (error) {
4096
4220
  throw new Error(`Oups... 🍻. An Error occurred while writing schema index file ${schemaFilePath} => ${String(error)}`, { cause: error });
4097
4221
  }
@@ -4110,15 +4234,21 @@ function generateImportsForBuilder(output, imports, relativeSchemasPath) {
4110
4234
  exports: imports.filter((i) => !i.importPath),
4111
4235
  dependency: relativeSchemasPath
4112
4236
  }];
4113
- else schemaImports = uniqueBy(imports.filter((i) => !i.importPath), (x) => x.name).map((i) => {
4114
- const name = conventionName(isZodSchemaOutput ? i.name : i.schemaName ?? i.name, output.namingConvention);
4115
- const suffix = isZodSchemaOutput ? ".zod" : "";
4116
- const importExtension = output.fileExtension.replace(/\.ts$/, "") || "";
4117
- return {
4118
- exports: [i],
4119
- dependency: joinSafe(relativeSchemasPath, `${name}${suffix}${importExtension}`)
4120
- };
4121
- });
4237
+ else {
4238
+ const importsByDependency = /* @__PURE__ */ new Map();
4239
+ for (const schemaImport of imports.filter((i) => !i.importPath)) {
4240
+ const normalizedName = conventionName(isZodSchemaOutput ? schemaImport.name : schemaImport.schemaName ?? schemaImport.name, output.namingConvention);
4241
+ const suffix = isZodSchemaOutput ? ".zod" : "";
4242
+ const importExtension = output.fileExtension.replace(/\.ts$/, "") || "";
4243
+ const dependency = joinSafe(relativeSchemasPath, `${normalizedName}${suffix}${importExtension}`);
4244
+ if (!importsByDependency.has(dependency)) importsByDependency.set(dependency, []);
4245
+ importsByDependency.get(dependency)?.push(schemaImport);
4246
+ }
4247
+ schemaImports = [...importsByDependency.entries()].map(([dependency, dependencyImports]) => ({
4248
+ dependency,
4249
+ exports: uniqueBy(dependencyImports, (entry) => `${entry.name}|${entry.alias ?? ""}|${String(entry.values)}|${String(entry.default)}`)
4250
+ }));
4251
+ }
4122
4252
  const otherImports = uniqueBy(imports.filter((i) => !!i.importPath), (x) => x.name + x.importPath).map((i) => {
4123
4253
  return {
4124
4254
  exports: [i],
@@ -4249,14 +4379,24 @@ interface TypedResponse<T> extends Response {
4249
4379
  async function writeSingleMode({ builder, output, projectName, header, needSchema }) {
4250
4380
  try {
4251
4381
  const { path } = getFileInfo(output.target, {
4252
- backupFilename: conventionName(builder.info.title, output.namingConvention),
4382
+ backupFilename: conventionName(builder.info.title ?? "filename", output.namingConvention),
4253
4383
  extension: output.fileExtension
4254
4384
  });
4255
4385
  const { imports, importsMock, implementation, implementationMock, mutators, clientMutators, formData, formUrlEncoded, paramsSerializer, fetchReviver } = generateTarget(builder, output);
4256
4386
  let data = header;
4257
4387
  const schemasPath = output.schemas ? getRelativeImportPath(path, getFileInfo(isString(output.schemas) ? output.schemas : output.schemas.path, { extension: output.fileExtension }).dirname) : void 0;
4258
4388
  const isAllowSyntheticDefaultImports = isSyntheticDefaultImportsAllow(output.tsconfig);
4259
- const importsForBuilder = schemasPath ? generateImportsForBuilder(output, imports.filter((imp) => !importsMock.some((impMock) => imp.name === impMock.name)), schemasPath) : [];
4389
+ const normalizedImports = imports.filter((imp) => {
4390
+ const searchWords = [imp.alias, imp.name].filter((part) => Boolean(part?.length)).map((part) => escapeRegExp(part)).join("|");
4391
+ if (!searchWords) return false;
4392
+ return new RegExp(String.raw`\b(${searchWords})\b`, "g").test(implementation);
4393
+ }).map((imp) => ({ ...imp }));
4394
+ for (const mockImport of importsMock) {
4395
+ const matchingImport = normalizedImports.find((imp) => imp.name === mockImport.name && (imp.alias ?? "") === (mockImport.alias ?? ""));
4396
+ if (!matchingImport) continue;
4397
+ if (!!mockImport.values || !!mockImport.isConstant || !!mockImport.default || !!mockImport.namespaceImport || !!mockImport.syntheticDefaultImport) matchingImport.values = true;
4398
+ }
4399
+ const importsForBuilder = schemasPath ? generateImportsForBuilder(output, normalizedImports, schemasPath) : [];
4260
4400
  data += builder.imports({
4261
4401
  client: output.client,
4262
4402
  implementation,
@@ -4271,7 +4411,7 @@ async function writeSingleMode({ builder, output, projectName, header, needSchem
4271
4411
  output
4272
4412
  });
4273
4413
  if (output.mock) {
4274
- const importsMockForBuilder = schemasPath ? generateImportsForBuilder(output, importsMock, schemasPath) : [];
4414
+ const importsMockForBuilder = schemasPath ? generateImportsForBuilder(output, importsMock.filter((impMock) => !normalizedImports.some((imp) => imp.name === impMock.name && (imp.alias ?? "") === (impMock.alias ?? ""))), schemasPath) : [];
4275
4415
  data += builder.importsMock({
4276
4416
  implementation: implementationMock,
4277
4417
  imports: importsMockForBuilder,
@@ -4304,7 +4444,7 @@ async function writeSingleMode({ builder, output, projectName, header, needSchem
4304
4444
  data += "\n\n";
4305
4445
  data += implementationMock;
4306
4446
  }
4307
- await fs$1.outputFile(path, data);
4447
+ await writeGeneratedFile(path, data);
4308
4448
  return [path];
4309
4449
  } catch (error) {
4310
4450
  const errorMsg = error instanceof Error ? error.message : "unknown error";
@@ -4317,7 +4457,7 @@ async function writeSingleMode({ builder, output, projectName, header, needSchem
4317
4457
  async function writeSplitMode({ builder, output, projectName, header, needSchema }) {
4318
4458
  try {
4319
4459
  const { path: targetPath, filename, dirname, extension } = getFileInfo(output.target, {
4320
- backupFilename: conventionName(builder.info.title, output.namingConvention),
4460
+ backupFilename: conventionName(builder.info.title ?? "filename", output.namingConvention),
4321
4461
  extension: output.fileExtension
4322
4462
  });
4323
4463
  const { imports, implementation, implementationMock, importsMock, mutators, clientMutators, formData, formUrlEncoded, paramsSerializer, fetchReviver } = generateTarget(builder, output);
@@ -4349,10 +4489,7 @@ async function writeSplitMode({ builder, output, projectName, header, needSchema
4349
4489
  options: isFunction(output.mock) ? void 0 : output.mock
4350
4490
  });
4351
4491
  const schemasPath = output.schemas ? void 0 : nodePath.join(dirname, filename + ".schemas" + extension);
4352
- if (schemasPath && needSchema) {
4353
- const schemasData = header + generateModelsInline(builder.schemas);
4354
- await fs$1.outputFile(schemasPath, schemasData);
4355
- }
4492
+ if (schemasPath && needSchema) await writeGeneratedFile(schemasPath, header + generateModelsInline(builder.schemas));
4356
4493
  if (mutators) implementationData += generateMutatorImports({
4357
4494
  mutators,
4358
4495
  implementation
@@ -4374,9 +4511,9 @@ async function writeSplitMode({ builder, output, projectName, header, needSchema
4374
4511
  mockData += `\n${implementationMock}`;
4375
4512
  const implementationFilename = filename + (OutputClient.ANGULAR === output.client ? ".service" : "") + extension;
4376
4513
  const implementationPath = nodePath.join(dirname, implementationFilename);
4377
- await fs$1.outputFile(implementationPath, implementationData);
4514
+ await writeGeneratedFile(implementationPath, implementationData);
4378
4515
  const mockPath = output.mock ? nodePath.join(dirname, filename + "." + getMockFileExtensionByTypeName(output.mock) + extension) : void 0;
4379
- if (mockPath) await fs$1.outputFile(mockPath, mockData);
4516
+ if (mockPath) await writeGeneratedFile(mockPath, mockData);
4380
4517
  return [
4381
4518
  implementationPath,
4382
4519
  ...schemasPath ? [schemasPath] : [],
@@ -4506,7 +4643,7 @@ function generateTargetForTags(builder, options) {
4506
4643
  //#region src/writers/split-tags-mode.ts
4507
4644
  async function writeSplitTagsMode({ builder, output, projectName, header, needSchema }) {
4508
4645
  const { filename, dirname, extension } = getFileInfo(output.target, {
4509
- backupFilename: conventionName(builder.info.title, output.namingConvention),
4646
+ backupFilename: conventionName(builder.info.title ?? "filename", output.namingConvention),
4510
4647
  extension: output.fileExtension
4511
4648
  });
4512
4649
  const target = generateTargetForTags(builder, output);
@@ -4546,10 +4683,7 @@ async function writeSplitTagsMode({ builder, output, projectName, header, needSc
4546
4683
  options: isFunction(output.mock) ? void 0 : output.mock
4547
4684
  });
4548
4685
  const schemasPath = output.schemas ? void 0 : nodePath.join(dirname, filename + ".schemas" + extension);
4549
- if (schemasPath && needSchema) {
4550
- const schemasData = header + generateModelsInline(builder.schemas);
4551
- await fs$1.outputFile(schemasPath, schemasData);
4552
- }
4686
+ if (schemasPath && needSchema) await writeGeneratedFile(schemasPath, header + generateModelsInline(builder.schemas));
4553
4687
  if (mutators) implementationData += generateMutatorImports({
4554
4688
  mutators,
4555
4689
  implementation,
@@ -4587,9 +4721,9 @@ async function writeSplitTagsMode({ builder, output, projectName, header, needSc
4587
4721
  mockData += `\n${implementationMock}`;
4588
4722
  const implementationFilename = tag + (OutputClient.ANGULAR === output.client ? ".service" : "") + extension;
4589
4723
  const implementationPath = nodePath.join(dirname, tag, implementationFilename);
4590
- await fs$1.outputFile(implementationPath, implementationData);
4724
+ await writeGeneratedFile(implementationPath, implementationData);
4591
4725
  const mockPath = output.mock ? nodePath.join(dirname, tag, tag + "." + getMockFileExtensionByTypeName(output.mock) + extension) : void 0;
4592
- if (mockPath) await fs$1.outputFile(mockPath, mockData);
4726
+ if (mockPath) await writeGeneratedFile(mockPath, mockData);
4593
4727
  return [
4594
4728
  implementationPath,
4595
4729
  ...schemasPath ? [schemasPath] : [],
@@ -4606,14 +4740,14 @@ async function writeSplitTagsMode({ builder, output, projectName, header, needSc
4606
4740
  }).join("");
4607
4741
  await fs$1.appendFile(indexFilePath, indexContent);
4608
4742
  }
4609
- return [...indexFilePath ? [indexFilePath] : [], ...generatedFilePathsArray.flat()];
4743
+ return [...new Set([...indexFilePath ? [indexFilePath] : [], ...generatedFilePathsArray.flat()])];
4610
4744
  }
4611
4745
 
4612
4746
  //#endregion
4613
4747
  //#region src/writers/tags-mode.ts
4614
4748
  async function writeTagsMode({ builder, output, projectName, header, needSchema }) {
4615
4749
  const { path: targetPath, filename, dirname, extension } = getFileInfo(output.target, {
4616
- backupFilename: conventionName(builder.info.title, output.namingConvention),
4750
+ backupFilename: conventionName(builder.info.title ?? "filename", output.namingConvention),
4617
4751
  extension: output.fileExtension
4618
4752
  });
4619
4753
  const target = generateTargetForTags(builder, output);
@@ -4623,7 +4757,17 @@ async function writeTagsMode({ builder, output, projectName, header, needSchema
4623
4757
  const { imports, implementation, implementationMock, importsMock, mutators, clientMutators, formData, formUrlEncoded, fetchReviver, paramsSerializer } = target;
4624
4758
  let data = header;
4625
4759
  const schemasPathRelative = output.schemas ? getRelativeImportPath(targetPath, getFileInfo(isString(output.schemas) ? output.schemas : output.schemas.path, { extension: output.fileExtension }).dirname) : "./" + filename + ".schemas";
4626
- const importsForBuilder = generateImportsForBuilder(output, imports.filter((imp) => !importsMock.some((impMock) => imp.name === impMock.name)), schemasPathRelative);
4760
+ const normalizedImports = imports.filter((imp) => {
4761
+ const searchWords = [imp.alias, imp.name].filter((part) => Boolean(part?.length)).map((part) => escapeRegExp(part)).join("|");
4762
+ if (!searchWords) return false;
4763
+ return new RegExp(String.raw`\b(${searchWords})\b`, "g").test(implementation);
4764
+ }).map((imp) => ({ ...imp }));
4765
+ for (const mockImport of importsMock) {
4766
+ const matchingImport = normalizedImports.find((imp) => imp.name === mockImport.name && (imp.alias ?? "") === (mockImport.alias ?? ""));
4767
+ if (!matchingImport) continue;
4768
+ if (!!mockImport.values || !!mockImport.isConstant || !!mockImport.default || !!mockImport.namespaceImport || !!mockImport.syntheticDefaultImport) matchingImport.values = true;
4769
+ }
4770
+ const importsForBuilder = generateImportsForBuilder(output, normalizedImports, schemasPathRelative);
4627
4771
  data += builder.imports({
4628
4772
  client: output.client,
4629
4773
  implementation,
@@ -4638,7 +4782,7 @@ async function writeTagsMode({ builder, output, projectName, header, needSchema
4638
4782
  output
4639
4783
  });
4640
4784
  if (output.mock) {
4641
- const importsMockForBuilder = generateImportsForBuilder(output, importsMock, schemasPathRelative);
4785
+ const importsMockForBuilder = generateImportsForBuilder(output, importsMock.filter((impMock) => !normalizedImports.some((imp) => imp.name === impMock.name && (imp.alias ?? "") === (impMock.alias ?? ""))), schemasPathRelative);
4642
4786
  data += builder.importsMock({
4643
4787
  implementation: implementationMock,
4644
4788
  imports: importsMockForBuilder,
@@ -4649,10 +4793,7 @@ async function writeTagsMode({ builder, output, projectName, header, needSchema
4649
4793
  });
4650
4794
  }
4651
4795
  const schemasPath = output.schemas ? void 0 : nodePath.join(dirname, filename + ".schemas" + extension);
4652
- if (schemasPath && needSchema) {
4653
- const schemasData = header + generateModelsInline(builder.schemas);
4654
- await fs$1.outputFile(schemasPath, schemasData);
4655
- }
4796
+ if (schemasPath && needSchema) await writeGeneratedFile(schemasPath, header + generateModelsInline(builder.schemas));
4656
4797
  if (mutators) data += generateMutatorImports({
4657
4798
  mutators,
4658
4799
  implementation
@@ -4677,7 +4818,7 @@ async function writeTagsMode({ builder, output, projectName, header, needSchema
4677
4818
  data += implementationMock;
4678
4819
  }
4679
4820
  const implementationPath = nodePath.join(dirname, `${kebab(tag)}${extension}`);
4680
- await fs$1.outputFile(implementationPath, data);
4821
+ await writeGeneratedFile(implementationPath, data);
4681
4822
  return [implementationPath, ...schemasPath ? [schemasPath] : []];
4682
4823
  } catch (error) {
4683
4824
  throw new Error(`Oups... 🍻. An Error occurred while writing tag ${tag} => ${String(error)}`, { cause: error });
@@ -4686,5 +4827,5 @@ async function writeTagsMode({ builder, output, projectName, header, needSchema
4686
4827
  }
4687
4828
 
4688
4829
  //#endregion
4689
- 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, 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 };
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 };
4690
4831
  //# sourceMappingURL=index.mjs.map