/// // // Issue #18 — Baseline ledger sync for out-of-band provisioned databases. // // The Labs/production PocketBase originally ran WITHOUT --migrationsDir: its // schema was created by scripts/setup-pb-collections.mjs / the admin UI, so its // _migrations ledger is empty. When the migrations dir was mounted for the // first time (Azure SSO, PR #17), PocketBase tried to replay the entire // migration history against that existing schema and crash-looped on the very // first file ("Collection name must be unique"). // // This migration sorts before every other file. When it detects that the // legacy schema already exists but the ledger has never tracked it, it marks // the historical migrations below as applied so the replay is skipped and the // chain continues with the genuinely new migrations (team_members_to_auth, // tighten_api_rules). On fresh databases and on databases that already have a // populated ledger it is a no-op. // // NOTE for future agents: never rename a migration file after it has been // applied anywhere — the ledger is filename-based. New migrations must sort // after 1781100001 and be appended to environments through a normal deploy. const HISTORICAL_MIGRATIONS = [ "1778948471_created_content.js", "1778948471_created_quiz_banks.js", "1778948471_created_relations.js", "1778948471_created_sources.js", "1778948471_created_team_members.js", "1778948471_created_topics.js", "1778948472_created_leaderboard.js", "1778948472_created_learn_progress.js", "1778948472_created_quiz_cache.js", "1778948472_created_quiz_results.js", "1778948472_created_settings.js", "1778954289_created_test_col2.js", "1778954310_deleted_relations.js", "1778954310_deleted_topics.js", "1778954310_deleted_users.js", "1778954311_deleted_content.js", "1778954311_deleted_leaderboard.js", "1778954311_deleted_learn_progress.js", "1778954311_deleted_quiz_banks.js", "1778954311_deleted_quiz_cache.js", "1778954311_deleted_quiz_results.js", "1778954311_deleted_settings.js", "1778954311_deleted_sources.js", "1778954311_deleted_team_members.js", "1778954311_deleted_test_col2.js", "1778954317_created_content.js", "1778954317_created_leaderboard.js", "1778954317_created_learn_progress.js", "1778954317_created_quiz_banks.js", "1778954317_created_quiz_cache.js", "1778954317_created_quiz_results.js", "1778954317_created_relations.js", "1778954317_created_settings.js", "1778954317_created_sources.js", "1778954317_created_team_members.js", "1778954317_created_topics.js", "1779005271_created_test_col3.js", "1779005289_created_test_col4.js", "1779005309_deleted_content.js", "1779005309_deleted_learn_progress.js", "1779005309_deleted_quiz_banks.js", "1779005309_deleted_quiz_cache.js", "1779005309_deleted_quiz_results.js", "1779005309_deleted_relations.js", "1779005309_deleted_sources.js", "1779005309_deleted_team_members.js", "1779005309_deleted_topics.js", "1779005310_deleted_leaderboard.js", "1779005310_deleted_settings.js", "1779005310_deleted_test_col3.js", "1779005310_deleted_test_col4.js", "1779005316_created_content.js", "1779005316_created_quiz_banks.js", "1779005316_created_relations.js", "1779005316_created_sources.js", "1779005316_created_topics.js", "1779005317_created_leaderboard.js", "1779005317_created_learn_progress.js", "1779005317_created_quiz_cache.js", "1779005317_created_quiz_results.js", "1779005317_created_settings.js", "1779005317_created_team_members.js", "1779127586_created_curriculum.js", "1779127759_collections_snapshot.js", "1779200000_updated_sources.js", "1780000001_updated_topics.js", "1780500000_updated_topics_relevance_locked.js", "1780500001_normalize_relation_types.js", "1780500002_created_llm_calls.js", "1780600000_curriculum_v2.js", "1780700000_sources_progress.js", "1780800000_created_micro_learnings.js", "1780800001_deleted_legacy_collections.js", "1780800002_update_micro_learnings_rules.js", "1780900000_team_members_enrollment.js", "1780900001_created_test_results.js", "1781000000_created_theme_sessions.js", "1781100000_created_question_bank.js", "1781100001_created_on_demand_attempts.js", ]; migrate((app) => { // Fresh database? (no legacy schema) -> let the normal chain run. try { app.findCollectionByNameOrId("content"); } catch (_) { return; } // Ledger already tracks the history? -> normally migrated DB, nothing to do. const probe = new DynamicModel({ count: 0 }); app.db() .newQuery("SELECT COUNT(*) AS count FROM _migrations WHERE file = {:file}") .bind({ file: "1778948471_created_content.js" }) .one(probe); if (probe.count > 0) { return; } // Out-of-band provisioned database: schema exists, ledger is empty. // Mark the historical migrations as applied so they are not replayed. const applied = Date.now(); for (const file of HISTORICAL_MIGRATIONS) { app.db() .newQuery("INSERT OR IGNORE INTO _migrations (file, applied) VALUES ({:file}, {:applied})") .bind({ file: file, applied: applied }) .execute(); } console.log( "baseline_ledger_sync: detected out-of-band provisioned database — marked " + HISTORICAL_MIGRATIONS.length + " historical migrations as applied" ); }, (_app) => { // Down: intentionally a no-op. The ledger rows describe environment-specific // bookkeeping; removing them would re-trigger the replay this fix prevents. });