Serve image bytes via the SDK's documented transform

The SDK returns a Node IncomingMessage, not a web ReadableStream. undici
accepts it (verified against the live bucket, 21882 bytes), but the cast
claimed a type it never had. transformToByteArray is the documented API;
buffering is fine with uploads capped at 10MB.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
JP
2026-08-08 20:26:58 +00:00
parent e0c1814cf1
commit 996b1b5360

View File

@@ -37,12 +37,20 @@ export async function GET(
const object = await getImage(key) const object = await getImage(key)
if (!object.Body) return new Response(null, { status: 404 }) if (!object.Body) return new Response(null, { status: 404 })
return new Response(object.Body as unknown as ReadableStream, { // The SDK hands back a Node IncomingMessage, not a web ReadableStream. undici
// happens to accept it, but transformToByteArray is the documented API and keeps
// the types honest. Buffering is fine here - uploads are capped at 10MB.
const bytes = await object.Body.transformToByteArray()
// Copy out to a plain ArrayBuffer: a Uint8Array view is not a valid BodyInit.
const body = bytes.buffer.slice(
bytes.byteOffset,
bytes.byteOffset + bytes.byteLength
) as ArrayBuffer
return new Response(body, {
headers: { headers: {
"Content-Type": object.ContentType ?? "application/octet-stream", "Content-Type": object.ContentType ?? "application/octet-stream",
...(object.ContentLength "Content-Length": String(bytes.byteLength),
? { "Content-Length": String(object.ContentLength) }
: {}),
// Keys embed a random UUID, so an object never changes under a given key. // Keys embed a random UUID, so an object never changes under a given key.
// `private` keeps it out of any shared cache now that it is session-gated. // `private` keeps it out of any shared cache now that it is session-gated.
"Cache-Control": "private, max-age=31536000, immutable", "Cache-Control": "private, max-age=31536000, immutable",