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

discrete-sim

v0.1.4

Published

Discrete-event simulation library for JavaScript/TypeScript

Readme

discrete-sim

npm version npm downloads License: MIT TypeScript GitHub issues

A modern TypeScript discrete-event simulation library inspired by Python's SimPy. Build and analyze complex systems with intuitive, generator-based process modeling.

New to discrete-event simulation? Check out the Beginner's Guide for tutorials and FAQs.

Features

  • Process-Based Modeling: Use generator functions to describe processes naturally
  • Resource Management: Built-in support for shared resources with FIFO queuing
  • Comprehensive Statistics: Time-weighted averages, counters, and timeseries tracking
  • Reproducible Results: Seedable random number generator for consistent experiments
  • TypeScript Native: Full type safety and excellent IDE support
  • Zero Dependencies: Lightweight and fast

Installation

npm install discrete-sim

Quick Start

import { Simulation, Resource, timeout } from 'discrete-sim';

// Define a simple customer process
function* customer(id: number, server: Resource) {
  console.log(`Customer ${id} arrives at ${sim.now}`);

  // Request the server
  yield server.request();
  console.log(`Customer ${id} starts service at ${sim.now}`);

  // Service time
  yield* timeout(5);

  // Release the server
  server.release();
  console.log(`Customer ${id} leaves at ${sim.now}`);
}

// Create simulation
const sim = new Simulation();
const server = new Resource(sim, 1, { name: 'Server' });

// Start 3 customer processes
for (let i = 0; i < 3; i++) {
  sim.process(() => customer(i, server));
}

// Run simulation
sim.run();

Core Concepts

Simulation Time

The simulation maintains a virtual clock that advances from event to event (not real-time).

const sim = new Simulation();
console.log(sim.now); // 0

sim.schedule(10, () => console.log(`Time: ${sim.now}`));
sim.run(); // Outputs: "Time: 10"

Event Cancellation:

You can cancel scheduled events before they execute:

const eventId = sim.schedule(100, () => console.log('This will be cancelled'));
sim.cancel(eventId);  // Returns true if cancelled, false if not found

// Useful for timeout patterns
const timeoutId = sim.schedule(30, () => console.log('Timeout!'));
// ... do some work ...
sim.cancel(timeoutId);  // Cancel if work completes early

Processes

Processes are described using generator functions. Use yield to wait for events.

function* myProcess() {
  yield* timeout(5);           // Wait 5 time units
  yield resource.request();    // Wait for resource
  yield* timeout(10);          // Use resource for 10 units
  resource.release();          // Release resource

  // Wait for condition with custom polling
  yield* waitFor(() => someValue > 10, {
    interval: 5,         // Check every 5 time units
    maxIterations: 100   // Timeout after 100 checks
  });
}

// Create and start a process
sim.process(myProcess);

// Or keep a reference for later control
const proc = sim.process(myProcess);
proc.interrupt();  // Can interrupt if needed

Resources

Resources represent shared, limited-capacity entities (servers, machines, staff).

const server = new Resource(sim, capacity: 2, { name: 'Server' });

function* worker() {
  yield server.request();  // Acquire resource
  yield* timeout(10);      // Do work
  server.release();        // Release resource
}

Resources automatically track:

  • Utilization rate
  • Average wait time
  • Average queue length

Priority Queuing:

Resources support priority-based queuing where lower priority values get served first:

const server = new Resource(sim, 1, { name: 'Server' });

function* customer(priority: number) {
  yield server.request(priority);  // 0 = highest priority
  yield* timeout(5);
  server.release();
}

// High priority customer (0) will be served before low priority (10)
sim.process(() => customer(10));  // Low priority
sim.process(() => customer(0));   // High priority - goes first

Preemptive Resources:

Preemptive resources allow higher-priority processes to interrupt lower-priority ones:

import { Resource, PreemptionError } from 'discrete-sim';

const server = new Resource(sim, 1, {
  name: 'Server',
  preemptive: true  // Enable preemption
});

function* lowPriorityJob() {
  try {
    yield server.request(10);  // Low priority
    yield* timeout(100);       // Long job
    server.release();
  } catch (err) {
    if (err instanceof PreemptionError) {
      console.log('Job was preempted by higher priority request');
      // Handle preemption - cleanup, retry, etc.
    }
  }
}

function* highPriorityJob() {
  yield server.request(0);  // High priority - will preempt low priority
  yield* timeout(5);
  server.release();
}

// Low priority starts first but gets interrupted
const p1 = new Process(sim, lowPriorityJob);
const p2 = new Process(sim, highPriorityJob);
p1.start();
sim.schedule(10, () => p2.start());  // High priority arrives later

