Skip to content
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

Add support for enum string assignments #273

Open
timsuchanek opened this issue Jul 29, 2019 · 109 comments
Open

Add support for enum string assignments #273

timsuchanek opened this issue Jul 29, 2019 · 109 comments
Labels
domain/client Issue in the "Client" domain: Prisma Client, Prisma Studio etc. domain/psl Issue in the "PSL" domain: Prisma Schema Language domain/schema Issue in the "Schema" domain: Prisma Schema, Introspection, Migrations etc. kind/feature A request for a new feature. topic: enum "type"/block `enum` topic: schema

Comments

@timsuchanek
Copy link
Contributor

enum Color {
  Red  = "RED"
  Teal = "TEAL"
}
@nlarusstone

This comment has been minimized.

@janpio

This comment has been minimized.

@nikolasburk

This comment has been minimized.

@matthewmueller
Copy link
Contributor

Problem

Currently we only support

enum Color {
  Red
  Teal
}

These enums are represented as integers. Occasionally you'd like to store a custom value in the database. Something like:

enum Color {
  Red  = "RED"
  Teal = "TEAL"
}

This issue is about that.

@matthewmueller

This comment has been minimized.

@cheapsteak
Copy link

cheapsteak commented Nov 27, 2020

The data I get back from an api is an string enum with a dash separator (e.g. abc-xyz)
Prisma's generated enum field is abc_xyz, and it doesn't look like that can be changed to abc-xyz
Feels weird to have to replace(/-/g, '_') the api response

Looks like string assignment would solve that (I see that the generated typescript type is a dictionary const anyway)

@myfreax
Copy link

myfreax commented Apr 14, 2021

@matthewmueller here have a example. my project with access control as permission control. This lib use string as action store to database. And UseRoles decorator action also is string.

@massivefermion
Copy link

+1 for string assignment

@danielweil
Copy link

Any word on this?

@kervin5
Copy link

kervin5 commented Dec 15, 2021

+1

@JackCurrie
Copy link

+1 for string assignment.

For me, having a consistent set of string values which are used at every level of the stack make debugging much easier for me. If I am on the front end and I am setting a value on the enum, it is much nicer to have an explicit string value that I can manually check against the backend, and even further back against the data storage layer.

Another thing that I prefer with strings vs the default enum behavior is that I don't know what the actual underlying values are. This compounds when I want to later on edit the enum and add more fields to it. Does it auto-increment? UUID? Does ordering matter? Lots of small things to think about there which can add unnecessary complexity.

https://www.postgresql.org/docs/9.1/datatype-enum.html
Also, postgres, the DB I'm using allows string enums so it seems like we should at least allow this to be done in the schema.prisma file somehow just to fully support all features of the DB.

btw, this is my first day using prisma and I am loving it so far--thank you to the team for making it!

@janpio janpio reopened this Dec 17, 2021
@danielweil

This comment has been minimized.

@janpio janpio added the topic: enum "type"/block `enum` label Dec 17, 2021
@janpio janpio added the domain/client Issue in the "Client" domain: Prisma Client, Prisma Studio etc. label Jan 3, 2022
@john555
Copy link

john555 commented Jan 10, 2022

+1

I have a use-case where I am creating a content-type enum.
It would be great to have:

enum ContentType {
  video_mp4 = "video/mp4"
}

@reubenjh
Copy link

reubenjh commented Oct 4, 2023

+1 for this feature, I currently have to do a boatload of type gymnastics to keep my string enums happy

@plepisnew

This comment was marked as off-topic.

@Abror4544
Copy link

Any updates?

1 similar comment
@ahsanzizan
Copy link

Any updates?

@RyKilleen
Copy link

RyKilleen commented Dec 4, 2023

A use-case I haven't seen mentioned here and likely falls into the same feature (apologies if I missed it):

I have the need to assign specific integers to my enum, e.g.

enum MyEnum {
    Empty             = 0,
    SomeValue         = 10,
    SomeOtherValue    = 11,
    SomeFurtherValue  = 20,
   // ... many more entries
}

This enum is shared across systems, and would love to weave it into some strongly-typed database schema!

@JohnnyTheTank
Copy link

