@objectstack/service-queue 4.0.3 → 4.0.4

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @objectstack/service-queue@4.0.3 build /home/runner/work/framework/framework/packages/services/service-queue
2
+ > @objectstack/service-queue@4.0.4 build /home/runner/work/framework/framework/packages/services/service-queue
3
3
  > tsup --config ../../../tsup.config.ts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -10,13 +10,13 @@
10
10
  CLI Cleaning output folder
11
11
  ESM Build start
12
12
  CJS Build start
13
- ESM dist/index.js 2.78 KB
14
- ESM dist/index.js.map 8.33 KB
15
- ESM ⚡️ Build success in 49ms
16
13
  CJS dist/index.cjs 3.89 KB
17
14
  CJS dist/index.cjs.map 8.90 KB
18
- CJS ⚡️ Build success in 59ms
15
+ CJS ⚡️ Build success in 94ms
16
+ ESM dist/index.js 2.78 KB
17
+ ESM dist/index.js.map 8.33 KB
18
+ ESM ⚡️ Build success in 96ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 6379ms
20
+ DTS ⚡️ Build success in 14371ms
21
21
  DTS dist/index.d.ts 3.63 KB
22
22
  DTS dist/index.d.cts 3.63 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @objectstack/service-queue
2
2
 
3
+ ## 4.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [326b66b]
8
+ - @objectstack/spec@4.0.4
9
+ - @objectstack/core@4.0.4
10
+
3
11
  ## 4.0.3
4
12
 
5
13
  ### Patch Changes
