Skip to content

Use @map'ed enum values as TS enum values #8446

@egorovli

Description

@egorovli

Problem

Consider the following schema:

model Payment {
  id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid

  amount   Decimal         @db.Decimal(14, 2)
  provider PaymentProvider

  createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
  updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz

  @@index([provider])
  @@map("payments")
}


enum PaymentProvider {
  MixplatSMS    @map("mixplat/sms")
  InternalToken @map("internal/token")
  Offline       @map("offline")

  @@map("payment_provider")
}

In database, it works as expected, the enum name and values are taken from the @map and @@map directives. In TypeScript, however, the generated values are as follows:

export const PaymentProvider: {
  MixplatSMS: 'MixplatSMS',
  InternalToken: 'InternalToken',
  Offline: 'Offline'
};

export type PaymentProvider = (typeof PaymentProvider)[keyof typeof PaymentProvider]

Which is a problem, because every time you want to serialize the object returned from the database by prisma, you have to remap these values to the ones that the project uses (which is the database version). A simplified example would look like this:

import { Middleware } from 'koa'
import { PaymentProvider } from '@prisma/client'

export let mapper: Record<PaymentProvider, string> = {
  [PaymentProvider.MixplatSMS]: 'mixplat/sms',
  [PaymentProvider.InternalToken]: 'internal/token',
  [PaymentProvider.Offline]: 'offline',
}

export let reverseMapper: Record<string, PaymentProvider> = {
  'mixplat/sms': PaymentProvider.MixplatSMS,
  'internal/token': PaymentProvider.InternalToken,
  'offline': PaymentProvider.Offline,
}

export let createPaymentHandler: Middleware = async (ctx, next) => {
  let payment = await prisma.payment.create({
    data: {
      ...ctx.request.body,
      // Here we remap the value of provider from the project's version to the
      // one prisma understands (e.g. from 'mixplat/sms' to 'MixplatSMS').
      provider: reverseMapper[ctx.request.body.provider]
    }
  })

  // ...
}

export let getPaymentHandler: Middleware = async (ctx, next) => {
  let payment = await prisma.payment.findFirst()

  ctx.body = {
    ...payment,
    // Here we remap the prisma's value of provider to the one that the project
    // actually uses (e.g. from 'InternalToken' to 'internal/token').
    provider: mapper[payment.provider]
  }
}

It gets much worse when there are nested includes.

Suggested solution

If explicitly defined, use values from @map directives as enum values, like so:

export const PaymentProvider: {
  MixplatSMS: 'mixplat/sms'
  InternalToken: 'internal/token'
  Offline: 'offline'
}

export type PaymentProvider = (typeof PaymentProvider)[keyof typeof PaymentProvider]

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions