apify-client 3.0.0-beta.87 → 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.
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apify-client",
3
- "version": "3.0.0-beta.87",
3
+ "version": "3.0.0-beta.89",
4
4
  "description": "Apify API client for JavaScript",
5
5
  "engines": {
6
6
  "node": ">=22.13.0"
@@ -66,42 +66,42 @@
66
66
  "@apify/consts": "^3.0.1",
67
67
  "@apify/log": "^3.0.1",
68
68
  "@apify/utilities": "^3.1.0",
69
- "@apify/validations": "^1.1.1",
69
+ "@apify/validations": "^1.1.2",
70
70
  "ansi-colors": "^4.1.3",
71
- "axios": "^1.20.0",
71
+ "axios": "^1.21.0",
72
72
  "content-type": "^3.1.1",
73
73
  "proxy-agent": "^8.0.2",
74
- "tslib": "^2.8.1",
74
+ "tslib": "^2.9.1",
75
75
  "type-fest": "^5.10.0",
76
- "zod": "^4.6.5"
76
+ "zod": "^4.6.7"
77
77
  },
78
78
  "devDependencies": {
79
79
  "@apify/oxlint-config": "^0.3.0",
80
80
  "@apify/tsconfig": "^0.2.0",
81
81
  "@crawlee/puppeteer": "4.0.0-rc.1",
82
- "@rsbuild/core": "^2.2.10",
82
+ "@rsbuild/core": "^2.3.0",
83
83
  "@rsbuild/plugin-node-polyfill": "^1.4.6",
84
- "@rspack/cli": "^2.2.8",
85
- "@rspack/core": "^2.2.8",
84
+ "@rspack/cli": "^2.3.0",
85
+ "@rspack/core": "^2.3.0",
86
86
  "@types/compression": "^1.8.1",
87
87
  "@types/express": "^5.0.6",
88
- "@types/node": "^26.0.0",
88
+ "@types/node": "^26.6.6",
89
89
  "ajv": "^8.20.0",
90
- "body-parser": "^2.3.0",
90
+ "body-parser": "^2.4.0",
91
91
  "compression": "^1.8.2",
92
92
  "esbuild": "0.28.2",
93
- "express": "^5.2.1",
93
+ "express": "^5.3.0",
94
94
  "oxfmt": "0.75.0",
95
95
  "oxlint": "1.90.0",
96
96
  "oxlint-tsgolint": "7.0.2005",
97
- "puppeteer": "^25.12.0",
97
+ "puppeteer": "^25.16.0",
98
98
  "rimraf": "^6.1.3",
99
- "rolldown": "^1.2.11",
99
+ "rolldown": "^1.2.16",
100
100
  "typescript": "^7.0.2",
101
101
  "typescript-v6": "npm:typescript@^6.0.3",
102
- "vitest": "^5.0.2",
103
- "webpack": "^5.111.1",
104
- "webpack-cli": "^7.2.3"
102
+ "vitest": "^5.1.0",
103
+ "webpack": "^5.112.2",
104
+ "webpack-cli": "^7.3.1"
105
105
  },
106
106
  "devEngines": {
107
107
  "packageManager": {