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

208
readme.md
View File

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