diff --git a/e2e/solid-start/basic-cloudflare/src/routes/index.tsx b/e2e/solid-start/basic-cloudflare/src/routes/index.tsx index 76e285a2baf..d3f3d7291b6 100644 --- a/e2e/solid-start/basic-cloudflare/src/routes/index.tsx +++ b/e2e/solid-start/basic-cloudflare/src/routes/index.tsx @@ -1,5 +1,6 @@ import { createFileRoute } from '@tanstack/solid-router' import { createServerFn } from '@tanstack/solid-start' +import { getRequestHeader } from '@tanstack/solid-start/server' import { env } from 'cloudflare:workers' export const Route = createFileRoute('/')({ @@ -11,6 +12,7 @@ const getData = createServerFn().handler(() => { return { message: `Running in ${navigator.userAgent}`, myVar: env.MY_VAR, + requestHeader: getRequestHeader('x-solid-start-context'), } }) @@ -22,6 +24,7 @@ function Home() {
{data().message}
{data().myVar}
+{data().requestHeader}
) } diff --git a/e2e/solid-start/basic-cloudflare/tests/app.spec.ts b/e2e/solid-start/basic-cloudflare/tests/app.spec.ts index add28cb73ee..6160dabe11a 100644 --- a/e2e/solid-start/basic-cloudflare/tests/app.spec.ts +++ b/e2e/solid-start/basic-cloudflare/tests/app.spec.ts @@ -17,6 +17,16 @@ test('returns the correct value from a Cloudflare binding', async ({ await expect(page.getByTestId('myVar')).toHaveText('Hello from Cloudflare') }) +test('preserves request context with nodejs_als', async ({ page }) => { + await page.setExtraHTTPHeaders({ + 'x-solid-start-context': 'request-context-works', + }) + await page.goto('/') + await expect(page.getByTestId('requestHeader')).toHaveText( + 'request-context-works', + ) +}) + test('prerender with Cloudflare Workers runtime', async ({ page }) => { // Verify the static page was prerendered during build const distDir = join(process.cwd(), 'dist', 'client') diff --git a/e2e/solid-start/basic-cloudflare/worker-configuration.d.ts b/e2e/solid-start/basic-cloudflare/worker-configuration.d.ts index f7a8f163972..be4bba60c37 100644 --- a/e2e/solid-start/basic-cloudflare/worker-configuration.d.ts +++ b/e2e/solid-start/basic-cloudflare/worker-configuration.d.ts @@ -1,18 +1,12 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: b11df627d8b3c51b1bf3230a546b0f20) -// Runtime types generated with workerd@1.20260317.1 2025-09-24 nodejs_compat +// Generated by Wrangler by running `wrangler types` (hash: 24a3fe972e2c4aaa57bb30eaba75ee7a) +// Runtime types generated with workerd@1.20260317.1 2025-09-24 nodejs_als declare namespace Cloudflare { interface Env { MY_VAR: "Hello from Cloudflare"; } } interface Env extends Cloudflare.Env {} -type StringifyValuesx
') + await new Promise((resolve) => setImmediate(resolve)) + + output.destroy(new Error('client gone')) + await new Promise((resolve) => setImmediate(resolve)) + await Promise.resolve() + + expect(aborts).toEqual(1) + expect(input.destroyed).toEqual(true) + expect(cleanup).toHaveBeenCalledOnce() + } finally { + if (!input.destroyed) { + input.destroy() + } + router.serverSsr?.cleanup() + } + }) }) diff --git a/packages/router-core/src/ssr/server.ts b/packages/router-core/src/ssr/server.ts index 93973999cbb..8a6fa73ed36 100644 --- a/packages/router-core/src/ssr/server.ts +++ b/packages/router-core/src/ssr/server.ts @@ -17,7 +17,6 @@ export type { SsrResponse, } from './handlerCallback' export { - transformPipeableStreamWithRouter, transformStreamWithRouter, transformReadableStreamWithRouter, } from './transformStreamWithRouter' diff --git a/packages/router-core/src/ssr/transformStreamWithRouter.ts b/packages/router-core/src/ssr/transformStreamWithRouter.ts index 03c6ecf2e13..bbdac533caf 100644 --- a/packages/router-core/src/ssr/transformStreamWithRouter.ts +++ b/packages/router-core/src/ssr/transformStreamWithRouter.ts @@ -1,5 +1,3 @@ -import { ReadableStream } from 'node:stream/web' -import { Readable } from 'node:stream' import { TSR_SCRIPT_BARRIER_ID } from './constants' import type { AnyRouter } from '../router' @@ -28,16 +26,6 @@ export function transformReadableStreamWithRouter( return transformStreamWithRouter(router, routerStream, opts) } -export function transformPipeableStreamWithRouter( - router: AnyRouter, - routerStream: Readable, - opts?: TransformStreamWithRouterOptions, -) { - return Readable.fromWeb( - transformStreamWithRouter(router, Readable.toWeb(routerStream), opts), - ) -} - // Minimum length of a valid closing tag: = 4 characters const MIN_CLOSING_TAG_LENGTH = 4 diff --git a/packages/router-core/tests/transformStreamBackpressure.perf.test.ts b/packages/router-core/tests/transformStreamBackpressure.perf.test.ts index 267241f4164..f3e92c596f1 100644 --- a/packages/router-core/tests/transformStreamBackpressure.perf.test.ts +++ b/packages/router-core/tests/transformStreamBackpressure.perf.test.ts @@ -8,7 +8,6 @@ // transformStreamWithRouter calls controller.enqueue unconditionally, // allowing controller.[[queue]] to accumulate Uint8Array chunks (external // memory) when the consumer drains slower than the producer. -import { ReadableStream } from 'node:stream/web' import { describe, expect, it } from 'vitest' import { transformStreamWithRouter } from '../src/ssr/transformStreamWithRouter' diff --git a/packages/router-core/tests/transformStreamWithRouter.test.ts b/packages/router-core/tests/transformStreamWithRouter.test.ts index 903efeb0c95..fbc4f9493b1 100644 --- a/packages/router-core/tests/transformStreamWithRouter.test.ts +++ b/packages/router-core/tests/transformStreamWithRouter.test.ts @@ -4,8 +4,6 @@ // (TanStack/router#7402) without relying on GC, timing of real I/O, or // process.memoryUsage(). On-demand backpressure/external-memory // assertions live in transformStreamBackpressure.perf.test.ts. -import { ReadableStream } from 'node:stream/web' -import { PassThrough } from 'node:stream' import { describe, expect, test, vi } from 'vitest' import { createMemoryHistory } from '@tanstack/history' import { BaseRootRoute, BaseRoute } from '../src' @@ -13,7 +11,6 @@ import { GLOBAL_TSR, TSR_SCRIPT_BARRIER_ID } from '../src/ssr/constants' import { createSsrStreamResponse } from '../src/ssr/handlerCallback' import { attachRouterServerSsrUtils } from '../src/ssr/ssr-server' import { - transformPipeableStreamWithRouter, transformReadableStreamWithRouter, transformStreamWithRouter, } from '../src/ssr/transformStreamWithRouter' @@ -1027,35 +1024,6 @@ describe('transformStreamWithRouter — injected HTML ordering', () => { expect(aborts).toBe(0) }) - test('onAbort: fires when pipeable wrapper consumer destroys', async () => { - const { router, finishSerialization } = makeRouter() - finishSerialization() - - const pass = new PassThrough() - let aborts = 0 - const out = transformPipeableStreamWithRouter(router as any, pass, { - onAbort: () => aborts++, - }) - // Swallow expected error emission from destroy(). - out.on('error', () => {}) - - // Push something so the read loop is engaged. - pass.write('x
') - // Wait a tick to let reader start. - await new Promise((r) => setImmediate(r)) - - // Destroy downstream — simulates Node response being closed by client. - out.destroy(new Error('client gone')) - // Allow microtasks + readable webstream cancel propagation. - await new Promise((r) => setImmediate(r)) - await Promise.resolve() - - expect(aborts).toBe(1) - - // Cleanup: destroy upstream so we don't leak. - if (!pass.destroyed) pass.destroy() - }) - test('onAbort: lifetime timeout triggers abort exactly once', async () => { vi.useFakeTimers() const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) diff --git a/packages/solid-router/src/ssr/renderRouterToStream.tsx b/packages/solid-router/src/ssr/renderRouterToStream.tsx index 7432f195105..55d8747fd68 100644 --- a/packages/solid-router/src/ssr/renderRouterToStream.tsx +++ b/packages/solid-router/src/ssr/renderRouterToStream.tsx @@ -6,7 +6,6 @@ import { } from '@tanstack/router-core/ssr/server' import { makeSsrSerovalPlugin } from '@tanstack/router-core' import type { JSXElement } from 'solid-js' -import type { ReadableStream } from 'node:stream/web' import type { AnyRouter } from '@tanstack/router-core' const noop = () => {} diff --git a/packages/start-server-core/src/index.tsx b/packages/start-server-core/src/index.tsx index ee818e0cf1f..cc0a44dad56 100644 --- a/packages/start-server-core/src/index.tsx +++ b/packages/start-server-core/src/index.tsx @@ -18,7 +18,6 @@ export { createRequestHandler, defineHandlerCallback, transformReadableStreamWithRouter, - transformPipeableStreamWithRouter, } from '@tanstack/router-core/ssr/server' export type { HandlerCallback } from '@tanstack/router-core/ssr/server' diff --git a/packages/vue-router/src/ssr/renderRouterToStream.tsx b/packages/vue-router/src/ssr/renderRouterToStream.tsx index d352c524972..c7413a7c98b 100644 --- a/packages/vue-router/src/ssr/renderRouterToStream.tsx +++ b/packages/vue-router/src/ssr/renderRouterToStream.tsx @@ -1,4 +1,3 @@ -import { ReadableStream as NodeReadableStream } from 'node:stream/web' import * as Vue from 'vue' import { pipeToWebWritable, renderToString } from 'vue/server-renderer' import { isbot } from 'isbot' @@ -15,8 +14,8 @@ const isAbortError = (request: Request, error: unknown) => (error as any)?.code === 'ABORT_ERR' function prependDoctype( - readable: globalThis.ReadableStream, -): NodeReadableStream