Compare commits
	
		
			15 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| df28cd4778 | |||
| f49cbd2b6a | |||
| 984b53cba2 | |||
| 4c55243646 | |||
| 49cfcaedd1 | |||
| 3996a69f91 | |||
| 629f6dd425 | |||
| d141ceeaf7 | |||
| 7d3c94cae6 | |||
| 5bae452365 | |||
| ffabcf7bdb | |||
| 361d97f440 | |||
| 35867d9148 | |||
| d455a34632 | |||
| 9c5a939499 | 
| @@ -23,24 +23,16 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v3 |       - uses: actions/checkout@v3 | ||||||
|  |  | ||||||
|       - name: Install pnpm and npmci |  | ||||||
|         run: | |  | ||||||
|           pnpm install -g pnpm |  | ||||||
|           pnpm install -g @ship.zone/npmci |  | ||||||
|  |  | ||||||
|       - name: Run npm prepare |  | ||||||
|         run: npmci npm prepare |  | ||||||
|  |  | ||||||
|       - name: Audit production dependencies |       - name: Audit production dependencies | ||||||
|         run: | |         run: | | ||||||
|           npmci command npm config set registry https://registry.npmjs.org |           npm config set registry https://registry.npmjs.org | ||||||
|           npmci command pnpm audit --audit-level=high --prod |           pnpm audit --audit-level=high --prod | ||||||
|         continue-on-error: true |         continue-on-error: true | ||||||
|  |  | ||||||
|       - name: Audit development dependencies |       - name: Audit development dependencies | ||||||
|         run: | |         run: | | ||||||
|           npmci command npm config set registry https://registry.npmjs.org |           npm config set registry https://registry.npmjs.org | ||||||
|           npmci command pnpm audit --audit-level=high --dev |           pnpm audit --audit-level=high --dev | ||||||
|         continue-on-error: true |         continue-on-error: true | ||||||
|  |  | ||||||
|   test: |   test: | ||||||
| @@ -55,12 +47,10 @@ jobs: | |||||||
|  |  | ||||||
|       - name: Test stable |       - name: Test stable | ||||||
|         run: | |         run: | | ||||||
|           npmci node install stable |           pnpm install | ||||||
|           npmci npm install |           pnpm test | ||||||
|           npmci npm test |  | ||||||
|  |  | ||||||
|       - name: Test build |       - name: Test build | ||||||
|         run: | |         run: | | ||||||
|           npmci node install stable |           pnpm install | ||||||
|           npmci npm install |           pnpm build | ||||||
|           npmci npm build |  | ||||||
|   | |||||||
| @@ -23,22 +23,16 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v3 |       - uses: actions/checkout@v3 | ||||||
|  |  | ||||||
|       - name: Prepare |  | ||||||
|         run: | |  | ||||||
|           pnpm install -g pnpm |  | ||||||
|           pnpm install -g @ship.zone/npmci |  | ||||||
|           npmci npm prepare |  | ||||||
|  |  | ||||||
|       - name: Audit production dependencies |       - name: Audit production dependencies | ||||||
|         run: | |         run: | | ||||||
|           npmci command npm config set registry https://registry.npmjs.org |           npm config set registry https://registry.npmjs.org | ||||||
|           npmci command pnpm audit --audit-level=high --prod |           pnpm audit --audit-level=high --prod | ||||||
|         continue-on-error: true |         continue-on-error: true | ||||||
|  |  | ||||||
|       - name: Audit development dependencies |       - name: Audit development dependencies | ||||||
|         run: | |         run: | | ||||||
|           npmci command npm config set registry https://registry.npmjs.org |           npm config set registry https://registry.npmjs.org | ||||||
|           npmci command pnpm audit --audit-level=high --dev |           pnpm audit --audit-level=high --dev | ||||||
|         continue-on-error: true |         continue-on-error: true | ||||||
|  |  | ||||||
|   test: |   test: | ||||||
| @@ -51,23 +45,15 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v3 |       - uses: actions/checkout@v3 | ||||||
|  |  | ||||||
|       - name: Prepare |  | ||||||
|         run: | |  | ||||||
|           pnpm install -g pnpm |  | ||||||
|           pnpm install -g @ship.zone/npmci |  | ||||||
|           npmci npm prepare |  | ||||||
|  |  | ||||||
|       - name: Test stable |       - name: Test stable | ||||||
|         run: | |         run: | | ||||||
|           npmci node install stable |           pnpm install | ||||||
|           npmci npm install |           pnpm test | ||||||
|           npmci npm test |  | ||||||
|  |  | ||||||
|       - name: Test build |       - name: Test build | ||||||
|         run: | |         run: | | ||||||
|           npmci node install stable |           pnpm install | ||||||
|           npmci npm install |           pnpm build | ||||||
|           npmci npm build |  | ||||||
|  |  | ||||||
|   release: |   release: | ||||||
|     needs: test |     needs: test | ||||||
| @@ -79,16 +65,27 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v3 |       - uses: actions/checkout@v3 | ||||||
|  |  | ||||||
|       - name: Prepare |  | ||||||
|         run: | |  | ||||||
|           pnpm install -g pnpm |  | ||||||
|           pnpm install -g @ship.zone/npmci |  | ||||||
|           npmci npm prepare |  | ||||||
|  |  | ||||||
|       - name: Release |       - name: Release | ||||||
|         run: | |         run: | | ||||||
|           npmci node install stable |           pnpm install | ||||||
|           npmci npm publish |           # Extract server host from GITHUB_SERVER_URL (remove https://) | ||||||
|  |           GITEA_HOST="${GITHUB_SERVER_URL#https://}" | ||||||
|  |           GITEA_REGISTRY="$GITHUB_SERVER_URL/api/packages/$GITHUB_REPOSITORY_OWNER/npm/" | ||||||
|  |  | ||||||
|  |           # Create .npmrc for Gitea authentication | ||||||
|  |           echo "@${GITHUB_REPOSITORY_OWNER}:registry=${GITEA_REGISTRY}" > .npmrc | ||||||
|  |           echo "//${GITEA_HOST}/api/packages/${GITHUB_REPOSITORY_OWNER}/npm/:_authToken=${GITEA_TOKEN}" >> .npmrc | ||||||
|  |  | ||||||
|  |           # Publish to Gitea | ||||||
|  |           pnpm publish --no-git-checks | ||||||
|  |  | ||||||
|  |           # Conditionally publish to npmjs.org if token exists | ||||||
|  |           if [ -n "$NPMCI_TOKEN_NPM" ]; then | ||||||
|  |             # Update .npmrc for npmjs.org | ||||||
|  |             echo "registry=https://registry.npmjs.org/" > .npmrc | ||||||
|  |             echo "//registry.npmjs.org/:_authToken=${NPMCI_TOKEN_NPM}" >> .npmrc | ||||||
|  |             pnpm publish --no-git-checks | ||||||
|  |           fi | ||||||
|  |  | ||||||
|   metadata: |   metadata: | ||||||
|     needs: test |     needs: test | ||||||
| @@ -101,24 +98,14 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v3 |       - uses: actions/checkout@v3 | ||||||
|  |  | ||||||
|       - name: Prepare |  | ||||||
|         run: | |  | ||||||
|           pnpm install -g pnpm |  | ||||||
|           pnpm install -g @ship.zone/npmci |  | ||||||
|           npmci npm prepare |  | ||||||
|  |  | ||||||
|       - name: Code quality |       - name: Code quality | ||||||
|         run: | |         run: | | ||||||
|           npmci command npm install -g typescript |           npm install -g typescript | ||||||
|           npmci npm install |           pnpm install | ||||||
|  |  | ||||||
|       - name: Trigger |  | ||||||
|         run: npmci trigger |  | ||||||
|  |  | ||||||
|       - name: Build docs and upload artifacts |       - name: Build docs and upload artifacts | ||||||
|         run: | |         run: | | ||||||
|           npmci node install stable |           pnpm install | ||||||
|           npmci npm install |  | ||||||
|           pnpm install -g @git.zone/tsdoc |           pnpm install -g @git.zone/tsdoc | ||||||
|           npmci command tsdoc |           tsdoc | ||||||
|         continue-on-error: true |         continue-on-error: true | ||||||
|   | |||||||
							
								
								
									
										59
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										59
									
								
								changelog.md
									
									
									
									
									
								
							| @@ -1,5 +1,64 @@ | |||||||
