Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 40 additions & 9 deletions docs/v1/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The client focuses on predictable behavior, extensibility, and a clean developer
- structured error classes
- auth support
- lifecycle hooks
- retry support
- custom `fetch` support

## Quick example
Expand All @@ -46,16 +47,46 @@ console.log(user.name);

## How requests work

A request in `@dfsync/client` follows this flow:
A request in `@dfsync/client` goes through the following lifecycle:

1. build final URL from `baseUrl`, `path`, and optional query params
2. merge default, client-level, and request-level headers
3. apply auth configuration
4. run `beforeRequest` hooks
5. send request with `fetch`
6. parse response as JSON, text, or `undefined` for `204`
7. throw structured errors for failed requests
8. run `afterResponse` or `onError` hooks
1. Build request URL

The final URL is constructed from `baseUrl`, `path`, and optional query parameters.

2. Merge headers

Default headers, client-level headers, and request-level headers are combined.

3. Apply authentication

The configured auth strategy (Bearer, API key, or custom) is applied to the request.

4. Run `beforeRequest` hooks

Hooks can modify the request before it is sent.

5. Execute the HTTP request

The request is sent using the Fetch API.

6. Retry if necessary

If the request fails with a retryable error, it may be retried according to the configured retry policy.

7. Parse the response

The response body is parsed automatically:
- JSON → parsed object
- text → string
- `204 No Content` → `undefined`

8. Handle errors

Non-success responses and network failures are converted into structured errors.

9. Run response hooks
- `afterResponse` runs for successful responses
- `onError` runs when an error occurs

## Runtime requirements

Expand Down
179 changes: 179 additions & 0 deletions docs/v1/retry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# Retry

`@dfsync/client` supports configurable retry policies for transient failures.

Retries are useful when communicating with external services that may temporarily fail or return `5xx` responses.

The retry behavior can be configured globally for the client or overridden per request.

---

## Basic retry configuration

```ts
import { createClient } from '@dfsync/client';

const client = createClient({
baseUrl: 'https://api.example.com',
retry: {
attempts: 2,
},
});
```

If a retryable error occurs, the request will be retried up to the configured number of attempts.

## Retry conditions

By default, retries happen for:

- network errors
- HTTP 5xx responses

Example:

```ts
const client = createClient({
baseUrl: 'https://api.example.com',
retry: {
attempts: 3,
},
});
```

## Retry backoff

Two retry strategies are supported:

### Fixed delay

```ts
const client = createClient({
baseUrl: 'https://api.example.com',
retry: {
attempts: 3,
backoff: 'fixed',
baseDelayMs: 300,
},
});
```

Retry delays:

```bash
300ms
300ms
300ms
```

### Exponential backoff

```ts
const client = createClient({
baseUrl: 'https://api.example.com',
retry: {
attempts: 3,
backoff: 'exponential',
baseDelayMs: 300,
},
});
```

Retry delays:

```bash
300ms
600ms
1200ms
```

If `attempts` is `0` (default), no retries are performed and retry delays are ignored.

## Retry methods

By default retries apply to:

- `GET`
- `PUT`
- `DELETE`

POST requests are **not retried by default**.

Example enabling POST retries:

```ts
const client = createClient({
baseUrl: 'https://api.example.com',
retry: {
attempts: 2,
retryMethods: ['GET', 'POST'],
},
});
```

## Retry conditions configuration

You can control which errors trigger retries.

Supported conditions:

- network-error
- 5xx
- 429

Example:

```ts
const client = createClient({
baseUrl: 'https://api.example.com',
retry: {
attempts: 2,
retryOn: ['network-error', '5xx', '429'],
},
});
```

## Per-request retry override

Request-level configuration overrides the client configuration.

```ts
await client.get('/users', {
retry: {
attempts: 1,
},
});
```

## Retry and hooks

Hooks behave as follows when retries are enabled:

| Hook | Behavior |
| --------------- | ------------------------------------- |
| `beforeRequest` | executed on every retry attempt |
| `afterResponse` | executed only on successful response |
| `onError` | executed once after the final failure |

Example:

```ts
const client = createClient({
baseUrl: 'https://api.example.com',
retry: { attempts: 2 },
hooks: {
onError(ctx) {
console.error(ctx.error);
},
},
});
```

## Summary

Retry is designed for **safe and predictable service-to-service communication** and works well for:

