Skip to main content

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.