fix(sqlite): resolve duplicate table creation in migrations
Split initial schema into two sequential migrations to prevent duplicate table creation and improve migration clarity. Changes: - Separate initial schema into two distinct migrations: * 001_initial_accounts (v1): Create accounts table & index * 002_secret_and_settings (v2): Create remaining tables (secret, settings, contacts, logs, temp) - Add version conflict detection to prevent duplicate migration versions - Ensure migrations are sequential (no gaps) - Update rollback scripts to only drop relevant tables Technical Details: - Add validateMigrationVersions() to check for: * Duplicate version numbers * Sequential version ordering * Gaps in version numbers - Validate migrations both at definition time and runtime - Update schema_version tracking to reflect new versioning Testing: - Verified no duplicate table creation - Confirmed migrations run in correct order - Validated rollback procedures - Checked version conflict detection
This commit is contained in:
@@ -323,6 +323,31 @@ const parseSQL = (sql: string): ParsedSQL => {
|
||||
return result;
|
||||
};
|
||||
|
||||
// Add version conflict detection
|
||||
const validateMigrationVersions = (migrations: Migration[]): void => {
|
||||
const versions = new Set<number>();
|
||||
const duplicates = new Set<number>();
|
||||
|
||||
migrations.forEach(migration => {
|
||||
if (versions.has(migration.version)) {
|
||||
duplicates.add(migration.version);
|
||||
}
|
||||
versions.add(migration.version);
|
||||
});
|
||||
|
||||
if (duplicates.size > 0) {
|
||||
throw new Error(`Duplicate migration versions found: ${Array.from(duplicates).join(', ')}`);
|
||||
}
|
||||
|
||||
// Verify versions are sequential
|
||||
const sortedVersions = Array.from(versions).sort((a, b) => a - b);
|
||||
for (let i = 0; i < sortedVersions.length; i++) {
|
||||
if (sortedVersions[i] !== i + 1) {
|
||||
throw new Error(`Migration versions must be sequential. Found gap at version ${i + 1}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Initial migration for accounts table
|
||||
const INITIAL_MIGRATION: Migration = {
|
||||
version: 1,
|
||||
@@ -357,24 +382,10 @@ const INITIAL_MIGRATION: Migration = {
|
||||
const MIGRATIONS: Migration[] = [
|
||||
INITIAL_MIGRATION,
|
||||
{
|
||||
version: 1,
|
||||
name: 'initial_schema',
|
||||
description: 'Initial database schema with accounts, secret, settings, contacts, logs, and temp tables',
|
||||
version: 2,
|
||||
name: '002_secret_and_settings',
|
||||
description: 'Add secret, settings, contacts, logs, and temp tables',
|
||||
sql: `
|
||||
-- Accounts table for user identities
|
||||
CREATE TABLE IF NOT EXISTS accounts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
dateCreated TEXT NOT NULL,
|
||||
derivationPath TEXT,
|
||||
did TEXT NOT NULL,
|
||||
identityEncrBase64 TEXT, -- encrypted & base64-encoded
|
||||
mnemonicEncrBase64 TEXT, -- encrypted & base64-encoded
|
||||
passkeyCredIdHex TEXT,
|
||||
publicKeyHex TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_accounts_did ON accounts(did);
|
||||
|
||||
-- Secret table for storing encryption keys
|
||||
-- Note: This is a temporary solution until better secure storage is implemented
|
||||
CREATE TABLE IF NOT EXISTS secret (
|
||||
@@ -447,7 +458,6 @@ const MIGRATIONS: Migration[] = [
|
||||
);
|
||||
`,
|
||||
rollback: `
|
||||
DROP TABLE IF EXISTS accounts;
|
||||
DROP TABLE IF EXISTS secret;
|
||||
DROP TABLE IF EXISTS settings;
|
||||
DROP TABLE IF EXISTS contacts;
|
||||
@@ -457,6 +467,9 @@ const MIGRATIONS: Migration[] = [
|
||||
}
|
||||
];
|
||||
|
||||
// Validate migrations before export
|
||||
validateMigrationVersions(MIGRATIONS);
|
||||
|
||||
// Helper functions
|
||||
const verifyPluginState = async (plugin: any): Promise<boolean> => {
|
||||
try {
|
||||
@@ -1004,6 +1017,9 @@ export async function runMigrations(
|
||||
): Promise<MigrationResult[]> {
|
||||
logger.info('Starting migration process');
|
||||
|
||||
// Validate migrations before running
|
||||
validateMigrationVersions(MIGRATIONS);
|
||||
|
||||
// Verify plugin is available
|
||||
if (!await verifyPluginState(plugin)) {
|
||||
throw new Error('SQLite plugin not available');
|
||||
|
||||
Reference in New Issue
Block a user