BREAKING CHANGE(core): major architectural refactoring with cross-platform support and SmartRequest rename
This commit is contained in:
208
readme.md
208
readme.md
@@ -1,12 +1,12 @@
|
||||
# @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
|
||||
```bash
|
||||
# Using npm
|
||||
npm install @push.rocks/smartrequest --save
|
||||
|
||||
# Using pnpm
|
||||
# Using pnpm
|
||||
pnpm add @push.rocks/smartrequest
|
||||
|
||||
# Using yarn
|
||||
@@ -16,28 +16,38 @@ yarn add @push.rocks/smartrequest
|
||||
## Key Features
|
||||
|
||||
- 🚀 **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
|
||||
- 🔁 **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
|
||||
- 🎯 **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
|
||||
- 🔄 **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
|
||||
|
||||
`@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
|
||||
|
||||
```typescript
|
||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
||||
import { SmartRequest } from '@push.rocks/smartrequest';
|
||||
|
||||
// Simple GET request
|
||||
async function fetchUserData(userId: number) {
|
||||
const response = await SmartRequestClient.create()
|
||||
const response = await SmartRequest.create()
|
||||
.url(`https://jsonplaceholder.typicode.com/users/${userId}`)
|
||||
.get();
|
||||
|
||||
@@ -48,7 +58,7 @@ async function fetchUserData(userId: number) {
|
||||
|
||||
// POST request with JSON body
|
||||
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')
|
||||
.json({ title, body, userId })
|
||||
.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
|
||||
|
||||
```typescript
|
||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
||||
import { SmartRequest } from '@push.rocks/smartrequest';
|
||||
|
||||
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')
|
||||
.header('Accept', 'application/vnd.github.v3+json')
|
||||
.query({
|
||||
@@ -81,10 +112,10 @@ async function searchRepositories(query: string, perPage: number = 10) {
|
||||
### Handling Timeouts and Retries
|
||||
|
||||
```typescript
|
||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
||||
import { SmartRequest } from '@push.rocks/smartrequest';
|
||||
|
||||
async function fetchWithRetry(url: string) {
|
||||
const response = await SmartRequestClient.create()
|
||||
const response = await SmartRequest.create()
|
||||
.url(url)
|
||||
.timeout(5000) // 5 seconds timeout
|
||||
.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:
|
||||
|
||||
```typescript
|
||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
||||
import { SmartRequest } from '@push.rocks/smartrequest';
|
||||
|
||||
// JSON response (default)
|
||||
async function fetchJson(url: string) {
|
||||
const response = await SmartRequestClient.create()
|
||||
const response = await SmartRequest.create()
|
||||
.url(url)
|
||||
.get();
|
||||
|
||||
@@ -112,7 +143,7 @@ async function fetchJson(url: string) {
|
||||
|
||||
// Text response
|
||||
async function fetchText(url: string) {
|
||||
const response = await SmartRequestClient.create()
|
||||
const response = await SmartRequest.create()
|
||||
.url(url)
|
||||
.get();
|
||||
|
||||
@@ -121,7 +152,7 @@ async function fetchText(url: string) {
|
||||
|
||||
// Binary data
|
||||
async function downloadImage(url: string) {
|
||||
const response = await SmartRequestClient.create()
|
||||
const response = await SmartRequest.create()
|
||||
.url(url)
|
||||
.accept('binary') // Optional: hints to server we want binary
|
||||
.get();
|
||||
@@ -130,35 +161,60 @@ async function downloadImage(url: string) {
|
||||
return Buffer.from(buffer); // Convert ArrayBuffer to Buffer if needed
|
||||
}
|
||||
|
||||
// Streaming response
|
||||
// Streaming response (Web Streams API)
|
||||
async function streamLargeFile(url: string) {
|
||||
const response = await SmartRequestClient.create()
|
||||
const response = await SmartRequest.create()
|
||||
.url(url)
|
||||
.get();
|
||||
|
||||
// Get the underlying Node.js stream
|
||||
// Get a web-style ReadableStream (works in both Node.js and browsers)
|
||||
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`);
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
stream.on('end', resolve);
|
||||
stream.on('error', reject);
|
||||
nodeStream.on('end', resolve);
|
||||
nodeStream.on('error', reject);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Response Object Methods
|
||||
|
||||
The `SmartResponse` object provides these methods:
|
||||
The response object provides these methods:
|
||||
|
||||
- `json<T>(): Promise<T>` - Parse response as JSON
|
||||
- `text(): Promise<string>` - Get response as text
|
||||
- `arrayBuffer(): Promise<ArrayBuffer>` - Get response as ArrayBuffer
|
||||
- `stream(): NodeJS.ReadableStream` - Get the underlying Node.js stream
|
||||
- `raw(): http.IncomingMessage` - Get the raw http.IncomingMessage
|
||||
- `stream(): ReadableStream<Uint8Array> | null` - Get web-style ReadableStream (cross-platform)
|
||||
- `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.
|
||||
|
||||
@@ -167,7 +223,7 @@ Each body method can only be called once per response, similar to the fetch API.
|
||||
### Form Data with File Uploads
|
||||
|
||||
```typescript
|
||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
||||
import { SmartRequest } from '@push.rocks/smartrequest';
|
||||
import * as fs from 'fs';
|
||||
|
||||
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'
|
||||
}));
|
||||
|
||||
const response = await SmartRequestClient.create()
|
||||
const response = await SmartRequest.create()
|
||||
.url('https://api.example.com/upload')
|
||||
.formData(formFields)
|
||||
.post();
|
||||
@@ -187,14 +243,14 @@ async function uploadMultipleFiles(files: Array<{name: string, path: string}>) {
|
||||
}
|
||||
```
|
||||
|
||||
### Unix Socket Support
|
||||
### Unix Socket Support (Node.js only)
|
||||
|
||||
```typescript
|
||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
||||
import { SmartRequest } from '@push.rocks/smartrequest';
|
||||
|
||||
// Connect to a service via Unix socket
|
||||
async function queryViaUnixSocket() {
|
||||
const response = await SmartRequestClient.create()
|
||||
const response = await SmartRequest.create()
|
||||
.url('http://unix:/var/run/docker.sock:/v1.24/containers/json')
|
||||
.get();
|
||||
|
||||
@@ -207,11 +263,11 @@ async function queryViaUnixSocket() {
|
||||
The library includes built-in support for various pagination strategies:
|
||||
|
||||
```typescript
|
||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
||||
import { SmartRequest } from '@push.rocks/smartrequest';
|
||||
|
||||
// Offset-based pagination (page & limit)
|
||||
async function fetchAllUsers() {
|
||||
const client = SmartRequestClient.create()
|
||||
const client = SmartRequest.create()
|
||||
.url('https://api.example.com/users')
|
||||
.withOffsetPagination({
|
||||
pageParam: 'page',
|
||||
@@ -239,7 +295,7 @@ async function fetchAllUsers() {
|
||||
|
||||
// Cursor-based pagination
|
||||
async function fetchAllPosts() {
|
||||
const allPosts = await SmartRequestClient.create()
|
||||
const allPosts = await SmartRequest.create()
|
||||
.url('https://api.example.com/posts')
|
||||
.withCursorPagination({
|
||||
cursorParam: 'cursor',
|
||||
@@ -253,7 +309,7 @@ async function fetchAllPosts() {
|
||||
|
||||
// Link header-based pagination (GitHub API style)
|
||||
async function fetchAllIssues(repo: string) {
|
||||
const paginatedResponse = await SmartRequestClient.create()
|
||||
const paginatedResponse = await SmartRequest.create()
|
||||
.url(`https://api.github.com/repos/${repo}/issues`)
|
||||
.header('Accept', 'application/vnd.github.v3+json')
|
||||
.withLinkPagination()
|
||||
@@ -263,17 +319,17 @@ async function fetchAllIssues(repo: string) {
|
||||
}
|
||||
```
|
||||
|
||||
### Keep-Alive Connections
|
||||
### Keep-Alive Connections (Node.js)
|
||||
|
||||
```typescript
|
||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
||||
import { SmartRequest } from '@push.rocks/smartrequest';
|
||||
|
||||
// Enable keep-alive for better performance with multiple requests
|
||||
async function performMultipleRequests() {
|
||||
const client = SmartRequestClient.create()
|
||||
const client = SmartRequest.create()
|
||||
.header('Connection', 'keep-alive');
|
||||
|
||||
// Requests will reuse the same connection
|
||||
// Requests will reuse the same connection in Node.js
|
||||
const results = await Promise.all([
|
||||
client.url('https://api.example.com/endpoint1').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
|
||||
|
||||
Here's a complete example of building a typed API client:
|
||||
|
||||
```typescript
|
||||
import { SmartRequestClient, type SmartResponse } from '@push.rocks/smartrequest';
|
||||
import { SmartRequest, type CoreResponse } from '@push.rocks/smartrequest';
|
||||
|
||||
interface User {
|
||||
id: number;
|
||||
@@ -308,7 +398,7 @@ class BlogApiClient {
|
||||
private baseUrl = 'https://jsonplaceholder.typicode.com';
|
||||
|
||||
private async request(path: string) {
|
||||
return SmartRequestClient.create()
|
||||
return SmartRequest.create()
|
||||
.url(`${this.baseUrl}${path}`)
|
||||
.header('Accept', 'application/json');
|
||||
}
|
||||
@@ -354,11 +444,11 @@ const posts = await api.getAllPosts(user.id);
|
||||
## Error Handling
|
||||
|
||||
```typescript
|
||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
||||
import { SmartRequest } from '@push.rocks/smartrequest';
|
||||
|
||||
async function fetchWithErrorHandling(url: string) {
|
||||
try {
|
||||
const response = await SmartRequestClient.create()
|
||||
const response = await SmartRequest.create()
|
||||
.url(url)
|
||||
.timeout(5000)
|
||||
.retry(2)
|
||||
@@ -384,6 +474,8 @@ async function fetchWithErrorHandling(url: string) {
|
||||
console.error('Connection refused - is the server running?');
|
||||
} else if (error.code === 'ETIMEDOUT') {
|
||||
console.error('Request timed out');
|
||||
} else if (error.name === 'AbortError') {
|
||||
console.error('Request was aborted');
|
||||
} else {
|
||||
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
|
||||
import { getJson, postJson, request } from '@push.rocks/smartrequest/legacy';
|
||||
|
||||
// Simple GET request
|
||||
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()` |
|
||||
1. **Legacy API Removed**: The function-based API (getJson, postJson, etc.) has been removed. Use SmartRequest instead.
|
||||
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.
|
||||
4. **Cross-Platform by Default**: The library now works in browsers out of the box with automatic platform detection.
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
|
Reference in New Issue
Block a user