Skip to content

feat(2.0): seroval json mode#2042

Open
lxsmnsyc wants to merge 20 commits intomainfrom
feat-seroval-json
Open

feat(2.0): seroval json mode#2042
lxsmnsyc wants to merge 20 commits intomainfrom
feat-seroval-json

Conversation

@lxsmnsyc
Copy link
Member

@lxsmnsyc lxsmnsyc commented Jan 4, 2026

Pretty straight forward. Adds an opt-in mode to use json mode (set by default) for seroval streaming, this is because the js stream is affected by CSP.

Also adds streaming support for client-to-server communication (except for url-encoded requests), assuming that the browser it comes from supports it.

@changeset-bot
Copy link

changeset-bot bot commented Jan 4, 2026

🦋 Changeset detected

Latest commit: 0defa47

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@netlify
Copy link

netlify bot commented Jan 4, 2026

Deploy Preview for solid-start-landing-page ready!

Name Link
🔨 Latest commit 0defa47
🔍 Latest deploy log https://app.netlify.com/projects/solid-start-landing-page/deploys/699768572eaac20008e8f9a0
😎 Deploy Preview https://deploy-preview-2042--solid-start-landing-page.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 4, 2026

Open in StackBlitz

npm i https://pkg.pr.new/solidjs/solid-start/@solidjs/start@2042
npm i https://pkg.pr.new/solidjs/solid-start/@solidjs/vite-plugin-nitro-2@2042

commit: 0defa47

