UNPKG

drizzle-dbml-generator

Version:
436 lines (426 loc) 17 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { mysqlGenerate: () => mysqlGenerate, pgGenerate: () => pgGenerate, sqliteGenerate: () => sqliteGenerate }); module.exports = __toCommonJS(src_exports); // src/dbml.ts var DBML = class { built = ""; insert(str) { this.built += str; return this; } concatAll(strs) { for (let i = 0; i < strs.length; i++) { this.insert(strs[i]); this.newLine(2); } return this; } /** * Escapes characters that aren't allowed in DBML surrounding the input with double quotes */ escapeSpaces(str) { this.built += /\W/.test(str) || /^[0-9_]/.test(str) ? `"${str}"` : str; return this; } escapeType(str) { this.built += str.includes(" ") || str.includes(")[") ? `"${str}"` : str; return this; } newLine(newLines = 1) { this.built += "\n".repeat(newLines); return this; } tab(tabs = 1) { this.built += " ".repeat(tabs * 2); return this; } build() { return this.built.trimEnd(); } }; // src/utils.ts function formatList(items, escapeName, escapeSpaces = false) { return items.reduce( (str, item) => `${str}, ${escapeSpaces && item.includes(" ") ? escapeName(item) : item}`, "" ).slice(2); } function wrapColumns(columns, escapeName) { const formatted = formatList( columns.map((column) => column.name), escapeName, true ); return columns.length === 1 ? columns[0].name : `(${formatted})`; } function wrapColumnNames(columns, escapeName) { return columns.length === 1 ? columns[0] : `(${formatList(columns, escapeName)})`; } // src/generators/common.ts var import_drizzle_orm = require("drizzle-orm"); // src/symbols.ts var AnyInlineForeignKeys = Symbol.for("drizzle:AnyInlineForeignKeys"); var PgInlineForeignKeys = Symbol.for("drizzle:PgInlineForeignKeys"); var MySqlInlineForeignKeys = Symbol.for("drizzle:MySqlInlineForeignKeys"); var SQLiteInlineForeignKeys = Symbol.for("drizzle:SQLiteInlineForeignKeys"); var TableName = Symbol.for("drizzle:Name"); var Schema = Symbol.for("drizzle:Schema"); var ExtraConfigBuilder = Symbol.for("drizzle:ExtraConfigBuilder"); var ExtraConfigColumns = Symbol.for("drizzle:ExtraConfigColumns"); // src/generators/common.ts var import_pg_core = require("drizzle-orm/pg-core"); var import_mysql_core = require("drizzle-orm/mysql-core"); var import_sqlite_core = require("drizzle-orm/sqlite-core"); var import_casing = require("drizzle-orm/casing"); var import_fs = require("fs"); var import_path = require("path"); var BaseGenerator = class { schema; relational; generatedRefs = []; InlineForeignKeys = AnyInlineForeignKeys; buildQueryConfig = { escapeName: () => "", escapeParam: () => "", escapeString: () => "", casing: new import_casing.CasingCache() }; constructor(schema, relational) { this.schema = schema; this.relational = relational; } isIncremental(_column) { return false; } mapDefaultValue(value) { let str = ""; if (typeof value === "string") { str = `'${value}'`; } else if (typeof value === "boolean" || typeof value === "number") { str = `${value}`; } else if (value === null) { str = "null"; } else if (value instanceof Date) { str = `'${value.toISOString().replace("T", " ").replace("Z", "")}'`; } else if ((0, import_drizzle_orm.is)(value, import_drizzle_orm.SQL)) { str = `\`${value.toQuery(this.buildQueryConfig).sql}\``; } else { str = `\`${JSON.stringify(value)}\``; } return str; } generateColumn(column) { const dbml = new DBML().tab().escapeSpaces(column.name).insert(" ").escapeType(column.getSQLType()); const constraints = []; if (column.primary) { constraints.push("pk"); } if (column.notNull) { constraints.push("not null"); } if (column.isUnique) { constraints.push("unique"); } if (this.isIncremental(column)) { constraints.push("increment"); } if (column.default !== void 0) { constraints.push(`default: ${this.mapDefaultValue(column.default)}`); } if (constraints.length > 0) { dbml.insert(` [${formatList(constraints, this.buildQueryConfig.escapeName)}]`); } return dbml.build(); } generateTable(table) { if (!this.relational) { this.generateForeignKeys(table[this.InlineForeignKeys]); } const dbml = new DBML().insert("table "); if (table[Schema]) { dbml.escapeSpaces(table[Schema]).insert("."); } dbml.escapeSpaces(table[TableName]).insert(" {").newLine(); const columns = (0, import_drizzle_orm.getTableColumns)(table); for (const columnName in columns) { const column = columns[columnName]; const columnDBML = this.generateColumn(column); dbml.insert(columnDBML).newLine(); } const extraConfigBuilder = table[ExtraConfigBuilder]; const extraConfigColumns = table[ExtraConfigColumns]; const extraConfig = extraConfigBuilder?.(extraConfigColumns ?? {}); const builtIndexes = (Array.isArray(extraConfig) ? extraConfig : Object.values(extraConfig ?? {})).map((b) => b?.build(table)).filter((b) => b !== void 0).filter((index) => !((0, import_drizzle_orm.is)(index, import_pg_core.Check) || (0, import_drizzle_orm.is)(index, import_mysql_core.Check) || (0, import_drizzle_orm.is)(index, import_sqlite_core.Check))); const fks = builtIndexes.filter( (index) => (0, import_drizzle_orm.is)(index, import_pg_core.ForeignKey) || (0, import_drizzle_orm.is)(index, import_mysql_core.ForeignKey) || (0, import_drizzle_orm.is)(index, import_sqlite_core.ForeignKey) ); if (!this.relational) { this.generateForeignKeys(fks); } if (extraConfigBuilder && builtIndexes.length > fks.length) { const indexes = extraConfig ?? {}; dbml.newLine().tab().insert("indexes {").newLine(); for (const indexName in indexes) { const index = indexes[indexName].build(table); dbml.tab(2); if ((0, import_drizzle_orm.is)(index, import_pg_core.Index) || (0, import_drizzle_orm.is)(index, import_mysql_core.Index) || (0, import_drizzle_orm.is)(index, import_sqlite_core.Index)) { const configColumns = index.config.columns.flatMap( (entry) => (0, import_drizzle_orm.is)(entry, import_drizzle_orm.SQL) ? entry.queryChunks.filter((v) => (0, import_drizzle_orm.is)(v, import_drizzle_orm.Column)) : entry ); const idxColumns = wrapColumns( configColumns, this.buildQueryConfig.escapeName ); const idxProperties = index.config.name ? ` [name: '${index.config.name}'${index.config.unique ? ", unique" : ""}]` : ""; dbml.insert(`${idxColumns}${idxProperties}`); } if ((0, import_drizzle_orm.is)(index, import_pg_core.PrimaryKey) || (0, import_drizzle_orm.is)(index, import_mysql_core.PrimaryKey) || (0, import_drizzle_orm.is)(index, import_sqlite_core.PrimaryKey)) { const pkColumns = wrapColumns(index.columns, this.buildQueryConfig.escapeName); dbml.insert(`${pkColumns} [pk]`); } if ((0, import_drizzle_orm.is)(index, import_pg_core.UniqueConstraint) || (0, import_drizzle_orm.is)(index, import_mysql_core.UniqueConstraint) || (0, import_drizzle_orm.is)(index, import_sqlite_core.UniqueConstraint)) { const uqColumns = wrapColumns(index.columns, this.buildQueryConfig.escapeName); const uqProperties = index.name ? `[name: '${index.name}', unique]` : "[unique]"; dbml.insert(`${uqColumns} ${uqProperties}`); } dbml.newLine(); } dbml.tab().insert("}").newLine(); } dbml.insert("}"); return dbml.build(); } generateEnum(_enum_) { return ""; } generateForeignKeys(fks) { for (let i = 0; i < fks.length; i++) { const sourceTable = fks[i].table; const foreignTable = fks[i].reference().foreignTable; const sourceSchema = sourceTable[Schema]; const foreignSchema = foreignTable[Schema]; const sourceColumns = fks[i].reference().columns; const foreignColumns = fks[i].reference().foreignColumns; const dbml = new DBML().insert(`ref ${fks[i].getName()}: `); if (sourceSchema) { dbml.escapeSpaces(sourceSchema).insert("."); } dbml.escapeSpaces(sourceTable[TableName]).insert(".").insert(wrapColumns(sourceColumns, this.buildQueryConfig.escapeName)).insert(" > "); if (foreignSchema) { dbml.escapeSpaces(foreignSchema).insert("."); } dbml.escapeSpaces(foreignTable[TableName]).insert(".").insert(wrapColumns(foreignColumns, this.buildQueryConfig.escapeName)); const actions = [ `delete: ${fks[i].onDelete || "no action"}`, `update: ${fks[i].onUpdate || "no action"}` ]; const actionsStr = ` [${formatList(actions, this.buildQueryConfig.escapeName)}]`; dbml.insert(actionsStr); this.generatedRefs.push(dbml.build()); } } generateRelations(relations_) { const left = {}; const right = {}; for (let i = 0; i < relations_.length; i++) { const relations = relations_[i].config({ one: (0, import_drizzle_orm.createOne)(relations_[i].table), many: (0, import_drizzle_orm.createMany)(relations_[i].table) }); for (const relationName in relations) { const relation = relations[relationName]; const tableNames = [ relations_[i].table[TableName], relation.referencedTableName ].sort(); const key = `${tableNames[0]}-${tableNames[1]}${relation.relationName ? `-${relation.relationName}` : ""}`; if ((0, import_drizzle_orm.is)(relation, import_drizzle_orm.One) && relation.config?.references.length || 0 > 0) { left[key] = { type: "one", sourceSchema: relation.sourceTable[Schema], sourceTable: relation.sourceTable[TableName], sourceColumns: relation.config?.fields.map((col) => col.name) || [], foreignSchema: relation.referencedTable[Schema], foreignTable: relation.referencedTableName, foreignColumns: relation.config?.references.map((col) => col.name) || [] }; } else { right[key] = { type: (0, import_drizzle_orm.is)(relation, import_drizzle_orm.One) ? "one" : "many" }; } } } for (const key in left) { const sourceSchema = left[key].sourceSchema || ""; const sourceTable = left[key].sourceTable || ""; const foreignSchema = left[key].foreignSchema || ""; const foreignTable = left[key].foreignTable || ""; const sourceColumns = left[key].sourceColumns || []; const foreignColumns = left[key].foreignColumns || []; const relationType = right[key]?.type || "one"; if (sourceColumns.length === 0 || foreignColumns.length === 0) { throw Error( `Not enough information was provided to create relation between "${sourceTable}" and "${foreignTable}"` ); } const dbml = new DBML().insert("ref: "); if (sourceSchema) { dbml.escapeSpaces(sourceSchema).insert("."); } dbml.escapeSpaces(sourceTable).insert(".").insert(wrapColumnNames(sourceColumns, this.buildQueryConfig.escapeName)).insert(` ${relationType === "one" ? "-" : ">"} `); if (foreignSchema) { dbml.escapeSpaces(foreignSchema).insert("."); } dbml.escapeSpaces(foreignTable).insert(".").insert(wrapColumnNames(foreignColumns, this.buildQueryConfig.escapeName)); this.generatedRefs.push(dbml.build()); } } generate() { const generatedEnums = []; const generatedTables = []; const relations = []; for (const key in this.schema) { const value = this.schema[key]; if ((0, import_pg_core.isPgEnum)(value)) { generatedEnums.push(this.generateEnum(value)); } else if ((0, import_drizzle_orm.is)(value, import_pg_core.PgTable) || (0, import_drizzle_orm.is)(value, import_mysql_core.MySqlTable) || (0, import_drizzle_orm.is)(value, import_sqlite_core.SQLiteTable)) { generatedTables.push(this.generateTable(value)); } else if ((0, import_drizzle_orm.is)(value, import_drizzle_orm.Relations)) { relations.push(value); } } if (this.relational) { this.generateRelations(relations); } const dbml = new DBML().concatAll(generatedEnums).concatAll(generatedTables).concatAll(this.generatedRefs).build(); return dbml; } }; function writeDBMLFile(dbml, outPath) { const path = (0, import_path.resolve)(process.cwd(), outPath); try { (0, import_fs.writeFileSync)(path, dbml, { encoding: "utf-8" }); } catch (err) { console.error("An error ocurred while writing the generated DBML"); throw err; } } // src/generators/pg.ts var import_casing2 = require("drizzle-orm/casing"); var PgGenerator = class extends BaseGenerator { InlineForeignKeys = PgInlineForeignKeys; buildQueryConfig = { escapeName: (name) => `"${name}"`, escapeParam: (num) => `$${num + 1}`, escapeString: (str) => `'${str.replace(/'/g, "''")}'`, casing: new import_casing2.CasingCache() }; isIncremental(column) { return column.getSQLType().includes("serial"); } generateEnum(enum_) { const dbml = new DBML().insert("enum ").escapeSpaces(enum_.enumName).insert(" {").newLine(); for (let i = 0; i < enum_.enumValues.length; i++) { dbml.tab().escapeSpaces(enum_.enumValues[i]).newLine(); } dbml.insert("}"); return dbml.build(); } }; function pgGenerate(options) { options.relational ||= false; const dbml = new PgGenerator(options.schema, options.relational).generate(); options.out && writeDBMLFile(dbml, options.out); return dbml; } // src/generators/mysql.ts var import_drizzle_orm2 = require("drizzle-orm"); var import_mysql_core2 = require("drizzle-orm/mysql-core"); var import_casing3 = require("drizzle-orm/casing"); var MySqlGenerator = class extends BaseGenerator { InlineForeignKeys = MySqlInlineForeignKeys; buildQueryConfig = { escapeName: (name) => `\`${name}\``, escapeParam: (_num) => "?", escapeString: (str) => `'${str.replace(/'/g, "''")}'`, casing: new import_casing3.CasingCache() }; isIncremental(column) { return column.getSQLType().includes("serial") || (0, import_drizzle_orm2.is)(column, import_mysql_core2.MySqlColumnWithAutoIncrement) && column.autoIncrement; } }; function mysqlGenerate(options) { options.relational ||= false; const dbml = new MySqlGenerator(options.schema, options.relational).generate(); options.out && writeDBMLFile(dbml, options.out); return dbml; } // src/generators/sqlite.ts var import_drizzle_orm3 = require("drizzle-orm"); var import_sqlite_core2 = require("drizzle-orm/sqlite-core"); var import_casing4 = require("drizzle-orm/casing"); var SQLiteGenerator = class extends BaseGenerator { InlineForeignKeys = SQLiteInlineForeignKeys; buildQueryConfig = { escapeName: (name) => `"${name}"`, escapeParam: (_num) => "?", escapeString: (str) => `'${str.replace(/'/g, "''")}'`, casing: new import_casing4.CasingCache() }; isIncremental(column) { return (0, import_drizzle_orm3.is)(column, import_sqlite_core2.SQLiteBaseInteger) && column.autoIncrement; } mapDefaultValue(value) { let str = ""; if (typeof value === "string") { str = `'${value}'`; } else if (typeof value === "boolean") { str = `${value ? 1 : 0}`; } else if (typeof value === "number") { str = `${value}`; } else if (value === null) { str = "null"; } else if ((0, import_drizzle_orm3.is)(value, import_drizzle_orm3.SQL)) { str = `\`${value.toQuery(this.buildQueryConfig).sql}\``; } else { str = `\`${JSON.stringify(value)}\``; } return str; } }; function sqliteGenerate(options) { options.relational ||= false; const dbml = new SQLiteGenerator(options.schema, options.relational).generate(); options.out && writeDBMLFile(dbml, options.out); return dbml; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { mysqlGenerate, pgGenerate, sqliteGenerate });