@effect/platform 0.69.26 → 0.69.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/cjs/HttpApiBuilder.js +35 -17
  2. package/dist/cjs/HttpApiBuilder.js.map +1 -1
  3. package/dist/cjs/HttpApiClient.js +2 -3
  4. package/dist/cjs/HttpApiClient.js.map +1 -1
  5. package/dist/cjs/HttpApiEndpoint.js.map +1 -1
  6. package/dist/cjs/HttpClient.js +4 -1
  7. package/dist/cjs/HttpClient.js.map +1 -1
  8. package/dist/cjs/OpenApi.js +22 -5
  9. package/dist/cjs/OpenApi.js.map +1 -1
  10. package/dist/cjs/PlatformLogger.js +2 -0
  11. package/dist/cjs/PlatformLogger.js.map +1 -1
  12. package/dist/cjs/internal/httpClient.js +4 -1
  13. package/dist/cjs/internal/httpClient.js.map +1 -1
  14. package/dist/dts/HttpApiBuilder.d.ts +11 -5
  15. package/dist/dts/HttpApiBuilder.d.ts.map +1 -1
  16. package/dist/dts/HttpApiClient.d.ts.map +1 -1
  17. package/dist/dts/HttpApiEndpoint.d.ts +3 -1
  18. package/dist/dts/HttpApiEndpoint.d.ts.map +1 -1
  19. package/dist/dts/HttpClient.d.ts +15 -3
  20. package/dist/dts/HttpClient.d.ts.map +1 -1
  21. package/dist/dts/OpenApi.d.ts.map +1 -1
  22. package/dist/dts/PlatformLogger.d.ts +6 -0
  23. package/dist/dts/PlatformLogger.d.ts.map +1 -1
  24. package/dist/dts/internal/httpClient.d.ts.map +1 -1
  25. package/dist/esm/HttpApiBuilder.js +35 -17
  26. package/dist/esm/HttpApiBuilder.js.map +1 -1
  27. package/dist/esm/HttpApiClient.js +2 -3
  28. package/dist/esm/HttpApiClient.js.map +1 -1
  29. package/dist/esm/HttpApiEndpoint.js.map +1 -1
  30. package/dist/esm/HttpClient.js +4 -1
  31. package/dist/esm/HttpClient.js.map +1 -1
  32. package/dist/esm/OpenApi.js +22 -5
  33. package/dist/esm/OpenApi.js.map +1 -1
  34. package/dist/esm/PlatformLogger.js +2 -0
  35. package/dist/esm/PlatformLogger.js.map +1 -1
  36. package/dist/esm/internal/httpClient.js +4 -1
  37. package/dist/esm/internal/httpClient.js.map +1 -1
  38. package/package.json +2 -2
  39. package/src/HttpApiBuilder.ts +57 -40
  40. package/src/HttpApiClient.ts +1 -6
  41. package/src/HttpApiEndpoint.ts +2 -1
  42. package/src/HttpClient.ts +23 -9
  43. package/src/OpenApi.ts +23 -7
  44. package/src/PlatformLogger.ts +6 -0
  45. package/src/internal/httpClient.ts +16 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect/platform",
3
- "version": "0.69.26",
3
+ "version": "0.69.28",
4
4
  "description": "Unified interfaces for common platform-specific services",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -14,7 +14,7 @@
14
14
  "multipasta": "^0.2.5"
15
15
  },
16
16
  "peerDependencies": {
17
- "effect": "^3.10.17"
17
+ "effect": "^3.10.19"
18
18
  },
