comq 0.15.7 → 0.15.8

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 (49) hide show
  1. package/.releaserc +1 -8
  2. package/features/recovery.feature +30 -0
  3. package/features/silence.feature +46 -0
  4. package/features/steps/.types/context.d.ts +17 -0
  5. package/features/steps/backpressure.js +1 -1
  6. package/features/steps/broker.js +1 -1
  7. package/features/steps/brokers.js +1 -1
  8. package/features/steps/common.js +1 -1
  9. package/features/steps/connection.js +41 -1
  10. package/features/steps/context.js +12 -2
  11. package/features/steps/events.js +1 -1
  12. package/features/steps/hooks.js +1 -0
  13. package/features/steps/network.js +50 -0
  14. package/features/steps/networks.js +90 -0
  15. package/features/steps/parameters.js +6 -0
  16. package/features/steps/poison.js +1 -1
  17. package/features/steps/rpc.js +1 -1
  18. package/features/steps/tasks.js +1 -1
  19. package/package.json +3 -6
  20. package/readme.md +13 -2
  21. package/source/attributes/lazy.test.js +1 -1
  22. package/source/attributes/track.test.js +3 -3
  23. package/source/connection.js +92 -11
  24. package/source/emitter.js +23 -1
  25. package/source/encoders/index.js +0 -2
  26. package/source/events.js +1 -1
  27. package/source/io.js +6 -5
  28. package/source/shards/channel.js +20 -7
  29. package/source/shards/connection.js +0 -5
  30. package/test/amqplib.mock.js +16 -4
  31. package/test/channel.fixtures.js +1 -1
  32. package/test/channel.test.js +1 -1
  33. package/test/connection.test.js +126 -6
  34. package/test/decode.test.js +0 -13
  35. package/test/emitter.test.js +71 -0
  36. package/test/encode.test.js +0 -9
  37. package/test/encodings.js +1 -1
  38. package/test/helpers.js +77 -0
  39. package/test/io.close.test.js +4 -3
  40. package/test/io.diagnostics.test.js +1 -1
  41. package/test/io.reply.test.js +2 -2
  42. package/test/io.request.test.js +16 -2
  43. package/test/pipeline.test.js +1 -1
  44. package/test/shards/channel.test.js +63 -9
  45. package/test/shards/connection.test.js +4 -3
  46. package/types/encoding.d.ts +1 -2
  47. package/AGENTS.md +0 -35
  48. package/CHANGELOG.md +0 -85
  49. package/source/encoders/msgpack.js +0 -6
@@ -1,7 +1,6 @@
1
1
  'use strict'
2
2
 
3
3
  const { generate } = require('randomstring')
4
- const { pack } = require('msgpackr')
5
4
 
6
5
  const { encode } = require('../source/encode')
7
6
 
@@ -26,14 +25,6 @@ it('should encode as json', async () => {
26
25
  expect(buffer).toStrictEqual(expected)
27
26
  })
28
27
 
29
- it('should encode as msgpack', async () => {
30
- const value = { [generate()]: generate() }
31
- const packed = pack(value)
32
- const buffer = encode(value, 'application/msgpack')
33
-
34
- expect(buffer).toStrictEqual(packed)
35
- })
36
-
37
28
  /** @type {[comq.Encoding, string, Buffer][]} */
