diff --git a/packages/server/src/builder.ts b/packages/server/src/builder.ts index 90602ee0..957f773a 100644 --- a/packages/server/src/builder.ts +++ b/packages/server/src/builder.ts @@ -244,7 +244,7 @@ export class Builder { ? LazyRouter : never { return createLazyProcedureOrLazyRouter({ - load: async () => (await load()).default, + load: () => load().then(m => m.default), middlewares: this.zz$b.middlewares, }) } diff --git a/packages/server/src/procedure-lazy.ts b/packages/server/src/procedure-lazy.ts index d59ce172..19a01c75 100644 --- a/packages/server/src/procedure-lazy.ts +++ b/packages/server/src/procedure-lazy.ts @@ -56,7 +56,7 @@ export function decorateLazyProcedure< TFuncOutput extends SchemaOutput, >( options: DecorateLazyProcedureOptions, -) { +): DecoratedLazyProcedure { const lazyProcedure: LazyProcedure = { [LAZY_PROCEDURE_SYMBOL]: { load: async () => { diff --git a/packages/server/src/router-builder.ts b/packages/server/src/router-builder.ts index 29a61849..024af5d4 100644 --- a/packages/server/src/router-builder.ts +++ b/packages/server/src/router-builder.ts @@ -1,3 +1,5 @@ +import type { DecoratedProcedure, Procedure } from './procedure' +import type { DecoratedLazyProcedure, LazyProcedure } from './procedure-lazy' import type { HandledRouter, Router } from './router' import type { LazyRouter } from './router-lazy' import type { Context, MergeContext } from './types' @@ -8,6 +10,7 @@ import { type Middleware, } from './middleware' import { decorateProcedure, isProcedure } from './procedure' +import { decorateLazyProcedure, isLazyProcedure, LAZY_PROCEDURE_SYMBOL } from './procedure-lazy' import { createLazyProcedureOrLazyRouter } from './router-lazy' export class RouterBuilder< @@ -84,42 +87,12 @@ export class RouterBuilder< router>( router: URouter, ): HandledRouter { - const handled: Router = {} - - for (const key in router) { - const item = router[key] - - if (isProcedure(item)) { - const builderMiddlewares = this.zz$rb.middlewares ?? [] - const itemMiddlewares = item.zz$p.middlewares ?? [] - - const middlewares = [ - ...builderMiddlewares, - ...itemMiddlewares.filter( - item => !builderMiddlewares.includes(item), - ), - ] - - const contract = DecoratedContractProcedure.decorate( - item.zz$p.contract, - ).addTags(...(this.zz$rb.tags ?? [])) - - handled[key] = decorateProcedure({ - zz$p: { - ...item.zz$p, - contract: this.zz$rb.prefix - ? contract.prefix(this.zz$rb.prefix) - : contract, - middlewares, - }, - }) - } - else { - handled[key] = this.router(item as any) - } - } - - return handled as HandledRouter + return createRouterInternal({ + current: router, + middlewares: this.zz$rb.middlewares, + tags: this.zz$rb.tags, + prefix: this.zz$rb.prefix, + }) as any } lazy>( @@ -131,3 +104,71 @@ export class RouterBuilder< }) as any } } + +/** + * @internal + */ +function createRouterInternal(options: { + current: Router | Procedure | LazyProcedure + middlewares?: Middleware[] + tags?: string[] + prefix?: HTTPPath +}) { + if (isProcedure(options.current)) { + const builderMiddlewares = options.middlewares ?? [] + const itemMiddlewares = options.current.zz$p.middlewares ?? [] + + const middlewares = [ + ...builderMiddlewares, + ...itemMiddlewares.filter( + item => !builderMiddlewares.includes(item), + ), + ] + + const contract = DecoratedContractProcedure.decorate( + options.current.zz$p.contract, + ).addTags(...(options.tags ?? [])) + + return decorateProcedure({ + zz$p: { + ...options.current.zz$p, + contract: options.prefix + ? contract.prefix(options.prefix) + : contract, + middlewares, + }, + }) + } + + let procedure: DecoratedLazyProcedure | undefined + + if (isLazyProcedure(options.current)) { + procedure = decorateLazyProcedure({ + ...options.current[LAZY_PROCEDURE_SYMBOL], + middlewares: options.middlewares, + }) + } + + const recursive = new Proxy(procedure ?? {}, { + get(target, key) { + if (typeof key !== 'string') { + return Reflect.get(target, key) + } + + const next = Reflect.get(options.current, key) + + if ((typeof next !== 'object' && typeof next !== 'function') || next === null) { + return next + } + + return createRouterInternal({ + current: next, + middlewares: options.middlewares, + tags: options.tags, + prefix: options.prefix, + }) + }, + }) + + return recursive +} diff --git a/packages/server/src/router-caller.test.ts b/packages/server/src/router-caller.test.ts index f63a24f6..2f09a8e4 100644 --- a/packages/server/src/router-caller.test.ts +++ b/packages/server/src/router-caller.test.ts @@ -29,14 +29,25 @@ describe('createRouterCaller', () => { ping, pong, }, - lazy: osw.lazy(() => Promise.resolve({ + lazy: osw.lazy(async () => ({ default: { ping, - pong: osw.lazy(() => Promise.resolve({ default: pong })), + pong: osw.lazy(async () => ({ default: pong })), }, })), }) + it('test', async () => { + const caller = osw.lazy(() => Promise.resolve({ + default: { + pong: osw.lazy(() => Promise.resolve({ default: pong })), + }, + })) + + // console.log(await caller.lazy.ping({ value: '123' })) + console.log(caller.pong()) + }) + it('infer context', () => { createRouterCaller({ router, @@ -94,6 +105,10 @@ describe('createRouterCaller', () => { value: '123', }) + expect(caller.lazy.pong({ value: '123' })).resolves.toEqual({ + value: true, + }) + // @ts-expect-error - invalid input expect(caller.ping({ value: new Date('2023-01-01') })).rejects.toThrowError( 'Validation input failed', @@ -118,6 +133,14 @@ describe('createRouterCaller', () => { ping, }, }, + lazy: osw.lazy(() => Promise.resolve({ + default: { + ping, + nested: { + ping: osw.lazy(() => Promise.resolve({ default: ping })), + }, + }, + })), }) const caller = createRouterCaller({ @@ -132,5 +155,13 @@ describe('createRouterCaller', () => { 'child', 'ping', ]) + + expect(caller.lazy.ping('')).resolves.toEqual(['lazy', 'ping']) + expect(caller.lazy.nested.ping('')).resolves.toEqual(['lazy', 'nested', 'ping']) + expect(caller.lazy.nested.ping('')).resolves.toEqual([ + 'lazy', + 'nested', + 'ping', + ]) }) }) diff --git a/packages/server/src/router-caller.ts b/packages/server/src/router-caller.ts index a7b58098..edddbb15 100644 --- a/packages/server/src/router-caller.ts +++ b/packages/server/src/router-caller.ts @@ -56,7 +56,7 @@ function createRouterCallerInternal( const procedureCaller = isLazyProcedure(options.current) || isProcedure(options.current) ? createProcedureCaller({ procedure: options.current, - context: options.context as any, + context: options.context, path: options.path, }) : {} @@ -76,7 +76,7 @@ function createRouterCallerInternal( return createRouterCallerInternal({ current: next, context: options.context, - path: [...(options.path ?? []), key], + path: [...options.path, key], }) }, }) diff --git a/packages/server/src/router-lazy.ts b/packages/server/src/router-lazy.ts index 6fd56a0d..a8ad7a48 100644 --- a/packages/server/src/router-lazy.ts +++ b/packages/server/src/router-lazy.ts @@ -5,8 +5,8 @@ import { isProcedure, type Procedure } from './procedure' import { decorateLazyProcedure, isLazyProcedure, LAZY_PROCEDURE_SYMBOL } from './procedure-lazy' export type LazyRouter> = { - [K in keyof TRouter]: TRouter[K] extends DecoratedLazyProcedure - ? TRouter[K] + [K in keyof TRouter]: TRouter[K] extends LazyProcedure + ? DecoratedLazyProcedure : TRouter[K] extends Procedure ? DecoratedLazyProcedure : TRouter[K] extends Router @@ -25,10 +25,12 @@ export function createLazyProcedureOrLazyRouter< : T extends Router ? LazyRouter : never { - return createLazyProcedureOrLazyRouterInternal({ + const result = createLazyProcedureOrLazyRouterInternal({ load: options.load, middlewares: options.middlewares, }) as any + + return result } function createLazyProcedureOrLazyRouterInternal( @@ -41,7 +43,7 @@ function createLazyProcedureOrLazyRouterInternal( const procedure = await options.load() if (isLazyProcedure(procedure)) { - return await procedure[LAZY_PROCEDURE_SYMBOL].load() + return procedure[LAZY_PROCEDURE_SYMBOL].load() } if (isProcedure(procedure)) { @@ -64,15 +66,16 @@ function createLazyProcedureOrLazyRouterInternal( const loadNext: () => Promise | Procedure> = async () => { const current = await options.load() + const next = Reflect.get(current, key) - if ((typeof current !== 'object' && typeof current !== 'function') || current === null) { + if ((typeof next !== 'object' && typeof next !== 'function') || next === null) { throw new Error('The loader reached the end of the chain') } - return Reflect.get(current, key) as any + return next } - return createLazyProcedureOrLazyRouter({ + return createLazyProcedureOrLazyRouterInternal({ load: loadNext, middlewares: options.middlewares, }) diff --git a/packages/server/src/router.ts b/packages/server/src/router.ts index 0ab2e619..17ef82ad 100644 --- a/packages/server/src/router.ts +++ b/packages/server/src/router.ts @@ -4,7 +4,7 @@ import type { SchemaInput, SchemaOutput, } from '@orpc/contract' -import type { DecoratedLazyProcedure } from './procedure-lazy' +import type { DecoratedLazyProcedure, LazyProcedure } from './procedure-lazy' import type { Context } from './types' import { isContractProcedure, @@ -16,7 +16,7 @@ import { } from './procedure' export interface Router { - [k: string]: Procedure | DecoratedLazyProcedure | Router + [k: string]: Procedure | LazyProcedure | Router } export type HandledRouter> = { @@ -26,6 +26,12 @@ export type HandledRouter> = { infer UInputSchema, infer UOutputSchema, infer UFuncOutput + > | LazyProcedure< + infer UContext, + infer UExtraContext, + infer UInputSchema, + infer UOutputSchema, + infer UFuncOutput > ? DecoratedProcedure< UContext,