comq 0.9.0 → 0.10.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 (59) 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/common.js +12 -0
  17. package/features/steps/context.js +9 -2
  18. package/features/steps/rpc.js +235 -0
  19. package/lerna.json +1 -2
  20. package/package.json +2 -1
  21. package/readme.md +140 -46
  22. package/source/.io/ReplyPipe.js +158 -0
  23. package/source/.io/ReplyStream.js +142 -0
  24. package/source/.io/const.js +10 -0
  25. package/source/.io/{replies.js → createReplyEmitter.js} +3 -6
  26. package/source/.io/index.js +5 -1
  27. package/source/attributes/failsafe.js +34 -0
  28. package/source/attributes/failsafe.test.js +133 -0
  29. package/source/attributes/index.js +13 -0
  30. package/source/attributes/lazy.js +79 -0
  31. package/source/attributes/lazy.test.js +299 -0
  32. package/source/attributes/memo.js +13 -0
  33. package/source/attributes/memo.test.js +36 -0
  34. package/source/attributes/recall.js +42 -0
  35. package/source/attributes/recall.test.js +92 -0
  36. package/source/attributes/track.js +32 -0
  37. package/source/attributes/track.test.js +133 -0
  38. package/source/channel.js +31 -18
  39. package/source/connect.js +2 -2
  40. package/source/connection.js +4 -11
  41. package/source/emitter.js +13 -0
  42. package/source/encode.js +1 -1
  43. package/source/events.js +2 -2
  44. package/source/io.js +165 -47
  45. package/source/shards/channel.js +13 -10
  46. package/source/shards/connection.js +2 -2
  47. package/test/channel.test.js +3 -1
  48. package/test/encode.test.js +2 -2
  49. package/test/encodings.js +1 -1
  50. package/test/io.diagnostics.test.js +2 -2
  51. package/test/io.request.test.js +4 -23
  52. package/test/shards/channel.test.js +1 -1
  53. package/test/shards/connection.test.js +1 -1
  54. package/types/channel.d.ts +13 -11
  55. package/types/connection.d.ts +3 -3
  56. package/types/diagnostic.d.ts +4 -3
  57. package/types/encoding.d.ts +2 -2
  58. package/types/index.d.ts +4 -4
  59. package/types/io.d.ts +36 -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
+ #feedback = 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,34 +74,47 @@ 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 { buffer, properties, emitter, reply } = this.#createRequest(queue, payload, encoding)
85
90
 
86
- properties.replyTo = replyToFormatter?.(emitter.queue) ?? emitter.queue
87
-
88
- emitter.once(correlationId, reply.resolve)
91
+ emitter.once(properties.correlationId, reply.resolve)
89
92
 
90
93
  await this.#requests.send(queue, buffer, properties)
91
94
 
92
95
  return reply
93
96
  }))
94
97
 
