prisma / prisma

Next-generation ORM for Node.js & TypeScript | PostgreSQL, MySQL, MariaDB, SQL Server, SQLite, MongoDB and CockroachDB
https://www.prisma.io
Apache License 2.0
38.87k stars 1.52k forks source link

Make `@default(nanoid())` alphabet configurable #17294

Open Jolg42 opened 1 year ago

Jolg42 commented 1 year ago

Note: the feature is not complete yet, see https://github.com/prisma/prisma/issues/17199

Example 1

    @rintaun This is a great enhancement! We've been using NanoId for years. Not sure we can directly use this, as we use in conjunction with a custom, "safe" alphabet but nevertheless really nice to see this coming into the mix.

For those curious, we distinguish between public unique identifiers and private ones. Are private ones have nearly all moved to cuid at this point, but we use smaller NanoIds along with a safe alphabet for identifiers that are more likely shared and/or make their way into URLs, etc.

Originally posted by @dvins in https://github.com/prisma/prisma-engines/issues/3556#issuecomment-1381592441

Example 2

    It would be awesome if we could specify our own alphabet 👀 

Like @dvins, I'm using my own alphabet based on the idea of the base58 from blockchain. Why? Because it leaves out awkward characters, to save you from making mistakes when transcribing (I = "i" uppercase vs l = "L" lowercase, 0 vs O = "o" uppercase).

  1 2 3 4 5 6 7 8 9
A B C D E F G H   J K L M N   P Q R S T U V W X Y Z
a b c d e f g h i j k   m n o p q r s t u v w x y z

Originally posted by @binajmen in https://github.com/prisma/prisma-engines/issues/3556#issuecomment-1381617208

Jolg42 commented 1 year ago

@binajmen @dvins

Do you have any suggested solution, alternatives or additional context for how to do this? It would help us and other people that need this.

binajmen commented 1 year ago

I'm not confident enough in Rust to help on the PR itself, but here are some useful food for thoughts:

Custom alphabet in JS version of nanoid: https://github.com/ai/nanoid#custom-alphabet-or-size

Custom alphabet in Rust version of nanoid: https://github.com/nikolay-govorov/nanoid#custom-alphabet-or-length

I suppose the alphabet itself should be defined somewhere in the generator client?

Jolg42 commented 1 year ago

@benjaminbours My first though was to have it in the schema, looks like this in my head:

model Blog {
    id        String @id @default(nanoid(length: 10, alphabet: "1234567890abcdef"))
}

It makes it extremely configurable (can use a different setting for different fields), this might be useful? The configuration on the generator would make it less configurable and global.

binajmen commented 1 year ago

@Jolg42 I had the same idea first, but what if I have many models using nanoid? Do I want to repeat the same parameters (length and/or alphabet) in each of them?

Maybe an hybrid solution where you could define globally these parameters, while being able to overwrite locally when needed?

Something like:

generator client {
  provider = "prisma-client-js"
  nanoid = { length: 12, alphabet: "123..abc.." } // <-- if omitted, use default values (*)
}

model Model1 {
  id String @id @default(nanoid()) // <-- will use the global settings
}

model Model2 {
  id String @id @default(nanoid(length: 10, alphabet: "1234567890abcdef")) // <-- will overwrite for this model
}

