Initial commit: add .gitignore and README

This commit is contained in:
defiQUG
2026-02-09 21:51:55 -08:00
commit d904c04c2d
52 changed files with 1613 additions and 0 deletions

View 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"
}
}

View 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;
}

View File

@@ -0,0 +1,9 @@
/**
* @workspace/api-client
* Common API client utilities
*/
export * from './client';
export * from './interceptors';
export * from './types';

View 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);
}
);
}

View 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;
}

View 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"]
}