import type * as Cause from "../Cause.ts" import type { Effect, Repeat, Retry } from "../Effect.ts" import { dual, identity } from "../Function.ts" import * as Option from "../Option.ts" import * as Pull from "../Pull.ts" import * as Schedule from "../Schedule.ts" import type { NoInfer } from "../Types.ts" import { internalCall } from "../Utils.ts" import * as core from "./core.ts" import * as effect from "./effect.ts" /** @internal */ export const repeatOrElse: { ( schedule: Schedule.Schedule, orElse: (error: E | E2, meta: Option.Option>) => Effect ): ( self: Effect ) => Effect ( self: Effect, schedule: Schedule.Schedule, orElse: (error: E | E2, meta: Option.Option>) => Effect ): Effect } = dual(3, ( self: Effect, schedule: Schedule.Schedule, orElse: (error: E | E2, meta: Option.Option>) => Effect ): Effect => effect.flatMap(Schedule.toStepWithMetadata(schedule), (step) => { let meta = Schedule.CurrentMetadata.defaultValue() const loop = (input: A): Effect => Pull.matchEffect(step(input), { onSuccess(meta_) { meta = meta_ return effect.flatMap(effect.provideService(self, Schedule.CurrentMetadata, meta), loop) }, onFailure: effect.failCause, onDone: effect.succeed }) return effect.catchCauseFilter( effect.flatMap(effect.provideService(self, Schedule.CurrentMetadata, meta), loop), effect.findRecoverableError, (error) => orElse(error, meta.attempt === 0 ? Option.none() : Option.some(meta as any)) ) })) /** @internal */ export const retryOrElse: { ( policy: Schedule.Schedule, E1, R1>, orElse: (e: NoInfer, out: A1) => Effect ): (self: Effect) => Effect ( self: Effect, policy: Schedule.Schedule, E1, R1>, orElse: (e: NoInfer, out: A1) => Effect ): Effect } = dual(3, ( self: Effect, policy: Schedule.Schedule, E1, R1>, orElse: (e: NoInfer, out: A1) => Effect ): Effect => effect.flatMap(Schedule.toStepWithMetadata(policy), (step) => { let meta = Schedule.CurrentMetadata.defaultValue() let lastError!: E const loop: Effect, R | R1> = effect.catchCauseFilter( effect.suspend(() => effect.provideService(self, Schedule.CurrentMetadata, meta)), effect.findRecoverableError, (error) => { lastError = error return effect.flatMap(step(error), (meta_) => { meta = meta_ return loop }) } ) return Pull.catchDone(loop, (out) => internalCall(() => orElse(lastError, out as A1))) })) /** @internal */ export const repeat = dual<{ , A>( options: O ): (self: Effect) => Repeat.Return ( schedule: Schedule.Schedule, Error, Env> ): (self: Effect) => Effect ( builder: ( $: (_: Schedule.Schedule, E, R>) => Schedule.Schedule ) => Schedule.Schedule, Error, Env> ): (self: Effect) => Effect }, { >( self: Effect, options: O ): Repeat.Return ( self: Effect, schedule: Schedule.Schedule, Error, Env> ): Effect ( self: Effect, builder: ( $: (_: Schedule.Schedule, E, R>) => Schedule.Schedule ) => Schedule.Schedule, Error, Env> ): Effect }>( 2, ( self: Effect, options: | Repeat.Options | Schedule.Schedule | (( $: (_: Schedule.Schedule) => Schedule.Schedule ) => Schedule.Schedule) ) => { const schedule = typeof options === "function" ? options(identity) : Schedule.isSchedule(options) ? options : buildFromOptions(options) return repeatOrElse(self, schedule, effect.fail) } ) /** @internal */ export const retry = dual<{ >( options: O ): ( self: Effect ) => Retry.Return ( policy: Schedule.Schedule, Error, Env> ): (self: Effect) => Effect ( builder: ( $: (_: Schedule.Schedule, SE, R>) => Schedule.Schedule ) => Schedule.Schedule, Error, Env> ): (self: Effect) => Effect }, { >( self: Effect, options: O ): Retry.Return ( self: Effect, policy: Schedule.Schedule, Error, Env> ): Effect ( self: Effect, builder: ( $: (_: Schedule.Schedule, SE, R>) => Schedule.Schedule ) => Schedule.Schedule, Error, Env> ): Effect }>( 2, ( self: Effect, options: | Retry.Options | Schedule.Schedule | (( $: (_: Schedule.Schedule) => Schedule.Schedule ) => Schedule.Schedule) ) => { const schedule = typeof options === "function" ? options(identity) : Schedule.isSchedule(options) ? options : buildFromOptions(options) return retryOrElse(self, schedule, effect.fail) } ) /** @internal */ export const scheduleFrom = dual< ( initial: Input, schedule: Schedule.Schedule ) => ( self: Effect ) => Effect, ( self: Effect, initial: Input, schedule: Schedule.Schedule ) => Effect >(3, ( self: Effect, initial: Input, schedule: Schedule.Schedule ): Effect => effect.flatMap(Schedule.toStepWithMetadata(schedule), (step) => { const loop = (input: Input): Effect => Pull.matchEffect(step(input), { onSuccess: (meta) => effect.flatMap(effect.provideService(self, Schedule.CurrentMetadata, meta), loop), onFailure: effect.failCause, onDone: effect.succeed }) return loop(initial) })) const passthroughForever = Schedule.passthrough(Schedule.forever) /** @internal */ export const buildFromOptions = (options: { schedule?: Schedule.Schedule | undefined while?: ((input: Input) => boolean | Effect) | undefined until?: ((input: Input) => boolean | Effect) | undefined times?: number | undefined }) => { let schedule: Schedule.Schedule = options.schedule ? Schedule.passthrough(options.schedule) : passthroughForever if (options.while) { schedule = Schedule.while(schedule, ({ input }) => { const applied = options.while!(input) return core.isEffect(applied) ? applied : effect.succeed(applied) }) } if (options.until) { schedule = Schedule.while(schedule, ({ input }) => { const applied = options.until!(input) return core.isEffect(applied) ? effect.map(applied, (b) => !b) : effect.succeed(!applied) }) } if (options.times !== undefined) { schedule = Schedule.while(schedule, ({ attempt }) => effect.succeed(attempt <= options.times!)) } return schedule }