UNPKG

@jokoor/sdk

Version:
302 lines 11.7 kB
"use strict"; /** * Base resource class for API wrappers */ Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseResource = void 0; const result_1 = require("../types/result"); /** * Convert camelCase to snake_case */ function toSnakeCase(str) { return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); } /** * Type-safe conversion of object keys from camelCase to snake_case */ function convertKeysToSnakeCase(obj) { if (obj === null || obj === undefined) { return obj; } if (Array.isArray(obj)) { return obj.map(item => convertKeysToSnakeCase(item)); } if (typeof obj !== 'object' || obj instanceof Date) { return obj; } const converted = {}; for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { const snakeKey = toSnakeCase(key); converted[snakeKey] = convertKeysToSnakeCase(obj[key]); } } return converted; } class BaseResource { constructor(configuration) { this.configuration = configuration; } /** * Convert parameters from camelCase to snake_case for API calls */ convertParamsToSnakeCase(params) { return convertKeysToSnakeCase(params); } /** * Convert snake_case to camelCase */ toCamelCase(str) { return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); } /** * Type-safe conversion of object keys from snake_case to camelCase */ convertKeysToCamelCase(obj) { if (obj === null || obj === undefined) { return obj; } if (Array.isArray(obj)) { return obj.map(item => this.convertKeysToCamelCase(item)); } if (typeof obj !== 'object' || obj instanceof Date) { return obj; } const converted = {}; for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { const camelKey = this.toCamelCase(key); converted[camelKey] = this.convertKeysToCamelCase(obj[key]); } } return converted; } /** * Extract data from response wrapper * The API returns {data?: T, error?: string} format * Returns a Result type instead of throwing */ extractData(response) { try { // Handle null/undefined responses if (!response || response.data === null || response.data === undefined) { return (0, result_1.ok)(response?.data); } // Handle axios response structure const apiResponse = response.data; // Check if the response is an object that could have error field if (typeof apiResponse === 'object' && apiResponse !== null) { // Check if the response contains an error if (apiResponse.error) { return (0, result_1.err)(apiResponse.error); } // If response has nested data structure, extract it if ('data' in apiResponse) { return (0, result_1.ok)(apiResponse.data); } } // Fallback for responses that don't follow the standard format // (some endpoints might return data directly) return (0, result_1.ok)(response.data); } catch (error) { // Handle any unexpected errors during extraction const message = error instanceof Error ? error.message : 'Failed to extract data from response'; return (0, result_1.err)(message); } } /** * Handle API calls with proper error catching * Converts exceptions to Result types */ async handleApiCall(apiCall) { try { const response = await apiCall(); return this.extractData(response); } catch (error) { // Handle axios errors if (error && typeof error === 'object' && 'response' in error) { const axiosError = error; // Extract error message from response if (axiosError.response?.data?.error) { return (0, result_1.err)(String(axiosError.response.data.error)); } // Handle specific HTTP status codes const status = axiosError.response?.status; if (status === 404) { return (0, result_1.err)('Resource not found'); } else if (status === 401) { return (0, result_1.err)('Authentication failed'); } else if (status === 403) { return (0, result_1.err)('Permission denied'); } else if (status === 429) { return (0, result_1.err)('Rate limit exceeded'); } else if (status === 500) { return (0, result_1.err)('Internal server error'); } else if (status === 502 || status === 503 || status === 504) { return (0, result_1.err)('Service temporarily unavailable'); } else if (status) { const statusText = axiosError.response?.statusText || 'Unknown error'; return (0, result_1.err)(`API Error: ${status} ${statusText}`); } } // Handle network errors if (error && typeof error === 'object' && 'code' in error) { const networkError = error; if (networkError.code === 'ECONNREFUSED') { return (0, result_1.err)('Unable to connect to API server'); } else if (networkError.code === 'ENOTFOUND') { return (0, result_1.err)('API server not found'); } else if (networkError.code === 'ETIMEDOUT') { return (0, result_1.err)('Request timed out'); } else if (networkError.code === 'ECONNRESET') { return (0, result_1.err)('Connection was reset'); } } // Handle other errors - ensure we always return a string let message; if (error instanceof Error) { message = error.message; } else if (typeof error === 'string') { message = error; } else if (error && typeof error === 'object' && 'toString' in error) { message = String(error); } else { message = 'An unexpected error occurred'; } return (0, result_1.err)(message); } } /** * Convert offset-based pagination to page number * @param offset The offset (number of records to skip) * @param limit The limit (page size) * @returns The page number (1-based) */ offsetToPage(offset = 0, limit = 10) { return Math.floor(offset / limit) + 1; } /** * Convert API response to SDK paginated response format * The API returns {items: T[], count: number, offset: number, limit: number} * @param response The API response * @returns Result containing SDK paginated response */ convertToPaginatedResponse(response) { try { // Handle axios response structure let apiResponse = response.data || response; // API returns {data: {items, count, offset, limit}} if (apiResponse.data && typeof apiResponse.data === 'object') { apiResponse = apiResponse.data; } const items = apiResponse.items || []; const count = apiResponse.count || 0; const offset = apiResponse.offset || 0; const limit = apiResponse.limit || 10; return (0, result_1.ok)({ items, count, offset, limit }); } catch (error) { const message = error instanceof Error ? error.message : 'Failed to parse paginated response'; return (0, result_1.err)(message); } } /** * Handle paginated API calls */ async handlePaginatedApiCall(apiCall) { try { const response = await apiCall(); // Check for API errors first if (response?.data?.error) { return (0, result_1.err)(String(response.data.error)); } return this.convertToPaginatedResponse(response); } catch (error) { // Handle axios errors if (error && typeof error === 'object' && 'response' in error) { const axiosError = error; // Extract error message from response if (axiosError.response?.data?.error) { return (0, result_1.err)(String(axiosError.response.data.error)); } // Handle specific HTTP status codes const status = axiosError.response?.status; if (status === 404) { return (0, result_1.err)('Resource not found'); } else if (status === 401) { return (0, result_1.err)('Authentication failed'); } else if (status === 403) { return (0, result_1.err)('Permission denied'); } else if (status === 429) { return (0, result_1.err)('Rate limit exceeded'); } else if (status === 500) { return (0, result_1.err)('Internal server error'); } else if (status === 502 || status === 503 || status === 504) { return (0, result_1.err)('Service temporarily unavailable'); } else if (status) { const statusText = axiosError.response?.statusText || 'Unknown error'; return (0, result_1.err)(`API Error: ${status} ${statusText}`); } } // Handle network errors if (error && typeof error === 'object' && 'code' in error) { const networkError = error; if (networkError.code === 'ECONNREFUSED') { return (0, result_1.err)('Unable to connect to API server'); } else if (networkError.code === 'ENOTFOUND') { return (0, result_1.err)('API server not found'); } else if (networkError.code === 'ETIMEDOUT') { return (0, result_1.err)('Request timed out'); } else if (networkError.code === 'ECONNRESET') { return (0, result_1.err)('Connection was reset'); } } // Handle other errors - ensure we always return a string let message; if (error instanceof Error) { message = error.message; } else if (typeof error === 'string') { message = error; } else if (error && typeof error === 'object' && 'toString' in error) { message = String(error); } else { message = 'An unexpected error occurred'; } return (0, result_1.err)(message); } } } exports.BaseResource = BaseResource; //# sourceMappingURL=base.js.map