From 4e4b8697368bd8f3541f54d785c832ac8b131c09 Mon Sep 17 00:00:00 2001 From: unnoq Date: Fri, 20 Dec 2024 10:01:49 +0700 Subject: [PATCH] rename caller to client, and some improvements --- packages/next/src/action-form.ts | 4 +- packages/next/src/action-safe.ts | 4 +- packages/openapi/src/fetch/base-handler.ts | 4 +- packages/server/src/builder.ts | 4 +- .../server/src/fetch/handle-request.test-d.ts | 4 +- .../server/src/fetch/orpc-handler.test.ts | 12 +-- packages/server/src/fetch/orpc-handler.ts | 4 +- packages/server/src/fetch/types.ts | 8 +- packages/server/src/index.ts | 4 +- packages/server/src/lazy-decorated.test-d.ts | 12 +-- packages/server/src/lazy-decorated.test.ts | 20 ++-- packages/server/src/lazy-decorated.ts | 15 +-- packages/server/src/lazy-utils.test-d.ts | 10 ++ packages/server/src/lazy-utils.test.ts | 37 ++++++++ packages/server/src/lazy-utils.ts | 21 ++++ packages/server/src/lazy.ts | 2 +- ...r.test-d.ts => procedure-client.test-d.ts} | 93 ++++++++++++++---- ...aller.test.ts => procedure-client.test.ts} | 68 ++++++------- ...rocedure-caller.ts => procedure-client.ts} | 16 ++-- packages/server/src/procedure-decorated.ts | 21 ++-- ...ller.test-d.ts => router-client.test-d.ts} | 59 ++++++------ ...r-caller.test.ts => router-client.test.ts} | 95 ++++++++----------- .../{router-caller.ts => router-client.ts} | 44 ++++----- packages/server/src/types.test-d.ts | 54 +---------- packages/server/src/types.ts | 10 +- packages/server/tsconfig.json | 1 + 26 files changed, 330 insertions(+), 296 deletions(-) create mode 100644 packages/server/src/lazy-utils.test-d.ts create mode 100644 packages/server/src/lazy-utils.test.ts create mode 100644 packages/server/src/lazy-utils.ts rename packages/server/src/{procedure-caller.test-d.ts => procedure-client.test-d.ts} (60%) rename packages/server/src/{procedure-caller.test.ts => procedure-client.test.ts} (84%) rename packages/server/src/{procedure-caller.ts => procedure-client.ts} (90%) rename packages/server/src/{router-caller.test-d.ts => router-client.test-d.ts} (60%) rename packages/server/src/{router-caller.test.ts => router-client.test.ts} (50%) rename packages/server/src/{router-caller.ts => router-client.ts} (60%) diff --git a/packages/next/src/action-form.ts b/packages/next/src/action-form.ts index 8f998852..5243826e 100644 --- a/packages/next/src/action-form.ts +++ b/packages/next/src/action-form.ts @@ -1,12 +1,12 @@ import type { ANY_LAZY_PROCEDURE, ANY_PROCEDURE, CreateProcedureCallerOptions } from '@orpc/server' -import { createProcedureCaller, loadProcedure, ORPCError } from '@orpc/server' +import { createProcedureClient, loadProcedure, ORPCError } from '@orpc/server' import { OpenAPIDeserializer } from '@orpc/transformer' import { forbidden, notFound, unauthorized } from 'next/navigation' export type FormAction = (input: FormData) => Promise export function createFormAction(opt: CreateProcedureCallerOptions): FormAction { - const caller = createProcedureCaller(opt) + const caller = createProcedureClient(opt) const formAction = async (input: FormData): Promise => { try { diff --git a/packages/next/src/action-safe.ts b/packages/next/src/action-safe.ts index 1210f018..054c2aad 100644 --- a/packages/next/src/action-safe.ts +++ b/packages/next/src/action-safe.ts @@ -1,6 +1,6 @@ import type { SchemaInput, SchemaOutput } from '@orpc/contract' import type { ANY_LAZY_PROCEDURE, ANY_PROCEDURE, CreateProcedureCallerOptions, Lazy, Procedure, WELL_ORPC_ERROR_JSON } from '@orpc/server' -import { createProcedureCaller, ORPCError } from '@orpc/server' +import { createProcedureClient, ORPCError } from '@orpc/server' export type SafeAction = T extends | Procedure @@ -16,7 +16,7 @@ export type SafeAction = T extends : never export function createSafeAction(opt: CreateProcedureCallerOptions): SafeAction { - const caller = createProcedureCaller(opt) + const caller = createProcedureClient(opt) const safeAction = async (...input: [any] | []) => { try { diff --git a/packages/openapi/src/fetch/base-handler.ts b/packages/openapi/src/fetch/base-handler.ts index c1349509..8ac99f4e 100644 --- a/packages/openapi/src/fetch/base-handler.ts +++ b/packages/openapi/src/fetch/base-handler.ts @@ -5,7 +5,7 @@ import type { ANY_LAZY_PROCEDURE, ANY_PROCEDURE, Router } from '@orpc/server' import type { FetchHandler } from '@orpc/server/fetch' import type { Router as HonoRouter } from 'hono/router' import type { EachContractLeafResultItem, EachLeafOptions } from '../utils' -import { createProcedureCaller, isLazy, isProcedure, LAZY_LOADER_SYMBOL, LAZY_ROUTER_PREFIX_SYMBOL, ORPCError } from '@orpc/server' +import { createProcedureClient, isLazy, isProcedure, LAZY_LOADER_SYMBOL, LAZY_ROUTER_PREFIX_SYMBOL, ORPCError } from '@orpc/server' import { executeWithHooks, isPlainObject, mapValues, ORPC_PROTOCOL_HEADER, ORPC_PROTOCOL_VALUE, trim, value } from '@orpc/shared' import { OpenAPIDeserializer, OpenAPISerializer, zodCoerce } from '@orpc/transformer' import { eachContractProcedureLeaf, standardizeHTTPPath } from '../utils' @@ -65,7 +65,7 @@ export function createOpenAPIHandler(createHonoRouter: () => Routing): FetchHand const input = await deserializeInput(options.request, procedure) const mergedInput = mergeParamsAndInput(params, input) - const caller = createProcedureCaller({ + const caller = createProcedureClient({ context, procedure, path, diff --git a/packages/server/src/builder.ts b/packages/server/src/builder.ts index e095f193..4a09e396 100644 --- a/packages/server/src/builder.ts +++ b/packages/server/src/builder.ts @@ -1,5 +1,5 @@ import type { ANY_CONTRACT_PROCEDURE, ContractRouter, HTTPPath, RouteOptions, Schema, SchemaInput, SchemaOutput } from '@orpc/contract' -import type { DecoratedLazy } from './lazy-decorated' +import type { FlattenLazy } from './lazy' import type { Middleware } from './middleware' import type { DecoratedMiddleware } from './middleware-decorated' import type { Router } from './router' @@ -137,7 +137,7 @@ export class Builder { lazy, any>>( loader: () => Promise<{ default: U }>, - ): DecoratedLazy> { + ): AdaptedRouter> { return new RouterBuilder(this['~orpc']).lazy(loader) } diff --git a/packages/server/src/fetch/handle-request.test-d.ts b/packages/server/src/fetch/handle-request.test-d.ts index e75863f7..cff1d0c2 100644 --- a/packages/server/src/fetch/handle-request.test-d.ts +++ b/packages/server/src/fetch/handle-request.test-d.ts @@ -1,5 +1,5 @@ import type { Procedure } from '../procedure' -import type { CallerOptions, WELL_CONTEXT } from '../types' +import type { WELL_CONTEXT, WithSignal } from '../types' import { lazy } from '../lazy' import { handleFetchRequest } from './handle-request' @@ -64,7 +64,7 @@ describe('handleFetchRequest', () => { expectTypeOf(output).toEqualTypeOf() expectTypeOf(input).toEqualTypeOf() expectTypeOf(context).toEqualTypeOf<{ auth: boolean }>() - expectTypeOf(meta).toEqualTypeOf() + expectTypeOf(meta).toEqualTypeOf() }, }) }) diff --git a/packages/server/src/fetch/orpc-handler.test.ts b/packages/server/src/fetch/orpc-handler.test.ts index 9034e289..6916e571 100644 --- a/packages/server/src/fetch/orpc-handler.test.ts +++ b/packages/server/src/fetch/orpc-handler.test.ts @@ -3,11 +3,11 @@ import { ORPC_PROTOCOL_HEADER, ORPC_PROTOCOL_VALUE } from '@orpc/shared' import { describe, expect, it, vi } from 'vitest' import { lazy } from '../lazy' import { Procedure } from '../procedure' -import { createProcedureCaller } from '../procedure-caller' +import { createProcedureClient } from '../procedure-client' import { createORPCHandler } from './orpc-handler' -vi.mock('../procedure-caller', () => ({ - createProcedureCaller: vi.fn(() => vi.fn()), +vi.mock('../procedure-client', () => ({ + createProcedureClient: vi.fn(() => vi.fn()), })) describe('createORPCHandler', () => { @@ -74,7 +74,7 @@ describe('createORPCHandler', () => { const handler = createORPCHandler() const caller = vi.fn().mockReturnValueOnce('__mocked__') - vi.mocked(createProcedureCaller).mockReturnValue(caller) + vi.mocked(createProcedureClient).mockReturnValue(caller) const mockRequest = new Request('https://example.com/ping', { headers: new Headers({ [ORPC_PROTOCOL_HEADER]: ORPC_PROTOCOL_VALUE }), @@ -119,7 +119,7 @@ describe('createORPCHandler', () => { it('should handle unexpected errors and return a 500 response', async () => { const handler = createORPCHandler() - vi.mocked(createProcedureCaller).mockImplementationOnce(() => { + vi.mocked(createProcedureClient).mockImplementationOnce(() => { throw new Error('Unexpected error') }) @@ -146,7 +146,7 @@ describe('createORPCHandler', () => { const handler = createORPCHandler() const caller = vi.fn().mockReturnValueOnce('__mocked__') - vi.mocked(createProcedureCaller).mockReturnValue(caller) + vi.mocked(createProcedureClient).mockReturnValue(caller) const mockRequest = new Request('https://example.com/ping', { headers: new Headers({ [ORPC_PROTOCOL_HEADER]: ORPC_PROTOCOL_VALUE }), diff --git a/packages/server/src/fetch/orpc-handler.ts b/packages/server/src/fetch/orpc-handler.ts index e88d784d..281c507e 100644 --- a/packages/server/src/fetch/orpc-handler.ts +++ b/packages/server/src/fetch/orpc-handler.ts @@ -5,7 +5,7 @@ import { ORPCError } from '@orpc/shared/error' import { ORPCDeserializer, ORPCSerializer } from '@orpc/transformer' import { unlazy } from '../lazy' import { isProcedure } from '../procedure' -import { createProcedureCaller } from '../procedure-caller' +import { createProcedureClient } from '../procedure-client' import { type ANY_ROUTER, getRouterChild } from '../router' const serializer = new ORPCSerializer() @@ -31,7 +31,7 @@ export function createORPCHandler(): FetchHandler { const input = await parseRequestInput(options.request) - const caller = createProcedureCaller({ + const caller = createProcedureClient({ context, procedure: match.procedure, path: match.path, diff --git a/packages/server/src/fetch/types.ts b/packages/server/src/fetch/types.ts index 0e810ac0..4b58f785 100644 --- a/packages/server/src/fetch/types.ts +++ b/packages/server/src/fetch/types.ts @@ -1,9 +1,7 @@ -/// - import type { HTTPPath } from '@orpc/contract' import type { Hooks, Value } from '@orpc/shared' import type { Router } from '../router' -import type { CallerOptions, Context } from '../types' +import type { Context, WithSignal } from '../types' export type FetchHandlerOptions = { @@ -27,8 +25,8 @@ export type FetchHandlerOptions = prefix?: HTTPPath } & NoInfer<(undefined extends T ? { context?: Value } : { context: Value })> - & CallerOptions - & Hooks + & WithSignal + & Hooks export type FetchHandler = ( options: FetchHandlerOptions diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 92c41230..4ffbfb39 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -10,12 +10,12 @@ export * from './middleware' export * from './middleware-decorated' export * from './procedure' export * from './procedure-builder' -export * from './procedure-caller' +export * from './procedure-client' export * from './procedure-decorated' export * from './procedure-implementer' export * from './router' export * from './router-builder' -export * from './router-caller' +export * from './router-client' export * from './router-implementer' export * from './types' export * from './utils' diff --git a/packages/server/src/lazy-decorated.test-d.ts b/packages/server/src/lazy-decorated.test-d.ts index 03c58e5d..6e30a7ea 100644 --- a/packages/server/src/lazy-decorated.test-d.ts +++ b/packages/server/src/lazy-decorated.test-d.ts @@ -1,4 +1,4 @@ -import type { ANY_PROCEDURE, ANY_ROUTER, Caller, DecoratedProcedure, Procedure, WELL_CONTEXT } from '.' +import type { ANY_PROCEDURE, ANY_ROUTER, DecoratedProcedure, Procedure, ProcedureClient, WELL_CONTEXT } from '.' import type { Lazy } from './lazy' import type { DecoratedLazy } from './lazy-decorated' import { z } from 'zod' @@ -42,7 +42,7 @@ describe('DecoratedLazy', () => { expectTypeOf(decorated).toMatchTypeOf>() expectTypeOf(decorated).toMatchTypeOf< - Caller + ProcedureClient >() }) @@ -53,19 +53,19 @@ describe('DecoratedLazy', () => { expectTypeOf({ router: decorated }).toMatchTypeOf() expectTypeOf(decorated.ping).toMatchTypeOf>() - expectTypeOf(decorated.ping).toMatchTypeOf >() + expectTypeOf(decorated.ping).toMatchTypeOf >() expectTypeOf(decorated.pong).toMatchTypeOf>() - expectTypeOf(decorated.pong).toMatchTypeOf>() + expectTypeOf(decorated.pong).toMatchTypeOf>() expectTypeOf(decorated.nested).toMatchTypeOf>() expectTypeOf({ router: decorated.nested }).toMatchTypeOf() expectTypeOf(decorated.nested.ping).toMatchTypeOf>() - expectTypeOf(decorated.nested.ping).toMatchTypeOf>() + expectTypeOf(decorated.nested.ping).toMatchTypeOf>() expectTypeOf(decorated.nested.pong).toMatchTypeOf>() - expectTypeOf(decorated.nested.pong).toMatchTypeOf>() + expectTypeOf(decorated.nested.pong).toMatchTypeOf>() }) it('flat lazy', () => { diff --git a/packages/server/src/lazy-decorated.test.ts b/packages/server/src/lazy-decorated.test.ts index 00dd6d99..97794c74 100644 --- a/packages/server/src/lazy-decorated.test.ts +++ b/packages/server/src/lazy-decorated.test.ts @@ -3,7 +3,7 @@ import { z } from 'zod' import { isLazy, lazy, unlazy } from './lazy' import { decorateLazy } from './lazy-decorated' import { Procedure } from './procedure' -import { createProcedureCaller } from './procedure-caller' +import { createProcedureClient } from './procedure-client' vi.mock('./procedure-caller', () => ({ createProcedureCaller: vi.fn(() => vi.fn()), @@ -62,19 +62,19 @@ describe('decorated lazy', () => { const signal = controller.signal const caller = vi.fn(() => '__mocked__') - vi.mocked(createProcedureCaller).mockReturnValue(caller as any) + vi.mocked(createProcedureClient).mockReturnValue(caller as any) it('on root', async () => { const decorated = decorateLazy(lazied) as any expect(decorated).toBeInstanceOf(Function) - expect(createProcedureCaller).toHaveBeenCalledTimes(1) - expect(createProcedureCaller).toHaveBeenCalledWith({ + expect(createProcedureClient).toHaveBeenCalledTimes(1) + expect(createProcedureClient).toHaveBeenCalledWith({ procedure: expect.any(Object), context: undefined, }) - expect(vi.mocked(createProcedureCaller).mock.calls[0]![0].procedure).toSatisfy(isLazy) - const unwrapped = await unlazy(vi.mocked(createProcedureCaller).mock.calls[0]![0].procedure as any) + expect(vi.mocked(createProcedureClient).mock.calls[0]![0].procedure).toSatisfy(isLazy) + const unwrapped = await unlazy(vi.mocked(createProcedureClient).mock.calls[0]![0].procedure as any) expect(unwrapped.default).toBe(router) expect(await decorated({ val: '1' }, { signal })).toBe('__mocked__') @@ -86,13 +86,13 @@ describe('decorated lazy', () => { const decorated = decorateLazy(lazied).nested.ping as any expect(decorated).toBeInstanceOf(Function) - expect(createProcedureCaller).toHaveBeenCalledTimes(3) - expect(createProcedureCaller).toHaveBeenNthCalledWith(3, { + expect(createProcedureClient).toHaveBeenCalledTimes(3) + expect(createProcedureClient).toHaveBeenNthCalledWith(3, { procedure: expect.any(Object), context: undefined, }) - expect(vi.mocked(createProcedureCaller).mock.calls[2]![0].procedure).toSatisfy(isLazy) - const unwrapped = await unlazy(vi.mocked(createProcedureCaller).mock.calls[2]![0].procedure as any) + expect(vi.mocked(createProcedureClient).mock.calls[2]![0].procedure).toSatisfy(isLazy) + const unwrapped = await unlazy(vi.mocked(createProcedureClient).mock.calls[2]![0].procedure as any) expect(unwrapped.default).toBe(ping) expect(await decorated({ val: '1' }, { signal })).toBe('__mocked__') diff --git a/packages/server/src/lazy-decorated.ts b/packages/server/src/lazy-decorated.ts index 1d4cbf23..ebb2683d 100644 --- a/packages/server/src/lazy-decorated.ts +++ b/packages/server/src/lazy-decorated.ts @@ -1,9 +1,10 @@ import type { SchemaInput, SchemaOutput } from '@orpc/contract' import type { Lazy } from './lazy' import type { Procedure } from './procedure' -import type { Caller } from './types' +import type { ProcedureClient } from './procedure-client' import { flatLazy } from './lazy' -import { createProcedureCaller } from './procedure-caller' +import { createLazyProcedureFormAnyLazy } from './lazy-utils' +import { createProcedureClient } from './procedure-client' import { type ANY_ROUTER, getRouterChild } from './router' export type DecoratedLazy = T extends Lazy @@ -13,7 +14,7 @@ export type DecoratedLazy = T extends Lazy & ( T extends Procedure ? undefined extends UContext - ? Caller, SchemaOutput> + ? ProcedureClient, SchemaOutput> : unknown : { [K in keyof T]: T[K] extends object ? DecoratedLazy : never @@ -23,14 +24,14 @@ export type DecoratedLazy = T extends Lazy export function decorateLazy>(lazied: T): DecoratedLazy { const flattenLazy = flatLazy(lazied) - const procedureCaller = createProcedureCaller({ - procedure: flattenLazy, + const procedureProcedureClient = createProcedureClient({ + procedure: createLazyProcedureFormAnyLazy(flattenLazy), context: undefined, }) - Object.assign(procedureCaller, flattenLazy) + Object.assign(procedureProcedureClient, flattenLazy) - const recursive = new Proxy(procedureCaller, { + const recursive = new Proxy(procedureProcedureClient, { get(target, key) { if (typeof key !== 'string') { return Reflect.get(target, key) diff --git a/packages/server/src/lazy-utils.test-d.ts b/packages/server/src/lazy-utils.test-d.ts new file mode 100644 index 00000000..13fe13e9 --- /dev/null +++ b/packages/server/src/lazy-utils.test-d.ts @@ -0,0 +1,10 @@ +import type { Lazy } from './lazy' +import type { ANY_PROCEDURE } from './procedure' +import { lazy } from './lazy' +import { createLazyProcedureFormAnyLazy } from './lazy-utils' + +it('createLazyProcedureFormAnyLazy return a Lazy', async () => { + const lazyPing = lazy(() => Promise.resolve({ default: {} as unknown })) + const lazyProcedure = createLazyProcedureFormAnyLazy(lazyPing) + expectTypeOf(lazyProcedure).toEqualTypeOf>() +}) diff --git a/packages/server/src/lazy-utils.test.ts b/packages/server/src/lazy-utils.test.ts new file mode 100644 index 00000000..7bd1c424 --- /dev/null +++ b/packages/server/src/lazy-utils.test.ts @@ -0,0 +1,37 @@ +import { ContractProcedure } from '@orpc/contract' +import { isLazy, lazy, unlazy } from './lazy' +import { createLazyProcedureFormAnyLazy } from './lazy-utils' +import { Procedure } from './procedure' + +describe('createLazyProcedureFormAnyLazy', () => { + const ping = new Procedure({ + contract: new ContractProcedure({ + InputSchema: undefined, + OutputSchema: undefined, + }), + func: vi.fn(), + }) + + it('return a Lazy', async () => { + const lazyPing = lazy(() => Promise.resolve({ default: ping })) + + const lazyProcedure = createLazyProcedureFormAnyLazy(lazyPing) + + expect(lazyProcedure).toSatisfy(isLazy) + expect(unlazy(lazyProcedure)).resolves.toEqual({ default: ping }) + }) + + it('throw un unlazy non-procedure', () => { + const lazyPing = lazy(() => Promise.resolve({ default: {} as unknown })) + const lazyProcedure = createLazyProcedureFormAnyLazy(lazyPing) + + expect(unlazy(lazyProcedure)).rejects.toThrow('Expected a lazy but got lazy') + }) + + it('flat lazy', () => { + const lazyPing = lazy(() => Promise.resolve({ default: lazy(() => Promise.resolve({ default: ping })) })) + const lazyProcedure = createLazyProcedureFormAnyLazy(lazyPing) + + expect(unlazy(lazyProcedure)).resolves.toEqual({ default: ping }) + }) +}) diff --git a/packages/server/src/lazy-utils.ts b/packages/server/src/lazy-utils.ts new file mode 100644 index 00000000..881f9784 --- /dev/null +++ b/packages/server/src/lazy-utils.ts @@ -0,0 +1,21 @@ +import type { Lazy } from './lazy' +import { flatLazy, lazy, unlazy } from './lazy' +import { type ANY_PROCEDURE, isProcedure } from './procedure' + +export function createLazyProcedureFormAnyLazy(lazied: Lazy): Lazy { + const lazyProcedure = lazy(async () => { + const { default: maybeProcedure } = await unlazy(flatLazy(lazied)) + + if (!isProcedure(maybeProcedure)) { + throw new Error(` + Expected a lazy but got lazy. + This should be caught by TypeScript compilation. + Please report this issue if this makes you feel uncomfortable. + `) + } + + return { default: maybeProcedure } + }) + + return lazyProcedure +} diff --git a/packages/server/src/lazy.ts b/packages/server/src/lazy.ts index 253079da..05dee30f 100644 --- a/packages/server/src/lazy.ts +++ b/packages/server/src/lazy.ts @@ -23,7 +23,7 @@ export function isLazy(item: unknown): item is ANY_LAZY { ) } -export function unlazy(lazied: Lazyable): Promise<{ default: T }> { +export function unlazy>(lazied: T): Promise<{ default: T extends Lazy ? U : T }> { return isLazy(lazied) ? lazied[LAZY_LOADER_SYMBOL]() : Promise.resolve({ default: lazied }) } diff --git a/packages/server/src/procedure-caller.test-d.ts b/packages/server/src/procedure-client.test-d.ts similarity index 60% rename from packages/server/src/procedure-caller.test-d.ts rename to packages/server/src/procedure-client.test-d.ts index 9f8428c7..befc42a5 100644 --- a/packages/server/src/procedure-caller.test-d.ts +++ b/packages/server/src/procedure-client.test-d.ts @@ -1,69 +1,122 @@ import type { Procedure } from './procedure' -import type { Caller, Meta, WELL_CONTEXT } from './types' +import type { ProcedureClient } from './procedure-client' +import type { Meta, WELL_CONTEXT, WithSignal } from './types' import { z } from 'zod' import { lazy } from './lazy' -import { createProcedureCaller } from './procedure-caller' +import { createProcedureClient } from './procedure-client' beforeEach(() => { vi.resetAllMocks() }) -describe('createProcedureCaller', () => { +describe('ProcedureClient', () => { + const fn: ProcedureClient = async (input, options) => { + expectTypeOf(input).toEqualTypeOf() + expectTypeOf(options).toEqualTypeOf() + return 123 + } + + const fnWithOptionalInput: ProcedureClient = async (...args) => { + const [input, options] = args + + expectTypeOf(input).toEqualTypeOf() + expectTypeOf(options).toEqualTypeOf() + return 123 + } + + it('just a function', () => { + expectTypeOf(fn).toEqualTypeOf<(input: string, options?: WithSignal) => Promise>() + expectTypeOf(fnWithOptionalInput).toMatchTypeOf<(input: string | undefined, options?: WithSignal) => Promise>() + }) + + it('infer correct input', () => { + fn('123') + fnWithOptionalInput('123') + + // @ts-expect-error - invalid input + fn(123) + // @ts-expect-error - invalid input + fnWithOptionalInput(123) + + // @ts-expect-error - invalid input + fn({}) + // @ts-expect-error - invalid input + fnWithOptionalInput({}) + }) + + it('accept signal', () => { + fn('123', { signal: new AbortSignal() }) + fnWithOptionalInput('123', { signal: new AbortSignal() }) + + // @ts-expect-error - invalid signal + fn('123', { signal: 1234 }) + // @ts-expect-error - invalid signal + fnWithOptionalInput('123', { signal: 1234 }) + }) + + it('can accept call without args', () => { + expectTypeOf(fnWithOptionalInput()).toEqualTypeOf>() + // @ts-expect-error - input is required + expectTypeOf(fn()).toEqualTypeOf>() + }) +}) + +describe('createProcedureClient', () => { const schema = z.object({ val: z.string().transform(v => Number(v)) }) const procedure = {} as Procedure const procedureWithContext = {} as Procedure<{ userId?: string }, { db: string }, typeof schema, typeof schema, { val: string }> - it('just a caller', () => { - const caller = createProcedureCaller({ + it('just a client', () => { + const client = createProcedureClient({ procedure, }) - expectTypeOf(caller).toEqualTypeOf>() + expectTypeOf(client).toEqualTypeOf>() }) it('context can be optional and can be a sync or async function', () => { - createProcedureCaller({ + createProcedureClient({ procedure, }) - createProcedureCaller({ + createProcedureClient({ procedure, context: undefined, }) // @ts-expect-error - missing context - createProcedureCaller({ + createProcedureClient({ procedure: procedureWithContext, }) - createProcedureCaller({ + createProcedureClient({ procedure: procedureWithContext, context: { userId: '123' }, }) - createProcedureCaller({ + createProcedureClient({ procedure: procedureWithContext, // @ts-expect-error invalid context context: { userId: 123 }, }) - createProcedureCaller({ + createProcedureClient({ procedure: procedureWithContext, context: () => ({ userId: '123' }), }) - createProcedureCaller({ + createProcedureClient({ procedure: procedureWithContext, // @ts-expect-error invalid context context: () => ({ userId: 123 }), }) - createProcedureCaller({ + createProcedureClient({ procedure: procedureWithContext, context: async () => ({ userId: '123' }), }) - createProcedureCaller({ + createProcedureClient({ procedure: procedureWithContext, // @ts-expect-error invalid context context: async () => ({ userId: 123 }), @@ -71,7 +124,7 @@ describe('createProcedureCaller', () => { }) it('accept hooks', () => { - createProcedureCaller({ + createProcedureClient({ procedure, async execute(input, context, meta) { @@ -109,12 +162,12 @@ describe('createProcedureCaller', () => { }) it('accept paths', () => { - createProcedureCaller({ + createProcedureClient({ procedure, path: ['users'], }) - createProcedureCaller({ + createProcedureClient({ procedure, // @ts-expect-error - invalid path path: [123], @@ -127,7 +180,7 @@ it('support lazy procedure', () => { const procedure = {} as Procedure<{ userId?: string }, undefined, typeof schema, typeof schema, { val: string }> const lazied = lazy(() => Promise.resolve({ default: procedure })) - const caller = createProcedureCaller({ + const client = createProcedureClient({ procedure: lazied, context: async () => ({ userId: 'string' }), path: ['users'], @@ -139,5 +192,5 @@ it('support lazy procedure', () => { }, }) - expectTypeOf(caller).toEqualTypeOf>() + expectTypeOf(client).toEqualTypeOf>() }) diff --git a/packages/server/src/procedure-caller.test.ts b/packages/server/src/procedure-client.test.ts similarity index 84% rename from packages/server/src/procedure-caller.test.ts rename to packages/server/src/procedure-client.test.ts index 3715f47e..cb592fb6 100644 --- a/packages/server/src/procedure-caller.test.ts +++ b/packages/server/src/procedure-client.test.ts @@ -3,7 +3,7 @@ import { ContractProcedure } from '@orpc/contract' import { z } from 'zod' import { isLazy, lazy, unlazy } from './lazy' import { Procedure } from './procedure' -import { createProcedureCaller } from './procedure-caller' +import { createProcedureClient } from './procedure-client' const schema = z.object({ val: z.string().transform(v => Number(v)) }) @@ -29,15 +29,15 @@ beforeEach(() => { vi.clearAllMocks() }) -describe.each(procedureCases)('createProcedureCaller - case %s', async (_, procedure) => { +describe.each(procedureCases)('createProcedureClient - case %s', async (_, procedure) => { const unwrappedProcedure = isLazy(procedure) ? (await unlazy(procedure)).default : procedure - it('just a caller', async () => { - const caller = createProcedureCaller({ + it('just a client', async () => { + const client = createProcedureClient({ procedure, }) - await expect(caller({ val: '123' })).resolves.toEqual({ val: 123 }) + await expect(client({ val: '123' })).resolves.toEqual({ val: 123 }) expect(func).toBeCalledTimes(1) expect(func).toBeCalledWith({ val: 123 }, undefined, { path: [], procedure: unwrappedProcedure }) @@ -50,26 +50,26 @@ describe.each(procedureCases)('createProcedureCaller - case %s', async (_, proce }) it('validate input and output', () => { - const caller = createProcedureCaller({ + const client = createProcedureClient({ procedure, }) // @ts-expect-error - invalid input - expect(caller({ val: 123 })).rejects.toThrow('Input validation failed') + expect(client({ val: 123 })).rejects.toThrow('Input validation failed') // @ts-expect-error - invalid output func.mockReturnValueOnce({ val: 1234 }) - expect(caller({ val: '1234' })).rejects.toThrow('Output validation failed') + expect(client({ val: '1234' })).rejects.toThrow('Output validation failed') }) it('middleware can return output directly', async () => { - const caller = createProcedureCaller({ + const client = createProcedureClient({ procedure, }) mid1.mockReturnValueOnce({ output: { val: '990' } }) - await expect(caller({ val: '123' })).resolves.toEqual({ val: 990 }) + await expect(client({ val: '123' })).resolves.toEqual({ val: 990 }) expect(mid1).toBeCalledTimes(1) expect(mid2).toBeCalledTimes(0) @@ -79,7 +79,7 @@ describe.each(procedureCases)('createProcedureCaller - case %s', async (_, proce mid2.mockReturnValueOnce({ output: { val: '9900' } }) - await expect(caller({ val: '123' })).resolves.toEqual({ val: 9900 }) + await expect(client({ val: '123' })).resolves.toEqual({ val: 9900 }) expect(mid1).toBeCalledTimes(1) expect(mid2).toBeCalledTimes(1) @@ -89,22 +89,22 @@ describe.each(procedureCases)('createProcedureCaller - case %s', async (_, proce }) it('output from middleware still be validated', async () => { - const caller = createProcedureCaller({ + const client = createProcedureClient({ procedure, context: { userId: '123' }, }) mid1.mockReturnValueOnce({ output: { val: 990 } }) - await expect(caller({ val: '1234' })).rejects.toThrow('Output validation failed') + await expect(client({ val: '1234' })).rejects.toThrow('Output validation failed') vi.clearAllMocks() mid2.mockReturnValueOnce({ output: { val: 9900 } }) - await expect(caller({ val: '1234' })).rejects.toThrow('Output validation failed') + await expect(client({ val: '1234' })).rejects.toThrow('Output validation failed') }) it('middleware can add extra context - single', async () => { - const caller = createProcedureCaller({ + const client = createProcedureClient({ procedure, }) @@ -124,7 +124,7 @@ describe.each(procedureCases)('createProcedureCaller - case %s', async (_, proce }) }) - await expect(caller({ val: '123' })).resolves.toEqual({ val: 123 }) + await expect(client({ val: '123' })).resolves.toEqual({ val: 123 }) expect(mid1).toBeCalledTimes(1) expect(mid1).toHaveBeenCalledWith(expect.any(Object), undefined, expect.any(Object)) @@ -137,7 +137,7 @@ describe.each(procedureCases)('createProcedureCaller - case %s', async (_, proce }) it('middleware can override context', async () => { - const caller = createProcedureCaller({ + const client = createProcedureClient({ procedure, context: { userId: '123' }, }) @@ -158,7 +158,7 @@ describe.each(procedureCases)('createProcedureCaller - case %s', async (_, proce }) }) - await expect(caller({ val: '123' })).resolves.toEqual({ val: 123 }) + await expect(client({ val: '123' })).resolves.toEqual({ val: 123 }) expect(mid1).toBeCalledTimes(1) expect(mid1).toHaveBeenCalledWith(expect.any(Object), expect.objectContaining({ userId: '123' }), expect.any(Object)) @@ -177,12 +177,12 @@ describe.each(procedureCases)('createProcedureCaller - case %s', async (_, proce ] as const it.each(contextCases)('can accept context: %s', async (_, context) => { - const caller = createProcedureCaller({ + const client = createProcedureClient({ procedure, context, }) - await caller({ val: '123' }) + await client({ val: '123' }) expect(mid1).toBeCalledTimes(1) expect(mid1).toBeCalledWith(expect.any(Object), { val: '__val__' }, expect.any(Object)) @@ -201,7 +201,7 @@ describe.each(procedureCases)('createProcedureCaller - case %s', async (_, proce const onError = vi.fn() const onFinish = vi.fn() - const caller = createProcedureCaller({ + const client = createProcedureClient({ procedure, context, path: ['users'], @@ -212,7 +212,7 @@ describe.each(procedureCases)('createProcedureCaller - case %s', async (_, proce onFinish, }) - await caller({ val: '123' }) + await client({ val: '123' }) const meta = { path: ['users'], @@ -246,13 +246,13 @@ describe.each(procedureCases)('createProcedureCaller - case %s', async (_, proce it('accept paths', async () => { const onSuccess = vi.fn() - const caller = createProcedureCaller({ + const client = createProcedureClient({ procedure, path: ['users'], onSuccess, }) - await caller({ val: '123' }) + await client({ val: '123' }) expect(mid1).toBeCalledTimes(1) expect(mid1).toHaveBeenCalledWith(expect.any(Object), undefined, expect.objectContaining({ path: ['users'] })) @@ -273,13 +273,13 @@ describe.each(procedureCases)('createProcedureCaller - case %s', async (_, proce const onSuccess = vi.fn() - const caller = createProcedureCaller({ + const client = createProcedureClient({ procedure, onSuccess, context: { userId: '123' }, }) - await caller({ val: '123' }, { signal }) + await client({ val: '123' }, { signal }) expect(mid1).toBeCalledTimes(1) expect(mid1).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.objectContaining({ signal })) @@ -304,11 +304,11 @@ it('still work without middleware', async () => { func, }) - const caller = createProcedureCaller({ + const client = createProcedureClient({ procedure, }) - await expect(caller({ val: '123' })).resolves.toEqual({ val: 123 }) + await expect(client({ val: '123' })).resolves.toEqual({ val: 123 }) expect(func).toBeCalledTimes(1) expect(func).toHaveBeenCalledWith({ val: 123 }, undefined, { path: [], procedure }) @@ -323,11 +323,11 @@ it('still work without InputSchema', async () => { func, }) - const caller = createProcedureCaller({ + const client = createProcedureClient({ procedure, }) - await expect(caller('anything')).resolves.toEqual({ val: 123 }) + await expect(client('anything')).resolves.toEqual({ val: 123 }) expect(func).toBeCalledTimes(1) expect(func).toHaveBeenCalledWith('anything', undefined, { path: [], procedure }) @@ -342,21 +342,21 @@ it('still work without OutputSchema', async () => { func, }) - const caller = createProcedureCaller({ + const client = createProcedureClient({ procedure, }) // @ts-expect-error - without output schema func.mockReturnValueOnce('anything') - await expect(caller({ val: '123' })).resolves.toEqual('anything') + await expect(client({ val: '123' })).resolves.toEqual('anything') expect(func).toBeCalledTimes(1) expect(func).toHaveBeenCalledWith({ val: 123 }, undefined, { path: [], procedure }) }) it('has helper `output` in meta', async () => { - const caller = createProcedureCaller({ + const client = createProcedureClient({ procedure, }) @@ -364,7 +364,7 @@ it('has helper `output` in meta', async () => { return meta.output({ val: '99990' }) }) - await expect(caller({ val: '123' })).resolves.toEqual({ val: 99990 }) + await expect(client({ val: '123' })).resolves.toEqual({ val: 99990 }) expect(mid1).toBeCalledTimes(1) expect(mid2).toBeCalledTimes(1) diff --git a/packages/server/src/procedure-caller.ts b/packages/server/src/procedure-client.ts similarity index 90% rename from packages/server/src/procedure-caller.ts rename to packages/server/src/procedure-client.ts index 4aa6296a..b9af07e1 100644 --- a/packages/server/src/procedure-caller.ts +++ b/packages/server/src/procedure-client.ts @@ -2,17 +2,17 @@ import type { Schema, SchemaInput, SchemaOutput } from '@orpc/contract' import type { Hooks, Value } from '@orpc/shared' import type { Lazyable } from './lazy' import type { MiddlewareMeta } from './middleware' -import type { - ANY_PROCEDURE, - Procedure, -} from './procedure' - -import type { Caller, Context, Meta, WELL_CONTEXT } from './types' +import type { ANY_PROCEDURE, Procedure } from './procedure' +import type { Context, Meta, WELL_CONTEXT, WithSignal } from './types' import { executeWithHooks, value } from '@orpc/shared' import { ORPCError } from '@orpc/shared/error' import { unlazy } from './lazy' import { mergeContext } from './utils' +export interface ProcedureClient { + (...opts: [input: TInput, options?: WithSignal] | (undefined extends TInput ? [] : never)): Promise +} + /** * Options for creating a procedure caller with comprehensive type safety */ @@ -40,14 +40,14 @@ export type CreateProcedureCallerOptions< } | (undefined extends TContext ? { context?: undefined } : never)) & Hooks, TContext, Meta> -export function createProcedureCaller< +export function createProcedureClient< TContext extends Context = WELL_CONTEXT, TInputSchema extends Schema = undefined, TOutputSchema extends Schema = undefined, TFuncOutput extends SchemaInput = SchemaInput, >( options: CreateProcedureCallerOptions, -): Caller, SchemaOutput> { +): ProcedureClient, SchemaOutput> { return async (...[input, callerOptions]) => { const path = options.path ?? [] const { default: procedure } = await unlazy(options.procedure) diff --git a/packages/server/src/procedure-decorated.ts b/packages/server/src/procedure-decorated.ts index b0a26774..48cc97c7 100644 --- a/packages/server/src/procedure-decorated.ts +++ b/packages/server/src/procedure-decorated.ts @@ -1,10 +1,11 @@ import type { HTTPPath, RouteOptions, Schema, SchemaInput, SchemaOutput } from '@orpc/contract' import type { ANY_MIDDLEWARE, MapInputMiddleware, Middleware } from './middleware' -import type { Caller, Context, MergeContext } from './types' +import type { ProcedureClient } from './procedure-client' +import type { Context, MergeContext } from './types' import { DecoratedContractProcedure } from '@orpc/contract' import { decorateMiddleware } from './middleware-decorated' import { Procedure } from './procedure' -import { createProcedureCaller } from './procedure-caller' +import { createProcedureClient } from './procedure-client' export type DecoratedProcedure< TContext extends Context, @@ -71,7 +72,7 @@ export type DecoratedProcedure< ) => DecoratedProcedure } - & (undefined extends TContext ? Caller, SchemaOutput> : unknown) + & (undefined extends TContext ? ProcedureClient, SchemaOutput> : unknown) export function decorateProcedure< TContext extends Context, @@ -80,16 +81,10 @@ export function decorateProcedure< TOutputSchema extends Schema, TFuncOutput extends SchemaInput, >( - procedure: Procedure< - TContext, - TExtraContext, - TInputSchema, - TOutputSchema, - TFuncOutput - >, -): DecoratedProcedure { - const caller = createProcedureCaller({ - procedure: procedure as any, + procedure: Procedure, +): DecoratedProcedure { + const caller = createProcedureClient({ + procedure, context: undefined as any, }) diff --git a/packages/server/src/router-caller.test-d.ts b/packages/server/src/router-client.test-d.ts similarity index 60% rename from packages/server/src/router-caller.test-d.ts rename to packages/server/src/router-client.test-d.ts index 5a9f8b50..056f5da0 100644 --- a/packages/server/src/router-caller.test-d.ts +++ b/packages/server/src/router-client.test-d.ts @@ -1,8 +1,9 @@ import type { Procedure } from './procedure' -import type { Caller, Meta, WELL_CONTEXT } from './types' +import type { ProcedureClient } from './procedure-client' +import type { Meta, WELL_CONTEXT } from './types' import { z } from 'zod' import { lazy } from './lazy' -import { createRouterCaller, type RouterCaller } from './router-caller' +import { createRouterClient, type RouterClient } from './router-client' const schema = z.object({ val: z.string().transform(val => Number(val)) }) const ping = {} as Procedure @@ -26,84 +27,84 @@ const routerWithLazy = { } })), } -describe('RouterCaller', () => { +describe('RouterClient', () => { it('router without lazy', () => { - const caller = {} as RouterCaller + const client = {} as RouterClient - expectTypeOf(caller.ping).toEqualTypeOf< - Caller<{ val: string }, { val: number }> + expectTypeOf(client.ping).toEqualTypeOf< + ProcedureClient<{ val: string }, { val: number }> >() - expectTypeOf(caller.pong).toEqualTypeOf< - Caller + expectTypeOf(client.pong).toEqualTypeOf< + ProcedureClient >() - expectTypeOf(caller.nested.ping).toEqualTypeOf< - Caller<{ val: string }, { val: number }> + expectTypeOf(client.nested.ping).toEqualTypeOf< + ProcedureClient<{ val: string }, { val: number }> >() - expectTypeOf(caller.nested.pong).toEqualTypeOf< - Caller + expectTypeOf(client.nested.pong).toEqualTypeOf< + ProcedureClient >() }) it('support lazy', () => { - expectTypeOf>().toEqualTypeOf>() + expectTypeOf>().toEqualTypeOf>() }) it('support procedure as router', () => { - expectTypeOf>().toEqualTypeOf>() + expectTypeOf>().toEqualTypeOf>() }) }) -describe('createRouterCaller', () => { - it('return RouterCaller', () => { - const caller = createRouterCaller({ +describe('createRouterClient', () => { + it('return RouterClient', () => { + const client = createRouterClient({ router, context: { auth: true }, }) - expectTypeOf(caller).toMatchTypeOf>() + expectTypeOf(client).toMatchTypeOf>() - const caller2 = createRouterCaller({ + const client2 = createRouterClient({ router: routerWithLazy, context: { auth: true }, }) - expectTypeOf(caller2).toMatchTypeOf>() + expectTypeOf(client2).toMatchTypeOf>() }) it('required context when needed', () => { - createRouterCaller({ + createRouterClient({ router: { ping }, }) - createRouterCaller({ + createRouterClient({ router: { pong }, context: { auth: true }, }) - createRouterCaller({ + createRouterClient({ router: { pong }, context: () => ({ auth: true }), }) - createRouterCaller({ + createRouterClient({ router: { pong }, context: async () => ({ auth: true }), }) - createRouterCaller({ + createRouterClient({ router: { pong }, // @ts-expect-error --- invalid context context: { auth: 'invalid' }, }) // @ts-expect-error --- missing context - createRouterCaller({ + createRouterClient({ router: { pong }, }) }) it('support hooks', () => { - createRouterCaller({ + createRouterClient({ router, context: { auth: true }, onSuccess: async ({ output }, context, meta) => { @@ -117,13 +118,13 @@ describe('createRouterCaller', () => { }) it('support base path', () => { - createRouterCaller({ + createRouterClient({ router: { ping }, context: { auth: true }, path: ['users'], }) - createRouterCaller({ + createRouterClient({ router: { ping }, context: { auth: true }, // @ts-expect-error --- invalid path diff --git a/packages/server/src/router-caller.test.ts b/packages/server/src/router-client.test.ts similarity index 50% rename from packages/server/src/router-caller.test.ts rename to packages/server/src/router-client.test.ts index 67f4e9f2..5eab7437 100644 --- a/packages/server/src/router-caller.test.ts +++ b/packages/server/src/router-client.test.ts @@ -1,19 +1,19 @@ import { ContractProcedure } from '@orpc/contract' import { z } from 'zod' -import { isLazy, lazy, unlazy } from './lazy' +import { lazy, unlazy } from './lazy' import { Procedure } from './procedure' -import { createProcedureCaller } from './procedure-caller' -import { createRouterCaller } from './router-caller' +import { createProcedureClient } from './procedure-client' +import { createRouterClient } from './router-client' -vi.mock('./procedure-caller', () => ({ - createProcedureCaller: vi.fn(() => vi.fn(() => '__mocked__')), +vi.mock('./procedure-client', () => ({ + createProcedureClient: vi.fn(() => vi.fn(() => '__mocked__')), })) beforeEach(() => { vi.clearAllMocks() }) -describe('createRouterCaller', () => { +describe('createRouterClient', () => { const schema = z.object({ val: z.string().transform(v => Number(v)) }) const ping = new Procedure({ contract: new ContractProcedure({ @@ -39,77 +39,77 @@ describe('createRouterCaller', () => { } })), } - const caller = createRouterCaller({ + const client = createRouterClient({ router, context: { auth: true }, path: ['users'], }) it('works', () => { - expect(caller.pong({ val: '123' })).toEqual('__mocked__') + expect(client.pong({ val: '123' })).toEqual('__mocked__') - expect(createProcedureCaller).toBeCalledTimes(1) - expect(createProcedureCaller).toBeCalledWith(expect.objectContaining({ + expect(createProcedureClient).toBeCalledTimes(1) + expect(createProcedureClient).toBeCalledWith(expect.objectContaining({ procedure: pong, context: { auth: true }, path: ['users', 'pong'], })) - expect(vi.mocked(createProcedureCaller).mock.results[0]?.value).toBeCalledTimes(1) - expect(vi.mocked(createProcedureCaller).mock.results[0]?.value).toBeCalledWith({ val: '123' }) + expect(vi.mocked(createProcedureClient).mock.results[0]?.value).toBeCalledTimes(1) + expect(vi.mocked(createProcedureClient).mock.results[0]?.value).toBeCalledWith({ val: '123' }) }) it('work with lazy', async () => { - expect(caller.ping({ val: '123' })).toEqual('__mocked__') + expect(client.ping({ val: '123' })).toEqual('__mocked__') - expect(createProcedureCaller).toBeCalledTimes(1) - expect(createProcedureCaller).toHaveBeenNthCalledWith(1, expect.objectContaining({ + expect(createProcedureClient).toBeCalledTimes(1) + expect(createProcedureClient).toHaveBeenNthCalledWith(1, expect.objectContaining({ procedure: expect.any(Object), context: { auth: true }, path: ['users', 'ping'], })) - expect((await unlazy(vi.mocked(createProcedureCaller as any).mock.calls[0]![0].procedure)).default).toBe(ping) + expect((await unlazy(vi.mocked(createProcedureClient as any).mock.calls[0]![0].procedure)).default).toBe(ping) - expect(vi.mocked(createProcedureCaller).mock.results[0]?.value).toBeCalledTimes(1) - expect(vi.mocked(createProcedureCaller).mock.results[0]?.value).toBeCalledWith({ val: '123' }) + expect(vi.mocked(createProcedureClient).mock.results[0]?.value).toBeCalledTimes(1) + expect(vi.mocked(createProcedureClient).mock.results[0]?.value).toBeCalledWith({ val: '123' }) }) it('work with nested lazy', async () => { - expect(caller.nested.ping({ val: '123' })).toEqual('__mocked__') + expect(client.nested.ping({ val: '123' })).toEqual('__mocked__') - expect(createProcedureCaller).toBeCalledTimes(2) - expect(createProcedureCaller).toHaveBeenNthCalledWith(2, expect.objectContaining({ + expect(createProcedureClient).toBeCalledTimes(2) + expect(createProcedureClient).toHaveBeenNthCalledWith(2, expect.objectContaining({ procedure: expect.any(Object), context: { auth: true }, path: ['users', 'nested', 'ping'], })) - const lazied = vi.mocked(createProcedureCaller as any).mock.calls[1]![0].procedure + const lazied = vi.mocked(createProcedureClient as any).mock.calls[1]![0].procedure expect(await unlazy(lazied)).toEqual({ default: ping }) - expect(vi.mocked(createProcedureCaller).mock.results[1]?.value).toBeCalledTimes(1) - expect(vi.mocked(createProcedureCaller).mock.results[1]?.value).toBeCalledWith({ val: '123' }) + expect(vi.mocked(createProcedureClient).mock.results[1]?.value).toBeCalledTimes(1) + expect(vi.mocked(createProcedureClient).mock.results[1]?.value).toBeCalledWith({ val: '123' }) }) it('work with procedure as router', () => { - const caller = createRouterCaller({ + const client = createRouterClient({ router: ping, context: { auth: true }, path: ['users'], }) - expect(caller({ val: '123' })).toEqual('__mocked__') + expect(client({ val: '123' })).toEqual('__mocked__') - expect(createProcedureCaller).toBeCalledTimes(1) - expect(createProcedureCaller).toHaveBeenCalledWith(expect.objectContaining({ + expect(createProcedureClient).toBeCalledTimes(1) + expect(createProcedureClient).toHaveBeenCalledWith(expect.objectContaining({ procedure: ping, context: { auth: true }, path: ['users'], })) - expect(vi.mocked(createProcedureCaller).mock.results[0]?.value).toBeCalledTimes(1) - expect(vi.mocked(createProcedureCaller).mock.results[0]?.value).toBeCalledWith({ val: '123' }) + expect(vi.mocked(createProcedureClient).mock.results[0]?.value).toBeCalledTimes(1) + expect(vi.mocked(createProcedureClient).mock.results[0]?.value).toBeCalledWith({ val: '123' }) }) it('hooks', async () => { @@ -119,7 +119,7 @@ describe('createRouterCaller', () => { const onFinish = vi.fn() const execute = vi.fn() - const caller = createRouterCaller({ + const client = createRouterClient({ router, context: { auth: true }, onStart, @@ -129,10 +129,10 @@ describe('createRouterCaller', () => { execute, }) - expect(caller.pong({ val: '123' })).toEqual('__mocked__') + expect(client.pong({ val: '123' })).toEqual('__mocked__') - expect(createProcedureCaller).toBeCalledTimes(1) - expect(createProcedureCaller).toHaveBeenCalledWith(expect.objectContaining({ + expect(createProcedureClient).toBeCalledTimes(1) + expect(createProcedureClient).toHaveBeenCalledWith(expect.objectContaining({ procedure: pong, context: { auth: true }, path: ['pong'], @@ -145,43 +145,28 @@ describe('createRouterCaller', () => { }) it('not recursive on symbol', () => { - expect((caller as any)[Symbol('something')]).toBeUndefined() - }) - - it('throw error if call on invalid lazy', async () => { - const caller = createRouterCaller({ - router: lazy(() => Promise.resolve({ default: undefined })), - }) - - // @ts-expect-error --- invalid lazy - caller.router.ping.pong({ val: '123' }) - - const procedure = vi.mocked(createProcedureCaller).mock.calls[0]![0].procedure - - expect(procedure).toSatisfy(isLazy) - - expect(unlazy(procedure)).rejects.toThrow('Expected a valid procedure or lazy but got unknown.') + expect((client as any)[Symbol('something')]).toBeUndefined() }) it('return undefined if access the undefined key', async () => { - const caller = createRouterCaller({ + const client = createRouterClient({ router: { ping, }, }) // @ts-expect-error --- invalid access - expect(caller.router).toBeUndefined() + expect(client.router).toBeUndefined() }) it('works without base path', async () => { - const caller = createRouterCaller({ + const client = createRouterClient({ router: { ping, }, }) - expect(caller.ping({ val: '123' })).toEqual('__mocked__') - expect(vi.mocked(createProcedureCaller).mock.calls[0]![0].path).toEqual(['ping']) + expect(client.ping({ val: '123' })).toEqual('__mocked__') + expect(vi.mocked(createProcedureClient).mock.calls[0]![0].path).toEqual(['ping']) }) }) diff --git a/packages/server/src/router-caller.ts b/packages/server/src/router-client.ts similarity index 60% rename from packages/server/src/router-caller.ts rename to packages/server/src/router-client.ts index 461cc87a..0653a965 100644 --- a/packages/server/src/router-caller.ts +++ b/packages/server/src/router-client.ts @@ -2,21 +2,23 @@ import type { SchemaInput, SchemaOutput } from '@orpc/contract' import type { Hooks, Value } from '@orpc/shared' import type { Lazy } from './lazy' import type { Procedure } from './procedure' -import type { Caller, Meta } from './types' -import { isLazy, lazy, unlazy } from './lazy' +import type { ProcedureClient } from './procedure-client' +import type { Meta } from './types' +import { isLazy } from './lazy' +import { createLazyProcedureFormAnyLazy } from './lazy-utils' import { isProcedure } from './procedure' -import { createProcedureCaller } from './procedure-caller' +import { createProcedureClient } from './procedure-client' import { type ANY_ROUTER, getRouterChild, type Router } from './router' -export type RouterCaller = T extends Lazy - ? RouterCaller +export type RouterClient = T extends Lazy + ? RouterClient : T extends Procedure - ? Caller, SchemaOutput> + ? ProcedureClient, SchemaOutput> : { - [K in keyof T]: T[K] extends ANY_ROUTER ? RouterCaller : never + [K in keyof T]: T[K] extends ANY_ROUTER ? RouterClient : never } -export type CreateRouterCallerOptions< +export type CreateRouterClientOptions< TRouter extends ANY_ROUTER, > = & { @@ -34,13 +36,13 @@ export type CreateRouterCallerOptions< : never) & Hooks ? UContext : never, Meta> -export function createRouterCaller< +export function createRouterClient< TRouter extends ANY_ROUTER, >( - options: CreateRouterCallerOptions, -): RouterCaller { + options: CreateRouterClientOptions, +): RouterClient { if (isProcedure(options.router)) { - const caller = createProcedureCaller({ + const caller = createProcedureClient({ ...options, procedure: options.router, context: options.context, @@ -51,21 +53,9 @@ export function createRouterCaller< } const procedureCaller = isLazy(options.router) - ? createProcedureCaller({ + ? createProcedureClient({ ...options, - procedure: lazy(async () => { - const { default: maybeProcedure } = await unlazy(options.router) - - if (!isProcedure(maybeProcedure)) { - throw new Error(` - Expected a valid procedure or lazy but got unknown. - This should be caught by TypeScript compilation. - Please report this issue if this makes you feel uncomfortable. - `) - } - - return { default: maybeProcedure } - }), + procedure: createLazyProcedureFormAnyLazy(options.router), context: options.context, path: options.path, }) @@ -83,7 +73,7 @@ export function createRouterCaller< return Reflect.get(target, key) } - return createRouterCaller({ + return createRouterClient({ ...options, router: next, path: [...(options.path ?? []), key], diff --git a/packages/server/src/types.test-d.ts b/packages/server/src/types.test-d.ts index a7f5aab0..d14c1d0e 100644 --- a/packages/server/src/types.test-d.ts +++ b/packages/server/src/types.test-d.ts @@ -1,4 +1,4 @@ -import type { Caller, CallerOptions, MergeContext } from './types' +import type { MergeContext } from './types' it('mergeContext', () => { expectTypeOf>().toEqualTypeOf() @@ -16,55 +16,3 @@ it('mergeContext', () => { bar: string }>() }) - -describe('Caller', () => { - const fn: Caller = async (input, options) => { - expectTypeOf(input).toEqualTypeOf() - expectTypeOf(options).toEqualTypeOf() - return 123 - } - - const fnWithOptionalInput: Caller = async (...args) => { - const [input, options] = args - - expectTypeOf(input).toEqualTypeOf() - expectTypeOf(options).toEqualTypeOf() - return 123 - } - - it('just a function', () => { - expectTypeOf(fn).toEqualTypeOf<(input: string, options?: CallerOptions) => Promise>() - expectTypeOf(fnWithOptionalInput).toMatchTypeOf<(input: string | undefined, options?: CallerOptions) => Promise>() - }) - - it('infer correct input', () => { - fn('123') - fnWithOptionalInput('123') - - // @ts-expect-error - invalid input - fn(123) - // @ts-expect-error - invalid input - fnWithOptionalInput(123) - - // @ts-expect-error - invalid input - fn({}) - // @ts-expect-error - invalid input - fnWithOptionalInput({}) - }) - - it('accept signal', () => { - fn('123', { signal: new AbortSignal() }) - fnWithOptionalInput('123', { signal: new AbortSignal() }) - - // @ts-expect-error - invalid signal - fn('123', { signal: 1234 }) - // @ts-expect-error - invalid signal - fnWithOptionalInput('123', { signal: 1234 }) - }) - - it('can accept call without args', () => { - expectTypeOf(fnWithOptionalInput()).toEqualTypeOf>() - // @ts-expect-error - input is required - expectTypeOf(fn()).toEqualTypeOf>() - }) -}) diff --git a/packages/server/src/types.ts b/packages/server/src/types.ts index ab2936e0..471b9621 100644 --- a/packages/server/src/types.ts +++ b/packages/server/src/types.ts @@ -1,5 +1,3 @@ -/// - import type { ANY_PROCEDURE } from './procedure' export type Context = Record | undefined @@ -10,15 +8,11 @@ export type MergeContext< TB extends Context, > = TA extends undefined ? TB : TB extends undefined ? TA : TA & TB -export interface CallerOptions { +export interface WithSignal { signal?: AbortSignal } -export interface Caller { - (...opts: [input: TInput, options?: CallerOptions] | (undefined extends TInput ? [] : never)): Promise -} - -export interface Meta extends CallerOptions { +export interface Meta extends WithSignal { path: string[] procedure: ANY_PROCEDURE } diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index 99061213..e6322353 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.lib.json", "compilerOptions": { + "lib": ["ES2022", "DOM"], "types": [] }, "references": [