@open-mercato/queue 0.4.6-main-be1825c150 → 0.4.6

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,4 +1,45 @@
1
1
  import { createQueue } from "../factory.js";
2
+ const managedQueues = /* @__PURE__ */ new Set();
3
+ let shutdownHandlersRegistered = false;
4
+ let shutdownInProgress = false;
5
+ function unregisterShutdownHandlers(sigtermHandler, sigintHandler) {
6
+ process.off("SIGTERM", sigtermHandler);
7
+ process.off("SIGINT", sigintHandler);
8
+ shutdownHandlersRegistered = false;
9
+ }
10
+ function registerShutdownHandlers() {
11
+ if (shutdownHandlersRegistered) return;
12
+ const shutdown = async (signal) => {
13
+ if (shutdownInProgress) return;
14
+ shutdownInProgress = true;
15
+ console.log(`[worker] Received ${signal}, shutting down gracefully...`);
16
+ let hasError = false;
17
+ for (const queue of managedQueues) {
18
+ try {
19
+ await queue.close();
20
+ } catch (error) {
21
+ hasError = true;
22
+ console.error("[worker] Error during shutdown:", error);
23
+ }
24
+ }
25
+ managedQueues.clear();
26
+ unregisterShutdownHandlers(sigtermHandler, sigintHandler);
27
+ shutdownInProgress = false;
28
+ if (!hasError) {
29
+ console.log("[worker] Worker closed successfully");
30
+ }
31
+ process.exit(hasError ? 1 : 0);
32
+ };
33
+ const sigtermHandler = () => {
34
+ void shutdown("SIGTERM");
35
+ };
36
+ const sigintHandler = () => {
37
+ void shutdown("SIGINT");
38
+ };
39
+ process.on("SIGTERM", sigtermHandler);
40
+ process.on("SIGINT", sigintHandler);
41
+ shutdownHandlersRegistered = true;
42
+ }
2
43
  async function runWorker(options) {
3
44
  const {
4
45
  queueName,
@@ -16,19 +57,8 @@ async function runWorker(options) {
16
57
  concurrency
17
58
  });
18
59
  if (gracefulShutdown) {
19
- const shutdown = async (signal) => {
20
- console.log(`[worker] Received ${signal}, shutting down gracefully...`);
21
- try {
22
- await queue.close();
23
- console.log("[worker] Worker closed successfully");
24
- process.exit(0);
25
- } catch (error) {
26
- console.error("[worker] Error during shutdown:", error);
27
- process.exit(1);
28
- }
29
- };
30
- process.on("SIGTERM", () => shutdown("SIGTERM"));
31
- process.on("SIGINT", () => shutdown("SIGINT"));
60
+ managedQueues.add(queue);
61
+ registerShutdownHandlers();
32
62
  }
33
63
  await queue.process(handler);
34
64
  console.log(`[worker] Worker running with concurrency ${concurrency}`);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/worker/runner.ts"],
4
- "sourcesContent": ["import { createQueue } from '../factory'\nimport type { JobHandler, AsyncQueueOptions, QueueStrategyType } from '../types'\n\n/**\n * Options for running a queue worker.\n */\nexport type WorkerRunnerOptions<T = unknown> = {\n /** Name of the queue to process */\n queueName: string\n /** Handler function to process each job */\n handler: JobHandler<T>\n /** Redis connection options (only used for async strategy) */\n connection?: AsyncQueueOptions['connection']\n /** Number of concurrent jobs to process */\n concurrency?: number\n /** Whether to set up graceful shutdown handlers */\n gracefulShutdown?: boolean\n /** If true, don't block - return immediately after starting processing (for multi-queue mode) */\n background?: boolean\n /** Queue strategy to use. Defaults to QUEUE_STRATEGY env var or 'local' */\n strategy?: QueueStrategyType\n}\n\n/**\n * Runs a queue worker that processes jobs continuously.\n *\n * This function:\n * 1. Creates an async queue instance\n * 2. Starts a BullMQ worker\n * 3. Sets up graceful shutdown on SIGTERM/SIGINT\n * 4. Keeps the process running until shutdown\n *\n * @template T - The job payload type\n * @param options - Worker configuration\n *\n * @example\n * ```typescript\n * import { runWorker } from '@open-mercato/queue/worker'\n *\n * await runWorker({\n * queueName: 'events',\n * handler: async (job, ctx) => {\n * console.log(`Processing ${ctx.jobId}:`, job.payload)\n * },\n * connection: { url: process.env.REDIS_URL },\n * concurrency: 5,\n * })\n * ```\n */\nexport async function runWorker<T = unknown>(\n options: WorkerRunnerOptions<T>\n): Promise<void> {\n const {\n queueName,\n handler,\n connection,\n concurrency = 1,\n gracefulShutdown = true,\n background = false,\n strategy: strategyOption,\n } = options\n\n // Determine queue strategy from option, env var, or default to 'local'\n const strategy: QueueStrategyType = strategyOption\n ?? (process.env.QUEUE_STRATEGY === 'async' ? 'async' : 'local')\n\n console.log(`[worker] Starting worker for queue \"${queueName}\" (strategy: ${strategy})...`)\n\n const queue = createQueue<T>(queueName, strategy, {\n connection,\n concurrency,\n })\n\n // Set up graceful shutdown\n if (gracefulShutdown) {\n const shutdown = async (signal: string) => {\n console.log(`[worker] Received ${signal}, shutting down gracefully...`)\n try {\n await queue.close()\n console.log('[worker] Worker closed successfully')\n process.exit(0)\n } catch (error) {\n console.error('[worker] Error during shutdown:', error)\n process.exit(1)\n }\n }\n\n process.on('SIGTERM', () => shutdown('SIGTERM'))\n process.on('SIGINT', () => shutdown('SIGINT'))\n }\n\n // Start processing\n await queue.process(handler)\n\n console.log(`[worker] Worker running with concurrency ${concurrency}`)\n\n if (background) {\n // Return immediately for multi-queue mode\n return\n }\n\n console.log('[worker] Press Ctrl+C to stop')\n\n // Keep the process alive (single-queue mode)\n await new Promise(() => {\n // This promise never resolves, keeping the worker running\n })\n}\n\n/**\n * Creates a worker handler that routes jobs to specific handlers based on job type.\n *\n * @template T - Base job payload type (must include a 'type' field)\n * @param handlers - Map of job types to their handlers\n *\n * @example\n * ```typescript\n * const handler = createRoutedHandler({\n * 'user.created': async (job) => { ... },\n * 'order.placed': async (job) => { ... },\n * })\n *\n * await runWorker({ queueName: 'events', handler })\n * ```\n */\nexport function createRoutedHandler<T extends { type: string }>(\n handlers: Record<string, JobHandler<T>>\n): JobHandler<T> {\n return async (job, ctx) => {\n const type = job.payload.type\n const handler = handlers[type]\n\n if (!handler) {\n console.warn(`[worker] No handler registered for job type \"${type}\"`)\n return\n }\n\n await handler(job, ctx)\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,mBAAmB;AAiD5B,eAAsB,UACpB,SACe;AACf,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,aAAa;AAAA,IACb,UAAU;AAAA,EACZ,IAAI;AAGJ,QAAM,WAA8B,mBAC9B,QAAQ,IAAI,mBAAmB,UAAU,UAAU;AAEzD,UAAQ,IAAI,uCAAuC,SAAS,gBAAgB,QAAQ,MAAM;AAE1F,QAAM,QAAQ,YAAe,WAAW,UAAU;AAAA,IAChD;AAAA,IACA;AAAA,EACF,CAAC;AAGD,MAAI,kBAAkB;AACpB,UAAM,WAAW,OAAO,WAAmB;AACzC,cAAQ,IAAI,qBAAqB,MAAM,+BAA+B;AACtE,UAAI;AACF,cAAM,MAAM,MAAM;AAClB,gBAAQ,IAAI,qCAAqC;AACjD,gBAAQ,KAAK,CAAC;AAAA,MAChB,SAAS,OAAO;AACd,gBAAQ,MAAM,mCAAmC,KAAK;AACtD,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAEA,YAAQ,GAAG,WAAW,MAAM,SAAS,SAAS,CAAC;AAC/C,YAAQ,GAAG,UAAU,MAAM,SAAS,QAAQ,CAAC;AAAA,EAC/C;AAGA,QAAM,MAAM,QAAQ,OAAO;AAE3B,UAAQ,IAAI,4CAA4C,WAAW,EAAE;AAErE,MAAI,YAAY;AAEd;AAAA,EACF;AAEA,UAAQ,IAAI,+BAA+B;AAG3C,QAAM,IAAI,QAAQ,MAAM;AAAA,EAExB,CAAC;AACH;AAkBO,SAAS,oBACd,UACe;AACf,SAAO,OAAO,KAAK,QAAQ;AACzB,UAAM,OAAO,IAAI,QAAQ;AACzB,UAAM,UAAU,SAAS,IAAI;AAE7B,QAAI,CAAC,SAAS;AACZ,cAAQ,KAAK,gDAAgD,IAAI,GAAG;AACpE;AAAA,IACF;AAEA,UAAM,QAAQ,KAAK,GAAG;AAAA,EACxB;AACF;",
4
+ "sourcesContent": ["import { createQueue } from '../factory'\nimport type { Queue, JobHandler, AsyncQueueOptions, QueueStrategyType } from '../types'\n\n/**\n * Options for running a queue worker.\n */\nexport type WorkerRunnerOptions<T = unknown> = {\n /** Name of the queue to process */\n queueName: string\n /** Handler function to process each job */\n handler: JobHandler<T>\n /** Redis connection options (only used for async strategy) */\n connection?: AsyncQueueOptions['connection']\n /** Number of concurrent jobs to process */\n concurrency?: number\n /** Whether to set up graceful shutdown handlers */\n gracefulShutdown?: boolean\n /** If true, don't block - return immediately after starting processing (for multi-queue mode) */\n background?: boolean\n /** Queue strategy to use. Defaults to QUEUE_STRATEGY env var or 'local' */\n strategy?: QueueStrategyType\n}\n\nconst managedQueues = new Set<Queue<unknown>>()\nlet shutdownHandlersRegistered = false\nlet shutdownInProgress = false\n\nfunction unregisterShutdownHandlers(sigtermHandler: () => void, sigintHandler: () => void): void {\n process.off('SIGTERM', sigtermHandler)\n process.off('SIGINT', sigintHandler)\n shutdownHandlersRegistered = false\n}\n\nfunction registerShutdownHandlers(): void {\n if (shutdownHandlersRegistered) return\n\n const shutdown = async (signal: string) => {\n if (shutdownInProgress) return\n shutdownInProgress = true\n\n console.log(`[worker] Received ${signal}, shutting down gracefully...`)\n\n let hasError = false\n for (const queue of managedQueues) {\n try {\n await queue.close()\n } catch (error) {\n hasError = true\n console.error('[worker] Error during shutdown:', error)\n }\n }\n\n managedQueues.clear()\n unregisterShutdownHandlers(sigtermHandler, sigintHandler)\n shutdownInProgress = false\n\n if (!hasError) {\n console.log('[worker] Worker closed successfully')\n }\n\n process.exit(hasError ? 1 : 0)\n }\n\n const sigtermHandler = () => {\n void shutdown('SIGTERM')\n }\n\n const sigintHandler = () => {\n void shutdown('SIGINT')\n }\n\n process.on('SIGTERM', sigtermHandler)\n process.on('SIGINT', sigintHandler)\n shutdownHandlersRegistered = true\n}\n\n/**\n * Runs a queue worker that processes jobs continuously.\n *\n * This function:\n * 1. Creates an async queue instance\n * 2. Starts a BullMQ worker\n * 3. Sets up graceful shutdown on SIGTERM/SIGINT\n * 4. Keeps the process running until shutdown\n *\n * @template T - The job payload type\n * @param options - Worker configuration\n *\n * @example\n * ```typescript\n * import { runWorker } from '@open-mercato/queue/worker'\n *\n * await runWorker({\n * queueName: 'events',\n * handler: async (job, ctx) => {\n * console.log(`Processing ${ctx.jobId}:`, job.payload)\n * },\n * connection: { url: process.env.REDIS_URL },\n * concurrency: 5,\n * })\n * ```\n */\nexport async function runWorker<T = unknown>(\n options: WorkerRunnerOptions<T>\n): Promise<void> {\n const {\n queueName,\n handler,\n connection,\n concurrency = 1,\n gracefulShutdown = true,\n background = false,\n strategy: strategyOption,\n } = options\n\n // Determine queue strategy from option, env var, or default to 'local'\n const strategy: QueueStrategyType = strategyOption\n ?? (process.env.QUEUE_STRATEGY === 'async' ? 'async' : 'local')\n\n console.log(`[worker] Starting worker for queue \"${queueName}\" (strategy: ${strategy})...`)\n\n const queue = createQueue<T>(queueName, strategy, {\n connection,\n concurrency,\n })\n\n // Set up graceful shutdown\n if (gracefulShutdown) {\n managedQueues.add(queue as Queue<unknown>)\n registerShutdownHandlers()\n }\n\n // Start processing\n await queue.process(handler)\n\n console.log(`[worker] Worker running with concurrency ${concurrency}`)\n\n if (background) {\n // Return immediately for multi-queue mode\n return\n }\n\n console.log('[worker] Press Ctrl+C to stop')\n\n // Keep the process alive (single-queue mode)\n await new Promise(() => {\n // This promise never resolves, keeping the worker running\n })\n}\n\n/**\n * Creates a worker handler that routes jobs to specific handlers based on job type.\n *\n * @template T - Base job payload type (must include a 'type' field)\n * @param handlers - Map of job types to their handlers\n *\n * @example\n * ```typescript\n * const handler = createRoutedHandler({\n * 'user.created': async (job) => { ... },\n * 'order.placed': async (job) => { ... },\n * })\n *\n * await runWorker({ queueName: 'events', handler })\n * ```\n */\nexport function createRoutedHandler<T extends { type: string }>(\n handlers: Record<string, JobHandler<T>>\n): JobHandler<T> {\n return async (job, ctx) => {\n const type = job.payload.type\n const handler = handlers[type]\n\n if (!handler) {\n console.warn(`[worker] No handler registered for job type \"${type}\"`)\n return\n }\n\n await handler(job, ctx)\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,mBAAmB;AAuB5B,MAAM,gBAAgB,oBAAI,IAAoB;AAC9C,IAAI,6BAA6B;AACjC,IAAI,qBAAqB;AAEzB,SAAS,2BAA2B,gBAA4B,eAAiC;AAC/F,UAAQ,IAAI,WAAW,cAAc;AACrC,UAAQ,IAAI,UAAU,aAAa;AACnC,+BAA6B;AAC/B;AAEA,SAAS,2BAAiC;AACxC,MAAI,2BAA4B;AAEhC,QAAM,WAAW,OAAO,WAAmB;AACzC,QAAI,mBAAoB;AACxB,yBAAqB;AAErB,YAAQ,IAAI,qBAAqB,MAAM,+BAA+B;AAEtE,QAAI,WAAW;AACf,eAAW,SAAS,eAAe;AACjC,UAAI;AACF,cAAM,MAAM,MAAM;AAAA,MACpB,SAAS,OAAO;AACd,mBAAW;AACX,gBAAQ,MAAM,mCAAmC,KAAK;AAAA,MACxD;AAAA,IACF;AAEA,kBAAc,MAAM;AACpB,+BAA2B,gBAAgB,aAAa;AACxD,yBAAqB;AAErB,QAAI,CAAC,UAAU;AACb,cAAQ,IAAI,qCAAqC;AAAA,IACnD;AAEA,YAAQ,KAAK,WAAW,IAAI,CAAC;AAAA,EAC/B;AAEA,QAAM,iBAAiB,MAAM;AAC3B,SAAK,SAAS,SAAS;AAAA,EACzB;AAEA,QAAM,gBAAgB,MAAM;AAC1B,SAAK,SAAS,QAAQ;AAAA,EACxB;AAEA,UAAQ,GAAG,WAAW,cAAc;AACpC,UAAQ,GAAG,UAAU,aAAa;AAClC,+BAA6B;AAC/B;AA4BA,eAAsB,UACpB,SACe;AACf,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,aAAa;AAAA,IACb,UAAU;AAAA,EACZ,IAAI;AAGJ,QAAM,WAA8B,mBAC9B,QAAQ,IAAI,mBAAmB,UAAU,UAAU;AAEzD,UAAQ,IAAI,uCAAuC,SAAS,gBAAgB,QAAQ,MAAM;AAE1F,QAAM,QAAQ,YAAe,WAAW,UAAU;AAAA,IAChD;AAAA,IACA;AAAA,EACF,CAAC;AAGD,MAAI,kBAAkB;AACpB,kBAAc,IAAI,KAAuB;AACzC,6BAAyB;AAAA,EAC3B;AAGA,QAAM,MAAM,QAAQ,OAAO;AAE3B,UAAQ,IAAI,4CAA4C,WAAW,EAAE;AAErE,MAAI,YAAY;AAEd;AAAA,EACF;AAEA,UAAQ,IAAI,+BAA+B;AAG3C,QAAM,IAAI,QAAQ,MAAM;AAAA,EAExB,CAAC;AACH;AAkBO,SAAS,oBACd,UACe;AACf,SAAO,OAAO,KAAK,QAAQ;AACzB,UAAM,OAAO,IAAI,QAAQ;AACzB,UAAM,UAAU,SAAS,IAAI;AAE7B,QAAI,CAAC,SAAS;AACZ,cAAQ,KAAK,gDAAgD,IAAI,GAAG;AACpE;AAAA,IACF;AAEA,UAAM,QAAQ,KAAK,GAAG;AAAA,EACxB;AACF;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/queue",
3
- "version": "0.4.6-main-be1825c150",
3
+ "version": "0.4.6",
4
4
  "description": "Multi-strategy job queue with local and BullMQ support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -48,6 +48,5 @@
48
48
  },
49
49
  "publishConfig": {
50
50
  "access": "public"
51
- },
52
- "stableVersion": "0.4.5"
51
+ }
53
52
  }
