Prisma client generated type for create input has weird type requirement for related table field, how to get a working type? - node.js

Use case is simple:
users table
sessions table
Each user can be logged in multiple places therefore they can have multiple sessions -> one to many relationship. in SQL this isn't a big deal/problem, but the generated types available in #prisma/client create a weird type when it comes to the input models.
I'm using NestJS and need TS models based on classes that exist at runtime, so I can make use of decorators on keys for input validation and graphql usage.
I'm creating a model based off of the Prisma.SessionCreateInput coming from #prisma/client.
prisma schema:
model User {
id String #id #default(uuid())
...
sessions Session[]
}
model Session {
id String #id #default(uuid())
user User #relation(fields: [userId], references: [id])
userId String
}
The TS model:
import { Prisma } from '#prisma/client';
export class SessionModel implements Prisma.SessionCreateInput {
id?: string;
userId?: string;
user: Prisma.UserCreateNestedOneWithoutSessionsInput;
}
Now I'm defining the type for user explicitly based on what Prisma is telling me to, in my opinion I don't need any user data there when creating a session, merely the userId.
UserCreateNestedOneWithoutSessionsInput:
type Prisma.UserCreateNestedOneWithoutSessionsInput = {
create?: (Prisma.Without<Prisma.UserCreateWithoutSessionsInput, Prisma.UserUncheckedCreateWithoutSessionsInput> & Prisma.UserUncheckedCreateWithoutSessionsInput) | (Prisma.Without<...> & Prisma.UserCreateWithoutSessionsInput);
connectOrCreate?: Prisma.UserCreateOrConnectWithoutSessionsInput;
connect?: Prisma.UserWhereUniqueInput;
}
which seems some sort of meta type that I very much do not need?
How can I maintain type safety and have these derived TS classes to work with?

Related

Creating entity relationship using a jdl in jhipster

I have the below JDL that I am using to create the jhipster application.
entity AuthClient(auth_client) {
msisdn String required maxlength(255),
email String required unique maxlength(255),
password String required maxlength(255),
lastLogin Instant,
createdAt Instant required,
createdBy Integer,
updatedAt Instant,
updatedBy Integer,
isDeleted Boolean required,
deletedAt Instant,
deletedBy Integer
}
entity AuthToken(auth_token) {
token String required maxlength(255),
appId Integer required,
appVersionName String maxlength(255),
clientId Integer required,
}
entity ClientProfile(client_profile) {
fName String required maxlength(255),
mName String maxlength(255),
lName String required maxlength(255),
gender Integer,
clientId Integer required
}
// Relations
relationship OneToMany {
AuthClient{AuthToken(clientId)} to AuthToken{AuthClient}
}
relationship OneToOne{
ClientProfile{AuthClient} to AuthClient{ClientProfile(clientId)},
}
// Options
service * with serviceClass
paginate * with pagination
dto * with mapstruct
filter *
However, instead of using the variable clientId as the foreign key it creates another field in the database.
I need to use the clientId as the foreign key in this application and not the generated new field Auth Client
Defining a clientId field as you did has no impact on the relationship, these are two separate things.
When you define a relationship, a field is automatically created in the entity and its type is of the related class: it's a reference to an object in the java entity and a foreign key column in the database table.
By default, the foreign key column will be named as auth_client_id and the field will be named as authClient. However, the JDL syntax for relationships lets you configure the relationship name and so modify the generated names.
So, remove all the clientId fields and modify your relationships definitions as follows:
// Relations
relationship OneToMany {
AuthClient to AuthToken{client}
}
relationship OneToOne{
ClientProfile{client} to AuthClient
}
This way you get the foreign key column named as client_id and the field named as client.
You can then define also which field of the related entity will be used for display in the generated UI.
There is more to learn about relationships in the doc: https://www.jhipster.tech/jdl/relationships

Prisma postgres - Unique constraint failed on the fields: (`id`)

For some reason I'm getting the error Unique constraint failed on the fields: (id) when trying to create a new Artist document.
Below is the function I'm calling.
async create(createArtistInput: CreateArtistInput): Promise<Artist> {
console.log(createArtistInput, 'create artist input')
const slug = slugify(createArtistInput.name, {
replacement: '-',
strict: true,
})
return this.db.artist.create({
data: {
name: createArtistInput.name,
spotifyArtistId: createArtistInput.spotifyArtistId,
spotifyArtistName: createArtistInput.spotifyArtistName,
slug,
},
})
}
The console log prints the following response, so I don't understand why the unique constraint of id is failing, as I'm not passing one in. I'm letting the prisma schema handle that.
{
name: 'twofiveone',
spotifyArtistId: '5Fex9xz9rkPqQqMBVtuIrE',
spotifyArtistName: 'twofiveone'
} create artist input
Here is the prisma schema if needed
model Artist {
id Int #id #default(autoincrement())
name String
slug String?
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
spotifyArtistId String?
spotifyArtistName String?
}
Does anyone have any idea what is happening? It's as if I can't create and new artists for some reason.
Autoincrement creates its own sequence starting from 1 which you can read more about here.
If you add random records with the id, then Postgres doesn't know that and it tries to start from 1. If 1 is already present, then it will throw an error.
So the best practice, in this case, is always let the database generate the id for you instead of you adding it manually. Hope this helps
The most likely reason for this is that somehow rows have been manually added with the id 0 at the start.

Prisma client type system creates strong coupling with service methods

