update
This commit is contained in:
@@ -5,7 +5,8 @@
|
|||||||
"description": "A module for modern HTTP/HTTPS requests with support for form data, file uploads, JSON, binary data, streams, and more.",
|
"description": "A module for modern HTTP/HTTPS requests with support for form data, file uploads, JSON, binary data, streams, and more.",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist_ts_web/index.js",
|
".": "./dist_ts_web/index.js",
|
||||||
"./legacy": "./dist_ts/legacy/index.js"
|
"./legacy": "./dist_ts/legacy/index.js",
|
||||||
|
"./fetch": "./dist_ts/core_fetch/index.js"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
4
ts/core_fetch/index.ts
Normal file
4
ts/core_fetch/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// Core fetch exports - native fetch implementation
|
||||||
|
export * from './types.js';
|
||||||
|
export * from './request.js';
|
||||||
|
export * from './response.js';
|
126
ts/core_fetch/request.ts
Normal file
126
ts/core_fetch/request.ts
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import * as types from './types.js';
|
||||||
|
import { CoreResponse } from './response.js';
|
||||||
|
import { CoreRequest as AbstractCoreRequest } from '../core_base/request.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch-based implementation of Core Request class
|
||||||
|
*/
|
||||||
|
export class CoreRequest extends AbstractCoreRequest<types.ICoreRequestOptions, CoreResponse> {
|
||||||
|
constructor(url: string, options: types.ICoreRequestOptions = {}) {
|
||||||
|
super(url, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the full URL with query parameters
|
||||||
|
*/
|
||||||
|
private buildUrl(): string {
|
||||||
|
if (!this.options.queryParams || Object.keys(this.options.queryParams).length === 0) {
|
||||||
|
return this.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(this.url);
|
||||||
|
Object.entries(this.options.queryParams).forEach(([key, value]) => {
|
||||||
|
url.searchParams.append(key, value);
|
||||||
|
});
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert our options to fetch RequestInit
|
||||||
|
*/
|
||||||
|
private buildFetchOptions(): RequestInit {
|
||||||
|
const fetchOptions: RequestInit = {
|
||||||
|
method: this.options.method,
|
||||||
|
headers: this.options.headers,
|
||||||
|
credentials: this.options.credentials,
|
||||||
|
mode: this.options.mode,
|
||||||
|
cache: this.options.cache,
|
||||||
|
redirect: this.options.redirect,
|
||||||
|
referrer: this.options.referrer,
|
||||||
|
referrerPolicy: this.options.referrerPolicy,
|
||||||
|
integrity: this.options.integrity,
|
||||||
|
keepalive: this.options.keepAlive,
|
||||||
|
signal: this.options.signal,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle request body
|
||||||
|
if (this.options.requestBody !== undefined) {
|
||||||
|
if (typeof this.options.requestBody === 'string' ||
|
||||||
|
this.options.requestBody instanceof ArrayBuffer ||
|
||||||
|
this.options.requestBody instanceof FormData ||
|
||||||
|
this.options.requestBody instanceof URLSearchParams ||
|
||||||
|
this.options.requestBody instanceof ReadableStream) {
|
||||||
|
fetchOptions.body = this.options.requestBody;
|
||||||
|
} else {
|
||||||
|
// Convert objects to JSON
|
||||||
|
fetchOptions.body = JSON.stringify(this.options.requestBody);
|
||||||
|
// Set content-type if not already set
|
||||||
|
if (!fetchOptions.headers) {
|
||||||
|
fetchOptions.headers = { 'Content-Type': 'application/json' };
|
||||||
|
} else if (fetchOptions.headers instanceof Headers) {
|
||||||
|
if (!fetchOptions.headers.has('Content-Type')) {
|
||||||
|
fetchOptions.headers.set('Content-Type', 'application/json');
|
||||||
|
}
|
||||||
|
} else if (typeof fetchOptions.headers === 'object' && !Array.isArray(fetchOptions.headers)) {
|
||||||
|
const headersObj = fetchOptions.headers as Record<string, string>;
|
||||||
|
if (!headersObj['Content-Type']) {
|
||||||
|
headersObj['Content-Type'] = 'application/json';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle timeout
|
||||||
|
if (this.options.timeout || this.options.hardDataCuttingTimeout) {
|
||||||
|
const timeout = this.options.hardDataCuttingTimeout || this.options.timeout;
|
||||||
|
const controller = new AbortController();
|
||||||
|
setTimeout(() => controller.abort(), timeout);
|
||||||
|
fetchOptions.signal = controller.signal;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetchOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fire the request and return a CoreResponse
|
||||||
|
*/
|
||||||
|
async fire(): Promise<CoreResponse> {
|
||||||
|
const response = await this.fireCore();
|
||||||
|
return new CoreResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fire the request and return the raw Response
|
||||||
|
*/
|
||||||
|
async fireCore(): Promise<Response> {
|
||||||
|
const url = this.buildUrl();
|
||||||
|
const options = this.buildFetchOptions();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, options);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
throw new Error('Request timed out');
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static factory method to create and fire a request
|
||||||
|
*/
|
||||||
|
static async create(
|
||||||
|
url: string,
|
||||||
|
options: types.ICoreRequestOptions = {}
|
||||||
|
): Promise<CoreResponse> {
|
||||||
|
const request = new CoreRequest(url, options);
|
||||||
|
return request.fire();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience exports for backward compatibility
|
||||||
|
*/
|
||||||
|
export const isUnixSocket = CoreRequest.isUnixSocket;
|
||||||
|
export const parseUnixSocketUrl = CoreRequest.parseUnixSocketUrl;
|
78
ts/core_fetch/response.ts
Normal file
78
ts/core_fetch/response.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import * as types from './types.js';
|
||||||
|
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> {
|
||||||
|
private response: Response;
|
||||||
|
private responseClone: Response;
|
||||||
|
|
||||||
|
// Public properties
|
||||||
|
public readonly ok: boolean;
|
||||||
|
public readonly status: number;
|
||||||
|
public readonly statusText: string;
|
||||||
|
public readonly headers: types.AbstractHeaders;
|
||||||
|
public readonly url: string;
|
||||||
|
|
||||||
|
constructor(response: Response) {
|
||||||
|
super();
|
||||||
|
// Clone the response so we can read the body multiple times if needed
|
||||||
|
this.response = response;
|
||||||
|
this.responseClone = response.clone();
|
||||||
|
|
||||||
|
this.ok = response.ok;
|
||||||
|
this.status = response.status;
|
||||||
|
this.statusText = response.statusText;
|
||||||
|
this.url = response.url;
|
||||||
|
|
||||||
|
// Convert Headers to plain object
|
||||||
|
this.headers = {};
|
||||||
|
response.headers.forEach((value, key) => {
|
||||||
|
this.headers[key] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse response as JSON
|
||||||
|
*/
|
||||||
|
async json(): Promise<T> {
|
||||||
|
this.ensureNotConsumed();
|
||||||
|
try {
|
||||||
|
return await this.response.json();
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to parse JSON: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get response as text
|
||||||
|
*/
|
||||||
|
async text(): Promise<string> {
|
||||||
|
this.ensureNotConsumed();
|
||||||
|
return await this.response.text();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get response as ArrayBuffer
|
||||||
|
*/
|
||||||
|
async arrayBuffer(): Promise<ArrayBuffer> {
|
||||||
|
this.ensureNotConsumed();
|
||||||
|
return await this.response.arrayBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get response as a readable stream (Web Streams API)
|
||||||
|
*/
|
||||||
|
stream(): ReadableStream<Uint8Array> | null {
|
||||||
|
this.ensureNotConsumed();
|
||||||
|
return this.response.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the raw Response object
|
||||||
|
*/
|
||||||
|
raw(): Response {
|
||||||
|
return this.responseClone;
|
||||||
|
}
|
||||||
|
}
|
27
ts/core_fetch/types.ts
Normal file
27
ts/core_fetch/types.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import * as baseTypes from '../core_base/types.js';
|
||||||
|
|
||||||
|
// Re-export base types
|
||||||
|
export * from '../core_base/types.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Core request options for fetch-based implementation
|
||||||
|
* Extends RequestInit from the Fetch API
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
}
|
@@ -6,8 +6,9 @@ export * from '../core_base/types.js';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Core request options extending Node.js RequestOptions
|
* Core request options extending Node.js RequestOptions
|
||||||
|
* Node.js RequestOptions already includes method and headers
|
||||||
*/
|
*/
|
||||||
export interface ICoreRequestOptions extends plugins.https.RequestOptions, Omit<baseTypes.IAbstractRequestOptions, 'method' | 'headers'> {
|
export interface ICoreRequestOptions extends plugins.https.RequestOptions {
|
||||||
keepAlive?: boolean;
|
keepAlive?: boolean;
|
||||||
requestBody?: any;
|
requestBody?: any;
|
||||||
queryParams?: { [key: string]: string };
|
queryParams?: { [key: string]: string };
|
||||||
|
Reference in New Issue
Block a user