diff --git a/.changeset/thick-sloths-write.md b/.changeset/thick-sloths-write.md new file mode 100644 index 0000000000..de7726c728 --- /dev/null +++ b/.changeset/thick-sloths-write.md @@ -0,0 +1,9 @@ +--- +'@tanstack/start-client-core': patch +'@tanstack/start-server-core': patch +'@tanstack/react-start': patch +'@tanstack/solid-start': patch +'@tanstack/vue-start': patch +--- + +fix(start): align request handler types with registered server context diff --git a/docs/start/framework/react/guide/server-entry-point.md b/docs/start/framework/react/guide/server-entry-point.md index 5acc8ee550..90d6b2270c 100644 --- a/docs/start/framework/react/guide/server-entry-point.md +++ b/docs/start/framework/react/guide/server-entry-point.md @@ -64,7 +64,7 @@ export default createServerEntry({ When your server needs to pass additional, typed data into request handlers (for example, authenticated user info, a database connection, or per-request flags), register a request context type via TypeScript module augmentation. The registered context is delivered as the second argument to the server `fetch` handler and is available throughout the server-side middleware chain — including global middleware, request/function middleware, server routes, server functions, and the router itself. -To add types for your request context, augment the `Register` interface from `@tanstack/react-router` with a `server.requestContext` property. The runtime `context` you pass to `handler.fetch` will then match that type. Example: +To add types for your request context, augment the `Register` interface from `@tanstack/react-start` with a `server.requestContext` property. The runtime `context` you pass to `handler.fetch` will then match that type. Example: ```tsx import handler, { createServerEntry } from '@tanstack/react-start/server-entry' @@ -74,7 +74,7 @@ type MyRequestContext = { foo: number } -declare module '@tanstack/react-router' { +declare module '@tanstack/react-start' { interface Register { server: { requestContext: MyRequestContext diff --git a/docs/start/framework/solid/guide/server-entry-point.md b/docs/start/framework/solid/guide/server-entry-point.md index 80c226eaae..ef261fe9b6 100644 --- a/docs/start/framework/solid/guide/server-entry-point.md +++ b/docs/start/framework/solid/guide/server-entry-point.md @@ -1,9 +1,5 @@ --- ref: docs/start/framework/react/guide/server-entry-point.md replace: - { - '@tanstack/react-start': '@tanstack/solid-start', - '@tanstack/react-router': '@tanstack/solid-router', - 'React': 'SolidJS', - } + { '@tanstack/react-start': '@tanstack/solid-start', 'React': 'SolidJS' } --- diff --git a/e2e/react-start/basic/src/server.ts b/e2e/react-start/basic/src/server.ts index 00d13b4d17..c89ad5e942 100644 --- a/e2e/react-start/basic/src/server.ts +++ b/e2e/react-start/basic/src/server.ts @@ -4,8 +4,21 @@ import handler from '@tanstack/react-start/server-entry' console.log("[server-entry]: using custom server entry in 'src/server.ts'") +declare module '@tanstack/react-start' { + interface Register { + server: { + /** + * This is just a test to make sure that the typing of the request context is working correctly in the custom server entry. + */ + requestContext: { + foo: string + } + } + } +} + export default { fetch(request: Request) { - return handler.fetch(request) + return handler.fetch(request, { context: { foo: 'bar' } }) }, } diff --git a/e2e/react-start/import-protection/src/routes/type-only-protected-import.tsx b/e2e/react-start/import-protection/src/routes/type-only-protected-import.tsx index 8f20802637..75c4b38945 100644 --- a/e2e/react-start/import-protection/src/routes/type-only-protected-import.tsx +++ b/e2e/react-start/import-protection/src/routes/type-only-protected-import.tsx @@ -1,9 +1,10 @@ import { createFileRoute } from '@tanstack/react-router' -import { type RequestHandler } from '@tanstack/react-start/server' +import type { Register } from '@tanstack/react-start' +import type { RequestHandler } from '@tanstack/react-start/server' import type { TypeOnlySecret } from '../violations/type-only.server' type TypeOnlyStatus = TypeOnlySecret & { - requestHandler?: RequestHandler> + requestHandler?: RequestHandler } const status: TypeOnlyStatus = { diff --git a/e2e/solid-start/basic/src/server.ts b/e2e/solid-start/basic/src/server.ts index d48e6df349..a6095893b6 100644 --- a/e2e/solid-start/basic/src/server.ts +++ b/e2e/solid-start/basic/src/server.ts @@ -4,8 +4,21 @@ import handler from '@tanstack/solid-start/server-entry' console.log("[server-entry]: using custom server entry in 'src/server.ts'") +declare module '@tanstack/solid-start' { + interface Register { + server: { + /** + * This is just a test to make sure that the typing of the request context is working correctly in the custom server entry. + */ + requestContext: { + foo: string + } + } + } +} + export default { fetch(request: Request) { - return handler.fetch(request) + return handler.fetch(request, { context: { foo: 'bar' } }) }, } diff --git a/e2e/vue-start/basic/src/server.ts b/e2e/vue-start/basic/src/server.ts index 8195b0ae95..249f259356 100644 --- a/e2e/vue-start/basic/src/server.ts +++ b/e2e/vue-start/basic/src/server.ts @@ -4,8 +4,21 @@ import handler from '@tanstack/vue-start/server-entry' console.log("[server-entry]: using custom server entry in 'src/server.ts'") +declare module '@tanstack/vue-start' { + interface Register { + server: { + /** + * This is just a test to make sure that the typing of the request context is working correctly in the custom server entry. + */ + requestContext: { + foo: string + } + } + } +} + export default { fetch(request: Request) { - return handler.fetch(request) + return handler.fetch(request, { context: { foo: 'bar' } }) }, } diff --git a/packages/react-start/src/default-entry/server.ts b/packages/react-start/src/default-entry/server.ts index 8e734a7e49..155194f9e9 100644 --- a/packages/react-start/src/default-entry/server.ts +++ b/packages/react-start/src/default-entry/server.ts @@ -2,18 +2,22 @@ import { createStartHandler, defaultStreamHandler, } from '@tanstack/react-start/server' -import type { Register } from '@tanstack/react-router' -import type { RequestHandler } from '@tanstack/react-start/server' +import type { Register } from '@tanstack/react-start' +import type { RequestOptions } from '@tanstack/react-start/server' const fetch = createStartHandler(defaultStreamHandler) -// Providing `RequestHandler` from `@tanstack/react-start/server` is required so that the output types don't import it from `@tanstack/start-server-core` -export type ServerEntry = { fetch: RequestHandler } +export type ServerEntry = { + fetch: ( + request: Request, + opts?: RequestOptions, + ) => Promise | Response +} export function createServerEntry(entry: ServerEntry): ServerEntry { return { - async fetch(...args) { - return await entry.fetch(...args) + async fetch(request, opts) { + return await entry.fetch(request, opts) }, } } diff --git a/packages/solid-start/src/default-entry/server.ts b/packages/solid-start/src/default-entry/server.ts index b8f3105c6a..aefdc14702 100644 --- a/packages/solid-start/src/default-entry/server.ts +++ b/packages/solid-start/src/default-entry/server.ts @@ -2,18 +2,22 @@ import { createStartHandler, defaultStreamHandler, } from '@tanstack/solid-start/server' -import type { Register } from '@tanstack/solid-router' -import type { RequestHandler } from '@tanstack/solid-start/server' +import type { Register } from '@tanstack/solid-start' +import type { RequestOptions } from '@tanstack/solid-start/server' const fetch = createStartHandler(defaultStreamHandler) -// Providing `RequestHandler` from `@tanstack/solid-start/server` is required so that the output types don't import it from `@tanstack/start-server-core` -export type ServerEntry = { fetch: RequestHandler } +export type ServerEntry = { + fetch: ( + request: Request, + opts?: RequestOptions, + ) => Promise | Response +} export function createServerEntry(entry: ServerEntry): ServerEntry { return { - async fetch(...args) { - return await entry.fetch(...args) + async fetch(request, opts) { + return await entry.fetch(request, opts) }, } } diff --git a/packages/start-server-core/src/createStartHandler.ts b/packages/start-server-core/src/createStartHandler.ts index 7d47388a3c..c3a934c1a7 100644 --- a/packages/start-server-core/src/createStartHandler.ts +++ b/packages/start-server-core/src/createStartHandler.ts @@ -36,17 +36,17 @@ import type { AnyFunctionMiddleware, AnyRequestMiddleware, AnyStartInstanceOptions, + Register, RouteMethod, RouteMethodHandlerFn, RouterEntry, StartEntry, } from '@tanstack/start-client-core' -import type { RequestHandler } from './request-handler' +import type { RequestHandler, RequestOptions } from './request-handler' import type { AnyRoute, AnyRouter, AnySerializationAdapter, - Register, } from '@tanstack/router-core' import type { HandlerCallback } from '@tanstack/router-core/ssr/server' import type { FinalManifestOptions } from './finalManifest' @@ -300,7 +300,7 @@ function handlerToMiddleware( * }) * ``` */ -export function createStartHandler( +export function createStartHandler( cbOrOptions: HandlerCallback | CreateStartHandlerOptions, ): RequestHandler { const handlerOptions: FinalManifestOptions = @@ -323,8 +323,8 @@ export function createStartHandler( } const startRequestResolver: RequestHandler = async ( - request, - requestOpts, + request: Request, + requestOpts?: RequestOptions, ) => { let router: AnyRouter | null = null as AnyRouter | null let cbWillCleanup = false as boolean @@ -585,7 +585,7 @@ export function createStartHandler( } } - return requestHandler(startRequestResolver) + return requestHandler(startRequestResolver) as RequestHandler } async function handleRedirectResponse( diff --git a/packages/start-server-core/src/request-handler.ts b/packages/start-server-core/src/request-handler.ts index e6729cd137..7479b2eeb4 100644 --- a/packages/start-server-core/src/request-handler.ts +++ b/packages/start-server-core/src/request-handler.ts @@ -1,4 +1,5 @@ import type { OnEarlyHints, ResponseLinkHeaderOptions } from './early-hints' +import type { Register } from '@tanstack/start-client-core' type BaseContext = { nonce?: string @@ -66,22 +67,11 @@ export type RequestOptions = EarlyHintsOptions & : { context: TRequestContext & BaseContext } : { context?: BaseContext }) -// Utility type: true if T has any required keys, else false -type HasRequired = keyof T extends never - ? false - : { - [K in keyof T]-?: undefined extends T[K] ? never : K - }[keyof T] extends never - ? false - : true +export type RequestHandlerParameters = + {} extends RequestOptions + ? [request: Request, opts?: RequestOptions] + : [request: Request, opts: RequestOptions] -export type RequestHandler = - HasRequired> extends true - ? ( - request: Request, - opts: RequestOptions, - ) => Promise | Response - : ( - request: Request, - opts?: RequestOptions, - ) => Promise | Response +export type RequestHandler = ( + ...args: RequestHandlerParameters +) => Promise | Response diff --git a/packages/start-server-core/src/request-response.ts b/packages/start-server-core/src/request-response.ts index e0ad8a336a..4f0b87d786 100644 --- a/packages/start-server-core/src/request-response.ts +++ b/packages/start-server-core/src/request-response.ts @@ -37,6 +37,7 @@ import type { SessionUpdate, } from './session' import type { StandardSchemaV1 } from '@standard-schema/spec' +import type { Register } from '@tanstack/start-client-core' import type { RequestHandler } from './request-handler' interface StartEvent { @@ -118,10 +119,13 @@ function attachResponseHeaders( return value } -export function requestHandler( +export function requestHandler( handler: RequestHandler, ) { - return (request: Request, requestOpts: any): Promise | Response => { + return (( + request: Request, + requestOpts?: any, + ): Promise | Response => { let h3Event: H3Event try { h3Event = new H3Event(request) @@ -139,7 +143,7 @@ export function requestHandler( handler(request, requestOpts), ) return h3_toResponse(attachResponseHeaders(response, h3Event), h3Event) - } + }) as RequestHandler } function getH3Event() { diff --git a/packages/vue-start/src/default-entry/server.ts b/packages/vue-start/src/default-entry/server.ts index 5729a469d3..6bc0b0c415 100644 --- a/packages/vue-start/src/default-entry/server.ts +++ b/packages/vue-start/src/default-entry/server.ts @@ -2,18 +2,22 @@ import { createStartHandler, defaultStreamHandler, } from '@tanstack/vue-start/server' -import type { Register } from '@tanstack/vue-router' -import type { RequestHandler } from '@tanstack/vue-start/server' +import type { Register } from '@tanstack/vue-start' +import type { RequestOptions } from '@tanstack/vue-start/server' const fetch = createStartHandler(defaultStreamHandler) -// Providing `RequestHandler` from `@tanstack/vue-start/server` is required so that the output types don't import it from `@tanstack/start-server-core` -export type ServerEntry = { fetch: RequestHandler } +export type ServerEntry = { + fetch: ( + request: Request, + opts?: RequestOptions, + ) => Promise | Response +} export function createServerEntry(entry: ServerEntry): ServerEntry { return { - async fetch(...args) { - return await entry.fetch(...args) + async fetch(request, opts) { + return await entry.fetch(request, opts) }, } }