EdgeAIG's picture
download
raw
17.4 kB
/**
* Describes immutable outgoing HTTP client requests.
*
* `HttpClientRequest` is the request model shared by Effect HTTP clients and
* platform adapters. A request stores its method, URL, query parameters, hash,
* headers, and body as structured data. This module includes constructors,
* helpers for updating requests, body encoders for common payloads, and
* conversions to and from Web `Request` values.
*
* @since 4.0.0
*/
import * as Context from "../../Context.js";
import * as Effect from "../../Effect.js";
import { dual } from "../../Function.js";
import * as Inspectable from "../../Inspectable.js";
import { stringOrRedacted } from "../../internal/redacted.js";
import * as Option from "../../Option.js";
import { pipeArguments } from "../../Pipeable.js";
import { hasProperty } from "../../Predicate.js";
import { redact } from "../../Redactable.js";
import * as Result from "../../Result.js";
import * as Stream from "../../Stream.js";
import * as Headers from "./Headers.js";
import * as HttpBody from "./HttpBody.js";
import { hasBody } from "./HttpMethod.js";
import * as UrlParams from "./UrlParams.js";
const TypeId = "~effect/http/HttpClientRequest";
/**
* Returns `true` when a value is an `HttpClientRequest`.
*
* @category guards
* @since 4.0.0
*/
export const isHttpClientRequest = u => hasProperty(u, TypeId);
const Proto = {
[TypeId]: TypeId,
...Inspectable.BaseProto,
toJSON() {
return {
_id: "HttpClientRequest",
method: this.method,
url: this.url,
urlParams: this.urlParams,
hash: this.hash,
headers: redact(this.headers),
body: this.body.toJSON()
};
},
pipe() {
return pipeArguments(this, arguments);
}
};
/**
* Constructs an `HttpClientRequest` from fully normalized request components.
*
* @category constructors
* @since 4.0.0
*/
export function makeWith(method, url, urlParams, hash, headers, body) {
const self = Object.create(Proto);
self.method = method;
self.url = url;
self.urlParams = urlParams;
self.hash = hash;
self.headers = headers;
self.body = body;
return self;
}
/**
* An empty `GET` request with no URL, query parameters, hash, headers, or body.
*
* @category constructors
* @since 4.0.0
*/
export const empty = /*#__PURE__*/makeWith("GET", "", UrlParams.empty, /*#__PURE__*/Option.none(), Headers.empty, HttpBody.empty);
/**
* Creates a request constructor for the specified HTTP method.
*
* @category constructors
* @since 4.0.0
*/
export const make = method => (url, options) => modify(empty, {
method,
url,
...(options ?? undefined)
});
/**
* Creates a `GET` request for the specified URL.
*
* @category constructors
* @since 4.0.0
*/
export const get = /*#__PURE__*/make("GET");
/**
* Creates a `POST` request for the specified URL.
*
* @category constructors
* @since 4.0.0
*/
export const post = /*#__PURE__*/make("POST");
/**
* Creates a `PATCH` request for the specified URL.
*
* @category constructors
* @since 4.0.0
*/
export const patch = /*#__PURE__*/make("PATCH");
/**
* Creates a `PUT` request for the specified URL.
*
* @category constructors
* @since 4.0.0
*/
export const put = /*#__PURE__*/make("PUT");
const del = /*#__PURE__*/make("DELETE");
export {
/**
* Creates a `DELETE` request for the specified URL.
*
* @category constructors
* @since 4.0.0
*/
del as delete };
/**
* Creates a `HEAD` request for the specified URL.
*
* @category constructors
* @since 4.0.0
*/
export const head = /*#__PURE__*/make("HEAD");
/**
* Creates an `OPTIONS` request for the specified URL.
*
* @category constructors
* @since 4.0.0
*/
export const options = /*#__PURE__*/make("OPTIONS");
/**
* Creates a `TRACE` request for the specified URL.
*
* @category constructors
* @since 4.0.0
*/
export const trace = /*#__PURE__*/make("TRACE");
/**
* Applies request options to an `HttpClientRequest`, returning a new request.
*
* @category combinators
* @since 4.0.0
*/
export const modify = /*#__PURE__*/dual(2, (self, options) => {
let result = self;
if (options.method) {
result = setMethod(result, options.method);
}
if (options.url) {
result = setUrl(result, options.url);
}
if (options.headers) {
result = setHeaders(result, options.headers);
}
if (options.urlParams) {
result = setUrlParams(result, options.urlParams);
}
if (options.hash) {
result = setHash(result, options.hash);
}
if (options.body) {
result = setBody(result, options.body);
}
if (options.accept) {
result = accept(result, options.accept);
}
if (options.acceptJson) {
result = acceptJson(result);
}
return result;
});
/**
* Sets the HTTP method on a request, returning a new request.
*
* @category combinators
* @since 4.0.0
*/
export const setMethod = /*#__PURE__*/dual(2, (self, method) => makeWith(method, self.url, self.urlParams, self.hash, self.headers, self.body));
/**
* Sets a single request header, replacing any existing value for that header.
*
* @category combinators
* @since 4.0.0
*/
export const setHeader = /*#__PURE__*/dual(3, (self, key, value) => makeWith(self.method, self.url, self.urlParams, self.hash, Headers.set(self.headers, key, value), self.body));
/**
* Sets multiple request headers from an input collection, replacing existing values with matching names.
*
* @category combinators
* @since 4.0.0
*/
export const setHeaders = /*#__PURE__*/dual(2, (self, input) => makeWith(self.method, self.url, self.urlParams, self.hash, Headers.setAll(self.headers, input), self.body));
/**
* Sets the `Authorization` header using HTTP Basic authentication credentials.
*
* @category combinators
* @since 4.0.0
*/
export const basicAuth = /*#__PURE__*/dual(3, (self, username, password) => setHeader(self, "Authorization", `Basic ${btoa(`${stringOrRedacted(username)}:${stringOrRedacted(password)}`)}`));
/**
* Sets the `Authorization` header using a bearer token.
*
* @category combinators
* @since 4.0.0
*/
export const bearerToken = /*#__PURE__*/dual(2, (self, token) => setHeader(self, "Authorization", `Bearer ${stringOrRedacted(token)}`));
/**
* Sets the `Accept` header to the specified media type.
*
* @category combinators
* @since 4.0.0
*/
export const accept = /*#__PURE__*/dual(2, (self, mediaType) => setHeader(self, "Accept", mediaType));
/**
* Sets the `Accept` header to `application/json`.
*
* @category combinators
* @since 4.0.0
*/
export const acceptJson = /*#__PURE__*/accept("application/json");
/**
* Sets the request URL. When given a `URL`, its search parameters and hash are extracted into the request's structured fields.
*
* @category combinators
* @since 4.0.0
*/
export const setUrl = /*#__PURE__*/dual(2, (self, url) => {
if (typeof url === "string") {
return makeWith(self.method, url, self.urlParams, self.hash, self.headers, self.body);
}
const clone = new URL(url.toString());
const urlParams = UrlParams.fromInput(clone.searchParams);
const hash = Option.fromNullishOr(clone.hash === "" ? undefined : clone.hash.slice(1));
clone.search = "";
clone.hash = "";
return makeWith(self.method, clone.toString(), urlParams, hash, self.headers, self.body);
});
/**
* Prepends a URL segment to the request URL, inserting or trimming one slash as needed.
*
* @category combinators
* @since 4.0.0
*/
export const prependUrl = /*#__PURE__*/dual(2, (self, path) => {
if (path === "") return self;
return makeWith(self.method, joinSegments(path, self.url), self.urlParams, self.hash, self.headers, self.body);
});
/**
* Appends a URL segment to the request URL, inserting or trimming one slash as needed.
*
* @category combinators
* @since 4.0.0
*/
export const appendUrl = /*#__PURE__*/dual(2, (self, path) => {
if (path === "") return self;
return makeWith(self.method, joinSegments(self.url, path), self.urlParams, self.hash, self.headers, self.body);
});
const joinSegments = (first, second) => {
const endsWithSlash = first.endsWith("/");
const startsWithSlash = second.startsWith("/");
const needsTrim = endsWithSlash && startsWithSlash;
const needsSlash = !endsWithSlash && !startsWithSlash;
return needsTrim ? first + second.slice(1) : needsSlash ? first + "/" + second : first + second;
};
/**
* Updates the request URL by applying a function to the current URL string.
*
* @category combinators
* @since 4.0.0
*/
export const updateUrl = /*#__PURE__*/dual(2, (self, f) => makeWith(self.method, f(self.url), self.urlParams, self.hash, self.headers, self.body));
/**
* Sets one query parameter, replacing existing values for that parameter name.
*
* @category combinators
* @since 4.0.0
*/
export const setUrlParam = /*#__PURE__*/dual(3, (self, key, value) => makeWith(self.method, self.url, UrlParams.set(self.urlParams, key, value), self.hash, self.headers, self.body));
/**
* Sets query parameters from an input collection, replacing existing values for matching names.
*
* @category combinators
* @since 4.0.0
*/
export const setUrlParams = /*#__PURE__*/dual(2, (self, input) => makeWith(self.method, self.url, UrlParams.setAll(self.urlParams, input), self.hash, self.headers, self.body));
/**
* Appends one query parameter value without removing existing values for the same name.
*
* @category combinators
* @since 4.0.0
*/
export const appendUrlParam = /*#__PURE__*/dual(3, (self, key, value) => makeWith(self.method, self.url, UrlParams.append(self.urlParams, key, value), self.hash, self.headers, self.body));
/**
* Appends query parameters from an input collection without removing existing values for matching names.
*
* @category combinators
* @since 4.0.0
*/
export const appendUrlParams = /*#__PURE__*/dual(2, (self, input) => makeWith(self.method, self.url, UrlParams.appendAll(self.urlParams, input), self.hash, self.headers, self.body));
/**
* Sets the URL fragment on a request without the leading `#`.
*
* @category combinators
* @since 4.0.0
*/
export const setHash = /*#__PURE__*/dual(2, (self, hash) => makeWith(self.method, self.url, self.urlParams, Option.some(hash), self.headers, self.body));
/**
* Removes the URL fragment from a request.
*
* @category combinators
* @since 4.0.0
*/
export const removeHash = self => makeWith(self.method, self.url, self.urlParams, Option.none(), self.headers, self.body);
/**
* Sets the request body and updates `Content-Type` and `Content-Length` headers from the body metadata when available.
*
* @category combinators
* @since 4.0.0
*/
export const setBody = /*#__PURE__*/dual(2, (self, body) => {
let headers = self.headers;
if (body._tag === "Empty" || body._tag === "FormData") {
headers = Headers.remove(Headers.remove(headers, "Content-Type"), "Content-length");
} else {
if (body.contentType) {
headers = Headers.set(headers, "content-type", body.contentType);
}
if (body.contentLength !== undefined) {
headers = Headers.set(headers, "content-length", body.contentLength.toString());
}
}
return makeWith(self.method, self.url, self.urlParams, self.hash, headers, body);
});
/**
* Sets a `Uint8Array` request body with an optional content type.
*
* @category combinators
* @since 4.0.0
*/
export const bodyUint8Array = /*#__PURE__*/dual(args => isHttpClientRequest(args[0]), (self, body, contentType) => setBody(self, HttpBody.uint8Array(body, contentType)));
/**
* Sets a text request body with an optional content type.
*
* @category combinators
* @since 4.0.0
*/
export const bodyText = /*#__PURE__*/dual(args => isHttpClientRequest(args[0]), (self, body, contentType) => setBody(self, HttpBody.text(body, contentType)));
/**
* Encodes a value as a JSON request body and sets it on the request, failing with `HttpBodyError` if encoding fails.
*
* @category combinators
* @since 4.0.0
*/
export const bodyJson = /*#__PURE__*/dual(2, (self, body) => Effect.map(HttpBody.json(body), body => setBody(self, body)));
/**
* Sets a JSON request body using unsafe JSON encoding.
*
* **When to use**
*
* Use when the request body is known to be JSON-serializable and a synchronous
* `HttpClientRequest` result is needed.
*
* **Gotchas**
*
* JSON encoding may throw instead of failing in the Effect error channel.
*
* @category combinators
* @since 4.0.0
*/
export const bodyJsonUnsafe = /*#__PURE__*/dual(2, (self, body) => setBody(self, HttpBody.jsonUnsafe(body)));
/**
* Creates a schema-based JSON body encoder that sets the encoded value on a request.
*
* @category combinators
* @since 4.0.0
*/
export const schemaBodyJson = (schema, options) => {
const encode = HttpBody.jsonSchema(schema, options);
return dual(2, (self, body) => Effect.map(encode(body), body => setBody(self, body)));
};
/**
* Sets an `application/x-www-form-urlencoded` request body from URL parameter input.
*
* @category combinators
* @since 4.0.0
*/
export const bodyUrlParams = /*#__PURE__*/dual(2, (self, input) => setBody(self, HttpBody.urlParams(UrlParams.fromInput(input))));
/**
* Sets a `FormData` request body.
*
* @category combinators
* @since 4.0.0
*/
export const bodyFormData = /*#__PURE__*/dual(2, (self, body) => setBody(self, HttpBody.formData(body)));
/**
* Creates a `FormData` request body from record-style entries and sets it on the request.
*
* @category combinators
* @since 4.0.0
*/
export const bodyFormDataRecord = /*#__PURE__*/dual(2, (self, entries) => setBody(self, HttpBody.formDataRecord(entries)));
/**
* Sets a streaming `Uint8Array` request body with optional content type and content length metadata.
*
* @category combinators
* @since 4.0.0
*/
export const bodyStream = /*#__PURE__*/dual(args => isHttpClientRequest(args[0]), (self, body, options) => setBody(self, HttpBody.stream(body, options?.contentType, options?.contentLength)));
/**
* Creates a file-backed request body from a filesystem path and sets it on the request.
*
* @category combinators
* @since 4.0.0
*/
export const bodyFile = /*#__PURE__*/dual(args => isHttpClientRequest(args[0]), (self, path, options) => Effect.map(HttpBody.file(path, options), body => setBody(self, body)));
/**
* Builds a `URL` from the request URL, query parameters, and hash, returning `Option.none()` if the URL is invalid.
*
* @category combinators
* @since 4.0.0
*/
export function toUrl(self) {
const r = UrlParams.makeUrl(self.url, self.urlParams, Option.getOrUndefined(self.hash));
if (Result.isSuccess(r)) {
return Option.some(r.success);
}
return Option.none();
}
/**
* Converts a Web `Request` into an `HttpClientRequest`, preserving method, URL, headers, and supported request bodies.
*
* @category converting
* @since 4.0.0
*/
export const fromWeb = request => {
const method = request.method.toUpperCase();
return modify(empty, {
method,
url: new URL(request.url),
headers: request.headers,
body: fromWebBody(request, method)
});
};
const fromWebBody = (request, method) => {
if (!hasBody(method) || request.body === null) {
return HttpBody.empty;
}
return HttpBody.raw(request.body, {
contentType: request.headers.get("content-type") ?? undefined,
contentLength: parseContentLength(request.headers.get("content-length"))
});
};
const parseContentLength = contentLength => {
if (contentLength === null) {
return undefined;
}
const parsed = Number.parseInt(contentLength, 10);
return Number.isNaN(parsed) ? undefined : parsed;
};
/**
* Converts an `HttpClientRequest` safely to a Web `Request` as a `Result`, failing when the request URL is invalid.
*
* @category converting
* @since 4.0.0
*/
export const toWebResult = (self, options) => {
const url = UrlParams.makeUrl(self.url, self.urlParams, Option.getOrUndefined(self.hash));
if (Result.isFailure(url)) {
return Result.fail(url.failure);
}
const requestInit = {
method: self.method,
headers: self.headers
};
if (options?.signal) {
requestInit.signal = options.signal;
}
if (hasBody(self.method)) {
switch (self.body._tag) {
case "Empty":
{
break;
}
case "Raw":
{
requestInit.body = self.body.body;
if (isReadableStream(self.body.body)) {
;
requestInit.duplex = "half";
}
break;
}
case "Uint8Array":
{
requestInit.body = self.body.body;
break;
}
case "FormData":
{
requestInit.body = self.body.formData;
break;
}
case "Stream":
{
requestInit.body = Stream.toReadableStreamWith(self.body.stream, options?.context ?? Context.empty());
requestInit.duplex = "half";
break;
}
}
}
return Result.try({
try: () => new Request(url.success, requestInit),
catch: cause => new UrlParams.UrlParamsError({
cause
})
});
};
const isReadableStream = u => typeof ReadableStream !== "undefined" && u instanceof ReadableStream;
/**
* Converts an `HttpClientRequest` to a Web `Request`, failing with `UrlParamsError` when the request URL is invalid.
*
* @category converting
* @since 4.0.0
*/
export const toWeb = (self, options) => Effect.contextWith(context => Effect.fromResult(toWebResult(self, {
context: context,
signal: options?.signal
})));
//# sourceMappingURL=HttpClientRequest.js.map

Xet Storage Details

Size:
17.4 kB
·
Xet hash:
671538866e8cf7e42794e6ba3637239901cf6b05e7fe67e715c36b165a4708ba

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.