From d59aff20c6d58102632551c33acf06e7ff246f29 Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Mon, 29 Dec 2025 13:35:16 +0800 Subject: [PATCH 1/7] Create icy-rings-exist.md --- .changeset/icy-rings-exist.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/icy-rings-exist.md diff --git a/.changeset/icy-rings-exist.md b/.changeset/icy-rings-exist.md new file mode 100644 index 000000000..27c7c5457 --- /dev/null +++ b/.changeset/icy-rings-exist.md @@ -0,0 +1,5 @@ +--- +"@solidjs/start": minor +--- + +seroval json mode From c3b50cc57c8aef2ebbfa9bd6e046366b5be1d987 Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Sun, 4 Jan 2026 10:03:58 +0800 Subject: [PATCH 2/7] Add seroval json mode --- packages/start/config/index.d.ts | 4 + packages/start/config/index.js | 1 + packages/start/src/runtime/serialization.ts | 253 +++++++++++++++++++ packages/start/src/runtime/server-handler.ts | 135 ++-------- packages/start/src/runtime/server-runtime.ts | 221 +++++----------- 5 files changed, 347 insertions(+), 267 deletions(-) create mode 100644 packages/start/src/runtime/serialization.ts diff --git a/packages/start/config/index.d.ts b/packages/start/config/index.d.ts index 513b935fa..7b00c1814 100644 --- a/packages/start/config/index.d.ts +++ b/packages/start/config/index.d.ts @@ -26,6 +26,10 @@ type SolidStartInlineConfig = { experimental?: { islands?: boolean; }; + serialization?: { + // This only matters for server function responses + mode?: 'js' | 'json'; + } vite?: | ViteCustomizableConfig | ((options: { router: "server" | "client" | "server-function" }) => ViteCustomizableConfig); diff --git a/packages/start/config/index.js b/packages/start/config/index.js index ba7bebc1f..90cc14eeb 100644 --- a/packages/start/config/index.js +++ b/packages/start/config/index.js @@ -167,6 +167,7 @@ export function defineConfig(baseConfig = {}) { "import.meta.env.SSR": JSON.stringify(true), "import.meta.env.START_SSR": JSON.stringify(start.ssr), "import.meta.env.START_DEV_OVERLAY": JSON.stringify(start.devOverlay), + "import.meta.env.SEROVAL_MODE": JSON.stringify(start.serialization?.mode || 'json'), ...userConfig.define } }) diff --git a/packages/start/src/runtime/serialization.ts b/packages/start/src/runtime/serialization.ts new file mode 100644 index 000000000..c157d157e --- /dev/null +++ b/packages/start/src/runtime/serialization.ts @@ -0,0 +1,253 @@ +import { + crossSerializeStream, + deserialize, + Feature, + fromCrossJSON, + getCrossReferenceHeader, + type SerovalNode, + toCrossJSONStream, +} from "seroval"; +import { + AbortSignalPlugin, + CustomEventPlugin, + DOMExceptionPlugin, + EventPlugin, + FormDataPlugin, + HeadersPlugin, + ReadableStreamPlugin, + RequestPlugin, + ResponsePlugin, + URLPlugin, + URLSearchParamsPlugin, +} from "seroval-plugins/web"; + +// TODO(Alexis): if we can, allow providing an option to extend these. +const DEFAULT_PLUGINS = [ + AbortSignalPlugin, + CustomEventPlugin, + DOMExceptionPlugin, + EventPlugin, + FormDataPlugin, + HeadersPlugin, + ReadableStreamPlugin, + RequestPlugin, + ResponsePlugin, + URLSearchParamsPlugin, + URLPlugin, +]; +const MAX_SERIALIZATION_DEPTH_LIMIT = 64; +const DISABLED_FEATURES = Feature.RegExp; + +/** + * Alexis: + * + * A "chunk" is a piece of data emitted by the streaming serializer. + * Each chunk is represented by a 32-bit value (encoded in hexadecimal), + * followed by the encoded string (8-bit representation). This format + * is important so we know how much of the chunk being streamed we + * are expecting before parsing the entire string data. + * + * This is sort of a bootleg "multipart/form-data" except it's bad at + * handling File/Blob LOL + * + * The format is as follows: + * ;0xFFFFFFFF; + */ +function createChunk(data: string): Uint8Array { + const encodeData = new TextEncoder().encode(data); + const bytes = encodeData.length; + const baseHex = bytes.toString(16); + const totalHex = "00000000".substring(0, 8 - baseHex.length) + baseHex; // 32-bit + const head = new TextEncoder().encode(`;0x${totalHex};`); + + const chunk = new Uint8Array(12 + bytes); + chunk.set(head); + chunk.set(encodeData, 12); + return chunk; +} + +export function serializeToJSStream(id: string, value: any) { + return new ReadableStream({ + start(controller) { + crossSerializeStream(value, { + scopeId: id, + plugins: DEFAULT_PLUGINS, + onSerialize(data: string, initial: boolean) { + controller.enqueue( + createChunk( + initial ? `(${getCrossReferenceHeader(id)},${data})` : data, + ), + ); + }, + onDone() { + controller.close(); + }, + onError(error: any) { + controller.error(error); + }, + }); + }, + }); +} + +export function serializeToJSONStream(value: any) { + return new ReadableStream({ + start(controller) { + toCrossJSONStream(value, { + disabledFeatures: DISABLED_FEATURES, + depthLimit: MAX_SERIALIZATION_DEPTH_LIMIT, + plugins: DEFAULT_PLUGINS, + onParse(node) { + controller.enqueue(createChunk(JSON.stringify(node))); + }, + onDone() { + controller.close(); + }, + onError(error) { + controller.error(error); + }, + }); + }, + }); +} + +class SerovalChunkReader { + reader: ReadableStreamDefaultReader; + buffer: Uint8Array; + done: boolean; + constructor(stream: ReadableStream) { + this.reader = stream.getReader(); + this.buffer = new Uint8Array(0); + this.done = false; + } + + async readChunk() { + // if there's no chunk, read again + const chunk = await this.reader.read(); + if (!chunk.done) { + // repopulate the buffer + const newBuffer = new Uint8Array(this.buffer.length + chunk.value.length); + newBuffer.set(this.buffer); + newBuffer.set(chunk.value, this.buffer.length); + this.buffer = newBuffer; + } else { + this.done = true; + } + } + + async next(): Promise< + { done: true; value: undefined } | { done: false; value: string } + > { + // Check if the buffer is empty + if (this.buffer.length === 0) { + // if we are already done... + if (this.done) { + return { + done: true, + value: undefined, + }; + } + // Otherwise, read a new chunk + await this.readChunk(); + return await this.next(); + } + // Read the "byte header" + // The byte header tells us how big the expected data is + // so we know how much data we should wait before we + // deserialize the data + const head = new TextDecoder().decode(this.buffer.subarray(1, 11)); + const bytes = Number.parseInt(head, 16); // ;0x00000000; + // Check if the buffer has enough bytes to be parsed + while (bytes > this.buffer.length - 12) { + // If it's not enough, and the reader is done + // then the chunk is invalid. + if (this.done) { + throw new Error("Malformed server function stream."); + } + // Otherwise, we read more chunks + await this.readChunk(); + } + // Extract the exact chunk as defined by the byte header + const partial = new TextDecoder().decode( + this.buffer.subarray(12, 12 + bytes), + ); + // The rest goes to the buffer + this.buffer = this.buffer.subarray(12 + bytes); + + // Deserialize the chunk + return { + done: false, + value: partial, + }; + } + + async drain(interpret: (chunk: string) => void) { + while (true) { + const result = await this.next(); + if (result.done) { + break; + } else { + interpret(result.value); + } + } + } +} + +export async function serializeToJSONString(value: any) { + const response = new Response(serializeToJSONStream(value)); + return await response.text(); +} + +export async function deserializeFromJSONString(json: string) { + const blob = new Response(json); + return await deserializeJSONStream(blob); +} + +export async function deserializeJSONStream(response: Response | Request) { + if (!response.body) { + throw new Error("missing body"); + } + const reader = new SerovalChunkReader(response.body); + const result = await reader.next(); + if (!result.done) { + const refs = new Map(); + + function interpretChunk(chunk: string): unknown { + const value = fromCrossJSON(JSON.parse(chunk) as SerovalNode, { + refs, + disabledFeatures: DISABLED_FEATURES, + depthLimit: MAX_SERIALIZATION_DEPTH_LIMIT, + plugins: DEFAULT_PLUGINS, + }); + return value; + } + + void reader.drain(interpretChunk); + + return interpretChunk(result.value); + } + return undefined; +} + +export async function deserializeJSStream(id: string, response: Response) { + if (!response.body) { + throw new Error("missing body"); + } + const reader = new SerovalChunkReader(response.body); + + const result = await reader.next(); + + if (!result.done) { + reader.drain(deserialize).then( + () => { + // @ts-ignore + delete $R[id]; + }, + () => { + // no-op + }, + ); + return deserialize(result.value); + } + return undefined; +} diff --git a/packages/start/src/runtime/server-handler.ts b/packages/start/src/runtime/server-handler.ts index 74ded0de7..a7136a0de 100644 --- a/packages/start/src/runtime/server-handler.ts +++ b/packages/start/src/runtime/server-handler.ts @@ -1,19 +1,5 @@ /// import { parseSetCookie } from "cookie-es"; -import { crossSerializeStream, fromJSON, getCrossReferenceHeader } from "seroval"; -// @ts-ignore -import { - CustomEventPlugin, - DOMExceptionPlugin, - EventPlugin, - FormDataPlugin, - HeadersPlugin, - ReadableStreamPlugin, - RequestPlugin, - ResponsePlugin, - URLPlugin, - URLSearchParamsPlugin -} from "seroval-plugins/web"; import { sharedConfig } from "solid-js"; import { renderToString } from "solid-js/web"; import { provideRequestEvent } from "solid-js/web/storage"; @@ -32,52 +18,7 @@ import { createPageEvent } from "../server/pageEvent"; import { FetchEvent, PageEvent } from "../server"; // @ts-ignore import serverFnManifest from "solidstart:server-fn-manifest"; - -function createChunk(data: string) { - const encodeData = new TextEncoder().encode(data); - const bytes = encodeData.length; - const baseHex = bytes.toString(16); - const totalHex = "00000000".substring(0, 8 - baseHex.length) + baseHex; // 32-bit - const head = new TextEncoder().encode(`;0x${totalHex};`); - - const chunk = new Uint8Array(12 + bytes); - chunk.set(head); - chunk.set(encodeData, 12); - return chunk; -} - -function serializeToStream(id: string, value: any) { - return new ReadableStream({ - start(controller) { - crossSerializeStream(value, { - scopeId: id, - plugins: [ - CustomEventPlugin, - DOMExceptionPlugin, - EventPlugin, - FormDataPlugin, - HeadersPlugin, - ReadableStreamPlugin, - RequestPlugin, - ResponsePlugin, - URLSearchParamsPlugin, - URLPlugin - ], - onSerialize(data, initial) { - controller.enqueue( - createChunk(initial ? `(${getCrossReferenceHeader(id)},${data})` : data) - ); - }, - onDone() { - controller.close(); - }, - onError(error) { - controller.error(error); - } - }); - } - }); -} +import { deserializeFromJSONString, deserializeJSONStream, serializeToJSONStream, serializeToJSStream } from "./serialization"; async function handleServerFunction(h3Event: HTTPEvent) { const event = getFetchEvent(h3Event); @@ -131,24 +72,10 @@ async function handleServerFunction(h3Event: HTTPEvent) { if (!instance || h3Event.method === "GET") { const args = url.searchParams.get("args"); if (args) { - const json = JSON.parse(args); - (json.t - ? (fromJSON(json, { - plugins: [ - CustomEventPlugin, - DOMExceptionPlugin, - EventPlugin, - FormDataPlugin, - HeadersPlugin, - ReadableStreamPlugin, - RequestPlugin, - ResponsePlugin, - URLSearchParamsPlugin, - URLPlugin - ] - }) as any) - : json - ).forEach((arg: any) => parsed.push(arg)); + const result = (await deserializeFromJSONString(args)) as any[]; + for (const arg of result) { + parsed.push(arg); + } } } if (h3Event.method === "POST") { @@ -168,42 +95,26 @@ async function handleServerFunction(h3Event: HTTPEvent) { (hasReadableStream && ((h3Request as EdgeIncomingMessage).body as ReadableStream).locked); const requestBody = isReadableStream ? h3Request : h3Request.body; + // workaround for https://github.com/unjs/nitro/issues/1721 + // (issue only in edge runtimes and netlify preset) + const tmpReq = isH3EventBodyStreamLocked + ? request + : new Request(request, { ...request, body: requestBody }); if ( contentType?.startsWith("multipart/form-data") || contentType?.startsWith("application/x-www-form-urlencoded") ) { // workaround for https://github.com/unjs/nitro/issues/1721 // (issue only in edge runtimes and netlify preset) - parsed.push( - await (isH3EventBodyStreamLocked - ? request - : new Request(request, { ...request, body: requestBody }) - ).formData() - ); + parsed.push(await tmpReq.formData()); // what should work when #1721 is fixed // parsed.push(await request.formData); - } else if (contentType?.startsWith("application/json")) { - // workaround for https://github.com/unjs/nitro/issues/1721 - // (issue only in edge runtimes and netlify preset) - const tmpReq = isH3EventBodyStreamLocked - ? request - : new Request(request, { ...request, body: requestBody }); + } else if (contentType?.startsWith('application/json')) { // what should work when #1721 is fixed // just use request.json() here - parsed = fromJSON(await tmpReq.json(), { - plugins: [ - CustomEventPlugin, - DOMExceptionPlugin, - EventPlugin, - FormDataPlugin, - HeadersPlugin, - ReadableStreamPlugin, - RequestPlugin, - ResponsePlugin, - URLSearchParamsPlugin, - URLPlugin - ] - }); + parsed = await tmpReq.json() as any[]; + } else if (request.headers.get('x-serialized')) { + parsed = await deserializeJSONStream(tmpReq) as any[]; } } try { @@ -238,8 +149,12 @@ async function handleServerFunction(h3Event: HTTPEvent) { // handle no JS success case if (!instance) return handleNoJS(result, request, parsed); - setHeader(h3Event, "content-type", "text/javascript"); - return serializeToStream(instance, result); + setHeader(h3Event, 'x-serialized', 'true'); + if (import.meta.env.SEROVAL_MODE === 'js') { + setHeader(h3Event, "content-type", "text/javascript"); + return serializeToJSStream(instance, result); + } + return serializeToJSONStream(result); } catch (x) { if (x instanceof Response) { if (singleFlight && instance) { @@ -261,8 +176,12 @@ async function handleServerFunction(h3Event: HTTPEvent) { x = handleNoJS(x, request, parsed, true); } if (instance) { - setHeader(h3Event, "content-type", "text/javascript"); - return serializeToStream(instance, x); + setHeader(h3Event, 'x-serialized', 'true'); + if (import.meta.env.SEROVAL_MODE === 'js') { + setHeader(h3Event, "content-type", "text/javascript"); + return serializeToJSStream(instance, x); + } + return serializeToJSONStream(x); } return x; } diff --git a/packages/start/src/runtime/server-runtime.ts b/packages/start/src/runtime/server-runtime.ts index 2900539a9..c06d23a9e 100644 --- a/packages/start/src/runtime/server-runtime.ts +++ b/packages/start/src/runtime/server-runtime.ts @@ -1,171 +1,57 @@ -import { deserialize, toJSONAsync } from "seroval"; -import { - CustomEventPlugin, - DOMExceptionPlugin, - EventPlugin, - FormDataPlugin, - HeadersPlugin, - ReadableStreamPlugin, - RequestPlugin, - ResponsePlugin, - URLPlugin, - URLSearchParamsPlugin -} from "seroval-plugins/web"; import { type Component } from "solid-js"; import { createIslandReference } from "../server/islands/index"; - -class SerovalChunkReader { - reader: ReadableStreamDefaultReader; - buffer: Uint8Array; - done: boolean; - constructor(stream: ReadableStream) { - this.reader = stream.getReader(); - this.buffer = new Uint8Array(0); - this.done = false; - } - - async readChunk() { - // if there's no chunk, read again - const chunk = await this.reader.read(); - if (!chunk.done) { - // repopulate the buffer - let newBuffer = new Uint8Array(this.buffer.length + chunk.value.length); - newBuffer.set(this.buffer); - newBuffer.set(chunk.value, this.buffer.length); - this.buffer = newBuffer; - } else { - this.done = true; - } - } - - async next(): Promise { - // Check if the buffer is empty - if (this.buffer.length === 0) { - // if we are already done... - if (this.done) { - return { - done: true, - value: undefined - }; - } - // Otherwise, read a new chunk - await this.readChunk(); - return await this.next(); - } - // Read the "byte header" - // The byte header tells us how big the expected data is - // so we know how much data we should wait before we - // deserialize the data - const head = new TextDecoder().decode(this.buffer.subarray(1, 11)); - const bytes = Number.parseInt(head, 16); // ;0x00000000; - if (Number.isNaN(bytes)) { - throw new Error(`Malformed server function stream header: ${head}`); - } - - // Check if the buffer has enough bytes to be parsed - while (bytes > this.buffer.length - 12) { - // If it's not enough, and the reader is done - // then the chunk is invalid. - if (this.done) { - throw new Error("Malformed server function stream."); - } - // Otherwise, we read more chunks - await this.readChunk(); - } - // Extract the exact chunk as defined by the byte header - const partial = new TextDecoder().decode(this.buffer.subarray(12, 12 + bytes)); - // The rest goes to the buffer - this.buffer = this.buffer.subarray(12 + bytes); - - // Deserialize the chunk - return { - done: false, - value: deserialize(partial) - }; - } - - async drain() { - while (true) { - const result = await this.next(); - if (result.done) { - break; - } - } - } -} - -async function deserializeStream(id: string, response: Response) { - if (!response.body) { - throw new Error("missing body"); - } - const reader = new SerovalChunkReader(response.body); - - const result = await reader.next(); - - if (!result.done) { - reader.drain().then( - () => { - // @ts-ignore - delete $R[id]; - }, - () => { - // no-op - } - ); - } - - return result.value; -} +import { + deserializeJSONStream, + deserializeJSStream, + serializeToJSONString, +} from "./serialization"; let INSTANCE = 0; -function createRequest(base: string, id: string, instance: string, options: RequestInit) { +function createRequest( + base: string, + id: string, + instance: string, + options: RequestInit, +) { return fetch(base, { method: "POST", ...options, headers: { ...options.headers, "X-Server-Id": id, - "X-Server-Instance": instance - } + "X-Server-Instance": instance, + }, }); } -const plugins = [ - CustomEventPlugin, - DOMExceptionPlugin, - EventPlugin, - FormDataPlugin, - HeadersPlugin, - ReadableStreamPlugin, - RequestPlugin, - ResponsePlugin, - URLSearchParamsPlugin, - URLPlugin -]; - async function fetchServerFunction( base: string, id: string, options: Omit, - args: any[] + args: any[], ) { const instance = `server-fn:${INSTANCE++}`; const response = await (args.length === 0 ? createRequest(base, id, instance, options) : args.length === 1 && args[0] instanceof FormData - ? createRequest(base, id, instance, { ...options, body: args[0] }) - : args.length === 1 && args[0] instanceof URLSearchParams - ? createRequest(base, id, instance, { - ...options, - body: args[0], - headers: { ...options.headers, "Content-Type": "application/x-www-form-urlencoded" } - }) - : createRequest(base, id, instance, { - ...options, - body: JSON.stringify(await Promise.resolve(toJSONAsync(args, { plugins }))), - headers: { ...options.headers, "Content-Type": "application/json" } - })); + ? createRequest(base, id, instance, { ...options, body: args[0] }) + : args.length === 1 && args[0] instanceof URLSearchParams + ? createRequest(base, id, instance, { + ...options, + body: args[0], + headers: { + ...options.headers, + "Content-Type": "application/x-www-form-urlencoded", + }, + }) + : createRequest(base, id, instance, { + ...options, + body: await serializeToJSONString(args), + // duplex: 'half', + // body: serializeToJSONStream(args), + headers: { ...options.headers, "Content-Type": "text/plain" }, + })); if ( response.headers.has("Location") || @@ -175,7 +61,10 @@ async function fetchServerFunction( if (response.body) { /* @ts-ignore-next-line */ response.customBody = () => { - return deserializeStream(instance, response); + if (import.meta.env.SEROVAL_MODE === "js") { + return deserializeJSStream(instance, response); + } + return deserializeJSONStream(response); }; } return response; @@ -187,8 +76,12 @@ async function fetchServerFunction( result = await response.text(); } else if (contentType && contentType.startsWith("application/json")) { result = await response.json(); - } else { - result = await deserializeStream(instance, response); + } else if (response.headers.get("x-serialized")) { + if (import.meta.env.SEROVAL_MODE === "js") { + result = await deserializeJSStream(instance, response); + } else { + result = await deserializeJSONStream(response); + } } if (response.headers.has("X-Error")) { throw result; @@ -208,23 +101,24 @@ export function createServerReference(fn: Function, id: string, name: string) { } if (prop === "withOptions") { const url = `${baseURL}/_server/?id=${encodeURIComponent(id)}&name=${encodeURIComponent( - name + name, )}`; return (options: RequestInit) => { const fn = async (...args: any[]) => { - const encodeArgs = options.method && options.method.toUpperCase() === "GET"; + const encodeArgs = + options.method && options.method.toUpperCase() === "GET"; return fetchServerFunction( encodeArgs ? url + - (args.length - ? `&args=${encodeURIComponent( - JSON.stringify(await Promise.resolve(toJSONAsync(args, { plugins }))) - )}` - : "") + (args.length + ? `&args=${encodeURIComponent( + await serializeToJSONString(args), + )}` + : "") : `${baseURL}/_server`, `${id}#${name}`, options, - encodeArgs ? [] : args + encodeArgs ? [] : args, ); }; fn.url = url; @@ -234,12 +128,21 @@ export function createServerReference(fn: Function, id: string, name: string) { return (target as any)[prop]; }, apply(target, thisArg, args) { - return fetchServerFunction(`${baseURL}/_server`, `${id}#${name}`, {}, args); - } + return fetchServerFunction( + `${baseURL}/_server`, + `${id}#${name}`, + {}, + args, + ); + }, }); } -export function createClientReference(Component: Component, id: string, name: string) { +export function createClientReference( + Component: Component, + id: string, + name: string, +) { if (typeof Component === "function") { return createIslandReference(Component, id, name); } From df14ad89b4cd42a93f0b6e21a3472e71b9a1bb92 Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Sun, 4 Jan 2026 10:07:32 +0800 Subject: [PATCH 3/7] Add new header --- packages/start/src/runtime/server-runtime.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/start/src/runtime/server-runtime.ts b/packages/start/src/runtime/server-runtime.ts index c06d23a9e..6f6887101 100644 --- a/packages/start/src/runtime/server-runtime.ts +++ b/packages/start/src/runtime/server-runtime.ts @@ -50,7 +50,11 @@ async function fetchServerFunction( body: await serializeToJSONString(args), // duplex: 'half', // body: serializeToJSONStream(args), - headers: { ...options.headers, "Content-Type": "text/plain" }, + headers: { + ...options.headers, + "x-serialized": "true", + "Content-Type": "text/plain" + }, })); if ( From 974500fd2f332620a97d502846651e87151087cf Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Mon, 5 Jan 2026 13:12:03 +0800 Subject: [PATCH 4/7] Clone response --- packages/start/src/runtime/server-runtime.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/start/src/runtime/server-runtime.ts b/packages/start/src/runtime/server-runtime.ts index 6f6887101..1a5b32214 100644 --- a/packages/start/src/runtime/server-runtime.ts +++ b/packages/start/src/runtime/server-runtime.ts @@ -66,25 +66,26 @@ async function fetchServerFunction( /* @ts-ignore-next-line */ response.customBody = () => { if (import.meta.env.SEROVAL_MODE === "js") { - return deserializeJSStream(instance, response); + return deserializeJSStream(instance, response.clone()); } - return deserializeJSONStream(response); + return deserializeJSONStream(response.clone()); }; } return response; } const contentType = response.headers.get("Content-Type"); + const cloned = response.clone(); let result; if (contentType && contentType.startsWith("text/plain")) { - result = await response.text(); + result = await cloned.text(); } else if (contentType && contentType.startsWith("application/json")) { - result = await response.json(); + result = await cloned.json(); } else if (response.headers.get("x-serialized")) { if (import.meta.env.SEROVAL_MODE === "js") { - result = await deserializeJSStream(instance, response); + result = await deserializeJSStream(instance, cloned); } else { - result = await deserializeJSONStream(response); + result = await deserializeJSONStream(cloned); } } if (response.headers.has("X-Error")) { From 1d1a515b1dab04c216453a30e11a99970949bc56 Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Fri, 9 Jan 2026 20:38:59 +0800 Subject: [PATCH 5/7] Fix seroval env --- packages/start/config/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/start/config/index.js b/packages/start/config/index.js index 90cc14eeb..de76a5784 100644 --- a/packages/start/config/index.js +++ b/packages/start/config/index.js @@ -235,6 +235,7 @@ export function defineConfig(baseConfig = {}) { "import.meta.env.START_SSR": JSON.stringify(start.ssr), "import.meta.env.START_DEV_OVERLAY": JSON.stringify(start.devOverlay), "import.meta.env.SERVER_BASE_URL": JSON.stringify(server?.baseURL ?? ""), + "import.meta.env.SEROVAL_MODE": JSON.stringify(start.serialization?.mode || 'json'), ...userConfig.define } }) @@ -294,6 +295,7 @@ export function defineConfig(baseConfig = {}) { "import.meta.env.SSR": JSON.stringify(true), "import.meta.env.START_SSR": JSON.stringify(start.ssr), "import.meta.env.START_DEV_OVERLAY": JSON.stringify(start.devOverlay), + "import.meta.env.SEROVAL_MODE": JSON.stringify(start.serialization?.mode || 'json'), ...userConfig.define } }) From e8eb334119a2c856da057a5c3468a98363e208e7 Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Wed, 14 Jan 2026 01:39:55 +0800 Subject: [PATCH 6/7] Revert request body serialization mode --- packages/start/src/runtime/serialization.ts | 17 +++++++++++++---- packages/start/src/runtime/server-handler.ts | 10 +++++----- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/start/src/runtime/serialization.ts b/packages/start/src/runtime/serialization.ts index c157d157e..b206a6830 100644 --- a/packages/start/src/runtime/serialization.ts +++ b/packages/start/src/runtime/serialization.ts @@ -3,9 +3,11 @@ import { deserialize, Feature, fromCrossJSON, + fromJSON, getCrossReferenceHeader, type SerovalNode, toCrossJSONStream, + toJSONAsync, } from "seroval"; import { AbortSignalPlugin, @@ -194,13 +196,20 @@ class SerovalChunkReader { } export async function serializeToJSONString(value: any) { - const response = new Response(serializeToJSONStream(value)); - return await response.text(); + // const response = new Response(serializeToJSONStream(value)); + // return await response.text(); + return JSON.stringify(toJSONAsync(value, { + plugins: DEFAULT_PLUGINS, + depthLimit: MAX_SERIALIZATION_DEPTH_LIMIT, + disabledFeatures: DISABLED_FEATURES, + })); } export async function deserializeFromJSONString(json: string) { - const blob = new Response(json); - return await deserializeJSONStream(blob); + return fromJSON(JSON.parse(json), { + plugins: DEFAULT_PLUGINS, + disabledFeatures: DISABLED_FEATURES, + }); } export async function deserializeJSONStream(response: Response | Request) { diff --git a/packages/start/src/runtime/server-handler.ts b/packages/start/src/runtime/server-handler.ts index a7136a0de..9b8e55739 100644 --- a/packages/start/src/runtime/server-handler.ts +++ b/packages/start/src/runtime/server-handler.ts @@ -18,7 +18,7 @@ import { createPageEvent } from "../server/pageEvent"; import { FetchEvent, PageEvent } from "../server"; // @ts-ignore import serverFnManifest from "solidstart:server-fn-manifest"; -import { deserializeFromJSONString, deserializeJSONStream, serializeToJSONStream, serializeToJSStream } from "./serialization"; +import { deserializeFromJSONString, serializeToJSONStream, serializeToJSStream } from "./serialization"; async function handleServerFunction(h3Event: HTTPEvent) { const event = getFetchEvent(h3Event); @@ -100,7 +100,9 @@ async function handleServerFunction(h3Event: HTTPEvent) { const tmpReq = isH3EventBodyStreamLocked ? request : new Request(request, { ...request, body: requestBody }); - if ( + if (request.headers.get('x-serialized')) { + parsed = await deserializeFromJSONString(await tmpReq.text()) as any[]; + } else if ( contentType?.startsWith("multipart/form-data") || contentType?.startsWith("application/x-www-form-urlencoded") ) { @@ -113,9 +115,7 @@ async function handleServerFunction(h3Event: HTTPEvent) { // what should work when #1721 is fixed // just use request.json() here parsed = await tmpReq.json() as any[]; - } else if (request.headers.get('x-serialized')) { - parsed = await deserializeJSONStream(tmpReq) as any[]; - } + } } try { let result = await provideRequestEvent(event, async () => { From 1958520aea29c360d158e3a7213f12f7bccc0a47 Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Sat, 21 Feb 2026 13:02:13 +0100 Subject: [PATCH 7/7] Improve DX and test-cases on seroval mode (#2073) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- apps/fixtures/serialization-modes/README.md | 19 ++ .../serialization-modes/app.config.ts | 8 + .../fixtures/serialization-modes/package.json | 21 ++ .../serialization-modes/public/favicon.ico | Bin 0 -> 664 bytes apps/fixtures/serialization-modes/src/app.css | 39 +++ apps/fixtures/serialization-modes/src/app.tsx | 22 ++ .../src/components/Counter.css | 21 ++ .../src/components/Counter.tsx | 11 + .../serialization-modes/src/entry-client.tsx | 4 + .../serialization-modes/src/entry-server.tsx | 21 ++ .../serialization-modes/src/global.d.ts | 1 + .../serialization-modes/src/middleware.ts | 26 ++ .../src/routes/[...404].tsx | 19 ++ .../serialization-modes/src/routes/index.tsx | 30 +++ .../serialization-modes/tsconfig.json | 19 ++ apps/tests/src/middleware.ts | 5 + .../src/routes/actions/use-submission.tsx | 23 ++ packages/start/config/index.d.ts | 15 +- packages/start/config/index.js | 9 +- pnpm-lock.yaml | 251 +++++++++++++++++- pnpm-workspace.yaml | 7 +- 21 files changed, 560 insertions(+), 11 deletions(-) create mode 100644 apps/fixtures/serialization-modes/README.md create mode 100644 apps/fixtures/serialization-modes/app.config.ts create mode 100644 apps/fixtures/serialization-modes/package.json create mode 100644 apps/fixtures/serialization-modes/public/favicon.ico create mode 100644 apps/fixtures/serialization-modes/src/app.css create mode 100644 apps/fixtures/serialization-modes/src/app.tsx create mode 100644 apps/fixtures/serialization-modes/src/components/Counter.css create mode 100644 apps/fixtures/serialization-modes/src/components/Counter.tsx create mode 100644 apps/fixtures/serialization-modes/src/entry-client.tsx create mode 100644 apps/fixtures/serialization-modes/src/entry-server.tsx create mode 100644 apps/fixtures/serialization-modes/src/global.d.ts create mode 100644 apps/fixtures/serialization-modes/src/middleware.ts create mode 100644 apps/fixtures/serialization-modes/src/routes/[...404].tsx create mode 100644 apps/fixtures/serialization-modes/src/routes/index.tsx create mode 100644 apps/fixtures/serialization-modes/tsconfig.json create mode 100644 apps/tests/src/middleware.ts create mode 100644 apps/tests/src/routes/actions/use-submission.tsx diff --git a/apps/fixtures/serialization-modes/README.md b/apps/fixtures/serialization-modes/README.md new file mode 100644 index 000000000..a117e56c4 --- /dev/null +++ b/apps/fixtures/serialization-modes/README.md @@ -0,0 +1,19 @@ +# Serialization checks + +This fixture is designed to point out the differences between Seroval 2 modes. + +```ts +export default defineConfig({ + middleware: "./src/middleware.ts", + serialization: { + mode: "js" // "json" + } +}); +``` + +On JS mode, seroval will use a custom serializer, while this improves performance and reduces payload size, it runs an `eval()` on client-side, +so a strict CSP will block deserialization. On JSON mode, the payload will be slightly larger, but deserialization happens via `JSON.parse` and thus CSP will not block it. + +> [!IMPORTANT] +> For backwards compatibility, `v1` has "js" as the default. +> On `v2`, "json" is the new default. diff --git a/apps/fixtures/serialization-modes/app.config.ts b/apps/fixtures/serialization-modes/app.config.ts new file mode 100644 index 000000000..313ebbb2d --- /dev/null +++ b/apps/fixtures/serialization-modes/app.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "@solidjs/start/config"; + +export default defineConfig({ + middleware: "./src/middleware.ts", + serialization: { + mode: "js" + } +}); diff --git a/apps/fixtures/serialization-modes/package.json b/apps/fixtures/serialization-modes/package.json new file mode 100644 index 000000000..9ce41c5d5 --- /dev/null +++ b/apps/fixtures/serialization-modes/package.json @@ -0,0 +1,21 @@ +{ + "name": "fixture-serialization-modes", + "type": "module", + "scripts": { + "dev": "vinxi dev", + "build": "vinxi build", + "start": "vinxi start", + "version": "vinxi version" + }, + "dependencies": { + "@solidjs/meta": "^0.29.4", + "@solidjs/router": "^0.15.0", + "@solidjs/start": "workspace:*", + "shieldwall": "^0.4.0", + "solid-js": "^1.9.5", + "vinxi": "^0.5.7" + }, + "engines": { + "node": ">=22" + } +} diff --git a/apps/fixtures/serialization-modes/public/favicon.ico b/apps/fixtures/serialization-modes/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..fb282da0719ef6ab4c1732df93be6216b0d85520 GIT binary patch literal 664 zcmV;J0%!e+P)m9ebk1R zejT~~6f_`?;`cEd!+`7(hw@%%2;?RN8gX-L?z6cM( zKoG@&w+0}f@Pfvwc+deid)qgE!L$ENKYjViZC_Zcr>L(`2oXUT8f0mRQ(6-=HN_Ai zeBBEz3WP+1Cw`m!49Wf!MnZzp5bH8VkR~BcJ1s-j90TAS2Yo4j!J|KodxYR%3Numw zA?gq6e`5@!W~F$_De3yt&uspo&2yLb$(NwcPPI-4LGc!}HdY%jfq@AFs8LiZ4k(p} zZ!c9o+qbWYs-Mg zgdyTALzJX&7QXHdI_DPTFL33;w}88{e6Zk)MX0kN{3DX9uz#O_L58&XRH$Nvvu;fO zf&)7@?C~$z1K<>j0ga$$MIg+5xN;eQ?1-CA=`^Y169@Ab6!vcaNP=hxfKN%@Ly^R* zK1iv*s1Yl6_dVyz8>ZqYhz6J4|3fQ@2LQeX@^%W(B~8>=MoEmBEGGD1;gHXlpX>!W ym)!leA2L@`cpb^hy)P75=I!`pBYxP7<2VfQ3j76qLgzIA0000 ( + + SolidStart - Basic + Index + About + {props.children} + + )} + > + + + ); +} diff --git a/apps/fixtures/serialization-modes/src/components/Counter.css b/apps/fixtures/serialization-modes/src/components/Counter.css new file mode 100644 index 000000000..220e17946 --- /dev/null +++ b/apps/fixtures/serialization-modes/src/components/Counter.css @@ -0,0 +1,21 @@ +.increment { + font-family: inherit; + font-size: inherit; + padding: 1em 2em; + color: #335d92; + background-color: rgba(68, 107, 158, 0.1); + border-radius: 2em; + border: 2px solid rgba(68, 107, 158, 0); + outline: none; + width: 200px; + font-variant-numeric: tabular-nums; + cursor: pointer; +} + +.increment:focus { + border: 2px solid #335d92; +} + +.increment:active { + background-color: rgba(68, 107, 158, 0.2); +} \ No newline at end of file diff --git a/apps/fixtures/serialization-modes/src/components/Counter.tsx b/apps/fixtures/serialization-modes/src/components/Counter.tsx new file mode 100644 index 000000000..091fc5d0b --- /dev/null +++ b/apps/fixtures/serialization-modes/src/components/Counter.tsx @@ -0,0 +1,11 @@ +import { createSignal } from "solid-js"; +import "./Counter.css"; + +export default function Counter() { + const [count, setCount] = createSignal(0); + return ( + + ); +} diff --git a/apps/fixtures/serialization-modes/src/entry-client.tsx b/apps/fixtures/serialization-modes/src/entry-client.tsx new file mode 100644 index 000000000..0ca4e3c30 --- /dev/null +++ b/apps/fixtures/serialization-modes/src/entry-client.tsx @@ -0,0 +1,4 @@ +// @refresh reload +import { mount, StartClient } from "@solidjs/start/client"; + +mount(() => , document.getElementById("app")!); diff --git a/apps/fixtures/serialization-modes/src/entry-server.tsx b/apps/fixtures/serialization-modes/src/entry-server.tsx new file mode 100644 index 000000000..401eff83f --- /dev/null +++ b/apps/fixtures/serialization-modes/src/entry-server.tsx @@ -0,0 +1,21 @@ +// @refresh reload +import { createHandler, StartServer } from "@solidjs/start/server"; + +export default createHandler(() => ( + ( + + + + + + {assets} + + +
{children}
+ {scripts} + + + )} + /> +)); diff --git a/apps/fixtures/serialization-modes/src/global.d.ts b/apps/fixtures/serialization-modes/src/global.d.ts new file mode 100644 index 000000000..dc6f10c22 --- /dev/null +++ b/apps/fixtures/serialization-modes/src/global.d.ts @@ -0,0 +1 @@ +/// diff --git a/apps/fixtures/serialization-modes/src/middleware.ts b/apps/fixtures/serialization-modes/src/middleware.ts new file mode 100644 index 000000000..b00d186a8 --- /dev/null +++ b/apps/fixtures/serialization-modes/src/middleware.ts @@ -0,0 +1,26 @@ +import { createMiddleware } from "@solidjs/start/middleware"; +import { csp } from "shieldwall/start"; +import { UNSAFE_INLINE } from "shieldwall/start/csp"; + +export default createMiddleware({ + onRequest: [ + csp({ + extend: "production_basic", + config: { + withNonce: false, + reportOnly: false, + value: { + "default-src": ["self"], + "script-src": ["self", UNSAFE_INLINE, "http:"], + "style-src": ["self", UNSAFE_INLINE], + "img-src": ["self", "data:", "https:", "http:"], + "font-src": ["self"], + "connect-src": ["self", "ws://localhost:*", "http://localhost:*"], + "frame-src": ["self"], + "base-uri": ["self"] + // "form-action": ["self"] + } + } + }) + ] +}); diff --git a/apps/fixtures/serialization-modes/src/routes/[...404].tsx b/apps/fixtures/serialization-modes/src/routes/[...404].tsx new file mode 100644 index 000000000..4ea71ec7f --- /dev/null +++ b/apps/fixtures/serialization-modes/src/routes/[...404].tsx @@ -0,0 +1,19 @@ +import { Title } from "@solidjs/meta"; +import { HttpStatusCode } from "@solidjs/start"; + +export default function NotFound() { + return ( +
+ Not Found + +

