diff --git a/packages/client/src/types.ts b/packages/client/src/types.ts index b5aef5ac9..6b946f8dc 100644 --- a/packages/client/src/types.ts +++ b/packages/client/src/types.ts @@ -1,3 +1,5 @@ +import type { PromiseWithError } from '@orpc/shared' + export type HTTPPath = `/${string}` export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' @@ -13,7 +15,7 @@ export type ClientRest = Record] : [input: TInput, options: FriendlyClientOptions] -export type ClientPromiseResult = Promise & { __error?: { type: TError } } +export type ClientPromiseResult = PromiseWithError export interface Client { (...rest: ClientRest): ClientPromiseResult diff --git a/packages/shared/src/interceptor.test-d.ts b/packages/shared/src/interceptor.test-d.ts index 301f54c45..73afd6dbd 100644 --- a/packages/shared/src/interceptor.test-d.ts +++ b/packages/shared/src/interceptor.test-d.ts @@ -1,11 +1,12 @@ import type { Interceptor } from './interceptor' +import type { PromiseWithError } from './types' import { onError, onFinish, onStart, onSuccess } from './interceptor' it('onStart', () => { const interceptor: Interceptor<{ foo: string }, 'success', 'error'> = onStart((options) => { expectTypeOf(options.foo).toEqualTypeOf() expectTypeOf(options.next).toBeCallableWith<[options?: { foo: string }]>() - expectTypeOf(options.next()).toEqualTypeOf & { __error?: { type: 'error' } }>() + expectTypeOf(options.next()).toEqualTypeOf>() }) }) @@ -15,7 +16,7 @@ it('onSuccess', () => { expectTypeOf(options.foo).toEqualTypeOf() expectTypeOf(options.next).toBeCallableWith<[options?: { foo: string }]>() - expectTypeOf(options.next()).toEqualTypeOf & { __error?: { type: 'error' } }>() + expectTypeOf(options.next()).toEqualTypeOf>() }) }) @@ -25,7 +26,7 @@ it('onError', () => { expectTypeOf(options.foo).toEqualTypeOf() expectTypeOf(options.next).toBeCallableWith<[options?: { foo: string }]>() - expectTypeOf(options.next()).toEqualTypeOf & { __error?: { type: 'error' } }>() + expectTypeOf(options.next()).toEqualTypeOf>() }) }) @@ -35,6 +36,6 @@ it('onFinish', () => { expectTypeOf(options.foo).toEqualTypeOf() expectTypeOf(options.next).toBeCallableWith<[options?: { foo: string }]>() - expectTypeOf(options.next()).toEqualTypeOf & { __error?: { type: 'error' } }>() + expectTypeOf(options.next()).toEqualTypeOf>() }) }) diff --git a/packages/shared/src/interceptor.ts b/packages/shared/src/interceptor.ts index 92fd86ef7..067a9b199 100644 --- a/packages/shared/src/interceptor.ts +++ b/packages/shared/src/interceptor.ts @@ -1,4 +1,5 @@ import type { Promisable } from 'type-fest' +import type { PromiseWithError } from './types' export type InterceptableOptions = Record @@ -7,14 +8,14 @@ export type InterceptorOptions< TResult, TError, > = Omit & { - next(options?: TOptions): Promise & { __error?: { type: TError } } + next(options?: TOptions): PromiseWithError } export type Interceptor< TOptions extends InterceptableOptions, TResult, TError, -> = (options: InterceptorOptions) => Promise & { __error?: { type: TError } } +> = (options: InterceptorOptions) => PromiseWithError /** * Can used for interceptors or middlewares @@ -44,15 +45,19 @@ export function onSuccess /** * Can used for interceptors or middlewares */ -export function onError( - callback: NoInfer<(error: TError, options: TOptions, ...rest: TRest) => Promisable>, -): (options: TOptions, ...rest: TRest) => Promise>> & { __error?: { type: TError } } { +export function onError( + callback: NoInfer<( + error: ReturnType extends PromiseWithError ? E : unknown, + options: TOptions, + ...rest: TRest + ) => Promisable>, +): (options: TOptions, ...rest: TRest) => Promise>> { return async (options, ...rest) => { try { return await options.next() } catch (error) { - await callback(error as TError, options, ...rest) + await callback(error as any, options, ...rest) throw error } } @@ -63,10 +68,18 @@ export type OnFinishState = [TResult, null, 'success'] | [undef /** * Can used for interceptors or middlewares */ -export function onFinish( - callback: NoInfer<(state: OnFinishState>, TError>, options: TOptions, ...rest: TRest) => Promisable>, -): (options: TOptions, ...rest: TRest) => Promise>> & { __error?: { type: TError } } { - let state: OnFinishState>, TError> | undefined +export function onFinish( + callback: NoInfer<( + state: OnFinishState< + Awaited>, + ReturnType extends PromiseWithError ? E : unknown + >, + options: TOptions, + ...rest: TRest + ) => Promisable>, +): (options: TOptions, ...rest: TRest) => Promise>> { + let state: any + return async (options, ...rest) => { try { const result = await options.next() @@ -74,11 +87,11 @@ export function onFinish, options, ...rest) + await callback(state, options, ...rest) } } } diff --git a/packages/shared/src/types.test-d.ts b/packages/shared/src/types.test-d.ts index 976581bea..29cb44794 100644 --- a/packages/shared/src/types.test-d.ts +++ b/packages/shared/src/types.test-d.ts @@ -1,4 +1,4 @@ -import type { IntersectPick, SetOptional } from './types' +import type { IntersectPick, PromiseWithError, SetOptional } from './types' it('SetOptional', () => { expectTypeOf>().toMatchTypeOf<{ a?: number }>() @@ -12,3 +12,10 @@ it('IntersectPick', () => { expectTypeOf>().toEqualTypeOf<{ b: number }>() expectTypeOf>().toEqualTypeOf() }) + +it('PromiseWithError', () => { + type C = PromiseWithError + + expectTypeOf ? T : never>().toEqualTypeOf() + expectTypeOf ? [T, E] : never>().toEqualTypeOf<[number | undefined | null, Error | undefined | null]>() +}) diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index 502967837..8e9d78950 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -1,3 +1,5 @@ export type SetOptional = Omit & Partial> export type IntersectPick = Pick + +export type PromiseWithError = Promise & { __error?: { type: TError } }