Is this possible for Prisma, too? I would be very handy to be able to have every instance of a model have some sort of method attached to it.
","upvoteCount":17,"answerCount":11,"acceptedAnswer":{"@type":"Answer","text":"Hey @florianmartens 👋
\nAs Prisma is not an ORM as described here, it just returns a plain JS object on querying. So it's not possible to add methods on the model as there is no Instance, it's just a plain object that's returned.
-
Hi, so, a pretty common pattern for ORMs is that you can typically attach methods to every instance of a model. In type ORM you'd do that via the entity:
Is this possible for Prisma, too? I would be very handy to be able to have every instance of a model have some sort of method attached to it. |
Beta Was this translation helpful? Give feedback.
-
Hey @florianmartens 👋 |
Beta Was this translation helpful? Give feedback.
-
I have a similar issue. The following works but is relatively complicated. @ryands17 is this what you had in mind? Is there really no easier solution? It seems like a common requirement if you don't just want to have intelligent domain objects. import { User as UserModel } from '@prisma/client'
interface User extends UserModel {
verifyPassword (): boolean
}
map (user: UserModel): User {
return { ...user, verifyPassword: () => { return false } }
}
function(...) {
const user = await prisma.user.findUnique({...})
const userExt = map(user)
userExt.verifyPassword()
} |
Beta Was this translation helpful? Give feedback.
-
Is there any solution nowadays with Prisma 2? |
Beta Was this translation helpful? Give feedback.
-
another hacky way around this is a wrapper class that contains the prisma object as an internal but this means none of the prisma methods are exposed on the wrapper class, so a lot of tedious mapping/duplication is needed. export class CPlayer {
data?: Player | null
constructor(data?: Player | null) {
this.data = data
}
static async findOrCreate(query: Prisma.PlayerWhereInput): Promise<CPlayer> {
let data = await prisma.player.findFirst({ where: query })
if (!data) {
const input = query as Prisma.PlayerCreateInput
data = await prisma.player.create({ data: input })
}
// construct object and return instance
return new CPlayer(data)
}
// move a player
async moveTo(px: number, py: number) {
this.data = await prisma.player.update({
where: { id: this.data!.id },
data: { px, py },
})
console.log('new data', this.data)
}
}
// use it like
const player = CPlayer.findOrCreate({name: 'bob'}) |
Beta Was this translation helpful? Give feedback.
-
Came to find the same kind of tricks!
What about using Proxy? class BaseModel {
static makeModel(modelData) {
return new Proxy(modelData, new this);
}
get(target, property) {
if (property in this) {
return this[property];
}
return target[property];
}
has(target, property) {
return (property in this) || (property in target)
}
}
export UserModel extends BaseModel {
verifyPassword(providedPassword) {
// Don't do this at home!
return this.password === providedPassword;
}
} The You'd use it this way: const user = UserModel.makeModel(await prisma.user.findUnique({...}))
if (!user.verifyPassword(providedPassword)) {
throw new Error("Invalid password");
} Curious to get your feedback on this; will try to start using this in a project of mine! |
Beta Was this translation helpful? Give feedback.
-
using proxy is certainly an interesting idea, but it seems quite an ugly API into instance creation, wrapping and passing an already queried prisma object: const user = UserModel.makeModel(await prisma.user.findUnique({ name: 'bob' })) I would prefer if there was a way to add all the prisma methods to the User class so we can do like this const user = UserModel.makeModel({ name: 'bob' })
// or
const user = UserModel.findUnique({ name: 'bob' }) and all other methods could then be patched onto the User class. A problem I ran into before is that all the prisma model generated types are unique, there is no hierarchy or baseClass or common interface for them. eg these are all unique and share nothing: So there wasn't any way to have my own BaseClass that would implement the finder methods in a generic way, so every class I create has to have a ton of boilerplate, basically all the methods all the time. There maybe a way with metaprogramming to generate all those methods, but this is more fragile magic piled on top of an already opaque build prisma generator process, and feels hard to maintain. |
Beta Was this translation helpful? Give feedback.
-
First of all, i'm a really newbie in JS (i've just discovered proxies^^), TS and prisma ^^ But found this post (and others) trying to mix differents methods to make my own trick (seems working as i want, i have to try furthermore), using an intern data object (prisma result) and two layers of proxies : [prismaModel] > [data] > [class handler]
Then, in use :
What do you think ? |
Beta Was this translation helpful? Give feedback.
-
Any update? UpdateFound this https://www.prisma.io/docs/concepts/components/prisma-client/client-extensions |
Beta Was this translation helpful? Give feedback.
-
UPDATE Adding a import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs';
const prisma = new PrismaClient().$extends({
result: {
user: {
validatePassword: {
needs: { password: true },
compute: user => async (rawPassword: string) => {
return await bcrypt.compare(rawPassword, user.password);
},
},
},
},
});
async function main() {
const user = await prisma.user.findFirst({ where: { email: '[email protected]' } });
if (user) {
const isValidPassword = user.validatePassword('mypassword');
}
} And you can also automatically hash the password when creating or updating a user using custom Prisma client queries import { PrismaClient, Prisma } from '@prisma/client';
const prisma = new PrismaClient().$extends({
query: {
user: {
async $allOperations({ args, model, operation, query }) {
if ((operation === 'create' || operation === 'update') && 'data' in args) {
const data = args.data as Prisma.UserCreateInput | Prisma.UserUpdateInput;
if (data.password) {
data.password = hashPassword(data.password);
}
}
return query(args);
},
},
},
}); Credit to @angelhdzmultimedia |
Beta Was this translation helpful? Give feedback.
-
You can add the validatePassword method as a result extension. Prisma extensions can be made for: I add my validatePassword as a So I would define a login.prisma.ts extension and a register.prisma.ts extension with the certainity that using these clients won't include the user's password. const user = await loginPrisma.user.findOne({email})
if (!user) {
throw new NotFoundException('Email not found')
}
const isPasswordValid: boolean = await user.validatePassword( loginDto.password )
if (!isPasswordValid) {
throw new UnauthorizedException('Email or password not valid')
}
return user // no password! |
Beta Was this translation helpful? Give feedback.
-
Hi @florianmartens, To keep our discussions organized and focused on the most relevant topics, we’re reviewing and tidying up our backlog. As part of this process, we’re closing discussions that have already been marked as answered but remain open. If this discussion still requires further input or clarification, feel free to reopen it or start a new one with updated details. Your contributions are invaluable to the community, and we’re here to help! For more details about our priorities and vision for the future of Prisma ORM, check out our latest blog post: https://www.prisma.io/blog/prisma-orm-manifesto. Thank you for your understanding and ongoing support of the Prisma community! |
Beta Was this translation helpful? Give feedback.
Hey @florianmartens 👋
As Prisma is not an ORM as described here, it just returns a plain JS object on querying. So it's not possible to add methods on the model as there is no Instance, it's just a plain object that's returned.