vite-plugin-openapi-codegen 1.1.3 → 1.2.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/README.md CHANGED
@@ -225,6 +225,8 @@ The generated client shape depends on the OpenAPI operation:
225
225
  - path parameters become `options.path`
226
226
  - query parameters become `options.query`
227
227
  - JSON request bodies become `options.body`
228
+ - `application/octet-stream` request bodies become `options.body` with `contentType: "application/octet-stream"`
229
+ - `multipart/form-data` request bodies become `options.body` as `FormData`
228
230
  - JSON responses become typed `Promise<T>`
229
231
  - empty responses use the configured void request function
230
232
 
package/dist/cli.mjs CHANGED
@@ -146,7 +146,11 @@ function createClientChannelField(key, channel) {
146
146
  function createClientFunctionDeclaration(operation) {
147
147
  const requestProperties = [ts.factory.createSpreadAssignment(ts.factory.createIdentifier("requestOptions")), ts.factory.createPropertyAssignment(ts.factory.createIdentifier("method"), ts.factory.createStringLiteral(operation.methodUpper))];
148
148
  if (operation.queryChannel.present) requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("searchParams"), ts.factory.createCallExpression(ts.factory.createIdentifier("buildSearchParams"), void 0, [ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "query")])));
149
- if (operation.bodyChannel.present) requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("json"), ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "body")));
149
+ if (operation.bodyChannel.present) {
150
+ if (operation.bodyContentType === "json") requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("json"), ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "body")));
151
+ else requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("body"), ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "body")));
152
+ if (operation.bodyContentType === "binary") requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("contentType"), ts.factory.createStringLiteral("application/octet-stream")));
153
+ }
150
154
  requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("signal"), ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "signal")));
151
155
  const requestCall = ts.factory.createCallExpression(ts.factory.createIdentifier(operation.requestFunction), operation.responseTypeExpr ? [createTypeNodeFromText(operation.responseTypeExpr)] : void 0, [parseExpression(operation.pathInvocationExpr), ts.factory.createObjectLiteralExpression(requestProperties, true)]);
152
156
  return ts.factory.createFunctionDeclaration([createExportModifier()], void 0, ts.factory.createIdentifier(operation.funcName), void 0, [ts.factory.createParameterDeclaration(void 0, void 0, ts.factory.createIdentifier("options"), void 0, ts.factory.createTypeReferenceNode(operation.optionTypeName)), ts.factory.createParameterDeclaration(void 0, void 0, ts.factory.createIdentifier("requestOptions"), void 0, ts.factory.createTypeReferenceNode("RuntimeRequestOptions"), ts.factory.createObjectLiteralExpression())], createTypeNodeFromText(operation.returnTypeExpr), ts.factory.createBlock([ts.factory.createReturnStatement(requestCall)], true));
