Refactor DB Handler. Use Bun native SQL adapter.

This commit is contained in:
2026-06-24 15:52:19 +01:00
parent eb86e1803d
commit 44f6c18a84
107 changed files with 324 additions and 4923 deletions
-2
View File
@@ -1,2 +0,0 @@
import { Command } from "commander";
export default function (): Command;
-41
View File
@@ -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();
});
}
-3
View File
@@ -1,3 +0,0 @@
type Params = {};
export default function listTables(params?: Params): Promise<"__exit__" | void>;
export {};
-78
View File
@@ -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();
// }
}
}
}
-3
View File
@@ -1,3 +0,0 @@
type Params = {};
export default function runSQL(params?: Params): Promise<void>;
export {};
-26
View File
@@ -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
View File
@@ -1,5 +0,0 @@
type Params = {
tableName: string;
};
export default function showEntries({ tableName }: Params): Promise<"__exit__" | undefined>;
export {};
-83
View File
@@ -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;
// }
}
}
-5
View File
@@ -1,5 +0,0 @@
type Params = {
tableName: string;
};
export default function showFields({ tableName, }: Params): Promise<"__exit__" | void>;
export {};
-69
View File
@@ -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();
// }
}