@kysera/migrations 0.3.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 LuxQuant
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,98 @@
1
+ import { Kysely } from 'kysely';
2
+
3
+ /**
4
+ * Migration interface
5
+ */
6
+ interface Migration {
7
+ /** Unique migration name (e.g., '001_create_users') */
8
+ name: string;
9
+ /** Migration up function - creates/modifies schema */
10
+ up: (db: Kysely<any>) => Promise<void>;
11
+ /** Optional migration down function - reverts changes */
12
+ down?: (db: Kysely<any>) => Promise<void>;
13
+ }
14
+ /**
15
+ * Migration metadata for tracking purposes
16
+ */
17
+ interface MigrationWithMeta extends Migration {
18
+ /** Human-readable description */
19
+ description?: string;
20
+ /** Whether this is a breaking change */
21
+ breaking?: boolean;
22
+ /** Estimated duration in milliseconds */
23
+ estimatedDuration?: number;
24
+ }
25
+ /**
26
+ * Migration status result
27
+ */
28
+ interface MigrationStatus {
29
+ /** List of executed migration names */
30
+ executed: string[];
31
+ /** List of pending migration names */
32
+ pending: string[];
33
+ }
34
+ /**
35
+ * Migration runner options
36
+ */
37
+ interface MigrationRunnerOptions {
38
+ /** Enable dry run mode (don't execute, just show what would run) */
39
+ dryRun?: boolean;
40
+ /** Logger function */
41
+ logger?: (message: string) => void;
42
+ }
43
+ /**
44
+ * Setup migrations table in database
45
+ */
46
+ declare function setupMigrations(db: Kysely<any>): Promise<void>;
47
+ /**
48
+ * Migration runner with state tracking
49
+ */
50
+ declare class MigrationRunner {
51
+ private db;
52
+ private migrations;
53
+ private options;
54
+ private logger;
55
+ constructor(db: Kysely<any>, migrations: Migration[], options?: MigrationRunnerOptions);
56
+ /**
57
+ * Get list of executed migrations from database
58
+ */
59
+ getExecutedMigrations(): Promise<string[]>;
60
+ /**
61
+ * Mark a migration as executed
62
+ */
63
+ markAsExecuted(name: string): Promise<void>;
64
+ /**
65
+ * Mark a migration as rolled back (remove from executed list)
66
+ */
67
+ markAsRolledBack(name: string): Promise<void>;
68
+ /**
69
+ * Run all pending migrations
70
+ */
71
+ up(): Promise<void>;
72
+ /**
73
+ * Rollback last N migrations
74
+ */
75
+ down(steps?: number): Promise<void>;
76
+ /**
77
+ * Show migration status
78
+ */
79
+ status(): Promise<MigrationStatus>;
80
+ /**
81
+ * Reset all migrations (dangerous!)
82
+ */
83
+ reset(): Promise<void>;
84
+ /**
85
+ * Run migrations up to a specific migration (inclusive)
86
+ */
87
+ upTo(targetName: string): Promise<void>;
88
+ }
89
+ /**
90
+ * Create a migration runner instance
91
+ */
92
+ declare function createMigrationRunner(db: Kysely<any>, migrations: Migration[], options?: MigrationRunnerOptions): MigrationRunner;
93
+ /**
94
+ * Helper to create a simple migration
95
+ */
96
+ declare function createMigration(name: string, up: (db: Kysely<any>) => Promise<void>, down?: (db: Kysely<any>) => Promise<void>): Migration;
97
+
98
+ export { type Migration, MigrationRunner, type MigrationRunnerOptions, type MigrationStatus, type MigrationWithMeta, createMigration, createMigrationRunner, setupMigrations };
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ import {sql}from'kysely';async function a(r){await r.schema.createTable("migrations").ifNotExists().addColumn("name","varchar(255)",i=>i.primaryKey()).addColumn("executed_at","timestamp",i=>i.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)).execute();}var g=class{constructor(i,e,t={}){this.db=i;this.migrations=e;this.options=t;this.logger=t.logger||console.log;}logger;async getExecutedMigrations(){return (await this.db.selectFrom("migrations").select("name").orderBy("executed_at","asc").execute()).map(e=>e.name)}async markAsExecuted(i){await this.db.insertInto("migrations").values({name:i}).execute();}async markAsRolledBack(i){await this.db.deleteFrom("migrations").where("name","=",i).execute();}async up(){await a(this.db);let i=await this.getExecutedMigrations();if(this.migrations.filter(t=>!i.includes(t.name)).length===0){this.logger("\u2705 No pending migrations");return}this.options.dryRun&&this.logger(`
2
+ \u{1F50D} DRY RUN - No changes will be made
3
+ `);for(let t of this.migrations){if(i.includes(t.name)){this.logger(`\u2713 ${t.name} (already executed)`);continue}try{this.logger(`\u2191 Running ${t.name}...`),this.options.dryRun||(await t.up(this.db),await this.markAsExecuted(t.name)),this.logger(`\u2713 ${t.name} completed`);}catch(o){throw this.logger(`\u2717 ${t.name} failed: ${o}`),o}}this.options.dryRun||this.logger(`
4
+ \u2705 All migrations completed successfully`);}async down(i=1){let e=await this.getExecutedMigrations();if(e.length===0){this.logger("\u26A0\uFE0F No executed migrations to rollback");return}let t=e.slice(-i).reverse();this.options.dryRun&&this.logger(`
5
+ \u{1F50D} DRY RUN - No changes will be made
6
+ `);for(let o of t){let n=this.migrations.find(s=>s.name===o);if(!n){this.logger(`\u26A0\uFE0F Migration ${o} not found in codebase`);continue}if(!n.down){this.logger(`\u26A0\uFE0F Migration ${o} has no down method`);continue}try{this.logger(`\u2193 Rolling back ${o}...`),this.options.dryRun||(await n.down(this.db),await this.markAsRolledBack(o)),this.logger(`\u2713 ${o} rolled back`);}catch(s){throw this.logger(`\u2717 ${o} rollback failed: ${s}`),s}}this.options.dryRun||this.logger(`
7
+ \u2705 Rollback completed successfully`);}async status(){await a(this.db);let i=await this.getExecutedMigrations(),e=this.migrations.filter(t=>!i.includes(t.name)).map(t=>t.name);return this.logger(`
8
+ \u{1F4CA} Migration Status:`),this.logger(` \u2705 Executed: ${i.length}`),this.logger(` \u23F3 Pending: ${e.length}`),i.length>0&&(this.logger(`
9
+ Executed migrations:`),i.forEach(t=>this.logger(` \u2713 ${t}`))),e.length>0&&(this.logger(`
10
+ Pending migrations:`),e.forEach(t=>this.logger(` - ${t}`))),{executed:i,pending:e}}async reset(){if(this.options.dryRun){this.logger(`
11
+ \u{1F50D} DRY RUN - Would rollback all migrations`);return}let i=await this.getExecutedMigrations();this.logger(`
12
+ \u26A0\uFE0F Resetting ${i.length} migrations...`),await this.down(i.length),this.logger(`
13
+ \u2705 All migrations reset`);}async upTo(i){await a(this.db);let e=await this.getExecutedMigrations(),t=this.migrations.findIndex(n=>n.name===i);if(t===-1)throw new Error(`Migration ${i} not found`);let o=this.migrations.slice(0,t+1);for(let n of o){if(e.includes(n.name)){this.logger(`\u2713 ${n.name} (already executed)`);continue}try{this.logger(`\u2191 Running ${n.name}...`),this.options.dryRun||(await n.up(this.db),await this.markAsExecuted(n.name)),this.logger(`\u2713 ${n.name} completed`);}catch(s){throw this.logger(`\u2717 ${n.name} failed: ${s}`),s}}this.logger(`
14
+ \u2705 Migrated up to ${i}`);}};function c(r,i,e){return new g(r,i,e)}function h(r,i,e){let t={name:r,up:i};return e!==void 0&&(t.down=e),t}export{g as MigrationRunner,h as createMigration,c as createMigrationRunner,a as setupMigrations};//# sourceMappingURL=index.js.map
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":["setupMigrations","db","col","sql","MigrationRunner","migrations","options","r","name","executed","m","migration","error","steps","toRollback","pending","targetName","targetIndex","migrationsToRun","createMigrationRunner","createMigration","up","down"],"mappings":"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","file":"index.js","sourcesContent":["import type { Kysely } from 'kysely'\nimport { sql } from 'kysely'\n\n/**\n * Migration interface\n */\nexport interface Migration {\n /** Unique migration name (e.g., '001_create_users') */\n name: string\n /** Migration up function - creates/modifies schema */\n up: (db: Kysely<any>) => Promise<void>\n /** Optional migration down function - reverts changes */\n down?: (db: Kysely<any>) => Promise<void>\n}\n\n/**\n * Migration metadata for tracking purposes\n */\nexport interface MigrationWithMeta extends Migration {\n /** Human-readable description */\n description?: string\n /** Whether this is a breaking change */\n breaking?: boolean\n /** Estimated duration in milliseconds */\n estimatedDuration?: number\n}\n\n/**\n * Migration status result\n */\nexport interface MigrationStatus {\n /** List of executed migration names */\n executed: string[]\n /** List of pending migration names */\n pending: string[]\n}\n\n/**\n * Migration runner options\n */\nexport interface MigrationRunnerOptions {\n /** Enable dry run mode (don't execute, just show what would run) */\n dryRun?: boolean\n /** Logger function */\n logger?: (message: string) => void\n}\n\n/**\n * Setup migrations table in database\n */\nexport async function setupMigrations(db: Kysely<any>): Promise<void> {\n await db.schema\n .createTable('migrations')\n .ifNotExists()\n .addColumn('name', 'varchar(255)', col => col.primaryKey())\n .addColumn('executed_at', 'timestamp', col =>\n col.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)\n )\n .execute()\n}\n\n/**\n * Migration runner with state tracking\n */\nexport class MigrationRunner {\n private logger: (message: string) => void\n\n constructor(\n private db: Kysely<any>,\n private migrations: Migration[],\n private options: MigrationRunnerOptions = {}\n ) {\n this.logger = options.logger || console.log\n }\n\n /**\n * Get list of executed migrations from database\n */\n async getExecutedMigrations(): Promise<string[]> {\n const rows = await this.db\n .selectFrom('migrations' as any)\n .select('name' as any)\n .orderBy('executed_at' as any, 'asc')\n .execute()\n\n return rows.map((r: any) => r.name)\n }\n\n /**\n * Mark a migration as executed\n */\n async markAsExecuted(name: string): Promise<void> {\n await this.db\n .insertInto('migrations' as any)\n .values({ name } as any)\n .execute()\n }\n\n /**\n * Mark a migration as rolled back (remove from executed list)\n */\n async markAsRolledBack(name: string): Promise<void> {\n await this.db\n .deleteFrom('migrations' as any)\n .where('name' as any, '=', name)\n .execute()\n }\n\n /**\n * Run all pending migrations\n */\n async up(): Promise<void> {\n await setupMigrations(this.db)\n const executed = await this.getExecutedMigrations()\n\n const pending = this.migrations.filter(m => !executed.includes(m.name))\n\n if (pending.length === 0) {\n this.logger('āœ… No pending migrations')\n return\n }\n\n if (this.options.dryRun) {\n this.logger('\\nšŸ” DRY RUN - No changes will be made\\n')\n }\n\n for (const migration of this.migrations) {\n if (executed.includes(migration.name)) {\n this.logger(`āœ“ ${migration.name} (already executed)`)\n continue\n }\n\n try {\n this.logger(`↑ Running ${migration.name}...`)\n\n if (!this.options.dryRun) {\n await migration.up(this.db)\n await this.markAsExecuted(migration.name)\n }\n\n this.logger(`āœ“ ${migration.name} completed`)\n } catch (error) {\n this.logger(`āœ— ${migration.name} failed: ${error}`)\n throw error\n }\n }\n\n if (!this.options.dryRun) {\n this.logger('\\nāœ… All migrations completed successfully')\n }\n }\n\n /**\n * Rollback last N migrations\n */\n async down(steps = 1): Promise<void> {\n const executed = await this.getExecutedMigrations()\n\n if (executed.length === 0) {\n this.logger('āš ļø No executed migrations to rollback')\n return\n }\n\n const toRollback = executed.slice(-steps).reverse()\n\n if (this.options.dryRun) {\n this.logger('\\nšŸ” DRY RUN - No changes will be made\\n')\n }\n\n for (const name of toRollback) {\n const migration = this.migrations.find(m => m.name === name)\n\n if (!migration) {\n this.logger(`āš ļø Migration ${name} not found in codebase`)\n continue\n }\n\n if (!migration.down) {\n this.logger(`āš ļø Migration ${name} has no down method`)\n continue\n }\n\n try {\n this.logger(`↓ Rolling back ${name}...`)\n\n if (!this.options.dryRun) {\n await migration.down(this.db)\n await this.markAsRolledBack(name)\n }\n\n this.logger(`āœ“ ${name} rolled back`)\n } catch (error) {\n this.logger(`āœ— ${name} rollback failed: ${error}`)\n throw error\n }\n }\n\n if (!this.options.dryRun) {\n this.logger('\\nāœ… Rollback completed successfully')\n }\n }\n\n /**\n * Show migration status\n */\n async status(): Promise<MigrationStatus> {\n await setupMigrations(this.db)\n const executed = await this.getExecutedMigrations()\n const pending = this.migrations\n .filter(m => !executed.includes(m.name))\n .map(m => m.name)\n\n this.logger('\\nšŸ“Š Migration Status:')\n this.logger(` āœ… Executed: ${executed.length}`)\n this.logger(` ā³ Pending: ${pending.length}`)\n\n if (executed.length > 0) {\n this.logger('\\nExecuted migrations:')\n executed.forEach(name => this.logger(` āœ“ ${name}`))\n }\n\n if (pending.length > 0) {\n this.logger('\\nPending migrations:')\n pending.forEach(name => this.logger(` - ${name}`))\n }\n\n return { executed, pending }\n }\n\n /**\n * Reset all migrations (dangerous!)\n */\n async reset(): Promise<void> {\n if (this.options.dryRun) {\n this.logger('\\nšŸ” DRY RUN - Would rollback all migrations')\n return\n }\n\n const executed = await this.getExecutedMigrations()\n this.logger(`\\nāš ļø Resetting ${executed.length} migrations...`)\n\n await this.down(executed.length)\n this.logger('\\nāœ… All migrations reset')\n }\n\n /**\n * Run migrations up to a specific migration (inclusive)\n */\n async upTo(targetName: string): Promise<void> {\n await setupMigrations(this.db)\n const executed = await this.getExecutedMigrations()\n\n const targetIndex = this.migrations.findIndex(m => m.name === targetName)\n if (targetIndex === -1) {\n throw new Error(`Migration ${targetName} not found`)\n }\n\n const migrationsToRun = this.migrations.slice(0, targetIndex + 1)\n\n for (const migration of migrationsToRun) {\n if (executed.includes(migration.name)) {\n this.logger(`āœ“ ${migration.name} (already executed)`)\n continue\n }\n\n try {\n this.logger(`↑ Running ${migration.name}...`)\n\n if (!this.options.dryRun) {\n await migration.up(this.db)\n await this.markAsExecuted(migration.name)\n }\n\n this.logger(`āœ“ ${migration.name} completed`)\n } catch (error) {\n this.logger(`āœ— ${migration.name} failed: ${error}`)\n throw error\n }\n }\n\n this.logger(`\\nāœ… Migrated up to ${targetName}`)\n }\n}\n\n/**\n * Create a migration runner instance\n */\nexport function createMigrationRunner(\n db: Kysely<any>,\n migrations: Migration[],\n options?: MigrationRunnerOptions\n): MigrationRunner {\n return new MigrationRunner(db, migrations, options)\n}\n\n/**\n * Helper to create a simple migration\n */\nexport function createMigration(\n name: string,\n up: (db: Kysely<any>) => Promise<void>,\n down?: (db: Kysely<any>) => Promise<void>\n): Migration {\n const migration: Migration = { name, up }\n if (down !== undefined) {\n migration.down = down\n }\n return migration\n}"]}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@kysera/migrations",
3
+ "version": "0.3.0",
4
+ "description": "Database migration management for Kysera ORM",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "keywords": [
19
+ "kysely",
20
+ "orm",
21
+ "migrations",
22
+ "database",
23
+ "typescript"
24
+ ],
25
+ "author": "Kysera Team",
26
+ "license": "MIT",
27
+ "peerDependencies": {
28
+ "kysely": "^0.28.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^22.10.2",
32
+ "kysely": "^0.28.7",
33
+ "typescript": "^5.9.2",
34
+ "vitest": "^3.2.4",
35
+ "better-sqlite3": "^12.4.1",
36
+ "@types/better-sqlite3": "^7.6.12",
37
+ "tsup": "^8.5.0"
38
+ },
39
+ "sideEffects": false,
40
+ "engines": {
41
+ "node": ">=20.0.0",
42
+ "bun": ">=1.0.0"
43
+ },
44
+ "scripts": {
45
+ "build": "tsup",
46
+ "dev": "tsup --watch",
47
+ "test": "vitest run",
48
+ "test:watch": "vitest",
49
+ "typecheck": "tsc --noEmit"
50
+ }
51
+ }