@@ -0,0 +1,83 @@
1
+ import { createQueue } from '../../factory'
2
+ import type { Queue } from '../../types'
3
+ import { runWorker } from '../runner'
4
+
5
+ jest.mock('../../factory', () => ({
6
+ createQueue: jest.fn(),
7
+ }))
8
+
9
+ function buildFakeQueue(name: string): Queue<unknown> {
10
+ return {
11
+ name,
12
+ strategy: 'local',
13
+ enqueue: jest.fn(async () => 'job-id'),
14
+ process: jest.fn(async () => ({ processed: -1, failed: -1, lastJobId: undefined })),
15
+ clear: jest.fn(async () => ({ removed: 0 })),
16
+ close: jest.fn(async () => {}),
17
+ getJobCounts: jest.fn(async () => ({ waiting: 0, active: 0, completed: 0, failed: 0 })),
18
+ }
19
+ }
20
+
21
+ describe('runWorker', () => {
22
+ const createQueueMock = createQueue as jest.MockedFunction<typeof createQueue>
23
+
24
+ afterEach(() => {
25
+ jest.clearAllMocks()
26
+ })
27
+
28
+ it('registers SIGINT/SIGTERM shutdown handlers only once and closes all queues', async () => {
29
+ const queueA = buildFakeQueue('queue-a')
30
+ const queueB = buildFakeQueue('queue-b')
31
+ createQueueMock.mockReturnValueOnce(queueA).mockReturnValueOnce(queueB)
32
+
33
+ const exitSpy = jest.spyOn(process, 'exit').mockImplementation((() => undefined) as never)
34
+ const initialSigtermListeners = process.listenerCount('SIGTERM')
35
+ const initialSigintListeners = process.listenerCount('SIGINT')
36
+
37
+ await runWorker({
38
+ queueName: 'queue-a',
39
+ handler: async () => {},
40
+ background: true,
41
+ gracefulShutdown: true,
42
+ })
43
+
44
+ await runWorker({
45
+ queueName: 'queue-b',
46
+ handler: async () => {},
47
+ background: true,
48
+ gracefulShutdown: true,
49
+ })
50
+
51
+ expect(process.listenerCount('SIGTERM')).toBe(initialSigtermListeners + 1)
52
+ expect(process.listenerCount('SIGINT')).toBe(initialSigintListeners + 1)
53
+
54
+ const sigtermHandler = process.listeners('SIGTERM')[initialSigtermListeners] as (() => void) | undefined
55
+ sigtermHandler?.()
56
+ await new Promise<void>((resolve) => setImmediate(resolve))
57
+
58
+ expect(queueA.close).toHaveBeenCalledTimes(1)
59
+ expect(queueB.close).toHaveBeenCalledTimes(1)
60
+ expect(exitSpy).toHaveBeenCalledWith(0)
61
+ expect(process.listenerCount('SIGTERM')).toBe(initialSigtermListeners)
62
+ expect(process.listenerCount('SIGINT')).toBe(initialSigintListeners)
63
+
64
+ exitSpy.mockRestore()
65
+ })
66
+
67
+ it('does not register shutdown handlers when gracefulShutdown is disabled', async () => {
68
+ createQueueMock.mockReturnValueOnce(buildFakeQueue('queue-c'))
69
+
70
+ const initialSigtermListeners = process.listenerCount('SIGTERM')
71
+ const initialSigintListeners = process.listenerCount('SIGINT')
72
+
73
+ await runWorker({
74
+ queueName: 'queue-c',
75
+ handler: async () => {},
76
+ background: true,
77
+ gracefulShutdown: false,
78
+ })
79
+
80
+ expect(process.listenerCount('SIGTERM')).toBe(initialSigtermListeners)
81
+ expect(process.listenerCount('SIGINT')).toBe(initialSigintListeners)
82
+ })
83
+ })
@@ -1,5 +1,5 @@
1
1
  import { createQueue } from '../factory'
