comq 0.5.3-dev.0 → 0.5.3-dev.1
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 -3
- package/lerna.json +1 -1
- package/package.json +2 -2
- package/readme.md +3 -3
- package/source/io.js +51 -35
- package/test/channel.test.js +4 -1
- package/test/io.reply.test.js +26 -15
- package/test/io.request.test.js +0 -2
package/.github/dependabot.yml
CHANGED
|
@@ -6,12 +6,11 @@ updates:
|
|
|
6
6
|
interval: "weekly"
|
|
7
7
|
target-branch: "dev"
|
|
8
8
|
labels:
|
|
9
|
-
- "
|
|
10
|
-
- "actions"
|
|
9
|
+
- "Bump"
|
|
11
10
|
- package-ecosystem: "npm"
|
|
12
11
|
directory: "/"
|
|
13
12
|
schedule:
|
|
14
13
|
interval: "daily"
|
|
15
14
|
target-branch: "dev"
|
|
16
15
|
labels:
|
|
17
|
-
- "
|
|
16
|
+
- "Bump"
|
package/lerna.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "comq",
|
|
3
|
-
"version": "0.5.3-dev.
|
|
3
|
+
"version": "0.5.3-dev.1",
|
|
4
4
|
"description": "ComQ",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Artem Gurtovoi",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"scripts": {
|
|
49
49
|
"prepare": "husky install",
|
|
50
50
|
"test": "standard && jest",
|
|
51
|
-
"
|
|
51
|
+
"features": "cucumber-js",
|
|
52
52
|
"lint": "standard --fix --verbose | snazzy"
|
|
53
53
|
},
|
|
54
54
|
"standard": {
|
package/readme.md
CHANGED
|
@@ -83,11 +83,11 @@ The Reply message is encoded using the same encoding format as the Request messa
|
|
|
83
83
|
`application/octet-stream` and the `producer` function returns something other than a `Buffer`, an
|
|
84
84
|
exception will be thrown.
|
|
85
85
|
|
|
86
|
-
> If the incoming message does not have a `replyTo` property, an exception is thrown without
|
|
87
|
-
> calling `producer`.
|
|
88
|
-
|
|
89
86
|
> The `replyTo` queue is not asserted, as it is expected to be done by the Consumer.
|
|
90
87
|
|
|
88
|
+
> If the incoming message does not have a `replyTo` property, the result of the `producer` is
|
|
89
|
+
> ignored.
|
|
90
|
+
|
|
91
91
|
### Example
|
|
92
92
|
|
|
93
93
|
```javascript
|
package/source/io.js
CHANGED
|
@@ -25,8 +25,8 @@ class IO {
|
|
|
25
25
|
/** @type {comq.Channel} */
|
|
26
26
|
#events
|
|
27
27
|
|
|
28
|
-
/** @type {
|
|
29
|
-
#emitters =
|
|
28
|
+
/** @type {Map<string, comq.ReplyEmitter>} */
|
|
29
|
+
#emitters = new Map()
|
|
30
30
|
|
|
31
31
|
/** @type {Set<toa.generic.Promex>} */
|
|
32
32
|
#pendingReplies = new Set()
|
|
@@ -67,12 +67,13 @@ class IO {
|
|
|
67
67
|
* @returns {Promise<void>}
|
|
68
68
|
*/
|
|
69
69
|
async (queue, payload, encoding, replyToFormatter) => {
|
|
70
|
-
const [buffer,
|
|
71
|
-
const
|
|
72
|
-
const emitter = this.#emitters[queue]
|
|
73
|
-
const replyTo = replyToFormatter?.(emitter.queue) ?? emitter.queue
|
|
74
|
-
const properties = { contentType, correlationId, replyTo }
|
|
70
|
+
const [buffer, properties] = this.#encode(payload, encoding)
|
|
71
|
+
const emitter = this.#emitters.get(queue)
|
|
75
72
|
const reply = this.#createReply()
|
|
73
|
+
const correlationId = randomBytes(8).toString('hex')
|
|
74
|
+
|
|
75
|
+
properties.replyTo = replyToFormatter?.(emitter.queue) ?? emitter.queue
|
|
76
|
+
properties.correlationId = correlationId
|
|
76
77
|
|
|
77
78
|
emitter.once(correlationId, reply.resolve)
|
|
78
79
|
|
|
@@ -103,8 +104,7 @@ class IO {
|
|
|
103
104
|
* @returns {Promise<void>}
|
|
104
105
|
*/
|
|
105
106
|
async (exchange, payload, encoding) => {
|
|
106
|
-
const [buffer,
|
|
107
|
-
const properties = { contentType }
|
|
107
|
+
const [buffer, properties] = this.#encode(payload, encoding)
|
|
108
108
|
|
|
109
109
|
await this.#events.publish(exchange, buffer, properties)
|
|
110
110
|
})
|
|
@@ -127,12 +127,16 @@ class IO {
|
|
|
127
127
|
// region initializers
|
|
128
128
|
|
|
129
129
|
async #createRequestReplyChannels () {
|
|
130
|
-
|
|
131
|
-
this.#replies = await this.#createChannel('reply')
|
|
130
|
+
await this.#createRequestChannel()
|
|
132
131
|
|
|
132
|
+
this.#replies = await this.#createChannel('reply')
|
|
133
133
|
this.#setupRetransmission()
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
async #createRequestChannel () {
|
|
137
|
+
this.#requests = await this.#createChannel('request')
|
|
138
|
+
}
|
|
139
|
+
|
|
136
140
|
async #createEventChannel () {
|
|
137
141
|
this.#events = await this.#createChannel('event')
|
|
138
142
|
}
|
|
@@ -141,7 +145,7 @@ class IO {
|
|
|
141
145
|
const emitter = io.createReplyEmitter(queue)
|
|
142
146
|
const consumer = this.#getReplyConsumer(queue, emitter)
|
|
143
147
|
|
|
144
|
-
this.#emitters
|
|
148
|
+
this.#emitters.set(queue, emitter)
|
|
145
149
|
|
|
146
150
|
await this.#replies.consume(emitter.queue, consumer)
|
|
147
151
|
}
|
|
@@ -173,24 +177,36 @@ class IO {
|
|
|
173
177
|
* @returns {comq.channels.consumer}
|
|
174
178
|
*/
|
|
175
179
|
#getRequestConsumer = (producer) =>
|
|
176
|
-
track(this,
|
|
177
|
-
|
|
180
|
+
track(this,
|
|
181
|
+
/**
|
|
182
|
+
* @param {comq.amqp.Message} request
|
|
183
|
+
* @returns {Promise<void>}
|
|
184
|
+
*/
|
|
185
|
+
async (request) => {
|
|
186
|
+
const payload = decode(request)
|
|
187
|
+
const reply = await producer(payload)
|
|
178
188
|
|
|
179
|
-
|
|
180
|
-
|
|
189
|
+
if ('replyTo' in request.properties) await this.#reply(request, reply)
|
|
190
|
+
})
|
|
181
191
|
|
|
182
|
-
|
|
192
|
+
/**
|
|
193
|
+
* @param {comq.amqp.Message} request
|
|
194
|
+
* @param {any} reply
|
|
195
|
+
* @returns {Promise<void>}
|
|
196
|
+
*/
|
|
197
|
+
async #reply (request, reply) {
|
|
198
|
+
if (reply === undefined) throw new Error('The `producer` function must return a value')
|
|
183
199
|
|
|
184
|
-
|
|
200
|
+
let { replyTo, correlationId, contentType } = request.properties
|
|
185
201
|
|
|
186
|
-
|
|
187
|
-
|
|
202
|
+
if (Buffer.isBuffer(reply)) contentType = OCTETS
|
|
203
|
+
if (contentType === undefined) throw new Error('Reply to a Request without the `contentType` property must be of type `Buffer`')
|
|
188
204
|
|
|
189
|
-
|
|
190
|
-
|
|
205
|
+
const buffer = contentType === OCTETS ? reply : encode(reply, contentType)
|
|
206
|
+
const properties = { contentType, correlationId }
|
|
191
207
|
|
|
192
|
-
|
|
193
|
-
|
|
208
|
+
await this.#replies.fire(replyTo, buffer, properties)
|
|
209
|
+
}
|
|
194
210
|
|
|
195
211
|
/**
|
|
196
212
|
* @param {string} queue
|
|
@@ -198,9 +214,6 @@ class IO {
|
|
|
198
214
|
* @returns {comq.channels.consumer}
|
|
199
215
|
*/
|
|
200
216
|
#getReplyConsumer = (queue, emitter) =>
|
|
201
|
-
/**
|
|
202
|
-
* @param {comq.amqp.Message} message
|
|
203
|
-
*/
|
|
204
217
|
(message) => {
|
|
205
218
|
const payload = decode(message)
|
|
206
219
|
|
|
@@ -226,7 +239,9 @@ class IO {
|
|
|
226
239
|
|
|
227
240
|
this.#pendingReplies.add(reply)
|
|
228
241
|
|
|
229
|
-
reply
|
|
242
|
+
reply
|
|
243
|
+
.catch(noop)
|
|
244
|
+
.finally(() => this.#pendingReplies.delete(reply))
|
|
230
245
|
|
|
231
246
|
return reply
|
|
232
247
|
}
|
|
@@ -236,7 +251,7 @@ class IO {
|
|
|
236
251
|
}
|
|
237
252
|
|
|
238
253
|
#retransmit = () => {
|
|
239
|
-
for (const emitter of
|
|
254
|
+
for (const emitter of this.#emitters.values()) emitter.clear()
|
|
240
255
|
|
|
241
256
|
// trigger failsafe attribute
|
|
242
257
|
for (const reply of this.#pendingReplies) reply.reject(RETRANSMISSION)
|
|
@@ -244,17 +259,18 @@ class IO {
|
|
|
244
259
|
|
|
245
260
|
/**
|
|
246
261
|
* @param {any} payload
|
|
247
|
-
* @param {comq.encoding} [
|
|
248
|
-
* @returns [Buffer,
|
|
262
|
+
* @param {comq.encoding} [contentType]
|
|
263
|
+
* @returns [Buffer, comq.amqp.options.Publish]
|
|
249
264
|
*/
|
|
250
|
-
#encode (payload,
|
|
265
|
+
#encode (payload, contentType) {
|
|
251
266
|
const raw = Buffer.isBuffer(payload)
|
|
252
267
|
|
|
253
|
-
|
|
268
|
+
contentType ??= raw ? OCTETS : DEFAULT
|
|
254
269
|
|
|
255
|
-
const buffer = raw ? payload : encode(payload,
|
|
270
|
+
const buffer = raw ? payload : encode(payload, contentType)
|
|
271
|
+
const properties = { contentType }
|
|
256
272
|
|
|
257
|
-
return [buffer,
|
|
273
|
+
return [buffer, properties]
|
|
258
274
|
}
|
|
259
275
|
}
|
|
260
276
|
|
package/test/channel.test.js
CHANGED
|
@@ -712,6 +712,9 @@ describe('recovery', () => {
|
|
|
712
712
|
|
|
713
713
|
const repl = await getCreatedChannel(replacement)
|
|
714
714
|
|
|
715
|
+
// await recovery effect
|
|
716
|
+
await timeout(5)
|
|
717
|
+
|
|
715
718
|
expect(repl.publish).toHaveBeenCalled()
|
|
716
719
|
})
|
|
717
720
|
|
|
@@ -747,7 +750,7 @@ describe('recovery', () => {
|
|
|
747
750
|
|
|
748
751
|
const repl = await getCreatedChannel(replacement)
|
|
749
752
|
|
|
750
|
-
await timeout(
|
|
753
|
+
await timeout(5)
|
|
751
754
|
|
|
752
755
|
expect(repl.publish).toHaveBeenCalledWith(
|
|
753
756
|
exchange, '', buffer, expect.objectContaining(options), expect.any(Function)
|
package/test/io.reply.test.js
CHANGED
|
@@ -70,15 +70,6 @@ describe('queues', () => {
|
|
|
70
70
|
it('should consume queue', async () => {
|
|
71
71
|
expect(requests.consume).toHaveBeenCalledWith(queue, expect.any(Function))
|
|
72
72
|
})
|
|
73
|
-
|
|
74
|
-
it('should throw if message is missing replyTo', async () => {
|
|
75
|
-
const content = randomBytes(10)
|
|
76
|
-
const properties = {}
|
|
77
|
-
const message = /** @type {comq.amqp.Message} */ { content, properties }
|
|
78
|
-
const producer = requests.consume.mock.calls[0][1]
|
|
79
|
-
|
|
80
|
-
await expect(producer(message)).rejects.toThrow('is missing the `replyTo` property')
|
|
81
|
-
})
|
|
82
73
|
})
|
|
83
74
|
|
|
84
75
|
describe('reply', () => {
|
|
@@ -89,14 +80,13 @@ describe('reply', () => {
|
|
|
89
80
|
let replies
|
|
90
81
|
|
|
91
82
|
beforeEach(async () => {
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('should `fire` reply', async () => {
|
|
95
83
|
await io.reply(queue, produce)
|
|
96
84
|
|
|
97
85
|
requests = await findChannel('request')
|
|
98
86
|
replies = await findChannel('reply')
|
|
87
|
+
})
|
|
99
88
|
|
|
89
|
+
it('should `fire` reply', async () => {
|
|
100
90
|
const content = randomBytes(10)
|
|
101
91
|
const properties = { replyTo: generate(), contentType: 'text/plain' }
|
|
102
92
|
const message = /** @type {comq.amqp.Message} */ { content, properties }
|
|
@@ -111,18 +101,39 @@ describe('reply', () => {
|
|
|
111
101
|
})
|
|
112
102
|
|
|
113
103
|
it('should throw if producer returned undefined', async () => {
|
|
104
|
+
jest.clearAllMocks()
|
|
105
|
+
|
|
114
106
|
const produce = () => undefined
|
|
115
107
|
|
|
116
108
|
await io.reply(queue, produce)
|
|
117
109
|
|
|
118
|
-
const requests = await findChannel('request')
|
|
119
110
|
const callback = requests.consume.mock.calls[0][1]
|
|
120
|
-
const content =
|
|
121
|
-
const properties = { replyTo: generate() }
|
|
111
|
+
const content = generate()
|
|
112
|
+
const properties = { replyTo: generate(), contentType: 'text/plain' }
|
|
122
113
|
const message = /** @type {comq.amqp.Message} */ { content, properties }
|
|
123
114
|
|
|
124
115
|
await expect(callback(message)).rejects.toThrow('must return a value')
|
|
125
116
|
})
|
|
117
|
+
|
|
118
|
+
it('should not throw if message is missing replyTo', async () => {
|
|
119
|
+
const content = generate()
|
|
120
|
+
const properties = { contentType: 'text/plain' }
|
|
121
|
+
const message = /** @type {comq.amqp.Message} */ { content, properties }
|
|
122
|
+
const producer = requests.consume.mock.calls[0][1]
|
|
123
|
+
|
|
124
|
+
await expect(producer(message)).resolves.not.toThrow()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('should not send reply if request has no `replyTo` property', async () => {
|
|
128
|
+
const content = generate()
|
|
129
|
+
const properties = { contentType: 'text/plain' }
|
|
130
|
+
const message = /** @type {comq.amqp.Message} */ { content, properties }
|
|
131
|
+
const producer = requests.consume.mock.calls[0][1]
|
|
132
|
+
|
|
133
|
+
await producer(message)
|
|
134
|
+
|
|
135
|
+
expect(replies.fire).not.toHaveBeenCalled()
|
|
136
|
+
})
|
|
126
137
|
})
|
|
127
138
|
|
|
128
139
|
describe('encoding', () => {
|