-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Automatically pascal case model names during introspection #1934
Comments
I also want fields must mapping to camelCase, because I introspect from a database with ~ 100 tables & 10~20 column per table. I don't want to map field by manual. I hope has a flag, when introspect used to switching between modes. |
I wanted to follow standard postgres naming practices (using snake case for column and field names so I don't have to quote everything when using raw sql), but still end up with a generated client that follows normal Typescript/prisma conventions (Pascal case for models, camelcase for field names, plurals for array relationships). I wrote a script that handles all of my personal naming issues. Might be easier with access to the AST and there could be cases I'm missing since I'm starting on a new app and so my schema is fairly simple, but works for my use case and shouldn't be too hard to add additional cases as needed. Didn't bother trying to handle spacing. Saving the file in vscode with the prisma plugin fixes it. It handles:
import fs from "fs";
import path from "path";
const PRISMA_FILE_PATH = path.join(__dirname, "..", "prisma", "schema.prisma");
function snakeToCamel(str: string) {
return str.replace(/([-_]\w)/g, (g) => g[1].toUpperCase());
}
function snakeToPascal(str: string) {
let camelCase = snakeToCamel(str);
return `${camelCase[0].toUpperCase()}${camelCase.substr(1)}`;
}
const PRISMA_PRIMITIVES = ["String", "Boolean", "Int", "Float", "DateTime"];
function isPrimitiveType(typeName: string) {
return PRISMA_PRIMITIVES.includes(typeName);
}
function fixFieldsArrayString(fields: string) {
return fields
.split(", ")
.map((field) => snakeToCamel(field))
.join(", ");
}
async function fixPrismaFile() {
const text = fs.readFileSync(path.join(PRISMA_FILE_PATH), "utf8");
const textAsArray = text.split("\n");
const fixedText = [];
let currentModelName: string | null = null;
let hasAddedModelMap = false;
for (let line of textAsArray) {
// Are we at the start of a model definition
const modelMatch = line.match(/^model (\w+) {$/);
if (modelMatch) {
currentModelName = modelMatch[1];
hasAddedModelMap = false;
const pascalModelName = snakeToPascal(currentModelName);
fixedText.push(`model ${pascalModelName} {`);
continue;
}
// We don't need to change anything if we aren't in a model body
if (!currentModelName) {
fixedText.push(line);
continue;
}
// Add the @@map to the table name for the model
if (!hasAddedModelMap && (line.match(/\s+@@/) || line === "}")) {
if (line === "}") {
fixedText.push("");
}
fixedText.push(` @@map("${currentModelName}")`);
hasAddedModelMap = true;
}
// Renames field and applies a @map to the field name if it is snake case
// Adds an s to the field name if the type is an array relation
const fieldMatch = line.match(/\s\s(\w+)\s+(\w+)(\[\])?/);
let fixedLine = line;
if (fieldMatch) {
const [, currentFieldName, currentFieldType, isArrayType] = fieldMatch;
let fixedFieldName = snakeToCamel(currentFieldName);
if (isArrayType && !fixedFieldName.endsWith("s")) {
fixedFieldName = `${fixedFieldName}s`;
}
fixedLine = fixedLine.replace(currentFieldName, fixedFieldName);
// Add map if we needed to convert the field name and the field is not a relational type
// If it's relational, the field type will be a non-primitive, hence the isPrimitiveType check
if (currentFieldName.includes("_") && isPrimitiveType(currentFieldType)) {
fixedLine = `${fixedLine} @map("${currentFieldName}")`;
}
}
// Capitalizes model names in field types
const fieldTypeMatch = fixedLine.match(/\s\s\w+\s+(\w+)/);
if (fieldTypeMatch) {
const currentFieldType = fieldTypeMatch[1];
const fieldTypeIndex = fieldTypeMatch[0].lastIndexOf(currentFieldType);
const fixedFieldType = snakeToPascal(currentFieldType);
const startOfLine = fixedLine.substr(0, fieldTypeIndex);
const restOfLine = fixedLine.substr(
fieldTypeIndex + currentFieldType.length
);
fixedLine = `${startOfLine}${fixedFieldType}${restOfLine}`;
}
// Changes `fields: [relation_id]` in @relation to camel case
const relationFieldsMatch = fixedLine.match(/fields:\s\[([\w,\s]+)\]/);
if (relationFieldsMatch) {
const fields = relationFieldsMatch[1];
fixedLine = fixedLine.replace(fields, fixFieldsArrayString(fields));
}
// Changes fields listed in @@index or @@unique to camel case
const indexUniqueFieldsMatch = fixedLine.match(/@@\w+\(\[([\w,\s]+)\]/);
if (indexUniqueFieldsMatch) {
const fields = indexUniqueFieldsMatch[1];
fixedLine = fixedLine.replace(fields, fixFieldsArrayString(fields));
}
fixedText.push(fixedLine);
}
fs.writeFileSync(PRISMA_FILE_PATH, fixedText.join("\n"));
}
fixPrismaFile(); Edit: Added check to avoid adding @Map to relation fields. |
Just as an aside @TLadd, you can use this snippet for prettier: import prettier from 'prettier';
let PRETTIER_OPTS = {};
if (!process.env._HAS_RESOLVED_PRETTIER) {
const prettierConfigPath = prettier.resolveConfigFile.sync();
if (prettierConfigPath) {
const o = prettier.resolveConfig.sync(prettierConfigPath);
if (o) {
PRETTIER_OPTS = o;
}
}
process.env._HAS_RESOLVED_PRETTIER = 'true';
}
export const formatWithPrettier = (output: string) => {
return prettier.format(output, {
parser: 'typescript', // or X parser
...PRETTIER_OPTS,
});
}; |
For those using import * as fs from 'fs'
import * as path from 'path'
const PRISMA_FILE_PATH = path.join(__dirname, 'schema.prisma')
function snakeToCamel(str: string) {
return str.replace(/([-_]\w)/g, (g) => g[1].toUpperCase())
}
function snakeToPascal(str: string) {
let camelCase = snakeToCamel(str)
return `${camelCase[0].toUpperCase()}${camelCase.substr(1)}`
}
const PRISMA_PRIMITIVES = ['String', 'Boolean', 'Int', 'Float', 'DateTime']
const KNEX_INTERNAL_MODELS = ['knex_migrations', 'knex_migrations_lock']
function isKnexInternalModel(typeName: string) {
return KNEX_INTERNAL_MODELS.includes(typeName)
}
function isPrimitiveType(typeName: string) {
return PRISMA_PRIMITIVES.includes(typeName)
}
function fixFieldsArrayString(fields: string) {
return fields
.split(', ')
.map((field) => snakeToCamel(field))
.join(', ')
}
async function fixPrismaFile() {
const text = await fs.promises.readFile(PRISMA_FILE_PATH, 'utf8')
const textAsArray = text.split('\n')
const fixedText = []
let currentModelName: string | null = null
let hasAddedModelMap = false
for (let line of textAsArray) {
// Are we at the start of a model definition
const modelMatch = line.match(/^model (\w+) {$/)
if (modelMatch) {
currentModelName = modelMatch[1]
if (isKnexInternalModel(currentModelName)) {
continue
}
hasAddedModelMap = false
const pascalModelName = snakeToPascal(currentModelName)
fixedText.push(`model ${pascalModelName} {`)
continue
}
if (currentModelName && isKnexInternalModel(currentModelName)) {
continue
}
// We don't need to change anything if we aren't in a model body
if (!currentModelName) {
fixedText.push(line)
continue
}
// Add the @@map to the table name for the model
if (!hasAddedModelMap && (line.match(/\s+@@/) || line === '}')) {
if (line === '}') {
fixedText.push('')
}
fixedText.push(` @@map("${currentModelName}")`)
hasAddedModelMap = true
}
// Renames field and applies a @map to the field name if it is snake case
// Adds an s to the field name if the type is an array relation
const fieldMatch = line.match(/\s\s(\w+)\s+(\w+)(\[\])?/)
let fixedLine = line
if (fieldMatch) {
const [, currentFieldName, currentFieldType, isArrayType] = fieldMatch
let fixedFieldName = snakeToCamel(currentFieldName)
if (isArrayType && !fixedFieldName.endsWith('s')) {
fixedFieldName = `${fixedFieldName}s`
}
fixedLine = fixedLine.replace(currentFieldName, fixedFieldName)
// Add map if we needed to convert the field name and the field is not a relational type
// If it's relational, the field type will be a non-primitive, hence the isPrimitiveType check
if (currentFieldName.includes('_') && isPrimitiveType(currentFieldType)) {
fixedLine = `${fixedLine} @map("${currentFieldName}")`
}
}
// Capitalizes model names in field types
const fieldTypeMatch = fixedLine.match(/\s\s\w+\s+(\w+)/)
if (fieldTypeMatch) {
const currentFieldType = fieldTypeMatch[1]
const fieldTypeIndex = fieldTypeMatch[0].lastIndexOf(currentFieldType)
const fixedFieldType = snakeToPascal(currentFieldType)
const startOfLine = fixedLine.substr(0, fieldTypeIndex)
const restOfLine = fixedLine.substr(
fieldTypeIndex + currentFieldType.length
)
fixedLine = `${startOfLine}${fixedFieldType}${restOfLine}`
}
// Changes `fields: [relation_id]` in @relation to camel case
const relationFieldsMatch = fixedLine.match(/fields:\s\[([\w,\s]+)\]/)
if (relationFieldsMatch) {
const fields = relationFieldsMatch[1]
fixedLine = fixedLine.replace(fields, fixFieldsArrayString(fields))
}
// Changes fields listed in @@index or @@unique to camel case
const indexUniqueFieldsMatch = fixedLine.match(/@@\w+\(\[([\w,\s]+)\]/)
if (indexUniqueFieldsMatch) {
const fields = indexUniqueFieldsMatch[1]
fixedLine = fixedLine.replace(fields, fixFieldsArrayString(fields))
}
fixedText.push(fixedLine)
}
await fs.promises.writeFile(PRISMA_FILE_PATH, fixedText.join('\n'))
}
fixPrismaFile() |
For those looking for fixing the naming convention automatically, I wrote a utility program that utilizes the Prisma schema AST, provided by the internal API https://github.com/IBM/prisma-schema-transformer |
@ExiaSR Thanks for the utility! I just found this that might help with the same thing https://paljs.com/cli/schema/ for anyone who is interested. It supposedly also helps generate the TypeScript interface classes for the models. |
Does resolving this take a lot of time? I think, with AST, we can easily deal with this. There are already many libraries for changing cases(e.g. snake_case to camelCase or PascalCase or vise versa) |
This is what worked for us, super simple and fast. Thanks! |
I'm looking https://paljs.com/cli/schema/, it's good, but it has some issues: feat: changing case of model name of single word · Issue #241 · paljs/prisma-tools Then I trying https://github.com/IBM/prisma-schema-transformer, it's great, but not yet supported prisma 3 now. (have committ log but not release) So I fork a patch for it and write my solution on README.md |
This feature request seems to be included in this one: Introspection Configuration File (#1184 ) Should we close it and point people to #1184 in the future? |
I wrote my own solution because:
So here's my solution: It works as a state machine, going over the file line by line.
Basically my workflow is:
Anyway, hope this helps anyone else with the same problem. |
It would be great to see options for naming conventions, like many are saying snake case for table names and for our team we use camel case on the model names. It's kind of crazy to see all the lengths that everyone has gone to, to handle their cases 😬 |
Hey 👋 I recently came across |
@nikolasburk @janpio this is another 3 year old issue asking for the same feature. the community demand is there, your competitors already support it. this is another missing DX feature that I'm sorry to say is not a good look for Prisma. I'd hope an open source project with corporate backing would have resources enough to tackle issues with this kind of age. |
It's open source, submit a PR, and help the community. |
what's open source? that's the first I've heard of it |
Prisma itself is open source. Feel free to create a PR and get some credits :-) |
@shellscape you stated yourself that its open source (link to description of open source):
This means the community owns it. You, me, anyone willing to help. To demean other volunteers because your feature hasn't been worked on, when you are free and enabled to build/test/deliver it yourself, feels a bit disingenuous. |
Credits! Oh man. That sounds awesome. What do I get to do with those credits? Wait so like, I can see the code and I can change the code?! That's incredible. I wonder if there's a project out there that millions of developers use every day that I could maintain. Maybe they'll let me if I have enough credits?
A thousand apologies, sir. The next time I have a criticism I'll just keep it to myself for sure. I surely wouldn't want to demean anyone with criticism. |
No, we don't, sorry. We are busy with other things that do not have easy manual or automated workarounds (see previous comments). (Please keep it civil, everyone. I don't want to have to start hiding or deleting comments. Thanks.) |
As mentioned above https://github.com/iiian/prisma-case-format works great. This is the script I used in package.json to pull the schema for an introspection-only workflow.
|
I am reading this: https://www.prisma.io/docs/orm/prisma-client/setup-and-configuration/custom-model-and-field-names And I am now doing a refacto migration of my whole code base and schema to not use the PascalCase that was generateed by prisma. This issue should be in the documentation, to save people time so they can save humanity. <3 |
Problem
Having to manually update model names to conform to Prisma's naming conventions using the introspection tool is 1) annoying to do for many snake cased table names, and 2) prevents an introspection-only schema approach to using prisma2.
Solution
Automatically change model names from this format:
To this:
Alternatives
I could write my own script.
Additional context
Started in discussion at:
#1925
Just going to dump some regex here that might help write that script:
The text was updated successfully, but these errors were encountered: