Tutorial: Your First Job in TypeScript
This tutorial extends the Quickstart with TypeScript types, retry policies, middleware, and batch operations.
Prerequisites
Section titled “Prerequisites”- Docker and Docker Compose
- Node.js 18 or later
- A running OJS server (see Quickstart Step 1)
Step 1: Initialize a TypeScript project
Section titled “Step 1: Initialize a TypeScript project”mkdir ojs-ts-tutorial && cd ojs-ts-tutorialnpm init -ynpm add @openjobspec/sdknpm add -D typescript @types/node tsxnpx tsc --init --target es2022 --module nodenext --moduleResolution nodenextStep 2: Define typed job arguments
Section titled “Step 2: Define typed job arguments”Create src/jobs.ts:
// Define the argument types for each job typeexport type EmailSendArgs = [to: string, template: string, options?: { priority: string }];export type ReportGenerateArgs = [reportId: string, format: 'pdf' | 'csv'];Step 3: Enqueue jobs with types
Section titled “Step 3: Enqueue jobs with types”Create src/enqueue.ts:
import { OJSClient } from '@openjobspec/sdk';import type { EmailSendArgs, ReportGenerateArgs } from './jobs.js';
const client = new OJSClient({ url: 'http://localhost:8080' });
// Type-safe enqueueconst emailJob = await client.enqueue('email.send', [ 'user@example.com', 'welcome', { priority: 'high' },] satisfies EmailSendArgs);
console.log(`Email job: ${emailJob.id} (${emailJob.state})`);
// Enqueue with retry policyconst reportJob = await client.enqueue('report.generate', [ 'rpt_001', 'pdf',] satisfies ReportGenerateArgs, { queue: 'reports', retry: { maxAttempts: 3, backoff: 'exponential', },});
console.log(`Report job: ${reportJob.id} (${reportJob.state})`);Run it:
npx tsx src/enqueue.tsStep 4: Build a worker with middleware
Section titled “Step 4: Build a worker with middleware”Create src/worker.ts:
import { OJSWorker } from '@openjobspec/sdk';
const worker = new OJSWorker({ url: 'http://localhost:8080', queues: ['default', 'reports'], concurrency: 10,});
// Middleware: log every jobworker.use(async (ctx, next) => { const start = Date.now(); console.log(`[START] ${ctx.job.type} (${ctx.job.id})`);
try { await next(ctx); console.log(`[DONE] ${ctx.job.type} took ${Date.now() - start}ms`); } catch (err) { console.error(`[FAIL] ${ctx.job.type} after ${Date.now() - start}ms:`, err); throw err; }});
// Handler: email.sendworker.handle('email.send', async (ctx) => { const [to, template, options] = ctx.args as [string, string, { priority: string }?]; console.log(` Sending "${template}" email to ${to} (priority: ${options?.priority ?? 'normal'})`);
// Simulate email sending await new Promise((r) => setTimeout(r, 200)); return { delivered: true };});
// Handler: report.generateworker.handle('report.generate', async (ctx) => { const [reportId, format] = ctx.args as [string, string]; console.log(` Generating ${format.toUpperCase()} report ${reportId}`);
// Simulate report generation await new Promise((r) => setTimeout(r, 1000)); return { url: `https://reports.example.com/${reportId}.${format}` };});
// Graceful shutdownprocess.on('SIGINT', () => worker.stop());process.on('SIGTERM', () => worker.stop());
await worker.start();console.log('Worker started on queues: default, reports');Run the worker:
npx tsx src/worker.tsStep 5: Batch enqueue
Section titled “Step 5: Batch enqueue”Enqueue multiple jobs in a single request:
import { OJSClient } from '@openjobspec/sdk';
const client = new OJSClient({ url: 'http://localhost:8080' });
const jobs = await client.enqueueBatch([ { type: 'email.send', args: ['alice@example.com', 'welcome'] }, { type: 'email.send', args: ['bob@example.com', 'welcome'] }, { type: 'email.send', args: ['carol@example.com', 'welcome'] },]);
console.log(`Enqueued ${jobs.length} jobs`);jobs.forEach((j) => console.log(` ${j.id}: ${j.state}`));Step 6: Check job status
Section titled “Step 6: Check job status”import { OJSClient } from '@openjobspec/sdk';
const client = new OJSClient({ url: 'http://localhost:8080' });
const jobId = process.argv[2];if (!jobId) { console.error('Usage: npx tsx src/status.ts <job-id>'); process.exit(1);}
const job = await client.getJob(jobId);console.log(`Job ${job.id}:`);console.log(` Type: ${job.type}`);console.log(` State: ${job.state}`);console.log(` Attempt: ${job.attempt}`);if (job.result) console.log(` Result: ${JSON.stringify(job.result)}`);if (job.errors?.length) console.log(` Errors: ${job.errors.length} recorded`);What you built
Section titled “What you built”- Type-safe job definitions with TypeScript argument types
- Custom middleware for logging and error tracking
- Multi-queue workers processing different job types
- Batch enqueue for efficient bulk operations
- Job status inspection for monitoring
Next steps
Section titled “Next steps”- Add workflow orchestration to chain jobs together
- Explore unique jobs for deduplication
- Read the SDK API reference for all available operations