Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: require transformers on frontend & backend when present #3289

Merged
merged 8 commits into from Dec 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/client/src/createTRPCClient.ts
Expand Up @@ -18,7 +18,7 @@ export function createTRPCClient<TRouter extends AnyRouter>(
return [httpBatchLink(opts)];
};
const client = new Client<TRouter>({
transformer: opts.transformer,
...opts,
links: getLinks(),
});
return client;
Expand Down
62 changes: 48 additions & 14 deletions packages/client/src/internals/TRPCClient.ts
@@ -1,7 +1,10 @@
import {
AnyRouter,
ClientDataTransformerOptions,
CombinedDataTransformer,
DataTransformer,
DataTransformerOptions,
DefaultDataTransformer,
inferProcedureInput,
inferProcedureOutput,
inferSubscriptionOutput,
Expand All @@ -22,13 +25,38 @@ import {
TRPCLink,
} from '../links/types';

interface CreateTRPCClientBaseOptions {
/**
* Data transformer
* @link https://trpc.io/docs/data-transformers
**/
transformer?: ClientDataTransformerOptions;
}
type CreateTRPCClientBaseOptions<TRouter extends AnyRouter> =
TRouter['_def']['_config']['transformer'] extends DefaultDataTransformer
? {
/**
* Data transformer
*
* You must use the same transformer on the backend and frontend
* @link https://trpc.io/docs/data-transformers
**/
transformer?: 'You must set a transformer on the backend router';
}
: TRouter['_def']['_config']['transformer'] extends DataTransformerOptions
? {
/**
* Data transformer
*
* You must use the same transformer on the backend and frontend
* @link https://trpc.io/docs/data-transformers
**/
transformer: TRouter['_def']['_config']['transformer'] extends CombinedDataTransformer
? DataTransformerOptions
: TRouter['_def']['_config']['transformer'];
}
: {
/**
* Data transformer
*
* You must use the same transformer on the backend and frontend
* @link https://trpc.io/docs/data-transformers
**/
transformer?: ClientDataTransformerOptions;
};

type TRPCType = 'subscription' | 'query' | 'mutation';
export interface TRPCRequestOptions {
Expand All @@ -49,7 +77,7 @@ export interface TRPCSubscriptionObserver<TValue, TError> {

/** @internal */
export type CreateTRPCClientOptions<TRouter extends AnyRouter> =
| CreateTRPCClientBaseOptions & {
| CreateTRPCClientBaseOptions<TRouter> & {
links: TRPCLink<TRouter>[];
};

Expand All @@ -62,16 +90,22 @@ export class TRPCClient<TRouter extends AnyRouter> {
this.requestId = 0;

function getTransformer(): DataTransformer {
if (!opts.transformer)
if (!opts.transformer || typeof opts.transformer === 'string')
return {
serialize: (data) => data,
deserialize: (data) => data,
};
if ('input' in opts.transformer)
return {
serialize: opts.transformer.input.serialize,
deserialize: opts.transformer.output.deserialize,
};
// Type guard for `opts.transformer` because it can be `any`
function isTransformer(obj: any): obj is ClientDataTransformerOptions {
return true;
}
if (isTransformer(opts.transformer)) {
if ('input' in opts.transformer)
return {
serialize: opts.transformer.input.serialize,
deserialize: opts.transformer.output.deserialize,
};
}
return opts.transformer;
}

Expand Down
5 changes: 1 addition & 4 deletions packages/server/src/core/initTRPC.ts
Expand Up @@ -7,7 +7,6 @@ import {
} from '../error/formatter';
import { createFlatProxy } from '../shared';
import {
CombinedDataTransformer,
DataTransformerOptions,
DefaultDataTransformer,
defaultTransformer,
Expand Down Expand Up @@ -86,9 +85,7 @@ function createTRPCInner<TParams extends PartialRootConfigTypes>() {
ErrorFormatter<$Context, DefaultErrorShape>
>;
type $Transformer = TOptions['transformer'] extends DataTransformerOptions
? TOptions['transformer'] extends DataTransformerOptions
? CombinedDataTransformer
: DefaultDataTransformer
? TOptions['transformer']
: DefaultDataTransformer;
type $ErrorShape = ErrorFormatterShape<$Formatter>;

Expand Down
4 changes: 2 additions & 2 deletions packages/tests/server/___testHelpers.ts
Expand Up @@ -70,14 +70,14 @@ export function routerToServerAndClientNew<TRouter extends AnyNewRouter>(
url: wssUrl,
...opts?.wsClient,
});
const trpcClientOptions: WithTRPCConfig<typeof router> = {
const trpcClientOptions = {
links: [httpBatchLink({ url: httpUrl })],
...(opts?.client
? typeof opts.client === 'function'
? opts.client({ httpUrl, wssUrl, wsClient })
: opts.client
: {}),
};
} as WithTRPCConfig<typeof router>;

const client = createTRPCClient<typeof router>(trpcClientOptions);
const proxy = createTRPCClientProxy<typeof router>(client);
Expand Down
3 changes: 1 addition & 2 deletions packages/tests/server/initTRPC.test.ts
@@ -1,5 +1,4 @@
import {
CombinedDataTransformer,
DataTransformerOptions,
DefaultDataTransformer,
initTRPC,
Expand Down Expand Up @@ -34,7 +33,7 @@ test('custom transformer', () => {
const router = t.router({});
expectTypeOf(
router._def._config.transformer,
).toMatchTypeOf<CombinedDataTransformer>();
).toMatchTypeOf<DataTransformerOptions>();
expectTypeOf(
router._def._config.transformer,
).not.toMatchTypeOf<DefaultDataTransformer>();
Expand Down
@@ -1,9 +1,6 @@
import { routerToServerAndClientNew } from '../___testHelpers';
import {
CreateTRPCClientOptions,
TRPCWebSocketClient,
WebSocketClientOptions,
} from '@trpc/client/src';
import { TRPCWebSocketClient, WebSocketClientOptions } from '@trpc/client/src';
import { WithTRPCConfig } from '@trpc/next';
import { CreateHTTPHandlerOptions } from '@trpc/server/src/adapters/standalone';
import { WSSHandlerOptions } from '@trpc/server/src/adapters/ws';
import { MigrateOldRouter } from '@trpc/server/src/deprecated/interop';
Expand All @@ -20,12 +17,12 @@ export function legacyRouterToServerAndClient<TOldRouter extends OldRouter>(
wssServer?: Partial<WSSHandlerOptions<MigrateOldRouter<TOldRouter>>>;
wsClient?: Partial<WebSocketClientOptions>;
client?:
| Partial<CreateTRPCClientOptions<MigrateOldRouter<TOldRouter>>>
| Partial<WithTRPCConfig<MigrateOldRouter<TOldRouter>>>
| ((opts: {
httpUrl: string;
wssUrl: string;
wsClient: TRPCWebSocketClient;
}) => Partial<CreateTRPCClientOptions<MigrateOldRouter<TOldRouter>>>);
}) => Partial<WithTRPCConfig<MigrateOldRouter<TOldRouter>>>);
},
) {
const router = _router.interop() as MigrateOldRouter<TOldRouter>;
Expand Down
50 changes: 50 additions & 0 deletions packages/tests/server/transformer.test.ts
Expand Up @@ -2,6 +2,7 @@
import { routerToServerAndClientNew, waitError } from './___testHelpers';
import {
TRPCClientError,
createTRPCProxyClient,
createWSClient,
httpBatchLink,
httpLink,
Expand Down Expand Up @@ -495,3 +496,52 @@ Object {

close();
});

describe('required tranformers', () => {
test('works without transformer', () => {
const t = initTRPC.create({});
const router = t.router({});

createTRPCProxyClient<typeof router>({
links: [httpBatchLink({ url: '' })],
});
});

test('works with transformer', () => {
const transformer = superjson;
const t = initTRPC.create({
transformer,
});
const router = t.router({});

createTRPCProxyClient<typeof router>({
links: [httpBatchLink({ url: '' })],
transformer,
});
});

test('errors with transformer set on backend but not on frontend', () => {
const transformer = superjson;
const t = initTRPC.create({
transformer,
});
const router = t.router({});

// @ts-expect-error missing transformer on frontend
createTRPCProxyClient<typeof router>({
links: [httpBatchLink({ url: '' })],
});
});

test('errors with transformer set on frontend but not on backend', () => {
const transformer = superjson;
const t = initTRPC.create({});
const router = t.router({});

createTRPCProxyClient<typeof router>({
links: [httpBatchLink({ url: '' })],
// @ts-expect-error missing transformer on backend
transformer,
});
});
});