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
@@ -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
  }
@@ -1,8 +1,8 @@
1
1
  'use strict'
2
2
 
3
- const { EventEmitter } = require('node:events')
4
3
  const events = require('../events')
5
4
  const channel = require('./channel')
5
+ const emitter = require('../emitter')
6
6
 
7
7
  /**
8
8
  * @implements {comq.Connection}
@@ -11,7 +11,7 @@ class Connection {
11
11
  /** @type {comq.Connection[]} */
12
12
  #connections
13
13
 
14
- #diagnostics = new EventEmitter()
14
+ #diagnostics = emitter.create()
15
15
 
16
16
  /**
17
17
  * @param {comq.Connection[]} connections
@@ -67,7 +67,9 @@ describe('consume', () => {
67
67
  it('should assert queue', async () => {
68
68
  await channel.consume(queue, consumer)
69
69
 
70
- const options = topology.durable ? { durable: true } : { exclusive: true }
70
+ const options = topology.durable
71
+ ? { durable: true }
72
+ : { exclusive: true }
71
73
 
72
74
  expect(chan.assertQueue).toHaveBeenCalledWith(queue, options)
73
75
  })
@@ -11,7 +11,7 @@ it('should be', async () => {
11
11
 
12
12
  it('should throw if encoding is not supported', async () => {
13
13
  const value = 1
14
- const encoding = /** @type {comq.encoding} */ 'wtf/' + generate()
14
+ const encoding = /** @type {comq.Encoding} */ 'wtf/' + generate()
15
15
 
16
16
  expect(() => encode(value, encoding)).toThrow('not supported')
17
17
  })
@@ -34,7 +34,7 @@ it('should encode as msgpack', async () => {
34
34
  expect(buffer).toStrictEqual(packed)
35
35
  })
36
36
 
37
- /** @type {[comq.encoding, string, Buffer][]} */
37
+ /** @type {[comq.Encoding, string, Buffer][]} */
38
38
  const samples = [
39
39
  ['text/plain', 'some string', Buffer.from('some string')]
40
40
  ]
package/test/encodings.js CHANGED
@@ -1,4 +1,4 @@
1
1
  'use strict'
2
2
 
3
- /** @type {comq.encoding[]} */
3
+ /** @type {comq.Encoding[]} */
4
4
  exports.encodings = ['application/msgpack', 'application/json']
