@prsm/queue 2.1.1 → 3.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 CHANGED
@@ -110,7 +110,7 @@ Throw an error to trigger retry. After `maxRetries`, the task fails permanently.
110
110
 
111
111
  ## Grouped Queues
112
112
 
113
- Isolated concurrency per key - perfect for per-tenant throttling.
113
+ Isolated concurrency per key - perfect for per-tenant throttling. Pass `{ group }` as the second argument to `push` or `pushAndWait`.
114
114
 
115
115
  ```js
116
116
  const queue = new Queue({
@@ -124,11 +124,11 @@ queue.process(async (payload) => {
124
124
 
125
125
  await queue.ready()
126
126
 
127
- await queue.group('tenant-123').push({ action: 'sync' })
128
- await queue.group('tenant-456').push({ action: 'sync' })
127
+ await queue.push({ action: 'sync' }, { group: 'tenant-123' })
128
+ await queue.push({ action: 'sync' }, { group: 'tenant-456' })
129
129
  ```
130
130
 
131
- Each tenant processes independently. One slow tenant won't block others. Total concurrent tasks across all tenants is capped by `concurrency`.
131
+ Each tenant processes independently. One slow tenant won't block others. Total concurrent tasks across all tenants is capped by `concurrency`. When the group is conditional, just omit the option - no branching needed.
132
132
 
133
133
  ## Events
134
134
 
@@ -147,7 +147,7 @@ queue.on('drain', () => {})
147
147
  uuid: string,
148
148
  payload: any,
149
149
  createdAt: number,
150
- groupKey?: string, // present when pushed via group()
150
+ groupKey?: string, // present when pushed with { group }
151
151
  attempts: number
152
152
  }
153
153
  ```
@@ -169,7 +169,7 @@ queue.process(async ({ prompt }) => {
169
169
 
170
170
  app.post('/api/generate', async (req, res) => {
171
171
  const { tenantId, prompt } = req.body
172
- const taskId = await queue.group(tenantId).push({ prompt })
172
+ const taskId = await queue.push({ prompt }, { group: tenantId })
173
173
  res.json({ queued: true, taskId })
174
174
  })
175
175
  ```
@@ -202,10 +202,10 @@ queue.on('failed', ({ task, error }) => {
202
202
  })
203
203
 
204
204
  mesh.exposeCommand('generate-report', async (ctx) => {
205
- const taskId = await queue.group(ctx.connection.id).push({
205
+ const taskId = await queue.push({
206
206
  connectionId: ctx.connection.id,
207
207
  ...ctx.payload,
208
- })
208
+ }, { group: ctx.connection.id })
209
209
  return { queued: true, taskId }
210
210
  })
211
211
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prsm/queue",
3
- "version": "2.1.1",
3
+ "version": "3.0.0",
4
4
  "description": "Redis-backed distributed task queue with grouped concurrency, retries, and rate limiting",
5
5
  "type": "module",
