DangerZone
DangerZone16mo ago

How can I generate trpc Generic Types?

I am building a trpc library, and as part of my implementation, I need the generic types of t, t.mergeRouters, t.procedure, t.router and t.middleware. I've haven't been able to locate those generic types in the @trpc/server and since trpc has such a complex type structure, would love to see an implementation for a generic set of types for the ones listed about.
2 Replies
Nick
Nick16mo ago
It's best to find another way generally, you can't really get away from coupling your routers and middlewares to the t instance right now, and it's not clear if that could ever be possible while retaining the full benefits of typescript Could you share a bit more about your use case?
DangerZone
DangerZone16mo ago
@Nick Lucas Yes, I am building an implementation for trpc in nestjs, a full opinionated one, for example here is how you'll define a route:
import { Inject } from '@nestjs/common';
import { Router, Query, Mutation } from 'nestjs-trpc';
import { z } from 'zod';
import { UserService } from './user.service';

@Router()
export class UserRouter {
constructor(@Inject(UserService) private readonly userService: UserService) {}

@Query({ output: z.string() })
authors() {
return this.userService.test();
}

@Mutation({ input: z.string(), output: z.string() })
createAuthor(input: string) {
return input;
}
}
import { Inject } from '@nestjs/common';
import { Router, Query, Mutation } from 'nestjs-trpc';
import { z } from 'zod';
import { UserService } from './user.service';

@Router()
export class UserRouter {
constructor(@Inject(UserService) private readonly userService: UserService) {}

@Query({ output: z.string() })
authors() {
return this.userService.test();
}

@Mutation({ input: z.string(), output: z.string() })
createAuthor(input: string) {
return input;
}
}
I have managed to get it all to work, but it is littered with any types since trpc doesn't really give us actual types, for example, if I want the Router() decorator to be able to accept meta, I don't really have a way to type it since its typed dynamically. Here is an example of how I generate the routers:
generateRoutes(
router,
mergeRoutes: MergeRouters<any>,
publicProcedure: ProcedureBuilder<any>,
): MergeRouters<any> {
const routers = this.getRouters();
console.log(routers);
const routerSchema = routers.map((route) => {
const { instance } = route;
const prototype = Object.getPrototypeOf(instance);
const procedures = this.getProcedures(instance, prototype);

const producersSchema = procedures.reduce((obj, procedure) => {
const { name, input, output, type } = procedure;
// Call the method on the instance with proper dependency injection handling
const instanceMethodImplementation = ({ input }) =>
instance[name](input);

let procedureSchema =
input != null ? publicProcedure.input(input) : publicProcedure;

procedureSchema =
output != null ? procedureSchema.output(output) : procedureSchema;

procedureSchema =
type === Procedure.Query
? procedureSchema.query(instanceMethodImplementation)
: (procedureSchema.mutation(instanceMethodImplementation) as any);

obj[name] = procedureSchema;
return obj;
}, {});

return router(producersSchema);
});

return mergeRoutes(...routerSchema);
}
}
generateRoutes(
router,
mergeRoutes: MergeRouters<any>,
publicProcedure: ProcedureBuilder<any>,
): MergeRouters<any> {
const routers = this.getRouters();
console.log(routers);
const routerSchema = routers.map((route) => {
const { instance } = route;
const prototype = Object.getPrototypeOf(instance);
const procedures = this.getProcedures(instance, prototype);

const producersSchema = procedures.reduce((obj, procedure) => {
const { name, input, output, type } = procedure;
// Call the method on the instance with proper dependency injection handling
const instanceMethodImplementation = ({ input }) =>
instance[name](input);

let procedureSchema =
input != null ? publicProcedure.input(input) : publicProcedure;

procedureSchema =
output != null ? procedureSchema.output(output) : procedureSchema;

procedureSchema =
type === Procedure.Query
? procedureSchema.query(instanceMethodImplementation)
: (procedureSchema.mutation(instanceMethodImplementation) as any);

obj[name] = procedureSchema;
return obj;
}, {});

return router(producersSchema);
});

return mergeRoutes(...routerSchema);
}
}
as you can see, it is really difficult to actually use types here, and I am forced to use any for most of the parts in this function, I would love to just pass t and use it throughout the function but t doesn't really lends itself with a generic type that I can actually use. And yes, I know I will lose the compile time benefits of trpc, but as you can see by my answer here: https://github.com/macstr1k3r/trpc-nestjs-adapter/issues/1#issuecomment-1483129949 The best solution for integrating trpc with nestjs is taking the @nestjs/graphql approach and generating the schema in build-time.
GitHub
Brainstorm: decorators for routing procedures? · Issue #1 · macstr1...
I am wondering if there is a way to create custom decorators to route the procedures, i.e. @Controller('api/cats') export class CatsController { @Mutation({ input: z.object({ name: z.string...