@push.rocks/smartrequest
Version:
A module for modern HTTP/HTTPS requests with support for form data, file uploads, JSON, binary data, streams, and more.
362 lines • 22.5 kB
JavaScript
import { CoreRequest, CoreResponse } from '../core/index.js';
import * as plugins from './plugins.js';
import { PaginationStrategy } from './types/pagination.js';
import { createPaginatedResponse } from './features/pagination.js';
/**
* Parse Retry-After header value to milliseconds
* @param retryAfter - The Retry-After header value (seconds or HTTP date)
* @returns Delay in milliseconds
*/
function parseRetryAfter(retryAfter) {
// Handle array of values (take first)
const value = Array.isArray(retryAfter) ? retryAfter[0] : retryAfter;
if (!value)
return 0;
// Try to parse as seconds (number)
const seconds = parseInt(value, 10);
if (!isNaN(seconds)) {
return seconds * 1000;
}
// Try to parse as HTTP date
const retryDate = new Date(value);
if (!isNaN(retryDate.getTime())) {
return Math.max(0, retryDate.getTime() - Date.now());
}
return 0;
}
/**
* Modern fluent client for making HTTP requests
*/
export class SmartRequest {
constructor() {
this._options = {};
this._retries = 0;
this._queryParams = {};
}
/**
* Create a new SmartRequest instance
*/
static create() {
return new SmartRequest();
}
/**
* Set the URL for the request
*/
url(url) {
this._url = url;
return this;
}
/**
* Set the HTTP method
*/
method(method) {
this._options.method = method;
return this;
}
/**
* Set JSON body for the request
*/
json(data) {
if (!this._options.headers) {
this._options.headers = {};
}
this._options.headers['Content-Type'] = 'application/json';
this._options.requestBody = data;
return this;
}
/**
* Set form data for the request
*/
formData(data) {
const form = new plugins.formData();
for (const item of data) {
if (Buffer.isBuffer(item.value)) {
form.append(item.name, item.value, {
filename: item.filename || 'file',
contentType: item.contentType || 'application/octet-stream'
});
}
else {
form.append(item.name, item.value);
}
}
if (!this._options.headers) {
this._options.headers = {};
}
this._options.headers = {
...this._options.headers,
...form.getHeaders()
};
this._options.requestBody = form;
return this;
}
/**
* Set request timeout in milliseconds
*/
timeout(ms) {
this._options.timeout = ms;
this._options.hardDataCuttingTimeout = ms;
return this;
}
/**
* Set number of retry attempts
*/
retry(count) {
this._retries = count;
return this;
}
/**
* Enable automatic 429 (Too Many Requests) handling with configurable backoff
*/
handle429Backoff(config) {
this._rateLimitConfig = {
maxRetries: config?.maxRetries ?? 3,
respectRetryAfter: config?.respectRetryAfter ?? true,
maxWaitTime: config?.maxWaitTime ?? 60000,
fallbackDelay: config?.fallbackDelay ?? 1000,
backoffFactor: config?.backoffFactor ?? 2,
onRateLimit: config?.onRateLimit
};
return this;
}
/**
* Set HTTP headers
*/
headers(headers) {
if (!this._options.headers) {
this._options.headers = {};
}
this._options.headers = {
...this._options.headers,
...headers
};
return this;
}
/**
* Set a single HTTP header
*/
header(name, value) {
if (!this._options.headers) {
this._options.headers = {};
}
this._options.headers[name] = value;
return this;
}
/**
* Set query parameters
*/
query(params) {
this._queryParams = {
...this._queryParams,
...params
};
return this;
}
/**
* Set additional request options
*/
options(options) {
this._options = {
...this._options,
...options
};
return this;
}
/**
* Enable or disable auto-drain for unconsumed response bodies (Node.js only)
* Default is true to prevent socket hanging
*/
autoDrain(enabled) {
this._options.autoDrain = enabled;
return this;
}
/**
* Set the Accept header to indicate what content type is expected
*/
accept(type) {
// Map response types to Accept header values
const acceptHeaders = {
'json': 'application/json',
'text': 'text/plain',
'binary': 'application/octet-stream',
'stream': '*/*'
};
return this.header('Accept', acceptHeaders[type]);
}
/**
* Configure pagination for requests
*/
pagination(config) {
this._paginationConfig = config;
return this;
}
/**
* Configure offset-based pagination (page & limit)
*/
withOffsetPagination(config = {}) {
this._paginationConfig = {
strategy: PaginationStrategy.OFFSET,
pageParam: config.pageParam || 'page',
limitParam: config.limitParam || 'limit',
startPage: config.startPage || 1,
pageSize: config.pageSize || 20,
totalPath: config.totalPath || 'total'
};
// Add initial pagination parameters
this.query({
[this._paginationConfig.pageParam]: String(this._paginationConfig.startPage),
[this._paginationConfig.limitParam]: String(this._paginationConfig.pageSize)
});
return this;
}
/**
* Configure cursor-based pagination
*/
withCursorPagination(config = {}) {
this._paginationConfig = {
strategy: PaginationStrategy.CURSOR,
cursorParam: config.cursorParam || 'cursor',
cursorPath: config.cursorPath || 'nextCursor',
hasMorePath: config.hasMorePath || 'hasMore'
};
return this;
}
/**
* Configure Link header-based pagination
*/
withLinkPagination() {
this._paginationConfig = {
strategy: PaginationStrategy.LINK_HEADER
};
return this;
}
/**
* Configure custom pagination
*/
withCustomPagination(config) {
this._paginationConfig = {
strategy: PaginationStrategy.CUSTOM,
hasNextPage: config.hasNextPage,
getNextPageParams: config.getNextPageParams
};
return this;
}
/**
* Make a GET request
*/
async get() {
return this.execute('GET');
}
/**
* Make a POST request
*/
async post() {
return this.execute('POST');
}
/**
* Make a PUT request
*/
async put() {
return this.execute('PUT');
}
/**
* Make a DELETE request
*/
async delete() {
return this.execute('DELETE');
}
/**
* Make a PATCH request
*/
async patch() {
return this.execute('PATCH');
}
/**
* Get paginated response
*/
async getPaginated() {
if (!this._paginationConfig) {
throw new Error('Pagination not configured. Call one of the pagination methods first.');
}
// Default to GET if no method specified
if (!this._options.method) {
this._options.method = 'GET';
}
const response = await this.execute();
return await createPaginatedResponse(response, this._paginationConfig, this._queryParams, (nextPageParams) => {
// Create a new client with the same configuration but updated query params
const nextClient = new SmartRequest();
Object.assign(nextClient, this);
nextClient._queryParams = nextPageParams;
return nextClient.getPaginated();
});
}
/**
* Get all pages at once (use with caution for large datasets)
*/
async getAllPages() {
const firstPage = await this.getPaginated();
return firstPage.getAllPages();
}
/**
* Execute the HTTP request
*/
async execute(method) {
if (method) {
this._options.method = method;
}
this._options.queryParams = this._queryParams;
// Track rate limit attempts separately
let rateLimitAttempt = 0;
let lastError;
// Main retry loop
for (let attempt = 0; attempt <= this._retries; attempt++) {
try {
const request = new CoreRequest(this._url, this._options);
const response = await request.fire();
// Check for 429 status if rate limit handling is enabled
if (this._rateLimitConfig && response.status === 429) {
if (rateLimitAttempt >= this._rateLimitConfig.maxRetries) {
// Max rate limit retries reached, return the 429 response
return response;
}
let waitTime;
if (this._rateLimitConfig.respectRetryAfter && response.headers['retry-after']) {
// Parse Retry-After header
waitTime = parseRetryAfter(response.headers['retry-after']);
// Cap wait time to maxWaitTime
waitTime = Math.min(waitTime, this._rateLimitConfig.maxWaitTime);
}
else {
// Use exponential backoff
waitTime = Math.min(this._rateLimitConfig.fallbackDelay * Math.pow(this._rateLimitConfig.backoffFactor, rateLimitAttempt), this._rateLimitConfig.maxWaitTime);
}
// Call rate limit callback if provided
if (this._rateLimitConfig.onRateLimit) {
this._rateLimitConfig.onRateLimit(rateLimitAttempt + 1, waitTime);
}
// Wait before retrying
await new Promise(resolve => setTimeout(resolve, waitTime));
rateLimitAttempt++;
// Decrement attempt to retry this attempt
attempt--;
continue;
}
// Success or non-429 error response
return response;
}
catch (error) {
lastError = error;
// If this is the last attempt, throw the error
if (attempt === this._retries) {
throw lastError;
}
// Otherwise, wait before retrying
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
// This should never be reached due to the throw in the loop above
throw lastError;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRyZXF1ZXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvY2xpZW50L3NtYXJ0cmVxdWVzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsV0FBVyxFQUFFLFlBQVksRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBRTdELE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBSXhDLE9BQU8sRUFFTCxrQkFBa0IsRUFLbkIsTUFBTSx1QkFBdUIsQ0FBQztBQUMvQixPQUFPLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUVuRTs7OztHQUlHO0FBQ0gsU0FBUyxlQUFlLENBQUMsVUFBNkI7SUFDcEQsc0NBQXNDO0lBQ3RDLE1BQU0sS0FBSyxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDO0lBRXJFLElBQUksQ0FBQyxLQUFLO1FBQUUsT0FBTyxDQUFDLENBQUM7SUFFckIsbUNBQW1DO0lBQ25DLE1BQU0sT0FBTyxHQUFHLFFBQVEsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDcEMsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDO1FBQ3BCLE9BQU8sT0FBTyxHQUFHLElBQUksQ0FBQztJQUN4QixDQUFDO0lBRUQsNEJBQTRCO0lBQzVCLE1BQU0sU0FBUyxHQUFHLElBQUksSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ2xDLElBQUksQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLE9BQU8sRUFBRSxDQUFDLEVBQUUsQ0FBQztRQUNoQyxPQUFPLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxFQUFFLFNBQVMsQ0FBQyxPQUFPLEVBQUUsR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUMsQ0FBQztJQUN2RCxDQUFDO0lBRUQs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