@push.rocks/smartrequest
Version:
A module for modern HTTP/HTTPS requests with support for form data, file uploads, JSON, binary data, streams, and more.
276 lines • 15.9 kB
JavaScript
import { CoreRequest, CoreResponse } from '../core_node/index.js';
import * as plugins from '../core_node/plugins.js';
import { PaginationStrategy } from './types/pagination.js';
import { createPaginatedResponse } from './features/pagination.js';
/**
* Modern fluent client for making HTTP requests
*/
export class SmartRequestClient {
constructor() {
this._options = {};
this._retries = 0;
this._queryParams = {};
}
/**
* Create a new SmartRequestClient instance
*/
static create() {
return new SmartRequestClient();
}
/**
* 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;
}
/**
* 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 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 SmartRequestClient();
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;
// Handle retry logic
let lastError;
for (let attempt = 0; attempt <= this._retries; attempt++) {
try {
const response = await CoreRequest.create(this._url, this._options);
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRyZXF1ZXN0Y2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvbW9kZXJuL3NtYXJ0cmVxdWVzdGNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsV0FBVyxFQUFFLFlBQVksRUFBNEIsTUFBTSx1QkFBdUIsQ0FBQztBQUM1RixPQUFPLEtBQUssT0FBTyxNQUFNLHlCQUF5QixDQUFDO0FBR25ELE9BQU8sRUFFTCxrQkFBa0IsRUFLbkIsTUFBTSx1QkFBdUIsQ0FBQztBQUMvQixPQUFPLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUVuRTs7R0FFRztBQUNILE1BQU0sT0FBTyxrQkFBa0I7SUFBL0I7UUFFVSxhQUFRLEdBQXdCLEVBQUUsQ0FBQztRQUNuQyxhQUFRLEdBQVcsQ0FBQyxDQUFDO1FBQ3JCLGlCQUFZLEdBQTJCLEVBQUUsQ0FBQztJQW1UcEQsQ0FBQztJQWhUQzs7T0FFRztJQUNILE1BQU0sQ0FBQyxNQUFNO1FBQ1gsT0FBTyxJQUFJLGtCQUFrQixFQUFLLENBQUM7SUFDckMsQ0FBQztJQUVEOztPQUVHO0lBQ0gsR0FBRyxDQUFDLEdBQVc7UUFDYixJQUFJLENBQUMsSUFBSSxHQUFHLEdBQUcsQ0FBQztRQUNoQixPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFFRDs7T0FFRztJQUNILE1BQU0sQ0FBQyxNQUFrQjtRQUN2QixJQUFJLENBQUMsUUFBUSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7UUFDOUIsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxJQUFJLENBQUMsSUFBUztRQUNaLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQzNCLElBQUksQ0FBQyxRQUFRLENBQUMsT0FBTyxHQUFHLEVBQUUsQ0FBQztRQUM3QixDQUFDO1FBQ0QsSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENB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