| # Changelog | # Changelog | ||||||
|  |  | ||||||
|  | ## 2025-10-26 - 4.3.6 - fix(ci) | ||||||
|  | Use .npmrc for registry authentication in Gitea workflow and add conditional npmjs publish | ||||||
|  |  | ||||||
|  | - Replace npm config set commands with creating a .npmrc file for Gitea registry authentication in .gitea/workflows/default_tags.yaml | ||||||
|  | - Add conditional update of .npmrc and publishing to npmjs.org when NPMCI_TOKEN_NPM is provided | ||||||
|  | - Keep pnpm publish --no-git-checks; improve CI credential handling to be file-based | ||||||
|  |  | ||||||
|  | ## 2025-10-26 - 4.3.5 - fix(workflows) | ||||||
|  | Remove npmci wrappers from CI workflows and use pnpm/npm CLI directly | ||||||
|  |  | ||||||
|  | - Removed global npmci installation and npmci npm prepare steps from Gitea workflow files | ||||||
|  | - Use pnpm install/test/build instead of npmci-wrapped commands in test jobs | ||||||
|  | - Replace npmci command npm config set ... with direct npm config set calls for registry/auth configuration | ||||||
|  | - Use pnpm publish --no-git-checks for Gitea publishing and use pnpm publish for conditional npmjs publish when token present | ||||||
|  | - Simplified dependency auditing to run pnpm audit and set registry via npm config set | ||||||
|  | - Install tsdoc globally and run tsdoc during docs build step (replacing npmci command usage) | ||||||
|  |  | ||||||
|  | ## 2025-10-25 - 4.3.4 - fix(ci) | ||||||
|  | Fix Gitea workflow publish invocation to run npm publish via npmci command | ||||||
|  |  | ||||||
|  | - Update .gitea/workflows/default_tags.yaml to use 'npmci command npm publish' for the publish step | ||||||
|  | - Ensures the workflow runs npm publish through the npmci command wrapper to avoid incorrect task invocation | ||||||
|  |  | ||||||
|  | ## 2025-10-25 - 4.3.3 - fix(ci) | ||||||
|  | Improve Gitea release workflow: install deps, configure Gitea npm registry, and optionally publish to npmjs.org | ||||||
|  |  | ||||||
|  | - Run npm install in the release job to ensure dependencies are available before publishing. | ||||||
|  | - Configure Gitea/npm registry using GITHUB_SERVER_URL and set auth token for the @<owner> scope. | ||||||
|  | - Publish to the Gitea npm registry during release. | ||||||
|  | - If NPMCI_TOKEN_NPM is provided, also publish to the public npmjs.org registry (conditional publish). | ||||||
|  | - Extract host from GITHUB_SERVER_URL to correctly set the registry auth URL. | ||||||
|  |  | ||||||
|  | ## 2025-10-17 - 4.3.2 - fix(core) | ||||||
|  | Remove stray console.log from core module | ||||||
|  |  | ||||||
|  | - Removed a stray debug console.log(modulePath) from ts/core/index.ts that printed the module path during Node environment initialization | ||||||
|  |  | ||||||
|  | ## 2025-08-19 - 4.3.1 - fix(core) | ||||||
|  | Improve streaming support and timeout handling; add browser streaming & timeout tests and README clarifications | ||||||
|  |  | ||||||
|  | - core_fetch: accept Uint8Array and Buffer-like bodies; set fetch duplex for ReadableStream bodies so streaming requests work in environments that require duplex | ||||||
|  | - core_fetch: implement AbortController-based timeouts and ensure timeouts are cleared on success/error to avoid hanging timers | ||||||
|  | - core_node: add explicit request timeout handling (request.setTimeout) and hard-data-cutting timeout tracking with proper timeoutId clear on success/error | ||||||
|  | - client: document that raw(streamFunc) is Node-only (not supported in browsers) | ||||||
|  | - tests: add browser streaming tests (test/test.streaming.browser.ts) that exercise buffer() and web ReadableStream via stream() | ||||||
|  | - tests: add timeout tests (test/test.timeout.ts) to validate clearing timers, enforcing timeouts, and preventing timer leaks across multiple requests | ||||||
|  | - docs: update README streaming section to clarify cross-platform behavior of buffer(), stream(), and raw() methods | ||||||
|  |  | ||||||
|  | ## 2025-08-18 - 4.3.0 - feat(client/smartrequest) | ||||||
|  | Add streaming and raw buffer support to SmartRequest (buffer, stream, raw); update docs and tests | ||||||
|  |  | ||||||
|  | - Add SmartRequest.buffer(data, contentType?) to send Buffer or Uint8Array bodies with Content-Type header. | ||||||
|  | - Add SmartRequest.stream(stream, contentType?) to accept Node.js Readable streams or web ReadableStream and set Content-Type when provided. | ||||||
|  | - Add SmartRequest.raw(streamFunc) to allow custom raw streaming functions (Node.js only) and a RawStreamFunction type. | ||||||
|  | - Wire Node.js stream handling into CoreRequest by passing a requestDataFunc when creating CoreRequest instances. | ||||||
|  | - Add comprehensive streaming examples and documentation to README describing buffer/stream/raw usage and streaming methods. | ||||||
|  | - Add tests for streaming behavior (test/test.streaming.ts) covering buffer, stream, raw, and Uint8Array usage. | ||||||
|  | - Update client exports and plugins to support streaming features and FormData usage where needed. | ||||||
|  |  | ||||||
| ## 2025-08-18 - 4.2.2 - fix(client) | ## 2025-08-18 - 4.2.2 - fix(client) | ||||||
| Fix CI configuration, prevent socket hangs with auto-drain, and apply various client/core TypeScript fixes and test updates | Fix CI configuration, prevent socket hangs with auto-drain, and apply various client/core TypeScript fixes and test updates | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "@push.rocks/smartrequest", |   "name": "@push.rocks/smartrequest", | ||||||
|   "version": "4.2.2", |   "version": "4.3.6", | ||||||
|   "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": { | ||||||
|   | |||||||
							
								
								
									
										99
									
								
								readme.md
									
									
									
									
									
								
							
							
						
						
									
										99
									
								
								readme.md
									
									
									
									
									
								
							| @@ -25,7 +25,7 @@ yarn add @push.rocks/smartrequest | |||||||
| - ⚡ **Keep-Alive Connections** - Efficient connection pooling in Node.js | - ⚡ **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** - Stream buffers, files, and custom data without loading into memory | ||||||
| - 🔧 **Highly Configurable** - Timeouts, retries, headers, and more | - 🔧 **Highly Configurable** - Timeouts, retries, headers, and more | ||||||
|  |  | ||||||
| ## Architecture | ## Architecture | ||||||
| @@ -303,6 +303,103 @@ async function uploadMultipleFiles( | |||||||
| } | } | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | ### Streaming Request Bodies | ||||||
|  |  | ||||||
|  | SmartRequest provides multiple ways to stream data in requests, making it easy to upload large files or send real-time data without loading everything into memory: | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | import { SmartRequest } from '@push.rocks/smartrequest'; | ||||||
|  | import * as fs from 'fs'; | ||||||
|  | import { Readable } from 'stream'; | ||||||
|  |  | ||||||
|  | // Stream a Buffer directly | ||||||
|  | async function uploadBuffer() { | ||||||
|  |   const buffer = Buffer.from('Hello, World!'); | ||||||
|  |    | ||||||
|  |   const response = await SmartRequest.create() | ||||||
|  |     .url('https://api.example.com/upload') | ||||||
|  |     .buffer(buffer, 'text/plain') | ||||||
|  |     .post(); | ||||||
|  |    | ||||||
|  |   return await response.json(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Stream a file using Node.js streams | ||||||
|  | async function uploadLargeFile(filePath: string) { | ||||||
|  |   const fileStream = fs.createReadStream(filePath); | ||||||
|  |    | ||||||
|  |   const response = await SmartRequest.create() | ||||||
|  |     .url('https://api.example.com/upload') | ||||||
|  |     .stream(fileStream, 'application/octet-stream') | ||||||
|  |     .post(); | ||||||
|  |    | ||||||
|  |   return await response.json(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Stream data from any readable source | ||||||
|  | async function streamData(dataSource: Readable) { | ||||||
|  |   const response = await SmartRequest.create() | ||||||
|  |     .url('https://api.example.com/stream') | ||||||
|  |     .stream(dataSource) | ||||||
|  |     .post(); | ||||||
|  |    | ||||||
|  |   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) | ||||||
|  | async function uploadBinaryData() { | ||||||
|  |   const data = new Uint8Array([72, 101, 108, 108, 111]); // "Hello" | ||||||
|  |    | ||||||
|  |   const response = await SmartRequest.create() | ||||||
|  |     .url('https://api.example.com/binary') | ||||||
|  |     .buffer(data, 'application/octet-stream') | ||||||
|  |     .post(); | ||||||
|  |    | ||||||
|  |   return await response.json(); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | #### Streaming Methods | ||||||
|  |  | ||||||
|  | - **`.buffer(data, contentType?)`** - Stream a Buffer or Uint8Array directly | ||||||
|  |   - `data`: Buffer (Node.js) or Uint8Array (both platforms) to send | ||||||
|  |   - `contentType`: Optional content type (defaults to 'application/octet-stream') | ||||||
|  |   - ✅ Works in both Node.js and browsers | ||||||
|  |  | ||||||
|  | - **`.stream(stream, contentType?)`** - Stream from ReadableStream | ||||||
|  |   - `stream`: Web ReadableStream (both platforms) or Node.js stream (Node.js only) | ||||||
|  |   - `contentType`: Optional content type | ||||||
|  |   - ✅ Web ReadableStream works in both Node.js and browsers | ||||||
|  |   - ⚠️ Node.js streams only work in Node.js environment | ||||||
|  |  | ||||||
|  | - **`.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 | ||||||
|  |   - 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 | ||||||
|  | - Proxying data between services | ||||||
|  | - Implementing chunked transfer encoding | ||||||
|  |  | ||||||
| ### Unix Socket Support (Node.js only) | ### Unix Socket Support (Node.js only) | ||||||
|  |  | ||||||
| ```typescript | ```typescript | ||||||
|   | |||||||
							
								
								
									
										41
									
								
								test/test.streaming.browser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								test/test.streaming.browser.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | import { tap, expect } from '@git.zone/tstest/tapbundle'; | ||||||
|  | import { SmartRequest } from '../ts/index.js'; | ||||||
|  |  | ||||||
|  | tap.test('browser: should send Uint8Array using buffer() method', async () => { | ||||||
|  |   const testData = new Uint8Array([72, 101, 108, 108, 111]); // "Hello" in ASCII | ||||||
|  |    | ||||||
|  |   const smartRequest = SmartRequest.create() | ||||||
|  |     .url('https://httpbin.org/post') | ||||||
|  |     .buffer(testData, 'application/octet-stream') | ||||||
|  |     .method('POST'); | ||||||
|  |    | ||||||
|  |   const response = await smartRequest.post(); | ||||||
|  |   const data = await response.json(); | ||||||
|  |    | ||||||
|  |   expect(data).toHaveProperty('data'); | ||||||
|  |   expect(data.headers['Content-Type']).toEqual('application/octet-stream'); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | tap.test('browser: should send web ReadableStream using stream() method', async () => { | ||||||
|  |   // Create a web ReadableStream | ||||||
|  |   const encoder = new TextEncoder(); | ||||||
|  |   const stream = new ReadableStream({ | ||||||
|  |     start(controller) { | ||||||
|  |       controller.enqueue(encoder.encode('Test stream data')); | ||||||
|  |       controller.close(); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  |    | ||||||
|  |   const smartRequest = SmartRequest.create() | ||||||
|  |     .url('https://httpbin.org/post') | ||||||
|  |     .stream(stream, 'text/plain') | ||||||
|  |     .method('POST'); | ||||||
|  |    | ||||||
|  |   const response = await smartRequest.post(); | ||||||
|  |   const data = await response.json(); | ||||||
|  |    | ||||||
|  |   expect(data).toHaveProperty('data'); | ||||||
|  |   // httpbin should receive the streamed data | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | export default tap.start(); | ||||||
							
								
								
									
										74
									
								
								test/test.streaming.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								test/test.streaming.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | |||||||
|  | import { tap, expect } from '@git.zone/tstest/tapbundle'; | ||||||
|  | import * as fs from 'fs'; | ||||||
|  | import { SmartRequest } from '../ts/index.js'; | ||||||
|  |  | ||||||
|  | tap.test('should send a buffer using buffer() method', async () => { | ||||||
|  |   const testBuffer = Buffer.from('Hello, World!'); | ||||||
|  |    | ||||||
|  |   const smartRequest = SmartRequest.create() | ||||||
|  |     .url('https://httpbin.org/post') | ||||||
|  |     .buffer(testBuffer, 'text/plain') | ||||||
|  |     .method('POST'); | ||||||
|  |    | ||||||
|  |   const response = await smartRequest.post(); | ||||||
|  |   const data = await response.json(); | ||||||
|  |    | ||||||
|  |   expect(data).toHaveProperty('data'); | ||||||
|  |   expect(data.data).toEqual('Hello, World!'); | ||||||
|  |   expect(data.headers['Content-Type']).toEqual('text/plain'); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | tap.test('should send a stream using stream() method', async () => { | ||||||
|  |   // Create a simple readable stream | ||||||
|  |   const { Readable } = await import('stream'); | ||||||
|  |   const testData = 'Stream data test'; | ||||||
|  |   const stream = Readable.from([testData]); | ||||||
|  |    | ||||||
|  |   const smartRequest = SmartRequest.create() | ||||||
|  |     .url('https://httpbin.org/post') | ||||||
|  |     .stream(stream, 'text/plain') | ||||||
|  |     .method('POST'); | ||||||
|  |    | ||||||
|  |   const response = await smartRequest.post(); | ||||||
|  |   const data = await response.json(); | ||||||
|  |    | ||||||
|  |   expect(data).toHaveProperty('data'); | ||||||
|  |   expect(data.data).toEqual(testData); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | tap.test('should handle raw streaming with custom function', async () => { | ||||||
|  |   const testData = 'Custom raw stream data'; | ||||||
|  |    | ||||||
|  |   const smartRequest = SmartRequest.create() | ||||||
|  |     .url('https://httpbin.org/post') | ||||||
|  |     .raw((request) => { | ||||||
|  |       // Custom streaming logic | ||||||
|  |       request.write(testData); | ||||||
|  |       request.end(); | ||||||
|  |     }) | ||||||
|  |     .method('POST'); | ||||||
|  |    | ||||||
|  |   const response = await smartRequest.post(); | ||||||
|  |   const data = await response.json(); | ||||||
|  |    | ||||||
|  |   expect(data).toHaveProperty('data'); | ||||||
|  |   expect(data.data).toEqual(testData); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | tap.test('should send Uint8Array using buffer() method', async () => { | ||||||
|  |   const testData = new Uint8Array([72, 101, 108, 108, 111]); // "Hello" in ASCII | ||||||
|  |    | ||||||
|  |   const smartRequest = SmartRequest.create() | ||||||
|  |     .url('https://httpbin.org/post') | ||||||
|  |     .buffer(testData, 'application/octet-stream') | ||||||
|  |     .method('POST'); | ||||||
|  |    | ||||||
|  |   const response = await smartRequest.post(); | ||||||
|  |   const data = await response.json(); | ||||||
|  |    | ||||||
|  |   // Just verify that data was sent | ||||||
|  |   expect(data).toHaveProperty('data'); | ||||||
|  |   expect(data.headers['Content-Type']).toEqual('application/octet-stream'); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | export default tap.start(); | ||||||
							
								
								
									
										27
									
								
								test/test.streamnode.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								test/test.streamnode.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | import { tap, expect } from '@git.zone/tstest/tapbundle'; | ||||||
|  | import { SmartRequest } from '../ts/index.js'; | ||||||
|  |  | ||||||
|  | tap.test('should have streamNode() method available', 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'); | ||||||
|  |    | ||||||
|  |   // In Node.js, it should return a stream | ||||||
|  |   const nodeStream = response.streamNode(); | ||||||
|  |   expect(nodeStream).toBeDefined(); | ||||||
|  |    | ||||||
|  |   // Verify it's a Node.js readable stream | ||||||
|  |   expect(typeof nodeStream.pipe).toEqual('function'); | ||||||
|  |   expect(typeof nodeStream.on).toEqual('function'); | ||||||
|  |    | ||||||
|  |   // Consume the stream to avoid hanging | ||||||
|  |   nodeStream.resume(); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | export default tap.start(); | ||||||
							
								
								
									
										60
									
								
								test/test.timeout.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								test/test.timeout.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | import { tap, expect } from '@git.zone/tstest/tapbundle'; | ||||||
|  | import { SmartRequest } from '../ts/index.js'; | ||||||
|  |  | ||||||
|  | tap.test('should clear timeout when request completes before timeout', async () => { | ||||||
|  |   // Set a long timeout that would keep the process alive if not cleared | ||||||
|  |   const response = await SmartRequest.create() | ||||||
|  |     .url('https://httpbin.org/delay/1') // 1 second delay | ||||||
|  |     .timeout(10000) // 10 second timeout (much longer than needed) | ||||||
|  |     .get(); | ||||||
|  |    | ||||||
|  |   const data = await response.json(); | ||||||
|  |   expect(data).toBeDefined(); | ||||||
|  |    | ||||||
|  |   // The test should complete quickly, not wait for the 10 second timeout | ||||||
|  |   // If the timeout isn't cleared, the process would hang for 10 seconds | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | tap.test('should timeout when request takes longer than timeout', async () => { | ||||||
|  |   let errorThrown = false; | ||||||
|  |    | ||||||
|  |   try { | ||||||
|  |     // Try to fetch with a very short timeout | ||||||
|  |     await SmartRequest.create() | ||||||
|  |       .url('https://httpbin.org/delay/3') // 3 second delay | ||||||
|  |       .timeout(100) // 100ms timeout (will fail) | ||||||
|  |       .get(); | ||||||
|  |   } catch (error) { | ||||||
|  |     errorThrown = true; | ||||||
|  |     expect(error.message).toContain('Request timed out'); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   expect(errorThrown).toBeTrue(); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | tap.test('should not leak timers with multiple successful requests', async () => { | ||||||
|  |   // Make multiple requests with timeouts to ensure no timer leaks | ||||||
|  |   const promises = []; | ||||||
|  |    | ||||||
|  |   for (let i = 0; i < 5; i++) { | ||||||
|  |     promises.push( | ||||||
|  |       SmartRequest.create() | ||||||
|  |         .url('https://httpbin.org/get') | ||||||
|  |         .timeout(5000) // 5 second timeout | ||||||
|  |         .get() | ||||||
|  |         .then(response => response.json()) | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   const results = await Promise.all(promises); | ||||||
|  |    | ||||||
|  |   // All requests should complete successfully | ||||||
|  |   expect(results).toHaveLength(5); | ||||||
|  |   results.forEach(result => { | ||||||
|  |     expect(result).toBeDefined(); | ||||||
|  |   }); | ||||||
|  |    | ||||||
|  |   // Process should exit cleanly after this test without hanging | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | export default tap.start(); | ||||||
| @@ -3,6 +3,6 @@ | |||||||
|  */ |  */ | ||||||
| export const commitinfo = { | export const commitinfo = { | ||||||
|   name: '@push.rocks/smartrequest', |   name: '@push.rocks/smartrequest', | ||||||
|   version: '4.2.2', |   version: '4.3.6', | ||||||
|   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.' | ||||||
| } | } | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ import type { | |||||||
|   ResponseType, |   ResponseType, | ||||||
|   FormField, |   FormField, | ||||||
|   RateLimitConfig, |   RateLimitConfig, | ||||||
|  |   RawStreamFunction, | ||||||
| } from './types/common.js'; | } from './types/common.js'; | ||||||
| import { | import { | ||||||
|   type TPaginationConfig, |   type TPaginationConfig, | ||||||
| @@ -121,6 +122,56 @@ export class SmartRequest<T = any> { | |||||||
|     return this; |     return this; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Set raw buffer data for the request | ||||||
|  |    */ | ||||||
|  |   buffer(data: Buffer | Uint8Array, contentType?: string): this { | ||||||
|  |     if (!this._options.headers) { | ||||||
|  |       this._options.headers = {}; | ||||||
|  |     } | ||||||
|  |     this._options.headers['Content-Type'] = contentType || 'application/octet-stream'; | ||||||
|  |     this._options.requestBody = data; | ||||||
|  |     return this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Stream data for the request | ||||||
|  |    * Accepts Node.js Readable streams or web ReadableStream | ||||||
|  |    */ | ||||||
|  |   stream(stream: NodeJS.ReadableStream | ReadableStream<Uint8Array>, contentType?: string): this { | ||||||
|  |     if (!this._options.headers) { | ||||||
|  |       this._options.headers = {}; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Set content type if provided | ||||||
|  |     if (contentType) { | ||||||
|  |       this._options.headers['Content-Type'] = contentType; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Check if it's a Node.js stream (has pipe method) | ||||||
|  |     if ('pipe' in stream && typeof (stream as any).pipe === 'function') { | ||||||
|  |       // For Node.js streams, we need to use a custom approach | ||||||
|  |       // Store the stream to be used later | ||||||
|  |       (this._options as any).__nodeStream = stream; | ||||||
|  |     } else { | ||||||
|  |       // For web ReadableStream, pass directly | ||||||
|  |       this._options.requestBody = stream; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     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 |    * Set request timeout in milliseconds | ||||||
|    */ |    */ | ||||||
| @@ -389,7 +440,22 @@ export class SmartRequest<T = any> { | |||||||
|     // Main retry loop |     // Main retry loop | ||||||
|     for (let attempt = 0; attempt <= this._retries; attempt++) { |     for (let attempt = 0; attempt <= this._retries; attempt++) { | ||||||
|       try { |       try { | ||||||
|         const request = new CoreRequest(this._url, this._options as any); |         // Check if we have a Node.js stream or raw function that needs special handling | ||||||
|  |         let requestDataFunc = null; | ||||||
|  |         if ((this._options as any).__nodeStream) { | ||||||
|  |           const nodeStream = (this._options as any).__nodeStream; | ||||||
|  |           requestDataFunc = (req: any) => { | ||||||
|  |             nodeStream.pipe(req); | ||||||
|  |           }; | ||||||
|  |           // Remove the temporary stream reference | ||||||
|  |           delete (this._options as any).__nodeStream; | ||||||
|  |         } else if ((this._options as any).__rawStreamFunc) { | ||||||
|  |           requestDataFunc = (this._options as any).__rawStreamFunc; | ||||||
|  |           // Remove the temporary function reference | ||||||
|  |           delete (this._options as any).__rawStreamFunc; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         const request = new CoreRequest(this._url, this._options as any, requestDataFunc); | ||||||
|         const response = (await request.fire()) as ICoreResponse<R>; |         const response = (await request.fire()) as ICoreResponse<R>; | ||||||
|  |  | ||||||
|         // Check for 429 status if rate limit handling is enabled |         // Check for 429 status if rate limit handling is enabled | ||||||
|   | |||||||
| @@ -66,3 +66,9 @@ export interface RateLimitConfig { | |||||||
|   backoffFactor?: number; // Exponential backoff factor (default: 2) |   backoffFactor?: number; // Exponential backoff factor (default: 2) | ||||||
|   onRateLimit?: (attempt: number, waitTime: number) => void; // Callback for rate limit events |   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; | ||||||
|   | |||||||
| @@ -15,7 +15,6 @@ if (smartenvInstance.isNode) { | |||||||
|     plugins.smartpath.dirname(import.meta.url), |     plugins.smartpath.dirname(import.meta.url), | ||||||
|     '../core_node/index.js', |     '../core_node/index.js', | ||||||
|   ); |   ); | ||||||
|   console.log(modulePath); |  | ||||||
|   const impl = await smartenvInstance.getSafeNodeModule(modulePath); |   const impl = await smartenvInstance.getSafeNodeModule(modulePath); | ||||||
|   CoreRequest = impl.CoreRequest; |   CoreRequest = impl.CoreRequest; | ||||||
|   CoreResponse = impl.CoreResponse; |   CoreResponse = impl.CoreResponse; | ||||||
|   | |||||||
| @@ -42,4 +42,9 @@ export abstract class CoreResponse<T = any> implements types.ICoreResponse<T> { | |||||||
|    * Get response as a web-style ReadableStream |    * Get response as a web-style ReadableStream | ||||||
|    */ |    */ | ||||||
|   abstract stream(): ReadableStream<Uint8Array> | null; |   abstract stream(): ReadableStream<Uint8Array> | null; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Get response as a Node.js stream (throws in browser) | ||||||
|  |    */ | ||||||
|  |   abstract streamNode(): NodeJS.ReadableStream | never; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -86,4 +86,5 @@ export interface ICoreResponse<T = any> { | |||||||
|   text(): Promise<string>; |   text(): Promise<string>; | ||||||
|   arrayBuffer(): Promise<ArrayBuffer>; |   arrayBuffer(): Promise<ArrayBuffer>; | ||||||
|   stream(): ReadableStream<Uint8Array> | null; // Always returns web-style stream |   stream(): ReadableStream<Uint8Array> | null; // Always returns web-style stream | ||||||
|  |   streamNode(): NodeJS.ReadableStream | never; // Returns Node.js stream or throws in browser | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,6 +9,9 @@ export class CoreRequest extends AbstractCoreRequest< | |||||||
|   types.ICoreRequestOptions, |   types.ICoreRequestOptions, | ||||||
|   CoreResponse |   CoreResponse | ||||||
| > { | > { | ||||||
|  |   private timeoutId: ReturnType<typeof setTimeout> | null = null; | ||||||
|  |   private abortController: AbortController | null = null; | ||||||
|  |  | ||||||
|   constructor(url: string, options: types.ICoreRequestOptions = {}) { |   constructor(url: string, options: types.ICoreRequestOptions = {}) { | ||||||
|     super(url, options); |     super(url, options); | ||||||
|  |  | ||||||
| @@ -61,11 +64,19 @@ export class CoreRequest extends AbstractCoreRequest< | |||||||
|       if ( |       if ( | ||||||
|         typeof this.options.requestBody === 'string' || |         typeof this.options.requestBody === 'string' || | ||||||
|         this.options.requestBody instanceof ArrayBuffer || |         this.options.requestBody instanceof ArrayBuffer || | ||||||
|  |         this.options.requestBody instanceof Uint8Array || | ||||||
|         this.options.requestBody instanceof FormData || |         this.options.requestBody instanceof FormData || | ||||||
|         this.options.requestBody instanceof URLSearchParams || |         this.options.requestBody instanceof URLSearchParams || | ||||||
|         this.options.requestBody instanceof ReadableStream |         this.options.requestBody instanceof ReadableStream || | ||||||
|  |         // Check for Buffer (Node.js polyfills in browser may provide this) | ||||||
|  |         (typeof Buffer !== 'undefined' && this.options.requestBody instanceof Buffer) | ||||||
|       ) { |       ) { | ||||||
|         fetchOptions.body = this.options.requestBody; |         fetchOptions.body = this.options.requestBody; | ||||||
|  |          | ||||||
|  |         // If streaming, we need to set duplex mode | ||||||
|  |         if (this.options.requestBody instanceof ReadableStream) { | ||||||
|  |           (fetchOptions as any).duplex = 'half'; | ||||||
|  |         } | ||||||
|       } else { |       } else { | ||||||
|         // Convert objects to JSON |         // Convert objects to JSON | ||||||
|         fetchOptions.body = JSON.stringify(this.options.requestBody); |         fetchOptions.body = JSON.stringify(this.options.requestBody); | ||||||
| @@ -92,9 +103,13 @@ export class CoreRequest extends AbstractCoreRequest< | |||||||
|     if (this.options.timeout || this.options.hardDataCuttingTimeout) { |     if (this.options.timeout || this.options.hardDataCuttingTimeout) { | ||||||
|       const timeout = |       const timeout = | ||||||
|         this.options.hardDataCuttingTimeout || this.options.timeout; |         this.options.hardDataCuttingTimeout || this.options.timeout; | ||||||
|       const controller = new AbortController(); |       this.abortController = new AbortController(); | ||||||
|       setTimeout(() => controller.abort(), timeout); |       this.timeoutId = setTimeout(() => { | ||||||
|       fetchOptions.signal = controller.signal; |         if (this.abortController) { | ||||||
|  |           this.abortController.abort(); | ||||||
|  |         } | ||||||
|  |       }, timeout); | ||||||
|  |       fetchOptions.signal = this.abortController.signal; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return fetchOptions; |     return fetchOptions; | ||||||
| @@ -117,8 +132,12 @@ export class CoreRequest extends AbstractCoreRequest< | |||||||
|  |  | ||||||
|     try { |     try { | ||||||
|       const response = await fetch(url, options); |       const response = await fetch(url, options); | ||||||
|  |       // Clear timeout on successful response | ||||||
|  |       this.clearTimeout(); | ||||||
|       return response; |       return response; | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|  |       // Clear timeout on error | ||||||
|  |       this.clearTimeout(); | ||||||
|       if (error.name === 'AbortError') { |       if (error.name === 'AbortError') { | ||||||
|         throw new Error('Request timed out'); |         throw new Error('Request timed out'); | ||||||
|       } |       } | ||||||
| @@ -126,6 +145,19 @@ export class CoreRequest extends AbstractCoreRequest< | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Clear the timeout and abort controller | ||||||
|  |    */ | ||||||
|  |   private clearTimeout(): void { | ||||||
|  |     if (this.timeoutId) { | ||||||
|  |       clearTimeout(this.timeoutId); | ||||||
|  |       this.timeoutId = null; | ||||||
|  |     } | ||||||
|  |     if (this.abortController) { | ||||||
|  |       this.abortController = null; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Static factory method to create and fire a request |    * Static factory method to create and fire a request | ||||||
|    */ |    */ | ||||||
|   | |||||||
| @@ -7,9 +7,6 @@ export * from '../core_base/types.js'; | |||||||
|  * Fetch-specific response extensions |  * Fetch-specific response extensions | ||||||
|  */ |  */ | ||||||
| export interface IFetchResponse<T = any> extends baseTypes.ICoreResponse<T> { | export interface IFetchResponse<T = any> extends baseTypes.ICoreResponse<T> { | ||||||
|   // Node.js stream method that throws in browser |  | ||||||
|   streamNode(): never; |  | ||||||
|  |  | ||||||
|   // Access to raw Response object |   // Access to raw Response object | ||||||
|   raw(): Response; |   raw(): Response; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -119,10 +119,11 @@ export class CoreRequest extends AbstractCoreRequest< | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Perform the request |     // Perform the request | ||||||
|  |     let timeoutId: NodeJS.Timeout | null = null; | ||||||
|     const request = requestModule.request(this.options, async (response) => { |     const request = requestModule.request(this.options, async (response) => { | ||||||
|       // Handle hard timeout |       // Handle hard timeout | ||||||
|       if (this.options.hardDataCuttingTimeout) { |       if (this.options.hardDataCuttingTimeout) { | ||||||
|         setTimeout(() => { |         timeoutId = setTimeout(() => { | ||||||
|           response.destroy(); |           response.destroy(); | ||||||
|           done.reject(new Error('Request timed out')); |           done.reject(new Error('Request timed out')); | ||||||
|         }, this.options.hardDataCuttingTimeout); |         }, this.options.hardDataCuttingTimeout); | ||||||
| @@ -132,6 +133,14 @@ export class CoreRequest extends AbstractCoreRequest< | |||||||
|       done.resolve(response); |       done.resolve(response); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     // Set request timeout (Node.js built-in timeout) | ||||||
|  |     if (this.options.timeout) { | ||||||
|  |       request.setTimeout(this.options.timeout, () => { | ||||||
|  |         request.destroy(); | ||||||
|  |         done.reject(new Error('Request timed out')); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // Write request body |     // Write request body | ||||||
|     if (this.options.requestBody) { |     if (this.options.requestBody) { | ||||||
|       if (this.options.requestBody instanceof plugins.formData) { |       if (this.options.requestBody instanceof plugins.formData) { | ||||||
| @@ -159,11 +168,23 @@ export class CoreRequest extends AbstractCoreRequest< | |||||||
|     request.on('error', (e) => { |     request.on('error', (e) => { | ||||||
|       console.error(e); |       console.error(e); | ||||||
|       request.destroy(); |       request.destroy(); | ||||||
|  |       // Clear timeout on error | ||||||
|  |       if (timeoutId) { | ||||||
|  |         clearTimeout(timeoutId); | ||||||
|  |         timeoutId = null; | ||||||
|  |       } | ||||||
|       done.reject(e); |       done.reject(e); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     // Get response and handle response errors |     // Get response and handle response errors | ||||||
|     const response = await done.promise; |     const response = await done.promise; | ||||||
|  |      | ||||||
|  |     // Clear timeout on successful response | ||||||
|  |     if (timeoutId) { | ||||||
|  |       clearTimeout(timeoutId); | ||||||
|  |       timeoutId = null; | ||||||
|  |     } | ||||||
|  |      | ||||||
|     response.on('error', (err) => { |     response.on('error', (err) => { | ||||||
|       console.error(err); |       console.error(err); | ||||||
|       response.destroy(); |       response.destroy(); | ||||||
|   | |||||||
| @@ -16,9 +16,6 @@ export interface IExtendedIncomingMessage<T = any> | |||||||
|  * Node.js specific response extensions |  * Node.js specific response extensions | ||||||
|  */ |  */ | ||||||
| export interface INodeResponse<T = any> extends baseTypes.ICoreResponse<T> { | export interface INodeResponse<T = any> extends baseTypes.ICoreResponse<T> { | ||||||
|   // Node.js specific methods |  | ||||||
|   streamNode(): NodeJS.ReadableStream; // Returns Node.js style stream |  | ||||||
|  |  | ||||||
|   // Legacy compatibility |   // Legacy compatibility | ||||||
|   raw(): plugins.http.IncomingMessage; |   raw(): plugins.http.IncomingMessage; | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user