@reliverse/rse
Version:
@reliverse/rse is your all-in-one companion for bootstrapping and improving any kind of projects (especially web apps built with frameworks like Next.js) — whether you're kicking off something new or upgrading an existing app. It is also a little AI-power
163 lines (162 loc) • 5.85 kB
JavaScript
import { produceSchema } from "@mrleebo/prisma-ast";
import { existsSync } from "@reliverse/relifso";
import { capitalizeFirstLetter } from "better-auth";
import { getAuthTables } from "better-auth/db";
import fs from "fs/promises";
import path from "path";
export const generatePrismaSchema = async ({
adapter,
options,
file
}) => {
const provider = adapter.options?.provider || "postgresql";
const tables = getAuthTables(options);
const filePath = file || "./prisma/schema.prisma";
const schemaPrismaExist = existsSync(path.join(process.cwd(), filePath));
let schemaPrisma = "";
if (schemaPrismaExist) {
schemaPrisma = await fs.readFile(
path.join(process.cwd(), filePath),
"utf-8"
);
} else {
schemaPrisma = getNewPrisma(provider);
}
const manyToManyRelations = /* @__PURE__ */ new Map();
for (const table in tables) {
const fields = tables[table]?.fields;
for (const field in fields) {
const attr = fields[field];
if (attr.references) {
const referencedModel = capitalizeFirstLetter(attr.references.model);
if (!manyToManyRelations.has(referencedModel)) {
manyToManyRelations.set(referencedModel, /* @__PURE__ */ new Set());
}
manyToManyRelations.get(referencedModel).add(capitalizeFirstLetter(table));
}
}
}
const schema = produceSchema(schemaPrisma, (builder) => {
for (const table in tables) {
let getType = function({
isBigint,
isOptional,
type
}) {
if (type === "string") {
return isOptional ? "String?" : "String";
}
if (type === "number" && isBigint) {
return isOptional ? "BigInt?" : "BigInt";
}
if (type === "number") {
return isOptional ? "Int?" : "Int";
}
if (type === "boolean") {
return isOptional ? "Boolean?" : "Boolean";
}
if (type === "date") {
return isOptional ? "DateTime?" : "DateTime";
}
if (type === "string[]") {
return isOptional ? "String[]" : "String[]";
}
if (type === "number[]") {
return isOptional ? "Int[]" : "Int[]";
}
};
const fields = tables[table]?.fields;
const originalTable = tables[table]?.modelName;
const modelName = capitalizeFirstLetter(originalTable || "");
const prismaModel = builder.findByType("model", {
name: modelName
});
if (!prismaModel) {
if (provider === "mongodb") {
builder.model(modelName).field("id", "String").attribute("id").attribute(`map("_id")`);
} else {
if (options.advanced?.database?.useNumberId) {
builder.model(modelName).field("id", "Int").attribute("id").attribute("default(autoincrement())");
} else {
builder.model(modelName).field("id", "String").attribute("id");
}
}
}
for (const field in fields) {
const attr = fields[field];
if (prismaModel) {
const isAlreadyExist = builder.findByType("field", {
name: field,
within: prismaModel.properties
});
if (isAlreadyExist) {
continue;
}
}
builder.model(modelName).field(
field,
field === "id" && options.advanced?.database?.useNumberId ? getType({
isBigint: false,
isOptional: false,
type: "number"
}) : getType({
isBigint: attr?.bigint || false,
isOptional: !attr?.required,
type: attr.references?.field === "id" ? options.advanced?.database?.useNumberId ? "number" : "string" : attr.type
})
);
if (attr.unique) {
builder.model(modelName).blockAttribute(`unique([${field}])`);
}
if (attr.references) {
let action = "Cascade";
if (attr.references.onDelete === "no action") action = "NoAction";
else if (attr.references.onDelete === "set null") action = "SetNull";
else if (attr.references.onDelete === "set default")
action = "SetDefault";
else if (attr.references.onDelete === "restrict") action = "Restrict";
builder.model(modelName).field(
attr.references.model.toLowerCase(),
capitalizeFirstLetter(attr.references.model)
).attribute(
`relation(fields: [${field}], references: [${attr.references.field}], onDelete: ${action})`
);
}
if (!attr.unique && !attr.references && provider === "mysql" && attr.type === "string") {
builder.model(modelName).field(field).attribute("db.Text");
}
}
if (manyToManyRelations.has(modelName)) {
for (const relatedModel of manyToManyRelations.get(modelName)) {
const fieldName = `${relatedModel.toLowerCase()}s`;
const existingField = builder.findByType("field", {
name: fieldName,
within: prismaModel?.properties
});
if (!existingField) {
builder.model(modelName).field(fieldName, `${relatedModel}[]`);
}
}
}
const hasAttribute = builder.findByType("attribute", {
name: "map",
within: prismaModel?.properties
});
if (originalTable !== modelName && !hasAttribute) {
builder.model(modelName).blockAttribute("map", originalTable);
}
}
});
return {
code: schema.trim() === schemaPrisma.trim() ? "" : schema,
fileName: filePath,
overwrite: true
};
};
const getNewPrisma = (provider) => `generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "${provider}"
url = ${provider === "sqlite" ? `"file:./dev.db"` : `env("DATABASE_URL")`}
}`;