11import { EventEmitter } from 'events' ;
2- import type { JobStatus , JobMeta , QueueMessage , QueueEvent , JobData , JobOptions , BaseJobRequest } from '../interfaces/job.ts' ;
2+ import type { JobStatus , JobMeta , QueueMessage , QueueEvent , JobData , JobOptions , BaseJobRequest , JobContext , JobHandlers } from '../interfaces/job.ts' ;
33import type { QueuePlugin , QueueOptions } from '../interfaces/plugin.ts' ;
44
55/**
@@ -34,6 +34,16 @@ export abstract class Queue<TJobMap = Record<string, any>, TJobRequest extends B
3434 protected pluginDisposers : Array < ( ) => Promise < void > > = [ ] ;
3535 public readonly name ?: string ;
3636
37+ /**
38+ * Registry of job handlers mapping job names to their handler functions.
39+ */
40+ public handlers : Map < string , Function > = new Map ( ) ;
41+
42+ /**
43+ * Flag indicating whether handlers have been registered.
44+ */
45+ public handlersRegistered = false ;
46+
3747 /**
3848 * Indicates whether this queue driver supports long polling.
3949 * Drivers that support long polling (like SQS) can efficiently wait for messages.
@@ -111,31 +121,78 @@ export abstract class Queue<TJobMap = Record<string, any>, TJobRequest extends B
111121 }
112122
113123 /**
114- * Registers a handler function for a specific job type with type safety.
124+ * Sets all job handlers at once. This method must be called before starting the queue.
125+ * All job types defined in TJobMap must have corresponding handlers.
126+ *
127+ * @param handlers - Complete mapping of job names to their handler functions
128+ *
129+ * @example
130+ * ```typescript
131+ * queue.setHandlers({
132+ * 'send-email': async ({ payload }, queue) => {
133+ * await emailService.send(payload.to, payload.subject, payload.body);
134+ * },
135+ * 'resize-image': async (job, queue) => {
136+ * const { payload, id } = job;
137+ * console.log(`Processing image resize job ${id}`);
138+ * await imageService.resize(payload.url, payload.width, payload.height);
139+ * }
140+ * });
141+ * ```
142+ */
143+ setHandlers ( handlers : JobHandlers < TJobMap > ) : void {
144+ this . handlers . clear ( ) ;
145+ for ( const [ jobName , handler ] of Object . entries ( handlers ) as Array < [ string , Function ] > ) {
146+ this . handlers . set ( jobName , handler ) ;
147+ }
148+ this . handlersRegistered = true ;
149+ }
150+
151+ /**
152+ * Sets or replaces a handler for a specific job type.
153+ * Useful for testing or dynamically updating handlers.
115154 *
116155 * @template K - The job name type from TJobMap
117156 * @param jobName - The name of the job type to handle
118157 * @param handler - Function to execute when this job type is processed
119- * @returns This queue instance for method chaining
120158 *
121159 * @example
122160 * ```typescript
123- * queue.onJob('send-email', async (payload) => {
124- * // payload is automatically typed as { to: string; subject: string; body: string }
125- * await emailService.send(payload.to, payload.subject, payload.body);
126- * });
127- *
128- * queue.onJob('resize-image', async (payload) => {
129- * // payload is automatically typed as { url: string; width: number; height: number }
130- * await imageService.resize(payload.url, payload.width, payload.height);
161+ * // Replace handler for testing
162+ * queue.setHandler('send-email', async ({ payload }, queue) => {
163+ * console.log('Mock email sent to:', payload.to);
131164 * });
132165 * ```
133166 */
134- onJob < K extends keyof TJobMap > (
167+ setHandler < K extends keyof TJobMap > (
135168 jobName : K ,
136- handler : ( payload : TJobMap [ K ] ) => Promise < void >
137- ) : this {
138- return super . on ( `job:${ String ( jobName ) } ` , handler ) ;
169+ handler : ( job : JobContext < TJobMap [ K ] > , queue : Queue < TJobMap > ) => Promise < void > | void
170+ ) : void {
171+ this . handlers . set ( String ( jobName ) , handler ) ;
172+ }
173+
174+ /**
175+ * Gets the current handler for a specific job type.
176+ * Useful for testing or introspection.
177+ *
178+ * @template K - The job name type from TJobMap
179+ * @param jobName - The name of the job type
180+ * @returns The handler function or undefined if not registered
181+ */
182+ getHandler < K extends keyof TJobMap > ( jobName : K ) : Function | undefined {
183+ return this . handlers . get ( String ( jobName ) ) ;
184+ }
185+
186+ /**
187+ * Validates that handlers have been registered before starting the queue.
188+ */
189+ public validateHandlers ( ) : void {
190+ if ( ! this . handlersRegistered ) {
191+ throw new Error (
192+ 'Handlers must be registered with setHandlers() before calling run(). ' +
193+ 'Use queue.setHandlers({ ... }) to register all job type handlers.'
194+ ) ;
195+ }
139196 }
140197
141198 override on ( event : string | symbol , listener : ( ...args : any [ ] ) => void ) : this {
@@ -162,6 +219,9 @@ export abstract class Queue<TJobMap = Record<string, any>, TJobRequest extends B
162219 * ```
163220 */
164221 async run ( repeat : boolean = false , timeout : number = 0 ) : Promise < void > {
222+ // Validate that handlers have been registered
223+ this . validateHandlers ( ) ;
224+
165225 const disposers = [ ...this . pluginDisposers ] ;
166226
167227 // 1. Initialize plugins if not already initialized
@@ -295,18 +355,23 @@ export abstract class Queue<TJobMap = Record<string, any>, TJobRequest extends B
295355 const beforeEvent : QueueEvent = { type : 'beforeExec' , id : message . id , name, payload, meta : message . meta } ;
296356 this . emit ( 'beforeExec' , beforeEvent ) ;
297357
298- // Execute the job handler
299- const jobEvent = `job:${ name } ` ;
300-
301- if ( this . listenerCount ( jobEvent ) === 0 ) {
358+ // Get the registered handler for this job type
359+ const handler = this . handlers . get ( name ) ;
360+ if ( ! handler ) {
302361 throw new Error ( `No handler registered for job type: ${ name } ` ) ;
303362 }
304363
305- // Get all handlers for this job type and execute them
306- const handlers = this . listeners ( jobEvent ) as Array < ( payload : any ) => Promise < void > > ;
307- const results = await Promise . all ( handlers . map ( handler => handler ( payload ) ) ) ;
308-
309- const result = results . length === 1 ? results [ 0 ] : results ;
364+ // Create job context object with full job information
365+ const jobContext : JobContext < any > = {
366+ id : message . id ,
367+ payload : payload ,
368+ meta : message . meta ,
369+ pushedAt : message . meta . pushedAt ,
370+ reservedAt : message . meta . reservedAt
371+ } ;
372+
373+ // Execute the handler with job context and queue reference
374+ const result = await handler ( jobContext , this ) ;
310375
311376 const afterEvent : QueueEvent = { type : 'afterExec' , id : message . id , name, payload, meta : message . meta , result } ;
312377 this . emit ( 'afterExec' , afterEvent ) ;
0 commit comments