import type * as Cause from "../Cause.ts" import type { Effect, Repeat, Retry } from "../Effect.ts" import { constant, constTrue, 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() return effect.catch_( effect.forever( effect.tap( effect.flatMap(effect.suspend(() => effect.provideService(self, Schedule.CurrentMetadata, meta)), step), (meta_) => effect.sync(() => { meta = meta_ }) ), { disableYield: true } ), (error) => core.isDone(error) ? effect.succeed(error.value as B) : orElse(error as E | E2, 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.catch_( effect.suspend(() => effect.provideService(self, Schedule.CurrentMetadata, meta)), (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) => { let meta = Schedule.CurrentMetadata.defaultValue() const selfWithMeta = effect.suspend(() => effect.provideService(self, Schedule.CurrentMetadata, meta)) return effect.catch_( effect.flatMap( step(initial), (meta_) => { meta = meta_ const body = constant(effect.flatMap(selfWithMeta, step)) return effect.whileLoop({ while: constTrue, body, step(meta_) { meta = meta_ } }) as Effect } ), (error) => core.isDone(error) ? effect.succeed(error.value as Output) : effect.fail(error as E) ) })) 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 }