BREAKING CHANGE(core): major architectural refactoring with fetch-like API
This commit is contained in:
@@ -1,19 +1,22 @@
|
||||
import { type IExtendedIncomingMessage } from '../../legacy/smartrequest.request.js';
|
||||
import { type SmartResponse } from '../../core/index.js';
|
||||
import { type TPaginationConfig, PaginationStrategy, type TPaginatedResponse } from '../types/pagination.js';
|
||||
|
||||
/**
|
||||
* Creates a paginated response from a regular response
|
||||
*/
|
||||
export function createPaginatedResponse<T>(
|
||||
response: IExtendedIncomingMessage<any>,
|
||||
export async function createPaginatedResponse<T>(
|
||||
response: SmartResponse<any>,
|
||||
paginationConfig: TPaginationConfig,
|
||||
queryParams: Record<string, string>,
|
||||
fetchNextPage: (params: Record<string, string>) => Promise<TPaginatedResponse<T>>
|
||||
): TPaginatedResponse<T> {
|
||||
): Promise<TPaginatedResponse<T>> {
|
||||
// Parse response body first
|
||||
const body = await response.json();
|
||||
|
||||
// Default to response.body for items if response is JSON
|
||||
let items: T[] = Array.isArray(response.body)
|
||||
? response.body
|
||||
: (response.body?.items || response.body?.data || response.body?.results || []);
|
||||
let items: T[] = Array.isArray(body)
|
||||
? body
|
||||
: (body?.items || body?.data || body?.results || []);
|
||||
|
||||
let hasNextPage = false;
|
||||
let nextPageParams: Record<string, string> = {};
|
||||
@@ -24,7 +27,7 @@ export function createPaginatedResponse<T>(
|
||||
const config = paginationConfig;
|
||||
const currentPage = parseInt(queryParams[config.pageParam || 'page'] || String(config.startPage || 1));
|
||||
const limit = parseInt(queryParams[config.limitParam || 'limit'] || String(config.pageSize || 20));
|
||||
const total = getValueByPath(response.body, config.totalPath || 'total') || 0;
|
||||
const total = getValueByPath(body, config.totalPath || 'total') || 0;
|
||||
|
||||
hasNextPage = currentPage * limit < total;
|
||||
|
||||
@@ -39,8 +42,8 @@ export function createPaginatedResponse<T>(
|
||||
|
||||
case PaginationStrategy.CURSOR: {
|
||||
const config = paginationConfig;
|
||||
const nextCursor = getValueByPath(response.body, config.cursorPath || 'nextCursor');
|
||||
const hasMore = getValueByPath(response.body, config.hasMorePath || 'hasMore');
|
||||
const nextCursor = getValueByPath(body, config.cursorPath || 'nextCursor');
|
||||
const hasMore = getValueByPath(body, config.hasMorePath || 'hasMore');
|
||||
|
||||
hasNextPage = !!nextCursor || !!hasMore;
|
||||
|
||||
|
@@ -1,6 +1,9 @@
|
||||
// Export the main client
|
||||
export { SmartRequestClient } from './smartrequestclient.js';
|
||||
|
||||
// Export response type from core
|
||||
export { SmartResponse } from '../core/index.js';
|
||||
|
||||
// Export types
|
||||
export type { HttpMethod, ResponseType, FormField, RetryConfig, TimeoutConfig } from './types/common.js';
|
||||
export {
|
||||
@@ -34,12 +37,12 @@ export function createFormClient<T = any>() {
|
||||
* Create a client pre-configured for binary data
|
||||
*/
|
||||
export function createBinaryClient<T = any>() {
|
||||
return SmartRequestClient.create<T>().responseType('binary');
|
||||
return SmartRequestClient.create<T>().accept('binary');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a client pre-configured for streaming
|
||||
*/
|
||||
export function createStreamClient() {
|
||||
return SmartRequestClient.create().responseType('stream');
|
||||
return SmartRequestClient.create().accept('stream');
|
||||
}
|
@@ -1,6 +1,5 @@
|
||||
import { type ISmartRequestOptions } from '../legacy/smartrequest.interfaces.js';
|
||||
import { request, type IExtendedIncomingMessage } from '../legacy/smartrequest.request.js';
|
||||
import * as plugins from '../legacy/smartrequest.plugins.js';
|
||||
import { request, SmartResponse, type ICoreRequestOptions } from '../core/index.js';
|
||||
import * as plugins from '../core/plugins.js';
|
||||
|
||||
import type { HttpMethod, ResponseType, FormField } from './types/common.js';
|
||||
import {
|
||||
@@ -18,9 +17,7 @@ import { createPaginatedResponse } from './features/pagination.js';
|
||||
*/
|
||||
export class SmartRequestClient<T = any> {
|
||||
private _url: string;
|
||||
private _options: ISmartRequestOptions = {};
|
||||
private _responseType: ResponseType = 'json';
|
||||
private _timeoutMs: number = 60000;
|
||||
private _options: ICoreRequestOptions = {};
|
||||
private _retries: number = 0;
|
||||
private _queryParams: Record<string, string> = {};
|
||||
private _paginationConfig?: TPaginationConfig;
|
||||
@@ -94,7 +91,6 @@ export class SmartRequestClient<T = any> {
|
||||
* Set request timeout in milliseconds
|
||||
*/
|
||||
timeout(ms: number): this {
|
||||
this._timeoutMs = ms;
|
||||
this._options.timeout = ms;
|
||||
this._options.hardDataCuttingTimeout = ms;
|
||||
return this;
|
||||
@@ -145,16 +141,18 @@ export class SmartRequestClient<T = any> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set response type
|
||||
* Set the Accept header to indicate what content type is expected
|
||||
*/
|
||||
responseType(type: ResponseType): this {
|
||||
this._responseType = type;
|
||||
|
||||
if (type === 'binary' || type === 'stream') {
|
||||
this._options.autoJsonParse = false;
|
||||
}
|
||||
|
||||
return this;
|
||||
accept(type: ResponseType): this {
|
||||
// Map response types to Accept header values
|
||||
const acceptHeaders: Record<ResponseType, string> = {
|
||||
'json': 'application/json',
|
||||
'text': 'text/plain',
|
||||
'binary': 'application/octet-stream',
|
||||
'stream': '*/*'
|
||||
};
|
||||
|
||||
return this.header('Accept', acceptHeaders[type]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,35 +223,35 @@ export class SmartRequestClient<T = any> {
|
||||
/**
|
||||
* Make a GET request
|
||||
*/
|
||||
async get<R = T>(): Promise<IExtendedIncomingMessage<R>> {
|
||||
async get<R = T>(): Promise<SmartResponse<R>> {
|
||||
return this.execute<R>('GET');
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a POST request
|
||||
*/
|
||||
async post<R = T>(): Promise<IExtendedIncomingMessage<R>> {
|
||||
async post<R = T>(): Promise<SmartResponse<R>> {
|
||||
return this.execute<R>('POST');
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a PUT request
|
||||
*/
|
||||
async put<R = T>(): Promise<IExtendedIncomingMessage<R>> {
|
||||
async put<R = T>(): Promise<SmartResponse<R>> {
|
||||
return this.execute<R>('PUT');
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a DELETE request
|
||||
*/
|
||||
async delete<R = T>(): Promise<IExtendedIncomingMessage<R>> {
|
||||
async delete<R = T>(): Promise<SmartResponse<R>> {
|
||||
return this.execute<R>('DELETE');
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a PATCH request
|
||||
*/
|
||||
async patch<R = T>(): Promise<IExtendedIncomingMessage<R>> {
|
||||
async patch<R = T>(): Promise<SmartResponse<R>> {
|
||||
return this.execute<R>('PATCH');
|
||||
}
|
||||
|
||||
@@ -272,7 +270,7 @@ export class SmartRequestClient<T = any> {
|
||||
|
||||
const response = await this.execute();
|
||||
|
||||
return createPaginatedResponse<ItemType>(
|
||||
return await createPaginatedResponse<ItemType>(
|
||||
response,
|
||||
this._paginationConfig,
|
||||
this._queryParams,
|
||||
@@ -298,7 +296,7 @@ export class SmartRequestClient<T = any> {
|
||||
/**
|
||||
* Execute the HTTP request
|
||||
*/
|
||||
private async execute<R = T>(method?: HttpMethod): Promise<IExtendedIncomingMessage<R>> {
|
||||
private async execute<R = T>(method?: HttpMethod): Promise<SmartResponse<R>> {
|
||||
if (method) {
|
||||
this._options.method = method;
|
||||
}
|
||||
@@ -310,28 +308,8 @@ export class SmartRequestClient<T = any> {
|
||||
|
||||
for (let attempt = 0; attempt <= this._retries; attempt++) {
|
||||
try {
|
||||
if (this._responseType === 'stream') {
|
||||
return await request(this._url, this._options, true) as IExtendedIncomingMessage<R>;
|
||||
} else if (this._responseType === 'binary') {
|
||||
const response = await request(this._url, this._options, true);
|
||||
|
||||
// Handle binary response
|
||||
const dataPromise = plugins.smartpromise.defer<Buffer>();
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
response.on('data', (chunk: Buffer) => chunks.push(chunk));
|
||||
response.on('end', () => {
|
||||
const buffer = Buffer.concat(chunks);
|
||||
(response as IExtendedIncomingMessage<R>).body = buffer as any;
|
||||
dataPromise.resolve();
|
||||
});
|
||||
|
||||
await dataPromise.promise;
|
||||
return response as IExtendedIncomingMessage<R>;
|
||||
} else {
|
||||
// Handle JSON or text response
|
||||
return await request(this._url, this._options) as IExtendedIncomingMessage<R>;
|
||||
}
|
||||
const response = await request(this._url, this._options);
|
||||
return response as SmartResponse<R>;
|
||||
} catch (error) {
|
||||
lastError = error as Error;
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { type IExtendedIncomingMessage } from '../../legacy/smartrequest.request.js';
|
||||
import { type SmartResponse } from '../../core/index.js';
|
||||
|
||||
/**
|
||||
* Pagination strategy options
|
||||
@@ -45,8 +45,8 @@ export interface LinkPaginationConfig {
|
||||
*/
|
||||
export interface CustomPaginationConfig {
|
||||
strategy: PaginationStrategy.CUSTOM;
|
||||
hasNextPage: (response: IExtendedIncomingMessage<any>) => boolean;
|
||||
getNextPageParams: (response: IExtendedIncomingMessage<any>, currentParams: Record<string, string>) => Record<string, string>;
|
||||
hasNextPage: (response: SmartResponse<any>) => boolean;
|
||||
getNextPageParams: (response: SmartResponse<any>, currentParams: Record<string, string>) => Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,5 +62,5 @@ export interface TPaginatedResponse<T> {
|
||||
hasNextPage: boolean; // Whether there are more pages
|
||||
getNextPage: () => Promise<TPaginatedResponse<T>>; // Function to get the next page
|
||||
getAllPages: () => Promise<T[]>; // Function to get all remaining pages and combine
|
||||
response: IExtendedIncomingMessage<any>; // Original response
|
||||
response: SmartResponse<any>; // Original response
|
||||
}
|
Reference in New Issue
Block a user