Brakebein / prisma-generator-nestjs-dto

Generates NestJS DTO classes from Prisma Schema
Apache License 2.0
47 stars 25 forks source link

throw new Error(`A circular dependency has been detected (property key: "${key}"). #39

Closed dwcahyo23 closed 2 months ago

dwcahyo23 commented 5 months ago

@Brakebein i have issue, i dont know how to solve it.

v : "@brakebein/prisma-generator-nestjs-dto": "^1.21.0", nest: "@nestjs/common": "^10.3.4",

throw new Error(`A circular dependency has been detected (property key: "${key}"). Please, make sure that each side of a bidirectional relationships are using lazy resolvers ("type: () => ClassType").`);
                  ^
Error: A circular dependency has been detected (property key: "zMch"). Please, make sure that each side of a bidirectional relationships are using lazy resolvers ("type: () => ClassType").

this my schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
  // directUrl= env("DIRECT_URL")
}

generator nestjsDto {
 provider                        = "prisma-generator-nestjs-dto"
  output                          = "../src/modules/models"
  outputToNestJsResourceStructure = "true"
  flatResourceStructure           = "false"
  exportRelationModifierClasses   = "true"
  reExport                        = "false"
  createDtoPrefix                 = "Create"
  updateDtoPrefix                 = "Update"
  dtoSuffix                       = "Dto"
  entityPrefix                    = ""
  entitySuffix                    = "Entity"
  fileNamingStyle                 = "camel"
  classValidation                 = "true"
  noDependencies                  = "false"
  outputType                      = "class"
  definiteAssignmentAssertion     = "true"
  prettier                        = "true"

}

model UserRole {
  id     Int    @id @default(autoincrement())
  role   Roles
  userId String
  user User @relation(fields: [userId], references: [id])
}

enum Roles {
  ADMIN
  MANAGER
  USER
}

model MstZ20 {
  /// @description Z20 ID
  zId  Int     @id @default(autoincrement())
  /// @description Z20 CODE
  zCd  String  @unique @db.VarChar(15)
  /// @description Z20 MAC ADDRESS
  zMac String? 
  /// @description Z20 BLE ADDRESS
  zBle String? 

  zMch LinkZMch?

  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional  
  createdBy String   @default("system") 
  updatedBy String   @default("system") 
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model MstMtv {
  /// @description MT100 ID
  mtId  Int     @id @default(autoincrement())
  /// @description MT100 CODE
  mtCd  String  @unique @db.VarChar(15)
  /// @description MT100 MAC ADDRESS
  mtMac String? 
  /// @description MT100 BLE ADDRESS
  mtBle String? 

  linkZmch LinkZMch[]

  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  createdBy String   @default("system") 
  updatedBy String   @default("system") 
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model MstWo {
  /// @description WO ID
  /// @IsNotEmpty()
  woId String @id
  /// @description WO COM
  /// @IsNotEmpty()
  woCom String
  /// @description WO DEPT
  /// @IsNotEmpty()
  woDep String
  /// @description WO MACHINE
  /// @IsNotEmpty()
  woMc String
  /// @description WO PRIO
  /// @IsNotEmpty()
  woPri String
  /// @description WO START AT
  /// @IsNotEmpty()
  woAt DateTime
  /// @description WO STOP AT
  /// @IsOptional()  
  woStopAt DateTime?
  /// @description WO INPUT AT
  /// @IsNotEmpty()
  woAppendAt DateTime
  /// @description WO VERIFY AT
  /// @IsNotEmpty()
  woCheckAt DateTime

  /// @description WO USER
  /// @IsNotEmpty()
  woUser String
  /// @description WO PROBLEM
  /// @IsNotEmpty()
  woMemo String
  /// @description WO REMAKRS
  /// @IsNotEmpty()
  woRemarks String

  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  createdBy String   @default("system") 
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  updatedBy String   @default("system") 
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  createdAt DateTime @default(now())
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  updatedAt DateTime @updatedAt
}

model LinkZMch {
  /// @description LINK ID
  ZmId Int @id @default(autoincrement())  

  /// @description Z20 CODE
  /// @IsNotEmpty()  
  zCd    String @unique
  /// @DtoRelationRequired
  /// @DtoRelationCanConnectOnCreate
  /// @DtoRelationCanConnectOnUpdate
  mstZ20 MstZ20 @relation(fields: [zCd], references: [zCd])

  /// @description MACHINE CODE
  /// @IsNotEmpty()
  mcCd   String @unique
  /// @DtoRelationRequired
  /// @DtoRelationCanConnectOnCreate
  /// @DtoRelationCanConnectOnUpdate
  mstMch MstMch @relation(fields: [mcCd], references: [mcCd])  

  /// @description MT100 CODE
  /// @IsNotEmpty()
  mtCd  String
  /// @DtoRelationRequired
  /// @DtoRelationCanConnectOnCreate
  /// @DtoRelationCanConnectOnUpdate
  mstMtv MstMtv @relation(fields: [mtCd], references: [mtCd])

  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  createdBy String   @default("system")
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional 
  updatedBy String   @default("system") 
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  createdAt DateTime @default(now())
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  updatedAt DateTime @updatedAt
}

model User {
  /// @description USER ID
  /// @IsNotEmpty()
  id        String   @unique @default(cuid())
  /// @description USER NIK
  /// @IsNotEmpty()
  nik         String  @unique
  /// @description USER PASSWORD
  /// @IsNotEmpty()
  /// @minimum 8
  password    String
  /// @description USER FIRST NAME
  /// @IsNotEmpty()
  firstName   String
  /// @description USER LAST NAME
  /// @IsOptional()
  lastName    String?

  /// @description USER DATA
  /// @IsOptional()
  data String? @db.LongText

  roles UserRole[]

  userMch UserMch[]

  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  createdBy String   @default("system") 
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  updatedBy String   @default("system") 
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  createdAt DateTime @default(now())
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  updatedAt DateTime @updatedAt

  @@id([id, nik])
}

model MstCom{
  /// @description COM ID
  /// @IsNotEmpty()
  comId String @id
  /// @description COM NAME
  /// @IsNotEmpty()
  comNm String 

  userMch UserMch[]
  mstMch MstMch[]

  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  createdBy String   @default("system") 
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  updatedBy String   @default("system") 
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  createdAt DateTime @default(now())
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  updatedAt DateTime @updatedAt
}

model MstMtnLoc {
  /// @description MTN ID
  /// @IsNotEmpty()
  mtnLocId String @id
  /// @description MTN NAME
  /// @IsNotEmpty()
  mtnLocNm String

  userMch UserMch[]
  mstMch MstMch[]

  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  createdBy String   @default("system")  
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  updatedBy String   @default("system")  
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  createdAt DateTime @default(now()) 
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  updatedAt DateTime @updatedAt
}

model UserMch {
  /// @description USER MACHINE ID
  id Int @id @default(autoincrement())

  /// @description COM
  /// @IsNotEmpty()  
  com String
  /// @DtoRelationRequired
  /// @DtoRelationCanConnectOnCreate
  /// @DtoRelationCanConnectOnUpdate
  mstCom MstCom @relation(fields: [com], references: [comId])

  /// @description USER NIK
  /// @IsNotEmpty()  
  nik String
  /// @DtoRelationRequired
  /// @DtoRelationCanConnectOnCreate
  /// @DtoRelationCanConnectOnUpdate
  user User @relation(fields: [nik], references: [nik])

  /// @description MTN ID
  /// @IsNotEmpty()  
  /// @DtoRelationIncludeId
  mtnLocId String
  /// @DtoRelationRequired
  /// @DtoRelationCanConnectOnCreate
  /// @DtoRelationCanConnectOnUpdate
  mtnLoc MstMtnLoc @relation(fields:[mtnLocId], references:[mtnLocId])

  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  createdBy String   @default("system")  
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  updatedBy String   @default("system")  
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  createdAt DateTime @default(now()) 
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  updatedAt DateTime @updatedAt  
}

model MstMch {
  /// @description MACHINE ID
  mcId  Int    @unique @default(autoincrement())
  /// @description MACHINE CODE
  /// @IsNotEmpty()  
  mcCd  String @unique 
  /// @description MACHINE NAME
  /// @IsNotEmpty()  
  mcNm  String 

  zMch LinkZMch?

  /// @description COM
  /// @IsNotEmpty()  
  mcComId String
  /// @DtoRelationRequired
  /// @DtoRelationCanConnectOnCreate
  /// @DtoRelationCanConnectOnUpdate
  mstCom MstCom @relation(fields: [mcComId], references: [comId])
  /// @description MTN LOC ID
  /// @IsNotEmpty()  
  /// @DtoRelationIncludeId
  mtnLocId String
  /// @DtoRelationRequired
  /// @DtoRelationCanConnectOnCreate
  /// @DtoRelationCanConnectOnUpdate
  mtnLoc MstMtnLoc @relation(fields:[mtnLocId], references:[mtnLocId])

  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  createdBy String   @default("system")  
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  updatedBy String   @default("system") 
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional 
  createdAt DateTime @default(now()) 
  /// @DtoEntityHidden
  /// @DtoCreateOptional
  /// @DtoUpdateOptional
  updatedAt DateTime @updatedAt 

  @@id([mcId, mcCd, mcComId])
}
Brakebein commented 5 months ago

Can you please test version 1.20.0? I might have accidentially introduced an old behavior with the latest release (because I solved this issue few months ago #31). But it would be good if you can test it as well.

iamciroja commented 5 months ago

I could confirm that I also have the same problem with v1.21.0 And after downgrading to v1.20.0 everything works fine again

Brakebein commented 5 months ago

Should now have been fixed with the latest release.

KoenLemmen commented 4 months ago

This unfortunately causes Cannot acccess 'YourType' before initialization errors if building with SWC. Here for typeorm they solve it using a wrapper: https://github.com/swc-project/swc/issues/5047#issuecomment-1188874962 Which is also what nestjs's recipe recommends under common pitfalls: https://docs.nestjs.com/recipes/swc

I am aware that this is maybe a pretty niche situation.

Brakebein commented 4 months ago

So, this is basically an issue with SWC, but even NestJs recommends a workaround?

So all relations and composite types would need to be wrapped with export type WrapperType<T> = T; as it says at the NestJs docs (https://docs.nestjs.com/recipes/swc#common-pitfalls)? I'm not sure when I have time for this the next days.

KoenLemmen commented 4 months ago

I'm messing with this myself currently see if I can do another PR. However the suggested workaround breaks swagger schemas for the wrapped types, the errors are gone but for the referred type its just empty in the schema, I myself find this undesirable, so I'm continuing my search for a solution. SWC is so much faster I don't really want to leave it behind.

KoenLemmen commented 4 months ago

Actually excluding the type property in the @ApiProperty while having the swagger plugin setup like this: https://docs.nestjs.com/openapi/cli-plugin fixes both the error and still displays it regularly in swagger.

Would you accept a PR with a setting for the generator nestjsDto function in the .prisma file that is called outputTypeToApiProperty that defaults to true, so I can set it to false?

Brakebein commented 4 months ago

I'm not sure if I got it right. I thought it's basically the type definition of the property?

export class User {
  @ApiProperty({
    type: () => Profile, // <--- you say it's okay to omit this line
  })
  profile: WrapperType<Profile>; // <--- docs say it needs the wrapper type here
}

Maybe you can share some example of what works for you and what not.

KoenLemmen commented 4 months ago

Yes, the docs say it needs the wrapper type there indeed, however I've not had any issues with circular dependencies + SWC while using normal types there. I assume that's because this is a newly defined property, where as @ApiProperty's type requires a (property) type?: string | Function | Record<string, any> | Type<unknown> | [Function] | undefined. That said, this depth is kind of out of my league, I just know what works for me and what doesn't.

When I tried adding the WrapperType to the type property in @ApiProperty because I assumed the same documented fix would work here too, it did fix the circular dependency issue with SWC, however it broke swagger's schema. So like I said previously this is undesirable to me so I continued looking for different solutions.

What I found out is, that it's okay to omit the type property in @ApiProperty IF you have the swagger cli-plugin setup (https://docs.nestjs.com/openapi/cli-plugin). This plugin will "set the type or enum property depending on the type (supports arrays as well)" apparently in a manner where SWC has no issue with it and swagger can still derive the actual schema from it.

Meaning that if we add an option that omits the type property in @ApiProperty and lean on the cli-plugin it works with swagger and with SWC without having to change much.

Please let me know if I can clarify anything else and thank you for your patience.

Brakebein commented 4 months ago

Sorry for the late response. If you are willing to do so, you can do a PR as you proposed with that flag. Maybe called outputApiPropertyType?

paultannenbaum commented 3 months ago

I too am running into this issue, where I am using swc and [this commit] is causing the Cannot acccess 'YourType' before initialization. Being able to omit the type property would fix this issue for me as well.

KoenLemmen commented 3 months ago

I made a PR which you could use until it's merged.

Brakebein commented 3 months ago

I just released a new version with the changes of @KoenLemmen

dwcahyo23 commented 2 months ago

Should now have been fixed with the latest release.

Brakebein commented 1 month ago

When omitting the type property in @ApiProperty, the type is not always inferred correctly by the swagger plugin (#49). Hence, I added a new setting that kind of wraps the imports as types, which should solve the circular dependency issue with SWC (see discussion above and https://docs.nestjs.com/recipes/swc#common-pitfalls).

Can you please test out 1.24.0-beta0 and check if it solves the issue?

Using following configs:

outputApiPropertyType = "true"
wrapRelationsAsType   = "true"

If it works, outputApiPropertyType should be obsolete.

KoenLemmen commented 1 week ago

Works for me.