Skip to content

Instantly share code, notes, and snippets.

@stevenaanen
Last active November 7, 2023 14:52
Show Gist options
  • Save stevenaanen/51788591ca8fc38fd40f54f8a2e6373f to your computer and use it in GitHub Desktop.
Save stevenaanen/51788591ca8fc38fd40f54f8a2e6373f to your computer and use it in GitHub Desktop.
Transform Prisma Schema to js-friendly names
// @ts-check
/*
* Run using `node convert-casing-after-prisma-pull.mjs` after installing `change-case` in your workspace.
* Then pipe your schema into it.
*
* Script will normalize the model names, field names and enums.
* It should not affect your database schema, only the generated js client from Prisma.
*
* Tip: after processing, run `prisma format` on the file.
*/
import { camelCase, pascalCase } from "change-case";
import fs from "node:fs";
// read command line input stream and store in variable
const input = fs.readFileSync(0, "utf-8");
// multiline split code blocks
const blocks = input.matchAll(/^.+\{\n[\S\s]*?\}/gm);
const PrismaScalars = [
"String",
"Boolean",
"Int",
"BigInt",
"Float",
"Decimal",
"DateTime",
"Json",
"Bytes",
"Unsupported",
"String?",
"Boolean?",
"Int?",
"BigInt?",
"Float?",
"Decimal?",
"DateTime?",
"Json?",
"Bytes?",
"Unsupported?",
];
// loop over iterator
for (const blockMatch of blocks) {
const block = blockMatch[0];
const modelReg = /(?<type>model|enum) (?<modelName>\S+) \{/;
const modelName = modelReg.exec(block)?.groups?.modelName;
const lines = block.split("\n");
const updatedLines = lines.map((line) => {
return (
line
// Replace model name
.replace(modelReg, (_, modelType, modelName) => {
return `${modelType} ${pascalCase(modelName)} {`;
})
// Replace field names and handle @map
.replace(
/^\s{2}(?<field>\w+_?[\w_]+)\s+(?<fieldType>[\w?]+)/,
(_, field, fieldType) => {
if (!PrismaScalars.includes(fieldType)) {
// these are relations
const isOptional = fieldType.endsWith("?");
return ` ${pascalCase(field)} ${pascalCase(fieldType)}${
isOptional ? "?" : ""
}`;
}
return ` ${camelCase(field)} ${fieldType} @map("${field}")`;
}
)
// Handle @relations
.replace(
/^\s{2}(?<field>\w+)\s+(?<relationName>[\w?]+)\s+@relation/,
(_, field, relationName) => {
const isOptional = relationName.endsWith("?");
return ` ${pascalCase(field)} ${pascalCase(relationName)}${
isOptional ? "?" : ""
} @relation`;
}
)
.replace(
/fields: \[(?<relationField>\w_?\w+)\]/,
(_, relationField) => {
return `fields: [${camelCase(relationField)}]`;
}
)
.replace(
/references: \[(?<referencesField>\w_?\w+)\]/,
(_, referencesField) => {
return `references: [${camelCase(referencesField)}]`;
}
)
.replace(
/references: \[(?<referencesField>\w_?\w+)\]/,
(_, referencesField) => {
return `references: [${camelCase(referencesField)}]`;
}
)
.replace(
/@@(?<type>id|index|unique)\(\[(?<relationships>[^\]]*)/,
(_, type, relationships) => {
const relations = relationships
.split(",")
.map((/** @type {string} */ r) => {
const relation = r.trim();
if (relation.includes("(length: ")) {
return relation.replace(
/(?<key>\w+)(?<params>\([^\)]*)\)/,
(_, key, params) => {
return `${camelCase(key)} ${params})`;
}
);
} else {
return camelCase(relation);
}
});
return `@@${type}([${relations.join(", ")}`;
}
)
// Handle constants
.replace(/^\s{2}(?<constant>[\w_]+)$/, (_, constant) => {
return ` ${pascalCase(constant)} @map("${constant}")`;
})
.replace(/^\}/, "")
);
});
const suffix = modelName ? `\n @@map("${modelName}")\n` : "";
process.stdout.write(`${updatedLines.join("\n")}${suffix}}\n\n`);
}
@stevenaanen
Copy link
Author

Nice! This helps a lot when pulling the schema from an old database.

I added a few things so that relationships are always CamelCase'd and relations and indexes keep working correctly.

Great improvements @DannyStreur! Updated the gist

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment