Skip to content

Commit 7605f6c

Browse files
dinwwwhgithub-actions[bot]gemini-code-assist[bot]
authored
fix(server): routing skips callable/actionable procedures (#1551)
Fixes #1545 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Added test coverage verifying traversal correctly finds callable procedures augmented with metadata, including nested paths. * **Refactor** * Improved router-node validation during traversal to use a stricter object check while preserving traversal behavior into nested routes. [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/middleapi/orpc/pull/1551) <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 0010bec commit 7605f6c

2 files changed

Lines changed: 35 additions & 9 deletions

File tree

packages/server/src/router-utils.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,36 @@ describe('traverseContractProcedures', () => {
176176
path: ['nested', 'pong'],
177177
})
178178
})
179+
180+
it('with callable procedures', () => {
181+
const callablePing = Object.assign(() => {}, {
182+
'~orpc': ping['~orpc'],
183+
})
184+
185+
const callablePong = Object.assign(() => {}, {
186+
'~orpc': pong['~orpc'],
187+
})
188+
189+
const router = { ping: callablePing, nested: { pong: callablePong } }
190+
191+
const callback = vi.fn()
192+
expect(traverseContractProcedures({
193+
router,
194+
path: [],
195+
}, callback)).toEqual([])
196+
197+
expect(callback).toHaveBeenCalledTimes(2)
198+
199+
expect(callback).toHaveBeenNthCalledWith(1, {
200+
contract: router.ping,
201+
path: ['ping'],
202+
})
203+
204+
expect(callback).toHaveBeenNthCalledWith(2, {
205+
contract: router.nested.pong,
206+
path: ['nested', 'pong'],
207+
})
208+
})
179209
})
180210

181211
it('resolveContractProcedures', async () => {

packages/server/src/router-utils.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { AnyMiddleware } from './middleware'
55
import type { AnyProcedure } from './procedure'
66
import type { AnyRouter } from './router'
77
import { enhanceRoute, isContractProcedure, mergeErrorMap, mergePrefix } from '@orpc/contract'
8+
import { isTypescriptObject } from '@orpc/shared'
89
import { getLazyMeta, isLazy, lazy, unlazy } from './lazy'
910
import { mergeMiddlewares } from './middleware-utils'
1011
import { isProcedure, Procedure } from './procedure'
@@ -27,7 +28,7 @@ export function getRouter<T extends Lazyable<AnyRouter | undefined>>(
2728
return undefined as any
2829
}
2930

30-
if (typeof current !== 'object') {
31+
if (!isTypescriptObject(current)) {
3132
return undefined as any
3233
}
3334

@@ -192,16 +193,11 @@ export function traverseContractProcedures(
192193
callback: (options: TraverseContractProcedureCallbackOptions) => void,
193194
lazyOptions: LazyTraverseContractProceduresOptions[] = [],
194195
): LazyTraverseContractProceduresOptions[] {
195-
// Guard before reading the hidden-contract symbol so that null/undefined
196-
// child exports don't crash in `getHiddenRouterContract`. Primitives like
197-
// strings autobox safely; only null/undefined throw on symbol access.
198-
if (typeof options.router !== 'object' || options.router === null) {
199-
return lazyOptions
200-
}
201-
202196
let currentRouter: AnyContractRouter | Lazyable<AnyRouter> = options.router
203197

204-
const hiddenContract = getHiddenRouterContract(options.router)
198+
const hiddenContract = isTypescriptObject(options.router)
199+
? getHiddenRouterContract(options.router)
200+
: undefined
205201

206202
if (hiddenContract !== undefined) {
207203
currentRouter = hiddenContract

0 commit comments

Comments
 (0)