diff --git "a/backend/node_modules/effect/src/Micro.ts" "b/backend/node_modules/effect/src/Micro.ts" new file mode 100644--- /dev/null +++ "b/backend/node_modules/effect/src/Micro.ts" @@ -0,0 +1,5556 @@ +/** + * 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") {}