From 3a8e066d577bfb7d9645467aa27c08655904f30d Mon Sep 17 00:00:00 2001 From: unnoq Date: Fri, 6 Jun 2025 15:33:37 +0700 Subject: [PATCH] feat(server): inferRPCMethodFromRouter util --- packages/client/src/utils.ts | 2 +- .../server/src/adapters/standard/utils.ts | 2 +- packages/server/src/index.ts | 1 + packages/server/src/link-utils.test-d.ts | 10 +++++++ packages/server/src/link-utils.test.ts | 24 +++++++++++++++++ packages/server/src/link-utils.ts | 26 +++++++++++++++++++ 6 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 packages/server/src/link-utils.test-d.ts create mode 100644 packages/server/src/link-utils.test.ts create mode 100644 packages/server/src/link-utils.ts diff --git a/packages/client/src/utils.ts b/packages/client/src/utils.ts index 6417190bd..1775d0d74 100644 --- a/packages/client/src/utils.ts +++ b/packages/client/src/utils.ts @@ -45,6 +45,6 @@ export async function safe(promise: ClientProm export function resolveFriendlyClientOptions(options: FriendlyClientOptions): ClientOptions { return { ...options, - context: options?.context ?? {} as T, // Context only optional if all fields are optional + context: options.context ?? {} as T, // Context only optional if all fields are optional } } diff --git a/packages/server/src/adapters/standard/utils.ts b/packages/server/src/adapters/standard/utils.ts index bdd82dbcb..b3c6bbd8d 100644 --- a/packages/server/src/adapters/standard/utils.ts +++ b/packages/server/src/adapters/standard/utils.ts @@ -8,6 +8,6 @@ export type FriendlyStandardHandleOptions = export function resolveFriendlyStandardHandleOptions(options: FriendlyStandardHandleOptions): StandardHandleOptions { return { ...options, - context: options?.context ?? {} as T, // Context only optional if all fields are optional + context: options.context ?? {} as T, // Context only optional if all fields are optional } } diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 6d9e7e6b9..d53c855e5 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -7,6 +7,7 @@ export * from './implementer' export * from './implementer-procedure' export * from './implementer-variants' export * from './lazy' +export * from './link-utils' export * from './middleware' export * from './middleware-decorated' export * from './middleware-utils' diff --git a/packages/server/src/link-utils.test-d.ts b/packages/server/src/link-utils.test-d.ts new file mode 100644 index 000000000..64309f971 --- /dev/null +++ b/packages/server/src/link-utils.test-d.ts @@ -0,0 +1,10 @@ +import { RPCLink } from '@orpc/client/fetch' +import { router } from '../tests/shared' +import { inferRPCMethodFromRouter } from './link-utils' + +it('inferRPCMethodFromContractRouter', () => { + const link = new RPCLink({ + url: 'http://localhost:3000/rpc', + method: inferRPCMethodFromRouter(router), + }) +}) diff --git a/packages/server/src/link-utils.test.ts b/packages/server/src/link-utils.test.ts new file mode 100644 index 000000000..c2fd8d19f --- /dev/null +++ b/packages/server/src/link-utils.test.ts @@ -0,0 +1,24 @@ +import { os } from './builder' +import { inferRPCMethodFromRouter } from './link-utils' + +it('inferRPCMethodFromRouter', async () => { + const method = inferRPCMethodFromRouter({ + head: os.route({ method: 'HEAD' }).handler(() => {}), + get: os.route({ method: 'GET' }).handler(() => { }), + post: os.route({}).handler(() => { }), + nested: os.lazy(() => Promise.resolve({ + default: { + get: os.lazy(() => Promise.resolve({ default: os.route({ method: 'GET' }).handler(() => { }) })), + delete: os.route({ method: 'DELETE' }).handler(() => { }), + }, + })), + }) + + expect(await method({}, ['head'])).toBe('GET') + expect(await method({}, ['get'])).toBe('GET') + expect(await method({}, ['post'])).toBe('POST') + expect(await method({}, ['nested', 'get'])).toBe('GET') + expect(await method({}, ['nested', 'delete'])).toBe('DELETE') + + await expect(method({}, ['nested', 'not-exist'])).rejects.toThrow(/No valid procedure found at path/) +}) diff --git a/packages/server/src/link-utils.ts b/packages/server/src/link-utils.ts new file mode 100644 index 000000000..67e88854a --- /dev/null +++ b/packages/server/src/link-utils.ts @@ -0,0 +1,26 @@ +import type { HTTPMethod } from '@orpc/client' +import type { AnyRouter } from './router' +import { fallbackContractConfig } from '@orpc/contract' +import { unlazy } from './lazy' +import { isProcedure } from './procedure' +import { getRouter } from './router-utils' + +/** + * Help RPCLink automatically send requests using the specified HTTP method in the router. + */ +export function inferRPCMethodFromRouter(router: AnyRouter): (options: unknown, path: readonly string[]) => Promise> { + return async (_, path) => { + const { default: procedure } = await unlazy(getRouter(router, path)) + + if (!isProcedure(procedure)) { + throw new Error( + `[inferRPCMethodFromRouter] No valid procedure found at path "${path.join('.')}". ` + + `This may happen when the router is not properly configured.`, + ) + } + + const method = fallbackContractConfig('defaultMethod', procedure['~orpc'].route.method) + + return method === 'HEAD' ? 'GET' : method + } +}