diff --git a/src/migrate.js b/src/migrate.js new file mode 100644 index 0000000..8d20f03 --- /dev/null +++ b/src/migrate.js @@ -0,0 +1,49 @@ +import MIGRATIONS_TABLE from './migrations_table.sql?raw'; +export class MigrationManager { + constructor(database) { + Object.defineProperty(this, "database", { + enumerable: true, + configurable: true, + writable: true, + value: database + }); + } + async migrate(migrations) { + await this.init(); + try { + await this.database.exec('BEGIN TRANSACTION'); + for (const migration of migrations) { + await this.migrateOne(migration); + } + await this.database.exec('COMMIT'); + } + catch (error) { + await this.database.exec('ROLLBACK'); + throw error; + } + } + async init() { + await this.database.exec(MIGRATIONS_TABLE); + } + async migrateOne(migration) { + const hashValue = await hash(migration.script); + const [row] = await this.database.exec('SELECT hash FROM migrations WHERE name = ?', [ + migration.name + ]); + if (row && row.hash !== hashValue) { + throw new Error(`Migration ${migration.name} has been modified`); + } + if (!row) { + await this.database.exec(migration.script); + await this.database.exec('INSERT INTO migrations (name, hash) VALUES (?, ?)', [ + migration.name, + hashValue + ]); + } + } +} +async function hash(input) { + const hashBuffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(input)); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + return hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); +} diff --git a/tsconfig.json b/tsconfig.json index 97c3dc1..11d7937 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,13 +5,15 @@ "module": "ESNext", "lib": ["ES2020", "DOM", "DOM.Iterable"], "skipLibCheck": true, + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "./dist", /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "isolatedModules": true, "moduleDetection": "force", - "noEmit": true, /* Linting */ "strict": true, @@ -19,8 +21,7 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true, - - "types": ["vite/client"] + "types": ["vite/client"], }, "include": ["src/migrate.ts"] }