@open-mercato/queue 0.4.2-canary-c02407ff85

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.
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Worker Registry
3
+ *
4
+ * Provides registration and lookup for auto-discovered queue workers.
5
+ * Workers are registered during bootstrap and accessed by the CLI worker command.
6
+ */
7
+
8
+ import type { WorkerDescriptor } from '../types'
9
+
10
+ const workers: Map<string, WorkerDescriptor> = new Map()
11
+
12
+ /**
13
+ * Register a single worker.
14
+ * @param worker - The worker descriptor to register
15
+ */
16
+ export function registerWorker(worker: WorkerDescriptor): void {
17
+ if (workers.has(worker.id)) {
18
+ console.warn(`[worker-registry] Worker "${worker.id}" already registered, overwriting`)
19
+ }
20
+ workers.set(worker.id, worker)
21
+ }
22
+
23
+ /**
24
+ * Register multiple workers at once (typically from module discovery).
25
+ * @param list - Array of worker descriptors to register
26
+ */
27
+ export function registerModuleWorkers(list: WorkerDescriptor[]): void {
28
+ for (const worker of list) {
29
+ registerWorker(worker)
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Get all registered workers.
35
+ * @returns Array of all worker descriptors
36
+ */
37
+ export function getWorkers(): WorkerDescriptor[] {
38
+ return Array.from(workers.values())
39
+ }
40
+
41
+ /**
42
+ * Get workers registered for a specific queue.
43
+ * @param queue - The queue name to filter by
44
+ * @returns Array of workers for the specified queue
45
+ */
46
+ export function getWorkersByQueue(queue: string): WorkerDescriptor[] {
47
+ return Array.from(workers.values()).filter((w) => w.queue === queue)
48
+ }
49
+
50
+ /**
51
+ * Get a specific worker by ID.
52
+ * @param id - The worker ID to look up
53
+ * @returns The worker descriptor if found, undefined otherwise
54
+ */
55
+ export function getWorker(id: string): WorkerDescriptor | undefined {
56
+ return workers.get(id)
57
+ }
58
+
59
+ /**
60
+ * Get all unique queue names that have registered workers.
61
+ * @returns Array of queue names
62
+ */
63
+ export function getRegisteredQueues(): string[] {
64
+ const queues = new Set<string>()
65
+ for (const worker of workers.values()) {
66
+ queues.add(worker.queue)
67
+ }
68
+ return Array.from(queues)
69
+ }
70
+
71
+ /**
72
+ * Clear all registered workers (useful for testing).
73
+ */
74
+ export function clearWorkers(): void {
75
+ workers.clear()
76
+ }
@@ -0,0 +1,140 @@
1
+ import { createQueue } from '../factory'
2
+ import type { JobHandler, AsyncQueueOptions, QueueStrategyType } from '../types'
3
+
4
+ /**
5
+ * Options for running a queue worker.
6
+ */
7
+ export type WorkerRunnerOptions<T = unknown> = {
8
+ /** Name of the queue to process */
9
+ queueName: string
10
+ /** Handler function to process each job */
11
+ handler: JobHandler<T>
12
+ /** Redis connection options (only used for async strategy) */
13
+ connection?: AsyncQueueOptions['connection']
14
+ /** Number of concurrent jobs to process */
15
+ concurrency?: number
16
+ /** Whether to set up graceful shutdown handlers */
17
+ gracefulShutdown?: boolean
18
+ /** If true, don't block - return immediately after starting processing (for multi-queue mode) */
19
+ background?: boolean
20
+ /** Queue strategy to use. Defaults to QUEUE_STRATEGY env var or 'local' */
21
+ strategy?: QueueStrategyType
22
+ }
23
+
24
+ /**
25
+ * Runs a queue worker that processes jobs continuously.
26
+ *
27
+ * This function:
28
+ * 1. Creates an async queue instance
29
+ * 2. Starts a BullMQ worker
30
+ * 3. Sets up graceful shutdown on SIGTERM/SIGINT
31
+ * 4. Keeps the process running until shutdown
32
+ *
33
+ * @template T - The job payload type
34
+ * @param options - Worker configuration
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * import { runWorker } from '@open-mercato/queue/worker'
39
+ *
40
+ * await runWorker({
41
+ * queueName: 'events',
42
+ * handler: async (job, ctx) => {
43
+ * console.log(`Processing ${ctx.jobId}:`, job.payload)
44
+ * },
45
+ * connection: { url: process.env.REDIS_URL },
46
+ * concurrency: 5,
47
+ * })
48
+ * ```
49
+ */
50
+ export async function runWorker<T = unknown>(
51
+ options: WorkerRunnerOptions<T>
52
+ ): Promise<void> {
53
+ const {
54
+ queueName,
55
+ handler,
56
+ connection,
57
+ concurrency = 1,
58
+ gracefulShutdown = true,
59
+ background = false,
60
+ strategy: strategyOption,
61
+ } = options
62
+
63
+ // Determine queue strategy from option, env var, or default to 'local'
64
+ const strategy: QueueStrategyType = strategyOption
65
+ ?? (process.env.QUEUE_STRATEGY === 'async' ? 'async' : 'local')
66
+
67
+ console.log(`[worker] Starting worker for queue "${queueName}" (strategy: ${strategy})...`)
68
+
69
+ const queue = createQueue<T>(queueName, strategy, {
70
+ connection,
71
+ concurrency,
72
+ })
73
+
74
+ // Set up graceful shutdown
75
+ 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'))
90
+ }
91
+
92
+ // Start processing
93
+ await queue.process(handler)
94
+
95
+ console.log(`[worker] Worker running with concurrency ${concurrency}`)
96
+
97
+ if (background) {
98
+ // Return immediately for multi-queue mode
99
+ return
100
+ }
101
+
102
+ console.log('[worker] Press Ctrl+C to stop')
103
+
104
+ // Keep the process alive (single-queue mode)
105
+ await new Promise(() => {
106
+ // This promise never resolves, keeping the worker running
107
+ })
108
+ }
109
+
110
+ /**
111
+ * Creates a worker handler that routes jobs to specific handlers based on job type.
112
+ *
113
+ * @template T - Base job payload type (must include a 'type' field)
114
+ * @param handlers - Map of job types to their handlers
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * const handler = createRoutedHandler({
119
+ * 'user.created': async (job) => { ... },
120
+ * 'order.placed': async (job) => { ... },
121
+ * })
122
+ *
123
+ * await runWorker({ queueName: 'events', handler })
124
+ * ```
125
+ */
126
+ export function createRoutedHandler<T extends { type: string }>(
127
+ handlers: Record<string, JobHandler<T>>
128
+ ): JobHandler<T> {
129
+ return async (job, ctx) => {
130
+ const type = job.payload.type
131
+ const handler = handlers[type]
132
+
133
+ if (!handler) {
134
+ console.warn(`[worker] No handler registered for job type "${type}"`)
135
+ return
136
+ }
137
+
138
+ await handler(job, ctx)
139
+ }
140
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "../../tsconfig.base.json",
4
+ "compilerOptions": {
5
+ "noEmit": true
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist", "**/__tests__/**"]
9
+ }
package/watch.mjs ADDED
@@ -0,0 +1,6 @@
1
+ import { watch } from '../../scripts/watch.mjs'
2
+ import { dirname } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url))
6
+ watch(__dirname)