middleware?: string;
serialization?: {
// This only matters for server function responses
mode?: 'js' | 'json';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When possible, add a plugins option here that we can load into the serializer. The only problem is how do we bridge it from the config to the runtime (do we need a "runtime" config API?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The non-magical way I could think of is to add this as an option to createHandler in entry-server.tsx 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

either that or an "entry" file for configs I suppose.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't we be able to set an environment variable, then use something like

const serialization = import.meta.env.VITE_SOLIDSTART_SERIALIZATION;
if (["js", "json"].includes(serialization ?? "js")) {
  // initialize seroval mode
} else if (serialization) {
  const [serializer, deserializer] = await import(serialization);
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@atk Not exactly this API in mind. I'm not trying to override seroval, I'm more of trying to think of an idea to allow incorporating 3P seroval plugins

URLSearchParamsPlugin,
URLPlugin,
];
const MAX_SERIALIZATION_DEPTH_LIMIT = 64;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seroval has a default limit of 1000, but that's because of my poor judgment. Will probably port this default back.

// TODO(Alexis): move to serializeToJSONStream
body: await serializeToJSONString(args),
// duplex: 'half',
// body: serializeToJSONStream(args),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bleeding edge. TypeScript doesn't have the duplex property defined yet so this one is invalid for now. On top of that, this doesn't actually "stream" as in the server responds immediately on request. What the browser does currently is buffer the entire stream then sends in bulk (technically speaking, duplex: half describes this behavior, duplex: full describes the desired behavior).

Once all browsers are ready for this (and also TypeScript), we can swap to duplex: full

});
} else if (contentType?.startsWith('application/json')) {
parsed = await event.request.json() as any[];
} else if (request.headers.has('x-serialized')) {
Copy link
Member Author

@lxsmnsyc lxsmnsyc Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. We no longer use the application/json mime type for seroval
  2. We wanted to distinguish seroval-based body from JSON body. Did I just add a new feature?

@lxsmnsyc lxsmnsyc changed the title feat: seroval json mode feat(2.0): seroval json mode Jan 14, 2026
...options,
// TODO(Alexis): move to serializeToJSONStream
body: await serializeToJSONString(args),
// duplex: 'half',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@katywings
Copy link
Contributor

Testing this branch on my project and uploading files through formData via https://github.com/hattipjs/hattip/tree/main/packages/base/multipart results in the following error 🙈:
[unhandledRejection] Error: Invalid content type

The error is throwed at this line: https://github.com/hattipjs/hattip/blob/15aa5ae4dec4478aa07785c85b2c85d4a753d0af/packages/base/multipart/src/form-data.ts#L79

It looks like with the changes in this branch, the upload doesn't set proper multipart boundaries anymore 🤔. The condition leading to the throw is !match.directives.boundary (https://github.com/hattipjs/hattip/blob/15aa5ae4dec4478aa07785c85b2c85d4a753d0af/packages/base/multipart/src/form-data.ts#L77C3-L77C29) - meaning: the content-type header misses the boundary flag 😅. E.g. the header should look as something like:

Content-Type: multipart/form-data; 
          boundary=gc0p4Jq0M2Yt08jU534c0p

(Reference: https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html)

@lxsmnsyc
Copy link
Member Author

Seemed like explicit definition of multipart/form-data overrides the derived content-type fetch does under the hood. Anyways fixed

@brenelz
Copy link
Contributor

brenelz commented Feb 8, 2026

Whats the latest on this pr?

@lxsmnsyc
Copy link
Member Author

lxsmnsyc commented Feb 9, 2026

@brenelz nothing much, but I haven't tested since the last commit lol

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new Seroval serialization layer for server functions with an opt-in response mode (js vs json, defaulting to json) to improve CSP compatibility, and expands the server-function transport to support more body types.

Changes:

  • Bumped seroval / seroval-plugins to ^1.5.0.
  • Added a new serialization module plus shared body-format helpers to support JS-stream vs JSON-stream response deserialization and additional body types (FormData, File, Blob, ArrayBuffer, Uint8Array, etc.).
  • Added new e2e/demo routes and tests for server-function round trips.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
pnpm-lock.yaml Updates lockfile for seroval/seroval-plugins upgrade.
packages/start/package.json Bumps seroval and seroval-plugins dependency ranges.
packages/start/src/config/index.ts Adds serialization.mode option and injects SEROVAL_MODE define.
packages/start/src/server/serialization.ts Introduces streaming serializer/deserializer utilities for JS and JSON modes.
packages/start/src/server/server-functions-shared.ts Adds body-format header constants + helpers for encoding/decoding request/response bodies.
packages/start/src/server/server-runtime.ts Updates client runtime to use the new serialization/body extraction helpers.
packages/start/src/server/server-functions-handler.ts Updates server handler to use the new serialization + body extraction helpers.
apps/tests/src/routes/server-function-ping.tsx Adds a basic server-function route for testing.
apps/tests/src/routes/server-function-form-data.tsx Adds a FormData/File server-function route for testing.
apps/tests/src/routes/server-function-blob.tsx Adds a Blob server-function route for testing.
apps/tests/src/e2e/server-function.test.ts Adds new e2e tests for ping and FormData server functions.
.changeset/plenty-geese-enter.md Adds changeset entry for the feature.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +55 to +56
if (request.method === "POST") {
parsed.push(await extractBody('', false, request.clone()));
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

POST argument parsing is broken here: extractBody(...) can return undefined when there is no body (so 0-arg server functions will be invoked with a single undefined), and when the client sends Seroval-encoded args it will deserialize to an args array but you currently push it as a single argument (so multi-arg server functions receive one array arg). Adjust this to (a) skip when request.body is null, and (b) when BODY_FORMAT_KEY indicates Seroval, treat the decoded value as the full args array (parsed = decoded).

Suggested change
if (request.method === "POST") {
parsed.push(await extractBody('', false, request.clone()));
if (request.method === "POST" && request.body !== null) {
const bodyFormat = request.headers.get(BODY_FORMAT_KEY);
const decoded = await extractBody("", false, request.clone());
if (bodyFormat === BodyFormat.Seroval) {
parsed = decoded as any[];
} else {
parsed.push(decoded);
}

Copilot uses AI. Check for mistakes.
Comment on lines +96 to +101
h3Event.res.headers.set(BODY_FORMAT_KEY, BodyFormat.Seroval);
if (import.meta.env.SEROVAL_MODE === "js") {
h3Event.res.headers.set("content-type", "text/javascript");
return serializeToJSStream(instance, result);
}
return serializeToJSONStream(result);
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When returning the JSON Seroval stream (serializeToJSONStream), no content-type header is set. Even though the client uses X-Start-Type, intermediaries and tooling may rely on content-type; consider setting it explicitly (e.g. text/plain) alongside X-Start-Type for the JSON-stream mode.

Copilot uses AI. Check for mistakes.
return value;
}

const blobURI = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAxNjYgMTU1LjMnPjxwYXRoIGQ9J00xNjMgMzVTMTEwLTQgNjkgNWwtMyAxYy02IDItMTEgNS0xNCA5bC0yIDMtMTUgMjYgMjYgNWMxMSA3IDI1IDEwIDM4IDdsNDYgOSAxOC0zMHonIGZpbGw9JyM3NmIzZTEnLz48bGluZWFyR3JhZGllbnQgaWQ9J2EnIGdyYWRpZW50VW5pdHM9J3VzZXJTcGFjZU9uVXNlJyB4MT0nMjcuNScgeTE9JzMnIHgyPScxNTInIHkyPSc2My41Jz48c3RvcCBvZmZzZXQ9Jy4xJyBzdG9wLWNvbG9yPScjNzZiM2UxJy8+PHN0b3Agb2Zmc2V0PScuMycgc3RvcC1jb2xvcj0nI2RjZjJmZCcvPjxzdG9wIG9mZnNldD0nMScgc3RvcC1jb2xvcj0nIzc2YjNlMScvPjwvbGluZWFyR3JhZGllbnQ+PHBhdGggZD0nTTE2MyAzNVMxMTAtNCA2OSA1bC0zIDFjLTYgMi0xMSA1LTE0IDlsLTIgMy0xNSAyNiAyNiA1YzExIDcgMjUgMTAgMzggN2w0NiA5IDE4LTMweicgb3BhY2l0eT0nLjMnIGZpbGw9J3VybCgjYSknLz48cGF0aCBkPSdNNTIgMzVsLTQgMWMtMTcgNS0yMiAyMS0xMyAzNSAxMCAxMyAzMSAyMCA0OCAxNWw2Mi0yMVM5MiAyNiA1MiAzNXonIGZpbGw9JyM1MThhYzgnLz48bGluZWFyR3JhZGllbnQgaWQ9J2InIGdyYWRpZW50VW5pdHM9J3VzZXJTcGFjZU9uVXNlJyB4MT0nOTUuOCcgeTE9JzMyLjYnIHgyPSc3NCcgeTI9JzEwNS4yJz48c3RvcCBvZmZzZXQ9JzAnIHN0b3AtY29sb3I9JyM3NmIzZTEnLz48c3RvcCBvZmZzZXQ9Jy41JyBzdG9wLWNvbG9yPScjNDM3N2JiJy8+PHN0b3Agb2Zmc2V0PScxJyBzdG9wLWNvbG9yPScjMWYzYjc3Jy8+PC9saW5lYXJHcmFkaWVudD48cGF0aCBkPSdNNTIgMzVsLTQgMWMtMTcgNS0yMiAyMS0xMyAzNSAxMCAxMyAzMSAyMCA0OCAxNWw2Mi0yMVM5MiAyNiA1MiAzNXonIG9wYWNpdHk9Jy4zJyBmaWxsPSd1cmwoI2IpJy8+PGxpbmVhckdyYWRpZW50IGlkPSdjJyBncmFkaWVudFVuaXRzPSd1c2VyU3BhY2VPblVzZScgeDE9JzE4LjQnIHkxPSc2NC4yJyB4Mj0nMTQ0LjMnIHkyPScxNDkuOCc+PHN0b3Agb2Zmc2V0PScwJyBzdG9wLWNvbG9yPScjMzE1YWE5Jy8+PHN0b3Agb2Zmc2V0PScuNScgc3RvcC1jb2xvcj0nIzUxOGFjOCcvPjxzdG9wIG9mZnNldD0nMScgc3RvcC1jb2xvcj0nIzMxNWFhOScvPjwvbGluZWFyR3JhZGllbnQ+PHBhdGggZD0nTTEzNCA4MGE0NSA0NSAwIDAwLTQ4LTE1TDI0IDg1IDQgMTIwbDExMiAxOSAyMC0zNmM0LTcgMy0xNS0yLTIzeicgZmlsbD0ndXJsKCNjKScvPjxsaW5lYXJHcmFkaWVudCBpZD0nZCcgZ3JhZGllbnRVbml0cz0ndXNlclNwYWNlT25Vc2UnIHgxPSc3NS4yJyB5MT0nNzQuNScgeDI9JzI0LjQnIHkyPScyNjAuOCc+PHN0b3Agb2Zmc2V0PScwJyBzdG9wLWNvbG9yPScjNDM3N2JiJy8+PHN0b3Agb2Zmc2V0PScuNScgc3RvcC1jb2xvcj0nIzFhMzM2YicvPjxzdG9wIG9mZnNldD0nMScgc3RvcC1jb2xvcj0nIzFhMzM2YicvPjwvbGluZWFyR3JhZGllbnQ+PHBhdGggZD0nTTExNCAxMTVhNDUgNDUgMCAwMC00OC0xNUw0IDEyMHM1MyA0MCA5NCAzMGwzLTFjMTctNSAyMy0yMSAxMy0zNHonIGZpbGw9J3VybCgjZCknLz48L3N2Zz4=';
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blobURI (and other new strings) use single quotes, which conflicts with the repo formatter config (.oxfmtrc.json sets singleQuote: false). Please convert these to double quotes.

Suggested change
const blobURI = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAxNjYgMTU1LjMnPjxwYXRoIGQ9J00xNjMgMzVTMTEwLTQgNjkgNWwtMyAxYy02IDItMTEgNS0xNCA5bC0yIDMtMTUgMjYgMjYgNWMxMSA3IDI1IDEwIDM4IDdsNDYgOSAxOC0zMHonIGZpbGw9JyM3NmIzZTEnLz48bGluZWFyR3JhZGllbnQgaWQ9J2EnIGdyYWRpZW50VW5pdHM9J3VzZXJTcGFjZU9uVXNlJyB4MT0nMjcuNScgeTE9JzMnIHgyPScxNTInIHkyPSc2My41Jz48c3RvcCBvZmZzZXQ9Jy4xJyBzdG9wLWNvbG9yPScjNzZiM2UxJy8+PHN0b3Agb2Zmc2V0PScuMycgc3RvcC1jb2xvcj0nI2RjZjJmZCcvPjxzdG9wIG9mZnNldD0nMScgc3RvcC1jb2xvcj0nIzc2YjNlMScvPjwvbGluZWFyR3JhZGllbnQ+PHBhdGggZD0nTTE2MyAzNVMxMTAtNCA2OSA1bC0zIDFjLTYgMi0xMSA1LTE0IDlsLTIgMy0xNSAyNiAyNiA1YzExIDcgMjUgMTAgMzggN2w0NiA5IDE4LTMweicgb3BhY2l0eT0nLjMnIGZpbGw9J3VybCgjYSknLz48cGF0aCBkPSdNNTIgMzVsLTQgMWMtMTcgNS0yMiAyMS0xMyAzNSAxMCAxMyAzMSAyMCA0OCAxNWw2Mi0yMVM5MiAyNiA1MiAzNXonIGZpbGw9JyM1MThhYzgnLz48bGluZWFyR3JhZGllbnQgaWQ9J2InIGdyYWRpZW50VW5pdHM9J3VzZXJTcGFjZU9uVXNlJyB4MT0nOTUuOCcgeTE9JzMyLjYnIHgyPSc3NCcgeTI9JzEwNS4yJz48c3RvcCBvZmZzZXQ9JzAnIHN0b3AtY29sb3I9JyM3NmIzZTEnLz48c3RvcCBvZmZzZXQ9Jy41JyBzdG9wLWNvbG9yPScjNDM3N2JiJy8+PHN0b3Agb2Zmc2V0PScxJyBzdG9wLWNvbG9yPScjMWYzYjc3Jy8+PC9saW5lYXJHcmFkaWVudD48cGF0aCBkPSdNNTIgMzVsLTQgMWMtMTcgNS0yMiAyMS0xMyAzNSAxMCAxMyAzMSAyMCA0OCAxNWw2Mi0yMVM5MiAyNiA1MiAzNXonIG9wYWNpdHk9Jy4zJyBmaWxsPSd1cmwoI2IpJy8+PGxpbmVhckdyYWRpZW50IGlkPSdjJyBncmFkaWVudFVuaXRzPSd1c2VyU3BhY2VPblVzZScgeDE9JzE4LjQnIHkxPSc2NC4yJyB4Mj0nMTQ0LjMnIHkyPScxNDkuOCc+PHN0b3Agb2Zmc2V0PScwJyBzdG9wLWNvbG9yPScjMzE1YWE5Jy8+PHN0b3Agb2Zmc2V0PScuNScgc3RvcC1jb2xvcj0nIzUxOGFjOCcvPjxzdG9wIG9mZnNldD0nMScgc3RvcC1jb2xvcj0nIzMxNWFhOScvPjwvbGluZWFyR3JhZGllbnQ+PHBhdGggZD0nTTEzNCA4MGE0NSA0NSAwIDAwLTQ4LTE1TDI0IDg1IDQgMTIwbDExMiAxOSAyMC0zNmM0LTcgMy0xNS0yLTIzeicgZmlsbD0ndXJsKCNjKScvPjxsaW5lYXJHcmFkaWVudCBpZD0nZCcgZ3JhZGllbnRVbml0cz0ndXNlclNwYWNlT25Vc2UnIHgxPSc3NS4yJyB5MT0nNzQuNScgeDI9JzI0LjQnIHkyPScyNjAuOCc+PHN0b3Agb2Zmc2V0PScwJyBzdG9wLWNvbG9yPScjNDM3N2JiJy8+PHN0b3Agb2Zmc2V0PScuNScgc3RvcC1jb2xvcj0nIzFhMzM2YicvPjxzdG9wIG9mZnNldD0nMScgc3RvcC1jb2xvcj0nIzFhMzM2YicvPjwvbGluZWFyR3JhZGllbnQ+PHBhdGggZD0nTTExNCAxMTVhNDUgNDUgMCAwMC00OC0xNUw0IDEyMHM1MyA0MCA5NCAzMGwzLTFjMTctNSAyMy0yMSAxMy0zNHonIGZpbGw9J3VybCgjZCknLz48L3N2Zz4=';
const blobURI = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAxNjYgMTU1LjMnPjxwYXRoIGQ9J00xNjMgMzVTMTEwLTQgNjkgNWwtMyAxYy02IDItMTEgNS0xNCA5bC0yIDMtMTUgMjYgMjYgNWMxMSA3IDI1IDEwIDM4IDdsNDYgOSAxOC0zMHonIGZpbGw9JyM3NmIzZTEnLz48bGluZWFyR3JhZGllbnQgaWQ9J2EnIGdyYWRpZW50VW5pdHM9J3VzZXJTcGFjZU9uVXNlJyB4MT0nMjcuNScgeTE9JzMnIHgyPScxNTInIHkyPSc2My41Jz48c3RvcCBvZmZzZXQ9Jy4xJyBzdG9wLWNvbG9yPScjNzZiM2UxJy8+PHN0b3Agb2Zmc2V0PScuMycgc3RvcC1jb2xvcj0nI2RjZjJmZCcvPjxzdG9wIG9mZnNldD0nMScgc3RvcC1jb2xvcj0nIzc2YjNlMScvPjwvbGluZWFyR3JhZGllbnQ+PHBhdGggZD0nTTE2MyAzNVMxMTAtNCA2OSA1bC0zIDFjLTYgMi0xMSA1LTE0IDlsLTIgMy0xNSAyNiAyNiA1YzExIDcgMjUgMTAgMzggN2w0NiA5IDE4LTMweicgb3BhY2l0eT0nLjMnIGZpbGw9J3VybCgjYSknLz48cGF0aCBkPSdNNTIgMzVsLTQgMWMtMTcgNS0yMiAyMS0xMyAzNSAxMCAxMyAzMSAyMCA0OCAxNWw2Mi0yMVM5MiAyNiA1MiAzNXonIGZpbGw9JyM1MThhYzgnLz48bGluZWFyR3JhZGllbnQgaWQ9J2InIGdyYWRpZW50VW5pdHM9J3VzZXJTcGFjZU9uVXNlJyB4MT0nOTUuOCcgeTE9JzMyLjYnIHgyPSc3NCcgeTI9JzEwNS4yJz48c3RvcCBvZmZzZXQ9JzAnIHN0b3AtY29sb3I9JyM3NmIzZTEnLz48c3RvcCBvZmZzZXQ9Jy41JyBzdG9wLWNvbG9yPScjNDM3N2JiJy8+PHN0b3Agb2Zmc2V0PScxJyBzdG9wLWNvbG9yPScjMWYzYjc3Jy8+PC9saW5lYXJHcmFkaWVudD48cGF0aCBkPSdNNTIgMzVsLTQgMWMtMTcgNS0yMiAyMS0xMyAzNSAxMCAxMyAzMSAyMCA0OCAxNWw2Mi0yMVM5MiAyNiA1MiAzNXonIG9wYWNpdHk9Jy4zJyBmaWxsPSd1cmwoI2IpJy8+PGxpbmVhckdyYWRpZW50IGlkPSdjJyBncmFkaWVudFVuaXRzPSd1c2VyU3BhY2VPblVzZScgeDE9JzE4LjQnIHkxPSc2NC4yJyB4Mj0nMTQ0LjMnIHkyPScxNDkuOCc+PHN0b3Agb2Zmc2V0PScwJyBzdG9wLWNvbG9yPScjMzE1YWE5Jy8+PHN0b3Agb2Zmc2V0PScuNScgc3RvcC1jb2xvcj0nIzUxOGFjOCcvPjxzdG9wIG9mZnNldD0nMScgc3RvcC1jb2xvcj0nIzMxNWFhOScvPjwvbGluZWFyR3JhZGllbnQ+PHBhdGggZD0nTTEzNCA4MGE0NSA0NSAwIDAwLTQ4LTE1TDI0IDg1IDQgMTIwbDExMiAxOSAyMC0zNmM0LTcgMy0xNS0yLTIzeicgZmlsbD0ndXJsKCNjKScvPjxsaW5lYXJHcmFkaWVudCBpZD0nZCcgZ3JhZGllbnRVbml0cz0ndXNlclNwYWNlT25Vc2UnIHgxPSc3NS4yJyB5MT0nNzQuNScgeDI9JzI0LjQnIHkyPScyNjAuOCc+PHN0b3Agb2Zmc2V0PScwJyBzdG9wLWNvbG9yPScjNDM3N2JiJy8+PHN0b3Agb2Zmc2V0PScuNScgc3RvcC1jb2xvcj0nIzFhMzM2YicvPjxzdG9wIG9mZnNldD0nMScgc3RvcC1jb2xvcj0nIzFhMzM2YicvPjwvbGluZWFyR3JhZGllbnQ+PHBhdGggZD0nTTExNCAxMTVhNDUgNDUgMCAwMC00OC0xNUw0IDEyMHM1MyA0MCA5NCAzMGwzLTFjMTctNSAyMy0yMSAxMy0zNHonIGZpbGw9J3VybCgjZCknLz48L3N2Zz4=";

Copilot uses AI. Check for mistakes.
test("should build with a server function w/ form data", async ({ page }) => {
await page.goto("http://localhost:3000/server-function-form-data");
await expect(page.locator("#server-fn-test")).toContainText('{"result":true}');
});
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new Blob server-function route was added (/server-function-blob), but there is no e2e coverage for it here. Consider adding an e2e test for the blob round-trip (or removing the route if it's not intended to be exercised) so the new body-format handling stays covered.

Suggested change
});
});
test("should build with a server function w/ blob data", async ({ page }) => {
await page.goto("http://localhost:3000/server-function-blob");
await expect(page.locator("#server-fn-test")).toContainText('{"result":true}');
});

