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

@oamm/runtime-context

v1.0.7

Published

Shared, injectable request-scoped runtime context for Node.js and Edge runtimes

Readme

@oamm/runtime-context

A production-ready TypeScript library that provides a shared, injectable request-scoped runtime context. Primarily backed by Node.js AsyncLocalStorage, but designed to be framework-agnostic and safe for environments where ALS is unavailable (like Edge runtimes).

Why?

In complex applications, multiple libraries or modules might need access to request-scoped data (like trace IDs, user information, etc.). If each library creates its own AsyncLocalStorage instance, they won't share data, leading to a "split-brain" problem.

This package provides a single, shared storage instance that can be injected into or shared across multiple libraries.

Installation

npm install @oamm/runtime-context

Usage

Basic Usage (Node.js)

import { runWithContext, getContext, setContext } from '@oamm/runtime-context';

const myContext = { requestId: '123' };

runWithContext(myContext, () => {
  const ctx = getContext(); // { requestId: '123' }
  setContext('userId', 'abc');
  // ctx is now { requestId: '123', userId: 'abc' }
});

Context Management Scenarios

The library supports three distinct scenarios for managing context, giving you flexibility depending on your needs.

1. Map-based Multi-Context (Automatic)

Use this when you want a clean, Map-based storage for various pieces of data. This allows you to use setContext and getContext immediately with keys.

import { runWithContext, setContext, getContext } from '@oamm/runtime-context';

runWithContext(() => {
  setContext('requestId', '123');
  // ...
  const id = getContext('requestId'); // '123'
});

2. Keyed Context (Multi-tenant/Shared)

Use this when you have multiple independent contexts (e.g., a Database context and a User context) and you want to keep them separated without mixing.

import { runWithContext, getContext } from '@oamm/runtime-context';

const dbCtx = { connection: '...' };
const userCtx = { id: 'abc' };

runWithContext('db', dbCtx, () => {
  runWithContext('user', userCtx, () => {
    const db = getContext('db');     // { connection: '...' }
    const user = getContext('user'); // { id: 'abc' }
    
    // getContext() returns the whole Map containing both
  });
});

3. Raw Context (Simple Object)

Use this for simple scenarios where you just need a single object as context and want to avoid the overhead of a Map.

import { runWithContext, getContext, setContext } from '@oamm/runtime-context';

const myContext = { requestId: '123' };

runWithContext(myContext, () => {
  const ctx = getContext(); // { requestId: '123' }
  setContext('userId', 'abc'); 
  // myContext is now { requestId: '123', userId: 'abc' }
});

Store Architecture

The library automatically manages the underlying storage structure based on how you initialize your context. Understanding the distinction between the Store and Context Objects is key:

  • Store: The top-level container held by AsyncLocalStorage. It is either a Map (Scenarios 1 & 2) or a Raw Object (Scenario 3).
  • Context Object: An object stored inside the Map (Scenario 2) or the Store itself if it's a Raw Object.

Visual Representation

1. Map-based Store (Scenarios 1 & 2)

ALS Store (Map)
│
├── 'db'   ──> { connection: '...' }  (Keyed Context)
├── 'user' ──> { id: 'abc' }          (Keyed Context)
└── [Default] ──> { requestId: '123' }   (Default Fallback Context)

2. Raw Object Store (Scenario 3)

ALS Store (Object)
│
└── { requestId: '123' }

Shared Storage (Injection)

If you are building a library that depends on this one, you can allow users to inject their own storage to ensure consistency:

import { initRuntimeContext } from '@oamm/runtime-context';

// In your app entry point
initRuntimeContext({ storage: mySharedStorage });

Debug Mode

You can enable internal debug logging to gain visibility into context operations (context entry, retrieval, and modifications). This is useful for troubleshooting context availability or state changes.

import { initRuntimeContext } from '@oamm/runtime-context';

initRuntimeContext({
  debug: true
});

When enabled, the library logs detailed information to console.debug prefixed with [runtime-context].

ensureContext

Ensures a context exists without double-wrapping, reusing the existing one if available. It supports the same three scenarios:

1. Map-based context (Default)

Ensures that a Map-based storage is available.

import { ensureContext, setItem } from '@oamm/runtime-context';

await ensureContext(async () => {
  setItem('traceId', 'abc');
});

2. Keyed context

Ensures a specific key exists within a Map-based context.

await ensureContext('my-key', () => ({ data: 1 }), () => {
  // If 'my-key' already exists, it is reused.
});

3. Raw (object-based) context

Ensures a specific context object exists. No Map is created if it's missing.

await ensureContext(() => ({ requestId: '123' }), async () => {
  // If a context already existed, it is reused.
  // Otherwise, a new object context is created for this scope.
});

Sharing across libraries

