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

@effectionx/converge

v0.1.1

Published

Recognize a desired state and synchronize on when that state has been achieved.

Readme

@effectionx/converge

Recognize a desired state and synchronize on when that state has been achieved.

This package is a port of @bigtest/convergence adapted for Effection structured concurrency.


Why Convergence?

Let's say you want to write an assertion to verify a simple cause and effect: when a certain button is clicked, a dialog appears containing some text that gets loaded from the network.

In order to do this, you have to make sure that your assertion runs after the effect you're testing has been realized.

Image of assertion after an effect

If not, then you could end up with a false negative, or "flaky test" because you ran the assertion too early. If you'd only waited a little bit longer, then your test would have passed. So sad!

Image of false negative test

In fact, test flakiness is the reason most people shy away from writing big tests in JavaScript in the first place. It seems almost impossible to write robust tests without having visibility into the internals of your runtime so that you can manually synchronize on things like rendering and data loading. Unfortunately, those can be a moving target, and worse, they couple you to your framework.

But what if instead of trying to run our assertions at just the right time, we ran them many times until they either pass or we decide to give up?

Image of convergent assertion

This is the essence of what @effectionx/converge provides: repeatedly testing for a condition and then allowing code to run when that condition has been met.

And it isn't just for assertions either. Because it is a general mechanism for synchronizing on any observed state, it can be used to properly time test setup and teardown as well.


Installation

npm install @effectionx/converge

Usage

when(assertion, options?)

Converges when the assertion passes within the timeout period. The assertion runs repeatedly (every 10ms by default) and is considered passing when it does not throw or return false. If it never passes within the timeout, the operation throws with the last error.

import { when } from "@effectionx/converge";

// Wait for a condition to become true
let { value } = yield* when(function* () {
  if (total !== 100) throw new Error("not ready");
  return total;
});

// With custom timeout
yield* when(
  function* () {
    if (!element.isVisible) throw new Error("not visible");
  },
  { timeout: 5000 },
);

always(assertion, options?)

Converges when the assertion passes throughout the timeout period. Like when(), the assertion runs repeatedly, but it must pass consistently for the entire duration. If it fails at any point, the operation throws immediately.

import { always } from "@effectionx/converge";

// Verify a condition remains true
yield* always(function* () {
  if (counter >= 100) throw new Error("counter exceeded limit");
});

// With custom timeout
yield* always(
  function* () {
    if (!connection.isAlive) throw new Error("connection lost");
  },
  { timeout: 5000 },
);

Options

Both when and always accept an options object:

| Option | Type | Default | Description | | ---------- | -------- | ------------------------------ | --------------------------------------------- | | timeout | number | 2000 (when) / 200 (always) | Maximum time to wait in milliseconds | | interval | number | 10 | Time between assertion retries in milliseconds |


Stats Object

Both functions return a ConvergeStats object with timing and execution info:

interface ConvergeStats<T> {
  start: number; // Timestamp when convergence started
  end: number; // Timestamp when convergence ended
  elapsed: number; // Milliseconds the convergence took
  runs: number; // Number of times the assertion was executed
  timeout: number; // The timeout that was configured
  interval: number; // The interval that was configured
  value: T; // The return value from the assertion
}

Example:

let stats = yield* when(
  function* () {
    return yield* fetchData();
  },
  { timeout: 5000 },
);

console.log(`Converged in ${stats.elapsed}ms after ${stats.runs} attempts`);
console.log(stats.value); // the fetched data

Examples

Waiting for an element to appear

yield* when(function* () {
  let element = document.querySelector('[data-test-id="dialog"]');
  if (!element) throw new Error("dialog not found");
  return element;
});

Verifying a value remains stable

yield* always(
  function* () {
    if (connection.status !== "connected") {
      throw new Error("connection dropped");
    }
  },
  { timeout: 1000 },
);

Using with file system operations

import { when } from "@effectionx/converge";
import { access } from "node:fs/promises";
import { until } from "effection";

// Wait for a file to exist
yield* when(
  function* () {
    let exists = yield* until(
      access(filePath).then(
        () => true,
        () => false,
      ),
    );
    if (!exists) throw new Error("file not found");
    return true;
  },
  { timeout: 10000 },
);