98
+ fetch = lazy(this, [this.#createRequestReplyChannels, this.#consumeReplies],
99
+ failsafe(this, this.#recover,
100
+ /**
101
+ * @param {string} queue
102
+ * @param {any} payload
103
+ * @param {comq.Encoding} [encoding]
104
+ * @returns {Promise<Readable>}
105
+ */
106
+ async (queue, payload, encoding) => {
107
+ const request = this.#createRequest(queue, payload, encoding)
108
+ const stream = new io.ReplyStream(request, this.#reply.bind(this))
109
+
110
+ this.#addReplyStream(stream)
111
+
112
+ await this.#requests.send(queue, request.buffer, request.properties)
113
+ await request.reply // wait for the confirmation
114
+
115
+ return stream
116
+ }))
117
+
95
118
  consume = lazy(this, this.#createEventChannel,
96
119
  async (exchange, group, callback) => {
97
120
  if (callback === undefined) { // two arguments passed
@@ -110,7 +133,7 @@ class IO {
110
133
  /**
111
134
  * @param {string} exchange
112
135
  * @param {any} payload
113
- * @param {comq.encoding | comq.amqp.options.Publish} [encoding]
136
+ * @param {comq.Encoding | comq.amqp.options.Publish} [encoding]
114
137
  * @returns {Promise<void>}
115
138
  */
116
139
  async (exchange, payload, encoding) => {
@@ -128,7 +151,7 @@ class IO {
128
151
  if (typeof encoding === 'object') { // properties passed
129
152
  Object.assign(properties, encoding)
130
153
 
131
- encoding = /** @type {comq.encoding} */ properties.contentType
154
+ encoding = /** @type {comq.Encoding} */ properties.contentType
132
155
  }
133
156
 
134
157
  const [buffer, contentType] = this.#encode(payload, encoding)
@@ -138,16 +161,18 @@ class IO {
138
161
  await this.#events.publish(exchange, buffer, properties)
139
162
  })
140
163
 
141
- async seal () {
164
+ seal = memo(async () => {
142
165
  await this.#requests?.seal()
143
166
  await this.#events?.seal()
144
- }
167
+ await this.#destroyStreams(this.#replyStreams)
168
+ })
145
169
 
146
- async close () {
170
+ close = memo(async () => {
147
171
  await this.seal()
172
+ await this.#destroyStreams(this.#replyPipes)
148
173
  await track(this)
149
174
  await this.#connection.close()
150
- }
175
+ })
151
176
 
152
177
  diagnose (event, listener) {
153
178
  this.#diagnostics.on(event, listener)
@@ -198,8 +223,8 @@ class IO {
198
223
  }
199
224
 
200
225
  /**
201
- * @param {comq.producer} producer
202
- * @returns {comq.channels.consumer}
226
+ * @param {comq.Producer} producer
227
+ * @returns {comq.channels.Consumer}
203
228
  */
204
229
  #getRequestConsumer = (producer) =>
205
230
  track(this,
@@ -211,43 +236,43 @@ class IO {
211
236
  const payload = decode(request)
212
237
  const reply = await producer(payload)
213
238
 
214
- if ('replyTo' in request.properties) await this.#reply(request, reply)
215
- })
239
+ if (request.properties.replyTo === undefined) return
216
240
 
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')
241
+ const iterator = typeof reply === 'object' &&
242
+ (Symbol.asyncIterator in reply ||
243
+ (Symbol.iterator in reply && !Array.isArray(reply) && !Buffer.isBuffer(reply)))
224
244
 
225
- let { replyTo, correlationId, contentType } = request.properties
245
+ if (iterator) {
246
+ const readable = reply instanceof stream.Readable
247
+ ? reply
248
+ : stream.Readable.from(reply)
226
249
 
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`')
250
+ this.#feedback ??= await this.#createFeedback()
229
251
 
230
- const buffer = contentType === OCTETS ? reply : encode(reply, contentType)
231
- const properties = { contentType, correlationId }
252
+ const pipe = await io.ReplyPipe.create(request, readable, this.#replies, this.#feedback,
253
+ (message, properties) => this.#reply(request, message, properties))
232
254
 
233
- await this.#replies.fire(replyTo, buffer, properties)
234
- }
255
+ this.#addReplyPipe(pipe)
256
+ } else {
257
+ await this.#reply(request, reply)
258
+ }
259
+ })
235
260
 
236
261
  /**
237
262
  * @param {string} queue
238
263
  * @param {comq.ReplyEmitter} emitter
239
- * @returns {comq.channels.consumer}
264
+ * @returns {comq.channels.Consumer}
240
265
  */
241
266
  #getReplyConsumer = (queue, emitter) =>
242
267
  (message) => {
243
268
  const payload = decode(message)
244
269
 
245
- emitter.emit(message.properties.correlationId, payload)
270
+ emitter.emit(message.properties.correlationId, payload, message.properties)
246
271
  }
247
272
 
248
273
  /**
249
- * @param {comq.consumer} callback
250
- * @returns {comq.channels.consumer}
274
+ * @param {comq.Consumer} callback
275
+ * @returns {comq.channels.Consumer}
251
276
  */
252
277
  #getEventConsumer = (callback) =>
253
278
  track(this, async (message) => {
@@ -256,6 +281,24 @@ class IO {
256
281
  await callback(payload, message.properties)
257
282
  })
258
283
 
284
+ /**
285
+ * @param {string} queue
286
+ * @param {any} payload
287
+ * @param {comq.Encoding} [encoding]
288
+ * @return {comq.Request}
289
+ */
290
+ #createRequest (queue, payload, encoding) {
291
+ const [buffer, contentType] = this.#encode(payload, encoding)
292
+ const emitter = this.#emitters.get(queue)
293
+ const correlationId = randomBytes(8).toString('hex')
294
+ const reply = this.#createReply()
295
+
296
+ /** @type {comq.amqp.Properties} */
297
+ const properties = { contentType, correlationId, replyTo: emitter.queue }
298
+
299
+ return { buffer, emitter, reply, properties }
300
+ }
301
+
259
302
  /**
260
303
  * @return {toa.generic.Promex}
261
304
  */
@@ -271,12 +314,87 @@ class IO {
271
314
  return reply
272
315
  }
273
316
 
317
+ /**
318
+ * @return {Promise<comq.ReplyEmitter>}
319
+ */
320
+ async #createFeedback () {
321
+ const queue = 'feedback'
322
+
323
+ await this.#consumeReplies(queue)
324
+
325
+ return this.#emitters.get(queue)
326
+ }
327
+
328
+ /**
329
+ * @param {comq.Destroyable} stream
330
+ */
331
+ #addReplyStream (stream) {
332
+ this.#addStream(stream, this.#replyStreams)
333
+ }
334
+
335
+ /**
336
+ * @param {comq.Destroyable} pipe
337
+ */
338
+ #addReplyPipe (pipe) {
339
+ this.#addStream(pipe, this.#replyPipes)
340
+ }
341
+
342
+ /**
343
+ * @param {comq.Destroyable} stream
344
+ * @param {Set<comq.Destroyable>} streams
345
+ */
346
+ #addStream (stream, streams) {
347
+ streams.add(stream)
348
+ stream.on('close', () => streams.delete(stream))
349
+ }
350
+
351
+ /**
352
+ * @param {Set<comq.Destroyable>} streams
353
+ * @return {Promise<void>}
354
+ */
355
+ async #destroyStreams (streams) {
356
+ if (streams.size === 0) return
357
+
358
+ for (const stream of streams) stream.destroy()
359
+
360
+ /*
361
+ When streams are destroyed, they attempt to send an 'end' control message.
362
+ Since these messages are sent without an acknowledgment,
363
+ it is needed to wait briefly before closing the connection.
364
+ Even if these messages are lost, the reply stream will be closed anyway,
365
+ either due to missing heartbeat or the deletion of the stream queue.
366
+ */
367
+ await timeout(50)
368
+ }
369
+
370
+ /**
371
+ * @param {Pick<comq.amqp.Message, 'properties'>} request
372
+ * @param {any} reply
373
+ * @param {comq.amqp.options.Publish} [properties]
374
+ * @returns {Promise<boolean>}
375
+ */
376
+ async #reply (request, reply, properties = {}) {
377
+ if (reply === undefined) throw new Error('The `producer` function must return a value')
378
+
379
+ let { replyTo, contentType } = request.properties
380
+
381
+ if (Buffer.isBuffer(reply)) contentType = OCTETS
382
+ if (contentType === undefined) throw new Error('Reply to a Request without the `contentType` property must be of type `Buffer`')
383
+
384
+ const buffer = contentType === OCTETS ? reply : encode(reply, contentType)
385
+
386
+ properties.contentType = contentType
387
+ properties.correlationId = request.properties.correlationId
388
+
389
+ return await this.#replies.fire(replyTo, buffer, properties)
390
+ }
391
+
274
392
  #recover (exception) {
275
393
  if (exception !== RETRANSMISSION) return false
276
394
  }
277
395
 
278
396
  #retransmit = () => {
279
- for (const emitter of this.#emitters.values()) emitter.clear()
397
+ for (const emitter of this.#emitters.values()) emitter.removeAllListeners()
280
398
 
281
399
  // trigger failsafe attribute
282
400
  for (const reply of this.#pendingReplies) reply.reject(RETRANSMISSION)
@@ -284,8 +402,8 @@ class IO {
284
402
 
285
403
  /**
286
404
  * @param {any} payload
287
- * @param {comq.encoding} [contentType]
288
- * @returns [Buffer, comq.encoding]
405
+ * @param {comq.Encoding} [contentType]
406
+ * @returns {[Buffer, comq.Encoding]}
289
407
  */
290
408
  #encode (payload, contentType) {
291
409
  const raw = Buffer.isBuffer(payload)
@@ -298,10 +416,10 @@ class IO {
298
416
  }
299
417
  }
300
418
 
301
- /** @type {comq.encoding} */
419
+ /** @type {comq.Encoding} */
302
420
  const OCTETS = 'application/octet-stream'
303
421
 
304
- /** @type {comq.encoding} */
422
+ /** @type {comq.Encoding} */
305
423
  const DEFAULT = 'application/msgpack'
306
424
 
307
425
  const RETRANSMISSION = /** @type {Error} */ Symbol('retransmission')
@@ -1,8 +1,8 @@
1
1
  'use strict'
2
2
 
3
- const { EventEmitter } = require('node:events')
4
3
  const { promex, sample } = require('@toa.io/generic')
5
4
  const events = require('../events')
5
+ const emitter = require('../emitter')
6
6
 
7
7
  /**
8
8
  * @implements {comq.Channel}
@@ -30,7 +30,7 @@ class Channel {
30
30
 
31
31
  #recovery = promex()
32
32
 
33
- #diagnostics = new EventEmitter()
33
+ #diagnostics = emitter.create()
34
34
 
35
35
  /**
36
36
  * @param {comq.Connection[]} connections
@@ -48,7 +48,7 @@ class Channel {
48
48
  }
49
49
 
50
50
  async consume (queue, consumer) {
51
- await this.#any((channel) => channel.consume(queue, consumer))
51
+ return await this.#any((channel) => channel.consume(queue, consumer))
52
52
  }
53
53
 
54
54
  async subscribe (queue, group, consumer) {
@@ -64,17 +64,22 @@ class Channel {
64
64
  }
65
65
 
66
66
  async fire (queue, buffer, options) {
67
- await this.#one((channel) => channel.fire(queue, buffer, options))
67
+ // noinspection JSValidateTypes
68
+ return await this.#one((channel) => channel.fire(queue, buffer, options))
68
69
  }
69
70
 
70
71
  async seal () {
71
72
  await this.#all((channel) => channel.seal())
72
73
  }
73
74
 
74
- async diagnose (event, listener) {
75
+ diagnose (event, listener) {
75
76
  this.#diagnostics.on(event, listener)
76
77
  }
77
78
 
79
+ forget (event, listener) {
80
+ this.#diagnostics.off(event, listener)
81
+ }
82
+
78
83
  /**
79
84
  * @param {comq.Connection} connection
80
85
  * @param {number} index
@@ -167,12 +172,11 @@ class Channel {
167
172
 
168
173
  /**
169
174
  * @param {(channel: comq.Channel) => void} fn
170
- * @return {Promise<void>}
171
175
  */
172
176
  async #any (fn) {
173
177
  const promises = this.#apply(fn)
174
178
 
175
- await Promise.any(promises)
179
+ return await Promise.any(promises)
176
180
  }
177
181
 
178
182
  /**
@@ -201,7 +205,6 @@ class Channel {
201
205
 
202
206
  /**
203
207
  * @param {(channel: comq.Channel) => void} fn
204
- * @return {Promise<void>}
205
208
  */
206
209
  async #one (fn) {
207
210
  if (this.#pool.length === 0) await this.#recovery
@@ -209,11 +212,11 @@ class Channel {
209
212
  const channel = sample(this.#pool)
210
213
 
211
214
  try {
212
- await fn(channel)
215
+ return await fn(channel)
213
216
  } catch {
214
217
  this.#remove(channel)
215
218
 
216
- await this.#one(fn)
219
+ return await this.#one(fn)
217
220
  }
218
221
  }
219
222
  }