@autobe/compiler
Version:
AI backend server code generator
319 lines • 10.9 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.writePrismaApplication = writePrismaApplication;
const utils_1 = require("@autobe/utils");
const crypto_1 = __importDefault(require("crypto"));
const ArrayUtil_1 = require("../utils/ArrayUtil");
function writePrismaApplication(props) {
for (const file of props.application.files)
for (const model of file.models)
fillMappingName(model);
return Object.assign(Object.assign({}, Object.fromEntries(props.application.files
.filter((file) => file.filename !== "main.prisma")
.map((file) => [
file.filename,
writeFile(Object.assign(Object.assign({}, props), { file })),
]))), { "main.prisma": props.dbms === "postgres" ? POSTGRES_MAIN_FILE : SQLITE_MAIN_FILE });
}
function writeFile(props) {
return props.file.models
.map((model) => writeModel(Object.assign(Object.assign({}, props), { model })))
.join("\n\n");
}
function writeModel(props) {
return [
writeComment([
props.model.description,
"",
...(props.model.material ? [] : [`@namespace ${props.file.namespace}`]),
"@author AutoBE - https://github.com/wrtnlabs/autobe",
].join("\n"), 80),
`model ${props.model.name} {`,
addIndent(ArrayUtil_1.ArrayUtil.paddle([writeColumns(props), writeRelations(props)]).join("\n")),
"}",
].join("\n");
}
function fillMappingName(model) {
const group = new Map();
for (const ff of model.foreignFields) {
utils_1.MapUtil.take(group, ff.relation.targetModel, () => []).push(ff);
if (ff.relation.targetModel == model.name)
ff.relation.mappingName = "recursive";
}
for (const array of group.values())
if (array.length !== 1)
for (const ff of array)
ff.relation.mappingName = shortName(`${model.name}_of_${ff.name}`);
}
/* -----------------------------------------------------------
COLUMNS
----------------------------------------------------------- */
function writeColumns(props) {
return [
"//----",
"// COLUMNS",
"//----",
writePrimary({
dbms: props.dbms,
field: props.model.primaryField,
}),
...props.model.foreignFields
.map((x) => [
"",
writeField({
dbms: props.dbms,
field: x,
}),
])
.flat(),
...props.model.plainFields
.map((x) => [
"",
writeField({
dbms: props.dbms,
field: x,
}),
])
.flat(),
];
}
function writePrimary(props) {
const type = props.dbms === "postgres" ? POSTGRES_PHYSICAL_TYPES.uuid : undefined;
return [
writeComment(props.field.description, 78),
`${props.field.name} String @id${type ? ` ${type}` : ""}`,
].join("\n");
}
function writeField(props) {
const logical = LOGICAL_TYPES[props.field.type];
const physical = props.dbms === "postgres"
? POSTGRES_PHYSICAL_TYPES[props.field.type]
: undefined;
return [
writeComment(props.field.description, 78),
[
props.field.name,
`${logical}${props.field.nullable ? "?" : ""}`,
...(physical ? [physical] : []),
].join(" "),
].join("\n");
}
/* -----------------------------------------------------------
RELATIONS
----------------------------------------------------------- */
function writeRelations(props) {
const hasRelationships = props.application.files
.map((otherFile) => otherFile.models.map((otherModel) => otherModel.foreignFields
.filter((otherForeign) => otherForeign.relation.targetModel === props.model.name)
.map((otherForeign) => ({
modelName: otherModel.name,
unique: otherForeign.unique,
mappingName: otherForeign.relation.mappingName,
}))))
.flat(2);
const foreignIndexes = props.model.foreignFields.filter((f) => {
if (f.unique === true)
return props.model.uniqueIndexes.every((u) => u.fieldNames.length !== 1 || u.fieldNames[0] !== f.name);
return (props.model.uniqueIndexes.every((u) => u.fieldNames[0] !== f.name) &&
props.model.plainIndexes.every((p) => p.fieldNames[0] !== f.name));
});
const contents = [
props.model.foreignFields.map((foreign) => writeConstraint({
dbms: props.dbms,
model: props.model,
foreign,
})),
hasRelationships.map((r) => {
var _a;
return [
(_a = r.mappingName) !== null && _a !== void 0 ? _a : r.modelName,
`${r.modelName}${r.unique ? "?" : "[]"}`,
...(r.mappingName ? [`@relation("${r.mappingName}")`] : []),
].join(" ");
}),
foreignIndexes.map((field) => writeForeignIndex({
model: props.model,
field,
})),
[
...props.model.uniqueIndexes.map((unique) => writeUniqueIndex({
model: props.model,
unique,
})),
...props.model.plainIndexes.map((plain) => writePlainIndex({
model: props.model,
plain,
})),
...(props.dbms === "postgres"
? props.model.ginIndexes.map((gin) => writeGinIndex({
model: props.model,
gin,
}))
: []),
],
];
if (contents.every((c) => c.length === 0))
return [];
return [
"//----",
"// RELATIONS",
"//----",
// paddled content
...ArrayUtil_1.ArrayUtil.paddle(contents),
];
}
function writeConstraint(props) {
// spellchecker:ignore-next-line
const name = `${props.model.name}_${props.foreign.name}_rela`;
return [
props.foreign.relation.name,
`${props.foreign.relation.targetModel}${props.foreign.nullable ? "?" : ""}`,
`@relation(${[
...(props.foreign.relation.mappingName
? [`"${props.foreign.relation.mappingName}"`]
: []),
`fields: [${props.foreign.name}]`,
`references: [id]`,
`onDelete: Cascade`,
...(props.dbms === "sqlite" || name.length <= MAX_IDENTIFIER_LENGTH
? []
: [`map: "${shortName(name)}"`]),
].join(", ")})`,
].join(" ");
}
function writeForeignIndex(props) {
const name = `${props.model.name}_${props.field.name}_fkey`;
const prefix = `@@${props.field.unique === true ? "unique" : "index"}([${props.field.name}]`;
if (name.length <= MAX_IDENTIFIER_LENGTH)
return `${prefix})`;
return `${prefix}, map: "${shortName(name)}")`;
}
function writeUniqueIndex(props) {
const name = `${props.model.name}_${props.unique.fieldNames.join("_")}_key`;
const prefix = `@@unique([${props.unique.fieldNames.join(", ")}]`;
if (name.length <= MAX_IDENTIFIER_LENGTH)
return `${prefix})`;
return `${prefix}, map: "${shortName(name)}")`;
}
function writePlainIndex(props) {
const name = `${props.model.name}_${props.plain.fieldNames.join("_")}_idx`;
const prefix = `@@index([${props.plain.fieldNames.join(", ")}]`;
if (name.length <= MAX_IDENTIFIER_LENGTH)
return `${prefix})`;
return `${prefix}, map: "${shortName(name)}")`;
}
function writeGinIndex(props) {
const name = `${props.model.name}_${props.gin.fieldName}_idx`;
const prefix = `@@index([${props.gin.fieldName}(ops: raw("gin_trgm_ops"))], type: Gin`;
if (name.length <= MAX_IDENTIFIER_LENGTH)
return `${prefix})`;
return `${prefix}, map: "${shortName(name)}")`;
}
/* -----------------------------------------------------------
BACKGROUND
----------------------------------------------------------- */
function writeComment(content, length) {
return content
.split("\r\n")
.join("\n")
.split("\n")
.map((line) => line.trim())
.map((line) => {
// 77자에서 "/// " 4자를 뺀 73자가 실제 컨텐츠 최대 길이
if (line.length <= length - 4)
return [line];
const words = line.split(" ");
const result = [];
let currentLine = "";
for (const word of words) {
const potentialLine = currentLine ? `${currentLine} ${word}` : word;
if (potentialLine.length <= 73) {
currentLine = potentialLine;
}
else {
if (currentLine)
result.push(currentLine);
currentLine = word;
}
}
if (currentLine)
result.push(currentLine);
return result;
})
.flat()
.map((str) => `///${str.length ? ` ${str}` : ""}`)
.join("\n")
.trim();
}
function addIndent(content) {
return content
.split("\r\n")
.join("\n")
.split("\n")
.map((str) => ` ${str}`)
.join("\n");
}
function shortName(name) {
if (name.length <= MAX_IDENTIFIER_LENGTH)
return name;
const hash = crypto_1.default
.createHash("md5")
.update(name)
.digest("hex")
.substring(0, HASH_TRUNCATION_LENGTH);
return `${name.substring(0, MAX_IDENTIFIER_LENGTH - HASH_TRUNCATION_LENGTH - 1)}_${hash}`;
}
const LOGICAL_TYPES = {
// native types
boolean: "Boolean",
int: "Int",
double: "Float",
string: "String",
// formats
datetime: "DateTime",
uuid: "String",
uri: "String",
};
const POSTGRES_PHYSICAL_TYPES = {
int: "@db.Integer",
double: "@db.DoublePrecision",
uuid: "@db.Uuid",
datetime: "@db.Timestamptz",
uri: "@db.VarChar(80000)",
};
const POSTGRES_MAIN_FILE = utils_1.StringUtil.trim `
generator client {
provider = "prisma-client-js"
engineType = "client"
previewFeatures = ["postgresqlExtensions", "views"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
extensions = [pg_trgm]
}
generator markdown {
provider = "prisma-markdown"
output = "../../docs/ERD.md"
}
`;
const SQLITE_MAIN_FILE = utils_1.StringUtil.trim `
generator client {
provider = "prisma-client-js"
engineType = "client"
}
datasource db {
provider = "sqlite"
url = "file:../db.sqlite"
}
generator markdown {
provider = "prisma-markdown"
output = "../../docs/ERD.md"
}
`;
const MAX_IDENTIFIER_LENGTH = 63;
const HASH_TRUNCATION_LENGTH = 8;
//# sourceMappingURL=writePrismaApplication.js.map