Skip to content

chore: release 4.0.0 - #174

Merged
bjohansebas merged 1 commit into
masterfrom
release-4.0.0
Jul 12, 2026
Merged

chore: release 4.0.0#174
bjohansebas merged 1 commit into
masterfrom
release-4.0.0

Conversation

@bjohansebas

Copy link
Copy Markdown
Collaborator

I'm planning to launch this on Saturday or Sunday.

⚠️ Breaking changes

  • Remove support for Node <22 - by @bjohansebas in #156

    Node.js versions prior to 22 are no longer supported. This allows the package to migrate to ESM and rely on require(esm), which does not work correctly on earlier versions.

  • Migrate to TypeScript and publish as ESM-only - by @bjohansebas in #157

    CommonJS consumers can keep loading the package through require(esm).

  • Use native TextDecoder instead of iconv-lite - by @bjohansebas in #143

    Supported encodings are now those of the WHATWG Encoding Standard; encodings outside the standard (e.g. UTF-32) now throw a 415 error. utf-16 no longer detects a big-endian BOM and always decodes as little-endian; use utf-16be for big-endian content.

  • Remove streams1 support - by @bjohansebas in #144 and #161

    Node streams that emit string chunks now error with a 500 stream.encoding.set through the callback (previously they were decoded silently, or crashed the process when no encoding was set), matching the web stream path. Streams are expected to implement the readable stream interface (unpipe, pause).

  • Validate the limit option - by @bjohansebas in #162

    A limit that does not parse to a byte count (e.g. 'banana', NaN) now throws a TypeError instead of silently reading with no limit at all. limit: null remains the explicit way to disable the limit.

🚀 Improvements

  • Support reading WHATWG ReadableStream (web streams) - by @bjohansebas in #148 and #160

    fetch Request/Response bodies, Blob.stream(), TransformStream readables, and Readable.toWeb() bridges can be read directly. Streams already locked, read, or cancelled error with a 500 stream.not.readable; client aborts are mapped to the same 400 request.aborted error as node streams, with the original error in cause; string chunks are accepted without an encoding (UTF-8 Buffer), but error with a 500 stream.encoding.set when combined with one, since the stream is already decoded; non-byte chunks (e.g. ArrayBuffer) error with a TypeError. On error the reader lock is released, but the stream is not cancelled; disposing it is up to the caller.

  • Add a custom decoder option - by @bjohansebas in #145

    A function compatible with iconv-lite's getDecoder can be plugged in to decode encodings outside the WHATWG Encoding Standard.

  • Use native stream.unpipe() and remove the unpipe dependency - by @Phillip9587 in #93

  • Remove the check for a global Promise when no callback is provided - by @bjohansebas in #146

  • Add a benchmark suite - by @bjohansebas in #163

    npm run bench compares the node and web stream paths with realistic request bodies.

  • Fail as soon as a stream exceeds its declared length - by @bjohansebas in #172

    A body that sends more bytes than its declared length now errors with a 400 request.size.invalid as soon as the excess arrives, rather than buffering the rest first. This bounds memory to length even when no limit is set. When both are set, a length overrun is reported before the limit's 413.

🐞 Bug fixes

  • Fix a length that does not parse to a number failing every request - by @bjohansebas in #172

    Values like '' passed the numeric pre-check but parsed to NaN, so the size check could never match and every request errored with a 400 request.size.invalid. They are now treated as no expected length, like any other unparseable value.

  • Fix node streams destroyed without an error never settling - by @bjohansebas in #158

    They now error through the callback / reject the promise with the same 400 request.aborted error as the web path, instead of never invoking the callback or settling the promise.

  • Fix process crash when a custom decoder throws while reading node streams - by @bjohansebas in #157

@Phillip9587

Copy link
Copy Markdown
Collaborator

My two cents on this release: do we really need to support streams, web streams, callback style, and promise style in the same function?

I think this would be cleaner if getRawBody were split into at least two functions: one for Node.js streams and one for Web Streams. raw-body sits on a very hot path for things like body-parser, so even if the stream-type check is small in theory, it could add up under high request volume. Separating the APIs would make the behavior clearer and could avoid unnecessary branching in performance-sensitive code.

I would even consider splitting the Promise and callback APIs into separate functions. Even Node.js core modules expose separate callback- and Promise-based APIs (e.g. fs and fs/promises) rather than detecting which style the caller intends to use at runtime. Following that pattern would simplify the implementation and make the API more explicit.

Having separate functions for Node.js streams and Web Streams would also make it easier for bundlers to eliminate the unused implementation, so applications that only target one stream type don't have to include the other.

@bjohansebas

Copy link
Copy Markdown
Collaborator Author

I like the idea of separating the Web API from the Node.js API. As for the Promise and callback APIs, I'd prefer to keep them as they are for now. We can revisit that in the next major release after this one, but for now I'd like to keep the API unchanged.

@bjohansebas
bjohansebas merged commit c3949f8 into master Jul 12, 2026
27 checks passed
@bjohansebas
bjohansebas deleted the release-4.0.0 branch July 12, 2026 02:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants