-
-
Notifications
You must be signed in to change notification settings - Fork 385
🔧 Fix 200 status code type inference in ~Routes #1550
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughAdded a CheckExcessProps type and tightened several response-typing generics: SelectiveStatus signature updated to accept an explicit T generic; ErrorContext.status uses CheckExcessProps for excess-property checking; UnwrapSchema conditional reversed; type-only tests adjusted for 200-response inference. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (5 passed)
✨ Finishing touches
🧪 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 |
commit: |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
test/types/index.ts (1)
2876-2929: Strengthen the 200-status type tests and align with existing styleThe
_typeTest/_typeTest2conditional checks will still pass even if the inferred 200 response type is widened toany(e.g. via an index signature), so they don’t reliably guard against the regression you’re targeting. They also diverge from the rest of this file, which consistently usesexpectTypeOf.Consider asserting the concrete 200/422 payload types directly via
expectTypeOf, and optionally updating the comment to reference issue#1548instead of#200for clarity. For example:-// Status code 200 type inference (issue #200) +// Status code 200 type inference (issue #1548) { const app = new Elysia().get( '/', () => ({ message: 'Hello Elysia' as const }), { response: { message: t.Literal('Hello Elysia') }) } } ) - type AppResponse = (typeof app)['~Routes']['get']['response'] - - // Should properly infer the 200 response type, not [x: string]: any - const _typeTest: AppResponse extends { - 200: { message: 'Hello Elysia' } - } - ? true - : false = true + type AppResponse = (typeof app)['~Routes']['get']['response'] + + // Ensure 200 response is the precise payload type, not widened (`any`) + expectTypeOf<AppResponse[200]>().toEqualTypeOf<{ + message: 'Hello Elysia' + }>() // Test with multiple status codes including 200 const app2 = new Elysia().post( @@ -2901,15 +2899,17 @@ } } ) type App2Response = (typeof app2)['~Routes']['test']['post']['response'] - const _typeTest2: App2Response extends { - 200: { message: 'Hello Elysia' } - 422: { error: string } - } - ? true - : false = true + expectTypeOf<App2Response[200]>().toEqualTypeOf<{ + message: 'Hello Elysia' + }>() + + expectTypeOf<App2Response[422]>().toEqualTypeOf<{ + error: string + }>() }This way the tests will fail if 200/422 ever regress to
anyagain, and they stay consistent with the rest of the type suite.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/error.ts(1 hunks)test/types/index.ts(1 hunks)
🔇 Additional comments (1)
src/error.ts (1)
45-55: SelectiveStatus generic reintroduction looks correct; consider factoring the constraintReintroducing a separate
Tgeneric tied toResvia theCode-indexed conditional restores the relationship between theresponseargument andElysiaCustomStatusResponse<Code, T>and should fix the 200-inference regression as intended. The constraint logic itself matches the previous inline conditional.If you want to improve readability, you could factor the conditional into a helper alias (e.g.
type ResponseForCode<Res, Code> = ...) and reuse it here and anywhere else this mapping is needed, but that’s optional.
|
hey @MarcelOlsen, tysm for reviewing the report so quickly. i really appreciate it. however, this PR seems to introduce an old error that was fixed in version 1.4.14. could you please take a look at it as well? thanks! |
|
Oh, thanks for catching this! I'll look into this asap. Tests passed, so I assumed I'm good |
|
Hey @Vicentesan I investigated the concern about reintroducing the #1510 bug, and I can confirm the fix maintains strict status checking. Here's why: The current implementation uses
it actually does reject extra properties. The proof is in the testsThe existing test suite (from the #1510 fix) still passes and catches the exact scenario you mentioned: // Strict status response (test/types/index.ts:2843-2875)
new Elysia().post('/mirror', async ({ status, body }) => {
// @ts-ignore - allows body property (wrong value type)
return status(201, { body, success: false })
// @ts-expect-error - correctly rejects wrong literal value
return status(200, { success: false }) // Expected: true
// @ts-expect-error - correctly rejects wrong literal value
return status(201, { success: true }) // Expected: false
}, {
response: {
200: t.Object({ success: t.Literal(true) }),
201: t.Object({ success: t.Literal(false) })
}
})All type tests pass, confirming strict checking works. The Both issues are now fixed
Let me know if I'm missing somehting |
i checked in my IDE with your pkg-pr version, and it allowed more properties in the response schema defined by zod than expected. i will try again and let you know |
|
heres my test: https://github.com/Vicentesan/elysia-zod-response-type-issue/tree/trying-possible-fix the AppResponse type is correct, but the status function is allowing additional properties |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/error.ts(1 hunks)test/types/index.ts(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
test/types/index.ts (2)
src/index.ts (4)
Elysia(185-8122)Elysia(8124-8124)t(8126-8126)status(8167-8167)src/error.ts (1)
status(94-102)
src/error.ts (1)
src/utils.ts (2)
StatusMap(560-622)StatusMap(630-630)
🔇 Additional comments (2)
src/error.ts (1)
42-63: LGTM! Type signature correctly restores missing generic parameter.The explicit generic parameter
Tfor the response type properly restores the relationship between the response schema and the returnedElysiaCustomStatusResponsetype. This change should fix the regression where status code 200 response types were incorrectly widened to index signatures.The conditional type logic correctly resolves the response type from either
Res[Code]orRes[StatusMap[Code]], maintaining consistency between theresponseparameter constraint and the return type.test/types/index.ts (1)
2849-2850: LGTM! Improved test clarity with explicit error expectation.The change from a generic suppression to
@ts-expect-errorwith a clear explanation ("should reject extra 'body' property") makes the test intent explicit. This validates that TypeScript correctly enforces strict property checking against the response schema.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/context.ts (1)
21-27: Consider hoistingCheckExcessPropsto a shared helper.The same helper now lives here and in
src/error.ts; moving it to a common spot (e.g.,src/types.ts) would keep future tweaks in one place and avoid divergence.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/context.ts(2 hunks)src/error.ts(1 hunks)src/types.ts(1 hunks)test/types/index.ts(7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- test/types/index.ts
🔇 Additional comments (2)
src/types.ts (1)
455-456: LGTM – preserves schema inference.Switching the guard to
Schema extends undefinedkeeps optional unions from collapsing wholesale tounknownwhile still defaulting the pureundefinedcase, maintaining the intended unwrap behaviour.src/error.ts (1)
50-75: Glad to see the generic restored.Reintroducing
Tand piping it through both the parameter guard andElysiaCustomStatusResponsere-establishes the response inference that regressed after the refactor.
|
Hey, @Vicentesan I think I misunderstood your original message, my bad. I think this should resolve the issue, my IDE is highlighting the additional data property: |
just tried, now seems to be working! tysm |






Fixes type inference for response schemas and adds excess property checking
What was broken
Type inference for
~Routeswas completely busted in 1.4.14+. Instead of getting the actual schema types, everything just resolved tounknown:Also, TypeScript wasn't catching excess properties in
status()calls:The fix
1. Fixed the backwards conditional in UnwrapSchema
Line 455 in
src/types.tshad the check backwards. Changedundefined extends SchematoSchema extends undefined. The original check would always be true for union types sinceSchema extends AnySchema | string | undefinedmeans undefined is potentially in the union.2. Added excess property checking
Added a
CheckExcessProps<T, U>type that enforces exact property matching. It distributes over union types correctly and still allowsas anyas an escape hatch when you need it.The intersection with excess keys as
nevergives much better error messages - TypeScript points directly at the problem property instead of complaining about the whole object.Applied this to
SelectiveStatusinsrc/error.tsand the status function insrc/context.ts.What works now
as anystill works when you need to bypass checksBetter error messages
Before:
After:
Points directly at the excess property instead of the whole object.
Tests
Here's what the type inference looks like now:
Resolves #1548