Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 30 additions & 8 deletions packages/context/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,17 @@ const USEX_DEFAULT_ERROR_MESSAGE = 'Not inside of a running context.';
const EMPTY_CONTEXT = Symbol();

/**
* Base context interface.
* Creates a context API for temporarily setting and accessing a value of type `T`.
*
* The returned API lets callers run a function with a provided context value (restoring the previous
* context after the function completes, even if it throws), read the current context value with a
* fallback, or read the current context value and throw if none is active.
*
* @param defaultContextValue - Value returned by `use()` when no context is active
* @returns An object with:
* - `run(value, cb)` — executes `cb` with the context set to `value` and restores the prior context after `cb` finishes (restoration occurs even if `cb` throws).
* - `use()` — returns the current context value if a context is active, otherwise returns `defaultContextValue`.
* - `useX(errorMessage?)` — returns the current context value if a context is active; throws an error with `errorMessage` (or a default message) when no context is active.
*/
export function createContext<T>(defaultContextValue?: T): CtxApi<T> {
let contextValue: T | symbol = EMPTY_CONTEXT;
Expand All @@ -34,17 +44,29 @@ export function createContext<T>(defaultContextValue?: T): CtxApi<T> {
return contextValue as T;
}

/**
* Executes a callback with the context set to the provided value and restores the previous context when the callback completes or throws.
*
* @param value - Context value to set for the duration of `cb`
* @param cb - Function to execute while the given context is active
* @returns The value returned by `cb`
*/
function run<R>(value: T, cb: () => R): R {
const parentContext = isInsideContext() ? use() : EMPTY_CONTEXT;

contextValue = value;

const res = cb();

contextValue = parentContext;
return res;

try {
return cb();
} finally {
contextValue = parentContext;
}
}

/**
* Determines whether a context is currently active.
*
* @returns `true` if a context is active, `false` otherwise.
*/
function isInsideContext(): boolean {
return contextValue !== EMPTY_CONTEXT;
}
Expand Down Expand Up @@ -97,4 +119,4 @@ export type CtxApi<T> = ContextConsumptionApi<T> & {
export type CtxCascadeApi<T> = ContextConsumptionApi<T> & {
run: <R>(value: Partial<T>, fn: () => R) => R;
bind: <Fn extends CB>(value: Partial<T>, fn: Fn) => Fn;
};
};
Loading