@iarayan/ch-orm
Version:
A Developer-First ClickHouse ORM with Powerful CLI Tools
328 lines • 14.6 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const chalk_1 = __importDefault(require("chalk"));
const commander_1 = require("commander");
const dotenv = __importStar(require("dotenv"));
const figlet_1 = __importDefault(require("figlet"));
const fs = __importStar(require("fs"));
const ora_1 = __importDefault(require("ora"));
const path = __importStar(require("path"));
const Connection_1 = require("../connection/Connection");
const MigrationRecord_1 = require("../schema/models/MigrationRecord");
const commands_1 = require("./commands");
/**
* Load configuration from environment variables or .env file
*/
function loadConfig() {
// Load environment variables from .env file if it exists
const envPath = path.resolve(process.cwd(), ".env");
if (fs.existsSync(envPath)) {
dotenv.config({ path: envPath });
}
// Get configuration from environment variables
const config = {
host: process.env.CLICKHOUSE_HOST || "localhost",
port: parseInt(process.env.CLICKHOUSE_PORT || "8123", 10),
database: process.env.CLICKHOUSE_DATABASE || "default",
username: process.env.CLICKHOUSE_USERNAME || "default",
password: process.env.CLICKHOUSE_PASSWORD || "",
protocol: process.env.CLICKHOUSE_PROTOCOL || "http",
debug: process.env.CLICKHOUSE_DEBUG === "true",
migrationsPath: process.env.CLICKHOUSE_MIGRATIONS_PATH || "./migrations",
modelsPath: process.env.CLICKHOUSE_MODELS_PATH || "./models",
seedersPath: process.env.CLICKHOUSE_SEEDERS_PATH || "./seeders",
};
return config;
}
/**
* Create a ClickHouse connection from configuration
*/
function createConnection(config) {
return new Connection_1.Connection({
host: config.host,
port: config.port,
database: config.database,
username: config.username,
password: config.password,
debug: config.debug,
});
}
/**
* Main CLI program
*/
async function main() {
console.log(chalk_1.default.cyan(figlet_1.default.textSync("CH-ORM", { horizontalLayout: "full" })));
console.log(chalk_1.default.yellow("A Developer-First ClickHouse ORM\n"));
const program = new commander_1.Command();
const config = loadConfig();
program.version("1.0.0").description("CH-ORM CLI for database management");
// Migrations
const migrationsCommand = program
.command("migrations")
.description("Manage database migrations");
migrationsCommand
.command("create <name>")
.description("Create a new migration file")
.option("-p, --path <path>", "Path to migrations directory", config.migrationsPath)
.action(async (name, options) => {
const spinner = (0, ora_1.default)("Creating migration...").start();
try {
const makeMigration = new commands_1.MakeMigrationCommand(options.path);
const filePath = makeMigration.execute(name);
spinner.succeed(`Migration created: ${chalk_1.default.green(filePath)}`);
}
catch (error) {
spinner.fail(`Failed to create migration: ${chalk_1.default.red(error.message)}`);
process.exit(1);
}
});
migrationsCommand
.command("run")
.description("Run all pending migrations")
.option("-p, --path <path>", "Path to migrations directory", config.migrationsPath)
.action(async (options) => {
const spinner = (0, ora_1.default)("Running migrations...").start();
try {
const connection = createConnection(config);
const runner = new commands_1.MigrationRunnerCommand(connection, options.path);
const status = await runner.getMigrationStatus();
const pending = status.filter((s) => s.status === "Pending");
if (pending.length === 0) {
spinner.succeed("No pending migrations");
return;
}
spinner.info(`Found ${pending.length} pending migrations`);
await runner.run();
spinner.succeed(`Successfully ran ${pending.length} migrations`);
}
catch (error) {
spinner.fail(`Migration failed: ${chalk_1.default.red(error.message)}`);
process.exit(1);
}
});
migrationsCommand
.command("rollback")
.description("Rollback the last batch of migrations")
.option("-p, --path <path>", "Path to migrations directory", config.migrationsPath)
.action(async (options) => {
const spinner = (0, ora_1.default)("Rolling back migrations...").start();
try {
const connection = createConnection(config);
const runner = new commands_1.MigrationRunnerCommand(connection, options.path);
// Get the last batch number
const lastBatchResult = await MigrationRecord_1.MigrationRecord.query()
.select("batch")
.orderBy("batch", "DESC")
.first();
if (!lastBatchResult) {
spinner.succeed("No migrations to rollback");
return;
}
const lastBatch = lastBatchResult.batch;
// Get all migrations from the last batch
const migrationsToRollback = await MigrationRecord_1.MigrationRecord.query()
.where("batch", lastBatch)
.orderBy("created_at", "DESC")
.get();
if (migrationsToRollback.length === 0) {
spinner.succeed("No migrations to rollback");
return;
}
spinner.info(`Rolling back batch #${lastBatch} with ${migrationsToRollback.length} migrations:`);
migrationsToRollback.forEach((record) => {
console.log(` - ${chalk_1.default.yellow(record.name)}`);
});
spinner.start("Executing rollback...");
await runner.rollback();
// Verify batch was removed from migrations table
const verifyBatchRemoved = await MigrationRecord_1.MigrationRecord.query()
.where("batch", lastBatch)
.count();
if (verifyBatchRemoved > 0) {
spinner.warn(`Warning: ${verifyBatchRemoved} migration records still exist in the migrations table`);
}
spinner.succeed(`Successfully rolled back ${migrationsToRollback.length} migrations from batch #${lastBatch}`);
}
catch (error) {
spinner.fail(`Rollback failed: ${chalk_1.default.red(error.message)}`);
process.exit(1);
}
});
migrationsCommand
.command("status")
.description("Show migration status")
.option("-p, --path <path>", "Path to migrations directory", config.migrationsPath)
.action(async (options) => {
const spinner = (0, ora_1.default)("Checking migration status...").start();
try {
const connection = createConnection(config);
const runner = new commands_1.MigrationRunnerCommand(connection, options.path);
const status = await runner.getMigrationStatus();
spinner.stop();
if (status.length === 0) {
console.log(chalk_1.default.yellow("No migrations found"));
return;
}
console.log(chalk_1.default.yellow("\nMigration Status:"));
console.log(chalk_1.default.cyan("Migration".padEnd(40)) + chalk_1.default.cyan("Status"));
console.log("-".repeat(50));
status.forEach(({ migration, status }) => {
console.log(migration.padEnd(40) +
(status === "Completed"
? chalk_1.default.green(status)
: chalk_1.default.yellow(status)));
});
}
catch (error) {
spinner.fail(`Failed to check status: ${chalk_1.default.red(error.message)}`);
process.exit(1);
}
});
migrationsCommand
.command("reset")
.description("Rollback all migrations")
.option("-p, --path <path>", "Path to migrations directory", config.migrationsPath)
.action(async (options) => {
const spinner = (0, ora_1.default)("Rolling back all migrations...").start();
try {
const connection = createConnection(config);
const runner = new commands_1.MigrationRunnerCommand(connection, options.path);
const completedMigrations = await runner.getCompletedMigrations();
if (completedMigrations.length === 0) {
spinner.succeed("No migrations to reset");
return;
}
spinner.info(`Found ${completedMigrations.length} migrations to reset:`);
completedMigrations.forEach((filename) => {
console.log(` - ${chalk_1.default.yellow(filename)}`);
});
spinner.start("Rolling back migrations...");
await runner.reset();
spinner.succeed(`Successfully reset ${completedMigrations.length} migrations`);
}
catch (error) {
spinner.fail(`Reset failed: ${chalk_1.default.red(error.message)}`);
process.exit(1);
}
});
migrationsCommand
.command("fresh")
.description("Drop all tables and re-run all migrations from scratch")
.option("-p, --path <path>", "Path to migrations directory", config.migrationsPath)
.action(async (options) => {
const spinner = (0, ora_1.default)("Starting fresh installation...").start();
try {
const connection = createConnection(config);
const runner = new commands_1.MigrationRunnerCommand(connection, options.path);
const status = await runner.getMigrationStatus();
// Show information about what we're about to drop
spinner.info(`Found ${status.length} migrations in total`);
spinner.info("Starting fresh installation - this will drop all database objects");
// This will drop all database objects and run migrations from scratch
await runner.fresh();
spinner.succeed(`Successfully completed fresh installation with ${status.length} migrations`);
}
catch (error) {
spinner.fail(`Fresh installation failed: ${chalk_1.default.red(error.message)}`);
process.exit(1);
}
});
// Models
const modelsCommand = program.command("models").description("Manage models");
modelsCommand
.command("create <name>")
.description("Create a new model file")
.option("-p, --path <path>", "Path to models directory", config.modelsPath)
.action(async (name, options) => {
const spinner = (0, ora_1.default)("Creating model...").start();
try {
const modelCommand = new commands_1.ModelCommand(options.path);
const filePath = modelCommand.execute(name);
spinner.succeed(`Model created: ${chalk_1.default.green(filePath)}`);
}
catch (error) {
spinner.fail(`Failed to create model: ${chalk_1.default.red(error.message)}`);
process.exit(1);
}
});
// Seeders
const seedersCommand = program
.command("seeders")
.description("Manage database seeders");
seedersCommand
.command("create <name>")
.description("Create a new seeder file")
.option("-p, --path <path>", "Path to seeders directory", config.seedersPath)
.action(async (name, options) => {
const spinner = (0, ora_1.default)("Creating seeder...").start();
try {
const seederCommand = new commands_1.SeederCommand(createConnection(config), options.path);
const filePath = seederCommand.createSeeder(name);
spinner.succeed(`Seeder created: ${chalk_1.default.green(filePath)}`);
}
catch (error) {
spinner.fail(`Failed to create seeder: ${chalk_1.default.red(error.message)}`);
process.exit(1);
}
});
seedersCommand
.command("run")
.description("Run database seeders")
.option("-p, --path <path>", "Path to seeders directory", config.seedersPath)
.action(async (options) => {
const spinner = (0, ora_1.default)("Running seeders...").start();
try {
const seederCommand = new commands_1.SeederCommand(createConnection(config), options.path);
await seederCommand.run();
spinner.succeed("Database seeded successfully");
}
catch (error) {
spinner.fail(`Seeding failed: ${chalk_1.default.red(error.message)}`);
process.exit(1);
}
});
await program.parseAsync(process.argv);
}
// Run the CLI
main().catch((error) => {
console.error(chalk_1.default.red("Fatal error:"), error);
process.exit(1);
});
//# sourceMappingURL=index.js.map