1import * as Data from "effect/Data";
2import * as Effect from "effect/Effect";
3import * as Predicate from "effect/Predicate";
4import * as Schedule from "effect/Schedule";
5
6export class PredicateFailed extends Data.TaggedError("PredicateFailed")<{
7 message: string;
8 actual: unknown;
9}> {}
10
11export const isPredicateFailed = (e: unknown): e is PredicateFailed =>
12 Predicate.isTagged(e, "PredicateFailed");
13
14/**
15 * Retries an effect until a predicate is met.
16 * @param input - The input to the poll function.
17 * @param input.description - The description of what is being polled; used in the error message if the predicate fails.
18 * @param input.effect - The effect to execute until the predicate is met.
19 * @param input.predicate - The predicate to check if the effect has met the desired state.
20 * @param input.schedule - The schedule to use for retries; defaults to every 3 seconds.
21 * @param input.times - The maximum number of times to poll; defaults to 50.
22 * @returns The value that satisfies the predicate.
23 */
24export const poll = Effect.fn("poll")(
25 <A, E, R>(input: {
26 description?: string;
27 effect: Effect.Effect<A, E, R>;
28 predicate: (value: A) => boolean;
29 schedule?: Schedule.Schedule<unknown, unknown, never>;
30 }) =>
31 input.effect.pipe(
32 Effect.filterOrFail(
33 input.predicate,
34 (actual) =>
35 new PredicateFailed({
36 message: `Predicate failed: ${input.description ?? "<no description>"}`,
37 actual,
38 }),
39 ),
40 Effect.retry({
41 while: isPredicateFailed,
42 schedule:
43 input.schedule ??
44 Schedule.max([Schedule.spaced("5 seconds"), Schedule.recurs(50)]),
45 }),
46 ),
47);