scrapely 1.2.33 → 1.2.34

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/NOTICE +12 -26
  3. package/README.md +1 -2
  4. package/dist/actor.d.ts +32 -25
  5. package/dist/actor.js +39 -23
  6. package/dist/charging.d.ts +49 -4
  7. package/dist/charging.js +293 -44
  8. package/dist/configuration.js +2 -2
  9. package/dist/index.js +1 -1
  10. package/dist/node-wrapper.js +1 -1
  11. package/dist/patch-browser-pool.js +7 -3
  12. package/dist/proxy-configuration.d.ts +11 -11
  13. package/dist/proxy-configuration.js +1 -1
  14. package/dist/proxy-input-aliases.d.ts +1 -1
  15. package/dist/proxy-input-aliases.js +1 -1
  16. package/dist/request-list.d.ts +2 -2
  17. package/dist/request-list.js +1 -1
  18. package/dist/scrapely-proxy-configuration.d.ts +15 -16
  19. package/dist/scrapely-proxy-configuration.js +27 -31
  20. package/dist/storage/api-storage-client.d.ts +4 -5
  21. package/dist/storage/api-storage-client.js +17 -15
  22. package/dist/storage/billing-tracker.d.ts +1 -1
  23. package/dist/storage/billing-tracker.js +1 -1
  24. package/dist/storage/key-value-store-client.d.ts +0 -3
  25. package/dist/storage/key-value-store-client.js +2 -5
  26. package/dist/storage/kv-store-collection-client.js +6 -2
  27. package/dist/storage/kvs-iteration.d.ts +1 -1
  28. package/dist/storage/kvs-iteration.js +1 -1
  29. package/dist/storage/request-queue-client.d.ts +1 -1
  30. package/dist/storage/request-queue-client.js +5 -5
  31. package/dist/storage/rq-lock-script.d.ts +2 -2
  32. package/dist/storage/rq-lock-script.js +2 -2
  33. package/dist/storage/run-metadata.js +1 -1
  34. package/dist/storage/ttl-manager.d.ts +7 -6
  35. package/dist/storage/ttl-manager.js +5 -4
  36. package/dist/storage-identifiers.d.ts +1 -2
  37. package/dist/storage-identifiers.js +1 -2
  38. package/dist/utils.d.ts +41 -8
  39. package/dist/utils.js +49 -3
  40. package/package.json +22 -11
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * Proxy input objects with both namings. An input-schema proxy field may be
4
4
  * written with the Scrapely names (`useScrapelyProxy`, `scrapelyProxyGroups`,
5
- * `scrapelyProxyCountry`, `scrapelyProxySubdivision`) or the Apify names
5
+ * `scrapelyProxyCountry`, `scrapelyProxySubdivision`) or the legacy names
6
6
  * (`useApifyProxy`, `apifyProxy*`). Actor.getInput() and Actor.input return
7
7
  * every set option under both names, so actor code that reads either naming
8
8
  * sees the user's choice.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * ScrapelyRequestList - parses the standard Scrapely/Apify `requestListSources` input format.
2
+ * ScrapelyRequestList - parses the standard Scrapely `requestListSources` input format.
3
3
  *
4
4
  * Parity port of the Python SDK's `scrapely.request_loaders.ScrapelyRequestList`.
5
5
  * It supports two kinds of entries:
@@ -24,7 +24,7 @@
24
24
  import { Request } from '@crawlee/core';
25
25
  /**
26
26
  * A single entry of the `requestListSources` input format.
27
- * Accepts both camelCase (Apify/Scrapely UI) and snake_case keys.
27
+ * Accepts both camelCase (Scrapely UI) and snake_case keys.
28
28
  */
