From 9cf14ef42a8ff07e5c0ea0302c8869ffe7171b86 Mon Sep 17 00:00:00 2001 From: Shivam Patidar Date: Thu, 9 Apr 2026 13:49:25 +0530 Subject: [PATCH 1/2] fix(file-store): disable default checksum calculation for MinIO AWS SDK v3 (>=3.421) changed the default to always compute checksums on uploads. When streaming without a known ContentLength, it tries to set x-amz-decoded-content-length to undefined, crashing before the request reaches MinIO. Set requestChecksumCalculation and responseChecksumValidation to WHEN_REQUIRED on the MinIO S3Client so checksums are only computed when the S3 protocol actually requires them. Co-Authored-By: Claude Sonnet 4.6 --- src/file-store.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/file-store.ts b/src/file-store.ts index 207edc2..3cafa4e 100644 --- a/src/file-store.ts +++ b/src/file-store.ts @@ -498,6 +498,8 @@ async function ConfigureMinio(f: FastifyInstance) { secretAccessKey: process.env.MINIO_SECRET_ACCESS_KEY, }, forcePathStyle: true, + requestChecksumCalculation: "WHEN_REQUIRED", + responseChecksumValidation: "WHEN_REQUIRED", }); const bucket = process.env.MINIO_BUCKET; From 290b5ac8ce55a99905825fb2c090f6d89a6ec421 Mon Sep 17 00:00:00 2001 From: Shivam Patidar Date: Thu, 9 Apr 2026 17:13:14 +0530 Subject: [PATCH 2/2] fix(file-store): use Upload from lib-storage for MinIO streaming AWS SDK v3's PutObjectCommand doesn't handle streams without known ContentLength properly. MinIO requires the Content-Length header. Replace PutObjectCommand with Upload from @aws-sdk/lib-storage for: - copyFromStream(): streams of unknown size - copyFromLocalFile(): file read streams Upload handles multipart uploads, calculates proper headers, and works across S3, MinIO, and GCS seamlessly. Also works for AWS S3 and Azure. Fixes: - "You must provide the Content-Length HTTP header" (MinIO) - "Invalid value undefined for header x-amz-decoded-content-length" --- package.json | 1 + src/file-store.ts | 23 +++++++++++++---------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 08e889c..cc794c0 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "dependencies": { "@aws-sdk/client-s3": "^3.1013.0", "@aws-sdk/credential-provider-node": "^3.972.23", + "@aws-sdk/lib-storage": "^3.1013.0", "@azure/identity": "^4.13.1", "@azure/service-bus": "^7.9.5", "@azure/storage-blob": "^12.31.0", diff --git a/src/file-store.ts b/src/file-store.ts index 3cafa4e..bb7edfc 100644 --- a/src/file-store.ts +++ b/src/file-store.ts @@ -3,6 +3,7 @@ import * as os from "node:os"; import * as path from "node:path"; import * as stream from "node:stream"; import * as S3 from "@aws-sdk/client-s3"; +import { Upload } from "@aws-sdk/lib-storage"; import * as AzureIden from "@azure/identity"; import { defaultProvider } from "@aws-sdk/credential-provider-node"; import { BlobServiceClient, ContainerClient } from "@azure/storage-blob"; @@ -363,14 +364,16 @@ class S3FileStore implements FileStore { contentType: string, localFilepath: string, ) { - await this.client.send( - new S3.PutObjectCommand({ + const upload = new Upload({ + client: this.client, + params: { Bucket: this.bucket, Key: filepath, Body: fs.createReadStream(localFilepath), ContentType: contentType, - }), - ); + }, + }); + await upload.done(); } async getAsStream(filepath: string): Promise { @@ -391,14 +394,16 @@ class S3FileStore implements FileStore { contentType: string, rs: stream.Readable, ): Promise { - await this.client.send( - new S3.PutObjectCommand({ + const upload = new Upload({ + client: this.client, + params: { Bucket: this.bucket, Key: filepath, Body: rs, ContentType: contentType, - }), - ); + }, + }); + await upload.done(); } async getInfo(filepath: string): Promise { @@ -498,8 +503,6 @@ async function ConfigureMinio(f: FastifyInstance) { secretAccessKey: process.env.MINIO_SECRET_ACCESS_KEY, }, forcePathStyle: true, - requestChecksumCalculation: "WHEN_REQUIRED", - responseChecksumValidation: "WHEN_REQUIRED", }); const bucket = process.env.MINIO_BUCKET;