BREAKING CHANGE(core): major architectural refactoring with cross-platform support and SmartRequest rename
This commit is contained in:
29
changelog.md
29
changelog.md
@@ -1,5 +1,34 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-07-28 - 4.0.0 - BREAKING CHANGE(core)
|
||||||
|
Complete architectural overhaul with cross-platform support
|
||||||
|
|
||||||
|
**Breaking Changes:**
|
||||||
|
- Renamed `SmartRequestClient` to `SmartRequest` for simpler, cleaner API
|
||||||
|
- Removed legacy API entirely (no more `/legacy` import path)
|
||||||
|
- Major architectural refactoring:
|
||||||
|
- Added abstraction layer with `core_base` containing abstract classes
|
||||||
|
- Split implementations into `core_node` (Node.js) and `core_fetch` (browser)
|
||||||
|
- Dynamic implementation selection based on environment
|
||||||
|
- Response streaming API changes:
|
||||||
|
- `stream()` now always returns web-style `ReadableStream<Uint8Array>`
|
||||||
|
- Added `streamNode()` for Node.js streams (throws error in browser)
|
||||||
|
- Unified type system with single `ICoreRequestOptions` interface
|
||||||
|
- Removed all "Abstract" prefixes from type names
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Full cross-platform support (Node.js and browsers)
|
||||||
|
- Automatic platform detection using @push.rocks/smartenv
|
||||||
|
- Consistent API across platforms with platform-specific capabilities
|
||||||
|
- Web Streams API support in both environments
|
||||||
|
- Better error messages for unsupported platform features
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
- Completely rewritten README with platform-specific examples
|
||||||
|
- Added architecture overview section
|
||||||
|
- Added migration guide from v2.x and v3.x
|
||||||
|
- Updated all examples to use the new `SmartRequest` class name
|
||||||
|
|
||||||
## 2025-07-27 - 3.0.0 - BREAKING CHANGE(core)
|
## 2025-07-27 - 3.0.0 - BREAKING CHANGE(core)
|
||||||
Major architectural refactoring with fetch-like API
|
Major architectural refactoring with fetch-like API
|
||||||
|
|
||||||
|
13
package.json
13
package.json
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartrequest",
|
"name": "@push.rocks/smartrequest",
|
||||||
"version": "3.0.0",
|
"version": "4.0.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"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",
|
"./core_node": "./dist_ts/core_node/index.js",
|
||||||
"./fetch": "./dist_ts/core_fetch/index.js"
|
"./core_fetch": "./dist_ts/core_fetch/index.js"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -44,13 +44,12 @@
|
|||||||
"@push.rocks/smartpromise": "^4.0.4",
|
"@push.rocks/smartpromise": "^4.0.4",
|
||||||
"@push.rocks/smarturl": "^3.1.0",
|
"@push.rocks/smarturl": "^3.1.0",
|
||||||
"agentkeepalive": "^4.5.0",
|
"agentkeepalive": "^4.5.0",
|
||||||
"form-data": "^4.0.1"
|
"form-data": "^4.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^2.2.0",
|
"@git.zone/tsbuild": "^2.6.4",
|
||||||
"@git.zone/tsrun": "^1.3.3",
|
"@git.zone/tsrun": "^1.3.3",
|
||||||
"@git.zone/tstest": "^1.0.90",
|
"@git.zone/tstest": "^2.3.2",
|
||||||
"@pushrocks/tapbundle": "^5.0.8",
|
|
||||||
"@types/node": "^22.9.0"
|
"@types/node": "^22.9.0"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
4185
pnpm-lock.yaml
generated
4185
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -11,44 +11,69 @@
|
|||||||
- written in TypeScript
|
- written in TypeScript
|
||||||
- continuously updated
|
- continuously updated
|
||||||
- uses node native http and https modules
|
- uses node native http and https modules
|
||||||
|
- supports both Node.js and browser environments
|
||||||
- used in modules like @push.rocks/smartproxy and @api.global/typedrequest
|
- used in modules like @push.rocks/smartproxy and @api.global/typedrequest
|
||||||
|
|
||||||
## Architecture Overview (as of latest refactoring)
|
## Architecture Overview (as of v3.0.0 major refactoring)
|
||||||
- The project is now structured with a clean separation between core functionality and API layers
|
- The project now has a multi-layer architecture with platform abstraction
|
||||||
- Core module (ts/core/) contains the essential HTTP request logic using Node.js http/https modules
|
- Base layer (ts/core_base/) contains abstract classes and unified types
|
||||||
- **Core always returns raw streams** - no parsing or body collection happens in the core request function
|
- Node.js implementation (ts/core_node/) uses native http/https modules
|
||||||
- Modern API (ts/modern/) provides a fluent, chainable interface with fetch-like Response objects
|
- Fetch implementation (ts/core_fetch/) uses Fetch API for browser compatibility
|
||||||
- Legacy API is maintained through a thin adapter layer for backward compatibility
|
- Core module (ts/core/) dynamically selects the appropriate implementation based on environment
|
||||||
|
- Client API (ts/client/) provides a fluent, chainable interface
|
||||||
|
- Legacy API has been completely removed in v3.0.0
|
||||||
|
|
||||||
## Key Components
|
## Key Components
|
||||||
|
|
||||||
### Core Module (ts/core/)
|
### Core Base Module (ts/core_base/)
|
||||||
- `request.ts`: Core HTTP/HTTPS request logic with unix socket support and keep-alive agents
|
- `request.ts`: Abstract CoreRequest class defining the request interface
|
||||||
- `coreRequest()` always returns a raw Node.js IncomingMessage stream
|
- `response.ts`: Abstract CoreResponse class with fetch-like API
|
||||||
- No response parsing or body collection happens here
|
- Defines `stream()` method that always returns web-style ReadableStream
|
||||||
- `response.ts`: SmartResponse class providing fetch-like API
|
|
||||||
- Methods like `json()`, `text()`, `arrayBuffer()` handle all parsing and body collection
|
|
||||||
- Response body is streamed and collected only when these methods are called
|
|
||||||
- Body can only be consumed once (throws error on second attempt)
|
- Body can only be consumed once (throws error on second attempt)
|
||||||
- `types.ts`: Core TypeScript interfaces and types
|
- `types.ts`: Unified TypeScript interfaces and types
|
||||||
- `plugins.ts`: Centralized dependencies
|
- Single `ICoreRequestOptions` interface for all implementations
|
||||||
|
- Implementations handle unsupported options by throwing errors
|
||||||
|
|
||||||
### Modern API
|
### Core Node Module (ts/core_node/)
|
||||||
- SmartRequestClient: Fluent API with method chaining
|
- `request.ts`: Node.js implementation using http/https modules
|
||||||
- Returns SmartResponse objects with fetch-like methods
|
- Supports unix socket connections and keep-alive agents
|
||||||
|
- Converts Node.js specific options from unified interface
|
||||||
|
- `response.ts`: Node.js CoreResponse implementation
|
||||||
|
- `stream()` method converts Node.js stream to web ReadableStream
|
||||||
|
- `streamNode()` method returns native Node.js stream
|
||||||
|
- Methods like `json()`, `text()`, `arrayBuffer()` handle parsing
|
||||||
|
|
||||||
|
### Core Fetch Module (ts/core_fetch/)
|
||||||
|
- `request.ts`: Fetch API implementation for browsers
|
||||||
|
- Throws errors for Node.js specific options (agent, socketPath)
|
||||||
|
- Native support for CORS, credentials, and other browser features
|
||||||
|
- `response.ts`: Fetch-based CoreResponse implementation
|
||||||
|
- `stream()` returns native web ReadableStream from response.body
|
||||||
|
- `streamNode()` throws error explaining it's not available in browser
|
||||||
|
|
||||||
|
### Core Module (ts/core/)
|
||||||
|
- Dynamically loads appropriate implementation based on environment
|
||||||
|
- Uses @push.rocks/smartenv for environment detection
|
||||||
|
- Exports unified types from core_base
|
||||||
|
|
||||||
|
### Client API (ts/client/)
|
||||||
|
- SmartRequest: Fluent API with method chaining
|
||||||
|
- Returns CoreResponse objects with fetch-like methods
|
||||||
- Supports pagination, retries, timeouts, and various response types
|
- Supports pagination, retries, timeouts, and various response types
|
||||||
|
|
||||||
### Binary Request Handling
|
### Stream Handling
|
||||||
- Binary requests are handled correctly when `responseType: 'binary'` is set
|
- `stream()` method always returns web-style ReadableStream<Uint8Array>
|
||||||
- Response body is kept as Buffer without string conversion
|
- In Node.js, converts native streams to web streams
|
||||||
- No automatic transformations applied to binary data
|
- `streamNode()` available only in Node.js environment for native streams
|
||||||
|
- Consistent API across platforms while preserving platform-specific capabilities
|
||||||
|
|
||||||
### Legacy Compatibility
|
### Binary Request Handling
|
||||||
- All legacy functions (getJson, postJson, etc.) are maintained through adapter.ts
|
- Binary requests handled through ArrayBuffer API
|
||||||
- Legacy API returns IExtendedIncomingMessage for backward compatibility
|
- Response body kept as Buffer/ArrayBuffer without string conversion
|
||||||
- Modern API can be accessed alongside legacy API
|
- No automatic transformations applied to binary data
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
- Use `pnpm test` to run all tests
|
- Use `pnpm test` to run all tests
|
||||||
- Modern API tests use the new SmartResponse methods (response.json(), response.text())
|
- Tests use @git.zone/tstest/tapbundle for assertions
|
||||||
- Legacy API tests continue to use the body property directly
|
- Separate test files for Node.js (test.node.ts) and browser (test.browser.ts)
|
||||||
|
- Browser tests run in headless Chromium via puppeteer
|
||||||
|
206
readme.md
206
readme.md
@@ -1,5 +1,5 @@
|
|||||||
# @push.rocks/smartrequest
|
# @push.rocks/smartrequest
|
||||||
A modern HTTP/HTTPS request library for Node.js with a fetch-like API, supporting form data, file uploads, JSON, binary data, streams, and unix sockets.
|
A modern, cross-platform HTTP/HTTPS request library for Node.js and browsers with a unified API, supporting form data, file uploads, JSON, binary data, streams, and unix sockets.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
```bash
|
```bash
|
||||||
@@ -16,28 +16,38 @@ yarn add @push.rocks/smartrequest
|
|||||||
## Key Features
|
## Key Features
|
||||||
|
|
||||||
- 🚀 **Modern Fetch-like API** - Familiar response methods (`.json()`, `.text()`, `.arrayBuffer()`, `.stream()`)
|
- 🚀 **Modern Fetch-like API** - Familiar response methods (`.json()`, `.text()`, `.arrayBuffer()`, `.stream()`)
|
||||||
- 🌐 **Unix Socket Support** - Connect to local services like Docker
|
- 🌐 **Cross-Platform** - Works in both Node.js and browsers with a unified API
|
||||||
|
- 🔌 **Unix Socket Support** - Connect to local services like Docker (Node.js only)
|
||||||
- 📦 **Form Data & File Uploads** - Built-in support for multipart/form-data
|
- 📦 **Form Data & File Uploads** - Built-in support for multipart/form-data
|
||||||
- 🔁 **Pagination Support** - Multiple strategies (offset, cursor, Link headers)
|
- 🔁 **Pagination Support** - Multiple strategies (offset, cursor, Link headers)
|
||||||
- ⚡ **Keep-Alive Connections** - Efficient connection pooling
|
- ⚡ **Keep-Alive Connections** - Efficient connection pooling in Node.js
|
||||||
- 🛡️ **TypeScript First** - Full type safety and IntelliSense support
|
- 🛡️ **TypeScript First** - Full type safety and IntelliSense support
|
||||||
- 🎯 **Zero Magic Defaults** - Explicit configuration following fetch API principles
|
- 🎯 **Zero Magic Defaults** - Explicit configuration following fetch API principles
|
||||||
- 🔌 **Streaming Support** - Handle large files and real-time data
|
- 📡 **Streaming Support** - Handle large files and real-time data
|
||||||
- 🔧 **Highly Configurable** - Timeouts, retries, headers, and more
|
- 🔧 **Highly Configurable** - Timeouts, retries, headers, and more
|
||||||
- 🔄 **Legacy API Available** - For backward compatibility
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
SmartRequest v3.0 features a multi-layer architecture that provides consistent behavior across platforms:
|
||||||
|
|
||||||
|
- **Core Base** - Abstract classes and unified types shared across implementations
|
||||||
|
- **Core Node** - Node.js implementation using native http/https modules
|
||||||
|
- **Core Fetch** - Browser implementation using the Fetch API
|
||||||
|
- **Core** - Dynamic implementation selection based on environment
|
||||||
|
- **Client** - High-level fluent API for everyday use
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
`@push.rocks/smartrequest` provides a clean, type-safe API inspired by the native fetch API but with additional features needed for server-side applications.
|
`@push.rocks/smartrequest` provides a clean, type-safe API inspired by the native fetch API but with additional features needed for modern applications.
|
||||||
|
|
||||||
### Basic Usage
|
### Basic Usage
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
import { SmartRequest } from '@push.rocks/smartrequest';
|
||||||
|
|
||||||
// Simple GET request
|
// Simple GET request
|
||||||
async function fetchUserData(userId: number) {
|
async function fetchUserData(userId: number) {
|
||||||
const response = await SmartRequestClient.create()
|
const response = await SmartRequest.create()
|
||||||
.url(`https://jsonplaceholder.typicode.com/users/${userId}`)
|
.url(`https://jsonplaceholder.typicode.com/users/${userId}`)
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
@@ -48,7 +58,7 @@ async function fetchUserData(userId: number) {
|
|||||||
|
|
||||||
// POST request with JSON body
|
// POST request with JSON body
|
||||||
async function createPost(title: string, body: string, userId: number) {
|
async function createPost(title: string, body: string, userId: number) {
|
||||||
const response = await SmartRequestClient.create()
|
const response = await SmartRequest.create()
|
||||||
.url('https://jsonplaceholder.typicode.com/posts')
|
.url('https://jsonplaceholder.typicode.com/posts')
|
||||||
.json({ title, body, userId })
|
.json({ title, body, userId })
|
||||||
.post();
|
.post();
|
||||||
@@ -58,13 +68,34 @@ async function createPost(title: string, body: string, userId: number) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Direct Core API Usage
|
||||||
|
|
||||||
|
For advanced use cases, you can use the Core API directly:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { CoreRequest } from '@push.rocks/smartrequest';
|
||||||
|
|
||||||
|
async function directCoreRequest() {
|
||||||
|
const request = new CoreRequest('https://api.example.com/data', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await request.fire();
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Setting Headers and Query Parameters
|
### Setting Headers and Query Parameters
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
import { SmartRequest } from '@push.rocks/smartrequest';
|
||||||
|
|
||||||
async function searchRepositories(query: string, perPage: number = 10) {
|
async function searchRepositories(query: string, perPage: number = 10) {
|
||||||
const response = await SmartRequestClient.create()
|
const response = await SmartRequest.create()
|
||||||
.url('https://api.github.com/search/repositories')
|
.url('https://api.github.com/search/repositories')
|
||||||
.header('Accept', 'application/vnd.github.v3+json')
|
.header('Accept', 'application/vnd.github.v3+json')
|
||||||
.query({
|
.query({
|
||||||
@@ -81,10 +112,10 @@ async function searchRepositories(query: string, perPage: number = 10) {
|
|||||||
### Handling Timeouts and Retries
|
### Handling Timeouts and Retries
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
import { SmartRequest } from '@push.rocks/smartrequest';
|
||||||
|
|
||||||
async function fetchWithRetry(url: string) {
|
async function fetchWithRetry(url: string) {
|
||||||
const response = await SmartRequestClient.create()
|
const response = await SmartRequest.create()
|
||||||
.url(url)
|
.url(url)
|
||||||
.timeout(5000) // 5 seconds timeout
|
.timeout(5000) // 5 seconds timeout
|
||||||
.retry(3) // Retry up to 3 times on failure
|
.retry(3) // Retry up to 3 times on failure
|
||||||
@@ -99,11 +130,11 @@ async function fetchWithRetry(url: string) {
|
|||||||
The API provides a fetch-like interface for handling different response types:
|
The API provides a fetch-like interface for handling different response types:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
import { SmartRequest } from '@push.rocks/smartrequest';
|
||||||
|
|
||||||
// JSON response (default)
|
// JSON response (default)
|
||||||
async function fetchJson(url: string) {
|
async function fetchJson(url: string) {
|
||||||
const response = await SmartRequestClient.create()
|
const response = await SmartRequest.create()
|
||||||
.url(url)
|
.url(url)
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
@@ -112,7 +143,7 @@ async function fetchJson(url: string) {
|
|||||||
|
|
||||||
// Text response
|
// Text response
|
||||||
async function fetchText(url: string) {
|
async function fetchText(url: string) {
|
||||||
const response = await SmartRequestClient.create()
|
const response = await SmartRequest.create()
|
||||||
.url(url)
|
.url(url)
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
@@ -121,7 +152,7 @@ async function fetchText(url: string) {
|
|||||||
|
|
||||||
// Binary data
|
// Binary data
|
||||||
async function downloadImage(url: string) {
|
async function downloadImage(url: string) {
|
||||||
const response = await SmartRequestClient.create()
|
const response = await SmartRequest.create()
|
||||||
.url(url)
|
.url(url)
|
||||||
.accept('binary') // Optional: hints to server we want binary
|
.accept('binary') // Optional: hints to server we want binary
|
||||||
.get();
|
.get();
|
||||||
@@ -130,35 +161,60 @@ async function downloadImage(url: string) {
|
|||||||
return Buffer.from(buffer); // Convert ArrayBuffer to Buffer if needed
|
return Buffer.from(buffer); // Convert ArrayBuffer to Buffer if needed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Streaming response
|
// Streaming response (Web Streams API)
|
||||||
async function streamLargeFile(url: string) {
|
async function streamLargeFile(url: string) {
|
||||||
const response = await SmartRequestClient.create()
|
const response = await SmartRequest.create()
|
||||||
.url(url)
|
.url(url)
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
// Get the underlying Node.js stream
|
// Get a web-style ReadableStream (works in both Node.js and browsers)
|
||||||
const stream = response.stream();
|
const stream = response.stream();
|
||||||
|
|
||||||
stream.on('data', (chunk) => {
|
if (stream) {
|
||||||
|
const reader = stream.getReader();
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
console.log(`Received ${value.length} bytes of data`);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
reader.releaseLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Node.js specific stream (only in Node.js environment)
|
||||||
|
async function streamWithNodeApi(url: string) {
|
||||||
|
const response = await SmartRequest.create()
|
||||||
|
.url(url)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
// Only available in Node.js, throws error in browser
|
||||||
|
const nodeStream = response.streamNode();
|
||||||
|
|
||||||
|
nodeStream.on('data', (chunk) => {
|
||||||
console.log(`Received ${chunk.length} bytes of data`);
|
console.log(`Received ${chunk.length} bytes of data`);
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
stream.on('end', resolve);
|
nodeStream.on('end', resolve);
|
||||||
stream.on('error', reject);
|
nodeStream.on('error', reject);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Response Object Methods
|
### Response Object Methods
|
||||||
|
|
||||||
The `SmartResponse` object provides these methods:
|
The response object provides these methods:
|
||||||
|
|
||||||
- `json<T>(): Promise<T>` - Parse response as JSON
|
- `json<T>(): Promise<T>` - Parse response as JSON
|
||||||
- `text(): Promise<string>` - Get response as text
|
- `text(): Promise<string>` - Get response as text
|
||||||
- `arrayBuffer(): Promise<ArrayBuffer>` - Get response as ArrayBuffer
|
- `arrayBuffer(): Promise<ArrayBuffer>` - Get response as ArrayBuffer
|
||||||
- `stream(): NodeJS.ReadableStream` - Get the underlying Node.js stream
|
- `stream(): ReadableStream<Uint8Array> | null` - Get web-style ReadableStream (cross-platform)
|
||||||
- `raw(): http.IncomingMessage` - Get the raw http.IncomingMessage
|
- `streamNode(): NodeJS.ReadableStream` - Get Node.js stream (Node.js only, throws in browser)
|
||||||
|
- `raw(): Response | http.IncomingMessage` - Get the underlying platform response
|
||||||
|
|
||||||
Each body method can only be called once per response, similar to the fetch API.
|
Each body method can only be called once per response, similar to the fetch API.
|
||||||
|
|
||||||
@@ -167,7 +223,7 @@ Each body method can only be called once per response, similar to the fetch API.
|
|||||||
### Form Data with File Uploads
|
### Form Data with File Uploads
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
import { SmartRequest } from '@push.rocks/smartrequest';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
|
|
||||||
async function uploadMultipleFiles(files: Array<{name: string, path: string}>) {
|
async function uploadMultipleFiles(files: Array<{name: string, path: string}>) {
|
||||||
@@ -178,7 +234,7 @@ async function uploadMultipleFiles(files: Array<{name: string, path: string}>) {
|
|||||||
contentType: 'application/octet-stream'
|
contentType: 'application/octet-stream'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const response = await SmartRequestClient.create()
|
const response = await SmartRequest.create()
|
||||||
.url('https://api.example.com/upload')
|
.url('https://api.example.com/upload')
|
||||||
.formData(formFields)
|
.formData(formFields)
|
||||||
.post();
|
.post();
|
||||||
@@ -187,14 +243,14 @@ async function uploadMultipleFiles(files: Array<{name: string, path: string}>) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Unix Socket Support
|
### Unix Socket Support (Node.js only)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
import { SmartRequest } from '@push.rocks/smartrequest';
|
||||||
|
|
||||||
// Connect to a service via Unix socket
|
// Connect to a service via Unix socket
|
||||||
async function queryViaUnixSocket() {
|
async function queryViaUnixSocket() {
|
||||||
const response = await SmartRequestClient.create()
|
const response = await SmartRequest.create()
|
||||||
.url('http://unix:/var/run/docker.sock:/v1.24/containers/json')
|
.url('http://unix:/var/run/docker.sock:/v1.24/containers/json')
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
@@ -207,11 +263,11 @@ async function queryViaUnixSocket() {
|
|||||||
The library includes built-in support for various pagination strategies:
|
The library includes built-in support for various pagination strategies:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
import { SmartRequest } from '@push.rocks/smartrequest';
|
||||||
|
|
||||||
// Offset-based pagination (page & limit)
|
// Offset-based pagination (page & limit)
|
||||||
async function fetchAllUsers() {
|
async function fetchAllUsers() {
|
||||||
const client = SmartRequestClient.create()
|
const client = SmartRequest.create()
|
||||||
.url('https://api.example.com/users')
|
.url('https://api.example.com/users')
|
||||||
.withOffsetPagination({
|
.withOffsetPagination({
|
||||||
pageParam: 'page',
|
pageParam: 'page',
|
||||||
@@ -239,7 +295,7 @@ async function fetchAllUsers() {
|
|||||||
|
|
||||||
// Cursor-based pagination
|
// Cursor-based pagination
|
||||||
async function fetchAllPosts() {
|
async function fetchAllPosts() {
|
||||||
const allPosts = await SmartRequestClient.create()
|
const allPosts = await SmartRequest.create()
|
||||||
.url('https://api.example.com/posts')
|
.url('https://api.example.com/posts')
|
||||||
.withCursorPagination({
|
.withCursorPagination({
|
||||||
cursorParam: 'cursor',
|
cursorParam: 'cursor',
|
||||||
@@ -253,7 +309,7 @@ async function fetchAllPosts() {
|
|||||||
|
|
||||||
// Link header-based pagination (GitHub API style)
|
// Link header-based pagination (GitHub API style)
|
||||||
async function fetchAllIssues(repo: string) {
|
async function fetchAllIssues(repo: string) {
|
||||||
const paginatedResponse = await SmartRequestClient.create()
|
const paginatedResponse = await SmartRequest.create()
|
||||||
.url(`https://api.github.com/repos/${repo}/issues`)
|
.url(`https://api.github.com/repos/${repo}/issues`)
|
||||||
.header('Accept', 'application/vnd.github.v3+json')
|
.header('Accept', 'application/vnd.github.v3+json')
|
||||||
.withLinkPagination()
|
.withLinkPagination()
|
||||||
@@ -263,17 +319,17 @@ async function fetchAllIssues(repo: string) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Keep-Alive Connections
|
### Keep-Alive Connections (Node.js)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
import { SmartRequest } from '@push.rocks/smartrequest';
|
||||||
|
|
||||||
// Enable keep-alive for better performance with multiple requests
|
// Enable keep-alive for better performance with multiple requests
|
||||||
async function performMultipleRequests() {
|
async function performMultipleRequests() {
|
||||||
const client = SmartRequestClient.create()
|
const client = SmartRequest.create()
|
||||||
.header('Connection', 'keep-alive');
|
.header('Connection', 'keep-alive');
|
||||||
|
|
||||||
// Requests will reuse the same connection
|
// Requests will reuse the same connection in Node.js
|
||||||
const results = await Promise.all([
|
const results = await Promise.all([
|
||||||
client.url('https://api.example.com/endpoint1').get(),
|
client.url('https://api.example.com/endpoint1').get(),
|
||||||
client.url('https://api.example.com/endpoint2').get(),
|
client.url('https://api.example.com/endpoint2').get(),
|
||||||
@@ -284,12 +340,46 @@ async function performMultipleRequests() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Platform-Specific Features
|
||||||
|
|
||||||
|
### Browser-Specific Options
|
||||||
|
|
||||||
|
When running in a browser, you can use browser-specific fetch options:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const response = await SmartRequest.create()
|
||||||
|
.url('https://api.example.com/data')
|
||||||
|
.option({
|
||||||
|
credentials: 'include', // Include cookies
|
||||||
|
mode: 'cors', // CORS mode
|
||||||
|
cache: 'no-cache', // Cache mode
|
||||||
|
referrerPolicy: 'no-referrer'
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Node.js-Specific Options
|
||||||
|
|
||||||
|
When running in Node.js, you can use Node-specific options:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Agent } from 'https';
|
||||||
|
|
||||||
|
const response = await SmartRequest.create()
|
||||||
|
.url('https://api.example.com/data')
|
||||||
|
.option({
|
||||||
|
agent: new Agent({ keepAlive: true }), // Custom agent
|
||||||
|
socketPath: '/var/run/api.sock', // Unix socket
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
```
|
||||||
|
|
||||||
## Complete Example: Building a REST API Client
|
## Complete Example: Building a REST API Client
|
||||||
|
|
||||||
Here's a complete example of building a typed API client:
|
Here's a complete example of building a typed API client:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { SmartRequestClient, type SmartResponse } from '@push.rocks/smartrequest';
|
import { SmartRequest, type CoreResponse } from '@push.rocks/smartrequest';
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -308,7 +398,7 @@ class BlogApiClient {
|
|||||||
private baseUrl = 'https://jsonplaceholder.typicode.com';
|
private baseUrl = 'https://jsonplaceholder.typicode.com';
|
||||||
|
|
||||||
private async request(path: string) {
|
private async request(path: string) {
|
||||||
return SmartRequestClient.create()
|
return SmartRequest.create()
|
||||||
.url(`${this.baseUrl}${path}`)
|
.url(`${this.baseUrl}${path}`)
|
||||||
.header('Accept', 'application/json');
|
.header('Accept', 'application/json');
|
||||||
}
|
}
|
||||||
@@ -354,11 +444,11 @@ const posts = await api.getAllPosts(user.id);
|
|||||||
## Error Handling
|
## Error Handling
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
import { SmartRequest } from '@push.rocks/smartrequest';
|
||||||
|
|
||||||
async function fetchWithErrorHandling(url: string) {
|
async function fetchWithErrorHandling(url: string) {
|
||||||
try {
|
try {
|
||||||
const response = await SmartRequestClient.create()
|
const response = await SmartRequest.create()
|
||||||
.url(url)
|
.url(url)
|
||||||
.timeout(5000)
|
.timeout(5000)
|
||||||
.retry(2)
|
.retry(2)
|
||||||
@@ -384,6 +474,8 @@ async function fetchWithErrorHandling(url: string) {
|
|||||||
console.error('Connection refused - is the server running?');
|
console.error('Connection refused - is the server running?');
|
||||||
} else if (error.code === 'ETIMEDOUT') {
|
} else if (error.code === 'ETIMEDOUT') {
|
||||||
console.error('Request timed out');
|
console.error('Request timed out');
|
||||||
|
} else if (error.name === 'AbortError') {
|
||||||
|
console.error('Request was aborted');
|
||||||
} else {
|
} else {
|
||||||
console.error('Request failed:', error.message);
|
console.error('Request failed:', error.message);
|
||||||
}
|
}
|
||||||
@@ -392,30 +484,14 @@ async function fetchWithErrorHandling(url: string) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Legacy API
|
## Migrating from v2.x to v3.x
|
||||||
|
|
||||||
For backward compatibility, the original function-based API is still available via a separate import:
|
Version 3.0 brings significant architectural improvements and a more consistent API:
|
||||||
|
|
||||||
```typescript
|
1. **Legacy API Removed**: The function-based API (getJson, postJson, etc.) has been removed. Use SmartRequest instead.
|
||||||
import { getJson, postJson, request } from '@push.rocks/smartrequest/legacy';
|
2. **Unified Response API**: All responses now use the same fetch-like interface regardless of platform.
|
||||||
|
3. **Stream Changes**: The `stream()` method now returns a web-style ReadableStream on all platforms. Use `streamNode()` for Node.js streams.
|
||||||
// Simple GET request
|
4. **Cross-Platform by Default**: The library now works in browsers out of the box with automatic platform detection.
|
||||||
const response = await getJson('https://api.example.com/data');
|
|
||||||
console.log(response.body);
|
|
||||||
|
|
||||||
// POST request
|
|
||||||
const result = await postJson('https://api.example.com/users', {
|
|
||||||
requestBody: { name: 'John', email: 'john@example.com' }
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
For migration from the legacy API to the modern API, here's a quick reference:
|
|
||||||
|
|
||||||
| Legacy API | Modern API |
|
|
||||||
|------------|------------|
|
|
||||||
| `getJson(url)` | `SmartRequestClient.create().url(url).get()` |
|
|
||||||
| `postJson(url, { requestBody: data })` | `SmartRequestClient.create().url(url).json(data).post()` |
|
|
||||||
| `request(url, options)` | `SmartRequestClient.create().url(url).[...configure].get()` |
|
|
||||||
|
|
||||||
## License and Legal Information
|
## License and Legal Information
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { tap, expect } from '@pushrocks/tapbundle';
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
|
||||||
// For browser tests, we need to import from a browser-safe path
|
// For browser tests, we need to import from a browser-safe path
|
||||||
// that doesn't trigger Node.js module imports
|
// that doesn't trigger Node.js module imports
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { tap, expect } from '@pushrocks/tapbundle';
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
|
||||||
import { SmartRequestClient } from '../ts/client/index.js';
|
import { SmartRequest } from '../ts/client/index.js';
|
||||||
|
|
||||||
tap.test('client: should request a html document over https', async () => {
|
tap.test('client: should request a html document over https', async () => {
|
||||||
const response = await SmartRequestClient.create()
|
const response = await SmartRequest.create()
|
||||||
.url('https://encrypted.google.com/')
|
.url('https://encrypted.google.com/')
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ tap.test('client: should request a html document over https', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
tap.test('client: should request a JSON document over https', async () => {
|
tap.test('client: should request a JSON document over https', async () => {
|
||||||
const response = await SmartRequestClient.create()
|
const response = await SmartRequest.create()
|
||||||
.url('https://jsonplaceholder.typicode.com/posts/1')
|
.url('https://jsonplaceholder.typicode.com/posts/1')
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ tap.test('client: should request a JSON document over https', async () => {
|
|||||||
|
|
||||||
tap.test('client: should post a JSON document over http', async () => {
|
tap.test('client: should post a JSON document over http', async () => {
|
||||||
const testData = { text: 'example_text' };
|
const testData = { text: 'example_text' };
|
||||||
const response = await SmartRequestClient.create()
|
const response = await SmartRequest.create()
|
||||||
.url('https://httpbin.org/post')
|
.url('https://httpbin.org/post')
|
||||||
.json(testData)
|
.json(testData)
|
||||||
.post();
|
.post();
|
||||||
@@ -41,7 +41,7 @@ tap.test('client: should set headers correctly', async () => {
|
|||||||
const customHeader = 'X-Custom-Header';
|
const customHeader = 'X-Custom-Header';
|
||||||
const headerValue = 'test-value';
|
const headerValue = 'test-value';
|
||||||
|
|
||||||
const response = await SmartRequestClient.create()
|
const response = await SmartRequest.create()
|
||||||
.url('https://httpbin.org/headers')
|
.url('https://httpbin.org/headers')
|
||||||
.header(customHeader, headerValue)
|
.header(customHeader, headerValue)
|
||||||
.get();
|
.get();
|
||||||
@@ -57,7 +57,7 @@ tap.test('client: should set headers correctly', async () => {
|
|||||||
tap.test('client: should handle query parameters', async () => {
|
tap.test('client: should handle query parameters', async () => {
|
||||||
const params = { param1: 'value1', param2: 'value2' };
|
const params = { param1: 'value1', param2: 'value2' };
|
||||||
|
|
||||||
const response = await SmartRequestClient.create()
|
const response = await SmartRequest.create()
|
||||||
.url('https://httpbin.org/get')
|
.url('https://httpbin.org/get')
|
||||||
.query(params)
|
.query(params)
|
||||||
.get();
|
.get();
|
||||||
@@ -72,7 +72,7 @@ tap.test('client: should handle query parameters', async () => {
|
|||||||
|
|
||||||
tap.test('client: should handle timeout configuration', async () => {
|
tap.test('client: should handle timeout configuration', async () => {
|
||||||
// This test just verifies that the timeout method doesn't throw
|
// This test just verifies that the timeout method doesn't throw
|
||||||
const client = SmartRequestClient.create()
|
const client = SmartRequest.create()
|
||||||
.url('https://httpbin.org/get')
|
.url('https://httpbin.org/get')
|
||||||
.timeout(5000);
|
.timeout(5000);
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ tap.test('client: should handle timeout configuration', async () => {
|
|||||||
|
|
||||||
tap.test('client: should handle retry configuration', async () => {
|
tap.test('client: should handle retry configuration', async () => {
|
||||||
// This test just verifies that the retry method doesn't throw
|
// This test just verifies that the retry method doesn't throw
|
||||||
const client = SmartRequestClient.create()
|
const client = SmartRequest.create()
|
||||||
.url('https://httpbin.org/get')
|
.url('https://httpbin.org/get')
|
||||||
.retry(1);
|
.retry(1);
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
// Export the main client
|
// Export the main client
|
||||||
export { SmartRequestClient } from './smartrequestclient.js';
|
export { SmartRequest } from './smartrequest.js';
|
||||||
|
|
||||||
// Export response type from core
|
// Export response type from core
|
||||||
export { CoreResponse } from '../core/index.js';
|
export { CoreResponse } from '../core/index.js';
|
||||||
@@ -17,32 +17,32 @@ export {
|
|||||||
} from './types/pagination.js';
|
} from './types/pagination.js';
|
||||||
|
|
||||||
// Convenience factory functions
|
// Convenience factory functions
|
||||||
import { SmartRequestClient } from './smartrequestclient.js';
|
import { SmartRequest } from './smartrequest.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a client pre-configured for JSON requests
|
* Create a client pre-configured for JSON requests
|
||||||
*/
|
*/
|
||||||
export function createJsonClient<T = any>() {
|
export function createJsonClient<T = any>() {
|
||||||
return SmartRequestClient.create<T>();
|
return SmartRequest.create<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a client pre-configured for form data requests
|
* Create a client pre-configured for form data requests
|
||||||
*/
|
*/
|
||||||
export function createFormClient<T = any>() {
|
export function createFormClient<T = any>() {
|
||||||
return SmartRequestClient.create<T>();
|
return SmartRequest.create<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a client pre-configured for binary data
|
* Create a client pre-configured for binary data
|
||||||
*/
|
*/
|
||||||
export function createBinaryClient<T = any>() {
|
export function createBinaryClient<T = any>() {
|
||||||
return SmartRequestClient.create<T>().accept('binary');
|
return SmartRequest.create<T>().accept('binary');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a client pre-configured for streaming
|
* Create a client pre-configured for streaming
|
||||||
*/
|
*/
|
||||||
export function createStreamClient() {
|
export function createStreamClient() {
|
||||||
return SmartRequestClient.create().accept('stream');
|
return SmartRequest.create().accept('stream');
|
||||||
}
|
}
|
@@ -17,7 +17,7 @@ import { createPaginatedResponse } from './features/pagination.js';
|
|||||||
/**
|
/**
|
||||||
* Modern fluent client for making HTTP requests
|
* Modern fluent client for making HTTP requests
|
||||||
*/
|
*/
|
||||||
export class SmartRequestClient<T = any> {
|
export class SmartRequest<T = any> {
|
||||||
private _url: string;
|
private _url: string;
|
||||||
private _options: ICoreRequestOptions = {};
|
private _options: ICoreRequestOptions = {};
|
||||||
private _retries: number = 0;
|
private _retries: number = 0;
|
||||||
@@ -25,10 +25,10 @@ export class SmartRequestClient<T = any> {
|
|||||||
private _paginationConfig?: TPaginationConfig;
|
private _paginationConfig?: TPaginationConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new SmartRequestClient instance
|
* Create a new SmartRequest instance
|
||||||
*/
|
*/
|
||||||
static create<T = any>(): SmartRequestClient<T> {
|
static create<T = any>(): SmartRequest<T> {
|
||||||
return new SmartRequestClient<T>();
|
return new SmartRequest<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -278,7 +278,7 @@ export class SmartRequestClient<T = any> {
|
|||||||
this._queryParams,
|
this._queryParams,
|
||||||
(nextPageParams) => {
|
(nextPageParams) => {
|
||||||
// Create a new client with the same configuration but updated query params
|
// Create a new client with the same configuration but updated query params
|
||||||
const nextClient = new SmartRequestClient<ItemType>();
|
const nextClient = new SmartRequest<ItemType>();
|
||||||
Object.assign(nextClient, this);
|
Object.assign(nextClient, this);
|
||||||
nextClient._queryParams = nextPageParams;
|
nextClient._queryParams = nextPageParams;
|
||||||
|
|
@@ -6,5 +6,5 @@ export { CoreResponse } from './core/index.js';
|
|||||||
export type { ICoreRequestOptions, ICoreResponse } from './core_base/types.js';
|
export type { ICoreRequestOptions, ICoreResponse } from './core_base/types.js';
|
||||||
|
|
||||||
// Default export for easier importing
|
// Default export for easier importing
|
||||||
import { SmartRequestClient } from './client/smartrequestclient.js';
|
import { SmartRequest } from './client/smartrequest.js';
|
||||||
export default SmartRequestClient;
|
export default SmartRequest;
|
Reference in New Issue
Block a user