Use createClient to create a reusable HTTP client instance.
import { createClient } from '@dfsync/client';
const client = createClient({
baseURL: 'https://api.example.com',
});const client = createClient({
baseUrl: 'https://api.example.com',
timeout: 5000,
});const client = createClient({
baseUrl: 'https://api.example.com',
headers: {
'x-service-name': 'billing-worker',
},
});const client = createClient({
baseUrl: 'https://api.example.com',
fetch: globalThis.fetch,
});type ClientConfig = {
baseUrl: string;
timeout?: number;
headers?: Record<string, string>;
fetch?: typeof globalThis.fetch;
auth?: {
// see Auth section
};
hooks?: {
// see Hooks section
};
retry?: RetryConfig;
};The client provides a predictable set of methods:
client.get(path, options?)
client.delete(path, options?)
client.post(path, body?, options?)
client.put(path, body?, options?)
client.patch(path, body?, options?)
client.request(config)
getanddeletedo not accept bodypost,put, andpatchaccept request body as the second argumentoptionsis used for headers, query, timeout, retry, and other settings
type User = {
id: string;
name: string;
};
const user = await client.get<User>('/users/1');const users = await client.get('/users', {
query: {
page: 1,
active: true,
},
});const created = await client.post('/users', {
name: 'Tom',
email: 'tom@example.com',
});const updated = await client.put('/users/1', {
name: 'Tom Updated',
});const updatedUser = await client.patch('/users/1', {
name: 'Jane',
});const result = await client.delete('/users/1');type RequestOptions = {
query?: Record<string, string | number | boolean | null | undefined>;
headers?: Record<string, string>;
timeout?: number;
retry?: RetryConfig;
signal?: AbortSignal;
requestId?: string;
};const result = await client.request({
method: 'POST',
path: '/events',
body: {
type: 'user.created',
},
headers: {
'x-request-id': 'req-123',
},
timeout: 3000,
});type RequestConfig = {
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
path: string;
query?: Record<string, string | number | boolean | null | undefined>;
body?: unknown;
headers?: Record<string, string>;
timeout?: number;
retry?: RetryConfig;
signal?: AbortSignal;
requestId?: string;
};Each request is executed within a request context that contains:
requestId— unique identifier for the requestattempt— current retry attemptsignal— AbortSignal for cancellationstartedAt— request start timestamp
This context is available in all lifecycle hooks.
Each request has a requestId that is:
- automatically generated by default
- can be overridden per request
- propagated via the
x-request-idheader
This allows tracing requests across services.
await client.get('/users', {
requestId: 'req_123',
});You can also override the header directly:
await client.get('/users', {
headers: {
'x-request-id': 'custom-id',
},
});Requests can be cancelled using AbortSignal:
const controller = new AbortController();
const promise = client.get('/users', {
signal: controller.signal,
});
controller.abort();Cancellation is treated differently from timeouts:
- timeout →
TimeoutError - manual cancellation →
RequestAbortedError
Headers are resolved as part of the request lifecycle in the following order:
- default headers
- client headers
- request headers
- auth modifications
- beforeRequest hook modifications
This means request-level headers override client-level headers, and auth can still overwrite auth-related header values.
Timeout is resolved as part of the request lifecycle:
- request-level timeout
- client-level timeout
- default timeout:
5000
Responses are parsed automatically during the response phase:
application/json→ parsed JSON- other content types → text
204 No Content→undefined
If request body is an object, the client:
- serializes it with
JSON.stringify(...) - sets
content-type: application/jsononly if you did not set it yourself
If request body is a string, the client:
- sends it as-is
- does not force a
content-type