comq 0.9.0 → 0.10.1

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 (60) hide show
  1. package/.github/dependabot.yml +2 -2
  2. package/.github/workflows/codeql.yml +1 -1
  3. package/.github/workflows/release.yaml +14 -15
  4. package/docs/reply-stream-topology-dark.jpg +0 -0
  5. package/docs/reply-stream-topology-light.jpg +0 -0
  6. package/examples/readme.md +19 -3
  7. package/examples/streams/consumer.js +36 -0
  8. package/examples/streams/producer.js +44 -0
  9. package/features/backpressure.feature +2 -2
  10. package/features/readme.md +2 -2
  11. package/features/rpc.streams.feature +81 -0
  12. package/features/rpc.streams.shards.feature +37 -0
  13. package/features/steps/.test/context.test.js +1 -1
  14. package/features/steps/.test/events.test.js +1 -1
  15. package/features/steps/.types/context.d.ts +9 -1
  16. package/features/steps/broker.js +2 -0
  17. package/features/steps/common.js +12 -0
  18. package/features/steps/context.js +9 -2
  19. package/features/steps/rpc.js +235 -0
  20. package/lerna.json +1 -2
  21. package/package.json +3 -2
  22. package/readme.md +136 -46
  23. package/source/.io/ReplyPipe.js +158 -0
  24. package/source/.io/ReplyStream.js +139 -0
  25. package/source/.io/const.js +10 -0
  26. package/source/.io/{replies.js → createReplyEmitter.js} +3 -6
  27. package/source/.io/index.js +5 -1
  28. package/source/attributes/failsafe.js +34 -0
  29. package/source/attributes/failsafe.test.js +133 -0
  30. package/source/attributes/index.js +13 -0
  31. package/source/attributes/lazy.js +79 -0
  32. package/source/attributes/lazy.test.js +299 -0
  33. package/source/attributes/memo.js +13 -0
  34. package/source/attributes/memo.test.js +36 -0
  35. package/source/attributes/recall.js +42 -0
  36. package/source/attributes/recall.test.js +92 -0
  37. package/source/attributes/track.js +32 -0
  38. package/source/attributes/track.test.js +133 -0
  39. package/source/channel.js +31 -18
  40. package/source/connect.js +2 -2
  41. package/source/connection.js +4 -11
  42. package/source/emitter.js +13 -0
  43. package/source/encode.js +1 -1
  44. package/source/events.js +2 -2
  45. package/source/io.js +187 -50
  46. package/source/shards/channel.js +13 -10
  47. package/source/shards/connection.js +2 -2
  48. package/test/channel.test.js +3 -1
  49. package/test/encode.test.js +2 -2
  50. package/test/encodings.js +1 -1
  51. package/test/io.diagnostics.test.js +2 -2
  52. package/test/io.request.test.js +4 -23
  53. package/test/shards/channel.test.js +1 -1
  54. package/test/shards/connection.test.js +1 -1
  55. package/types/channel.d.ts +13 -11
  56. package/types/connection.d.ts +3 -3
  57. package/types/diagnostic.d.ts +4 -3
  58. package/types/encoding.d.ts +2 -2
  59. package/types/index.d.ts +4 -4
  60. package/types/io.d.ts +33 -40
package/source/channel.js CHANGED
@@ -1,7 +1,8 @@
1
1
  'use strict'
2
2
 
3
- const { EventEmitter } = require('node:events')
4
- const { lazy, recall, promex, failsafe, timeout } = require('@toa.io/generic')
3
+ const { promex, timeout } = require('@toa.io/generic')
4
+ const { failsafe, lazy, recall } = require('./attributes')
5
+ const emitter = require('./emitter')
5
6
 
6
7
  /**
7
8
  * @implements {comq.Channel}
@@ -24,8 +25,8 @@ class Channel {
24
25
  /** @type {string[]} */
25
26
  #tags = []
26
27
 
