diff --git a/.changepacks/changepack_log_KSLv5lwwQVCRgMsInMFuh.json b/.changepacks/changepack_log_KSLv5lwwQVCRgMsInMFuh.json new file mode 100644 index 0000000..831370f --- /dev/null +++ b/.changepacks/changepack_log_KSLv5lwwQVCRgMsInMFuh.json @@ -0,0 +1 @@ +{"changes":{"packages/generator/package.json":"Patch"},"note":"Fix Formdata issue","date":"2026-02-18T18:18:07.532985400Z"} \ No newline at end of file diff --git a/examples/next/app/page.tsx b/examples/next/app/page.tsx index 8aa1098..223e125 100644 --- a/examples/next/app/page.tsx +++ b/examples/next/app/page.tsx @@ -97,6 +97,10 @@ export default function Home() { tags: 'tag1,tag2', }, }) + + api3.POST('/typed-form', { + body: new FormData(), + }) }, [mutateAsync]) return ( diff --git a/packages/generator/src/__tests__/__snapshots__/generate-interface.test.ts.snap b/packages/generator/src/__tests__/__snapshots__/generate-interface.test.ts.snap index c017752..54ab29a 100644 --- a/packages/generator/src/__tests__/__snapshots__/generate-interface.test.ts.snap +++ b/packages/generator/src/__tests__/__snapshots__/generate-interface.test.ts.snap @@ -1834,13 +1834,13 @@ declare module "@devup-api/fetch" { interface DevupPostApiStruct { 'openapi.json': { '/form': { - body: DevupObject<'request', 'openapi.json'>['SubscribeRequest']; + body: DevupObject<'request', 'openapi.json'>['SubscribeRequest'] | FormData; response?: { ok?: boolean; }; }; subscribe: { - body: DevupObject<'request', 'openapi.json'>['SubscribeRequest']; + body: DevupObject<'request', 'openapi.json'>['SubscribeRequest'] | FormData; response?: { ok?: boolean; }; @@ -1875,19 +1875,19 @@ declare module "@devup-api/fetch" { interface DevupPostApiStruct { 'openapi.json': { '/form/contact': { - body?: { - name?: string; - email?: string; - message?: string; - }; + body: { + name?: string; + email?: string; + message?: string; +} | FormData; response?: {}; }; contact: { - body?: { - name?: string; - email?: string; - message?: string; - }; + body: { + name?: string; + email?: string; + message?: string; +} | FormData; response?: {}; }; } @@ -1913,13 +1913,13 @@ declare module "@devup-api/fetch" { interface DevupPostApiStruct { 'openapi.json': { '/typed-form': { - body: DevupObject<'request', 'openapi.json'>['CreateFileUploadRequest']; + body: DevupObject<'request', 'openapi.json'>['CreateFileUploadRequest'] | FormData; response?: { id?: string; }; }; createFileUpload: { - body: DevupObject<'request', 'openapi.json'>['CreateFileUploadRequest']; + body: DevupObject<'request', 'openapi.json'>['CreateFileUploadRequest'] | FormData; response?: { id?: string; }; @@ -1993,14 +1993,14 @@ declare module "@devup-api/fetch" { params: { id: string; }; - body: DevupObject<'request', 'openapi.json'>['UpdateFileUploadRequest']; + body: DevupObject<'request', 'openapi.json'>['UpdateFileUploadRequest'] | FormData; response?: {}; }; updateFileUpload: { params: { id: string; }; - body: DevupObject<'request', 'openapi.json'>['UpdateFileUploadRequest']; + body: DevupObject<'request', 'openapi.json'>['UpdateFileUploadRequest'] | FormData; response?: {}; }; } @@ -2012,14 +2012,14 @@ declare module "@devup-api/fetch" { params: { id: string; }; - body: DevupObject<'request', 'openapi.json'>['PatchFileUploadRequest']; + body: DevupObject<'request', 'openapi.json'>['PatchFileUploadRequest'] | FormData; response?: {}; }; patchFileUpload: { params: { id: string; }; - body: DevupObject<'request', 'openapi.json'>['PatchFileUploadRequest']; + body: DevupObject<'request', 'openapi.json'>['PatchFileUploadRequest'] | FormData; response?: {}; }; } @@ -2064,11 +2064,11 @@ declare module "@devup-api/fetch" { response?: {}; }; '/form': { - body: DevupObject<'request', 'openapi.json'>['SubscribeRequest']; + body: DevupObject<'request', 'openapi.json'>['SubscribeRequest'] | FormData; response?: {}; }; subscribe: { - body: DevupObject<'request', 'openapi.json'>['SubscribeRequest']; + body: DevupObject<'request', 'openapi.json'>['SubscribeRequest'] | FormData; response?: {}; }; '/form/upload': { diff --git a/packages/generator/src/generate-interface.ts b/packages/generator/src/generate-interface.ts index 2762acf..bfc0266 100644 --- a/packages/generator/src/generate-interface.ts +++ b/packages/generator/src/generate-interface.ts @@ -16,6 +16,7 @@ import { getRequestBodyContent, isErrorStatusCode, normalizeServerName, + resolveRef, } from './openapi-utils' import { wrapInterfaceKeyGuard } from './wrap-interface-key-guard' @@ -117,6 +118,30 @@ function extractContentType( return extractInlineType(jsonContent.schema) } +/** + * Check if a request body uses form or multipart content type. + */ +function isFormOrMultipartRequestBody( + requestBody: OpenAPIV3_1.RequestBodyObject | OpenAPIV3_1.ReferenceObject, + document: OpenAPIV3_1.Document, +): boolean { + let content: OpenAPIV3_1.RequestBodyObject['content'] | undefined + if ('$ref' in requestBody) { + const resolved = resolveRef( + requestBody.$ref, + document, + ) + content = resolved?.content + } else { + content = requestBody.content + } + if (!content) return false + return ( + content['multipart/form-data'] !== undefined || + content['application/x-www-form-urlencoded'] !== undefined + ) +} + // Generate interface for a single schema function generateSchemaInterface( schema: OpenAPIV3_1.Document, @@ -346,6 +371,22 @@ function generateSchemaInterface( } } if (requestBodyType !== undefined) { + // For form/multipart endpoints, also allow FormData as body type + if (operation.requestBody) { + const isFormMultipart = isFormOrMultipartRequestBody( + operation.requestBody, + schema, + ) + if (isFormMultipart) { + const bodyStr = + typeof requestBodyType === 'string' + ? requestBodyType + : formatTypeValue(requestBodyType) + if (!bodyStr.includes('FormData')) { + requestBodyType = `${bodyStr} | FormData` + } + } + } endpoint.body = requestBodyType }