Skip to main content Link Search Menu Expand Document (external link)

EffectX overview

Generic, framework-agnostic extensions to Effect’s Effect module.

Added in v0.0.0


Table of contents


conversions

fromOptionOrElse

Converts an Option to an Effect, mapping the None case to a caller-chosen error via the onNone thunk.

Equivalent to Effect.mapError(Effect.fromOption(option), onNone): it bridges the NoSuchElementError that Effect.fromOption produces to the caller’s own error type, so callers never have to handle NoSuchElementError. This fills the v4 gap where Effect.mapError no longer accepts an Option directly — instead of pipe(option, Effect.fromOption, Effect.mapError(() => new MyError())), write pipe(option, EffectX.fromOptionOrElse(() => new MyError())). The onNone thunk runs only when the Option is None.

Signature

export declare const fromOptionOrElse: {
  <E>(onNone: () => E): <A>(option: Option.Option<A>) => Effect.Effect<A, E>
  <A, E>(option: Option.Option<A>, onNone: () => E): Effect.Effect<A, E>
}

Example

import { Effect, Option, Result, pipe } from "effect"
import { EffectX } from "@nunofyobiz/effect-extras"

// data-first
const some = EffectX.fromOptionOrElse(Option.some(42), () => "missing")
assert.deepStrictEqual(Effect.runSync(Effect.result(some)), Result.succeed(42))

// data-last (piped) — None maps to the chosen error
const none = pipe(
  Option.none<number>(),
  EffectX.fromOptionOrElse(() => "missing")
)
assert.deepStrictEqual(Effect.runSync(Effect.result(none)), Result.fail("missing"))

Added in v0.0.0

sequencing

flattenOption

Flattens an Effect that succeeds with an Option into an Effect that fails with onNone() when the Option is None.

When the wrapped Option is Some(value) the effect succeeds with value; when it is None the effect fails with the error produced by the onNone thunk. An existing failure of the source effect is preserved untouched, so the result’s error channel is the union of the original error and the None error.

Signature

export declare const flattenOption: (<A, E1, E2, R>(
  onNone: () => E2
) => (effect: Effect.Effect<Option.Option<A>, E1, R>) => Effect.Effect<A, E1 | E2, R>) &
  (<A, E1, E2, R>(effect: Effect.Effect<Option.Option<A>, E1, R>, onNone: () => E2) => Effect.Effect<A, E1 | E2, R>)

Example

import { Effect, Option, Result } from "effect"
import { EffectX } from "@nunofyobiz/effect-extras"

const some = EffectX.flattenOption(Effect.succeed(Option.some(1)), () => "missing")
assert.deepStrictEqual(Effect.runSync(Effect.result(some)), Result.succeed(1))

const none = EffectX.flattenOption(Effect.succeed(Option.none<number>()), () => "missing")
assert.deepStrictEqual(Effect.runSync(Effect.result(none)), Result.fail("missing"))

Added in v0.0.0

tryUntil

Repeatedly calls a synchronous try thunk until its result satisfies the until refinement, sleeping sleepDuration between attempts and failing with a TimeoutError once maxDuration elapses.

The thunk is evaluated immediately; if its first result already passes the refinement the effect succeeds without any delay. Otherwise it polls on the sleepDuration interval (defaulting to 200ms — the threshold below which a delay reads as “instant” to a user) until either the predicate holds (the effect succeeds with the narrowed B value) or maxDuration is exceeded (the effect fails with a Cause.TimeoutError). Use it to await an external, non-effectful condition such as a flag flipped by a callback.

Signature

export declare const tryUntil: <A, B extends A>({
  try: doTry,
  until: isDone,
  sleepDuration,
  maxDuration
}: {
  try: () => A
  until: Predicate.Refinement<A, B>
  sleepDuration?: Duration.Duration
  maxDuration: Duration.Duration
}) => Effect.Effect<B, Cause.TimeoutError, never>

Example

import { Duration, Effect } from "effect"
import { EffectX } from "@nunofyobiz/effect-extras"

// First attempt already matches, so it resolves immediately.
const effect = EffectX.tryUntil({
  try: () => 1,
  until: (value: number): value is number => value === 1,
  sleepDuration: Duration.millis(100),
  maxDuration: Duration.seconds(1)
})

assert.deepStrictEqual(Effect.runSync(effect), 1)

Added in v0.0.0