@prsm/queue 1.0.1 → 2.0.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 +56 -15
- package/package.json +11 -21
- package/src/index.js +1 -0
- package/src/queue.js +271 -0
- package/types/index.d.ts +1 -0
- package/types/queue.d.ts +4759 -0
- package/dist/index.cjs +0 -269
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -73
- package/dist/index.d.ts +0 -73
- package/dist/index.js +0 -236
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ npm install @prsm/queue
|
|
|
10
10
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
|
-
```
|
|
13
|
+
```js
|
|
14
14
|
import Queue from '@prsm/queue'
|
|
15
15
|
|
|
16
16
|
const queue = new Queue({
|
|
@@ -30,12 +30,13 @@ queue.on('failed', ({ task, error }) => {
|
|
|
30
30
|
console.log('Failed after retries:', task.uuid, error.message)
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
+
await queue.ready()
|
|
33
34
|
await queue.push({ userId: 123, action: 'sync' })
|
|
34
35
|
```
|
|
35
36
|
|
|
36
37
|
## Options
|
|
37
38
|
|
|
38
|
-
```
|
|
39
|
+
```js
|
|
39
40
|
const queue = new Queue({
|
|
40
41
|
concurrency: 2, // worker count
|
|
41
42
|
delay: '100ms', // pause between tasks (string or ms)
|
|
@@ -58,7 +59,7 @@ const queue = new Queue({
|
|
|
58
59
|
|
|
59
60
|
## Process Handler
|
|
60
61
|
|
|
61
|
-
```
|
|
62
|
+
```js
|
|
62
63
|
queue.process(async (payload, task) => {
|
|
63
64
|
console.log('Task:', task.uuid, 'Attempt:', task.attempts)
|
|
64
65
|
return await someWork(payload)
|
|
@@ -71,7 +72,7 @@ Throw an error to trigger retry. After `maxRetries`, the task fails permanently.
|
|
|
71
72
|
|
|
72
73
|
Isolated concurrency per key - perfect for per-tenant rate limiting.
|
|
73
74
|
|
|
74
|
-
```
|
|
75
|
+
```js
|
|
75
76
|
const queue = new Queue({
|
|
76
77
|
groups: { concurrency: 1, delay: '50ms' }
|
|
77
78
|
})
|
|
@@ -80,6 +81,8 @@ queue.process(async (payload) => {
|
|
|
80
81
|
return await callExternalAPI(payload)
|
|
81
82
|
})
|
|
82
83
|
|
|
84
|
+
await queue.ready()
|
|
85
|
+
|
|
83
86
|
await queue.group('tenant-123').push({ action: 'sync' })
|
|
84
87
|
await queue.group('tenant-456').push({ action: 'sync' })
|
|
85
88
|
```
|
|
@@ -88,21 +91,22 @@ Each tenant processes independently. One slow tenant won't block others.
|
|
|
88
91
|
|
|
89
92
|
## Events
|
|
90
93
|
|
|
91
|
-
```
|
|
94
|
+
```js
|
|
92
95
|
queue.on('new', ({ task }) => {})
|
|
93
96
|
queue.on('complete', ({ task, result }) => {})
|
|
94
97
|
queue.on('retry', ({ task, error, attempt }) => {})
|
|
95
98
|
queue.on('failed', ({ task, error }) => {})
|
|
99
|
+
queue.on('drain', () => {})
|
|
96
100
|
```
|
|
97
101
|
|
|
98
102
|
## Task Object
|
|
99
103
|
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
uuid: string
|
|
103
|
-
payload:
|
|
104
|
-
createdAt: number
|
|
105
|
-
groupKey?: string
|
|
104
|
+
```js
|
|
105
|
+
{
|
|
106
|
+
uuid: string,
|
|
107
|
+
payload: any,
|
|
108
|
+
createdAt: number,
|
|
109
|
+
groupKey?: string, // present when pushed via group()
|
|
106
110
|
attempts: number
|
|
107
111
|
}
|
|
108
112
|
```
|
|
@@ -111,7 +115,7 @@ interface Task<T> {
|
|
|
111
115
|
|
|
112
116
|
20 LLM calls/sec per tenant:
|
|
113
117
|
|
|
114
|
-
```
|
|
118
|
+
```js
|
|
115
119
|
const queue = new Queue({
|
|
116
120
|
groups: { concurrency: 20, delay: '50ms' },
|
|
117
121
|
maxRetries: 3
|
|
@@ -128,15 +132,52 @@ app.post('/api/generate', async (req, res) => {
|
|
|
128
132
|
})
|
|
129
133
|
```
|
|
130
134
|
|
|
135
|
+
## WebSocket Integration with [mesh](https://github.com/nvms/mesh)
|
|
136
|
+
|
|
137
|
+
Queue events are local-only - only the server that processes a task emits `complete`/`failed`. Use [mesh](https://github.com/nvms/mesh) to push results to connected clients in real time.
|
|
138
|
+
|
|
139
|
+
Send results to a specific client:
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
import Queue from '@prsm/queue'
|
|
143
|
+
import { MeshServer } from '@mesh-kit/server'
|
|
144
|
+
|
|
145
|
+
const mesh = new MeshServer({ redis: { host: 'localhost', port: 6379 } })
|
|
146
|
+
const queue = new Queue({ groups: { concurrency: 1 } })
|
|
147
|
+
|
|
148
|
+
queue.process(async (payload) => {
|
|
149
|
+
return await generateReport(payload)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
queue.on('complete', ({ task, result }) => {
|
|
153
|
+
mesh.sendTo(task.payload.connectionId, 'job:complete', result)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
queue.on('failed', ({ task, error }) => {
|
|
157
|
+
mesh.sendTo(task.payload.connectionId, 'job:failed', { error: error.message })
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
mesh.exposeCommand('generate-report', async (ctx) => {
|
|
161
|
+
const taskId = await queue.group(ctx.connection.id).push({
|
|
162
|
+
connectionId: ctx.connection.id,
|
|
163
|
+
...ctx.payload,
|
|
164
|
+
})
|
|
165
|
+
return { queued: true, taskId }
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
await queue.ready()
|
|
169
|
+
await mesh.listen(8080)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Both queue and mesh use the same Redis instance. No key conflicts (`queue:*` vs `mesh:*`).
|
|
173
|
+
|
|
131
174
|
## Horizontal Scaling
|
|
132
175
|
|
|
133
176
|
Multiple servers can push to the same queue. Redis coordinates via atomic operations - no duplicate processing.
|
|
134
177
|
|
|
135
|
-
Note: events are local. Only the server that processes a task emits `complete`/`failed`. Use Redis pub/sub or WebSockets to broadcast results across servers.
|
|
136
|
-
|
|
137
178
|
## Cleanup
|
|
138
179
|
|
|
139
|
-
```
|
|
180
|
+
```js
|
|
140
181
|
await queue.close()
|
|
141
182
|
```
|
|
142
183
|
|
package/package.json
CHANGED
|
@@ -1,31 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prsm/queue",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Redis-backed distributed task queue with grouped concurrency, retries, and rate limiting",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./dist/index.cjs",
|
|
7
|
-
"module": "./dist/index.js",
|
|
8
|
-
"types": "./dist/index.d.ts",
|
|
9
6
|
"exports": {
|
|
10
7
|
".": {
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
"default": "./dist/index.js"
|
|
14
|
-
},
|
|
15
|
-
"require": {
|
|
16
|
-
"types": "./dist/index.d.cts",
|
|
17
|
-
"default": "./dist/index.cjs"
|
|
18
|
-
}
|
|
8
|
+
"types": "./types/index.d.ts",
|
|
9
|
+
"default": "./src/index.js"
|
|
19
10
|
}
|
|
20
11
|
},
|
|
12
|
+
"types": "./types/index.d.ts",
|
|
21
13
|
"files": [
|
|
22
|
-
"
|
|
14
|
+
"src",
|
|
15
|
+
"types"
|
|
23
16
|
],
|
|
24
17
|
"scripts": {
|
|
25
|
-
"
|
|
26
|
-
"test": "vitest",
|
|
27
|
-
"
|
|
28
|
-
"prepublishOnly": "npm run build"
|
|
18
|
+
"test": "vitest --reporter=verbose --run",
|
|
19
|
+
"test:watch": "vitest",
|
|
20
|
+
"prepublishOnly": "npx tsc --declaration --allowJs --emitDeclarationOnly --skipLibCheck --target es2020 --module nodenext --moduleResolution nodenext --strict false --esModuleInterop true --outDir ./types src/index.js"
|
|
29
21
|
},
|
|
30
22
|
"keywords": [
|
|
31
23
|
"queue",
|
|
@@ -38,7 +30,6 @@
|
|
|
38
30
|
"concurrency",
|
|
39
31
|
"retry"
|
|
40
32
|
],
|
|
41
|
-
"author": "",
|
|
42
33
|
"license": "MIT",
|
|
43
34
|
"dependencies": {
|
|
44
35
|
"@prsm/ms": "^1.0.1",
|
|
@@ -46,9 +37,8 @@
|
|
|
46
37
|
},
|
|
47
38
|
"devDependencies": {
|
|
48
39
|
"@types/node": "^22.15.29",
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"vitest": "^3.1.4"
|
|
40
|
+
"typescript": "^5.9.3",
|
|
41
|
+
"vitest": "^3.2.4"
|
|
52
42
|
},
|
|
53
43
|
"engines": {
|
|
54
44
|
"node": ">=18"
|
package/src/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./queue.js"
|
package/src/queue.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { createClient } from "redis"
|
|
2
|
+
import { EventEmitter } from "events"
|
|
3
|
+
import { randomUUID } from "crypto"
|
|
4
|
+
import ms from "@prsm/ms"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {Object} QueueOptions
|
|
8
|
+
* @property {number} [concurrency] - worker count (default 1)
|
|
9
|
+
* @property {number|string} [delay] - pause between tasks, ms or string like "100ms" (default 0)
|
|
10
|
+
* @property {number|string} [timeout] - max task duration, ms or string like "30s" (default 0, no limit)
|
|
11
|
+
* @property {number} [maxRetries] - attempts before failing (default 3)
|
|
12
|
+
* @property {{concurrency?: number, delay?: number|string, timeout?: number|string, maxRetries?: number}} [groups] - overrides for grouped queues
|
|
13
|
+
* @property {{url?: string, host?: string, port?: number, password?: string}} [redisOptions]
|
|
14
|
+
* @property {number} [cleanupInterval] - ms between empty group cleanup (default 30000, 0 to disable)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {Object} Task
|
|
19
|
+
* @property {string} uuid
|
|
20
|
+
* @property {any} payload
|
|
21
|
+
* @property {number} createdAt
|
|
22
|
+
* @property {string} [groupKey]
|
|
23
|
+
* @property {number} attempts
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @callback TaskHandler
|
|
28
|
+
* @param {any} payload
|
|
29
|
+
* @param {Task} task
|
|
30
|
+
* @returns {Promise<any>|any}
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
export default class Queue extends EventEmitter {
|
|
34
|
+
/** @param {QueueOptions} [options] */
|
|
35
|
+
constructor(options = {}) {
|
|
36
|
+
super()
|
|
37
|
+
|
|
38
|
+
this._options = {
|
|
39
|
+
concurrency: options.concurrency ?? 1,
|
|
40
|
+
delay: ms(options.delay ?? 0),
|
|
41
|
+
timeout: ms(options.timeout ?? 0),
|
|
42
|
+
maxRetries: options.maxRetries ?? 3,
|
|
43
|
+
groups: {
|
|
44
|
+
concurrency: options.groups?.concurrency ?? options.concurrency ?? 1,
|
|
45
|
+
delay: ms(options.groups?.delay ?? options.delay ?? 0),
|
|
46
|
+
timeout: ms(options.groups?.timeout ?? options.timeout ?? 0),
|
|
47
|
+
maxRetries: options.groups?.maxRetries ?? options.maxRetries ?? 3,
|
|
48
|
+
},
|
|
49
|
+
redisOptions: options.redisOptions ?? {},
|
|
50
|
+
cleanupInterval: options.cleanupInterval ?? 30000,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this._handler = null
|
|
54
|
+
this._workers = new Map()
|
|
55
|
+
this._groupWorkers = new Map()
|
|
56
|
+
this._workerClients = []
|
|
57
|
+
this._cleanupTimer = null
|
|
58
|
+
this._inFlight = 0
|
|
59
|
+
this._totalSettled = 0
|
|
60
|
+
|
|
61
|
+
this._redis = createClient(this._options.redisOptions)
|
|
62
|
+
this._redis.on("error", () => {})
|
|
63
|
+
this._readyPromise = this._initialize()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** @returns {Promise<void>} */
|
|
67
|
+
ready() {
|
|
68
|
+
return this._readyPromise
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** @returns {number} */
|
|
72
|
+
get inFlight() {
|
|
73
|
+
return this._inFlight
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** @param {TaskHandler} handler */
|
|
77
|
+
process(handler) {
|
|
78
|
+
this._handler = handler
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {any} payload
|
|
83
|
+
* @returns {Promise<string>}
|
|
84
|
+
*/
|
|
85
|
+
async push(payload) {
|
|
86
|
+
const task = { uuid: randomUUID(), payload, createdAt: Date.now(), attempts: 0 }
|
|
87
|
+
await this._redis.lPush("queue:tasks", JSON.stringify(task))
|
|
88
|
+
this.emit("new", { task })
|
|
89
|
+
return task.uuid
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {string} key
|
|
94
|
+
* @returns {{ push: (payload: any) => Promise<string> }}
|
|
95
|
+
*/
|
|
96
|
+
group(key) {
|
|
97
|
+
return {
|
|
98
|
+
push: async (payload) => {
|
|
99
|
+
const task = { uuid: randomUUID(), payload, createdAt: Date.now(), groupKey: key, attempts: 0 }
|
|
100
|
+
await this._redis.lPush(`queue:groups:${key}`, JSON.stringify(task))
|
|
101
|
+
this.emit("new", { task })
|
|
102
|
+
if (!this._groupWorkers.has(key)) {
|
|
103
|
+
this._groupWorkers.set(key, new Map())
|
|
104
|
+
await this._startGroupWorkers(key)
|
|
105
|
+
}
|
|
106
|
+
return task.uuid
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** @returns {Promise<void>} */
|
|
112
|
+
async close() {
|
|
113
|
+
await this._readyPromise.catch(() => {})
|
|
114
|
+
if (this._cleanupTimer) clearInterval(this._cleanupTimer)
|
|
115
|
+
this._workers.clear()
|
|
116
|
+
for (const groupWorkers of this._groupWorkers.values()) groupWorkers.clear()
|
|
117
|
+
this._groupWorkers.clear()
|
|
118
|
+
for (const client of this._workerClients) {
|
|
119
|
+
if (client.isOpen) await client.disconnect()
|
|
120
|
+
}
|
|
121
|
+
this._workerClients = []
|
|
122
|
+
if (this._redis.isOpen) await this._redis.quit()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async _initialize() {
|
|
126
|
+
await this._redis.connect()
|
|
127
|
+
await this._startWorkers()
|
|
128
|
+
if (this._options.cleanupInterval > 0) {
|
|
129
|
+
this._cleanupTimer = setInterval(() => this._periodicCleanup(), this._options.cleanupInterval)
|
|
130
|
+
this._cleanupTimer.unref()
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async _createWorkerClient() {
|
|
135
|
+
const client = this._redis.duplicate()
|
|
136
|
+
client.on("error", () => {})
|
|
137
|
+
await client.connect()
|
|
138
|
+
this._workerClients.push(client)
|
|
139
|
+
return client
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async _startWorkers() {
|
|
143
|
+
const ready = []
|
|
144
|
+
for (let i = 0; i < this._options.concurrency; i++) {
|
|
145
|
+
ready.push(this._startWorker(`worker-${i}`))
|
|
146
|
+
}
|
|
147
|
+
await Promise.all(ready)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async _startGroupWorkers(groupKey) {
|
|
151
|
+
const groupWorkers = this._groupWorkers.get(groupKey)
|
|
152
|
+
const ready = []
|
|
153
|
+
for (let i = 0; i < this._options.groups.concurrency; i++) {
|
|
154
|
+
const workerId = `group-${groupKey}-worker-${i}`
|
|
155
|
+
groupWorkers.set(workerId, true)
|
|
156
|
+
ready.push(this._startGroupWorker(workerId, groupKey))
|
|
157
|
+
}
|
|
158
|
+
await Promise.all(ready)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async _startWorker(workerId) {
|
|
162
|
+
this._workers.set(workerId, true)
|
|
163
|
+
const client = await this._createWorkerClient()
|
|
164
|
+
this._runWorkerLoop(workerId, client, "queue:tasks", this._workers, (task) => this._processTask(task))
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async _startGroupWorker(workerId, groupKey) {
|
|
168
|
+
const groupWorkers = this._groupWorkers.get(groupKey)
|
|
169
|
+
const client = await this._createWorkerClient()
|
|
170
|
+
this._runWorkerLoop(workerId, client, `queue:groups:${groupKey}`, groupWorkers, (task) => this._processGroupTask(task))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async _runWorkerLoop(workerId, client, key, activeMap, processFn) {
|
|
174
|
+
const isGrouped = key.startsWith("queue:groups:")
|
|
175
|
+
const delay = isGrouped ? this._options.groups.delay : this._options.delay
|
|
176
|
+
|
|
177
|
+
while (activeMap.get(workerId)) {
|
|
178
|
+
try {
|
|
179
|
+
if (!client.isOpen) break
|
|
180
|
+
const taskData = await client.brPop(key, 1)
|
|
181
|
+
if (taskData) {
|
|
182
|
+
const task = JSON.parse(taskData.element)
|
|
183
|
+
this._inFlight++
|
|
184
|
+
await processFn(task)
|
|
185
|
+
}
|
|
186
|
+
if (delay > 0) await new Promise((resolve) => setTimeout(resolve, delay))
|
|
187
|
+
} catch (err) {
|
|
188
|
+
if (err.message?.includes("closed") || err.message?.includes("ClientClosedError")) break
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async _processTask(task) {
|
|
194
|
+
task.attempts++
|
|
195
|
+
try {
|
|
196
|
+
if (!this._handler) {
|
|
197
|
+
this.emit("complete", { task, result: undefined })
|
|
198
|
+
this._settle()
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
const timeoutPromise = this._options.timeout > 0
|
|
202
|
+
? new Promise((_, reject) => setTimeout(() => reject(new Error("Task timeout")), this._options.timeout))
|
|
203
|
+
: null
|
|
204
|
+
const workPromise = Promise.resolve(this._handler(task.payload, task))
|
|
205
|
+
const result = timeoutPromise ? await Promise.race([workPromise, timeoutPromise]) : await workPromise
|
|
206
|
+
this.emit("complete", { task, result })
|
|
207
|
+
this._settle()
|
|
208
|
+
} catch (error) {
|
|
209
|
+
if (task.attempts < this._options.maxRetries) {
|
|
210
|
+
this.emit("retry", { task, error, attempt: task.attempts })
|
|
211
|
+
this._inFlight--
|
|
212
|
+
await this._redis.lPush("queue:tasks", JSON.stringify(task))
|
|
213
|
+
} else {
|
|
214
|
+
this.emit("failed", { task, error })
|
|
215
|
+
this._settle()
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async _processGroupTask(task) {
|
|
221
|
+
task.attempts++
|
|
222
|
+
try {
|
|
223
|
+
if (!this._handler) {
|
|
224
|
+
this.emit("complete", { task, result: undefined })
|
|
225
|
+
this._settle()
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
const timeoutPromise = this._options.groups.timeout > 0
|
|
229
|
+
? new Promise((_, reject) => setTimeout(() => reject(new Error("Task timeout")), this._options.groups.timeout))
|
|
230
|
+
: null
|
|
231
|
+
const workPromise = Promise.resolve(this._handler(task.payload, task))
|
|
232
|
+
const result = timeoutPromise ? await Promise.race([workPromise, timeoutPromise]) : await workPromise
|
|
233
|
+
this.emit("complete", { task, result })
|
|
234
|
+
this._settle()
|
|
235
|
+
} catch (error) {
|
|
236
|
+
if (task.attempts < this._options.groups.maxRetries) {
|
|
237
|
+
this.emit("retry", { task, error, attempt: task.attempts })
|
|
238
|
+
this._inFlight--
|
|
239
|
+
await this._redis.lPush(`queue:groups:${task.groupKey}`, JSON.stringify(task))
|
|
240
|
+
} else {
|
|
241
|
+
this.emit("failed", { task, error })
|
|
242
|
+
this._settle()
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
_settle() {
|
|
248
|
+
this._inFlight--
|
|
249
|
+
this._totalSettled++
|
|
250
|
+
if (this._inFlight === 0 && this._totalSettled > 0) this.emit("drain")
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async _periodicCleanup() {
|
|
254
|
+
try {
|
|
255
|
+
if (!this._redis.isOpen) return
|
|
256
|
+
const groupKeys = Array.from(this._groupWorkers.keys())
|
|
257
|
+
for (const groupKey of groupKeys) {
|
|
258
|
+
const length = await this._redis.lLen(`queue:groups:${groupKey}`)
|
|
259
|
+
if (length === 0) {
|
|
260
|
+
const keyExists = await this._redis.exists(`queue:groups:${groupKey}`)
|
|
261
|
+
if (keyExists) await this._redis.del(`queue:groups:${groupKey}`)
|
|
262
|
+
const groupWorkers = this._groupWorkers.get(groupKey)
|
|
263
|
+
if (groupWorkers) {
|
|
264
|
+
groupWorkers.clear()
|
|
265
|
+
this._groupWorkers.delete(groupKey)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
} catch {}
|
|
270
|
+
}
|
|
271
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./queue.js";
|