diff --git a/ts/core_base/response.ts b/ts/core_base/response.ts index 5b7d22b..b6cf45e 100644 --- a/ts/core_base/response.ts +++ b/ts/core_base/response.ts @@ -37,4 +37,9 @@ export abstract class CoreResponse implements types.ICoreResponse { * Get response as ArrayBuffer */ abstract arrayBuffer(): Promise; + + /** + * Get response as a web-style ReadableStream + */ + abstract stream(): ReadableStream | null; } \ No newline at end of file diff --git a/ts/core_base/types.ts b/ts/core_base/types.ts index f00bc9e..d75367c 100644 --- a/ts/core_base/types.ts +++ b/ts/core_base/types.ts @@ -77,4 +77,5 @@ export interface ICoreResponse { json(): Promise; text(): Promise; arrayBuffer(): Promise; + stream(): ReadableStream | null; // Always returns web-style stream } \ No newline at end of file diff --git a/ts/core_fetch/response.ts b/ts/core_fetch/response.ts index d770978..8ea8b93 100644 --- a/ts/core_fetch/response.ts +++ b/ts/core_fetch/response.ts @@ -69,6 +69,13 @@ export class CoreResponse extends AbstractCoreResponse implements ty 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 */ diff --git a/ts/core_fetch/types.ts b/ts/core_fetch/types.ts index 212a213..3f17973 100644 --- a/ts/core_fetch/types.ts +++ b/ts/core_fetch/types.ts @@ -7,8 +7,8 @@ export * from '../core_base/types.js'; * Fetch-specific response extensions */ export interface IFetchResponse extends baseTypes.ICoreResponse { - // Fetch-specific methods - stream(): ReadableStream | null; + // Node.js stream method that throws in browser + streamNode(): never; // Access to raw Response object raw(): Response; diff --git a/ts/core_node/response.ts b/ts/core_node/response.ts index 7337577..6a3f12e 100644 --- a/ts/core_node/response.ts +++ b/ts/core_node/response.ts @@ -84,9 +84,44 @@ export class CoreResponse extends AbstractCoreResponse implements ty } /** - * Get response as a readable stream + * Get response as a web-style ReadableStream */ - stream(): NodeJS.ReadableStream { + stream(): ReadableStream | null { + this.ensureNotConsumed(); + + // Convert Node.js stream to web stream + // In Node.js 16.5+ we can use Readable.toWeb() + 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({ + 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; } diff --git a/ts/core_node/types.ts b/ts/core_node/types.ts index d66b4ce..7ab822f 100644 --- a/ts/core_node/types.ts +++ b/ts/core_node/types.ts @@ -16,7 +16,7 @@ export interface IExtendedIncomingMessage extends plugins.http.Incoming */ export interface INodeResponse extends baseTypes.ICoreResponse { // Node.js specific methods - stream(): NodeJS.ReadableStream; + streamNode(): NodeJS.ReadableStream; // Returns Node.js style stream // Legacy compatibility raw(): plugins.http.IncomingMessage;