| /** | |
| * Represents responses returned by the Effect HTTP client. | |
| * | |
| * An `HttpClientResponse` keeps the original request together with the response | |
| * status, headers, cookies, and body accessors from the shared incoming-message | |
| * model. This module includes constructors, schema-based decoders, helpers for | |
| * streaming response bodies, and utilities for matching or filtering by HTTP | |
| * status. | |
| * | |
| * @since 4.0.0 | |
| */ | |
| import * as Effect from "../../Effect.js"; | |
| import { dual } from "../../Function.js"; | |
| import * as Inspectable from "../../Inspectable.js"; | |
| import * as Option from "../../Option.js"; | |
| import { pipeArguments } from "../../Pipeable.js"; | |
| import * as Schema from "../../Schema.js"; | |
| import * as Stream from "../../Stream.js"; | |
| import * as Cookies from "./Cookies.js"; | |
| import * as Headers from "./Headers.js"; | |
| import * as Error from "./HttpClientError.js"; | |
| import * as HttpIncomingMessage from "./HttpIncomingMessage.js"; | |
| import * as UrlParams from "./UrlParams.js"; | |
| export { | |
| /** | |
| * Creates a decoder that reads a response JSON body and decodes it with the supplied schema. | |
| * | |
| * @category schemas | |
| * @since 4.0.0 | |
| */ | |
| schemaBodyJson, | |
| /** | |
| * Creates a decoder that reads response URL-encoded body parameters and decodes them with the supplied schema. | |
| * | |
| * @category schemas | |
| * @since 4.0.0 | |
| */ | |
| schemaBodyUrlParams, | |
| /** | |
| * Creates a decoder that validates and decodes response headers with the supplied schema. | |
| * | |
| * @category schemas | |
| * @since 4.0.0 | |
| */ | |
| schemaHeaders } from "./HttpIncomingMessage.js"; | |
| /** | |
| * Type identifier for `HttpClientResponse` values. | |
| * | |
| * @category type IDs | |
| * @since 4.0.0 | |
| */ | |
| export const TypeId = "~effect/http/HttpClientResponse"; | |
| /** | |
| * Wraps a Web `Response` and its original `HttpClientRequest` as an `HttpClientResponse`. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const fromWeb = (request, source) => new WebHttpClientResponse(request, source); | |
| /** | |
| * Creates a decoder for a response's status, headers, and JSON body using the supplied schema. | |
| * | |
| * @category schemas | |
| * @since 4.0.0 | |
| */ | |
| export const schemaJson = (schema, options) => { | |
| const decode = Schema.decodeEffect(Schema.toCodecJson(schema).annotate({ | |
| options | |
| })); | |
| return self => Effect.flatMap(self.json, body => decode({ | |
| status: self.status, | |
| headers: self.headers, | |
| body | |
| })); | |
| }; | |
| /** | |
| * Creates a decoder for a response's status and headers without reading a response body. | |
| * | |
| * @category schemas | |
| * @since 4.0.0 | |
| */ | |
| export const schemaNoBody = (schema, options) => { | |
| const decode = Schema.decodeEffect(schema.annotate({ | |
| options | |
| })); | |
| return self => decode({ | |
| status: self.status, | |
| headers: self.headers | |
| }); | |
| }; | |
| /** | |
| * Converts an effect producing an `HttpClientResponse` into a stream of response body bytes. | |
| * | |
| * @category accessors | |
| * @since 4.0.0 | |
| */ | |
| export const stream = effect => Stream.unwrap(Effect.map(effect, self => self.stream)); | |
| /** | |
| * Pattern matches on a response status, checking exact status handlers before status-class handlers and `orElse`. | |
| * | |
| * @category pattern matching | |
| * @since 4.0.0 | |
| */ | |
| export const matchStatus = /*#__PURE__*/dual(2, (self, cases) => { | |
| const status = self.status; | |
| if (cases[status]) { | |
| return cases[status](self); | |
| } else if (status >= 200 && status < 300 && cases["2xx"]) { | |
| return cases["2xx"](self); | |
| } else if (status >= 300 && status < 400 && cases["3xx"]) { | |
| return cases["3xx"](self); | |
| } else if (status >= 400 && status < 500 && cases["4xx"]) { | |
| return cases["4xx"](self); | |
| } else if (status >= 500 && status < 600 && cases["5xx"]) { | |
| return cases["5xx"](self); | |
| } | |
| return cases.orElse(self); | |
| }); | |
| /** | |
| * Succeeds with the response when its status satisfies the predicate, otherwise fails with `HttpClientError`. | |
| * | |
| * @category filters | |
| * @since 4.0.0 | |
| */ | |
| export const filterStatus = /*#__PURE__*/dual(2, (self, f) => Effect.suspend(() => f(self.status) ? Effect.succeed(self) : Effect.fail(new Error.HttpClientError({ | |
| reason: new Error.StatusCodeError({ | |
| response: self, | |
| request: self.request, | |
| description: "invalid status code" | |
| }) | |
| })))); | |
| /** | |
| * Succeeds with the response only when its status is in the 2xx range, otherwise fails with `HttpClientError`. | |
| * | |
| * @category filters | |
| * @since 4.0.0 | |
| */ | |
| export const filterStatusOk = self => self.status >= 200 && self.status < 300 ? Effect.succeed(self) : Effect.fail(new Error.HttpClientError({ | |
| reason: new Error.StatusCodeError({ | |
| response: self, | |
| request: self.request, | |
| description: "non 2xx status code" | |
| }) | |
| })); | |
| // ----------------------------------------------------------------------------- | |
| // internal | |
| // ----------------------------------------------------------------------------- | |
| class WebHttpClientResponse extends Inspectable.Class { | |
| [HttpIncomingMessage.TypeId]; | |
| [TypeId]; | |
| request; | |
| source; | |
| constructor(request, source) { | |
| super(); | |
| this.request = request; | |
| this.source = source; | |
| this[HttpIncomingMessage.TypeId] = HttpIncomingMessage.TypeId; | |
| this[TypeId] = TypeId; | |
| } | |
| toJSON() { | |
| return HttpIncomingMessage.inspect(this, { | |
| _id: "HttpClientResponse", | |
| request: this.request.toJSON(), | |
| status: this.status | |
| }); | |
| } | |
| get status() { | |
| return this.source.status; | |
| } | |
| get headers() { | |
| return Headers.fromInput(this.source.headers); | |
| } | |
| cachedCookies; | |
| get cookies() { | |
| if (this.cachedCookies) { | |
| return this.cachedCookies; | |
| } | |
| return this.cachedCookies = Cookies.fromSetCookie(this.source.headers.getSetCookie()); | |
| } | |
| get remoteAddress() { | |
| return Option.none(); | |
| } | |
| get stream() { | |
| return this.source.body ? Stream.fromReadableStream({ | |
| evaluate: () => this.source.body, | |
| onError: cause => new Error.HttpClientError({ | |
| reason: new Error.DecodeError({ | |
| request: this.request, | |
| response: this, | |
| cause | |
| }) | |
| }) | |
| }) : Stream.fail(new Error.HttpClientError({ | |
| reason: new Error.EmptyBodyError({ | |
| request: this.request, | |
| response: this, | |
| description: "can not create stream from empty body" | |
| }) | |
| })); | |
| } | |
| get json() { | |
| return Effect.flatMap(this.text, text => Effect.try({ | |
| try: () => text === "" ? null : JSON.parse(text), | |
| catch: cause => new Error.HttpClientError({ | |
| reason: new Error.DecodeError({ | |
| request: this.request, | |
| response: this, | |
| cause | |
| }) | |
| }) | |
| })); | |
| } | |
| textBody; | |
| get text() { | |
| if (this.textBody) { | |
| return this.textBody; | |
| } | |
| this.textBody = Effect.tryPromise({ | |
| try: () => this.source.text(), | |
| catch: cause => new Error.HttpClientError({ | |
| reason: new Error.DecodeError({ | |
| request: this.request, | |
| response: this, | |
| cause | |
| }) | |
| }) | |
| }).pipe(Effect.cached, Effect.runSync); | |
| this.arrayBufferBody = Effect.map(this.textBody, _ => new TextEncoder().encode(_).buffer); | |
| return this.textBody; | |
| } | |
| get urlParamsBody() { | |
| return Effect.flatMap(this.text, _ => Effect.try({ | |
| try: () => UrlParams.fromInput(new URLSearchParams(_)), | |
| catch: cause => new Error.HttpClientError({ | |
| reason: new Error.DecodeError({ | |
| request: this.request, | |
| response: this, | |
| cause | |
| }) | |
| }) | |
| })); | |
| } | |
| formDataBody; | |
| get formData() { | |
| return this.formDataBody ??= Effect.tryPromise({ | |
| try: () => this.source.formData(), | |
| catch: cause => new Error.HttpClientError({ | |
| reason: new Error.DecodeError({ | |
| request: this.request, | |
| response: this, | |
| cause | |
| }) | |
| }) | |
| }).pipe(Effect.cached, Effect.runSync); | |
| } | |
| arrayBufferBody; | |
| get arrayBuffer() { | |
| if (this.arrayBufferBody) { | |
| return this.arrayBufferBody; | |
| } | |
| this.arrayBufferBody = Effect.tryPromise({ | |
| try: () => this.source.arrayBuffer(), | |
| catch: cause => new Error.HttpClientError({ | |
| reason: new Error.DecodeError({ | |
| request: this.request, | |
| response: this, | |
| cause | |
| }) | |
| }) | |
| }).pipe(Effect.cached, Effect.runSync); | |
| this.textBody = Effect.map(this.arrayBufferBody, _ => new TextDecoder().decode(_)); | |
| return this.arrayBufferBody; | |
| } | |
| pipe() { | |
| return pipeArguments(this, arguments); | |
| } | |
| } | |
| //# sourceMappingURL=HttpClientResponse.js.map |
Xet Storage Details
- Size:
- 8.31 kB
- Xet hash:
- b36aa6786d0ab182ced07db2085a27fead1e61450807f1c22ff75de09cde88b6
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.