27
- /** @type {toa.generic.Promex} */
28
- #paused
28
+ /** @type {toa.generic.Promex | null} */
29
+ #paused = null
29
30
 
30
31
  /** @type {boolean} */
31
32
  #sealed = false
@@ -36,7 +37,7 @@ class Channel {
36
37
  /** @type {Set<toa.generic.Promex>} */
37
38
  #confirmations = new Set()
38
39
 
39
- #diagnostics = new EventEmitter()
40
+ #diagnostics = emitter.create()
40
41
 
41
42
  /**
42
43
  * @param {comq.amqp.Connection} connection
@@ -56,6 +57,9 @@ class Channel {
56
57
  async create () {
57
58
  if (this.#topology.confirms) this.#channel = await this.#connection.createConfirmChannel()
58
59
  else this.#channel = await this.#connection.createChannel()
60
+
61
+ this.#channel.on('drain', this.#unpause)
62
+ this.#channel.on('return', (message) => this.#diagnostics.emit('return', message))
59
63
  }
60
64
 
61
65
  consume = recall(this,
@@ -63,10 +67,10 @@ class Channel {
63
67
  lazy(this, this.#assertQueue,
64
68
  /**
65
69
  * @param {string} queue
66
- * @param {comq.channels.consumer} callback
70
+ * @param {comq.channels.Consumer} callback
67
71
  */
68
72
  async (queue, callback) => {
69
- if (!this.#sealed) await this.#consume(queue, callback)
73
+ if (!this.#sealed) return await this.#consume(queue, callback)
70
74
  })))
71
75
 
