Skip to content

Commit b149c1d

Browse files
committed
feat(nestjs-trpc): support zod 4 schemas in decorator input/output
Replace ZodSchema type with inlined tRPC Parser type in decorator input/output signatures. This accepts both Zod 3 and Zod 4 schemas (plus Valibot, ArkType, Standard Schema, and others) without importing from tRPC's unstable internals. Closes #100
1 parent 701976e commit b149c1d

File tree

8 files changed

+111
-27
lines changed

8 files changed

+111
-27
lines changed

packages/nestjs-trpc/lib/decorators/__tests__/mutation.decorator.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import 'reflect-metadata';
22
import { Mutation } from '../mutation.decorator';
33
import { PROCEDURE_METADATA_KEY, PROCEDURE_TYPE_KEY } from '../../trpc.constants';
44
import { ProcedureType } from '../../trpc.enum';
5-
import { ZodSchema, z } from 'zod';
5+
import { z } from 'zod';
66

77
describe('Mutation Decorator', () => {
88
it('should set procedure type metadata', () => {
@@ -16,8 +16,8 @@ describe('Mutation Decorator', () => {
1616
});
1717

1818
it('should set procedure metadata with input and output schemas', () => {
19-
const inputSchema: ZodSchema = z.string();
20-
const outputSchema: ZodSchema = z.number();
19+
const inputSchema = z.string();
20+
const outputSchema = z.number();
2121

2222
class TestClass {
2323
@Mutation({ input: inputSchema, output: outputSchema })
@@ -39,7 +39,7 @@ describe('Mutation Decorator', () => {
3939
});
4040

4141
it('should set procedure metadata with only input schema', () => {
42-
const inputSchema: ZodSchema = z.string();
42+
const inputSchema = z.string();
4343

4444
class TestClass {
4545
@Mutation({ input: inputSchema })
@@ -51,7 +51,7 @@ describe('Mutation Decorator', () => {
5151
});
5252

5353
it('should set procedure metadata with only output schema', () => {
54-
const outputSchema: ZodSchema = z.number();
54+
const outputSchema = z.number();
5555

5656
class TestClass {
5757
@Mutation({ output: outputSchema })
@@ -63,7 +63,7 @@ describe('Mutation Decorator', () => {
6363
});
6464

6565
it('should set procedure metadata with meta', () => {
66-
const inputSchema: ZodSchema = z.string();
66+
const inputSchema = z.string();
6767
const meta = { roles: ['admin'] };
6868

6969
class TestClass {

packages/nestjs-trpc/lib/decorators/mutation.decorator.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { ZodSchema } from 'zod';
21
import { applyDecorators, SetMetadata } from '@nestjs/common';
32
import { PROCEDURE_METADATA_KEY, PROCEDURE_TYPE_KEY } from '../trpc.constants';
43
import { ProcedureType } from '../trpc.enum';
4+
import type { Parser } from '../interfaces/parser.interface';
55

66
/**
77
* Decorator that marks a router class method as a TRPC mutation procedure that can receive inbound
@@ -11,16 +11,16 @@ import { ProcedureType } from '../trpc.enum';
1111
* for example `Mutation /trpc/userRouter.createUser`.
1212
*
1313
* @param {object} args configuration object specifying:
14-
* - `input` - defines a `ZodSchema` validation logic for the input.
15-
* - `output` - defines a `ZodSchema` validation logic for the output.
14+
* - `input` - defines a schema validation logic for the input.
15+
* - `output` - defines a schema validation logic for the output.
1616
*
1717
* @see [Method Decorators](https://nestjs-trpc.io/docs/routers#procedures)
1818
*
1919
* @publicApi
2020
*/
2121
export function Mutation(args?: {
22-
input?: ZodSchema;
23-
output?: ZodSchema;
22+
input?: Parser;
23+
output?: Parser;
2424
meta?: Record<string, unknown>;
2525
}) {
2626
return applyDecorators(

packages/nestjs-trpc/lib/decorators/query.decorator.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { ZodSchema } from 'zod';
21
import { applyDecorators, SetMetadata } from '@nestjs/common';
32
import { PROCEDURE_METADATA_KEY, PROCEDURE_TYPE_KEY } from '../trpc.constants';
43
import { ProcedureType } from '../trpc.enum';
4+
import type { Parser } from '../interfaces/parser.interface';
55

66
/**
77
* Decorator that marks a router class method as a TRPC query procedure that can receive inbound
@@ -11,16 +11,16 @@ import { ProcedureType } from '../trpc.enum';
1111
* for example `Query /trpc/userRouter.getUsers`.
1212
*
1313
* @param {object} args configuration object specifying:
14-
* - `input` - defines a `ZodSchema` validation logic for the input.
15-
* - `output` - defines a `ZodSchema` validation logic for the output.
14+
* - `input` - defines a schema validation logic for the input.
15+
* - `output` - defines a schema validation logic for the output.
1616
*
1717
* @see [Method Decorators](https://nestjs-trpc.io/docs/routers#procedures)
1818
*
1919
* @publicApi
2020
*/
2121
export function Query(args?: {
22-
input?: ZodSchema;
23-
output?: ZodSchema;
22+
input?: Parser;
23+
output?: Parser;
2424
meta?: Record<string, unknown>;
2525
}) {
2626
return applyDecorators(

packages/nestjs-trpc/lib/decorators/subscription.decorator.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { ZodSchema } from 'zod';
21
import { applyDecorators, SetMetadata } from '@nestjs/common';
32
import { PROCEDURE_METADATA_KEY, PROCEDURE_TYPE_KEY } from '../trpc.constants';
43
import { ProcedureType } from '../trpc.enum';
4+
import type { Parser } from '../interfaces/parser.interface';
55

66
/**
77
* Decorator that marks a router class method as a TRPC subscription procedure that can receive inbound
@@ -11,16 +11,16 @@ import { ProcedureType } from '../trpc.enum';
1111
* for example `Subscription /trpc/eventRouter.onMessage`.
1212
*
1313
* @param {object} args configuration object specifying:
14-
* - `input` - defines a `ZodSchema` validation logic for the input.
15-
* - `output` - defines a `ZodSchema` validation logic for the output.
14+
* - `input` - defines a schema validation logic for the input.
15+
* - `output` - defines a schema validation logic for the output.
1616
*
1717
* @see [Method Decorators](https://nestjs-trpc.io/docs/routers#procedures)
1818
*
1919
* @publicApi
2020
*/
2121
export function Subscription(args?: {
22-
input?: ZodSchema;
23-
output?: ZodSchema;
22+
input?: Parser;
23+
output?: Parser;
2424
meta?: Record<string, unknown>;
2525
}) {
2626
return applyDecorators(

packages/nestjs-trpc/lib/interfaces/factory.interface.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import type {
33
AnyRouter,
44
TRPCProcedureBuilder,
55
} from '@trpc/server';
6-
import type { ZodSchema, ZodType, ZodTypeDef } from 'zod';
76
import type { TRPCMiddleware } from './middleware.interface';
87
import type { Class, Constructor } from 'type-fest';
98
import type { ProcedureType } from '../trpc.enum';
9+
import type { Parser } from './parser.interface';
1010

1111
export enum ProcedureParamDecoratorType {
1212
Options = 'options',
@@ -21,8 +21,8 @@ export type ProcedureImplementation = ({
2121
input,
2222
output,
2323
}: {
24-
input?: ZodType<any, ZodTypeDef, any>;
25-
output?: ZodType<any, ZodTypeDef, any>;
24+
input?: Parser;
25+
output?: Parser;
2626
}) => any;
2727

2828
interface ProcedureParamDecoratorBase {
@@ -41,8 +41,8 @@ export type ProcedureParamDecorator =
4141

4242
export interface ProcedureFactoryMetadata {
4343
type: ProcedureType;
44-
input: ZodSchema | undefined;
45-
output: ZodSchema | undefined;
44+
input: Parser | undefined;
45+
output: Parser | undefined;
4646
meta: Record<string, unknown> | undefined;
4747
middlewares: Array<Constructor<TRPCMiddleware> | Class<TRPCMiddleware>>;
4848
name: string;

packages/nestjs-trpc/lib/interfaces/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ export * from './error-handler.interface';
33
export * from './middleware.interface';
44
export * from './factory.interface';
55
export * from './module-options.interface';
6+
export * from './parser.interface';
67
export * from './procedure-options.interface';
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* Inlined from @trpc/server's parser types to avoid importing from unstable internals.
3+
* @see https://github.com/trpc/trpc/blob/main/packages/server/src/unstable-core-do-not-import/parser.ts
4+
*/
5+
6+
type StandardSchemaV1Result<Output> =
7+
| { readonly value: Output; readonly issues?: undefined }
8+
| { readonly issues: ReadonlyArray<{ readonly message: string }> };
9+
10+
interface StandardSchemaV1<Input = unknown, Output = Input> {
11+
readonly '~standard': {
12+
readonly version: 1;
13+
readonly vendor: string;
14+
readonly validate: (
15+
value: unknown,
16+
) =>
17+
| StandardSchemaV1Result<Output>
18+
| Promise<StandardSchemaV1Result<Output>>;
19+
readonly types?:
20+
| { readonly input: Input; readonly output: Output }
21+
| undefined;
22+
};
23+
}
24+
25+
// Zod / typeschema
26+
type ParserZodEsque<TInput, TParsedInput> = {
27+
_input: TInput;
28+
_output: TParsedInput;
29+
};
30+
31+
type ParserValibotEsque<TInput, TParsedInput> = {
32+
schema: {
33+
_types?: {
34+
input: TInput;
35+
output: TParsedInput;
36+
};
37+
};
38+
};
39+
40+
type ParserArkTypeEsque<TInput, TParsedInput> = {
41+
inferIn: TInput;
42+
infer: TParsedInput;
43+
};
44+
45+
type ParserStandardSchemaEsque<TInput, TParsedInput> = StandardSchemaV1<
46+
TInput,
47+
TParsedInput
48+
>;
49+
50+
type ParserMyZodEsque<TInput> = {
51+
parse: (input: any) => TInput;
52+
};
53+
54+
type ParserSuperstructEsque<TInput> = {
55+
create: (input: unknown) => TInput;
56+
};
57+
58+
type ParserCustomValidatorEsque<TInput> = (
59+
input: unknown,
60+
) => Promise<TInput> | TInput;
61+
62+
type ParserYupEsque<TInput> = {
63+
validateSync: (input: unknown) => TInput;
64+
};
65+
66+
type ParserScaleEsque<TInput> = {
67+
assert(value: unknown): asserts value is TInput;
68+
};
69+
70+
type ParserWithoutInput<TInput> =
71+
| ParserCustomValidatorEsque<TInput>
72+
| ParserMyZodEsque<TInput>
73+
| ParserScaleEsque<TInput>
74+
| ParserSuperstructEsque<TInput>
75+
| ParserYupEsque<TInput>;
76+
77+
type ParserWithInputOutput<TInput, TParsedInput> =
78+
| ParserZodEsque<TInput, TParsedInput>
79+
| ParserValibotEsque<TInput, TParsedInput>
80+
| ParserArkTypeEsque<TInput, TParsedInput>
81+
| ParserStandardSchemaEsque<TInput, TParsedInput>;
82+
83+
export type Parser = ParserWithInputOutput<any, any> | ParserWithoutInput<any>;

packages/nestjs-trpc/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
"@trpc/server": "^11.0.0",
6161
"reflect-metadata": "^0.1.13 || ^0.2.0",
6262
"rxjs": "7.8.1",
63-
"zod": "^3.14.0"
63+
"zod": "^3.14.0 || ^4.0.0"
6464
},
6565
"devDependencies": {
6666
"@nestjs/common": "10.4.1",

0 commit comments

Comments
 (0)