This commit is contained in:
2025-07-28 22:37:36 +00:00
parent bc99aa3569
commit eb2ccd8d9f
21 changed files with 228 additions and 99 deletions

View File

@@ -1,17 +1,18 @@
import { type CoreResponse } from '../../core_node/index.js';
import { type CoreResponse } from '../../core/index.js';
import type { ICoreResponse } from '../../core_base/types.js';
import { type TPaginationConfig, PaginationStrategy, type TPaginatedResponse } from '../types/pagination.js';
/**
* Creates a paginated response from a regular response
*/
export async function createPaginatedResponse<T>(
response: CoreResponse<any>,
response: ICoreResponse<any>,
paginationConfig: TPaginationConfig,
queryParams: Record<string, string>,
fetchNextPage: (params: Record<string, string>) => Promise<TPaginatedResponse<T>>
): Promise<TPaginatedResponse<T>> {
// Parse response body first
const body = await response.json();
const body = await response.json() as any;
// Default to response.body for items if response is JSON
let items: T[] = Array.isArray(body)

View File

@@ -2,7 +2,7 @@
export { SmartRequestClient } from './smartrequestclient.js';
// Export response type from core
export { CoreResponse } from '../core_node/index.js';
export { CoreResponse } from '../core/index.js';
// Export types
export type { HttpMethod, ResponseType, FormField, RetryConfig, TimeoutConfig } from './types/common.js';

6
ts/client/plugins.ts Normal file
View File

@@ -0,0 +1,6 @@
// plugins for client module
import FormData from 'form-data';
export {
FormData as formData
};

View File

@@ -1,6 +1,7 @@
import { CoreRequest, CoreResponse } from '../core/index.js';
import * as plugins from '../core_node/plugins.js';
import type { IAbstractRequestOptions } from '../core_base/types.js';
import type { ICoreResponse } from '../core_base/types.js';
import * as plugins from './plugins.js';
import type { ICoreRequestOptions } from '../core_base/types.js';
import type { HttpMethod, ResponseType, FormField } from './types/common.js';
import {
@@ -18,7 +19,7 @@ import { createPaginatedResponse } from './features/pagination.js';
*/
export class SmartRequestClient<T = any> {
private _url: string;
private _options: IAbstractRequestOptions = {};
private _options: ICoreRequestOptions = {};
private _retries: number = 0;
private _queryParams: Record<string, string> = {};
private _paginationConfig?: TPaginationConfig;
@@ -224,35 +225,35 @@ export class SmartRequestClient<T = any> {
/**
* Make a GET request
*/
async get<R = T>(): Promise<CoreResponse<R>> {
async get<R = T>(): Promise<ICoreResponse<R>> {
return this.execute<R>('GET');
}
/**
* Make a POST request
*/
async post<R = T>(): Promise<CoreResponse<R>> {
async post<R = T>(): Promise<ICoreResponse<R>> {
return this.execute<R>('POST');
}
/**
* Make a PUT request
*/
async put<R = T>(): Promise<CoreResponse<R>> {
async put<R = T>(): Promise<ICoreResponse<R>> {
return this.execute<R>('PUT');
}
/**
* Make a DELETE request
*/
async delete<R = T>(): Promise<CoreResponse<R>> {
async delete<R = T>(): Promise<ICoreResponse<R>> {
return this.execute<R>('DELETE');
}
/**
* Make a PATCH request
*/
async patch<R = T>(): Promise<CoreResponse<R>> {
async patch<R = T>(): Promise<ICoreResponse<R>> {
return this.execute<R>('PATCH');
}
@@ -297,7 +298,7 @@ export class SmartRequestClient<T = any> {
/**
* Execute the HTTP request
*/
private async execute<R = T>(method?: HttpMethod): Promise<CoreResponse<R>> {
private async execute<R = T>(method?: HttpMethod): Promise<ICoreResponse<R>> {
if (method) {
this._options.method = method;
}
@@ -309,8 +310,9 @@ export class SmartRequestClient<T = any> {
for (let attempt = 0; attempt <= this._retries; attempt++) {
try {
const response = await CoreRequest.create(this._url, this._options);
return response as CoreResponse<R>;
const request = new CoreRequest(this._url, this._options as any);
const response = await request.fire();
return response as ICoreResponse<R>;
} catch (error) {
lastError = error as Error;

View File

@@ -1,4 +1,5 @@
import { type CoreResponse } from '../../core_node/index.js';
import { type CoreResponse } from '../../core/index.js';
import type { ICoreResponse } from '../../core_base/types.js';
/**
* Pagination strategy options
@@ -45,8 +46,8 @@ export interface LinkPaginationConfig {
*/
export interface CustomPaginationConfig {
strategy: PaginationStrategy.CUSTOM;
hasNextPage: (response: CoreResponse<any>) => boolean;
getNextPageParams: (response: CoreResponse<any>, currentParams: Record<string, string>) => Record<string, string>;
hasNextPage: (response: ICoreResponse<any>) => boolean;
getNextPageParams: (response: ICoreResponse<any>, currentParams: Record<string, string>) => Record<string, string>;
}
/**
@@ -62,5 +63,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: CoreResponse<any>; // Original response
response: ICoreResponse<any>; // Original response
}

View File

@@ -1,22 +1,30 @@
import * as smartenv from '@push.rocks/smartenv';
import * as plugins from './plugins.js';
// Export all base types - these are the public API
export * from '../core_base/types.js';
const smartenvInstance = new smartenv.Smartenv();
const smartenvInstance = new plugins.smartenv.Smartenv();
// Load the appropriate implementation based on environment
const implementation = await (async () => {
if (smartenvInstance.isNode) {
return smartenvInstance.getSafeNodeModule<typeof import('../core_node/index.js')>('../core_node/index.js');
} else {
return import('../core_fetch/index.js');
}
})();
// Dynamically load the appropriate implementation
let CoreRequest: any;
let CoreResponse: any;
// Export the implementation classes
export const CoreRequest = implementation.CoreRequest;
export const CoreResponse = implementation.CoreResponse;
if (smartenvInstance.isNode) {
// In Node.js, load the node implementation
const modulePath = plugins.smartpath.join(
plugins.smartpath.dirname(import.meta.url),
'../core_node/index.js'
)
console.log(modulePath);
const impl = await smartenvInstance.getSafeNodeModule(modulePath);
CoreRequest = impl.CoreRequest;
CoreResponse = impl.CoreResponse;
} else {
// In browser, load the fetch implementation
const impl = await import('../core_fetch/index.js');
CoreRequest = impl.CoreRequest;
CoreResponse = impl.CoreResponse;
}
// Export CoreResponse as a type for type annotations
export type CoreResponse<T = any> = InstanceType<typeof implementation.CoreResponse>;
// Export the loaded implementations
export { CoreRequest, CoreResponse };

4
ts/core/plugins.ts Normal file
View File

@@ -0,0 +1,4 @@
import * as smartenv from '@push.rocks/smartenv';
import * as smartpath from '@push.rocks/smartpath/iso';
export { smartenv, smartpath };

View File

@@ -3,7 +3,7 @@ import * as types from './types.js';
/**
* Abstract Core Request class that defines the interface for all HTTP/HTTPS requests
*/
export abstract class CoreRequest<TOptions extends types.IAbstractRequestOptions = types.IAbstractRequestOptions, TResponse = any> {
export abstract class CoreRequest<TOptions extends types.ICoreRequestOptions = types.ICoreRequestOptions, TResponse = any> {
/**
* Tests if a URL is a unix socket
*/

View File

@@ -3,14 +3,14 @@ import * as types from './types.js';
/**
* Abstract Core Response class that provides a fetch-like API
*/
export abstract class CoreResponse<T = any> implements types.IAbstractResponse<T> {
export abstract class CoreResponse<T = any> implements types.ICoreResponse<T> {
protected consumed = false;
// Public properties
public abstract readonly ok: boolean;
public abstract readonly status: number;
public abstract readonly statusText: string;
public abstract readonly headers: types.AbstractHeaders;
public abstract readonly headers: types.Headers;
public abstract readonly url: string;
/**

View File

@@ -27,9 +27,10 @@ export interface IUrlEncodedField {
}
/**
* Abstract request options - platform agnostic
* Core request options - unified interface for all implementations
*/
export interface IAbstractRequestOptions {
export interface ICoreRequestOptions {
// Common options
method?: THttpMethod | string; // Allow string for compatibility
headers?: any; // Allow any for platform compatibility
keepAlive?: boolean;
@@ -37,22 +38,39 @@ export interface IAbstractRequestOptions {
queryParams?: { [key: string]: string };
timeout?: number;
hardDataCuttingTimeout?: number;
// Node.js specific options (ignored in fetch implementation)
agent?: any;
socketPath?: string;
hostname?: string;
port?: number;
path?: string;
// Fetch API specific options (ignored in Node.js implementation)
credentials?: RequestCredentials;
mode?: RequestMode;
cache?: RequestCache;
redirect?: RequestRedirect;
referrer?: string;
referrerPolicy?: ReferrerPolicy;
integrity?: string;
signal?: AbortSignal;
}
/**
* Abstract response headers - platform agnostic
* Response headers - platform agnostic
*/
export type AbstractHeaders = Record<string, string | string[]>;
export type Headers = Record<string, string | string[]>;
/**
* Abstract response interface - platform agnostic
* Core response interface - platform agnostic
*/
export interface IAbstractResponse<T = any> {
export interface ICoreResponse<T = any> {
// Properties
ok: boolean;
status: number;
statusText: string;
headers: AbstractHeaders;
headers: Headers;
url: string;
// Methods

View File

@@ -8,6 +8,11 @@ import { CoreRequest as AbstractCoreRequest } from '../core_base/request.js';
export class CoreRequest extends AbstractCoreRequest<types.ICoreRequestOptions, CoreResponse> {
constructor(url: string, options: types.ICoreRequestOptions = {}) {
super(url, options);
// Check for unsupported Node.js-specific options
if (options.agent || options.socketPath) {
throw new Error('Node.js specific options (agent, socketPath) are not supported in browser/fetch implementation');
}
}
/**

View File

@@ -4,7 +4,7 @@ import { CoreResponse as AbstractCoreResponse } from '../core_base/response.js';
/**
* Fetch-based implementation of Core Response class
*/
export class CoreResponse<T = any> extends AbstractCoreResponse<T> implements types.ICoreResponse<T> {
export class CoreResponse<T = any> extends AbstractCoreResponse<T> implements types.IFetchResponse<T> {
private response: Response;
private responseClone: Response;
@@ -12,7 +12,7 @@ export class CoreResponse<T = any> extends AbstractCoreResponse<T> implements ty
public readonly ok: boolean;
public readonly status: number;
public readonly statusText: string;
public readonly headers: types.AbstractHeaders;
public readonly headers: types.Headers;
public readonly url: string;
constructor(response: Response) {

View File

@@ -4,24 +4,12 @@ import * as baseTypes from '../core_base/types.js';
export * from '../core_base/types.js';
/**
* Core request options for fetch-based implementation
* Extends RequestInit from the Fetch API
* Fetch-specific response extensions
*/
export interface ICoreRequestOptions extends RequestInit {
// Override method to be more specific
method?: baseTypes.THttpMethod;
// Additional options not in RequestInit
requestBody?: any;
queryParams?: { [key: string]: string };
timeout?: number;
hardDataCuttingTimeout?: number;
// keepAlive maps to keepalive in RequestInit
keepAlive?: boolean;
}
/**
* Core response object for fetch implementation
*/
export interface ICoreResponse<T = any> extends baseTypes.IAbstractResponse<T> {
// Fetch-specific properties (all from base interface)
export interface IFetchResponse<T = any> extends baseTypes.ICoreResponse<T> {
// Fetch-specific methods
stream(): ReadableStream<Uint8Array> | null;
// Access to raw Response object
raw(): Response;
}

View File

@@ -39,6 +39,12 @@ export class CoreRequest extends AbstractCoreRequest<types.ICoreRequestOptions,
) {
super(url, options);
this.requestDataFunc = requestDataFunc;
// Check for unsupported fetch-specific options
if (options.credentials || options.mode || options.cache || options.redirect ||
options.referrer || options.referrerPolicy || options.integrity) {
throw new Error('Fetch API specific options (credentials, mode, cache, redirect, referrer, referrerPolicy, integrity) are not supported in Node.js implementation');
}
}
/**

View File

@@ -5,7 +5,7 @@ import { CoreResponse as AbstractCoreResponse } from '../core_base/response.js';
/**
* Node.js implementation of Core Response class that provides a fetch-like API
*/
export class CoreResponse<T = any> extends AbstractCoreResponse<T> implements types.ICoreResponse<T> {
export class CoreResponse<T = any> extends AbstractCoreResponse<T> implements types.INodeResponse<T> {
private incomingMessage: plugins.http.IncomingMessage;
private bodyBufferPromise: Promise<Buffer> | null = null;

View File

@@ -4,17 +4,6 @@ import * as baseTypes from '../core_base/types.js';
// Re-export base types
export * from '../core_base/types.js';
/**
* Core request options extending Node.js RequestOptions
* Node.js RequestOptions already includes method and headers
*/
export interface ICoreRequestOptions extends plugins.https.RequestOptions {
keepAlive?: boolean;
requestBody?: any;
queryParams?: { [key: string]: string };
hardDataCuttingTimeout?: number;
}
/**
* Extended IncomingMessage with body property (legacy compatibility)
*/
@@ -23,20 +12,10 @@ export interface IExtendedIncomingMessage<T = any> extends plugins.http.Incoming
}
/**
* Core response object that provides fetch-like API with Node.js specific methods
* Node.js specific response extensions
*/
export interface ICoreResponse<T = any> extends baseTypes.IAbstractResponse<T> {
// Properties
ok: boolean;
status: number;
statusText: string;
headers: plugins.http.IncomingHttpHeaders;
url: string;
// Methods
json(): Promise<T>;
text(): Promise<string>;
arrayBuffer(): Promise<ArrayBuffer>;
export interface INodeResponse<T = any> extends baseTypes.ICoreResponse<T> {
// Node.js specific methods
stream(): NodeJS.ReadableStream;
// Legacy compatibility

View File

@@ -3,7 +3,7 @@ export * from './client/index.js';
// Core exports for advanced usage
export { CoreResponse } from './core/index.js';
export type { IAbstractRequestOptions, IAbstractResponse } from './core_base/types.js';
export type { ICoreRequestOptions, ICoreResponse } from './core_base/types.js';
// Default export for easier importing
import { SmartRequestClient } from './client/smartrequestclient.js';