Skip to content

perf(types): reduce deep and exhaustive type inference complexity#45

Merged
halvaradop merged 8 commits intomasterfrom
perf/optimize-types
May 8, 2026
Merged

perf(types): reduce deep and exhaustive type inference complexity#45
halvaradop merged 8 commits intomasterfrom
perf/optimize-types

Conversation

@halvaradop
Copy link
Copy Markdown
Member

@halvaradop halvaradop commented May 8, 2026

Description

This pull request improves internal type-system consistency for type inference and type references across the router internals, especially for deeply nested types such as EndpointSchemas, EndpointConfig, MiddlewareFunction, and RequestContext.

These improvements significantly reduce occurrences of the following TypeScript compiler error:

TS2589: Type instantiation is excessively deep and possibly infinite

During the investigation, one of the primary causes of this issue was identified as Valibot's InferOutput type. To address this, a built-in output inference utility was introduced to reduce type complexity and improve compiler stability.

Additionally, several distributive conditional types were simplified to minimize recursive type expansion. Some of these optimizations were initially introduced in the previous PR and are further refined in this update.

Benchs

Files:                         425
Lines of Library:            51352
Lines of Definitions:       115411
Lines of TypeScript:          1409
Lines of JavaScript:             0
Lines of JSON:                   0
Lines of Other:                  0
Identifiers:                180286
Symbols:                    111027
Types:                        8739
Instantiations:              16154
Memory used:               183967K
Assignability cache size:     1375
Identity cache size:            22
Subtype cache size:            160
Strict subtype cache size:     115
I/O Read time:               0.06s
Parse time:                  0.29s
ResolveModule time:          0.15s
ResolveTypeReference time:   0.01s
ResolveLibrary time:         0.01s
Program time:                0.58s
Bind time:                   0.15s
Check time:                  0.23s
transformTime time:          0.02s
printTime time:              0.00s
Emit time:                   0.00s
Total time:                  0.96s

Related PRs

#44

Summary by CodeRabbit

Release Notes

  • New Features

    • Added strongly-typed client generator for route endpoints with improved schema-based request/response inference.
  • Refactor

    • Reorganized and consolidated type definitions into a dedicated types module structure.
    • Improved type inference for middleware functions and request contexts with stricter schema validation.
  • Chores

    • Enabled strict TypeScript mode for improved code safety and consistency.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
router Ready Ready Preview, Comment May 8, 2026 2:49pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Review Change Stack

Warning

Rate limit exceeded

@halvaradop has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 47 minutes and 5 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e00197d5-8aa1-4e84-8f7c-4661df92aadf

📥 Commits

Reviewing files that changed from the base of the PR and between 2c0c9fc and 0619902.

📒 Files selected for processing (4)
  • package.json
  • src/@types/client.ts
  • src/@types/types.ts
  • tsconfig.json
📝 Walkthrough

Walkthrough

This PR centralizes endpoint schema inference via a new UnwrapSchema and adds a consolidated src/@types surface (client/http/types). RequestContext and MiddlewareFunction are retyped to use EndpointSchemas; getSearchParams, executeMiddlewares, router imports, tests, and package/deno exports are updated to the new types.

Changes

Schema Inference Refactoring

