// ets_tracing: off
/* eslint-disable prefer-const */
import * as A from "../Collections/Immutable/Array/index.js"
import * as E from "../Either/index.js"
import type { FiberID } from "../Fiber/id.js"
import type { Trace } from "../Fiber/index.js"
import { identity, pipe } from "../Function/index.js"
import * as S from "../IO/index.js"
import * as O from "../Option/index.js"
import { Stack } from "../Stack/index.js"
import type { Both, Cause, Then, Traced } from "./cause.js"
import { combinePar, combineSeq, die, empty, fail, interrupt, traced } from "./cause.js"
import { InterruptedException } from "./errors.js"
export {
combinePar,
Cause,
die,
empty,
fail,
interrupt,
combineSeq,
traced,
isEmpty
} from "./cause.js"
/**
* Applicative's ap
*/
export function ap(fa: Cause): (fab: Cause<(a: A) => B>) => Cause {
return chain((f) => pipe(fa, map(f)))
}
/**
* Substitute the E in the cause
*/
export function as(e: E1) {
return map(() => e)
}
/**
* Builds a Cause depending on the result of another
*/
export function chain_(cause: Cause, f: (_: E) => Cause): Cause {
return S.run(chainSafe_(cause, f))
}
/**
* Builds a Cause depending on the result of another
*/
export function chain(f: (_: E) => Cause) {
return (cause: Cause): Cause => chain_(cause, f)
}
/**
* Builds a Cause depending on the result of another
*/
export function chainSafe_(
cause: Cause,
f: (_: E) => Cause
): S.IO> {
switch (cause._tag) {
case "Empty": {
return S.succeed(empty)
}
case "Fail": {
return S.succeed(f(cause.value))
}
case "Die": {
return S.succeed(cause)
}
case "Interrupt": {
return S.succeed(cause)
}
case "Then": {
return S.zipWith_(
S.suspend(() => chainSafe_(cause.left, f)),
S.suspend(() => chainSafe_(cause.right, f)),
(l, r) => combineSeq(l, r)
)
}
case "Both": {
return S.zipWith_(
S.suspend(() => chainSafe_(cause.left, f)),
S.suspend(() => chainSafe_(cause.right, f)),
(l, r) => combinePar(l, r)
)
}
case "Traced": {
return S.map_(chainSafe_(cause.cause, f), (x) => traced(x, cause.trace))
}
}
}
/**
* Equivalent to chain((a) => Fail(f(a)))
*/
export function map_(cause: Cause, f: (e: E) => E1) {
return chain_(cause, (e: E) => fail(f(e)))
}
/**
* Equivalent to chain((a) => Fail(f(a)))
*/
export function map(f: (e: E) => E1) {
return (cause: Cause) => map_(cause, f)
}
/**
* Determines if this cause contains or is equal to the specified cause.
*/
export function contains(that: Cause) {
return (cause: Cause) => S.run(containsSafe(that)(cause))
}
/**
* Determines if this cause contains or is equal to the specified cause.
*/
export function containsSafe(that: Cause) {
return (cause: Cause) =>
S.gen(function* (_) {
if (yield* _(cause.equalsSafe(that))) {
return true
}
return yield* _(
pipe(
cause,
reduceLeft(S.succeed(false))((_, c) =>
O.some(S.chain_(_, (b) => (b ? S.succeed(b) : c.equalsSafe(that))))
)
)
)
})
}
/**
* Extracts a list of non-recoverable errors from the `Cause`.
*/
export function defects(cause: Cause): readonly unknown[] {
return pipe(
cause,
reduceLeft([])((a, c) =>
c._tag === "Die" ? O.some([...a, c.value]) : O.none
)
)
}
/**
* Returns the `Error` associated with the first `Die` in this `Cause` if
* one exists.
*/
export function dieOption(cause: Cause) {
return pipe(
cause,
find((c) => (c._tag === "Die" ? O.some(c.value) : O.none))
)
}
/**
* Returns if a cause contains a defect
*/
export function died(cause: Cause) {
return pipe(
cause,
dieOption,
O.map(() => true),
O.getOrElse(() => false)
)
}
/**
* Returns the `E` associated with the first `Fail` in this `Cause` if one
* exists.
*/
export function failureOption(cause: Cause) {
return pipe(
cause,
find((c) => (c._tag === "Fail" ? O.some(c.value) : O.none))
)
}
/**
* Returns if the cause has a failure in it
*/
export function failed(cause: Cause) {
return pipe(
cause,
failureOption,
O.map(() => true),
O.getOrElse(() => false)
)
}
/**
* Retrieve the first checked error on the `Left` if available,
* if there are no checked errors return the rest of the `Cause`
* that is known to contain only `Die` or `Interrupt` causes.
* */
export function failureOrCause(cause: Cause): E.Either> {
return pipe(
cause,
failureOption,
O.map(E.left),
O.getOrElse(() => E.right(cause as Cause)) // no E inside this cause, can safely cast
)
}
/**
* Produces a list of all recoverable errors `E` in the `Cause`.
*/
export function failures(cause: Cause) {
return pipe(
cause,
reduceLeft([])((a, c) =>
c._tag === "Fail" ? O.some([...a, c.value]) : O.none
)
)
}
/**
* Remove all `Die` causes that the specified partial function is defined at,
* returning `Some` with the remaining causes or `None` if there are no
* remaining causes.
*/
export function stripSomeDefects(f: (_: unknown) => O.Option) {
return (cause: Cause): O.Option> => {
return S.run(stripSomeDefectsSafe(cause, f))
}
}
/**
* Remove all `Die` causes that the specified partial function is defined at,
* returning `Some` with the remaining causes or `None` if there are no
* remaining causes.
*/
export function stripSomeDefects_(
cause: Cause,
f: (_: unknown) => O.Option
): O.Option> {
return S.run(stripSomeDefectsSafe(cause, f))
}
/**
* Filter out all `Die` causes according to the specified function,
* returning `Some` with the remaining causes or `None` if there are no
* remaining causes.
*/
export function stripSomeDefectsSafe(
cause: Cause,
f: (_: unknown) => O.Option
): S.IO>> {
switch (cause._tag) {
case "Empty": {
return S.succeed(O.none)
}
case "Interrupt": {
return S.succeed(O.some(cause))
}
case "Fail": {
return S.succeed(O.some(cause))
}
case "Die": {
return S.succeed(O.map_(f(cause.value), die))
}
case "Both": {
return S.zipWith_(
S.suspend(() => stripSomeDefectsSafe(cause.left, f)),
S.suspend(() => stripSomeDefectsSafe(cause.right, f)),
(l, r) => {
if (l._tag === "Some" && r._tag === "Some") {
return O.some(combinePar(l.value, r.value))
} else if (l._tag === "Some") {
return l
} else if (r._tag === "Some") {
return r
} else {
return O.none
}
}
)
}
case "Then": {
return S.zipWith_(
S.suspend(() => stripSomeDefectsSafe(cause.left, f)),
S.suspend(() => stripSomeDefectsSafe(cause.right, f)),
(l, r) => {
if (l._tag === "Some" && r._tag === "Some") {
return O.some(combineSeq(l.value, r.value))
} else if (l._tag === "Some") {
return l
} else if (r._tag === "Some") {
return r
} else {
return O.none
}
}
)
}
case "Traced": {
return S.suspend(() => stripSomeDefectsSafe(cause.cause, f))
}
}
}
/**
* Finds the first result matching f
*/
export function find(
f: (cause: Cause) => O.Option
): (cause: Cause) => O.Option {
return (cause) => S.run(findSafe(f)(cause))
}
/**
* Finds the first result matching f
*/
export function findSafe(
f: (cause: Cause) => O.Option
): (cause: Cause) => S.IO> {
return (cause) => {
const apply = f(cause)
if (apply._tag === "Some") {
return S.succeed(apply)
}
switch (cause._tag) {
case "Then": {
return S.chain_(
S.suspend(() => findSafe(f)(cause.left)),
(isLeft) => {
if (isLeft._tag === "Some") {
return S.succeed(isLeft)
} else {
return findSafe(f)(cause.right)
}
}
)
}
case "Traced": {
return S.suspend(() => findSafe(f)(cause.cause))
}
case "Both": {
return S.chain_(
S.suspend(() => findSafe(f)(cause.left)),
(isLeft) => {
if (isLeft._tag === "Some") {
return S.succeed(isLeft)
} else {
return findSafe(f)(cause.right)
}
}
)
}
default: {
return S.succeed(apply)
}
}
}
}
/**
* Equivalent to chain(identity)
*/
export const flatten: (cause: Cause>) => Cause = chain(identity)
/**
* Folds over a cause
*/
export function fold(
empty: () => Z,
failCase: (_: E) => Z,
dieCase: (_: unknown) => Z,
interruptCase: (_: FiberID) => Z,
thenCase: (_: Z, __: Z) => Z,
bothCase: (_: Z, __: Z) => Z,
tracedCase: (_: Z, __: Trace) => Z
) {
return (cause: Cause): Z =>
S.run(
foldSafe(
empty,
failCase,
dieCase,
interruptCase,
thenCase,
bothCase,
tracedCase
)(cause)
)
}
/**
* Folds over a cause
*/
export function foldSafe(
empty: () => Z,
failCase: (_: E) => Z,
dieCase: (_: unknown) => Z,
interruptCase: (_: FiberID) => Z,
thenCase: (_: Z, __: Z) => Z,
bothCase: (_: Z, __: Z) => Z,
tracedCase: (_: Z, __: Trace) => Z
) {
return (cause: Cause): S.IO => {
switch (cause._tag) {
case "Empty": {
return S.succeedWith(empty)
}
case "Fail": {
return S.succeed(failCase(cause.value))
}
case "Die": {
return S.succeed(dieCase(cause.value))
}
case "Interrupt": {
return S.succeed(interruptCase(cause.fiberId))
}
case "Traced": {
return S.map_(
S.suspend(() =>
foldSafe(
empty,
failCase,
dieCase,
interruptCase,
thenCase,
bothCase,
tracedCase
)(cause.cause)
),
(x) => tracedCase(x, cause.trace)
)
}
case "Both": {
return S.zipWith_(
S.suspend(() =>
foldSafe(
empty,
failCase,
dieCase,
interruptCase,
thenCase,
bothCase,
tracedCase
)(cause.left)
),
S.suspend(() =>
foldSafe(
empty,
failCase,
dieCase,
interruptCase,
thenCase,
bothCase,
tracedCase
)(cause.right)
),
(l, r) => bothCase(l, r)
)
}
case "Then": {
return S.zipWith_(
S.suspend(() =>
foldSafe(
empty,
failCase,
dieCase,
interruptCase,
thenCase,
bothCase,
tracedCase
)(cause.left)
),
S.suspend(() =>
foldSafe(
empty,
failCase,
dieCase,
interruptCase,
thenCase,
bothCase,
tracedCase
)(cause.right)
),
(l, r) => thenCase(l, r)
)
}
}
}
}
/**
* Accumulates a state over a Cause
*/
export function reduceLeft(z: Z) {
return (f: (z: Z, cause: Cause) => O.Option): ((cause: Cause) => Z) => {
return (cause) => {
let causes: Stack> | undefined = undefined
let current: Cause | undefined = cause
let acc = z
while (current) {
const x = f(acc, current)
acc = x._tag === "Some" ? x.value : acc
switch (current._tag) {
case "Then": {
causes = new Stack(current.right, causes)
current = current.left
break
}
case "Both": {
causes = new Stack(current.right, causes)
current = current.left
break
}
case "Traced": {
current = current.cause
break
}
default: {
current = undefined
break
}
}
if (!current && causes) {
current = causes.value
causes = causes.previous
}
}
return acc
}
}
}
/**
* Returns if the cause contains an interruption in it
*/
export function interrupted(cause: Cause) {
return pipe(
cause,
interruptOption,
O.map(() => true),
O.getOrElse(() => false)
)
}
/**
* Returns the `FiberID` associated with the first `Interrupt` in this `Cause` if one
* exists.
*/
export function interruptOption(cause: Cause) {
return pipe(
cause,
find((c) => (c._tag === "Interrupt" ? O.some(c.fiberId) : O.none))
)
}
/**
* Determines if the `Cause` contains only interruptions and not any `Die` or
* `Fail` causes.
*/
export function interruptedOnly(cause: Cause) {
return pipe(
cause,
find((c) => (c._tag === "Die" || c._tag === "Fail" ? O.some(false) : O.none)),
O.getOrElse(() => true)
)
}
/**
* Returns a set of interruptors, fibers that interrupted the fiber described
* by this `Cause`.
*/
export function interruptors(cause: Cause): readonly FiberID[] {
return Array.from(
pipe(
cause,
reduceLeft>(new Set())((s, c) =>
c._tag === "Interrupt" ? O.some(s.add(c.fiberId)) : O.none
)
)
)
}
/**
* Remove all `Fail` and `Interrupt` nodes from this `Cause`,
* return only `Die` cause/finalizer defects.
*/
export function keepDefectsSafe(cause: Cause): S.IO>> {
switch (cause._tag) {
case "Empty": {
return S.succeed(O.none)
}
case "Fail": {
return S.succeed(O.none)
}
case "Interrupt": {
return S.succeed(O.none)
}
case "Die": {
return S.succeed(O.some(cause))
}
case "Traced": {
return S.map_(
S.suspend(() => keepDefectsSafe(cause.cause)),
(x) => O.map_(x, (_) => traced(_, cause.trace))
)
}
case "Then": {
return S.zipWith_(
S.suspend(() => keepDefectsSafe(cause.left)),
S.suspend(() => keepDefectsSafe(cause.right)),
(l, r) => {
if (l._tag === "Some" && r._tag === "Some") {
return O.some(combineSeq(l.value, r.value))
} else if (l._tag === "Some") {
return l
} else if (r._tag === "Some") {
return r
} else {
return O.none
}
}
)
}
case "Both": {
return S.zipWith_(
S.suspend(() => keepDefectsSafe(cause.left)),
S.suspend(() => keepDefectsSafe(cause.right)),
(l, r) => {
if (l._tag === "Some" && r._tag === "Some") {
return O.some(combinePar(l.value, r.value))
} else if (l._tag === "Some") {
return l
} else if (r._tag === "Some") {
return r
} else {
return O.none
}
}
)
}
}
}
/**
* Remove all `Fail` and `Interrupt` nodes from this `Cause`,
* return only `Die` cause/finalizer defects.
*/
export function keepDefects(cause: Cause): O.Option> {
return S.run(keepDefectsSafe(cause))
}
/**
* Converts the specified `Cause>` to an `Either, A>`.
*/
export function sequenceCauseEither(
c: Cause>
): E.Either, A> {
return S.run(sequenceCauseEitherSafe(c))
}
/**
* Converts the specified `Cause>` to an `Either, A>`.
*/
export function sequenceCauseEitherSafe(
c: Cause>
): S.IO, A>> {
switch (c._tag) {
case "Empty": {
return S.succeed(E.left(empty))
}
case "Interrupt": {
return S.succeed(E.left(c))
}
case "Fail": {
return S.succeed(
c.value._tag === "Left" ? E.left(fail(c.value.left)) : E.right(c.value.right)
)
}
case "Traced": {
return S.map_(
S.suspend(() => sequenceCauseEitherSafe(c.cause)),
(x) => E.mapLeft_(x, (_) => traced(_, c.trace))
)
}
case "Die": {
return S.succeed(E.left(c))
}
case "Then": {
return S.zipWith_(
S.suspend(() => sequenceCauseEitherSafe(c.left)),
S.suspend(() => sequenceCauseEitherSafe(c.right)),
(l, r) => {
if (l._tag === "Left") {
if (r._tag === "Right") {
return E.right(r.right)
} else {
return E.left(combineSeq(l.left, r.left))
}
} else {
return E.right(l.right)
}
}
)
}
case "Both": {
return S.zipWith_(
S.suspend(() => sequenceCauseEitherSafe(c.left)),
S.suspend(() => sequenceCauseEitherSafe(c.right)),
(l, r) => {
if (l._tag === "Left") {
if (r._tag === "Right") {
return E.right(r.right)
} else {
return E.left(combinePar(l.left, r.left))
}
} else {
return E.right(l.right)
}
}
)
}
}
}
/**
* Converts the specified `Cause