@plakboek/db 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -8,11 +8,11 @@ for Plakboek CMS installations.
8
8
  - `createDb` -- a Drizzle (`drizzle-orm/postgres-js`) connection factory
9
9
  over the `postgres` (porsager) driver.
10
10
  - `runMigrations` -- applies this package's own statically registered
11
- migrations, holding a Postgres advisory lock for the whole run so
12
- concurrent migrators can never double-apply (D-03). What Phase 6's deploy
13
- step invokes.
14
- - `withMigrationLock`, `MigrationLockTimeoutError` -- the session-scoped
15
- advisory lock primitive `runMigrations` wraps around every run.
11
+ migrations, taking a session-scoped Postgres advisory lock internally for
12
+ the whole run so concurrent migrators can never double-apply (D-03). What
13
+ Phase 6's deploy step invokes.
14
+ - `MigrationLockTimeoutError` -- thrown by `runMigrations` when another
15
+ migrator still holds that advisory lock after `lockWaitMs`.
16
16
  - The migration authoring convention every migration -- this package's own
17
17
  and every host/host-extension migration -- follows: see
18
18
  [`MIGRATIONS.md`](./MIGRATIONS.md).
package/dist/index.js CHANGED
@@ -32,6 +32,15 @@ function createDb(options) {
32
32
  }
33
33
  };
34
34
  }
35
+ //#endregion
36
+ //#region src/lock.ts
37
+ /**
38
+ * Fixed advisory-lock key reserved for @plakboek/db's migration runner.
39
+ * Postgres advisory locks share a single namespace per database across every
40
+ * caller -- this key must never be reused by another subsystem within a
41
+ * @plakboek/db-managed database.
42
+ */
43
+ const MIGRATION_LOCK_KEY = "4839120657231098713";
35
44
  var MigrationLockTimeoutError = class extends Error {
36
45
  waitedMs;
37
46
  constructor(waitedMs) {
@@ -92,14 +101,26 @@ async function runGuarded(fn) {
92
101
  *
93
102
  * The unlock query always runs after `fn` settles (success or failure) --
94
103
  * this is expressed as unconditional sequential code (not a `try/finally`)
95
- * so returning `outcome.value` never needs an unsafe cast to narrow `T`.
104
+ * so returning `outcome.value` never needs an unsafe cast to narrow `T`. The
105
+ * unlock query itself is guarded the same way `fn` is: if `fn` already
106
+ * succeeded (its work is already durable in Postgres) and the unlock query
107
+ * then fails -- e.g. the connection drops between commit and unlock -- that
108
+ * failure is logged rather than thrown, so a lock-release problem can never
109
+ * discard an already-successful `outcome.value`. Postgres releases a
110
+ * session-scoped advisory lock automatically once the session disconnects,
111
+ * so a failed unlock does not leave the lock stuck. If `fn` itself failed,
112
+ * `fn`'s error is what gets thrown regardless of the unlock outcome.
96
113
  */
97
114
  async function withMigrationLock(client, fn, options) {
98
115
  await acquire(client, options?.waitMs ?? DEFAULT_WAIT_MS, options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
99
116
  const outcome = await runGuarded(fn);
100
- const unlocked = (await client.query("SELECT pg_advisory_unlock($1::bigint) AS unlocked", ["4839120657231098713"])).rows[0]?.unlocked ?? false;
117
+ const unlockOutcome = await runGuarded(() => client.query("SELECT pg_advisory_unlock($1::bigint) AS unlocked", [MIGRATION_LOCK_KEY]));
101
118
  if (outcome.ok) {
102
- if (!unlocked) throw new Error("@plakboek/db: migration lock was not held at release -- this indicates a bug in withMigrationLock or an external pg_advisory_unlock call on the same key");
119
+ if (!unlockOutcome.ok) {
120
+ console.error("@plakboek/db: migrations applied successfully, but releasing the migration lock failed -- the lock releases automatically once this session disconnects", unlockOutcome.error);
121
+ return outcome.value;
122
+ }
123
+ if (!(unlockOutcome.value.rows[0]?.unlocked ?? false)) throw new Error("@plakboek/db: migration lock was not held at release -- this indicates a bug in withMigrationLock or an external pg_advisory_unlock call on the same key");
103
124
  return outcome.value;
104
125
  }
105
126
  throw outcome.error;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plakboek/db",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Postgres connection factory and migration-lock primitives for Plakboek CMS installations",
5
5
  "homepage": "https://github.com/flovan/plakboek/tree/main/packages/db#readme",
6
6
  "bugs": {
@@ -25,7 +25,8 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "publishConfig": {
28
- "access": "public"
28
+ "access": "public",
29
+ "provenance": true
29
30
  },
30
31
  "dependencies": {
31
32
  "pg": "^8.23.0",
@@ -45,7 +46,7 @@
45
46
  "drizzle-orm": "^0.45.2"
46
47
  },
47
48
  "engines": {
48
- "node": ">=22.16"
49
+ "node": ">=22.18"
49
50
  },
50
51
  "scripts": {
51
52
  "build": "tsdown",