Layer / File(s) Summary
New @types modules
src/@types/client.ts, src/@types/http.ts, src/@types/index.ts
Adds typed client utilities, HTTP method/content/header types, and an index re-export surface.
UnwrapSchema & Context inference
src/@types/types.ts, src/types.ts
Introduces UnwrapSchema and rewrites ContextSearchParams, ContextBody, and ContextParams to infer directly from EndpointSchemas.
Request Context & Middleware Types
src/types.ts
Reworks RequestContext generics and MiddlewareFunction to be parameterized by Schemas extends EndpointSchemas and to use RequestContext<Route, { schemas: Schemas }> for ctx/returns.
Context Function
src/context.ts
getSearchParams return type changed to ContextSearchParams<NonNullable<Config['schemas']>>; both schema-validated and fallback URLSearchParams are cast to the unified type.
Middleware Execution
src/middlewares.ts
executeMiddlewares generic changed to Config extends EndpointSchemas and accepts MiddlewareFunction<Route, Config>[]; runtime iteration and error-wrapping unchanged.
Router Type Suppression
src/router.ts
Adds // @ts-ignore`` before building the route RequestContext to bypass overlapping-type checks.
Runtime imports & exports
src/index.ts, src/endpoint.ts, src/response.ts, src/client.ts, src/trie.ts, src/assert.ts, bench files
Switches many type-only imports and the public type re-export to @/@types/index.ts; adjusts some path-alias imports.
Validator & Compiler Config
src/validator/registry.ts, tsconfig.json, deno.json, package.json
Reformats ArkType isError predicate; sets compilerOptions.alwaysStrict: true; removes baseUrl while preserving paths; updates ./types export targets to the new @types path.
Tests & Type-tests / Benchmarks
test/*, test/type.test-d.ts, bench/*
Updates imports to @/@types/index.ts; inserts // @ts-ignore`` in context tests for getSearchParams; revises type-test expectations to match flattened inferred types and updated middleware generics.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • aura-stack-ts/router#43: Both PRs modify the same schema/validation/type-inference surfaces (validator registry, context parsing, and types to accept Valibot/Zod schemas).
  • aura-stack-ts/router#44: Adds Arktype support overlapping the validator and type changes in this PR.
  • aura-stack-ts/router#38: Related middleware typing and RequestContext/MiddlewareFunction signature changes.

Suggested labels

enhancement

Poem

🐰 I hopped through types and made them neat,
Unwrapped schemas with nimble feet,
New @types paths stitched up the seams,
Middleware and context got flatter dreams,
A rabbit cheers for tidy teams!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly describes the main change: reducing type inference complexity in the codebase, which aligns with the extensive refactoring of type definitions and schema inference patterns across multiple files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/optimize-types

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
Copy Markdown

@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: 2

Caution

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

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

129-144: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

RequestContext typing relies on NonNullable<Config["schemas"]>, which collapses to never when schemas is absent.

Config defaults to { schemas?: EndpointSchemas }, so Config["schemas"] can be undefined, and NonNullable<undefined> is never. Passing never into ContextParams / ContextBody / ContextSearchParams causes the [S] extends [ZodObject<any>] branch to match (since never is assignable to every type), producing surprising inferred shapes for callers without schemas. This is very likely the root cause of the // @ts-ignore`` workarounds in src/router.ts and `test/context.test.ts`.

Consider switching to a Config["schemas"] extends EndpointSchemas ? Config["schemas"] : {} pattern (or making RequestContext take Schemas directly and let call sites compute it), so the absent-schemas case yields EndpointSchemas/{} and UnwrapSchema cleanly hits the Fallback.

🤖 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 `@src/types.ts` around lines 129 - 144, RequestContext currently uses
NonNullable<Config["schemas"]> which collapses to never when schemas is absent
and causes ContextParams/ContextBody/ContextSearchParams to pick the wrong
branch; change the generic handling so absent schemas resolve to an empty shape
(e.g. replace NonNullable<Config["schemas"]> with a conditional type like
Config["schemas"] extends EndpointSchemas ? Config["schemas"] : {}), or refactor
RequestContext to accept a Schemas generic parameter computed at call sites, and
update the references to ContextParams, ContextBody and ContextSearchParams to
use that resolved Schemas type (symbols: RequestContext, Config["schemas"],
ContextParams, ContextBody, ContextSearchParams, EndpointSchemas).
src/middlewares.ts (1)

48-65: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Per-route middleware loop silently drops Response returns.

MiddlewareFunction is typed to return Response | RequestContext<Route, { schemas: Config }> | Promise<Response | ...>, but line 55 unconditionally casts the result to RequestContext. If a route-level middleware returns a Response (which the public type advertises and which executeGlobalMiddlewares already handles correctly), it gets assigned to ctx and the next middleware/handler receives a Response instance instead of a RequestContext — at runtime this will fail when code calls ctx.searchParams, ctx.headers, etc.

Mirror the global-middleware short-circuit (lines 24–27 in executeGlobalMiddlewares): check instanceof Response and return early. Update the caller in src/router.ts to also branch on Response.

🛠 Proposed fix
         try {
             let ctx = context
             for (const middleware of use) {
                 if (typeof middleware !== "function") {
                     throw new RouterError("BAD_REQUEST", "Middleware must be a function")
                 }
                 try {
-                    ctx = (await middleware(ctx)) as RequestContext<Route, { schemas: Config }>
+                    const result = await middleware(ctx)
+                    if (result instanceof Response) {
+                        return result
+                    }
+                    ctx = result as RequestContext<Route, { schemas: Config }>
                 } catch (error) {
                     if (error instanceof RouterError) throw error
                     throw new RouterError("BAD_REQUEST", "Handler threw an error", "MiddlewareError")
                 }
             }
             return ctx
         } catch {
             throw new RouterError("BAD_REQUEST", "Handler threw an error")
         }
🤖 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 `@src/middlewares.ts` around lines 48 - 65, The per-route middleware loop
assigns middleware results to ctx without handling Response returns, causing a
Response to be passed as a RequestContext; update the loop in the function that
iterates over use/middleware (the block that sets let ctx = context and does ctx
= (await middleware(ctx)) as RequestContext<Route, { schemas: Config }>) to
detect if the result is an instance of Response and return it immediately
(mirroring executeGlobalMiddlewares behavior) instead of casting to
RequestContext; preserve existing RouterError checks and throw MiddlewareError
for non-RouterError exceptions, and also update the caller in src/router.ts that
awaits this middleware runner to branch on a returned Response (handle
short-circuiting) before treating the result as a RequestContext.
🧹 Nitpick comments (3)
src/router.ts (1)

58-59: ⚡ Quick win

Suppress the symptom, not the type system — and prefer @ts-expect-error.

The "overlapping types" being silenced here are the NonNullable<Config["schemas"]> issue flagged on src/types.ts. Two improvements:

  1. Once RequestContext/UnwrapSchema correctly handle the absent-schemas case, this directive should become unnecessary — fixing the helpers is preferable to suppressing each call site.
  2. In the meantime, use @ts-expect-error (with a short reason) so TS will fail the build the moment the underlying type is fixed and the directive becomes obsolete. A bare @ts-ignore will silently linger.
♻️ Stop-gap until the helper types are fixed
-        // `@ts-ignore` Skip type checking here because there's overlapping types
+        // `@ts-expect-error` endpoint.config has a wider Schemas than getSearchParams's generic; tracked in src/types.ts UnwrapSchema/RequestContext
         const searchParams = getSearchParams(globalRequestContext.request.url, endpoint.config)
🤖 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 `@src/router.ts` around lines 58 - 59, Replace the bare "@ts-ignore" before the
getSearchParams call with an "@ts-expect-error" and a short reason (e.g. "//
`@ts-expect-error`: temporary until RequestContext/UnwrapSchema handle absent
Config['schemas']") so TypeScript will fail once the underlying types are
corrected; also add a TODO referencing RequestContext and UnwrapSchema to remind
maintainers to remove this once the NonNullable<Config['schemas']> issue is
fixed, and avoid adding further ignores at other call sites like usages of
globalRequestContext.request.url or getSearchParams.
test/context.test.ts (1)

298-299: ⚡ Quick win

Prefer @ts-expect-error (with a reason) over @ts-ignore.

Same applies to line 348. These are workarounds for the upstream RequestContext/UnwrapSchema typing issue. Switching to @ts-expect-error ensures the directive auto-fails once the helper types are fixed, so these tests stop silently masking real type regressions.

🤖 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 `@test/context.test.ts` around lines 298 - 299, Replace the two occurrences of
the loose suppression comment with explicit expected-error comments: change the
existing `@ts-ignore` above the getSearchParams call (and the similar suppression
at the other test around line with the UnwrapSchema/RequestContext workaround)
to `@ts-expect-error` and include a brief reason string explaining it’s a
temporary workaround for the upstream RequestContext/UnwrapSchema typing issue
so the tests will fail once the upstream types are fixed; locate the
suppressions by the getSearchParams call and the test referencing
UnwrapSchema/RequestContext and update both comments accordingly.
src/middlewares.ts (1)

44-47: 💤 Low value

Generic name Config is misleading now that it holds EndpointSchemas.

The parameter is no longer an EndpointConfig — it's the schemas slice — so Config extends EndpointSchemas reads confusingly at call sites ({ schemas: Config }). Renaming to Schemas keeps it consistent with MiddlewareFunction<Route, Schemas> in src/types.ts.

♻️ Proposed rename
-export const executeMiddlewares = async <Route extends RoutePattern, Config extends EndpointSchemas = EndpointSchemas>(
-    context: RequestContext<Route, { schemas: Config }>,
-    use: MiddlewareFunction<Route, Config>[] = []
-) => {
+export const executeMiddlewares = async <Route extends RoutePattern, Schemas extends EndpointSchemas = EndpointSchemas>(
+    context: RequestContext<Route, { schemas: Schemas }>,
+    use: MiddlewareFunction<Route, Schemas>[] = []
+) => {
🤖 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 `@src/middlewares.ts` around lines 44 - 47, Rename the generic type parameter
Config to Schemas in the executeMiddlewares declaration so the signature reads
executeMiddlewares<Route extends RoutePattern, Schemas extends EndpointSchemas =
EndpointSchemas>(context: RequestContext<Route, { schemas: Schemas }>, use:
MiddlewareFunction<Route, Schemas>[] = []) to reflect that the generic
represents the schemas slice; update all references within the function and its
type annotations (including the RequestContext generic payload and the
MiddlewareFunction generic) from Config to Schemas to keep names consistent with
src/types.ts and avoid confusing call-site reads.
🤖 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 `@src/types.ts`:
- Around line 90-99: The UnwrapSchema conditional for the valibot branch returns
the literal type false when an ObjectSchema's "~types" doesn't match the
expected { output: infer Output }, which then pollutes
ContextBody/ContextSearchParams/ContextParams; change the valibot branch in
UnwrapSchema (the clause matching [S] extends [ObjectSchema<any, undefined>]) to
fall back to Fallback (or unknown) instead of false by making the final
conditional yield Fallback when the inferred Output can't be extracted from
S["~types"] so callers receive a usable type; update references to UnwrapSchema,
ObjectSchema and the "~types" extraction logic accordingly.
- Around line 92-96: The code uses valibot's internal NonNullable<S["~types"]>
to get schema output which is fragile; change this branch to use the public
InferOutput<S> helper (same approach as InferSchema) instead of accessing
"~types", and ensure InferOutput is imported/available where the conditional is
declared; if you intentionally need the internal path instead, pin valibot to an
exact version and add a tsd/expectTypeOf regression test that will fail if
"~types" changes.

---

Outside diff comments:
In `@src/middlewares.ts`:
- Around line 48-65: The per-route middleware loop assigns middleware results to
ctx without handling Response returns, causing a Response to be passed as a
RequestContext; update the loop in the function that iterates over
use/middleware (the block that sets let ctx = context and does ctx = (await
middleware(ctx)) as RequestContext<Route, { schemas: Config }>) to detect if the
result is an instance of Response and return it immediately (mirroring
executeGlobalMiddlewares behavior) instead of casting to RequestContext;
preserve existing RouterError checks and throw MiddlewareError for
non-RouterError exceptions, and also update the caller in src/router.ts that
awaits this middleware runner to branch on a returned Response (handle
short-circuiting) before treating the result as a RequestContext.

In `@src/types.ts`:
- Around line 129-144: RequestContext currently uses
NonNullable<Config["schemas"]> which collapses to never when schemas is absent
and causes ContextParams/ContextBody/ContextSearchParams to pick the wrong
branch; change the generic handling so absent schemas resolve to an empty shape
(e.g. replace NonNullable<Config["schemas"]> with a conditional type like
Config["schemas"] extends EndpointSchemas ? Config["schemas"] : {}), or refactor
RequestContext to accept a Schemas generic parameter computed at call sites, and
update the references to ContextParams, ContextBody and ContextSearchParams to
use that resolved Schemas type (symbols: RequestContext, Config["schemas"],
ContextParams, ContextBody, ContextSearchParams, EndpointSchemas).

---

Nitpick comments:
In `@src/middlewares.ts`:
- Around line 44-47: Rename the generic type parameter Config to Schemas in the
executeMiddlewares declaration so the signature reads executeMiddlewares<Route
extends RoutePattern, Schemas extends EndpointSchemas =
EndpointSchemas>(context: RequestContext<Route, { schemas: Schemas }>, use:
MiddlewareFunction<Route, Schemas>[] = []) to reflect that the generic
represents the schemas slice; update all references within the function and its
type annotations (including the RequestContext generic payload and the
MiddlewareFunction generic) from Config to Schemas to keep names consistent with
src/types.ts and avoid confusing call-site reads.

In `@src/router.ts`:
- Around line 58-59: Replace the bare "@ts-ignore" before the getSearchParams
call with an "@ts-expect-error" and a short reason (e.g. "// `@ts-expect-error`:
temporary until RequestContext/UnwrapSchema handle absent Config['schemas']") so
TypeScript will fail once the underlying types are corrected; also add a TODO
referencing RequestContext and UnwrapSchema to remind maintainers to remove this
once the NonNullable<Config['schemas']> issue is fixed, and avoid adding further
ignores at other call sites like usages of globalRequestContext.request.url or
getSearchParams.

In `@test/context.test.ts`:
- Around line 298-299: Replace the two occurrences of the loose suppression
comment with explicit expected-error comments: change the existing `@ts-ignore`
above the getSearchParams call (and the similar suppression at the other test
around line with the UnwrapSchema/RequestContext workaround) to `@ts-expect-error`
and include a brief reason string explaining it’s a temporary workaround for the
upstream RequestContext/UnwrapSchema typing issue so the tests will fail once
the upstream types are fixed; locate the suppressions by the getSearchParams
call and the test referencing UnwrapSchema/RequestContext and update both
comments accordingly.
🪄 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: 686914a1-766f-4f94-8d47-8183570d9adf

📥 Commits

Reviewing files that changed from the base of the PR and between 1654e27 and e974fb2.

📒 Files selected for processing (10)
  • src/context.ts
  • src/middlewares.ts
  • src/router.ts
  • src/types.ts
  • src/validator/registry.ts
  • test/context.test.ts
  • test/endpoint.test.ts
  • test/middlewares.test.ts
  • test/type.test-d.ts
  • tsconfig.json

Comment thread src/@types/types.ts
Comment thread src/types.ts Outdated
Copy link
Copy Markdown

@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

🧹 Nitpick comments (5)
tsconfig.json (1)

13-32: 💤 Low value

Several added strict flags are already implied by strict: true.

alwaysStrict, noImplicitAny, strictNullChecks, and strictFunctionTypes are all enabled when "strict": true is set; listing them again is redundant. The non-redundant additions worth keeping explicit are noUnusedLocals, noUnusedParameters, noImplicitReturns, noFallthroughCasesInSwitch, and verbatimModuleSyntax (since these are not part of the strict family).

♻️ Proposed cleanup
     "strict": true,
     "allowJs": true,
     "checkJs": true,
-    "alwaysStrict": true,
     "esModuleInterop": true,
     ...
     /* Type checking */
     "noUnusedLocals": true,
     "noUnusedParameters": true,
