import type { DatabaseAdapter } from "./types.js"; import type { DiscoveredModel } from "./model.js"; /** * If stmt is an ALTER TABLE ... ADD on Firebird and the column already exists, * returns a skip reason string. Returns null if the statement should execute normally. * * Exported (like its shouldSkipCreateTable sibling) so it can be driven * directly against a REAL Firebird connection in * test/migrationContract.test.ts -- no fake adapter needed. */ export declare function shouldSkipForFirebird(db: DatabaseAdapter, stmt: string): Promise; /** * Make CREATE TABLE idempotent on engines lacking IF NOT EXISTS. * * Firebird and MSSQL do not support `CREATE TABLE IF NOT EXISTS`, so a raw * CREATE in a re-run migration raises "object already exists". When the target * table already exists on those engines, return a skip reason so the statement * is skipped (mirrors the Firebird ALTER-TABLE-ADD idempotency guard). * SQLite/MySQL/PostgreSQL support IF NOT EXISTS and are left to the engine. * Only a genuine already-exists is skipped — every other error still raises. */ export declare function shouldSkipCreateTable(db: DatabaseAdapter, stmt: string): Promise; /** * Sync model definitions to the database (create tables, add columns). */ export declare function syncModels(models: DiscoveredModel[]): Promise; /** * Ensure the migration tracking table exists in the canonical shape (creating * it or upgrading an older one in place) on the global adapter. */ export declare function ensureMigrationTable(): Promise; /** * Get the current batch number (max batch + 1). */ export declare function getNextBatch(): Promise; /** * Check if a migration has already been applied (a row with passed = 1). */ export declare function isMigrationApplied(name: string): Promise; /** * Record a migration as applied (public API). Routes through recordApplied() so * a leftover passed=0 row for the same migration_name is deleted before the * fresh row is written (at most one row per migration_name). */ export declare function recordMigration(name: string, batch: number, passed?: number): Promise; /** * Apply a migration (run its up function and record it). */ export declare function applyMigration(name: string, up: () => void | Promise, batch: number): Promise; /** * Get all migrations from the last batch. */ export declare function getLastBatchMigrations(): Promise>; /** * Remove a migration record (used during rollback). */ export declare function removeMigrationRecord(name: string): Promise; /** * Rollback the last batch of migrations using .down.sql files. * * FAIL-SAFE (MIG-DEC-02, reuses the Python reference model): for each * migration in the last batch (in reverse order), the down artifact must * actually run before the tracking record is removed. A MISSING .down.sql * (or, on the legacy Map API, no registered down function) or a FAILING down * statement now THROWS instead of logging a warning/error and still deleting * the record — the old behaviour was the exact MIG-ROLLBACK-DROPS-LEDGER bug: * the schema stayed applied but the ledger row vanished, silently untracked. * The DELETE runs inside the SAME transaction as the down statements, so a * partially-executed down (some statements ran, a later one failed) rolls * back too — no half-reversed schema left behind either. * * @param migrationsDir - Directory containing migration files (default: "migrations") * @param delimiter - SQL statement delimiter (default: ";") * @returns Array of the down-migration files that were run, e.g. * "000001_create_users.down.sql". (The legacy down-FUNCTION Map API returns the * bare migration name instead, since no .down.sql file is involved there.) * @throws When a migration in the batch has no down artifact, or its down * statement(s) fail — the batch stops at that migration; earlier * migrations in the SAME call that already rolled back stay rolled back * (each is its own transaction). * * NOTE on return form (intentional, cross-framework): migration return values reflect * WHAT each method acted on, so the forms differ by method and that is by design (not * unified). migrate()/getApplied()/getPending() return the up-migration filename * ("name.sql"); rollback() returns the DOWN-migration filename it executed * ("name.down.sql") — matching the Python master. So a caller diffing rollback() * against getApplied() compares ".down.sql" vs ".sql": strip the suffixes (or compare * the bare "name" stem) to relate them. */ export declare function rollback(migrationsDir?: string | Map void | Promise>, delimiter?: string): Promise; /** * Get all applied migrations. */ export declare function getAppliedMigrations(): Promise>; /** * Result returned by the `migrate()` function. */ export interface MigrationResult { /** Filenames of successfully applied migrations. */ applied: string[]; /** Filenames that were already applied (skipped). */ skipped: string[]; /** Filenames that failed with error details. */ failed: string[]; } /** * Result returned by the `status()` function. */ export interface MigrationStatus { /** Filenames of completed (already applied) migrations. */ completed: string[]; /** Filenames of pending (not yet applied) migrations. */ pending: string[]; } /** * Replace smart/curly quotes with straight ASCII quotes so migration SQL * authored or pasted from an editor/doc actually runs (those code points are * not valid SQL delimiters). Already-straight quotes and ordinary string * content are returned byte-for-byte unchanged. */ export declare function normalizeQuotes(sql: string): string; /** * Return the new terminator from a `SET TERM ` directive. * * `SET TERM` is a script-level directive (recognised by isql and other * InterBase/Firebird tooling, not run by the engine) that changes the * terminator separating statements. Recognising it lets a statement whose own * body contains the default `;` terminator — a trigger, stored procedure or * `EXECUTE BLOCK` — be kept intact rather than split on those inner `;`. The * terminator may be more than one character (e.g. `!!`). * * @param statement A single, already-trimmed statement. * @returns The new terminator, or `null` when `statement` is not a `SET TERM` * directive. */ export declare function parseSetTerm(statement: string): string | null; /** * Split SQL text into individual statements with a single-pass, quote- and * comment-aware scanner. The split decision is made character by character so * the delimiter only ever fires in real statement position. * * This is the fix for issue #54: the old implementation split on `delimiter` * BEFORE stripping `-- …` line comments, so a `;` inside a line comment * fragmented one statement into several broken pieces. A scanner that knows * where it is (code / comment / string) cannot make that mistake. * * Handled, in priority order, only when NOT already inside a stored-proc block: * - `$$ … $$` and `// … //` stored-proc blocks are kept intact (inner `;` never * splits). A `//` preceded by `:` is a URL scheme (`https://…`), not a delimiter. * - `/* … *​/` block comments are stripped. * - `-- …` line comments are stripped to end of line (the newline is kept). * - `'…'` single-quoted strings and `"…"` double-quoted identifiers are copied * verbatim, honouring the SQL doubled-quote escape (`''` / `""`); a `;`, `--` * or `/*` inside a literal is data, not a delimiter or comment. * - A `SET TERM ` directive switches the active terminator and is * consumed (never emitted), so a statement whose own body contains the default * terminator — a Firebird trigger, stored procedure or `EXECUTE BLOCK` — * survives as one. Multi-character terminators (e.g. `!!`) are supported. * Mirrors the tina4-python `_split_statements` / tina4-php / tina4-ruby scanner (parity). */ export declare function splitStatements(sql: string, delimiter?: string): string[]; /** * Sort migration filenames supporting both naming patterns: * - Sequential: 000001_name.sql, 000002_name.sql * - Timestamp: 20240315120000_name.sql (YYYYMMDDHHMMSS) * * Numeric-aware: a file with a leading numeric/timestamp prefix sorts first by * that number (so `9_*` applies before `10_*` — a plain lexical sort misorders * unpadded prefixes because "10" < "9"). Files with NO numeric prefix sort * AFTER the numbered ones, then lexically. Mirrors Python's `_migration_sort_key`. */ export declare function sortMigrationFiles(files: string[]): string[]; /** * Run all pending SQL-file migrations. * * Supports both naming patterns: * - Sequential: 000001_description.sql * - Timestamp: YYYYMMDDHHMMSS_description.sql * * 1. Creates the `tina4_migration` tracking table if it doesn't exist. * 2. Scans `migrationsDir` for `.sql` files (excluding `.down.sql`), sorted. * 3. Skips files already recorded as applied. * 4. Splits file content on `delimiter` and executes each statement. * 5. On success records the migration with the current batch number. * 6. On error logs and continues. * 7. Returns a summary of applied / skipped / failed files. * * @param adapter - A DatabaseAdapter instance (or omit to use the global adapter). * @param options - Optional configuration. */ export declare function migrate(adapter?: DatabaseAdapter, options?: { migrationsDir?: string; delimiter?: string; }): Promise; /** * Get migration status: which migrations are completed and which are pending. * * @param adapter - A DatabaseAdapter instance (or omit to use the global adapter). * @param options - Optional configuration. * @returns Object with `completed` and `pending` arrays of filenames. */ export declare function status(adapter?: DatabaseAdapter, options?: { migrationsDir?: string; }): Promise; /** * Create a new empty SQL migration file with a timestamp prefix. * * Creates BOTH the up migration (.sql) and the down migration (.down.sql). * * @param description - Human-readable description (used in filename). * @param options - Optional configuration. * @returns Object with paths to the created up and down files. */ export declare function createMigration(description: string, options?: { migrationsDir?: string; kind?: "sql" | "code" | "class"; }): Promise; /** * Create a new TypeScript class-based migration file with a timestamp prefix. * * @param description - Human-readable description (used in filename and class name). * @param options - Optional configuration. * @returns Path to the created file. */ export declare function createClassMigration(description: string, options?: { migrationsDir?: string; }): Promise; /** * Object-oriented Migration class — canonical Tina4 Migration API. * * Provides parity with Python, PHP, and Ruby: * - migrate() Run all pending migrations * - rollback(steps=1) Roll back last N batches * - status() Show completed/pending * - create(description) Scaffold new .sql + .down.sql files * - getApplied() List applied migrations * - getPending() List pending migration filenames * - getFiles() List all migration files on disk * * @example * const m = new Migration(db, { migrationsDir: "migrations" }); * await m.migrate(); * await m.rollback(2); * await m.status(); * await m.create("add users table"); */ export declare class Migration { private db?; private dir; private delimiter; constructor(db?: DatabaseAdapter, options?: { migrationsDir?: string; delimiter?: string; }); /** Run all pending migrations. Returns applied/skipped/failed summary. */ migrate(): Promise; /** Roll back the last N batches. Returns list of rolled-back migration names. */ rollback(steps?: number): Promise; /** Get migration status: which are completed and which are pending. */ status(): Promise; /** * Scaffold a new migration file. * * kind="sql" — creates {timestamp}_{description}.sql + .down.sql (default) * kind="code" — creates {timestamp}_{description}.ts with a TypeScript class * template. "class" is accepted as a legacy alias. * * Returns the path to the created up file (or class file). */ create(description: string, kind?: "sql" | "code" | "class"): Promise; /** Return list of completed (applied) migration filenames. */ getApplied(): Promise; /** Return list of pending migration filenames. */ getPending(): Promise; /** Return sorted list of all migration files on disk (excludes .down.sql). */ getFiles(): string[]; }