@tonyism1/db-utils
Version:
MySQL utilities with automatic table and column creation, plus B2 Backblaze media storage
137 lines (119 loc) • 4.93 kB
JavaScript
import mysql from "mysql2/promise";
import { detectType, mapKeysToSnakeCase, toSnakeCase } from "./utils.js";
export async function createDB(config) {
const pool = mysql.createPool(config);
// Ensure table exists; create with minimal structure if missing
async function ensureTable(table) {
const [rows] = await pool.query("SHOW TABLES LIKE ?", [table]);
if (rows.length === 0) {
console.log(`Creating table '${table}'`);
await pool.query(`CREATE TABLE \`${table}\` (id INT AUTO_INCREMENT PRIMARY KEY)`);
}
}
// Ensure all columns exist; create missing ones
async function ensureColumns(table, data) {
await ensureTable(table);
const columnsQuery = await pool.query(`SHOW COLUMNS FROM \`${table}\``);
const existingColumns = columnsQuery[0].map(r => r.Field);
const snakeData = mapKeysToSnakeCase(data);
for (const key of Object.keys(snakeData)) {
if (!existingColumns.includes(key)) {
const type = detectType(snakeData[key]);
console.log(`Adding column '${key}' to table '${table}' as ${type}`);
await pool.query(
`ALTER TABLE \`${table}\` ADD COLUMN \`${key}\` ${type} DEFAULT NULL`
);
}
}
return snakeData;
}
return {
pool,
async insert(table, data) {
const snakeData = await ensureColumns(table, data);
const keys = Object.keys(snakeData);
const values = Object.values(snakeData);
const placeholders = keys.map(() => "?").join(",");
const [result] = await pool.query(
`INSERT INTO \`${table}\` (${keys.join(",")}) VALUES (${placeholders})`,
values
);
return result.insertId;
},
async update(table, data, where) {
const snakeData = await ensureColumns(table, data);
const set = Object.keys(snakeData).map(k => `\`${k}\` = ?`).join(", ");
const whereKeys = mapKeysToSnakeCase(where);
const whereClause = Object.keys(whereKeys).map(k => `\`${k}\` = ?`).join(" AND ");
const values = [...Object.values(snakeData), ...Object.values(whereKeys)];
await pool.query(`UPDATE \`${table}\` SET ${set} WHERE ${whereClause}`, values);
},
async findOne(table, where) {
const whereKeys = mapKeysToSnakeCase(where);
const whereClause = Object.keys(whereKeys).map(k => `\`${k}\` = ?`).join(" AND ");
const values = Object.values(whereKeys);
const [rows] = await pool.query(`SELECT * FROM \`${table}\` WHERE ${whereClause} LIMIT 1`, values);
return rows[0] || null;
},
async findAll(table, where = {}, options = {}) {
const whereKeys = mapKeysToSnakeCase(where);
const whereClause = Object.keys(whereKeys).length
? "WHERE " + Object.keys(whereKeys).map(k => `\`${k}\` = ?`).join(" AND ")
: "";
const values = Object.values(whereKeys);
let sql = `SELECT * FROM \`${table}\` ${whereClause}`;
if (options.limit) sql += ` LIMIT ${options.limit}`;
const [rows] = await pool.query(sql, values);
return rows;
},
async delete(table, where = {}) {
const whereKeys = mapKeysToSnakeCase(where);
const whereClause = Object.keys(whereKeys).length
? "WHERE " + Object.keys(whereKeys).map(k => `\`${k}\` = ?`).join(" AND ")
: "";
const values = Object.values(whereKeys);
const sql = `DELETE FROM \`${table}\` ${whereClause}`;
await pool.query(sql, values);
},
async createTable(table, columns = {}) {
// Create table with specified columns or just id column if none provided
let columnDefinitions = "id INT AUTO_INCREMENT PRIMARY KEY";
// Add additional columns if provided
for (const [key, type] of Object.entries(columns)) {
const snakeKey = toSnakeCase(key);
columnDefinitions += `, \`${snakeKey}\` ${type}`;
}
const sql = `CREATE TABLE IF NOT EXISTS \`${table}\` (${columnDefinitions})`;
await pool.query(sql);
},
async dropTable(table) {
const sql = `DROP TABLE IF EXISTS \`${table}\``;
await pool.query(sql);
},
async addColumn(table, column, type) {
const snakeColumn = toSnakeCase(column);
const sql = `ALTER TABLE \`${table}\` ADD COLUMN \`${snakeColumn}\` ${type} DEFAULT NULL`;
await pool.query(sql);
},
async getTables() {
const [rows] = await pool.query("SHOW TABLES");
// Extract table names from the result
return rows.map(row => Object.values(row)[0]);
},
async getTableColumns(table) {
const [rows] = await pool.query(`SHOW COLUMNS FROM \`${table}\``);
return rows.map(row => ({
field: row.Field,
type: row.Type,
null: row.Null,
key: row.Key,
default: row.Default,
extra: row.Extra
}));
},
async query(sql, params = []) {
const [rows] = await pool.query(sql, params);
return rows;
}
};
}