Skip to content

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.

A task is a named, retryable unit of work:

// @skip-typecheck
import { 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.`,
});
},
});
// From a route handler
route.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 subscriber
bus.on('auth:user.created', async ({ userId, email }) => {
await orchestrator.enqueue(sendWelcomeEmail, { email, name: userId });
});

The request returns immediately. The task runs in the background.

When work has multiple steps that depend on each other:

// @skip-typecheck
import { 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.

Orchestration adapters control where and how tasks execute:

AdapterBest for
memoryDevelopment and testing
sqliteSingle-instance production
bullmqMulti-instance with Redis
temporalEnterprise durable workflows
// @skip-typecheck
import { 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-typecheck
import { 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.

  • 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

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.