sqs-consumer 8.1.5 → 9.0.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.
@@ -17,8 +17,6 @@ jobs:
17
17
  cache: 'npm'
18
18
  registry-url: 'https://registry.npmjs.org'
19
19
  - run: npm ci
20
- - name: Verify the integrity of provenance attestations and registry signatures for installed dependencies
21
- run: npm audit signatures
22
20
  - name: Release
23
21
  env:
24
22
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
package/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ ## [9.0.0](https://github.com/bbc/sqs-consumer/compare/v8.2.0...v9.0.0) (2024-03-11)
2
+
3
+
4
+ ### ⚠ BREAKING CHANGES
5
+
6
+ * removing handler_processing debugger in favour of status (#470)
7
+ * replacing the isRunning method with status (#459)
8
+
9
+ ### Features
10
+
11
+ * removing handler_processing debugger in favour of status ([#470](https://github.com/bbc/sqs-consumer/issues/470)) ([b554da6](https://github.com/bbc/sqs-consumer/commit/b554da6e700bc0e69b77032352f250f19dba081b))
12
+ * replacing the isRunning method with status ([#459](https://github.com/bbc/sqs-consumer/issues/459)) ([9f07383](https://github.com/bbc/sqs-consumer/commit/9f07383b0244fa0ce05238c45f27238cd3459ebf))
13
+
14
+
15
+ ### Chores
16
+
17
+ * adding a link to the documentation ([#471](https://github.com/bbc/sqs-consumer/issues/471)) ([bd7fbbb](https://github.com/bbc/sqs-consumer/commit/bd7fbbb78a7e1d10cb7eae1b7d8f595c48b3438e))
18
+ * **deps:** upgrading dependencies - March 24 ([#468](https://github.com/bbc/sqs-consumer/issues/468)) ([2d342cd](https://github.com/bbc/sqs-consumer/commit/2d342cd63365bb42c095e2d4f56a7e2433f43f25))
19
+ * handle breaking changes ([2924ca2](https://github.com/bbc/sqs-consumer/commit/2924ca24e9fda687bf47a5daf45d17618561dae1))
20
+ * **release:** v9.0.0 ([bc5593c](https://github.com/bbc/sqs-consumer/commit/bc5593c4f9940e30c05e6463a1e7339bf14f1e50))
21
+ * upgrading @semantic-release/npm ([bed4dc6](https://github.com/bbc/sqs-consumer/commit/bed4dc63e42344325de0be398952c2b99994e631))
package/README.md CHANGED
@@ -26,6 +26,10 @@ npm install sqs-consumer
26
26
 
27
27
  We will only support Node versions that are actively or security supported by the Node team. If you are still using an Node 14, please use a version of this library before the v7 release, if you are using Node 16, please use a version before the v7.3.0 release.
28
28
 
29
+ ## Documentation
30
+
31
+ Visit [https://bbc.github.io/sqs-consumer/](https://bbc.github.io/sqs-consumer/) for the full API documentation.
32
+
29
33
  ## Usage
30
34
 
31
35
  ```js
@@ -125,9 +129,15 @@ By default, the value of `abort` is set to `false` which means pre existing requ
125
129
 
126
130
  `consumer.stop({ abort: true })`
127
131
 
128
- ### `consumer.isRunning`
132
+ ### `consumer.status`
133
+
134
+ Returns the current status of the consumer.
135
+
136
+ - `isRunning` - `true` if the consumer has been started and not stopped, `false` if was not started or if it was stopped.
137
+ - `isPolling` - `true` if the consumer is actively polling, `false` if it is not.
129
138
 
130
- Returns the current polling state of the consumer: `true` if it is actively polling, `false` if it is not.
139
+ > **Note:**
140
+ > This method is not available in versions before v9.0.0 and replaced the method `isRunning` to supply both running and polling states.
131
141
 
132
142
  ### `consumer.updateOption(option, value)`
133
143
 
@@ -23,7 +23,10 @@ export declare class Consumer extends TypedEventEmitter {
23
23
  private waitTimeSeconds;
24
24
  private authenticationErrorTimeout;
25
25
  private pollingWaitTimeMs;
26
+ private pollingCompleteWaitTimeMs;
26
27
  private heartbeatInterval;
28
+ private isPolling;
29
+ private stopRequestedAtTimestamp;
27
30
  abortController: AbortController;
28
31
  constructor(options: ConsumerOptions);
29
32
  /**
@@ -43,9 +46,18 @@ export declare class Consumer extends TypedEventEmitter {
43
46
  */
44
47
  stop(options?: StopOptions): void;
45
48
  /**
46
- * Returns the current polling state of the consumer: `true` if it is actively polling, `false` if it is not.
49
+ * Wait for final poll and in flight messages to complete.
50
+ * @private
47
51
  */
48
- get isRunning(): boolean;
52
+ private waitForPollingToComplete;
53
+ /**
54
+ * Returns the current status of the consumer.
55
+ * This includes whether it is running or currently polling.
56
+ */
57
+ get status(): {
58
+ isRunning: boolean;
59
+ isPolling: boolean;
60
+ };
49
61
  /**
50
62
  * Validates and then updates the provided option to the provided value.
51
63
  * @param option The option to validate and then update
package/dist/consumer.js CHANGED
@@ -12,10 +12,11 @@ const logger_1 = require("./logger");
12
12
  */
13
13
  class Consumer extends emitter_1.TypedEventEmitter {
14
14
  constructor(options) {
15
- var _a, _b, _c, _d, _e;
15
+ var _a, _b, _c, _d, _e, _f;
16
16
  super();
17
17
  this.pollingTimeoutId = undefined;
18
18
  this.stopped = true;
19
+ this.isPolling = false;
19
20
  (0, validation_1.assertOptions)(options);
20
21
  this.queueUrl = options.queueUrl;
21
22
  this.handleMessage = options.handleMessage;
@@ -34,8 +35,9 @@ class Consumer extends emitter_1.TypedEventEmitter {
34
35
  this.authenticationErrorTimeout =
35
36
  (_b = options.authenticationErrorTimeout) !== null && _b !== void 0 ? _b : 10000;
36
37
  this.pollingWaitTimeMs = (_c = options.pollingWaitTimeMs) !== null && _c !== void 0 ? _c : 0;
37
- this.shouldDeleteMessages = (_d = options.shouldDeleteMessages) !== null && _d !== void 0 ? _d : true;
38
- this.alwaysAcknowledge = (_e = options.alwaysAcknowledge) !== null && _e !== void 0 ? _e : false;
38
+ this.pollingCompleteWaitTimeMs = (_d = options.pollingCompleteWaitTimeMs) !== null && _d !== void 0 ? _d : 0;
39
+ this.shouldDeleteMessages = (_e = options.shouldDeleteMessages) !== null && _e !== void 0 ? _e : true;
40
+ this.alwaysAcknowledge = (_f = options.alwaysAcknowledge) !== null && _f !== void 0 ? _f : false;
39
41
  this.sqs =
40
42
  options.sqs ||
41
43
  new client_sqs_1.SQSClient({
@@ -92,13 +94,37 @@ class Consumer extends emitter_1.TypedEventEmitter {
92
94
  this.abortController.abort();
93
95
  this.emit('aborted');
94
96
  }
95
- this.emit('stopped');
97
+ this.stopRequestedAtTimestamp = Date.now();
98
+ this.waitForPollingToComplete();
96
99
  }
97
100
  /**
98
- * Returns the current polling state of the consumer: `true` if it is actively polling, `false` if it is not.
101
+ * Wait for final poll and in flight messages to complete.
102
+ * @private
99
103
  */
100
- get isRunning() {
101
- return !this.stopped;
104
+ waitForPollingToComplete() {
105
+ if (!this.isPolling || !(this.pollingCompleteWaitTimeMs > 0)) {
106
+ this.emit('stopped');
107
+ return;
108
+ }
109
+ const exceededTimeout = Date.now() - this.stopRequestedAtTimestamp >
110
+ this.pollingCompleteWaitTimeMs;
111
+ if (exceededTimeout) {
112
+ this.emit('waiting_for_polling_to_complete_timeout_exceeded');
113
+ this.emit('stopped');
114
+ return;
115
+ }
116
+ this.emit('waiting_for_polling_to_complete');
117
+ setTimeout(this.waitForPollingToComplete, 1000);
118
+ }
119
+ /**
120
+ * Returns the current status of the consumer.
121
+ * This includes whether it is running or currently polling.
122
+ */
123
+ get status() {
124
+ return {
125
+ isRunning: !this.stopped,
126
+ isPolling: this.isPolling
127
+ };
102
128
  }
103
129
  /**
104
130
  * Validates and then updates the provided option to the provided value.
@@ -140,6 +166,7 @@ class Consumer extends emitter_1.TypedEventEmitter {
140
166
  return;
141
167
  }
142
168
  logger_1.logger.debug('polling');
169
+ this.isPolling = true;
143
170
  let currentPollingTimeout = this.pollingWaitTimeMs;
144
171
  this.receiveMessage({
145
172
  QueueUrl: this.queueUrl,
@@ -168,6 +195,9 @@ class Consumer extends emitter_1.TypedEventEmitter {
168
195
  })
169
196
  .catch((err) => {
170
197
  this.emitError(err);
198
+ })
199
+ .finally(() => {
200
+ this.isPolling = false;
171
201
  });
172
202
  }
173
203
  /**
@@ -196,18 +226,12 @@ class Consumer extends emitter_1.TypedEventEmitter {
196
226
  */
197
227
  async handleSqsResponse(response) {
198
228
  if ((0, validation_1.hasMessages)(response)) {
199
- const handlerProcessingDebugger = setInterval(() => {
200
- logger_1.logger.debug('handler_processing', {
201
- detail: 'The handler is still processing the message(s)...'
202
- });
203
- }, 1000);
204
229
  if (this.handleMessageBatch) {
205
230
  await this.processMessageBatch(response.Messages);
206
231
  }
207
232
  else {
208
233
  await Promise.all(response.Messages.map(this.processMessage));
209
234
  }
210
- clearInterval(handlerProcessingDebugger);
211
235
  this.emit('response_processed');
212
236
  }
213
237
  else if (response) {
package/dist/types.d.ts CHANGED
@@ -45,6 +45,12 @@ export interface ConsumerOptions {
45
45
  * @defaultvalue `0`
46
46
  */
47
47
  pollingWaitTimeMs?: number;
48
+ /**
49
+ * If you want the stop action to wait for the final poll to complete and in-flight messages
50
+ * to be processed before emitting 'stopped' set this to the max amount of time to wait.
51
+ * @defaultvalue `0`
52
+ */
53
+ pollingCompleteWaitTimeMs?: number;
48
54
  /**
49
55
  * If true, sets the message visibility timeout to 0 after a `processing_error`.
50
56
  * @defaultvalue `false`
@@ -183,6 +189,14 @@ export interface Events {
183
189
  * Fired when an option is updated
184
190
  */
185
191
  option_updated: [UpdatableOptions, ConsumerOptions[UpdatableOptions]];
192
+ /**
193
+ * Fired when the Consumer is waiting for polling to complete before stopping.
194
+ */
195
+ waiting_for_polling_to_complete: [];
196
+ /**
197
+ * Fired when the Consumer has waited for polling to complete and is stopping due to a timeout.
198
+ */
199
+ waiting_for_polling_to_complete_timeout_exceeded: [];
186
200
  }
187
201
  export type AWSError = {
188
202
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sqs-consumer",
3
- "version": "8.1.5",
3
+ "version": "9.0.0",
4
4
  "description": "Build SQS-based Node applications without the boilerplate",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -21,7 +21,7 @@
21
21
  "lcov": "c8 mocha && c8 report --reporter=lcov",
22
22
  "lint": "eslint . --ext .ts",
23
23
  "lint:fix": "eslint . --fix",
24
- "format": "prettier --loglevel warn --write \"**/*.{js,json,jsx,md,ts,tsx,html}\"",
24
+ "format": "prettier --log-level warn --write \"**/*.{js,json,jsx,md,ts,tsx,html}\"",
25
25
  "format:check": "prettier --check \"**/*.{js,json,jsx,md,ts,tsx,html}\"",
26
26
  "posttest": "npm run lint && npm run format:check",
27
27
  "generate-docs": "typedoc"
@@ -57,6 +57,10 @@
57
57
  {
58
58
  "preset": "conventionalcommits",
59
59
  "releaseRules": [
60
+ {
61
+ "type": "breaking",
62
+ "release": "major"
63
+ },
60
64
  {
61
65
  "type": "feat",
62
66
  "release": "minor"
@@ -118,44 +122,45 @@
118
122
  }
119
123
  }
120
124
  ],
121
- "@semantic-release/git",
125
+ "@semantic-release/changelog",
122
126
  "@semantic-release/github",
123
127
  "@semantic-release/npm"
124
128
  ]
125
129
  },
126
130
  "devDependencies": {
127
- "@cucumber/cucumber": "10.0.1",
131
+ "@cucumber/cucumber": "10.3.1",
132
+ "@semantic-release/changelog": "^6.0.3",
128
133
  "@semantic-release/commit-analyzer": "^11.1.0",
129
134
  "@semantic-release/git": "^10.0.1",
130
135
  "@semantic-release/github": "^9.2.6",
131
- "@semantic-release/npm": "^11.0.2",
136
+ "@semantic-release/npm": "11.0.3",
132
137
  "@semantic-release/release-notes-generator": "^12.1.0",
133
- "@types/chai": "^4.3.9",
134
- "@types/mocha": "^10.0.3",
135
- "@types/node": "^20.8.7",
136
- "@types/sinon": "^10.0.20",
137
- "c8": "^8.0.1",
138
+ "@types/chai": "^4.3.12",
139
+ "@types/mocha": "^10.0.6",
140
+ "@types/node": "^20.11.25",
141
+ "@types/sinon": "^17.0.3",
142
+ "c8": "^9.1.0",
138
143
  "chai": "^4.3.10",
139
144
  "conventional-changelog-conventionalcommits": "^7.0.2",
140
- "eslint": "^8.52.0",
141
- "eslint-config-iplayer": "^9.1.0",
142
- "eslint-config-prettier": "^9.0.0",
143
- "mocha": "^10.2.0",
145
+ "eslint": "^8.57.0",
146
+ "eslint-config-iplayer": "^9.2.0",
147
+ "eslint-config-prettier": "^9.1.0",
148
+ "mocha": "^10.3.0",
144
149
  "p-event": "^4.2.0",
145
- "prettier": "^3.0.3",
146
- "semantic-release": "^23.0.0",
147
- "sinon": "^17.0.0",
148
- "sqs-producer": "^4.0.0",
149
- "ts-node": "^10.9.1",
150
- "typedoc": "^0.25.2",
151
- "typescript": "^5.2.2"
150
+ "prettier": "^3.2.5",
151
+ "semantic-release": "^23.0.2",
152
+ "sinon": "^17.0.1",
153
+ "sqs-producer": "^5.0.0",
154
+ "ts-node": "^10.9.2",
155
+ "typedoc": "^0.25.12",
156
+ "typescript": "^5.4.2"
152
157
  },
153
158
  "dependencies": {
154
- "@aws-sdk/client-sqs": "^3.447.0",
159
+ "@aws-sdk/client-sqs": "^3.529.1",
155
160
  "debug": "^4.3.4"
156
161
  },
157
162
  "peerDependencies": {
158
- "@aws-sdk/client-sqs": "^3.447.0"
163
+ "@aws-sdk/client-sqs": "^3.529.1"
159
164
  },
160
165
  "mocha": {
161
166
  "spec": "test/tests/**/**/*.test.ts",
package/src/consumer.ts CHANGED
@@ -54,7 +54,10 @@ export class Consumer extends TypedEventEmitter {
54
54
  private waitTimeSeconds: number;
55
55
  private authenticationErrorTimeout: number;
56
56
  private pollingWaitTimeMs: number;
57
+ private pollingCompleteWaitTimeMs: number;
57
58
  private heartbeatInterval: number;
59
+ private isPolling = false;
60
+ private stopRequestedAtTimestamp: number;
58
61
  public abortController: AbortController;
59
62
 
60
63
  constructor(options: ConsumerOptions) {
@@ -77,6 +80,7 @@ export class Consumer extends TypedEventEmitter {
77
80
  this.authenticationErrorTimeout =
78
81
  options.authenticationErrorTimeout ?? 10000;
79
82
  this.pollingWaitTimeMs = options.pollingWaitTimeMs ?? 0;
83
+ this.pollingCompleteWaitTimeMs = options.pollingCompleteWaitTimeMs ?? 0;
80
84
  this.shouldDeleteMessages = options.shouldDeleteMessages ?? true;
81
85
  this.alwaysAcknowledge = options.alwaysAcknowledge ?? false;
82
86
  this.sqs =
@@ -142,14 +146,45 @@ export class Consumer extends TypedEventEmitter {
142
146
  this.emit('aborted');
143
147
  }
144
148
 
145
- this.emit('stopped');
149
+ this.stopRequestedAtTimestamp = Date.now();
150
+ this.waitForPollingToComplete();
146
151
  }
147
152
 
148
153
  /**
149
- * Returns the current polling state of the consumer: `true` if it is actively polling, `false` if it is not.
154
+ * Wait for final poll and in flight messages to complete.
155
+ * @private
150
156
  */
151
- public get isRunning(): boolean {
152
- return !this.stopped;
157
+ private waitForPollingToComplete(): void {
158
+ if (!this.isPolling || !(this.pollingCompleteWaitTimeMs > 0)) {
159
+ this.emit('stopped');
160
+ return;
161
+ }
162
+
163
+ const exceededTimeout =
164
+ Date.now() - this.stopRequestedAtTimestamp >
165
+ this.pollingCompleteWaitTimeMs;
166
+ if (exceededTimeout) {
167
+ this.emit('waiting_for_polling_to_complete_timeout_exceeded');
168
+ this.emit('stopped');
169
+ return;
170
+ }
171
+
172
+ this.emit('waiting_for_polling_to_complete');
173
+ setTimeout(this.waitForPollingToComplete, 1000);
174
+ }
175
+
176
+ /**
177
+ * Returns the current status of the consumer.
178
+ * This includes whether it is running or currently polling.
179
+ */
180
+ public get status(): {
181
+ isRunning: boolean;
182
+ isPolling: boolean;
183
+ } {
184
+ return {
185
+ isRunning: !this.stopped,
186
+ isPolling: this.isPolling
187
+ };
153
188
  }
154
189
 
155
190
  /**
@@ -198,6 +233,8 @@ export class Consumer extends TypedEventEmitter {
198
233
 
199
234
  logger.debug('polling');
200
235
 
236
+ this.isPolling = true;
237
+
201
238
  let currentPollingTimeout = this.pollingWaitTimeMs;
202
239
  this.receiveMessage({
203
240
  QueueUrl: this.queueUrl,
@@ -227,6 +264,9 @@ export class Consumer extends TypedEventEmitter {
227
264
  })
228
265
  .catch((err) => {
229
266
  this.emitError(err);
267
+ })
268
+ .finally(() => {
269
+ this.isPolling = false;
230
270
  });
231
271
  }
232
272
 
@@ -264,20 +304,12 @@ export class Consumer extends TypedEventEmitter {
264
304
  response: ReceiveMessageCommandOutput
265
305
  ): Promise<void> {
266
306
  if (hasMessages(response)) {
267
- const handlerProcessingDebugger = setInterval(() => {
268
- logger.debug('handler_processing', {
269
- detail: 'The handler is still processing the message(s)...'
270
- });
271
- }, 1000);
272
-
273
307
  if (this.handleMessageBatch) {
274
308
  await this.processMessageBatch(response.Messages);
275
309
  } else {
276
310
  await Promise.all(response.Messages.map(this.processMessage));
277
311
  }
278
312
 
279
- clearInterval(handlerProcessingDebugger);
280
-
281
313
  this.emit('response_processed');
282
314
  } else if (response) {
283
315
  this.emit('empty');
package/src/types.ts CHANGED
@@ -46,6 +46,12 @@ export interface ConsumerOptions {
46
46
  * @defaultvalue `0`
47
47
  */
48
48
  pollingWaitTimeMs?: number;
49
+ /**
50
+ * If you want the stop action to wait for the final poll to complete and in-flight messages
51
+ * to be processed before emitting 'stopped' set this to the max amount of time to wait.
52
+ * @defaultvalue `0`
53
+ */
54
+ pollingCompleteWaitTimeMs?: number;
49
55
  /**
50
56
  * If true, sets the message visibility timeout to 0 after a `processing_error`.
51
57
  * @defaultvalue `false`
@@ -191,6 +197,14 @@ export interface Events {
191
197
  * Fired when an option is updated
192
198
  */
193
199
  option_updated: [UpdatableOptions, ConsumerOptions[UpdatableOptions]];
200
+ /**
201
+ * Fired when the Consumer is waiting for polling to complete before stopping.
202
+ */
203
+ waiting_for_polling_to_complete: [];
204
+ /**
205
+ * Fired when the Consumer has waited for polling to complete and is stopping due to a timeout.
206
+ */
207
+ waiting_for_polling_to_complete_timeout_exceeded: [];
194
208
  }
195
209
 
196
210
  export type AWSError = {