-    "noImplicitAny": true,
     "noImplicitReturns": true,
     "noFallthroughCasesInSwitch": true,
-    "strictNullChecks": true,
-    "strictFunctionTypes": true,
     "verbatimModuleSyntax": true,
🤖 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 `@tsconfig.json` around lines 13 - 32, Remove redundant strict-family compiler
options that are implied by "strict": true — specifically delete "alwaysStrict",
"noImplicitAny", "strictNullChecks", and "strictFunctionTypes" from
tsconfig.json; retain the explicit non-strict flags you want to keep such as
"noUnusedLocals", "noUnusedParameters", "noImplicitReturns",
"noFallthroughCasesInSwitch", and "verbatimModuleSyntax" so they remain explicit
and effective.
src/@types/client.ts (2)

47-62: 💤 Low value

HasSchemas<C> collapses to never for empty schemas, relying on vacuous never extends false.

When Schemas = {}, Schemas[keyof Schemas] is never, so HasSchemas<C> distributes to never. The downstream HasSchemas<Config> extends false ? noBodyArm : bodyArm still resolves to noBodyArm because never extends false is true, so the result is correct — but this is non-obvious. A short comment would help future readers, or you could short-circuit explicitly with keyof Schemas extends never ? false : ....

🤖 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 `@src/`@types/client.ts around lines 47 - 62, The conditional type HasSchemas
currently collapses to never for empty schema objects (because Schemas[keyof
Schemas] becomes never), causing a non-obvious `never extends false` behavior
used by Client; update HasSchemas to explicitly short-circuit empty schemas
(e.g., make it return false when `keyof Schemas extends never`) so the
downstream conditional in the Client type is clear and stable, or add a concise
comment next to HasSchemas explaining the vacuous `never` distribution; refer to
the HasSchemas type and the Client<Endpoints...> generic so the fix is applied
where the schema presence check is defined and consumed.

