zenstackhq / zenstack

Fullstack TypeScript toolkit that enhances Prisma ORM with flexible Authorization layer for RBAC/ABAC/PBAC/ReBAC, offering auto-generated type-safe APIs and frontend hooks.
https://zenstack.dev
MIT License
2.07k stars 88 forks source link

Reference to model with abstract parent doesn't resolve when id field defined in non-immediate parent #540

Closed chunkerchunker closed 10 months ago

chunkerchunker commented 1 year ago

Description and expected behavior

If a (non-abstract) model has multiple levels of abstract parents, where the id field is not defined in the immediate parent, then references to the model fail to resolve. This affects both the ZenStack CLI and the VSCode extension.

Here is a sample schema:

abstract model Base1 {
  id    String @id @default(cuid())
}

abstract model Base2 extends Base1 {
  fieldA String
}

model ModelA extends Base2 {
  fieldB String

  bref  ModelB[]
}

model ModelB {
  id    String @id @default(cuid())

  aref  ModelA @relation(fields: [arefId], references: [id])
  arefId String
}

When ModelA derives from Base2, the ModelB.aref field fails to resolve. If the schema is changed so that ModelA extends Base1, then ModelB.aref resolves as expected.

The Zenstack CLI error is:

line 27: Value is not assignable to parameter [references: [id]]
line 27: field reference is unresolved [id]
line 27: values of "references" and "fields" must have the same type [@relation(fields: [arefId], references: [id])]
line 27: expression cannot be resolved [[id]]
line 27: expression cannot be resolved [id]

This is a contrived example, but multiple levels of abstract classes -- with the id property defined in the root class -- is a useful pattern for us when modeling complex schemas.

Environment:

Additional context

(Thank you for creating this tool!)

jiashengguo commented 1 year ago

@chunkerchunker, Sorry for the trouble. Actually, I'm actually aware of this when I implemented it. I thought no one would acutally use multip-level inheritance soon, so following the "Less is more" principle I just left it there. 😂

Anyway, I think it's not hard to support it. I will try to make it in the next release. Before that, I think you can instead use multi-parent inheritance to keep your schema DRY for now.

abstract model Base1 {
    id String @id @default(cuid())
}

abstract model Base2 {
    fieldA String
}

model ModelA extends Base1,Base2 {
    fieldB String

    bref ModelB[]
}

model ModelB {
    id String @id @default(cuid())

    aref ModelA @relation(fields: [arefId], references: [id])
    arefId String
}
chunkerchunker commented 1 year ago

Thanks @jiashengguo! I should have noted that multi-parent inheritance is a workaround and saved you some typing.

AlexOwl commented 10 months ago

Bump (Test1 model gives error regarding id)

abstract model Base {
    id String @id @default(cuid())

    createdAt DateTime @default(now())
    updatedAt DateTime @updatedAt
}

abstract model BaseDeletable extends Base {
    deleted Boolean @default(false) @omit

    @@deny('read', deleted)
}

model Test1 extends BaseDeletable {
    @@allow('all', true)
}
AlexOwl commented 10 months ago

@jiashengguo

ymc9 commented 10 months ago

Hi @AlexOwl , the issue has been fixed in the 1.4.0 release.