Core Building Blocks / Queue & Worker
The Queue Builder
Define typed queue contracts with lifecycle config, execution profiles, and result policies.
getQueueBuilder (called on a service builder) declares what work a queue can perform, how it is typed, and how jobs behave during their lifecycle. It does not define how the work is executed — that is the worker’s job.
Builder workflow
flowchart LR
A[Call getQueueBuilder] --> B[Define Payload Schema]
B --> C[Set Lifecycle Config]
C --> D[Set Execution Profile]
D --> E[Set Result Policy]
E --> F[Add to Service]
Step 1 — Create the builder
import { imageV1ServiceBuilder } from '../imageV1ServiceBuilder.js'
const imageQueue = imageV1ServiceBuilder.getQueueBuilder(
'processImage',
'Process uploaded images in the background',
)
Step 2 — Define the payload schema
import { z } from 'zod'
const imagePayloadSchema = z.object({
imageUrl: z.string().url(),
maxWidth: z.number().optional(),
maxHeight: z.number().optional(),
format: z.enum(['jpeg', 'png', 'webp']).default('jpeg'),
})
imageQueue.addPayloadSchema(imagePayloadSchema)
Step 3 — Set lifecycle configuration
imageQueue.setLifecycleConfig({
maxAttempts: 4, // 1 initial + 3 retries
visibilityTimeoutMs: 30_000, // lock expires after 30s if worker goes silent
retryWindowMs: 3_600_000, // retry window: 1 hour
poisonMessageFailureThreshold: 10,
poisonMessageAction: 'pause-worker',
})
| Option | Description |
|---|---|
maxAttempts | Total attempts before the job is dead-lettered (1 initial + retries) |
visibilityTimeoutMs | How long a job is locked to a worker before it becomes visible again |
heartbeatIntervalMs | How often workers should heartbeat to hold the lock |
retryWindowMs | Total window within which retries are allowed |
retryStrategy | Retry strategy object controlling backoff behavior |
poisonMessageFailureThreshold | Failure count before treating as poison |
poisonMessageAction | 'none' or 'pause-worker' |
Step 4 — Set execution profile
imageQueue.setExecutionProfile('longRunning', {
maxRuntimeMs: 300_000, // 5 minutes
strict: false, // allow slight overrun
})
Use longRunning for jobs that may take minutes or hours. Use the default profile for quick tasks.
Step 5 — Set result policy
imageQueue.setResultPolicy({
mode: 'event',
successEventName: 'imageProcessed',
})
// Or use the convenience helper:
// imageQueue.emitResultAsEvent('imageProcessed')
| Mode | Behavior |
|---|---|
none | Job completes silently |
event | Emit a custom event on success |
state | Store result for later retrieval |
state-and-event | Both emit and store |
Step 6 — Add to the service
import { imageV1ServiceBuilder } from './imageV1ServiceBuilder.js'
import { imageQueue } from './queues/imageQueue.js'
import { imageWorker } from './workers/imageWorker.js'
export const imageV1Service = imageV1ServiceBuilder
.addQueueDefinition(imageQueue)
.addQueueWorkerDefinition(imageWorker)
const imageService = await imageV1ServiceBuilder.getInstance(eventBridge, { queueBridge })
await imageService.start()
Full example
import { z } from 'zod'
import { imageV1ServiceBuilder } from '../imageV1ServiceBuilder.js'
export const imageQueue = imageV1ServiceBuilder
.getQueueBuilder(
'processImage',
'Process uploaded images in the background',
)
.addPayloadSchema(z.object({
imageUrl: z.string().url(),
maxWidth: z.number().optional(),
maxHeight: z.number().optional(),
format: z.enum(['jpeg', 'png', 'webp']).default('jpeg'),
}))
.setLifecycleConfig({
maxAttempts: 4,
visibilityTimeoutMs: 30_000,
retryWindowMs: 3_600_000,
poisonMessageFailureThreshold: 5,
poisonMessageAction: 'pause-worker',
})
.setExecutionProfile('longRunning', {
maxRuntimeMs: 300_000,
strict: false,
})
.setResultPolicy({
mode: 'event',
successEventName: 'imageProcessed',
})
Registering with the service
Pass the queue builder directly to .addQueueDefinition() on the service:
export const imageV1Service = imageV1ServiceBuilder
.addQueueDefinition(imageQueue)
.addQueueWorkerDefinition(imageWorker)