npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

duroxide

v0.1.17

Published

Durable execution runtime for Node.js, powered by Rust

Readme

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()
  • Custom status — set orchestration progress visible to external clients via ctx.setCustomStatus()
  • Event queues — persistent FIFO message passing with ctx.dequeueEvent() and client.enqueueEvent()
  • 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
  • KV store — durable per-instance key-value state with snapshots and pruning via ctx.getKvAllValues() / client.getKvAllValues()

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.dequeueEvent(queueName) | Dequeue from persistent FIFO mailbox | | yield ctx.scheduleActivityWithRetryOnSession(name, input, retry, sessionId) | Retry with session affinity | | yield ctx.continueAsNew(newInput) | Restart with fresh history | | ctx.setValue(key, value) | Set a durable KV entry (no yield) | | ctx.getValue(key) | Read a KV entry for the current instance (no yield) | | ctx.clearValue(key) | Remove a single KV entry (no yield) | | ctx.clearAllValues() | Remove all KV entries (no yield) | | ctx.getKvAllValues() | Snapshot all KV entries for the current instance | | ctx.getKvAllKeys() | List all KV keys for the current instance | | ctx.getKvLength() | Count KV entries for the current instance | | ctx.pruneKvValuesUpdatedBefore(cutoffMs) | Remove persisted KV entries older than a cutoff | | yield ctx.getValueFromInstance(instanceId, key) | Read another instance's KV entry |

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 | | ctx.setCustomStatus(status) | Set progress visible to clients | | ctx.resetCustomStatus() | Clear custom status |

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);

// Event queues
await client.enqueueEvent(instanceId, 'queueName', JSON.stringify(data));

// Custom status polling
const change = await client.waitForStatusChange(instanceId, 0, 100, 30000);
// change: { customStatus, customStatusVersion } or null on timeout

// 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();

KV Store

// Read a KV entry from an orchestration instance
const value = await client.getValue(instanceId, 'myKey');

// Snapshot every KV entry for an orchestration instance
const allValues = await client.getKvAllValues(instanceId);

// Wait until a KV key is set (with timeout in ms)
const readyValue = await client.waitForValue(instanceId, 'myKey', 30000);

// Inside an orchestration, inspect or prune the local KV snapshot
const snapshot = ctx.getKvAllValues();
const keys = ctx.getKvAllKeys();
const size = ctx.getKvLength();
const removed = ctx.pruneKvValuesUpdatedBefore(cutoffMs);

MAX_KV_KEYS is now 100.

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