Initial commit: add .gitignore and README
This commit is contained in:
29
packages/api-client/package.json
Normal file
29
packages/api-client/package.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "@workspace/api-client",
|
||||
"version": "1.0.0",
|
||||
"description": "Common API client utilities",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "vitest",
|
||||
"lint": "eslint src",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.5",
|
||||
"@workspace/shared-types": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.4",
|
||||
"vitest": "^1.2.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
}
|
||||
}
|
||||
|
||||
32
packages/api-client/src/client.ts
Normal file
32
packages/api-client/src/client.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* API client factory
|
||||
*/
|
||||
|
||||
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
|
||||
import { setupInterceptors } from './interceptors';
|
||||
|
||||
export interface ApiClientConfig extends AxiosRequestConfig {
|
||||
baseURL: string;
|
||||
timeout?: number;
|
||||
retries?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create configured API client
|
||||
*/
|
||||
export function createApiClient(config: ApiClientConfig): AxiosInstance {
|
||||
const client = axios.create({
|
||||
baseURL: config.baseURL,
|
||||
timeout: config.timeout || 30000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...config.headers,
|
||||
},
|
||||
});
|
||||
|
||||
// Setup interceptors
|
||||
setupInterceptors(client, config.retries || 3);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
9
packages/api-client/src/index.ts
Normal file
9
packages/api-client/src/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @workspace/api-client
|
||||
* Common API client utilities
|
||||
*/
|
||||
|
||||
export * from './client';
|
||||
export * from './interceptors';
|
||||
export * from './types';
|
||||
|
||||
56
packages/api-client/src/interceptors.ts
Normal file
56
packages/api-client/src/interceptors.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Axios interceptors
|
||||
*/
|
||||
|
||||
import { AxiosInstance, AxiosError, InternalAxiosRequestConfig } from 'axios';
|
||||
|
||||
/**
|
||||
* Setup request and response interceptors
|
||||
*/
|
||||
export function setupInterceptors(client: AxiosInstance, maxRetries: number = 3): void {
|
||||
// Request interceptor
|
||||
client.interceptors.request.use(
|
||||
(config: InternalAxiosRequestConfig) => {
|
||||
// Add auth token if available
|
||||
const token = process.env.API_TOKEN;
|
||||
if (token && config.headers) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// Response interceptor with retry logic
|
||||
client.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error: AxiosError) => {
|
||||
const config = error.config as InternalAxiosRequestConfig & { __retryCount?: number };
|
||||
|
||||
if (!config) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
config.__retryCount = config.__retryCount || 0;
|
||||
|
||||
// Retry on network errors or 5xx errors
|
||||
if (
|
||||
(error.code === 'ECONNABORTED' || (error.response?.status && error.response.status >= 500)) &&
|
||||
config.__retryCount < maxRetries
|
||||
) {
|
||||
config.__retryCount += 1;
|
||||
|
||||
// Exponential backoff
|
||||
const delay = Math.pow(2, config.__retryCount) * 1000;
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
|
||||
return client(config);
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
21
packages/api-client/src/types.ts
Normal file
21
packages/api-client/src/types.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* API client types
|
||||
*/
|
||||
|
||||
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
|
||||
export interface ApiRequestConfig extends AxiosRequestConfig {
|
||||
retries?: number;
|
||||
}
|
||||
|
||||
export interface ApiResponse<T = unknown> extends AxiosResponse<T> {
|
||||
data: T;
|
||||
}
|
||||
|
||||
export interface ApiError {
|
||||
message: string;
|
||||
code?: string;
|
||||
status?: number;
|
||||
details?: unknown;
|
||||
}
|
||||
|
||||
20
packages/api-client/tsconfig.json
Normal file
20
packages/api-client/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022"],
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user