BREAKING CHANGE(client/streaming): Unify streaming APIs: remove raw()/streamNode() and standardize on web ReadableStream across runtimes

This commit is contained in:
2025-11-17 14:18:58 +00:00
parent 1305b92ebe
commit 0cf48b3688
15 changed files with 1273 additions and 1524 deletions

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartrequest',
version: '4.4.2',
version: '5.0.0',
description: 'A module for modern HTTP/HTTPS requests with support for form data, file uploads, JSON, binary data, streams, and more.'
}

View File

@@ -8,7 +8,6 @@ import type {
ResponseType,
FormField,
RateLimitConfig,
RawStreamFunction,
} from './types/common.js';
import {
type TPaginationConfig,
@@ -142,12 +141,12 @@ export class SmartRequest<T = any> {
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
@@ -157,18 +156,7 @@ export class SmartRequest<T = any> {
// 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;
}
@@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
}
/**