openapi-typescript-codegen
openapi-typescript-codegen copied to clipboard
(FormData) TypeError: source.on is not a function
What is the problem?
I'm using openapi-typescript-codegen for creating an axios client. When I try to call one of the services I end up getting the following error:
.../_nestjs/node_modules/delayed-stream/lib/delayed_stream.js:33
source.on('error', function() {});
^
TypeError: source.on is not a function
at Function.DelayedStream.create (.../_nestjs/node_modules/delayed-stream/lib/delayed_stream.js:33:10)
at FormData.CombinedStream.append (.../_nestjs/node_modules/combined-stream/lib/combined_stream.js:45:37)
at FormData.append (.../_nestjs/node_modules/form-data/lib/form_data.js:75:3)
at process (.../_nestjs/client/app/src/generated/core/request.ts:117:26)
at .../_nestjs/client/app/src/generated/core/request.ts:127:40
at Array.forEach (<anonymous>)
at .../_nestjs/client/app/src/generated/core/request.ts:127:27
at Array.forEach (<anonymous>)
at getFormData (.../_nestjs/client/app/src/generated/core/request.ts:125:14)
at .../_nestjs/client/app/src/generated/core/request.ts:294:41
Like the stacktrace shows the error is thrown inside request.ts file in the getFormData function.
export const getFormData = (options: ApiRequestOptions): FormData | undefined => {
if (options.formData) {
const formData = new FormData();
const process = (key: string, value: any) => {
if (isString(value) || isBlob(value)) {
formData.append(key, value); // 🚨 this is where it breaks (isBlob == true)
} else {
formData.append(key, JSON.stringify(value));
}
};
Object.entries(options.formData)
.filter(([_, value]) => isDefined(value))
.forEach(([key, value]) => {
if (Array.isArray(value)) {
value.forEach(v => process(key, v));
} else {
process(key, value);
}
});
return formData;
}
return undefined;
};
Reproducable example
❯ node -v
v20.10.0
package.json
{
"name": "api",
"version": "0.0.1",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"prebuild": "rm -rf dist",
"gen": "openapi -i openapi.json -o src/generated -c axios --useOptions --useUnionTypes",
"build": "tsc"
},
"dependencies": {
"axios": "^1.6.2",
"form-data": "^4.0.0"
},
"devDependencies": {
"@types/node": "^20.10.4",
"openapi-typescript-codegen": "0.25.0",
"ts-node": "^10.9.2",
"tsc": "2.0.4",
"typescript": "^5.3.3"
}
}
tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"declaration": true,
"removeComments": false,
"emitDecoratorMetadata": true,
"allowSyntheticDefaultImports": true,
"incremental": false,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"noFallthroughCasesInSwitch": false,
"lib": ["ES2022", "dom"],
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": false,
"skipLibCheck": true,
"baseUrl": "./",
"rootDir": "./",
"outDir": "dist"
},
"include": ["src"],
"exclude": ["node_modules"]
}
My openapi spec is looking like this:
{
"openapi": "3.0.0",
"paths": {
"/mail/send": {
"post": {
"operationId": "sendMail",
"summary": "Send a generic mail",
"parameters": [
{ "name": "subject", "required": true, "in": "query", "schema": { "type": "string" } },
{ "name": "recipients", "required": true, "in": "query", "schema": { "type": "array", "items": { "type": "string" } } },
{ "name": "displayName", "required": true, "in": "query", "schema": { "type": "string" } },
{ "name": "replyTo", "required": true, "in": "query", "schema": { "type": "string" } },
{ "name": "ccs", "required": false, "in": "query", "schema": { "type": "array", "items": { "type": "string" } } },
{ "name": "bccs", "required": false, "in": "query", "schema": { "type": "array", "items": { "type": "string" } } }
],
"requestBody": { "required": true, "content": { "multipart/form-data": { "schema": { "$ref": "#/components/schemas/SendGenericMailBodyDto" } } } },
"responses": { "201": { "description": "" } },
"tags": ["Mail"],
"security": [{ "JWT": [] }]
}
}
},
"info": { "title": "API" },
"tags": [],
"servers": [],
"components": {
"schemas": {
"SendGenericMailBodyDto": { "type": "object", "properties": { "attachments": { "type": "array", "items": { "type": "string", "format": "binary" } } }, "required": ["attachments"] }
}
}
}
The generated code for that service looks like this:
...
export class MailService {
/**
* Send a generic mail
* @returns any
* @throws ApiError
*/
public static sendMail({
subject,
recipients,
displayName,
replyTo,
formData,
ccs,
bccs,
}: {
subject: string,
recipients: Array<string>,
displayName: string,
replyTo: string,
formData: SendGenericMailBodyDto,
ccs?: Array<string>,
bccs?: Array<string>,
}): CancelablePromise<any> {
return __request(OpenAPI, {
method: 'POST',
url: '/mail/send',
query: {
'subject': subject,
'recipients': recipients,
'displayName': displayName,
'replyTo': replyTo,
'ccs': ccs,
'bccs': bccs,
},
formData: formData,
mediaType: 'multipart/form-data',
});
}
}
Now when I try to call that service the error occurs:
import * as MAIL from '..';
export async function sendMail() {
const obj = { hello: 'world' };
const blob = new Blob([JSON.stringify(obj, null, 2)], {
type: 'application/json',
});
await MAIL.MailService.sendMail({
displayName: 'Test',
recipients: ['[email protected]'],
subject: 'Test',
replyTo: '[email protected]',
formData: {
attachments: [blob],
},
});
}
Am I missing something?
@kumboleijo Did you ever figure out what caused this issue?
@melanki sadly no. But what I can say is, that it is working in browser based environments (React App built with vite). Not sure if I'm doing something wrong in my typescript setup and mess up with ESM and CJS?
I have also had this exact same issue when trying to use the generated code to upload a file. For now I have had to build my own request using axios. I am using the client in my e2e Playwright tests for creating test data.
I can provide more context if and examples if you are looking into this issue.
Thanks
@costa-collibra Would you mind sharing some more insights on this and what exactly you had to tweak in your custom request in order to get it work? 🙏
@kumboleijo My choice of words were probably a bit poor. In order to get it to work we couldnt use the library so we used axios directly.
export const uploadZip = async ({ filePath, fileName }: { filePath: string; fileName: string }): Promise<any> => {
const data = new FormData();
const setCustomAuthHeader = btoa(
`${envConfig().CUSTOM_ADMIN_TEST_USERNAME}:${envConfig().CUSTOM_ADMIN_TEST_PASSWORD}`,
);
data.append('file', fs.createReadStream(filePath));
data.append('fileName', fileName);
const config = {
method: 'post',
maxBodyLength: Infinity,
url: envConfig().BASE_URL + '/rest/2.0/workflowDefinitions',
headers: {
Authorization: `Basic ${setCustomAuthHeader}`,
...data.getHeaders(),
},
data: data,
};
const response = await axios.request(config);
console.log(`RESPONSE: ${JSON.stringify(response.data)}`);
return response;
};
This is why I think the issue resides in this library as this code is working.
I really hope this gets fixed though so we can go back to library for this endpoint.
@costa-collibra oh I see... I thought you provided your own custom request file using:
npx openapi-typescript-codegen --input ./spec.json --output ./generated --request ./request.ts
- https://github.com/ferdikoomen/openapi-typescript-codegen/wiki/Custom-request-file
I guess that could be a good workaround for you since you already have custom code written for that call.
@costa-collibra oh I see... I thought you provided your own custom request file using:
npx openapi-typescript-codegen --input ./spec.json --output ./generated --request ./request.ts
- https://github.com/ferdikoomen/openapi-typescript-codegen/wiki/Custom-request-file
I guess that could be a good workaround for you since you already have custom code written for that call.
I didnt even know this was possible, hopefully its only a temp workaround
I am also experiencing this issue. In another browser based project, I was able to get this working, but I built an Electron app with electon-react-boilerplate and see the issue here.
I also just directly used Axios and made my own FormData() and see no issues with that.
@jordanshatford we have this fixed in @hey-api/openapi-ts, right?
@jordanshatford we have this fixed in @hey-api/openapi-ts, right?
Oh really. I will check this out asap.
@costa-collibra let me know please, I'm not sure as I never ran into this with my Axios client
@costa-collibra if it's not fixed, we have a whole issue around a slew of similar bugs https://github.com/hey-api/openapi-ts/issues/29
Leave it with me and il get back to you
@costa-collibra @mrlubos it has been fixed in the node client, working on updating the axios client to be fixed aswell. Should be in the next release
@jordanshatford this is fantastic thanks for the update
for clarity have you fixed it in this https://github.com/hey-api/openapi-ts
Check out our fork of this repository @hey-api/openapi-ts. We have fixed this issue in v0.32.1. If you run into any further issues, open an issue in our repository. Thanks.
NOTE: this is now fixed for both node and axios clients. No other clients experience this issue.
Oh I didn't know about that fork / project 😱 Will check it out 🤝 Thanks for the hint! @jordanshatford @costa-collibra @mrlubos
You're welcome @kumboleijo. If anyone in this thread still has to use a custom request file, please let us know why and how you use it as we're trying to discourage this pattern