Skip to content

Nested relations not exposed in a polymorphic relation #1994

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
simoami opened this issue Feb 20, 2025 · 5 comments
Closed

Nested relations not exposed in a polymorphic relation #1994

simoami opened this issue Feb 20, 2025 · 5 comments

Comments

@simoami
Copy link

simoami commented Feb 20, 2025

Description and expected behavior

Suppose you have an m-to-n relation: OrganizationRole <- OrganizationRolePrivilege -> Privilege. The Organization role is a delegate further breaking down into 2 concrete models: SystemDefinedRole and CustomOrganizationRole:

model OrganizationRole {
  id              Int @id @default(autoincrement())
  name        String
  rolePrivileges  OrganizationRolePrivilege[]
  type            String
  @@delegate(type)
}

// roles common to all orgs, defined once
model SystemDefinedRole extends OrganizationRole {
  name String @unique
}

// roles specifc to each org
model CustomOrganizationRole extends OrganizationRole {
  name String
  organizationId Int
  organization   Organization @relation(fields: [organizationId], references: [id])

  @@unique([organizationId, name])
  @@index([organizationId])
}

model OrganizationRolePrivilege {
  organizationRoleId Int
  privilegeId        Int

  organizationRole   OrganizationRole @relation(fields: [organizationRoleId], references: [id])
  privilege          Privilege        @relation(fields: [privilegeId], references: [id])

  @@id([organizationRoleId, privilegeId])
}

model Privilege {
  id                  Int @id @default(autoincrement())
  name                String // e.g. "org:manage"

  orgRolePrivileges   OrganizationRolePrivilege[]
  @@unique([name])
}

With the Privilege table is already preloaded with all privileges, it's not possible to create a new concrete Role and connect it to existing privileges.

Below are a few attempts but unsuccessful:

Attempt 1: Connecting by foreign key

await db.systemDefinedRole.create({
    data: {
        name: 'Admin',
        rolePrivileges: {
            create: [
                {
                    privilegeId: 1,
                    // ✖ currently requires a organizationRoleId but organizationRole not created yet
                    organizationRoleId:1
                }
            ],
        },
    },
});

Attempt 2: Connecting by a relation name

await db.systemDefinedRole.create({
    data: {
      name: 'Admin',
      rolePrivileges: {
        create: [
          {
            // ✖ privilege relation not exposed
            privilege: {
              connect: { id: 1 },
            },
          },
        ],
      },
    },
  })

Environment (please complete the following information):

  • ZenStack version: 2.11.6
  • Prisma version: 5.22.0
  • Database type: Postgresql

Additional context

See related Discord thread.
With raw Prisma, this is achievable in 2 different ways. Both of which expose the privilege relation to connect to:

const db = await prismaEnhanced({ bypassPolicy: true})
  const privileges = await db.privilege.createMany({
    data: [...orgPrivileges, ...studyPrivileges],
  })

// 1. Using raw prisma with the OrganizationRole delegate model
await prisma.organizationRole.create({
    data: {
      // name: 'Owner',
      description:
        'Admin access',
      type: 'SystemDefinedRole',
      delegate_aux_systemDefinedRole: {
        create: { name: 'Admin' },
      },
      rolePrivileges: {
        create: [
          {
            privilege: {
              connect: { id: 1 },
            },
          },
        ],
      },
    },
  })

// 2: Using raw prisma with the SystemDefinedRole concrete model
  await prisma.systemDefinedRole.create({
    data: {
      name: 'Admin',
      delegate_aux_organizationRole: {
        create: {
          description:
            'Admin access',
          type: 'SystemDefinedRole',
          rolePrivileges: {
            create: [
              {
                privilege: {
                  connect: { id: 1 },
                },
              },
            ],
          },
        },
      },
    },
  })
@ymc9
Copy link
Member

ymc9 commented Feb 24, 2025

Hi @simoami , thanks for filing this!

It seems to be pure typing issue. If you make a cast to "any", both attempt 1 and 2 should work at runtime. I'll see how to fix the typing problem.

@ymc9
Copy link
Member

ymc9 commented Mar 24, 2025

Fixed in 2.13.0

@ymc9 ymc9 closed this as completed Mar 24, 2025
@simoami
Copy link
Author

simoami commented Mar 24, 2025

@ymc9 Thanks for the fix. I just tried 2.13.0. But seeing issues now.
This fragment:

await db.user.update({
    where: { id: 1 },
    data: {
      orgMemberships: {
        create: [
          {
            organizationId: 1,
            roleId: 1,
          }
       ]
    }
})

worked in 2.12.1 for both PrismaClient and EnhancedPrismaClient.
With 2.13.0, no syntax errors but execution leads to:

Error calling enhanced Prisma method `user.update`:
Invalid `prisma.organizationMember.create()` invocation:

{
  data: {
    organizationId: 1,
    roleId: 1,
    deactivatedById: 1,
+   organization: {
+     create: OrganizationCreateWithoutMembersInput | OrganizationUncheckedCreateWithoutMembersInput,
+     connectOrCreate: OrganizationCreateOrConnectWithoutMembersInput,
+     connect: OrganizationWhereUniqueInput
+   }
  },
  select: {
    id: true
  }
}

Argument `organization` is missing.
    at OmitHandler.<anonymous> (/Users/.../node_modules/.pnpm/@[email protected]_@[email protected][email protected]_/node_modules/@zenstackhq/src/enhancements/node/proxy.ts:92:60),\n' +

I followed the suggestion as it seems it only works with the full objects. I updated the fragment above to:

await db.user.update({
    where: { id: 1 },
    data: {
      orgMemberships: {
        create: [
          {
            role: { connect: { id: 1 } },
            organization: { connect: { id: 1 } },
          }
       ]
    }
})

Now another error says it wants the user object to be specified. This code also works with the standard prisma client. Prisma accepts both formats: foreign keys (roleId, organizationId) and full objects (role(connect), organization(connect). Because these membership records are related inserts, it would normally assume the join to the user record, but it's behaving as if I called db.orgMembership.create([]), in which case, a user connect would be needed:

Error calling enhanced Prisma method `user.update`:
Invalid `prisma.organizationMember.create()` invocation:

{
  data: {
    role: {
      connect: {
        id: 1
      }
    },
    organization: {
      connect: {
        id: 3
      }
    },
    deactivatedBy: {
      connect: {
        id: 2
      }
    },
+   user: {
+     create: UserCreateWithoutOrgMembershipsInput | UserUncheckedCreateWithoutOrgMembershipsInput,
+     connectOrCreate: UserCreateOrConnectWithoutOrgMembershipsInput,
+     connect: UserWhereUniqueInput
+   }
  },
  select: {
    id: true
  }
}

Argument `user` is missing.
    at OmitHandler.<anonymous> (/Users/.../node_modules/.pnpm/@[email protected]_@[email protected][email protected]_/node_modules/@zenstackhq/src/enhancements/node/proxy.ts:92:60),

This version works but typescript shouts the 'user' prop is not a known prop, which is aligned with the original PrismaClient:

await db.user.update({
  where: { id: user?.id },
  data: {
    orgMemberships: {
      create: [
        {
          user: { connect: { id: 1} },
          role: { connect: { id: 1 } },
          organization: { connect: { id: 1 } },
        },
      ],
    },
  },
})

@ymc9
Copy link
Member

ymc9 commented Mar 24, 2025

Hi @simoami , could you show me your User model?

@ymc9
Copy link
Member

ymc9 commented Mar 24, 2025

Actually probably the full ZModel would be the best. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants