noex

|

Build fault-tolerant, scalable systems with TypeScript

Node.js is great, but...

Common challenges that slow down development and cause production issues

Race Conditions

Shared mutable state leads to unpredictable bugs

Error Cascades

One failure can crash your entire application

Scaling Complexity

Distributed systems are hard to get right

noex solves these problems

Core Concepts

Battle-tested patterns from Erlang/OTP, reimagined for TypeScript

GenServer

Stateful processes without race conditions. Sequential message processing guarantees consistency.

class Counter extends GenServer {
  init() { return 0; }

  handleCall(msg: 'get') {
    return this.state;
  }

  handleCast(msg: 'inc') {
    this.state++;
  }
}

Supervisor

Automatic restart on failures. Let it crash philosophy with smart recovery strategies.

const supervisor = new Supervisor({
  children: [
    { id: 'counter', start: () => Counter },
    { id: 'cache', start: () => Cache }
  ],
  strategy: 'one_for_one',
  maxRestarts: 3
});

Registry

Named process lookup. Find processes by name instead of managing references.

// Register a process
Registry.register('user:123', userProcess);

// Find it anywhere
const user = Registry.lookup('user:123');
await user.call({ type: 'getProfile' });

Built-in Services

Production-ready utilities that work seamlessly with your processes

Cache

In-memory caching with TTL and automatic cleanup

Session storageAPI responsesComputed data
cache.ts
// Cache with TTL
const cache = new Cache({ ttl: 60_000 });

await cache.set('user:123', userData);
const user = await cache.get('user:123');

// Auto-cleanup on expiration

EventBus

Publish/subscribe messaging between processes

Decoupled communicationEvent sourcingReal-time updates
eventbus.ts
// Pub/sub messaging
const bus = new EventBus();

bus.subscribe('order:created', async (order) => {
  await sendConfirmation(order);
});

bus.publish('order:created', newOrder);

RateLimiter

Token bucket rate limiting for APIs and resources

API protectionResource throttlingDoS prevention
ratelimiter.ts
// Token bucket rate limiting
const limiter = new RateLimiter({
  tokens: 100,
  interval: 60_000
});

if (await limiter.acquire('api:user:123')) {
  // Process request
}

Observability Built-in

Monitor your processes in real-time with zero configuration

noex dashboard — Terminal
┌─ Process Tree ────────────────┐ ┌─ Stats Table ─────────────────────────────────────┐
supervisor:main │ │ ID │ Status │ Msgs │ Mem │ Up │
│ ├─ counter (running) │ │─────────────────┼─────────┼──────┼────────┼───────│
│ ├─ cache (running) │ │ counter │ running1.2k2.4 MB │ 01:23 │
│ ├─ rate-limiter (running) │ │ cache │ running │ 847 │ 12 MB │ 01:23 │
│ └─ event-bus (running) │ │ rate-limiter │ running │ 156 │ 1.1 MB │ 01:23 │
│ │ │ event-bus │ running2.1k3.2 MB │ 01:23 │
└───────────────────────────────┘ └───────────────────────────────────────────────────┘
┌─ Memory ──────────────────────┐ ┌─ Event Log ───────────────────────────────────────┐
│ │ [12:34:56] GenServer started: counter │
67% / 256MB │ │ [12:34:57] GenServer started: cache │
│ │ │ [12:34:58] Supervisor: main (4 children) │
└───────────────────────────────┘ └───────────────────────────────────────────────────┘
[q]uit [r]efresh [?]help [1-3]layout │ Processes: 4 │ Up: 00:01:23
npx @hamicek/noex dashboard

Web Dashboard

Web-based interface for remote monitoring

Terminal UI

Interactive terminal dashboard with keyboard navigation

Process Metrics

Real-time CPU, memory, and message throughput

Structured Logs

Timestamped events with severity levels

Real-time updates Zero configuration Cluster support

Distributed by Design

Scale across machines with location transparency. Your processes work the same whether on one machine or hundreds.

Local Node Remote Nodes
Auto-discovery No single point of failure Seamless failover

P2P Clustering

Nodes automatically discover each other using mDNS or seed nodes. No central coordinator required.

Global Registry

Register and find processes across all nodes by name. One API for local and remote processes.

Location Transparency

Send messages to processes regardless of their physical location. The runtime handles routing.

cluster.ts
// Register a process globally
await GlobalRegistry.register('user:session:123', pid);

// Find it from any node in the cluster
const pid = await GlobalRegistry.whereis('user:session:123');

// Send a message - works across nodes
await GenServer.call(pid, { type: 'get_data' });

Same API for local and remote processes

Try It Out

Explore real-world examples and see noex in action

counter.ts
import { GenServer, Supervisor, Registry } from class=class="hl-string">"hl-string">'@hamicek/noex';

class Counter extends GenServer {
  init() {
    return 0;
  }

  handleCall(msg: { type: class=class="hl-string">"hl-string">'get' }) {
    return this.state;
  }

  handleCast(msg: { type: class=class="hl-string">"hl-string">'increment' } | { type: class=class="hl-string">"hl-string">'decrement' }) {
    if (msg.type === class=class="hl-string">"hl-string">'increment') {
      this.state++;
    } else {
      this.state--;
    }
  }
}

class=class="hl-string">"hl-comment">// Start the counter
const counter = await Counter.start();

class=class="hl-string">"hl-comment">// Interact with it
await counter.cast({ type: class=class="hl-string">"hl-string">'increment' });
await counter.cast({ type: class=class="hl-string">"hl-string">'increment' });
const value = await counter.call({ type: class=class="hl-string">"hl-string">'get' });

console.log(class=class="hl-string">"hl-string">'Counter value:', value);
Terminal

Ready to build resilient systems?

Get started in minutes with a simple npm install

Install with npm
$ npm install @hamicek/noex

Quick Start

  1. 1
    Install the package npm install @hamicek/noex
  2. 2
    Import and use import { GenServer } from '@hamicek/noex'
  3. 3
    Run your process await Counter.start()
counter.ts
import { GenServer } from class=class="hl-string">"hl-string">'@hamicek/noex';

class Counter extends GenServer {
  init() { return 0; }

  handleCast(msg: class=class="hl-string">"hl-string">'inc') {
    this.state++;
  }
}

const counter = await Counter.start();

Support the Project

Love noex? Help us keep building amazing tools

Bitcoin
Bitcoin QR Code