If you are building a library that needs access to the request context, simply import getContext from this package.

// my-library.ts
import { getContext } from '@oamm/runtime-context';

export function myLibraryFunction() {
  const ctx = getContext();
  // ... do something with ctx ...
}

As long as the main application uses @oamm/runtime-context, your library will automatically have access to the same context.

This works even if your library is bundled separately, thanks to our use of a global storage key.

API

Core

  • initRuntimeContext(config): Initializes the global storage and configuration (e.g., debug mode, storage injection).
  • getContext<T>(key?): Returns the current context or undefined. Supports automatic type inference when using classes as keys.
  • requireContext<T>(key?): Returns the current context or throws.
  • runWithContext(fn): Runs fn with an automatically initialized Map-based context (Scenario 1).
  • runWithContext(key, ctx, fn): Runs fn with a specific key in a Map-based multi-context (Scenario 2).
  • runWithContext(ctx, fn): Runs fn within the given context, explicitly avoiding Map creation (Scenario 3).
  • ensureContext(fn): Ensures a Map-based context exists (Scenario 1).
  • ensureContext(key, create, fn): Ensures a specific keyed context exists in a Map (Scenario 2).
  • ensureContext(create, fn): Ensures a context exists, avoiding Map creation if it needs to be created (Scenario 3).

Multi-Context (Map-based)

When you need to manage multiple independent contexts (e.g., a TokenKit and a Session), you can use keys (strings, symbols, or classes). The library automatically handles Map creation and clones the parent Map when nesting contexts to ensure changes in a nested scope do not leak back to the parent.

class TokenKit {
  constructor(public token: string) {}
}

const tk = new TokenKit('abc');

runWithContext(TokenKit, tk, () => {
  // Type is automatically inferred as TokenKit | undefined
  const context = getContext(TokenKit); 
});

Bound Accessors (Method References)

If you need a reference to a context accessor (e.g., for dependency injection), use an arrow function:

// For classes:
const getSessionContext = () => getContext(SessionContext);

// For interfaces or default context (uses the whole store):
interface MyContext { user: string }
const getMyContext = () => getContext<MyContext>();

// For specific keys with interfaces:
const getSpecific = () => getContext<MyContext>('my-key');

// Later, call them without arguments
const session = getSessionContext(); // inferred as SessionContext | undefined

Helpers

The library provides several helpers to interact with the context. There is an important distinction between interacting with the Store (Map or Object) and interacting with Properties within a context object.

Store Helpers (Top-level)

  • setContext(key, value): Directly interacts with the top-level ALS store.
    • Map-based: Performs map.set(key, value).
    • Raw Object: Performs object[key] = value.
  • getContext(key?): Retrieves a value from the ALS store.
  • requireContext(key?): Retrieves a value from the ALS store or throws an error.

Property Helpers (Object-level)

These helpers are designed to work with properties inside context objects, and they automatically handle the "Default Context Fallback".

  • setValue(key, value, contextKey?): Sets a property on a context object.
    • If contextKey is provided, it targets the object stored under that key in the Map.
    • If not provided, it targets the Default Context object. If no default object exists, it falls back to setContext(key, value).
  • getValue(key, contextKey?): Gets a property from a context object.
    • If contextKey is provided, it targets the object at that key.
    • If not provided, it first checks the Map for key. If not found, it falls back to checking the Default Context object for that property.
  • mergeContext(partial, contextKey?): Merges an object into a context object (targets the Default Context if no contextKey is provided).

Default Context Fallback

When transitioning from a Raw Object context (Scenario 3) to a Map-based context (Scenarios 1 & 2), the library automatically preserves the parent object as a "default" context.

const globalContext = { traceId: '123' };

runWithContext(globalContext, () => {
  // We are in Scenario 3 (Raw Object)
  
  runWithContext('user', { id: 'abc' }, () => {
    // We are now in Scenario 2 (Map-based)
    // The Map contains: { 'user' => { id: 'abc' }, [Default] => globalContext }
    
    const traceId = getValue('traceId'); // '123' (fallback to default context)
    const userId = getValue('id', 'user'); // 'abc' (from 'user' keyed context)
    
    setValue('newVal', 'foo'); // Sets globalContext.newVal = 'foo'
  });
});

Helpers (Map-based context)

  • setItem(key, value)
  • getItem(key)

Environment Support

  • Node.js: Automatically uses AsyncLocalStorage.
  • Edge/Other: You must inject an AsyncContextStorage implementation or use initRuntimeContext({ storage }) if you want to share storage across libraries. By default, it will throw an error if you try to run without a valid storage.

Testing

Use resetForTests() in beforeEach to ensure test isolation.

import { resetForTests } from '@oamm/runtime-context';

beforeEach(() => {
  resetForTests();
});