@@ -242,13 +246,6 @@ function getParametersByLocation(operation, location) {
242
246
  function hasRequiredChannel(parameters) {
243
247
  return parameters.some((parameter) => parameter.required);
244
248
  }
245
- function getJsonRequestBody(operation) {
246
- const requestBody = operation.requestBody;
247
- if (!requestBody) return void 0;
248
- const jsonBody = requestBody.content?.["application/json"];
249
- if (!jsonBody) throw new Error(`Operation "${operation.operationId ?? "unknown"}" has a requestBody but no application/json content`);
250
- return jsonBody;
251
- }
252
249
  function getSuccessResponseInfo(operation) {
253
250
  const successResponses = Object.entries(operation.responses ?? {}).filter(([statusKey]) => isSuccessStatus(statusKey)).sort(([left], [right]) => Number(left) - Number(right));
254
251
  if (successResponses.length === 0) throw new Error(`Operation "${operation.operationId ?? "unknown"}" has no 2xx success response`);
@@ -335,10 +332,11 @@ function normalizeOperation(entry, context, requestFunctionNames) {
335
332
  const builderAlias = getBuilderAlias(entry.funcName);
336
333
  const pathChannel = normalizeParameterChannel(entry, context, "path");
337
334
  const queryChannel = normalizeParameterChannel(entry, context, "query");
338
- const bodyChannel = normalizeBodyChannel(entry, context);
335
+ const bodyResolution = normalizeBodyChannel(entry, context);
339
336
  const responseTypeRef = resolveResponseTypeReference(entry, context, successResponse);
340
337
  return {
341
- bodyChannel,
338
+ bodyChannel: bodyResolution.channel,
339
+ bodyContentType: bodyResolution.contentType,
342
340
  builderAlias,
343
341
  entry,
344
342
  optionTypeName: getClientOptionTypeName(entry.funcName),
@@ -364,7 +362,35 @@ function normalizeParameterChannel(entry, context, location) {
364
362
  };
365
363
  }
366
364
  function normalizeBodyChannel(entry, context) {
367
- const typeRef = resolveRequestBodyTypeReference(entry, context);
365
+ const requestBody = entry.operation.requestBody;
366
+ if (!requestBody) return {
367
+ channel: {
368
+ present: false,
369
+ required: false,
370
+ typeRef: null
371
+ },
372
+ contentType: null
373
+ };
374
+ const content = requestBody.content ?? {};
375
+ const jsonBody = content["application/json"];
376
+ if (jsonBody) return {
377
+ channel: createRequestBodyChannel(entry, context, jsonBody, "json"),
378
+ contentType: "json"
379
+ };
380
+ const binaryBody = content["application/octet-stream"];
381
+ if (binaryBody) return {
382
+ channel: createRequestBodyChannel(entry, context, binaryBody, "binary"),
383
+ contentType: "binary"
384
+ };
385
+ const multipartBody = content["multipart/form-data"];
386
+ if (multipartBody) return {
387
+ channel: createRequestBodyChannel(entry, context, multipartBody, "formData"),
388
+ contentType: "formData"
389
+ };
390
+ throw new Error(`Operation "${entry.operationId ?? "unknown"}" has a requestBody but no supported content type`);
391
+ }
392
+ function createRequestBodyChannel(entry, context, body, kind) {
393
+ const typeRef = resolveRequestBodyTypeReference(entry, context, body, kind);
368
394
  if (!typeRef) return {
369
395
  present: false,
370
396
  required: false,
@@ -376,10 +402,18 @@ function normalizeBodyChannel(entry, context) {
376
402
  typeRef
377
403
  };
378
404
  }
379
- function resolveRequestBodyTypeReference(entry, context) {
380
- const jsonBody = getJsonRequestBody(entry.operation);
381
- if (!jsonBody) return null;
382
- const schemaTypeRef = resolveSchemaTypeReference(context, jsonBody.schema);
405
+ function resolveRequestBodyTypeReference(entry, context, body, kind) {
406
+ if (kind === "formData") return {
407
+ aliasDefinitionExpr: "FormData",
408
+ sourceExpr: "FormData",
409
+ typeName: allocateOperationTypeName(context, entry.funcName, "Request")
410
+ };
411
+ if (kind === "binary") return {
412
+ aliasDefinitionExpr: "Blob | File | ArrayBuffer | string",
413
+ sourceExpr: "Blob | File | ArrayBuffer | string",
414
+ typeName: allocateOperationTypeName(context, entry.funcName, "Request")
415
+ };
416
+ const schemaTypeRef = resolveSchemaTypeReference(context, body.schema);
383
417
  if (schemaTypeRef) return schemaTypeRef;
384
418
  return {
385
419
  aliasDefinitionExpr: `operations['${entry.operationId}']['requestBody']['content']['application/json']`,
@@ -526,6 +560,7 @@ function createApiEntries(normalizedOps, useTypeAliases) {
526
560
  return normalizedOps.map((op) => ({
527
561
  funcName: op.entry.funcName,
528
562
  group: op.entry.group,
563
+ bodyContentType: op.bodyContentType,
529
564
  pathTypeExpr: op.pathChannel.typeRef == null ? null : useTypeAliases ? op.pathChannel.typeRef.typeName : op.pathChannel.typeRef.sourceExpr,
530
565
  strippedPath: op.entry.strippedPath
531
566
  }));
@@ -540,6 +575,7 @@ function createClientRenderModel(model, useTypeAliases) {
540
575
  needsSearchParamsHelper: model.needsSearchParamsHelper,
541
576
  operations: model.operations.map((operation) => ({
542
577
  bodyChannel: resolveChannel(operation.bodyChannel, useTypeAliases),
578
+ bodyContentType: operation.bodyContentType,
543
579
  builderAlias: operation.builderAlias,
544
580
  funcName: operation.entry.funcName,
545
581
  group: operation.entry.group,
package/dist/index.mjs CHANGED
@@ -144,7 +144,11 @@ function createClientChannelField(key, channel) {
144
144
  function createClientFunctionDeclaration(operation) {
145
145
  const requestProperties = [ts.factory.createSpreadAssignment(ts.factory.createIdentifier("requestOptions")), ts.factory.createPropertyAssignment(ts.factory.createIdentifier("method"), ts.factory.createStringLiteral(operation.methodUpper))];
146
146
  if (operation.queryChannel.present) requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("searchParams"), ts.factory.createCallExpression(ts.factory.createIdentifier("buildSearchParams"), void 0, [ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "query")])));
147
- if (operation.bodyChannel.present) requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("json"), ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "body")));
147
+ if (operation.bodyChannel.present) {
148
+ if (operation.bodyContentType === "json") requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("json"), ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "body")));
149
+ else requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("body"), ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "body")));
150
+ if (operation.bodyContentType === "binary") requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("contentType"), ts.factory.createStringLiteral("application/octet-stream")));
151
+ }
148
152
  requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("signal"), ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "signal")));
