Skip to content

Jobs ​

Jobs are self-contained units of work you can dispatch onto a queue to run in the background. @arkstack/jobs is the authoring layer: it provides the Job base class, the dispatch() helper, and a registry that lets a worker reconstruct your job classes from a stored payload.

Jobs build on Queue. The queue config (src/config/queue.ts) determines where dispatched jobs go and how they are processed — jobs themselves have no separate config file.

Install ​

Full app templates include the jobs package. If you are adding it manually, install it alongside the queue:

sh
npm i @arkstack/jobs @arkstack/queue
sh
pnpm add @arkstack/jobs @arkstack/queue
sh
yarn add @arkstack/jobs @arkstack/queue

Importing @arkstack/jobs anywhere wires the queue (de)serialization automatically. For an explicit bootstrap hook — alongside @arkstack/database/setup — import the setup entry:

ts
import '@arkstack/jobs/setup';

Writing a job ​

Generate one with the CLI:

sh
ark make:job SendWelcomeEmail

This creates src/app/jobs/SendWelcomeEmail.ts. Extend Job and implement handle():

ts
// src/app/jobs/SendWelcomeEmail.ts
import { Job } from '@arkstack/jobs';

export class SendWelcomeEmail extends Job {
  constructor(public userId: number) {
    super();
  }

  async handle() {
    // ... send the email
  }
}

A job's constructor arguments become its serialized state, so keep them to plain, serializable values (ids, primitives, plain objects) rather than live instances.

Dispatching ​

ts
import { dispatch } from '@arkstack/jobs';
import { SendWelcomeEmail } from '@app/jobs/SendWelcomeEmail';

// static helper
await SendWelcomeEmail.dispatch(user.id);
await SendWelcomeEmail.dispatch(user.id).onQueue('mail').withDelay(60);
await SendWelcomeEmail.dispatchSync(user.id); // run inline now

// functional helper
await dispatch(new SendWelcomeEmail(user.id));
await dispatch(new SendWelcomeEmail(user.id), { queue: 'mail', delay: 60 });

The pending dispatch is awaitable and chainable:

MethodDescription
onConnection(name)Send to a specific queue connection.
onQueue(name)Send to a specific queue.
withDelay(seconds | Date)Delay before the job becomes available.

With the default sync connection the job runs immediately. Configure a database or redis connection (see Queue) and run a worker to process jobs in the background:

sh
ark queue:work

Retries & failure handling ​

Control attempts and backoff with instance properties, and react to permanent failure with a failed hook:

ts
import { Job } from '@arkstack/jobs';

export class ChargeInvoice extends Job {
  tries = 3; // max attempts before the job is marked failed
  backoff = 30; // seconds to wait before a released job retries

  constructor(public invoiceId: number) {
    super();
  }

  async handle() {
    // ...
  }

  async failed(error: unknown) {
    // called once attempts are exhausted
  }
}

How reconstruction works ​

Each Job registers itself with the JobRegistry when constructed. When a worker pops a payload, the registry rebuilds the instance — bypassing the constructor — and assigns the serialized data back onto it. Override serialize() for custom payloads:

ts
serialize() {
  return { userId: this.userId };
}

A dedicated worker process constructs none of your jobs, so ark queue:work imports every job module in src/app/jobs (or its build output) before it starts working, registering each class it finds under its class name. Jobs kept elsewhere can be registered by loading that directory instead:

ts
import { loadJobs } from '@arkstack/jobs';

await loadJobs('domain/jobs'); // relative to src/, or the build output

JobRegistry.register(MyJob) still registers a single class explicitly.