Jobs and Orchestration
Some work does not belong in a request handler. Sending a welcome email should not make the registration endpoint slower. Importing 10,000 rows should not time out an HTTP request. Retrying a flaky external API call needs its own lifecycle.
Move that work into tasks and workflows.
Define a task
Section titled “Define a task”A task is a named, retryable unit of work:
// @skip-typecheckimport { defineTask } from '@lastshotlabs/slingshot-orchestration-engine';
declare const emailService: { send(opts: { to: string; subject: string; body: string }): Promise<void>;};
const sendWelcomeEmail = defineTask({ name: 'send-welcome-email', handler: async (input: { email: string; name: string }) => { await emailService.send({ to: input.email, subject: 'Welcome!', body: `Hi ${input.name}, welcome to the platform.`, }); },});Trigger it from a route or event
Section titled “Trigger it from a route or event”// From a route handlerroute.post({ path: '/users', auth: 'userAuth', handler: async ({ body, respond }) => { const user = await createUser(body);
// Fire and forget — runs in the background await orchestrator.enqueue(sendWelcomeEmail, { email: user.email, name: user.name, });
return respond.json(user, 201); },});
// Or from an event subscriberbus.on('auth:user.created', async ({ userId, email }) => { await orchestrator.enqueue(sendWelcomeEmail, { email, name: userId });});The request returns immediately. The task runs in the background.
Compose tasks into workflows
Section titled “Compose tasks into workflows”When work has multiple steps that depend on each other:
// @skip-typecheckimport { defineWorkflow } from '@lastshotlabs/slingshot-orchestration-engine';
const onboardUser = defineWorkflow({ name: 'onboard-user', steps: [ { task: sendWelcomeEmail }, { task: createDefaultWorkspace, delay: '5s' }, { task: scheduleCheckIn, delay: '7d' }, ],});Workflows handle retries, delays, and failure recovery. If createDefaultWorkspace fails,
it retries without re-sending the welcome email.
Choose the right adapter
Section titled “Choose the right adapter”Orchestration adapters control where and how tasks execute:
| Adapter | Best for |
|---|---|
memory | Development and testing |
sqlite | Single-instance production |
bullmq | Multi-instance with Redis |
temporal | Enterprise durable workflows |
// @skip-typecheckimport { createOrchestrationPackage } from '@lastshotlabs/slingshot-orchestration';
createOrchestrationPackage({ adapter: 'sqlite', // swap to 'bullmq' when you need multi-instance tasks: [sendWelcomeEmail], workflows: [onboardUser],});Your task and workflow definitions stay the same. Only the adapter changes.
Reading framework state from hooks and tasks
Section titled “Reading framework state from hooks and tasks”Workflow lifecycle hooks (onStart, onComplete, onFail) and TaskContext
both carry an optional services?: HookServices accessor. It exposes the same
typed entity reader, capability resolver, plugin-state map, event bus, and
logger that route handlers see — so background work can fan out into the rest
of the framework without capturing the app reference manually.
// @skip-typecheckimport { defineTask, defineWorkflow } from '@lastshotlabs/slingshot-orchestration-engine';
interface UserAdapter { markOnboarded(id: string): Promise<void>;}
const finalize = defineTask({ name: 'finalize-onboarding', handler: async (input: { userId: string }, ctx) => { // ctx.services is undefined for adapters that run in worker isolates // (notably the Temporal adapter). Guard before use. if (!ctx.services) return; const users = ctx.services.entities.get<UserAdapter>({ entity: 'User' }); await users.markOnboarded(input.userId); },});
const onboardUser = defineWorkflow({ name: 'onboard-user', steps: [{ task: finalize }], onComplete: async ({ runId, services }) => { services?.bus.emit('app:user.onboarded', { runId }); },});The Temporal adapter explicitly sets services: undefined because activities
run in a separate Node.js process from the Slingshot app — the framework
references aren’t reachable there. If a task needs framework state under
Temporal, do the read at the workflow level (where hooks run in-process) and
thread the data into the task’s typed input. See
HookServices for the full contract.
Common use cases
Section titled “Common use cases”- Emails: send welcome emails, password resets, notifications
- Imports: process CSV uploads, sync external data
- Retries: call external APIs with exponential backoff
- Scheduling: run cleanup jobs, send reminders, generate reports
- Pipelines: multi-step onboarding, approval flows, data processing
Next in your journey
Section titled “Next in your journey”Your app reads, writes, identifies, authorizes, streams, and runs background work. The journey ends with shipping it:
- Production — readiness checklist, deployment, runtime selection, secrets, and horizontal scaling.
Go deeper into orchestration
Section titled “Go deeper into orchestration”- Orchestration Overview — architecture and concepts
- Orchestration Guide — detailed task and workflow authoring
- Tasks and Workflows — full API reference
- Adapters — adapter setup and configuration
- Orchestration Example — complete working app