Skip to content

Option brand the model name into data #5315

@jasonkuhrt

Description

@jasonkuhrt

Since creating this issue there are updates below:


Problem

Prisma Client does not introduce data classes/model classes, instead just returning/working with simple native JS data structures (plain objects, arrays, etc.).

This is a problem when the identity of data is needed. There are lots of cases where identity is needed. Here are two cases I am currently working with:

  1. While implementing an Oso policy where we want to pattern match on a specific kind of resource.

  2. While implementing polymorphism in GraphQL we want to use the Discriminant Model Field (DMF) Strategy.

Suggested solution

Ideally something as simple as this:

const prisma = new PrismaClient({
  brandTypes: true | { fieldName: string, case: "lower" | "upper" | "pascal" }
})

I don't see how we can enable this by default without being backwards incompatible because there are no namespace guarantees. So alas I guess this would default to false.

#5315 (comment)

We can use $type (or $kind) knowing that it will not break user code (except maybe in a very esoteric user case that we can accept breaking).

I think having this builtin and enabled by default makes sense for Prisma Client because model identity is a fundamental issue and it is currently impossible without resorting to duck-typing and offers no integration with TS discriminant union types.

When enabled via true the default field used could be something like kind or $kind or type or $type.

Users would be able to overwrite this default with additional configuration, passing their desired field name (the second union member above).

An additional option may be the casing of the string. E.g. let the user decide between these:

user.kind === 'user'
user.kind === 'USER'
user.kind === 'User'

The problem I see with this is that it won't show up in the static typing. That is, with this setting enabled, the following should be statically safe:

const user = prisma.user.findUnique(...)

const a: 'user' = user.kind

const org = prisma.org.findUnique(...)  // nested relation example

const b: 'org' = org.kind

const c: 'user'[] = org.members.map(user => user.kind)

// and so on

This is key because it is the only way to leverage TS discriminant union types.

The only way I can see Prisma Client being able to achieve this (without potentially major complexity via runtime reflection to generate typegen like Nexus) is for Prisma to add some new configuration at the generator level.

generator client {
  provider = "prisma-client-js"
  brandTypes = true
}
generator client {
  provider = "prisma-client-js"
  brandTypes {
    fieldName = "kind"
    case = "lower" | "upper" | "pascal"
  }
}

Alternatives

It is currently possible I think to solve this by putting a field on every model that will simply be a constant of the model kind:

enum UserKind {
  user
}

model User {
  kind          UserKind    @default(user)
}

This is suboptimal because:

  1. Application level concern mixed with database level
  2. A constant is wasting space in the database, repeated for every row, the same value
  3. Error prone if that field is ever accidentally set by some operation
  4. Developer needs to remember to always select the kind field on any query.
  5. Related to above point: Any automation/abstract might not and lead to integration issues.

I considered using middleware but this did not seem to work because:

  1. Monkey patching kind fields would not be reflected in the TS types... (but maybe I can leverage TS interface declaration merging? But actually no, not easily, because Prisma model types are not globals. So I would need to create a new module of model types anyways)

  2. When there are nested relations I would need to traverse them and brand them too, which AFASICT is not possible because the model names of nested relations is not available in the middleware (nor is it clear anyways how it would be in a way that I could map to data during the traversal process...).

Additional context

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain/clientIssue in the "Client" domain: Prisma Client, Prisma Studio etc.kind/featureA request for a new feature.topic: client api

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions