From b02a9b7f6845ee3c0716a71447be19e85d0cf23c Mon Sep 17 00:00:00 2001 From: Romain Lenzotti Date: Wed, 16 Oct 2024 07:58:47 +0200 Subject: [PATCH] fix(prisma): fix circular reference when column is optional Closes: #2863 --- packages/orm/prisma/package.json | 1 + .../transform/transformScalarToType.spec.ts | 8 +- .../transform/transformScalarToType.ts | 10 +- .../__snapshots__/generateModels.spec.ts.snap | 118 +----------------- .../orm/prisma/test/circular-ref/package.json | 10 ++ .../test/circular-ref/prisma/schema.prisma | 32 +++++ .../snapshots/generate_code/enums/index.ts | 2 +- .../generate_code/interfaces/index.ts | 2 +- .../generate_code/models/PostModel.ts | 10 +- .../generate_code/models/UserModel.ts | 14 ++- .../snapshots/generate_code/models/index.ts | 4 +- .../repositories/PostsRepository.ts | 24 ++-- .../repositories/UsersRepository.ts | 24 ++-- .../generate_code/repositories/index.ts | 4 +- .../generate_code/services/PrismaService.ts | 8 +- 15 files changed, 106 insertions(+), 165 deletions(-) create mode 100644 packages/orm/prisma/test/circular-ref/package.json create mode 100644 packages/orm/prisma/test/circular-ref/prisma/schema.prisma diff --git a/packages/orm/prisma/package.json b/packages/orm/prisma/package.json index a963ca88b42..eb72d5f530d 100644 --- a/packages/orm/prisma/package.json +++ b/packages/orm/prisma/package.json @@ -28,6 +28,7 @@ "generate:postgres:esm": "yarn build && cd test/postgres-esm && prisma -v && prisma generate", "generate:mongo": "yarn build && cd test/mongo && prisma -v && prisma generate", "generate:mongo:esm": "yarn build && cd test/mongo-esm && prisma -v && prisma generate", + "generate:circular:esm": "yarn build && cd test/circular-ref && prisma -v && prisma generate", "test:ci": "vitest run --coverage.thresholds.autoUpdate=true" }, "dependencies": { diff --git a/packages/orm/prisma/src/generator/transform/transformScalarToType.spec.ts b/packages/orm/prisma/src/generator/transform/transformScalarToType.spec.ts index 46339b8b77c..7142d34e6ab 100644 --- a/packages/orm/prisma/src/generator/transform/transformScalarToType.spec.ts +++ b/packages/orm/prisma/src/generator/transform/transformScalarToType.spec.ts @@ -107,11 +107,11 @@ describe("transformScalarToType()", () => { }); // @ts-ignore field.model.name = "User"; - expect(transformScalarToType(field, ctx)).toEqual("UserModel | null"); - expect(field.model.addImportDeclaration).not.toHaveBeenCalled(); + expect(transformScalarToType(field, ctx)).toEqual("Relation | null"); + expect(field.model.addImportDeclaration).toHaveBeenCalledWith("@tsed/core", "Relation", true); }); - it("should transform User to User", () => { + it("should transform Role to User", () => { const ctx = createContextFixture(); const field = createDmmfFieldFixture({ kind: "object", @@ -119,7 +119,7 @@ describe("transformScalarToType()", () => { }); // @ts-ignore field.model.name = "User"; - expect(transformScalarToType(field, ctx)).toEqual("RoleModel | null"); + expect(transformScalarToType(field, ctx)).toEqual("Relation | null"); expect(field.model.addImportDeclaration).toHaveBeenCalledWith("./RoleModel", "RoleModel"); }); }); diff --git a/packages/orm/prisma/src/generator/transform/transformScalarToType.ts b/packages/orm/prisma/src/generator/transform/transformScalarToType.ts index 5bbfbf1a4d1..5646f135e3b 100644 --- a/packages/orm/prisma/src/generator/transform/transformScalarToType.ts +++ b/packages/orm/prisma/src/generator/transform/transformScalarToType.ts @@ -31,6 +31,11 @@ export function transformScalarToType(field: DmmfField, ctx: TransformContext): TSType += "[]"; } + if (!isList && hasCircularRef && ["inputObjectTypes", "enumTypes"].includes(location)) { + hasCircularRef && !isList && field.model.addImportDeclaration("@tsed/core", "Relation", true); + TSType = `Relation<${TSType}>`; + } + if (!isRequired) { if (model.isInputType) { TSType += " | undefined"; @@ -41,10 +46,7 @@ export function transformScalarToType(field: DmmfField, ctx: TransformContext): TSType += " | null"; } - if (isRequired && !isList && !isNullable && hasCircularRef && ["inputObjectTypes", "enumTypes"].includes(location)) { - hasCircularRef && !isList && field.model.addImportDeclaration("@tsed/core", "Relation", true); - TSType = `Relation<${TSType}>`; - } + return TSType; } diff --git a/packages/orm/prisma/src/generator/utils/__snapshots__/generateModels.spec.ts.snap b/packages/orm/prisma/src/generator/utils/__snapshots__/generateModels.spec.ts.snap index d10497cc36c..637d3525e70 100644 --- a/packages/orm/prisma/src/generator/utils/__snapshots__/generateModels.spec.ts.snap +++ b/packages/orm/prisma/src/generator/utils/__snapshots__/generateModels.spec.ts.snap @@ -21,6 +21,7 @@ exports[`generateModels > should generate models (post) 1`] = ` "import { Post } from "../client/index"; import { Integer, Required, Property, Allow } from "@tsed/schema"; import { UserModel } from "./UserModel"; +import type { Relation } from "@tsed/core"; export class PostModel implements Post { @Property(Number) @@ -30,7 +31,7 @@ export class PostModel implements Post { @Property(() => UserModel) @Allow(null) - user: UserModel | null; + user: Relation | null; @Property(Number) @Integer() @@ -44,6 +45,7 @@ export class PostModel implements Post { exports[`generateModels > should generate models (user) 1`] = ` "import { User } from "../client/index"; import { Integer, Required, Property, Groups, Format, Email, Description, Allow, Enum, CollectionOf } from "@tsed/schema"; +import type { Relation } from "@tsed/core"; import { Role } from "../enums/index"; import { PostModel } from "./PostModel"; @@ -84,121 +86,11 @@ export class UserModel implements User { @Property(() => UserModel) @Allow(null) - successor: UserModel | null; + successor: Relation | null; @Property(() => UserModel) @Allow(null) - predecessor: UserModel | null; - - @Required() - @Enum(Role) - role: Role; - - @CollectionOf(() => PostModel) - @Required() - posts: PostModel[]; - - @CollectionOf(String) - @Required() - keywords: string[]; - - @Property(Object) - @Required() - biography: any; -} - -" -`; - -exports[`generateModels should generate models (info) 1`] = ` -"import { Info } from \\"../client/index\\"; -import { Required, Property } from \\"@tsed/schema\\"; - -export class InfoModel implements Info { - @Property(String) - @Required() - firstName: string; - - @Property(String) - @Required() - lastName: string; -} - -" -`; - -exports[`generateModels should generate models (post) 1`] = ` -"import { Post } from \\"../client/index\\"; -import { Integer, Required, Property, Allow } from \\"@tsed/schema\\"; -import { UserModel } from \\"./UserModel\\"; - -export class PostModel implements Post { - @Property(Number) - @Integer() - @Required() - id: number; - - @Property(() => UserModel) - @Allow(null) - user: UserModel | null; - - @Property(Number) - @Integer() - @Allow(null) - userId: number | null; -} - -" -`; - -exports[`generateModels should generate models (user) 1`] = ` -"import { User } from \\"../client/index\\"; -import { Integer, Required, Property, Groups, Format, Email, Description, Allow, Enum, CollectionOf } from \\"@tsed/schema\\"; -import { Role } from \\"../enums/index\\"; -import { PostModel } from \\"./PostModel\\"; - -export class UserModel implements User { - @Property(Number) - @Integer() - @Required() - @Groups(\\"!creation\\") - id: number; - - @Property(Date) - @Format(\\"date-time\\") - @Required() - createdAt: Date; - - @Property(String) - @Required() - @Email() - @Description(\\"User email. This email must be unique!\\") - email: string; - - @Property(Number) - @Allow(null) - weight: number | null; - - @Property(Boolean) - @Allow(null) - is18: boolean | null; - - @Property(String) - @Allow(null) - name: string | null; - - @Property(Number) - @Integer() - @Allow(null) - successorId: number | null; - - @Property(() => UserModel) - @Allow(null) - successor: UserModel | null; - - @Property(() => UserModel) - @Allow(null) - predecessor: UserModel | null; + predecessor: Relation | null; @Required() @Enum(Role) diff --git a/packages/orm/prisma/test/circular-ref/package.json b/packages/orm/prisma/test/circular-ref/package.json new file mode 100644 index 00000000000..d72ff0fbc66 --- /dev/null +++ b/packages/orm/prisma/test/circular-ref/package.json @@ -0,0 +1,10 @@ +{ + "name": "@tsed/prisma-test", + "type": "commonjs", + "devDependencies": { + "prisma": "^4.0.0" + }, + "dependencies": { + "@prisma/client": "^4.0.0" + } +} diff --git a/packages/orm/prisma/test/circular-ref/prisma/schema.prisma b/packages/orm/prisma/test/circular-ref/prisma/schema.prisma new file mode 100644 index 00000000000..41ec7537a96 --- /dev/null +++ b/packages/orm/prisma/test/circular-ref/prisma/schema.prisma @@ -0,0 +1,32 @@ + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" + binaryTargets = ["native", "windows", "debian-openssl-1.1.x"] + output = "../prisma/generated/client" +} + +generator tsed { + provider = "node ../../lib/cjs/generator.js" + output = "../prisma/generated/tsed" + emitDMMF = true +} + +model Conversation { + id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid + assignmentId String? @map("assignment_id") @db.Uuid + assignment Assignment? @relation(fields: [assignmentId], references: [id], onDelete: NoAction, onUpdate: NoAction) + + @@map("conversation") +} + +model Assignment { + id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid + conversations Conversation[] + + @@map("assignment") +} diff --git a/packages/orm/prisma/test/snapshots/generate_code/enums/index.ts b/packages/orm/prisma/test/snapshots/generate_code/enums/index.ts index 148ee216f63..a6b89e5435d 100644 --- a/packages/orm/prisma/test/snapshots/generate_code/enums/index.ts +++ b/packages/orm/prisma/test/snapshots/generate_code/enums/index.ts @@ -1 +1 @@ -export {Role} from "./Role"; +export { Role } from "./Role"; diff --git a/packages/orm/prisma/test/snapshots/generate_code/interfaces/index.ts b/packages/orm/prisma/test/snapshots/generate_code/interfaces/index.ts index 55bea49090d..0250d13da8d 100644 --- a/packages/orm/prisma/test/snapshots/generate_code/interfaces/index.ts +++ b/packages/orm/prisma/test/snapshots/generate_code/interfaces/index.ts @@ -1,4 +1,4 @@ -import {Prisma} from "../client/index"; +import { Prisma } from "../client/index"; declare global { namespace TsED { diff --git a/packages/orm/prisma/test/snapshots/generate_code/models/PostModel.ts b/packages/orm/prisma/test/snapshots/generate_code/models/PostModel.ts index e7c83adc521..7d301c377c4 100644 --- a/packages/orm/prisma/test/snapshots/generate_code/models/PostModel.ts +++ b/packages/orm/prisma/test/snapshots/generate_code/models/PostModel.ts @@ -1,6 +1,7 @@ -import {Post} from "../client/index"; -import {Integer, Required, Property, Allow} from "@tsed/schema"; -import {UserModel} from "./UserModel"; +import { Post } from "../client/index"; +import { Integer, Required, Property, Allow } from "@tsed/schema"; +import { UserModel } from "./UserModel"; +import type { Relation } from "@tsed/core"; export class PostModel implements Post { @Property(Number) @@ -10,10 +11,11 @@ export class PostModel implements Post { @Property(() => UserModel) @Allow(null) - user: UserModel | null; + user: Relation | null; @Property(Number) @Integer() @Allow(null) userId: number | null; } + diff --git a/packages/orm/prisma/test/snapshots/generate_code/models/UserModel.ts b/packages/orm/prisma/test/snapshots/generate_code/models/UserModel.ts index c77d5b92cf9..7a33004ea41 100644 --- a/packages/orm/prisma/test/snapshots/generate_code/models/UserModel.ts +++ b/packages/orm/prisma/test/snapshots/generate_code/models/UserModel.ts @@ -1,7 +1,8 @@ -import {User} from "../client/index"; -import {Integer, Required, Property, Groups, Format, Email, Description, Allow, Enum, CollectionOf} from "@tsed/schema"; -import {Role} from "../enums/index"; -import {PostModel} from "./PostModel"; +import { User } from "../client/index"; +import { Integer, Required, Property, Groups, Format, Email, Description, Allow, Enum, CollectionOf } from "@tsed/schema"; +import type { Relation } from "@tsed/core"; +import { Role } from "../enums/index"; +import { PostModel } from "./PostModel"; export class UserModel implements User { @Property(Number) @@ -40,11 +41,11 @@ export class UserModel implements User { @Property(() => UserModel) @Allow(null) - successor: UserModel | null; + successor: Relation | null; @Property(() => UserModel) @Allow(null) - predecessor: UserModel | null; + predecessor: Relation | null; @Required() @Enum(Role) @@ -62,3 +63,4 @@ export class UserModel implements User { @Required() biography: any; } + diff --git a/packages/orm/prisma/test/snapshots/generate_code/models/index.ts b/packages/orm/prisma/test/snapshots/generate_code/models/index.ts index aa50c049b83..3090a3fa09f 100644 --- a/packages/orm/prisma/test/snapshots/generate_code/models/index.ts +++ b/packages/orm/prisma/test/snapshots/generate_code/models/index.ts @@ -1,2 +1,2 @@ -export {PostModel} from "./PostModel"; -export {UserModel} from "./UserModel"; +export { PostModel } from "./PostModel"; +export { UserModel } from "./UserModel"; diff --git a/packages/orm/prisma/test/snapshots/generate_code/repositories/PostsRepository.ts b/packages/orm/prisma/test/snapshots/generate_code/repositories/PostsRepository.ts index a42f797cf02..2821ab7f81c 100644 --- a/packages/orm/prisma/test/snapshots/generate_code/repositories/PostsRepository.ts +++ b/packages/orm/prisma/test/snapshots/generate_code/repositories/PostsRepository.ts @@ -1,9 +1,9 @@ -import {isArray} from "@tsed/core"; -import {deserialize} from "@tsed/json-mapper"; -import {Injectable, Inject} from "@tsed/di"; -import {PrismaService} from "../services/PrismaService"; -import {Prisma, Post} from "../client/index"; -import {PostModel} from "../models/index"; +import { isArray } from "@tsed/core"; +import { deserialize } from "@tsed/json-mapper"; +import { Injectable, Inject } from "@tsed/di"; +import { PrismaService } from "../services/PrismaService"; +import { Prisma, Post } from "../client/index"; +import { PostModel } from "../models/index"; @Injectable() export class PostsRepository { @@ -11,15 +11,15 @@ export class PostsRepository { protected prisma: PrismaService; get collection() { - return this.prisma.post; + return this.prisma.post } get groupBy() { - return this.collection.groupBy.bind(this.collection); + return this.collection.groupBy.bind(this.collection) } protected deserialize(obj: null | Post | Post[]): T { - return deserialize(obj, {type: PostModel, collectionType: isArray(obj) ? Array : undefined}); + return deserialize(obj, { type: PostModel, collectionType: isArray(obj) ? Array : undefined }) } async findUnique(args: Prisma.PostFindUniqueArgs): Promise { @@ -58,14 +58,14 @@ export class PostsRepository { } deleteMany(args: Prisma.PostDeleteManyArgs) { - return this.collection.deleteMany(args); + return this.collection.deleteMany(args) } updateMany(args: Prisma.PostUpdateManyArgs) { - return this.collection.updateMany(args); + return this.collection.updateMany(args) } aggregate(args: Prisma.PostAggregateArgs) { - return this.collection.aggregate(args); + return this.collection.aggregate(args) } } diff --git a/packages/orm/prisma/test/snapshots/generate_code/repositories/UsersRepository.ts b/packages/orm/prisma/test/snapshots/generate_code/repositories/UsersRepository.ts index 7566942f840..568b22d6e6c 100644 --- a/packages/orm/prisma/test/snapshots/generate_code/repositories/UsersRepository.ts +++ b/packages/orm/prisma/test/snapshots/generate_code/repositories/UsersRepository.ts @@ -1,9 +1,9 @@ -import {isArray} from "@tsed/core"; -import {deserialize} from "@tsed/json-mapper"; -import {Injectable, Inject} from "@tsed/di"; -import {PrismaService} from "../services/PrismaService"; -import {Prisma, User} from "../client/index"; -import {UserModel} from "../models/index"; +import { isArray } from "@tsed/core"; +import { deserialize } from "@tsed/json-mapper"; +import { Injectable, Inject } from "@tsed/di"; +import { PrismaService } from "../services/PrismaService"; +import { Prisma, User } from "../client/index"; +import { UserModel } from "../models/index"; @Injectable() export class UsersRepository { @@ -11,15 +11,15 @@ export class UsersRepository { protected prisma: PrismaService; get collection() { - return this.prisma.user; + return this.prisma.user } get groupBy() { - return this.collection.groupBy.bind(this.collection); + return this.collection.groupBy.bind(this.collection) } protected deserialize(obj: null | User | User[]): T { - return deserialize(obj, {type: UserModel, collectionType: isArray(obj) ? Array : undefined}); + return deserialize(obj, { type: UserModel, collectionType: isArray(obj) ? Array : undefined }) } async findUnique(args: Prisma.UserFindUniqueArgs): Promise { @@ -58,14 +58,14 @@ export class UsersRepository { } deleteMany(args: Prisma.UserDeleteManyArgs) { - return this.collection.deleteMany(args); + return this.collection.deleteMany(args) } updateMany(args: Prisma.UserUpdateManyArgs) { - return this.collection.updateMany(args); + return this.collection.updateMany(args) } aggregate(args: Prisma.UserAggregateArgs) { - return this.collection.aggregate(args); + return this.collection.aggregate(args) } } diff --git a/packages/orm/prisma/test/snapshots/generate_code/repositories/index.ts b/packages/orm/prisma/test/snapshots/generate_code/repositories/index.ts index b5023f49f35..9a5bbdda0f3 100644 --- a/packages/orm/prisma/test/snapshots/generate_code/repositories/index.ts +++ b/packages/orm/prisma/test/snapshots/generate_code/repositories/index.ts @@ -1,2 +1,2 @@ -export {PostsRepository} from "./PostsRepository"; -export {UsersRepository} from "./UsersRepository"; +export { PostsRepository } from "./PostsRepository"; +export { UsersRepository } from "./UsersRepository"; diff --git a/packages/orm/prisma/test/snapshots/generate_code/services/PrismaService.ts b/packages/orm/prisma/test/snapshots/generate_code/services/PrismaService.ts index df371b86e60..ddd83788fa8 100644 --- a/packages/orm/prisma/test/snapshots/generate_code/services/PrismaService.ts +++ b/packages/orm/prisma/test/snapshots/generate_code/services/PrismaService.ts @@ -1,6 +1,6 @@ -import {Inject, Injectable, Configuration, OnInit, OnDestroy} from "@tsed/di"; -import {Logger} from "@tsed/logger"; -import {PrismaClient} from "../client/index"; +import { Inject, Injectable, Configuration, OnInit, OnDestroy } from "@tsed/di"; +import { Logger } from "@tsed/logger"; +import { PrismaClient } from "../client/index"; @Injectable() export class PrismaService extends PrismaClient implements OnInit, OnDestroy { @@ -8,7 +8,7 @@ export class PrismaService extends PrismaClient implements OnInit, OnDestroy { protected logger: Logger; constructor(@Configuration() settings: Configuration) { - super(settings.get("prisma")); + super(settings.get('prisma')); } async $onInit(): Promise {