{"version":3,"file":"pg-migration-lock.mjs","names":["PostgresAdapter","KyselyPostgresAdapter"],"sources":["../../src/database/pg-migration-lock.ts"],"sourcesContent":["/**\n * Fail-fast Postgres migration locking.\n *\n * Kysely's stock `PostgresAdapter.acquireMigrationLock` runs\n * `select pg_advisory_xact_lock(...)` — an unbounded blocking wait on a\n * database-wide advisory lock, held for the whole migration transaction.\n * On Cloudflare Workers that is a stampede amplifier (#1744): every isolate\n * that cold-starts while migrations are pending parks a connection inside\n * Postgres waiting for the lock, and when the holder's migration fails and\n * rolls back, the next waiter acquires the lock and re-runs the same\n * failing migration.\n *\n * `FailFastPostgresDialect` swaps the blocking wait for\n * `pg_try_advisory_xact_lock`: when another migrator holds the lock, the\n * adapter throws `MIGRATION_LOCK_BUSY_MESSAGE` immediately instead of\n * queueing inside the database. `runMigrations` treats that error like the\n * SQLite/D1 concurrent-migration race — it polls the migration table with\n * cheap bounded SELECTs until the concurrent migrator finishes (or the\n * wait deadline passes), without holding a transaction open.\n */\n\nimport type { DialectAdapter, Kysely, MigrationLockOptions } from \"kysely\";\nimport { PostgresAdapter as KyselyPostgresAdapter, PostgresDialect, sql } from \"kysely\";\n\n/**\n * Sentinel message thrown when another migrator holds the advisory lock.\n * `runMigrations` matches on it to route into the concurrent-migrator wait.\n */\nexport const MIGRATION_LOCK_BUSY_MESSAGE = \"EMDASH_MIGRATION_LOCK_BUSY\";\n\n/**\n * Kysely's advisory lock id (LOCK_ID in kysely's postgres-adapter). Reused\n * deliberately: during a rolling deploy, old isolates still run the stock\n * blocking adapter, and using the same id keeps old and new builds mutually\n * exclusive on the same lock.\n */\nconst KYSELY_PG_MIGRATION_LOCK_ID = BigInt(\"3853314791062309107\");\n\n/**\n * Extends the stock adapter so every capability flag and `instanceof` check\n * is inherited.\n */\nclass PostgresAdapter extends KyselyPostgresAdapter {\n\t// eslint-disable-next-line typescript/no-explicit-any -- matches the DialectAdapter signature\n\toverride async acquireMigrationLock(db: Kysely<any>, _opt: MigrationLockOptions): Promise<void> {\n\t\t// Transaction-level lock, like the stock adapter: Postgres supports\n\t\t// transactional DDL, so `db` here is the migration transaction and\n\t\t// the lock is released automatically on commit/rollback.\n\t\tconst result = await sql<{ acquired: boolean }>`\n\t\t\tselect pg_try_advisory_xact_lock(${sql.lit(KYSELY_PG_MIGRATION_LOCK_ID)}) as acquired\n\t\t`.execute(db);\n\t\tif (!result.rows[0]?.acquired) {\n\t\t\tthrow new Error(MIGRATION_LOCK_BUSY_MESSAGE);\n\t\t}\n\t}\n}\n\n/**\n * Drop-in replacement for Kysely's `PostgresDialect` whose migration lock\n * fails fast instead of blocking inside the database. Everything except\n * `createAdapter` is inherited unchanged, and because it subclasses\n * `PostgresDialect`, the public dialect type of `emdash/db/postgres` stays\n * `PostgresDialect` and `instanceof` checks keep working.\n */\nexport class FailFastPostgresDialect extends PostgresDialect {\n\toverride createAdapter(): DialectAdapter {\n\t\treturn new PostgresAdapter();\n\t}\n}\n"],"mappings":";;;;;;;AA4BA,MAAa,8BAA8B;;;;;;;AAQ3C,MAAM,8BAA8B,OAAO,sBAAsB;;;;;AAMjE,IAAMA,oBAAN,cAA8BC,gBAAsB;CAEnD,MAAe,qBAAqB,IAAiB,MAA2C;AAO/F,MAAI,EAHW,MAAM,GAA0B;sCACX,IAAI,IAAI,4BAA4B,CAAC;IACvE,QAAQ,GAAG,EACD,KAAK,IAAI,SACpB,OAAM,IAAI,MAAM,4BAA4B;;;;;;;;;;AAY/C,IAAa,0BAAb,cAA6C,gBAAgB;CAC5D,AAAS,gBAAgC;AACxC,SAAO,IAAID,mBAAiB"}