node-simple-context is a minimalist, type-safe context manager for Node.js, inspired by React Context.
Built on top of AsyncLocalStorage, it provides isolated contexts that work seamlessly with async/await and promises — with zero runtime dependencies.
This library is highly inspired by nctx. You definitely should check it out! Thanks to @devthejo for his help.
npm install node-simple-contextDefine a schema for compile-time type safety on keys and values:
import{createSimpleContext}from'node-simple-context';typeRequestSchema={userId: string;role: 'admin'|'user';requestId: string;};constcontext=createSimpleContext<RequestSchema>();context.set('userId','12345');// OKcontext.set('role','admin');// OKcontext.set('role','invalid');// Type error!context.set('unknown','value');// Type error!constuserId=context.get('userId');// string | undefinedconstrole=context.get('role');// 'admin' | 'user' | undefinedWithout a schema, all string keys are accepted and values are unknown:
constcontext=createSimpleContext();context.set('foo','bar');context.set('count',42);constvalue=context.get('foo');// unknown | undefinedCreates a new SimpleContext instance. Optionally accepts a type parameter defining the context's keys and value types.
import{createSimpleContext}from'node-simple-context';// Untypedconstcontext=createSimpleContext();// TypedtypeMySchema={userId: string;count: number};consttypedContext=createSimpleContext<MySchema>();Retrieves a value from the context by key.
Parameters:
key- The key to retrieve. Must be a non-empty string.
Returns: The value associated with the key, or undefined if not found.
Throws:TypeError if key is not a string or is empty.
Example:
constuserId=context.get('userId');// string | undefined (with typed schema)Sets a value in the context by key.
Parameters:
key- The key to set. Must be a non-empty string.value- The value to associate with the key. Must match the schema type.
Throws:TypeError if key is not a string or is empty.
Example:
context.set('userId','12345');Deletes a value from the context by key.
Parameters:
key- The key to delete. Must be a non-empty string.
Returns:true if the key existed and was deleted, false if it didn't exist.
Throws:TypeError if key is not a string or is empty.
Example:
context.delete('userId');Checks if a key exists in the context.
Parameters:
key- The key to check. Must be a non-empty string.
Returns:true if the key exists, false otherwise.
Throws:TypeError if key is not a string or is empty.
Example:
if(context.has('userId')){console.log('User ID is set');}Clears all values from the current context.
Example:
context.clear();Gets all key-value pairs from the current context as a plain object.
Returns: A shallow copy of all key-value pairs in the current context.
Example:
constallValues=context.getAll();console.log(allValues);// { userId: '12345', sessionId: 'abc' }Gets all keys from the current context.
Returns: An array of all keys in the current context.
Example:
constkeys=context.keys();console.log(keys);// ['userId', 'sessionId']Gets the number of key-value pairs in the current context.
Returns: The number of entries in the context.
Example:
constcount=context.size();console.log(count);// 2Runs a callback within a forked context that inherits current values. Changes inside the fork don't affect the parent context. This is the recommended way to use fork.
Parameters:
callback- The callback to execute within the forked context.
Returns: The callback's return value.
Example:
context.set('userId','parent');constresult=context.fork(()=>{context.set('userId','child');returncontext.get('userId');// 'child'});context.get('userId');// 'parent' — unchangedCreates a new forked context without a callback. Deprecated because it uses AsyncLocalStorage.enterWith() which permanently replaces the store for the entire current async execution context. Prefer fork(callback) instead.
typeSessionSchema={userId: string;sessionId: string;};constcontext=createSimpleContext<SessionSchema>();// Set valuescontext.set('userId','12345');context.set('sessionId','abc-def');// Check if a key existsif(context.has('userId')){console.log('User is logged in');}// Get the number of entriesconsole.log(context.size());// 2// Get all keysconsole.log(context.keys());// ['userId', 'sessionId']// Get all valuesconsole.log(context.getAll());// { userId: '12345', sessionId: 'abc-def' }// Delete a specific keycontext.delete('sessionId');// Clear all valuescontext.clear();console.log(context.size());// 0Thanks to AsyncLocalStorage, you can fork your context to create isolated scopes in async operations:
constcontext=createSimpleContext();context.set('requestId','root');// Each fork gets its own isolated copyconstresults=awaitPromise.all([context.fork(()=>{context.set('requestId','req-1');returnnewPromise((resolve)=>{setTimeout(()=>resolve(context.get('requestId')),100);});}),context.fork(()=>{context.set('requestId','req-2');returnnewPromise((resolve)=>{setTimeout(()=>resolve(context.get('requestId')),50);});}),]);console.log(results);// ['req-1', 'req-2']console.log(context.get('requestId'));// 'root' — parent unchangedForks can be nested to any depth, each level isolated from the others:
constcontext=createSimpleContext();context.set('level','root');context.fork(()=>{context.set('level','child');context.fork(()=>{context.set('level','grandchild');console.log(context.get('level'));// 'grandchild'});console.log(context.get('level'));// 'child'});console.log(context.get('level'));// 'root'constcontextA=createSimpleContext();constcontextB=createSimpleContext();contextA.set('foo','from A');contextB.set('foo','from B');console.log(contextA.get('foo'));// 'from A'console.log(contextB.get('foo'));// 'from B'This library supports both ESM and CommonJS:
// ESMimport{createSimpleContext}from'node-simple-context';// CommonJSconst{ createSimpleContext }=require('node-simple-context');MIT