apify 4.0.0-beta.19 → 4.0.0-beta.21

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,249 @@
1
+ /* eslint-disable max-classes-per-file */
2
+ import { AsyncLocalStorage } from 'node:async_hooks';
3
+ import { createHash } from 'node:crypto';
4
+ import { DatasetClient as ApifyDatasetClient } from 'apify-client';
5
+ import { cryptoRandomObjectId } from '@apify/utilities';
6
+ import { ApifyDatasetBackend } from './apify_dataset_backend.js';
7
+ import { ApifyKeyValueStoreBackend } from './apify_key_value_store_backend.js';
8
+ import { ApifyRequestQueueSharedBackend } from './apify_request_queue_shared_backend.js';
9
+ import { ApifyRequestQueueSingleBackend } from './apify_request_queue_single_backend.js';
10
+ import { DEFAULT_DATASET_ITEM_EVENT, mergeChargeResults, pushDataAndCharge, } from './charging.js';
11
+ /** The reserved alias crawlee uses for the default (unnamed) storage. */
12
+ const DEFAULT_STORAGE_ALIAS = '__default__';
13
+ /** The maximum clientKey length accepted by the request queue API. */
14
+ const MAX_CLIENT_KEY_LENGTH = 32;
15
+ const DEFAULT_ID_CONFIG_KEY = {
16
+ Dataset: 'defaultDatasetId',
17
+ KeyValueStore: 'defaultKeyValueStoreId',
18
+ RequestQueue: 'defaultRequestQueueId',
19
+ };
20
+ const ACTOR_STORAGES_TYPE_KEY = {
21
+ Dataset: 'datasets',
22
+ KeyValueStore: 'keyValueStores',
23
+ RequestQueue: 'requestQueues',
24
+ };
25
+ /** Marks a dataset backend whose underlying client charges for pushed items (pay-per-event). @internal */
26
+ export const USES_PUSH_DATA_INTERCEPTION = Symbol('apify:uses-push-data-interception');
27
+ export const pushDataChargingContext = new AsyncLocalStorage();
28
+ /**
29
+ * Default `DatasetClient` that charges for pushed items (pay-per-event). Used
30
+ * only for the run's default dataset when a `apify-default-dataset-item` price
31
+ * is configured; for everything else the plain `apify-client` dataset client is
32
+ * used.
33
+ */
34
+ class PpeAwareDatasetClient extends ApifyDatasetClient {
35
+ getChargingManager;
36
+ constructor(options, getChargingManager) {
37
+ super(options);
38
+ this.getChargingManager = getChargingManager;
39
+ }
40
+ normalizeItems(items) {
41
+ if (typeof items === 'string') {
42
+ const parsed = JSON.parse(items);
43
+ return Array.isArray(parsed) ? parsed : [parsed];
44
+ }
45
+ if (Array.isArray(items)) {
46
+ return items.flatMap((item) => typeof item === 'string' ? JSON.parse(item) : item);
47
+ }
48
+ return [items];
49
+ }
50
+ async pushItems(items) {
51
+ const context = pushDataChargingContext.getStore();
52
+ // A single JSON string may encode multiple items (e.g. '[{...},{...}]'),
53
+ // which the charging logic would miscount — parse strings into arrays so
54
+ // each logical item is counted individually.
55
+ const normalizedItems = this.normalizeItems(items);
56
+ const result = await pushDataAndCharge({
57
+ chargingManager: this.getChargingManager(),
58
+ items: normalizedItems,
59
+ eventName: context?.eventName,
60
+ isDefaultDataset: true,
61
+ // stringify for faster validation in the Apify client
62
+ pushFn: async (limitedItems) => super.pushItems(JSON.stringify(limitedItems)),
63
+ });
64
+ if (!context)
65
+ return;
66
+ // One `Actor.pushData()` may map to several `pushItems()` calls — aggregate.
67
+ context.chargeResult =
68
+ context.chargeResult === undefined ? result : mergeChargeResults(context.chargeResult, result);
69
+ }
70
+ }
71
+ /**
72
+ * Bridges `apify-client`'s synchronous resource accessors (`dataset(id)`,
73
+ * `keyValueStore(id)`, `requestQueue(id, options?)`) to crawlee v4's
74
+ * `StorageBackend` interface (async factory methods accepting an `id`,
75
+ * a `name`, or an `alias`).
76
+ *
77
+ * For the run's default dataset it transparently swaps in a charging-aware
78
+ * dataset client (pay-per-event on `Actor.pushData()`), provided a charging
79
+ * manager is supplied and a default-dataset-item price is configured.
80
+ *
81
+ * `Actor` wires this up automatically; construct it directly only to use Apify
82
+ * platform storage with crawlee's storage classes outside of `Actor` — e.g. to
83
+ * read another run's output with an explicit token:
84
+ *
85
+ * ```ts
86
+ * import { ApifyClient, ApifyStorageBackend, Dataset } from 'apify';
87
+ *
88
+ * const client = new ApifyClient({ token });
89
+ * const dataset = await Dataset.open(datasetId, { storageBackend: new ApifyStorageBackend(client) });
90
+ * const { items } = await dataset.getData();
91
+ * ```
92
+ */
93
+ export class ApifyStorageBackend {
94
+ client;
95
+ config;
96
+ requestQueueAccess;
97
+ getChargingManager;
98
+ /** Unnamed storages created for aliases in this process, so an alias maps to one storage. */
99
+ aliasIdCache = new Map();
100
+ /** Fallback request queue client key when the run id is unavailable — one per backend. */
101
+ fallbackClientKey;
102
+ constructor(client, options = {}) {
103
+ this.client = client;
104
+ this.config = options.configuration;
105
+ this.requestQueueAccess = options.requestQueueAccess ?? 'single';
106
+ this.getChargingManager = options.getChargingManager;
107
+ }
108
+ /**
109
+ * Partitions crawlee's storage-instance cache by API base URL and token, so the same storage
110
+ * opened through two differently-authenticated backends is cached separately. The request
111
+ * queue access mode is deliberately not part of the key — opening the same queue in `single`
112
+ * and `shared` mode at once is not supported, and whichever backend opens it first wins.
113
+ */
114
+ getStorageBackendCacheKey() {
115
+ const hash = createHash('sha256')
116
+ .update(`${this.client.publicBaseUrl}${this.client.token ?? ''}`)
117
+ .digest('hex')
118
+ .slice(0, 8);
119
+ return `ApifyStorageBackend:${hash}`;
120
+ }
121
+ async storageExists(id, type) {
122
+ // Lets `Dataset.open(idOrName)` and friends resolve a string to an id first (when one
123
+ // exists on the platform) and fall back to a name otherwise; without this, crawlee would
124
+ // treat every string as a name and silently create a new storage named like the passed id.
125
+ // Apify's `GET /v2/{kind}/{idOrName}` matches by either id or name;
126
+ // confirm it was an *id* match so crawlee can fall through to `{ name }`.
127
+ const info = await this.resourceClient(id, type).get();
128
+ return info?.id === id;
129
+ }
130
+ async createDatasetBackend(options) {
131
+ const id = await this.resolveId(options, 'Dataset');
132
+ const chargingClient = this.chargingDatasetClient(id);
133
+ const backend = new ApifyDatasetBackend(chargingClient ?? this.client.dataset(id));
134
+ if (chargingClient) {
135
+ // `Actor.pushData()` looks for this marker on the dataset's backend to know the
136
+ // pay-per-event charging happens inside the intercepted `pushItems()` calls.
137
+ Object.assign(backend, { [USES_PUSH_DATA_INTERCEPTION]: true });
138
+ }
139
+ return backend;
140
+ }
141
+ async createKeyValueStoreBackend(options) {
142
+ const id = await this.resolveId(options, 'KeyValueStore');
143
+ return new ApifyKeyValueStoreBackend(this.client.keyValueStore(id));
144
+ }
145
+ async createRequestQueueBackend(options) {
146
+ const id = await this.resolveId(options, 'RequestQueue');
147
+ const client = this.client.requestQueue(id, { clientKey: this.requestQueueClientKey() });
148
+ return this.requestQueueAccess === 'shared'
149
+ ? new ApifyRequestQueueSharedBackend(client)
150
+ : new ApifyRequestQueueSingleBackend(client);
151
+ }
152
+ /**
153
+ * A stable per-run client key makes the API's `hadMultipleClients` flag meaningful and lets a
154
+ * migrated or resurrected run re-acquire the request locks of its previous incarnation.
155
+ */
156
+ requestQueueClientKey() {
157
+ const key = this.config?.actorRunId ?? (this.fallbackClientKey ??= cryptoRandomObjectId(MAX_CLIENT_KEY_LENGTH));
158
+ return key.slice(0, MAX_CLIENT_KEY_LENGTH);
159
+ }
160
+ /**
161
+ * Returns a charging-aware dataset client when `id` is the run's default
162
+ * dataset and a default-dataset-item price is configured; otherwise
163
+ * `undefined` (caller uses the plain client).
164
+ */
165
+ chargingDatasetClient(id) {
166
+ const { getChargingManager } = this;
167
+ if (!getChargingManager)
168
+ return undefined;
169
+ if (id !== this.config?.defaultDatasetId)
170
+ return undefined;
171
+ const hasDefaultDatasetItemEvent = DEFAULT_DATASET_ITEM_EVENT in getChargingManager().getPricingInfo().perEventPrices;
172
+ if (!hasDefaultDatasetItemEvent)
173
+ return undefined;
174
+ return new PpeAwareDatasetClient({
175
+ id,
176
+ baseUrl: this.client.baseUrl,
177
+ publicBaseUrl: this.client.publicBaseUrl,
178
+ apifyClient: this.client,
179
+ httpClient: this.client.httpClient,
180
+ }, getChargingManager);
181
+ }
182
+ /**
183
+ * Resolves a crawlee {@link StorageIdentifier} to a platform storage id.
184
+ *
185
+ * Aliases resolve to unnamed storages: the reserved `__default__` alias maps to the run's
186
+ * default storage, and other aliases to the storages declared in the Actor's schema (via the
187
+ * `ACTOR_STORAGES_JSON` environment variable, maintained by the platform). Outside the
188
+ * platform, an unnamed storage is created per alias instead (remembered for this process only).
189
+ */
190
+ async resolveId(options, type) {
191
+ if (options?.id)
192
+ return options.id;
193
+ if (options?.name) {
194
+ return (await this.collectionClient(type).getOrCreate(options.name)).id;
195
+ }
196
+ const alias = (options && 'alias' in options && options.alias) || DEFAULT_STORAGE_ALIAS;
197
+ if (alias === DEFAULT_STORAGE_ALIAS) {
198
+ const defaultId = this.config?.[DEFAULT_ID_CONFIG_KEY[type]];
199
+ if (defaultId)
200
+ return defaultId;
201
+ }
202
+ else {
203
+ const declaredId = this.aliasFromActorStorages(alias, type);
204
+ if (declaredId)
205
+ return declaredId;
206
+ if (this.config?.isAtHome) {
207
+ throw new Error(`Storage alias "${alias}" cannot be resolved because it is not declared in the Actor's schema storages. ` +
208
+ `Declare it in the Actor schema, or open the storage by name instead.`);
209
+ }
210
+ }
211
+ // No platform-provided id for this alias (e.g. cloud storage used locally via an API
212
+ // token) — create an unnamed storage for it, one per alias per process.
213
+ const cacheKey = `${type}:${alias}`;
214
+ const cachedId = this.aliasIdCache.get(cacheKey);
215
+ if (cachedId)
216
+ return cachedId;
217
+ const created = await this.collectionClient(type).getOrCreate();
218
+ this.aliasIdCache.set(cacheKey, created.id);
219
+ return created.id;
220
+ }
221
+ /** Looks an alias up in the Actor's schema storages (the `ACTOR_STORAGES_JSON` env var). */
222
+ aliasFromActorStorages(alias, type) {
223
+ const storagesJson = this.config?.actorStoragesJson;
224
+ if (!storagesJson)
225
+ return undefined;
226
+ let storages;
227
+ try {
228
+ storages = JSON.parse(storagesJson);
229
+ }
230
+ catch {
231
+ throw new Error(`Failed to parse ACTOR_STORAGES_JSON environment variable: ${storagesJson}`);
232
+ }
233
+ return storages[ACTOR_STORAGES_TYPE_KEY[type]]?.[alias];
234
+ }
235
+ resourceClient(id, type) {
236
+ if (type === 'Dataset')
237
+ return this.client.dataset(id);
238
+ if (type === 'KeyValueStore')
239
+ return this.client.keyValueStore(id);
240
+ return this.client.requestQueue(id);
241
+ }
242
+ collectionClient(type) {
243
+ if (type === 'Dataset')
244
+ return this.client.datasets();
245
+ if (type === 'KeyValueStore')
246
+ return this.client.keyValueStores();
247
+ return this.client.requestQueues();
248
+ }
249
+ }
@@ -47,9 +47,11 @@ export declare const apifyConfigFields: {
47
47
  actorStoragesJson: ConfigField<z.ZodOptional<z.ZodString>>;
48
48
  storageClientOptions: ConfigField<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
49
49
  maxUsedCpuRatio: ConfigField<z.ZodDefault<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>>>;
50
+ internalTimeoutMillis: ConfigField<z.ZodOptional<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>>>;
50
51
  systemInfoIntervalMillis: ConfigField<z.ZodDefault<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>>>;
51
52
  logLevel: ConfigField<z.ZodOptional<z.ZodPipe<z.ZodTransform<{} | null | undefined, unknown>, z.ZodEnum<typeof import("@apify/log").LogLevel>>>>;
52
53
  persistStorage: ConfigField<z.ZodDefault<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodBoolean>>>;
54
+ storageDir: ConfigField<z.ZodDefault<z.ZodString>>;
53
55
  containerized: ConfigField<z.ZodOptional<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodBoolean>>>;
54
56
  };
