1import type * as Cause from "../Cause.ts"
2import type { Effect, Repeat, Retry } from "../Effect.ts"
3import { constant, constTrue, dual, identity } from "../Function.ts"
4import * as Option from "../Option.ts"
5import * as Pull from "../Pull.ts"
6import * as Schedule from "../Schedule.ts"
7import type { NoInfer } from "../Types.ts"
8import { internalCall } from "../Utils.ts"
9import * as core from "./core.ts"
10import * as effect from "./effect.ts"
11
12/** @internal */
13export 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 */
51export 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 */
83export 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 */
131export 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 */
181export 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
220const passthroughForever = Schedule.passthrough(Schedule.forever)
221
222/** @internal */
223export 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}