diff --git a/packages/context/src/context.ts b/packages/context/src/context.ts index 1f9bb82f3..569b852cf 100644 --- a/packages/context/src/context.ts +++ b/packages/context/src/context.ts @@ -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(defaultContextValue?: T): CtxApi { let contextValue: T | symbol = EMPTY_CONTEXT; @@ -34,17 +44,29 @@ export function createContext(defaultContextValue?: T): CtxApi { 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(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; } @@ -97,4 +119,4 @@ export type CtxApi = ContextConsumptionApi & { export type CtxCascadeApi = ContextConsumptionApi & { run: (value: Partial, fn: () => R) => R; bind: (value: Partial, fn: Fn) => Fn; -}; +}; \ No newline at end of file