| /** | |
| * Describes immutable responses returned by Effect HTTP handlers. | |
| * | |
| * An `HttpServerResponse` stores the status, optional status text, headers, | |
| * cookies, and body that the server runtime later turns into a platform | |
| * response such as a Web `Response`. This module includes constructors for | |
| * common response bodies, helpers for updating response data, file response | |
| * support through `HttpPlatform`, and conversions to or from Web and Effect | |
| * HTTP client responses. | |
| * | |
| * @since 4.0.0 | |
| */ | |
| import * as Context from "../../Context.js"; | |
| import * as Effect from "../../Effect.js"; | |
| import * as ErrorReporter from "../../ErrorReporter.js"; | |
| import { dual } from "../../Function.js"; | |
| import * as Inspectable from "../../Inspectable.js"; | |
| import { PipeInspectableProto } from "../../internal/core.js"; | |
| import * as Option from "../../Option.js"; | |
| import { pipeArguments } from "../../Pipeable.js"; | |
| import { hasProperty } from "../../Predicate.js"; | |
| import { redact } from "../../Redactable.js"; | |
| import * as Stream from "../../Stream.js"; | |
| import * as Cookies from "./Cookies.js"; | |
| import * as Headers from "./Headers.js"; | |
| import * as Body from "./HttpBody.js"; | |
| import * as HttpClientError from "./HttpClientError.js"; | |
| import * as HttpClientRequest from "./HttpClientRequest.js"; | |
| import * as HttpClientResponse from "./HttpClientResponse.js"; | |
| import * as HttpIncomingMessage from "./HttpIncomingMessage.js"; | |
| import * as Template from "./Template.js"; | |
| import * as UrlParams from "./UrlParams.js"; | |
| const TypeId = "~effect/http/HttpServerResponse"; | |
| /** | |
| * Returns `true` when the supplied value is an `HttpServerResponse`. | |
| * | |
| * @category guards | |
| * @since 4.0.0 | |
| */ | |
| export const isHttpServerResponse = u => hasProperty(u, TypeId); | |
| /** | |
| * Creates an empty HTTP response. | |
| * | |
| * **Details** | |
| * | |
| * The default status is `204`. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const empty = options => makeResponse({ | |
| status: options?.status ?? 204, | |
| statusText: options?.statusText, | |
| headers: options?.headers ? Headers.fromInput(options.headers) : undefined, | |
| cookies: options?.cookies | |
| }); | |
| /** | |
| * Creates a redirect response with a `Location` header. | |
| * | |
| * **Details** | |
| * | |
| * The default status is `302`; custom headers are merged with the generated | |
| * `Location` header. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const redirect = (location, options) => { | |
| const headers = Headers.fromRecordUnsafe({ | |
| location: location.toString() | |
| }); | |
| return makeResponse({ | |
| status: options?.status ?? 302, | |
| statusText: options?.statusText, | |
| headers: options?.headers ? Headers.merge(headers, Headers.fromInput(options.headers)) : headers, | |
| cookies: options?.cookies ?? Cookies.empty | |
| }); | |
| }; | |
| /** | |
| * Creates an HTTP response whose body is a `Uint8Array`. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const uint8Array = (body, options) => { | |
| const headers = options?.headers ? Headers.fromInput(options.headers) : Headers.empty; | |
| return makeResponse({ | |
| status: options?.status ?? 200, | |
| statusText: options?.statusText, | |
| headers, | |
| cookies: options?.cookies ?? Cookies.empty, | |
| body: Body.uint8Array(body, getContentType(options, headers)) | |
| }); | |
| }; | |
| const getContentType = (options, headers) => { | |
| if (options?.contentType) { | |
| return options.contentType; | |
| } else if (options?.headers) { | |
| return headers["content-type"]; | |
| } | |
| }; | |
| /** | |
| * Creates an HTTP response whose body is a string. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const text = (body, options) => { | |
| const headers = options?.headers ? Headers.fromInput(options.headers) : Headers.empty; | |
| return makeResponse({ | |
| status: options?.status ?? 200, | |
| statusText: options?.statusText, | |
| headers, | |
| cookies: options?.cookies ?? Cookies.empty, | |
| body: Body.text(body, getContentType(options, headers)) | |
| }); | |
| }; | |
| /** | |
| * Creates an HTML response with the `text/html` content type. | |
| * | |
| * **Details** | |
| * | |
| * Passing a string returns a response directly. Using it as a template tag returns | |
| * an effect so interpolated values can be rendered with their required services | |
| * and errors. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const html = (strings, ...args) => { | |
| if (typeof strings === "string") { | |
| return text(strings, { | |
| contentType: "text/html" | |
| }); | |
| } | |
| return Effect.map(Template.make(strings, ...args), _ => text(_, { | |
| contentType: "text/html" | |
| })); | |
| }; | |
| /** | |
| * Creates a streaming HTML response from a template. | |
| * | |
| * **Details** | |
| * | |
| * The template is encoded as a byte stream and can use streaming interpolated | |
| * values from the current context. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const htmlStream = (strings, ...args) => Effect.map(Effect.context(), context => stream(Stream.provideContext(Stream.encodeText(Template.stream(strings, ...args)), context), { | |
| contentType: "text/html" | |
| })); | |
| /** | |
| * Creates a JSON HTTP response. | |
| * | |
| * **Details** | |
| * | |
| * The body is serialized with `JSON.stringify`; serialization errors are captured | |
| * as `HttpBodyError` failures. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const json = (body, options) => { | |
| const headers = options?.headers ? Headers.fromInput(options.headers) : Headers.empty; | |
| return Effect.map(Body.json(body, getContentType(options, headers)), body => makeResponse({ | |
| status: options?.status ?? 200, | |
| statusText: options?.statusText, | |
| headers, | |
| cookies: options?.cookies, | |
| body | |
| })); | |
| }; | |
| /** | |
| * Creates a JSON response constructor backed by a schema encoder. | |
| * | |
| * **Details** | |
| * | |
| * The returned function encodes the value with the supplied schema before | |
| * serializing it as JSON, and can fail with `HttpBodyError` if schema encoding or | |
| * JSON serialization fails. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const schemaJson = (schema, options) => { | |
| const encode = Body.jsonSchema(schema, options); | |
| return (body, options) => { | |
| const headers = options?.headers ? Headers.fromInput(options.headers) : Headers.empty; | |
| return Effect.map(encode(body, getContentType(options, headers)), body => makeResponse({ | |
| status: options?.status ?? 200, | |
| statusText: options?.statusText, | |
| headers, | |
| cookies: options?.cookies, | |
| body | |
| })); | |
| }; | |
| }; | |
| /** | |
| * Creates a JSON HTTP response synchronously. | |
| * | |
| * **When to use** | |
| * | |
| * Use when the response body is known to be JSON-serializable and you need a | |
| * synchronous `HttpServerResponse`. | |
| * | |
| * **Gotchas** | |
| * | |
| * Unlike `json`, serialization errors from `JSON.stringify` are not captured in | |
| * `Effect`. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const jsonUnsafe = (body, options) => { | |
| const headers = options?.headers ? Headers.fromInput(options.headers) : Headers.empty; | |
| return makeResponse({ | |
| status: options?.status ?? 200, | |
| statusText: options?.statusText, | |
| headers, | |
| cookies: options?.cookies, | |
| body: Body.jsonUnsafe(body, getContentType(options, headers)) | |
| }); | |
| }; | |
| /** | |
| * Creates a response from URL parameters using the | |
| * `application/x-www-form-urlencoded` content type by default. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const urlParams = (body, options) => { | |
| const headers = options?.headers ? Headers.fromInput(options.headers) : Headers.empty; | |
| return makeResponse({ | |
| status: options?.status ?? 200, | |
| statusText: options?.statusText, | |
| headers, | |
| cookies: options?.cookies, | |
| body: Body.text(UrlParams.toString(UrlParams.fromInput(body)), getContentType(options, headers) ?? "application/x-www-form-urlencoded") | |
| }); | |
| }; | |
| /** | |
| * Creates a response with a raw body value. | |
| * | |
| * **When to use** | |
| * | |
| * Use when you want to pass through a body value already understood by the | |
| * underlying runtime, such as a Web `Response`, `Blob`, or `ReadableStream`, | |
| * for later platform conversion. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const raw = (body, options) => makeResponse({ | |
| status: options?.status ?? 200, | |
| statusText: options?.statusText, | |
| headers: options?.headers && Headers.fromInput(options.headers), | |
| cookies: options?.cookies, | |
| body: Body.raw(body, { | |
| contentType: options?.contentType, | |
| contentLength: options?.contentLength | |
| }) | |
| }); | |
| /** | |
| * Creates a response whose body is a Web `FormData` value. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const formData = (body, options) => makeResponse({ | |
| status: options?.status ?? 200, | |
| statusText: options?.statusText, | |
| headers: options?.headers && Headers.fromInput(options.headers), | |
| cookies: options?.cookies, | |
| body: Body.formData(body) | |
| }); | |
| /** | |
| * Creates a streaming response from a stream of byte chunks. | |
| * | |
| * **Details** | |
| * | |
| * Optional response metadata can supply the status, headers, content type, and | |
| * content length. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const stream = (body, options) => { | |
| const headers = options?.headers ? Headers.fromInput(options.headers) : Headers.empty; | |
| return makeResponse({ | |
| status: options?.status ?? 200, | |
| statusText: options?.statusText, | |
| headers, | |
| cookies: options?.cookies, | |
| body: Body.stream(body, getContentType(options, headers), options?.contentLength) | |
| }); | |
| }; | |
| const HttpPlatformKey = /*#__PURE__*/Context.Service("effect/http/HttpPlatform"); | |
| /** | |
| * Creates a streamed file response for a file system path. | |
| * | |
| * **Details** | |
| * | |
| * The effect requires `HttpPlatform`, can fail with a platform error, and supports | |
| * options for status, headers, offset, and byte range. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const file = (path, options) => Effect.flatMap(HttpPlatformKey, platform => platform.fileResponse(path, options)); | |
| /** | |
| * Creates a streamed file response for a Web `File`-like value. | |
| * | |
| * **Details** | |
| * | |
| * The effect requires `HttpPlatform` and supports options for status, headers, | |
| * offset, and byte range. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const fileWeb = (file, options) => Effect.flatMap(HttpPlatformKey, platform => platform.fileWebResponse(file, options)); | |
| /** | |
| * Returns a response with the specified header set to the supplied value. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const setHeader = /*#__PURE__*/dual(3, (self, key, value) => makeResponse({ | |
| ...self, | |
| headers: Headers.set(self.headers, key, value) | |
| })); | |
| /** | |
| * Returns a response with all supplied headers set on the existing header map. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const setHeaders = /*#__PURE__*/dual(2, (self, input) => makeResponse({ | |
| ...self, | |
| headers: Headers.setAll(self.headers, input) | |
| })); | |
| /** | |
| * Returns a response with the cookie of the specified name removed. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const removeCookie = /*#__PURE__*/dual(2, (self, name) => makeResponse({ | |
| ...self, | |
| cookies: Cookies.remove(self.cookies, name) | |
| })); | |
| /** | |
| * Returns a response with its cookie collection replaced by the supplied cookies. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const replaceCookies = /*#__PURE__*/dual(2, (self, cookies) => makeResponse({ | |
| ...self, | |
| cookies | |
| })); | |
| /** | |
| * Sets a cookie on the response. | |
| * | |
| * **Details** | |
| * | |
| * The effect fails with `CookiesError` if the cookie name, value, or options are | |
| * invalid. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const setCookie = /*#__PURE__*/dual(args => isHttpServerResponse(args[0]), (self, name, value, options) => Effect.map(Effect.fromResult(Cookies.set(self.cookies, name, value, options)), cookies => makeResponse({ | |
| ...self, | |
| cookies | |
| }))); | |
| /** | |
| * Sets an expired cookie on an `HttpServerResponse`. | |
| * | |
| * **Details** | |
| * | |
| * Returns an effect because cookie encoding can fail. The original response is not | |
| * mutated; the effect succeeds with a response containing the updated cookie set. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const expireCookie = /*#__PURE__*/dual(args => isHttpServerResponse(args[0]), (self, name, options) => Effect.map(Effect.fromResult(Cookies.expireCookie(self.cookies, name, options)), cookies => makeResponse({ | |
| ...self, | |
| cookies | |
| }))); | |
| /** | |
| * Sets a cookie on an `HttpServerResponse`, throwing if the cookie cannot be | |
| * encoded. | |
| * | |
| * **When to use** | |
| * | |
| * Use when you need to set one trusted cookie and want encoding failures to | |
| * throw instead of being represented as `CookiesError` failures. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const setCookieUnsafe = /*#__PURE__*/dual(args => isHttpServerResponse(args[0]), (self, name, value, options) => makeResponse({ | |
| ...self, | |
| cookies: Cookies.setUnsafe(self.cookies, name, value, options) | |
| })); | |
| /** | |
| * Sets an expired cookie on an `HttpServerResponse`, throwing if the expiration cookie | |
| * cannot be encoded. | |
| * | |
| * **When to use** | |
| * | |
| * Use when you need to expire one trusted cookie and want encoding failures to | |
| * throw instead of being represented as `CookiesError` failures. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const expireCookieUnsafe = /*#__PURE__*/dual(args => isHttpServerResponse(args[0]), (self, name, options) => makeResponse({ | |
| ...self, | |
| cookies: Cookies.expireCookieUnsafe(self.cookies, name, options) | |
| })); | |
| /** | |
| * Updates the cookies attached to an `HttpServerResponse` using the supplied | |
| * function. | |
| * | |
| * **Details** | |
| * | |
| * The original response is not mutated; a new response is returned with the | |
| * callback result as its cookie collection. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const updateCookies = /*#__PURE__*/dual(2, (self, f) => makeResponse({ | |
| ...self, | |
| cookies: f(self.cookies) | |
| })); | |
| /** | |
| * Merges additional cookies into the cookies attached to an | |
| * `HttpServerResponse`. | |
| * | |
| * **Details** | |
| * | |
| * The original response is not mutated; a new response is returned with the merged | |
| * cookie collection. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const mergeCookies = /*#__PURE__*/dual(2, (self, cookies) => makeResponse({ | |
| ...self, | |
| cookies: Cookies.merge(self.cookies, cookies) | |
| })); | |
| /** | |
| * Sets multiple cookies on an `HttpServerResponse`. | |
| * | |
| * **Details** | |
| * | |
| * Each input entry contains a cookie name, value, and optional cookie options. The | |
| * returned effect fails with `CookiesError` if any cookie cannot be encoded. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const setCookies = /*#__PURE__*/dual(2, (self, cookies) => Effect.map(Effect.fromResult(Cookies.setAll(self.cookies, cookies)), cookies => makeResponse({ | |
| ...self, | |
| cookies | |
| }))); | |
| /** | |
| * Sets multiple cookies on an `HttpServerResponse`, throwing if any cookie cannot | |
| * be encoded. | |
| * | |
| * **When to use** | |
| * | |
| * Use when you need to set multiple trusted cookies and want encoding failures | |
| * to throw instead of being represented as `CookiesError` failures. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const setCookiesUnsafe = /*#__PURE__*/dual(2, (self, cookies) => makeResponse({ | |
| ...self, | |
| cookies: Cookies.setAllUnsafe(self.cookies, cookies) | |
| })); | |
| /** | |
| * Replaces the body of an `HttpServerResponse`. | |
| * | |
| * **Details** | |
| * | |
| * When the body carries a content type or content length, the returned response | |
| * includes the corresponding headers. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const setBody = /*#__PURE__*/dual(2, (self, body) => makeResponse({ | |
| ...self, | |
| body | |
| })); | |
| /** | |
| * Sets the HTTP status code of an `HttpServerResponse`. | |
| * | |
| * **Details** | |
| * | |
| * When `statusText` is omitted, the existing status text is preserved. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const setStatus = /*#__PURE__*/dual(args => isHttpServerResponse(args[0]), (self, status, statusText) => makeResponse({ | |
| ...self, | |
| status, | |
| statusText: statusText ?? self.statusText | |
| })); | |
| /** | |
| * Converts an `HttpServerResponse` to a Web `Response`. | |
| * | |
| * **Details** | |
| * | |
| * Cookies are appended as `Set-Cookie` headers. Stream bodies are converted using | |
| * the supplied context, and `withoutBody` can be used for responses such as HEAD | |
| * responses. | |
| * | |
| * @category converting | |
| * @since 4.0.0 | |
| */ | |
| export const toWeb = (response, options) => { | |
| const headers = new globalThis.Headers(response.headers); | |
| if (!Cookies.isEmpty(response.cookies)) { | |
| const toAdd = Cookies.toSetCookieHeaders(response.cookies); | |
| for (const header of toAdd) { | |
| headers.append("set-cookie", header); | |
| } | |
| } | |
| if (options?.withoutBody) { | |
| return new Response(undefined, { | |
| status: response.status, | |
| statusText: response.statusText, | |
| headers | |
| }); | |
| } | |
| const body = response.body; | |
| switch (body._tag) { | |
| case "Empty": | |
| { | |
| return new Response(undefined, { | |
| status: response.status, | |
| statusText: response.statusText, | |
| headers | |
| }); | |
| } | |
| case "Uint8Array": | |
| case "Raw": | |
| { | |
| if (body.body instanceof Response) { | |
| for (const [key, value] of headers) { | |
| body.body.headers.set(key, value); | |
| } | |
| return body.body; | |
| } | |
| return new Response(body.body, { | |
| status: response.status, | |
| statusText: response.statusText, | |
| headers | |
| }); | |
| } | |
| case "FormData": | |
| { | |
| return new Response(body.formData, { | |
| status: response.status, | |
| statusText: response.statusText, | |
| headers | |
| }); | |
| } | |
| case "Stream": | |
| { | |
| return new Response(Stream.toReadableStreamWith(body.stream, options?.context ?? Context.empty()), { | |
| status: response.status, | |
| statusText: response.statusText, | |
| headers | |
| }); | |
| } | |
| } | |
| }; | |
| /** | |
| * Wraps an `HttpServerResponse` as an `HttpClientResponse`. | |
| * | |
| * **Details** | |
| * | |
| * An optional request can be supplied for client-response metadata and decode | |
| * errors. | |
| * | |
| * @category converting | |
| * @since 4.0.0 | |
| */ | |
| export const toClientResponse = (response, options) => new ServerHttpClientResponse(options?.request ?? HttpClientRequest.empty, response); | |
| class ServerHttpClientResponse extends Inspectable.Class { | |
| [HttpIncomingMessage.TypeId]; | |
| [HttpClientResponse.TypeId]; | |
| request; | |
| response; | |
| constructor(request, response) { | |
| super(); | |
| this.request = request; | |
| this.response = response; | |
| this[HttpIncomingMessage.TypeId] = HttpIncomingMessage.TypeId; | |
| this[HttpClientResponse.TypeId] = HttpClientResponse.TypeId; | |
| } | |
| toJSON() { | |
| return HttpIncomingMessage.inspect(this, { | |
| _id: "HttpClientResponse", | |
| request: this.request.toJSON(), | |
| status: this.status | |
| }); | |
| } | |
| get status() { | |
| return this.response.status; | |
| } | |
| cachedHeaders; | |
| get headers() { | |
| return this.cachedHeaders ??= this.response.body._tag === "FormData" ? Headers.merge(this.response.headers, Headers.fromInput(this.getFormDataResponse().headers)) : this.response.headers; | |
| } | |
| get cookies() { | |
| return this.response.cookies; | |
| } | |
| get remoteAddress() { | |
| return Option.none(); | |
| } | |
| get stream() { | |
| const body = this.response.body; | |
| switch (body._tag) { | |
| case "Empty": | |
| { | |
| return Stream.empty; | |
| } | |
| case "Stream": | |
| { | |
| return Stream.mapError(body.stream, cause => this.decodeError(cause)); | |
| } | |
| case "Uint8Array": | |
| { | |
| return Stream.succeed(body.body); | |
| } | |
| case "Raw": | |
| { | |
| const rawBody = body.body; | |
| if (rawBody instanceof Response) { | |
| return rawBody.body ? Stream.fromReadableStream({ | |
| evaluate: () => rawBody.body, | |
| onError: cause => this.decodeError(cause) | |
| }) : Stream.empty; | |
| } | |
| if (isReadableStream(rawBody)) { | |
| return Stream.fromReadableStream({ | |
| evaluate: () => rawBody, | |
| onError: cause => this.decodeError(cause) | |
| }); | |
| } | |
| if (rawBody instanceof Blob) { | |
| return Stream.fromReadableStream({ | |
| evaluate: () => rawBody.stream(), | |
| onError: cause => this.decodeError(cause) | |
| }); | |
| } | |
| return Stream.unwrap(Effect.map(this.bytes, Stream.succeed)); | |
| } | |
| case "FormData": | |
| { | |
| const response = this.getFormDataResponse(); | |
| return Stream.fromReadableStream({ | |
| evaluate: () => response.body, | |
| onError: cause => this.decodeError(cause) | |
| }); | |
| } | |
| } | |
| } | |
| get json() { | |
| return Effect.flatMap(this.text, text => Effect.try({ | |
| try: () => text === "" ? null : JSON.parse(text), | |
| catch: cause => new HttpClientError.HttpClientError({ | |
| reason: new HttpClientError.DecodeError({ | |
| request: this.request, | |
| response: this, | |
| cause | |
| }) | |
| }) | |
| })); | |
| } | |
| get bytes() { | |
| const body = this.response.body; | |
| switch (body._tag) { | |
| case "Empty": | |
| { | |
| return Effect.succeed(new Uint8Array(0)); | |
| } | |
| case "Uint8Array": | |
| { | |
| return Effect.succeed(body.body); | |
| } | |
| case "Stream": | |
| { | |
| return Stream.mkUint8Array(this.stream); | |
| } | |
| case "Raw": | |
| { | |
| const rawBody = body.body; | |
| if (rawBody instanceof Response) { | |
| return Effect.tryPromise({ | |
| try: () => rawBody.arrayBuffer().then(buffer => new Uint8Array(buffer)), | |
| catch: cause => this.decodeError(cause) | |
| }); | |
| } | |
| return Effect.tryPromise({ | |
| try: () => new Response(rawBody).arrayBuffer().then(buffer => new Uint8Array(buffer)), | |
| catch: cause => this.decodeError(cause) | |
| }); | |
| } | |
| case "FormData": | |
| { | |
| return Effect.tryPromise({ | |
| try: () => new Response(body.formData).arrayBuffer().then(buffer => new Uint8Array(buffer)), | |
| catch: cause => this.decodeError(cause) | |
| }); | |
| } | |
| } | |
| } | |
| get text() { | |
| return Effect.map(this.bytes, bytes => textDecoder.decode(bytes)); | |
| } | |
| get urlParamsBody() { | |
| return Effect.flatMap(this.text, _ => Effect.try({ | |
| try: () => UrlParams.fromInput(new URLSearchParams(_)), | |
| catch: cause => new HttpClientError.HttpClientError({ | |
| reason: new HttpClientError.DecodeError({ | |
| request: this.request, | |
| response: this, | |
| cause | |
| }) | |
| }) | |
| })); | |
| } | |
| get formData() { | |
| const body = this.response.body; | |
| if (body._tag === "FormData") { | |
| return Effect.succeed(body.formData); | |
| } | |
| return Effect.contextWith(context => { | |
| const readableStream = Stream.toReadableStreamWith(this.stream, context); | |
| return Effect.tryPromise({ | |
| try: () => new Response(readableStream, { | |
| headers: this.headers | |
| }).formData(), | |
| catch: cause => this.decodeError(cause) | |
| }); | |
| }); | |
| } | |
| get arrayBuffer() { | |
| return Effect.map(this.bytes, bytes => bytes.slice().buffer); | |
| } | |
| decodeError(cause) { | |
| return new HttpClientError.HttpClientError({ | |
| reason: new HttpClientError.DecodeError({ | |
| request: this.request, | |
| response: this, | |
| cause | |
| }) | |
| }); | |
| } | |
| formDataResponse; | |
| getFormDataResponse() { | |
| return this.formDataResponse ??= new Response(this.response.body.formData); | |
| } | |
| pipe() { | |
| return pipeArguments(this, arguments); | |
| } | |
| } | |
| const textDecoder = /*#__PURE__*/new TextDecoder(); | |
| /** | |
| * Converts an `HttpClientResponse` to an `HttpServerResponse`. | |
| * | |
| * **Details** | |
| * | |
| * The response body is streamed from the client response. `Set-Cookie` headers are | |
| * removed from the header map and represented in the response cookie collection. | |
| * | |
| * @category converting | |
| * @since 4.0.0 | |
| */ | |
| export const fromClientResponse = response => { | |
| const headers = Headers.remove(response.headers, "set-cookie"); | |
| return makeResponse({ | |
| status: response.status, | |
| headers, | |
| cookies: response.cookies, | |
| body: Body.stream(Stream.catchIf(response.stream, isEmptyBodyError, () => Stream.empty), Option.getOrUndefined(Headers.get(headers, "content-type")), getContentLength(headers)) | |
| }); | |
| }; | |
| const isReadableStream = u => typeof ReadableStream !== "undefined" && u instanceof ReadableStream; | |
| const isEmptyBodyError = error => HttpClientError.isHttpClientError(error) && error.reason._tag === "EmptyBodyError"; | |
| const getContentLength = headers => { | |
| const contentLength = Option.getOrUndefined(Headers.get(headers, "content-length")); | |
| if (contentLength === undefined) { | |
| return undefined; | |
| } | |
| const parsed = Number(contentLength); | |
| return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined; | |
| }; | |
| const Proto = { | |
| ...PipeInspectableProto, | |
| [TypeId]: TypeId, | |
| [ErrorReporter.ignore]: true, | |
| toJSON() { | |
| return { | |
| _id: "HttpServerResponse", | |
| status: this.status, | |
| statusText: this.statusText, | |
| headers: redact(this.headers), | |
| cookies: this.cookies.toJSON(), | |
| body: this.body.toJSON() | |
| }; | |
| } | |
| }; | |
| const makeResponse = options => { | |
| const self = Object.create(Proto); | |
| self.status = options.status; | |
| self.statusText = options.statusText; | |
| self.cookies = options.cookies ?? Cookies.empty; | |
| self.body = options.body ?? Body.empty; | |
| if (self.body._tag !== "Empty" && (self.body.contentType || self.body.contentLength)) { | |
| const newHeaders = Headers.fromRecordUnsafe({ | |
| ...options.headers | |
| }); | |
| if (self.body.contentType) { | |
| newHeaders["content-type"] = self.body.contentType; | |
| } | |
| if (self.body.contentLength) { | |
| newHeaders["content-length"] = self.body.contentLength.toString(); | |
| } | |
| self.headers = newHeaders; | |
| } else { | |
| self.headers = options.headers ?? Headers.empty; | |
| } | |
| return self; | |
| }; | |
| /** | |
| * Converts a Web `Response` to an `HttpServerResponse`. | |
| * | |
| * **Details** | |
| * | |
| * `Set-Cookie` headers are parsed into the response cookie collection and removed | |
| * from the header map. A present Web body is exposed as a stream body. | |
| * | |
| * @category converting | |
| * @since 4.0.0 | |
| */ | |
| export const fromWeb = response => { | |
| const headers = new globalThis.Headers(response.headers); | |
| const setCookieHeaders = headers.getSetCookie(); | |
| headers.delete("set-cookie"); | |
| let self = empty({ | |
| status: response.status, | |
| statusText: response.statusText, | |
| headers: headers, | |
| cookies: Cookies.fromSetCookie(setCookieHeaders) | |
| }); | |
| if (response.body) { | |
| const contentType = response.headers.get("content-type"); | |
| self = setBody(self, Body.stream(Stream.fromReadableStream({ | |
| evaluate: () => response.body, | |
| onError: e => e | |
| }), contentType ?? undefined)); | |
| } | |
| return self; | |
| }; | |
| //# sourceMappingURL=HttpServerResponse.js.map |
Xet Storage Details
- Size:
- 26.5 kB
- Xet hash:
- ff41fd65799a2efdeefa07e68929d9592282486fcad1703d9410c04c3ff338ce
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.