5-6: 💤 Low value

Merge the two imports from the same module.

Lines 5 and 6 both pull from @/@types/types.ts; combine into a single import type statement.

♻️ Proposed change
-import type { InferValibotSchema, RoutePattern } from "@/@types/types.ts"
-import type { EndpointConfig, Prettify, RouteEndpoint } from "@/@types/types.ts"
+import type { EndpointConfig, InferValibotSchema, Prettify, RouteEndpoint, RoutePattern } from "@/@types/types.ts"
🤖 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 `@src/`@types/client.ts around lines 5 - 6, Merge the two separate import lines
that both import from the same module into a single import type statement;
replace the two imports that reference InferValibotSchema, RoutePattern and
EndpointConfig, Prettify, RouteEndpoint with one consolidated `import type` line
that lists all five symbols (InferValibotSchema, RoutePattern, EndpointConfig,
Prettify, RouteEndpoint) so there is a single import from the module.
src/@types/types.ts (1)

70-81: 💤 Low value

Add a documentation note about the internal ~types dependency.

InferValibotSchema intentionally reads from Valibot's internal ~types phantom field to avoid performance issues with InferOutput. Valibot's documentation marks ~types as internal API, meaning it could change in minor versions. Document this dependency so future Valibot upgrades trigger a deliberate review.