Copilot uses AI. Check for mistakes.
Comment on lines +2 to +8
import {
deserializeJSONStream,
deserializeJSStream,
// serializeToJSONStream,
serializeToJSONString,
} from "./serialization.ts";
import { BODY_FORMAL_FILE, BODY_FORMAT_KEY, BodyFormat, extractBody, getHeadersAndBody } from "./server-functions-shared.ts";
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deserializeJSONStream / deserializeJSStream and BODY_FORMAL_FILE are imported but unused in this module, and there is commented-out code around serializeToJSONStream. Please remove unused imports and stale commented code to keep the runtime lean and avoid confusion about which serialization path is active.

Copilot uses AI. Check for mistakes.
middleware?: string;
serialization?: {
// This only matters for server function responses
mode?: 'js' | 'json';
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quote style here is inconsistent with the repo formatter config (.oxfmtrc.json has singleQuote: false). Use double quotes for the string union values to avoid churn/formatting failures.

Suggested change
mode?: 'js' | 'json';
mode?: "js" | "json";

Copilot uses AI. Check for mistakes.
"import.meta.env.START_APP_ENTRY": JSON.stringify(appEntryPath),
"import.meta.env.START_CLIENT_ENTRY": JSON.stringify(handlers.client),
"import.meta.env.START_DEV_OVERLAY": JSON.stringify(start.devOverlay),
"import.meta.env.SEROVAL_MODE": JSON.stringify(start.serialization?.mode || 'json'),
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This define uses single quotes, which conflicts with the repo oxfmt config (singleQuote: false). Use double quotes for the default value to match the enforced formatting.

Suggested change
"import.meta.env.SEROVAL_MODE": JSON.stringify(start.serialization?.mode || 'json'),
"import.meta.env.SEROVAL_MODE": JSON.stringify(start.serialization?.mode || "json"),

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +6
const file = value.get('example') as File;
return await file.text();
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file uses single quotes in several new strings, which conflicts with the repo formatter config (.oxfmtrc.json sets singleQuote: false). Please switch to double quotes so pnpm format doesn't rewrite these lines.

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +16
export const enum BodyFormat {
Seroval = "0",
String = "1",
FormData = "2",
URLSearchParams = "3",
Blob = "4",
File = "5",
ArrayBuffer = "6",
Uint8Array = "7",
}
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BodyFormat is declared as a const enum, but this package is built with tsc + isolatedModules: true (and likely esbuild for TS transpilation). const enum will cause compilation/transpilation failures in isolated module builds. Use a regular enum, or replace it with a const object (as const) plus a string-literal union type.

Copilot uses AI. Check for mistakes.
Comment on lines +111 to +112
case startType === BodyFormat.Uint8Array:
return await clone.bytes();
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clone.bytes() is not part of the standard Fetch API and is not available in most browsers/undici. For Uint8Array bodies, use await clone.arrayBuffer() and wrap it in new Uint8Array(...) (or handle both cases if you want to support runtimes that implement bytes()).

Suggested change
case startType === BodyFormat.Uint8Array:
return await clone.bytes();
case startType === BodyFormat.Uint8Array: {
const anyClone = clone as any;
if (typeof anyClone.bytes === "function") {
return await anyClone.bytes();
}
const buffer = await clone.arrayBuffer();
return new Uint8Array(buffer);
}

Copilot uses AI. Check for mistakes.
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.

5 participants

Comments