@doeixd/effectively
Version:
Enhanced async/await effects for TypeScript applications. Effectively provides resilient error handling, dependency injection, retry logic, timeouts, circuit breakers, and resource cleanup for Node.js and browser environments. Build testable async workflo
1,053 lines (1,050 loc) • 180 kB
text/typescript
import { createContext as createContext$1 } from 'unctx';
import { AsyncLocalStorage } from 'node:async_hooks';
import { Result } from 'neverthrow';
import { Plugin } from 'seroval';
/**
* @module
* This module implements the "Effects Handler" pattern, a powerful way to
* decouple the declaration of side effects (the "what") from their implementation
* (the "how"). This enables maximum testability and allows for swapping
* implementations at runtime.
*
* ---
*
* ### Recommended Usage
*
* - For **multiple effects** in an application, use `createEffectSuite` for the best end-to-end type safety.
* - For a **single effect** or for one-off overrides in tests, use `defineEffect` and its attached `.withHandler()` method for a clean, safe, and ergonomic API.
*
* Standalone helpers are also available for backward compatibility and advanced use cases.
*/
/**
* A unique `Symbol` used as the key for the effects handler map within the context.
* Using `Symbol.for()` ensures that even if multiple versions of this library
* are present in a project, they will all share the same symbol, guaranteeing interoperability.
*/
declare const HANDLERS_KEY: unique symbol;
/** A generic type representing the shape of any function. */
type AnyFn = (...args: any[]) => any;
/** An object type where keys are strings and values are functions. Used as a generic constraint. */
type EffectsSchema = Record<string, AnyFn>;
/**
* Represents a callable effect function. It is an async-first version of the
* function signature `T`.
*/
type Effect<T extends AnyFn> = (...args: Parameters<T>) => Promise<ReturnType<T>>;
/** A generic mapping of effect names to their concrete handler implementations. */
type Handlers = Record<string, AnyFn>;
type EffectSuiteTools<T extends EffectsSchema> = {
effects: EffectsSuite<T>;
createHandlers: (handlers: T) => T;
withHandlers(handlers: T): {
overrides: {
[HANDLERS_KEY]: Handlers;
};
};
};
/**
* The return type of the `defineEffect` function. It is the callable effect
* function with attached helper methods for easily providing an implementation.
*/
type EffectWithHelpers<T extends AnyFn> = Effect<T> & {
/**
* Creates run options with a handler for this specific effect. This is the
* safest and most convenient way to provide a one-off implementation for an
* effect, especially in tests.
* @param implementation The function that implements the effect. Its signature
* is validated against the effect's definition.
*/
withHandler: (implementation: T) => {
overrides: {
[HANDLERS_KEY]: Handlers;
};
};
/**
* Creates a single-entry `Handlers` object for this effect. Useful when
* combining multiple handlers from different effects.
* @param implementation The function that implements the effect.
*/
createHandler: (implementation: T) => Handlers;
};
/** The return type of `defineEffects` or the `effects` property from `createEffectSuite`. */
type EffectsSuite<T extends EffectsSchema> = {
[K in keyof T]: EffectWithHelpers<T[K]>;
};
/** The shape a context must have to support the effects pattern. */
interface EffectsContext extends BaseContext {
[HANDLERS_KEY]?: Handlers;
}
/**
* An error thrown when an effect is called but no corresponding handler
* has been provided in the context at runtime.
*/
declare class EffectHandlerNotFoundError extends Error {
readonly effectName: string;
constructor(effectName: string);
}
/**
* Creates a complete, type-safe suite for managing a domain of **multiple effects**.
*
* This is the **recommended entry point** for full applications. It takes a single
* generic argument (your effects contract) and returns a matched set of tools that
* provide end-to-end type safety.
*
* @template T A `type` or `interface` that defines the effect names and their function signatures.
*/
declare function createEffectSuite<T extends EffectsSchema>(): {
effects: EffectsSuite<T>;
createHandlers: (handlers: T) => T;
withHandlers: (handlers: T) => {
overrides: {
[HANDLERS_KEY]: Handlers;
};
};
};
/**
* Defines a **single, typed effect**. This is the recommended way to handle individual
* effects or one-off overrides in tests.
*
* It returns a directly callable, async effect function. For convenience,
* it also comes with attached helper methods (`.withHandler` and `.createHandler`)
* to make providing a single implementation safe and easy.
*
* @template T The function signature of the effect.
* @param effectName A unique string identifier for this effect.
* @returns A callable `Effect` function with attached helpers.
*
* @example (Providing a one-off override in a test)
* ```typescript
* const getUniqueId = defineEffect<() => string>('getUniqueId');
*
* test('should use a predictable ID', async () => {
* const user = await run(
* createUserTask,
* 'test-user',
* getUniqueId.withHandler(() => 'test-id-123') // ✅ Simple and type-safe!
* );
* expect(user.id).toBe('test-id-123');
* });
* ```
*/
declare function defineEffect<T extends AnyFn>(effectName: string): EffectWithHelpers<T>;
declare function defineEffects<T extends EffectsSchema>(): EffectsSuite<T>;
declare function defineEffects<T extends EffectsSchema>(effectsConfig: T): EffectsSuite<T>;
declare function createHandlers<T extends EffectsSchema>(handlers: T): T;
declare function createHandlers(handlers: Handlers): Handlers;
declare function withHandlers<T extends EffectsSchema>(handlers: T): {
overrides: {
[HANDLERS_KEY]: T;
};
};
declare function withHandlers(handlers: Handlers): {
overrides: {
[HANDLERS_KEY]: Handlers;
};
};
/**
* [Advanced] Creates run options for a single handler by providing the full
* effects suite, the name of the effect to handle, and its implementation.
* @param effectsSuite The full `effects` object from `createEffectSuite` or `defineEffects`.
* @param effectName The key of the effect within the suite to provide a handler for.
* @param implementation The function that implements the effect.
* @example run(myTask, withHandler(effects, 'getUniqueId', () => 'test-id'))
*/
declare function withHandler<T extends EffectsSchema, K extends keyof T>(effectsSuite: EffectsSuite<T>, effectName: K, implementation: T[K]): {
overrides: {
[HANDLERS_KEY]: Handlers;
};
};
/**
* [Advanced] Creates run options for a single handler by providing its
* signature via a generic, its name, and its implementation.
* @template T The function signature of the effect.
* @param effectName The unique string name of the effect.
* @param implementation The function that implements the effect.
* @example run(myTask, withHandler<() => string>('getUniqueId', () => 'test-id'))
*/
declare function withHandler<T extends AnyFn>(effectName: string, implementation: T): {
overrides: {
[HANDLERS_KEY]: Handlers;
};
};
/**
* [Advanced] A standalone function to create run options for a single handler
* by providing the effect object and its implementation.
* @param effect The effect object returned by `defineEffect`.
* @param implementation The function that implements the effect.
* @example run(myTask, withHandler(getUniqueId, () => 'test-id'))
*/
declare function withHandler<T extends AnyFn>(effect: EffectWithHelpers<T>, implementation: T): {
overrides: {
[HANDLERS_KEY]: Handlers;
};
};
/**
* Base context that all contexts must extend.
* This ensures type safety and provides the minimum required properties.
*/
type BaseContext = {
readonly scope: Scope;
};
/**
* Utility type for merging two contexts safely.
* Properties in B override properties in A.
*/
type MergeContexts<A extends BaseContext, B extends Record<string, unknown>> = Omit<A, keyof B> & B & {
scope: Scope;
};
/**
* Utility type for context with optional extensions.
* Useful for contexts that may or may not have certain dependencies.
*/
type WithOptionalContext<Base extends BaseContext, Extension extends Record<string, unknown>> = Base & Partial<Extension>;
type EnhancedOverrides<C extends BaseContext> = Partial<Omit<C, "scope">> & {
[HANDLERS_KEY]?: Record<string, any>;
};
/**
* Type for context validation functions.
*/
type ContextValidator<T> = (value: unknown) => value is T;
/**
* Schema definition for runtime context validation.
*/
type ContextSchema<T extends BaseContext> = {
readonly [K in keyof Omit<T, "scope">]: ContextValidator<T[K]>;
};
/**
* Error thrown when context validation fails.
*/
declare class ContextValidationError extends Error {
readonly _tag: "ContextValidationError";
readonly field: string;
readonly expectedType: string;
readonly actualValue: unknown;
constructor(field: string, expectedType: string, actualValue: unknown);
}
/**
* Represents the execution scope for managing cancellation and cleanup.
* The scope provides a unified way to handle cancellation across all tasks
* in a workflow. When the signal is aborted, all tasks should gracefully
* terminate their operations.
*/
interface Scope {
/**
* An AbortSignal that is triggered if the entire `run` operation is cancelled.
* All tasks within the scope should respect this signal.
*
* @example
* ```typescript
* const { scope } = getContext();
* const response = await fetch(url, { signal: scope.signal });
* ```
*/
readonly signal: AbortSignal;
}
/**
* Logger interface for workflow execution logging.
* Compatible with common logging libraries like winston, pino, console, etc.
*/
interface Logger {
debug(message: string, ...args: unknown[]): void;
info(message: string, ...args: unknown[]): void;
warn(message: string, ...args: unknown[]): void;
error(message: string, ...args: unknown[]): void;
}
/**
* A no-op logger that discards all log messages.
*/
declare const noopLogger: Logger;
/**
* The fundamental unit of work in the library.
*
* A Task is an async function that receives a context and an input value,
* and returns a Promise of an output value. Tasks are composable and can
* be chained together using a `createWorkflow` function from the `utils` module.
*
* @template C The shape of the application's context (must extend BaseContext).
* @template V The input value type for the task.
* @template R The resolved output value type of the task.
*/
type Task<C extends BaseContext, V, R> = ((context: C, value: V) => Promise<R>) & {
/**
* Internal property used to identify tasks for backtracking.
* This is automatically set by `defineTask` and should not be manually modified.
* @internal
*/
__task_id?: symbol;
/**
* Internal property used by the `createWorkflow` utility to store the composed steps.
* @internal
*/
__steps?: ReadonlyArray<Task<C, unknown, unknown>>;
__task_type?: "stream" | "request";
};
/**
* A special signal used for non-linear control flow.
*
* When a task throws this signal, the `run` function will catch it and restart
* the workflow from the specified target task with a new input value.
* This enables powerful patterns like state machines and conditional flow control.
*/
declare class BacktrackSignal<C extends BaseContext = BaseContext, V = unknown> extends Error {
readonly _tag: "BacktrackSignal";
readonly target: Task<C, V, unknown>;
readonly value: V;
constructor(target: Task<C, V, unknown>, value: V);
}
/**
* Type guard to check if an error is a BacktrackSignal.
*/
declare function isBacktrackSignal(error: unknown): error is BacktrackSignal;
/**
* A structured error type for all workflow execution failures.
*/
declare class WorkflowError extends Error {
readonly cause?: unknown;
readonly taskName?: string;
readonly taskIndex?: number;
constructor(message: string, cause?: unknown, taskName?: string, taskIndex?: number);
}
/**
* Options for the `run` function with default error-throwing behavior.
*/
interface RunOptionsThrow<C extends BaseContext> {
throw?: true;
logger?: Logger;
overrides?: EnhancedOverrides<C>;
parentSignal?: AbortSignal;
}
/**
* Options for the `run` function with `Result` return type.
*/
interface RunOptionsResult<C extends BaseContext> {
throw: false;
logger?: Logger;
overrides?: EnhancedOverrides<C>;
parentSignal?: AbortSignal;
}
type RunOptions<C extends BaseContext> = RunOptionsThrow<C> | RunOptionsResult<C>;
/**
* Error thrown when `getContext` is called outside of a `run` execution.
*/
declare class ContextNotFoundError extends Error {
constructor(message?: string);
}
/**
* The set of tools returned by `createContext<C>()` for working with
* a specifically typed and isolated Effectively context.
*
* All methods within these tools operate on the context of type `C`
* that was defined when `createContext<C>()` was called. They use an
* internal `unctx` instance unique to this set of tools.
*
* @template C The specific `BaseContext` type this set of tools operates on.
*/
interface ContextTools<C extends BaseContext, G extends BaseContext = DefaultGlobalContext> {
/**
* Executes a workflow (a Task or a chain of Tasks) within this specific context.
*
* @template V The input value type for the workflow.
* @template R The result type of the workflow.
*
* @param workflow The `Task<C, V, R>` or a compatible task to execute.
* If a `Task<any, V, R>` (from global `defineTask`) is passed,
* it will run within this specific context `C`.
* @param initialValue The initial input value for the first task.
* @param options Optional `RunOptions<C>` to control error handling (`throw`),
* logging, context overrides specific to this run, and parent cancellation.
* Overrides are applied on top of the default context data for these tools.
*
* @returns A Promise that resolves to the workflow's final result `R`, or, if
* `options.throw` is `false`, a `Promise<Result<R, WorkflowError>>`.
*/
run<V, R>(workflow: Task<C, V, R> | Task<any, V, R>, // Can run tasks defined globally or for this specific context
initialValue: V, options?: RunOptionsThrow<C>): Promise<R>;
run<V, R>(workflow: Task<C, V, R> | Task<any, V, R>, initialValue: V, options: RunOptionsResult<C>): Promise<Result<R, WorkflowError>>;
/**
* Retrieves the currently active context of type `C` for this specific
* `ContextTools` instance.
*
* @returns The context object of type `C`.
* @throws {ContextNotFoundError} If called outside of an active `run` or
* `provide` scope managed by *this specific set of tools*.
*/
getContext: () => C;
/**
* Safely retrieves the currently active context of type `C` as a `Result`.
*
* @returns `Ok(context)` if the context is active, otherwise `Err(ContextNotFoundError)`.
*/
getContextSafe: () => Result<C, ContextNotFoundError>;
/**
* Retrieves the currently active context of type `C`, or `undefined` if not
* within an active scope of these tools.
*
* @returns The context object `C` or `undefined`.
*/
getContextOrUndefined: () => C | undefined;
/**
* Defines a Task that is strongly typed to operate within context `C`.
*
* The provided function `fn` should use `this.getContext()` (or the `getContext`
* destructured from these tools) to access the context `C`.
*
* @template V The input value type for the task's logic.
* @template R The result type the task's logic will resolve to.
*
* @param fn The core asynchronous logic of the task.
* @param taskNameOrOptions Optional name for the task or `DefineTaskOptions`.
* @returns A `Task<C, V, R>`, strongly typed to this context.
*/
defineTask: {
/**
* Defines a context-aware Task from a standard async function.
*/
<V, R>(fn: (value: V) => Promise<R>, taskNameOrOptions?: string | DefineTaskOptions): Task<C, V, R>;
/**
* Defines a context-aware Task from an async generator for streaming.
*/
<V, R>(fn: (value: V) => AsyncGenerator<R, any, unknown> | AsyncIterable<R>, taskNameOrOptions?: string | DefineTaskOptions): Task<C, V, AsyncIterable<R>>;
};
/**
* Executes a function within a new, nested context scope that temporarily
* includes the specified overrides. This new scope is managed by the same
* `unctx` instance associated with these `ContextTools`.
*
* The `scope` property is always inherited from the parent context of this
* `provide` call and cannot be overridden.
*
* @template Pr The result type of the function `fn`. (Using `Pr` to avoid conflict with `R` from `run`)
*
* @param overrides An object with properties to merge into (or override on)
* the current context for the duration of `fn`'s execution.
* Overrides are typed against `C`.
* @param fn The asynchronous function to execute within the new, modified context.
* Calls to `this.getContext()` within this function will resolve to the new context.
* @param options Optional `ProvideImplOptions` to specify the context merging
* strategy (e.g., 'spread' or 'proxy'). Defaults to 'spread'.
*
* @returns A Promise that resolves with the result of `fn`.
*
* @example
* ```typescript
* const { provide, run, defineTask, getContext } = createContext<MyContext>(...);
*
* const myInnerTask = defineTask(async () => {
* const ctx = getContext(); // Gets MyContext + { temporaryApi: ... }
* // ctx.temporaryApi.call();
* });
*
* await provide(
* { temporaryApi: new MockApi() },
* async () => {
* // Code here runs with temporaryApi in context
* await run(myInnerTask, undefined);
* }
* );
* ```
*/
provide<Pr>(overrides: Partial<Omit<C, "scope" | typeof UNCTX_INSTANCE_SYMBOL> & Record<string | symbol, any>>, fn: () => Promise<Pr>, options?: ProvideImplOptions): Promise<Pr>;
/**
* Injects a dependency from the current context `C` using its token.
*
* @template T The type of the dependency to inject.
* @param token The `InjectionToken<T>` for the dependency.
* @returns The resolved dependency of type `T`.
* @throws Error if the token is not found in the current context `C`.
*/
inject<T>(token: InjectionToken<T>): T;
/**
* Safely injects a dependency from the current context `C`, returning
* `undefined` if the token is not found.
*
* @template T The type of the dependency to inject.
* @param token The `InjectionToken<T>` for the dependency.
* @returns The resolved dependency of type `T`, or `undefined`.
*/
injectOptional<T>(token: InjectionToken<T>): T | undefined;
/**
* Retrieves this toolset's specific context `C_Specific` if active,
* otherwise falls back to the application's global context `G_AppGlobal`.
*/
getContextOrGlobal: () => C | G;
}
/**
* Symbol used to store the unctx instance on context objects for smart function detection
*/
declare const UNCTX_INSTANCE_SYMBOL: unique symbol;
declare function _INTERNAL_setCurrentUnctxInstance(instance: ReturnType<typeof createContext$1<any>> | null): void;
declare function _INTERNAL_getCurrentUnctxInstance(): ReturnType<typeof createContext$1<any>> | null;
/**
* Internal Helper: Retrieves the context object from the *currently active specific*
* `unctx` instance, if one is set (via `currentUnctxInstance`).
*
* This function is used by "smart" global functions (like `getContext`, `run`, `provide`)
* to detect if they are operating within a scope established by a specific
* `createContext().run()` or `createContext().provide()` call.
*
* If `currentUnctxInstance` is `null`, it means no specific Effectively context's
* `run` or `provide` is currently active up the async call stack, so this function
* will return `undefined`.
*
* If `currentUnctxInstance` is set, it attempts to retrieve the context stored
* by that `unctx` instance for the current asynchronous execution path.
*
* @template C The expected type of the context. This is an assertion by the caller.
* @returns The active specific context object of type `C` if found and compatible,
* otherwise `undefined`.
*/
declare function getContextOrUndefinedFromActiveInstance<C extends BaseContext>(): C | undefined;
/**
* Default context interface that can be used without explicit context creation
*/
interface DefaultGlobalContext extends BaseContext {
}
declare global {
var __effectively_default_context__: ContextTools<DefaultGlobalContext, DefaultGlobalContext> | undefined;
}
interface DefineTaskOptions {
name?: string;
idSymbol?: symbol;
}
/**
* Defines a context-aware Task, specifying the expected Context type `C`.
* This overload is for standard async functions.
*
* @param fn An async function `(value: V) => Promise<R>`.
* @param taskNameOrOptions Optional name or configuration for the task.
*/
declare function defineTask<C extends BaseContext, V = any, R = any>(fn: (value: V) => Promise<R>, taskNameOrOptions?: string | DefineTaskOptions): Task<any, V, R>;
/**
* Defines a context-aware Task, specifying the expected Context type `C`.
* This overload is for async generator functions (streaming).
*
* @param fn An async generator function `(value: V) => AsyncGenerator<R> | AsyncIterable<R>`.
* @param taskNameOrOptions Optional name or configuration for the task.
*/
declare function defineTask<C extends BaseContext, V = any, R = any>(fn: (value: V) => AsyncGenerator<R> | AsyncIterable<R>, taskNameOrOptions?: string | DefineTaskOptions): Task<any, V, AsyncIterable<R>>;
/**
* Retrieves the currently active execution context.
*
* This "smart" version of `getContext` is the primary way for tasks to access
* their environment. It operates with the following priority:
*
* 1. **Active Specific Context:** If the current asynchronous flow is operating
* within a context established by `createContext<C>().run(...)` or
* `createContext<C>().provide(...)`, that specific, typed context is returned.
* This is facilitated by `currentUnctxInstance` pointing to the relevant `unctx` store.
*
* 2. **Global Default Context:** If no specific context is active (e.g., when
* `getContext` is called at the top level of a module, or within a task
* run by the global smart `run` function without a prior specific context),
* a shared, global default context object is returned. This global default
* is guaranteed to at least satisfy `BaseContext` (containing a `scope`).
*
* The generic type parameter `C` allows the caller to assert the expected type of
* the context.
* - If `getContext<UserContext>()` is called within a `run` scope for `UserContext`,
* `UserContext` is returned and is fully typed.
* - If `getContext<UserContext>()` is called at the top level (global scope),
* the global default context object is returned, but **cast** to `UserContext`.
* It is the developer's responsibility in such cases to ensure that the
* global default context has been appropriately configured (e.g., via
* `provideGlobal`) to actually satisfy the `UserContext` interface. Otherwise,
* accessing `UserContext`-specific properties might lead to runtime errors
* (e.g., `property undefined`).
* - If `getContext()` is called with no type argument, it defaults to returning
* the `DefaultGlobalContext` type.
*
* This function is designed to always return a context object and typically does not throw
* `ContextNotFoundError` itself (that's more for `getContextLocal`).
*
* @template C The expected type of the context. Defaults to `DefaultGlobalContext`.
* @returns The resolved context, typed as `C`.
*
* @example
* ```typescript
* import { getContext, createContext, defineTask, type BaseContext, type DefaultGlobalContext } from 'effectively';
*
* // 1. Global scope:
* const globalCtx = getContext(); // Type: DefaultGlobalContext
* console.log('Global scope signal:', globalCtx.scope.signal.aborted);
*
* interface AppEnvContext extends BaseContext { env: string; }
* // Using provideGlobal to enhance the global context object
* // await provideGlobal({ env: "production" }, async () => {
* // const appEnvCtx = getContext<AppEnvContext>(); // Now safely gets { scope:..., env:"production" }
* // console.log(appEnvCtx.env);
* // });
*
* // 2. Specific context scope:
* interface UserContext extends BaseContext { userId: string; permissions: string[]; }
* const { run: runUserScope, defineTask: defineUserTask } =
* createContext<UserContext>({ userId: 'guest', permissions: [] });
*
* const fetchUserData = defineUserTask(async (id: string) => {
* const ctx = getContext<UserContext>(); // Type: UserContext. Correctly resolves to the UserContext instance.
* console.log(`Fetching data for user ${ctx.userId} (permissions: ${ctx.permissions.join(',')}) with id ${id}`);
* if (ctx.userId === 'guest' && id !== 'public') throw new Error("Guest access denied");
* return { data: `data_for_${id}` };
* });
*
* // await runUserScope(fetchUserData, 'user123', { overrides: { userId: 'user123', permissions: ['read'] } });
* ```
*/
declare function getContext(): DefaultGlobalContext;
declare function getContext<C extends BaseContext>(): C;
/**
* Safely retrieves the currently active execution context as a `Result`.
*
* This "smart" version first attempts to resolve a specific context active in the
* current asynchronous flow. If found, it returns `Ok(context)`.
* If no specific context is active, it falls back to the global default context
* and returns `Ok(globalDefaultContext)`.
*
* This function itself should generally not throw `ContextNotFoundError` directly,
* as it aims to always provide a context (even if it's the global default).
*
* The type parameter `C` allows the caller to assert the expected shape.
*
* @template C The expected type of the context. Defaults to `DefaultGlobalContext`.
* @returns A `Result<C, ContextNotFoundError>`. `Ok(context)` on success.
* It's designed to always return `Ok` because a global default is always available.
*
* @example
* ```typescript
* const ctxResult = getContextSafe<UserContext>();
* if (ctxResult.isOk()) {
* const userCtx = ctxResult.value; // userCtx is UserContext
* // ...
* }
* ```
*/
declare function getContextSafe(): Result<DefaultGlobalContext, ContextNotFoundError>;
declare function getContextSafe<C extends BaseContext>(): Result<C, ContextNotFoundError>;
/**
* Retrieves the currently active execution context, or the global default if no specific one is active.
*
* This "smart" version first attempts to resolve a specific context active in the
* current asynchronous flow. If found, it's returned.
* If no specific context is active, it falls back to returning the global default context.
*
* This function will always return a context object (either specific or global default)
* due to the presence of `getGlobalDefaultContextObjectSingleton()`.
* The `| undefined` in the return type for the generic version is a concession to callers
* who might expect `getContextOrUndefined` to potentially return `undefined`, though
* in this setup, it's less likely unless `getContextOrUndefinedFromActiveInstance` or
* `getGlobalDefaultContextObjectSingleton` were to somehow return `undefined` (which
* they are not designed to do).
*
* @template C The expected type of the context. Defaults to `DefaultGlobalContext`.
* @returns The resolved context typed as `C` (if specific and active) or the global
* default context cast to `C`. It should always return a context object.
*
* @example
* ```typescript
* const userCtx = getContextOrUndefined<UserContext>();
* // userCtx will be UserContext if active, or DefaultGlobalContext (cast to UserContext) otherwise.
* // It will not be undefined if getGlobalDefaultContextObjectSingleton() is robust.
* if (userCtx) { // This check is more for type narrowing if C could truly be undefined.
* console.log(userCtx.scope);
* }
* ```
*/
declare function getContextOrUndefined(): DefaultGlobalContext;
declare function getContextOrUndefined<C extends BaseContext>(): C;
/**
* Executes a workflow (a Task or a chain of Tasks) with an initial value and options.
*
* This "smart" version of `run` intelligently manages context:
* 1. **Inheritance:** It determines the parent context (either an active specific
* context or the global default).
* 2. **Construction:** It creates a new execution context for this run by merging
* the parent's properties with any `overrides` provided in the options.
* 3. **Scope Management:** It creates a new, unique `scope` for this run, linking
* its cancellation to the parent's scope and any `parentSignal` in options.
*
* @param workflow The Task or composed workflow to execute.
* @param initialValue The initial input value for the first task in the workflow.
* @param options Optional `RunOptions` to control error handling, logging, context
* overrides, and parent cancellation signal.
*
* @returns A Promise resolving to the workflow's result `R`, or a `Result<R, WorkflowError>`.
*
* @example
* ```typescript
* // Example 1: Simple top-level execution
* const myTask = defineTask(async (name: string) => `Hello, ${name}`);
* const greeting = await run(myTask, 'World'); // -> "Hello, World"
*
* // Example 2: With context overrides
* interface AppContext extends BaseContext { greeting: string; }
* const myTaskWithContext = defineTask<AppContext, string, string>(async (name) => {
* const { greeting } = getContext<AppContext>();
* return `${greeting}, ${name}!`;
* });
* const result = await run(myTaskWithContext, 'Again', {
* overrides: { greeting: 'Hi there' }
* }); // -> "Hi there, Again!"
*
* // Example 3: Handling errors with a Result type
* const failingTask = defineTask(async () => { throw new Error('Failure'); });
* const errorResult = await run(failingTask, null, { throw: false });
* if (errorResult.isErr()) {
* console.log(errorResult.error.message); // -> "Task failed: Failure"
* }
* ```
*/
declare function run<V, R>(workflow: Task<any, V, R>, initialValue: V, options?: RunOptionsThrow<BaseContext>): Promise<R>;
declare function run<V, R>(workflow: Task<any, V, R>, initialValue: V, options: RunOptionsResult<BaseContext>): Promise<Result<R, WorkflowError>>;
declare function run<C extends BaseContext, V, R>(workflow: Task<C, V, R>, initialValue: V, options?: RunOptionsThrow<C>): Promise<R>;
declare function run<C extends BaseContext, V, R>(workflow: Task<C, V, R>, initialValue: V, options: RunOptionsResult<C>): Promise<Result<R, WorkflowError>>;
/**
* Executes a given function within a new context scope that temporarily
* includes the specified overrides.
*
* This "smart" version of `provide` intelligently manages context nesting:
* 1. **Nested Provide:** If called from within an already active Effectively context
* (e.g., a task calling `provide` for a sub-operation), it creates a new
* nested context that inherits from the parent. The `unctx` instance of the
* parent context is used to manage this new nested scope.
* 2. **Top-Level Provide:** If called outside any active Effectively context,
* it uses the global default context as the base and the global `unctx`
* instance to manage the new scope.
*
* The `scope` (and its `AbortSignal`) from the parent/base context is always
* inherited by the new context created by `provide` to ensure cancellation
* propagates correctly. Overrides cannot change the `scope`.
*
* @template R The result type of the function `fn`.
* @template CtxForOverrides Used by overloads to type `overrides` more specifically
* if `provide` is called in a way that implies a known context type.
* Defaults to `BaseContext`.
*
* @param overrides An object containing properties to merge into (or override on)
* the parent/base context for the duration of `fn`'s execution.
* The `scope` property cannot be overridden.
* @param fn The asynchronous function to execute within the new, modified context.
* Calls to `getContext()` within this function will resolve to the new context.
* @param options ParamImplOptions
* @returns A Promise that resolves with the result of `fn`.
*
* @example
* ```typescript
* // At top level (uses global context as base)
* await provide({ myService: mockService }, async () => {
* const ctx = getContext(); // ctx will have myService and global defaults
* // await run(someTaskThatUsesMyService);
* });
*
* // Nested within a specific context
* const { run, defineTask, provide: specificProvide } = createContext<UserContext>(...);
* const myTask = defineTask(async () => {
* await specificProvide({ temporaryFlag: true }, async () => {
* const innerCtx = getContext<UserContext & { temporaryFlag: boolean }>();
* // innerCtx has UserContext properties + temporaryFlag
* });
* });
* ```
*/
declare function provide<R>(overrides: Partial<Omit<DefaultGlobalContext, "scope"> & Record<string | symbol | number, any>>, fn: () => Promise<R>, options?: ProvideImplOptions): Promise<R>;
declare function provide<C extends BaseContext, R>(overrides: Partial<Omit<C, "scope"> & Record<string | symbol | number, any>>, // `overrides` typed against `C`
fn: () => Promise<R>, options?: ProvideImplOptions): Promise<R>;
/**
* Creates a new context scope with temporary overrides using a Proxy.
* This version avoids cloning the parent context object for performance benefits
* in scenarios with frequent `provide` calls or very large context objects.
*
* The returned Proxy layers the `overrides` on top of the `parentContextData`.
* Property access prioritizes `overrides`. The `scope` property is always
* inherited from the `parentContextData` and cannot be overridden.
* The `UNCTX_INSTANCE_SYMBOL` is also specially handled to point to the
* `unctx` instance managing this new proxied scope.
*
* @template R The result type of the function `fn`.
* @template CtxForOverrides The asserted type of the context including overrides.
* The actual parent context might be `BaseContext` or `DefaultGlobalContext`.
*
* @param overrides An object containing properties to make available in the new scope.
* The `scope` property in `overrides` will be ignored.
* @param fn The asynchronous function to execute within the new proxied context scope.
*
* @returns A Promise that resolves with the result of `fn`.
*/
declare function provideWithProxy<R, CtxForOverrides extends BaseContext = BaseContext>(overrides: Partial<Omit<CtxForOverrides, "scope" | typeof UNCTX_INSTANCE_SYMBOL> & Record<string | symbol, any>>, fn: () => Promise<R>): Promise<R>;
/**
* Defines a Task that is **strictly bound to the currently active specific context**.
*
* If no specific Effectively context is active when this function is called
* (i.e., not within a `run` or `provide` scope initiated by `createContext().tools`),
* this function will throw a `ContextNotFoundError`.
*
* The created Task, when executed by `runLocal` or a context-specific `run`, will
* operate within that active context `C`. Internal `getContext<C>()` calls within
* the task's `fn` logic will resolve to this `C`.
*
* @template C The specific `BaseContext` type of the *currently active* context.
* This is an assertion by the caller; the function checks for any active context.
* @template V The input value type for the task's logic.
* @template R The result type the task's logic will resolve to.
*
* @param fn The core asynchronous logic of the task.
* @param taskNameOrOptions Optional name or options for the task.
* @returns A `Task<C, V, R>`, typed to the asserted active context `C`.
* @throws {ContextNotFoundError} If no specific context is active when `defineTaskLocal` is called.
*/
declare function defineTaskLocal<C extends BaseContext, V, R>(fn: (value: V) => Promise<R>, taskNameOrOptions?: string | DefineTaskOptions): Task<C, V, R>;
/**
* Retrieves the **currently active specific context**, throwing an error if none is found.
* This function *never* falls back to a global default context.
*
* @template C The expected type of the active specific context.
* @returns The active specific context, typed as `C`.
* @throws {ContextNotFoundError} If no specific context is active.
*/
declare function getContextLocal<C extends BaseContext>(): C;
/**
* Safely retrieves the **currently active specific context** as a `Result`,
* returning `Err(ContextNotFoundError)` if none is found.
* This function *never* falls back to a global default context.
*
* @template C The expected type of the active specific context.
* @returns `Ok(context)` if an active specific context is found, otherwise `Err(ContextNotFoundError)`.
*/
declare function getContextSafeLocal<C extends BaseContext>(): Result<C, ContextNotFoundError>;
/**
* Retrieves the **currently active specific context**, or `undefined` if none is found.
* This function *never* falls back to a global default context.
*
* @template C The expected type of the active specific context.
* @returns The active specific context typed as `C`, or `undefined`.
*/
declare function getContextOrUndefinedLocal<C extends BaseContext>(): C | undefined;
/**
* Executes a workflow **exclusively within the currently active specific context**.
*
* If no specific Effectively context is active when `runLocal` is called, it will
* either throw a `ContextNotFoundError` (if `options.throw` is `true` or default)
* or return an `Err(WorkflowError)` (if `options.throw` is `false`).
* It *never* falls back to a global default context.
*
* The `workflow` (often created with `defineTaskLocal` or a context-specific `defineTask`)
* must be compatible with the active context `C`.
*
* @template C The specific `BaseContext` type of the *currently active* context.
* @template V The input value type for the workflow.
* @template R The result type of the workflow.
*
* @param workflow The Task or workflow to execute, typed as `Task<C, V, R>`.
* @param initialValue The initial input value for the workflow.
* @param options Optional `RunOptions<C>` for this local run. Overrides apply to the
* currently active context.
*
* @returns A Promise resolving to `R` or `Result<R, WorkflowError>` based on `options.throw`.
* @throws {ContextNotFoundError} If `options.throw` is not `false` and no active specific context.
*/
declare function runLocal<C extends BaseContext, V, R>(workflow: Task<C, V, R>, initialValue: V, options?: RunOptionsThrow<C>): Promise<R>;
declare function runLocal<C extends BaseContext, V, R>(workflow: Task<C, V, R>, initialValue: V, options: RunOptionsResult<C>): Promise<Result<R, WorkflowError>>;
/**
* Executes a function within a new nested scope derived from the **currently active
* specific context**, including the specified overrides.
*
* If no specific Effectively context is active when `provideLocal` is called,
* it will throw a `ContextNotFoundError`. It *never* falls back to a global default.
* The `scope` from the active specific context is inherited.
*
* @template C The specific `BaseContext` type of the *currently active* context.
* @template R The result type of the function `fn`.
*
* @param overrides An object of properties to merge into (or override on) the
* active specific context `C` for `fn`'s execution.
* @param fn The asynchronous function to execute. `getContextLocal<C>()` or `getContext<C>()`
* within `fn` will resolve to the new nested context.
* @param options Optional `ProvideImplOptions` (e.g., to specify 'proxy' strategy).
*
* @returns A Promise that resolves with the result of `fn`.
* @throws {ContextNotFoundError} If no active specific context is found.
*/
declare function provideLocal<C extends BaseContext, R>(overrides: Partial<Omit<C, "scope" | typeof UNCTX_INSTANCE_SYMBOL> & Record<string | symbol, any>>, fn: () => Promise<R>, options?: ProvideImplOptions): Promise<R>;
/**
* Defines a Task that is **always** bound to the global default context.
*
* When this task is executed (e.g., via `runGlobal` or even a specific `run`),
* any internal calls to `getContext()` will resolve to the global default context object,
* regardless of any other specific context that might have been active when `run` was called.
*
* This is useful for creating utility tasks or services that should consistently
* operate with global settings or state, bypassing any local context overrides.
*
* The `fn` (task logic) should use `getContext<DefaultGlobalContext>()` or simply `getContext()`
* to access the global context.
*
* @template V The input value type for the task's logic.
* @template R The result type the task's logic will resolve to.
*
* @param fn The core asynchronous logic of the task.
* @param taskNameOrOptions Optional name or options for the task.
* @returns A `Task<DefaultGlobalContext, V, R>`, specifically typed to the global context.
*/
declare const defineTaskGlobal: <V, R>(fn: (value: V) => Promise<R>, taskNameOrOptions?: string | DefineTaskOptions) => Task<DefaultGlobalContext, V, R>;
/**
* Retrieves the singleton global default context object.
* This function *always* returns the global default context, ignoring any
* active specific context.
*
* @returns The `DefaultGlobalContext` object.
*/
declare const getContextGlobal: () => DefaultGlobalContext;
/**
* Executes a workflow **exclusively** within the global default context.
* Any active specific context that might exist when `runGlobal` is called
* will be ignored for the execution of this workflow and its tasks.
* Internal `getContext()` calls within the workflow will resolve to the
* global default context object.
*
* @template V The input value type for the workflow.
* @template R The result type of the workflow.
*
* @param workflow The Task (often created with `defineTaskGlobal`) or workflow to execute.
* It should be typed as `Task<DefaultGlobalContext, V, R>` or `Task<any, V, R>`.
* @param initialValue The initial input value for the workflow.
* @param options Optional `RunOptions<DefaultGlobalContext>` for this global run.
* Overrides here apply on top of the `globalDefaultContextObject`.
*
* @returns A Promise resolving to `R` or `Result<R, WorkflowError>` based on `options.throw`.
*/
declare function runGlobal<V, R>(workflow: Task<DefaultGlobalContext, V, R> | Task<any, V, R>, // Accepts globally or specifically (any) defined tasks
initialValue: V, options?: RunOptionsThrow<DefaultGlobalContext>): Promise<R>;
declare function runGlobal<V, R>(workflow: Task<DefaultGlobalContext, V, R> | Task<any, V, R>, initialValue: V, options: RunOptionsResult<DefaultGlobalContext>): Promise<Result<R, WorkflowError>>;
/**
* Executes a function within a new scope that is an extension of the
* **global default context**, including the specified overrides.
* Any active specific context is ignored.
*
* The `scope` from the global default context object is inherited.
*
* @template R The result type of the function `fn`.
*
* @param overrides An object containing properties to merge into (or override on)
* the global default context for `fn`'s execution.
* @param fn The asynchronous function to execute. `getContext()` within `fn` will
* resolve to the new context based on global defaults + overrides.
* @param options Optional `ProvideImplOptions` (e.g., to specify 'proxy' strategy).
*
* @returns A Promise that resolves with the result of `fn`.
*/
declare const provideGlobal: <R>(overrides: Partial<Omit<DefaultGlobalContext, "scope" | typeof UNCTX_INSTANCE_SYMBOL> & Record<string | symbol, any>>, fn: () => Promise<R>, options?: ProvideImplOptions) => Promise<R>;
/**
* Validates a context object against a schema.
* Returns a Result indicating success or failure with detailed error information.
*/
declare function validateContext<C extends BaseContext>(schema: ContextSchema<C>, context: unknown): Result<C, ContextValidationError>;
/**
* Type-safe context merging function.
* Combines two contexts, with properties in the second context taking precedence.
*/
declare function mergeContexts<A extends BaseContext, B extends Record<string, unknown>>(contextA: A, contextB: B): MergeContexts<A, B>;
/**
* Creates a context transformer function that can be used to modify contexts.
*/
declare function createContextTransformer<C1 extends BaseContext, C2 extends BaseContext>(transformer: (ctx: C1) => Omit<C2, "scope">): (ctx: C1) => C2;
/**
* Type-safe context property accessor.
* Provides compile-time safety when accessing context properties.
*/
declare function useContextProperty<K extends keyof DefaultGlobalContext>(key: K): DefaultGlobalContext[K];
declare function useContextProperty<C extends BaseContext, K extends keyof C>(key: K): C[K];
/**
* Requires specific properties to be present in the context.
* Throws a descriptive error if any required property is missing.
*/
declare function requireContextProperties<C extends BaseContext = DefaultGlobalContext>(...requirements: (keyof C)[]): C;
/**
* Creates a task that provides additional context to its child task.
*/
declare function withContextEnhancement<C extends BaseContext, Enhancement extends Record<string, unknown>, V, R>(enhancement: Enhancement, task: Task<MergeContexts<C, Enhancement>, V, R>): Task<C, V, R>;
/**
* Token for dependency injection.
* A unique symbol used to identify injectable services.
*/
type InjectionToken<T> = symbol & {
__type?: T;
};
/**
* Creates a new injection token with type information.
*/
declare function createInjectionToken<T>(description: string): InjectionToken<T>;
/**
* Injectable service configuration.
*/
interface Injectable<T> {
provide: InjectionToken<T>;
useValue?: T;
useFactory?: () => T | Promise<T>;
}
/**
* Context provider configuration.
*/
interface ContextProvider<T> {
provide: InjectionToken<T>;
value: T;
}
/**
* Injects a dependency from the current context using its token.
* Throws an error if the dependency is not found.
*/
declare function inject<T>(token: InjectionToken<T>): T;
/**
* Safely injects a dependency, returning undefined if not found.
*/
declare function injectOptional<T>(token: InjectionToken<T>): T | undefined;
/**
* Creates a context provider that supplies a value for a specific token.
*/
declare function createContextProvider<T>(token: InjectionToken<T>): {
Provider: <C extends BaseContext, V, R>(value: T, task: Task<C & Record<symbol, T>, V, R>) => Task<C, V, R>;
useValue: () => T;
};
/**
* Creates a scoped context that temporarily provides additional services.
*/
declare function withScope<C extends BaseContext, V, R>(providers: ContextProvider<unknown>[], task: Task<C, V, R>): Task<C, V, R>;
/**
* Higher-order function that creates a task with pre-configured dependencies.
*/
declare function withDependencies<C extends BaseContext, Deps extends Record<string, unknown>>(dependencies: Deps): <V, R>(taskFactory: (deps: Deps) => Task<C, V, R>) => Task<C, V, R>;
/**
* Creates a lazy-loaded dependency that is only instantiated when first accessed.
*/
declare function createLazyDependency<T>(factory: () => T | Promise<T>): () => Promise<T>;
type ProvideStrategy = "spread" | "proxy";
interface ProvideImplOptions {
strategy?: ProvideStrategy;
}
/**
* Creates a new, isolated "Effectively" execution environment, providing a
* set of tools (`ContextTools`) strongly typed to a specific context shape `C`.
*
* Each call to `createContext` establishes a distinct context system with its
* own internal state for asynchronous context propagation (via `unctx` and
* typically `AsyncLocalStorage`). This ensures that tasks run using tools from
* one `createContext` call do not interfere with tasks run by tools from another.
*
* The `defaultContextDataForC` parameter provides the initial, default values
* (excluding `scope`) for this specific context type `C`. The `scope` (containing
* an `AbortSignal`) is managed automatically by the `run` method of the returned tools.
*
* @template C The specific type of the context that this environment will manage.
* It must extend `BaseContext` (which requires a `scope` property).
* Example: `interface UserSessionContext extends BaseContext { userId: string; }`
*
* @param defaultContextDataForC An object containing the default properties and values
* for your context type `C`. The `scope` property should
* be omitted as it's managed internally by the `run` method.
* Example: `{ userId: 'guest', tenantId: 'default' }`
*
** @param asyncLocalStorage Optional. An `AsyncLocalStorage` constructor to use for
* asynchronous context propagation. Defaults to the native
* `node:async_hooks.AsyncLocalStorage`. Useful for testing
* or providing polyfills in environments where the native
* versio