vite-plugin-openapi-codegen 1.1.3 → 1.2.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/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,11 +146,21 @@ 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
+ const bodyPropertyName = operation.bodyContentType === "json" ? "json" : "body";
151
+ const bodyAssignment = ts.factory.createObjectLiteralExpression([ts.factory.createPropertyAssignment(ts.factory.createIdentifier(bodyPropertyName), ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "body"))], false);
152
+ if (operation.bodyContentType === "json") requestProperties.push(createBodyRequestProperty(operation.bodyChannel, bodyAssignment));
153
+ else requestProperties.push(createBodyRequestProperty(operation.bodyChannel, bodyAssignment));
154
+ if (operation.bodyContentType === "binary") requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("contentType"), ts.factory.createStringLiteral("application/octet-stream")));
155
+ }
150
156
  requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("signal"), ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "signal")));
151
157
  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
158
  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));
153
159
  }
160
+ function createBodyRequestProperty(bodyChannel, bodyAssignment) {
161
+ if (bodyChannel.required) return bodyAssignment.properties[0];
162
+ return ts.factory.createSpreadAssignment(ts.factory.createParenthesizedExpression(ts.factory.createConditionalExpression(ts.factory.createBinaryExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "body"), ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), ts.factory.createIdentifier("undefined")), ts.factory.createToken(ts.SyntaxKind.QuestionToken), ts.factory.createObjectLiteralExpression([], false), ts.factory.createToken(ts.SyntaxKind.ColonToken), bodyAssignment)));
163
+ }
154
164
  function createBuildSearchParamsFunction() {
155
165
  return ts.factory.createFunctionDeclaration(void 0, void 0, ts.factory.createIdentifier("buildSearchParams"), void 0, [ts.factory.createParameterDeclaration(void 0, void 0, ts.factory.createIdentifier("query"), void 0, createTypeNodeFromText("Record<string, unknown> | undefined"))], createTypeNodeFromText("URLSearchParams | undefined"), ts.factory.createBlock([
156
166
  ts.factory.createIfStatement(ts.factory.createBinaryExpression(ts.factory.createIdentifier("query"), ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), ts.factory.createIdentifier("undefined")), ts.factory.createReturnStatement(ts.factory.createIdentifier("undefined"))),
@@ -242,13 +252,6 @@ function getParametersByLocation(operation, location) {
242
252
  function hasRequiredChannel(parameters) {
243
253
  return parameters.some((parameter) => parameter.required);
244
254
  }
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
255
  function getSuccessResponseInfo(operation) {
253
256
  const successResponses = Object.entries(operation.responses ?? {}).filter(([statusKey]) => isSuccessStatus(statusKey)).sort(([left], [right]) => Number(left) - Number(right));
254
257
  if (successResponses.length === 0) throw new Error(`Operation "${operation.operationId ?? "unknown"}" has no 2xx success response`);
@@ -335,10 +338,11 @@ function normalizeOperation(entry, context, requestFunctionNames) {
335
338
  const builderAlias = getBuilderAlias(entry.funcName);
336
339
  const pathChannel = normalizeParameterChannel(entry, context, "path");
337
340
  const queryChannel = normalizeParameterChannel(entry, context, "query");
338
- const bodyChannel = normalizeBodyChannel(entry, context);
341
+ const bodyResolution = normalizeBodyChannel(entry, context);
339
342
  const responseTypeRef = resolveResponseTypeReference(entry, context, successResponse);
340
343
  return {
341
- bodyChannel,
344
+ bodyChannel: bodyResolution.channel,
345
+ bodyContentType: bodyResolution.contentType,
342
346
  builderAlias,
343
347
  entry,
344
348
  optionTypeName: getClientOptionTypeName(entry.funcName),
@@ -364,7 +368,35 @@ function normalizeParameterChannel(entry, context, location) {
364
368
  };
365
369
  }
366
370
  function normalizeBodyChannel(entry, context) {
367
- const typeRef = resolveRequestBodyTypeReference(entry, context);
371
+ const requestBody = entry.operation.requestBody;
372
+ if (!requestBody) return {
373
+ channel: {
374
+ present: false,
375
+ required: false,
376
+ typeRef: null
377
+ },
378
+ contentType: null
379
+ };
380
+ const content = requestBody.content ?? {};
381
+ const jsonBody = content["application/json"];
382
+ if (jsonBody) return {
383
+ channel: createRequestBodyChannel(entry, context, jsonBody, "json"),
384
+ contentType: "json"
385
+ };
386
+ const binaryBody = content["application/octet-stream"];
387
+ if (binaryBody) return {
388
+ channel: createRequestBodyChannel(entry, context, binaryBody, "binary"),
389
+ contentType: "binary"
390
+ };
391
+ const multipartBody = content["multipart/form-data"];
392
+ if (multipartBody) return {
393
+ channel: createRequestBodyChannel(entry, context, multipartBody, "formData"),
394
+ contentType: "formData"
395
+ };
396
+ throw new Error(`Operation "${entry.operationId ?? "unknown"}" has a requestBody but no supported content type`);
397
+ }
398
+ function createRequestBodyChannel(entry, context, body, kind) {
399
+ const typeRef = resolveRequestBodyTypeReference(entry, context, body, kind);
368
400
  if (!typeRef) return {
369
401
  present: false,
370
402
  required: false,
@@ -372,14 +404,22 @@ function normalizeBodyChannel(entry, context) {
372
404
  };
373
405
  return {
374
406
  present: true,
375
- required: entry.operation.requestBody?.required !== false,
407
+ required: entry.operation.requestBody?.required === true,
376
408
  typeRef
377
409
  };
378
410
  }
379
- function resolveRequestBodyTypeReference(entry, context) {
380
- const jsonBody = getJsonRequestBody(entry.operation);
381
- if (!jsonBody) return null;
382
- const schemaTypeRef = resolveSchemaTypeReference(context, jsonBody.schema);
411
+ function resolveRequestBodyTypeReference(entry, context, body, kind) {
412
+ if (kind === "formData") return {
413
+ aliasDefinitionExpr: "FormData",
414
+ sourceExpr: "FormData",
415
+ typeName: allocateOperationTypeName(context, entry.funcName, "Request")
416
+ };
417
+ if (kind === "binary") return {
418
+ aliasDefinitionExpr: "Blob | File | ArrayBuffer | string",
419
+ sourceExpr: "Blob | File | ArrayBuffer | string",
420
+ typeName: allocateOperationTypeName(context, entry.funcName, "Request")
421
+ };
422
+ const schemaTypeRef = resolveSchemaTypeReference(context, body.schema);
383
423
  if (schemaTypeRef) return schemaTypeRef;
384
424
  return {
385
425
  aliasDefinitionExpr: `operations['${entry.operationId}']['requestBody']['content']['application/json']`,
@@ -526,6 +566,7 @@ function createApiEntries(normalizedOps, useTypeAliases) {
526
566
  return normalizedOps.map((op) => ({
527
567
  funcName: op.entry.funcName,
528
568
  group: op.entry.group,
569
+ bodyContentType: op.bodyContentType,
529
570
  pathTypeExpr: op.pathChannel.typeRef == null ? null : useTypeAliases ? op.pathChannel.typeRef.typeName : op.pathChannel.typeRef.sourceExpr,
530
571
  strippedPath: op.entry.strippedPath
531
572
  }));
@@ -540,6 +581,7 @@ function createClientRenderModel(model, useTypeAliases) {
540
581
  needsSearchParamsHelper: model.needsSearchParamsHelper,
541
582
  operations: model.operations.map((operation) => ({
542
583
  bodyChannel: resolveChannel(operation.bodyChannel, useTypeAliases),
584
+ bodyContentType: operation.bodyContentType,
543
585
  builderAlias: operation.builderAlias,
544
586
  funcName: operation.entry.funcName,
545
587
  group: operation.entry.group,
package/dist/index.d.mts CHANGED
@@ -33,6 +33,7 @@ interface OpenAPIOperation {
33
33
  type OpenAPIPathItem = Partial<Record<HttpMethod, OpenAPIOperation>>;
34
34
  interface OpenAPISchema {
35
35
  properties?: Record<string, unknown>;
36
+ required?: string[];
36
37
  type?: string;
37
38
  }
38
39
  interface OpenAPISpec {
package/dist/index.mjs CHANGED
@@ -144,11 +144,21 @@ 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
+ const bodyPropertyName = operation.bodyContentType === "json" ? "json" : "body";
149
+ const bodyAssignment = ts.factory.createObjectLiteralExpression([ts.factory.createPropertyAssignment(ts.factory.createIdentifier(bodyPropertyName), ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "body"))], false);
150
+ if (operation.bodyContentType === "json") requestProperties.push(createBodyRequestProperty(operation.bodyChannel, bodyAssignment));
151
+ else requestProperties.push(createBodyRequestProperty(operation.bodyChannel, bodyAssignment));
152
+ if (operation.bodyContentType === "binary") requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("contentType"), ts.factory.createStringLiteral("application/octet-stream")));
153
+ }
148
154
  requestProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier("signal"), ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "signal")));
149
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)]);
150
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));
151
157
  }
158
+ function createBodyRequestProperty(bodyChannel, bodyAssignment) {
159
+ if (bodyChannel.required) return bodyAssignment.properties[0];
160
+ return ts.factory.createSpreadAssignment(ts.factory.createParenthesizedExpression(ts.factory.createConditionalExpression(ts.factory.createBinaryExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("options"), "body"), ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), ts.factory.createIdentifier("undefined")), ts.factory.createToken(ts.SyntaxKind.QuestionToken), ts.factory.createObjectLiteralExpression([], false), ts.factory.createToken(ts.SyntaxKind.ColonToken), bodyAssignment)));
161
+ }
152
162
  function createBuildSearchParamsFunction() {
153
163
  return ts.factory.createFunctionDeclaration(void 0, void 0, ts.factory.createIdentifier("buildSearchParams"), void 0, [ts.factory.createParameterDeclaration(void 0, void 0, ts.factory.createIdentifier("query"), void 0, createTypeNodeFromText("Record<string, unknown> | undefined"))], createTypeNodeFromText("URLSearchParams | undefined"), ts.factory.createBlock([
154
164
  ts.factory.createIfStatement(ts.factory.createBinaryExpression(ts.factory.createIdentifier("query"), ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), ts.factory.createIdentifier("undefined")), ts.factory.createReturnStatement(ts.factory.createIdentifier("undefined"))),
@@ -240,13 +250,6 @@ function getParametersByLocation(operation, location) {
240
250
  function hasRequiredChannel(parameters) {
241
251
  return parameters.some((parameter) => parameter.required);
242
252
  }
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
253
  function getSuccessResponseInfo(operation) {
251
254
  const successResponses = Object.entries(operation.responses ?? {}).filter(([statusKey]) => isSuccessStatus(statusKey)).sort(([left], [right]) => Number(left) - Number(right));
252
255
  if (successResponses.length === 0) throw new Error(`Operation "${operation.operationId ?? "unknown"}" has no 2xx success response`);
@@ -333,10 +336,11 @@ function normalizeOperation(entry, context, requestFunctionNames) {
333
336
  const builderAlias = getBuilderAlias(entry.funcName);
334
337
  const pathChannel = normalizeParameterChannel(entry, context, "path");
335
338
  const queryChannel = normalizeParameterChannel(entry, context, "query");
336
- const bodyChannel = normalizeBodyChannel(entry, context);
339
+ const bodyResolution = normalizeBodyChannel(entry, context);
337
340
  const responseTypeRef = resolveResponseTypeReference(entry, context, successResponse);
338
341
  return {
339
- bodyChannel,
342
+ bodyChannel: bodyResolution.channel,
343
+ bodyContentType: bodyResolution.contentType,
340
344
  builderAlias,
341
345
  entry,
342
346
  optionTypeName: getClientOptionTypeName(entry.funcName),
@@ -362,7 +366,35 @@ function normalizeParameterChannel(entry, context, location) {
362
366
  };
363
367
  }
364
368
  function normalizeBodyChannel(entry, context) {
365
- const typeRef = resolveRequestBodyTypeReference(entry, context);
369
+ const requestBody = entry.operation.requestBody;
370
+ if (!requestBody) return {
371
+ channel: {
372
+ present: false,
373
+ required: false,
374
+ typeRef: null
375
+ },
376
+ contentType: null
377
+ };
378
+ const content = requestBody.content ?? {};
379
+ const jsonBody = content["application/json"];
380
+ if (jsonBody) return {
381
+ channel: createRequestBodyChannel(entry, context, jsonBody, "json"),
382
+ contentType: "json"
383
+ };
384
+ const binaryBody = content["application/octet-stream"];
385
+ if (binaryBody) return {
386
+ channel: createRequestBodyChannel(entry, context, binaryBody, "binary"),
387
+ contentType: "binary"
388
+ };
389
+ const multipartBody = content["multipart/form-data"];
390
+ if (multipartBody) return {
391
+ channel: createRequestBodyChannel(entry, context, multipartBody, "formData"),
392
+ contentType: "formData"
393
+ };
394
+ throw new Error(`Operation "${entry.operationId ?? "unknown"}" has a requestBody but no supported content type`);
395
+ }
396
+ function createRequestBodyChannel(entry, context, body, kind) {
397
+ const typeRef = resolveRequestBodyTypeReference(entry, context, body, kind);
366
398
  if (!typeRef) return {
367
399
  present: false,
368
400
  required: false,
@@ -370,14 +402,22 @@ function normalizeBodyChannel(entry, context) {
370
402
  };
371
403
  return {
372
404
  present: true,
373
- required: entry.operation.requestBody?.required !== false,
405
+ required: entry.operation.requestBody?.required === true,
374
406
  typeRef
375
407
  };
376
408
  }
377
- function resolveRequestBodyTypeReference(entry, context) {
378
- const jsonBody = getJsonRequestBody(entry.operation);
379
- if (!jsonBody) return null;
380
- const schemaTypeRef = resolveSchemaTypeReference(context, jsonBody.schema);
409
+ function resolveRequestBodyTypeReference(entry, context, body, kind) {
410
+ if (kind === "formData") return {
411
+ aliasDefinitionExpr: "FormData",
412
+ sourceExpr: "FormData",
413
+ typeName: allocateOperationTypeName(context, entry.funcName, "Request")
414
+ };
415
+ if (kind === "binary") return {
416
+ aliasDefinitionExpr: "Blob | File | ArrayBuffer | string",
417
+ sourceExpr: "Blob | File | ArrayBuffer | string",
418
+ typeName: allocateOperationTypeName(context, entry.funcName, "Request")
419
+ };
420
+ const schemaTypeRef = resolveSchemaTypeReference(context, body.schema);
381
421
  if (schemaTypeRef) return schemaTypeRef;
382
422
  return {
383
423
  aliasDefinitionExpr: `operations['${entry.operationId}']['requestBody']['content']['application/json']`,
@@ -524,6 +564,7 @@ function createApiEntries(normalizedOps, useTypeAliases) {
524
564
  return normalizedOps.map((op) => ({
525
565
  funcName: op.entry.funcName,
526
566
  group: op.entry.group,
567
+ bodyContentType: op.bodyContentType,
527
568
  pathTypeExpr: op.pathChannel.typeRef == null ? null : useTypeAliases ? op.pathChannel.typeRef.typeName : op.pathChannel.typeRef.sourceExpr,
528
569
  strippedPath: op.entry.strippedPath
529
570
  }));
@@ -538,6 +579,7 @@ function createClientRenderModel(model, useTypeAliases) {
538
579
  needsSearchParamsHelper: model.needsSearchParamsHelper,
539
580
  operations: model.operations.map((operation) => ({
540
581
  bodyChannel: resolveChannel(operation.bodyChannel, useTypeAliases),
582
+ bodyContentType: operation.bodyContentType,
541
583
  builderAlias: operation.builderAlias,
542
584
  funcName: operation.entry.funcName,
543
585
  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.1",
4
4
  "description": "Vite plugin that generates typed API clients and route builders from OpenAPI specs",
5
5
  "keywords": [
6
6
  "api-client",