sim.run();

When preemption occurs:

  • The preempted process throws a PreemptionError
  • The process can catch this error to handle cleanup
  • Statistics track the total number of preemptions

Statistics

Collect and analyze simulation data with comprehensive metrics:

const stats = new Statistics(sim);

// Time-weighted averages
stats.recordValue('temperature', 25.5);

// Counters
stats.increment('customers-served');

// Advanced statistics (v0.1.2+)
stats.enableSampleTracking('wait-time');
stats.recordSample('wait-time', 5.2);
stats.recordSample('wait-time', 3.1);

// Get statistics
const avgTemp = stats.getAverage('temperature');
const count = stats.getCount('customers-served');

// Percentiles for SLA tracking
const p50 = stats.getPercentile('wait-time', 50);  // Median
const p95 = stats.getPercentile('wait-time', 95);
const p99 = stats.getPercentile('wait-time', 99);

// Variance and standard deviation (optimized with Welford's algorithm)
const variance = stats.getVariance('wait-time');  // O(1) - instant!
const stdDev = stats.getStdDev('wait-time');      // O(1) - instant!

// Histograms
const histogram = stats.getHistogram('wait-time', 10);

// Warm-up period (v0.1.3+)
stats.setWarmupPeriod(1000); // Exclude first 1000 time units
// Statistics now only include steady-state behavior after warm-up

Performance Note: Mean, variance, and standard deviation calculations use Welford's online algorithm for O(1) computation, making them instantaneous even with millions of samples.

Random Number Generation

Reproducible randomness for validation and experimentation:

const rng = new Random(seed: 12345);

const u = rng.uniform(0, 10);          // Uniform [0, 10)
const e = rng.exponential(mean: 5);    // Exponential (lambda=1/5)
const n = rng.normal(mean: 100, stdDev: 15);  // Normal
const i = rng.randint(1, 6);           // Integer [1, 6]
const t = rng.triangular(5, 20, 10);   // Triangular (min, max, mode)
const p = rng.poisson(3);              // Poisson (lambda=3)

Error Handling & Validation

The library provides comprehensive input validation with helpful error messages to catch common mistakes early:

import { ValidationError } from 'discrete-sim';

// Example: Negative capacity
try {
  const resource = new Resource(sim, -1);
} catch (error) {
  console.error(error.message);
  // "capacity must be at least 1 (got -1). Resource must have at least 1 unit of capacity"
}

// Example: Invalid timeout
try {
  yield* timeout(-5);
} catch (error) {
  console.error(error.message);
  // "delay must be non-negative (got -5). Use timeout(0) for immediate continuation..."
}

// Example: Releasing unrequested resource
try {
  resource.release();
} catch (error) {
  console.error(error.message);
  // "Cannot release resource 'Server': no units currently in use. Did you forget to request it first?"
}

ValidationError includes context information for debugging:

try {
  sim.schedule(-10, () => {});
} catch (error) {
  if (error instanceof ValidationError) {
    console.log(error.context); // { delay: -10 }
  }
}

