sidekiq-ts 1.0.0 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -272,7 +272,7 @@ process.on("SIGTERM", async () => {
272
272
  ### CLI
273
273
 
274
274
  ```bash
275
- sidekiq-ts [options]
275
+ npx sidekiq [options]
276
276
  ```
277
277
 
278
278
  **Options:**
@@ -294,13 +294,13 @@ sidekiq-ts [options]
294
294
 
295
295
  ```bash
296
296
  # Basic usage
297
- sidekiq-ts -r ./dist/jobs.js
297
+ npx sidekiq -r ./dist/jobs.js
298
298
 
299
299
  # Multiple queues with weights
300
- sidekiq-ts -q critical,5 -q default,2 -q background -c 10
300
+ npx sidekiq -q critical,5 -q default,2 -q background -c 10
301
301
 
302
302
  # With config file
303
- sidekiq-ts -C config/sidekiq.json -e production
303
+ npx sidekiq -C config/sidekiq.json -e production
304
304
  ```
305
305
 
306
306
  ## Configuration File
@@ -640,7 +640,7 @@ After=network.target redis.service
640
640
  Type=simple
641
641
  User=app
642
642
  WorkingDirectory=/app
643
- ExecStart=/usr/bin/node /app/node_modules/.bin/sidekiq-ts -C /app/sidekiq.json
643
+ ExecStart=/usr/bin/npx sidekiq -C /app/sidekiq.json
644
644
  Restart=always
645
645
  RestartSec=5
646
646
 
@@ -655,14 +655,32 @@ WantedBy=multi-user.target
655
655
  module.exports = {
656
656
  apps: [{
657
657
  name: "sidekiq-worker",
658
- script: "node_modules/.bin/sidekiq-ts",
659
- args: "-C sidekiq.json",
658
+ script: "npx",
659
+ args: "sidekiq -C sidekiq.json",
660
660
  instances: 2,
661
661
  exec_mode: "cluster",
662
662
  }]
663
663
  };
664
664
  ```
665
665
 
666
+ **Docker example:**
667
+
668
+ ```dockerfile
669
+ FROM node:24-alpine
670
+ WORKDIR /app
671
+ COPY package*.json ./
672
+ RUN npm ci --omit=dev
673
+ COPY dist ./dist
674
+ COPY sidekiq.json ./
675
+ CMD ["npx", "sidekiq", "-C", "sidekiq.json"]
676
+ ```
677
+
678
+ ```bash
679
+ # Build and run
680
+ docker build -t my-worker .
681
+ docker run -e REDIS_URL=redis://host:6379 my-worker
682
+ ```
683
+
666
684
  ### Redis Configuration
667
685
 
668
686
  For production Redis:
@@ -1,4 +1,5 @@
1
1
  import type { Config } from "./config.js";
2
+ import type { Logger } from "./logger.js";
2
3
  export declare const DEFAULT_CONFIG_PATH = "sidekiq.json";
3
4
  export interface CliOptions {
4
5
  configPath: string;
@@ -19,4 +20,14 @@ export declare const applyCliOptions: (config: Config, options: CliOptions, load
19
20
  config: Config;
20
21
  requirePaths: string[];
21
22
  };
23
+ export interface ShutdownDeps {
24
+ logger: Logger;
25
+ stop: () => Promise<void>;
26
+ exit: (code: number) => void;
27
+ }
28
+ export interface ShutdownHandler {
29
+ (signal: string): Promise<void>;
30
+ isShuttingDown: () => boolean;
31
+ }
32
+ export declare const createShutdownHandler: (deps: ShutdownDeps) => ShutdownHandler;
22
33
  //# sourceMappingURL=cli-helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli-helpers.d.ts","sourceRoot":"","sources":["../src/cli-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAG1C,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAElD,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;CACtB;AAWD,eAAO,MAAM,SAAS,GAAI,MAAM,MAAM,EAAE,KAAG,UAiH1C,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,SAAS,MAAM,KAAG,MAAM,GAAG,SAKvC,CAAC;AASxB,eAAO,MAAM,eAAe,GAC1B,QAAQ,MAAM,EACd,SAAS,UAAU,EACnB,qBAAoB,MAAM,EAAO,KAChC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CAuB1C,CAAC"}
1
+ {"version":3,"file":"cli-helpers.d.ts","sourceRoot":"","sources":["../src/cli-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAElD,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;CACtB;AAWD,eAAO,MAAM,SAAS,GAAI,MAAM,MAAM,EAAE,KAAG,UAiH1C,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,SAAS,MAAM,KAAG,MAAM,GAAG,SAKvC,CAAC;AASxB,eAAO,MAAM,eAAe,GAC1B,QAAQ,MAAM,EACd,SAAS,UAAU,EACnB,qBAAoB,MAAM,EAAO,KAChC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CAuB1C,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,cAAc,EAAE,MAAM,OAAO,CAAC;CAC/B;AAED,eAAO,MAAM,qBAAqB,GAAI,MAAM,YAAY,KAAG,eAkB1D,CAAC"}
@@ -150,3 +150,19 @@ export const applyCliOptions = (config, options, loadedRequirePaths = []) => {
150
150
  }
151
151
  return { config, requirePaths };
152
152
  };
153
+ export const createShutdownHandler = (deps) => {
154
+ let shuttingDown = false;
155
+ const handler = async (signal) => {
156
+ if (shuttingDown) {
157
+ deps.logger.info(() => `Received ${signal}, forcing exit`);
158
+ deps.exit(1);
159
+ return;
160
+ }
161
+ shuttingDown = true;
162
+ deps.logger.info(() => `Received ${signal}, shutting down`);
163
+ await deps.stop();
164
+ deps.exit(0);
165
+ };
166
+ handler.isShuttingDown = () => shuttingDown;
167
+ return handler;
168
+ };
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import { access } from "node:fs/promises";
3
3
  import { createRequire } from "node:module";
4
4
  import { resolve } from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
- import { applyCliOptions, parseArgs, resolveEnvironment, } from "./cli-helpers.js";
6
+ import { applyCliOptions, createShutdownHandler, parseArgs, resolveEnvironment, } from "./cli-helpers.js";
7
7
  import { Config } from "./config.js";
8
8
  import { loadConfigFile } from "./config-loader.js";
9
9
  import { Sidekiq } from "./sidekiq.js";
@@ -80,17 +80,12 @@ const main = async () => {
80
80
  await import(pathToFileURL(fullPath).href);
81
81
  }
82
82
  const runner = await Sidekiq.run({ config });
83
- let shuttingDown = false;
84
83
  let quieting = false;
85
- const shutdown = async (signal) => {
86
- if (shuttingDown) {
87
- return;
88
- }
89
- shuttingDown = true;
90
- config.logger.info(() => `Received ${signal}, shutting down`);
91
- await runner.stop();
92
- process.exit(0);
93
- };
84
+ const shutdown = createShutdownHandler({
85
+ logger: config.logger,
86
+ stop: () => runner.stop(),
87
+ exit: (code) => process.exit(code),
88
+ });
94
89
  const quiet = async (signal) => {
95
90
  if (quieting) {
96
91
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAU1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAqG7C,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,eAAe,CAAC,CAAiB;IACzC,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,eAAe,CAAC,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,SAAS,CAAC,CAAgD;IAClE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAEnB;IACT,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAGtB;IACJ,OAAO,CAAC,QAAQ,CAAC,UAAU,CAGvB;IACJ,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgB;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;IAChD,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC,OAAO,CAAC,kBAAkB,CAAC,CAAoB;gBAEnC,MAAM,EAAE,MAAM;IAQpB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA8BtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA8B3B;;OAEG;IACH,MAAM,IAAI,OAAO;IAIjB;;OAEG;IACH,IAAI,iBAAiB,IAAI,iBAAiB,GAAG,SAAS,CAErD;IAED,YAAY,IAAI,YAAY,EAAE;IAsB9B,OAAO,CAAC,cAAc;YAKR,gBAAgB;YAWhB,WAAW;YAoBX,YAAY;IAM1B,OAAO,CAAC,kBAAkB;YAIZ,kBAAkB;IAqBhC,OAAO,CAAC,aAAa;YAQP,SAAS;YA0DT,YAAY;IAoB1B,OAAO,CAAC,aAAa;YAkBP,cAAc;IAa5B,OAAO,CAAC,WAAW;YAcL,QAAQ;IAWtB,OAAO,CAAC,SAAS;YAoBH,YAAY;YAMZ,cAAc;YAQd,iBAAiB;YAkBjB,eAAe;YAWf,eAAe;YAWf,gBAAgB;IA8B9B,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,aAAa;YAOP,gBAAgB;YA6BhB,QAAQ;YAiCR,SAAS;YA4BT,UAAU;YAwEV,aAAa;IA2H3B,OAAO,CAAC,WAAW;YAoBL,gBAAgB;YA8BhB,YAAY;YAYZ,gBAAgB;YAmBhB,gBAAgB;IAsB9B,OAAO,CAAC,iBAAiB;IAmBzB,OAAO,CAAC,gBAAgB;YAQV,UAAU;CAczB"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAU1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAsG7C,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,eAAe,CAAC,CAAiB;IACzC,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,eAAe,CAAC,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,SAAS,CAAC,CAAgD;IAClE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAEnB;IACT,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAGtB;IACJ,OAAO,CAAC,QAAQ,CAAC,UAAU,CAGvB;IACJ,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgB;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;IAChD,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC,OAAO,CAAC,kBAAkB,CAAC,CAAoB;gBAEnC,MAAM,EAAE,MAAM;IAQpB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA8BtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAoD3B;;OAEG;IACH,MAAM,IAAI,OAAO;IAIjB;;OAEG;IACH,IAAI,iBAAiB,IAAI,iBAAiB,GAAG,SAAS,CAErD;IAED,YAAY,IAAI,YAAY,EAAE;IAsB9B,OAAO,CAAC,cAAc;YAKR,gBAAgB;YAWhB,WAAW;YAoBX,YAAY;IAM1B,OAAO,CAAC,kBAAkB;YAIZ,kBAAkB;IAqBhC,OAAO,CAAC,aAAa;YAQP,SAAS;YA0DT,YAAY;IAoB1B,OAAO,CAAC,aAAa;YAkBP,cAAc;IAa5B,OAAO,CAAC,WAAW;YAcL,QAAQ;IAWtB,OAAO,CAAC,SAAS;YAoBH,YAAY;YAMZ,cAAc;YAQd,iBAAiB;YAkBjB,eAAe;YAWf,eAAe;YAWf,gBAAgB;IA8B9B,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,aAAa;YAOP,gBAAgB;YA6BhB,QAAQ;YAwCR,SAAS;YA4BT,UAAU;YAwEV,aAAa;IA2H3B,OAAO,CAAC,WAAW;YAoBL,gBAAgB;YA8BhB,YAAY;YAYZ,gBAAgB;YAmBhB,gBAAgB;IAsB9B,OAAO,CAAC,iBAAiB;IAmBzB,OAAO,CAAC,gBAAgB;YAQV,UAAU;CAczB"}
package/dist/runner.js CHANGED
@@ -10,6 +10,7 @@ import { resolveJob } from "./registry.js";
10
10
  const FETCH_TIMEOUT_SECONDS = 2;
11
11
  const STATS_TTL_SECONDS = 5 * 365 * 24 * 60 * 60;
12
12
  const INITIAL_WAIT_SECONDS = 10;
13
+ const PAUSE_TIME_MS = 500;
13
14
  const LUA_ZPOPBYSCORE = `
14
15
  local key, now = KEYS[1], ARGV[1]
15
16
  local jobs = redis.call("zrange", key, "-inf", now, "byscore", "limit", 0, 1)
@@ -158,7 +159,22 @@ export class Runner {
158
159
  await this.leaderElector?.stop();
159
160
  this.stopHeartbeat();
160
161
  this.stopScheduler();
162
+ // Brief pause to allow idle processors to finish
163
+ await sleep(PAUSE_TIME_MS);
161
164
  const deadline = Date.now() + this.config.timeout * 1000;
165
+ // Early exit if no jobs are running
166
+ if (this.inProgress.size === 0) {
167
+ await this.waitForWorkers(deadline);
168
+ await this.clearHeartbeat();
169
+ await Promise.all(this.workerRedis.map(async (client) => {
170
+ if (client.isOpen) {
171
+ await client.quit();
172
+ }
173
+ }));
174
+ await this.config.fireEvent("exit", { reverse: true });
175
+ return;
176
+ }
177
+ this.config.logger.info(() => "Pausing to allow jobs to finish...");
162
178
  await this.waitForDrain(deadline);
163
179
  if (this.inProgress.size > 0) {
164
180
  await this.requeueInProgress();
@@ -511,7 +527,11 @@ export class Runner {
511
527
  }
512
528
  }
513
529
  async workLoop(index) {
514
- while (!this.stopping) {
530
+ while (true) {
531
+ // Check stopping BEFORE fetching - don't start new work
532
+ if (this.stopping) {
533
+ break;
534
+ }
515
535
  if (this.quieting) {
516
536
  await sleep(50);
517
537
  continue;
@@ -520,6 +540,8 @@ export class Runner {
520
540
  if (!unit) {
521
541
  continue;
522
542
  }
543
+ // Process the job - even if stopping became true during fetchWork
544
+ // Once we've popped a job from Redis, we MUST process it
523
545
  const workerId = `worker-${index}`;
524
546
  this.inProgress.set(workerId, {
525
547
  queue: unit.queue,
package/package.json CHANGED
@@ -1,9 +1,16 @@
1
1
  {
2
2
  "name": "sidekiq-ts",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
4
4
  "description": "TypeScript client for Sidekiq job processing",
5
5
  "license": "MIT",
6
- "keywords": ["sidekiq", "redis", "job", "queue", "worker", "typescript"],
6
+ "keywords": [
7
+ "sidekiq",
8
+ "redis",
9
+ "job",
10
+ "queue",
11
+ "worker",
12
+ "typescript"
13
+ ],
7
14
  "type": "module",
8
15
  "main": "dist/index.js",
9
16
  "types": "dist/index.d.ts",
@@ -16,7 +23,9 @@
16
23
  "bin": {
17
24
  "sidekiq": "dist/cli.js"
18
25
  },
19
- "files": ["dist"],
26
+ "files": [
27
+ "dist"
28
+ ],
20
29
  "engines": {
21
30
  "node": ">=24.12.0"
22
31
  },
@@ -37,6 +46,6 @@
37
46
  "tsx": "^4.19.2",
38
47
  "typescript": "^5.7.2",
39
48
  "ultracite": "^6.5.0",
40
- "vitest": "^2.1.8"
49
+ "vitest": "^4.0.16"
41
50
  }
42
51
  }