I am using prisma ORM with nestjs and it is awesome. Can you please help me understand how can I separate my database layer from my service methods since results produced by prisma client queries are of types generated by prisma client itself ( so i wont be having those types when i shift to lets say typeorm ). how can i prevent such coupling of my service methods returning results of types generated by prisma client and not my custom entities. Hope it makes sense.
The generated #prisma/client library is responsible for generating both the types as well as the custom entity classes. As a result, if you replace Prisma you end up losing both.
Here are two possible workarounds that can decouple the types of your service methods from the Prisma ORM.
Workaround 1: Generate types indepedently of Prisma
With this approach you can get rid of Prisma altogether in the future by manually defining the types for your functions. You can use the types generated by Prisma as reference (or just copy paste them directly). Let me show you an example.
Imagine this is your Prisma Schema.
model Post {
id Int #default(autoincrement()) #id
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
title String #db.VarChar(255)
author User #relation(fields: [authorId], references: [id])
authorId Int
}
model User {
id Int #default(autoincrement()) #id
name String?
posts Post[]
}
You could define a getUserWithPosts function as follows:
// Copied over from '#prisma/client'. Modify as necessary.
type User = {
id: number
name: string | null
}
// Copied over from '#prisma/client'. Modify as necessary.
type Post = {
id: number
createdAt: Date
updatedAt: Date
title: string
authorId: number
}
type UserWithPosts = User & {posts: Post[]}
const prisma = new PrismaClient()
async function getUserWithPosts(userId: number) : Promise<UserWithPosts> {
let user = await prisma.user.findUnique({
where: {
id: userId,
},
include: {
posts: true
}
})
return user;
}
This way, you should be able to get rid of Prisma altogether and replace it with an ORM of your choice. One notable drawback is this approach increases the maintenance burden upon changes to the Prisma schema as you need to manually maintain the types.
Workaround 2: Generate types using Prisma
You could keep Prisma in your codebase simply to generate the #prisma/client and use it for your types. This is possible with the Prisma.validator type that is exposed by the #prisma/client. Code snippet to demonstrate this for the exact same function:
// 1: Define the validator
const userWithPosts = Prisma.validator<Prisma.UserArgs>()({
include: { posts: true },
})
// 2: This type will include a user and all their posts
type UserWithPosts = Prisma.UserGetPayload<typeof userWithPosts>
// function is same as before
async function getUserWithPosts(userId: number): Promise<UserWithPosts> {
let user = await prisma.user.findUnique({
where: {
id: userId,
},
include: {
posts: true
}
})
return user;
}
Additionally, you can always keep the Prisma types updated to your current database state using the Introspect feature. This will work even for changes you have made with other ORMS/Query Builders/SQL.
If you want more details, a lot of what I've mentioned here is touched opon in the Operating against partial structures of your model types concept guide in the Prisma Docs.
Finally, if this dosen't solve your problem, I would request that you open a new issue with the problem and your use case. This really helps us to track and prioritize problems that people are facing.

Access TypeORM repository via it's name (string)

For example, I have entities like Photo, Company, Car, etc. I know that they have columns with same names and I can access it via QueryBuilder. I don't know which repository I will process, but it's passed like string parameter to my function. Like this:
function haveAccess(user, entity, entityId) {
// model is string like 'photo', 'company', 'car'
}
In fact, I want to check if user have access to the entity with given ID via separate function not binded to just one Entity.
Is it possible to initialize Repository or QueryBuilder for Entity in TypeORM just by string somehow?
You can get repositories like this:
import {getRepository} from "typeorm";
import {User} from "./entity/User";
const userRepository = getRepository(User); // you can also get it via getConnection().getRepository() or getManager().getRepository()
connection.getRepository() can take three things, an Object (annotated with #Entity), an EntitySchema, or a string.
getRepository<Entity>(target: ObjectType<Entity> | EntitySchema<Entity> | string): Repository<Entity>;
So in typescript that could be:
connection.getRepository<MyEntity>(MyEntity)

type-graphql entities how to omit fields from JSON.stringify?

We are using https://github.com/MichalLytek/type-graphql to define our graphql schema When we serialize the raw typescript entity object this doesn't respect the various field annotations in our GQL entities and ends up leaking unwanted data. Example below a Profile entity class
import { Field, Int, ObjectType } from 'type-graphql'
import { Column, Entity, ManyToOne, OneToMany } from 'typeorm'
import { Account } from '../account/account.entity'
export class Profile {
#Field()
#Column({ unique: true })
public username: string
#Field()
#Column()
public name: string
// Relations
#Column()
public accountId: string
#ManyToOne(type => Account, account => account.profiles, { eager: true })
public account: Account
}
account has sensitive data. When we JSON.stringify a Profile reference we don't want account output. Account is not annotated with #Field and we expect it would not be output.
The decorators used by type-graphql only exist to instruct type-graphql how to translate your class to a GraphQL type -- they are not going to somehow impact how an instance of the class is serialized by a native function like JSON.stringify.
In the context of your schema, the account won't ever be returned in the response unless you explicitly create a field for it, even if the Profile instance used by your resolvers has that property. This is a symptom of how field resolution works in GraphQL.js. However, a Profile instance will always have an account property on it because that's what you've defined as part of your class.
It's unclear from your question why you're calling stringify in the first place, but assuming it's to use in some other context, like logging, then you'll want to expose your own method for serializing the instance that limits which properties are returned. This can be done easily using something like lodash's pick or omit.
serialize () {
return _.omit(this, ['account'])
}

Resources