@openstage/monadyssey-fetch 3.0.0-beta.8 → 3.0.0-rc.1

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 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,27 @@ export declare type Credentials = "omit" | "same-origin" | "include";
21
22
  export declare const decodeEvents: <E>() => Pipe<E, Uint8Array, ServerSentEvent>;
22
23
 
23
24
  /**
24
- * A composable HTTP client that wraps the native `fetch` API, returning `IO` instances instead of Promises.
25
+ * A composable HTTP client that wraps the native `fetch` API, returning `IO` instances instead of
26
+ * Promises. Each instance carries its own configuration (base URL, interceptors, default headers,
27
+ * timeout, credentials), so different parts of an application can use independently configured
28
+ * clients.
25
29
  *
26
- * Unlike v1, `HttpClient` is instantiable — each instance carries its own configuration (base URL,
27
- * interceptors, default headers, timeout, credentials). This allows different parts of an application
28
- * to use independently configured clients.
30
+ * Three kinds of call, by what you want back:
31
+ * - **the decoded body** — `get`, `post`, `put`, `patch`, `delete`, `fetch` return
32
+ * `IO<HttpError, A>`. A response with no body (`204`, an empty `200`) fails with
33
+ * `kind: "decode"`, since a value was expected; pass `{ empty: "option" }` when absence is
34
+ * legitimate, to get `IO<HttpError, Option<A>>`.
35
+ * - **nothing** — `send(method, uri, { body })` returns `IO<HttpError, void>` for actions whose
36
+ * response body you do not read (publish, archive, leave …); any body, or none, is fine.
37
+ * - **the `Response`** — `raw(method, uri, options?)` returns `IO<HttpError, Response>` for
38
+ * status codes, headers or streaming.
29
39
  *
30
- * All HTTP methods return `IO<HttpError, A | null>`, enabling lazy execution, functional composition,
31
- * and explicit error handling. Cancellation is supported: when an IO is cancelled (via fiber or
32
- * timeout), the underlying `fetch` call is aborted through `AbortSignal`.
40
+ * Every non-2xx response fails with `kind: "response"`, whichever you use. Cancellation is
41
+ * supported: when the IO is cancelled (via a fiber or `timeout`), the underlying `fetch` is
42
+ * aborted through its `AbortSignal`.
33
43
  *
34
- * **On the `| null` in the return type:** when the server responds with `204 No Content` or
35
- * `205 Reset Content`, there is no body to parse and the IO succeeds with `null`. This is a
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.
44
+ * `A` is what you claim the body is; the JSON is not checked unless you pass a `transform` that
45
+ * validates it (a schema's `parse`, say) — a throw there fails with `kind: "decode"`.
42
46
  *
43
47
  * **Default credentials:** `same-origin`, matching the platform `fetch` default. Set
44
48
  * `credentials: "include"` explicitly per-client or per-request if you need cookies to flow
@@ -48,11 +52,12 @@ export declare const decodeEvents: <E>() => Pipe<E, Uint8Array, ServerSentEvent>
48
52
  * const api = new HttpClient({
49
53
  * baseUrl: "https://api.example.com",
50
54
  * interceptors: [authInterceptor],
51
- * defaultHeaders: { "Accept": "application/json" },
52
55
  * timeout: 5000,
53
56
  * });
54
57
  *
55
- * const users = api.get<User[]>("/users");
58
+ * const users = api.get<User[]>("/users"); // IO<HttpError, User[]>
59
+ * const avatar = api.get<Avatar>("/me/avatar", { empty: "option" }); // IO<HttpError, Option<Avatar>>
60
+ * const leave = api.send("POST", `/communities/${slug}/leave`); // IO<HttpError, void>
56
61
  */
