apify 4.0.0-beta.30 → 4.0.0-beta.32

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.
@@ -39,9 +39,8 @@ import { Configuration } from './configuration.js';
39
39
  * you can achieve the same effect using `setInterval()` and listening for the `migrating` event.
40
40
  */
41
41
  export declare class PlatformEventManager extends EventManager {
42
+ #private;
42
43
  readonly configuration: Configuration;
43
- /** Websocket connection to Actor events. */
44
- private eventsWs?;
45
44
  constructor(configuration?: Configuration);
46
45
  /**
47
46
  * Initializes `Actor.events` event emitter by creating a connection to a websocket that provides them.
@@ -44,7 +44,7 @@ import { Configuration } from './configuration.js';
44
44
  export class PlatformEventManager extends EventManager {
45
45
  configuration;
46
46
  /** Websocket connection to Actor events. */
47
- eventsWs;
47
+ #eventsWs;
48
48
  constructor(configuration = Configuration.getGlobalConfiguration()) {
49
49
  super({
50
50
  persistStateIntervalMillis: configuration.persistStateIntervalMillis,
@@ -69,8 +69,8 @@ export class PlatformEventManager extends EventManager {
69
69
  this.createWebSocketConnection(eventsWsUrl);
70
70
  }
71
71
  createWebSocketConnection(eventsWsUrl) {
72
- this.eventsWs = new WebSocket(eventsWsUrl);
73
- this.eventsWs.on('message', (message) => {
72
+ this.#eventsWs = new WebSocket(eventsWsUrl);
73
+ this.#eventsWs.on('message', (message) => {
74
74
  if (!message)
75
75
  return;
76
76
  try {
@@ -87,15 +87,15 @@ export class PlatformEventManager extends EventManager {
87
87
  this.log.exception(err, 'Cannot parse Actor event');
88
88
  }
89
89
  });
90
- this.eventsWs.on('error', (err) => {
90
+ this.#eventsWs.on('error', (err) => {
91
91
  // Don't print this error as this happens in the case of very short Actor.main().
92
92
  if (err.message === 'WebSocket was closed before the connection was established')
93
93
  return;
94
94
  this.log.exception(err, 'web socket connection failed');
95
95
  });
96
- this.eventsWs.on('close', () => {
96
+ this.#eventsWs.on('close', () => {
97
97
  this.log.debug('web socket has been closed');
98
- this.eventsWs = undefined;
98
+ this.#eventsWs = undefined;
99
99
  });
100
100
  }
101
101
  /**
@@ -108,6 +108,6 @@ export class PlatformEventManager extends EventManager {
108
108
  return;
109
109
  }
110
110
  await super.close();
111
- this.eventsWs?.close();
111
+ this.#eventsWs?.close();
112
112
  }
113
113
  }
@@ -151,14 +151,8 @@ export interface ProxyInfo extends CoreProxyInfo {
151
151
  * @category Scaling
152
152
  */
153
153
  export declare class ProxyConfiguration extends CoreProxyConfiguration {
154
+ #private;
154
155
  readonly configuration: Configuration;
155
- private groups;
156
- private countryCode?;
157
- private subdivisionCode?;
158
- private password?;
159
- private hostname;
160
- private port?;
161
- private usesApifyProxy?;
162
156
  protected readonly log: import("@apify/log").Log;
163
157
  /**
164
158
  * @internal
@@ -190,22 +184,22 @@ export declare class ProxyConfiguration extends CoreProxyConfiguration {
190
184
  /**
191
185
  * Returns proxy username.
192
186
  */
193
- protected _getUsername(sessionId: string): string;
187
+ protected getUsername(sessionId: string): string;
194
188
  protected composeDefaultUrl(sessionId: string): string;
195
189
  /**
196
190
  * Fetch & set the proxy password from Apify API if an Apify token is provided.
197
191
  */
198
- protected _setPasswordIfToken(): Promise<void>;
192
+ private setPasswordIfToken;
199
193
  /**
200
194
  * Checks whether the user has access to the proxies specified in the provided ProxyConfigurationOptions.
201
195
  * If the check can not be made, it only prints a warning and allows the program to continue. This is to
202
196
  * prevent program crashes caused by short downtimes of Proxy.
203
197
  */
204
- protected _checkAccess(): Promise<boolean>;
198
+ protected checkAccess(): Promise<boolean>;
205
199
  /**
206
200
  * Apify Proxy can be down for a second or a minute, but this should not crash processes.
207
201
  */
208
- protected _fetchStatus(): Promise<ProxyStatus | undefined>;
202
+ protected fetchStatus(): Promise<ProxyStatus | undefined>;
209
203
  /**
210
204
  * Fetches the Apify Proxy status endpoint once, *through* the proxy, so the
211
205
  * response reports on this exact connection (auth + man-in-the-middle).
@@ -214,16 +208,11 @@ export declare class ProxyConfiguration extends CoreProxyConfiguration {
214
208
  * plus a `Proxy-Authorization` header — so no proxy-agent dependency is
215
209
  * needed. The status endpoint (`http://proxy.apify.com`) is plain HTTP.
216
210
  */
217
- protected _requestStatus(statusUrl: string, proxyUrl: string): Promise<ProxyStatus>;
211
+ protected requestStatus(statusUrl: string, proxyUrl: string): Promise<ProxyStatus>;
218
212
  /**
219
213
  * Throws cannot combine custom proxies with Apify Proxy
220
214
  * @internal
221
215
  */
222
- protected _throwCannotCombineCustomWithApify(): void;
223
- /**
224
- * Throws cannot combine custom proxies with custom generating function
225
- * @internal
226
- */
227
- protected _throwCannotCombineCustomMethods(): void;
216
+ protected throwCannotCombineCustomWithApify(): void;
228
217
  }
229
218
  export {};
@@ -53,13 +53,13 @@ const SESSION_ID_LENGTH = 12;
53
53
  */
54
54
  export class ProxyConfiguration extends CoreProxyConfiguration {
55
55
  configuration;
56
- groups;
57
- countryCode;
58
- subdivisionCode;
59
- password;
60
- hostname;
61
- port;
62
- usesApifyProxy;
56
+ #groups;
57
+ #countryCode;
58
+ #subdivisionCode;
59
+ #password;
60
+ #hostname;
61
+ #port;
62
+ #usesApifyProxy;
63
63
  log = defaultLog.child({ prefix: 'ProxyConfiguration' });
64
64
  /**
65
65
  * @internal
@@ -96,17 +96,15 @@ export class ProxyConfiguration extends CoreProxyConfiguration {
96
96
  }
97
97
  // Validation
98
98
  if ((proxyUrls || newUrlFunction) && (groupsToUse.length || countryCodeToUse || subdivisionCodeToUse)) {
99
- this._throwCannotCombineCustomWithApify();
99
+ this.throwCannotCombineCustomWithApify();
100
100
  }
101
- if (proxyUrls && newUrlFunction)
102
- this._throwCannotCombineCustomMethods();
103
- this.groups = groupsToUse;
104
- this.countryCode = countryCodeToUse;
105
- this.subdivisionCode = subdivisionCodeToUse;
106
- this.password = password;
107
- this.hostname = hostname;
108
- this.port = port;
109
- this.usesApifyProxy = !proxyUrls && !newUrlFunction;
101
+ this.#groups = groupsToUse;
102
+ this.#countryCode = countryCodeToUse;
103
+ this.#subdivisionCode = subdivisionCodeToUse;
104
+ this.#password = password;
105
+ this.#hostname = hostname;
106
+ this.#port = port;
107
+ this.#usesApifyProxy = !proxyUrls && !newUrlFunction;
110
108
  if (proxyUrls && proxyUrls.some((url) => url?.includes('apify.com'))) {
111
109
  this.log.warning('Some Apify proxy features may work incorrectly. Please consider setting up Apify properties instead of `proxyUrls`.\n' +
112
110
  'See https://docs.apify.com/sdk/js/docs/concepts/proxy-management#apify-proxy-configuration');
@@ -121,11 +119,11 @@ export class ProxyConfiguration extends CoreProxyConfiguration {
121
119
  * `ProxyConfiguration` instance instead of calling this manually.
122
120
  */
123
121
  async initialize(options) {
124
- if (this.usesApifyProxy) {
125
- if (!this.password) {
126
- await this._setPasswordIfToken();
122
+ if (this.#usesApifyProxy) {
123
+ if (!this.#password) {
124
+ await this.setPasswordIfToken();
127
125
  }
128
- if (!this.password) {
126
+ if (!this.#password) {
129
127
  if (Actor.isAtHome()) {
130
128
  throw new Error(`Apify Proxy password must be provided using options.password or the "${APIFY_ENV_VARS.PROXY_PASSWORD}" environment variable. ` +
131
129
  `You can also provide your Apify token via the "${APIFY_ENV_VARS.TOKEN}" environment variable, ` +
@@ -139,7 +137,7 @@ export class ProxyConfiguration extends CoreProxyConfiguration {
139
137
  }
140
138
  }
141
139
  if (options?.checkAccess !== false) {
142
- return this._checkAccess();
140
+ return this.checkAccess();
143
141
  }
144
142
  }
145
143
  return true;
@@ -161,12 +159,12 @@ export class ProxyConfiguration extends CoreProxyConfiguration {
161
159
  hostname: parsed.hostname,
162
160
  port: parsed.port,
163
161
  };
164
- if (this.usesApifyProxy) {
165
- result.groups = this.groups;
166
- if (this.countryCode !== undefined)
167
- result.countryCode = this.countryCode;
168
- if (this.subdivisionCode !== undefined)
169
- result.subdivisionCode = this.subdivisionCode;
162
+ if (this.#usesApifyProxy) {
163
+ result.groups = this.#groups;
164
+ if (this.#countryCode !== undefined)
165
+ result.countryCode = this.#countryCode;
166
+ if (this.#subdivisionCode !== undefined)
167
+ result.subdivisionCode = this.#subdivisionCode;
170
168
  }
171
169
  return result;
172
170
  }
@@ -176,7 +174,7 @@ export class ProxyConfiguration extends CoreProxyConfiguration {
176
174
  * `proxyUrls`, the URLs are rotated round-robin.
177
175
  */
178
176
  async newUrl(options) {
179
- if (!this.usesApifyProxy) {
177
+ if (!this.#usesApifyProxy) {
180
178
  return super.newUrl(options);
181
179
  }
182
180
  return this.composeDefaultUrl(cryptoRandomObjectId(SESSION_ID_LENGTH));
@@ -184,40 +182,38 @@ export class ProxyConfiguration extends CoreProxyConfiguration {
184
182
  /**
185
183
  * Returns proxy username.
186
184
  */
187
- _getUsername(sessionId) {
188
- const { groups, countryCode, subdivisionCode } = this;
185
+ getUsername(sessionId) {
189
186
  const parts = [];
190
- if (groups && groups.length) {
191
- parts.push(`groups-${groups.join('+')}`);
187
+ if (this.#groups && this.#groups.length) {
188
+ parts.push(`groups-${this.#groups.join('+')}`);
192
189
  }
193
190
  parts.push(`session-${sessionId}`);
194
- if (subdivisionCode) {
195
- parts.push(`country-${countryCode}_${subdivisionCode}`);
191
+ if (this.#subdivisionCode) {
192
+ parts.push(`country-${this.#countryCode}_${this.#subdivisionCode}`);
196
193
  }
197
- else if (countryCode) {
198
- parts.push(`country-${countryCode}`);
194
+ else if (this.#countryCode) {
195
+ parts.push(`country-${this.#countryCode}`);
199
196
  }
200
197
  return parts.join(',');
201
198
  }
202
199
  composeDefaultUrl(sessionId) {
203
- const username = this._getUsername(sessionId);
204
- const url = new URL(`http://${this.hostname}:${this.port}`);
200
+ const username = this.getUsername(sessionId);
201
+ const url = new URL(`http://${this.#hostname}:${this.#port}`);
205
202
  url.username = `${username}`;
206
- url.password = `${this.password}`;
203
+ url.password = `${this.#password}`;
207
204
  const urlString = url.toString();
208
205
  return urlString.substring(0, urlString.length - 1);
209
206
  }
210
207
  /**
211
208
  * Fetch & set the proxy password from Apify API if an Apify token is provided.
212
209
  */
213
- // TODO: Make this private
214
- async _setPasswordIfToken() {
210
+ async setPasswordIfToken() {
215
211
  const { token } = this.configuration;
216
212
  if (!token)
217
213
  return;
218
214
  try {
219
215
  const user = await Actor.apifyClient.user().get();
220
- this.password = user.proxy?.password;
216
+ this.#password = user.proxy?.password;
221
217
  }
222
218
  catch (error) {
223
219
  if (Actor.isAtHome()) {
@@ -235,8 +231,8 @@ export class ProxyConfiguration extends CoreProxyConfiguration {
235
231
  * If the check can not be made, it only prints a warning and allows the program to continue. This is to
236
232
  * prevent program crashes caused by short downtimes of Proxy.
237
233
  */
238
- async _checkAccess() {
239
- const status = await this._fetchStatus();
234
+ async checkAccess() {
235
+ const status = await this.fetchStatus();
240
236
  if (!status) {
241
237
  this.log.warning('Apify Proxy access check timed out. Watch out for errors with status code 407. ' +
242
238
  "If you see some, it most likely means you don't have access to either all or some of the proxies you're trying to use.");
@@ -261,7 +257,7 @@ export class ProxyConfiguration extends CoreProxyConfiguration {
261
257
  /**
262
258
  * Apify Proxy can be down for a second or a minute, but this should not crash processes.
263
259
  */
264
- async _fetchStatus() {
260
+ async fetchStatus() {
265
261
  const { proxyStatusUrl } = this.configuration;
266
262
  const statusUrl = `${proxyStatusUrl}/?format=json`;
267
263
  const proxyUrl = await this.newUrl();
@@ -270,7 +266,7 @@ export class ProxyConfiguration extends CoreProxyConfiguration {
270
266
  return undefined;
271
267
  for (let attempt = 1; attempt <= CHECK_ACCESS_MAX_ATTEMPTS; attempt++) {
272
268
  try {
273
- return await this._requestStatus(statusUrl, proxyUrl);
269
+ return await this.requestStatus(statusUrl, proxyUrl);
274
270
  }
275
271
  catch {
276
272
  // retry connection errors
@@ -286,7 +282,7 @@ export class ProxyConfiguration extends CoreProxyConfiguration {
286
282
  * plus a `Proxy-Authorization` header — so no proxy-agent dependency is
287
283
  * needed. The status endpoint (`http://proxy.apify.com`) is plain HTTP.
288
284
  */
289
- async _requestStatus(statusUrl, proxyUrl) {
285
+ async requestStatus(statusUrl, proxyUrl) {
290
286
  const target = new URL(statusUrl);
291
287
  const proxy = new URL(proxyUrl);
292
288
  const headers = { host: target.host };
@@ -304,7 +300,7 @@ export class ProxyConfiguration extends CoreProxyConfiguration {
304
300
  });
305
301
  request.end();
306
302
  // `once` rejects if the request emits `error` first (connection refused,
307
- // timeout/abort), so failures propagate to the retry loop in `_fetchStatus`.
303
+ // timeout/abort), so failures propagate to the retry loop in `fetchStatus`.
308
304
  const [response] = (await once(request, 'response'));
309
305
  const statusCode = response.statusCode ?? 0;
310
306
  if (statusCode < 200 || statusCode >= 300) {
@@ -317,17 +313,10 @@ export class ProxyConfiguration extends CoreProxyConfiguration {
317
313
  * Throws cannot combine custom proxies with Apify Proxy
318
314
  * @internal
319
315
  */
320
- _throwCannotCombineCustomWithApify() {
316
+ throwCannotCombineCustomWithApify() {
321
317
  throw new Error('Cannot combine custom proxies with Apify Proxy! ' +
322
318
  'It is not allowed to set "options.proxyUrls" or "options.newUrlFunction" combined with ' +
323
319
  '"options.groups", "options.apifyProxyGroups", "options.countryCode", "options.apifyProxyCountry", ' +
324
320
  '"options.subdivisionCode" or "options.apifyProxySubdivision".');
325
321
  }
326
- /**
327
- * Throws cannot combine custom proxies with custom generating function
328
- * @internal
329
- */
330
- _throwCannotCombineCustomMethods() {
331
- throw new Error('Cannot combine custom proxies "options.proxyUrls" with custom generating function "options.newUrlFunction".');
332
- }
333
322
  }
package/dist/storage.d.ts CHANGED
@@ -1,6 +1,3 @@
1
- import type { IStorage, StorageOpenOptions } from '@crawlee/core';
2
- import type { Constructor, StorageBackend } from '@crawlee/types';
3
- import type { Configuration } from './configuration.js';
4
1
  export interface OpenStorageOptions {
5
2
  /**
6
3
  * If set to `true` then the cloud storage is used even if the `CRAWLEE_STORAGE_DIR`
@@ -10,8 +7,11 @@ export interface OpenStorageOptions {
10
7
  forceCloud?: boolean;
11
8
  }
12
9
  /**
13
- * Identifies a storage by its alias from the Actor's schema storages
14
- * (resolved via the `ACTOR_STORAGES_JSON` environment variable).
10
+ * Identifies a run-scoped storage by its alias.
11
+ *
12
+ * An alias declared in the Actor's schema storages (the `ACTOR_STORAGES_JSON` environment variable)
13
+ * resolves to the storage the platform created for it; any other alias gets an unnamed storage of
14
+ * its own, for this run only.
15
15
  */
16
16
  export interface StorageAlias {
17
17
  alias: string;
@@ -31,28 +31,8 @@ export interface StorageName {
31
31
  /**
32
32
  * Identifies a storage to open. Can be:
33
33
  * - A plain `string` for backward compatibility (treated as ID or name)
34
- * - `{ alias: string }` to resolve from the Actor's schema storages (`ACTOR_STORAGES_JSON`)
34
+ * - `{ alias: string }` to open a run-scoped storage see {@link StorageAlias}
35
35
  * - `{ id: string }` to open by explicit platform ID
36
36
  * - `{ name: string }` to open by explicit name
37
37
  */
38
38
  export type StorageIdentifier = string | StorageAlias | StorageId | StorageName;
39
- /**
40
- * Identifies a storage to open, without alias support.
41
- * Used for key-value stores and request queues, which do not support aliases.
42
- * Can be:
43
- * - A plain `string` for backward compatibility (treated as ID or name)
44
- * - `{ id: string }` to open by explicit platform ID
45
- * - `{ name: string }` to open by explicit name
46
- */
47
- export type StorageIdentifierWithoutAlias = string | StorageId | StorageName;
48
- export interface OpenStorageContext {
49
- config: Configuration;
50
- backend?: StorageBackend;
51
- purgedStorageAliases: Set<string>;
52
- }
53
- /**
54
- * Opens a storage by its identifier, handling Apify alias resolution and local purging.
55
- */
56
- export declare function openStorage<T extends IStorage>(storageClass: Constructor<T> & {
57
- open(id?: string | null, options?: StorageOpenOptions): Promise<T>;
58
- }, identifier: StorageIdentifier | null | undefined, context: OpenStorageContext): Promise<T>;
package/dist/storage.js CHANGED
@@ -1,79 +1 @@
1
- import { ApifyStorageBackend } from './apify_storage_backend.js';
2
- const STORAGE_TYPE_KEYS = {
3
- Dataset: 'datasets',
4
- KeyValueStore: 'keyValueStores',
5
- RequestQueue: 'requestQueues',
6
- };
7
- const parsedStoragesJson = new Map();
8
- /**
9
- * Resolves a {@link StorageIdentifier} to a plain string ID or name
10
- * that can be passed to crawlee v4's `<Storage>.open()`.
11
- */
12
- function resolveStorageIdentifier(storageType, identifier, config) {
13
- if (identifier === null || identifier === undefined) {
14
- return undefined;
15
- }
16
- if (typeof identifier === 'string') {
17
- return identifier;
18
- }
19
- if ('id' in identifier) {
20
- return identifier.id;
21
- }
22
- if ('name' in identifier) {
23
- return identifier.name;
24
- }
25
- // { alias: string }
26
- const storagesJson = config.actorStoragesJson;
27
- if (config.isAtHome && storagesJson) {
28
- let storages;
29
- try {
30
- if (!parsedStoragesJson.has(storagesJson)) {
31
- parsedStoragesJson.set(storagesJson, JSON.parse(storagesJson));
32
- }
33
- storages = parsedStoragesJson.get(storagesJson);
34
- }
35
- catch {
36
- throw new Error(`Failed to parse ACTOR_STORAGES_JSON environment variable: ${storagesJson}`);
37
- }
38
- const typeKey = STORAGE_TYPE_KEYS[storageType];
39
- const resolvedId = storages[typeKey]?.[identifier.alias];
40
- if (resolvedId) {
41
- return resolvedId;
42
- }
43
- throw new Error(`Storage alias "${identifier.alias}" not found in ACTOR_STORAGES_JSON for storage type "${storageType}". ` +
44
- `Available aliases: ${Object.keys(storages[typeKey] ?? {}).join(', ') || '(none)'}`);
45
- }
46
- // When using local storage, just use the alias as a name.
47
- // When using platform storage, we can't just make up a name — the alias must be
48
- // in ACTOR_STORAGES_JSON.
49
- if (config.isAtHome) {
50
- throw new Error(`Storage alias "${identifier.alias}" cannot be resolved because ACTOR_STORAGES_JSON is not set. ` +
51
- `Aliases are only available for storages declared in the Actor's schema.`);
52
- }
53
- return identifier.alias;
54
- }
55
- /**
56
- * Opens a storage by its identifier, handling Apify alias resolution and local purging.
57
- */
58
- export async function openStorage(storageClass, identifier, context) {
59
- const isAlias = identifier !== null && identifier !== undefined && typeof identifier === 'object' && 'alias' in identifier;
60
- if (isAlias && !context.config.isAtHome && context.backend instanceof ApifyStorageBackend) {
61
- throw new Error('The `alias` option is not allowed for Apify-based storages running outside of Apify');
62
- }
63
- const resolvedIdOrName = resolveStorageIdentifier(storageClass.name, identifier, context.config);
64
- // When running locally, purge aliased storages on first open
65
- // (similar to how crawlee purges default storages on start).
66
- if (isAlias &&
67
- !context.config.isAtHome &&
68
- context.config.purgeOnStart &&
69
- !context.purgedStorageAliases.has(identifier.alias)) {
70
- context.purgedStorageAliases.add(identifier.alias);
71
- const existingStorage = await storageClass.open(resolvedIdOrName ?? null, {
72
- storageBackend: context.backend,
73
- });
74
- await existingStorage.drop();
75
- }
76
- return storageClass.open(resolvedIdOrName ?? null, {
77
- storageBackend: context.backend,
78
- });
79
- }
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apify",
3
- "version": "4.0.0-beta.30",
3
+ "version": "4.0.0-beta.32",
4
4
  "description": "The scalable web crawling and scraping library for JavaScript/Node.js. Enables development of data extraction and web automation jobs (not only) with headless Chrome and Puppeteer.",
5
5
  "engines": {
6
6
  "node": ">=22.0.0"