BREAKING CHANGE(core): major architectural refactoring with fetch-like API
Some checks failed
Default (tags) / security (push) Failing after 24s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped

This commit is contained in:
2025-07-27 21:23:20 +00:00
parent f7d2c6de4f
commit bbb57004d9
24 changed files with 1038 additions and 593 deletions

View File

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

View File

@@ -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');
}

View File

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

View File

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