multipart.ts

_250
import type { APIRoute } from "astro";
_250
import { API } from "../../utils/api";
_250
_250
interface MultipartUploadRequest {
_250
key: string;
_250
contentType?: string;
_250
}
_250
_250
interface CompleteMultipartRequest {
_250
uploadId: string;
_250
key: string;
_250
parts: R2UploadedPart[];
_250
}
_250
_250
// Creates and completes a new multipart upload session
_250
export const POST: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (!action) {
_250
return API.error("Missing action parameter", request, 400);
_250
}
_250
_250
switch (action) {
_250
case "create": {
_250
// Create a new multipart upload
_250
const body: MultipartUploadRequest = await request.json();
_250
_250
if (!body.key) {
_250
return API.error("Missing key parameter", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = await bucket.createMultipartUpload(body.key, {
_250
httpMetadata: body.contentType
_250
? {
_250
contentType: body.contentType,
_250
}
_250
: undefined,
_250
});
_250
_250
return API.success(
_250
{
_250
success: true,
_250
key: multipartUpload.key,
_250
uploadId: multipartUpload.uploadId,
_250
},
_250
request
_250
);
_250
} catch (error) {
_250
console.error("Failed to create multipart upload:", error);
_250
return API.error("Failed to create multipart upload", request, 500);
_250
}
_250
}
_250
_250
case "complete": {
_250
// Complete a multipart upload
_250
const body: CompleteMultipartRequest = await request.json();
_250
_250
if (!body.uploadId || !body.key || !body.parts) {
_250
return API.error("Missing required parameters", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(
_250
body.key,
_250
body.uploadId
_250
);
_250
_250
// Parts are already in R2UploadedPart format
_250
const r2Parts = body.parts;
_250
_250
const object = await multipartUpload.complete(r2Parts);
_250
_250
return API.success(
_250
{
_250
success: true,
_250
key: object.key,
_250
etag: object.httpEtag,
_250
size: object.size,
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to complete multipart upload:", error);
_250
return API.error(
_250
error.message || "Failed to complete multipart upload",
_250
request,
_250
400
_250
);
_250
}
_250
}
_250
_250
default:
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
} catch (error) {
_250
console.error("Multipart upload error:", error);
_250
return API.error("Multipart upload failed", request, 500);
_250
}
_250
};
_250
_250
// Uploads individual parts of a multipart upload
_250
export const PUT: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (action !== "upload-part") {
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
_250
const uploadId = url.searchParams.get("uploadId");
_250
const partNumberStr = url.searchParams.get("partNumber");
_250
const key = url.searchParams.get("key");
_250
_250
if (!uploadId || !partNumberStr || !key) {
_250
return API.error("Missing uploadId, partNumber, or key", request, 400);
_250
}
_250
_250
const partNumber = parseInt(partNumberStr);
_250
if (isNaN(partNumber) || partNumber < 1) {
_250
return API.error("Invalid part number", request, 400);
_250
}
_250
_250
if (!request.body) {
_250
return API.error("Missing request body", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(key, uploadId);
_250
_250
// Convert request body to ArrayBuffer to get known length
_250
const arrayBuffer = await request.arrayBuffer();
_250
const uploadedPart = await multipartUpload.uploadPart(
_250
partNumber,
_250
arrayBuffer
_250
);
_250
_250
return API.success(
_250
{
_250
success: true,
_250
partNumber: uploadedPart.partNumber,
_250
etag: uploadedPart.etag,
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to upload part:", error);
_250
return API.error(error.message || "Failed to upload part", request, 400);
_250
}
_250
} catch (error) {
_250
console.error("Upload part error:", error);
_250
return API.error("Upload part failed", request, 500);
_250
}
_250
};
_250
_250
// Aborts a multipart upload
_250
export const DELETE: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (action !== "abort") {
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
_250
const uploadId = url.searchParams.get("uploadId");
_250
const key = url.searchParams.get("key");
_250
_250
if (!uploadId || !key) {
_250
return API.error("Missing uploadId or key", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(key, uploadId);
_250
await multipartUpload.abort();
_250
_250
return API.success(
_250
{
_250
success: true,
_250
message: "Multipart upload aborted successfully",
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to abort multipart upload:", error);
_250
return API.error(
_250
error.message || "Failed to abort multipart upload",
_250
request,
_250
400
_250
);
_250
}
_250
} catch (error) {
_250
console.error("Abort multipart upload error:", error);
_250
return API.error("Abort multipart upload failed", request, 500);
_250
}
_250
};
_250
_250
export const OPTIONS: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
return API.cors(request);
_250
};

Create the POST Endpoint

The POST endpoint manages multipart uploads through two key operations: initializing new uploads and finalizing completed ones. The action query parameter determines which operation to perform.

Be sure to include the locals parameter in the POST endpoint to access your environment variables and CLOUD_FILES binding.

Setup CORS handling

multipart.ts

_250
import type { APIRoute } from "astro";
_250
import { API } from "../../utils/api";
_250
_250
interface MultipartUploadRequest {
_250
key: string;
_250
contentType?: string;
_250
}
_250
_250
interface CompleteMultipartRequest {
_250
uploadId: string;
_250
key: string;
_250
parts: R2UploadedPart[];
_250
}
_250
_250
// Creates and completes a new multipart upload session
_250
export const POST: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (!action) {
_250
return API.error("Missing action parameter", request, 400);
_250
}
_250
_250
switch (action) {
_250
case "create": {
_250
// Create a new multipart upload
_250
const body: MultipartUploadRequest = await request.json();
_250
_250
if (!body.key) {
_250
return API.error("Missing key parameter", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = await bucket.createMultipartUpload(body.key, {
_250
httpMetadata: body.contentType
_250
? {
_250
contentType: body.contentType,
_250
}
_250
: undefined,
_250
});
_250
_250
return API.success(
_250
{
_250
success: true,
_250
key: multipartUpload.key,
_250
uploadId: multipartUpload.uploadId,
_250
},
_250
request
_250
);
_250
} catch (error) {
_250
console.error("Failed to create multipart upload:", error);
_250
return API.error("Failed to create multipart upload", request, 500);
_250
}
_250
}
_250
_250
case "complete": {
_250
// Complete a multipart upload
_250
const body: CompleteMultipartRequest = await request.json();
_250
_250
if (!body.uploadId || !body.key || !body.parts) {
_250
return API.error("Missing required parameters", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(
_250
body.key,
_250
body.uploadId
_250
);
_250
_250
// Parts are already in R2UploadedPart format
_250
const r2Parts = body.parts;
_250
_250
const object = await multipartUpload.complete(r2Parts);
_250
_250
return API.success(
_250
{
_250
success: true,
_250
key: object.key,
_250
etag: object.httpEtag,
_250
size: object.size,
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to complete multipart upload:", error);
_250
return API.error(
_250
error.message || "Failed to complete multipart upload",
_250
request,
_250
400
_250
);
_250
}
_250
}
_250
_250
default:
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
} catch (error) {
_250
console.error("Multipart upload error:", error);
_250
return API.error("Multipart upload failed", request, 500);
_250
}
_250
};
_250
_250
// Uploads individual parts of a multipart upload
_250
export const PUT: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (action !== "upload-part") {
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
_250
const uploadId = url.searchParams.get("uploadId");
_250
const partNumberStr = url.searchParams.get("partNumber");
_250
const key = url.searchParams.get("key");
_250
_250
if (!uploadId || !partNumberStr || !key) {
_250
return API.error("Missing uploadId, partNumber, or key", request, 400);
_250
}
_250
_250
const partNumber = parseInt(partNumberStr);
_250
if (isNaN(partNumber) || partNumber < 1) {
_250
return API.error("Invalid part number", request, 400);
_250
}
_250
_250
if (!request.body) {
_250
return API.error("Missing request body", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(key, uploadId);
_250
_250
// Convert request body to ArrayBuffer to get known length
_250
const arrayBuffer = await request.arrayBuffer();
_250
const uploadedPart = await multipartUpload.uploadPart(
_250
partNumber,
_250
arrayBuffer
_250
);
_250
_250
return API.success(
_250
{
_250
success: true,
_250
partNumber: uploadedPart.partNumber,
_250
etag: uploadedPart.etag,
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to upload part:", error);
_250
return API.error(error.message || "Failed to upload part", request, 400);
_250
}
_250
} catch (error) {
_250
console.error("Upload part error:", error);
_250
return API.error("Upload part failed", request, 500);
_250
}
_250
};
_250
_250
// Aborts a multipart upload
_250
export const DELETE: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (action !== "abort") {
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
_250
const uploadId = url.searchParams.get("uploadId");
_250
const key = url.searchParams.get("key");
_250
_250
if (!uploadId || !key) {
_250
return API.error("Missing uploadId or key", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(key, uploadId);
_250
await multipartUpload.abort();
_250
_250
return API.success(
_250
{
_250
success: true,
_250
message: "Multipart upload aborted successfully",
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to abort multipart upload:", error);
_250
return API.error(
_250
error.message || "Failed to abort multipart upload",
_250
request,
_250
400
_250
);
_250
}
_250
} catch (error) {
_250
console.error("Abort multipart upload error:", error);
_250
return API.error("Abort multipart upload failed", request, 500);
_250
}
_250
};
_250
_250
export const OPTIONS: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
return API.cors(request);
_250
};

Since multipart uploads require direct communication with the worker domain (not your Webflow Cloud domain), you need to set up proper CORS configuration. The imported API utility automatically handles CORS headers and preflight requests to ensure seamless cross-origin communication.

Access your Object Storage bucket

multipart.ts

_250
import type { APIRoute } from "astro";
_250
import { API } from "../../utils/api";
_250
_250
interface MultipartUploadRequest {
_250
key: string;
_250
contentType?: string;
_250
}
_250
_250
interface CompleteMultipartRequest {
_250
uploadId: string;
_250
key: string;
_250
parts: R2UploadedPart[];
_250
}
_250
_250
// Creates and completes a new multipart upload session
_250
export const POST: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (!action) {
_250
return API.error("Missing action parameter", request, 400);
_250
}
_250
_250
switch (action) {
_250
case "create": {
_250
// Create a new multipart upload
_250
const body: MultipartUploadRequest = await request.json();
_250
_250
if (!body.key) {
_250
return API.error("Missing key parameter", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = await bucket.createMultipartUpload(body.key, {
_250
httpMetadata: body.contentType
_250
? {
_250
contentType: body.contentType,
_250
}
_250
: undefined,
_250
});
_250
_250
return API.success(
_250
{
_250
success: true,
_250
key: multipartUpload.key,
_250
uploadId: multipartUpload.uploadId,
_250
},
_250
request
_250
);
_250
} catch (error) {
_250
console.error("Failed to create multipart upload:", error);
_250
return API.error("Failed to create multipart upload", request, 500);
_250
}
_250
}
_250
_250
case "complete": {
_250
// Complete a multipart upload
_250
const body: CompleteMultipartRequest = await request.json();
_250
_250
if (!body.uploadId || !body.key || !body.parts) {
_250
return API.error("Missing required parameters", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(
_250
body.key,
_250
body.uploadId
_250
);
_250
_250
// Parts are already in R2UploadedPart format
_250
const r2Parts = body.parts;
_250
_250
const object = await multipartUpload.complete(r2Parts);
_250
_250
return API.success(
_250
{
_250
success: true,
_250
key: object.key,
_250
etag: object.httpEtag,
_250
size: object.size,
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to complete multipart upload:", error);
_250
return API.error(
_250
error.message || "Failed to complete multipart upload",
_250
request,
_250
400
_250
);
_250
}
_250
}
_250
_250
default:
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
} catch (error) {
_250
console.error("Multipart upload error:", error);
_250
return API.error("Multipart upload failed", request, 500);
_250
}
_250
};
_250
_250
// Uploads individual parts of a multipart upload
_250
export const PUT: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (action !== "upload-part") {
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
_250
const uploadId = url.searchParams.get("uploadId");
_250
const partNumberStr = url.searchParams.get("partNumber");
_250
const key = url.searchParams.get("key");
_250
_250
if (!uploadId || !partNumberStr || !key) {
_250
return API.error("Missing uploadId, partNumber, or key", request, 400);
_250
}
_250
_250
const partNumber = parseInt(partNumberStr);
_250
if (isNaN(partNumber) || partNumber < 1) {
_250
return API.error("Invalid part number", request, 400);
_250
}
_250
_250
if (!request.body) {
_250
return API.error("Missing request body", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(key, uploadId);
_250
_250
// Convert request body to ArrayBuffer to get known length
_250
const arrayBuffer = await request.arrayBuffer();
_250
const uploadedPart = await multipartUpload.uploadPart(
_250
partNumber,
_250
arrayBuffer
_250
);
_250
_250
return API.success(
_250
{
_250
success: true,
_250
partNumber: uploadedPart.partNumber,
_250
etag: uploadedPart.etag,
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to upload part:", error);
_250
return API.error(error.message || "Failed to upload part", request, 400);
_250
}
_250
} catch (error) {
_250
console.error("Upload part error:", error);
_250
return API.error("Upload part failed", request, 500);
_250
}
_250
};
_250
_250
// Aborts a multipart upload
_250
export const DELETE: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (action !== "abort") {
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
_250
const uploadId = url.searchParams.get("uploadId");
_250
const key = url.searchParams.get("key");
_250
_250
if (!uploadId || !key) {
_250
return API.error("Missing uploadId or key", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(key, uploadId);
_250
await multipartUpload.abort();
_250
_250
return API.success(
_250
{
_250
success: true,
_250
message: "Multipart upload aborted successfully",
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to abort multipart upload:", error);
_250
return API.error(
_250
error.message || "Failed to abort multipart upload",
_250
request,
_250
400
_250
);
_250
}
_250
} catch (error) {
_250
console.error("Abort multipart upload error:", error);
_250
return API.error("Abort multipart upload failed", request, 500);
_250
}
_250
};
_250
_250
export const OPTIONS: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
return API.cors(request);
_250
};

Retrieve the CLOUD_FILES binding that provides access to your Object Storage bucket. This binding contains all the multipart upload methods you'll need throughout the upload process.

Get Action Parameter

multipart.ts

_250
import type { APIRoute } from "astro";
_250
import { API } from "../../utils/api";
_250
_250
interface MultipartUploadRequest {
_250
key: string;
_250
contentType?: string;
_250
}
_250
_250
interface CompleteMultipartRequest {
_250
uploadId: string;
_250
key: string;
_250
parts: R2UploadedPart[];
_250
}
_250
_250
// Creates and completes a new multipart upload session
_250
export const POST: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (!action) {
_250
return API.error("Missing action parameter", request, 400);
_250
}
_250
_250
switch (action) {
_250
case "create": {
_250
// Create a new multipart upload
_250
const body: MultipartUploadRequest = await request.json();
_250
_250
if (!body.key) {
_250
return API.error("Missing key parameter", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = await bucket.createMultipartUpload(body.key, {
_250
httpMetadata: body.contentType
_250
? {
_250
contentType: body.contentType,
_250
}
_250
: undefined,
_250
});
_250
_250
return API.success(
_250
{
_250
success: true,
_250
key: multipartUpload.key,
_250
uploadId: multipartUpload.uploadId,
_250
},
_250
request
_250
);
_250
} catch (error) {
_250
console.error("Failed to create multipart upload:", error);
_250
return API.error("Failed to create multipart upload", request, 500);
_250
}
_250
}
_250
_250
case "complete": {
_250
// Complete a multipart upload
_250
const body: CompleteMultipartRequest = await request.json();
_250
_250
if (!body.uploadId || !body.key || !body.parts) {
_250
return API.error("Missing required parameters", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(
_250
body.key,
_250
body.uploadId
_250
);
_250
_250
// Parts are already in R2UploadedPart format
_250
const r2Parts = body.parts;
_250
_250
const object = await multipartUpload.complete(r2Parts);
_250
_250
return API.success(
_250
{
_250
success: true,
_250
key: object.key,
_250
etag: object.httpEtag,
_250
size: object.size,
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to complete multipart upload:", error);
_250
return API.error(
_250
error.message || "Failed to complete multipart upload",
_250
request,
_250
400
_250
);
_250
}
_250
}
_250
_250
default:
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
} catch (error) {
_250
console.error("Multipart upload error:", error);
_250
return API.error("Multipart upload failed", request, 500);
_250
}
_250
};
_250
_250
// Uploads individual parts of a multipart upload
_250
export const PUT: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (action !== "upload-part") {
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
_250
const uploadId = url.searchParams.get("uploadId");
_250
const partNumberStr = url.searchParams.get("partNumber");
_250
const key = url.searchParams.get("key");
_250
_250
if (!uploadId || !partNumberStr || !key) {
_250
return API.error("Missing uploadId, partNumber, or key", request, 400);
_250
}
_250
_250
const partNumber = parseInt(partNumberStr);
_250
if (isNaN(partNumber) || partNumber < 1) {
_250
return API.error("Invalid part number", request, 400);
_250
}
_250
_250
if (!request.body) {
_250
return API.error("Missing request body", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(key, uploadId);
_250
_250
// Convert request body to ArrayBuffer to get known length
_250
const arrayBuffer = await request.arrayBuffer();
_250
const uploadedPart = await multipartUpload.uploadPart(
_250
partNumber,
_250
arrayBuffer
_250
);
_250
_250
return API.success(
_250
{
_250
success: true,
_250
partNumber: uploadedPart.partNumber,
_250
etag: uploadedPart.etag,
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to upload part:", error);
_250
return API.error(error.message || "Failed to upload part", request, 400);
_250
}
_250
} catch (error) {
_250
console.error("Upload part error:", error);
_250
return API.error("Upload part failed", request, 500);
_250
}
_250
};
_250
_250
// Aborts a multipart upload
_250
export const DELETE: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (action !== "abort") {
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
_250
const uploadId = url.searchParams.get("uploadId");
_250
const key = url.searchParams.get("key");
_250
_250
if (!uploadId || !key) {
_250
return API.error("Missing uploadId or key", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(key, uploadId);
_250
await multipartUpload.abort();
_250
_250
return API.success(
_250
{
_250
success: true,
_250
message: "Multipart upload aborted successfully",
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to abort multipart upload:", error);
_250
return API.error(
_250
error.message || "Failed to abort multipart upload",
_250
request,
_250
400
_250
);
_250
}
_250
} catch (error) {
_250
console.error("Abort multipart upload error:", error);
_250
return API.error("Abort multipart upload failed", request, 500);
_250
}
_250
};
_250
_250
export const OPTIONS: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
return API.cors(request);
_250
};

Parse the action query parameter to route between different multipart upload operations. The POST endpoint supports both create and complete actions.

Initialize multipart upload session

multipart.ts

_250
import type { APIRoute } from "astro";
_250
import { API } from "../../utils/api";
_250
_250
interface MultipartUploadRequest {
_250
key: string;
_250
contentType?: string;
_250
}
_250
_250
interface CompleteMultipartRequest {
_250
uploadId: string;
_250
key: string;
_250
parts: R2UploadedPart[];
_250
}
_250
_250
// Creates and completes a new multipart upload session
_250
export const POST: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (!action) {
_250
return API.error("Missing action parameter", request, 400);
_250
}
_250
_250
switch (action) {
_250
case "create": {
_250
// Create a new multipart upload
_250
const body: MultipartUploadRequest = await request.json();
_250
_250
if (!body.key) {
_250
return API.error("Missing key parameter", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = await bucket.createMultipartUpload(body.key, {
_250
httpMetadata: body.contentType
_250
? {
_250
contentType: body.contentType,
_250
}
_250
: undefined,
_250
});
_250
_250
return API.success(
_250
{
_250
success: true,
_250
key: multipartUpload.key,
_250
uploadId: multipartUpload.uploadId,
_250
},
_250
request
_250
);
_250
} catch (error) {
_250
console.error("Failed to create multipart upload:", error);
_250
return API.error("Failed to create multipart upload", request, 500);
_250
}
_250
}
_250
_250
case "complete": {
_250
// Complete a multipart upload
_250
const body: CompleteMultipartRequest = await request.json();
_250
_250
if (!body.uploadId || !body.key || !body.parts) {
_250
return API.error("Missing required parameters", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(
_250
body.key,
_250
body.uploadId
_250
);
_250
_250
// Parts are already in R2UploadedPart format
_250
const r2Parts = body.parts;
_250
_250
const object = await multipartUpload.complete(r2Parts);
_250
_250
return API.success(
_250
{
_250
success: true,
_250
key: object.key,
_250
etag: object.httpEtag,
_250
size: object.size,
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to complete multipart upload:", error);
_250
return API.error(
_250
error.message || "Failed to complete multipart upload",
_250
request,
_250
400
_250
);
_250
}
_250
}
_250
_250
default:
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
} catch (error) {
_250
console.error("Multipart upload error:", error);
_250
return API.error("Multipart upload failed", request, 500);
_250
}
_250
};
_250
_250
// Uploads individual parts of a multipart upload
_250
export const PUT: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (action !== "upload-part") {
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
_250
const uploadId = url.searchParams.get("uploadId");
_250
const partNumberStr = url.searchParams.get("partNumber");
_250
const key = url.searchParams.get("key");
_250
_250
if (!uploadId || !partNumberStr || !key) {
_250
return API.error("Missing uploadId, partNumber, or key", request, 400);
_250
}
_250
_250
const partNumber = parseInt(partNumberStr);
_250
if (isNaN(partNumber) || partNumber < 1) {
_250
return API.error("Invalid part number", request, 400);
_250
}
_250
_250
if (!request.body) {
_250
return API.error("Missing request body", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(key, uploadId);
_250
_250
// Convert request body to ArrayBuffer to get known length
_250
const arrayBuffer = await request.arrayBuffer();
_250
const uploadedPart = await multipartUpload.uploadPart(
_250
partNumber,
_250
arrayBuffer
_250
);
_250
_250
return API.success(
_250
{
_250
success: true,
_250
partNumber: uploadedPart.partNumber,
_250
etag: uploadedPart.etag,
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to upload part:", error);
_250
return API.error(error.message || "Failed to upload part", request, 400);
_250
}
_250
} catch (error) {
_250
console.error("Upload part error:", error);
_250
return API.error("Upload part failed", request, 500);
_250
}
_250
};
_250
_250
// Aborts a multipart upload
_250
export const DELETE: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (action !== "abort") {
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
_250
const uploadId = url.searchParams.get("uploadId");
_250
const key = url.searchParams.get("key");
_250
_250
if (!uploadId || !key) {
_250
return API.error("Missing uploadId or key", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(key, uploadId);
_250
await multipartUpload.abort();
_250
_250
return API.success(
_250
{
_250
success: true,
_250
message: "Multipart upload aborted successfully",
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to abort multipart upload:", error);
_250
return API.error(
_250
error.message || "Failed to abort multipart upload",
_250
request,
_250
400
_250
);
_250
}
_250
} catch (error) {
_250
console.error("Abort multipart upload error:", error);
_250
return API.error("Abort multipart upload failed", request, 500);
_250
}
_250
};
_250
_250
export const OPTIONS: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
return API.cors(request);
_250
};

When action=create, this code establishes a new multipart upload session in Object Storage. The browser provides the key and contentType, then receives a unique uploadId that identifies the entire upload process.

Finalize multipart upload

multipart.ts

_250
import type { APIRoute } from "astro";
_250
import { API } from "../../utils/api";
_250
_250
interface MultipartUploadRequest {
_250
key: string;
_250
contentType?: string;
_250
}
_250
_250
interface CompleteMultipartRequest {
_250
uploadId: string;
_250
key: string;
_250
parts: R2UploadedPart[];
_250
}
_250
_250
// Creates and completes a new multipart upload session
_250
export const POST: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (!action) {
_250
return API.error("Missing action parameter", request, 400);
_250
}
_250
_250
switch (action) {
_250
case "create": {
_250
// Create a new multipart upload
_250
const body: MultipartUploadRequest = await request.json();
_250
_250
if (!body.key) {
_250
return API.error("Missing key parameter", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = await bucket.createMultipartUpload(body.key, {
_250
httpMetadata: body.contentType
_250
? {
_250
contentType: body.contentType,
_250
}
_250
: undefined,
_250
});
_250
_250
return API.success(
_250
{
_250
success: true,
_250
key: multipartUpload.key,
_250
uploadId: multipartUpload.uploadId,
_250
},
_250
request
_250
);
_250
} catch (error) {
_250
console.error("Failed to create multipart upload:", error);
_250
return API.error("Failed to create multipart upload", request, 500);
_250
}
_250
}
_250
_250
case "complete": {
_250
// Complete a multipart upload
_250
const body: CompleteMultipartRequest = await request.json();
_250
_250
if (!body.uploadId || !body.key || !body.parts) {
_250
return API.error("Missing required parameters", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(
_250
body.key,
_250
body.uploadId
_250
);
_250
_250
// Parts are already in R2UploadedPart format
_250
const r2Parts = body.parts;
_250
_250
const object = await multipartUpload.complete(r2Parts);
_250
_250
return API.success(
_250
{
_250
success: true,
_250
key: object.key,
_250
etag: object.httpEtag,
_250
size: object.size,
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to complete multipart upload:", error);
_250
return API.error(
_250
error.message || "Failed to complete multipart upload",
_250
request,
_250
400
_250
);
_250
}
_250
}
_250
_250
default:
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
} catch (error) {
_250
console.error("Multipart upload error:", error);
_250
return API.error("Multipart upload failed", request, 500);
_250
}
_250
};
_250
_250
// Uploads individual parts of a multipart upload
_250
export const PUT: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (action !== "upload-part") {
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
_250
const uploadId = url.searchParams.get("uploadId");
_250
const partNumberStr = url.searchParams.get("partNumber");
_250
const key = url.searchParams.get("key");
_250
_250
if (!uploadId || !partNumberStr || !key) {
_250
return API.error("Missing uploadId, partNumber, or key", request, 400);
_250
}
_250
_250
const partNumber = parseInt(partNumberStr);
_250
if (isNaN(partNumber) || partNumber < 1) {
_250
return API.error("Invalid part number", request, 400);
_250
}
_250
_250
if (!request.body) {
_250
return API.error("Missing request body", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(key, uploadId);
_250
_250
// Convert request body to ArrayBuffer to get known length
_250
const arrayBuffer = await request.arrayBuffer();
_250
const uploadedPart = await multipartUpload.uploadPart(
_250
partNumber,
_250
arrayBuffer
_250
);
_250
_250
return API.success(
_250
{
_250
success: true,
_250
partNumber: uploadedPart.partNumber,
_250
etag: uploadedPart.etag,
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to upload part:", error);
_250
return API.error(error.message || "Failed to upload part", request, 400);
_250
}
_250
} catch (error) {
_250
console.error("Upload part error:", error);
_250
return API.error("Upload part failed", request, 500);
_250
}
_250
};
_250
_250
// Aborts a multipart upload
_250
export const DELETE: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (action !== "abort") {
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
_250
const uploadId = url.searchParams.get("uploadId");
_250
const key = url.searchParams.get("key");
_250
_250
if (!uploadId || !key) {
_250
return API.error("Missing uploadId or key", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(key, uploadId);
_250
await multipartUpload.abort();
_250
_250
return API.success(
_250
{
_250
success: true,
_250
message: "Multipart upload aborted successfully",
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to abort multipart upload:", error);
_250
return API.error(
_250
error.message || "Failed to abort multipart upload",
_250
request,
_250
400
_250
);
_250
}
_250
} catch (error) {
_250
console.error("Abort multipart upload error:", error);
_250
return API.error("Abort multipart upload failed", request, 500);
_250
}
_250
};
_250
_250
export const OPTIONS: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
return API.cors(request);
_250
};

Once all parts are uploaded through the PUT endpoint, the browser will send a request to this POST endpoint with the action parameter set to complete with an ordered list of all uploaded parts with their corresponding entity tags (ETags).

When action=complete, this endpoint consolidates all uploaded parts into a single file in Object Storage using multipartUpload.complete() method

Create the POST Endpoint

The POST endpoint manages multipart uploads through two key operations: initializing new uploads and finalizing completed ones. The action query parameter determines which operation to perform.

Be sure to include the locals parameter in the POST endpoint to access your environment variables and CLOUD_FILES binding.

Setup CORS handling

Since multipart uploads require direct communication with the worker domain (not your Webflow Cloud domain), you need to set up proper CORS configuration. The imported API utility automatically handles CORS headers and preflight requests to ensure seamless cross-origin communication.

Access your Object Storage bucket

Retrieve the CLOUD_FILES binding that provides access to your Object Storage bucket. This binding contains all the multipart upload methods you'll need throughout the upload process.

Get Action Parameter

Parse the action query parameter to route between different multipart upload operations. The POST endpoint supports both create and complete actions.

Initialize multipart upload session

When action=create, this code establishes a new multipart upload session in Object Storage. The browser provides the key and contentType, then receives a unique uploadId that identifies the entire upload process.

Finalize multipart upload

Once all parts are uploaded through the PUT endpoint, the browser will send a request to this POST endpoint with the action parameter set to complete with an ordered list of all uploaded parts with their corresponding entity tags (ETags).

When action=complete, this endpoint consolidates all uploaded parts into a single file in Object Storage using multipartUpload.complete() method

multipart.ts
ExpandClose

_250
import type { APIRoute } from "astro";
_250
import { API } from "../../utils/api";
_250
_250
interface MultipartUploadRequest {
_250
key: string;
_250
contentType?: string;
_250
}
_250
_250
interface CompleteMultipartRequest {
_250
uploadId: string;
_250
key: string;
_250
parts: R2UploadedPart[];
_250
}
_250
_250
// Creates and completes a new multipart upload session
_250
export const POST: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (!action) {
_250
return API.error("Missing action parameter", request, 400);
_250
}
_250
_250
switch (action) {
_250
case "create": {
_250
// Create a new multipart upload
_250
const body: MultipartUploadRequest = await request.json();
_250
_250
if (!body.key) {
_250
return API.error("Missing key parameter", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = await bucket.createMultipartUpload(body.key, {
_250
httpMetadata: body.contentType
_250
? {
_250
contentType: body.contentType,
_250
}
_250
: undefined,
_250
});
_250
_250
return API.success(
_250
{
_250
success: true,
_250
key: multipartUpload.key,
_250
uploadId: multipartUpload.uploadId,
_250
},
_250
request
_250
);
_250
} catch (error) {
_250
console.error("Failed to create multipart upload:", error);
_250
return API.error("Failed to create multipart upload", request, 500);
_250
}
_250
}
_250
_250
case "complete": {
_250
// Complete a multipart upload
_250
const body: CompleteMultipartRequest = await request.json();
_250
_250
if (!body.uploadId || !body.key || !body.parts) {
_250
return API.error("Missing required parameters", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(
_250
body.key,
_250
body.uploadId
_250
);
_250
_250
// Parts are already in R2UploadedPart format
_250
const r2Parts = body.parts;
_250
_250
const object = await multipartUpload.complete(r2Parts);
_250
_250
return API.success(
_250
{
_250
success: true,
_250
key: object.key,
_250
etag: object.httpEtag,
_250
size: object.size,
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to complete multipart upload:", error);
_250
return API.error(
_250
error.message || "Failed to complete multipart upload",
_250
request,
_250
400
_250
);
_250
}
_250
}
_250
_250
default:
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
} catch (error) {
_250
console.error("Multipart upload error:", error);
_250
return API.error("Multipart upload failed", request, 500);
_250
}
_250
};
_250
_250
// Uploads individual parts of a multipart upload
_250
export const PUT: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (action !== "upload-part") {
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
_250
const uploadId = url.searchParams.get("uploadId");
_250
const partNumberStr = url.searchParams.get("partNumber");
_250
const key = url.searchParams.get("key");
_250
_250
if (!uploadId || !partNumberStr || !key) {
_250
return API.error("Missing uploadId, partNumber, or key", request, 400);
_250
}
_250
_250
const partNumber = parseInt(partNumberStr);
_250
if (isNaN(partNumber) || partNumber < 1) {
_250
return API.error("Invalid part number", request, 400);
_250
}
_250
_250
if (!request.body) {
_250
return API.error("Missing request body", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(key, uploadId);
_250
_250
// Convert request body to ArrayBuffer to get known length
_250
const arrayBuffer = await request.arrayBuffer();
_250
const uploadedPart = await multipartUpload.uploadPart(
_250
partNumber,
_250
arrayBuffer
_250
);
_250
_250
return API.success(
_250
{
_250
success: true,
_250
partNumber: uploadedPart.partNumber,
_250
etag: uploadedPart.etag,
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to upload part:", error);
_250
return API.error(error.message || "Failed to upload part", request, 400);
_250
}
_250
} catch (error) {
_250
console.error("Upload part error:", error);
_250
return API.error("Upload part failed", request, 500);
_250
}
_250
};
_250
_250
// Aborts a multipart upload
_250
export const DELETE: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
_250
// Handle CORS preflight requests
_250
if (request.method === "OPTIONS") {
_250
console.log("CORS preflight request from:", request.headers.get("Origin"));
_250
return API.cors(request);
_250
}
_250
_250
try {
_250
// Check if bucket is available
_250
const bucket = locals.runtime.env.CLOUD_FILES;
_250
if (!bucket) {
_250
return API.error("Cloud storage not configured", request, 500);
_250
}
_250
_250
const url = new URL(request.url);
_250
const action = url.searchParams.get("action");
_250
_250
if (action !== "abort") {
_250
return API.error(`Unknown action: ${action}`, request, 400);
_250
}
_250
_250
const uploadId = url.searchParams.get("uploadId");
_250
const key = url.searchParams.get("key");
_250
_250
if (!uploadId || !key) {
_250
return API.error("Missing uploadId or key", request, 400);
_250
}
_250
_250
try {
_250
const multipartUpload = bucket.resumeMultipartUpload(key, uploadId);
_250
await multipartUpload.abort();
_250
_250
return API.success(
_250
{
_250
success: true,
_250
message: "Multipart upload aborted successfully",
_250
},
_250
request
_250
);
_250
} catch (error: any) {
_250
console.error("Failed to abort multipart upload:", error);
_250
return API.error(
_250
error.message || "Failed to abort multipart upload",
_250
request,
_250
400
_250
);
_250
}
_250
} catch (error) {
_250
console.error("Abort multipart upload error:", error);
_250
return API.error("Abort multipart upload failed", request, 500);
_250
}
_250
};
_250
_250
export const OPTIONS: APIRoute = async ({ request, locals }) => {
_250
// Set the origin for the API
_250
API.init(locals.runtime.env.ORIGIN);
_250
return API.cors(request);
_250
};