57
62
  export declare class HttpClient {
58
63
  private readonly interceptors;
@@ -67,81 +72,102 @@ export declare class HttpClient {
67
72
  */
68
73
  constructor(config?: HttpClientConfig);
69
74
  /**
70
- * Performs a GET request.
75
+ * Performs a GET request and decodes the body.
71
76
  *
72
- * @template A - The expected type of the response body.
77
+ * @template A - The type of the decoded body.
73
78
  * @param {string} uri - The URL or path to request.
74
- * @param {Omit<Options<A>, "body">} [options] - Request options (excluding body).
75
- * @returns {IO<HttpError, A | null>} An IO representing the result.
79
+ * @param {Options<A>} [options] - Request options; `{ empty: "option" }` yields `Option<A>`.
80
+ * @returns {IO<HttpError, A>} The decoded body.
76
81
  */
77
- get(uri: string, options: Omit<Options, "body"> & {
78
- observe: "response";
79
- }): IO<HttpError, Response>;
80
- get<A = unknown>(uri: string, options?: Omit<Options<A>, "body">): IO<HttpError, A | null>;
82
+ get<A = unknown>(uri: string, options: Omit<OptionalBody<A>, "body">): IO<HttpError, Option_2<NonNullable<A>>>;
83
+ get<A = unknown>(uri: string, options?: Omit<RequiredBody<A>, "body">): IO<HttpError, A>;
81
84
  /**
82
- * Performs a POST request.
85
+ * Performs a POST request and decodes the body.
83
86
  *
84
- * @template A - The expected type of the response body.
87
+ * @template A - The type of the decoded body.
85
88
  * @param {string} uri - The URL or path to request.
86
89
  * @param {unknown} [body] - The request payload.
87
- * @param {Options<A>} [options] - Request options.
88
- * @returns {IO<HttpError, A | null>} An IO representing the result.
90
+ * @param {Options<A>} [options] - Request options; `{ empty: "option" }` yields `Option<A>`.
91
+ * @returns {IO<HttpError, A>} The decoded body.
89
92
  */
90
- post(uri: string, body: unknown, options: Options & {
91
- observe: "response";
92
- }): IO<HttpError, Response>;
93
- post<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;
93
+ post<A = unknown>(uri: string, body: unknown, options: OptionalBody<A>): IO<HttpError, Option_2<NonNullable<A>>>;
94
+ post<A = unknown>(uri: string, body?: unknown, options?: RequiredBody<A>): IO<HttpError, A>;
94
95
  /**
95
- * Performs a PUT request.
96
+ * Performs a PUT request and decodes the body.
96
97
  *
97
- * @template A - The expected type of the response body.
98
+ * @template A - The type of the decoded body.
98
99
  * @param {string} uri - The URL or path to request.
99
100
  * @param {unknown} [body] - The request payload.
100
- * @param {Options<A>} [options] - Request options.
101
- * @returns {IO<HttpError, A | null>} An IO representing the result.
101
+ * @param {Options<A>} [options] - Request options; `{ empty: "option" }` yields `Option<A>`.
102
+ * @returns {IO<HttpError, A>} The decoded body.
102
103
  */
103
- put(uri: string, body: unknown, options: Options & {
104
- observe: "response";
105
- }): IO<HttpError, Response>;
106
- put<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;
104
+ put<A = unknown>(uri: string, body: unknown, options: OptionalBody<A>): IO<HttpError, Option_2<NonNullable<A>>>;
105
+ put<A = unknown>(uri: string, body?: unknown, options?: RequiredBody<A>): IO<HttpError, A>;
107
106
  /**
108
- * Performs a PATCH request.
107
+ * Performs a PATCH request and decodes the body.
109
108
  *
110
- * @template A - The expected type of the response body.
109
+ * @template A - The type of the decoded body.
111
110
  * @param {string} uri - The URL or path to request.
112
111
  * @param {unknown} [body] - The request payload.
113
- * @param {Options<A>} [options] - Request options.
114
- * @returns {IO<HttpError, A | null>} An IO representing the result.
112
+ * @param {Options<A>} [options] - Request options; `{ empty: "option" }` yields `Option<A>`.
113
+ * @returns {IO<HttpError, A>} The decoded body.
115
114
  */
116
- patch(uri: string, body: unknown, options: Options & {
117
- observe: "response";
118
- }): IO<HttpError, Response>;
119
- patch<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;
115
+ patch<A = unknown>(uri: string, body: unknown, options: OptionalBody<A>): IO<HttpError, Option_2<NonNullable<A>>>;
116
+ patch<A = unknown>(uri: string, body?: unknown, options?: RequiredBody<A>): IO<HttpError, A>;
120
117
  /**
121
- * Performs a DELETE request.
118
+ * Performs a DELETE request and decodes the body. For a DELETE that answers `204 No Content`,
119
+ * use `send("DELETE", uri)` instead.
122
120
  *
123
- * @template A - The expected type of the response body.
121
+ * @template A - The type of the decoded body.
124
122
  * @param {string} uri - The URL or path to request.
125
- * @param {Omit<Options<A>, "body">} [options] - Request options (excluding body).
126
- * @returns {IO<HttpError, A | null>} An IO representing the result.
123
+ * @param {Options<A>} [options] - Request options; `{ empty: "option" }` yields `Option<A>`.
124
+ * @returns {IO<HttpError, A>} The decoded body.
127
125
  */
128
- delete(uri: string, options: Omit<Options, "body"> & {
129
- observe: "response";
130
- }): IO<HttpError, Response>;
131
- delete<A = unknown>(uri: string, options?: Omit<Options<A>, "body">): IO<HttpError, A | null>;
126
+ delete<A = unknown>(uri: string, options: Omit<OptionalBody<A>, "body">): IO<HttpError, Option_2<NonNullable<A>>>;
127
+ delete<A = unknown>(uri: string, options?: Omit<RequiredBody<A>, "body">): IO<HttpError, A>;
132
128
  /**
133
- * Performs a custom HTTP request with the specified method.
129
+ * Performs a request with any method and decodes the body.
134
130
  *
135
- * @template A - The expected type of the response body.
131
+ * @template A - The type of the decoded body.
136
132
  * @param {string} uri - The URL or path to request.
137
133
  * @param {Method} method - The HTTP method.
138
- * @param {Options<A>} [options] - Request options.
139
- * @returns {IO<HttpError, A | null>} An IO representing the result.
134
+ * @param {Options<A>} [options] - Request options, including `body`; `{ empty: "option" }` yields `Option<A>`.
135
+ * @returns {IO<HttpError, A>} The decoded body.
140
136
  */
141
- fetch(uri: string, method: Method, options: Options & {
142
- observe: "response";
143
- }): IO<HttpError, Response>;
144
- fetch<A = unknown>(uri: string, method: Method, options?: Options<A>): IO<HttpError, A | null>;
137
+ fetch<A = unknown>(uri: string, method: Method, options: OptionalBody<A>): IO<HttpError, Option_2<NonNullable<A>>>;
138
+ fetch<A = unknown>(uri: string, method: Method, options?: RequiredBody<A>): IO<HttpError, A>;
139
+ /**
140
+ * Performs a request whose response body you do not need — an action such as publish,
141
+ * archive or leave. Succeeds with `void` for any 2xx, with or without a body (which is
142
+ * discarded); a non-2xx response fails with `kind: "response"` and its body.
143
+ *
144
+ * @param {Method} method - The HTTP method.
145
+ * @param {string} uri - The URL or path to request.
146
+ * @param {SendOptions} [options] - Headers, credentials, timeout and body. A `GET` or `HEAD`
147
+ * request with a body throws `TypeError` where it is built.
148
+ * @returns {IO<HttpError, void>}
149
+ *
150
+ * @example
151
+ * client.send("POST", `/api/v1/communities/${slug}/leave`);
152
+ * client.send("PUT", `/api/v1/articles/${id}`, { body: article });
153
+ * client.send("DELETE", `/api/v1/passkeys/${id}`, { headers: { "X-Step-Up": token } });
154
+ */
155
+ send(method: Method, uri: string, options?: SendOptions): IO<HttpError, void>;
156
+ /**
157
+ * Performs a request and yields the `Response` itself, for status codes, headers or streaming.
158
+ * A non-2xx response still fails with `kind: "response"` (its body read into the error); a 2xx
159
+ * `Response` is returned unread. When you only need the status or headers, cancel the body
160
+ * (`response.body?.cancel()`) so the connection is released; `send` does this for you.
161
+ *
162
+ * @param {Method} method - The HTTP method.
163
+ * @param {string} uri - The URL or path to request.
164
+ * @param {SendOptions} [options] - Headers, credentials, timeout and body.
165
+ * @returns {IO<HttpError, Response>}
166
+ *
167
+ * @example
168
+ * client.raw("POST", "/api/v1/communities", { body: input }).map((r) => r.headers.get("Location"));
169
+ */
170
+ raw(method: Method, uri: string, options?: SendOptions): IO<HttpError, Response>;
145
171
  /**
146
172
  * Opens a server-sent-events stream and emits each {@link ServerSentEvent} as it arrives.
147
173
  *
@@ -158,13 +184,17 @@ export declare class HttpClient {
158
184
  * @param {string} uri - The URL or path to connect to.
159
185
  * @param {SseOptions} [options] - Headers, credentials, and reconnection settings.
160
186
  * @returns {Stream<HttpError, ServerSentEvent>} A stream of parsed events.
187
+ * @throws {RangeError} If `options.retry` is negative or `NaN`.
161
188
  *
162
189
  * @example
163
190
  * const events = client.sse("/events", { headers: { Authorization: `Bearer ${token}` } });
164
- * await events.runForEach((event) => IO.lift(() => handle(event))).unsafeRun();
191
+ * await events.runForEach((event) => IO.sync(() => handle(event))).unsafeRun();
165
192
  */
166
193
  sse(uri: string, options?: SseOptions): Stream<HttpError, ServerSentEvent>;
167
194
  private resolveUrl;
195
+ /** Prepares the request, runs it through the interceptors and `fetch`, and yields the `Response`. */
196
+ private exchange;
197
+ private withTimeout;
168
198
  private request;
169
199
  }
170
200
 
@@ -193,7 +223,8 @@ export declare type HttpClientConfig = {
193
223
  *
194
224
  * `kind` tells the two facts apart that `status` alone cannot: "the server said 500" is
195
225
  * `kind: "response", status: 500`, while "nothing answered" is `kind: "network", status: 0`.
196
- * For every kind other than `"response"`, `status` is `0` and `body` is `null`.
226
+ * For every kind other than `"response"` and `"decode"`, `status` is `0`; `body` is `null` except
227
+ * for `"response"`.
197
228
  *
198
229
  * @example
199
230
  * client.post("/payments", payment).mapErr((error) =>
@@ -233,6 +264,16 @@ export declare class HttpError extends Error {
233
264
  * @param {number} after The timeout in milliseconds.
234
265
  */
235
266
  static timeout(url: string, after: number): HttpError;
267
+ /**
268
+ * An error for a 2xx response whose body could not be used: it failed to parse, a `transform`
269
+ * threw, or there was no body where one was expected. `status` is the real status, `body` is
270
+ * `null`, and `cause` carries the underlying error.
271
+ *
272
+ * @param {string} url The request URL.
273
+ * @param {Response} response The response that could not be decoded.
274
+ * @param {unknown} cause The parse error, the value a `transform` threw, or a description.
275
+ */
276
+ static decode(url: string, response: Response, cause: unknown): HttpError;
236
277
  /**
237
278
  * An error for a request that could not be built, so nothing was sent — typically a body that
238
279
  * could not be serialized. `status` is `0` and `body` is `null`.
@@ -269,75 +310,112 @@ export declare class HttpError extends Error {
269
310
  * (e) => `${e.code}: ${e.message}`
270
311
  * );
271
312
  */
272
- bodyAs<T>(guard: (x: unknown) => x is T): Option_2<T>;
313
+ bodyAs<T>(guard: (x: unknown) => x is T): Option_2<NonNullable<T>>;
273
314
  }
274
315
 
275
316
  /**
276
317
  * How a request failed.
277
318
  * - `"request"`: the request could not be built — typically the body could not be serialized.
278
319
  * Nothing was sent.
279
- * - `"response"`: the server answered with a non-2xx status, or a 2xx whose body could not be parsed.
320
+ * - `"response"`: the server answered with a non-2xx status. `body` carries what it sent.
321
+ * - `"decode"`: the server answered 2xx, but the body could not be used — it failed to parse, a
322
+ * `transform` threw, or there was no body where one was expected (a `204`, say). `status` is the
323
+ * real status and `cause` the underlying error.
280
324
  * - `"network"`: no response arrived — DNS failure, offline, CORS block, or a rejected interceptor.
281
325
  * - `"timeout"`: the client's `timeout` option fired before the response, or its body, completed.
282
326
  * The request may have reached the server.
283
327
  * - `"aborted"`: the request was aborted by something other than IO cancellation, such as an
284
328
  * interceptor's own signal (an IO cancelled via `fiber.cancel()` or `IO.timeout` ends as
285
329
  * `Cancelled`, not as an `HttpError`).
330
+ *
331
+ * New kinds may be added in a minor release, so give an exhaustive `switch` a `default` branch.
286
332
  */
287
- export declare type HttpErrorKind = "request" | "response" | "network" | "timeout" | "aborted";
333
+ export declare type HttpErrorKind = "request" | "response" | "decode" | "network" | "timeout" | "aborted";
288
334
 
289
335
  /**
290
- * An HTTP interceptor that can modify or handle request and response data
291
- * before and after an outgoing `fetch` call.
336
+ * An HTTP interceptor that can modify or handle a request before, and its response after, the
337
+ * outgoing `fetch` call.
292
338
  */
293
339
  export declare interface HttpInterceptor {
294
340
  /**
295
341
  * Intercepts an outgoing HTTP request.
296
342
  *
297
- * Call `next(request)` to continue the chain with the (optionally modified) request.
298
- * Return a `Promise<Response>` without calling `next` to short-circuit the request.
343
+ * Call `next(request)` to continue the chain with the (optionally modified) request — e.g.
344
+ * `next({ url: request.url, init: { ...request.init, headers } })`. Return a
345
+ * `Promise<Response>` without calling `next` to short-circuit the request.
299
346
  *
300
- * @param request - The configuration object for the pending `fetch` call.
347
+ * @param request - The resolved URL and `fetch` options of the pending request.
301
348
  * @param next - Forwards the request to the next interceptor or to `fetch`.
302
349
  * @returns A Promise of the resulting `Response`.
303
350
  */
304
- intercept(request: RequestInit, next: (req: RequestInit) => Promise<Response>): Promise<Response>;
351
+ intercept(request: InterceptedRequest, next: (request: InterceptedRequest) => Promise<Response>): Promise<Response>;
305
352
  }
306
353
 
354
+ /**
355
+ * A request as seen by an {@link HttpInterceptor}: the resolved URL and the `fetch` options.
356
+ * `init.headers` is always a `Headers` instance — copy it with `new Headers(request.init.headers)`
357
+ * (spreading a `Headers` object yields `{}`).
358
+ */
359
+ export declare type InterceptedRequest = {
360
+ readonly url: string;
361
+ readonly init: Omit<RequestInit, "headers"> & {
362
+ readonly headers: Headers;
363
+ };
364
+ };
365
+
307
366
  /**
308
367
  * Represents the HTTP method for the request.
309
368
  */
310
369
  export declare type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD";
311
370
 
371
+ /** Options for a request that expects no body back, or `Option` of one. */
372
+ declare type OptionalBody<A> = Options<A> & {
373
+ empty: "option";
374
+ };
375
+
312
376
  /**
313
- * Specifies how the response should be observed.
314
- * - `"body"`: Return the response body.
315
- * - `"response"`: Return the full `Response` object.
377
+ * Options for a request whose response body is decoded (`get`, `post`, `put`, `patch`, `delete`,
378
+ * `fetch`).
379
+ *
380
+ * @template A - The type of the decoded response body.
316
381
  */
317
- export declare type Observe = "body" | "response";
382
+ export declare type Options<A = unknown> = RequestOptions & {
383
+ /** The request payload (for `fetch`; the other methods take it as an argument). */
384
+ body?: unknown;
385
+ /** How the response body is read. Defaults to `"json"`. */
386
+ responseType?: ResponseType_2;
387
+ /**
388
+ * Turns the parsed body into an `A` — a decoder or validator. Called only when there is a body;
389
+ * a throw fails the request with `kind: "decode"`.
390
+ */
391
+ transform?: (data: unknown) => A;
392
+ /**
393
+ * What a response without a body (`204`, `205`, an empty `200`) means:
394
+ * - `"error"` (default): a value was expected, so it fails with `kind: "decode"`;
395
+ * - `"option"`: absence is expected, so the request yields `Option<A>` — `None` for no body.
396
+ *
397
+ * For requests whose body you never read, use `send()`; to inspect the `Response`, use `raw()`.
398
+ */
399
+ empty?: "error" | "option";
400
+ };
318
401
 
319
402
  /**
320
- * Options for configuring an individual HTTP request.
321
- *
322
- * @template A - The expected type of the response body after transformation.
403
+ * Options shared by every request: headers, credentials and timeout.
323
404
  */
324
- export declare type Options<A = unknown> = {
405
+ export declare type RequestOptions = {
325
406
  /** Custom headers for the request as key-value pairs. */
326
407
  headers?: Record<string, string>;
327
- /** The request payload. */
328
- body?: unknown;
329
- /** The expected type of the response body. Defaults to `"json"`. */
330
- responseType?: ResponseType_2;
331
408
  /** The credential policy for the request. Defaults to the client-level setting. */
332
409
  credentials?: Credentials;
333
- /** Specifies how the response should be observed. Defaults to `"body"`. */
334
- observe?: Observe;
335
- /** A function to transform the response body into the desired type. */
336
- transform?: (data: unknown) => A;
337
410
  /** Per-request timeout in milliseconds. Overrides the client-level timeout. */
338
411
  timeout?: number;
339
412
  };
340
413
 
414
+ /** Options for a request whose body is required (the default). */
415
+ declare type RequiredBody<A> = Options<A> & {
416
+ empty?: "error";
417
+ };
418
+
341
419
  /**
342
420
  * Specifies the expected response type.
343
421
  * - `"json"`: Parse the response as JSON.
@@ -349,10 +427,20 @@ export declare type Options<A = unknown> = {
349
427
  declare type ResponseType_2 = "json" | "text" | "blob" | "arrayBuffer" | "formData";
350
428
  export { ResponseType_2 as ResponseType }
351
429
 
430
+ /**
431
+ * Options for `send` and `raw`: the shared request options plus the payload.
432
+ */
433
+ export declare type SendOptions = RequestOptions & {
434
+ /** The request payload. */
435
+ body?: unknown;
436
+ };
437
+
352
438
  /**
353
439
  * A single server-sent event parsed from a text/event-stream response.
354
440
  *
355
- * @property id - The event id, if the server sent an `id:` field. Used to resume a stream.
441
+ * @property id - The last event id in effect when the event was dispatched — set by the event's
442
+ * own `id:` field or by an earlier one, as the spec's `MessageEvent.lastEventId`. Absent until
443
+ * the server sends an `id:`; the empty string after the server resets it. Used to resume a stream.
356
444
  * @property event - The event type. Defaults to `"message"` when the server sends no `event:` field.
357
445
  * @property data - The event payload. Multiple `data:` lines are joined with a newline.
358
446
  * @property retry - The reconnection time in milliseconds, if the server sent a `retry:` field.
@@ -377,12 +465,21 @@ export declare type SseOptions = {
377
465
  * (network error or a `5xx` status). Terminal `4xx` responses (including auth failures) are not
378
466
  * retried. On reconnect the last received event id is sent as `Last-Event-ID`, and the request
379
467
  * headers (including `Authorization`) and credentials are re-applied. Defaults to `true`.
468
+ *
469
+ * `true` waits `retry` ms (or the server's `retry:` value) before each reconnect, forever. Pass a
470
+ * `Schedule` for backoff, jitter or a limit on consecutive failed reconnects, e.g.
471
+ * `Schedule.exponential(1000).maxDelay(30_000).jittered()`; a server `retry:` value still
472
+ * takes precedence as the delay once received. `maxAttempts` counts connection attempts since the
473
+ * last delivered event, the connection that delivered it included — `maxAttempts(3)` allows two
474
+ * reconnects in a row that deliver nothing — and the count resets with every delivered event.
380
475
  */
381
- reconnect?: boolean;
476
+ reconnect?: boolean | Schedule;
382
477
  /**
383
478
  * The delay in milliseconds before reconnecting, used until the server sends a `retry:` value.
384
479
  * 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. Defaults to `3000`.
480
+ * client reconnect in a tight loop. Delays above 2,147,483,647 ms (the `setTimeout` limit) are
481
+ * lowered to it. Must be a non-negative number: `sse()` throws `RangeError` otherwise.
482
+ * Defaults to `3000`.
386
483
  */
387
484
  retry?: number;
388
485
  };