comq 0.4.1 → 0.5.0-dev.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 (61) hide show
  1. package/.github/labels.yaml +15 -0
  2. package/.github/workflows/labels.yaml +18 -0
  3. package/.github/workflows/release.yaml +20 -8
  4. package/docker-compose.yaml +20 -2
  5. package/docs/notes.md +14 -8
  6. package/examples/const.js +1 -3
  7. package/examples/events/consumer.js +5 -6
  8. package/examples/events/producer.js +3 -3
  9. package/examples/package.json +3 -0
  10. package/examples/rpc/consumer.js +3 -3
  11. package/examples/rpc/producer.js +3 -3
  12. package/features/connection.feature +3 -0
  13. package/features/events.feature +1 -1
  14. package/features/shards.feature +61 -0
  15. package/features/steps/.test/backpressure.test.js +2 -2
  16. package/features/steps/.test/broker.test.js +32 -6
  17. package/features/steps/.test/connection.test.js +105 -1
  18. package/features/steps/.test/context.mock.js +2 -2
  19. package/features/steps/.test/context.test.js +13 -5
  20. package/features/steps/.test/events.test.js +66 -3
  21. package/features/steps/.test/rpc.test.js +99 -1
  22. package/features/steps/.types/context.d.ts +11 -1
  23. package/features/steps/backpressure.js +1 -1
  24. package/features/steps/broker.js +26 -9
  25. package/features/steps/connection.js +69 -2
  26. package/features/steps/context.js +28 -12
  27. package/features/steps/events.js +41 -2
  28. package/features/steps/rpc.js +58 -1
  29. package/lerna.json +1 -1
  30. package/package.json +5 -5
  31. package/readme.md +75 -25
  32. package/source/channel.js +56 -32
  33. package/source/connect.js +28 -2
  34. package/source/connection.js +24 -17
  35. package/source/decode.js +1 -1
  36. package/source/events.js +7 -0
  37. package/source/io.js +21 -14
  38. package/source/shards/channel.js +219 -0
  39. package/source/shards/connection.js +56 -0
  40. package/source/shards/index.js +5 -0
  41. package/test/channel.test.js +240 -145
  42. package/test/connect.test.js +39 -11
  43. package/test/connection.mock.js +17 -13
  44. package/test/connection.test.js +65 -12
  45. package/test/decode.test.js +5 -5
  46. package/test/io.close.test.js +2 -2
  47. package/test/io.consume.test.js +1 -1
  48. package/test/io.reply.test.js +7 -7
  49. package/test/io.request.test.js +27 -1
  50. package/test/presets.test.js +1 -1
  51. package/test/shards/channel.test.js +367 -0
  52. package/test/shards/connection.test.js +106 -0
  53. package/types/amqp.d.ts +3 -1
  54. package/types/channel.d.ts +8 -5
  55. package/types/connection.d.ts +3 -1
  56. package/types/diagnostic.d.ts +1 -1
  57. package/types/topology.d.ts +4 -0
  58. /package/source/{presets → topology}/event.json +0 -0
  59. /package/source/{presets → topology}/index.js +0 -0
  60. /package/source/{presets → topology}/reply.json +0 -0
  61. /package/source/{presets → topology}/request.json +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { AssertionError } = require('node:assert')
4
4
  const { generate } = require('randomstring')
5
- const { promex } = require('@toa.io/generic')
5
+ const { promex, timeout, random } = require('@toa.io/generic')
6
6
  const tomato = require('@toa.io/tomato')
7
7
  const { io } = require('./io.mock')
8
8
  const mock = { tomato }
@@ -57,8 +57,8 @@ describe('Given {token} consuming events from the {token} exchange is expected',
57
57
  it('should be', async () => undefined)
58
58
  })
59
59
 
