Skip to content

Commit ff7053f

Browse files
authored
support non-errors in HttpClient.retryTransient (#5917)
1 parent 77eeb86 commit ff7053f

File tree

3 files changed

+91
-30
lines changed

3 files changed

+91
-30
lines changed

.changeset/grumpy-plums-grow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@effect/platform": minor
3+
---
4+
5+
support non-errors in HttpClient.retryTransient

packages/platform/src/HttpClient.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -523,20 +523,39 @@ export const retry: {
523523
* @category error handling
524524
*/
525525
export const retryTransient: {
526-
<B, E, R1 = never>(
526+
<
527+
B,
528+
E,
529+
R1 = never,
530+
const Mode extends "errors-only" | "response-only" | "both" = never,
531+
Input = "errors-only" extends Mode ? E
532+
: "response-only" extends Mode ? ClientResponse.HttpClientResponse
533+
: ClientResponse.HttpClientResponse | E
534+
>(
527535
options: {
528-
readonly while?: Predicate.Predicate<NoInfer<E>>
529-
readonly schedule?: Schedule.Schedule<B, NoInfer<E>, R1>
536+
readonly mode?: Mode | undefined
537+
readonly while?: Predicate.Predicate<NoInfer<Input>>
538+
readonly schedule?: Schedule.Schedule<B, NoInfer<Input>, R1>
530539
readonly times?: number
531-
} | Schedule.Schedule<B, NoInfer<E>, R1>
540+
} | Schedule.Schedule<B, NoInfer<Input>, R1>
532541
): <R>(self: HttpClient.With<E, R>) => HttpClient.With<E, R1 | R>
533-
<E, R, B, R1 = never>(
542+
<
543+
E,
544+
R,
545+
B,
546+
R1 = never,
547+
const Mode extends "errors-only" | "response-only" | "both" = never,
548+
Input = "errors-only" extends Mode ? E
549+
: "response-only" extends Mode ? ClientResponse.HttpClientResponse
550+
: ClientResponse.HttpClientResponse | E
551+
>(
534552
self: HttpClient.With<E, R>,
535553
options: {
536-
readonly while?: Predicate.Predicate<NoInfer<E>>
537-
readonly schedule?: Schedule.Schedule<B, NoInfer<E>, R1>
554+
readonly mode?: Mode | undefined
555+
readonly while?: Predicate.Predicate<NoInfer<Input>>
556+
readonly schedule?: Schedule.Schedule<B, NoInfer<Input>, R1>
538557
readonly times?: number
539-
} | Schedule.Schedule<B, NoInfer<E>, R1>
558+
} | Schedule.Schedule<B, NoInfer<Input>, R1>
540559
): HttpClient.With<E, R1 | R>
541560
} = internal.retryTransient
542561

packages/platform/src/internal/httpClient.ts

Lines changed: 59 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as Context from "effect/Context"
33
import * as Effect from "effect/Effect"
44
import type * as Fiber from "effect/Fiber"
55
import * as FiberRef from "effect/FiberRef"
6-
import { constFalse, dual } from "effect/Function"
6+
import { constFalse, dual, flow, identity } from "effect/Function"
77
import { globalValue } from "effect/GlobalValue"
88
import * as Inspectable from "effect/Inspectable"
99
import * as Layer from "effect/Layer"
@@ -744,41 +744,76 @@ export const retry: {
744744

745745
/** @internal */
746746
export const retryTransient: {
747-
<B, E, R1 = never>(
747+
<
748+
B,
749+
E,
750+
R1 = never,
751+
const Mode extends "errors-only" | "response-only" | "both" = never,
752+
Input = "errors-only" extends Mode ? E
753+
: "response-only" extends Mode ? ClientResponse.HttpClientResponse
754+
: ClientResponse.HttpClientResponse | E
755+
>(
748756
options: {
749-
readonly while?: Predicate.Predicate<NoInfer<E>>
750-
readonly schedule?: Schedule.Schedule<B, NoInfer<E>, R1>
757+
readonly mode?: Mode | undefined
758+
readonly while?: Predicate.Predicate<NoInfer<Input>>
759+
readonly schedule?: Schedule.Schedule<B, NoInfer<Input>, R1>
751760
readonly times?: number
752-
} | Schedule.Schedule<B, NoInfer<E>, R1>
761+
} | Schedule.Schedule<B, NoInfer<Input>, R1>
753762
): <R>(self: Client.HttpClient.With<E, R>) => Client.HttpClient.With<E, R1 | R>
754-
<E, R, B, R1 = never>(
763+
<
764+
E,
765+
R,
766+
B,
767+
R1 = never,
768+
const Mode extends "errors-only" | "response-only" | "both" = never,
769+
Input = "errors-only" extends Mode ? E
770+
: "response-only" extends Mode ? ClientResponse.HttpClientResponse
771+
: ClientResponse.HttpClientResponse | E
772+
>(
755773
self: Client.HttpClient.With<E, R>,
756774
options: {
757-
readonly while?: Predicate.Predicate<NoInfer<E>>
758-
readonly schedule?: Schedule.Schedule<B, NoInfer<E>, R1>
775+
readonly mode?: Mode | undefined
776+
readonly while?: Predicate.Predicate<NoInfer<Input>>
777+
readonly schedule?: Schedule.Schedule<B, NoInfer<Input>, R1>
759778
readonly times?: number
760-
} | Schedule.Schedule<B, NoInfer<E>, R1>
779+
} | Schedule.Schedule<B, NoInfer<Input>, R1>
761780
): Client.HttpClient.With<E, R1 | R>
762781
} = dual(
763782
2,
764783
<E extends E0, E0, R, B, R1 = never>(
765784
self: Client.HttpClient.With<E, R>,
766785
options: {
767-
readonly while?: Predicate.Predicate<NoInfer<E>>
768-
readonly schedule?: Schedule.Schedule<B, NoInfer<E>, R1>
786+
readonly mode?: "errors-only" | "response-only" | "both" | undefined
787+
readonly while?: Predicate.Predicate<ClientResponse.HttpClientResponse | NoInfer<E>>
788+
readonly schedule?: Schedule.Schedule<B, ClientResponse.HttpClientResponse | NoInfer<E>, R1>
769789
readonly times?: number
770-
} | Schedule.Schedule<B, NoInfer<E>, R1>
771-
): Client.HttpClient.With<E, R | R1> =>
772-
transformResponse(
790+
} | Schedule.Schedule<B, ClientResponse.HttpClientResponse | NoInfer<E>, R1>
791+
): Client.HttpClient.With<E, R | R1> => {
792+
const isOnlySchedule = Schedule.ScheduleTypeId in options
793+
const mode = isOnlySchedule ? "both" : options.mode ?? "both"
794+
const schedule = isOnlySchedule ? options : options.schedule
795+
const passthroughSchedule = schedule && Schedule.passthrough(schedule)
796+
const times = isOnlySchedule ? undefined : options.times
797+
return transformResponse(
773798
self,
774-
Effect.retry({
775-
while: Schedule.ScheduleTypeId in options || options.while === undefined
776-
? isTransientError
777-
: Predicate.or(isTransientError, options.while),
778-
schedule: Schedule.ScheduleTypeId in options ? options : options.schedule,
779-
times: Schedule.ScheduleTypeId in options ? undefined : options.times
780-
})
799+
flow(
800+
mode === "errors-only" ? identity : Effect.repeat({
801+
schedule: passthroughSchedule,
802+
times,
803+
while: isOnlySchedule || options.while === undefined
804+
? isTransientResponse
805+
: Predicate.and(isTransientResponse, options.while)
806+
}),
807+
mode === "response-only" ? identity : Effect.retry({
808+
while: isOnlySchedule || options.while === undefined
809+
? isTransientError
810+
: Predicate.or(isTransientError, options.while),
811+
schedule,
812+
times
813+
})
814+
)
781815
)
816+
}
782817
)
783818

784819
const isTransientError = (error: unknown) =>
@@ -787,7 +822,9 @@ const isTransientError = (error: unknown) =>
787822
const isTransientHttpError = (error: unknown) =>
788823
Error.isHttpClientError(error) &&
789824
((error._tag === "RequestError" && error.reason === "Transport") ||
790-
(error._tag === "ResponseError" && error.response.status >= 429))
825+
(error._tag === "ResponseError" && isTransientResponse(error.response)))
826+
827+
const isTransientResponse = (response: ClientResponse.HttpClientResponse) => response.status >= 429
791828

792829
/** @internal */
793830
export const tap = dual<

0 commit comments

Comments
 (0)