Skip to content

Conversation

KurtGokhan
Copy link
Contributor

@KurtGokhan KurtGokhan commented Sep 11, 2025

Addresses #1395

The main cause of the problem is, undefined extends T is true for T = undefined | Something so any type that had that signature was being resolved to the default type.

I added proper IsUndefined check. Also noticed that it should also fallback to the default type when the type is unknown, so I added IsUnknown check. Then, I merged them in the IsSkipped check so that can be used generally for these cases. If the condition to fallback to the default type changes in the future, it will be enough to change the implementation of IsSkipped.

Finally, added a t.Undefinable helper. t.Optional would have been a better name but Typebox uses it for making a schema partial. t.MaybeEmpty also includes null type, and that makes this syntax not possible:

new Elysia().post("/", ({ query: { name = 'Elysia' } = {} }) => `Hello ${name}`, {
  query: t.Undefinable(t.Object({ name: t.String() })),
});

I know the changes I made are somewhat risky and intrusive. And I know you are working on integrating standard-schema. So feel free to modify or discard this PR. But I think a change like this is needed, as there is no way to have a type that is unioned with undefined at the moment.

Note: A similar change is also needed for Eden, and I am planning to also make that if this is merged. Because the same problem exists there too.

Summary by CodeRabbit

  • New Features

    • Added t.Undefinable to define optional (undefined-only) values without allowing null across schemas.
  • Improvements

    • More accurate TypeScript inference for query, params, headers, and cookie in route contexts, especially when segments are optional or skipped.
    • Enhanced schema merging behavior to better respect optional/unknown inputs for body, headers, query, and cookie.
  • Tests

    • Added type-level tests validating t.Undefinable inference for route inputs (query, headers, body) on POST endpoints.

Copy link

coderabbitai bot commented Sep 11, 2025

Walkthrough

Introduces IsSkipped type-based gating across context and schema merge utilities, adds new utility types (IsUnknown, IsUndefined, IsSkipped), updates merge logic to use IsSkipped, and adds a new type builder Undefinable to the type-system with a public alias t.Undefinable. Adds type-only tests verifying Undefinable inference.

Changes

Cohort / File(s) Summary
Context gating with IsSkipped
src/context.ts
Replaced undefined-based checks with IsSkipped for query, params, headers, cookie in ErrorContext and Context type definitions.
Type builder addition and export
src/type-system/index.ts
Added ElysiaType.Undefinable(schema, options) returning schema | undefined; exposed as t.Undefinable via runtime alias.
Schema merge and utility types
src/types.ts
Introduced IsUnknown, IsUndefined, IsSkipped. Updated MergeSchema and MergeStandaloneSchema to use IsSkipped for body, headers, query, cookie branching and intersections.
Type inference tests
test/types/index.ts
Added TS type tests ensuring t.Undefinable propagates undefined in query, headers, body for a POST route.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

I thump my paws on fertile ground,
New types sprout up without a sound—
Skipped or known, the branches split,
Undefinable finds its fit.
Headers, query, body aligned,
In tidy unions, neatly twined.
Carrot-shaped checks, compile-time kind. 🥕✨

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

Pre-merge checks (3 passed)

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title "fix: infer type of undefinable parameters correctly" accurately and concisely summarizes the primary intent of the changeset — fixing type inference for undefinable parameters (the IsUndefined/IsUnknown/IsSkipped logic and t.Undefinable helper) — and uses a clear conventional-commit style without extraneous detail.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • 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

pkg-pr-new bot commented Sep 11, 2025

Open in StackBlitz

npm i https://pkg.pr.new/elysiajs/elysia@1397

commit: 1ded85f

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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/types.ts (1)

667-667: Replace legacy undefined-check with IsSkipped for cookie merge

undefined extends A['cookie'] incorrectly treats unions like X | undefined as "skipped"; align with other fields by using IsSkipped.

Location: src/types.ts:667

-    cookie: undefined extends A['cookie'] ? B['cookie'] : A['cookie']
+    cookie: IsSkipped<A['cookie']> extends true ? B['cookie'] : A['cookie']
🧹 Nitpick comments (3)
test/types/index.ts (3)

