UNPKG

@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
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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