38
29
  const samples = [
39
30
  ['text/plain', 'some string', Buffer.from('some string')]
package/test/encodings.js CHANGED
@@ -1,4 +1,4 @@
1
1
  'use strict'
2
2
 
3
3
  /** @type {comq.Encoding[]} */
4
- exports.encodings = ['application/msgpack', 'application/json']
4
+ exports.encodings = ['application/json']
@@ -0,0 +1,77 @@
1
+ 'use strict'
2
+
3
+ const timeout = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
4
+
5
+ const immediate = () => new Promise((resolve) => setImmediate(resolve))
6
+
7
+ const random = (max = 100) => Math.floor(Math.random() * max)
8
+
9
+ const flip = () => Math.random() < 0.5
10
+
11
+ const sample = (array) => array[Math.floor(Math.random() * array.length)]
12
+
13
+ const quantity = (input) => {
14
+ const matched = String(input).match(/^(\d+(?:\.\d+)?)([^\d\W]*)$/)
15
+
16
+ if (matched === null) {
17
+ throw new Error(`'${input}' doesn't look like a quantity of something`)
18
+ }
19
+
20
+ const number = +matched[1]
21
+ const suffix = matched[2]
22
+
23
+ if (suffix.length === 0) {
24
+ return number
25
+ }
26
+
27
+ const pair = MULTIPLIERS.find(([unit]) => suffix.startsWith(unit))
28
+
29
+ if (pair === undefined) {
30
+ throw new Error(`'${suffix}' doesn't look like a quantity unit`)
31
+ }
32
+
33
+ return number * pair[1]
34
+ }
35
+
36
+ const match = (reference, candidate) => {
37
+ if (typeof candidate !== typeof reference) {
38
+ return false
39
+ }
40
+
41
+ if (Array.isArray(candidate)) {
42
+ return Array.isArray(reference) &&
43
+ candidate.every((value) => reference.some((item) => match(item, value)))
44
+ }
45
+
46
+ if (typeof candidate === 'object') {
47
+ if (candidate === null) {
48
+ return reference === null
49
+ }
50
+
51
+ if (reference === null) {
52
+ return false
53
+ }
54
+
55
+ return Object.entries(candidate).every(([key, value]) => match(reference[key], value))
56
+ }
57
+
58
+ return reference === candidate
59
+ }
60
+
61
+ const MULTIPLIERS = Object.entries({
62
+ k: 1000,
63
+ Ki: 1024,
64
+ K: 1024,
65
+ Mi: 1024 ** 2,
66
+ M: 1000 ** 2,
67
+ Gi: 1024 ** 3,
68
+ G: 1000 ** 3
69
+ })
70
+
71
+ exports.timeout = timeout
72
+ exports.immediate = immediate
73
+ exports.random = random
74
+ exports.flip = flip
75
+ exports.sample = sample
76
+ exports.quantity = quantity
77
+ exports.match = match
@@ -2,7 +2,8 @@
2
2
 
3
3
  const { randomBytes } = require('node:crypto')
4
4
  const { generate } = require('randomstring')
5
- const { promex, immediate } = require('@toa.io/generic')
5
+ const { Promex } = require('promex')
6
+ const { immediate } = require('./helpers')
6
7
 
7
8
  const mock = require('./connection.mock')
8
9
  const { IO } = require('../source/io')
@@ -93,7 +94,7 @@ describe('close', () => {
93
94
  })
94
95
 
95
96
  it('should wait for event processing completion', async () => {
96
- const promise = /** @type {Promise<void>} */ promex()
97
+ const promise = /** @type {Promise<void>} */ new Promex()
97
98
  const queue = generate()
98
99
  const group = generate()
99
100
  const consumer = jest.fn(async () => promise)
@@ -128,7 +129,7 @@ describe('close', () => {
128
129
  })
129
130
 
130
131
  it('should wait for request processing completion', async () => {
131
- const promise = /** @type {Promise<void>} */ promex()
132
+ const promise = /** @type {Promise<void>} */ new Promex()
132
133
  const producer = jest.fn(async () => promise)
133
134
 
134
135
  await reply(/** @type {Function} */ producer)
@@ -28,7 +28,7 @@ describe.each(['request', 'reply', 'event'])('%s channel events',
28
28
  * @param {comq.topology.type} type
29
29
  */
30
30
  (type) => {
31
- it.each(['flow', 'drain', 'recover', 'discard', 'pause', 'resume'])('should re-emit %s',
31
+ it.each(['flow', 'drain', 'recover', 'discard', 'pause', 'resume', 'lost', 'remove'])('should re-emit %s',
32
32
  /**
33
33
  * @param {comq.diagnostics.Event} event
34
34
  */
@@ -194,12 +194,12 @@ describe('encoding', () => {
194
194
 
195
195
  it('should set octet-stream for Buffer reply', async () => {
196
196
  const request = { [generate()]: generate() }
197
- const content = encode(request, 'application/msgpack')
197
+ const content = encode(request, 'application/json')
198
198
 
199
199
  const properties = {
200
200
  replyTo: generate(),
201
201
  correlationId: generate(),
202
- contentType: 'application/msgpack'
202
+ contentType: 'application/json'
203
203
  }
204
204
 
205
205
  const message = /** @type {comq.amqp.Message} */ { content, properties }
@@ -5,7 +5,7 @@
5
5
  const stream = require('node:stream')
6
6
  const { randomBytes } = require('node:crypto')
7
7
  const { generate } = require('randomstring')
8
- const { immediate } = require('@toa.io/generic')
8
+ const { immediate } = require('./helpers')
9
9
  const { encode } = require('../source/encode')
10
10
 
11
11
  const mock = require('./connection.mock')
@@ -182,6 +182,20 @@ describe('send', () => {
182
182
  expect(requests.send).toHaveBeenCalledTimes(2)
183
183
  })
184
184
 
185
+ // a reply that could not be routed has been dropped by the broker
186
+ it('should resend unanswered Requests when the reply channel recovers', async () => {
187
+ expect(replies.diagnose).toHaveBeenCalledWith('recover', expect.any(Function))
188
+
189
+ const calls = replies.diagnose.mock.calls.filter((call) => call[0] === 'recover')
190
+ const listeners = calls.map((call) => call[1])
191
+
192
+ for (const listener of listeners) listener()
193
+
194
+ await immediate()
195
+
196
+ expect(requests.send).toHaveBeenCalledTimes(2)
197
+ })
198
+
185
199
  it('should resend unanswered Requests on sharded connection', async () => {
186
200
  jest.clearAllMocks()
187
201
 
@@ -338,7 +352,7 @@ describe('reply', () => {
338
352
  expect(output).toStrictEqual(content)
339
353
  })
340
354
 
341
- const encodings = ['application/msgpack', 'application/json']
355
+ const encodings = ['application/json']
342
356
 
343
357
  it.each(encodings)('should decode %s',
344
358
  /**
@@ -3,7 +3,7 @@
3
3
  const { EventEmitter } = require('node:events')
4
4
  const stream = require('node:stream')
5
5
  const { pipeline, transform } = require('../source/pipeline')
6
- const { timeout } = require('@toa.io/generic')
6
+ const { timeout } = require('./helpers')
7
7
 
8
8
  /** @type {Channel} */
9
9
  let channel
@@ -2,7 +2,8 @@
2
2
 
3
3
  const { randomBytes } = require('node:crypto')
4
4
  const { generate } = require('randomstring')
5
- const { promex, sample, immediate, random, each } = require('@toa.io/generic')
5
+ const { Promex } = require('promex')
6
+ const { sample, immediate, random } = require('../helpers')
6
7
 
7
8
  const mock = require('../connection.mock')
8
9
 
@@ -130,6 +131,31 @@ it('should wait for a shard to recover when all are lost', async () => {
130
131
  expect(channels[1].send).not.toHaveBeenCalled()
131
132
  })
132
133
 
134
+ it('should pause when no shard is left to publish to', async () => {
135
+ channel = await create(connections, type)
136
+
137
+ const pause = /** @type {jest.MockedFunction} */ jest.fn()
138
+ const resume = /** @type {jest.MockedFunction} */ jest.fn()
139
+
140
+ channel.diagnose('pause', pause)
141
+ channel.diagnose('resume', resume)
142
+
143
+ lose(connections[0])
144
+
145
+ // the other shard is still reachable
146
+ expect(pause).not.toHaveBeenCalled()
147
+
148
+ lose(connections[1])
149
+
150
+ expect(pause).toHaveBeenCalledTimes(1)
151
+ expect(resume).not.toHaveBeenCalled()
152
+
153
+ restore(connections[1])
154
+
155
+ expect(resume).toHaveBeenCalledTimes(1)
156
+ expect(pause).toHaveBeenCalledTimes(1)
157
+ })
158
+
133
159
  // a lost shard is not benched, so it is used again as soon as it reconnects
134
160
  it('should publish to a recovered shard', async () => {
135
161
  channel = await create(connections, type)
@@ -150,11 +176,11 @@ it('should publish to a recovered shard', async () => {
150
176
  })
151
177
 
152
178
  it('should resolve when one of the connections has created a channel', async () => {
153
- /** @type {toa.generic.Promex[]} */
179
+ /** @type {Promex[]} */
154
180
  const promises = []
155
181
 
156
182
  for (const conn of connections) {
157
- const promise = promex()
183
+ const promise = new Promex()
158
184
 
159
185
  conn.createChannel.mockImplementationOnce(() => promise)
160
186
 
@@ -183,7 +209,7 @@ it('should resolve when one of the connections has created a channel', async ()
183
209
  it('should create failfast channels of given type', async () => {
184
210
  await create(connections, type)
185
211
 
186
- each(connections, (conn, index) => {
212
+ connections.forEach((conn, index) => {
187
213
  expect(conn.createChannel).toHaveBeenCalledWith(type, index)
188
214
  })
189
215
  })
@@ -208,7 +234,7 @@ describe.each(['consume', 'subscribe'])('%s', (method) => {
208
234
  })
209
235
 
210
236
  it(`should not resolve ${method} until pending channels are created`, async () => {
211
- const promise = promex()
237
+ const promise = new Promex()
212
238
 
213
239
  connections[0].createChannel.mockImplementationOnce(() => promise)
214
240
 
@@ -238,7 +264,7 @@ describe.each(['consume', 'subscribe'])('%s', (method) => {
238
264
  })
239
265
 
240
266
  it(`should resolve ${method} without a disconnected shard`, async () => {
241
- const promise = promex()
267
+ const promise = new Promex()
242
268
 
243
269
  connections[0].createChannel.mockImplementationOnce(() => promise)
244
270
  connections[0].connected = false
@@ -260,7 +286,7 @@ describe.each(['consume', 'subscribe'])('%s', (method) => {
260
286
  })
261
287
 
262
288
  it(`should resolve ${method} when a shard disconnects`, async () => {
263
- const promise = promex()
289
+ const promise = new Promex()
264
290
 
265
291
  connections[0].createChannel.mockImplementationOnce(() => promise)
266
292
 
@@ -413,7 +439,7 @@ describe('seal', () => {
413
439
 
414
440
  const channels = await getCreatedChannels()
415
441
  const chan = sample(channels)
416
- const promise = promex()
442
+ const promise = new Promex()
417
443
  let sealed = false
418
444
 
419
445
  chan.seal.mockImplementationOnce(() => promise)
@@ -433,7 +459,7 @@ describe('seal', () => {
433
459
 
434
460
  it('should seal pending channel', async () => {
435
461
  const connection = sample(connections)
436
- const promise = promex()
462
+ const promise = new Promex()
437
463
 
438
464
  connection.createChannel.mockImplementationOnce(() => promise)
439
465
 
@@ -656,6 +682,34 @@ function emitReturn (channel, message) {
656
682
  for (const call of calls) call[1](message)
657
683
  }
658
684
 
685
+ /**
686
+ * @param {jest.MockedObject<comq.Connection>} connection
687
+ * @param {Error} [error]
688
+ */
689
+ function lose (connection, error) {
690
+ emit(connection, 'close', error)
691
+ }
692
+
693
+ /**
694
+ * @param {jest.MockedObject<comq.Connection>} connection
695
+ */
696
+ function restore (connection) {
697
+ emit(connection, 'open')
698
+ }
699
+
700
+ /**
701
+ * @param {jest.MockedObject<comq.Connection>} connection
702
+ * @param {comq.diagnostics.Event} event
703
+ * @param {any[]} args
704
+ */
705
+ function emit (connection, event, ...args) {
706
+ const calls = connection.diagnose.mock.calls.filter((call) => call[0] === event)
707
+
708
+ expect(calls.length).toBeGreaterThan(0)
709
+
710
+ for (const call of calls) call[1](...args)
711
+ }
712
+
659
713
  /**
660
714
  * @return {Promise<jest.MockedObject<comq.Channel>[]>}
661
715
  */
@@ -1,7 +1,8 @@
1
1
  'use strict'
2
2
 
3
3
  const { generate } = require('randomstring')
4
- const { random, promex, immediate } = require('@toa.io/generic')
4
+ const { Promex } = require('promex')
5
+ const { random, immediate } = require('../helpers')
5
6
 
6
7
  const { Connection } = require('../../source/shards')
7
8
 
@@ -32,11 +33,11 @@ describe('open', () => {
32
33
  it('should resolve when all of the connections are established', async () => {
33
34
  expect.assertions(2)
34
35
 
35
- /** @type {toa.generic.Promex[]} */
36
+ /** @type {Promex[]} */
36
37
  const promises = []
37
38
 
38
39
  for (const conn of connections) {
39
- const promise = promex()
40
+ const promise = new Promex()
40
41
 
41
42
  conn.open.mockImplementation(() => promise)
42
43
  promises.push(promise)
@@ -1,8 +1,7 @@
1
1
  declare namespace comq {
2
2
 
3
3
  type Encoding =
4
- 'application/msgpack'
5
- | 'application/json'
4
+ 'application/json'
6
5
  | 'application/octet-stream'
7
6
  | 'text/plain'
8
7
 
package/AGENTS.md DELETED
@@ -1,35 +0,0 @@
1
- # AGENTS.md
2
-
3
- ## Cursor Cloud specific instructions
4
-
5
- ### Project Overview
6
-
7
- ComQ is a Node.js communication library over AMQP (RabbitMQ). It provides RPC, pub/sub, task queues, pipelines, and sharded connections.
8
-
9
- ### Services
10
-
11
- Feature tests start two RabbitMQ instances automatically via Testcontainers (`features/steps/brokers.js`). Credentials: `developer` / `secret`. AMQP ports are assigned dynamically on the host.
12
-
13
- ### Docker
14
-
15
- Feature tests require a running Docker daemon. In nested container environments, start Docker with:
16
-
17
- ```bash
18
- sudo dockerd --exec-opt native.cgroupdriver=cgroupfs &
19
- sudo chmod 666 /var/run/docker.sock
20
- ```
21
-
22
- Do not use `docker compose up` for feature tests — Testcontainers manages broker lifecycle. `docker compose` may fail here because `deploy.resources.limits.memory` requires cgroupv2 memory limits that are unavailable in nested setups.
23
-
24
- ### Commands
25
-
26
- - `npm test` — runs `standard` (lint) + `jest` (300 unit tests, no RabbitMQ required)
27
- - `npm run features` — runs all Cucumber feature tests (starts RabbitMQ via Testcontainers)
28
- - `npm run lint` — runs `standard --fix --verbose | snazzy`
29
-
30
- ### Testing Notes
31
-
32
- - Unit tests (`jest`) use mocks and do NOT require running RabbitMQ.
33
- - Feature tests (`cucumber-js`) start brokers in `BeforeAll` and stop them in `AfterAll`.
34
- - Recovery/connection tests (in `features/connection.feature`, `features/recovery.feature`, `features/shards.feature`) stop/kill broker containers and take 5-15 minutes to complete.
35
- - To run only fast feature tests (no broker stop/start): `npx cucumber-js --fail-fast -t 'not @manual' features/events.feature features/rpc.feature features/tasks.feature features/singleton.feature features/properties.feature features/tasks.shards.feature`
package/CHANGELOG.md DELETED
@@ -1,85 +0,0 @@
1
- ## [0.15.7](https://github.com/toa-io/comq/compare/v0.15.6...v0.15.7) (2026-08-26)
2
-
3
-
4
- ### Bug Fixes
5
-
6
- * wait instead of publishing when every shard is down ([41cd715](https://github.com/toa-io/comq/commit/41cd715ad07c1b141f0eabd23b2d444c60855b65))
7
- * wait instead of publishing when every shard is down ([#250](https://github.com/toa-io/comq/issues/250)) ([968882c](https://github.com/toa-io/comq/commit/968882c342c51b129133ad27d761cfcbfb622fce))
8
- * wait instead of publishing when every shard is down ([#251](https://github.com/toa-io/comq/issues/251)) ([a6cfe8a](https://github.com/toa-io/comq/commit/a6cfe8a75128cb63ad1bbbbf0e1c57cb2fbfdc37)), closes [#250](https://github.com/toa-io/comq/issues/250) [#250](https://github.com/toa-io/comq/issues/250)
9
-
10
- ## [0.15.6](https://github.com/toa-io/comq/compare/v0.15.5...v0.15.6) (2026-08-26)
11
-
12
-
13
- ### Bug Fixes
14
-
15
- * do not report shards as lost on shutdown ([abbe0e8](https://github.com/toa-io/comq/commit/abbe0e8e1048509ff2c981b7aa33659c0829e821))
16
- * do not report shards as lost on shutdown ([#248](https://github.com/toa-io/comq/issues/248)) ([b269a5c](https://github.com/toa-io/comq/commit/b269a5cc78d8ca74036290abec234a1e87056e9c))
17
-
18
- ## [0.15.5](https://github.com/toa-io/comq/compare/v0.15.4...v0.15.5) (2026-08-25)
19
-
20
-
21
- ### Bug Fixes
22
-
23
- * recover from a lost connection reliably ([59b1fab](https://github.com/toa-io/comq/commit/59b1fab61d4f27ab6eb608693da6493b5b1f4ae5))
24
- * recover from a lost connection reliably ([#247](https://github.com/toa-io/comq/issues/247)) ([e4701cf](https://github.com/toa-io/comq/commit/e4701cfa384d5451c15d0a6bf53c82ce1da91ac5))
25
-
26
- ## [0.15.4](https://github.com/toa-io/comq/compare/v0.15.3...v0.15.4) (2026-08-16)
27
-
28
-
29
- ### Bug Fixes
30
-
31
- * remove request() timeout ([#246](https://github.com/toa-io/comq/issues/246)) ([d432388](https://github.com/toa-io/comq/commit/d4323889bcad867a870d0ed351b92098f6aad551))
32
-
33
- ## [0.15.3](https://github.com/toa-io/comq/compare/v0.15.2...v0.15.3) (2026-08-04)
34
-
35
-
36
- ### Bug Fixes
37
-
38
- * re-send requests left unanswered by a lost shard ([c534a01](https://github.com/toa-io/comq/commit/c534a0136e5f0189cdb6976cd9cffb9a2ca5a148))
39
- * re-send requests left unanswered by a lost shard ([#242](https://github.com/toa-io/comq/issues/242)) ([c821da8](https://github.com/toa-io/comq/commit/c821da895049bd78a907c6705477ebe74c160e1b))
40
-
41
- ## [0.15.2](https://github.com/toa-io/comq/compare/v0.15.1...v0.15.2) (2026-08-04)
42
-
43
-
44
- ### Bug Fixes
45
-
46
- * report and recover undeliverable replies ([b2d2d0f](https://github.com/toa-io/comq/commit/b2d2d0f11d7714c2d32d3d85894fa78e2203f8c6))
47
- * report and recover undeliverable replies ([#241](https://github.com/toa-io/comq/issues/241)) ([73e5592](https://github.com/toa-io/comq/commit/73e5592762634fbead29d825be68a3da3136729b))
48
-
49
- ## [0.15.1](https://github.com/toa-io/comq/compare/v0.15.0...v0.15.1) (2026-08-02)
50
-
51
-
52
- ### Bug Fixes
53
-
54
- * skip end control when reply stream buffer overflows ([654e046](https://github.com/toa-io/comq/commit/654e046bdcbfbbbbbb789e5bb032bc16564221d1)), closes [#control](https://github.com/toa-io/comq/issues/control) [#reply](https://github.com/toa-io/comq/issues/reply)
55
- * skip end control when reply stream buffer overflows ([#237](https://github.com/toa-io/comq/issues/237)) ([c34d87f](https://github.com/toa-io/comq/commit/c34d87f6d219ce6c8509d989117eadd55a33ca86))
56
-
57
- # [0.15.0](https://github.com/toa-io/comq/compare/v0.14.0...v0.15.0) (2026-08-01)
58
-
59
-
60
- ### Bug Fixes
61
-
62
- * avoid channel recover deadlock after broker restart ([8eeaf2e](https://github.com/toa-io/comq/commit/8eeaf2e12321be71b57913adb6a8fb04bd24d439)), closes [#recovery](https://github.com/toa-io/comq/issues/recovery)
63
- * destroy half-open connections via socket idle watchdog ([9f7ed5f](https://github.com/toa-io/comq/commit/9f7ed5fe50e9ab1857547a6e908185b6b472162f))
64
- * ignore close events from replaced AMQP connections ([c1070ab](https://github.com/toa-io/comq/commit/c1070abeca9eb30a1c0ed07925bc06e530c1d9ce))
65
- * reconnect hang after broker restart ([#235](https://github.com/toa-io/comq/issues/235)) ([be07079](https://github.com/toa-io/comq/commit/be07079863194301d0279e2bdd551260196ce20a))
66
- * retry transient network errors on initial connect ([c436a20](https://github.com/toa-io/comq/commit/c436a2017a2b52d102afa05bc694c9f22faf2065)), closes [#transient](https://github.com/toa-io/comq/issues/transient)
67
- * tolerate empty YAML documents after js-yaml 5 bump ([0a05ecb](https://github.com/toa-io/comq/commit/0a05ecbf744822b3aa884382642ce51a93922dfc))
68
-
69
-
70
- ### Features
71
-
72
- * allow request() to time out pending replies ([ac9e2bd](https://github.com/toa-io/comq/commit/ac9e2bd7eb0834d19d404c84f24548bd5475017f))
73
- * tag container-manipulation features [@heavy](https://github.com/heavy) and add features:fast script ([c0f52ec](https://github.com/toa-io/comq/commit/c0f52ecf72c010e387e853b3daed6884c1cef474))
74
-
75
- # [0.14.0](https://github.com/toa-io/comq/compare/v0.13.0...v0.14.0) (2026-05-24)
76
-
77
-
78
- ### Features
79
-
80
- * **features:** manage RabbitMQ brokers with Testcontainers ([1d9ca59](https://github.com/toa-io/comq/commit/1d9ca591ff458c75caf87ab63224b15942ba7d07))
81
- * **features:** use Testcontainers for RabbitMQ in Gherkin tests ([#219](https://github.com/toa-io/comq/issues/219)) ([a33e66b](https://github.com/toa-io/comq/commit/a33e66bfc357f0f089c728bc60c08f6f24b2c8d1))
82
-
83
- # Changelog
84
-
85
- All notable changes to this project are documented in this file by [semantic-release](https://github.com/semantic-release/semantic-release).
@@ -1,6 +0,0 @@
1
- 'use strict'
2
-
3
- const { pack, unpack } = require('msgpackr')
4
-
5
- exports.encode = pack
6
- exports.decode = unpack