novel-reader-sdk
Version:
SDK for Novel Reader API
246 lines • 6.88 kB
JavaScript
;
/**
* SDK Utility Functions
*
* Helper functions for URL building, query parameters, and response handling
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultRetryConfig = exports.defaultFetchOptions = void 0;
exports.buildUrl = buildUrl;
exports.buildQueryString = buildQueryString;
exports.buildFullUrl = buildFullUrl;
exports.fetchWithTimeout = fetchWithTimeout;
exports.parseJsonResponse = parseJsonResponse;
exports.validateDatabaseId = validateDatabaseId;
exports.validateUrl = validateUrl;
exports.extractHostname = extractHostname;
exports.normalizeString = normalizeString;
exports.arrayToString = arrayToString;
exports.parseNumber = parseNumber;
exports.withRetry = withRetry;
exports.logRequest = logRequest;
exports.logResponse = logResponse;
const errors_1 = require("./errors");
// ===== URL Building Utilities =====
/**
* Build URL with path parameters
*/
function buildUrl(baseUrl, path, params) {
let url = `${baseUrl.replace(/\/$/, '')}${path}`;
if (params) {
for (const [key, value] of Object.entries(params)) {
url = url.replace(`:${key}`, encodeURIComponent(value));
}
}
return url;
}
/**
* Build query string from parameters
*/
function buildQueryString(params) {
if (!params || Object.keys(params).length === 0) {
return '';
}
const searchParams = new URLSearchParams();
for (const [key, value] of Object.entries(params)) {
if (value !== undefined && value !== null) {
searchParams.append(key, value);
}
}
const queryString = searchParams.toString();
return queryString ? `?${queryString}` : '';
}
/**
* Combine URL with query parameters
*/
function buildFullUrl(baseUrl, path, params, query) {
const url = buildUrl(baseUrl, path, params);
const queryString = buildQueryString(query);
return `${url}${queryString}`;
}
// ===== HTTP Request Utilities =====
/**
* Default fetch options for API requests
*/
exports.defaultFetchOptions = {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
};
/**
* Enhanced fetch with timeout and error handling
*/
async function fetchWithTimeout(url, options = {}) {
const { timeout = 30000, ...fetchOptions } = options;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...exports.defaultFetchOptions,
...fetchOptions,
signal: controller.signal,
});
clearTimeout(timeoutId);
return response;
}
catch (error) {
clearTimeout(timeoutId);
if (error instanceof Error && error.name === 'AbortError') {
throw new errors_1.TimeoutError(`Request timeout after ${timeout}ms`);
}
throw new errors_1.NetworkError('Network request failed', error);
}
}
/**
* Parse JSON response with error handling
*/
async function parseJsonResponse(response) {
const text = await response.text();
if (!text) {
throw new errors_1.NetworkError('Empty response body');
}
try {
const data = JSON.parse(text);
// Check if response is an error
if (!response.ok) {
if (isApiError(data)) {
throw (0, errors_1.createErrorFromApiResponse)(data);
}
throw new errors_1.NetworkError(`HTTP ${response.status}: ${response.statusText}`);
}
return data;
}
catch (error) {
if (error instanceof Error && error.name === 'SyntaxError') {
throw new errors_1.NetworkError('Invalid JSON response', error);
}
throw error;
}
}
/**
* Type guard to check if response is an API error
*/
function isApiError(data) {
if (typeof data === 'object' &&
data !== null &&
Object.prototype.hasOwnProperty.call(data, 'error')) {
const error = data.error;
if (typeof error === 'object' &&
error !== null &&
Object.prototype.hasOwnProperty.call(error, 'code') &&
Object.prototype.hasOwnProperty.call(error, 'message')) {
return true;
}
}
return false;
}
// ===== Validation Utilities =====
/**
* Validate database ID
*/
function validateDatabaseId(id) {
if (!id || typeof id !== 'string') {
throw new Error('Database ID must be a non-empty string');
}
}
/**
* Validate URL format
*/
function validateUrl(url) {
try {
new URL(url);
return true;
}
catch {
return false;
}
}
/**
* Extract hostname from URL
*/
function extractHostname(url) {
try {
return new URL(url).hostname;
}
catch {
throw new Error(`Invalid URL: ${url}`);
}
}
// ===== Data Transformation Utilities =====
/**
* Clean and normalize string data
*/
function normalizeString(str) {
return (str ?? '').trim();
}
/**
* Convert array to comma-separated string
*/
function arrayToString(arr) {
return arr.filter(Boolean).join(', ');
}
/**
* Safe number parsing
*/
function parseNumber(value) {
if (typeof value === 'number') {
return value;
}
if (typeof value === 'string') {
const parsed = parseFloat(value);
return isNaN(parsed) ? undefined : parsed;
}
return undefined;
}
/**
* Default retry configuration
*/
exports.defaultRetryConfig = {
maxAttempts: 3,
baseDelay: 1000,
maxDelay: 10000,
backoffFactor: 2,
};
/**
* Retry function with exponential backoff
*/
async function withRetry(fn, config = {}) {
const { maxAttempts, baseDelay, maxDelay, backoffFactor } = {
...exports.defaultRetryConfig,
...config,
};
let lastError = new Error('No attempts made');
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
}
catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
if (attempt === maxAttempts) {
break;
}
const delay = Math.min(baseDelay * Math.pow(backoffFactor, attempt - 1), maxDelay);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
// ===== Debug Utilities =====
/**
* Log request details for debugging
*/
function logRequest(method, url, data) {
if (process.env.NODE_ENV === 'development') {
console.log(`[ScrapingSDK] ${method} ${url}`, data ? { data } : '');
}
}
/**
* Log response details for debugging
*/
function logResponse(url, status, data) {
if (process.env.NODE_ENV === 'development') {
console.log(`[ScrapingSDK] Response ${status} from ${url}`, data ? { data } : '');
}
}
//# sourceMappingURL=utils.js.map