♻️ Suggested doc update
 /**
  * Utility type to infer the output type of a Valibot ObjectSchema. This utility is used instead
  * of `InferOutput` directly to avoid performance issues caused by deeply nested conditional types.
+ *
+ * Note: relies on Valibot's internal `~types` field. Verify compatibility on every Valibot major/minor bump.
  */
 export type InferValibotSchema<S extends ObjectSchema<any, undefined>> = NonNullable<S["~types"]>["output"]
🤖 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 `@src/`@types/types.ts around lines 70 - 81, Add a short doc comment above
InferValibotSchema explaining that it intentionally reads Valibot's internal
phantom field "~types" (used by InferValibotSchema) to avoid performance issues
with InferOutput, and note that "~types" is an internal API that may change
across minor versions so upgrades should include a deliberate review and
testing; reference the InferValibotSchema type and the UnwrapSchema helper
(which branches on ObjectSchema and uses InferValibotSchema) so maintainers know
where the dependency exists.
package.json (1)

83-83: 💤 Low value

The string form "./types": "./dist/@types/index.d.ts" is valid but consider the explicit conditional for robustness.

TypeScript's modern moduleResolution modes (node16, nodenext) will resolve the string-valued export and discover the .d.ts file via sibling lookup. However, downstream consumers using older TypeScript configurations benefit from the explicit "types" condition for clearer intent and more predictable type resolution across different module-resolution strategies.

