UNPKG

flint-orm

Version:

Type-safe SQLite ORM for JavaScript

1,396 lines (1,377 loc) 60.6 kB
// @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/index.ts init_flint(); // src/schema/columns.ts function nullCast() { return null; } function makeColumn(config) { const base = { name: config.name, __internal: { _type: undefined, sqlType: config.sqlType, isPrimaryKey: false, isNotNull: false, isUnique: false, hasDefault: false, defaultValue: undefined, defaultFn: undefined, isAutoIncrement: false, hasDefaultNow: false, hasOnUpdate: false, referencesTable: null, referencesColumn: null, onDelete: null, onUpdate: null, encode: config.encode, decode: config.decode, tableName: null } }; const col = { ...base, primaryKey() { return { ...this, __internal: { ...this.__internal, isPrimaryKey: true } }; }, notNull() { return { ...this, __internal: { ...this.__internal, isNotNull: true } }; }, unique() { return { ...this, __internal: { ...this.__internal, isUnique: true } }; }, default(value) { return { ...this, __internal: { ...this.__internal, hasDefault: true, defaultValue: value } }; }, defaultFn(fn) { return { ...this, __internal: { ...this.__internal, hasDefault: true, defaultFn: fn } }; }, references(target) { return { ...this, __internal: { ...this.__internal, referencesTable: target.__internal.tableName, referencesColumn: target.name } }; }, onDelete(action) { return { ...this, __internal: { ...this.__internal, onDelete: action } }; }, onUpdate(action) { return { ...this, __internal: { ...this.__internal, onUpdate: action } }; } }; return col; } function text(name) { return makeColumn({ name: name ?? "", sqlType: "text", encode: (v) => v, decode: (v) => v == null ? nullCast() : v }); } function integer(name) { const base = makeColumn({ name: name ?? "", sqlType: "integer", encode: (v) => v, decode: (v) => v == null ? nullCast() : Number(v) }); const intCol = { ...base, primaryKey() { return { ...this, __internal: { ...this.__internal, isPrimaryKey: true } }; }, notNull() { return { ...this, __internal: { ...this.__internal, isNotNull: true } }; }, unique() { return { ...this, __internal: { ...this.__internal, isUnique: true } }; }, default(value) { return { ...this, __internal: { ...this.__internal, hasDefault: true, defaultValue: value } }; }, defaultFn(fn) { return { ...this, __internal: { ...this.__internal, hasDefault: true, defaultFn: fn } }; }, autoIncrement() { return { ...this, __internal: { ...this.__internal, isAutoIncrement: true } }; } }; return intCol; } function boolean(name) { return makeColumn({ name: name ?? "", sqlType: "integer", encode: (v) => v ? 1 : 0, decode: (v) => v == null ? nullCast() : Boolean(v) }); } function json(name) { return makeColumn({ name: name ?? "", sqlType: "text", encode: (v) => v == null ? null : JSON.stringify(v), decode: (v) => v == null ? nullCast() : JSON.parse(v) }); } function date(name) { const base = makeColumn({ name: name ?? "", sqlType: "integer", encode: (v) => v == null ? null : v.getTime(), decode: (v) => v == null ? nullCast() : new Date(v) }); const dateCol = { ...base, primaryKey() { return { ...this, __internal: { ...this.__internal, isPrimaryKey: true } }; }, notNull() { return { ...this, __internal: { ...this.__internal, isNotNull: true } }; }, unique() { return { ...this, __internal: { ...this.__internal, isUnique: true } }; }, default(value) { return { ...this, __internal: { ...this.__internal, hasDefault: true, defaultValue: value } }; }, defaultFn(fn) { return { ...this, __internal: { ...this.__internal, hasDefault: true, defaultFn: fn } }; }, defaultNow() { return { ...this, __internal: { ...this.__internal, hasDefaultNow: true } }; }, onUpdateTimestamp() { return { ...this, __internal: { ...this.__internal, hasOnUpdate: true } }; } }; return dateCol; } function real(name) { return makeColumn({ name: name ?? "", sqlType: "real", encode: (v) => v, decode: (v) => v == null ? nullCast() : Number(v) }); } // src/schema/table.ts function table(name, columns, indexFn)