morpheus-node
Version:
Official Node.js SDK for the Morpheus API Gateway - Connect to the Morpheus-Lumerin AI Marketplace
180 lines (179 loc) • 6.92 kB
JavaScript
/**
* HTTP Client for Morpheus Node SDK
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpClient = void 0;
const cross_fetch_1 = __importDefault(require("cross-fetch"));
const errors_1 = require("./errors");
class HttpClient {
constructor(config) {
this.config = config;
this.accessToken = config.accessToken;
}
setAccessToken(token) {
this.accessToken = token;
}
async delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
shouldRetry(error, attempt) {
if (attempt >= this.config.maxRetries)
return false;
// Retry on network errors
if (error instanceof TypeError || error instanceof errors_1.MorpheusNetworkError) {
return true;
}
// Retry on timeout
if (error instanceof errors_1.MorpheusTimeoutError) {
return true;
}
// Retry on specific status codes
if (error instanceof errors_1.MorpheusError && error.status) {
// Retry on 429 (rate limit), 502, 503, 504
return [429, 502, 503, 504].includes(error.status);
}
return false;
}
getRetryDelay(attempt, error) {
// If we have a rate limit error with retry-after header
if (error?.status === 429 && error.details?.retryAfter) {
return error.details.retryAfter * 1000;
}
// Exponential backoff: 1s, 2s, 4s, 8s...
return Math.min(this.config.retryDelay * Math.pow(2, attempt), 30000);
}
async request(method, path, data, options) {
const url = `${this.config.baseURL}${path}`;
let lastError;
for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
try {
const response = await this.makeRequest(method, url, data, options);
return await this.handleResponse(response);
}
catch (error) {
lastError = error;
if (!this.shouldRetry(error, attempt)) {
throw error;
}
const delay = this.getRetryDelay(attempt, error);
console.warn(`Request failed, retrying in ${delay}ms...`, {
attempt: attempt + 1,
error: error instanceof Error ? error.message : 'Unknown error'
});
await this.delay(delay);
}
}
throw lastError || new errors_1.MorpheusError('Max retries exceeded');
}
async makeRequest(method, url, data, options) {
const controller = new AbortController();
const timeout = options?.timeout || this.config.timeout;
const timeoutId = setTimeout(() => {
controller.abort();
}, timeout);
try {
const headers = {
'Content-Type': 'application/json',
'User-Agent': 'morpheus-node/1.0.0',
...this.config.headers,
...options?.headers
};
// Add authorization header
if (this.accessToken) {
headers['Authorization'] = `Bearer ${this.accessToken}`;
}
else if (this.config.apiKey) {
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
}
const fetchOptions = {
method,
headers,
signal: options?.signal || controller.signal
};
if (data && ['POST', 'PUT', 'PATCH'].includes(method)) {
fetchOptions.body = JSON.stringify(data);
}
const response = await (0, cross_fetch_1.default)(url, fetchOptions);
clearTimeout(timeoutId);
return response;
}
catch (error) {
clearTimeout(timeoutId);
if (error instanceof Error) {
if (error.name === 'AbortError') {
throw new errors_1.MorpheusTimeoutError(`Request timeout after ${timeout}ms`);
}
throw new errors_1.MorpheusNetworkError(error.message, error);
}
throw new errors_1.MorpheusNetworkError('Unknown network error');
}
}
async handleResponse(response) {
let responseData;
try {
const contentType = response.headers.get('content-type');
if (contentType?.includes('application/json')) {
responseData = await response.json();
}
else {
responseData = await response.text();
}
}
catch (error) {
// If we can't parse the response, use the status text
responseData = response.statusText;
}
if (!response.ok) {
// Check if it's a structured error response
if (responseData?.error) {
throw errors_1.MorpheusError.fromResponse(responseData, response.status);
}
// Create error based on status code
throw (0, errors_1.createErrorFromStatus)(response.status, typeof responseData === 'string' ? responseData : 'Request failed', responseData);
}
return responseData;
}
async *stream(method, path, data, options) {
const url = `${this.config.baseURL}${path}`;
const response = await this.makeRequest(method, url, data, options);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw (0, errors_1.createErrorFromStatus)(response.status, errorData.message || response.statusText, errorData);
}
const reader = response.body?.getReader();
if (!reader) {
throw new errors_1.MorpheusError('Failed to get response reader');
}
const decoder = new TextDecoder();
let buffer = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done)
break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.trim() === '')
continue;
if (line.startsWith('data: ')) {
const data = line.slice(6).trim();
if (data === '[DONE]') {
return;
}
yield data;
}
}
}
}
finally {
reader.releaseLock();
}
}
}
exports.HttpClient = HttpClient;
;