2685-2689: Avoid runtime access of "~Routes"; use type-level typeof instead

Accessing app["~Routes"].post... is a value-level read and can throw if executed. Prefer the established pattern using typeof so this remains purely type-checked.

-	expectTypeOf(app["~Routes"].post.query).not.toEqualTypeOf<unknown>();
-	expectTypeOf(app["~Routes"].post.query).toEqualTypeOf<{ name: string } | undefined>();
-	expectTypeOf(app["~Routes"].post.headers).toEqualTypeOf<{ token: string } | undefined>();
-	expectTypeOf(app["~Routes"].post.body).toEqualTypeOf<{ id: number } | undefined>();
+	expectTypeOf<(typeof app)['~Routes']['post']['query']>().not.toBeUnknown()
+	expectTypeOf<(typeof app)['~Routes']['post']['query']>().toEqualTypeOf<{ name: string } | undefined>()
+	expectTypeOf<(typeof app)['~Routes']['post']['headers']>().toEqualTypeOf<{ token: string } | undefined>()
+	expectTypeOf<(typeof app)['~Routes']['post']['body']>().toEqualTypeOf<{ id: number } | undefined>()

2685-2689: Optional: assert headers/body are not unknown for completeness

Mirror the query assertion to ensure headers/body don’t regress to unknown.

+	expectTypeOf<(typeof app)['~Routes']['post']['headers']>().not.toBeUnknown()
+	expectTypeOf<(typeof app)['~Routes']['post']['body']>().not.toBeUnknown()

2677-2683: Stylistic nits: align quotes/semicolons with file conventions

Most of this file uses single quotes and omits trailing semicolons. Consider matching that for consistency.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c63dc0 and 1ded85f.

📒 Files selected for processing (4)
  • src/context.ts (3 hunks)
  • src/type-system/index.ts (2 hunks)
  • src/types.ts (4 hunks)
  • test/types/index.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/context.ts (1)
src/types.ts (2)
  • IsSkipped (753-753)
  • ResolvePath (239-249)
🔇 Additional comments (9)
src/type-system/index.ts (2)

583-583: Public alias looks good.

Exposing as t.Undefinable keeps parity with existing helpers.


422-427: Clarify Undefinable semantics & verify OpenAPI/JSON Schema handling of TypeBox Undefined

File: src/type-system/index.ts (lines ~422-427)

  • Update the doc comment: this builder unions with Undefined only and does NOT mark properties optional. Suggested diff:
/**
- * Allow Optional and Undefined
+ * Allow Undefined (does NOT mark properties optional)
 */
Undefinable: <T extends TSchema>(schema: T, options?: SchemaOptions) =>
	t.Union([schema, t.Undefined()], options),
  • Verify your schema→OpenAPI/JSON Schema emitter tolerates TypeBox's Undefined inside unions (or document/transform it). If no emitter exists in this repo, state where conversion happens so compatibility can be checked.
src/types.ts (4)

657-660: Move to IsSkipped for body/headers/query merge — good.

This fixes the “undefined extends T” pitfall and preserves unions like T | undefined.


682-703: Standalone merge for body/headers/query updated correctly.

Symmetric IsSkipped branching reads well.


710-717: Cookie standalone merge aligned — good.

This further highlights the inconsistency in MergeSchema’s cookie; fix there as noted.


745-754: Helper types look correct and non-distributive.

  • IsUnknown excludes any and matches only exact unknown.
  • IsUndefined checks exact undefined via tuple wrapping.
  • IsSkipped composition is clear.
src/context.ts (3)

15-17: Importing IsSkipped for gating — good.

Keeps types local and avoids repeating logic here.


33-51: ErrorContext gating now respects unknown/exact-undefined — good.

Safer defaults for query/headers/cookie while preserving typed branches when provided.


126-146: Context query/params/headers/cookie gating updated correctly.

Behavior now matches the new inference strategy; params fallback handling still covers path-derived types.

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.

1 participant