Race Conditions
Shared mutable state leads to unpredictable bugs
|
Build fault-tolerant, scalable systems with TypeScript
Common challenges that slow down development and cause production issues
Shared mutable state leads to unpredictable bugs
One failure can crash your entire application
Distributed systems are hard to get right
noex solves these problems
Battle-tested patterns from Erlang/OTP, reimagined for TypeScript
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++;
}
} 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
});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' });Production-ready utilities that work seamlessly with your processes
In-memory caching with TTL and automatic cleanup
// 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 expirationPublish/subscribe messaging between processes
// Pub/sub messaging
const bus = new EventBus();
bus.subscribe('order:created', async (order) => {
await sendConfirmation(order);
});
bus.publish('order:created', newOrder);Token bucket rate limiting for APIs and resources
// Token bucket rate limiting
const limiter = new RateLimiter({
tokens: 100,
interval: 60_000
});
if (await limiter.acquire('api:user:123')) {
// Process request
}Monitor your processes in real-time with zero configuration
┌─ Process Tree ────────────────┐ ┌─ Stats Table ─────────────────────────────────────┐│ ▼ supervisor:main │ │ ID │ Status │ Msgs │ Mem │ Up ││ ├─ ● counter (running) │ │─────────────────┼─────────┼──────┼────────┼───────││ ├─ ● cache (running) │ │ counter │ running │ 1.2k │ 2.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 │ running │ 2.1k │ 3.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:23npx @hamicek/noex dashboardWeb-based interface for remote monitoring
Interactive terminal dashboard with keyboard navigation
Real-time CPU, memory, and message throughput
Timestamped events with severity levels
Scale across machines with location transparency. Your processes work the same whether on one machine or hundreds.
Nodes automatically discover each other using mDNS or seed nodes. No central coordinator required.
Register and find processes across all nodes by name. One API for local and remote processes.
Send messages to processes regardless of their physical location. The runtime handles routing.
// 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
Explore real-world examples and see noex in action
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); Get started in minutes with a simple npm install
$ npm install @hamicek/noex npm install @hamicek/noeximport { GenServer } from '@hamicek/noex'await Counter.start()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(); Love noex? Help us keep building amazing tools