@trpc/client
Version:
226 lines (208 loc) • 6.37 kB
text/typescript
/* oxlint-disable typescript/no-non-null-assertion */
import type { Unsubscribable } from '@trpc/server/observable';
import type {
AnyProcedure,
AnyRouter,
inferAsyncIterableYield,
inferClientTypes,
inferProcedureErrorShape,
inferProcedureInput,
InferrableClientTypes,
inferTransformedProcedureOutput,
ProcedureType,
RouterRecord,
} from '@trpc/server/unstable-core-do-not-import';
import {
createFlatProxy,
createRecursiveProxy,
} from '@trpc/server/unstable-core-do-not-import';
import type { CreateTRPCClientOptions } from './createTRPCUntypedClient';
import type {
TRPCSubscriptionCallbacks,
TRPCSubscriptionObserver,
TRPCSubscriptionWithoutCallbacks,
} from './internals/TRPCUntypedClient';
import { TRPCUntypedClient } from './internals/TRPCUntypedClient';
import type { TRPCProcedureOptions } from './internals/types';
import type { TRPCTaggedPromise } from './safe';
import type { TRPCClientError } from './TRPCClientError';
/**
* The type of a client for a router.
* @deprecated Use {@link TRPCClient} instead. This will be removed in v12.
* @public
**/
export type inferRouterClient<TRouter extends AnyRouter> = TRPCClient<TRouter>;
/**
* The type of a client for a router.
* @deprecated Use {@link TRPCClient} instead. This will be removed in v12.
* @public
**/
export type CreateTRPCClient<TRouter extends AnyRouter> = TRPCClient<TRouter>;
const untypedClientSymbol = Symbol.for('trpc_untypedClient');
/**
* A typed client for a router, as `createTRPCClient` returns it. Each
* procedure has `.query()`, `.mutate()` or `.subscribe()`.
* @see https://trpc.io/docs/client/vanilla/infer-types
* @public
**/
export type TRPCClient<TRouter extends AnyRouter> = DecoratedProcedureRecord<
{
transformer: TRouter['_def']['_config']['$types']['transformer'];
errorShape: TRouter['_def']['_config']['$types']['errorShape'];
},
TRouter['_def']['record']
> & {
[untypedClientSymbol]: TRPCUntypedClient<TRouter>;
};
/**
* A procedure's types, as the client sees them.
* @internal
*/
export type TRPCResolverDef = {
input: any;
output: any;
transformer: boolean;
errorShape: any;
};
type coerceAsyncGeneratorToIterable<T> =
T extends AsyncGenerator<infer $T, infer $Return, infer $Next>
? AsyncIterable<$T, $Return, $Next>
: T;
/**
* The `.query()` or `.mutate()` of a procedure on the client.
* @internal
*/
export type Resolver<TDef extends TRPCResolverDef> = (
input: TDef['input'],
opts?: TRPCProcedureOptions,
) => TRPCTaggedPromise<
coerceAsyncGeneratorToIterable<TDef['output']>,
TDef['errorShape']
>;
/**
* The `.subscribe()` of a subscription procedure on the client.
* @internal
*/
export type SubscriptionResolver<TDef extends TRPCResolverDef> = {
(
input: TDef['input'],
opts: TRPCSubscriptionCallbacks<TDef['output'], TRPCClientError<TDef>> &
TRPCProcedureOptions,
): Unsubscribable;
<
TOpts extends TRPCProcedureOptions &
Partial<TRPCSubscriptionObserver<TDef['output'], TRPCClientError<TDef>>> =
TRPCProcedureOptions,
>(
input: TDef['input'],
opts?: TRPCSubscriptionWithoutCallbacks<TOpts>,
): AsyncIterableIterator<inferAsyncIterableYield<TDef['output']>>;
};
type DecorateProcedure<
TType extends ProcedureType,
TDef extends TRPCResolverDef,
> = TType extends 'query'
? {
query: Resolver<TDef>;
}
: TType extends 'mutation'
? {
mutate: Resolver<TDef>;
}
: TType extends 'subscription'
? {
subscribe: SubscriptionResolver<TDef>;
}
: never;
/**
* @internal
*/
type DecoratedProcedureRecord<
TRoot extends InferrableClientTypes,
TRecord extends RouterRecord,
> = {
[TKey in keyof TRecord]: TRecord[TKey] extends infer $Value
? $Value extends AnyProcedure
? DecorateProcedure<
$Value['_def']['type'],
{
input: inferProcedureInput<$Value>;
output: inferTransformedProcedureOutput<
inferClientTypes<TRoot>,
$Value
>;
errorShape: inferProcedureErrorShape<TRoot, $Value>;
transformer: inferClientTypes<TRoot>['transformer'];
}
>
: $Value extends RouterRecord
? DecoratedProcedureRecord<TRoot, $Value>
: never
: never;
};
const clientCallTypeMap: Record<
keyof DecorateProcedure<any, any>,
ProcedureType
> = {
query: 'query',
mutate: 'mutation',
subscribe: 'subscription',
};
/**
* The procedure type of a client method: `query`, `mutate` or `subscribe`.
* @internal
*/
export const clientCallTypeToProcedureType = (
clientCallType: string,
): ProcedureType => {
return clientCallTypeMap[clientCallType as keyof typeof clientCallTypeMap];
};
/**
* Wraps an untyped client in the typed proxy `createTRPCClient` returns.
* @internal
*/
export function createTRPCClientProxy<TRouter extends AnyRouter>(
client: TRPCUntypedClient<TRouter>,
): TRPCClient<TRouter> {
const proxy = createRecursiveProxy<TRPCClient<TRouter>>(({ path, args }) => {
const pathCopy = [...path];
const procedureType = clientCallTypeToProcedureType(pathCopy.pop()!);
const fullPath = pathCopy.join('.');
return (client[procedureType] as any)(fullPath, ...(args as any));
});
return createFlatProxy<TRPCClient<TRouter>>((key) => {
if (key === untypedClientSymbol) {
return client;
}
return proxy[key];
});
}
/**
* Creates a typed client for a router. Pass the router's type, and the
* links that send its calls.
* @example
* ```ts
* const client = createTRPCClient<AppRouter>({
* links: [httpBatchLink({ url: 'http://localhost:3000/trpc' })],
* });
* const post = await client.post.byId.query({ id: '1' });
* ```
* @see https://trpc.io/docs/client/vanilla/setup#3-initialize-the-trpc-client
*/
export function createTRPCClient<TRouter extends AnyRouter>(
opts: CreateTRPCClientOptions<TRouter>,
): TRPCClient<TRouter> {
const client = new TRPCUntypedClient(opts);
const proxy = createTRPCClientProxy<TRouter>(client);
return proxy;
}
/**
* The untyped client behind a typed client, which calls procedures by
* path string.
* @internal
*/
export function getUntypedClient<TRouter extends AnyRouter>(
client: TRPCClient<TRouter>,
): TRPCUntypedClient<TRouter> {
return client[untypedClientSymbol];
}