This is the changelog for multipart-parser. It follows semantic versioning.
- Bumped
@remix-run/*dependencies:
- Bumped
@remix-run/*dependencies:
-
Avoid using Web Encoding globals while importing the multipart parser and while scanning multipart syntax. Header and text decoding now creates the UTF-8 decoder lazily when decoded part fields are accessed.
-
Bumped
@remix-run/*dependencies:
-
BREAKING CHANGE:
MultipartPart.headersis now a plain decoded object keyed by lower-case header name instead of a nativeHeadersinstance. Access part headers with bracket notation likepart.headers['content-type']instead ofpart.headers.get('Content-Type').This lets multipart part headers preserve decoded UTF-8 field names and filenames that native
Headerscannot store.
-
BREAKING CHANGE:
parseMultipart(),parseMultipartStream(), andparseMultipartRequest()now enforce finite defaultmaxPartsandmaxTotalSizelimits, and addMaxPartsExceededErrorandMaxTotalSizeExceededErrorfor handling multipart envelope limit failures.Apps that intentionally accept large multipart requests may need to raise
maxPartsormaxTotalSizeexplicitly.
- Changed
@remix-run/*peer dependencies to regular dependencies
- Update
@remix-run/headerspeer dependency to use the new header parsing methods.
- Move
@remix-run/headerstopeerDependencies
- Build using
tscinstead ofesbuild. This means modules in thedistdirectory now mirror the layout of modules in thesrcdirectory.
- BREAKING CHANGE: Removed CommonJS build. This package is now ESM-only. If you need to use this package in a CommonJS project, you will need to use dynamic
import().
- Renamed package from
@mjackson/multipart-parserto@remix-run/multipart-parser
- Add doc comments on custom error classes
This release represents a major refactoring and simplification of this library from a async/promise-based architecture to a generator that suspends the parser as parts are found.
This is a reversion to the generator-based interface used before v0.8 when I switched to a promise interface to get around deadlock issues with consuming part streams inside a yield suspension point. The deadlock occurred when trying to read part.body inside a yield, because the parser was suspended and wouldn't emit any more bytes to the stream while the consumer was waiting for the stream to complete.
With this release, I realized that instead of getting rid of the generator, which is actually a fantastic interface for a streaming parser, I should've gotten rid of the part.body stream instead and replaced it with a part.content property that contains all the content for that part. This gives us a better parser interface and also makes error handling simpler when e.g. the parser's maxFileSize is exceeded. This also makes the parser easier to use because you don't have to e.g. await part.text() anymore, and you have access to part.size up front.
- BREAKING CHANGE:
parseMultipartandparseMultipartRequestare now generators thatyieldMultipartPartobjects as they are parsed - BREAKING CHANGE:
parseMultipartno longer parses streams, useparseMultipartStreaminstead - BREAKING CHANGE:
parser.parse()is removed - BREAKING CHANGE:
part.body,part.bodyUsedare removed - BREAKING CHANGE:
part.arrayBuffer,part.bytes,part.textare now sync getters instead ofasyncmethods - BREAKING CHANGE: Default
maxFileSizeis now 2MiB, same as PHP's defaultupload_max_filesize
New APIs:
parseMultipartStream(stream, options)is a generator that parses a stream of dataparser.write(chunk)andparser.finish()are low-level APIs for running the parser directlypart.contentis aUint8Array[]of all content in that partpart.isTextistrueif the part originates from a text fieldpart.sizeis the total size of the content in bytes
If you're upgrading, check the README for current usage recommendations. Here's a high-level taste of the before/after of this release.
import { parseMultipartRequest } from '@remix-run/multipart-parser'
// before
await parseMultipartRequest(request, async (part) => {
let buffer = await part.arrayBuffer()
// ...
})
// after
for await (let part of parseMultipartRequest(request)) {
let buffer = part.arrayBuffer
// ...
}- Add
/srcto npm package, so "go to definition" goes to the actual source - Use one set of types for all built files, instead of separate types for ESM and CJS
- Build using esbuild directly instead of tsup
- Add
Promise<void>toMultipartPartHandlerreturn type
- Fix bad publish that left a
workspace:^version identifier in package.json
This release improves error handling and simplifies some of the internals of the parser.
- BREAKING CHANGE: Change
parseMultipartRequestandparseMultipartinterfaces fromfor await...oftoawait+ callback API.
import { parseMultipartRequest } from '@remix-run/multipart-parser'
// before
for await (let part of parseMultipartRequest(request)) {
// ...
}
// after
await parseMultipartRequest(request, (part) => {
// ...
})This change greatly simplifies the implementation of parseMultipartRequest/parseMultipart and fixes a subtle bug that did not properly catch parse errors when maxFileSize was exceeded (see #28).
- Add
MaxHeaderSizeExceededErrorandMaxFileSizeExceededErrorto make it easier to have finer-grained error handling.
import * as http from 'node:http'
import {
MultipartParseError,
MaxFileSizeExceededError,
parseMultipartRequest,
} from '@remix-run/multipart-parser/node'
const tenMb = 10 * Math.pow(2, 20)
const server = http.createServer(async (req, res) => {
try {
await parseMultipartRequest(req, { maxFileSize: tenMb }, (part) => {
// ...
})
} catch (error) {
if (error instanceof MaxFileSizeExceededError) {
res.writeHead(413)
res.end(error.message)
} else if (error instanceof MultipartParseError) {
res.writeHead(400)
res.end('Invalid multipart request')
} else {
console.error(error)
res.writeHead(500)
res.end('Internal Server Error')
}
}
})- Add support for environments that do not support
ReadableStream.prototype[Symbol.asyncIterator](i.e. Safari), see #46
- Fix dependency on
headersin package.json
-
Re-export everything from
multipart-parser/node. If you're usingmultipart-parser/node, you shouldimporteverything from there. Don't import anything frommultipart-parser. -
Added CommonJS build
- Moved to a new monorepo
- Provide correct type for
part.arrayBuffer() part.isFilenow correctly detectspart.mediaType === 'application/octet-stream'
- More small performance improvements
- BREAKING: Removed some low-level API (
parser.push()andparser.reset()) that was duplicating higher-level API. Useparser.parse()instead. - Added
parser.maxHeaderSizeandparser.maxFileSizeproperties - Small performance improvements when parsing large files
- Change default
maxFileSizefrom 10 MB toInfinity - Simplify internal buffer management and search, which leads to more consistent chunk flow when handling large file uploads
- Fix bug where max file size exceeded error would crash Node.js servers (mjackson/multipart-parser#8)
- Add
typekeyword toMultipartParserOptionsexport for Deno (mjackson/multipart-parser#11)
- Switch dependency from
fetch-super-headersto@remix-run/headers - Use
for await...ofto iterate overReadableStreaminternally. This will also cancel the stream when the loop exits from e.g. an error in a user-definedparthandler.