comq 0.22.0 → 0.24.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.
Files changed (42) hide show
  1. package/discussions/amqp-1.0.md +127 -0
  2. package/discussions/one-parking-queue.md +63 -0
  3. package/discussions/one-reply-queue.md +44 -0
  4. package/discussions/poison-message-handling.md +2 -1
  5. package/docs/headers.md +1 -1
  6. package/features/parking.feature +32 -0
  7. package/features/rpc.call.feature +23 -1
  8. package/features/rpc.call.recovery.feature +0 -21
  9. package/features/rpc.parked.feature +6 -6
  10. package/features/rpc.replies.feature +26 -0
  11. package/features/steps/brokers.js +25 -1
  12. package/features/steps/call.js +90 -30
  13. package/features/steps/events.js +1 -1
  14. package/features/steps/parked.js +7 -18
  15. package/features/steps/parking.js +113 -0
  16. package/features/steps/poison.js +17 -19
  17. package/features/steps/replies.js +35 -0
  18. package/package.json +6 -4
  19. package/readme.md +54 -75
  20. package/source/.io/ReplyPipe.js +16 -1
  21. package/source/channel.js +19 -27
  22. package/source/connection.js +1 -4
  23. package/source/index.js +0 -2
  24. package/source/io.js +48 -59
  25. package/test/ReplyPipe.test.js +40 -0
  26. package/test/amqplib.mock.js +1 -1
  27. package/test/channel.test.js +29 -18
  28. package/test/connection.test.js +1 -1
  29. package/test/{io.request.terms.test.js → io.call.terms.test.js} +31 -30
  30. package/test/io.call.test.js +0 -10
  31. package/test/io.request.test.js +26 -3
  32. package/test/memory.test.js +1 -1
  33. package/types/amqp.d.ts +1 -1
  34. package/types/channel.d.ts +1 -1
  35. package/types/index.d.ts +0 -6
  36. package/types/io.d.ts +9 -14
  37. package/features/closing.feature +0 -32
  38. package/features/rpc.timeout.feature +0 -19
  39. package/features/steps/closing.js +0 -109
  40. package/source/abandoned.js +0 -15
  41. package/source/batch.js +0 -62
  42. package/test/batch.test.js +0 -94
package/source/index.js CHANGED
@@ -4,7 +4,6 @@ const { connect, assert } = require('./connect')
4
4
  const { Retry, Park } = require('./verdicts')
5
5
  const { Unroutable } = require('./unroutable')
6
6
  const { Interrupted } = require('./interrupted')
7
- const { Abandoned } = require('./abandoned')
8
7
 
9
8
  exports.connect = connect
10
9
  exports.assert = assert
@@ -13,4 +12,3 @@ exports.Retry = Retry
13
12
  exports.Park = Park
14
13
  exports.Unroutable = Unroutable
15
14
  exports.Interrupted = Interrupted
16
- exports.Abandoned = Abandoned
package/source/io.js CHANGED
@@ -6,7 +6,6 @@ const { Promex } = require('promex')
6
6
  const { memo, failsafe, lazy, track } = require('./attributes')
7
7
  const { verdictOf, PARK } = require('./verdicts')
8
8
  const { Unroutable } = require('./unroutable')
9
- const { Abandoned } = require('./abandoned')
10
9
 
11
10
  const { decode } = require('./decode')
12
11
  const { encode } = require('./encode')