60
- describe('Given that events are exclusively consumed from the {token} exchange', () => {
61
- tomato.steps.Gi('that events are exclusively consumed from the {token} exchange')
60
+ describe('Given (that )events are exclusively consumed from the {token} exchange', () => {
61
+ tomato.steps.Gi('(that )events are exclusively consumed from the {token} exchange')
62
62
 
63
63
  it('should be', async () => undefined)
64
64
  })
@@ -152,3 +152,66 @@ describe('Then the event is received', () => {
152
152
  await expect(step.call(context, group)).resolves.not.toThrow()
153
153
  })
154
154
  })
155
+
156
+
157
+ describe('Given I\'m publishing {quantity}B events to the {token} exchange at {quantity}Hz', () => {
158
+ const step = tomato.steps.Gi('I\'m publishing {quantity}B events to the {token} exchange at {quantity}Hz')
159
+
160
+ it('should be', async () => undefined)
161
+
162
+ it('should publish events continuously', async () => {
163
+ context = /** @type {comq.features.Context} */ {
164
+ io: /** @type {jest.MockedObject<comq.IO>} */ { emit: jest.fn() },
165
+ requestsSent: [],
166
+ eventsPublishedCount: 0
167
+ }
168
+
169
+ const bytesQ = '1k'
170
+ const exchange = generate()
171
+ const frequencyQ = '100'
172
+
173
+ step.call(context, bytesQ, exchange, frequencyQ)
174
+
175
+ await timeout(100 + 11)
176
+
177
+ expect(context.io.emit).toHaveBeenCalledTimes(10)
178
+ expect(context.publishing).toBeDefined()
179
+ expect(context.eventsPublishedCount).toStrictEqual(10)
180
+
181
+ clearInterval(context.publishing)
182
+ })
183
+ })
184
+
185
+ describe('Then all events have been received', () => {
186
+ const step = tomato.steps.Th('all events have been received')
187
+
188
+ /** @type {jest.MockedObject<comq.features.Context>} */
189
+ let context
190
+
191
+ it('should be', async () => undefined)
192
+
193
+ beforeEach(async () => {
194
+ context = /** @type {jest.MockedObject<comq.features.Context>} */ {
195
+ eventsPublishedCount: random(100),
196
+ eventsConsumedCount: 100 + random()
197
+ }
198
+ })
199
+
200
+ it('should pass if events received', async () => {
201
+ context.eventsConsumedCount = context.eventsPublishedCount
202
+
203
+ await expect(step.call(context)).resolves.not.toThrow()
204
+ })
205
+
206
+ it('should fail if events are not received', async () => {
207
+ await expect(step.call(context)).rejects.toThrow(AssertionError)
208
+ })
209
+
210
+ it('should wait for last events (50ms)', async () => {
211
+ setTimeout(() => {
212
+ context.eventsConsumedCount = context.eventsPublishedCount
213
+ }, 49)
214
+
215
+ await expect(step.call(context)).resolves.not.toThrow()
216
+ })
217
+ })
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { AssertionError } = require('node:assert')
4
4
  const { generate } = require('randomstring')
5
- const { promex } = require('@toa.io/generic')
5
+ const { promex, timeout } = require('@toa.io/generic')
6
6
  const { dump } = require('@toa.io/yaml')
7
7
  const tomato = require('@toa.io/tomato')
8
8
  const { io } = require('./io.mock')
@@ -42,6 +42,24 @@ describe('Given function replying {token} queue:', () => {
42
42
  })
43
43
  })
44
44
 
45
+ describe('Given a producer replying {token} queue', () => {
46
+ const step = tomato.steps.Gi('a producer replying {token} queue')
47
+
48
+ it('should be', async () => undefined)
49
+
50
+ it('should reply', async () => {
51
+ await step.call(context, queue)
52
+
53
+ expect(io.reply).toHaveBeenCalledWith(queue, expect.any(Function))
54
+
55
+ const producer = io.reply.mock.calls[0][1]
56
+ const message = generate()
57
+ const reply = await producer(message)
58
+
59
+ expect(reply).toStrictEqual(message)
60
+ })
61
+ })
62
+
45
63
  describe('Given function replying {token} queue is expected:', () => {
46
64
  tomato.steps.Gi('function replying {token} queue is expected:')
47
65
 
@@ -140,6 +158,30 @@ describe('Then the consumer receives the reply:', () => {
140
158
  })
141
159
  })
