@crawlee/core 4.0.0-beta.99 → 4.0.0-rc.0

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 (109) hide show
  1. package/autoscaling/autoscaled_pool.d.ts +3 -21
  2. package/autoscaling/autoscaled_pool.js +105 -97
  3. package/autoscaling/concurrency_system.d.ts +7 -22
  4. package/autoscaling/concurrency_system.js +112 -100
  5. package/autoscaling/cpu_load_signal.d.ts +1 -2
  6. package/autoscaling/cpu_load_signal.js +11 -10
  7. package/autoscaling/event_loop_load_signal.d.ts +1 -4
  8. package/autoscaling/event_loop_load_signal.js +18 -18
  9. package/autoscaling/index.d.ts +1 -1
  10. package/autoscaling/index.js +1 -1
  11. package/autoscaling/load_signal.d.ts +8 -7
  12. package/autoscaling/load_signal.js +14 -12
  13. package/autoscaling/memory_load_signal.d.ts +3 -12
  14. package/autoscaling/memory_load_signal.js +41 -41
  15. package/autoscaling/snapshotter.d.ts +7 -10
  16. package/autoscaling/snapshotter.js +18 -18
  17. package/autoscaling/{client_load_signal.d.ts → storage_backend_load_signal.d.ts} +14 -17
  18. package/autoscaling/{client_load_signal.js → storage_backend_load_signal.js} +27 -27
  19. package/autoscaling/system_status.d.ts +9 -11
  20. package/autoscaling/system_status.js +13 -13
  21. package/configuration.d.ts +16 -16
  22. package/configuration.js +6 -6
  23. package/crawlers/context_pipeline.js +6 -6
  24. package/crawlers/crawler_commons.d.ts +14 -112
  25. package/crawlers/crawler_commons.js +1 -107
  26. package/crawlers/error_snapshotter.d.ts +2 -4
  27. package/crawlers/error_snapshotter.js +5 -5
  28. package/crawlers/index.d.ts +1 -1
  29. package/crawlers/index.js +0 -1
  30. package/crawlers/statistics.d.ts +164 -45
  31. package/crawlers/statistics.js +339 -153
  32. package/debug.js +4 -4
  33. package/enqueue_links/enqueue_links.d.ts +51 -159
  34. package/enqueue_links/enqueue_links.js +38 -231
  35. package/enqueue_links/shared.d.ts +34 -24
  36. package/enqueue_links/shared.js +54 -36
  37. package/errors.d.ts +43 -12
  38. package/errors.js +48 -9
  39. package/events/event_manager.d.ts +2 -2
  40. package/events/event_manager.js +7 -6
  41. package/events/local_event_manager.d.ts +1 -1
  42. package/events/local_event_manager.js +5 -5
  43. package/http.d.ts +9 -0
  44. package/http.js +28 -0
  45. package/index.d.ts +1 -1
  46. package/index.js +1 -1
  47. package/log.js +5 -1
  48. package/memory-storage/memory-storage.d.ts +3 -11
  49. package/memory-storage/memory-storage.js +42 -48
  50. package/memory-storage/resource-clients/dataset.d.ts +1 -1
  51. package/memory-storage/resource-clients/dataset.js +8 -13
  52. package/memory-storage/resource-clients/key-value-store.d.ts +1 -1
  53. package/memory-storage/resource-clients/key-value-store.js +36 -38
  54. package/memory-storage/resource-clients/request-queue.d.ts +4 -23
  55. package/memory-storage/resource-clients/request-queue.js +68 -80
  56. package/owned_or_injected.d.ts +1 -3
  57. package/owned_or_injected.js +17 -17
  58. package/package.json +8 -9
  59. package/proxy_configuration.d.ts +1 -3
  60. package/proxy_configuration.js +18 -14
  61. package/recoverable_state.d.ts +80 -45
  62. package/recoverable_state.js +163 -75
  63. package/request.d.ts +3 -4
  64. package/request.js +57 -44
  65. package/router.d.ts +6 -9
  66. package/router.js +23 -23
  67. package/serialization.js +14 -13
  68. package/service_locator.d.ts +19 -10
  69. package/service_locator.js +57 -48
  70. package/session_pool/session.d.ts +3 -14
  71. package/session_pool/session.js +76 -74
  72. package/session_pool/session_pool.d.ts +6 -12
  73. package/session_pool/session_pool.js +85 -77
  74. package/storages/batched_adds.d.ts +37 -0
  75. package/storages/batched_adds.js +73 -0
  76. package/storages/dataset.d.ts +12 -1
  77. package/storages/dataset.js +133 -32
  78. package/storages/index.d.ts +2 -1
  79. package/storages/index.js +2 -1
  80. package/storages/key_value_store.d.ts +19 -4
  81. package/storages/key_value_store.js +204 -75
  82. package/storages/request_dedup_cache.d.ts +1 -2
  83. package/storages/request_dedup_cache.js +9 -9
  84. package/storages/request_list.d.ts +4 -23
  85. package/storages/request_list.js +103 -96
  86. package/storages/request_loader.d.ts +5 -0
  87. package/storages/request_manager_tandem.d.ts +1 -10
  88. package/storages/request_manager_tandem.js +27 -27
  89. package/storages/request_queue.d.ts +21 -18
  90. package/storages/request_queue.js +318 -177
  91. package/storages/sitemap_request_loader.d.ts +22 -70
  92. package/storages/sitemap_request_loader.js +122 -116
  93. package/storages/storage_instance_manager.d.ts +1 -2
  94. package/storages/storage_instance_manager.js +17 -17
  95. package/storages/storage_stats.d.ts +1 -1
  96. package/storages/storage_stats.js +4 -4
  97. package/storages/throttling_request_manager.d.ts +239 -0
  98. package/storages/throttling_request_manager.js +646 -0
  99. package/storages/transaction.d.ts +252 -0
  100. package/storages/transaction.js +251 -0
  101. package/storages/utils.d.ts +6 -3
  102. package/storages/utils.js +11 -2
  103. package/system-info/runtime.js +7 -7
  104. package/url.d.ts +9 -0
  105. package/url.js +11 -0
  106. package/validators.d.ts +22 -25
  107. package/validators.js +13 -25
  108. package/storages/access_checking.d.ts +0 -12
  109. package/storages/access_checking.js +0 -17