149
153
  const requestCall = ts.factory.createCallExpression(ts.factory.createIdentifier(operation.requestFunction), operation.responseTypeExpr ? [createTypeNodeFromText(operation.responseTypeExpr)] : void 0, [parseExpression(operation.pathInvocationExpr), ts.factory.createObjectLiteralExpression(requestProperties, true)]);
150
154
  return ts.factory.createFunctionDeclaration([createExportModifier()], void 0, ts.factory.createIdentifier(operation.funcName), void 0, [ts.factory.createParameterDeclaration(void 0, void 0, ts.factory.createIdentifier("options"), void 0, ts.factory.createTypeReferenceNode(operation.optionTypeName)), ts.factory.createParameterDeclaration(void 0, void 0, ts.factory.createIdentifier("requestOptions"), void 0, ts.factory.createTypeReferenceNode("RuntimeRequestOptions"), ts.factory.createObjectLiteralExpression())], createTypeNodeFromText(operation.returnTypeExpr), ts.factory.createBlock([ts.factory.createReturnStatement(requestCall)], true));
@@ -240,13 +244,6 @@ function getParametersByLocation(operation, location) {
240
244
  function hasRequiredChannel(parameters) {
241
245
  return parameters.some((parameter) => parameter.required);
242
246
  }
243
- function getJsonRequestBody(operation) {
244
- const requestBody = operation.requestBody;
245
- if (!requestBody) return void 0;
246
- const jsonBody = requestBody.content?.["application/json"];
247
- if (!jsonBody) throw new Error(`Operation "${operation.operationId ?? "unknown"}" has a requestBody but no application/json content`);
248
- return jsonBody;
249
- }
250
247
  function getSuccessResponseInfo(operation) {
251
248
  const successResponses = Object.entries(operation.responses ?? {}).filter(([statusKey]) => isSuccessStatus(statusKey)).sort(([left], [right]) => Number(left) - Number(right));
252
249
  if (successResponses.length === 0) throw new Error(`Operation "${operation.operationId ?? "unknown"}" has no 2xx success response`);
@@ -333,10 +330,11 @@ function normalizeOperation(entry, context, requestFunctionNames) {
333
330
  const builderAlias = getBuilderAlias(entry.funcName);
334
331
  const pathChannel = normalizeParameterChannel(entry, context, "path");
335
332
  const queryChannel = normalizeParameterChannel(entry, context, "query");
336
- const bodyChannel = normalizeBodyChannel(entry, context);
333
+ const bodyResolution = normalizeBodyChannel(entry, context);
337
334
  const responseTypeRef = resolveResponseTypeReference(entry, context, successResponse);
338
335
  return {
339
- bodyChannel,
336
+ bodyChannel: bodyResolution.channel,
337
+ bodyContentType: bodyResolution.contentType,
340
338
  builderAlias,
341
339
  entry,
342
340
  optionTypeName: getClientOptionTypeName(entry.funcName),
@@ -362,7 +360,35 @@ function normalizeParameterChannel(entry, context, location) {
362
360
  };
363
361
  }
364
362
  function normalizeBodyChannel(entry, context) {
365
- const typeRef = resolveRequestBodyTypeReference(entry, context);
363
+ const requestBody = entry.operation.requestBody;
364
+ if (!requestBody) return {
365
+ channel: {
366
+ present: false,
367
+ required: false,
368
+ typeRef: null
369
+ },
370
+ contentType: null
371
+ };
372
+ const content = requestBody.content ?? {};
373
+ const jsonBody = content["application/json"];
374
+ if (jsonBody) return {
375
+ channel: createRequestBodyChannel(entry, context, jsonBody, "json"),
376
+ contentType: "json"
377
+ };
378
+ const binaryBody = content["application/octet-stream"];
379
+ if (binaryBody) return {
380
+ channel: createRequestBodyChannel(entry, context, binaryBody, "binary"),
381
+ contentType: "binary"
382
+ };
383
+ const multipartBody = content["multipart/form-data"];
384
+ if (multipartBody) return {
385
+ channel: createRequestBodyChannel(entry, context, multipartBody, "formData"),
386
+ contentType: "formData"
387
+ };
388
+ throw new Error(`Operation "${entry.operationId ?? "unknown"}" has a requestBody but no supported content type`);
389
+ }
390
+ function createRequestBodyChannel(entry, context, body, kind) {
391
+ const typeRef = resolveRequestBodyTypeReference(entry, context, body, kind);
366
392
  if (!typeRef) return {
367
393
  present: false,
368
394
  required: false,
@@ -374,10 +400,18 @@ function normalizeBodyChannel(entry, context) {
374
400
  typeRef
375
401
  };
376
402
  }
377
- function resolveRequestBodyTypeReference(entry, context) {
378
- const jsonBody = getJsonRequestBody(entry.operation);
379
- if (!jsonBody) return null;
380
- const schemaTypeRef = resolveSchemaTypeReference(context, jsonBody.schema);
403
+ function resolveRequestBodyTypeReference(entry, context, body, kind) {
404
+ if (kind === "formData") return {
405
+ aliasDefinitionExpr: "FormData",
406
+ sourceExpr: "FormData",
407
+ typeName: allocateOperationTypeName(context, entry.funcName, "Request")
408
+ };
409
+ if (kind === "binary") return {
410
+ aliasDefinitionExpr: "Blob | File | ArrayBuffer | string",
411
+ sourceExpr: "Blob | File | ArrayBuffer | string",
412
+ typeName: allocateOperationTypeName(context, entry.funcName, "Request")
413
+ };
414
+ const schemaTypeRef = resolveSchemaTypeReference(context, body.schema);
381
415
  if (schemaTypeRef) return schemaTypeRef;
382
416
  return {
383
417
  aliasDefinitionExpr: `operations['${entry.operationId}']['requestBody']['content']['application/json']`,
@@ -524,6 +558,7 @@ function createApiEntries(normalizedOps, useTypeAliases) {
524
558
  return normalizedOps.map((op) => ({
525
559
  funcName: op.entry.funcName,
526
560
  group: op.entry.group,
561
+ bodyContentType: op.bodyContentType,
527
562
  pathTypeExpr: op.pathChannel.typeRef == null ? null : useTypeAliases ? op.pathChannel.typeRef.typeName : op.pathChannel.typeRef.sourceExpr,
528
563
  strippedPath: op.entry.strippedPath
529
564
  }));
@@ -538,6 +573,7 @@ function createClientRenderModel(model, useTypeAliases) {
538
573
  needsSearchParamsHelper: model.needsSearchParamsHelper,
539
574
  operations: model.operations.map((operation) => ({
540
575
  bodyChannel: resolveChannel(operation.bodyChannel, useTypeAliases),
576
+ bodyContentType: operation.bodyContentType,
541
577
  builderAlias: operation.builderAlias,
542
578
  funcName: operation.entry.funcName,
543
579
  group: operation.entry.group,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-openapi-codegen",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "description": "Vite plugin that generates typed API clients and route builders from OpenAPI specs",
5
5
  "keywords": [
6
6
  "api-client",