bullmq 6.0.11 → 6.1.0

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.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isPgPool = exports.LATEST_SCHEMA_VERSION = exports.quoteSchemaName = exports.DEFAULT_SCHEMA = exports.MIGRATION_ADVISORY_LOCK_KEY = exports.RECOMMENDED_POSTGRES_VERSION = exports.MINIMUM_POSTGRES_VERSION = exports.assertPostgresVersion = exports.UnsupportedPostgresVersionError = exports.SchemaVersionMismatchError = exports.runMigrations = exports.PostgresQueueBackend = exports.PostgresConnection = exports.createPostgresBackend = void 0;
3
+ exports.isPgPool = exports.LATEST_SCHEMA_VERSION = exports.quoteSchemaName = exports.DEFAULT_SCHEMA = exports.MIGRATION_ADVISORY_LOCK_KEY = exports.BULLMQ_MAJOR_VERSION = exports.RECOMMENDED_POSTGRES_VERSION = exports.MINIMUM_POSTGRES_VERSION = exports.assertPostgresVersion = exports.UnsupportedPostgresVersionError = exports.SchemaVersionMismatchError = exports.SchemaMigrationRequiredError = exports.assertSchemaCompatibility = exports.runMigrations = exports.PostgresQueueBackend = exports.PostgresConnection = exports.createPostgresBackend = void 0;
4
4
  var create_postgres_backend_1 = require("./create-postgres-backend");
5
5
  Object.defineProperty(exports, "createPostgresBackend", { enumerable: true, get: function () { return create_postgres_backend_1.createPostgresBackend; } });
6
6
  var postgres_connection_1 = require("./postgres-connection");
@@ -9,11 +9,14 @@ var postgres_queue_backend_1 = require("./postgres-queue-backend");
9
9
  Object.defineProperty(exports, "PostgresQueueBackend", { enumerable: true, get: function () { return postgres_queue_backend_1.PostgresQueueBackend; } });
10
10
  var migrator_1 = require("./migrator");
11
11
  Object.defineProperty(exports, "runMigrations", { enumerable: true, get: function () { return migrator_1.runMigrations; } });
12
+ Object.defineProperty(exports, "assertSchemaCompatibility", { enumerable: true, get: function () { return migrator_1.assertSchemaCompatibility; } });
13
+ Object.defineProperty(exports, "SchemaMigrationRequiredError", { enumerable: true, get: function () { return migrator_1.SchemaMigrationRequiredError; } });
12
14
  Object.defineProperty(exports, "SchemaVersionMismatchError", { enumerable: true, get: function () { return migrator_1.SchemaVersionMismatchError; } });
13
15
  Object.defineProperty(exports, "UnsupportedPostgresVersionError", { enumerable: true, get: function () { return migrator_1.UnsupportedPostgresVersionError; } });
14
16
  Object.defineProperty(exports, "assertPostgresVersion", { enumerable: true, get: function () { return migrator_1.assertPostgresVersion; } });
15
17
  Object.defineProperty(exports, "MINIMUM_POSTGRES_VERSION", { enumerable: true, get: function () { return migrator_1.MINIMUM_POSTGRES_VERSION; } });
16
18
  Object.defineProperty(exports, "RECOMMENDED_POSTGRES_VERSION", { enumerable: true, get: function () { return migrator_1.RECOMMENDED_POSTGRES_VERSION; } });
19
+ Object.defineProperty(exports, "BULLMQ_MAJOR_VERSION", { enumerable: true, get: function () { return migrator_1.BULLMQ_MAJOR_VERSION; } });
17
20
  Object.defineProperty(exports, "MIGRATION_ADVISORY_LOCK_KEY", { enumerable: true, get: function () { return migrator_1.MIGRATION_ADVISORY_LOCK_KEY; } });
18
21
  Object.defineProperty(exports, "DEFAULT_SCHEMA", { enumerable: true, get: function () { return migrator_1.DEFAULT_SCHEMA; } });
19
22
  Object.defineProperty(exports, "quoteSchemaName", { enumerable: true, get: function () { return migrator_1.quoteSchemaName; } });
@@ -10,17 +10,20 @@ exports.MIGRATIONS = [
10
10
  {
11
11
  version: 1,
12
12
  name: '0001_schema',
13
+ minClientVersion: 6,
13
14
  load: () => (0, sql_loader_1.loadMigrationSql)('0001_schema.sql'),
14
15
  },
15
16
  {
16
17
  version: 2,
17
18
  name: '0002_functions',
19
+ // This initial schema split is the sole same-major exception. Future schema
20
+ // migrations are breaking changes and require a new BullMQ major version.
21
+ minClientVersion: 6,
18
22
  load: () => (0, sql_loader_1.loadMigrationSql)('0002_functions.sql'),
19
23
  },
20
24
  ];