♻️ Suggested form
-    "./types": "./dist/@types/index.d.ts"
+    "./types": {
+      "types": "./dist/@types/index.d.ts"
+    }
🤖 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 `@package.json` at line 83, Replace the simple string export for the "./types"
key with an explicit conditional export so TypeScript's older moduleResolution
modes reliably pick up the .d.ts entry; locate the "./types" entry in
package.json and change it to an exports-style conditional that includes a
"types" condition targeting "./dist/@types/index.d.ts (and fallbacks for
default/node as needed)" so both modern and legacy TS consumers resolve your
declaration file consistently.
🤖 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 `@src/`@types/types.ts:
- Around line 144-149: The exported MiddlewareFunction signature changed its
second generic from the old config-wrapper shape to take EndpointSchemas
directly, which is a breaking API change; revert or restore a
backwards-compatible overload so MiddlewareFunction<Route, { schemas: ... }>
continues to work: either restore the original generic shape that expects
RequestContext<Route, { schemas: Schemas }> (using the existing types
RequestContext, RoutePattern and EndpointSchemas) or add an overload/alias that
maps the wrapper form to the new shape, and add a CHANGELOG/migration note
documenting the change and recommended typings for external users.

---

Nitpick comments:
In `@package.json`:
- Line 83: Replace the simple string export for the "./types" key with an
explicit conditional export so TypeScript's older moduleResolution modes
reliably pick up the .d.ts entry; locate the "./types" entry in package.json and
change it to an exports-style conditional that includes a "types" condition
targeting "./dist/@types/index.d.ts (and fallbacks for default/node as needed)"
so both modern and legacy TS consumers resolve your declaration file
consistently.

