bun-sqlite-orm
Version:
A lightweight TypeScript ORM for Bun runtime with Bun SQLite, featuring Active Record pattern and decorator-based entities
122 lines (97 loc) • 4.46 kB
text/typescript
import { injectable } from 'tsyringe';
import type { ColumnMetadata, SQLQueryBindings } from '../types';
import { buildInsertClause, buildSetClause, buildWhereClause } from './query-utils';
()
export class QueryBuilder {
createTable(tableName: string, columns: Map<string, ColumnMetadata>): string {
const columnDefinitions: string[] = [];
for (const [propertyName, metadata] of columns) {
// Map JSON column type to TEXT for SQLite storage
const sqlType = metadata.type === 'json' ? 'TEXT' : metadata.type.toUpperCase();
let definition = `${propertyName} ${sqlType}`;
if (metadata.isPrimary && metadata.generationStrategy === 'increment') {
definition += ' PRIMARY KEY AUTOINCREMENT';
} else if (metadata.isPrimary) {
definition += ' PRIMARY KEY';
}
if (!metadata.nullable && !metadata.isPrimary) {
definition += ' NOT NULL';
}
if (metadata.unique && !metadata.isPrimary) {
definition += ' UNIQUE';
}
if (metadata.default !== undefined && typeof metadata.default === 'string') {
definition += ` DEFAULT '${metadata.default}'`;
} else if (metadata.default !== undefined && typeof metadata.default === 'number') {
definition += ` DEFAULT ${metadata.default}`;
}
columnDefinitions.push(definition);
}
return `CREATE TABLE ${tableName} (${columnDefinitions.join(', ')})`;
}
insert(tableName: string, data: Record<string, SQLQueryBindings>): { sql: string; params: SQLQueryBindings[] } {
// Prevent generating invalid SQL with empty data
if (!data || Object.keys(data).length === 0) {
throw new Error('Cannot perform INSERT with empty data: at least one column value must be provided');
}
const { columns, placeholders, params } = buildInsertClause(data);
return {
sql: `INSERT INTO ${tableName} (${columns}) VALUES (${placeholders})`,
params,
};
}
select(
tableName: string,
conditions?: Record<string, SQLQueryBindings>,
limit?: number
): { sql: string; params: SQLQueryBindings[] } {
let sql = `SELECT * FROM ${tableName}`;
const { whereClause, params } = buildWhereClause(conditions || {});
sql += whereClause;
if (limit) {
sql += ` LIMIT ${limit}`;
}
return { sql, params };
}
update(
tableName: string,
data: Record<string, SQLQueryBindings>,
conditions: Record<string, SQLQueryBindings>,
allowBulkUpdate = false
): { sql: string; params: SQLQueryBindings[] } {
// Prevent accidental full-table updates unless explicitly allowed
if (!allowBulkUpdate && (!conditions || Object.keys(conditions).length === 0)) {
throw new Error('Cannot perform UPDATE without WHERE conditions: this would update all rows in the table');
}
const { setClause, params: setParams } = buildSetClause(data);
const { whereClause, params: whereParams } = buildWhereClause(conditions || {});
return {
sql: `UPDATE ${tableName} SET ${setClause}${whereClause}`,
params: [...setParams, ...whereParams],
};
}
delete(
tableName: string,
conditions: Record<string, SQLQueryBindings>,
allowBulkDelete = false
): { sql: string; params: SQLQueryBindings[] } {
// Prevent accidental full-table deletions unless explicitly allowed
if (!allowBulkDelete && (!conditions || Object.keys(conditions).length === 0)) {
throw new Error('Cannot perform DELETE without WHERE conditions: this would delete all rows in the table');
}
const { whereClause, params } = buildWhereClause(conditions || {});
return {
sql: `DELETE FROM ${tableName}${whereClause}`,
params,
};
}
count(
tableName: string,
conditions?: Record<string, SQLQueryBindings>
): { sql: string; params: SQLQueryBindings[] } {
let sql = `SELECT COUNT(*) as count FROM ${tableName}`;
const { whereClause, params } = buildWhereClause(conditions || {});
sql += whereClause;
return { sql, params };
}
}