@crawlee/core 4.0.0-beta.8 → 4.0.0-beta.80

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 (257) hide show
  1. package/README.md +17 -13
  2. package/autoscaling/autoscaled_pool.d.ts +5 -6
  3. package/autoscaling/autoscaled_pool.js +12 -11
  4. package/autoscaling/client_load_signal.d.ts +25 -0
  5. package/autoscaling/client_load_signal.js +36 -0
  6. package/autoscaling/cpu_load_signal.d.ts +28 -0
  7. package/autoscaling/cpu_load_signal.js +24 -0
  8. package/autoscaling/event_loop_load_signal.d.ts +23 -0
  9. package/autoscaling/event_loop_load_signal.js +35 -0
  10. package/autoscaling/index.d.ts +5 -1
  11. package/autoscaling/index.js +5 -1
  12. package/autoscaling/load_signal.d.ts +99 -0
  13. package/autoscaling/load_signal.js +135 -0
  14. package/autoscaling/memory_load_signal.d.ts +43 -0
  15. package/autoscaling/memory_load_signal.js +102 -0
  16. package/autoscaling/snapshotter.d.ts +35 -72
  17. package/autoscaling/snapshotter.js +78 -181
  18. package/autoscaling/system_status.d.ts +21 -36
  19. package/autoscaling/system_status.js +46 -84
  20. package/configuration.d.ts +88 -228
  21. package/configuration.js +162 -224
  22. package/cookie_utils.d.ts +4 -3
  23. package/cookie_utils.js +20 -13
  24. package/crawlers/context_pipeline.d.ts +70 -0
  25. package/crawlers/context_pipeline.js +122 -0
  26. package/crawlers/crawler_commons.d.ts +20 -30
  27. package/crawlers/crawler_commons.js +12 -21
  28. package/crawlers/crawler_utils.d.ts +2 -3
  29. package/crawlers/crawler_utils.js +1 -2
  30. package/crawlers/error_snapshotter.d.ts +3 -3
  31. package/crawlers/error_snapshotter.js +2 -3
  32. package/crawlers/error_tracker.d.ts +2 -2
  33. package/crawlers/error_tracker.js +0 -1
  34. package/crawlers/index.d.ts +1 -2
  35. package/crawlers/index.js +1 -2
  36. package/crawlers/internals/types.d.ts +7 -0
  37. package/crawlers/internals/types.js +1 -0
  38. package/crawlers/statistics.d.ts +22 -17
  39. package/crawlers/statistics.js +30 -26
  40. package/enqueue_links/enqueue_links.d.ts +44 -22
  41. package/enqueue_links/enqueue_links.js +51 -30
  42. package/enqueue_links/index.d.ts +0 -1
  43. package/enqueue_links/index.js +0 -1
  44. package/enqueue_links/shared.d.ts +25 -9
  45. package/enqueue_links/shared.js +69 -38
  46. package/errors.d.ts +53 -4
  47. package/errors.js +70 -5
  48. package/events/event_manager.d.ts +34 -8
  49. package/events/event_manager.js +8 -10
  50. package/events/index.d.ts +0 -1
  51. package/events/index.js +0 -1
  52. package/events/local_event_manager.d.ts +15 -5
  53. package/events/local_event_manager.js +37 -40
  54. package/index.d.ts +4 -3
  55. package/index.js +3 -2
  56. package/log.d.ts +82 -3
  57. package/log.js +102 -1
  58. package/memory-storage/consts.d.ts +4 -0
  59. package/memory-storage/consts.js +4 -0
  60. package/memory-storage/index.d.ts +1 -0
  61. package/memory-storage/index.js +1 -0
  62. package/memory-storage/memory-storage.d.ts +46 -0
  63. package/memory-storage/memory-storage.js +136 -0
  64. package/memory-storage/resource-clients/common/base-client.d.ts +4 -0
  65. package/memory-storage/resource-clients/common/base-client.js +6 -0
  66. package/memory-storage/resource-clients/dataset.d.ts +40 -0
  67. package/memory-storage/resource-clients/dataset.js +113 -0
  68. package/memory-storage/resource-clients/key-value-store.d.ts +63 -0
  69. package/memory-storage/resource-clients/key-value-store.js +203 -0
  70. package/memory-storage/resource-clients/request-queue.d.ts +96 -0
  71. package/memory-storage/resource-clients/request-queue.js +421 -0
  72. package/memory-storage/utils.d.ts +17 -0
  73. package/memory-storage/utils.js +42 -0
  74. package/package.json +12 -10
  75. package/proxy_configuration.d.ts +14 -149
  76. package/proxy_configuration.js +19 -168
  77. package/recoverable_state.d.ts +120 -0
  78. package/recoverable_state.js +141 -0
  79. package/request.d.ts +84 -13
  80. package/request.js +107 -28
  81. package/router.d.ts +117 -15
  82. package/router.js +124 -30
  83. package/serialization.d.ts +0 -1
  84. package/serialization.js +1 -2
  85. package/service_locator.d.ts +156 -0
  86. package/service_locator.js +238 -0
  87. package/session_pool/consts.d.ts +1 -2
  88. package/session_pool/consts.js +1 -2
  89. package/session_pool/errors.d.ts +0 -1
  90. package/session_pool/errors.js +0 -1
  91. package/session_pool/fingerprint.d.ts +9 -0
  92. package/session_pool/fingerprint.js +30 -0
  93. package/session_pool/index.d.ts +0 -2
  94. package/session_pool/index.js +0 -2
  95. package/session_pool/session.d.ts +35 -73
  96. package/session_pool/session.js +46 -99
  97. package/session_pool/session_pool.d.ts +71 -72
  98. package/session_pool/session_pool.js +111 -100
  99. package/storages/access_checking.d.ts +0 -1
  100. package/storages/access_checking.js +5 -2
  101. package/storages/dataset.d.ts +101 -51
  102. package/storages/dataset.js +168 -126
  103. package/storages/index.d.ts +8 -7
  104. package/storages/index.js +6 -7
  105. package/storages/key_value_store.d.ts +165 -36
  106. package/storages/key_value_store.js +269 -122
  107. package/storages/key_value_store_codec.d.ts +32 -0
  108. package/storages/key_value_store_codec.js +113 -0
  109. package/storages/request_dedup_cache.d.ts +23 -0
  110. package/storages/request_dedup_cache.js +48 -0
  111. package/storages/request_list.d.ts +38 -82
  112. package/storages/request_list.js +65 -41
  113. package/storages/request_loader.d.ts +96 -0
  114. package/storages/request_loader.js +1 -0
  115. package/storages/request_manager.d.ts +33 -0
  116. package/storages/request_manager.js +1 -0
  117. package/storages/request_manager_tandem.d.ts +106 -0
  118. package/storages/request_manager_tandem.js +197 -0
  119. package/storages/request_queue.d.ts +303 -46
  120. package/storages/request_queue.js +629 -214
  121. package/storages/{sitemap_request_list.d.ts → sitemap_request_loader.d.ts} +28 -24
  122. package/storages/{sitemap_request_list.js → sitemap_request_loader.js} +49 -46
  123. package/storages/storage_instance_manager.d.ts +91 -0
  124. package/storages/storage_instance_manager.js +258 -0
  125. package/storages/storage_stats.d.ts +48 -0
  126. package/storages/storage_stats.js +29 -0
  127. package/storages/utils.d.ts +51 -6
  128. package/storages/utils.js +60 -9
  129. package/system-info/cpu-info.d.ts +67 -0
  130. package/system-info/cpu-info.js +216 -0
  131. package/system-info/memory-info.d.ts +31 -0
  132. package/system-info/memory-info.js +115 -0
  133. package/system-info/ps-tree.d.ts +17 -0
  134. package/system-info/ps-tree.js +144 -0
  135. package/system-info/runtime.d.ts +14 -0
  136. package/system-info/runtime.js +80 -0
  137. package/typedefs.d.ts +1 -2
  138. package/typedefs.js +0 -1
  139. package/validators.d.ts +8 -1
  140. package/validators.js +8 -1
  141. package/autoscaling/autoscaled_pool.d.ts.map +0 -1
  142. package/autoscaling/autoscaled_pool.js.map +0 -1
  143. package/autoscaling/index.d.ts.map +0 -1
  144. package/autoscaling/index.js.map +0 -1
  145. package/autoscaling/snapshotter.d.ts.map +0 -1
  146. package/autoscaling/snapshotter.js.map +0 -1
  147. package/autoscaling/system_status.d.ts.map +0 -1
  148. package/autoscaling/system_status.js.map +0 -1
  149. package/configuration.d.ts.map +0 -1
  150. package/configuration.js.map +0 -1
  151. package/cookie_utils.d.ts.map +0 -1
  152. package/cookie_utils.js.map +0 -1
  153. package/crawlers/crawler_commons.d.ts.map +0 -1
  154. package/crawlers/crawler_commons.js.map +0 -1
  155. package/crawlers/crawler_extension.d.ts +0 -12
  156. package/crawlers/crawler_extension.d.ts.map +0 -1
  157. package/crawlers/crawler_extension.js +0 -14
  158. package/crawlers/crawler_extension.js.map +0 -1
  159. package/crawlers/crawler_utils.d.ts.map +0 -1
  160. package/crawlers/crawler_utils.js.map +0 -1
  161. package/crawlers/error_snapshotter.d.ts.map +0 -1
  162. package/crawlers/error_snapshotter.js.map +0 -1
  163. package/crawlers/error_tracker.d.ts.map +0 -1
  164. package/crawlers/error_tracker.js.map +0 -1
  165. package/crawlers/index.d.ts.map +0 -1
  166. package/crawlers/index.js.map +0 -1
  167. package/crawlers/statistics.d.ts.map +0 -1
  168. package/crawlers/statistics.js.map +0 -1
  169. package/enqueue_links/enqueue_links.d.ts.map +0 -1
  170. package/enqueue_links/enqueue_links.js.map +0 -1
  171. package/enqueue_links/index.d.ts.map +0 -1
  172. package/enqueue_links/index.js.map +0 -1
  173. package/enqueue_links/shared.d.ts.map +0 -1
  174. package/enqueue_links/shared.js.map +0 -1
  175. package/errors.d.ts.map +0 -1
  176. package/errors.js.map +0 -1
  177. package/events/event_manager.d.ts.map +0 -1
  178. package/events/event_manager.js.map +0 -1
  179. package/events/index.d.ts.map +0 -1
  180. package/events/index.js.map +0 -1
  181. package/events/local_event_manager.d.ts.map +0 -1
  182. package/events/local_event_manager.js.map +0 -1
  183. package/http_clients/base-http-client.d.ts +0 -134
  184. package/http_clients/base-http-client.d.ts.map +0 -1
  185. package/http_clients/base-http-client.js +0 -33
  186. package/http_clients/base-http-client.js.map +0 -1
  187. package/http_clients/form-data-like.d.ts +0 -67
  188. package/http_clients/form-data-like.d.ts.map +0 -1
  189. package/http_clients/form-data-like.js +0 -5
  190. package/http_clients/form-data-like.js.map +0 -1
  191. package/http_clients/got-scraping-http-client.d.ts +0 -15
  192. package/http_clients/got-scraping-http-client.d.ts.map +0 -1
  193. package/http_clients/got-scraping-http-client.js +0 -69
  194. package/http_clients/got-scraping-http-client.js.map +0 -1
  195. package/http_clients/index.d.ts +0 -3
  196. package/http_clients/index.d.ts.map +0 -1
  197. package/http_clients/index.js +0 -3
  198. package/http_clients/index.js.map +0 -1
  199. package/index.d.ts.map +0 -1
  200. package/index.js.map +0 -1
  201. package/log.d.ts.map +0 -1
  202. package/log.js.map +0 -1
  203. package/proxy_configuration.d.ts.map +0 -1
  204. package/proxy_configuration.js.map +0 -1
  205. package/request.d.ts.map +0 -1
  206. package/request.js.map +0 -1
  207. package/router.d.ts.map +0 -1
  208. package/router.js.map +0 -1
  209. package/serialization.d.ts.map +0 -1
  210. package/serialization.js.map +0 -1
  211. package/session_pool/consts.d.ts.map +0 -1
  212. package/session_pool/consts.js.map +0 -1
  213. package/session_pool/errors.d.ts.map +0 -1
  214. package/session_pool/errors.js.map +0 -1
  215. package/session_pool/events.d.ts +0 -3
  216. package/session_pool/events.d.ts.map +0 -1
  217. package/session_pool/events.js +0 -3
  218. package/session_pool/events.js.map +0 -1
  219. package/session_pool/index.d.ts.map +0 -1
  220. package/session_pool/index.js.map +0 -1
  221. package/session_pool/session.d.ts.map +0 -1
  222. package/session_pool/session.js.map +0 -1
  223. package/session_pool/session_pool.d.ts.map +0 -1
  224. package/session_pool/session_pool.js.map +0 -1
  225. package/storages/access_checking.d.ts.map +0 -1
  226. package/storages/access_checking.js.map +0 -1
  227. package/storages/dataset.d.ts.map +0 -1
  228. package/storages/dataset.js.map +0 -1
  229. package/storages/index.d.ts.map +0 -1
  230. package/storages/index.js.map +0 -1
  231. package/storages/key_value_store.d.ts.map +0 -1
  232. package/storages/key_value_store.js.map +0 -1
  233. package/storages/request_list.d.ts.map +0 -1
  234. package/storages/request_list.js.map +0 -1
  235. package/storages/request_provider.d.ts +0 -307
  236. package/storages/request_provider.d.ts.map +0 -1
  237. package/storages/request_provider.js +0 -555
  238. package/storages/request_provider.js.map +0 -1
  239. package/storages/request_queue.d.ts.map +0 -1
  240. package/storages/request_queue.js.map +0 -1
  241. package/storages/request_queue_v2.d.ts +0 -87
  242. package/storages/request_queue_v2.d.ts.map +0 -1
  243. package/storages/request_queue_v2.js +0 -438
  244. package/storages/request_queue_v2.js.map +0 -1
  245. package/storages/sitemap_request_list.d.ts.map +0 -1
  246. package/storages/sitemap_request_list.js.map +0 -1
  247. package/storages/storage_manager.d.ts +0 -58
  248. package/storages/storage_manager.d.ts.map +0 -1
  249. package/storages/storage_manager.js +0 -105
  250. package/storages/storage_manager.js.map +0 -1
  251. package/storages/utils.d.ts.map +0 -1
  252. package/storages/utils.js.map +0 -1
  253. package/tsconfig.build.tsbuildinfo +0 -1
  254. package/typedefs.d.ts.map +0 -1
  255. package/typedefs.js.map +0 -1
  256. package/validators.d.ts.map +0 -1
  257. package/validators.js.map +0 -1
