@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.
- package/build.mjs +61 -0
- package/dist/factory.js +12 -0
- package/dist/factory.js.map +7 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +7 -0
- package/dist/strategies/async.js +125 -0
- package/dist/strategies/async.js.map +7 -0
- package/dist/strategies/local.js +199 -0
- package/dist/strategies/local.js.map +7 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +7 -0
- package/dist/worker/registry.js +41 -0
- package/dist/worker/registry.js.map +7 -0
- package/dist/worker/runner.js +57 -0
- package/dist/worker/runner.js.map +7 -0
- package/jest.config.cjs +19 -0
- package/package.json +53 -0
- package/src/__tests__/local.strategy.test.ts +192 -0
- package/src/factory.ts +55 -0
- package/src/index.ts +28 -0
- package/src/strategies/async.ts +209 -0
- package/src/strategies/local.ts +303 -0
- package/src/types.ts +238 -0
- package/src/worker/__tests__/registry.test.ts +176 -0
- package/src/worker/registry.ts +76 -0
- package/src/worker/runner.ts +140 -0
- package/tsconfig.json +9 -0
- package/watch.mjs +6 -0
|
@@ -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