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
11 changes: 11 additions & 0 deletions .changeset/release-v0.1.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@ciscode/api-kit': minor
---

Initial release of @ciscode/api-kit v0.1.0

- `createApiClient` factory with typed `get`, `post`, `put`, `patch`, `delete` methods
- Built-in auth token injection via `getToken` config
- Composable request, response, and error interceptors
- `ApiError` class normalizing all HTTP/network errors
- Configurable retry with exponential backoff
187 changes: 154 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,165 @@
# React TypeScript DeveloperKit (Template)
# @ciscode/api-kit

Template repository for building reusable React TypeScript **npm libraries**
(components + hooks + utilities).
Typed HTTP client for TypeScript applications. Wraps Axios internally and exposes
a clean, strongly-typed API with zero Axios types in the public surface.

## What you get
## Features

- ESM + CJS + Types build (tsup)
- Vitest testing
- ESLint + Prettier (flat config)
- Changesets (manual release flow, no automation PR)
- Husky (pre-commit + pre-push)
- Enforced public API via `src/index.ts`
- Dependency-free styling (Tailwind-compatible by convention only)
- `react` and `react-dom` as peerDependencies
- Typed `get`, `post`, `put`, `patch`, `delete` methods returning `Promise<T>`
- Built-in auth token injection via `getToken`
- Composable request, response, and error interceptors
- `ApiError` class with `status`, `code`, `message`, and `details`
- Configurable retry with exponential backoff
- No Axios types leak to consumers

## Package structure
## Installation

- `src/components` – reusable UI components
- `src/hooks` – reusable React hooks
- `src/utils` – framework-agnostic utilities
- `src/index.ts` – **only public API** (no deep imports allowed)
```bash
npm install @ciscode/api-kit
```

Anything not exported from `src/index.ts` is considered private.
## Quick Start

## Scripts
```ts
import { createApiClient } from '@ciscode/api-kit';

- `npm run build` – build to `dist/` (tsup)
- `npm test` – run tests (vitest)
- `npm run typecheck` – TypeScript typecheck
- `npm run lint` – ESLint
- `npm run format` / `npm run format:write` – Prettier
- `npx changeset` – create a changeset
const api = createApiClient({
baseURL: 'https://api.example.com',
timeout: 10000,
});

## Release flow (summary)
const users = await api.get<User[]>('/users');
```

- Work on a `feature` branch from `develop`
- Merge to `develop`
- Add a changeset for user-facing changes: `npx changeset`
- Promote `develop` → `master`
- Tag `vX.Y.Z` to publish (npm OIDC)
## Configuration

This repository is a **template**. Teams should clone it and focus only on
library logic, not tooling or release mechanics.
`createApiClient(config)` accepts the following options:

| Option | Type | Default | Description |
| ------------ | ------------------------ | ------------ | --------------------------------------- |
| `baseURL` | `string` | _(required)_ | Base URL for all requests |
| `timeout` | `number` | `undefined` | Request timeout in milliseconds |
| `headers` | `Record<string, string>` | `undefined` | Default headers for every request |
| `getToken` | `() => string \| null` | `undefined` | Token provider for Authorization header |
| `retry` | `number` | `0` | Number of retry attempts |
| `retryDelay` | `number` | `500` | Base delay in ms (doubles each attempt) |

## HTTP Methods

All methods accept an optional generic type parameter and return `Promise<T>`.

```ts
// GET
const user = await api.get<User>('/users/1');

// GET with query params and abort signal
const controller = new AbortController();
const users = await api.get<User[]>('/users', {
params: { page: 1, limit: 10 },
signal: controller.signal,
});

// POST
const created = await api.post<User>('/users', { name: 'Jane' });

// PUT
const updated = await api.put<User>('/users/1', { name: 'Jane Doe' });

// PATCH
const patched = await api.patch<User>('/users/1', { name: 'Jane' });

// DELETE
const result = await api.delete<{ success: boolean }>('/users/1');
```

### Per-Request Config

Every method accepts an optional `RequestConfig`:

```ts
await api.get('/data', {
headers: { 'X-Custom': 'value' },
params: { query: 'search' },
signal: abortController.signal,
});
```

## Auth Token Injection

Provide a `getToken` function to automatically inject `Authorization: Bearer <token>`
on every request. When `getToken` returns `null`, the header is silently skipped.

```ts
const api = createApiClient({
baseURL: 'https://api.example.com',
getToken: () => localStorage.getItem('access_token'),
});

// Authorization header is injected automatically
const profile = await api.get<Profile>('/me');
```

## Interceptors

Register composable interceptors that execute in registration order.

```ts
// Request interceptor — add correlation ID
api.addRequestInterceptor((req) => {
req.headers['X-Request-Id'] = crypto.randomUUID();
return req;
});

// Response interceptor — log responses
api.addResponseInterceptor((res) => {
console.log(`[${res.status}]`, res.data);
return res;
});

// Error interceptor — report errors
api.addErrorInterceptor((error) => {
console.error('API error:', error.message);
});
```

## Error Handling

All HTTP errors are normalized into `ApiError` instances. No Axios error types
reach consumer code.

```ts
import { createApiClient, ApiError } from '@ciscode/api-kit';

try {
await api.get('/protected');
} catch (error) {
if (error instanceof ApiError) {
console.error(error.status); // 404
console.error(error.code); // "HTTP_404"
console.error(error.message); // "Not Found"
console.error(error.details); // response body (if any)
}
}
```

Network errors produce `status: 0` and `code: "NETWORK_ERROR"`.

## Retry with Exponential Backoff

Configure automatic retries for transient failures. Only retryable status codes
are retried: `408`, `429`, `500`, `502`, `503`, `504`. Other errors (e.g. `400`,
`401`, `403`, `404`) fail immediately.

```ts
const api = createApiClient({
baseURL: 'https://api.example.com',
retry: 3, // up to 3 retry attempts
retryDelay: 500, // 500ms, 1000ms, 2000ms (doubles each attempt)
});
```

Backoff formula: `retryDelay * 2^attempt`

## License

MIT
Loading
Loading