@@ -30,7 +30,7 @@ describe.each(['request', 'reply', 'event'])('%s channel events',
30
30
  (type) => {
31
31
  it.each(['flow', 'drain', 'recover', 'discard', 'pause', 'resume'])('should re-emit %s',
32
32
  /**
33
- * @param {comq.diagnostics.event} event
33
+ * @param {comq.diagnostics.Event} event
34
34
  */
35
35
  async (event) => {
36
36
  const listener = /** @type {Function} */ jest.fn()
@@ -56,7 +56,7 @@ describe.each(['request', 'reply', 'event'])('%s channel events',
56
56
 
57
57
  it.each(['open', 'close'])('should re-emit %s from connection',
58
58
  /**
59
- * @param {comq.diagnostics.event} event
59
+ * @param {comq.diagnostics.Event} event
60
60
  */
61
61
  async (event) => {
62
62
  const listener = /** @type {Function} */ jest.fn(() => undefined)
@@ -89,25 +89,6 @@ describe('send', () => {
89
89
  expect(properties.replyTo).toMatch(rx)
90
90
  })
91
91
 
92
- it('should set replyTo using specified formatter', async () => {
93
- jest.clearAllMocks()
94
-
95
- /** @type {comq.encoding} */
96
- const prefix = generate()
97
- const encoding = 'application/json'
98
- const formatter = (replyTo) => prefix + replyTo
99
-
100
- promise = io.request(queue, payload, encoding, formatter)
101
-
102
- await immediate()
103
-
104
- call = requests.send.mock.calls[0]
105
-
106
- const properties = requests.send.mock.calls[0][2]
107
-
108
- expect(properties.replyTo).toMatch(new RegExp(`${prefix}${queue}..[a-z0-9]+`))
109
- })
110
-
111
92
  it('should consume replyTo', async () => {
112
93
  const properties = call[2]
113
94
  const queue = replies.consume.mock.calls[0][0]
@@ -116,7 +97,7 @@ describe('send', () => {
116
97
  })
117
98
 
118
99
  it('should encode message with msgpack by default', async () => {
119
- /** @type {comq.encoding} */
100
+ /** @type {comq.Encoding} */
120
101
  const contentType = 'application/msgpack'
121
102
  const buffer = encode(payload, contentType)
122
103
 
@@ -125,7 +106,7 @@ describe('send', () => {
125
106
  })
126
107
 
127
108
  it('should throw if encoding is not supported', async () => {
128
- const encoding = /** @type {comq.encoding} */ 'wtf/' + generate()
109
+ const encoding = /** @type {comq.Encoding} */ 'wtf/' + generate()
129
110
 
130
111
  await expect(io.request(queue, payload, encoding)).rejects.toThrow('is not supported')
131
112
  })
@@ -153,7 +134,7 @@ describe('send', () => {
153
134
 
154
135
  setImmediate(reply)
155
136
 
156
- await io.request(queue, payload, /** @type {comq.encoding} */ encoding)
137
+ await io.request(queue, payload, /** @type {comq.Encoding} */ encoding)
157
138
 
158
139
  const [, buffer, properties] = requests.send.mock.calls[0]
159
140
 
@@ -244,7 +225,7 @@ describe('reply', () => {
244
225
 
245
226
  it.each(encodings)('should decode %s',
246
227
  /**
247
- * @param {comq.encoding}contentType
228
+ * @param {comq.Encoding}contentType
248
229
  */
249
230
  async (contentType) => {
250
231
  const value = generate()
@@ -289,7 +289,7 @@ describe('seal', () => {
289
289
  })
290
290
 
291
291
  describe('diagnose', () => {
292
- const events = /** @type {comq.diagnostics.event[]} */ ['flow', 'drain', 'recover', 'discard']
292
+ const events = /** @type {comq.diagnostics.Event[]} */ ['flow', 'drain', 'recover', 'discard']
293
293
 
294
294
  /** @type {jest.MockedObject<comq.Channel>[]} */
295
295
  let channels
@@ -106,7 +106,7 @@ describe('createChannel', () => {
106
106
  })
107
107
  })
108
108
 
109
- describe.each(/** @type {comq.diagnostics.event[]} */ ['open', 'close'])('diagnose %s event',
109
+ describe.each(/** @type {comq.diagnostics.Event[]} */ ['open', 'close'])('diagnose %s event',
110
110
  (event) => {
111
111
  it('should re-emit event', async () => {
112
112
  const index = random(connections.length)
@@ -5,7 +5,7 @@ declare namespace comq {
5
5
 
6
6
  namespace channels {
7
7
 
8
- type consumer = (message: Message) => void | Promise<void>
8
+ type Consumer = (message: Message) => void | Promise<void>
9
9
 
10
10
  }
11
11
 
@@ -13,26 +13,28 @@ declare namespace comq {
13
13
  index?: number
14
14
  sharded?: boolean
15
15
 
16
- create(): Promise<void>
16
+ create (): Promise<void>
17
17
 
18
- consume(queue: string, consumer: channels.consumer): Promise<void>
18
+ consume (queue: string, consumer: channels.Consumer): Promise<string>
19
19
 
20
- subscribe(exchange: string, queue: string, consumer: channels.consumer): Promise<void>
20
+ subscribe (exchange: string, queue: string, consumer: channels.Consumer): Promise<void>
21
21
 
22
- send(queue: string, buffer: Buffer, options?: Options.Publish): Promise<void>
22
+ send (queue: string, buffer: Buffer, options?: Options.Publish): Promise<void>
23
23
 
24
- publish(exchange: string, buffer: Buffer, options?: Options.Publish): Promise<void>
24
+ publish (exchange: string, buffer: Buffer, options?: Options.Publish): Promise<void>
25
25
 
26
- fire(queue: string, buffer: Buffer, options?: Options.Publish): Promise<void>
26
+ fire (queue: string, buffer: Buffer, options?: Options.Publish): Promise<boolean>
27
27
 
28
- seal(): Promise<void>
28
+ seal (): Promise<void>
29
29
 
30
- diagnose(event: _diagnostics.event, listener: Function): void
30
+ diagnose (event: _diagnostics.Event, listener: Function): void
31
31
 
32
- recover(connection: Connection): Promise<void>
32
+ forget (event: _diagnostics.Event, listener: Function): void
33
+
34
+ recover (connection: Connection): Promise<void>
33
35
  }
34
36
 
35
37
  }
36
38
 
37
39
  export type Channel = comq.Channel
38
- export type consumer = comq.channels.consumer
40
+ export type Consumer = comq.channels.Consumer
@@ -14,11 +14,11 @@ declare namespace comq {
14
14
 
15
15
  createChannel(type: _topology.type, index: number): Promise<_channel.Channel>
16
16
 
17
- diagnose(event: _diagnostics.event, listener: Function): void
17
+ diagnose(event: _diagnostics.Event, listener: Function): void
18
18
  }
19
19
 
20
- type connect = (...urls: string[]) => Promise<_io.IO>
20
+ type Connect = (...urls: string[]) => Promise<_io.IO>
21
21
 
22
22
  }
23
23
 
24
- export type connect = comq.connect
24
+ export type Connect = comq.Connect
@@ -1,12 +1,13 @@
1
1
  declare namespace comq.diagnostics {
2
2
 
3
- type event = 'open' | 'close' | 'flow' | 'drain' | 'remove' | 'recover' | 'discard' | 'pause' | 'resume'
3
+ type Event = 'open' | 'close' | 'flow' | 'drain' | 'remove' | 'recover' | 'discard' |
4
+ 'pause' | 'resume' | 'return'
4
5
 
5
6
  interface Diagnosable {
6
- diagnose(event: event, listener: Function): void
7
+ diagnose(event: Event, listener: Function): void
7
8
  }
8
9
 
9
10
  }
10
11
 
11
- export type event = comq.diagnostics.event
12
+ export type Event = comq.diagnostics.Event
12
13
  export type Diagnosable = comq.diagnostics.Diagnosable
@@ -1,6 +1,6 @@
1
1
  declare namespace comq {
2
2
 
3
- type encoding =
3
+ type Encoding =
4
4
  'application/msgpack'
5
5
  | 'application/json'
6
6
  | 'application/octet-stream'
@@ -8,4 +8,4 @@ declare namespace comq {
8
8
 
9
9
  }
10
10
 
11
- export type encoding = comq.encoding
11
+ export type Encoding = comq.Encoding
package/types/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { connect } from './connection'
1
+ import { Connect } from './connection'
2
2
 
3
- export type { IO, consumer, producer } from './io'
3
+ export type { IO, Consumer, Producer } from './io'
4
4
 
5
- export const connect: connect
6
- export const assert: connect
5
+ export const connect: Connect
6
+ export const assert: Connect
package/types/io.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Readable } from 'node:stream'
2
+ import type { EventEmitter } from 'node:events'
2
3
  import * as _diagnostics from './diagnostic'
3
4
  import * as _encoding from './encoding'
4
5
  import * as _topology from './topology'
@@ -6,70 +7,62 @@ import * as _amqp from './amqp'
6
7
 
7
8
  declare namespace comq {
8
9
 
9
- type producer = (message: any) => any | Promise<any>
10
- type consumer = (message: any, headers?: _amqp.Properties) => void | Promise<void>
10
+ type Producer<Input = any, Output = any> = (message: Input) => Output | Promise<Output>
11
+ type Consumer<T = any> = (message: T, headers?: _amqp.Properties) => void | Promise<void>
11
12
 
12
- interface ReplyEmitter {
13
- queue: string
14
-
15
- once(name: string, callback: Function): void
16
-
17
- emit(name: string, value: any): void
13
+ interface ReplyEmitter extends EventEmitter {
14
+ readonly queue: string
15
+ }
18
16
 
19
- clear(): void
17
+ interface Destroyable extends EventEmitter {
18
+ destroy (): void
20
19
  }
21
20
 
22
- type ReplyToPropertyFormatter = (queue: string) => string
21
+ interface Request {
22
+ buffer: Buffer
23
+ emitter: ReplyEmitter
24
+ properties: _amqp.Properties
25
+ }
23
26
 
24
27
  interface IO extends _diagnostics.Diagnosable {
25
- reply(queue: string, produce: producer): Promise<void>
28
+ reply (queue: string, produce: Producer): Promise<void>
26
29
 
27
- request(
28
- queue: string,
29
- payload: any,
30
- encoding?: _encoding.encoding,
31
- replyToFormatter?: ReplyToPropertyFormatter)
32
- : Promise<any>
30
+ request<Reply = any, Request = any> (queue: string, payload: Request, encoding?: _encoding.Encoding): Promise<Reply> | Promise<Readable>
33
31
 
34
- request(
35
- queue: string,
36
- stream: Readable,
37
- encoding?: _encoding.encoding,
38
- replyToFormatter?: ReplyToPropertyFormatter)
39
- : Promise<Readable>
32
+ request (queue: string, stream: Readable, encoding?: _encoding.Encoding): Promise<Readable>
40
33
 
41
- consume(exchange: string, group: string, consumer: consumer): Promise<void>
34
+ consume<T = any> (exchange: string, group: string, consumer: Consumer<T>): Promise<void>
42
35
 
43
- consume(exchange: string, consumer: consumer): Promise<void>
36
+ consume<T = any> (exchange: string, consumer: Consumer<T>): Promise<void>
44
37
 
45
- emit(exchange: string, payload: any, encoding?: _encoding.encoding): Promise<void>
38
+ emit (exchange: string, payload: any, encoding?: _encoding.Encoding): Promise<void>
46
39
 
47
- emit(exchange: string, payload: any, properties?: _amqp.Properties): Promise<void>
40
+ emit (exchange: string, payload: any, properties?: _amqp.Properties): Promise<void>
48
41
 
49
- emit(exchange: string, stream: Readable, encoding?: _encoding.encoding): Promise<void>
42
+ emit (exchange: string, stream: Readable, encoding?: _encoding.Encoding): Promise<void>
50
43
 
51
- emit(exchange: string, stream: Readable, properties?: _amqp.Properties): Promise<void>
44
+ emit (exchange: string, stream: Readable, properties?: _amqp.Properties): Promise<void>
52
45
 
53
- seal(): Promise<void>
46
+ seal (): Promise<void>
54
47
 
55
- close(): Promise<void>
48
+ close (): Promise<void>
56
49
 
57
- diagnose(event: 'open', listener: (index?: number) => void): void
50
+ diagnose (event: 'open', listener: (index?: number) => void): void
58
51
 
59
- diagnose(event: 'close', listener: (index?: number) => void): void
52
+ diagnose (event: 'close', listener: (index?: number) => void): void
60
53
 
61
- diagnose(event: 'flow', listener: (channel: _topology.type, index?: number) => void): void
54
+ diagnose (event: 'flow', listener: (channel: _topology.type, index?: number) => void): void
62
55
 
63
- diagnose(event: 'drain', listener: (channel: _topology.type, index?: number) => void): void
56
+ diagnose (event: 'drain', listener: (channel: _topology.type, index?: number) => void): void
64
57
 
65
- diagnose(event: 'remove', listener: (index?: number) => void): void
58
+ diagnose (event: 'remove', listener: (index?: number) => void): void
66
59
 
67
- diagnose(event: 'recover', listener: (channel: _topology.type, index?: number) => void): void
60
+ diagnose (event: 'recover', listener: (channel: _topology.type, index?: number) => void): void
68
61
 
69
- diagnose(event: 'discard', listener: (channel: _topology.type, message: any, index?: number) => void): void
62
+ diagnose (event: 'discard', listener: (channel: _topology.type, message: any, index?: number) => void): void
70
63
  }
71
64
  }
72
65
 
73
- export type producer = comq.producer
74
- export type consumer = comq.consumer
66
+ export type Producer = comq.Producer
67
+ export type Consumer = comq.Consumer
75
68
  export type IO = comq.IO