@@ -31,8 +30,15 @@ class IO {
31
30
  /** @type {comq.Channel} */
32
31
  #events
33
32
 
34
- /** @type {Map<string, comq.ReplyEmitter>} */
35
- #emitters = new Map()
33
+ /**
34
+ * Where this IO's replies arrive. One queue answers every request it makes: a reply is
35
+ * found by its correlation identifier, which is unique across processes, so a queue per
36
+ * target would name what the identifier already tells apart — and the broker would hold
37
+ * one for every distinct queue the caller has ever requested.
38
+ *
39
+ * @type {comq.ReplyEmitter}
40
+ */
41
+ #emitter
36
42
 
37
43
  /** @type {comq.ReplyEmitter | null} */
38
44
  #control = null
@@ -40,9 +46,6 @@ class IO {
40
46
  /** @type {Map<Promex, comq.Request>} */
41
47
  #pendingReplies = new Map()
42
48
 
43
- /** Whether this has stopped waiting for Replies, see `abandon`. */
44
- #abandoning = false
45
-
46
49
  /** @type {[comq.diagnostics.Event, Function][]} */
47
50
  #forwarders = []
48
51
 
@@ -81,16 +84,21 @@ class IO {
81
84
  })
82
85
 
83
86
  /**
87
+ * Waits for the Reply for as long as it takes: a Request is answered eventually, and one no
88
+ * Producer can answer is parked rather than abandoned.
89
+ *
84
90
  * @param {string} queue
85
91
  * @param {any | Readable} payload
86
- * @param {comq.Encoding | comq.RequestOptions} [options]
92
+ * @param {comq.Encoding} [encoding]
87
93
  * @returns {Promise<any | Readable>}
88
94
  */
89
- request (queue, payload, options) {
90
- const settled = terms(options)
95
+ async request (queue, payload, encoding) {
96
+ // a caller of the version that took a timeout here would otherwise wait on in silence
97
+ if (typeof encoding === 'object' && encoding !== null) {
98
+ throw new TypeError('A Request takes an encoding and waits for its Reply')
99
+ }
91
100
 
92
- // the channels and the queue a first Request declares are waited for within the terms too
93
- return abortable(this.#request(queue, payload, settled), settled.signal)
101
+ return await this.#request(queue, payload, { encoding })
94
102
  }
95
103
 
96
104
  /**
@@ -100,12 +108,13 @@ class IO {
100
108
  * @param {string} exchange
101
109
  * @param {string} key
102
110
  * @param {any} payload
103
- * @param {comq.Encoding | comq.RequestOptions} [options]
111
+ * @param {comq.Encoding | comq.CallOptions} [options]
104
112
  * @returns {Promise<any | Readable>}
105
113
  */
106
114
  call (exchange, key, payload, options) {
107
115
  const settled = terms(options)
108
116
 
117
+ // the channels a first call declares are waited for within the terms too
109
118
  return abortable(this.#call(exchange, key, payload, settled), settled.signal)
110
119
  }
111
120
 
@@ -126,8 +135,7 @@ class IO {
126
135
  await this.#requests.held(exchange, exchange + '.' + key, key, consumer)
127
136
  })
128
137
 
129
- // failsafe is aimed to retransmit unanswered messages; the terms are taken once, so a
130
- // re-sent Request keeps the deadline of the first
138
+ // failsafe is aimed to retransmit unanswered messages
131
139
  #request = lazy(this, [this.#createRequestReplyChannels, this.#consumeReplies],
132
140
  failsafe(this, this.#recover,
133
141
  /**
@@ -149,6 +157,7 @@ class IO {
149
157
  (buffer, properties) => this.#requests.send(queue, buffer, properties))
150
158
  }))
151
159
 
160
+ // the terms are taken once, so a re-sent call keeps the deadline of the first
152
161
  #call = lazy(this, [this.#createRequestReplyChannels, this.#consumeReplies],
153
162
  failsafe(this, this.#recover,
154
163
  /**
@@ -254,30 +263,6 @@ class IO {
254
263
  await this.#destroyStreams(this.#replyStreams)
255
264
  })
256
265
 
257
- /**
258
- * Stops waiting for Replies: every one still outstanding is rejected with `Abandoned`, and a
259
- * Request made from now on is refused with it.
260
- *
261
- * A close waits for every consumer callback to return, and such a callback may be waiting on
262
- * a Request of its own. Where the answer to that Request is the very thing going away — the
263
- * process it would come from is shutting down alongside this one — the close waits for as
264
- * long as the answer takes. This is how that wait is ended. What the Requests themselves do
265
- * is not affected: they were sent and may well be processed; what is given up is this side's
266
- * interest in the answers.
267
- *
268
- * Callable at any time, including while a close is already under way.
269
- */
270
- abandon = memo(async () => {
271
- this.#abandoning = true
272
-
273
- // the same three steps `#abandon` takes for one Reply whose caller stopped waiting
274
- for (const [reply, request] of this.#pendingReplies) {
275
- request.emitter.off(request.properties.correlationId)
276
- this.#pendingReplies.delete(reply)
277
- reply.reject(new Abandoned())
278
- }
279
- })
280
-
281
266
  close = memo(async () => {
282
267
  await this.seal()
283
268
  await this.#destroyStreams(this.#replyPipes)
@@ -340,13 +325,20 @@ class IO {
340
325
  return [buffer, properties]
341
326
  }
342
327
 
343
- async #consumeReplies (queue) {
344
- const emitter = io.createReplyEmitter(queue)
345
- const consumer = this.#getReplyConsumer(queue, emitter)
328
+ async #consumeReplies () {
329
+ this.#emitter = await this.#consumeRepliesOf(REPLY)
330
+ }
331
+
332
+ /**
333
+ * @param {string} label
334
+ * @returns {Promise<comq.ReplyEmitter>}
335
+ */
336
+ async #consumeRepliesOf (label) {
337
+ const emitter = io.createReplyEmitter(label)
346
338
 
347
- this.#emitters.set(queue, emitter)
339
+ await this.#replies.consume(emitter.queue, this.#getReplyConsumer(emitter))
348
340
 
349
- await this.#replies.consume(emitter.queue, consumer)
341
+ return emitter
350
342
  }
351
343
 
352
344
  // endregion
@@ -417,11 +409,10 @@ class IO {
417
409
  })
418
410
 
419
411
  /**
420
- * @param {string} queue
421
412
  * @param {comq.ReplyEmitter} emitter
422
413
  * @returns {comq.channels.Consumer}
423
414
  */
424
- #getReplyConsumer = (queue, emitter) =>
415
+ #getReplyConsumer = (emitter) =>
425
416
  (message) => {
426
417
  const payload = decode(message)
427
418
 
@@ -453,11 +444,8 @@ class IO {
453
444
 
454
445
  signal?.throwIfAborted()
455
446
 
456
- // one made now would be waited for by nobody: the Replies are abandoned already
457
- if (this.#abandoning) throw new Abandoned()
458
-
459
447
  const [buffer, contentType] = this.#encode(payload, terms.encoding)
460
- const request = this.#createRequest(target, contentType, signal)
448
+ const request = this.#createRequest(contentType, signal)
461
449
  const properties = { ...request.properties }
462
450
 
463
451
  if (expires !== undefined) {
@@ -524,13 +512,12 @@ class IO {
524
512
  * The request holds no copy of what was sent: a retransmission encodes the
525
513
  * payload anew, and an unanswered request would otherwise keep two of it.
526
514
  *
527
- * @param {string} queue
528
515
  * @param {comq.Encoding} contentType
529
516
  * @param {AbortSignal} [signal]
530
517
  * @return {comq.Request}
531
518
  */
532
- #createRequest (queue, contentType, signal) {
533
- const emitter = this.#emitters.get(queue)
519
+ #createRequest (contentType, signal) {
520
+ const emitter = this.#emitter
534
521
  const correlationId = emitter.next()
535
522
 
536
523
  /** @type {comq.amqp.Properties} */
@@ -611,11 +598,7 @@ class IO {
611
598
  * @return {Promise<comq.ReplyEmitter>}
612
599
  */
613
600
  async #createControl () {
614
- const queue = 'control'
615
-
616
- await this.#consumeReplies(queue)
617
-
618
- return this.#emitters.get(queue)
601
+ return await this.#consumeRepliesOf(CONTROL)
619
602
  }
620
603
 
621
604
  /**
@@ -724,11 +707,17 @@ const DEFAULT = 'application/json'
724
707
 
725
708
  const RETRANSMISSION = /** @type {Error} */ Symbol('retransmission')
726
709
 
710
+ /** What this IO's replies arrive on, before the random suffix that makes the queue its own. */
711
+ const REPLY = 'comq.reply'
712
+
713
+ /** What a producer steers its reply streams from, likewise. */
714
+ const CONTROL = 'control'
715
+
727
716
  /**
728
- * What a caller passed, settled once: a timeout becomes the moment the Request expires, so that
717
+ * What a caller passed, settled once: a timeout becomes the moment the call expires, so that
729
718
  * every attempt of it shares one deadline.
730
719
  *
731
- * @param {comq.Encoding | comq.RequestOptions} [options]
720
+ * @param {comq.Encoding | comq.CallOptions} [options]
732
721
  * @returns {comq.Terms}
733
722
  */
734
723
  function terms (options) {
@@ -168,3 +168,43 @@ it('should heartbeat while idle', async () => {
168
168
 
169
169
  pipe.destroy()
170
170
  })
171
+
172
+ // the confirmation is published before the source is read, and a blocked broker holds the publish
173
+ // for as long as it is blocked: the source may fail in the meantime, and nobody listens to it yet
174
+ it('should survive a source that fails before the confirmation is sent', async () => {
175
+ const gate = new Promex()
176
+
177
+ reply.mockImplementation(async (message, properties) => {
178
+ sent.push([message, properties])
179
+ await gate
180
+
181
+ return true
182
+ })
183
+
184
+ const source = new Readable({ objectMode: true, read () {} })
185
+ const creating = ReplyPipe.create(request, source, channel, feedback, reply)
186
+
187
+ source.destroy(new Error('nothing arrived'))
188
+
189
+ await timeout(10)
190
+
191
+ gate.resolve()
192
+
193
+ const pipe = await creating
194
+
195
+ await closing(pipe)
196
+
197
+ expect(source.destroyed).toStrictEqual(true)
198
+ })
199
+
200
+ it('should destroy the source if the confirmation cannot be sent', async () => {
201
+ reply.mockImplementation(async () => {
202
+ throw new Error('channel closed')
203
+ })
204
+
205
+ const source = new Readable({ objectMode: true, read () {} })
206
+
207
+ await expect(ReplyPipe.create(request, source, channel, feedback, reply)).rejects.toThrow('channel closed')
208
+
209
+ expect(source.destroyed).toStrictEqual(true)
210
+ })
@@ -58,7 +58,7 @@ class Connection extends EventEmitter {
58
58
 
59
59
  const connect = async (url) => new Connection(url)
60
60
 
61
- /** @type {jest.MockedObject<import('amqplib')>} */
61
+ /** @type {jest.MockedObject<import('@toa.io/amqplib')>} */
62
62
  const amqplib = {
63
63
  connect: jest.fn(connect)
64
64
  }
@@ -1040,6 +1040,7 @@ describe('release', () => {
1040
1040
  describe('failed messages', () => {
1041
1041
  const DELAY = 1000
1042
1042
  const RETRY = 'comq.retry.' + DELAY
1043
+ const PARKED = 'comq.parked'
1043
1044
 
1044
1045
  let queue
1045
1046
  let exception
@@ -1108,10 +1109,18 @@ describe('failed messages', () => {
1108
1109
  it('should assert the parked queue', async () => {
1109
1110
  await channel.consume(queue, consumer)
1110
1111
 
1111
- expect(chan.assertQueue).toHaveBeenCalledWith('comq.parked.' + queue,
1112
+ expect(chan.assertQueue).toHaveBeenCalledWith(PARKED,
1112
1113
  expect.objectContaining({ durable: true }))
1113
1114
  })
1114
1115
 
1116
+ it('should not name the parked queue after the queue it serves', async () => {
1117
+ await channel.consume(queue, consumer)
1118
+
1119
+ const named = chan.assertQueue.mock.calls.filter(([name]) => name.includes(queue))
1120
+
1121
+ expect(named).toHaveLength(1) // the source queue itself
1122
+ })
1123
+
1115
1124
  it('should assert the source queue first', async () => {
1116
1125
  await channel.consume(queue, consumer)
1117
1126
 
@@ -1130,15 +1139,14 @@ describe('failed messages', () => {
1130
1139
  expect(queues).toHaveLength(1)
1131
1140
  })
1132
1141
 
1133
- it('should assert a parked queue per consumed queue', async () => {
1134
- const one = generate()
1135
- const another = generate()
1142
+ it('should assert one parked queue however many queues are consumed', async () => {
1143
+ await channel.consume(generate(), consumer)
1144
+ await channel.consume(generate(), consumer)
1145
+ await channel.consume(generate(), consumer)
1136
1146
 
1137
- await channel.consume(one, consumer)
1138
- await channel.consume(another, consumer)
1147
+ const parked = chan.assertQueue.mock.calls.filter(([name]) => name === PARKED)
1139
1148
 
1140
- expect(chan.assertQueue).toHaveBeenCalledWith('comq.parked.' + one, expect.anything())
1141
- expect(chan.assertQueue).toHaveBeenCalledWith('comq.parked.' + another, expect.anything())
1149
+ expect(parked).toHaveLength(1)
1142
1150
  })
1143
1151
 
1144
1152
  it('should not assert either without acknowledgments', async () => {
@@ -1156,7 +1164,9 @@ describe('failed messages', () => {
1156
1164
  expect(chan.assertExchange).not.toHaveBeenCalled()
1157
1165
  })
1158
1166
 
1159
- it('should declare the parked queue exclusive for an exclusive queue', async () => {
1167
+ it('should declare the parked queue durable whatever the channel is', async () => {
1168
+ // what a groupless subscriber could not process is waiting for a person, and
1169
+ // there is nobody left to tell once its own queue has gone with its connection
1160
1170
  jest.clearAllMocks()
1161
1171
 
1162
1172
  topology.durable = false
@@ -1166,9 +1176,10 @@ describe('failed messages', () => {
1166
1176
  await channel.consume(queue, consumer)
1167
1177
 
1168
1178
  const [, options] = chan.assertQueue.mock.calls
1169
- .find(([name]) => name === 'comq.parked.' + queue)
1179
+ .find(([name]) => name === PARKED)
1170
1180
 
1171
- expect(options).toMatchObject({ exclusive: true })
1181
+ expect(options).toMatchObject({ durable: true })
1182
+ expect(options.exclusive).toBeUndefined()
1172
1183
  })
1173
1184
 
1174
1185
  it('should re-assert after recovery', async () => {
@@ -1181,7 +1192,7 @@ describe('failed messages', () => {
1181
1192
  const repl = await getCreatedChannel(replacement)
1182
1193
 
1183
1194
  expect(repl.assertExchange).toHaveBeenCalledWith(RETRY, 'fanout', expect.anything())
1184
- expect(repl.assertQueue).toHaveBeenCalledWith('comq.parked.' + queue, expect.anything())
1195
+ expect(repl.assertQueue).toHaveBeenCalledWith(PARKED, expect.anything())
1185
1196
  })
1186
1197
  })
1187
1198
 
@@ -1471,7 +1482,7 @@ describe('failed messages', () => {
1471
1482
 
1472
1483
  expect(asserted()).toStrictEqual(['comq.retry.1000'])
1473
1484
  expect(publications().map(([exchange, key]) => exchange || key))
1474
- .toStrictEqual(['comq.retry.1000', 'comq.parked.' + queue])
1485
+ .toStrictEqual(['comq.retry.1000', PARKED])
1475
1486
  })
1476
1487
  })
1477
1488
 
@@ -1486,7 +1497,7 @@ describe('failed messages', () => {
1486
1497
  const [exchange, key] = publications()[0]
1487
1498
 
1488
1499
  expect(exchange).toStrictEqual('')
1489
- expect(key).toStrictEqual('comq.parked.' + queue)
1500
+ expect(key).toStrictEqual(PARKED)
1490
1501
  })
1491
1502
 
1492
1503
  it('should not retry once the attempts are spent', async () => {
@@ -1563,7 +1574,7 @@ describe('failed messages', () => {
1563
1574
 
1564
1575
  const targets = publications().map(([, key]) => key)
1565
1576
 
1566
- expect(targets).toStrictEqual([queue, 'comq.parked.' + queue])
1577
+ expect(targets).toStrictEqual([queue, PARKED])
1567
1578
  })
1568
1579
 
1569
1580
  it('should give a message one delivery more than the ladder has rungs', async () => {
@@ -1581,7 +1592,7 @@ describe('failed messages', () => {
1581
1592
 
1582
1593
  const targets = publications().map(([, key]) => key)
1583
1594
 
1584
- expect(targets).toStrictEqual([queue, queue, 'comq.parked.' + queue])
1595
+ expect(targets).toStrictEqual([queue, queue, PARKED])
1585
1596
  })
1586
1597
  })
1587
1598
 
@@ -1596,7 +1607,7 @@ describe('failed messages', () => {
1596
1607
  const [exchange, key] = publications()[0]
1597
1608
 
1598
1609
  expect(exchange).toStrictEqual('')
1599
- expect(key).toStrictEqual('comq.parked.' + queue)
1610
+ expect(key).toStrictEqual(PARKED)
1600
1611
  expect(publications()).toHaveLength(1)
1601
1612
  })
1602
1613
 
@@ -1684,7 +1695,7 @@ describe('failed messages', () => {
1684
1695
 
1685
1696
  await deliver(delivery({}))
1686
1697
 
1687
- expect(publications()[0][1]).toStrictEqual('comq.parked.' + queue)
1698
+ expect(publications()[0][1]).toStrictEqual(PARKED)
1688
1699
  })
1689
1700
  })
1690
1701
 
@@ -10,7 +10,7 @@ const { amqplib, connect } = require('./amqplib.mock')
10
10
  const { channel: create } = require('./connection.mock')
11
11
  const mock = { amqplib, channel: { create } }
12
12
 
13
- jest.mock('amqplib', () => mock.amqplib)
13
+ jest.mock('@toa.io/amqplib', () => mock.amqplib)
14
14
  jest.mock('../source/channel', () => mock.channel)
15
15
 
16
16
  const presets = require('../source/topology')
@@ -21,7 +21,8 @@ let requests
21
21
  /** @type {jest.MockedObject<comq.Channel>} */
22
22
  let replies
23
23
 
24
- const queue = generate()
24
+ const exchange = generate()
25
+ const key = generate()
25
26
  const payload = { [generate()]: generate() }
26
27
 
27
28
  beforeEach(async () => {
@@ -34,12 +35,12 @@ beforeEach(async () => {
34
35
  // endregion
35
36
 
36
37
  describe('timeout', () => {
37
- it('should expire the Request when the caller stops waiting', async () => {
38
- io.request(queue, payload, { timeout: 10000 }).catch(noop)
38
+ it('should expire the call when the caller stops waiting', async () => {
39
+ io.call(exchange, key, payload, { timeout: 10000 }).catch(noop)
39
40
 
40
41
  await initialized()
41
42
 
42
- const expiration = Number(requests.send.mock.calls[0][2].expiration)
43
+ const expiration = Number(requests.route.mock.calls[0][3].expiration)
43
44
 
44
45
  expect(expiration).toBeGreaterThan(9000)
45
46
  expect(expiration).toBeLessThanOrEqual(10000)
@@ -48,29 +49,29 @@ describe('timeout', () => {
48
49
  it('should keep the encoding', async () => {
49
50
  const encoding = 'application/octet-stream'
50
51
 
51
- io.request(queue, randomBytes(8), { encoding, timeout: 10000 }).catch(noop)
52
+ io.call(exchange, key, randomBytes(8), { encoding, timeout: 10000 }).catch(noop)
52
53
 
53
54
  await initialized()
54
55
 
55
- expect(requests.send.mock.calls[0][2].contentType).toStrictEqual(encoding)
56
+ expect(requests.route.mock.calls[0][3].contentType).toStrictEqual(encoding)
56
57
  })
57
58
 
58
59
  it('should stop waiting while the channels are being created', async () => {
59
60
  connection.createChannel.mockImplementation(() => new Promise(noop))
60
61
 
61
- const promise = io.request(queue, payload, { timeout: 20 })
62
+ const promise = io.call(exchange, key, payload, { timeout: 20 })
62
63
 
63
64
  await expect(promise).rejects.toMatchObject({ name: 'TimeoutError' })
64
65
  })
65
66
 
66
67
  it('should reject once it has passed', async () => {
67
- const promise = io.request(queue, payload, { timeout: 20 })
68
+ const promise = io.call(exchange, key, payload, { timeout: 20 })
68
69
 
69
70
  await expect(promise).rejects.toMatchObject({ name: 'TimeoutError' })
70
71
  })
71
72
 
72
73
  it('should resolve with a reply that arrives in time', async () => {
73
- const promise = io.request(queue, payload, { timeout: 10000 })
74
+ const promise = io.call(exchange, key, payload, { timeout: 10000 })
74
75
  const content = randomBytes(8)
75
76
 
76
77
  await initialized()
@@ -80,7 +81,7 @@ describe('timeout', () => {
80
81
  })
81
82
 
82
83
  it('should re-send with the time that is left', async () => {
83
- io.request(queue, payload, { timeout: 10000 }).catch(noop)
84
+ io.call(exchange, key, payload, { timeout: 10000 }).catch(noop)
84
85
 
85
86
  await initialized()
86
87
  await timeout(20)
@@ -89,10 +90,10 @@ describe('timeout', () => {
89
90
 
90
91
  await immediate()
91
92
 
92
- expect(requests.send).toHaveBeenCalledTimes(2)
93
+ expect(requests.route).toHaveBeenCalledTimes(2)
93
94
 
94
- const first = Number(requests.send.mock.calls[0][2].expiration)
95
- const second = Number(requests.send.mock.calls[1][2].expiration)
95
+ const first = Number(requests.route.mock.calls[0][3].expiration)
96
+ const second = Number(requests.route.mock.calls[1][3].expiration)
96
97
 
97
98
  expect(second).toBeLessThan(first)
98
99
  })
@@ -102,7 +103,7 @@ describe('signal', () => {
102
103
  it('should reject with the reason it was aborted for', async () => {
103
104
  const controller = new AbortController()
104
105
  const reason = new Error(generate())
105
- const promise = io.request(queue, payload, { signal: controller.signal })
106
+ const promise = io.call(exchange, key, payload, { signal: controller.signal })
106
107
 
107
108
  await initialized()
108
109
 
@@ -111,14 +112,14 @@ describe('signal', () => {
111
112
  await expect(promise).rejects.toBe(reason)
112
113
  })
113
114
 
114
- it('should leave the Request in its queue', async () => {
115
+ it('should leave the call in its queue', async () => {
115
116
  const controller = new AbortController()
116
117
 
117
- io.request(queue, payload, { signal: controller.signal }).catch(noop)
118
+ io.call(exchange, key, payload, { signal: controller.signal }).catch(noop)
118
119
 
119
120
  await initialized()
120
121
 
121
- expect(requests.send.mock.calls[0][2].expiration).toBeUndefined()
122
+ expect(requests.route.mock.calls[0][3].expiration).toBeUndefined()
122
123
  })
123
124
 
124
125
  it('should reject at once when aborted before the channels exist', async () => {
@@ -129,27 +130,27 @@ describe('signal', () => {
129
130
 
130
131
  controller.abort(reason)
131
132
 
132
- await expect(io.request(queue, payload, { signal: controller.signal })).rejects.toBe(reason)
133
+ await expect(io.call(exchange, key, payload, { signal: controller.signal })).rejects.toBe(reason)
133
134
  })
134
135
 
135
136
  it('should send nothing once aborted', async () => {
136
137
  const controller = new AbortController()
137
138
 
138
- // the channels exist before the aborted Request is made
139
- io.request(queue, payload).catch(noop)
139
+ // the channels exist before the aborted call is made
140
+ io.call(exchange, key, payload).catch(noop)
140
141
 
141
142
  await initialized()
142
143
 
143
144
  controller.abort()
144
145
 
145
- await expect(io.request(queue, payload, { signal: controller.signal })).rejects.toBeDefined()
146
+ await expect(io.call(exchange, key, payload, { signal: controller.signal })).rejects.toBeDefined()
146
147
 
147
- expect(requests.send).toHaveBeenCalledTimes(1)
148
+ expect(requests.route).toHaveBeenCalledTimes(1)
148
149
  })
149
150
 
150
151
  it('should not re-send what it abandoned', async () => {
151
152
  const controller = new AbortController()
152
- const promise = io.request(queue, payload, { signal: controller.signal })
153
+ const promise = io.call(exchange, key, payload, { signal: controller.signal })
153
154
 
154
155
  await initialized()
155
156
 
@@ -161,19 +162,19 @@ describe('signal', () => {
161
162
 
162
163
  await immediate()
163
164
 
164
- expect(requests.send).toHaveBeenCalledTimes(1)
165
+ expect(requests.route).toHaveBeenCalledTimes(1)
165
166
  })
166
167
 
167
168
  it('should stop waiting for a publication', async () => {
168
- io.request(queue, payload).catch(noop)
169
+ io.call(exchange, key, payload).catch(noop)
169
170
 
170
171
  await initialized()
171
172
 
172
- requests.send.mockImplementationOnce(() => new Promise(noop))
173
+ requests.route.mockImplementationOnce(() => new Promise(noop))
173
174
 
174
175
  const controller = new AbortController()
175
176
  const reason = new Error(generate())
176
- const promise = io.request(queue, payload, { signal: controller.signal })
177
+ const promise = io.call(exchange, key, payload, { signal: controller.signal })
177
178
 
178
179
  await immediate()
179
180
 
@@ -184,7 +185,7 @@ describe('signal', () => {
184
185
 
185
186
  it('should discard a reply that arrives afterwards', async () => {
186
187
  const controller = new AbortController()
187
- const promise = io.request(queue, payload, { signal: controller.signal })
188
+ const promise = io.call(exchange, key, payload, { signal: controller.signal })
188
189
 
189
190
  await initialized()
190
191
 
@@ -197,7 +198,7 @@ describe('signal', () => {
197
198
 
198
199
  it('should end the wait within the timeout', async () => {
199
200
  const controller = new AbortController()
200
- const promise = io.request(queue, payload, { signal: controller.signal, timeout: 20 })
201
+ const promise = io.call(exchange, key, payload, { signal: controller.signal, timeout: 20 })
201
202
 
202
203
  await expect(promise).rejects.toMatchObject({ name: 'TimeoutError' })
203
204
  })
@@ -216,7 +217,7 @@ async function initialized () {
216
217
  * @param {Buffer} [content]
217
218
  */
218
219
  async function answer (index, content = randomBytes(8)) {
219
- const { correlationId } = requests.send.mock.calls[index][2]
220
+ const { correlationId } = requests.route.mock.calls[index][3]
220
221
  const deliver = replies.consume.mock.calls[0][1]
221
222
 
222
223
  await deliver({ content, properties: { correlationId } })
@@ -98,16 +98,6 @@ describe('call', () => {
98
98
  })
99
99
  })
100
100
 
101
- describe('timeout', () => {
102
- it('should stop waiting while the channels are being created', async () => {
103
- connection.createChannel.mockImplementation(() => new Promise(() => undefined))
104
-
105
- const promise = io.call(exchange, key, payload, { timeout: 20 })
106
-
107
- await expect(promise).rejects.toMatchObject({ name: 'TimeoutError' })
108
- })
109
- })
110
-
111
101
  describe('back', () => {
112
102
  const producer = jest.fn(() => generate())
113
103