/** * A lightweight alternative to the `Effect` data type, with a subset of the functionality. * * @since 3.4.0 * @experimental */ import * as Arr from "./Array.js" import type { Channel } from "./Channel.js" import * as Context from "./Context.js" import type { Effect, EffectUnify, EffectUnifyIgnore } from "./Effect.js" import * as Effectable from "./Effectable.js" import * as Either from "./Either.js" import * as Equal from "./Equal.js" import type { LazyArg } from "./Function.js" import { constTrue, constVoid, dual, identity } from "./Function.js" import { globalValue } from "./GlobalValue.js" import * as Hash from "./Hash.js" import type { TypeLambda } from "./HKT.js" import type { Inspectable } from "./Inspectable.js" import { format, NodeInspectSymbol, toStringUnknown } from "./Inspectable.js" import * as InternalContext from "./internal/context.js" import * as doNotation from "./internal/doNotation.js" import { StructuralPrototype } from "./internal/effectable.js" import * as Option from "./Option.js" import type { Pipeable } from "./Pipeable.js" import { pipeArguments } from "./Pipeable.js" import type { Predicate, Refinement } from "./Predicate.js" import { hasProperty, isIterable, isTagged } from "./Predicate.js" import type { Sink } from "./Sink.js" import type { Stream } from "./Stream.js" import type { Concurrency, Covariant, Equals, NoExcessProperties, NotFunction, Simplify } from "./Types.js" import type * as Unify from "./Unify.js" import { SingleShotGen, YieldWrap, yieldWrapGet } from "./Utils.js" /** * @since 3.4.0 * @experimental * @category type ids */ export const TypeId: unique symbol = Symbol.for("effect/Micro") /** * @since 3.4.0 * @experimental * @category type ids */ export type TypeId = typeof TypeId /** * @since 3.4.0 * @experimental * @category MicroExit */ export const MicroExitTypeId: unique symbol = Symbol.for( "effect/Micro/MicroExit" ) /** * @since 3.4.0 * @experimental * @category MicroExit */ export type MicroExitTypeId = typeof TypeId /** * A lightweight alternative to the `Effect` data type, with a subset of the functionality. * * @since 3.4.0 * @experimental * @category models */ export interface Micro extends Effect { readonly [TypeId]: Micro.Variance [Symbol.iterator](): MicroIterator> [Unify.typeSymbol]?: unknown [Unify.unifySymbol]?: MicroUnify [Unify.ignoreSymbol]?: MicroUnifyIgnore } /** * @category models * @since 3.4.3 */ export interface MicroUnify extends EffectUnify { Micro?: () => A[Unify.typeSymbol] extends Micro | infer _ ? Micro : never } /** * @category models * @since 3.4.3 */ export interface MicroUnifyIgnore extends EffectUnifyIgnore { Effect?: true } /** * @category type lambdas * @since 3.4.1 */ export interface MicroTypeLambda extends TypeLambda { readonly type: Micro } /** * @since 3.4.0 * @experimental */ export declare namespace Micro { /** * @since 3.4.0 * @experimental */ export interface Variance { _A: Covariant _E: Covariant _R: Covariant } /** * @since 3.4.0 * @experimental */ export type Success = T extends Micro ? _A : never /** * @since 3.4.0 * @experimental */ export type Error = T extends Micro ? _E : never /** * @since 3.4.0 * @experimental */ export type Context = T extends Micro ? _R : never } /** * @since 3.4.0 * @experimental * @category guards */ export const isMicro = (u: unknown): u is Micro => typeof u === "object" && u !== null && TypeId in u /** * @since 3.4.0 * @experimental * @category models */ export interface MicroIterator> { next(...args: ReadonlyArray): IteratorResult, Micro.Success> } // ---------------------------------------------------------------------------- // MicroCause // ---------------------------------------------------------------------------- /** * @since 3.4.6 * @experimental * @category MicroCause */ export const MicroCauseTypeId = Symbol.for("effect/Micro/MicroCause") /** * @since 3.4.6 * @experimental * @category MicroCause */ export type MicroCauseTypeId = typeof MicroCauseTypeId /** * A `MicroCause` is a data type that represents the different ways a `Micro` can fail. * * **Details** * * `MicroCause` comes in three forms: * * - `Die`: Indicates an unforeseen defect that wasn't planned for in the system's logic. * - `Fail`: Covers anticipated errors that are recognized and typically handled within the application. * - `Interrupt`: Signifies an operation that has been purposefully stopped. * * @since 3.4.6 * @experimental * @category MicroCause */ export type MicroCause = | MicroCause.Die | MicroCause.Fail | MicroCause.Interrupt /** * @since 3.6.6 * @experimental * @category guards */ export const isMicroCause = (self: unknown): self is MicroCause => hasProperty(self, MicroCauseTypeId) /** * @since 3.4.6 * @experimental * @category MicroCause */ export declare namespace MicroCause { /** * @since 3.4.6 * @experimental */ export type Error = T extends MicroCause.Fail ? E : never /** * @since 3.4.0 * @experimental */ export interface Proto extends Pipeable, globalThis.Error { readonly [MicroCauseTypeId]: { _E: Covariant } readonly _tag: Tag readonly traces: ReadonlyArray } /** * @since 3.4.6 * @experimental * @category MicroCause */ export interface Die extends Proto<"Die", never> { readonly defect: unknown } /** * @since 3.4.6 * @experimental * @category MicroCause */ export interface Fail extends Proto<"Fail", E> { readonly error: E } /** * @since 3.4.6 * @experimental * @category MicroCause */ export interface Interrupt extends Proto<"Interrupt", never> {} } const microCauseVariance = { _E: identity } abstract class MicroCauseImpl extends globalThis.Error implements MicroCause.Proto { readonly [MicroCauseTypeId]: { _E: Covariant } constructor( readonly _tag: Tag, originalError: unknown, readonly traces: ReadonlyArray ) { const causeName = `MicroCause.${_tag}` let name: string let message: string let stack: string if (originalError instanceof globalThis.Error) { name = `(${causeName}) ${originalError.name}` message = originalError.message as string const messageLines = message.split("\n").length stack = originalError.stack ? `(${causeName}) ${ originalError.stack .split("\n") .slice(0, messageLines + 3) .join("\n") }` : `${name}: ${message}` } else { name = causeName message = toStringUnknown(originalError, 0) stack = `${name}: ${message}` } if (traces.length > 0) { stack += `\n ${traces.join("\n ")}` } super(message) this[MicroCauseTypeId] = microCauseVariance this.name = name this.stack = stack } pipe() { return pipeArguments(this, arguments) } toString() { return this.stack } [NodeInspectSymbol]() { return this.stack } } class Fail extends MicroCauseImpl<"Fail", E> implements MicroCause.Fail { constructor( readonly error: E, traces: ReadonlyArray = [] ) { super("Fail", error, traces) } } /** * @since 3.4.6 * @experimental * @category MicroCause */ export const causeFail = ( error: E, traces: ReadonlyArray = [] ): MicroCause => new Fail(error, traces) class Die extends MicroCauseImpl<"Die", never> implements MicroCause.Die { constructor( readonly defect: unknown, traces: ReadonlyArray = [] ) { super("Die", defect, traces) } } /** * @since 3.4.6 * @experimental * @category MicroCause */ export const causeDie = ( defect: unknown, traces: ReadonlyArray = [] ): MicroCause => new Die(defect, traces) class Interrupt extends MicroCauseImpl<"Interrupt", never> implements MicroCause.Interrupt { constructor(traces: ReadonlyArray = []) { super("Interrupt", "interrupted", traces) } } /** * @since 3.4.6 * @experimental * @category MicroCause */ export const causeInterrupt = ( traces: ReadonlyArray = [] ): MicroCause => new Interrupt(traces) /** * @since 3.4.6 * @experimental * @category MicroCause */ export const causeIsFail = ( self: MicroCause ): self is MicroCause.Fail => self._tag === "Fail" /** * @since 3.4.6 * @experimental * @category MicroCause */ export const causeIsDie = (self: MicroCause): self is MicroCause.Die => self._tag === "Die" /** * @since 3.4.6 * @experimental * @category MicroCause */ export const causeIsInterrupt = ( self: MicroCause ): self is MicroCause.Interrupt => self._tag === "Interrupt" /** * @since 3.4.6 * @experimental * @category MicroCause */ export const causeSquash = (self: MicroCause): unknown => self._tag === "Fail" ? self.error : self._tag === "Die" ? self.defect : self /** * @since 3.4.6 * @experimental * @category MicroCause */ export const causeWithTrace: { /** * @since 3.4.6 * @experimental * @category MicroCause */ (trace: string): (self: MicroCause) => MicroCause /** * @since 3.4.6 * @experimental * @category MicroCause */ (self: MicroCause, trace: string): MicroCause } = dual(2, (self: MicroCause, trace: string): MicroCause => { const traces = [...self.traces, trace] switch (self._tag) { case "Die": return causeDie(self.defect, traces) case "Interrupt": return causeInterrupt(traces) case "Fail": return causeFail(self.error, traces) } }) // ---------------------------------------------------------------------------- // MicroFiber // ---------------------------------------------------------------------------- /** * @since 3.11.0 * @experimental * @category MicroFiber */ export const MicroFiberTypeId = Symbol.for("effect/Micro/MicroFiber") /** * @since 3.11.0 * @experimental * @category MicroFiber */ export type MicroFiberTypeId = typeof MicroFiberTypeId /** * @since 3.11.0 * @experimental * @category MicroFiber */ export interface MicroFiber { readonly [MicroFiberTypeId]: MicroFiber.Variance readonly currentOpCount: number readonly getRef: (ref: Context.Reference) => A readonly context: Context.Context readonly addObserver: (cb: (exit: MicroExit) => void) => () => void readonly unsafeInterrupt: () => void readonly unsafePoll: () => MicroExit | undefined } /** * @since 3.11.0 * @experimental * @category MicroFiber */ export declare namespace MicroFiber { /** * @since 3.11.0 * @experimental * @category MicroFiber */ export interface Variance { readonly _A: Covariant readonly _E: Covariant } } const fiberVariance = { _A: identity, _E: identity } class MicroFiberImpl implements MicroFiber { readonly [MicroFiberTypeId]: MicroFiber.Variance readonly _stack: Array = [] readonly _observers: Array<(exit: MicroExit) => void> = [] _exit: MicroExit | undefined public _children: Set> | undefined public currentOpCount = 0 constructor( public context: Context.Context, public interruptible = true ) { this[MicroFiberTypeId] = fiberVariance } getRef(ref: Context.Reference): A { return InternalContext.unsafeGetReference(this.context, ref) } addObserver(cb: (exit: MicroExit) => void): () => void { if (this._exit) { cb(this._exit) return constVoid } this._observers.push(cb) return () => { const index = this._observers.indexOf(cb) if (index >= 0) { this._observers.splice(index, 1) } } } _interrupted = false unsafeInterrupt(): void { if (this._exit) { return } this._interrupted = true if (this.interruptible) { this.evaluate(exitInterrupt as any) } } unsafePoll(): MicroExit | undefined { return this._exit } evaluate(effect: Primitive): void { if (this._exit) { return } else if (this._yielded !== undefined) { const yielded = this._yielded as () => void this._yielded = undefined yielded() } const exit = this.runLoop(effect) if (exit === Yield) { return } // the interruptChildren middlware is added in Micro.fork, so it can be // tree-shaken if not used const interruptChildren = fiberMiddleware.interruptChildren && fiberMiddleware.interruptChildren(this) if (interruptChildren !== undefined) { return this.evaluate(flatMap(interruptChildren, () => exit) as any) } this._exit = exit for (let i = 0; i < this._observers.length; i++) { this._observers[i](exit) } this._observers.length = 0 } runLoop(effect: Primitive): MicroExit | Yield { let yielding = false let current: Primitive | Yield = effect this.currentOpCount = 0 try { while (true) { this.currentOpCount++ if (!yielding && this.getRef(CurrentScheduler).shouldYield(this as any)) { yielding = true const prev = current current = flatMap(yieldNow, () => prev as any) as any } current = (current as any)[evaluate](this) if (current === Yield) { const yielded = this._yielded! if (MicroExitTypeId in yielded) { this._yielded = undefined return yielded } return Yield } } } catch (error) { if (!hasProperty(current, evaluate)) { return exitDie(`MicroFiber.runLoop: Not a valid effect: ${String(current)}`) } return exitDie(error) } } getCont( symbol: S ): Primitive & Record Primitive> | undefined { while (true) { const op = this._stack.pop() if (!op) return undefined const cont = op[ensureCont] && op[ensureCont](this) if (cont) return { [symbol]: cont } as any if (op[symbol]) return op as any } } // cancel the yielded operation, or for the yielded exit value _yielded: MicroExit | (() => void) | undefined = undefined yieldWith(value: MicroExit | (() => void)): Yield { this._yielded = value return Yield } children(): Set> { return this._children ??= new Set() } } const fiberMiddleware = globalValue("effect/Micro/fiberMiddleware", () => ({ interruptChildren: undefined as ((fiber: MicroFiberImpl) => Micro | undefined) | undefined })) const fiberInterruptChildren = (fiber: MicroFiberImpl) => { if (fiber._children === undefined || fiber._children.size === 0) { return undefined } return fiberInterruptAll(fiber._children) } /** * @since 3.11.0 * @experimental * @category MicroFiber */ export const fiberAwait = (self: MicroFiber): Micro> => async((resume) => sync(self.addObserver((exit) => resume(succeed(exit))))) /** * @since 3.11.2 * @experimental * @category MicroFiber */ export const fiberJoin = (self: MicroFiber): Micro => flatten(fiberAwait(self)) /** * @since 3.11.0 * @experimental * @category MicroFiber */ export const fiberInterrupt = (self: MicroFiber): Micro => suspend(() => { self.unsafeInterrupt() return asVoid(fiberAwait(self)) }) /** * @since 3.11.0 * @experimental * @category MicroFiber */ export const fiberInterruptAll = >>(fibers: A): Micro => suspend(() => { for (const fiber of fibers) fiber.unsafeInterrupt() const iter = fibers[Symbol.iterator]() const wait: Micro = suspend(() => { let result = iter.next() while (!result.done) { if (result.value.unsafePoll()) { result = iter.next() continue } const fiber = result.value return async((resume) => { fiber.addObserver((_) => { resume(wait) }) }) } return exitVoid }) return wait }) const identifier = Symbol.for("effect/Micro/identifier") type identifier = typeof identifier const args = Symbol.for("effect/Micro/args") type args = typeof args const evaluate = Symbol.for("effect/Micro/evaluate") type evaluate = typeof evaluate const successCont = Symbol.for("effect/Micro/successCont") type successCont = typeof successCont const failureCont = Symbol.for("effect/Micro/failureCont") type failureCont = typeof failureCont const ensureCont = Symbol.for("effect/Micro/ensureCont") type ensureCont = typeof ensureCont const Yield = Symbol.for("effect/Micro/Yield") type Yield = typeof Yield interface Primitive { readonly [identifier]: string readonly [successCont]: ((value: unknown, fiber: MicroFiberImpl) => Primitive | Yield) | undefined readonly [failureCont]: | ((cause: MicroCause, fiber: MicroFiberImpl) => Primitive | Yield) | undefined readonly [ensureCont]: | ((fiber: MicroFiberImpl) => | ((value: unknown, fiber: MicroFiberImpl) => Primitive | Yield) | undefined) | undefined [evaluate](fiber: MicroFiberImpl): Primitive | Yield } const microVariance = { _A: identity, _E: identity, _R: identity } const MicroProto = { ...Effectable.EffectPrototype, _op: "Micro", [TypeId]: microVariance, pipe() { return pipeArguments(this, arguments) }, [Symbol.iterator]() { return new SingleShotGen(new YieldWrap(this)) as any }, toJSON(this: Primitive) { return { _id: "Micro", op: this[identifier], ...(args in this ? { args: this[args] } : undefined) } }, toString() { return format(this) }, [NodeInspectSymbol]() { return format(this) } } function defaultEvaluate(_fiber: MicroFiberImpl): Primitive | Yield { return exitDie(`Micro.evaluate: Not implemented`) as any } const makePrimitiveProto = (options: { readonly op: Op readonly eval?: (fiber: MicroFiberImpl) => Primitive | Micro | Yield readonly contA?: (this: Primitive, value: any, fiber: MicroFiberImpl) => Primitive | Micro | Yield readonly contE?: ( this: Primitive, cause: MicroCause, fiber: MicroFiberImpl ) => Primitive | Micro | Yield readonly ensure?: (this: Primitive, fiber: MicroFiberImpl) => void | ((value: any, fiber: MicroFiberImpl) => void) }): Primitive => ({ ...MicroProto, [identifier]: options.op, [evaluate]: options.eval ?? defaultEvaluate, [successCont]: options.contA, [failureCont]: options.contE, [ensureCont]: options.ensure } as any) const makePrimitive = ) => any, Single extends boolean = true>(options: { readonly op: string readonly single?: Single readonly eval?: ( this: Primitive & { readonly [args]: Single extends true ? Parameters[0] : Parameters }, fiber: MicroFiberImpl ) => Primitive | Micro | Yield readonly contA?: ( this: Primitive & { readonly [args]: Single extends true ? Parameters[0] : Parameters }, value: any, fiber: MicroFiberImpl ) => Primitive | Micro | Yield readonly contE?: ( this: Primitive & { readonly [args]: Single extends true ? Parameters[0] : Parameters }, cause: MicroCause, fiber: MicroFiberImpl ) => Primitive | Micro | Yield readonly ensure?: ( this: Primitive & { readonly [args]: Single extends true ? Parameters[0] : Parameters }, fiber: MicroFiberImpl ) => void | ((value: any, fiber: MicroFiberImpl) => void) }): Fn => { const Proto = makePrimitiveProto(options as any) return function() { const self = Object.create(Proto) self[args] = options.single === false ? arguments : arguments[0] return self } as Fn } const makeExit = ) => any, Prop extends string>(options: { readonly op: "Success" | "Failure" readonly prop: Prop readonly eval: ( this: & MicroExit & { [args]: Parameters[0] }, fiber: MicroFiberImpl ) => Primitive | Yield }): Fn => { const Proto = { ...makePrimitiveProto(options), [MicroExitTypeId]: MicroExitTypeId, _tag: options.op, get [options.prop](): any { return (this as any)[args] }, toJSON(this: any) { return { _id: "MicroExit", _tag: options.op, [options.prop]: this[args] } }, [Equal.symbol](this: any, that: any): boolean { return isMicroExit(that) && that._tag === options.op && Equal.equals(this[args], (that as any)[args]) }, [Hash.symbol](this: any): number { return Hash.cached(this, Hash.combine(Hash.string(options.op))(Hash.hash(this[args]))) } } return function(value: unknown) { const self = Object.create(Proto) self[args] = value self[successCont] = undefined self[failureCont] = undefined self[ensureCont] = undefined return self } as Fn } /** * Creates a `Micro` effect that will succeed with the specified constant value. * * @since 3.4.0 * @experimental * @category constructors */ export const succeed: (value: A) => Micro = makeExit({ op: "Success", prop: "value", eval(fiber) { const cont = fiber.getCont(successCont) return cont ? cont[successCont](this[args], fiber) : fiber.yieldWith(this) } }) /** * Creates a `Micro` effect that will fail with the specified `MicroCause`. * * @since 3.4.6 * @experimental * @category constructors */ export const failCause: (cause: MicroCause) => Micro = makeExit({ op: "Failure", prop: "cause", eval(fiber) { let cont = fiber.getCont(failureCont) while (causeIsInterrupt(this[args]) && cont && fiber.interruptible) { cont = fiber.getCont(failureCont) } return cont ? cont[failureCont](this[args], fiber) : fiber.yieldWith(this) } }) /** * Creates a `Micro` effect that fails with the given error. * * This results in a `Fail` variant of the `MicroCause` type, where the error is * tracked at the type level. * * @since 3.4.0 * @experimental * @category constructors */ export const fail = (error: E): Micro => failCause(causeFail(error)) /** * Creates a `Micro` effect that succeeds with a lazily evaluated value. * * If the evaluation of the value throws an error, the effect will fail with a * `Die` variant of the `MicroCause` type. * * @since 3.4.0 * @experimental * @category constructors */ export const sync: (evaluate: LazyArg) => Micro = makePrimitive({ op: "Sync", eval(fiber): Primitive | Yield { const value = this[args]() const cont = fiber.getCont(successCont) return cont ? cont[successCont](value, fiber) : fiber.yieldWith(exitSucceed(value)) } }) /** * Lazily creates a `Micro` effect from the given side-effect. * * @since 3.4.0 * @experimental * @category constructors */ export const suspend: (evaluate: LazyArg>) => Micro = makePrimitive({ op: "Suspend", eval(_fiber) { return this[args]() } }) /** * Pause the execution of the current `Micro` effect, and resume it on the next * scheduler tick. * * @since 3.4.0 * @experimental * @category constructors */ export const yieldNowWith: (priority?: number) => Micro = makePrimitive({ op: "Yield", eval(fiber) { let resumed = false fiber.getRef(CurrentScheduler).scheduleTask(() => { if (resumed) return fiber.evaluate(exitVoid as any) }, this[args] ?? 0) return fiber.yieldWith(() => { resumed = true }) } }) /** * Pause the execution of the current `Micro` effect, and resume it on the next * scheduler tick. * * @since 3.4.0 * @experimental * @category constructors */ export const yieldNow: Micro = yieldNowWith(0) /** * Creates a `Micro` effect that will succeed with the value wrapped in `Some`. * * @since 3.4.0 * @experimental * @category constructors */ export const succeedSome = (a: A): Micro> => succeed(Option.some(a)) /** * Creates a `Micro` effect that succeeds with `None`. * * @since 3.4.0 * @experimental * @category constructors */ export const succeedNone: Micro> = succeed(Option.none()) /** * Creates a `Micro` effect that will fail with the lazily evaluated `MicroCause`. * * @since 3.4.0 * @experimental * @category constructors */ export const failCauseSync = (evaluate: LazyArg>): Micro => suspend(() => failCause(evaluate())) /** * Creates a `Micro` effect that will die with the specified error. * * This results in a `Die` variant of the `MicroCause` type, where the error is * not tracked at the type level. * * @since 3.4.0 * @experimental * @category constructors */ export const die = (defect: unknown): Micro => exitDie(defect) /** * Creates a `Micro` effect that will fail with the lazily evaluated error. * * This results in a `Fail` variant of the `MicroCause` type, where the error is * tracked at the type level. * * @since 3.4.6 * @experimental * @category constructors */ export const failSync = (error: LazyArg): Micro => suspend(() => fail(error())) /** * Converts an `Option` into a `Micro` effect, that will fail with * `NoSuchElementException` if the option is `None`. Otherwise, it will succeed with the * value of the option. * * @since 3.4.0 * @experimental * @category constructors */ export const fromOption = (option: Option.Option): Micro => option._tag === "Some" ? succeed(option.value) : fail(new NoSuchElementException({})) /** * Converts an `Either` into a `Micro` effect, that will fail with the left side * of the either if it is a `Left`. Otherwise, it will succeed with the right * side of the either. * * @since 3.4.0 * @experimental * @category constructors */ export const fromEither = (either: Either.Either): Micro => either._tag === "Right" ? succeed(either.right) : fail(either.left) const void_: Micro = succeed(void 0) export { /** * A `Micro` effect that will succeed with `void` (`undefined`). * * @since 3.4.0 * @experimental * @category constructors */ void_ as void } const try_ = (options: { try: LazyArg catch: (error: unknown) => E }): Micro => suspend(() => { try { return succeed(options.try()) } catch (err) { return fail(options.catch(err)) } }) export { /** * The `Micro` equivalent of a try / catch block, which allows you to map * thrown errors to a specific error type. * * @example * ```ts * import { Micro } from "effect" * * Micro.try({ * try: () => { throw new Error("boom") }, * catch: (cause) => new Error("caught", { cause }) * }) * ``` * * @since 3.4.0 * @experimental * @category constructors */ try_ as try } /** * Wrap a `Promise` into a `Micro` effect. * * Any errors will result in a `Die` variant of the `MicroCause` type, where the * error is not tracked at the type level. * * @since 3.4.0 * @experimental * @category constructors */ export const promise = (evaluate: (signal: AbortSignal) => PromiseLike): Micro => asyncOptions(function(resume, signal) { evaluate(signal!).then( (a) => resume(succeed(a)), (e) => resume(die(e)) ) }, evaluate.length !== 0) /** * Wrap a `Promise` into a `Micro` effect. Any errors will be caught and * converted into a specific error type. * * @example * ```ts * import { Micro } from "effect" * * Micro.tryPromise({ * try: () => Promise.resolve("success"), * catch: (cause) => new Error("caught", { cause }) * }) * ``` * * @since 3.4.0 * @experimental * @category constructors */ export const tryPromise = (options: { readonly try: (signal: AbortSignal) => PromiseLike readonly catch: (error: unknown) => E }): Micro => asyncOptions(function(resume, signal) { try { options.try(signal!).then( (a) => resume(succeed(a)), (e) => resume(fail(options.catch(e))) ) } catch (err) { resume(fail(options.catch(err))) } }, options.try.length !== 0) /** * Create a `Micro` effect using the current `MicroFiber`. * * @since 3.4.0 * @experimental * @category constructors */ export const withMicroFiber: ( evaluate: (fiber: MicroFiberImpl) => Micro ) => Micro = makePrimitive({ op: "WithMicroFiber", eval(fiber) { return this[args](fiber) } }) /** * Flush any yielded effects that are waiting to be executed. * * @since 3.4.0 * @experimental * @category constructors */ export const yieldFlush: Micro = withMicroFiber((fiber) => { fiber.getRef(CurrentScheduler).flush() return exitVoid }) const asyncOptions: ( register: ( resume: (effect: Micro) => void, signal?: AbortSignal ) => void | Micro, withSignal: boolean ) => Micro = makePrimitive({ op: "Async", single: false, eval(fiber) { const register = this[args][0] let resumed = false let yielded: boolean | Primitive = false const controller = this[args][1] ? new AbortController() : undefined const onCancel = register((effect) => { if (resumed) return resumed = true if (yielded) { fiber.evaluate(effect as any) } else { yielded = effect as any } }, controller?.signal) if (yielded !== false) return yielded yielded = true fiber._yielded = () => { resumed = true } if (controller === undefined && onCancel === undefined) { return Yield } fiber._stack.push(asyncFinalizer(() => { resumed = true controller?.abort() return onCancel ?? exitVoid })) return Yield } }) const asyncFinalizer: (onInterrupt: () => Micro) => Primitive = makePrimitive({ op: "AsyncFinalizer", ensure(fiber) { if (fiber.interruptible) { fiber.interruptible = false fiber._stack.push(setInterruptible(true)) } }, contE(cause, _fiber) { return causeIsInterrupt(cause) ? flatMap(this[args](), () => failCause(cause)) : failCause(cause) } }) /** * Create a `Micro` effect from an asynchronous computation. * * You can return a cleanup effect that will be run when the effect is aborted. * It is also passed an `AbortSignal` that is triggered when the effect is * aborted. * * @since 3.4.0 * @experimental * @category constructors */ export const async = ( register: ( resume: (effect: Micro) => void, signal: AbortSignal ) => void | Micro ): Micro => asyncOptions(register as any, register.length >= 2) /** * A `Micro` that will never succeed or fail. It wraps `setInterval` to prevent * the Javascript runtime from exiting. * * @since 3.4.0 * @experimental * @category constructors */ export const never: Micro = async(function() { const interval = setInterval(constVoid, 2147483646) return sync(() => clearInterval(interval)) }) /** * @since 3.4.0 * @experimental * @category constructors */ export const gen = >, AEff>( ...args: | [self: Self, body: (this: Self) => Generator] | [body: () => Generator] ): Micro< AEff, [Eff] extends [never] ? never : [Eff] extends [YieldWrap>] ? E : never, [Eff] extends [never] ? never : [Eff] extends [YieldWrap>] ? R : never > => suspend(() => fromIterator(args.length === 1 ? args[0]() : args[1].call(args[0]) as any)) const fromIterator: ( iterator: Iterator>> ) => Micro = makePrimitive({ op: "Iterator", contA(value, fiber) { const state = this[args].next(value) if (state.done) return succeed(state.value) fiber._stack.push(this) return yieldWrapGet(state.value) }, eval(this: any, fiber: MicroFiberImpl) { return this[successCont](undefined, fiber) } }) // ---------------------------------------------------------------------------- // mapping & sequencing // ---------------------------------------------------------------------------- /** * Create a `Micro` effect that will replace the success value of the given * effect. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ export const as: { // ---------------------------------------------------------------------------- // mapping & sequencing // ---------------------------------------------------------------------------- /** * Create a `Micro` effect that will replace the success value of the given * effect. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ (value: B): (self: Micro) => Micro // ---------------------------------------------------------------------------- // mapping & sequencing // ---------------------------------------------------------------------------- /** * Create a `Micro` effect that will replace the success value of the given * effect. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ (self: Micro, value: B): Micro } = dual(2, (self: Micro, value: B): Micro => map(self, (_) => value)) /** * Wrap the success value of this `Micro` effect in a `Some`. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ export const asSome = (self: Micro): Micro, E, R> => map(self, Option.some) /** * Swap the error and success types of the `Micro` effect. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ export const flip = (self: Micro): Micro => matchEffect(self, { onFailure: succeed, onSuccess: fail }) /** * A more flexible version of `flatMap` that combines `map` and `flatMap` into a * single API. * * It also lets you directly pass a `Micro` effect, which will be executed after * the current effect. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ export const andThen: { /** * A more flexible version of `flatMap` that combines `map` and `flatMap` into a * single API. * * It also lets you directly pass a `Micro` effect, which will be executed after * the current effect. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ (f: (a: A) => X): ( self: Micro ) => [X] extends [Micro] ? Micro : Micro /** * A more flexible version of `flatMap` that combines `map` and `flatMap` into a * single API. * * It also lets you directly pass a `Micro` effect, which will be executed after * the current effect. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ (f: NotFunction): ( self: Micro ) => [X] extends [Micro] ? Micro : Micro /** * A more flexible version of `flatMap` that combines `map` and `flatMap` into a * single API. * * It also lets you directly pass a `Micro` effect, which will be executed after * the current effect. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ (self: Micro, f: (a: A) => X): [X] extends [Micro] ? Micro : Micro /** * A more flexible version of `flatMap` that combines `map` and `flatMap` into a * single API. * * It also lets you directly pass a `Micro` effect, which will be executed after * the current effect. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ (self: Micro, f: NotFunction): [X] extends [Micro] ? Micro : Micro } = dual( 2, (self: Micro, f: any): Micro => flatMap(self, (a) => { const value = isMicro(f) ? f : typeof f === "function" ? f(a) : f return isMicro(value) ? value : succeed(value) }) ) /** * Execute a side effect from the success value of the `Micro` effect. * * It is similar to the `andThen` api, but the success value is ignored. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ export const tap: { /** * Execute a side effect from the success value of the `Micro` effect. * * It is similar to the `andThen` api, but the success value is ignored. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ (f: (a: NoInfer) => X): ( self: Micro ) => [X] extends [Micro] ? Micro : Micro /** * Execute a side effect from the success value of the `Micro` effect. * * It is similar to the `andThen` api, but the success value is ignored. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ (f: NotFunction): ( self: Micro ) => [X] extends [Micro] ? Micro : Micro /** * Execute a side effect from the success value of the `Micro` effect. * * It is similar to the `andThen` api, but the success value is ignored. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ (self: Micro, f: (a: NoInfer) => X): [X] extends [Micro] ? Micro : Micro /** * Execute a side effect from the success value of the `Micro` effect. * * It is similar to the `andThen` api, but the success value is ignored. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ (self: Micro, f: NotFunction): [X] extends [Micro] ? Micro : Micro } = dual( 2, (self: Micro, f: (a: A) => Micro): Micro => flatMap(self, (a) => { const value = isMicro(f) ? f : typeof f === "function" ? f(a) : f return isMicro(value) ? as(value, a) : succeed(a) }) ) /** * Replace the success value of the `Micro` effect with `void`. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ export const asVoid = (self: Micro): Micro => flatMap(self, (_) => exitVoid) /** * Access the `MicroExit` of the given `Micro` effect. * * @since 3.4.6 * @experimental * @category mapping & sequencing */ export const exit = (self: Micro): Micro, never, R> => matchCause(self, { onFailure: exitFailCause, onSuccess: exitSucceed }) /** * Replace the error type of the given `Micro` with the full `MicroCause` object. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ export const sandbox = (self: Micro): Micro, R> => catchAllCause(self, fail) /** * Returns an effect that races all the specified effects, * yielding the value of the first effect to succeed with a value. Losers of * the race will be interrupted immediately * * @since 3.4.0 * @experimental * @category sequencing */ export const raceAll = >( all: Iterable ): Micro, Micro.Error, Micro.Context> => withMicroFiber((parent) => async((resume) => { const effects = Arr.fromIterable(all) const len = effects.length let doneCount = 0 let done = false const fibers = new Set>() const causes: Array> = [] const onExit = (exit: MicroExit) => { doneCount++ if (exit._tag === "Failure") { causes.push(exit.cause) if (doneCount >= len) { resume(failCause(causes[0])) } return } done = true resume(fibers.size === 0 ? exit : flatMap(uninterruptible(fiberInterruptAll(fibers)), () => exit)) } for (let i = 0; i < len; i++) { if (done) break const fiber = unsafeFork(parent, interruptible(effects[i]), true, true) fibers.add(fiber) fiber.addObserver((exit) => { fibers.delete(fiber) onExit(exit) }) } return fiberInterruptAll(fibers) }) ) /** * Returns an effect that races all the specified effects, * yielding the value of the first effect to succeed or fail. Losers of * the race will be interrupted immediately. * * @since 3.4.0 * @experimental * @category sequencing */ export const raceAllFirst = >( all: Iterable ): Micro, Micro.Error, Micro.Context> => withMicroFiber((parent) => async((resume) => { let done = false const fibers = new Set>() const onExit = (exit: MicroExit) => { done = true resume(fibers.size === 0 ? exit : flatMap(fiberInterruptAll(fibers), () => exit)) } for (const effect of all) { if (done) break const fiber = unsafeFork(parent, interruptible(effect), true, true) fibers.add(fiber) fiber.addObserver((exit) => { fibers.delete(fiber) onExit(exit) }) } return fiberInterruptAll(fibers) }) ) /** * Returns an effect that races two effects, yielding the value of the first * effect to succeed. Losers of the race will be interrupted immediately. * * @since 3.4.0 * @experimental * @category sequencing */ export const race: { /** * Returns an effect that races two effects, yielding the value of the first * effect to succeed. Losers of the race will be interrupted immediately. * * @since 3.4.0 * @experimental * @category sequencing */ (that: Micro): (self: Micro) => Micro /** * Returns an effect that races two effects, yielding the value of the first * effect to succeed. Losers of the race will be interrupted immediately. * * @since 3.4.0 * @experimental * @category sequencing */ (self: Micro, that: Micro): Micro } = dual( 2, (self: Micro, that: Micro): Micro => raceAll([self, that]) ) /** * Returns an effect that races two effects, yielding the value of the first * effect to succeed *or* fail. Losers of the race will be interrupted immediately. * * @since 3.4.0 * @experimental * @category sequencing */ export const raceFirst: { /** * Returns an effect that races two effects, yielding the value of the first * effect to succeed *or* fail. Losers of the race will be interrupted immediately. * * @since 3.4.0 * @experimental * @category sequencing */ (that: Micro): (self: Micro) => Micro /** * Returns an effect that races two effects, yielding the value of the first * effect to succeed *or* fail. Losers of the race will be interrupted immediately. * * @since 3.4.0 * @experimental * @category sequencing */ (self: Micro, that: Micro): Micro } = dual( 2, (self: Micro, that: Micro): Micro => raceAllFirst([self, that]) ) /** * Map the success value of this `Micro` effect to another `Micro` effect, then * flatten the result. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ export const flatMap: { /** * Map the success value of this `Micro` effect to another `Micro` effect, then * flatten the result. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ (f: (a: A) => Micro): (self: Micro) => Micro /** * Map the success value of this `Micro` effect to another `Micro` effect, then * flatten the result. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ (self: Micro, f: (a: A) => Micro): Micro } = dual( 2, ( self: Micro, f: (a: A) => Micro ): Micro => { const onSuccess = Object.create(OnSuccessProto) onSuccess[args] = self onSuccess[successCont] = f return onSuccess } ) const OnSuccessProto = makePrimitiveProto({ op: "OnSuccess", eval(this: any, fiber: MicroFiberImpl): Primitive { fiber._stack.push(this) return this[args] } }) // ---------------------------------------------------------------------------- // mapping & sequencing // ---------------------------------------------------------------------------- /** * Flattens any nested `Micro` effects, merging the error and requirement types. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ export const flatten = ( self: Micro, E2, R2> ): Micro => flatMap(self, identity) /** * Transforms the success value of the `Micro` effect with the specified * function. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ export const map: { /** * Transforms the success value of the `Micro` effect with the specified * function. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ (f: (a: A) => B): (self: Micro) => Micro /** * Transforms the success value of the `Micro` effect with the specified * function. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ (self: Micro, f: (a: A) => B): Micro } = dual( 2, (self: Micro, f: (a: A) => B): Micro => flatMap(self, (a) => succeed(f(a))) ) // ---------------------------------------------------------------------------- // MicroExit // ---------------------------------------------------------------------------- /** * The `MicroExit` type is used to represent the result of a `Micro` computation. It * can either be successful, containing a value of type `A`, or it can fail, * containing an error of type `E` wrapped in a `MicroCause`. * * @since 3.4.6 * @experimental * @category MicroExit */ export type MicroExit = | MicroExit.Success | MicroExit.Failure /** * @since 3.4.6 * @experimental * @category MicroExit */ export declare namespace MicroExit { /** * @since 3.4.6 * @experimental * @category MicroExit */ export interface Proto extends Micro { readonly [MicroExitTypeId]: MicroExitTypeId } /** * @since 3.4.6 * @experimental * @category MicroExit */ export interface Success extends Proto { readonly _tag: "Success" readonly value: A } /** * @since 3.4.6 * @experimental * @category MicroExit */ export interface Failure extends Proto { readonly _tag: "Failure" readonly cause: MicroCause } } /** * @since 3.4.6 * @experimental * @category MicroExit */ export const isMicroExit = (u: unknown): u is MicroExit => hasProperty(u, MicroExitTypeId) /** * @since 3.4.6 * @experimental * @category MicroExit */ export const exitSucceed: (a: A) => MicroExit = succeed as any /** * @since 3.4.6 * @experimental * @category MicroExit */ export const exitFailCause: (cause: MicroCause) => MicroExit = failCause as any /** * @since 3.4.6 * @experimental * @category MicroExit */ export const exitInterrupt: MicroExit = exitFailCause(causeInterrupt()) /** * @since 3.4.6 * @experimental * @category MicroExit */ export const exitFail = (e: E): MicroExit => exitFailCause(causeFail(e)) /** * @since 3.4.6 * @experimental * @category MicroExit */ export const exitDie = (defect: unknown): MicroExit => exitFailCause(causeDie(defect)) /** * @since 3.4.6 * @experimental * @category MicroExit */ export const exitIsSuccess = ( self: MicroExit ): self is MicroExit.Success => self._tag === "Success" /** * @since 3.4.6 * @experimental * @category MicroExit */ export const exitIsFailure = ( self: MicroExit ): self is MicroExit.Failure => self._tag === "Failure" /** * @since 3.4.6 * @experimental * @category MicroExit */ export const exitIsInterrupt = ( self: MicroExit ): self is MicroExit.Failure & { readonly cause: MicroCause.Interrupt } => exitIsFailure(self) && self.cause._tag === "Interrupt" /** * @since 3.4.6 * @experimental * @category MicroExit */ export const exitIsFail = ( self: MicroExit ): self is MicroExit.Failure & { readonly cause: MicroCause.Fail } => exitIsFailure(self) && self.cause._tag === "Fail" /** * @since 3.4.6 * @experimental * @category MicroExit */ export const exitIsDie = ( self: MicroExit ): self is MicroExit.Failure & { readonly cause: MicroCause.Die } => exitIsFailure(self) && self.cause._tag === "Die" /** * @since 3.4.6 * @experimental * @category MicroExit */ export const exitVoid: MicroExit = exitSucceed(void 0) /** * @since 3.11.0 * @experimental * @category MicroExit */ export const exitVoidAll = >>( exits: I ): MicroExit> ? _E : never> => { for (const exit of exits) { if (exit._tag === "Failure") { return exit } } return exitVoid } // ---------------------------------------------------------------------------- // scheduler // ---------------------------------------------------------------------------- /** * @since 3.5.9 * @experimental * @category scheduler */ export interface MicroScheduler { readonly scheduleTask: (task: () => void, priority: number) => void readonly shouldYield: (fiber: MicroFiber) => boolean readonly flush: () => void } const setImmediate = "setImmediate" in globalThis ? globalThis.setImmediate : (f: () => void) => setTimeout(f, 0) /** * @since 3.5.9 * @experimental * @category scheduler */ export class MicroSchedulerDefault implements MicroScheduler { private tasks: Array<() => void> = [] private running = false /** * @since 3.5.9 */ scheduleTask(task: () => void, _priority: number) { this.tasks.push(task) if (!this.running) { this.running = true setImmediate(this.afterScheduled) } } /** * @since 3.5.9 */ afterScheduled = () => { this.running = false this.runTasks() } /** * @since 3.5.9 */ runTasks() { const tasks = this.tasks this.tasks = [] for (let i = 0, len = tasks.length; i < len; i++) { tasks[i]() } } /** * @since 3.5.9 */ shouldYield(fiber: MicroFiber) { return fiber.currentOpCount >= fiber.getRef(MaxOpsBeforeYield) } /** * @since 3.5.9 */ flush() { while (this.tasks.length > 0) { this.runTasks() } } } /** * Access the given `Context.Tag` from the environment. * * @since 3.4.0 * @experimental * @category environment */ export const service: { /** * Access the given `Context.Tag` from the environment. * * @since 3.4.0 * @experimental * @category environment */ (tag: Context.Reference): Micro /** * Access the given `Context.Tag` from the environment. * * @since 3.4.0 * @experimental * @category environment */ (tag: Context.Tag): Micro } = ((tag: Context.Tag): Micro => withMicroFiber((fiber) => succeed(Context.unsafeGet(fiber.context, tag)))) as any /** * Access the given `Context.Tag` from the environment, without tracking the * dependency at the type level. * * It will return an `Option` of the service, depending on whether it is * available in the environment or not. * * @since 3.4.0 * @experimental * @category environment */ export const serviceOption = ( tag: Context.Tag ): Micro> => withMicroFiber((fiber) => succeed(Context.getOption(fiber.context, tag))) /** * Update the Context with the given mapping function. * * @since 3.11.0 * @experimental * @category environment */ export const updateContext: { /** * Update the Context with the given mapping function. * * @since 3.11.0 * @experimental * @category environment */ (f: (context: Context.Context) => Context.Context>): (self: Micro) => Micro /** * Update the Context with the given mapping function. * * @since 3.11.0 * @experimental * @category environment */ ( self: Micro, f: (context: Context.Context) => Context.Context> ): Micro } = dual( 2, ( self: Micro, f: (context: Context.Context) => Context.Context> ): Micro => withMicroFiber((fiber) => { const prev = fiber.context as Context.Context fiber.context = f(prev) return onExit( self as any, () => { fiber.context = prev return void_ } ) }) ) /** * Update the service for the given `Context.Tag` in the environment. * * @since 3.11.0 * @experimental * @category environment */ export const updateService: { /** * Update the service for the given `Context.Tag` in the environment. * * @since 3.11.0 * @experimental * @category environment */ (tag: Context.Reference, f: (value: A) => A): (self: Micro) => Micro /** * Update the service for the given `Context.Tag` in the environment. * * @since 3.11.0 * @experimental * @category environment */ (tag: Context.Tag, f: (value: A) => A): (self: Micro) => Micro /** * Update the service for the given `Context.Tag` in the environment. * * @since 3.11.0 * @experimental * @category environment */ (self: Micro, tag: Context.Reference, f: (value: A) => A): Micro /** * Update the service for the given `Context.Tag` in the environment. * * @since 3.11.0 * @experimental * @category environment */ (self: Micro, tag: Context.Tag, f: (value: A) => A): Micro } = dual( 3, ( self: Micro, tag: Context.Reference, f: (value: A) => A ): Micro => withMicroFiber((fiber) => { const prev = Context.unsafeGet(fiber.context, tag) fiber.context = Context.add(fiber.context, tag, f(prev)) return onExit( self, () => { fiber.context = Context.add(fiber.context, tag, prev) return void_ } ) }) ) /** * Access the current `Context` from the environment. * * @since 3.4.0 * @experimental * @category environment */ export const context = (): Micro> => getContext as any const getContext = withMicroFiber((fiber) => succeed(fiber.context)) /** * Merge the given `Context` with the current context. * * @since 3.4.0 * @experimental * @category environment */ export const provideContext: { /** * Merge the given `Context` with the current context. * * @since 3.4.0 * @experimental * @category environment */ (context: Context.Context): (self: Micro) => Micro> /** * Merge the given `Context` with the current context. * * @since 3.4.0 * @experimental * @category environment */ (self: Micro, context: Context.Context): Micro> } = dual( 2, ( self: Micro, provided: Context.Context ): Micro> => updateContext(self, Context.merge(provided)) as any ) /** * Add the provided service to the current context. * * @since 3.4.0 * @experimental * @category environment */ export const provideService: { /** * Add the provided service to the current context. * * @since 3.4.0 * @experimental * @category environment */ (tag: Context.Tag, service: S): (self: Micro) => Micro> /** * Add the provided service to the current context. * * @since 3.4.0 * @experimental * @category environment */ (self: Micro, tag: Context.Tag, service: S): Micro> } = dual( 3, ( self: Micro, tag: Context.Tag, service: S ): Micro> => updateContext(self, Context.add(tag, service)) as any ) /** * Create a service using the provided `Micro` effect, and add it to the * current context. * * @since 3.4.6 * @experimental * @category environment */ export const provideServiceEffect: { /** * Create a service using the provided `Micro` effect, and add it to the * current context. * * @since 3.4.6 * @experimental * @category environment */ (tag: Context.Tag, acquire: Micro): (self: Micro) => Micro | R2> /** * Create a service using the provided `Micro` effect, and add it to the * current context. * * @since 3.4.6 * @experimental * @category environment */ (self: Micro, tag: Context.Tag, acquire: Micro): Micro | R2> } = dual( 3, ( self: Micro, tag: Context.Tag, acquire: Micro ): Micro | R2> => flatMap(acquire, (service) => provideService(self, tag, service)) ) // ======================================================================== // References // ======================================================================== /** * @since 3.11.0 * @experimental * @category references */ export class MaxOpsBeforeYield extends Context.Reference()< "effect/Micro/currentMaxOpsBeforeYield", number >( "effect/Micro/currentMaxOpsBeforeYield", { defaultValue: () => 2048 } ) {} /** * @since 3.11.0 * @experimental * @category environment refs */ export class CurrentConcurrency extends Context.Reference()< "effect/Micro/currentConcurrency", "unbounded" | number >( "effect/Micro/currentConcurrency", { defaultValue: () => "unbounded" } ) {} /** * @since 3.11.0 * @experimental * @category environment refs */ export class CurrentScheduler extends Context.Reference()< "effect/Micro/currentScheduler", MicroScheduler >( "effect/Micro/currentScheduler", { defaultValue: () => new MicroSchedulerDefault() } ) {} /** * If you have a `Micro` that uses `concurrency: "inherit"`, you can use this * api to control the concurrency of that `Micro` when it is run. * * @example * ```ts * import * as Micro from "effect/Micro" * * Micro.forEach([1, 2, 3], (n) => Micro.succeed(n), { * concurrency: "inherit" * }).pipe( * Micro.withConcurrency(2) // use a concurrency of 2 * ) * ``` * * @since 3.4.0 * @experimental * @category environment refs */ export const withConcurrency: { /** * If you have a `Micro` that uses `concurrency: "inherit"`, you can use this * api to control the concurrency of that `Micro` when it is run. * * @example * ```ts * import * as Micro from "effect/Micro" * * Micro.forEach([1, 2, 3], (n) => Micro.succeed(n), { * concurrency: "inherit" * }).pipe( * Micro.withConcurrency(2) // use a concurrency of 2 * ) * ``` * * @since 3.4.0 * @experimental * @category environment refs */ (concurrency: "unbounded" | number): (self: Micro) => Micro /** * If you have a `Micro` that uses `concurrency: "inherit"`, you can use this * api to control the concurrency of that `Micro` when it is run. * * @example * ```ts * import * as Micro from "effect/Micro" * * Micro.forEach([1, 2, 3], (n) => Micro.succeed(n), { * concurrency: "inherit" * }).pipe( * Micro.withConcurrency(2) // use a concurrency of 2 * ) * ``` * * @since 3.4.0 * @experimental * @category environment refs */ (self: Micro, concurrency: "unbounded" | number): Micro } = dual( 2, ( self: Micro, concurrency: "unbounded" | number ): Micro => provideService(self, CurrentConcurrency, concurrency) ) // ---------------------------------------------------------------------------- // zipping // ---------------------------------------------------------------------------- /** * Combine two `Micro` effects into a single effect that produces a tuple of * their results. * * @since 3.4.0 * @experimental * @category zipping */ export const zip: { // ---------------------------------------------------------------------------- // zipping // ---------------------------------------------------------------------------- /** * Combine two `Micro` effects into a single effect that produces a tuple of * their results. * * @since 3.4.0 * @experimental * @category zipping */ ( that: Micro, options?: | { readonly concurrent?: boolean | undefined } | undefined ): (self: Micro) => Micro<[A, A2], E2 | E, R2 | R> // ---------------------------------------------------------------------------- // zipping // ---------------------------------------------------------------------------- /** * Combine two `Micro` effects into a single effect that produces a tuple of * their results. * * @since 3.4.0 * @experimental * @category zipping */ ( self: Micro, that: Micro, options?: { readonly concurrent?: boolean | undefined } ): Micro<[A, A2], E | E2, R | R2> } = dual((args) => isMicro(args[1]), ( self: Micro, that: Micro, options?: { readonly concurrent?: boolean | undefined } ): Micro<[A, A2], E | E2, R | R2> => zipWith(self, that, (a, a2) => [a, a2], options)) /** * The `Micro.zipWith` function combines two `Micro` effects and allows you to * apply a function to the results of the combined effects, transforming them * into a single value. * * @since 3.4.3 * @experimental * @category zipping */ export const zipWith: { /** * The `Micro.zipWith` function combines two `Micro` effects and allows you to * apply a function to the results of the combined effects, transforming them * into a single value. * * @since 3.4.3 * @experimental * @category zipping */ ( that: Micro, f: (a: A, b: A2) => B, options?: { readonly concurrent?: boolean | undefined } ): (self: Micro) => Micro /** * The `Micro.zipWith` function combines two `Micro` effects and allows you to * apply a function to the results of the combined effects, transforming them * into a single value. * * @since 3.4.3 * @experimental * @category zipping */ ( self: Micro, that: Micro, f: (a: A, b: A2) => B, options?: { readonly concurrent?: boolean | undefined } ): Micro } = dual((args) => isMicro(args[1]), ( self: Micro, that: Micro, f: (a: A, b: A2) => B, options?: { readonly concurrent?: boolean | undefined } ): Micro => options?.concurrent // Use `all` exclusively for concurrent cases, as it introduces additional overhead due to the management of concurrency ? map(all([self, that], { concurrency: 2 }), ([a, a2]) => f(a, a2)) : flatMap(self, (a) => map(that, (a2) => f(a, a2)))) // ---------------------------------------------------------------------------- // filtering & conditionals // ---------------------------------------------------------------------------- /** * Filter the specified effect with the provided function, failing with specified * `MicroCause` if the predicate fails. * * In addition to the filtering capabilities discussed earlier, you have the option to further * refine and narrow down the type of the success channel by providing a * * @since 3.4.0 * @experimental * @category filtering & conditionals */ export const filterOrFailCause: { // ---------------------------------------------------------------------------- // filtering & conditionals // ---------------------------------------------------------------------------- /** * Filter the specified effect with the provided function, failing with specified * `MicroCause` if the predicate fails. * * In addition to the filtering capabilities discussed earlier, you have the option to further * refine and narrow down the type of the success channel by providing a * * @since 3.4.0 * @experimental * @category filtering & conditionals */ ( refinement: Refinement, orFailWith: (a: NoInfer) => MicroCause ): (self: Micro) => Micro // ---------------------------------------------------------------------------- // filtering & conditionals // ---------------------------------------------------------------------------- /** * Filter the specified effect with the provided function, failing with specified * `MicroCause` if the predicate fails. * * In addition to the filtering capabilities discussed earlier, you have the option to further * refine and narrow down the type of the success channel by providing a * * @since 3.4.0 * @experimental * @category filtering & conditionals */ ( predicate: Predicate>, orFailWith: (a: NoInfer) => MicroCause ): (self: Micro) => Micro // ---------------------------------------------------------------------------- // filtering & conditionals // ---------------------------------------------------------------------------- /** * Filter the specified effect with the provided function, failing with specified * `MicroCause` if the predicate fails. * * In addition to the filtering capabilities discussed earlier, you have the option to further * refine and narrow down the type of the success channel by providing a * * @since 3.4.0 * @experimental * @category filtering & conditionals */ ( self: Micro, refinement: Refinement, orFailWith: (a: A) => MicroCause ): Micro // ---------------------------------------------------------------------------- // filtering & conditionals // ---------------------------------------------------------------------------- /** * Filter the specified effect with the provided function, failing with specified * `MicroCause` if the predicate fails. * * In addition to the filtering capabilities discussed earlier, you have the option to further * refine and narrow down the type of the success channel by providing a * * @since 3.4.0 * @experimental * @category filtering & conditionals */ ( self: Micro, predicate: Predicate, orFailWith: (a: A) => MicroCause ): Micro } = dual((args) => isMicro(args[0]), ( self: Micro, refinement: Refinement, orFailWith: (a: A) => MicroCause ): Micro => flatMap(self, (a) => refinement(a) ? succeed(a) : failCause(orFailWith(a)))) /** * Filter the specified effect with the provided function, failing with specified * error if the predicate fails. * * In addition to the filtering capabilities discussed earlier, you have the option to further * refine and narrow down the type of the success channel by providing a * * @since 3.4.0 * @experimental * @category filtering & conditionals */ export const filterOrFail: { /** * Filter the specified effect with the provided function, failing with specified * error if the predicate fails. * * In addition to the filtering capabilities discussed earlier, you have the option to further * refine and narrow down the type of the success channel by providing a * * @since 3.4.0 * @experimental * @category filtering & conditionals */ (refinement: Refinement, orFailWith: (a: NoInfer) => E2): (self: Micro) => Micro /** * Filter the specified effect with the provided function, failing with specified * error if the predicate fails. * * In addition to the filtering capabilities discussed earlier, you have the option to further * refine and narrow down the type of the success channel by providing a * * @since 3.4.0 * @experimental * @category filtering & conditionals */ (predicate: Predicate>, orFailWith: (a: NoInfer) => E2): (self: Micro) => Micro /** * Filter the specified effect with the provided function, failing with specified * error if the predicate fails. * * In addition to the filtering capabilities discussed earlier, you have the option to further * refine and narrow down the type of the success channel by providing a * * @since 3.4.0 * @experimental * @category filtering & conditionals */ ( self: Micro, refinement: Refinement, orFailWith: (a: A) => E2 ): Micro /** * Filter the specified effect with the provided function, failing with specified * error if the predicate fails. * * In addition to the filtering capabilities discussed earlier, you have the option to further * refine and narrow down the type of the success channel by providing a * * @since 3.4.0 * @experimental * @category filtering & conditionals */ (self: Micro, predicate: Predicate, orFailWith: (a: A) => E2): Micro } = dual((args) => isMicro(args[0]), ( self: Micro, refinement: Refinement, orFailWith: (a: A) => E2 ): Micro => flatMap(self, (a) => refinement(a) ? succeed(a) : fail(orFailWith(a)))) /** * The moral equivalent of `if (p) exp`. * * @since 3.4.0 * @experimental * @category filtering & conditionals */ export const when: { /** * The moral equivalent of `if (p) exp`. * * @since 3.4.0 * @experimental * @category filtering & conditionals */ (condition: LazyArg | Micro): (self: Micro) => Micro, E | E2, R | R2> /** * The moral equivalent of `if (p) exp`. * * @since 3.4.0 * @experimental * @category filtering & conditionals */ (self: Micro, condition: LazyArg | Micro): Micro, E | E2, R | R2> } = dual( 2, ( self: Micro, condition: LazyArg | Micro ): Micro, E | E2, R | R2> => flatMap(isMicro(condition) ? condition : sync(condition), (pass) => pass ? asSome(self) : succeedNone) ) // ---------------------------------------------------------------------------- // repetition // ---------------------------------------------------------------------------- /** * Repeat the given `Micro` using the provided options. * * The `while` predicate will be checked after each iteration, and can use the * fall `MicroExit` of the effect to determine if the repetition should continue. * * @since 3.4.6 * @experimental * @category repetition */ export const repeatExit: { // ---------------------------------------------------------------------------- // repetition // ---------------------------------------------------------------------------- /** * Repeat the given `Micro` using the provided options. * * The `while` predicate will be checked after each iteration, and can use the * fall `MicroExit` of the effect to determine if the repetition should continue. * * @since 3.4.6 * @experimental * @category repetition */ ( options: { while: Predicate> times?: number | undefined schedule?: MicroSchedule | undefined } ): (self: Micro) => Micro // ---------------------------------------------------------------------------- // repetition // ---------------------------------------------------------------------------- /** * Repeat the given `Micro` using the provided options. * * The `while` predicate will be checked after each iteration, and can use the * fall `MicroExit` of the effect to determine if the repetition should continue. * * @since 3.4.6 * @experimental * @category repetition */ ( self: Micro, options: { while: Predicate> times?: number | undefined schedule?: MicroSchedule | undefined } ): Micro } = dual(2, (self: Micro, options: { while: Predicate> times?: number | undefined schedule?: MicroSchedule | undefined }): Micro => suspend(() => { const startedAt = options.schedule ? Date.now() : 0 let attempt = 0 const loop: Micro = flatMap(exit(self), (exit) => { if (options.while !== undefined && !options.while(exit)) { return exit } else if (options.times !== undefined && attempt >= options.times) { return exit } attempt++ let delayEffect = yieldNow if (options.schedule !== undefined) { const elapsed = Date.now() - startedAt const duration = options.schedule(attempt, elapsed) if (Option.isNone(duration)) { return exit } delayEffect = sleep(duration.value) } return flatMap(delayEffect, () => loop) }) return loop })) /** * Repeat the given `Micro` effect using the provided options. Only successful * results will be repeated. * * @since 3.4.0 * @experimental * @category repetition */ export const repeat: { /** * Repeat the given `Micro` effect using the provided options. Only successful * results will be repeated. * * @since 3.4.0 * @experimental * @category repetition */ ( options?: { while?: Predicate | undefined times?: number | undefined schedule?: MicroSchedule | undefined } | undefined ): (self: Micro) => Micro /** * Repeat the given `Micro` effect using the provided options. Only successful * results will be repeated. * * @since 3.4.0 * @experimental * @category repetition */ ( self: Micro, options?: { while?: Predicate | undefined times?: number | undefined schedule?: MicroSchedule | undefined } | undefined ): Micro } = dual((args) => isMicro(args[0]), ( self: Micro, options?: { while?: Predicate | undefined times?: number | undefined schedule?: MicroSchedule | undefined } | undefined ): Micro => repeatExit(self, { ...options, while: (exit) => exit._tag === "Success" && (options?.while === undefined || options.while(exit.value)) })) /** * Replicates the given effect `n` times. * * @since 3.11.0 * @experimental * @category repetition */ export const replicate: { /** * Replicates the given effect `n` times. * * @since 3.11.0 * @experimental * @category repetition */ (n: number): (self: Micro) => Array> /** * Replicates the given effect `n` times. * * @since 3.11.0 * @experimental * @category repetition */ (self: Micro, n: number): Array> } = dual( 2, (self: Micro, n: number): Array> => Array.from({ length: n }, () => self) ) /** * Performs this effect the specified number of times and collects the * results. * * @since 3.11.0 * @category repetition */ export const replicateEffect: { /** * Performs this effect the specified number of times and collects the * results. * * @since 3.11.0 * @category repetition */ ( n: number, options?: { readonly concurrency?: Concurrency | undefined readonly discard?: false | undefined } ): (self: Micro) => Micro, E, R> /** * Performs this effect the specified number of times and collects the * results. * * @since 3.11.0 * @category repetition */ ( n: number, options: { readonly concurrency?: Concurrency | undefined readonly discard: true } ): (self: Micro) => Micro /** * Performs this effect the specified number of times and collects the * results. * * @since 3.11.0 * @category repetition */ ( self: Micro, n: number, options?: { readonly concurrency?: Concurrency | undefined readonly discard?: false | undefined } ): Micro, E, R> /** * Performs this effect the specified number of times and collects the * results. * * @since 3.11.0 * @category repetition */ ( self: Micro, n: number, options: { readonly concurrency?: Concurrency | undefined readonly discard: true } ): Micro } = dual( (args) => isMicro(args[0]), ( self: Micro, n: number, options: { readonly concurrency?: Concurrency | undefined readonly discard: true } ): Micro => all(replicate(self, n), options) ) /** * Repeat the given `Micro` effect forever, only stopping if the effect fails. * * @since 3.4.0 * @experimental * @category repetition */ export const forever = (self: Micro): Micro => repeat(self) as any // ---------------------------------------------------------------------------- // scheduling // ---------------------------------------------------------------------------- /** * The `MicroSchedule` type represents a function that can be used to calculate * the delay between repeats. * * The function takes the current attempt number and the elapsed time since the * first attempt, and returns the delay for the next attempt. If the function * returns `None`, the repetition will stop. * * @since 3.4.6 * @experimental * @category scheduling */ export type MicroSchedule = (attempt: number, elapsed: number) => Option.Option /** * Create a `MicroSchedule` that will stop repeating after the specified number * of attempts. * * @since 3.4.6 * @experimental * @category scheduling */ export const scheduleRecurs = (n: number): MicroSchedule => (attempt) => attempt <= n ? Option.some(0) : Option.none() /** * Create a `MicroSchedule` that will generate a constant delay. * * @since 3.4.6 * @experimental * @category scheduling */ export const scheduleSpaced = (millis: number): MicroSchedule => () => Option.some(millis) /** * Create a `MicroSchedule` that will generate a delay with an exponential backoff. * * @since 3.4.6 * @experimental * @category scheduling */ export const scheduleExponential = (baseMillis: number, factor = 2): MicroSchedule => (attempt) => Option.some(Math.pow(factor, attempt) * baseMillis) /** * Returns a new `MicroSchedule` with an added calculated delay to each delay * returned by this schedule. * * @since 3.4.6 * @experimental * @category scheduling */ export const scheduleAddDelay: { /** * Returns a new `MicroSchedule` with an added calculated delay to each delay * returned by this schedule. * * @since 3.4.6 * @experimental * @category scheduling */ (f: () => number): (self: MicroSchedule) => MicroSchedule /** * Returns a new `MicroSchedule` with an added calculated delay to each delay * returned by this schedule. * * @since 3.4.6 * @experimental * @category scheduling */ (self: MicroSchedule, f: () => number): MicroSchedule } = dual( 2, (self: MicroSchedule, f: () => number): MicroSchedule => (attempt, elapsed) => Option.map(self(attempt, elapsed), (duration) => duration + f()) ) /** * Transform a `MicroSchedule` to one that will have a delay that will never exceed * the specified maximum. * * @since 3.4.6 * @experimental * @category scheduling */ export const scheduleWithMaxDelay: { /** * Transform a `MicroSchedule` to one that will have a delay that will never exceed * the specified maximum. * * @since 3.4.6 * @experimental * @category scheduling */ (max: number): (self: MicroSchedule) => MicroSchedule /** * Transform a `MicroSchedule` to one that will have a delay that will never exceed * the specified maximum. * * @since 3.4.6 * @experimental * @category scheduling */ (self: MicroSchedule, max: number): MicroSchedule } = dual( 2, (self: MicroSchedule, max: number): MicroSchedule => (attempt, elapsed) => Option.map(self(attempt, elapsed), (duration) => Math.min(duration, max)) ) /** * Transform a `MicroSchedule` to one that will stop repeating after the specified * amount of time. * * @since 3.4.6 * @experimental * @category scheduling */ export const scheduleWithMaxElapsed: { /** * Transform a `MicroSchedule` to one that will stop repeating after the specified * amount of time. * * @since 3.4.6 * @experimental * @category scheduling */ (max: number): (self: MicroSchedule) => MicroSchedule /** * Transform a `MicroSchedule` to one that will stop repeating after the specified * amount of time. * * @since 3.4.6 * @experimental * @category scheduling */ (self: MicroSchedule, max: number): MicroSchedule } = dual( 2, (self: MicroSchedule, max: number): MicroSchedule => (attempt, elapsed) => elapsed < max ? self(attempt, elapsed) : Option.none() ) /** * Combines two `MicroSchedule`s, by recurring if either schedule wants to * recur, using the minimum of the two durations between recurrences. * * @since 3.4.6 * @experimental * @category scheduling */ export const scheduleUnion: { /** * Combines two `MicroSchedule`s, by recurring if either schedule wants to * recur, using the minimum of the two durations between recurrences. * * @since 3.4.6 * @experimental * @category scheduling */ (that: MicroSchedule): (self: MicroSchedule) => MicroSchedule /** * Combines two `MicroSchedule`s, by recurring if either schedule wants to * recur, using the minimum of the two durations between recurrences. * * @since 3.4.6 * @experimental * @category scheduling */ (self: MicroSchedule, that: MicroSchedule): MicroSchedule } = dual( 2, (self: MicroSchedule, that: MicroSchedule): MicroSchedule => (attempt, elapsed) => Option.zipWith(self(attempt, elapsed), that(attempt, elapsed), (d1, d2) => Math.min(d1, d2)) ) /** * Combines two `MicroSchedule`s, by recurring only if both schedules want to * recur, using the maximum of the two durations between recurrences. * * @since 3.4.6 * @experimental * @category scheduling */ export const scheduleIntersect: { /** * Combines two `MicroSchedule`s, by recurring only if both schedules want to * recur, using the maximum of the two durations between recurrences. * * @since 3.4.6 * @experimental * @category scheduling */ (that: MicroSchedule): (self: MicroSchedule) => MicroSchedule /** * Combines two `MicroSchedule`s, by recurring only if both schedules want to * recur, using the maximum of the two durations between recurrences. * * @since 3.4.6 * @experimental * @category scheduling */ (self: MicroSchedule, that: MicroSchedule): MicroSchedule } = dual( 2, (self: MicroSchedule, that: MicroSchedule): MicroSchedule => (attempt, elapsed) => Option.zipWith(self(attempt, elapsed), that(attempt, elapsed), (d1, d2) => Math.max(d1, d2)) ) // ---------------------------------------------------------------------------- // error handling // ---------------------------------------------------------------------------- /** * Catch the full `MicroCause` object of the given `Micro` effect, allowing you to * recover from any kind of cause. * * @since 3.4.6 * @experimental * @category error handling */ export const catchAllCause: { // ---------------------------------------------------------------------------- // error handling // ---------------------------------------------------------------------------- /** * Catch the full `MicroCause` object of the given `Micro` effect, allowing you to * recover from any kind of cause. * * @since 3.4.6 * @experimental * @category error handling */ (f: (cause: NoInfer>) => Micro): (self: Micro) => Micro // ---------------------------------------------------------------------------- // error handling // ---------------------------------------------------------------------------- /** * Catch the full `MicroCause` object of the given `Micro` effect, allowing you to * recover from any kind of cause. * * @since 3.4.6 * @experimental * @category error handling */ ( self: Micro, f: (cause: NoInfer>) => Micro ): Micro } = dual( 2, ( self: Micro, f: (cause: NoInfer>) => Micro ): Micro => { const onFailure = Object.create(OnFailureProto) onFailure[args] = self onFailure[failureCont] = f return onFailure } ) const OnFailureProto = makePrimitiveProto({ op: "OnFailure", eval(this: any, fiber: MicroFiberImpl): Primitive { fiber._stack.push(this as any) return this[args] } }) /** * Selectively catch a `MicroCause` object of the given `Micro` effect, * using the provided predicate to determine if the failure should be caught. * * @since 3.4.6 * @experimental * @category error handling */ export const catchCauseIf: { /** * Selectively catch a `MicroCause` object of the given `Micro` effect, * using the provided predicate to determine if the failure should be caught. * * @since 3.4.6 * @experimental * @category error handling */ >( refinement: Refinement, EB>, f: (cause: EB) => Micro ): ( self: Micro ) => Micro> | E2, R | R2> /** * Selectively catch a `MicroCause` object of the given `Micro` effect, * using the provided predicate to determine if the failure should be caught. * * @since 3.4.6 * @experimental * @category error handling */ ( predicate: Predicate>>, f: (cause: NoInfer>) => Micro ): (self: Micro) => Micro /** * Selectively catch a `MicroCause` object of the given `Micro` effect, * using the provided predicate to determine if the failure should be caught. * * @since 3.4.6 * @experimental * @category error handling */ >( self: Micro, refinement: Refinement, EB>, f: (cause: EB) => Micro ): Micro> | E2, R | R2> /** * Selectively catch a `MicroCause` object of the given `Micro` effect, * using the provided predicate to determine if the failure should be caught. * * @since 3.4.6 * @experimental * @category error handling */ ( self: Micro, predicate: Predicate>>, f: (cause: NoInfer>) => Micro ): Micro } = dual( 3, ( self: Micro, predicate: Predicate>, f: (cause: MicroCause) => Micro ): Micro => catchAllCause(self, (cause) => predicate(cause) ? f(cause) : failCause(cause) as any) ) /** * Catch the error of the given `Micro` effect, allowing you to recover from it. * * It only catches expected errors. * * @since 3.4.6 * @experimental * @category error handling */ export const catchAll: { /** * Catch the error of the given `Micro` effect, allowing you to recover from it. * * It only catches expected errors. * * @since 3.4.6 * @experimental * @category error handling */ (f: (e: NoInfer) => Micro): (self: Micro) => Micro /** * Catch the error of the given `Micro` effect, allowing you to recover from it. * * It only catches expected errors. * * @since 3.4.6 * @experimental * @category error handling */ (self: Micro, f: (e: NoInfer) => Micro): Micro } = dual( 2, ( self: Micro, f: (a: NoInfer) => Micro ): Micro => catchCauseIf(self, causeIsFail, (cause) => f(cause.error)) ) /** * Catch any unexpected errors of the given `Micro` effect, allowing you to recover from them. * * @since 3.4.6 * @experimental * @category error handling */ export const catchAllDefect: { /** * Catch any unexpected errors of the given `Micro` effect, allowing you to recover from them. * * @since 3.4.6 * @experimental * @category error handling */ (f: (defect: unknown) => Micro): (self: Micro) => Micro /** * Catch any unexpected errors of the given `Micro` effect, allowing you to recover from them. * * @since 3.4.6 * @experimental * @category error handling */ (self: Micro, f: (defect: unknown) => Micro): Micro } = dual( 2, (self: Micro, f: (defect: unknown) => Micro): Micro => catchCauseIf(self, causeIsDie, (die) => f(die.defect)) ) /** * Perform a side effect using the full `MicroCause` object of the given `Micro`. * * @since 3.4.6 * @experimental * @category error handling */ export const tapErrorCause: { /** * Perform a side effect using the full `MicroCause` object of the given `Micro`. * * @since 3.4.6 * @experimental * @category error handling */ (f: (cause: NoInfer>) => Micro): (self: Micro) => Micro /** * Perform a side effect using the full `MicroCause` object of the given `Micro`. * * @since 3.4.6 * @experimental * @category error handling */ ( self: Micro, f: (cause: NoInfer>) => Micro ): Micro } = dual( 2, ( self: Micro, f: (cause: NoInfer>) => Micro ): Micro => tapErrorCauseIf(self, constTrue, f) ) /** * Perform a side effect using if a `MicroCause` object matches the specified * predicate. * * @since 3.4.0 * @experimental * @category error handling */ export const tapErrorCauseIf: { /** * Perform a side effect using if a `MicroCause` object matches the specified * predicate. * * @since 3.4.0 * @experimental * @category error handling */ >(refinement: Refinement, EB>, f: (a: EB) => Micro): (self: Micro) => Micro /** * Perform a side effect using if a `MicroCause` object matches the specified * predicate. * * @since 3.4.0 * @experimental * @category error handling */ ( predicate: (cause: NoInfer>) => boolean, f: (a: NoInfer>) => Micro ): (self: Micro) => Micro /** * Perform a side effect using if a `MicroCause` object matches the specified * predicate. * * @since 3.4.0 * @experimental * @category error handling */ >( self: Micro, refinement: Refinement, EB>, f: (a: EB) => Micro ): Micro /** * Perform a side effect using if a `MicroCause` object matches the specified * predicate. * * @since 3.4.0 * @experimental * @category error handling */ ( self: Micro, predicate: (cause: NoInfer>) => boolean, f: (a: NoInfer>) => Micro ): Micro } = dual( 3, >( self: Micro, refinement: Refinement, EB>, f: (a: EB) => Micro ): Micro => catchCauseIf(self, refinement, (cause) => andThen(f(cause), failCause(cause))) ) /** * Perform a side effect from expected errors of the given `Micro`. * * @since 3.4.6 * @experimental * @category error handling */ export const tapError: { /** * Perform a side effect from expected errors of the given `Micro`. * * @since 3.4.6 * @experimental * @category error handling */ (f: (e: NoInfer) => Micro): (self: Micro) => Micro /** * Perform a side effect from expected errors of the given `Micro`. * * @since 3.4.6 * @experimental * @category error handling */ (self: Micro, f: (e: NoInfer) => Micro): Micro } = dual( 2, (self: Micro, f: (e: NoInfer) => Micro): Micro => tapErrorCauseIf(self, causeIsFail, (fail) => f(fail.error)) ) /** * Perform a side effect from unexpected errors of the given `Micro`. * * @since 3.4.6 * @experimental * @category error handling */ export const tapDefect: { /** * Perform a side effect from unexpected errors of the given `Micro`. * * @since 3.4.6 * @experimental * @category error handling */ (f: (defect: unknown) => Micro): (self: Micro) => Micro /** * Perform a side effect from unexpected errors of the given `Micro`. * * @since 3.4.6 * @experimental * @category error handling */ (self: Micro, f: (defect: unknown) => Micro): Micro } = dual( 2, (self: Micro, f: (defect: unknown) => Micro): Micro => tapErrorCauseIf(self, causeIsDie, (die) => f(die.defect)) ) /** * Catch any expected errors that match the specified predicate. * * @since 3.4.0 * @experimental * @category error handling */ export const catchIf: { /** * Catch any expected errors that match the specified predicate. * * @since 3.4.0 * @experimental * @category error handling */ (refinement: Refinement, EB>, f: (e: EB) => Micro): (self: Micro) => Micro, R2 | R> /** * Catch any expected errors that match the specified predicate. * * @since 3.4.0 * @experimental * @category error handling */ (predicate: Predicate>, f: (e: NoInfer) => Micro): (self: Micro) => Micro /** * Catch any expected errors that match the specified predicate. * * @since 3.4.0 * @experimental * @category error handling */ ( self: Micro, refinement: Refinement, f: (e: EB) => Micro ): Micro, R | R2> /** * Catch any expected errors that match the specified predicate. * * @since 3.4.0 * @experimental * @category error handling */ ( self: Micro, predicate: Predicate, f: (e: E) => Micro ): Micro } = dual( 3, ( self: Micro, predicate: Predicate, f: (e: E) => Micro ): Micro => catchCauseIf( self, (f): f is MicroCause.Fail => causeIsFail(f) && predicate(f.error), (fail) => f(fail.error) ) ) /** * Recovers from the specified tagged error. * * @since 3.4.0 * @experimental * @category error handling */ export const catchTag: { /** * Recovers from the specified tagged error. * * @since 3.4.0 * @experimental * @category error handling */ (k: K, f: (e: Extract) => Micro): (self: Micro) => Micro, R1 | R> /** * Recovers from the specified tagged error. * * @since 3.4.0 * @experimental * @category error handling */ ( self: Micro, k: K, f: (e: Extract) => Micro ): Micro, R | R1> } = dual(3, ( self: Micro, k: K, f: (e: Extract) => Micro ): Micro, R | R1> => catchIf(self, isTagged(k) as Refinement>, f) as any) /** * Transform the full `MicroCause` object of the given `Micro` effect. * * @since 3.4.6 * @experimental * @category error handling */ export const mapErrorCause: { /** * Transform the full `MicroCause` object of the given `Micro` effect. * * @since 3.4.6 * @experimental * @category error handling */ (f: (e: MicroCause) => MicroCause): (self: Micro) => Micro /** * Transform the full `MicroCause` object of the given `Micro` effect. * * @since 3.4.6 * @experimental * @category error handling */ (self: Micro, f: (e: MicroCause) => MicroCause): Micro } = dual( 2, (self: Micro, f: (e: MicroCause) => MicroCause): Micro => catchAllCause(self, (cause) => failCause(f(cause))) ) /** * Transform any expected errors of the given `Micro` effect. * * @since 3.4.0 * @experimental * @category error handling */ export const mapError: { /** * Transform any expected errors of the given `Micro` effect. * * @since 3.4.0 * @experimental * @category error handling */ (f: (e: E) => E2): (self: Micro) => Micro /** * Transform any expected errors of the given `Micro` effect. * * @since 3.4.0 * @experimental * @category error handling */ (self: Micro, f: (e: E) => E2): Micro } = dual( 2, (self: Micro, f: (e: E) => E2): Micro => catchAll(self, (error) => fail(f(error))) ) /** * Elevate any expected errors of the given `Micro` effect to unexpected errors, * resulting in an error type of `never`. * * @since 3.4.0 * @experimental * @category error handling */ export const orDie = (self: Micro): Micro => catchAll(self, die) /** * Recover from all errors by succeeding with the given value. * * @since 3.4.0 * @experimental * @category error handling */ export const orElseSucceed: { /** * Recover from all errors by succeeding with the given value. * * @since 3.4.0 * @experimental * @category error handling */ (f: LazyArg): (self: Micro) => Micro /** * Recover from all errors by succeeding with the given value. * * @since 3.4.0 * @experimental * @category error handling */ (self: Micro, f: LazyArg): Micro } = dual( 2, (self: Micro, f: LazyArg): Micro => catchAll(self, (_) => sync(f)) ) /** * Ignore any expected errors of the given `Micro` effect, returning `void`. * * @since 3.4.0 * @experimental * @category error handling */ export const ignore = (self: Micro): Micro => matchEffect(self, { onFailure: (_) => void_, onSuccess: (_) => void_ }) /** * Ignore any expected errors of the given `Micro` effect, returning `void`. * * @since 3.4.0 * @experimental * @category error handling */ export const ignoreLogged = (self: Micro): Micro => matchEffect(self, { // eslint-disable-next-line no-console onFailure: (error) => sync(() => console.error(error)), onSuccess: (_) => void_ }) /** * Replace the success value of the given `Micro` effect with an `Option`, * wrapping the success value in `Some` and returning `None` if the effect fails * with an expected error. * * @since 3.4.0 * @experimental * @category error handling */ export const option = (self: Micro): Micro, never, R> => match(self, { onFailure: Option.none, onSuccess: Option.some }) /** * Replace the success value of the given `Micro` effect with an `Either`, * wrapping the success value in `Right` and wrapping any expected errors with * a `Left`. * * @since 3.4.0 * @experimental * @category error handling */ export const either = (self: Micro): Micro, never, R> => match(self, { onFailure: Either.left, onSuccess: Either.right }) /** * Retry the given `Micro` effect using the provided options. * * @since 3.4.0 * @experimental * @category error handling */ export const retry: { /** * Retry the given `Micro` effect using the provided options. * * @since 3.4.0 * @experimental * @category error handling */ ( options?: { while?: Predicate | undefined times?: number | undefined schedule?: MicroSchedule | undefined } | undefined ): (self: Micro) => Micro /** * Retry the given `Micro` effect using the provided options. * * @since 3.4.0 * @experimental * @category error handling */ ( self: Micro, options?: { while?: Predicate | undefined times?: number | undefined schedule?: MicroSchedule | undefined } | undefined ): Micro } = dual((args) => isMicro(args[0]), ( self: Micro, options?: { while?: Predicate | undefined times?: number | undefined schedule?: MicroSchedule | undefined } | undefined ): Micro => repeatExit(self, { ...options, while: (exit) => exit._tag === "Failure" && exit.cause._tag === "Fail" && (options?.while === undefined || options.while(exit.cause.error)) })) /** * Add a stack trace to any failures that occur in the effect. The trace will be * added to the `traces` field of the `MicroCause` object. * * @since 3.4.0 * @experimental * @category error handling */ export const withTrace: { /** * Add a stack trace to any failures that occur in the effect. The trace will be * added to the `traces` field of the `MicroCause` object. * * @since 3.4.0 * @experimental * @category error handling */ (name: string): (self: Micro) => Micro /** * Add a stack trace to any failures that occur in the effect. The trace will be * added to the `traces` field of the `MicroCause` object. * * @since 3.4.0 * @experimental * @category error handling */ (self: Micro, name: string): Micro } = function() { const prevLimit = globalThis.Error.stackTraceLimit globalThis.Error.stackTraceLimit = 2 const error = new globalThis.Error() globalThis.Error.stackTraceLimit = prevLimit function generate(name: string, cause: MicroCause) { const stack = error.stack if (!stack) { return cause } const line = stack.split("\n")[2]?.trim().replace(/^at /, "") if (!line) { return cause } const lineMatch = line.match(/\((.*)\)$/) return causeWithTrace(cause, `at ${name} (${lineMatch ? lineMatch[1] : line})`) } const f = (name: string) => (self: Micro) => onError(self, (cause) => failCause(generate(name, cause))) if (arguments.length === 2) { return f(arguments[1])(arguments[0]) } return f(arguments[0]) } as any // ---------------------------------------------------------------------------- // pattern matching // ---------------------------------------------------------------------------- /** * @since 3.4.6 * @experimental * @category pattern matching */ export const matchCauseEffect: { // ---------------------------------------------------------------------------- // pattern matching // ---------------------------------------------------------------------------- /** * @since 3.4.6 * @experimental * @category pattern matching */ ( options: { readonly onFailure: (cause: MicroCause) => Micro readonly onSuccess: (a: A) => Micro } ): (self: Micro) => Micro // ---------------------------------------------------------------------------- // pattern matching // ---------------------------------------------------------------------------- /** * @since 3.4.6 * @experimental * @category pattern matching */ ( self: Micro, options: { readonly onFailure: (cause: MicroCause) => Micro readonly onSuccess: (a: A) => Micro } ): Micro } = dual( 2, ( self: Micro, options: { readonly onFailure: (cause: MicroCause) => Micro readonly onSuccess: (a: A) => Micro } ): Micro => { const primitive = Object.create(OnSuccessAndFailureProto) primitive[args] = self primitive[successCont] = options.onSuccess primitive[failureCont] = options.onFailure return primitive } ) const OnSuccessAndFailureProto = makePrimitiveProto({ op: "OnSuccessAndFailure", eval(this: any, fiber: MicroFiberImpl): Primitive { fiber._stack.push(this) return this[args] } }) /** * @since 3.4.6 * @experimental * @category pattern matching */ export const matchCause: { /** * @since 3.4.6 * @experimental * @category pattern matching */ ( options: { readonly onFailure: (cause: MicroCause) => A2 readonly onSuccess: (a: A) => A3 } ): (self: Micro) => Micro /** * @since 3.4.6 * @experimental * @category pattern matching */ ( self: Micro, options: { readonly onFailure: (cause: MicroCause) => A2 readonly onSuccess: (a: A) => A3 } ): Micro } = dual( 2, ( self: Micro, options: { readonly onFailure: (cause: MicroCause) => A2 readonly onSuccess: (a: A) => A3 } ): Micro => matchCauseEffect(self, { onFailure: (cause) => sync(() => options.onFailure(cause)), onSuccess: (value) => sync(() => options.onSuccess(value)) }) ) /** * @since 3.4.6 * @experimental * @category pattern matching */ export const matchEffect: { /** * @since 3.4.6 * @experimental * @category pattern matching */ ( options: { readonly onFailure: (e: E) => Micro readonly onSuccess: (a: A) => Micro } ): (self: Micro) => Micro /** * @since 3.4.6 * @experimental * @category pattern matching */ ( self: Micro, options: { readonly onFailure: (e: E) => Micro readonly onSuccess: (a: A) => Micro } ): Micro } = dual( 2, ( self: Micro, options: { readonly onFailure: (e: E) => Micro readonly onSuccess: (a: A) => Micro } ): Micro => matchCauseEffect(self, { onFailure: (cause) => cause._tag === "Fail" ? options.onFailure(cause.error) : failCause(cause), onSuccess: options.onSuccess }) ) /** * @since 3.4.0 * @experimental * @category pattern matching */ export const match: { /** * @since 3.4.0 * @experimental * @category pattern matching */ ( options: { readonly onFailure: (error: E) => A2 readonly onSuccess: (value: A) => A3 } ): (self: Micro) => Micro /** * @since 3.4.0 * @experimental * @category pattern matching */ ( self: Micro, options: { readonly onFailure: (error: E) => A2 readonly onSuccess: (value: A) => A3 } ): Micro } = dual( 2, ( self: Micro, options: { readonly onFailure: (error: E) => A2 readonly onSuccess: (value: A) => A3 } ): Micro => matchEffect(self, { onFailure: (error) => sync(() => options.onFailure(error)), onSuccess: (value) => sync(() => options.onSuccess(value)) }) ) // ---------------------------------------------------------------------------- // delays & timeouts // ---------------------------------------------------------------------------- /** * Create a `Micro` effect that will sleep for the specified duration. * * @since 3.4.0 * @experimental * @category delays & timeouts */ export const sleep = (millis: number): Micro => async((resume) => { const timeout = setTimeout(() => { resume(void_) }, millis) return sync(() => { clearTimeout(timeout) }) }) /** * Returns an effect that will delay the execution of this effect by the * specified duration. * * @since 3.4.0 * @experimental * @category delays & timeouts */ export const delay: { /** * Returns an effect that will delay the execution of this effect by the * specified duration. * * @since 3.4.0 * @experimental * @category delays & timeouts */ (millis: number): (self: Micro) => Micro /** * Returns an effect that will delay the execution of this effect by the * specified duration. * * @since 3.4.0 * @experimental * @category delays & timeouts */ (self: Micro, millis: number): Micro } = dual( 2, (self: Micro, millis: number): Micro => andThen(sleep(millis), self) ) /** * Returns an effect that will timeout this effect, that will execute the * fallback effect if the timeout elapses before the effect has produced a value. * * If the timeout elapses, the running effect will be safely interrupted. * * @since 3.4.0 * @experimental * @category delays & timeouts */ export const timeoutOrElse: { /** * Returns an effect that will timeout this effect, that will execute the * fallback effect if the timeout elapses before the effect has produced a value. * * If the timeout elapses, the running effect will be safely interrupted. * * @since 3.4.0 * @experimental * @category delays & timeouts */ ( options: { readonly duration: number readonly onTimeout: LazyArg> } ): (self: Micro) => Micro /** * Returns an effect that will timeout this effect, that will execute the * fallback effect if the timeout elapses before the effect has produced a value. * * If the timeout elapses, the running effect will be safely interrupted. * * @since 3.4.0 * @experimental * @category delays & timeouts */ ( self: Micro, options: { readonly duration: number readonly onTimeout: LazyArg> } ): Micro } = dual( 2, (self: Micro, options: { readonly duration: number readonly onTimeout: LazyArg> }): Micro => raceFirst(self, andThen(interruptible(sleep(options.duration)), options.onTimeout)) ) /** * Returns an effect that will timeout this effect, that will fail with a * `TimeoutException` if the timeout elapses before the effect has produced a * value. * * If the timeout elapses, the running effect will be safely interrupted. * * @since 3.4.0 * @experimental * @category delays & timeouts */ export const timeout: { /** * Returns an effect that will timeout this effect, that will fail with a * `TimeoutException` if the timeout elapses before the effect has produced a * value. * * If the timeout elapses, the running effect will be safely interrupted. * * @since 3.4.0 * @experimental * @category delays & timeouts */ (millis: number): (self: Micro) => Micro /** * Returns an effect that will timeout this effect, that will fail with a * `TimeoutException` if the timeout elapses before the effect has produced a * value. * * If the timeout elapses, the running effect will be safely interrupted. * * @since 3.4.0 * @experimental * @category delays & timeouts */ (self: Micro, millis: number): Micro } = dual( 2, (self: Micro, millis: number): Micro => timeoutOrElse(self, { duration: millis, onTimeout: () => fail(new TimeoutException()) }) ) /** * Returns an effect that will timeout this effect, succeeding with a `None` * if the timeout elapses before the effect has produced a value; and `Some` of * the produced value otherwise. * * If the timeout elapses, the running effect will be safely interrupted. * * @since 3.4.0 * @experimental * @category delays & timeouts */ export const timeoutOption: { /** * Returns an effect that will timeout this effect, succeeding with a `None` * if the timeout elapses before the effect has produced a value; and `Some` of * the produced value otherwise. * * If the timeout elapses, the running effect will be safely interrupted. * * @since 3.4.0 * @experimental * @category delays & timeouts */ (millis: number): (self: Micro) => Micro, E, R> /** * Returns an effect that will timeout this effect, succeeding with a `None` * if the timeout elapses before the effect has produced a value; and `Some` of * the produced value otherwise. * * If the timeout elapses, the running effect will be safely interrupted. * * @since 3.4.0 * @experimental * @category delays & timeouts */ (self: Micro, millis: number): Micro, E, R> } = dual( 2, (self: Micro, millis: number): Micro, E, R> => raceFirst( asSome(self), as(interruptible(sleep(millis)), Option.none()) ) ) // ---------------------------------------------------------------------------- // resources & finalization // ---------------------------------------------------------------------------- /** * @since 3.4.0 * @experimental * @category resources & finalization */ export const MicroScopeTypeId: unique symbol = Symbol.for("effect/Micro/MicroScope") /** * @since 3.4.0 * @experimental * @category resources & finalization */ export type MicroScopeTypeId = typeof MicroScopeTypeId /** * @since 3.4.0 * @experimental * @category resources & finalization */ export interface MicroScope { readonly [MicroScopeTypeId]: MicroScopeTypeId readonly addFinalizer: (finalizer: (exit: MicroExit) => Micro) => Micro readonly fork: Micro } /** * @since 3.4.0 * @experimental * @category resources & finalization */ export declare namespace MicroScope { /** * @since 3.4.0 * @experimental * @category resources & finalization */ export interface Closeable extends MicroScope { readonly close: (exit: MicroExit) => Micro } } /** * @since 3.4.0 * @experimental * @category resources & finalization */ export const MicroScope: Context.Tag = Context.GenericTag("effect/Micro/MicroScope") class MicroScopeImpl implements MicroScope.Closeable { readonly [MicroScopeTypeId]: MicroScopeTypeId state: { readonly _tag: "Open" readonly finalizers: Set<(exit: MicroExit) => Micro> } | { readonly _tag: "Closed" readonly exit: MicroExit } = { _tag: "Open", finalizers: new Set() } constructor() { this[MicroScopeTypeId] = MicroScopeTypeId } unsafeAddFinalizer(finalizer: (exit: MicroExit) => Micro): void { if (this.state._tag === "Open") { this.state.finalizers.add(finalizer) } } addFinalizer(finalizer: (exit: MicroExit) => Micro): Micro { return suspend(() => { if (this.state._tag === "Open") { this.state.finalizers.add(finalizer) return void_ } return finalizer(this.state.exit) }) } unsafeRemoveFinalizer(finalizer: (exit: MicroExit) => Micro): void { if (this.state._tag === "Open") { this.state.finalizers.delete(finalizer) } } close(microExit: MicroExit): Micro { return suspend(() => { if (this.state._tag === "Open") { const finalizers = Array.from(this.state.finalizers).reverse() this.state = { _tag: "Closed", exit: microExit } return flatMap( forEach(finalizers, (finalizer) => exit(finalizer(microExit))), exitVoidAll ) } return void_ }) } get fork() { return sync(() => { const newScope = new MicroScopeImpl() if (this.state._tag === "Closed") { newScope.state = this.state return newScope } function fin(exit: MicroExit) { return newScope.close(exit) } this.state.finalizers.add(fin) newScope.unsafeAddFinalizer((_) => sync(() => this.unsafeRemoveFinalizer(fin))) return newScope }) } } /** * @since 3.4.0 * @experimental * @category resources & finalization */ export const scopeMake: Micro = sync(() => new MicroScopeImpl()) /** * @since 3.4.0 * @experimental * @category resources & finalization */ export const scopeUnsafeMake = (): MicroScope.Closeable => new MicroScopeImpl() /** * Access the current `MicroScope`. * * @since 3.4.0 * @experimental * @category resources & finalization */ export const scope: Micro = service(MicroScope) /** * Provide a `MicroScope` to an effect. * * @since 3.4.0 * @experimental * @category resources & finalization */ export const provideScope: { /** * Provide a `MicroScope` to an effect. * * @since 3.4.0 * @experimental * @category resources & finalization */ (scope: MicroScope): (self: Micro) => Micro> /** * Provide a `MicroScope` to an effect. * * @since 3.4.0 * @experimental * @category resources & finalization */ (self: Micro, scope: MicroScope): Micro> } = dual( 2, (self: Micro, scope: MicroScope): Micro> => provideService(self, MicroScope, scope) ) /** * Provide a `MicroScope` to the given effect, closing it after the effect has * finished executing. * * @since 3.4.0 * @experimental * @category resources & finalization */ export const scoped = (self: Micro): Micro> => suspend(() => { const scope = new MicroScopeImpl() return onExit(provideService(self, MicroScope, scope), (exit) => scope.close(exit)) }) /** * Create a resource with a cleanup `Micro` effect, ensuring the cleanup is * executed when the `MicroScope` is closed. * * @since 3.4.0 * @experimental * @category resources & finalization */ export const acquireRelease = ( acquire: Micro, release: (a: A, exit: MicroExit) => Micro ): Micro => uninterruptible(flatMap( scope, (scope) => tap(acquire, (a) => scope.addFinalizer((exit) => release(a, exit))) )) /** * Add a finalizer to the current `MicroScope`. * * @since 3.4.0 * @experimental * @category resources & finalization */ export const addFinalizer = ( finalizer: (exit: MicroExit) => Micro ): Micro => flatMap(scope, (scope) => scope.addFinalizer(finalizer)) /** * When the `Micro` effect is completed, run the given finalizer effect with the * `MicroExit` of the executed effect. * * @since 3.4.6 * @experimental * @category resources & finalization */ export const onExit: { /** * When the `Micro` effect is completed, run the given finalizer effect with the * `MicroExit` of the executed effect. * * @since 3.4.6 * @experimental * @category resources & finalization */ (f: (exit: MicroExit) => Micro): (self: Micro) => Micro /** * When the `Micro` effect is completed, run the given finalizer effect with the * `MicroExit` of the executed effect. * * @since 3.4.6 * @experimental * @category resources & finalization */ (self: Micro, f: (exit: MicroExit) => Micro): Micro } = dual( 2, ( self: Micro, f: (exit: MicroExit) => Micro ): Micro => uninterruptibleMask((restore) => matchCauseEffect(restore(self), { onFailure: (cause) => flatMap(f(exitFailCause(cause)), () => failCause(cause)), onSuccess: (a) => flatMap(f(exitSucceed(a)), () => succeed(a)) }) ) ) /** * Regardless of the result of the this `Micro` effect, run the finalizer effect. * * @since 3.4.0 * @experimental * @category resources & finalization */ export const ensuring: { /** * Regardless of the result of the this `Micro` effect, run the finalizer effect. * * @since 3.4.0 * @experimental * @category resources & finalization */ (finalizer: Micro): (self: Micro) => Micro /** * Regardless of the result of the this `Micro` effect, run the finalizer effect. * * @since 3.4.0 * @experimental * @category resources & finalization */ (self: Micro, finalizer: Micro): Micro } = dual( 2, ( self: Micro, finalizer: Micro ): Micro => onExit(self, (_) => finalizer) ) /** * When the `Micro` effect is completed, run the given finalizer effect if it * matches the specified predicate. * * @since 3.4.6 * @experimental * @category resources & finalization */ export const onExitIf: { /** * When the `Micro` effect is completed, run the given finalizer effect if it * matches the specified predicate. * * @since 3.4.6 * @experimental * @category resources & finalization */ >( refinement: Refinement, B>, f: (exit: B) => Micro ): (self: Micro) => Micro /** * When the `Micro` effect is completed, run the given finalizer effect if it * matches the specified predicate. * * @since 3.4.6 * @experimental * @category resources & finalization */ ( predicate: Predicate, NoInfer>>, f: (exit: MicroExit, NoInfer>) => Micro ): (self: Micro) => Micro /** * When the `Micro` effect is completed, run the given finalizer effect if it * matches the specified predicate. * * @since 3.4.6 * @experimental * @category resources & finalization */ >( self: Micro, refinement: Refinement, B>, f: (exit: B) => Micro ): Micro /** * When the `Micro` effect is completed, run the given finalizer effect if it * matches the specified predicate. * * @since 3.4.6 * @experimental * @category resources & finalization */ ( self: Micro, predicate: Predicate, NoInfer>>, f: (exit: MicroExit, NoInfer>) => Micro ): Micro } = dual( 3, >( self: Micro, refinement: Refinement, B>, f: (exit: B) => Micro ): Micro => onExit(self, (exit) => (refinement(exit) ? f(exit) : exitVoid)) ) /** * When the `Micro` effect fails, run the given finalizer effect with the * `MicroCause` of the executed effect. * * @since 3.4.6 * @experimental * @category resources & finalization */ export const onError: { /** * When the `Micro` effect fails, run the given finalizer effect with the * `MicroCause` of the executed effect. * * @since 3.4.6 * @experimental * @category resources & finalization */ (f: (cause: MicroCause>) => Micro): (self: Micro) => Micro /** * When the `Micro` effect fails, run the given finalizer effect with the * `MicroCause` of the executed effect. * * @since 3.4.6 * @experimental * @category resources & finalization */ ( self: Micro, f: (cause: MicroCause>) => Micro ): Micro } = dual( 2, ( self: Micro, f: (cause: MicroCause>) => Micro ): Micro => onExitIf(self, exitIsFailure, (exit) => f(exit.cause)) ) /** * If this `Micro` effect is aborted, run the finalizer effect. * * @since 3.4.6 * @experimental * @category resources & finalization */ export const onInterrupt: { /** * If this `Micro` effect is aborted, run the finalizer effect. * * @since 3.4.6 * @experimental * @category resources & finalization */ (finalizer: Micro): (self: Micro) => Micro /** * If this `Micro` effect is aborted, run the finalizer effect. * * @since 3.4.6 * @experimental * @category resources & finalization */ (self: Micro, finalizer: Micro): Micro } = dual( 2, (self: Micro, finalizer: Micro): Micro => onExitIf(self, exitIsInterrupt, (_) => finalizer) ) /** * Acquire a resource, use it, and then release the resource when the `use` * effect has completed. * * @since 3.4.0 * @experimental * @category resources & finalization */ export const acquireUseRelease = ( acquire: Micro, use: (a: Resource) => Micro, release: (a: Resource, exit: MicroExit) => Micro ): Micro => uninterruptibleMask((restore) => flatMap( acquire, (a) => flatMap( exit(restore(use(a))), (exit) => andThen(release(a, exit), exit) ) ) ) // ---------------------------------------------------------------------------- // interruption // ---------------------------------------------------------------------------- /** * Abort the current `Micro` effect. * * @since 3.4.6 * @experimental * @category interruption */ export const interrupt: Micro = failCause(causeInterrupt()) /** * Flag the effect as uninterruptible, which means that when the effect is * interrupted, it will be allowed to continue running until completion. * * @since 3.4.0 * @experimental * @category flags */ export const uninterruptible = ( self: Micro ): Micro => withMicroFiber((fiber) => { if (!fiber.interruptible) return self fiber.interruptible = false fiber._stack.push(setInterruptible(true)) return self }) const setInterruptible: (interruptible: boolean) => Primitive = makePrimitive({ op: "SetInterruptible", ensure(fiber) { fiber.interruptible = this[args] if (fiber._interrupted && fiber.interruptible) { return () => exitInterrupt } } }) /** * Flag the effect as interruptible, which means that when the effect is * interrupted, it will be interrupted immediately. * * @since 3.4.0 * @experimental * @category flags */ export const interruptible = ( self: Micro ): Micro => withMicroFiber((fiber) => { if (fiber.interruptible) return self fiber.interruptible = true fiber._stack.push(setInterruptible(false)) if (fiber._interrupted) return exitInterrupt return self }) /** * Wrap the given `Micro` effect in an uninterruptible region, preventing the * effect from being aborted. * * You can use the `restore` function to restore a `Micro` effect to the * interruptibility state before the `uninterruptibleMask` was applied. * * @example * ```ts * import * as Micro from "effect/Micro" * * Micro.uninterruptibleMask((restore) => * Micro.sleep(1000).pipe( // uninterruptible * Micro.andThen(restore(Micro.sleep(1000))) // interruptible * ) * ) * ``` * * @since 3.4.0 * @experimental * @category interruption */ export const uninterruptibleMask = ( f: ( restore: (effect: Micro) => Micro ) => Micro ): Micro => withMicroFiber((fiber) => { if (!fiber.interruptible) return f(identity) fiber.interruptible = false fiber._stack.push(setInterruptible(true)) return f(interruptible) }) // ======================================================================== // collecting & elements // ======================================================================== /** * @since 3.4.0 * @experimental */ export declare namespace All { /** * @since 3.4.0 * @experimental */ export type MicroAny = Micro /** * @since 3.4.0 * @experimental */ export type ReturnIterable, Discard extends boolean> = [T] extends [Iterable>] ? Micro< Discard extends true ? void : Array, E, R > : never /** * @since 3.4.0 * @experimental */ export type ReturnTuple, Discard extends boolean> = Micro< Discard extends true ? void : T[number] extends never ? [] : { -readonly [K in keyof T]: T[K] extends Micro ? _A : never }, T[number] extends never ? never : T[number] extends Micro ? _E : never, T[number] extends never ? never : T[number] extends Micro ? _R : never > extends infer X ? X : never /** * @since 3.4.0 * @experimental */ export type ReturnObject = [T] extends [{ [K: string]: MicroAny }] ? Micro< Discard extends true ? void : { -readonly [K in keyof T]: [T[K]] extends [Micro] ? _A : never }, keyof T extends never ? never : T[keyof T] extends Micro ? _E : never, keyof T extends never ? never : T[keyof T] extends Micro ? _R : never > : never /** * @since 3.4.0 * @experimental */ export type IsDiscard = [Extract] extends [never] ? false : true /** * @since 3.4.0 * @experimental */ export type Return< Arg extends Iterable | Record, O extends NoExcessProperties<{ readonly concurrency?: Concurrency | undefined readonly discard?: boolean | undefined }, O> > = [Arg] extends [ReadonlyArray] ? ReturnTuple> : [Arg] extends [Iterable] ? ReturnIterable> : [Arg] extends [Record] ? ReturnObject> : never } /** * Runs all the provided effects in sequence respecting the structure provided in input. * * Supports multiple arguments, a single argument tuple / array or record / struct. * * @since 3.4.0 * @experimental * @category collecting & elements */ export const all = < const Arg extends Iterable> | Record>, O extends NoExcessProperties<{ readonly concurrency?: Concurrency | undefined readonly discard?: boolean | undefined }, O> >(arg: Arg, options?: O): All.Return => { if (Array.isArray(arg) || isIterable(arg)) { return (forEach as any)(arg, identity, options) } else if (options?.discard) { return (forEach as any)(Object.values(arg), identity, options) } return suspend(() => { const out: Record = {} return as( forEach(Object.entries(arg), ([key, effect]) => map(effect, (value) => { out[key] = value }), { discard: true, concurrency: options?.concurrency }), out ) }) as any } /** * @since 3.11.0 * @experimental * @category collecting & elements */ export const whileLoop: (options: { readonly while: LazyArg readonly body: LazyArg> readonly step: (a: A) => void }) => Micro = makePrimitive({ op: "While", contA(value, fiber) { this[args].step(value) if (this[args].while()) { fiber._stack.push(this) return this[args].body() } return exitVoid }, eval(fiber) { if (this[args].while()) { fiber._stack.push(this) return this[args].body() } return exitVoid } }) /** * For each element of the provided iterable, run the effect and collect the * results. * * If the `discard` option is set to `true`, the results will be discarded and * the effect will return `void`. * * The `concurrency` option can be set to control how many effects are run * concurrently. By default, the effects are run sequentially. * * @since 3.4.0 * @experimental * @category collecting & elements */ export const forEach: { /** * For each element of the provided iterable, run the effect and collect the * results. * * If the `discard` option is set to `true`, the results will be discarded and * the effect will return `void`. * * The `concurrency` option can be set to control how many effects are run * concurrently. By default, the effects are run sequentially. * * @since 3.4.0 * @experimental * @category collecting & elements */ ( iterable: Iterable, f: (a: A, index: number) => Micro, options?: { readonly concurrency?: Concurrency | undefined readonly discard?: false | undefined } ): Micro, E, R> /** * For each element of the provided iterable, run the effect and collect the * results. * * If the `discard` option is set to `true`, the results will be discarded and * the effect will return `void`. * * The `concurrency` option can be set to control how many effects are run * concurrently. By default, the effects are run sequentially. * * @since 3.4.0 * @experimental * @category collecting & elements */ ( iterable: Iterable, f: (a: A, index: number) => Micro, options: { readonly concurrency?: Concurrency | undefined readonly discard: true } ): Micro } = < A, B, E, R >(iterable: Iterable, f: (a: A, index: number) => Micro, options?: { readonly concurrency?: Concurrency | undefined readonly discard?: boolean | undefined }): Micro => withMicroFiber((parent) => { const concurrencyOption = options?.concurrency === "inherit" ? parent.getRef(CurrentConcurrency) : options?.concurrency ?? 1 const concurrency = concurrencyOption === "unbounded" ? Number.POSITIVE_INFINITY : Math.max(1, concurrencyOption) const items = Arr.fromIterable(iterable) let length = items.length if (length === 0) { return options?.discard ? void_ : succeed([]) } const out: Array | undefined = options?.discard ? undefined : new Array(length) let index = 0 if (concurrency === 1) { return as( whileLoop({ while: () => index < items.length, body: () => f(items[index], index), step: out ? (b) => out[index++] = b : (_) => index++ }), out as any ) } return async((resume) => { const fibers = new Set>() let result: MicroExit | undefined = undefined let inProgress = 0 let doneCount = 0 let pumping = false let interrupted = false function pump() { pumping = true while (inProgress < concurrency && index < length) { const currentIndex = index const item = items[currentIndex] index++ inProgress++ try { const child = unsafeFork(parent, f(item, currentIndex), true, true) fibers.add(child) child.addObserver((exit) => { fibers.delete(child) if (interrupted) { return } else if (exit._tag === "Failure") { if (result === undefined) { result = exit length = index fibers.forEach((fiber) => fiber.unsafeInterrupt()) } } else if (out !== undefined) { out[currentIndex] = exit.value } doneCount++ inProgress-- if (doneCount === length) { resume(result ?? succeed(out)) } else if (!pumping && inProgress < concurrency) { pump() } }) } catch (err) { result = exitDie(err) length = index fibers.forEach((fiber) => fiber.unsafeInterrupt()) } } pumping = false } pump() return suspend(() => { interrupted = true index = length return fiberInterruptAll(fibers) }) }) }) /** * Effectfully filter the elements of the provided iterable. * * Use the `concurrency` option to control how many elements are processed * concurrently. * * @since 3.4.0 * @experimental * @category collecting & elements */ export const filter = (iterable: Iterable, f: (a: NoInfer) => Micro, options?: { readonly concurrency?: Concurrency | undefined readonly negate?: boolean | undefined }): Micro, E, R> => filterMap(iterable, (a) => map(f(a), (pass) => { pass = options?.negate ? !pass : pass return pass ? Option.some(a) : Option.none() }), options) /** * Effectfully filter the elements of the provided iterable. * * Use the `concurrency` option to control how many elements are processed * concurrently. * * @since 3.4.0 * @experimental * @category collecting & elements */ export const filterMap = ( iterable: Iterable, f: (a: NoInfer) => Micro, E, R>, options?: { readonly concurrency?: Concurrency | undefined } ): Micro, E, R> => suspend(() => { const out: Array = [] return as( forEach(iterable, (a) => map(f(a), (o) => { if (o._tag === "Some") { out.push(o.value) } }), { discard: true, concurrency: options?.concurrency }), out ) }) // ---------------------------------------------------------------------------- // do notation // ---------------------------------------------------------------------------- /** * Start a do notation block. * * @since 3.4.0 * @experimental * @category do notation */ export const Do: Micro<{}> = succeed({}) /** * Bind the success value of this `Micro` effect to the provided name. * * @since 3.4.0 * @experimental * @category do notation */ export const bindTo: { /** * Bind the success value of this `Micro` effect to the provided name. * * @since 3.4.0 * @experimental * @category do notation */ (name: N): (self: Micro) => Micro<{ [K in N]: A }, E, R> /** * Bind the success value of this `Micro` effect to the provided name. * * @since 3.4.0 * @experimental * @category do notation */ (self: Micro, name: N): Micro<{ [K in N]: A }, E, R> } = doNotation.bindTo(map) /** * Bind the success value of this `Micro` effect to the provided name. * * @since 3.4.0 * @experimental * @category do notation */ export const bind: { /** * Bind the success value of this `Micro` effect to the provided name. * * @since 3.4.0 * @experimental * @category do notation */ , B, E2, R2>(name: N, f: (a: NoInfer) => Micro): (self: Micro) => Micro & { [K in N]: B }>, E | E2, R | R2> /** * Bind the success value of this `Micro` effect to the provided name. * * @since 3.4.0 * @experimental * @category do notation */ , E, R, B, E2, R2, N extends string>(self: Micro, name: N, f: (a: NoInfer) => Micro): Micro & { [K in N]: B }>, E | E2, R | R2> } = doNotation.bind(map, flatMap) const let_: { , B>( name: N, f: (a: NoInfer) => B ): (self: Micro) => Micro & { [K in N]: B }>, E, R> , E, R, B, N extends string>( self: Micro, name: N, f: (a: NoInfer) => B ): Micro & { [K in N]: B }>, E, R> } = doNotation.let_(map) export { /** * Bind the result of a synchronous computation to the given name. * * @since 3.4.0 * @experimental * @category do notation */ let_ as let } // ---------------------------------------------------------------------------- // fibers & forking // ---------------------------------------------------------------------------- /** * Run the `Micro` effect in a new `MicroFiber` that can be awaited, joined, or * aborted. * * When the parent `Micro` finishes, this `Micro` will be aborted. * * @since 3.4.0 * @experimental * @category fiber & forking */ export const fork = ( self: Micro ): Micro, never, R> => withMicroFiber((fiber) => { fiberMiddleware.interruptChildren ??= fiberInterruptChildren return succeed(unsafeFork(fiber, self)) }) const unsafeFork = ( parent: MicroFiberImpl, effect: Micro, immediate = false, daemon = false ): MicroFiber => { const child = new MicroFiberImpl(parent.context, parent.interruptible) if (!daemon) { parent.children().add(child) child.addObserver(() => parent.children().delete(child)) } if (immediate) { child.evaluate(effect as any) } else { parent.getRef(CurrentScheduler).scheduleTask(() => child.evaluate(effect as any), 0) } return child } /** * Run the `Micro` effect in a new `MicroFiber` that can be awaited, joined, or * aborted. * * It will not be aborted when the parent `Micro` finishes. * * @since 3.4.0 * @experimental * @category fiber & forking */ export const forkDaemon = ( self: Micro ): Micro, never, R> => withMicroFiber((fiber) => succeed(unsafeFork(fiber, self, false, true))) /** * Run the `Micro` effect in a new `MicroFiber` that can be awaited, joined, or * aborted. * * The lifetime of the handle will be attached to the provided `MicroScope`. * * @since 3.4.0 * @experimental * @category fiber & forking */ export const forkIn: { /** * Run the `Micro` effect in a new `MicroFiber` that can be awaited, joined, or * aborted. * * The lifetime of the handle will be attached to the provided `MicroScope`. * * @since 3.4.0 * @experimental * @category fiber & forking */ (scope: MicroScope): (self: Micro) => Micro, never, R> /** * Run the `Micro` effect in a new `MicroFiber` that can be awaited, joined, or * aborted. * * The lifetime of the handle will be attached to the provided `MicroScope`. * * @since 3.4.0 * @experimental * @category fiber & forking */ (self: Micro, scope: MicroScope): Micro, never, R> } = dual( 2, (self: Micro, scope: MicroScope): Micro, never, R> => uninterruptibleMask((restore) => flatMap(scope.fork, (scope) => tap( restore(forkDaemon(onExit(self, (exit) => scope.close(exit)))), (fiber) => scope.addFinalizer((_) => fiberInterrupt(fiber)) )) ) ) /** * Run the `Micro` effect in a new `MicroFiber` that can be awaited, joined, or * aborted. * * The lifetime of the handle will be attached to the current `MicroScope`. * * @since 3.4.0 * @experimental * @category fiber & forking */ export const forkScoped = (self: Micro): Micro, never, R | MicroScope> => flatMap(scope, (scope) => forkIn(self, scope)) // ---------------------------------------------------------------------------- // execution // ---------------------------------------------------------------------------- /** * Execute the `Micro` effect and return a `MicroFiber` that can be awaited, joined, * or aborted. * * You can listen for the result by adding an observer using the handle's * `addObserver` method. * * @example * ```ts * import * as Micro from "effect/Micro" * * const handle = Micro.succeed(42).pipe( * Micro.delay(1000), * Micro.runFork * ) * * handle.addObserver((exit) => { * console.log(exit) * }) * ``` * * @since 3.4.0 * @experimental * @category execution */ export const runFork = ( effect: Micro, options?: { readonly signal?: AbortSignal | undefined readonly scheduler?: MicroScheduler | undefined } | undefined ): MicroFiberImpl => { const fiber = new MicroFiberImpl(CurrentScheduler.context( options?.scheduler ?? new MicroSchedulerDefault() )) fiber.evaluate(effect as any) if (options?.signal) { if (options.signal.aborted) { fiber.unsafeInterrupt() } else { const abort = () => fiber.unsafeInterrupt() options.signal.addEventListener("abort", abort, { once: true }) fiber.addObserver(() => options.signal!.removeEventListener("abort", abort)) } } return fiber } /** * Execute the `Micro` effect and return a `Promise` that resolves with the * `MicroExit` of the computation. * * @since 3.4.6 * @experimental * @category execution */ export const runPromiseExit = ( effect: Micro, options?: { readonly signal?: AbortSignal | undefined readonly scheduler?: MicroScheduler | undefined } | undefined ): Promise> => new Promise((resolve, _reject) => { const handle = runFork(effect, options) handle.addObserver(resolve) }) /** * Execute the `Micro` effect and return a `Promise` that resolves with the * successful value of the computation. * * @since 3.4.0 * @experimental * @category execution */ export const runPromise = ( effect: Micro, options?: { readonly signal?: AbortSignal | undefined readonly scheduler?: MicroScheduler | undefined } | undefined ): Promise => runPromiseExit(effect, options).then((exit) => { if (exit._tag === "Failure") { throw exit.cause } return exit.value }) /** * Attempt to execute the `Micro` effect synchronously and return the `MicroExit`. * * If any asynchronous effects are encountered, the function will return a * `CauseDie` containing the `MicroFiber`. * * @since 3.4.6 * @experimental * @category execution */ export const runSyncExit = (effect: Micro): MicroExit => { const scheduler = new MicroSchedulerDefault() const fiber = runFork(effect, { scheduler }) scheduler.flush() return fiber._exit ?? exitDie(fiber) } /** * Attempt to execute the `Micro` effect synchronously and return the success * value. * * @since 3.4.0 * @experimental * @category execution */ export const runSync = (effect: Micro): A => { const exit = runSyncExit(effect) if (exit._tag === "Failure") throw exit.cause return exit.value } // ---------------------------------------------------------------------------- // Errors // ---------------------------------------------------------------------------- /** * @since 3.4.0 * @experimental * @category errors */ export interface YieldableError extends Pipeable, Inspectable, Readonly { readonly [Effectable.EffectTypeId]: Effect.VarianceStruct readonly [Effectable.StreamTypeId]: Stream.VarianceStruct readonly [Effectable.SinkTypeId]: Sink.VarianceStruct readonly [Effectable.ChannelTypeId]: Channel.VarianceStruct readonly [TypeId]: Micro.Variance [Symbol.iterator](): MicroIterator> } const YieldableError: new(message?: string) => YieldableError = (function() { class YieldableError extends globalThis.Error {} // @effect-diagnostics-next-line floatingEffect:off Object.assign(YieldableError.prototype, MicroProto, StructuralPrototype, { [identifier]: "Failure", [evaluate]() { return fail(this) }, toString(this: Error) { return this.message ? `${this.name}: ${this.message}` : this.name }, toJSON() { return { ...this } }, [NodeInspectSymbol](this: Error): string { const stack = this.stack if (stack) { return `${this.toString()}\n${stack.split("\n").slice(1).join("\n")}` } return this.toString() } }) return YieldableError as any })() /** * @since 3.4.0 * @experimental * @category errors */ export const Error: new = {}>( args: Equals extends true ? void : { readonly [P in keyof A]: A[P] } ) => YieldableError & Readonly = (function() { return class extends YieldableError { constructor(args: any) { super() if (args) { Object.assign(this, args) } } } as any })() /** * @since 3.4.0 * @experimental * @category errors */ export const TaggedError = (tag: Tag): new = {}>( args: Equals extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] } ) => YieldableError & { readonly _tag: Tag } & Readonly => { class Base extends Error<{}> { readonly _tag = tag } ;(Base.prototype as any).name = tag return Base as any } /** * Represents a checked exception which occurs when an expected element was * unable to be found. * * @since 3.4.4 * @experimental * @category errors */ export class NoSuchElementException extends TaggedError("NoSuchElementException")<{ message?: string | undefined }> {} /** * Represents a checked exception which occurs when a timeout occurs. * * @since 3.4.4 * @experimental * @category errors */ export class TimeoutException extends TaggedError("TimeoutException") {}