comq 0.15.7 → 0.16.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 (56) hide show
  1. package/.releaserc +15 -6
  2. package/features/channels.feature +14 -0
  3. package/features/recovery.feature +30 -0
  4. package/features/silence.feature +46 -0
  5. package/features/steps/.types/context.d.ts +17 -0
  6. package/features/steps/backpressure.js +1 -1
  7. package/features/steps/broker.js +1 -1
  8. package/features/steps/brokers.js +1 -1
  9. package/features/steps/channels.js +45 -0
  10. package/features/steps/common.js +1 -1
  11. package/features/steps/connection.js +41 -1
  12. package/features/steps/context.js +24 -4
  13. package/features/steps/events.js +1 -1
  14. package/features/steps/hooks.js +2 -0
  15. package/features/steps/network.js +50 -0
  16. package/features/steps/networks.js +90 -0
  17. package/features/steps/parameters.js +6 -0
  18. package/features/steps/poison.js +1 -1
  19. package/features/steps/rpc.js +1 -1
  20. package/features/steps/tasks.js +1 -1
  21. package/package.json +8 -9
  22. package/readme.md +17 -2
  23. package/source/attributes/lazy.test.js +1 -1
  24. package/source/attributes/track.test.js +3 -3
  25. package/source/channel.js +21 -0
  26. package/source/connection.js +112 -12
  27. package/source/emitter.js +23 -1
  28. package/source/encoders/index.js +0 -2
  29. package/source/events.js +2 -2
  30. package/source/io.js +12 -5
  31. package/source/shards/channel.js +28 -7
  32. package/source/shards/connection.js +0 -5
  33. package/test/amqplib.mock.js +20 -4
  34. package/test/channel.fixtures.js +1 -1
  35. package/test/channel.test.js +44 -1
  36. package/test/connection.mock.js +2 -0
  37. package/test/connection.test.js +226 -6
  38. package/test/decode.test.js +0 -13
  39. package/test/emitter.test.js +71 -0
  40. package/test/encode.test.js +0 -9
  41. package/test/encodings.js +1 -1
  42. package/test/helpers.js +77 -0
  43. package/test/io.close.test.js +25 -3
  44. package/test/io.diagnostics.test.js +2 -2
  45. package/test/io.reply.test.js +2 -2
  46. package/test/io.request.test.js +16 -2
  47. package/test/pipeline.test.js +1 -1
  48. package/test/shards/channel.test.js +63 -9
  49. package/test/shards/connection.test.js +4 -3
  50. package/types/channel.d.ts +4 -0
  51. package/types/diagnostic.d.ts +2 -2
  52. package/types/encoding.d.ts +1 -2
  53. package/types/io.d.ts +2 -0
  54. package/AGENTS.md +0 -35
  55. package/CHANGELOG.md +0 -85
  56. package/source/encoders/msgpack.js +0 -6
package/.releaserc CHANGED
@@ -3,12 +3,22 @@
3
3
  "release"
4
4
  ],
