fix(start): align request handler types with registered server context#7428
fix(start): align request handler types with registered server context#7428SeanCassiere wants to merge 9 commits into
Conversation
…t-start Add a Start-owned Register interface that extends router-core's Register, then export and consume it from `start-client-core`. This lets declarations against `@tanstack/react-start`, including `server.requestContext`, flow into Start-specific type consumers instead of requiring users to augment `@tanstack/router-core` or `@tanstack/react-router` directly. Router-core Register augmentations remain compatible because Start's Register extends the core Register.
Import Register from `@tanstack/start-client-core` in `createStartHandler` instead of `@tanstack/router-core`. This makes the server handler default to Start's augmentation surface, allowing `@tanstack/react-start` Register declarations such as `server.requestContext` to flow into RequestHandler typing.
…rt's Register type
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAligns request handler types across Start packages by introducing RequestHandlerParameters, constraining generics to Register, switching framework server entries to explicit fetch signatures, updating core utilities, and exercising typed requestContext in E2E servers and documentation. ChangesRequest Handler Type Alignment
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
View your CI Pipeline Execution ↗ for commit 714d5e2
☁️ Nx Cloud last updated this comment at |
🚀 Changeset Version Preview5 package(s) bumped directly, 9 bumped as dependents. 🟩 Patch bumps
|
Bundle Size Benchmarks
Current gzip tracks all emitted client JS chunks. Initial gzip tracks only the entry/import graph. Trend sparkline is historical current gzip ending with this PR measurement; lower is better. |
Merging this PR will not alter performance
Comparing Footnotes
|
Use the shared router-core Register for Start request handler types while preserving the documented custom server entry shape. Request options now derive required context from Register.server.requestContext, but framework ServerEntry wrappers still accept the universal fetch handler form with optional request options.
89310e0 to
d6b0635
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/start-server-core/src/createStartHandler.ts`:
- Around line 325-327: startRequestResolver is declared as
RequestHandler<Register> with requestOpts?: RequestOptions<Register>, but the
code later casts it to RequestHandler<TRegister>, which hides
RequestOptions<TRegister> constraints such as required server.requestContext;
instead make startRequestResolver generic over TRegister (or accept
RequestOptions<TRegister>) within createStartHandler so its signature matches
RequestHandler<TRegister> without casting—update the function/type parameters
(startRequestResolver, RequestOptions, RequestHandler, createStartHandler,
TRegister, Register) so the resolver uses the generic TRegister request option
types (including requestContext when present) and remove the unsafe cast.
In `@packages/start-server-core/src/request-response.ts`:
- Around line 125-128: The wrapper currently declares requestOpts?: any which
bypasses the RequestHandler<TRegister> contract and then re-establishes types
via a cast; change the wrapper signature to accept the strongly-typed options
instead of any so the wrapper itself matches RequestHandler<TRegister> (i.e.,
replace requestOpts?: any with the correct generic opts type for TRegister and
adjust the wrapper return type to Promise<Response> | Response as needed),
remove the subsequent as RequestHandler<TRegister> cast, and ensure the internal
call sites use the typed opts variable (references: the wrapper function taking
(request: Request, requestOpts), the RequestHandler<TRegister> type and the
TRegister generic).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7286b653-e8ba-4a37-ae1f-5478603a1e1f
📒 Files selected for processing (8)
.changeset/thick-sloths-write.mdpackages/react-start/src/default-entry/server.tspackages/solid-start/src/default-entry/server.tspackages/start-server-core/src/createStartHandler.tspackages/start-server-core/src/index.tsxpackages/start-server-core/src/request-handler.tspackages/start-server-core/src/request-response.tspackages/vue-start/src/default-entry/server.ts
✅ Files skipped from review due to trivial changes (2)
- packages/start-server-core/src/index.tsx
- .changeset/thick-sloths-write.md
| const startRequestResolver: RequestHandler<Register> = async ( | ||
| request, | ||
| requestOpts, | ||
| request: Request, | ||
| requestOpts?: RequestOptions<Register>, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify generic erasure/cast pattern in this file
rg -n -C2 'RequestHandler<Register>|RequestOptions<Register>|as RequestHandler<TRegister>' packages/start-server-core/src/createStartHandler.tsRepository: TanStack/router
Length of output: 398
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/start-server-core/src/createStartHandler.ts"
echo "== Context around startRequestResolver (lines ~280-360) =="
nl -ba "$FILE" | sed -n '280,360p'
echo "== Context around return requestHandler cast (lines ~540-620) =="
nl -ba "$FILE" | sed -n '540,620p'
echo "== Find TRegister/Register/RequestOptions usage in file =="
rg -n "TRegister|Register|RequestOptions<|requestHandler<|requestHandler\(" "$FILE"
echo "== Find type of requestHandler helper usage (look for its generic signature or import) =="
rg -n "function requestHandler|const requestHandler|requestHandler\s*<" "$FILE"Repository: TanStack/router
Length of output: 179
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/start-server-core/src/createStartHandler.ts"
echo "== Context around startRequestResolver (rough lines ~280-360) =="
cat -n "$FILE" | sed -n '280,360p'
echo "== Context around return requestHandler cast (rough lines ~540-620) =="
cat -n "$FILE" | sed -n '540,620p'
echo "== Search TRegister/Register/RequestOptions/requestHandler in file =="
rg -n "TRegister|Register|RequestOptions<|requestHandler<|requestHandler\s*\(" "$FILE"
echo "== Search for definition/signature of requestHandler helper (generic signature) =="
rg -n "function requestHandler|const requestHandler|requestHandler\s*<" "$FILE"Repository: TanStack/router
Length of output: 7191
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/start-server-core/src/createStartHandler.ts"
echo "== Top of file (imports/types) =="
cat -n "$FILE" | sed -n '1,140p'
echo "== Search for RequestOptions type/import usage =="
rg -n "RequestOptions" "$FILE"
echo "== Search for RequestHandlerParameters or requestHandler helper import/identifier =="
rg -n "RequestHandlerParameters|requestHandler\b" "$FILE"Repository: TanStack/router
Length of output: 5904
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate likely files
rg -n "export type RequestHandler|export interface RequestHandler|type RequestHandler" packages/start-server-core/src -S
rg -n "export type RequestOptions|interface RequestOptions|type RequestOptions" packages/start-server-core/src -S
rg -n "export function requestHandler|const requestHandler|function requestHandler" packages/start-server-core/src -S
# Print the relevant files (if found); fall back to targeted cat by resolving known paths
echo "== Try cat request-handler.ts =="
ls -1 packages/start-server-core/src | rg "request-handler"
if [ -f packages/start-server-core/src/request-handler.ts ]; then
cat -n packages/start-server-core/src/request-handler.ts | sed -n '1,220p'
fi
echo "== Try cat request-response.ts =="
ls -1 packages/start-server-core/src | rg "request-response"
if [ -f packages/start-server-core/src/request-response.ts ]; then
cat -n packages/start-server-core/src/request-response.ts | sed -n '1,260p'
fi
# If filenames differ, show a short list of matches
echo "== Show matches for request-handler import path =="
rg -n "from '\\./request-handler'|from \"\\./request-handler\"" packages/start-server-core/src/createStartHandler.tsRepository: TanStack/router
Length of output: 13081
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find the definition of Register in start-client-core (in-repo)
rg -n "export (type|interface) Register" packages/start-client-core -S
rg -n "type Register|interface Register" packages/start-client-core -S
# Also inspect server.requestContext typing if nearby
rg -n "requestContext" packages/start-client-core -SRepository: TanStack/router
Length of output: 41
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Search repo for Register definition =="
rg -n "export (type|interface) Register|type Register\s*=" . -S
echo "== Search repo for requestContext typing =="
rg -n "requestContext" . -SRepository: TanStack/router
Length of output: 1991
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect Register type
FILE1="packages/router-core/src/router.ts"
echo "== router-core Register (around its definition) =="
rg -n "export interface Register|export type Register" "$FILE1"
start=$(rg -n "export interface Register" "$FILE1" | head -n1 | cut -d: -f1)
end=$((start+60))
sed -n "${start},${end}p" "$FILE1" | cat -n
# Inspect createStartHandler around executeMiddleware and requestOpts?.context usage
FILE2="packages/start-server-core/src/createStartHandler.ts"
echo "== createStartHandler executeMiddleware context usage (around ~510-590) =="
cat -n "$FILE2" | sed -n '500,590p'Repository: TanStack/router
Length of output: 6008
Avoid casting RequestHandler<Register> to RequestHandler<TRegister> in createStartHandler
startRequestResolver is typed as RequestHandler<Register> with requestOpts?: RequestOptions<Register> (325-327), but RequestOptions<TRegister> can require context when TRegister defines server.requestContext. Casting the result to RequestHandler<TRegister> (588) masks that constraint from callers.
Proposed type-safe fix
- const startRequestResolver: RequestHandler<Register> = async (
- request: Request,
- requestOpts?: RequestOptions<Register>,
- ) => {
+ const startRequestResolver: RequestHandler<TRegister> = async (
+ ...args: RequestHandlerParameters<TRegister>
+ ) => {
+ const [request, requestOpts] = args
let router: AnyRouter | null = null as AnyRouter | null
let cbWillCleanup = false as boolean
@@
- return requestHandler(startRequestResolver) as RequestHandler<TRegister>
+ return requestHandler<TRegister>(startRequestResolver)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/start-server-core/src/createStartHandler.ts` around lines 325 - 327,
startRequestResolver is declared as RequestHandler<Register> with requestOpts?:
RequestOptions<Register>, but the code later casts it to
RequestHandler<TRegister>, which hides RequestOptions<TRegister> constraints
such as required server.requestContext; instead make startRequestResolver
generic over TRegister (or accept RequestOptions<TRegister>) within
createStartHandler so its signature matches RequestHandler<TRegister> without
casting—update the function/type parameters (startRequestResolver,
RequestOptions, RequestHandler, createStartHandler, TRegister, Register) so the
resolver uses the generic TRegister request option types (including
requestContext when present) and remove the unsafe cast.
| return (( | ||
| request: Request, | ||
| requestOpts?: any, | ||
| ): Promise<Response> | Response => { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify unsafe typing in wrapper signatures
rg -n -C2 'requestOpts\?: any|as RequestHandler<|RequestHandlerParameters<' packages/start-server-core/src/request-response.tsRepository: TanStack/router
Length of output: 342
requestOpts?: any bypasses the handler type guarantees (and is masked by a cast).
In packages/start-server-core/src/request-response.ts, the wrapper takes requestOpts?: any (line 127) and then relies on as RequestHandler<TRegister> (line 146) to restore typing. This defeats strict type safety for the TRegister-specific opts contract. Replace any with the correct typed parameters for TRegister and remove/avoid the cast by aligning the wrapper’s parameter/return types with RequestHandler<TRegister] directly.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/start-server-core/src/request-response.ts` around lines 125 - 128,
The wrapper currently declares requestOpts?: any which bypasses the
RequestHandler<TRegister> contract and then re-establishes types via a cast;
change the wrapper signature to accept the strongly-typed options instead of any
so the wrapper itself matches RequestHandler<TRegister> (i.e., replace
requestOpts?: any with the correct generic opts type for TRegister and adjust
the wrapper return type to Promise<Response> | Response as needed), remove the
subsequent as RequestHandler<TRegister> cast, and ensure the internal call sites
use the typed opts variable (references: the wrapper function taking (request:
Request, requestOpts), the RequestHandler<TRegister> type and the TRegister
generic).
There was a problem hiding this comment.
Important
At least one additional CI pipeline execution has run since the conclusion below was written and it may no longer be applicable.
Nx Cloud is proposing a fix for your failed CI:
We updated type-only-protected-import.tsx to remove the Record<string, never> generic argument from RequestHandler<Record<string, never>>, which no longer satisfies the new TRegister extends Register constraint introduced by this PR. Using RequestHandler without a type argument falls back to the default Register type, which both satisfies the constraint and preserves the test's original intent of verifying that type-only imports of server types are safe from the import-protection plugin.
Tip
✅ We verified this fix by re-running tanstack-react-start-e2e-import-protection:build, tanstack-react-start-e2e-import-protection:build:vite:ssr.
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 8f208026..d4a17a17 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
@@ -3,7 +3,7 @@ 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
}
const status: TypeOnlyStatus = {
Or Apply changes locally with:
npx nx-cloud apply-locally 5NcI-t5LF
Apply fix locally with your editor ↗ View interactive diff ↗
🎓 Learn more about Self-Healing CI on nx.dev
In reference to #7399
Summary by CodeRabbit
Bug Fixes
Documentation
Tests