| import * as Cause from "../../Cause.js"; | |
| import { Clock } from "../../Clock.js"; | |
| import * as Context from "../../Context.js"; | |
| import * as Duration from "../../Duration.js"; | |
| import * as Effect from "../../Effect.js"; | |
| import * as Fiber from "../../Fiber.js"; | |
| import { constant, constFalse, constTrue, dual, flow, identity } from "../../Function.js"; | |
| import * as Inspectable from "../../Inspectable.js"; | |
| import * as Layer from "../../Layer.js"; | |
| import * as Option from "../../Option.js"; | |
| import { pipeArguments } from "../../Pipeable.js"; | |
| import * as Predicate from "../../Predicate.js"; | |
| import * as Ref from "../../Ref.js"; | |
| import * as Result from "../../Result.js"; | |
| import * as Schedule from "../../Schedule.js"; | |
| import * as Stream from "../../Stream.js"; | |
| import * as Tracer from "../../Tracer.js"; | |
| import * as Cookies from "./Cookies.js"; | |
| import * as Headers from "./Headers.js"; | |
| import * as Error from "./HttpClientError.js"; | |
| import * as HttpClientRequest from "./HttpClientRequest.js"; | |
| import * as HttpClientResponse from "./HttpClientResponse.js"; | |
| import * as HttpIncomingMessage from "./HttpIncomingMessage.js"; | |
| import * as HttpMethod from "./HttpMethod.js"; | |
| import * as TraceContext from "./HttpTraceContext.js"; | |
| import * as UrlParams from "./UrlParams.js"; | |
| const TypeId = "~effect/http/HttpClient"; | |
| /** | |
| * Returns `true` if the provided value is an `HttpClient`. | |
| * | |
| * @category guards | |
| * @since 4.0.0 | |
| */ | |
| export const isHttpClient = u => Predicate.hasProperty(u, TypeId); | |
| /** | |
| * Service tag for the default outgoing HTTP client service. | |
| * | |
| * **When to use** | |
| * | |
| * Use to provide the default outgoing HTTP client service used by request | |
| * accessors such as `execute`, `get`, and `post`. | |
| * | |
| * @category services | |
| * @since 4.0.0 | |
| */ | |
| export const HttpClient = /*#__PURE__*/Context.Service("effect/HttpClient"); | |
| const accessor = method => (...args) => Effect.flatMap(HttpClient, client => client[method](...args)); | |
| /** | |
| * Executes a prebuilt `HttpClientRequest` using the `HttpClient` service from the environment. | |
| * | |
| * @category accessors | |
| * @since 4.0.0 | |
| */ | |
| export const execute = /*#__PURE__*/accessor("execute"); | |
| /** | |
| * Executes a `GET` request using the `HttpClient` service from the environment. | |
| * | |
| * @category accessors | |
| * @since 4.0.0 | |
| */ | |
| export const get = /*#__PURE__*/accessor("get"); | |
| /** | |
| * Executes a `HEAD` request using the `HttpClient` service from the environment. | |
| * | |
| * @category accessors | |
| * @since 4.0.0 | |
| */ | |
| export const head = /*#__PURE__*/accessor("head"); | |
| /** | |
| * Executes a `POST` request using the `HttpClient` service from the environment. | |
| * | |
| * @category accessors | |
| * @since 4.0.0 | |
| */ | |
| export const post = /*#__PURE__*/accessor("post"); | |
| /** | |
| * Executes a `PATCH` request using the `HttpClient` service from the environment. | |
| * | |
| * @category accessors | |
| * @since 4.0.0 | |
| */ | |
| export const patch = /*#__PURE__*/accessor("patch"); | |
| /** | |
| * Executes a `PUT` request using the `HttpClient` service from the environment. | |
| * | |
| * @category accessors | |
| * @since 4.0.0 | |
| */ | |
| export const put = /*#__PURE__*/accessor("put"); | |
| /** | |
| * Executes a `DELETE` request using the `HttpClient` service from the environment. | |
| * | |
| * @category accessors | |
| * @since 4.0.0 | |
| */ | |
| export const del = /*#__PURE__*/accessor("del"); | |
| /** | |
| * Executes an `OPTIONS` request using the `HttpClient` service from the environment. | |
| * | |
| * @category accessors | |
| * @since 4.0.0 | |
| */ | |
| export const options = /*#__PURE__*/accessor("options"); | |
| /** | |
| * Transforms a client by wrapping the response effect for each request. | |
| * | |
| * **Details** | |
| * | |
| * The transformation receives both the response effect and the original request, allowing it to change success, error, and environment behavior. | |
| * | |
| * @category mapping & sequencing | |
| * @since 4.0.0 | |
| */ | |
| export const transform = /*#__PURE__*/dual(2, (self, f) => makeWith(Effect.flatMap(request => f(self.postprocess(Effect.succeed(request)), request)), self.preprocess)); | |
| /** | |
| * Transforms a client by applying an effectful transformation to each response effect. | |
| * | |
| * @category mapping & sequencing | |
| * @since 4.0.0 | |
| */ | |
| export const transformResponse = /*#__PURE__*/dual(2, (self, f) => makeWith(request => f(self.postprocess(request)), self.preprocess)); | |
| const catch_ = /*#__PURE__*/dual(2, (self, f) => transformResponse(self, Effect.catch(f))); | |
| export { | |
| /** | |
| * Handles all client failures with an effectful recovery function and returns a transformed client. | |
| * | |
| * @category error handling | |
| * @since 4.0.0 | |
| */ | |
| catch_ as catch }; | |
| /** | |
| * Handles client failures with one or more matching `_tag` values and returns a transformed client. | |
| * | |
| * @category error handling | |
| * @since 4.0.0 | |
| */ | |
| export const catchTag = /*#__PURE__*/dual(3, (self, tag, f) => transformResponse(self, effect => Effect.catchTag(effect, tag, f))); | |
| /** | |
| * Handles client failures by matching their `_tag` values against a case map. | |
| * | |
| * @category error handling | |
| * @since 4.0.0 | |
| */ | |
| export const catchTags = /*#__PURE__*/dual(2, (self, cases) => transformResponse(self, Effect.catchTags(cases))); | |
| /** | |
| * Filters the result of a response, or runs an alternative effect if the predicate fails. | |
| * | |
| * @category filters | |
| * @since 4.0.0 | |
| */ | |
| export const filterOrElse = /*#__PURE__*/dual(3, (self, f, orElse) => transformResponse(self, Effect.filterOrElse(f, orElse))); | |
| /** | |
| * Filters successful responses, or fails with the error produced by `orFailWith` when the predicate does not match. | |
| * | |
| * @category filters | |
| * @since 4.0.0 | |
| */ | |
| export const filterOrFail = /*#__PURE__*/dual(3, (self, f, orFailWith) => transformResponse(self, Effect.filterOrFail(f, orFailWith))); | |
| /** | |
| * Filters responses by HTTP status code. | |
| * | |
| * @category filters | |
| * @since 4.0.0 | |
| */ | |
| export const filterStatus = /*#__PURE__*/dual(2, (self, f) => transformResponse(self, Effect.flatMap(HttpClientResponse.filterStatus(f)))); | |
| /** | |
| * Filters responses that return a 2xx status code. | |
| * | |
| * @category filters | |
| * @since 4.0.0 | |
| */ | |
| export const filterStatusOk = /*#__PURE__*/transformResponse(/*#__PURE__*/Effect.flatMap(HttpClientResponse.filterStatusOk)); | |
| /** | |
| * Constructs an `HttpClient.With` from a preprocessing function and a postprocessing function. | |
| * | |
| * **Details** | |
| * | |
| * `execute` applies preprocessing to the request and then passes the resulting request effect to postprocessing. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const makeWith = (postprocess, preprocess) => { | |
| const self = Object.create(Proto); | |
| self.preprocess = preprocess; | |
| self.postprocess = postprocess; | |
| self.execute = function (request) { | |
| return postprocess(preprocess(request)); | |
| }; | |
| return self; | |
| }; | |
| const Proto = { | |
| [TypeId]: TypeId, | |
| pipe() { | |
| return pipeArguments(this, arguments); | |
| }, | |
| ...Inspectable.BaseProto, | |
| toJSON() { | |
| return { | |
| _id: "effect/HttpClient" | |
| }; | |
| }, | |
| ... /*#__PURE__*/Object.fromEntries(/*#__PURE__*/HttpMethod.allShort.map(([fullMethod, method]) => [method, function (url, options) { | |
| return this.execute(HttpClientRequest.make(fullMethod)(url, options)); | |
| }])) | |
| }; | |
| /** | |
| * Constructs an `HttpClient` from a low-level request runner. | |
| * | |
| * **Details** | |
| * | |
| * The runner receives the request, resolved URL, abort signal, and current fiber. The client wrapper handles URL construction failures, tracing and propagation, header redaction, and aborting non-scoped requests on interruption. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const make = f => makeWith(effect => Effect.flatMap(effect, request => Effect.withFiber(fiber => { | |
| const scopedController = scopedRequests.get(request); | |
| const controller = scopedController ?? new AbortController(); | |
| const urlResult = UrlParams.makeUrl(request.url, request.urlParams, Option.getOrUndefined(request.hash)); | |
| if (Result.isFailure(urlResult)) { | |
| return Effect.fail(new Error.HttpClientError({ | |
| reason: new Error.InvalidUrlError({ | |
| request, | |
| cause: urlResult.failure | |
| }) | |
| })); | |
| } | |
| const url = urlResult.success; | |
| const tracerDisabled = fiber.getRef(Tracer.DisablePropagation) || fiber.getRef(TracerDisabledWhen)(request); | |
| if (tracerDisabled) { | |
| const effect = f(request, url, controller.signal, fiber); | |
| if (scopedController) return effect; | |
| return Effect.uninterruptibleMask(restore => Effect.matchCauseEffect(restore(effect), { | |
| onSuccess(response) { | |
| responseRegistry.register(response, controller); | |
| return Effect.succeed(new InterruptibleResponse(response, controller)); | |
| }, | |
| onFailure(cause) { | |
| if (Cause.hasInterrupts(cause)) { | |
| controller.abort(); | |
| } | |
| return Effect.failCause(cause); | |
| } | |
| })); | |
| } | |
| return Effect.useSpan(fiber.getRef(SpanNameGenerator)(request), { | |
| kind: "client" | |
| }, span => { | |
| span.attribute("http.request.method", request.method); | |
| span.attribute("server.address", url.origin); | |
| if (url.port !== "") { | |
| span.attribute("server.port", +url.port); | |
| } | |
| span.attribute("url.full", url.toString()); | |
| span.attribute("url.path", url.pathname); | |
| span.attribute("url.scheme", url.protocol.slice(0, -1)); | |
| const query = url.search.slice(1); | |
| if (query !== "") { | |
| span.attribute("url.query", query); | |
| } | |
| const redactedHeaderNames = fiber.getRef(Headers.CurrentRedactedNames); | |
| const redactedHeaders = Headers.redact(request.headers, redactedHeaderNames); | |
| for (const name in redactedHeaders) { | |
| span.attribute(`http.request.header.${name}`, String(redactedHeaders[name])); | |
| } | |
| request = fiber.getRef(TracerPropagationEnabled) ? HttpClientRequest.setHeaders(request, TraceContext.toHeaders(span)) : request; | |
| return Effect.uninterruptibleMask(restore => restore(f(request, url, controller.signal, fiber)).pipe(Effect.withParentSpan(span, { | |
| captureStackTrace: false | |
| }), Effect.matchCauseEffect({ | |
| onSuccess: response => { | |
| span.attribute("http.response.status_code", response.status); | |
| const redactedHeaders = Headers.redact(response.headers, redactedHeaderNames); | |
| for (const name in redactedHeaders) { | |
| span.attribute(`http.response.header.${name}`, String(redactedHeaders[name])); | |
| } | |
| if (scopedController) return Effect.succeed(response); | |
| responseRegistry.register(response, controller); | |
| return Effect.succeed(new InterruptibleResponse(response, controller)); | |
| }, | |
| onFailure(cause) { | |
| if (!scopedController && Cause.hasInterrupts(cause)) { | |
| controller.abort(); | |
| } | |
| return Effect.failCause(cause); | |
| } | |
| }))); | |
| }); | |
| })), Effect.succeed); | |
| /** | |
| * Appends a transformation of the request object before sending it. | |
| * | |
| * @category mapping & sequencing | |
| * @since 4.0.0 | |
| */ | |
| export const mapRequest = /*#__PURE__*/dual(2, (self, f) => makeWith(self.postprocess, request => Effect.map(self.preprocess(request), f))); | |
| /** | |
| * Appends an effectful transformation of the request object before sending it. | |
| * | |
| * @category mapping & sequencing | |
| * @since 4.0.0 | |
| */ | |
| export const mapRequestEffect = /*#__PURE__*/dual(2, (self, f) => makeWith(self.postprocess, request => Effect.flatMap(self.preprocess(request), f))); | |
| /** | |
| * Prepends a transformation of the request object before sending it. | |
| * | |
| * @category mapping & sequencing | |
| * @since 4.0.0 | |
| */ | |
| export const mapRequestInput = /*#__PURE__*/dual(2, (self, f) => makeWith(self.postprocess, request => self.preprocess(f(request)))); | |
| /** | |
| * Prepends an effectful transformation of the request object before sending it. | |
| * | |
| * @category mapping & sequencing | |
| * @since 4.0.0 | |
| */ | |
| export const mapRequestInputEffect = /*#__PURE__*/dual(2, (self, f) => makeWith(self.postprocess, request => Effect.flatMap(f(request), self.preprocess))); | |
| /** | |
| * Retries the request based on a provided schedule or policy. | |
| * | |
| * @category error handling | |
| * @since 4.0.0 | |
| */ | |
| export const retry = /*#__PURE__*/dual(2, (self, policy) => transformResponse(self, Effect.retry(policy))); | |
| /** | |
| * Retries common transient errors, such as rate limiting, timeouts or network issues. | |
| * | |
| * **When to use** | |
| * | |
| * Use to focus on retrying errors, transient responses, or both. | |
| * | |
| * **Details** | |
| * | |
| * Specifying a `while` predicate allows you to consider other errors as | |
| * transient, and is ignored in "response-only" mode. | |
| * | |
| * @category error handling | |
| * @since 4.0.0 | |
| */ | |
| export const retryTransient = /*#__PURE__*/dual(2, (self, options) => { | |
| const isOnlySchedule = Schedule.isSchedule(options); | |
| const retryOn = isOnlySchedule ? "errors-and-responses" : options.retryOn ?? "errors-and-responses"; | |
| const schedule = isOnlySchedule ? options : options.schedule; | |
| const passthroughSchedule = schedule && Schedule.passthrough(schedule); | |
| const times = isOnlySchedule ? undefined : options.times; | |
| return transformResponse(self, flow(retryOn === "errors-only" ? identity : Effect.repeat({ | |
| schedule: passthroughSchedule, | |
| times, | |
| while: isTransientResponse | |
| }), retryOn === "response-only" ? identity : Effect.retry({ | |
| while: isOnlySchedule || options.while === undefined ? isTransientError : Predicate.or(isTransientError, options.while), | |
| schedule, | |
| times | |
| }))); | |
| }); | |
| /** | |
| * Applies request rate limiting using the `RateLimiter` service. | |
| * | |
| * **Details** | |
| * | |
| * It can update limits by inspecting common rate limit response headers and | |
| * automatically retries HTTP `429` responses (or `HttpClientError` values | |
| * wrapping a `429` response) by forcing the retry back through the limiter. | |
| * | |
| * @category rate limiting | |
| * @since 4.0.0 | |
| */ | |
| export const withRateLimiter = /*#__PURE__*/dual(2, (self, options) => { | |
| const initialState = { | |
| initial: true, | |
| limit: options.limit, | |
| window: Duration.max(Duration.fromInputUnsafe(options.window), Duration.millis(1)) | |
| }; | |
| const states = new Map(); | |
| const keyOption = options.key; | |
| const resolveKey = typeof keyOption === "function" ? keyOption : constant(keyOption); | |
| const tokensOption = options.tokens; | |
| const resolveTokens = typeof tokensOption === "function" ? tokensOption : constant(tokensOption ?? 1); | |
| const getState = key => { | |
| const current = states.get(key); | |
| if (current !== undefined) { | |
| return current; | |
| } | |
| states.set(key, initialState); | |
| return initialState; | |
| }; | |
| const onResponse = options.disableResponseInspection ? undefined : (clock, key, headers, tokens) => { | |
| const current = getState(key); | |
| const next = parseRateLimiterState(current, clock, headers, tokens); | |
| if (next.limit !== current.limit || !Duration.equals(next.window, current.window)) { | |
| states.set(key, next); | |
| } | |
| }; | |
| return transform(self, function loop(effect, request) { | |
| const fiber = Fiber.getCurrent(); | |
| const clock = fiber.getRef(Clock); | |
| const key = resolveKey(request); | |
| const tokens = Math.max(resolveTokens(request), 1); | |
| const current = getState(key); | |
| function retry(response) { | |
| if (options.disableResponseInspection) return loop(effect, request); | |
| const retryAfter = parseRetryAfter(clock, getHeader(response.headers, "retry-after")); | |
| return retryAfter ? Effect.flatMap(Effect.sleep(retryAfter), () => loop(effect, request)) : loop(effect, request); | |
| } | |
| return Effect.flatMap(options.limiter.consume({ | |
| algorithm: options.algorithm, | |
| onExceeded: "delay", | |
| key, | |
| limit: current.limit, | |
| window: current.window, | |
| tokens | |
| }), ({ | |
| delay | |
| }) => { | |
| const run = Effect.matchEffect(effect, { | |
| onSuccess(response) { | |
| onResponse?.(clock, key, response.headers, tokens); | |
| if (response.status !== 429) return Effect.succeed(response); | |
| return retry(response); | |
| }, | |
| onFailure(error) { | |
| if (isTooManyRequestsHttpClientError(error)) { | |
| onResponse?.(clock, key, error.reason.response.headers, tokens); | |
| return retry(error.reason.response); | |
| } | |
| return Effect.fail(error); | |
| } | |
| }); | |
| return Duration.isZero(delay) ? run : Effect.delay(run, delay); | |
| }); | |
| }); | |
| }); | |
| const parseRateLimiterState = (state, clock, headers, tokens) => { | |
| const limit = parseRateLimitLimit(state, headers, tokens) ?? state.limit; | |
| const window = parseRateLimitWindow(clock, headers) ?? state.window; | |
| if (limit === state.limit && Duration.equals(window, state.window)) { | |
| return state; | |
| } | |
| return { | |
| limit, | |
| window, | |
| initial: false | |
| }; | |
| }; | |
| const parseRateLimitLimit = (state, headers, tokens) => { | |
| const raw = getHeader(headers, "ratelimit-limit", "x-ratelimit-limit"); | |
| const value = parseNumberHeader(raw); | |
| if (value !== undefined && value > 0) { | |
| return value; | |
| } | |
| const remaining = parseRateLimitRemaining(headers); | |
| if (remaining === undefined) { | |
| return undefined; | |
| } | |
| return state.initial ? remaining + tokens : Math.max(remaining + tokens, state.limit); | |
| }; | |
| const parseRateLimitRemaining = headers => { | |
| const raw = getHeader(headers, "ratelimit-remaining", "x-ratelimit-remaining"); | |
| const value = parseNumberHeader(raw); | |
| return value !== undefined && value >= 0 ? value : undefined; | |
| }; | |
| const parseRateLimitWindow = (clock, headers) => { | |
| const retryAfter = parseRetryAfter(clock, getHeader(headers, "retry-after")); | |
| if (retryAfter !== undefined) { | |
| return retryAfter; | |
| } | |
| const resetAfter = parseResetAfter(getHeader(headers, "ratelimit-reset-after", "x-ratelimit-reset-after")); | |
| if (resetAfter !== undefined) { | |
| return resetAfter; | |
| } | |
| return parseResetHeader(clock, getHeader(headers, "ratelimit-reset", "x-ratelimit-reset")); | |
| }; | |
| const parseRetryAfter = (clock, value) => { | |
| if (value === undefined) { | |
| return undefined; | |
| } | |
| const numeric = parseNumberHeader(value); | |
| if (numeric !== undefined) { | |
| return Duration.max(Duration.seconds(numeric), Duration.millis(1)); | |
| } | |
| const parsedDate = Date.parse(value); | |
| if (Number.isNaN(parsedDate)) { | |
| return undefined; | |
| } | |
| const millis = parsedDate - clock.currentTimeMillisUnsafe(); | |
| if (millis <= 0) { | |
| return Duration.millis(1); | |
| } | |
| return Duration.millis(millis); | |
| }; | |
| const parseResetAfter = value => { | |
| const numeric = parseNumberHeader(value); | |
| if (numeric === undefined || numeric <= 0) { | |
| return undefined; | |
| } | |
| return Duration.max(Duration.seconds(numeric), Duration.millis(1)); | |
| }; | |
| const parseResetHeader = (clock, value) => { | |
| const numeric = parseNumberHeader(value); | |
| if (numeric === undefined || numeric <= 0) { | |
| return undefined; | |
| } | |
| const nowMillis = clock.currentTimeMillisUnsafe(); | |
| if (numeric > 1_000_000_000_000) { | |
| return Duration.millis(Math.max(numeric - nowMillis, 1)); | |
| } | |
| if (numeric > 1_000_000_000) { | |
| return Duration.millis(Math.max(numeric * 1_000 - nowMillis, 1)); | |
| } | |
| return Duration.max(Duration.seconds(numeric), Duration.millis(1)); | |
| }; | |
| const parseNumberHeader = value => { | |
| if (value === undefined) { | |
| return undefined; | |
| } | |
| const match = /-?\d+(?:\.\d+)?/.exec(value); | |
| if (match === null) { | |
| return undefined; | |
| } | |
| const parsed = Number(match[0]); | |
| return Number.isFinite(parsed) ? parsed : undefined; | |
| }; | |
| const getHeader = (headers, ...keys) => { | |
| for (let i = 0; i < keys.length; i++) { | |
| const value = headers[keys[i]]; | |
| if (value !== undefined) { | |
| return value; | |
| } | |
| } | |
| return undefined; | |
| }; | |
| /** | |
| * Performs an additional effect after a successful request. | |
| * | |
| * @category mapping & sequencing | |
| * @since 4.0.0 | |
| */ | |
| export const tap = /*#__PURE__*/dual(2, (self, f) => transformResponse(self, Effect.tap(f))); | |
| /** | |
| * Performs an additional effect after an unsuccessful request. | |
| * | |
| * @category mapping & sequencing | |
| * @since 4.0.0 | |
| */ | |
| export const tapError = /*#__PURE__*/dual(2, (self, f) => transformResponse(self, Effect.tapError(f))); | |
| /** | |
| * Performs an additional effect on the request before sending it. | |
| * | |
| * @category mapping & sequencing | |
| * @since 4.0.0 | |
| */ | |
| export const tapRequest = /*#__PURE__*/dual(2, (self, f) => makeWith(self.postprocess, request => Effect.tap(self.preprocess(request), f))); | |
| /** | |
| * Adds a `Ref` of cookies to the client for handling cookies across requests. | |
| * | |
| * **When to use** | |
| * | |
| * Use to add shared cookie storage to a client so response cookies are retained | |
| * and sent by later requests. | |
| * | |
| * @category cookies | |
| * @since 4.0.0 | |
| */ | |
| export const withCookiesRef = /*#__PURE__*/dual(2, (self, ref) => makeWith(request => Effect.tap(self.postprocess(request), response => Ref.update(ref, cookies => Cookies.merge(cookies, response.cookies))), request => Effect.flatMap(self.preprocess(request), request => Effect.map(Ref.get(ref), cookies => Cookies.isEmpty(cookies) ? request : HttpClientRequest.setHeader(request, "cookie", Cookies.toCookieHeader(cookies)))))); | |
| /** | |
| * Attaches the lifetime of the `HttpClientRequest` to a `Scope`. | |
| * | |
| * @category resource management | |
| * @since 4.0.0 | |
| */ | |
| export const withScope = self => transform(self, (effect, request) => { | |
| const controller = new AbortController(); | |
| scopedRequests.set(request, controller); | |
| return Effect.andThen(Effect.addFinalizer(() => Effect.sync(() => controller.abort())), effect); | |
| }); | |
| /** | |
| * Enables following HTTP redirects up to a specified number of times. | |
| * | |
| * @category redirects | |
| * @since 4.0.0 | |
| */ | |
| export const followRedirects = /*#__PURE__*/dual(args => isHttpClient(args[0]), (self, maxRedirects) => makeWith(request => { | |
| const loop = (request, redirects) => Effect.flatMap(self.postprocess(Effect.succeed(request)), response => response.status >= 300 && response.status < 400 && response.headers.location && redirects < (maxRedirects ?? 10) ? loop(HttpClientRequest.setUrl(request, new URL(response.headers.location, response.request.url)), redirects + 1) : Effect.succeed(response)); | |
| return Effect.flatMap(request, request => loop(request, 0)); | |
| }, self.preprocess)); | |
| /** | |
| * Context reference for a predicate that disables client-side tracing for matching outgoing requests. | |
| * | |
| * @category references | |
| * @since 4.0.0 | |
| */ | |
| export const TracerDisabledWhen = /*#__PURE__*/Context.Reference("effect/http/HttpClient/TracerDisabledWhen", { | |
| defaultValue: () => constFalse | |
| }); | |
| /** | |
| * Context reference that controls whether outgoing client spans are propagated to request headers. | |
| * | |
| * @category references | |
| * @since 4.0.0 | |
| */ | |
| export const TracerPropagationEnabled = /*#__PURE__*/Context.Reference("effect/HttpClient/TracerPropagationEnabled", { | |
| defaultValue: constTrue | |
| }); | |
| /** | |
| * Context reference for generating the span name used for outgoing client request spans. | |
| * | |
| * @category references | |
| * @since 4.0.0 | |
| */ | |
| export const SpanNameGenerator = /*#__PURE__*/Context.Reference("effect/http/HttpClient/SpanNameGenerator", { | |
| defaultValue: () => request => `http.client ${request.method}` | |
| }); | |
| /** | |
| * Creates an `HttpClient` layer and merges the layer construction context into client response effects. | |
| * | |
| * @category layers | |
| * @since 4.0.0 | |
| */ | |
| export const layerMergedContext = effect => Layer.effect(HttpClient)(Effect.contextWith(context => Effect.map(effect, client => transformResponse(client, Effect.updateContext(input => Context.merge(context, input)))))); | |
| // ----------------------------------------------------------------------------- | |
| // internal | |
| // ----------------------------------------------------------------------------- | |
| const responseRegistry = /*#__PURE__*/(() => { | |
| if ("FinalizationRegistry" in globalThis && globalThis.FinalizationRegistry) { | |
| const registry = /*#__PURE__*/new FinalizationRegistry(controller => { | |
| controller.abort(); | |
| }); | |
| return { | |
| register(response, controller) { | |
| registry.register(response, controller, response); | |
| }, | |
| unregister(response) { | |
| registry.unregister(response); | |
| } | |
| }; | |
| } | |
| const timers = /*#__PURE__*/new Map(); | |
| return { | |
| register(response, controller) { | |
| timers.set(response, setTimeout(() => controller.abort(), 5000)); | |
| }, | |
| unregister(response) { | |
| const timer = timers.get(response); | |
| if (timer === undefined) return; | |
| clearTimeout(timer); | |
| timers.delete(response); | |
| } | |
| }; | |
| })(); | |
| const scopedRequests = /*#__PURE__*/new WeakMap(); | |
| class InterruptibleResponse { | |
| original; | |
| controller; | |
| constructor(original, controller) { | |
| this.original = original; | |
| this.controller = controller; | |
| } | |
| [HttpClientResponse.TypeId] = HttpClientResponse.TypeId; | |
| [HttpIncomingMessage.TypeId] = HttpIncomingMessage.TypeId; | |
| applyInterrupt(effect) { | |
| return Effect.suspend(() => { | |
| responseRegistry.unregister(this.original); | |
| return Effect.onInterrupt(effect, () => Effect.sync(() => { | |
| this.controller.abort(); | |
| })); | |
| }); | |
| } | |
| get request() { | |
| return this.original.request; | |
| } | |
| get status() { | |
| return this.original.status; | |
| } | |
| get headers() { | |
| return this.original.headers; | |
| } | |
| get cookies() { | |
| return this.original.cookies; | |
| } | |
| get remoteAddress() { | |
| return this.original.remoteAddress; | |
| } | |
| get formData() { | |
| return this.applyInterrupt(this.original.formData); | |
| } | |
| get text() { | |
| return this.applyInterrupt(this.original.text); | |
| } | |
| get json() { | |
| return this.applyInterrupt(this.original.json); | |
| } | |
| get urlParamsBody() { | |
| return this.applyInterrupt(this.original.urlParamsBody); | |
| } | |
| get arrayBuffer() { | |
| return this.applyInterrupt(this.original.arrayBuffer); | |
| } | |
| get stream() { | |
| return Stream.suspend(() => { | |
| responseRegistry.unregister(this.original); | |
| return Stream.ensuring(this.original.stream, Effect.sync(() => { | |
| this.controller.abort(); | |
| })); | |
| }); | |
| } | |
| toJSON() { | |
| return this.original.toJSON(); | |
| } | |
| [Inspectable.NodeInspectSymbol]() { | |
| return this.original[Inspectable.NodeInspectSymbol](); | |
| } | |
| pipe() { | |
| return pipeArguments(this, arguments); | |
| } | |
| } | |
| const isTransientError = error => Cause.isTimeoutError(error) || isTransientHttpError(error); | |
| const isTransientHttpError = error => Error.isHttpClientError(error) && (error.reason._tag === "TransportError" || error.reason._tag === "StatusCodeError" && isTransientResponse(error.reason.response)); | |
| const isTooManyRequestsHttpClientError = error => Error.isHttpClientError(error) && error.reason._tag === "StatusCodeError" && error.reason.response.status === 429; | |
| const isTransientResponse = response => response.status === 408 || response.status === 429 || response.status === 500 || response.status === 502 || response.status === 503 || response.status === 504; | |
| //# sourceMappingURL=HttpClient.js.map |
Xet Storage Details
- Size:
- 26.3 kB
- Xet hash:
- 2893522b368b8214a3ebcdc2fcfd279a4604bf48e0c08bf3761e99dfd0dc10ff
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.