UNPKG

effect

Version:

The missing standard library for TypeScript, for writing production-grade software.

1,650 lines (1,649 loc) 334 kB
import { dual } from "./Function.js"; import * as internalCause from "./internal/cause.js"; import * as console_ from "./internal/console.js"; import { TagProto } from "./internal/context.js"; import * as effect from "./internal/core-effect.js"; import * as core from "./internal/core.js"; import * as defaultServices from "./internal/defaultServices.js"; import * as circular from "./internal/effect/circular.js"; import * as internalExecutionPlan from "./internal/executionPlan.js"; import * as fiberRuntime from "./internal/fiberRuntime.js"; import * as layer from "./internal/layer.js"; import * as option_ from "./internal/option.js"; import * as query from "./internal/query.js"; import * as runtime_ from "./internal/runtime.js"; import * as schedule_ from "./internal/schedule.js"; import * as internalTracer from "./internal/tracer.js"; import * as Random from "./Random.js"; import * as Request from "./Request.js"; import * as Scheduler from "./Scheduler.js"; import { isGeneratorFunction } from "./Utils.js"; /** * @since 2.0.0 * @category Symbols */ export const EffectTypeId = core.EffectTypeId; /** * Checks if a given value is an `Effect` value. * * **When to Use** * * This function can be useful for checking the type of a value before * attempting to operate on it as an `Effect` value. For example, you could use * `Effect.isEffect` to check the type of a value before using it as an argument * to a function that expects an `Effect` value. * * @since 2.0.0 * @category Guards */ export const isEffect = core.isEffect; /** * Returns an effect that caches its result for a specified `Duration`, * known as "timeToLive" (TTL). * * **Details** * * This function is used to cache the result of an effect for a specified amount * of time. This means that the first time the effect is evaluated, its result * is computed and stored. * * If the effect is evaluated again within the specified `timeToLive`, the * cached result will be used, avoiding recomputation. * * After the specified duration has passed, the cache expires, and the effect * will be recomputed upon the next evaluation. * * **When to Use** * * Use this function when you have an effect that involves costly operations or * computations, and you want to avoid repeating them within a short time frame. * * It's ideal for scenarios where the result of an effect doesn't change * frequently and can be reused for a specified duration. * * By caching the result, you can improve efficiency and reduce unnecessary * computations, especially in performance-critical applications. * * **Example** * * ```ts * import { Effect, Console } from "effect" * * let i = 1 * const expensiveTask = Effect.promise<string>(() => { * console.log("expensive task...") * return new Promise((resolve) => { * setTimeout(() => { * resolve(`result ${i++}`) * }, 100) * }) * }) * * const program = Effect.gen(function* () { * const cached = yield* Effect.cachedWithTTL(expensiveTask, "150 millis") * yield* cached.pipe(Effect.andThen(Console.log)) * yield* cached.pipe(Effect.andThen(Console.log)) * yield* Effect.sleep("100 millis") * yield* cached.pipe(Effect.andThen(Console.log)) * }) * * Effect.runFork(program) * // Output: * // expensive task... * // result 1 * // result 1 * // expensive task... * // result 2 * ``` * * @see {@link cached} for a similar function that caches the result * indefinitely. * @see {@link cachedInvalidateWithTTL} for a similar function that includes an * additional effect for manually invalidating the cached value. * * @since 2.0.0 * @category Caching */ export const cachedWithTTL = circular.cached; /** * Caches an effect's result for a specified duration and allows manual * invalidation before expiration. * * **Details** * * This function behaves similarly to {@link cachedWithTTL} by caching the * result of an effect for a specified period of time. However, it introduces an * additional feature: it provides an effect that allows you to manually * invalidate the cached result before it naturally expires. * * This gives you more control over the cache, allowing you to refresh the * result when needed, even if the original cache has not yet expired. * * Once the cache is invalidated, the next time the effect is evaluated, the * result will be recomputed, and the cache will be refreshed. * * **When to Use** * * Use this function when you have an effect whose result needs to be cached for * a certain period, but you also want the option to refresh the cache manually * before the expiration time. * * This is useful when you need to ensure that the cached data remains valid for * a certain period but still want to invalidate it if the underlying data * changes or if you want to force a recomputation. * * **Example** * * ```ts * import { Effect, Console } from "effect" * * let i = 1 * const expensiveTask = Effect.promise<string>(() => { * console.log("expensive task...") * return new Promise((resolve) => { * setTimeout(() => { * resolve(`result ${i++}`) * }, 100) * }) * }) * * const program = Effect.gen(function* () { * const [cached, invalidate] = yield* Effect.cachedInvalidateWithTTL( * expensiveTask, * "1 hour" * ) * yield* cached.pipe(Effect.andThen(Console.log)) * yield* cached.pipe(Effect.andThen(Console.log)) * yield* invalidate * yield* cached.pipe(Effect.andThen(Console.log)) * }) * * Effect.runFork(program) * // Output: * // expensive task... * // result 1 * // result 1 * // expensive task... * // result 2 * ``` * * @see {@link cached} for a similar function that caches the result * indefinitely. * @see {@link cachedWithTTL} for a similar function that caches the result for * a specified duration but does not include an effect for manual invalidation. * * @since 2.0.0 * @category Caching */ export const cachedInvalidateWithTTL = circular.cachedInvalidateWithTTL; /** * Returns an effect that lazily computes a result and caches it for subsequent * evaluations. * * **Details** * * This function wraps an effect and ensures that its result is computed only * once. Once the result is computed, it is cached, meaning that subsequent * evaluations of the same effect will return the cached result without * re-executing the logic. * * **When to Use** * * Use this function when you have an expensive or time-consuming operation that * you want to avoid repeating. The first evaluation will compute the result, * and all following evaluations will immediately return the cached value, * improving performance and reducing unnecessary work. * * **Example** * * ```ts * import { Effect, Console } from "effect" * * let i = 1 * const expensiveTask = Effect.promise<string>(() => { * console.log("expensive task...") * return new Promise((resolve) => { * setTimeout(() => { * resolve(`result ${i++}`) * }, 100) * }) * }) * * const program = Effect.gen(function* () { * console.log("non-cached version:") * yield* expensiveTask.pipe(Effect.andThen(Console.log)) * yield* expensiveTask.pipe(Effect.andThen(Console.log)) * console.log("cached version:") * const cached = yield* Effect.cached(expensiveTask) * yield* cached.pipe(Effect.andThen(Console.log)) * yield* cached.pipe(Effect.andThen(Console.log)) * }) * * Effect.runFork(program) * // Output: * // non-cached version: * // expensive task... * // result 1 * // expensive task... * // result 2 * // cached version: * // expensive task... * // result 3 * // result 3 * ``` * * @see {@link cachedWithTTL} for a similar function that includes a * time-to-live duration for the cached value. * @see {@link cachedInvalidateWithTTL} for a similar function that includes an * additional effect for manually invalidating the cached value. * * @since 2.0.0 * @category Caching */ export const cached = effect.memoize; /** * Returns a memoized version of a function with effects, reusing results for * the same inputs. * * **Details** * * This function creates a memoized version of a given function that performs an * effect. Memoization ensures that once a result is computed for a specific * input, it is stored and reused for subsequent calls with the same input, * reducing the need to recompute the result. * * The function can optionally take an `Equivalence` parameter to * determine how inputs are compared for caching purposes. * * **When to Use** * * Use this function when you have a function that performs an effect and you * want to avoid recomputing the result for the same input multiple times. * * It's ideal for functions that produce deterministic results based on their * inputs, and you want to improve performance by caching the output. * * This is particularly useful in scenarios where the function involves * expensive calculations or operations that should be avoided after the first * execution with the same parameters. * * **Example** * * ```ts * import { Effect, Random } from "effect" * * const program = Effect.gen(function* () { * const randomNumber = (n: number) => Random.nextIntBetween(1, n) * console.log("non-memoized version:") * console.log(yield* randomNumber(10)) * console.log(yield* randomNumber(10)) * * console.log("memoized version:") * const memoized = yield* Effect.cachedFunction(randomNumber) * console.log(yield* memoized(10)) * console.log(yield* memoized(10)) * }) * * Effect.runFork(program) * // Example Output: * // non-memoized version: * // 2 * // 8 * // memoized version: * // 5 * // 5 * ``` * * @since 2.0.0 * @category Caching */ export const cachedFunction = circular.cachedFunction; /** * Returns an effect that executes only once, regardless of how many times it's * called. * * **Details** * * This function ensures that a specific effect is executed only a single time, * no matter how many times it is invoked. The result of the effect will be * cached, and subsequent calls to the effect will immediately return the cached * result without re-executing the original logic. * * **When to Use** * * Use this function when you need to perform a task only once, regardless of * how many times the effect is triggered. It's particularly useful when you * have initialization tasks, logging, or other one-time actions that should not * be repeated. This can help optimize performance and avoid redundant actions. * * **Example** * * ```ts * import { Effect, Console } from "effect" * * const program = Effect.gen(function* () { * const task1 = Console.log("task1") * yield* Effect.repeatN(task1, 2) * const task2 = yield* Effect.once(Console.log("task2")) * yield* Effect.repeatN(task2, 2) * }) * * Effect.runFork(program) * // Output: * // task1 * // task1 * // task1 * // task2 * ``` * * @since 2.0.0 * @category Caching */ export const once = effect.once; /** * Combines multiple effects into one, returning results based on the input * structure. * * **Details** * * Use this function when you need to run multiple effects and combine their * results into a single output. It supports tuples, iterables, structs, and * records, making it flexible for different input types. * * For instance, if the input is a tuple: * * ```ts skip-type-checking * // ┌─── a tuple of effects * // ▼ * Effect.all([effect1, effect2, ...]) * ``` * * the effects are executed sequentially, and the result is a new effect * containing the results as a tuple. The results in the tuple match the order * of the effects passed to `Effect.all`. * * **Concurrency** * * You can control the execution order (e.g., sequential vs. concurrent) using * the `concurrency` option. * * **Short-Circuiting Behavior** * * This function stops execution on the first error it encounters, this is * called "short-circuiting". If any effect in the collection fails, the * remaining effects will not run, and the error will be propagated. To change * this behavior, you can use the `mode` option, which allows all effects to run * and collect results as `Either` or `Option`. * * **The `mode` option** * * The `{ mode: "either" }` option changes the behavior of `Effect.all` to * ensure all effects run, even if some fail. Instead of stopping on the first * failure, this mode collects both successes and failures, returning an array * of `Either` instances where each result is either a `Right` (success) or a * `Left` (failure). * * Similarly, the `{ mode: "validate" }` option uses `Option` to indicate * success or failure. Each effect returns `None` for success and `Some` with * the error for failure. * * **Example** (Combining Effects in Tuples) * * ```ts * import { Effect, Console } from "effect" * * const tupleOfEffects = [ * Effect.succeed(42).pipe(Effect.tap(Console.log)), * Effect.succeed("Hello").pipe(Effect.tap(Console.log)) * ] as const * * // ┌─── Effect<[number, string], never, never> * // ▼ * const resultsAsTuple = Effect.all(tupleOfEffects) * * Effect.runPromise(resultsAsTuple).then(console.log) * // Output: * // 42 * // Hello * // [ 42, 'Hello' ] * ``` * * **Example** (Combining Effects in Iterables) * * ```ts * import { Effect, Console } from "effect" * * const iterableOfEffects: Iterable<Effect.Effect<number>> = [1, 2, 3].map( * (n) => Effect.succeed(n).pipe(Effect.tap(Console.log)) * ) * * // ┌─── Effect<number[], never, never> * // ▼ * const resultsAsArray = Effect.all(iterableOfEffects) * * Effect.runPromise(resultsAsArray).then(console.log) * // Output: * // 1 * // 2 * // 3 * // [ 1, 2, 3 ] * ``` * * **Example** (Combining Effects in Structs) * * ```ts * import { Effect, Console } from "effect" * * const structOfEffects = { * a: Effect.succeed(42).pipe(Effect.tap(Console.log)), * b: Effect.succeed("Hello").pipe(Effect.tap(Console.log)) * } * * // ┌─── Effect<{ a: number; b: string; }, never, never> * // ▼ * const resultsAsStruct = Effect.all(structOfEffects) * * Effect.runPromise(resultsAsStruct).then(console.log) * // Output: * // 42 * // Hello * // { a: 42, b: 'Hello' } * ``` * * **Example** (Combining Effects in Records) * * ```ts * import { Effect, Console } from "effect" * * const recordOfEffects: Record<string, Effect.Effect<number>> = { * key1: Effect.succeed(1).pipe(Effect.tap(Console.log)), * key2: Effect.succeed(2).pipe(Effect.tap(Console.log)) * } * * // ┌─── Effect<{ [x: string]: number; }, never, never> * // ▼ * const resultsAsRecord = Effect.all(recordOfEffects) * * Effect.runPromise(resultsAsRecord).then(console.log) * // Output: * // 1 * // 2 * // { key1: 1, key2: 2 } * ``` * * **Example** (Short-Circuiting Behavior) * * ```ts * import { Effect, Console } from "effect" * * const program = Effect.all([ * Effect.succeed("Task1").pipe(Effect.tap(Console.log)), * Effect.fail("Task2: Oh no!").pipe(Effect.tap(Console.log)), * // Won't execute due to earlier failure * Effect.succeed("Task3").pipe(Effect.tap(Console.log)) * ]) * * Effect.runPromiseExit(program).then(console.log) * // Output: * // Task1 * // { * // _id: 'Exit', * // _tag: 'Failure', * // cause: { _id: 'Cause', _tag: 'Fail', failure: 'Task2: Oh no!' } * // } * ``` * * **Example** (Collecting Results with `mode: "either"`) * * ```ts * import { Effect, Console } from "effect" * * const effects = [ * Effect.succeed("Task1").pipe(Effect.tap(Console.log)), * Effect.fail("Task2: Oh no!").pipe(Effect.tap(Console.log)), * Effect.succeed("Task3").pipe(Effect.tap(Console.log)) * ] * * const program = Effect.all(effects, { mode: "either" }) * * Effect.runPromiseExit(program).then(console.log) * // Output: * // Task1 * // Task3 * // { * // _id: 'Exit', * // _tag: 'Success', * // value: [ * // { _id: 'Either', _tag: 'Right', right: 'Task1' }, * // { _id: 'Either', _tag: 'Left', left: 'Task2: Oh no!' }, * // { _id: 'Either', _tag: 'Right', right: 'Task3' } * // ] * // } * ``` * * **Example** (Collecting Results with `mode: "validate"`) * * ```ts * import { Effect, Console } from "effect" * * const effects = [ * Effect.succeed("Task1").pipe(Effect.tap(Console.log)), * Effect.fail("Task2: Oh no!").pipe(Effect.tap(Console.log)), * Effect.succeed("Task3").pipe(Effect.tap(Console.log)) * ] * * const program = Effect.all(effects, { mode: "validate" }) * * Effect.runPromiseExit(program).then((result) => console.log("%o", result)) * // Output: * // Task1 * // Task3 * // { * // _id: 'Exit', * // _tag: 'Failure', * // cause: { * // _id: 'Cause', * // _tag: 'Fail', * // failure: [ * // { _id: 'Option', _tag: 'None' }, * // { _id: 'Option', _tag: 'Some', value: 'Task2: Oh no!' }, * // { _id: 'Option', _tag: 'None' } * // ] * // } * // } * ``` * * @see {@link forEach} for iterating over elements and applying an effect. * @see {@link allWith} for a data-last version of this function. * * @since 2.0.0 * @category Collecting */ export const all = fiberRuntime.all; /** * A data-last version of {@link all}, designed for use in pipelines. * * **When to Use** * * This function enables you to combine multiple effects and customize execution * options such as concurrency levels. This version is useful in functional * pipelines where you first define your data and then apply operations to it. * * **Example** * * ```ts * import { Effect, pipe } from "effect" * * const task1 = Effect.succeed(1).pipe( * Effect.delay("200 millis"), * Effect.tap(Effect.log("task1 done")) * ) * * const task2 = Effect.succeed("hello").pipe( * Effect.delay("100 millis"), * Effect.tap(Effect.log("task2 done")) * ) * * const program = pipe( * [task1, task2], * // Run both effects concurrently using the concurrent option * Effect.allWith({ concurrency: 2 }) * ) * * Effect.runPromise(program).then(console.log) * // Output: * // timestamp=... level=INFO fiber=#3 message="task2 done" * // timestamp=... level=INFO fiber=#2 message="task1 done" * // [ 1, 'hello' ] * ``` * * @since 2.0.0 * @category Collecting */ export const allWith = fiberRuntime.allWith; /** * Evaluates and runs each effect in the iterable, collecting only the * successful results while discarding failures. * * **Details** * * This function function processes an iterable of effects and runs each one. If * an effect is successful, its result is collected; if it fails, the result is * discarded. This ensures that only successful outcomes are kept. * * **Options** * * The function also allows you to customize how the effects are handled by * specifying options such as concurrency, batching, and how finalizers behave. * These options provide flexibility in running the effects concurrently or * adjusting other execution details. * * **Example** * * ```ts * import { Effect } from "effect" * * const tasks = [ * Effect.succeed(1), * Effect.fail("Error 1"), * Effect.succeed(2), * Effect.fail("Error 2") * ] * * const program = Effect.gen(function*() { * const successfulResults = yield* Effect.allSuccesses(tasks) * console.log(successfulResults) * }) * * Effect.runFork(program) * // Output: [1, 2] * * ``` * * @since 2.0.0 * @category Collecting */ export const allSuccesses = fiberRuntime.allSuccesses; /** * Drops elements until the effectful predicate returns `true`. * * **Details** * * This function processes a collection of elements and uses an effectful * predicate to determine when to stop dropping elements. It drops elements from * the beginning of the collection until the predicate returns `true`. * * The predicate is a function that takes an element and its index in the * collection and returns an effect that evaluates to a boolean. * * Once the predicate returns `true`, the remaining elements of the collection * are returned. * * **Note**: The first element for which the predicate returns `true` is also * dropped. * * **When to Use** * * This function allows you to conditionally skip over a part of the collection * based on some criteria defined in the predicate. * * **Example** * * ```ts * import { Effect } from "effect" * * const numbers = [1, 2, 3, 4, 5, 6] * const predicate = (n: number, i: number) => Effect.succeed(n > 3) * * const program = Effect.gen(function*() { * const result = yield* Effect.dropUntil(numbers, predicate) * console.log(result) * }) * * Effect.runFork(program) * // Output: [5, 6] * ``` * * @see {@link dropWhile} for a similar function that drops elements while the * predicate returns `true`. * * @since 2.0.0 * @category Collecting */ export const dropUntil = effect.dropUntil; /** * Drops elements as long as the predicate returns `true`. * * **Details** * * This function processes a collection of elements and uses a predicate to * decide whether to drop an element. * * The predicate is a function that takes an element and its index, and it * returns an effect that evaluates to a boolean. * * As long as the predicate returns `true`, elements will continue to be dropped * from the collection. * * Once the predicate returns `false`, the remaining elements are kept. * * **When to Use** * * This function allows you to discard elements from the start of a collection * based on a condition, and only keep the rest when the condition no longer * holds. * * **Example** * * ```ts * import { Effect } from "effect" * * const numbers = [1, 2, 3, 4, 5, 6] * const predicate = (n: number, i: number) => Effect.succeed(n <= 3) * * const program = Effect.gen(function*() { * const result = yield* Effect.dropWhile(numbers, predicate) * console.log(result) * }) * * Effect.runFork(program) * // Output: [4, 5, 6] * ``` * * @see {@link dropUntil} for a similar function that drops elements until the * predicate returns `true`. * * @since 2.0.0 * @category Collecting */ export const dropWhile = effect.dropWhile; /** * Takes elements from a collection until the effectful predicate returns * `true`. * * **Details** * * This function processes a collection of elements and uses an effectful * predicate to decide when to stop taking elements. The elements are taken from * the beginning of the collection until the predicate returns `true`. * * The predicate is a function that takes an element and its index in the * collection, and returns an effect that resolves to a boolean. * * Once the predicate returns `true`, the remaining elements of the collection * are discarded, and the function stops taking more elements. * * **Note**: The first element for which the predicate returns `true` is also * included in the result. * * **When to Use** * * Use this function when you want to conditionally take elements from a * collection based on a dynamic condition. For example, you may want to collect * numbers from a list until a certain threshold is reached, or gather items * until a specific condition is met. * * **Example** * * ```ts * import { Effect } from "effect" * * const numbers = [1, 2, 3, 4, 5, 6] * const predicate = (n: number, i: number) => Effect.succeed(n > 3) * * const program = Effect.gen(function*() { * const result = yield* Effect.takeUntil(numbers, predicate) * console.log(result) * }) * * Effect.runFork(program) * // Output: [ 1, 2, 3, 4 ] * ``` * * @see {@link takeWhile} for a similar function that takes elements while the * predicate returns `true`. * * @since 2.0.0 * @category Collecting */ export const takeUntil = effect.takeUntil; /** * Takes elements as long as the predicate returns `true`. * * **Details** * * This function processes a collection of elements and uses a predicate to * decide whether to take an element. * * The predicate is a function that takes an element and its index, and it * returns an effect that evaluates to a boolean. * * As long as the predicate returns `true`, elements will continue to be taken * from the collection. * * Once the predicate returns `false`, the remaining elements are discarded. * * **Example** * * ```ts * import { Effect } from "effect" * * const numbers = [1, 2, 3, 4, 5, 6] * const predicate = (n: number, i: number) => Effect.succeed(n <= 3) * * const program = Effect.gen(function*() { * const result = yield* Effect.takeWhile(numbers, predicate) * console.log(result) * }) * * Effect.runFork(program) * // Output: [1, 2, 3] * ``` * * @see {@link takeUntil} for a similar function that takes elements until the predicate returns `true`. * * @since 2.0.0 * @category Collecting */ export const takeWhile = effect.takeWhile; /** * Determines whether all elements of the iterable satisfy the effectful * predicate. * * **Details** * * This function checks whether every element in a given collection (an * iterable) satisfies a condition defined by an effectful predicate. * * The predicate is a function that takes an element and its index, and it * returns an effect that evaluates to a boolean. * * The function will process each element and return `true` if all elements * satisfy the predicate; otherwise, it returns `false`. * * **When to Use** * * This function is useful when you need to verify that all items in a * collection meet certain criteria, even when the evaluation of each item * involves effects, such as asynchronous checks or complex computations. * * **Example** * * ```ts * import { Effect } from "effect" * * const numbers = [2, 4, 6, 8] * const predicate = (n: number, i: number) => Effect.succeed(n % 2 === 0) * * const program = Effect.gen(function*() { * const allEven = yield* Effect.every(numbers, predicate) * console.log(allEven) * }) * * Effect.runFork(program) * // Output: true * ``` * * @see {@link exists} for a similar function that returns a boolean indicating * whether **any** element satisfies the predicate. * * @since 2.0.0 * @category Condition Checking */ export const every = effect.every; /** * Determines whether any element of the iterable satisfies the effectual * predicate. * * **Details** * * This function checks whether any element in a given collection (an iterable) * satisfies a condition defined by an effectful predicate. * * The predicate is a function that takes an element and its index, and it * returns an effect that evaluates to a boolean. * * The function will process each element, and if any element satisfies the * predicate (returns `true`), the function will immediately return `true`. * * If none of the elements satisfy the condition, it will return `false`. * * **When to Use** * * This function allows you to quickly check for a condition in a collection * without having to manually iterate over it. * * **Example** * * ```ts * import { Effect } from "effect" * * const numbers = [1, 2, 3, 4] * const predicate = (n: number, i: number) => Effect.succeed(n > 2) * * const program = Effect.gen(function*() { * const hasLargeNumber = yield* Effect.exists(numbers, predicate) * console.log(hasLargeNumber) * }) * * Effect.runFork(program) * // Output: true * ``` * * @see {@link every} for a similar function that checks if **all** elements * satisfy the predicate. * * @since 2.0.0 * @category Condition Checking */ export const exists = fiberRuntime.exists; /** * Filters an iterable using the specified effectful predicate. * * **Details** * * This function filters a collection (an iterable) by applying an effectful * predicate. * * The predicate is a function that takes an element and its index, and it * returns an effect that evaluates to a boolean. * * The function processes each element in the collection and keeps only those * that satisfy the condition defined by the predicate. * * **Options** * * You can also adjust the behavior with options such as concurrency, batching, * or whether to negate the condition. * * **When to Use** * * This function allows you to selectively keep or remove elements based on a * condition that may involve asynchronous or side-effect-causing operations. * * **Example** * * ```ts * import { Effect } from "effect" * * const numbers = [1, 2, 3, 4, 5] * const predicate = (n: number, i: number) => Effect.succeed(n % 2 === 0) * * const program = Effect.gen(function*() { * const result = yield* Effect.filter(numbers, predicate) * console.log(result) * }) * * Effect.runFork(program) * // Output: [2, 4] * ``` * * @since 2.0.0 * @category Filtering */ export const filter = fiberRuntime.filter; /** * Filters and maps elements sequentially in one operation. * * This function processes each element one by one. It applies a function that * returns an `Option` to each element. If the function returns `Some`, the * element is kept; if it returns `None`, the element is removed. The operation * is done sequentially for each element. * * **Example** * * ```ts * import { Console, Effect, Option } from "effect" * * const task = (n: number) => * Effect.succeed(n).pipe( * Effect.delay(1000 - (n * 100)), * Effect.tap(Console.log(`task${n} done`)) * ) * * const program = Effect.filterMap( * [task(1), task(2), task(3), task(4)], * (n) => n % 2 === 0 ? Option.some(n) : Option.none() * ) * * Effect.runPromise(program).then(console.log) * // Output: * // task1 done * // task2 done * // task3 done * // task4 done * // [ 2, 4 ] * ``` * * @since 2.0.0 * @category Filtering */ export const filterMap = effect.filterMap; /** * Returns the first element that satisfies the effectful predicate. * * **Details** * * This function processes a collection of elements and applies an effectful * predicate to each element. * * The predicate is a function that takes an element and its index in the * collection, and it returns an effect that evaluates to a boolean. * * The function stops as soon as it finds the first element for which the * predicate returns `true` and returns that element wrapped in an `Option`. * * If no element satisfies the predicate, the result will be `None`. * * **When to Use** * * This function allows you to efficiently find an element that meets a specific * condition, even when the evaluation involves effects like asynchronous * operations or side effects. * * **Example** * * ```ts * import { Effect } from "effect" * * const numbers = [1, 2, 3, 4, 5] * const predicate = (n: number, i: number) => Effect.succeed(n > 3) * * const program = Effect.gen(function*() { * const result = yield* Effect.findFirst(numbers, predicate) * console.log(result) * }) * * Effect.runFork(program) * // Output: { _id: 'Option', _tag: 'Some', value: 4 } * ``` * * @since 2.0.0 * @category Collecting */ export const findFirst = effect.findFirst; /** * Executes an effectful operation for each element in an `Iterable`. * * **Details** * * This function applies a provided operation to each element in the iterable, * producing a new effect that returns an array of results. * * If any effect fails, the iteration stops immediately (short-circuiting), and * the error is propagated. * * **Concurrency** * * The `concurrency` option controls how many operations are performed * concurrently. By default, the operations are performed sequentially. * * **Discarding Results** * * If the `discard` option is set to `true`, the intermediate results are not * collected, and the final result of the operation is `void`. * * **Example** (Applying Effects to Iterable Elements) * * ```ts * import { Effect, Console } from "effect" * * const result = Effect.forEach([1, 2, 3, 4, 5], (n, index) => * Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2)) * ) * * Effect.runPromise(result).then(console.log) * // Output: * // Currently at index 0 * // Currently at index 1 * // Currently at index 2 * // Currently at index 3 * // Currently at index 4 * // [ 2, 4, 6, 8, 10 ] * ``` * * **Example** (Discarding Results) * * ```ts * import { Effect, Console } from "effect" * * // Apply effects but discard the results * const result = Effect.forEach( * [1, 2, 3, 4, 5], * (n, index) => * Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2)), * { discard: true } * ) * * Effect.runPromise(result).then(console.log) * // Output: * // Currently at index 0 * // Currently at index 1 * // Currently at index 2 * // Currently at index 3 * // Currently at index 4 * // undefined * ``` * * @see {@link all} for combining multiple effects into one. * * @since 2.0.0 * @category Looping */ export const forEach = fiberRuntime.forEach; /** * Returns the first element of the iterable if the collection is non-empty, or * fails with the error `NoSuchElementException` if the collection is empty. * * **When to Use** * * This function is useful when you need to retrieve the first item from a * collection and want to handle the case where the collection might be empty * without causing an unhandled exception. * * **Example** * * ```ts * import { Effect } from "effect" * * // Simulate an async operation * const fetchNumbers = Effect.succeed([1, 2, 3]).pipe(Effect.delay("100 millis")) * * const program = Effect.gen(function*() { * const firstElement = yield* Effect.head(fetchNumbers) * console.log(firstElement) * }) * * Effect.runFork(program) * // Output: 1 * ``` * * @since 2.0.0 * @category Collecting */ export const head = effect.head; /** * Merges an `Iterable<Effect<A, E, R>>` to a single effect. * * **Details** * * This function takes an iterable of effects and combines them into a single * effect. It does this by iterating over each effect in the collection and * applying a function that accumulates results into a "zero" value, which * starts with an initial value and is updated with each effect's success. * * The provided function `f` is called for each element in the iterable, * allowing you to specify how to combine the results. * * **Options** * * The function also allows you to customize how the effects are handled by * specifying options such as concurrency, batching, and how finalizers behave. * These options provide flexibility in running the effects concurrently or * adjusting other execution details. * * **Example** * * ```ts * import { Effect } from "effect" * * const numbers = [Effect.succeed(1), Effect.succeed(2), Effect.succeed(3)] * const add = (sum: number, value: number, i: number) => sum + value * const zero = 0 * * const program = Effect.gen(function*() { * const total = yield* Effect.mergeAll(numbers, zero, add) * console.log(total) * }) * * Effect.runFork(program) * // Output: 6 * ``` * * @since 2.0.0 * @category Collecting */ export const mergeAll = fiberRuntime.mergeAll; /** * Processes an iterable and applies an effectful function to each element, * categorizing the results into successes and failures. * * **Details** * * This function processes each element in the provided iterable by applying an * effectful function to it. The results are then categorized into two separate * lists: one for failures and another for successes. This separation allows you * to handle the two categories differently. Failures are collected in a list * without interrupting the processing of the remaining elements, so the * operation continues even if some elements fail. This is particularly useful * when you need to handle both successful and failed results separately, * without stopping the entire process on encountering a failure. * * **When to Use** * * Use this function when you want to process a collection of items and handle * errors or failures without interrupting the processing of other items. It's * useful when you need to distinguish between successful and failed results and * process them separately, for example, when logging errors while continuing to * work with valid data. The function ensures that failures are captured, while * successes are processed normally. * * **Example** * * ```ts * import { Effect } from "effect" * * // ┌─── Effect<[string[], number[]], never, never> * // ▼ * const program = Effect.partition([0, 1, 2, 3, 4], (n) => { * if (n % 2 === 0) { * return Effect.succeed(n) * } else { * return Effect.fail(`${n} is not even`) * } * }) * * Effect.runPromise(program).then(console.log, console.error) * // Output: * // [ [ '1 is not even', '3 is not even' ], [ 0, 2, 4 ] ] * ``` * * @see {@link validateAll} for a function that either collects all failures or all successes. * @see {@link validateFirst} for a function that stops at the first success. * * @since 2.0.0 * @category Error Accumulation */ export const partition = fiberRuntime.partition; /** * Reduces an `Iterable<A>` using an effectual function `f`, working * sequentially from left to right. * * **Details** * * This function takes an iterable and applies a function `f` to each element in * the iterable. The function works sequentially, starting with an initial value * `zero` and then combining it with each element in the collection. The * provided function `f` is called for each element in the iterable, allowing * you to accumulate a result based on the current value and the element being * processed. * * **When to Use** * * The function is often used for operations like summing a collection of * numbers or combining results from multiple tasks. It ensures that operations * are performed one after the other, maintaining the order of the elements. * * **Example** * * ```ts * import { Console, Effect } from "effect" * * const processOrder = (id: number) => * Effect.succeed({ id, price: 100 * id }) * .pipe(Effect.tap(() => Console.log(`Order ${id} processed`)), Effect.delay(500 - (id * 100))) * * const program = Effect.reduce( * [1, 2, 3, 4], * 0, * (acc, id, i) => * processOrder(id) * .pipe(Effect.map((order) => acc + order.price)) * ) * * Effect.runPromise(program).then(console.log) * // Output: * // Order 1 processed * // Order 2 processed * // Order 3 processed * // Order 4 processed * // 1000 * ``` * * @see {@link reduceWhile} for a similar function that stops the process based on a predicate. * @see {@link reduceRight} for a similar function that works from right to left. * * @since 2.0.0 * @category Collecting */ export const reduce = effect.reduce; /** * Reduces an `Iterable<A>` using an effectual function `body`, working * sequentially from left to right, stopping the process early when the * predicate `while` is not satisfied. * * **Details** * * This function processes a collection of elements, applying a function `body` * to reduce them to a single value, starting from the first element. It checks * the value of the accumulator against a predicate (`while`). If at any point * the predicate returns `false`, the reduction stops, and the accumulated * result is returned. * * **When to Use** * * Use this function when you need to reduce a collection of elements, but only * continue the process as long as a certain condition holds true. For example, * if you want to sum values in a list but stop as soon as the sum exceeds a * certain threshold, you can use this function. * * **Example** * * ```ts * import { Console, Effect } from "effect" * * const processOrder = (id: number) => * Effect.succeed({ id, price: 100 * id }) * .pipe(Effect.tap(() => Console.log(`Order ${id} processed`)), Effect.delay(500 - (id * 100))) * * const program = Effect.reduceWhile( * [1, 2, 3, 4], * 0, * { * body: (acc, id, i) => * processOrder(id) * .pipe(Effect.map((order) => acc + order.price)), * while: (acc) => acc < 500 * } * ) * * Effect.runPromise(program).then(console.log) * // Output: * // Order 1 processed * // Order 2 processed * // Order 3 processed * // 600 * ``` * * @since 2.0.0 * @category Collecting */ export const reduceWhile = effect.reduceWhile; /** * Reduces an `Iterable<A>` using an effectual function `f`, working * sequentially from right to left. * * **Details** * * This function takes an iterable and applies a function `f` to each element in * the iterable. The function works sequentially, starting with an initial value * `zero` and then combining it with each element in the collection. The * provided function `f` is called for each element in the iterable, allowing * you to accumulate a result based on the current value and the element being * processed. * * **When to Use** * * The function is often used for operations like summing a collection of * numbers or combining results from multiple tasks. It ensures that operations * are performed one after the other, maintaining the order of the elements. * * **Example** * * ```ts * import { Console, Effect } from "effect" * * const processOrder = (id: number) => * Effect.succeed({ id, price: 100 * id }) * .pipe(Effect.tap(() => Console.log(`Order ${id} processed`)), Effect.delay(500 - (id * 100))) * * const program = Effect.reduceRight( * [1, 2, 3, 4], * 0, * (id, acc, i) => * processOrder(id) * .pipe(Effect.map((order) => acc + order.price)) * ) * * Effect.runPromise(program).then(console.log) * // Output: * // Order 4 processed * // Order 3 processed * // Order 2 processed * // Order 1 processed * // 1000 * ``` * * @see {@link reduce} for a similar function that works from left to right. * * @since 2.0.0 * @category Collecting */ export const reduceRight = effect.reduceRight; /** * Reduces an `Iterable<Effect<A, E, R>>` to a single effect. * * **Details** * * This function processes a collection of effects and combines them into one * single effect. It starts with an initial effect (`zero`) and applies a * function `f` to each element in the collection. * * **Options** * * The function also allows you to customize how the effects are handled by * specifying options such as concurrency, batching, and how finalizers behave. * These options provide flexibility in running the effects concurrently or * adjusting other execution details. * * **Example** * * ```ts * import { Console, Effect } from "effect" * * const processOrder = (id: number) => * Effect.succeed({ id, price: 100 * id }) * .pipe(Effect.tap(() => Console.log(`Order ${id} processed`)), Effect.delay(500 - (id * 100))) * * const program = Effect.reduceEffect( * [processOrder(1), processOrder(2), processOrder(3), processOrder(4)], * Effect.succeed(0), * (acc, order, i) => acc + order.price * ) * * Effect.runPromise(program).then(console.log) * // Output: * // Order 1 processed * // Order 2 processed * // Order 3 processed * // Order 4 processed * // 1000 * ``` * * @since 2.0.0 * @category Collecting */ export const reduceEffect = fiberRuntime.reduceEffect; /** * Replicates the given effect `n` times. * * **Details** * * This function takes an effect and replicates it a specified number of times * (`n`). The result is an array of `n` effects, each of which is identical to * the original effect. * * **Example** * * ```ts * import { Console, Effect } from "effect" * * const task = Effect.succeed("Hello, World!").pipe( * Effect.tap(Console.log) * ) * * const program = Effect.gen(function*() { * // Replicate the task 3 times * const tasks = Effect.replicate(task, 3) * for (const t of tasks) { * // Run each task * yield* t * } * }) * * Effect.runFork(program) * // Output: * // Hello, World! * // Hello, World! * // Hello, World! * ``` * * @since 2.0.0 */ export const replicate = fiberRuntime.replicate; /** * Performs this effect the specified number of times and collects the results. * * **Details** * * This function repeats an effect multiple times and collects the results into * an array. You specify how many times to execute the effect, and it runs that * many times, either in sequence or concurrently depending on the provided * options. * * **Options** * * If the `discard` option is set to `true`, the intermediate results are not * collected, and the final result of the operation is `void`. * * The function also allows you to customize how the effects are handled by * specifying options such as concurrency, batching, and how finalizers behave. * These options provide flexibility in running the effects concurrently or * adjusting other execution details. * * **Example** * * ```ts * import { Console, Effect } from "effect" * * let counter = 0 * * const task = Effect.sync(() => ++counter).pipe( * Effect.tap(() => Console.log(`Task completed`)) * ) * * const program = Effect.gen(function*() { * // Replicate the task 3 times and collect the results * const results = yield* Effect.replicateEffect(task, 3) * yield* Console.log(`Results: ${results.join(", ")}`) * }) * * Effect.runFork(program) * // Output: * // Task completed * // Task completed * // Task completed * // Results: 1, 2, 3 * ``` * * @since 2.0.0 * @category Collecting */ export const replicateEffect = fiberRuntime.replicateEffect; /** * Applies an effectful operation to each element in a collection while * collecting both successes and failures. * * **Details** * * This function allows you to apply an effectful operation to every item in a * collection. * * Unlike {@link forEach}, which would stop at the first error, this function * continues processing all elements, accumulating both successes and failures. * * **When to Use** * * Use this function when you want to process every item in a collection, even * if some items fail. This is particularly useful when you need to perform * operations on all elements without halting due to an error. * * Keep in mind that if there are any failures, **all successes will be lost**, * so this function is not suitable when you need to keep the successful results * in case of errors. * * **Example** * * ```ts * import { Effect, Console } from "effect" * * // ┌─── Effect<number[], [string, ...string[]], never> * // ▼ * const program = Effect.validateAll([1, 2, 3, 4, 5], (n) => { * if (n < 4) { * return Console.log(`item ${n}`).pipe(Effect.as(n)) * } else { * return Effect.fail(`${n} is not less that 4`) * } * }) * * Effect.runPromiseExit(program).then(console.log) * // Output: * // item 1 * // item 2 * // item 3 * // { * // _id: 'Exit', * // _tag: 'Failure', * // cause: { * // _id: 'Cause', * // _tag: 'Fail', * // failure: [ '4 is not less that 4', '5 is not less that 4' ] * // } * // } * ``` * * @see {@link forEach} for a similar function that stops at the first error. * @see {@link partition} when you need to separate successes and failures * instead of losing successes with errors. * * @since 2.0.0 * @category Error Accumulation */ export const validateAll = fiberRuntime.validateAll; /** * This function is similar to {@link validateAll} but with a key difference: it * returns the first successful result or all errors if none of the operations * succeed. * * **Details** * * This function processes a collection of elements and applies an effectful * operation to each. Unlike {@link validateAll}, which accumulates both * successes and failures, `Effect.validateFirst` stops and returns the first * success it encounters. If no success occurs, it returns all accumulated * errors. This can be useful when you are interested in the first successful * result and want to avoid processing further once a valid result is found. * * **Example** * * ```ts * import { Effect, Console } from "effect" * * // ┌─── Effect<number, string[], never> * // ▼ * const program = Effect.validateFirst([1, 2, 3, 4, 5], (n) => { * if (n < 4) { * return Effect.fail(`${n} is not less that 4`) * } else { * return Console.log(`item ${n}`).pipe(Effect.as(n)) * } * }) * * Effect.runPromise(program).then(console.log, console.error) * // Output: * // item 4 * // 4 * ``` * * @see {@link validateAll} for a similar function that accumulates all results. * @see {@link firstSuccessOf} for a similar function that processes multiple * effects and returns the first successful one or the last error. * * @since 2.0.0 * @category Error Accumulation */ export const validateFirst = fiberRuntime.validateFirst; /** * Creates an `Effect` from a callback-based asynchronous function. * * **Details** * * The `resume` function: * - Must be called exactly once. Any additional calls will be ignored. * - Can return an optional `Effect` that will be run if the `Fiber` executing * this `Effect` is interrupted. This can be useful in scenarios where you * need to handle resource cleanup if the operation is interrupted. * - Can receive an `AbortSignal` to handle interruption if needed. * * The `FiberId` of the fiber that may complete the async callback may also be * specified using the `blockingOn` argument. This is called the "blocking * fiber" because it suspends the fiber executing the `async` effect (i.e. * semantically blocks the fiber from making progress). Specifying this fiber id * in cases where it is known will improve diagnostics, but not affect the * behavior of the returned effect. * * **When to Use** * * Use `Effect.async` when dealing with APIs that use callback-style instead of * `async/await` or `Promise`.