vite-plugin-openapi-codegen 1.1.0 → 1.1.2

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/README.md CHANGED
@@ -149,6 +149,25 @@ export default defineConfig({
149
149
  });
150
150
  ```
151
151
 
152
+ If you want shorter generated type references, enable `typeAliases`. This keeps the type name suffixes, but avoids long chains like `components["schemas"]["CreateUserRequest"]` and `operations["list_users"]["parameters"]["query"]` in the generated output:
153
+
154
+ ```ts
155
+ import { defineConfig } from "vite-plus";
156
+ import { openapiCodegen } from "vite-plugin-openapi-codegen";
157
+
158
+ export default defineConfig({
159
+ plugins: [
160
+ openapiCodegen({
161
+ input: "openapi.json",
162
+ output: "src/generated",
163
+ typeAliases: true,
164
+ }),
165
+ ],
166
+ });
167
+ ```
168
+
169
+ When enabled, the plugin writes the raw OpenAPI types to `api-types.d.ts` and adds top-level aliases for schema and operation types that the generated `api.ts` and `client.ts` files import directly.
170
+
152
171
  ## Generated Output
153
172
 
154
173
  Given a spec path like `/api/users/{user_id}`, the plugin generates a path builder:
@@ -201,6 +220,7 @@ interface Options {
201
220
  output: string;
202
221
  pathPrefix?: string;
203
222
  stripPrefix?: boolean;
223
+ typeAliases?: boolean;
204
224
  httpClient?: {
205
225
  module?: string;
206
226
  jsonFunction?: string;
@@ -229,6 +249,10 @@ Only paths starting with this prefix are included. The default is `"/api/"`.
229
249
 
230
250
  Controls whether the `pathPrefix` is removed from generated path builders. The default is `true`.
231
251
 
252
+ ### `typeAliases`
253
+
254
+ When enabled, the plugin generates top-level aliases for schema and operation types and makes the emitted `api.ts` and `client.ts` import those shorter names. The suffixes stay intact, so generated names remain readable while avoiding long `components["schemas"][...]` and `operations["..."][...]` chains. The default is `false` to preserve existing output.
255
+
232
256
  ### `httpClient`
233
257
 
234
258
  Overrides the runtime import path and symbol names used by generated clients.
package/dist/index.d.mts CHANGED
@@ -75,12 +75,15 @@ interface Options {
75
75
  stripPrefix?: boolean;
76
76
  /** HTTP client configuration */
77
77
  httpClient?: HttpClientConfig;
78
+ /** Generate and consume top-level type aliases. Default: false */
79
+ typeAliases?: boolean;
78
80
  }
79
81
  interface GeneratedArtifacts {
80
82
  api: string;
83
+ apiTypes?: string;
81
84
  client: string;
82
85
  }
83
- declare function renderGeneratedArtifacts(spec: OpenAPISpec, options: Pick<Options, "httpClient" | "pathPrefix" | "stripPrefix">, preCollectedOperations?: OperationEntry[]): GeneratedArtifacts;
86
+ declare function renderGeneratedArtifacts(spec: OpenAPISpec, options: Pick<Options, "httpClient" | "pathPrefix" | "stripPrefix" | "typeAliases">, preCollectedOperations?: OperationEntry[]): GeneratedArtifacts;
84
87
  declare function openapiCodegen(options: Options): Plugin;
85
88
  //#endregion
86
89
  export { type HttpClientConfig, type OpenAPISpec, type OperationEntry, type Options, openapiCodegen, renderGeneratedArtifacts };
package/dist/index.mjs CHANGED
@@ -23,9 +23,9 @@ function renderApiSource(entries, generatedHeader) {
23
23
  function renderClientSource(model, generatedHeader, httpClient) {
24
24
  const statements = [createTypeOnlyImport([httpClient.requestOptionsType], httpClient.module), createValueImport([{ name: httpClient.jsonFunction }, { name: httpClient.voidFunction }], httpClient.module)];
25
25
  const apiTypeImports = collectApiTypeImports(model.operations.flatMap((operation) => [
26
- operation.bodyChannel.typeExpr,
27
- operation.pathChannel.typeExpr,
28
- operation.queryChannel.typeExpr,
26
+ operation.bodyChannel.typeRef?.sourceExpr,
27
+ operation.pathChannel.typeRef?.sourceExpr,
28
+ operation.queryChannel.typeRef?.sourceExpr,
29
29
  operation.responseTypeExpr,
30
30
  operation.returnTypeExpr
31
31
  ]));
@@ -46,6 +46,10 @@ function renderClientSource(model, generatedHeader, httpClient) {
46
46
  }
47
47
  return printGeneratedFile(statements, generatedHeader);
48
48
  }
49
+ function renderOperationTypeAliases(typeAliases) {
50
+ if (typeAliases.length === 0) return "";
51
+ return `${typeAliases.map((alias) => `export type ${alias.typeName} = ${alias.definitionExpr};`).join("\n")}\n`;
52
+ }
49
53
  function printGeneratedFile(statements, generatedHeader) {
50
54
  const sourceFile = ts.factory.createSourceFile(statements, ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
51
55
  const printed = AST_PRINTER.printFile(sourceFile).trim();
@@ -75,10 +79,41 @@ function collectApiTypeImports(typeExprs) {
75
79
  for (const typeExpr of typeExprs) {
76
80
  if (!typeExpr) continue;
77
81
  if (typeExpr.includes("components[")) names.add("components");
78
- if (typeExpr.includes("operations[")) names.add("operations");
82
+ if (typeExpr.includes("operations[")) {
83
+ names.add("operations");
84
+ continue;
85
+ }
86
+ const name = typeExpr.trim();
87
+ if (/^[A-Za-z_$][\w$]*$/.test(name) && !isBuiltinTypeName(name)) names.add(name);
79
88
  }
80
89
  return [...names].sort();
81
90
  }
91
+ function isBuiltinTypeName(name) {
92
+ return new Set([
93
+ "AbortSignal",
94
+ "Array",
95
+ "Blob",
96
+ "Date",
97
+ "Error",
98
+ "File",
99
+ "FormData",
100
+ "Map",
101
+ "Omit",
102
+ "Promise",
103
+ "Record",
104
+ "Set",
105
+ "URLSearchParams",
106
+ "boolean",
107
+ "never",
108
+ "null",
109
+ "number",
110
+ "object",
111
+ "string",
112
+ "undefined",
113
+ "unknown",
114
+ "void"
115
+ ]).has(name);
116
+ }
82
117
  function createPathExpression(strippedPath) {
83
118
  const matches = [...strippedPath.matchAll(/\{(\w+)\}/g)];
84
119
  if (matches.length === 0) return ts.factory.createStringLiteral(strippedPath);
@@ -104,7 +139,7 @@ function createClientOptionsDeclaration(operation) {
104
139
  ]);
105
140
  }
106
141
  function createClientChannelField(key, channel) {
107
- return ts.factory.createPropertySignature(void 0, ts.factory.createIdentifier(key), channel.present && channel.required ? void 0 : ts.factory.createToken(ts.SyntaxKind.QuestionToken), channel.present && channel.typeExpr ? createTypeNodeFromText(channel.typeExpr) : ts.factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword));
142
+ return ts.factory.createPropertySignature(void 0, ts.factory.createIdentifier(key), channel.present && channel.required ? void 0 : ts.factory.createToken(ts.SyntaxKind.QuestionToken), channel.present && channel.typeRef ? createTypeNodeFromText(channel.typeRef.sourceExpr) : ts.factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword));
108
143
  }
109
144
  function createClientFunctionDeclaration(operation) {
110
145
  const requestProperties = [ts.factory.createSpreadAssignment(ts.factory.createIdentifier("requestOptions")), ts.factory.createPropertyAssignment(ts.factory.createIdentifier("method"), ts.factory.createStringLiteral(operation.methodUpper))];
@@ -145,7 +180,8 @@ function buildClientRenderModelFromOperations(operations, spec, requestFunctionN
145
180
  const normalized = operations.map((entry) => normalizeOperation(entry, context, requestFunctionNames));
146
181
  return {
147
182
  operations: normalized,
148
- needsSearchParamsHelper: normalized.some((operation) => operation.queryChannel.present)
183
+ needsSearchParamsHelper: normalized.some((operation) => operation.queryChannel.present),
184
+ typeAliases: collectTypeAliases(normalized)
149
185
  };
150
186
  }
151
187
  function collectOperations(spec, pathPrefix = "/api/", stripPrefix = true) {
@@ -161,7 +197,7 @@ function collectOperations(spec, pathPrefix = "/api/", stripPrefix = true) {
161
197
  const strippedPath = stripPrefix ? apiPath.replace(pathPrefix, "") : apiPath;
162
198
  entries.push({
163
199
  apiPath,
164
- funcName: makeFuncName(method, apiPath, pathPrefix),
200
+ funcName: makeFuncName(operation.operationId, method, apiPath, pathPrefix),
165
201
  group: strippedPath.split("/")[0] ?? "misc",
166
202
  method,
167
203
  operation,
@@ -194,7 +230,8 @@ function buildNormalizationContext(spec) {
194
230
  }
195
231
  return {
196
232
  schemaAliasIndex,
197
- schemaNames
233
+ schemaNames,
234
+ usedTypeNames: new Set(schemaNames)
198
235
  };
199
236
  }
200
237
  function getParametersByLocation(operation, location) {
@@ -236,7 +273,9 @@ function getBuilderAlias(funcName) {
236
273
  function getClientOptionTypeName(funcName) {
237
274
  return `${capitalize(funcName)}Options`;
238
275
  }
239
- function makeFuncName(method, apiPath, pathPrefix = "/api/") {
276
+ function makeFuncName(operationId, method, apiPath, pathPrefix = "/api/") {
277
+ const normalizedOperationId = toFunctionName(operationId);
278
+ if (normalizedOperationId.length > 0) return normalizedOperationId;
240
279
  const segments = apiPath.replace(pathPrefix, "").split("/");
241
280
  const result = [];
242
281
  for (const segment of segments) {
@@ -252,6 +291,13 @@ function makeFuncName(method, apiPath, pathPrefix = "/api/") {
252
291
  return index === 0 ? clean : `${clean[0].toUpperCase()}${clean.slice(1)}`;
253
292
  }).join(""))}`;