package/README.md ADDED
@@ -0,0 +1,453 @@
1
+ # @objectstack/service-queue
2
+
3
+ Queue Service for ObjectStack — implements `IQueueService` with in-memory and BullMQ adapters.
4
+
5
+ ## Features
6
+
7
+ - **Multiple Adapters**: In-memory (development) and BullMQ/Redis (production)
8
+ - **Job Queues**: Organize work into named queues with priorities
9
+ - **Worker Pools**: Process jobs concurrently with configurable workers
10
+ - **Retry Logic**: Automatic retry with exponential backoff
11
+ - **Job Scheduling**: Delay job execution or schedule for future
12
+ - **Progress Tracking**: Track job progress and completion
13
+ - **Job Events**: Listen to job lifecycle events (active, completed, failed)
14
+ - **Rate Limiting**: Control job processing rate
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ pnpm add @objectstack/service-queue
20
+ ```
21
+
22
+ For BullMQ adapter (production):
23
+ ```bash
24
+ pnpm add bullmq ioredis
25
+ ```
26
+
27
+ ## Basic Usage
28
+
29
+ ```typescript
30
+ import { defineStack } from '@objectstack/spec';
31
+ import { ServiceQueue } from '@objectstack/service-queue';
32
+
33
+ const stack = defineStack({
34
+ services: [
35
+ ServiceQueue.configure({
36
+ adapter: 'memory', // or 'bullmq'
37
+ defaultQueue: 'default',
38
+ }),
39
+ ],
40
+ });
41
+ ```
42
+
43
+ ## Configuration
44
+
45
+ ### In-Memory Adapter (Development)
46
+
47
+ ```typescript
48
+ ServiceQueue.configure({
49
+ adapter: 'memory',
50
+ concurrency: 5, // Max concurrent jobs
51
+ });
52
+ ```
53
+
54
+ ### BullMQ Adapter (Production)
55
+
56
+ ```typescript
57
+ ServiceQueue.configure({
58
+ adapter: 'bullmq',
59
+ redis: {
60
+ host: 'localhost',
61
+ port: 6379,
62
+ password: process.env.REDIS_PASSWORD,
63
+ },
64
+ queues: {
65
+ default: { concurrency: 10 },
66
+ email: { concurrency: 5, rateLimit: { max: 100, duration: 60000 } },
67
+ reports: { concurrency: 2 },
68
+ },
69
+ });
70
+ ```
71
+
72
+ ## Service API
73
+
74
+ ```typescript
75
+ // Get queue service
76
+ const queue = kernel.getService<IQueueService>('queue');
77
+ ```
78
+
79
+ ### Adding Jobs
80
+
81
+ ```typescript
82
+ // Add a simple job
83
+ await queue.add('email', 'send_welcome', {
84
+ to: 'user@example.com',
85
+ template: 'welcome',
86
+ });
87
+
88
+ // Add job with options
89
+ await queue.add('reports', 'generate_monthly', {
90
+ month: '2024-01',
91
+ format: 'pdf',
92
+ }, {
93
+ priority: 1, // Higher number = higher priority
94
+ attempts: 3, // Retry up to 3 times
95
+ backoff: {
96
+ type: 'exponential',
97
+ delay: 1000,
98
+ },
99
+ });
100
+
101
+ // Add delayed job (runs in 1 hour)
102
+ await queue.add('notifications', 'reminder', {
103
+ userId: '123',
104
+ message: 'Don't forget!',
105
+ }, {
106
+ delay: 3600000, // 1 hour in milliseconds
107
+ });
108
+
109
+ // Schedule job for specific time
110
+ await queue.add('cleanup', 'old_files', {}, {
111
+ timestamp: new Date('2024-12-31T23:59:59Z').getTime(),
112
+ });
113
+ ```
114
+
115
+ ### Processing Jobs
116
+
117
+ ```typescript
118
+ // Register a job processor
119
+ queue.process('email', async (job) => {
120
+ console.log('Processing email job:', job.data);
121
+
122
+ // Access job data
123
+ const { to, template } = job.data;
124
+
125
+ // Update progress
126
+ await job.updateProgress(25);
127
+
128
+ // Send email
129
+ await sendEmail(to, template);
130
+
131
+ await job.updateProgress(100);
132
+
133
+ // Return result
134
+ return { sent: true, messageId: 'msg_123' };
135
+ });
136
+
137
+ // Process with concurrency
138
+ queue.process('reports', 5, async (job) => {
139
+ // Up to 5 reports generated concurrently
140
+ return await generateReport(job.data);
141
+ });
142
+
143
+ // Process with named handler
144
+ queue.process('default', 'calculate_metrics', async (job) => {
145
+ return await calculateMetrics(job.data);
146
+ });
147
+ ```
148
+
149
+ ### Job Management
150
+
151
+ ```typescript
152
+ // Get job by ID
153
+ const job = await queue.getJob('email', 'job_abc123');
154
+
155
+ // Get job status
156
+ const status = await job.getState();
157
+ // 'waiting' | 'active' | 'completed' | 'failed' | 'delayed'
158
+
159
+ // Remove job
160
+ await queue.removeJob('email', 'job_abc123');
161
+
162
+ // Retry failed job
163
+ await queue.retryJob('email', 'job_abc123');
164
+
165
+ // Get job result
166
+ const result = await job.returnvalue;
167
+ ```
168
+
169
+ ### Queue Operations
170
+
171
+ ```typescript
172
+ // Pause queue (stop processing new jobs)
173
+ await queue.pause('email');
174
+
175
+ // Resume queue
176
+ await queue.resume('email');
177
+
178
+ // Clear all jobs in queue
179
+ await queue.clear('email');
180
+
181
+ // Get queue statistics
182
+ const stats = await queue.getStats('email');
183
+ // {
184
+ // waiting: 45,
185
+ // active: 5,
186
+ // completed: 1250,
187
+ // failed: 12,
188
+ // delayed: 3
189
+ // }
190
+ ```
191
+
192
+ ## Advanced Features
193
+
194
+ ### Job Events
195
+
196
+ ```typescript
197
+ // Listen to job lifecycle events
198
+ queue.on('email', 'completed', async (job, result) => {
199
+ console.log(`Email sent: ${result.messageId}`);
200
+ });
201
+
202
+ queue.on('email', 'failed', async (job, error) => {
203
+ console.error(`Email failed: ${error.message}`);
204
+ // Send alert to admin
205
+ });
206
+
207
+ queue.on('email', 'progress', async (job, progress) => {
208
+ console.log(`Email progress: ${progress}%`);
209
+ });
210
+
211
+ queue.on('email', 'active', async (job) => {
212
+ console.log(`Email job started: ${job.id}`);
213
+ });
214
+ ```
215
+
216
+ ### Bulk Operations
217
+
218
+ ```typescript
219
+ // Add multiple jobs at once
220
+ await queue.addBulk('email', [
221
+ { name: 'send_welcome', data: { to: 'user1@example.com' } },
222
+ { name: 'send_welcome', data: { to: 'user2@example.com' } },
223
+ { name: 'send_welcome', data: { to: 'user3@example.com' } },
224
+ ]);
225
+
226
+ // Get multiple jobs
227
+ const jobs = await queue.getJobs('email', ['waiting', 'active']);
228
+ ```
229
+
230
+ ### Job Patterns
231
+
232
+ #### Worker Pattern
233
+
234
+ ```typescript
235
+ // Dedicated worker process
236
+ queue.process('heavy_processing', async (job) => {
237
+ // CPU-intensive work
238
+ const result = await processLargeDataset(job.data);
239
+ return result;
240
+ });
241
+ ```
242
+
243
+ #### Fan-Out Pattern
244
+
245
+ ```typescript
246
+ // Split work across multiple jobs
247
+ await queue.add('orchestrator', 'process_batch', { batchId: '123' });
248
+
249
+ queue.process('orchestrator', async (job) => {
250
+ const items = await loadBatchItems(job.data.batchId);
251
+
252
+ // Create sub-jobs for each item
253
+ for (const item of items) {
254
+ await queue.add('worker', 'process_item', { item });
255
+ }
256
+ });
257
+
258
+ queue.process('worker', async (job) => {
259
+ return await processItem(job.data.item);
260
+ });
261
+ ```
262
+
263
+ #### Priority Queues
264
+
265
+ ```typescript
266
+ // High priority
267
+ await queue.add('tasks', 'urgent', data, { priority: 10 });
268
+
269
+ // Normal priority
270
+ await queue.add('tasks', 'normal', data, { priority: 5 });
271
+
272
+ // Low priority
273
+ await queue.add('tasks', 'background', data, { priority: 1 });
274
+ ```
275
+
276
+ ### Rate Limiting
277
+
278
+ ```typescript
279
+ // Limit queue to 100 jobs per minute
280
+ ServiceQueue.configure({
281
+ adapter: 'bullmq',
282
+ queues: {
283
+ api_calls: {
284
+ concurrency: 5,
285
+ rateLimit: {
286
+ max: 100,
287
+ duration: 60000, // 1 minute
288
+ },
289
+ },
290
+ },
291
+ });
292
+ ```
293
+
294
+ ### Repeatable Jobs
295
+
296
+ ```typescript
297
+ // Add cron-based repeatable job
298
+ await queue.addRepeatable('cleanup', 'old_sessions', {}, {
299
+ cron: '0 2 * * *', // Daily at 2 AM
300
+ });
301
+
302
+ // Add interval-based repeatable job
303
+ await queue.addRepeatable('sync', 'data', {}, {
304
+ every: 300000, // Every 5 minutes
305
+ });
306
+
307
+ // Remove repeatable job
308
+ await queue.removeRepeatable('cleanup', 'old_sessions');
309
+ ```
310
+
311
+ ## Common Use Cases
312
+
313
+ ### Email Queue
314
+
315
+ ```typescript
316
+ queue.process('email', async (job) => {
317
+ const { to, subject, body, template } = job.data;
318
+
319
+ try {
320
+ const result = await emailProvider.send({
321
+ to,
322
+ subject,
323
+ html: renderTemplate(template, job.data),
324
+ });
325
+
326
+ return { messageId: result.id, sentAt: new Date() };
327
+ } catch (error) {
328
+ // Throw error to trigger retry
329
+ throw new Error(`Failed to send email: ${error.message}`);
330
+ }
331
+ });
332
+
333
+ // Add email job
334
+ await queue.add('email', 'welcome', {
335
+ to: 'newuser@example.com',
336
+ template: 'welcome',
337
+ name: 'John Doe',
338
+ }, {
339
+ attempts: 3,
340
+ backoff: { type: 'exponential', delay: 5000 },
341
+ });
342
+ ```
343
+
344
+ ### Report Generation
345
+
346
+ ```typescript
347
+ queue.process('reports', async (job) => {
348
+ const { reportType, userId, dateRange } = job.data;
349
+
350
+ await job.updateProgress(10);
351
+
352
+ // Fetch data
353
+ const data = await fetchReportData(reportType, dateRange);
354
+
355
+ await job.updateProgress(50);
356
+
357
+ // Generate report
358
+ const report = await generatePDF(data);
359
+
360
+ await job.updateProgress(90);
361
+
362
+ // Upload to storage
363
+ const url = await uploadReport(report);
364
+
365
+ await job.updateProgress(100);
366
+
367
+ // Notify user
368
+ await notifyUser(userId, { reportUrl: url });
369
+
370
+ return { url, size: report.length };
371
+ });
372
+ ```
373
+
374
+ ### Webhook Processing
375
+
376
+ ```typescript
377
+ queue.process('webhooks', async (job) => {
378
+ const { url, payload, headers } = job.data;
379
+
380
+ const response = await fetch(url, {
381
+ method: 'POST',
382
+ headers,
383
+ body: JSON.stringify(payload),
384
+ });
385
+
386
+ if (!response.ok) {
387
+ throw new Error(`Webhook failed: ${response.status}`);
388
+ }
389
+
390
+ return { status: response.status, responseTime: Date.now() - job.timestamp };
391
+ });
392
+ ```
393
+
394
+ ## REST API Endpoints
395
+
396
+ ```
397
+ POST /api/v1/queues/:queue/jobs # Add job
398
+ GET /api/v1/queues/:queue/jobs/:id # Get job
399
+ DELETE /api/v1/queues/:queue/jobs/:id # Remove job
400
+ POST /api/v1/queues/:queue/jobs/:id/retry # Retry failed job
401
+ GET /api/v1/queues/:queue/stats # Get queue stats
402
+ POST /api/v1/queues/:queue/pause # Pause queue
403
+ POST /api/v1/queues/:queue/resume # Resume queue
404
+ DELETE /api/v1/queues/:queue # Clear queue
405
+ ```
406
+
407
+ ## Best Practices
408
+
409
+ 1. **Idempotent Jobs**: Design jobs to be safely retried
410
+ 2. **Error Handling**: Always handle errors and throw to trigger retry
411
+ 3. **Progress Updates**: Update progress for long-running jobs
412
+ 4. **Resource Limits**: Set appropriate concurrency limits
413
+ 5. **Job Data**: Keep job data small (< 1MB)
414
+ 6. **Monitoring**: Track queue metrics and job failure rates
415
+ 7. **Cleanup**: Remove completed jobs periodically
416
+
417
+ ## Performance Considerations
418
+
419
+ - **Concurrency**: Tune based on system resources and external API limits
420
+ - **Rate Limiting**: Prevent overwhelming external services
421
+ - **Job Size**: Keep job payloads small for faster serialization
422
+ - **Redis Connection**: Use connection pooling for BullMQ
423
+ - **Queue Organization**: Use separate queues for different job types
424
+
425
+ ## Contract Implementation
426
+
427
+ Implements `IQueueService` from `@objectstack/spec/contracts`:
428
+
429
+ ```typescript
430
+ interface IQueueService {
431
+ add(queue: string, name: string, data: any, options?: JobOptions): Promise<Job>;
432
+ addBulk(queue: string, jobs: JobDefinition[]): Promise<Job[]>;
433
+ process(queue: string, handler: JobHandler): void;
434
+ getJob(queue: string, jobId: string): Promise<Job | null>;
435
+ removeJob(queue: string, jobId: string): Promise<void>;
436
+ retryJob(queue: string, jobId: string): Promise<void>;
437
+ getStats(queue: string): Promise<QueueStats>;
438
+ pause(queue: string): Promise<void>;
439
+ resume(queue: string): Promise<void>;
440
+ clear(queue: string): Promise<void>;
441
+ on(queue: string, event: JobEvent, handler: EventHandler): void;
442
+ }
443
+ ```
444
+
445
+ ## License
446
+
447
+ Apache-2.0
448
+
449
+ ## See Also
450
+
451
+ - [BullMQ Documentation](https://docs.bullmq.io/)
452
+ - [@objectstack/spec/contracts](../../spec/src/contracts/)
453
+ - [Queue Patterns Guide](/content/docs/guides/queues/)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@objectstack/service-queue",
3
- "version": "4.0.3",
3
+ "version": "4.0.4",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Queue Service for ObjectStack — implements IQueueService with in-memory and BullMQ adapters",
6
6
  "type": "module",
@@ -14,8 +14,8 @@
14
14
  }
15
15
  },
16
16
  "dependencies": {
17
- "@objectstack/core": "4.0.3",
18
- "@objectstack/spec": "4.0.3"
17
+ "@objectstack/core": "4.0.4",
18
+ "@objectstack/spec": "4.0.4"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^25.6.0",