diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
index 413d263..bf80a4c 100644
--- a/.github/workflows/testing.yml
+++ b/.github/workflows/testing.yml
@@ -45,3 +45,12 @@ jobs:
- name: Run tests
run: npm test
+
+ - name: Upload coverage reports to Codecov
+ uses: codecov/codecov-action@v5
+ with:
+ fail_ci_if_error: true
+ token: ${{ secrets.CODECOV_TOKEN }}
+ files: ./packages/httio/coverage/lcov.info,./packages/rest/coverage/lcov.info
+ flags: httio,rest
+ slug: vladstsk/httio
diff --git a/README.md b/README.md
index aee052d..77241c7 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,10 @@
# **Httio**
+[](https://codecov.io/gh/vladstsk/httio?flag=httio)
[](https://bundlephobia.com/package/httio)
-[](https://github.com/vladstsk/httio/blob/main/LICENSE)
+[](https://bundlephobia.com/package/httio)
[](https://github.com/vladstsk/httio)
+[](https://github.com/vladstsk/httio/blob/main/LICENSE)
> Lightweight, type-safe HTTP client for browsers and Node.js.
> Built on top of the native `fetch` but provides a better DX with strong typing, middleware and a minimalistic API.
@@ -12,31 +14,68 @@
## Table of Contents
1. [Why Httio?](#why-httio)
-2. [Installation](#installation)
-3. [Quick Start](#quick-start)
-4. [Type-Safe Requests](#type-safe-requests)
-5. [Client Configuration](#client-configuration)
-6. [Handling Responses](#handling-responses)
-7. [Middleware](#middleware)
-8. [Error Handling](#error-handling)
-9. [Recipes](#recipes)
-10. [API Reference](#api-reference)
-11. [FAQ](#faq)
-12. [License](#license)
+2. [Httio vs. The Rest](#httio-vs-the-rest)
+3. [Installation](#installation)
+4. [Quick Start](#quick-start)
+ - [ES Modules (`import`)](#es-modules-import)
+ - [CommonJS (`require`)](#commonjs-require)
+5. [Type-Safe Requests](#type-safe-requests)
+6. [Client Configuration](#client-configuration)
+ - [Base client](#base-client)
+ - [Extending an existing client](#extending-an-existing-client)
+ - [Supported options](#supported-options)
+7. [Handling Responses](#handling-responses)
+8. [Middleware](#middleware)
+9. [Error Handling](#error-handling)
+10. [Recipes](#recipes)
+ - [Passing query params](#passing-query-params)
+ - [Uploading files](#uploading-files)
+ - [Working with streams](#working-with-streams)
+ - [Reusing an `AbortController`](#reusing-an-abortcontroller)
+11. [API Reference](#api-reference)
+ - [`client(options?)`](#clientoptions)
+ - [HttioClient` instance](#httioclient-instance)
+ - [`RequestOptions`](#requestoptions)
+12. [FAQ](#faq)
+13. [License](#license)
---
## Why Httio?
-| Feature | Description |
-|---------------------------|-------------------------------------------------------------------------------------------------------|
-| **TypeScript-first** | All public types are exported; strict compile-time checks. |
-| **Tiny footprint** | Ships as ESM + CJS, zero runtime dependencies. |
-| **Lazy parsing** | Body is not parsed automatically—_you_ decide when and how. |
+| Feature | Description |
+|------------------------------|-------------------------------------------------------------------------------------------------------|
+| **TypeScript-first** | All public types are exported; strict compile-time checks. |
+| **Tiny footprint** | Ships as ESM + CJS, zero runtime dependencies. |
+| **Lazy parsing** | Body is not parsed automatically—_you_ decide when and how. |
| **One interface everywhere** | Works in browsers, Node 18+, edge functions—no polyfills required. |
-| **Extensible** | Middleware chain for logging, auth, caching, etc. |
-| **Convenient cloning** | `extends()` lets you reuse and override base options elegantly. |
-| **Full control** | Everything from `fetch` is exposed plus syntactic sugar (`params`, `json`, `timeout`). |
+| **Extensible** | Middleware chain for logging, auth, caching, etc. |
+| **Convenient cloning** | `extends()` lets you reuse and override base options elegantly. |
+| **Full control** | Everything from `fetch` is exposed plus syntactic sugar (`params`, `json`, `timeout`). |
+
+---
+
+## Httio vs. The Rest
+
+| Library | Size
(min + gzip) | TS‑first | Browser | Node | Native `fetch` | Middleware | Retries | Deps | Notes |
+|------------------|:--------------------------------------------------------------------------:|:--------------:|:-------:|:----:|:-------------------:|:----------:|:-------:|:--------------------------------------------------------------------------------------------------------------------------------------:|-------------------------------|
+| **Httio** |  | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | [](https://bundlephobia.com/package/httio) | Modern, tiny |
+| Axios |  | ⚠️ | ✅ | ✅ | ❌ | ⚠️ | ⚠️ | [](https://bundlephobia.com/package/axios) | Heavy |
+| isomorphic-fetch |  | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | [](https://bundlephobia.com/package/isomorphic-fetch) | Simple shim |
+| ky |  | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | [](https://bundlephobia.com/package/ky) | Small, fetch‑first |
+| superagent |  | ⚠️ | ✅ | ✅ | ❌ | ✅ | ⚠️ | [](https://bundlephobia.com/package/superagent) | Classic |
+| request |  | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | [](https://bundlephobia.com/package/request) | Deprecated |
+| r2 |  | ⚠️ | ❌ | ✅ | ✅ | ❌ | ❌ | [](https://bundlephobia.com/package/r2) | Minimal |
+| phin |  | ⚠️ | ❌ | ✅ | ❌ | ❌ | ✅ | [](https://bundlephobia.com/package/phin) | Promise client |
+
+
+### Key takeaways
+
+1. **Native by design** – Httio is a thin layer above `fetch`, so knowledge transfers instantly and no shims are required in modern runtimes.
+2. **Strict types everywhere** – every public method is generically typed, turning many runtime bugs into compile-time errors.
+3. **Zero runtime deps** – smaller bundles, faster cold starts, less supply-chain risk.
+4. **Middleware without bloat** – add logging, auth or caching in a few lines, no heavyweight “interceptors” machinery.
+5. **One client → any environment** – the same code runs in browsers, Node 18+, edge workers and service workers.
---
@@ -60,26 +99,51 @@ For earlier versions add any fetch polyfill.
## Quick Start
+### ES Modules (`import`)
+
```ts
import httio from 'httio';
// GET
const users = await httio.get('https://api.example.com/users').json();
+const payload = {
+ name: 'Alice',
+ email: 'alice@example.com',
+};
+
// POST
-await httio.post(
- 'https://api.example.com/users',
- { name: 'Alice', email: 'alice@example.com' }
-).json();
+await httio.post('https://api.example.com/users', payload);
// PUT with query params
-await httio.put(
- 'https://api.example.com/users/42',
- { name: 'Bob' },
- { params: { notify: true } }
-);
+await httio.put('https://api.example.com/users/42', payload, {
+ params: {
+ notify: true,
+ },
+});
```
+### CommonJS (`require`)
+
+If your project is still on CommonJS (for example when you run Node ≤ 12 or simply prefer `require`), import the library like this:
+
+```javascript
+// Option 1 — named export (recommended)
+const { client } = require('httio');
+
+// Option 2 — default export (identical to ESM default)
+const httio = require('httio');
+
+// Example
+const api = client({
+ url: 'https://api.example.com',
+});
+
+api.get('/users/me').json().then(console.log);
+```
+
+`httio` ships with dual ESM/CJS bundles (`index.mjs` & `index.cjs`), so no extra Babel or `type: "module"` setup is required.
+
---
## Type-Safe Requests
@@ -131,35 +195,46 @@ const api = client({
`extends()` returns a _new_ instance with inherited and/or overridden options:
```ts
-const v2 = api.extends({ url: '/v2' });
-await v2.get('/status'); // → https://api.example.com/v2/status
+const v2 = api.extends({
+ url: '/v2',
+});
+
+await v2.get('/status'); // → https://api.example.com/v2/status
```
### Supported options
-| Option | Type | Default | Description |
-|-----------|----------------------------------------|------------------------------------|-------------------------------------------------|
-| `url` | `string \| URL` | – | Base URL for relative paths. |
-| `headers` | `HeadersInit` | – | Global headers. |
-| `params` | `Record` | – | Query params (auto-encoded). |
-| `timeout` | `number` | – | Abort request after N ms via `AbortController`. |
-| `url` | `string \| URL` (only in `extends`) | – | Alias for `base` when only host changes. |
-| `fetch` | `(input, init) => Promise` | `globalThis.fetch \| window.fetch` | Custom fetch implementation (handy in tests). |
+| Option | Type | Default | Description |
+|-----------|-----------------------------------------------------------------|-----------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `url` | `string \| URL` | – | Base URL for relative paths. |
+| `url` | `string \| URL` (only in `extends`) | – | Extends the base URL or replaces it if the link contains a host. |
+| `params` | Record<string, string \| number> | – | Query params (auto-encoded). |
+| `timeout` | `number` | `1000` | Abort request after N ms. |
+| `retry` | `number \| RetryOptions` | { limit: 3, delay: 1000 } | How many times (and with what delay) to retry failed requests. |
+| `fetch` | `Fetcher` | globalThis.fetch \| window.fetch | Custom fetch implementation (handy in tests). |
+
+> You can also pass any field accepted by the standard Fetch `Request`/`Response` objects; those options are forwarded unchanged.
---
## Handling Responses
-Httio returns a **wrapper** around `Response` with extra parsing helpers.
-They are _lazy_—HTTP call is sent immediately, but body reading starts only when a parser is invoked.
+Httio returns a **wrapper** of type `HttioBody & Promise` around the native `Response`.
+
+It behaves as both:
+
+1. `Promise` – you can `await` it or call `.then()`.
+2. `HttioBody` – exposes lazy body-parsers: `json()`, `text()`, `blob()`, `buffer()`, `bytes()`, `stream()`.
-| Method | Return type |
-|------------------|------------------------|
-| `json()` | `Promise` |
-| `text()` | `Promise` |
-| `blob()` | `Promise` |
-| `bytes()` | `Promise` |
-| `buffer()` | `Promise` |
+The HTTP request is dispatched immediately, while the response body is read only when one of the parsers is invoked.
+
+| Method | Return type |
+|------------------|---------------------------|
+| `json()` | `Promise` |
+| `text()` | `Promise` |
+| `blob()` | `Promise` |
+| `bytes()` | `Promise` |
+| `buffer()` | `Promise` |
| `stream()` | `Promise` |
Example of deferred parsing:
@@ -179,23 +254,23 @@ const data = await response.json();
## Middleware
-Middleware are async functions `(req, next) => Response` executed in a chain:
+Middleware are async functions `(request: HttioRequest, next: NextMiddleware): MaybePromise` executed in a chain:
```ts
import httio, { type Middleware } from 'httio';
const auth: Middleware = async (req, next) => {
req.headers.set('Authorization', `Bearer ${getToken()}`);
-
+
return next(req);
};
const logger: Middleware = async (req, next) => {
const started = performance.now();
-
const res = await next(req);
+
console.log(`${req.method} ${req.url} → ${res.status} (${Date.now() - started} ms)`);
-
+
return res;
};
@@ -213,34 +288,6 @@ HTTP status `4xx/5xx` triggers a `HttpError`:
```ts
import httio, { HttpError } from 'httio';
-try {
- await httio.get('/admin').json();
-} catch (err) {
- if (err instanceof HttpError) {
- console.error(`Error ${err.status}: ${err.message}`);
- } else {
- throw err; // non-HTTP error
- }
-}
-```
-
-`HttpError` exposes:
-
-| Property | Description |
-|-------------|----------------------------------|
-| `status` | HTTP status code |
-| `statusText`| Human-readable text (if any) |
-| `response` | Original `Response` object |
-| `url` | Final URL (after redirects) |
-
----
-
-## Error Handling
-
-HTTP status codes in the `4xx/5xx` range throw an instance of `HttpError`:
-```ts
-import httio, { HttpError } from 'httio';
-
try {
await httio.get('/admin').json();
} catch (err) {
@@ -257,14 +304,14 @@ try {
}
```
-`HttpError` exposes only two properties:
+`HttpError` extends the native `Error` class and additionally exposes:
-| Property | Type | Description |
-|-----------|-----------------|------------------------------------------|
-| `request` | `HttioRequest` | Object representing the original request |
-| `response`| `HttioResponse` | Object representing the server response |
+| Property | Type | Description |
+|------------|-----------------|---------------------------------------------------------------|
+| `request` | `HttioRequest` | The request object that triggered the error (post-middleware) |
+| `response` | `HttioResponse` | The received response (contains status, headers, etc.) |
-You can read any additional details (status code, headers, body, etc.) from the `response` object itself.
+Standard `Error` fields (`name`, `message`, `stack`) remain available.
---
@@ -297,11 +344,11 @@ const reader = stream.getReader();
while (true) {
const { value, done } = await reader.read();
-
+
if (done) {
break;
}
-
+
console.log(new TextDecoder().decode(value));
}
```
@@ -327,20 +374,21 @@ See [configuration](#supported-options) for the list of parameters.
### `HttioClient` instance
-| Method | Description |
-|-------------------------------------|------------------------------------------------|
+| Method | Description |
+|-------------------------------------|----------------------------------------------------------------------|
| `get(url, opts?)` | `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS` — same signature |
-| `extends(opts)` | Returns a new client inheriting options |
-| `use(...middleware)` | Adds middleware |
+| `extends(opts)` | Returns a new client inheriting options |
+| `use(...middleware)` | Adds middleware |
### `RequestOptions`
Extends the native `RequestInit`:
-| Extra field | Type | Description |
-|-------------|---------------------------------|--------------------------------|
-| `params` | `Record` | Adds query parameters |
-| `timeout` | `number` | Aborts the request after N ms |
+| Extra field | Type | Description |
+|-------------|-----------------------------------------------------------------|----------------------------------------------------------------|
+| `params` | Record<string, string \| number> | Adds query parameters |
+| `timeout` | `number` | Aborts the request after N ms |
+| `retry` | `number \| RetryOptions` | How many times (and with what delay) to retry failed requests. |
### Types
@@ -373,7 +421,10 @@ import { client } from 'httio';
import { createFetchMock } from '@mswjs/interceptors';
const fetchMock = createFetchMock();
-const api = client({ fetch: fetchMock });
+
+const api = client({
+ fetch: fetchMock,
+});
```
---
diff --git a/packages/httio/README.md b/packages/httio/README.md
index fc21c87..f2e3850 100644
--- a/packages/httio/README.md
+++ b/packages/httio/README.md
@@ -1,8 +1,10 @@
# **Httio**
+[](https://codecov.io/gh/vladstsk/httio?flag=httio)
[](https://bundlephobia.com/package/httio)
-[](https://github.com/vladstsk/httio/blob/main/LICENSE)
+[](https://bundlephobia.com/package/httio)
[](https://github.com/vladstsk/httio)
+[](https://github.com/vladstsk/httio/blob/main/LICENSE)
> Lightweight, type-safe HTTP client for browsers and Node.js.
> Built on top of the native `fetch` but provides a better DX with strong typing, middleware and a minimalistic API.
@@ -12,31 +14,68 @@
## Table of Contents
1. [Why Httio?](#why-httio)
-2. [Installation](#installation)
-3. [Quick Start](#quick-start)
-4. [Type-Safe Requests](#type-safe-requests)
-5. [Client Configuration](#client-configuration)
-6. [Handling Responses](#handling-responses)
-7. [Middleware](#middleware)
-8. [Error Handling](#error-handling)
-9. [Recipes](#recipes)
-10. [API Reference](#api-reference)
-11. [FAQ](#faq)
-12. [License](#license)
+2. [Httio vs. The Rest](#httio-vs-the-rest)
+3. [Installation](#installation)
+4. [Quick Start](#quick-start)
+ - [ES Modules (`import`)](#es-modules-import)
+ - [CommonJS (`require`)](#commonjs-require)
+5. [Type-Safe Requests](#type-safe-requests)
+6. [Client Configuration](#client-configuration)
+ - [Base client](#base-client)
+ - [Extending an existing client](#extending-an-existing-client)
+ - [Supported options](#supported-options)
+7. [Handling Responses](#handling-responses)
+8. [Middleware](#middleware)
+9. [Error Handling](#error-handling)
+10. [Recipes](#recipes)
+ - [Passing query params](#passing-query-params)
+ - [Uploading files](#uploading-files)
+ - [Working with streams](#working-with-streams)
+ - [Reusing an `AbortController`](#reusing-an-abortcontroller)
+11. [API Reference](#api-reference)
+ - [`client(options?)`](#clientoptions)
+ - [HttioClient` instance](#httioclient-instance)
+ - [`RequestOptions`](#requestoptions)
+12. [FAQ](#faq)
+13. [License](#license)
---
## Why Httio?
-| Feature | Description |
-|---------------------------|-------------------------------------------------------------------------------------------------------|
-| **TypeScript-first** | All public types are exported; strict compile-time checks. |
-| **Tiny footprint** | Ships as ESM + CJS, zero runtime dependencies. |
-| **Lazy parsing** | Body is not parsed automatically—_you_ decide when and how. |
+| Feature | Description |
+|------------------------------|-------------------------------------------------------------------------------------------------------|
+| **TypeScript-first** | All public types are exported; strict compile-time checks. |
+| **Tiny footprint** | Ships as ESM + CJS, zero runtime dependencies. |
+| **Lazy parsing** | Body is not parsed automatically—_you_ decide when and how. |
| **One interface everywhere** | Works in browsers, Node 18+, edge functions—no polyfills required. |
-| **Extensible** | Middleware chain for logging, auth, caching, etc. |
-| **Convenient cloning** | `extends()` lets you reuse and override base options elegantly. |
-| **Full control** | Everything from `fetch` is exposed plus syntactic sugar (`params`, `json`, `timeout`). |
+| **Extensible** | Middleware chain for logging, auth, caching, etc. |
+| **Convenient cloning** | `extends()` lets you reuse and override base options elegantly. |
+| **Full control** | Everything from `fetch` is exposed plus syntactic sugar (`params`, `json`, `timeout`). |
+
+---
+
+## Httio vs. The Rest
+
+| Library | Size
(min + gzip) | TS‑first | Browser | Node | Native `fetch` | Middleware | Retries | Deps | Notes |
+|------------------|:--------------------------------------------------------------------------:|:--------------:|:-------:|:----:|:-------------------:|:----------:|:-------:|:--------------------------------------------------------------------------------------------------------------------------------------:|-------------------------------|
+| **Httio** |  | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | [](https://bundlephobia.com/package/httio) | Modern, tiny |
+| Axios |  | ⚠️ | ✅ | ✅ | ❌ | ⚠️ | ⚠️ | [](https://bundlephobia.com/package/axios) | Heavy |
+| isomorphic-fetch |  | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | [](https://bundlephobia.com/package/isomorphic-fetch) | Simple shim |
+| ky |  | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | [](https://bundlephobia.com/package/ky) | Small, fetch‑first |
+| superagent |  | ⚠️ | ✅ | ✅ | ❌ | ✅ | ⚠️ | [](https://bundlephobia.com/package/superagent) | Classic |
+| request |  | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | [](https://bundlephobia.com/package/request) | Deprecated |
+| r2 |  | ⚠️ | ❌ | ✅ | ✅ | ❌ | ❌ | [](https://bundlephobia.com/package/r2) | Minimal |
+| phin |  | ⚠️ | ❌ | ✅ | ❌ | ❌ | ✅ | [](https://bundlephobia.com/package/phin) | Promise client |
+
+
+### Key takeaways
+
+1. **Native by design** – Httio is a thin layer above `fetch`, so knowledge transfers instantly and no shims are required in modern runtimes.
+2. **Strict types everywhere** – every public method is generically typed, turning many runtime bugs into compile-time errors.
+3. **Zero runtime deps** – smaller bundles, faster cold starts, less supply-chain risk.
+4. **Middleware without bloat** – add logging, auth or caching in a few lines, no heavyweight “interceptors” machinery.
+5. **One client → any environment** – the same code runs in browsers, Node 18+, edge workers and service workers.
---
@@ -60,26 +99,51 @@ For earlier versions add any fetch polyfill.
## Quick Start
+### ES Modules (`import`)
+
```ts
import httio from 'httio';
// GET
const users = await httio.get('https://api.example.com/users').json();
+const payload = {
+ name: 'Alice',
+ email: 'alice@example.com',
+};
+
// POST
-await httio.post(
- 'https://api.example.com/users',
- { name: 'Alice', email: 'alice@example.com' }
-).json();
+await httio.post('https://api.example.com/users', payload);
// PUT with query params
-await httio.put(
- 'https://api.example.com/users/42',
- { name: 'Bob' },
- { params: { notify: true } }
-);
+await httio.put('https://api.example.com/users/42', payload, {
+ params: {
+ notify: true,
+ },
+});
```
+### CommonJS (`require`)
+
+If your project is still on CommonJS (for example when you run Node ≤ 12 or simply prefer `require`), import the library like this:
+
+```javascript
+// Option 1 — named export (recommended)
+const { client } = require('httio');
+
+// Option 2 — default export (identical to ESM default)
+const httio = require('httio');
+
+// Example
+const api = client({
+ url: 'https://api.example.com',
+});
+
+api.get('/users/me').json().then(console.log);
+```
+
+`httio` ships with dual ESM/CJS bundles (`index.mjs` & `index.cjs`), so no extra Babel or `type: "module"` setup is required.
+
---
## Type-Safe Requests
@@ -131,35 +195,46 @@ const api = client({
`extends()` returns a _new_ instance with inherited and/or overridden options:
```ts
-const v2 = api.extends({ url: '/v2' });
-await v2.get('/status'); // → https://api.example.com/v2/status
+const v2 = api.extends({
+ url: '/v2',
+});
+
+await v2.get('/status'); // → https://api.example.com/v2/status
```
### Supported options
-| Option | Type | Default | Description |
-|-----------|----------------------------------------|------------------------------------|-------------------------------------------------|
-| `url` | `string \| URL` | – | Base URL for relative paths. |
-| `headers` | `HeadersInit` | – | Global headers. |
-| `params` | `Record` | – | Query params (auto-encoded). |
-| `timeout` | `number` | – | Abort request after N ms via `AbortController`. |
-| `url` | `string \| URL` (only in `extends`) | – | Alias for `base` when only host changes. |
-| `fetch` | `(input, init) => Promise` | `globalThis.fetch \| window.fetch` | Custom fetch implementation (handy in tests). |
+| Option | Type | Default | Description |
+|-----------|-----------------------------------------------------------------|-----------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `url` | `string \| URL` | – | Base URL for relative paths. |
+| `url` | `string \| URL` (only in `extends`) | – | Extends the base URL or replaces it if the link contains a host. |
+| `params` | Record<string, string \| number> | – | Query params (auto-encoded). |
+| `timeout` | `number` | `1000` | Abort request after N ms. |
+| `retry` | `number \| RetryOptions` | { limit: 3, delay: 1000 } | How many times (and with what delay) to retry failed requests. |
+| `fetch` | `Fetcher` | globalThis.fetch \| window.fetch | Custom fetch implementation (handy in tests). |
+
+> You can also pass any field accepted by the standard Fetch `Request`/`Response` objects; those options are forwarded unchanged.
---
## Handling Responses
-Httio returns a **wrapper** around `Response` with extra parsing helpers.
-They are _lazy_—HTTP call is sent immediately, but body reading starts only when a parser is invoked.
+Httio returns a **wrapper** of type `HttioBody & Promise` around the native `Response`.
+
+It behaves as both:
+
+1. `Promise` – you can `await` it or call `.then()`.
+2. `HttioBody` – exposes lazy body-parsers: `json()`, `text()`, `blob()`, `buffer()`, `bytes()`, `stream()`.
-| Method | Return type |
-|------------------|------------------------|
-| `json()` | `Promise` |
-| `text()` | `Promise` |
-| `blob()` | `Promise` |
-| `bytes()` | `Promise` |
-| `buffer()` | `Promise` |
+The HTTP request is dispatched immediately, while the response body is read only when one of the parsers is invoked.
+
+| Method | Return type |
+|------------------|---------------------------|
+| `json()` | `Promise` |
+| `text()` | `Promise` |
+| `blob()` | `Promise` |
+| `bytes()` | `Promise` |
+| `buffer()` | `Promise` |
| `stream()` | `Promise` |
Example of deferred parsing:
@@ -179,23 +254,23 @@ const data = await response.json();
## Middleware
-Middleware are async functions `(req, next) => Response` executed in a chain:
+Middleware are async functions `(request: HttioRequest, next: NextMiddleware): MaybePromise` executed in a chain:
```ts
import httio, { type Middleware } from 'httio';
const auth: Middleware = async (req, next) => {
req.headers.set('Authorization', `Bearer ${getToken()}`);
-
+
return next(req);
};
const logger: Middleware = async (req, next) => {
const started = performance.now();
-
const res = await next(req);
+
console.log(`${req.method} ${req.url} → ${res.status} (${Date.now() - started} ms)`);
-
+
return res;
};
@@ -213,34 +288,6 @@ HTTP status `4xx/5xx` triggers a `HttpError`:
```ts
import httio, { HttpError } from 'httio';
-try {
- await httio.get('/admin').json();
-} catch (err) {
- if (err instanceof HttpError) {
- console.error(`Error ${err.status}: ${err.message}`);
- } else {
- throw err; // non-HTTP error
- }
-}
-```
-
-`HttpError` exposes:
-
-| Property | Description |
-|-------------|----------------------------------|
-| `status` | HTTP status code |
-| `statusText`| Human-readable text (if any) |
-| `response` | Original `Response` object |
-| `url` | Final URL (after redirects) |
-
----
-
-## Error Handling
-
-HTTP status codes in the `4xx/5xx` range throw an instance of `HttpError`:
-```ts
-import httio, { HttpError } from 'httio';
-
try {
await httio.get('/admin').json();
} catch (err) {
@@ -257,14 +304,14 @@ try {
}
```
-`HttpError` exposes only two properties:
+`HttpError` extends the native `Error` class and additionally exposes:
-| Property | Type | Description |
-|-----------|-----------------|------------------------------------------|
-| `request` | `HttioRequest` | Object representing the original request |
-| `response`| `HttioResponse` | Object representing the server response |
+| Property | Type | Description |
+|------------|-----------------|---------------------------------------------------------------|
+| `request` | `HttioRequest` | The request object that triggered the error (post-middleware) |
+| `response` | `HttioResponse` | The received response (contains status, headers, etc.) |
-You can read any additional details (status code, headers, body, etc.) from the `response` object itself.
+Standard `Error` fields (`name`, `message`, `stack`) remain available.
---
@@ -297,11 +344,11 @@ const reader = stream.getReader();
while (true) {
const { value, done } = await reader.read();
-
+
if (done) {
break;
}
-
+
console.log(new TextDecoder().decode(value));
}
```
@@ -327,20 +374,21 @@ See [configuration](#supported-options) for the list of parameters.
### `HttioClient` instance
-| Method | Description |
-|-------------------------------------|------------------------------------------------|
+| Method | Description |
+|-------------------------------------|----------------------------------------------------------------------|
| `get(url, opts?)` | `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS` — same signature |
-| `extends(opts)` | Returns a new client inheriting options |
-| `use(...middleware)` | Adds middleware |
+| `extends(opts)` | Returns a new client inheriting options |
+| `use(...middleware)` | Adds middleware |
### `RequestOptions`
Extends the native `RequestInit`:
-| Extra field | Type | Description |
-|-------------|---------------------------------|--------------------------------|
-| `params` | `Record` | Adds query parameters |
-| `timeout` | `number` | Aborts the request after N ms |
+| Extra field | Type | Description |
+|-------------|-----------------------------------------------------------------|----------------------------------------------------------------|
+| `params` | Record<string, string \| number> | Adds query parameters |
+| `timeout` | `number` | Aborts the request after N ms |
+| `retry` | `number \| RetryOptions` | How many times (and with what delay) to retry failed requests. |
### Types
@@ -373,7 +421,10 @@ import { client } from 'httio';
import { createFetchMock } from '@mswjs/interceptors';
const fetchMock = createFetchMock();
-const api = client({ fetch: fetchMock });
+
+const api = client({
+ fetch: fetchMock,
+});
```
---