@docyrus/api-client
v0.0.3
Published
A modern, type-safe API client library for JavaScript/TypeScript applications with support for multiple backend types, streaming, error handling, and token management.
Readme
@docyrus/api-client
A modern, type-safe API client library for JavaScript/TypeScript applications with support for multiple backend types, streaming, error handling, and token management.
Features
- 🚀 Multiple Client Types: REST, Edge Functions, and Cloudflare Workers
- 🔐 Built-in Token Management: Memory, Storage, and Async token managers
- 🌊 Streaming Support: SSE and chunked responses
- 🔄 Request/Response Interceptors: Transform requests and handle responses
- ⚡ Retry Logic: Automatic retry with configurable conditions
- 🎯 Type-Safe: Full TypeScript support with comprehensive type definitions
- 🌐 Isomorphic: Works in both browser and Node.js environments
- 🛡️ Comprehensive Error Handling: Typed error classes for different scenarios
- ⏱️ Timeout Support: Configurable request timeouts with AbortController
- 📄 HTML to PDF: Built-in support for HTML to PDF conversion
Installation
npm install @docyrus/api-clientyarn add @docyrus/api-clientpnpm add @docyrus/api-clientQuick Start
import { RestApiClient, MemoryTokenManager } from '@docyrus/api-client';
// Create a client instance
const client = new RestApiClient({
baseURL: 'https://api.example.com',
tokenManager: new MemoryTokenManager(),
timeout: 5000,
headers: {
'X-API-Version': '1.0'
}
});
// Set authentication token
await client.setAccessToken('your-auth-token');
// Make API calls
const response = await client.get('/users', {
params: { page: 1, limit: 10 }
});
console.log(response.data);Client Types
RestApiClient
Standard REST API client for traditional HTTP endpoints.
import { RestApiClient } from '@docyrus/api-client';
const client = new RestApiClient({
baseURL: 'https://api.example.com'
});
// GET request
const users = await client.get('/users');
// POST request with data
const newUser = await client.post('/users', {
name: 'John Doe',
email: '[email protected]'
});
// PUT request
const updatedUser = await client.put('/users/123', {
name: 'Jane Doe'
});
// DELETE request
await client.delete('/users/123');EdgeFunctionClient
Optimized for edge function deployments (Vercel, Netlify, etc.).
import { EdgeFunctionClient } from '@docyrus/api-client';
const client = new EdgeFunctionClient({
baseURL: 'https://edge.example.com'
});
// Invoke edge function
const result = await client.invokeFunction('processData', {
input: 'data'
});
// Stream from edge function
for await (const chunk of client.streamFunction('generateContent', { prompt: 'Hello' })) {
console.log(chunk);
}CloudflareWorkerClient
Specialized client for Cloudflare Workers with dynamic function routing.
import { CloudflareWorkerClient } from '@docyrus/api-client';
const client = new CloudflareWorkerClient({
baseURL: 'https://{{function}}.example.workers.dev'
});
// Automatically replaces {{function}} with the function name
const response = await client.post('/myfunction/endpoint', data);Token Management
MemoryTokenManager
Stores tokens in memory (default).
import { MemoryTokenManager } from '@docyrus/api-client';
const tokenManager = new MemoryTokenManager();
tokenManager.setToken('token');
const token = tokenManager.getToken();StorageTokenManager
Persists tokens in browser storage.
import { StorageTokenManager } from '@docyrus/api-client';
// Use localStorage
const localManager = new StorageTokenManager(localStorage, 'auth_token');
// Use sessionStorage
const sessionManager = new StorageTokenManager(sessionStorage, 'auth_token');AsyncTokenManager
For async token operations (e.g., secure storage, encrypted tokens).
import { AsyncTokenManager } from '@docyrus/api-client';
const tokenManager = new AsyncTokenManager({
async getToken() {
return await secureStorage.get('token');
},
async setToken(token: string) {
await secureStorage.set('token', token);
},
async clearToken() {
await secureStorage.remove('token');
}
});Interceptors
Transform requests and handle responses globally.
// Request interceptor
client.use({
async request(config) {
// Add timestamp to all requests
config.headers = {
...config.headers,
'X-Request-Time': new Date().toISOString()
};
return config;
}
});
// Response interceptor
client.use({
async response(response, request) {
console.log(`API call to ${request.url} took ${Date.now() - request.timestamp}ms`);
return response;
}
});
// Error interceptor
client.use({
async error(error, request, response) {
if (error.status === 401) {
// Handle authentication errors
await refreshToken();
throw error;
}
return { error, request, response };
}
});Streaming
Server-Sent Events (SSE)
const eventSource = client.sse('/events', {
onMessage(data) {
console.log('Received:', data);
},
onError(error) {
console.error('SSE error:', error);
},
onComplete() {
console.log('Stream completed');
}
});
// Stop the stream
eventSource.close();Chunked Streaming
for await (const chunk of client.stream('/stream', {
method: 'POST',
body: { query: 'stream data' }
})) {
console.log('Chunk:', chunk);
}Error Handling
Comprehensive error types for different scenarios:
import {
ApiError,
NetworkError,
TimeoutError,
AuthenticationError,
AuthorizationError,
NotFoundError,
RateLimitError,
ValidationError
} from '@docyrus/api-client';
try {
const data = await client.get('/protected-resource');
} catch (error) {
if (error instanceof AuthenticationError) {
// Handle authentication error (401)
console.log('Please login');
} else if (error instanceof AuthorizationError) {
// Handle authorization error (403)
console.log('Access denied');
} else if (error instanceof NotFoundError) {
// Handle not found error (404)
console.log('Resource not found');
} else if (error instanceof RateLimitError) {
// Handle rate limit error (429)
console.log(`Rate limited. Retry after: ${error.retryAfter}`);
} else if (error instanceof NetworkError) {
// Handle network errors
console.log('Network issue');
} else if (error instanceof TimeoutError) {
// Handle timeout errors
console.log('Request timed out');
}
}Advanced Features
Retry Logic
import { withRetry } from '@docyrus/api-client';
const response = await withRetry(
() => client.get('/flaky-endpoint'),
{
retries: 3,
retryDelay: 1000,
retryCondition: (error) => error.status >= 500
}
);File Upload
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('description', 'File description');
const response = await client.post('/upload', formData, {
headers: {
// Content-Type will be set automatically for FormData
}
});File Download
const response = await client.get('/download/file.pdf', {
responseType: 'blob'
});
// Save the file
const url = URL.createObjectURL(response.data);
const link = document.createElement('a');
link.href = url;
link.download = 'file.pdf';
link.click();HTML to PDF
const pdfResult = await client.html2pdf({
url: 'https://example.com',
// or
html: '<html><body>Content</body></html>',
options: {
format: 'A4',
margin: { top: 10, bottom: 10, left: 10, right: 10 },
landscape: false
}
});Filter Queries
import { prepareFilterQueryForApi } from '@docyrus/api-client';
const filter = {
operator: 'and',
rules: [
{ field: 'status', operator: 'eq', value: 'active' },
{ field: 'age', operator: 'gte', value: 18 }
]
};
const queryString = prepareFilterQueryForApi(filter);
const response = await client.get(`/users?${queryString}`);Configuration Options
interface ApiClientConfig {
// Base URL for all requests
baseURL?: string;
// Token manager instance
tokenManager?: TokenManager;
// Default headers
headers?: Record<string, string>;
// Request timeout in milliseconds
timeout?: number;
// Custom fetch implementation
fetch?: typeof fetch;
// Custom FormData implementation
FormData?: typeof FormData;
// Custom AbortController implementation
AbortController?: typeof AbortController;
// Storage for persistence (browser only)
storage?: Storage;
}Utility Functions
import {
buildUrl,
isAbortError,
parseContentDisposition,
isFormData,
isBlob,
isStream,
createAbortSignal,
jsonToQueryString
} from '@docyrus/api-client';
// Build URL with query parameters
const url = buildUrl('/api/users', { page: 1, limit: 10 });
// Result: /api/users?page=1&limit=10
// Check if error is abort error
if (isAbortError(error)) {
console.log('Request was aborted');
}
// Parse content disposition header
const { filename, type } = parseContentDisposition(
'attachment; filename="document.pdf"'
);
// Create abort signal with timeout
const signal = createAbortSignal(5000); // 5 second timeoutTypeScript Support
Full TypeScript support with generic types:
interface User {
id: number;
name: string;
email: string;
}
interface ApiResponse<T> {
data: T;
meta: {
page: number;
total: number;
};
}
// Typed responses
const response = await client.get<ApiResponse<User[]>>('/users');
const users: User[] = response.data.data;
// Typed request bodies
const newUser = await client.post<User>('/users', {
name: 'John Doe',
email: '[email protected]'
} as Omit<User, 'id'>);Browser Support
- Chrome/Edge: Latest 2 versions
- Firefox: Latest 2 versions
- Safari: Latest 2 versions
- Node.js: 18+
Contributing
Contributions are welcome! Please read our contributing guidelines before submitting PRs.
License
MIT © Docyrus
Support
For issues and feature requests, please open an issue on GitHub.
Development
# Install dependencies
pnpm install
# Development mode with watch
pnpm dev
# Build the package
pnpm build
# Run linting
pnpm lint
# Type checking
pnpm typecheck
# Publish package
pnpm publish