@starbemtech/star-db-query-builder
Version:
A query builder to be used with mysql or postgres
677 lines • 25.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.beginTransaction = exports.withTransaction = exports.rawQuery = exports.joins = exports.deleteMany = exports.deleteOne = exports.updateMany = exports.update = exports.insertMany = exports.insert = exports.findMany = exports.findFirst = void 0;
const uuid_1 = require("uuid");
const utils_1 = require("./utils");
/**
* Finds the first record in the specified table
*
* This function retrieves the first record from the specified table based on the provided
* query parameters. It constructs the SQL query, executes it, and returns the result.
*
* @template T - The type of the record to be returned
* @param params - Query parameters including table name, database client, select fields, where conditions, group by, order by, and limit
* @returns Promise<T | null> - The first record or null if no record is found
*
* @throws {Error} When table name is not provided
* @throws {Error} When database client is not provided
*
* @example
* const firstRecord = await findFirst({
* tableName: 'users',
* dbClient: dbClient,
* select: ['id', 'name', 'email'],
* where: { status: 'active' },
* groupBy: ['status'],
* orderBy: [{ field: 'created_at', direction: 'DESC' }],
* })
*
* @example
* const firstRecord = await findFirst({
* tableName: 'users',
* dbClient: dbClient,
* select: ['id', 'name', 'email'],
* where: { status: 'active' },
* groupBy: ['status'],
* orderBy: [{ field: 'created_at', direction: 'DESC' }],
* })
*/
const findFirst = async ({ tableName, dbClient, select, where, groupBy, orderBy, }) => {
if (!tableName)
throw new Error('Table name is required');
if (!dbClient)
throw new Error('DB client is required');
const fields = (0, utils_1.createSelectFields)(select, dbClient.clientType);
const [whereClause, params] = (0, utils_1.createWhereClause)(where, 1, dbClient.clientType);
const orderByClause = (0, utils_1.createOrderByClause)(orderBy);
const groupByClause = (0, utils_1.createGroupByClause)(groupBy);
const rows = await dbClient.query(`SELECT ${fields} FROM ${tableName}
${whereClause.length > 7 ? whereClause : ''}
${groupByClause}
${orderByClause}
`, params);
return rows[0] || null;
};
exports.findFirst = findFirst;
/**
* Finds multiple records in the specified table
*
* This function retrieves multiple records from the specified table based on the provided
* query parameters. It constructs the SQL query, executes it, and returns the result.
*
* @template T - The type of the records to be returned
* @param params - Query parameters including table name, database client, select fields, where conditions, group by, order by, limit, and offset
* @returns Promise<T[]> - The records found in the table
*
* @throws {Error} When table name is not provided
* @throws {Error} When database client is not provided
*
* @example
* const records = await findMany({
* tableName: 'users',
* dbClient: dbClient,
* select: ['id', 'name', 'email'],
* where: { status: 'active' },
* groupBy: ['status'],
* orderBy: [{ field: 'created_at', direction: 'DESC' }],
* limit: 10,
* offset: 0,
* unaccent: true,
* })
*/
const findMany = async ({ tableName, dbClient, select, where, groupBy, orderBy, limit, offset, unaccent, }) => {
if (!tableName)
throw new Error('Table name is required');
if (!dbClient)
throw new Error('DB client is required');
const fields = (0, utils_1.createSelectFields)(select, dbClient.clientType);
const [whereClause, params] = (0, utils_1.createWhereClause)(where, 1, dbClient.clientType, unaccent);
const orderByClause = (0, utils_1.createOrderByClause)(orderBy);
const groupByClause = (0, utils_1.createGroupByClause)(groupBy);
const limitClause = (0, utils_1.createLimitClause)(limit);
const offsetClause = (0, utils_1.createOffsetClause)(offset);
const rows = await dbClient.query(`SELECT ${fields} FROM ${tableName}
${whereClause.length > 7 ? whereClause : ''}
${groupByClause}
${orderByClause}
${limitClause}
${offsetClause}
`, params);
return rows || [];
};
exports.findMany = findMany;
/**
* Inserts a new record into the specified table
*
* This function inserts a new record into the specified table based on the provided
* query parameters. It constructs the SQL query, executes it, and returns the result.
*
* @template P - The type of the data to be inserted
* @template R - The type of the record to be returned
* @param params - Query parameters including table name, database client, data to be inserted, and optional returning fields
* @returns Promise<R> - The inserted record
*
* @throws {Error} When table name is not provided
* @throws {Error} When database client is not provided
* @throws {Error} When data object is not provided
*
* @example
* const insertedRecord = await insert({
* tableName: 'users',
* dbClient: dbClient,
* data: { name: 'John Doe', email: 'john.doe@example.com' },
* returning: ['id', 'name', 'email'],
* })
*/
const insert = async ({ tableName, dbClient, data, returning, }) => {
if (!tableName)
throw new Error('Table name is required');
if (!dbClient)
throw new Error('DB client is required');
if (!data)
throw new Error('Data object is required');
const keys = dbClient.clientType === 'pg'
? Object.keys(data).map((key) => key === 'authorization' ? `"${key}"` : key)
: Object.keys(data);
const values = Object.values(data);
keys.unshift('id');
const generatedUUID = (0, uuid_1.v4)();
values.unshift(generatedUUID);
keys.push('updated_at');
values.push(new Date());
const placeholders = (0, utils_1.generatePlaceholders)(keys, dbClient.clientType);
let query = `INSERT INTO ${tableName} (${keys.join(', ')}) VALUES (${placeholders})`;
if (dbClient.clientType === 'pg') {
if (returning && returning.length > 0) {
query += ` RETURNING ${(0, utils_1.createSelectFields)(returning, dbClient.clientType)}`;
}
}
const inserted = await dbClient.query(query, values);
if (dbClient.clientType === 'mysql') {
const rows = await dbClient.query(`SELECT ${returning && returning.length > 0
? (0, utils_1.createSelectFields)(returning, dbClient.clientType)
: '*'} FROM ${tableName}
WHERE
id = ?
`, [generatedUUID]);
return rows[0];
}
return inserted[0];
};
exports.insert = insert;
/**
* Inserts multiple records into the specified table
*
* This function inserts multiple records into the specified table based on the provided
* query parameters. It constructs the SQL query, executes it, and returns the result.
*
* @template P - The type of the data to be inserted
* @template R - The type of the record to be returned
* @param params - Query parameters including table name, database client, data to be inserted, and optional returning fields
* @returns Promise<R[]> - The inserted records
*
* @throws {Error} When table name is not provided
* @throws {Error} When database client is not provided
* @throws {Error} When data array is not provided or empty
*
* @example
* const insertedRecords = await insertMany({
* tableName: 'users',
* dbClient: dbClient,
* data: [{ name: 'John Doe', email: 'john.doe@example.com' }, { name: 'Jane Doe', email: 'jane.doe@example.com' }],
* returning: ['id', 'name', 'email'],
* })
*/
const insertMany = async ({ tableName, dbClient, data, returning, }) => {
if (!tableName)
throw new Error('Table name is required');
if (!dbClient)
throw new Error('DB client is required');
if (!data || data.length === 0)
throw new Error('Data array is required and cannot be empty');
const firstItem = data[0];
const keys = dbClient.clientType === 'pg'
? Object.keys(firstItem).map((key) => key === 'authorization' ? `"${key}"` : key)
: Object.keys(firstItem);
const allKeys = ['id', ...keys, 'updated_at'];
let query = `INSERT INTO ${tableName} (${allKeys.join(', ')}) VALUES `;
const allValues = [];
const valueRows = [];
const generatedIds = [];
data.forEach((item, rowIndex) => {
const values = Object.values(item);
const generatedUUID = (0, uuid_1.v4)();
generatedIds.push(generatedUUID);
const currentValues = [generatedUUID, ...values, new Date()];
allValues.push(...currentValues);
// Generate unique placeholders for each row
if (dbClient.clientType === 'pg') {
const startIndex = rowIndex * allKeys.length + 1;
const placeholders = allKeys
.map((_, index) => `$${startIndex + index}`)
.join(', ');
valueRows.push(`(${placeholders})`);
}
else {
const placeholders = allKeys.map(() => '?').join(', ');
valueRows.push(`(${placeholders})`);
}
});
query += valueRows.join(', ');
if (dbClient.clientType === 'pg') {
if (returning && returning.length > 0) {
query += ` RETURNING ${(0, utils_1.createSelectFields)(returning, dbClient.clientType)}`;
}
}
const inserted = await dbClient.query(query, allValues);
if (dbClient.clientType === 'mysql') {
const placeholders = generatedIds.map(() => '?').join(', ');
const rows = await dbClient.query(`SELECT ${returning && returning.length > 0
? (0, utils_1.createSelectFields)(returning, dbClient.clientType)
: '*'} FROM ${tableName}
WHERE id IN (${placeholders})
ORDER BY FIELD(id, ${placeholders})
`, [...generatedIds, ...generatedIds]);
return rows;
}
return inserted;
};
exports.insertMany = insertMany;
/**
* Updates a record in the specified table
*
* This function updates a record in the specified table based on the provided
* query parameters. It constructs the SQL query, executes it, and returns the result.
*
* @template P - The type of the data to be updated
* @template R - The type of the record to be returned
* @param params - Query parameters including table name, database client, ID of the record to be updated, data to be updated, and optional returning fields
* @returns Promise<R | void> - The updated record or void if no record is found
*
* @throws {Error} When table name is not provided
* @throws {Error} When database client is not provided
* @throws {Error} When ID is not provided
*
* @example
* const updatedRecord = await update({
* tableName: 'users',
* dbClient: dbClient,
* id: '123',
* data: { name: 'John Doe', email: 'john.doe@example.com' },
* returning: ['id', 'name', 'email'],
* })
*/
const update = async ({ tableName, dbClient, id, data, returning, }) => {
if (!tableName)
throw new Error('Table name is required');
if (!dbClient)
throw new Error('DB client is required');
if (!id)
throw new Error('ID is required');
if (!data)
throw new Error('Data object is required');
const keys = Object.keys(data);
const values = Object.values(data);
const setClause = (0, utils_1.generateSetClause)(keys, dbClient.clientType);
let query = `UPDATE ${tableName} SET ${setClause} WHERE id = '${id}'`;
if (dbClient.clientType === 'pg') {
if (returning && returning.length > 0) {
query += ` RETURNING ${(0, utils_1.createSelectFields)(returning, dbClient.clientType)}`;
}
}
const updated = await dbClient.query(query, values);
if (dbClient.clientType === 'mysql') {
const rows = await dbClient.query(`SELECT ${returning && returning.length > 0
? (0, utils_1.createSelectFields)(returning, dbClient.clientType)
: '*'} FROM ${tableName}
WHERE
id = ?
`, [id]);
return rows[0];
}
return updated[0];
};
exports.update = update;
/**
* Updates multiple records in the specified table
*
* This function updates multiple records in the specified table based on the provided
* query parameters. It constructs the SQL query, executes it, and returns the result.
*
* @template P - The type of the data to be updated
* @template R - The type of the record to be returned
* @param params - Query parameters including table name, database client, data to be updated, where conditions, and optional returning fields
* @returns Promise<R[]> - The updated records
*
* @throws {Error} When table name is not provided
* @throws {Error} When database client is not provided
* @throws {Error} When data object is not provided
* @throws {Error} When where condition is not provided
*
* @example
* const updatedRecords = await updateMany({
* tableName: 'users',
* dbClient: dbClient,
* data: { name: 'John Doe', email: 'john.doe@example.com' },
* where: { status: 'active' },
* returning: ['id', 'name', 'email'],
* })
*/
const updateMany = async ({ tableName, dbClient, data, where, returning, }) => {
if (!tableName)
throw new Error('Table name is required');
if (!dbClient)
throw new Error('DB client is required');
if (!data)
throw new Error('Data object is required');
if (!where)
throw new Error('Where condition is required');
const keys = Object.keys(data);
const values = Object.values(data);
// Generate SET clause with correct placeholders
const setClause = keys
.map((key, index) => dbClient.clientType === 'pg' ? `${key} = $${index + 1}` : `${key} = ?`)
.join(', ');
// Generate WHERE clause with placeholders starting after SET values
const [whereClause, whereParams] = (0, utils_1.createWhereClause)(where, values.length + 1, dbClient.clientType);
let query = `UPDATE ${tableName} SET ${setClause} ${whereClause}`;
if (dbClient.clientType === 'pg') {
if (returning && returning.length > 0) {
query += ` RETURNING ${(0, utils_1.createSelectFields)(returning, dbClient.clientType)}`;
}
}
const updated = await dbClient.query(query, [...values, ...whereParams]);
if (dbClient.clientType === 'mysql') {
// For MySQL, we need to fetch the updated records separately
// since MySQL doesn't support RETURNING clause
const rows = await dbClient.query(`SELECT ${returning && returning.length > 0
? (0, utils_1.createSelectFields)(returning, dbClient.clientType)
: '*'} FROM ${tableName}
${whereClause}
`, whereParams);
return rows;
}
return updated;
};
exports.updateMany = updateMany;
/**
* Deletes a record from the specified table
*
* This function deletes a record from the specified table based on the provided
* query parameters. It constructs the SQL query, executes it, and returns the result.
*
* @template T - The type of the record to be deleted
* @param params - Query parameters including table name, database client, ID of the record to be deleted, and optional permanently flag
* @returns Promise<void> - Resolves when the record is deleted
*
* @throws {Error} When table name is not provided
* @throws {Error} When database client is not provided
* @throws {Error} When ID is not provided
*
* @example
* await deleteOne({
* tableName: 'users',
* dbClient: dbClient,
* id: '123',
* permanently: true,
* })
*/
const deleteOne = async ({ tableName, dbClient, id, permanently = false, }) => {
if (!tableName)
throw new Error('Table name is required');
if (!dbClient)
throw new Error('DB client is required');
if (!id)
throw new Error('ID is required');
await dbClient.query(permanently
? `DELETE FROM ${tableName} WHERE id = ${dbClient.clientType === 'pg' ? '$1' : '?'}`
: `UPDATE ${tableName} SET status = 'deleted' WHERE id = ${dbClient.clientType === 'pg' ? '$1' : '?'}`, [id]);
};
exports.deleteOne = deleteOne;
/**
* Deletes multiple records from the specified table
*
* This function deletes multiple records from the specified table based on the provided
* query parameters. It constructs the SQL query, executes it, and returns the result.
*
* @template T - The type of the record to be deleted
* @param params - Query parameters including table name, database client, IDs of the records to be deleted, field to be used for deletion, and optional permanently flag
* @returns Promise<void> - Resolves when the records are deleted
*
* @throws {Error} When table name is not provided
* @throws {Error} When database client is not provided
* @throws {Error} When IDs are not provided or empty
* @throws {Error} When field is not provided
*
* @example
* await deleteMany({
* tableName: 'users',
* dbClient: dbClient,
* ids: ['123', '456', '789'],
* field: 'id',
* permanently: true,
* })
*/
const deleteMany = async ({ tableName, dbClient, ids, field = 'id', permanently = false, }) => {
if (!tableName)
throw new Error('Table name is required');
if (!dbClient)
throw new Error('DB client is required');
if (!ids || ids.length === 0)
throw new Error('IDs are required and cannot be empty');
if (!field)
throw new Error('Field is required');
const placeholders = dbClient.clientType === 'pg'
? ids.map((_, index) => `$${index + 1}`).join(', ')
: ids.map(() => '?').join(', ');
const query = permanently
? `DELETE FROM ${tableName} WHERE ${field} IN (${placeholders})`
: `UPDATE ${tableName} SET status = 'deleted' WHERE ${field} IN (${placeholders})`;
await dbClient.query(query, ids);
};
exports.deleteMany = deleteMany;
/**
* Joins multiple tables in the specified table
*
* This function joins multiple tables in the specified table based on the provided
* query parameters. It constructs the SQL query, executes it, and returns the result.
*
* @template T - The type of the record to be joined
* @param params - Query parameters including table name, database client, select fields, joins, where conditions, group by, order by, limit, and offset
* @returns Promise<T[]> - The records found in the joined tables
*
* @throws {Error} When table name is not provided
* @throws {Error} When database client is not provided
* @throws {Error} When select fields are not provided
* @throws {Error} When joins are not provided
*
* @example
* const records = await joins({
* tableName: 'users',
* dbClient: dbClient,
* select: ['id', 'name', 'email'],
* joins: [{ type: 'INNER', table: 'orders', on: 'users.id = orders.user_id' }],
* where: { status: 'active' },
* groupBy: ['status'],
* orderBy: [{ field: 'created_at', direction: 'DESC' }],
* limit: 10,
* offset: 0,
* unaccent: true,
* })
*/
const joins = async ({ tableName, dbClient, select, joins, where, groupBy, orderBy, limit, offset, unaccent, }) => {
if (!tableName)
throw new Error('Table name is required');
if (!dbClient)
throw new Error('DB client is required');
const fields = Array.isArray(select) ? select : [];
const selectFields = (0, utils_1.createSelectFields)(fields, dbClient.clientType);
const [whereClause, params] = (0, utils_1.createWhereClause)(where, 1, dbClient.clientType, unaccent);
const groupByClause = (0, utils_1.createGroupByClause)(groupBy);
const orderByClause = (0, utils_1.createOrderByClause)(orderBy);
const limitClause = (0, utils_1.createLimitClause)(limit);
const offsetClause = (0, utils_1.createOffsetClause)(offset);
const queryBuilder = {
select: [selectFields],
from: tableName,
joins: joins,
where: whereClause,
groupBy: [groupByClause],
orderBy: orderByClause,
limit: limitClause,
offset: offsetClause,
};
const queryString = await buildQuery(queryBuilder);
const rows = await dbClient.query(queryString, params);
return rows;
};
exports.joins = joins;
/**
* Executa uma query SQL raw diretamente no banco de dados
* @param params - Parâmetros da query raw
* @returns Promise com o resultado da query
*
* @example
* // Query simples sem parâmetros
* const users = await rawQuery({
* dbClient,
* sql: 'SELECT * FROM users WHERE active = true'
* })
*
* @example
* // Query com parâmetros
* const user = await rawQuery({
* dbClient,
* sql: 'SELECT * FROM users WHERE id = ? AND email = ?',
* params: ['user-id', 'user@example.com']
* })
*
* @example
* // Query de agregação
* const stats = await rawQuery({
* dbClient,
* sql: `
* SELECT
* COUNT(*) as total_users,
* AVG(age) as avg_age,
* MAX(created_at) as last_created
* FROM users
* WHERE created_at >= ?
* `,
* params: [new Date('2023-01-01')]
* })
*/
const rawQuery = async ({ dbClient, sql, params = [], }) => {
if (!dbClient)
throw new Error('DB client is required');
if (!sql || typeof sql !== 'string')
throw new Error('SQL query is required and must be a string');
try {
const result = await dbClient.query(sql, params);
return result;
}
catch (error) {
throw new Error(`Raw query execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
};
exports.rawQuery = rawQuery;
/**
* Builds a query string from the provided query parameters
*
* This function builds a query string from the provided query parameters. It constructs the SQL query, executes it, and returns the result.
*
* @param params - Query parameters including select fields, from table, joins, where conditions, group by, order by, limit, and offset
* @returns Promise<string> - The query string
*
* @example
* const queryString = await buildQuery({
* select: ['id', 'name', 'email'],
* from: 'users',
* joins: [{ type: 'INNER', table: 'orders', on: 'users.id = orders.user_id' }],
* where: { status: 'active' },
* groupBy: ['status'],
* orderBy: [{ field: 'created_at', direction: 'DESC' }],
* limit: 10,
* offset: 0,
* })
*/
async function buildQuery(params) {
let queryString = `SELECT ${params.select.join(', ')} FROM ${params.from}`;
if (params.joins) {
for (const join of params.joins) {
queryString += ` ${join.type} JOIN ${join.table} ON ${join.on}`;
}
}
if (params.where) {
queryString += `${params.where}`;
}
if (params.groupBy) {
queryString += `${params.groupBy}`;
}
if (params.orderBy) {
queryString += `${params.orderBy}`;
}
if (params.limit) {
queryString += `${params.limit}`;
}
if (params.offset) {
queryString += `${params.offset}`;
}
return queryString;
}
/**
* Executes a function within a database transaction
* @param dbClient - Database client instance
* @param transactionFn - Function to execute within the transaction
* @returns Promise with the result of the transaction function
*
* @example
* // Simple transaction
* const result = await withTransaction(dbClient, async (tx) => {
* const user = await insert({
* tableName: 'users',
* dbClient: tx,
* data: { name: 'John', email: 'john@example.com' }
* })
*
* await insert({
* tableName: 'user_profiles',
* dbClient: tx,
* data: { user_id: user.id, bio: 'Hello world' }
* })
*
* return user
* })
*
* @example
* // Transaction with error handling
* try {
* const result = await withTransaction(dbClient, async (tx) => {
* // Multiple operations that must succeed or fail together
* const order = await insert({
* tableName: 'orders',
* dbClient: tx,
* data: { user_id: 'user-123', total: 100 }
* })
*
* await update({
* tableName: 'users',
* dbClient: tx,
* id: 'user-123',
* data: { last_order_id: order.id }
* })
*
* return order
* })
* } catch (error) {
* // Transaction was automatically rolled back
* console.error('Transaction failed:', error)
* }
*/
const withTransaction = async (dbClient, transactionFn) => {
const transaction = await dbClient.beginTransaction();
try {
const result = await transactionFn(transaction);
await transaction.commit();
return result;
}
catch (error) {
await transaction.rollback();
throw error;
}
};
exports.withTransaction = withTransaction;
/**
* Creates a transaction client for manual transaction management
* @param dbClient - Database client instance
* @returns Promise with the transaction client
*
* @example
* // Manual transaction management
* const transaction = await beginTransaction(dbClient)
*
* try {
* const user = await insert({
* tableName: 'users',
* dbClient: transaction,
* data: { name: 'John', email: 'john@example.com' }
* })
*
* await insert({
* tableName: 'user_profiles',
* dbClient: transaction,
* data: { user_id: user.id, bio: 'Hello world' }
* })
*
* await transaction.commit()
* return user
* } catch (error) {
* await transaction.rollback()
* throw error
* }
*/
const beginTransaction = async (dbClient) => {
return dbClient.beginTransaction();
};
exports.beginTransaction = beginTransaction;
//# sourceMappingURL=repository.js.map