Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 735440fd11 | |||
| 47bd335f4f | |||
| 8622ee78d1 | |||
| c08e8b9132 | |||
| 980675ea05 | |||
| 15819d8a23 | |||
| bc71d2e5a8 | |||
| 0cf48b3688 |
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"@git.zone/npmts": {
|
||||
"coverageTreshold": 50
|
||||
},
|
||||
"@ship.zone/szci": {
|
||||
"npmGlobalTools": [],
|
||||
"npmRegistryUrl": "registry.npmjs.org"
|
||||
},
|
||||
"@git.zone/cli": {
|
||||
"projectType": "npm",
|
||||
"module": {
|
||||
"githost": "code.foss.global",
|
||||
"gitscope": "push.rocks",
|
||||
"gitrepo": "smartrequest",
|
||||
"shortDescription": "modern HTTP request utilities",
|
||||
"description": "A module for modern HTTP/HTTPS requests with support for form data, file uploads, JSON, binary data, streams, and more.",
|
||||
"npmPackagename": "@push.rocks/smartrequest",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"HTTP",
|
||||
"HTTPS",
|
||||
"request library",
|
||||
"form data",
|
||||
"file uploads",
|
||||
"JSON",
|
||||
"binary data",
|
||||
"streams",
|
||||
"keepAlive",
|
||||
"TypeScript",
|
||||
"modern web requests",
|
||||
"drop-in replacement",
|
||||
"Bun",
|
||||
"Deno",
|
||||
"Node.js",
|
||||
"unix sockets"
|
||||
]
|
||||
},
|
||||
"release": {
|
||||
"registries": [
|
||||
"https://verdaccio.lossless.digital",
|
||||
"https://registry.npmjs.org"
|
||||
],
|
||||
"accessLevel": "public"
|
||||
}
|
||||
},
|
||||
"@git.zone/tsdoc": {
|
||||
"legal": "\n## License and Legal Information\n\nThis 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. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,35 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-05-01 - 5.0.3 - fix(core_node)
|
||||
support agentkeepalive default export fallback and bump dependency to ^4.6.0
|
||||
|
||||
- Adds a fallback to use the default agentkeepalive export when HttpAgent is not exposed directly.
|
||||
- Updates agentkeepalive from ^4.5.0 to ^4.6.0 to align with the compatibility fix.
|
||||
|
||||
## 2026-05-01 - 5.0.2 - fix(core-node)
|
||||
update agentkeepalive import usage and align package metadata with smartconfig
|
||||
|
||||
- Switches the Node plugin integration to use the default agentkeepalive export and map HttpAgent/HttpsAgent from it.
|
||||
- Adds .smartconfig.json and includes it together with the license file in the published package.
|
||||
- Updates npmextra.json to the new scoped configuration keys and expands project release metadata and keywords.
|
||||
- Adjusts package metadata to include the author email address.
|
||||
|
||||
## 2025-11-17 - 5.0.1 - fix(test)
|
||||
Enable --logfile in test script and bump @git.zone/tstest to ^2.8.2
|
||||
|
||||
- Update npm script: add --logfile flag to the test command to produce test logs
|
||||
- Bump devDependency @git.zone/tstest from ^2.8.1 to ^2.8.2
|
||||
|
||||
## 2025-11-17 - 5.0.0 - BREAKING CHANGE(client/streaming)
|
||||
Unify streaming APIs: remove raw()/streamNode() and standardize on web ReadableStream across runtimes
|
||||
|
||||
- Removed SmartRequest.raw() and RawStreamFunction type. The raw streaming function API is gone — use .stream() with a web ReadableStream for request body streaming.
|
||||
- Removed response.streamNode() from all runtimes. Responses now expose only response.stream() (ReadableStream<Uint8Array>). Node.js consumers must convert using Readable.fromWeb() if a Node.js stream is required.
|
||||
- Node implementation now uses Readable.toWeb() to convert native Node streams into web ReadableStream for a single cross-platform streaming API.
|
||||
- Client request.stream() still accepts Node.js streams but they are converted internally to web streams; temporary internal properties for raw streaming were removed.
|
||||
- Updated tests and documentation (readme) with migration guidance and examples for converting between web and Node.js streams.
|
||||
- Bumped devDependencies (@git.zone/tsbuild, tsrun, tstest) and upgraded form-data to a newer patch release.
|
||||
|
||||
## 2025-11-16 - 4.4.2 - fix(core_base/request)
|
||||
Strip 'unix:' prefix when parsing unix socket URLs so socketPath is a clean filesystem path
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2026 Task Venture Capital GmbH <hello@task.vc>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+18
-6
@@ -1,17 +1,18 @@
|
||||
{
|
||||
"npmts": {
|
||||
"@git.zone/npmts": {
|
||||
"coverageTreshold": 50
|
||||
},
|
||||
"npmci": {
|
||||
"@ship.zone/szci": {
|
||||
"npmGlobalTools": [],
|
||||
"npmAccessLevel": "public"
|
||||
"npmRegistryUrl": "registry.npmjs.org"
|
||||
},
|
||||
"gitzone": {
|
||||
"@git.zone/cli": {
|
||||
"projectType": "npm",
|
||||
"module": {
|
||||
"githost": "code.foss.global",
|
||||
"gitscope": "push.rocks",
|
||||
"gitrepo": "smartrequest",
|
||||
"shortDescription": "modern HTTP request utilities",
|
||||
"description": "A module for modern HTTP/HTTPS requests with support for form data, file uploads, JSON, binary data, streams, and more.",
|
||||
"npmPackagename": "@push.rocks/smartrequest",
|
||||
"license": "MIT",
|
||||
@@ -27,11 +28,22 @@
|
||||
"keepAlive",
|
||||
"TypeScript",
|
||||
"modern web requests",
|
||||
"drop-in replacement"
|
||||
"drop-in replacement",
|
||||
"Bun",
|
||||
"Deno",
|
||||
"Node.js",
|
||||
"unix sockets"
|
||||
]
|
||||
},
|
||||
"release": {
|
||||
"registries": [
|
||||
"https://verdaccio.lossless.digital",
|
||||
"https://registry.npmjs.org"
|
||||
],
|
||||
"accessLevel": "public"
|
||||
}
|
||||
},
|
||||
"tsdoc": {
|
||||
"@git.zone/tsdoc": {
|
||||
"legal": "\n## License and Legal Information\n\nThis 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. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
||||
}
|
||||
}
|
||||
|
||||
+10
-8
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@push.rocks/smartrequest",
|
||||
"version": "4.4.2",
|
||||
"version": "5.0.3",
|
||||
"private": false,
|
||||
"description": "A module for modern HTTP/HTTPS requests with support for form data, file uploads, JSON, binary data, streams, and more.",
|
||||
"exports": {
|
||||
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "(tstest test/ --verbose --timeout 120)",
|
||||
"test": "(tstest test/ --verbose --timeout 120 --logfile)",
|
||||
"build": "(tsbuild --web)",
|
||||
"buildDocs": "tsdoc"
|
||||
},
|
||||
@@ -38,7 +38,7 @@
|
||||
"Node.js",
|
||||
"unix sockets"
|
||||
],
|
||||
"author": "Task Venture Capital GmbH",
|
||||
"author": "Task Venture Capital GmbH <hello@task.vc>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://code.foss.global/push.rocks/smartrequest/issues"
|
||||
@@ -49,13 +49,13 @@
|
||||
"@push.rocks/smartpath": "^6.0.0",
|
||||
"@push.rocks/smartpromise": "^4.0.4",
|
||||
"@push.rocks/smarturl": "^3.1.0",
|
||||
"agentkeepalive": "^4.5.0",
|
||||
"form-data": "^4.0.4"
|
||||
"agentkeepalive": "^4.6.0",
|
||||
"form-data": "^4.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^2.7.1",
|
||||
"@git.zone/tsrun": "^1.6.2",
|
||||
"@git.zone/tstest": "^2.7.0",
|
||||
"@git.zone/tsbuild": "^3.1.0",
|
||||
"@git.zone/tsrun": "^2.0.0",
|
||||
"@git.zone/tstest": "^2.8.2",
|
||||
"@types/node": "^22.9.0"
|
||||
},
|
||||
"files": [
|
||||
@@ -67,6 +67,8 @@
|
||||
"dist_ts_web/**/*",
|
||||
"assets/**/*",
|
||||
"cli.js",
|
||||
".smartconfig.json",
|
||||
"license",
|
||||
"npmextra.json",
|
||||
"readme.md"
|
||||
],
|
||||
|
||||
Generated
+871
-1212
File diff suppressed because it is too large
Load Diff
@@ -182,11 +182,11 @@ async function downloadImage(url: string) {
|
||||
return Buffer.from(buffer); // Convert ArrayBuffer to Buffer if needed
|
||||
}
|
||||
|
||||
// Streaming response (Web Streams API)
|
||||
// Streaming response (Web Streams API - cross-platform)
|
||||
async function streamLargeFile(url: string) {
|
||||
const response = await SmartRequest.create().url(url).get();
|
||||
|
||||
// Get a web-style ReadableStream (works in both Node.js and browsers)
|
||||
// Get a web-style ReadableStream (works everywhere)
|
||||
const stream = response.stream();
|
||||
|
||||
if (stream) {
|
||||
@@ -204,12 +204,14 @@ async function streamLargeFile(url: string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Node.js specific stream (only in Node.js environment)
|
||||
// Convert to Node.js stream if needed (Node.js only)
|
||||
async function streamWithNodeApi(url: string) {
|
||||
const response = await SmartRequest.create().url(url).get();
|
||||
|
||||
// Only available in Node.js, throws error in browser/Bun/Deno
|
||||
const nodeStream = response.streamNode();
|
||||
// Convert web stream to Node.js stream
|
||||
import { Readable } from 'stream';
|
||||
const webStream = response.stream();
|
||||
const nodeStream = Readable.fromWeb(webStream);
|
||||
|
||||
nodeStream.on('data', (chunk) => {
|
||||
console.log(`Received ${chunk.length} bytes of data`);
|
||||
@@ -230,8 +232,7 @@ The response object provides these methods:
|
||||
- `text(): Promise<string>` - Get response as text
|
||||
- `arrayBuffer(): Promise<ArrayBuffer>` - Get response as ArrayBuffer
|
||||
- `stream(): ReadableStream<Uint8Array> | null` - Get web-style ReadableStream (cross-platform)
|
||||
- `streamNode(): NodeJS.ReadableStream` - Get Node.js stream (Node.js only, throws in browser/Bun/Deno)
|
||||
- `raw(): Response | http.IncomingMessage` - Get the underlying platform response
|
||||
- `raw(): Response | http.IncomingMessage` - Get the underlying platform response object
|
||||
|
||||
Each body method can only be called once per response, similar to the fetch API.
|
||||
|
||||
@@ -368,24 +369,7 @@ async function streamData(dataSource: Readable) {
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
// Advanced: Full control over request streaming (Node.js only)
|
||||
async function customStreaming() {
|
||||
const response = await SmartRequest.create()
|
||||
.url('https://api.example.com/stream')
|
||||
.raw((request) => {
|
||||
// Custom streaming logic - you have full control
|
||||
request.write('chunk1');
|
||||
request.write('chunk2');
|
||||
|
||||
// Stream from another source
|
||||
someReadableStream.pipe(request);
|
||||
})
|
||||
.post();
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
// Send Uint8Array (works in both Node.js and browser)
|
||||
// Send Uint8Array (works everywhere)
|
||||
async function uploadBinaryData() {
|
||||
const data = new Uint8Array([72, 101, 108, 108, 111]); // "Hello"
|
||||
|
||||
@@ -411,11 +395,6 @@ async function uploadBinaryData() {
|
||||
- ✅ Web ReadableStream works everywhere (Node.js, Bun, Deno, browsers)
|
||||
- ⚠️ Node.js streams only work in Node.js (automatically converted to web streams in Bun/Deno)
|
||||
|
||||
- **`.raw(streamFunc)`** - Advanced control over request streaming
|
||||
- `streamFunc`: Function that receives the raw request object for custom streaming
|
||||
- ❌ **Node.js only** - not supported in browsers, Bun, or Deno
|
||||
- Use for advanced scenarios like chunked transfer encoding
|
||||
|
||||
These methods are particularly useful for:
|
||||
- Uploading large files without loading them into memory
|
||||
- Streaming real-time data to servers
|
||||
@@ -687,13 +666,12 @@ const response = await SmartRequest.create()
|
||||
})
|
||||
.get();
|
||||
|
||||
// Bun uses web streams - streamNode() throws an error
|
||||
// Bun uses web streams natively
|
||||
const streamResponse = await SmartRequest.create()
|
||||
.url('https://api.example.com/data')
|
||||
.get();
|
||||
|
||||
const webStream = streamResponse.stream(); // ✅ Use web streams in Bun
|
||||
// streamNode() is not available - throws error directing you to use stream()
|
||||
```
|
||||
|
||||
### Deno-Specific Options
|
||||
@@ -716,13 +694,12 @@ const response = await SmartRequest.create()
|
||||
// Remember to clean up clients when done
|
||||
client.close();
|
||||
|
||||
// Deno uses web streams - streamNode() throws an error
|
||||
// Deno uses web streams natively
|
||||
const streamResponse = await SmartRequest.create()
|
||||
.url('https://api.example.com/data')
|
||||
.get();
|
||||
|
||||
const webStream = streamResponse.stream(); // ✅ Use web streams in Deno
|
||||
// streamNode() is not available - throws error directing you to use stream()
|
||||
```
|
||||
|
||||
## Complete Example: Building a REST API Client
|
||||
@@ -838,6 +815,144 @@ async function fetchWithErrorHandling(url: string) {
|
||||
|
||||
## Migrating from Earlier Versions
|
||||
|
||||
### From v4.x to v5.x
|
||||
|
||||
Version 5.0 completes the transition to modern web standards by removing Node.js-specific streaming APIs:
|
||||
|
||||
#### **Breaking Changes**
|
||||
|
||||
1. **`.streamNode()` Method Removed**
|
||||
- The `.streamNode()` method has been removed from all response objects
|
||||
- Use the cross-platform `.stream()` method instead, which returns a web `ReadableStream<Uint8Array>`
|
||||
- For Node.js users who need Node.js streams, convert using `Readable.fromWeb()`
|
||||
|
||||
```typescript
|
||||
// ❌ Before (v4.x) - Node.js only
|
||||
const response = await SmartRequest.create().url(url).get();
|
||||
const nodeStream = response.streamNode();
|
||||
|
||||
// ✅ After (v5.x) - Cross-platform
|
||||
import { Readable } from 'stream';
|
||||
|
||||
const response = await SmartRequest.create().url(url).get();
|
||||
const webStream = response.stream();
|
||||
const nodeStream = Readable.fromWeb(webStream); // Convert to Node.js stream
|
||||
```
|
||||
|
||||
2. **Request `.raw()` Method Removed**
|
||||
- The `.raw(streamFunc)` method has been removed from the SmartRequest client
|
||||
- Use `.stream()` with a web `ReadableStream` instead for request body streaming
|
||||
- Node.js users can create web streams from Node.js streams using `Readable.toWeb()`
|
||||
|
||||
```typescript
|
||||
// ❌ Before (v4.x) - Node.js only
|
||||
const response = await SmartRequest.create()
|
||||
.url(url)
|
||||
.raw((request) => {
|
||||
request.write('chunk1');
|
||||
request.write('chunk2');
|
||||
request.end();
|
||||
})
|
||||
.post();
|
||||
|
||||
// ✅ After (v5.x) - Cross-platform
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(new TextEncoder().encode('chunk1'));
|
||||
controller.enqueue(new TextEncoder().encode('chunk2'));
|
||||
controller.close();
|
||||
}
|
||||
});
|
||||
|
||||
const response = await SmartRequest.create()
|
||||
.url(url)
|
||||
.stream(stream)
|
||||
.post();
|
||||
|
||||
// Or convert from Node.js stream (Node.js only)
|
||||
import { Readable } from 'stream';
|
||||
import * as fs from 'fs';
|
||||
|
||||
const nodeStream = fs.createReadStream('file.txt');
|
||||
const webStream = Readable.toWeb(nodeStream);
|
||||
|
||||
const response = await SmartRequest.create()
|
||||
.url(url)
|
||||
.stream(webStream)
|
||||
.post();
|
||||
```
|
||||
|
||||
3. **Response `.raw()` Method Preserved**
|
||||
- The `response.raw()` method is still available for accessing platform-specific response objects
|
||||
- Returns `http.IncomingMessage` in Node.js or `Response` in other runtimes
|
||||
- Use for advanced scenarios requiring access to raw platform objects
|
||||
|
||||
```typescript
|
||||
// ✅ Still works in v5.x
|
||||
const response = await SmartRequest.create().url(url).get();
|
||||
const rawResponse = response.raw(); // http.IncomingMessage or Response
|
||||
```
|
||||
|
||||
#### **Migration Guide**
|
||||
|
||||
**For Response Streaming:**
|
||||
|
||||
```typescript
|
||||
// Before (v4.x)
|
||||
const response = await SmartRequest.create().url(url).get();
|
||||
const nodeStream = response.streamNode();
|
||||
|
||||
nodeStream.on('data', (chunk) => {
|
||||
console.log(`Received ${chunk.length} bytes`);
|
||||
});
|
||||
|
||||
// After (v5.x) - Option 1: Use web streams directly
|
||||
const response = await SmartRequest.create().url(url).get();
|
||||
const webStream = response.stream();
|
||||
|
||||
if (webStream) {
|
||||
const reader = webStream.getReader();
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
console.log(`Received ${value.length} bytes`);
|
||||
}
|
||||
reader.releaseLock();
|
||||
}
|
||||
|
||||
// After (v5.x) - Option 2: Convert to Node.js stream (Node.js only)
|
||||
import { Readable } from 'stream';
|
||||
|
||||
const response = await SmartRequest.create().url(url).get();
|
||||
const webStream = response.stream();
|
||||
const nodeStream = Readable.fromWeb(webStream);
|
||||
|
||||
nodeStream.on('data', (chunk) => {
|
||||
console.log(`Received ${chunk.length} bytes`);
|
||||
});
|
||||
```
|
||||
|
||||
**For Request Streaming:**
|
||||
|
||||
Node.js streams are still accepted by the `.stream()` method and automatically converted internally. No changes required for most use cases:
|
||||
|
||||
```typescript
|
||||
// ✅ Still works in v5.x
|
||||
import * as fs from 'fs';
|
||||
|
||||
const fileStream = fs.createReadStream('large-file.bin');
|
||||
const response = await SmartRequest.create()
|
||||
.url('https://api.example.com/upload')
|
||||
.stream(fileStream, 'application/octet-stream')
|
||||
.post();
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- ✅ True cross-platform compatibility
|
||||
- ✅ Modern web standards
|
||||
- ✅ Cleaner API surface
|
||||
- ✅ Single streaming approach works everywhere
|
||||
|
||||
### From v3.x to v4.x
|
||||
|
||||
Version 4.0 adds comprehensive cross-platform support:
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { SmartRequest } from '../ts/index.js';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
tap.test('should have streamNode() method available', async () => {
|
||||
tap.test('should have stream() method that returns web ReadableStream', async () => {
|
||||
const response = await SmartRequest.create()
|
||||
.url('https://httpbin.org/get')
|
||||
.get();
|
||||
|
||||
// Verify streamNode() method exists
|
||||
expect(response.streamNode).toBeDefined();
|
||||
expect(typeof response.streamNode).toEqual('function');
|
||||
// Verify stream() method exists
|
||||
expect(response.stream).toBeDefined();
|
||||
expect(typeof response.stream).toEqual('function');
|
||||
|
||||
// In Node.js, it should return a stream
|
||||
const nodeStream = response.streamNode();
|
||||
// Get web stream
|
||||
const webStream = response.stream();
|
||||
expect(webStream).toBeDefined();
|
||||
|
||||
// Verify it's a web ReadableStream
|
||||
expect(typeof webStream.getReader).toEqual('function');
|
||||
expect(typeof webStream.cancel).toEqual('function');
|
||||
|
||||
// Convert to Node.js stream using Readable.fromWeb()
|
||||
// Known TypeScript limitation: @types/node ReadableStream differs from web ReadableStream
|
||||
const nodeStream = Readable.fromWeb(webStream as any);
|
||||
expect(nodeStream).toBeDefined();
|
||||
|
||||
// Verify it's a Node.js readable stream
|
||||
@@ -22,6 +32,37 @@ tap.test('should have streamNode() method available', async () => {
|
||||
nodeStream.resume();
|
||||
});
|
||||
|
||||
tap.test('should convert web stream to Node.js stream correctly', async () => {
|
||||
const response = await SmartRequest.create()
|
||||
.url('https://httpbin.org/get')
|
||||
.get();
|
||||
|
||||
const webStream = response.stream();
|
||||
const nodeStream = Readable.fromWeb(webStream as any);
|
||||
|
||||
// Collect data from stream
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
nodeStream.on('data', (chunk) => {
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
nodeStream.on('end', () => {
|
||||
resolve();
|
||||
});
|
||||
|
||||
nodeStream.on('error', reject);
|
||||
});
|
||||
|
||||
// Verify we received data
|
||||
const data = Buffer.concat(chunks);
|
||||
expect(data.length).toBeGreaterThan(0);
|
||||
|
||||
// Verify it's valid JSON
|
||||
const json = JSON.parse(data.toString('utf-8'));
|
||||
expect(json).toBeDefined();
|
||||
expect(json.url).toEqual('https://httpbin.org/get');
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartrequest',
|
||||
version: '4.4.2',
|
||||
version: '5.0.3',
|
||||
description: 'A module for modern HTTP/HTTPS requests with support for form data, file uploads, JSON, binary data, streams, and more.'
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import type {
|
||||
ResponseType,
|
||||
FormField,
|
||||
RateLimitConfig,
|
||||
RawStreamFunction,
|
||||
} from './types/common.js';
|
||||
import {
|
||||
type TPaginationConfig,
|
||||
@@ -161,17 +160,6 @@ export class SmartRequest<T = any> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a custom function to handle raw request streaming
|
||||
* This gives full control over the request body streaming
|
||||
* Note: Only works in Node.js environment, not supported in browsers
|
||||
*/
|
||||
raw(streamFunc: RawStreamFunction): this {
|
||||
// Store the raw streaming function to be used later
|
||||
(this._options as any).__rawStreamFunc = streamFunc;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set request timeout in milliseconds
|
||||
*/
|
||||
@@ -440,7 +428,7 @@ export class SmartRequest<T = any> {
|
||||
// Main retry loop
|
||||
for (let attempt = 0; attempt <= this._retries; attempt++) {
|
||||
try {
|
||||
// Check if we have a Node.js stream or raw function that needs special handling
|
||||
// Check if we have a Node.js stream that needs special handling
|
||||
let requestDataFunc = null;
|
||||
if ((this._options as any).__nodeStream) {
|
||||
const nodeStream = (this._options as any).__nodeStream;
|
||||
@@ -449,16 +437,12 @@ export class SmartRequest<T = any> {
|
||||
};
|
||||
// Don't delete __nodeStream yet - let CoreRequest implementations handle it
|
||||
// Node.js will use requestDataFunc, Bun/Deno will convert the stream
|
||||
} else if ((this._options as any).__rawStreamFunc) {
|
||||
requestDataFunc = (this._options as any).__rawStreamFunc;
|
||||
// Don't delete __rawStreamFunc yet - let CoreRequest implementations handle it
|
||||
}
|
||||
|
||||
const request = new CoreRequest(this._url, this._options as any, requestDataFunc);
|
||||
|
||||
// Clean up temporary properties after CoreRequest has been created
|
||||
delete (this._options as any).__nodeStream;
|
||||
delete (this._options as any).__rawStreamFunc;
|
||||
const response = (await request.fire()) as ICoreResponse<R>;
|
||||
|
||||
// Check for 429 status if rate limit handling is enabled
|
||||
|
||||
@@ -66,9 +66,3 @@ export interface RateLimitConfig {
|
||||
backoffFactor?: number; // Exponential backoff factor (default: 2)
|
||||
onRateLimit?: (attempt: number, waitTime: number) => void; // Callback for rate limit events
|
||||
}
|
||||
|
||||
/**
|
||||
* Raw streaming function for advanced request body control
|
||||
* Note: The request parameter type depends on the environment (Node.js ClientRequest or fetch Request)
|
||||
*/
|
||||
export type RawStreamFunction = (request: any) => void;
|
||||
|
||||
@@ -42,9 +42,4 @@ export abstract class CoreResponse<T = any> implements types.ICoreResponse<T> {
|
||||
* Get response as a web-style ReadableStream
|
||||
*/
|
||||
abstract stream(): ReadableStream<Uint8Array> | null;
|
||||
|
||||
/**
|
||||
* Get response as a Node.js stream (throws in browser)
|
||||
*/
|
||||
abstract streamNode(): NodeJS.ReadableStream | never;
|
||||
}
|
||||
|
||||
@@ -86,5 +86,4 @@ export interface ICoreResponse<T = any> {
|
||||
text(): Promise<string>;
|
||||
arrayBuffer(): Promise<ArrayBuffer>;
|
||||
stream(): ReadableStream<Uint8Array> | null; // Always returns web-style stream
|
||||
streamNode(): NodeJS.ReadableStream | never; // Returns Node.js stream or throws in browser
|
||||
}
|
||||
|
||||
@@ -72,20 +72,6 @@ export class CoreResponse<T = any>
|
||||
return this.response.body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get response as a Node.js-style stream
|
||||
* Bun supports Node.js streams, so we can provide this functionality
|
||||
*
|
||||
* Note: In Bun, you may also be able to use the web stream directly with stream() method
|
||||
*/
|
||||
streamNode(): never {
|
||||
// Bun primarily uses web streams and has excellent compatibility
|
||||
// For most use cases, use stream() which returns a standard ReadableStream
|
||||
throw new Error(
|
||||
'streamNode() is not available in Bun environment. Use stream() for web-style ReadableStream, which Bun fully supports.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw Response object
|
||||
*/
|
||||
|
||||
@@ -72,16 +72,6 @@ export class CoreResponse<T = any>
|
||||
return this.response.body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Node.js stream method - not available in Deno's standard mode
|
||||
* Throws an error as Deno uses web-standard ReadableStream
|
||||
*/
|
||||
streamNode(): never {
|
||||
throw new Error(
|
||||
'streamNode() is not available in Deno environment. Use stream() for web-style ReadableStream.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw Response object
|
||||
*/
|
||||
|
||||
@@ -72,15 +72,6 @@ export class CoreResponse<T = any>
|
||||
return this.response.body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Node.js stream method - not available in browser
|
||||
*/
|
||||
streamNode(): never {
|
||||
throw new Error(
|
||||
'streamNode() is not available in browser/fetch environment. Use stream() for web-style ReadableStream.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw Response object
|
||||
*/
|
||||
|
||||
@@ -14,8 +14,11 @@ import * as smarturl from '@push.rocks/smarturl';
|
||||
export { smartpromise, smarturl };
|
||||
|
||||
// third party scope
|
||||
import { HttpAgent, HttpsAgent } from 'agentkeepalive';
|
||||
const agentkeepalive = { HttpAgent, HttpsAgent };
|
||||
import AgentKeepAlive from 'agentkeepalive';
|
||||
const agentkeepalive = {
|
||||
HttpAgent: AgentKeepAlive.HttpAgent ?? AgentKeepAlive,
|
||||
HttpsAgent: AgentKeepAlive.HttpsAgent,
|
||||
};
|
||||
import formData from 'form-data';
|
||||
|
||||
export { agentkeepalive, formData };
|
||||
|
||||
@@ -129,41 +129,15 @@ export class CoreResponse<T = any>
|
||||
stream(): ReadableStream<Uint8Array> | null {
|
||||
this.ensureNotConsumed();
|
||||
|
||||
// Convert Node.js stream to web stream
|
||||
// In Node.js 16.5+ we can use Readable.toWeb()
|
||||
// Convert Node.js stream to web stream using Readable.toWeb()
|
||||
// This creates a proper Node.js-compatible web stream that works with Readable.fromWeb()
|
||||
if (this.incomingMessage.readableEnded || this.incomingMessage.destroyed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create a web ReadableStream from the Node.js stream
|
||||
const nodeStream = this.incomingMessage;
|
||||
return new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
nodeStream.on('data', (chunk) => {
|
||||
controller.enqueue(new Uint8Array(chunk));
|
||||
});
|
||||
|
||||
nodeStream.on('end', () => {
|
||||
controller.close();
|
||||
});
|
||||
|
||||
nodeStream.on('error', (err) => {
|
||||
controller.error(err);
|
||||
});
|
||||
},
|
||||
|
||||
cancel() {
|
||||
nodeStream.destroy();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get response as a Node.js readable stream
|
||||
*/
|
||||
streamNode(): NodeJS.ReadableStream {
|
||||
this.ensureNotConsumed();
|
||||
return this.incomingMessage;
|
||||
// Use Readable.toWeb() to convert Node.js stream to web stream (Node.js 16.5+)
|
||||
// The returned type is automatically Node.js ReadableStream which is compatible with Readable.fromWeb()
|
||||
return plugins.stream.Readable.toWeb(this.incomingMessage) as any;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user