Skip to content

Commit 36b16ea

Browse files
MaxwellCalkinclaude
authored andcommitted
fix(storage): add stream duplex handling to uploadToSignedUrl
The raw-body path in uploadToSignedUrl was missing the stream detection and duplex: 'half' propagation that uploadOrUpdate already has. Without this, passing a ReadableStream or Node.js stream to uploadToSignedUrl fails on Node 20+ fetch which requires duplex: 'half' for stream bodies. Mirrors the exact pattern from uploadOrUpdate (lines 121-142) into uploadToSignedUrl for consistency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4f09780 commit 36b16ea

2 files changed

Lines changed: 15 additions & 3 deletions

File tree

packages/core/storage-js/src/packages/StorageFileApi.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,6 @@ export default class StorageFileApi extends BaseApiClient<StorageError> {
267267
let body
268268
const options = {
269269
...DEFAULT_FILE_OPTIONS,
270-
upsert: DEFAULT_FILE_OPTIONS.upsert,
271270
...fileOptions,
272271
}
273272
let headers: Record<string, string> = {
@@ -300,13 +299,26 @@ export default class StorageFileApi extends BaseApiClient<StorageError> {
300299
if (metadata) {
301300
headers['x-metadata'] = this.toBase64(this.encodeMetadata(metadata))
302301
}
302+
303+
// Node.js streams require duplex option for fetch in Node 20+
304+
// Check for both web ReadableStream and Node.js streams
305+
const isStream =
306+
(typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) ||
307+
(body && typeof body === 'object' && 'pipe' in body && typeof body.pipe === 'function')
308+
309+
if (isStream && !options.duplex) {
310+
options.duplex = 'half'
311+
}
303312
}
304313

305314
if (fileOptions?.headers) {
306315
headers = { ...headers, ...fileOptions.headers }
307316
}
308317

309-
const data = await put(this.fetch, url.toString(), body as object, { headers })
318+
const data = await put(this.fetch, url.toString(), body as object, {
319+
headers,
320+
...(options?.duplex ? { duplex: options.duplex } : {}),
321+
})
310322

311323
return { path: cleanPath, fullPath: data.Key }
312324
})

packages/core/storage-js/test/storageFileApi.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -954,7 +954,7 @@ describe('StorageFileApi Edge Cases', () => {
954954
const [, , body] = mockPut.mock.calls[0]
955955
expect(body.get('cacheControl')).toBe('7200')
956956
})
957-
957+
958958
test('uploadToSignedUrl with Blob includes metadata in FormData', async () => {
959959
const testBlob = new Blob(['test content'], { type: 'text/plain' })
960960
const metadata = { customKey: 'customValue', author: 'test' }

0 commit comments

Comments
 (0)