In `@src/`@types/client.ts:
- Around line 47-62: The conditional type HasSchemas currently collapses to
never for empty schema objects (because Schemas[keyof Schemas] becomes never),
causing a non-obvious `never extends false` behavior used by Client; update
HasSchemas to explicitly short-circuit empty schemas (e.g., make it return false
when `keyof Schemas extends never`) so the downstream conditional in the Client
type is clear and stable, or add a concise comment next to HasSchemas explaining
the vacuous `never` distribution; refer to the HasSchemas type and the
Client<Endpoints...> generic so the fix is applied where the schema presence
check is defined and consumed.
- Around line 5-6: Merge the two separate import lines that both import from the
same module into a single import type statement; replace the two imports that
reference InferValibotSchema, RoutePattern and EndpointConfig, Prettify,
RouteEndpoint with one consolidated `import type` line that lists all five
symbols (InferValibotSchema, RoutePattern, EndpointConfig, Prettify,
RouteEndpoint) so there is a single import from the module.

In `@src/`@types/types.ts:
- Around line 70-81: Add a short doc comment above InferValibotSchema explaining
that it intentionally reads Valibot's internal phantom field "~types" (used by
InferValibotSchema) to avoid performance issues with InferOutput, and note that
"~types" is an internal API that may change across minor versions so upgrades
should include a deliberate review and testing; reference the InferValibotSchema
type and the UnwrapSchema helper (which branches on ObjectSchema and uses
InferValibotSchema) so maintainers know where the dependency exists.

