sidekiq-ts 1.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 +686 -0
- package/dist/api.d.ts +172 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +679 -0
- package/dist/backtrace.d.ts +3 -0
- package/dist/backtrace.d.ts.map +1 -0
- package/dist/backtrace.js +16 -0
- package/dist/cli-helpers.d.ts +22 -0
- package/dist/cli-helpers.d.ts.map +1 -0
- package/dist/cli-helpers.js +152 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +143 -0
- package/dist/client.d.ts +25 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +212 -0
- package/dist/config-loader.d.ts +16 -0
- package/dist/config-loader.d.ts.map +1 -0
- package/dist/config-loader.js +37 -0
- package/dist/config.d.ts +59 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +155 -0
- package/dist/context.d.ts +10 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +29 -0
- package/dist/cron.d.ts +44 -0
- package/dist/cron.d.ts.map +1 -0
- package/dist/cron.js +173 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/interrupt-handler.d.ts +8 -0
- package/dist/interrupt-handler.d.ts.map +1 -0
- package/dist/interrupt-handler.js +24 -0
- package/dist/iterable-constants.d.ts +3 -0
- package/dist/iterable-constants.d.ts.map +1 -0
- package/dist/iterable-constants.js +2 -0
- package/dist/iterable-errors.d.ts +10 -0
- package/dist/iterable-errors.d.ts.map +1 -0
- package/dist/iterable-errors.js +18 -0
- package/dist/iterable.d.ts +44 -0
- package/dist/iterable.d.ts.map +1 -0
- package/dist/iterable.js +298 -0
- package/dist/job-logger.d.ts +12 -0
- package/dist/job-logger.d.ts.map +1 -0
- package/dist/job-logger.js +64 -0
- package/dist/job-util.d.ts +8 -0
- package/dist/job-util.d.ts.map +1 -0
- package/dist/job-util.js +158 -0
- package/dist/job.d.ts +73 -0
- package/dist/job.d.ts.map +1 -0
- package/dist/job.js +200 -0
- package/dist/json.d.ts +3 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +2 -0
- package/dist/leader.d.ts +63 -0
- package/dist/leader.d.ts.map +1 -0
- package/dist/leader.js +193 -0
- package/dist/logger.d.ts +53 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +143 -0
- package/dist/middleware.d.ts +23 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +92 -0
- package/dist/periodic.d.ts +80 -0
- package/dist/periodic.d.ts.map +1 -0
- package/dist/periodic.js +205 -0
- package/dist/redis.d.ts +3 -0
- package/dist/redis.d.ts.map +1 -0
- package/dist/redis.js +1 -0
- package/dist/registry.d.ts +11 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +8 -0
- package/dist/runner.d.ts +81 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +791 -0
- package/dist/sidekiq.d.ts +43 -0
- package/dist/sidekiq.d.ts.map +1 -0
- package/dist/sidekiq.js +189 -0
- package/dist/testing.d.ts +32 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +112 -0
- package/dist/types.d.ts +116 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,686 @@
|
|
|
1
|
+
# sidekiq-ts
|
|
2
|
+
|
|
3
|
+
A TypeScript implementation of [Sidekiq](https://sidekiq.org/) for Node.js. Process background jobs with Redis-backed queues, featuring type-safe job definitions, automatic retries, scheduled jobs, leader election, and cron scheduling.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Type-safe jobs** - Generic argument types with full TypeScript support
|
|
8
|
+
- **Multiple queues** - Priority-based queue processing with weighted selection
|
|
9
|
+
- **Job scheduling** - Execute jobs immediately, after a delay, or at specific times
|
|
10
|
+
- **Automatic retries** - Exponential backoff with configurable retry limits
|
|
11
|
+
- **Dead letter queue** - Failed jobs preserved for debugging
|
|
12
|
+
- **Leader election** - Coordinate across distributed workers
|
|
13
|
+
- **Cron jobs** - Periodic job scheduling with standard cron expressions
|
|
14
|
+
- **Middleware** - Customize job enqueueing and execution
|
|
15
|
+
- **CLI** - Run workers from the command line
|
|
16
|
+
- **Testing utilities** - Fake and inline modes for testing
|
|
17
|
+
|
|
18
|
+
## Requirements
|
|
19
|
+
|
|
20
|
+
- Node.js >= 24.12.0
|
|
21
|
+
- Redis server
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install sidekiq-ts
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { Job, Sidekiq } from "sidekiq-ts";
|
|
33
|
+
|
|
34
|
+
// Configure Redis connection
|
|
35
|
+
Sidekiq.defaultConfiguration.redis = { url: "redis://localhost:6379" };
|
|
36
|
+
|
|
37
|
+
// Define a job
|
|
38
|
+
class WelcomeEmailJob extends Job<[string, string]> {
|
|
39
|
+
perform(email: string, name: string) {
|
|
40
|
+
console.log(`Sending welcome email to ${name} <${email}>`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Register the job
|
|
45
|
+
Sidekiq.registerJob(WelcomeEmailJob);
|
|
46
|
+
|
|
47
|
+
// Enqueue jobs
|
|
48
|
+
await WelcomeEmailJob.performAsync("alice@example.com", "Alice");
|
|
49
|
+
|
|
50
|
+
// Start the worker
|
|
51
|
+
const runner = await Sidekiq.run();
|
|
52
|
+
|
|
53
|
+
// Later, stop gracefully
|
|
54
|
+
await runner.stop();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Defining Jobs
|
|
58
|
+
|
|
59
|
+
Extend the `Job` class with a type parameter specifying your argument types:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { Job } from "sidekiq-ts";
|
|
63
|
+
|
|
64
|
+
class ProcessOrderJob extends Job<[string, number, boolean]> {
|
|
65
|
+
perform(orderId: string, amount: number, isPriority: boolean) {
|
|
66
|
+
// Your job logic here
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The `perform` method can be synchronous or return a Promise:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
class FetchDataJob extends Job<[string]> {
|
|
75
|
+
async perform(url: string) {
|
|
76
|
+
const response = await fetch(url);
|
|
77
|
+
// Process response...
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Accessing Job Context
|
|
83
|
+
|
|
84
|
+
Inside `perform`, you can access:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
class MyJob extends Job<[string]> {
|
|
88
|
+
perform(data: string) {
|
|
89
|
+
// Unique job ID
|
|
90
|
+
console.log(this.jid);
|
|
91
|
+
|
|
92
|
+
// Check if worker is stopping (for graceful shutdown)
|
|
93
|
+
if (this._context?.stopping()) {
|
|
94
|
+
return; // Exit early
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Job Options
|
|
101
|
+
|
|
102
|
+
Configure jobs using the static `sidekiqOptions` property:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
class PaymentJob extends Job<[string, number]> {
|
|
106
|
+
static sidekiqOptions = {
|
|
107
|
+
queue: "critical", // Queue name (default: "default")
|
|
108
|
+
retry: 5, // Number of retries (default: 25, false to disable)
|
|
109
|
+
backtrace: 10, // Lines of backtrace to keep (default: false)
|
|
110
|
+
dead: true, // Add to dead queue on failure (default: true)
|
|
111
|
+
tags: ["payments"], // Tags for categorization
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
perform(orderId: string, amount: number) {
|
|
115
|
+
// ...
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Custom Retry Delay
|
|
121
|
+
|
|
122
|
+
Override the default exponential backoff:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
PaymentJob.retryIn((retryCount, error, payload) => {
|
|
126
|
+
// Return delay in seconds
|
|
127
|
+
return 10 * Math.pow(2, retryCount - 1); // 10s, 20s, 40s, 80s...
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Retries Exhausted Handler
|
|
132
|
+
|
|
133
|
+
Handle permanent failures:
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
PaymentJob.retriesExhausted((payload, error) => {
|
|
137
|
+
console.log(`Job ${payload.jid} failed permanently: ${error.message}`);
|
|
138
|
+
// Notify external service, send alerts, etc.
|
|
139
|
+
|
|
140
|
+
return undefined; // Send to dead queue
|
|
141
|
+
// return "discard"; // Skip dead queue entirely
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Per-Job Option Override
|
|
146
|
+
|
|
147
|
+
Override options for a specific enqueue:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
await NotificationJob.set({ queue: "critical" }).performAsync(userId, message);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Enqueueing Jobs
|
|
154
|
+
|
|
155
|
+
### Immediate Execution
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
const jid = await MyJob.performAsync(arg1, arg2);
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Delayed Execution
|
|
162
|
+
|
|
163
|
+
Execute after a delay (in seconds):
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
const jid = await ReportJob.performIn(300, "daily-report"); // 5 minutes
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Scheduled Execution
|
|
170
|
+
|
|
171
|
+
Execute at a specific Unix timestamp:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
const tomorrow = Math.floor(Date.now() / 1000) + 86400;
|
|
175
|
+
const jid = await ReportJob.performAt(tomorrow, "weekly-report");
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Bulk Enqueueing
|
|
179
|
+
|
|
180
|
+
Enqueue multiple jobs efficiently:
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
const jids = await ReminderJob.performBulk([
|
|
184
|
+
[1, "Meeting at 3pm"],
|
|
185
|
+
[2, "Review PR #123"],
|
|
186
|
+
[3, "Deploy to staging"],
|
|
187
|
+
]);
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Configuration
|
|
191
|
+
|
|
192
|
+
### Redis Connection
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
Sidekiq.defaultConfiguration.redis = {
|
|
196
|
+
url: "redis://localhost:6379",
|
|
197
|
+
// Or use environment variable
|
|
198
|
+
// url: process.env.REDIS_URL
|
|
199
|
+
};
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Server Configuration
|
|
203
|
+
|
|
204
|
+
Configure worker-specific options:
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
Sidekiq.configureServer({
|
|
208
|
+
concurrency: 10, // Worker threads (default: 5)
|
|
209
|
+
timeout: 30, // Shutdown timeout in seconds (default: 25)
|
|
210
|
+
maxRetries: 25, // Default retry limit (default: 25)
|
|
211
|
+
tag: "worker-1", // Process tag for identification
|
|
212
|
+
labels: ["api", "prod"], // Process labels
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Client Configuration
|
|
217
|
+
|
|
218
|
+
Configure client-only options (for processes that only enqueue jobs):
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
Sidekiq.configureClient({
|
|
222
|
+
strictArgs: "raise", // "raise" | "warn" | "none"
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Queue Configuration
|
|
227
|
+
|
|
228
|
+
### Simple Queues
|
|
229
|
+
|
|
230
|
+
Process queues in random order with equal priority:
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
Sidekiq.configureServer({
|
|
234
|
+
queues: ["default", "emails", "reports"],
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Weighted Queues
|
|
239
|
+
|
|
240
|
+
Higher weights get proportionally more processing time:
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
Sidekiq.configureServer({
|
|
244
|
+
queues: [
|
|
245
|
+
["critical", 5], // 5x weight
|
|
246
|
+
["default", 2], // 2x weight
|
|
247
|
+
["background", 1], // 1x weight
|
|
248
|
+
],
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Running Workers
|
|
253
|
+
|
|
254
|
+
### Programmatic
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
import { Sidekiq } from "sidekiq-ts";
|
|
258
|
+
|
|
259
|
+
// Import your job files to register them
|
|
260
|
+
import "./jobs/email-job.js";
|
|
261
|
+
import "./jobs/report-job.js";
|
|
262
|
+
|
|
263
|
+
const runner = await Sidekiq.run();
|
|
264
|
+
|
|
265
|
+
// Handle shutdown signals
|
|
266
|
+
process.on("SIGTERM", async () => {
|
|
267
|
+
await runner.stop();
|
|
268
|
+
process.exit(0);
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### CLI
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
sidekiq-ts [options]
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Options:**
|
|
279
|
+
|
|
280
|
+
| Option | Description |
|
|
281
|
+
|--------|-------------|
|
|
282
|
+
| `-C, --config PATH` | Path to JSON config file (default: sidekiq.json if present) |
|
|
283
|
+
| `-c, --concurrency NUM` | Number of worker threads |
|
|
284
|
+
| `-e, --environment ENV` | Application environment |
|
|
285
|
+
| `-g, --tag TAG` | Process tag for identification |
|
|
286
|
+
| `-q, --queue QUEUE[,WT]` | Queue with optional weight (repeatable) |
|
|
287
|
+
| `-r, --require PATH` | File to import before startup (repeatable) |
|
|
288
|
+
| `-t, --timeout NUM` | Shutdown timeout in seconds |
|
|
289
|
+
| `-v, --verbose` | Enable debug logging |
|
|
290
|
+
| `-V, --version` | Print version and exit |
|
|
291
|
+
| `-h, --help` | Show help message |
|
|
292
|
+
|
|
293
|
+
**Examples:**
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
# Basic usage
|
|
297
|
+
sidekiq-ts -r ./dist/jobs.js
|
|
298
|
+
|
|
299
|
+
# Multiple queues with weights
|
|
300
|
+
sidekiq-ts -q critical,5 -q default,2 -q background -c 10
|
|
301
|
+
|
|
302
|
+
# With config file
|
|
303
|
+
sidekiq-ts -C config/sidekiq.json -e production
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Configuration File
|
|
307
|
+
|
|
308
|
+
Create `sidekiq.json` in your project root:
|
|
309
|
+
|
|
310
|
+
```json
|
|
311
|
+
{
|
|
312
|
+
"concurrency": 10,
|
|
313
|
+
"queues": [
|
|
314
|
+
["critical", 5],
|
|
315
|
+
["default", 1]
|
|
316
|
+
],
|
|
317
|
+
"timeout": 30,
|
|
318
|
+
"require": [
|
|
319
|
+
"./dist/jobs/index.js"
|
|
320
|
+
],
|
|
321
|
+
"redis": {
|
|
322
|
+
"url": "redis://localhost:6379"
|
|
323
|
+
},
|
|
324
|
+
"development": {
|
|
325
|
+
"concurrency": 2
|
|
326
|
+
},
|
|
327
|
+
"production": {
|
|
328
|
+
"concurrency": 20,
|
|
329
|
+
"redis": {
|
|
330
|
+
"url": "redis://prod-redis:6379"
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Environment-specific settings override the defaults when `NODE_ENV` matches.
|
|
337
|
+
|
|
338
|
+
## Middleware
|
|
339
|
+
|
|
340
|
+
### Client Middleware
|
|
341
|
+
|
|
342
|
+
Intercept jobs during enqueueing:
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
import { Sidekiq } from "sidekiq-ts";
|
|
346
|
+
|
|
347
|
+
class LoggingMiddleware {
|
|
348
|
+
call(
|
|
349
|
+
jobClass: string | unknown,
|
|
350
|
+
payload: JobPayload,
|
|
351
|
+
queue: string,
|
|
352
|
+
redis: RedisClient,
|
|
353
|
+
next: () => Promise<JobPayload | null>
|
|
354
|
+
) {
|
|
355
|
+
console.log(`Enqueueing ${payload.class} to ${queue}`);
|
|
356
|
+
return next();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
Sidekiq.useClientMiddleware(LoggingMiddleware);
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Server Middleware
|
|
364
|
+
|
|
365
|
+
Intercept jobs during execution:
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
class TimingMiddleware {
|
|
369
|
+
async call(
|
|
370
|
+
job: Job,
|
|
371
|
+
payload: JobPayload,
|
|
372
|
+
queue: string,
|
|
373
|
+
next: () => Promise<void>
|
|
374
|
+
) {
|
|
375
|
+
const start = Date.now();
|
|
376
|
+
try {
|
|
377
|
+
await next();
|
|
378
|
+
} finally {
|
|
379
|
+
console.log(`${payload.class} took ${Date.now() - start}ms`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
Sidekiq.useServerMiddleware(TimingMiddleware);
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Leader Election
|
|
388
|
+
|
|
389
|
+
For tasks that should only run on one worker (like cron jobs), use leader election:
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
const runner = await Sidekiq.run();
|
|
393
|
+
|
|
394
|
+
// Check if this process is the leader
|
|
395
|
+
if (runner.leader()) {
|
|
396
|
+
console.log("I am the leader!");
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Leader Lifecycle Events
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
Sidekiq.on("leader", () => {
|
|
404
|
+
console.log("Became leader");
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
Sidekiq.on("follower", () => {
|
|
408
|
+
console.log("Lost leadership");
|
|
409
|
+
});
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## Periodic Jobs (Cron)
|
|
413
|
+
|
|
414
|
+
Schedule jobs to run on a cron schedule. Only the leader process enqueues periodic jobs.
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
import { Job, Sidekiq } from "sidekiq-ts";
|
|
418
|
+
|
|
419
|
+
class DailyReportJob extends Job<[]> {
|
|
420
|
+
perform() {
|
|
421
|
+
console.log("Generating daily report...");
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
Sidekiq.registerJob(DailyReportJob);
|
|
426
|
+
|
|
427
|
+
const runner = await Sidekiq.run();
|
|
428
|
+
|
|
429
|
+
// Register a cron job (runs at midnight daily)
|
|
430
|
+
runner.periodicScheduler.register("0 0 * * *", DailyReportJob, {
|
|
431
|
+
queue: "reports",
|
|
432
|
+
args: [],
|
|
433
|
+
});
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### Cron Expression Format
|
|
437
|
+
|
|
438
|
+
Standard 5-field cron expressions:
|
|
439
|
+
|
|
440
|
+
```
|
|
441
|
+
┌───────────── minute (0-59)
|
|
442
|
+
│ ┌───────────── hour (0-23)
|
|
443
|
+
│ │ ┌───────────── day of month (1-31)
|
|
444
|
+
│ │ │ ┌───────────── month (1-12 or jan-dec)
|
|
445
|
+
│ │ │ │ ┌───────────── day of week (0-6 or sun-sat)
|
|
446
|
+
│ │ │ │ │
|
|
447
|
+
* * * * *
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
**Examples:**
|
|
451
|
+
|
|
452
|
+
| Expression | Description |
|
|
453
|
+
|------------|-------------|
|
|
454
|
+
| `* * * * *` | Every minute |
|
|
455
|
+
| `*/5 * * * *` | Every 5 minutes |
|
|
456
|
+
| `0 * * * *` | Every hour |
|
|
457
|
+
| `0 0 * * *` | Daily at midnight |
|
|
458
|
+
| `0 9 * * 1-5` | Weekdays at 9am |
|
|
459
|
+
| `0 0 1 * *` | First day of month |
|
|
460
|
+
|
|
461
|
+
## Monitoring & API
|
|
462
|
+
|
|
463
|
+
### Statistics
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
import { Stats } from "sidekiq-ts";
|
|
467
|
+
|
|
468
|
+
const stats = new Stats(config);
|
|
469
|
+
await stats.fetch();
|
|
470
|
+
|
|
471
|
+
console.log(stats.processed); // Total processed jobs
|
|
472
|
+
console.log(stats.failed); // Total failed jobs
|
|
473
|
+
console.log(stats.enqueued); // Jobs waiting in queues
|
|
474
|
+
console.log(stats.scheduled); // Scheduled job count
|
|
475
|
+
console.log(stats.retry); // Jobs awaiting retry
|
|
476
|
+
console.log(stats.dead); // Jobs in dead queue
|
|
477
|
+
console.log(stats.processes); // Active worker count
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Queue Information
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
import { Queue } from "sidekiq-ts";
|
|
484
|
+
|
|
485
|
+
const queue = new Queue("default", config);
|
|
486
|
+
console.log(await queue.size()); // Jobs in queue
|
|
487
|
+
console.log(await queue.latency()); // Oldest job age in seconds
|
|
488
|
+
await queue.clear(); // Remove all jobs
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Process Information
|
|
492
|
+
|
|
493
|
+
```typescript
|
|
494
|
+
import { ProcessSet } from "sidekiq-ts";
|
|
495
|
+
|
|
496
|
+
const processes = new ProcessSet(config);
|
|
497
|
+
for await (const process of processes) {
|
|
498
|
+
console.log(process.identity); // hostname:pid
|
|
499
|
+
console.log(process.concurrency); // Worker threads
|
|
500
|
+
console.log(process.busy); // Currently processing
|
|
501
|
+
console.log(process.queues); // Assigned queues
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Dead Jobs
|
|
506
|
+
|
|
507
|
+
```typescript
|
|
508
|
+
import { DeadSet } from "sidekiq-ts";
|
|
509
|
+
|
|
510
|
+
const dead = new DeadSet(config);
|
|
511
|
+
console.log(await dead.size());
|
|
512
|
+
|
|
513
|
+
for await (const entry of dead) {
|
|
514
|
+
console.log(entry.item.class);
|
|
515
|
+
console.log(entry.item.error_message);
|
|
516
|
+
|
|
517
|
+
await entry.retry(); // Re-enqueue the job
|
|
518
|
+
// or
|
|
519
|
+
await entry.delete(); // Remove permanently
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
## Testing
|
|
524
|
+
|
|
525
|
+
### Fake Mode
|
|
526
|
+
|
|
527
|
+
Collect jobs without executing them:
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
import { Testing, Queues } from "sidekiq-ts";
|
|
531
|
+
|
|
532
|
+
Testing.fake();
|
|
533
|
+
|
|
534
|
+
await MyJob.performAsync("test");
|
|
535
|
+
|
|
536
|
+
// Check enqueued jobs
|
|
537
|
+
const jobs = Queues.jobs();
|
|
538
|
+
expect(jobs).toHaveLength(1);
|
|
539
|
+
expect(jobs[0].class).toBe("MyJob");
|
|
540
|
+
|
|
541
|
+
// Clear for next test
|
|
542
|
+
Queues.clearAll();
|
|
543
|
+
|
|
544
|
+
// Disable fake mode
|
|
545
|
+
Testing.disable();
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### Inline Mode
|
|
549
|
+
|
|
550
|
+
Execute jobs synchronously:
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
Testing.inline();
|
|
554
|
+
|
|
555
|
+
await MyJob.performAsync("test"); // Executes immediately
|
|
556
|
+
|
|
557
|
+
Testing.disable();
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### Scoped Testing
|
|
561
|
+
|
|
562
|
+
```typescript
|
|
563
|
+
await Testing.fake(async () => {
|
|
564
|
+
await MyJob.performAsync("test");
|
|
565
|
+
expect(Queues.jobs()).toHaveLength(1);
|
|
566
|
+
}); // Automatically restores previous mode
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
## Error Handling
|
|
570
|
+
|
|
571
|
+
### Error Handlers
|
|
572
|
+
|
|
573
|
+
Called for every job failure (including retries):
|
|
574
|
+
|
|
575
|
+
```typescript
|
|
576
|
+
Sidekiq.on("error", (error, context) => {
|
|
577
|
+
console.error(`Job failed: ${error.message}`);
|
|
578
|
+
// Send to error tracking service
|
|
579
|
+
});
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### Death Handlers
|
|
583
|
+
|
|
584
|
+
Called when a job exhausts all retries:
|
|
585
|
+
|
|
586
|
+
```typescript
|
|
587
|
+
Sidekiq.on("death", (payload, error) => {
|
|
588
|
+
console.error(`Job ${payload.jid} died: ${error.message}`);
|
|
589
|
+
// Alert on-call, create incident ticket, etc.
|
|
590
|
+
});
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
## Lifecycle Events
|
|
594
|
+
|
|
595
|
+
```typescript
|
|
596
|
+
Sidekiq.on("startup", () => {
|
|
597
|
+
console.log("Worker starting");
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
Sidekiq.on("quiet", () => {
|
|
601
|
+
console.log("Worker quieting (no new jobs)");
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
Sidekiq.on("shutdown", () => {
|
|
605
|
+
console.log("Worker shutting down");
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
Sidekiq.on("heartbeat", () => {
|
|
609
|
+
// Called every 10 seconds
|
|
610
|
+
});
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
## Graceful Shutdown
|
|
614
|
+
|
|
615
|
+
The worker handles these signals:
|
|
616
|
+
|
|
617
|
+
| Signal | Action |
|
|
618
|
+
|--------|--------|
|
|
619
|
+
| `SIGINT` | Graceful shutdown |
|
|
620
|
+
| `SIGTERM` | Graceful shutdown |
|
|
621
|
+
| `SIGTSTP` | Quiet mode (stop accepting new jobs) |
|
|
622
|
+
| `SIGTTIN` | Dump current job state to logs |
|
|
623
|
+
|
|
624
|
+
The shutdown timeout (default: 25 seconds) allows in-flight jobs to complete before forced termination.
|
|
625
|
+
|
|
626
|
+
## Production Deployment
|
|
627
|
+
|
|
628
|
+
### Process Manager
|
|
629
|
+
|
|
630
|
+
Use a process manager like systemd, PM2, or Docker:
|
|
631
|
+
|
|
632
|
+
**systemd example:**
|
|
633
|
+
|
|
634
|
+
```ini
|
|
635
|
+
[Unit]
|
|
636
|
+
Description=Sidekiq Worker
|
|
637
|
+
After=network.target redis.service
|
|
638
|
+
|
|
639
|
+
[Service]
|
|
640
|
+
Type=simple
|
|
641
|
+
User=app
|
|
642
|
+
WorkingDirectory=/app
|
|
643
|
+
ExecStart=/usr/bin/node /app/node_modules/.bin/sidekiq-ts -C /app/sidekiq.json
|
|
644
|
+
Restart=always
|
|
645
|
+
RestartSec=5
|
|
646
|
+
|
|
647
|
+
[Install]
|
|
648
|
+
WantedBy=multi-user.target
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
**PM2 example:**
|
|
652
|
+
|
|
653
|
+
```javascript
|
|
654
|
+
// ecosystem.config.js
|
|
655
|
+
module.exports = {
|
|
656
|
+
apps: [{
|
|
657
|
+
name: "sidekiq-worker",
|
|
658
|
+
script: "node_modules/.bin/sidekiq-ts",
|
|
659
|
+
args: "-C sidekiq.json",
|
|
660
|
+
instances: 2,
|
|
661
|
+
exec_mode: "cluster",
|
|
662
|
+
}]
|
|
663
|
+
};
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### Redis Configuration
|
|
667
|
+
|
|
668
|
+
For production Redis:
|
|
669
|
+
|
|
670
|
+
```typescript
|
|
671
|
+
Sidekiq.defaultConfiguration.redis = {
|
|
672
|
+
url: process.env.REDIS_URL,
|
|
673
|
+
// Connection pool settings are handled by the redis package
|
|
674
|
+
};
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### Monitoring
|
|
678
|
+
|
|
679
|
+
- Use the Stats API to build dashboards
|
|
680
|
+
- Set up alerts on the dead queue size
|
|
681
|
+
- Monitor process count and job latency
|
|
682
|
+
- Track processed/failed rates over time
|
|
683
|
+
|
|
684
|
+
## License
|
|
685
|
+
|
|
686
|
+
See LICENSE for details.
|