First version without typescript errors

This commit is contained in:
Benjamin Toby 2026-06-21 07:05:49 +01:00
parent 9c8f6e20aa
commit eb86e1803d
16 changed files with 364 additions and 225 deletions

View File

@ -25,9 +25,6 @@ export default function () {
const dbSchema = global.DB_SCHEMA; const dbSchema = global.DB_SCHEMA;
const { ROOT_DIR, BUN_MARIADB_TEMP_DB_FILE_PATH } = grabDirNames(); 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 { try {
const isVector = Boolean(opts.vector || opts.v); const isVector = Boolean(opts.vector || opts.v);
@ -66,7 +63,6 @@ export default function () {
process.exit(); process.exit();
} catch (error) { } catch (error) {
console.log(error); console.log(error);
cpSync(BUN_MARIADB_TEMP_DB_FILE_PATH, db_file_path);
process.exit(1); process.exit(1);
} }
}); });

View File

@ -3,4 +3,5 @@ export const AppData = {
MaxBackups: 10, MaxBackups: 10,
DefaultBackupDirName: ".backups", DefaultBackupDirName: ".backups",
DbSchemaManagerTableName: "__db_schema_manager__", DbSchemaManagerTableName: "__db_schema_manager__",
DbSchemaFileName: "schema.ts",
} as const; } as const;

View File

