🚧 AvleonJs is in active development and not ready for production use.

Queue

Background jobs with BullMQ.

Avleon's queue is built on BullMQ, which runs on Redis. Both are optional peer dependencies:

npm i bullmq ioredis

There are two ways to work with jobs: a queue class, which both produces and consumes jobs, and a worker class, which only consumes a queue someone else fills.

A queue

Extend AvleonQueue<T> and decorate it with @Queue. T is the shape of the job payload.

queues/email.queue.ts
import { AvleonQueue, Queue, JobHandler, Job } from '@avleon/core';

export type EmailPayload = {
  to: string;
  subject: string;
};

@Queue({
  name: 'email',
  adapter: { connection: { host: '127.0.0.1', port: 6379 } },
})
export class EmailQueue extends AvleonQueue<EmailPayload> {
  @JobHandler('welcome')
  async sendWelcome(job: Job<EmailPayload>) {
    console.log(`Sending welcome mail to ${job.data.to}`);
  }

  @JobHandler('reset-password')
  async sendReset(job: Job<EmailPayload>) {
    console.log(`Sending reset mail to ${job.data.to}`);
  }
}

Each @JobHandler('name') handles jobs added under that name. For jobs added without a name — or with a name no handler matches — define a handler method on the class, or pass handler in the decorator config.

Registering

useWorker resolves the class through the container, which is what starts its worker. Workers are closed automatically when the app shuts down.

app.ts
import { Avleon } from '@avleon/core';
import { EmailQueue } from './queues/email.queue';

const app = Avleon.createApplication();

app.useWorker([EmailQueue]);

export default app;

Adding jobs

Inject the queue anywhere and call add:

services/signup.service.ts
import { AppService } from '@avleon/core';
import { EmailQueue } from '../queues/email.queue';

@AppService
export class SignupService {
  constructor(private readonly emailQueue: EmailQueue) {}

  async register(email: string) {
    await this.emailQueue.add('welcome', {
      to: email,
      subject: 'Welcome aboard',
    });
  }
}

Other producer methods:

// run 30 seconds from now
await emailQueue.delay('welcome', { to, subject }, 30_000);

// add with any BullMQ job option
await emailQueue.add('welcome', { to, subject }, { attempts: 3, backoff: 1000 });

// inspect and control
const job = await emailQueue.getJob(jobId);
await emailQueue.pause();
await emailQueue.resume();

A worker

When the producer lives elsewhere, extend AvleonWorkerBase<T> and point @AvleonWorker at the queue name. Implement process; the lifecycle hooks are optional.

workers/report.worker.ts
import { AvleonWorker, AvleonWorkerBase, Job } from '@avleon/core';

export type ReportPayload = { reportId: number };

@AvleonWorker({
  queue: 'report',
  concurrency: 5,
  connection: { host: '127.0.0.1', port: 6379 },
})
export class ReportWorker extends AvleonWorkerBase<ReportPayload> {
  async process(job: Job<ReportPayload>) {
    return { generated: job.data.reportId };
  }

  onCompleted(job: Job<ReportPayload>, result: any) {
    console.log(`Report ${job.data.reportId} done`, result);
  }

  onFailed(job: Job<ReportPayload> | undefined, error: Error) {
    console.error(`Report failed:`, error.message);
  }
}

Register it the same way:

app.useWorker([EmailQueue, ReportWorker]);

A worker starts as soon as it is resolved. Pass autoStart: false in the decorator config to start it yourself later with .start().

Note: bullmq is loaded lazily, only when a queue or worker actually starts — so importing @avleon/core never requires Redis to be running.