Page Not Found

+

+ Visit{" "} + + start.solidjs.com + {" "} + to learn how to build SolidStart apps. +

+
+ ); +} diff --git a/apps/fixtures/serialization-modes/src/routes/index.tsx b/apps/fixtures/serialization-modes/src/routes/index.tsx new file mode 100644 index 000000000..a08ccf229 --- /dev/null +++ b/apps/fixtures/serialization-modes/src/routes/index.tsx @@ -0,0 +1,30 @@ +import { Title } from "@solidjs/meta"; +import { createEffect } from "solid-js"; +import Counter from "~/components/Counter"; + +const breakval = () => { + "use server"; + + return new Date(); +}; + +export default function Home() { + createEffect(() => { + console.log(breakval()); + }); + + return ( +
+ Hello World +

Hello world!

+ +

+ Visit{" "} + + start.solidjs.com + {" "} + to learn how to build SolidStart apps. +

+
+ ); +} diff --git a/apps/fixtures/serialization-modes/tsconfig.json b/apps/fixtures/serialization-modes/tsconfig.json new file mode 100644 index 000000000..7d5871a07 --- /dev/null +++ b/apps/fixtures/serialization-modes/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "allowJs": true, + "strict": true, + "noEmit": true, + "types": ["vinxi/types/client"], + "isolatedModules": true, + "paths": { + "~/*": ["./src/*"] + } + } +} diff --git a/apps/tests/src/middleware.ts b/apps/tests/src/middleware.ts new file mode 100644 index 000000000..ad8bab262 --- /dev/null +++ b/apps/tests/src/middleware.ts @@ -0,0 +1,5 @@ +import { createMiddleware } from "@solidjs/start/middleware"; + +export default createMiddleware({ + onRequest: [] +}); diff --git a/apps/tests/src/routes/actions/use-submission.tsx b/apps/tests/src/routes/actions/use-submission.tsx new file mode 100644 index 000000000..e1cb69d3b --- /dev/null +++ b/apps/tests/src/routes/actions/use-submission.tsx @@ -0,0 +1,23 @@ +import { Title } from "@solidjs/meta"; +import { action, useSubmission } from "@solidjs/router"; +import { Show } from "solid-js"; + +const actionStuff = action(async () => { + "use server"; + + return "Hello world!"; +}, "actionStuff"); + +export default function Home() { + const actionData = useSubmission(actionStuff); + return ( +
+ Hello World +
+ +
+ + {result =>

{result().result}

}
+
+ ); +} diff --git a/packages/start/config/index.d.ts b/packages/start/config/index.d.ts index 7b00c1814..bb1613f64 100644 --- a/packages/start/config/index.d.ts +++ b/packages/start/config/index.d.ts @@ -27,9 +27,18 @@ type SolidStartInlineConfig = { islands?: boolean; }; serialization?: { - // This only matters for server function responses - mode?: 'js' | 'json'; - } + /** + * The serialization mode to use for server functions. + * The "js" mode uses a custom binary format that is more efficient than JSON, but requires a custom deserializer (with `eval()`) on the client. + * A strong CSP should block `eval()` executions, which would prevent the "js" mode from working. + * The "json" mode uses JSON for serialization, which is less efficient but can be deserialized with `JSON.parse` on the client. + * + * @default "js" + * @ + * @warning on v2, "json" will be the default. + */ + mode?: "js" | "json"; + }; vite?: | ViteCustomizableConfig | ((options: { router: "server" | "client" | "server-function" }) => ViteCustomizableConfig); diff --git a/packages/start/config/index.js b/packages/start/config/index.js index de76a5784..afc40039e 100644 --- a/packages/start/config/index.js +++ b/packages/start/config/index.js @@ -89,6 +89,7 @@ export function defineConfig(baseConfig = {}) { } } }); + const routeDir = join(start.appRoot, start.routeDir); let server = start.server; if (!start.ssr) { @@ -99,6 +100,8 @@ export function defineConfig(baseConfig = {}) { entryExtension = ".jsx"; } + const serializationMode = start.serialization?.mode || 'js' + return createApp({ server: { compressPublicAssets: { @@ -167,7 +170,7 @@ export function defineConfig(baseConfig = {}) { "import.meta.env.SSR": JSON.stringify(true), "import.meta.env.START_SSR": JSON.stringify(start.ssr), "import.meta.env.START_DEV_OVERLAY": JSON.stringify(start.devOverlay), - "import.meta.env.SEROVAL_MODE": JSON.stringify(start.serialization?.mode || 'json'), + "import.meta.env.SEROVAL_MODE": JSON.stringify(serializationMode), ...userConfig.define } }) @@ -235,7 +238,7 @@ export function defineConfig(baseConfig = {}) { "import.meta.env.START_SSR": JSON.stringify(start.ssr), "import.meta.env.START_DEV_OVERLAY": JSON.stringify(start.devOverlay), "import.meta.env.SERVER_BASE_URL": JSON.stringify(server?.baseURL ?? ""), - "import.meta.env.SEROVAL_MODE": JSON.stringify(start.serialization?.mode || 'json'), + "import.meta.env.SEROVAL_MODE": JSON.stringify(serializationMode), ...userConfig.define } }) @@ -295,7 +298,7 @@ export function defineConfig(baseConfig = {}) { "import.meta.env.SSR": JSON.stringify(true), "import.meta.env.START_SSR": JSON.stringify(start.ssr), "import.meta.env.START_DEV_OVERLAY": JSON.stringify(start.devOverlay), - "import.meta.env.SEROVAL_MODE": JSON.stringify(start.serialization?.mode || 'json'), + "import.meta.env.SEROVAL_MODE": JSON.stringify(serializationMode), ...userConfig.define } }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f17d8a90b..8e93bea21 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,132 @@ importers: apps/fixtures: {} + apps/fixtures/bare: + dependencies: + '@solidjs/start': + specifier: ^1.1.0 + version: 1.2.1(@testing-library/jest-dom@6.8.0)(solid-js@1.9.9)(vinxi@0.5.8(@types/node@25.0.3)(db0@0.3.2)(ioredis@5.7.0)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1))(vite@6.3.6(@types/node@25.0.3)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1)) + solid-js: + specifier: ^1.9.5 + version: 1.9.9 + vinxi: + specifier: ^0.5.7 + version: 0.5.8(@types/node@25.0.3)(db0@0.3.2)(ioredis@5.7.0)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1) + + apps/fixtures/basic: + dependencies: + '@solidjs/meta': + specifier: ^0.29.4 + version: 0.29.4(solid-js@1.9.9) + '@solidjs/router': + specifier: ^0.15.0 + version: 0.15.3(solid-js@1.9.9) + '@solidjs/start': + specifier: ^1.1.0 + version: 1.2.1(@testing-library/jest-dom@6.8.0)(solid-js@1.9.9)(vinxi@0.5.8(@types/node@25.0.3)(db0@0.3.2)(ioredis@5.7.0)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1))(vite@6.3.6(@types/node@25.0.3)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1)) + solid-js: + specifier: ^1.9.5 + version: 1.9.9 + vinxi: + specifier: ^0.5.7 + version: 0.5.8(@types/node@25.0.3)(db0@0.3.2)(ioredis@5.7.0)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1) + + apps/fixtures/experiments: + dependencies: + '@solidjs/meta': + specifier: ^0.29.4 + version: 0.29.4(solid-js@1.9.9) + '@solidjs/router': + specifier: ^0.15.0 + version: 0.15.3(solid-js@1.9.9) + '@solidjs/start': + specifier: ^1.1.0 + version: 1.2.1(@testing-library/jest-dom@6.8.0)(solid-js@1.9.9)(vinxi@0.5.8(@types/node@25.0.3)(db0@0.3.2)(ioredis@5.7.0)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1))(vite@6.3.6(@types/node@25.0.3)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1)) + solid-js: + specifier: ^1.9.5 + version: 1.9.9 + vinxi: + specifier: ^0.5.7 + version: 0.5.8(@types/node@25.0.3)(db0@0.3.2)(ioredis@5.7.0)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1) + + apps/fixtures/hackernews: + dependencies: + '@solidjs/router': + specifier: ^0.15.0 + version: 0.15.3(solid-js@1.9.9) + '@solidjs/start': + specifier: ^1.1.0 + version: 1.2.1(@testing-library/jest-dom@6.8.0)(solid-js@1.9.9)(vinxi@0.5.8(@types/node@25.0.3)(db0@0.3.2)(ioredis@5.7.0)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1))(vite@6.3.6(@types/node@25.0.3)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1)) + solid-js: + specifier: ^1.9.5 + version: 1.9.9 + vinxi: + specifier: ^0.5.7 + version: 0.5.8(@types/node@25.0.3)(db0@0.3.2)(ioredis@5.7.0)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1) + + apps/fixtures/notes: + dependencies: + '@solidjs/router': + specifier: ^0.15.0 + version: 0.15.3(solid-js@1.9.9) + '@solidjs/start': + specifier: ^1.1.0 + version: 1.2.1(@testing-library/jest-dom@6.8.0)(solid-js@1.9.9)(vinxi@0.5.8(@types/node@25.0.3)(db0@0.3.2)(ioredis@5.7.0)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1))(vite@6.3.6(@types/node@25.0.3)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1)) + date-fns: + specifier: ^3.6.0 + version: 3.6.0 + marked: + specifier: ^12.0.1 + version: 12.0.2 + solid-js: + specifier: ^1.9.5 + version: 1.9.9 + unstorage: + specifier: 1.10.2 + version: 1.10.2(ioredis@5.7.0) + vinxi: + specifier: ^0.5.7 + version: 0.5.8(@types/node@25.0.3)(db0@0.3.2)(ioredis@5.7.0)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1) + + apps/fixtures/serialization-modes: + dependencies: + '@solidjs/meta': + specifier: ^0.29.4 + version: 0.29.4(solid-js@1.9.9) + '@solidjs/router': + specifier: ^0.15.0 + version: 0.15.3(solid-js@1.9.9) + '@solidjs/start': + specifier: workspace:* + version: link:../../../packages/start + shieldwall: + specifier: ^0.4.0 + version: 0.4.0(@solidjs/start@packages+start) + solid-js: + specifier: ^1.9.5 + version: 1.9.9 + vinxi: + specifier: ^0.5.7 + version: 0.5.8(@types/node@25.0.3)(db0@0.3.2)(ioredis@5.7.0)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1) + + apps/fixtures/todomvc: + dependencies: + '@solidjs/router': + specifier: ^0.15.0 + version: 0.15.3(solid-js@1.9.9) + '@solidjs/start': + specifier: ^1.1.0 + version: 1.2.1(@testing-library/jest-dom@6.8.0)(solid-js@1.9.9)(vinxi@0.5.8(@types/node@25.0.3)(db0@0.3.2)(ioredis@5.7.0)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1))(vite@6.3.6(@types/node@25.0.3)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1)) + solid-js: + specifier: ^1.9.5 + version: 1.9.9 + unstorage: + specifier: 1.10.2 + version: 1.10.2(ioredis@5.7.0) + vinxi: + specifier: ^0.5.7 + version: 0.5.8(@types/node@25.0.3)(db0@0.3.2)(ioredis@5.7.0)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1) + apps/landing-page: dependencies: '@solidjs/meta': @@ -1568,6 +1694,11 @@ packages: peerDependencies: solid-js: ^1.8.6 + '@solidjs/start@1.2.1': + resolution: {integrity: sha512-O5E7rcCwm2f8GlXKgS2xnU37Ld5vMVXJgo/qR7UI5iR5uFo9V2Ac+SSVNXkM98CeHKHt55h1UjbpxxTANEsHmA==} + peerDependencies: + vinxi: ^0.5.7 + '@solidjs/testing-library@0.8.10': resolution: {integrity: sha512-qdeuIerwyq7oQTIrrKvV0aL9aFeuwTd86VYD3afdq5HYEwoox1OBTJy4y8A3TFZr8oAR0nujYgCzY/8wgHGfeQ==} engines: {node: '>= 14'} @@ -2400,6 +2531,10 @@ packages: crossws@0.3.5: resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + csp-header@5.2.1: + resolution: {integrity: sha512-qOJNu39JZkPrbrAM40a1tQCePEPYVIoI6nMDhX4RA07QjU8efS+zyd/zE83XJu85KKazH9NjKlvvlswFMteMgg==} + engines: {node: '>=10'} + css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} @@ -2436,8 +2571,12 @@ packages: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} + date-fns@3.6.0: + resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} + dax-sh@0.43.2: resolution: {integrity: sha512-uULa1sSIHgXKGCqJ/pA0zsnzbHlVnuq7g8O2fkHokWFNwEGIhh5lAJlxZa1POG5En5ba7AU4KcBAvGQWMMf8rg==} + deprecated: This package has moved to simply be 'dax' instead of 'dax-sh' dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} @@ -2962,11 +3101,12 @@ packages: glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} @@ -3452,6 +3592,11 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} + marked@12.0.2: + resolution: {integrity: sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==} + engines: {node: '>= 18'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -4196,6 +4341,11 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shieldwall@0.4.0: + resolution: {integrity: sha512-3IO6SxrRUlL7BGWnfhdACnuG8bqa+kKzSgtuW0daZpdF9iGoEW9EroY2djuP1AjY/R3SCbql6xCJ1CtX+GDMRw==} + peerDependencies: + '@solidjs/start': ^1.1.1 + shiki@1.29.2: resolution: {integrity: sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg==} @@ -4442,6 +4592,7 @@ packages: tar@7.4.4: resolution: {integrity: sha512-O1z7ajPkjTgEgmTGz0v9X4eqeEXTDREPTO77pVC1Nbs86feBU1Zhdg+edzavPmYW1olxkwsqA2v4uOw6E8LeDg==} engines: {node: '>=18'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} @@ -4689,6 +4840,50 @@ packages: resolution: {integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==} engines: {node: '>=18.12.0'} + unstorage@1.10.2: + resolution: {integrity: sha512-cULBcwDqrS8UhlIysUJs2Dk0Mmt8h7B0E6mtR+relW9nZvsf/u4SkAYyNliPiPW7XtFNb5u3IUMkxGxFTTRTgQ==} + peerDependencies: + '@azure/app-configuration': ^1.5.0 + '@azure/cosmos': ^4.0.0 + '@azure/data-tables': ^13.2.2 + '@azure/identity': ^4.0.1 + '@azure/keyvault-secrets': ^4.8.0 + '@azure/storage-blob': ^12.17.0 + '@capacitor/preferences': ^5.0.7 + '@netlify/blobs': ^6.5.0 || ^7.0.0 + '@planetscale/database': ^1.16.0 + '@upstash/redis': ^1.28.4 + '@vercel/kv': ^1.0.1 + idb-keyval: ^6.2.1 + ioredis: ^5.3.2 + peerDependenciesMeta: + '@azure/app-configuration': + optional: true + '@azure/cosmos': + optional: true + '@azure/data-tables': + optional: true + '@azure/identity': + optional: true + '@azure/keyvault-secrets': + optional: true + '@azure/storage-blob': + optional: true + '@capacitor/preferences': + optional: true + '@netlify/blobs': + optional: true + '@planetscale/database': + optional: true + '@upstash/redis': + optional: true + '@vercel/kv': + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + unstorage@1.17.1: resolution: {integrity: sha512-KKGwRTT0iVBCErKemkJCLs7JdxNVfqTPc/85ae1XES0+bsHbc/sFBfVi5kJp156cc51BHinIH2l3k0EZ24vOBQ==} peerDependencies: @@ -4931,6 +5126,7 @@ packages: whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-mimetype@4.0.0: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} @@ -6647,6 +6843,30 @@ snapshots: dependencies: solid-js: 1.9.9 + '@solidjs/start@1.2.1(@testing-library/jest-dom@6.8.0)(solid-js@1.9.9)(vinxi@0.5.8(@types/node@25.0.3)(db0@0.3.2)(ioredis@5.7.0)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1))(vite@6.3.6(@types/node@25.0.3)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1))': + dependencies: + '@tanstack/server-functions-plugin': 1.121.21(vite@6.3.6(@types/node@25.0.3)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1)) + '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.8(@types/node@25.0.3)(db0@0.3.2)(ioredis@5.7.0)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1)) + '@vinxi/server-components': 0.5.1(vinxi@0.5.8(@types/node@25.0.3)(db0@0.3.2)(ioredis@5.7.0)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1)) + cookie-es: 2.0.0 + defu: 6.1.4 + error-stack-parser: 2.1.4 + html-to-image: 1.11.13 + radix3: 1.1.2 + seroval: 1.4.1 + seroval-plugins: 1.4.0(seroval@1.4.1) + shiki: 1.29.2 + source-map-js: 1.2.1 + terracotta: 1.0.6(solid-js@1.9.9) + tinyglobby: 0.2.15 + vinxi: 0.5.8(@types/node@25.0.3)(db0@0.3.2)(ioredis@5.7.0)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1) + vite-plugin-solid: 2.11.8(@testing-library/jest-dom@6.8.0)(solid-js@1.9.9)(vite@6.3.6(@types/node@25.0.3)(jiti@2.6.0)(terser@5.44.1)(yaml@2.8.1)) + transitivePeerDependencies: + - '@testing-library/jest-dom' + - solid-js + - supports-color + - vite + '@solidjs/testing-library@0.8.10(@solidjs/router@0.15.3(solid-js@1.9.9))(solid-js@1.9.9)': dependencies: '@testing-library/dom': 10.4.1 @@ -6836,7 +7056,7 @@ snapshots: consola: 3.4.2 defu: 6.1.4 get-port-please: 3.2.0 - h3: 1.15.3 + h3: 1.15.4 http-shutdown: 1.2.2 jiti: 1.21.7 mlly: 1.8.0 @@ -7588,6 +7808,8 @@ snapshots: dependencies: uncrypto: 0.1.3 + csp-header@5.2.1: {} + css.escape@1.5.1: {} cssesc@3.0.0: {} @@ -7667,6 +7889,8 @@ snapshots: whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 + date-fns@3.6.0: {} + dax-sh@0.43.2: dependencies: '@deno/shim-deno': 0.19.2 @@ -8671,6 +8895,8 @@ snapshots: dependencies: semver: 7.7.2 + marked@12.0.2: {} + math-intrinsics@1.1.0: {} mdast-util-to-hast@13.2.0: @@ -9508,6 +9734,12 @@ snapshots: shebang-regex@3.0.0: {} + shieldwall@0.4.0(@solidjs/start@packages+start): + dependencies: + '@solidjs/start': link:packages/start + csp-header: 5.2.1 + h3: 1.15.4 + shiki@1.29.2: dependencies: '@shikijs/core': 1.29.2 @@ -10051,6 +10283,21 @@ snapshots: picomatch: 4.0.3 webpack-virtual-modules: 0.6.2 + unstorage@1.10.2(ioredis@5.7.0): + dependencies: + anymatch: 3.1.3 + chokidar: 3.6.0 + destr: 2.0.5 + h3: 1.15.4 + listhen: 1.9.0 + lru-cache: 10.4.3 + mri: 1.2.0 + node-fetch-native: 1.6.7 + ofetch: 1.4.1 + ufo: 1.6.1 + optionalDependencies: + ioredis: 5.7.0 + unstorage@1.17.1(db0@0.3.2)(ioredis@5.7.0): dependencies: anymatch: 3.1.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index a52b018da..0202392bf 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,11 +1,12 @@ packages: - packages/* - apps/* - - '!**/.tmp/**' + - apps/fixtures/* + - "!**/.tmp/**" ignoredBuiltDependencies: - - '@prisma/client' - - '@prisma/engines' + - "@prisma/client" + - "@prisma/engines" - better-sqlite3 - msw - prisma