Last active
March 14, 2025 02:34
-
-
Save w00kie/09154981be8af8e757ed240c6844b2fc to your computer and use it in GitHub Desktop.
Cloudflare Worker that bundles R2 objects in a Zip file
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { z } from 'zod'; | |
import { ZipWriter, BlobReader, configure } from '@zip.js/zip.js'; | |
// Without this, we get uncaught error due to Workers runtime bug | |
// See: https://github.com/gildas-lormeau/zip.js/discussions/514 | |
configure({ | |
useCompressionStream: false, | |
}); | |
// Payload schema that lists the files to be bundled, their filenames and the archive filename | |
const schema = z.object({ | |
archive_filename: z.string().endsWith('.zip'), | |
files: z.array( | |
z.object({ | |
r2key: z.string(), | |
filename: z.string(), | |
}) | |
), | |
}); | |
export default { | |
async fetch(request, env, ctx): Promise<Response> { | |
const body = await request.json(); | |
const payload = schema.safeParse(body); | |
if (!payload.success) { | |
return new Response(JSON.stringify({ status: 'failed', error: payload.error }), { | |
status: 409, | |
headers: { 'Content-Type': 'application/json' }, | |
}); | |
} | |
let { readable, writable } = new IdentityTransformStream(); | |
// Create a ZIP archive stream | |
const archive = new ZipWriter(writable); | |
// Store all the promises to catch any errors all together later | |
let promises: Promise<any>[] = []; | |
for (const receipt of payload.data.files) { | |
const { r2key, filename } = receipt; | |
// Fetch the receipt from the R2 bucket | |
const fileContent = await env.STORAGE_BUCKET.get(r2key); | |
if (!fileContent) { | |
return new Response(`Object not found: ${r2key}`, { status: 404 }); | |
} | |
// Add the receipt to the ZIP archive | |
promises.push(archive.add(filename, new BlobReader(await fileContent.blob()))); | |
} | |
promises.push(archive.close()); | |
Promise.all(promises).catch((err) => { | |
console.log(err); | |
}); | |
return new Response(readable, { | |
headers: { | |
'Content-Type': 'application/zip', | |
'Content-Disposition': `attachment; filename="${payload.data.archive_filename}"`, | |
}, | |
}); | |
}, | |
} satisfies ExportedHandler<Env>; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment