BillyBob
BillyBob8mo 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
Nick8mo 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
BillyBob8mo ago
No description
BillyBob
BillyBob8mo 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
Nick8mo 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
BillyBob8mo 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
Nick8mo 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
BillyBob8mo ago
i can put together a stackblitz no problem
Nick
Nick8mo ago
I don’t use Prisma though so I’d need to have a play with a dummy version 😇
BillyBob
BillyBob8mo 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
Nick8mo 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
BillyBob8mo 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
Nick8mo 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
BillyBob8mo 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
Nick8mo 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