Refactor DB Handler. Use Bun native SQL adapter.
This commit is contained in:
Vendored
-2
@@ -1,2 +0,0 @@
|
||||
import { Command } from "commander";
|
||||
export default function (): Command;
|
||||
Vendored
-41
@@ -1,41 +0,0 @@
|
||||
import { Command } from "commander";
|
||||
import init from "../../functions/init";
|
||||
import grabDBDir from "../../utils/grab-db-dir";
|
||||
import chalk from "chalk";
|
||||
import { select } from "@inquirer/prompts";
|
||||
import listTables from "./list-tables";
|
||||
import runSQL from "./run-sql";
|
||||
export default function () {
|
||||
return new Command("admin")
|
||||
.description("View Tables and Data, Run SQL Queries, Etc.")
|
||||
.action(async () => {
|
||||
const { config } = await init();
|
||||
const { db_file_path } = grabDBDir({ config });
|
||||
console.log(chalk.bold(chalk.blue("\nBun SQLite Admin\n")));
|
||||
try {
|
||||
while (true) {
|
||||
const paradigm = await select({
|
||||
message: "Choose an action:",
|
||||
choices: [
|
||||
{ name: "Tables", value: "list_tables" },
|
||||
{ name: "SQL", value: "run_sql" },
|
||||
{ name: chalk.dim("✕ Exit"), value: "exit" },
|
||||
],
|
||||
});
|
||||
if (paradigm === "exit")
|
||||
break;
|
||||
if (paradigm === "list_tables") {
|
||||
const result = await listTables();
|
||||
if (result === "__exit__")
|
||||
break;
|
||||
}
|
||||
if (paradigm === "run_sql")
|
||||
await runSQL();
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
type Params = {};
|
||||
export default function listTables(params?: Params): Promise<"__exit__" | void>;
|
||||
export {};
|
||||
Vendored
-78
@@ -1,78 +0,0 @@
|
||||
import { Database } from "bun:sqlite";
|
||||
import chalk from "chalk";
|
||||
import { select } from "@inquirer/prompts";
|
||||
import { AppData } from "../../data/app-data";
|
||||
import showEntries from "./show-entries";
|
||||
import showFields from "./show-fields";
|
||||
import dbHandler from "../../lib/db-handler";
|
||||
export default async function listTables(params) {
|
||||
const tables = (await dbHandler({
|
||||
query: `SELECT table_name FROM ${AppData["DbSchemaManagerTableName"]}`,
|
||||
})).payload;
|
||||
if (!tables?.length) {
|
||||
console.log(chalk.yellow("\nNo tables found.\n"));
|
||||
return;
|
||||
}
|
||||
// Level 1: table selection loop
|
||||
while (true) {
|
||||
const tableName = await select({
|
||||
message: "Select a table:",
|
||||
choices: [
|
||||
...tables.map((t) => ({
|
||||
name: t.table_name,
|
||||
value: t.table_name,
|
||||
})),
|
||||
{ name: chalk.dim("← Go Back"), value: "__back__" },
|
||||
{ name: chalk.dim("✕ Exit"), value: "__exit__" },
|
||||
],
|
||||
});
|
||||
if (tableName === "__back__")
|
||||
break;
|
||||
if (tableName === "__exit__")
|
||||
return "__exit__";
|
||||
// Level 2: action loop — stays here until "Go Back"
|
||||
while (true) {
|
||||
const action = await select({
|
||||
message: `"${tableName}" — choose an action:`,
|
||||
choices: [
|
||||
{ name: "Entries", value: "entries" },
|
||||
{ name: "Fields", value: "fields" },
|
||||
{ name: "Schema", value: "schema" },
|
||||
{ name: chalk.dim("← Go Back"), value: "__back__" },
|
||||
{ name: chalk.dim("✕ Exit"), value: "__exit__" },
|
||||
],
|
||||
});
|
||||
if (action === "__back__")
|
||||
break;
|
||||
if (action === "__exit__")
|
||||
return "__exit__";
|
||||
if (action === "entries") {
|
||||
const result = await showEntries({ tableName });
|
||||
if (result === "__exit__")
|
||||
return "__exit__";
|
||||
}
|
||||
if (action === "fields") {
|
||||
const result = await showFields({ tableName });
|
||||
if (result === "__exit__")
|
||||
return "__exit__";
|
||||
}
|
||||
// if (action === "schema") {
|
||||
// const columns = db
|
||||
// .query<ColumnInfo, []>(`PRAGMA table_info("${tableName}")`)
|
||||
// .all();
|
||||
// console.log(`\n${chalk.bold(`Schema for "${tableName}":`)} \n`);
|
||||
// console.table(
|
||||
// columns.map((c) => ({
|
||||
// "#": c.cid,
|
||||
// Name: c.name,
|
||||
// Type: c.type,
|
||||
// "Not Null": c.notnull ? "YES" : "NO",
|
||||
// Default: c.dflt_value ?? "(none)",
|
||||
// "Primary Key": c.pk ? "YES" : "NO",
|
||||
// })),
|
||||
// );
|
||||
// console.log();
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
type Params = {};
|
||||
export default function runSQL(params?: Params): Promise<void>;
|
||||
export {};
|
||||
Vendored
-26
@@ -1,26 +0,0 @@
|
||||
import { Database } from "bun:sqlite";
|
||||
import { input } from "@inquirer/prompts";
|
||||
import chalk from "chalk";
|
||||
import dbHandler from "../../lib/db-handler";
|
||||
export default async function runSQL(params) {
|
||||
const sql = await input({
|
||||
message: "Enter SQL query:",
|
||||
validate: (val) => val.trim().length > 0 || "Query cannot be empty",
|
||||
});
|
||||
try {
|
||||
const res = await dbHandler({ query: sql });
|
||||
if (res.payload) {
|
||||
console.log(`\n${chalk.bold(`Result (${res.payload.length} row${res.payload.length !== 1 ? "s" : ""}):`)} \n`);
|
||||
if (res.payload.length)
|
||||
console.table(res.payload);
|
||||
else
|
||||
console.log(chalk.yellow("No res returned.\n"));
|
||||
}
|
||||
else if (res.single_res) {
|
||||
console.log(chalk.green(`\nSuccess! Affected rows: ${res.single_res.changes}, Last insert ID: ${res.single_res.lastInsertRowid}\n`));
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(chalk.red(`\nSQL Error: ${error.message}\n`));
|
||||
}
|
||||
}
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
type Params = {
|
||||
tableName: string;
|
||||
};
|
||||
export default function showEntries({ tableName }: Params): Promise<"__exit__" | undefined>;
|
||||
export {};
|
||||
Vendored
-83
@@ -1,83 +0,0 @@
|
||||
import { Database } from "bun:sqlite";
|
||||
import chalk from "chalk";
|
||||
import { select, input } from "@inquirer/prompts";
|
||||
import dbHandler from "../../lib/db-handler";
|
||||
const LIMIT = 50;
|
||||
export default async function showEntries({ tableName }) {
|
||||
let page = 0;
|
||||
let searchField = null;
|
||||
let searchTerm = null;
|
||||
while (true) {
|
||||
const offset = page * LIMIT;
|
||||
const rows = searchTerm
|
||||
? (await dbHandler({
|
||||
query: `SELECT * FROM "${tableName}" WHERE "${searchField}" LIKE ? LIMIT ${LIMIT} OFFSET ${offset}`,
|
||||
values: [`%${searchTerm}%`],
|
||||
})).payload
|
||||
: (await dbHandler({
|
||||
query: `SELECT * FROM "${tableName}" LIMIT ${LIMIT} OFFSET ${offset}`,
|
||||
})).payload;
|
||||
const countRow = searchTerm
|
||||
? (await dbHandler({
|
||||
query: `SELECT COUNT(*) as count FROM "${tableName}" WHERE "${searchField}" LIKE ?`,
|
||||
values: [`%${searchTerm}%`],
|
||||
})).payload
|
||||
: (await dbHandler({
|
||||
query: `SELECT COUNT(*) as count FROM "${tableName}"`,
|
||||
})).payload;
|
||||
const total = countRow?.[0]?.count;
|
||||
const searchInfo = searchTerm
|
||||
? chalk.dim(` · searching "${searchField}" = "${searchTerm}"`)
|
||||
: "";
|
||||
if (!rows) {
|
||||
return;
|
||||
}
|
||||
console.log(`\n${chalk.bold(tableName)} — Page ${page + 1}${searchInfo} (${rows.length} of ${total}):\n`);
|
||||
if (rows.length) {
|
||||
console.log(rows);
|
||||
// if (rows.length) console.table(rows);
|
||||
}
|
||||
else {
|
||||
console.log(chalk.yellow("No rows found."));
|
||||
console.log();
|
||||
}
|
||||
const choices = [];
|
||||
if (page > 0)
|
||||
choices.push({ name: "← Previous Page", value: "prev" });
|
||||
if (offset + rows.length < total)
|
||||
choices.push({ name: "Next Page →", value: "next" });
|
||||
choices.push({ name: "Search by Field", value: "search" });
|
||||
if (searchTerm)
|
||||
choices.push({ name: "Clear Search", value: "clear_search" });
|
||||
choices.push({ name: chalk.dim("← Go Back"), value: "__back__" });
|
||||
choices.push({ name: chalk.dim("✕ Exit"), value: "__exit__" });
|
||||
const action = await select({ message: "Navigate:", choices });
|
||||
if (action === "__back__")
|
||||
break;
|
||||
if (action === "__exit__")
|
||||
return "__exit__";
|
||||
if (action === "next")
|
||||
page++;
|
||||
if (action === "prev")
|
||||
page--;
|
||||
if (action === "clear_search") {
|
||||
searchField = null;
|
||||
searchTerm = null;
|
||||
page = 0;
|
||||
}
|
||||
// if (action === "search") {
|
||||
// const columns = db
|
||||
// .query<ColumnInfo, []>(`PRAGMA table_info("${tableName}")`)
|
||||
// .all();
|
||||
// searchField = await select({
|
||||
// message: "Search by field:",
|
||||
// choices: columns.map((c) => ({ name: c.name, value: c.name })),
|
||||
// });
|
||||
// searchTerm = await input({
|
||||
// message: `Search term for "${searchField}":`,
|
||||
// validate: (v) => v.trim().length > 0 || "Cannot be empty",
|
||||
// });
|
||||
// page = 0;
|
||||
// }
|
||||
}
|
||||
}
|
||||
Vendored
-5
@@ -1,5 +0,0 @@
|
||||
type Params = {
|
||||
tableName: string;
|
||||
};
|
||||
export default function showFields({ tableName, }: Params): Promise<"__exit__" | void>;
|
||||
export {};
|
||||
Vendored
-69
@@ -1,69 +0,0 @@
|
||||
import { Database } from "bun:sqlite";
|
||||
import chalk from "chalk";
|
||||
import { select } from "@inquirer/prompts";
|
||||
export default async function showFields({ tableName, }) {
|
||||
// const columns = db
|
||||
// .query<ColumnInfo, []>(`PRAGMA table_info("${tableName}")`)
|
||||
// .all();
|
||||
// const indexes = db
|
||||
// .query<IndexInfo, []>(`PRAGMA index_list("${tableName}")`)
|
||||
// .all();
|
||||
// const foreignKeys = db
|
||||
// .query<ForeignKey, []>(`PRAGMA foreign_key_list("${tableName}")`)
|
||||
// .all();
|
||||
// const indexedFields = new Map<string, { unique: boolean }>();
|
||||
// for (const idx of indexes) {
|
||||
// const cols = db
|
||||
// .query<IndexColumn, []>(`PRAGMA index_info("${idx.name}")`)
|
||||
// .all();
|
||||
// for (const col of cols) {
|
||||
// indexedFields.set(col.name, { unique: idx.unique === 1 });
|
||||
// }
|
||||
// }
|
||||
// while (true) {
|
||||
// const fieldName = await select({
|
||||
// message: `"${tableName}" — select a field:`,
|
||||
// choices: [
|
||||
// ...columns.map((c) => ({ name: c.name, value: c.name })),
|
||||
// { name: chalk.dim("← Go Back"), value: "__back__" },
|
||||
// { name: chalk.dim("✕ Exit"), value: "__exit__" },
|
||||
// ],
|
||||
// });
|
||||
// if (fieldName === "__back__") break;
|
||||
// if (fieldName === "__exit__") return "__exit__";
|
||||
// const col = columns.find((c) => c.name === fieldName)!;
|
||||
// const idx = indexedFields.get(fieldName);
|
||||
// const fk = foreignKeys.find((f) => f.from === fieldName);
|
||||
// console.log(`\n${chalk.bold(`Field: "${fieldName}"`)}\n`);
|
||||
// console.log(` ${chalk.dim("Table")} ${tableName}`);
|
||||
// console.log(` ${chalk.dim("Column #")} ${col.cid}`);
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Type")} ${col.type || chalk.italic("(none)")}`,
|
||||
// );
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Primary Key")} ${col.pk ? chalk.green("YES") : "NO"}`,
|
||||
// );
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Not Null")} ${col.notnull ? chalk.yellow("YES") : "NO"}`,
|
||||
// );
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Default")} ${col.dflt_value ?? chalk.italic("(none)")}`,
|
||||
// );
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Indexed")} ${idx ? chalk.cyan("YES") : "NO"}`,
|
||||
// );
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Unique")} ${idx?.unique ? chalk.cyan("YES") : "NO"}`,
|
||||
// );
|
||||
// if (fk) {
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Foreign Key")} ${chalk.magenta(`${fk.table}(${fk.to})`)}`,
|
||||
// );
|
||||
// console.log(` ${chalk.dim("On Update")} ${fk.on_update}`);
|
||||
// console.log(` ${chalk.dim("On Delete")} ${fk.on_delete}`);
|
||||
// } else {
|
||||
// console.log(` ${chalk.dim("Foreign Key")} NO`);
|
||||
// }
|
||||
// console.log();
|
||||
// }
|
||||
}
|
||||
Vendored
-2
@@ -1,2 +0,0 @@
|
||||
import { Command } from "commander";
|
||||
export default function (): Command;
|
||||
Vendored
-22
@@ -1,22 +0,0 @@
|
||||
import { Command } from "commander";
|
||||
import init from "../functions/init";
|
||||
import path from "path";
|
||||
import grabDBDir from "../utils/grab-db-dir";
|
||||
import fs from "fs";
|
||||
import grabDBBackupFileName from "../utils/grab-db-backup-file-name";
|
||||
import chalk from "chalk";
|
||||
import trimBackups from "../utils/trim-backups";
|
||||
export default function () {
|
||||
return new Command("backup")
|
||||
.description("Backup Database")
|
||||
.action(async (opts) => {
|
||||
console.log(`Backing up database ...`);
|
||||
const { config } = await init();
|
||||
const { backup_dir, db_file_path } = grabDBDir({ config });
|
||||
const new_db_file_name = grabDBBackupFileName({ config });
|
||||
fs.cpSync(db_file_path, path.join(backup_dir, new_db_file_name));
|
||||
trimBackups({ config });
|
||||
console.log(`${chalk.bold(chalk.green(`DB Backup Success!`))}`);
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
Vendored
-6
@@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* # Declare Global Variables
|
||||
*/
|
||||
declare global { }
|
||||
export {};
|
||||
Vendored
-33
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
import { program } from "commander";
|
||||
import schema from "./schema";
|
||||
import typedef from "./typedef";
|
||||
import backup from "./backup";
|
||||
import restore from "./restore";
|
||||
import admin from "./admin";
|
||||
/**
|
||||
* # Describe Program
|
||||
*/
|
||||
program
|
||||
.name(`bun-mariadb`)
|
||||
.description(`MariaDB manager for Bun`)
|
||||
.version(`1.0.0`);
|
||||
/**
|
||||
* # Declare Commands
|
||||
*/
|
||||
program.addCommand(schema());
|
||||
program.addCommand(typedef());
|
||||
program.addCommand(backup());
|
||||
program.addCommand(restore());
|
||||
program.addCommand(admin());
|
||||
/**
|
||||
* # Handle Unavailable Commands
|
||||
*/
|
||||
program.on("command:*", () => {
|
||||
console.error("Invalid command: %s\nSee --help for a list of available commands.", program.args.join(" "));
|
||||
process.exit(1);
|
||||
});
|
||||
/**
|
||||
* # Parse Arguments
|
||||
*/
|
||||
program.parse(Bun.argv);
|
||||
Vendored
-2
@@ -1,2 +0,0 @@
|
||||
import { Command } from "commander";
|
||||
export default function (): Command;
|
||||
Vendored
-44
@@ -1,44 +0,0 @@
|
||||
import { Command } from "commander";
|
||||
import init from "../functions/init";
|
||||
import grabDBDir from "../utils/grab-db-dir";
|
||||
import fs from "fs";
|
||||
import chalk from "chalk";
|
||||
import grabSortedBackups from "../utils/grab-sorted-backups";
|
||||
import { select } from "@inquirer/prompts";
|
||||
import grabBackupData from "../utils/grab-backup-data";
|
||||
import path from "path";
|
||||
export default function () {
|
||||
return new Command("restore")
|
||||
.description("Restore Database")
|
||||
.action(async (opts) => {
|
||||
console.log(`Restoring up database ...`);
|
||||
const { config } = await init();
|
||||
const { backup_dir, db_file_path } = grabDBDir({ config });
|
||||
const backups = grabSortedBackups({ config });
|
||||
if (!backups?.[0]) {
|
||||
console.error(`No Backups to restore. Use the \`backup\` command to create a backup`);
|
||||
process.exit(1);
|
||||
}
|
||||
try {
|
||||
const selected_backup = await select({
|
||||
message: "Select a backup:",
|
||||
choices: backups.map((b, i) => {
|
||||
const { backup_date } = grabBackupData({
|
||||
backup_name: b,
|
||||
});
|
||||
return {
|
||||
name: `Backup #${i + 1}: ${backup_date.toDateString()} ${backup_date.getHours()}:${backup_date.getMinutes()}:${backup_date.getSeconds().toString().padStart(2, "0")}`,
|
||||
value: b,
|
||||
};
|
||||
}),
|
||||
});
|
||||
fs.cpSync(path.join(backup_dir, selected_backup), db_file_path);
|
||||
console.log(`${chalk.bold(chalk.green(`DB Restore Success!`))}`);
|
||||
process.exit();
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Backup Restore ERROR => ${error.message}`);
|
||||
process.exit();
|
||||
}
|
||||
});
|
||||
}
|
||||
Vendored
-2
@@ -1,2 +0,0 @@
|
||||
import { Command } from "commander";
|
||||
export default function (): Command;
|
||||
Vendored
-54
@@ -1,54 +0,0 @@
|
||||
import { Command } from "commander";
|
||||
import { MariaDBSchemaManager } from "../lib/mariadb/db-schema-manager";
|
||||
import init from "../functions/init";
|
||||
import grabDirNames from "../data/grab-dir-names";
|
||||
import path from "path";
|
||||
import dbSchemaToTypeDef from "../lib/mariadb/schema-to-typedef";
|
||||
import _ from "lodash";
|
||||
import appendDefaultFieldsToDbSchema from "../utils/append-default-fields-to-db-schema";
|
||||
import chalk from "chalk";
|
||||
import { writeLiveSchema } from "../functions/live-schema";
|
||||
import grabDBDir from "../utils/grab-db-dir";
|
||||
import { cpSync } from "fs";
|
||||
export default function () {
|
||||
return new Command("schema")
|
||||
.description("Build DB From Schema")
|
||||
.option("-v, --vector", "Recreate Vector Tables. This will drop and rebuild all vector tables")
|
||||
.option("-t, --typedef", "Generate typescript type definitions")
|
||||
.action(async (opts) => {
|
||||
console.log(`Starting process ...`);
|
||||
const { config, dbSchema } = await init();
|
||||
const { ROOT_DIR, BUN_MARIADB_TEMP_DB_FILE_PATH } = grabDirNames();
|
||||
const { db_file_path } = grabDBDir({ config });
|
||||
cpSync(db_file_path, BUN_MARIADB_TEMP_DB_FILE_PATH);
|
||||
try {
|
||||
const isVector = Boolean(opts.vector || opts.v);
|
||||
const isTypeDef = Boolean(opts.typedef || opts.t);
|
||||
const finaldbSchema = appendDefaultFieldsToDbSchema({
|
||||
dbSchema,
|
||||
});
|
||||
const manager = new MariaDBSchemaManager({
|
||||
schema: finaldbSchema,
|
||||
recreate_vector_table: isVector,
|
||||
});
|
||||
await manager.syncSchema();
|
||||
manager.close();
|
||||
if (isTypeDef && config.typedef_file_path) {
|
||||
const out_file = path.resolve(ROOT_DIR, config.typedef_file_path);
|
||||
dbSchemaToTypeDef({
|
||||
dbSchema: finaldbSchema,
|
||||
dst_file: out_file,
|
||||
config,
|
||||
});
|
||||
}
|
||||
writeLiveSchema({ schema: finaldbSchema });
|
||||
console.log(`${chalk.bold(chalk.green(`DB Schema setup success!`))}`);
|
||||
process.exit();
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
cpSync(BUN_MARIADB_TEMP_DB_FILE_PATH, db_file_path);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
Vendored
-2
@@ -1,2 +0,0 @@
|
||||
import { Command } from "commander";
|
||||
export default function (): Command;
|
||||
Vendored
-31
@@ -1,31 +0,0 @@
|
||||
import { Command } from "commander";
|
||||
import init from "../functions/init";
|
||||
import dbSchemaToTypeDef from "../lib/mariadb/schema-to-typedef";
|
||||
import path from "path";
|
||||
import grabDirNames from "../data/grab-dir-names";
|
||||
import appendDefaultFieldsToDbSchema from "../utils/append-default-fields-to-db-schema";
|
||||
import chalk from "chalk";
|
||||
export default function () {
|
||||
return new Command("typedef")
|
||||
.description("Build DB From Schema")
|
||||
.action(async (opts) => {
|
||||
console.log(`Creating Type Definition From DB Schema ...`);
|
||||
const { config, dbSchema } = await init();
|
||||
const { ROOT_DIR } = grabDirNames();
|
||||
const finaldbSchema = appendDefaultFieldsToDbSchema({ dbSchema });
|
||||
if (config.typedef_file_path) {
|
||||
const out_file = path.resolve(ROOT_DIR, config.typedef_file_path);
|
||||
dbSchemaToTypeDef({
|
||||
dbSchema: finaldbSchema,
|
||||
dst_file: out_file,
|
||||
config,
|
||||
});
|
||||
}
|
||||
else {
|
||||
console.error(``);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`${chalk.bold(chalk.green(`Typedef gen success!`))}`);
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user