254
293
  }
294
+ function toFunctionName(value) {
295
+ const segments = value.split(/[^a-zA-Z0-9]+/g).map((segment) => segment.trim()).filter((segment) => segment.length > 0);
296
+ if (segments.length === 0) return "";
297
+ const [firstSegment, ...restSegments] = segments;
298
+ const normalized = [firstSegment[0].toLowerCase() + firstSegment.slice(1), ...restSegments.map((segment) => capitalize(segment.toLowerCase()))].join("");
299
+ return /^[A-Za-z_$]/.test(normalized) ? normalized : `_${normalized}`;
300
+ }
255
301
  function capitalize(value) {
256
302
  if (value.length === 0) return value;
257
303
  return `${value[0].toUpperCase()}${value.slice(1)}`;
@@ -264,13 +310,23 @@ function getTemplateParameterNames(apiPath) {
264
310
  const matches = apiPath.match(/\{(\w+)\}/g) ?? [];
265
311
  return new Set(matches.map((match) => match.slice(1, -1)));
266
312
  }
267
- function resolveParameterTypeExpression(entry, context, location) {
313
+ function resolveParameterTypeReference(entry, context, location) {
268
314
  const effectiveParameters = getEffectiveParametersByLocation(entry, location);
269
- if (effectiveParameters.length === 0) return "never";
270
- const schemaTypeExpr = resolveAlias(context, effectiveParameters.map((parameter) => parameter.name), entry.operation.tags?.[0]);
271
- if (schemaTypeExpr) return schemaTypeExpr;
272
- if (hasSameParameterNames(getParametersByLocation(entry.operation, location), effectiveParameters)) return `operations['${entry.operationId}']['parameters']['${location}']`;
273
- return renderInlineParameterObject(effectiveParameters);
315
+ if (effectiveParameters.length === 0) return null;
316
+ const schemaTypeRef = resolveAlias(context, effectiveParameters.map((parameter) => parameter.name), entry.operation.tags?.[0]);
317
+ if (schemaTypeRef) return schemaTypeRef;
318
+ const rawParameters = getParametersByLocation(entry.operation, location);
319
+ const typeName = allocateOperationTypeName(context, entry.funcName, location === "path" ? "Path" : "Query");
320
+ if (hasSameParameterNames(rawParameters, effectiveParameters)) return {
321
+ aliasDefinitionExpr: `operations['${entry.operationId}']['parameters']['${location}']`,
322
+ sourceExpr: `operations['${entry.operationId}']['parameters']['${location}']`,
323
+ typeName
324
+ };
325
+ return {
326
+ aliasDefinitionExpr: renderInlineParameterObject(effectiveParameters),
327
+ sourceExpr: renderInlineParameterObject(effectiveParameters),
328
+ typeName
329
+ };
274
330
  }
275
331
  function normalizeOperation(entry, context, requestFunctionNames) {
276
332
  const successResponse = getSuccessResponseInfo(entry.operation);
@@ -278,7 +334,7 @@ function normalizeOperation(entry, context, requestFunctionNames) {
278
334
  const pathChannel = normalizeParameterChannel(entry, context, "path");
279
335
  const queryChannel = normalizeParameterChannel(entry, context, "query");
280
336
  const bodyChannel = normalizeBodyChannel(entry, context);
281
- const responseTypeExpr = resolveResponseTypeExpression(entry, context, successResponse);
337
+ const responseTypeRef = resolveResponseTypeReference(entry, context, successResponse);
282
338
  return {
283
339
  bodyChannel,
284
340
  builderAlias,
@@ -287,9 +343,9 @@ function normalizeOperation(entry, context, requestFunctionNames) {
287
343
  pathChannel,
288
344
  pathInvocationExpr: pathChannel.present ? `${builderAlias}(options.path)` : `${builderAlias}()`,
289
345
  queryChannel,
290
- requestFunction: responseTypeExpr ? requestFunctionNames.json : requestFunctionNames.void,
291
- responseTypeExpr,
292
- returnTypeExpr: responseTypeExpr ? `Promise<${responseTypeExpr}>` : "Promise<void>"
346
+ requestFunction: responseTypeRef ? requestFunctionNames.json : requestFunctionNames.void,
347
+ responseTypeRef,
348
+ returnTypeExpr: responseTypeRef ? `Promise<${responseTypeRef.typeName}>` : "Promise<void>"
293
349
  };
294
350
  }
295
351
  function normalizeParameterChannel(entry, context, location) {
@@ -297,40 +353,48 @@ function normalizeParameterChannel(entry, context, location) {
297
353
  if (parameters.length === 0) return {
298
354
  present: false,
299
355
  required: false,
300
- typeExpr: null
356
+ typeRef: null
301
357
  };
302
358
  return {
303
359
  present: true,
304
360
  required: hasRequiredChannel(parameters),
305
- typeExpr: resolveParameterTypeExpression(entry, context, location)
361
+ typeRef: resolveParameterTypeReference(entry, context, location)
306
362
  };
307
363
  }
308
364
  function normalizeBodyChannel(entry, context) {
309
- const typeExpr = resolveRequestBodyTypeExpression(entry, context);
310
- if (!typeExpr) return {
365
+ const typeRef = resolveRequestBodyTypeReference(entry, context);
366
+ if (!typeRef) return {
311
367
  present: false,
312
368
  required: false,
313
- typeExpr: null
369
+ typeRef: null
314
370
  };
315
371
  return {
316
372
  present: true,
317
373
  required: entry.operation.requestBody?.required !== false,
318
- typeExpr
374
+ typeRef
319
375
  };
320
376
  }
321
- function resolveRequestBodyTypeExpression(entry, context) {
377
+ function resolveRequestBodyTypeReference(entry, context) {
322
378
  const jsonBody = getJsonRequestBody(entry.operation);
323
379
  if (!jsonBody) return null;
324
- const schemaTypeExpr = resolveSchemaTypeExpression(context, jsonBody.schema);
325
- if (schemaTypeExpr) return schemaTypeExpr;
326
- return `operations['${entry.operationId}']['requestBody']['content']['application/json']`;
380
+ const schemaTypeRef = resolveSchemaTypeReference(context, jsonBody.schema);
381
+ if (schemaTypeRef) return schemaTypeRef;
382
+ return {
383
+ aliasDefinitionExpr: `operations['${entry.operationId}']['requestBody']['content']['application/json']`,
384
+ sourceExpr: `operations['${entry.operationId}']['requestBody']['content']['application/json']`,
385
+ typeName: allocateOperationTypeName(context, entry.funcName, "Request")
386
+ };
327
387
  }
328
- function resolveResponseTypeExpression(entry, context, successResponse) {
388
+ function resolveResponseTypeReference(entry, context, successResponse) {
329
389
  if (!successResponse.hasJsonBody) return null;
330
390
  const jsonContent = (entry.operation.responses?.[successResponse.statusKey])?.content?.["application/json"];
331
- const schemaTypeExpr = resolveSchemaTypeExpression(context, jsonContent?.schema);
332
- if (schemaTypeExpr) return schemaTypeExpr;
333
- return `operations['${entry.operationId}']['responses'][${formatStatusKey(successResponse.statusKey)}]['content']['application/json']`;
391
+ const schemaTypeRef = resolveSchemaTypeReference(context, jsonContent?.schema);
392
+ if (schemaTypeRef) return schemaTypeRef;
393
+ return {
394
+ aliasDefinitionExpr: `operations['${entry.operationId}']['responses'][${formatStatusKey(successResponse.statusKey)}]['content']['application/json']`,
395
+ sourceExpr: `operations['${entry.operationId}']['responses'][${formatStatusKey(successResponse.statusKey)}]['content']['application/json']`,
396
+ typeName: allocateOperationTypeName(context, entry.funcName, "Response")
397
+ };
334
398
  }
335
399
  function hasSameParameterNames(left, right) {
336
400
  if (left.length !== right.length) return false;
@@ -342,24 +406,28 @@ function resolveAlias(context, parameterNames, tag) {
342
406
  const key = [...parameterNames].sort().join(",");
343
407
  const candidates = context.schemaAliasIndex.get(key);
344
408
  if (!candidates || candidates.length === 0) return;
345
- if (candidates.length === 1) return createSchemaTypeExpression(candidates[0]);
409
+ if (candidates.length === 1) return createSchemaTypeReference(candidates[0]);
346
410
  if (tag) {
347
411
  const singularTag = tag.replace(/s$/, "");
348
412
  const prefix = `${singularTag[0]?.toUpperCase() ?? ""}${singularTag.slice(1)}`;
349
413
  const match = candidates.find((candidate) => candidate.startsWith(prefix));
350
- if (match) return createSchemaTypeExpression(match);
414
+ if (match) return createSchemaTypeReference(match);
351
415
  }
352
- return createSchemaTypeExpression(candidates[0]);
416
+ return createSchemaTypeReference(candidates[0]);
353
417
  }
354
- function resolveSchemaTypeExpression(context, schema) {
418
+ function resolveSchemaTypeReference(context, schema) {
355
419
  const ref = readSchemaRef(schema);
356
420
  if (!ref) return;
357
421
  const schemaName = ref.split("/").pop();
358
422
  if (!schemaName || !context.schemaNames.has(schemaName)) return;
359
- return createSchemaTypeExpression(schemaName);
423
+ return createSchemaTypeReference(schemaName);
360
424
  }
361
- function createSchemaTypeExpression(schemaName) {
362
- return `components['schemas']['${schemaName}']`;
425
+ function createSchemaTypeReference(schemaName) {
426
+ return {
427
+ aliasDefinitionExpr: null,
428
+ sourceExpr: `components['schemas']['${schemaName}']`,
429
+ typeName: schemaName
430
+ };
363
431
  }
364
432
  function readSchemaRef(schema) {
365
433
  if (!schema || typeof schema !== "object") return;
@@ -378,6 +446,40 @@ function renderPrimitiveSchemaType(schema) {
378
446
  if (Array.isArray(type)) return type.map((memberType) => mapPrimitiveType(memberType)).join(" | ");
379
447
  return mapPrimitiveType(type);
380
448
  }
449
+ function allocateOperationTypeName(context, funcName, suffix) {
450
+ const preferredName = `${capitalize(funcName)}${suffix}`;
451
+ if (!context.usedTypeNames.has(preferredName)) {
452
+ context.usedTypeNames.add(preferredName);
453
+ return preferredName;
454
+ }
455
+ let counter = 2;
456
+ let candidate = `${preferredName}_${counter}`;
457
+ while (context.usedTypeNames.has(candidate)) {
458
+ counter += 1;
459
+ candidate = `${preferredName}_${counter}`;
460
+ }
461
+ context.usedTypeNames.add(candidate);
462
+ return candidate;
463
+ }
464
+ function collectTypeAliases(operations) {
465
+ const aliases = /* @__PURE__ */ new Map();
466
+ for (const operation of operations) {
467
+ collectTypeAlias(aliases, operation.pathChannel.typeRef);
468
+ collectTypeAlias(aliases, operation.queryChannel.typeRef);
469
+ collectTypeAlias(aliases, operation.bodyChannel.typeRef);
470
+ collectTypeAlias(aliases, operation.responseTypeRef);
471
+ }
472
+ return [...aliases.entries()].map(([typeName, definitionExpr]) => ({
473
+ definitionExpr,
474
+ typeName
475
+ })).sort((left, right) => left.typeName.localeCompare(right.typeName));
476
+ }
477
+ function collectTypeAlias(aliases, typeRef) {
478
+ if (!typeRef || typeRef.aliasDefinitionExpr == null) return;
479
+ const existing = aliases.get(typeRef.typeName);
480
+ if (existing && existing !== typeRef.aliasDefinitionExpr) throw new Error(`Conflicting generated type alias "${typeRef.typeName}" with incompatible definitions.`);
481
+ aliases.set(typeRef.typeName, typeRef.aliasDefinitionExpr);
482
+ }
381
483
  function mapPrimitiveType(type) {
382
484
  switch (type) {
383
485
  case "integer":
@@ -409,50 +511,71 @@ function resolveHttpClientConfig(config) {
409
511
  };
410
512
  }
411
513
  const GENERATED_HEADER = ["// This file is auto-generated by vite-plugin-openapi-codegen.", "// Do not edit manually. Changes will be overwritten on next build."];
412
- async function generateApiTypes(source, outputDir) {
514
+ async function generateApiTypes(source, outputDir, useTypeAliases) {
413
515
  const { default: openapiTS, astToString } = await import("openapi-typescript");
414
- const contents = astToString(await openapiTS(source));
516
+ const contents = astToString(await openapiTS(source, useTypeAliases ? {
517
+ rootTypes: true,
518
+ rootTypesKeepCasing: true,
519
+ rootTypesNoSchemaPrefix: true
520
+ } : void 0));
415
521
  writeFileSync(resolve(outputDir, "api-types.d.ts"), `${GENERATED_HEADER.join("\n")}\n\n${contents}`);
416
522
  }
417
- function createApiEntries(normalizedOps) {
523
+ function createApiEntries(normalizedOps, useTypeAliases) {
418
524
  return normalizedOps.map((op) => ({
419
525
  funcName: op.entry.funcName,
420
526
  group: op.entry.group,
421
- pathTypeExpr: op.pathChannel.present ? op.pathChannel.typeExpr : null,
527
+ pathTypeExpr: op.pathChannel.typeRef == null ? null : useTypeAliases ? op.pathChannel.typeRef.typeName : op.pathChannel.typeRef.sourceExpr,
422
528
  strippedPath: op.entry.strippedPath
423
529
  }));
424
530
  }
425
- function createClientRenderModel(model) {
531
+ function createClientRenderModel(model, useTypeAliases) {
532
+ const resolveTypeExpr = (typeRef) => typeRef == null ? null : useTypeAliases ? typeRef.typeName : typeRef.sourceExpr;
533
+ const resolveReturnTypeExpr = (operation) => {
534
+ const responseTypeExpr = resolveTypeExpr(operation.responseTypeRef);
535
+ return responseTypeExpr ? `Promise<${responseTypeExpr}>` : "Promise<void>";
536
+ };
426
537
  return {
427
538
  needsSearchParamsHelper: model.needsSearchParamsHelper,
428
539
  operations: model.operations.map((operation) => ({
429
- bodyChannel: operation.bodyChannel,
540
+ bodyChannel: resolveChannel(operation.bodyChannel, useTypeAliases),
430
541
  builderAlias: operation.builderAlias,
431
542
  funcName: operation.entry.funcName,
432
543
  group: operation.entry.group,
433
544
  methodUpper: operation.entry.method.toUpperCase(),
434
545
  optionTypeName: operation.optionTypeName,
435
- pathChannel: operation.pathChannel,
546
+ pathChannel: resolveChannel(operation.pathChannel, useTypeAliases),
436
547
  pathInvocationExpr: operation.pathInvocationExpr,
437
- queryChannel: operation.queryChannel,
548
+ queryChannel: resolveChannel(operation.queryChannel, useTypeAliases),
438
549
  requestFunction: operation.requestFunction,
439
- responseTypeExpr: operation.responseTypeExpr,
440
- returnTypeExpr: operation.returnTypeExpr
550
+ responseTypeExpr: resolveTypeExpr(operation.responseTypeRef),
551
+ returnTypeExpr: resolveReturnTypeExpr(operation)
441
552
  }))
442
553
  };
443
554
  }
555
+ function resolveChannel(channel, useTypeAliases) {
556
+ if (!channel.typeRef) return channel;
557
+ return {
558
+ ...channel,
559
+ typeRef: {
560
+ ...channel.typeRef,
561
+ sourceExpr: useTypeAliases ? channel.typeRef.typeName : channel.typeRef.sourceExpr
562
+ }
563
+ };
564
+ }
444
565
  function renderGeneratedArtifacts(spec, options, preCollectedOperations) {
445
566
  const pathPrefix = options.pathPrefix ?? "/api/";
446
567
  const stripPrefix = options.stripPrefix ?? true;
447
568
  const httpClient = resolveHttpClientConfig(options.httpClient);
569
+ const useTypeAliases = options.typeAliases ?? false;
448
570
  const clientModel = buildClientRenderModelFromOperations(preCollectedOperations ?? collectOperations(spec, pathPrefix, stripPrefix), spec, {
449
571
  json: httpClient.jsonFunction,
450
572
  void: httpClient.voidFunction
451
573
  });
452
574
  if (clientModel.operations.length === 0) throw new Error(`No paths matching prefix "${pathPrefix}" found in openapi.json`);
453
575
  return {
454
- api: renderApiSource(createApiEntries(clientModel.operations), GENERATED_HEADER),
455
- client: renderClientSource(createClientRenderModel(clientModel), GENERATED_HEADER, httpClient)
576
+ api: renderApiSource(createApiEntries(clientModel.operations, useTypeAliases), GENERATED_HEADER),
577
+ client: renderClientSource(createClientRenderModel(clientModel, useTypeAliases), GENERATED_HEADER, httpClient),
578
+ ...useTypeAliases && clientModel.typeAliases.length > 0 ? { apiTypes: renderOperationTypeAliases(clientModel.typeAliases) } : {}
456
579
  };
457
580
  }
458
581
  async function loadOpenAPIInput(root, input) {
@@ -495,7 +618,8 @@ async function generate(root, options) {
495
618
  const operations = collectOperations(spec, options.pathPrefix ?? "/api/", options.stripPrefix ?? true);
496
619
  const artifacts = renderGeneratedArtifacts(spec, options, operations);
497
620
  warnOnParameterLocationMismatch(operations);
498
- await generateApiTypes(apiTypesSource, outputDir);
621
+ await generateApiTypes(apiTypesSource, outputDir, options.typeAliases ?? false);
622
+ if (artifacts.apiTypes) writeFileSync(resolve(outputDir, "api-types.d.ts"), artifacts.apiTypes, { flag: "a" });
499
623
  writeFileSync(resolve(outputDir, "api.ts"), artifacts.api);
500
624
  writeFileSync(resolve(outputDir, "client.ts"), artifacts.client);
501
625
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-openapi-codegen",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Vite plugin that generates typed API clients and route builders from OpenAPI specs",
5
5
  "keywords": [
6
6
  "api-client",