1/**
2 * Fetch-based implementation of the Effect HTTP client service.
3 *
4 * This module provides an `HttpClient` layer that executes requests through a
5 * Web Fetch API implementation. It is the transport to use in browsers, edge
6 * runtimes, and Node.js environments where `globalThis.fetch` is available, or
7 * anywhere a compatible fetch function can be supplied.
8 *
9 * @stability unstable
10 * @since 4.0.0
11 */
12import * as Context from "../Context.ts"
13import * as Effect from "../Effect.ts"
14import type * as Layer from "../Layer.ts"
15import * as Stream from "../Stream.ts"
16import * as Headers from "./Headers.ts"
17import * as HttpClient from "./HttpClient.ts"
18import * as HttpClientError from "./HttpClientError.ts"
19import * as HttpClientResponse from "./HttpClientResponse.ts"
20
21/**
22 * Context reference for the `fetch` implementation used by the fetch-based HTTP client.
23 *
24 * **Details**
25 *
26 * Defaults to `globalThis.fetch`.
27 *
28 * @stability unstable
29 * @category services
30 * @since 4.0.0
31 */
32export const Fetch = Context.Reference<typeof globalThis.fetch>("effect/http/FetchHttpClient/Fetch", {
33 defaultValue: () => globalThis.fetch
34})
35
36/**
37 * Service that contains default fetch options for the fetch-based HTTP client.
38 *
39 * **When to use**
40 *
41 * Use to provide default credentials, cache, redirect, integrity, or other
42 * fetch options for outgoing HTTP requests.
43 *
44 * **Details**
45 *
46 * Request-specific method, headers, body, and abort signal are supplied by the client when a request is executed.
47 *
48 * @stability unstable
49 * @category services
50 * @since 4.0.0
51 */
52export class RequestInit extends Context.Service<RequestInit, globalThis.RequestInit>()(
53 "effect/http/FetchHttpClient/RequestInit"
54) {}
55
56const fetch: HttpClient.HttpClient = HttpClient.make((request, url, signal, fiber) => {
57 const fetch = fiber.getRef(Fetch)
58 const options: globalThis.RequestInit = Context.getOrUndefined(fiber.context, RequestInit) ?? {}
59 let headers = options.headers
60 ? Headers.merge(Headers.fromInput(options.headers as Headers.Input), request.headers)
61 : request.headers
62 if (headers["content-length"]) {
63 headers = Headers.remove(headers, "content-length")
64 }
65 const send = (body: BodyInit | undefined) =>
66 Effect.map(
67 Effect.tryPromise({
68 try: () =>
69 fetch(url, {
70 ...options,
71 method: request.method,
72 headers,
73 body,
74 duplex: typeof ReadableStream !== "undefined" && body instanceof ReadableStream ? "half" : undefined,
75 signal
76 } as any),
77 catch: (cause) =>
78 new HttpClientError.HttpClientError({
79 reason: new HttpClientError.TransportError({
80 request,
81 cause
82 })
83 })
84 }),
85 (response) => HttpClientResponse.fromWeb(request, response)
86 )
87 switch (request.body._tag) {
88 case "Raw":
89 case "Uint8Array":
90 return send(request.body.body as any)
91 case "FormData":
92 return send(request.body.formData)
93 case "Stream":
94 return Effect.flatMap(Stream.toReadableStreamEffect(request.body.stream), send)
95 }
96 return send(undefined)
97})
98
99/**
100 * Layer that provides an `HttpClient` implementation backed by the configured
101 * `Fetch` function.
102 *
103 * **When to use**
104 *
105 * Use when an Effect program should execute `HttpClient` requests through the
106 * platform `fetch` implementation, especially in browser, edge, or Node.js
107 * runtimes with `globalThis.fetch`.
108 *
109 * **Details**
110 *
111 * The layer uses the current `Fetch` reference and optional `RequestInit`
112 * service for each request. Request-specific method, headers, body, and abort
113 * signal are supplied by the client and override matching `RequestInit` fields.
114 *
115 * **Gotchas**
116 *
117 * Fetch behavior comes from the runtime's implementation, so CORS, cookies,
118 * redirects, abort handling, and streaming support can vary by platform. Stream
119 * request bodies are sent as Web streams with `duplex: "half"`, and any
120 * `content-length` header is removed before calling `fetch`.
121 *
122 * @see {@link Fetch} for supplying the fetch implementation used by this layer
123 * @see {@link RequestInit} for default `RequestInit` options applied before request-specific fields
124 *
125 * @stability unstable
126 * @category layers
127 * @since 4.0.0
128 */
129export const layer: Layer.Layer<HttpClient.HttpClient> = HttpClient.layerMergedContext(Effect.succeed(fetch))