Configuration

Everything worker.config.ts accepts.

worker.config.ts is the single source of truth for a worker. The CLI loads it for dev, build, and start; pass --config <path> to use a different file.

worker.config.ts
import { defineConfig, postgresAdapter } from '@openqueue/sdk';

export default defineConfig({
  namespace: 'my-app',
  dirs: ['./worker'],
  exclude: ['**/*.test.ts'],
  redis: {
    url: process.env.REDIS_URL!,
  },
  concurrency: {
    global: 8,
    queues: { exports: 2 },
  },
  workbench: {
    enabled: true,
    title: 'Jobs',
    basePath: '/workbench',
  },
});

Reference

FieldDescription
namespacePrefix for every key this worker touches. Lets multiple apps share one backend.
dirsDirectories scanned for exported task() definitions.
tasksExplicit task modules ({ module, export? }) instead of — or alongside — directory scanning.
excludeGlob patterns removed from discovery (tests, fixtures).
redis.urlRedis connection string. Sugar for a BullMQ world; XOR with world.
redis.bullPrefixOverride the BullMQ key prefix.
worldA world factory (e.g. worldPostgres({ url })) — the non-BullMQ path. XOR with redis. When set, the world owns durable state, so storage is not accepted.
storageA QueueStorage — use postgresAdapter to persist run history alongside the redis transport.
drainsSinks for run lifecycle events; consoleDrain() ships in the box, composeDrains() combines several.
retentionAge-based pruning of durable run history — { completed: 30, failed: 90, logs: 30 } days by default, false keeps a category forever. See Retention.
concurrency.globalWorker-wide cap on parallel jobs.
concurrency.queuesPer-queue caps, by queue name.
api.tokenBearer token(s) for the /openqueue/v1 control API. Sugar for a leading apiKey() strategy. Unset = open in dev, 401 in production.
api.authOrdered AuthStrategy walk for the control API. Empty array = always 401 (fail-closed).
metricsToggle metric collection and key prefix.
workbenchDashboard options — see below.
lifecycleonReady / onShutdown hooks run in the worker process — on both openqueue dev and the built artifact. Each receives the runtime; enqueue with runtime.trigger(id, input, { jobId }). See below.
buildoutDir, extraFiles, external for openqueue build.

Lifecycle hooks

Run code inside the worker process itself, on both openqueue dev and the built artifact. The canonical use is post-deploy convergence: the worker is the only process that observes "a new build just booted", and it already holds a runtime that can enqueue.

export default defineConfig({
  lifecycle: {
    onReady: async (runtime) => {
      await runtime.trigger(
        'release',
        { release: BUILD_ID },
        { jobId: `release:${BUILD_ID}` },
      );
    },
  },
});
FieldDescription
onReadyRuns once every consumer is started, so a job enqueued here is immediately consumable by this worker.
onShutdownRuns on shutdown after /ready starts failing but before consumers drain, so the runtime is still live enough to enqueue.

Both hooks run on every replica, and a rollout boots several. When the effect must happen once per cluster, pass a stable jobIdrelease:${BUILD_ID} above — so concurrent boots collapse to a single job.

Neither hook can take the process down. A throwing hook is logged and the boot (or drain) continues. So is a hook still running after 10 seconds: the worker stops waiting and carries on, and the hook keeps running detached. That budget is sized for an enqueue, which is what these hooks are for — do the actual work in the job you trigger, not in the hook, or a slow onReady will hold the port shut and a slow onShutdown will eat the termination grace period.

Workbench options

FieldDescription
enabledServe the dashboard from this worker.
titleName shown in the dashboard chrome.
basePathMount path, for example /workbench.
readonlyDisable mutating actions (retry, enqueue, pause).
auth{ username, password } for basic auth, or an ordered AuthStrategy walk. Unset = dashboard open.
tagFieldsPayload field names that Workbench can extract as filterable tags.

Persistent run history

Redis keeps recent state; Postgres keeps the paper trail. Wire the Drizzle adapter to persist runs, schedules, and alerts:

import { defineConfig, postgresAdapter } from '@openqueue/sdk';
import { db } from './src/db';
import { queueSchema } from './src/queue-schema';

export default defineConfig({
  namespace: 'my-app',
  dirs: ['./worker'],
  redis: { url: process.env.REDIS_URL! },
  storage: postgresAdapter({ db, schema: queueSchema }),
});

See Persistence for the table definitions, how to generate the Drizzle migrations, and retention patterns.

On this page