I could really use this feature

@fuomag9
Copy link

fuomag9 commented Dec 21, 2023

I'd love to have this feature as well as I needed compatibility with tables created by SQLAlchemy, which use strings by default
https://docs.sqlalchemy.org/en/20/core/type_basics.html#sqlalchemy.types.Enum

@adrian-ozuna
Copy link

I'm really looking forward this feature being implemented

@btd1337
Copy link

btd1337 commented Jan 17, 2024

Well, I'm going to mention my progress, and perhaps it will help the next person take the next step.

I used as a starting point the script made by @PinkFromTheFuture.

Basically, it creates a file (enums.ts) within the /prisma folder containing the enums in the expected format.

Example:

// prisma/schema.prisma

enum ErrorCode {
  DEPENDENT_ERROR        @map("d1")
  POLICY_TEETH_EMPTY     @map("p1")
  POLICY_HEALTH_EMPTY    @map("p2")
  POLICY_HEALTH_UPDATED  @map("p3")
  POLICY_TEETH_UPDATED   @map("p4")
  POLICIES_EMPTY         @map("p5")
}
// prisma/enums.ts

export enum ErrorCode {
  DEPENDENT_ERROR = "d1",
  POLICY_TEETH_EMPTY = "p1",
  POLICY_HEALTH_EMPTY = "p2",
  POLICY_HEALTH_UPDATED = "p3",
  POLICY_TEETH_UPDATED = "p4",
  POLICIES_EMPTY = "p5",
}

In other words, in the schema file, you can define any name for your keys, and in @Map, you map the values that are in your database.

The problem I detected:

When you define a column of a model in the Prisma schema using enums, Prisma generates something like this in your definition file:

// node_modules/.prisma/client/index.d.ts

