diff --git a/.changeset/angry-falcons-fail.md b/.changeset/angry-falcons-fail.md new file mode 100644 index 0000000000..57c1efc7c9 --- /dev/null +++ b/.changeset/angry-falcons-fail.md @@ -0,0 +1,73 @@ +--- +"effect": patch +--- + +Micro: align with `Effect` module (renamings and new combinators). + +General naming convention rule: ``. + +- `Failure` -> `MicroCause` + - `Failure.Expected` -> `MicroCause.Fail` + - `Failure.Unexpected` -> `MicroCause.Die` + - `Failure.Aborted` -> `MicroCause.Interrupt` + - `FailureExpected` -> `causeFail` + - `FailureUnexpected` -> `causeDie` + - `FailureAborted` -> `causeInterrupt` + - `failureIsExpected` -> `causeIsFail` + - `failureIsExpected` -> `causeIsFail` + - `failureIsUnexpected` -> `causeIsDie` + - `failureIsAborted` -> `causeIsInterrupt` + - `failureSquash` -> `causeSquash` + - `failureWithTrace` -> `causeWithTrace` +- `Result` -> `MicroExit` + - `ResultAborted` -> `exitInterrupt` + - `ResultSuccess` -> `exitSucceed` + - `ResultFail` -> `exitFail` + - `ResultFailUnexpected` -> `exitDie` + - `ResultFailWith` -> `exitFailCause` + - `resultIsSuccess` -> `exitIsSuccess` + - `resultIsFailure` -> `exitIsFailure` + - `resultIsAborted` -> `exitIsInterrupt` + - `resultIsFailureExpected` -> `exitIsFail` + - `resultIsFailureUnexpected` -> `exitIsDie` + - `resultVoid` -> `exitVoid` +- `DelayFn` -> `MicroSchedule` + - `delayExponential` -> `scheduleExponential` + - `delaySpaced` -> `scheduleSpaced` + - `delayWithMax` -> `scheduleWithMaxDelay` + - `delayWithMaxElapsed` -> `scheduleWithMaxElapsed` + - `delayWithRecurs` -> `scheduleRecurs` and make it a constructor + - add `scheduleAddDelay` combinator + - add `scheduleUnion` combinator + - add `scheduleIntersect` combinator +- `Handle` + - `abort` -> `interrupt` + - `unsafeAbort` -> `unsafeInterrupt` +- `provideServiceMicro` -> `provideServiceEffect` +- `fromResult` -> `fromExit` +- `fromResultSync` -> `fromExitSync` +- `failWith` -> `failCause` +- `failWithSync` -> `failCauseSync` +- `asResult` -> `exit` +- `filterOrFailWith` -> `filterOrFailCause` +- `repeatResult` -> `repeatExit` +- `catchFailure` -> `catchAllCause` +- `catchFailureIf` -> `catchCauseIf` +- `catchExpected` -> `catchAll` +- `catchUnexpected` -> `catchAllDefect` +- `tapFailure` -> `tapErrorCause` +- `tapFailureIf` -> `tapErrorCauseIf` +- `tapExpected` -> `tapError` +- `tapUnexpected` -> `tapDefect` +- `mapFailure` -> `mapErrorCause` +- `matchFailureMicro` -> `matchCauseEffect` +- `matchFailure` -> `matchCause` +- `matchMicro` -> `matchEffect` +- `onResult` -> `onExit` +- `onResultIf` -> `onExitIf` +- `onFailure` -> `onError` +- `onAbort` -> `onInterrupt` +- `abort` -> `interrupt` +- `runPromiseResult` -> `runPromiseExit` +- `runSyncResult` -> `runSyncExit` +- rename `delay` option to `schedule` diff --git a/.changeset/fuzzy-bananas-push.md b/.changeset/fuzzy-bananas-push.md new file mode 100644 index 0000000000..82b94ff99d --- /dev/null +++ b/.changeset/fuzzy-bananas-push.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Micro: rename `timeout` to `timeoutOption`, and add a `timeout` that fails with a `TimeoutException` diff --git a/.changeset/olive-rockets-hang.md b/.changeset/olive-rockets-hang.md new file mode 100644 index 0000000000..b4779164bf --- /dev/null +++ b/.changeset/olive-rockets-hang.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Micro: move MicroExit types to a namespace diff --git a/packages/effect/dtslint/Micro.ts b/packages/effect/dtslint/Micro.ts new file mode 100644 index 0000000000..6fe64a1d85 --- /dev/null +++ b/packages/effect/dtslint/Micro.ts @@ -0,0 +1,22 @@ +import { hole, Micro } from "effect" + +// ------------------------------------------------------------------------------------- +// catchCauseIf +// ------------------------------------------------------------------------------------- + +// $ExpectType Micro +hole>().pipe(Micro.catchCauseIf( + (cause): cause is Micro.MicroCause => true, + ( + _cause // $ExpectType MicroCause + ) => hole>() +)) + +// $ExpectType Micro +Micro.catchCauseIf( + hole>(), + (cause): cause is Micro.MicroCause => true, + ( + _cause // $ExpectType MicroCause + ) => hole>() +) diff --git a/packages/effect/src/Micro.ts b/packages/effect/src/Micro.ts index f48a205a46..3386b555f3 100644 --- a/packages/effect/src/Micro.ts +++ b/packages/effect/src/Micro.ts @@ -64,7 +64,7 @@ export type runSymbol = typeof runSymbol */ export interface Micro extends Effect { readonly [TypeId]: Micro.Variance - readonly [runSymbol]: (env: Env, onResult: (result: Result) => void) => void + readonly [runSymbol]: (env: Env, onExit: (exit: MicroExit) => void) => void [Unify.typeSymbol]?: unknown [Unify.unifySymbol]?: MicroUnify [Unify.ignoreSymbol]?: MicroUnifyIgnore @@ -145,44 +145,50 @@ export interface MicroIterator> { } // ---------------------------------------------------------------------------- -// Failures +// MicroCause // ---------------------------------------------------------------------------- /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category failure + * @category MicroCause */ -export const FailureTypeId = Symbol.for("effect/Micro/Failure") +export const MicroCauseTypeId = Symbol.for("effect/Micro/MicroCause") /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category failure + * @category MicroCause */ -export type FailureTypeId = typeof FailureTypeId +export type MicroCauseTypeId = typeof MicroCauseTypeId /** - * A Micro Failure is a data type that represents the different ways a Micro can fail. + * A Micro Cause is a data type that represents the different ways a Micro can fail. * - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category failure + * @category MicroCause */ -export type Failure = Failure.Unexpected | Failure.Expected | Failure.Aborted +export type MicroCause = MicroCause.Die | MicroCause.Fail | MicroCause.Interrupt /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category failure + * @category MicroCause */ -export declare namespace Failure { +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 [FailureTypeId]: { + readonly [MicroCauseTypeId]: { _E: Covariant } readonly _tag: Tag @@ -190,37 +196,37 @@ export declare namespace Failure { } /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category failure + * @category MicroCause */ - export interface Unexpected extends Proto<"Unexpected", never> { + export interface Die extends Proto<"Die", never> { readonly defect: unknown } /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category failure + * @category MicroCause */ - export interface Expected extends Proto<"Expected", E> { + export interface Fail extends Proto<"Fail", E> { readonly error: E } /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category failure + * @category MicroCause */ - export interface Aborted extends Proto<"Aborted", never> {} + export interface Interrupt extends Proto<"Interrupt", never> {} } -const failureVariance = { +const microCauseVariance = { _E: identity } -abstract class FailureImpl extends globalThis.Error implements Failure.Proto { - readonly [FailureTypeId]: { +abstract class MicroCauseImpl extends globalThis.Error implements MicroCause.Proto { + readonly [MicroCauseTypeId]: { _E: Covariant } constructor( @@ -228,19 +234,19 @@ abstract class FailureImpl extends globalThis.Error imple originalError: unknown, readonly traces: ReadonlyArray ) { - const failureName = `Failure${_tag}` + const causeName = `MicroCause.${_tag}` let name: string let message: string let stack: string if (originalError instanceof globalThis.Error) { - name = `(${failureName}) ${originalError.name}` + name = `(${causeName}) ${originalError.name}` message = originalError.message as string const messageLines = message.split("\n").length stack = originalError.stack - ? `(${failureName}) ${originalError.stack.split("\n").slice(0, messageLines + 3).join("\n")}` + ? `(${causeName}) ${originalError.stack.split("\n").slice(0, messageLines + 3).join("\n")}` : `${name}: ${message}` } else { - name = failureName + name = causeName message = toStringUnknown(originalError, 0) stack = `${name}: ${message}` } @@ -248,7 +254,7 @@ abstract class FailureImpl extends globalThis.Error imple stack += `\n ${traces.join("\n ")}` } super(message) - this[FailureTypeId] = failureVariance + this[MicroCauseTypeId] = microCauseVariance this.name = name this.stack = stack } @@ -263,188 +269,211 @@ abstract class FailureImpl extends globalThis.Error imple } } -class FailureExpectedImpl extends FailureImpl<"Expected", E> implements Failure.Expected { +class FailImpl extends MicroCauseImpl<"Fail", E> implements MicroCause.Fail { constructor(readonly error: E, traces: ReadonlyArray = []) { - super("Expected", error, traces) + super("Fail", error, traces) } } /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category failure + * @category MicroCause */ -export const FailureExpected = (error: E, traces: ReadonlyArray = []): Failure => - new FailureExpectedImpl(error, traces) +export const causeFail = (error: E, traces: ReadonlyArray = []): MicroCause => new FailImpl(error, traces) -class FailureUnexpectedImpl extends FailureImpl<"Unexpected", never> implements Failure.Unexpected { +class DieImpl extends MicroCauseImpl<"Die", never> implements MicroCause.Die { constructor(readonly defect: unknown, traces: ReadonlyArray = []) { - super("Unexpected", defect, traces) + super("Die", defect, traces) } } /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category failure + * @category MicroCause */ -export const FailureUnexpected = (defect: unknown, traces: ReadonlyArray = []): Failure => - new FailureUnexpectedImpl(defect, traces) +export const causeDie = (defect: unknown, traces: ReadonlyArray = []): MicroCause => + new DieImpl(defect, traces) -class FailureAbortedImpl extends FailureImpl<"Aborted", never> implements Failure.Aborted { +class InterruptImpl extends MicroCauseImpl<"Interrupt", never> implements MicroCause.Interrupt { constructor(traces: ReadonlyArray = []) { - super("Aborted", "aborted", traces) + super("Interrupt", "interrupted", traces) } } /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category failure + * @category MicroCause */ -export const FailureAborted = (traces: ReadonlyArray = []): Failure => new FailureAbortedImpl(traces) +export const causeInterrupt = (traces: ReadonlyArray = []): MicroCause => new InterruptImpl(traces) /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category failure + * @category MicroCause */ -export const failureIsExpected = (self: Failure): self is Failure.Expected => self._tag === "Expected" +export const causeIsFail = (self: MicroCause): self is MicroCause.Fail => self._tag === "Fail" /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category failure + * @category MicroCause */ -export const failureIsUnexpected = (self: Failure): self is Failure.Unexpected => self._tag === "Unexpected" +export const causeIsDie = (self: MicroCause): self is MicroCause.Die => self._tag === "Die" /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category failure + * @category MicroCause */ -export const failureIsAborted = (self: Failure): self is Failure.Aborted => self._tag === "Aborted" +export const causeIsInterrupt = (self: MicroCause): self is MicroCause.Interrupt => self._tag === "Interrupt" /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category failure + * @category MicroCause */ -export const failureSquash = (self: Failure): unknown => - self._tag === "Expected" ? self.error : self._tag === "Unexpected" ? self.defect : self +export const causeSquash = (self: MicroCause): unknown => + self._tag === "Fail" ? self.error : self._tag === "Die" ? self.defect : self /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category failure + * @category MicroCause */ -export const failureWithTrace: { - (trace: string): (self: Failure) => Failure - (self: Failure, trace: string): Failure -} = dual(2, (self: Failure, trace: string): Failure => { - if (self._tag === "Expected") { - return FailureExpected(self.error, [...self.traces, trace]) - } else if (self._tag === "Unexpected") { - return FailureUnexpected(self.defect, [...self.traces, trace]) +export const causeWithTrace: { + (trace: string): (self: MicroCause) => 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) } - return FailureAborted([...self.traces, trace]) }) // ---------------------------------------------------------------------------- -// Result +// MicroExit // ---------------------------------------------------------------------------- /** - * The Micro Result type is a data type that represents the result of a Micro + * @since 3.4.6 + * @experimental + * @category MicroExit + */ +export declare namespace MicroExit { + /** + * @since 3.4.6 + * @experimental + * @category MicroExit + */ + export type Success = Either.Right, A> + + /** + * @since 3.4.6 + * @experimental + * @category MicroExit + */ + export type Failure = Either.Left, A> +} + +/** + * The MicroExit type is a data type that represents the result of a Micro * computation. * * It uses the `Either` data type to represent the success and failure cases. * - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category result + * @category MicroExit */ -export type Result = Either.Either> +export type MicroExit = MicroExit.Success | MicroExit.Failure /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category result + * @category MicroExit */ -export const ResultAborted: Result = Either.left(FailureAborted()) +export const exitInterrupt: MicroExit = Either.left(causeInterrupt()) /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category result + * @category MicroExit */ -export const ResultSuccess: (a: A) => Result = Either.right +export const exitSucceed: (a: A) => MicroExit = Either.right /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category result + * @category MicroExit */ -export const ResultFail = (e: E): Result => Either.left(FailureExpected(e)) +export const exitFail = (e: E): MicroExit => Either.left(causeFail(e)) /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category result + * @category MicroExit */ -export const ResultFailUnexpected = (defect: unknown): Result => Either.left(FailureUnexpected(defect)) +export const exitDie = (defect: unknown): MicroExit => Either.left(causeDie(defect)) /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category result + * @category MicroExit */ -export const ResultFailWith: (failure: Failure) => Result = Either.left +export const exitFailCause: (cause: MicroCause) => MicroExit = Either.left /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category result + * @category MicroExit */ -export const resultIsSuccess: (self: Result) => self is Either.Right, A> = Either.isRight +export const exitIsSuccess: (self: MicroExit) => self is MicroExit.Success = Either.isRight /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category result + * @category MicroExit */ -export const resultIsFailure: (self: Result) => self is Either.Left, A> = Either.isLeft +export const exitIsFailure: (self: MicroExit) => self is MicroExit.Failure = Either.isLeft /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category result + * @category MicroExit */ -export const resultIsAborted = (self: Result): self is Either.Left => - resultIsFailure(self) && self.left._tag === "Aborted" +export const exitIsInterrupt = (self: MicroExit): self is Either.Left => + exitIsFailure(self) && self.left._tag === "Interrupt" /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category result + * @category MicroExit */ -export const resultIsFailureExpected = (self: Result): self is Either.Left, A> => - resultIsFailure(self) && self.left._tag === "Expected" +export const exitIsFail = (self: MicroExit): self is Either.Left, A> => + exitIsFailure(self) && self.left._tag === "Fail" /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category result + * @category MicroExit */ -export const resultIsFailureUnexpected = (self: Result): self is Either.Left => - resultIsFailure(self) && self.left._tag === "Unexpected" +export const exitIsDie = (self: MicroExit): self is Either.Left => + exitIsFailure(self) && self.left._tag === "Die" /** - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category result + * @category MicroExit */ -export const resultVoid: Result = ResultSuccess(void 0) +export const exitVoid: MicroExit = exitSucceed(void 0) // ---------------------------------------------------------------------------- // env @@ -582,8 +611,8 @@ export const envMutate: { * @category environment */ export const service = (tag: Context.Tag): Micro => - make(function(env, onResult) { - onResult(ResultSuccess(Context.get(envGet(env, currentContext) as Context.Context, tag as any) as S)) + make(function(env, onExit) { + onExit(exitSucceed(Context.get(envGet(env, currentContext) as Context.Context, tag as any) as S)) }) /** @@ -598,8 +627,8 @@ export const service = (tag: Context.Tag): Micro => * @category environment */ export const serviceOption = (tag: Context.Tag): Micro> => - make(function(env, onResult) { - onResult(ResultSuccess(Context.getOption(envGet(env, currentContext) as Context.Context, tag))) + make(function(env, onExit) { + onExit(exitSucceed(Context.getOption(envGet(env, currentContext) as Context.Context, tag))) }) /** @@ -610,7 +639,7 @@ export const serviceOption = (tag: Context.Tag): Micro(envRef: EnvRef): Micro => - make((env, onResult) => onResult(Either.right(envGet(env, envRef)))) + make((env, onExit) => onExit(Either.right(envGet(env, envRef)))) /** * Set the value of the given `EnvRef` for the duration of the effect. @@ -625,7 +654,7 @@ export const locally: { } = dual( 3, (self: Micro, fiberRef: EnvRef, value: A): Micro => - make((env, onResult) => self[runSymbol](envSet(env, fiberRef, value), onResult)) + make((env, onExit) => self[runSymbol](envSet(env, fiberRef, value), onExit)) ) /** @@ -650,10 +679,10 @@ export const provideContext: { } = dual( 2, (self: Micro, provided: Context.Context): Micro> => - make(function(env, onResult) { + make(function(env, onExit) { const context = envGet(env, currentContext) const nextEnv = envSet(env, currentContext, Context.merge(context, provided)) - self[runSymbol](nextEnv, onResult) + self[runSymbol](nextEnv, onExit) }) ) @@ -670,10 +699,10 @@ export const provideService: { } = dual( 3, (self: Micro, tag: Context.Tag, service: S): Micro> => - make(function(env, onResult) { + make(function(env, onExit) { const context = envGet(env, currentContext) const nextEnv = envSet(env, currentContext, Context.add(context, tag, service)) - self[runSymbol](nextEnv, onResult) + self[runSymbol](nextEnv, onExit) }) ) @@ -681,11 +710,11 @@ export const provideService: { * Create a service using the provided `Micro` effect, and add it to the * current context. * - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category environment */ -export const provideServiceMicro: { +export const provideServiceEffect: { ( tag: Context.Tag, acquire: Micro @@ -827,36 +856,36 @@ const microDepthState = globalValue("effect/Micro/microDepthState", () => ({ maxDepthBeforeYield: currentMaxDepthBeforeYield.initial })) -const unsafeMake = ( - run: (env: Env, onResult: (result: Result) => void) => void +const unsafeMake = ( + run: (env: Env, onExit: (exit: MicroExit) => void) => void ): Micro => { const self = Object.create(MicroProto) self[runSymbol] = run return self } -const unsafeMakeOptions = ( - run: (env: Env, onResult: (result: Result) => void) => void, +const unsafeMakeOptions = ( + run: (env: Env, onExit: (exit: MicroExit) => void) => void, checkAbort: boolean ): Micro => - unsafeMake(function execute(env, onResult) { + unsafeMake(function execute(env, onExit) { if ( checkAbort && env.refs[currentInterruptible.key] !== false && (env.refs[currentAbortSignal.key] as AbortSignal).aborted ) { - return onResult(ResultAborted) + return onExit(exitInterrupt) } microDepthState.depth++ if (microDepthState.depth === 1) { microDepthState.maxDepthBeforeYield = envGet(env, currentMaxDepthBeforeYield) } if (microDepthState.depth >= microDepthState.maxDepthBeforeYield) { - yieldAdd(() => execute(env, onResult)) + yieldAdd(() => execute(env, onExit)) } else { try { - run(env, onResult) + run(env, onExit) } catch (err) { - onResult(ResultFailUnexpected(err)) + onExit(exitDie(err)) } } microDepthState.depth-- @@ -871,32 +900,32 @@ const unsafeMakeOptions = ( * @experimental * @category constructors */ -export const make = ( - run: (env: Env, onResult: (result: Result) => void) => void +export const make = ( + run: (env: Env, onExit: (exit: MicroExit) => void) => void ): Micro => unsafeMakeOptions(run, true) /** - * Converts a `Result` into a `Micro` effect. + * Converts a `MicroExit` into a `Micro` effect. * - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category constructors */ -export const fromResult = (self: Result): Micro => - make(function(_env, onResult) { - onResult(self) +export const fromExit = (self: MicroExit): Micro => + make(function(_env, onExit) { + onExit(self) }) /** - * Converts a lazy `Result` into a `Micro` effect. + * Converts a lazy `MicroExit` into a `Micro` effect. * - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category constructors */ -export const fromResultSync = (self: LazyArg>): Micro => - make(function(_env, onResult) { - onResult(self()) +export const fromExitSync = (self: LazyArg>): Micro => + make(function(_env, onExit) { + onExit(self()) }) /** @@ -906,7 +935,7 @@ export const fromResultSync = (self: LazyArg>): Micro = * @experimental * @category constructors */ -export const succeed = (a: A): Micro => fromResult(ResultSuccess(a)) +export const succeed = (a: A): Micro => fromExit(exitSucceed(a)) /** * Creates a `Micro` effect that will succeed with `Option.Some` of the value. @@ -929,19 +958,19 @@ export const succeedNone: Micro> = succeed(Option.none()) /** * Creates a `Micro` effect that will fail with the specified error. * - * This will result in a `FailureExpected`, where the error is tracked at the + * This will result in a `CauseFail`, where the error is tracked at the * type level. * * @since 3.4.0 * @experimental * @category constructors */ -export const fail = (e: E): Micro => fromResult(ResultFail(e)) +export const fail = (e: E): Micro => fromExit(exitFail(e)) /** * Creates a `Micro` effect that will fail with the lazily evaluated error. * - * This will result in a `FailureExpected`, where the error is tracked at the + * This will result in a `CauseFail`, where the error is tracked at the * type level. * * @since 3.4.0 @@ -949,54 +978,54 @@ export const fail = (e: E): Micro => fromResult(ResultFail(e)) * @category constructors */ export const failSync = (e: LazyArg): Micro => - make(function(_env, onResult) { - onResult(ResultFail(e())) + make(function(_env, onExit) { + onExit(exitFail(e())) }) /** * Creates a `Micro` effect that will die with the specified error. * - * This will result in a `FailureUnexpected`, where the error is not tracked at + * This will result in a `CauseDie`, where the error is not tracked at * the type level. * * @since 3.4.0 * @experimental * @category constructors */ -export const die = (defect: unknown): Micro => fromResult(ResultFailUnexpected(defect)) +export const die = (defect: unknown): Micro => fromExit(exitDie(defect)) /** - * Creates a `Micro` effect that will fail with the specified `Failure`. + * Creates a `Micro` effect that will fail with the specified `MicroCause`. * - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category constructors */ -export const failWith = (failure: Failure): Micro => fromResult(ResultFailWith(failure)) +export const failCause = (cause: MicroCause): Micro => fromExit(exitFailCause(cause)) /** - * Creates a `Micro` effect that will fail with the lazily evaluated `Failure`. + * Creates a `Micro` effect that will fail with the lazily evaluated `MicroCause`. * - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category constructors */ -export const failWithSync = (failure: LazyArg>): Micro => - fromResultSync(() => ResultFailWith(failure())) +export const failCauseSync = (cause: LazyArg>): Micro => + fromExitSync(() => exitFailCause(cause())) /** * Creates a `Micro` effect that will succeed with the lazily evaluated value. * * If the evaluation of the value throws an error, the effect will fail with - * `FailureUnexpected`. + * `CauseDie`. * * @since 3.4.0 * @experimental * @category constructors */ export const sync = (evaluate: LazyArg): Micro => - make(function(_env, onResult) { - onResult(ResultSuccess(evaluate())) + make(function(_env, onExit) { + onExit(exitSucceed(evaluate())) }) /** @@ -1009,8 +1038,8 @@ export const sync = (evaluate: LazyArg): Micro => * @category constructors */ export const fromOption = (option: Option.Option): Micro => - make(function(_env, onResult) { - onResult(option._tag === "Some" ? ResultSuccess(option.value) : ResultFail(new NoSuchElementException({}))) + make(function(_env, onExit) { + onExit(option._tag === "Some" ? exitSucceed(option.value) : exitFail(new NoSuchElementException({}))) }) /** @@ -1023,8 +1052,8 @@ export const fromOption = (option: Option.Option): Micro(either: Either.Either): Micro => - make(function(_env, onResult) { - onResult(either._tag === "Right" ? either : ResultFail(either.left) as any) + make(function(_env, onExit) { + onExit(either._tag === "Right" ? either as MicroExit : exitFail(either.left)) }) /** @@ -1035,8 +1064,8 @@ export const fromEither = (either: Either.Either): Micro => * @category constructors */ export const suspend = (evaluate: LazyArg>): Micro => - make(function(env, onResult) { - evaluate()[runSymbol](env, onResult) + make(function(env, onExit) { + evaluate()[runSymbol](env, onExit) }) const void_: Micro = succeed(void 0) @@ -1065,16 +1094,16 @@ export { export const async = ( register: (resume: (effect: Micro) => void, signal: AbortSignal) => void | Micro ): Micro => - make(function(env, onResult) { + make(function(env, onExit) { let resumed = false const controller = register.length > 1 ? new AbortController() : undefined const signal = envGet(env, currentAbortSignal) let cleanup: Micro | void = undefined function onAbort() { if (cleanup) { - resume(uninterruptible(andThen(cleanup, fromResult(ResultAborted)))) + resume(uninterruptible(andThen(cleanup, fromExit(exitInterrupt)))) } else { - resume(fromResult(ResultAborted)) + resume(fromExit(exitInterrupt)) } if (controller !== undefined) { controller.abort() @@ -1086,7 +1115,7 @@ export const async = ( } resumed = true signal.removeEventListener("abort", onAbort) - effect[runSymbol](env, onResult) + effect[runSymbol](env, onExit) } cleanup = controller === undefined ? (register as any)(resume) @@ -1099,11 +1128,11 @@ const try_ = (options: { try: LazyArg catch: (error: unknown) => E }): Micro => - make(function(_env, onResult) { + make(function(_env, onExit) { try { - onResult(ResultSuccess(options.try())) + onExit(exitSucceed(options.try())) } catch (err) { - onResult(ResultFail(options.catch(err))) + onExit(exitFail(options.catch(err))) } }) export { @@ -1127,7 +1156,7 @@ export { /** * Wrap a `Promise` into a `Micro` effect. Any errors will result in a - * `FailureUnexpected`. + * `CauseDie`. * * @since 3.4.0 * @experimental @@ -1208,8 +1237,8 @@ const yieldAdd = (task: () => void) => { * @experimental * @category constructors */ -export const yieldNow: Micro = make(function(_env, onResult) { - yieldAdd(() => onResult(resultVoid)) +export const yieldNow: Micro = make(function(_env, onExit) { + yieldAdd(() => onExit(exitVoid)) }) /** @@ -1252,7 +1281,7 @@ export const gen = >, AEff>( [Eff] extends [never] ? never : [Eff] extends [YieldWrap>] ? E : never, [Eff] extends [never] ? never : [Eff] extends [YieldWrap>] ? R : never > => - make(function(env, onResult) { + make(function(env, onExit) { const iterator: Generator = args.length === 1 ? args[0]() : args[1].call(args[0]) let running = false let value: any = undefined @@ -1263,21 +1292,21 @@ export const gen = >, AEff>( while (shouldContinue) { const result = iterator.next(value) if (result.done) { - return onResult(ResultSuccess(result.value)) + return onExit(exitSucceed(result.value)) } shouldContinue = false - yieldWrapGet(result.value)[runSymbol](env, function(result) { - if (result._tag === "Left") { - onResult(result) + yieldWrapGet(result.value)[runSymbol](env, function(exit) { + if (exit._tag === "Left") { + onExit(exit) } else { shouldContinue = true - value = result.right + value = exit.right if (!running) run() } }) } } catch (err) { - onResult(ResultFailUnexpected(err)) + onExit(exitDie(err)) } running = false } @@ -1296,10 +1325,10 @@ export const gen = >, AEff>( * @category mapping & sequencing */ export const flatten = (self: Micro, E2, R2>): Micro => - make(function(env, onResult) { + make(function(env, onExit) { self[runSymbol]( env, - (result) => result._tag === "Left" ? onResult(result as any) : result.right[runSymbol](env, onResult) + (exit) => exit._tag === "Left" ? onExit(exit as MicroExit) : exit.right[runSymbol](env, onExit) ) }) @@ -1315,9 +1344,9 @@ export const map: { (f: (a: A) => B): (self: Micro) => Micro (self: Micro, f: (a: A) => B): Micro } = dual(2, (self: Micro, f: (a: A) => B): Micro => - make(function(env, onResult) { - self[runSymbol](env, function(result) { - onResult(result._tag === "Left" ? result as any : ResultSuccess(f(result.right))) + make(function(env, onExit) { + self[runSymbol](env, function(exit) { + onExit(exit._tag === "Left" ? exit as MicroExit : exitSucceed(f(exit.right))) }) })) @@ -1341,7 +1370,7 @@ export const as: { * @experimental * @category mapping & sequencing */ -export const asSome = (self: Micro): Micro, E, R> => map(self, Option.some) as any +export const asSome = (self: Micro): Micro, E, R> => map(self, Option.some) /** * Map the success value of this `Micro` effect to another `Micro` effect, then @@ -1357,12 +1386,12 @@ export const flatMap: { } = dual( 2, (self: Micro, f: (a: A) => Micro): Micro => - make(function(env, onResult) { - self[runSymbol](env, function(result) { - if (result._tag === "Left") { - return onResult(result as any) + make(function(env, onExit) { + self[runSymbol](env, function(exit) { + if (exit._tag === "Left") { + return onExit(exit as MicroExit) } - f(result.right)[runSymbol](env, onResult) + f(exit.right)[runSymbol](env, onExit) }) }) ) @@ -1375,7 +1404,7 @@ export const flatMap: { * @category mapping & sequencing */ export const flip = (self: Micro): Micro => - matchMicro(self, { + matchEffect(self, { onFailure: succeed, onSuccess: fail }) @@ -1417,18 +1446,18 @@ export const andThen: { } = dual( 2, (self: Micro, f: any): Micro => - make(function(env, onResult) { - self[runSymbol](env, function(result) { - if (result._tag === "Left") { - return onResult(result as any) + make(function(env, onExit) { + self[runSymbol](env, function(exit) { + if (exit._tag === "Left") { + return onExit(exit as MicroExit) } else if (envGet(env, currentAbortSignal).aborted) { - return onResult(ResultAborted) + return onExit(exitInterrupt) } - const value = isMicro(f) ? f : typeof f === "function" ? f(result.right) : f + const value = isMicro(f) ? f : typeof f === "function" ? f(exit.right) : f if (isMicro(value)) { - value[runSymbol](env, onResult) + value[runSymbol](env, onExit) } else { - onResult(ResultSuccess(value)) + onExit(exitSucceed(value)) } }) }) @@ -1469,23 +1498,23 @@ export const tap: { } = dual( 2, (self: Micro, f: (a: A) => Micro): Micro => - make(function(env, onResult) { - self[runSymbol](env, function(selfResult) { - if (selfResult._tag === "Left") { - return onResult(selfResult as any) + make(function(env, onExit) { + self[runSymbol](env, function(selfExit) { + if (selfExit._tag === "Left") { + return onExit(selfExit as MicroExit) } else if (envGet(env, currentAbortSignal).aborted) { - return onResult(ResultAborted) + return onExit(exitInterrupt) } - const value = isMicro(f) ? f : typeof f === "function" ? f(selfResult.right) : f + const value = isMicro(f) ? f : typeof f === "function" ? f(selfExit.right) : f if (isMicro(value)) { - value[runSymbol](env, function(tapResult) { - if (tapResult._tag === "Left") { - return onResult(tapResult) + value[runSymbol](env, function(tapExit) { + if (tapExit._tag === "Left") { + return onExit(tapExit) } - onResult(selfResult) + onExit(selfExit) }) } else { - onResult(selfResult) + onExit(selfExit) } }) }) @@ -1501,28 +1530,28 @@ export const tap: { export const asVoid = (self: Micro): Micro => map(self, (_) => void 0) /** - * Access the `Result` of the given `Micro` effect. + * Access the `MicroExit` of the given `Micro` effect. * - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category mapping & sequencing */ -export const asResult = (self: Micro): Micro, never, R> => - make(function(env, onResult) { - self[runSymbol](env, function(result) { - onResult(ResultSuccess(result)) +export const exit = (self: Micro): Micro, never, R> => + make(function(env, onExit) { + self[runSymbol](env, function(exit) { + onExit(exitSucceed(exit)) }) }) /** - * Replace the error type of the given `Micro` with the full `Failure` object. + * 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> => - catchFailure(self, (failure) => fail(failure)) +export const sandbox = (self: Micro): Micro, R> => + catchAllCause(self, (cause) => fail(cause)) function forkSignal(env: Env) { const controller = new AbortController() @@ -1552,26 +1581,26 @@ function forkSignal(env: Env) { export const raceAll = >( all: Iterable ): Micro, Micro.Error, Micro.Context> => - make(function(env, onResult) { + make(function(env, onExit) { const [envWithSignal, onAbort] = forkSignal(env) const effects = Array.from(all) let len = effects.length let index = 0 let done = 0 - let result: Result | undefined = undefined - const failures: Array> = [] - function onDone(result_: Result) { + let exit: MicroExit | undefined = undefined + const causes: Array> = [] + function onDone(exit_: MicroExit) { done++ - if (result_._tag === "Right" && result === undefined) { + if (exit_._tag === "Right" && exit === undefined) { len = index - result = result_ + exit = exit_ onAbort() - } else if (result_._tag === "Left") { - failures.push(result_.left) + } else if (exit_._tag === "Left") { + causes.push(exit_.left) } if (done >= len) { - onResult(result ?? Either.left(failures[0])) + onExit(exit ?? Either.left(causes[0])) } } @@ -1592,24 +1621,24 @@ export const raceAll = >( export const raceAllFirst = >( all: Iterable ): Micro, Micro.Error, Micro.Context> => - make(function(env, onResult) { + make(function(env, onExit) { const [envWithSignal, onAbort] = forkSignal(env) const effects = Array.from(all) let len = effects.length let index = 0 let done = 0 - let result: Result | undefined = undefined - const failures: Array> = [] - function onDone(result_: Result) { + let exit: MicroExit | undefined = undefined + const causes: Array> = [] + function onDone(exit_: MicroExit) { done++ - if (result === undefined) { + if (exit === undefined) { len = index - result = result_ + exit = exit_ onAbort() } if (done >= len) { - onResult(result ?? Either.left(failures[0])) + onExit(exit ?? Either.left(causes[0])) } } @@ -1722,7 +1751,7 @@ export const zipWith: { /** * Filter the specified effect with the provided function, failing with specified - * `Failure` if the predicate fails. + * `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 @@ -1731,26 +1760,30 @@ export const zipWith: { * @experimental * @category filtering & conditionals */ -export const filterOrFailWith: { +export const filterOrFailCause: { ( refinement: Refinement, - orFailWith: (a: NoInfer) => Failure + orFailWith: (a: NoInfer) => MicroCause ): (self: Micro) => Micro ( predicate: Predicate>, - orFailWith: (a: NoInfer) => Failure + orFailWith: (a: NoInfer) => MicroCause ): (self: Micro) => Micro ( self: Micro, refinement: Refinement, - orFailWith: (a: A) => Failure + orFailWith: (a: A) => MicroCause ): Micro - (self: Micro, predicate: Predicate, orFailWith: (a: A) => Failure): Micro + ( + self: Micro, + predicate: Predicate, + orFailWith: (a: A) => MicroCause + ): Micro } = dual((args) => isMicro(args[0]), ( self: Micro, refinement: Refinement, - orFailWith: (a: A) => Failure -): Micro => flatMap(self, (a) => refinement(a) ? succeed(a as any) : failWith(orFailWith(a)))) + 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 @@ -1782,7 +1815,7 @@ export const filterOrFail: { self: Micro, refinement: Refinement, orFailWith: (a: A) => E2 -): Micro => flatMap(self, (a) => refinement(a) ? succeed(a as any) : fail(orFailWith(a)))) +): Micro => flatMap(self, (a) => refinement(a) ? succeed(a) : fail(orFailWith(a)))) /** * The moral equivalent of `if (p) exp`. @@ -1816,50 +1849,50 @@ export const when: { * Repeat the given `Micro` using the provided options. * * The `while` predicate will be checked after each iteration, and can use the - * fall `Result` of the effect to determine if the repetition should continue. + * fall `MicroExit` of the effect to determine if the repetition should continue. * - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category repetition */ -export const repeatResult: { +export const repeatExit: { (options: { - while: Predicate> + while: Predicate> times?: number | undefined - delay?: DelayFn | undefined + schedule?: MicroSchedule | undefined }): (self: Micro) => Micro (self: Micro, options: { - while: Predicate> + while: Predicate> times?: number | undefined - delay?: DelayFn | undefined + schedule?: MicroSchedule | undefined }): Micro } = dual(2, (self: Micro, options: { - while: Predicate> + while: Predicate> times?: number | undefined - delay?: DelayFn | undefined + schedule?: MicroSchedule | undefined }): Micro => - make(function(env, onResult) { - const startedAt = options.delay ? Date.now() : 0 + make(function(env, onExit) { + const startedAt = options.schedule ? Date.now() : 0 let attempt = 0 - self[runSymbol](env, function loop(result) { - if (options.while !== undefined && !options.while(result)) { - return onResult(result) + self[runSymbol](env, function loop(exit) { + if (options.while !== undefined && !options.while(exit)) { + return onExit(exit) } else if (options.times !== undefined && attempt >= options.times) { - return onResult(result) + return onExit(exit) } attempt++ let delayEffect = yieldNow - if (options.delay !== undefined) { + if (options.schedule !== undefined) { const elapsed = Date.now() - startedAt - const duration = options.delay(attempt, elapsed) + const duration = options.schedule(attempt, elapsed) if (Option.isNone(duration)) { - return onResult(result) + return onExit(exit) } delayEffect = sleep(duration.value) } - delayEffect[runSymbol](env, function(result) { - if (result._tag === "Left") { - return onResult(result as any) + delayEffect[runSymbol](env, function(exit) { + if (exit._tag === "Left") { + return onExit(exit as MicroExit) } self[runSymbol](env, loop) }) @@ -1879,7 +1912,7 @@ export const repeat: { options?: { while?: Predicate | undefined times?: number | undefined - delay?: DelayFn | undefined + schedule?: MicroSchedule | undefined } | undefined ): (self: Micro) => Micro ( @@ -1887,7 +1920,7 @@ export const repeat: { options?: { while?: Predicate | undefined times?: number | undefined - delay?: DelayFn | undefined + schedule?: MicroSchedule | undefined } | undefined ): Micro } = dual((args) => isMicro(args[0]), ( @@ -1895,12 +1928,12 @@ export const repeat: { options?: { while?: Predicate | undefined times?: number | undefined - delay?: DelayFn | undefined + schedule?: MicroSchedule | undefined } | undefined ): Micro => - repeatResult(self, { + repeatExit(self, { ...options, - while: (result) => result._tag === "Right" && (options?.while === undefined || options.while(result.right)) + while: (exit) => exit._tag === "Right" && (options?.while === undefined || options.while(exit.right)) })) /** @@ -1913,89 +1946,135 @@ export const repeat: { export const forever = (self: Micro): Micro => repeat(self) as any // ---------------------------------------------------------------------------- -// delay fn +// scheduling // ---------------------------------------------------------------------------- /** - * Represents a function that can be used to calculate the delay between - * repeats. + * 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. + * 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.0 + * @since 3.4.6 * @experimental - * @category delay fn + * @category scheduling */ -export type DelayFn = (attempt: number, elapsed: number) => Option.Option +export type MicroSchedule = (attempt: number, elapsed: number) => Option.Option /** - * Create a `DelayFn` that will generate a duration with an exponential backoff. + * Create a `MicroSchedule` that will stop repeating after the specified number + * of attempts. * - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category delay fn + * @category scheduling */ -export const delayExponential = (baseMillis: number, factor = 2): DelayFn => (attempt) => - Option.some(attempt ** factor * baseMillis) +export const scheduleRecurs = (n: number): MicroSchedule => (attempt) => attempt <= n ? Option.some(0) : Option.none() /** - * Create a `DelayFn` that will generate a duration with fixed intervals. + * Create a `MicroSchedule` that will generate a constant delay. * - * @since 3.4.0 + * @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 delay fn + * @category scheduling */ -export const delaySpaced = (millis: number): DelayFn => (_) => Option.some(millis) +export const scheduleExponential = (baseMillis: number, factor = 2): MicroSchedule => (attempt) => + Option.some(Math.pow(factor, attempt) * baseMillis) /** - * Transform a `DelayFn` to one that will have a duration that will never exceed + * 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: { + (f: () => number): (self: MicroSchedule) => MicroSchedule + (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.0 + * @since 3.4.6 * @experimental - * @category delay fn + * @category scheduling */ -export const delayWithMax: { - (max: number): (self: DelayFn) => DelayFn - (self: DelayFn, max: number): DelayFn +export const scheduleWithMaxDelay: { + (max: number): (self: MicroSchedule) => MicroSchedule + (self: MicroSchedule, max: number): MicroSchedule } = dual( 2, - (self: DelayFn, max: number): DelayFn => (attempt, elapsed) => + (self: MicroSchedule, max: number): MicroSchedule => (attempt, elapsed) => Option.map(self(attempt, elapsed), (duration) => Math.min(duration, max)) ) /** - * Transform a `DelayFn` to one that will stop repeating after the specified + * Transform a `MicroSchedule` to one that will stop repeating after the specified * amount of time. * - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category delay fn + * @category scheduling */ -export const delayWithMaxElapsed: { - (max: number): (self: DelayFn) => DelayFn - (self: DelayFn, max: number): DelayFn +export const scheduleWithMaxElapsed: { + (max: number): (self: MicroSchedule) => MicroSchedule + (self: MicroSchedule, max: number): MicroSchedule } = dual( 2, - (self: DelayFn, max: number): DelayFn => (attempt, elapsed) => elapsed < max ? self(attempt, elapsed) : Option.none() + (self: MicroSchedule, max: number): MicroSchedule => (attempt, elapsed) => + elapsed < max ? self(attempt, elapsed) : Option.none() ) /** - * Transform a `DelayFn` to one that will stop repeating after the specified - * number of attempts. + * Combines two `MicroSchedule`s, by recurring if either schedule wants to + * recur, using the minimum of the two durations between recurrences. * - * @since 3.4.0 + * @since 3.4.6 * @experimental - * @category delay fn + * @category scheduling */ -export const delayWithRecurs: { - (n: number): (self: DelayFn) => DelayFn - (self: DelayFn, n: number): DelayFn +export const scheduleUnion: { + (that: MicroSchedule): (self: MicroSchedule) => MicroSchedule + (self: MicroSchedule, that: MicroSchedule): MicroSchedule } = dual( 2, - (self: DelayFn, n: number): DelayFn => (attempt, elapsed) => Option.filter(self(attempt, elapsed), () => attempt <= n) + (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: { + (that: MicroSchedule): (self: MicroSchedule) => MicroSchedule + (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)) ) // ---------------------------------------------------------------------------- @@ -2003,80 +2082,81 @@ export const delayWithRecurs: { // ---------------------------------------------------------------------------- /** - * Catch the full `Failure` object of the given `Micro` effect, allowing you to - * recover from any kind of failure. + * Catch the full `MicroCause` object of the given `Micro` effect, allowing you to + * recover from any kind of cause. * - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category error handling */ -export const catchFailure: { +export const catchAllCause: { ( - f: (failure: NoInfer>) => Micro + f: (cause: NoInfer>) => Micro ): (self: Micro) => Micro ( self: Micro, - f: (failure: NoInfer>) => Micro + f: (cause: NoInfer>) => Micro ): Micro } = dual( 2, ( self: Micro, - f: (failure: NoInfer>) => Micro - ): Micro => catchFailureIf(self, constTrue, f) + f: (cause: NoInfer>) => Micro + ): Micro => catchCauseIf(self, constTrue, f) as Micro ) /** - * Selectively catch a `Failure` object of the given `Micro` effect, + * 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.0 + * @since 3.4.6 * @experimental * @category error handling */ -export const catchFailureIf: { - >( - refinement: Refinement, EB>, - f: (failure: EB) => Micro - ): (self: Micro) => Micro +export const catchCauseIf: { + >( + refinement: Refinement, EB>, + f: (cause: EB) => Micro + ): (self: Micro) => Micro> | E2, R | R2> ( - predicate: Predicate>>, - f: (failure: NoInfer>) => Micro - ): (self: Micro) => Micro - >( + predicate: Predicate>>, + f: (cause: NoInfer>) => Micro + ): (self: Micro) => Micro + >( self: Micro, - refinement: Refinement, EB>, - f: (failure: EB) => Micro - ): Micro + refinement: Refinement, EB>, + f: (cause: EB) => Micro + ): Micro> | E2, R | R2> ( self: Micro, - predicate: Predicate>>, - f: (failure: NoInfer>) => Micro - ): Micro -} = dual(3, >( + predicate: Predicate>>, + f: (cause: NoInfer>) => Micro + ): Micro +} = dual(3, ( self: Micro, - refinement: Refinement, EB>, - f: (failure: EB) => Micro -): Micro => - make(function(env, onResult) { - self[runSymbol](env, function(result) { - if (result._tag === "Right" || !refinement(result.left)) { - return onResult(result as any) + predicate: Predicate>, + f: (cause: MicroCause) => Micro +): Micro => + make(function(env, onExit) { + self[runSymbol](env, function(exit) { + if (exit._tag === "Right" || !predicate(exit.left)) { + onExit(exit) + } else { + f(exit.left)[runSymbol](env, onExit) } - f(result.left)[runSymbol](env, onResult) }) })) /** * Catch the error of the given `Micro` effect, allowing you to recover from it. * - * It only catches expected (`FailureExpected`) errors. + * It only catches expected (`MicroCause.Fail`) errors. * - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category error handling */ -export const catchExpected: { +export const catchAll: { ( f: (e: NoInfer) => Micro ): (self: Micro) => Micro @@ -2086,17 +2166,17 @@ export const catchExpected: { ( self: Micro, f: (a: NoInfer) => Micro - ): Micro => catchFailureIf(self, failureIsExpected, (failure) => f(failure.error)) + ): Micro => catchAllCause(self, (cause) => causeIsFail(cause) ? f(cause.error) : failCause(cause)) ) /** * Catch any unexpected errors of the given `Micro` effect, allowing you to recover from them. * - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category error handling */ -export const catchUnexpected: { +export const catchAllDefect: { ( f: (defect: unknown) => Micro ): (self: Micro) => Micro @@ -2104,92 +2184,94 @@ export const catchUnexpected: { } = dual( 2, (self: Micro, f: (defect: unknown) => Micro): Micro => - catchFailureIf(self, failureIsUnexpected, (failure) => f(failure.defect)) + catchCauseIf(self, causeIsDie, (die) => f(die.defect)) ) /** - * Perform a side effect using the full `Failure` object of the given `Micro`. + * Perform a side effect using the full `MicroCause` object of the given `Micro`. * - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category error handling */ -export const tapFailure: { +export const tapErrorCause: { ( - f: (a: NoInfer>) => Micro + f: (cause: NoInfer>) => Micro ): (self: Micro) => Micro - (self: Micro, f: (a: NoInfer>) => Micro): Micro + ( + self: Micro, + f: (cause: NoInfer>) => Micro + ): Micro } = dual( 2, ( self: Micro, - f: (a: NoInfer>) => Micro - ): Micro => tapFailureIf(self, constTrue, f) + f: (cause: NoInfer>) => Micro + ): Micro => tapErrorCauseIf(self, constTrue, f) ) /** - * Perform a side effect using if a `Failure` object matches the specified + * Perform a side effect using if a `MicroCause` object matches the specified * predicate. * * @since 3.4.0 * @experimental * @category error handling */ -export const tapFailureIf: { - >( - refinement: Refinement, EB>, +export const tapErrorCauseIf: { + >( + refinement: Refinement, EB>, f: (a: EB) => Micro ): (self: Micro) => Micro ( - predicate: (failure: NoInfer>) => boolean, - f: (a: NoInfer>) => Micro + predicate: (cause: NoInfer>) => boolean, + f: (a: NoInfer>) => Micro ): (self: Micro) => Micro - >( + >( self: Micro, - refinement: Refinement, EB>, + refinement: Refinement, EB>, f: (a: EB) => Micro ): Micro ( self: Micro, - predicate: (failure: NoInfer>) => boolean, - f: (a: NoInfer>) => Micro + predicate: (cause: NoInfer>) => boolean, + f: (a: NoInfer>) => Micro ): Micro } = dual( 3, - >( + >( self: Micro, - refinement: Refinement, EB>, + refinement: Refinement, EB>, f: (a: EB) => Micro - ): Micro => - catchFailureIf(self, refinement, (failure) => andThen(f(failure as any), failWith(failure))) + ): Micro => catchCauseIf(self, refinement, (cause) => andThen(f(cause), failCause(cause))) ) /** * Perform a side effect from expected errors of the given `Micro`. * - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category error handling */ -export const tapExpected: { +export const tapError: { ( - f: (a: NoInfer) => Micro + f: (e: NoInfer) => Micro ): (self: Micro) => Micro - (self: Micro, f: (a: NoInfer) => Micro): Micro + (self: Micro, f: (e: NoInfer) => Micro): Micro } = dual( 2, - (self: Micro, f: (a: NoInfer) => Micro): Micro => - tapFailureIf(self, failureIsExpected, (failure) => f(failure.error)) + (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.0 + * @since 3.4.6 * @experimental * @category error handling */ -export const tapUnexpected: { +export const tapDefect: { ( f: (defect: unknown) => Micro ): (self: Micro) => Micro @@ -2197,7 +2279,7 @@ export const tapUnexpected: { } = dual( 2, (self: Micro, f: (defect: unknown) => Micro): Micro => - tapFailureIf(self, failureIsUnexpected, (failure) => f(failure.defect)) + tapErrorCauseIf(self, causeIsDie, (die) => f(die.defect)) ) /** @@ -2233,10 +2315,10 @@ export const catchIf: { predicate: Predicate, f: (e: E) => Micro ): Micro => - catchFailureIf( + catchCauseIf( self, - (f): f is Failure.Expected => failureIsExpected(f) && predicate(f.error), - (failure) => f(failure.error) + (f): f is MicroCause.Fail => causeIsFail(f) && predicate(f.error), + (fail) => f(fail.error) ) ) @@ -2265,19 +2347,19 @@ export const catchTag: { catchIf(self, isTagged(k) as Refinement>, f) as any) /** - * Transform the full `Failure` object of the given `Micro` effect. + * Transform the full `MicroCause` object of the given `Micro` effect. * - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category error handling */ -export const mapFailure: { - (f: (a: Failure) => Failure): (self: Micro) => Micro - (self: Micro, f: (a: Failure) => Failure): Micro +export const mapErrorCause: { + (f: (e: MicroCause) => MicroCause): (self: Micro) => Micro + (self: Micro, f: (e: MicroCause) => MicroCause): Micro } = dual( 2, - (self: Micro, f: (a: Failure) => Failure): Micro => - catchFailure(self, (failure) => failWith(f(failure))) + (self: Micro, f: (e: MicroCause) => MicroCause): Micro => + catchAllCause(self, (cause) => failCause(f(cause))) ) /** @@ -2288,12 +2370,11 @@ export const mapFailure: { * @category error handling */ export const mapError: { - (f: (a: E) => E2): (self: Micro) => Micro - (self: Micro, f: (a: E) => E2): Micro + (f: (e: E) => E2): (self: Micro) => Micro + (self: Micro, f: (e: E) => E2): Micro } = dual( 2, - (self: Micro, f: (a: E) => E2): Micro => - catchExpected(self, (error) => fail(f(error))) + (self: Micro, f: (e: E) => E2): Micro => catchAll(self, (error) => fail(f(error))) ) /** @@ -2304,7 +2385,7 @@ export const mapError: { * @experimental * @category error handling */ -export const orDie = (self: Micro): Micro => catchExpected(self, die) +export const orDie = (self: Micro): Micro => catchAll(self, die) /** * Recover from all errors by succeeding with the given value. @@ -2318,7 +2399,7 @@ export const orElseSucceed: { (self: Micro, f: LazyArg): Micro } = dual( 2, - (self: Micro, f: LazyArg): Micro => catchExpected(self, (_) => sync(f)) + (self: Micro, f: LazyArg): Micro => catchAll(self, (_) => sync(f)) ) /** @@ -2329,7 +2410,7 @@ export const orElseSucceed: { * @category error handling */ export const ignore = (self: Micro): Micro => - matchMicro(self, { onFailure: (_) => void_, onSuccess: (_) => void_ }) + matchEffect(self, { onFailure: (_) => void_, onSuccess: (_) => void_ }) /** * Ignore any expected errors of the given `Micro` effect, returning `void`. @@ -2339,8 +2420,8 @@ export const ignore = (self: Micro): Micro => * @category error handling */ export const ignoreLogged = (self: Micro): Micro => - matchMicro(self, { - onFailure: (failure) => sync(() => console.error(failure)), + matchEffect(self, { + onFailure: (error) => sync(() => console.error(error)), onSuccess: (_) => void_ }) @@ -2380,7 +2461,7 @@ export const retry: { options?: { while?: Predicate | undefined times?: number | undefined - delay?: DelayFn | undefined + schedule?: MicroSchedule | undefined } | undefined ): (self: Micro) => Micro ( @@ -2388,7 +2469,7 @@ export const retry: { options?: { while?: Predicate | undefined times?: number | undefined - delay?: DelayFn | undefined + schedule?: MicroSchedule | undefined } | undefined ): Micro } = dual((args) => isMicro(args[0]), ( @@ -2396,19 +2477,19 @@ export const retry: { options?: { while?: Predicate | undefined times?: number | undefined - delay?: DelayFn | undefined + schedule?: MicroSchedule | undefined } | undefined ): Micro => - repeatResult(self, { + repeatExit(self, { ...options, - while: (result) => - result._tag === "Left" && result.left._tag === "Expected" && - (options?.while === undefined || options.while(result.left.error)) + while: (exit) => + exit._tag === "Left" && exit.left._tag === "Fail" && + (options?.while === undefined || options.while(exit.left.error)) })) /** * Add a stack trace to any failures that occur in the effect. The trace will be - * added to the `traces` field of the `Failure` object. + * added to the `traces` field of the `MicroCause` object. * * @since 3.4.0 * @experimental @@ -2422,22 +2503,22 @@ export const withTrace: { globalThis.Error.stackTraceLimit = 2 const error = new globalThis.Error() globalThis.Error.stackTraceLimit = prevLimit - function generate(name: string, failure: Failure) { + function generate(name: string, cause: MicroCause) { const stack = error.stack if (!stack) { - return failure + return cause } const line = stack.split("\n")[2]?.trim().replace(/^at /, "") if (!line) { - return failure + return cause } const lineMatch = line.match(/\((.*)\)$/) - return failureWithTrace(failure, `at ${name} (${lineMatch ? lineMatch[1] : line})`) + return causeWithTrace(cause, `at ${name} (${lineMatch ? lineMatch[1] : line})`) } const f = (name: string) => (self: Micro) => - unsafeMakeOptions(function(env, onResult) { - self[runSymbol](env, function(result) { - onResult(result._tag === "Left" ? Either.left(generate(name, result.left)) : result) + unsafeMakeOptions(function(env, onExit) { + self[runSymbol](env, function(exit) { + onExit(exit._tag === "Left" ? Either.left(generate(name, exit.left)) : exit) }) }, false) if (arguments.length === 2) { @@ -2451,21 +2532,21 @@ export const withTrace: { // ---------------------------------------------------------------------------- /** - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category pattern matching */ -export const matchFailureMicro: { +export const matchCauseEffect: { ( options: { - readonly onFailure: (failure: Failure) => Micro + readonly onFailure: (cause: MicroCause) => Micro readonly onSuccess: (a: A) => Micro } ): (self: Micro) => Micro ( self: Micro, options: { - readonly onFailure: (failure: Failure) => Micro + readonly onFailure: (cause: MicroCause) => Micro readonly onSuccess: (a: A) => Micro } ): Micro @@ -2474,38 +2555,38 @@ export const matchFailureMicro: { ( self: Micro, options: { - readonly onFailure: (failure: Failure) => Micro + readonly onFailure: (cause: MicroCause) => Micro readonly onSuccess: (a: A) => Micro } ): Micro => - make(function(env, onResult) { - self[runSymbol](env, function(result) { + make(function(env, onExit) { + self[runSymbol](env, function(exit) { try { - const next = result._tag === "Left" ? options.onFailure(result.left) : options.onSuccess(result.right) - next[runSymbol](env, onResult) + const next = exit._tag === "Left" ? options.onFailure(exit.left) : options.onSuccess(exit.right) + next[runSymbol](env, onExit) } catch (err) { - onResult(ResultFailUnexpected(err)) + onExit(exitDie(err)) } }) }) ) /** - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category pattern matching */ -export const matchFailure: { +export const matchCause: { ( options: { - readonly onFailure: (failure: Failure) => A2 + readonly onFailure: (cause: MicroCause) => A2 readonly onSuccess: (a: A) => A3 } ): (self: Micro) => Micro ( self: Micro, options: { - readonly onFailure: (failure: Failure) => A2 + readonly onFailure: (cause: MicroCause) => A2 readonly onSuccess: (a: A) => A3 } ): Micro @@ -2514,22 +2595,22 @@ export const matchFailure: { ( self: Micro, options: { - readonly onFailure: (failure: Failure) => A2 + readonly onFailure: (cause: MicroCause) => A2 readonly onSuccess: (a: A) => A3 } ): Micro => - matchFailureMicro(self, { - onFailure: (failure) => sync(() => options.onFailure(failure)), + matchCauseEffect(self, { + onFailure: (cause) => sync(() => options.onFailure(cause)), onSuccess: (value) => sync(() => options.onSuccess(value)) }) ) /** - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category pattern matching */ -export const matchMicro: { +export const matchEffect: { ( options: { readonly onFailure: (e: E) => Micro @@ -2552,8 +2633,8 @@ export const matchMicro: { readonly onSuccess: (a: A) => Micro } ): Micro => - matchFailureMicro(self, { - onFailure: (failure) => failure._tag === "Expected" ? options.onFailure(failure.error) : failWith(failure), + matchCauseEffect(self, { + onFailure: (cause) => cause._tag === "Fail" ? options.onFailure(cause.error) : failCause(cause), onSuccess: options.onSuccess }) ) @@ -2586,7 +2667,7 @@ export const match: { readonly onSuccess: (value: A) => A3 } ): Micro => - matchMicro(self, { + matchEffect(self, { onFailure: (error) => sync(() => options.onFailure(error)), onSuccess: (value) => sync(() => options.onSuccess(value)) }) @@ -2657,6 +2738,26 @@ export const timeoutOrElse: { 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: { + (millis: number): (self: Micro) => Micro + (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 @@ -2668,7 +2769,7 @@ export const timeoutOrElse: { * @experimental * @category delays & timeouts */ -export const timeout: { +export const timeoutOption: { (millis: number): (self: Micro) => Micro, E, R> (self: Micro, millis: number): Micro, E, R> } = dual( @@ -2705,7 +2806,7 @@ export type MicroScopeTypeId = typeof MicroScopeTypeId */ export interface MicroScope { readonly [MicroScopeTypeId]: MicroScopeTypeId - readonly addFinalizer: (finalizer: (result: Result) => Micro) => Micro + readonly addFinalizer: (finalizer: (exit: MicroExit) => Micro) => Micro readonly fork: Micro } @@ -2721,7 +2822,7 @@ export declare namespace MicroScope { * @category resources & finalization */ export interface Closeable extends MicroScope { - readonly close: (result: Result) => Micro + readonly close: (exit: MicroExit) => Micro } } @@ -2732,47 +2833,47 @@ export declare namespace MicroScope { */ export const MicroScope: Context.Tag = Context.GenericTag("effect/Micro/MicroScope") -class ScopeImpl implements MicroScope.Closeable { +class MicroScopeImpl implements MicroScope.Closeable { readonly [MicroScopeTypeId]: MicroScopeTypeId state: { readonly _tag: "Open" - readonly finalizers: Set<(result: Result) => Micro> + readonly finalizers: Set<(exit: MicroExit) => Micro> } | { readonly _tag: "Closed" - readonly result: Result + readonly exit: MicroExit } = { _tag: "Open", finalizers: new Set() } constructor() { this[MicroScopeTypeId] = MicroScopeTypeId } - unsafeAddFinalizer(finalizer: (result: Result) => Micro): void { + unsafeAddFinalizer(finalizer: (exit: MicroExit) => Micro): void { if (this.state._tag === "Open") { this.state.finalizers.add(finalizer) } } - addFinalizer(finalizer: (result: Result) => Micro): Micro { + addFinalizer(finalizer: (exit: MicroExit) => Micro): Micro { return suspend(() => { if (this.state._tag === "Open") { this.state.finalizers.add(finalizer) return void_ } - return finalizer(this.state.result) + return finalizer(this.state.exit) }) } - unsafeRemoveFinalizer(finalizer: (result: Result) => Micro): void { + unsafeRemoveFinalizer(finalizer: (exit: MicroExit) => Micro): void { if (this.state._tag === "Open") { this.state.finalizers.delete(finalizer) } } - close(result: Result): Micro { + close(microExit: MicroExit): Micro { return suspend(() => { if (this.state._tag === "Open") { const finalizers = Array.from(this.state.finalizers).reverse() - this.state = { _tag: "Closed", result } + this.state = { _tag: "Closed", exit: microExit } return flatMap( - forEach(finalizers, (finalizer) => asResult(finalizer(result))), - (results) => asVoid(fromResult(Either.all(results))) + forEach(finalizers, (finalizer) => exit(finalizer(microExit))), + (exits) => asVoid(fromExit(Either.all(exits))) ) } return void_ @@ -2780,13 +2881,13 @@ class ScopeImpl implements MicroScope.Closeable { } get fork() { return sync(() => { - const newScope = new ScopeImpl() + const newScope = new MicroScopeImpl() if (this.state._tag === "Closed") { newScope.state = this.state return newScope } - function fin(result: Result) { - return newScope.close(result) + function fin(exit: MicroExit) { + return newScope.close(exit) } this.state.finalizers.add(fin) newScope.unsafeAddFinalizer((_) => sync(() => this.unsafeRemoveFinalizer(fin))) @@ -2800,14 +2901,14 @@ class ScopeImpl implements MicroScope.Closeable { * @experimental * @category resources & finalization */ -export const scopeMake: Micro = sync(() => new ScopeImpl()) +export const scopeMake: Micro = sync(() => new MicroScopeImpl()) /** * @since 3.4.0 * @experimental * @category resources & finalization */ -export const scopeUnsafeMake = (): MicroScope.Closeable => new ScopeImpl() +export const scopeUnsafeMake = (): MicroScope.Closeable => new MicroScopeImpl() /** * Access the current `MicroScope`. @@ -2844,8 +2945,8 @@ export const provideScope: { */ export const scoped = (self: Micro): Micro> => suspend(function() { - const scope = new ScopeImpl() - return onResult(provideService(self, MicroScope, scope), (result) => scope.close(result)) + const scope = new MicroScopeImpl() + return onExit(provideService(self, MicroScope, scope), (exit) => scope.close(exit)) }) /** @@ -2858,11 +2959,11 @@ export const scoped = (self: Micro): Micro( acquire: Micro, - release: (a: A, result: Result) => Micro + release: (a: A, exit: MicroExit) => Micro ): Micro => uninterruptible(flatMap( scope, - (scope) => tap(acquire, (a) => scope.addFinalizer((result) => release(a, result))) + (scope) => tap(acquire, (a) => scope.addFinalizer((exit) => release(a, exit))) )) /** @@ -2873,73 +2974,75 @@ export const acquireRelease = ( * @category resources & finalization */ export const addFinalizer = ( - finalizer: (result: Result) => Micro + finalizer: (exit: MicroExit) => Micro ): Micro => flatMap(scope, (scope) => scope.addFinalizer(finalizer)) /** * When the `Micro` effect is completed, run the given finalizer effect with the - * `Result` of the executed effect. + * `MicroExit` of the executed effect. * - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category resources & finalization */ -export const onResult: { +export const onExit: { ( - f: (result: Result) => Micro + f: (exit: MicroExit) => Micro ): (self: Micro) => Micro - (self: Micro, f: (result: Result) => Micro): Micro + (self: Micro, f: (exit: MicroExit) => Micro): Micro } = dual( 2, - (self: Micro, f: (result: Result) => Micro): Micro => - onResultIf(self, constTrue, f) + ( + self: Micro, + f: (exit: MicroExit) => Micro + ): Micro => onExitIf(self, constTrue, f) ) /** * When the `Micro` effect is completed, run the given finalizer effect if it * matches the specified predicate. * - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category resources & finalization */ -export const onResultIf: { - >( - refinement: Refinement, B>, - f: (result: B) => Micro +export const onExitIf: { + >( + refinement: Refinement, B>, + f: (exit: B) => Micro ): (self: Micro) => Micro ( - predicate: Predicate, NoInfer>>, - f: (result: Result, NoInfer>) => Micro + predicate: Predicate, NoInfer>>, + f: (exit: MicroExit, NoInfer>) => Micro ): (self: Micro) => Micro - >( + >( self: Micro, - refinement: Refinement, B>, - f: (result: B) => Micro + refinement: Refinement, B>, + f: (exit: B) => Micro ): Micro ( self: Micro, - predicate: Predicate, NoInfer>>, - f: (result: Result, NoInfer>) => Micro + predicate: Predicate, NoInfer>>, + f: (exit: MicroExit, NoInfer>) => Micro ): Micro } = dual( 3, - >( + >( self: Micro, - refinement: Refinement, B>, - f: (result: B) => Micro + refinement: Refinement, B>, + f: (exit: B) => Micro ): Micro => uninterruptibleMask((restore) => - make(function(env, onResult) { - restore(self)[runSymbol](env, function(result) { - if (!refinement(result)) { - return onResult(result) + make(function(env, onExit) { + restore(self)[runSymbol](env, function(exit) { + if (!refinement(exit)) { + return onExit(exit) } - f(result)[runSymbol](env, function(finalizerResult) { - if (finalizerResult._tag === "Left") { - return onResult(finalizerResult as any) + f(exit)[runSymbol](env, function(finalizerExit) { + if (finalizerExit._tag === "Left") { + return onExit(finalizerExit as MicroExit) } - onResult(result) + onExit(exit) }) }) }) @@ -2961,41 +3064,41 @@ export const ensuring: { } = dual( 2, (self: Micro, finalizer: Micro): Micro => - onResult(self, (_) => finalizer) + onExit(self, (_) => finalizer) ) /** * When the `Micro` effect fails, run the given finalizer effect with the - * `Failure` of the executed effect. + * `MicroCause` of the executed effect. * - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category resources & finalization */ -export const onFailure: { +export const onError: { ( - f: (failure: Failure>) => Micro + f: (cause: MicroCause>) => Micro ): (self: Micro) => Micro ( self: Micro, - f: (failure: Failure>) => Micro + f: (cause: MicroCause>) => Micro ): Micro } = dual( 2, ( self: Micro, - f: (failure: Failure>) => Micro - ): Micro => onResultIf(self, resultIsFailure, (result) => f(result.left)) + f: (cause: MicroCause>) => Micro + ): Micro => onExitIf(self, exitIsFailure, (exit) => f(exit.left)) ) /** * If this `Micro` effect is aborted, run the finalizer effect. * - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category resources & finalization */ -export const onAbort: { +export const onInterrupt: { ( finalizer: Micro ): (self: Micro) => Micro @@ -3003,7 +3106,7 @@ export const onAbort: { } = dual( 2, (self: Micro, finalizer: Micro): Micro => - onResultIf(self, resultIsAborted, (_) => finalizer) + onExitIf(self, exitIsInterrupt, (_) => finalizer) ) /** @@ -3017,15 +3120,15 @@ export const onAbort: { export const acquireUseRelease = ( acquire: Micro, use: (a: Resource) => Micro, - release: (a: Resource, result: Result) => Micro + release: (a: Resource, exit: MicroExit) => Micro ): Micro => uninterruptibleMask((restore) => flatMap( acquire, (a) => flatMap( - asResult(restore(use(a))), - (result) => andThen(release(a, result), fromResult(result)) + exit(restore(use(a))), + (exit) => andThen(release(a, exit), fromExit(exit)) ) ) ) @@ -3037,14 +3140,14 @@ export const acquireUseRelease = ( /** * Abort the current `Micro` effect. * - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category interruption */ -export const abort: Micro = make(function(env, onResult) { +export const interrupt: Micro = make(function(env, onExit) { const controller = envGet(env, currentAbortController) controller.abort() - onResult(ResultAborted) + onExit(exitInterrupt) }) /** @@ -3056,13 +3159,13 @@ export const abort: Micro = make(function(env, onResult) { * @category interruption */ export const uninterruptible = (self: Micro): Micro => - unsafeMakeOptions(function(env, onResult) { + unsafeMakeOptions(function(env, onExit) { const nextEnv = envMutate(env, function(env) { env[currentInterruptible.key] = false env[currentAbortSignal.key] = new AbortController().signal return env }) - self[runSymbol](nextEnv, onResult) + self[runSymbol](nextEnv, onExit) }, false) /** @@ -3087,7 +3190,7 @@ export const uninterruptible = (self: Micro): Micro = export const uninterruptibleMask = ( f: (restore: (effect: Micro) => Micro) => Micro ): Micro => - unsafeMakeOptions((env, onResult) => { + unsafeMakeOptions((env, onExit) => { const isInterruptible = envGet(env, currentInterruptible) const effect = isInterruptible ? f(interruptible) : f(identity) const nextEnv = isInterruptible ? @@ -3097,7 +3200,7 @@ export const uninterruptibleMask = ( return env }) : env - effect[runSymbol](nextEnv, onResult) + effect[runSymbol](nextEnv, onExit) }, false) /** @@ -3109,7 +3212,7 @@ export const uninterruptibleMask = ( * @category interruption */ export const interruptible = (self: Micro): Micro => - make((env, onResult) => { + make((env, onExit) => { const isInterruptible = envGet(env, currentInterruptible) let newEnv = env if (!isInterruptible) { @@ -3120,7 +3223,7 @@ export const interruptible = (self: Micro): Micro => return env }) } - self[runSymbol](newEnv, onResult) + self[runSymbol](newEnv, onExit) }) // ======================================================================== @@ -3271,7 +3374,7 @@ export const forEach: { readonly concurrency?: Concurrency | undefined readonly discard?: boolean | undefined }): Micro => - make(function(env, onResult) { + make(function(env, onExit) { const concurrencyOption = options?.concurrency === "inherit" ? envGet(env, currentConcurrency) : options?.concurrency ?? 1 @@ -3283,7 +3386,7 @@ export const forEach: { const [envWithSignal, onAbort] = forkSignal(env) // iterate - let failure: Result | undefined = undefined + let result: MicroExit | undefined = undefined const items = Array.from(iterable) let length = items.length const out: Array | undefined = options?.discard ? undefined : new Array(length) @@ -3299,26 +3402,26 @@ export const forEach: { index++ inProgress++ try { - f(item, currentIndex)[runSymbol](envWithSignal, function(result) { - if (result._tag === "Left") { - if (failure === undefined) { - failure = result + f(item, currentIndex)[runSymbol](envWithSignal, function(exit) { + if (exit._tag === "Left") { + if (result === undefined) { + result = exit length = index onAbort() } } else if (out !== undefined) { - out[currentIndex] = result.right + out[currentIndex] = exit.right } doneCount++ inProgress-- if (doneCount === length) { - onResult(failure ?? Either.right(out)) + onExit(result ?? Either.right(out)) } else if (!pumping && inProgress < concurrency) { pump() } }) } catch (err) { - failure = ResultFailUnexpected(err) + result = exitDie(err) length = index onAbort() } @@ -3345,7 +3448,7 @@ export const filter = (iterable: Iterable, f: (a: NoInfer) => Mic 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. @@ -3471,13 +3574,13 @@ export type HandleTypeId = typeof HandleTypeId */ export interface Handle { readonly [HandleTypeId]: HandleTypeId - readonly await: Micro> + readonly await: Micro> readonly join: Micro - readonly abort: Micro> - readonly unsafeAbort: () => void - readonly addObserver: (observer: (result: Result) => void) => void - readonly removeObserver: (observer: (result: Result) => void) => void - readonly unsafePoll: () => Result | null + readonly interrupt: Micro> + readonly unsafeInterrupt: () => void + readonly addObserver: (observer: (exit: MicroExit) => void) => void + readonly removeObserver: (observer: (exit: MicroExit) => void) => void + readonly unsafePoll: () => MicroExit | null } /** @@ -3491,8 +3594,8 @@ export const isHandle = (u: unknown): u is Handle => class HandleImpl implements Handle { readonly [HandleTypeId]: HandleTypeId - readonly observers: Set<(result: Result) => void> = new Set() - private _result: Result | undefined = undefined + readonly observers: Set<(exit: MicroExit) => void> = new Set() + private _exit: MicroExit | undefined = undefined _controller: AbortController readonly isRoot: boolean @@ -3501,49 +3604,49 @@ class HandleImpl implements Handle { this.isRoot = controller !== undefined this._controller = controller ?? new AbortController() if (!this.isRoot) { - parentSignal.addEventListener("abort", this.unsafeAbort) + parentSignal.addEventListener("abort", this.unsafeInterrupt) } } - unsafePoll(): Result | null { - return this._result ?? null + unsafePoll(): MicroExit | null { + return this._exit ?? null } - unsafeAbort = () => { + unsafeInterrupt = () => { this._controller.abort() } - emit(result: Result): void { - if (this._result) { + emit(exit: MicroExit): void { + if (this._exit) { return } - this._result = result + this._exit = exit if (!this.isRoot) { - this.parentSignal.removeEventListener("abort", this.unsafeAbort) + this.parentSignal.removeEventListener("abort", this.unsafeInterrupt) } - this.observers.forEach((observer) => observer(result)) + this.observers.forEach((observer) => observer(exit)) this.observers.clear() } - addObserver(observer: (result: Result) => void): void { - if (this._result) { - return observer(this._result) + addObserver(observer: (exit: MicroExit) => void): void { + if (this._exit) { + return observer(this._exit) } this.observers.add(observer) } - removeObserver(observer: (result: Result) => void): void { + removeObserver(observer: (exit: MicroExit) => void): void { this.observers.delete(observer) } - get await(): Micro> { + get await(): Micro> { return suspend(() => { - if (this._result) { - return succeed(this._result) + if (this._exit) { + return succeed(this._exit) } return async((resume) => { - function observer(result: Result) { - resume(succeed(result)) + function observer(exit: MicroExit) { + resume(succeed(exit)) } this.addObserver(observer) return sync(() => { @@ -3554,12 +3657,12 @@ class HandleImpl implements Handle { } get join(): Micro { - return flatMap(this.await, fromResult) + return flatMap(this.await, fromExit) } - get abort(): Micro> { + get interrupt(): Micro> { return suspend(() => { - this.unsafeAbort() + this.unsafeInterrupt() return this.await }) } @@ -3576,7 +3679,7 @@ class HandleImpl implements Handle { * @category handle & forking */ export const fork = (self: Micro): Micro, never, R> => - make(function(env, onResult) { + make(function(env, onExit) { const signal = envGet(env, currentAbortSignal) const handle = new HandleImpl(signal) const nextEnv = envMutate(env, (map) => { @@ -3585,11 +3688,11 @@ export const fork = (self: Micro): Micro, never, return map }) yieldAdd(() => { - self[runSymbol](nextEnv, (result) => { - handle.emit(result) + self[runSymbol](nextEnv, (exit) => { + handle.emit(exit) }) }) - onResult(Either.right(handle)) + onExit(Either.right(handle)) }) /** @@ -3603,7 +3706,7 @@ export const fork = (self: Micro): Micro, never, * @category handle & forking */ export const forkDaemon = (self: Micro): Micro, never, R> => - make(function(env, onResult) { + make(function(env, onExit) { const controller = new AbortController() const handle = new HandleImpl(controller.signal, controller) const nextEnv = envMutate(env, (map) => { @@ -3612,11 +3715,11 @@ export const forkDaemon = (self: Micro): Micro, n return map }) yieldAdd(() => { - self[runSymbol](nextEnv, (result) => { - handle.emit(result) + self[runSymbol](nextEnv, (exit) => { + handle.emit(exit) }) }) - onResult(Either.right(handle)) + onExit(Either.right(handle)) }) /** @@ -3638,8 +3741,8 @@ export const forkIn: { uninterruptibleMask((restore) => flatMap(scope.fork, (scope) => tap( - restore(forkDaemon(onResult(self, (result) => scope.close(result)))), - (fiber) => scope.addFinalizer((_) => asVoid(fiber.abort)) + restore(forkDaemon(onExit(self, (exit) => scope.close(exit)))), + (fiber) => scope.addFinalizer((_) => asVoid(fiber.interrupt)) )) ) ) @@ -3679,8 +3782,8 @@ export const forkScoped = (self: Micro): Micro, n * Micro.runFork * ) * - * handle.addObserver((result) => { - * console.log(result) + * handle.addObserver((exit) => { + * console.log(exit) * }) */ export const runFork = ( @@ -3695,17 +3798,17 @@ export const runFork = ( refs[currentAbortSignal.key] = controller.signal const env = envMake(refs) const handle = new HandleImpl(controller.signal, controller) - effect[runSymbol](envSet(env, currentAbortSignal, handle._controller.signal), (result) => { - handle.emit(result) + effect[runSymbol](envSet(env, currentAbortSignal, handle._controller.signal), (exit) => { + handle.emit(exit) if (options?.signal) { - options.signal.removeEventListener("abort", handle.unsafeAbort) + options.signal.removeEventListener("abort", handle.unsafeInterrupt) } }) if (options?.signal) { if (options.signal.aborted) { - handle.unsafeAbort() + handle.unsafeInterrupt() } else { - options.signal.addEventListener("abort", handle.unsafeAbort, { once: true }) + options.signal.addEventListener("abort", handle.unsafeInterrupt, { once: true }) } } return handle @@ -3713,18 +3816,18 @@ export const runFork = ( /** * Execute the `Micro` effect and return a `Promise` that resolves with the - * `Result` of the computation. + * `MicroExit` of the computation. * - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category execution */ -export const runPromiseResult = ( +export const runPromiseExit = ( effect: Micro, options?: { readonly signal?: AbortSignal | undefined } | undefined -): Promise> => +): Promise> => new Promise((resolve, _reject) => { const handle = runFork(effect, options) handle.addObserver(resolve) @@ -3744,33 +3847,33 @@ export const runPromise = ( readonly signal?: AbortSignal | undefined } | undefined ): Promise => - runPromiseResult(effect, options).then((result) => { - if (result._tag === "Left") { - throw result.left + runPromiseExit(effect, options).then((exit) => { + if (exit._tag === "Left") { + throw exit.left } - return result.right + return exit.right }) /** - * Attempt to execute the `Micro` effect synchronously and return the `Result`. + * Attempt to execute the `Micro` effect synchronously and return the `MicroExit`. * * If any asynchronous effects are encountered, the function will return a - * FailureUnexpected containing the `Handle`. + * `CauseDie` containing the `Handle`. * - * @since 3.4.0 + * @since 3.4.6 * @experimental * @category execution */ -export const runSyncResult = (effect: Micro): Result => { +export const runSyncExit = (effect: Micro): MicroExit => { const handle = runFork(effect) while (yieldState.tasks.length > 0) { yieldRunTasks() } - const result = handle.unsafePoll() - if (result === null) { - return ResultFailUnexpected(handle) + const exit = handle.unsafePoll() + if (exit === null) { + return exitDie(handle) } - return result + return exit } /** @@ -3782,11 +3885,11 @@ export const runSyncResult = (effect: Micro): Result => { * @category execution */ export const runSync = (effect: Micro): A => { - const result = runSyncResult(effect) - if (result._tag === "Left") { - throw result.left + const exit = runSyncExit(effect) + if (exit._tag === "Left") { + throw exit.left } - return result.right + return exit.right } // ---------------------------------------------------------------------------- @@ -3804,14 +3907,14 @@ export interface YieldableError extends Pipeable, Inspectable, Readonly { readonly [SinkTypeId]: Sink.VarianceStruct readonly [ChannelTypeId]: Channel.VarianceStruct readonly [TypeId]: Micro.Variance - readonly [runSymbol]: (env: Env, onResult: (result: Result) => void) => void + readonly [runSymbol]: (env: Env, onExit: (exit: MicroExit) => void) => void [Symbol.iterator](): MicroIterator> } const YieldableError: new(message?: string) => YieldableError = (function() { class YieldableError extends globalThis.Error { - [runSymbol](_env: any, onResult: any) { - onResult(ResultFail(this)) + [runSymbol](_env: any, onExit: any) { + onExit(exitFail(this)) } toString() { return this.message ? `${this.name}: ${this.message}` : this.name @@ -3875,3 +3978,12 @@ export const TaggedError = (tag: Tag): new {} + +/** + * Represents a checked exception which occurs when a timeout occurs. + * + * @since 3.4.4 + * @experimental + * @category errors + */ +export class TimeoutException extends TaggedError("TimeoutException") {} diff --git a/packages/effect/src/internal/fiberRuntime.ts b/packages/effect/src/internal/fiberRuntime.ts index 95b9443673..170e590247 100644 --- a/packages/effect/src/internal/fiberRuntime.ts +++ b/packages/effect/src/internal/fiberRuntime.ts @@ -1085,13 +1085,13 @@ export class FiberRuntime implements Fiber.RuntimeFi return resume(core.exitSucceed(result.right)) } switch (result.left._tag) { - case "Aborted": { + case "Interrupt": { return resume(core.exitFailCause(internalCause.interrupt(FiberId.none))) } - case "Expected": { + case "Fail": { return resume(core.fail(result.left.error)) } - case "Unexpected": { + case "Die": { return resume(core.die(result.left.defect)) } } diff --git a/packages/effect/test/Micro.test.ts b/packages/effect/test/Micro.test.ts index 970a70c435..f173185938 100644 --- a/packages/effect/test/Micro.test.ts +++ b/packages/effect/test/Micro.test.ts @@ -30,7 +30,7 @@ describe.concurrent("Micro", () => { assert.strictEqual(result, 1) }) - it("acquireUseRelease abort", async () => { + it("acquireUseRelease interrupt", async () => { let acquire = false let use = false let release = false @@ -49,9 +49,9 @@ describe.concurrent("Micro", () => { release = true }) ).pipe(Micro.runFork) - handle.unsafeAbort() + handle.unsafeInterrupt() const result = await Micro.runPromise(handle.await) - assert.deepStrictEqual(result, Micro.ResultAborted) + assert.deepStrictEqual(result, Micro.exitInterrupt) assert.isTrue(acquire) assert.isFalse(use) assert.isTrue(release) @@ -77,7 +77,7 @@ describe.concurrent("Micro", () => { release = true }) ).pipe(Micro.uninterruptible, Micro.runFork) - handle.unsafeAbort() + handle.unsafeInterrupt() const result = await Micro.runPromise(handle.await) assert.deepStrictEqual(result, Either.right(123)) assert.isTrue(acquire) @@ -182,9 +182,9 @@ describe.concurrent("Micro", () => { return i }).pipe(Micro.delay(50))).pipe(Micro.fork) yield* Micro.sleep(125) - yield* handle.abort + yield* handle.interrupt const result = yield* handle.await - assert.deepStrictEqual(result, Micro.ResultAborted) + assert.deepStrictEqual(result, Micro.exitInterrupt) assert.deepStrictEqual(done, [1, 2]) }).pipe(Micro.runPromise)) @@ -197,9 +197,9 @@ describe.concurrent("Micro", () => { return i }).pipe(Micro.delay(50)), { concurrency: "unbounded" }).pipe(Micro.fork) yield* Micro.sleep(25) - yield* handle.abort + yield* handle.interrupt const result = yield* handle.await - assert.deepStrictEqual(result, Micro.ResultAborted) + assert.deepStrictEqual(result, Micro.exitInterrupt) assert.deepStrictEqual(done, []) }).pipe(Micro.runPromise)) @@ -212,9 +212,9 @@ describe.concurrent("Micro", () => { return i }).pipe(Micro.delay(50)), { concurrency: 2 }).pipe(Micro.fork) yield* Micro.sleep(75) - yield* handle.abort + yield* handle.interrupt const result = yield* handle.await - assert.deepStrictEqual(result, Micro.ResultAborted) + assert.deepStrictEqual(result, Micro.exitInterrupt) assert.deepStrictEqual(done, [1, 2]) }).pipe(Micro.runPromise)) @@ -229,7 +229,7 @@ describe.concurrent("Micro", () => { concurrency: "unbounded" }).pipe(Micro.fork) const result = yield* handle.await - assert.deepStrictEqual(result, Micro.ResultFail("error")) + assert.deepStrictEqual(result, Micro.exitFail("error")) assert.deepStrictEqual(done, [1, 2, 3]) }).pipe(Micro.runPromise)) }) @@ -305,7 +305,7 @@ describe.concurrent("Micro", () => { }) describe("acquireRelease", () => { - it("releases on abort", () => + it("releases on interrupt", () => Micro.gen(function*() { let release = false const handle = yield* Micro.acquireRelease( @@ -316,7 +316,7 @@ describe.concurrent("Micro", () => { }) ).pipe(Micro.scoped, Micro.fork) yield* Micro.yieldFlush - handle.unsafeAbort() + handle.unsafeInterrupt() yield* handle.await assert.strictEqual(release, true) }).pipe(Micro.runPromise)) @@ -328,7 +328,7 @@ describe.concurrent("Micro", () => { const result = yield* Micro.raceAll([100, 75, 50, 0, 25].map((ms) => (ms === 0 ? Micro.fail("boom") : Micro.succeed(ms)).pipe( Micro.delay(ms), - Micro.onAbort( + Micro.onInterrupt( Micro.sync(() => { interrupted.push(ms) }) @@ -345,26 +345,26 @@ describe.concurrent("Micro", () => { const result = yield* Micro.raceAllFirst([100, 75, 50, 0, 25].map((ms) => (ms === 0 ? Micro.fail("boom") : Micro.succeed(ms)).pipe( Micro.delay(ms), - Micro.onAbort( + Micro.onInterrupt( Micro.sync(() => { interrupted.push(ms) }) ) ) - )).pipe(Micro.asResult) - assert.deepStrictEqual(result, Micro.ResultFail("boom")) + )).pipe(Micro.exit) + assert.deepStrictEqual(result, Micro.exitFail("boom")) assert.deepStrictEqual(interrupted, [100, 75, 50, 25]) }).pipe(Micro.runPromise)) describe("valid Effect", () => { it.effect("success", () => - Effect.gen(function*(_) { + Effect.gen(function*() { const result = yield* Micro.succeed(123) assert.strictEqual(result, 123) })) it.effect("failure", () => - Effect.gen(function*(_) { + Effect.gen(function*() { const result = yield* Micro.fail("boom").pipe( Effect.sandbox, Effect.flip @@ -373,7 +373,7 @@ describe.concurrent("Micro", () => { })) it.effect("defects", () => - Effect.gen(function*(_) { + Effect.gen(function*() { const result = yield* Micro.die("boom").pipe( Effect.sandbox, Effect.flip @@ -382,7 +382,7 @@ describe.concurrent("Micro", () => { })) it.effect("context", () => - Effect.gen(function*(_) { + Effect.gen(function*() { const result = yield* ATag.pipe( Micro.service, Micro.map((_) => _) @@ -391,7 +391,7 @@ describe.concurrent("Micro", () => { }).pipe(Effect.provideService(ATag, "A"))) it.effect("interruption", () => - Effect.gen(function*(_) { + Effect.gen(function*() { const fiber = yield* Micro.never.pipe( Effect.fork ) @@ -411,7 +411,7 @@ describe.concurrent("Micro", () => { it.effect("is interruptible", () => Micro.void.pipe( Micro.forever, - Micro.timeout(50) + Micro.timeoutOption(50) )) it("works with runSync", () => { @@ -422,15 +422,12 @@ describe.concurrent("Micro", () => { assert.deepStrictEqual(result, 123) }) - it.effect("delayWithRecurs", () => + it.effect("scheduleRecurs", () => Micro.gen(function*() { let count = 0 yield* Micro.sync(() => count++).pipe( Micro.repeat({ - delay: pipe( - Micro.delaySpaced(0), - Micro.delayWithRecurs(3) - ) + schedule: Micro.scheduleRecurs(3) }) ) assert.deepStrictEqual(count, 4) @@ -468,13 +465,13 @@ describe.concurrent("Micro", () => { })) }) - describe("timeout", () => { + describe("timeoutOption", () => { it.live("timeout a long computation", () => Micro.gen(function*() { const result = yield* pipe( Micro.sleep(60_000), Micro.andThen(Micro.succeed(true)), - Micro.timeout(10) + Micro.timeoutOption(10) ) assert.deepStrictEqual(result, Option.none()) })) @@ -491,7 +488,7 @@ describe.concurrent("Micro", () => { Micro.sandbox, Micro.flip ) - assert.deepStrictEqual(result, Micro.FailureUnexpected(error)) + assert.deepStrictEqual(result, Micro.causeDie(error)) })) it.effect("timeout repetition of uninterruptible effect", () => Micro.gen(function*() { @@ -499,16 +496,29 @@ describe.concurrent("Micro", () => { Micro.void, Micro.uninterruptible, Micro.forever, - Micro.timeout(10) + Micro.timeoutOption(10) ) assert.deepStrictEqual(result, Option.none()) })) it.effect("timeout in uninterruptible region", () => Micro.gen(function*() { - yield* Micro.void.pipe(Micro.timeout(20_000), Micro.uninterruptible) + yield* Micro.void.pipe(Micro.timeoutOption(20_000), Micro.uninterruptible) }), { timeout: 1000 }) }) + describe("timeout", () => { + it.live("timeout a long computation", () => + Micro.gen(function*() { + const result = yield* pipe( + Micro.sleep(60_000), + Micro.andThen(Micro.succeed(true)), + Micro.timeout(10), + Micro.flip + ) + assert.deepStrictEqual(result, new Micro.TimeoutException()) + })) + }) + describe("Error", () => { class TestError extends Micro.Error {} @@ -549,9 +559,9 @@ describe.concurrent("Micro", () => { Micro.sandbox, Micro.flip ) - assert.strictEqual(failure.name, "FailureUnexpected") + assert.strictEqual(failure.name, "MicroCause.Die") assert.strictEqual(failure.message, JSON.stringify({ some: "error" })) - assert.include(failure.stack, `FailureUnexpected: ${JSON.stringify({ some: "error" })}`) + assert.include(failure.stack, `MicroCause.Die: ${JSON.stringify({ some: "error" })}`) assert.include(failure.stack, "at test trace (") })) @@ -562,9 +572,9 @@ describe.concurrent("Micro", () => { Micro.sandbox, Micro.flip ) - assert.strictEqual(failure.name, "FailureExpected") + assert.strictEqual(failure.name, "MicroCause.Fail") assert.strictEqual(failure.message, JSON.stringify({ some: "error" })) - assert.include(failure.stack, `FailureExpected: ${JSON.stringify({ some: "error" })}`) + assert.include(failure.stack, `MicroCause.Fail: ${JSON.stringify({ some: "error" })}`) assert.include(failure.stack, "at test trace (") })) @@ -575,9 +585,9 @@ describe.concurrent("Micro", () => { Micro.sandbox, Micro.flip ) - assert.strictEqual(failure.name, "(FailureExpected) Error") + assert.strictEqual(failure.name, "(MicroCause.Fail) Error") assert.strictEqual(failure.message, "boom") - assert.include(failure.stack, `(FailureExpected) Error: boom`) + assert.include(failure.stack, `(MicroCause.Fail) Error: boom`) assert.include(failure.stack, "at test trace (") })) }) @@ -586,15 +596,15 @@ describe.concurrent("Micro", () => { it.effect("sync forever is interruptible", () => Micro.gen(function*() { const fiber = yield* pipe(Micro.succeed(1), Micro.forever, Micro.fork) - const result = yield* fiber.abort - assert.deepStrictEqual(result, Micro.ResultAborted) + const result = yield* fiber.interrupt + assert.deepStrictEqual(result, Micro.exitInterrupt) })) it.effect("interrupt of never is interrupted with cause", () => Micro.gen(function*() { const fiber = yield* Micro.fork(Micro.never) - const result = yield* fiber.abort - assert.deepStrictEqual(result, Micro.ResultAborted) + const result = yield* fiber.interrupt + assert.deepStrictEqual(result, Micro.exitInterrupt) })) it.effect("catchAll + ensuring + interrupt", () => @@ -602,7 +612,7 @@ describe.concurrent("Micro", () => { let catchFailure = false let ensuring = false const handle = yield* Micro.never.pipe( - Micro.catchFailure((_) => + Micro.catchAllCause((_) => Micro.sync(() => { catchFailure = true }) @@ -613,7 +623,7 @@ describe.concurrent("Micro", () => { Micro.fork ) yield* Micro.yieldFlush - yield* handle.abort + yield* handle.interrupt assert.isFalse(catchFailure) assert.isTrue(ensuring) })) @@ -623,17 +633,17 @@ describe.concurrent("Micro", () => { let recovered = false const fiber = yield* Micro.never.pipe( Micro.interruptible, - Micro.asResult, + Micro.exit, Micro.flatMap((result) => Micro.sync(() => { - recovered = result._tag === "Left" && result.left._tag === "Aborted" + recovered = result._tag === "Left" && result.left._tag === "Interrupt" }) ), Micro.uninterruptible, Micro.fork ) yield* Micro.yieldFlush - yield* fiber.abort + yield* fiber.interrupt assert.isTrue(recovered) })) @@ -642,20 +652,20 @@ describe.concurrent("Micro", () => { let counter = 0 const fiber = yield* Micro.never.pipe( Micro.interruptible, - Micro.asResult, + Micro.exit, Micro.andThen(Micro.sync(() => { counter++ })), Micro.uninterruptible, Micro.interruptible, - Micro.asResult, + Micro.exit, Micro.andThen(Micro.sync(() => { counter++ })), Micro.uninterruptible, Micro.fork ) - yield* fiber.abort + yield* fiber.interrupt assert.strictEqual(counter, 2) })) @@ -676,7 +686,7 @@ describe.concurrent("Micro", () => { Micro.fork ) yield* Micro.yieldFlush - yield* fiber.abort + yield* fiber.interrupt assert.isTrue(ref) })) @@ -691,7 +701,7 @@ describe.concurrent("Micro", () => { Micro.fork ) yield* Micro.yieldFlush - yield* fiber.abort + yield* fiber.interrupt assert.isTrue(ref) })) @@ -706,7 +716,7 @@ describe.concurrent("Micro", () => { ) const fiber = yield* child.pipe(Micro.uninterruptible, Micro.fork) yield* Micro.yieldFlush - yield* fiber.abort + yield* fiber.interrupt assert.isTrue(ref) })) @@ -717,89 +727,89 @@ describe.concurrent("Micro", () => { signal = signal_ }).pipe(Micro.fork) yield* Micro.yieldFlush - yield* fiber.abort + yield* fiber.interrupt assert.strictEqual(signal!.aborted, true) })) }) describe("fork", () => { - it.effect("is aborted with parent", () => + it.effect("is interrupted with parent", () => Micro.gen(function*() { let child = false let parent = false const handle = yield* Micro.never.pipe( - Micro.onAbort(Micro.sync(() => { + Micro.onInterrupt(Micro.sync(() => { child = true })), Micro.fork, Micro.andThen(Micro.never), - Micro.onAbort(Micro.sync(() => { + Micro.onInterrupt(Micro.sync(() => { parent = true })), Micro.fork ) yield* Micro.yieldFlush - yield* handle.abort + yield* handle.interrupt assert.isTrue(child) assert.isTrue(parent) })) }) describe("forkDaemon", () => { - it.effect("is not aborted with parent", () => + it.effect("is not interrupted with parent", () => Micro.gen(function*() { let child = false let parent = false const handle = yield* Micro.never.pipe( - Micro.onAbort(Micro.sync(() => { + Micro.onInterrupt(Micro.sync(() => { child = true })), Micro.forkDaemon, Micro.andThen(Micro.never), - Micro.onAbort(Micro.sync(() => { + Micro.onInterrupt(Micro.sync(() => { parent = true })), Micro.fork ) yield* Micro.yieldFlush - yield* handle.abort + yield* handle.interrupt assert.isFalse(child) assert.isTrue(parent) })) }) describe("forkIn", () => { - it.effect("is aborted when scope is closed", () => + it.effect("is interrupted when scope is closed", () => Micro.gen(function*() { - let aborted = false + let interrupted = false const scope = yield* Micro.scopeMake yield* Micro.never.pipe( - Micro.onAbort(Micro.sync(() => { - aborted = true + Micro.onInterrupt(Micro.sync(() => { + interrupted = true })), Micro.forkIn(scope) ) yield* Micro.yieldFlush - yield* scope.close(Micro.resultVoid) - assert.isTrue(aborted) + yield* scope.close(Micro.exitVoid) + assert.isTrue(interrupted) })) }) describe("forkScoped", () => { - it.effect("is aborted when scope is closed", () => + it.effect("is interrupted when scope is closed", () => Micro.gen(function*() { - let aborted = false + let interrupted = false const scope = yield* Micro.scopeMake yield* Micro.never.pipe( - Micro.onAbort(Micro.sync(() => { - aborted = true + Micro.onInterrupt(Micro.sync(() => { + interrupted = true })), Micro.forkScoped, Micro.provideScope(scope) ) yield* Micro.yieldFlush - yield* scope.close(Micro.resultVoid) - assert.isTrue(aborted) + yield* scope.close(Micro.exitVoid) + assert.isTrue(interrupted) })) }) @@ -824,7 +834,7 @@ describe.concurrent("Micro", () => { Micro.flatMap((_) => loop) ) return loop.pipe( - Micro.timeout(50) + Micro.timeoutOption(50) ) }) }) @@ -839,9 +849,9 @@ describe.concurrent("Micro", () => { Micro.ensuring(Micro.sync(() => { finalized = true })), - Micro.asResult + Micro.exit ) - assert.deepStrictEqual(result, Micro.ResultFail(ExampleError)) + assert.deepStrictEqual(result, Micro.exitFail(ExampleError)) assert.isTrue(finalized) })) @@ -849,14 +859,14 @@ describe.concurrent("Micro", () => { Micro.gen(function*() { let finalized = false const result = yield* Micro.fail(ExampleError).pipe( - Micro.onFailure(() => + Micro.onError(() => Micro.sync(() => { finalized = true }) ), - Micro.asResult + Micro.exit ) - assert.deepStrictEqual(result, Micro.ResultFail(ExampleError)) + assert.deepStrictEqual(result, Micro.exitFail(ExampleError)) assert.isTrue(finalized) })) @@ -872,12 +882,12 @@ describe.concurrent("Micro", () => { Micro.flip, Micro.map((cause) => cause) ) - assert.deepStrictEqual(result, Micro.FailureUnexpected(e3)) + assert.deepStrictEqual(result, Micro.causeDie(e3)) })) it.effect("finalizer errors reported", () => Micro.gen(function*() { - let reported: Micro.Result | undefined + let reported: Micro.MicroExit | undefined const result = yield* pipe( Micro.succeed(42), Micro.ensuring(Micro.die(ExampleError)), @@ -894,7 +904,7 @@ describe.concurrent("Micro", () => { ) ) assert.isUndefined(result) - assert.isFalse(reported !== undefined && Micro.resultIsSuccess(reported)) + assert.isFalse(reported !== undefined && Micro.exitIsSuccess(reported)) })) it.effect("acquireUseRelease usage result", () => @@ -915,9 +925,9 @@ describe.concurrent("Micro", () => { () => Micro.void, () => Micro.void ), - Micro.asResult + Micro.exit ) - assert.deepStrictEqual(result, Micro.ResultFail(ExampleError)) + assert.deepStrictEqual(result, Micro.exitFail(ExampleError)) })) it.effect("error in just release", () => @@ -928,9 +938,9 @@ describe.concurrent("Micro", () => { () => Micro.void, () => Micro.die(ExampleError) ), - Micro.asResult + Micro.exit ) - assert.deepStrictEqual(result, Micro.ResultFailUnexpected(ExampleError)) + assert.deepStrictEqual(result, Micro.exitDie(ExampleError)) })) it.effect("error in just usage", () => @@ -941,9 +951,9 @@ describe.concurrent("Micro", () => { () => Micro.fail(ExampleError), () => Micro.void ), - Micro.asResult + Micro.exit ) - assert.deepStrictEqual(result, Micro.ResultFail(ExampleError)) + assert.deepStrictEqual(result, Micro.exitFail(ExampleError)) })) it.effect("rethrown caught error in acquisition", () => @@ -964,9 +974,9 @@ describe.concurrent("Micro", () => { () => Micro.void, () => Micro.die(ExampleError) ), - Micro.asResult + Micro.exit ) - assert.deepStrictEqual(result, Micro.ResultFailUnexpected(ExampleError)) + assert.deepStrictEqual(result, Micro.exitDie(ExampleError)) })) it.effect("rethrown caught error in usage", () => @@ -975,16 +985,16 @@ describe.concurrent("Micro", () => { Micro.void, () => Micro.fail(ExampleError), () => Micro.void - ).pipe(Micro.asResult) - assert.deepEqual(result, Micro.ResultFail(ExampleError)) + ).pipe(Micro.exit) + assert.deepEqual(result, Micro.exitFail(ExampleError)) })) it.effect("onResult - ensures that a cleanup function runs when an effect fails", () => Micro.gen(function*() { let ref = false yield* Micro.die("boom").pipe( - Micro.onResult((result) => - Micro.resultIsFailureUnexpected(result) ? + Micro.onExit((result) => + Micro.exitIsDie(result) ? Micro.sync(() => { ref = true }) : @@ -1062,4 +1072,118 @@ describe.concurrent("Micro", () => { }) }) }) + + describe("catchCauseIf", () => { + it.effect("first argument as success", () => + Micro.gen(function*() { + const result = yield* Micro.catchCauseIf(Micro.succeed(1), () => false, () => Micro.fail("e2")) + assert.deepStrictEqual(result, 1) + })) + it.effect("first argument as failure and predicate return false", () => + Micro.gen(function*() { + const result = yield* Micro.flip( + Micro.catchCauseIf(Micro.fail("e1" as const), () => false, () => Micro.fail("e2" as const)) + ) + assert.deepStrictEqual(result, "e1") + })) + it.effect("first argument as failure and predicate return true", () => + Micro.gen(function*() { + const result = yield* Micro.flip( + Micro.catchCauseIf(Micro.fail("e1" as const), () => true, () => Micro.fail("e2" as const)) + ) + assert.deepStrictEqual(result, "e2") + })) + }) + + describe("catchAll", () => { + it.effect("first argument as success", () => + Micro.gen(function*() { + const result = yield* Micro.catchAll(Micro.succeed(1), () => Micro.fail("e2" as const)) + assert.deepStrictEqual(result, 1) + })) + it.effect("first argument as failure", () => + Micro.gen(function*() { + const result = yield* Micro.flip(Micro.catchAll(Micro.fail("e1" as const), () => Micro.fail("e2" as const))) + assert.deepStrictEqual(result, "e2") + })) + }) + + describe("catchAllCause", () => { + it.effect("first argument as success", () => + Micro.gen(function*() { + const result = yield* Micro.catchAllCause(Micro.succeed(1), () => Micro.fail("e2" as const)) + assert.deepStrictEqual(result, 1) + })) + it.effect("first argument as failure", () => + Micro.gen(function*() { + const result = yield* Micro.flip( + Micro.catchAllCause(Micro.fail("e1" as const), () => Micro.fail("e2" as const)) + ) + assert.deepStrictEqual(result, "e2") + })) + }) + + describe("schedules", () => { + // returns an array of delays, an item for each attempt + const dryRun = (schedule: Micro.MicroSchedule, maxAttempt: number = 7): Array => { + let attempt = 1 + let elapsed = 0 + let duration = schedule(attempt, elapsed) + const out: Array = [] + while (Option.isSome(duration) && attempt <= maxAttempt) { + const value = duration.value + attempt++ + elapsed += value + out.push(value) + duration = schedule(attempt, elapsed) + } + return out + } + + it("scheduleRecurs", () => { + const out = dryRun(Micro.scheduleRecurs(5)) + assert.deepStrictEqual(out, [0, 0, 0, 0, 0]) + }) + + it("scheduleSpaced", () => { + const out = dryRun(Micro.scheduleSpaced(10)) + assert.deepStrictEqual(out, [10, 10, 10, 10, 10, 10, 10]) + }) + + it("scheduleExponential", () => { + const out = dryRun(Micro.scheduleExponential(10)) + assert.deepStrictEqual(out, [20, 40, 80, 160, 320, 640, 1280]) + }) + + it("scheduleAddDelay", () => { + const out = dryRun(Micro.scheduleAddDelay(Micro.scheduleRecurs(5), () => 10)) + assert.deepStrictEqual(out, [10, 10, 10, 10, 10]) + }) + + it("scheduleWithMaxDelay", () => { + const out = dryRun(Micro.scheduleWithMaxDelay(Micro.scheduleExponential(10), 400)) + assert.deepStrictEqual(out, [20, 40, 80, 160, 320, 400, 400]) + }) + + it("scheduleWithMaxElapsed", () => { + const out = dryRun(Micro.scheduleWithMaxElapsed(Micro.scheduleExponential(10), 400)) + assert.deepStrictEqual(out, [20, 40, 80, 160, 320]) + }) + + it("scheduleUnion", () => { + const out = dryRun(Micro.scheduleUnion( + Micro.scheduleExponential(10), + Micro.scheduleSpaced(100) + )) + assert.deepStrictEqual(out, [20, 40, 80, 100, 100, 100, 100]) + }) + + it("scheduleIntersect", () => { + const out = dryRun(Micro.scheduleIntersect( + Micro.scheduleExponential(10), + Micro.scheduleSpaced(100) + )) + assert.deepStrictEqual(out, [100, 100, 100, 160, 320, 640, 1280]) + }) + }) })