update
This commit is contained in:
@@ -1,45 +0,0 @@
|
|||||||
import { tap, expect, expectAsync } from '@pushrocks/tapbundle';
|
|
||||||
|
|
||||||
import * as smartrequest from '../ts/legacy/index.js';
|
|
||||||
|
|
||||||
tap.test('should request a html document over https', async () => {
|
|
||||||
await expectAsync(smartrequest.getJson('https://encrypted.google.com/')).toHaveProperty('body');
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('should request a JSON document over https', async () => {
|
|
||||||
await expectAsync(smartrequest.getJson('https://jsonplaceholder.typicode.com/posts/1'))
|
|
||||||
.property('body')
|
|
||||||
.property('id')
|
|
||||||
.toEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('should post a JSON document over http', async () => {
|
|
||||||
const testData = { text: 'example_text' };
|
|
||||||
await expectAsync(smartrequest.postJson('https://httpbin.org/post', { requestBody: testData }))
|
|
||||||
.property('body')
|
|
||||||
.property('json')
|
|
||||||
.property('text')
|
|
||||||
.toEqual('example_text');
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('should safe get stuff', async () => {
|
|
||||||
smartrequest.safeGet('http://coffee.link/');
|
|
||||||
smartrequest.safeGet('https://coffee.link/');
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.skip.test('should deal with unix socks', async () => {
|
|
||||||
const socketResponse = await smartrequest.request(
|
|
||||||
'http://unix:/var/run/docker.sock:/containers/json',
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Host: 'docker.sock',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
console.log(socketResponse.body);
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.skip.test('should correctly upload a file using formData', async () => {});
|
|
||||||
|
|
||||||
tap.start();
|
|
@@ -1,5 +1,6 @@
|
|||||||
import { CoreRequest, CoreResponse, type ICoreRequestOptions } from '../core_node/index.js';
|
import { CoreRequest, CoreResponse } from '../core/index.js';
|
||||||
import * as plugins from '../core_node/plugins.js';
|
import * as plugins from '../core_node/plugins.js';
|
||||||
|
import type { IAbstractRequestOptions } from '../core_base/types.js';
|
||||||
|
|
||||||
import type { HttpMethod, ResponseType, FormField } from './types/common.js';
|
import type { HttpMethod, ResponseType, FormField } from './types/common.js';
|
||||||
import {
|
import {
|
||||||
@@ -17,7 +18,7 @@ import { createPaginatedResponse } from './features/pagination.js';
|
|||||||
*/
|
*/
|
||||||
export class SmartRequestClient<T = any> {
|
export class SmartRequestClient<T = any> {
|
||||||
private _url: string;
|
private _url: string;
|
||||||
private _options: ICoreRequestOptions = {};
|
private _options: IAbstractRequestOptions = {};
|
||||||
private _retries: number = 0;
|
private _retries: number = 0;
|
||||||
private _queryParams: Record<string, string> = {};
|
private _queryParams: Record<string, string> = {};
|
||||||
private _paginationConfig?: TPaginationConfig;
|
private _paginationConfig?: TPaginationConfig;
|
@@ -1,33 +1,22 @@
|
|||||||
import * as smartenv from '@push.rocks/smartenv';
|
import * as smartenv from '@push.rocks/smartenv';
|
||||||
|
|
||||||
// Re-export base types that are common to all implementations
|
// Export all base types - these are the public API
|
||||||
export * from '../core_base/types.js';
|
export * from '../core_base/types.js';
|
||||||
|
|
||||||
// Define the implementation interface
|
|
||||||
interface IImplementation {
|
|
||||||
CoreRequest: any;
|
|
||||||
CoreResponse: any;
|
|
||||||
isUnixSocket?: (url: string) => boolean;
|
|
||||||
parseUnixSocketUrl?: (url: string) => { socketPath: string; path: string };
|
|
||||||
// Type exports
|
|
||||||
ICoreRequestOptions?: any;
|
|
||||||
ICoreResponse?: any;
|
|
||||||
IExtendedIncomingMessage?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
let usedImplementation: IImplementation;
|
|
||||||
const smartenvInstance = new smartenv.Smartenv();
|
const smartenvInstance = new smartenv.Smartenv();
|
||||||
|
|
||||||
// Initialize the implementation based on environment
|
// Load the appropriate implementation based on environment
|
||||||
const initPromise = (async () => {
|
const implementation = await (async () => {
|
||||||
if (smartenvInstance.isNode) {
|
if (smartenvInstance.isNode) {
|
||||||
const nodeImpl = await smartenvInstance.getSafeNodeModule<typeof import('../core_node/index.js')>('../core_node/index.js');
|
return smartenvInstance.getSafeNodeModule<typeof import('../core_node/index.js')>('../core_node/index.js');
|
||||||
usedImplementation = nodeImpl as IImplementation;
|
|
||||||
} else {
|
} else {
|
||||||
const fetchImpl = await import('../core_fetch/index.js');
|
return import('../core_fetch/index.js');
|
||||||
usedImplementation = fetchImpl as IImplementation;
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Export a function to ensure implementation is loaded
|
// Export the implementation classes
|
||||||
export const ensureImplementationLoaded = () => initPromise;
|
export const CoreRequest = implementation.CoreRequest;
|
||||||
|
export const CoreResponse = implementation.CoreResponse;
|
||||||
|
|
||||||
|
// Export CoreResponse as a type for type annotations
|
||||||
|
export type CoreResponse<T = any> = InstanceType<typeof implementation.CoreResponse>;
|
||||||
|
12
ts/index.ts
12
ts/index.ts
@@ -1,12 +1,10 @@
|
|||||||
// Legacy API exports (for backward compatibility)
|
// Client API exports
|
||||||
export * from './legacy/index.js';
|
export * from './client/index.js';
|
||||||
|
|
||||||
// Modern API exports
|
|
||||||
export * from './modern/index.js';
|
|
||||||
|
|
||||||
// Core exports for advanced usage
|
// Core exports for advanced usage
|
||||||
export { CoreResponse, type ICoreRequestOptions, type ICoreResponse } from './core_node/index.js';
|
export { CoreResponse } from './core/index.js';
|
||||||
|
export type { IAbstractRequestOptions, IAbstractResponse } from './core_base/types.js';
|
||||||
|
|
||||||
// Default export for easier importing
|
// Default export for easier importing
|
||||||
import { SmartRequestClient } from './modern/smartrequestclient.js';
|
import { SmartRequestClient } from './client/smartrequestclient.js';
|
||||||
export default SmartRequestClient;
|
export default SmartRequestClient;
|
@@ -1,243 +0,0 @@
|
|||||||
/**
|
|
||||||
* Legacy adapter that provides backward compatibility
|
|
||||||
* Maps legacy API to the new core module
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as core from '../core_node/index.js';
|
|
||||||
import * as plugins from '../core_node/plugins.js';
|
|
||||||
|
|
||||||
const smartpromise = plugins.smartpromise;
|
|
||||||
|
|
||||||
// Re-export types for backward compatibility
|
|
||||||
export { type IExtendedIncomingMessage } from '../core_node/types.js';
|
|
||||||
export interface ISmartRequestOptions extends core.ICoreRequestOptions {
|
|
||||||
autoJsonParse?: boolean;
|
|
||||||
responseType?: 'json' | 'text' | 'binary' | 'stream';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-export interface for form fields
|
|
||||||
export interface IFormField {
|
|
||||||
name: string;
|
|
||||||
type: 'string' | 'filePath' | 'Buffer';
|
|
||||||
payload: string | Buffer;
|
|
||||||
fileName?: string;
|
|
||||||
contentType?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to convert stream to IExtendedIncomingMessage for legacy compatibility
|
|
||||||
*/
|
|
||||||
async function streamToExtendedMessage(
|
|
||||||
stream: plugins.http.IncomingMessage,
|
|
||||||
autoJsonParse = true
|
|
||||||
): Promise<core.IExtendedIncomingMessage> {
|
|
||||||
const done = smartpromise.defer<core.IExtendedIncomingMessage>();
|
|
||||||
const chunks: Buffer[] = [];
|
|
||||||
|
|
||||||
stream.on('data', (chunk: Buffer) => {
|
|
||||||
chunks.push(chunk);
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.on('end', () => {
|
|
||||||
const buffer = Buffer.concat(chunks);
|
|
||||||
const extendedMessage = stream as core.IExtendedIncomingMessage;
|
|
||||||
|
|
||||||
if (autoJsonParse) {
|
|
||||||
const text = buffer.toString('utf-8');
|
|
||||||
try {
|
|
||||||
extendedMessage.body = JSON.parse(text);
|
|
||||||
} catch (err) {
|
|
||||||
extendedMessage.body = text;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
extendedMessage.body = buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
done.resolve(extendedMessage);
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.on('error', (err) => {
|
|
||||||
done.reject(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
return done.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Legacy request function that returns IExtendedIncomingMessage
|
|
||||||
*/
|
|
||||||
export async function request(
|
|
||||||
urlArg: string,
|
|
||||||
optionsArg: ISmartRequestOptions = {},
|
|
||||||
responseStreamArg = false,
|
|
||||||
requestDataFunc?: (req: plugins.http.ClientRequest) => void
|
|
||||||
): Promise<core.IExtendedIncomingMessage> {
|
|
||||||
const coreRequest = new core.CoreRequest(urlArg, optionsArg, requestDataFunc);
|
|
||||||
const stream = await coreRequest.fireCore();
|
|
||||||
|
|
||||||
if (responseStreamArg) {
|
|
||||||
// For stream responses, just cast and return
|
|
||||||
return stream as core.IExtendedIncomingMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert stream to IExtendedIncomingMessage
|
|
||||||
const autoJsonParse = optionsArg.autoJsonParse !== false;
|
|
||||||
return streamToExtendedMessage(stream, autoJsonParse);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Safe GET request
|
|
||||||
*/
|
|
||||||
export async function safeGet(urlArg: string): Promise<core.IExtendedIncomingMessage | null> {
|
|
||||||
const agentToUse = urlArg.startsWith('http://')
|
|
||||||
? new plugins.http.Agent()
|
|
||||||
: new plugins.https.Agent();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await request(urlArg, {
|
|
||||||
method: 'GET',
|
|
||||||
agent: agentToUse,
|
|
||||||
timeout: 5000,
|
|
||||||
hardDataCuttingTimeout: 5000,
|
|
||||||
autoJsonParse: false,
|
|
||||||
});
|
|
||||||
return response;
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET JSON request
|
|
||||||
*/
|
|
||||||
export async function getJson(urlArg: string, optionsArg: ISmartRequestOptions = {}) {
|
|
||||||
optionsArg.method = 'GET';
|
|
||||||
return request(urlArg, optionsArg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST JSON request
|
|
||||||
*/
|
|
||||||
export async function postJson(urlArg: string, optionsArg: ISmartRequestOptions = {}) {
|
|
||||||
optionsArg.method = 'POST';
|
|
||||||
if (
|
|
||||||
typeof optionsArg.requestBody === 'object' &&
|
|
||||||
(!optionsArg.headers || !optionsArg.headers['Content-Type'])
|
|
||||||
) {
|
|
||||||
// make sure headers exist
|
|
||||||
if (!optionsArg.headers) {
|
|
||||||
optionsArg.headers = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// assign the right Content-Type, leaving all other headers in place
|
|
||||||
optionsArg.headers = {
|
|
||||||
...optionsArg.headers,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return request(urlArg, optionsArg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PUT JSON request
|
|
||||||
*/
|
|
||||||
export async function putJson(urlArg: string, optionsArg: ISmartRequestOptions = {}) {
|
|
||||||
optionsArg.method = 'PUT';
|
|
||||||
return request(urlArg, optionsArg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DELETE JSON request
|
|
||||||
*/
|
|
||||||
export async function delJson(urlArg: string, optionsArg: ISmartRequestOptions = {}) {
|
|
||||||
optionsArg.method = 'DELETE';
|
|
||||||
return request(urlArg, optionsArg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET binary data
|
|
||||||
*/
|
|
||||||
export async function getBinary(urlArg: string, optionsArg: ISmartRequestOptions = {}) {
|
|
||||||
optionsArg = {
|
|
||||||
...optionsArg,
|
|
||||||
autoJsonParse: false,
|
|
||||||
responseType: 'binary'
|
|
||||||
};
|
|
||||||
return request(urlArg, optionsArg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST form data
|
|
||||||
*/
|
|
||||||
export async function postFormData(urlArg: string, formFields: IFormField[], optionsArg: ISmartRequestOptions = {}) {
|
|
||||||
const form = new plugins.formData();
|
|
||||||
|
|
||||||
for (const formField of formFields) {
|
|
||||||
if (formField.type === 'filePath') {
|
|
||||||
const fileData = plugins.fs.readFileSync(
|
|
||||||
plugins.path.isAbsolute(formField.payload as string)
|
|
||||||
? formField.payload as string
|
|
||||||
: plugins.path.join(process.cwd(), formField.payload as string)
|
|
||||||
);
|
|
||||||
form.append(formField.name, fileData, {
|
|
||||||
filename: formField.fileName || plugins.path.basename(formField.payload as string),
|
|
||||||
contentType: formField.contentType
|
|
||||||
});
|
|
||||||
} else if (formField.type === 'Buffer') {
|
|
||||||
form.append(formField.name, formField.payload, {
|
|
||||||
filename: formField.fileName,
|
|
||||||
contentType: formField.contentType
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
form.append(formField.name, formField.payload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
optionsArg.method = 'POST';
|
|
||||||
optionsArg.requestBody = form;
|
|
||||||
if (!optionsArg.headers) {
|
|
||||||
optionsArg.headers = {};
|
|
||||||
}
|
|
||||||
optionsArg.headers = {
|
|
||||||
...optionsArg.headers,
|
|
||||||
...form.getHeaders()
|
|
||||||
};
|
|
||||||
|
|
||||||
return request(urlArg, optionsArg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST URL encoded form data
|
|
||||||
*/
|
|
||||||
export async function postFormDataUrlEncoded(
|
|
||||||
urlArg: string,
|
|
||||||
formFields: { key: string; content: string }[],
|
|
||||||
optionsArg: ISmartRequestOptions = {}
|
|
||||||
) {
|
|
||||||
optionsArg.method = 'POST';
|
|
||||||
if (!optionsArg.headers) {
|
|
||||||
optionsArg.headers = {};
|
|
||||||
}
|
|
||||||
optionsArg.headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
||||||
|
|
||||||
const urlEncodedBody = formFields
|
|
||||||
.map(field => `${encodeURIComponent(field.key)}=${encodeURIComponent(field.content)}`)
|
|
||||||
.join('&');
|
|
||||||
|
|
||||||
optionsArg.requestBody = urlEncodedBody;
|
|
||||||
|
|
||||||
return request(urlArg, optionsArg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET stream
|
|
||||||
*/
|
|
||||||
export async function getStream(
|
|
||||||
urlArg: string,
|
|
||||||
optionsArg: ISmartRequestOptions = {}
|
|
||||||
): Promise<plugins.http.IncomingMessage> {
|
|
||||||
optionsArg.method = 'GET';
|
|
||||||
const response = await request(urlArg, optionsArg, true);
|
|
||||||
return response;
|
|
||||||
}
|
|
@@ -1,2 +0,0 @@
|
|||||||
// Export everything from the legacy adapter
|
|
||||||
export * from './adapter.js';
|
|
Reference in New Issue
Block a user