142
160
 
161
+ describe('Then the consumer receives the reply', () => {
162
+ const step = tomato.steps.Th('the consumer receives the reply')
163
+
164
+ it('should be', async () => undefined)
165
+
166
+ it('should await reply', async () => {
167
+ context.reply = promex()
168
+
169
+ let completed = false
170
+
171
+ setImmediate(() => {
172
+ expect(completed).toStrictEqual(false)
173
+
174
+ context.reply.resolve()
175
+
176
+ completed = true
177
+ })
178
+
179
+ await step.call(context)
180
+
181
+ expect(completed).toStrictEqual(true)
182
+ })
183
+ })
184
+
143
185
  describe('Then the consumer does not receive the reply', () => {
144
186
  const step = tomato.steps.Th('the consumer does not receive the reply')
145
187
 
@@ -159,3 +201,59 @@ describe('Then the consumer does not receive the reply', () => {
159
201
  await expect(step.call(context)).resolves.not.toThrow(AssertionError)
160
202
  })
161
203
  })
204
+
205
+ describe('Given I\'m sending {quantity}B requests to the {token} queue at {quantity}Hz', () => {
206
+ const step = tomato.steps.Gi('I\'m sending {quantity}B requests to the {token} queue at {quantity}Hz')
207
+
208
+ it('should be', async () => undefined)
209
+
210
+ it('should send requests continuously', async () => {
211
+ context = /** @type {comq.features.Context} */ {
212
+ io: /** @type {jest.MockedObject<comq.IO>} */ { request: jest.fn() },
213
+ requestsSent: []
214
+ }
215
+
216
+ const bytesQ = '1k'
217
+ const queue = generate()
218
+ const frequencyQ = '100'
219
+
220
+ step.call(context, bytesQ, queue, frequencyQ)
221
+
222
+ await timeout(100 + 11)
223
+
224
+ expect(context.io.request).toHaveBeenCalledTimes(10)
225
+ expect(context.sending).toBeDefined()
226
+ expect(context.requestsSent.length).toStrictEqual(10)
227
+
228
+ clearInterval(context.sending)
229
+ })
230
+ })
231
+
232
+ describe('Then all replies have been received', () => {
233
+ const step = tomato.steps.Th('all replies have been received')
234
+
235
+ /** @type {jest.MockedObject<comq.features.Context>} */
236
+ let context
237
+
238
+ it('should be', async () => undefined)
239
+
240
+ beforeEach(async () => {
241
+ context = /** @type {jest.MockedObject<comq.features.Context>} */ {
242
+ requestsSent: [promex(), promex()]
243
+ }
244
+ })
245
+
246
+ it('should pass if replies received', async () => {
247
+ let completed = false
248
+
249
+ setImmediate(() => {
250
+ completed = true
251
+
252
+ for (const promise of context.requestsSent) promise.resolve()
253
+ })
254
+
255
+ await step.call(context)
256
+
257
+ expect(completed).toStrictEqual(true)
258
+ })
259
+ })
@@ -6,12 +6,22 @@ declare namespace comq.features {
6
6
  interface Context {
7
7
  io?: _io.IO
8
8
  connected?: boolean
9
+ connecting: Promise<any>
10
+ requestsSent: Promise<any>[]
9
11
  reply?: Promise<any>
10
- consumed?: Record<string, any>
11
12
  published?: any
13
+ eventsPublishedCount: number
14
+ eventsConsumedCount: number
15
+ consumed?: Record<string, any>
16
+ consumedCount: number
12
17
  events?: { [K in _diagnostics.event]?: boolean }
13
18
  exception?: Error
14
19
  expected?: Promise<any>
20
+ sharded: boolean
21
+ shard: number
22
+ sealing: Promise<any>
23
+ sending: any
24
+ publishing: any
15
25
 
16
26
  connect(user?: string, password?: string): Promise<void>
17
27
  disconnect(): Promise<void>
@@ -42,5 +42,5 @@ Then('back pressure was applied',
42
42
  * @this {comq.features.Context}
43
43
  */
44
44
  function () {
45
- assert.equal(this.events?.flow, true, 'Back pressure hasn\'t been applied')
45
+ assert.equal(this.events.flow, true, 'Back pressure hasn\'t been applied')
46
46
  })
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { timeout } = require('@toa.io/generic')
3
+ const { timeout, random } = require('@toa.io/generic')
4
4
  const { execute } = require('@toa.io/command')
5
5
 
6
6
  const { Given } = require('@cucumber/cucumber')
@@ -11,19 +11,34 @@ Given('the broker is/has {status}',
11
11
  * @this {comq.features.Context}
12
12
  */
13
13
  async function (status) {
14
- await actions[status](this)
14
+ const n = this.shard ?? 0
15
+
16
+ await actions[status](n)
17
+ })
18
+
19
+ Given('one of the brokers is/has {status}',
20
+ /**
21
+ * @param {'up' | 'down'} status
22
+ * @this {comq.features.Context}
23
+ */
24
+ async function (status) {
25
+ const shard = random(BROKERS_AMOUNT)
26
+
27
+ this.shard = shard
28
+
29
+ await actions[status](shard)
15
30
  })
16
31
 
17
32
  const actions = {
18
- up: async () => {
19
- await execute('docker start comq-rmq')
33
+ up: async (n = 0) => {
34
+ await execute('docker start comq-rmq-' + n)
20
35
  await healthy()
21
36
  },
22
- down: async () => {
23
- await execute('docker stop comq-rmq')
37
+ down: async (n = 0) => {
38
+ await execute('docker stop comq-rmq-' + n)
24
39
  },
25
- crashed: async () => {
26
- await execute('docker kill comq-rmq')
40
+ crashed: async (n = 0) => {
41
+ await execute('docker kill comq-rmq-' + n)
27
42
  }
28
43
  }
29
44
 
@@ -33,6 +48,8 @@ async function healthy () {
33
48
  do {
34
49
  await timeout(1000)
35
50
 
36
- process = await execute('docker inspect -f {{.State.Health.Status}} comq-rmq')
51
+ process = await execute('docker inspect -f {{.State.Health.Status}} comq-rmq-0')
37
52
  } while (process.output !== 'healthy')
38
53
  }
54
+
55
+ const BROKERS_AMOUNT = 2
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const assert = require('node:assert')
4
- const { timeout } = require('@toa.io/generic')
4
+ const { timeout, promex } = require('@toa.io/generic')
5
5
 
6
6
  const { Given, When, Then } = require('@cucumber/cucumber')
7
7
 
@@ -15,6 +15,35 @@ Given('an active connection to the broker',
15
15
  await this.connecting
16
16
  })
17
17
 
18
+ Given('an active sharded connection',
19
+ /**
20
+ * @this {comq.features.Context}
21
+ */
22
+ async function () {
23
+ this.sharded = true
24
+ this.connecting = this.connect()
25
+
26
+ await this.connecting
27
+ })
28
+
29
+ Given('the connection to both shards is established',
30
+ /**
31
+ * @this {comq.features.Context}
32
+ */
33
+ async function () {
34
+ const promise = promex()
35
+
36
+ this.sharded = true
37
+ this.connecting = this.connect()
38
+
39
+ await this.connecting
40
+
41
+ // second shard is connected
42
+ this.io.diagnose('open', () => promise.resolve())
43
+
44
+ return promise
45
+ })
46
+
18
47
  When('I attempt to connect to the broker for {number} second(s)',
19
48
  /**
20
49
  * @param {number} interval
@@ -46,6 +75,28 @@ When('I attempt to connect to the broker as {string} with password {string}',
46
75
  await connect(this, user, password)
47
76
  })
48
77
 
78
+ When('I attempt to establish sharded connection',
79
+ /**
80
+ * @this {comq.features.Context}
81
+ */
82
+ async function () {
83
+ this.sharded = true
84
+
85
+ await connect(this)
86
+ })
87
+
88
+ When('I attempt to establish a sharded connection as {string} with password {string}',
89
+ /**
90
+ * @param {string} user
91
+ * @param {string} password
92
+ * @this {comq.features.Context}
93
+ */
94
+ async function (user, password) {
95
+ this.sharded = true
96
+
97
+ await connect(this, user, password)
98
+ })
99
+
49
100
  Then('the connection is not established',
50
101
  /**
51
102
  * @this {comq.features.Context}
@@ -98,6 +149,22 @@ Then('the connection is {connection-event}',
98
149
  assert.equal(this.events[event], true, 'connection was not ' + key)
99
150
  })
100
151
 
152
+ Given('the connection has started sealing',
153
+ /**
154
+ * @this {comq.features.Context}
155
+ */
156
+ async function () {
157
+ this.sealing = this.io.seal()
158
+ })
159
+
160
+ Then('the connection is sealed',
161
+ /**
162
+ * @this {comq.features.Context}
163
+ */
164
+ async function () {
165
+ await this.sealing
166
+ })
167
+
101
168
  /**
102
169
  * @param {comq.features.Context} context
103
170
  * @param {string} [user]
@@ -108,7 +175,7 @@ const connect = async (context, user, password) => {
108
175
  try {
109
176
  await context.connect(user, password)
110
177
  } catch (exception) {
111
- context.exception = exception
178
+ context.exception = exception instanceof AggregateError ? exception.errors[0] : exception
112
179
  }
113
180
  }
114
181
 
@@ -10,17 +10,23 @@ class Context extends World {
10
10
  io
11
11
  connected = false
12
12
  connecting
13
+ requestsSent = []
13
14
  reply
14
15
  consumed
15
16
  published
17
+ eventsPublishedCount = 0
18
+ eventsConsumedCount = 0
16
19
  events = {}
17
20
  exception
18
21
  expected
22
+ sharded
23
+ shard
24
+ sealing
19
25
 
20
26
  async connect (user, password) {
21
- const url = locator(user, password)
27
+ const urls = this.#urls(user, password)
22
28
 
23
- await this.#connect(url)
29
+ await this.#connect(...urls)
24
30
  }
25
31
 
26
32
  async disconnect () {
@@ -34,13 +40,13 @@ class Context extends World {
34
40
  }
35
41
 
36
42
  /**
37
- * @param {string} url
43
+ * @param {...string} urls
38
44
  * @return {Promise<void>}
39
45
  */
40
- async #connect (url) {
46
+ async #connect (...urls) {
41
47
  if (this.io !== undefined) await this.disconnect()
42
48
 
43
- this.io = await connect(url)
49
+ this.io = await connect(...urls)
44
50
  this.connected = true
45
51
 
46
52
  for (const event of EVENTS) this.io.diagnose(event, () => (this.events[event] = true))
@@ -48,19 +54,29 @@ class Context extends World {
48
54
  this.io.diagnose('close', () => (this.connected = false))
49
55
  this.io.diagnose('open', () => (this.connected = true))
50
56
  }
51
- }
52
57
 
53
- const locator = (user, password) => {
54
- if (user === undefined) {
55
- user = USER
56
- password = PASSWORD
58
+ #urls (user, password) {
59
+ if (user === undefined) {
60
+ user = USER
61
+ password = PASSWORD
62
+ }
63
+
64
+ const urls = []
65
+
66
+ urls.push(this.#url(0, user, password))
67
+
68
+ if (this.sharded) urls.push(this.#url(1, user, password))
69
+
70
+ return urls
57
71
  }
58
72
 
59
- return PROTOCOL + user + ':' + password + '@' + HOST
73
+ #url (i, user, password) {
74
+ return PROTOCOL + user + ':' + password + '@' + SHARDS[i]
75
+ }
60
76
  }
61
77
 
62
78
  const PROTOCOL = 'amqp://'
63
- const HOST = 'localhost:5673'
79
+ const SHARDS = ['localhost:5673', 'localhost:5674']
64
80
  const USER = 'developer'
65
81
  const PASSWORD = 'secret'
66
82
 
@@ -2,8 +2,9 @@
2
2
 
3
3
  const assert = require('node:assert')
4
4
  const { generate } = require('randomstring')
5
- const { timeout } = require('@toa.io/generic')
5
+ const { timeout, quantity } = require('@toa.io/generic')
6
6
  const { Given, When, Then } = require('@cucumber/cucumber')
7
+ const { randomBytes } = require('node:crypto')
7
8
 
8
9
  Given('(that ){token} is consuming events from the {token} exchange',
9
10
  /**
@@ -27,7 +28,7 @@ Given('{token} consuming events from the {token} exchange is expected',
27
28
  this.expected = consume.call(this, group, exchange)
28
29
  })
29
30
 
30
- Given('that events are exclusively consumed from the {token} exchange',
31
+ Given('(that )events are exclusively consumed from the {token} exchange',
31
32
  /**
32
33
  * @param {string} exchange
33
34
  * @this {comq.features.Context}
@@ -68,6 +69,43 @@ Then('the event is received',
68
69
  await consumed.call(this)
69
70
  })
70
71
 
72
+ Given('I\'m publishing {quantity}B events to the {token} exchange at {quantity}Hz',
73
+ /**
74
+ * @param {string} bytesQ
75
+ * @param {string} queue
76
+ * @param {string} frequencyQ
77
+ * @this {comq.features.Context}
78
+ */
79
+ async function (bytesQ, queue, frequencyQ) {
80
+ const bytes = quantity(bytesQ)
81
+ const frequency = quantity(frequencyQ)
82
+ const buffer = randomBytes(bytes)
83
+
84
+ const delay = Math.max((1000 / frequency), 1)
85
+
86
+ const emit = async () => {
87
+ await this.io.emit(queue, buffer)
88
+
89
+ this.eventsPublishedCount++
90
+ }
91
+
92
+ this.publishing = setInterval(emit, delay)
93
+
94
+ await timeout(delay * 2) // send at least twice
95
+ })
96
+
97
+ Then('all events have been received',
98
+ /**
99
+ * @this {comq.features.Context}
100
+ */
101
+ async function () {
102
+ clearInterval(this.publishing)
103
+
104
+ await timeout(50)
105
+
106
+ assert.equal(this.eventsPublishedCount, this.eventsConsumedCount, 'Not all events has been consumed')
107
+ })
108
+
71
109
  /**
72
110
  * @this {comq.features.Context} context
73
111
  * @param {string} group
@@ -79,6 +117,7 @@ async function consume (group, exchange) {
79
117
 
80
118
  return this.io.consume(exchange, group, async (payload) => {
81
119
  this.consumed[group] = payload
120
+ this.eventsConsumedCount++
82
121
  })
83
122
  }
84
123
 
@@ -3,7 +3,7 @@
3
3
  const assert = require('node:assert')
4
4
  const { randomBytes } = require('node:crypto')
5
5
  const { parse } = require('@toa.io/yaml')
6
- const { match, timeout } = require('@toa.io/generic')
6
+ const { match, timeout, quantity } = require('@toa.io/generic')
7
7
  const { Given, When, Then } = require('@cucumber/cucumber')
8
8
 
9
9
  Given('function replying {token} queue:',
@@ -19,6 +19,21 @@ Given('function replying {token} queue:',
19
19
  await this.io.reply(queue, producer)
20
20
  })
21
21
 
22
+ Given('a producer replying {token} queue',
23
+ /**
24
+ * @param {string} queue
25
+ * @this {comq.features.Context}
26
+ */
27
+ async function (queue) {
28
+ const producer = async (request) => {
29
+ await timeout(10)
30
+
31
+ return request
32
+ }
33
+
34
+ await this.io.reply(queue, producer)
35
+ })
36
+
22
37
  Given('function replying {token} queue is expected:',
23
38
  /**
24
39
  * @param {string} queue
@@ -68,6 +83,14 @@ Then('the consumer receives the reply:',
68
83
  assert.equal(matches, true, 'Reply mismatch')
69
84
  })
70
85
 
86
+ Then('the consumer receives the reply',
87
+ /**
88
+ * @this {comq.features.Context}
89
+ */
90
+ async function () {
91
+ await this.reply
92
+ })
93
+
71
94
  Then('the consumer does not receive the reply',
72
95
  /**
73
96
  * @this {comq.features.Context}
@@ -83,6 +106,40 @@ Then('the consumer does not receive the reply',
83
106
  assert.equal(reply, undefined, 'The reply was received')
84
107
  })
85
108
 
109
+ Given('I\'m sending {quantity}B requests to the {token} queue at {quantity}Hz',
110
+ /**
111
+ * @param {string} bytesQ
112
+ * @param {string} queue
113
+ * @param {string} frequencyQ
114
+ * @this {comq.features.Context}
115
+ */
116
+ async function (bytesQ, queue, frequencyQ) {
117
+ const bytes = quantity(bytesQ)
118
+ const buffer = randomBytes(bytes)
119
+ const frequency = quantity(frequencyQ)
120
+ const delay = Math.max((1000 / frequency), 1)
121
+
122
+ const emit = () => {
123
+ const promise = this.io.request(queue, buffer)
124
+
125
+ this.requestsSent.push(promise)
126
+ }
127
+
128
+ this.sending = setInterval(emit, delay)
129
+
130
+ await timeout(delay) // send at least twice
131
+ })
132
+
133
+ Then('all replies have been received',
134
+ /**
135
+ * @this {comq.features.Context}
136
+ */
137
+ async function () {
138
+ clearInterval(this.sending)
139
+
140
+ await Promise.all(this.requestsSent)
141
+ })
142
+
86
143
  async function send (queue, payload) {
87
144
  if (this.expected) await this.expected
88
145
 
package/lerna.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "useWorkspaces": true,
3
- "version": "0.4.1",
3
+ "version": "0.5.0-dev.0",
4
4
  "conventionalCommits": true
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "comq",
3
- "version": "0.4.1",
3
+ "version": "0.5.0-dev.0",
4
4
  "description": "ComQ",
5
5
  "author": {
6
6
  "name": "Artem Gurtovoi",
@@ -37,15 +37,15 @@
37
37
  "standard": "17.0.0"
38
38
  },
39
39
  "dependencies": {
40
- "@toa.io/generic": "0.4.0-dev.3",
40
+ "@toa.io/generic": "0.4.0-dev.5",
41
41
  "amqplib": "0.10.3",
42
42
  "msgpackr": "1.8.5"
43
43
  },
44
44
  "scripts": {
45
45
  "prepare": "husky install",
46
- "test": "npm run lint && jest",
47
- "test:features": "npx cucumber-js",
48
- "lint": "npx standard --verbose | npx snazzy"
46
+ "test": "standard && jest",
47
+ "test:features": "cucumber-js",
48
+ "lint": "standard --fix --verbose | snazzy"
49
49
  },
50
50
  "standard": {
51
51
  "env": [