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 🙏

© 2024 – Pkg Stats / Ryan Hefner

async-context-containers

v1.1.0

Published

A library for sharing context deep down in asynchronous call stacks based on Node's async-hooks. Works with callbacks and promises

Downloads

11

Readme

Async context containers

What is the async-context-containers library?

Async context containers is a strongly-typed Typescript library that helps deeply nested async functions with getting context set higher up the call stack. It relies on cls-hooked, which is using the experimental node feature async-hooks.

Disclaimer!

As the async-hooks feature in Node.js is not yet stable, you should expect that your data could be undefined when you access it. Not that this happens that often (haven't tried it yet myself), but it is good practice to check for it anyways.

Concept

Context container: A piece of context that can be shared at some point in your app, eg. request context, config context, etc. You can access your context containers state with the .get() method, and set state with te .set() method. These methods are exposed by a ContainerContext instance.

Context map (Typescript only): A context map is a Typescript interface that lays out a map of all your context containers. Here is an example:

interface ContextMap {
    config: ConfigContextContainer; // The 'config' key is the context name
    request: RequestContextContainer; // The 'request' key is the context name
}

// The properties describes the context state you can access with the .get() and .set() functions
interface ConfigContextContainer {
    debug: boolean; 
}

// Perhaps you want to share an Express request object longer down the call stack...
interface RequestContextContainer {
    req: Request; 
    res: Resposne;
}

Context factory: The object instance which is being used to load and create ContextContainer instances.

Point of introduction: When dealing with async shared context, we are also dealing with the concept of time. Therefore, the context we would to provide our app with, can only be available after we introduce it at some point in the async call stack, and then only be available for the duration of that async call stack. This can be a little hard to grasp at first, but when you start writing some code with it, it should start to sink in.

Context provider: When we want to introduce the context in the async call stack, we use the generateContextProvider() method with some parameters that then can inserted into this function call...

withContext(...contextProviders: ContextProvider[]).in(async() => { 
    // Move on in the async call stack 
});

They are pretty much just a type of middleware. You will learn more about this in the following Getting Started guide.

Getting started

Installation:

# With NPM
npm install async-context-containers --save

# Or with Yarn
yarn add async-context-containers

The following usage examples uses Typescript as this library provides type-hinting...

Create a file for your context logic. Mine is called context.ts

context.ts:

// Import the factory creator function
import { createContextFactory } from 'async-context-containers'; 

// Define what your context looks like. Remember, keys are context names and the type describes
// what state can be accessed
interface ContextMap {
    config: ConfigContextContainer;
}

// Example: config context
interface ConfigContextContainer {
    debug: boolean;
    greeting: string;
}

// Export the Context factory object as you are going to use 
// this to access context  around in your app.
// Make sure to pass in your ContextMap as this will provide type hinting
export const Context = createContextFactory<ContextMap>();

// Create context provider for the config context..
export const configCtx = (debug: boolean) => {
    return Context.generateContextProvider({
        contextName: 'config', // Can only be 'config' here as it is the only context name stated in the context map
        use: async (configContextContainer, next) => {
            // We can only set 'debug' and 'gretting' state, as they are the only properties described on 
            // the ConfigContextContainer interface.
            configContextContainer?.set('debug', debug); 
            configContextContainer?.set('greeting', 'Hello, World!');
            
            // Always run next() function at last, so we can go to next context provider
            return next();
        }       
    });
}

Now we got our context boilerplate code sorted out, we will need to find where we want to introduce our context into our application. For demonstration purposes we will introduce the context at the starting point of our app, which of course also is a valid strategy. I do this in my index.ts

index.ts:

import { withContext } from 'async-context-containers';
import { configCtx, Context } from './context.ts';

async function main() {
    
    // Introduce our context into the async call stack
    await withContext(configCtx()).in(async () => {
        // Continue down the async call stack...
        // For demonstration purposes we just call another async method
        await sideEffect();

        // Remember only calls made inside this 'in' closure can use the context provided. In our
        // case, the config context.
    });
}

// Run application
main()
  .then(() => {})
  .catch((err) => console.log(err));


async function sideEffect() {
    const configContextContainer = Context.load('config'); // Load context container by name
    const debug = configContextContainer?.get('debug'); // Boolean, but can be in rare circumstances be undefined 

    if (debug) {
        const greeting = configContextContainer?.get('greeting');
        console.log(greeting); // logs: "Hello, World!"
    }
}

That is pretty much it. Feel free to play around with it all you like! If you find a bug, be sure to submit an issue on GitHub.