servcraft 0.1.0 → 0.1.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.
- package/.claude/settings.local.json +29 -0
- package/.github/CODEOWNERS +18 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
- package/.github/dependabot.yml +59 -0
- package/.github/workflows/ci.yml +188 -0
- package/.github/workflows/release.yml +195 -0
- package/AUDIT.md +602 -0
- package/README.md +1070 -1
- package/dist/cli/index.cjs +2026 -2168
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +2026 -2168
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +595 -616
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +114 -52
- package/dist/index.d.ts +114 -52
- package/dist/index.js +595 -616
- package/dist/index.js.map +1 -1
- package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
- package/docs/DATABASE_MULTI_ORM.md +399 -0
- package/docs/PHASE1_BREAKDOWN.md +346 -0
- package/docs/PROGRESS.md +550 -0
- package/docs/modules/ANALYTICS.md +226 -0
- package/docs/modules/API-VERSIONING.md +252 -0
- package/docs/modules/AUDIT.md +192 -0
- package/docs/modules/AUTH.md +431 -0
- package/docs/modules/CACHE.md +346 -0
- package/docs/modules/EMAIL.md +254 -0
- package/docs/modules/FEATURE-FLAG.md +291 -0
- package/docs/modules/I18N.md +294 -0
- package/docs/modules/MEDIA-PROCESSING.md +281 -0
- package/docs/modules/MFA.md +266 -0
- package/docs/modules/NOTIFICATION.md +311 -0
- package/docs/modules/OAUTH.md +237 -0
- package/docs/modules/PAYMENT.md +804 -0
- package/docs/modules/QUEUE.md +540 -0
- package/docs/modules/RATE-LIMIT.md +339 -0
- package/docs/modules/SEARCH.md +288 -0
- package/docs/modules/SECURITY.md +327 -0
- package/docs/modules/SESSION.md +382 -0
- package/docs/modules/SWAGGER.md +305 -0
- package/docs/modules/UPLOAD.md +296 -0
- package/docs/modules/USER.md +505 -0
- package/docs/modules/VALIDATION.md +294 -0
- package/docs/modules/WEBHOOK.md +270 -0
- package/docs/modules/WEBSOCKET.md +691 -0
- package/package.json +53 -38
- package/prisma/schema.prisma +395 -1
- package/src/cli/commands/add-module.ts +520 -87
- package/src/cli/commands/db.ts +3 -4
- package/src/cli/commands/docs.ts +256 -6
- package/src/cli/commands/generate.ts +12 -19
- package/src/cli/commands/init.ts +384 -214
- package/src/cli/index.ts +0 -4
- package/src/cli/templates/repository.ts +6 -1
- package/src/cli/templates/routes.ts +6 -21
- package/src/cli/utils/docs-generator.ts +6 -7
- package/src/cli/utils/env-manager.ts +717 -0
- package/src/cli/utils/field-parser.ts +16 -7
- package/src/cli/utils/interactive-prompt.ts +223 -0
- package/src/cli/utils/template-manager.ts +346 -0
- package/src/config/database.config.ts +183 -0
- package/src/config/env.ts +0 -10
- package/src/config/index.ts +0 -14
- package/src/core/server.ts +1 -1
- package/src/database/adapters/mongoose.adapter.ts +132 -0
- package/src/database/adapters/prisma.adapter.ts +118 -0
- package/src/database/connection.ts +190 -0
- package/src/database/interfaces/database.interface.ts +85 -0
- package/src/database/interfaces/index.ts +7 -0
- package/src/database/interfaces/repository.interface.ts +129 -0
- package/src/database/models/mongoose/index.ts +7 -0
- package/src/database/models/mongoose/payment.schema.ts +347 -0
- package/src/database/models/mongoose/user.schema.ts +154 -0
- package/src/database/prisma.ts +1 -4
- package/src/database/redis.ts +101 -0
- package/src/database/repositories/mongoose/index.ts +7 -0
- package/src/database/repositories/mongoose/payment.repository.ts +380 -0
- package/src/database/repositories/mongoose/user.repository.ts +255 -0
- package/src/database/seed.ts +6 -1
- package/src/index.ts +9 -20
- package/src/middleware/security.ts +2 -6
- package/src/modules/analytics/analytics.routes.ts +80 -0
- package/src/modules/analytics/analytics.service.ts +364 -0
- package/src/modules/analytics/index.ts +18 -0
- package/src/modules/analytics/types.ts +180 -0
- package/src/modules/api-versioning/index.ts +15 -0
- package/src/modules/api-versioning/types.ts +86 -0
- package/src/modules/api-versioning/versioning.middleware.ts +120 -0
- package/src/modules/api-versioning/versioning.routes.ts +54 -0
- package/src/modules/api-versioning/versioning.service.ts +189 -0
- package/src/modules/audit/audit.repository.ts +206 -0
- package/src/modules/audit/audit.service.ts +27 -59
- package/src/modules/auth/auth.controller.ts +2 -2
- package/src/modules/auth/auth.middleware.ts +3 -9
- package/src/modules/auth/auth.routes.ts +10 -107
- package/src/modules/auth/auth.service.ts +126 -23
- package/src/modules/auth/index.ts +3 -4
- package/src/modules/cache/cache.service.ts +367 -0
- package/src/modules/cache/index.ts +10 -0
- package/src/modules/cache/types.ts +44 -0
- package/src/modules/email/email.service.ts +3 -10
- package/src/modules/email/templates.ts +2 -8
- package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
- package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
- package/src/modules/feature-flag/feature-flag.service.ts +566 -0
- package/src/modules/feature-flag/index.ts +20 -0
- package/src/modules/feature-flag/types.ts +192 -0
- package/src/modules/i18n/i18n.middleware.ts +186 -0
- package/src/modules/i18n/i18n.routes.ts +191 -0
- package/src/modules/i18n/i18n.service.ts +456 -0
- package/src/modules/i18n/index.ts +18 -0
- package/src/modules/i18n/types.ts +118 -0
- package/src/modules/media-processing/index.ts +17 -0
- package/src/modules/media-processing/media-processing.routes.ts +111 -0
- package/src/modules/media-processing/media-processing.service.ts +245 -0
- package/src/modules/media-processing/types.ts +156 -0
- package/src/modules/mfa/index.ts +20 -0
- package/src/modules/mfa/mfa.repository.ts +206 -0
- package/src/modules/mfa/mfa.routes.ts +595 -0
- package/src/modules/mfa/mfa.service.ts +572 -0
- package/src/modules/mfa/totp.ts +150 -0
- package/src/modules/mfa/types.ts +57 -0
- package/src/modules/notification/index.ts +20 -0
- package/src/modules/notification/notification.repository.ts +356 -0
- package/src/modules/notification/notification.service.ts +483 -0
- package/src/modules/notification/types.ts +119 -0
- package/src/modules/oauth/index.ts +20 -0
- package/src/modules/oauth/oauth.repository.ts +219 -0
- package/src/modules/oauth/oauth.routes.ts +446 -0
- package/src/modules/oauth/oauth.service.ts +293 -0
- package/src/modules/oauth/providers/apple.provider.ts +250 -0
- package/src/modules/oauth/providers/facebook.provider.ts +181 -0
- package/src/modules/oauth/providers/github.provider.ts +248 -0
- package/src/modules/oauth/providers/google.provider.ts +189 -0
- package/src/modules/oauth/providers/twitter.provider.ts +214 -0
- package/src/modules/oauth/types.ts +94 -0
- package/src/modules/payment/index.ts +19 -0
- package/src/modules/payment/payment.repository.ts +733 -0
- package/src/modules/payment/payment.routes.ts +390 -0
- package/src/modules/payment/payment.service.ts +354 -0
- package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
- package/src/modules/payment/providers/paypal.provider.ts +190 -0
- package/src/modules/payment/providers/stripe.provider.ts +215 -0
- package/src/modules/payment/types.ts +140 -0
- package/src/modules/queue/cron.ts +438 -0
- package/src/modules/queue/index.ts +87 -0
- package/src/modules/queue/queue.routes.ts +600 -0
- package/src/modules/queue/queue.service.ts +842 -0
- package/src/modules/queue/types.ts +222 -0
- package/src/modules/queue/workers.ts +366 -0
- package/src/modules/rate-limit/index.ts +59 -0
- package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
- package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
- package/src/modules/rate-limit/rate-limit.service.ts +348 -0
- package/src/modules/rate-limit/stores/memory.store.ts +165 -0
- package/src/modules/rate-limit/stores/redis.store.ts +322 -0
- package/src/modules/rate-limit/types.ts +153 -0
- package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
- package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
- package/src/modules/search/adapters/memory.adapter.ts +278 -0
- package/src/modules/search/index.ts +21 -0
- package/src/modules/search/search.service.ts +234 -0
- package/src/modules/search/types.ts +214 -0
- package/src/modules/security/index.ts +40 -0
- package/src/modules/security/sanitize.ts +223 -0
- package/src/modules/security/security-audit.service.ts +388 -0
- package/src/modules/security/security.middleware.ts +398 -0
- package/src/modules/session/index.ts +3 -0
- package/src/modules/session/session.repository.ts +159 -0
- package/src/modules/session/session.service.ts +340 -0
- package/src/modules/session/types.ts +38 -0
- package/src/modules/swagger/index.ts +7 -1
- package/src/modules/swagger/schema-builder.ts +16 -4
- package/src/modules/swagger/swagger.service.ts +9 -10
- package/src/modules/swagger/types.ts +0 -2
- package/src/modules/upload/index.ts +14 -0
- package/src/modules/upload/types.ts +83 -0
- package/src/modules/upload/upload.repository.ts +199 -0
- package/src/modules/upload/upload.routes.ts +311 -0
- package/src/modules/upload/upload.service.ts +448 -0
- package/src/modules/user/index.ts +3 -3
- package/src/modules/user/user.controller.ts +15 -9
- package/src/modules/user/user.repository.ts +237 -113
- package/src/modules/user/user.routes.ts +39 -164
- package/src/modules/user/user.service.ts +4 -3
- package/src/modules/validation/validator.ts +12 -17
- package/src/modules/webhook/index.ts +91 -0
- package/src/modules/webhook/retry.ts +196 -0
- package/src/modules/webhook/signature.ts +135 -0
- package/src/modules/webhook/types.ts +181 -0
- package/src/modules/webhook/webhook.repository.ts +358 -0
- package/src/modules/webhook/webhook.routes.ts +442 -0
- package/src/modules/webhook/webhook.service.ts +457 -0
- package/src/modules/websocket/features.ts +504 -0
- package/src/modules/websocket/index.ts +106 -0
- package/src/modules/websocket/middlewares.ts +298 -0
- package/src/modules/websocket/types.ts +181 -0
- package/src/modules/websocket/websocket.service.ts +692 -0
- package/src/utils/errors.ts +7 -0
- package/src/utils/pagination.ts +4 -1
- package/tests/helpers/db-check.ts +79 -0
- package/tests/integration/auth-redis.test.ts +94 -0
- package/tests/integration/cache-redis.test.ts +387 -0
- package/tests/integration/mongoose-repositories.test.ts +410 -0
- package/tests/integration/payment-prisma.test.ts +637 -0
- package/tests/integration/queue-bullmq.test.ts +417 -0
- package/tests/integration/user-prisma.test.ts +441 -0
- package/tests/integration/websocket-socketio.test.ts +552 -0
- package/tests/setup.ts +11 -9
- package/vitest.config.ts +3 -8
- package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
- package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
- package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
- package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
- package/npm-cache/_cacache/index-v5/58/94/c2cba79e0f16b4c10e95a87e32255741149e8222cc314a476aab67c39cc0 +0 -5
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
# Queue Module Documentation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Queue module provides robust background job processing using **BullMQ** with **Redis** persistence. This ensures jobs are never lost, even if the server restarts, and supports horizontal scaling across multiple instances.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Persistent Storage**: All jobs stored in Redis, surviving server restarts
|
|
10
|
+
- **Automatic Retries**: Configurable retry strategies with exponential backoff
|
|
11
|
+
- **Job Prioritization**: Critical, high, normal, and low priority levels
|
|
12
|
+
- **Delayed Jobs**: Schedule jobs to run at specific times
|
|
13
|
+
- **Repeatable Jobs**: Cron-based recurring job scheduling
|
|
14
|
+
- **Concurrency Control**: Limit parallel execution per worker
|
|
15
|
+
- **Real-time Events**: Monitor job lifecycle with events
|
|
16
|
+
- **Metrics Collection**: Track throughput, success rates, and processing times
|
|
17
|
+
- **Multi-instance Safe**: Safe for horizontal scaling with multiple server instances
|
|
18
|
+
- **Graceful Shutdown**: Wait for active jobs before closing
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
BullMQ and ioredis are already included in ServCraft dependencies:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install bullmq ioredis
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Configuration
|
|
29
|
+
|
|
30
|
+
### Environment Variables
|
|
31
|
+
|
|
32
|
+
```env
|
|
33
|
+
# Redis connection for queues
|
|
34
|
+
REDIS_HOST=localhost
|
|
35
|
+
REDIS_PORT=6379
|
|
36
|
+
REDIS_PASSWORD=your_password
|
|
37
|
+
REDIS_DB=0
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Service Configuration
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { QueueService } from '@servcraft/queue';
|
|
44
|
+
|
|
45
|
+
const queueService = new QueueService({
|
|
46
|
+
redis: {
|
|
47
|
+
host: process.env.REDIS_HOST || 'localhost',
|
|
48
|
+
port: parseInt(process.env.REDIS_PORT || '6379', 10),
|
|
49
|
+
password: process.env.REDIS_PASSWORD,
|
|
50
|
+
db: 0,
|
|
51
|
+
},
|
|
52
|
+
prefix: 'myapp:queue', // Redis key prefix
|
|
53
|
+
metrics: true, // Enable metrics collection
|
|
54
|
+
defaultJobOptions: {
|
|
55
|
+
attempts: 3,
|
|
56
|
+
backoff: {
|
|
57
|
+
type: 'exponential',
|
|
58
|
+
delay: 1000,
|
|
59
|
+
},
|
|
60
|
+
removeOnComplete: true,
|
|
61
|
+
removeOnFail: false,
|
|
62
|
+
timeout: 60000,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Basic Usage
|
|
68
|
+
|
|
69
|
+
### Creating a Queue
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// Create a queue (or get existing one)
|
|
73
|
+
const queue = queueService.createQueue('emails');
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Adding Jobs
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// Simple job
|
|
80
|
+
const job = await queueService.addJob('emails', 'send-welcome', {
|
|
81
|
+
userId: 'user-123',
|
|
82
|
+
email: 'user@example.com',
|
|
83
|
+
template: 'welcome',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
console.log(`Job ${job.id} added`);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Adding Jobs with Options
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// High priority job
|
|
93
|
+
await queueService.addJob('emails', 'send-critical', data, {
|
|
94
|
+
priority: 'critical', // critical, high, normal, low
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Delayed job (runs in 5 minutes)
|
|
98
|
+
await queueService.addJob('notifications', 'reminder', data, {
|
|
99
|
+
delay: 5 * 60 * 1000,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Job with custom retry settings
|
|
103
|
+
await queueService.addJob('webhooks', 'send-webhook', data, {
|
|
104
|
+
attempts: 5,
|
|
105
|
+
backoff: {
|
|
106
|
+
type: 'exponential',
|
|
107
|
+
delay: 2000, // 2s, 4s, 8s, 16s, 32s
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Repeatable job (every hour)
|
|
112
|
+
await queueService.addJob('reports', 'generate-daily', data, {
|
|
113
|
+
repeat: {
|
|
114
|
+
every: 60 * 60 * 1000, // every hour
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Cron job (every day at midnight)
|
|
119
|
+
await queueService.addJob('cleanup', 'remove-old-files', data, {
|
|
120
|
+
repeat: {
|
|
121
|
+
cron: '0 0 * * *',
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Adding Bulk Jobs
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const jobs = await queueService.addBulkJobs('emails', {
|
|
130
|
+
jobs: [
|
|
131
|
+
{ name: 'send-email', data: { userId: '1', type: 'welcome' } },
|
|
132
|
+
{ name: 'send-email', data: { userId: '2', type: 'welcome' } },
|
|
133
|
+
{ name: 'send-email', data: { userId: '3', type: 'welcome' } },
|
|
134
|
+
],
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
console.log(`Added ${jobs.length} jobs`);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Registering Workers
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import type { Job, Worker } from '@servcraft/queue';
|
|
144
|
+
|
|
145
|
+
// Simple worker
|
|
146
|
+
const emailWorker: Worker = {
|
|
147
|
+
name: 'send-welcome',
|
|
148
|
+
process: async (job: Job) => {
|
|
149
|
+
const { userId, email, template } = job.data;
|
|
150
|
+
|
|
151
|
+
// Process the job
|
|
152
|
+
await sendEmail(email, template);
|
|
153
|
+
|
|
154
|
+
return { sent: true, email };
|
|
155
|
+
},
|
|
156
|
+
concurrency: 5, // Process 5 emails in parallel
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
queueService.registerWorker('emails', emailWorker);
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Worker with Progress Updates
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
const imageWorker: Worker = {
|
|
166
|
+
name: 'process-image',
|
|
167
|
+
process: async (job: Job) => {
|
|
168
|
+
const { imageUrl, operations } = job.data;
|
|
169
|
+
|
|
170
|
+
// Update progress
|
|
171
|
+
await queueService.updateJobProgress('images', job.id, 0);
|
|
172
|
+
|
|
173
|
+
// Perform operations
|
|
174
|
+
for (let i = 0; i < operations.length; i++) {
|
|
175
|
+
await performOperation(operations[i]);
|
|
176
|
+
await queueService.updateJobProgress(
|
|
177
|
+
'images',
|
|
178
|
+
job.id,
|
|
179
|
+
Math.round(((i + 1) / operations.length) * 100)
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return { processed: true };
|
|
184
|
+
},
|
|
185
|
+
concurrency: 2,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
queueService.registerWorker('images', imageWorker);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Job Management
|
|
192
|
+
|
|
193
|
+
### Get Job by ID
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
const job = await queueService.getJob('emails', 'job-id-123');
|
|
197
|
+
console.log(job.status); // waiting, active, completed, failed, delayed
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### List Jobs
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
// List waiting jobs
|
|
204
|
+
const waitingJobs = await queueService.listJobs('emails', {
|
|
205
|
+
status: 'waiting',
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// List failed jobs
|
|
209
|
+
const failedJobs = await queueService.listJobs('emails', {
|
|
210
|
+
status: 'failed',
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// List with filters
|
|
214
|
+
const filteredJobs = await queueService.listJobs('emails', {
|
|
215
|
+
status: ['waiting', 'active'],
|
|
216
|
+
name: 'send-welcome',
|
|
217
|
+
limit: 50,
|
|
218
|
+
offset: 0,
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Retry Failed Jobs
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
// Retry a specific failed job
|
|
226
|
+
await queueService.retryJob('emails', 'failed-job-id');
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Remove Jobs
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
// Remove a job (cannot remove active jobs)
|
|
233
|
+
await queueService.removeJob('emails', 'job-id');
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Clean Old Jobs
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
// Clean completed jobs older than 24 hours
|
|
240
|
+
const cleaned = await queueService.cleanJobs('emails', 'completed', 24 * 60 * 60 * 1000);
|
|
241
|
+
console.log(`Cleaned ${cleaned} jobs`);
|
|
242
|
+
|
|
243
|
+
// Clean both completed and failed jobs
|
|
244
|
+
await queueService.cleanJobs('emails', ['completed', 'failed'], 7 * 24 * 60 * 60 * 1000);
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Queue Operations
|
|
248
|
+
|
|
249
|
+
### Pause/Resume Queue
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
// Pause queue (stops processing new jobs)
|
|
253
|
+
await queueService.pauseQueue('emails');
|
|
254
|
+
|
|
255
|
+
// Resume queue
|
|
256
|
+
await queueService.resumeQueue('emails');
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Drain Queue
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
// Remove all waiting jobs
|
|
263
|
+
await queueService.drainQueue('emails');
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Obliterate Queue
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
// Completely remove queue and all its data
|
|
270
|
+
await queueService.obliterateQueue('emails');
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Statistics and Metrics
|
|
274
|
+
|
|
275
|
+
### Queue Statistics
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
const stats = await queueService.getStats('emails');
|
|
279
|
+
console.log({
|
|
280
|
+
waiting: stats.waiting,
|
|
281
|
+
active: stats.active,
|
|
282
|
+
completed: stats.completed,
|
|
283
|
+
failed: stats.failed,
|
|
284
|
+
delayed: stats.delayed,
|
|
285
|
+
paused: stats.paused,
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Queue Metrics
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
const metrics = await queueService.getMetrics('emails');
|
|
293
|
+
console.log({
|
|
294
|
+
totalProcessed: metrics.totalProcessed,
|
|
295
|
+
totalFailed: metrics.totalFailed,
|
|
296
|
+
successRate: metrics.successRate,
|
|
297
|
+
avgProcessingTime: metrics.avgProcessingTime,
|
|
298
|
+
currentConcurrency: metrics.currentConcurrency,
|
|
299
|
+
peakConcurrency: metrics.peakConcurrency,
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Events
|
|
304
|
+
|
|
305
|
+
### Subscribe to Job Events
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
// Listen for specific events
|
|
309
|
+
queueService.on('added', (event) => {
|
|
310
|
+
console.log(`Job ${event.jobId} added`);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
queueService.on('active', (event) => {
|
|
314
|
+
console.log(`Job ${event.jobId} started processing`);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
queueService.on('completed', (event) => {
|
|
318
|
+
console.log(`Job ${event.jobId} completed with:`, event.data);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
queueService.on('failed', (event) => {
|
|
322
|
+
console.log(`Job ${event.jobId} failed:`, event.data);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
queueService.on('progress', (event) => {
|
|
326
|
+
console.log(`Job ${event.jobId} progress:`, event.data);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
queueService.on('stalled', (event) => {
|
|
330
|
+
console.log(`Job ${event.jobId} stalled`);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Listen for all events
|
|
334
|
+
queueService.on('job:event', (event) => {
|
|
335
|
+
console.log(`Event ${event.event} for job ${event.jobId}`);
|
|
336
|
+
});
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Graceful Shutdown
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
// In your server shutdown handler
|
|
343
|
+
process.on('SIGTERM', async () => {
|
|
344
|
+
console.log('Shutting down...');
|
|
345
|
+
|
|
346
|
+
// Wait for active jobs to complete
|
|
347
|
+
await queueService.close();
|
|
348
|
+
|
|
349
|
+
console.log('Queue service closed');
|
|
350
|
+
process.exit(0);
|
|
351
|
+
});
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Common Use Cases
|
|
355
|
+
|
|
356
|
+
### Email Queue
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
// Add email job
|
|
360
|
+
await queueService.addJob('emails', 'send-email', {
|
|
361
|
+
to: 'user@example.com',
|
|
362
|
+
subject: 'Welcome!',
|
|
363
|
+
template: 'welcome',
|
|
364
|
+
data: { name: 'John' },
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// Email worker
|
|
368
|
+
queueService.registerWorker('emails', {
|
|
369
|
+
name: 'send-email',
|
|
370
|
+
process: async (job) => {
|
|
371
|
+
const { to, subject, template, data } = job.data;
|
|
372
|
+
await emailService.send(to, subject, template, data);
|
|
373
|
+
return { sent: true };
|
|
374
|
+
},
|
|
375
|
+
concurrency: 10,
|
|
376
|
+
});
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Webhook Delivery
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
// Add webhook job with retries
|
|
383
|
+
await queueService.addJob('webhooks', 'deliver', {
|
|
384
|
+
url: 'https://api.example.com/webhook',
|
|
385
|
+
payload: { event: 'user.created', data: { id: '123' } },
|
|
386
|
+
}, {
|
|
387
|
+
attempts: 5,
|
|
388
|
+
backoff: { type: 'exponential', delay: 1000 },
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// Webhook worker
|
|
392
|
+
queueService.registerWorker('webhooks', {
|
|
393
|
+
name: 'deliver',
|
|
394
|
+
process: async (job) => {
|
|
395
|
+
const { url, payload } = job.data;
|
|
396
|
+
const response = await fetch(url, {
|
|
397
|
+
method: 'POST',
|
|
398
|
+
headers: { 'Content-Type': 'application/json' },
|
|
399
|
+
body: JSON.stringify(payload),
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
if (!response.ok) {
|
|
403
|
+
throw new Error(`Webhook failed: ${response.status}`);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return { delivered: true, status: response.status };
|
|
407
|
+
},
|
|
408
|
+
concurrency: 5,
|
|
409
|
+
});
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Image Processing
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
// Add image processing job
|
|
416
|
+
await queueService.addJob('images', 'process', {
|
|
417
|
+
source: '/uploads/image.jpg',
|
|
418
|
+
operations: [
|
|
419
|
+
{ type: 'resize', width: 800 },
|
|
420
|
+
{ type: 'compress', quality: 80 },
|
|
421
|
+
],
|
|
422
|
+
output: '/processed/image.webp',
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// Image worker
|
|
426
|
+
queueService.registerWorker('images', {
|
|
427
|
+
name: 'process',
|
|
428
|
+
process: async (job) => {
|
|
429
|
+
const { source, operations, output } = job.data;
|
|
430
|
+
await imageProcessor.process(source, operations, output);
|
|
431
|
+
return { processed: true, output };
|
|
432
|
+
},
|
|
433
|
+
concurrency: 2, // CPU-intensive, limit concurrency
|
|
434
|
+
});
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Scheduled Reports
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
// Daily report at 6 AM
|
|
441
|
+
await queueService.addJob('reports', 'daily-summary', {
|
|
442
|
+
type: 'daily',
|
|
443
|
+
}, {
|
|
444
|
+
repeat: { cron: '0 6 * * *' },
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// Report worker
|
|
448
|
+
queueService.registerWorker('reports', {
|
|
449
|
+
name: 'daily-summary',
|
|
450
|
+
process: async (job) => {
|
|
451
|
+
const report = await generateReport(job.data.type);
|
|
452
|
+
await sendReportEmail(report);
|
|
453
|
+
return { generated: true };
|
|
454
|
+
},
|
|
455
|
+
concurrency: 1,
|
|
456
|
+
});
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
## Best Practices
|
|
460
|
+
|
|
461
|
+
1. **Use Meaningful Queue Names**: Group related jobs in named queues (e.g., `emails`, `webhooks`, `reports`)
|
|
462
|
+
|
|
463
|
+
2. **Set Appropriate Concurrency**: CPU-intensive tasks should have lower concurrency
|
|
464
|
+
|
|
465
|
+
3. **Configure Retries**: Always set retry attempts for jobs that may fail temporarily
|
|
466
|
+
|
|
467
|
+
4. **Use Exponential Backoff**: Prevents overwhelming external services during failures
|
|
468
|
+
|
|
469
|
+
5. **Clean Old Jobs**: Periodically clean completed/failed jobs to manage Redis memory
|
|
470
|
+
|
|
471
|
+
6. **Monitor Metrics**: Track success rates and processing times to identify issues
|
|
472
|
+
|
|
473
|
+
7. **Implement Graceful Shutdown**: Always wait for active jobs before closing
|
|
474
|
+
|
|
475
|
+
8. **Use Job Progress**: For long-running jobs, update progress to show status
|
|
476
|
+
|
|
477
|
+
## Troubleshooting
|
|
478
|
+
|
|
479
|
+
### Jobs Not Processing
|
|
480
|
+
|
|
481
|
+
1. Check Redis connection: `queueService.isConnected()`
|
|
482
|
+
2. Verify worker is registered for the job name
|
|
483
|
+
3. Check if queue is paused: `await queueService.getStats(queueName)`
|
|
484
|
+
|
|
485
|
+
### Jobs Stuck in Active State
|
|
486
|
+
|
|
487
|
+
Jobs may be stalled if workers crash. BullMQ automatically handles stalled jobs and retries them.
|
|
488
|
+
|
|
489
|
+
### High Memory Usage in Redis
|
|
490
|
+
|
|
491
|
+
Clean old completed/failed jobs regularly:
|
|
492
|
+
```typescript
|
|
493
|
+
await queueService.cleanJobs('queueName', ['completed', 'failed'], 7 * 24 * 60 * 60 * 1000);
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Connection Errors
|
|
497
|
+
|
|
498
|
+
Ensure Redis is running and accessible. Check connection info:
|
|
499
|
+
```typescript
|
|
500
|
+
console.log(queueService.getConnectionInfo());
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
## API Reference
|
|
504
|
+
|
|
505
|
+
### QueueService Methods
|
|
506
|
+
|
|
507
|
+
| Method | Description |
|
|
508
|
+
|--------|-------------|
|
|
509
|
+
| `createQueue(name)` | Create or get a queue |
|
|
510
|
+
| `addJob(queue, name, data, options?)` | Add a job |
|
|
511
|
+
| `addBulkJobs(queue, options)` | Add multiple jobs |
|
|
512
|
+
| `registerWorker(queue, worker)` | Register a worker |
|
|
513
|
+
| `getJob(queue, jobId)` | Get job by ID |
|
|
514
|
+
| `listJobs(queue, filter?)` | List jobs with filters |
|
|
515
|
+
| `removeJob(queue, jobId)` | Remove a job |
|
|
516
|
+
| `retryJob(queue, jobId)` | Retry a failed job |
|
|
517
|
+
| `cleanJobs(queue, status, olderThanMs)` | Clean old jobs |
|
|
518
|
+
| `getStats(queue)` | Get queue statistics |
|
|
519
|
+
| `getMetrics(queue)` | Get queue metrics |
|
|
520
|
+
| `pauseQueue(queue)` | Pause a queue |
|
|
521
|
+
| `resumeQueue(queue)` | Resume a queue |
|
|
522
|
+
| `drainQueue(queue)` | Remove all waiting jobs |
|
|
523
|
+
| `obliterateQueue(queue)` | Delete queue and all data |
|
|
524
|
+
| `listQueues()` | List all queues |
|
|
525
|
+
| `close()` | Graceful shutdown |
|
|
526
|
+
| `isConnected()` | Check connection status |
|
|
527
|
+
| `getConnectionInfo()` | Get connection details |
|
|
528
|
+
|
|
529
|
+
### Job Options
|
|
530
|
+
|
|
531
|
+
| Option | Type | Description |
|
|
532
|
+
|--------|------|-------------|
|
|
533
|
+
| `priority` | `'critical' \| 'high' \| 'normal' \| 'low'` | Job priority |
|
|
534
|
+
| `delay` | `number` | Delay in milliseconds |
|
|
535
|
+
| `attempts` | `number` | Max retry attempts |
|
|
536
|
+
| `backoff` | `{ type: 'fixed' \| 'exponential', delay: number }` | Retry backoff strategy |
|
|
537
|
+
| `removeOnComplete` | `boolean \| number` | Remove job on completion |
|
|
538
|
+
| `removeOnFail` | `boolean \| number` | Remove job on failure |
|
|
539
|
+
| `timeout` | `number` | Job timeout in milliseconds |
|
|
540
|
+
| `repeat` | `{ cron?: string, every?: number, limit?: number }` | Repeat configuration |
|