Skip to content

feat(server,contract): guard against primitive values in router tree traversal#1522

Merged
dinwwwh merged 6 commits intomiddleapi:mainfrom
jameskranz:fix/enhance-router-primitive-recursion-v2
Apr 9, 2026
Merged

feat(server,contract): guard against primitive values in router tree traversal#1522
dinwwwh merged 6 commits intomiddleapi:mainfrom
jameskranz:fix/enhance-router-primitive-recursion-v2

Conversation

@jameskranz
Copy link
Copy Markdown
Contributor

@jameskranz jameskranz commented Apr 3, 2026

Summary

  • Router utility functions that use for (const key in router) loops without validating that values are objects crash with RangeError: Maximum call stack size exceeded when a router module exports primitives (e.g., export const FOO = "bar").
  • Server package: Added type guards to enhanceRouter, traverseContractProcedures, and unlazyRouter in packages/server/src/router-utils.ts.
  • Contract package: Applied the same fix to enhanceContractRouter, minifyContractRouter, and populateContractRouterPaths in packages/contract/src/router-utils.ts.
  • Added tests covering strings, single-character strings, numbers, booleans, null, and undefined for all affected functions in both packages.

Test plan

  • All new tests pass (server + contract packages)
  • All existing tests continue to pass
  • Verified tests fail without the fix (infinite recursion crashes the test worker)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Router and contract utilities now safely handle primitive and non-object inputs, preventing erroneous traversal, stack/recursion issues, and preserving primitive exports (strings/numbers/booleans/null/undefined).
  • Tests

    • Added tests covering mixed exports and primitive-only cases (including single-character string edge cases) to verify procedures are preserved and primitives remain unchanged.

jameskranz and others added 2 commits April 3, 2026 11:18
`enhanceRouter`, `traverseContractProcedures`, and `unlazyRouter` all
use `for (const key in router)` without verifying the value is an
object. When a router module re-exports a primitive (e.g.
`export const FOO = 'bar'`), the `for...in` loop iterates string
character indices, eventually hitting a single-character string that
recurses on itself infinitely — crashing with RangeError: Maximum call
stack size exceeded.

Add type guards before the `for...in` fallback in all three functions
so that non-object values are returned/skipped instead of iterated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use .toEqual() with expected structure instead of .toBeDefined()
for the unlazyRouter primitive-value test case.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. bug Something isn't working javascript Pull requests that update javascript code labels Apr 3, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces safety guards in packages/server/src/router-utils.ts to prevent infinite recursion when traversing router trees that contain non-object primitive values. It also adds descriptive JSDoc comments to several utility functions and includes a new suite of tests to verify correct handling of various primitive types. Feedback suggests that similar logic in the contract package should also be updated to prevent potential crashes in those modules.

@jameskranz jameskranz force-pushed the fix/enhance-router-primitive-recursion-v2 branch from 32952fd to 92ffa80 Compare April 3, 2026 18:22
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 3, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added runtime guards to server and contract router utilities to prevent traversal into non-object (primitive or null) exports; added tests in both packages verifying procedures are augmented while primitive exports are left unchanged, including single-character string edge cases.

Changes

Cohort / File(s) Summary
Server router utils
packages/server/src/router-utils.ts
Added early-return guards: getRouter, enhanceRouter, unlazyRouter return non-object/null inputs unchanged; traverseContractProcedures only recurses when the current value is a non-null object.
Server router tests
packages/server/src/router-utils.test.ts
New tests asserting procedure exports receive ~orpc metadata, primitive exports (string, single-char string, number, boolean, null, undefined) remain unchanged, traversal skips primitives, and no throw/infinite traversal occurs for single-char strings.
Contract router utils
packages/contract/src/router-utils.ts
Added early-return guards: getContractRouter, enhanceContractRouter, minifyContractRouter, populateContractRouterPaths return non-object/null inputs unchanged; preserved procedure-first checks.
Contract router tests
packages/contract/src/router-utils.test.ts
New tests verifying contract utilities preserve primitives, identify contract procedures, populate ~orpc.route.path for procedures, and safely handle single-character string exports (rejecting indexed-character traversal).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • longnguyen2004

Poem

