Skip to content
30 changes: 30 additions & 0 deletions packages/server/src/router-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,36 @@ describe('traverseContractProcedures', () => {
path: ['nested', 'pong'],
})
})

it('with callable procedures', () => {
const callablePing = Object.assign(() => {}, {
'~orpc': ping['~orpc'],
})

Comment thread
dinwwwh marked this conversation as resolved.
const callablePong = Object.assign(() => {}, {
'~orpc': pong['~orpc'],
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const router = { ping: callablePing, nested: { pong: callablePong } }

const callback = vi.fn()
expect(traverseContractProcedures({
router,
path: [],
}, callback)).toEqual([])

expect(callback).toHaveBeenCalledTimes(2)

expect(callback).toHaveBeenNthCalledWith(1, {
contract: router.ping,
path: ['ping'],
})

expect(callback).toHaveBeenNthCalledWith(2, {
contract: router.nested.pong,
path: ['nested', 'pong'],
})
})
})

it('resolveContractProcedures', async () => {
Expand Down
14 changes: 5 additions & 9 deletions packages/server/src/router-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { AnyMiddleware } from './middleware'
import type { AnyProcedure } from './procedure'
import type { AnyRouter } from './router'
import { enhanceRoute, isContractProcedure, mergeErrorMap, mergePrefix } from '@orpc/contract'
import { isTypescriptObject } from '@orpc/shared'
import { getLazyMeta, isLazy, lazy, unlazy } from './lazy'
import { mergeMiddlewares } from './middleware-utils'
import { isProcedure, Procedure } from './procedure'
Expand All @@ -27,7 +28,7 @@ export function getRouter<T extends Lazyable<AnyRouter | undefined>>(
return undefined as any
}

if (typeof current !== 'object') {
if (!isTypescriptObject(current)) {
Comment thread
dinwwwh marked this conversation as resolved.
return undefined as any
}

Expand Down Expand Up @@ -192,16 +193,11 @@ export function traverseContractProcedures(
callback: (options: TraverseContractProcedureCallbackOptions) => void,
lazyOptions: LazyTraverseContractProceduresOptions[] = [],
): LazyTraverseContractProceduresOptions[] {
// Guard before reading the hidden-contract symbol so that null/undefined
// child exports don't crash in `getHiddenRouterContract`. Primitives like
// strings autobox safely; only null/undefined throw on symbol access.
if (typeof options.router !== 'object' || options.router === null) {
return lazyOptions
}

let currentRouter: AnyContractRouter | Lazyable<AnyRouter> = options.router

const hiddenContract = getHiddenRouterContract(options.router)
const hiddenContract = isTypescriptObject(options.router)
? getHiddenRouterContract(options.router)
: undefined

if (hiddenContract !== undefined) {
currentRouter = hiddenContract
Expand Down
Loading