comq 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/dependabot.yml +2 -2
- package/.github/workflows/codeql.yml +1 -1
- package/.github/workflows/release.yaml +16 -15
- package/docs/reply-stream-topology-dark.jpg +0 -0
- package/docs/reply-stream-topology-light.jpg +0 -0
- package/examples/readme.md +19 -3
- package/examples/streams/consumer.js +36 -0
- package/examples/streams/producer.js +44 -0
- package/features/backpressure.feature +2 -2
- package/features/events.feature +5 -0
- package/features/readme.md +2 -2
- package/features/recovery.feature +0 -3
- package/features/rpc.feature +15 -10
- package/features/rpc.streams.feature +81 -0
- package/features/rpc.streams.shards.feature +37 -0
- package/features/steps/.test/context.test.js +1 -1
- package/features/steps/.test/events.test.js +2 -2
- package/features/steps/.test/rpc.test.js +1 -1
- package/features/steps/.types/context.d.ts +9 -1
- package/features/steps/common.js +12 -0
- package/features/steps/context.js +9 -2
- package/features/steps/events.js +29 -0
- package/features/steps/rpc.js +273 -0
- package/lerna.json +1 -2
- package/package.json +2 -2
- package/readme.md +159 -31
- package/source/.io/ReplyPipe.js +158 -0
- package/source/.io/ReplyStream.js +142 -0
- package/source/.io/const.js +10 -0
- package/source/.io/{replies.js → createReplyEmitter.js} +3 -6
- package/source/.io/index.js +5 -1
- package/source/attributes/failsafe.js +34 -0
- package/source/attributes/failsafe.test.js +133 -0
- package/source/attributes/index.js +13 -0
- package/source/attributes/lazy.js +79 -0
- package/source/attributes/lazy.test.js +299 -0
- package/source/attributes/memo.js +13 -0
- package/source/attributes/memo.test.js +36 -0
- package/source/attributes/recall.js +42 -0
- package/source/attributes/recall.test.js +92 -0
- package/source/attributes/track.js +32 -0
- package/source/attributes/track.test.js +133 -0
- package/source/channel.js +33 -18
- package/source/connect.js +2 -2
- package/source/connection.js +4 -11
- package/source/emitter.js +13 -0
- package/source/encode.js +1 -1
- package/source/events.js +3 -3
- package/source/io.js +184 -48
- package/source/pipeline.js +43 -0
- package/source/shards/channel.js +28 -12
- package/source/shards/connection.js +2 -2
- package/test/channel.test.js +9 -1
- package/test/encode.test.js +2 -2
- package/test/encodings.js +1 -1
- package/test/io.diagnostics.test.js +3 -3
- package/test/io.emit.test.js +18 -3
- package/test/io.request.test.js +45 -23
- package/test/pipeline.test.js +81 -0
- package/test/shards/channel.test.js +39 -3
- package/test/shards/connection.test.js +1 -1
- package/types/channel.d.ts +13 -11
- package/types/connection.d.ts +3 -3
- package/types/diagnostic.d.ts +4 -3
- package/types/encoding.d.ts +2 -2
- package/types/index.d.ts +4 -4
- package/types/io.d.ts +40 -32
package/source/shards/channel.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { EventEmitter } = require('node:events')
|
|
4
3
|
const { promex, sample } = require('@toa.io/generic')
|
|
5
4
|
const events = require('../events')
|
|
5
|
+
const emitter = require('../emitter')
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @implements {comq.Channel}
|
|
@@ -30,7 +30,7 @@ class Channel {
|
|
|
30
30
|
|
|
31
31
|
#recovery = promex()
|
|
32
32
|
|
|
33
|
-
#diagnostics =
|
|
33
|
+
#diagnostics = emitter.create()
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* @param {comq.Connection[]} connections
|
|
@@ -48,7 +48,7 @@ class Channel {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
async consume (queue, consumer) {
|
|
51
|
-
await this.#any((channel) => channel.consume(queue, consumer))
|
|
51
|
+
return await this.#any((channel) => channel.consume(queue, consumer))
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
async subscribe (queue, group, consumer) {
|
|
@@ -64,17 +64,22 @@ class Channel {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
async fire (queue, buffer, options) {
|
|
67
|
-
|
|
67
|
+
// noinspection JSValidateTypes
|
|
68
|
+
return await this.#one((channel) => channel.fire(queue, buffer, options))
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
async seal () {
|
|
71
72
|
await this.#all((channel) => channel.seal())
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
diagnose (event, listener) {
|
|
75
76
|
this.#diagnostics.on(event, listener)
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
forget (event, listener) {
|
|
80
|
+
this.#diagnostics.off(event, listener)
|
|
81
|
+
}
|
|
82
|
+
|
|
78
83
|
/**
|
|
79
84
|
* @param {comq.Connection} connection
|
|
80
85
|
* @param {number} index
|
|
@@ -120,7 +125,7 @@ class Channel {
|
|
|
120
125
|
*/
|
|
121
126
|
#add (channel) {
|
|
122
127
|
this.#channels.add(channel)
|
|
123
|
-
this.#
|
|
128
|
+
this.#update()
|
|
124
129
|
}
|
|
125
130
|
|
|
126
131
|
/**
|
|
@@ -131,10 +136,23 @@ class Channel {
|
|
|
131
136
|
|
|
132
137
|
this.#bench.set(channel, promex())
|
|
133
138
|
this.#channels.delete(channel)
|
|
134
|
-
this.#
|
|
139
|
+
this.#update()
|
|
135
140
|
this.#diagnostics.emit('remove', channel.index)
|
|
136
141
|
}
|
|
137
142
|
|
|
143
|
+
#update () {
|
|
144
|
+
const from = this.#pool?.length
|
|
145
|
+
const to = this.#channels.size
|
|
146
|
+
|
|
147
|
+
this.#pool = Array.from(this.#channels)
|
|
148
|
+
|
|
149
|
+
if (from === undefined) return
|
|
150
|
+
|
|
151
|
+
if (from !== 0 && to === 0) { this.#diagnostics.emit('pause') }
|
|
152
|
+
|
|
153
|
+
if (from === 0 && to !== 0) { this.#diagnostics.emit('resume') }
|
|
154
|
+
}
|
|
155
|
+
|
|
138
156
|
/**
|
|
139
157
|
* @param {comq.Channel} channel
|
|
140
158
|
*/
|
|
@@ -154,12 +172,11 @@ class Channel {
|
|
|
154
172
|
|
|
155
173
|
/**
|
|
156
174
|
* @param {(channel: comq.Channel) => void} fn
|
|
157
|
-
* @return {Promise<void>}
|
|
158
175
|
*/
|
|
159
176
|
async #any (fn) {
|
|
160
177
|
const promises = this.#apply(fn)
|
|
161
178
|
|
|
162
|
-
await Promise.any(promises)
|
|
179
|
+
return await Promise.any(promises)
|
|
163
180
|
}
|
|
164
181
|
|
|
165
182
|
/**
|
|
@@ -188,7 +205,6 @@ class Channel {
|
|
|
188
205
|
|
|
189
206
|
/**
|
|
190
207
|
* @param {(channel: comq.Channel) => void} fn
|
|
191
|
-
* @return {Promise<void>}
|
|
192
208
|
*/
|
|
193
209
|
async #one (fn) {
|
|
194
210
|
if (this.#pool.length === 0) await this.#recovery
|
|
@@ -196,11 +212,11 @@ class Channel {
|
|
|
196
212
|
const channel = sample(this.#pool)
|
|
197
213
|
|
|
198
214
|
try {
|
|
199
|
-
await fn(channel)
|
|
215
|
+
return await fn(channel)
|
|
200
216
|
} catch {
|
|
201
217
|
this.#remove(channel)
|
|
202
218
|
|
|
203
|
-
await this.#one(fn)
|
|
219
|
+
return await this.#one(fn)
|
|
204
220
|
}
|
|
205
221
|
}
|
|
206
222
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { EventEmitter } = require('node:events')
|
|
4
3
|
const events = require('../events')
|
|
5
4
|
const channel = require('./channel')
|
|
5
|
+
const emitter = require('../emitter')
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @implements {comq.Connection}
|
|
@@ -11,7 +11,7 @@ class Connection {
|
|
|
11
11
|
/** @type {comq.Connection[]} */
|
|
12
12
|
#connections
|
|
13
13
|
|
|
14
|
-
#diagnostics =
|
|
14
|
+
#diagnostics = emitter.create()
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* @param {comq.Connection[]} connections
|
package/test/channel.test.js
CHANGED
|
@@ -67,7 +67,9 @@ describe('consume', () => {
|
|
|
67
67
|
it('should assert queue', async () => {
|
|
68
68
|
await channel.consume(queue, consumer)
|
|
69
69
|
|
|
70
|
-
const options = topology.durable
|
|
70
|
+
const options = topology.durable
|
|
71
|
+
? { durable: true }
|
|
72
|
+
: { exclusive: true }
|
|
71
73
|
|
|
72
74
|
expect(chan.assertQueue).toHaveBeenCalledWith(queue, options)
|
|
73
75
|
})
|
|
@@ -823,17 +825,23 @@ describe('diagnostics', () => {
|
|
|
823
825
|
|
|
824
826
|
let flowed = false
|
|
825
827
|
let drained = false
|
|
828
|
+
let paused = false
|
|
829
|
+
let resumed = false
|
|
826
830
|
|
|
827
831
|
channel.diagnose('flow', () => (flowed = true))
|
|
828
832
|
channel.diagnose('drain', () => (drained = true))
|
|
833
|
+
channel.diagnose('pause', () => (paused = true))
|
|
834
|
+
channel.diagnose('resume', () => (resumed = true))
|
|
829
835
|
|
|
830
836
|
await channel.publish(exchange, buffer)
|
|
831
837
|
|
|
832
838
|
expect(flowed).toStrictEqual(true)
|
|
839
|
+
expect(paused).toStrictEqual(true)
|
|
833
840
|
|
|
834
841
|
chan.emit('drain')
|
|
835
842
|
|
|
836
843
|
expect(drained).toStrictEqual(true)
|
|
844
|
+
expect(resumed).toStrictEqual(true)
|
|
837
845
|
})
|
|
838
846
|
|
|
839
847
|
it.each(/** @type {[string, boolean][]} */ [
|
package/test/encode.test.js
CHANGED
|
@@ -11,7 +11,7 @@ it('should be', async () => {
|
|
|
11
11
|
|
|
12
12
|
it('should throw if encoding is not supported', async () => {
|
|
13
13
|
const value = 1
|
|
14
|
-
const encoding = /** @type {comq.
|
|
14
|
+
const encoding = /** @type {comq.Encoding} */ 'wtf/' + generate()
|
|
15
15
|
|
|
16
16
|
expect(() => encode(value, encoding)).toThrow('not supported')
|
|
17
17
|
})
|
|
@@ -34,7 +34,7 @@ it('should encode as msgpack', async () => {
|
|
|
34
34
|
expect(buffer).toStrictEqual(packed)
|
|
35
35
|
})
|
|
36
36
|
|
|
37
|
-
/** @type {[comq.
|
|
37
|
+
/** @type {[comq.Encoding, string, Buffer][]} */
|
|
38
38
|
const samples = [
|
|
39
39
|
['text/plain', 'some string', Buffer.from('some string')]
|
|
40
40
|
]
|
package/test/encodings.js
CHANGED
|
@@ -28,9 +28,9 @@ 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'])('should re-emit %s',
|
|
31
|
+
it.each(['flow', 'drain', 'recover', 'discard', 'pause', 'resume'])('should re-emit %s',
|
|
32
32
|
/**
|
|
33
|
-
* @param {comq.diagnostics.
|
|
33
|
+
* @param {comq.diagnostics.Event} event
|
|
34
34
|
*/
|
|
35
35
|
async (event) => {
|
|
36
36
|
const listener = /** @type {Function} */ jest.fn()
|
|
@@ -56,7 +56,7 @@ describe.each(['request', 'reply', 'event'])('%s channel events',
|
|
|
56
56
|
|
|
57
57
|
it.each(['open', 'close'])('should re-emit %s from connection',
|
|
58
58
|
/**
|
|
59
|
-
* @param {comq.diagnostics.
|
|
59
|
+
* @param {comq.diagnostics.Event} event
|
|
60
60
|
*/
|
|
61
61
|
async (event) => {
|
|
62
62
|
const listener = /** @type {Function} */ jest.fn(() => undefined)
|
package/test/io.emit.test.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const stream = require('node:stream')
|
|
3
4
|
const { randomBytes } = require('node:crypto')
|
|
4
5
|
const { generate } = require('randomstring')
|
|
5
6
|
|
|
@@ -18,6 +19,9 @@ let connection
|
|
|
18
19
|
/** @type {jest.MockedObject<comq.Channel>} */
|
|
19
20
|
let events
|
|
20
21
|
|
|
22
|
+
const exchange = generate()
|
|
23
|
+
const payload = generate()
|
|
24
|
+
|
|
21
25
|
beforeEach(async () => {
|
|
22
26
|
jest.clearAllMocks()
|
|
23
27
|
|
|
@@ -33,9 +37,6 @@ it('should be', async () => {
|
|
|
33
37
|
expect(io.emit).toBeDefined()
|
|
34
38
|
})
|
|
35
39
|
|
|
36
|
-
const exchange = generate()
|
|
37
|
-
const payload = generate()
|
|
38
|
-
|
|
39
40
|
it('should create events channel', async () => {
|
|
40
41
|
expect(connection.createChannel).toHaveBeenCalledWith('event')
|
|
41
42
|
expect(events).toBeDefined()
|
|
@@ -49,6 +50,20 @@ it('should publish to an exchange', async () => {
|
|
|
49
50
|
expect(args[0]).toStrictEqual(exchange)
|
|
50
51
|
})
|
|
51
52
|
|
|
53
|
+
it('should publish stream of events', async () => {
|
|
54
|
+
jest.clearAllMocks()
|
|
55
|
+
|
|
56
|
+
function * generate () {
|
|
57
|
+
for (let i = 0; i < 10; i++) yield i
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const input = stream.Readable.from(generate())
|
|
61
|
+
|
|
62
|
+
await io.emit(exchange, input)
|
|
63
|
+
|
|
64
|
+
expect(events.publish).toHaveBeenCalledTimes(10)
|
|
65
|
+
})
|
|
66
|
+
|
|
52
67
|
it('should encode message as msgpack by default', async () => {
|
|
53
68
|
const encoding = 'application/msgpack'
|
|
54
69
|
const buf = encode(payload, encoding)
|
package/test/io.request.test.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// region setup
|
|
4
4
|
|
|
5
|
+
const stream = require('node:stream')
|
|
5
6
|
const { randomBytes } = require('node:crypto')
|
|
6
7
|
const { generate } = require('randomstring')
|
|
7
8
|
const { immediate } = require('@toa.io/generic')
|
|
@@ -88,25 +89,6 @@ describe('send', () => {
|
|
|
88
89
|
expect(properties.replyTo).toMatch(rx)
|
|
89
90
|
})
|
|
90
91
|
|
|
91
|
-
it('should set replyTo using specified formatter', async () => {
|
|
92
|
-
jest.clearAllMocks()
|
|
93
|
-
|
|
94
|
-
/** @type {comq.encoding} */
|
|
95
|
-
const prefix = generate()
|
|
96
|
-
const encoding = 'application/json'
|
|
97
|
-
const formatter = (replyTo) => prefix + replyTo
|
|
98
|
-
|
|
99
|
-
promise = io.request(queue, payload, encoding, formatter)
|
|
100
|
-
|
|
101
|
-
await immediate()
|
|
102
|
-
|
|
103
|
-
call = requests.send.mock.calls[0]
|
|
104
|
-
|
|
105
|
-
const properties = requests.send.mock.calls[0][2]
|
|
106
|
-
|
|
107
|
-
expect(properties.replyTo).toMatch(new RegExp(`${prefix}${queue}..[a-z0-9]+`))
|
|
108
|
-
})
|
|
109
|
-
|
|
110
92
|
it('should consume replyTo', async () => {
|
|
111
93
|
const properties = call[2]
|
|
112
94
|
const queue = replies.consume.mock.calls[0][0]
|
|
@@ -115,7 +97,7 @@ describe('send', () => {
|
|
|
115
97
|
})
|
|
116
98
|
|
|
117
99
|
it('should encode message with msgpack by default', async () => {
|
|
118
|
-
/** @type {comq.
|
|
100
|
+
/** @type {comq.Encoding} */
|
|
119
101
|
const contentType = 'application/msgpack'
|
|
120
102
|
const buffer = encode(payload, contentType)
|
|
121
103
|
|
|
@@ -124,7 +106,7 @@ describe('send', () => {
|
|
|
124
106
|
})
|
|
125
107
|
|
|
126
108
|
it('should throw if encoding is not supported', async () => {
|
|
127
|
-
const encoding = /** @type {comq.
|
|
109
|
+
const encoding = /** @type {comq.Encoding} */ 'wtf/' + generate()
|
|
128
110
|
|
|
129
111
|
await expect(io.request(queue, payload, encoding)).rejects.toThrow('is not supported')
|
|
130
112
|
})
|
|
@@ -152,7 +134,7 @@ describe('send', () => {
|
|
|
152
134
|
|
|
153
135
|
setImmediate(reply)
|
|
154
136
|
|
|
155
|
-
await io.request(queue, payload, /** @type {comq.
|
|
137
|
+
await io.request(queue, payload, /** @type {comq.Encoding} */ encoding)
|
|
156
138
|
|
|
157
139
|
const [, buffer, properties] = requests.send.mock.calls[0]
|
|
158
140
|
|
|
@@ -160,6 +142,33 @@ describe('send', () => {
|
|
|
160
142
|
expect(properties.contentType).toStrictEqual(encoding)
|
|
161
143
|
})
|
|
162
144
|
|
|
145
|
+
it('should send stream of requests', async () => {
|
|
146
|
+
requests.send.mockClear()
|
|
147
|
+
|
|
148
|
+
function * generate () {
|
|
149
|
+
for (let i = 0; i < 10; i++) yield i
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const encoding = 'application/json'
|
|
153
|
+
const replyFormatter = (queue) => queue
|
|
154
|
+
|
|
155
|
+
setTimeout(replyAll, 10) // read the stream to end
|
|
156
|
+
|
|
157
|
+
const input = stream.Readable.from(generate())
|
|
158
|
+
|
|
159
|
+
/** @type {Readable} */
|
|
160
|
+
const output = await io.request(queue, input, encoding, replyFormatter)
|
|
161
|
+
|
|
162
|
+
expect(output).toBeInstanceOf(stream.Readable)
|
|
163
|
+
|
|
164
|
+
const replies = []
|
|
165
|
+
|
|
166
|
+
for await (const reply of output) replies.push(reply)
|
|
167
|
+
|
|
168
|
+
expect(replies.length).toStrictEqual(10)
|
|
169
|
+
expect(requests.send).toHaveBeenCalledTimes(10)
|
|
170
|
+
})
|
|
171
|
+
|
|
163
172
|
it('should resend unanswered Requests', async () => {
|
|
164
173
|
expect(requests.diagnose).toHaveBeenCalledWith('recover', expect.any(Function))
|
|
165
174
|
|
|
@@ -216,7 +225,7 @@ describe('reply', () => {
|
|
|
216
225
|
|
|
217
226
|
it.each(encodings)('should decode %s',
|
|
218
227
|
/**
|
|
219
|
-
* @param {comq.
|
|
228
|
+
* @param {comq.Encoding}contentType
|
|
220
229
|
*/
|
|
221
230
|
async (contentType) => {
|
|
222
231
|
const value = generate()
|
|
@@ -239,6 +248,19 @@ const reply = async (content = randomBytes(8), contentType = undefined) => {
|
|
|
239
248
|
await callback(message)
|
|
240
249
|
}
|
|
241
250
|
|
|
251
|
+
async function replyAll () {
|
|
252
|
+
const callback = replies.consume.mock.calls[0][1]
|
|
253
|
+
|
|
254
|
+
for (let i = 0; i < requests.send.mock.calls.length; i++) {
|
|
255
|
+
const call = requests.send.mock.calls[i]
|
|
256
|
+
const correlationId = call[2].correlationId
|
|
257
|
+
const properties = { correlationId }
|
|
258
|
+
const message = /** @type {comq.amqp.Message} */ { content: randomBytes(8), properties }
|
|
259
|
+
|
|
260
|
+
await callback(message)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
242
264
|
/**
|
|
243
265
|
* @param {comq.topology.type} type
|
|
244
266
|
* @returns {jest.MockedObject<comq.Channel>}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { EventEmitter } = require('node:events')
|
|
4
|
+
const stream = require('node:stream')
|
|
5
|
+
const { pipeline, transform } = require('../source/pipeline')
|
|
6
|
+
const { timeout } = require('@toa.io/generic')
|
|
7
|
+
|
|
8
|
+
/** @type {Channel} */
|
|
9
|
+
let channel
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
channel = new Channel()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('should pipeline', async () => {
|
|
16
|
+
const input = stream.Readable.from([1, 2, 3])
|
|
17
|
+
const pipe = pipeline(input, (x) => x * x, channel)
|
|
18
|
+
const output = []
|
|
19
|
+
|
|
20
|
+
for await (const x of pipe) {
|
|
21
|
+
output.push(x)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
expect(output).toStrictEqual([1, 4, 9])
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should control source', async () => {
|
|
28
|
+
async function * generate () {
|
|
29
|
+
for (let i = 1; i < 4; i++) {
|
|
30
|
+
await timeout(1)
|
|
31
|
+
|
|
32
|
+
yield i
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const input = stream.Readable.from(generate())
|
|
37
|
+
|
|
38
|
+
// eslint-disable-next-line no-void
|
|
39
|
+
void pipeline(input, (x) => x * x, channel)
|
|
40
|
+
|
|
41
|
+
channel.emit('pause')
|
|
42
|
+
|
|
43
|
+
await timeout(0)
|
|
44
|
+
|
|
45
|
+
expect(input.isPaused()).toBe(true)
|
|
46
|
+
|
|
47
|
+
channel.emit('resume')
|
|
48
|
+
|
|
49
|
+
await timeout(0)
|
|
50
|
+
|
|
51
|
+
expect(input.isPaused()).toBe(false)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('should wait for end', async () => {
|
|
55
|
+
async function * generate () {
|
|
56
|
+
for (let i = 1; i < 4; i++) {
|
|
57
|
+
await timeout(1)
|
|
58
|
+
|
|
59
|
+
yield i
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let finished = 0
|
|
64
|
+
|
|
65
|
+
const fn = jest.fn(async () => {
|
|
66
|
+
await timeout(1)
|
|
67
|
+
finished++
|
|
68
|
+
})
|
|
69
|
+
const input = stream.Readable.from(generate())
|
|
70
|
+
|
|
71
|
+
await transform(input, fn, channel)
|
|
72
|
+
|
|
73
|
+
expect(fn).toHaveBeenCalledTimes(3)
|
|
74
|
+
expect(finished).toBe(3)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
class Channel extends EventEmitter {
|
|
78
|
+
diagnose (event, callback) {
|
|
79
|
+
this.on(event, callback)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -187,7 +187,7 @@ describe.each(/** @type {string[]} */['send', 'publish', 'fire'])('%s', (method)
|
|
|
187
187
|
|
|
188
188
|
const last = label + i
|
|
189
189
|
|
|
190
|
-
// now channel has thrown an exception
|
|
190
|
+
// now the channel has thrown an exception
|
|
191
191
|
expect(broken[method]).toHaveBeenCalledWith(last, buffer, options)
|
|
192
192
|
|
|
193
193
|
const r = (b + 1) % 2
|
|
@@ -202,7 +202,7 @@ describe.each(/** @type {string[]} */['send', 'publish', 'fire'])('%s', (method)
|
|
|
202
202
|
/** @type {jest.MockedObject<comq.Channel>} */
|
|
203
203
|
let recovered
|
|
204
204
|
|
|
205
|
-
// both shards
|
|
205
|
+
// both shards reject and will be removed from the pool
|
|
206
206
|
channels[0][method].mockImplementationOnce(reject)
|
|
207
207
|
channels[1][method].mockImplementationOnce(reject)
|
|
208
208
|
|
|
@@ -289,7 +289,7 @@ describe('seal', () => {
|
|
|
289
289
|
})
|
|
290
290
|
|
|
291
291
|
describe('diagnose', () => {
|
|
292
|
-
const events = /** @type {comq.diagnostics.
|
|
292
|
+
const events = /** @type {comq.diagnostics.Event[]} */ ['flow', 'drain', 'recover', 'discard']
|
|
293
293
|
|
|
294
294
|
/** @type {jest.MockedObject<comq.Channel>[]} */
|
|
295
295
|
let channels
|
|
@@ -338,6 +338,42 @@ describe('diagnose', () => {
|
|
|
338
338
|
|
|
339
339
|
expect(listener).toHaveBeenCalledWith(expect.any(Number))
|
|
340
340
|
})
|
|
341
|
+
|
|
342
|
+
it('should emit `pause` and `unpause` events', async () => {
|
|
343
|
+
const queue = generate()
|
|
344
|
+
const buffer = randomBytes(8)
|
|
345
|
+
const pause = /** @type {jest.MockedFunction} */ jest.fn()
|
|
346
|
+
const resume = /** @type {jest.MockedFunction} */ jest.fn()
|
|
347
|
+
|
|
348
|
+
channel.diagnose('pause', pause)
|
|
349
|
+
channel.diagnose('resume', resume)
|
|
350
|
+
|
|
351
|
+
for (const chan of channels) {
|
|
352
|
+
chan.send.mockImplementationOnce(() => {
|
|
353
|
+
expect(pause).not.toHaveBeenCalled()
|
|
354
|
+
throw new Error()
|
|
355
|
+
})
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// recover
|
|
359
|
+
setImmediate(() => {
|
|
360
|
+
for (const chan of channels) {
|
|
361
|
+
// subscriptions to the 'recover' event
|
|
362
|
+
const calls = chan.diagnose.mock.calls.filter(
|
|
363
|
+
(call) => call[0] === 'recover')
|
|
364
|
+
|
|
365
|
+
expect(calls.length).toBeGreaterThan(0)
|
|
366
|
+
|
|
367
|
+
// emit 'recover' event to return shard to the pool
|
|
368
|
+
for (const call of calls) call[1]()
|
|
369
|
+
}
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
await channel.send(queue, buffer)
|
|
373
|
+
|
|
374
|
+
expect(pause).toHaveBeenCalledTimes(1)
|
|
375
|
+
expect(resume).toHaveBeenCalledTimes(1)
|
|
376
|
+
})
|
|
341
377
|
})
|
|
342
378
|
|
|
343
379
|
it('should not throw on recovery (if the "bench" is empty)', async () => {
|
|
@@ -106,7 +106,7 @@ describe('createChannel', () => {
|
|
|
106
106
|
})
|
|
107
107
|
})
|
|
108
108
|
|
|
109
|
-
describe.each(/** @type {comq.diagnostics.
|
|
109
|
+
describe.each(/** @type {comq.diagnostics.Event[]} */ ['open', 'close'])('diagnose %s event',
|
|
110
110
|
(event) => {
|
|
111
111
|
it('should re-emit event', async () => {
|
|
112
112
|
const index = random(connections.length)
|
package/types/channel.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ declare namespace comq {
|
|
|
5
5
|
|
|
6
6
|
namespace channels {
|
|
7
7
|
|
|
8
|
-
type
|
|
8
|
+
type Consumer = (message: Message) => void | Promise<void>
|
|
9
9
|
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -13,26 +13,28 @@ declare namespace comq {
|
|
|
13
13
|
index?: number
|
|
14
14
|
sharded?: boolean
|
|
15
15
|
|
|
16
|
-
create(): Promise<void>
|
|
16
|
+
create (): Promise<void>
|
|
17
17
|
|
|
18
|
-
consume(queue: string, consumer: channels.
|
|
18
|
+
consume (queue: string, consumer: channels.Consumer): Promise<string>
|
|
19
19
|
|
|
20
|
-
subscribe(exchange: string, queue: string, consumer: channels.
|
|
20
|
+
subscribe (exchange: string, queue: string, consumer: channels.Consumer): Promise<void>
|
|
21
21
|
|
|
22
|
-
send(queue: string, buffer: Buffer, options?: Options.Publish): Promise<void>
|
|
22
|
+
send (queue: string, buffer: Buffer, options?: Options.Publish): Promise<void>
|
|
23
23
|
|
|
24
|
-
publish(exchange: string, buffer: Buffer, options?: Options.Publish): Promise<void>
|
|
24
|
+
publish (exchange: string, buffer: Buffer, options?: Options.Publish): Promise<void>
|
|
25
25
|
|
|
26
|
-
fire(queue: string, buffer: Buffer, options?: Options.Publish): Promise<
|
|
26
|
+
fire (queue: string, buffer: Buffer, options?: Options.Publish): Promise<boolean>
|
|
27
27
|
|
|
28
|
-
seal(): Promise<void>
|
|
28
|
+
seal (): Promise<void>
|
|
29
29
|
|
|
30
|
-
diagnose(event: _diagnostics.
|
|
30
|
+
diagnose (event: _diagnostics.Event, listener: Function): void
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
forget (event: _diagnostics.Event, listener: Function): void
|
|
33
|
+
|
|
34
|
+
recover (connection: Connection): Promise<void>
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
export type Channel = comq.Channel
|
|
38
|
-
export type
|
|
40
|
+
export type Consumer = comq.channels.Consumer
|
package/types/connection.d.ts
CHANGED
|
@@ -14,11 +14,11 @@ declare namespace comq {
|
|
|
14
14
|
|
|
15
15
|
createChannel(type: _topology.type, index: number): Promise<_channel.Channel>
|
|
16
16
|
|
|
17
|
-
diagnose(event: _diagnostics.
|
|
17
|
+
diagnose(event: _diagnostics.Event, listener: Function): void
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
type
|
|
20
|
+
type Connect = (...urls: string[]) => Promise<_io.IO>
|
|
21
21
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export type
|
|
24
|
+
export type Connect = comq.Connect
|
package/types/diagnostic.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
declare namespace comq.diagnostics {
|
|
2
2
|
|
|
3
|
-
type
|
|
3
|
+
type Event = 'open' | 'close' | 'flow' | 'drain' | 'remove' | 'recover' | 'discard' |
|
|
4
|
+
'pause' | 'resume' | 'return'
|
|
4
5
|
|
|
5
6
|
interface Diagnosable {
|
|
6
|
-
diagnose(event:
|
|
7
|
+
diagnose(event: Event, listener: Function): void
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
export type
|
|
12
|
+
export type Event = comq.diagnostics.Event
|
|
12
13
|
export type Diagnosable = comq.diagnostics.Diagnosable
|
package/types/encoding.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
declare namespace comq {
|
|
2
2
|
|
|
3
|
-
type
|
|
3
|
+
type Encoding =
|
|
4
4
|
'application/msgpack'
|
|
5
5
|
| 'application/json'
|
|
6
6
|
| 'application/octet-stream'
|
|
@@ -8,4 +8,4 @@ declare namespace comq {
|
|
|
8
8
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export type
|
|
11
|
+
export type Encoding = comq.Encoding
|
package/types/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Connect } from './connection'
|
|
2
2
|
|
|
3
|
-
export type { IO,
|
|
3
|
+
export type { IO, Consumer, Producer } from './io'
|
|
4
4
|
|
|
5
|
-
export const connect:
|
|
6
|
-
export const assert:
|
|
5
|
+
export const connect: Connect
|
|
6
|
+
export const assert: Connect
|