| 92 | * error on `result.file.stream`. |
| 93 | */ |
| 94 | export function readMultipart( |
| 95 | request: MultipartRequest, |
| 96 | options: ReadMultipartOptions |
| 97 | ): Promise<ParsedMultipart> { |
| 98 | const { maxFileBytes, requiredFieldsBeforeFile = [], fileFieldName = 'file', signal } = options |
| 99 | |
| 100 | return new Promise<ParsedMultipart>((resolve, reject) => { |
| 101 | const contentType = request.headers.get('content-type') |
| 102 | if (!contentType || !contentType.toLowerCase().includes('multipart/form-data')) { |
| 103 | reject(new MultipartError('NOT_MULTIPART', 'Expected multipart/form-data request')) |
| 104 | return |
| 105 | } |
| 106 | if (!request.body) { |
| 107 | reject(new MultipartError('NO_BODY', 'Request has no body')) |
| 108 | return |
| 109 | } |
| 110 | |
| 111 | let bb: busboy.Busboy |
| 112 | try { |
| 113 | bb = busboy({ |
| 114 | headers: { 'content-type': contentType }, |
| 115 | limits: { fileSize: maxFileBytes, files: 1 }, |
| 116 | }) |
| 117 | } catch (err) { |
| 118 | reject( |
| 119 | new MultipartError( |
| 120 | 'NOT_MULTIPART', |
| 121 | err instanceof Error ? err.message : 'Invalid multipart request' |
| 122 | ) |
| 123 | ) |
| 124 | return |
| 125 | } |
| 126 | |
| 127 | // double-cast-allowed: the web ReadableStream on request.body isn't structurally assignable to the Node type Readable.fromWeb expects |
| 128 | const nodeStream = Readable.fromWeb(request.body as unknown as NodeReadableStream<Uint8Array>) |
| 129 | const fields: Record<string, string> = {} |
| 130 | let settled = false |
| 131 | let fileSeen = false |
| 132 | |
| 133 | const onAbort = () => { |
| 134 | const reason = signal?.reason instanceof Error ? signal.reason : new Error('Aborted') |
| 135 | nodeStream.destroy(reason) |
| 136 | bb.destroy() |
| 137 | if (!settled) { |
| 138 | settled = true |
| 139 | reject(reason) |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | const cleanup = () => { |
| 144 | signal?.removeEventListener('abort', onAbort) |
| 145 | } |
| 146 | |
| 147 | const settle = (fn: () => void) => { |
| 148 | if (settled) return |
| 149 | settled = true |
| 150 | cleanup() |
| 151 | fn() |