@ -2,10 +2,11 @@ import path from "path";
import fs from "fs"; import fs from "fs";
import { AppData } from "../data/app-data"; import { AppData } from "../data/app-data";
import grabDirNames from "../data/grab-dir-names"; import grabDirNames from "../data/grab-dir-names";
import type { import {
BunMariaDBConfig, type BunMariaDBConfig,
BunMariaDBConfigReturn, type BunMariaDBConfigReturn,
BUN_MARIADB_DatabaseSchemaType, type BUN_MARIADB_DatabaseSchemaType,
RequiredENVs,
} from "../types"; } from "../types";
export default async function init(): Promise<BunMariaDBConfigReturn> { export default async function init(): Promise<BunMariaDBConfigReturn> {
@ -25,27 +26,53 @@ export default async function init(): Promise<BunMariaDBConfigReturn> {
const ConfigImport = await import(ConfigFilePath); const ConfigImport = await import(ConfigFilePath);
const Config = ConfigImport["default"] as BunMariaDBConfig; const Config = ConfigImport["default"] as BunMariaDBConfig;
if (!Config) {
console.error(
`No default export from \`${ConfigFilePath}\`. Please export a default module.`,
);
process.exit(1);
}
if (!Config.db_name) { if (!Config.db_name) {
console.error(`\`db_name\` is required in your config`); console.error(`\`db_name\` is required in your config`);
process.exit(1); process.exit(1);
} }
if (!Config.db_schema_file_name) { RequiredENVs.forEach((env_key) => {
console.error(`\`db_schema_file_name\` is required in your config`); if (
env_key == "BUN_MARIADB_SERVER_PORT" ||
env_key == "BUN_MARIADB_SERVER_SSL_KEY_PATH"
) {
return;
}
if (!process.env[env_key]) {
console.error(`\`${env_key}\` env variable is required.`);
process.exit(1);
}
});
if (!Config.db_dir) {
console.error(
`\`db_dir\` is required in your config. This directory holds all database related configuration. Also note that a \`${AppData["DbSchemaFileName"]}\` file is also required in this directory to define your database schema`,
);
process.exit(1); process.exit(1);
} }
let db_dir = ROOT_DIR; const db_dir = path.resolve(ROOT_DIR, Config.db_dir);
if (!fs.existsSync(Config.db_dir)) {
if (Config.db_dir) { fs.mkdirSync(Config.db_dir, { recursive: true });
db_dir = path.resolve(ROOT_DIR, Config.db_dir); }
if (!fs.existsSync(Config.db_dir)) { const DBSchemaFilePath = path.join(db_dir, AppData["DbSchemaFileName"]);
fs.mkdirSync(Config.db_dir, { recursive: true });
} if (!fs.existsSync(DBSchemaFilePath)) {
console.error(
`Please create a schema file at \`${DBSchemaFilePath}\`. Don't forget to export a default module from this file.`,
);
process.exit(1);
} }
const DBSchemaFilePath = path.join(db_dir, Config.db_schema_file_name);
const DbSchemaImport = await import(DBSchemaFilePath); const DbSchemaImport = await import(DBSchemaFilePath);
const DbSchema = DbSchemaImport[ const DbSchema = DbSchemaImport[
"default" "default"

View File

@ -5,9 +5,6 @@ import grabDBConnection from "./grab-db-connection";
type Param = { type Param = {
query: string; query: string;
values?: string[] | object; values?: string[] | object;
noErrorLogs?: boolean;
database?: string;
tableSchema?: BUN_MARIADB_TableSchemaType;
config?: ConnectionConfig; config?: ConnectionConfig;
}; };
@ -17,18 +14,12 @@ type Param = {
*/ */
export default async function dbHandler< export default async function dbHandler<
T extends { [k: string]: any } = { [k: string]: any }, T extends { [k: string]: any } = { [k: string]: any },
>({ >({ query, values, config }: Param): Promise<DBResponseObject> {
query,
values,
noErrorLogs,
database,
config,
}: Param): Promise<DBResponseObject> {
let CONNECTION: Connection | undefined; let CONNECTION: Connection | undefined;
let results: T | null = null; let results: T | null = null;
try { try {
CONNECTION = await grabDBConnection({ database, config }); CONNECTION = await grabDBConnection({ config });
if (query && values) { if (query && values) {
const queryResults = await CONNECTION.query(query, values); const queryResults = await CONNECTION.query(query, values);
@ -46,10 +37,6 @@ export default async function dbHandler<
throw new Error("Authentication Failed!"); throw new Error("Authentication Failed!");
} }
if (!noErrorLogs) {
console.log(error);
}
results = null; results = null;
} finally { } finally {
await CONNECTION?.end(); await CONNECTION?.end();

View File

@ -8,7 +8,7 @@ type Return = ConnectionConfig["ssl"] | undefined;
* # Grab SSL * # Grab SSL
*/ */
export default function grabDbSSL(): Return { export default function grabDbSSL(): Return {
const caProivdedPath = process.env.DSQL_SSL_CA_CERT; const caProivdedPath = process.env.BUN_MARIADB_SERVER_SSL_KEY_PATH;
if (!caProivdedPath?.match(/./)) { if (!caProivdedPath?.match(/./)) {
return undefined; return undefined;

View File

@ -8,17 +8,18 @@ import grabDbSSL from "./grab-db-ssl";
export default function grabDSQLConnectionConfig( export default function grabDSQLConnectionConfig(
param?: DsqlConnectionParam, param?: DsqlConnectionParam,
): ConnectionConfig { ): ConnectionConfig {
const CONN_TIMEOUT = 10000; const configData = global.CONFIG;
const CONN_TIMEOUT = configData?.connection_timeout || 10000;
const config: ConnectionConfig = { const config: ConnectionConfig = {
host: process.env.DSQL_DB_HOST, host: process.env.BUN_MARIADB_SERVER_HOST,
user: process.env.DSQL_DB_USERNAME, user: process.env.BUN_MARIADB_SERVER_USERNAME,
password: process.env.DSQL_DB_PASSWORD, password: process.env.BUN_MARIADB_SERVER_PASSWORD,
// database: param?.dbFullName, database: configData?.db_name,
port: process.env.DSQL_DB_PORT port: process.env.BUN_MARIADB_SERVER_PORT
? Number(process.env.DSQL_DB_PORT) ? Number(process.env.BUN_MARIADB_SERVER_PORT)
: undefined, : undefined,
charset: "utf8mb4", charset: configData?.charset || "utf8mb4",
ssl: grabDbSSL(), ssl: grabDbSSL(),
bigIntAsNumber: true, bigIntAsNumber: true,
supportBigNumbers: true, supportBigNumbers: true,

View File

@ -4,37 +4,45 @@ type Params = {
data: any | any[]; data: any | any[];
}; };
export default async function ({ sql: passed_sql, table, data }: Params) { function quoteIdentifier(identifier: string): string {
let sql = passed_sql; return `\`${identifier.replace(/`/g, "``")}\``;
}
export default async function ({ sql: passedSql, table, data }: Params) {
const config = global.CONFIG; const config = global.CONFIG;
const dbSchema = global.DB_SCHEMA; const dbSchema = global.DB_SCHEMA;
const table_schema = dbSchema.tables.find((t) => t.tableName == table); const tableSchema = dbSchema?.tables.find(
const now = Date.now(); (t: { tableName: string }) => t.tableName == table,
);
if (table_schema?.tableName) { if (!tableSchema?.tableName) {
const set_sql_arr = Object.keys( return passedSql;
Array.isArray(data) ? data[0] : data,
).map((field) => `${field} = excluded.${field}`);
set_sql_arr.push(`updated_at = ${now}`);
const set_sql = set_sql_arr.join(", ");
const unique_fields = table_schema.fields.filter((f) => f.unique);
for (let i = 0; i < unique_fields.length; i++) {
const field = unique_fields[i];
sql += ` ON CONFLICT(${field?.fieldName}) DO UPDATE SET ${set_sql}`;
}
if (table_schema.uniqueConstraints?.[0]) {
for (let i = 0; i < table_schema.uniqueConstraints.length; i++) {
const constraint = table_schema.uniqueConstraints[i];
sql += ` ON CONFLICT(${constraint?.constraintTableFields?.map((c) => c.value)?.join(", ")}) DO UPDATE SET ${set_sql}`;
}
}
} }
return sql; const dataObject = Array.isArray(data) ? data[0] : data;
const dataKeys = Object.keys(dataObject || {});
if (dataKeys.length === 0) {
return passedSql;
}
const hasUniqueField =
tableSchema.fields.some((field) => field.unique) ||
Boolean(tableSchema.uniqueConstraints?.length);
if (!hasUniqueField) {
return passedSql;
}
const setSql = dataKeys
.filter((field: string) => field !== "updated_at")
.map(
(field: string) =>
`${quoteIdentifier(field)} = VALUES(${quoteIdentifier(field)})`,
);
setSql.push(`updated_at = ${Date.now()}`);
return `${passedSql} ON DUPLICATE KEY UPDATE ${setSql.join(", ")}`;
} }

View File

@ -1,4 +1,4 @@
import DbClient from "."; import dbHandler from "../db-handler";
import _ from "lodash"; import _ from "lodash";
import type { APIResponseObject, ServerQueryParam } from "../../types"; import type { APIResponseObject, ServerQueryParam } from "../../types";
import sqlGenerator from "../../utils/sql-generator"; import sqlGenerator from "../../utils/sql-generator";
@ -12,6 +12,10 @@ type Params<
targetId?: number | string; targetId?: number | string;
}; };
function quoteIdentifier(identifier: string): string {
return `\`${identifier.replace(/`/g, "``")}\``;
}
export default async function DbDelete< export default async function DbDelete<
Schema extends { [k: string]: any } = { [k: string]: any }, Schema extends { [k: string]: any } = { [k: string]: any },
Table extends string = string, Table extends string = string,
@ -39,30 +43,13 @@ export default async function DbDelete<
} }
sqlObj = sqlGenerator({ sqlObj = sqlGenerator({
tableName: table, tableName: quoteIdentifier(table),
genObject: finalQuery, genObject: finalQuery,
}); });
const whereClause = sqlObj.string.match(/WHERE .*/)?.[0]; const whereClause = sqlObj.string.match(/WHERE .*/)?.[0];
if (whereClause) { if (!whereClause) {
let sql = `DELETE FROM ${table} ${whereClause}`;
sqlObj.string = sql;
const res = DbClient.run(sql, sqlObj.values);
return {
success: Boolean(res.changes),
postInsertReturn: {
affectedRows: res.changes,
insertId: Number(res.lastInsertRowid),
},
debug: {
sqlObj,
},
};
} else {
return { return {
success: false, success: false,
msg: `No WHERE clause`, msg: `No WHERE clause`,
@ -71,6 +58,38 @@ export default async function DbDelete<
}, },
}; };
} }
const sql = `DELETE FROM ${quoteIdentifier(table)} ${whereClause}`;
sqlObj.string = sql;
const res = await dbHandler({
query: sql,
values: sqlObj.values as any,
});
if (!res.success) {
return {
success: false,
msg: "Database delete failed",
debug: {
sqlObj,
},
};
}
const singleRes = res.single_res as any;
return {
success: Boolean(singleRes?.affectedRows),
postInsertReturn: {
affectedRows: Number(singleRes?.affectedRows),
insertId: Number(singleRes?.insertId),
},
debug: {
sqlObj,
},
};
} catch (error: any) { } catch (error: any) {
return { return {
success: false, success: false,

View File

@ -1,4 +1,4 @@
import DbClient from "."; import dbHandler from "../db-handler";
import type { APIResponseObject, SQLInsertGenReturn } from "../../types"; import type { APIResponseObject, SQLInsertGenReturn } from "../../types";
import sqlInsertGenerator from "../../utils/sql-insert-generator"; import sqlInsertGenerator from "../../utils/sql-insert-generator";
import grabDuplicateSafeInsertSql from "../grab-duplicate-safe-insert-sql"; import grabDuplicateSafeInsertSql from "../grab-duplicate-safe-insert-sql";
@ -35,21 +35,41 @@ export default async function DbInsert<
data: finalData as any[], data: finalData as any[],
}) || null; }) || null;
let sql = sqlObj?.query || ""; if (!sqlObj) {
return {
success: false,
msg: "No insert SQL generated",
};
}
let sql = sqlObj.query;
if (update_on_duplicate && data[0]) { if (update_on_duplicate && data[0]) {
sql = await grabDuplicateSafeInsertSql({ data, table, sql }); sql = await grabDuplicateSafeInsertSql({ data, table, sql });
} }
(sqlObj || ({} as any)).query = sql; const res = await dbHandler({
query: sql,
values: sqlObj.values as any,
});
const res = DbClient.run(sql, sqlObj?.values || []); if (!res.success) {
return {
success: false,
msg: "Database insert failed",
debug: {
sqlObj,
},
};
}
const singleRes = res.single_res as any;
return { return {
success: Boolean(Number(res.lastInsertRowid)), success: Boolean(singleRes?.affectedRows || singleRes?.insertId),
postInsertReturn: { postInsertReturn: {
affectedRows: res.changes, affectedRows: Number(singleRes?.affectedRows),
insertId: Number(res.lastInsertRowid), insertId: Number(singleRes?.insertId),
}, },
debug: { debug: {
sqlObj, sqlObj,

View File

@ -59,10 +59,6 @@ class MariaDBSchemaManager {
console.log("Schema synchronization complete!"); console.log("Schema synchronization complete!");
} }
private getDatabaseName(): string | undefined {
return this.db_schema.dbName || this.db_schema.dbSlug;
}
private quoteIdentifier(identifier: string): string { private quoteIdentifier(identifier: string): string {
return `\`${identifier.replace(/`/g, "``")}\``; return `\`${identifier.replace(/`/g, "``")}\``;
} }
@ -71,7 +67,8 @@ class MariaDBSchemaManager {
where: string; where: string;
values: QueryValues; values: QueryValues;
} { } {
const databaseName = this.getDatabaseName(); const config = global.CONFIG;
const databaseName = config?.db_name;
if (databaseName) { if (databaseName) {
return { return {
@ -87,12 +84,11 @@ class MariaDBSchemaManager {
} }
private async run(query: string, values?: QueryValues): Promise<void> { private async run(query: string, values?: QueryValues): Promise<void> {
const config = global.CONFIG;
const res = await dbHandler({ const res = await dbHandler({
query, query,
values: values as any, values: values as any,
config: this.getDatabaseName() config: config?.db_config,
? { database: this.getDatabaseName() }
: undefined,
}); });
if (!res.success) { if (!res.success) {
@ -107,9 +103,7 @@ class MariaDBSchemaManager {
const res = await dbHandler<T>({ const res = await dbHandler<T>({
query, query,
values: values as any, values: values as any,
config: this.getDatabaseName() config: global.CONFIG.db_config,
? { database: this.getDatabaseName() }
: undefined,
}); });
if (!res.success) { if (!res.success) {
@ -195,7 +189,9 @@ class MariaDBSchemaManager {
for (const tableName of tablesToDrop) { for (const tableName of tablesToDrop) {
console.log(`Dropping table: ${tableName}`); console.log(`Dropping table: ${tableName}`);
await this.run(`DROP TABLE IF EXISTS ${this.quoteIdentifier(tableName)}`); await this.run(
`DROP TABLE IF EXISTS ${this.quoteIdentifier(tableName)}`,
);
await this.removeDbManagerTable(tableName); await this.removeDbManagerTable(tableName);
} }
} }
@ -308,7 +304,10 @@ class MariaDBSchemaManager {
const options = ["ENGINE=InnoDB"]; const options = ["ENGINE=InnoDB"];
if (table.collation) { if (table.collation) {
options.push("DEFAULT CHARSET=utf8mb4", `COLLATE ${table.collation}`); options.push(
"DEFAULT CHARSET=utf8mb4",
`COLLATE ${table.collation}`,
);
} }
return ` ${options.join(" ")}`; return ` ${options.join(" ")}`;
@ -321,11 +320,12 @@ class MariaDBSchemaManager {
await this.recreateTable(table); await this.recreateTable(table);
} }
private async getTableColumns( private async getTableColumns(tableName: string): Promise<ColumnInfoRow[]> {
tableName: string,
): Promise<ColumnInfoRow[]> {
const tableSchemaWhere = this.tableSchemaWhere(tableName); const tableSchemaWhere = this.tableSchemaWhere(tableName);
const rows = await this.query<{ COLUMN_NAME: string; COLUMN_TYPE: string }>( const rows = await this.query<{
COLUMN_NAME: string;
COLUMN_TYPE: string;
}>(
`SELECT COLUMN_NAME AS name, COLUMN_TYPE AS type FROM information_schema.COLUMNS WHERE ${tableSchemaWhere.where} AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION`, `SELECT COLUMN_NAME AS name, COLUMN_TYPE AS type FROM information_schema.COLUMNS WHERE ${tableSchemaWhere.where} AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION`,
tableSchemaWhere.values, tableSchemaWhere.values,
); );
@ -379,7 +379,9 @@ class MariaDBSchemaManager {
existingRows = await this.query<Record<string, any>>( existingRows = await this.query<Record<string, any>>(
`SELECT * FROM ${this.quoteIdentifier(table.tableName)}`, `SELECT * FROM ${this.quoteIdentifier(table.tableName)}`,
); );
await this.run(`DROP TABLE ${this.quoteIdentifier(table.tableName)}`); await this.run(
`DROP TABLE ${this.quoteIdentifier(table.tableName)}`,
);
} }
await this.createTable(table); await this.createTable(table);
@ -395,7 +397,9 @@ class MariaDBSchemaManager {
const existingColumns = await this.getTableColumns(table.tableName); const existingColumns = await this.getTableColumns(table.tableName);
const columnsToKeep = table.fields const columnsToKeep = table.fields
.filter((field) => .filter((field) =>
existingColumns.some((column) => column.name === field.fieldName), existingColumns.some(
(column) => column.name === field.fieldName,
),
) )
.map((field) => field.fieldName) .map((field) => field.fieldName)
.filter((fieldName): fieldName is string => Boolean(fieldName)); .filter((fieldName): fieldName is string => Boolean(fieldName));
@ -624,7 +628,9 @@ class MariaDBSchemaManager {
if (!stillExists) { if (!stillExists) {
console.log(`Dropping index: ${indexName}`); console.log(`Dropping index: ${indexName}`);
await this.run(`DROP INDEX ${this.quoteIdentifier(indexName)} ON ${this.quoteIdentifier(table.tableName)}`); await this.run(
`DROP INDEX ${this.quoteIdentifier(indexName)} ON ${this.quoteIdentifier(table.tableName)}`,
);
} }
} }

View File

@ -1,5 +1,4 @@
import mysql from "mysql"; import dbHandler from "../db-handler";
import DbClient from ".";
import _ from "lodash"; import _ from "lodash";
import type { APIResponseObject, ServerQueryParam } from "../../types"; import type { APIResponseObject, ServerQueryParam } from "../../types";
import sqlGenerator from "../../utils/sql-generator"; import sqlGenerator from "../../utils/sql-generator";
@ -14,6 +13,10 @@ type Params<
targetId?: number | string; targetId?: number | string;
}; };
function quoteIdentifier(identifier: string): string {
return `\`${identifier.replace(/`/g, "``")}\``;
}
export default async function DbSelect< export default async function DbSelect<
Schema extends { [k: string]: any } = { [k: string]: any }, Schema extends { [k: string]: any } = { [k: string]: any },
Table extends string = string, Table extends string = string,
@ -42,44 +45,75 @@ export default async function DbSelect<
} }
sqlObj = sqlGenerator({ sqlObj = sqlGenerator({
tableName: table, tableName: quoteIdentifier(table),
genObject: finalQuery, genObject: finalQuery,
}); });
let sql = mysql.format(sqlObj.string, sqlObj.values); const res = await dbHandler<Schema>({
query: sqlObj.string,
values: sqlObj.values as any,
});
const res = DbClient.query<Schema, Schema[]>(sql); if (!res.success) {
const batchRes = res.all(); return {
success: false,
msg: "Database select failed",
debug: {
sqlObj,
sql: sqlObj.string,
},
};
}
const batchRes = (res.payload || []) as Schema[];
let resp: APIResponseObject<Schema> = { let resp: APIResponseObject<Schema> = {
success: Boolean(batchRes[0]), success: true,
payload: batchRes, payload: batchRes,
singleRes: batchRes[0], singleRes: batchRes[0],
debug: { debug: {
sqlObj, sqlObj,
sql, sql: sqlObj.string,
}, },
}; };
if (count) { if (count) {
let count_sql_object = sqlGenerator({ const countSqlObject = sqlGenerator({
tableName: table, tableName: quoteIdentifier(table),
genObject: finalQuery, genObject: finalQuery,
count, count,
}); });
let count_sql = mysql.format( const countSql = `SELECT COUNT(*) AS count FROM (${countSqlObject.string}) AS c`;
count_sql_object.string,
count_sql_object.values,
);
count_sql = `SELECT COUNT(*) FROM (${count_sql}) as c`; const countRes = await dbHandler<{ count: number }>({
query: countSql,
values: countSqlObject.values as any,
});
const count_res = DbClient.query<Schema, Schema[]>(count_sql).all(); if (!countRes.success) {
return {
...resp,
success: false,
msg: "Database count failed",
debug: {
...resp.debug,
count_sql: countSql,
},
};
}
const count_val = count_res[0]?.["COUNT(*)"]; const countRows = countRes.payload || [];
resp["count"] = Number(count_val); const countVal = countRows[0]?.count ?? countRows[0]?.["COUNT(*)"];
resp["debug"]["count_sql"] = count_sql;
resp = {
...resp,
count: Number(countVal),
debug: {
...resp.debug,
count_sql: countSql,
},
};
} }
return resp; return resp;

View File

@ -1,5 +1,4 @@
import DbClient from "."; import dbHandler from "../db-handler";
import _ from "lodash";
import type { APIResponseObject, SQLInsertGenValueType } from "../../types"; import type { APIResponseObject, SQLInsertGenValueType } from "../../types";
type Params = { type Params = {
@ -11,25 +10,45 @@ export default async function DbSQL<
T extends { [k: string]: any } = { [k: string]: any }, T extends { [k: string]: any } = { [k: string]: any },
>({ sql, values }: Params): Promise<APIResponseObject<T>> { >({ sql, values }: Params): Promise<APIResponseObject<T>> {
try { try {
const trimmed_sql = sql.trim(); const trimmedSql = sql.trim();
const isSelect = trimmedSql.match(/^select/i);
const res = trimmed_sql.match(/^select/i) const res = await dbHandler<T>({
? DbClient.query(trimmed_sql).all(...(values || [])) query: trimmedSql,
: DbClient.run(trimmed_sql, values || []); values: values as any,
});
if (!res.success) {
return {
success: false,
msg: "Database query failed",
debug: {
sqlObj: {
sql: trimmedSql,
values,
},
sql,
},
};
}
const payload = isSelect ? ((res.payload || []) as T[]) : undefined;
const singleRes = isSelect ? payload?.[0] : (res.single_res as T);
const singleRaw = res.single_res as any;
return { return {
success: true, success: true,
payload: Array.isArray(res) ? (res as T[]) : undefined, payload,
singleRes: Array.isArray(res) ? (res as T[])?.[0] : undefined, singleRes,
postInsertReturn: Array.isArray(res) postInsertReturn: isSelect
? undefined ? undefined
: { : {
affectedRows: res.changes, affectedRows: Number(singleRaw?.affectedRows),
insertId: Number(res.lastInsertRowid), insertId: Number(singleRaw?.insertId),
}, },
debug: { debug: {
sqlObj: { sqlObj: {
sql: trimmed_sql, sql: trimmedSql,
values, values,
}, },
sql, sql,

View File

@ -1,4 +1,4 @@
import DbClient from "."; import dbHandler from "../db-handler";
import _ from "lodash"; import _ from "lodash";
import type { import type {
APIResponseObject, APIResponseObject,
@ -17,6 +17,10 @@ type Params<
targetId?: number | string; targetId?: number | string;
}; };
function quoteIdentifier(identifier: string): string {
return `\`${identifier.replace(/`/g, "``")}\``;
}
export default async function DbUpdate< export default async function DbUpdate<
Schema extends { [k: string]: any } = { [k: string]: any }, Schema extends { [k: string]: any } = { [k: string]: any },
Table extends string = string, Table extends string = string,
@ -45,63 +49,76 @@ export default async function DbUpdate<
} }
const sqlQueryObj = sqlGenerator({ const sqlQueryObj = sqlGenerator({
tableName: table, tableName: quoteIdentifier(table),
genObject: finalQuery, genObject: finalQuery,
}); });
let values: SQLInsertGenValueType[] = [];
const whereClause = sqlQueryObj.string.match(/WHERE .*/)?.[0]; const whereClause = sqlQueryObj.string.match(/WHERE .*/)?.[0];
if (whereClause) { if (!whereClause) {
let sql = `UPDATE ${table} SET`;
const finalData: { [k: string]: SQLInsertGenValueType } = {
updated_at: Date.now(),
...data,
};
const keys = Object.keys(finalData);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (!key) continue;
const isLast = i == keys.length - 1;
sql += ` ${key}=?`;
const value = finalData[key];
values.push(value || null);
if (!isLast) {
sql += `,`;
}
}
sql += ` ${whereClause}`;
values = [...values, ...sqlQueryObj.values];
sqlObj.string = sql;
sqlObj.values = values as any[];
const res = DbClient.run(sql, values);
return {
success: Boolean(res.changes),
postInsertReturn: {
affectedRows: res.changes,
insertId: Number(res.lastInsertRowid),
},
debug: {
sqlObj,
},
};
} else {
return { return {
success: false, success: false,
msg: `No WHERE clause`, msg: `No WHERE clause`,
}; };
} }
let values: SQLInsertGenValueType[] = [];
let sql = `UPDATE ${quoteIdentifier(table)} SET`;
const finalData: { [k: string]: SQLInsertGenValueType } = {
updated_at: Date.now(),
...data,
};
const keys = Object.keys(finalData);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (!key) continue;
const isLast = i == keys.length - 1;
sql += ` ${quoteIdentifier(key)}=?`;
values.push(finalData[key] ?? null);
if (!isLast) {
sql += `,`;
}
}
sql += ` ${whereClause}`;
values = [...values, ...sqlQueryObj.values];
sqlObj.string = sql;
sqlObj.values = values as any[];
const res = await dbHandler({
query: sql,
values: values as any,
});
if (!res.success) {
return {
success: false,
msg: "Database update failed",
debug: {
sqlObj,
},
};
}
const singleRes = res.single_res as any;
return {
success: Boolean(singleRes?.affectedRows || singleRes?.changedRows),
postInsertReturn: {
affectedRows: Number(singleRes?.affectedRows),
insertId: Number(singleRes?.insertId),
},
debug: {
sqlObj,
},
};
} catch (error: any) { } catch (error: any) {
return { return {
success: false, success: false,

View File

@ -1,7 +0,0 @@
import _ from "lodash";
import type { BUN_MARIADB_DatabaseSchemaType } from "../../types";
export const DbSchema: BUN_MARIADB_DatabaseSchemaType = {
dbName: "travis-ai",
tables: [],
};

View File

@ -28,18 +28,8 @@ export const UsersOmitedFields = [
* database metadata. * database metadata.
*/ */
export interface BUN_MARIADB_DatabaseSchemaType { export interface BUN_MARIADB_DatabaseSchemaType {
id?: string | number;
dbName?: string;
dbSlug?: string;
dbFullName?: string;
dbDescription?: string;
dbImage?: string;
tables: BUN_MARIADB_TableSchemaType[]; tables: BUN_MARIADB_TableSchemaType[];
childrenDatabases?: BUN_MARIADB_ChildrenDatabaseObject[]; collation?: MariaDbCollationsType;
childDatabase?: boolean;
childDatabaseDbId?: string | number;
updateData?: boolean;
collation?: (typeof MariaDBCollations)[number];
} }
/** /**
@ -58,6 +48,12 @@ export const MariaDBCollations = [
"utf8mb4_unicode_520_ci", "utf8mb4_unicode_520_ci",
] as const; ] as const;
export type MariaDbCollationsType = (typeof MariaDBCollations)[number];
export const MariaDBCharsets = ["utf8mb4"] as const;
export type MariaDbCharsetType = (typeof MariaDBCharsets)[number];
/** /**
* Describes a single table within a database schema, including its fields, * Describes a single table within a database schema, including its fields,
* indexes, unique constraints, and parent/child table relationships. * indexes, unique constraints, and parent/child table relationships.
@ -1498,11 +1494,6 @@ export type SQLInsertGenParams = {
*/ */
export type BunMariaDBConfig = { export type BunMariaDBConfig = {
db_name: string; db_name: string;
/**
* The Name of the Database Schema File. Eg `db_schema.ts`. This is
* relative to `db_dir`, or root dir if `db_dir` is not provided
*/
db_schema_file_name: string;
/** /**
* The Directory for backups. Relative to db_dir. * The Directory for backups. Relative to db_dir.
*/ */
@ -1518,9 +1509,17 @@ export type BunMariaDBConfig = {
*/ */
typedef_file_path?: string; typedef_file_path?: string;
/** /**
* Whether to enable `WAL` mode * Configuration for the MariaDB Connection
*/ */
wal_mode?: boolean; db_config?: ConnectionConfig;
/**
* Database charset. Defaults to `utf8mb4`
*/
charset?: MariaDbCharsetType;
/**
* Database Connection timeout. Defaults to `10000`
*/
connection_timeout?: number;
}; };
/** /**
@ -1589,3 +1588,11 @@ export type DBResponseObject<
payload?: T[]; payload?: T[];
single_res?: T; single_res?: T;
}; };
export const RequiredENVs = [
"BUN_MARIADB_SERVER_HOST",
"BUN_MARIADB_SERVER_PORT",
"BUN_MARIADB_SERVER_USERNAME",
"BUN_MARIADB_SERVER_PASSWORD",
"BUN_MARIADB_SERVER_SSL_KEY_PATH",
] as const;

View File

@ -4,6 +4,10 @@ import type {
SQLInsertGenValueType, SQLInsertGenValueType,
} from "../types"; } from "../types";
function quoteIdentifier(identifier: string): string {
return `\`${identifier.replace(/`/g, "``")}\``;
}
/** /**
* # SQL Insert Generator * # SQL Insert Generator
*/ */
@ -12,7 +16,7 @@ export default function sqlInsertGenerator({
data, data,
dbFullName, dbFullName,
}: SQLInsertGenParams): SQLInsertGenReturn | undefined { }: SQLInsertGenParams): SQLInsertGenReturn | undefined {
const finalDbName = dbFullName ? `${dbFullName}.` : ""; const finalDbName = dbFullName ? `${quoteIdentifier(dbFullName)}.` : "";
try { try {
if (Array.isArray(data) && data?.[0]) { if (Array.isArray(data) && data?.[0]) {
@ -64,9 +68,9 @@ export default function sqlInsertGenerator({
.join(",")})`, .join(",")})`,
); );
}); });
let query = `INSERT INTO ${finalDbName}${tableName} (${insertKeys.join(
",", const insertColumns = insertKeys.map(quoteIdentifier).join(",");
)}) VALUES ${queryBatches.join(",")}`; let query = `INSERT INTO ${finalDbName}${quoteIdentifier(tableName)} (${insertColumns}) VALUES ${queryBatches.join(",")}`;
return { return {
query: query, query: query,