openalex-client
Version:
TypeScript SDK for OpenAlex academic database API
182 lines • 6.16 kB
JavaScript
;
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