| 1 | import type * as Cause from "../Cause.ts" |
| 2 | import type { Effect, Repeat, Retry } from "../Effect.ts" |
| 3 | import { constant, constTrue, dual, identity } from "../Function.ts" |
| 4 | import * as Option from "../Option.ts" |
| 5 | import * as Pull from "../Pull.ts" |
| 6 | import * as Schedule from "../Schedule.ts" |
| 7 | import type { NoInfer } from "../Types.ts" |
| 8 | import { internalCall } from "../Utils.ts" |
| 9 | import * as core from "./core.ts" |
| 10 | import * as effect from "./effect.ts" |
| 11 | |
| 12 | /** @internal */ |
| 13 | export const repeatOrElse: { |
| 14 | <R2, A, B, E, E2, E3, R3>( |
| 15 | schedule: Schedule.Schedule<B, A, E2, R2>, |
| 16 | orElse: (error: E | E2, meta: Option.Option<Schedule.Metadata<B, A>>) => Effect<B, E3, R3> |
| 17 | ): <R>( |
| 18 | self: Effect<A, E, R> |
| 19 | ) => Effect<B, E3, R | R2 | R3> |
| 20 | <A, E, R, R2, B, E2, E3, R3>( |
| 21 | self: Effect<A, E, R>, |
| 22 | schedule: Schedule.Schedule<B, A, E2, R2>, |
| 23 | orElse: (error: E | E2, meta: Option.Option<Schedule.Metadata<B, A>>) => Effect<B, E3, R3> |
| 24 | ): Effect<B, E3, R | R2 | R3> |
| 25 | } = dual(3, <A, E, R, R2, B, E2, E3, R3>( |
| 26 | self: Effect<A, E, R>, |
| 27 | schedule: Schedule.Schedule<B, A, E2, R2>, |
| 28 | orElse: (error: E | E2, meta: Option.Option<Schedule.Metadata<B, A>>) => Effect<B, E3, R3> |
| 29 | ): Effect<B, E3, R | R2 | R3> => |
| 30 | effect.flatMap(Schedule.toStepWithMetadata(schedule), (step) => { |
| 31 | let meta = Schedule.CurrentMetadata.defaultValue() |
| 32 | return effect.catch_( |
| 33 | effect.forever( |
| 34 | effect.tap( |
| 35 | effect.flatMap(effect.suspend(() => effect.provideService(self, Schedule.CurrentMetadata, meta)), step), |
| 36 | (meta_) => |
| 37 | effect.sync(() => { |
| 38 | meta = meta_ |
| 39 | }) |
| 40 | ), |
| 41 | { disableYield: true } |
| 42 | ), |
| 43 | (error) => |
| 44 | core.isDone(error) |
| 45 | ? effect.succeed(error.value as B) |
| 46 | : orElse(error as E | E2, meta.attempt === 0 ? Option.none() : Option.some(meta as any)) |
| 47 | ) |
| 48 | })) |
| 49 | |
| 50 | /** @internal */ |
| 51 | export const retryOrElse: { |
| 52 | <A1, E, E1, R1, A2, E2, R2>( |
| 53 | policy: Schedule.Schedule<A1, NoInfer<E>, E1, R1>, |
| 54 | orElse: (e: NoInfer<E>, out: A1) => Effect<A2, E2, R2> |
| 55 | ): <A, R>(self: Effect<A, E, R>) => Effect<A | A2, E1 | E2, R | R1 | R2> |
| 56 | <A, E, R, A1, E1, R1, A2, E2, R2>( |
| 57 | self: Effect<A, E, R>, |
| 58 | policy: Schedule.Schedule<A1, NoInfer<E>, E1, R1>, |
| 59 | orElse: (e: NoInfer<E>, out: A1) => Effect<A2, E2, R2> |
| 60 | ): Effect<A | A2, E1 | E2, R | R1 | R2> |
| 61 | } = dual(3, <A, E, R, A1, E1, R1, A2, E2, R2>( |
| 62 | self: Effect<A, E, R>, |
| 63 | policy: Schedule.Schedule<A1, NoInfer<E>, E1, R1>, |
| 64 | orElse: (e: NoInfer<E>, out: A1) => Effect<A2, E2, R2> |
| 65 | ): Effect<A | A2, E1 | E2, R | R1 | R2> => |
| 66 | effect.flatMap(Schedule.toStepWithMetadata(policy), (step) => { |
| 67 | let meta = Schedule.CurrentMetadata.defaultValue() |
| 68 | let lastError!: E |
| 69 | const loop: Effect<A, E1 | Cause.Done<A1>, R | R1> = effect.catch_( |
| 70 | effect.suspend(() => effect.provideService(self, Schedule.CurrentMetadata, meta)), |
| 71 | (error) => { |
| 72 | lastError = error |
| 73 | return effect.flatMap(step(error), (meta_) => { |
| 74 | meta = meta_ |
| 75 | return loop |
| 76 | }) |
| 77 | } |
| 78 | ) |
| 79 | return Pull.catchDone(loop, (out) => internalCall(() => orElse(lastError, out as A1))) |
| 80 | })) |
| 81 | |
| 82 | /** @internal */ |
| 83 | export const repeat = dual<{ |
| 84 | <O extends Repeat.Options<A>, A>( |
| 85 | options: O |
| 86 | ): <E, R>(self: Effect<A, E, R>) => Repeat.Return<R, E, A, O> |
| 87 | <Output, Input, Error, Env>( |
| 88 | schedule: Schedule.Schedule<Output, NoInfer<Input>, Error, Env> |
| 89 | ): <E, R>(self: Effect<Input, E, R>) => Effect<Output, E | Error, R | Env> |
| 90 | <Output, Input, Error, Env>( |
| 91 | builder: ( |
| 92 | $: <O, E, R>(_: Schedule.Schedule<O, NoInfer<Input>, E, R>) => Schedule.Schedule<O, Input, E, R> |
| 93 | ) => Schedule.Schedule<Output, NoInfer<Input>, Error, Env> |
| 94 | ): <E, R>(self: Effect<Input, E, R>) => Effect<Output, E | Error, R | Env> |
| 95 | }, { |
| 96 | <A, E, R, O extends Repeat.Options<A>>( |
| 97 | self: Effect<A, E, R>, |
| 98 | options: O |
| 99 | ): Repeat.Return<R, E, A, O> |
| 100 | <Input, E, R, Output, Error, Env>( |
| 101 | self: Effect<Input, E, R>, |
| 102 | schedule: Schedule.Schedule<Output, NoInfer<Input>, Error, Env> |
| 103 | ): Effect<Output, E | Error, R | Env> |
| 104 | <Input, E, R, Output, Error, Env>( |
| 105 | self: Effect<Input, E, R>, |
| 106 | builder: ( |
| 107 | $: <O, E, R>(_: Schedule.Schedule<O, NoInfer<Input>, E, R>) => Schedule.Schedule<O, Input, E, R> |
| 108 | ) => Schedule.Schedule<Output, NoInfer<Input>, Error, Env> |
| 109 | ): Effect<Output, E | Error, R | Env> |
| 110 | }>( |
| 111 | 2, |
| 112 | ( |
| 113 | self: Effect<any, any, any>, |
| 114 | options: |
| 115 | | Repeat.Options<any> |
| 116 | | Schedule.Schedule<any, any, any, any> |
| 117 | | (( |
| 118 | $: <O, E, R>(_: Schedule.Schedule<O, any, E, R>) => Schedule.Schedule<O, any, E, R> |
| 119 | ) => Schedule.Schedule<any, any, any, any>) |
| 120 | ) => { |
| 121 | const schedule = typeof options === "function" |
| 122 | ? options(identity) |
| 123 | : Schedule.isSchedule(options) |
| 124 | ? options |
| 125 | : buildFromOptions(options) |
| 126 | return repeatOrElse(self, schedule, effect.fail) |
| 127 | } |
| 128 | ) |
| 129 | |
| 130 | /** @internal */ |
| 131 | export const retry = dual<{ |
| 132 | <E, O extends Retry.Options<E>>( |
| 133 | options: O |
| 134 | ): <A, R>( |
| 135 | self: Effect<A, E, R> |
| 136 | ) => Retry.Return<R, E, A, O> |
| 137 | <B, E, Error, Env>( |
| 138 | policy: Schedule.Schedule<B, NoInfer<E>, Error, Env> |
| 139 | ): <A, R>(self: Effect<A, E, R>) => Effect<A, E | Error, R | Env> |
| 140 | <B, E, Error, Env>( |
| 141 | builder: ( |
| 142 | $: <O, SE, R>(_: Schedule.Schedule<O, NoInfer<E>, SE, R>) => Schedule.Schedule<O, E, SE, R> |
| 143 | ) => Schedule.Schedule<B, NoInfer<E>, Error, Env> |
| 144 | ): <A, R>(self: Effect<A, E, R>) => Effect<A, E | Error, R | Env> |
| 145 | }, { |
| 146 | <A, E, R, O extends Retry.Options<E>>( |
| 147 | self: Effect<A, E, R>, |
| 148 | options: O |
| 149 | ): Retry.Return<R, E, A, O> |
| 150 | <A, E, R, B, Error, Env>( |
| 151 | self: Effect<A, E, R>, |
| 152 | policy: Schedule.Schedule<B, NoInfer<E>, Error, Env> |
| 153 | ): Effect<A, E | Error, R | Env> |
| 154 | <A, E, R, B, Error, Env>( |
| 155 | self: Effect<A, E, R>, |
| 156 | builder: ( |
| 157 | $: <O, SE, R>(_: Schedule.Schedule<O, NoInfer<E>, SE, R>) => Schedule.Schedule<O, E, SE, R> |
| 158 | ) => Schedule.Schedule<B, NoInfer<E>, Error, Env> |
| 159 | ): Effect<A, E | Error, R | Env> |
| 160 | }>( |
| 161 | 2, |
| 162 | ( |
| 163 | self: Effect<any, any, any>, |
| 164 | options: |
| 165 | | Retry.Options<any> |
| 166 | | Schedule.Schedule<any, any, any, any> |
| 167 | | (( |
| 168 | $: <O, SE, R>(_: Schedule.Schedule<O, any, SE, R>) => Schedule.Schedule<O, any, SE, R> |
| 169 | ) => Schedule.Schedule<any, any, any, any>) |
| 170 | ) => { |
| 171 | const schedule = typeof options === "function" |
| 172 | ? options(identity) |
| 173 | : Schedule.isSchedule(options) |
| 174 | ? options |
| 175 | : buildFromOptions(options) |
| 176 | return retryOrElse(self, schedule, effect.fail) |
| 177 | } |
| 178 | ) |
| 179 | |
| 180 | /** @internal */ |
| 181 | export const scheduleFrom = dual< |
| 182 | <Input, Output, Error, Env>( |
| 183 | initial: Input, |
| 184 | schedule: Schedule.Schedule<Output, Input, Error, Env> |
| 185 | ) => <E, R>( |
| 186 | self: Effect<Input, E, R> |
| 187 | ) => Effect<Output, E | Error, R | Env>, |
| 188 | <Input, E, R, Output, Error, Env>( |
| 189 | self: Effect<Input, E, R>, |
| 190 | initial: Input, |
| 191 | schedule: Schedule.Schedule<Output, Input, Error, Env> |
| 192 | ) => Effect<Output, E | Error, R | Env> |
| 193 | >(3, <Input, E, R, Output, Error, Env>( |
| 194 | self: Effect<Input, E, R>, |
| 195 | initial: Input, |
| 196 | schedule: Schedule.Schedule<Output, Input, Error, Env> |
| 197 | ): Effect<Output, E | Error, R | Env> => |
| 198 | effect.flatMap(Schedule.toStepWithMetadata(schedule), (step) => { |
| 199 | let meta = Schedule.CurrentMetadata.defaultValue() |
| 200 | const selfWithMeta = effect.suspend(() => effect.provideService(self, Schedule.CurrentMetadata, meta)) |
| 201 | return effect.catch_( |
| 202 | effect.flatMap( |
| 203 | step(initial), |
| 204 | (meta_) => { |
| 205 | meta = meta_ |
| 206 | const body = constant(effect.flatMap(selfWithMeta, step)) |
| 207 | return effect.whileLoop({ |
| 208 | while: constTrue, |
| 209 | body, |
| 210 | step(meta_) { |
| 211 | meta = meta_ |
| 212 | } |
| 213 | }) as Effect<never, E, R | Env> |
| 214 | } |
| 215 | ), |
| 216 | (error) => core.isDone(error) ? effect.succeed(error.value as Output) : effect.fail(error as E | Error) |
| 217 | ) |
| 218 | })) |
| 219 | |
| 220 | const passthroughForever = Schedule.passthrough(Schedule.forever) |
| 221 | |
| 222 | /** @internal */ |
| 223 | export const buildFromOptions = <Input>(options: { |
| 224 | schedule?: Schedule.Schedule<any, Input, any, any> | undefined |
| 225 | while?: ((input: Input) => boolean | Effect<boolean, any, any>) | undefined |
| 226 | until?: ((input: Input) => boolean | Effect<boolean, any, any>) | undefined |
| 227 | times?: number | undefined |
| 228 | }) => { |
| 229 | let schedule: Schedule.Schedule<any, Input, any, any> = options.schedule |
| 230 | ? Schedule.passthrough(options.schedule) |
| 231 | : passthroughForever |
| 232 | if (options.while) { |
| 233 | schedule = Schedule.while(schedule, ({ input }) => { |
| 234 | const applied = options.while!(input) |
| 235 | return core.isEffect(applied) ? applied : effect.succeed(applied) |
| 236 | }) |
| 237 | } |
| 238 | if (options.until) { |
| 239 | schedule = Schedule.while(schedule, ({ input }) => { |
| 240 | const applied = options.until!(input) |
| 241 | return core.isEffect(applied) ? effect.map(applied, (b) => !b) : effect.succeed(!applied) |
| 242 | }) |
| 243 | } |
| 244 | if (options.times !== undefined) { |
| 245 | schedule = Schedule.while(schedule, ({ attempt }) => effect.succeed(attempt <= options.times!)) |
| 246 | } |
| 247 | return schedule |
| 248 | } |