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

state-change-notifier

v1.0.0

Published

A lightweight utility for awaiting state transitions in a type-safe, asynchronous manner—ideal for coordinating state-dependent workflows such as lifecycle management or idempotent initialization. Supports custom state change requests (inclusive or exclus

Readme

The StateChangeNotifier class provides a type-safe mechanism for awaiting state transitions.

It functions similarly to a condition variable, allowing asynchronous tasks to pause until a state change occurs. Consumers can await any transition or specify target states of interest.

This utility is ideal for coordinating asynchronous flows that rely on state progression - such as initialization routines, lifecycle management, or event-driven logic.

Table of Contents

:sparkles: Key Features

  • State Management :arrows_counterclockwise:: Provides methods to get and set the current state, as well as to await state changes using modern async/await syntax. Asynchronous tasks can pause until a state transition occurs, either to any new state or to specific target states of interest. The optional ICustomStateChangeRequest argument of the waitForChange method allows specifying the desired state(s) to wait for - either in an inclusive manner ("one of the following states") or exclusive ("any state except the following").
  • Comprehensive documentation :books:: Fully documented, enabling IDEs to provide intelligent tooltips for an enhanced development experience.
  • Thoroughly Tested :test_tube:: Backed by extensive unit tests, to ensure reliability in production.
  • Zero Runtime Dependencies :dove:: Only development dependencies are included.
  • ES2020 Compatibility: The project targets ES2020 for modern JavaScript support.
  • Full TypeScript Support: Designed for seamless TypeScript integration.

:globe_with_meridians: API

The StateChangeNotifier class provides the following methods:

  • state getter: Retrieves the current state.
  • state setter: Updates the current state. If the new state differs from the previous one, all pending state change awaiters are resolved - either unconditionally or if their custom request criteria are satisfied.
  • waitForChange: Awaits a state change. Optionally, a filtering request can be provided to wait only for relevant state transitions.
  • rejectAllStateChangeAwaiters: Rejects all current waitForChange awaiters with the provided error. This method can be used for error propagation or teardown procedures, allowing all awaiting consumers to be notified of failure or cancellation.

If needed, refer to the code documentation for a more comprehensive description of each method.

⏯️ Use Case Example: Lifecycle Management of a Toggleable Component

In systems where a component can be started and stopped multiple times, each transition often requires an asynchronous process - such as opening or closing network connections, initializing resources, or flushing buffers.

StateChangeNotifier can coordinate these state transitions safely. For instance, if a consumer attempts to start() the service while it is in the process of stopping, the notifier ensures the consumer waits until the stop process is complete before initiating the start sequence. This prevents race conditions, ensures consistent state, and reduces the need for manual state checks.

import {
  ICustomStateChangeRequest,
  StateChangeNotifier
} from 'state-change-notifier';

type LifecycleState =
  | 'INACTIVE'
  | 'STARTING'
  | 'ACTIVE'
  | 'STOPPING';

class ToggleableComponent {
  // Initialized with the initial state.
  private readonly _stateNotifier = new StateChangeNotifier<LifecycleState>('INACTIVE');

  public async start(): Promise<void> {
    if (this._stateNotifier.state === 'ACTIVE') {
      return; // Already active - no action needed.
    }

    if (this._stateNotifier.state === 'STARTING') {
      // Wait for the ongoing initialization to complete.
      return this._stateNotifier.waitForChange();
    }

    if (this._stateNotifier.state === 'STOPPING') {
      // Gracefully wait for teardown to complete *before* reinitializing.
      // This avoids conflicting operations like opening and closing
      // the same network connection simultaneously.
      await this._stateNotifier.waitForChange();
    }

    // We are now in the 'INACTIVE' state.
    this._stateNotifier.state = 'STARTING';
    try {
      await this._establishConnection();
    } catch (err) {
      this._stateNotifier.rejectAllStateChangeAwaiters(err);
      this._stateNotifier.state = 'INACTIVE';
      throw err;
    }

    this._stateNotifier.state = 'ACTIVE';
  }

  public async stop(): Promise<void> {
    if (this._stateNotifier.state === 'INACTIVE') {
      return; // Already inactive - no action needed.
    }

    if (this._stateNotifier.state === 'STOPPING') {
      // Wait for the ongoing teardown to complete.
      return this._stateNotifier.waitForChange();
    }

    if (this._stateNotifier.state === 'STARTING') {
      // Gracefully wait for initialization to finish before stopping.
      // This avoids conflicting operations like opening and closing
      // the same network connection simultaneously.
      await this._stateNotifier.waitForChange();
    }

    // We are now in the 'ACTIVE' state.
    this._stateNotifier.state = 'STOPPING';
    try {
      await this._closeConnection();
    } catch (err) {
      this._stateNotifier.rejectAllStateChangeAwaiters(err);
      // Note: in real-world scenarios, a failed teardown may warrant
      // a dedicated 'FAILED' or 'ERROR' state to represent uncertainty.
      // For simplicity, we revert to 'ACTIVE'.
      this._stateNotifier.state = 'ACTIVE';
      throw err;
    }

    this._stateNotifier.state = 'INACTIVE';
  }

  private _establishConnection(): Promise<void> {
    // Connects to an external resource (e.g., a database or socket).
  }

  private _closeConnection(): Promise<void> {
    // Gracefully closes the connection to an external resource.
  }
}

:scroll: License

Apache 2.0