flint-orm
Version:
Type-safe SQLite ORM for JavaScript
1,242 lines (1,223 loc) • 45.6 kB
JavaScript
// @bun
var __create = Object.create;
var __getProtoOf = Object.getPrototypeOf;
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
function __accessProp(key) {
return this[key];
}
var __toESMCache_node;
var __toESMCache_esm;
var __toESM = (mod, isNodeMode, target) => {
var canCache = mod != null && typeof mod === "object";
if (canCache) {
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
var cached = cache.get(mod);
if (cached)
return cached;
}
target = mod != null ? __create(__getProtoOf(mod)) : {};
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
for (let key of __getOwnPropNames(mod))
if (!__hasOwnProp.call(to, key))
__defProp(to, key, {
get: __accessProp.bind(mod, key),
enumerable: true
});
if (canCache)
cache.set(mod, to);
return to;
};
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
var __returnValue = (v) => v;
function __exportSetter(name, newValue) {
this[name] = __returnValue.bind(null, newValue);
}
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, {
get: all[name],
enumerable: true,
configurable: true,
set: __exportSetter.bind(all, name)
});
};
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
var __require = import.meta.require;
// src/query/conditions.ts
function isColumnDef(value) {
return value !== null && typeof value === "object" && "__internal" in value;
}
function eq(left, valueOrColumn) {
if (isColumnDef(valueOrColumn)) {
return { type: "eqColumn", left, right: valueOrColumn };
}
return { type: "eq", column: left, value: valueOrColumn };
}
function and(...conditions) {
return { type: "and", conditions };
}
function or(...conditions) {
return { type: "or", conditions };
}
function isIn(column, values) {
return { type: "in", column, values };
}
function isNotIn(column, values) {
return { type: "notIn", column, values };
}
function isNull(column) {
return { type: "isNull", column };
}
function isNotNull(column) {
return { type: "isNotNull", column };
}
function like(column, pattern) {
return { type: "like", column, pattern };
}
function glob(column, pattern) {
return { type: "glob", column, pattern };
}
function between(column, low, high) {
return { type: "between", column, low, high };
}
function gt(column, value) {
return { type: "gt", column, value };
}
function gte(column, value) {
return { type: "gte", column, value };
}
function lt(column, value) {
return { type: "lt", column, value };
}
function lte(column, value) {
return { type: "lte", column, value };
}
function neq(column, value) {
return { type: "neq", column, value };
}
function compileCondition(cond, params) {
switch (cond.type) {
case "eq":
params.push(cond.column.__internal.encode(cond.value));
return `${cond.column.name} = ?`;
case "eqColumn": {
const leftName = cond.left.__internal.tableName ? `${cond.left.__internal.tableName}.${cond.left.name}` : cond.left.name;
const rightName = cond.right.__internal.tableName ? `${cond.right.__internal.tableName}.${cond.right.name}` : cond.right.name;
return `${leftName} = ${rightName}`;
}
case "in": {
const encoded = cond.values.map((v) => cond.column.__internal.encode(v));
params.push(...encoded);
const placeholders = encoded.map(() => "?").join(", ");
return `${cond.column.name} IN (${placeholders})`;
}
case "notIn": {
const encoded = cond.values.map((v) => cond.column.__internal.encode(v));
params.push(...encoded);
const placeholders = encoded.map(() => "?").join(", ");
return `${cond.column.name} NOT IN (${placeholders})`;
}
case "isNull":
return `${cond.column.name} IS NULL`;
case "isNotNull":
return `${cond.column.name} IS NOT NULL`;
case "like":
params.push(cond.pattern);
return `${cond.column.name} LIKE ?`;
case "glob":
params.push(cond.pattern);
return `${cond.column.name} GLOB ?`;
case "between":
params.push(cond.column.__internal.encode(cond.low));
params.push(cond.column.__internal.encode(cond.high));
return `${cond.column.name} BETWEEN ? AND ?`;
case "gt":
params.push(cond.column.__internal.encode(cond.value));
return `${cond.column.name} > ?`;
case "gte":
params.push(cond.column.__internal.encode(cond.value));
return `${cond.column.name} >= ?`;
case "lt":
params.push(cond.column.__internal.encode(cond.value));
return `${cond.column.name} < ?`;
case "lte":
params.push(cond.column.__internal.encode(cond.value));
return `${cond.column.name} <= ?`;
case "neq":
params.push(cond.column.__internal.encode(cond.value));
return `${cond.column.name} != ?`;
case "and":
return cond.conditions.map((c) => compileCondition(c, params)).join(" AND ");
case "or":
return `(${cond.conditions.map((c) => compileCondition(c, params)).join(" OR ")})`;
}
}
function compileConditions(conditions, params) {
if (conditions.length === 0)
return "1=1";
return conditions.map((c) => compileCondition(c, params)).join(" AND ");
}
// src/errors.ts
var FlintError, FlintValidationError, FlintQueryError;
var init_errors = __esm(() => {
FlintError = class FlintError extends Error {
constructor(message) {
super(message);
this.name = "FlintError";
}
};
FlintValidationError = class FlintValidationError extends FlintError {
constructor(message) {
super(message);
this.name = "FlintValidationError";
}
};
FlintQueryError = class FlintQueryError extends FlintError {
originalError;
constructor(message, originalError) {
super(message);
this.name = "FlintQueryError";
this.originalError = originalError;
}
};
});
// src/query/builder.ts
function getCol(tbl, key) {
const col = tbl[key];
if (!col)
throw new FlintValidationError(`Column "${key}" not found in table`);
return col;
}
function columnEntries(tbl) {
return Object.entries(tbl).filter(([k]) => k !== "_" && k !== "__indexes");
}
function decodeRow(raw, tbl) {
const out = {};
for (const [key, col] of columnEntries(tbl)) {
out[key] = col.__internal.decode(raw[col.name]);
}
return out;
}
function decodeSelectedRow(raw, tbl, keys) {
const out = {};
for (const key of keys) {
const col = getCol(tbl, key);
out[key] = col.__internal.decode(raw[col.name]);
}
return out;
}
function findPKKeys(tbl) {
const keys = [];
for (const [key, col] of columnEntries(tbl)) {
if (col.__internal.isPrimaryKey)
keys.push(key);
}
if (keys.length === 0) {
throw new FlintValidationError("Table has no primary key column");
}
return keys;
}
function resolveForeignKeyCondition(parent, parentName, child, childName) {
for (const [, col] of columnEntries(child)) {
if (col.__internal.referencesTable === parentName && col.__internal.referencesColumn) {
const parentCol = getCol(parent, col.__internal.referencesColumn);
if (parentCol) {
return eq(parentCol, col);
}
}
}
throw new FlintValidationError(`No foreign key reference found from "${childName}" to "${parentName}". Use .references() on the child table or provide an explicit condition.`);
}
function extractColumns(cond) {
switch (cond.type) {
case "eq":
return [cond.column];
case "eqColumn":
return [cond.left, cond.right];
case "in":
case "notIn":
case "isNull":
case "isNotNull":
case "like":
case "glob":
case "between":
return [cond.column];
case "and":
case "or":
return cond.conditions.flatMap(extractColumns);
default:
return [];
}
}
function validateColumnOwnership(conditions, allowedTables, context) {
const allowedColumns = new Set(allowedTables.flatMap((t) => columnEntries(t).map(([, c]) => c)));
for (const cond of conditions) {
const cols = extractColumns(cond);
for (const col of cols) {
if (!allowedColumns.has(col)) {
throw new FlintValidationError(`Column "${col.name}" does not belong to ${context}. ` + `Check that you're using a column from the queried table, not a different table.`);
}
}
}
}
function resolveColumns(table, selectedColumns, prefix) {
if (selectedColumns) {
return selectedColumns.map((k) => {
const name = getCol(table, k).name;
return prefix ? `${prefix}.${name}` : name;
}).join(", ");
}
const entries = columnEntries(table);
return entries.map(([, c]) => prefix ? `${prefix}.${c.name}` : c.name).join(", ");
}
class SelectBuilder {
#executor;
#tableName;
#table;
#conditions;
#selectedColumns;
#orderByClauses;
#limitValue;
#offsetValue;
#distinct;
constructor(executor, tableName, table, conditions = [], selectedColumns = null, orderByClauses = [], limitValue = null, offsetValue = null, distinct = false) {
this.#executor = executor;
this.#tableName = tableName;
this.#table = table;
this.#conditions = conditions;
this.#selectedColumns = selectedColumns;
this.#orderByClauses = orderByClauses;
this.#limitValue = limitValue;
this.#offsetValue = offsetValue;
this.#distinct = distinct;
}
where(condition) {
return new SelectBuilder(this.#executor, this.#tableName, this.#table, [...this.#conditions, condition], this.#selectedColumns, this.#orderByClauses, this.#limitValue, this.#offsetValue, this.#distinct);
}
columns(keys) {
return new NarrowedSelectBuilder(this.#executor, this.#tableName, this.#table, this.#conditions, keys, this.#orderByClauses, this.#limitValue, this.#offsetValue, this.#distinct);
}
single() {
return new SingleSelectBuilder(this.#executor, this.#tableName, this.#table, this.#conditions, this.#selectedColumns, this.#orderByClauses, this.#offsetValue, this.#distinct);
}
distinct() {
return new SelectBuilder(this.#executor, this.#tableName, this.#table, this.#conditions, this.#selectedColumns, this.#orderByClauses, this.#limitValue, this.#offsetValue, true);
}
orderBy(key, direction = "asc") {
const column = getCol(this.#table, key);
return new SelectBuilder(this.#executor, this.#tableName, this.#table, this.#conditions, this.#selectedColumns, [...this.#orderByClauses, { column, direction }], this.#limitValue, this.#offsetValue, this.#distinct);
}
limit(n) {
return new SelectBuilder(this.#executor, this.#tableName, this.#table, this.#conditions, this.#selectedColumns, this.#orderByClauses, n, this.#offsetValue, this.#distinct);
}
offset(n) {
return new SelectBuilder(this.#executor, this.#tableName, this.#table, this.#conditions, this.#selectedColumns, this.#orderByClauses, this.#limitValue, n, this.#distinct);
}
toSQL() {
validateColumnOwnership(this.#conditions, [this.#table], `SELECT from "${this.#tableName}"`);
const cols = resolveColumns(this.#table, this.#selectedColumns);
const params = [];
const distinct = this.#distinct ? "DISTINCT " : "";
let sql = `SELECT ${distinct}${cols} FROM ${this.#tableName}`;
const where = compileConditions(this.#conditions, params);
if (where !== "1=1")
sql += ` WHERE ${where}`;
if (this.#orderByClauses.length > 0) {
const orderClauses = this.#orderByClauses.map((o) => `${o.column.name} ${o.direction.toUpperCase()}`).join(", ");
sql += ` ORDER BY ${orderClauses}`;
}
if (this.#limitValue !== null)
sql += ` LIMIT ${this.#limitValue}`;
if (this.#offsetValue !== null)
sql += ` OFFSET ${this.#offsetValue}`;
return { sql, params };
}
async execute() {
const { sql, params } = this.toSQL();
try {
const rows = await this.#executor.all(sql, params);
const records = rows;
if (this.#selectedColumns) {
return records.map((r) => decodeSelectedRow(r, this.#table, this.#selectedColumns));
}
return records.map((r) => decodeRow(r, this.#table));
} catch (e) {
throw new FlintQueryError(`Failed to execute query: ${sql}`, e);
}
}
}
class NarrowedSelectBuilder {
#executor;
#tableName;
#table;
#conditions;
#selectedColumns;
#orderByClauses;
#limitValue;
#offsetValue;
#distinct;
constructor(executor, tableName, table, conditions, selectedColumns, orderByClauses, limitValue, offsetValue, distinct) {
this.#executor = executor;
this.#tableName = tableName;
this.#table = table;
this.#conditions = conditions;
this.#selectedColumns = selectedColumns;
this.#orderByClauses = orderByClauses;
this.#limitValue = limitValue;
this.#offsetValue = offsetValue;
this.#distinct = distinct;
}
where(condition) {
return new NarrowedSelectBuilder(this.#executor, this.#tableName, this.#table, [...this.#conditions, condition], this.#selectedColumns, this.#orderByClauses, this.#limitValue, this.#offsetValue, this.#distinct);
}
distinct() {
return new NarrowedSelectBuilder(this.#executor, this.#tableName, this.#table, this.#conditions, this.#selectedColumns, this.#orderByClauses, this.#limitValue, this.#offsetValue, true);
}
single() {
return new NarrowedSingleSelectBuilder(this.#executor, this.#tableName, this.#table, this.#conditions, this.#selectedColumns, this.#orderByClauses, this.#offsetValue, this.#distinct);
}
orderBy(key, direction = "asc") {
const column = getCol(this.#table, key);
return new NarrowedSelectBuilder(this.#executor, this.#tableName, this.#table, this.#conditions, this.#selectedColumns, [...this.#orderByClauses, { column, direction }], this.#limitValue, this.#offsetValue, this.#distinct);
}
limit(n) {
return new NarrowedSelectBuilder(this.#executor, this.#tableName, this.#table, this.#conditions, this.#selectedColumns, this.#orderByClauses, n, this.#offsetValue, this.#distinct);
}
offset(n) {
return new NarrowedSelectBuilder(this.#executor, this.#tableName, this.#table, this.#conditions, this.#selectedColumns, this.#orderByClauses, this.#limitValue, n, this.#distinct);
}
toSQL() {
validateColumnOwnership(this.#conditions, [this.#table], `SELECT from "${this.#tableName}"`);
const cols = resolveColumns(this.#table, this.#selectedColumns);
const params = [];
const distinct = this.#distinct ? "DISTINCT " : "";
let sql = `SELECT ${distinct}${cols} FROM ${this.#tableName}`;
const where = compileConditions(this.#conditions, params);
if (where !== "1=1")
sql += ` WHERE ${where}`;
if (this.#orderByClauses.length > 0) {
const orderClauses = this.#orderByClauses.map((o) => `${o.column.name} ${o.direction.toUpperCase()}`).join(", ");
sql += ` ORDER BY ${orderClauses}`;
}
if (this.#limitValue !== null)
sql += ` LIMIT ${this.#limitValue}`;
if (this.#offsetValue !== null)
sql += ` OFFSET ${this.#offsetValue}`;
return { sql, params };
}
async execute() {
const { sql, params } = this.toSQL();
try {
const rows = await this.#executor.all(sql, params);
return rows.map((r) => decodeSelectedRow(r, this.#table, this.#selectedColumns));
} catch (e) {
throw new FlintQueryError(`Failed to execute query: ${sql}`, e);
}
}
}
class SingleSelectBuilder {
#executor;
#tableName;
#table;
#conditions;
#selectedColumns;
#orderByClauses;
#offsetValue;
#distinct;
constructor(executor, tableName, table, conditions, selectedColumns = null, orderByClauses = [], offsetValue = null, distinct = false) {
this.#executor = executor;
this.#tableName = tableName;
this.#table = table;
this.#conditions = conditions;
this.#selectedColumns = selectedColumns;
this.#orderByClauses = orderByClauses;
this.#offsetValue = offsetValue;
this.#distinct = distinct;
}
where(condition) {
return new SingleSelectBuilder(this.#executor, this.#tableName, this.#table, [...this.#conditions, condition], this.#selectedColumns, this.#orderByClauses, this.#offsetValue, this.#distinct);
}
orderBy(key, direction = "asc") {
const column = getCol(this.#table, key);
return new SingleSelectBuilder(this.#executor, this.#tableName, this.#table, this.#conditions, this.#selectedColumns, [...this.#orderByClauses, { column, direction }], this.#offsetValue, this.#distinct);
}
offset(n) {
return new SingleSelectBuilder(this.#executor, this.#tableName, this.#table, this.#conditions, this.#selectedColumns, this.#orderByClauses, n, this.#distinct);
}
toSQL() {
validateColumnOwnership(this.#conditions, [this.#table], `SELECT from "${this.#tableName}"`);
const cols = resolveColumns(this.#table, this.#selectedColumns);
const params = [];
const distinct = this.#distinct ? "DISTINCT " : "";
let sql = `SELECT ${distinct}${cols} FROM ${this.#tableName}`;
const where = compileConditions(this.#conditions, params);
if (where !== "1=1")
sql += ` WHERE ${where}`;
if (this.#orderByClauses.length > 0) {
const orderClauses = this.#orderByClauses.map((o) => `${o.column.name} ${o.direction.toUpperCase()}`).join(", ");
sql += ` ORDER BY ${orderClauses}`;
}
sql += " LIMIT 1";
if (this.#offsetValue !== null)
sql += ` OFFSET ${this.#offsetValue}`;
return { sql, params };
}
async execute() {
const { sql, params } = this.toSQL();
try {
const row = await this.#executor.get(sql, params);
if (!row)
return null;
const record = row;
if (this.#selectedColumns) {
return decodeSelectedRow(record, this.#table, this.#selectedColumns);
}
return decodeRow(record, this.#table);
} catch (e) {
throw new FlintQueryError(`Failed to execute query: ${sql}`, e);
}
}
}
class NarrowedSingleSelectBuilder {
#executor;
#tableName;
#table;
#conditions;
#selectedColumns;
#orderByClauses;
#offsetValue;
#distinct;
constructor(executor, tableName, table, conditions, selectedColumns, orderByClauses, offsetValue, distinct) {
this.#executor = executor;
this.#tableName = tableName;
this.#table = table;
this.#conditions = conditions;
this.#selectedColumns = selectedColumns;
this.#orderByClauses = orderByClauses;
this.#offsetValue = offsetValue;
this.#distinct = distinct;
}
where(condition) {
return new NarrowedSingleSelectBuilder(this.#executor, this.#tableName, this.#table, [...this.#conditions, condition], this.#selectedColumns, this.#orderByClauses, this.#offsetValue, this.#distinct);
}
orderBy(key, direction = "asc") {
const column = getCol(this.#table, key);
return new NarrowedSingleSelectBuilder(this.#executor, this.#tableName, this.#table, this.#conditions, this.#selectedColumns, [...this.#orderByClauses, { column, direction }], this.#offsetValue, this.#distinct);
}
offset(n) {
return new NarrowedSingleSelectBuilder(this.#executor, this.#tableName, this.#table, this.#conditions, this.#selectedColumns, this.#orderByClauses, n, this.#distinct);
}
toSQL() {
validateColumnOwnership(this.#conditions, [this.#table], `SELECT from "${this.#tableName}"`);
const cols = resolveColumns(this.#table, this.#selectedColumns);
const params = [];
const distinct = this.#distinct ? "DISTINCT " : "";
let sql = `SELECT ${distinct}${cols} FROM ${this.#tableName}`;
const where = compileConditions(this.#conditions, params);
if (where !== "1=1")
sql += ` WHERE ${where}`;
if (this.#orderByClauses.length > 0) {
const orderClauses = this.#orderByClauses.map((o) => `${o.column.name} ${o.direction.toUpperCase()}`).join(", ");
sql += ` ORDER BY ${orderClauses}`;
}
sql += " LIMIT 1";
if (this.#offsetValue !== null)
sql += ` OFFSET ${this.#offsetValue}`;
return { sql, params };
}
async execute() {
const { sql, params } = this.toSQL();
try {
const row = await this.#executor.get(sql, params);
if (!row)
return null;
return decodeSelectedRow(row, this.#table, this.#selectedColumns);
} catch (e) {
throw new FlintQueryError(`Failed to execute query: ${sql}`, e);
}
}
}
class JoinStage1 {
#executor;
#parent;
#parentName;
#joinType;
constructor(executor, parent, parentName, joinType) {
this.#executor = executor;
this.#parent = parent;
this.#parentName = parentName;
this.#joinType = joinType;
}
on(child, condition) {
const childName = child._.name;
const resolvedCondition = condition ?? resolveForeignKeyCondition(this.#parent, this.#parentName, child, childName);
return new JoinBuilderImpl(this.#executor, this.#parent, this.#parentName, [{ table: child, name: childName, condition: resolvedCondition }], this.#joinType);
}
}
class JoinBuilderImpl {
#executor;
#parent;
#parentName;
#joins;
#joinType;
#conditions;
#selectedColumns;
#orderByClauses;
#limitValue;
#offsetValue;
constructor(executor, parent, parentName, joins, joinType, conditions = [], selectedColumns = null, orderByClauses = [], limitValue = null, offsetValue = null) {
this.#executor = executor;
this.#parent = parent;
this.#parentName = parentName;
this.#joins = joins;
this.#joinType = joinType;
this.#conditions = conditions;
this.#selectedColumns = selectedColumns;
this.#orderByClauses = orderByClauses;
this.#limitValue = limitValue;
this.#offsetValue = offsetValue;
}
on(child, condition) {
const childName = child._.name;
const resolvedCondition = condition ?? resolveForeignKeyCondition(this.#parent, this.#parentName, child, childName);
return new JoinBuilderImpl(this.#executor, this.#parent, this.#parentName, [...this.#joins, { table: child, name: childName, condition: resolvedCondition }], this.#joinType, this.#conditions, this.#selectedColumns);
}
where(condition) {
return new JoinBuilderImpl(this.#executor, this.#parent, this.#parentName, this.#joins, this.#joinType, [...this.#conditions, condition], this.#selectedColumns);
}
columns(keys) {
return new JoinBuilderImpl(this.#executor, this.#parent, this.#parentName, this.#joins, this.#joinType, this.#conditions, keys, this.#orderByClauses, this.#limitValue, this.#offsetValue);
}
orderBy(key, direction = "asc") {
const column = getCol(this.#parent, key);
return new JoinBuilderImpl(this.#executor, this.#parent, this.#parentName, this.#joins, this.#joinType, this.#conditions, this.#selectedColumns, [...this.#orderByClauses, { column, direction }], this.#limitValue, this.#offsetValue);
}
limit(n) {
return new JoinBuilderImpl(this.#executor, this.#parent, this.#parentName, this.#joins, this.#joinType, this.#conditions, this.#selectedColumns, this.#orderByClauses, n, this.#offsetValue);
}
offset(n) {
return new JoinBuilderImpl(this.#executor, this.#parent, this.#parentName, this.#joins, this.#joinType, this.#conditions, this.#selectedColumns, this.#orderByClauses, this.#limitValue, n);
}
single() {
return new SingleJoinBuilderImpl(this.#executor, this.#parent, this.#parentName, this.#joins, this.#joinType, this.#conditions, this.#selectedColumns, this.#orderByClauses, this.#offsetValue);
}
toSQL() {
const allowedTables = [this.#parent, ...this.#joins.map((j) => j.table)];
validateColumnOwnership(this.#conditions, allowedTables, `SELECT from "${this.#parentName}"`);
const parentCols = resolveColumns(this.#parent, this.#selectedColumns, this.#parentName);
const childCols = [];
for (const join of this.#joins) {
const entries = columnEntries(join.table);
for (const [, c] of entries) {
childCols.push(`${join.name}.${c.name} AS ${join.name}_${c.name}`);
}
}
const joinKeyword = this.#joinType === "left" ? "LEFT JOIN" : "INNER JOIN";
const joinClauses = [];
const joinParams = [];
for (const join of this.#joins) {
const joinOn = compileConditions([join.condition], joinParams);
joinClauses.push(`${joinKeyword} ${join.name} ON ${joinOn}`);
}
const whereParams = [];
const where = compileConditions(this.#conditions, whereParams);
let sql = `SELECT ${parentCols}${childCols.length ? ", " + childCols.join(", ") : ""} FROM ${this.#parentName} ${joinClauses.join(" ")}`;
if (where !== "1=1")
sql += ` WHERE ${where}`;
if (this.#orderByClauses.length > 0) {
const orderClauses = this.#orderByClauses.map((o) => `${o.column.name} ${o.direction.toUpperCase()}`).join(", ");
sql += ` ORDER BY ${orderClauses}`;
}
if (this.#limitValue !== null)
sql += ` LIMIT ${this.#limitValue}`;
if (this.#offsetValue !== null)
sql += ` OFFSET ${this.#offsetValue}`;
return { sql, params: [...joinParams, ...whereParams] };
}
async execute() {
const { sql, params } = this.toSQL();
try {
const rows = await this.#executor.all(sql, params);
return this.#decodeJoinRows(rows);
} catch (e) {
if (e instanceof FlintQueryError)
throw e;
throw new FlintQueryError(`Failed to execute query: ${sql}`, e);
}
}
#decodeJoinRows(rows) {
const parentEntries = columnEntries(this.#parent);
const pkKeys = findPKKeys(this.#parent);
const pkColNames = pkKeys.map((k) => getCol(this.#parent, k).name);
const childEntryMaps = [];
for (const j of this.#joins) {
childEntryMaps.push({
name: j.name,
entries: columnEntries(j.table),
table: j.table
});
}
const grouped = new Map;
for (const row of rows) {
const pk = pkColNames.length === 1 ? row[pkColNames[0]] : pkColNames.map((name) => String(row[name])).join("||");
if (!grouped.has(pk)) {
const parentRow = {};
for (const [key, col] of parentEntries) {
parentRow[key] = row[col.name];
}
grouped.set(pk, {
parent: parentRow,
children: childEntryMaps.map(() => [])
});
}
const group = grouped.get(pk);
childEntryMaps.forEach((childMap, i) => {
const childRow = {};
let hasNonNullChild = false;
for (const [key, col] of childMap.entries) {
const val = row[`${childMap.name}_${col.name}`];
childRow[key] = val;
if (val != null)
hasNonNullChild = true;
}
if (this.#joinType === "left" && !hasNonNullChild)
return;
group.children[i].push(childRow);
});
}
const result = [];
for (const { parent, children } of grouped.values()) {
const decodedParent = this.#selectedColumns ? decodeSelectedRow(parent, this.#parent, this.#selectedColumns) : decodeRow(parent, this.#parent);
const nested = { ...decodedParent };
childEntryMaps.forEach((childMap, i) => {
nested[childMap.name] = children[i].map((c) => decodeRow(c, childMap.table));
});
result.push(nested);
}
return result;
}
}
class SingleJoinBuilderImpl {
#executor;
#parent;
#parentName;
#joins;
#joinType;
#conditions;
#selectedColumns;
#orderByClauses;
#offsetValue;
constructor(executor, parent, parentName, joins, joinType, conditions, selectedColumns = null, orderByClauses = [], offsetValue = null) {
this.#executor = executor;
this.#parent = parent;
this.#parentName = parentName;
this.#joins = joins;
this.#joinType = joinType;
this.#conditions = conditions;
this.#selectedColumns = selectedColumns;
this.#orderByClauses = orderByClauses;
this.#offsetValue = offsetValue;
}
where(condition) {
return new SingleJoinBuilderImpl(this.#executor, this.#parent, this.#parentName, this.#joins, this.#joinType, [...this.#conditions, condition], this.#selectedColumns, this.#orderByClauses, this.#offsetValue);
}
orderBy(key, direction = "asc") {
const column = getCol(this.#parent, key);
return new SingleJoinBuilderImpl(this.#executor, this.#parent, this.#parentName, this.#joins, this.#joinType, this.#conditions, this.#selectedColumns, [...this.#orderByClauses, { column, direction }], this.#offsetValue);
}
offset(n) {
return new SingleJoinBuilderImpl(this.#executor, this.#parent, this.#parentName, this.#joins, this.#joinType, this.#conditions, this.#selectedColumns, this.#orderByClauses, n);
}
toSQL() {
const allowedTables = [this.#parent, ...this.#joins.map((j) => j.table)];
validateColumnOwnership(this.#conditions, allowedTables, `SELECT from "${this.#parentName}"`);
const parentCols = resolveColumns(this.#parent, this.#selectedColumns, this.#parentName);
const childCols = [];
for (const join of this.#joins) {
const entries = columnEntries(join.table);
for (const [, c] of entries) {
childCols.push(`${join.name}.${c.name} AS ${join.name}_${c.name}`);
}
}
const joinKeyword = this.#joinType === "left" ? "LEFT JOIN" : "INNER JOIN";
const joinClauses = [];
const joinParams = [];
for (const join of this.#joins) {
const joinOn = compileConditions([join.condition], joinParams);
joinClauses.push(`${joinKeyword} ${join.name} ON ${joinOn}`);
}
const whereParams = [];
const where = compileConditions(this.#conditions, whereParams);
let sql = `SELECT ${parentCols}${childCols.length ? ", " + childCols.join(", ") : ""} FROM ${this.#parentName} ${joinClauses.join(" ")}`;
if (where !== "1=1")
sql += ` WHERE ${where}`;
if (this.#orderByClauses.length > 0) {
const orderClauses = this.#orderByClauses.map((o) => `${o.column.name} ${o.direction.toUpperCase()}`).join(", ");
sql += ` ORDER BY ${orderClauses}`;
}
sql += " LIMIT 1";
if (this.#offsetValue !== null)
sql += ` OFFSET ${this.#offsetValue}`;
return { sql, params: [...joinParams, ...whereParams] };
}
async execute() {
const { sql, params } = this.toSQL();
try {
const rows = await this.#executor.all(sql, params);
const records = rows;
if (records.length === 0)
return null;
return this.#decodeJoinRow(records);
} catch (e) {
if (e instanceof FlintQueryError)
throw e;
throw new FlintQueryError(`Failed to execute query: ${sql}`, e);
}
}
#decodeJoinRow(rows) {
const parentEntries = columnEntries(this.#parent);
const pkKeys = findPKKeys(this.#parent);
const pkColNames = pkKeys.map((k) => getCol(this.#parent, k).name);
const childEntryMaps = [];
for (const j of this.#joins) {
childEntryMaps.push({
name: j.name,
entries: columnEntries(j.table),
table: j.table
});
}
const grouped = new Map;
for (const r of rows) {
const pk = pkColNames.length === 1 ? r[pkColNames[0]] : pkColNames.map((name) => String(r[name])).join("||");
if (!grouped.has(pk)) {
const parentRow = {};
for (const [key, col] of parentEntries) {
parentRow[key] = r[col.name];
}
grouped.set(pk, {
parent: parentRow,
children: childEntryMaps.map(() => [])
});
}
const group = grouped.get(pk);
childEntryMaps.forEach((childMap, i) => {
const childRow = {};
let hasNonNullChild = false;
for (const [key, col] of childMap.entries) {
const val = r[`${childMap.name}_${col.name}`];
childRow[key] = val;
if (val != null)
hasNonNullChild = true;
}
if (this.#joinType === "left" && !hasNonNullChild)
return;
group.children[i].push(childRow);
});
}
const first = grouped.values().next().value;
if (!first)
return null;
const decodedParent = this.#selectedColumns ? decodeSelectedRow(first.parent, this.#parent, this.#selectedColumns) : decodeRow(first.parent, this.#parent);
const nested = { ...decodedParent };
childEntryMaps.forEach((childMap, i) => {
nested[childMap.name] = first.children[i].map((c) => decodeRow(c, childMap.table));
});
return nested;
}
}
class InsertValuesBuilder {
#executor;
#tableName;
#table;
constructor(executor, tableName, table) {
this.#executor = executor;
this.#tableName = tableName;
this.#table = table;
}
values(rowOrRows) {
return new InsertBuilder(this.#executor, this.#tableName, this.#table, rowOrRows);
}
}
class InsertBuilder {
#executor;
#tableName;
#table;
#rows;
#returning;
#onConflict;
constructor(executor, tableName, table, rowOrRows, returning = false, onConflict) {
this.#executor = executor;
this.#tableName = tableName;
this.#table = table;
this.#rows = Array.isArray(rowOrRows) ? rowOrRows : [rowOrRows];
this.#returning = returning;
this.#onConflict = onConflict;
}
returning(keys) {
return new InsertBuilder(this.#executor, this.#tableName, this.#table, this.#rows, keys ?? true, this.#onConflict);
}
onConflictDoNothing() {
return new InsertBuilder(this.#executor, this.#tableName, this.#table, this.#rows, this.#returning, { mode: "nothing" });
}
onConflictDoUpdate(options) {
return new InsertBuilder(this.#executor, this.#tableName, this.#table, this.#rows, this.#returning, {
mode: "update",
target: options.target,
set: options.set
});
}
toSQL() {
const entries = columnEntries(this.#table);
const inserts = [];
for (const [key, c] of entries) {
const allUndefined = this.#rows.every((row) => row[key] === undefined);
if (allUndefined && (c.__internal.hasDefault || c.__internal.isAutoIncrement)) {
continue;
}
inserts.push([key, c]);
}
if (inserts.length === 0) {
const allDefault = entries.filter(([, c]) => c.__internal.hasDefault || c.__internal.isAutoIncrement || c.__internal.hasDefaultNow);
const names2 = allDefault.map(([, c]) => c.name).join(", ");
const placeholders = allDefault.map(() => "DEFAULT").join(", ");
return {
sql: `INSERT INTO ${this.#tableName} (${names2}) VALUES (${placeholders})`,
params: []
};
}
const names = inserts.map(([, c]) => c.name).join(", ");
const placeholderRow = inserts.map(() => "?").join(", ");
const allPlaceholders = this.#rows.map(() => `(${placeholderRow})`).join(", ");
const params = [];
for (const row of this.#rows) {
for (const [key, c] of inserts) {
const value = row[key];
if (value === undefined && c.__internal.hasDefaultNow) {
params.push(c.__internal.encode(new Date));
} else {
params.push(c.__internal.encode(value));
}
}
}
let sql = `INSERT INTO ${this.#tableName} (${names}) VALUES ${allPlaceholders}`;
if (this.#onConflict) {
if (this.#onConflict.mode === "nothing") {
sql += " ON CONFLICT DO NOTHING";
} else {
const target = this.#onConflict.target;
const targetCols = Array.isArray(target) ? target : [target];
const targetNames = targetCols.map((c) => c.name).join(", ");
const setEntries = Object.entries(this.#onConflict.set);
const setClauses = setEntries.map(([key, value]) => {
const col = getCol(this.#table, key);
if (value === undefined)
return null;
return `${col.name} = excluded.${col.name}`;
}).filter(Boolean);
if (setClauses.length > 0) {
sql += ` ON CONFLICT (${targetNames}) DO UPDATE SET ${setClauses.join(", ")}`;
}
}
}
if (this.#returning) {
if (Array.isArray(this.#returning)) {
const cols = this.#returning.map((k) => getCol(this.#table, k).name).join(", ");
sql += ` RETURNING ${cols}`;
} else {
sql += " RETURNING *";
}
}
return { sql, params };
}
async execute() {
const { sql, params } = this.toSQL();
try {
if (this.#returning) {
const rows = await this.#executor.all(sql, params);
const records = rows;
if (Array.isArray(this.#returning)) {
return records.map((r) => decodeSelectedRow(r, this.#table, this.#returning));
}
return records.map((r) => decodeRow(r, this.#table));
}
return await this.#executor.run(sql, params);
} catch (e) {
throw new FlintQueryError(`Failed to execute query: ${sql}`, e);
}
}
}
class UpdateSetBuilder {
#executor;
#tableName;
#table;
constructor(executor, tableName, table) {
this.#executor = executor;
this.#tableName = tableName;
this.#table = table;
}
set(partial) {
return new UpdateBuilder(this.#executor, this.#tableName, this.#table, partial);
}
}
class UpdateBuilder {
#executor;
#tableName;
#table;
#set;
#conditions;
#returning;
constructor(executor, tableName, table, set, conditions = [], returning = false) {
this.#executor = executor;
this.#tableName = tableName;
this.#table = table;
this.#set = set;
this.#conditions = conditions;
this.#returning = returning;
}
set(partial) {
return new UpdateBuilder(this.#executor, this.#tableName, this.#table, { ...this.#set, ...partial }, this.#conditions, this.#returning);
}
where(condition) {
return new UpdateBuilder(this.#executor, this.#tableName, this.#table, this.#set, [...this.#conditions, condition], this.#returning);
}
returning(keys) {
return new UpdateBuilder(this.#executor, this.#tableName, this.#table, this.#set, this.#conditions, keys ?? true);
}
toSQL() {
validateColumnOwnership(this.#conditions, [this.#table], `UPDATE "${this.#tableName}"`);
const params = [];
const setClauses = [];
for (const key of Object.keys(this.#set)) {
const col = getCol(this.#table, key);
if (col.__internal.hasOnUpdate) {
setClauses.push(`${col.name} = ?`);
params.push(col.__internal.encode(new Date));
continue;
}
setClauses.push(`${col.name} = ?`);
params.push(col.__internal.encode(this.#set[key]));
}
let sql = `UPDATE ${this.#tableName} SET ${setClauses.join(", ")}`;
const where = compileConditions(this.#conditions, params);
if (where !== "1=1")
sql += ` WHERE ${where}`;
if (this.#returning) {
if (Array.isArray(this.#returning)) {
const cols = this.#returning.map((k) => getCol(this.#table, k).name).join(", ");
sql += ` RETURNING ${cols}`;
} else {
sql += " RETURNING *";
}
}
return { sql, params };
}
async execute() {
const { sql, params } = this.toSQL();
try {
if (this.#returning) {
const rows = await this.#executor.all(sql, params);
const records = rows;
if (Array.isArray(this.#returning)) {
return records.map((r) => decodeSelectedRow(r, this.#table, this.#returning));
}
return records.map((r) => decodeRow(r, this.#table));
}
return await this.#executor.run(sql, params);
} catch (e) {
throw new FlintQueryError(`Failed to execute query: ${sql}`, e);
}
}
}
class DeleteBuilder {
#executor;
#tableName;
#table;
#conditions;
#returning;
constructor(executor, tableName, table, conditions = [], returning = false) {
this.#executor = executor;
this.#tableName = tableName;
this.#table = table;
this.#conditions = conditions;
this.#returning = returning;
}
where(condition) {
return new DeleteBuilder(this.#executor, this.#tableName, this.#table, [...this.#conditions, condition], this.#returning);
}
returning(keys) {
return new DeleteBuilder(this.#executor, this.#tableName, this.#table, this.#conditions, keys ?? true);
}
toSQL() {
validateColumnOwnership(this.#conditions, [this.#table], `DELETE from "${this.#tableName}"`);
const params = [];
let sql = `DELETE FROM ${this.#tableName}`;
const where = compileConditions(this.#conditions, params);
if (where !== "1=1")
sql += ` WHERE ${where}`;
if (this.#returning) {
if (Array.isArray(this.#returning)) {
const cols = this.#returning.map((k) => getCol(this.#table, k).name).join(", ");
sql += ` RETURNING ${cols}`;
} else {
sql += " RETURNING *";
}
}
return { sql, params };
}
async execute() {
const { sql, params } = this.toSQL();
try {
if (this.#returning) {
const rows = await this.#executor.all(sql, params);
const records = rows;
if (Array.isArray(this.#returning)) {
return records.map((r) => decodeSelectedRow(r, this.#table, this.#returning));
}
return records.map((r) => decodeRow(r, this.#table));
}
return await this.#executor.run(sql, params);
} catch (e) {
throw new FlintQueryError(`Failed to execute query: ${sql}`, e);
}
}
}
var init_builder = __esm(() => {
init_errors();
});
// src/query/aggregates.ts
function getTableName(table) {
return table._.name;
}
function getColumnName(column) {
return column.name;
}
function compileWhere(condition) {
if (!condition) {
return { whereSql: "", params: [] };
}
const params = [];
const whereSql = compileCondition(condition, params);
return { whereSql: ` WHERE ${whereSql}`, params };
}
async function count(executor, table, condition) {
const tableName = getTableName(table);
const { whereSql, params } = compileWhere(condition);
const sql = `SELECT count(*) as cnt FROM ${tableName}${whereSql}`;
const result = await executor.get(sql, params);
return result.cnt;
}
async function countColumn(executor, table, column, condition) {
const tableName = getTableName(table);
const columnName = getColumnName(column);
const { whereSql, params } = compileWhere(condition);
const sql = `SELECT count(${columnName}) as cnt FROM ${tableName}${whereSql}`;
const result = await executor.get(sql, params);
return result.cnt;
}
async function sum(executor, table, column, condition) {
const tableName = getTableName(table);
const columnName = getColumnName(column);
const { whereSql, params } = compileWhere(condition);
const sql = `SELECT sum(${columnName}) as total FROM ${tableName}${whereSql}`;
const result = await executor.get(sql, params);
return result.total;
}
async function avg(executor, table, column, condition) {
const tableName = getTableName(table);
const columnName = getColumnName(column);
const { whereSql, params } = compileWhere(condition);
const sql = `SELECT avg(${columnName}) as average FROM ${tableName}${whereSql}`;
const result = await executor.get(sql, params);
return result.average;
}
async function min(executor, table, column, condition) {
const tableName = getTableName(table);
const columnName = getColumnName(column);
const { whereSql, params } = compileWhere(condition);
const sql = `SELECT min(${columnName}) as minimum FROM ${tableName}${whereSql}`;
const result = await executor.get(sql, params);
return result.minimum;
}
async function max(executor, table, column, condition) {
const tableName = getTableName(table);
const columnName = getColumnName(column);
const { whereSql, params } = compileWhere(condition);
const sql = `SELECT max(${columnName}) as maximum FROM ${tableName}${whereSql}`;
const result = await executor.get(sql, params);
return result.maximum;
}
var init_aggregates = () => {};
// src/flint.ts
function createClient(executor) {
return {
selectFrom: (table) => new SelectBuilder(executor, table._.name, table),
insert: (table) => new InsertValuesBuilder(executor, table._.name, table),
update: (table) => new UpdateSetBuilder(executor, table._.name, table),
delete: (table) => new DeleteBuilder(executor, table._.name, table),
leftJoin: (parent) => new JoinStage1(executor, parent, parent._.name, "left"),
innerJoin: (parent) => new JoinStage1(executor, parent, parent._.name, "inner"),
batch: (queries) => {
const stmts = queries.map((q) => q.toSQL());
return executor.transaction(async () => {
for (const stmt of stmts) {
await executor.run(stmt.sql, stmt.params);
}
});
},
count: (table, condition) => count(executor, table, condition),
countColumn: (table, column, condition) => countColumn(executor, table, column, condition),
sum: (table, column, condition) => sum(executor, table, column, condition),
avg: (table, column, condition) => avg(executor, table, column, condition),
min: (table, column, condition) => min(executor, table, column, condition),
max: (table, column, condition) => max(executor, table, column, condition),
$run(query, ...params) {
return executor.run(query, params);
},
$executor: executor
};
}
function sql(strings, ...values) {
let query = "";
const params = [];
for (let i = 0;i < strings.length; i++) {
query += strings[i];
if (i < values.length) {
query += "?";
params.push(values[i]);
}
}
return { sql: query, params };
}
var init_flint = __esm(() => {
init_builder();
init_aggregates();
});
// src/entries/expressions.ts
init_flint();
init_aggregates();
export {
sum,
sql,
or,
neq,
min,
max,
lte,
lt,
like,
isNull,
isNotNull,
isNotIn,
isIn,
gte,
gt,
glob,
eq,
countColumn,
count,
between,
avg,
and
};