amqplib 1.0.5 → 1.0.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Change log for amqplib
2
2
 
3
+ ## v1.0.6
4
+ - Fix channel.get() not invoking callback with error on channel close; previously only an error event was emitted (fixes #832). **Note:** if you use the callback API, ensure your channel.get() callbacks handle errors — they will now be invoked in error cases where previously they were not. If you use the promise API, the returned promise now rejects with a proper Error object (with .code, .classId and .methodId properties) rather than a raw close frame.
5
+
3
6
  ## v1.0.5
4
7
  - Fix ConfirmChannel callbacks silently dropped on channel close when some publishes had no callback (fixes #191)
5
8
 
package/Makefile CHANGED
@@ -2,7 +2,7 @@ RABBITMQ_SRC_VERSION=v3.12.13
2
2
  JSON=amqp-rabbitmq-0.9.1.json
3
3
  AMQP_JSON=https://raw.githubusercontent.com/rabbitmq/rabbitmq-server/$(RABBITMQ_SRC_VERSION)/deps/rabbitmq_codegen/$(JSON)
4
4
 
5
- NODEJS_VERSIONS='18.13.0' '20.10.0' '22.14.0' '24.7.0'
5
+ NODEJS_VERSIONS='18.20.0' '20.10.0' '22.14.0' '24.7.0'
6
6
 
7
7
  UGLIFY=./node_modules/.bin/uglifyjs
8
8
 
@@ -1,8 +1,7 @@
1
1
  const defs = require('./defs');
2
2
  const EventEmitter = require('node:events');
3
- const BaseChannel = require('./channel').BaseChannel;
4
- const acceptMessage = require('./channel').acceptMessage;
5
3
  const Args = require('./api_args');
4
+ const { BaseChannel, acceptMessage, convertCloseFrameToError } = require('./channel');
6
5
 
7
6
  class CallbackModel extends EventEmitter {
8
7
  constructor(connection) {
@@ -179,17 +178,17 @@ class Channel extends BaseChannel {
179
178
  const fields = Args.get(queue, options);
180
179
  const cb = callbackWrapper(this, cb0);
181
180
  this.sendOrEnqueue(defs.BasicGet, fields, (err, f) => {
182
- if (err === null) {
183
- if (f.id === defs.BasicGetEmpty) {
184
- cb(null, false);
185
- } else if (f.id === defs.BasicGetOk) {
186
- this.handleMessage = acceptMessage((m) => {
187
- m.fields = f.fields;
188
- cb(null, m);
189
- });
190
- } else {
191
- cb(new Error(`Unexpected response to BasicGet: ${inspect(f)}`));
192
- }
181
+ if (err instanceof Error) return cb(err);
182
+ if (err) return cb(convertCloseFrameToError(defs.BasicGet, err));
183
+ if (f.id === defs.BasicGetEmpty) {
184
+ cb(null, false);
185
+ } else if (f.id === defs.BasicGetOk) {
186
+ this.handleMessage = acceptMessage((m) => {
187
+ m.fields = f.fields;
188
+ cb(null, m);
189
+ });
190
+ } else {
191
+ cb(new Error(`Unexpected response to BasicGet: ${inspect(f)}`));
193
192
  }
194
193
  });
195
194
  return this;
package/lib/channel.js CHANGED
@@ -116,19 +116,7 @@ class Channel extends EventEmitter {
116
116
  else if (err instanceof Error) return cb(err);
117
117
  // A close frame will be given if this is the RPC awaiting reply
118
118
  // and the channel is closed by the server
119
- else {
120
- // otherwise, it's a close frame
121
- const closeReason = (err.fields.classId << 16) + err.fields.methodId;
122
- const e =
123
- method === closeReason
124
- ? fmt('Operation failed: %s; %s', methodName(method), closeMsg(err))
125
- : fmt('Channel closed by server: %s', closeMsg(err));
126
- const closeFrameError = new Error(e);
127
- closeFrameError.code = err.fields.replyCode;
128
- closeFrameError.classId = err.fields.classId;
129
- closeFrameError.methodId = err.fields.methodId;
130
- return cb(closeFrameError);
131
- }
119
+ else return cb(convertCloseFrameToError(method, err));
132
120
  }
133
121
 
134
122
  this.sendOrEnqueue(method, fields, reply);
@@ -428,6 +416,19 @@ function acceptMessage(continuation) {
428
416
  }
429
417
  }
430
418
 
419
+ function convertCloseFrameToError(method, closeFrame) {
420
+ const closeReason = (closeFrame.fields.classId << 16) + closeFrame.fields.methodId;
421
+ const e =
422
+ method === closeReason
423
+ ? fmt('Operation failed: %s; %s', methodName(method), closeMsg(closeFrame))
424
+ : fmt('Channel closed by server: %s', closeMsg(closeFrame));
425
+ const error = new Error(e);
426
+ error.code = closeFrame.fields.replyCode;
427
+ error.classId = closeFrame.fields.classId;
428
+ error.methodId = closeFrame.fields.methodId;
429
+ return error;
430
+ }
431
+
431
432
  // This adds just a bit more stuff useful for the APIs, but not
432
433
  // low-level machinery.
433
434
  class BaseChannel extends Channel {
@@ -471,3 +472,4 @@ class BaseChannel extends Channel {
471
472
  module.exports.acceptMessage = acceptMessage;
472
473
  module.exports.BaseChannel = BaseChannel;
473
474
  module.exports.Channel = Channel;
475
+ module.exports.convertCloseFrameToError = convertCloseFrameToError;
@@ -1,9 +1,8 @@
1
1
  const EventEmitter = require('node:events');
2
- const promisify = require('node:util').promisify;
2
+ const { promisify } = require('node:util');
3
3
  const defs = require('./defs');
4
- const { BaseChannel } = require('./channel');
5
- const { acceptMessage } = require('./channel');
6
4
  const Args = require('./api_args');
5
+ const { BaseChannel, acceptMessage, convertCloseFrameToError } = require('./channel');
7
6
  const { inspect } = require('./format');
8
7
 
9
8
  class ChannelModel extends EventEmitter {
@@ -159,7 +158,8 @@ class Channel extends BaseChannel {
159
158
  const fields = Args.get(queue, options);
160
159
  return new Promise((resolve, reject) => {
161
160
  this.sendOrEnqueue(defs.BasicGet, fields, (err, f) => {
162
- if (err) return reject(err);
161
+ if (err instanceof Error) return reject(err);
162
+ if (err) return reject(convertCloseFrameToError(defs.BasicGet, err));
163
163
  if (f.id === defs.BasicGetEmpty) {
164
164
  return resolve(false);
165
165
  } else if (f.id === defs.BasicGetOk) {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "amqplib",
3
3
  "homepage": "http://amqp-node.github.io/amqplib/",
4
4
  "main": "./channel_api.js",
5
- "version": "1.0.5",
5
+ "version": "1.0.6",
6
6
  "description": "An AMQP 0-9-1 (e.g., RabbitMQ) library and client.",
7
7
  "repository": {
8
8
  "type": "git",
@@ -401,39 +401,18 @@ describe('Callback API', () => {
401
401
  });
402
402
  });
403
403
 
404
- /*
405
- When this test was refactored as part of the move to the node test framework
406
- it failed because only the domain error handler was ever invoked and not the
407
- channel.get error callback.
408
-
409
- The original test passed because twice.first (refactored to use util.latch) short circuits on error rather
410
- than waiting for twice.second to be invoked. I have verified that the pre-refactored
411
- amqplib does not invoke the channel.get callback when the queue does not exist.
412
- See lib/callback_model.js
413
- */
414
404
  error_test('Get from non-queue invokes error', (ch, done, dom) => {
415
405
  const decrementLatch = latch(2, () => done());
416
406
  dom.on('error', (err) => {
417
407
  assert.match(err.message, /404 \(NOT-FOUND\)/);
418
- decrementLatch(err);
408
+ decrementLatch();
419
409
  });
420
410
  ch.get('', {}, (err) => {
421
411
  assert.match(err.message, /404 \(NOT-FOUND\)/)
422
- decrementLatch(err)
412
+ decrementLatch()
423
413
  });
424
414
  });
425
415
 
426
- /*
427
- When this test was refactored as part of the move to the node test framework
428
- it failed because only the domain error handler was ever invoked and not the
429
- channel.consume error callback. Unlike the above channel.get test,
430
- channel.consume does invoke the channel.consume callback with an error, but
431
- the original test did not supply one, mistakenly providing a message handler
432
- function instead.
433
-
434
- The original test passed because twice.first (refactored to use util.latch) short circuits on error
435
- rather than waiting for twice.second to be invoked.
436
- */
437
416
  error_test('Consume from non-queue invokes error', (ch, done, dom) => {
438
417
  const decrementLatch = latch(2, done);
439
418
  dom.on('error', (err) => {
@@ -176,6 +176,48 @@ describe('sendMessage', () => {
176
176
  });
177
177
  });
178
178
 
179
+ describe('get', () => {
180
+ it('returns false when queue is empty', () => {
181
+ return withChannel((ch) =>
182
+ ch.assertQueue('test.get-empty', QUEUE_OPTS)
183
+ .then(() => ch.purgeQueue('test.get-empty'))
184
+ .then(() => ch.get('test.get-empty', { noAck: true }))
185
+ .then((m) => assert.strictEqual(false, m))
186
+ );
187
+ });
188
+
189
+ it('returns a message when queue has messages', () => {
190
+ const msg = randomString();
191
+ return withChannel((ch) =>
192
+ ch.assertQueue('test.get-message', QUEUE_OPTS)
193
+ .then(() => ch.purgeQueue('test.get-message'))
194
+ .then(() => {
195
+ ch.sendToQueue('test.get-message', Buffer.from(msg));
196
+ return waitForMessages('test.get-message');
197
+ })
198
+ .then(() => ch.get('test.get-message', { noAck: true }))
199
+ .then((m) => {
200
+ assert(m);
201
+ assert.equal(msg, m.content.toString());
202
+ })
203
+ );
204
+ });
205
+
206
+ it('rejects with a proper error when queue does not exist', () => {
207
+ return withChannel((ch) => {
208
+ ch.once('error', ignore);
209
+ return assert.rejects(() => ch.get('', {}), (err) => {
210
+ assert(err instanceof Error);
211
+ assert.match(err.message, /NOT_FOUND/);
212
+ assert.strictEqual(404, err.code);
213
+ assert.strictEqual(60, err.classId);
214
+ assert.strictEqual(70, err.methodId);
215
+ return true;
216
+ });
217
+ });
218
+ });
219
+ });
220
+
179
221
  describe('binding, consuming', () => {
180
222
  // bind, publish, get
181
223
  it('route message', () => {