In `@tsconfig.json`:
- Around line 13-32: Remove redundant strict-family compiler options that are
implied by "strict": true — specifically delete "alwaysStrict", "noImplicitAny",
"strictNullChecks", and "strictFunctionTypes" from tsconfig.json; retain the
explicit non-strict flags you want to keep such as "noUnusedLocals",
"noUnusedParameters", "noImplicitReturns", "noFallthroughCasesInSwitch", and
"verbatimModuleSyntax" so they remain explicit and effective.
🪄 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: c7405de8-1529-4ca3-a0fd-839a39c8d41e

📥 Commits

Reviewing files that changed from the base of the PR and between e974fb2 and 2c0c9fc.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • deno.lock is excluded by !**/*.lock
📒 Files selected for processing (24)
  • bench/comparator.bench.mts
  • bench/router.bench.ts
  • deno.json
  • package.json
  • src/@types/client.ts
  • src/@types/http.ts
  • src/@types/index.ts
  • src/@types/types.ts
  • src/assert.ts
  • src/client.ts
  • src/context.ts
  • src/endpoint.ts
  • src/index.ts
  • src/middlewares.ts
  • src/on-error.ts
  • src/response.ts
  • src/router.ts
  • src/trie.ts
  • test/client.test.ts
  • test/context.test.ts
  • test/endpoint.test.ts
  • test/middlewares.test.ts
  • test/type.test-d.ts
  • tsconfig.json
✅ Files skipped from review due to trivial changes (11)
  • src/trie.ts
  • deno.json
  • src/@types/index.ts
  • src/endpoint.ts
  • src/on-error.ts
  • src/response.ts
  • src/assert.ts
  • src/@types/http.ts
  • src/client.ts
  • bench/comparator.bench.mts
  • test/endpoint.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/router.ts
  • test/context.test.ts
  • test/middlewares.test.ts
  • src/context.ts
  • src/middlewares.ts

Comment thread src/@types/types.ts
Comment on lines +144 to +149
export type MiddlewareFunction<Route extends RoutePattern = RoutePattern, Schemas extends EndpointSchemas = {}> = (
ctx: Prettify<RequestContext<Route, { schemas: Schemas }>>
) =>
| Response
| RequestContext<Route, { schemas: Schemas }>
| Promise<Response | Prettify<RequestContext<Route, { schemas: Schemas }>>>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

MiddlewareFunction's second generic shape changed — this is a breaking change for direct users.

Previously the second generic accepted a config-like wrapper (e.g. { schemas: ... }); it now takes EndpointSchemas directly. Tests have been updated, but external code that types middleware as MiddlewareFunction<Route, { schemas: { body: ... } }> will silently start to mean something different (the wrapper would be treated as EndpointSchemas and not satisfy the constraint). Worth a CHANGELOG / migration note.

🤖 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 `@src/`@types/types.ts around lines 144 - 149, The exported MiddlewareFunction
signature changed its second generic from the old config-wrapper shape to take
EndpointSchemas directly, which is a breaking API change; revert or restore a
backwards-compatible overload so MiddlewareFunction<Route, { schemas: ... }>
continues to work: either restore the original generic shape that expects
RequestContext<Route, { schemas: Schemas }> (using the existing types
RequestContext, RoutePattern and EndpointSchemas) or add an overload/alias that
maps the wrapper form to the new shape, and add a CHANGELOG/migration note
documenting the change and recommended typings for external users.

@halvaradop halvaradop merged commit 093f602 into master May 8, 2026
6 checks passed
@halvaradop halvaradop deleted the perf/optimize-types branch May 8, 2026 14:50
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