BREAKING CHANGE(core): major architectural refactoring with cross-platform support and SmartRequest rename
Some checks failed
Default (tags) / security (push) Failing after 24s
Default (tags) / test (push) Failing after 12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped

This commit is contained in:
2025-07-28 23:20:52 +00:00
parent 8e75047d1f
commit 2dc82bd730
10 changed files with 2019 additions and 2545 deletions

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -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
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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