fix(client): Fix CI configuration, prevent socket hangs with auto-drain, and apply various client/core TypeScript fixes and test updates
This commit is contained in:
@@ -5,7 +5,10 @@ import { CoreResponse as AbstractCoreResponse } from '../core_base/response.js';
|
||||
/**
|
||||
* Node.js implementation of Core Response class that provides a fetch-like API
|
||||
*/
|
||||
export class CoreResponse<T = any> extends AbstractCoreResponse<T> implements types.INodeResponse<T> {
|
||||
export class CoreResponse<T = any>
|
||||
extends AbstractCoreResponse<T>
|
||||
implements types.INodeResponse<T>
|
||||
{
|
||||
private incomingMessage: plugins.http.IncomingMessage;
|
||||
private bodyBufferPromise: Promise<Buffer> | null = null;
|
||||
private _autoDrainTimeout: NodeJS.Immediate | null = null;
|
||||
@@ -17,7 +20,11 @@ export class CoreResponse<T = any> extends AbstractCoreResponse<T> implements ty
|
||||
public readonly headers: plugins.http.IncomingHttpHeaders;
|
||||
public readonly url: string;
|
||||
|
||||
constructor(incomingMessage: plugins.http.IncomingMessage, url: string, options: types.ICoreRequestOptions = {}) {
|
||||
constructor(
|
||||
incomingMessage: plugins.http.IncomingMessage,
|
||||
url: string,
|
||||
options: types.ICoreRequestOptions = {},
|
||||
) {
|
||||
super();
|
||||
this.incomingMessage = incomingMessage;
|
||||
this.url = url;
|
||||
@@ -25,14 +32,16 @@ export class CoreResponse<T = any> extends AbstractCoreResponse<T> implements ty
|
||||
this.statusText = incomingMessage.statusMessage || '';
|
||||
this.ok = this.status >= 200 && this.status < 300;
|
||||
this.headers = incomingMessage.headers;
|
||||
|
||||
|
||||
// Auto-drain unconsumed streams to prevent socket hanging
|
||||
// This prevents keep-alive sockets from timing out when response bodies aren't consumed
|
||||
// Default to true if not specified
|
||||
if (options.autoDrain !== false) {
|
||||
this._autoDrainTimeout = setImmediate(() => {
|
||||
if (!this.consumed && !this.incomingMessage.readableEnded) {
|
||||
console.log(`Auto-draining unconsumed response body for ${this.url} (status: ${this.status})`);
|
||||
console.log(
|
||||
`Auto-draining unconsumed response body for ${this.url} (status: ${this.status})`,
|
||||
);
|
||||
this.incomingMessage.resume(); // Drain without processing
|
||||
}
|
||||
});
|
||||
@@ -48,7 +57,7 @@ export class CoreResponse<T = any> extends AbstractCoreResponse<T> implements ty
|
||||
clearImmediate(this._autoDrainTimeout);
|
||||
this._autoDrainTimeout = null;
|
||||
}
|
||||
|
||||
|
||||
super.ensureNotConsumed();
|
||||
}
|
||||
|
||||
@@ -57,22 +66,22 @@ export class CoreResponse<T = any> extends AbstractCoreResponse<T> implements ty
|
||||
*/
|
||||
private async collectBody(): Promise<Buffer> {
|
||||
this.ensureNotConsumed();
|
||||
|
||||
|
||||
if (this.bodyBufferPromise) {
|
||||
return this.bodyBufferPromise;
|
||||
}
|
||||
|
||||
this.bodyBufferPromise = new Promise<Buffer>((resolve, reject) => {
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
|
||||
this.incomingMessage.on('data', (chunk: Buffer) => {
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
|
||||
this.incomingMessage.on('end', () => {
|
||||
resolve(Buffer.concat(chunks));
|
||||
});
|
||||
|
||||
|
||||
this.incomingMessage.on('error', reject);
|
||||
});
|
||||
|
||||
@@ -85,7 +94,7 @@ export class CoreResponse<T = any> extends AbstractCoreResponse<T> implements ty
|
||||
async json(): Promise<T> {
|
||||
const buffer = await this.collectBody();
|
||||
const text = buffer.toString('utf-8');
|
||||
|
||||
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (error) {
|
||||
@@ -106,7 +115,10 @@ export class CoreResponse<T = any> extends AbstractCoreResponse<T> implements ty
|
||||
*/
|
||||
async arrayBuffer(): Promise<ArrayBuffer> {
|
||||
const buffer = await this.collectBody();
|
||||
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
||||
return buffer.buffer.slice(
|
||||
buffer.byteOffset,
|
||||
buffer.byteOffset + buffer.byteLength,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,13 +126,13 @@ export class CoreResponse<T = any> extends AbstractCoreResponse<T> implements ty
|
||||
*/
|
||||
stream(): ReadableStream<Uint8Array> | 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<Uint8Array>({
|
||||
@@ -128,22 +140,22 @@ export class CoreResponse<T = any> extends AbstractCoreResponse<T> implements ty
|
||||
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
|
||||
*/
|
||||
@@ -158,5 +170,4 @@ export class CoreResponse<T = any> extends AbstractCoreResponse<T> implements ty
|
||||
raw(): plugins.http.IncomingMessage {
|
||||
return this.incomingMessage;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user