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.
- package/.releaserc +1 -8
- package/features/recovery.feature +30 -0
- package/features/silence.feature +46 -0
- package/features/steps/.types/context.d.ts +17 -0
- package/features/steps/backpressure.js +1 -1
- package/features/steps/broker.js +1 -1
- package/features/steps/brokers.js +1 -1
- package/features/steps/common.js +1 -1
- package/features/steps/connection.js +41 -1
- package/features/steps/context.js +12 -2
- package/features/steps/events.js +1 -1
- package/features/steps/hooks.js +1 -0
- package/features/steps/network.js +50 -0
- package/features/steps/networks.js +90 -0
- package/features/steps/parameters.js +6 -0
- package/features/steps/poison.js +1 -1
- package/features/steps/rpc.js +1 -1
- package/features/steps/tasks.js +1 -1
- package/package.json +3 -6
- package/readme.md +13 -2
- package/source/attributes/lazy.test.js +1 -1
- package/source/attributes/track.test.js +3 -3
- package/source/connection.js +92 -11
- package/source/emitter.js +23 -1
- package/source/encoders/index.js +0 -2
- package/source/events.js +1 -1
- package/source/io.js +6 -5
- package/source/shards/channel.js +20 -7
- package/source/shards/connection.js +0 -5
- package/test/amqplib.mock.js +16 -4
- package/test/channel.fixtures.js +1 -1
- package/test/channel.test.js +1 -1
- package/test/connection.test.js +126 -6
- package/test/decode.test.js +0 -13
- package/test/emitter.test.js +71 -0
- package/test/encode.test.js +0 -9
- package/test/encodings.js +1 -1
- package/test/helpers.js +77 -0
- package/test/io.close.test.js +4 -3
- package/test/io.diagnostics.test.js +1 -1
- package/test/io.reply.test.js +2 -2
- package/test/io.request.test.js +16 -2
- package/test/pipeline.test.js +1 -1
- package/test/shards/channel.test.js +63 -9
- package/test/shards/connection.test.js +4 -3
- package/types/encoding.d.ts +1 -2
- package/AGENTS.md +0 -35
- package/CHANGELOG.md +0 -85
- package/source/encoders/msgpack.js +0 -6
package/test/encode.test.js
CHANGED
|
@@ -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
package/test/helpers.js
ADDED
|
@@ -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
|
package/test/io.close.test.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
const { randomBytes } = require('node:crypto')
|
|
4
4
|
const { generate } = require('randomstring')
|
|
5
|
-
const {
|
|
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>} */
|
|
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>} */
|
|
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
|
*/
|
package/test/io.reply.test.js
CHANGED
|
@@ -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/
|
|
197
|
+
const content = encode(request, 'application/json')
|
|
198
198
|
|
|
199
199
|
const properties = {
|
|
200
200
|
replyTo: generate(),
|
|
201
201
|
correlationId: generate(),
|
|
202
|
-
contentType: 'application/
|
|
202
|
+
contentType: 'application/json'
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
const message = /** @type {comq.amqp.Message} */ { content, properties }
|
package/test/io.request.test.js
CHANGED
|
@@ -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('
|
|
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/
|
|
355
|
+
const encodings = ['application/json']
|
|
342
356
|
|
|
343
357
|
it.each(encodings)('should decode %s',
|
|
344
358
|
/**
|
package/test/pipeline.test.js
CHANGED
|
@@ -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('
|
|
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 {
|
|
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 {
|
|
179
|
+
/** @type {Promex[]} */
|
|
154
180
|
const promises = []
|
|
155
181
|
|
|
156
182
|
for (const conn of connections) {
|
|
157
|
-
const promise =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 {
|
|
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 {
|
|
36
|
+
/** @type {Promex[]} */
|
|
36
37
|
const promises = []
|
|
37
38
|
|
|
38
39
|
for (const conn of connections) {
|
|
39
|
-
const promise =
|
|
40
|
+
const promise = new Promex()
|
|
40
41
|
|
|
41
42
|
conn.open.mockImplementation(() => promise)
|
|
42
43
|
promises.push(promise)
|
package/types/encoding.d.ts
CHANGED
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).
|