🐇 I nibbled keys and peeked inside,
Found tiny values where primitives hide,
I halted hops that once would glide,
Kept procedures tagged, calm and wide,
A carrot cheer for guarded stride 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main change: adding guards against primitive values in router tree traversal across both server and contract packages.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@jameskranz jameskranz force-pushed the fix/enhance-router-primitive-recursion-v2 branch from 92ffa80 to 24438df Compare April 3, 2026 18:24
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.

🧹 Nitpick comments (2)
packages/contract/src/router-utils.test.ts (2)

111-129: Consider adding boolean and null/undefined tests for consistency.

enhanceContractRouter tests cover boolean and null/undefined values (lines 98-108), but this suite doesn't. While the guard implementation handles all primitive types uniformly, adding these tests would ensure consistent coverage across all three functions.

💡 Suggested additional test cases
   it('handles number values without infinite recursion', () => {
     const routerWithNumber = { ping, VERSION: 42 } as any
     const minified = minifyContractRouter(routerWithNumber)
     expect(isContractProcedure((minified as any).ping)).toBe(true)
   })
+
+  it('handles boolean values without infinite recursion', () => {
+    const routerWithBool = { ping, ENABLED: true } as any
+    const minified = minifyContractRouter(routerWithBool)
+    expect(isContractProcedure((minified as any).ping)).toBe(true)
+  })
+
+  it('handles null and undefined values without infinite recursion', () => {
+    const routerWithNullish = { ping, NIL: null, UNDEF: undefined } as any
+    const minified = minifyContractRouter(routerWithNullish)
+    expect(isContractProcedure((minified as any).ping)).toBe(true)
+  })
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/contract/src/router-utils.test.ts` around lines 111 - 129, Add tests
to the "minifyContractRouter with primitive values" suite to cover boolean and
null/undefined cases similar to the existing string/number tests: create router
objects like { ping, FLAG: true } and { ping, MISSING: null } (or undefined),
call minifyContractRouter on each, and assert isContractProcedure((minified as
any).ping) is true; place the tests as additional it(...) blocks alongside the
existing ones to ensure parity with enhanceContractRouter coverage.

131-149: Same suggestion: consider adding boolean and null/undefined tests.

For the same consistency reason as minifyContractRouter, adding tests for boolean and null/undefined would complete the coverage parity with enhanceContractRouter.

💡 Suggested additional test cases
   it('handles number values without infinite recursion', () => {
     const routerWithNumber = { ping: oc.input(inputSchema), VERSION: 42 } as any
     const populated = populateContractRouterPaths(routerWithNumber)
     expect(isContractProcedure(populated.ping)).toBe(true)
   })
+
+  it('handles boolean values without infinite recursion', () => {
+    const routerWithBool = { ping: oc.input(inputSchema), ENABLED: true } as any
+    const populated = populateContractRouterPaths(routerWithBool)
+    expect(isContractProcedure(populated.ping)).toBe(true)
+  })
+
+  it('handles null and undefined values without infinite recursion', () => {
+    const routerWithNullish = { ping: oc.input(inputSchema), NIL: null, UNDEF: undefined } as any
+    const populated = populateContractRouterPaths(routerWithNullish)
+    expect(isContractProcedure(populated.ping)).toBe(true)
+  })
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/contract/src/router-utils.test.ts` around lines 131 - 149, Add tests
to populateContractRouterPaths to cover boolean and null/undefined primitive
values similar to the existing string/number tests: create router variations
like { ping: oc.input(inputSchema), FLAG: true } and { ping:
oc.input(inputSchema), MISSING: null } (and undefined), call
populateContractRouterPaths on each, and assert
expect(isContractProcedure(populated.ping)).toBe(true). Mirror the style and
naming of the current cases so coverage matches minifyContractRouter and
enhanceContractRouter.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/contract/src/router-utils.test.ts`:
- Around line 111-129: Add tests to the "minifyContractRouter with primitive
values" suite to cover boolean and null/undefined cases similar to the existing
string/number tests: create router objects like { ping, FLAG: true } and { ping,
MISSING: null } (or undefined), call minifyContractRouter on each, and assert
isContractProcedure((minified as any).ping) is true; place the tests as
additional it(...) blocks alongside the existing ones to ensure parity with
enhanceContractRouter coverage.
- Around line 131-149: Add tests to populateContractRouterPaths to cover boolean
and null/undefined primitive values similar to the existing string/number tests:
create router variations like { ping: oc.input(inputSchema), FLAG: true } and {
ping: oc.input(inputSchema), MISSING: null } (and undefined), call
populateContractRouterPaths on each, and assert
expect(isContractProcedure(populated.ping)).toBe(true). Mirror the style and
naming of the current cases so coverage matches minifyContractRouter and
enhanceContractRouter.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b4523cde-6f58-4035-85e1-37ca5c843057

📥 Commits

Reviewing files that changed from the base of the PR and between 24438df and 9167d2b.

📒 Files selected for processing (2)
  • packages/contract/src/router-utils.test.ts
  • packages/contract/src/router-utils.ts

Apply the same primitive type guards to enhanceContractRouter,
minifyContractRouter, and populateContractRouterPaths in the contract
package. These functions have the same infinite recursion vulnerability
when router modules export string values alongside contract procedures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jameskranz jameskranz force-pushed the fix/enhance-router-primitive-recursion-v2 branch from 9167d2b to f2fc836 Compare April 3, 2026 18:38
@jameskranz jameskranz changed the title fix: guard against primitive values in router tree traversal fix(server,contract): guard against primitive values in router tree traversal Apr 3, 2026
@dinwwwh
Copy link
Copy Markdown
Member

dinwwwh commented Apr 3, 2026

But why is this necessary? Why doesn't TypeScript warn you that the oRPC router is invalid when it contains non-procedure properties?

@jameskranz
Copy link
Copy Markdown
Contributor Author

But why is this necessary? Why doesn't TypeScript warn you that the oRPC router is invalid when it contains non-procedure properties?

The Router mapped type does map non-procedure properties to never, so in theory TypeScript should catch this. But it doesn't when you use import * as module to compose routers -- the namespace type is wide enough to slip past the constraint, and a few internal .router() paths cast through as any.

We've hit this twice in our codebase. Both times we were using import * as namespace imports, which is pretty much the standard way to compose routers from module files. Once it was a string constant exported alongside procedures, and once it was an exported object with circular internals. No TypeScript errors either time.

Since the type system has this gap with namespace imports, it seems like the runtime should just skip non-procedure values instead of blowing up the stack. The change itself is pretty small -- just a typeof check before the for...in fallback, same pattern traverseContractProcedures already uses.

@dinwwwh
Copy link
Copy Markdown
Member

dinwwwh commented Apr 4, 2026

@jameskranz This PR doesn't actually fix the circular object case mentioned. The typeof !== 'object' guard only handles primitives; a circular reference still passes through and causes the same stack overflow.

The as any cast also feels like an antipattern here since it suppresses the type error rather than fixing the root cause.

A cleaner approach would be filtering non-procedure exports at the boundary before they enter the router at all:

function filterInternalExport<T extends Record<any, any>>(module: T): T {
  return Object.fromEntries(
    Object.entries(module).filter(([, v]) => return false for internal exports)
  )
}

// Usage
import * as userModule from './user'
const router = { user: filterInternalExport(userModule) }

@jameskranz
Copy link
Copy Markdown
Contributor Author

jameskranz commented Apr 4, 2026

@jameskranz This PR doesn't actually fix the circular object case mentioned. The typeof !== 'object' guard only handles primitives; a circular reference still passes through and causes the same stack overflow.

The as any cast also feels like an antipattern here since it suppresses the type error rather than fixing the root cause.

A cleaner approach would be filtering non-procedure exports at the boundary before they enter the router at all:

function filterInternalExport<T extends Record<any, any>>(module: T): T {
  return Object.fromEntries(
    Object.entries(module).filter(([, v]) => return false for internal exports)
  )
}

// Usage
import * as userModule from './user'
const router = { user: filterInternalExport(userModule) }

You're right that this doesn't handle circular objects -- that would need visited-set tracking, which is a bigger change. But those are pretty different problems. Circular references in plain objects are rare and arguably a bug in the consumer's code. Exporting a string constant next to procedures is just normal module authoring, and it shouldn't take down the process.

On the 'as any' -- those casts are all pre-existing in router-utils.ts, not something this PR introduces. Every branch in enhanceRouter already returns as any (lazy, procedure, object iteration). The primitive guard just follows the same convention. Definitely worth cleaning up but feels like separate work.

The filter approach works but it means every consumer needs to know that namespace imports are unsafe and wrap them before passing to .router(). That filter also needs to know what to keep, which basically means checking isProcedure, at which point it makes more sense to do that inside the library where you already have access to those checks.

If you'd rather go that route -- skipping non-procedure values with isProcedure/isContractProcedure checks instead of the primitive guard -- I'm open to reworking the PR around that. It'd handle both primitives and unexpected objects, and it's more precise about what actually gets iterated.

@dinwwwh
Copy link
Copy Markdown
Member

dinwwwh commented Apr 4, 2026

Circular references in plain objects are rare and arguably a bug in the consumer's code

export a primitive value and import it as a router is rare too, so both cases are equally edge cases.

For the filter approach, oRPC exports almost everything already so this use case is too specific to justify adding it to the library.

Honestly I think the real fix is just being explicit about what goes into the router rather than relying on whole module exports:

import * as userModule from './user'
const router = { user: { procedure1: userModule.procedure1, procedure2: userModule.procedure2 } }

If your module exports more than what the router needs, that's a signal to be explicit rather than relying on the library to silently filter things out.

@jameskranz
Copy link
Copy Markdown
Contributor Author

Circular references in plain objects are rare and arguably a bug in the consumer's code

export a primitive value and import it as a router is rare too, so both cases are equally edge cases.

For the filter approach, oRPC exports almost everything already so this use case is too specific to justify adding it to the library.

Honestly I think the real fix is just being explicit about what goes into the router rather than relying on whole module exports:

import * as userModule from './user'
const router = { user: { procedure1: userModule.procedure1, procedure2: userModule.procedure2 } }

If your module exports more than what the router needs, that's a signal to be explicit rather than relying on the library to silently filter things out.

I hear where you're coming from on keeping the library core lean, but I think "Explicit Property Mapping" shifts a heavy maintenance burden onto the consumer that scales poorly. In our project, we’re managing over 50 modules and hundreds of procedures. If we have to manually map every single one in both the main contract and the implementation router, we’re essentially creating a redundant registry that has to be kept in sync by hand. It’s a constant point of failure—every time a dev adds a procedure, they have to remember to update two other files, or the client-side types just silently break.

Namespace imports are a standard way to compose large systems in TypeScript. The fact that oRPC hits a stack overflow because of a stray constant suggests the traversal logic is a bit too trusting of the input shape. Instead of the library crashing because it found something it didn't expect, it'd be much more robust to just ignore anything that isn't a procedure or a nested router.

By using isProcedure or isContractProcedure during traversal, we move to an allow-list approach. The library becomes defensive against any non-oRPC exports—whether that's a version string, a helper function, or even a circular reference—without forcing users into hundreds of lines of boilerplate. It seems like a win for stability without adding meaningful complexity to the codebase.

@dinwwwh dinwwwh changed the title fix(server,contract): guard against primitive values in router tree traversal feat(server,contract): guard against primitive values in router tree traversal Apr 8, 2026
dinwwwh
dinwwwh previously approved these changes Apr 8, 2026
@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Apr 8, 2026
@dinwwwh
Copy link
Copy Markdown
Member

dinwwwh commented Apr 8, 2026

I believe to support your case we need improve https://github.com/middleapi/orpc/blob/main/packages/server/src/router-utils.ts#L31 too

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 8, 2026

More templates

@orpc/ai-sdk

npm i https://pkg.pr.new/@orpc/ai-sdk@1522

@orpc/arktype

npm i https://pkg.pr.new/@orpc/arktype@1522

@orpc/client

npm i https://pkg.pr.new/@orpc/client@1522

@orpc/contract

npm i https://pkg.pr.new/@orpc/contract@1522

@orpc/experimental-durable-iterator

npm i https://pkg.pr.new/@orpc/experimental-durable-iterator@1522

@orpc/hey-api

npm i https://pkg.pr.new/@orpc/hey-api@1522

@orpc/interop

npm i https://pkg.pr.new/@orpc/interop@1522

@orpc/json-schema

npm i https://pkg.pr.new/@orpc/json-schema@1522

@orpc/nest

npm i https://pkg.pr.new/@orpc/nest@1522

@orpc/openapi

npm i https://pkg.pr.new/@orpc/openapi@1522

@orpc/openapi-client

npm i https://pkg.pr.new/@orpc/openapi-client@1522

@orpc/otel

npm i https://pkg.pr.new/@orpc/otel@1522

@orpc/experimental-pino

npm i https://pkg.pr.new/@orpc/experimental-pino@1522

@orpc/experimental-publisher

npm i https://pkg.pr.new/@orpc/experimental-publisher@1522

@orpc/experimental-publisher-durable-object

npm i https://pkg.pr.new/@orpc/experimental-publisher-durable-object@1522

@orpc/experimental-ratelimit

npm i https://pkg.pr.new/@orpc/experimental-ratelimit@1522

@orpc/react

npm i https://pkg.pr.new/@orpc/react@1522

@orpc/react-query

npm i https://pkg.pr.new/@orpc/react-query@1522

@orpc/experimental-react-swr

npm i https://pkg.pr.new/@orpc/experimental-react-swr@1522

@orpc/server

npm i https://pkg.pr.new/@orpc/server@1522

@orpc/shared

npm i https://pkg.pr.new/@orpc/shared@1522

@orpc/solid-query

npm i https://pkg.pr.new/@orpc/solid-query@1522

@orpc/standard-server

npm i https://pkg.pr.new/@orpc/standard-server@1522

@orpc/standard-server-aws-lambda

npm i https://pkg.pr.new/@orpc/standard-server-aws-lambda@1522

@orpc/standard-server-fastify

npm i https://pkg.pr.new/@orpc/standard-server-fastify@1522

@orpc/standard-server-fetch

npm i https://pkg.pr.new/@orpc/standard-server-fetch@1522

@orpc/standard-server-node

npm i https://pkg.pr.new/@orpc/standard-server-node@1522

@orpc/standard-server-peer

npm i https://pkg.pr.new/@orpc/standard-server-peer@1522

@orpc/svelte-query

npm i https://pkg.pr.new/@orpc/svelte-query@1522

@orpc/tanstack-query

npm i https://pkg.pr.new/@orpc/tanstack-query@1522

@orpc/trpc

npm i https://pkg.pr.new/@orpc/trpc@1522

@orpc/valibot

npm i https://pkg.pr.new/@orpc/valibot@1522

@orpc/vue-colada

npm i https://pkg.pr.new/@orpc/vue-colada@1522

@orpc/vue-query

npm i https://pkg.pr.new/@orpc/vue-query@1522

@orpc/zod

npm i https://pkg.pr.new/@orpc/zod@1522

commit: b76d344

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 8, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

- Add typeof guard to getRouter and getContractRouter, mirroring the
  pattern used by enhance/minify/populate/unlazy in this PR. Without
  it, traversal walks character indices ('v'[0] === 'v') and returns
  garbage instead of undefined.
- Replace inferred-union test bindings with precise type casts so
  property assertions are statically checked instead of any-cast.
- Add primitive-traversal coverage for getRouter / getContractRouter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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 (1)
packages/server/src/router-utils.test.ts (1)

253-266: Primitive passthrough assertions are incomplete.

moduleWithPrimitives includes DEPRECATED and OPTIONAL_FEATURE, but these are not asserted in enhanceRouter and unlazyRouter tests. Add explicit checks to lock in null/undefined preservation behavior.

Proposed test assertions
@@
       expect(enhanced.API_VERSION).toBe('v2')
       expect(enhanced.MAX_PAGE_SIZE).toBe(100)
       expect(enhanced.ENABLE_CACHE).toBe(true)
+      expect(enhanced.DEPRECATED).toBeNull()
+      expect(enhanced.OPTIONAL_FEATURE).toBeUndefined()
@@
       expect(result.getUser).toEqual(pong)
       expect(result.listUsers).toEqual(pong)
       expect(result.API_VERSION).toBe('v2')
       expect(result.MAX_PAGE_SIZE).toBe(100)
+      expect(result.ENABLE_CACHE).toBe(true)
+      expect(result.DEPRECATED).toBeNull()
+      expect(result.OPTIONAL_FEATURE).toBeUndefined()

Also applies to: 312-318

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/router-utils.test.ts` around lines 253 - 266, Update the
tests for enhanceRouter and unlazyRouter to assert that primitive exports with
null/undefined are preserved: locate the test cases using moduleWithPrimitives
and add explicit expectations that DEPRECATED is null and OPTIONAL_FEATURE is
undefined (or the exact values present in moduleWithPrimitives) after calling
enhanceRouter and unlazyRouter; reference the enhanced variables (e.g.,
enhanced.DEPRECATED, enhanced.OPTIONAL_FEATURE and similarly for the result of
unlazyRouter) so the tests lock in null/undefined passthrough behavior alongside
the existing API_VERSION / MAX_PAGE_SIZE / ENABLE_CACHE assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/server/src/router-utils.test.ts`:
- Around line 294-305: Test is excluding null/undefined crash path; update
traverseContractProcedures test to include cases where exported values can be
null/undefined and then fix the traversal order in
packages/server/src/router-utils.ts so the type guard runs before accessing
hidden contract data. Specifically, in traverseContractProcedures ensure the
guard that checks for non-null/undefined and proper shape runs before any call
to getHiddenRouterContract (or any access of hiddenContract properties) and
adjust the iteration order in the traverseContractProcedures implementation so
getHiddenRouterContract is only invoked after the guard passes (refer to
function names traverseContractProcedures and getHiddenRouterContract to locate
the logic). Ensure tests reflect the real failure mode by adding an export like
export const X = null and asserting traversal skips or handles it without
throwing.

---

Nitpick comments:
In `@packages/server/src/router-utils.test.ts`:
- Around line 253-266: Update the tests for enhanceRouter and unlazyRouter to
assert that primitive exports with null/undefined are preserved: locate the test
cases using moduleWithPrimitives and add explicit expectations that DEPRECATED
is null and OPTIONAL_FEATURE is undefined (or the exact values present in
moduleWithPrimitives) after calling enhanceRouter and unlazyRouter; reference
the enhanced variables (e.g., enhanced.DEPRECATED, enhanced.OPTIONAL_FEATURE and
similarly for the result of unlazyRouter) so the tests lock in null/undefined
passthrough behavior alongside the existing API_VERSION / MAX_PAGE_SIZE /
ENABLE_CACHE assertions.
🪄 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: 5cd64dea-2ff2-4a19-8ac3-a8db5d04912d

📥 Commits

Reviewing files that changed from the base of the PR and between f2fc836 and 92b7eab.

📒 Files selected for processing (4)
  • packages/contract/src/router-utils.test.ts
  • packages/contract/src/router-utils.ts
  • packages/server/src/router-utils.test.ts
  • packages/server/src/router-utils.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/contract/src/router-utils.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/contract/src/router-utils.test.ts
  • packages/server/src/router-utils.ts

@jameskranz
Copy link
Copy Markdown
Contributor Author

Addressed the getRouter improvement. Same typeof guard pattern, plus the parity fix on getContractRouter. Tests cover the worst-case single-character export for both.

…exports

getHiddenRouterContract reads a symbol property and throws on null or
undefined. Recursing into a child export of `null` (e.g.
`export const X = null`) crashed before any guard ran. Move the
typeof/null guard above the hidden-contract lookup, and update the test
to use a fixture containing null/undefined so the crash path is covered.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…sertions

The shape assertions on the return values of enhanceContractRouter,
populateContractRouterPaths, and enhanceRouter target a concrete object
shape, but the functions return a union with the procedure type, which
doesn't structurally overlap. tsc rejects the direct cast (TS2352) and
suggests routing through unknown — applying that at the three sites.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jameskranz
Copy link
Copy Markdown
Contributor Author

hopefully final fix for lint. all pass locally

@dinwwwh dinwwwh merged commit 99d5d75 into middleapi:main Apr 9, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working javascript Pull requests that update javascript code lgtm This PR has been approved by a maintainer size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants