gongxiangp2/lib/multipart-route-limits.ts
chenpei 7326e273e1 fix: harden media upload and release safety
Protect upload cleanup and atomic completion paths, add durable media worker leases and brand asset validation, and introduce verified migration/bootstrap/deployment gates.
2026-08-12 18:04:08 +08:00

52 lines
1.7 KiB
TypeScript

import {
MaterialUploadRequestError,
MaterialUploadRequestLimiter,
} from '@/lib/material-upload-request-guard'
import { MultipartRequestError } from '@/lib/multipart-request-guard'
const MEBIBYTE = 1024 * 1024
/**
* requestBytes intentionally include multipart framing headroom. File limits
* remain the business boundary and must be rechecked after formData parsing.
*/
export const MULTIPART_ROUTE_LIMITS = Object.freeze({
legacyS3RequestBytes: 102 * MEBIBYTE,
legacyS3FileBytes: 100 * MEBIBYTE,
previewRequestBytes: 52 * MEBIBYTE,
previewFileBytes: 50 * MEBIBYTE,
skuArchiveRequestBytes: 130 * MEBIBYTE,
skuArchiveFileBytes: 128 * MEBIBYTE,
avatarRequestBytes: 6 * MEBIBYTE,
avatarFileBytes: 5 * MEBIBYTE,
materialUpdateRequestBytes: 10 * MEBIBYTE,
materialUpdatePreviewBytes: 8 * MEBIBYTE,
logoRequestBytes: 6 * MEBIBYTE,
logoFileBytes: 5 * MEBIBYTE,
psdRequestBytes: 130 * MEBIBYTE,
whiteBackgroundRequestBytes: 105 * MEBIBYTE,
})
// This limiter starts before formData(), so at most two large parsed multipart
// bodies can coexist. CPU-heavy image/PDF work still has its own single slot.
export const boundedMultipartRequestLimiter = new MaterialUploadRequestLimiter(2, 16)
export function getBoundedMultipartFailure(error: unknown): {
message: string
status: number
code: string
retryAfterSeconds?: number
} | null {
if (error instanceof MultipartRequestError || error instanceof MaterialUploadRequestError) {
return {
message: error.message,
status: error.status,
code: error.code,
...('retryAfterSeconds' in error && error.retryAfterSeconds
? { retryAfterSeconds: error.retryAfterSeconds }
: {}),
}
}
return null
}