export const ErrorCode: {
  DEPENDENT_ERROR: 'DEPENDENT_ERROR',
  POLICY_TEETH_EMPTY: 'POLICY_TEETH_EMPTY',
  POLICY_HEALTH_EMPTY: 'POLICY_HEALTH_EMPTY',
  POLICY_HEALTH_UPDATED: 'POLICY_HEALTH_UPDATED',
  POLICY_TEETH_UPDATED: 'POLICY_TEETH_UPDATED',
  POLICIES_EMPTY: 'POLICIES_EMPTY',

Consequently, trying to use enums (via prisma/enums.ts) in the code results in an error because the values of the keys are divergent.

What I thought:

To write a script that uses the values generated by enums and updates the Prisma file with the mapped values. Then the Prisma file looks like this:

// node_modules/.prisma/client/index.d.ts

export const ErrorCode: {
   DEPENDENT_ERROR: 'd1',
  POLICY_TEETH_EMPTY: 'p1',
  POLICY_HEALTH_EMPTY: 'p2',
  POLICY_HEALTH_UPDATED: 'p3',
  POLICY_TEETH_UPDATED: 'p4',
  POLICIES_EMPTY: 'p5',
}

From there, code errors no longer occur. You can use the values as enums normally until... until when saving to the database, some Prisma function, it seems, expected to receive the string in the old format. For example, the key DEPENDENT_ERROR should pass the string 'DEPENDENT_ERROR' instead of 'd1', which is the value the database expects.

This function that expects the old value instead of the new updated value for the key is the last obstacle for the hackfix to work.

I will leave below the source code of the solution as far as I have come.

To make it work, just add to your package.json script that runs Prisma Generate:

// package.json

"scripts": {
    "prisma:update-client": "prisma generate && ts-node prisma/utils/replace-enum-values.ts",
    (...)
}

Note: I added the script in the prisma/utils/ folder; change it if necessary for you.

Source code:

// prisma/utils/replace-enum-values.ts

import * as path from 'path';
import * as fs from 'fs/promises';

class EnumProcessor {
  private enumsFilePath: string;
  private prismaFilePath: string;
  private schemaPath: string = 'prisma/schema.prisma';

  constructor({ enumsFilePath, prismaFilePath }: { enumsFilePath: string; prismaFilePath: string }) {
    this.prismaFilePath = prismaFilePath;
    this.enumsFilePath = enumsFilePath;
  }

  async generateEnumsFile(): Promise<void> {
    try {
      const data: string = await fs.readFile(this.schemaPath, 'utf8');

      const enumRegex: RegExp = /enum\s+(\w+)\s+\{([\s\S]+?)\}/g;
      let match;
      let allEnumsOutput: string = '';

      while ((match = enumRegex.exec(data)) !== null) {
        const enumName: string = match[1];
        const enumBody: string = match[2];

        const enumItemRegex: RegExp = /(\w+)\s+@map\((.+)\)/g;
        let enumItems: string = '';

        let itemMatch;
        while ((itemMatch = enumItemRegex.exec(enumBody)) !== null) {
          const itemName: string = itemMatch[1];
          const itemValue: string = itemMatch[2].trim();

          enumItems += `  ${itemName} = ${itemValue},\n`;
        }

        const enumOutput: string = `export enum ${enumName} {\n${enumItems}}\n\n`;
        allEnumsOutput += enumOutput;
      }

      const absolutePath: string = path.resolve(__dirname, this.enumsFilePath);

      await fs.writeFile(absolutePath, allEnumsOutput, 'utf8');
      console.log(`Generated ${absolutePath}`);
    } catch (err) {
      console.error('Error processing enums file:', err);
    }
  }

  processEnums(): [string, string][] {
    const absolutePath: string = path.resolve(__dirname, this.enumsFilePath);
    const enums: Record<string, Record<string, any>> = require(absolutePath);

    const enumsPairs: [string, string][] = [];

    function formatEnumAsString(enumName: string, enumObject: Record<string, any>): string {
      const keysAndValues: string = Object.entries(enumObject)
        .map(([key, value]) => `${key}: '${value}'`)
        .join(',\n  ');

      return `export const ${enumName}: {\n  ${keysAndValues}\n};\n`;
    }

    for (const enumName in enums) {
      if (Object.prototype.hasOwnProperty.call(enums, enumName)) {
        const enumObject: Record<string, any> = enums[enumName];
        if (typeof enumObject === 'object' && !Array.isArray(enumObject) && Object.keys(enumObject).length > 0) {
          const enumString: string = formatEnumAsString(enumName, enumObject);
          enumsPairs.push([enumName, enumString]);
        }
      }
    }

    return enumsPairs;
  }

  async getConstantText(constantName: string): Promise<string> {
    const absolutePath: string = path.resolve(__dirname, this.prismaFilePath);
    const fileContent: string = await fs.readFile(absolutePath, 'utf-8');
    const regex: RegExp = new RegExp(`export const ${constantName}: {[\\s\\S]*?};`);
    const match: RegExpMatchArray | null = regex.exec(fileContent);

    if (match) {
      return match[0];
    } else {
      console.error(`Constant ${constantName} not found.`);
      return '';
    }
  }

  async replaceTextInFile(searchText: string, replaceText: string): Promise<void> {
    const absolutePath: string = path.resolve(__dirname, this.prismaFilePath);
    let fileContent: string = await fs.readFile(absolutePath, 'utf-8');

    if (fileContent.includes(searchText)) {
      fileContent = fileContent.replace(searchText, replaceText);
      await fs.writeFile(absolutePath, fileContent);
    } else {
      console.error(`Text '${searchText}' not found in the file.`);
    }
  }

  async processAndReplaceEnums(): Promise<void> {
    await this.generateEnumsFile();

    const enumsPairs: [string, string][] = this.processEnums();

    for (const [enumName, enumString] of enumsPairs) {
      const constCurrentText: string = await this.getConstantText(enumName);
      await this.replaceTextInFile(constCurrentText, enumString);
    }

    console.log('Enums successfully updated in the Prisma file');
  }
}

new EnumProcessor({
  enumsFilePath: '../enums.ts',
  prismaFilePath: './../../node_modules/.pnpm/@prisma+client@5.2.0_prisma@5.2.0/node_modules/.prisma/client/index.d.ts',
}).processAndReplaceEnums();

Note:

Open a Prisma class in your editor to identify where your Prisma definition file is stored. In my case, it is in the folder node_modules/.pnpm/@prisma+client@5.2.0_prisma@5.2.0/node_modules/.prisma/client/index.d.ts.

Modify the prismaFilePath variable at the end of the file to match your code.


Well, I hope this helps the next person who manages to create a hackfix that will solve the problem when saving values to the database or, even better, inspire the Prisma team to implement this feature definitively.

@bok1c4
Copy link

bok1c4 commented Feb 13, 2024

enum PurchaseStatus {
  NotPurchased = "NotPurchased"
  Purchased = "Purchased"
  Delivering = "Delivering"
  Delivered = "Delivered"
}

please add feature for using string values with enum values :)

