feat(node): add TmpFileUploadHandlerPlugin - #1863
Conversation
…rser-plugin-a9babe
…up to TmpFileUploadHandlerPlugin Adopts resolveStandardBodyHint from @standardserver/core 0.8.0 for body kind resolution. Requests are limited per content category (memory, file, stream) through a grouped maxBodySize option, subsuming the request limit plugin. Tmp file removal defers onto streaming response bodies so they can read the upload while transmitting, and waits for in-flight logic on abandoned transfers. Limit and storage errors carry no implementation details to the client, while filesystem failures keep their server-fault status through the decode step's client-error mapping.
|
Run failed. View the logs →
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
More templates
@orpc/ai-sdk
@orpc/arktype
@orpc/bun
@orpc/client
@orpc/cloudflare
@orpc/contract
@orpc/experimental-effect
@orpc/evlog
@orpc/hibernation
@orpc/json-schema
@orpc/nest
@orpc/next
@orpc/node
@orpc/openapi
@orpc/opentelemetry
@orpc/pinia-colada
@orpc/pino
@orpc/publisher
@orpc/ratelimit
@orpc/server
@orpc/shared
@orpc/swr
@orpc/tanstack-query
@orpc/trpc
@orpc/valibot
@orpc/zod
commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
One shared harness replaces three duplicated interceptor drivers, and new cases cover every remaining branch: bodiless delegation, default options, non-stream adapter passthroughs on each body kind, file bodies without content headers, form-data hints without a content type, append and seal filesystem failures, and the parser's ignored headers and parameter-less dispositions. packages/node/src sits at 100% statements, branches, functions, and lines.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
The incremental change (commit 7203188c) moves the maxBodySize configuration from the inline Setup code example into its own "Limiting Body Sizes" section with a dedicated code block. The text is polished and the structure is clearer — no behavioral code changed.
- Moved
maxBodySizeJSDoc + config into a self-contained "Limiting Body Sizes" section - Polished prose throughout (grammar fixes, tighter wording)
- Added cross-reference that the plugin subsumes the Request Limit Plugin when all three limits are configured
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
…rt parser The parser is form-data specific, so the serialization escapes of quotes and line breaks in names and filenames decode where those parameters are extracted, instead of leaving callers with encoded values.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Single commit 327ccf13 tightens multipart part header parsing to match the standard parser's exact shape (RFC 9110 token header names, lowercase form-data disposition, quoted name/filename in order, bare-CR-as-line-break, last-wins repeated headers, strict rejection of filename*), with an adversarial differential test locking every edge case to Response.formData() live behavior.
- Replaced
parseHeaderParameters-based content-disposition parsing with a strictparseContentDispositionfunction matching standard-parser accept/reject behavior exactly - Added
HEADER_NAME_REGEXvalidation per RFC 9110 tokens for all part header names - Added 29-case adversarial differential test plus an RFC 2046 leniency test
- Updated parser JSDoc to document the now-exact contract with deliberate leniencies called out
The parser now rejects shapes the standard parser rejects (uppercase disposition type, non-token header names, filename*, whitespace-before-semicolon, out-of-order parameters), and its only leniencies (RFC 2046 preamble/epilogue/transport padding) are explicitly tested as such.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
327ccf1 to
920ba2f
Compare
Audited against the grammar and other parsers (undici, busboy, @fastify/busboy): malformed bodies, including boundary matches without a delimiter tail, throw instead of growing reinterpretation machinery, matching the standard parser. Parameters without a value skip cleanly, chunk intake avoids a copy when nothing is retained, and tests pin the strict cases plus the leniencies the comparison confirmed: header colon spacing, close-delimiter junk, ignored filename*, and first-wins duplicate parameters.
35325ef to
32ae030
Compare
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Single commit 35325eff fixes the multipart parser's handling of boundary matches that aren't real delimiters (trailing tail is not CRLF or --). Instead of throwing, those bytes are now treated as ordinary content, matching the RFC grammar.
- Replaced the
throwin delimiter-end with a fallback that writes the delimiter back to the writer and continues scanning - Extended
parseHeaderParametersto skip valueless parameters rather than breaking, matching first-wins semantics - Added 2 new regression test cases (boundary-as-content in body and preamble, cross-parser leniencies), changed 1 existing test (
garbageAfterBoundary), and addedparseHeaderParametersskip-without-value coverage - All 55 tests pass (18 multipart + 37 plugin), including the existing differential fuzzing suite
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
The author force-pushed to replace commit 35325eff (tolerant boundary handling) with 32ae030a (strict parser). The key differences:
- Strict reject, not tolerant: boundary matches not followed by CRLF or
--nowthrowrather than being treated as ordinary content. This matches the RFC grammar — a\r\n--boundarysequence in the body IS a delimiter, and its tail being anything other than CRLF/--is a framing error. - Simpler state machine:
writer.end()is called immediately when a delimiter match is found in body state (before tail confirmation), eliminating thestateBeforeDelimiterrollback logic. The previous guard onwriterin the--close check is also removed since writer is alwaysundefinedat delimiter-end. - Buffer copy optimization: when
bufferis empty (flushed),Buffer.from(chunk.buffer, ...)is used instead ofBuffer.concatto avoid a copy. - New test:
falseDelimiterInBodyvalidates the strict throw on body content containing a boundary-like pattern. - Test removed: the "treats boundary matches without a delimiter tail as ordinary content" test is gone with the tolerant approach.
- All 54 tests pass (17 multipart + 37 plugin).
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Adds
TmpFileUploadHandlerPluginto@orpc/node: file uploads and multipart file parts stream into per-request temporary files instead of memory, so requests far larger than available memory parse in constant memory. Kind-aware size limits let it replace the request limit plugin while sizing each kind of body to what it actually costs. Also upgrades@standardserver/*to 0.8.0 repo-wide and adopts itsresolveStandardBodyHint, so the plugin decides body kinds exactly as the standard parsers do.Behavior
TmpFileinstances (aFileexposing its backingpath), so an upload can be kept with a cheap rename; files are removed when the request finishes.maxBodySizegroups three limits that are required together:memory(JSON, urlencoded, multipart fields),file(content spooled to disk), andstream(content consumed on the fly). A declared content-length over the limit rejects before any byte is read, enforcement continues while streaming so a lying length cannot bypass it, and a multipart body as a whole is bounded by the memory and file limits combined so framing cannot hide bytes. Limit and storage errors carry no implementation details to the client, and filesystem failures keep their 500 status through the decode step's client-error mapping.%22/%0D/%0Adecoding, path-preserving filenames, UTF-8 field values, preserved content-type parameters, and strict rejection of malformed parts.Testing
Response.formData()down to 1-byte chunking with async delivery, drip-fed socket uploads, flat file-descriptor usage across 300-part bodies, and cleanup on every error path.RPCLink, composition with the request compression and request limit plugins, streaming-response echo, and abort cleanup; the plugin joined the all-plugins matrix test.docs:validatepass on the 0.8.0 upgrade; docs page added atdocs/plugins/tmp-file-upload.