Skip to content
Warlock.js v4

API reference

Two exports — the abstract Context<TStore> class and the ContextManager class (plus its contextManager singleton). That’s the whole surface.

import { Context, ContextManager, contextManager } from "@warlock.js/context";

Abstract base class. Extend it to declare a typed AsyncLocalStorage-backed context.

abstract class Context<TStore extends Record<string, any>>

Abstract. Provides the initial store. Called by ContextManager.buildStores(payload).

public abstract buildStore(payload?: Record<string, any>): TStore;
public buildStore(payload?: Record<string, any>): UserStore {
return { userId: payload?.userId ?? "", role: payload?.role ?? "guest" };
}

Run a callback inside a new context scope. The store auto-clears when the callback resolves or rejects.

public run<T>(store: TStore, callback: () => Promise<T>): Promise<T>;
await userContext.run({ userId: "u-1", role: "admin" }, async () => {
await doWork();
});

Set the store and return — no callback, no auto-cleanup. Use for legacy middleware that calls next() and returns synchronously.

public enter(store: TStore): void;
function middleware(req, res, next) {
userContext.enter({ userId: req.user.id, role: req.user.role });
next();
}

Merge a partial into the current store via Object.assign. If no store is active, enters a new one with the partial cast to TStore.

public update(updates: Partial<TStore>): void;
userContext.update({ role: "admin" });

Read one key from the current store. Returns undefined if no context is active.

public get<K extends keyof TStore>(key: K): TStore[K] | undefined;
const userId = userContext.get("userId"); // string | undefined

Sugar for update({ [key]: value }). Same caveat — calling it outside an active context enters a new one with just that key.

public set<K extends keyof TStore>(key: K, value: TStore[K]): void;
userContext.set("role", "admin");

Return the whole store record, or undefined if no context is active.

public getStore(): TStore | undefined;
const store = userContext.getStore();

Replace the current store with {} cast to TStore. Does not exit the scope — hasContext() still returns true afterwards.

public clear(): void;
userContext.clear();

Returns true if a store is active in the current async scope. Distinguishes “no value” from “no context”.

public hasContext(): boolean;
if (!userContext.hasContext()) {
throw new Error("called outside a request scope");
}

Orchestrates multiple registered Context instances. The package exports a singleton contextManager; you can also instantiate new ContextManager() if you need a separate orchestrator.

class ContextManager

Register a context under a name. Returns the manager for chaining. Re-registering the same name overwrites.

public register(name: string, context: Context<any>): this;
contextManager
.register("trace", traceContext)
.register("user", userContext);

Remove a registration. Returns true if a registration existed.

public unregister(name: string): boolean;
contextManager.unregister("debug");

Call every registered context’s buildStore(payload) and return the results keyed by registration name.

public buildStores(payload?: Record<string, any>): Record<string, any>;
const stores = contextManager.buildStores({ request: req, response: res });

Nest every registered context’s run() in registration order, invoke the callback at the innermost layer.

public runAll<T>(stores: Record<string, any>, callback: () => Promise<T>): Promise<T>;
await contextManager.runAll(stores, async () => {
await handleRequest(req, res);
});

Call enter() on every context whose name has a truthy value in stores. No auto-cleanup.

public enterAll(stores: Record<string, any>): void;
function middleware(req, res, next) {
contextManager.enterAll(contextManager.buildStores({ user: req.user }));
next();
}

Call clear() on every registered context.

public clearAll(): void;
contextManager.clearAll();

Look up a registered context by name. The generic parameter narrows the return type.

public getContext<T extends Context<any>>(name: string): T | undefined;
const tenant = contextManager.getContext<TenantContext>("tenant");

Check whether a context is registered (not whether it has an active store).

public hasContext(name: string): boolean;
if (!contextManager.hasContext("trace")) {
contextManager.register("trace", traceContext);
}

The singleton instance of ContextManager exported from the package.

const contextManager: ContextManager;
import { contextManager } from "@warlock.js/context";
contextManager.register("user", userContext);