@ManHatos
Copy link

4 and 1/2 years, and prisma still does not support more flexible enum declarations by default
is there any reason why this feature request is taking such a long time?

in my case, I'm trying to store enum values as integers to indiciate a type as their "labels" may change at any given time, and needing to update all stored values in the database is inconvenient

enum SomeType {
  Primary = 0
  Secondary = 1
  // and so on...
}

which would hopefully generate a matching typescript enum

@mwitteveen
Copy link

This issue has been the determining reason to take my startup from nodejs. Honestly I have no words for this. We rewrote over 250k lines to golang after I was following this thread for half a year.

This issue very strongly represents the community mess NodeJS brings along. 10.000 badly designed open source packages made by mediocre developers.

I’m going to unsubscribe now. Good luck 😕

p.s. guys Golang development will reintroduce you to the pleasure it is to code.

@israelins85
Copy link

israelins85 commented Mar 5, 2024

For me works this way:

enum ENUM_TYPE {
  VALUE_01 @map("VALUE 01")
  VALUE_02 @map("VALUE 02")
  VALUE_03 @map("VALUE 03")
}

@erikhuck
Copy link

erikhuck commented Mar 6, 2024

@israelins85 does that actually work or is that a proposal you're making?

@JohnnyTheTank
Copy link

For me works this way:

enum ENUM_TYPE {
  VALUE_01 @map("VALUE 01")
  VALUE_02 @map("VALUE 02")
  VALUE_03 @map("VALUE 03")
}

this does not work for me (prisma 5.10.2 & postgres)

@israelins85
Copy link

For me works this way:

enum ENUM_TYPE {
  VALUE_01 @map("VALUE 01")
  VALUE_02 @map("VALUE 02")
  VALUE_03 @map("VALUE 03")
}

works on MySql...

@israelins85
Copy link

@israelins85 does that actually work or is that a proposal you're making?

MySQL

@jpaylor
Copy link

jpaylor commented Mar 14, 2024

For me works this way:

enum ENUM_TYPE {
  VALUE_01 @map("VALUE 01")
  VALUE_02 @map("VALUE 02")
  VALUE_03 @map("VALUE 03")
}

From what I can see this approach does not work if your enum map values are kebab case e.g. 'my-value' because Prisma converts this into pascal case 'MyValue'.

Example in schema.prisma:

enum APIEndpoint {
  ProdIn    @map("prod-in")
  OrdIn     @map("ord-in")
  ExpRecIn  @map("exp-rec-in")
  PurOrdIn  @map("pur-ord-in")
  LblIn     @map("lbl-in")
  ExpRetIn  @map("exp-ret-in")
  DespOut   @map("desp-out")
  StkRecOut @map("stk-rec-out")
  StkInvOut @map("stk-inv-out")
  StkAdjOut @map("stk-adj-out")
  StkRetOut @map("stk-ret-out")
  StkLvlOut @map("stk-lvl-out")
}

Prisma client generated output:

exports.APIEndpoint = exports.$Enums.APIEndpoint = {
  ProdIn: 'ProdIn',
  OrdIn: 'OrdIn',
  ExpRecIn: 'ExpRecIn',
  PurOrdIn: 'PurOrdIn',
  LblIn: 'LblIn',
  ExpRetIn: 'ExpRetIn',
  DespOut: 'DespOut',
  StkRecOut: 'StkRecOut',
  StkInvOut: 'StkInvOut',
  StkAdjOut: 'StkAdjOut',
  StkRetOut: 'StkRetOut',
  StkLvlOut: 'StkLvlOut'
};

@SarenT
Copy link

