apify-client 3.0.0-beta.6 → 3.0.0-beta.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -2
- package/dist/apify_api_error.d.ts +11 -26
- package/dist/apify_api_error.js +31 -22
- package/dist/apify_client.d.ts +125 -10
- package/dist/apify_client.js +133 -48
- package/dist/base/api_client.d.ts +7 -7
- package/dist/base/api_client.js +36 -36
- package/dist/base/resource_client.d.ts +21 -8
- package/dist/base/resource_client.js +67 -33
- package/dist/base/resource_collection_client.d.ts +12 -4
- package/dist/base/resource_collection_client.js +34 -11
- package/dist/body_parser.d.ts +3 -4
- package/dist/body_parser.js +19 -26
- package/dist/bundle.js +27 -22
- package/dist/bundle.js.map +1 -1
- package/dist/generated/api.d.ts +785 -290
- package/dist/generated/schemas.d.ts +771 -798
- package/dist/generated/schemas.js +183 -179
- package/dist/http_clients/axios.d.ts +60 -0
- package/dist/http_clients/axios.js +117 -0
- package/dist/http_clients/base.d.ts +331 -0
- package/dist/http_clients/base.js +563 -0
- package/dist/http_clients/index.d.ts +4 -0
- package/dist/http_clients/index.js +2 -0
- package/dist/http_compressors/base.d.ts +40 -0
- package/dist/http_compressors/base.js +9 -0
- package/dist/http_compressors/brotli.d.ts +33 -0
- package/dist/http_compressors/brotli.js +39 -0
- package/dist/http_compressors/gzip.d.ts +33 -0
- package/dist/http_compressors/gzip.js +39 -0
- package/dist/http_compressors/index.d.ts +6 -0
- package/dist/http_compressors/index.js +2 -0
- package/dist/http_compressors/resolve.d.ts +23 -0
- package/dist/http_compressors/resolve.js +27 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/invalid_response_body_error.d.ts +13 -0
- package/dist/invalid_response_body_error.js +17 -0
- package/dist/models.d.ts +162 -194
- package/dist/models.js +6 -4
- package/dist/resource_clients/actor.d.ts +74 -34
- package/dist/resource_clients/actor.js +87 -56
- package/dist/resource_clients/actor_collection.d.ts +18 -14
- package/dist/resource_clients/actor_collection.js +11 -3
- package/dist/resource_clients/actor_env_var.d.ts +11 -4
- package/dist/resource_clients/actor_env_var.js +16 -6
- package/dist/resource_clients/actor_env_var_collection.d.ts +16 -20
- package/dist/resource_clients/actor_env_var_collection.js +17 -11
- package/dist/resource_clients/actor_version.d.ts +20 -5
- package/dist/resource_clients/actor_version.js +19 -9
- package/dist/resource_clients/actor_version_collection.d.ts +18 -19
- package/dist/resource_clients/actor_version_collection.js +19 -11
- package/dist/resource_clients/build.d.ts +20 -7
- package/dist/resource_clients/build.js +35 -15
- package/dist/resource_clients/build_collection.d.ts +7 -5
- package/dist/resource_clients/build_collection.js +4 -1
- package/dist/resource_clients/dataset.d.ts +53 -35
- package/dist/resource_clients/dataset.js +105 -80
- package/dist/resource_clients/dataset_collection.d.ts +10 -6
- package/dist/resource_clients/dataset_collection.js +13 -2
- package/dist/resource_clients/key_value_store.d.ts +73 -23
- package/dist/resource_clients/key_value_store.js +86 -43
- package/dist/resource_clients/key_value_store_collection.d.ts +10 -6
- package/dist/resource_clients/key_value_store_collection.js +13 -2
- package/dist/resource_clients/log.d.ts +13 -20
- package/dist/resource_clients/log.js +141 -59
- package/dist/resource_clients/request_queue.d.ts +74 -36
- package/dist/resource_clients/request_queue.js +182 -106
- package/dist/resource_clients/request_queue_collection.d.ts +9 -5
- package/dist/resource_clients/request_queue_collection.js +9 -3
- package/dist/resource_clients/run.d.ts +119 -28
- package/dist/resource_clients/run.js +190 -49
- package/dist/resource_clients/run_collection.d.ts +5 -3
- package/dist/resource_clients/run_collection.js +4 -1
- package/dist/resource_clients/schedule.d.ts +16 -7
- package/dist/resource_clients/schedule.js +30 -22
- package/dist/resource_clients/schedule_collection.d.ts +6 -2
- package/dist/resource_clients/schedule_collection.js +9 -3
- package/dist/resource_clients/store_collection.d.ts +6 -4
- package/dist/resource_clients/store_collection.js +4 -1
- package/dist/resource_clients/task.d.ts +36 -13
- package/dist/resource_clients/task.js +79 -46
- package/dist/resource_clients/task_collection.d.ts +9 -5
- package/dist/resource_clients/task_collection.js +9 -3
- package/dist/resource_clients/user.d.ts +19 -10
- package/dist/resource_clients/user.js +42 -38
- package/dist/resource_clients/webhook.d.ts +17 -8
- package/dist/resource_clients/webhook.js +30 -22
- package/dist/resource_clients/webhook_collection.d.ts +8 -4
- package/dist/resource_clients/webhook_collection.js +9 -3
- package/dist/resource_clients/webhook_dispatch.d.ts +4 -1
- package/dist/resource_clients/webhook_dispatch.js +7 -2
- package/dist/resource_clients/webhook_dispatch_collection.d.ts +3 -1
- package/dist/resource_clients/webhook_dispatch_collection.js +4 -1
- package/dist/runtime/node.d.ts +6 -0
- package/dist/runtime/node.js +49 -0
- package/dist/runtime/types.d.ts +52 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/web.d.ts +5 -0
- package/dist/runtime/web.js +18 -0
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.js +1 -1
- package/dist/statistics.d.ts +1 -6
- package/dist/statistics.js +2 -2
- package/dist/timeouts.d.ts +84 -0
- package/dist/timeouts.js +37 -0
- package/dist/timezones.d.ts +1 -1
- package/dist/utils.d.ts +89 -53
- package/dist/utils.js +214 -161
- package/dist/wait_for_resources.d.ts +8 -0
- package/dist/wait_for_resources.js +39 -0
- package/package.json +32 -29
- package/dist/http_client.d.ts +0 -88
- package/dist/http_client.js +0 -255
- package/dist/interceptors.d.ts +0 -19
- package/dist/interceptors.js +0 -105
package/dist/http_client.d.ts
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import type http from 'node:http';
|
|
2
|
-
import type https from 'node:https';
|
|
3
|
-
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
|
4
|
-
import type { Log } from '@apify/log';
|
|
5
|
-
import type { RequestInterceptorFunction } from './interceptors.js';
|
|
6
|
-
import type { Statistics } from './statistics.js';
|
|
7
|
-
export declare class HttpClient {
|
|
8
|
-
stats: Statistics;
|
|
9
|
-
maxRetries: number;
|
|
10
|
-
minDelayBetweenRetriesMillis: number;
|
|
11
|
-
userProvidedRequestInterceptors: RequestInterceptorFunction[];
|
|
12
|
-
logger: Log;
|
|
13
|
-
timeoutMillis: number;
|
|
14
|
-
httpAgent?: http.Agent;
|
|
15
|
-
httpsAgent?: https.Agent;
|
|
16
|
-
axios: AxiosInstance;
|
|
17
|
-
workflowKey?: string;
|
|
18
|
-
private nodeInitPromise?;
|
|
19
|
-
private userAgentSuffix?;
|
|
20
|
-
constructor(options: HttpClientOptions);
|
|
21
|
-
private ensureNodeInit;
|
|
22
|
-
private initNode;
|
|
23
|
-
call<T = any>(config: ApifyRequestConfig): Promise<ApifyResponse<T>>;
|
|
24
|
-
private _informAboutStreamNoRetry;
|
|
25
|
-
/**
|
|
26
|
-
* Successful responses are returned, errors and unsuccessful
|
|
27
|
-
* status codes are retried. See the following functions for the
|
|
28
|
-
* retrying logic.
|
|
29
|
-
*/
|
|
30
|
-
private _createRequestHandler;
|
|
31
|
-
private _isStatusOk;
|
|
32
|
-
/**
|
|
33
|
-
* Handles all unexpected errors that can happen, but are not
|
|
34
|
-
* Apify API typed errors. E.g. network errors, timeouts and so on.
|
|
35
|
-
*/
|
|
36
|
-
private _handleRequestError;
|
|
37
|
-
/**
|
|
38
|
-
* Axios calls req.abort() on timeouts so timeout errors will
|
|
39
|
-
* have a code ECONNABORTED.
|
|
40
|
-
*/
|
|
41
|
-
private _isTimeoutError;
|
|
42
|
-
/**
|
|
43
|
-
* We don't want to retry every exception thrown from Axios.
|
|
44
|
-
* The common denominator for retryable errors are network issues.
|
|
45
|
-
* @param {Error} err
|
|
46
|
-
* @private
|
|
47
|
-
*/
|
|
48
|
-
private _isRetryableError;
|
|
49
|
-
/**
|
|
50
|
-
* When a network connection to our API is interrupted in the middle of streaming
|
|
51
|
-
* a response, the request often does not fail, but simply contains
|
|
52
|
-
* an incomplete response. This can often be fixed by retrying.
|
|
53
|
-
*/
|
|
54
|
-
private _isResponseBodyInvalid;
|
|
55
|
-
/**
|
|
56
|
-
* When a network request is attempted by axios and fails,
|
|
57
|
-
* it throws an AxiosError, which will have the request
|
|
58
|
-
* and config (and other) properties.
|
|
59
|
-
*/
|
|
60
|
-
private _isNetworkError;
|
|
61
|
-
/**
|
|
62
|
-
* We retry 429 (rate limit) and 500+.
|
|
63
|
-
* For status codes 300-499 (except 429) we do not retry the request,
|
|
64
|
-
* because it's probably caused by invalid url (redirect 3xx) or invalid user input (4xx).
|
|
65
|
-
*/
|
|
66
|
-
private _isStatusCodeRetryable;
|
|
67
|
-
private _onRequestRetry;
|
|
68
|
-
}
|
|
69
|
-
export interface ApifyRequestConfig extends AxiosRequestConfig {
|
|
70
|
-
stringifyFunctions?: boolean;
|
|
71
|
-
forceBuffer?: boolean;
|
|
72
|
-
doNotRetryTimeouts?: boolean;
|
|
73
|
-
}
|
|
74
|
-
export interface ApifyResponse<T = any> extends AxiosResponse<T> {
|
|
75
|
-
config: ApifyRequestConfig & InternalAxiosRequestConfig;
|
|
76
|
-
}
|
|
77
|
-
export interface HttpClientOptions {
|
|
78
|
-
apifyClientStats: Statistics;
|
|
79
|
-
maxRetries: number;
|
|
80
|
-
minDelayBetweenRetriesMillis: number;
|
|
81
|
-
requestInterceptors: RequestInterceptorFunction[];
|
|
82
|
-
timeoutSecs: number;
|
|
83
|
-
logger: Log;
|
|
84
|
-
token?: string;
|
|
85
|
-
workflowKey?: string;
|
|
86
|
-
/** @internal */
|
|
87
|
-
userAgentSuffix?: string | string[];
|
|
88
|
-
}
|
package/dist/http_client.js
DELETED
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
import retry from 'async-retry';
|
|
2
|
-
import axios, { AxiosHeaders } from 'axios';
|
|
3
|
-
import { APIFY_ENV_VARS } from '@apify/consts';
|
|
4
|
-
import { ApifyApiError } from './apify_api_error.js';
|
|
5
|
-
import { InvalidResponseBodyError, requestInterceptors, responseInterceptors } from './interceptors.js';
|
|
6
|
-
import { asArray, cast, getVersionData, isNode, isStream } from './utils.js';
|
|
7
|
-
const { version } = getVersionData();
|
|
8
|
-
const RATE_LIMIT_EXCEEDED_STATUS_CODE = 429;
|
|
9
|
-
export class HttpClient {
|
|
10
|
-
stats;
|
|
11
|
-
maxRetries;
|
|
12
|
-
minDelayBetweenRetriesMillis;
|
|
13
|
-
userProvidedRequestInterceptors;
|
|
14
|
-
logger;
|
|
15
|
-
timeoutMillis;
|
|
16
|
-
httpAgent;
|
|
17
|
-
httpsAgent;
|
|
18
|
-
axios;
|
|
19
|
-
workflowKey;
|
|
20
|
-
nodeInitPromise;
|
|
21
|
-
userAgentSuffix;
|
|
22
|
-
constructor(options) {
|
|
23
|
-
const { token } = options;
|
|
24
|
-
this.stats = options.apifyClientStats;
|
|
25
|
-
this.maxRetries = options.maxRetries;
|
|
26
|
-
this.minDelayBetweenRetriesMillis = options.minDelayBetweenRetriesMillis;
|
|
27
|
-
this.userProvidedRequestInterceptors = options.requestInterceptors;
|
|
28
|
-
this.timeoutMillis = options.timeoutSecs * 1000;
|
|
29
|
-
this.logger = options.logger;
|
|
30
|
-
this.workflowKey = options.workflowKey || process.env[APIFY_ENV_VARS.WORKFLOW_KEY];
|
|
31
|
-
this.userAgentSuffix = options.userAgentSuffix;
|
|
32
|
-
this._onRequestRetry = this._onRequestRetry.bind(this);
|
|
33
|
-
this.axios = axios.create({
|
|
34
|
-
// Disable axios's built-in proxy handling since we're using custom agents
|
|
35
|
-
proxy: false,
|
|
36
|
-
paramsSerializer: (params) => {
|
|
37
|
-
const formattedParams = Object.entries(params)
|
|
38
|
-
.filter(([, value]) => value !== undefined)
|
|
39
|
-
.map(([key, value]) => {
|
|
40
|
-
if (value instanceof Date) {
|
|
41
|
-
return [key, value.toISOString()];
|
|
42
|
-
}
|
|
43
|
-
const updatedValue = typeof value === 'boolean' ? Number(value) : value;
|
|
44
|
-
return [key, String(updatedValue)];
|
|
45
|
-
});
|
|
46
|
-
return new URLSearchParams(formattedParams).toString();
|
|
47
|
-
},
|
|
48
|
-
validateStatus: null,
|
|
49
|
-
// Using interceptors for this functionality.
|
|
50
|
-
transformRequest: undefined,
|
|
51
|
-
transformResponse: undefined,
|
|
52
|
-
responseType: 'arraybuffer',
|
|
53
|
-
timeout: this.timeoutMillis,
|
|
54
|
-
// maxBodyLength needs to be Infinity, because -1 falls back to a 10 MB default
|
|
55
|
-
// from an axios subdependency - 'follow-redirects'
|
|
56
|
-
maxBodyLength: Infinity,
|
|
57
|
-
// maxContentLength must be -1, because Infinity will cause axios to run super slow
|
|
58
|
-
// thanks to a bug that's now fixed, but not released yet https://github.com/axios/axios/pull/3738
|
|
59
|
-
maxContentLength: -1,
|
|
60
|
-
});
|
|
61
|
-
// Clean all default headers because they only make a mess and their merging is difficult to understand and buggy.
|
|
62
|
-
this.axios.defaults.headers = new AxiosHeaders();
|
|
63
|
-
// If workflow key is available, pass it as a header
|
|
64
|
-
if (this.workflowKey) {
|
|
65
|
-
this.axios.defaults.headers['X-Apify-Workflow-Key'] = this.workflowKey;
|
|
66
|
-
}
|
|
67
|
-
// Attach Authorization header for all requests if token was provided
|
|
68
|
-
if (token) {
|
|
69
|
-
this.axios.defaults.headers.Authorization = `Bearer ${token}`;
|
|
70
|
-
}
|
|
71
|
-
requestInterceptors.forEach((i) => this.axios.interceptors.request.use(i));
|
|
72
|
-
this.userProvidedRequestInterceptors.forEach((i) => this.axios.interceptors.request.use(i));
|
|
73
|
-
responseInterceptors.forEach((i) => this.axios.interceptors.response.use(i));
|
|
74
|
-
}
|
|
75
|
-
async ensureNodeInit() {
|
|
76
|
-
if (!isNode())
|
|
77
|
-
return;
|
|
78
|
-
this.nodeInitPromise ??= this.initNode();
|
|
79
|
-
return this.nodeInitPromise;
|
|
80
|
-
}
|
|
81
|
-
async initNode() {
|
|
82
|
-
if (!isNode())
|
|
83
|
-
return;
|
|
84
|
-
const [{ ProxyAgent }, os] = await Promise.all([import('proxy-agent'), import('node:os')]);
|
|
85
|
-
// We want to keep sockets alive for better performance.
|
|
86
|
-
// Enhanced agent configuration based on agentkeepalive best practices:
|
|
87
|
-
// - Nagle's algorithm disabled for lower latency
|
|
88
|
-
// - Free socket timeout to prevent socket leaks
|
|
89
|
-
// - LIFO scheduling to reuse recent sockets
|
|
90
|
-
// - Socket TTL for connection freshness
|
|
91
|
-
const agentOptions = {
|
|
92
|
-
keepAlive: true,
|
|
93
|
-
// Timeout for inactive sockets
|
|
94
|
-
// Prevents socket leaks from idle connections
|
|
95
|
-
timeout: this.timeoutMillis,
|
|
96
|
-
// Keep alive timeout for free sockets (15 seconds)
|
|
97
|
-
// Node.js will close unused sockets after this period
|
|
98
|
-
keepAliveMsecs: 15_000,
|
|
99
|
-
// Maximum number of sockets per host
|
|
100
|
-
maxSockets: 256,
|
|
101
|
-
maxFreeSockets: 256,
|
|
102
|
-
// LIFO scheduling - reuse most recently used sockets for better performance
|
|
103
|
-
scheduling: 'lifo',
|
|
104
|
-
};
|
|
105
|
-
// Use ProxyAgent which automatically detects proxy from environment variables
|
|
106
|
-
// and supports CONNECT tunneling
|
|
107
|
-
const proxyAgent = new ProxyAgent(agentOptions);
|
|
108
|
-
this.httpAgent = proxyAgent;
|
|
109
|
-
this.httpsAgent = proxyAgent;
|
|
110
|
-
// Disable Nagle's algorithm for lower latency
|
|
111
|
-
// This sends data immediately instead of buffering small packets
|
|
112
|
-
const setNoDelay = (socket) => {
|
|
113
|
-
socket.setNoDelay(true);
|
|
114
|
-
};
|
|
115
|
-
this.httpAgent.on('socket', setNoDelay);
|
|
116
|
-
this.httpsAgent.on('socket', setNoDelay);
|
|
117
|
-
this.axios.defaults.httpAgent = this.httpAgent;
|
|
118
|
-
this.axios.defaults.httpsAgent = this.httpsAgent;
|
|
119
|
-
// Works only in Node. Cannot be set in browser
|
|
120
|
-
const isAtHome = !!process.env[APIFY_ENV_VARS.IS_AT_HOME];
|
|
121
|
-
let userAgent = `ApifyClient/${version} (${os.platform()}; Node/${process.version}); isAtHome/${isAtHome}`;
|
|
122
|
-
if (this.userAgentSuffix) {
|
|
123
|
-
userAgent += `; ${asArray(this.userAgentSuffix).join('; ')}`;
|
|
124
|
-
}
|
|
125
|
-
this.axios.defaults.headers['User-Agent'] = userAgent;
|
|
126
|
-
}
|
|
127
|
-
async call(config) {
|
|
128
|
-
await this.ensureNodeInit();
|
|
129
|
-
this.stats.calls++;
|
|
130
|
-
const makeRequest = this._createRequestHandler(config);
|
|
131
|
-
return retry(makeRequest, {
|
|
132
|
-
retries: this.maxRetries,
|
|
133
|
-
minTimeout: this.minDelayBetweenRetriesMillis,
|
|
134
|
-
onRetry: this._onRequestRetry,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
_informAboutStreamNoRetry() {
|
|
138
|
-
this.logger.warningOnce('Request body was a stream - retrying will not work, as part of it was already consumed.');
|
|
139
|
-
this.logger.warningOnce('If you want Apify client to handle retries for you, collect the stream into a buffer before sending it.');
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Successful responses are returned, errors and unsuccessful
|
|
143
|
-
* status codes are retried. See the following functions for the
|
|
144
|
-
* retrying logic.
|
|
145
|
-
*/
|
|
146
|
-
_createRequestHandler(config) {
|
|
147
|
-
const makeRequest = async (stopTrying, attempt) => {
|
|
148
|
-
this.stats.requests++;
|
|
149
|
-
let response;
|
|
150
|
-
const requestIsStream = isStream(config.data);
|
|
151
|
-
try {
|
|
152
|
-
if (requestIsStream) {
|
|
153
|
-
// Handling redirects is not possible without buffering - part of the stream has already been sent and can't be recovered
|
|
154
|
-
// when server sends the redirect. Therefore we need to override this in Axios config to prevent it from buffering the body.
|
|
155
|
-
// see also axios/axios#1045
|
|
156
|
-
config = { ...config, maxRedirects: 0 };
|
|
157
|
-
}
|
|
158
|
-
// Increase timeout with each attempt. Max timeout is bounded by the client timeout.
|
|
159
|
-
config.timeout = Math.min(this.timeoutMillis, (config.timeout ?? this.timeoutMillis) * 2 ** (attempt - 1));
|
|
160
|
-
response = await this.axios.request(config);
|
|
161
|
-
if (this._isStatusOk(response.status))
|
|
162
|
-
return response;
|
|
163
|
-
}
|
|
164
|
-
catch (err) {
|
|
165
|
-
return cast(this._handleRequestError(err, config, stopTrying));
|
|
166
|
-
}
|
|
167
|
-
if (response.status === RATE_LIMIT_EXCEEDED_STATUS_CODE) {
|
|
168
|
-
this.stats.addRateLimitError(attempt);
|
|
169
|
-
}
|
|
170
|
-
const apiError = ApifyApiError.fromResponse(response, attempt);
|
|
171
|
-
if (this._isStatusCodeRetryable(response.status)) {
|
|
172
|
-
if (requestIsStream) {
|
|
173
|
-
this._informAboutStreamNoRetry();
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
// allow a retry
|
|
177
|
-
throw apiError;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
stopTrying(apiError);
|
|
181
|
-
return response;
|
|
182
|
-
};
|
|
183
|
-
return makeRequest;
|
|
184
|
-
}
|
|
185
|
-
_isStatusOk(statusCode) {
|
|
186
|
-
return statusCode < 300;
|
|
187
|
-
}
|
|
188
|
-
/**
|
|
189
|
-
* Handles all unexpected errors that can happen, but are not
|
|
190
|
-
* Apify API typed errors. E.g. network errors, timeouts and so on.
|
|
191
|
-
*/
|
|
192
|
-
_handleRequestError(err, config, stopTrying) {
|
|
193
|
-
if (this._isTimeoutError(err) && config.doNotRetryTimeouts) {
|
|
194
|
-
return stopTrying(err);
|
|
195
|
-
}
|
|
196
|
-
if (this._isRetryableError(err)) {
|
|
197
|
-
if (isStream(config.data)) {
|
|
198
|
-
this._informAboutStreamNoRetry();
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
-
throw err;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
return stopTrying(err);
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Axios calls req.abort() on timeouts so timeout errors will
|
|
208
|
-
* have a code ECONNABORTED.
|
|
209
|
-
*/
|
|
210
|
-
_isTimeoutError(err) {
|
|
211
|
-
return err.code === 'ECONNABORTED';
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* We don't want to retry every exception thrown from Axios.
|
|
215
|
-
* The common denominator for retryable errors are network issues.
|
|
216
|
-
* @param {Error} err
|
|
217
|
-
* @private
|
|
218
|
-
*/
|
|
219
|
-
_isRetryableError(err) {
|
|
220
|
-
return this._isNetworkError(err) || this._isResponseBodyInvalid(err);
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* When a network connection to our API is interrupted in the middle of streaming
|
|
224
|
-
* a response, the request often does not fail, but simply contains
|
|
225
|
-
* an incomplete response. This can often be fixed by retrying.
|
|
226
|
-
*/
|
|
227
|
-
_isResponseBodyInvalid(err) {
|
|
228
|
-
return err instanceof InvalidResponseBodyError;
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* When a network request is attempted by axios and fails,
|
|
232
|
-
* it throws an AxiosError, which will have the request
|
|
233
|
-
* and config (and other) properties.
|
|
234
|
-
*/
|
|
235
|
-
_isNetworkError(err) {
|
|
236
|
-
const hasRequest = err.request && typeof err.request === 'object';
|
|
237
|
-
const hasConfig = err.config && typeof err.config === 'object';
|
|
238
|
-
return hasRequest && hasConfig;
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* We retry 429 (rate limit) and 500+.
|
|
242
|
-
* For status codes 300-499 (except 429) we do not retry the request,
|
|
243
|
-
* because it's probably caused by invalid url (redirect 3xx) or invalid user input (4xx).
|
|
244
|
-
*/
|
|
245
|
-
_isStatusCodeRetryable(statusCode) {
|
|
246
|
-
const isRateLimitError = statusCode === RATE_LIMIT_EXCEEDED_STATUS_CODE;
|
|
247
|
-
const isInternalError = statusCode >= 500;
|
|
248
|
-
return isRateLimitError || isInternalError;
|
|
249
|
-
}
|
|
250
|
-
_onRequestRetry(error, attempt) {
|
|
251
|
-
if (attempt === Math.round(this.maxRetries / 2)) {
|
|
252
|
-
this.logger.warning(`API request failed ${attempt} times. Max attempts: ${this.maxRetries + 1}.\nCause:${error.stack}`);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
package/dist/interceptors.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { AxiosInterceptorManager, AxiosResponse } from 'axios';
|
|
2
|
-
import type { ApifyRequestConfig, ApifyResponse } from './http_client.js';
|
|
3
|
-
/**
|
|
4
|
-
* This error exists for the quite common situation, where only a partial JSON response is received and
|
|
5
|
-
* an attempt to parse the JSON throws an error. In most cases this can be resolved by retrying the
|
|
6
|
-
* request. We do that by identifying this error in HttpClient.
|
|
7
|
-
*
|
|
8
|
-
* The properties mimic AxiosError for easier integration in HttpClient error handling.
|
|
9
|
-
*/
|
|
10
|
-
export declare class InvalidResponseBodyError extends Error {
|
|
11
|
-
code: string;
|
|
12
|
-
response: AxiosResponse;
|
|
13
|
-
cause: Error;
|
|
14
|
-
constructor(response: AxiosResponse, cause: Error);
|
|
15
|
-
}
|
|
16
|
-
export type RequestInterceptorFunction = Parameters<AxiosInterceptorManager<ApifyRequestConfig>['use']>[0];
|
|
17
|
-
export type ResponseInterceptorFunction = Parameters<AxiosInterceptorManager<ApifyResponse>['use']>[0];
|
|
18
|
-
export declare const requestInterceptors: RequestInterceptorFunction[];
|
|
19
|
-
export declare const responseInterceptors: ResponseInterceptorFunction[];
|
package/dist/interceptors.js
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import axios, { AxiosHeaders } from 'axios';
|
|
2
|
-
import contentTypeParser from 'content-type';
|
|
3
|
-
import { maybeParseBody } from './body_parser.js';
|
|
4
|
-
import { isNode, maybeCompressValue } from './utils.js';
|
|
5
|
-
/**
|
|
6
|
-
* This error exists for the quite common situation, where only a partial JSON response is received and
|
|
7
|
-
* an attempt to parse the JSON throws an error. In most cases this can be resolved by retrying the
|
|
8
|
-
* request. We do that by identifying this error in HttpClient.
|
|
9
|
-
*
|
|
10
|
-
* The properties mimic AxiosError for easier integration in HttpClient error handling.
|
|
11
|
-
*/
|
|
12
|
-
export class InvalidResponseBodyError extends Error {
|
|
13
|
-
code;
|
|
14
|
-
response;
|
|
15
|
-
constructor(response, cause) {
|
|
16
|
-
super(`Response body could not be parsed.\nCause:${cause.message}`);
|
|
17
|
-
this.name = this.constructor.name;
|
|
18
|
-
this.code = 'invalid-response-body';
|
|
19
|
-
this.response = response;
|
|
20
|
-
this.cause = cause;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
function serializeRequest(config) {
|
|
24
|
-
const [defaultTransform] = axios.defaults.transformRequest;
|
|
25
|
-
// The function not only serializes data, but it also adds correct headers.
|
|
26
|
-
const data = defaultTransform(config.data, config.headers);
|
|
27
|
-
// Actor inputs can include functions and we don't want to omit those,
|
|
28
|
-
// because it's convenient for users. JSON.stringify removes them.
|
|
29
|
-
// It's a bit inefficient that we serialize the JSON twice, but I feel
|
|
30
|
-
// it's a small price to pay. The axios default transform does a lot
|
|
31
|
-
// of body type checks and we would have to copy all of them to the resource clients.
|
|
32
|
-
if (config.stringifyFunctions) {
|
|
33
|
-
const contentTypeHeader = config.headers?.['Content-Type'] || config.headers?.['content-type'];
|
|
34
|
-
try {
|
|
35
|
-
const { type } = contentTypeParser.parse(contentTypeHeader);
|
|
36
|
-
if (type === 'application/json' && typeof config.data === 'object') {
|
|
37
|
-
config.data = stringifyWithFunctions(config.data);
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
config.data = data;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
config.data = data;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
config.data = data;
|
|
49
|
-
}
|
|
50
|
-
return config;
|
|
51
|
-
}
|
|
52
|
-
function ensureHeadersPrototype(config) {
|
|
53
|
-
if (config.headers && !(config.headers instanceof AxiosHeaders)) {
|
|
54
|
-
Object.setPrototypeOf(config.headers, AxiosHeaders.prototype);
|
|
55
|
-
}
|
|
56
|
-
return config;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* JSON.stringify() that serializes functions to string instead
|
|
60
|
-
* of replacing them with null or removing them.
|
|
61
|
-
*/
|
|
62
|
-
function stringifyWithFunctions(obj) {
|
|
63
|
-
return JSON.stringify(obj, (_key, value) => {
|
|
64
|
-
return typeof value === 'function' ? value.toString() : value;
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
async function maybeCompressRequest(config) {
|
|
68
|
-
if (config.headers?.['content-encoding'])
|
|
69
|
-
return config;
|
|
70
|
-
const maybeCompressed = await maybeCompressValue(config.data);
|
|
71
|
-
if (maybeCompressed) {
|
|
72
|
-
config.headers ??= {};
|
|
73
|
-
config.headers['content-encoding'] = maybeCompressed.encoding;
|
|
74
|
-
config.data = maybeCompressed.data;
|
|
75
|
-
}
|
|
76
|
-
return config;
|
|
77
|
-
}
|
|
78
|
-
function parseResponseData(response) {
|
|
79
|
-
if (!response.data || // Nothing to do here.
|
|
80
|
-
response.config.responseType !== 'arraybuffer' || // We don't want to parse custom response types.
|
|
81
|
-
response.config.forceBuffer // Apify custom property to prevent parsing of buffer.
|
|
82
|
-
) {
|
|
83
|
-
return response;
|
|
84
|
-
}
|
|
85
|
-
const isBufferEmpty = isNode() ? !response.data.length : !response.data.byteLength;
|
|
86
|
-
if (isBufferEmpty) {
|
|
87
|
-
// undefined is better than an empty buffer
|
|
88
|
-
response.data = undefined;
|
|
89
|
-
return response;
|
|
90
|
-
}
|
|
91
|
-
const contentTypeHeader = response.headers['content-type'];
|
|
92
|
-
try {
|
|
93
|
-
response.data = maybeParseBody(response.data, contentTypeHeader);
|
|
94
|
-
}
|
|
95
|
-
catch (err) {
|
|
96
|
-
throw new InvalidResponseBodyError(response, err);
|
|
97
|
-
}
|
|
98
|
-
return response;
|
|
99
|
-
}
|
|
100
|
-
export const requestInterceptors = [
|
|
101
|
-
maybeCompressRequest,
|
|
102
|
-
serializeRequest,
|
|
103
|
-
ensureHeadersPrototype,
|
|
104
|
-
];
|
|
105
|
-
export const responseInterceptors = [parseResponseData];
|