MultipartReader
MultipartReader parses a multipart/form-data request body and exposes each part as a typed field or file item. It streams the incoming body using busboy and writes uploaded files to the OS temp directory.
Package: @opra/http
Obtaining an instance
Do not construct MultipartReader directly. Obtain one from the operation context:
import { HttpContext, MultipartReader } from '@opra/http';
async upload(ctx: HttpContext) {
if (!ctx.isMultipart) return;
const reader = await ctx.getMultipartReader();
}
Methods
getAll()
getAll(): Promise<MultipartReader.Item[]>
Reads all remaining parts from the stream and returns them as an array. Waits until the entire request body has been consumed.
Use getAll() when the upload is small and bounded, or when you need all parts before starting any processing.
const parts = await reader.getAll();
for (const part of parts) {
if (part.kind === 'file') {
await store(part.storedPath, part.filename);
}
if (part.kind === 'field') {
console.log(part.field, part.value);
}
}
await reader.purge(); // delete temp files when done
getNext()
getNext(): Promise<MultipartReader.Item | undefined>
Reads and returns the next part from the stream. Returns undefined when all parts have been consumed.
Use getNext() for large uploads or when you want to start processing a part before the rest of the body has arrived.
let part: MultipartReader.Item | undefined;
while ((part = await reader.getNext())) {
if (part.kind === 'file') {
// File is already written to disk at part.storedPath.
// Start processing immediately — the stream continues in parallel.
await processFile(part.storedPath);
await part.delete(); // free disk space as soon as possible
}
if (part.kind === 'field') {
options[part.field] = part.value;
}
}
If a field declared as required in .MultipartContent() was never sent by the client, getNext() throws a BadRequestError after the last part is consumed and the stream closes.
cancel()
cancel(): void
Aborts stream processing. No further parts are emitted after cancel() is called. Useful when an early validation error makes reading the rest of the body unnecessary.
const part = await reader.getNext();
if (!isAllowed(part)) {
reader.cancel();
throw new ForbiddenError('Upload not permitted');
}
purge()
purge(): Promise<void>
Deletes all temporary files created during this request. By default, uploaded files are automatically deleted when the LocalFile object is garbage-collected (autoDelete: true). Call purge() to delete them immediately after you are done processing.
const parts = await reader.getAll();
try {
await processAll(parts);
} finally {
await reader.purge();
}
Properties
| Property | Type | Description |
|---|---|---|
items | MultipartReader.Item[] | All parts received so far (grows as the stream is consumed). |
scope | string | undefined | Validation scope applied to field decoding. |
Events
MultipartReader extends EventEmitter. Listen to events for reactive processing instead of polling with getNext().
| Event | Payload | Emitted when |
|---|---|---|
'item' | MultipartReader.Item | Any part (field or file) becomes available. |
'field' | MultipartReader.Field | A form field part becomes available. |
'file' | MultipartReader.File | A file part finishes writing to disk. |
'error' | Error | The underlying stream encounters an error. |
reader.on('file', (file: MultipartReader.File) => {
console.log(`Received file: ${file.filename} (${file.size} bytes)`);
});
reader.on('field', (field: MultipartReader.Field) => {
console.log(`Received field: ${field.field} = ${field.value}`);
});
reader.resume(); // start the stream when using event mode
Interfaces
MultipartReader.Field
Represents a non-file form field. The value property is decoded and validated against the type declared in .Field() on the operation.
| Property | Type | Description |
|---|---|---|
kind | 'field' | Discriminator. |
field | string | Field name as declared in .Field(fieldName, ...). |
value | any | Decoded field value. Type is determined by the schema declaration. |
mimeType | string | undefined | MIME type reported by the client. |
encoding | string | undefined | Transfer encoding reported by the client. |
MultipartReader.File
Represents an uploaded file. Extends LocalFile. The file is written to a temporary path on disk before this item is emitted.
| Property | Type | Description |
|---|---|---|
kind | 'file' | Discriminator. |
field | string | Field name as declared in .File(fieldName, ...). |
storedPath | string | Absolute path to the temp file on disk. |
filename | string | Original file name sent by the client. |
type | string | undefined | MIME type reported by the client, e.g. 'image/png'. |
encoding | BufferEncoding | undefined | Transfer encoding of the file. |
size | number | File size in bytes (reads fs.statSync). |
autoDelete | boolean | When true, the temp file is deleted automatically when the object is GC'd. Defaults to true. |
| Method | Returns | Description |
|---|---|---|
buffer() | Promise<Buffer> | Reads the temp file into memory as a Buffer. |
text() | Promise<string> | Reads the temp file as a UTF-8 string. |
delete() | Promise<void> | Deletes the temp file immediately. |
MultipartReader.Options
Options are passed internally by the adapter based on the .MultipartContent() declaration. The following properties come from busboy's config and are set via the first argument to .MultipartContent():
| Option | Type | Description |
|---|---|---|
tempDirectory | string | Directory where uploaded files are written. Defaults to os.tmpdir(). |
scope | string | Validation scope for field decoding. |
highWaterMark | number | Stream high-water mark in bytes. |
fileHwm | number | File stream high-water mark in bytes. |
defCharset | string | Default character set for non-file fields. |
defParamCharset | string | Default character set for parameter values. |
preservePath | boolean | Preserve the full path in filename. |
limits.fieldNameSize | number | Max field name size in bytes. |
limits.fieldSize | number | Max field value size in bytes. |
limits.fields | number | Max number of non-file fields. |
limits.fileSize | number | Max file size in bytes. Maps to maxFileSize in .MultipartContent(). |
limits.files | number | Max number of file fields. Maps to maxFiles in .MultipartContent(). |
limits.parts | number | Max number of parts (fields + files). |
limits.headerPairs | number | Max number of header key-value pairs per part. |