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