comq 0.4.1 → 0.5.0-dev.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.
Files changed (61) hide show
  1. package/.github/labels.yaml +15 -0
  2. package/.github/workflows/labels.yaml +18 -0
  3. package/.github/workflows/release.yaml +20 -8
  4. package/docker-compose.yaml +20 -2
  5. package/docs/notes.md +14 -8
  6. package/examples/const.js +1 -3
  7. package/examples/events/consumer.js +5 -6
  8. package/examples/events/producer.js +3 -3
  9. package/examples/package.json +3 -0
  10. package/examples/rpc/consumer.js +3 -3
  11. package/examples/rpc/producer.js +3 -3
  12. package/features/connection.feature +3 -0
  13. package/features/events.feature +1 -1
  14. package/features/shards.feature +61 -0
  15. package/features/steps/.test/backpressure.test.js +2 -2
  16. package/features/steps/.test/broker.test.js +32 -6
  17. package/features/steps/.test/connection.test.js +105 -1
  18. package/features/steps/.test/context.mock.js +2 -2
  19. package/features/steps/.test/context.test.js +13 -5
  20. package/features/steps/.test/events.test.js +66 -3
  21. package/features/steps/.test/rpc.test.js +99 -1
  22. package/features/steps/.types/context.d.ts +11 -1
  23. package/features/steps/backpressure.js +1 -1
  24. package/features/steps/broker.js +26 -9
  25. package/features/steps/connection.js +69 -2
  26. package/features/steps/context.js +28 -12
  27. package/features/steps/events.js +41 -2
  28. package/features/steps/rpc.js +58 -1
  29. package/lerna.json +1 -1
  30. package/package.json +5 -5
  31. package/readme.md +75 -25
  32. package/source/channel.js +56 -32
  33. package/source/connect.js +28 -2
  34. package/source/connection.js +24 -17
  35. package/source/decode.js +1 -1
  36. package/source/events.js +7 -0
  37. package/source/io.js +21 -14
  38. package/source/shards/channel.js +219 -0
  39. package/source/shards/connection.js +56 -0
  40. package/source/shards/index.js +5 -0
  41. package/test/channel.test.js +240 -145
  42. package/test/connect.test.js +39 -11
  43. package/test/connection.mock.js +17 -13
  44. package/test/connection.test.js +65 -12
  45. package/test/decode.test.js +5 -5
  46. package/test/io.close.test.js +2 -2
  47. package/test/io.consume.test.js +1 -1
  48. package/test/io.reply.test.js +7 -7
  49. package/test/io.request.test.js +27 -1
  50. package/test/presets.test.js +1 -1
  51. package/test/shards/channel.test.js +367 -0
  52. package/test/shards/connection.test.js +106 -0
  53. package/types/amqp.d.ts +3 -1
  54. package/types/channel.d.ts +8 -5
  55. package/types/connection.d.ts +3 -1
  56. package/types/diagnostic.d.ts +1 -1
  57. package/types/topology.d.ts +4 -0
  58. /package/source/{presets → topology}/event.json +0 -0
  59. /package/source/{presets → topology}/index.js +0 -0
  60. /package/source/{presets → topology}/reply.json +0 -0
  61. /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
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ const { Connection } = require('./connection')
4
+
5
+ exports.Connection = Connection