This directory contains the database migration system for the Memorall application.
The migration system provides:
- Version tracking: Tracks which migrations have been applied
- Sequential execution: Runs migrations in order based on version numbers
- Rollback support: Optional down migrations for reverting changes
- Error handling: Comprehensive error reporting and transaction safety
- Migration Tracking Table:
_migrationstable stores applied migration history - Sequential Execution: Migrations run in version order, skipping already applied ones
- Atomic Operations: Each migration runs independently with proper error handling
- Logging: Detailed console output shows migration progress
migrations/
├── README.md # This documentation
├── index.ts # Migration system core
├── initial.ts # Version 1: Initial schema
├── 002_example_future_migration.ts # Example for future migrations
└── [future migrations...]
Create a new file with format: {version}_{description}.ts
// 003_add_user_settings.ts
import type { PGlite } from "@electric-sql/pglite";
export const up = async (db: PGlite) => {
await db.exec(\`
CREATE TABLE IF NOT EXISTS user_settings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
setting_key TEXT NOT NULL UNIQUE,
setting_value JSONB NOT NULL,
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
updated_at TIMESTAMP DEFAULT NOW() NOT NULL
);
CREATE INDEX IF NOT EXISTS user_settings_key_idx ON user_settings(setting_key);
\`);
};
export const down = async (db: PGlite) => {
await db.exec(\`
DROP TABLE IF EXISTS user_settings;
\`);
};Add the migration to index.ts:
import { up as userSettingsUp, down as userSettingsDown } from './003_add_user_settings';
export const migrations: Migration[] = [
// ... existing migrations
{
id: '003_add_user_settings',
version: 3,
description: 'Add user settings table',
up: userSettingsUp,
down: userSettingsDown,
},
];Migrations run automatically on database initialization. Test by:
- Starting the application
- Checking console output for migration logs
- Verifying the
_migrationstable contains your new entry
- Incremental Changes: Make small, focused changes per migration
- Backwards Compatible: Avoid breaking existing data when possible
- Descriptive Names: Use clear, descriptive migration IDs and descriptions
- Test Thoroughly: Test both up and down migrations if provided
- Use Transactions: Complex migrations should use explicit transaction blocks
- Version Numbers: Always increment version numbers sequentially
- No Edits: Never edit existing migrations that have been applied
- Schema Changes: Update Drizzle schema definitions when adding tables/columns
- Data Migrations: Be careful with data transformations in production
- Editing applied migrations
- Skipping version numbers
- Complex business logic in migrations
- Dropping data without backup considerations
export const up = async (db: PGlite) => {
await db.exec(\`
CREATE TABLE IF NOT EXISTS new_feature (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
data JSONB DEFAULT '{}',
created_at TIMESTAMP DEFAULT NOW() NOT NULL
);
CREATE INDEX IF NOT EXISTS new_feature_name_idx ON new_feature(name);
\`);
};export const up = async (db: PGlite) => {
await db.exec(\`
ALTER TABLE existing_table
ADD COLUMN IF NOT EXISTS new_column TEXT;
CREATE INDEX IF NOT EXISTS existing_table_new_column_idx
ON existing_table(new_column);
\`);
};export const up = async (db: PGlite) => {
await db.exec(\`
-- Add new column
ALTER TABLE users ADD COLUMN IF NOT EXISTS full_name TEXT;
-- Migrate existing data
UPDATE users
SET full_name = first_name || ' ' || last_name
WHERE full_name IS NULL;
\`);
};runMigrations(db: PGlite): Run all pending migrationsgetAppliedMigrations(db: PGlite): Get list of applied migration IDsmarkMigrationApplied(db: PGlite, migration: Migration): Mark migration as applied
interface Migration {
id: string; // Unique identifier
version: number; // Sequential version number
description: string; // Human-readable description
up: (db: PGlite) => Promise<void>; // Forward migration
down?: (db: PGlite) => Promise<void>; // Optional rollback
}If a migration fails:
- Check console error output
- Verify SQL syntax and PGlite compatibility
- Check for missing dependencies or extensions
- Fix the migration file and restart the application
Currently, rollbacks are manual. To rollback:
- Remove the migration entry from
_migrationstable - Run the down migration manually if provided
- Restart the application to re-run migrations
To start fresh (development only):
- Clear IndexedDB data in browser dev tools
- Restart the application
- All migrations will run from scratch
Check migration status:
-- View applied migrations
SELECT * FROM _migrations ORDER BY version;
-- Check latest migration
SELECT * FROM _migrations ORDER BY version DESC LIMIT 1;This migration system ensures reliable, trackable database schema evolution while maintaining data integrity and providing clear upgrade paths.