21
25
  /**
22
- * The highest schema version this BullMQ build knows how to produce. Compared
23
- * against the version recorded in the database to decide whether to migrate
24
- * (database older), no-op (equal), or refuse to run (database newer).
26
+ * The highest schema version this BullMQ build knows how to produce. Explicit
27
+ * migration applies older pending versions.
25
28
  */
26
29
  exports.LATEST_SCHEMA_VERSION = exports.MIGRATIONS.length > 0 ? exports.MIGRATIONS[exports.MIGRATIONS.length - 1].version : 0;
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SchemaVersionMismatchError = exports.UnsupportedPostgresVersionError = exports.RECOMMENDED_POSTGRES_VERSION = exports.MINIMUM_POSTGRES_VERSION = exports.MIGRATION_ADVISORY_LOCK_KEY = exports.DEFAULT_SCHEMA = void 0;
3
+ exports.SchemaMigrationRequiredError = exports.SchemaVersionMismatchError = exports.UnsupportedPostgresVersionError = exports.BULLMQ_MAJOR_VERSION = exports.RECOMMENDED_POSTGRES_VERSION = exports.MINIMUM_POSTGRES_VERSION = exports.MIGRATION_ADVISORY_LOCK_KEY = exports.DEFAULT_SCHEMA = void 0;
4
4
  exports.assertPostgresVersion = assertPostgresVersion;
5
5
  exports.quoteSchemaName = quoteSchemaName;
6
+ exports.assertSchemaCompatibility = assertSchemaCompatibility;
6
7
  exports.runMigrations = runMigrations;
7
8
  exports.getCurrentSchemaVersion = getCurrentSchemaVersion;
8
9
  const migrations_1 = require("./migrations");
10
+ const version_1 = require("../version");
9
11
  /**
10
12
  * Default PostgreSQL schema (namespace) the backend lives in. The schema is the
11
13
  * connection-level namespace for *all* queues — the SQL-native replacement for
@@ -38,6 +40,8 @@ exports.MINIMUM_POSTGRES_VERSION = 13;
38
40
  * (mirrors the Redis backend's `recommendedMinimumVersion`).
39
41
  */
40
42
  exports.RECOMMENDED_POSTGRES_VERSION = 14;
43
+ /** The BullMQ major version used for PostgreSQL schema compatibility checks. */
44
+ exports.BULLMQ_MAJOR_VERSION = parseInt(version_1.version.split('.')[0], 10);
41
45
  /**
42
46
  * Thrown when the connected PostgreSQL server is older than {@link
43
47
  * MINIMUM_POSTGRES_VERSION}. Pass `skipVersionCheck: true` on the connection to
@@ -69,19 +73,7 @@ async function assertPostgresVersion(client, skipVersionCheck = false) {
69
73
  }
70
74
  const { rows } = await client.query(`SELECT current_setting('server_version_num') AS num, ` +
71
75
  `current_setting('server_version') AS ver`);
72
- const major = Math.floor(parseInt((_b = (_a = rows[0]) === null || _a === void 0 ? void 0 : _a.num) !== null && _b !== void 0 ? _b : '0', 10) / 10000);
73
- const reported = (_d = (_c = rows[0]) === null || _c === void 0 ? void 0 : _c.ver) !== null && _d !== void 0 ? _d : 'unknown';
74
- if (major < exports.MINIMUM_POSTGRES_VERSION) {
75
- throw new UnsupportedPostgresVersionError(reported, exports.MINIMUM_POSTGRES_VERSION);
76
- }
77
- if (major < exports.RECOMMENDED_POSTGRES_VERSION) {
78
- const warned = assertPostgresVersion._warnedRecommendedVersion;
79
- if (!warned) {
80
- assertPostgresVersion._warnedRecommendedVersion = true;
81
- console.warn(`BullMQ: PostgreSQL ${exports.RECOMMENDED_POSTGRES_VERSION} or newer is ` +
82
- `recommended for the PostgreSQL backend (detected ${reported}).`);
83
- }
84
- }
76
+ assertPostgresVersionValues((_b = (_a = rows[0]) === null || _a === void 0 ? void 0 : _a.num) !== null && _b !== void 0 ? _b : '0', (_d = (_c = rows[0]) === null || _c === void 0 ? void 0 : _c.ver) !== null && _d !== void 0 ? _d : 'unknown');
85
77
  }
86
78
  /**
87
79
  * Validates a PostgreSQL schema name and returns it double-quoted for safe
@@ -107,26 +99,70 @@ function quoteSchemaName(schema) {
107
99
  * to upgrade BullMQ — schema downgrades are not supported.
108
100
  */
