mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-18 01:09:40 +02:00
✨ feat: add @manacore/shared-api-client package
Create unified API client for all web apps with: - createApiClient factory function - ApiResult<T> Go-style error handling - HTTP methods: get, post, put, patch, delete, upload - Auto token handling via getAuthToken callback - Timeout support with AbortController - Retry logic with exponential backoff - Runtime URL injection for Docker - FormData support for file uploads Migrate clock app as proof of concept: - Replace local fetchApi with shared createApiClient - Update stores to use ApiError.message Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2b3210df85
commit
e23d1194d8
12 changed files with 562 additions and 292 deletions
94
packages/shared-api-client/src/utils.ts
Normal file
94
packages/shared-api-client/src/utils.ts
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* API Client Utilities
|
||||
*/
|
||||
|
||||
import type { ApiError, ApiErrorCode } from './types';
|
||||
|
||||
/**
|
||||
* Build a query string from parameters object
|
||||
* Handles undefined values and proper encoding
|
||||
*/
|
||||
export function buildQueryString(
|
||||
params: Record<string, string | number | boolean | undefined>
|
||||
): string {
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
searchParams.append(key, String(value));
|
||||
}
|
||||
}
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
return queryString ? `?${queryString}` : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine error code from HTTP status
|
||||
*/
|
||||
export function getErrorCodeFromStatus(status: number): ApiErrorCode {
|
||||
if (status === 401) return 'UNAUTHORIZED';
|
||||
if (status === 403) return 'FORBIDDEN';
|
||||
if (status === 404) return 'NOT_FOUND';
|
||||
if (status === 422 || status === 400) return 'VALIDATION_ERROR';
|
||||
if (status >= 500) return 'SERVER_ERROR';
|
||||
return 'UNKNOWN';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a standardized API error
|
||||
*/
|
||||
export function createApiError(
|
||||
message: string,
|
||||
code: ApiErrorCode,
|
||||
status?: number,
|
||||
details?: unknown
|
||||
): ApiError {
|
||||
return { message, code, status, details };
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse error response body
|
||||
*/
|
||||
export async function parseErrorResponse(response: Response): Promise<string> {
|
||||
try {
|
||||
const data = await response.json();
|
||||
return data.message || data.error || JSON.stringify(data);
|
||||
} catch {
|
||||
return response.statusText || 'Unknown error';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep utility for retry delays
|
||||
*/
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if error is retryable (network issues, 5xx errors)
|
||||
*/
|
||||
export function isRetryableError(error: ApiError): boolean {
|
||||
if (error.code === 'NETWORK_ERROR' || error.code === 'TIMEOUT') {
|
||||
return true;
|
||||
}
|
||||
if (error.status && error.status >= 500) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get base URL with runtime injection support for Docker
|
||||
* Checks window.__PUBLIC_BACKEND_URL__ first, then falls back to provided URL
|
||||
*/
|
||||
export function getBaseUrl(configuredUrl: string): string {
|
||||
if (typeof window !== 'undefined') {
|
||||
const runtimeUrl = (window as unknown as Record<string, unknown>).__PUBLIC_BACKEND_URL__;
|
||||
if (typeof runtimeUrl === 'string' && runtimeUrl) {
|
||||
return runtimeUrl;
|
||||
}
|
||||
}
|
||||
return configuredUrl;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue