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:
@@ -3,14 +3,19 @@ import type { ICoreResponse } from '../core_base/types.js';
|
||||
import * as plugins from './plugins.js';
|
||||
import type { ICoreRequestOptions } from '../core_base/types.js';
|
||||
|
||||
import type { HttpMethod, ResponseType, FormField, RateLimitConfig } from './types/common.js';
|
||||
import type {
|
||||
HttpMethod,
|
||||
ResponseType,
|
||||
FormField,
|
||||
RateLimitConfig,
|
||||
} from './types/common.js';
|
||||
import {
|
||||
type TPaginationConfig,
|
||||
PaginationStrategy,
|
||||
type OffsetPaginationConfig,
|
||||
type CursorPaginationConfig,
|
||||
type CustomPaginationConfig,
|
||||
type TPaginatedResponse
|
||||
type TPaginatedResponse,
|
||||
} from './types/pagination.js';
|
||||
import { createPaginatedResponse } from './features/pagination.js';
|
||||
|
||||
@@ -22,21 +27,21 @@ import { createPaginatedResponse } from './features/pagination.js';
|
||||
function parseRetryAfter(retryAfter: string | string[]): number {
|
||||
// Handle array of values (take first)
|
||||
const value = Array.isArray(retryAfter) ? retryAfter[0] : retryAfter;
|
||||
|
||||
|
||||
if (!value) return 0;
|
||||
|
||||
|
||||
// Try to parse as seconds (number)
|
||||
const seconds = parseInt(value, 10);
|
||||
if (!isNaN(seconds)) {
|
||||
return seconds * 1000;
|
||||
}
|
||||
|
||||
|
||||
// Try to parse as HTTP date
|
||||
const retryDate = new Date(value);
|
||||
if (!isNaN(retryDate.getTime())) {
|
||||
return Math.max(0, retryDate.getTime() - Date.now());
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -96,7 +101,7 @@ export class SmartRequest<T = any> {
|
||||
if (Buffer.isBuffer(item.value)) {
|
||||
form.append(item.name, item.value, {
|
||||
filename: item.filename || 'file',
|
||||
contentType: item.contentType || 'application/octet-stream'
|
||||
contentType: item.contentType || 'application/octet-stream',
|
||||
});
|
||||
} else {
|
||||
form.append(item.name, item.value);
|
||||
@@ -109,7 +114,7 @@ export class SmartRequest<T = any> {
|
||||
|
||||
this._options.headers = {
|
||||
...this._options.headers,
|
||||
...form.getHeaders()
|
||||
...form.getHeaders(),
|
||||
};
|
||||
|
||||
this._options.requestBody = form;
|
||||
@@ -143,7 +148,7 @@ export class SmartRequest<T = any> {
|
||||
maxWaitTime: config?.maxWaitTime ?? 60000,
|
||||
fallbackDelay: config?.fallbackDelay ?? 1000,
|
||||
backoffFactor: config?.backoffFactor ?? 2,
|
||||
onRateLimit: config?.onRateLimit
|
||||
onRateLimit: config?.onRateLimit,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
@@ -157,7 +162,7 @@ export class SmartRequest<T = any> {
|
||||
}
|
||||
this._options.headers = {
|
||||
...this._options.headers,
|
||||
...headers
|
||||
...headers,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
@@ -179,7 +184,7 @@ export class SmartRequest<T = any> {
|
||||
query(params: Record<string, string>): this {
|
||||
this._queryParams = {
|
||||
...this._queryParams,
|
||||
...params
|
||||
...params,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
@@ -190,7 +195,7 @@ export class SmartRequest<T = any> {
|
||||
options(options: Partial<ICoreRequestOptions>): this {
|
||||
this._options = {
|
||||
...this._options,
|
||||
...options
|
||||
...options,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
@@ -210,12 +215,12 @@ export class SmartRequest<T = any> {
|
||||
accept(type: ResponseType): this {
|
||||
// Map response types to Accept header values
|
||||
const acceptHeaders: Record<ResponseType, string> = {
|
||||
'json': 'application/json',
|
||||
'text': 'text/plain',
|
||||
'binary': 'application/octet-stream',
|
||||
'stream': '*/*'
|
||||
json: 'application/json',
|
||||
text: 'text/plain',
|
||||
binary: 'application/octet-stream',
|
||||
stream: '*/*',
|
||||
};
|
||||
|
||||
|
||||
return this.header('Accept', acceptHeaders[type]);
|
||||
}
|
||||
|
||||
@@ -230,20 +235,26 @@ export class SmartRequest<T = any> {
|
||||
/**
|
||||
* Configure offset-based pagination (page & limit)
|
||||
*/
|
||||
withOffsetPagination(config: Omit<OffsetPaginationConfig, 'strategy'> = {}): this {
|
||||
withOffsetPagination(
|
||||
config: Omit<OffsetPaginationConfig, 'strategy'> = {},
|
||||
): this {
|
||||
this._paginationConfig = {
|
||||
strategy: PaginationStrategy.OFFSET,
|
||||
pageParam: config.pageParam || 'page',
|
||||
limitParam: config.limitParam || 'limit',
|
||||
startPage: config.startPage || 1,
|
||||
pageSize: config.pageSize || 20,
|
||||
totalPath: config.totalPath || 'total'
|
||||
totalPath: config.totalPath || 'total',
|
||||
};
|
||||
|
||||
// Add initial pagination parameters
|
||||
this.query({
|
||||
[this._paginationConfig.pageParam]: String(this._paginationConfig.startPage),
|
||||
[this._paginationConfig.limitParam]: String(this._paginationConfig.pageSize)
|
||||
[this._paginationConfig.pageParam]: String(
|
||||
this._paginationConfig.startPage,
|
||||
),
|
||||
[this._paginationConfig.limitParam]: String(
|
||||
this._paginationConfig.pageSize,
|
||||
),
|
||||
});
|
||||
|
||||
return this;
|
||||
@@ -252,12 +263,14 @@ export class SmartRequest<T = any> {
|
||||
/**
|
||||
* Configure cursor-based pagination
|
||||
*/
|
||||
withCursorPagination(config: Omit<CursorPaginationConfig, 'strategy'> = {}): this {
|
||||
withCursorPagination(
|
||||
config: Omit<CursorPaginationConfig, 'strategy'> = {},
|
||||
): this {
|
||||
this._paginationConfig = {
|
||||
strategy: PaginationStrategy.CURSOR,
|
||||
cursorParam: config.cursorParam || 'cursor',
|
||||
cursorPath: config.cursorPath || 'nextCursor',
|
||||
hasMorePath: config.hasMorePath || 'hasMore'
|
||||
hasMorePath: config.hasMorePath || 'hasMore',
|
||||
};
|
||||
return this;
|
||||
}
|
||||
@@ -267,7 +280,7 @@ export class SmartRequest<T = any> {
|
||||
*/
|
||||
withLinkPagination(): this {
|
||||
this._paginationConfig = {
|
||||
strategy: PaginationStrategy.LINK_HEADER
|
||||
strategy: PaginationStrategy.LINK_HEADER,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
@@ -279,7 +292,7 @@ export class SmartRequest<T = any> {
|
||||
this._paginationConfig = {
|
||||
strategy: PaginationStrategy.CUSTOM,
|
||||
hasNextPage: config.hasNextPage,
|
||||
getNextPageParams: config.getNextPageParams
|
||||
getNextPageParams: config.getNextPageParams,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
@@ -324,7 +337,9 @@ export class SmartRequest<T = any> {
|
||||
*/
|
||||
async getPaginated<ItemType = T>(): Promise<TPaginatedResponse<ItemType>> {
|
||||
if (!this._paginationConfig) {
|
||||
throw new Error('Pagination not configured. Call one of the pagination methods first.');
|
||||
throw new Error(
|
||||
'Pagination not configured. Call one of the pagination methods first.',
|
||||
);
|
||||
}
|
||||
|
||||
// Default to GET if no method specified
|
||||
@@ -345,7 +360,7 @@ export class SmartRequest<T = any> {
|
||||
nextClient._queryParams = nextPageParams;
|
||||
|
||||
return nextClient.getPaginated<ItemType>();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -375,8 +390,8 @@ export class SmartRequest<T = any> {
|
||||
for (let attempt = 0; attempt <= this._retries; attempt++) {
|
||||
try {
|
||||
const request = new CoreRequest(this._url, this._options as any);
|
||||
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
|
||||
if (this._rateLimitConfig && response.status === 429) {
|
||||
if (rateLimitAttempt >= this._rateLimitConfig.maxRetries) {
|
||||
@@ -385,18 +400,22 @@ export class SmartRequest<T = any> {
|
||||
}
|
||||
|
||||
let waitTime: number;
|
||||
|
||||
if (this._rateLimitConfig.respectRetryAfter && response.headers['retry-after']) {
|
||||
|
||||
if (
|
||||
this._rateLimitConfig.respectRetryAfter &&
|
||||
response.headers['retry-after']
|
||||
) {
|
||||
// Parse Retry-After header
|
||||
waitTime = parseRetryAfter(response.headers['retry-after']);
|
||||
|
||||
|
||||
// Cap wait time to maxWaitTime
|
||||
waitTime = Math.min(waitTime, this._rateLimitConfig.maxWaitTime);
|
||||
} else {
|
||||
// Use exponential backoff
|
||||
waitTime = Math.min(
|
||||
this._rateLimitConfig.fallbackDelay * Math.pow(this._rateLimitConfig.backoffFactor, rateLimitAttempt),
|
||||
this._rateLimitConfig.maxWaitTime
|
||||
this._rateLimitConfig.fallbackDelay *
|
||||
Math.pow(this._rateLimitConfig.backoffFactor, rateLimitAttempt),
|
||||
this._rateLimitConfig.maxWaitTime,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -406,14 +425,14 @@ export class SmartRequest<T = any> {
|
||||
}
|
||||
|
||||
// Wait before retrying
|
||||
await new Promise(resolve => setTimeout(resolve, waitTime));
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
||||
|
||||
rateLimitAttempt++;
|
||||
// Decrement attempt to retry this attempt
|
||||
attempt--;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Success or non-429 error response
|
||||
return response;
|
||||
} catch (error) {
|
||||
@@ -425,11 +444,11 @@ export class SmartRequest<T = any> {
|
||||
}
|
||||
|
||||
// Otherwise, wait before retrying
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
|
||||
// This should never be reached due to the throw in the loop above
|
||||
throw lastError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user