@@ -120,8 +120,8 @@ class StorageCache {
120
120
  * assigns a reserved default alias.
121
121
  */
122
122
  export class StorageInstanceManager {
123
- cache = new StorageCache();
124
- openerLocks = new Map();
123
+ #cache = new StorageCache();
124
+ #openerLocks = new Map();
125
125
  /**
126
126
  * Open (or retrieve from cache) a storage instance.
127
127
  *
@@ -142,46 +142,46 @@ export class StorageInstanceManager {
142
142
  }
143
143
  // Fast-path cache check (no lock).
144
144
  if (alias !== undefined) {
145
- const cached = this.cache.get(cls, { alias, backendCacheKey });
145
+ const cached = this.#cache.get(cls, { alias, backendCacheKey });
146
146
  if (cached)
147
147
  return cached;
148
148
  }
149
149
  else if (id) {
150
- const cached = this.cache.get(cls, { id, backendCacheKey });
150
+ const cached = this.#cache.get(cls, { id, backendCacheKey });
151
151
  if (cached)
152
152
  return cached;
153
153
  }
154
154
  else if (name) {
155
- const cached = this.cache.get(cls, { name, backendCacheKey });
155
+ const cached = this.#cache.get(cls, { name, backendCacheKey });
156
156
  if (cached)
157
157
  return cached;
158
158
  }
159
159
  const identifierKey = id ?? name ?? alias ?? DEFAULT_STORAGE_ALIAS;
160
160
  const lockKey = `${cls.name}:${identifierKey}:${backendCacheKey}`;
161
- if (!this.openerLocks.has(lockKey)) {
162
- this.openerLocks.set(lockKey, new AsyncQueue());
161
+ if (!this.#openerLocks.has(lockKey)) {
162
+ this.#openerLocks.set(lockKey, new AsyncQueue());
163
163
  }
164
- const queue = this.openerLocks.get(lockKey);
164
+ const queue = this.#openerLocks.get(lockKey);
165
165
  await queue.wait();
166
166
  try {
167
167
  // Double-check cache under lock (another caller may have filled it while we waited).
168
168
  if (alias !== undefined) {
169
- const cached = this.cache.get(cls, { alias, backendCacheKey });
169
+ const cached = this.#cache.get(cls, { alias, backendCacheKey });
170
170
  if (cached)
171
171
  return cached;
172
172
  }
173
173
  else if (id) {
174
- const cached = this.cache.get(cls, { id, backendCacheKey });
174
+ const cached = this.#cache.get(cls, { id, backendCacheKey });
175
175
  if (cached)
176
176
  return cached;
177
177
  }
178
178
  else if (name) {
179
- const cached = this.cache.get(cls, { name, backendCacheKey });
179
+ const cached = this.#cache.get(cls, { name, backendCacheKey });
180
180
  if (cached)
181
181
  return cached;
182
182
  }
183
183
  // Prevent the same string from being used as both a name and an alias.
184
- this.cache.checkNameAliasConflict(cls, { name, alias, backendCacheKey });
184
+ this.#cache.checkNameAliasConflict(cls, { name, alias, backendCacheKey });
185
185
  // Cache miss — create the sub-backend and storage instance.
186
186
  const subBackend = await backendOpener();
187
187
  const storageInfo = await subBackend.getMetadata();
@@ -189,7 +189,7 @@ export class StorageInstanceManager {
189
189
  // we just fetched (so `id`/`name` etc. are available synchronously) along with the backend.
190
190
  const instance = new cls({ metadata: storageInfo, backend: subBackend });
191
191
  // Atomic cache writes (no awaits between these).
192
- this.cache.set(cls, instance, backendCacheKey, alias);
192
+ this.#cache.set(cls, instance, backendCacheKey, alias);
193
193
  return instance;
194
194
  }
195
195
  finally {
@@ -197,7 +197,7 @@ export class StorageInstanceManager {
197
197
  // Clean up idle locks so the map doesn't grow unboundedly
198
198
  // (mirrors crawlee-python's WeakValueDictionary behaviour).
199
199
  if (queue.remaining === 0) {
200
- this.openerLocks.delete(lockKey);
200
+ this.#openerLocks.delete(lockKey);
201
201
  }
202
202
  }
203
203
  }
@@ -205,7 +205,7 @@ export class StorageInstanceManager {
205
205
  * Remove a storage instance from the cache (called from `storage.drop()`).
206
206
  */
207
207
  removeFromCache(instance) {
208
- this.cache.removeFromCache(instance);
208
+ this.#cache.removeFromCache(instance);
209
209
  }
210
210
  /**
211
211
  * Clear the entire cache. Also calls `clearCache()` on any cached KeyValueStore
@@ -213,12 +213,12 @@ export class StorageInstanceManager {
213
213
  * Called during service locator reset.
214
214
  */
215
215
  clearCache() {
216
- for (const instance of this.cache.allValues()) {
216
+ for (const instance of this.#cache.allValues()) {
217
217
  if ('clearCache' in instance && typeof instance.clearCache === 'function') {
218
218
  instance.clearCache();
219
219
  }
220
220
  }
221
- this.cache.clear();
221
+ this.#cache.clear();
222
222
  }
223
223
  }
224
224
  /**
@@ -39,7 +39,7 @@ export interface RequestQueueStats {
39
39
  * the buckets that make sense for it.
40
40
  */
41
41
  export declare class StorageStatsTracker<T extends Record<keyof T, number>> {
42
- private readonly counters;
42
+ #private;
43
43
  constructor(initial: T);
44
44
  /** Increment a counter bucket by `by` (default `1`). */
45
45
  add(key: keyof T, by?: number): void;
@@ -14,16 +14,16 @@
14
14
  * the buckets that make sense for it.
15
15
  */
16
16
  export class StorageStatsTracker {
17
- counters;
17
+ #counters;
18
18
  constructor(initial) {
19
- this.counters = { ...initial };
19
+ this.#counters = { ...initial };
20
20
  }
21
21
  /** Increment a counter bucket by `by` (default `1`). */
22
22
  add(key, by = 1) {
23
- this.counters[key] += by;
23
+ this.#counters[key] += by;
24
24
  }
25
25
  /** Return a snapshot of the current counters. The returned object is a copy and safe to keep. */
26
26
  get current() {
27
- return { ...this.counters };
27
+ return { ...this.#counters };
28
28
  }
29
29
  }
@@ -0,0 +1,239 @@
1
+ import type { Dictionary } from '@crawlee/types';
2
+ import type { Configuration } from '../configuration.js';
3
+ import type { Request, Source } from '../request.js';
4
+ import type { IRequestManager, RequestsLike } from './request_manager.js';
5
+ import type { AddRequestsBatchedOptions, AddRequestsBatchedResult, RequestQueueOperationInfo, RequestQueueOperationOptions } from './request_queue.js';
6
+ import type { StorageIdentifier } from './storage_instance_manager.js';
7
+ import type { StorageOpenOptions } from './utils.js';
8
+ /**
9
+ * Opens a request manager, matching the shape of storage `open` methods such as
10
+ * {@link RequestQueue.open|`RequestQueue.open`}.
11
+ *
12
+ * {@link ThrottlingRequestManager} calls this once per configured domain, so every per-domain queue shares the
13
+ * concrete type and storage backend of the manager being wrapped.
14
+ */
15
+ export type RequestManagerOpener<T extends IRequestManager = IRequestManager> = (identifier: string | StorageIdentifier, options?: StorageOpenOptions) => Promise<T>;
16
+ /**
17
+ * A request manager that can pace requests per domain, as {@link ThrottlingRequestManager} does.
18
+ *
19
+ * The crawlers detect this structurally rather than by type, so a wrapper can opt in by forwarding these three
20
+ * methods without {@link IRequestManager} having to know that throttling exists.
21
+ */
22
+ export interface SupportsDomainThrottling {
23
+ /** @see {@link ThrottlingRequestManager.recordDomainDelay} */
24
+ recordDomainDelay(url: string, retryAfterMs?: number | null): boolean;
25
+ /** @see {@link ThrottlingRequestManager.setCrawlDelay} */
26
+ setCrawlDelay(url: string, delaySeconds: number): boolean;
27
+ /** @see {@link ThrottlingRequestManager.assertNoStalledDomains} */
28
+ assertNoStalledDomains(): Promise<void>;
29
+ }
30
+ /** Whether `manager` can pace requests per domain. */
31
+ export declare function supportsDomainThrottling(manager: unknown): manager is SupportsDomainThrottling;
32
+ /** Options for {@link ThrottlingRequestManager}. */
33
+ export interface ThrottlingRequestManagerOptions<T extends IRequestManager = IRequestManager> {
34
+ /**
35
+ * The request manager to wrap, usually a {@link RequestQueue}. Requests for domains that are not throttled
36
+ * are stored here.
37
+ */
38
+ inner: T;
39
+ /**
40
+ * Which domains to throttle: a list of hostnames, or `'all'` for every domain the crawl encounters.
41
+ *
42
+ * Matching a listed hostname is case-insensitive and exact - wildcards such as `*.example.com` are not
43
+ * supported, so list each subdomain you care about (or set
44
+ * {@link ThrottlingRequestManagerOptions.throttleBy|`throttleBy: 'registrableDomain'`}). An
45
+ * internationalized domain may be given in either its unicode or its punycode form, and an IPv6 address has
46
+ * to be bracketed (`[::1]`). Requests for any other domain bypass throttling entirely.
47
+ *
48
+ * `'all'` gives each domain a queue of its own the first time it is seen, so that it can be held back
49
+ * without its requests being repeatedly popped and re-enqueued. One request queue per domain is not free,
50
+ * which is what {@link ThrottlingRequestManagerOptions.maxThrottledDomains|`maxThrottledDomains`} is
51
+ * there to bound.
52
+ */
53
+ domains: string[] | 'all';
54
+ /**
55
+ * A floor under the crawl delay of every throttled domain, in seconds - the proactive clock described on
56
+ * {@link ThrottlingRequestManager}. A domain whose robots.txt asks for a longer `Crawl-delay` gets the
57
+ * longer one; this is a minimum, not an override.
58
+ * @default 0
59
+ */
60
+ minCrawlDelaySecs?: number;
61
+ /**
62
+ * What counts as "the same domain": the exact hostname, or the registrable domain it belongs to
63
+ * (`example.com` for `www.example.com`, `a.example.co.uk` and so on). Hosts with no registrable domain -
64
+ * IP addresses, `localhost` - are always throttled per hostname.
65
+ *
66
+ * Grouping by registrable domain gives subdomains a single pair of clocks and a single queue, which is what
67
+ * you want when the pacing is there to be polite to one server rather than to satisfy a specific host's
68
+ * rate limit.
69
+ * @default 'hostname'
70
+ */
71
+ throttleBy?: 'hostname' | 'registrableDomain';
72
+ /**
73
+ * The most domains a run may throttle at once. Exceeding it throws, rather than silently letting the
74
+ * throttling lapse - one request queue per domain is not free, and a crawl that discovers domains without
75
+ * bound would drown the storage backend in them.
76
+ *
77
+ * Only domains discovered under `domains: 'all'` count against this; an explicit list is taken at face value.
78
+ * @default 100
79
+ */
80
+ maxThrottledDomains?: number;
81
+ /**
82
+ * The key under which the discovered domain list is kept in the default key-value store, so that a restart
83
+ * with `purgeOnStart` disabled reopens their queues instead of stranding whatever they still hold. Only
84
+ * written under `domains: 'all'`.
85
+ *
86
+ * Give each manager its own key when running several of them against the same storage.
87
+ * @default 'CRAWLEE_THROTTLED_DOMAINS'
88
+ */
89
+ persistStateKey?: string;
90
+ /**
91
+ * Opens the per-domain queues, one per throttled domain, each under the alias `throttled-<domain>`.
92
+ * @default RequestQueue.open
93
+ */
94
+ requestManagerOpener?: RequestManagerOpener<T>;
95
+ /**
96
+ * The delay applied after a domain's first HTTP 429, doubled on each subsequent one.
97
+ * @default 2
98
+ */
99
+ baseDelaySecs?: number;
100
+ /**
101
+ * Upper bound on the delay between requests to a rate-limited domain, applied to both the exponential
102
+ * backoff and a `Retry-After` value.
103
+ * @default 60
104
+ */
105
+ maxDelaySecs?: number;
106
+ /**
107
+ * How long a domain may rate-limit us without a single request getting through before the crawl is
108
+ * abandoned with a {@link PersistentRateLimitError}.
109
+ *
110
+ * A domain that keeps answering 429 for this long is not going to be crawled by waiting longer - the
111
+ * concurrency is too high for it, or it has blocked us outright. Its requests are deliberately left in
112
+ * their queue, so re-running the crawl with `purgeOnStart` disabled picks them up once the domain recovers.
113
+ *
114
+ * A crawler running with `keepAlive` is exempt - outliving a domain that will not let us through is the
115
+ * whole point there.
116
+ * @default 900
117
+ */
118
+ maxDomainStallSecs?: number;
119
+ }
120
+ /**
121
+ * A request manager that wraps another one and paces requests per domain.
122
+ *
123
+ * Requests for a throttled domain are routed into their own queue when they are added, so each request lives in
124
+ * exactly one place and deduplication keeps working. Everything else goes to the wrapped manager untouched.
125
+ *
126
+ * {@link ThrottlingRequestManager.fetchNextRequest|`fetchNextRequest()`} serves the domain that has been waiting
127
+ * longest and skips any that are backing off, falling back to the wrapped manager. It never blocks: while every
128
+ * remaining request belongs to a throttled domain it returns `null` and {@link ThrottlingRequestManager.isEmpty}
129
+ * reports `true`, so the crawler idles instead of holding a concurrency slot open.
130
+ *
131
+ * Each throttled domain runs two independent clocks, and may be dispatched to once **both** have run out:
132
+ * - **Backoff**, set by HTTP 429 responses - honouring `Retry-After`, and otherwise doubling from `baseDelaySecs`.
133
+ * Reactive and temporary: it decays once the domain stops turning us away. The crawlers report the 429s
134
+ * themselves; a request held back this way is retried later without counting against `maxRequestRetries` and
135
+ * without penalising its session.
136
+ * - **Crawl delay**, the minimum interval between two dispatches to the domain, armed after each one. Proactive
137
+ * and constant: whatever the domain's robots.txt asks for, floored by
138
+ * {@link ThrottlingRequestManagerOptions.minCrawlDelaySecs|`minCrawlDelaySecs`}. Either may be absent, in
139
+ * which case the other one is the delay.
140
+ *
141
+ * Which domains get those clocks is {@link ThrottlingRequestManagerOptions.domains|`domains`} - a list, or
142
+ * `'all'` for every domain the crawl encounters.
143
+ *
144
+ * **Example usage:**
145
+ *
146
+ * ```ts
147
+ * const crawler = new CheerioCrawler({
148
+ * requestManager: new ThrottlingRequestManager({
149
+ * inner: await RequestQueue.open(),
150
+ * domains: ['api.example.com', 'slow-site.org'],
151
+ * }),
152
+ * requestHandler: async ({ request }) => { ... },
153
+ * });
154
+ * ```
155
+ *
156
+ * @category Sources
157
+ */
158
+ export declare class ThrottlingRequestManager<T extends IRequestManager = IRequestManager> implements IRequestManager, SupportsDomainThrottling {
159
+ #private;
160
+ private readonly config;
161
+ private readonly domainStates;
162
+ private readonly log;
163
+ constructor(options: ThrottlingRequestManagerOptions<T>, config?: Configuration);
164
+ /** The wrapped manager, holding every request whose domain is not throttled. */
165
+ get innerManager(): T;
166
+ /**
167
+ * Records a 429 response and puts the URL's domain into backoff.
168
+ *
169
+ * @returns `false` if the domain is not configured for throttling, in which case this is a no-op.
170
+ */
171
+ recordDomainDelay(url: string, retryAfterMs?: number | null): boolean;
172
+ /**
173
+ * Records the `Crawl-delay` a domain's robots.txt asked for, which becomes its crawl delay unless
174
+ * {@link ThrottlingRequestManagerOptions.minCrawlDelaySecs|`minCrawlDelaySecs`} asks for longer.
175
+ *
176
+ * The first value wins, so a robots.txt re-fetch cannot change the cadence mid-crawl.
177
+ *
178
+ * @returns `false` if the domain is not throttled, in which case this is a no-op.
179
+ */
180
+ setCrawlDelay(url: string, delaySeconds: number): boolean;
181
+ /**
182
+ * Throws {@link PersistentRateLimitError} if any domain has been rate-limiting us past
183
+ * {@link ThrottlingRequestManagerOptions.maxDomainStallSecs|`maxDomainStallSecs`} without letting a single
184
+ * request through.
185
+ *
186
+ * A domain qualifies only while it still has queued requests and is actively rate-limiting - a domain that
187
+ * has simply run out of work is finished, not stalled, and one being waited out under a long robots.txt
188
+ * `Crawl-delay` is being obeyed, not stonewalled.
189
+ */
190
+ assertNoStalledDomains(): Promise<void>;
191
+ addRequest(requestLike: Source, options?: RequestQueueOperationOptions): Promise<RequestQueueOperationInfo>;
192
+ /**
193
+ * Adds requests in batches, routing each one to the manager that owns its domain.
194
+ *
195
+ * Batching, validation, deduplication and `Retry-After`-free bookkeeping are all delegated to the target
196
+ * managers - this only decides where each request goes, one batch at a time, so a lazy or unbounded input
197
+ * iterable is never fully materialized.
198
+ */
199
+ addRequestsBatched(requests: RequestsLike, options?: AddRequestsBatchedOptions): Promise<AddRequestsBatchedResult>;
200
+ reclaimRequest(request: Request, options?: RequestQueueOperationOptions): Promise<RequestQueueOperationInfo | null>;
201
+ markRequestAsHandled(request: Request): Promise<RequestQueueOperationInfo | void | null>;
202
+ getTotalCount(): Promise<number>;
203
+ getPendingCount(): Promise<number>;
204
+ getHandledCount(): Promise<number>;
205
+ /**
206
+ * Whether the next {@link ThrottlingRequestManager.fetchNextRequest} would return `null`.
207
+ *
208
+ * Requests waiting on a throttled domain count as unavailable, so a crawler whose task loop is gated on
209
+ * this idles for the backoff instead of spinning on a fetch that cannot succeed yet.
210
+ */
211
+ isEmpty(): Promise<boolean>;
212
+ /** Unlike {@link ThrottlingRequestManager.isEmpty}, throttled requests still count as outstanding work. */
213
+ isFinished(): Promise<boolean>;
214
+ /**
215
+ * Empties every manager and clears the accumulated backoff. A robots.txt `Crawl-delay` is a property of the
216
+ * site rather than of the run, so it survives.
217
+ */
218
+ purge(): Promise<void>;
219
+ /**
220
+ * Empties the per-domain queues, leaving the wrapped manager alone.
221
+ *
222
+ * Those queues are this manager's own no matter who owns the one it wraps, which is what makes this safe to
223
+ * call where a full {@link ThrottlingRequestManager.purge|`purge()`} would not be.
224
+ */
225
+ purgeDomainQueues(): Promise<void>;
226
+ setExpectedRequestProcessingTimeSecs(secs: number): Promise<void>;
227
+ /**
228
+ * Returns the next request from a domain that is not backing off, or from the inner manager.
229
+ *
230
+ * Returns `null` while every remaining request belongs to a throttled domain - it never waits the backoff
231
+ * out, because a consumer parked in here holds a concurrency slot, which the autoscaler reads as spare
232
+ * capacity and answers by scaling up. Callers poll instead, and {@link ThrottlingRequestManager.isEmpty}
233
+ * reports `true` meanwhile so the crawler's task loop idles rather than spins.
234
+ */
235
+ fetchNextRequest<R extends Dictionary = Dictionary>(): Promise<Request<R> | null>;
236
+ [Symbol.asyncIterator](): AsyncGenerator<Request<Dictionary>, void, unknown>;
237
+ persistState(): Promise<void>;
238
+ drop(): Promise<void>;
239
+ }