使用 NestJS 上传 GraphQL 时超出了最大调用堆栈大小

Maximum call stack size exceeded on GraphQL Upload with NestJS

我在使用 ReadStream 函数通过 GraphQL Upload 上传文件时遇到错误:

error: 17:10:32.466+02:00 [ExceptionsHandler] Maximum call stack size exceeded
error: 17:10:32.467+02:00 [graphql] Maximum call stack size exceeded RangeError: Maximum call stack size exceeded
    at ReadStream.open (/Users/xxxx/Documents/Xxxx/xxxxx/xxxxx-api/node_modules/fs-capacitor/lib/index.js:80:7)
    at _openReadFs (internal/fs/streams.js:117:12)
    at ReadStream.<anonymous> (internal/fs/streams.js:110:3)
    at ReadStream.deprecated [as open] (internal/util.js:96:15)
    at ReadStream.open (/Users/xxxx/Documents/Xxxxx/xxxx/xxxxx-api/node_modules/fs-capacitor/lib/index.js:90:11)
    at _openReadFs (internal/fs/streams.js:117:12)
    at ReadStream.<anonymous> (internal/fs/streams.js:110:3)
    at ReadStream.deprecated [as open] (internal/util.js:96:15)
    at ReadStream.open (/Users/xxxx/Documents/Xxxxx/xxxxx/xxxxx-api/node_modules/fs-capacitor/lib/index.js:90:11)
    at _openReadFs (internal/fs/streams.js:117:12) {"stack":"RangeError: Maximum call stack size exceeded\n    at ReadStream.open (/Users/xxxx/Documents/Xxxxxx/xxxxx/xxxx-api/node_modules/fs-capacitor/lib/index.js:80:7)\n    at _openReadFs (internal/fs/streams.js:117:12)\n    at ReadStream.<anonymous> (internal/fs/streams.js:110:3)\n    at ReadStream.deprecated [as open] (internal/util.js:96:15)\n    at ReadStream.open (/Users/xxxxx/Documents/Xxxxx/xxxxx/xxxxx-api/node_modules/fs-capacitor/lib/index.js:90:11)\n    at _openReadFs (internal/fs/streams.js:117:12)\n    at ReadStream.<anonymous> (internal/fs/streams.js:110:3)\n    at ReadStream.deprecated [as open] (internal/util.js:96:15)\n    at ReadStream.open (/Users/xxxx/Documents/Xxxxxx/xxxx/xxxxx-api/node_modules/fs-capacitor/lib/index.js:90:11)\n    at _openReadFs (internal/fs/streams.js:117:12)"}
(node:44569) [DEP0135] DeprecationWarning: ReadStream.prototype.open() is deprecated
(Use `node --trace-deprecation ...` to show where the warning was created)

这是我用来上传文件的函数:

public async cleanUpload(upload: GraphqlUpload, oldName?: string) {
    let uploadResponse: FileInfo;
    try {
      if (oldName) {
        this.safeRemove(oldName);
      }
      uploadResponse = await this.uploadFile(
        {
          fileName: upload.filename,
          stream: upload.createReadStream(),
          mimetype: upload.mimetype,
        },
        { isPublic: true, filter: imageFilterFunction },
      );
      return uploadResponse;
    } catch (e) {
      this.logger.error('unable to upload', e);
      if (uploadResponse) {
        this.safeRemove(uploadResponse.fileName);
      }
      throw e;
    }
  }

解决方案是将节点版本14.17降级到12.18

要继续使用 Node 14.17,您可以禁用 Apollo 的内部上传并使用 graphql-upload

请参阅 this comment,其中概述了此处引用的方法。

For any future readers, here is how to fix the issue once and for all.

The problem is that @nestjs/graphql's dependency, apollo-server-core, depends on an old version of graphql-upload (v8.0) which has conflicts with newer versions of Node.js and various packages. Apollo Server v2.21.0 seems to have fixed this but @nestjs/graphql is still on v2.16.1. Furthermore, Apollo Server v3 will be removing the built-in graphql-upload.

The solution suggested in this comment is to disable Apollo Server's built-in handling of uploads and use your own. This can be done in 3 simple steps:

1. package.json

Remove the fs-capacitor and graphql-upload entries from the resolutions section if you added them, and install the latest version of graphql-upload (v11.0.0 at this time) package as a dependency.

2. src/app.module.ts

Disable Apollo Server's built-in upload handling and add the graphqlUploadExpress middleware to your application.

import { graphqlUploadExpress } from "graphql-upload"
import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common"

@Module({
  imports: [
    GraphQLModule.forRoot({
      uploads: false, // disable built-in upload handling
    }),
  ],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(graphqlUploadExpress()).forRoutes("graphql")
  }
}

3. src/blog/post.resolver.ts (example resolver)

Remove the GraphQLUpload import from apollo-server-core and import from graphql-upload instead

// import { GraphQLUpload } from "apollo-server-core" <-- remove this
import { FileUpload, GraphQLUpload } from "graphql-upload"

@Mutation(() => Post)
async postCreate(
  @Args("title") title: string,
  @Args("body") body: string,
  @Args("attachment", { type: () => GraphQLUpload }) attachment: Promise<FileUpload>,
) {
  const { filename, mimetype, encoding, createReadStream } = await attachment
  console.log("attachment:", filename, mimetype, encoding)

  const stream = createReadStream()
  stream.on("data", (chunk: Buffer) => /* do stuff with data here */)
}