109
101
  class SchemaVersionMismatchError extends Error {
110
- constructor(databaseVersion, supportedVersion) {
111
- super(`BullMQ: the PostgreSQL schema is at version ${databaseVersion}, but this ` +
112
- `version of BullMQ only supports schema versions up to ${supportedVersion}. ` +
113
- `The database was migrated by a newer BullMQ release; upgrade BullMQ to ` +
114
- `continue (schema downgrades are not supported).`);
115
- this.databaseVersion = databaseVersion;
116
- this.supportedVersion = supportedVersion;
102
+ constructor(minimumClientVersion, clientVersion) {
103
+ super(`BullMQ: the PostgreSQL schema requires BullMQ major version ` +
104
+ `${minimumClientVersion} or newer, but this client is major version ` +
105
+ `${clientVersion}. Upgrade BullMQ to use this schema.`);
106
+ this.minimumClientVersion = minimumClientVersion;
107
+ this.clientVersion = clientVersion;
117
108
  this.name = 'SchemaVersionMismatchError';
109
+ this.databaseVersion = minimumClientVersion;
110
+ this.supportedVersion = clientVersion;
118
111
  }
119
112
  }
120
113
  exports.SchemaVersionMismatchError = SchemaVersionMismatchError;
114
+ /** Thrown when a connection uses a schema that has not been initialized. */
115
+ class SchemaMigrationRequiredError extends Error {
116
+ constructor(schema) {
117
+ super(`BullMQ: PostgreSQL schema ${JSON.stringify(schema)} is not initialized. ` +
118
+ 'Run the migrations explicitly or pass `migrate: true` on the connection.');
119
+ this.schema = schema;
120
+ this.name = 'SchemaMigrationRequiredError';
121
+ }
122
+ }
123
+ exports.SchemaMigrationRequiredError = SchemaMigrationRequiredError;
124
+ /**
125
+ * Checks schema and server compatibility with one read and without locks or DDL.
126
+ */
127
+ async function assertSchemaCompatibility(client, schema = exports.DEFAULT_SCHEMA, options = {}) {
128
+ var _a, _b, _c, _d;
129
+ const quotedSchema = quoteSchemaName(schema);
130
+ let rows;
131
+ try {
132
+ ({ rows } = await client.query(`SELECT COALESCE(MAX(version), 0)::int AS version,
133
+ COALESCE(
134
+ MAX((to_jsonb(migration)->>'min_client_version')::int),
135
+ $1
136
+ )::int AS min_client_version,
137
+ current_setting('server_version_num') AS server_version_num,
138
+ current_setting('server_version') AS server_version
139
+ FROM ${quotedSchema}.migration`, [exports.BULLMQ_MAJOR_VERSION]));
140
+ }
141
+ catch (err) {
142
+ if (err.code === '42P01') {
143
+ throw new SchemaMigrationRequiredError(schema);
144
+ }
145
+ throw err;
146
+ }
147
+ const state = rows[0];
148
+ if (!options.skipVersionCheck) {
149
+ assertPostgresVersionValues((_a = state === null || state === void 0 ? void 0 : state.server_version_num) !== null && _a !== void 0 ? _a : '0', (_b = state === null || state === void 0 ? void 0 : state.server_version) !== null && _b !== void 0 ? _b : 'unknown');
150
+ }
151
+ const minimumClientVersion = (_c = state === null || state === void 0 ? void 0 : state.min_client_version) !== null && _c !== void 0 ? _c : exports.BULLMQ_MAJOR_VERSION;
152
+ if (minimumClientVersion > exports.BULLMQ_MAJOR_VERSION) {
153
+ throw new SchemaVersionMismatchError(minimumClientVersion, exports.BULLMQ_MAJOR_VERSION);
154
+ }
155
+ return (_d = state === null || state === void 0 ? void 0 : state.version) !== null && _d !== void 0 ? _d : 0;
156
+ }
121
157
  /**
122
158
  * Brings the database schema up to {@link LATEST_SCHEMA_VERSION}.
123
159
  *
124
- * Run on the backend's first `waitUntilReady()` (a constructor cannot perform
125
- * async I/O). Behaviour by current database version:
160
+ * Run explicitly with `runMigrations()` or by passing `migrate: true` on the
161
+ * connection. Behaviour by current database version:
126
162
  *
127
163
  * - **older** than supported → applies the pending migrations in order.
128
164
  * - **equal** to supported → no-op.
129
- * - **newer** than supported → throws {@link SchemaVersionMismatchError}.
165
+ * - **newer** than bundled → no-op if its minimum client major is compatible.
130
166
  *
131
167
  * ## Atomicity
132
168
  *
@@ -185,9 +221,9 @@ async function runMigrations(client, schema = exports.DEFAULT_SCHEMA, options =
185
221
  await client.query(`SET LOCAL search_path TO ${quotedSchema}`);
186
222
  await ensureLedgerTable(client);
187
223
  const currentVersion = await getCurrentSchemaVersion(client);
188
- if (currentVersion > migrations_1.LATEST_SCHEMA_VERSION) {
189
- // Rolled back by the catch below; nothing has been written anyway.
190
- throw new SchemaVersionMismatchError(currentVersion, migrations_1.LATEST_SCHEMA_VERSION);
224
+ const minimumClientVersion = await getMinimumClientVersion(client);
225
+ if (minimumClientVersion > exports.BULLMQ_MAJOR_VERSION) {
226
+ throw new SchemaVersionMismatchError(minimumClientVersion, exports.BULLMQ_MAJOR_VERSION);
191
227
  }
192
228
  if (currentVersion < migrations_1.LATEST_SCHEMA_VERSION) {
193
229
  for (const migration of migrations_1.MIGRATIONS) {
@@ -220,13 +256,35 @@ async function ensureLedgerTable(client) {
220
256
  await client.query(`CREATE TABLE IF NOT EXISTS migration (
221
257
  version integer PRIMARY KEY,
222
258
  name text NOT NULL,
259
+ min_client_version integer NOT NULL,
223
260
  applied_at timestamptz NOT NULL DEFAULT now()
224
261
  )`);
262
+ await client.query('ALTER TABLE migration ADD COLUMN IF NOT EXISTS min_client_version integer');
263
+ await client.query('UPDATE migration SET min_client_version = $1 WHERE min_client_version IS NULL', [exports.BULLMQ_MAJOR_VERSION]);
264
+ await client.query('ALTER TABLE migration ALTER COLUMN min_client_version SET NOT NULL');
265
+ }
266
+ async function getMinimumClientVersion(client) {
267
+ var _a, _b;
268
+ const { rows } = await client.query(`SELECT COALESCE(MAX(min_client_version), $1)::int AS min_client_version
269
+ FROM migration`, [exports.BULLMQ_MAJOR_VERSION]);
270
+ return (_b = (_a = rows[0]) === null || _a === void 0 ? void 0 : _a.min_client_version) !== null && _b !== void 0 ? _b : exports.BULLMQ_MAJOR_VERSION;
225
271
  }
226
272
  async function applyMigration(client, migration) {
227
273
  await client.query(migration.load());
228
- await client.query('INSERT INTO migration (version, name) VALUES ($1, $2)', [
229
- migration.version,
230
- migration.name,
231
- ]);
274
+ await client.query(`INSERT INTO migration (version, name, min_client_version)
275
+ VALUES ($1, $2, $3)`, [migration.version, migration.name, migration.minClientVersion]);
276
+ }
277
+ function assertPostgresVersionValues(num, reported) {
278
+ const major = Math.floor(parseInt(num, 10) / 10000);
279
+ if (major < exports.MINIMUM_POSTGRES_VERSION) {
280
+ throw new UnsupportedPostgresVersionError(reported, exports.MINIMUM_POSTGRES_VERSION);
281
+ }
282
+ if (major < exports.RECOMMENDED_POSTGRES_VERSION) {
283
+ const warned = assertPostgresVersion._warnedRecommendedVersion;
284
+ if (!warned) {
285
+ assertPostgresVersion._warnedRecommendedVersion = true;
286
+ console.warn(`BullMQ: PostgreSQL ${exports.RECOMMENDED_POSTGRES_VERSION} or newer is ` +
287
+ `recommended for the PostgreSQL backend (detected ${reported}).`);
288
+ }
289
+ }
232
290
  }
@@ -35,10 +35,8 @@ function loadPgModule() {
35
35
  * "wait for job" primitive (lazily established).
36
36
  *
37
37
  * Lifecycle mirrors {@link RedisConnection}: it is an {@link EventEmitter} that
38
- * surfaces normalized `'ready' | 'error' | 'close'` events, exposes
39
- * {@link PostgresConnection.waitUntilReady} (which also runs the schema
40
- * migrations exactly once, on a dedicated checked-out client) and
41
- * {@link PostgresConnection.close}.
38
+ * surfaces normalized `'ready' | 'error' | 'close'` events and exposes
39
+ * {@link PostgresConnection.waitUntilReady} and {@link PostgresConnection.close}.
42
40
  */
43
41
  class PostgresConnection extends events_1.EventEmitter {
44
42
  constructor(connection) {
@@ -53,6 +51,7 @@ class PostgresConnection extends events_1.EventEmitter {
53
51
  this.ownsPool = false;
54
52
  this.schema = migrator_1.DEFAULT_SCHEMA;
55
53
  this.skipVersionCheck = false;
54
+ this.migrateOnConnect = false;
56
55
  this.pgModule = undefined;
57
56
  this.listenClientConfig = undefined;
58
57
  }
@@ -62,11 +61,18 @@ class PostgresConnection extends events_1.EventEmitter {
62
61
  ? {
63
62
  schema: undefined,
64
63
  skipVersionCheck: undefined,
64
+ migrate: undefined,
65
+ skipMigrations: undefined,
65
66
  connectionString: connection,
66
67
  }
67
- : connection, { schema, skipVersionCheck } = _a, poolConfig = tslib_1.__rest(_a, ["schema", "skipVersionCheck"]);
68
+ : connection, { schema, skipVersionCheck, migrate, skipMigrations } = _a, poolConfig = tslib_1.__rest(_a, ["schema", "skipVersionCheck", "migrate", "skipMigrations"]);
69
+ if (migrate !== undefined && skipMigrations !== undefined) {
70
+ throw new Error('BullMQ: `migrate` and `skipMigrations` are mutually exclusive. Set only one.');
71
+ }
68
72
  this.schema = schema !== null && schema !== void 0 ? schema : migrator_1.DEFAULT_SCHEMA;
69
73
  this.skipVersionCheck = skipVersionCheck !== null && skipVersionCheck !== void 0 ? skipVersionCheck : false;
74
+ this.migrateOnConnect =
75
+ migrate !== null && migrate !== void 0 ? migrate : (skipMigrations === undefined ? false : !skipMigrations);
70
76
  // Validate early so a bad schema name fails fast (and before any DDL).
71
77
  const quotedSchema = (0, migrator_1.quoteSchemaName)(this.schema);
72
78
  // Pin every pooled connection's search_path to the schema so the `.sql`
@@ -101,11 +107,11 @@ class PostgresConnection extends events_1.EventEmitter {
101
107
  }
102
108
  }
103
109
  /**
104
- * Resolves once the pool is reachable and the schema is up to date.
110
+ * Resolves once the pool is reachable and the schema is compatible.
105
111
  *
106
- * Idempotent and memoized: the migration runs exactly once per connection,
107
- * on a single dedicated client checked out of the pool (so the migration's
108
- * advisory lock and transaction share one session — see {@link runMigrations}).
112
+ * Idempotent and memoized. By default this only reads the migration ledger.
113
+ * When `migrate: true` was configured, migrations run on one checked-out
114
+ * client so their advisory lock and transaction share a session.
109
115
  */
110
116
  async waitUntilReady() {
111
117
  if (!this.readyPromise) {
@@ -116,9 +122,16 @@ class PostgresConnection extends events_1.EventEmitter {
116
122
  async bootstrap() {
117
123
  const client = await this.pool.connect();
118
124
  try {
119
- await (0, migrator_1.runMigrations)(client, this.schema, {
120
- skipVersionCheck: this.skipVersionCheck,
121
- });
125
+ if (this.migrateOnConnect) {
126
+ await (0, migrator_1.runMigrations)(client, this.schema, {
127
+ skipVersionCheck: this.skipVersionCheck,
128
+ });
129
+ }
130
+ else {
131
+ await (0, migrator_1.assertSchemaCompatibility)(client, this.schema, {
132
+ skipVersionCheck: this.skipVersionCheck,
133
+ });
134
+ }
122
135
  }
123
136
  finally {
124
137
  client.release();