@jokoor/sdk
Version:
Jokoor SMS API SDK for JavaScript/TypeScript
302 lines • 11.7 kB
JavaScript
"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