@@ -4,11 +4,13 @@ import {
44} from '@/lib/uploads/shared/types'
55
66export const MULTI_FILE_UPLOAD_MAX_FILES = 20
7+ const MULTI_FILE_UPLOAD_MAX_TOTAL_FILE_EQUIVALENTS = 5
78export const MULTI_FILE_UPLOAD_MAX_FILE_BYTES = Math . min (
89 MAX_KNOWLEDGE_DOCUMENT_FILE_SIZE ,
910 MAX_WORKSPACE_FORMDATA_FILE_SIZE
1011)
11- export const MULTI_FILE_UPLOAD_MAX_TOTAL_BYTES = 5 * MULTI_FILE_UPLOAD_MAX_FILE_BYTES
12+ export const MULTI_FILE_UPLOAD_MAX_TOTAL_BYTES =
13+ MULTI_FILE_UPLOAD_MAX_TOTAL_FILE_EQUIVALENTS * MULTI_FILE_UPLOAD_MAX_FILE_BYTES
1214
1315export type MultiFileUploadAdmissionErrorCode =
1416 | 'UPLOAD_FILE_COUNT_EXCEEDED'
@@ -32,18 +34,29 @@ interface UploadAdmissionFile {
3234
3335interface MultiFileUploadAdmissionOptions {
3436 existingFiles ?: ArrayLike < UploadAdmissionFile >
37+ maxFileBytes ?: number
38+ maxTotalBytes ?: number
3539}
3640
3741/**
3842 * Bounds one user upload action before previews, UI rows, or upload sessions are allocated.
39- * Both consumers accept 100 MiB files, so five maximum-sized files form the 500 MiB aggregate
40- * budget while the count cap still bounds actions containing many small files .
43+ * Knowledge uploads use the shared 100 MiB / 500 MiB defaults. Direct-to-storage consumers may
44+ * provide their larger server-side limit while retaining the same count and aggregate bounds .
4145 */
4246export function assertMultiFileUploadAdmission (
4347 files : ArrayLike < UploadAdmissionFile > ,
4448 options : MultiFileUploadAdmissionOptions = { }
4549) : void {
4650 const existingFiles = options . existingFiles
51+ const maxFileBytes = options . maxFileBytes ?? MULTI_FILE_UPLOAD_MAX_FILE_BYTES
52+ const maxTotalBytes =
53+ options . maxTotalBytes ?? MULTI_FILE_UPLOAD_MAX_TOTAL_FILE_EQUIVALENTS * maxFileBytes
54+ if ( ! Number . isSafeInteger ( maxFileBytes ) || maxFileBytes < 1 ) {
55+ throw new Error ( 'Invalid per-file upload limit' )
56+ }
57+ if ( ! Number . isSafeInteger ( maxTotalBytes ) || maxTotalBytes < maxFileBytes ) {
58+ throw new Error ( 'Invalid aggregate upload limit' )
59+ }
4760 const existingCount = existingFiles ?. length ?? 0
4861 const totalCount = existingCount + files . length
4962 if ( totalCount > MULTI_FILE_UPLOAD_MAX_FILES ) {
@@ -61,21 +74,29 @@ export function assertMultiFileUploadAdmission(
6174 if ( ! file || ! Number . isSafeInteger ( file . size ) || file . size < 0 ) {
6275 throw new Error ( 'Invalid file size in upload selection' )
6376 }
64- if ( file . size > MULTI_FILE_UPLOAD_MAX_FILE_BYTES ) {
77+ if ( file . size > maxFileBytes ) {
6578 const label = file . name ? `"${ file . name } "` : 'A selected file'
6679 throw new MultiFileUploadAdmissionError (
67- `${ label } is too large. Each file must be 100 MiB or smaller.` ,
80+ `${ label } is too large. Each file must be ${ formatBinaryBytes ( maxFileBytes ) } or smaller.` ,
6881 'UPLOAD_FILE_SIZE_EXCEEDED'
6982 )
7083 }
7184 totalBytes += file . size
7285 }
7386 }
7487
75- if ( totalBytes > MULTI_FILE_UPLOAD_MAX_TOTAL_BYTES ) {
88+ if ( totalBytes > maxTotalBytes ) {
7689 throw new MultiFileUploadAdmissionError (
77- ' Select files totaling 500 MiB or less.' ,
90+ ` Select files totaling ${ formatBinaryBytes ( maxTotalBytes ) } or less.` ,
7891 'UPLOAD_TOTAL_SIZE_EXCEEDED'
7992 )
8093 }
8194}
95+
96+ function formatBinaryBytes ( bytes : number ) : string {
97+ const gibibyte = 1024 ** 3
98+ if ( bytes % gibibyte === 0 ) return `${ bytes / gibibyte } GiB`
99+ const mebibyte = 1024 ** 2
100+ if ( bytes % mebibyte === 0 ) return `${ bytes / mebibyte } MiB`
101+ return `${ bytes } bytes`
102+ }
0 commit comments