( bucketPath: string, body: Uint8Array | ArrayBuffer, )
| 210 | }; |
| 211 | |
| 212 | export const putBucket = async ( |
| 213 | bucketPath: string, |
| 214 | body: Uint8Array | ArrayBuffer, |
| 215 | ) => { |
| 216 | // We only use multipart uploads for files larger than 80MB since Cloudflare |
| 217 | // limits the maximum request size to 100MB |
| 218 | const partSize = 80 * 1024 * 1024; |
| 219 | if (body.byteLength < partSize) { |
| 220 | keepAwake(SLEEP_MINUTES); |
| 221 | const resp = await fetch( |
| 222 | `https://upload.fontsource.org/put/${bucketPath}`, |
| 223 | { |
| 224 | method: 'PUT', |
| 225 | headers: { |
| 226 | Authorization: `Bearer ${ |
| 227 | // biome-ignore lint/style/noNonNullAssertion: <explanation> |
| 228 | process.env.UPLOAD_KEY! |
| 229 | }`, |
| 230 | }, |
| 231 | body, |
| 232 | }, |
| 233 | ); |
| 234 | |
| 235 | if (!resp.ok) { |
| 236 | const error = await resp.text(); |
| 237 | handleBucketError(resp, `Unable to upload file ${bucketPath}. ${error}`); |
| 238 | } |
| 239 | |
| 240 | return; |
| 241 | } |
| 242 | |
| 243 | info(`Uploading ${bucketPath} in parts with size ${body.byteLength}`); |
| 244 | |
| 245 | const uploadId = await initiateMultipartUpload(bucketPath); |
| 246 | |
| 247 | const parts: R2UploadedPart[] = []; |
| 248 | let offset = 0; |
| 249 | let partNumber = 1; |
| 250 | |
| 251 | // Upload buffers in parts |
| 252 | while (offset < body.byteLength) { |
| 253 | const end = Math.min(offset + partSize, body.byteLength); |
| 254 | const partData = body.slice(offset, end); |
| 255 | info(`Uploading part ${partNumber} with size ${partData.byteLength}`); |
| 256 | |
| 257 | const etag = await uploadPart(bucketPath, uploadId, partNumber, partData); |
| 258 | |
| 259 | parts.push({ |
| 260 | etag, |
| 261 | partNumber, |
| 262 | }); |
| 263 | |
| 264 | offset = end; |
| 265 | partNumber++; |
| 266 | } |
| 267 | |
| 268 | // Complete multipart upload |
| 269 | await completeMultipartUpload(bucketPath, uploadId, parts); |
no test coverage detected