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
package/.releaserc
CHANGED
|
@@ -5,12 +5,6 @@
|
|
|
5
5
|
"plugins": [
|
|
6
6
|
"@semantic-release/commit-analyzer",
|
|
7
7
|
"@semantic-release/release-notes-generator",
|
|
8
|
-
[
|
|
9
|
-
"@semantic-release/changelog",
|
|
10
|
-
{
|
|
11
|
-
"changelogFile": "CHANGELOG.md"
|
|
12
|
-
}
|
|
13
|
-
],
|
|
14
8
|
[
|
|
15
9
|
"@semantic-release/npm"
|
|
16
10
|
],
|
|
@@ -19,8 +13,7 @@
|
|
|
19
13
|
{
|
|
20
14
|
"assets": [
|
|
21
15
|
"package.json",
|
|
22
|
-
"package-lock.json"
|
|
23
|
-
"CHANGELOG.md"
|
|
16
|
+
"package-lock.json"
|
|
24
17
|
],
|
|
25
18
|
"message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
|
|
26
19
|
}
|
|
@@ -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
|
}
|
package/features/steps/broker.js
CHANGED
|
@@ -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('
|
|
5
|
+
const { timeout } = require('../../test/helpers')
|
|
6
6
|
const { RabbitMQContainer } = require('@testcontainers/rabbitmq')
|
|
7
7
|
const { getContainerRuntimeClient } = require('testcontainers')
|
|
8
8
|
|
package/features/steps/common.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const assert = require('node:assert')
|
|
4
|
-
const { timeout } = require('
|
|
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,7 +98,8 @@ class Context extends World {
|
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
#url (i, user, password) {
|
|
92
|
-
const
|
|
101
|
+
const address = this.networks[i]?.address ?? getAddress(i)
|
|
102
|
+
const url = PROTOCOL + user + ':' + password + '@' + address
|
|
93
103
|
const heartbeat = global.COMQ_TESTING_AMQP_HEARTBEAT
|
|
94
104
|
|
|
95
105
|
// the watchdog measures silence, so it may only be shortened along with the
|
|
@@ -101,6 +111,6 @@ class Context extends World {
|
|
|
101
111
|
const PROTOCOL = 'amqp://'
|
|
102
112
|
|
|
103
113
|
/** @type {comq.diagnostics.Event[]} */
|
|
104
|
-
const EVENTS = ['open', 'close', 'flow', 'discard']
|
|
114
|
+
const EVENTS = ['open', 'close', 'flow', 'discard', 'pause', 'resume']
|
|
105
115
|
|
|
106
116
|
exports.Context = Context
|
package/features/steps/events.js
CHANGED
|
@@ -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('
|
|
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
|
|
package/features/steps/hooks.js
CHANGED
|
@@ -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)/,
|
package/features/steps/poison.js
CHANGED
package/features/steps/rpc.js
CHANGED
|
@@ -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('
|
|
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')
|
package/features/steps/tasks.js
CHANGED
|
@@ -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('
|
|
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.
|
|
3
|
+
"version": "0.15.8",
|
|
4
4
|
"description": "ComQ",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Artem Gurtovoi",
|
|
@@ -27,15 +27,13 @@
|
|
|
27
27
|
"feat": "scripts/feat.js"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@cucumber/cucumber": "^13.2.
|
|
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": "^
|
|
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
36
|
"@testcontainers/rabbitmq": "^12.0.4",
|
|
38
|
-
"@toa.io/generic": "^1.0.0-alpha.208",
|
|
39
37
|
"@types/amqplib": "^0.10.8",
|
|
40
38
|
"@types/jest": "^30.0.0",
|
|
41
39
|
"fast-glob": "^3.3.3",
|
|
@@ -50,7 +48,6 @@
|
|
|
50
48
|
},
|
|
51
49
|
"dependencies": {
|
|
52
50
|
"amqplib": "^2.0.1",
|
|
53
|
-
"msgpackr": "^2.0.5",
|
|
54
51
|
"promex": "^0.1.1",
|
|
55
52
|
"reretry": "^0.1.0"
|
|
56
53
|
},
|
package/readme.md
CHANGED
|
@@ -299,7 +299,6 @@ If the specified encoding format is not supported, an exception will be thrown.
|
|
|
299
299
|
The following encoding formats are supported:
|
|
300
300
|
|
|
301
301
|
- `application/json`
|
|
302
|
-
- `application/msgpack`
|
|
303
302
|
- `application/octet-stream`
|
|
304
303
|
- `text/plain`
|
|
305
304
|
|
|
@@ -314,6 +313,12 @@ the connection is restored.
|
|
|
314
313
|
|
|
315
314
|
When the established connection is lost, it will be automatically restored.
|
|
316
315
|
Reconnection attempts will be made indefinitely, with intervals increasing up to 30 seconds.
|
|
316
|
+
Unless the URL sets one, a 15 second heartbeat is requested, so that a connection that is gone
|
|
317
|
+
without a word, such as after a machine wakes from sleep, is noticed within a minute instead of
|
|
318
|
+
being left to whatever the broker suggests.
|
|
319
|
+
A connection that stays silent for three heartbeats is destroyed regardless of what the broker and
|
|
320
|
+
the operating system have reported, since neither is guaranteed to report anything at all.
|
|
321
|
+
Requesting `heartbeat=0` disables both.
|
|
317
322
|
If the broker rejects the connection, for example, due to access being denied, an exception will be thrown.
|
|
318
323
|
Once reconnected, the topology will be recovered, and any unanswered requests and unconfirmed events will be
|
|
319
324
|
retransmitted.
|
|
@@ -491,11 +496,12 @@ Subscribe to one of the diagnostic events:
|
|
|
491
496
|
Optional [`error`](https://amqp-node.github.io/amqplib/channel_api.html#model_events) is passed
|
|
492
497
|
as an argument.
|
|
493
498
|
- `error`: an attempt to restore the connection has failed. The exception is passed as an argument.
|
|
494
|
-
Attempts continue
|
|
499
|
+
Attempts continue until one succeeds.
|
|
500
|
+
- `reconnect`: an attempt to restore the connection has started. Followed by `open` or `error`.
|
|
495
501
|
- `flow`: back pressure is applied to a channel. [Channel type](./types/topology.d.ts) is passed as
|
|
496
502
|
an argument.
|
|
497
503
|
- `drain`: back pressure is removed from a channel. Channel type is passed.
|
|
498
|
-
- `remove`: channel is removed from the [pool](#sharded-connection).
|
|
504
|
+
- `remove`: channel is removed from the [pool](#sharded-connection), having failed to publish.
|
|
499
505
|
- `lost`: a shard has lost its connection, hence the requests awaiting their replies on it are
|
|
500
506
|
re-sent. Channel type is passed.
|
|
501
507
|
- `recover`: channel's topology is recovered. Channel type is passed.
|
|
@@ -503,7 +509,13 @@ Subscribe to one of the diagnostic events:
|
|
|
503
509
|
exceptions. Channel type,
|
|
504
510
|
raw [amqp message object](https://amqp-node.github.io/amqplib/channel_api.html#channel_consume)
|
|
505
511
|
and the exception are passed as arguments.
|
|
512
|
+
- `return`: message is returned by the broker as unroutable. Channel type and the raw
|
|
513
|
+
[amqp message object](https://amqp-node.github.io/amqplib/channel_api.html#channel_publish) are
|
|
514
|
+
passed as arguments. In the case of a [sharded connection](#sharded-connection), the message is
|
|
515
|
+
reported only once every shard has rejected it.
|
|
506
516
|
- `pause`: channel is paused. Channel type is passed.
|
|
517
|
+
In the case of a [sharded connection](#sharded-connection), it means that there is no shard left
|
|
518
|
+
to publish to, be it because every one of them has rejected a publish or lost its connection.
|
|
507
519
|
- `resume`: channel is resumed. Channel type is passed.
|
|
508
520
|
|
|
509
521
|
In the case of a [sharded connection](#sharded-connection), an additional argument specifying the
|
|
@@ -520,9 +532,12 @@ established, there is no way to capture the initial `open` event.
|
|
|
520
532
|
```javascript
|
|
521
533
|
io.diagnose('flow', (type) => console.log(`Back pressure was applied to the ${type} channel`))
|
|
522
534
|
|
|
523
|
-
io.diagnose('
|
|
524
|
-
io.diagnose('
|
|
525
|
-
io.diagnose('
|
|
535
|
+
io.diagnose('reconnect', (shard) => console.log('AMQP reconnecting', { shard }))
|
|
536
|
+
io.diagnose('open', (shard) => console.log('AMQP connection established', { shard }))
|
|
537
|
+
io.diagnose('close', (error, shard) => console.log('AMQP connection closed', { message: error?.message, shard }))
|
|
538
|
+
io.diagnose('error', (error, shard) => console.log('AMQP connection failed', { message: error.message, shard }))
|
|
539
|
+
io.diagnose('lost', (type, shard) => console.log('AMQP shard lost', { type, shard }))
|
|
540
|
+
io.diagnose('recover', (type, shard) => console.log('AMQP channel recovered', { type, shard }))
|
|
526
541
|
```
|
|
527
542
|
|
|
528
543
|
# Gratitude
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { generate } = require('randomstring')
|
|
4
|
-
const {
|
|
4
|
+
const { Promex } = require('promex')
|
|
5
5
|
|
|
6
6
|
const { track } = require('./track')
|
|
7
7
|
|
|
@@ -35,8 +35,8 @@ let test
|
|
|
35
35
|
beforeEach(() => {
|
|
36
36
|
jest.clearAllMocks()
|
|
37
37
|
|
|
38
|
-
done =
|
|
39
|
-
undone =
|
|
38
|
+
done = new Promex()
|
|
39
|
+
undone = new Promex()
|
|
40
40
|
test = new Test()
|
|
41
41
|
})
|
|
42
42
|
|