Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/thick-sloths-write.md
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions docs/start/framework/react/guide/server-entry-point.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -74,7 +74,7 @@ type MyRequestContext = {
foo: number
}

declare module '@tanstack/react-router' {
declare module '@tanstack/react-start' {
interface Register {
server: {
requestContext: MyRequestContext
Expand Down
6 changes: 1 addition & 5 deletions docs/start/framework/solid/guide/server-entry-point.md
Original file line number Diff line number Diff line change
@@ -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' }
---
15 changes: 14 additions & 1 deletion e2e/react-start/basic/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' } })
},
}
12 changes: 12 additions & 0 deletions e2e/react-start/basic/src/start.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createMiddleware, createStart } from '@tanstack/react-start'

const globalMiddleware = createMiddleware().server(
async ({ next, context }) => {
console.debug('[server-entry]: global middleware', context)
return next()
},
)

export const startInstance = createStart(() => ({
requestMiddleware: [globalMiddleware],
}))
Original file line number Diff line number Diff line change
@@ -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<Record<string, never>>
requestHandler?: RequestHandler<Register>
}

const status: TypeOnlyStatus = {
Expand Down
15 changes: 14 additions & 1 deletion e2e/solid-start/basic/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' } })
},
}
15 changes: 14 additions & 1 deletion e2e/vue-start/basic/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' } })
},
}
16 changes: 10 additions & 6 deletions packages/react-start/src/default-entry/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Register> }
export type ServerEntry = {
fetch: (
request: Request,
opts?: RequestOptions<Register>,
) => Promise<Response> | 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)
},
}
}
Expand Down
16 changes: 10 additions & 6 deletions packages/solid-start/src/default-entry/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Register> }
export type ServerEntry = {
fetch: (
request: Request,
opts?: RequestOptions<Register>,
) => Promise<Response> | 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)
},
}
}
Expand Down
12 changes: 6 additions & 6 deletions packages/start-server-core/src/createStartHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,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,
Expand Down Expand Up @@ -374,7 +374,7 @@ function handlerToMiddleware(
* })
* ```
*/
export function createStartHandler<TRegister = Register>(
export function createStartHandler<TRegister extends Register = Register>(
cbOrOptions: HandlerCallback<AnyRouter> | CreateStartHandlerOptions,
): RequestHandler<TRegister> {
const handlerOptions: FinalManifestOptions =
Expand All @@ -397,8 +397,8 @@ export function createStartHandler<TRegister = Register>(
}

const startRequestResolver: RequestHandler<Register> = async (
request,
requestOpts,
request: Request,
requestOpts?: RequestOptions<Register>,
) => {
let router: AnyRouter | null = null as AnyRouter | null
let responseOwnsCleanup = false as boolean
Expand Down Expand Up @@ -673,7 +673,7 @@ export function createStartHandler<TRegister = Register>(
}
}

return requestHandler(startRequestResolver)
return requestHandler(startRequestResolver) as RequestHandler<TRegister>
}

async function handleRedirectResponse(
Expand Down
26 changes: 8 additions & 18 deletions packages/start-server-core/src/request-handler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { OnEarlyHints, ResponseLinkHeaderOptions } from './early-hints'
import type { Register } from '@tanstack/start-client-core'

type BaseContext = {
nonce?: string
Expand Down Expand Up @@ -67,22 +68,11 @@ export type RequestOptions<TRegister> = EarlyHintsOptions &
: { context: TRequestContext & BaseContext }
: { context?: BaseContext })

// Utility type: true if T has any required keys, else false
type HasRequired<T> = keyof T extends never
? false
: {
[K in keyof T]-?: undefined extends T[K] ? never : K
}[keyof T] extends never
? false
: true
export type RequestHandlerParameters<TRegister> =
{} extends RequestOptions<TRegister>
? [request: Request, opts?: RequestOptions<TRegister>]
: [request: Request, opts: RequestOptions<TRegister>]

export type RequestHandler<TRegister> =
HasRequired<RequestOptions<TRegister>> extends true
? (
request: Request,
opts: RequestOptions<TRegister>,
) => Promise<Response> | Response
: (
request: Request,
opts?: RequestOptions<TRegister>,
) => Promise<Response> | Response
export type RequestHandler<TRegister extends Register = Register> = (
...args: RequestHandlerParameters<TRegister>
) => Promise<Response> | Response
10 changes: 7 additions & 3 deletions packages/start-server-core/src/request-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -118,10 +119,13 @@ function attachResponseHeaders<T>(
return value
}

export function requestHandler<TRegister = unknown>(
export function requestHandler<TRegister extends Register = Register>(
handler: RequestHandler<TRegister>,
) {
return (request: Request, requestOpts: any): Promise<Response> | Response => {
return ((
request: Request,
requestOpts?: any,
): Promise<Response> | Response => {
let h3Event: H3Event
try {
h3Event = new H3Event(request)
Expand All @@ -139,7 +143,7 @@ export function requestHandler<TRegister = unknown>(
handler(request, requestOpts),
)
return h3_toResponse(attachResponseHeaders(response, h3Event), h3Event)
}
}) as RequestHandler<TRegister>
}

function getH3Event() {
Expand Down
16 changes: 10 additions & 6 deletions packages/vue-start/src/default-entry/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Register> }
export type ServerEntry = {
fetch: (
request: Request,
opts?: RequestOptions<Register>,
) => Promise<Response> | 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)
},
}
}
Expand Down
Loading