EdgeAIG's picture
download
raw
8.31 kB
/**
* 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.