Skip to content

Effect.retry: support Refinement narrowing for while option #6122

@IGassmann

Description

@IGassmann

What is the problem

Effect.retry supports error type narrowing via Refinement for the until option but not for while.

With until, passing a Refinement<E, E2> narrows the output error type to E2 (the errors that stop retrying and pass through):

// Line 4260 of Effect.ts
O extends { until: Refinement<E, infer E2> } ? E2

The while option has no equivalent branch — it always preserves the full error type E, even when the predicate is a type guard.

Expected behavior

while should support Refinement<E, E2> symmetrically. When while receives a refinement that identifies the errors to keep retrying on (E2), the output error type should be narrowed to Exclude<E, E2> — the errors that cause while to return false and pass through.

Example

import { Effect, Predicate } from "effect"

declare const myEffect: Effect.Effect<void, ErrorA | ErrorB | ErrorC>

// Currently: output error is still ErrorA | ErrorB | ErrorC
const result = Effect.retry(myEffect, {
  while: (e): e is ErrorA => e._tag === "ErrorA",
})

// Desired: output error narrowed to ErrorB | ErrorC
// (ErrorA is always retried, so only ErrorB | ErrorC can pass through)

Workaround

Use until with an inverted refinement:

Effect.retry(myEffect, {
  until: (e): e is Exclude<typeof e, ErrorA> => e._tag !== "ErrorA",
})

This works but is less readable than the while form, especially when the intent is "retry while offline."

Suggested change

In Retry.Return, add a Refinement branch for while:

// Current (line 4259-4261):
O extends { schedule: Schedule.Schedule<infer _O, infer _I, infer _R> } ? E
  : O extends { until: Refinement<E, infer E2> } ? E2
  : E

// Proposed:
O extends { schedule: Schedule.Schedule<infer _O, infer _I, infer _R> } ? E
  : O extends { while: Refinement<E, infer E2> } ? Exclude<E, E2>
  : O extends { until: Refinement<E, infer E2> } ? E2
  : E

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions