Durable Execution for Modern Backends
Make any function survive server crashes, restarts, and deployments.
Reseolio is an open-source, polyglot sidecar that brings durable execution to your existing application stack. Backed by PostgreSQL, it ensures your critical functions run to completion—even across server crashes, restarts, or node failures.
It runs alongside your application as an ultra-lightweight Rust binary (~5MB RAM), managing job state globally while delegating actual code execution back to your application.
# Install
npm install reseolio
# That's it! No separate server to run.import { Reseolio } from 'reseolio';
const reseolio = new Reseolio({
storage: 'postgres://user:pass@localhost:5432/mydb'
});
await reseolio.start();
// Make any function durable with one wrapper
const sendEmail = reseolio.durable(
reseolio.namespace('notifications', 'email', 'send'),
async (to: string, subject: string) => {
await emailService.send(to, subject);
return { sent: true };
}
);
// Call it like a normal function - it's now crash-proof!
const handle = await sendEmail('user@example.com', 'Welcome!');
const result = await handle.result(); // { sent: true }That's it. Your function now survives crashes, has automatic retries, and can be tracked.
Building reliable distributed systems usually implies massive operational complexity. Reseolio eliminates the "glue code" and infrastructure bloat typically required for reliable background jobs.
Server crashed? Deployment rolled back? kill -9? Reseolio doesn't care. Your jobs pick up exactly where they left off the moment your app comes back online.
Turn complex background work into a simple async call:
// Looks like normal code, but it's crash-proof and distributed
const result = await generatePDFReport(data);No Redis. No RabbitMQ. No message broker cluster. Just your existing PostgreSQL and a single binary.
Decouple heavy operations from API responses. Offload PDF generation, webhooks, or data processing instantly.
const sendEmail = reseolio.durable(
reseolio.namespace('notifications', 'email', 'send'),
async (to: string, subject: string) => {
await emailService.send(to, subject);
}
);
// Returns immediately - email is guaranteed to be sent
await sendEmail('user@example.com', 'Welcome!');For critical workflows with multiple steps, chain durable functions for per-step durability:
// Each step is independently durable
const chargePayment = reseolio.durable('orders:payment:charge', ...);
const reserveInventory = reseolio.durable('orders:inventory:reserve', ...);
const createShipping = reseolio.durable('orders:shipping:create', ...);
// Orchestrator chains them
const processOrder = reseolio.durable(
reseolio.namespace('orders', 'fulfillment', 'process'),
async (orderId: string) => {
const payment = await chargePayment(orderId);
await payment.result(); // Wait for payment
const inventory = await reserveInventory(orderId);
await inventory.result(); // Wait for inventory
const shipping = await createShipping(orderId);
await shipping.result(); // Wait for shipping
return { status: 'fulfilled' };
}
);📚 See EXAMPLES.md for comprehensive patterns including:
- Fire-and-forget vs await patterns
- Chained durable functions (saga)
- Parallel fan-out execution
- Real-world scenarios (e-commerce, webhooks, onboarding)
- Best practices for idempotency
| Feature | Description |
|---|---|
| 🔄 Smart Retries | Exponential, linear, or fixed backoff with jitter |
| 🆔 Idempotency | Built-in deduplication prevents duplicate jobs |
| 🌍 Global State | PostgreSQL-backed, accessible from any node |
| 📊 Observability | Query job status, history, and results via API |
| ⚡ Low Overhead | ~6ms enqueue latency, ~5MB sidecar memory |
| 🔌 Polyglot | Language-agnostic gRPC protocol (Node.js SDK today, Python/Go coming) |
| Metric | Avg (ms) | P95 (ms) | Notes |
|---|---|---|---|
| Enqueue | 6.23 | 9.57 | Time to persist job intent |
| Scheduling Lag | 8.39 | 10.10 | Time until worker picks up |
| Full Round-Trip | 9.85 | 11.50 | Total: enqueue → execute → result |
~6ms overhead is the "cost of durability" — negligible for most API handlers.
| Aspect | Message Queues (BullMQ, RabbitMQ) | Temporal/Cadence | Reseolio |
|---|---|---|---|
| Philosophy | Fire and forget | Complex workflows | Simple durability |
| Infrastructure | Redis / RabbitMQ cluster | 4+ services + Cassandra | Just PostgreSQL |
| Learning Curve | Moderate | Steep | Minimal |
| Result Tracking | Manual | Built-in | Built-in |
| Await Job Result | ❌ | ✅ | ✅ |
| Multi-Node Support | ✅ | ✅ | ✅ (via Postgres) |
| Best For | High-throughput messaging | Enterprise orchestration | 90% of durability use cases |
Temporal is an amazing tool for complex orchestration. Choose it if you need:
- Deterministic Replay — Resume mid-workflow after crashes (Reseolio restarts from the beginning)
- Multi-Month Workflows — Workflows that pause for weeks waiting for human approval
- Signals & Queries — Send events to or query running workflows
- Built-in Compensation — Native saga rollback primitives
- Workflow Versioning — Deploy new code without breaking in-flight workflows
For most applications—processing payments, sending emails, syncing with external APIs, running background tasks—you don't need these features. Reseolio gives you 90% of the durability benefits with 10% of the complexity.
💡 Roadmap: We're exploring simplified patterns for long-running workflows and compensation in future releases. Our goal is to bring the power of durable execution to everyone, not just teams with dedicated platform engineers.
npm install reseolioPrerequisites:
- Node.js 18+
- PostgreSQL database
import { Reseolio } from 'reseolio';
// Initialize ONCE at app startup
const reseolio = new Reseolio({
storage: 'postgres://user:pass@localhost:5432/mydb',
});
await reseolio.start();
// Define durable functions
const myJob = reseolio.durable(
reseolio.namespace('domain', 'entity', 'action'),
async (arg1, arg2) => {
// Your logic here
return result;
},
{
maxAttempts: 5, // Retry up to 5 times
backoff: 'exponential', // exponential | linear | fixed
initialDelayMs: 1000, // Start with 1s delay
timeoutMs: 30000, // 30s timeout per attempt
}
);
// Call it
const handle = await myJob(arg1, arg2);
// Optionally await the result
const result = await handle.result();
// Or check status later
const details = await handle.details();
console.log(details.status); // 'pending' | 'running' | 'success' | 'dead'// Even if called 10 times, only runs once per orderId
await processPayment(amount, {
idempotencyKey: `charge-${orderId}`
});reseolio.on('job:start', (job) => console.log(`Started: ${job.name}`));
reseolio.on('job:success', (job) => console.log(`Success: ${job.name}`));
reseolio.on('job:error', (job, err) => console.log(`Error: ${err}`)); GLOBAL STATE (Postgres)
┌──────────────────────┐
│ Job Queue & State │
└──────────▲───────────┘
│
┌──────────┴───────────┐
│ │
┌─────────────────────────────────────▼──┐ ┌──────────▼─────────────────────────┐
│ Node 1 (Your App) │ │ Node 2 (Backup/Scale-out) │
│ │ │ │
│ ┌──────────────────────────────────┐ │ │ ┌──────────────────────────────┐ │
│ │ ⚡ Reseolio Sidecar (Rust) │ │ │ │ ⚡ Reseolio Sidecar (Rust) │ │
│ │ (Scheduler / Poller) │ │ │ │ (Scheduler / Poller) │ │
│ └──────────────┬───────────────────┘ │ │ └──────────────┬───────────────┘ │
│ │ gRPC │ │ │ gRPC │
│ ┌──────────────▼───────────────────┐ │ │ ┌──────────────▼───────────────┐ │
│ │ 📦 Node.js Runtime │ │ │ │ 📦 Node.js Runtime │ │
│ │ (Executes YOUR code) │ │ │ │ (Executes YOUR code) │ │
│ └──────────────────────────────────┘ │ │ └──────────────────────────────┘ │
└────────────────────────────────────────┘ └────────────────────────────────────┘
How it works:
- Sidecar (Rust): Polls Postgres efficiently. When a job is ready, it signals the SDK via gRPC.
- Runtime (Node.js): Receives the signal and executes your JavaScript function.
- Result: The SDK sends the result back to the Sidecar, which persists it to Postgres.
Prevent collisions with a strict naming convention: domain:entity:action
// ❌ Ambiguous
reseolio.durable('process-data', ...)
// ✅ Crystal clear
reseolio.namespace('billing', 'subscription', 'charge')// ✅ Pass IDs
processOrder({ orderId: '123' })
// ❌ Don't pass large objects
processOrder({ ...hugeUserObject, ...hugeOrderHistory })reseolio.durable('...', handler, {
timeoutMs: 5000 // Fail after 5s to prevent zombie jobs
});In rare edge cases (network failure after execution but before ack), a job might retry. Make your handlers safe to re-run:
// Use DB constraints
await db.query('INSERT ... ON CONFLICT DO NOTHING');
// Or check state first
if (await isAlreadyProcessed(orderId)) return;- Python SDK
- Go SDK
- Cron Scheduling: Native
0 * * * *expressions - Visual Dashboard: Web UI for job monitoring
We welcome contributions!
- Fork the repo
- Create a feature branch (
git checkout -b feat/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feat/amazing-feature) - Open a Pull Request
Reseolio is the core durability engine. We are building a suite of open-source tools and modules to solve specific infrastructure challenges:
Coming soon tools that solve annoying backend problems:
Coming soon Drop-in durable backends built on Reseolio:
- Reseolio Recce: High-performance financial reconciliation engine.
- Reseolio Meter: Usage-based billing and event counting.
Need help designing a crash-proof payment system? Cimulink provides specialized consulting:
- Architecture Audits: Identify race conditions and data risks in your current stack.
- Managed Implementation: We integrate Reseolio into your core business flows (Payments, Approvals, Logistics).
- Priority Support: SLA-backed support for your critical infrastructure.
MIT © Reseolio Contributors