55
57
  export type ApifyConfigurationInput = FieldsInput<typeof apifyConfigFields>;
@@ -61,13 +63,13 @@ export interface Configuration extends ApifyResolvedConfigValues {
61
63
  /**
62
64
  * `Configuration` is a value object holding the SDK configuration. We can use it in two ways:
63
65
  *
64
- * 1. When using `Actor` class, we can get the instance configuration via `sdk.config`
66
+ * 1. When using `Actor` class, we can get the instance configuration via `sdk.configuration`
65
67
  *
66
68
  * ```javascript
67
69
  * import { Actor } from 'apify';
68
70
  *
69
71
  * const sdk = new Actor({ token: '123' });
70
- * console.log(sdk.config.token); // '123'
72
+ * console.log(sdk.configuration.token); // '123'
71
73
  * ```
72
74
  *
73
75
  * 2. To get the global configuration (singleton instance). It will respect the environment variables.
@@ -75,7 +77,7 @@ export interface Configuration extends ApifyResolvedConfigValues {
75
77
  * ```javascript
76
78
  * import { Configuration } from 'apify';
77
79
  *
78
- * const config = Configuration.getGlobalConfig();
80
+ * const config = Configuration.getGlobalConfiguration();
79
81
  * console.log(config.headless);
80
82
  * console.log(config.persistStateIntervalMillis);
81
83
  * ```
@@ -139,5 +141,5 @@ export declare class Configuration extends CoreConfiguration {
139
141
  * what crawlee internals resolve against; this singleton is only the
140
142
  * fallback for code reaching for a configuration without an explicit one.
141
143
  */
142
- static getGlobalConfig(): Configuration;
144
+ static getGlobalConfiguration(): Configuration;
143
145
  }
