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.
@@ -6,12 +6,11 @@ updates:
6
6
  interval: "weekly"
7
7
  target-branch: "dev"
8
8
  labels:
9
- - "dependency"
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
- - "dependency"
16
+ - "Bump"
package/lerna.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "useWorkspaces": true,
3
- "version": "0.5.3-dev.0",
3
+ "version": "0.5.3-dev.1",
4
4
  "conventionalCommits": true
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "comq",
3
- "version": "0.5.3-dev.0",
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
- "test:features": "cucumber-js",
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 {Record<string, comq.ReplyEmitter>} */
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, contentType] = this.#encode(payload, encoding)
71
- const correlationId = randomBytes(8).toString('hex')
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, contentType] = this.#encode(payload, encoding)
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
- this.#requests = await this.#createChannel('request')
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[queue] = emitter
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, async (message) => {
177
- if (!('replyTo' in message.properties)) throw new Error('Request is missing the `replyTo` property')
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
- const payload = decode(message)
180
- const reply = await producer(payload)
189
+ if ('replyTo' in request.properties) await this.#reply(request, reply)
190
+ })
181
191
 
182
- if (reply === undefined) throw new Error('The `producer` function must return a value')
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
- let { correlationId, contentType } = message.properties
200
+ let { replyTo, correlationId, contentType } = request.properties
185
201
 
186
- if (Buffer.isBuffer(reply)) contentType = OCTETS
187
- if (contentType === undefined) throw new Error('Reply to a Request without the `contentType` property must be of type `Buffer`')
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
- const buffer = contentType === OCTETS ? reply : encode(reply, contentType)
190
- const properties = { contentType, correlationId }
205
+ const buffer = contentType === OCTETS ? reply : encode(reply, contentType)
206
+ const properties = { contentType, correlationId }
191
207
 
192
- await this.#replies.fire(message.properties.replyTo, buffer, properties)
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.catch(noop).finally(() => this.#pendingReplies.delete(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 Object.values(this.#emitters)) emitter.clear()
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} [encoding]
248
- * @returns [Buffer, string]
262
+ * @param {comq.encoding} [contentType]
263
+ * @returns [Buffer, comq.amqp.options.Publish]
249
264
  */
250
- #encode (payload, encoding) {
265
+ #encode (payload, contentType) {
251
266
  const raw = Buffer.isBuffer(payload)
252
267
 
253
- encoding ??= raw ? OCTETS : DEFAULT
268
+ contentType ??= raw ? OCTETS : DEFAULT
254
269
 
255
- const buffer = raw ? payload : encode(payload, encoding)
270
+ const buffer = raw ? payload : encode(payload, contentType)
271
+ const properties = { contentType }
256
272
 
257
- return [buffer, encoding]
273
+ return [buffer, properties]
258
274
  }
259
275
  }
260
276
 
@@ -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(1)
753
+ await timeout(5)
751
754
 
752
755
  expect(repl.publish).toHaveBeenCalledWith(
753
756
  exchange, '', buffer, expect.objectContaining(options), expect.any(Function)
@@ -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 = randomBytes(10)
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', () => {
@@ -43,8 +43,6 @@ let promise
43
43
  const REPLY = new RegExp(`^${queue}..[0-9a-f]+$`)
44
44
 
45
45
  beforeEach(async () => {
46
- jest.clearAllMocks()
47
-
48
46
  promise = io.request(queue, payload)
49
47
 
50
48
  // allows initializers to run