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 PUT Endpoint

The PUT endpoint handles uploading individual file parts. Each part is uploaded as a separate request, allowing browsers to upload multiple parts concurrently for better performance.

Include the locals parameter, and set up CORS preflight requests.

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. The PUT endpoint only supports the upload-part action. If the action isn't upload-part, the endpoint returns an error.

Validate part upload parameters

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
};

Extract and validate the required parameters for part uploads, including the uploadId, partNumber, and key. Each part must have a unique number starting from 1, and all parts must be associated with the same upload session.

The client will send the correct uploadId, partNumber, and key for each part.

Upload individual part

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
};

Process a single file part upload. The request body contains the binary data for that specific chunk of the file.

What happens:

  • Resumes the multipart upload using the uploadId
  • Converts the request body to ArrayBuffer for Object Storage compatibility
  • Uploads the part with its assigned partNumber
  • Returns the partNumber and etag for tracking

Return the uploaded part

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
};

After successfully uploading a part, return a response containing:

  • success: true - Confirms the operation completed successfully
  • partNumber - The sequential number of this part
  • etag - The entity tag for this specific part, used for integrity verification

The client should store both the partNumber and etag for each uploaded part. These values are required when completing the multipart upload to ensure all parts are included and haven't been corrupted during transfer.

Create the PUT Endpoint

The PUT endpoint handles uploading individual file parts. Each part is uploaded as a separate request, allowing browsers to upload multiple parts concurrently for better performance.

Include the locals parameter, and set up CORS preflight requests.

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. The PUT endpoint only supports the upload-part action. If the action isn't upload-part, the endpoint returns an error.

Validate part upload parameters

Extract and validate the required parameters for part uploads, including the uploadId, partNumber, and key. Each part must have a unique number starting from 1, and all parts must be associated with the same upload session.

The client will send the correct uploadId, partNumber, and key for each part.

Upload individual part

Process a single file part upload. The request body contains the binary data for that specific chunk of the file.

What happens:

  • Resumes the multipart upload using the uploadId
  • Converts the request body to ArrayBuffer for Object Storage compatibility
  • Uploads the part with its assigned partNumber
  • Returns the partNumber and etag for tracking

Return the uploaded part

After successfully uploading a part, return a response containing:

  • success: true - Confirms the operation completed successfully
  • partNumber - The sequential number of this part
  • etag - The entity tag for this specific part, used for integrity verification

The client should store both the partNumber and etag for each uploaded part. These values are required when completing the multipart upload to ensure all parts are included and haven't been corrupted during transfer.

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
};