@openstage/monadyssey-fetch 3.0.0-beta.8 → 3.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +24 -0
- package/dist/monadyssey-fetch.d.ts +233 -101
- package/dist/monadyssey-fetch.mjs +187 -79
- package/dist/monadyssey-fetch.mjs.map +1 -1
- package/package.json +13 -9
- package/readme.md +27 -8
- package/dist/monadyssey-fetch.cjs +0 -8
- package/dist/monadyssey-fetch.cjs.map +0 -1
- package/dist/monadyssey-fetch.umd.js +0 -8
- package/dist/monadyssey-fetch.umd.js.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Gabriel Bornea
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
|
6
|
+
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
|
7
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
|
8
|
+
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
11
|
+
|
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
13
|
+
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
14
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
15
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
16
|
+
|
|
17
|
+
Inspiration Acknowledgment:
|
|
18
|
+
|
|
19
|
+
The monadyssey library's development and design approaches draw conceptual inspiration from the Haskell language,
|
|
20
|
+
the Arrow library for Kotlin, and the Cats library for Scala. This acknowledgment is to highlight the influence
|
|
21
|
+
these projects have had on monadyssey's API design and embrace of functional programming paradigms. It is crucial
|
|
22
|
+
to note that this inspiration pertains solely to API design and architecture principles; no direct code from these
|
|
23
|
+
projects has been used in monadyssey. This library stands as an independent work, and this section is included to
|
|
24
|
+
honor the spirit of shared knowledge and to contribute to community growth within the functional programming ecosystem.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { IO } from '@openstage/monadyssey-core';
|
|
2
2
|
import { Option as Option_2 } from '@openstage/monadyssey-core';
|
|
3
|
-
import { Pipe } from '@openstage/monadyssey-core';
|
|
3
|
+
import type { Pipe } from '@openstage/monadyssey-core';
|
|
4
|
+
import type { Schedule } from '@openstage/monadyssey-core';
|
|
4
5
|
import { Stream } from '@openstage/monadyssey-core';
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -21,24 +22,37 @@ export declare type Credentials = "omit" | "same-origin" | "include";
|
|
|
21
22
|
export declare const decodeEvents: <E>() => Pipe<E, Uint8Array, ServerSentEvent>;
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
|
-
*
|
|
25
|
+
* `T` restricted to the keys of `Shape`: every other key is typed `never`, so an object with an extra
|
|
26
|
+
* key is rejected even when it is a variable rather than a literal. `send` / `raw` take their options
|
|
27
|
+
* this way, so a payload passed positionally (`send("POST", url, article)`, where `article` happens to
|
|
28
|
+
* have a `body` key) does not type-check as the options.
|
|
29
|
+
*/
|
|
30
|
+
declare type Exact<T, Shape> = T & {
|
|
31
|
+
[K in Exclude<keyof T, keyof Shape>]: never;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A composable HTTP client that wraps the native `fetch` API, returning `IO` instances instead of
|
|
36
|
+
* Promises. Each instance carries its own configuration (base URL, interceptors, default headers,
|
|
37
|
+
* timeout, credentials), so different parts of an application can use independently configured
|
|
38
|
+
* clients.
|
|
25
39
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
40
|
+
* Three kinds of call, by what you want back:
|
|
41
|
+
* - **the decoded body** — `get`, `post`, `put`, `patch`, `delete`, `fetch` return
|
|
42
|
+
* `IO<HttpError, A>`. A response with no body (`204`, an empty `200`) fails with
|
|
43
|
+
* `kind: "decode"`, since a value was expected; pass `{ empty: "option" }` when absence is
|
|
44
|
+
* legitimate, to get `IO<HttpError, Option<A>>`.
|
|
45
|
+
* - **nothing** — `send(method, uri, { body })` returns `IO<HttpError, void>` for actions whose
|
|
46
|
+
* response body you do not read (publish, archive, leave …); any body, or none, is fine.
|
|
47
|
+
* - **the `Response`** — `raw(method, uri, options?)` returns `IO<HttpError, Response>` for
|
|
48
|
+
* status codes, headers or streaming.
|
|
29
49
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
50
|
+
* Every non-2xx response fails with `kind: "response"`, whichever you use. Cancellation is
|
|
51
|
+
* supported: when the IO is cancelled (via a fiber or `timeout`), the underlying `fetch` is
|
|
52
|
+
* aborted through its `AbortSignal`.
|
|
33
53
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* protocol-level fact (the HTTP spec says these statuses have no body), distinct from a
|
|
37
|
-
* domain-level optionality. For that reason the methods return `IO<HttpError, A | null>` rather
|
|
38
|
-
* than `IO<HttpError, Option<A>>`. `Option` is the right tool when a value might be absent at
|
|
39
|
-
* the domain level; `| null` is honest when the absence is structural to the protocol. For the
|
|
40
|
-
* 95% case where you control the endpoint and never receive 204, the `| null` is a brief
|
|
41
|
-
* `?? defaultValue` away.
|
|
54
|
+
* `A` is what you claim the body is; the JSON is not checked unless you pass a `transform` that
|
|
55
|
+
* validates it (a schema's `parse`, say) — a throw there fails with `kind: "decode"`.
|
|
42
56
|
*
|
|
43
57
|
* **Default credentials:** `same-origin`, matching the platform `fetch` default. Set
|
|
44
58
|
* `credentials: "include"` explicitly per-client or per-request if you need cookies to flow
|
|
@@ -48,11 +62,12 @@ export declare const decodeEvents: <E>() => Pipe<E, Uint8Array, ServerSentEvent>
|
|
|
48
62
|
* const api = new HttpClient({
|
|
49
63
|
* baseUrl: "https://api.example.com",
|
|
50
64
|
* interceptors: [authInterceptor],
|
|
51
|
-
* defaultHeaders: { "Accept": "application/json" },
|
|
52
65
|
* timeout: 5000,
|
|
53
66
|
* });
|
|
54
67
|
*
|
|
55
|
-
* const users = api.get<User[]>("/users");
|
|
68
|
+
* const users = api.get<User[]>("/users"); // IO<HttpError, User[]>
|
|
69
|
+
* const avatar = api.get<Avatar>("/me/avatar", { empty: "option" }); // IO<HttpError, Option<Avatar>>
|
|
70
|
+
* const leave = api.send("POST", `/communities/${slug}/leave`); // IO<HttpError, void>
|
|
56
71
|
*/
|
|
57
72
|
export declare class HttpClient {
|
|
58
73
|
private readonly interceptors;
|
|
@@ -67,81 +82,105 @@ export declare class HttpClient {
|
|
|
67
82
|
*/
|
|
68
83
|
constructor(config?: HttpClientConfig);
|
|
69
84
|
/**
|
|
70
|
-
* Performs a GET request.
|
|
85
|
+
* Performs a GET request and decodes the body.
|
|
71
86
|
*
|
|
72
|
-
* @template A - The
|
|
87
|
+
* @template A - The type of the decoded body.
|
|
73
88
|
* @param {string} uri - The URL or path to request.
|
|
74
|
-
* @param {
|
|
75
|
-
* @returns {IO<HttpError, A
|
|
89
|
+
* @param {Options<A>} [options] - Request options; `{ empty: "option" }` yields `Option<A>`.
|
|
90
|
+
* @returns {IO<HttpError, A>} The decoded body.
|
|
76
91
|
*/
|
|
77
|
-
get(uri: string, options: Omit<
|
|
78
|
-
|
|
79
|
-
}): IO<HttpError, Response>;
|
|
80
|
-
get<A = unknown>(uri: string, options?: Omit<Options<A>, "body">): IO<HttpError, A | null>;
|
|
92
|
+
get<A = unknown>(uri: string, options: Omit<OptionalBody<A>, "body">): IO<HttpError, Option_2<NonNullable<A>>>;
|
|
93
|
+
get<A = unknown>(uri: string, options?: Omit<RequiredBody<A>, "body">): IO<HttpError, A>;
|
|
81
94
|
/**
|
|
82
|
-
* Performs a POST request.
|
|
95
|
+
* Performs a POST request and decodes the body.
|
|
83
96
|
*
|
|
84
|
-
* @template A - The
|
|
97
|
+
* @template A - The type of the decoded body.
|
|
85
98
|
* @param {string} uri - The URL or path to request.
|
|
86
99
|
* @param {unknown} [body] - The request payload.
|
|
87
|
-
* @param {Options<A>} [options] - Request options
|
|
88
|
-
* @returns {IO<HttpError, A
|
|
100
|
+
* @param {Options<A>} [options] - Request options; `{ empty: "option" }` yields `Option<A>`.
|
|
101
|
+
* @returns {IO<HttpError, A>} The decoded body.
|
|
89
102
|
*/
|
|
90
|
-
post(uri: string, body: unknown, options:
|
|
91
|
-
|
|
92
|
-
}): IO<HttpError, Response>;
|
|
93
|
-
post<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;
|
|
103
|
+
post<A = unknown>(uri: string, body: unknown, options: OptionalBody<A>): IO<HttpError, Option_2<NonNullable<A>>>;
|
|
104
|
+
post<A = unknown>(uri: string, body?: unknown, options?: RequiredBody<A>): IO<HttpError, A>;
|
|
94
105
|
/**
|
|
95
|
-
* Performs a PUT request.
|
|
106
|
+
* Performs a PUT request and decodes the body.
|
|
96
107
|
*
|
|
97
|
-
* @template A - The
|
|
108
|
+
* @template A - The type of the decoded body.
|
|
98
109
|
* @param {string} uri - The URL or path to request.
|
|
99
110
|
* @param {unknown} [body] - The request payload.
|
|
100
|
-
* @param {Options<A>} [options] - Request options
|
|
101
|
-
* @returns {IO<HttpError, A
|
|
111
|
+
* @param {Options<A>} [options] - Request options; `{ empty: "option" }` yields `Option<A>`.
|
|
112
|
+
* @returns {IO<HttpError, A>} The decoded body.
|
|
102
113
|
*/
|
|
103
|
-
put(uri: string, body: unknown, options:
|
|
104
|
-
|
|
105
|
-
}): IO<HttpError, Response>;
|
|
106
|
-
put<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;
|
|
114
|
+
put<A = unknown>(uri: string, body: unknown, options: OptionalBody<A>): IO<HttpError, Option_2<NonNullable<A>>>;
|
|
115
|
+
put<A = unknown>(uri: string, body?: unknown, options?: RequiredBody<A>): IO<HttpError, A>;
|
|
107
116
|
/**
|
|
108
|
-
* Performs a PATCH request.
|
|
117
|
+
* Performs a PATCH request and decodes the body.
|
|
109
118
|
*
|
|
110
|
-
* @template A - The
|
|
119
|
+
* @template A - The type of the decoded body.
|
|
111
120
|
* @param {string} uri - The URL or path to request.
|
|
112
121
|
* @param {unknown} [body] - The request payload.
|
|
113
|
-
* @param {Options<A>} [options] - Request options
|
|
114
|
-
* @returns {IO<HttpError, A
|
|
122
|
+
* @param {Options<A>} [options] - Request options; `{ empty: "option" }` yields `Option<A>`.
|
|
123
|
+
* @returns {IO<HttpError, A>} The decoded body.
|
|
115
124
|
*/
|
|
116
|
-
patch(uri: string, body: unknown, options:
|
|
117
|
-
|
|
118
|
-
}): IO<HttpError, Response>;
|
|
119
|
-
patch<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;
|
|
125
|
+
patch<A = unknown>(uri: string, body: unknown, options: OptionalBody<A>): IO<HttpError, Option_2<NonNullable<A>>>;
|
|
126
|
+
patch<A = unknown>(uri: string, body?: unknown, options?: RequiredBody<A>): IO<HttpError, A>;
|
|
120
127
|
/**
|
|
121
|
-
* Performs a DELETE request.
|
|
128
|
+
* Performs a DELETE request and decodes the body. For a DELETE that answers `204 No Content`,
|
|
129
|
+
* use `send("DELETE", uri)` instead.
|
|
122
130
|
*
|
|
123
|
-
* @template A - The
|
|
131
|
+
* @template A - The type of the decoded body.
|
|
124
132
|
* @param {string} uri - The URL or path to request.
|
|
125
|
-
* @param {
|
|
126
|
-
* @returns {IO<HttpError, A
|
|
133
|
+
* @param {Options<A>} [options] - Request options; `{ empty: "option" }` yields `Option<A>`.
|
|
134
|
+
* @returns {IO<HttpError, A>} The decoded body.
|
|
127
135
|
*/
|
|
128
|
-
delete(uri: string, options: Omit<
|
|
129
|
-
|
|
130
|
-
}): IO<HttpError, Response>;
|
|
131
|
-
delete<A = unknown>(uri: string, options?: Omit<Options<A>, "body">): IO<HttpError, A | null>;
|
|
136
|
+
delete<A = unknown>(uri: string, options: Omit<OptionalBody<A>, "body">): IO<HttpError, Option_2<NonNullable<A>>>;
|
|
137
|
+
delete<A = unknown>(uri: string, options?: Omit<RequiredBody<A>, "body">): IO<HttpError, A>;
|
|
132
138
|
/**
|
|
133
|
-
* Performs a
|
|
139
|
+
* Performs a request with any method and decodes the body.
|
|
134
140
|
*
|
|
135
|
-
* @template A - The
|
|
141
|
+
* @template A - The type of the decoded body.
|
|
136
142
|
* @param {string} uri - The URL or path to request.
|
|
137
143
|
* @param {Method} method - The HTTP method.
|
|
138
|
-
* @param {Options<A>} [options] - Request options
|
|
139
|
-
* @returns {IO<HttpError, A
|
|
144
|
+
* @param {Options<A>} [options] - Request options, including `body`; `{ empty: "option" }` yields `Option<A>`.
|
|
145
|
+
* @returns {IO<HttpError, A>} The decoded body.
|
|
146
|
+
*/
|
|
147
|
+
fetch<A = unknown>(uri: string, method: Method, options: OptionalBody<A>): IO<HttpError, Option_2<NonNullable<A>>>;
|
|
148
|
+
fetch<A = unknown>(uri: string, method: Method, options?: RequiredBody<A>): IO<HttpError, A>;
|
|
149
|
+
/**
|
|
150
|
+
* Performs a request whose response body you do not need — an action such as publish,
|
|
151
|
+
* archive or leave. Succeeds with `void` for any 2xx, with or without a body (which is
|
|
152
|
+
* discarded); a non-2xx response fails with `kind: "response"` and its body.
|
|
153
|
+
*
|
|
154
|
+
* @param {Method} method - The HTTP method.
|
|
155
|
+
* @param {string} uri - The URL or path to request.
|
|
156
|
+
* @param {SendOptions} [options] - Headers, credentials, timeout and body. Every key must be one of
|
|
157
|
+
* `SendOptions`: a payload passed here by mistake (`send("POST", url, article)`) is rejected at
|
|
158
|
+
* compile time; it belongs in `{ body: article }`. A `GET` or `HEAD` request with a body throws
|
|
159
|
+
* `TypeError` where it is built.
|
|
160
|
+
* @returns {IO<HttpError, void>}
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* client.send("POST", `/api/v1/communities/${slug}/leave`);
|
|
164
|
+
* client.send("PUT", `/api/v1/articles/${id}`, { body: article });
|
|
165
|
+
* client.send("DELETE", `/api/v1/passkeys/${id}`, { headers: { "X-Step-Up": token } });
|
|
140
166
|
*/
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
167
|
+
send<O extends SendOptions>(method: Method, uri: string, options?: Exact<O, SendOptions>): IO<HttpError, void>;
|
|
168
|
+
/**
|
|
169
|
+
* Performs a request and yields the `Response` itself, for status codes, headers or streaming.
|
|
170
|
+
* A non-2xx response still fails with `kind: "response"` (its body read into the error); a 2xx
|
|
171
|
+
* `Response` is returned unread. When you only need the status or headers, cancel the body
|
|
172
|
+
* (`response.body?.cancel()`) so the connection is released; `send` does this for you.
|
|
173
|
+
*
|
|
174
|
+
* @param {Method} method - The HTTP method.
|
|
175
|
+
* @param {string} uri - The URL or path to request.
|
|
176
|
+
* @param {SendOptions} [options] - Headers, credentials, timeout and body; every key must be one of
|
|
177
|
+
* `SendOptions`, as for `send`.
|
|
178
|
+
* @returns {IO<HttpError, Response>}
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* client.raw("POST", "/api/v1/communities", { body: input }).map((r) => r.headers.get("Location"));
|
|
182
|
+
*/
|
|
183
|
+
raw<O extends SendOptions>(method: Method, uri: string, options?: Exact<O, SendOptions>): IO<HttpError, Response>;
|
|
145
184
|
/**
|
|
146
185
|
* Opens a server-sent-events stream and emits each {@link ServerSentEvent} as it arrives.
|
|
147
186
|
*
|
|
@@ -158,13 +197,17 @@ export declare class HttpClient {
|
|
|
158
197
|
* @param {string} uri - The URL or path to connect to.
|
|
159
198
|
* @param {SseOptions} [options] - Headers, credentials, and reconnection settings.
|
|
160
199
|
* @returns {Stream<HttpError, ServerSentEvent>} A stream of parsed events.
|
|
200
|
+
* @throws {RangeError} If `options.retry` is negative or `NaN`.
|
|
161
201
|
*
|
|
162
202
|
* @example
|
|
163
203
|
* const events = client.sse("/events", { headers: { Authorization: `Bearer ${token}` } });
|
|
164
|
-
* await events.runForEach((event) => IO.
|
|
204
|
+
* await events.runForEach((event) => IO.sync(() => handle(event))).unsafeRun();
|
|
165
205
|
*/
|
|
166
206
|
sse(uri: string, options?: SseOptions): Stream<HttpError, ServerSentEvent>;
|
|
167
207
|
private resolveUrl;
|
|
208
|
+
/** Prepares the request, runs it through the interceptors and `fetch`, and yields the `Response`. */
|
|
209
|
+
private exchange;
|
|
210
|
+
private withTimeout;
|
|
168
211
|
private request;
|
|
169
212
|
}
|
|
170
213
|
|
|
@@ -172,13 +215,18 @@ export declare class HttpClient {
|
|
|
172
215
|
* Configuration for creating an HttpClient instance.
|
|
173
216
|
*/
|
|
174
217
|
export declare type HttpClientConfig = {
|
|
175
|
-
/**
|
|
218
|
+
/**
|
|
219
|
+
* Prepended to relative paths by string concatenation with exactly one `/` between them, so the
|
|
220
|
+
* base's own path is kept: `baseUrl: "https://h/api"` and `"/v1/x"` give `https://h/api/v1/x`
|
|
221
|
+
* (`new URL("/v1/x", base)` would drop `/api`). A `uri` with a scheme (`https://…`, `data:`,
|
|
222
|
+
* `blob:`) is used as is.
|
|
223
|
+
*/
|
|
176
224
|
baseUrl?: string;
|
|
177
225
|
/** Interceptors applied in registration order (first registered = outermost). Immutable after construction. */
|
|
178
226
|
interceptors?: HttpInterceptor[];
|
|
179
227
|
/** Default headers merged into every request. Per-request headers take precedence. */
|
|
180
228
|
defaultHeaders?: Record<string, string>;
|
|
181
|
-
/** Default timeout in milliseconds for all requests. Can be overridden per-request. */
|
|
229
|
+
/** Default timeout in milliseconds for all requests; none by default. Can be overridden per-request. */
|
|
182
230
|
timeout?: number;
|
|
183
231
|
/** Default credentials policy. Defaults to `"same-origin"` (matches the platform `fetch` default). */
|
|
184
232
|
credentials?: Credentials;
|
|
@@ -193,7 +241,12 @@ export declare type HttpClientConfig = {
|
|
|
193
241
|
*
|
|
194
242
|
* `kind` tells the two facts apart that `status` alone cannot: "the server said 500" is
|
|
195
243
|
* `kind: "response", status: 500`, while "nothing answered" is `kind: "network", status: 0`.
|
|
196
|
-
* For every kind other than `"response"`, `status` is `0
|
|
244
|
+
* For every kind other than `"response"` and `"decode"`, `status` is `0`; `body` is `null` except
|
|
245
|
+
* for `"response"`.
|
|
246
|
+
*
|
|
247
|
+
* `body`, `headers` and `url` hold what the server and the request contained, which may be sensitive
|
|
248
|
+
* (a token in a query string, an error page echoing input); redact them before logging an error
|
|
249
|
+
* outside a trusted sink. `Set-Cookie` is never carried.
|
|
197
250
|
*
|
|
198
251
|
* @example
|
|
199
252
|
* client.post("/payments", payment).mapErr((error) =>
|
|
@@ -205,7 +258,7 @@ export declare type HttpClientConfig = {
|
|
|
205
258
|
export declare class HttpError extends Error {
|
|
206
259
|
/** The transport outcome. See {@link HttpErrorKind}. */
|
|
207
260
|
readonly kind: HttpErrorKind;
|
|
208
|
-
/** The HTTP status code, or `0` when no response arrived
|
|
261
|
+
/** The HTTP status code (`"response"` and `"decode"`), or `0` when no response arrived. */
|
|
209
262
|
readonly status: number;
|
|
210
263
|
readonly rawMessage: string;
|
|
211
264
|
/** The parsed response body, or `null` when none was parsed. */
|
|
@@ -233,6 +286,16 @@ export declare class HttpError extends Error {
|
|
|
233
286
|
* @param {number} after The timeout in milliseconds.
|
|
234
287
|
*/
|
|
235
288
|
static timeout(url: string, after: number): HttpError;
|
|
289
|
+
/**
|
|
290
|
+
* An error for a 2xx response whose body could not be used: it failed to parse, a `transform`
|
|
291
|
+
* threw, or there was no body where one was expected. `status` is the real status, `body` is
|
|
292
|
+
* `null`, and `cause` carries the underlying error.
|
|
293
|
+
*
|
|
294
|
+
* @param {string} url The request URL.
|
|
295
|
+
* @param {Response} response The response that could not be decoded.
|
|
296
|
+
* @param {unknown} cause The parse error, the value a `transform` threw, or a description.
|
|
297
|
+
*/
|
|
298
|
+
static decode(url: string, response: Response, cause: unknown): HttpError;
|
|
236
299
|
/**
|
|
237
300
|
* An error for a request that could not be built, so nothing was sent — typically a body that
|
|
238
301
|
* could not be serialized. `status` is `0` and `body` is `null`.
|
|
@@ -269,75 +332,122 @@ export declare class HttpError extends Error {
|
|
|
269
332
|
* (e) => `${e.code}: ${e.message}`
|
|
270
333
|
* );
|
|
271
334
|
*/
|
|
272
|
-
bodyAs<T>(guard: (x: unknown) => x is T): Option_2<T
|
|
335
|
+
bodyAs<T>(guard: (x: unknown) => x is T): Option_2<NonNullable<T>>;
|
|
273
336
|
}
|
|
274
337
|
|
|
275
338
|
/**
|
|
276
339
|
* How a request failed.
|
|
277
340
|
* - `"request"`: the request could not be built — typically the body could not be serialized.
|
|
278
341
|
* Nothing was sent.
|
|
279
|
-
* - `"response"`: the server answered with a non-2xx status
|
|
342
|
+
* - `"response"`: the server answered with a non-2xx status. `body` carries what it sent.
|
|
343
|
+
* - `"decode"`: the server answered 2xx, but the body could not be used — it failed to parse, a
|
|
344
|
+
* `transform` threw, or there was no body where one was expected (a `204`, say). `status` is the
|
|
345
|
+
* real status and `cause` the underlying error.
|
|
280
346
|
* - `"network"`: no response arrived — DNS failure, offline, CORS block, or a rejected interceptor.
|
|
281
347
|
* - `"timeout"`: the client's `timeout` option fired before the response, or its body, completed.
|
|
282
348
|
* The request may have reached the server.
|
|
283
349
|
* - `"aborted"`: the request was aborted by something other than IO cancellation, such as an
|
|
284
350
|
* interceptor's own signal (an IO cancelled via `fiber.cancel()` or `IO.timeout` ends as
|
|
285
351
|
* `Cancelled`, not as an `HttpError`).
|
|
352
|
+
*
|
|
353
|
+
* New kinds may be added in a minor release, so give an exhaustive `switch` a `default` branch.
|
|
286
354
|
*/
|
|
287
|
-
export declare type HttpErrorKind = "request" | "response" | "network" | "timeout" | "aborted";
|
|
355
|
+
export declare type HttpErrorKind = "request" | "response" | "decode" | "network" | "timeout" | "aborted";
|
|
288
356
|
|
|
289
357
|
/**
|
|
290
|
-
* An HTTP interceptor that can modify or handle request and response
|
|
291
|
-
*
|
|
358
|
+
* An HTTP interceptor that can modify or handle a request before, and its response after, the
|
|
359
|
+
* outgoing `fetch` call.
|
|
292
360
|
*/
|
|
293
361
|
export declare interface HttpInterceptor {
|
|
294
362
|
/**
|
|
295
363
|
* Intercepts an outgoing HTTP request.
|
|
296
364
|
*
|
|
297
|
-
* Call `next(request)` to continue the chain with the (optionally modified) request.
|
|
298
|
-
*
|
|
365
|
+
* Call `next(request)` to continue the chain with the (optionally modified) request — e.g.
|
|
366
|
+
* `next({ url: request.url, init: { ...request.init, headers } })`. Return a
|
|
367
|
+
* `Promise<Response>` without calling `next` to short-circuit the request.
|
|
299
368
|
*
|
|
300
|
-
*
|
|
369
|
+
* Keep `request.init.signal` when building the request you pass on: it is how a timeout or a
|
|
370
|
+
* cancelled fiber aborts the `fetch`. A request forwarded without it runs to completion in the
|
|
371
|
+
* background after the IO has already failed or been cancelled.
|
|
372
|
+
*
|
|
373
|
+
* @param request - The resolved URL and `fetch` options of the pending request.
|
|
301
374
|
* @param next - Forwards the request to the next interceptor or to `fetch`.
|
|
302
375
|
* @returns A Promise of the resulting `Response`.
|
|
303
376
|
*/
|
|
304
|
-
intercept(request:
|
|
377
|
+
intercept(request: InterceptedRequest, next: (request: InterceptedRequest) => Promise<Response>): Promise<Response>;
|
|
305
378
|
}
|
|
306
379
|
|
|
307
380
|
/**
|
|
308
|
-
*
|
|
381
|
+
* A request as seen by an {@link HttpInterceptor}: the resolved URL and the `fetch` options.
|
|
382
|
+
* `init.headers` is always a `Headers` instance — copy it with `new Headers(request.init.headers)`
|
|
383
|
+
* (spreading a `Headers` object yields `{}`).
|
|
309
384
|
*/
|
|
310
|
-
export declare type
|
|
385
|
+
export declare type InterceptedRequest = {
|
|
386
|
+
readonly url: string;
|
|
387
|
+
readonly init: Omit<RequestInit, "headers"> & {
|
|
388
|
+
readonly headers: Headers;
|
|
389
|
+
};
|
|
390
|
+
};
|
|
311
391
|
|
|
312
392
|
/**
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
* - `"response"`: Return the full `Response` object.
|
|
393
|
+
* Represents the HTTP method for the request. Methods may be added in a minor release, so give an
|
|
394
|
+
* exhaustive `switch` or a `Record<Method, …>` a fallback.
|
|
316
395
|
*/
|
|
317
|
-
export declare type
|
|
396
|
+
export declare type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD";
|
|
397
|
+
|
|
398
|
+
/** Options for a request that expects no body back, or `Option` of one. */
|
|
399
|
+
declare type OptionalBody<A> = Options<A> & {
|
|
400
|
+
empty: "option";
|
|
401
|
+
};
|
|
318
402
|
|
|
319
403
|
/**
|
|
320
|
-
* Options for
|
|
404
|
+
* Options for a request whose response body is decoded (`get`, `post`, `put`, `patch`, `delete`,
|
|
405
|
+
* `fetch`).
|
|
321
406
|
*
|
|
322
|
-
* @template A - The
|
|
407
|
+
* @template A - The type of the decoded response body.
|
|
323
408
|
*/
|
|
324
|
-
export declare type Options<A = unknown> = {
|
|
325
|
-
/**
|
|
326
|
-
headers?: Record<string, string>;
|
|
327
|
-
/** The request payload. */
|
|
409
|
+
export declare type Options<A = unknown> = RequestOptions & {
|
|
410
|
+
/** The request payload (for `fetch`; the other methods take it as an argument). */
|
|
328
411
|
body?: unknown;
|
|
329
|
-
/**
|
|
412
|
+
/** How the response body is read. Defaults to `"json"`. */
|
|
330
413
|
responseType?: ResponseType_2;
|
|
414
|
+
/**
|
|
415
|
+
* Turns the parsed body into an `A` — a decoder or validator. Called only when there is a body;
|
|
416
|
+
* a throw fails the request with `kind: "decode"`.
|
|
417
|
+
*/
|
|
418
|
+
transform?: (data: unknown) => A;
|
|
419
|
+
/**
|
|
420
|
+
* What a response without a body means (`204`, `205`, a `HEAD` response, or an empty 2xx under
|
|
421
|
+
* `responseType: "json"`; under `"text"` an empty body is `""`, not absence):
|
|
422
|
+
* - `"error"` (default): a value was expected, so it fails with `kind: "decode"`;
|
|
423
|
+
* - `"option"`: absence is expected, so the request yields `Option<A>` — `None` for no body.
|
|
424
|
+
*
|
|
425
|
+
* For requests whose body you never read, use `send()`; to inspect the `Response`, use `raw()`.
|
|
426
|
+
*/
|
|
427
|
+
empty?: "error" | "option";
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Options shared by every request: headers, credentials and timeout.
|
|
432
|
+
*/
|
|
433
|
+
export declare type RequestOptions = {
|
|
434
|
+
/** Custom headers for the request as key-value pairs. */
|
|
435
|
+
headers?: Record<string, string>;
|
|
331
436
|
/** The credential policy for the request. Defaults to the client-level setting. */
|
|
332
437
|
credentials?: Credentials;
|
|
333
|
-
/**
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
438
|
+
/**
|
|
439
|
+
* Per-request timeout in milliseconds. Overrides the client-level timeout. Values above
|
|
440
|
+
* 2,147,483,647 ms (the `setTimeout` limit), including `Infinity`, are lowered to it; a negative
|
|
441
|
+
* or `NaN` value throws `RangeError` where the request is built.
|
|
442
|
+
*/
|
|
338
443
|
timeout?: number;
|
|
339
444
|
};
|
|
340
445
|
|
|
446
|
+
/** Options for a request whose body is required (the default). */
|
|
447
|
+
declare type RequiredBody<A> = Options<A> & {
|
|
448
|
+
empty?: "error";
|
|
449
|
+
};
|
|
450
|
+
|
|
341
451
|
/**
|
|
342
452
|
* Specifies the expected response type.
|
|
343
453
|
* - `"json"`: Parse the response as JSON.
|
|
@@ -345,14 +455,27 @@ export declare type Options<A = unknown> = {
|
|
|
345
455
|
* - `"blob"`: Parse the response as a Blob.
|
|
346
456
|
* - `"arrayBuffer"`: Parse the response as an ArrayBuffer.
|
|
347
457
|
* - `"formData"`: Parse the response as FormData.
|
|
458
|
+
*
|
|
459
|
+
* Response types may be added in a minor release, so give an exhaustive `switch` or a
|
|
460
|
+
* `Record<ResponseType, …>` a fallback.
|
|
348
461
|
*/
|
|
349
462
|
declare type ResponseType_2 = "json" | "text" | "blob" | "arrayBuffer" | "formData";
|
|
350
463
|
export { ResponseType_2 as ResponseType }
|
|
351
464
|
|
|
465
|
+
/**
|
|
466
|
+
* Options for `send` and `raw`: the shared request options plus the payload.
|
|
467
|
+
*/
|
|
468
|
+
export declare type SendOptions = RequestOptions & {
|
|
469
|
+
/** The request payload. */
|
|
470
|
+
body?: unknown;
|
|
471
|
+
};
|
|
472
|
+
|
|
352
473
|
/**
|
|
353
474
|
* A single server-sent event parsed from a text/event-stream response.
|
|
354
475
|
*
|
|
355
|
-
* @property id - The event id
|
|
476
|
+
* @property id - The last event id in effect when the event was dispatched — set by the event's
|
|
477
|
+
* own `id:` field or by an earlier one, as the spec's `MessageEvent.lastEventId`. Absent until
|
|
478
|
+
* the server sends an `id:`; the empty string after the server resets it. Used to resume a stream.
|
|
356
479
|
* @property event - The event type. Defaults to `"message"` when the server sends no `event:` field.
|
|
357
480
|
* @property data - The event payload. Multiple `data:` lines are joined with a newline.
|
|
358
481
|
* @property retry - The reconnection time in milliseconds, if the server sent a `retry:` field.
|
|
@@ -377,12 +500,21 @@ export declare type SseOptions = {
|
|
|
377
500
|
* (network error or a `5xx` status). Terminal `4xx` responses (including auth failures) are not
|
|
378
501
|
* retried. On reconnect the last received event id is sent as `Last-Event-ID`, and the request
|
|
379
502
|
* headers (including `Authorization`) and credentials are re-applied. Defaults to `true`.
|
|
503
|
+
*
|
|
504
|
+
* `true` waits `retry` ms (or the server's `retry:` value) before each reconnect, forever. Pass a
|
|
505
|
+
* `Schedule` for backoff, jitter or a limit on consecutive failed reconnects, e.g.
|
|
506
|
+
* `Schedule.exponential(1000).maxDelay(30_000).jittered()`; a server `retry:` value still
|
|
507
|
+
* takes precedence as the delay once received. `maxAttempts` counts connection attempts since the
|
|
508
|
+
* last delivered event, the connection that delivered it included — `maxAttempts(3)` allows two
|
|
509
|
+
* reconnects in a row that deliver nothing — and the count resets with every delivered event.
|
|
380
510
|
*/
|
|
381
|
-
reconnect?: boolean;
|
|
511
|
+
reconnect?: boolean | Schedule;
|
|
382
512
|
/**
|
|
383
513
|
* The delay in milliseconds before reconnecting, used until the server sends a `retry:` value.
|
|
384
514
|
* A server-sent value below 100 ms is raised to 100 ms so a misbehaving server cannot make the
|
|
385
|
-
* client reconnect in a tight loop.
|
|
515
|
+
* client reconnect in a tight loop. Delays above 2,147,483,647 ms (the `setTimeout` limit) are
|
|
516
|
+
* lowered to it. Must be a non-negative number: `sse()` throws `RangeError` otherwise.
|
|
517
|
+
* Defaults to `3000`.
|
|
386
518
|
*/
|
|
387
519
|
retry?: number;
|
|
388
520
|
};
|