langaro-api 1.2.3 → 1.2.5
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/bin/langaro-api.js +12 -2
- package/lib/cli/documentation-templates/01-architecture-overview.md +240 -0
- package/lib/cli/documentation-templates/02-crud-layer.md +504 -0
- package/lib/cli/documentation-templates/03-models.md +362 -0
- package/lib/cli/documentation-templates/04-services.md +355 -0
- package/lib/cli/documentation-templates/05-controllers.md +395 -0
- package/lib/cli/documentation-templates/06-routes.md +268 -0
- package/lib/cli/documentation-templates/07-jobs.md +361 -0
- package/lib/cli/documentation-templates/08-tasks.md +265 -0
- package/lib/cli/documentation-templates/09-middlewares.md +238 -0
- package/lib/cli/documentation-templates/10-integrations.md +332 -0
- package/lib/cli/documentation-templates/11-config-and-bootstrap.md +352 -0
- package/lib/cli/documentation-templates/12-queues.md +205 -0
- package/lib/cli/documentation-templates/13-utils.md +281 -0
- package/lib/cli/documentation-templates/14-testing.md +315 -0
- package/lib/cli/documentation-templates/15-cli-and-scaffolding.md +344 -0
- package/lib/cli/documentation-templates/SUMMARY.md +116 -0
- package/lib/cli/init.js +30 -2
- package/lib/generators/crud.js +7 -0
- package/package.json +2 -2
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# Configuration & Bootstrap
|
|
2
|
+
|
|
3
|
+
## Role
|
|
4
|
+
|
|
5
|
+
The config layer handles application startup, middleware wiring, database connections, Redis, queue initialization, and error handling. These files are generated by `langaro-api init` and rarely need modification.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## server.js
|
|
10
|
+
|
|
11
|
+
Entry point for the application. Handles environment setup, server creation, and graceful shutdown.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Location: src/config/server.js
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Responsibilities:**
|
|
18
|
+
1. Load environment variables (`.env` or `.env.test`)
|
|
19
|
+
2. Create Knex database connection
|
|
20
|
+
3. Call `getAppInstance(knex)` to initialize the App
|
|
21
|
+
4. Listen on `PORT`
|
|
22
|
+
5. Handle `SIGINT`/`SIGTERM` for graceful shutdown
|
|
23
|
+
|
|
24
|
+
**Graceful shutdown sequence:**
|
|
25
|
+
1. Stop accepting new connections
|
|
26
|
+
2. Shut down BullMQ queues (wait for active jobs to complete)
|
|
27
|
+
3. Close HTTP server
|
|
28
|
+
4. Destroy Knex connection
|
|
29
|
+
5. Exit process
|
|
30
|
+
|
|
31
|
+
**When to modify:** Almost never. Only if you need custom shutdown logic or additional initialization before the app starts.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## app.js
|
|
36
|
+
|
|
37
|
+
The core application class that wires everything together.
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
Location: src/config/app.js
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### App Class Structure
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
class App {
|
|
47
|
+
constructor(knexInstance, servicesInstance) {
|
|
48
|
+
// Initialize Express, HTTP server, Socket.io
|
|
49
|
+
// Set up readiness promise (blocks requests until boot completes)
|
|
50
|
+
// Call init()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async init() {
|
|
54
|
+
// 1. Set query parser
|
|
55
|
+
// 2. Apply readiness middleware
|
|
56
|
+
// 3. Apply standard middlewares
|
|
57
|
+
// 4. Wire routes (models → services → queues → controllers → routes → tasks)
|
|
58
|
+
// 5. Apply error handling
|
|
59
|
+
// 6. Mark as ready
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
middlewares() {
|
|
63
|
+
// requestIp, CORS, morgan, bodyParser (50MB limit), multer
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async routes() {
|
|
67
|
+
// Socket.io setup
|
|
68
|
+
// loadModels → loadServices → initializeQueues → loadControllers → attachRouters → loadTasks
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
handleErrors() {
|
|
72
|
+
// Sentry error handler
|
|
73
|
+
// Axios network errors → 422
|
|
74
|
+
// Safe errors → err.code + err.data
|
|
75
|
+
// Unsafe errors → 500 + Sentry
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
bullBoard() {
|
|
79
|
+
// Queue dashboard at BULLBOARD_PATH
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Readiness Middleware
|
|
85
|
+
|
|
86
|
+
The app blocks all incoming requests until initialization completes:
|
|
87
|
+
```javascript
|
|
88
|
+
async readinessMiddleware(req, res, next) {
|
|
89
|
+
if (this.isReady) return next();
|
|
90
|
+
try {
|
|
91
|
+
await this.readyPromise; // Wait for init() to resolve
|
|
92
|
+
next();
|
|
93
|
+
} catch (error) {
|
|
94
|
+
res.status(503).json({ error: { message: 'Server is starting up.' } });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Error Handling
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
// Safe errors (created via ApiError) — returned to client
|
|
103
|
+
if (err.safe) {
|
|
104
|
+
res.status(err.code).json({ error: err.data });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Unsafe errors (uncaught) — generic response + Sentry
|
|
108
|
+
res.status(500).json({
|
|
109
|
+
error: { message: 'Unable to process request. Please try again later.' },
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Creating safe errors:**
|
|
114
|
+
```javascript
|
|
115
|
+
const { ApiError, StatusCodes } = require('@/utils');
|
|
116
|
+
|
|
117
|
+
// In controllers or services:
|
|
118
|
+
ApiError(StatusCodes.NOT_FOUND, 'User not found');
|
|
119
|
+
ApiError(StatusCodes.BAD_REQUEST, 'Invalid email format', 'INVALID_EMAIL');
|
|
120
|
+
ApiError(StatusCodes.CONFLICT, 'Email already registered');
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**When to modify:** When adding global middleware, changing body size limits, adding new middleware to the chain, or customizing error handling.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Database Connection
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
Location: src/database/connection.js
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Creates and configures the Knex instance:
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
module.exports = (env = NODE_ENV, dbName = DB_NAME) => {
|
|
137
|
+
const knex = require('knex')({
|
|
138
|
+
client: 'mysql2',
|
|
139
|
+
connection: {
|
|
140
|
+
host: DB_HOST,
|
|
141
|
+
user: DB_USER,
|
|
142
|
+
port: DB_PORT,
|
|
143
|
+
password: DB_PASSWORD,
|
|
144
|
+
database: dbName,
|
|
145
|
+
typeCast(field, next) {
|
|
146
|
+
// TINY(1) → boolean conversion
|
|
147
|
+
if (field.type === 'TINY' && field.length === 1) {
|
|
148
|
+
const value = field.string();
|
|
149
|
+
if (value === null) return null;
|
|
150
|
+
return value === '1';
|
|
151
|
+
}
|
|
152
|
+
return next();
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
pool: { min: 10, max: 100 },
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Attach pagination plugin
|
|
159
|
+
const { attachPaginate } = require('knex-paginate');
|
|
160
|
+
if (!knex.paginate) attachPaginate();
|
|
161
|
+
|
|
162
|
+
// Attach schema inspector
|
|
163
|
+
const { SchemaInspector } = require('knex-schema-inspector');
|
|
164
|
+
knex.inspector = SchemaInspector(knex);
|
|
165
|
+
|
|
166
|
+
return knex;
|
|
167
|
+
};
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Key features:**
|
|
171
|
+
- MySQL TINY(1) → JavaScript boolean conversion
|
|
172
|
+
- Connection pooling (min 10, max 100)
|
|
173
|
+
- `knex-paginate` for built-in pagination
|
|
174
|
+
- `knex-schema-inspector` for runtime schema introspection
|
|
175
|
+
|
|
176
|
+
**Test safety:** Throws if test environment tries to use non-test database (name must contain `_tests`).
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Redis
|
|
181
|
+
|
|
182
|
+
### Redis Config
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
Location: src/database/redis-config.js
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
module.exports = {
|
|
190
|
+
connection: {
|
|
191
|
+
host: process.env.REDIS_HOST || '127.0.0.1',
|
|
192
|
+
port: process.env.REDIS_PORT || 6379,
|
|
193
|
+
password: process.env.REDIS_PASSWORD,
|
|
194
|
+
username: process.env.REDIS_USERNAME,
|
|
195
|
+
tls: process.env.REDIS_TLS || false,
|
|
196
|
+
maxRetriesPerRequest: null,
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Redis Cache
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
Location: src/database/redis-cache.js
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Singleton class providing simple key-value caching:
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
class RedisCache {
|
|
211
|
+
async get(key) // Returns parsed JSON or null
|
|
212
|
+
async set(key, value, expiresInSeconds) // Stores as JSON with TTL
|
|
213
|
+
async delete(key) // Removes key
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
module.exports = new RedisCache();
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Key format:** `redis-cache:{key}`
|
|
220
|
+
|
|
221
|
+
**Usage:**
|
|
222
|
+
```javascript
|
|
223
|
+
const RedisCache = require('@/database/redis-cache');
|
|
224
|
+
|
|
225
|
+
// Cache a value for 1 hour
|
|
226
|
+
await RedisCache.set('exchange-rates', rates, 3600);
|
|
227
|
+
|
|
228
|
+
// Read from cache
|
|
229
|
+
const cached = await RedisCache.get('exchange-rates');
|
|
230
|
+
if (cached) return cached;
|
|
231
|
+
|
|
232
|
+
// Invalidate
|
|
233
|
+
await RedisCache.delete('exchange-rates');
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Queue System
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
Location: src/config/queues.js
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
See `12-queues.md` for detailed queue documentation.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Sentry (Error Tracking)
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
Location: src/config/instrument.js
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
```javascript
|
|
255
|
+
const Sentry = require('@sentry/node');
|
|
256
|
+
Sentry.init({
|
|
257
|
+
dsn: process.env.SENTRY_APP,
|
|
258
|
+
tracesSampleRate: 0,
|
|
259
|
+
profilesSampleRate: 0,
|
|
260
|
+
environment: process.env.NODE_ENV,
|
|
261
|
+
});
|
|
262
|
+
module.exports = Sentry;
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**When to modify:** To enable performance monitoring (`tracesSampleRate > 0`) or add custom Sentry configuration.
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Environment Variables Reference
|
|
270
|
+
|
|
271
|
+
### Required
|
|
272
|
+
|
|
273
|
+
| Variable | Description | Example |
|
|
274
|
+
|----------|-------------|---------|
|
|
275
|
+
| `NODE_ENV` | Environment | `development`, `production`, `test` |
|
|
276
|
+
| `PORT` | Server port | `8282` |
|
|
277
|
+
| `DB_HOST` | MySQL host | `127.0.0.1` |
|
|
278
|
+
| `DB_USER` | MySQL user | `root` |
|
|
279
|
+
| `DB_PASSWORD` | MySQL password | `password` |
|
|
280
|
+
| `DB_NAME` | MySQL database | `my_project` |
|
|
281
|
+
| `JWT_SECRET` | JWT signing key | `auto-generated-uuid` |
|
|
282
|
+
|
|
283
|
+
### Optional
|
|
284
|
+
|
|
285
|
+
| Variable | Description | Example |
|
|
286
|
+
|----------|-------------|---------|
|
|
287
|
+
| `DB_PORT` | MySQL port | `3306` |
|
|
288
|
+
| `REDIS_HOST` | Redis host | `127.0.0.1` |
|
|
289
|
+
| `REDIS_PORT` | Redis port | `6379` |
|
|
290
|
+
| `REDIS_PASSWORD` | Redis password | `password` |
|
|
291
|
+
| `REDIS_USERNAME` | Redis username | — |
|
|
292
|
+
| `REDIS_TLS` | Redis TLS | `true` |
|
|
293
|
+
| `MASTER_PASS` | Dev master password | `auto-generated` |
|
|
294
|
+
| `BULLBOARD_PATH` | Queue dashboard URL | `/admin/queues` |
|
|
295
|
+
| `SENTRY_APP` | Sentry DSN | `https://...@sentry.io/...` |
|
|
296
|
+
| `POSTMARK_API_KEY` | Email service | `key-...` |
|
|
297
|
+
| `AWS_ACCESS_KEY_ID` | AWS credentials | `AKIA...` |
|
|
298
|
+
| `AWS_SECRET_ACCESS_KEY` | AWS credentials | `...` |
|
|
299
|
+
| `AWS_REGION` | AWS region | `us-east-1` |
|
|
300
|
+
| `GOOGLE_CLIENT_ID` | OAuth | `...apps.googleusercontent.com` |
|
|
301
|
+
| `GOOGLE_CLIENT_SECRET` | OAuth | `...` |
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Babel Configuration
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
Location: .babelrc
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Enables ES6+ features and the `@` path alias:
|
|
312
|
+
|
|
313
|
+
```json
|
|
314
|
+
{
|
|
315
|
+
"presets": [["@babel/preset-env", { "targets": { "node": "current" } }]],
|
|
316
|
+
"plugins": [
|
|
317
|
+
"@babel/transform-runtime",
|
|
318
|
+
"@babel/plugin-proposal-class-properties",
|
|
319
|
+
["module-resolver", { "root": ["./"], "alias": { "@": "./src/" } }]
|
|
320
|
+
]
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
The `@` alias lets you use `require('@/database/connection')` instead of relative paths.
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## When to Modify Bootstrap Files
|
|
329
|
+
|
|
330
|
+
| Need | File to modify |
|
|
331
|
+
|------|---------------|
|
|
332
|
+
| Add global middleware | `app.js` → `middlewares()` |
|
|
333
|
+
| Change body size limit | `app.js` → `middlewares()` bodyParser config |
|
|
334
|
+
| Add custom error handling | `app.js` → `handleErrors()` |
|
|
335
|
+
| Change DB pool size | `connection.js` → `pool` config |
|
|
336
|
+
| Add new env variable | `.env` + `.env.example` |
|
|
337
|
+
| Change graceful shutdown | `server.js` → SIGTERM handler |
|
|
338
|
+
| Configure Sentry | `instrument.js` |
|
|
339
|
+
| Change Redis config | `redis-config.js` |
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Checklist
|
|
344
|
+
|
|
345
|
+
When modifying configuration:
|
|
346
|
+
|
|
347
|
+
- [ ] Changes work in both development and production
|
|
348
|
+
- [ ] New env variables added to `.env.example`
|
|
349
|
+
- [ ] No hardcoded credentials or secrets
|
|
350
|
+
- [ ] Error handling maintains safe/unsafe distinction
|
|
351
|
+
- [ ] Middleware order is correct (readiness → CORS → logging → parsing → routes → errors)
|
|
352
|
+
- [ ] Pool sizes are appropriate for the deployment environment
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Queue System (BullMQ)
|
|
2
|
+
|
|
3
|
+
## Role
|
|
4
|
+
|
|
5
|
+
The queue system provides **reliable asynchronous job processing** using BullMQ backed by Redis. It handles job creation, worker management, retry logic, rate limiting, and monitoring.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐
|
|
13
|
+
│ Controllers │────▶│ Queue.add │────▶│ Redis (BullMQ) │
|
|
14
|
+
│ Tasks │ │ │ │ │
|
|
15
|
+
│ Other Jobs │ └──────────────┘ └────────┬────────┘
|
|
16
|
+
│ │ │
|
|
17
|
+
└──────────────────┘ ▼
|
|
18
|
+
┌─────────────────┐
|
|
19
|
+
│ Worker (per job)│
|
|
20
|
+
│ job.handle() │
|
|
21
|
+
└─────────────────┘
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- **Single Redis connection** shared by all queues
|
|
25
|
+
- **Lazy creation**: Queues and workers are created on first `add()` call
|
|
26
|
+
- **Bull Board dashboard** at `BULLBOARD_PATH` for monitoring
|
|
27
|
+
- **Automatic cleanup**: Inactive queues close after 10 minutes
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Queue Lifecycle
|
|
32
|
+
|
|
33
|
+
### Initialization (at boot)
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
1. initializeQueues(services, io, bullBoardSetQueues)
|
|
37
|
+
2. Create Redis connection
|
|
38
|
+
3. loadJobs(services) → scan src/jobs/ → register all job configs in Map
|
|
39
|
+
4. Scan Redis for existing queues with pending jobs
|
|
40
|
+
5. Start workers for any queues that have waiting/active/delayed jobs
|
|
41
|
+
6. Start inactivity cleanup interval (checks every 1 minute)
|
|
42
|
+
7. Return { add } function
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Adding a Job
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
1. Queue.add('job-name', data, options)
|
|
49
|
+
2. Lookup base config from jobConfigs Map
|
|
50
|
+
3. If grouped: extract groupId, create unique queue name
|
|
51
|
+
4. getOrCreateQueueAndWorker():
|
|
52
|
+
a. If queue exists → update lastActivity, return
|
|
53
|
+
b. If new → create Queue + Worker with merged options
|
|
54
|
+
c. Register worker event handlers (failed → Sentry)
|
|
55
|
+
d. Worker.run()
|
|
56
|
+
5. queue.add(name, data, options) → Redis
|
|
57
|
+
6. Update Bull Board
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Processing a Job
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
1. Worker picks up job from Redis
|
|
64
|
+
2. Update lastActivity timestamp
|
|
65
|
+
3. Call job.handle(data, job, { add }, io)
|
|
66
|
+
4. If success → job complete → removed per retention policy
|
|
67
|
+
5. If error → retry according to backoff settings
|
|
68
|
+
6. If error + discard → permanent failure
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Inactivity Cleanup
|
|
72
|
+
|
|
73
|
+
Every 1 minute, the system checks all active queues:
|
|
74
|
+
- If a queue has been inactive for 10+ minutes
|
|
75
|
+
- AND has 0 active, 0 waiting, 0 delayed jobs
|
|
76
|
+
- → Close worker and queue, free resources
|
|
77
|
+
|
|
78
|
+
### Graceful Shutdown
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
1. Pause all workers (stop accepting new jobs)
|
|
82
|
+
2. Wait for active jobs to complete (up to ~20 minutes / 400 retries × 3s)
|
|
83
|
+
3. Close all workers and queues
|
|
84
|
+
4. Clear Redis connection
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Default Options
|
|
90
|
+
|
|
91
|
+
### Job Options (applied to all jobs)
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
{
|
|
95
|
+
attempts: 100, // Max retry attempts
|
|
96
|
+
backoff: { type: 'fixed', delay: 60000 }, // 1 minute between retries
|
|
97
|
+
removeOnComplete: { age: 2592000, count: 1000 }, // Keep 30 days / max 1000
|
|
98
|
+
removeOnFail: { age: 7776000, count: 5000 }, // Keep 90 days / max 5000
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Worker Options (applied to all workers)
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
{
|
|
106
|
+
autorun: false, // Workers started manually
|
|
107
|
+
limiter: { max: 2, duration: 1000 }, // 2 jobs per second
|
|
108
|
+
maxStalledCount: 5, // Fail after 5 stalls
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Grouped Queues
|
|
115
|
+
|
|
116
|
+
When a job config has `group: true`, each unique `groupId` creates a separate queue + worker:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
Queue.add('sync-data', data, { groupId: 'company-abc' })
|
|
120
|
+
→ Creates queue: sync-data-company-abc
|
|
121
|
+
→ Creates worker for: sync-data-company-abc
|
|
122
|
+
|
|
123
|
+
Queue.add('sync-data', data, { groupId: 'company-xyz' })
|
|
124
|
+
→ Creates queue: sync-data-company-xyz
|
|
125
|
+
→ Creates worker for: sync-data-company-xyz
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Use case:** Per-company processing isolation. Each company's jobs run independently without blocking other companies.
|
|
129
|
+
|
|
130
|
+
**Missing groupId:** If `group: true` but no `groupId` in options → throws error.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Test Environment
|
|
135
|
+
|
|
136
|
+
When `NODE_ENV === 'test'`, `initializeQueues` returns a mock:
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
return { add: jest.fn() };
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
No Redis connection, no workers, no queues. Tests can verify `Queue.add` was called without side effects.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Monitoring
|
|
147
|
+
|
|
148
|
+
### Bull Board Dashboard
|
|
149
|
+
|
|
150
|
+
Available at `process.env.BULLBOARD_PATH` (default: `/admin/queues`).
|
|
151
|
+
|
|
152
|
+
Shows:
|
|
153
|
+
- All active queues
|
|
154
|
+
- Job counts (waiting, active, completed, failed, delayed)
|
|
155
|
+
- Individual job details and retry history
|
|
156
|
+
|
|
157
|
+
### Sentry Integration
|
|
158
|
+
|
|
159
|
+
All worker failures are automatically captured:
|
|
160
|
+
```javascript
|
|
161
|
+
worker.on('failed', (job, error) => {
|
|
162
|
+
Sentry.captureException(error, {
|
|
163
|
+
tags: { queue: queueName },
|
|
164
|
+
contexts: { data: { 'Job data': job?.data, queue: queueName } },
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Queue API Reference
|
|
172
|
+
|
|
173
|
+
### `Queue.add(name, data, options)`
|
|
174
|
+
|
|
175
|
+
| Parameter | Type | Description |
|
|
176
|
+
|-----------|------|-------------|
|
|
177
|
+
| `name` | string | Job name (must match a registered job) |
|
|
178
|
+
| `data` | object | Payload passed to `handle(data, ...)` |
|
|
179
|
+
| `options` | object | Optional: `{ delay, groupId, priority, ... }` |
|
|
180
|
+
|
|
181
|
+
**Options:**
|
|
182
|
+
- `delay: 5000` — Delay execution by 5 seconds
|
|
183
|
+
- `groupId: 'abc'` — Route to grouped queue (requires `group: true` in job config)
|
|
184
|
+
- Any BullMQ job option
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Startup Queue Scanning
|
|
189
|
+
|
|
190
|
+
On boot, the queue system scans Redis for existing `bull:*:meta` keys to find queues that have pending work from before the restart. It matches queue names against registered job configs and starts workers for any that have waiting/active/delayed jobs.
|
|
191
|
+
|
|
192
|
+
This ensures jobs added before a deploy or restart are still processed.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## When to Modify
|
|
197
|
+
|
|
198
|
+
The queue configuration (`src/config/queues.js`) rarely needs changes. Common modifications:
|
|
199
|
+
|
|
200
|
+
| Need | What to change |
|
|
201
|
+
|------|---------------|
|
|
202
|
+
| Different retry defaults | `getDefaultJobOptions()` |
|
|
203
|
+
| Different rate limits | `getDefaultWorkerOptions()` |
|
|
204
|
+
| Longer inactivity timeout | `inactivityTimeLimitMs` |
|
|
205
|
+
| Disable cleanup interval | Set `inactivityCheckIntervalMs` to 0 |
|