Skip to content

Conversation

@abhishekg999
Copy link
Contributor

@abhishekg999 abhishekg999 commented Oct 16, 2025

Resolves #217

import { Elysia, t } from 'elysia'
import { treaty } from './src/treaty2'

const app = new Elysia().get('/optional', ({ query: { name } }) => name, {
    query: t.Object({
        name: t.Optional(t.String())
    }),
    headers: t.Object({
        'x-token': t.Optional(t.String())
    })
})

export type App = typeof app

const api = treaty<App>('localhost:8080')

type Query = NonNullable<Parameters<typeof api.optional.get>[0]>['query']
// type QueryParams = ({ name?: string | undefined; } & Record<string, unknown>) | undefined
type Headers = NonNullable<Parameters<typeof api.optional.get>[0]>['headers']
// ({ "x-token"?: string | undefined; } & Record<string, unknown>) | undefined

api.optional.get({
    query: {
        name: 'John',
        allowed: true // additional properties still allowed like old behavior
    },
    headers: {
        'x-token': '123',
        allowed: true // additional properties still allowed like old behavior
    }
})

api.optional.get({
    query: {
        name: 1, // type error: Type 'number' is not assignable to type 'string'.
        allowed: true
    },
    headers: {
        'x-token': 1, // type error: Type 'number' is not assignable to type 'string'.
        allowed: true
    }
})

Summary by CodeRabbit

  • Refactor
    • Improved type inference for API request parameters to provide more consistent and flexible handling of optional versus required parameters.

@coderabbitai
Copy link

coderabbitai bot commented Oct 16, 2025

Walkthrough

Rewritten conditional typing logic for headers and query parameters in Treaty.Sign. The new evaluation order uses undefined extends and {} extends checks to determine whether parameters should be optional, optional with expanded shapes, or required, directly affecting the inferred parameter shapes.

Changes

Cohort / File(s) Summary
Treaty.Sign conditional type refactoring
src/treaty2/types.ts
Updated conditional typing for headers and query parameters within Treaty.Sign. Changed evaluation order: undefined extends Type → optional Record<string, unknown>; {} extends Type → optional Type & Record<string, unknown>; otherwise → required Type. Affects parameter inference for endpoint signatures.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

The changes involve intricate conditional type logic affecting the public API surface. Review requires careful evaluation of TypeScript type inference semantics, assessment of backwards compatibility impact on existing routes, and verification that parameter optional/required states are correctly inferred across various header and query type combinations.

Poem

🐰 Headers dance, queries flex and bend,
Optional when undefined—their new trend,
Empty objects bloom with broader grace,
Type conditions find their rightful place. ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "fix: type hints for optional query and headers" directly and accurately describes the main change in the pull request. The modifications to Treaty.Sign<Route> in src/treaty2/types.ts specifically rewrite the conditional typing logic to properly handle optional query and header parameters, making the title fully aligned with the changeset's primary objective. The title is concise, clear, and specific enough that a teammate reviewing the commit history would understand the purpose of this change without additional context.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between 72a0472 and 3b0d363.

📒 Files selected for processing (1)
  • src/treaty2/types.ts (1 hunks)

Comment on lines +93 to 104
? (undefined extends Headers
? { headers?: Record<string, unknown> }
: {} extends Headers
? { headers?: Headers & Record<string, unknown> }
: {
headers: Headers
}) &
({} extends Query
? {
query?: Record<string, unknown>
}
: undefined extends Query
? { query?: Record<string, unknown> }
(undefined extends Query
? { query?: Record<string, unknown> }
: {} extends Query
? { query?: Query & Record<string, unknown> }
: { query: Query }) extends infer Param
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Optional branch drops header/query schema types

When Headers (or Query) includes undefined, the new branch resolves to { headers?: Record<string, unknown> } / { query?: Record<string, unknown> }. That erases the concrete schema (e.g. 'x-token'?: string now becomes unknown), so callers lose type safety. We need to keep the original shape while still making the property optional. You can fix this by intersecting with Exclude<Headers, undefined> (and the query equivalent) so the optional case preserves the schema:

-    ? { headers?: Record<string, unknown> }
+    ? {
+          headers?: (Exclude<Headers, undefined> extends Record<string, unknown>
+              ? Exclude<Headers, undefined> & Record<string, unknown>
+              : Record<string, unknown>)
+      }

and likewise for the query branch. This keeps the schema-specific property types intact while still allowing optional usage.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/treaty2/types.ts around lines 93 to 104, the optional branches for
Headers and Query currently map to Record<string, unknown> when those types
include undefined, which erases the concrete schema; change the optional
branches to preserve the original shape by intersecting with Exclude<Headers,
undefined> (and Exclude<Query, undefined> for the query branch) so the optional
property uses the original schema type (e.g. { headers?: Exclude<Headers,
undefined> & Record<string, unknown> } or equivalently an optional property
typed as Exclude<Headers, undefined>) — apply the same pattern to the query
branch to keep schema-specific keys while making the property optional.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

type hints when all query params are optional are lost

1 participant