Control flow library for your persistency layer driven applications.
Documentation | Getting Started
Run your application logic as a series of background jobs that are started alongside state change transactions in your persistency layer. Perform long-running tasks with side-effects reliably in the background and keep track of their progress in your database. Own your stack and avoid vendor lock-in by using the tools you trust.
Imagine a user signs up and you want to send them a welcome email. You don't want to block the registration request, so you queue it as a background job.
const jobTypeRegistry = defineJobTypeRegistry<{
"send-welcome-email": {
entry: true;
input: { userId: number; email: string; name: string };
output: { sentAt: string };
};
}>();
const client = await createClient({
stateAdapter,
registry: jobTypeRegistry,
});
await withTransactionHooks(async (transactionHooks) =>
db.transaction(async (tx) => {
const user = await tx.users.create({
name: "Alice",
email: "alice@example.com",
});
await client.startJobChain({
tx,
transactionHooks,
typeName: "send-welcome-email",
input: { userId: user.id, email: user.email, name: user.name },
});
}),
);We scheduled the job inside a database transaction. This ensures that if the transaction rolls back (e.g., user creation fails), the job is not started. No orphaned emails. (Refer to transactional outbox pattern.)
Later, a background worker picks up the job and sends the email:
const worker = await createInProcessWorker({
client,
processorRegistry: createJobTypeProcessorRegistry(client, jobTypeRegistry, {
"send-welcome-email": {
attemptHandler: async ({ job, complete }) => {
await sendEmail({
to: job.input.email,
subject: "Welcome!",
body: `Hello ${job.input.name}, welcome to our platform!`,
});
return complete(async () => ({
sentAt: new Date().toISOString(),
}));
},
},
}),
});
const stop = await worker.start(); // Call stop() for graceful shutdown- Your database is the source of truth — No separate persistence layer. Jobs live alongside your application data.
- True transactional consistency — Start jobs inside your database transactions. If the transaction rolls back, the job is never created. No dual-write problems.
- No vendor lock-in — Works with PostgreSQL and SQLite. Bring your own ORM (Kysely, Drizzle, Prisma, raw drivers).
- Simple mental model — Job chains work like Promise chains. No determinism requirements, no replay semantics to learn.
- Full type safety — TypeScript inference for inputs, outputs, continuations, and blockers. Catch errors at compile time.
- Flexible notifications — Use Redis, NATS, or PostgreSQL LISTEN/NOTIFY for low-latency. Or just poll—no extra infrastructure required.
- MIT licensed — No enterprise licensing concerns.
# Core package (required)
npm install queuert
# State adapters (pick one)
npm install @queuert/postgres # PostgreSQL - recommended for production
npm install @queuert/sqlite # SQLite (experimental)
# Notify adapters (optional, for reduced latency)
npm install @queuert/redis # Redis pub/sub - recommended for production
npm install @queuert/nats # NATS pub/sub (experimental)
# Or use PostgreSQL LISTEN/NOTIFY via @queuert/postgres (no extra infra)
# Dashboard (optional)
npm install @queuert/dashboard # Embeddable web UI for job observation
# Observability (optional)
npm install @queuert/otel # OpenTelemetry metrics and histogramsVisit the documentation site for guides on:
- Transaction Hooks
- Job Processing Modes
- Job Chain Patterns
- Error Handling
- Scheduling & Recurring Jobs
- Deduplication
- Feature Slices
- Horizontal Scaling
- Dashboard
- Observability
- And more...
MIT