Common Validations:

  • Delays must be non-negative and finite (no NaN/Infinity)
  • Resource capacity must be a positive integer
  • Cannot release resources that aren't in use
  • Process state transitions must be valid (can't start a running process)
  • Generator functions must yield proper types (Timeout, ResourceRequest, Condition)
  • Random seeds must be finite integers within safe range (0 to 2^32-1)

Debugging & Event Tracing

Enable detailed event tracing for debugging and analysis:

import { Simulation } from 'discrete-sim';

const sim = new Simulation();

// Enable event tracing
sim.enableEventTrace();

sim.schedule(10, () => console.log('Event 1'), 5);
sim.schedule(20, () => console.log('Event 2'), 3);
sim.schedule(10, () => console.log('Event 3'), 0);

sim.run();

// Get execution trace
const trace = sim.getEventTrace();

trace.forEach(entry => {
  console.log(`Event ${entry.id}:`);
  console.log(`  Time: ${entry.time}`);
  console.log(`  Priority: ${entry.priority}`);
  console.log(`  Executed at: ${entry.executedAt}`);
});

// Clear trace for next run
sim.clearEventTrace();

// Disable tracing when done
sim.disableEventTrace();

Event tracing is useful for:

  • Understanding event execution order
  • Debugging priority scheduling issues
  • Performance analysis
  • Verifying simulation correctness

Examples

M/M/1 Queue (Validation)

Classic single-server queue with theoretical validation. Demonstrates exponential distributions and statistics collection.

npx tsx examples/mm1-queue/index.ts

Key Features:

  • Validates simulation against queuing theory
  • Shows 99%+ accuracy for queue metrics
  • Demonstrates reproducible results with seeded RNG

Full documentation

Warehouse Simulation

Multi-stage process with multiple resource types (docks, forklifts, inspectors).

npx tsx examples/warehouse/index.ts

Key Features:

  • Multiple resource types with different capacities
  • Bottleneck identification and analysis
  • Multi-stage workflow modeling

Full documentation

Restaurant Simulation

Customer service with variable group sizes and satisfaction metrics.

npx tsx examples/restaurant/index.ts

Key Features:

  • Variable-size customer groups (1-6 people)
  • Service phases (order, eat, pay)
  • Customer satisfaction assessment

Full documentation

Bank Tellers

SLA tracking and staffing optimization with different transaction types.

npx tsx examples/bank-tellers/index.ts

Key Features:

  • Service Level Agreement (SLA) tracking
  • Quick vs. complex transaction differentiation
  • Automated staffing recommendations

Full documentation

API Reference

Simulation

class Simulation {
  constructor(options?: SimulationOptions);

  // Core methods
  run(until?: number): SimulationResult;
  step(): boolean;
  reset(): void;

  // Time
  get now(): number;

  // Scheduling
  schedule(delay: number, callback: Function, priority?: number): string;
  cancel(eventId: string): boolean;

  // Process creation (convenience method)
  process(generatorFn: () => Generator): Process;

  // Events
  on(event: 'step' | 'complete' | 'error', handler: Function): void;
  off(event: string, handler: Function): void;
}

interface SimulationResult {
  endTime: number;           // Final simulation time
  eventsProcessed: number;   // Number of events processed
  statistics: {              // Simulation statistics
    currentTime: number;
    eventsProcessed: number;
    eventsInQueue: number;
  };
}

Process

class Process {
  constructor(simulation: Simulation, generatorFn: () => Generator);

  start(): void;
  interrupt(reason?: Error): void;

  get isRunning(): boolean;
  get isCompleted(): boolean;
  get isInterrupted(): boolean;
}

// Helper functions
function* timeout(delay: number): Generator<Timeout, void, void>;
function* waitFor(
  predicate: () => boolean,
  options?: WaitForOptions
): Generator<Condition, void, void>;

interface WaitForOptions {
  interval?: number;        // Polling interval (default: 1)
  maxIterations?: number;   // Max iterations before timeout (default: Infinity)
}

// Error types
class ConditionTimeoutError extends Error {
  iterations: number;
}

Resource

class Resource {
  constructor(simulation: Simulation, capacity: number, options?: ResourceOptions);

  request(): ResourceRequest;
  release(): void;

  get inUse(): number;
  get available(): number;
  get queueLength(): number;
  get utilization(): number;
  get stats(): ResourceStatistics;
}

Statistics

class Statistics {
  constructor(simulation: Simulation);

  // Time-weighted averages
  recordValue(name: string, value: number): void;
  getAverage(name: string): number;

  // Counters
  increment(name: string, amount?: number): void;
  getCount(name: string): number;

  // Timeseries
  enableTimeseries(name: string): void;
  getTimeseries(name: string): TimePoint[];

  // Advanced statistics (v0.1.2+)
  enableSampleTracking(name: string): void;
  recordSample(name: string, value: number): void;
  getPercentile(name: string, percentile: number): number;
  getVariance(name: string): number;
  getStdDev(name: string): number;
  getMin(name: string): number;
  getMax(name: string): number;
  getSampleMean(name: string): number;
  getSampleCount(name: string): number;
  getHistogram(name: string, bins?: number): HistogramBin[];

  // Export
  toJSON(): Record<string, unknown>;
  toCSV(): string;
}

Random

class Random {
  constructor(seed?: number);

  // Continuous distributions
  uniform(min: number, max: number): number;
  exponential(mean: number): number;
  normal(mean: number, stdDev: number): number;
  triangular(min: number, max: number, mode?: number): number;

  // Discrete distributions
  randint(min: number, max: number): number;
  poisson(lambda: number): number;

  // Array operations
  choice<T>(array: T[]): T;
  shuffle<T>(array: T[]): T[];

  // Seed management
  getSeed(): number;
  setSeed(seed: number): void;
}

ValidationError

class ValidationError extends Error {
  constructor(message: string, context?: Record<string, unknown>);

  name: 'ValidationError';
  context?: Record<string, unknown>;
}

Thrown when invalid parameters are provided to simulation methods. Includes helpful error messages with suggestions and context information for debugging.

Development

# Install dependencies
npm install

# Run tests
npm test

# Run tests with coverage
npm run test:coverage

# Run tests in watch mode
npm run test:watch

# Build
npm run build

# Lint
npm run lint

# Format
npm run format

Testing

The library has comprehensive test coverage:

  • 223 tests across unit and integration suites
  • 100% pass rate
  • Coverage: 80%+ overall, 95%+ for core modules
npm test

Architecture

Event Queue

Binary min-heap priority queue with O(log n) operations. Events ordered by:

  1. Time (ascending)
  2. Priority (ascending)
  3. ID (deterministic tie-breaking)

Process Execution

Generator-based with synchronous execution until first yield. Supports:

  • timeout(delay): Wait for time to pass
  • resource.request(): Acquire resource (returns token to yield)
  • waitFor(predicate, options): Wait for condition with configurable polling
    • interval: Polling interval in simulation time (default: 1)
    • maxIterations: Maximum polling attempts before timeout (default: Infinity)
    • Throws ConditionTimeoutError when max iterations exceeded

Resource Management

Token-based API with synchronous callbacks to maintain discrete-event semantics. Avoids Promise microtask queue for deterministic execution.

Statistics Collection

Time-weighted averaging for continuous metrics:

average = sum(value_i * duration_i) / total_time

Sample statistics (mean, variance, standard deviation) use Welford's online algorithm for O(1) incremental updates with excellent numerical stability.

Limitations & Performance

Scale Considerations

discrete-sim is designed for small to medium-scale simulations (up to ~100,000 events). Performance characteristics:

  • 10,000 events: ~100ms (excellent for prototyping and education)
  • 100,000 events: ~1-2s (good for most practical applications)
  • 1,000,000+ events: May become slow (8-15 minutes) due to JavaScript's performance characteristics

These benchmarks are for single simulation runs. For Monte Carlo analysis with multiple independent runs, consider using Node.js worker threads for parallelization.

Memory Considerations

  • Event queue: Each event uses ~100-150 bytes of memory
  • Statistics with sample tracking: Stores all samples in memory - can grow large for long simulations
  • Timeseries recording: Unbounded growth - use selectively for critical metrics
  • Practical limit: ~1-2 million concurrent events before memory pressure on typical systems

When to Consider Alternatives

Consider SimPy (Python) or other tools if you need:

  • Very large-scale simulations (millions of events with heavy statistics)
  • High-performance computing requirements
  • Integration with scientific Python (NumPy, SciPy, Pandas) for complex analysis
  • Parallel simulation across dozens of CPU cores
  • Academic research where Python is the established standard

When discrete-sim is the Right Choice

Use discrete-sim when you need:

  • Web applications or browser-based simulation dashboards
  • Integration with Node.js/TypeScript codebases
  • Type safety and excellent IDE support for development
  • Zero dependencies and lightweight deployment
  • Serverless environments (AWS Lambda, Cloudflare Workers)
  • Interactive teaching tools with immediate feedback
  • Rapid prototyping with modern JavaScript tooling

Performance Tips

  1. Disable sample tracking when not needed - use time-weighted averages instead
  2. Limit timeseries recording to critical metrics only
  3. Use warm-up periods to exclude initial transient behavior
  4. Batch independent simulations using worker threads for Monte Carlo analysis
  5. Profile before optimizing - use event tracing to identify bottlenecks
  6. Statistics are optimized - Mean, variance, and standard deviation use Welford's online algorithm (O(1) queries)

Design Decisions

Why Generators Instead of Async/Await?

Generators provide synchronous execution within the simulation timeline, while Promises execute in the microtask queue outside our control. This maintains discrete-event semantics and deterministic execution order.

Why Token-Based Resources?

The resource.request() returns a token to yield, not a Promise. This allows synchronous callback execution when resources become available, keeping everything in the simulation timeline.

Why LCG for Random Numbers?

Linear Congruential Generator is simple, fast, and sufficient for simulation. It's deterministic (critical for reproducibility) and has acceptable statistical properties for most applications.

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass (npm test)
  5. Submit a pull request

License

MIT

Credits

Inspired by SimPy, the excellent Python discrete-event simulation library.

Documentation

Full documentation is available at https://www.discrete-sim.dev

Support

Citation

If you use discrete-sim in academic work, please cite:

@software{discrete-sim,
  title = {discrete-sim: A TypeScript Discrete-Event Simulation Library},
  author = {Anes Mulalic},
  year = {2026},
  url = {https://github.com/anesask/discrete-sim}
}

Developer

Created and maintained by Anes Mulalic