@abgov/nx-adsp 13.7.2 → 13.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abgov/nx-adsp",
3
- "version": "13.7.2",
3
+ "version": "13.7.3",
4
4
  "license": "Apache-2.0",
5
5
  "main": "src/index.js",
6
6
  "description": "Government of Alberta - Nx plugin for ADSP apps.",
@@ -105,6 +105,11 @@ describe('Express Service Generator', () => {
105
105
  expect(host.exists('apps/test/src/db/schema.ts')).toBeTruthy();
106
106
  expect(host.exists('apps/test/src/database.ts')).toBeTruthy();
107
107
  expect(host.exists('apps/test/src/migrate.ts')).toBeTruthy();
108
+ const migrateTs = host.read('apps/test/src/migrate.ts').toString();
109
+ // Guards against concurrent replicas racing drizzle-orm's migrate(), which has
110
+ // no built-in protection (drizzle-team/drizzle-orm#874).
111
+ expect(migrateTs).toContain('pg_advisory_lock');
112
+ expect(migrateTs).toContain('pg_advisory_unlock');
108
113
  expect(host.exists('apps/test/drizzle.config.ts')).toBeTruthy();
109
114
  expect(host.exists('apps/test/scripts/dev-db.sh')).toBeTruthy();
110
115
  expect(host.exists('apps/test/.env.example')).toBeTruthy();
@@ -4,6 +4,13 @@ import { migrate } from 'drizzle-orm/node-postgres/migrator';
4
4
  import { Pool } from 'pg';
5
5
 
6
6
  const MIGRATIONS_FOLDER = 'drizzle';
7
+ // Arbitrary, stable key — advisory locks are scoped per-database, not shared
8
+ // across a Postgres instance (confirmed via the pg_locks documentation: "the
9
+ // same advisory lock key can be held simultaneously in different databases
10
+ // ... they don't conflict across DB boundaries"), so this only needs to be
11
+ // unique within this app's own database, not across every app sharing a
12
+ // sandbox Postgres instance.
13
+ const MIGRATION_LOCK_KEY = 8812345;
7
14
 
8
15
  // Standalone migration runner — the deployment runs this as an init container
9
16
  // (`node migrate.js`) before the app starts. It uses only drizzle-orm + pg,
@@ -25,11 +32,24 @@ async function main() {
25
32
  }
26
33
 
27
34
  const pool = new Pool({ connectionString });
35
+ // drizzle-orm's migrate() has no protection against concurrent execution
36
+ // (github.com/drizzle-team/drizzle-orm/issues/874, open, acknowledged by
37
+ // its maintainers) — it reads the last-applied migration with a plain
38
+ // SELECT before opening a transaction, so two replicas starting at once can
39
+ // both see "nothing applied yet" and both try to run the same migration.
40
+ // A session-scoped advisory lock on a dedicated connection serializes
41
+ // concurrent runs of this script: whichever loses blocks here until the
42
+ // winner releases the lock, then proceeds against an already-migrated
43
+ // database — a safe no-op, since __drizzle_migrations already records it.
44
+ const lockClient = await pool.connect();
28
45
  try {
46
+ await lockClient.query('SELECT pg_advisory_lock($1)', [MIGRATION_LOCK_KEY]);
29
47
  await migrate(drizzle(pool), { migrationsFolder: MIGRATIONS_FOLDER });
30
48
  // eslint-disable-next-line no-console
31
49
  console.log('Migrations applied.');
32
50
  } finally {
51
+ await lockClient.query('SELECT pg_advisory_unlock($1)', [MIGRATION_LOCK_KEY]);
52
+ lockClient.release();
33
53
  await pool.end();
34
54
  }
35
55
  }