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

als-unhooked

v1.0.2

Published

cls-hooked replacement using modern context tracking

Readme

Asynchronous Local Storage ( UN-Hooked )

This is a fork of cls-hooked using AsyncLocalStorage instead of async_hooks.

Asynchronous context tracking has come a long way in the last decade. AsyncLocalStorage, the official stable implementation of async_hooks, provides many optimizations we can benefit from. And with the introduction of AsyncContextFrame in v22, async_hooks can be left behind completely. Therefore, this package provides a drop-in replacement for cls-hooked based on AsyncLocalStorage, so it can continue to benefit from ongoing development by the Node.js team.

Contents

Documentation

API Docs

Usage

One common use case is tracking a user for the duration of an http request. Suppose you use a middleware to authenticate your users:

// app.js
import express from 'express'
import als from 'als-unhooked/legacy';

const app = express();
const namespace = als.createNamespace('user_session');

// enter the namespace context
app.use((req, res, next) => {
  namespace.run(() => {
    next();
  });
});

// auth middleware
app.use(async (req, res, next) => {
  const user = await db.getUserFromReq(req);
  namespace.set('user', user);
  next();
});

Later on in the lifetime of the request, you need to record which user created some database record:

// thing.module.js

import als from 'als-unhooked/legacy';
import db from './lib/db.js';

const namespace = als.getNamespace('user_session');

async function createThing(_thing) {
  _thing.createdBy = namespace.get('user').id;
  await db.thing.create(_thing);
}

Use with Sequelize v6

A major motivator in creating this package was for use with sequelize v6, which uses cls-hooked for automatic transaction passing. This package is not officially supported by sequelize at this time, but the Modern API has been designed to be compatible with sequelize's implementation of cls-hooked.

// app.js

import { Sequelize } from 'sequelize';
import ALS from 'als-unhooked/modern';

Sequelize.useCLS(new ALS());

The Legacy API is, of course, also compatible with sequelize. But the modern API is recommended as it is more performant.

Overriding Sub-dependencies

If you don't use cls-hooked directly, but one of your (sub-)dependencies does, you can add an override to your package.json:

// package.json
{
  // ...
  "overrides": {
    "cls-hooked": "npm:als-unhooked"
  }
}

The "overrides" field works with npm and bun. If you use yarn or pnpm, the syntax is slightly different:

With yarn:

// package.json
{
  // ...
  "resolutions": {
    "cls-hooked": "npm:als-unhooked"
  }
}

With pnpm:

// pnpm-workspace.yaml
overrides:
  "cls-hooked": "npm:als-unhooked"

Modern vs Legacy

TL;DR If you're using sequelize v6 or just want a nice AsyncLocalStorage implementation, use the modern API. If you need a drop-in replacement for cls-hooked for use with something other than sequelize, you probably want the Legacy API.

The package has 2 different APIs: modern and legacy. The legacy API is a total drop-in replacement for cls-hooked. In accomplishing this, it creates overhead that, while minimal, can be avoided for many applications. It also inherits some mostly-outdated design patterns, such as process-level global variables. To eliminate these, a minimal wrapper of AsyncLocalStorage was created to be:

  1. Easy to use
  2. Compatible with sequelize

Performance

JS benchmarks should always be taken with a grain of salt. That said, the following results should hopefully show that both the the Modern API and Legacy API of als-unhooked are more performant than cls-hooked. Further, the Modern API is generally more performant than the Legacy API.

benchmark

Note: Only the average run times are shown. See benchmark/_exec.js for more details on how the benchmarks are run.

Note 2: The Modern API was retrofitted with some functionality from the legacy interface for the purpose of these tests.

Working with EventEmitters

Listeners triggered by event-driven classes like EventEmitter will sometimes be called in a different execution context, resulting in so-called "context loss." To mitigate this, cls-hooked provides the Namespace#bindEmitter method, which uses emitter-listener to monkeypatch the EventEmitter, automatically binding every listener to the current Namespace context. This method is convenient, and may even be necessary in some cases. In most cases, however, avoiding context loss is as simple is binding the listener before adding it to the emitter:

namespace.run(() => {
                            // ⬇️ bound. Retains current context
  eventEmitter.on('my_event', namespace.bind(() => {
    namespace.get('color'); // === 'red'
  }));
                            // ⬇️ NOT bound. Receives context in which emit() is called.
  eventEmitter.on('my_event', () => {
    namespace.get('color'); // === 'blue'
  });

  // set value for bound handler
  namespace.set('color', 'red');

  // enter new context
  namespace.run(() => {
    // set value for unbound handler
    namespace.set('color', 'blue');

    // emit
    eventEmitter.emit('my_event');
  });
});

If you determine that you do need to use Namespace#bindEmitter, be sure to install emitter-listener, as it is not installed alongside als-unhooked by default.

Acknowledgments

Thanks to @jeff-lewis for cls-hooked, and to the many others who have contributed to async context tracking in node over the years.

License

See LICENSE for the details of the BSD 2-clause "simplified" license used by als-unhooked.