aluvia-ts-sdk
Version:
Official Aluvia proxy management SDK for Node.js and modern JavaScript environments
133 lines • 4.94 kB
JavaScript
import { initializeFetch } from "./fetch.js";
import { ApiError, NetworkError, AuthenticationError, RateLimitError, } from "./errors.js";
/** The default Aluvia API endpoint */
export const API_ORIGIN = "https://api.aluvia.io";
/**
* Universal HTTP client that works in both Node.js and browser environments.
*
* Uses native fetch where available, with automatic fallback to node-fetch
* for older Node.js versions. Provides a consistent interface for all HTTP operations.
*
* @internal
*/
export class ApiClient {
constructor(baseURL = API_ORIGIN || "") {
this.fetchInstance = null;
this.baseURL = baseURL;
}
async getFetch() {
if (!this.fetchInstance) {
this.fetchInstance = await initializeFetch();
}
return this.fetchInstance;
}
async request(endpoint, options = {}) {
try {
const fetch = await this.getFetch();
const url = `${this.baseURL}${endpoint}`;
const response = await fetch(url, {
method: options.method || "GET",
headers: {
"Content-Type": "application/json",
...options.headers,
},
body: options.body,
});
if (!response.ok) {
await this.handleErrorResponse(response);
}
return response.json();
}
catch (error) {
if (error instanceof Error && !error.name.includes("Error")) {
// Network or other fetch errors
throw new NetworkError(`Request failed: ${error.message}`, {
endpoint,
method: options.method || "GET",
originalError: error.message,
});
}
throw error; // Re-throw our custom errors
}
}
/**
* Handles HTTP error responses and throws appropriate error types
*/
async handleErrorResponse(response) {
const status = response.status;
let errorMessage = `HTTP ${status}`;
let errorDetails = {
status,
statusText: response.statusText,
url: response.url,
};
// Try to parse error details from response body
try {
const errorBody = await response.text();
if (errorBody) {
try {
const parsed = JSON.parse(errorBody);
errorMessage = parsed.message || parsed.error || errorMessage;
errorDetails = { ...errorDetails, ...parsed };
}
catch {
errorDetails.responseBody = errorBody;
}
}
}
catch {
// Ignore errors when parsing response body
}
// Throw appropriate error type based on status code
switch (status) {
case 401:
throw new AuthenticationError(errorMessage.includes("HTTP")
? "Authentication failed"
: errorMessage, errorDetails);
case 403:
throw new AuthenticationError(errorMessage.includes("HTTP") ? "Access forbidden" : errorMessage, errorDetails);
case 404:
throw new ApiError(errorMessage.includes("HTTP") ? "Resource not found" : errorMessage, status, errorDetails);
case 429: {
const retryAfter = response.headers.get("Retry-After");
throw new RateLimitError(errorMessage.includes("HTTP") ? "Rate limit exceeded" : errorMessage, retryAfter ? parseInt(retryAfter, 10) : undefined, errorDetails);
}
case 500:
case 502:
case 503:
case 504:
throw new ApiError(errorMessage.includes("HTTP") ? "Server error" : errorMessage, status, errorDetails);
default:
throw new ApiError(errorMessage, status, errorDetails);
}
}
async get(endpoint, headers) {
return this.request(endpoint, { method: "GET", headers });
}
async post(endpoint, data, headers) {
return this.request(endpoint, {
method: "POST",
headers,
body: data ? JSON.stringify(data) : undefined,
});
}
async put(endpoint, data, headers) {
return this.request(endpoint, {
method: "PUT",
headers,
body: data ? JSON.stringify(data) : undefined,
});
}
async delete(endpoint, headers) {
return this.request(endpoint, { method: "DELETE", headers });
}
async patch(endpoint, data, headers) {
return this.request(endpoint, {
method: "PATCH",
headers,
body: data ? JSON.stringify(data) : undefined,
});
}
}
export const api = new ApiClient();
//# sourceMappingURL=api-client.js.map