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.
- package/dist/actor.d.ts +29 -6
- package/dist/actor.js +46 -47
- package/dist/apify_dataset_backend.d.ts +18 -0
- package/dist/apify_dataset_backend.js +33 -0
- package/dist/apify_key_value_store_backend.d.ts +23 -0
- package/dist/apify_key_value_store_backend.js +54 -0
- package/dist/apify_request_queue_backend.d.ts +78 -0
- package/dist/apify_request_queue_backend.js +114 -0
- package/dist/apify_request_queue_shared_backend.d.ts +44 -0
- package/dist/apify_request_queue_shared_backend.js +211 -0
- package/dist/apify_request_queue_single_backend.d.ts +52 -0
- package/dist/apify_request_queue_single_backend.js +256 -0
- package/dist/apify_storage_backend.d.ts +109 -0
- package/dist/apify_storage_backend.js +249 -0
- package/dist/configuration.d.ts +6 -4
- package/dist/configuration.js +4 -4
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -3
- package/dist/input-schemas.d.ts +1 -1
- package/dist/platform_event_manager.d.ts +2 -2
- package/dist/platform_event_manager.js +5 -5
- package/dist/proxy_configuration.d.ts +8 -2
- package/dist/proxy_configuration.js +21 -10
- package/dist/storage.d.ts +3 -3
- package/dist/storage.js +4 -4
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +11 -0
- package/package.json +6 -6
- package/dist/apify_storage_client.d.ts +0 -66
- package/dist/apify_storage_client.js +0 -200
- package/dist/key_value_store.d.ts +0 -21
- package/dist/key_value_store.js +0 -41
|
@@ -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
|
+
}
|
package/dist/configuration.d.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
|
144
|
+
static getGlobalConfiguration(): Configuration;
|
|
143
145
|
}
|
package/dist/configuration.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
|
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 {
|
|
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
|
|
10
|
-
export {
|
|
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 {
|
|
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
|
|
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';
|
package/dist/input-schemas.d.ts
CHANGED
|
@@ -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
|
|
42
|
+
readonly configuration: Configuration;
|
|
43
43
|
/** Websocket connection to Actor events. */
|
|
44
44
|
private eventsWs?;
|
|
45
|
-
constructor(
|
|
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
|
-
|
|
45
|
+
configuration;
|
|
46
46
|
/** Websocket connection to Actor events. */
|
|
47
47
|
eventsWs;
|
|
48
|
-
constructor(
|
|
48
|
+
constructor(configuration = Configuration.getGlobalConfiguration()) {
|
|
49
49
|
super({
|
|
50
|
-
persistStateIntervalMillis:
|
|
50
|
+
persistStateIntervalMillis: configuration.persistStateIntervalMillis,
|
|
51
51
|
});
|
|
52
|
-
this.
|
|
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.
|
|
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
|
|
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,
|
|
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
|
-
|
|
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 = {},
|
|
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.
|
|
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 =
|
|
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 =
|
|
89
|
-
const port =
|
|
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 = !
|
|
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.
|
|
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.
|
|
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.
|
|
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 {
|
|
2
|
-
import type {
|
|
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
|
-
|
|
50
|
+
backend?: StorageBackend;
|
|
51
51
|
purgedStorageAliases: Set<string>;
|
|
52
52
|
}
|
|
53
53
|
/**
|
package/dist/storage.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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.
|
|
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
|
-
|
|
72
|
+
storageBackend: context.backend,
|
|
73
73
|
});
|
|
74
74
|
await existingStorage.drop();
|
|
75
75
|
}
|
|
76
76
|
return storageClass.open(resolvedIdOrName ?? null, {
|
|
77
|
-
|
|
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 = '';
|