6
6
  "exports": {
package/src/queue.js CHANGED
@@ -151,14 +151,17 @@ export default class Queue extends EventEmitter {
151
151
 
152
152
  /**
153
153
  * @param {any} payload
154
+ * @param {{ group?: string }} [options]
154
155
  * @returns {Promise<string>}
155
156
  */
156
- async push(payload) {
157
+ async push(payload, { group } = {}) {
157
158
  if (this._closed) throw new Error("Queue is closed")
158
- const task = { uuid: randomUUID(), payload, createdAt: Date.now(), attempts: 0 }
159
+ const task = group
160
+ ? { uuid: randomUUID(), payload, createdAt: Date.now(), groupKey: group, attempts: 0 }
161
+ : { uuid: randomUUID(), payload, createdAt: Date.now(), attempts: 0 }
159
162
  this._pushed++
160
163
  try {
161
- await this._redis.lPush("queue:tasks", JSON.stringify(task))
164
+ await this._enqueue(task, group)
162
165
  } catch (err) {
163
166
  this._pushed--
164
167
  throw err
@@ -169,16 +172,18 @@ export default class Queue extends EventEmitter {
169
172
 
170
173
  /**
171
174
  * @param {any} payload
172
- * @param {number|string} [timeout] - max time to wait for result, ms or string like "30s" (default 0, no limit)
175
+ * @param {{ group?: string, timeout?: number|string }} [options]
173
176
  * @returns {Promise<any>}
174
177
  */
175
- pushAndWait(payload, timeout = 0) {
178
+ pushAndWait(payload, { group, timeout = 0 } = {}) {
176
179
  if (this._closed) return Promise.reject(new Error("Queue is closed"))
177
- const task = { uuid: randomUUID(), payload, createdAt: Date.now(), attempts: 0 }
180
+ const task = group
181
+ ? { uuid: randomUUID(), payload, createdAt: Date.now(), groupKey: group, attempts: 0 }
182
+ : { uuid: randomUUID(), payload, createdAt: Date.now(), attempts: 0 }
178
183
  this._pushed++
179
184
  const result = this._awaitTask(task.uuid, timeout)
180
185
  result.catch(() => {})
181
- return this._redis.lPush("queue:tasks", JSON.stringify(task)).then(() => {
186
+ return this._enqueue(task, group).then(() => {
182
187
  this.emit("new", { task })
183
188
  return result
184
189
  }, (err) => {
@@ -187,44 +192,17 @@ export default class Queue extends EventEmitter {
187
192
  })
188
193
  }
189
194
 
190
- /**
191
- * @param {string} key
192
- * @returns {{ push: (payload: any) => Promise<string>, pushAndWait: (payload: any, timeout?: number|string) => Promise<any> }}
193
- */
194
- group(key) {
195
- const makeTask = (payload) => {
196
- if (this._closed) throw new Error("Queue is closed")
197
- return { uuid: randomUUID(), payload, createdAt: Date.now(), groupKey: key, attempts: 0 }
198
- }
199
-
200
- const commit = (task) => {
201
- return this._redis.lPush(`queue:groups:${key}`, JSON.stringify(task)).then(async () => {
202
- this.emit("new", { task })
203
- if (!this._groupWorkers.has(key)) {
204
- this._groupWorkers.set(key, new Map())
205
- this._groupInFlight.set(key, 0)
206
- await this._startGroupWorkers(key)
207
- }
208
- }, (err) => {
209
- this._pushed--
210
- throw err
211
- })
212
- }
213
-
214
- return {
215
- push: async (payload) => {
216
- const task = makeTask(payload)
217
- this._pushed++
218
- await commit(task)
219
- return task.uuid
220
- },
221
- pushAndWait: (payload, timeout = 0) => {
222
- const task = makeTask(payload)
223
- this._pushed++
224
- const result = this._awaitTask(task.uuid, timeout)
225
- result.catch(() => {})
226
- return commit(task).then(() => result)
227
- },
195
+ /** @private */
196
+ async _enqueue(task, group) {
197
+ if (group) {
198
+ await this._redis.lPush(`queue:groups:${group}`, JSON.stringify(task))
199
+ if (!this._groupWorkers.has(group)) {
200
+ this._groupWorkers.set(group, new Map())
201
+ this._groupInFlight.set(group, 0)
202
+ await this._startGroupWorkers(group)
203
+ }
204
+ } else {
205
+ await this._redis.lPush("queue:tasks", JSON.stringify(task))
228
206
  }
229
207
  }
230
208
 
package/types/queue.d.ts CHANGED
@@ -2357,23 +2357,23 @@ export default class Queue extends EventEmitter<[never]> {
2357
2357
  process(handler: TaskHandler): void;
2358
2358
  /**
2359
2359
  * @param {any} payload
2360
+ * @param {{ group?: string }} [options]
2360
2361
  * @returns {Promise<string>}
2361
2362
  */
2362
- push(payload: any): Promise<string>;
2363
+ push(payload: any, { group }?: {
2364
+ group?: string;
2365
+ }): Promise<string>;
2363
2366
  /**
2364
2367
  * @param {any} payload
2365
- * @param {number|string} [timeout] - max time to wait for result, ms or string like "30s" (default 0, no limit)
2368
+ * @param {{ group?: string, timeout?: number|string }} [options]
2366
2369
  * @returns {Promise<any>}
2367
2370
  */
2368
- pushAndWait(payload: any, timeout?: number | string): Promise<any>;
2369
- /**
2370
- * @param {string} key
2371
- * @returns {{ push: (payload: any) => Promise<string>, pushAndWait: (payload: any, timeout?: number|string) => Promise<any> }}
2372
- */
2373
- group(key: string): {
2374
- push: (payload: any) => Promise<string>;
2375
- pushAndWait: (payload: any, timeout?: number | string) => Promise<any>;
2376
- };
2371
+ pushAndWait(payload: any, { group, timeout }?: {
2372
+ group?: string;
2373
+ timeout?: number | string;
2374
+ }): Promise<any>;
2375
+ /** @private */
2376
+ private _enqueue;
2377
2377
  /** @private */
2378
2378
  private _awaitTask;
2379
2379
  /** @returns {Promise<void>} */