BREAKING CHANGE(core): major architectural refactoring with fetch-like API
Some checks failed
Default (tags) / security (push) Failing after 24s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped

This commit is contained in:
2025-07-27 21:23:20 +00:00
parent f7d2c6de4f
commit bbb57004d9
24 changed files with 1038 additions and 593 deletions

288
readme.md
View File

@@ -1,5 +1,5 @@
# @push.rocks/smartrequest
A module providing a drop-in replacement for the deprecated Request library, focusing on modern HTTP/HTTPS requests with support for form data, file uploads, JSON, binary data, and streams. The library offers both a legacy API and a modern fluent API for maximum flexibility.
A modern HTTP/HTTPS request library for Node.js with support for form data, file uploads, JSON, binary data, streams, and unix sockets. Features both a legacy API for backward compatibility and a modern fetch-like API for new projects.
## Install
To install `@push.rocks/smartrequest`, use one of the following commands:
@@ -17,22 +17,46 @@ yarn add @push.rocks/smartrequest
This will add `@push.rocks/smartrequest` to your project's dependencies.
## Key Features
- 🚀 **Modern Fetch-like API** - Familiar response methods (`.json()`, `.text()`, `.arrayBuffer()`, `.stream()`)
- 🔄 **Two API Styles** - Legacy function-based API and modern fluent chainable API
- 🌐 **Unix Socket Support** - Connect to local services like Docker
- 📦 **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
- 🛡️ **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
- 🔧 **Highly Configurable** - Timeouts, retries, headers, and more
## Usage
`@push.rocks/smartrequest` is designed as a versatile, modern HTTP client library for making HTTP/HTTPS requests. It supports a range of features, including handling form data, file uploads, JSON requests, binary data, streaming, pagination, and much more, all within a modern, promise-based API.
`@push.rocks/smartrequest` is designed as a versatile, modern HTTP client library for making HTTP/HTTPS requests in Node.js environments. It provides a clean, type-safe API inspired by the native fetch API but with additional features needed for server-side applications.
The library provides two distinct APIs:
1. **Legacy API** - Simple function-based API for quick and straightforward HTTP requests
2. **Modern Fluent API** - A chainable, builder-style API for more complex scenarios and better TypeScript integration
1. **Legacy API** - Simple function-based API for quick requests and backward compatibility
2. **Modern Fluent API** - A chainable, fetch-like API for more complex scenarios
Below we will cover key usage scenarios of `@push.rocks/smartrequest`, showcasing its capabilities and providing you with a solid starting point to integrate it into your projects.
### Import Guide
```typescript
// Modern API (recommended for new projects)
import { SmartRequestClient } from '@push.rocks/smartrequest';
// Legacy API (for backward compatibility)
import { getJson, postJson, request } from '@push.rocks/smartrequest/legacy';
```
### Simple GET Request
For fetching data from a REST API or any web service that returns JSON:
```typescript
import { getJson } from '@push.rocks/smartrequest';
import { getJson } from '@push.rocks/smartrequest/legacy';
async function fetchGitHubUserInfo(username: string) {
const response = await getJson(`https://api.github.com/users/${username}`);
@@ -49,7 +73,7 @@ The `getJson` function simplifies the process of sending a GET request and parsi
When you need to send JSON data to a server, for example, creating a new resource:
```typescript
import { postJson } from '@push.rocks/smartrequest';
import { postJson } from '@push.rocks/smartrequest/legacy';
async function createTodoItem(todoDetails: { title: string; completed: boolean }) {
const response = await postJson('https://jsonplaceholder.typicode.com/todos', {
@@ -68,7 +92,7 @@ createTodoItem({ title: 'Implement smartrequest', completed: false });
`@push.rocks/smartrequest` simplifies the process of uploading files and submitting form data to a server:
```typescript
import { postFormData, IFormField } from '@push.rocks/smartrequest';
import { postFormData, IFormField } from '@push.rocks/smartrequest/legacy';
async function uploadProfilePicture(formDataFields: IFormField[]) {
await postFormData('https://api.example.com/upload', {}, formDataFields);
@@ -85,7 +109,7 @@ uploadProfilePicture([
For cases when dealing with large datasets or streaming APIs, `@push.rocks/smartrequest` provides streaming capabilities:
```typescript
import { getStream } from '@push.rocks/smartrequest';
import { getStream } from '@push.rocks/smartrequest/legacy';
async function streamLargeFile(url: string) {
const stream = await getStream(url);
@@ -109,7 +133,7 @@ streamLargeFile('https://example.com/largefile');
`@push.rocks/smartrequest` is built to be flexible, allowing you to specify additional options to tailor requests to your needs:
```typescript
import { request, ISmartRequestOptions } from '@push.rocks/smartrequest';
import { request, ISmartRequestOptions } from '@push.rocks/smartrequest/legacy';
async function customRequestExample() {
const options: ISmartRequestOptions = {
@@ -131,7 +155,7 @@ customRequestExample();
## Modern Fluent API
In addition to the legacy API shown above, `@push.rocks/smartrequest` provides a modern, fluent API that offers a more chainable and TypeScript-friendly approach to making HTTP requests.
In addition to the legacy API shown above, `@push.rocks/smartrequest` provides a modern, fluent API with a fetch-like response interface that offers a more chainable and TypeScript-friendly approach to making HTTP requests.
### Basic Usage with the Modern API
@@ -144,7 +168,9 @@ async function fetchUserData(userId: number) {
.url(`https://jsonplaceholder.typicode.com/users/${userId}`)
.get();
console.log(response.body); // The JSON response
// Use the fetch-like response API
const userData = await response.json();
console.log(userData); // The parsed JSON response
}
// POST request with JSON body
@@ -154,7 +180,8 @@ async function createPost(title: string, body: string, userId: number) {
.json({ title, body, userId })
.post();
console.log(response.body); // The created post
const createdPost = await response.json();
console.log(createdPost); // The created post
}
```
@@ -173,7 +200,8 @@ async function searchRepositories(query: string, perPage: number = 10) {
})
.get();
return response.body.items;
const data = await response.json();
return data.items;
}
```
@@ -189,41 +217,62 @@ async function fetchWithRetry(url: string) {
.retry(3) // Retry up to 3 times on failure
.get();
return response.body;
return await response.json();
}
```
### Working with Different Response Types
The modern API provides a fetch-like interface for handling different response types:
```typescript
import { SmartRequestClient } from '@push.rocks/smartrequest';
// JSON response (default)
async function fetchJson(url: string) {
const response = await SmartRequestClient.create()
.url(url)
.get();
return await response.json(); // Parses JSON automatically
}
// Text response
async function fetchText(url: string) {
const response = await SmartRequestClient.create()
.url(url)
.get();
return await response.text(); // Returns response as string
}
// Binary data
async function downloadImage(url: string) {
const response = await SmartRequestClient.create()
.url(url)
.responseType('binary')
.accept('binary') // Optional: hints to server we want binary
.get();
// response.body is a Buffer
return response.body;
const buffer = await response.arrayBuffer();
return Buffer.from(buffer); // Convert ArrayBuffer to Buffer if needed
}
// Streaming response
async function streamLargeFile(url: string) {
const response = await SmartRequestClient.create()
.url(url)
.responseType('stream')
.get();
// response is a stream
response.on('data', (chunk) => {
// Get the underlying Node.js stream
const stream = response.stream();
stream.on('data', (chunk) => {
console.log(`Received ${chunk.length} bytes of data`);
});
return new Promise((resolve, reject) => {
response.on('end', resolve);
response.on('error', reject);
stream.on('end', resolve);
stream.on('error', reject);
});
}
```
@@ -289,29 +338,78 @@ async function fetchAllIssues(repo: string) {
}
```
### Convenience Factory Functions
### Advanced Features
The library provides several factory functions for common use cases:
#### Unix Socket Support
```typescript
import { createJsonClient, createBinaryClient, createStreamClient } from '@push.rocks/smartrequest';
import { SmartRequestClient } from '@push.rocks/smartrequest';
// Pre-configured for JSON requests
const jsonClient = createJsonClient()
.url('https://api.example.com/data')
.get();
// Pre-configured for binary data
const binaryClient = createBinaryClient()
.url('https://example.com/image.jpg')
.get();
// Pre-configured for streaming
const streamClient = createStreamClient()
.url('https://example.com/large-file')
.get();
// Connect to a service via Unix socket
async function queryViaUnixSocket() {
const response = await SmartRequestClient.create()
.url('http://unix:/var/run/docker.sock:/v1.24/containers/json')
.get();
return await response.json();
}
```
#### Form Data with File Uploads
```typescript
import { SmartRequestClient } from '@push.rocks/smartrequest';
async function uploadMultipleFiles(files: Array<{name: string, path: string}>) {
const formFields = files.map(file => ({
name: 'files',
value: fs.readFileSync(file.path),
filename: file.name,
contentType: 'application/octet-stream'
}));
const response = await SmartRequestClient.create()
.url('https://api.example.com/upload')
.formData(formFields)
.post();
return await response.json();
}
```
#### Keep-Alive Connections
```typescript
import { SmartRequestClient } from '@push.rocks/smartrequest';
// Enable keep-alive for better performance with multiple requests
async function performMultipleRequests() {
const client = SmartRequestClient.create()
.header('Connection', 'keep-alive');
// Requests will reuse the same connection
const results = await Promise.all([
client.url('https://api.example.com/endpoint1').get(),
client.url('https://api.example.com/endpoint2').get(),
client.url('https://api.example.com/endpoint3').get()
]);
return Promise.all(results.map(r => r.json()));
}
```
### Response Object Methods
The modern API returns a `SmartResponse` object with the following 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
Each body method can only be called once per response, similar to the fetch API.
Through its comprehensive set of features tailored for modern web development, `@push.rocks/smartrequest` aims to provide developers with a powerful tool for handling HTTP/HTTPS requests efficiently. Whether it's a simple API call, handling form data, processing streams, or working with paginated APIs, `@push.rocks/smartrequest` delivers a robust, type-safe solution to fit your project's requirements.
## Migration Guide: Legacy API to Modern API
@@ -325,11 +423,121 @@ If you're currently using the legacy API and want to migrate to the modern fluen
| `putJson(url, { requestBody: data })` | `SmartRequestClient.create().url(url).json(data).put()` |
| `delJson(url)` | `SmartRequestClient.create().url(url).delete()` |
| `postFormData(url, {}, fields)` | `SmartRequestClient.create().url(url).formData(fields).post()` |
| `getStream(url)` | `SmartRequestClient.create().url(url).responseType('stream').get()` |
| `getStream(url)` | `SmartRequestClient.create().url(url).accept('stream').get()` |
| `request(url, options)` | `SmartRequestClient.create().url(url).[...configure options...].get()` |
The modern API provides more flexibility and better TypeScript integration, making it the recommended approach for new projects.
## Complete Examples
### Building a REST API Client
Here's a complete example of building a typed API client using smartrequest:
```typescript
import { SmartRequestClient, type SmartResponse } from '@push.rocks/smartrequest';
interface User {
id: number;
name: string;
email: string;
}
interface Post {
id: number;
title: string;
body: string;
userId: number;
}
class BlogApiClient {
private baseUrl = 'https://jsonplaceholder.typicode.com';
private async request(path: string) {
return SmartRequestClient.create()
.url(`${this.baseUrl}${path}`)
.header('Accept', 'application/json');
}
async getUser(id: number): Promise<User> {
const response = await this.request(`/users/${id}`).get();
return response.json<User>();
}
async createPost(post: Omit<Post, 'id'>): Promise<Post> {
const response = await this.request('/posts')
.json(post)
.post();
return response.json<Post>();
}
async deletePost(id: number): Promise<void> {
const response = await this.request(`/posts/${id}`).delete();
if (!response.ok) {
throw new Error(`Failed to delete post: ${response.statusText}`);
}
}
async getAllPosts(userId?: number): Promise<Post[]> {
const client = this.request('/posts');
if (userId) {
client.query({ userId: userId.toString() });
}
const response = await client.get();
return response.json<Post[]>();
}
}
// Usage
const api = new BlogApiClient();
const user = await api.getUser(1);
const posts = await api.getAllPosts(user.id);
```
### Error Handling
```typescript
import { SmartRequestClient } from '@push.rocks/smartrequest';
async function fetchWithErrorHandling(url: string) {
try {
const response = await SmartRequestClient.create()
.url(url)
.timeout(5000)
.retry(2)
.get();
// Check if request was successful
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// Handle different content types
const contentType = response.headers['content-type'];
if (contentType?.includes('application/json')) {
return await response.json();
} else if (contentType?.includes('text/')) {
return await response.text();
} else {
return await response.arrayBuffer();
}
} catch (error) {
if (error.code === 'ECONNREFUSED') {
console.error('Connection refused - is the server running?');
} else if (error.code === 'ETIMEDOUT') {
console.error('Request timed out');
} else {
console.error('Request failed:', error.message);
}
throw error;
}
}
```
## License and Legal Information
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.