Skip to content

affandar/duroxide-node

Repository files navigation

duroxide-node

Node.js/TypeScript SDK for the Duroxide durable execution runtime. Write reliable, long-running workflows in JavaScript using generator functions — backed by a Rust runtime that handles persistence, replay, and fault tolerance.

See CHANGELOG.md for release notes.

Features

  • Durable orchestrations — generator-based workflows that survive process restarts
  • Automatic replay — the Rust runtime replays history on restart, your code picks up where it left off
  • Activities — async functions for side effects (API calls, DB writes, etc.)
  • Timers — durable delays that persist across restarts
  • Sub-orchestrations — compose workflows from smaller workflows
  • External events — pause workflows and wait for signals
  • Fan-out/fan-in — run tasks in parallel with ctx.all() (supports all task types)
  • Race conditions — wait for the first of multiple tasks with ctx.race() (supports all task types)
  • Cooperative cancellation — activities detect when they're no longer needed via ctx.isCancelled()
  • Activity client access — activities can start new orchestrations via ctx.getClient()
  • Continue-as-new — restart orchestrations with fresh history for eternal workflows
  • Structured tracing — orchestration and activity logs route through Rust's tracing crate
  • Runtime metricsmetricsSnapshot() for orchestration/activity counters
  • SQLite & PostgreSQL — pluggable storage backends

Quick Start

npm install duroxide
const { SqliteProvider, Client, Runtime } = require('duroxide');

async function main() {
  // 1. Open a storage backend
  const provider = await SqliteProvider.open('sqlite:myapp.db');
  const client = new Client(provider);
  const runtime = new Runtime(provider);

  // 2. Register activities (async functions with side effects)
  runtime.registerActivity('Greet', async (ctx, name) => {
    ctx.traceInfo(`greeting ${name}`);
    return `Hello, ${name}!`;
  });

  // 3. Register orchestrations (generator functions)
  runtime.registerOrchestration('GreetWorkflow', function* (ctx, input) {
    const greeting = yield ctx.scheduleActivity('Greet', input.name);
    ctx.traceInfo(`got: ${greeting}`);
    return greeting;
  });

  // 4. Start the runtime
  await runtime.start();

  // 5. Start an orchestration and wait for it
  await client.startOrchestration('greet-1', 'GreetWorkflow', { name: 'World' });
  const result = await client.waitForOrchestration('greet-1');
  console.log(result.output); // "Hello, World!"

  await runtime.shutdown();
}

main();

Why Generators (not async/await)?

Duroxide uses function* generators instead of async function for orchestrations. This is a deliberate design choice — see Architecture for the full explanation. The short version: generators give Rust full control over when and how each step executes, which is essential for deterministic replay.

// ✅ Orchestrations use yield
runtime.registerOrchestration('MyWorkflow', function* (ctx, input) {
  const result = yield ctx.scheduleActivity('DoWork', input);
  return result;
});

// ✅ Activities use async/await (normal async functions)
runtime.registerActivity('DoWork', async (ctx, input) => {
  const data = await fetch(`https://api.example.com/${input}`);
  return data;
});

Orchestration Context API

All scheduling methods return descriptors that must be yielded:

Method Description
yield ctx.scheduleActivity(name, input) Run an activity
yield ctx.scheduleActivityWithRetry(name, input, retryPolicy) Run with retry
yield ctx.scheduleTimer(delayMs) Durable delay
yield ctx.waitForEvent(eventName) Wait for external signal
yield ctx.scheduleSubOrchestration(name, input) Run child workflow (await result)
yield ctx.scheduleSubOrchestrationWithId(name, id, input) Child with explicit ID
yield ctx.startOrchestration(name, id, input) Fire-and-forget orchestration
yield ctx.all([task1, task2, ...]) Parallel execution (like Promise.all)
yield ctx.race(task1, task2) First-to-complete (like Promise.race)
yield ctx.utcNow() Deterministic timestamp
yield ctx.newGuid() Deterministic GUID
yield ctx.continueAsNew(newInput) Restart with fresh history

Tracing methods are fire-and-forget (no yield needed):

Method Description
ctx.traceInfo(message) INFO log (suppressed during replay)
ctx.traceWarn(message) WARN log
ctx.traceError(message) ERROR log
ctx.traceDebug(message) DEBUG log

Storage Backends

SQLite

const provider = await SqliteProvider.open('sqlite:path/to/db.db');
// or in-memory:
const provider = await SqliteProvider.inMemory();

PostgreSQL

const provider = await PostgresProvider.connectWithSchema(
  'postgresql://user:pass@host:5432/db',
  'my_schema'
);

Logging

Duroxide uses Rust's tracing crate. Control verbosity with RUST_LOG:

RUST_LOG=info node app.js              # INFO and above
RUST_LOG=duroxide=debug node app.js    # DEBUG for duroxide only
RUST_LOG=duroxide::activity=info node app.js  # Activity traces only

Management API

The Client class includes a management API for inspecting and managing orchestration instances:

const client = new Client(provider);

// Instance management
const instances = await client.listAllInstances();
const info = await client.getInstanceInfo(instanceId);
const tree = await client.getInstanceTree(instanceId);
await client.deleteInstance(instanceId, false);

// Execution history with full event data
const executions = await client.listExecutions(instanceId);
const events = await client.readExecutionHistory(instanceId, executions[0]);
for (const event of events) {
  console.log(event.kind, event.data);
  // event.kind: "OrchestrationStarted" | "ActivityScheduled" | "ActivityCompleted" | ...
  // event.data: JSON string with event-specific content (result, input, error, etc.)
}

// Metrics
const metrics = await client.getSystemMetrics();
const depths = await client.getQueueDepths();

Documentation

  • Architecture — how the Rust/JS interop works, yield vs await, limitations
  • User Guide — patterns, recipes, and best practices

Tests

Requires PostgreSQL (see .env.example):

npm test                 # e2e tests (25 PG + 1 SQLite smoketest)
npm run test:races       # Race/join composition tests (7 tests)
npm run test:admin       # Admin API tests (14 tests)
npm run test:scenarios   # Scenario tests (6 tests)
npm run test:all         # Everything (52 tests)

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published