flint-orm
Version:
Type-safe SQLite ORM for JavaScript
1,526 lines (1,511 loc) • 459 kB
JavaScript
#!/usr/bin/env bun
// @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;
// node_modules/sisteransi/src/index.js
var require_src = __commonJS((exports, module) => {
var ESC2 = "\x1B";
var CSI2 = `${ESC2}[`;
var beep = "\x07";
var cursor = {
to(x, y) {
if (!y)
return `${CSI2}${x + 1}G`;
return `${CSI2}${y + 1};${x + 1}H`;
},
move(x, y) {
let ret = "";
if (x < 0)
ret += `${CSI2}${-x}D`;
else if (x > 0)
ret += `${CSI2}${x}C`;
if (y < 0)
ret += `${CSI2}${-y}A`;
else if (y > 0)
ret += `${CSI2}${y}B`;
return ret;
},
up: (count = 1) => `${CSI2}${count}A`,
down: (count = 1) => `${CSI2}${count}B`,
forward: (count = 1) => `${CSI2}${count}C`,
backward: (count = 1) => `${CSI2}${count}D`,
nextLine: (count = 1) => `${CSI2}E`.repeat(count),
prevLine: (count = 1) => `${CSI2}F`.repeat(count),
left: `${CSI2}G`,
hide: `${CSI2}?25l`,
show: `${CSI2}?25h`,
save: `${ESC2}7`,
restore: `${ESC2}8`
};
var scroll = {
up: (count = 1) => `${CSI2}S`.repeat(count),
down: (count = 1) => `${CSI2}T`.repeat(count)
};
var erase = {
screen: `${CSI2}2J`,
up: (count = 1) => `${CSI2}1J`.repeat(count),
down: (count = 1) => `${CSI2}J`.repeat(count),
line: `${CSI2}2K`,
lineEnd: `${CSI2}K`,
lineStart: `${CSI2}1K`,
lines(count) {
let clear = "";
for (let i = 0;i < count; i++)
clear += this.line + (i < count - 1 ? cursor.up() : "");
if (count)
clear += cursor.left;
return clear;
}
};
module.exports = { cursor, scroll, erase, beep };
});
// node_modules/picocolors/picocolors.js
var require_picocolors = __commonJS((exports, module) => {
var p2 = process || {};
var argv = p2.argv || [];
var env = p2.env || {};
var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p2.platform === "win32" || (p2.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
var formatter = (open, close, replace = open) => (input) => {
let string = "" + input, index = string.indexOf(close, open.length);
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
};
var replaceClose = (string, close, replace, index) => {
let result = "", cursor3 = 0;
do {
result += string.substring(cursor3, index) + replace;
cursor3 = index + close.length;
index = string.indexOf(close, cursor3);
} while (~index);
return result + string.substring(cursor3);
};
var createColors = (enabled = isColorSupported) => {
let f2 = enabled ? formatter : () => String;
return {
isColorSupported: enabled,
reset: f2("\x1B[0m", "\x1B[0m"),
bold: f2("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
dim: f2("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
italic: f2("\x1B[3m", "\x1B[23m"),
underline: f2("\x1B[4m", "\x1B[24m"),
inverse: f2("\x1B[7m", "\x1B[27m"),
hidden: f2("\x1B[8m", "\x1B[28m"),
strikethrough: f2("\x1B[9m", "\x1B[29m"),
black: f2("\x1B[30m", "\x1B[39m"),
red: f2("\x1B[31m", "\x1B[39m"),
green: f2("\x1B[32m", "\x1B[39m"),
yellow: f2("\x1B[33m", "\x1B[39m"),
blue: f2("\x1B[34m", "\x1B[39m"),
magenta: f2("\x1B[35m", "\x1B[39m"),
cyan: f2("\x1B[36m", "\x1B[39m"),
white: f2("\x1B[37m", "\x1B[39m"),
gray: f2("\x1B[90m", "\x1B[39m"),
bgBlack: f2("\x1B[40m", "\x1B[49m"),
bgRed: f2("\x1B[41m", "\x1B[49m"),
bgGreen: f2("\x1B[42m", "\x1B[49m"),
bgYellow: f2("\x1B[43m", "\x1B[49m"),
bgBlue: f2("\x1B[44m", "\x1B[49m"),
bgMagenta: f2("\x1B[45m", "\x1B[49m"),
bgCyan: f2("\x1B[46m", "\x1B[49m"),
bgWhite: f2("\x1B[47m", "\x1B[49m"),
blackBright: f2("\x1B[90m", "\x1B[39m"),
redBright: f2("\x1B[91m", "\x1B[39m"),
greenBright: f2("\x1B[92m", "\x1B[39m"),
yellowBright: f2("\x1B[93m", "\x1B[39m"),
blueBright: f2("\x1B[94m", "\x1B[39m"),
magentaBright: f2("\x1B[95m", "\x1B[39m"),
cyanBright: f2("\x1B[96m", "\x1B[39m"),
whiteBright: f2("\x1B[97m", "\x1B[39m"),
bgBlackBright: f2("\x1B[100m", "\x1B[49m"),
bgRedBright: f2("\x1B[101m", "\x1B[49m"),
bgGreenBright: f2("\x1B[102m", "\x1B[49m"),
bgYellowBright: f2("\x1B[103m", "\x1B[49m"),
bgBlueBright: f2("\x1B[104m", "\x1B[49m"),
bgMagentaBright: f2("\x1B[105m", "\x1B[49m"),
bgCyanBright: f2("\x1B[106m", "\x1B[49m"),
bgWhiteBright: f2("\x1B[107m", "\x1B[49m")
};
};
module.exports = createColors();
module.exports.createColors = createColors;
});
// src/migration/operations.ts
function addTable(table) {
return { type: "addTable", table: { ...table, indexes: table.indexes ?? [] } };
}
function dropTable(tableName, columns) {
return { type: "dropTable", tableName, columns };
}
function renameTable(from, to) {
return { type: "renameTable", from, to };
}
function addColumn(tableName, column) {
return { type: "addColumn", tableName, column };
}
function dropColumn(tableName, columnName) {
return { type: "dropColumn", tableName, columnName };
}
function renameColumn(tableName, from, to) {
return { type: "renameColumn", tableName, from, to };
}
function createIndex(tableName, index) {
return { type: "createIndex", tableName, index };
}
function dropIndex(indexName) {
return { type: "dropIndex", indexName };
}
function modifyColumn(tableName, columnName, changes) {
return { type: "modifyColumn", tableName, columnName, changes };
}
function modifyIndex(tableName, indexName, from, to) {
return { type: "modifyIndex", tableName, indexName, from, to };
}
function rebuildTable(tableName, oldTable, newTable) {
return { type: "rebuildTable", tableName, oldTable, newTable };
}
// src/migration/diff.ts
var exports_diff = {};
__export(exports_diff, {
resolveRenames: () => resolveRenames,
emptyState: () => emptyState,
diffSchemas: () => diffSchemas,
CancellationError: () => CancellationError
});
function topologicalSort(tables) {
const deps = new Map;
for (const table of tables) {
const tableDeps = new Set;
for (const col of table.columns) {
if (col.referencesTable && col.referencesTable !== table.name) {
tableDeps.add(col.referencesTable);
}
}
deps.set(table.name, tableDeps);
}
const tableByName = new Map(tables.map((t2) => [t2.name, t2]));
const remaining = new Set(tables.map((t2) => t2.name));
const sorted = [];
while (remaining.size > 0) {
const ready = [...remaining].filter((name) => {
const d = deps.get(name);
return [...d].every((dep) => !remaining.has(dep));
});
if (ready.length === 0) {
const cycle = [...remaining].join(", ");
throw new Error(`Circular foreign key dependency detected: ${cycle}`);
}
for (const name of ready) {
sorted.push(tableByName.get(name));
remaining.delete(name);
}
}
return sorted;
}
function diffColumns(tableName, prevCols, currCols) {
const ops = [];
let unsafe = false;
const prevByName = new Map(prevCols.map((c3) => [c3.name, c3]));
const currByName = new Map(currCols.map((c3) => [c3.name, c3]));
for (const [name, col] of currByName) {
if (!prevByName.has(name)) {
ops.push(addColumn(tableName, col));
}
}
for (const [name] of prevByName) {
if (!currByName.has(name)) {
ops.push(dropColumn(tableName, name));
}
}
for (const [name, currCol] of currByName) {
const prevCol = prevByName.get(name);
if (!prevCol)
continue;
const changes = {};
let hasChanges = false;
if (prevCol.sqlType !== currCol.sqlType) {
unsafe = true;
}
if (prevCol.isPrimaryKey !== currCol.isPrimaryKey) {
unsafe = true;
}
if (prevCol.isAutoIncrement !== undefined && (prevCol.isAutoIncrement ?? false) !== (currCol.isAutoIncrement ?? false)) {
unsafe = true;
}
if (prevCol.isNotNull !== currCol.isNotNull) {
if (prevCol.isNotNull && !currCol.isNotNull) {
unsafe = true;
}
if (!prevCol.isNotNull && currCol.isNotNull) {
if (!currCol.hasDefault) {
unsafe = true;
} else {
changes.isNotNull = true;
hasChanges = true;
}
}
}
if (prevCol.isUnique !== currCol.isUnique) {
unsafe = true;
}
if (prevCol.hasDefault !== currCol.hasDefault || prevCol.defaultValue !== currCol.defaultValue) {
if (prevCol.hasDefault && !currCol.hasDefault) {
unsafe = true;
}
if (!unsafe || hasChanges) {
changes.hasDefault = currCol.hasDefault;
changes.defaultValue = currCol.defaultValue;
hasChanges = true;
}
}
if (prevCol.referencesTable !== currCol.referencesTable || prevCol.referencesColumn !== currCol.referencesColumn) {
unsafe = true;
}
const hadFk = !!prevCol.referencesTable;
const hasFk = !!currCol.referencesTable;
if (hadFk !== hasFk) {
unsafe = true;
}
if (prevCol.onDelete !== currCol.onDelete || prevCol.onUpdate !== currCol.onUpdate) {
unsafe = true;
}
if (hasChanges) {
ops.push(modifyColumn(tableName, name, changes));
}
}
return { ops, unsafe };
}
function diffTable(tableName, prev, curr) {
const { ops: columnOps, unsafe } = diffColumns(tableName, prev.columns, curr.columns);
if (unsafe) {
return [rebuildTable(tableName, prev, curr)];
}
const prevPK = prev.primaryKeyColumns ?? [];
const currPK = curr.primaryKeyColumns ?? [];
if (JSON.stringify(prevPK) !== JSON.stringify(currPK)) {
return [rebuildTable(tableName, prev, curr)];
}
const ops = [...columnOps];
const prevIndexes = new Map(prev.indexes.map((i2) => [i2.name, i2]));
const currIndexes = new Map(curr.indexes.map((i2) => [i2.name, i2]));
for (const [name, idx] of currIndexes) {
if (!prevIndexes.has(name)) {
ops.push(createIndex(tableName, idx));
}
}
for (const [name] of prevIndexes) {
if (!currIndexes.has(name)) {
ops.push(dropIndex(name));
}
}
for (const [name, currIdx] of currIndexes) {
const prevIdx = prevIndexes.get(name);
if (!prevIdx)
continue;
const columnsChanged = JSON.stringify(prevIdx.columns) !== JSON.stringify(currIdx.columns);
const uniqueChanged = prevIdx.unique !== currIdx.unique;
if (columnsChanged || uniqueChanged) {
ops.push(modifyIndex(tableName, name, prevIdx, currIdx));
}
}
return ops;
}
function diffSchemas(previous, current) {
const ops = [];
const prevTables = new Map(Object.entries(previous.tables));
const currTables = new Map(Object.entries(current.tables));
const addedTables = [...currTables.entries()].filter(([name]) => !prevTables.has(name)).map(([, table]) => table);
const sortedAdded = topologicalSort(addedTables);
for (const table of sortedAdded) {
ops.push(addTable(table));
}
const droppedTables = [...prevTables.entries()].filter(([name]) => !currTables.has(name)).map(([, table]) => table);
const sortedDropped = topologicalSort(droppedTables).reverse();
for (const table of sortedDropped) {
ops.push(dropTable(table.name, table.columns));
}
for (const [name, prevTable] of prevTables) {
const currTable = currTables.get(name);
if (currTable) {
ops.push(...diffTable(name, prevTable, currTable));
}
}
return ops;
}
function emptyState() {
return { version: 1, tables: {} };
}
async function resolveRenames(operations, options) {
const interactive = options?.interactive ?? true;
const prompt = options?.prompt;
const renameGroups = new Map;
const droppedTables = operations.filter((op) => op.type === "dropTable");
const addedTables = operations.filter((op) => op.type === "addTable");
for (const dropped of droppedTables) {
for (const added of addedTables) {
const oldColNames = new Set((dropped.columns ?? []).map((c3) => c3.name));
if (oldColNames.size === 0)
continue;
const newColNames = added.table.columns.map((c3) => c3.name);
const overlap = newColNames.filter((name) => oldColNames.has(name));
if (overlap.length > 0) {
const key = `table:${added.table.name}`;
if (!renameGroups.has(key))
renameGroups.set(key, []);
renameGroups.get(key).push({
type: "table",
tableName: added.table.name,
from: dropped.tableName,
to: added.table.name
});
}
}
}
const droppedColumns = operations.filter((op) => op.type === "dropColumn");
const addedColumns = operations.filter((op) => op.type === "addColumn");
for (const added of addedColumns) {
const candidates = droppedColumns.filter((d) => d.tableName === added.tableName);
if (candidates.length > 0) {
const key = `column:${added.tableName}:${added.column.name}`;
if (!renameGroups.has(key))
renameGroups.set(key, []);
for (const dropped of candidates) {
renameGroups.get(key).push({
type: "column",
tableName: added.tableName,
from: dropped.columnName,
to: added.column.name
});
}
}
}
if (renameGroups.size === 0) {
return operations;
}
if (!interactive) {
return operations;
}
const resolvedOps = [...operations];
const consumedDrops = new Set;
for (const [, renames] of renameGroups) {
const firstRename = renames[0];
const entityLabel = firstRename.type === "table" ? "table" : "column";
const dropType = firstRename.type === "table" ? "dropTable" : "dropColumn";
const availableRenames = renames.filter((r2) => !consumedDrops.has(`${dropType}:${r2.tableName}:${r2.from}`));
if (availableRenames.length === 0) {
continue;
}
const promptOptions = [
{
value: "add",
label: firstRename.to,
hint: firstRename.type === "table" ? "create table" : "add column"
},
...availableRenames.map((r2) => ({
value: `rename:${r2.from}`,
label: `${r2.from} \u2192 ${r2.to}`,
hint: `rename ${entityLabel}`
}))
];
if (!prompt) {
continue;
}
const result = await prompt(`Is ${firstRename.to} ${entityLabel} added or renamed?`, promptOptions);
if (typeof result === "symbol") {
throw new CancellationError("Operation cancelled.");
}
if (result !== "add") {
const fromName = result.replace("rename:", "");
const rename = availableRenames.find((r2) => r2.from === fromName);
if (!rename) {
continue;
}
consumedDrops.add(`${dropType}:${rename.tableName}:${rename.from}`);
const renameOp = rename.type === "table" ? renameTable(rename.from, rename.to) : renameColumn(rename.tableName, rename.from, rename.to);
const dropIdx = resolvedOps.findIndex((op) => {
if (op.type !== dropType)
return false;
if (rename.type === "table") {
return op.tableName === rename.from;
}
return op.columnName === rename.from && op.tableName === rename.tableName;
});
let oldColNames;
if (dropIdx !== -1 && rename.type === "table") {
const dropOp = resolvedOps[dropIdx];
oldColNames = new Set((dropOp.columns ?? []).map((c3) => c3.name));
resolvedOps.splice(dropIdx, 1);
} else if (dropIdx !== -1) {
resolvedOps.splice(dropIdx, 1);
}
const addType = rename.type === "table" ? "addTable" : "addColumn";
const addIdx = resolvedOps.findIndex((op) => {
if (op.type !== addType)
return false;
if (rename.type === "table") {
return op.table?.name === rename.to;
}
return op.column?.name === rename.to && op.tableName === rename.tableName;
});
if (rename.type === "table" && addIdx !== -1 && oldColNames) {
const addOp = resolvedOps[addIdx];
for (const col of addOp.table.columns) {
if (!oldColNames.has(col.name)) {
resolvedOps.push(addColumn(rename.from, col));
}
}
}
if (addIdx !== -1) {
resolvedOps.splice(addIdx, 1);
}
if (rename.type === "table") {
for (const op of resolvedOps) {
if (op.type === "addColumn" && op.tableName === rename.to) {
op.tableName = rename.from;
}
}
}
resolvedOps.push(renameOp);
}
}
return resolvedOps;
}
var CancellationError;
var init_diff = __esm(() => {
CancellationError = class CancellationError extends Error {
constructor(message) {
super(message);
this.name = "CancellationError";
}
};
});
// src/migration/serialize.ts
var exports_serialize = {};
__export(exports_serialize, {
serializeSchema: () => serializeSchema
});
function serializeColumn(col) {
const internal = col.__internal;
const result = {
name: col.name,
sqlType: internal.sqlType,
isPrimaryKey: internal.isPrimaryKey,
isAutoIncrement: internal.isAutoIncrement ?? false,
isNotNull: internal.isNotNull,
isUnique: internal.isUnique,
hasDefault: internal.hasDefault,
defaultValue: internal.defaultValue
};
if (result.isAutoIncrement && !result.isPrimaryKey) {
throw new Error(`Column "${col.name}": autoIncrement() requires primaryKey()`);
}
if (internal.referencesTable && internal.referencesColumn) {
result.referencesTable = internal.referencesTable;
result.referencesColumn = internal.referencesColumn;
if (internal.onDelete && internal.onDelete !== "no action")
result.onDelete = internal.onDelete;
if (internal.onUpdate && internal.onUpdate !== "no action")
result.onUpdate = internal.onUpdate;
}
return result;
}
function serializeTable(table) {
const tableName = table._.name;
const columns = [];
const indexes = [];
for (const [key, value] of Object.entries(table)) {
if (key === "_")
continue;
if (value && typeof value === "object" && "__internal" in value) {
columns.push(serializeColumn(value));
}
}
const tableObj = table;
if (tableObj.__indexes) {
for (const idx of tableObj.__indexes) {
indexes.push({
name: idx.name,
columns: idx.columns,
unique: idx.unique
});
}
}
let primaryKeyColumns;
if (tableObj.__primaryKey) {
const pkDef = tableObj.__primaryKey;
primaryKeyColumns = pkDef.columns;
for (const col of columns) {
if (col.isPrimaryKey) {
throw new Error(`Column "${col.name}" has primaryKey() but table "${tableName}" also defines a composite primaryKey(). Use one or the other.`);
}
}
}
return { name: tableName, columns, indexes, primaryKeyColumns };
}
function serializeSchema(tables) {
const tableMap = {};
for (const t2 of tables) {
const serialized = serializeTable(t2);
tableMap[serialized.name] = serialized;
}
return {
version: 1,
tables: tableMap
};
}
// src/migration/sql.ts
var exports_sql = {};
__export(exports_sql, {
generateSQLStatements: () => generateSQLStatements,
generateSQL: () => generateSQL
});
function sqlType(col) {
return col.sqlType.toUpperCase();
}
function columnToDDL(col, isCompositePK = false) {
const parts = [col.name, sqlType(col)];
if (col.isPrimaryKey && !isCompositePK)
parts.push("PRIMARY KEY");
if (col.isAutoIncrement === true)
parts.push("AUTOINCREMENT");
if (col.isNotNull && !col.isPrimaryKey)
parts.push("NOT NULL");
if (col.isUnique && !col.isPrimaryKey)
parts.push("UNIQUE");
if (col.hasDefault) {
const val = col.defaultValue;
if (typeof val === "string") {
parts.push(`DEFAULT '${val.replace(/'/g, "''")}'`);
} else if (val === null) {
parts.push("DEFAULT NULL");
} else {
parts.push(`DEFAULT ${val}`);
}
}
if (col.referencesTable && col.referencesColumn) {
let fkClause = `REFERENCES ${col.referencesTable}(${col.referencesColumn})`;
if (col.onDelete)
fkClause += ` ON DELETE ${col.onDelete.toUpperCase()}`;
if (col.onUpdate)
fkClause += ` ON UPDATE ${col.onUpdate.toUpperCase()}`;
parts.push(fkClause);
}
return parts.join(" ");
}
function indexToSQL(idx, tableName) {
const unique = idx.unique ? "UNIQUE " : "";
const cols = idx.columns.join(", ");
return `CREATE ${unique}INDEX ${idx.name} ON ${tableName} (${cols})`;
}
function formatDefault(value) {
if (value === null)
return "NULL";
if (typeof value === "string")
return `'${value.replace(/'/g, "''")}'`;
if (typeof value === "boolean")
return value ? "1" : "0";
return String(value);
}
function rebuildTableToSQL(op) {
const { tableName, oldTable, newTable } = op;
const tempName = `_flint_rebuild_${tableName}`;
const stmts = [];
stmts.push("PRAGMA defer_foreign_keys = 1");
const isComposite = newTable.primaryKeyColumns && newTable.primaryKeyColumns.length > 0;
const colDefs = newTable.columns.map((c3) => columnToDDL(c3, isComposite));
if (isComposite) {
colDefs.push(`PRIMARY KEY(${newTable.primaryKeyColumns.join(", ")})`);
}
const cols = colDefs.join(`,
`);
stmts.push(`CREATE TABLE ${tempName} (
${cols}
)`);
const oldColSet = new Set(oldTable.columns.map((c3) => c3.name));
const insertCols = [];
const selectExprs = [];
for (const newCol of newTable.columns) {
insertCols.push(newCol.name);
if (oldColSet.has(newCol.name)) {
selectExprs.push(newCol.name);
} else {
selectExprs.push(`DEFAULT`);
}
}
stmts.push(`INSERT INTO ${tempName} (${insertCols.join(", ")}) SELECT ${selectExprs.join(", ")} FROM ${tableName}`);
stmts.push(`DROP TABLE ${tableName}`);
stmts.push(`ALTER TABLE ${tempName} RENAME TO ${tableName}`);
for (const idx of newTable.indexes) {
stmts.push(indexToSQL(idx, tableName));
}
return stmts;
}
function operationToSQL(op) {
switch (op.type) {
case "addTable": {
const isComposite = op.table.primaryKeyColumns && op.table.primaryKeyColumns.length > 0;
const colDefs = op.table.columns.map((c3) => columnToDDL(c3, isComposite));
if (isComposite) {
colDefs.push(`PRIMARY KEY(${op.table.primaryKeyColumns.join(", ")})`);
}
const cols = colDefs.join(`,
`);
const stmts = [`CREATE TABLE ${op.table.name} (
${cols}
)`];
for (const idx of op.table.indexes) {
stmts.push(indexToSQL(idx, op.table.name));
}
return stmts;
}
case "dropTable":
return [`DROP TABLE ${op.tableName}`];
case "renameTable":
return [`ALTER TABLE ${op.from} RENAME TO ${op.to}`];
case "addColumn":
return [`ALTER TABLE ${op.tableName} ADD COLUMN ${columnToDDL(op.column)}`];
case "dropColumn":
return [`ALTER TABLE ${op.tableName} DROP COLUMN ${op.columnName}`];
case "renameColumn":
return [`ALTER TABLE ${op.tableName} RENAME COLUMN ${op.from} TO ${op.to}`];
case "createIndex":
return [indexToSQL(op.index, op.tableName)];
case "dropIndex":
return [`DROP INDEX ${op.indexName}`];
case "modifyColumn": {
const stmts = [];
if (op.changes.isNotNull !== undefined && op.changes.isNotNull) {
stmts.push(`ALTER TABLE ${op.tableName} ALTER COLUMN ${op.columnName} SET NOT NULL`);
}
if (op.changes.hasDefault !== undefined) {
if (op.changes.hasDefault && op.changes.defaultValue !== undefined) {
const val = formatDefault(op.changes.defaultValue);
stmts.push(`ALTER TABLE ${op.tableName} ALTER COLUMN ${op.columnName} SET DEFAULT ${val}`);
} else if (!op.changes.hasDefault) {
stmts.push(`ALTER TABLE ${op.tableName} ALTER COLUMN ${op.columnName} DROP DEFAULT`);
}
}
return stmts;
}
case "modifyIndex":
return [`DROP INDEX IF EXISTS ${op.indexName}`, indexToSQL(op.to, op.tableName)];
case "rebuildTable":
return rebuildTableToSQL(op);
}
}
function generateSQL(operations) {
return operations.flatMap(operationToSQL).join(`;
`) + ";";
}
function generateSQLStatements(operations) {
return operations.flatMap(operationToSQL);
}
// src/migration/generate.ts
var exports_generate = {};
__export(exports_generate, {
generate: () => generate
});
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from "fs";
import { join } from "path";
function randomWords() {
const adj = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
return `${adj}_${noun}`;
}
function generateFolderName(migrationName) {
const timestamp = Math.floor(Date.now() / 1000);
if (migrationName) {
return `${timestamp}_${migrationName}`;
}
const words = randomWords();
return `${timestamp}_${words}`;
}
function findLatestState(migrationsDir) {
if (!existsSync(migrationsDir))
return null;
const entries = readdirSync(migrationsDir);
const migrationFolders = entries.filter((e) => /^\d{10}_/.test(e)).sort().reverse();
for (const folder of migrationFolders) {
const statePath = join(migrationsDir, folder, "state.json");
if (existsSync(statePath)) {
return JSON.parse(readFileSync(statePath, "utf-8"));
}
}
return null;
}
function serializeOpArg(op) {
switch (op.type) {
case "addTable":
return serializeTableArg(op.table);
case "dropTable":
return JSON.stringify(op.tableName);
case "renameTable":
return `${JSON.stringify(op.from)}, ${JSON.stringify(op.to)}`;
case "addColumn":
return `${JSON.stringify(op.tableName)}, ${serializeColumnArg(op.column)}`;
case "dropColumn":
return `${JSON.stringify(op.tableName)}, ${JSON.stringify(op.columnName)}`;
case "renameColumn":
return `${JSON.stringify(op.tableName)}, ${JSON.stringify(op.from)}, ${JSON.stringify(op.to)}`;
case "createIndex":
return `${JSON.stringify(op.tableName)}, ${serializeIndexArg(op.index)}`;
case "dropIndex":
return JSON.stringify(op.indexName);
case "modifyColumn":
return `${JSON.stringify(op.tableName)}, ${JSON.stringify(op.columnName)}, ${JSON.stringify(op.changes)}`;
case "modifyIndex":
return `${JSON.stringify(op.tableName)}, ${JSON.stringify(op.indexName)}, ${serializeIndexArg(op.from)}, ${serializeIndexArg(op.to)}`;
case "rebuildTable":
return `${JSON.stringify(op.tableName)}, ${serializeTableArg(op.oldTable)}, ${serializeTableArg(op.newTable)}`;
}
}
function serializeColumnArg(col) {
return JSON.stringify(col);
}
function serializeIndexArg(idx) {
return `{ name: ${JSON.stringify(idx.name)}, columns: ${JSON.stringify(idx.columns)}, unique: ${idx.unique} }`;
}
function serializeTableArg(table) {
const cols = table.columns.map((c3) => serializeColumnArg(c3)).join(`,
`);
const idxs = table.indexes.map((i2) => serializeIndexArg(i2)).join(`,
`);
let arg = `{
name: ${JSON.stringify(table.name)},
columns: [
${cols}
]`;
if (table.indexes.length > 0) {
arg += `,
indexes: [
${idxs}
]`;
}
arg += `
}`;
return arg;
}
async function generate(tables, migrationsDir, nameOrOptions) {
const options = typeof nameOrOptions === "string" ? { name: nameOrOptions } : nameOrOptions ?? {};
const migrationName = options.name;
const previous = findLatestState(migrationsDir) ?? emptyState();
const current = serializeSchema(tables);
const rawOps = diffSchemas(previous, current);
if (rawOps.length === 0) {
throw new Error("No changes detected \u2014 schema is already up to date.");
}
const operations = await resolveRenames(rawOps, { interactive: options.interactive, prompt: options.prompt });
const sql = generateSQL(operations);
const folderName = generateFolderName(migrationName);
const migrationDir = join(migrationsDir, folderName);
mkdirSync(migrationDir, { recursive: true });
const uniqueOps = [...new Set(operations.map((op) => op.type))];
const imports = uniqueOps.map((op) => `import { ${op} } from "flint-orm/migration";`).join(`
`);
const operationLines = operations.map((op) => {
const arg = serializeOpArg(op);
return ` ${op.type}(${arg}),`;
}).join(`
`);
const displayName = migrationName ?? folderName;
const migrationContent = `// Migration: ${displayName}
// Generated by flint generate
import { defineMigration } from "flint-orm/migration";
${imports}
export default defineMigration({
name: "${displayName}",
operations: [
${operationLines}
],
});
`;
writeFileSync(join(migrationDir, "migration.ts"), migrationContent);
writeFileSync(join(migrationDir, "state.json"), JSON.stringify(current, null, 2));
return {
folderName,
operations,
sql,
state: current
};
}
var ADJECTIVES, NOUNS;
var init_generate = __esm(() => {
init_diff();
ADJECTIVES = [
"amber",
"azure",
"blank",
"brisk",
"calm",
"clear",
"cold",
"cool",
"crisp",
"dark",
"deep",
"dull",
"dusk",
"dawn",
"fair",
"faint",
"flat",
"foggy",
"frost",
"glad",
"golden",
"gray",
"green",
"gross",
"happy",
"harsh",
"hazy",
"keen",
"kind",
"light",
"lively",
"long",
"loud",
"lucky",
"mild",
"misty",
"mossy",
"neat",
"noble",
"odd",
"pale",
"plain",
"proud",
"pure",
"quick",
"quiet",
"rare",
"raw",
"rich",
"ripe",
"rough",
"royal",
"rusty",
"sharp",
"sheer",
"shiny",
"silent",
"silver",
"sleek",
"slim",
"slow",
"smooth",
"soft",
"solid",
"sour",
"stark",
"steep",
"stern",
"still",
"stout",
"strict",
"swift",
"tall",
"tame",
"thin",
"tidy",
"tough",
"vast",
"vivid",
"warm",
"wild",
"wise",
"young"
];
NOUNS = [
"badger",
"birch",
"bison",
"bloom",
"brick",
"brook",
"cedar",
"cider",
"clay",
"cobra",
"coral",
"crane",
"creek",
"crow",
"deer",
"delta",
"dune",
"eagle",
"elm",
"ember",
"fern",
"finch",
"flint",
"fox",
"glacier",
"gorse",
"granite",
"hare",
"hawk",
"hazel",
"heron",
"hickory",
"hornet",
"ivy",
"jay",
"jasper",
"kestrel",
"kite",
"larch",
"lea",
"lichen",
"linen",
"lion",
"maple",
"marsh",
"mink",
"moss",
"moth",
"newt",
"oak",
"onyx",
"otter",
"owl",
"pearl",
"pine",
"plume",
"quail",
"quartz",
"rabbit",
"rain",
"raven",
"ridge",
"river",
"robin",
"rose",
"sage",
"salmon",
"scarab",
"shale",
"silk",
"skunk",
"slate",
"sparrow",
"spice",
"stone",
"storm",
"swift",
"thorn",
"tide",
"tile",
"toad",
"tulip",
"vale",
"viper",
"wasp",
"willow",
"wren",
"yarrow"
];
});
// src/migration/migrate.ts
var exports_migrate = {};
__export(exports_migrate, {
migrate: () => migrate,
getMigrationStatus: () => getMigrationStatus
});
import { existsSync as existsSync2, readdirSync as readdirSync2 } from "fs";
import { join as join2 } from "path";
import { pathToFileURL } from "url";
async function ensureTrackingTable(executor) {
await executor.run(`CREATE TABLE IF NOT EXISTS ${TRACKING_TABLE} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
applied_at INTEGER NOT NULL
)`, []);
}
async function getAppliedMigrations(executor) {
const rows = await executor.all(`SELECT name FROM ${TRACKING_TABLE} ORDER BY id`, []);
return new Set(rows.map((r2) => r2.name));
}
async function recordMigration(executor, name) {
await executor.run(`INSERT INTO ${TRACKING_TABLE} (name, applied_at) VALUES (?, ?)`, [name, Date.now()]);
}
async function checkIncomingForeignKeys(executor, tableName) {
const allTables = await executor.all(`SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__flint_%'`, []);
const referencing = [];
for (const { name } of allTables) {
if (name === tableName)
continue;
const fkRows = await executor.all(`PRAGMA foreign_key_list('${name}')`, []);
if (fkRows.some((fk) => fk.table === tableName)) {
referencing.push(name);
}
}
if (referencing.length > 0) {
throw new Error(`Cannot rebuild "${tableName}" \u2014 referenced by: ${referencing.join(", ")}. ` + `Rebuild or migrate those tables first.`);
}
}
function discoverMigrations(migrationsDir) {
if (!existsSync2(migrationsDir))
return [];
const entries = readdirSync2(migrationsDir);
const migrationFolders = entries.filter((e) => /^\d{10}_/.test(e)).sort();
return migrationFolders.map((folder) => {
const name = folder.replace(/^\d{10}_/, "");
return {
folderName: folder,
name,
path: join2(migrationsDir, folder)
};
});
}
async function loadMigration(entry) {
const migrationPath = join2(entry.path, "migration.ts");
if (!existsSync2(migrationPath)) {
throw new Error(`Migration file not found: ${migrationPath}`);
}
const url = pathToFileURL(migrationPath).href;
const mod = await import(url);
const migration = mod.default;
if (!migration || !migration.operations || !Array.isArray(migration.operations)) {
throw new Error(`Invalid migration file: ${migrationPath}`);
}
return migration;
}
async function migrate(executor, options) {
const { migrationsDir, dryRun = false } = options;
if (!dryRun) {
await ensureTrackingTable(executor);
}
const applied = dryRun ? new Set : await getAppliedMigrations(executor);
const allMigrations = discoverMigrations(migrationsDir);
const pending = allMigrations.filter((m2) => !applied.has(m2.folderName));
if (pending.length === 0) {
return { applied: [], skipped: allMigrations.map((m2) => m2.folderName) };
}
if (dryRun) {
return {
applied: pending.map((m2) => m2.folderName),
skipped: allMigrations.filter((m2) => applied.has(m2.folderName)).map((m2) => m2.folderName)
};
}
const newlyApplied = [];
for (const entry of pending) {
const migration = await loadMigration(entry);
for (const op of migration.operations) {
if (op.type === "rebuildTable") {
await checkIncomingForeignKeys(executor, op.tableName);
}
}
const statements = generateSQLStatements(migration.operations);
await executor.transaction(async () => {
for (const stmt of statements) {
await executor.run(stmt, []);
}
await recordMigration(executor, entry.folderName);
});
newlyApplied.push(entry.folderName);
}
return {
applied: newlyApplied,
skipped: allMigrations.filter((m2) => applied.has(m2.folderName)).map((m2) => m2.folderName)
};
}
async function getMigrationStatus(executor, migrationsDir) {
await ensureTrackingTable(executor);
const appliedNames = await getAppliedMigrations(executor);
const allMigrations = discoverMigrations(migrationsDir);
return {
applied: allMigrations.filter((m2) => appliedNames.has(m2.folderName)).map((m2) => ({ name: m2.name, folderName: m2.folderName })),
pending: allMigrations.filter((m2) => !appliedNames.has(m2.folderName)).map((m2) => ({ name: m2.name, folderName: m2.folderName }))
};
}
var TRACKING_TABLE = "__flint_migrations";
var init_migrate = () => {};
// 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((c3) => compileCondition(c3, params)).join(" AND ");
case "or":
return `(${cond.conditions.map((c3) => compileCondition(c3, params)).join(" OR ")})`;
}
}
function compileConditions(conditions, params) {
if (conditions.length === 0)
return "1=1";
return conditions.map((c3) => compileCondition(c3, 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((t2) => columnEntries(t2).map(([, c3]) => c3)));
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(([, c3]) => prefix ? `${prefix}.${c3.name}` : c3.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(n3) {
return new SelectBuilder(this.#executor, this.#tableName, this.#table, this.#conditions, this.#selectedColumns, this.#orderByClauses, n3, this.#offsetValue, this.#distinct);
}
offset(n3) {
return new SelectBuilder(this.#executor, this.#tableName, this.#table, this.#conditions, this.#selectedColumns, this.#orderByClauses, this.#limitValue, n3, 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((o2) => `${o2.column.name} ${o2.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((r2) => decodeSelectedRow(r2, this.#table, this.#selectedColumns));
}
return records.map((r2) => decodeRow(r2, 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(n3) {
return new NarrowedSelectBuilder(this.#executor, this.#tableName, this.#table, this.#conditions, this.#selectedColumns, this.#orderByClauses, n3, this.#offsetValue, this.#distinct);
}
offset(n3) {
return new NarrowedSelectBuilder(this.#executor, this.#tableName, this.#table, this.#conditions, this.#selectedColumns, this.#orderByClauses, this.#limitValue, n3, this.#distinct);
}
toSQL() {
validateColumnOwnership(this.#conditi