pg-boss 12.5.4 → 12.7.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.
package/README.md CHANGED
@@ -52,6 +52,108 @@ This will likely cater the most to teams already familiar with the simplicity of
52
52
  * Serverless function compatible
53
53
  * Multi-master compatible (for example, in a Kubernetes ReplicaSet)
54
54
 
55
+ ## CLI
56
+
57
+ pg-boss includes a command-line interface for managing database migrations without writing code. This is useful for CI/CD pipelines, database setup scripts, or manual schema management.
58
+
59
+ ### Installation
60
+
61
+ When installed globally, the CLI is available as `pg-boss`:
62
+
63
+ ```bash
64
+ npm install -g pg-boss
65
+ pg-boss --help
66
+ ```
67
+
68
+ Or run directly with npx:
69
+
70
+ ```bash
71
+ npx pg-boss --help
72
+ ```
73
+
74
+ ### Commands
75
+
76
+ | Command | Description |
77
+ |---------|-------------|
78
+ | `migrate` | Run pending migrations (creates schema if not exists) |
79
+ | `create` | Create initial pg-boss schema |
80
+ | `version` | Show current schema version |
81
+ | `rollback` | Rollback the last migration |
82
+ | `plans <subcommand>` | Output SQL without executing (subcommands: `create`, `migrate`, `rollback`) |
83
+
84
+ ### Connection Configuration
85
+
86
+ The CLI supports multiple ways to configure the database connection, in order of precedence:
87
+
88
+ 1. **Command-line arguments**
89
+ ```bash
90
+ pg-boss migrate --connection-string postgres://user:pass@host/database
91
+ # or individual options
92
+ pg-boss migrate --host localhost --port 5432 --database mydb --user postgres --password secret
93
+ ```
94
+
95
+ 2. **Environment variables**
96
+ ```bash
97
+ PGBOSS_DATABASE_URL=postgres://user:pass@host/database pg-boss migrate
98
+ # or individual variables
99
+ PGBOSS_HOST=localhost PGBOSS_PORT=5432 PGBOSS_DATABASE=mydb PGBOSS_USER=postgres PGBOSS_PASSWORD=secret pg-boss migrate
100
+ ```
101
+
102
+ This allows admin credentials for migrations to coexist with regular application database credentials (e.g., `DATABASE_URL` for the app, `PGBOSS_DATABASE_URL` for migrations).
103
+
104
+ 3. **Config file** (pgboss.json or .pgbossrc in current directory, or specify with `--config`)
105
+ ```bash
106
+ pg-boss migrate --config ./config/pgboss.json
107
+ ```
108
+
109
+ Config file format:
110
+ ```json
111
+ {
112
+ "host": "localhost",
113
+ "port": 5432,
114
+ "database": "mydb",
115
+ "user": "postgres",
116
+ "password": "secret",
117
+ "schema": "pgboss"
118
+ }
119
+ ```
120
+
121
+ ### Options
122
+
123
+ | Option | Short | Description |
124
+ |--------|-------|-------------|
125
+ | `--connection-string` | `-c` | PostgreSQL connection string |
126
+ | `--host` | | Database host |
127
+ | `--port` | | Database port |
128
+ | `--database` | `-d` | Database name |
129
+ | `--user` | `-u` | Database user |
130
+ | `--password` | `-p` | Database password |
131
+ | `--schema` | `-s` | pg-boss schema name (default: pgboss) |
132
+ | `--config` | | Path to config file |
133
+ | `--dry-run` | | Show SQL without executing (for migrate, create, rollback) |
134
+
135
+ ### Examples
136
+
137
+ ```bash
138
+ # Create schema in a new database
139
+ pg-boss create --connection-string postgres://localhost/myapp
140
+
141
+ # Run migrations in CI/CD pipeline
142
+ PGBOSS_DATABASE_URL=$PGBOSS_DATABASE_URL pg-boss migrate
143
+
144
+ # Preview migration SQL before running
145
+ pg-boss migrate --connection-string postgres://localhost/myapp --dry-run
146
+
147
+ # Check current schema version
148
+ pg-boss version -c postgres://localhost/myapp
149
+
150
+ # Use a custom schema name
151
+ pg-boss migrate -c postgres://localhost/myapp --schema myapp_jobs
152
+
153
+ # Output SQL for creating schema (useful for review or manual execution)
154
+ pg-boss plans create --schema myapp_jobs
155
+ ```
156
+
55
157
  ## Requirements
