UNPKG

flint-orm

Version:

Type-safe SQLite ORM for JavaScript

1,084 lines (1,073 loc) 32.9 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/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 };