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
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type http from 'node:http';
|
|
2
|
+
import type https from 'node:https';
|
|
3
|
+
import type { AxiosInstance, AxiosInterceptorManager, InternalAxiosRequestConfig } from 'axios';
|
|
4
|
+
import type { HttpClientOptions, HttpRequest, HttpResponse } from './base.js';
|
|
5
|
+
import { HttpClient } from './base.js';
|
|
6
|
+
/**
|
|
7
|
+
* An [axios request interceptor](https://axios-http.com/docs/interceptors). It runs on every attempt, right before
|
|
8
|
+
* the request is handed to axios, and sees the request as the shared pipeline prepared it: headers merged and the
|
|
9
|
+
* body already serialized and compressed.
|
|
10
|
+
*/
|
|
11
|
+
export type RequestInterceptorFunction = Parameters<AxiosInterceptorManager<InternalAxiosRequestConfig>['use']>[0];
|
|
12
|
+
/**
|
|
13
|
+
* Configuration of {@link AxiosHttpClient}.
|
|
14
|
+
*/
|
|
15
|
+
export interface AxiosHttpClientOptions extends HttpClientOptions {
|
|
16
|
+
/** @default [] */
|
|
17
|
+
requestInterceptors?: RequestInterceptorFunction[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The default HTTP client of {@link ApifyClient}, built on [axios](https://axios-http.com).
|
|
21
|
+
*
|
|
22
|
+
* It inherits the request pipeline from {@link HttpClient} and adds the transport: in Node.js, keep-alive agents
|
|
23
|
+
* that honor the `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` environment variables; in the browser, the XHR
|
|
24
|
+
* adapter of axios. Network errors are retried, timeouts included, and a stream body is sent without following
|
|
25
|
+
* redirects, since the part of it already sent could not be replayed.
|
|
26
|
+
*/
|
|
27
|
+
export declare class AxiosHttpClient extends HttpClient {
|
|
28
|
+
#private;
|
|
29
|
+
/** The axios instance the requests go through. */
|
|
30
|
+
axios: AxiosInstance;
|
|
31
|
+
/**
|
|
32
|
+
* Keep-alive agent for plain HTTP, created with the first request in Node.js. It is the same `ProxyAgent` as
|
|
33
|
+
* {@link httpsAgent}, so the socket pool is shared across both schemes.
|
|
34
|
+
*/
|
|
35
|
+
httpAgent?: http.Agent;
|
|
36
|
+
/** Keep-alive agent for HTTPS. The same instance as {@link httpAgent}. */
|
|
37
|
+
httpsAgent?: https.Agent;
|
|
38
|
+
constructor(options?: AxiosHttpClientOptions);
|
|
39
|
+
/**
|
|
40
|
+
* A timeout is an axios error coded `ECONNABORTED` or `ETIMEDOUT`, besides the `TimeoutError` the base class
|
|
41
|
+
* recognizes.
|
|
42
|
+
*/
|
|
43
|
+
isTimeoutError(error: unknown): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* A network failure is an axios error that carries the request it was sending: a connection that could not be
|
|
46
|
+
* made, dropped mid-way or timed out. Errors raised before a request exists, such as an invalid URL, are not
|
|
47
|
+
* retried.
|
|
48
|
+
*/
|
|
49
|
+
isRetryableTransportError(error: unknown): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Destroys the keep-alive agents, closing every pooled socket. A client that never sent a request in Node.js
|
|
52
|
+
* has nothing to close.
|
|
53
|
+
*/
|
|
54
|
+
close(): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Sends the request through axios and hands the raw response back. A stream body is sent without following
|
|
57
|
+
* redirects, since the part of it already sent could not be replayed.
|
|
58
|
+
*/
|
|
59
|
+
sendRequest(request: HttpRequest): Promise<HttpResponse>;
|
|
60
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import axios, { AxiosError, AxiosHeaders } from 'axios';
|
|
2
|
+
import { runtime } from '#runtime';
|
|
3
|
+
import { isStream } from '../utils.js';
|
|
4
|
+
import { HttpClient } from './base.js';
|
|
5
|
+
/**
|
|
6
|
+
* The default HTTP client of {@link ApifyClient}, built on [axios](https://axios-http.com).
|
|
7
|
+
*
|
|
8
|
+
* It inherits the request pipeline from {@link HttpClient} and adds the transport: in Node.js, keep-alive agents
|
|
9
|
+
* that honor the `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` environment variables; in the browser, the XHR
|
|
10
|
+
* adapter of axios. Network errors are retried, timeouts included, and a stream body is sent without following
|
|
11
|
+
* redirects, since the part of it already sent could not be replayed.
|
|
12
|
+
*/
|
|
13
|
+
export class AxiosHttpClient extends HttpClient {
|
|
14
|
+
/** The axios instance the requests go through. */
|
|
15
|
+
axios;
|
|
16
|
+
/**
|
|
17
|
+
* Keep-alive agent for plain HTTP, created with the first request in Node.js. It is the same `ProxyAgent` as
|
|
18
|
+
* {@link httpsAgent}, so the socket pool is shared across both schemes.
|
|
19
|
+
*/
|
|
20
|
+
httpAgent;
|
|
21
|
+
/** Keep-alive agent for HTTPS. The same instance as {@link httpAgent}. */
|
|
22
|
+
httpsAgent;
|
|
23
|
+
#httpAgentsPromise;
|
|
24
|
+
constructor(options = {}) {
|
|
25
|
+
super(options);
|
|
26
|
+
this.axios = axios.create({
|
|
27
|
+
// The agents handle the proxy environment variables.
|
|
28
|
+
proxy: false,
|
|
29
|
+
validateStatus: null,
|
|
30
|
+
// The shared pipeline serializes and parses the bodies, so axios only moves bytes.
|
|
31
|
+
transformRequest: [],
|
|
32
|
+
transformResponse: [],
|
|
33
|
+
responseType: 'arraybuffer',
|
|
34
|
+
// maxBodyLength needs to be Infinity, because -1 falls back to a 10 MB default
|
|
35
|
+
// from an axios subdependency - 'follow-redirects'
|
|
36
|
+
maxBodyLength: Infinity,
|
|
37
|
+
// maxContentLength must be -1, because Infinity will cause axios to run super slow
|
|
38
|
+
// thanks to a bug that's now fixed, but not released yet https://github.com/axios/axios/pull/3738
|
|
39
|
+
maxContentLength: -1,
|
|
40
|
+
});
|
|
41
|
+
// The pipeline merges every header the client sends, so the axios defaults would only get in the way.
|
|
42
|
+
this.axios.defaults.headers = new AxiosHeaders();
|
|
43
|
+
for (const interceptor of options.requestInterceptors ?? []) {
|
|
44
|
+
this.axios.interceptors.request.use(interceptor);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A timeout is an axios error coded `ECONNABORTED` or `ETIMEDOUT`, besides the `TimeoutError` the base class
|
|
49
|
+
* recognizes.
|
|
50
|
+
*/
|
|
51
|
+
isTimeoutError(error) {
|
|
52
|
+
if (super.isTimeoutError(error))
|
|
53
|
+
return true;
|
|
54
|
+
// Axios aborts the request when its own timeout fires, which gives ECONNABORTED, or ETIMEDOUT under
|
|
55
|
+
// `clarifyTimeoutError`. ETIMEDOUT also arrives straight from the socket, when the connection itself
|
|
56
|
+
// times out.
|
|
57
|
+
return (axios.isAxiosError(error) && (error.code === AxiosError.ECONNABORTED || error.code === AxiosError.ETIMEDOUT));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A network failure is an axios error that carries the request it was sending: a connection that could not be
|
|
61
|
+
* made, dropped mid-way or timed out. Errors raised before a request exists, such as an invalid URL, are not
|
|
62
|
+
* retried.
|
|
63
|
+
*/
|
|
64
|
+
isRetryableTransportError(error) {
|
|
65
|
+
if (!axios.isAxiosError(error))
|
|
66
|
+
return false;
|
|
67
|
+
return typeof error.request === 'object' && error.request !== null && error.config != null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Destroys the keep-alive agents, closing every pooled socket. A client that never sent a request in Node.js
|
|
71
|
+
* has nothing to close.
|
|
72
|
+
*/
|
|
73
|
+
async close() {
|
|
74
|
+
// A failed initialization left nothing to release, and its error belongs to the request that hit it.
|
|
75
|
+
await this.#httpAgentsPromise?.catch(() => { });
|
|
76
|
+
this.httpAgent?.destroy();
|
|
77
|
+
this.httpsAgent?.destroy();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Sends the request through axios and hands the raw response back. A stream body is sent without following
|
|
81
|
+
* redirects, since the part of it already sent could not be replayed.
|
|
82
|
+
*/
|
|
83
|
+
async sendRequest(request) {
|
|
84
|
+
await this.#ensureHttpAgents();
|
|
85
|
+
const { method, url, headers, body, timeoutMillis, stream, signal } = request;
|
|
86
|
+
const response = await this.axios.request({
|
|
87
|
+
method,
|
|
88
|
+
url,
|
|
89
|
+
headers,
|
|
90
|
+
data: body,
|
|
91
|
+
// Axios reads 0 as no timeout.
|
|
92
|
+
timeout: timeoutMillis ?? 0,
|
|
93
|
+
signal,
|
|
94
|
+
responseType: stream ? 'stream' : 'arraybuffer',
|
|
95
|
+
// See axios/axios#1045 for why axios must not buffer a stream body to follow redirects.
|
|
96
|
+
...(isStream(body) ? { maxRedirects: 0 } : {}),
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
status: response.status,
|
|
100
|
+
headers: AxiosHeaders.from(response.headers).toJSON(),
|
|
101
|
+
body: response.data,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
async #ensureHttpAgents() {
|
|
105
|
+
this.#httpAgentsPromise ??= this.#initHttpAgents();
|
|
106
|
+
return this.#httpAgentsPromise;
|
|
107
|
+
}
|
|
108
|
+
async #initHttpAgents() {
|
|
109
|
+
const agents = await runtime.createHttpAgents({ timeoutMillis: this.timeoutMaxMillis });
|
|
110
|
+
if (!agents)
|
|
111
|
+
return;
|
|
112
|
+
this.httpAgent = agents.httpAgent;
|
|
113
|
+
this.httpsAgent = agents.httpsAgent;
|
|
114
|
+
this.axios.defaults.httpAgent = this.httpAgent;
|
|
115
|
+
this.axios.defaults.httpsAgent = this.httpsAgent;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import type { Readable } from 'node:stream';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import type { Log } from '@apify/log';
|
|
4
|
+
import type { HttpCompressor } from '../http_compressors/base.js';
|
|
5
|
+
import type { HttpCompressionAlgorithm } from '../http_compressors/resolve.js';
|
|
6
|
+
import type { HttpMethod } from '../models.js';
|
|
7
|
+
import { Statistics } from '../statistics.js';
|
|
8
|
+
import type { Timeout, TimeoutTier } from '../timeouts.js';
|
|
9
|
+
export type { HttpMethod } from '../models.js';
|
|
10
|
+
export declare const DEFAULT_MAX_RETRIES = 8;
|
|
11
|
+
export declare const DEFAULT_MIN_DELAY_BETWEEN_RETRIES_MILLIS = 500;
|
|
12
|
+
/**
|
|
13
|
+
* Bounds of the numeric {@link HttpClientOptions}. `ApifyClientOptions` checks the same bounds and adds the defaults.
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export declare const httpClientOptionsShape: {
|
|
17
|
+
maxRetries: z.ZodNumber;
|
|
18
|
+
minDelayBetweenRetriesMillis: z.ZodNumber;
|
|
19
|
+
timeoutShortSecs: z.ZodNumber;
|
|
20
|
+
timeoutMediumSecs: z.ZodNumber;
|
|
21
|
+
timeoutLongSecs: z.ZodNumber;
|
|
22
|
+
timeoutMaxSecs: z.ZodNumber;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Response headers keyed by lowercase header name. A header the server sent more than once may arrive as an array.
|
|
26
|
+
*/
|
|
27
|
+
export type HttpResponseHeaders = Record<string, string | string[] | undefined>;
|
|
28
|
+
/**
|
|
29
|
+
* A request body as the transport receives it: already serialized, and compressed when that paid off. A `Readable`
|
|
30
|
+
* is passed through untouched and is only available in Node.js.
|
|
31
|
+
*/
|
|
32
|
+
export type HttpRequestBody = string | Buffer | ArrayBuffer | ArrayBufferView | Readable;
|
|
33
|
+
/**
|
|
34
|
+
* A response body as the transport hands it back: the raw bytes, a `Readable` when the request asked for a streamed
|
|
35
|
+
* response, or `undefined` when the response carried no body.
|
|
36
|
+
*/
|
|
37
|
+
export type HttpResponseBody = Buffer | ArrayBuffer | Readable | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* How {@link HttpClient.call} hands back the response body.
|
|
40
|
+
*
|
|
41
|
+
* - `parsed` (the default) decodes it by content type: JSON becomes an object, text and XML become a string, anything
|
|
42
|
+
* else stays a `Buffer` (an `ArrayBuffer` in the browser).
|
|
43
|
+
* - `buffer` skips the decoding and hands back the raw bytes.
|
|
44
|
+
* - `stream` hands back a `Readable` with the body unread. Node.js only.
|
|
45
|
+
*/
|
|
46
|
+
export type ApifyResponseType = 'parsed' | 'buffer' | 'stream';
|
|
47
|
+
/**
|
|
48
|
+
* A request as the resource clients hand it to {@link HttpClient.call}.
|
|
49
|
+
*/
|
|
50
|
+
export interface ApifyRequestConfig {
|
|
51
|
+
/** Full URL of the endpoint, without the query string. */
|
|
52
|
+
url: string;
|
|
53
|
+
/** HTTP method. */
|
|
54
|
+
method: HttpMethod;
|
|
55
|
+
/**
|
|
56
|
+
* Query parameters. `undefined` values are dropped, booleans are sent as `1` and `0`, `Date` values as ISO 8601
|
|
57
|
+
* strings and arrays comma-separated.
|
|
58
|
+
*/
|
|
59
|
+
params?: Record<string, unknown>;
|
|
60
|
+
/** Per-request headers. They win over the client's default headers, header names compared case-insensitively. */
|
|
61
|
+
headers?: Record<string, string>;
|
|
62
|
+
/**
|
|
63
|
+
* Request body. A plain object or array is serialized to JSON and sent as `application/json`, or form-encoded
|
|
64
|
+
* when a `Content-Type: application/x-www-form-urlencoded` header asks for it. A string, binary value
|
|
65
|
+
* (`Buffer`, `ArrayBuffer`, typed array) or `Readable` is sent as it is, and `URLSearchParams` is sent
|
|
66
|
+
* form-encoded. A `Blob`, `File`, `FormData` or web `ReadableStream` is rejected with a `TypeError`, since
|
|
67
|
+
* serializing one would send an empty body.
|
|
68
|
+
*/
|
|
69
|
+
data?: unknown;
|
|
70
|
+
/**
|
|
71
|
+
* Timeout of the request: a tier name, a number of seconds, or `'noTimeout'`. The client resolves it to the
|
|
72
|
+
* timeout of each attempt in milliseconds, doubling it with every retry up to `timeoutMaxSecs`.
|
|
73
|
+
* @default 'medium'
|
|
74
|
+
*/
|
|
75
|
+
timeoutSecs?: Timeout;
|
|
76
|
+
/** @default 'parsed' */
|
|
77
|
+
responseType?: ApifyResponseType;
|
|
78
|
+
/**
|
|
79
|
+
* Serialize function-valued fields of a JSON body to their source instead of dropping them, which is how
|
|
80
|
+
* Actor input carrying page functions reaches the API.
|
|
81
|
+
*/
|
|
82
|
+
stringifyFunctions?: boolean;
|
|
83
|
+
/** Give up on the first transport timeout instead of retrying it. */
|
|
84
|
+
doNotRetryTimeouts?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Aborts the call. Once the signal aborts, the attempt in flight is ended, no retry follows, and the call
|
|
87
|
+
* rejects with the signal's `reason`.
|
|
88
|
+
*/
|
|
89
|
+
signal?: AbortSignal;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A successful response as {@link HttpClient.call} resolves to it.
|
|
93
|
+
*
|
|
94
|
+
* @template T - Type of the parsed body.
|
|
95
|
+
*/
|
|
96
|
+
export interface ApifyResponse<T = any> {
|
|
97
|
+
/** HTTP status code. */
|
|
98
|
+
status: number;
|
|
99
|
+
/** Response headers keyed by lowercase header name. */
|
|
100
|
+
headers: HttpResponseHeaders;
|
|
101
|
+
/** The response body, decoded according to the request's {@link ApifyRequestConfig.responseType}. */
|
|
102
|
+
data: T;
|
|
103
|
+
/** The request this response answers, as it was passed to {@link HttpClient.call}. */
|
|
104
|
+
config: ApifyRequestConfig;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* One prepared request attempt, as {@link HttpClient.sendRequest} receives it from the shared pipeline.
|
|
108
|
+
*/
|
|
109
|
+
export interface HttpRequest {
|
|
110
|
+
/** HTTP method. */
|
|
111
|
+
method: HttpMethod;
|
|
112
|
+
/** Full request URL with the query string already encoded into it. */
|
|
113
|
+
url: string;
|
|
114
|
+
/** Final request headers, with the client's default headers already merged in. */
|
|
115
|
+
headers: Record<string, string>;
|
|
116
|
+
/** Request body, already serialized and compressed, or `undefined` for a request without one. */
|
|
117
|
+
body?: HttpRequestBody;
|
|
118
|
+
/**
|
|
119
|
+
* Timeout for this attempt in milliseconds, or `undefined` for a request that runs without one, which is what
|
|
120
|
+
* `timeoutSecs: 'noTimeout'` asks for.
|
|
121
|
+
*/
|
|
122
|
+
timeoutMillis?: number;
|
|
123
|
+
/** Whether to hand the body back unread as a `Readable`, so the caller can stream it. */
|
|
124
|
+
stream: boolean;
|
|
125
|
+
/**
|
|
126
|
+
* The caller's abort signal, to hand to the HTTP library alongside the timeout. The pipeline stops retrying
|
|
127
|
+
* once it aborts, so the transport only has to end the request in flight.
|
|
128
|
+
*/
|
|
129
|
+
signal?: AbortSignal;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* A response as the transport returns it from {@link HttpClient.sendRequest}, error statuses included.
|
|
133
|
+
*/
|
|
134
|
+
export interface HttpResponse {
|
|
135
|
+
/** HTTP status code. */
|
|
136
|
+
status: number;
|
|
137
|
+
/** Response headers keyed by lowercase header name. */
|
|
138
|
+
headers: HttpResponseHeaders;
|
|
139
|
+
/** The body as raw bytes, or unread as a `Readable` when the request asked for a stream. */
|
|
140
|
+
body: HttpResponseBody;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Configuration shared by every {@link HttpClient}.
|
|
144
|
+
*/
|
|
145
|
+
export interface HttpClientOptions {
|
|
146
|
+
/** Apify API token, sent as a `Bearer` token in the `Authorization` header. */
|
|
147
|
+
token?: string;
|
|
148
|
+
/** How many times a failed request is retried at most. @default 8 */
|
|
149
|
+
maxRetries?: number;
|
|
150
|
+
/**
|
|
151
|
+
* Lower bound for the delay before the first retry in milliseconds. It doubles with every further retry.
|
|
152
|
+
* @default 500
|
|
153
|
+
*/
|
|
154
|
+
minDelayBetweenRetriesMillis?: number;
|
|
155
|
+
/** Duration of the `short` timeout tier, in seconds. @default 5 */
|
|
156
|
+
timeoutShortSecs?: number;
|
|
157
|
+
/** Duration of the `medium` timeout tier, in seconds. @default 30 */
|
|
158
|
+
timeoutMediumSecs?: number;
|
|
159
|
+
/** Duration of the `long` timeout tier, in seconds. @default 360 */
|
|
160
|
+
timeoutLongSecs?: number;
|
|
161
|
+
/** Upper bound for the timeout of a single attempt, in seconds. @default 360 */
|
|
162
|
+
timeoutMaxSecs?: number;
|
|
163
|
+
/**
|
|
164
|
+
* Compression of request bodies: the name of a built-in algorithm, or an {@link HttpCompressor} for a custom
|
|
165
|
+
* quality or algorithm.
|
|
166
|
+
* @default 'brotli'
|
|
167
|
+
*/
|
|
168
|
+
compression?: HttpCompressionAlgorithm | HttpCompressor;
|
|
169
|
+
/** Additional headers sent with every request. They win over the built-in defaults. */
|
|
170
|
+
headers?: Record<string, string>;
|
|
171
|
+
/** Statistics the client records its calls into. Created when omitted. */
|
|
172
|
+
stats?: Statistics;
|
|
173
|
+
/** Logger for the retry warnings. Defaults to a child of the `@apify/log` default with an `ApifyClient` prefix. */
|
|
174
|
+
logger?: Log;
|
|
175
|
+
/** Value of the `X-Apify-Workflow-Key` header. Defaults to the `APIFY_WORKFLOW_KEY` environment variable. */
|
|
176
|
+
workflowKey?: string;
|
|
177
|
+
/** @internal */
|
|
178
|
+
userAgentSuffix?: string | string[];
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Base class for the HTTP clients used by {@link ApifyClient}.
|
|
182
|
+
*
|
|
183
|
+
* It holds the shared request pipeline: {@link call} merges the default headers in, serializes and compresses the
|
|
184
|
+
* body, encodes the query parameters, retries transient failures with exponential backoff, grows the timeout with
|
|
185
|
+
* every attempt, decodes the response body by content type, records statistics and converts error statuses to
|
|
186
|
+
* {@link ApifyApiError}. A concrete client only has to implement {@link sendRequest}, the transport that moves one
|
|
187
|
+
* prepared request over the wire, and can override the classification and lifecycle hooks {@link isTimeoutError},
|
|
188
|
+
* {@link isRetryableTransportError} and {@link close}.
|
|
189
|
+
*
|
|
190
|
+
* Overriding {@link call} itself is also supported and bypasses the transport hooks entirely. Such a client has
|
|
191
|
+
* to send the default headers from `defaultHeaders` with every request, otherwise the `Authorization` header never
|
|
192
|
+
* reaches the API. The protected helpers `prepareRequest()`, `buildUrl()` and `computeTimeoutMillis()` stay
|
|
193
|
+
* available to it.
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```javascript
|
|
197
|
+
* import { ApifyClient, HttpClient } from 'apify-client';
|
|
198
|
+
*
|
|
199
|
+
* class FetchHttpClient extends HttpClient {
|
|
200
|
+
* async sendRequest({ method, url, headers, body, timeoutMillis, signal }) {
|
|
201
|
+
* const signals = signal ? [signal] : [];
|
|
202
|
+
* if (timeoutMillis !== undefined) signals.push(AbortSignal.timeout(timeoutMillis));
|
|
203
|
+
* const response = await fetch(url, { method, headers, body, signal: AbortSignal.any(signals) });
|
|
204
|
+
* return {
|
|
205
|
+
* status: response.status,
|
|
206
|
+
* headers: Object.fromEntries(response.headers),
|
|
207
|
+
* body: Buffer.from(await response.arrayBuffer()),
|
|
208
|
+
* };
|
|
209
|
+
* }
|
|
210
|
+
*
|
|
211
|
+
* isRetryableTransportError(error) {
|
|
212
|
+
* // Without this the client gives up on the first connection failure.
|
|
213
|
+
* return this.isTimeoutError(error) || error instanceof TypeError;
|
|
214
|
+
* }
|
|
215
|
+
* }
|
|
216
|
+
*
|
|
217
|
+
* const client = ApifyClient.withCustomHttpClient({ token: 'my-token', httpClient: new FetchHttpClient() });
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
export declare abstract class HttpClient {
|
|
221
|
+
#private;
|
|
222
|
+
/** Statistics of the API calls made through this client. */
|
|
223
|
+
stats: Statistics;
|
|
224
|
+
/** Logger for the retry warnings. */
|
|
225
|
+
logger: Log;
|
|
226
|
+
/** How many times a failed request is retried at most. */
|
|
227
|
+
maxRetries: number;
|
|
228
|
+
/** Lower bound for the delay before the first retry in milliseconds. It doubles with every further retry. */
|
|
229
|
+
minDelayBetweenRetriesMillis: number;
|
|
230
|
+
/** Duration of each timeout tier, in milliseconds. */
|
|
231
|
+
timeoutMillis: Record<TimeoutTier, number>;
|
|
232
|
+
/** Upper bound for the timeout of a single attempt, in milliseconds. */
|
|
233
|
+
timeoutMaxMillis: number;
|
|
234
|
+
/** Compressor the request pipeline runs the bodies worth compressing through. */
|
|
235
|
+
readonly httpCompressor: HttpCompressor;
|
|
236
|
+
/**
|
|
237
|
+
* Headers sent with every request: the `Authorization` header built from the token, the `User-Agent` where the
|
|
238
|
+
* runtime lets a client set one, the `X-Apify-Workflow-Key` when a workflow key is configured, and whatever
|
|
239
|
+
* {@link HttpClientOptions.headers} added.
|
|
240
|
+
*/
|
|
241
|
+
protected readonly defaultHeaders: Record<string, string>;
|
|
242
|
+
/**
|
|
243
|
+
* @param options - Configuration of the pipeline. The retry and timeout options are validated the same way the
|
|
244
|
+
* `ApifyClient` constructor validates them.
|
|
245
|
+
* @throws {ArgumentValidationError} When a retry or timeout option is out of bounds.
|
|
246
|
+
*/
|
|
247
|
+
constructor(options?: HttpClientOptions);
|
|
248
|
+
/**
|
|
249
|
+
* Sets the `Authorization` header from the token, unless an authorization header is already configured.
|
|
250
|
+
*
|
|
251
|
+
* @param token - The Apify API token to send as the `Bearer` token.
|
|
252
|
+
*/
|
|
253
|
+
setDefaultAuthorization(token: string): void;
|
|
254
|
+
/**
|
|
255
|
+
* Whether an error thrown by the transport is a timeout.
|
|
256
|
+
*
|
|
257
|
+
* Recognizes errors named `TimeoutError`, which is what `AbortSignal.timeout()` produces. Transports extend it
|
|
258
|
+
* with the timeout errors their HTTP library throws. The classification is independent of retryability: a
|
|
259
|
+
* timeout the retry loop should retry has to be covered by {@link isRetryableTransportError} too.
|
|
260
|
+
*/
|
|
261
|
+
isTimeoutError(error: unknown): boolean;
|
|
262
|
+
/**
|
|
263
|
+
* Whether an error thrown by the transport is worth retrying.
|
|
264
|
+
*
|
|
265
|
+
* The default classifies nothing as retryable, so a transport that does not override it gives up on the first
|
|
266
|
+
* connection failure. Every transport should map its own transient errors here: connection resets, refused
|
|
267
|
+
* connections, timeouts and the like. Error responses are not the transport's concern, the pipeline decides on
|
|
268
|
+
* them from the status code.
|
|
269
|
+
*/
|
|
270
|
+
isRetryableTransportError(_error: unknown): boolean;
|
|
271
|
+
/**
|
|
272
|
+
* Releases resources owned by the transport, such as a connection pool. The default does nothing.
|
|
273
|
+
*/
|
|
274
|
+
close(): Promise<void>;
|
|
275
|
+
/**
|
|
276
|
+
* Sends one prepared request through the underlying HTTP library.
|
|
277
|
+
*
|
|
278
|
+
* The inherited {@link call} needs it, so every transport has to implement it. Let the library's errors
|
|
279
|
+
* propagate unwrapped: {@link call} classifies them through {@link isRetryableTransportError} and
|
|
280
|
+
* {@link isTimeoutError}. Return error responses as they are too, the pipeline turns them into
|
|
281
|
+
* {@link ApifyApiError} and decides whether to retry.
|
|
282
|
+
*
|
|
283
|
+
* @param request - The request to send, with the headers merged, the body serialized and the query encoded.
|
|
284
|
+
* @returns The response, with the body unread when `request.stream` is set and as raw bytes otherwise.
|
|
285
|
+
*/
|
|
286
|
+
sendRequest(request: HttpRequest): Promise<HttpResponse>;
|
|
287
|
+
/**
|
|
288
|
+
* Makes an API request with automatic retries and exponential backoff.
|
|
289
|
+
*
|
|
290
|
+
* Network errors the transport classifies as retryable, rate limits (HTTP 429) and server errors (HTTP 5xx)
|
|
291
|
+
* are retried up to {@link maxRetries} times. Any other error status is thrown as {@link ApifyApiError} right
|
|
292
|
+
* away. A request whose body is a `Readable` is never retried, since part of the stream has already been
|
|
293
|
+
* consumed by the time the failure shows. Aborting `config.signal` ends the attempt in flight, skips the
|
|
294
|
+
* remaining retries and rejects the call with the signal's `reason`.
|
|
295
|
+
*
|
|
296
|
+
* @template T - Type of the parsed response body.
|
|
297
|
+
* @param config - The request to make.
|
|
298
|
+
* @returns The successful response.
|
|
299
|
+
* @throws {ApifyApiError} When the API responds with an error status the retries could not fix.
|
|
300
|
+
*/
|
|
301
|
+
call<T = any>(config: ApifyRequestConfig): Promise<ApifyResponse<T>>;
|
|
302
|
+
/**
|
|
303
|
+
* Prepares the headers and the body of a request.
|
|
304
|
+
*
|
|
305
|
+
* Merges the client's default headers with the per-request ones, header names compared case-insensitively and
|
|
306
|
+
* the per-request values winning. Serializes an object body to JSON, setting `Content-Type: application/json`
|
|
307
|
+
* unless the caller supplied a content type. Runs the body through {@link httpCompressor} unless a
|
|
308
|
+
* `Content-Encoding` header is already set, the body is too small, or its content type says the payload is
|
|
309
|
+
* already compressed. A caller-supplied `Content-Encoding` is forwarded as it is, which is how a pre-encoded
|
|
310
|
+
* body is uploaded, and `Content-Encoding: identity` opts a single request out of compression.
|
|
311
|
+
*/
|
|
312
|
+
protected prepareRequest(config: ApifyRequestConfig): Promise<{
|
|
313
|
+
headers: Record<string, string>;
|
|
314
|
+
body: HttpRequestBody | undefined;
|
|
315
|
+
}>;
|
|
316
|
+
/**
|
|
317
|
+
* Appends the query parameters to the URL. `undefined` values are dropped, booleans are sent as `1` and `0`,
|
|
318
|
+
* `Date` values as ISO 8601 strings and arrays comma-separated.
|
|
319
|
+
*/
|
|
320
|
+
protected buildUrl(url: string, params?: Record<string, unknown>): string;
|
|
321
|
+
/**
|
|
322
|
+
* Resolves `timeoutSecs` to the number of milliseconds the given attempt gets. A tier name resolves to its
|
|
323
|
+
* configured duration, a number is taken as seconds, and `'noTimeout'` becomes `undefined`. The result doubles
|
|
324
|
+
* with each attempt and is capped at `timeoutMaxMillis`. A requested value above the cap is capped too, which
|
|
325
|
+
* warns once, since it does not take effect in full.
|
|
326
|
+
*
|
|
327
|
+
* @param attempt - Current attempt number, starting at 1.
|
|
328
|
+
* @param timeoutSecs - The request's timeout. Defaults to the `medium` tier.
|
|
329
|
+
*/
|
|
330
|
+
protected computeTimeoutMillis(attempt: number, timeoutSecs?: Timeout): number | undefined;
|
|
331
|
+
}
|