sealed-migrations 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,151 @@
1
+ //#region src/types.d.ts
2
+ /**
3
+ * The subset of a database connection the runner needs. A `mysql2/promise`
4
+ * Connection satisfies it structurally (so a consumer can pass
5
+ * `mysql.createConnection(...)` with no cast), and so does any compatible
6
+ * driver or test fake. The consumer owns the driver, so this package has no
7
+ * runtime dependency on mysql2 (or any specific client).
8
+ *
9
+ * `params` and the row tuples are intentionally loose: this is the untyped
10
+ * driver boundary, and a stricter shape would reject real driver types through
11
+ * parameter contravariance. Callers narrow rows internally.
12
+ */
13
+ interface MigrationConnection {
14
+ query: <T = any>(sql: string) => Promise<[T, any]>;
15
+ execute: <T = any>(sql: string, params?: any) => Promise<[T, any]>;
16
+ beginTransaction: () => Promise<void>;
17
+ commit: () => Promise<void>;
18
+ rollback: () => Promise<void>;
19
+ end?: () => Promise<void>;
20
+ }
21
+ type Logger = Pick<Console, 'log' | 'warn' | 'error'>;
22
+ interface RunnerConfig {
23
+ /** Path to the migrations directory (absolute, or relative to `process.cwd()`). */
24
+ migrationsDir: string;
25
+ /** Opens a connection the runner owns and closes for a single command. */
26
+ connect: () => Promise<MigrationConnection>;
27
+ /** MySQL advisory lock name serialising up/down/seal/rehash. */
28
+ lockName?: string;
29
+ /** Seconds to wait for the advisory lock (default 120). */
30
+ lockTimeoutSec?: number;
31
+ /** Version treated as the baseline (default `001_baseline`). */
32
+ baselineVersion?: string;
33
+ /**
34
+ * Database name, used only to detect an existing schema for the baseline
35
+ * short-circuit. Omit to always execute the baseline SQL.
36
+ */
37
+ databaseName?: string;
38
+ /** Optional pre-flight assertion, e.g. refuse a non-MariaDB server. */
39
+ assertServer?: (conn: MigrationConnection) => Promise<void>;
40
+ /**
41
+ * Wraps retryable writes, e.g. a Galera deadlock retry. Defaults to a plain
42
+ * pass-through, so a single-writer setup needs no configuration.
43
+ */
44
+ withRetry?: <T>(fn: () => Promise<T>, label: string) => Promise<T>;
45
+ /** Recorded in `schema_migrations.app_version`. */
46
+ appVersion?: string | null;
47
+ /** Recorded in `schema_migrations.executed_by`. */
48
+ executedBy?: string | null;
49
+ /** Sink for progress messages (default `console`). */
50
+ logger?: Logger;
51
+ }
52
+ type TxMode = 'transactional' | 'non_transactional' | 'baseline_mark';
53
+ interface ParsedMigrationName {
54
+ isDev: boolean;
55
+ sortNumber: number;
56
+ version: string;
57
+ description: string;
58
+ }
59
+ interface Migration extends ParsedMigrationName {
60
+ upSql: string;
61
+ downSql: string;
62
+ checksum: string;
63
+ }
64
+ interface AppliedMigration {
65
+ checksum: string;
66
+ appliedAt?: unknown;
67
+ }
68
+ type AppliedMap = Map<string, AppliedMigration>;
69
+ interface ValidateOptions {
70
+ /** How a checksum mismatch on an applied dev migration is handled. */
71
+ devChecksumMismatch?: 'error' | 'warn';
72
+ }
73
+ interface UpOptions {
74
+ to?: string;
75
+ allowDev?: boolean;
76
+ }
77
+ interface DownOptions {
78
+ to?: string;
79
+ }
80
+ //#endregion
81
+ //#region src/commands.d.ts
82
+ declare function assertDevMigrationsAllowed(migrations: Migration[], allowDev: boolean): void;
83
+ /** Next free number = max(numbered repo folders ∪ numbered applied versions) + 1. */
84
+ declare function computeNextSealNumber(migrations: Migration[], appliedVersions: string[]): string;
85
+ declare function commandCurrent(conn: MigrationConnection, migrations: Migration[], logger: Logger): Promise<void>;
86
+ declare function commandStatus(conn: MigrationConnection, migrations: Migration[], logger: Logger): Promise<void>;
87
+ declare function commandUp(conn: MigrationConnection, migrations: Migration[], config: RunnerConfig, options: UpOptions, logger: Logger): Promise<void>;
88
+ declare function commandDown(conn: MigrationConnection, migrations: Migration[], options: DownOptions, logger: Logger): Promise<void>;
89
+ declare function commandSeal(conn: MigrationConnection, migrations: Migration[], migrationsDir: string, slug: string | undefined, logger: Logger): Promise<void>;
90
+ declare function commandRehash(conn: MigrationConnection, migrations: Migration[], slug: string | undefined, logger: Logger): Promise<void>;
91
+ //#endregion
92
+ //#region src/migrations.d.ts
93
+ declare const MIGRATION_DIR_PATTERN: RegExp;
94
+ declare const DEV_MIGRATION_DIR_PATTERN: RegExp;
95
+ /** SHA-256 of `up.sql + "\n--down-sql--\n" + down.sql`, content only. */
96
+ declare function hashMigration(upSql: string, downSql: string): string;
97
+ declare function isDevVersion(version: string): boolean;
98
+ declare function parseMigrationDirName(dirName: string): ParsedMigrationName;
99
+ /**
100
+ * Normalises a `seal`/`rehash` argument into a `dev_<slug>` version. Accepts
101
+ * both `foo` and `dev_foo`, and refuses sealed (numbered) versions, which are
102
+ * immutable.
103
+ */
104
+ declare function normalizeDevVersion(input: string | undefined): string;
105
+ /**
106
+ * Reads every migration folder, sorted numbered-ascending then dev
107
+ * alphabetical. Duplicate numeric prefixes stay deterministic via the folder
108
+ * name tie-break.
109
+ */
110
+ declare function readMigrations(migrationsDir: string): Promise<Migration[]>;
111
+ //#endregion
112
+ //#region src/runner.d.ts
113
+ interface ParsedArgs {
114
+ args: Record<string, string | boolean>;
115
+ positionals: string[];
116
+ }
117
+ /** Minimal `--flag`, `--key=value`, `--key value` and positional parser. */
118
+ declare function parseArgs(argv: string[]): ParsedArgs;
119
+ interface Runner {
120
+ up: (options?: UpOptions) => Promise<void>;
121
+ down: (options: DownOptions) => Promise<void>;
122
+ seal: (slug: string) => Promise<void>;
123
+ rehash: (slug: string) => Promise<void>;
124
+ current: () => Promise<void>;
125
+ status: () => Promise<void>;
126
+ /** Parses `argv` (without node/script), dispatches, and throws on error. */
127
+ runCli: (argv: string[]) => Promise<void>;
128
+ }
129
+ declare function createRunner(config: RunnerConfig): Runner;
130
+ //#endregion
131
+ //#region src/sql.d.ts
132
+ declare function splitSqlStatements(sqlFileContent: string): string[];
133
+ /**
134
+ * Picks the transaction mode for a migration file. An explicit
135
+ * `-- migrate: tx | no-tx | auto` directive wins; otherwise DDL forces the
136
+ * non-transactional path (MySQL/MariaDB cannot roll back DDL).
137
+ */
138
+ declare function resolveTxMode(sql: string, statements: string[]): 'transactional' | 'non_transactional';
139
+ //#endregion
140
+ //#region src/state.d.ts
141
+ /**
142
+ * Checks applied state against the repo:
143
+ * - a dev migration applied elsewhere but missing here is only a warning
144
+ * (the normal state of a shared dev DB with parallel branches),
145
+ * - a numbered migration missing here is a hard error (sealed history is strict),
146
+ * - a checksum mismatch is a hard error, except a dev mismatch in `warn` mode
147
+ * (the `down` path, so the SQL-first edit loop can start).
148
+ */
149
+ declare function validateAppliedState(migrations: Migration[], appliedByVersion: AppliedMap, options?: ValidateOptions, logger?: Logger): void;
150
+ //#endregion
151
+ export { type AppliedMap, type AppliedMigration, DEV_MIGRATION_DIR_PATTERN, type DownOptions, type Logger, MIGRATION_DIR_PATTERN, type Migration, type MigrationConnection, type ParsedArgs, type ParsedMigrationName, type Runner, type RunnerConfig, type TxMode, type UpOptions, type ValidateOptions, assertDevMigrationsAllowed, commandCurrent, commandDown, commandRehash, commandSeal, commandStatus, commandUp, computeNextSealNumber, createRunner, hashMigration, isDevVersion, normalizeDevVersion, parseArgs, parseMigrationDirName, readMigrations, resolveTxMode, splitSqlStatements, validateAppliedState };
@@ -0,0 +1,151 @@
1
+ //#region src/types.d.ts
2
+ /**
3
+ * The subset of a database connection the runner needs. A `mysql2/promise`
4
+ * Connection satisfies it structurally (so a consumer can pass
5
+ * `mysql.createConnection(...)` with no cast), and so does any compatible
6
+ * driver or test fake. The consumer owns the driver, so this package has no
7
+ * runtime dependency on mysql2 (or any specific client).
8
+ *
9
+ * `params` and the row tuples are intentionally loose: this is the untyped
10
+ * driver boundary, and a stricter shape would reject real driver types through
11
+ * parameter contravariance. Callers narrow rows internally.
12
+ */
13
+ interface MigrationConnection {
14
+ query: <T = any>(sql: string) => Promise<[T, any]>;
15
+ execute: <T = any>(sql: string, params?: any) => Promise<[T, any]>;
16
+ beginTransaction: () => Promise<void>;
17
+ commit: () => Promise<void>;
18
+ rollback: () => Promise<void>;
19
+ end?: () => Promise<void>;
20
+ }
21
+ type Logger = Pick<Console, 'log' | 'warn' | 'error'>;
22
+ interface RunnerConfig {
23
+ /** Path to the migrations directory (absolute, or relative to `process.cwd()`). */
24
+ migrationsDir: string;
25
+ /** Opens a connection the runner owns and closes for a single command. */
26
+ connect: () => Promise<MigrationConnection>;
27
+ /** MySQL advisory lock name serialising up/down/seal/rehash. */
28
+ lockName?: string;
29
+ /** Seconds to wait for the advisory lock (default 120). */
30
+ lockTimeoutSec?: number;
31
+ /** Version treated as the baseline (default `001_baseline`). */
32
+ baselineVersion?: string;
33
+ /**
34
+ * Database name, used only to detect an existing schema for the baseline
35
+ * short-circuit. Omit to always execute the baseline SQL.
36
+ */
37
+ databaseName?: string;
38
+ /** Optional pre-flight assertion, e.g. refuse a non-MariaDB server. */
39
+ assertServer?: (conn: MigrationConnection) => Promise<void>;
40
+ /**
41
+ * Wraps retryable writes, e.g. a Galera deadlock retry. Defaults to a plain
42
+ * pass-through, so a single-writer setup needs no configuration.
43
+ */
44
+ withRetry?: <T>(fn: () => Promise<T>, label: string) => Promise<T>;
45
+ /** Recorded in `schema_migrations.app_version`. */
46
+ appVersion?: string | null;
47
+ /** Recorded in `schema_migrations.executed_by`. */
48
+ executedBy?: string | null;
49
+ /** Sink for progress messages (default `console`). */
50
+ logger?: Logger;
51
+ }
52
+ type TxMode = 'transactional' | 'non_transactional' | 'baseline_mark';
53
+ interface ParsedMigrationName {
54
+ isDev: boolean;
55
+ sortNumber: number;
56
+ version: string;
57
+ description: string;
58
+ }
59
+ interface Migration extends ParsedMigrationName {
60
+ upSql: string;
61
+ downSql: string;
62
+ checksum: string;
63
+ }
64
+ interface AppliedMigration {
65
+ checksum: string;
66
+ appliedAt?: unknown;
67
+ }
68
+ type AppliedMap = Map<string, AppliedMigration>;
69
+ interface ValidateOptions {
70
+ /** How a checksum mismatch on an applied dev migration is handled. */
71
+ devChecksumMismatch?: 'error' | 'warn';
72
+ }
73
+ interface UpOptions {
74
+ to?: string;
75
+ allowDev?: boolean;
76
+ }
77
+ interface DownOptions {
78
+ to?: string;
79
+ }
80
+ //#endregion
81
+ //#region src/commands.d.ts
82
+ declare function assertDevMigrationsAllowed(migrations: Migration[], allowDev: boolean): void;
83
+ /** Next free number = max(numbered repo folders ∪ numbered applied versions) + 1. */
84
+ declare function computeNextSealNumber(migrations: Migration[], appliedVersions: string[]): string;
85
+ declare function commandCurrent(conn: MigrationConnection, migrations: Migration[], logger: Logger): Promise<void>;
86
+ declare function commandStatus(conn: MigrationConnection, migrations: Migration[], logger: Logger): Promise<void>;
87
+ declare function commandUp(conn: MigrationConnection, migrations: Migration[], config: RunnerConfig, options: UpOptions, logger: Logger): Promise<void>;
88
+ declare function commandDown(conn: MigrationConnection, migrations: Migration[], options: DownOptions, logger: Logger): Promise<void>;
89
+ declare function commandSeal(conn: MigrationConnection, migrations: Migration[], migrationsDir: string, slug: string | undefined, logger: Logger): Promise<void>;
90
+ declare function commandRehash(conn: MigrationConnection, migrations: Migration[], slug: string | undefined, logger: Logger): Promise<void>;
91
+ //#endregion
92
+ //#region src/migrations.d.ts
93
+ declare const MIGRATION_DIR_PATTERN: RegExp;
94
+ declare const DEV_MIGRATION_DIR_PATTERN: RegExp;
95
+ /** SHA-256 of `up.sql + "\n--down-sql--\n" + down.sql`, content only. */
96
+ declare function hashMigration(upSql: string, downSql: string): string;
97
+ declare function isDevVersion(version: string): boolean;
98
+ declare function parseMigrationDirName(dirName: string): ParsedMigrationName;
99
+ /**
100
+ * Normalises a `seal`/`rehash` argument into a `dev_<slug>` version. Accepts
101
+ * both `foo` and `dev_foo`, and refuses sealed (numbered) versions, which are
102
+ * immutable.
103
+ */
104
+ declare function normalizeDevVersion(input: string | undefined): string;
105
+ /**
106
+ * Reads every migration folder, sorted numbered-ascending then dev
107
+ * alphabetical. Duplicate numeric prefixes stay deterministic via the folder
108
+ * name tie-break.
109
+ */
110
+ declare function readMigrations(migrationsDir: string): Promise<Migration[]>;
111
+ //#endregion
112
+ //#region src/runner.d.ts
113
+ interface ParsedArgs {
114
+ args: Record<string, string | boolean>;
115
+ positionals: string[];
116
+ }
117
+ /** Minimal `--flag`, `--key=value`, `--key value` and positional parser. */
118
+ declare function parseArgs(argv: string[]): ParsedArgs;
119
+ interface Runner {
120
+ up: (options?: UpOptions) => Promise<void>;
121
+ down: (options: DownOptions) => Promise<void>;
122
+ seal: (slug: string) => Promise<void>;
123
+ rehash: (slug: string) => Promise<void>;
124
+ current: () => Promise<void>;
125
+ status: () => Promise<void>;
126
+ /** Parses `argv` (without node/script), dispatches, and throws on error. */
127
+ runCli: (argv: string[]) => Promise<void>;
128
+ }
129
+ declare function createRunner(config: RunnerConfig): Runner;
130
+ //#endregion
131
+ //#region src/sql.d.ts
132
+ declare function splitSqlStatements(sqlFileContent: string): string[];
133
+ /**
134
+ * Picks the transaction mode for a migration file. An explicit
135
+ * `-- migrate: tx | no-tx | auto` directive wins; otherwise DDL forces the
136
+ * non-transactional path (MySQL/MariaDB cannot roll back DDL).
137
+ */
138
+ declare function resolveTxMode(sql: string, statements: string[]): 'transactional' | 'non_transactional';
139
+ //#endregion
140
+ //#region src/state.d.ts
141
+ /**
142
+ * Checks applied state against the repo:
143
+ * - a dev migration applied elsewhere but missing here is only a warning
144
+ * (the normal state of a shared dev DB with parallel branches),
145
+ * - a numbered migration missing here is a hard error (sealed history is strict),
146
+ * - a checksum mismatch is a hard error, except a dev mismatch in `warn` mode
147
+ * (the `down` path, so the SQL-first edit loop can start).
148
+ */
149
+ declare function validateAppliedState(migrations: Migration[], appliedByVersion: AppliedMap, options?: ValidateOptions, logger?: Logger): void;
150
+ //#endregion
151
+ export { type AppliedMap, type AppliedMigration, DEV_MIGRATION_DIR_PATTERN, type DownOptions, type Logger, MIGRATION_DIR_PATTERN, type Migration, type MigrationConnection, type ParsedArgs, type ParsedMigrationName, type Runner, type RunnerConfig, type TxMode, type UpOptions, type ValidateOptions, assertDevMigrationsAllowed, commandCurrent, commandDown, commandRehash, commandSeal, commandStatus, commandUp, computeNextSealNumber, createRunner, hashMigration, isDevVersion, normalizeDevVersion, parseArgs, parseMigrationDirName, readMigrations, resolveTxMode, splitSqlStatements, validateAppliedState };