apify-client 3.0.0-beta.88 → 3.0.0-beta.89
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 +6 -0
- package/dist/bundle.js +10 -10
- package/dist/bundle.js.map +1 -1
- package/dist/http_clients/axios.d.ts +2 -1
- package/dist/http_clients/axios.js +6 -4
- package/dist/http_clients/base.d.ts +64 -14
- package/dist/http_clients/base.js +68 -30
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/resource_clients/actor.d.ts +30 -0
- package/dist/resource_clients/actor.js +43 -0
- package/dist/resource_clients/actor_server.d.ts +141 -0
- package/dist/resource_clients/actor_server.js +195 -0
- package/package.json +1 -1
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { Readable } from 'node:stream';
|
|
2
|
+
import type { ApiClientSubResourceOptions } from '../base/api_client.js';
|
|
3
|
+
import { ResourceClient } from '../base/resource_client.js';
|
|
4
|
+
import type { ApifyResponse, ApifyResponseType, HttpMethod } from '../http_clients/index.js';
|
|
5
|
+
import type { HttpCompressor } from '../http_compressors/base.js';
|
|
6
|
+
import type { HttpCompressionAlgorithm } from '../http_compressors/resolve.js';
|
|
7
|
+
import type { TimeoutOptions } from '../timeouts.js';
|
|
8
|
+
/**
|
|
9
|
+
* Client for the server interface of a specific Actor.
|
|
10
|
+
*
|
|
11
|
+
* Provides methods to work with the web server of a Server Actor, such as sending it requests or getting its OpenAPI
|
|
12
|
+
* specification. Get an instance with {@link ActorClient.server}.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```javascript
|
|
16
|
+
* const client = new ApifyClient({ token: 'my-token' });
|
|
17
|
+
* const actorServerClient = client.actor('username/actor-name').server();
|
|
18
|
+
*
|
|
19
|
+
* const response = await actorServerClient.request('/search', { params: { q: 'apify' } });
|
|
20
|
+
* console.log(response.data);
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @see https://docs.apify.com/platform/actors/running/standby
|
|
24
|
+
*/
|
|
25
|
+
export declare class ActorServerClient extends ResourceClient {
|
|
26
|
+
#private;
|
|
27
|
+
/**
|
|
28
|
+
* @hidden
|
|
29
|
+
*/
|
|
30
|
+
constructor(options: ApiClientSubResourceOptions);
|
|
31
|
+
/**
|
|
32
|
+
* Returns the URL of the Actor's server.
|
|
33
|
+
*
|
|
34
|
+
* The URL is the Actor's `standbyUrl`, fetched from the API by the first call of this method or
|
|
35
|
+
* {@link getOpenApiSpec} and reused by later calls on this client. Use it to call the server with another HTTP
|
|
36
|
+
* client, for example one generated from the server's OpenAPI specification.
|
|
37
|
+
*
|
|
38
|
+
* @param options - Request options
|
|
39
|
+
* @param options.timeoutSecs - Timeout for the API request. Default is `'short'`.
|
|
40
|
+
* @returns The URL of the Actor's server
|
|
41
|
+
* @throws {ApifyClientError} When the Actor is not a Server Actor.
|
|
42
|
+
* @throws {NotFoundError} When the Actor does not exist.
|
|
43
|
+
* @see https://docs.apify.com/platform/actors/running/standby
|
|
44
|
+
*/
|
|
45
|
+
getUrl(options?: TimeoutOptions): Promise<string>;
|
|
46
|
+
/**
|
|
47
|
+
* Returns the OpenAPI specification of the Actor's server.
|
|
48
|
+
*
|
|
49
|
+
* The specification is the web server schema from the Actor's definition, taken from the build that serves the
|
|
50
|
+
* server interface: the build set in `actorStandby.build`, or `latest` if none is set. Use it to generate an HTTP
|
|
51
|
+
* client for the server, and point that client at the URL returned by {@link getUrl}.
|
|
52
|
+
*
|
|
53
|
+
* @param options - Request options
|
|
54
|
+
* @param options.timeoutSecs - Timeout for each API request. Default is `'short'`.
|
|
55
|
+
* @returns The OpenAPI specification of the Actor's server, or `undefined` if the Actor's definition doesn't
|
|
56
|
+
* include one
|
|
57
|
+
* @throws {ApifyClientError} When the Actor is not a Server Actor, or the build that serves its server interface
|
|
58
|
+
* doesn't exist.
|
|
59
|
+
* @throws {NotFoundError} When the Actor does not exist.
|
|
60
|
+
* @see https://docs.apify.com/platform/actors/development/actor-definition/web-server-schema
|
|
61
|
+
*/
|
|
62
|
+
getOpenApiSpec(options?: TimeoutOptions): Promise<Record<string, unknown> | undefined>;
|
|
63
|
+
/**
|
|
64
|
+
* Sends an HTTP request to the Actor's server.
|
|
65
|
+
*
|
|
66
|
+
* The request goes to the URL returned by {@link getUrl} and carries the client's token in the
|
|
67
|
+
* `X-Apify-Authorization` header, which the platform consumes, so it never reaches the Actor's code. Headers you
|
|
68
|
+
* pass, `Authorization` included, reach the Actor's server as they are. The first request to an idle Actor waits
|
|
69
|
+
* for the platform to start a server run, which is why the timeout defaults to `'long'`.
|
|
70
|
+
*
|
|
71
|
+
* The method resolves to any response of the server, error statuses included. A rate-limit (429) or Service
|
|
72
|
+
* Unavailable (503) response is retried with the client's retry settings, and the last one is returned once the
|
|
73
|
+
* retries run out. The Actor's code can return these statuses too, and then it gets the request again. A redirect
|
|
74
|
+
* is returned as it is, since following it would send the token to a host the Actor's code picked. A browser
|
|
75
|
+
* follows redirects itself, though, so there the token goes with a redirect. Request bodies are sent uncompressed
|
|
76
|
+
* unless `compression` is set, since the Actor's server may not decode a compressed body.
|
|
77
|
+
*
|
|
78
|
+
* @template T - Type of the parsed response body.
|
|
79
|
+
* @param path - The path on the Actor's server, for example `/search`. It is always resolved against the server
|
|
80
|
+
* URL, so the request, and the token with it, can't be sent to another host. Pass the query in `params`. A
|
|
81
|
+
* fragment (`#...`) is dropped, since it's never sent to the server.
|
|
82
|
+
* @param options - Request options
|
|
83
|
+
* @param options.method - HTTP method of the request. Default is `'GET'`.
|
|
84
|
+
* @param options.params - Query parameters of the request, converted the same way as for API calls.
|
|
85
|
+
* @param options.headers - Additional headers of the request.
|
|
86
|
+
* @param options.data - Request body, serialized the same way as for API calls: a plain object or array is sent
|
|
87
|
+
* as JSON, and a string or binary value as it is.
|
|
88
|
+
* @param options.compression - Compression algorithm for the request body, as a name or an {@link HttpCompressor}.
|
|
89
|
+
* The body is compressed under the same conditions as for API calls. Set it only if the Actor's server decodes
|
|
90
|
+
* the resulting `Content-Encoding`.
|
|
91
|
+
* @param options.responseType - How to return the response body: `'parsed'` by its content type, `'buffer'` as
|
|
92
|
+
* raw bytes, or `'stream'` as a Readable stream with the body unread. A stream holds its connection open until
|
|
93
|
+
* you consume it to the end or destroy it. Node.js only. Default is `'parsed'`.
|
|
94
|
+
* @param options.timeoutSecs - Timeout for the HTTP request. Default is `'long'`. With `'stream'`, it bounds the
|
|
95
|
+
* wait for the server to start responding.
|
|
96
|
+
* @returns The response of the Actor's server, with the body in the form `responseType` asks for. An error
|
|
97
|
+
* response whose body doesn't parse as its content type comes with the raw bytes in `data`.
|
|
98
|
+
* @throws {ApifyClientError} When the Actor is not a Server Actor.
|
|
99
|
+
* @throws {InvalidResponseBodyError} When a success response's body doesn't parse as its content type, with
|
|
100
|
+
* `responseType: 'parsed'`.
|
|
101
|
+
* @throws {NotFoundError} When the Actor does not exist.
|
|
102
|
+
* @see https://docs.apify.com/platform/actors/running/standby
|
|
103
|
+
*/
|
|
104
|
+
request<T = any>(path?: string, options?: ActorServerRequestOptions & {
|
|
105
|
+
responseType?: 'parsed';
|
|
106
|
+
}): Promise<ApifyResponse<T>>;
|
|
107
|
+
request(path: string, options: ActorServerRequestOptions & {
|
|
108
|
+
responseType: 'buffer';
|
|
109
|
+
}): Promise<ApifyResponse<Buffer>>;
|
|
110
|
+
request(path: string, options: ActorServerRequestOptions & {
|
|
111
|
+
responseType: 'stream';
|
|
112
|
+
}): Promise<ApifyResponse<Readable>>;
|
|
113
|
+
request(path?: string, options?: ActorServerRequestOptions): Promise<ApifyResponse>;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Options of {@link ActorServerClient.request}.
|
|
117
|
+
*/
|
|
118
|
+
export interface ActorServerRequestOptions extends TimeoutOptions {
|
|
119
|
+
/** HTTP method of the request. @default 'GET' */
|
|
120
|
+
method?: HttpMethod;
|
|
121
|
+
/** Query parameters of the request, converted the same way as for API calls. */
|
|
122
|
+
params?: Record<string, unknown>;
|
|
123
|
+
/** Additional headers of the request. */
|
|
124
|
+
headers?: Record<string, string>;
|
|
125
|
+
/**
|
|
126
|
+
* Request body, serialized the same way as for API calls: a plain object or array is sent as JSON, and a string
|
|
127
|
+
* or binary value as it is.
|
|
128
|
+
*/
|
|
129
|
+
data?: unknown;
|
|
130
|
+
/**
|
|
131
|
+
* Compression algorithm for the request body, as a name or an {@link HttpCompressor}. The body is sent
|
|
132
|
+
* uncompressed when it's not set, since the Actor's server may not decode a compressed body.
|
|
133
|
+
*/
|
|
134
|
+
compression?: HttpCompressionAlgorithm | HttpCompressor;
|
|
135
|
+
/**
|
|
136
|
+
* How to return the response body: `'parsed'` by its content type, `'buffer'` as raw bytes, or `'stream'` as a
|
|
137
|
+
* Readable stream with the body unread. Node.js only.
|
|
138
|
+
* @default 'parsed'
|
|
139
|
+
*/
|
|
140
|
+
responseType?: ApifyResponseType;
|
|
141
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ApifyClientError } from '../apify_client_error.js';
|
|
3
|
+
import { ResourceClient } from '../base/resource_client.js';
|
|
4
|
+
import { compressionSchema, resolveCompressor } from '../http_compressors/resolve.js';
|
|
5
|
+
import * as schemas from '../schemas.js';
|
|
6
|
+
import { timeoutOptionsSchema, timeoutOptionsShape } from '../timeouts.js';
|
|
7
|
+
import { runtime } from '#runtime';
|
|
8
|
+
import { parseArgument, parseResponse } from '../utils.js';
|
|
9
|
+
import { BuildClient } from './build.js';
|
|
10
|
+
import { BuildCollectionClient } from './build_collection.js';
|
|
11
|
+
/**
|
|
12
|
+
* Statuses a server request is retried on. The platform answers with them when it can't pass a request to a server
|
|
13
|
+
* run, so a retry usually doesn't run the Actor's code again. An Actor that returns one itself gets the request again.
|
|
14
|
+
*/
|
|
15
|
+
const SERVER_RETRY_STATUS_CODES = [429, 503];
|
|
16
|
+
const BUILD_NUMBER_PATTERN = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
17
|
+
/**
|
|
18
|
+
* Splits a build number like `0.1.23` into its version and sequence number, or returns `undefined` for anything else.
|
|
19
|
+
*/
|
|
20
|
+
function parseBuildNumber(buildNumber) {
|
|
21
|
+
const match = BUILD_NUMBER_PATTERN.exec(buildNumber ?? '');
|
|
22
|
+
if (!match)
|
|
23
|
+
return undefined;
|
|
24
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Returns whether `buildNumber` belongs to the same version as `target` and comes before it. Builds are listed newest
|
|
28
|
+
* first and the sequence numbers of a version grow over time, so meeting such a build means the target doesn't exist.
|
|
29
|
+
*/
|
|
30
|
+
function isOlderBuild(buildNumber, target) {
|
|
31
|
+
const parsed = parseBuildNumber(buildNumber);
|
|
32
|
+
return parsed !== undefined && parsed[0] === target[0] && parsed[1] === target[1] && parsed[2] < target[2];
|
|
33
|
+
}
|
|
34
|
+
const requestOptionsSchema = z.strictObject({
|
|
35
|
+
method: z
|
|
36
|
+
.enum(['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH'])
|
|
37
|
+
.optional(),
|
|
38
|
+
params: z.record(z.string(), z.unknown()).optional(),
|
|
39
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
40
|
+
data: z.unknown().optional(),
|
|
41
|
+
compression: compressionSchema.optional(),
|
|
42
|
+
responseType: z.enum(['parsed', 'buffer', 'stream']).optional(),
|
|
43
|
+
...timeoutOptionsShape,
|
|
44
|
+
});
|
|
45
|
+
/**
|
|
46
|
+
* Client for the server interface of a specific Actor.
|
|
47
|
+
*
|
|
48
|
+
* Provides methods to work with the web server of a Server Actor, such as sending it requests or getting its OpenAPI
|
|
49
|
+
* specification. Get an instance with {@link ActorClient.server}.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```javascript
|
|
53
|
+
* const client = new ApifyClient({ token: 'my-token' });
|
|
54
|
+
* const actorServerClient = client.actor('username/actor-name').server();
|
|
55
|
+
*
|
|
56
|
+
* const response = await actorServerClient.request('/search', { params: { q: 'apify' } });
|
|
57
|
+
* console.log(response.data);
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @see https://docs.apify.com/platform/actors/running/standby
|
|
61
|
+
*/
|
|
62
|
+
export class ActorServerClient extends ResourceClient {
|
|
63
|
+
/**
|
|
64
|
+
* The server URL. Getting it takes an API call for the Actor, so it's cached for {@link getUrl} and
|
|
65
|
+
* {@link request}. It's pending while the first lookup runs, so concurrent first calls share one API request.
|
|
66
|
+
*/
|
|
67
|
+
#url;
|
|
68
|
+
/**
|
|
69
|
+
* @hidden
|
|
70
|
+
*/
|
|
71
|
+
constructor(options) {
|
|
72
|
+
super({
|
|
73
|
+
resourcePath: 'actors',
|
|
74
|
+
...options,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Returns the URL of the Actor's server.
|
|
79
|
+
*
|
|
80
|
+
* The URL is the Actor's `standbyUrl`, fetched from the API by the first call of this method or
|
|
81
|
+
* {@link getOpenApiSpec} and reused by later calls on this client. Use it to call the server with another HTTP
|
|
82
|
+
* client, for example one generated from the server's OpenAPI specification.
|
|
83
|
+
*
|
|
84
|
+
* @param options - Request options
|
|
85
|
+
* @param options.timeoutSecs - Timeout for the API request. Default is `'short'`.
|
|
86
|
+
* @returns The URL of the Actor's server
|
|
87
|
+
* @throws {ApifyClientError} When the Actor is not a Server Actor.
|
|
88
|
+
* @throws {NotFoundError} When the Actor does not exist.
|
|
89
|
+
* @see https://docs.apify.com/platform/actors/running/standby
|
|
90
|
+
*/
|
|
91
|
+
async getUrl(options = {}) {
|
|
92
|
+
const { timeoutSecs = 'short', signal } = parseArgument(options, timeoutOptionsSchema, 'TimeoutOptions');
|
|
93
|
+
const url = (this.#url ??= this.#getServerActor(timeoutSecs, signal).then((actor) => actor.standbyUrl));
|
|
94
|
+
try {
|
|
95
|
+
return await url;
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
// A failed lookup is not kept, so the next call tries again.
|
|
99
|
+
if (this.#url === url)
|
|
100
|
+
this.#url = undefined;
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Returns the OpenAPI specification of the Actor's server.
|
|
106
|
+
*
|
|
107
|
+
* The specification is the web server schema from the Actor's definition, taken from the build that serves the
|
|
108
|
+
* server interface: the build set in `actorStandby.build`, or `latest` if none is set. Use it to generate an HTTP
|
|
109
|
+
* client for the server, and point that client at the URL returned by {@link getUrl}.
|
|
110
|
+
*
|
|
111
|
+
* @param options - Request options
|
|
112
|
+
* @param options.timeoutSecs - Timeout for each API request. Default is `'short'`.
|
|
113
|
+
* @returns The OpenAPI specification of the Actor's server, or `undefined` if the Actor's definition doesn't
|
|
114
|
+
* include one
|
|
115
|
+
* @throws {ApifyClientError} When the Actor is not a Server Actor, or the build that serves its server interface
|
|
116
|
+
* doesn't exist.
|
|
117
|
+
* @throws {NotFoundError} When the Actor does not exist.
|
|
118
|
+
* @see https://docs.apify.com/platform/actors/development/actor-definition/web-server-schema
|
|
119
|
+
*/
|
|
120
|
+
async getOpenApiSpec(options = {}) {
|
|
121
|
+
const { timeoutSecs = 'short', signal } = parseArgument(options, timeoutOptionsSchema, 'TimeoutOptions');
|
|
122
|
+
const actor = await this.#getServerActor(timeoutSecs, signal);
|
|
123
|
+
const buildRef = actor.actorStandby?.build || 'latest';
|
|
124
|
+
let buildId = actor.taggedBuilds?.[buildRef]?.buildId ?? undefined;
|
|
125
|
+
const targetBuildNumber = parseBuildNumber(buildRef);
|
|
126
|
+
if (buildId === undefined && targetBuildNumber !== undefined) {
|
|
127
|
+
// Not a tag, so look `buildRef` up as a build number, which the API can't resolve directly.
|
|
128
|
+
const builds = new BuildCollectionClient(this.subResourceOptions({ resourcePath: 'builds' }));
|
|
129
|
+
for await (const build of builds.list({ desc: true, timeoutSecs, signal })) {
|
|
130
|
+
if (build.buildNumber === buildRef) {
|
|
131
|
+
buildId = build.id;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
if (isOlderBuild(build.buildNumber, targetBuildNumber))
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const build = buildId === undefined
|
|
139
|
+
? undefined
|
|
140
|
+
: await new BuildClient({
|
|
141
|
+
baseUrl: this.apifyClient.baseUrl,
|
|
142
|
+
publicBaseUrl: this.apifyClient.publicBaseUrl,
|
|
143
|
+
httpClient: this.httpClient,
|
|
144
|
+
apifyClient: this.apifyClient,
|
|
145
|
+
id: buildId,
|
|
146
|
+
}).get({ timeoutSecs, signal });
|
|
147
|
+
if (build === undefined) {
|
|
148
|
+
throw new ApifyClientError(`Build ${buildRef} of Actor ${this.id} does not exist.`);
|
|
149
|
+
}
|
|
150
|
+
return build.actorDefinition?.webServerSchema;
|
|
151
|
+
}
|
|
152
|
+
async request(path = '/', options = {}) {
|
|
153
|
+
parseArgument(path, z.string());
|
|
154
|
+
const { method = 'GET', params, headers, data, compression, responseType, timeoutSecs = 'long', signal, } = parseArgument(options, requestOptionsSchema, 'ActorServerRequestOptions');
|
|
155
|
+
if (responseType === 'stream' && !runtime.isNode) {
|
|
156
|
+
throw new Error("The 'stream' response type can only be used in Node.js environment.");
|
|
157
|
+
}
|
|
158
|
+
const serverUrl = await this.getUrl({ signal });
|
|
159
|
+
const serverPath = path.split('#')[0].replace(/^\/+/, '');
|
|
160
|
+
return this.httpClient.call({
|
|
161
|
+
url: `${serverUrl.replace(/\/+$/, '')}/${serverPath}`,
|
|
162
|
+
method,
|
|
163
|
+
params,
|
|
164
|
+
headers,
|
|
165
|
+
data,
|
|
166
|
+
compression: compression === undefined ? false : resolveCompressor(compression),
|
|
167
|
+
authorizationHeader: 'X-Apify-Authorization',
|
|
168
|
+
throwOnErrorStatus: false,
|
|
169
|
+
retryStatusCodes: SERVER_RETRY_STATUS_CODES,
|
|
170
|
+
retryTransportErrors: false,
|
|
171
|
+
followRedirects: false,
|
|
172
|
+
responseType,
|
|
173
|
+
timeoutSecs,
|
|
174
|
+
signal,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Fetches the Actor, checks that it is a Server Actor, and caches its server URL.
|
|
179
|
+
*/
|
|
180
|
+
async #getServerActor(timeoutSecs, signal) {
|
|
181
|
+
const response = await this.httpClient.call({
|
|
182
|
+
url: this.buildUrl(),
|
|
183
|
+
method: 'GET',
|
|
184
|
+
params: this.buildParams(),
|
|
185
|
+
timeoutSecs,
|
|
186
|
+
signal,
|
|
187
|
+
});
|
|
188
|
+
const actor = parseResponse(response, schemas.ActorResource());
|
|
189
|
+
if (!actor.standbyUrl) {
|
|
190
|
+
throw new ApifyClientError(`Actor ${this.id} is not a Server Actor.`);
|
|
191
|
+
}
|
|
192
|
+
this.#url = Promise.resolve(actor.standbyUrl);
|
|
193
|
+
return actor;
|
|
194
|
+
}
|
|
195
|
+
}
|