sebastienbarre
sebastienbarre2mo ago

Struggling to refactor into sub-routers

Howdy. I'm struggling to refactor the creation of a sub-router into a function that is independent of the server instance.
import { publicProcedure, router } from "./trpc.ts";
import { actorRepo } from "../../../Domain/Actor/Persistence/Drizzle/SQLite/ActorDrizzleSQLiteRepository.ts";

export const appRouter = router({
actor: {
findMany: publicProcedure.query(actorRepo.findMany),
},
});

export type AppRouter = typeof appRouter;
import { publicProcedure, router } from "./trpc.ts";
import { actorRepo } from "../../../Domain/Actor/Persistence/Drizzle/SQLite/ActorDrizzleSQLiteRepository.ts";

export const appRouter = router({
actor: {
findMany: publicProcedure.query(actorRepo.findMany),
},
});

export type AppRouter = typeof appRouter;
I would like to turn the above into:
import { createActorTRPCRouter } from "../../../Domain/Actor/API/tRPC/createActorTRPCRouter.ts";

export const appRouter = router({
actor: createActorTRPCRouter({ router, publicProcedure, actorRepo }),
});
import { createActorTRPCRouter } from "../../../Domain/Actor/API/tRPC/createActorTRPCRouter.ts";

export const appRouter = router({
actor: createActorTRPCRouter({ router, publicProcedure, actorRepo }),
});
But TypeScript is very unhappy about my createActorTRPCRouter.ts:
import type { TRPCProcedureBuilder, TRPCRouterBuilder } from "@trpc/server";
import type { ActorRepositoryInterface } from "../../Persistence/ActorRepositoryInterface.ts";

export const createActorTRPCRouter = ({
router,
publicProcedure,
actorRepo,
}: {
router: TRPCRouterBuilder<any>;
publicProcedure: TRPCProcedureBuilder<any, any, any, any, any, any, any, any>;
actorRepo: ActorRepositoryInterface;
}) =>
router({
findMany: publicProcedure.query(actorRepo.findMany),
});
import type { TRPCProcedureBuilder, TRPCRouterBuilder } from "@trpc/server";
import type { ActorRepositoryInterface } from "../../Persistence/ActorRepositoryInterface.ts";

export const createActorTRPCRouter = ({
router,
publicProcedure,
actorRepo,
}: {
router: TRPCRouterBuilder<any>;
publicProcedure: TRPCProcedureBuilder<any, any, any, any, any, any, any, any>;
actorRepo: ActorRepositoryInterface;
}) =>
router({
findMany: publicProcedure.query(actorRepo.findMany),
});
I'm getting the following error:
Type '((input: any) => Promise<any>) | QueryProcedure<{ input: any; output: any; meta: any; }>' is not assignable to type 'CreateRouterOptions | AnyProcedure | AnyRouter | (() => Promise<AnyRouter>)'.
Type '(input: any) => Promise<any>' is not assignable to type 'CreateRouterOptions | AnyProcedure | AnyRouter | (() => Promise<AnyRouter>)'.
Type '(input: any) => Promise<any>' is missing the following properties from type 'QueryProcedure<any>': _def, meta
Type '((input: any) => Promise<any>) | QueryProcedure<{ input: any; output: any; meta: any; }>' is not assignable to type 'CreateRouterOptions | AnyProcedure | AnyRouter | (() => Promise<AnyRouter>)'.
Type '(input: any) => Promise<any>' is not assignable to type 'CreateRouterOptions | AnyProcedure | AnyRouter | (() => Promise<AnyRouter>)'.
Type '(input: any) => Promise<any>' is missing the following properties from type 'QueryProcedure<any>': _def, meta
7 Replies
Nick
Nick2mo ago
findMany: publicProcedure.query(actorRepo.findMany), findMany: () => publicProcedure.query(actorRepo.findMany), Just a mistake by the looks of it
sebastienbarre
sebastienbarreOP2mo ago
Not sure I'm following you. Looking at the documentation, that not how routers are defined? Adding () => breaks the code at run-time, it's no longer returning the same thing. Note: the code "runs" in the example I gave, but I would love for TypeScript to "understand" it. Is this type of refactoring not possible or not recommended? Essentially I'm trying to refactor code that is independent of a const t = initTRPC.create();, i.e. that doesn't assume the backend exists, and takes a router and a procedure to create a router.
Nick
Nick2mo ago
Oh sorry, yes I misread and thought you were passing it a query directly but it’s a query procedure You probably want to use a generic to take the type of the public procedure rather than typing it as any
Nick
Nick2mo ago
DEV Community
tRPC & React Patterns: Router Factories
This post comes in 2 halves: tRPC Router Factories Consuming Router Factories in a React...
Nick
Nick2mo ago
Though yours is a more advanced case where you also want to inject the procedure, it’s the same technique on the factory function but for the procedure
sebastienbarre
sebastienbarreOP2mo ago
Thanks. I figured a solution. I'm a TypeScript beginner, so I don't know if this is the solution, but it works now. Previously:
export const createActorTRPCRouter = ({
router,
publicProcedure,
actorRepo,
}: {
router: TRPCRouterBuilder<any>;
publicProcedure: TRPCProcedureBuilder<any, any, any, any, any, any, any, any>;
actorRepo: ActorRepositoryInterface;
}) =>
router({
findMany: publicProcedure.query(actorRepo.findMany),
});
export const createActorTRPCRouter = ({
router,
publicProcedure,
actorRepo,
}: {
router: TRPCRouterBuilder<any>;
publicProcedure: TRPCProcedureBuilder<any, any, any, any, any, any, any, any>;
actorRepo: ActorRepositoryInterface;
}) =>
router({
findMany: publicProcedure.query(actorRepo.findMany),
});
Woudn't work, findMany was not parsed correctly by TypeScript. Changing it to:
export const createActorTRPCRouter = ({
router,
publicProcedure,
actorRepo,
}: {
router: TRPCRouterBuilder<any>;
publicProcedure: TRPCProcedureBuilder<
object,
object,
object,
TRPCUnsetMarker,
TRPCUnsetMarker,
TRPCUnsetMarker,
TRPCUnsetMarker,
false
>;
actorRepo: ActorRepositoryInterface;
}) =>
export const createActorTRPCRouter = ({
router,
publicProcedure,
actorRepo,
}: {
router: TRPCRouterBuilder<any>;
publicProcedure: TRPCProcedureBuilder<
object,
object,
object,
TRPCUnsetMarker,
TRPCUnsetMarker,
TRPCUnsetMarker,
TRPCUnsetMarker,
false
>;
actorRepo: ActorRepositoryInterface;
}) =>
Did the trick. I assume one of the any in the previous declaration of TRPCProcedureBuilder would prevent the parsing of the input and ouput types. I looked at what my publicProcedure type was when coming out of trpc.ts and copied it over to match that signature. I'll revisit once I'm more fluent in TypeScript. Thanks! (Unfortunately I don't remember how to mark as solved)
Nick
Nick2mo ago
Yes for a simple publicProcedure that’s probably fine, but if you start using meta or context you might find limitations in the type safety, using a generic similar to TEntity in my blog post but for TProcedure would fix that if it comes to it But really do what works for you (And sorry I’d prefer to give a code example but been away from desk all evening here)

Did you find this page helpful?