72
76
  subscribe = recall(this,
@@ -75,7 +79,7 @@ class Channel {
75
79
  /**
76
80
  * @param {string} exchange
77
81
  * @param {string} queue
78
- * @param {comq.channels.consumer} callback
82
+ * @param {comq.channels.Consumer} callback
79
83
  * @returns {Promise<void>}
80
84
  */
81
85
  async (exchange, queue, callback) => {
@@ -107,9 +111,13 @@ class Channel {
107
111
  async fire (queue, buffer, options) {
108
112
  try {
109
113
  await this.#publish(DEFAULT, queue, buffer, options)
114
+
115
+ return true
110
116
  } catch (exception) {
111
117
  if (this.#failfast) throw exception
112
118
  // ignore otherwise
119
+
120
+ return false
113
121
  }
114
122
  }
115
123
 
@@ -125,6 +133,10 @@ class Channel {
125
133
  this.#diagnostics.on(event, listener)
126
134
  }
127
135
 
136
+ forget (event, listener) {
137
+ this.#diagnostics.off(event, listener)
138
+ }
139
+
128
140
  async recover (connection) {
129
141
  this.#connection = connection
130
142
 
@@ -137,7 +149,7 @@ class Channel {
137
149
 
138
150
  for (const confirmation of this.#confirmations) confirmation.reject(INTERRUPTION)
139
151
 
140
- await timeout(0) // let unpause and confirmation interruptions be handled
152
+ await timeout(0) // handle interruptions
141
153
 
142
154
  this.#recovery.resolve()
143
155
  this.#recovery = promex()
@@ -197,7 +209,7 @@ class Channel {
197
209
  * @param {comq.amqp.options.Publish} options
198
210
  */
199
211
  async #publish (exchange, queue, buffer, options) {
200
- if (this.#paused !== undefined) await this.#unpaused()
212
+ if (this.#paused !== null) await this.#unpaused()
201
213
 
202
214
  options = Object.assign({ persistent: this.#topology.persistent }, options)
203
215
 
@@ -226,8 +238,8 @@ class Channel {
226
238
 
227
239
  /**
228
240
  * @param {string} queue
229
- * @param {comq.channels.consumer} consumer
230
- * @returns {Promise<void>}
241
+ * @param {comq.channels.Consumer} consumer
242
+ * @returns {Promise<string>}
231
243
  */
232
244
  async #consume (queue, consumer) {
233
245
  /** @type {comq.amqp.options.Consume} */
@@ -239,11 +251,13 @@ class Channel {
239
251
  const response = await this.#channel.consume(queue, consumer, options)
240
252
 
241
253
  this.#tags.push(response.consumerTag)
254
+
255
+ return response.consumerTag
242
256
  }
243
257
 
244
258
  /**
245
- * @param {comq.channels.consumer} consumer
246
- * @returns {comq.channels.consumer}
259
+ * @param {comq.channels.Consumer} consumer
260
+ * @returns {comq.channels.Consumer}
247
261
  */
248
262
  #getAcknowledgingConsumer = (consumer) =>
249
263
  async (message) => {
@@ -276,10 +290,9 @@ class Channel {
276
290
  }
277
291
 
278
292
  #pause () {
279
- if (this.#paused !== undefined) return
293
+ if (this.#paused !== null) return
280
294
 
281
295
  this.#paused = promex()
282
- this.#channel.once('drain', this.#unpause)
283
296
  this.#diagnostics.emit('flow')
284
297
  this.#diagnostics.emit('pause')
285
298
  }
@@ -288,12 +301,12 @@ class Channel {
288
301
  * @param {Error} [exception]
289
302
  */
290
303
  #unpause = (exception) => {
291
- if (this.#paused === undefined) return
304
+ if (this.#paused === null) return
292
305
 
293
306
  if (exception === undefined) this.#paused.resolve()
294
307
  else this.#paused.reject(exception)
295
308
 
296
- this.#paused = undefined
309
+ this.#paused = null
297
310
  this.#diagnostics.emit('drain')
298
311
  this.#diagnostics.emit('resume')
299
312
  }
package/source/connect.js CHANGED
@@ -5,12 +5,12 @@ const { Connection } = require('./connection')
5
5
  const { SingletonConnection } = require('./singleton')
6
6
  const shards = require('./shards')
7
7
 
8
- /** @type {comq.connect} */
8
+ /** @type {comq.Connect} */
9
9
  const connect = async (...urls) => {
10
10
  return create(urls, connectionConstructor(Connection))
11
11
  }
12
12
 
13
- /** @type {comq.connect} */
13
+ /** @type {comq.Connect} */
14
14
  const assert = async (...urls) => {
15
15
  return create(urls, connectionConstructor(SingletonConnection))
16
16
  }
@@ -1,11 +1,12 @@
1
1
  'use strict'
2
2
 
3
- const { EventEmitter } = require('node:events')
4
3
  const amqp = require('amqplib')
5
- const { retry, promex, failsafe } = require('@toa.io/generic')
4
+ const { retry, promex } = require('@toa.io/generic')
6
5
 
6
+ const { failsafe } = require('./attributes')
7
7
  const presets = require('./topology')
8
8
  const channels = require('./channel')
9
+ const emitter = require('./emitter')
9
10
 
10
11
  /**
11
12
  * @implements {comq.Connection}
@@ -26,7 +27,7 @@ class Connection {
26
27
  /** @type {boolean} */
27
28
  #running = false
28
29
 
29
- #diagnostics = createEmitter()
30
+ #diagnostics = emitter.create()
30
31
 
31
32
  /**
32
33
  * @param {string} url
@@ -114,14 +115,6 @@ class Connection {
114
115
  }
115
116
  }
116
117
 
117
- function createEmitter () {
118
- const emitter = new EventEmitter()
119
-
120
- emitter.setMaxListeners(10000)
121
-
122
- return emitter
123
- }
124
-
125
118
  function noop () {}
126
119
 
127
120
  /** @type {toa.generic.retry.Options} */
@@ -0,0 +1,13 @@
1
+ 'use strict'
2
+
3
+ const { EventEmitter } = require('node:events')
4
+
5
+ function create () {
6
+ const emitter = new EventEmitter()
7
+
8
+ emitter.setMaxListeners(0)
9
+
10
+ return emitter
11
+ }
12
+
13
+ exports.create = create
package/source/encode.js CHANGED
@@ -4,7 +4,7 @@ const encoders = require('./encoders')
4
4
 
5
5
  /**
6
6
  * @param {any} payload
7
- * @param {comq.encoding} encoding
7
+ * @param {comq.Encoding} encoding
8
8
  * @returns {Buffer}
9
9
  */
10
10
  const encode = (payload, encoding) => {
package/source/events.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
- /** @type {comq.diagnostics.event[]} */
3
+ /** @type {comq.diagnostics.Event[]} */
4
4
  exports.connection = ['open', 'close']
5
5
 
6
- /** @type {comq.diagnostics.event[]} */
6
+ /** @type {comq.diagnostics.Event[]} */
7
7
  exports.channel = ['flow', 'drain', 'recover', 'discard', 'pause', 'resume']
package/source/io.js CHANGED
@@ -3,7 +3,8 @@
3
3
  const stream = require('node:stream')
4
4
  const { EventEmitter } = require('node:events')
5
5
  const { randomBytes } = require('node:crypto')
6
- const { lazy, track, failsafe, promex } = require('@toa.io/generic')
6
+ const { promex, timeout } = require('@toa.io/generic')
7
+ const { memo, failsafe, lazy, track } = require('./attributes')
7
8
 
8
9
  const { decode } = require('./decode')
9
10
  const { encode } = require('./encode')
@@ -30,9 +31,18 @@ class IO {
30
31
  /** @type {Map<string, comq.ReplyEmitter>} */
31
32
  #emitters = new Map()
32
33
 
34
+ /** @type {comq.ReplyEmitter | null} */
35
+ #control = null
36
+
33
37
  /** @type {Set<toa.generic.Promex>} */
34
38
  #pendingReplies = new Set()
35
39
 
40
+ /** @type {Set<comq.Destroyable>} */
41
+ #replyStreams = new Set()
42
+
43
+ /** @type {Set<comq.Destroyable>} */
44
+ #replyPipes = new Set()
45
+
36
46
  #diagnostics = new EventEmitter()
37
47
 
38
48
  /**
@@ -49,7 +59,7 @@ class IO {
49
59
  reply = lazy(this, this.#createRequestReplyChannels,
50
60
  /**
51
61
  * @param {string} queue
52
- * @param {comq.producer} callback
62
+ * @param {comq.Producer} callback
53
63
  * @returns {Promise<void>}
54
64
  */
55
65
  async (queue, callback) => {
@@ -64,30 +74,22 @@ class IO {
64
74
  /**
65
75
  * @param {string} queue
66
76
  * @param {any | Readable} payload
67
- * @param {comq.encoding} [encoding]
68
- * @param {comq.ReplyToPropertyFormatter} [replyToFormatter]
77
+ * @param {comq.Encoding} [encoding]
69
78
  * @returns {Promise<any | Readable>}
70
79
  */
71
- async (queue, payload, encoding, replyToFormatter) => {
80
+ async (queue, payload, encoding) => {
72
81
  if (payload instanceof stream.Readable) {
73
82
  return pipeline(
74
83
  payload,
75
- (payload) => this.request(queue, payload, encoding, replyToFormatter),
84
+ (payload) => this.request(queue, payload, encoding),
76
85
  this.#requests
77
86
  )
78
87
  }
79
88
 
80
- const [buffer, contentType] = this.#encode(payload, encoding)
81
- const emitter = this.#emitters.get(queue)
82
- const reply = this.#createReply()
83
- const correlationId = randomBytes(8).toString('hex')
84
- const properties = { contentType, correlationId }
89
+ const request = this.#createRequest(queue, payload, encoding)
90
+ const reply = this.#createReply(request)
85
91
 
86
- properties.replyTo = replyToFormatter?.(emitter.queue) ?? emitter.queue
87
-
88
- emitter.once(correlationId, reply.resolve)
89
-
90
- await this.#requests.send(queue, buffer, properties)
92
+ await this.#requests.send(queue, request.buffer, request.properties)
91
93
 
92
94
  return reply
93
95
  }))
@@ -110,7 +112,7 @@ class IO {
110
112
  /**
111
113
  * @param {string} exchange
112
114
  * @param {any} payload
113
- * @param {comq.encoding | comq.amqp.options.Publish} [encoding]
115
+ * @param {comq.Encoding | comq.amqp.options.Publish} [encoding]
114
116
  * @returns {Promise<void>}
115
117
  */
116
118
  async (exchange, payload, encoding) => {
@@ -128,7 +130,7 @@ class IO {
128
130
  if (typeof encoding === 'object') { // properties passed
129
131
  Object.assign(properties, encoding)
130
132
 
131
- encoding = /** @type {comq.encoding} */ properties.contentType
133
+ encoding = /** @type {comq.Encoding} */ properties.contentType
132
134
  }
133
135
 
134
136
  const [buffer, contentType] = this.#encode(payload, encoding)
@@ -138,16 +140,18 @@ class IO {
138
140
  await this.#events.publish(exchange, buffer, properties)
139
141
  })
140
142
 
141
- async seal () {
143
+ seal = memo(async () => {
142
144
  await this.#requests?.seal()
143
145
  await this.#events?.seal()
144
- }
146
+ await this.#destroyStreams(this.#replyStreams)
147
+ })
145
148
 
146
- async close () {
149
+ close = memo(async () => {
147
150
  await this.seal()
151
+ await this.#destroyStreams(this.#replyPipes)
148
152
  await track(this)
149
153
  await this.#connection.close()
150
- }
154
+ })
151
155
 
152
156
  diagnose (event, listener) {
153
157
  this.#diagnostics.on(event, listener)
@@ -198,8 +202,8 @@ class IO {
198
202
  }
199
203
 
200
204
  /**
201
- * @param {comq.producer} producer
202
- * @returns {comq.channels.consumer}
205
+ * @param {comq.Producer} producer
206
+ * @returns {comq.channels.Consumer}
203
207
  */
204
208
  #getRequestConsumer = (producer) =>
205
209
  track(this,
@@ -211,43 +215,43 @@ class IO {
211
215
  const payload = decode(request)
212
216
  const reply = await producer(payload)
213
217
 
214
- if ('replyTo' in request.properties) await this.#reply(request, reply)
215
- })
218
+ if (request.properties.replyTo === undefined) return
216
219
 
217
- /**
218
- * @param {comq.amqp.Message} request
219
- * @param {any} reply
220
- * @returns {Promise<void>}
221
- */
222
- async #reply (request, reply) {
223
- if (reply === undefined) throw new Error('The `producer` function must return a value')
220
+ const iterator = typeof reply === 'object' &&
221
+ (Symbol.asyncIterator in reply ||
222
+ (Symbol.iterator in reply && !Array.isArray(reply) && !Buffer.isBuffer(reply)))
224
223
 
225
- let { replyTo, correlationId, contentType } = request.properties
224
+ if (iterator) {
225
+ const readable = reply instanceof stream.Readable
226
+ ? reply
227
+ : stream.Readable.from(reply)
226
228
 
227
- if (Buffer.isBuffer(reply)) contentType = OCTETS
228
- if (contentType === undefined) throw new Error('Reply to a Request without the `contentType` property must be of type `Buffer`')
229
+ this.#control ??= await this.#createControl()
229
230
 
230
- const buffer = contentType === OCTETS ? reply : encode(reply, contentType)
231
- const properties = { contentType, correlationId }
231
+ const pipe = await io.ReplyPipe.create(request, readable, this.#replies, this.#control,
232
+ (message, properties) => this.#reply(request, message, properties))
232
233
 
233
- await this.#replies.fire(replyTo, buffer, properties)
234
- }
234
+ this.#addReplyPipe(pipe)
235
+ } else {
236
+ await this.#reply(request, reply)
237
+ }
238
+ })
235
239
 
236
240
  /**
237
241
  * @param {string} queue
238
242
  * @param {comq.ReplyEmitter} emitter
239
- * @returns {comq.channels.consumer}
243
+ * @returns {comq.channels.Consumer}
240
244
  */
241
245
  #getReplyConsumer = (queue, emitter) =>
242
246
  (message) => {
243
247
  const payload = decode(message)
244
248
 
245
- emitter.emit(message.properties.correlationId, payload)
249
+ emitter.emit(message.properties.correlationId, payload, message.properties)
246
250
  }
247
251
 
248
252
  /**
249
- * @param {comq.consumer} callback
250
- * @returns {comq.channels.consumer}
253
+ * @param {comq.Consumer} callback
254
+ * @returns {comq.channels.Consumer}
251
255
  */
252
256
  #getEventConsumer = (callback) =>
253
257
  track(this, async (message) => {
@@ -256,10 +260,39 @@ class IO {
256
260
  await callback(payload, message.properties)
257
261
  })
258
262
 
263
+ /**
264
+ * @param {string} queue
265
+ * @param {any} payload
266
+ * @param {comq.Encoding} [encoding]
267
+ * @return {comq.Request}
268
+ */
269
+ #createRequest (queue, payload, encoding) {
270
+ const [buffer, contentType] = this.#encode(payload, encoding)
271
+ const emitter = this.#emitters.get(queue)
272
+ const correlationId = randomBytes(8).toString('hex')
273
+
274
+ /** @type {comq.amqp.Properties} */
275
+ const properties = { contentType, correlationId, replyTo: emitter.queue }
276
+
277
+ return { buffer, emitter, properties }
278
+ }
279
+
280
+ /**
281
+ * @param {comq.Request} request
282
+ * @return {Promise<any>}
283
+ */
284
+ #createReply (request) {
285
+ const reply = this.#createPendingReply()
286
+
287
+ request.emitter.once(request.properties.correlationId, this.#getReplyResolver(request, reply))
288
+
289
+ return reply
290
+ }
291
+
259
292
  /**
260
293
  * @return {toa.generic.Promex}
261
294
  */
262
- #createReply () {
295
+ #createPendingReply () {
263
296
  const reply = promex()
264
297
 
265
298
  this.#pendingReplies.add(reply)
@@ -271,12 +304,116 @@ class IO {
271
304
  return reply
272
305
  }
273
306
 
307
+ /**
308
+ * @param {comq.Request} request
309
+ * @param reply
310
+ */
311
+ #getReplyResolver (request, reply) {
312
+ return async (payload, properties) => {
313
+ const isStream = properties.headers?.index !== undefined
314
+
315
+ if (isStream) {
316
+ const stream = this.#createReplyStream(request, payload, properties)
317
+
318
+ await stream.confirmation
319
+
320
+ reply.resolve(stream)
321
+ } else {
322
+ reply.resolve(payload)
323
+ }
324
+ }
325
+ }
326
+
327
+ #createReplyStream (request, payload, properties) {
328
+ const stream = new io.ReplyStream(request, this.#reply.bind(this))
329
+
330
+ stream.arrange(payload, properties)
331
+ this.#addReplyStream(/** @type {comq.Destroyable} */ stream)
332
+
333
+ return stream
334
+ }
335
+
336
+ /**
337
+ * @return {Promise<comq.ReplyEmitter>}
338
+ */
339
+ async #createControl () {
340
+ const queue = 'control'
341
+
342
+ await this.#consumeReplies(queue)
343
+
344
+ return this.#emitters.get(queue)
345
+ }
346
+
347
+ /**
348
+ * @param {comq.Destroyable} stream
349
+ */
350
+ #addReplyStream (stream) {
351
+ this.#addStream(stream, this.#replyStreams)
352
+ }
353
+
354
+ /**
355
+ * @param {comq.Destroyable} pipe
356
+ */
357
+ #addReplyPipe (pipe) {
358
+ this.#addStream(pipe, this.#replyPipes)
359
+ }
360
+
361
+ /**
362
+ * @param {comq.Destroyable} stream
363
+ * @param {Set<comq.Destroyable>} streams
364
+ */
365
+ #addStream (stream, streams) {
366
+ streams.add(stream)
367
+ stream.on('close', () => streams.delete(stream))
368
+ }
369
+
370
+ /**
371
+ * @param {Set<comq.Destroyable>} streams
372
+ * @return {Promise<void>}
373
+ */
374
+ async #destroyStreams (streams) {
375
+ if (streams.size === 0) return
376
+
377
+ for (const stream of streams) stream.destroy()
378
+
379
+ /*
380
+ When streams are destroyed, they attempt to send an 'end' control message.
381
+ Since these messages are sent without an acknowledgment,
382
+ it is needed to wait briefly before closing the connection.
383
+ Even if these messages are lost, the reply stream will be closed anyway,
384
+ either due to missing heartbeat or the deletion of the stream queue.
385
+ */
386
+ await timeout(50)
387
+ }
388
+
389
+ /**
390
+ * @param {Pick<comq.amqp.Message, 'properties'>} request
391
+ * @param {any} reply
392
+ * @param {comq.amqp.options.Publish} [properties]
393
+ * @returns {Promise<boolean>}
394
+ */
395
+ async #reply (request, reply, properties = {}) {
396
+ if (reply === undefined) throw new Error('The `producer` function must return a value')
397
+
398
+ let { replyTo, contentType } = request.properties
399
+
400
+ if (Buffer.isBuffer(reply)) contentType = OCTETS
401
+ if (contentType === undefined) throw new Error('Reply to a Request without the `contentType` property must be of type `Buffer`')
402
+
403
+ const buffer = contentType === OCTETS ? reply : encode(reply, contentType)
404
+
405
+ properties.contentType = contentType
406
+ properties.correlationId = request.properties.correlationId
407
+
408
+ return await this.#replies.fire(replyTo, buffer, properties)
409
+ }
410
+
274
411
  #recover (exception) {
275
412
  if (exception !== RETRANSMISSION) return false
276
413
  }
277
414
 
278
415
  #retransmit = () => {
279
- for (const emitter of this.#emitters.values()) emitter.clear()
416
+ for (const emitter of this.#emitters.values()) emitter.removeAllListeners()
280
417
 
281
418
  // trigger failsafe attribute
282
419
  for (const reply of this.#pendingReplies) reply.reject(RETRANSMISSION)
@@ -284,8 +421,8 @@ class IO {
284
421
 
285
422
  /**
286
423
  * @param {any} payload
287
- * @param {comq.encoding} [contentType]
288
- * @returns [Buffer, comq.encoding]
424
+ * @param {comq.Encoding} [contentType]
425
+ * @returns {[Buffer, comq.Encoding]}
289
426
  */
290
427
  #encode (payload, contentType) {
291
428
  const raw = Buffer.isBuffer(payload)
@@ -298,10 +435,10 @@ class IO {
298
435
  }
299
436
  }
300
437
 
301
- /** @type {comq.encoding} */
438
+ /** @type {comq.Encoding} */
302
439
  const OCTETS = 'application/octet-stream'
303
440
 
304
- /** @type {comq.encoding} */
441
+ /** @type {comq.Encoding} */
305
442
  const DEFAULT = 'application/msgpack'
306
443
 
307
444
  const RETRANSMISSION = /** @type {Error} */ Symbol('retransmission')