flint-orm
Version:
Type-safe SQLite ORM for JavaScript
1,084 lines (1,073 loc) • 32.9 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/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 t of tables) {
const serialized = serializeTable(t);
tableMap[serialized.name] = serialized;
}
return {
version: 1,
tables: tableMap
};
}
// 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((t) => [t.name, t]));
const remaining = new Set(tables.map((t) => t.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((c) => [c.name, c]));
const currByName = new Map(currCols.map((c) => [c.name, c]));
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((i) => [i.name, i]));
const currIndexes = new Map(curr.indexes.map((i) => [i.name, i]));
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((c) => c.name));
if (oldColNames.size === 0)
continue;
const newColNames = added.table.columns.map((c) => c.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((r) => !consumedDrops.has(`${dropType}:${r.tableName}:${r.from}`));
if (availableRenames.length === 0) {
continue;
}
const promptOptions = [
{
value: "add",
label: firstRename.to,
hint: firstRename.type === "table" ? "create table" : "add column"
},
...availableRenames.map((r) => ({
value: `rename:${r.from}`,
label: `${r.from} \u2192 ${r.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((r) => r.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((c) => c.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/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((c) => columnToDDL(c, 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((c) => c.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((c) => columnToDDL(c, 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((c) => serializeColumnArg(c)).join(`,
`);
const idxs = table.indexes.map((i) => serializeIndexArg(i)).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((r) => r.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((m) => !applied.has(m.folderName));
if (pending.length === 0) {
return { applied: [], skipped: allMigrations.map((m) => m.folderName) };
}
if (dryRun) {
return {
applied: pending.map((m) => m.folderName),
skipped: allMigrations.filter((m) => applied.has(m.folderName)).map((m) => m.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((m) => applied.has(m.folderName)).map((m) => m.folderName)
};
}
async function getMigrationStatus(executor, migrationsDir) {
await ensureTrackingTable(executor);
const appliedNames = await getAppliedMigrations(executor);
const allMigrations = discoverMigrations(migrationsDir);
return {
applied: allMigrations.filter((m) => appliedNames.has(m.folderName)).map((m) => ({ name: m.name, folderName: m.folderName })),
pending: allMigrations.filter((m) => !appliedNames.has(m.folderName)).map((m) => ({ name: m.name, folderName: m.folderName }))
};
}
var TRACKING_TABLE = "__flint_migrations";
var init_migrate = () => {};
// src/migration/migration.ts
function defineMigration(config) {
return {
name: config.name,
operations: config.operations
};
}
// src/migration/index.ts
init_diff();
init_generate();
init_migrate();
export {
serializeSchema,
resolveRenames,
renameTable,
renameColumn,
rebuildTable,
modifyIndex,
modifyColumn,
migrate,
getMigrationStatus,
generateSQL,
generate,
emptyState,
dropTable,
dropIndex,
dropColumn,
diffSchemas,
defineMigration,
createIndex,
addTable,
addColumn,
CancellationError
};