@@ -0,0 +1,23 @@
1
+ /**
2
+ * A fixed-size, direct-mapped cache for `uniqueKey`-based request deduplication.
3
+ *
4
+ * `RequestProvider.requestCache` only remembers the first batch of requests, so repeated
5
+ * `addRequestsBatched()` calls with overlapping URLs re-submit already-enqueued requests
6
+ * (https://github.com/apify/crawlee/issues/3120). This is a separate, cheaper cache we can populate on
7
+ * every batch: a fixed number of slots indexed by a hash of the request's cache key, storing the
8
+ * server-assigned `requestId`. Memory is capped by the slot count regardless of the working set size;
9
+ * a hash collision just overwrites a slot, causing an occasional cache miss (a harmless re-submission)
10
+ * but never a false hit — so a genuinely new request is never dropped.
11
+ *
12
+ * @internal
13
+ */
14
+ export declare class RequestDeduplicationCache {
15
+ private readonly size;
16
+ private keys;
17
+ private ids;
18
+ constructor(size?: number);
19
+ get(cacheKey: string): string | null;
20
+ add(cacheKey: string, requestId: string): void;
21
+ clear(): void;
22
+ private indexOf;
23
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * A fixed-size, direct-mapped cache for `uniqueKey`-based request deduplication.
3
+ *
4
+ * `RequestProvider.requestCache` only remembers the first batch of requests, so repeated
5
+ * `addRequestsBatched()` calls with overlapping URLs re-submit already-enqueued requests
6
+ * (https://github.com/apify/crawlee/issues/3120). This is a separate, cheaper cache we can populate on
7
+ * every batch: a fixed number of slots indexed by a hash of the request's cache key, storing the
8
+ * server-assigned `requestId`. Memory is capped by the slot count regardless of the working set size;
9
+ * a hash collision just overwrites a slot, causing an occasional cache miss (a harmless re-submission)
10
+ * but never a false hit — so a genuinely new request is never dropped.
11
+ *
12
+ * @internal
13
+ */
14
+ export class RequestDeduplicationCache {
15
+ size;
16
+ keys;
17
+ ids;
18
+ // The slot count is the same for every queue, so it's a fixed default rather than a per-consumer option.
19
+ constructor(size = 1_000_000) {
20
+ this.size = size;
21
+ this.keys = new Array(size);
22
+ this.ids = new Array(size);
23
+ }
24
+ get(cacheKey) {
25
+ const index = this.indexOf(cacheKey);
26
+ return this.keys[index] === cacheKey ? this.ids[index] : null;
27
+ }
28
+ add(cacheKey, requestId) {
29
+ const index = this.indexOf(cacheKey);
30
+ this.keys[index] = cacheKey;
31
+ this.ids[index] = requestId;
32
+ }
33
+ clear() {
34
+ this.keys = new Array(this.size);
35
+ this.ids = new Array(this.size);
36
+ }
37
+ // A cheap FNV-1a hash of the cache key — avoids pulling in a dedicated hashing dependency.
38
+ indexOf(cacheKey) {
39
+ /* eslint-disable no-bitwise */
40
+ let hash = 0x811c9dc5;
41
+ for (let i = 0; i < cacheKey.length; i++) {
42
+ hash ^= cacheKey.charCodeAt(i);
43
+ hash = Math.imul(hash, 0x01000193);
44
+ }
45
+ return (hash >>> 0) % this.size;
46
+ /* eslint-enable no-bitwise */
47
+ }
48
+ }
@@ -1,75 +1,13 @@
1
- import type { Dictionary } from '@crawlee/types';
2
- import { Configuration } from '../configuration.js';
1
+ import type { BaseHttpClient, Dictionary } from '@crawlee/types';
2
+ import type { Configuration } from '../configuration.js';
3
3
  import type { ProxyConfiguration } from '../proxy_configuration.js';
4
4
  import { type InternalSource, Request, type RequestOptions, type Source } from '../request.js';
5
+ import type { IRequestLoader } from './request_loader.js';
6
+ import type { IRequestManager } from './request_manager.js';
5
7
  /** @internal */
6
8
  export declare const STATE_PERSISTENCE_KEY = "REQUEST_LIST_STATE";
7
9
  /** @internal */
8
10
  export declare const REQUESTS_PERSISTENCE_KEY = "REQUEST_LIST_REQUESTS";
9
- /**
10
- * Represents a static list of URLs to crawl.
11
- */
12
- export interface IRequestList {
13
- /**
14
- * Returns the total number of unique requests present in the list.
15
- */
16
- length(): number;
17
- /**
18
- * Returns `true` if all requests were already handled and there are no more left.
19
- */
20
- isFinished(): Promise<boolean>;
21
- /**
22
- * Resolves to `true` if the next call to {@link IRequestList.fetchNextRequest} function
23
- * would return `null`, otherwise it resolves to `false`.
24
- * Note that even if the list is empty, there might be some pending requests currently being processed.
25
- */
26
- isEmpty(): Promise<boolean>;
27
- /**
28
- * Returns number of handled requests.
29
- */
30
- handledCount(): number;
31
- /**
32
- * Persists the current state of the `IRequestList` into the default {@link KeyValueStore}.
33
- * The state is persisted automatically in regular intervals, but calling this method manually
34
- * is useful in cases where you want to have the most current state available after you pause
35
- * or stop fetching its requests. For example after you pause or abort a crawl. Or just before
36
- * a server migration.
37
- */
38
- persistState(): Promise<void>;
39
- /**
40
- * Gets the next {@link Request} to process. First, the function gets a request previously reclaimed
41
- * using the {@link RequestList.reclaimRequest} function, if there is any.
42
- * Otherwise it gets the next request from sources.
43
- *
44
- * The function's `Promise` resolves to `null` if there are no more
45
- * requests to process.
46
- */
47
- fetchNextRequest(): Promise<Request | null>;
48
- /**
49
- * Gets the next {@link Request} to process. First, the function gets a request previously reclaimed
50
- * using the {@link RequestList.reclaimRequest} function, if there is any.
51
- * Otherwise it gets the next request from sources.
52
- *
53
- * The function resolves to `null` if there are no more requests to process.
54
- *
55
- * Can be used to iterate over the `RequestList` instance in a `for await .. of` loop.
56
- * Provides an alternative for the repeated use of `fetchNextRequest`.
57
- */
58
- [Symbol.asyncIterator](): AsyncGenerator<Request>;
59
- /**
60
- * Reclaims request to the list if its processing failed.
61
- * The request will become available in the next `this.fetchNextRequest()`.
62
- */
63
- reclaimRequest(request: Request): Promise<void>;
64
- /**
65
- * Marks request as handled after successful processing.
66
- */
67
- markRequestHandled(request: Request): Promise<void>;
68
- /**
69
- * @internal
70
- */
71
- inProgress: Set<string>;
72
- }
73
11
  export interface RequestListOptions {
74
12
  /**
75
13
  * An array of sources of URLs for the {@link RequestList}. It can be either an array of strings,
@@ -209,6 +147,12 @@ export interface RequestListOptions {
209
147
  keepDuplicateUrls?: boolean;
210
148
  /** @internal */
211
149
  config?: Configuration;
150
+ /**
151
+ * The HTTP client to be used to download `requestsFromUrl` URLs.
152
+ *
153
+ * If not specified the `RequestList` will use the default HTTP client.
154
+ */
155
+ httpClient?: BaseHttpClient;
212
156
  }
213
157
  /**
214
158
  * Represents a static list of URLs to crawl.
@@ -232,8 +176,8 @@ export interface RequestListOptions {
232
176
  * > In practical terms, such a combination can be useful when there is a large number of initial URLs,
233
177
  * > but more URLs would be added dynamically by the crawler.
234
178
  *
235
- * `RequestList` has an internal state where it stores information about which requests were already handled,
236
- * which are in progress and which were reclaimed. The state may be automatically persisted to the default
179
+ * `RequestList` has an internal state where it stores information about which requests were already handled
180
+ * and which are in progress. The state may be automatically persisted to the default
237
181
  * {@link KeyValueStore} by setting the `persistStateKey` option so that if the Node.js process is restarted,
238
182
  * the crawling can continue where it left off. The automated persisting is launched upon receiving the `persistState`
239
183
  * event that is periodically emitted by {@link EventManager}.
@@ -271,7 +215,7 @@ export interface RequestListOptions {
271
215
  * ```
272
216
  * @category Sources
273
217
  */
274
- export declare class RequestList implements IRequestList {
218
+ export declare class RequestList implements IRequestLoader {
275
219
  private log;
276
220
  /**
277
221
  * Array of all requests from all sources, in the order as they appeared in sources.
@@ -289,10 +233,11 @@ export declare class RequestList implements IRequestList {
289
233
  */
290
234
  inProgress: Set<string>;
291
235
  /**
292
- * Set of `uniqueKey`s of requests for which reclaimRequest() was called.
236
+ * `uniqueKey`s of requests that were in progress when the state was last persisted and thus need to be
237
+ * re-crawled after a restart. They are served before advancing through the rest of the sources.
293
238
  * @internal
294
239
  */
295
- reclaimed: Set<string>;
240
+ private requestsToRetry;
296
241
  /**
297
242
  * Starts as true because until we handle the first request, the list is effectively persisted by doing nothing.
298
243
  * @internal
@@ -313,7 +258,7 @@ export declare class RequestList implements IRequestList {
313
258
  private sources;
314
259
  private sourcesFunction?;
315
260
  private proxyConfiguration?;
316
- private events;
261
+ private httpClient?;
317
262
  /**
318
263
  * To create new instance of `RequestList` we need to use `RequestList.open()` factory method.
319
264
  * @param options All `RequestList` configuration options
@@ -342,6 +287,12 @@ export declare class RequestList implements IRequestList {
342
287
  * @inheritDoc
343
288
  */
344
289
  persistState(): Promise<void>;
290
+ /**
291
+ * Removes the `PERSIST_STATE` event listener registered during initialization and persists
292
+ * the current state one last time. Call this when you are done with the `RequestList` to avoid
293
+ * leaking the listener (and the requests it retains) on the shared event manager.
294
+ */
295
+ teardown(): Promise<void>;
345
296
  /**
346
297
  * Unlike persistState(), this is used only internally, since the sources
347
298
  * are automatically persisted at RequestList initialization (if the persistRequestsKey is set),
@@ -382,11 +333,7 @@ export declare class RequestList implements IRequestList {
382
333
  /**
383
334
  * @inheritDoc
384
335
  */
385
- markRequestHandled(request: Request): Promise<void>;
386
- /**
387
- * @inheritDoc
388
- */
389
- reclaimRequest(request: Request): Promise<void>;
336
+ markRequestAsHandled(request: Request): Promise<void>;
390
337
  /**
391
338
  * Adds all fetched requests from a URL from a remote resource.
392
339
  */
@@ -408,9 +355,9 @@ export declare class RequestList implements IRequestList {
408
355
  */
409
356
  protected _ensureUniqueKeyValid(uniqueKey: string): void;
410
357
  /**
411
- * Checks that request is not reclaimed and throws an error if so.
358
+ * Checks that a request is currently being processed and throws an error if not.
412
359
  */
413
- protected _ensureInProgressAndNotReclaimed(uniqueKey: string): void;
360
+ protected _ensureInProgress(uniqueKey: string): void;
414
361
  /**
415
362
  * Throws an error if request list wasn't initialized.
416
363
  */
@@ -418,11 +365,21 @@ export declare class RequestList implements IRequestList {
418
365
  /**
419
366
  * Returns the total number of unique requests present in the `RequestList`.
420
367
  */
421
- length(): number;
368
+ getTotalCount(): Promise<number>;
369
+ /**
370
+ * Returns the number of pending requests in the `RequestList`.
371
+ */
372
+ getPendingCount(): Promise<number>;
373
+ /**
374
+ * Combines this list with a request manager (a {@link RequestQueue} by default) into a
375
+ * {@link RequestManagerTandem}, allowing requests to be added and reclaimed while still
376
+ * being read from this list first.
377
+ */
378
+ toTandem(requestManager?: IRequestManager): Promise<IRequestManager>;
422
379
  /**
423
380
  * @inheritDoc
424
381
  */
425
- handledCount(): number;
382
+ getHandledCount(): Promise<number>;
426
383
  /**
427
384
  * Opens a request list and returns a promise resolving to an instance
428
385
  * of the {@link RequestList} class that is already initialized.
@@ -509,4 +466,3 @@ export interface RequestListState {
509
466
  type RequestListSource = string | Source;
510
467
  export type RequestListSourcesFunction = () => Promise<RequestListSource[]>;
511
468
  export {};
512
- //# sourceMappingURL=request_list.d.ts.map
@@ -1,9 +1,8 @@
1
1
  import { downloadListOfUrls } from '@crawlee/utils';
2
2
  import ow, { ArgumentError } from 'ow';
3
- import { Configuration } from '../configuration.js';
4
- import { log } from '../log.js';
5
3
  import { Request } from '../request.js';
6
4
  import { createDeserialize, serializeArray } from '../serialization.js';
5
+ import { serviceLocator } from '../service_locator.js';
7
6
  import { KeyValueStore } from './key_value_store.js';
8
7
  import { purgeDefaultStorages } from './utils.js';
9
8
  /** @internal */
@@ -33,8 +32,8 @@ const CONTENT_TYPE_BINARY = 'application/octet-stream';
33
32
  * > In practical terms, such a combination can be useful when there is a large number of initial URLs,
34
33
  * > but more URLs would be added dynamically by the crawler.
35
34
  *
36
- * `RequestList` has an internal state where it stores information about which requests were already handled,
37
- * which are in progress and which were reclaimed. The state may be automatically persisted to the default
35
+ * `RequestList` has an internal state where it stores information about which requests were already handled
36
+ * and which are in progress. The state may be automatically persisted to the default
38
37
  * {@link KeyValueStore} by setting the `persistStateKey` option so that if the Node.js process is restarted,
39
38
  * the crawling can continue where it left off. The automated persisting is launched upon receiving the `persistState`
40
39
  * event that is periodically emitted by {@link EventManager}.
@@ -73,7 +72,7 @@ const CONTENT_TYPE_BINARY = 'application/octet-stream';
73
72
  * @category Sources
74
73
  */
75
74
  export class RequestList {
76
- log = log.child({ prefix: 'RequestList' });
75
+ log = serviceLocator.getLogger().child({ prefix: 'RequestList' });
77
76
  /**
78
77
  * Array of all requests from all sources, in the order as they appeared in sources.
79
78
  * All requests in the array have distinct uniqueKey!
@@ -90,10 +89,11 @@ export class RequestList {
90
89
  */
91
90
  inProgress = new Set();
92
91
  /**
93
- * Set of `uniqueKey`s of requests for which reclaimRequest() was called.
92
+ * `uniqueKey`s of requests that were in progress when the state was last persisted and thus need to be
93
+ * re-crawled after a restart. They are served before advancing through the rest of the sources.
94
94
  * @internal
95
95
  */
96
- reclaimed = new Set();
96
+ requestsToRetry = [];
97
97
  /**
98
98
  * Starts as true because until we handle the first request, the list is effectively persisted by doing nothing.
99
99
  * @internal
@@ -114,14 +114,14 @@ export class RequestList {
114
114
  sources;
115
115
  sourcesFunction;
116
116
  proxyConfiguration;
117
- events;
117
+ httpClient;
118
118
  /**
119
119
  * To create new instance of `RequestList` we need to use `RequestList.open()` factory method.
120
120
  * @param options All `RequestList` configuration options
121
121
  * @internal
122
122
  */
123
123
  constructor(options = {}) {
124
- const { sources, sourcesFunction, persistStateKey, persistRequestsKey, state, proxyConfiguration, keepDuplicateUrls = false, config = Configuration.getGlobalConfig(), } = options;
124
+ const { sources, sourcesFunction, persistStateKey, persistRequestsKey, state, proxyConfiguration, keepDuplicateUrls = false, httpClient, } = options;
125
125
  if (!(sources || sourcesFunction)) {
126
126
  throw new ArgumentError('At least one of "sources" or "sourcesFunction" must be provided.', this.constructor);
127
127
  }
@@ -137,11 +137,12 @@ export class RequestList {
137
137
  }),
138
138
  keepDuplicateUrls: ow.optional.boolean,
139
139
  proxyConfiguration: ow.optional.object,
140
+ httpClient: ow.optional.object,
140
141
  }));
141
- this.persistStateKey = persistStateKey ? `SDK_${persistStateKey}` : persistStateKey;
142
- this.persistRequestsKey = persistRequestsKey ? `SDK_${persistRequestsKey}` : persistRequestsKey;
142
+ this.persistStateKey = persistStateKey ? `CRAWLEE_${persistStateKey}` : persistStateKey;
143
+ this.persistRequestsKey = persistRequestsKey ? `CRAWLEE_${persistRequestsKey}` : persistRequestsKey;
143
144
  this.initialState = state;
144
- this.events = config.getEventManager();
145
+ this.httpClient = httpClient;
145
146
  // If this option is set then all requests will get a pre-generated unique ID and duplicate URLs will be kept in the list.
146
147
  this.keepDuplicateUrls = keepDuplicateUrls;
147
148
  // Will be empty after initialization to save memory.
@@ -149,6 +150,7 @@ export class RequestList {
149
150
  this.sourcesFunction = sourcesFunction;
150
151
  // The proxy configuration used for `requestsFromUrl` requests.
151
152
  this.proxyConfiguration = proxyConfiguration;
153
+ this.persistState = this.persistState.bind(this);
152
154
  }
153
155
  /**
154
156
  * Loads all remote sources of URLs and potentially starts periodic state persistence.
@@ -174,7 +176,7 @@ export class RequestList {
174
176
  if (this.persistRequestsKey && !this.areRequestsPersisted)
175
177
  await this._persistRequests();
176
178
  if (this.persistStateKey) {
177
- this.events.on("persistState" /* EventType.PERSIST_STATE */, this.persistState.bind(this));
179
+ serviceLocator.getEventManager().on("persistState" /* EventType.PERSIST_STATE */, this.persistState);
178
180
  }
179
181
  return this;
180
182
  }
@@ -187,6 +189,7 @@ export class RequestList {
187
189
  // We don't need the sources so we purge them to
188
190
  // prevent them from hanging in memory.
189
191
  for (let i = 0; i < this.sources.length; i++) {
192
+ // oxlint-disable-next-line typescript/no-array-delete -- intentional, drop the slot so V8 can collect the object
190
193
  delete this.sources[i];
191
194
  }
192
195
  this.sources = [];
@@ -209,6 +212,7 @@ export class RequestList {
209
212
  const source = this.sources[i];
210
213
  // Using delete here to drop the original object ASAP to free memory
211
214
  // .pop would reverse the array and .shift is SLOW.
215
+ // oxlint-disable-next-line typescript/no-array-delete
212
216
  delete this.sources[i];
213
217
  if (typeof source === 'object' && source.requestsFromUrl) {
214
218
  const fetchedRequests = await this._fetchRequestsFromUrl(source);
@@ -225,9 +229,12 @@ export class RequestList {
225
229
  const sourcesFromFunction = await this.sourcesFunction();
226
230
  const sourcesFromFunctionCount = sourcesFromFunction.length;
227
231
  for (let i = 0; i < sourcesFromFunctionCount; i++) {
228
- const source = sourcesFromFunction.shift();
232
+ const source = sourcesFromFunction[i];
233
+ // oxlint-disable-next-line typescript/no-array-delete -- intentional, drop the slot so V8 can collect the object
234
+ delete sourcesFromFunction[i];
229
235
  this._addRequest(source);
230
236
  }
237
+ sourcesFromFunction.length = 0;
231
238
  }
232
239
  catch (e) {
233
240
  const err = e;
@@ -254,6 +261,17 @@ export class RequestList {
254
261
  this.log.exception(err, 'Attempted to persist state, but failed.');
255
262
  }
256
263
  }
264
+ /**
265
+ * Removes the `PERSIST_STATE` event listener registered during initialization and persists
266
+ * the current state one last time. Call this when you are done with the `RequestList` to avoid
267
+ * leaking the listener (and the requests it retains) on the shared event manager.
268
+ */
269
+ async teardown() {
270
+ serviceLocator.getEventManager().off("persistState" /* EventType.PERSIST_STATE */, this.persistState);
271
+ if (this.persistStateKey) {
272
+ await this.persistState();
273
+ }
274
+ }
257
275
  /**
258
276
  * Unlike persistState(), this is used only internally, since the sources
259
277
  * are automatically persisted at RequestList initialization (if the persistRequestsKey is set),
@@ -316,8 +334,8 @@ export class RequestList {
316
334
  this.inProgress.delete(uniqueKey);
317
335
  }
318
336
  }
319
- // All in-progress requests need to be re-crawled
320
- this.reclaimed = new Set(this.inProgress);
337
+ // All in-progress requests were interrupted and need to be re-crawled.
338
+ this.requestsToRetry = [...this.inProgress];
321
339
  }
322
340
  /**
323
341
  * Attempts to load state and requests using the `RequestList` configuration
@@ -359,7 +377,7 @@ export class RequestList {
359
377
  */
360
378
  async isEmpty() {
361
379
  this._ensureIsInitialized();
362
- return this.reclaimed.size === 0 && this.nextIndex >= this.requests.length;
380
+ return this.requestsToRetry.length === 0 && this.nextIndex >= this.requests.length;
363
381
  }
364
382
  /**
365
383
  * @inheritDoc
@@ -373,10 +391,9 @@ export class RequestList {
373
391
  */
374
392
  async fetchNextRequest() {
375
393
  this._ensureIsInitialized();
376
- // First return reclaimed requests if any.
377
- const uniqueKey = this.reclaimed.values().next().value;
394
+ // First re-serve any requests that were interrupted before the last state persist.
395
+ const uniqueKey = this.requestsToRetry.shift();
378
396
  if (uniqueKey) {
379
- this.reclaimed.delete(uniqueKey);
380
397
  const index = this.uniqueKeyToIndex[uniqueKey];
381
398
  return this.ensureRequest(this.requests[index], index);
382
399
  }
@@ -412,24 +429,14 @@ export class RequestList {
412
429
  /**
413
430
  * @inheritDoc
414
431
  */
415
- async markRequestHandled(request) {
432
+ async markRequestAsHandled(request) {
416
433
  const { uniqueKey } = request;
417
434
  this._ensureUniqueKeyValid(uniqueKey);
418
- this._ensureInProgressAndNotReclaimed(uniqueKey);
435
+ this._ensureInProgress(uniqueKey);
419
436
  this._ensureIsInitialized();
420
437
  this.inProgress.delete(uniqueKey);
421
438
  this.isStatePersisted = false;
422
439
  }
423
- /**
424
- * @inheritDoc
425
- */
426
- async reclaimRequest(request) {
427
- const { uniqueKey } = request;
428
- this._ensureUniqueKeyValid(uniqueKey);
429
- this._ensureInProgressAndNotReclaimed(uniqueKey);
430
- this._ensureIsInitialized();
431
- this.reclaimed.add(uniqueKey);
432
- }
433
440
  /**
434
441
  * Adds all fetched requests from a URL from a remote resource.
435
442
  */
@@ -524,15 +531,12 @@ export class RequestList {
524
531
  }
525
532
  }
526
533
  /**
527
- * Checks that request is not reclaimed and throws an error if so.
534
+ * Checks that a request is currently being processed and throws an error if not.
528
535
  */
529
- _ensureInProgressAndNotReclaimed(uniqueKey) {
536
+ _ensureInProgress(uniqueKey) {
530
537
  if (!this.inProgress.has(uniqueKey)) {
531
538
  throw new Error(`The request is not being processed (uniqueKey: ${uniqueKey})`);
532
539
  }
533
- if (this.reclaimed.has(uniqueKey)) {
534
- throw new Error(`The request was already reclaimed (uniqueKey: ${uniqueKey})`);
535
- }
536
540
  }
537
541
  /**
538
542
  * Throws an error if request list wasn't initialized.
@@ -545,14 +549,32 @@ export class RequestList {
545
549
  /**
546
550
  * Returns the total number of unique requests present in the `RequestList`.
547
551
  */
548
- length() {
552
+ async getTotalCount() {
549
553
  this._ensureIsInitialized();
550
554
  return this.requests.length;
551
555
  }
556
+ /**
557
+ * Returns the number of pending requests in the `RequestList`.
558
+ */
559
+ async getPendingCount() {
560
+ this._ensureIsInitialized();
561
+ return this.requests.length - (this.nextIndex - this.inProgress.size);
562
+ }
563
+ /**
564
+ * Combines this list with a request manager (a {@link RequestQueue} by default) into a
565
+ * {@link RequestManagerTandem}, allowing requests to be added and reclaimed while still
566
+ * being read from this list first.
567
+ */
568
+ async toTandem(requestManager) {
569
+ // Import here to avoid circular imports.
570
+ const { RequestManagerTandem } = await import('./request_manager_tandem.js');
571
+ const { RequestQueue } = await import('./request_queue.js');
572
+ return new RequestManagerTandem(this, requestManager ?? (await RequestQueue.open()));
573
+ }
552
574
  /**
553
575
  * @inheritDoc
554
576
  */
555
- handledCount() {
577
+ async getHandledCount() {
556
578
  this._ensureIsInitialized();
557
579
  return this.nextIndex - this.inProgress.size;
558
580
  }
@@ -633,7 +655,9 @@ export class RequestList {
633
655
  * @internal wraps public utility for mocking purposes
634
656
  */
635
657
  async _downloadListOfUrls(options) {
636
- return downloadListOfUrls(options);
658
+ return downloadListOfUrls({
659
+ ...options,
660
+ httpClient: this.httpClient,
661
+ });
637
662
  }
638
663
  }
639
- //# sourceMappingURL=request_list.js.map
@@ -0,0 +1,96 @@
1
+ import type { Dictionary } from '@crawlee/types';
2
+ import type { Request } from '../request.js';
3
+ import type { IRequestManager } from './request_manager.js';
4
+ import type { RequestQueueOperationInfo } from './request_queue.js';
5
+ /**
6
+ * An abstract interface defining a read-only stream of requests to crawl.
7
+ *
8
+ * Request loaders are used to manage and provide access to a storage of crawling requests.
9
+ *
10
+ * Key responsibilities:
11
+ * - Fetching the next request to be processed.
12
+ * - Marking requests as handled once they are no longer in progress.
13
+ * - Managing state information such as the total and handled request counts.
14
+ *
15
+ * ## Request lifecycle contract
16
+ *
17
+ * Every request returned by {@link IRequestLoader.fetchNextRequest} is considered **in progress**
18
+ * until it is passed to {@link IRequestLoader.markRequestAsHandled}. Once you fetch a request, you are
19
+ * obligated to eventually mark it as handled — there is no way to hand a request back to a loader
20
+ * (only an {@link IRequestManager} can reclaim requests for a retry). "Handled" therefore means
21
+ * "finished with this request", whether processing succeeded or was abandoned after exhausting retries.
22
+ *
23
+ * Honoring this contract matters for three reasons:
24
+ * - **Restarts and migrations:** loaders that persist their state (see {@link IRequestLoader.persistState})
25
+ * treat in-progress requests as interrupted and re-serve them after a restart. A request that is fetched
26
+ * but never marked handled will be crawled again.
27
+ * - **Termination detection:** {@link IRequestLoader.isFinished} only resolves to `true` once nothing is
28
+ * in progress. Leaving a request unmarked keeps the crawler running indefinitely.
29
+ * - **Bookkeeping:** the handled and pending counts are derived from the set of in-progress requests, so
30
+ * skipping {@link IRequestLoader.markRequestAsHandled} corrupts {@link IRequestLoader.getHandledCount}
31
+ * and {@link IRequestLoader.getPendingCount}.
32
+ *
33
+ * Concrete implementations such as {@link RequestList} or {@link SitemapRequestLoader} build on this interface.
34
+ * The {@link IRequestManager} interface extends it with the capability to enqueue and reclaim requests.
35
+ */
36
+ export interface IRequestLoader {
37
+ /**
38
+ * Returns an approximation of the total number of requests in the loader (i.e. pending + handled).
39
+ */
40
+ getTotalCount(): Promise<number>;
41
+ /**
42
+ * Returns an approximation of the number of pending requests in the loader.
43
+ */
44
+ getPendingCount(): Promise<number>;
45
+ /**
46
+ * Returns the number of requests in the loader that have been handled.
47
+ */
48
+ getHandledCount(): Promise<number>;
49
+ /**
50
+ * Returns `true` if all requests were already handled and there are no more left.
51
+ */
52
+ isFinished(): Promise<boolean>;
53
+ /**
54
+ * Resolves to `true` if the next call to {@link IRequestLoader.fetchNextRequest} function
55
+ * would return `null`, otherwise it resolves to `false`.
56
+ * Note that even if the loader is empty, there might be some pending requests currently being processed.
57
+ */
58
+ isEmpty(): Promise<boolean>;
59
+ /**
60
+ * Gets the next {@link Request} to process, or `null` if there are no more pending requests.
61
+ *
62
+ * The returned request is marked as **in progress** and remains so until it is passed to
63
+ * {@link IRequestLoader.markRequestAsHandled}. The caller is responsible for eventually marking
64
+ * every fetched request as handled; otherwise the loader never considers itself finished and the
65
+ * request may be re-served after a restart. See the request lifecycle contract on {@link IRequestLoader}.
66
+ */
67
+ fetchNextRequest<T extends Dictionary = Dictionary>(): Promise<Request<T> | null>;
68
+ /**
69
+ * Can be used to iterate over the loader instance in a `for await .. of` loop.
70
+ * Provides an alternative for the repeated use of `fetchNextRequest`.
71
+ */
72
+ [Symbol.asyncIterator](): AsyncGenerator<Request>;
73
+ /**
74
+ * Marks a request previously returned by {@link IRequestLoader.fetchNextRequest} as handled,
75
+ * removing it from the set of in-progress requests.
76
+ *
77
+ * Call this once you are done with the request — whether processing succeeded or was abandoned after
78
+ * exhausting retries. Because a loader cannot take a request back, marking it handled is the only way to
79
+ * signal completion; failing to do so prevents {@link IRequestLoader.isFinished} from ever resolving to
80
+ * `true` and skews the handled and pending counts. See the request lifecycle contract on {@link IRequestLoader}.
81
+ */
82
+ markRequestAsHandled(request: Request): Promise<RequestQueueOperationInfo | void | null>;
83
+ /**
84
+ * Persists the current state of the loader into the default {@link KeyValueStore}.
85
+ *
86
+ * Not all loaders support persistence; implementations that do not should leave this `undefined`.
87
+ */
88
+ persistState?(): Promise<void>;
89
+ /**
90
+ * Combines the loader with a request manager to support adding and reclaiming requests.
91
+ *
92
+ * @param requestManager Request manager to combine the loader with. If not provided, the default
93
+ * {@link RequestQueue} is used.
94
+ */
95
+ toTandem?(requestManager?: IRequestManager): Promise<IRequestManager>;
96
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,33 @@
1
+ import type { Request, Source } from '../request.js';
2
+ import type { IRequestLoader } from './request_loader.js';
3
+ import type { AddRequestsBatchedOptions, AddRequestsBatchedResult, RequestQueueOperationInfo, RequestQueueOperationOptions } from './request_queue.js';
4
+ export type RequestsLike = AsyncIterable<Source | string> | Iterable<Source | string> | (Source | string)[];
5
+ /**
6
+ * Extends the read-only {@link IRequestLoader} interface with the capability to enqueue new requests
7
+ * and reclaim failed ones.
8
+ */
9
+ export interface IRequestManager extends IRequestLoader {
10
+ /**
11
+ * Reclaims request to the provider if its processing failed.
12
+ * The request will be returned by some subsequent `fetchNextRequest()` call.
13
+ */
14
+ reclaimRequest(request: Request, options?: RequestQueueOperationOptions): Promise<RequestQueueOperationInfo | null>;
15
+ addRequest(requestLike: Source, options?: RequestQueueOperationOptions): Promise<RequestQueueOperationInfo>;
16
+ addRequestsBatched(requests: RequestsLike, options?: AddRequestsBatchedOptions): Promise<AddRequestsBatchedResult>;
17
+ /**
18
+ * Remove all requests from the queue but keep the queue itself, resetting it
19
+ * so it can be reused (e.g. across multiple `crawler.run()` calls).
20
+ *
21
+ * Implementations that do not support purging may leave this `undefined`.
22
+ */
23
+ purge?(): Promise<void>;
24
+ /**
25
+ * Tells the manager how long a consumer expects to hold a request fetched via `fetchNextRequest()`
26
+ * before marking it handled or reclaiming it (typically the request-handler timeout plus padding).
27
+ *
28
+ * Managers backed by a storage backend that reserves requests via locking use this to avoid handing
29
+ * the same request out again while it is still being processed. Implementations that do not need
30
+ * this hint may leave it `undefined`.
31
+ */
32
+ setExpectedRequestProcessingTimeSecs?(secs: number): Promise<void>;
33
+ }
@@ -0,0 +1 @@
1
+ export {};