19
19
  "publishConfig": {
20
20
  "provenance": true
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @since 1.0.0
3
3
  */
4
+ import * as Cause from "effect/Cause"
4
5
  import * as Chunk from "effect/Chunk"
5
6
  import * as Context from "effect/Context"
6
7
  import * as Effect from "effect/Effect"
@@ -88,7 +89,7 @@ export const serve = <R = never>(
88
89
  httpApp.pipe(
89
90
  Effect.map((app) => HttpServer.serve(app as any, middleware!)),
90
91
  Layer.unwrapEffect,
91
- Layer.provide(Router.Live)
92
+ Layer.provide([Router.Live, Middleware.layer])
92
93
  )
93
94
 
94
95
  /**
@@ -100,23 +101,24 @@ export const serve = <R = never>(
100
101
  export const httpApp: Effect.Effect<
101
102
  HttpApp.Default<never, HttpRouter.HttpRouter.DefaultServices>,
102
103
  never,
103
- Router | HttpApi.Api
104
+ Router | HttpApi.Api | Middleware
104
105
  > = Effect.gen(function*() {
105
106
  const { api, context } = yield* HttpApi.Api
106
107
  const middleware = makeMiddlewareMap(api.middlewares, context)
107
108
  const router = applyMiddleware(middleware, yield* Router.router)
108
- const apiMiddleware = yield* Effect.serviceOption(Middleware)
109
+ const apiMiddlewareService = yield* Middleware
110
+ const apiMiddleware = yield* apiMiddlewareService.retrieve
109
111
  const errorSchema = makeErrorSchema(api as any)
110
112
  const encodeError = Schema.encodeUnknown(errorSchema)
111
113
  return router.pipe(
112
- apiMiddleware._tag === "Some" ? apiMiddleware.value : identity,
113
- Effect.catchAll((error) =>
114
- Effect.matchEffect(Effect.provide(encodeError(error), context), {
115
- onFailure: () => Effect.die(error),
114
+ apiMiddleware,
115
+ Effect.catchAllCause((cause) =>
116
+ Effect.matchEffect(Effect.provide(encodeError(Cause.squash(cause)), context), {
117
+ onFailure: () => Effect.failCause(cause),
116
118
  onSuccess: Effect.succeed
117
119
  })
118
120
  )
119
- )
121
+ ) as any
120
122
  })
121
123
 
122
124
  /**
@@ -125,6 +127,7 @@ export const httpApp: Effect.Effect<
125
127
  * @since 1.0.0
126
128
  * @category constructors
127
129
  * @example
130
+ * ```ts
128
131
  * import { HttpApi, HttpApiBuilder, HttpServer } from "@effect/platform"
129
132
  * import { Layer } from "effect"
130
133
  *
@@ -140,6 +143,7 @@ export const httpApp: Effect.Effect<
140
143
  * HttpServer.layerContext
141
144
  * )
142
145
  * )
146
+ * ```
143
147
  */
144
148
  export const toWebHandler = <LA, LE>(
145
149
  layer: Layer.Layer<LA | HttpApi.Api | HttpRouter.HttpRouter.DefaultServices, LE>,
@@ -157,7 +161,7 @@ export const toWebHandler = <LA, LE>(
157
161
  readonly dispose: () => Promise<void>
158
162
  } => {
159
163
  const runtime = ManagedRuntime.make(
160
- Layer.merge(layer, Router.Live),
164
+ Layer.mergeAll(layer, Router.Live, Middleware.layer),
161
165
  options?.memoMap
162
166
  )
163
167
  let handlerCached: ((request: Request) => Promise<Response>) | undefined
@@ -505,8 +509,7 @@ export const handler = <
505
509
 
506
510
  const requestPayload = (
507
511
  request: HttpServerRequest.HttpServerRequest,
508
- urlParams: ReadonlyRecord<string, string | Array<string>>,
509
- isMultipart: boolean
512
+ urlParams: ReadonlyRecord<string, string | Array<string>>
510
513
  ): Effect.Effect<
511
514
  unknown,
512
515
  never,
@@ -515,7 +518,7 @@ const requestPayload = (
515
518
  | Scope
516
519
  > =>
517
520
  HttpMethod.hasBody(request.method)
518
- ? isMultipart
521
+ ? request.headers["content-type"].includes("multipart/form-data")
519
522
  ? Effect.orDie(request.multipart)
520
523
  : Effect.orDie(request.json)
521
524
  : Effect.succeed(urlParams)
@@ -551,10 +554,6 @@ const handlerToRoute = (
551
554
  ): HttpRouter.Route<any, any> => {
552
555
  const endpoint = endpoint_ as HttpApiEndpoint.HttpApiEndpoint.AnyWithProps
553
556
  const decodePath = Option.map(endpoint.pathSchema, Schema.decodeUnknown)
554
- const isMultipart = endpoint.payloadSchema.pipe(
555
- Option.map((schema) => HttpApiSchema.getMultipart(schema.ast)),
556
- Option.getOrElse(() => false)
557
- )
558
557
  const decodePayload = Option.map(endpoint.payloadSchema, Schema.decodeUnknown)
559
558
  const decodeHeaders = Option.map(endpoint.headersSchema, Schema.decodeUnknown)
560
559
  const decodeUrlParams = Option.map(endpoint.urlParamsSchema, Schema.decodeUnknown)
@@ -576,7 +575,7 @@ const handlerToRoute = (
576
575
  }
577
576
  if (decodePayload._tag === "Some") {
578
577
  request.payload = yield* Effect.flatMap(
579
- requestPayload(httpRequest, urlParams, isMultipart),
578
+ requestPayload(httpRequest, urlParams),
580
579
  decodePayload.value
581
580
  )
582
581
  }
@@ -745,8 +744,26 @@ const toResponseError = toResponseSchema(HttpApiSchema.getStatusErrorAST)
745
744
  */
746
745
  export class Middleware extends Context.Tag("@effect/platform/HttpApiBuilder/Middleware")<
747
746
  Middleware,
748
- HttpMiddleware.HttpMiddleware
749
- >() {}
747
+ {
748
+ readonly add: (middleware: HttpMiddleware.HttpMiddleware) => Effect.Effect<void>
749
+ readonly retrieve: Effect.Effect<HttpMiddleware.HttpMiddleware>
750
+ }
751
+ >() {
752
+ /**
753
+ * @since 1.0.0
754
+ */
755
+ static readonly layer = Layer.sync(Middleware, () => {
756
+ let middleware: HttpMiddleware.HttpMiddleware = identity
757
+ return Middleware.of({
758
+ add: (f) =>
759
+ Effect.sync(() => {
760
+ const prev = middleware
761
+ middleware = (app) => f(prev(app))
762
+ }),
763
+ retrieve: Effect.sync(() => middleware)
764
+ })
765
+ })
766
+ }
750
767
 
751
768
  /**
752
769
  * @since 1.0.0
@@ -756,26 +773,24 @@ export type MiddlewareFn<Error, R = HttpRouter.HttpRouter.Provided> = (
756
773
  httpApp: HttpApp.Default
757
774
  ) => HttpApp.Default<Error, R>
758
775
 
759
- const middlewareAdd = (middleware: HttpMiddleware.HttpMiddleware): Effect.Effect<HttpMiddleware.HttpMiddleware> =>
760
- Effect.map(
761
- Effect.context<never>(),
762
- (context) => {
763
- const current = Context.getOption(context, Middleware)
764
- const withContext: HttpMiddleware.HttpMiddleware = (httpApp) =>
765
- Effect.mapInputContext(middleware(httpApp), (input) => Context.merge(context, input))
766
- return current._tag === "None" ? withContext : (httpApp) => withContext(current.value(httpApp))
767
- }
768
- )
776
+ const middlewareAdd = (
777
+ middleware: HttpMiddleware.HttpMiddleware
778
+ ): Effect.Effect<void, never, Middleware> =>
779
+ Effect.gen(function*() {
780
+ const context = yield* Effect.context<never>()
781
+ const service = yield* Middleware
782
+ yield* service.add((httpApp) =>
783
+ Effect.mapInputContext(middleware(httpApp), (input) => Context.merge(context, input))
784
+ )
785
+ })
769
786
 
770
787
  const middlewareAddNoContext = (
771
788
  middleware: HttpMiddleware.HttpMiddleware
772
- ): Effect.Effect<HttpMiddleware.HttpMiddleware> =>
773
- Effect.map(
774
- Effect.serviceOption(Middleware),
775
- (current): HttpMiddleware.HttpMiddleware => {
776
- return current._tag === "None" ? middleware : (httpApp) => middleware(current.value(httpApp))
777
- }
778
- )
789
+ ): Effect.Effect<void, never, Middleware> =>
790
+ Effect.gen(function*() {
791
+ const service = yield* Middleware
792
+ yield* service.add(middleware)
793
+ })
779
794
 
780
795
  /**
781
796
  * Create an `HttpApi` level middleware `Layer`.
@@ -852,9 +867,9 @@ export const middleware: {
852
867
  const withContext = apiFirst ? args[2]?.withContext === true : (args as any)[1]?.withContext === true
853
868
  const add = withContext ? middlewareAdd : middlewareAddNoContext
854
869
  const middleware = apiFirst ? args[1] : args[0]
855
- return Effect.isEffect(middleware)
856
- ? Layer.effect(Middleware, Effect.flatMap(middleware as any, add))
857
- : Layer.effect(Middleware, add(middleware as any))
870
+ return (Effect.isEffect(middleware)
871
+ ? Layer.effectDiscard(Effect.flatMap(middleware as any, add))
872
+ : Layer.effectDiscard(add(middleware as any))).pipe(Layer.provide(Middleware.layer))
858
873
  }
859
874
 
860
875
  /**
@@ -937,7 +952,9 @@ export const middlewareScoped: {
937
952
  const withContext = apiFirst ? args[2]?.withContext === true : (args as any)[1]?.withContext === true
938
953
  const add = withContext ? middlewareAdd : middlewareAddNoContext
939
954
  const middleware = apiFirst ? args[1] : args[0]
940
- return Layer.scoped(Middleware, Effect.flatMap(middleware as any, add))
955
+ return Layer.scopedDiscard(Effect.flatMap(middleware as any, add)).pipe(
956
+ Layer.provide(Middleware.layer)
957
+ )
941
958
  }
942
959
 
943
960
  /**
@@ -160,12 +160,7 @@ const makeClient = <Groups extends HttpApiGroup.Any, ApiError, ApiR>(
160
160
  successes.forEach((ast, status) => {
161
161
  decodeMap[status] = ast._tag === "None" ? responseAsVoid : schemaToResponse(ast.value)
162
162
  })
163
- const isMultipart = endpoint.payloadSchema.pipe(
164
- Option.map((schema) => HttpApiSchema.getMultipart(schema.ast)),
165
- Option.getOrElse(() => false)
166
- )
167
163
  const encodePayload = endpoint.payloadSchema.pipe(
168
- Option.filter(() => !isMultipart),
169
164
  Option.map(Schema.encodeUnknown)
170
165
  )
171
166
  const encodeHeaders = endpoint.headersSchema.pipe(
@@ -185,7 +180,7 @@ const makeClient = <Groups extends HttpApiGroup.Any, ApiError, ApiR>(
185
180
  let httpRequest = HttpClientRequest.make(endpoint.method)(
186
181
  request && request.path ? makeUrl(request.path) : endpoint.path
187
182
  )
188
- if (isMultipart) {
183
+ if (request.payload instanceof FormData) {
189
184
  httpRequest = HttpClientRequest.bodyFormData(httpRequest, request.payload)
190
185
  } else if (encodePayload._tag === "Some") {
191
186
  const payload = yield* encodePayload.value(request.payload)
@@ -406,7 +406,8 @@ export declare namespace HttpApiEndpoint {
406
406
  & ([UrlParams] extends [never] ? {} : { readonly urlParams: UrlParams })
407
407
  & ([Headers] extends [never] ? {} : { readonly headers: Headers })
408
408
  & ([Payload] extends [never] ? {}
409
- : [Payload] extends [Brand<HttpApiSchema.MultipartTypeId>] ? { readonly payload: FormData }
409
+ : Payload extends infer P ?
410
+ P extends Brand<HttpApiSchema.MultipartTypeId> ? { readonly payload: FormData } : { readonly payload: P }
410
411
  : { readonly payload: Payload })
411
412
  ) extends infer Req ? keyof Req extends never ? (void | { readonly withResponse?: WithResponse }) :
412
413
  Req & { readonly withResponse?: WithResponse } :
package/src/HttpClient.ts CHANGED
@@ -12,6 +12,7 @@ import type * as Predicate from "effect/Predicate"
12
12
  import type { Ref } from "effect/Ref"
13
13
  import type * as Schedule from "effect/Schedule"
14
14
  import type * as Scope from "effect/Scope"
15
+ import type { NoInfer } from "effect/Types"
15
16
  import type { Cookies } from "./Cookies.js"
16
17
  import type * as Error from "./HttpClientError.js"
17
18
  import type * as ClientRequest from "./HttpClientRequest.js"
@@ -636,34 +637,47 @@ export const retry: {
636
637
  } = internal.retry
637
638
 
638
639
  /**
639
- * Retries common transient errors, such as rate limiting or network issues.
640
+ * Retries common transient errors, such as rate limiting, timeouts or network issues.
641
+ *
642
+ * Specifying a `while` predicate allows you to consider other errors as
643
+ * transient.
640
644
  *
641
645
  * @since 1.0.0
642
646
  * @category error handling
643
647
  */
644
648
  export const retryTransient: {
645
649
  /**
646
- * Retries common transient errors, such as rate limiting or network issues.
650
+ * Retries common transient errors, such as rate limiting, timeouts or network issues.
651
+ *
652
+ * Specifying a `while` predicate allows you to consider other errors as
653
+ * transient.
647
654
  *
648
655
  * @since 1.0.0
649
656
  * @category error handling
650
657
  */
651
658
  <B, E, R1 = never>(
652
- options:
653
- | { readonly schedule?: Schedule.Schedule<B, NoInfer<E>, R1>; readonly times?: number }
654
- | Schedule.Schedule<B, NoInfer<E>, R1>
659
+ options: {
660
+ readonly while?: Predicate.Predicate<NoInfer<E>>
661
+ readonly schedule?: Schedule.Schedule<B, NoInfer<E>, R1>
662
+ readonly times?: number
663
+ } | Schedule.Schedule<B, NoInfer<E>, R1>
655
664
  ): <R>(self: HttpClient<E, R>) => HttpClient<E, R1 | R>
656
665
  /**
657
- * Retries common transient errors, such as rate limiting or network issues.
666
+ * Retries common transient errors, such as rate limiting, timeouts or network issues.
667
+ *
668
+ * Specifying a `while` predicate allows you to consider other errors as
669
+ * transient.
658
670
  *
659
671
  * @since 1.0.0
660
672
  * @category error handling
661
673
  */
662
674
  <E, R, B, R1 = never>(
663
675
  self: HttpClient<E, R>,
664
- options:
665
- | { readonly schedule?: Schedule.Schedule<B, NoInfer<E>, R1>; readonly times?: number }
666
- | Schedule.Schedule<B, NoInfer<E>, R1>
676
+ options: {
677
+ readonly while?: Predicate.Predicate<NoInfer<E>>
678
+ readonly schedule?: Schedule.Schedule<B, NoInfer<E>, R1>
679
+ readonly times?: number
680
+ } | Schedule.Schedule<B, NoInfer<E>, R1>
667
681
  ): HttpClient<E, R1 | R>
668
682
  } = internal.retryTransient
669
683
 
package/src/OpenApi.ts CHANGED
@@ -269,14 +269,30 @@ export const fromApi = <A extends HttpApi.HttpApi.Any>(self: A): OpenAPISpec =>
269
269
  endpoint.payloadSchema.pipe(
270
270
  Option.filter(() => HttpMethod.hasBody(endpoint.method)),
271
271
  Option.map((schema) => {
272
- op.requestBody = {
273
- content: {
274
- [HttpApiSchema.getMultipart(schema.ast) ? "multipart/form-data" : "application/json"]: {
275
- schema: makeJsonSchemaOrRef(schema)
276
- }
277
- },
278
- required: true
272
+ const content: Mutable<OpenApiSpecContent> = {}
273
+ const members = schema.ast._tag === "Union" ? schema.ast.types : [schema.ast]
274
+ const jsonTypes: Array<AST.AST> = []
275
+ const multipartTypes: Array<AST.AST> = []
276
+
277
+ for (const member of members) {
278
+ if (HttpApiSchema.getMultipart(member)) {
279
+ multipartTypes.push(member)
280
+ } else {
281
+ jsonTypes.push(member)
282
+ }
283
+ }
284
+
285
+ if (jsonTypes.length > 0) {
286
+ content["application/json"] = {
287
+ schema: makeJsonSchemaOrRef(Schema.make(AST.Union.make(jsonTypes)))
288
+ }
289
+ }
290
+ if (multipartTypes.length > 0) {
291
+ content["multipart/form-data"] = {
292
+ schema: makeJsonSchemaOrRef(Schema.make(AST.Union.make(multipartTypes)))
293
+ }
279
294
  }
295
+ op.requestBody = { content, required: true }
280
296
  })
281
297
  )
282
298
  for (const [status, ast] of successes) {
@@ -14,6 +14,7 @@ import * as internal from "./internal/platformLogger.js"
14
14
  *
15
15
  * @since 1.0.0
16
16
  * @example
17
+ * ```ts
17
18
  * import { PlatformLogger } from "@effect/platform"
18
19
  * import { NodeFileSystem, NodeRuntime } from "@effect/platform-node"
19
20
  * import { Effect, Layer, Logger } from "effect"
@@ -31,6 +32,7 @@ import * as internal from "./internal/platformLogger.js"
31
32
  * Effect.provide(LoggerLive),
32
33
  * NodeRuntime.runMain
33
34
  * )
35
+ * ```
34
36
  */
35
37
  export const toFile: {
36
38
  /**
@@ -38,6 +40,7 @@ export const toFile: {
38
40
  *
39
41
  * @since 1.0.0
40
42
  * @example
43
+ * ```ts
41
44
  * import { PlatformLogger } from "@effect/platform"
42
45
  * import { NodeFileSystem, NodeRuntime } from "@effect/platform-node"
43
46
  * import { Effect, Layer, Logger } from "effect"
@@ -55,6 +58,7 @@ export const toFile: {
55
58
  * Effect.provide(LoggerLive),
56
59
  * NodeRuntime.runMain
57
60
  * )
61
+ * ```
58
62
  */
59
63
  (
60
64
  path: string,
@@ -73,6 +77,7 @@ export const toFile: {
73
77
  *
74
78
  * @since 1.0.0
75
79
  * @example
80
+ * ```ts
76
81
  * import { PlatformLogger } from "@effect/platform"
77
82
  * import { NodeFileSystem, NodeRuntime } from "@effect/platform-node"
78
83
  * import { Effect, Layer, Logger } from "effect"
@@ -90,6 +95,7 @@ export const toFile: {
90
95
  * Effect.provide(LoggerLive),
91
96
  * NodeRuntime.runMain
92
97
  * )
98
+ * ```
93
99
  */
94
100
  <Message>(
95
101
  self: Logger.Logger<Message, string>,
@@ -1,3 +1,4 @@
1
+ import * as Cause from "effect/Cause"
1
2
  import * as Context from "effect/Context"
2
3
  import * as Effect from "effect/Effect"
3
4
  import type * as Fiber from "effect/Fiber"
@@ -11,6 +12,7 @@ import * as Predicate from "effect/Predicate"
11
12
  import * as Ref from "effect/Ref"
12
13
  import * as Schedule from "effect/Schedule"
13
14
  import * as Scope from "effect/Scope"
15
+ import type { NoInfer } from "effect/Types"
14
16
  import * as Cookies from "../Cookies.js"
15
17
  import * as Headers from "../Headers.js"
16
18
  import type * as Client from "../HttpClient.js"
@@ -565,6 +567,7 @@ export const retry: {
565
567
  export const retryTransient: {
566
568
  <B, E, R1 = never>(
567
569
  options: {
570
+ readonly while?: Predicate.Predicate<NoInfer<E>>
568
571
  readonly schedule?: Schedule.Schedule<B, NoInfer<E>, R1>
569
572
  readonly times?: number
570
573
  } | Schedule.Schedule<B, NoInfer<E>, R1>
@@ -572,6 +575,7 @@ export const retryTransient: {
572
575
  <E, R, B, R1 = never>(
573
576
  self: Client.HttpClient<E, R>,
574
577
  options: {
578
+ readonly while?: Predicate.Predicate<NoInfer<E>>
575
579
  readonly schedule?: Schedule.Schedule<B, NoInfer<E>, R1>
576
580
  readonly times?: number
577
581
  } | Schedule.Schedule<B, NoInfer<E>, R1>
@@ -581,6 +585,7 @@ export const retryTransient: {
581
585
  <E extends E0, E0, R, B, R1 = never>(
582
586
  self: Client.HttpClient<E, R>,
583
587
  options: {
588
+ readonly while?: Predicate.Predicate<NoInfer<E>>
584
589
  readonly schedule?: Schedule.Schedule<B, NoInfer<E>, R1>
585
590
  readonly times?: number
586
591
  } | Schedule.Schedule<B, NoInfer<E>, R1>
@@ -588,16 +593,23 @@ export const retryTransient: {
588
593
  transformResponse(
589
594
  self,
590
595
  Effect.retry({
591
- while: (error) =>
592
- Error.isHttpClientError(error) &&
593
- ((error._tag === "RequestError" && error.reason === "Transport") ||
594
- (error._tag === "ResponseError" && error.response.status >= 429)),
596
+ while: Schedule.ScheduleTypeId in options || options.while === undefined
597
+ ? isTransientError
598
+ : Predicate.or(isTransientError, options.while),
595
599
  schedule: Schedule.ScheduleTypeId in options ? options : options.schedule,
596
600
  times: Schedule.ScheduleTypeId in options ? undefined : options.times
597
601
  })
598
602
  )
599
603
  )
600
604
 
605
+ const isTransientError = (error: unknown) =>
606
+ Predicate.hasProperty(error, Cause.TimeoutExceptionTypeId) || isTransientHttpError(error)
607
+
608
+ const isTransientHttpError = (error: unknown) =>
609
+ Error.isHttpClientError(error) &&
610
+ ((error._tag === "RequestError" && error.reason === "Transport") ||
611
+ (error._tag === "ResponseError" && error.response.status >= 429))
612
+
601
613
  /** @internal */
602
614
  export const tap = dual<
603
615
  <_, E2, R2>(