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
35 changes: 35 additions & 0 deletions docs/v1/api-reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# API Reference

## createClient

Creates a configured HTTP client.

## Client methods

- `get`
- `post`
- `put`
- `patch`
- `delete`

## Configuration

- `baseUrl`
- `timeout`
- `retry`
- `auth`
- `hooks`

## Hooks

- `beforeRequest`
- `afterResponse`
- `onError`
- `onRetry`

## Errors

- `HttpError`
- `NetworkError`
- `TimeoutError`
- `RequestAbortedError`
104 changes: 96 additions & 8 deletions docs/v1/create-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@

Use `createClient` to create a reusable HTTP client instance.

It provides a consistent way to configure:

- base URL and default headers
- timeouts and retries
- auth
- lifecycle hooks
- request observability metadata

## Basic client

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

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

Expand Down Expand Up @@ -59,6 +67,20 @@ type ClientConfig = {
};
```

Hook configuration supports:

- `beforeRequest`
- `afterResponse`
- `onError`
- `onRetry`

Retry configuration supports:

- retry attempts
- retry conditions
- backoff strategy
- `Retry-After` handling

## HTTP methods

The client provides a predictable set of methods:
Expand Down Expand Up @@ -146,6 +168,10 @@ type RequestOptions = {
};
```

`requestId` can be provided explicitly when you want to correlate logs or trace a request across services.

Request-level `retry` overrides client-level retry settings.

## Low-level request

```ts
Expand All @@ -155,13 +181,13 @@ const result = await client.request({
body: {
type: 'user.created',
},
headers: {
'x-request-id': 'req-123',
},
requestId: 'req-123',
timeout: 3000,
});
```

If both `requestId` and `x-request-id` header are provided, `x-request-id` takes precedence.

### Request Config

```ts
Expand All @@ -180,14 +206,26 @@ type RequestConfig = {

## Request context

Each request is executed within a request context that contains:
Each request attempt is executed within a request context that contains:

- `requestId` — unique identifier for the request
- `attempt` — current retry attempt
- `requestId` — stable identifier for the full request lifecycle
- `attempt` — current retry attempt (zero-based)
- `maxAttempts` — total number of allowed attempts, including the initial request
- `signal` — AbortSignal for cancellation
- `startedAt` — request start timestamp

This context is available in all lifecycle hooks.
Completed attempts may also expose:

- `endedAt` — request end timestamp
- `durationMs` — total duration for the current attempt

Retry-specific contexts may also expose:

- `retryDelayMs`
- `retryReason`
- `retrySource`

This context is available through lifecycle hooks.

## Request ID

Expand All @@ -196,9 +234,12 @@ Each request has a `requestId` that is:
- automatically generated by default
- can be overridden per request
- propagated via the `x-request-id` header
- remains stable across retries

This allows tracing requests across services.

It also makes retries easier to correlate in logs and monitoring systems.

### Example

```ts
Expand Down Expand Up @@ -275,3 +316,50 @@ If request body is a string, the client:

- sends it as-is
- does not force a `content-type`

## Retry observability

Retries can be observed using lifecycle hooks.

```ts
const client = createClient({
baseUrl: 'https://api.example.com',
retry: {
attempts: 2,
retryOn: ['5xx', '429'],
backoff: 'exponential',
baseDelayMs: 300,
},
hooks: {
onRetry({ requestId, attempt, maxAttempts, retryDelayMs, retryReason, retrySource }) {
console.log({
requestId,
attempt,
maxAttempts,
retryDelayMs,
retryReason,
retrySource,
});
},
},
});
```

This is useful for logging, monitoring, and debugging retry behavior.

## Retry-After support

When a retryable response includes a `Retry-After` header, `@dfsync/client` uses that value before falling back to the configured backoff strategy.

Supported formats:

- seconds
- HTTP-date

If the header value is invalid, `@dfsync/client` falls back to normal retry backoff.

## Related guides

- See **Hooks** for lifecycle hooks and observability metadata
- See **Retry** for retry conditions, backoff, and `Retry-After`
- See **Errors** for failure behavior and error types
42 changes: 16 additions & 26 deletions docs/v1/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,37 +31,27 @@ The client focuses on predictable behavior, extensibility, and a clean developer
```ts
import { createClient } from '@dfsync/client';

type User = {
id: string;
name: string;
};

const client = createClient({
baseUrl: 'https://api.example.com',
timeout: 5000,
retry: {
attempts: 2,
retryOn: ['5xx', 'network-error'],
},
hooks: {
onRetry: ({ requestId, retryReason, retryDelayMs }) => {
console.log(`[${requestId}] retrying in ${retryDelayMs}ms`, retryReason);
},
},
});

const user = await client.get<User>('/users/1');
const data = await client.get('/health');
```

## How requests work

A request in `@dfsync/client` follows a predictable lifecycle:

1. create request context
2. build final URL from `baseUrl`, `path`, and query params
3. merge client and request headers
4. apply authentication
5. attach request metadata (e.g. `x-request-id`)
6. run `beforeRequest` hooks
7. send request with `fetch`
8. retry on failure (if configured)
9. parse response (JSON, text, or `undefined`)
10. run `afterResponse` or `onError` hooks

## Runtime requirements

- Node.js >= 20
- a working fetch implementation
This gives you:

If you do not pass a custom `fetch`, the client uses `globalThis.fetch`.
- timeouts
- retries
- structured errors
- request lifecycle hooks
- built-in retry observability
Loading
Loading