29
29
  export interface RequestListSource {
30
30
  /** Direct URL to crawl (mutually exclusive with `requestsFromUrl`). */
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * ScrapelyRequestList - parses the standard Scrapely/Apify `requestListSources` input format.
3
+ * ScrapelyRequestList - parses the standard Scrapely `requestListSources` input format.
4
4
  *
5
5
  * Parity port of the Python SDK's `scrapely.request_loaders.ScrapelyRequestList`.
6
6
  * It supports two kinds of entries:
@@ -6,12 +6,12 @@ type TieredProxyOptions = Parameters<CrawleeProxyConfiguration['newUrl']>[1];
6
6
  /** Returns true for platform-proxy sentinel values ('auto', 'http://auto'). */
7
7
  export declare function isPlatformProxySentinel(url: string | null | undefined): boolean;
8
8
  /**
9
- * Validates a proxy session ID the same way the Apify SDK does: at most 50
9
+ * Validates a proxy session ID: at most 50
10
10
  * characters, and only `0-9`, `a-z`, `A-Z`, `.`, `_` and `~`. Throws otherwise.
11
11
  */
12
12
  export declare function validateProxySessionId(sessionId: string): string;
13
13
  /**
14
- * Resolves and validates the country/subdivision options (Apify parity).
14
+ * Resolves and validates the country/subdivision options.
15
15
  * Country must be a two-letter ISO code; subdivision an ISO 3166-2 code
16
16
  * (1-3 uppercase alphanumeric characters) and requires a country. Returns
17
17
  * the normalized values used by the username grammar.
@@ -20,21 +20,21 @@ export declare function resolveCountryOptions(options: ProxyConfigurationOptions
20
20
  countryCode?: string;
21
21
  subdivisionCode?: string;
22
22
  };
23
- /** Status payload returned by the gateway status endpoint (Apify format). */
23
+ /** Status payload returned by the gateway status endpoint. */
24
24
  export interface ProxyStatus {
25
25
  connected: boolean;
26
26
  connectionError: string;
27
27
  isManInTheMiddle: boolean;
28
28
  }
29
29
  /**
30
- * Build a platform proxy URL using the Apify username grammar:
30
+ * Build a platform proxy URL using the gateway username grammar:
31
31
  * http://GROUPS-G1+G2,country-US,session-a1b2c3:PASSWORD@HOST:PORT
32
32
  *
33
33
  * `groups` (joined with `+`), `country` and `session` fragments are
34
34
  * comma-joined. When the server injected a subaccount username
35
35
  * (SCRAPELY_PROXY_USERNAME / APIFY_PROXY_USERNAME) it is prepended so the
36
36
  * gateway can attribute traffic to the right Scrapely tenant; without it the
37
- * username falls back to Apify's `auto` when no fragments apply. The password
37
+ * username falls back to `auto` when no fragments apply. The password
38
38
  * and gateway host/port come from the environment credentials injected by the
39
39
  * server.
40
40
  */
@@ -42,12 +42,12 @@ export declare function buildPlatformProxyUrl(options: ProxyConfigurationOptions
42
42
  /** True when any proxy URL list reserves a platform slot via the 'auto' sentinel. */
43
43
  export declare function hasPlatformSentinel(options: ProxyConfigurationOptions): boolean;
44
44
  /**
45
- * Apify-compatible ProxyConfiguration.
45
+ * Scrapely ProxyConfiguration.
46
46
  *
47
47
  * Modes:
48
48
  * - Platform proxy: when the server injected APIFY_PROXY_* or SCRAPELY_PROXY_*
49
49
  * credentials (Evomi-backed) and no custom proxies were requested, the
50
- * configuration routes through the platform gateway using the Apify
50
+ * configuration routes through the platform gateway using the gateway
51
51
  * username grammar (groups/country/session fragments).
52
52
  * - BYOP: custom proxyUrls / newUrlFunction / tieredProxyUrls are passed
53
53
  * through to Crawlee unchanged.
@@ -68,31 +68,30 @@ export declare class ProxyConfiguration extends CrawleeProxyConfiguration {
68
68
  * Returns a new proxy URL. Translates the internal platform-proxy
69
69
  * placeholder to a concrete platform URL (applying groups/countryCode and
70
70
  * session stickiness) whenever Crawlee's rotation lands on it; BYOP
71
- * entries pass through unchanged. Session IDs are validated like in the
72
- * Apify SDK (max 50 chars, only `0-9 a-z A-Z . _ ~`).
71
+ * entries pass through unchanged. Session IDs are validated (max 50 chars,
72
+ * only `0-9 a-z A-Z . _ ~`).
73
73
  */
74
74
  newUrl(sessionId?: string | number, options?: TieredProxyOptions): Promise<string | undefined>;
75
75
  /**
76
76
  * Returns a new ProxyInfo. When the resolved URL points at the platform
77
- * gateway, `groups`, `countryCode` and `subdivisionCode` are attached
78
- * (Apify parity).
77
+ * gateway, `groups`, `countryCode` and `subdivisionCode` are attached.
79
78
  */
80
79
  newProxyInfo(sessionId?: string | number, options?: TieredProxyOptions): Promise<ProxyInfo | undefined>;
81
80
  /**
82
- * Fetches the gateway status payload (Apify format) by sending
81
+ * Fetches the gateway status payload by sending
83
82
  * `GET {statusUrl}/?format=json` *through the platform gateway*, so the
84
83
  * proxy handshake itself authenticates the run's credentials. Retries
85
- * connection errors (Apify parity: 4s timeout, 2 attempts); returns
84
+ * connection errors (4s timeout, 2 attempts); returns
86
85
  * undefined when the endpoint stays unreachable.
87
86
  */
88
87
  private fetchProxyStatus;
89
88
  /**
90
89
  * Verifies the proxy configuration. For platform proxy mode with an
91
- * injected status URL, performs the Apify-format access check through the
90
+ * injected status URL, performs the access check through the
92
91
  * gateway (`GET {statusUrl}/?format=json`). When the gateway answers
93
92
  * `connected: false` (e.g. an unknown proxy group), it throws with the
94
- * gateway's `connectionError` on the platform and warns locally, like
95
- * Apify's `_checkAccess`; timeouts and HTTP errors only warn.
93
+ * gateway's `connectionError` on the platform and warns locally;
94
+ * timeouts and HTTP errors only warn.
96
95
  * BYOP mode only verifies that a test URL can be generated.
97
96
  */
98
97
  initialize(options?: {
@@ -18,26 +18,25 @@ const node_tls_1 = __importDefault(require("node:tls"));
18
18
  const core_1 = require("@crawlee/core");
19
19
  const configuration_js_1 = require("./configuration.js");
20
20
  const proxy_configuration_js_1 = require("./proxy-configuration.js");
21
- /** Max length of a proxy session ID (Apify parity). */
21
+ /** Max length of a proxy session ID. */
22
22
  const MAX_SESSION_ID_LENGTH = 50;
23
- /** Allowed session ID characters (Apify parity): 0-9 a-z A-Z . _ ~ */
23
+ /** Allowed session ID characters: 0-9 a-z A-Z . _ ~ */
24
24
  const SESSION_ID_REGEX = /^[a-zA-Z0-9._~]+$/;
25
- /** Two-letter ISO country code (Apify parity). */
25
+ /** Two-letter ISO country code. */
26
26
  const COUNTRY_CODE_REGEX = /^[A-Z]{2}$/;
27
27
  /**
28
28
  * ISO 3166-2 subdivision codes are 1-3 uppercase alphanumeric characters,
29
- * e.g. 'CA' (California), 'NSW' (New South Wales), '9' (Wien, AT-9)
30
- * (Apify parity).
29
+ * e.g. 'CA' (California), 'NSW' (New South Wales), '9' (Wien, AT-9).
31
30
  */
32
31
  const SUBDIVISION_CODE_REGEX = /^[A-Z0-9]{1,3}$/;
33
- /** Status-check timeout / attempts (Apify parity). */
32
+ /** Status-check timeout / attempts. */
34
33
  const CHECK_ACCESS_REQUEST_TIMEOUT_MILLIS = 4_000;
35
34
  const CHECK_ACCESS_MAX_ATTEMPTS = 2;
36
35
  /**
37
36
  * Sentinel values that reserve a slot for the platform proxy inside
38
37
  * `proxyUrls` / `tieredProxyUrls` / `tieredProxyConfig[].proxyUrls`,
39
38
  * allowing BYOP and platform proxies to be mixed in one configuration
40
- * (Apify-compatible: both `'auto'` and `'http://auto'` are accepted).
39
+ * (both `'auto'` and `'http://auto'` are accepted).
41
40
  */
42
41
  const PLATFORM_PROXY_SENTINELS = new Set(['auto', 'http://auto']);
43
42
  /**
@@ -64,7 +63,7 @@ function isPlatformProxySentinel(url) {
64
63
  return url != null && PLATFORM_PROXY_SENTINELS.has(url);
65
64
  }
66
65
  /**
67
- * Validates a proxy session ID the same way the Apify SDK does: at most 50
66
+ * Validates a proxy session ID: at most 50
68
67
  * characters, and only `0-9`, `a-z`, `A-Z`, `.`, `_` and `~`. Throws otherwise.
69
68
  */
70
69
  function validateProxySessionId(sessionId) {
@@ -84,7 +83,7 @@ function platformGroups(options) {
84
83
  return options.scrapelyProxyGroups ?? options.apifyProxyGroups;
85
84
  }
86
85
  /**
87
- * Resolves and validates the country/subdivision options (Apify parity).
86
+ * Resolves and validates the country/subdivision options.
88
87
  * Country must be a two-letter ISO code; subdivision an ISO 3166-2 code
89
88
  * (1-3 uppercase alphanumeric characters) and requires a country. Returns
90
89
  * the normalized values used by the username grammar.
@@ -109,7 +108,7 @@ function resolveCountryOptions(options) {
109
108
  /**
110
109
  * Strips URL-breaking characters from a username fragment. The gateway grammar
111
110
  * is comma-based, so fragments themselves must never contain `,@:/?#` or
112
- * whitespace (Apify behavior: the username is NOT percent-encoded).
111
+ * whitespace (the username is NOT percent-encoded).
113
112
  */
114
113
  function sanitizeUsernameFragment(value) {
115
114
  return value.replace(/[:@/?#\s]/g, '');
@@ -126,8 +125,7 @@ function encodeUserInfoPart(value) {
126
125
  }
127
126
  /**
128
127
  * Gateway upstream error codes (590-599, proxy-chain semantics). The gateway
129
- * uses these instead of a bare 502 to describe upstream failures — the same
130
- * table Apify Proxy uses. See docs/evomi-integration.md for the contract.
128
+ * uses these instead of a bare 502 to describe upstream failures. See docs/evomi-integration.md for the contract.
131
129
  */
132
130
  const PROXY_STATUS_CODE_DESCRIPTIONS = {
133
131
  590: 'Non Successful: upstream responded with a non-200 status code',
@@ -223,14 +221,14 @@ async function requestThroughProxy(targetUrl, proxyUrl, timeoutMs) {
223
221
  });
224
222
  }
225
223
  /**
226
- * Build a platform proxy URL using the Apify username grammar:
224
+ * Build a platform proxy URL using the gateway username grammar:
227
225
  * http://GROUPS-G1+G2,country-US,session-a1b2c3:PASSWORD@HOST:PORT
228
226
  *
229
227
  * `groups` (joined with `+`), `country` and `session` fragments are
230
228
  * comma-joined. When the server injected a subaccount username
231
229
  * (SCRAPELY_PROXY_USERNAME / APIFY_PROXY_USERNAME) it is prepended so the
232
230
  * gateway can attribute traffic to the right Scrapely tenant; without it the
233
- * username falls back to Apify's `auto` when no fragments apply. The password
231
+ * username falls back to `auto` when no fragments apply. The password
234
232
  * and gateway host/port come from the environment credentials injected by the
235
233
  * server.
236
234
  */
@@ -238,7 +236,7 @@ function buildPlatformProxyUrl(options, env, sessionId) {
238
236
  const groups = platformGroups(options);
239
237
  const { countryCode, subdivisionCode } = resolveCountryOptions(options);
240
238
  const password = options.password ?? env.password;
241
- // Apify username grammar: groups, then session, then country — with the
239
+ // Username grammar: groups, then session, then country — with the
242
240
  // subdivision merged into the country fragment (ISO 3166-2: `country-US_CA`).
243
241
  const usernameParts = [];
244
242
  if (groups?.length) {
@@ -291,8 +289,7 @@ function hasRealCustomProxyOptions(options) {
291
289
  * `proxyUrls` pass through (sentinels become per-tier placeholders); tiers
292
290
  * without any URLs become per-tier placeholders too, so their platform
293
291
  * options (groups/countryCode/password) and the session ID are applied
294
- * lazily in `newUrl()` — mirroring Apify's `_generateTieredProxyUrls` while
295
- * preserving session stickiness.
292
+ * lazily in `newUrl()` while preserving session stickiness.
296
293
  */
297
294
  function mapTieredProxyConfig(tieredProxyConfig, globalOptions, env, placeholderOptions) {
298
295
  return tieredProxyConfig.map((tier, index) => {
@@ -364,12 +361,12 @@ function needsPlatformProxyUrls(options) {
364
361
  !!options.tieredProxyConfig?.some((tier) => !tier.proxyUrls));
365
362
  }
366
363
  /**
367
- * Apify-compatible ProxyConfiguration.
364
+ * Scrapely ProxyConfiguration.
368
365
  *
369
366
  * Modes:
370
367
  * - Platform proxy: when the server injected APIFY_PROXY_* or SCRAPELY_PROXY_*
371
368
  * credentials (Evomi-backed) and no custom proxies were requested, the
372
- * configuration routes through the platform gateway using the Apify
369
+ * configuration routes through the platform gateway using the gateway
373
370
  * username grammar (groups/country/session fragments).
374
371
  * - BYOP: custom proxyUrls / newUrlFunction / tieredProxyUrls are passed
375
372
  * through to Crawlee unchanged.
@@ -388,7 +385,7 @@ class ProxyConfiguration extends core_1.ProxyConfiguration {
388
385
  constructor(options = {}, config = configuration_js_1.Configuration.getGlobalConfig()) {
389
386
  const { proxyUrls, newUrlFunction } = options;
390
387
  const platformEnv = (0, proxy_configuration_js_1.getPlatformProxyEnv)();
391
- // Validate country/subdivision options eagerly (Apify parity: bad
388
+ // Validate country/subdivision options eagerly (bad
392
389
  // values throw at construction, not on the first newUrl() call).
393
390
  resolveCountryOptions(options);
394
391
  if (hasRealCustomProxyOptions(options) && hasApifyOnlyOptions(options)) {
@@ -429,8 +426,8 @@ class ProxyConfiguration extends core_1.ProxyConfiguration {
429
426
  * Returns a new proxy URL. Translates the internal platform-proxy
430
427
  * placeholder to a concrete platform URL (applying groups/countryCode and
431
428
  * session stickiness) whenever Crawlee's rotation lands on it; BYOP
432
- * entries pass through unchanged. Session IDs are validated like in the
433
- * Apify SDK (max 50 chars, only `0-9 a-z A-Z . _ ~`).
429
+ * entries pass through unchanged. Session IDs are validated (max 50 chars,
430
+ * only `0-9 a-z A-Z . _ ~`).
434
431
  */
435
432
  async newUrl(sessionId, options) {
436
433
  const sid = sessionId != null ? validateProxySessionId(String(sessionId)) : undefined;
@@ -448,8 +445,7 @@ class ProxyConfiguration extends core_1.ProxyConfiguration {
448
445
  }
449
446
  /**
450
447
  * Returns a new ProxyInfo. When the resolved URL points at the platform
451
- * gateway, `groups`, `countryCode` and `subdivisionCode` are attached
452
- * (Apify parity).
448
+ * gateway, `groups`, `countryCode` and `subdivisionCode` are attached.
453
449
  */
454
450
  async newProxyInfo(sessionId, options) {
455
451
  const sid = sessionId != null ? validateProxySessionId(String(sessionId)) : undefined;
@@ -467,10 +463,10 @@ class ProxyConfiguration extends core_1.ProxyConfiguration {
467
463
  return info;
468
464
  }
469
465
  /**
470
- * Fetches the gateway status payload (Apify format) by sending
466
+ * Fetches the gateway status payload by sending
471
467
  * `GET {statusUrl}/?format=json` *through the platform gateway*, so the
472
468
  * proxy handshake itself authenticates the run's credentials. Retries
473
- * connection errors (Apify parity: 4s timeout, 2 attempts); returns
469
+ * connection errors (4s timeout, 2 attempts); returns
474
470
  * undefined when the endpoint stays unreachable.
475
471
  */
476
472
  async fetchProxyStatus() {
@@ -519,18 +515,18 @@ class ProxyConfiguration extends core_1.ProxyConfiguration {
519
515
  }
520
516
  }
521
517
  catch {
522
- // Retry connection errors (Apify behavior).
518
+ // Retry connection errors.
523
519
  }
524
520
  }
525
521
  return undefined;
526
522
  }
527
523
  /**
528
524
  * Verifies the proxy configuration. For platform proxy mode with an
529
- * injected status URL, performs the Apify-format access check through the
525
+ * injected status URL, performs the access check through the
530
526
  * gateway (`GET {statusUrl}/?format=json`). When the gateway answers
531
527
  * `connected: false` (e.g. an unknown proxy group), it throws with the
532
- * gateway's `connectionError` on the platform and warns locally, like
533
- * Apify's `_checkAccess`; timeouts and HTTP errors only warn.
528
+ * gateway's `connectionError` on the platform and warns locally;
529
+ * timeouts and HTTP errors only warn.
534
530
  * BYOP mode only verifies that a test URL can be generated.
535
531
  */
536
532
  async initialize(options = {}) {
@@ -538,7 +534,7 @@ class ProxyConfiguration extends core_1.ProxyConfiguration {
538
534
  if (!checkAccess) {
539
535
  return true;
540
536
  }
541
- // Apify-parity access check: GET {statusUrl}/?format=json *through the
537
+ // Access check: GET {statusUrl}/?format=json *through the
542
538
  // platform gateway* so the proxy handshake itself authenticates the
543
539
  // run's credentials.
544
540
  if (checkAccess && this.platformEnv?.statusUrl) {
@@ -114,6 +114,8 @@ export declare abstract class BaseApiClient {
114
114
  */
115
115
  private send;
116
116
  protected request<T>(method: string, path: string, body?: any, contentType?: string, options?: ApiRequestOptions): Promise<T>;
117
+ /** HEAD request: true on 2xx, false on 404; other errors throw. */
118
+ protected exists(path: string): Promise<boolean>;
117
119
  protected requestWithHeaders<T>(method: string, path: string, body?: any, contentType?: string): Promise<{
118
120
  data: T;
119
121
  contentType: string;
@@ -171,7 +173,6 @@ export declare class ApiKeyValueStoreClient extends BaseApiClient implements Key
171
173
  entries(options?: KeyValueStoreClientListOptions): AsyncIterable<[string, unknown]> & Promise<[string, unknown][]>;
172
174
  /**
173
175
  * Iterates over all keys in the key-value store, calling the iteratee function for each key.
174
- * Similar to Apify's KeyValueStore.forEachKey() method.
175
176
  */
176
177
  forEachKey(iteratee: (key: string, index: number, info: {
177
178
  size: number;
@@ -179,7 +180,6 @@ export declare class ApiKeyValueStoreClient extends BaseApiClient implements Key
179
180
  /**
180
181
  * Gets a value from the key-value store that is automatically saved when modified.
181
182
  * If the key doesn't exist, returns the defaultValue and stores it for future use.
182
- * Similar to Apify's KeyValueStore.getAutoSavedValue() method.
183
183
  *
184
184
  * @param key - The key of the record to get
185
185
  * @param defaultValue - The default value to return and store if the key doesn't exist
@@ -189,7 +189,6 @@ export declare class ApiKeyValueStoreClient extends BaseApiClient implements Key
189
189
  /**
190
190
  * Returns a URL for the given key that may be used to publicly access the value
191
191
  * in the remote key-value store.
192
- * Similar to Apify's KeyValueStore.getPublicUrl() method.
193
192
  *
194
193
  * @param key - The key of the record to get the public URL for
195
194
  * @returns A public URL for accessing the record
@@ -218,11 +217,11 @@ export declare class ApiRequestQueueClient extends BaseApiClient implements Requ
218
217
  prolongRequestLock(id: string, options: ProlongRequestLockOptions): Promise<ProlongRequestLockResult>;
219
218
  deleteRequestLock(id: string, options?: DeleteRequestLockOptions): Promise<void>;
220
219
  /**
221
- * List requests in the queue with optional filtering and pagination (Apify REST API).
220
+ * List requests in the queue with optional filtering and pagination.
222
221
  */
223
222
  listRequests(options?: ListRequestsOptions): Promise<ListRequestsResult>;
224
223
  /**
225
- * Unlock all requests locked by this clientKey (Apify REST API).
224
+ * Unlock all requests locked by this clientKey.
226
225
  */
227
226
  unlockRequests(options: {
228
227
  clientKey?: string;
@@ -100,7 +100,7 @@ class AdaptiveRateLimiter {
100
100
  }
101
101
  }
102
102
  /**
103
- * Unwrap Apify-style v2 response envelopes ({ data: ... }).
103
+ * Unwrap v2 response envelopes ({ data: ... }).
104
104
  */
105
105
  function unwrapData(value) {
106
106
  if (Array.isArray(value))
@@ -545,6 +545,17 @@ class BaseApiClient {
545
545
  return unwrapData(await response.json());
546
546
  });
547
547
  }
548
+ /** HEAD request: true on 2xx, false on 404; other errors throw. */
549
+ async exists(path) {
550
+ try {
551
+ return await this.send('HEAD', path, undefined, undefined, undefined, async () => true);
552
+ }
553
+ catch (err) {
554
+ if (isApiStorageClientError(err) && err.statusCode === 404)
555
+ return false;
556
+ throw err;
557
+ }
558
+ }
548
559
  async requestWithHeaders(method, path, body, contentType) {
549
560
  return this.send(method, path, body, contentType, undefined, async (response) => {
550
561
  if (response.status === 204) {
@@ -723,13 +734,7 @@ class ApiKeyValueStoreClient extends BaseApiClient {
723
734
  await this.request('DELETE', `/key-value-stores/${this.id}`);
724
735
  }
725
736
  async recordExists(key) {
726
- try {
727
- await this.getRecord(key);
728
- return true;
729
- }
730
- catch {
731
- return false;
732
- }
737
+ return this.exists(`/key-value-stores/${this.id}/records/${key}`);
733
738
  }
734
739
  async getRecord(key, options) {
735
740
  try {
@@ -754,7 +759,7 @@ class ApiKeyValueStoreClient extends BaseApiClient {
754
759
  const { key, value, contentType } = record;
755
760
  let body;
756
761
  let ct;
757
- // Handle Readable streams (e.g., fs.createReadStream) - Apify compatibility
762
+ // Handle Readable streams (e.g., fs.createReadStream)
758
763
  // Streams are objects with a pipe method. We need to buffer them.
759
764
  if (value && typeof value === 'object' && typeof value.pipe === 'function') {
760
765
  const chunks = [];
@@ -765,7 +770,7 @@ class ApiKeyValueStoreClient extends BaseApiClient {
765
770
  ct = 'application/octet-stream';
766
771
  }
767
772
  else if (value instanceof Uint8Array) {
768
- // Raw bytes, as Apify stores them; the API returns them unchanged on GET.
773
+ // Raw bytes, stored as-is; the API returns them unchanged on GET.
769
774
  body = value;
770
775
  ct = 'application/octet-stream';
771
776
  }
@@ -816,7 +821,6 @@ class ApiKeyValueStoreClient extends BaseApiClient {
816
821
  }
817
822
  /**
818
823
  * Iterates over all keys in the key-value store, calling the iteratee function for each key.
819
- * Similar to Apify's KeyValueStore.forEachKey() method.
820
824
  */
821
825
  async forEachKey(iteratee, options) {
822
826
  return (0, kvs_iteration_js_1.forEachKeyPaginated)((opts) => this.listKeys(opts), iteratee, options);
@@ -824,7 +828,6 @@ class ApiKeyValueStoreClient extends BaseApiClient {
824
828
  /**
825
829
  * Gets a value from the key-value store that is automatically saved when modified.
826
830
  * If the key doesn't exist, returns the defaultValue and stores it for future use.
827
- * Similar to Apify's KeyValueStore.getAutoSavedValue() method.
828
831
  *
829
832
  * @param key - The key of the record to get
830
833
  * @param defaultValue - The default value to return and store if the key doesn't exist
@@ -842,7 +845,6 @@ class ApiKeyValueStoreClient extends BaseApiClient {
842
845
  /**
843
846
  * Returns a URL for the given key that may be used to publicly access the value
844
847
  * in the remote key-value store.
845
- * Similar to Apify's KeyValueStore.getPublicUrl() method.
846
848
  *
847
849
  * @param key - The key of the record to get the public URL for
848
850
  * @returns A public URL for accessing the record
@@ -965,7 +967,7 @@ class ApiRequestQueueClient extends BaseApiClient {
965
967
  await this.request('DELETE', `/request-queues/${this.id}/requests/${id}/lock${query}`);
966
968
  }
967
969
  /**
968
- * List requests in the queue with optional filtering and pagination (Apify REST API).
970
+ * List requests in the queue with optional filtering and pagination.
969
971
  */
970
972
  async listRequests(options) {
971
973
  const params = new URLSearchParams();
@@ -984,7 +986,7 @@ class ApiRequestQueueClient extends BaseApiClient {
984
986
  return this.request('GET', `/request-queues/${this.id}/requests${query ? `?${query}` : ''}`);
985
987
  }
986
988
  /**
987
- * Unlock all requests locked by this clientKey (Apify REST API).
989
+ * Unlock all requests locked by this clientKey.
988
990
  */
989
991
  async unlockRequests(options) {
990
992
  const query = buildRqQueryParams(options.clientKey ?? this.clientKey, {
@@ -15,7 +15,7 @@ export interface BillingClient {
15
15
  * - Request queues (requests stored)
16
16
  * - Logs (log size)
17
17
  *
18
- * This data is used to calculate storage-hour charges like Apify.
18
+ * This data is used to calculate storage-hour charges.
19
19
  */
20
20
  export declare class BillingTracker {
21
21
  private client;
@@ -10,7 +10,7 @@ exports.BillingTracker = void 0;
10
10
  * - Request queues (requests stored)
11
11
  * - Logs (log size)
12
12
  *
13
- * This data is used to calculate storage-hour charges like Apify.
13
+ * This data is used to calculate storage-hour charges.
14
14
  */
15
15
  class BillingTracker {
16
16
  client;
@@ -36,13 +36,11 @@ export declare class DragonflyKeyValueStoreClient implements KeyValueStoreClient
36
36
  entries(options?: KeyValueStoreClientListOptions): AsyncIterable<[string, unknown]> & Promise<[string, unknown][]>;
37
37
  /**
38
38
  * Iterates over all keys in the key-value store, calling the iteratee function for each key.
39
- * Similar to Apify's KeyValueStore.forEachKey() method.
40
39
  */
41
40
  forEachKey(iteratee: KeyConsumer, options?: ForEachKeyOptions): Promise<void>;
42
41
  /**
43
42
  * Gets a value from the key-value store that is automatically saved when modified.
44
43
  * If the key doesn't exist, returns the defaultValue and stores it for future use.
45
- * Similar to Apify's KeyValueStore.getAutoSavedValue() method.
46
44
  *
47
45
  * @param key - The key of the record to get
48
46
  * @param defaultValue - The default value to return and store if the key doesn't exist
@@ -52,7 +50,6 @@ export declare class DragonflyKeyValueStoreClient implements KeyValueStoreClient
52
50
  /**
53
51
  * Returns a URL for the given key that may be used to publicly access the value
54
52
  * in the remote key-value store.
55
- * Similar to Apify's KeyValueStore.getPublicUrl() method.
56
53
  *
57
54
  * @param key - The key of the record to get the public URL for
58
55
  * @returns A public URL for accessing the record
@@ -134,7 +134,7 @@ class DragonflyKeyValueStoreClient {
134
134
  const { key, value, contentType } = record;
135
135
  let stored;
136
136
  let billingValue = value; // Use for billing estimation (may differ from original if stream)
137
- // Handle Readable streams (e.g., fs.createReadStream) - Apify compatibility
137
+ // Handle Readable streams (e.g., fs.createReadStream)
138
138
  // Streams are objects with a pipe method. We need to buffer them.
139
139
  if (value && typeof value === 'object' && typeof value.pipe === 'function') {
140
140
  const chunks = [];
@@ -266,7 +266,6 @@ class DragonflyKeyValueStoreClient {
266
266
  }
267
267
  /**
268
268
  * Iterates over all keys in the key-value store, calling the iteratee function for each key.
269
- * Similar to Apify's KeyValueStore.forEachKey() method.
270
269
  */
271
270
  async forEachKey(iteratee, options) {
272
271
  return (0, kvs_iteration_js_1.forEachKeyPaginated)((opts) => this._listKeys(opts), iteratee, options);
@@ -274,7 +273,6 @@ class DragonflyKeyValueStoreClient {
274
273
  /**
275
274
  * Gets a value from the key-value store that is automatically saved when modified.
276
275
  * If the key doesn't exist, returns the defaultValue and stores it for future use.
277
- * Similar to Apify's KeyValueStore.getAutoSavedValue() method.
278
276
  *
279
277
  * @param key - The key of the record to get
280
278
  * @param defaultValue - The default value to return and store if the key doesn't exist
@@ -292,14 +290,13 @@ class DragonflyKeyValueStoreClient {
292
290
  /**
293
291
  * Returns a URL for the given key that may be used to publicly access the value
294
292
  * in the remote key-value store.
295
- * Similar to Apify's KeyValueStore.getPublicUrl() method.
296
293
  *
297
294
  * @param key - The key of the record to get the public URL for
298
295
  * @returns A public URL for accessing the record
299
296
  */
300
297
  getPublicUrl(key) {
301
298
  const config = configuration_js_1.Configuration.getGlobalConfig();
302
- const baseUrl = config.get('apiPublicBaseUrl') || config.get('apiBaseUrl') || 'https://api-staging.scrape.ly';
299
+ const baseUrl = config.get('apiPublicBaseUrl') || config.get('apiBaseUrl') || 'https://api.scrape.ly';
303
300
  return `${baseUrl}/v2/key-value-stores/${this.id}/records/${key}`;
304
301
  }
305
302
  }
@@ -16,10 +16,14 @@ class DragonflyKeyValueStoreCollectionClient {
16
16
  }
17
17
  const id = (0, utils_js_1.generateId)();
18
18
  const now = (0, utils_js_1.nowISO)();
19
+ // Derive ownership from the active billing context (as the request-queue
20
+ // client does); the server authorizes against this persisted field.
21
+ // The legacy '1' placeholder applies only when no context is present.
22
+ const userId = this.client.billingContext?.userId ?? '1';
19
23
  const data = {
20
24
  id,
21
25
  name,
22
- userId: '1',
26
+ userId,
23
27
  createdAt: now,
24
28
  modifiedAt: now,
25
29
  accessedAt: now,
@@ -33,7 +37,7 @@ class DragonflyKeyValueStoreCollectionClient {
33
37
  await this.client.redis.hset(infoKey, {
34
38
  id,
35
39
  name: name ?? '',
36
- userId: '1',
40
+ userId,
37
41
  createdAt: now.toISOString(),
38
42
  modifiedAt: now.toISOString(),
39
43
  accessedAt: now.toISOString(),
@@ -1,7 +1,7 @@
1
1
  import type { KeyValueStoreClientListData, KeyValueStoreClientListOptions, KeyValueStoreItemData } from '@crawlee/types';
2
2
  export type ListKeysFn = (options?: KeyValueStoreClientListOptions) => Promise<KeyValueStoreClientListData>;
3
3
  /**
4
- * Filter keys by Apify-style collection config from a KVS schema.
4
+ * Filter keys by collection config from a KVS schema.
5
5
  */
6
6
  export declare function filterKeysByCollection(allKeys: string[], schemaJson: string | undefined, collection: string): string[];
7
7
  export declare function paginateKeyItems(listKeys: ListKeysFn, options?: KeyValueStoreClientListOptions): AsyncGenerator<KeyValueStoreItemData>;
@@ -7,7 +7,7 @@ exports.makePaginatingKeysIterable = makePaginatingKeysIterable;
7
7
  exports.makePaginatingValuesIterable = makePaginatingValuesIterable;
8
8
  exports.makePaginatingEntriesIterable = makePaginatingEntriesIterable;
9
9
  /**
10
- * Filter keys by Apify-style collection config from a KVS schema.
10
+ * Filter keys by collection config from a KVS schema.
11
11
  */
12
12
  function filterKeysByCollection(allKeys, schemaJson, collection) {
13
13
  if (!schemaJson) {
@@ -44,7 +44,7 @@ export declare class DragonflyRequestQueueClient implements RequestQueueClient {
44
44
  */
45
45
  private trackStorageBytes;
46
46
  /**
47
- * Track clientKey access for hadMultipleClients (per Apify spec).
47
+ * Track clientKey access for hadMultipleClients.
48
48
  * Returns the current hadMultipleClients flag after update.
49
49
  */
50
50
  private trackClientKey;
@@ -66,7 +66,7 @@ class DragonflyRequestQueueClient {
66
66
  }
67
67
  }
68
68
  /**
69
- * Track clientKey access for hadMultipleClients (per Apify spec).
69
+ * Track clientKey access for hadMultipleClients.
70
70
  * Returns the current hadMultipleClients flag after update.
71
71
  */
72
72
  async trackClientKey(clientKey) {
@@ -333,11 +333,11 @@ class DragonflyRequestQueueClient {
333
333
  async listRequests(options) {
334
334
  const limit = Math.min(options?.limit ?? 100, 10000);
335
335
  const now = Date.now();
336
- // Validate that filter is not used with deprecated exclusiveStartId (per Apify spec)
336
+ // Validate that filter is not used with deprecated exclusiveStartId
337
337
  if (options?.filter && options.filter.length > 0 && options?.exclusiveStartId) {
338
338
  throw new Error('filter parameter is not compatible with deprecated exclusiveStartId parameter');
339
339
  }
340
- // Track clientKey for hadMultipleClients (per Apify spec)
340
+ // Track clientKey for hadMultipleClients
341
341
  if (options?.clientKey) {
342
342
  const existingClientKey = await this.client.redis.hget(this.infoKey, 'clientKey');
343
343
  if (existingClientKey && existingClientKey !== options.clientKey) {
@@ -348,7 +348,7 @@ class DragonflyRequestQueueClient {
348
348
  }
349
349
  }
350
350
  else {
351
- // No clientKey provided - treat as new client per Apify spec
351
+ // No clientKey provided - treat as new client
352
352
  const existingClientKey = await this.client.redis.hget(this.infoKey, 'clientKey');
353
353
  if (existingClientKey) {
354
354
  await this.client.redis.hset(this.infoKey, 'hadMultipleClients', '1');
@@ -456,7 +456,7 @@ class DragonflyRequestQueueClient {
456
456
  lockSecs: options.lockSecs,
457
457
  items,
458
458
  // From the atomic lock script: active locks held by ANY client
459
- // (including this call's own), per the Apify spec.
459
+ // (including this call's own).
460
460
  queueHasLockedRequests: activeLockCount > 0,
461
461
  };
462
462
  }