UNPKG

@mjackson/form-data-parser

Version:

A request.formData() wrapper with streaming file upload handling

132 lines (115 loc) 4 kB
import type { MultipartParserOptions } from '@mjackson/multipart-parser'; import { isMultipartRequest, parseMultipartRequest, MultipartPart, } from '@mjackson/multipart-parser'; /** * The base class for errors thrown by the form data parser. */ export class FormDataParseError extends Error { constructor(message: string) { super(message); this.name = 'FormDataParseError'; } } /** * An error thrown when the maximum number of files allowed in a request is exceeded. */ export class MaxFilesExceededError extends FormDataParseError { constructor(maxFiles: number) { super(`Maximum number of files exceeded: ${maxFiles}`); this.name = 'MaxFilesExceededError'; } } /** * A file that was uploaded as part of a `multipart/form-data` request. */ export class FileUpload extends File { /** * The name of the <input> field used to upload the file. */ readonly fieldName: string; constructor(part: MultipartPart, fieldName: string) { super(part.content, part.filename ?? 'file-upload', { type: part.mediaType ?? 'application/octet-stream', }); this.fieldName = fieldName; } } /** * A function used for handling file uploads. */ export interface FileUploadHandler { (file: FileUpload): void | null | string | Blob | Promise<void | null | string | Blob>; } async function defaultFileUploadHandler(file: FileUpload): Promise<File> { // By default just keep the file around in memory. return file; } export interface ParseFormDataOptions extends MultipartParserOptions { /** * The maximum number of files that can be uploaded in a single request. * If this limit is exceeded, a `MaxFilesExceededError` will be thrown. * * Default: 20 */ maxFiles?: number; } /** * Parses a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) body into a [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) * object. This is useful when accessing the data contained in a HTTP `multipart/form-data` request * generated by a HTML `<form>` element. * * This is a drop-in replacement for [the built-in `request.formData()` API](https://developer.mozilla.org/en-US/docs/Web/API/Request/formData) * with the main difference being the ability to customize the handling of file uploads. Instead of * keeping all files in memory, the `uploadHandler` allows you to store the file on disk or a * cloud storage service. * * @param request The `Request` object to parse * @param uploadHandler A function that handles file uploads. It receives a `File` object and may return any value that is valid in a `FormData` object * @return A `Promise` that resolves to a `FormData` object containing the parsed data */ export async function parseFormData( request: Request, uploadHandler?: FileUploadHandler, ): Promise<FormData>; export async function parseFormData( request: Request, options: ParseFormDataOptions, uploadHandler?: FileUploadHandler, ): Promise<FormData>; export async function parseFormData( request: Request, options?: ParseFormDataOptions | FileUploadHandler, uploadHandler: FileUploadHandler = defaultFileUploadHandler, ): Promise<FormData> { if (typeof options === 'function') { uploadHandler = options; options = {}; } else if (options == null) { options = {}; } if (!isMultipartRequest(request)) { return request.formData(); } let { maxFiles = 20, ...parserOptions } = options; let formData = new FormData(); let fileCount = 0; for await (let part of parseMultipartRequest(request, parserOptions)) { let fieldName = part.name; if (!fieldName) continue; if (part.isFile) { if (++fileCount > maxFiles) { throw new MaxFilesExceededError(maxFiles); } let value = await uploadHandler(new FileUpload(part, fieldName)); if (value != null) { formData.append(fieldName, value); } } else { formData.append(fieldName, part.text); } } return formData; }