SarenT commented Apr 4, 2024

Here is a use case and I am open to any argument, why this is not a good one. Almost any data is also to be displayed. Enums are not an exception. For instance, in a web application form, this could a choice between user types, or payment methods etc. Having an associated label embedded to the keys is quite practical. In some cases these could be quite long as well. I think supporters of string assignment are thinking something inline of this.

@reubenjh
Copy link

For those of us just wanting this for display purposes as @SarenT suggests above, a simple solution i've gone for is to just establish a pattern of your own in your enums like replace spaces with underscores and ampersands with the word "and", and when you display these to users run it through a helper function 🤷

export const beautify = (str: string, ampersand?: boolean) => {
  let newStr = str.replaceAll("_", " ");
  if (ampersand) {
    newStr = newStr.replace(/\band\b/gi, "&");
  }
  return newStr;
};

@Sid-Turner-Ellis
Copy link

My use case is representing Stripe subscription statuses in the database without manually mapping them from the string value to the enum value

e.g.

const stripeSubscriptionStatus = subscription.status // "active"

await db.subscription.update({
  where: { id },
  data: { status: stripeSubscriptionStatus }
})

Instead, I have to map the stripe value to the enum value.

The @Map() doesn't work for me, at least it's not in the client generation (postgres)

@Aloysius999
Copy link

Is there any update to this feature?
I'm doing full-stack development and the handling of enums on the DB with EntityFramework is virtually impossible unless stored explicitly as ints or strings.
As a work around I'm thinking of manually mapping my enums in Next.js to ints - but I'd prefer to continue working with Prisma enums.
I'm free to change the DB at this stage since I'm at a very early stage of development and have no meaningful data in my DB - so if this feature isn't yet available, I'll have to change the column type to int or string.

@htchtc052
Copy link

I whant int value in enum

Like enum Resultions {
1536
2400
4800
}

@KerickHowlett
Copy link

What I'm about to propose may make things more complicated, but I came across this thread seeking a way to include an optional description to every enum value.

Enum values, more often than not, need to be relatively short While many values are self-explanatory, others may not.

For just a quick and dirty example, say I am managing a company shift schedule. I create a AssignedShift bridge model used for keepings records of a given Worker relative to their assigned Shift. I add a status field to the aforementioned AssignedShift bridge model that is typed with a ShiftStatus enum.

This enum lists values like Scheduled, Completed, and InProgress. All of those are self-explanatory so far, with no description needed. Yes? Well, what if I threw in a curve ball by appending a new value labeled Absent? While it may seem self-explanatory, this could mean a plethora of things: FMLA Leave, Sick Day, Vacation (PTO), Maternity/Paternity Leave, and so on.

Ideally, this is to indicate an Unnotified Abscene. Now, I already know many of you are gonna suggest why not just have a value for each of the Scheduled Absences i just listed. Yes, that would be the best option, and I have done just that for similar situations.

However, when you take user experience and human nature into account, where a busy manager has to select from a dropdown list—a list that should be sorted alphabetically—Absent would be the first option to appear. This could lead to managers marking their employees with an unscheduled absent and would lead to the worker getting fired and/or create some chaos for HR to deal with.

Yes, I know there are several other fixes for this without having a description field for this, such as changing the Absent value to UnnotifiedAbsent instead or the like, but everyone here with the experience knows something like this could be helpful at some point, even if any of you find them as simple edge cases.

I'm thinking the most straightforward solution may be to create a specialized "enum model" and the ability to create a @relation, but I will admit that I'm still rather new to Prisma, so this may be the best solution, either.

I just know there will always be a vast variety of enum-related edge cases, so I figured the best solution would be a dynamic and versatile one.

@SanditZZ
Copy link

SanditZZ commented Oct 4, 2024

+1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
domain/client Issue in the "Client" domain: Prisma Client, Prisma Studio etc. domain/psl Issue in the "PSL" domain: Prisma Schema Language domain/schema Issue in the "Schema" domain: Prisma Schema, Introspection, Migrations etc. kind/feature A request for a new feature. topic: enum "type"/block `enum` topic: schema
Projects
None yet
Development

No branches or pull requests