(*) official default values are alphabet = A-Za-z0-9_- and length = 21 (https://github.com/ai/nanoid#api)

Jolg42 commented 1 year ago

Indeed good questions to design the feature!

I think this will not make it at first, so first nanoid() will be supported but without alphabet configuration.

If more people can show up here and let us know what/why it's important for them, it will help prioritize the feature request work here.

dvins commented 1 year ago

@Jolg42 @binajmen Crawl -> Walk -> Run. A shared setting in the generator configuration would be grand, but would happily settle for cut and pasting the alphabet and/or length on the column and table definitions where needed.

In the Javascript ecosystem there's https://github.com/CyberAP/nanoid-dictionary and it offers predefined custom alphabets, such as nolookalikes which would provide another way as well. I suppose the alphabets and names could be transfered into Rust in some fashion. But ultimately the more stuffed into this feature the longer it will take to move through the process and introduces other risks and probably even more edge cases.

smolak commented 1 year ago

The length and alphabet features of nanoid are IMHO required to be configured. Sure, take steps, but I would like to see it adopted and configured.

What would be great as well, actually for all string-generated IDs, is the possibility to set prefixes, e.g. user_ for users, post_ for posts, etc. In my project, I rely on middleware to achieve all 3: length, alphabet, and prefix.

Jolg42 commented 1 year ago

@smolak Check this feature issue https://github.com/prisma/prisma/issues/6719, it is more generic and mentions the prefix configuration. Does it cover everything you want to do? Feel free to comment there 🙌🏼

smolak commented 1 year ago

@Jolg42: I am active there, but thanks. I have even proposed a solution to prefixing, that is abstract from DB one is using: https://github.com/prisma/prisma/issues/6719#issuecomment-1178211695

I don't like any of those that start with: if you're using PostgreSQL.... Any solution should be abstracted from the DB that is being used, and be available on the Prisma level - if you asked me.

touhidrahman commented 1 year ago

Indeed good questions to design the feature!

I think this will not make it at first, so first nanoid() will be supported but without alphabet configuration.

If more people can show up here and let us know what/why it's important for them, it will help prioritize the feature request work here.

I believe the default nanoid generator also includes symbols like dash and ubderscore to the id which is not wanted by many devs since it doesn't look good. Also, confusing 'i, I, l, 1, 0, o' is a major argument like others said. Code verification after login/signup is another user journey where safe alphabets come in handy. You would give a limited set of alphanumeric chars to make a random code that is easy to input (eg. only safe uppercase letters and numbers without 0).

smolak commented 1 year ago

Indeed good questions to design the feature! I think this will not make it at first, so first nanoid() will be supported but without alphabet configuration. If more people can show up here and let us know what/why it's important for them, it will help prioritize the feature request work here.

I believe the default nanoid generator also includes symbols like dash and ubderscore to the id which is not wanted by many devs since it doesn't look good. Also, confusing 'i, I, l, 1, 0, o' is a major argument like others said. Code verification after login/signup is another user journey where safe alphabets come in handy. You would give a limited set of alphanumeric chars to make a random code that is easy to input (eg. only safe uppercase letters and numbers without 0).

Exactly that. That counts for readability. The DX (developer's experience) is also important, e.g. take those two strings:

sdfsdf_adfasda sdfsdf-sdfsdff

Double-click on each. Only the first will be selected as a whole. It will work if you click 3x, but if you have an ID in a longer string, 3x will select the entire string, and we want to select the ID alone. Small things like that count at some point. So you want to limit the possible special characters (if you want to achieve that DX).

binajmen commented 1 year ago

I think @smolak summarised it pretty well in his previous post:

The length and alphabet features of nanoid are IMHO required to be configured. Sure, take steps, but I would like to see it adopted and configured.

What would be great as well, actually for all string-generated IDs, is the possibility to set prefixes, e.g. user for users, post for posts, etc. In my project, I rely on middleware to achieve all 3: length, alphabet, and prefix.

The perfect solution should offer the capability to define:

With these three characteristics, I'd be an happy developer 😃

djdembeck commented 1 year ago

+1 for custom alphabet support. would love to have only a-z0-9 as my id alphabet

DavoCg commented 8 months ago

Hi everyone,

We ended up using creating a custom migration adding nanoid-postgres function .

To do that you can use refer to this doc

Then you can use nanoid with length or alphabet as parameters.


model User {
  id                 String              @id @default(dbgenerated("nanoid(12, 'abcdef')"))
  createdAt          DateTime            @default(now())
  updatedAt          DateTime            @updatedAt
  username           String?             @unique
}

Hope it helps

lukeshumard commented 5 months ago

for those using MySQL / Planetscale, this is how i'm creating a nanoID with a custom length and alphabet for all models.

# lib/prisma.ts
import { PrismaClient } from '@prisma/client'
import { customAlphabet } from 'nanoid'

const prismaClientSingleton = () => {
  const nanoid = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 12)
  return new PrismaClient().$extends({
    query: {
      $allModels: {
        async create({ args, query }) {
          const id = nanoid()
          args.data = {
            ...args.data,
            id,
          }
          return query(args)
        }
      }
    }
  })
}

export const prisma = global.prisma || prismaClientSingleton()

if (process.env.NODE_ENV !== "production") global.prisma = prisma

export * from "@prisma/client"
pavankumar-v commented 5 months ago

Hi everyone,

We ended up using creating a custom migration adding nanoid-postgres function .

To do that you can use refer to this doc

Then you can use nanoid with length or alphabet as parameters.

model User { id String @id @default(dbgenerated("nanoid(12, 'abcdef')")) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt username String? @unique } Hope it helps

This Worked for me 🥳

Steps

Step 1: Create a dummy migration

npx prisma migrate dev --create-only --name add_nanoid_postgres_util

Important Read: paste this sql query to your newly generated .sql migration file

Setp 2: Add nanoid

id          String    @id @default(dbgenerated("nanoid(7, '0123456789')"))

Step 3: Create Migration and generate migration

npx prisma migrate dev --name add_nonoid

That's it, this should work! 🚀

cblberlin commented 3 months ago

If anyone are using Neon.tech or any other PostgreSQL database, you can generate a unique 6-digit (or any structure (take example from @pavankumar-v answer) that you want to) code by following these steps:

First, create a function (this one is answered by ChatGPT) in the Neon SQL Editor (or any applications that you can run SQL Script in your db) then run it:

CREATE OR REPLACE FUNCTION generate_six_digit_code()
RETURNS TEXT AS $$
DECLARE
  code TEXT;
BEGIN
  code := substring(to_char(floor(random() * 1000000)::integer, 'FM000000') from 1 for 6);
  RETURN code;
END;
$$ LANGUAGE plpgsql;

Then, for example define your Coupon model in your Prisma schema as follows:

model Coupon {
  id            String       @id @default(uuid())
  ownerId       String?
  owner         User?        @relation(fields: [ownerId], references: [userId])
  code          String       @unique @default(dbgenerated("generate_six_digit_code()"))
  createdAt     DateTime     @default(now())
  updatedAt     DateTime     @updatedAt
}

then run npx prisma generate, npx prisma db push

close your local dev environment Ctrl + C then npm run dev so see if it worked

smolak commented 3 months ago

This solves it, yes, but on a different layer, whereas it should be done through Prisma, regardless of the DB used. Just sayin'.