UNPKG

effect

Version:

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

1,846 lines (1,706 loc) 157 kB
/** * 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, 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<out A, out E = never, out R = never> extends Effect<A, E, R> { readonly [TypeId]: Micro.Variance<A, E, R> [Symbol.iterator](): MicroIterator<Micro<A, E, R>> [Unify.typeSymbol]?: unknown [Unify.unifySymbol]?: MicroUnify<this> [Unify.ignoreSymbol]?: MicroUnifyIgnore } /** * @category models * @since 3.4.3 */ export interface MicroUnify<A extends { [Unify.typeSymbol]?: any }> extends EffectUnify<A> { Micro?: () => A[Unify.typeSymbol] extends Micro<infer A0, infer E0, infer R0> | infer _ ? Micro<A0, E0, R0> : 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<this["Target"], this["Out1"], this["Out2"]> } /** * @since 3.4.0 * @experimental */ export declare namespace Micro { /** * @since 3.4.0 * @experimental */ export interface Variance<A, E, R> { _A: Covariant<A> _E: Covariant<E> _R: Covariant<R> } /** * @since 3.4.0 * @experimental */ export type Success<T> = T extends Micro<infer _A, infer _E, infer _R> ? _A : never /** * @since 3.4.0 * @experimental */ export type Error<T> = T extends Micro<infer _A, infer _E, infer _R> ? _E : never /** * @since 3.4.0 * @experimental */ export type Context<T> = T extends Micro<infer _A, infer _E, infer _R> ? _R : never } /** * @since 3.4.0 * @experimental * @category guards */ export const isMicro = (u: unknown): u is Micro<any, any, any> => typeof u === "object" && u !== null && TypeId in u /** * @since 3.4.0 * @experimental * @category models */ export interface MicroIterator<T extends Micro<any, any, any>> { next(...args: ReadonlyArray<any>): IteratorResult<YieldWrap<T>, Micro.Success<T>> } // ---------------------------------------------------------------------------- // 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<E> = | MicroCause.Die | MicroCause.Fail<E> | MicroCause.Interrupt /** * @since 3.6.6 * @experimental * @category guards */ export const isMicroCause = (self: unknown): self is MicroCause<unknown> => hasProperty(self, MicroCauseTypeId) /** * @since 3.4.6 * @experimental * @category MicroCause */ export declare namespace MicroCause { /** * @since 3.4.6 * @experimental */ export type Error<T> = T extends MicroCause.Fail<infer E> ? E : never /** * @since 3.4.0 * @experimental */ export interface Proto<Tag extends string, E> extends Pipeable, globalThis.Error { readonly [MicroCauseTypeId]: { _E: Covariant<E> } readonly _tag: Tag readonly traces: ReadonlyArray<string> } /** * @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<E> 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<Tag extends string, E> extends globalThis.Error implements MicroCause.Proto<Tag, E> { readonly [MicroCauseTypeId]: { _E: Covariant<E> } constructor( readonly _tag: Tag, originalError: unknown, readonly traces: ReadonlyArray<string> ) { 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<E> extends MicroCauseImpl<"Fail", E> implements MicroCause.Fail<E> { constructor( readonly error: E, traces: ReadonlyArray<string> = [] ) { super("Fail", error, traces) } } /** * @since 3.4.6 * @experimental * @category MicroCause */ export const causeFail = <E>( error: E, traces: ReadonlyArray<string> = [] ): MicroCause<E> => new Fail(error, traces) class Die extends MicroCauseImpl<"Die", never> implements MicroCause.Die { constructor( readonly defect: unknown, traces: ReadonlyArray<string> = [] ) { super("Die", defect, traces) } } /** * @since 3.4.6 * @experimental * @category MicroCause */ export const causeDie = ( defect: unknown, traces: ReadonlyArray<string> = [] ): MicroCause<never> => new Die(defect, traces) class Interrupt extends MicroCauseImpl<"Interrupt", never> implements MicroCause.Interrupt { constructor(traces: ReadonlyArray<string> = []) { super("Interrupt", "interrupted", traces) } } /** * @since 3.4.6 * @experimental * @category MicroCause */ export const causeInterrupt = ( traces: ReadonlyArray<string> = [] ): MicroCause<never> => new Interrupt(traces) /** * @since 3.4.6 * @experimental * @category MicroCause */ export const causeIsFail = <E>( self: MicroCause<E> ): self is MicroCause.Fail<E> => self._tag === "Fail" /** * @since 3.4.6 * @experimental * @category MicroCause */ export const causeIsDie = <E>(self: MicroCause<E>): self is MicroCause.Die => self._tag === "Die" /** * @since 3.4.6 * @experimental * @category MicroCause */ export const causeIsInterrupt = <E>( self: MicroCause<E> ): self is MicroCause.Interrupt => self._tag === "Interrupt" /** * @since 3.4.6 * @experimental * @category MicroCause */ export const causeSquash = <E>(self: MicroCause<E>): 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): <E>(self: MicroCause<E>) => MicroCause<E> /** * @since 3.4.6 * @experimental * @category MicroCause */ <E>(self: MicroCause<E>, trace: string): MicroCause<E> } = dual(2, <E>(self: MicroCause<E>, trace: string): MicroCause<E> => { 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<out A, out E = never> { readonly [MicroFiberTypeId]: MicroFiber.Variance<A, E> readonly currentOpCount: number readonly getRef: <I, A>(ref: Context.Reference<I, A>) => A readonly context: Context.Context<never> readonly addObserver: (cb: (exit: MicroExit<A, E>) => void) => () => void readonly unsafeInterrupt: () => void readonly unsafePoll: () => MicroExit<A, E> | undefined } /** * @since 3.11.0 * @experimental * @category MicroFiber */ export declare namespace MicroFiber { /** * @since 3.11.0 * @experimental * @category MicroFiber */ export interface Variance<out A, out E = never> { readonly _A: Covariant<A> readonly _E: Covariant<E> } } const fiberVariance = { _A: identity, _E: identity } class MicroFiberImpl<in out A = any, in out E = any> implements MicroFiber<A, E> { readonly [MicroFiberTypeId]: MicroFiber.Variance<A, E> readonly _stack: Array<Primitive> = [] readonly _observers: Array<(exit: MicroExit<A, E>) => void> = [] _exit: MicroExit<A, E> | undefined public _children: Set<MicroFiberImpl<any, any>> | undefined public currentOpCount = 0 constructor( public context: Context.Context<never>, public interruptible = true ) { this[MicroFiberTypeId] = fiberVariance } getRef<I, A>(ref: Context.Reference<I, A>): A { return InternalContext.unsafeGetReference(this.context, ref) } addObserver(cb: (exit: MicroExit<A, E>) => 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<A, E> | 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<A, E> | 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<S extends successCont | failureCont>( symbol: S ): Primitive & Record<S, (value: any, fiber: MicroFiberImpl) => 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<any, any> | (() => void) | undefined = undefined yieldWith(value: MicroExit<any, any> | (() => void)): Yield { this._yielded = value return Yield } children(): Set<MicroFiber<any, any>> { return this._children ??= new Set() } } const fiberMiddleware = globalValue("effect/Micro/fiberMiddleware", () => ({ interruptChildren: undefined as ((fiber: MicroFiberImpl) => Micro<void> | 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 = <A, E>(self: MicroFiber<A, E>): Micro<MicroExit<A, E>> => async((resume) => sync(self.addObserver((exit) => resume(succeed(exit))))) /** * @since 3.11.2 * @experimental * @category MicroFiber */ export const fiberJoin = <A, E>(self: MicroFiber<A, E>): Micro<A, E> => flatten(fiberAwait(self)) /** * @since 3.11.0 * @experimental * @category MicroFiber */ export const fiberInterrupt = <A, E>(self: MicroFiber<A, E>): Micro<void> => suspend(() => { self.unsafeInterrupt() return asVoid(fiberAwait(self)) }) /** * @since 3.11.0 * @experimental * @category MicroFiber */ export const fiberInterruptAll = <A extends Iterable<MicroFiber<any, any>>>(fibers: A): Micro<void> => suspend(() => { for (const fiber of fibers) fiber.unsafeInterrupt() const iter = fibers[Symbol.iterator]() const wait: Micro<void> = 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<unknown>, 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 = <Op extends string>(options: { readonly op: Op readonly eval?: (fiber: MicroFiberImpl) => Primitive | Micro<any, any, any> | Yield readonly contA?: (this: Primitive, value: any, fiber: MicroFiberImpl) => Primitive | Micro<any, any, any> | Yield readonly contE?: ( this: Primitive, cause: MicroCause<any>, fiber: MicroFiberImpl ) => Primitive | Micro<any, any, any> | 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 = <Fn extends (...args: Array<any>) => any, Single extends boolean = true>(options: { readonly op: string readonly single?: Single readonly eval?: ( this: Primitive & { readonly [args]: Single extends true ? Parameters<Fn>[0] : Parameters<Fn> }, fiber: MicroFiberImpl ) => Primitive | Micro<any, any, any> | Yield readonly contA?: ( this: Primitive & { readonly [args]: Single extends true ? Parameters<Fn>[0] : Parameters<Fn> }, value: any, fiber: MicroFiberImpl ) => Primitive | Micro<any, any, any> | Yield readonly contE?: ( this: Primitive & { readonly [args]: Single extends true ? Parameters<Fn>[0] : Parameters<Fn> }, cause: MicroCause<any>, fiber: MicroFiberImpl ) => Primitive | Micro<any, any, any> | Yield readonly ensure?: ( this: Primitive & { readonly [args]: Single extends true ? Parameters<Fn>[0] : Parameters<Fn> }, 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 = <Fn extends (...args: Array<any>) => any, Prop extends string>(options: { readonly op: "Success" | "Failure" readonly prop: Prop readonly eval: ( this: & MicroExit<unknown, unknown> & { [args]: Parameters<Fn>[0] }, fiber: MicroFiberImpl<unknown, unknown> ) => 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: <A>(value: A) => Micro<A> = 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: <E>(cause: MicroCause<E>) => Micro<never, E> = 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 = <E>(error: E): Micro<never, E> => 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: <A>(evaluate: LazyArg<A>) => Micro<A> = 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: <A, E, R>(evaluate: LazyArg<Micro<A, E, R>>) => Micro<A, E, R> = 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<void> = 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<void> = 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: A): Micro<Option.Option<A>> => succeed(Option.some(a)) /** * Creates a `Micro` effect that succeeds with `None`. * * @since 3.4.0 * @experimental * @category constructors */ export const succeedNone: Micro<Option.Option<never>> = 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 = <E>(evaluate: LazyArg<MicroCause<E>>): Micro<never, E> => 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<never> => 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 = <E>(error: LazyArg<E>): Micro<never, E> => 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 = <A>(option: Option.Option<A>): Micro<A, NoSuchElementException> => 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 = <R, L>(either: Either.Either<R, L>): Micro<R, L> => either._tag === "Right" ? succeed(either.right) : fail(either.left) const void_: Micro<void> = 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_ = <A, E>(options: { try: LazyArg<A> catch: (error: unknown) => E }): Micro<A, E> => 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 = <A>(evaluate: (signal: AbortSignal) => PromiseLike<A>): Micro<A> => asyncOptions<A>(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 = <A, E>(options: { readonly try: (signal: AbortSignal) => PromiseLike<A> readonly catch: (error: unknown) => E }): Micro<A, E> => asyncOptions<A, E>(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: <A, E = never, R = never>( evaluate: (fiber: MicroFiberImpl<A, E>) => Micro<A, E, R> ) => Micro<A, E, R> = 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<void> = withMicroFiber((fiber) => { fiber.getRef(CurrentScheduler).flush() return exitVoid }) const asyncOptions: <A, E = never, R = never>( register: ( resume: (effect: Micro<A, E, R>) => void, signal?: AbortSignal ) => void | Micro<void, never, R>, withSignal: boolean ) => Micro<A, E, R> = 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<void, any, any>) => 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 = <A, E = never, R = never>( register: ( resume: (effect: Micro<A, E, R>) => void, signal: AbortSignal ) => void | Micro<void, never, R> ): Micro<A, E, R> => 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<never> = async<never>(function() { const interval = setInterval(constVoid, 2147483646) return sync(() => clearInterval(interval)) }) /** * @since 3.4.0 * @experimental * @category constructors */ export const gen = <Self, Eff extends YieldWrap<Micro<any, any, any>>, AEff>( ...args: | [self: Self, body: (this: Self) => Generator<Eff, AEff, never>] | [body: () => Generator<Eff, AEff, never>] ): Micro< AEff, [Eff] extends [never] ? never : [Eff] extends [YieldWrap<Micro<infer _A, infer E, infer _R>>] ? E : never, [Eff] extends [never] ? never : [Eff] extends [YieldWrap<Micro<infer _A, infer _E, infer R>>] ? R : never > => suspend(() => fromIterator(args.length === 1 ? args[0]() : args[1].call(args[0]) as any)) const fromIterator: ( iterator: Iterator<any, YieldWrap<Micro<any, any, any>>> ) => Micro<any, any, any> = 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 */ <A, B>(value: B): <E, R>(self: Micro<A, E, R>) => Micro<B, E, R> // ---------------------------------------------------------------------------- // mapping & sequencing // ---------------------------------------------------------------------------- /** * Create a `Micro` effect that will replace the success value of the given * effect. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ <A, E, R, B>(self: Micro<A, E, R>, value: B): Micro<B, E, R> } = dual(2, <A, E, R, B>(self: Micro<A, E, R>, value: B): Micro<B, E, R> => 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 = <A, E, R>(self: Micro<A, E, R>): Micro<Option.Option<A>, 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 = <A, E, R>(self: Micro<A, E, R>): Micro<E, A, R> => 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 */ <A, X>(f: (a: A) => X): <E, R>( self: Micro<A, E, R> ) => [X] extends [Micro<infer A1, infer E1, infer R1>] ? Micro<A1, E | E1, R | R1> : Micro<X, E, R> /** * 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 */ <X>(f: NotFunction<X>): <A, E, R>( self: Micro<A, E, R> ) => [X] extends [Micro<infer A1, infer E1, infer R1>] ? Micro<A1, E | E1, R | R1> : Micro<X, E, R> /** * 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 */ <A, E, R, X>(self: Micro<A, E, R>, f: (a: A) => X): [X] extends [Micro<infer A1, infer E1, infer R1>] ? Micro<A1, E | E1, R | R1> : Micro<X, E, R> /** * 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 */ <A, E, R, X>(self: Micro<A, E, R>, f: NotFunction<X>): [X] extends [Micro<infer A1, infer E1, infer R1>] ? Micro<A1, E | E1, R | R1> : Micro<X, E, R> } = dual( 2, <A, E, R, B, E2, R2>(self: Micro<A, E, R>, f: any): Micro<B, E | E2, R | R2> => 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 */ <A, X>(f: (a: NoInfer<A>) => X): <E, R>( self: Micro<A, E, R> ) => [X] extends [Micro<infer _A1, infer E1, infer R1>] ? Micro<A, E | E1, R | R1> : Micro<A, E, R> /** * 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 */ <X>(f: NotFunction<X>): <A, E, R>( self: Micro<A, E, R> ) => [X] extends [Micro<infer _A1, infer E1, infer R1>] ? Micro<A, E | E1, R | R1> : Micro<A, E, R> /** * 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 */ <A, E, R, X>(self: Micro<A, E, R>, f: (a: NoInfer<A>) => X): [X] extends [Micro<infer _A1, infer E1, infer R1>] ? Micro<A, E | E1, R | R1> : Micro<A, E, R> /** * 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 */ <A, E, R, X>(self: Micro<A, E, R>, f: NotFunction<X>): [X] extends [Micro<infer _A1, infer E1, infer R1>] ? Micro<A, E | E1, R | R1> : Micro<A, E, R> } = dual( 2, <A, E, R, B, E2, R2>(self: Micro<A, E, R>, f: (a: A) => Micro<B, E2, R2>): Micro<A, E | E2, R | R2> => 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 = <A, E, R>(self: Micro<A, E, R>): Micro<void, E, R> => flatMap(self, (_) => exitVoid) /** * Access the `MicroExit` of the given `Micro` effect. * * @since 3.4.6 * @experimental * @category mapping & sequencing */ export const exit = <A, E, R>(self: Micro<A, E, R>): Micro<MicroExit<A, E>, 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 = <A, E, R>(self: Micro<A, E, R>): Micro<A, MicroCause<E>, 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 = <Eff extends Micro<any, any, any>>( all: Iterable<Eff> ): Micro<Micro.Success<Eff>, Micro.Error<Eff>, Micro.Context<Eff>> => withMicroFiber((parent) => async((resume) => { const effects = Arr.fromIterable(all) const len = effects.length let doneCount = 0 let done = false const fibers = new Set<MicroFiber<any, any>>() const causes: Array<MicroCause<any>> = [] const onExit = (exit: MicroExit<any, any>) => { 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 = <Eff extends Micro<any, any, any>>( all: Iterable<Eff> ): Micro<Micro.Success<Eff>, Micro.Error<Eff>, Micro.Context<Eff>> => withMicroFiber((parent) => async((resume) => { let done = false const fibers = new Set<MicroFiber<any, any>>() const onExit = (exit: MicroExit<any, any>) => { 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 */ <A2, E2, R2>(that: Micro<A2, E2, R2>): <A, E, R>(self: Micro<A, E, R>) => Micro<A | A2, E | E2, R | R2> /** * 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 */ <A, E, R, A2, E2, R2>(self: Micro<A, E, R>, that: Micro<A2, E2, R2>): Micro<A | A2, E | E2, R | R2> } = dual( 2, <A, E, R, A2, E2, R2>(self: Micro<A, E, R>, that: Micro<A2, E2, R2>): Micro<A | A2, E | E2, R | R2> => 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 */ <A2, E2, R2>(that: Micro<A2, E2, R2>): <A, E, R>(self: Micro<A, E, R>) => Micro<A | A2, E | E2, R | R2> /** * 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 */ <A, E, R, A2, E2, R2>(self: Micro<A, E, R>, that: Micro<A2, E2, R2>): Micro<A | A2, E | E2, R | R2> } = dual( 2, <A, E, R, A2, E2, R2>(self: Micro<A, E, R>, that: Micro<A2, E2, R2>): Micro<A | A2, E | E2, R | R2> => 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 */ <A, B, E2, R2>(f: (a: A) => Micro<B, E2, R2>): <E, R>(self: Micro<A, E, R>) => Micro<B, E | E2, R | R2> /** * Map the success value of this `Micro` effect to another `Micro` effect, then * flatten the result. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ <A, E, R, B, E2, R2>(self: Micro<A, E, R>, f: (a: A) => Micro<B, E2, R2>): Micro<B, E | E2, R | R2> } = dual( 2, <A, E, R, B, E2, R2>( self: Micro<A, E, R>, f: (a: A) => Micro<B, E2, R2> ): Micro<B, E | E2, R | R2> => { 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 = <A, E, R, E2, R2>( self: Micro<Micro<A, E, R>, E2, R2> ): Micro<A, E | E2, R | R2> => 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 */ <A, B>(f: (a: A) => B): <E, R>(self: Micro<A, E, R>) => Micro<B, E, R> /** * Transforms the success value of the `Micro` effect with the specified * function. * * @since 3.4.0 * @experimental * @category mapping & sequencing */ <A, E, R, B>(self: Micro<A, E, R>, f: (a: A) => B): Micro<B, E, R> } = dual( 2, <A, E, R, B>(self: Micro<A, E, R>, f: (a: A) => B): Micro<B, E, R> => 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<A, E = never> = | MicroExit.Success<A, E> | MicroExit.Failure<A, E> /** * @since 3.4.6 * @experimental * @category MicroExit */ export declare namespace MicroExit { /** * @since 3.4.6 * @experimental * @category MicroExit */ export interface Proto<out A, out E = never> extends Micro<A, E> { readonly [MicroExitTypeId]: MicroExitTypeId } /** * @since 3.4.6 * @experimental * @category MicroExit */ export interface Success<out A, out E> extends Proto<A, E> { readonly _tag: "Success" readonly value: A } /** * @since 3.4.6 * @experimental * @category MicroExit */ export interface Failure<out A, out E> extends Proto<A, E> { readonly _tag: "Failure" readonly cause: MicroCause<E> } } /** * @since 3.4.6 * @experimental * @category MicroExit */ export const isMicroExit = (u: unknown): u is MicroExit<unknown, unknown> => hasProperty(u, MicroExitTypeId) /** * @since 3.4.6 * @experimental * @category MicroExit */ export const exitSucceed: <A>(a: A) => MicroExit<A, never> = succeed as any /** * @since 3.4.6 * @experimental * @category MicroExit */ export const exitFailCause: <E>(cause: MicroCause<E>) => MicroExit<never, E> = failCause as any /** * @since 3.4.6 * @experimental * @category MicroExit */ export const exitInterrupt: MicroExit<never> = exitFailCause(causeInterrupt()) /** * @since 3.4.6 * @experimental * @category MicroExit */ export const exitFail = <E>(e: E): MicroExit<never, E> => exitFailCause(causeFail(e)) /** * @since 3.4.6 * @experimental * @category MicroExit */ export const exitDie = (defect: unknown): MicroExit<never> => exitFailCause(causeDie(defect)) /** * @since 3.4.6 * @experimental * @category MicroExit */ export const exitIsSuccess = <A, E>( self: MicroExit<A, E> ): self is MicroExit.Success<A, E> => self._tag === "Success" /** * @since 3.4.6 * @experimental * @category MicroExit */