@@ -89,13 +89,13 @@ export const apifyConfigFields = {
89
89
  /**
90
90
  * `Configuration` is a value object holding the SDK configuration. We can use it in two ways:
91
91
  *
92
- * 1. When using `Actor` class, we can get the instance configuration via `sdk.config`
92
+ * 1. When using `Actor` class, we can get the instance configuration via `sdk.configuration`
93
93
  *
94
94
  * ```javascript
95
95
  * import { Actor } from 'apify';
96
96
  *
97
97
  * const sdk = new Actor({ token: '123' });
98
- * console.log(sdk.config.token); // '123'
98
+ * console.log(sdk.configuration.token); // '123'
99
99
  * ```
100
100
  *
101
101
  * 2. To get the global configuration (singleton instance). It will respect the environment variables.
@@ -103,7 +103,7 @@ export const apifyConfigFields = {
103
103
  * ```javascript
104
104
  * import { Configuration } from 'apify';
105
105
  *
106
- * const config = Configuration.getGlobalConfig();
106
+ * const config = Configuration.getGlobalConfiguration();
107
107
  * console.log(config.headless);
108
108
  * console.log(config.persistStateIntervalMillis);
109
109
  * ```
@@ -173,7 +173,7 @@ export class Configuration extends CoreConfiguration {
173
173
  * what crawlee internals resolve against; this singleton is only the
174
174
  * fallback for code reaching for a configuration without an explicit one.
175
175
  */
176
- static getGlobalConfig() {
176
+ static getGlobalConfiguration() {
177
177
  Configuration.globalConfig ??= new Configuration();
178
178
  return Configuration.globalConfig;
179
179
  }
package/dist/index.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  export * from './actor.js';
2
- export { ApifyStorageClient } from './apify_storage_client.js';
2
+ export { ApifyStorageBackend, type ApifyStorageBackendOptions } from './apify_storage_backend.js';
3
+ export type { RequestQueueAccessMode } from './apify_request_queue_backend.js';
3
4
  export { ArgumentValidationError } from './utils.js';
4
5
  export type { OpenStorageOptions, StorageAlias, StorageId, StorageName, StorageIdentifier, StorageIdentifierWithoutAlias, } from './storage.js';
5
6
  export { ChargeOptions, ChargeResult, ActorPricingInfo, ChargingManager } from './charging.js';
6
7
  export * from './configuration.js';
7
8
  export * from './proxy_configuration.js';
8
9
  export * from './platform_event_manager.js';
9
- export * from './key_value_store.js';
10
- export { Dataset, DatasetDataOptions, DatasetIteratorOptions, DatasetConsumer, DatasetMapper, DatasetReducer, DatasetOptions, DatasetContent, RequestQueue, QueueOperationInfo, RequestQueueOperationOptions, RequestQueueOptions, KeyConsumer, KeyValueStoreOptions, RecordOptions, KeyValueStoreIteratorOptions, log, Log, LoggerOptions, LogLevel, Logger, LoggerJson, LoggerText, } from '@crawlee/core';
10
+ export { Dataset, DatasetDataOptions, DatasetIteratorOptions, DatasetConsumer, DatasetMapper, DatasetReducer, DatasetOptions, DatasetContent, RequestQueue, RequestQueueOperationOptions, RequestQueueOptions, KeyValueStore, KeyConsumer, KeyValueStoreOptions, RecordOptions, KeyValueStoreIteratorOptions, log, Log, LoggerOptions, LogLevel, Logger, LoggerJson, LoggerText, } from '@crawlee/core';
11
+ export type { QueueOperationInfo } from '@crawlee/types';
11
12
  export { ApifyClient, ApifyClientOptions } from 'apify-client';
package/dist/index.js CHANGED
@@ -1,10 +1,9 @@
1
1
  export * from './actor.js';
2
- export { ApifyStorageClient } from './apify_storage_client.js';
2
+ export { ApifyStorageBackend } from './apify_storage_backend.js';
3
3
  export { ArgumentValidationError } from './utils.js';
4
4
  export { ChargingManager } from './charging.js';
5
5
  export * from './configuration.js';
6
6
  export * from './proxy_configuration.js';
7
7
  export * from './platform_event_manager.js';
8
- export * from './key_value_store.js';
9
- export { Dataset, RequestQueue, log, Log, LogLevel, Logger, LoggerJson, LoggerText, } from '@crawlee/core';
8
+ export { Dataset, RequestQueue, KeyValueStore, log, Log, LogLevel, Logger, LoggerJson, LoggerText, } from '@crawlee/core';
10
9
  export { ApifyClient } from 'apify-client';
@@ -1,4 +1,4 @@
1
- import type { Dictionary } from '@crawlee/utils';
1
+ import type { Dictionary } from '@crawlee/types';
2
2
  /**
3
3
  * @ignore
4
4
  */
@@ -39,10 +39,10 @@ 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
- readonly config: Configuration;
42
+ readonly configuration: Configuration;
43
43
  /** Websocket connection to Actor events. */
44
44
  private eventsWs?;
45
- constructor(config?: Configuration);
45
+ constructor(configuration?: Configuration);
46
46
  /**
47
47
  * Initializes `Actor.events` event emitter by creating a connection to a websocket that provides them.
48
48
  * This is an internal function that is automatically called by `Actor.main()`.
@@ -42,14 +42,14 @@ import { Configuration } from './configuration.js';
42
42
  * you can achieve the same effect using `setInterval()` and listening for the `migrating` event.
43
43
  */
44
44
  export class PlatformEventManager extends EventManager {
45
- config;
45
+ configuration;
46
46
  /** Websocket connection to Actor events. */
47
47
  eventsWs;
48
- constructor(config = Configuration.getGlobalConfig()) {
48
+ constructor(configuration = Configuration.getGlobalConfiguration()) {
49
49
  super({
50
- persistStateIntervalMillis: config.persistStateIntervalMillis,
50
+ persistStateIntervalMillis: configuration.persistStateIntervalMillis,
51
51
  });
52
- this.config = config;
52
+ this.configuration = configuration;
53
53
  }
54
54
  /**
55
55
  * Initializes `Actor.events` event emitter by creating a connection to a websocket that provides them.
@@ -60,7 +60,7 @@ export class PlatformEventManager extends EventManager {
60
60
  return;
61
61
  }
62
62
  await super.init();
63
- const eventsWsUrl = this.config.actorEventsWsUrl;
63
+ const eventsWsUrl = this.configuration.actorEventsWsUrl;
64
64
  // Locally there is no web socket to connect, so just print a log message.
65
65
  if (!eventsWsUrl) {
66
66
  this.log.debug(`Environment variable ${ACTOR_ENV_VARS.EVENTS_WEBSOCKET_URL} is not set, no events from Apify platform will be emitted.`);
@@ -151,7 +151,7 @@ export interface ProxyInfo extends CoreProxyInfo {
151
151
  * @category Scaling
152
152
  */
153
153
  export declare class ProxyConfiguration extends CoreProxyConfiguration {
154
- readonly config: Configuration;
154
+ readonly configuration: Configuration;
155
155
  private groups;
156
156
  private countryCode?;
157
157
  private subdivisionCode?;
@@ -159,10 +159,11 @@ export declare class ProxyConfiguration extends CoreProxyConfiguration {
159
159
  private hostname;
160
160
  private port?;
161
161
  private usesApifyProxy?;
162
+ protected readonly log: import("@apify/log").Log;
162
163
  /**
163
164
  * @internal
164
165
  */
165
- constructor(options?: ProxyConfigurationOptions, config?: Configuration);
166
+ constructor(options?: ProxyConfigurationOptions, configuration?: Configuration);
166
167
  /**
167
168
  * Loads proxy password if token is provided and checks access to Apify Proxy and provided proxy groups
168
169
  * if Apify Proxy configuration is used.
@@ -219,5 +220,10 @@ export declare class ProxyConfiguration extends CoreProxyConfiguration {
219
220
  * @internal
220
221
  */
221
222
  protected _throwCannotCombineCustomWithApify(): void;
223
+ /**
224
+ * Throws cannot combine custom proxies with custom generating function
225
+ * @internal
226
+ */
227
+ protected _throwCannotCombineCustomMethods(): void;
222
228
  }
223
229
  export {};
@@ -4,6 +4,7 @@ import { json } from 'node:stream/consumers';
4
4
  import { ProxyConfiguration as CoreProxyConfiguration } from '@crawlee/core';
5
5
  import { z } from 'zod';
6
6
  import { APIFY_ENV_VARS, APIFY_PROXY_VALUE_REGEX } from '@apify/consts';
7
+ import defaultLog from '@apify/log';
7
8
  import { cryptoRandomObjectId } from '@apify/utilities';
8
9
  import { Actor } from './actor.js';
9
10
  import { Configuration } from './configuration.js';
@@ -51,7 +52,7 @@ const SESSION_ID_LENGTH = 12;
51
52
  * @category Scaling
52
53
  */
53
54
  export class ProxyConfiguration extends CoreProxyConfiguration {
54
- config;
55
+ configuration;
55
56
  groups;
56
57
  countryCode;
57
58
  subdivisionCode;
@@ -59,17 +60,18 @@ export class ProxyConfiguration extends CoreProxyConfiguration {
59
60
  hostname;
60
61
  port;
61
62
  usesApifyProxy;
63
+ log = defaultLog.child({ prefix: 'ProxyConfiguration' });
62
64
  /**
63
65
  * @internal
64
66
  */
65
- constructor(options = {}, config = Configuration.getGlobalConfig()) {
67
+ constructor(options = {}, configuration = Configuration.getGlobalConfiguration()) {
66
68
  const { proxyUrls, newUrlFunction, ...rest } = options;
67
69
  super({
68
70
  proxyUrls,
69
71
  newUrlFunction,
70
72
  ['validateRequired']: false,
71
73
  });
72
- this.config = config;
74
+ this.configuration = configuration;
73
75
  validate(z
74
76
  .object({
75
77
  groups: z.array(z.string().regex(APIFY_PROXY_VALUE_REGEX)).optional(),
@@ -81,12 +83,12 @@ export class ProxyConfiguration extends CoreProxyConfiguration {
81
83
  password: z.string().optional(),
82
84
  })
83
85
  .strict(), rest);
84
- const { groups = [], apifyProxyGroups = [], countryCode, apifyProxyCountry, subdivisionCode, apifyProxySubdivision, password = config.proxyPassword, } = options;
86
+ const { groups = [], apifyProxyGroups = [], countryCode, apifyProxyCountry, subdivisionCode, apifyProxySubdivision, password = configuration.proxyPassword, } = options;
85
87
  const groupsToUse = groups.length ? groups : apifyProxyGroups;
86
88
  const countryCodeToUse = countryCode || apifyProxyCountry;
87
89
  const subdivisionCodeToUse = subdivisionCode || apifyProxySubdivision;
88
- const hostname = config.proxyHostname;
89
- const port = config.proxyPort;
90
+ const hostname = configuration.proxyHostname;
91
+ const port = configuration.proxyPort;
90
92
  // The Apify Proxy subdivision is expressed as part of the country
91
93
  // username parameter (`country-US_CA`), so a country is required.
92
94
  if (subdivisionCodeToUse && !countryCodeToUse) {
@@ -104,7 +106,7 @@ export class ProxyConfiguration extends CoreProxyConfiguration {
104
106
  this.password = password;
105
107
  this.hostname = hostname;
106
108
  this.port = port;
107
- this.usesApifyProxy = !this.proxyUrls && !this.newUrlFunction;
109
+ this.usesApifyProxy = !proxyUrls && !newUrlFunction;
108
110
  if (proxyUrls && proxyUrls.some((url) => url?.includes('apify.com'))) {
109
111
  this.log.warning('Some Apify proxy features may work incorrectly. Please consider setting up Apify properties instead of `proxyUrls`.\n' +
110
112
  'See https://docs.apify.com/sdk/js/docs/concepts/proxy-management#apify-proxy-configuration');
@@ -174,7 +176,7 @@ export class ProxyConfiguration extends CoreProxyConfiguration {
174
176
  * `proxyUrls`, the URLs are rotated round-robin.
175
177
  */
176
178
  async newUrl(options) {
177
- if (this.newUrlFunction || this.proxyUrls) {
179
+ if (!this.usesApifyProxy) {
178
180
  return super.newUrl(options);
179
181
  }
180
182
  return this.composeDefaultUrl(cryptoRandomObjectId(SESSION_ID_LENGTH));
@@ -210,7 +212,7 @@ export class ProxyConfiguration extends CoreProxyConfiguration {
210
212
  */
211
213
  // TODO: Make this private
212
214
  async _setPasswordIfToken() {
213
- const { token } = this.config;
215
+ const { token } = this.configuration;
214
216
  if (!token)
215
217
  return;
216
218
  try {
@@ -241,6 +243,8 @@ export class ProxyConfiguration extends CoreProxyConfiguration {
241
243
  return true;
242
244
  }
243
245
  const { connected, connectionError, isManInTheMiddle } = status;
246
+ // Declared `readonly false` on the base class; the status check is the one place that
247
+ // learns the actual value, so bypass the readonly marker.
244
248
  this.isManInTheMiddle = isManInTheMiddle;
245
249
  if (connected) {
246
250
  return true;
@@ -258,7 +262,7 @@ export class ProxyConfiguration extends CoreProxyConfiguration {
258
262
  * Apify Proxy can be down for a second or a minute, but this should not crash processes.
259
263
  */
260
264
  async _fetchStatus() {
261
- const { proxyStatusUrl } = this.config;
265
+ const { proxyStatusUrl } = this.configuration;
262
266
  const statusUrl = `${proxyStatusUrl}/?format=json`;
263
267
  const proxyUrl = await this.newUrl();
264
268
  // Without a proxy URL we can't perform the (proxied) status check.
@@ -319,4 +323,11 @@ export class ProxyConfiguration extends CoreProxyConfiguration {
319
323
  '"options.groups", "options.apifyProxyGroups", "options.countryCode", "options.apifyProxyCountry", ' +
320
324
  '"options.subdivisionCode" or "options.apifyProxySubdivision".');
321
325
  }
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
+ }
322
333
  }
package/dist/storage.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { Constructor, IStorage, StorageOpenOptions } from '@crawlee/core';
2
- import type { StorageClient } from '@crawlee/types';
1
+ import type { IStorage, StorageOpenOptions } from '@crawlee/core';
2
+ import type { Constructor, StorageBackend } from '@crawlee/types';
3
3
  import type { Configuration } from './configuration.js';
4
4
  export interface OpenStorageOptions {
5
5
  /**
@@ -47,7 +47,7 @@ export type StorageIdentifier = string | StorageAlias | StorageId | StorageName;
47
47
  export type StorageIdentifierWithoutAlias = string | StorageId | StorageName;
48
48
  export interface OpenStorageContext {
49
49
  config: Configuration;
50
- client?: StorageClient;
50
+ backend?: StorageBackend;
51
51
  purgedStorageAliases: Set<string>;
52
52
  }
53
53
  /**
package/dist/storage.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ApifyStorageClient } from './apify_storage_client.js';
1
+ import { ApifyStorageBackend } from './apify_storage_backend.js';
2
2
  const STORAGE_TYPE_KEYS = {
3
3
  Dataset: 'datasets',
4
4
  KeyValueStore: 'keyValueStores',
@@ -57,7 +57,7 @@ function resolveStorageIdentifier(storageType, identifier, config) {
57
57
  */
58
58
  export async function openStorage(storageClass, identifier, context) {
59
59
  const isAlias = identifier !== null && identifier !== undefined && typeof identifier === 'object' && 'alias' in identifier;
60
- if (isAlias && !context.config.isAtHome && context.client instanceof ApifyStorageClient) {
60
+ if (isAlias && !context.config.isAtHome && context.backend instanceof ApifyStorageBackend) {
61
61
  throw new Error('The `alias` option is not allowed for Apify-based storages running outside of Apify');
62
62
  }
63
63
  const resolvedIdOrName = resolveStorageIdentifier(storageClass.name, identifier, context.config);
@@ -69,11 +69,11 @@ export async function openStorage(storageClass, identifier, context) {
69
69
  !context.purgedStorageAliases.has(identifier.alias)) {
70
70
  context.purgedStorageAliases.add(identifier.alias);
71
71
  const existingStorage = await storageClass.open(resolvedIdOrName ?? null, {
72
- storageClient: context.client,
72
+ storageBackend: context.backend,
73
73
  });
74
74
  await existingStorage.drop();
75
75
  }
76
76
  return storageClass.open(resolvedIdOrName ?? null, {
77
- storageClient: context.client,
77
+ storageBackend: context.backend,
78
78
  });
79
79
  }
package/dist/utils.d.ts CHANGED
@@ -5,6 +5,11 @@ import type { z } from 'zod';
5
5
  * @internal
6
6
  */
7
7
  export declare function isNonEmptyObject(value: unknown): value is Record<string, unknown>;
8
+ /**
9
+ * Converts a `SNAKE_CASE` string to `camelCase` (previously provided by `@crawlee/utils`).
10
+ * @internal
11
+ */
12
+ export declare function snakeCaseToCamelCase(snakeCaseStr: string): string;
8
13
  /**
9
14
  * Error thrown when an argument fails validation (e.g. by `Actor.addWebhook()`
10
15
  * or the `ProxyConfiguration` constructor).
package/dist/utils.js CHANGED
@@ -20,6 +20,17 @@ const require = createRequire(import.meta.url);
20
20
  export function isNonEmptyObject(value) {
21
21
  return typeof value === 'object' && value !== null && !Array.isArray(value) && Object.keys(value).length > 0;
22
22
  }
23
+ /**
24
+ * Converts a `SNAKE_CASE` string to `camelCase` (previously provided by `@crawlee/utils`).
25
+ * @internal
26
+ */
27
+ export function snakeCaseToCamelCase(snakeCaseStr) {
28
+ return snakeCaseStr
29
+ .toLowerCase()
30
+ .split('_')
31
+ .map((part, index) => (index > 0 ? part.charAt(0).toUpperCase() + part.slice(1) : part))
32
+ .join('');
33
+ }
23
34
  /** Formats a zod issue path like `groups[0]` or `countryCode`. */
24
35
  function formatIssuePath(path) {
25
36
  let out = '';