comq 0.15.6 → 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 +21 -6
- package/source/attributes/lazy.test.js +1 -1
- package/source/attributes/track.test.js +3 -3
- package/source/connection.js +137 -12
- package/source/emitter.js +23 -1
- package/source/encoders/index.js +0 -2
- package/source/events.js +2 -2
- package/source/io.js +6 -5
- package/source/shards/channel.js +34 -17
- 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 +176 -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 +2 -2
- 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 +97 -9
- package/test/shards/connection.test.js +4 -3
- package/types/diagnostic.d.ts +2 -2
- package/types/encoding.d.ts +1 -2
- package/types/io.d.ts +2 -0
- package/AGENTS.md +0 -35
- package/CHANGELOG.md +0 -76
- package/source/encoders/msgpack.js +0 -6
|
@@ -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
|
|
|
@@ -96,6 +97,65 @@ it('should not publish to a lost shard', async () => {
|
|
|
96
97
|
expect(channels[1].send).not.toHaveBeenCalled()
|
|
97
98
|
})
|
|
98
99
|
|
|
100
|
+
it('should wait for a shard to recover when all are lost', async () => {
|
|
101
|
+
channel = await create(connections, type)
|
|
102
|
+
|
|
103
|
+
const channels = await getCreatedChannels()
|
|
104
|
+
const buffer = randomBytes(8)
|
|
105
|
+
const label = generate()
|
|
106
|
+
let sent = false
|
|
107
|
+
|
|
108
|
+
for (const connection of connections) {
|
|
109
|
+
const calls = connection.diagnose.mock.calls.filter((call) => call[0] === 'close')
|
|
110
|
+
|
|
111
|
+
for (const call of calls) call[1](new Error('gone'))
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const sending = channel.send(label, buffer).then(() => { sent = true })
|
|
115
|
+
|
|
116
|
+
await immediate()
|
|
117
|
+
|
|
118
|
+
expect(sent).toStrictEqual(false)
|
|
119
|
+
|
|
120
|
+
for (const chan of channels) expect(chan.send).not.toHaveBeenCalled()
|
|
121
|
+
|
|
122
|
+
const open = connections[0].diagnose.mock.calls.filter((call) => call[0] === 'open')
|
|
123
|
+
const recover = channels[0].diagnose.mock.calls.filter((call) => call[0] === 'recover')
|
|
124
|
+
|
|
125
|
+
for (const call of open) call[1]()
|
|
126
|
+
for (const call of recover) call[1]()
|
|
127
|
+
|
|
128
|
+
await sending
|
|
129
|
+
|
|
130
|
+
expect(channels[0].send).toHaveBeenCalled()
|
|
131
|
+
expect(channels[1].send).not.toHaveBeenCalled()
|
|
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
|
+
|
|
99
159
|
// a lost shard is not benched, so it is used again as soon as it reconnects
|
|
100
160
|
it('should publish to a recovered shard', async () => {
|
|
101
161
|
channel = await create(connections, type)
|
|
@@ -116,11 +176,11 @@ it('should publish to a recovered shard', async () => {
|
|
|
116
176
|
})
|
|
117
177
|
|
|
118
178
|
it('should resolve when one of the connections has created a channel', async () => {
|
|
119
|
-
/** @type {
|
|
179
|
+
/** @type {Promex[]} */
|
|
120
180
|
const promises = []
|
|
121
181
|
|
|
122
182
|
for (const conn of connections) {
|
|
123
|
-
const promise =
|
|
183
|
+
const promise = new Promex()
|
|
124
184
|
|
|
125
185
|
conn.createChannel.mockImplementationOnce(() => promise)
|
|
126
186
|
|
|
@@ -149,7 +209,7 @@ it('should resolve when one of the connections has created a channel', async ()
|
|
|
149
209
|
it('should create failfast channels of given type', async () => {
|
|
150
210
|
await create(connections, type)
|
|
151
211
|
|
|
152
|
-
|
|
212
|
+
connections.forEach((conn, index) => {
|
|
153
213
|
expect(conn.createChannel).toHaveBeenCalledWith(type, index)
|
|
154
214
|
})
|
|
155
215
|
})
|
|
@@ -174,7 +234,7 @@ describe.each(['consume', 'subscribe'])('%s', (method) => {
|
|
|
174
234
|
})
|
|
175
235
|
|
|
176
236
|
it(`should not resolve ${method} until pending channels are created`, async () => {
|
|
177
|
-
const promise =
|
|
237
|
+
const promise = new Promex()
|
|
178
238
|
|
|
179
239
|
connections[0].createChannel.mockImplementationOnce(() => promise)
|
|
180
240
|
|
|
@@ -204,7 +264,7 @@ describe.each(['consume', 'subscribe'])('%s', (method) => {
|
|
|
204
264
|
})
|
|
205
265
|
|
|
206
266
|
it(`should resolve ${method} without a disconnected shard`, async () => {
|
|
207
|
-
const promise =
|
|
267
|
+
const promise = new Promex()
|
|
208
268
|
|
|
209
269
|
connections[0].createChannel.mockImplementationOnce(() => promise)
|
|
210
270
|
connections[0].connected = false
|
|
@@ -226,7 +286,7 @@ describe.each(['consume', 'subscribe'])('%s', (method) => {
|
|
|
226
286
|
})
|
|
227
287
|
|
|
228
288
|
it(`should resolve ${method} when a shard disconnects`, async () => {
|
|
229
|
-
const promise =
|
|
289
|
+
const promise = new Promex()
|
|
230
290
|
|
|
231
291
|
connections[0].createChannel.mockImplementationOnce(() => promise)
|
|
232
292
|
|
|
@@ -379,7 +439,7 @@ describe('seal', () => {
|
|
|
379
439
|
|
|
380
440
|
const channels = await getCreatedChannels()
|
|
381
441
|
const chan = sample(channels)
|
|
382
|
-
const promise =
|
|
442
|
+
const promise = new Promex()
|
|
383
443
|
let sealed = false
|
|
384
444
|
|
|
385
445
|
chan.seal.mockImplementationOnce(() => promise)
|
|
@@ -399,7 +459,7 @@ describe('seal', () => {
|
|
|
399
459
|
|
|
400
460
|
it('should seal pending channel', async () => {
|
|
401
461
|
const connection = sample(connections)
|
|
402
|
-
const promise =
|
|
462
|
+
const promise = new Promex()
|
|
403
463
|
|
|
404
464
|
connection.createChannel.mockImplementationOnce(() => promise)
|
|
405
465
|
|
|
@@ -622,6 +682,34 @@ function emitReturn (channel, message) {
|
|
|
622
682
|
for (const call of calls) call[1](message)
|
|
623
683
|
}
|
|
624
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
|
+
|
|
625
713
|
/**
|
|
626
714
|
* @return {Promise<jest.MockedObject<comq.Channel>[]>}
|
|
627
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/diagnostic.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
declare namespace comq.diagnostics {
|
|
2
2
|
|
|
3
|
-
type Event = 'open' | 'close' | 'error' | '
|
|
4
|
-
'pause' | 'resume' | 'return'
|
|
3
|
+
type Event = 'open' | 'close' | 'error' | 'reconnect' | 'flow' | 'drain' | 'remove' | 'lost' |
|
|
4
|
+
'recover' | 'discard' | 'pause' | 'resume' | 'return'
|
|
5
5
|
|
|
6
6
|
interface Diagnosable {
|
|
7
7
|
diagnose(event: Event, listener: Function): void
|
package/types/encoding.d.ts
CHANGED
package/types/io.d.ts
CHANGED
|
@@ -63,6 +63,8 @@ declare namespace comq {
|
|
|
63
63
|
|
|
64
64
|
diagnose(event: 'error', listener: (error: Error, index?: number) => void): void
|
|
65
65
|
|
|
66
|
+
diagnose(event: 'reconnect', listener: (index?: number) => void): void
|
|
67
|
+
|
|
66
68
|
diagnose(event: 'flow', listener: (channel: _topology.type, index?: number) => void): void
|
|
67
69
|
|
|
68
70
|
diagnose(event: 'drain', listener: (channel: _topology.type, index?: number) => void): void
|
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,76 +0,0 @@
|
|
|
1
|
-
## [0.15.6](https://github.com/toa-io/comq/compare/v0.15.5...v0.15.6) (2026-08-26)
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
### Bug Fixes
|
|
5
|
-
|
|
6
|
-
* do not report shards as lost on shutdown ([abbe0e8](https://github.com/toa-io/comq/commit/abbe0e8e1048509ff2c981b7aa33659c0829e821))
|
|
7
|
-
* 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))
|
|
8
|
-
|
|
9
|
-
## [0.15.5](https://github.com/toa-io/comq/compare/v0.15.4...v0.15.5) (2026-08-25)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
### Bug Fixes
|
|
13
|
-
|
|
14
|
-
* recover from a lost connection reliably ([59b1fab](https://github.com/toa-io/comq/commit/59b1fab61d4f27ab6eb608693da6493b5b1f4ae5))
|
|
15
|
-
* recover from a lost connection reliably ([#247](https://github.com/toa-io/comq/issues/247)) ([e4701cf](https://github.com/toa-io/comq/commit/e4701cfa384d5451c15d0a6bf53c82ce1da91ac5))
|
|
16
|
-
|
|
17
|
-
## [0.15.4](https://github.com/toa-io/comq/compare/v0.15.3...v0.15.4) (2026-08-16)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
### Bug Fixes
|
|
21
|
-
|
|
22
|
-
* remove request() timeout ([#246](https://github.com/toa-io/comq/issues/246)) ([d432388](https://github.com/toa-io/comq/commit/d4323889bcad867a870d0ed351b92098f6aad551))
|
|
23
|
-
|
|
24
|
-
## [0.15.3](https://github.com/toa-io/comq/compare/v0.15.2...v0.15.3) (2026-08-04)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
### Bug Fixes
|
|
28
|
-
|
|
29
|
-
* re-send requests left unanswered by a lost shard ([c534a01](https://github.com/toa-io/comq/commit/c534a0136e5f0189cdb6976cd9cffb9a2ca5a148))
|
|
30
|
-
* 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))
|
|
31
|
-
|
|
32
|
-
## [0.15.2](https://github.com/toa-io/comq/compare/v0.15.1...v0.15.2) (2026-08-04)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
### Bug Fixes
|
|
36
|
-
|
|
37
|
-
* report and recover undeliverable replies ([b2d2d0f](https://github.com/toa-io/comq/commit/b2d2d0f11d7714c2d32d3d85894fa78e2203f8c6))
|
|
38
|
-
* report and recover undeliverable replies ([#241](https://github.com/toa-io/comq/issues/241)) ([73e5592](https://github.com/toa-io/comq/commit/73e5592762634fbead29d825be68a3da3136729b))
|
|
39
|
-
|
|
40
|
-
## [0.15.1](https://github.com/toa-io/comq/compare/v0.15.0...v0.15.1) (2026-08-02)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
### Bug Fixes
|
|
44
|
-
|
|
45
|
-
* 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)
|
|
46
|
-
* 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))
|
|
47
|
-
|
|
48
|
-
# [0.15.0](https://github.com/toa-io/comq/compare/v0.14.0...v0.15.0) (2026-08-01)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
### Bug Fixes
|
|
52
|
-
|
|
53
|
-
* 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)
|
|
54
|
-
* destroy half-open connections via socket idle watchdog ([9f7ed5f](https://github.com/toa-io/comq/commit/9f7ed5fe50e9ab1857547a6e908185b6b472162f))
|
|
55
|
-
* ignore close events from replaced AMQP connections ([c1070ab](https://github.com/toa-io/comq/commit/c1070abeca9eb30a1c0ed07925bc06e530c1d9ce))
|
|
56
|
-
* reconnect hang after broker restart ([#235](https://github.com/toa-io/comq/issues/235)) ([be07079](https://github.com/toa-io/comq/commit/be07079863194301d0279e2bdd551260196ce20a))
|
|
57
|
-
* 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)
|
|
58
|
-
* tolerate empty YAML documents after js-yaml 5 bump ([0a05ecb](https://github.com/toa-io/comq/commit/0a05ecbf744822b3aa884382642ce51a93922dfc))
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
### Features
|
|
62
|
-
|
|
63
|
-
* allow request() to time out pending replies ([ac9e2bd](https://github.com/toa-io/comq/commit/ac9e2bd7eb0834d19d404c84f24548bd5475017f))
|
|
64
|
-
* tag container-manipulation features [@heavy](https://github.com/heavy) and add features:fast script ([c0f52ec](https://github.com/toa-io/comq/commit/c0f52ecf72c010e387e853b3daed6884c1cef474))
|
|
65
|
-
|
|
66
|
-
# [0.14.0](https://github.com/toa-io/comq/compare/v0.13.0...v0.14.0) (2026-05-24)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
### Features
|
|
70
|
-
|
|
71
|
-
* **features:** manage RabbitMQ brokers with Testcontainers ([1d9ca59](https://github.com/toa-io/comq/commit/1d9ca591ff458c75caf87ab63224b15942ba7d07))
|
|
72
|
-
* **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))
|
|
73
|
-
|
|
74
|
-
# Changelog
|
|
75
|
-
|
|
76
|
-
All notable changes to this project are documented in this file by [semantic-release](https://github.com/semantic-release/semantic-release).
|