This commit is contained in:
2026-07-14 09:10:30 +01:00
parent 79c5b896fe
commit 56462e6cc2
38 changed files with 1056 additions and 1720 deletions
-4
View File
@@ -1,5 +1,4 @@
import { Command } from "commander";
import grabDBDir from "../../utils/grab-db-dir";
import chalk from "chalk";
import { select } from "@inquirer/prompts";
import listTables from "./list-tables";
@@ -9,9 +8,6 @@ export default function () {
return new Command("admin")
.description("View Tables and Data, Run SQL Queries, Etc.")
.action(async () => {
const config = global.CONFIG;
const { db_file_path } = grabDBDir({ config });
console.log(chalk.bold(chalk.blue("\nBun MariaDB Admin\n")));
try {
+36 -30
View File
@@ -4,24 +4,16 @@ import { AppData } from "../../data/app-data";
import showEntries from "./show-entries";
import showFields from "./show-fields";
import dbHandler from "../../lib/db-handler";
import MariaDBQuoteGen from "../../lib/schema/mariadb-quote-gen";
type Params = {};
type ColumnInfo = {
cid: number;
name: string;
type: string;
notnull: number;
dflt_value: string | null;
pk: number;
};
export default async function listTables(
params?: Params,
): Promise<"__exit__" | void> {
const tables = (
await dbHandler({
query: `SELECT table_name FROM ${AppData["DbSchemaManagerTableName"]}`,
await dbHandler<{ table_name: string }>({
query: `SELECT table_name FROM ${MariaDBQuoteGen(AppData["DbSchemaManagerTableName"])}`,
})
).payload;
@@ -30,7 +22,6 @@ export default async function listTables(
return;
}
// Level 1: table selection loop
while (true) {
const tableName = await select({
message: "Select a table:",
@@ -47,7 +38,6 @@ export default async function listTables(
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:`,
@@ -73,24 +63,40 @@ export default async function listTables(
if (result === "__exit__") return "__exit__";
}
// if (action === "schema") {
// const columns = db
// .query<ColumnInfo, []>(`PRAGMA table_info("${tableName}")`)
// .all();
if (action === "schema") {
const columns = (
await dbHandler<{
ORDINAL_POSITION: number;
COLUMN_NAME: string;
COLUMN_TYPE: string;
IS_NULLABLE: string;
COLUMN_DEFAULT: string | null;
COLUMN_KEY: string;
EXTRA: string;
}>({
query: `SELECT ORDINAL_POSITION, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_KEY, EXTRA FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION`,
values: [tableName],
})
).payload;
// 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();
// }
console.log(`\n${chalk.bold(`Schema for "${tableName}":`)} \n`);
if (columns?.length) {
console.table(
columns.map((c) => ({
"#": c.ORDINAL_POSITION,
Name: c.COLUMN_NAME,
Type: c.COLUMN_TYPE,
Nullable: c.IS_NULLABLE,
Default: c.COLUMN_DEFAULT ?? "(none)",
Key: c.COLUMN_KEY || "",
Extra: c.EXTRA || "",
})),
);
} else {
console.log(chalk.yellow("No columns found."));
}
console.log();
}
}
}
}
+6 -5
View File
@@ -13,18 +13,19 @@ export default async function runSQL(params?: Params) {
try {
const res = await dbHandler({ query: sql });
if (res.payload) {
if (res.payload?.length) {
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.table(res.payload);
} else if (res.success) {
console.log(
chalk.green(
`\nSuccess! Affected rows: ${res.single_res.changes}, Last insert ID: ${res.single_res.lastInsertRowid}\n`,
`\nSuccess! Affected rows: ${res.insert_return?.affected_rows ?? res.count ?? 0}, Last insert ID: ${res.insert_return?.last_insert_id ?? "—"}\n`,
),
);
} else {
console.error(chalk.red(`\nSQL Error: ${res.error || res.msg}\n`));
}
} catch (error: any) {
console.error(chalk.red(`\nSQL Error: ${error.message}\n`));
+37 -24
View File
@@ -1,11 +1,11 @@
import chalk from "chalk";
import { select, input } from "@inquirer/prompts";
import dbHandler from "../../lib/db-handler";
import MariaDBQuoteGen from "../../lib/schema/mariadb-quote-gen";
type Params = {
tableName: string;
};
type ColumnInfo = { cid: number; name: string };
const LIMIT = 50;
@@ -14,36 +14,38 @@ export default async function showEntries({ tableName }: Params) {
let searchField: string | null = null;
let searchTerm: string | null = null;
const quotedTable = MariaDBQuoteGen(tableName);
while (true) {
const offset = page * LIMIT;
const rows = searchTerm
? (
await dbHandler({
query: `SELECT * FROM "${tableName}" WHERE "${searchField}" LIKE ? LIMIT ${LIMIT} OFFSET ${offset}`,
query: `SELECT * FROM ${quotedTable} WHERE ${MariaDBQuoteGen(searchField!)} LIKE ? LIMIT ${LIMIT} OFFSET ${offset}`,
values: [`%${searchTerm}%`],
})
).payload
: (
await dbHandler({
query: `SELECT * FROM "${tableName}" LIMIT ${LIMIT} OFFSET ${offset}`,
query: `SELECT * FROM ${quotedTable} LIMIT ${LIMIT} OFFSET ${offset}`,
})
).payload;
const countRow = searchTerm
? (
await dbHandler({
query: `SELECT COUNT(*) as count FROM "${tableName}" WHERE "${searchField}" LIKE ?`,
await dbHandler<{ count: number }>({
query: `SELECT COUNT(*) as count FROM ${quotedTable} WHERE ${MariaDBQuoteGen(searchField!)} LIKE ?`,
values: [`%${searchTerm}%`],
})
).payload
: (
await dbHandler({
query: `SELECT COUNT(*) as count FROM "${tableName}"`,
await dbHandler<{ count: number }>({
query: `SELECT COUNT(*) as count FROM ${quotedTable}`,
})
).payload;
const total = countRow?.[0]?.count;
const total = Number(countRow?.[0]?.count || 0);
const searchInfo = searchTerm
? chalk.dim(` · searching "${searchField}" = "${searchTerm}"`)
: "";
@@ -57,8 +59,7 @@ export default async function showEntries({ tableName }: Params) {
);
if (rows.length) {
console.log(rows);
// if (rows.length) console.table(rows);
console.table(rows);
} else {
console.log(chalk.yellow("No rows found."));
console.log();
@@ -85,19 +86,31 @@ export default async function showEntries({ tableName }: Params) {
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;
// }
if (action === "search") {
const columns = (
await dbHandler<{ COLUMN_NAME: string }>({
query: `SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION`,
values: [tableName],
})
).payload;
if (!columns?.length) {
console.log(chalk.yellow("No columns found for search."));
continue;
}
searchField = await select({
message: "Search by field:",
choices: columns.map((c) => ({
name: c.COLUMN_NAME,
value: c.COLUMN_NAME,
})),
});
searchTerm = await input({
message: `Search term for "${searchField}":`,
validate: (v) => v.trim().length > 0 || "Cannot be empty",
});
page = 0;
}
}
}
+101 -84
View File
@@ -1,92 +1,109 @@
import chalk from "chalk";
import { select } from "@inquirer/prompts";
import dbHandler from "../../lib/db-handler";
type Params = {
tableName: string;
};
type ColumnInfo = {
cid: number;
name: string;
type: string;
notnull: number;
dflt_value: string | null;
pk: number;
};
type IndexInfo = { name: string; unique: number; origin: string };
type IndexColumn = { name: string };
type ForeignKey = {
id: number;
from: string;
table: string;
to: string;
on_update: string;
on_delete: string;
};
export default async function showFields({
tableName,
}: Params): Promise<"__exit__" | void> {
// 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();
// }
const columns = (
await dbHandler<{
ORDINAL_POSITION: number;
COLUMN_NAME: string;
COLUMN_TYPE: string;
IS_NULLABLE: string;
COLUMN_DEFAULT: string | null;
COLUMN_KEY: string;
EXTRA: string;
COLUMN_COMMENT: string;
}>({
query: `SELECT ORDINAL_POSITION, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_KEY, EXTRA, COLUMN_COMMENT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION`,
values: [tableName],
})
).payload;
if (!columns?.length) {
console.log(chalk.yellow(`\nNo columns found for "${tableName}".\n`));
return;
}
const indexes = (
await dbHandler<{
INDEX_NAME: string;
COLUMN_NAME: string;
NON_UNIQUE: number;
INDEX_TYPE: string;
}>({
query: `SELECT INDEX_NAME, COLUMN_NAME, NON_UNIQUE, INDEX_TYPE FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY INDEX_NAME, SEQ_IN_INDEX`,
values: [tableName],
})
).payload;
const foreignKeys = (
await dbHandler<{
CONSTRAINT_NAME: string;
COLUMN_NAME: string;
REFERENCED_TABLE_NAME: string;
REFERENCED_COLUMN_NAME: string;
}>({
query: `SELECT CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND REFERENCED_TABLE_NAME IS NOT NULL`,
values: [tableName],
})
).payload;
while (true) {
const fieldName = await select({
message: `"${tableName}" — select a field:`,
choices: [
...columns.map((c) => ({
name: c.COLUMN_NAME,
value: c.COLUMN_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.COLUMN_NAME === fieldName)!;
const colIndexes =
indexes?.filter((i) => i.COLUMN_NAME === fieldName) || [];
const fk = foreignKeys?.find((f) => f.COLUMN_NAME === fieldName);
console.log(`\n${chalk.bold(`Field: "${fieldName}"`)}\n`);
console.log(` ${chalk.dim("Table")} ${tableName}`);
console.log(` ${chalk.dim("Column #")} ${col.ORDINAL_POSITION}`);
console.log(` ${chalk.dim("Type")} ${col.COLUMN_TYPE}`);
console.log(
` ${chalk.dim("Primary Key")} ${col.COLUMN_KEY === "PRI" ? chalk.green("YES") : "NO"}`,
);
console.log(
` ${chalk.dim("Not Null")} ${col.IS_NULLABLE === "NO" ? chalk.yellow("YES") : "NO"}`,
);
console.log(
` ${chalk.dim("Default")} ${col.COLUMN_DEFAULT ?? chalk.italic("(none)")}`,
);
console.log(
` ${chalk.dim("Extra")} ${col.EXTRA || chalk.italic("(none)")}`,
);
console.log(
` ${chalk.dim("Indexed")} ${colIndexes.length ? chalk.cyan(colIndexes.map((i) => i.INDEX_NAME).join(", ")) : "NO"}`,
);
if (fk) {
console.log(
` ${chalk.dim("Foreign Key")} ${chalk.magenta(`${fk.REFERENCED_TABLE_NAME}(${fk.REFERENCED_COLUMN_NAME})`)}`,
);
} else {
console.log(` ${chalk.dim("Foreign Key")} NO`);
}
if (col.COLUMN_COMMENT) {
console.log(` ${chalk.dim("Comment")} ${col.COLUMN_COMMENT}`);
}
console.log();
}
}
+79 -9
View File
@@ -1,28 +1,98 @@
import { Command } from "commander";
import path from "path";
import grabDBDir from "../utils/grab-db-dir";
import fs from "fs";
import grabDBDir from "../utils/grab-db-dir";
import grabDBBackupFileName from "../utils/grab-db-backup-file-name";
import chalk from "chalk";
import trimBackups from "../utils/trim-backups";
import mariadbCliEnv, {
mariadbCliConnectionArgs,
} from "../utils/mariadb-cli-env";
/**
* Prefer mariadb-dump, fall back to mysqldump.
*/
function resolveDumpBinary(): string {
const candidates = ["mariadb-dump", "mysqldump"];
for (const bin of candidates) {
try {
const result = Bun.spawnSync(["which", bin], {
stdout: "pipe",
stderr: "pipe",
});
if (result.exitCode === 0) {
return new TextDecoder().decode(result.stdout).trim() || bin;
}
} catch {
// try next
}
}
return "mariadb-dump";
}
export default function () {
return new Command("backup")
.description("Backup Database")
.action(async (opts) => {
.description("Backup MariaDB database (SQL dump)")
.action(async () => {
console.log(`Backing up database ...`);
const config = global.CONFIG;
const { backup_dir } = grabDBDir({ config });
const { backup_dir, db_file_path } = grabDBDir({ config });
if (!fs.existsSync(backup_dir)) {
fs.mkdirSync(backup_dir, { recursive: true });
}
const new_db_file_name = grabDBBackupFileName({ config });
const backup_file_name = grabDBBackupFileName({ config });
const backup_path = path.join(backup_dir, backup_file_name);
fs.cpSync(db_file_path, path.join(backup_dir, new_db_file_name));
const dumpBin = resolveDumpBinary();
const args = [
dumpBin,
...mariadbCliConnectionArgs(),
"--single-transaction",
"--routines",
"--triggers",
"--events",
config.db_name,
];
trimBackups({ config });
try {
const proc = Bun.spawn(args, {
stdout: "pipe",
stderr: "pipe",
env: mariadbCliEnv(),
});
console.log(`${chalk.bold(chalk.green(`DB Backup Success!`))}`);
process.exit();
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
if (exitCode !== 0) {
console.error(
chalk.red(
`Backup failed (exit ${exitCode}): ${stderr || "unknown error"}`,
),
);
process.exit(1);
}
fs.writeFileSync(backup_path, stdout, "utf-8");
trimBackups({ config });
console.log(
`${chalk.bold(chalk.green(`DB Backup Success!`))}${backup_path}`,
);
process.exit(0);
} catch (error: any) {
console.error(
chalk.red(
`Backup ERROR => ${error.message}. Ensure \`mariadb-dump\` or \`mysqldump\` is installed.`,
),
);
process.exit(1);
}
});
}
+70 -12
View File
@@ -6,22 +6,47 @@ import grabSortedBackups from "../utils/grab-sorted-backups";
import { select } from "@inquirer/prompts";
import grabBackupData from "../utils/grab-backup-data";
import path from "path";
import mariadbCliEnv, {
mariadbCliConnectionArgs,
} from "../utils/mariadb-cli-env";
/**
* Prefer mariadb client, fall back to mysql.
*/
function resolveClientBinary(): string {
const candidates = ["mariadb", "mysql"];
for (const bin of candidates) {
try {
const result = Bun.spawnSync(["which", bin], {
stdout: "pipe",
stderr: "pipe",
});
if (result.exitCode === 0) {
return new TextDecoder().decode(result.stdout).trim() || bin;
}
} catch {
// try next
}
}
return "mariadb";
}
export default function () {
return new Command("restore")
.description("Restore Database")
.action(async (opts) => {
console.log(`Restoring up database ...`);
.description("Restore MariaDB database from an SQL dump")
.action(async () => {
console.log(`Restoring database ...`);
const config = global.CONFIG;
const { backup_dir } = grabDBDir({ config });
const { backup_dir, db_file_path } = grabDBDir({ config });
const backups = grabSortedBackups({ config });
const backups = grabSortedBackups({ config }).filter((b) =>
b.endsWith(".sql"),
);
if (!backups?.[0]) {
console.error(
`No Backups to restore. Use the \`backup\` command to create a backup`,
`No SQL backups to restore. Use the \`backup\` command to create a backup.`,
);
process.exit(1);
}
@@ -40,16 +65,49 @@ export default function () {
}),
});
fs.cpSync(path.join(backup_dir, selected_backup), db_file_path);
const backup_path = path.join(backup_dir, selected_backup);
if (!fs.existsSync(backup_path)) {
console.error(`Backup file not found: ${backup_path}`);
process.exit(1);
}
const clientBin = resolveClientBinary();
const sql = fs.readFileSync(backup_path, "utf-8");
const args = [
clientBin,
...mariadbCliConnectionArgs(),
config.db_name,
];
const proc = Bun.spawn(args, {
stdin: new Blob([sql]),
stdout: "pipe",
stderr: "pipe",
env: mariadbCliEnv(),
});
const [stderr, exitCode] = await Promise.all([
new Response(proc.stderr).text(),
proc.exited,
]);
if (exitCode !== 0) {
console.error(
chalk.red(
`Restore failed (exit ${exitCode}): ${stderr || "unknown error"}`,
),
);
process.exit(1);
}
console.log(
`${chalk.bold(chalk.green(`DB Restore Success!`))}`,
`${chalk.bold(chalk.green(`DB Restore Success!`))}${selected_backup}`,
);
process.exit();
process.exit(0);
} catch (error: any) {
console.error(`Backup Restore ERROR => ${error.message}`);
process.exit();
process.exit(1);
}
});
}
+6 -16
View File
@@ -1,9 +1,7 @@
import { Command } from "commander";
import { MariaDBSchemaManager } from "../lib/mariadb/db-schema-manager";
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";
@@ -12,10 +10,6 @@ import createDBSchema from "../lib/schema/create-db-schema";
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 ...`);
@@ -32,14 +26,10 @@ export default function () {
dbSchema,
});
// const manager = new MariaDBSchemaManager({
// schema: finaldbSchema,
// });
// await manager.syncSchema();
// manager.close();
await createDBSchema({ db_schema: finaldbSchema });
await createDBSchema({
db_schema: finaldbSchema,
config,
});
if (isTypeDef && config.typedef_file_path) {
const out_file = path.resolve(
@@ -59,9 +49,9 @@ export default function () {
console.log(
`${chalk.bold(chalk.green(`DB Schema setup success!`))}`,
);
process.exit();
process.exit(0);
} catch (error) {
console.log(error);
console.error(error);
process.exit(1);
}
});
+13 -15
View File
@@ -7,8 +7,8 @@ import chalk from "chalk";
export default function () {
return new Command("typedef")
.description("Build DB From Schema")
.action(async (opts) => {
.description("Generate TypeScript type definitions from the schema")
.action(async () => {
console.log(`Creating Type Definition From DB Schema ...`);
const config = global.CONFIG;
@@ -18,23 +18,21 @@ export default function () {
const finaldbSchema = appendDefaultFieldsToDbSchema({ dbSchema });
if (config.typedef_file_path) {
const out_file = path.resolve(
ROOT_DIR,
config.typedef_file_path,
if (!config.typedef_file_path) {
console.error(
`\`typedef_file_path\` is required in bun-mariadb.config.ts to generate types.`,
);
dbSchemaToTypeDef({
dbSchema: finaldbSchema,
dst_file: out_file,
config,
});
} else {
console.error(``);
process.exit(1);
}
console.log(`${chalk.bold(chalk.green(`Typedef gen success!`))}`);
const out_file = path.resolve(ROOT_DIR, config.typedef_file_path);
dbSchemaToTypeDef({
dbSchema: finaldbSchema,
dst_file: out_file,
config,
});
process.exit();
console.log(`${chalk.bold(chalk.green(`Typedef gen success!`))}`);
process.exit(0);
});
}
-5
View File
@@ -9,10 +9,6 @@ export default function grabDirNames(params?: Params) {
const ROOT_DIR = process.cwd();
const BUN_MARIADB_DIR = path.join(ROOT_DIR, ".bun-mariadb");
const BUN_MARIADB_TEMP_DIR = path.join(BUN_MARIADB_DIR, ".tmp");
const BUN_MARIADB_TEMP_DB_FILE_PATH = path.join(
BUN_MARIADB_TEMP_DIR,
"temp.db",
);
const BUN_MARIADB_LIVE_SCHEMA = path.join(
BUN_MARIADB_DIR,
"live-schema.json",
@@ -23,6 +19,5 @@ export default function grabDirNames(params?: Params) {
BUN_MARIADB_DIR,
BUN_MARIADB_TEMP_DIR,
BUN_MARIADB_LIVE_SCHEMA,
BUN_MARIADB_TEMP_DB_FILE_PATH,
};
}
+31 -9
View File
@@ -7,12 +7,40 @@ type Params = {
config: BunMariaDBConfig;
};
/** Reuse one client per database name to avoid exhausting MariaDB connections. */
const clientCache = new Map<string, Bun.SQL>();
export default function grabMariaDBClient({
config,
}: Params): Bun.SQL | undefined {
const cacheKey = config.db_name || "__default__";
const cached = clientCache.get(cacheKey);
if (cached) {
return cached;
}
// Prefer the process-wide client when it matches the requested database
if (
global.MARIADB_CLIENT &&
(!global.CONFIG?.db_name || global.CONFIG.db_name === config.db_name)
) {
clientCache.set(cacheKey, global.MARIADB_CLIENT);
return global.MARIADB_CLIENT;
}
const { ROOT_DIR } = grabDirNames();
try {
const tls = config.ssl_ca
? {
ca: Bun.file(path.resolve(ROOT_DIR, config.ssl_ca)),
rejectUnauthorized: false,
}
: {
rejectUnauthorized: false,
};
const MariaDBClient = new SQL({
hostname: process.env.BUN_MARIADB_SERVER_HOST,
username: process.env.BUN_MARIADB_SERVER_USERNAME,
@@ -22,17 +50,11 @@ export default function grabMariaDBClient({
? Number(process.env.BUN_MARIADB_SERVER_PORT)
: undefined,
...config.db_config,
tls: config.ssl_ca
? {
ca: Bun.file(path.resolve(ROOT_DIR, config.ssl_ca)),
rejectUnauthorized: false,
}
: {
rejectUnauthorized: false,
},
tls,
adapter: "mariadb",
});
} as Bun.SQL.Options);
clientCache.set(cacheKey, MariaDBClient);
return MariaDBClient;
} catch (error: any) {
console.error(`Couldn't grab MariaDB Client => ` + error.message);
+2 -3
View File
@@ -7,7 +7,6 @@ import {
type BUN_MARIADB_DatabaseSchemaType,
RequiredENVs,
} from "../types";
import { SQL } from "bun";
import setMariaDBClient from "./set-mariadb-client";
/**
@@ -70,8 +69,8 @@ export default function init(): void {
}
const db_dir = path.resolve(ROOT_DIR, Config.db_dir);
if (!fs.existsSync(Config.db_dir)) {
fs.mkdirSync(Config.db_dir, { recursive: true });
if (!fs.existsSync(db_dir)) {
fs.mkdirSync(db_dir, { recursive: true });
}
const DBSchemaFilePath = path.join(db_dir, AppData["DbSchemaFileName"]);
+12
View File
@@ -22,3 +22,15 @@ const BunMariaDB = {
} as const;
export default BunMariaDB;
export type {
BunMariaDBConfig,
BUN_MARIADB_DatabaseSchemaType,
BUN_MARIADB_TableSchemaType,
BUN_MARIADB_FieldSchemaType,
BUN_MARIADB_IndexSchemaType,
BUN_MARIADB_UniqueConstraintSchemaType,
BUN_MARIADB_ForeignKeyType,
DBResponseObject,
ServerQueryParam,
} from "./types";
+86 -66
View File
@@ -1,66 +1,86 @@
import grabMariaDBClient from "../functions/grab-mariadb-client";
import type {
BunMariaDBConfig,
DBInsertReturn,
DBResponseObject,
} from "../types";
type Param = {
query: string;
values?: any[];
config?: BunMariaDBConfig;
};
/**
* # Main DB Handler Function
* @requires DSQL_DB_CONN - Gobal Variable for Datasquirel Database
*/
export default async function dbHandler<
T extends { [k: string]: any } = { [k: string]: any },
>({ query, values, config }: Param): Promise<DBResponseObject<T>> {
try {
const CLIENT = config
? grabMariaDBClient({ config })
: global.MARIADB_CLIENT;
if (!CLIENT) {
throw new Error(`Couldn't grab MariaDB Client.`);
}
const res = await CLIENT.unsafe(query, values);
const count = res.count;
const res_array = (() => {
try {
return JSON.parse(JSON.stringify(res)) as T[];
} catch (error) {
return undefined;
}
})();
const last_insert_id = res_array?.[0] ? res_array[0]?.id : undefined;
const affected_rows = res_array?.[0] ? res_array.length : undefined;
const insert_return: DBInsertReturn = {
count,
last_insert_id,
affected_rows,
};
return {
success: true,
payload: res_array,
single_res: res_array?.[0],
insert_return,
count,
};
} catch (error: any) {
return {
success: false,
error: "DB Handler Error => " + error.message,
msg: "DB Handler Error => " + error.message,
};
} finally {
}
}
import grabMariaDBClient from "../functions/grab-mariadb-client";
import type {
BunMariaDBConfig,
DBInsertReturn,
DBResponseObject,
} from "../types";
type Param = {
query: string;
values?: any[];
config?: BunMariaDBConfig;
};
/**
* Resolve a shared MariaDB client. Prefer the global client so schema sync
* and CRUD do not open a new connection per query.
*/
function resolveClient(config?: BunMariaDBConfig): Bun.SQL | undefined {
const resolvedConfig = config || global.CONFIG;
// Same database as the global client → reuse it
if (
global.MARIADB_CLIENT &&
(!resolvedConfig?.db_name ||
!global.CONFIG?.db_name ||
resolvedConfig.db_name === global.CONFIG.db_name)
) {
return global.MARIADB_CLIENT;
}
if (resolvedConfig) {
return grabMariaDBClient({ config: resolvedConfig });
}
return global.MARIADB_CLIENT;
}
/**
* # Main DB Handler Function
*/
export default async function dbHandler<
T extends { [k: string]: any } = { [k: string]: any },
>({ query, values, config }: Param): Promise<DBResponseObject<T>> {
try {
const CLIENT = resolveClient(config);
if (!CLIENT) {
throw new Error(`Couldn't grab MariaDB Client.`);
}
const res = await CLIENT.unsafe(query, values);
const count = res.count;
const res_array = (() => {
try {
return JSON.parse(JSON.stringify(res)) as T[];
} catch (error) {
return undefined;
}
})();
const last_insert_id = res_array?.[0] ? res_array[0]?.id : undefined;
const affected_rows = res_array?.[0] ? res_array.length : undefined;
const insert_return: DBInsertReturn = {
count,
last_insert_id,
affected_rows,
};
return {
success: true,
payload: res_array,
single_res: res_array?.[0],
insert_return,
count,
};
} catch (error: any) {
return {
success: false,
error: "DB Handler Error => " + error.message,
msg: "DB Handler Error => " + error.message,
};
}
}
-29
View File
@@ -1,29 +0,0 @@
import fs from "fs";
import type { ConnectionConfig } from "mariadb";
import path from "path";
/**
* # Grab SSL
*/
export default function grabDbSSL(): ConnectionConfig["ssl"] {
const caProivdedPath =
global.CONFIG.ssl_ca || process.env.BUN_MARIADB_SERVER_SSL_KEY_PATH;
if (!caProivdedPath?.match(/./)) {
return {
rejectUnauthorized: false,
};
}
const ca_file_path = path.resolve(process.cwd(), caProivdedPath);
if (!fs.existsSync(ca_file_path)) {
console.log(`${ca_file_path} does not exist`);
return undefined;
}
return {
ca: fs.readFileSync(ca_file_path),
rejectUnauthorized: false,
};
}
File diff suppressed because it is too large Load Diff
-49
View File
@@ -1,49 +0,0 @@
// import { SQL } from "bun";
// import grabDirNames from "../../data/grab-dir-names";
// import path from "path";
// if (!global.CONFIG) {
// console.error(`Couldn't grab global Config.`);
// process.exit(1);
// }
// if (!global.DB_SCHEMA) {
// console.error(`Couldn't grab Database Schema.`);
// process.exit(1);
// }
// const config = global.CONFIG;
// const { ROOT_DIR } = grabDirNames();
// const MariaDBClient = new SQL({
// hostname: process.env.BUN_MARIADB_SERVER_HOST,
// username: process.env.BUN_MARIADB_SERVER_USERNAME,
// password: process.env.BUN_MARIADB_SERVER_PASSWORD,
// database: config.db_name,
// port: process.env.BUN_MARIADB_SERVER_PORT
// ? Number(process.env.BUN_MARIADB_SERVER_PORT)
// : undefined,
// ...config.db_config,
// tls: config.ssl_ca
// ? {
// ca: Bun.file(path.resolve(ROOT_DIR, config.ssl_ca)),
// rejectUnauthorized: false,
// }
// : {
// rejectUnauthorized: false,
// },
// adapter: "mariadb",
// });
// global.MARIADB_CLIENT = MariaDBClient;
// const test = await MariaDBClient.unsafe(`SHOW DATABASES`);
// if (!test.count) {
// console.error(`MariaDBClient Error: Database not ready.`);
// console.log(test);
// process.exit(1);
// }
// export default MariaDBClient;
+9 -2
View File
@@ -1,4 +1,5 @@
import type { BUN_MARIADB_FieldSchemaType } from "../../types";
import isVectorField from "./is-vector-field";
import mapDataType from "./map-data-types";
import MariaDBQuoteGen from "./mariadb-quote-gen";
@@ -16,13 +17,19 @@ export default function buildColumnDefinition(
parts.push("AUTO_INCREMENT");
}
if (field.notNullValue || field.primaryKey || field.isVector) {
// Vector columns used in VECTOR INDEX must be NOT NULL
if (
field.notNullValue ||
field.primaryKey ||
isVectorField(field)
) {
if (!field.primaryKey) {
parts.push("NOT NULL");
}
}
if (field.unique && !field.primaryKey) {
// VECTOR columns cannot be UNIQUE in the usual sense
if (field.unique && !field.primaryKey && !isVectorField(field)) {
parts.push("UNIQUE");
}
+14 -14
View File
@@ -19,26 +19,23 @@ export default async function handleDBSchemaTable({
}: CreateDBSchemaTableHandlerParams) {
const resolvedTable = resolveTable(table, db_schema);
const schemaCond = schemaCondition(config);
const liveTables = await querySchemaRows<{ TABLE_NAME: string }>({
query: `SELECT TABLE_NAME FROM information_schema.TABLES WHERE ${schemaCond.where} AND TABLE_TYPE = 'BASE TABLE'`,
values: schemaCond.values,
config,
});
const liveTableNames = liveTables.map((t) => t.TABLE_NAME);
let tableExistsTracked = Boolean(db_manager_table_name);
let tableExistsLive = Boolean(
existing_live_table?.TABLE_NAME ||
liveTableNames.includes(resolvedTable.tableName),
);
let tableExistsLive = Boolean(existing_live_table?.TABLE_NAME);
let wasRenamed = false;
if (
resolvedTable.tableNameOld &&
resolvedTable.tableNameOld !== resolvedTable.tableName
) {
if (liveTableNames.includes(resolvedTable.tableNameOld)) {
// Only hit information_schema when a rename is declared
const schemaCond = schemaCondition(config);
const liveTables = await querySchemaRows<{ TABLE_NAME: string }>({
query: `SELECT TABLE_NAME FROM information_schema.TABLES WHERE ${schemaCond.where} AND TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = ?`,
values: [...schemaCond.values, resolvedTable.tableNameOld],
config,
});
if (liveTables.length > 0) {
console.log(
`Renaming table: ${resolvedTable.tableNameOld} -> ${resolvedTable.tableName}`,
);
@@ -68,7 +65,10 @@ export default async function handleDBSchemaTable({
});
} else {
if (!wasRenamed) {
await updateTable({ table: resolvedTable, config });
await updateTable({
table: resolvedTable,
config,
});
}
await upsertDbManagerTable({
tableName: resolvedTable.tableName,
+11
View File
@@ -0,0 +1,11 @@
import type { BUN_MARIADB_FieldSchemaType } from "../../types";
/**
* True when a field is a MariaDB native vector column.
*/
export default function isVectorField(
field?: BUN_MARIADB_FieldSchemaType,
): boolean {
if (!field) return false;
return field.isVector === true || field.dataType === "VECTOR";
}
+3 -6
View File
@@ -6,8 +6,9 @@ export default function mapDataType(
const dataType = field.dataType?.toUpperCase() || "TEXT";
const vectorSize = field.vectorSize || 1536;
if (field.isVector) {
return `LONGTEXT COMMENT 'vector_size=${vectorSize}'`;
// Native MariaDB VECTOR type (11.7+). Prefer this over LONGTEXT storage.
if (field.isVector || dataType === "VECTOR") {
return `VECTOR(${vectorSize})`;
}
switch (dataType) {
@@ -78,10 +79,6 @@ export default function mapDataType(
return "JSON";
case "INET6":
return "INET6";
case "VECTOR": {
const dimensions = field.vectorSize || 1536;
return `VECTOR(${dimensions})`;
}
case "BOOLEAN":
return "TINYINT(1)";
case "ENUM": {
+56
View File
@@ -25,6 +25,10 @@ async function checkIfTableExists({
return Boolean(rows[0]?.table_exists);
}
/**
* Full table rebuild. For `isVector` tables this drops and recreates in place
* (preserving rows when possible). For regular tables it uses a temp-table swap.
*/
export default async function recreateTable({
table,
config,
@@ -42,6 +46,58 @@ export default async function recreateTable({
return;
}
/**
* Vector tables: drop + recreate + reinsert (MariaDB VECTOR INDEX / dim
* changes are not reliably alterable in place).
*/
if (table.isVector) {
console.log(`Recreating vector table: ${table.tableName}`);
const existingRows = await querySchemaRows<Record<string, any>>({
query: `SELECT * FROM ${MariaDBQuoteGen(table.tableName)}`,
config,
});
await runSchemaQuery({ query: `SET FOREIGN_KEY_CHECKS = 0`, config });
try {
await runSchemaQuery({
query: `DROP TABLE IF EXISTS ${MariaDBQuoteGen(table.tableName)}`,
config,
});
await createTable({ table, config });
} finally {
await runSchemaQuery({ query: `SET FOREIGN_KEY_CHECKS = 1`, config });
}
if (existingRows.length > 0) {
const schemaFieldNames = new Set(
(table.fields || [])
.map((f) => f.fieldName)
.filter((n): n is string => Boolean(n)),
);
for (const row of existingRows) {
const columns = Object.keys(row).filter((c) =>
schemaFieldNames.has(c),
);
if (columns.length === 0) continue;
const placeholders = columns.map(() => "?").join(", ");
const columnList = columns
.map((c) => MariaDBQuoteGen(c))
.join(", ");
await runSchemaQuery({
query: `INSERT INTO ${MariaDBQuoteGen(table.tableName)} (${columnList}) VALUES (${placeholders})`,
values: columns.map((c) => row[c] ?? null),
config,
});
}
}
return;
}
const tempTableName = `${table.tableName}_temp_${Date.now()}`;
const backupOldTableName = `${table.tableName}_old_${Date.now()}`;
const existingColumns = await getTableColumns({
+24 -12
View File
@@ -1,11 +1,33 @@
import type {
BUN_MARIADB_IndexSchemaType,
BUN_MARIADB_TableSchemaType,
BunMariaDBConfig,
} from "../../types";
import isVectorField from "./is-vector-field";
import MariaDBQuoteGen from "./mariadb-quote-gen";
import runSchemaQuery, { querySchemaRows } from "./run-schema-query";
import schemaCondition from "./schema-condition";
function isVectorIndexDef(
index: BUN_MARIADB_IndexSchemaType,
table: BUN_MARIADB_TableSchemaType,
): boolean {
if (index.indexType === "VECTOR") return true;
if (table.isVector) return true;
const firstFieldName = index.indexTableFields?.[0];
if (!firstFieldName) return false;
const field = table.fields?.find((f) => f.fieldName === firstFieldName);
return isVectorField(field);
}
function vectorDistanceMetric(index: BUN_MARIADB_IndexSchemaType): string {
if (index.vectorDistanceMetric === "cosine") return "cosine";
if (index.vectorDistanceMetric === "euclidean") return "euclidean";
return "euclidean";
}
export default async function syncIndexes({
table,
config,
@@ -121,20 +143,10 @@ export default async function syncIndexes({
}
if (!existingIndexesMap.has(index.indexName)) {
const isVectorIndex =
table.isVector ||
table.fields?.some(
(f) =>
f.fieldName === index.indexTableFields![0] && f.isVector,
);
if (isVectorIndex) {
if (isVectorIndexDef(index, table)) {
console.log(`Creating Vector index: ${index.indexName}`);
const targetField = MariaDBQuoteGen(index.indexTableFields[0]!);
const distanceMetric =
(index.indexType as string)?.toLowerCase() === "cosine"
? "cosine"
: "euclidean";
const distanceMetric = vectorDistanceMetric(index);
await runSchemaQuery({
query: `ALTER TABLE ${MariaDBQuoteGen(table.tableName)} ADD VECTOR INDEX ${MariaDBQuoteGen(index.indexName)} (${targetField}) M=8 DISTANCE=${distanceMetric}`,
+47 -5
View File
@@ -6,6 +6,7 @@ import type {
import buildColumnDefinition from "./build-column-definition";
import createTable from "./create-table";
import getTableColumns from "./get-table-columns";
import isVectorField from "./is-vector-field";
import mapDataType from "./map-data-types";
import MariaDBQuoteGen from "./mariadb-quote-gen";
import recreateTable from "./recreate-table";
@@ -29,6 +30,30 @@ function columnTypesMatch(liveType: string, expectedType: string): boolean {
return false;
}
function vectorTypeDiverged(
liveType: string,
liveComment: string,
field: BUN_MARIADB_FieldSchemaType,
): boolean {
const dimensions = field.vectorSize || 1536;
const expectedNative = `vector(${dimensions})`;
const live = liveType.toLowerCase().replace(/\s+/g, "");
if (live === expectedNative) return false;
// Legacy LONGTEXT storage with vector_size comment
if (live.startsWith("longtext") || live.startsWith("text")) {
const match = liveComment.match(/vector_size\s*=\s*(\d+)/i);
if (match && Number(match[1]) === dimensions) {
// Still legacy storage — treat as diverged so we can migrate to VECTOR
return true;
}
return true;
}
return true;
}
async function addColumn({
tableName,
field,
@@ -109,6 +134,7 @@ export default async function updateTable({
const fieldsToAdd: BUN_MARIADB_FieldSchemaType[] = [];
const fieldsToModify: BUN_MARIADB_FieldSchemaType[] = [];
const fieldsToDrop: string[] = [];
let needsVectorRecreate = false;
for (const field of table.fields || []) {
if (!field.fieldName) continue;
@@ -116,6 +142,8 @@ export default async function updateTable({
const liveField = liveFieldsMap.get(field.fieldName);
if (!liveField) {
// Adding a new vector column can require rebuild if VECTOR INDEX
// constraints conflict; still try surgical add first.
fieldsToAdd.push(field);
} else {
let typeDiverged = !columnTypesMatch(
@@ -123,10 +151,15 @@ export default async function updateTable({
mapDataType(field),
);
if (field.isVector || field.dataType === "VECTOR") {
const dimensions = field.vectorSize || 1536;
const expectedNativeToken = `vector(${dimensions})`;
typeDiverged = liveField.type.toLowerCase() !== expectedNativeToken;
if (isVectorField(field)) {
typeDiverged = vectorTypeDiverged(
liveField.type,
liveField.comment,
field,
);
if (typeDiverged) {
needsVectorRecreate = true;
}
}
if (typeDiverged) {
@@ -135,6 +168,15 @@ export default async function updateTable({
}
}
// Vector dimension / storage type changes → full rebuild automatically
if (needsVectorRecreate) {
console.log(
`Vector column change detected on \`${table.tableName}\`; recreating table`,
);
await recreateTable({ table, config });
return;
}
for (const col of existingColumns) {
if (!codeFieldsMap.has(col.name)) {
fieldsToDrop.push(col.name);
@@ -208,7 +250,7 @@ export default async function updateTable({
try {
await modifyColumn({ tableName: table.tableName, field, config });
} catch (err: any) {
if (field.isVector || field.dataType === "VECTOR") {
if (isVectorField(field)) {
console.warn(
`[Vector Resize] Re-aligning dimension spaces natively via safe migration schema rebuild.`,
);
+8 -8
View File
@@ -1,5 +1,4 @@
import type { RequestOptions } from "https";
import type { ConnectionConfig } from "mariadb";
/**
* Fully-qualified database name used when a database needs to be referenced
@@ -98,13 +97,9 @@ export interface BUN_MARIADB_TableSchemaType {
childTableDbId?: string | number;
collation?: (typeof MariaDBCollations)[number];
/**
* If this is a vector table
* If this is a vector-oriented table (native MariaDB VECTOR columns/indexes)
*/
isVector?: boolean;
/**
* Type of vector. Defaults to `vec0`
*/
vectorType?: string;
}
/**
@@ -234,6 +229,7 @@ export const MariaDBIndexTypes = [
"HASH",
"FULLTEXT",
"SPATIAL",
"VECTOR",
] as const;
export type MariaDBIndexType = (typeof MariaDBIndexTypes)[number];
@@ -252,9 +248,13 @@ export interface BUN_MARIADB_IndexSchemaType {
*/
indexTableFields?: string[];
/**
* Under the hood index type (BTREE, HASH) or modifier (FULLTEXT, SPATIAL)
* Under the hood index type (BTREE, HASH) or modifier (FULLTEXT, SPATIAL, VECTOR)
*/
indexType?: MariaDBIndexType;
/**
* Distance metric for VECTOR indexes (`euclidean` | `cosine`). Defaults to euclidean.
*/
vectorDistanceMetric?: "euclidean" | "cosine";
/**
* Optional documentation or tuning note inside the DB metadata
@@ -1519,7 +1519,7 @@ export type BunMariaDBConfig = {
db_backup_dir?: string;
max_backups?: number;
/**
* The Root Directory for the DB file and schema
* Root directory for schema, types, and local artifacts (relative to project root)
*/
db_dir?: string;
/**
+5 -1
View File
@@ -2,8 +2,12 @@ type Params = {
backup_name: string;
};
/**
* Parse timestamped backup names: `{db_name}-{timestamp}[.sql]`
*/
export default function grabBackupData({ backup_name }: Params) {
const backup_parts = backup_name.split("-");
const normalized = backup_name.replace(/\.sql$/, "");
const backup_parts = normalized.split("-");
const backup_date_timestamp = Number(backup_parts.pop());
const origin_backup_name = backup_parts.join("-");
+1 -3
View File
@@ -5,7 +5,5 @@ type Params = {
};
export default function grabDBBackupFileName({ config }: Params) {
const new_db_file_name = `${config.db_name}-${Date.now()}`;
return new_db_file_name;
return `${config.db_name}-${Date.now()}.sql`;
}
+4 -7
View File
@@ -10,17 +10,14 @@ type Params = {
export default function grabDBDir({ config }: Params) {
const { ROOT_DIR } = grabDirNames();
let db_dir = ROOT_DIR;
if (config.db_dir) {
db_dir = config.db_dir;
}
const db_dir = config.db_dir
? path.resolve(ROOT_DIR, config.db_dir)
: ROOT_DIR;
const backup_dir_name =
config.db_backup_dir || AppData["DefaultBackupDirName"];
const backup_dir = path.resolve(db_dir, backup_dir_name);
const db_file_path = path.resolve(db_dir, config.db_name);
return { db_dir, backup_dir, db_file_path };
return { db_dir, backup_dir };
}
+11 -12
View File
@@ -6,24 +6,23 @@ type Params = {
config: BunMariaDBConfig;
};
function backupTimestamp(name: string): number {
const base = name.replace(/\.sql$/, "");
const ts = Number(base.split("-").pop());
return Number.isFinite(ts) ? ts : 0;
}
export default function grabSortedBackups({ config }: Params) {
const { backup_dir } = grabDBDir({ config });
if (!fs.existsSync(backup_dir)) {
return [] as string[];
}
const backups = fs.readdirSync(backup_dir);
/**
* Order Backups. Most recent first.
*/
const ordered_backups = backups.sort((a, b) => {
const a_date = Number(a.split("-").pop());
const b_date = Number(b.split("-").pop());
if (a_date > b_date) {
return -1;
}
return 1;
});
return ordered_backups;
return backups.sort((a, b) => backupTimestamp(b) - backupTimestamp(a));
}
+28
View File
@@ -0,0 +1,28 @@
/**
* Build env for mariadb / mariadb-dump child processes without putting
* the password on the process argv (visible via `ps`).
*/
export default function mariadbCliEnv(): NodeJS.ProcessEnv {
const env: NodeJS.ProcessEnv = { ...process.env };
const password = process.env.BUN_MARIADB_SERVER_PASSWORD;
if (password) {
// Standard MySQL/MariaDB client env vars (prefer not using -p on argv)
env.MYSQL_PWD = password;
env.MARIADB_PWD = password;
}
return env;
}
export function mariadbCliConnectionArgs(): string[] {
const host = process.env.BUN_MARIADB_SERVER_HOST || "127.0.0.1";
const user = process.env.BUN_MARIADB_SERVER_USERNAME || "root";
const port = process.env.BUN_MARIADB_SERVER_PORT;
return [
`-h${host}`,
`-u${user}`,
...(port ? [`-P${port}`] : []),
];
}
+1 -1
View File
@@ -25,7 +25,7 @@ type Return = {
/**
* # SQL Gen Operator Gen
* @description Generates an SQL operator for node module `mysql` or `serverless-mysql`
* @description Maps query equality operators to MariaDB SQL fragments
*/
export default function sqlGenOperatorGen({
fieldName,
+1 -1
View File
@@ -20,7 +20,7 @@ type Return = {
/**
* # SQL Query Generator
* @description Generates an SQL Query for node module `mysql` or `serverless-mysql`
* @description Builds parameterized SELECT SQL for MariaDB
*/
export default function sqlGenerator<
T extends { [key: string]: any } = { [key: string]: any },