ExecutionContext
The eridu-tech/execution-context module provides a type-safe, composable, and environment-agnostic way to store and propagate contextual data (such as request IDs, user info, or tracing metadata) across async boundaries and function calls. IExecutionContext uses symbols internally for reliable context isolation.
Initial configuration
To begin using the execution-context, you'll need to create and configure an instance:
import { ExecutionContext, contextToken } from "eridu-tech/execution-context";
import { AlsExecutionContextAdapter } from "eridu-tech/execution-context/als-execution-context-adapter";
// Create an execution-context instance with an adapter
const executionContext = new ExecutionContext(new AlsExecutionContextAdapter());
ExecutionContext basics
Running code with context
You can run code within a context boundary, and all context values will be accessible throughout the call chain:
// Define context tokens with type-safe identifiers
type User = { id: string; name: string };
const userToken = contextToken<User>("user");
const requestIdToken = contextToken<string>("requestId");
function logData(): void {
// Access context values later in the call chain
// { id: "123", name: "Alice" }
const user = executionContext.get(userToken);
// "req-456"
const reqId = executionContext.get(requestIdToken);
console.log("user:", user);
console.log("reqId:", reqId);
}
executionContext.run(() => {
executionContext.put(userToken, { id: "123", name: "Alice" });
executionContext.put(requestIdToken, "req-456");
logData();
});
Binding functions to context
You can bind a function to the current context, so it always executes with the captured context values:
executionContext.run(() => {
executionContext.put(userToken, { id: "123", name: "Alice" });
executionContext.put(requestIdToken, "req-456");
const logData = executionContext.bind((msg: string): void => {
// Access context values later in the call chain
const user = executionContext.get(userToken); // { id: "123", name: "Alice" }
const reqId = executionContext.get(requestIdToken); // "req-456"
console.log("message:", msg);
console.log("user:", user);
console.log("reqId:", reqId);
});
logData("hello");
});
Patterns
Type safety with context tokens
You can enforce compile-time type safety by defining context tokens with specific types:
const userToken = contextToken<{ id: string; name: string }>("user");
executionContext.put(userToken, { id: "123", name: "Alice" });
// TypeScript will error if you try to put a value of the wrong type.
Immutable and chainable context operations
All context mutation methods return the context instance, allowing for method chaining:
executionContext
.put(userToken, { id: "123", name: "Alice" })
.put(requestIdToken, "req-456");
Conditional context updates
You can conditionally update the context:
executionContext.when(true, (ctx) =>
ctx.put(userToken, { id: "conditional", name: "Bob" }),
);
Adapters
AlsExecutionContextAdapter: Uses Node.js AsyncLocalStorage for async context propagation.NoOpExecutionContextAdapter: No-op adapter for testing or environments without async context support.
Separating reading, updating, and execution concerns
The library includes several contracts that separate concerns for different use cases:
IReadableContext: Read-only access to context values (safe for consumers that should not mutate context).IContext: Adds all mutation methods (put, update, remove, etc.) for full context management.IExecutionContextBase: Adds execution boundary methods (run, bind) for context propagation and isolation.IExecutionContext: Combines all of the above for complete context management and execution.
IExecutionContextBase
run(invocable)— Runs a function within the current execution-context. All context values are accessible during execution.bind(fn)— Returns a new function that, when called, executes the original function within the captured context.
IContext
add(token, value)— Adds a value only if it doesn't already exist. No-op if the key exists.put(token, value)— Sets or overwrites a value for the token.putIncrement(token, settings?)— Initializes (if missing) and increments a numeric value. Optional max cap.putDecrement(token, settings?)— Initializes (if missing) and decrements a numeric value. Optional min floor.putPush(token, ...values)— Initializes (if missing) and pushes values to an array.update(token, value)— Updates a value only if it exists. No-op if missing.updateIncrement(token, settings?)— Increments a numeric value only if it exists. Optional max cap.updateDecrement(token, settings?)— Decrements a numeric value only if it exists. Optional min floor.updatePush(token, ...values)— Pushes values to an array only if it exists. No-op if missing.remove(token)— Removes a value from the context.when(condition, ...invocables)— Conditionally applies operations if the condition is true.
IReadableContext
contains(token, matchValue)— Checks if an array context value contains a specific item or matches a predicate.exists(token)— Checks if a value exists for the token.missing(token)— Checks if a value is missing for the token.get(token)— Retrieves a value or null if not found.getOr(token, defaultValue)— Retrieves a value or returns the provided default if not found.getOrFail(token)— Retrieves a value or throws if not found.
Further information
For further information refer to eridu-tech/execution-context API docs.