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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@hy_ong/monitor

v0.0.2

Published

A complete TypeScript implementation of Ruby's Monitor class for synchronization in async/await code

Readme

@hy_ong/monitor

A complete TypeScript implementation of Ruby's Monitor class for synchronization in async/await code.

License: MIT

Features

  • 🔒 Mutual Exclusion - Ensures only one async context executes critical sections at a time
  • 🔄 Condition Variables - Wait/signal patterns for complex synchronization scenarios
  • 🎯 Ruby Compatible - Complete implementation of Ruby's Monitor API
  • 📦 Zero Dependencies - Pure TypeScript with no external dependencies
  • Fully Tested - 37 comprehensive tests with 100% coverage
  • 📝 Well Documented - Complete JSDoc comments for all public APIs

Installation

bun add @hy_ong/monitor

Or with npm:

npm install @hy_ong/monitor

Quick Start

Basic Synchronization

import {Monitor} from '@hy_ong/monitor';

const monitor = new Monitor();
let counter = 0;

// Multiple concurrent operations
await Promise.all([
  monitor.synchronize(async () => {
    counter++;
  }),
  monitor.synchronize(async () => {
    counter++;
  }),
  monitor.synchronize(async () => {
    counter++;
  })
]);

console.log(counter); // Always 3, never loses updates

Manual Lock Control

const monitor = new Monitor();

const owner = await monitor.enter();
try {
  // Critical section
  console.log('Exclusive access');
} finally {
  monitor.exit(owner);
}

Condition Variables

const monitor = new Monitor();
const cond = monitor.newCond();
let ready = false;

// Waiter
await monitor.synchronize(async () => {
  while (!ready) {
    await cond.wait();
  }
  console.log('Ready!');
});

// Signaler
await monitor.synchronize(async () => {
  ready = true;
  cond.signal();
});

Producer-Consumer Pattern

const monitor = new Monitor();
const notEmpty = monitor.newCond();
const notFull = monitor.newCond();
const queue: number[] = [];
const MAX_SIZE = 5;

// Producer
async function produce(item: number) {
  await monitor.synchronize(async () => {
    while (queue.length >= MAX_SIZE) {
      await notFull.wait();
    }
    queue.push(item);
    notEmpty.signal();
  });
}

// Consumer
async function consume() {
  return await monitor.synchronize(async () => {
    while (queue.length === 0) {
      await notEmpty.wait();
    }
    const item = queue.shift()!;
    notFull.signal();
    return item;
  });
}

API Reference

Monitor Class

new Monitor()

Creates a new Monitor instance for synchronization.

synchronize<T>(block: () => T | Promise<T>): Promise<T>

Executes a function with exclusive access to the monitor. Automatically handles lock acquisition and release.

Parameters:

  • block - The function to execute with exclusive access

Returns: The return value of the block function

Example:

await monitor.synchronize(async () => {
  // Critical section
});

async enter(): Promise<LockOwner>

Acquires the monitor lock. Must be paired with exit().

Returns: A LockOwner object to be passed to exit()

exit(owner: LockOwner): void

Releases the monitor lock.

Parameters:

  • owner - The LockOwner object from enter()

tryEnter(): LockOwner | null

Attempts to acquire the lock without blocking.

Returns: LockOwner if successful, null if lock is held

newCond(): ConditionVariable

Creates a new condition variable associated with this monitor.

Returns: A new ConditionVariable instance

monLocked(): boolean

Checks if the monitor is currently locked.

Returns: true if locked, false otherwise

monOwned(owner?: LockOwner): boolean

Checks if the current context owns the lock.

Parameters:

  • owner - The LockOwner object to check

Returns: true if the current context owns the lock

Ruby Method Aliases

For compatibility with Ruby's Monitor API:

  • monEnter() - Alias for enter()
  • monExit(owner) - Alias for exit()
  • monTryEnter() - Alias for tryEnter()
  • tryMonEnter() - Alias for tryEnter()
  • monSynchronize(block) - Alias for synchronize()

ConditionVariable Class

async wait(timeout?: number): Promise<boolean>

Releases the lock and waits for a signal. Reacquires the lock before returning.

Parameters:

  • timeout - Optional timeout in milliseconds

Returns: true if signaled, false if timed out

async waitWhile(condition: () => boolean, timeout?: number): Promise<boolean>

Waits while the condition is true.

Parameters:

  • condition - Function that returns true while waiting should continue
  • timeout - Optional timeout in milliseconds

Returns: true if condition became false, false if timed out

async waitUntil(condition: () => boolean, timeout?: number): Promise<boolean>

Waits until the condition becomes true.

Parameters:

  • condition - Function that returns true when waiting should stop
  • timeout - Optional timeout in milliseconds

Returns: true if condition became true, false if timed out

signal(): void

Wakes up the first waiting context.

broadcast(): void

Wakes up all waiting contexts.

MonitorMixin Class

static extendObject<T>(obj: T): T & MonitorMixin

Extends an existing object with monitor functionality.

Parameters:

  • obj - The object to extend

Returns: The same object with monitor methods added

Example:

const myObject = {value: 0};
const monitored = MonitorMixin.extendObject(myObject);

await monitored.synchronize(async () => {
  monitored.value++;
});

Advanced Usage

Extending Classes with MonitorMixin

import {MonitorMixin} from '@hy_ong/monitor';

class Counter extends MonitorMixin {
  private value = 0;

  async increment(): Promise<number> {
    return await this.synchronize(async () => {
      this.value++;
      return this.value;
    });
  }

  async getValue(): Promise<number> {
    return await this.synchronize(() => this.value);
  }
}

const counter = new Counter();
await Promise.all([
  counter.increment(),
  counter.increment(),
  counter.increment()
]);

console.log(await counter.getValue()); // 3

Timeout Patterns

const monitor = new Monitor();
const cond = monitor.newCond();

// Wait with timeout
await monitor.synchronize(async () => {
  const signaled = await cond.wait(5000); // 5 second timeout
  if (!signaled) {
    console.log('Timeout!');
  }
});

// Wait until with timeout
await monitor.synchronize(async () => {
  const success = await cond.waitUntil(() => ready, 5000);
  if (!success) {
    console.log('Condition not met within timeout');
  }
});

Differences from Ruby Monitor

Due to fundamental differences between Ruby's threading model and JavaScript's async/await:

  1. Owner Parameter: TypeScript version requires explicitly passing the LockOwner object to exit(), as JavaScript lacks thread-local storage.

  2. Async/Await: All blocking operations use async/await instead of blocking threads.

  3. Naming: Uses camelCase (monEnter) instead of snake_case (mon_enter) following TypeScript conventions.

However, all Ruby Monitor functionality is preserved, and the API semantics remain identical.

Testing

bun test

All 37 tests pass, covering:

  • Basic synchronization
  • Manual lock control
  • Non-blocking lock attempts
  • Condition variables (wait, signal, broadcast)
  • Timeout handling
  • Producer-consumer patterns
  • Class extension with MonitorMixin
  • Stress tests with concurrent operations

Building

bun run build

License

MIT License - see LICENSE file for details

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Author

Ong Hoe Yuan

Acknowledgments

This is a complete TypeScript implementation of Ruby's Monitor class, maintaining full API compatibility while adapting to JavaScript's async/await model.