comq 0.4.1 → 0.5.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/labels.yaml +15 -0
- package/.github/workflows/labels.yaml +18 -0
- package/.github/workflows/release.yaml +20 -8
- package/docker-compose.yaml +20 -2
- package/docs/notes.md +14 -8
- package/examples/const.js +1 -3
- package/examples/events/consumer.js +5 -6
- package/examples/events/producer.js +3 -3
- package/examples/package.json +3 -0
- package/examples/rpc/consumer.js +3 -3
- package/examples/rpc/producer.js +3 -3
- package/features/connection.feature +3 -0
- package/features/events.feature +1 -1
- package/features/shards.feature +61 -0
- package/features/steps/.test/backpressure.test.js +2 -2
- package/features/steps/.test/broker.test.js +32 -6
- package/features/steps/.test/connection.test.js +105 -1
- package/features/steps/.test/context.mock.js +2 -2
- package/features/steps/.test/context.test.js +13 -5
- package/features/steps/.test/events.test.js +66 -3
- package/features/steps/.test/rpc.test.js +99 -1
- package/features/steps/.types/context.d.ts +11 -1
- package/features/steps/backpressure.js +1 -1
- package/features/steps/broker.js +26 -9
- package/features/steps/connection.js +69 -2
- package/features/steps/context.js +28 -12
- package/features/steps/events.js +41 -2
- package/features/steps/rpc.js +58 -1
- package/lerna.json +1 -1
- package/package.json +5 -5
- package/readme.md +75 -25
- package/source/channel.js +56 -32
- package/source/connect.js +28 -2
- package/source/connection.js +24 -17
- package/source/decode.js +1 -1
- package/source/events.js +7 -0
- package/source/io.js +21 -14
- package/source/shards/channel.js +219 -0
- package/source/shards/connection.js +56 -0
- package/source/shards/index.js +5 -0
- package/test/channel.test.js +240 -145
- package/test/connect.test.js +39 -11
- package/test/connection.mock.js +17 -13
- package/test/connection.test.js +65 -12
- package/test/decode.test.js +5 -5
- package/test/io.close.test.js +2 -2
- package/test/io.consume.test.js +1 -1
- package/test/io.reply.test.js +7 -7
- package/test/io.request.test.js +27 -1
- package/test/presets.test.js +1 -1
- package/test/shards/channel.test.js +367 -0
- package/test/shards/connection.test.js +106 -0
- package/types/amqp.d.ts +3 -1
- package/types/channel.d.ts +8 -5
- package/types/connection.d.ts +3 -1
- package/types/diagnostic.d.ts +1 -1
- package/types/topology.d.ts +4 -0
- /package/source/{presets → topology}/event.json +0 -0
- /package/source/{presets → topology}/index.js +0 -0
- /package/source/{presets → topology}/reply.json +0 -0
- /package/source/{presets → topology}/request.json +0 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { EventEmitter } = require('node:events')
|
|
4
|
+
const { promex, sample } = require('@toa.io/generic')
|
|
5
|
+
const events = require('../events')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @implements {comq.Channel}
|
|
9
|
+
*/
|
|
10
|
+
class Channel {
|
|
11
|
+
sharded = true
|
|
12
|
+
|
|
13
|
+
/** @type {comq.Connection[]} */
|
|
14
|
+
#connections
|
|
15
|
+
|
|
16
|
+
/** @type {Set<comq.Channel>} */
|
|
17
|
+
#channels = new Set()
|
|
18
|
+
|
|
19
|
+
/** @type {comq.Channel[]} */
|
|
20
|
+
#pool
|
|
21
|
+
|
|
22
|
+
/** @type {Set<Promise<comq.Channel>>} */
|
|
23
|
+
#pending = new Set()
|
|
24
|
+
|
|
25
|
+
/** @type {Record<comq.Channel, toa.generic.Promex>} */
|
|
26
|
+
#bench = {}
|
|
27
|
+
|
|
28
|
+
/** @type {comq.topology.type} */
|
|
29
|
+
#type
|
|
30
|
+
|
|
31
|
+
#recovery = promex()
|
|
32
|
+
|
|
33
|
+
#diagnostics = new EventEmitter()
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {comq.Connection[]} connections
|
|
37
|
+
* @param {comq.topology.type} type
|
|
38
|
+
*/
|
|
39
|
+
constructor (connections, type) {
|
|
40
|
+
this.#connections = connections
|
|
41
|
+
this.#type = type
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async create () {
|
|
45
|
+
const promises = this.#connections.map(this.#create)
|
|
46
|
+
|
|
47
|
+
await Promise.any(promises)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async consume (queue, consumer) {
|
|
51
|
+
await this.#any((channel) => channel.consume(queue, consumer))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async subscribe (queue, group, consumer) {
|
|
55
|
+
await this.#any((channel) => channel.subscribe(queue, group, consumer))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async send (queue, buffer, options) {
|
|
59
|
+
await this.#one((channel) => channel.send(queue, buffer, options))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async publish (exchange, buffer, options) {
|
|
63
|
+
await this.#one((channel) => channel.publish(exchange, buffer, options))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async throw (queue, buffer, options) {
|
|
67
|
+
await this.#one((channel) => channel.throw(queue, buffer, options))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async seal () {
|
|
71
|
+
await this.#all((channel) => channel.seal())
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async diagnose (event, listener) {
|
|
75
|
+
this.#diagnostics.on(event, listener)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param {comq.Connection} connection
|
|
80
|
+
* @param {number} index
|
|
81
|
+
* @return {Promise<void>}
|
|
82
|
+
*/
|
|
83
|
+
#create = async (connection, index) => {
|
|
84
|
+
const pending = connection.createChannel(this.#type, index)
|
|
85
|
+
const channel = await this.#pend(pending)
|
|
86
|
+
|
|
87
|
+
this.#add(channel)
|
|
88
|
+
this.#pipe(channel)
|
|
89
|
+
|
|
90
|
+
channel.diagnose('recover', () => this.#recover(channel))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {Promise<comq.Channel>} pending
|
|
95
|
+
* @return {Promise<comq.Channel>}
|
|
96
|
+
*/
|
|
97
|
+
async #pend (pending) {
|
|
98
|
+
this.#pending.add(pending)
|
|
99
|
+
|
|
100
|
+
const channel = await pending
|
|
101
|
+
|
|
102
|
+
this.#pending.delete(pending)
|
|
103
|
+
|
|
104
|
+
return channel
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @param {comq.Channel} channel
|
|
109
|
+
*/
|
|
110
|
+
#pipe (channel) {
|
|
111
|
+
for (const event of events.channel) {
|
|
112
|
+
channel.diagnose(event, (...args) => this.#diagnostics.emit(event, ...args, channel.index))
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @param {comq.Channel} channel
|
|
118
|
+
*/
|
|
119
|
+
#add (channel) {
|
|
120
|
+
this.#channels.add(channel)
|
|
121
|
+
this.#pool = Array.from(this.#channels)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @param {comq.Channel} channel
|
|
126
|
+
*/
|
|
127
|
+
#remove (channel) {
|
|
128
|
+
if (!this.#channels.has(channel)) return
|
|
129
|
+
|
|
130
|
+
this.#diagnostics.emit('remove', channel.index)
|
|
131
|
+
|
|
132
|
+
this.#bench[channel] = promex()
|
|
133
|
+
this.#channels.delete(channel)
|
|
134
|
+
this.#pool = Array.from(this.#channels)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @param {comq.Channel} channel
|
|
139
|
+
*/
|
|
140
|
+
#recover (channel) {
|
|
141
|
+
if (channel in this.#bench) this.#comeback(channel)
|
|
142
|
+
|
|
143
|
+
this.#add(channel)
|
|
144
|
+
|
|
145
|
+
this.#recovery.resolve()
|
|
146
|
+
this.#recovery = promex()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
#comeback (channel) {
|
|
150
|
+
this.#bench[channel].resolve(channel)
|
|
151
|
+
delete this.#bench[channel]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @param {(channel: comq.Channel) => void} fn
|
|
156
|
+
* @return {Promise<void>}
|
|
157
|
+
*/
|
|
158
|
+
async #any (fn) {
|
|
159
|
+
const promises = this.#apply(fn)
|
|
160
|
+
|
|
161
|
+
await Promise.any(promises)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @param {(channel: comq.Channel) => void} fn
|
|
166
|
+
* @return {Promise<void>}
|
|
167
|
+
*/
|
|
168
|
+
async #all (fn) {
|
|
169
|
+
const promises = this.#apply(fn)
|
|
170
|
+
|
|
171
|
+
await Promise.all(promises)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* @param {(channel: comq.Channel) => void} fn
|
|
176
|
+
* @return {Promise<any>[]}
|
|
177
|
+
*/
|
|
178
|
+
#apply (fn) {
|
|
179
|
+
const promises = []
|
|
180
|
+
|
|
181
|
+
for (const channel of this.#channels) promises.push(fn(channel))
|
|
182
|
+
for (const pending of this.#pending) promises.push(pending.then(fn))
|
|
183
|
+
for (const recover of Object.values(this.#bench)) promises.push(recover.then(fn))
|
|
184
|
+
|
|
185
|
+
return promises
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @param {(channel: comq.Channel) => void} fn
|
|
190
|
+
* @return {Promise<void>}
|
|
191
|
+
*/
|
|
192
|
+
async #one (fn) {
|
|
193
|
+
if (this.#pool.length === 0) await this.#recovery
|
|
194
|
+
|
|
195
|
+
const channel = sample(this.#pool)
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
await fn(channel)
|
|
199
|
+
} catch (e) {
|
|
200
|
+
this.#remove(channel)
|
|
201
|
+
await this.#one(fn)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* @param {comq.Connection[]} connections
|
|
208
|
+
* @param {comq.topology.type} type
|
|
209
|
+
* @return {comq.Channel}
|
|
210
|
+
*/
|
|
211
|
+
async function create (connections, type) {
|
|
212
|
+
const channel = new Channel(connections, type)
|
|
213
|
+
|
|
214
|
+
await channel.create()
|
|
215
|
+
|
|
216
|
+
return channel
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
exports.create = create
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { EventEmitter } = require('node:events')
|
|
4
|
+
const events = require('../events')
|
|
5
|
+
const channel = require('./channel')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @implements {comq.Connection}
|
|
9
|
+
*/
|
|
10
|
+
class Connection {
|
|
11
|
+
/** @type {comq.Connection[]} */
|
|
12
|
+
#connections
|
|
13
|
+
|
|
14
|
+
#diagnostics = new EventEmitter()
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {comq.Connection[]} connections
|
|
18
|
+
*/
|
|
19
|
+
constructor (connections) {
|
|
20
|
+
this.#connections = connections
|
|
21
|
+
|
|
22
|
+
connections.map(this.#pipe)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async open () {
|
|
26
|
+
const opening = this.#connections.map((connection) => connection.open())
|
|
27
|
+
|
|
28
|
+
await Promise.any(opening)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async close () {
|
|
32
|
+
const closing = this.#connections.map((connection) => connection.close())
|
|
33
|
+
|
|
34
|
+
await Promise.all(closing)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async createChannel (type) {
|
|
38
|
+
return channel.create(this.#connections, type)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async diagnose (event, listener) {
|
|
42
|
+
this.#diagnostics.on(event, listener)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {comq.Connection} connection
|
|
47
|
+
* @param {number} index
|
|
48
|
+
*/
|
|
49
|
+
#pipe = (connection, index) => {
|
|
50
|
+
for (const event of events.connection) {
|
|
51
|
+
connection.diagnose(event, (...args) => this.#diagnostics.emit(event, ...args, index))
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
exports.Connection = Connection
|