@liam-hq/cli
Version:
Command-line tool designed to generate a web application that displays ER diagrams. See https://liambx.com/docs/cli
1,334 lines (1,323 loc) • 3.54 MB
JavaScript
#!/usr/bin/env node
import { createRequire } from 'node:module';
import { Command } from 'commander';
import { parseSync } from '@swc/core';
import pkg from '@prisma/internals';
import { readFile } from 'node:fs/promises';
import { fileURLToPath, URL as URL$1 } from 'node:url';
import tty from 'node:tty';
import fs, { existsSync, mkdirSync, cpSync } from 'node:fs';
import path, { resolve, dirname, relative } from 'node:path';
import { glob } from 'glob';
import { exit } from 'node:process';
import inquirer from 'inquirer';
import { Transform, render, Text } from 'ink';
import require$$0 from 'os';
import require$$1 from 'tty';
/**
* Type definitions for Drizzle ORM MySQL schema parsing
*/
/**
* Type guard to check if a value is an object
*/
const isObject$1 = (value) => {
return typeof value === 'object' && value !== null;
};
/**
* Safe property checker without type casting
*/
const hasProperty$1 = (obj, key) => {
return typeof obj === 'object' && obj !== null && key in obj;
};
/**
* Safe property getter without type casting
*/
const getPropertyValue$1 = (obj, key) => {
if (hasProperty$1(obj, key)) {
return obj[key];
}
return undefined;
};
/**
* Type guard for CompositePrimaryKeyDefinition
*/
const isCompositePrimaryKey$1 = (value) => {
return (isObject$1(value) &&
getPropertyValue$1(value, 'type') === 'primaryKey' &&
hasProperty$1(value, 'columns') &&
Array.isArray(getPropertyValue$1(value, 'columns')));
};
/**
* Type guard for DrizzleIndexDefinition
*/
const isDrizzleIndex$1 = (value) => {
return (isObject$1(value) &&
hasProperty$1(value, 'name') &&
hasProperty$1(value, 'columns') &&
hasProperty$1(value, 'unique'));
};
/**
* AST manipulation utilities for Drizzle ORM MySQL schema parsing
*/
/**
* Type guard for SWC Argument wrapper
*/
const isArgumentWrapper$1 = (arg) => {
return isObject$1(arg) && hasProperty$1(arg, 'expression');
};
/**
* Extract expression from SWC Argument wrapper
*/
const getArgumentExpression$1 = (arg) => {
if (isArgumentWrapper$1(arg)) {
return arg.expression;
}
return null;
};
/**
* Type guard for string literal expressions
*/
const isStringLiteral$1 = (expr) => {
return (isObject$1(expr) &&
getPropertyValue$1(expr, 'type') === 'StringLiteral' &&
hasProperty$1(expr, 'value') &&
typeof getPropertyValue$1(expr, 'value') === 'string');
};
/**
* Type guard for object expressions
*/
const isObjectExpression$1 = (expr) => {
return isObject$1(expr) && getPropertyValue$1(expr, 'type') === 'ObjectExpression';
};
/**
* Type guard for array expressions
*/
const isArrayExpression$1 = (expr) => {
return (isObject$1(expr) &&
getPropertyValue$1(expr, 'type') === 'ArrayExpression' &&
hasProperty$1(expr, 'elements') &&
Array.isArray(getPropertyValue$1(expr, 'elements')));
};
/**
* Type guard for identifier nodes
*/
const isIdentifier$1 = (node) => {
return (isObject$1(node) &&
getPropertyValue$1(node, 'type') === 'Identifier' &&
hasProperty$1(node, 'value') &&
typeof getPropertyValue$1(node, 'value') === 'string');
};
/**
* Check if a node is an identifier with a specific name
*/
const isIdentifierWithName$1 = (node, name) => {
return isIdentifier$1(node) && node.value === name;
};
/**
* Type guard for member expressions
*/
const isMemberExpression$1 = (node) => {
return (isObject$1(node) &&
getPropertyValue$1(node, 'type') === 'MemberExpression' &&
hasProperty$1(node, 'object') &&
hasProperty$1(node, 'property') &&
typeof getPropertyValue$1(node, 'object') === 'object' &&
typeof getPropertyValue$1(node, 'property') === 'object');
};
/**
* Check if a call expression is a mysqlTable call
*/
const isMysqlTableCall = (callExpr) => {
return isIdentifierWithName$1(callExpr.callee, 'mysqlTable');
};
/**
* Check if a call expression is a mysqlSchema call
*/
const isMysqlSchemaCall = (callExpr) => {
return isIdentifierWithName$1(callExpr.callee, 'mysqlSchema');
};
/**
* Check if a call expression is a schema.table() call
*/
const isSchemaTableCall$1 = (callExpr) => {
return (isMemberExpression$1(callExpr.callee) &&
isIdentifier$1(callExpr.callee.property) &&
callExpr.callee.property.value === 'table');
};
/**
* Extract string value from a string literal
*/
const getStringValue$1 = (node) => {
if (node.type === 'StringLiteral') {
return node.value;
}
return null;
};
/**
* Extract identifier name
*/
const getIdentifierName$1 = (node) => {
if (isIdentifier$1(node)) {
return node.value;
}
return null;
};
/**
* Parse method call chain from a call expression
*/
const parseMethodChain$1 = (expr) => {
const methods = [];
let current = expr;
while (current.type === 'CallExpression') {
if (current.callee.type === 'MemberExpression' &&
current.callee.property.type === 'Identifier') {
methods.unshift({
name: current.callee.property.value,
args: current.arguments,
});
current = current.callee.object;
}
else {
break;
}
}
return methods;
};
/**
* Convert Drizzle column types to MySQL column types
* ref: https://orm.drizzle.team/docs/column-types/mysql
*/
const convertDrizzleTypeToMysqlType = (drizzleType, options) => {
switch (drizzleType) {
// String types with length options
case 'varchar':
if (options?.['length']) {
return `varchar(${options['length']})`;
}
return 'varchar';
case 'char':
if (options?.['length']) {
return `char(${options['length']})`;
}
return 'char';
// Numeric types with precision/scale
case 'decimal':
case 'numeric':
if (options?.['precision'] && options?.['scale']) {
return `decimal(${options['precision']},${options['scale']})`;
}
if (options?.['precision']) {
return `decimal(${options['precision']})`;
}
return 'decimal';
// Enum type - handled as is
case 'mysqlEnum':
return 'enum';
// Serial type (PostgreSQL compatibility in MySQL context)
case 'serial':
return 'int';
// Default case: return type name as-is (works for most standard MySQL types)
default:
return drizzleType;
}
};
/**
* Convert default values from Drizzle to MySQL format
*/
const convertDefaultValue$1 = (value, _drizzleType) => {
if (value === undefined || value === null) {
return null;
}
// Handle function calls like defaultNow(), autoincrement()
if (typeof value === 'string') {
if (value === 'defaultNow' || value === 'now()') {
return 'now()';
}
if (value === 'autoincrement' || value === 'autoincrement()') {
return 'autoincrement()';
}
if (value === 'defaultRandom') {
// Note: MySQL's UUID() generates UUID v1 (time-based), not truly random like PostgreSQL's gen_random_uuid()
return 'UUID()';
}
}
// Handle primitive values
if (typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean') {
return value;
}
return null;
};
/**
* Convert constraint reference options from Drizzle to MySQL format
*/
const convertReferenceOption$1 = (option) => {
switch (option.toLowerCase()) {
case 'cascade':
return 'CASCADE';
case 'restrict':
return 'RESTRICT';
case 'setnull':
case 'set null':
return 'SET_NULL';
case 'setdefault':
case 'set default':
return 'SET_DEFAULT';
default:
return 'NO_ACTION';
}
};
/**
* Data conversion logic for Drizzle ORM MySQL schema parsing
*/
/**
* Convert Drizzle table definition to internal Table format
*/
const convertToTable$1 = (tableDef, enums = {}, variableToTableMapping = {}) => {
const columns = {};
const constraints = {};
const indexes = {};
// Convert columns
for (const [columnName, columnDef] of Object.entries(tableDef.columns)) {
// Check if this is an enum type and get the actual enum name
let columnType = columnDef.type;
// Check if this is an enum variable name (like userRoleEnum -> user_role)
for (const [enumVarName, enumDef] of Object.entries(enums)) {
if (columnDef.type === enumVarName) {
columnType = enumDef.name;
break;
}
}
// If not found, it might be a call to an enum function (like roleEnum('role'))
// In this case, the type is already the enum name from the first argument
if (columnType === columnDef.type) {
// Check if any enum definition matches this type name
for (const enumDef of Object.values(enums)) {
if (enumDef.name === columnDef.type) {
columnType = enumDef.name;
break;
}
}
}
const column = {
name: columnDef.name,
type: convertDrizzleTypeToMysqlType(columnType, columnDef.typeOptions),
default: convertDefaultValue$1(columnDef.default ||
(columnType === 'int' && columnDef.primaryKey
? 'autoincrement()'
: undefined)),
notNull: columnDef.notNull,
comment: columnDef.comment || null,
check: null,
};
columns[columnName] = column;
// Add primary key constraint
if (columnDef.primaryKey) {
const constraintName = `PRIMARY_${columnDef.name}`;
constraints[constraintName] = {
type: 'PRIMARY KEY',
name: constraintName,
columnNames: [columnDef.name],
};
// Add primary key index
const indexName = `${tableDef.name}_pkey`;
indexes[indexName] = {
name: indexName,
columns: [columnDef.name],
unique: true,
type: '',
};
}
// Add unique constraint (inline unique does not create index, only constraint)
if (columnDef.unique && !columnDef.primaryKey) {
const constraintName = `UNIQUE_${columnDef.name}`;
constraints[constraintName] = {
type: 'UNIQUE',
name: constraintName,
columnNames: [columnDef.name],
};
}
// Add foreign key constraint
if (columnDef.references) {
// Resolve variable name to actual table name
const targetTableName = variableToTableMapping[columnDef.references.table] ||
columnDef.references.table;
const constraintName = `${tableDef.name}_${columnDef.name}_${columnDef.references.table}_${columnDef.references.column}_fk`;
const constraint = {
type: 'FOREIGN KEY',
name: constraintName,
columnNames: [columnDef.name], // Use actual column name, not JS property name
targetTableName: targetTableName,
targetColumnNames: [columnDef.references.column],
updateConstraint: columnDef.references.onUpdate
? convertReferenceOption$1(columnDef.references.onUpdate)
: 'NO_ACTION',
deleteConstraint: columnDef.references.onDelete
? convertReferenceOption$1(columnDef.references.onDelete)
: 'NO_ACTION',
};
constraints[constraintName] = constraint;
}
}
// Handle composite primary key
if (tableDef.compositePrimaryKey) {
// Map JS property names to actual column names
const actualColumnNames = tableDef.compositePrimaryKey.columns
.map((jsPropertyName) => {
const columnDef = tableDef.columns[jsPropertyName];
return columnDef ? columnDef.name : jsPropertyName;
})
.filter((name) => name && name.length > 0);
// Create composite primary key constraint
const constraintName = `${tableDef.name}_pkey`;
constraints[constraintName] = {
type: 'PRIMARY KEY',
name: constraintName,
columnNames: actualColumnNames,
};
// Add composite primary key index
indexes[constraintName] = {
name: constraintName,
columns: actualColumnNames,
unique: true,
type: '',
};
}
// Convert indexes
for (const [_, indexDef] of Object.entries(tableDef.indexes)) {
// Map JS property names to actual column names
const actualColumnNames = indexDef.columns.map((jsPropertyName) => {
const columnDef = tableDef.columns[jsPropertyName];
return columnDef ? columnDef.name : jsPropertyName;
});
// Use the actual index name from the definition
const actualIndexName = indexDef.name;
indexes[actualIndexName] = {
name: actualIndexName,
columns: actualColumnNames,
unique: indexDef.unique,
type: indexDef.type || '',
};
}
// Convert constraints from Drizzle table definition
if (tableDef.constraints) {
for (const [constraintName, constraintDef] of Object.entries(tableDef.constraints)) {
constraints[constraintName] = constraintDef;
}
}
return {
name: tableDef.name,
columns,
constraints,
indexes,
comment: tableDef.comment || null,
};
};
/**
* Fix foreign key constraint targetColumnName from JS property names to actual DB column names
*/
const fixForeignKeyTargetColumnNames$1 = (tables, drizzleTables) => {
for (const table of Object.values(tables)) {
for (const constraint of Object.values(table.constraints)) {
if (constraint.type === 'FOREIGN KEY') {
// Check in drizzleTables for column mapping
const drizzleTargetTable = drizzleTables[constraint.targetTableName];
if (drizzleTargetTable) {
// Map each target column from JS property name to actual DB column name
constraint.targetColumnNames = constraint.targetColumnNames.map((jsPropertyName) => {
const targetColumnDef = drizzleTargetTable.columns[jsPropertyName];
return targetColumnDef ? targetColumnDef.name : jsPropertyName;
});
}
}
}
}
};
/**
* Convert parsed Drizzle tables to internal format with error handling
*/
const convertDrizzleTablesToInternal$1 = (drizzleTables, enums, variableToTableMapping = {}) => {
const tables = {};
const errors = [];
// Convert Drizzle tables to internal format
for (const tableDef of Object.values(drizzleTables)) {
try {
// Use original table name for the key, not schema-prefixed name
// This maintains compatibility while handling schema namespaces internally
tables[tableDef.name] = convertToTable$1(tableDef, enums, variableToTableMapping);
}
catch (error) {
errors.push(new Error(`Error parsing table ${tableDef.name}: ${error instanceof Error ? error.message : String(error)}`));
}
}
// Fix foreign key constraint targetColumnName from JS property names to actual DB column names
fixForeignKeyTargetColumnNames$1(tables, drizzleTables);
return { tables, errors };
};
/**
* Enum definition parsing for Drizzle ORM MySQL schema parsing
*/
/**
* Parse mysqlEnum call expression
*/
const parseMysqlEnumCall = (callExpr) => {
if (callExpr.arguments.length < 2)
return null;
const enumNameArg = callExpr.arguments[0];
const valuesArg = callExpr.arguments[1];
if (!enumNameArg || !valuesArg)
return null;
// Extract expression from SWC argument structure
const enumNameExpr = getArgumentExpression$1(enumNameArg);
const valuesExpr = getArgumentExpression$1(valuesArg);
const enumName = enumNameExpr ? getStringValue$1(enumNameExpr) : null;
if (!enumName || !valuesExpr || !isArrayExpression$1(valuesExpr))
return null;
const values = [];
for (const element of valuesExpr.elements) {
if (isStringLiteral$1(element)) {
values.push(element.value);
}
}
return { name: enumName, values };
};
/**
* Schema parsing for Drizzle ORM MySQL schema parsing
*/
/**
* Parse mysqlSchema call expression
*/
const parseMysqlSchemaCall = (callExpr) => {
if (callExpr.arguments.length < 1)
return null;
const schemaNameArg = callExpr.arguments[0];
if (!schemaNameArg)
return null;
// Extract expression from SWC argument structure
const schemaNameExpr = getArgumentExpression$1(schemaNameArg);
const schemaName = schemaNameExpr ? getStringValue$1(schemaNameExpr) : null;
if (!schemaName)
return null;
return {
name: schemaName,
};
};
/**
* Expression parsing utilities for Drizzle ORM MySQL schema parsing
*/
/**
* Parse special function/identifier values
*/
const parseSpecialValue = (value) => {
switch (value) {
case 'defaultNow':
return 'now()';
case 'defaultRandom':
return 'defaultRandom';
default:
return value;
}
};
/**
* Parse default value from expression
*/
const parseDefaultValue$1 = (expr) => {
switch (expr.type) {
case 'StringLiteral':
return expr.value;
case 'NumericLiteral':
return expr.value;
case 'BooleanLiteral':
return expr.value;
case 'NullLiteral':
return null;
case 'Identifier':
return parseSpecialValue(expr.value);
case 'CallExpression':
// Handle function calls like defaultNow()
if (expr.callee.type === 'Identifier') {
return parseSpecialValue(expr.callee.value);
}
return undefined;
default:
return undefined;
}
};
/**
* Parse object expression to plain object
*/
const parseObjectExpression$1 = (obj) => {
const result = {};
for (const prop of obj.properties) {
if (prop.type === 'KeyValueProperty') {
const key = prop.key.type === 'Identifier'
? getIdentifierName$1(prop.key)
: prop.key.type === 'StringLiteral'
? getStringValue$1(prop.key)
: null;
if (key) {
result[key] = parsePropertyValue$1(prop.value);
}
}
}
return result;
};
/**
* Type guard for expression-like objects
*/
const isExpressionLike$1 = (value) => {
return (isObject$1(value) &&
hasProperty$1(value, 'type') &&
typeof getPropertyValue$1(value, 'type') === 'string');
};
/**
* Safe parser for unknown values as expressions
*/
const parseUnknownValue$1 = (value) => {
if (isExpressionLike$1(value)) {
return parseDefaultValue$1(value);
}
return value;
};
/**
* Parse property value (including arrays)
*/
const parsePropertyValue$1 = (expr) => {
if (isArrayExpression$1(expr)) {
const result = [];
for (const element of expr.elements) {
const elementExpr = getArgumentExpression$1(element);
if (elementExpr &&
elementExpr.type === 'MemberExpression' &&
elementExpr.object.type === 'Identifier' &&
elementExpr.property.type === 'Identifier') {
// For table.columnName references, use the property name
result.push(elementExpr.property.value);
}
else if (isMemberExpression$1(element) &&
isIdentifier$1(element.object) &&
isIdentifier$1(element.property)) {
// Direct MemberExpression (not wrapped in { expression })
result.push(element.property.value);
}
else {
const parsed = elementExpr
? parseDefaultValue$1(elementExpr)
: parseUnknownValue$1(element);
result.push(parsed);
}
}
return result;
}
return parseUnknownValue$1(expr);
};
/**
* Column definition parsing for Drizzle ORM MySQL schema parsing
*/
/**
* Parse runtime function from $defaultFn or $onUpdate arguments
*/
const parseRuntimeFunction = (args) => {
if (args.length === 0) {
return 'custom_function()';
}
const argExpr = getArgumentExpression$1(args[0]);
if (!argExpr || argExpr.type !== 'ArrowFunctionExpression') {
return 'custom_function()';
}
const body = argExpr.body;
if (body.type === 'CallExpression' && body.callee.type === 'Identifier') {
return `${body.callee.value}()`;
}
if (body.type === 'NewExpression' && body.callee.type === 'Identifier') {
return `new ${body.callee.value}()`;
}
return 'custom_function()';
};
/**
* Parse column definition from object property
*/
const parseColumnFromProperty$1 = (prop) => {
if (prop.type !== 'KeyValueProperty')
return null;
const columnName = prop.key.type === 'Identifier' ? getIdentifierName$1(prop.key) : null;
if (!columnName)
return null;
if (prop.value.type !== 'CallExpression')
return null;
// Parse the method chain to find the base type
const methods = parseMethodChain$1(prop.value);
// Find the base type from the root of the chain
let baseType = null;
let current = prop.value;
// Traverse to the bottom of the method chain to find the base type call
while (current.type === 'CallExpression' &&
current.callee.type === 'MemberExpression') {
current = current.callee.object;
}
if (current.type === 'CallExpression' &&
current.callee.type === 'Identifier') {
baseType = current.callee.value;
}
if (!baseType)
return null;
// Extract the actual column name from the first argument of the base type call
let actualColumnName = columnName; // Default to JS property name
if (current.type === 'CallExpression' && current.arguments.length > 0) {
const firstArg = current.arguments[0];
const firstArgExpr = getArgumentExpression$1(firstArg);
if (firstArgExpr && isStringLiteral$1(firstArgExpr)) {
actualColumnName = firstArgExpr.value;
}
}
const column = {
name: actualColumnName,
type: baseType,
notNull: false,
primaryKey: false,
unique: false,
};
// Parse type options from second argument (like { length: 255 })
if (current.type === 'CallExpression' && current.arguments.length > 1) {
const secondArg = current.arguments[1];
const secondArgExpr = getArgumentExpression$1(secondArg);
if (secondArgExpr && isObjectExpression$1(secondArgExpr)) {
column.typeOptions = parseObjectExpression$1(secondArgExpr);
}
}
// Parse method calls in the chain (already parsed above)
for (const method of methods) {
switch (method.name) {
case 'primaryKey':
column.primaryKey = true;
column.notNull = true;
break;
case 'autoincrement':
// MySQL specific: int().autoincrement()
break;
case 'notNull':
column.notNull = true;
break;
case 'unique':
column.unique = true;
break;
case 'default':
if (method.args.length > 0) {
const argExpr = getArgumentExpression$1(method.args[0]);
if (argExpr) {
column.default = parseDefaultValue$1(argExpr);
}
}
break;
case 'defaultNow':
column.default = 'now()';
break;
case '$defaultFn':
column.default = parseRuntimeFunction(method.args);
break;
case '$onUpdate':
column.onUpdate = parseRuntimeFunction(method.args);
break;
case 'onUpdateNow':
// MySQL specific: ON UPDATE NOW() - ignore for now
break;
case '$type':
// Type assertion - ignore for parsing purposes
break;
case 'references':
if (method.args.length > 0) {
const argExpr = getArgumentExpression$1(method.args[0]);
// Parse references: () => table.column
if (argExpr && argExpr.type === 'ArrowFunctionExpression') {
const body = argExpr.body;
if (body.type === 'MemberExpression' &&
body.object.type === 'Identifier' &&
body.property.type === 'Identifier') {
const referencesOptions = {
table: body.object.value,
column: body.property.value,
};
// Parse the second argument for onDelete/onUpdate options
if (method.args.length > 1) {
const optionsExpr = getArgumentExpression$1(method.args[1]);
if (optionsExpr && isObjectExpression$1(optionsExpr)) {
const options = parseObjectExpression$1(optionsExpr);
if (typeof options['onDelete'] === 'string') {
referencesOptions.onDelete = options['onDelete'];
}
if (typeof options['onUpdate'] === 'string') {
referencesOptions.onUpdate = options['onUpdate'];
}
}
}
column.references = referencesOptions;
}
}
}
break;
case '$comment':
if (method.args.length > 0) {
const argExpr = getArgumentExpression$1(method.args[0]);
const commentValue = argExpr ? getStringValue$1(argExpr) : null;
if (commentValue) {
column.comment = commentValue;
}
}
break;
}
}
// Handle autoincrement for primary key columns
const hasAutoincrementMethod = methods.some((m) => m.name === 'autoincrement');
if (column.primaryKey &&
((baseType === 'int' && hasAutoincrementMethod) || baseType === 'serial')) {
column.default = 'autoincrement()';
}
return column;
};
/**
* Table structure parsing for Drizzle ORM MySQL schema parsing
*/
/**
* Parse columns from object expression
*/
const parseTableColumns = (columnsExpr) => {
const columns = {};
for (const prop of columnsExpr.properties) {
if (prop.type === 'KeyValueProperty') {
const column = parseColumnFromProperty$1(prop);
if (column) {
// Use the JS property name as the key
const jsPropertyName = prop.key.type === 'Identifier' ? getIdentifierName$1(prop.key) : null;
if (jsPropertyName) {
columns[jsPropertyName] = column;
}
}
}
}
return columns;
};
/**
* Parse indexes, constraints, and composite primary keys from third argument
*/
const parseTableExtensions = (thirdArgExpr, tableColumns) => {
const result = {
indexes: {},
};
if (thirdArgExpr.type === 'ArrowFunctionExpression') {
// Parse arrow function like (table) => ({ nameIdx: index(...), pk: primaryKey(...) })
let returnExpr = thirdArgExpr.body;
// Handle parenthesized expressions like (table) => ({ ... })
if (returnExpr.type === 'ParenthesisExpression') {
returnExpr = returnExpr.expression;
}
if (returnExpr.type === 'ObjectExpression') {
for (const prop of returnExpr.properties) {
if (prop.type === 'KeyValueProperty') {
const indexName = prop.key.type === 'Identifier' ? getIdentifierName$1(prop.key) : null;
if (indexName && prop.value.type === 'CallExpression') {
const indexDef = parseIndexDefinition$1(prop.value, indexName);
if (indexDef) {
if (isCompositePrimaryKey$1(indexDef)) {
result.compositePrimaryKey = indexDef;
}
else if (isDrizzleIndex$1(indexDef)) {
result.indexes[indexName] = indexDef;
}
}
// Handle check constraints
const checkConstraint = parseCheckConstraint(prop.value, indexName);
if (checkConstraint) {
result.constraints = result.constraints || {};
result.constraints[checkConstraint.name] = {
type: 'CHECK',
name: checkConstraint.name,
detail: checkConstraint.condition,
};
}
// Handle unique constraints
const uniqueConstraint = parseUniqueConstraint(prop.value, indexName, tableColumns);
if (uniqueConstraint) {
result.constraints = result.constraints || {};
result.constraints[uniqueConstraint.name] = {
type: 'UNIQUE',
name: uniqueConstraint.name,
columnNames: uniqueConstraint.columnNames,
};
}
}
}
}
}
}
return result;
};
/**
* Parse mysqlTable call with comment method chain
*/
const parseMysqlTableWithComment = (commentCallExpr) => {
// Extract the comment from the call arguments
let comment = null;
if (commentCallExpr.arguments.length > 0) {
const commentArg = commentCallExpr.arguments[0];
const commentExpr = getArgumentExpression$1(commentArg);
if (commentExpr && isStringLiteral$1(commentExpr)) {
comment = commentExpr.value;
}
}
// Get the mysqlTable call from the object of the member expression
if (commentCallExpr.callee.type === 'MemberExpression') {
const mysqlTableCall = commentCallExpr.callee.object;
if (mysqlTableCall.type === 'CallExpression' &&
isMysqlTableCall(mysqlTableCall)) {
const table = parseMysqlTableCall(mysqlTableCall);
if (table && comment) {
table.comment = comment;
}
return table;
}
}
return null;
};
/**
* Parse mysqlTable call expression
*/
const parseMysqlTableCall = (callExpr) => {
if (callExpr.arguments.length < 2)
return null;
const tableNameArg = callExpr.arguments[0];
const columnsArg = callExpr.arguments[1];
if (!tableNameArg || !columnsArg)
return null;
// Extract expression from SWC argument structure
const tableNameExpr = getArgumentExpression$1(tableNameArg);
const columnsExpr = getArgumentExpression$1(columnsArg);
const tableName = tableNameExpr ? getStringValue$1(tableNameExpr) : null;
if (!tableName || !columnsExpr || !isObjectExpression$1(columnsExpr))
return null;
const table = {
name: tableName,
columns: {},
indexes: {},
};
// Parse columns from the object expression
table.columns = parseTableColumns(columnsExpr);
// Parse indexes and composite primary key from third argument if present
if (callExpr.arguments.length > 2) {
const thirdArg = callExpr.arguments[2];
const thirdArgExpr = getArgumentExpression$1(thirdArg);
if (thirdArgExpr) {
const extensions = parseTableExtensions(thirdArgExpr, table.columns);
table.indexes = extensions.indexes;
if (extensions.constraints) {
table.constraints = extensions.constraints;
}
if (extensions.compositePrimaryKey) {
table.compositePrimaryKey = extensions.compositePrimaryKey;
}
}
}
return table;
};
/**
* Parse schema.table() call expression
*/
const parseSchemaTableCall$1 = (callExpr) => {
if (!isSchemaTableCall$1(callExpr) || callExpr.arguments.length < 2)
return null;
// Extract expression from SWC argument structure
const tableNameExpr = getArgumentExpression$1(callExpr.arguments[0]);
const columnsExpr = getArgumentExpression$1(callExpr.arguments[1]);
const tableName = tableNameExpr ? getStringValue$1(tableNameExpr) : null;
if (!tableName || !columnsExpr || !isObjectExpression$1(columnsExpr))
return null;
// Extract schema name from the member expression (e.g., authSchema.table -> authSchema)
let schemaName = '';
if (callExpr.callee.type === 'MemberExpression' &&
callExpr.callee.object.type === 'Identifier') {
schemaName = callExpr.callee.object.value;
}
const table = {
name: tableName, // Keep the original table name for DB operations
columns: {},
indexes: {},
schemaName, // Add schema information for namespace handling
};
// Note: We now handle schema namespace by storing the schema name
// and using the original table name for database operations
// Parse columns from the object expression
table.columns = parseTableColumns(columnsExpr);
// Parse indexes and composite primary key from third argument if present
if (callExpr.arguments.length > 2) {
const thirdArg = callExpr.arguments[2];
const thirdArgExpr = getArgumentExpression$1(thirdArg);
if (thirdArgExpr) {
const extensions = parseTableExtensions(thirdArgExpr, table.columns);
table.indexes = extensions.indexes;
if (extensions.constraints) {
table.constraints = extensions.constraints;
}
if (extensions.compositePrimaryKey) {
table.compositePrimaryKey = extensions.compositePrimaryKey;
}
}
}
return table;
};
/**
* Parse index or primary key definition
*/
const parseIndexDefinition$1 = (callExpr, name) => {
// Handle primaryKey({ columns: [...] })
if (callExpr.callee.type === 'Identifier' &&
callExpr.callee.value === 'primaryKey') {
if (callExpr.arguments.length > 0) {
const configArg = callExpr.arguments[0];
const configExpr = getArgumentExpression$1(configArg);
if (configExpr && isObjectExpression$1(configExpr)) {
const config = parseObjectExpression$1(configExpr);
if (config['columns'] && Array.isArray(config['columns'])) {
const columns = config['columns'].filter((col) => typeof col === 'string');
return {
type: 'primaryKey',
columns,
};
}
}
}
return null;
}
// Handle index('name').on(...) or uniqueIndex('name').on(...) with optional .using(...)
let isUnique = false;
let indexName = name;
let indexType = ''; // Index type (btree, hash, etc.)
let currentExpr = callExpr;
// Traverse the method chain to find index(), on(), and using() calls
const methodCalls = [];
while (currentExpr.type === 'CallExpression' &&
currentExpr.callee.type === 'MemberExpression' &&
currentExpr.callee.property.type === 'Identifier') {
const methodName = currentExpr.callee.property.value;
methodCalls.unshift({ method: methodName, expr: currentExpr });
currentExpr = currentExpr.callee.object;
}
// The base should be index() or uniqueIndex()
if (currentExpr.type === 'CallExpression' &&
currentExpr.callee.type === 'Identifier') {
const baseMethod = currentExpr.callee.value;
if (baseMethod === 'index' || baseMethod === 'uniqueIndex') {
isUnique = baseMethod === 'uniqueIndex';
// Get the index name from the first argument
if (currentExpr.arguments.length > 0) {
const nameArg = currentExpr.arguments[0];
const nameExpr = getArgumentExpression$1(nameArg);
if (nameExpr && isStringLiteral$1(nameExpr)) {
indexName = nameExpr.value;
}
}
}
}
// Parse method chain to extract columns and index type
const columns = [];
for (const { method, expr } of methodCalls) {
if (method === 'on') {
// Parse column references from .on(...) arguments
for (const arg of expr.arguments) {
const argExpr = getArgumentExpression$1(arg);
if (argExpr &&
argExpr.type === 'MemberExpression' &&
argExpr.object.type === 'Identifier' &&
argExpr.property.type === 'Identifier') {
columns.push(argExpr.property.value);
}
}
}
else if (method === 'using') {
// Parse index type from .using('type') - only the first argument specifies the type
if (expr.arguments.length > 0) {
const typeArg = expr.arguments[0];
const typeExpr = getArgumentExpression$1(typeArg);
if (typeExpr && isStringLiteral$1(typeExpr)) {
indexType = typeExpr.value;
}
}
}
}
if (columns.length > 0) {
return {
name: indexName,
columns,
unique: isUnique,
type: indexType,
};
}
return null;
};
/**
* Parse check constraint definition
*/
const parseCheckConstraint = (callExpr, name) => {
// Handle check('constraint_name', sql`condition`)
if (callExpr.callee.type === 'Identifier' &&
callExpr.callee.value === 'check') {
// Extract the constraint name from the first argument
let constraintName = name;
if (callExpr.arguments.length > 0) {
const nameArg = callExpr.arguments[0];
const nameExpr = getArgumentExpression$1(nameArg);
if (nameExpr && isStringLiteral$1(nameExpr)) {
constraintName = nameExpr.value;
}
}
// Extract the condition from the second argument (sql template literal)
let condition = 'true'; // Default condition
if (callExpr.arguments.length > 1) {
const conditionArg = callExpr.arguments[1];
const conditionExpr = getArgumentExpression$1(conditionArg);
if (conditionExpr) {
// Handle sql`condition` template literal
if (conditionExpr.type === 'TaggedTemplateExpression' &&
conditionExpr.tag.type === 'Identifier' &&
conditionExpr.tag.value === 'sql') {
// Extract the condition from template literal
if (conditionExpr.template.type === 'TemplateLiteral' &&
conditionExpr.template.quasis.length > 0) {
const firstQuasi = conditionExpr.template.quasis[0];
if (firstQuasi && firstQuasi.type === 'TemplateElement') {
// SWC TemplateElement has different structure than TypeScript's
// We need to access the raw string from the SWC AST structure
// Use property access with type checking to avoid type assertions
const hasRaw = 'raw' in firstQuasi && typeof firstQuasi.raw === 'string';
const hasCooked = 'cooked' in firstQuasi && typeof firstQuasi.cooked === 'string';
if (hasRaw) {
condition = firstQuasi.raw || '';
}
else if (hasCooked) {
condition = firstQuasi.cooked || '';
}
}
}
}
// Handle direct function call like sql('condition')
else if (conditionExpr.type === 'CallExpression' &&
conditionExpr.callee.type === 'Identifier' &&
conditionExpr.callee.value === 'sql' &&
conditionExpr.arguments.length > 0) {
const sqlArg = getArgumentExpression$1(conditionExpr.arguments[0]);
if (sqlArg && isStringLiteral$1(sqlArg)) {
condition = sqlArg.value;
}
}
}
}
return {
type: 'check',
name: constraintName,
condition,
};
}
return null;
};
/**
* Parse unique constraint definition
*/
const parseUniqueConstraint = (callExpr, name, tableColumns) => {
// Handle unique('constraint_name').on(...) method chain
let constraintName = name;
let currentExpr = callExpr;
const columns = [];
// First check if we have a method chain ending with .on(...)
const methodCalls = [];
// Traverse method chain to collect all calls
while (currentExpr.type === 'CallExpression' &&
currentExpr.callee.type === 'MemberExpression' &&
currentExpr.callee.property.type === 'Identifier') {
const methodName = currentExpr.callee.property.value;
methodCalls.unshift({ method: methodName, expr: currentExpr });
currentExpr = currentExpr.callee.object;
}
// The base should be unique()
if (currentExpr.type === 'CallExpression' &&
currentExpr.callee.type === 'Identifier' &&
currentExpr.callee.value === 'unique') {
// Get the constraint name from the first argument
if (currentExpr.arguments.length > 0) {
const nameArg = currentExpr.arguments[0];
const nameExpr = getArgumentExpression$1(nameArg);
if (nameExpr && isStringLiteral$1(nameExpr)) {
constraintName = nameExpr.value;
}
}
// Find the .on() method call and parse columns
for (const { method, expr } of methodCalls) {
if (method === 'on') {
// Parse column references from .on(...) arguments
for (const arg of expr.arguments) {
const argExpr = getArgumentExpression$1(arg);
if (argExpr &&
argExpr.type === 'MemberExpression' &&
argExpr.object.type === 'Identifier' &&
argExpr.property.type === 'Identifier') {
// Get the JavaScript property name
const jsPropertyName = argExpr.property.value;
// Find the actual database column name from the table columns
const column = tableColumns[jsPropertyName];
if (column) {
columns.push(column.name); // Use database column name
}
else {
columns.push(jsPropertyName); // Fallback to JS property name
}
}
}
break;
}
}
if (columns.length > 0) {
return {
type: 'UNIQUE',
name: constraintName,
columnNames: columns,
};
}
}
return null;
};
/**
* Main orchestrator for Drizzle ORM MySQL schema parsing
*/
/**
* Parse Drizzle TypeScript schema to extract table definitions using SWC AST
*/
const parseDrizzleSchema$1 = (sourceCode) => {
// Parse TypeScript code into AST
const ast = parseSync(sourceCode, {
syntax: 'typescript',
target: 'es2022',
});
const tables = {};
const enums = {};
const schemas = {};
const variableToTableMapping = {};
// Traverse the AST to find mysqlTable, mysqlSchema calls
visitModule$1(ast, tables, enums, schemas, variableToTableMapping);
return { tables, enums, schemas, variableToTableMapping };
};
/**
* Visit and traverse the module AST
*/
const visitModule$1 = (module, tables, enums, schemas, variableToTableMapping) => {
for (const item of module.body) {
if (item.type === 'VariableDeclaration') {
for (const declarator of item.declarations) {
visitVariableDeclarator$1(declarator, tables, enums, schemas, variableToTableMapping);
}
}
else if (item.type === 'ExportDeclaration' &&
item.declaration?.type === 'VariableDeclaration') {
for (const declarator of item.declaration.declarations) {
visitVariableDeclarator$1(declarator, tables, enums, schemas, variableToTableMapping);
}
}
}
};
/**
* Visit variable declarator to find mysqlTable, mysqlEnum, mysqlSchema, or relations calls
*/
const visitVariableDeclarator$1 = (declarator, tables, enums, schemas, variableToTableMapping) => {
if (!declarator.init || declarator.init.type !== 'CallExpression')
return;
const callExpr = declarator.init;
if (isMysqlTableCall(callExpr)) {
const table = parseMysqlTableCall(callExpr);
if (table && declarator.id.type === 'Identifier') {
tables[table.name] = table;
// Map variable name to table name
variableToTableMapping[declarator.id.value] = table.name;
}
}
else if (isSchemaTableCall$1(callExpr)) {
const table = parseSchemaTableCall$1(callExpr);
if (table && declarator.id.type === 'Identifier') {
tables[table.name] = table;
// Map variable name to table name
variableToTableMapping[declarator.id.value] = table.name;
}
}
else if (declarator.init.type === 'CallExpression' &&
declarator.init.callee.type === 'MemberExpression' &&
declarator.init.callee.property.type === 'Identifier' &&
declarator.init.callee.property.value === '$comment') {
// Handle table comments: mysqlTable(...).comment(...)
const table = parseMysqlTableWithComment(declarator.init);
if (table && declarator.id.type === 'Identifier') {
tables[table.name] = table;
// Map variable name to table name
variableToTableMapping[declarator.id.value] = table.name;
}
}
else if (callExpr.callee.type === 'Identifier' &&
callExpr.callee.value === 'mysqlEnum') {
const enumDef = parseMysqlEnumCall(callExpr);
if (enumDef && declarator.id.type === 'Identifier') {
enums[declarator.id.value] = enumDef;
}
}
else if (isMysqlSchemaCall(callExpr)) {
const schemaDef = parseMysqlSchemaCall(callExpr);
if (schemaDef && declarator.id.type === 'Identifier') {
schemas[declarator.id.value] = schemaDef;
}
}
};
/**
* Main processor function for Drizzle MySQL schemas
*/
const parseDrizzleSchemaString$1 = (schemaString) => {
try {
const { tables: drizzleTables, enums, variableToTableMapping, } = parseDrizzleSchema$1(schemaString);
const { tables, errors } = convertDrizzleTablesToInternal$1(drizzleTables, enums, variableToTableMapping);
return Promise.resolve({
value: { tables },
errors,
});
}
catch (error) {
return Promise.resolve({
value: { tables: {} },
errors: [
new Error(`Error parsing Drizzle MySQL schema: ${error instanceof Error ? error.message : String(error)}`),
],
});
}
};
const processor$6 = parseDrizzleSchemaString$1;
/**
* Type definitions for Drizzle ORM schema parsing
*/
/**
* Type guard to check if a value is an object
*/
const isObject = (value) => {
return typeof value === 'object' && value !== null;
};
/**
* Safe property checker without type casting
*/
const hasProperty = (obj, key) => {
return typeof obj === 'object' && obj !== null && key in obj;
};
/**
* Safe property getter without type casting
*/
const getPropertyValue = (obj, key) => {
if (hasProperty(obj, key)) {
return obj[key];
}
return undefined;
};
/**
* Type guard for CompositePrimaryKeyDefinition
*/
const isCompositePrimaryKey = (value) => {
return (isObject(val