56
158
  * Node 22.12 or higher for CommonJS's require(esm)
57
159
  * PostgreSQL 13 or higher
@@ -1 +1 @@
1
- {"version":3,"file":"attorney.d.ts","sourceRoot":"","sources":["../src/attorney.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,KAAK,MAAM,YAAY,CAAA;AAExC,QAAA,MAAM,MAAM;;;;CAIX,CAAA;AAMD,iBAAS,iBAAiB,CAAE,MAAM,GAAE,GAAQ,QAW3C;AAED,iBAAS,aAAa,CAAE,IAAI,EAAE,GAAG,GAAG,KAAK,CAAC,OAAO,CA8ChD;AAED,iBAAS,aAAa,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG;IAClD,OAAO,EAAE,KAAK,CAAC,mBAAmB,CAAA;IAClC,QAAQ,EAAE,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;CACjC,CAyBA;AAED,iBAAS,cAAc,CAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,QAOlD;AAED,iBAAS,SAAS,CAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,kBAAkB,GAAG,KAAK,CAAC,0BAA0B,CAmB9F;AAkBD,iBAAS,wBAAwB,CAAE,IAAI,EAAE,MAAM,QAK9C;AAED,iBAAS,eAAe,CAAE,IAAI,EAAE,MAAM,QAIrC;AAED,iBAAS,SAAS,CAAE,GAAG,EAAE,MAAM,QAI9B;AAuFD,OAAO,EACL,SAAS,EACT,wBAAwB,EACxB,eAAe,EACf,cAAc,EACd,aAAa,EACb,aAAa,EACb,SAAS,EACT,MAAM,EACN,iBAAiB,EAClB,CAAA"}
1
+ {"version":3,"file":"attorney.d.ts","sourceRoot":"","sources":["../src/attorney.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,KAAK,MAAM,YAAY,CAAA;AAExC,QAAA,MAAM,MAAM;;;;CAIX,CAAA;AAMD,iBAAS,iBAAiB,CAAE,MAAM,GAAE,GAAQ,QAW3C;AAED,iBAAS,aAAa,CAAE,IAAI,EAAE,GAAG,GAAG,KAAK,CAAC,OAAO,CA+ChD;AAmED,iBAAS,aAAa,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG;IAClD,OAAO,EAAE,KAAK,CAAC,mBAAmB,CAAA;IAClC,QAAQ,EAAE,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;CACjC,CA2BA;AAED,iBAAS,cAAc,CAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,QAOlD;AAED,iBAAS,SAAS,CAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,kBAAkB,GAAG,KAAK,CAAC,0BAA0B,CAoB9F;AAkBD,iBAAS,wBAAwB,CAAE,IAAI,EAAE,MAAM,QAK9C;AAED,iBAAS,eAAe,CAAE,IAAI,EAAE,MAAM,QAIrC;AAED,iBAAS,SAAS,CAAE,GAAG,EAAE,MAAM,QAI9B;AA+FD,OAAO,EACL,SAAS,EACT,wBAAwB,EACxB,eAAe,EACf,cAAc,EACd,aAAa,EACb,aAAa,EACb,SAAS,EACT,MAAM,EACN,iBAAiB,EAClB,CAAA"}
package/dist/attorney.js CHANGED
@@ -51,8 +51,57 @@ function checkSendArgs(args) {
51
51
  validateExpirationConfig(options);
52
52
  validateRetentionConfig(options);
53
53
  validateDeletionConfig(options);
54
+ validateGroupConfig(options);
54
55
  return { name, data, options };
55
56
  }
57
+ function validateGroupConfig(config) {
58
+ if (!('group' in config) || config.group === undefined || config.group === null) {
59
+ return;
60
+ }
61
+ assert(typeof config.group === 'object', 'group must be an object');
62
+ assert(typeof config.group.id === 'string' && config.group.id.length > 0, 'group.id must be a non-empty string');
63
+ assert(!('tier' in config.group) || (typeof config.group.tier === 'string' && config.group.tier.length > 0), 'group.tier must be a non-empty string if provided');
64
+ }
65
+ function validateGroupConcurrencyValue(value, optionName) {
66
+ if (typeof value === 'number') {
67
+ assert(Number.isInteger(value) && value >= 1, `${optionName} must be an integer >= 1`);
68
+ return;
69
+ }
70
+ assert(typeof value === 'object', `${optionName} must be a number or an object with { default, tiers? }`);
71
+ assert(Number.isInteger(value.default) && value.default >= 1, `${optionName}.default must be an integer >= 1`);
72
+ if ('tiers' in value && value.tiers) {
73
+ assert(typeof value.tiers === 'object', `${optionName}.tiers must be an object`);
74
+ for (const [tier, limit] of Object.entries(value.tiers)) {
75
+ assert(typeof tier === 'string' && tier.length > 0, `${optionName} tier keys must be non-empty strings`);
76
+ assert(Number.isInteger(limit) && limit >= 1, `${optionName}.tiers["${tier}"] must be an integer >= 1`);
77
+ }
78
+ }
79
+ }
80
+ function validateGroupConcurrencyConfig(config) {
81
+ const hasGlobal = config.groupConcurrency != null;
82
+ const hasLocal = config.localGroupConcurrency != null;
83
+ assert(!(hasGlobal && hasLocal), 'cannot specify both groupConcurrency and localGroupConcurrency - choose one');
84
+ if (hasGlobal)
85
+ validateGroupConcurrencyValue(config.groupConcurrency, 'groupConcurrency');
86
+ if (hasLocal) {
87
+ validateGroupConcurrencyValue(config.localGroupConcurrency, 'localGroupConcurrency');
88
+ validateLocalGroupConcurrencyLimit(config.localGroupConcurrency, config.localConcurrency);
89
+ }
90
+ }
91
+ function validateLocalGroupConcurrencyLimit(localGroupConcurrency, localConcurrency) {
92
+ const effectiveLocalConcurrency = localConcurrency ?? 1;
93
+ if (typeof localGroupConcurrency === 'number') {
94
+ assert(localGroupConcurrency <= effectiveLocalConcurrency, `localGroupConcurrency (${localGroupConcurrency}) cannot exceed localConcurrency (${effectiveLocalConcurrency})`);
95
+ }
96
+ else if (typeof localGroupConcurrency === 'object') {
97
+ assert(localGroupConcurrency.default <= effectiveLocalConcurrency, `localGroupConcurrency.default (${localGroupConcurrency.default}) cannot exceed localConcurrency (${effectiveLocalConcurrency})`);
98
+ if (localGroupConcurrency.tiers) {
99
+ for (const [tier, limit] of Object.entries(localGroupConcurrency.tiers)) {
100
+ assert(limit <= effectiveLocalConcurrency, `localGroupConcurrency.tiers["${tier}"] (${limit}) cannot exceed localConcurrency (${effectiveLocalConcurrency})`);
101
+ }
102
+ }
103
+ }
104
+ }
56
105
  function checkWorkArgs(name, args) {
57
106
  let options, callback;
58
107
  assert(name, 'queue name is required');
@@ -71,6 +120,8 @@ function checkWorkArgs(name, args) {
71
120
  assert(!('batchSize' in options) || (Number.isInteger(options.batchSize) && options.batchSize >= 1), 'batchSize must be an integer > 0');
72
121
  assert(!('includeMetadata' in options) || typeof options.includeMetadata === 'boolean', 'includeMetadata must be a boolean');
73
122
  assert(!('priority' in options) || typeof options.priority === 'boolean', 'priority must be a boolean');
123
+ assert(!('localConcurrency' in options) || (Number.isInteger(options.localConcurrency) && options.localConcurrency >= 1), 'localConcurrency must be an integer >= 1');
124
+ validateGroupConcurrencyConfig(options);
74
125
  return { options, callback };
75
126
  }
76
127
  function checkFetchArgs(name, options) {
@@ -92,6 +143,7 @@ function getConfig(value) {
92
143
  applySchemaConfig(config);
93
144
  applyOpsConfig(config);
94
145
  applyScheduleConfig(config);
146
+ applyBamConfig(config);
95
147
  validateWarningConfig(config);
96
148
  return config;
97
149
  }
@@ -157,7 +209,7 @@ function applyOpsConfig(config) {
157
209
  assert(config.queueCacheIntervalSeconds / 60 / 60 <= POLICY.MAX_EXPIRATION_HOURS, `configuration assert: queueCacheIntervalSeconds cannot exceed ${POLICY.MAX_EXPIRATION_HOURS} hours`);
158
210
  }
159
211
  function validateDeletionConfig(config) {
160
- assert(!('deleteAfterSeconds' in config) || config.deleteAfterSeconds >= 1, 'configuration assert: deleteAfterSeconds must be at least every second');
212
+ assert(!('deleteAfterSeconds' in config) || config.deleteAfterSeconds >= 0, 'configuration assert: deleteAfterSeconds must be at least 0 (0 disables deletion)');
161
213
  }
162
214
  function applyScheduleConfig(config) {
163
215
  assert(!('clockMonitorIntervalSeconds' in config) || (config.clockMonitorIntervalSeconds >= 1 && config.clockMonitorIntervalSeconds <= 600), 'configuration assert: clockMonitorIntervalSeconds must be between 1 second and 10 minutes');
@@ -167,4 +219,9 @@ function applyScheduleConfig(config) {
167
219
  assert(!('cronWorkerIntervalSeconds' in config) || (config.cronWorkerIntervalSeconds >= 1 && config.cronWorkerIntervalSeconds <= 45), 'configuration assert: cronWorkerIntervalSeconds must be between 1 and 45 seconds');
168
220
  config.cronWorkerIntervalSeconds = config.cronWorkerIntervalSeconds || 5;
169
221
  }
222
+ function applyBamConfig(config) {
223
+ const minInterval = config.__test__bypass_bam_interval_check ? 1 : 10;
224
+ assert(!('bamIntervalSeconds' in config) || config.bamIntervalSeconds >= minInterval, `configuration assert: bamIntervalSeconds must be at least ${minInterval} seconds`);
225
+ config.bamIntervalSeconds = config.bamIntervalSeconds || 60;
226
+ }
170
227
  export { assertKey, assertPostgresObjectName, assertQueueName, checkFetchArgs, checkSendArgs, checkWorkArgs, getConfig, POLICY, validateQueueArgs };
package/dist/bam.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import EventEmitter from 'node:events';
2
+ import * as types from './types.js';
3
+ declare class Bam extends EventEmitter implements types.EventsMixin {
4
+ #private;
5
+ events: {
6
+ error: string;
7
+ bam: string;
8
+ };
9
+ constructor(db: types.IDatabase, config: types.ResolvedConstructorOptions);
10
+ start(): Promise<void>;
11
+ stop(): Promise<void>;
12
+ }
13
+ export default Bam;
14
+ //# sourceMappingURL=bam.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bam.d.ts","sourceRoot":"","sources":["../src/bam.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,aAAa,CAAA;AAEtC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAOnC,cAAM,GAAI,SAAQ,YAAa,YAAW,KAAK,CAAC,WAAW;;IAOzD,MAAM;;;MAAS;gBAGb,EAAE,EAAE,KAAK,CAAC,SAAS,EACnB,MAAM,EAAE,KAAK,CAAC,0BAA0B;IAUpC,KAAK;IAWL,IAAI;CAgGX;AAED,eAAe,GAAG,CAAA"}
package/dist/bam.js ADDED
@@ -0,0 +1,114 @@
1
+ import EventEmitter from 'node:events';
2
+ import * as plans from './plans.js';
3
+ import * as types from './types.js';
4
+ const events = {
5
+ error: 'error',
6
+ bam: 'bam'
7
+ };
8
+ class Bam extends EventEmitter {
9
+ #stopped;
10
+ #working;
11
+ #pollInterval;
12
+ #db;
13
+ #config;
14
+ events = events;
15
+ constructor(db, config) {
16
+ super();
17
+ this.#db = db;
18
+ this.#config = config;
19
+ this.#stopped = true;
20
+ this.#working = false;
21
+ }
22
+ async start() {
23
+ if (!this.#stopped)
24
+ return;
25
+ this.#stopped = false;
26
+ setImmediate(() => this.#onPoll());
27
+ this.#pollInterval = setInterval(() => this.#onPoll(), this.#config.bamIntervalSeconds * 1000);
28
+ }
29
+ async stop() {
30
+ if (this.#stopped)
31
+ return;
32
+ this.#stopped = true;
33
+ if (this.#pollInterval) {
34
+ clearInterval(this.#pollInterval);
35
+ this.#pollInterval = undefined;
36
+ }
37
+ }
38
+ async #onPoll() {
39
+ if (this.#stopped || this.#working || !this.#config.migrate)
40
+ return;
41
+ this.#working = true;
42
+ try {
43
+ if (this.#config.__test__throw_bam) {
44
+ throw new Error(this.#config.__test__throw_bam);
45
+ }
46
+ const sql = plans.trySetBamTime(this.#config.schema, this.#config.bamIntervalSeconds);
47
+ const { rows } = await this.#db.executeSql(sql);
48
+ if (rows.length === 1) {
49
+ await this.#processCommands();
50
+ }
51
+ }
52
+ catch (err) {
53
+ this.emit(events.error, err);
54
+ }
55
+ finally {
56
+ this.#working = false;
57
+ }
58
+ }
59
+ async #processCommands() {
60
+ if (this.#stopped)
61
+ return;
62
+ const entry = await this.#getNextCommand();
63
+ if (!entry || this.#stopped)
64
+ return;
65
+ this.emit(events.bam, {
66
+ id: entry.id,
67
+ name: entry.name,
68
+ status: 'in_progress',
69
+ queue: entry.queue,
70
+ table: entry.table
71
+ });
72
+ try {
73
+ await this.#db.executeSql(entry.command);
74
+ if (this.#stopped)
75
+ return;
76
+ await this.#markCompleted(entry.id);
77
+ this.emit(events.bam, {
78
+ id: entry.id,
79
+ name: entry.name,
80
+ status: 'completed',
81
+ queue: entry.queue,
82
+ table: entry.table
83
+ });
84
+ }
85
+ catch (err) {
86
+ if (this.#stopped)
87
+ return;
88
+ await this.#markFailed(entry.id, err);
89
+ this.emit(events.error, err);
90
+ this.emit(events.bam, {
91
+ id: entry.id,
92
+ name: entry.name,
93
+ status: 'failed',
94
+ queue: entry.queue,
95
+ table: entry.table,
96
+ error: String(err)
97
+ });
98
+ }
99
+ }
100
+ async #getNextCommand() {
101
+ const sql = plans.getNextBamCommand(this.#config.schema);
102
+ const { rows } = await this.#db.executeSql(sql);
103
+ return rows[0] || null;
104
+ }
105
+ async #markCompleted(id) {
106
+ const sql = plans.setBamCompleted(this.#config.schema, id);
107
+ await this.#db.executeSql(sql);
108
+ }
109
+ async #markFailed(id, error) {
110
+ const sql = plans.setBamFailed(this.#config.schema, id, String(error));
111
+ await this.#db.executeSql(sql);
112
+ }
113
+ }
114
+ export default Bam;
@@ -1 +1 @@
1
- {"version":3,"file":"boss.d.ts","sourceRoot":"","sources":["../src/boss.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,aAAa,CAAA;AACtC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAGvC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAYnC,cAAM,IAAK,SAAQ,YAAa,YAAW,KAAK,CAAC,WAAW;;IAQ1D,MAAM;;;MAAS;gBAGb,EAAE,EAAE,KAAK,CAAC,SAAS,EACnB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,KAAK,CAAC,0BAA0B;IAkBpC,KAAK;IAUL,IAAI;IAiEJ,SAAS,CAAE,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,WAAW,EAAE;CAwEtD;AAED,eAAe,IAAI,CAAA"}
1
+ {"version":3,"file":"boss.d.ts","sourceRoot":"","sources":["../src/boss.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,aAAa,CAAA;AACtC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAGvC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAYnC,cAAM,IAAK,SAAQ,YAAa,YAAW,KAAK,CAAC,WAAW;;IAS1D,MAAM;;;MAAS;gBAGb,EAAE,EAAE,KAAK,CAAC,SAAS,EACnB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,KAAK,CAAC,0BAA0B;IAmBpC,KAAK;IAWL,IAAI;IAkEJ,SAAS,CAAE,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,WAAW,EAAE;CAuFtD;AAED,eAAe,IAAI,CAAA"}
package/dist/boss.js CHANGED
@@ -12,6 +12,7 @@ const WARNINGS = {
12
12
  };
13
13
  class Boss extends EventEmitter {
14
14
  #stopped;
15
+ #stopping;
15
16
  #maintaining;
16
17
  #superviseInterval;
17
18
  #db;
@@ -24,6 +25,7 @@ class Boss extends EventEmitter {
24
25
  this.#config = config;
25
26
  this.#manager = manager;
26
27
  this.#stopped = true;
28
+ this.#stopping = false;
27
29
  if (config.warningSlowQuerySeconds) {
28
30
  WARNINGS.SLOW_QUERY.seconds = config.warningSlowQuerySeconds;
29
31
  }
@@ -33,12 +35,14 @@ class Boss extends EventEmitter {
33
35
  }
34
36
  async start() {
35
37
  if (this.#stopped) {
38
+ this.#stopping = false;
36
39
  this.#superviseInterval = setInterval(() => this.#onSupervise(), this.#config.superviseIntervalSeconds * 1000);
37
40
  this.#stopped = false;
38
41
  }
39
42
  }
40
43
  async stop() {
41
44
  if (!this.#stopped) {
45
+ this.#stopping = true;
42
46
  if (this.#superviseInterval)
43
47
  clearInterval(this.#superviseInterval);
44
48
  this.#stopped = true;
@@ -105,9 +109,13 @@ class Boss extends EventEmitter {
105
109
  return acc;
106
110
  }, {});
107
111
  for (const queueGroup of Object.values(queueGroups)) {
112
+ if (this.#stopping)
113
+ return;
108
114
  const { table, queues } = queueGroup;
109
115
  const names = queues.map((i) => i.name);
110
116
  while (names.length) {
117
+ if (this.#stopping)
118
+ return;
111
119
  const chunk = names.splice(0, 100);
112
120
  await this.#monitor(table, chunk);
113
121
  await this.#maintain(table, chunk);
@@ -115,12 +123,18 @@ class Boss extends EventEmitter {
115
123
  }
116
124
  }
117
125
  async #monitor(table, names) {
126
+ if (this.#stopping)
127
+ return;
118
128
  const command = plans.trySetQueueMonitorTime(this.#config.schema, names, this.#config.monitorIntervalSeconds);
119
129
  const { rows } = await this.#executeQuery(command);
130
+ if (this.#stopping)
131
+ return;
120
132
  if (rows.length) {
121
133
  const queues = rows.map((q) => q.name);
122
134
  const cacheStatsSql = plans.cacheQueueStats(this.#config.schema, table, queues);
123
135
  const { rows: rowsCacheStats } = await this.#executeSql(cacheStatsSql);
136
+ if (this.#stopping)
137
+ return;
124
138
  const warnings = rowsCacheStats.filter(i => i.queuedCount > (i.warningQueueSize || WARNINGS.LARGE_QUEUE.size));
125
139
  for (const warning of warnings) {
126
140
  this.emit(events.warning, {
@@ -133,8 +147,12 @@ class Boss extends EventEmitter {
133
147
  }
134
148
  }
135
149
  async #maintain(table, names) {
150
+ if (this.#stopping)
151
+ return;
136
152
  const command = plans.trySetQueueDeletionTime(this.#config.schema, names, this.#config.maintenanceIntervalSeconds);
137
153
  const { rows } = await this.#executeQuery(command);
154
+ if (this.#stopping)
155
+ return;
138
156
  if (rows.length) {
139
157
  const queues = rows.map((q) => q.name);
140
158
  const sql = plans.deletion(this.#config.schema, table, queues);
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}