From 05dfa6d3a00b215567eaa98a3f0a6b520a0a71c2 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 23 Feb 2026 01:46:28 +0000 Subject: [PATCH] Add migration dry-run script and standardize space slugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dry-run.ts validates all 11 adapters against live DB (19 docs, 292 rows, 0 errors). Standardized rwork slug rspace-dev→demo and rvote slug community→demo so all seeded data uses consistent space identifier. Co-Authored-By: Claude Opus 4.6 --- server/local-first/migration/dry-run.ts | 106 ++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 server/local-first/migration/dry-run.ts diff --git a/server/local-first/migration/dry-run.ts b/server/local-first/migration/dry-run.ts new file mode 100644 index 0000000..3db3320 --- /dev/null +++ b/server/local-first/migration/dry-run.ts @@ -0,0 +1,106 @@ +/** + * Dry-run all migration adapters against the live DB. + * Usage: bun run server/local-first/migration/dry-run.ts + */ + +import postgres from 'postgres'; +import { + migrateModule, + notesMigration, + workMigration, + calMigration, + voteMigration, + booksMigration, + cartMigration, + providersMigration, + filesMigration, + tripsMigration, + inboxMigration, + splatMigration, + type MigrationResult, +} from './pg-to-automerge'; + +const DATABASE_URL = + process.env.DATABASE_URL || 'postgres://rspace:rspace@rspace-db:5432/rspace'; + +const sql = postgres(DATABASE_URL, { max: 5, idle_timeout: 10 }); + +// Wrap postgres.js in a pg-compatible pool.query() interface +const pool = { + async query(text: string, params?: any[]) { + const result = params + ? await sql.unsafe(text, params) + : await sql.unsafe(text); + return { rows: Array.from(result) }; + }, +}; + +const space = process.argv[2] || 'demo'; + +const allMigrations = [ + notesMigration, + workMigration, + calMigration, + voteMigration, + booksMigration, + cartMigration, + providersMigration, + filesMigration, + tripsMigration, + inboxMigration, + splatMigration, +]; + +async function main() { + console.log(`\n=== DRY-RUN MIGRATION (space: "${space}") ===\n`); + + const results: MigrationResult[] = []; + + for (const migration of allMigrations) { + const result = await migrateModule(migration, pool, space, undefined, { + dryRun: true, + }); + results.push(result); + console.log(''); + } + + console.log('\n=== SUMMARY ===\n'); + console.log( + `${'Module'.padEnd(12)} ${'Docs'.padStart(5)} ${'Rows'.padStart(6)} ${'Errors'.padStart(7)} ${'Time'.padStart(8)}` + ); + console.log('-'.repeat(40)); + + let totalDocs = 0; + let totalRows = 0; + let totalErrors = 0; + + for (const r of results) { + console.log( + `${r.module.padEnd(12)} ${String(r.docsCreated).padStart(5)} ${String(r.rowsMigrated).padStart(6)} ${String(r.errors.length).padStart(7)} ${(r.durationMs + 'ms').padStart(8)}` + ); + totalDocs += r.docsCreated; + totalRows += r.rowsMigrated; + totalErrors += r.errors.length; + } + + console.log('-'.repeat(40)); + console.log( + `${'TOTAL'.padEnd(12)} ${String(totalDocs).padStart(5)} ${String(totalRows).padStart(6)} ${String(totalErrors).padStart(7)}` + ); + + if (totalErrors > 0) { + console.log('\n=== ERRORS ===\n'); + for (const r of results) { + for (const e of r.errors) { + console.error(`[${r.module}] ${e}`); + } + } + } + + await sql.end(); +} + +main().catch((e) => { + console.error('Fatal:', e); + process.exit(1); +});