openapi-typescript-codegen: (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: ['test@test.com'],
    subject: 'Test',
    replyTo: 'test@test.com',
    formData: {
      attachments: [blob],
    },
  });
}

Am I missing something?

About this issue

  • Original URL
  • State: open
  • Created 7 months ago
  • Reactions: 2
  • Comments: 18

Most upvoted comments

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.

@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

Oh I didn’t know about that fork / project 😱 Will check it out 🤝 Thanks for the hint! @jordanshatford @costa-collibra @mrlubos

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