Skip to content

Formidable monorepo and v4 perp#997

Open
tunnckoCore wants to merge 12 commits into
masterfrom
monorepo
Open

Formidable monorepo and v4 perp#997
tunnckoCore wants to merge 12 commits into
masterfrom
monorepo

Conversation

@tunnckoCore

@tunnckoCore tunnckoCore commented Apr 27, 2025

Copy link
Copy Markdown
Member

Moving to a monorepo and TypeScript.

There will be couple of benefits with that like we can have v1, v2, v3 and others as separate directories and not separate branches. We can make CI builds based on that too.

I played with a lot of variants, so there will be:

  • package for each major, like packages/v1 (or similar), v2, v3,
  • a v3-trimmed variant,
  • benchmarks package,
  • v4 built on the old/current parser (the v1/v2/v3 parser is byte by byte streaming which is slow for large files)
  • v4 with fresh parser,
  • v4 built on @mjackson/multipart-parser fork
  • helpers and s3 adapter
  • frontend utils

What i just started realizing is that the HTTP Multipart is pretty bad, and there might be an alternative way to handle all that a lot better, like..

What if on the backend user defines formidable(req, options) but on his frontend he also get the formidableClient(e.target, options), the client side part will basically get the FileList and send in parallel each File to a server endpoint that is handled by the formidable(req) and just does the validation and checking on the server.

That way we can accept multiple requests at the same time and validate and stream to a third-party like S3 or the disk. Usually what happens is, no matter how many files and fields there are, on the server end we (any body parser) get just one stream that we gotta process "synchronously", there's no other way - but that's the nature of streams.

eg. in frontend

function handler() {
  const files = event.target.files as FileList;
  const results = await Promise.all(files.map((file: File) => {
    return fetch('/api/upload', {
      method: 'POST',
      body: file
    })
  }))
}

and then, on the endpoint backend, you just get the requests, validate it and send it wherever you want (disk or S3).

Signed-off-by: tunnckoCore <5038030+tunnckoCore@users.noreply.github.com>
Signed-off-by: tunnckoCore <5038030+tunnckoCore@users.noreply.github.com>
Signed-off-by: tunnckoCore <5038030+tunnckoCore@users.noreply.github.com>
@tunnckoCore tunnckoCore added the do not merge When something is not yet finished. Could be used with Status:blocked label Apr 28, 2025
Signed-off-by: tunnckoCore <5038030+tunnckoCore@users.noreply.github.com>
}
function quote(value) {
if (value.includes('"') || value.includes(';') || value.includes(' ')) {
return `"${value.replace(/"/g, '\\"')}"`;

Check failure

Code scanning / CodeQL

Incomplete string escaping or encoding

This does not escape backslash characters in the input.

Copilot Autofix

AI about 1 year ago

To fix the issue, the quote function should be updated to escape backslashes in addition to double quotes. This can be achieved by first replacing all backslashes with escaped backslashes (\\), and then escaping double quotes. The order of these replacements is important to avoid double-escaping backslashes introduced during the first replacement.

The updated quote function will:

  1. Replace all backslashes (\) with double backslashes (\\).
  2. Replace all double quotes (") with escaped double quotes (\").

This ensures that both backslashes and double quotes are properly escaped.


Suggested changeset 1
packages/formidable-next/src/super-headers.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/formidable-next/src/super-headers.js b/packages/formidable-next/src/super-headers.js
--- a/packages/formidable-next/src/super-headers.js
+++ b/packages/formidable-next/src/super-headers.js
@@ -26,3 +26,3 @@
   if (value.includes('"') || value.includes(';') || value.includes(' ')) {
-    return `"${value.replace(/"/g, '\\"')}"`;
+    return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
   }
EOF
@@ -26,3 +26,3 @@
if (value.includes('"') || value.includes(';') || value.includes(' ')) {
return `"${value.replace(/"/g, '\\"')}"`;
return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
}
Copilot is powered by AI and may make mistakes. Always verify output.
Signed-off-by: tunnckoCore <5038030+tunnckoCore@users.noreply.github.com>
@tunnckoCore

tunnckoCore commented Apr 30, 2025

Copy link
Copy Markdown
Member Author

Published v4 RC on formidable@next using a fork of the @mjackson/multipart-parser with several limit options and other tweaks i don't like there. We can't just build directly on top of it for few reasons.

What's cool is the remix-the-web monorepo is thoroughly tested, including the multipart-parser and the headers package that is used under the hood to parse the part's headers and expose it through the part.headers getter.

Signed-off-by: tunnckoCore <5038030+tunnckoCore@users.noreply.github.com>
Signed-off-by: tunnckoCore <5038030+tunnckoCore@users.noreply.github.com>
Signed-off-by: tunnckoCore <5038030+tunnckoCore@users.noreply.github.com>
…e to tsdown, fix exports

Signed-off-by: tunnckoCore <5038030+tunnckoCore@users.noreply.github.com>
Signed-off-by: tunnckoCore <5038030+tunnckoCore@users.noreply.github.com>
@tunnckoCore

tunnckoCore commented May 12, 2025

Copy link
Copy Markdown
Member Author

The tests in test-node/standalone are not actually working. They all fail at parsing level, erroring with

FormidableError: MultipartParser.end(): stream ended unexpectedly: state = PART_DATA

All tests in general are broken beyond belief. And all tests are basically testing weird stuff with emitters and streams. It's absolute nightmare.

Rather prefer to switch the whole thing under the hood with v4 + compat for limit options, and to actually test that.

Signed-off-by: tunnckoCore <5038030+tunnckoCore@users.noreply.github.com>
tunnckoCore added a commit that referenced this pull request Apr 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do not merge When something is not yet finished. Could be used with Status:blocked

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants