BillyBob
BillyBob6mo ago

Reusable Component to take router as props / dependency injection. How to type?

for example:
export const Content = async ({ api }) => {
const items = await api.getAll.query({ watched: true })

if (!items) {
return <EmptyState />
}

return (
<CardsContainer>
{items.map((i) => {
return <Card key={i.id} imgSrc={buildImgSrc(i.posterPath)} {...i} />
})}
</CardsContainer>
)
}
export const Content = async ({ api }) => {
const items = await api.getAll.query({ watched: true })

if (!items) {
return <EmptyState />
}

return (
<CardsContainer>
{items.map((i) => {
return <Card key={i.id} imgSrc={buildImgSrc(i.posterPath)} {...i} />
})}
</CardsContainer>
)
}
Content is either movies or series. I have movieRouter and seriesRouter with the same procedures. How can i type api ?
22 Replies
Nick
Nick6mo ago
You linked to the blog post I wrote on this right? The answer is the second half to that If you have any more specific questions I can try to help but this is a broad one
BillyBob
BillyBob6mo ago
No description
BillyBob
BillyBob6mo ago
This expression is not callable.
Each member of the union type '(<T extends MovieFindManyArgs<DefaultArgs>>(args?: SelectSubset<T, MovieFindManyArgs<DefaultArgs>> | undefined) => PrismaPromise<...>) | (<T extends SerieFindManyArgs<...>>(args?: SelectSubset<...> | undefined) => PrismaPromise<...>)' has signatures, but none of those signatures are compatible with each other.
This expression is not callable.
Each member of the union type '(<T extends MovieFindManyArgs<DefaultArgs>>(args?: SelectSubset<T, MovieFindManyArgs<DefaultArgs>> | undefined) => PrismaPromise<...>) | (<T extends SerieFindManyArgs<...>>(args?: SelectSubset<...> | undefined) => PrismaPromise<...>)' has signatures, but none of those signatures are compatible with each other.
Hi @Nick Lucas Thanks for responding. I am not sure how to solve this type issue.
Nick
Nick6mo ago
If you can put together a stackblitz example then I could take a look. It’s typescript foo though so if you don’t understand the solution then you’re going to be in trouble down the line Should be possible anyway
BillyBob
BillyBob6mo ago
hm, i use typescript and have for a couple of years, but if its messy for the sake of readability i might need another solution. I was trying to create a reuseable createService
import { type Prisma, PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

type CreateFunction<T> = (data: T) => Promise<T>
type GetAllFunction<T> = () => Promise<T[]>
type GetByIdFunction<T> = (id: number) => Promise<T | null>
type DeleteFunction<T> = (id: number) => Promise<T | null>
type UpdateFunction<T> = (id: number, data: T) => Promise<T | null>

interface EntityService<T> {
create: CreateFunction<T>
getAll: GetAllFunction<T>
getById: GetByIdFunction<T>
delete: DeleteFunction<T>
update: UpdateFunction<T>
}

const createService = <T>( // This line?
modelName: Uncapitalize<Prisma.ModelName>,
): EntityService<T> => {
const prismaModel = prisma[modelName]

return {
create: (data) => prismaModel.create({ data }),
getAll: () => prismaModel.findMany(),
getById: (id) => prismaModel.findUnique({ where: { id } }),
delete: (id) => prismaModel.delete({ where: { id } }),
update: (id, data) => prismaModel.update({ where: { id }, data }),
}
}

// Usage
const movieService = createService<Prisma.MovieCreateInput>('movie')
const serieService = createService<Prisma.SerieCreateInput>('serie')
import { type Prisma, PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

type CreateFunction<T> = (data: T) => Promise<T>
type GetAllFunction<T> = () => Promise<T[]>
type GetByIdFunction<T> = (id: number) => Promise<T | null>
type DeleteFunction<T> = (id: number) => Promise<T | null>
type UpdateFunction<T> = (id: number, data: T) => Promise<T | null>

interface EntityService<T> {
create: CreateFunction<T>
getAll: GetAllFunction<T>
getById: GetByIdFunction<T>
delete: DeleteFunction<T>
update: UpdateFunction<T>
}

const createService = <T>( // This line?
modelName: Uncapitalize<Prisma.ModelName>,
): EntityService<T> => {
const prismaModel = prisma[modelName]

return {
create: (data) => prismaModel.create({ data }),
getAll: () => prismaModel.findMany(),
getById: (id) => prismaModel.findUnique({ where: { id } }),
delete: (id) => prismaModel.delete({ where: { id } }),
update: (id, data) => prismaModel.update({ where: { id }, data }),
}
}

// Usage
const movieService = createService<Prisma.MovieCreateInput>('movie')
const serieService = createService<Prisma.SerieCreateInput>('serie')
Nick
Nick6mo ago
Yeah I have several of these at work, we have about 25 generated routers. It’s a good pattern once you crack the generics stuff
BillyBob
BillyBob6mo ago
i can put together a stackblitz no problem
Nick
Nick6mo ago
I don’t use Prisma though so I’d need to have a play with a dummy version 😇
BillyBob
BillyBob6mo ago
yeah i was trying to find some base Prisma delegate verison const prismaModel: Prisma.MovieDelegate<DefaultArgs> | Prisma.SerieDelegate<DefaultArgs> | Prisma.UserDelegate<DefaultArgs> to extend from but cannot find anything
Nick
Nick6mo ago
Sometimes you have to F12 into the declarations and really understand what to use that way Prisma must have some common type you can extend from
BillyBob
BillyBob6mo ago
thats what i was hoping for but cant find it but im very rusty with TS too, had a year off. maybe you can see.
Nick
Nick6mo ago
What about “keyof prisma” If you get the model by key then having a generic key is likely enough to calculate your return type and get the concrete model
BillyBob
BillyBob6mo ago
does this work? https://stackblitz.com/~/github.com/lwears/ReelScore checking this hm no i need to fix the stackblitz hm, seems prisma cannot run in a webcontainer / stackblitz
Nick
Nick6mo ago
That’s usually okay, we’re looking at types anyway Afraid I’m at work without access for now though so I’ll need to check it out later tonight
BillyBob
BillyBob6mo ago
no problem, any help appreciated Tried this but still getting the error:
interface Model {
movie: Movie // Prisma Movie type
serie: Serie // Prisma serie type
user: User
}

type ModelName = keyof Model

const createService = (
modelName: ModelName,
): EntityService<Model[ModelName]> => {
const prismaModel = prisma[modelName]
return {
create: (data) => prismaModel.create({ data }),
getAll: () => prismaModel.findMany(),
getById: (id) => prismaModel.findUnique({ where: { id } }),
delete: (id) => prismaModel.delete({ where: { id } }),
update: (id, data) => prismaModel.update({ where: { id }, data }),
}
}
interface Model {
movie: Movie // Prisma Movie type
serie: Serie // Prisma serie type
user: User
}

type ModelName = keyof Model

const createService = (
modelName: ModelName,
): EntityService<Model[ModelName]> => {
const prismaModel = prisma[modelName]
return {
create: (data) => prismaModel.create({ data }),
getAll: () => prismaModel.findMany(),
getById: (id) => prismaModel.findUnique({ where: { id } }),
delete: (id) => prismaModel.delete({ where: { id } }),
update: (id, data) => prismaModel.update({ where: { id }, data }),
}
}
To get the types in stackblitz you need to have a CORS enabling extension installed. CD to apps/api npx prisma generate
BillyBob
BillyBob6mo ago
GitHub
StackBlitz Webcontainer: Prisma fails in multiple ways · Issue #718...
Prisma can be installed in the new StackBlitz Webcontainers with a small workaround of installing a Chrome extension that disables CORS: #7185 (comment) Then you can: Go to https://stackblitz.com/e...
Nick
Nick6mo ago
Yeah this is fairly difficult because they really don't have some generic base class for a Delegate do they? It's definitely possible, just will end up fairly explicit
Nick
Nick6mo ago
So you'll end up needing to define your own MyCrudRepository<T> which implements a handful of methods over a given model, and then you type this router with extends MyCrudRepository<TEntity>
BillyBob
BillyBob6mo ago
ok i think i understand. will give it a shot thanks to be honest it might just be a little too complicated for what i am trying to achieve. I switched from Web Dev to CyberSec a year ago. I'm going to switch back so just want something on my github i can show to recruiters I appreciate your time and help though
Nick
Nick6mo ago
No worries!
More Posts
tRPC mutate call firing two requests with custom linkNode: 18.16 tRPC: 10.38.0 pnpm What's wrong with this custom link that's causing it's addition to aIssue with Fastify / tRPC / AngularWe are trying to setup Angular project with tRPC. In Angular we get following error: ``` Error: nodDrizzle query not working only inside trpcMy drizzle query works inside a regular nextjs api route. The same query running inside trpc throws Server freezes when doing mutations...I have the exact same issue as mentioned in this post: https://discord-questions.trpc.io/m/117697170Create Wrapper only for Procedures that support Infinite QueryGreetings, I'm attempting to create a help that will handle some generic logic I desire for a seriWhy am I getting 'Argument type is not assignable to parameter type ProcedureResolver<unknown>'?I have this warning in my editor (jetbrains InteliJ): ``` Argument type () => any[] is not assignabEfficient way to use tRPC client with auth headers from secure storageWondering if anyone has a recommended pattern on caching the deviceId / authHeader using a React ConUsing tRPC with Expo API Routes feature?Is there a possibility to merge "tRPC Express Adapter" with "Expo API Route Express Deployment"? tRTRPC not working on multi tenant appEverything works on localhost. When I deploy it on the vercel None of the mutations work. Every qTRPCClientError: Unable to transform response from serverHi 🙂 I've just started - so nothing more than boiler plate code but for some reason it doesn't work