UNPKG

openalex-client

Version:

TypeScript SDK for OpenAlex academic database API

182 lines 6.16 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.OpenAlexClient = void 0; class OpenAlexClient { constructor(config = {}) { this.config = { timeout: config.timeout || 30000, retries: config.retries || 3, retryDelay: config.retryDelay || 1000, }; } buildURL(endpoint, params) { const url = new URL(endpoint, "https://api.openalex.org/"); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== null) { if (Array.isArray(value)) { url.searchParams.set(key, value.join("|")); } else { url.searchParams.set(key, String(value)); } } }); } return url.toString(); } async makeRequest(url, options = {}, attempt = 1) { // Create abort controller for timeout this.abortController = new AbortController(); const timeoutId = setTimeout(() => { this.abortController?.abort(); }, this.config.timeout); try { const startTime = Date.now(); const response = await fetch(url); clearTimeout(timeoutId); // Log response time in development if (process.env.NODE_ENV === "development") { const duration = Date.now() - startTime; console.log(`Request to ${url} took ${duration}ms`); } // Handle HTTP errors if (!response.ok) { let errorData; try { errorData = await this.safeParseJSON(response); } catch { errorData = { message: response.statusText }; } throw this.createHTTPError(response, errorData); } // Parse JSON response const data = await this.parseJSON(response); return { data, status: response.status, statusText: response.statusText, headers: response.headers, }; } catch (error) { clearTimeout(timeoutId); // Handle retry logic if (this.shouldRetry(error, attempt)) { const delay = this.config.retryDelay * Math.pow(2, attempt - 1); await this.sleep(delay); return this.makeRequest(url, options, attempt + 1); } throw this.handleError(error); } } async parseJSON(response) { const text = await response.text(); if (!text) { throw new Error("Empty response body"); } try { return JSON.parse(text); } catch (error) { throw new Error(`Invalid JSON response: ${error instanceof Error ? error.message : "Unknown error"}`); } } async safeParseJSON(response) { try { const text = await response.text(); return text ? JSON.parse(text) : { message: response.statusText }; } catch { return { message: response.statusText }; } } createHTTPError(response, errorData) { return { error: "HTTP Error", message: errorData?.message || errorData?.error || response.statusText, status_code: response.status, }; } shouldRetry(error, attempt) { if (attempt >= this.config.retries) { return false; } // Retry on network errors if (error.name === "AbortError" || error.name === "TypeError") { return true; } // Retry on 5xx status codes if (error.status_code && error.status_code >= 500) { return true; } // Retry on specific error codes if (error.code === "ECONNABORTED" || error.code === "ENOTFOUND" || error.code === "ECONNRESET") { return true; } return false; } handleError(error) { if (error.name === "AbortError") { return { error: "Timeout Error", message: `Request timed out after ${this.config.timeout}ms`, }; } if (error.name === "TypeError" && error.message.includes("fetch")) { return { error: "Network Error", message: "Failed to fetch - check your internet connection", }; } // If it's already an OpenAlexError, return as-is if (error.error && error.message) { return error; } // Generic error handling return { error: "Request Error", message: error.message || "Unknown error occurred", status_code: error.status, }; } sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } async get(endpoint, params) { const url = this.buildURL(endpoint, params); return this.makeRequest(url, { method: "GET" }); } async post(endpoint, data, params) { const url = this.buildURL(endpoint, params); return this.makeRequest(url, { method: "POST", body: data ? JSON.stringify(data) : undefined, }); } async put(endpoint, data, params) { const url = this.buildURL(endpoint, params); return this.makeRequest(url, { method: "PUT", body: data ? JSON.stringify(data) : undefined, }); } async delete(endpoint, params) { const url = this.buildURL(endpoint, params); return this.makeRequest(url, { method: "DELETE" }); } updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; } abort() { this.abortController?.abort(); } getConfig() { return { ...this.config }; } } exports.OpenAlexClient = OpenAlexClient; //# sourceMappingURL=client.js.map