5
5
  "plugins": [
6
- "@semantic-release/commit-analyzer",
7
- "@semantic-release/release-notes-generator",
8
6
  [
9
- "@semantic-release/changelog",
7
+ "@semantic-release/commit-analyzer",
10
8
  {
11
- "changelogFile": "CHANGELOG.md"
9
+ "preset": "conventionalcommits",
10
+ "releaseRules": [
11
+ {
12
+ "breaking": true,
13
+ "release": "minor"
14
+ }
15
+ ]
16
+ }
17
+ ],
18
+ [
19
+ "@semantic-release/release-notes-generator",
20
+ {
21
+ "preset": "conventionalcommits"
12
22
  }
13
23
  ],
14
24
  [
@@ -19,8 +29,7 @@
19
29
  {
20
30
  "assets": [
21
31
  "package.json",
22
- "package-lock.json",
23
- "CHANGELOG.md"
32
+ "package-lock.json"
24
33
  ],
25
34
  "message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
26
35
  }
@@ -0,0 +1,14 @@
1
+ Feature: Channel exhaustion
2
+
3
+ A connection allocates channels from a range the broker negotiates, and once the range
4
+ is spent there is nothing a reconnection can do about it. The refusal is reported and
5
+ passed on rather than retried, so that whoever asked for a channel is told instead of
6
+ waiting for a recovery that is not coming.
7
+
8
+ Scenario: Reporting a connection with no channel left to allocate
9
+ Given the AMQP channel limit is set to 2
10
+ And an active connection to the broker
11
+ And a producer replying `echo` queue
12
+ When emitting an event to the `exhaustion` exchange is attempted
13
+ Then the exception is thrown: "No channels left to allocate"
14
+ And the `exhausted` event is emitted
@@ -84,6 +84,36 @@ Feature: Request-Reply Topology Recovery
84
84
  5
85
85
  """
86
86
 
87
+ Scenario: Sending another request while the broker is down
88
+
89
+ The reply queue is already being consumed from, so the request is published as soon as the
90
+ request channel recovers, which is before the reply queue has been declared again
91
+
92
+ Given function replying `add_numbers` queue:
93
+ """
94
+ ({ a, b }) => { return a + b }
95
+ """
96
+ When the consumer sends the following request to the `add_numbers` queue:
97
+ """yaml
98
+ a: 1
99
+ b: 2
100
+ """
101
+ Then the consumer receives the reply:
102
+ """yaml
103
+ 3
104
+ """
105
+ When the broker has crashed
106
+ And the consumer sends the following request to the `add_numbers` queue:
107
+ """yaml
108
+ a: 2
109
+ b: 3
110
+ """
111
+ Then the broker is up
112
+ And the consumer receives the reply:
113
+ """yaml
114
+ 5
115
+ """
116
+
87
117
  Scenario: Defining emitter while the broker is down
88
118
  Given the broker is down
89
119
  And `logger` consuming events from the `numbers_added` exchange is expected
@@ -0,0 +1,46 @@
1
+ Feature: Silent Connection Tolerance
2
+
3
+ A connection that has gone silent is what a machine that woke from sleep is
4
+ left with: the socket is open, neither the broker nor the operating system
5
+ reports anything, and nothing but the watchdog tells it from an idle connection.
6
+
7
+ Background:
8
+ Given watchdog interval is set to 2000ms with 60s AMQP heartbeat
9
+ And a network that can go silent
10
+
11
+ Scenario: Restoring a connection that has gone silent
12
+ Given an active connection to the broker
13
+ And a producer replying `echo` queue
14
+ When the network goes silent
15
+ Then the connection is lost within 10 seconds
16
+ When the consumer sends a request to the `echo` queue
17
+ Then the consumer receives the reply
18
+
19
+ Scenario: Restoring a sharded connection whose every shard has gone silent
20
+ Given an active sharded connection
21
+ And a producer replying `echo` queue
22
+ When the network goes silent
23
+ Then the connection is lost within 10 seconds
24
+ When the consumer sends a request to the `echo` queue
25
+ Then the consumer receives the reply
26
+
27
+ Scenario: Answering a request sent as the connection goes silent
28
+ Given an active connection to the broker
29
+ And a producer replying `echo` queue
30
+ When the consumer sends a request to the `echo` queue as the network goes silent
31
+ Then the consumer receives the reply
32
+ And the connection is lost within 10 seconds
33
+
34
+ Scenario: Answering a request sent as every shard goes silent
35
+ Given an active sharded connection
36
+ And a producer replying `echo` queue
37
+ When the consumer sends a request to the `echo` queue as the network goes silent
38
+ Then the consumer receives the reply
39
+ And the connection is lost within 10 seconds
40
+
41
+ Scenario: Reporting that there is nowhere left to publish
42
+ Given an active sharded connection
43
+ And a producer replying `echo` queue
44
+ When the network goes silent
45
+ Then publishing is paused within 10 seconds
46
+ And publishing is resumed within 10 seconds
@@ -1,4 +1,5 @@
1
1
  import { Readable } from 'node:stream'
2
+ import { Socket } from 'node:net'
2
3
  import * as _diagnostics from '../../../types/diagnostic'
3
4
  import * as _io from '../../../types/io'
4
5
  import * as _amqp from '../../../types/amqp'
@@ -34,10 +35,26 @@ declare namespace comq.features {
34
35
  streamsValues: Record<number, any[]>
35
36
  streamsEnded: Record<number, boolean>
36
37
  generatorDestroyed: boolean
38
+ networks: Network[]
37
39
 
38
40
  connect(user?: string, password?: string): Promise<void>
39
41
  assert(user?: string, password?: string): Promise<void>
40
42
  disconnect(): Promise<void>
43
+ unplug(): Promise<void>
44
+ }
45
+
46
+ interface Network {
47
+ readonly address: string
48
+
49
+ open(): Promise<void>
50
+ silence(): void
51
+ close(): Promise<void>
52
+ }
53
+
54
+ interface Tunnel {
55
+ client: Socket
56
+ upstream: Socket
57
+ silent: boolean
41
58
  }
42
59
 
43
60
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  const assert = require('node:assert')
4
4
  const { randomBytes } = require('node:crypto')
5
- const { quantity, timeout } = require('@toa.io/generic')
5
+ const { quantity, timeout } = require('../../test/helpers')
6
6
 
7
7
  const { When } = require('@cucumber/cucumber')
8
8
 
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { random } = require('@toa.io/generic')
3
+ const { random } = require('../../test/helpers')
4
4
 
5
5
  const { Given } = require('@cucumber/cucumber')
6
6
  const { BROKERS_AMOUNT, actions } = require('./brokers')
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { execFile } = require('node:child_process')
4
4
  const { promisify } = require('node:util')
5
- const { timeout } = require('@toa.io/generic')
5
+ const { timeout } = require('../../test/helpers')
6
6
  const { RabbitMQContainer } = require('@testcontainers/rabbitmq')
7
7
  const { getContainerRuntimeClient } = require('testcontainers')
8
8
 
@@ -0,0 +1,45 @@
1
+ 'use strict'
2
+
3
+ const assert = require('node:assert')
4
+ const { randomBytes } = require('node:crypto')
5
+
6
+ const { Given, When, Then } = require('@cucumber/cucumber')
7
+
8
+ Given('the AMQP channel limit is set to {number}',
9
+ /**
10
+ * @param {number} limit
11
+ */
12
+ function (limit) {
13
+ global.COMQ_TESTING_AMQP_CHANNEL_MAX = limit
14
+ })
15
+
16
+ When('emitting an event to the {token} exchange is attempted',
17
+ /**
18
+ * @param {string} exchange
19
+ * @this {comq.features.Context}
20
+ */
21
+ async function (exchange) {
22
+ this.exception = undefined
23
+
24
+ await this.io.emit(exchange, randomBytes(8))
25
+ .catch((exception) => { this.exception = exception })
26
+ })
27
+
28
+ Then('the exception is thrown: {string}',
29
+ /**
30
+ * @param {string} message
31
+ * @this {comq.features.Context}
32
+ */
33
+ function (message) {
34
+ assert.notEqual(this.exception, undefined, 'no exception was thrown')
35
+ assert.equal(this.exception.message, message)
36
+ })
37
+
38
+ Then('the {token} event is emitted',
39
+ /**
40
+ * @param {comq.diagnostics.Event} event
41
+ * @this {comq.features.Context}
42
+ */
43
+ function (event) {
44
+ assert.equal(this.events[event], true, `the '${event}' event was not emitted`)
45
+ })
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { When } = require('@cucumber/cucumber')
4
- const { timeout } = require('@toa.io/generic')
4
+ const { timeout } = require('../../test/helpers')
5
5
 
6
6
  When('after {number}ms',
7
7
  /**
@@ -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 } = require('../../test/helpers')
5
5
 
6
6
  const { Given, When, Then } = require('@cucumber/cucumber')
7
7
 
@@ -167,6 +167,27 @@ Then('the connection is {connection-event}',
167
167
  assert.equal(this.events[event], true, 'connection was not ' + key)
168
168
  })
169
169
 
170
+ Then('the connection is lost within {number} second(s)',
171
+ /**
172
+ * @param {number} seconds
173
+ * @this {comq.features.Context}
174
+ */
175
+ async function (seconds) {
176
+ await emitted.call(this, 'close', seconds, 'connection was not lost')
177
+ })
178
+
179
+ Then('publishing is {paused-event} within {number} second(s)',
180
+ /**
181
+ * @param {'paused' | 'resumed'} key
182
+ * @param {number} seconds
183
+ * @this {comq.features.Context}
184
+ */
185
+ async function (key, seconds) {
186
+ const event = PUBLISHING_EVENTS[key]
187
+
188
+ await emitted.call(this, event, seconds, 'publishing was not ' + key)
189
+ })
190
+
170
191
  Given('the connection has started sealing',
171
192
  /**
172
193
  * @this {comq.features.Context}
@@ -197,6 +218,25 @@ const connect = async (context, user, password) => {
197
218
  }
198
219
  }
199
220
 
221
+ /**
222
+ * @param {comq.diagnostics.Event} event
223
+ * @param {number} seconds
224
+ * @param {string} message
225
+ * @this {comq.features.Context}
226
+ */
227
+ async function emitted (event, seconds, message) {
228
+ const deadline = Date.now() + seconds * 1000
229
+
230
+ while (this.events[event] !== true && Date.now() < deadline) await timeout(50)
231
+
232
+ assert.equal(this.events[event], true, message)
233
+ }
234
+
235
+ const PUBLISHING_EVENTS = {
236
+ paused: 'pause',
237
+ resumed: 'resume'
238
+ }
239
+
200
240
  const CONNECTION_EVENTS = {
201
241
  lost: 'close',
202
242
  restored: 'open'
@@ -34,6 +34,9 @@ class Context extends World {
34
34
  streamsEnded = {}
35
35
  generatorDestroyed = false
36
36
 
37
+ /** @type {comq.features.Network[]} */
38
+ networks = []
39
+
37
40
  async connect (user, password) {
38
41
  const urls = this.#urls(user, password)
39
42
 
@@ -46,6 +49,12 @@ class Context extends World {
46
49
  await this.#connect(urls, assert)
47
50
  }
48
51
 
52
+ async unplug () {
53
+ await Promise.all(this.networks.map((network) => network.close()))
54
+
55
+ this.networks = []
56
+ }
57
+
49
58
  async disconnect () {
50
59
  if (this.io === undefined) return
51
60
 
@@ -89,18 +98,29 @@ class Context extends World {
89
98
  }
90
99
 
91
100
  #url (i, user, password) {
92
- const url = PROTOCOL + user + ':' + password + '@' + getAddress(i)
93
- const heartbeat = global.COMQ_TESTING_AMQP_HEARTBEAT
101
+ const address = this.networks[i]?.address ?? getAddress(i)
102
+ const url = PROTOCOL + user + ':' + password + '@' + address
103
+ const query = []
94
104
 
95
105
  // the watchdog measures silence, so it may only be shortened along with the
96
106
  // interval at which a healthy broker is expected to say something
97
- return heartbeat === undefined ? url : url + '?heartbeat=' + heartbeat
107
+ if (global.COMQ_TESTING_AMQP_HEARTBEAT !== undefined) {
108
+ query.push('heartbeat=' + global.COMQ_TESTING_AMQP_HEARTBEAT)
109
+ }
110
+
111
+ // a broker negotiates the channel limit down to what the client asks for,
112
+ // which is how exhaustion is reached without opening two thousand channels
113
+ if (global.COMQ_TESTING_AMQP_CHANNEL_MAX !== undefined) {
114
+ query.push('channelMax=' + global.COMQ_TESTING_AMQP_CHANNEL_MAX)
115
+ }
116
+
117
+ return query.length === 0 ? url : url + '?' + query.join('&')
98
118
  }
99
119
  }
100
120
 
101
121
  const PROTOCOL = 'amqp://'
102
122
 
103
123
  /** @type {comq.diagnostics.Event[]} */
104
- const EVENTS = ['open', 'close', 'flow', 'discard']
124
+ const EVENTS = ['open', 'close', 'flow', 'discard', 'pause', 'resume', 'exhausted']
105
125
 
106
126
  exports.Context = Context
@@ -3,7 +3,7 @@
3
3
  const stream = require('node:stream')
4
4
  const assert = require('node:assert')
5
5
  const { randomBytes } = require('node:crypto')
6
- const { timeout, quantity, match } = require('@toa.io/generic')
6
+ const { timeout, quantity, match } = require('../../test/helpers')
7
7
  const { parse } = require('./yaml')
8
8
  const { Given, When, Then } = require('@cucumber/cucumber')
9
9
 
@@ -14,12 +14,14 @@ After(
14
14
  async function () {
15
15
  delete global.COMQ_TESTING_WATCHDOG_INTERVAL
16
16
  delete global.COMQ_TESTING_AMQP_HEARTBEAT
17
+ delete global.COMQ_TESTING_AMQP_CHANNEL_MAX
17
18
 
18
19
  await Promise.all(
19
20
  Array.from({ length: BROKERS_AMOUNT }, (_, n) => actions.up(n))
20
21
  )
21
22
 
22
23
  await this.disconnect()
24
+ await this.unplug()
23
25
  })
24
26
 
25
27
  AfterAll(async function () {
@@ -0,0 +1,50 @@
1
+ 'use strict'
2
+
3
+ const { Given, When } = require('@cucumber/cucumber')
4
+ const { BROKERS_AMOUNT } = require('./brokers')
5
+ const { Network } = require('./networks')
6
+
7
+ Given('a network that can go silent',
8
+ /**
9
+ * @this {comq.features.Context}
10
+ */
11
+ async function () {
12
+ for (let n = 0; n < BROKERS_AMOUNT; n++) {
13
+ const network = new Network(n)
14
+
15
+ await network.open()
16
+
17
+ this.networks[n] = network
18
+ }
19
+ })
20
+
21
+ When('the network goes silent',
22
+ /**
23
+ * @this {comq.features.Context}
24
+ */
25
+ function () {
26
+ silence.call(this)
27
+ })
28
+
29
+ // a request that is answered over a connection that is already silent proves
30
+ // nothing, hence the window between the two is left as short as a step boundary
31
+ When('the consumer sends a request to the {token} queue as the network goes silent',
32
+ /**
33
+ * @param {string} queue
34
+ * @this {comq.features.Context}
35
+ */
36
+ function (queue) {
37
+ this.reply = this.io.request(queue, 'hello')
38
+
39
+ silence.call(this)
40
+ })
41
+
42
+ /**
43
+ * @this {comq.features.Context}
44
+ */
45
+ function silence () {
46
+ // only a connection lost from now on counts as lost
47
+ delete this.events.close
48
+
49
+ for (const network of this.networks) network.silence()
50
+ }
@@ -0,0 +1,90 @@
1
+ 'use strict'
2
+
3
+ const net = require('node:net')
4
+ const { getAddress } = require('./brokers')
5
+
6
+ /**
7
+ * A network that can go silent, which is what a machine that woke from sleep is
8
+ * left with: the socket stays open, nothing is delivered over it, and neither
9
+ * the broker nor the operating system reports anything. Stopping, killing or
10
+ * pausing a broker does not reproduce it, since all three end up closing the
11
+ * socket the moment the connection is used.
12
+ */
13
+ class Network {
14
+ /** @type {net.Server} */
15
+ #server
16
+
17
+ /** @type {Set<comq.features.Tunnel>} */
18
+ #tunnels = new Set()
19
+
20
+ /** @type {string} */
21
+ #host
22
+
23
+ /** @type {number} */
24
+ #port
25
+
26
+ /**
27
+ * @param {number} [n] broker index
28
+ */
29
+ constructor (n = 0) {
30
+ const [host, port] = getAddress(n).split(':')
31
+
32
+ this.#host = host
33
+ this.#port = Number(port)
34
+ }
35
+
36
+ get address () {
37
+ return 'localhost:' + this.#server.address().port
38
+ }
39
+
40
+ async open () {
41
+ this.#server = net.createServer(this.#tunnel)
42
+
43
+ await new Promise((resolve) => this.#server.listen(0, '127.0.0.1', resolve))
44
+ }
45
+
46
+ /**
47
+ * Only the tunnels that are already open go silent: a connection established
48
+ * afterwards is forwarded as usual, just as it is once a machine is awake.
49
+ */
50
+ silence () {
51
+ for (const tunnel of this.#tunnels) tunnel.silent = true
52
+ }
53
+
54
+ async close () {
55
+ for (const tunnel of this.#tunnels) this.#collapse(tunnel)
56
+
57
+ await new Promise((resolve) => this.#server.close(resolve))
58
+ }
59
+
60
+ /**
61
+ * @param {net.Socket} client
62
+ */
63
+ #tunnel = (client) => {
64
+ const upstream = net.connect(this.#port, this.#host)
65
+
66
+ /** @type {comq.features.Tunnel} */
67
+ const tunnel = { client, upstream, silent: false }
68
+
69
+ this.#tunnels.add(tunnel)
70
+
71
+ client.on('data', (chunk) => { if (!tunnel.silent) upstream.write(chunk) })
72
+ upstream.on('data', (chunk) => { if (!tunnel.silent) client.write(chunk) })
73
+
74
+ for (const socket of [client, upstream]) {
75
+ socket.on('error', () => this.#collapse(tunnel))
76
+ socket.on('close', () => this.#collapse(tunnel))
77
+ }
78
+ }
79
+
80
+ /**
81
+ * @param {comq.features.Tunnel} tunnel
82
+ */
83
+ #collapse (tunnel) {
84
+ this.#tunnels.delete(tunnel)
85
+ tunnel.client.destroy()
86
+ tunnel.upstream.destroy()
87
+ }
88
+ }
89
+
90
+ exports.Network = Network
@@ -38,6 +38,12 @@ defineParameterType({
38
38
  transformer: (value) => value
39
39
  })
40
40
 
41
+ defineParameterType({
42
+ name: 'paused-event',
43
+ regexp: /(paused|resumed)/,
44
+ transformer: (value) => value
45
+ })
46
+
41
47
  defineParameterType({
42
48
  name: 'message',
43
49
  regexp: /(request|event)/,
@@ -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 } = require('../../test/helpers')
5
5
  const { Then } = require('@cucumber/cucumber')
6
6
 
7
7
  Then('the message is discarded',
@@ -4,7 +4,7 @@ const stream = require('node:stream')
4
4
  const assert = require('node:assert')
5
5
  const { randomBytes } = require('node:crypto')
6
6
  const { parse } = require('./yaml')
7
- const { match, timeout, quantity, random } = require('@toa.io/generic')
7
+ const { match, timeout, quantity, random } = require('../../test/helpers')
8
8
  const { Given, When, Then } = require('@cucumber/cucumber')
9
9
  const { Readable } = require('node:stream')
10
10
  const { actions, BROKERS_AMOUNT } = require('./brokers')
@@ -2,7 +2,7 @@
2
2
 
3
3
  const assert = require('node:assert')
4
4
  const { Given, Then, When } = require('@cucumber/cucumber')
5
- const { quantity, timeout } = require('@toa.io/generic')
5
+ const { quantity, timeout } = require('../../test/helpers')
6
6
  const { randomBytes } = require('node:crypto')
7
7
  const stream = require('node:stream')
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "comq",
3
- "version": "0.15.7",
3
+ "version": "0.16.0",
4
4
  "description": "ComQ",
5
5
  "author": {
6
6
  "name": "Artem Gurtovoi",
@@ -27,30 +27,29 @@
27
27
  "feat": "scripts/feat.js"
28
28
  },
29
29
  "devDependencies": {
30
- "@cucumber/cucumber": "^13.2.0",
31
- "@semantic-release/changelog": "^7.0.0",
30
+ "@cucumber/cucumber": "^13.2.1",
32
31
  "@semantic-release/commit-analyzer": "^13.0.1",
33
- "@semantic-release/git": "^10.0.1",
32
+ "@semantic-release/git": "^11.0.1",
34
33
  "@semantic-release/github": "^12.0.2",
35
34
  "@semantic-release/npm": "^13.1.2",
36
35
  "@semantic-release/release-notes-generator": "^14.1.0",
37
- "@testcontainers/rabbitmq": "^12.0.4",
38
- "@toa.io/generic": "^1.0.0-alpha.208",
36
+ "@testcontainers/rabbitmq": "^12.1.0",
39
37
  "@types/amqplib": "^0.10.8",
40
38
  "@types/jest": "^30.0.0",
39
+ "conventional-changelog-conventionalcommits": "^8.0.0",
41
40
  "fast-glob": "^3.3.3",
42
- "fuzzysort": "3.1.0",
41
+ "fuzzysort": "4.0.2",
43
42
  "husky": "^9.1.7",
44
43
  "jest": "^30.4.2",
45
- "js-yaml": "^5.2.3",
44
+ "js-yaml": "^5.4.1",
46
45
  "randomstring": "^1.3.1",
46
+ "semantic-release": "^25.0.9",
47
47
  "snazzy": "^9.0.0",
48
48
  "standard": "^17.1.2",
49
49
  "testcontainers": "^12.0.4"
50
50
  },
51
51
  "dependencies": {
52
52
  "amqplib": "^2.0.1",
53
- "msgpackr": "^2.0.5",
54
53
  "promex": "^0.1.1",
55
54
  "reretry": "^0.1.0"
56
55
  },