2
- import type { JobHandler, AsyncQueueOptions, QueueStrategyType } from '../types'
2
+ import type { Queue, JobHandler, AsyncQueueOptions, QueueStrategyType } from '../types'
3
3
 
4
4
  /**
5
5
  * Options for running a queue worker.
@@ -21,6 +21,59 @@ export type WorkerRunnerOptions<T = unknown> = {
21
21
  strategy?: QueueStrategyType
22
22
  }
23
23
 
24
+ const managedQueues = new Set<Queue<unknown>>()
25
+ let shutdownHandlersRegistered = false
26
+ let shutdownInProgress = false
27
+
28
+ function unregisterShutdownHandlers(sigtermHandler: () => void, sigintHandler: () => void): void {
29
+ process.off('SIGTERM', sigtermHandler)
30
+ process.off('SIGINT', sigintHandler)
31
+ shutdownHandlersRegistered = false
32
+ }
33
+
34
+ function registerShutdownHandlers(): void {
35
+ if (shutdownHandlersRegistered) return
36
+
37
+ const shutdown = async (signal: string) => {
38
+ if (shutdownInProgress) return
39
+ shutdownInProgress = true
40
+
41
+ console.log(`[worker] Received ${signal}, shutting down gracefully...`)
42
+
43
+ let hasError = false
44
+ for (const queue of managedQueues) {
45
+ try {
46
+ await queue.close()
47
+ } catch (error) {
48
+ hasError = true
49
+ console.error('[worker] Error during shutdown:', error)
50
+ }
51
+ }
52
+
53
+ managedQueues.clear()
54
+ unregisterShutdownHandlers(sigtermHandler, sigintHandler)
55
+ shutdownInProgress = false
56
+
57
+ if (!hasError) {
58
+ console.log('[worker] Worker closed successfully')
59
+ }
60
+
61
+ process.exit(hasError ? 1 : 0)
62
+ }
63
+
64
+ const sigtermHandler = () => {
65
+ void shutdown('SIGTERM')
66
+ }
67
+
68
+ const sigintHandler = () => {
69
+ void shutdown('SIGINT')
70
+ }
71
+
72
+ process.on('SIGTERM', sigtermHandler)
73
+ process.on('SIGINT', sigintHandler)
74
+ shutdownHandlersRegistered = true
75
+ }
76
+
24
77
  /**
25
78
  * Runs a queue worker that processes jobs continuously.
26
79
  *
@@ -73,20 +126,8 @@ export async function runWorker<T = unknown>(
73
126
 
74
127
  // Set up graceful shutdown
75
128
  if (gracefulShutdown) {
76
- const shutdown = async (signal: string) => {
77
- console.log(`[worker] Received ${signal}, shutting down gracefully...`)
78
- try {
79
- await queue.close()
80
- console.log('[worker] Worker closed successfully')
81
- process.exit(0)
82
- } catch (error) {
83
- console.error('[worker] Error during shutdown:', error)
84
- process.exit(1)
85
- }
86
- }
87
-
88
- process.on('SIGTERM', () => shutdown('SIGTERM'))
89
- process.on('SIGINT', () => shutdown('SIGINT'))
129
+ managedQueues.add(queue as Queue<unknown>)
130
+ registerShutdownHandlers()
90
131
  }
91
132
 
92
133
  // Start processing