Defining Jobs
Jobs in Glasswork are defined using the defineJob helper. This function creates a type-safe definition that includes the job's name, configuration, payload schema, and handler.
Basic Definition
A minimal job definition requires a name and a handler.
import { defineJob } from 'glasswork/jobs';
export const simpleJob = defineJob({
name: 'simple-task',
handler: async (payload, context) => {
// Do work
},
});Payload Validation
Glasswork uses Valibot for runtime payload validation. By providing a schema, you ensure that your job handler always receives valid data.
import * as v from 'valibot';
import { defineJob } from 'glasswork/jobs';
export const processOrder = defineJob({
name: 'process-order',
schema: v.object({
orderId: v.string(),
amount: v.number(),
currency: v.picklist(['USD', 'EUR', 'GBP']),
}),
handler: async ({ orderId, amount }, { logger }) => {
// Types are inferred automatically!
// orderId is string
// amount is number
logger.info({ orderId, amount }, 'Processing order');
},
});If the payload does not match the schema, the job will fail with an InvalidJobPayloadError. This error is treated as retriable—the job will retry until the configured maxAttempts is reached, then go to the Dead Letter Queue (DLQ).
Retry Configuration
Configure how many times a job should retry before being considered "dead":
// Default: 25 retries, send to DLQ when exhausted
export const emailJob = defineJob({
name: 'send-email',
handler: async () => { ... },
});
// Custom retry count
export const webhookJob = defineJob({
name: 'send-webhook',
retry: { maxAttempts: 5 },
handler: async () => { ... },
});
// No retries (fire-and-forget)
export const analyticsJob = defineJob({
name: 'track-event',
retry: false,
handler: async () => { ... },
});
// Shorthand syntax
export const quickJob = defineJob({
name: 'quick-task',
retry: 3, // Same as { maxAttempts: 3 }
handler: async () => { ... },
});See Error Handling & Retries for complete retry documentation.
Queue Configuration
You can specify a target queue for the job. If omitted, the job will be sent to the default queue configured in the JobService.
export const highPriorityJob = defineJob({
name: 'critical-task',
queue: 'high-priority',
handler: async () => { ... },
});Job Uniqueness (FIFO)
For SQS FIFO queues, you can enforce job uniqueness to prevent duplicate processing. Glasswork allows you to define a unique key generator based on the payload.
export const generateReport = defineJob({
name: 'generate-report',
queue: 'reports.fifo',
unique: {
// Only one job per reportId will be processed at a time
key: (payload) => `report-${payload.reportId}`,
},
handler: async () => { ... },
});When using unique.key:
- The generated key is used as the SQS
MessageDeduplicationId. - SQS ensures that if a message with the same deduplication ID is sent within a 5-minute interval, it is treated as a duplicate and not delivered.
The Job Handler
The handler function receives two arguments:
- Payload: The validated data for the job.
- Context: An object containing useful utilities.
Job Context
interface JobContext {
/**
* The dependency injection container cradle.
* Contains all your services (e.g., services.userService).
*/
services: Record<string, unknown>;
/**
* A logger instance scoped to this job execution.
* Automatically includes jobId and jobName in logs.
*/
logger: Logger;
/**
* The unique ID of the job execution.
*/
jobId: string;
/**
* The number of times this job has been attempted.
* Starts at 1.
*/
attemptNumber: number;
/**
* The date when the job was originally enqueued.
*/
enqueuedAt: Date;
}Periodic Jobs
For jobs that run on a schedule (e.g., cron), use definePeriodicJob. These jobs typically don't have a payload and are triggered by EventBridge.
import { definePeriodicJob } from 'glasswork/jobs';
export const dailyCleanup = definePeriodicJob({
name: 'daily-cleanup',
handler: async (_payload, { services, logger }) => {
logger.info('Starting daily cleanup');
await services.cleanupService.run();
},
});See Dispatching & Scheduling for how to configure the infrastructure for periodic jobs.
Schema Input and Handler Output
JobService accepts and validates schema input, then sends the original payload to the queue. The worker parses that input and passes the validated output to the handler, applying transforms, defaults, and unknown-key stripping. The mock driver follows the same contract when executing jobs immediately.
The schema runs at enqueue time and on each execution attempt, always against the original input. Output is never passed back through the schema: an increment transform, for example, increments the value once. Keep transforms and default factories pure and deterministic, without side effects. Payload limits, uniqueness keys, enqueue hooks, and worker hooks use the original input.
JobDefinition<TInput, TOutput = TInput> separates these types, and defineJob infers both from the schema. Existing jobs without transforms retain their types. Handlers can now rely on schema defaults and transforms; update explicit type annotations where input and output differ. Existing queued messages remain schema input and need no message-format migration.
