consumer-pgmq 2.0.1 → 3.1.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/README.md +119 -22
- package/dist/consumer.js +43 -5
- package/dist/queueDriver/PostgresQueueDriver.js +144 -11
- package/dist/queueDriver/SupabaseQueueDriver.js +19 -0
- package/examples/consumerPostgresDriver.ts +42 -43
- package/examples/consumerSupabaseDriver.ts +15 -15
- package/package.json +3 -2
- package/schema.sql +50 -0
- package/src/consumer.ts +59 -6
- package/src/queueDriver/PostgresQueueDriver.ts +175 -14
- package/src/queueDriver/SupabaseQueueDriver.ts +23 -0
- package/src/type.ts +31 -0
- package/tests/consumer.spec.ts +201 -7
package/tests/consumer.spec.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import Consumer from '../src/consumer'
|
|
2
2
|
import { Message, QueueDriver, Options } from '../src/type'
|
|
3
|
-
import timersPromises from 'node:timers/promises'
|
|
4
3
|
|
|
5
4
|
describe('Consumer', () => {
|
|
6
5
|
let queueDriver: jest.Mocked<QueueDriver>
|
|
@@ -18,6 +17,9 @@ describe('Consumer', () => {
|
|
|
18
17
|
get: jest.fn(),
|
|
19
18
|
pop: jest.fn(),
|
|
20
19
|
delete: jest.fn(),
|
|
20
|
+
send: jest.fn(),
|
|
21
|
+
allocateConsumer: jest.fn(),
|
|
22
|
+
freeConsumer: jest.fn()
|
|
21
23
|
}
|
|
22
24
|
jest.useFakeTimers();
|
|
23
25
|
})
|
|
@@ -28,6 +30,165 @@ describe('Consumer', () => {
|
|
|
28
30
|
})
|
|
29
31
|
|
|
30
32
|
|
|
33
|
+
it('Should throw error if set dead letter queue and no set total retries before send to dlq', async () => {
|
|
34
|
+
try {
|
|
35
|
+
queueDriver.get.mockResolvedValueOnce({ data: [message], error: null })
|
|
36
|
+
queueDriver.delete.mockResolvedValueOnce({ error: null })
|
|
37
|
+
queueDriver.get.mockResolvedValueOnce({ data: [], error: null })
|
|
38
|
+
|
|
39
|
+
const handler = jest.fn(async () => { })
|
|
40
|
+
const consumer = new Consumer(
|
|
41
|
+
{
|
|
42
|
+
queueName: 'q',
|
|
43
|
+
consumeType: 'read',
|
|
44
|
+
visibilityTime: 1,
|
|
45
|
+
poolSize: 1,
|
|
46
|
+
timeMsWaitBeforeNextPolling: 1,
|
|
47
|
+
enabledPolling: true,
|
|
48
|
+
queueNameDlq: 'q_dlq',
|
|
49
|
+
},
|
|
50
|
+
handler,
|
|
51
|
+
queueDriver
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
const onFinish = jest.fn()
|
|
55
|
+
consumer.on('finish', onFinish)
|
|
56
|
+
await consumer.start()
|
|
57
|
+
} catch (error: any) {
|
|
58
|
+
expect(error).toBeInstanceOf(Error)
|
|
59
|
+
expect(error.message).toBe('The option totalRetriesBeforeSendToDlq is required when queueNameDlq is set')
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
it('Should send message to dlq if read count is greater than total retries before send to dlq', async () => {
|
|
65
|
+
const messageToSendDlq = { ...message }
|
|
66
|
+
messageToSendDlq.read_ct = 3
|
|
67
|
+
messageToSendDlq.msg_id = 2
|
|
68
|
+
queueDriver.get.mockResolvedValueOnce({ data: [messageToSendDlq], error: null })
|
|
69
|
+
queueDriver.delete.mockResolvedValueOnce({ error: null })
|
|
70
|
+
queueDriver.get.mockResolvedValueOnce({ data: [], error: null })
|
|
71
|
+
queueDriver.send.mockResolvedValueOnce({ error: null })
|
|
72
|
+
|
|
73
|
+
const handler = jest.fn(async () => { })
|
|
74
|
+
const consumer = new Consumer(
|
|
75
|
+
{
|
|
76
|
+
queueName: 'q',
|
|
77
|
+
consumeType: 'read',
|
|
78
|
+
visibilityTime: 1,
|
|
79
|
+
poolSize: 1,
|
|
80
|
+
timeMsWaitBeforeNextPolling: 1,
|
|
81
|
+
enabledPolling: true,
|
|
82
|
+
queueNameDlq: 'q_dlq',
|
|
83
|
+
totalRetriesBeforeSendToDlq: 2
|
|
84
|
+
},
|
|
85
|
+
handler,
|
|
86
|
+
queueDriver
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
const onFinish = jest.fn()
|
|
90
|
+
consumer.on('finish', onFinish)
|
|
91
|
+
await consumer.start()
|
|
92
|
+
|
|
93
|
+
expect(handler).toHaveBeenCalledTimes(0)
|
|
94
|
+
expect(onFinish).toHaveBeenCalledTimes(0)
|
|
95
|
+
expect(queueDriver.delete).toHaveBeenCalledTimes(1)
|
|
96
|
+
expect(queueDriver.send).toHaveBeenCalledTimes(1)
|
|
97
|
+
expect(queueDriver.send).toHaveBeenCalledWith(
|
|
98
|
+
'q_dlq', messageToSendDlq.message, expect.any(AbortSignal)
|
|
99
|
+
)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('Should send 2 messages to dlq if read count is greater than total retries before send to dlq', async () => {
|
|
103
|
+
const messageToSendDlq = { ...message }
|
|
104
|
+
messageToSendDlq.read_ct = 3
|
|
105
|
+
messageToSendDlq.msg_id = 2
|
|
106
|
+
queueDriver.get.mockResolvedValueOnce({
|
|
107
|
+
data: [
|
|
108
|
+
messageToSendDlq, messageToSendDlq
|
|
109
|
+
], error: null
|
|
110
|
+
})
|
|
111
|
+
queueDriver.delete.mockResolvedValueOnce({ error: null })
|
|
112
|
+
queueDriver.get.mockResolvedValueOnce({ data: [], error: null })
|
|
113
|
+
queueDriver.send.mockResolvedValue({ error: null })
|
|
114
|
+
|
|
115
|
+
const handler = jest.fn(async () => { })
|
|
116
|
+
const consumer = new Consumer(
|
|
117
|
+
{
|
|
118
|
+
queueName: 'q',
|
|
119
|
+
consumeType: 'read',
|
|
120
|
+
visibilityTime: 1,
|
|
121
|
+
poolSize: 2,
|
|
122
|
+
timeMsWaitBeforeNextPolling: 1,
|
|
123
|
+
enabledPolling: true,
|
|
124
|
+
queueNameDlq: 'q_dlq',
|
|
125
|
+
totalRetriesBeforeSendToDlq: 2
|
|
126
|
+
},
|
|
127
|
+
handler,
|
|
128
|
+
queueDriver
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
const onFinish = jest.fn()
|
|
132
|
+
consumer.on('finish', onFinish)
|
|
133
|
+
await consumer.start()
|
|
134
|
+
|
|
135
|
+
expect(handler).toHaveBeenCalledTimes(0)
|
|
136
|
+
expect(onFinish).toHaveBeenCalledTimes(0)
|
|
137
|
+
expect(queueDriver.delete).toHaveBeenCalledTimes(2)
|
|
138
|
+
expect(queueDriver.send).toHaveBeenCalledTimes(2)
|
|
139
|
+
expect(queueDriver.send).toHaveBeenCalledWith(
|
|
140
|
+
'q_dlq', messageToSendDlq.message, expect.any(AbortSignal)
|
|
141
|
+
)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
it('Should send only 1 message to dlq if read count is greater than total retries before send to dlq', async () => {
|
|
146
|
+
const messageToSendDlq = { ...message }
|
|
147
|
+
messageToSendDlq.read_ct = 3
|
|
148
|
+
messageToSendDlq.msg_id = 2
|
|
149
|
+
|
|
150
|
+
const messageToSendDlq2 = { ...message }
|
|
151
|
+
messageToSendDlq2.read_ct = 1
|
|
152
|
+
messageToSendDlq2.msg_id = 3
|
|
153
|
+
queueDriver.get.mockResolvedValueOnce({
|
|
154
|
+
data: [
|
|
155
|
+
messageToSendDlq, messageToSendDlq2
|
|
156
|
+
], error: null
|
|
157
|
+
})
|
|
158
|
+
queueDriver.delete.mockResolvedValue({ error: null })
|
|
159
|
+
queueDriver.get.mockResolvedValueOnce({ data: [], error: null })
|
|
160
|
+
queueDriver.send.mockResolvedValue({ error: null })
|
|
161
|
+
|
|
162
|
+
const handler = jest.fn(async () => {
|
|
163
|
+
return Promise.resolve()
|
|
164
|
+
})
|
|
165
|
+
const consumer = new Consumer(
|
|
166
|
+
{
|
|
167
|
+
queueName: 'q',
|
|
168
|
+
consumeType: 'read',
|
|
169
|
+
visibilityTime: 1,
|
|
170
|
+
poolSize: 2,
|
|
171
|
+
timeMsWaitBeforeNextPolling: 1,
|
|
172
|
+
enabledPolling: true,
|
|
173
|
+
queueNameDlq: 'q_dlq',
|
|
174
|
+
totalRetriesBeforeSendToDlq: 2
|
|
175
|
+
},
|
|
176
|
+
handler,
|
|
177
|
+
queueDriver
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
const onFinish = jest.fn()
|
|
181
|
+
consumer.on('finish', onFinish)
|
|
182
|
+
await consumer.start()
|
|
183
|
+
|
|
184
|
+
expect(handler).toHaveBeenCalledTimes(1)
|
|
185
|
+
expect(onFinish).toHaveBeenCalledTimes(1)
|
|
186
|
+
expect(queueDriver.delete).toHaveBeenCalledTimes(2)
|
|
187
|
+
expect(queueDriver.send).toHaveBeenCalledTimes(1)
|
|
188
|
+
expect(queueDriver.send).toHaveBeenCalledWith(
|
|
189
|
+
'q_dlq', messageToSendDlq.message, expect.any(AbortSignal)
|
|
190
|
+
)
|
|
191
|
+
})
|
|
31
192
|
|
|
32
193
|
it('Should not process message if read method does not return any message second polling', async () => {
|
|
33
194
|
queueDriver.get.mockResolvedValueOnce({ data: [message], error: null })
|
|
@@ -41,7 +202,8 @@ describe('Consumer', () => {
|
|
|
41
202
|
consumeType: 'read',
|
|
42
203
|
visibilityTime: 1,
|
|
43
204
|
poolSize: 1,
|
|
44
|
-
timeMsWaitBeforeNextPolling: 1
|
|
205
|
+
timeMsWaitBeforeNextPolling: 1,
|
|
206
|
+
enabledPolling: true
|
|
45
207
|
},
|
|
46
208
|
handler,
|
|
47
209
|
queueDriver
|
|
@@ -69,7 +231,8 @@ describe('Consumer', () => {
|
|
|
69
231
|
consumeType: 'pop',
|
|
70
232
|
visibilityTime: 1,
|
|
71
233
|
poolSize: 1,
|
|
72
|
-
timeMsWaitBeforeNextPolling: 1
|
|
234
|
+
timeMsWaitBeforeNextPolling: 1,
|
|
235
|
+
enabledPolling: true
|
|
73
236
|
},
|
|
74
237
|
handler,
|
|
75
238
|
queueDriver
|
|
@@ -95,7 +258,8 @@ describe('Consumer', () => {
|
|
|
95
258
|
consumeType: 'pop',
|
|
96
259
|
visibilityTime: 1,
|
|
97
260
|
poolSize: 1,
|
|
98
|
-
timeMsWaitBeforeNextPolling: 1
|
|
261
|
+
timeMsWaitBeforeNextPolling: 1,
|
|
262
|
+
enabledPolling: true
|
|
99
263
|
},
|
|
100
264
|
handler,
|
|
101
265
|
queueDriver
|
|
@@ -121,7 +285,8 @@ describe('Consumer', () => {
|
|
|
121
285
|
consumeType: 'read',
|
|
122
286
|
visibilityTime: 1,
|
|
123
287
|
poolSize: 1,
|
|
124
|
-
timeMsWaitBeforeNextPolling: 1
|
|
288
|
+
timeMsWaitBeforeNextPolling: 1,
|
|
289
|
+
enabledPolling: true
|
|
125
290
|
},
|
|
126
291
|
handler,
|
|
127
292
|
queueDriver
|
|
@@ -150,7 +315,8 @@ describe('Consumer', () => {
|
|
|
150
315
|
consumeType: 'read',
|
|
151
316
|
visibilityTime: 1,
|
|
152
317
|
poolSize: 1,
|
|
153
|
-
timeMsWaitBeforeNextPolling: 1
|
|
318
|
+
timeMsWaitBeforeNextPolling: 1,
|
|
319
|
+
enabledPolling: true
|
|
154
320
|
},
|
|
155
321
|
handler,
|
|
156
322
|
queueDriver
|
|
@@ -179,7 +345,8 @@ describe('Consumer', () => {
|
|
|
179
345
|
consumeType: 'read',
|
|
180
346
|
visibilityTime: 1,
|
|
181
347
|
poolSize: 4,
|
|
182
|
-
timeMsWaitBeforeNextPolling: 1
|
|
348
|
+
timeMsWaitBeforeNextPolling: 1,
|
|
349
|
+
enabledPolling: true
|
|
183
350
|
},
|
|
184
351
|
handler,
|
|
185
352
|
queueDriver
|
|
@@ -230,8 +397,35 @@ describe('Consumer', () => {
|
|
|
230
397
|
|
|
231
398
|
await consumer.start()
|
|
232
399
|
|
|
400
|
+
expect(queueDriver.allocateConsumer).toHaveBeenCalledTimes(0)
|
|
233
401
|
expect(onError).toHaveBeenCalled()
|
|
234
402
|
expect((onError.mock.calls[0][0] as Error).message)
|
|
235
403
|
.toBe('visibilityTime is required for read')
|
|
236
404
|
})
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
it('Should call allocateConsumer when option enableControlConsumer is true', async () => {
|
|
408
|
+
queueDriver.allocateConsumer.mockResolvedValue({ id: '1' })
|
|
409
|
+
const consumer = new Consumer(
|
|
410
|
+
{ queueName: 'q', consumeType: 'read', enableControlConsumer: true } as Options,
|
|
411
|
+
async () => { },
|
|
412
|
+
queueDriver
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
await consumer.start()
|
|
416
|
+
|
|
417
|
+
expect(queueDriver.allocateConsumer).toHaveBeenCalled()
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
it('Should call freeConsumer when option enableControlConsumer is true and process exit', async () => {
|
|
422
|
+
queueDriver.allocateConsumer.mockResolvedValue({ id: '1' })
|
|
423
|
+
const consumer = new Consumer(
|
|
424
|
+
{ queueName: 'q', consumeType: 'read', enableControlConsumer: true } as Options,
|
|
425
|
+
async () => { },
|
|
426
|
+
queueDriver
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
await consumer.start()
|
|
430
|
+
})
|
|
237
431
|
})
|