- microservices
- external APIs
- background workers
- integration services
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>dfsync — Reliable HTTP client for Node.js Microservices</title>
<title>dfsync — Reliable HTTP communication for Node.js environments.</title>
<meta
name="description"
content="dfsync is a reliable HTTP client for service-to-service communication in Node.js and TypeScript."
Expand Down
27 changes: 19 additions & 8 deletions src/components/Features/Features.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import HubIcon from '@mui/icons-material/Hub';
import SecurityIcon from '@mui/icons-material/Security';
import VpnKeyIcon from '@mui/icons-material/VpnKey';
import LockIcon from '@mui/icons-material/Lock';
import ReplayIcon from '@mui/icons-material/Replay';
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import BoltIcon from '@mui/icons-material/Bolt';
import { Card, CardContent, Container, Grid, Stack, Typography } from '@mui/material';

Expand All @@ -18,10 +20,19 @@ const items = [
'A clean, predictable HTTP client setup without repeating the same boilerplate in every project.',
},
{
icon: <VpnKeyIcon fontSize="large" />,
title: 'Auth & lifecycle hooks',
description:
'Built-in support for bearer tokens, API keys, and request lifecycle hooks like beforeRequest, afterResponse, and onError.',
icon: <LockIcon fontSize="large" />,
title: 'Auth support',
description: 'Built-in support for bearer tokens, API keys, and custom auth flows.',
},
{
icon: <ReplayIcon fontSize="large" />,
title: 'Retry support',
description: 'Built-in configurable retry policies for transient failures.',
},
{
icon: <DeviceHubIcon fontSize="large" />,
title: 'Lifecycle hooks',
description: 'Built-in request lifecycle hooks like beforeRequest, afterResponse, and onError.',
},
{
icon: <SecurityIcon fontSize="large" />,
Expand All @@ -36,11 +47,11 @@ export const Features = () => {
<Container maxWidth="lg" sx={{ pb: { xs: 8, md: 12 } }}>
<Stack spacing={2} sx={{ mb: 5 }}>
<Typography variant="h2" sx={{ fontSize: { xs: '2rem', md: '3rem' } }}>
Why dfsync
Why @dfsync/client
</Typography>
<Typography color="text.secondary" sx={{ maxWidth: 720 }}>
A focused foundation for dependable HTTP communication between services — with sensible
defaults, auth strategies, and lifecycle hooks.
A lightweight HTTP client for service-to-service communication, with sensible defaults,
authentication strategies, lifecycle hooks, and retry support.
</Typography>
</Stack>

Expand Down
2 changes: 1 addition & 1 deletion src/components/Footer/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const Footer = () => {
npm
</Link>
<Link
href="https://github.com/dfsyncjs/dfsync/tree/main/packages/client"
href="https://github.com/dfsyncjs/dfsync"
target="_blank"
rel="noreferrer"
underline="hover"
Expand Down
2 changes: 1 addition & 1 deletion src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const Header = () => {
</Button>
<Button
color="inherit"
href="https://github.com/dfsyncjs/dfsync/tree/main/packages/client"
href="https://github.com/dfsyncjs/dfsync"
target="_blank"
rel="noreferrer"
startIcon={<GitHubIcon />}
Expand Down
20 changes: 9 additions & 11 deletions src/components/Hero/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ export const Hero = () => {
<Box
component="section"
sx={{
py: { xs: 10, md: 14 },
py: { xs: 6, md: 12 },
background: 'radial-gradient(circle at top, rgba(56,189,248,0.15), transparent 40%)',
}}
>
<Container maxWidth="lg">
<Stack spacing={4} alignItems="flex-start">
<Chip label="Open-source TypeScript HTTP library" color="primary" />
<Chip label="Open-source toolkit for backend communication" color="primary" />

<Box>
<Typography
Expand All @@ -27,17 +27,13 @@ export const Hero = () => {
maxWidth: 900,
}}
>
Reliable HTTP communication for modern services.
Reliable toolkit for service-to-service communication
</Typography>

<Chip
label="NEW · Auth & lifecycle hooks"
label="Built for microservices, internal APIs, integrations, and background workers"
color="primary"
variant="outlined"
sx={{
mt: 2,
fontWeight: 500,
}}
sx={{ mt: 1, mb: 1 }}
/>

<ProjectBadges />
Expand All @@ -52,8 +48,9 @@ export const Hero = () => {
lineHeight: 1.6,
}}
>
dfsync provides a unified HTTP client with sensible defaults for service-to-service
communication across microservices, internal APIs, workers, and external integrations.
The first package, <strong>@dfsync/client</strong>, provides a lightweight and
reliable HTTP client for service-to-service communication in Node.js, with built-in
retry, authentication, and lifecycle hooks.
</Typography>
</Box>

Expand Down Expand Up @@ -113,6 +110,7 @@ export const Hero = () => {

const client = createClient({
baseURL: "https://api.example.com",
retry: { attempts: 3 }
});

const users = await client.get("/users");`}
Expand Down
Loading
Loading