@crawlee/core 4.0.0-beta.22 → 4.0.0-beta.220

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 (270) hide show
  1. package/README.md +15 -15
  2. package/byte_utils.d.ts +17 -0
  3. package/byte_utils.js +42 -0
  4. package/configuration.d.ts +77 -235
  5. package/configuration.js +162 -226
  6. package/debug.d.ts +36 -0
  7. package/debug.js +70 -0
  8. package/errors.d.ts +45 -18
  9. package/errors.js +58 -27
  10. package/events/event_manager.d.ts +35 -9
  11. package/events/event_manager.js +12 -13
  12. package/events/index.d.ts +1 -1
  13. package/events/index.js +0 -1
  14. package/events/local_event_manager.d.ts +14 -9
  15. package/events/local_event_manager.js +45 -19
  16. package/events/system_info.d.ts +38 -0
  17. package/events/system_info.js +1 -0
  18. package/index.d.ts +7 -11
  19. package/index.js +8 -10
  20. package/internal.d.ts +8 -0
  21. package/internal.js +9 -0
  22. package/iterables.d.ts +79 -0
  23. package/iterables.js +134 -0
  24. package/log.d.ts +77 -1
  25. package/log.js +134 -1
  26. package/memory-storage/consts.d.ts +4 -0
  27. package/memory-storage/consts.js +4 -0
  28. package/memory-storage/index.d.ts +1 -0
  29. package/memory-storage/index.js +1 -0
  30. package/memory-storage/memory-storage.d.ts +43 -0
  31. package/memory-storage/memory-storage.js +158 -0
  32. package/memory-storage/resource-clients/common/base-client.d.ts +4 -0
  33. package/memory-storage/resource-clients/common/base-client.js +6 -0
  34. package/memory-storage/resource-clients/dataset.d.ts +35 -0
  35. package/memory-storage/resource-clients/dataset.js +105 -0
  36. package/memory-storage/resource-clients/key-value-store.d.ts +54 -0
  37. package/memory-storage/resource-clients/key-value-store.js +179 -0
  38. package/memory-storage/resource-clients/request-queue.d.ts +55 -0
  39. package/memory-storage/resource-clients/request-queue.js +413 -0
  40. package/memory-storage/utils.d.ts +16 -0
  41. package/memory-storage/utils.js +41 -0
  42. package/owned_or_injected.d.ts +58 -0
  43. package/owned_or_injected.js +98 -0
  44. package/package.json +20 -21
  45. package/proxy_configuration.d.ts +28 -79
  46. package/proxy_configuration.js +40 -146
  47. package/recoverable_state.d.ts +106 -52
  48. package/recoverable_state.js +200 -72
  49. package/request.d.ts +48 -93
  50. package/request.js +112 -230
  51. package/serialization.d.ts +0 -1
  52. package/serialization.js +18 -18
  53. package/service_locator.d.ts +168 -0
  54. package/service_locator.js +255 -0
  55. package/storages/batched_adds.d.ts +37 -0
  56. package/storages/batched_adds.js +73 -0
  57. package/storages/dataset.d.ts +105 -58
  58. package/storages/dataset.js +291 -149
  59. package/storages/index.d.ts +8 -9
  60. package/storages/index.js +4 -9
  61. package/storages/key_value_store.d.ts +169 -72
  62. package/storages/key_value_store.js +451 -196
  63. package/storages/key_value_store_codec.d.ts +32 -0
  64. package/storages/key_value_store_codec.js +108 -0
  65. package/storages/request_dedup_cache.d.ts +20 -0
  66. package/storages/request_dedup_cache.js +48 -0
  67. package/storages/request_list.d.ts +33 -173
  68. package/storages/request_list.js +275 -254
  69. package/storages/request_loader.d.ts +127 -0
  70. package/storages/request_loader.js +36 -0
  71. package/storages/request_manager.d.ts +119 -0
  72. package/storages/request_manager.js +1 -0
  73. package/storages/request_manager_tandem.d.ts +39 -25
  74. package/storages/request_manager_tandem.js +134 -58
  75. package/storages/request_queue.d.ts +261 -48
  76. package/storages/request_queue.js +766 -216
  77. package/storages/storage_instance_manager.d.ts +86 -0
  78. package/storages/storage_instance_manager.js +256 -0
  79. package/storages/storage_stats.d.ts +48 -0
  80. package/storages/storage_stats.js +29 -0
  81. package/storages/transaction.d.ts +270 -0
  82. package/storages/transaction.js +296 -0
  83. package/storages/utils.d.ts +59 -11
  84. package/storages/utils.js +75 -15
  85. package/system-info/cpu-info.d.ts +67 -0
  86. package/system-info/cpu-info.js +216 -0
  87. package/system-info/memory-info.d.ts +31 -0
  88. package/system-info/memory-info.js +115 -0
  89. package/system-info/ps-tree.d.ts +17 -0
  90. package/system-info/ps-tree.js +144 -0
  91. package/system-info/runtime.d.ts +14 -0
  92. package/system-info/runtime.js +80 -0
  93. package/typedefs.d.ts +0 -6
  94. package/typedefs.js +0 -1
  95. package/url.d.ts +9 -0
  96. package/url.js +11 -0
  97. package/validators.d.ts +23 -18
  98. package/validators.js +14 -18
  99. package/autoscaling/autoscaled_pool.d.ts +0 -300
  100. package/autoscaling/autoscaled_pool.d.ts.map +0 -1
  101. package/autoscaling/autoscaled_pool.js +0 -533
  102. package/autoscaling/autoscaled_pool.js.map +0 -1
  103. package/autoscaling/index.d.ts +0 -4
  104. package/autoscaling/index.d.ts.map +0 -1
  105. package/autoscaling/index.js +0 -4
  106. package/autoscaling/index.js.map +0 -1
  107. package/autoscaling/snapshotter.d.ts +0 -189
  108. package/autoscaling/snapshotter.d.ts.map +0 -1
  109. package/autoscaling/snapshotter.js +0 -285
  110. package/autoscaling/snapshotter.js.map +0 -1
  111. package/autoscaling/system_status.d.ts +0 -181
  112. package/autoscaling/system_status.d.ts.map +0 -1
  113. package/autoscaling/system_status.js +0 -169
  114. package/autoscaling/system_status.js.map +0 -1
  115. package/configuration.d.ts.map +0 -1
  116. package/configuration.js.map +0 -1
  117. package/cookie_utils.d.ts +0 -43
  118. package/cookie_utils.d.ts.map +0 -1
  119. package/cookie_utils.js +0 -111
  120. package/cookie_utils.js.map +0 -1
  121. package/crawlers/context_pipeline.d.ts +0 -61
  122. package/crawlers/context_pipeline.d.ts.map +0 -1
  123. package/crawlers/context_pipeline.js +0 -99
  124. package/crawlers/context_pipeline.js.map +0 -1
  125. package/crawlers/crawler_commons.d.ts +0 -194
  126. package/crawlers/crawler_commons.d.ts.map +0 -1
  127. package/crawlers/crawler_commons.js +0 -108
  128. package/crawlers/crawler_commons.js.map +0 -1
  129. package/crawlers/crawler_utils.d.ts +0 -10
  130. package/crawlers/crawler_utils.d.ts.map +0 -1
  131. package/crawlers/crawler_utils.js +0 -12
  132. package/crawlers/crawler_utils.js.map +0 -1
  133. package/crawlers/error_snapshotter.d.ts +0 -60
  134. package/crawlers/error_snapshotter.d.ts.map +0 -1
  135. package/crawlers/error_snapshotter.js +0 -118
  136. package/crawlers/error_snapshotter.js.map +0 -1
  137. package/crawlers/error_tracker.d.ts +0 -55
  138. package/crawlers/error_tracker.d.ts.map +0 -1
  139. package/crawlers/error_tracker.js +0 -309
  140. package/crawlers/error_tracker.js.map +0 -1
  141. package/crawlers/index.d.ts +0 -7
  142. package/crawlers/index.d.ts.map +0 -1
  143. package/crawlers/index.js +0 -7
  144. package/crawlers/index.js.map +0 -1
  145. package/crawlers/internals/types.d.ts +0 -8
  146. package/crawlers/internals/types.d.ts.map +0 -1
  147. package/crawlers/internals/types.js +0 -2
  148. package/crawlers/internals/types.js.map +0 -1
  149. package/crawlers/statistics.d.ts +0 -213
  150. package/crawlers/statistics.d.ts.map +0 -1
  151. package/crawlers/statistics.js +0 -349
  152. package/crawlers/statistics.js.map +0 -1
  153. package/enqueue_links/enqueue_links.d.ts +0 -249
  154. package/enqueue_links/enqueue_links.d.ts.map +0 -1
  155. package/enqueue_links/enqueue_links.js +0 -269
  156. package/enqueue_links/enqueue_links.js.map +0 -1
  157. package/enqueue_links/index.d.ts +0 -3
  158. package/enqueue_links/index.d.ts.map +0 -1
  159. package/enqueue_links/index.js +0 -3
  160. package/enqueue_links/index.js.map +0 -1
  161. package/enqueue_links/shared.d.ts +0 -73
  162. package/enqueue_links/shared.d.ts.map +0 -1
  163. package/enqueue_links/shared.js +0 -212
  164. package/enqueue_links/shared.js.map +0 -1
  165. package/errors.d.ts.map +0 -1
  166. package/errors.js.map +0 -1
  167. package/events/event_manager.d.ts.map +0 -1
  168. package/events/event_manager.js.map +0 -1
  169. package/events/index.d.ts.map +0 -1
  170. package/events/index.js.map +0 -1
  171. package/events/local_event_manager.d.ts.map +0 -1
  172. package/events/local_event_manager.js.map +0 -1
  173. package/http_clients/base-http-client.d.ts +0 -12
  174. package/http_clients/base-http-client.d.ts.map +0 -1
  175. package/http_clients/base-http-client.js +0 -44
  176. package/http_clients/base-http-client.js.map +0 -1
  177. package/http_clients/form-data-like.d.ts +0 -67
  178. package/http_clients/form-data-like.d.ts.map +0 -1
  179. package/http_clients/form-data-like.js +0 -5
  180. package/http_clients/form-data-like.js.map +0 -1
  181. package/http_clients/got-scraping-http-client.d.ts +0 -22
  182. package/http_clients/got-scraping-http-client.d.ts.map +0 -1
  183. package/http_clients/got-scraping-http-client.js +0 -113
  184. package/http_clients/got-scraping-http-client.js.map +0 -1
  185. package/http_clients/index.d.ts +0 -3
  186. package/http_clients/index.d.ts.map +0 -1
  187. package/http_clients/index.js +0 -3
  188. package/http_clients/index.js.map +0 -1
  189. package/index.d.ts.map +0 -1
  190. package/index.js.map +0 -1
  191. package/log.d.ts.map +0 -1
  192. package/log.js.map +0 -1
  193. package/proxy_configuration.d.ts.map +0 -1
  194. package/proxy_configuration.js.map +0 -1
  195. package/recoverable_state.d.ts.map +0 -1
  196. package/recoverable_state.js.map +0 -1
  197. package/request.d.ts.map +0 -1
  198. package/request.js.map +0 -1
  199. package/router.d.ts +0 -134
  200. package/router.d.ts.map +0 -1
  201. package/router.js +0 -164
  202. package/router.js.map +0 -1
  203. package/serialization.d.ts.map +0 -1
  204. package/serialization.js.map +0 -1
  205. package/session_pool/consts.d.ts +0 -4
  206. package/session_pool/consts.d.ts.map +0 -1
  207. package/session_pool/consts.js +0 -4
  208. package/session_pool/consts.js.map +0 -1
  209. package/session_pool/errors.d.ts +0 -8
  210. package/session_pool/errors.d.ts.map +0 -1
  211. package/session_pool/errors.js +0 -12
  212. package/session_pool/errors.js.map +0 -1
  213. package/session_pool/events.d.ts +0 -3
  214. package/session_pool/events.d.ts.map +0 -1
  215. package/session_pool/events.js +0 -3
  216. package/session_pool/events.js.map +0 -1
  217. package/session_pool/index.d.ts +0 -6
  218. package/session_pool/index.d.ts.map +0 -1
  219. package/session_pool/index.js +0 -6
  220. package/session_pool/index.js.map +0 -1
  221. package/session_pool/session.d.ts +0 -186
  222. package/session_pool/session.d.ts.map +0 -1
  223. package/session_pool/session.js +0 -278
  224. package/session_pool/session.js.map +0 -1
  225. package/session_pool/session_pool.d.ts +0 -258
  226. package/session_pool/session_pool.d.ts.map +0 -1
  227. package/session_pool/session_pool.js +0 -387
  228. package/session_pool/session_pool.js.map +0 -1
  229. package/storages/access_checking.d.ts +0 -13
  230. package/storages/access_checking.d.ts.map +0 -1
  231. package/storages/access_checking.js +0 -14
  232. package/storages/access_checking.js.map +0 -1
  233. package/storages/dataset.d.ts.map +0 -1
  234. package/storages/dataset.js.map +0 -1
  235. package/storages/index.d.ts.map +0 -1
  236. package/storages/index.js.map +0 -1
  237. package/storages/key_value_store.d.ts.map +0 -1
  238. package/storages/key_value_store.js.map +0 -1
  239. package/storages/request_list.d.ts.map +0 -1
  240. package/storages/request_list.js.map +0 -1
  241. package/storages/request_list_adapter.d.ts +0 -58
  242. package/storages/request_list_adapter.d.ts.map +0 -1
  243. package/storages/request_list_adapter.js +0 -81
  244. package/storages/request_list_adapter.js.map +0 -1
  245. package/storages/request_manager_tandem.d.ts.map +0 -1
  246. package/storages/request_manager_tandem.js.map +0 -1
  247. package/storages/request_provider.d.ts +0 -372
  248. package/storages/request_provider.d.ts.map +0 -1
  249. package/storages/request_provider.js +0 -591
  250. package/storages/request_provider.js.map +0 -1
  251. package/storages/request_queue.d.ts.map +0 -1
  252. package/storages/request_queue.js.map +0 -1
  253. package/storages/request_queue_v2.d.ts +0 -87
  254. package/storages/request_queue_v2.d.ts.map +0 -1
  255. package/storages/request_queue_v2.js +0 -438
  256. package/storages/request_queue_v2.js.map +0 -1
  257. package/storages/sitemap_request_list.d.ts +0 -250
  258. package/storages/sitemap_request_list.d.ts.map +0 -1
  259. package/storages/sitemap_request_list.js +0 -431
  260. package/storages/sitemap_request_list.js.map +0 -1
  261. package/storages/storage_manager.d.ts +0 -62
  262. package/storages/storage_manager.d.ts.map +0 -1
  263. package/storages/storage_manager.js +0 -105
  264. package/storages/storage_manager.js.map +0 -1
  265. package/storages/utils.d.ts.map +0 -1
  266. package/storages/utils.js.map +0 -1
  267. package/typedefs.d.ts.map +0 -1
  268. package/typedefs.js.map +0 -1
  269. package/validators.d.ts.map +0 -1
  270. package/validators.js.map +0 -1
@@ -1,9 +1,10 @@
1
1
  import { downloadListOfUrls } from '@crawlee/utils';
2
- import ow, { ArgumentError } from 'ow';
3
- import { Configuration } from '../configuration.js';
4
- import { log } from '../log.js';
2
+ import { z } from 'zod';
3
+ import { convertStateSync, RecoverableState } from '../recoverable_state.js';
5
4
  import { Request } from '../request.js';
6
5
  import { createDeserialize, serializeArray } from '../serialization.js';
6
+ import { serviceLocator } from '../service_locator.js';
7
+ import { parseArgument, schemas, validators } from '../validators.js';
7
8
  import { KeyValueStore } from './key_value_store.js';
8
9
  import { purgeDefaultStorages } from './utils.js';
9
10
  /** @internal */
@@ -11,6 +12,87 @@ export const STATE_PERSISTENCE_KEY = 'REQUEST_LIST_STATE';
11
12
  /** @internal */
12
13
  export const REQUESTS_PERSISTENCE_KEY = 'REQUEST_LIST_REQUESTS';
13
14
  const CONTENT_TYPE_BINARY = 'application/octet-stream';
15
+ const persistedRequestListState = z.object({
16
+ nextIndex: z.number().int().nonnegative(),
17
+ nextUniqueKey: z.string().nullable(),
18
+ inProgress: z.array(z.string()),
19
+ });
20
+ const requestListInternalState = z.object({
21
+ /** Position of the next request to be processed. */
22
+ nextIndex: z.number().int().nonnegative(),
23
+ /** `uniqueKey`s of the requests being processed at the moment. */
24
+ inProgress: z.set(z.string()),
25
+ });
26
+ const requestListOptionsSchema = z.strictObject({
27
+ sources: schemas.anyArray.optional(), // check only for array and not subtypes to avoid iteration over the whole thing
28
+ sourcesFunction: schemas.anyFunction.optional(),
29
+ persistStateKey: z.string().optional(),
30
+ persistRequestsKey: z.string().optional(),
31
+ state: persistedRequestListState.optional(),
32
+ keepDuplicateUrls: z.boolean().default(false),
33
+ proxyConfiguration: validators.proxyConfiguration.optional(),
34
+ httpClient: schemas.httpClient.optional(),
35
+ });
36
+ const listNameSchema = z.string().nullish();
37
+ const openOptionsSchema = z.looseObject({});
38
+ /**
39
+ * The conversion between the crawling position and its persisted record. Decoding also checks the record against
40
+ * the loaded requests, which is why the codec is built per list.
41
+ */
42
+ function buildRequestListStateCodec(list) {
43
+ return z.codec(persistedRequestListState, requestListInternalState, {
44
+ decode: (record, ctx) => {
45
+ const { requests } = list;
46
+ const { nextIndex, nextUniqueKey } = record;
47
+ const inconsistent = (message) => {
48
+ ctx.issues.push({ code: 'custom', message, input: record });
49
+ return z.NEVER;
50
+ };
51
+ if (nextIndex > requests.length) {
52
+ return inconsistent('The state object is not consistent with RequestList, too few requests loaded.');
53
+ }
54
+ if (nextIndex < requests.length && requests[nextIndex].uniqueKey !== nextUniqueKey) {
55
+ return inconsistent('The state object is not consistent with RequestList the order of URLs seems to have changed.');
56
+ }
57
+ const inProgress = new Set(record.inProgress);
58
+ const deleteFromInProgress = [];
59
+ for (const uniqueKey of inProgress) {
60
+ const index = list.indexOf(uniqueKey);
61
+ if (index === undefined) {
62
+ return inconsistent('The state object is not consistent with RequestList. Unknown uniqueKey is present in the state.');
63
+ }
64
+ if (index >= nextIndex) {
65
+ deleteFromInProgress.push(uniqueKey);
66
+ }
67
+ }
68
+ // WORKAROUND:
69
+ // It happened to some users that state object contained something like:
70
+ // {
71
+ // "nextIndex": 11308,
72
+ // "nextUniqueKey": "https://www.anychart.com",
73
+ // "inProgress": {
74
+ // "https://www.ams360.com": true,
75
+ // ...
76
+ // "https://www.anychart.com": true,
77
+ // }
78
+ // Which then caused error "The request is not being processed (uniqueKey: https://www.anychart.com)"
79
+ // As a workaround, we just remove all inProgress requests whose index >= nextIndex,
80
+ // since they will be crawled again.
81
+ if (deleteFromInProgress.length) {
82
+ list.log.warning("RequestList's in-progress field is not consistent, skipping invalid in-progress entries", { deleteFromInProgress });
83
+ for (const uniqueKey of deleteFromInProgress) {
84
+ inProgress.delete(uniqueKey);
85
+ }
86
+ }
87
+ return { nextIndex, inProgress };
88
+ },
89
+ encode: ({ nextIndex, inProgress }) => ({
90
+ nextIndex,
91
+ nextUniqueKey: nextIndex < list.requests.length ? list.requests[nextIndex].uniqueKey : null,
92
+ inProgress: [...inProgress],
93
+ }),
94
+ });
95
+ }
14
96
  /**
15
97
  * Represents a static list of URLs to crawl.
16
98
  * The URLs can be provided either in code or parsed from a text file hosted on the web.
@@ -33,8 +115,8 @@ const CONTENT_TYPE_BINARY = 'application/octet-stream';
33
115
  * > In practical terms, such a combination can be useful when there is a large number of initial URLs,
34
116
  * > but more URLs would be added dynamically by the crawler.
35
117
  *
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
118
+ * `RequestList` has an internal state where it stores information about which requests were already handled
119
+ * and which are in progress. The state may be automatically persisted to the default
38
120
  * {@link KeyValueStore} by setting the `persistStateKey` option so that if the Node.js process is restarted,
39
121
  * the crawling can continue where it left off. The automated persisting is launched upon receiving the `persistState`
40
122
  * event that is periodically emitted by {@link EventManager}.
@@ -73,112 +155,124 @@ const CONTENT_TYPE_BINARY = 'application/octet-stream';
73
155
  * @category Sources
74
156
  */
75
157
  export class RequestList {
76
- log = log.child({ prefix: 'RequestList' });
158
+ #log = serviceLocator.getLogger().child({ prefix: 'RequestList' });
77
159
  /**
78
160
  * Array of all requests from all sources, in the order as they appeared in sources.
79
161
  * All requests in the array have distinct uniqueKey!
80
162
  * @internal
81
163
  */
82
164
  requests = [];
83
- /** Index to the next item in requests array to fetch. All previous requests are either handled or in progress. */
84
- nextIndex = 0;
85
165
  /** Dictionary, key is Request.uniqueKey, value is corresponding index in the requests array. */
86
- uniqueKeyToIndex = {};
166
+ #uniqueKeyToIndex = {};
87
167
  /**
88
- * Set of `uniqueKey`s of requests that were returned by fetchNextRequest().
89
- * @internal
168
+ * The crawling position, kept in a {@link RecoverableState} that handles the loading, validation and
169
+ * periodic persistence of the record under `persistStateKey`.
90
170
  */
91
- inProgress = new Set();
171
+ #state;
172
+ #stateCodec;
92
173
  /**
93
- * Set of `uniqueKey`s of requests for which reclaimRequest() was called.
174
+ * Set of `uniqueKey`s of requests that were returned by fetchNextRequest().
94
175
  * @internal
95
176
  */
96
- reclaimed = new Set();
177
+ get inProgress() {
178
+ return this.#state.currentValue.inProgress;
179
+ }
97
180
  /**
98
- * Starts as true because until we handle the first request, the list is effectively persisted by doing nothing.
181
+ * `uniqueKey`s of requests that were in progress when the state was last persisted and thus need to be
182
+ * re-crawled after a restart. They are served before advancing through the rest of the sources.
99
183
  * @internal
100
184
  */
101
- isStatePersisted = true;
185
+ #requestsToRetry = [];
102
186
  /**
103
187
  * Starts as false because we don't know yet and sources might change in the meantime (eg. download from live list).
104
188
  * @internal
105
189
  */
106
190
  areRequestsPersisted = false;
107
- isLoading = false;
108
- isInitialized = false;
109
- persistStateKey;
110
- persistRequestsKey;
111
- initialState;
112
- store;
113
- keepDuplicateUrls;
114
- sources;
115
- sourcesFunction;
116
- proxyConfiguration;
117
- events;
118
- httpClient;
191
+ #isLoading = false;
192
+ #isInitialized = false;
193
+ #persistStateKey;
194
+ #persistRequestsKey;
195
+ #store;
196
+ #keepDuplicateUrls;
197
+ #sources;
198
+ #sourcesFunction;
199
+ #proxyConfiguration;
200
+ #httpClient;
119
201
  /**
120
202
  * To create new instance of `RequestList` we need to use `RequestList.open()` factory method.
121
203
  * @param options All `RequestList` configuration options
122
204
  * @internal
123
205
  */
124
206
  constructor(options = {}) {
125
- const { sources, sourcesFunction, persistStateKey, persistRequestsKey, state, proxyConfiguration, keepDuplicateUrls = false, config = Configuration.getGlobalConfig(), httpClient, } = options;
207
+ const { sources, sourcesFunction, persistStateKey, persistRequestsKey, state, proxyConfiguration, keepDuplicateUrls, httpClient, } = parseArgument(options, requestListOptionsSchema);
126
208
  if (!(sources || sourcesFunction)) {
127
- throw new ArgumentError('At least one of "sources" or "sourcesFunction" must be provided.', this.constructor);
128
- }
129
- ow(options, ow.object.exactShape({
130
- sources: ow.optional.array, // check only for array and not subtypes to avoid iteration over the whole thing
131
- sourcesFunction: ow.optional.function,
132
- persistStateKey: ow.optional.string,
133
- persistRequestsKey: ow.optional.string,
134
- state: ow.optional.object.exactShape({
135
- nextIndex: ow.number,
136
- nextUniqueKey: ow.string,
137
- inProgress: ow.object,
138
- }),
139
- keepDuplicateUrls: ow.optional.boolean,
140
- proxyConfiguration: ow.optional.object,
141
- httpClient: ow.optional.object,
142
- }));
143
- this.persistStateKey = persistStateKey ? `SDK_${persistStateKey}` : persistStateKey;
144
- this.persistRequestsKey = persistRequestsKey ? `SDK_${persistRequestsKey}` : persistRequestsKey;
145
- this.initialState = state;
146
- this.events = config.getEventManager();
147
- this.httpClient = httpClient;
209
+ throw new Error('At least one of "sources" or "sourcesFunction" must be provided.');
210
+ }
211
+ this.#persistStateKey = persistStateKey ? `CRAWLEE_${persistStateKey}` : persistStateKey;
212
+ this.#persistRequestsKey = persistRequestsKey ? `CRAWLEE_${persistRequestsKey}` : persistRequestsKey;
213
+ this.#httpClient = httpClient;
214
+ this.#stateCodec = buildRequestListStateCodec({
215
+ requests: this.requests,
216
+ indexOf: (uniqueKey) => {
217
+ const index = this.#uniqueKeyToIndex[uniqueKey];
218
+ return typeof index === 'number' ? index : undefined;
219
+ },
220
+ log: this.#log,
221
+ });
222
+ const stateKey = this.#persistStateKey ?? STATE_PERSISTENCE_KEY;
223
+ this.#state = new RecoverableState({
224
+ // The `state` option is where the list starts when there is no persisted record to restore. The factory
225
+ // runs on `initialize()`, once the requests it is validated against are loaded.
226
+ defaultState: () => state
227
+ ? convertStateSync(this.#stateCodec, state, stateKey)
228
+ : { nextIndex: 0, inProgress: new Set() },
229
+ persistStateKey: stateKey,
230
+ persistenceEnabled: !!this.#persistStateKey,
231
+ logger: this.#log,
232
+ // The codec validates in the decode direction, so it is a Standard Schema as-is; encoding needs a call.
233
+ deserialize: this.#stateCodec,
234
+ serialize: (internalState) => this.#stateCodec.encode(internalState),
235
+ });
148
236
  // If this option is set then all requests will get a pre-generated unique ID and duplicate URLs will be kept in the list.
149
- this.keepDuplicateUrls = keepDuplicateUrls;
237
+ this.#keepDuplicateUrls = keepDuplicateUrls;
150
238
  // Will be empty after initialization to save memory.
151
- this.sources = sources ? [...sources] : [];
152
- this.sourcesFunction = sourcesFunction;
239
+ this.#sources = sources ? [...sources] : [];
240
+ this.#sourcesFunction = sourcesFunction;
153
241
  // The proxy configuration used for `requestsFromUrl` requests.
154
- this.proxyConfiguration = proxyConfiguration;
242
+ this.#proxyConfiguration = proxyConfiguration;
155
243
  }
156
244
  /**
157
245
  * Loads all remote sources of URLs and potentially starts periodic state persistence.
158
246
  * This function must be called before you can start using the instance in a meaningful way.
159
247
  */
160
- async initialize() {
161
- if (this.isLoading) {
248
+ async #initialize() {
249
+ if (this.#isLoading) {
162
250
  throw new Error('RequestList sources are already loading or were loaded.');
163
251
  }
164
- this.isLoading = true;
252
+ this.#isLoading = true;
165
253
  await purgeDefaultStorages({ onlyPurgeOnce: true });
166
- const [state, persistedRequests] = await this._loadStateAndPersistedRequests();
254
+ let persistedRequests;
255
+ if (this.#persistRequestsKey) {
256
+ persistedRequests = await this.#getPersistedState(this.#persistRequestsKey);
257
+ if (persistedRequests)
258
+ this.#log.debug('Loaded requests from key value store using the persistRequestsKey.');
259
+ }
167
260
  // Add persisted requests / new sources in a memory efficient way because with very
168
261
  // large lists, we were running out of memory.
169
262
  if (persistedRequests) {
170
- await this._addPersistedRequests(persistedRequests);
263
+ await this.#addPersistedRequests(persistedRequests);
171
264
  }
172
265
  else {
173
- await this._addRequestsFromSources();
174
- }
175
- this._restoreState(state);
176
- this.isInitialized = true;
177
- if (this.persistRequestsKey && !this.areRequestsPersisted)
178
- await this._persistRequests();
179
- if (this.persistStateKey) {
180
- this.events.on("persistState" /* EventType.PERSIST_STATE */, this.persistState.bind(this));
181
- }
266
+ await this.#addRequestsFromSources();
267
+ }
268
+ // Restores the crawling position - the requests have to be loaded first, as the record is validated
269
+ // against them. Also starts the periodic state persistence when `persistStateKey` is set.
270
+ await this.#state.initialize();
271
+ // All in-progress requests were interrupted and need to be re-crawled.
272
+ this.#requestsToRetry = [...this.inProgress];
273
+ this.#isInitialized = true;
274
+ if (this.#persistRequestsKey && !this.areRequestsPersisted)
275
+ await this.#persistRequests();
182
276
  return this;
183
277
  }
184
278
  /**
@@ -186,17 +280,18 @@ export class RequestList {
186
280
  * This needs to be done in a memory efficient way. We should update the input
187
281
  * to a Stream once apify-client supports streams.
188
282
  */
189
- async _addPersistedRequests(persistedRequests) {
283
+ async #addPersistedRequests(persistedRequests) {
190
284
  // We don't need the sources so we purge them to
191
285
  // prevent them from hanging in memory.
192
- for (let i = 0; i < this.sources.length; i++) {
193
- delete this.sources[i];
286
+ for (let i = 0; i < this.#sources.length; i++) {
287
+ // oxlint-disable-next-line typescript/no-array-delete -- intentional, drop the slot so V8 can collect the object
288
+ delete this.#sources[i];
194
289
  }
195
- this.sources = [];
290
+ this.#sources = [];
196
291
  this.areRequestsPersisted = true;
197
292
  const requestStream = createDeserialize(persistedRequests);
198
293
  for await (const request of requestStream) {
199
- this._addRequest(request);
294
+ this.#addRequest(request);
200
295
  }
201
296
  }
202
297
  /**
@@ -205,32 +300,36 @@ export class RequestList {
205
300
  * We need to avoid keeping both sources and requests in memory
206
301
  * to reduce memory footprint with very large sources.
207
302
  */
208
- async _addRequestsFromSources() {
303
+ async #addRequestsFromSources() {
209
304
  // We'll load all sources in sequence to ensure that they get loaded in the right order.
210
- const sourcesCount = this.sources.length;
305
+ const sourcesCount = this.#sources.length;
211
306
  for (let i = 0; i < sourcesCount; i++) {
212
- const source = this.sources[i];
307
+ const source = this.#sources[i];
213
308
  // Using delete here to drop the original object ASAP to free memory
214
309
  // .pop would reverse the array and .shift is SLOW.
215
- delete this.sources[i];
310
+ // oxlint-disable-next-line typescript/no-array-delete
311
+ delete this.#sources[i];
216
312
  if (typeof source === 'object' && source.requestsFromUrl) {
217
- const fetchedRequests = await this._fetchRequestsFromUrl(source);
218
- await this._addFetchedRequests(source, fetchedRequests);
313
+ const fetchedRequests = await this.#fetchRequestsFromUrl(source);
314
+ await this.#addFetchedRequests(source, fetchedRequests);
219
315
  }
220
316
  else {
221
- this._addRequest(source);
317
+ this.#addRequest(source);
222
318
  }
223
319
  }
224
320
  // Drop the original array full of empty indexes.
225
- this.sources = [];
226
- if (this.sourcesFunction) {
321
+ this.#sources = [];
322
+ if (this.#sourcesFunction) {
227
323
  try {
228
- const sourcesFromFunction = await this.sourcesFunction();
324
+ const sourcesFromFunction = await this.#sourcesFunction();
229
325
  const sourcesFromFunctionCount = sourcesFromFunction.length;
230
326
  for (let i = 0; i < sourcesFromFunctionCount; i++) {
231
- const source = sourcesFromFunction.shift();
232
- this._addRequest(source);
327
+ const source = sourcesFromFunction[i];
328
+ // oxlint-disable-next-line typescript/no-array-delete -- intentional, drop the slot so V8 can collect the object
329
+ delete sourcesFromFunction[i];
330
+ this.#addRequest(source);
233
331
  }
332
+ sourcesFromFunction.length = 0;
234
333
  }
235
334
  catch (e) {
236
335
  const err = e;
@@ -242,155 +341,74 @@ export class RequestList {
242
341
  * @inheritDoc
243
342
  */
244
343
  async persistState() {
245
- if (!this.persistStateKey) {
344
+ if (!this.#persistStateKey) {
246
345
  throw new Error('Cannot persist state. options.persistStateKey is not set.');
247
346
  }
248
- if (this.isStatePersisted)
249
- return;
250
347
  try {
251
- this.store ??= await KeyValueStore.open();
252
- await this.store.setValue(this.persistStateKey, this.getState());
253
- this.isStatePersisted = true;
348
+ await this.#state.persistState();
254
349
  }
255
350
  catch (e) {
256
351
  const err = e;
257
- this.log.exception(err, 'Attempted to persist state, but failed.');
352
+ this.#log.exception(err, 'Attempted to persist state, but failed.');
258
353
  }
259
354
  }
355
+ /**
356
+ * Removes the `PERSIST_STATE` event listener registered during initialization and persists
357
+ * the current state one last time. Call this when you are done with the `RequestList` to avoid
358
+ * leaking the listener (and the requests it retains) on the shared event manager.
359
+ */
360
+ async teardown() {
361
+ await this.#state.teardown();
362
+ }
260
363
  /**
261
364
  * Unlike persistState(), this is used only internally, since the sources
262
365
  * are automatically persisted at RequestList initialization (if the persistRequestsKey is set),
263
366
  * but there's no reason to persist it again afterwards, because RequestList is immutable.
264
367
  */
265
- async _persistRequests() {
368
+ async #persistRequests() {
266
369
  const serializedRequests = await serializeArray(this.requests);
267
- this.store ??= await KeyValueStore.open();
268
- await this.store.setValue(this.persistRequestsKey, serializedRequests, { contentType: CONTENT_TYPE_BINARY });
370
+ this.#store ??= await KeyValueStore.open();
371
+ await this.#store.setValue(this.#persistRequestsKey, serializedRequests, { contentType: CONTENT_TYPE_BINARY });
269
372
  this.areRequestsPersisted = true;
270
373
  }
271
- /**
272
- * Restores RequestList state from a state object.
273
- */
274
- _restoreState(state) {
275
- // If there's no state it means we've not persisted any (yet).
276
- if (!state)
277
- return;
278
- // Restore previous state.
279
- if (typeof state.nextIndex !== 'number' || state.nextIndex < 0) {
280
- throw new Error('The state object is invalid: nextIndex must be a non-negative number.');
281
- }
282
- if (state.nextIndex > this.requests.length) {
283
- throw new Error('The state object is not consistent with RequestList, too few requests loaded.');
284
- }
285
- if (state.nextIndex < this.requests.length &&
286
- this.requests[state.nextIndex].uniqueKey !== state.nextUniqueKey) {
287
- throw new Error('The state object is not consistent with RequestList the order of URLs seems to have changed.');
288
- }
289
- const deleteFromInProgress = [];
290
- state.inProgress.forEach((uniqueKey) => {
291
- const index = this.uniqueKeyToIndex[uniqueKey];
292
- if (typeof index !== 'number') {
293
- throw new Error('The state object is not consistent with RequestList. Unknown uniqueKey is present in the state.');
294
- }
295
- if (index >= state.nextIndex) {
296
- deleteFromInProgress.push(uniqueKey);
297
- }
298
- });
299
- this.nextIndex = state.nextIndex;
300
- this.inProgress = new Set(state.inProgress);
301
- // WORKAROUND:
302
- // It happened to some users that state object contained something like:
303
- // {
304
- // "nextIndex": 11308,
305
- // "nextUniqueKey": "https://www.anychart.com",
306
- // "inProgress": {
307
- // "https://www.ams360.com": true,
308
- // ...
309
- // "https://www.anychart.com": true,
310
- // }
311
- // Which then caused error "The request is not being processed (uniqueKey: https://www.anychart.com)"
312
- // As a workaround, we just remove all inProgress requests whose index >= nextIndex,
313
- // since they will be crawled again.
314
- if (deleteFromInProgress.length) {
315
- this.log.warning("RequestList's in-progress field is not consistent, skipping invalid in-progress entries", {
316
- deleteFromInProgress,
317
- });
318
- for (const uniqueKey of deleteFromInProgress) {
319
- this.inProgress.delete(uniqueKey);
320
- }
321
- }
322
- // All in-progress requests need to be re-crawled
323
- this.reclaimed = new Set(this.inProgress);
324
- }
325
- /**
326
- * Attempts to load state and requests using the `RequestList` configuration
327
- * and returns a tuple of [state, requests] where each may be null if not loaded.
328
- */
329
- async _loadStateAndPersistedRequests() {
330
- let state;
331
- let persistedRequests;
332
- if (this.initialState) {
333
- state = this.initialState;
334
- this.log.debug('Loaded state from options.state argument.');
335
- }
336
- else if (this.persistStateKey) {
337
- state = await this._getPersistedState(this.persistStateKey);
338
- if (state)
339
- this.log.debug('Loaded state from key value store using the persistStateKey.');
340
- }
341
- if (this.persistRequestsKey) {
342
- persistedRequests = await this._getPersistedState(this.persistRequestsKey);
343
- if (persistedRequests)
344
- this.log.debug('Loaded requests from key value store using the persistRequestsKey.');
345
- }
346
- return [state, persistedRequests];
347
- }
348
374
  /**
349
375
  * Returns an object representing the internal state of the `RequestList` instance.
350
376
  * Note that the object's fields can change in future releases.
351
377
  */
352
378
  getState() {
353
- this._ensureIsInitialized();
354
- return {
355
- nextIndex: this.nextIndex,
356
- nextUniqueKey: this.nextIndex < this.requests.length ? this.requests[this.nextIndex].uniqueKey : null,
357
- inProgress: [...this.inProgress],
358
- };
379
+ this.#ensureIsInitialized();
380
+ return this.#stateCodec.encode(this.#state.currentValue);
359
381
  }
360
382
  /**
361
383
  * @inheritDoc
362
384
  */
363
- async isEmpty() {
364
- this._ensureIsInitialized();
365
- return this.reclaimed.size === 0 && this.nextIndex >= this.requests.length;
366
- }
367
- /**
368
- * @inheritDoc
369
- */
370
- async isFinished() {
371
- this._ensureIsInitialized();
372
- return this.inProgress.size === 0 && this.nextIndex >= this.requests.length;
385
+ async checkReadiness() {
386
+ this.#ensureIsInitialized();
387
+ if (this.#requestsToRetry.length > 0 || this.#state.currentValue.nextIndex < this.requests.length) {
388
+ return { status: 'ready' };
389
+ }
390
+ // `#requestsToRetry` is a subset of `inProgress`, so nothing in progress means nothing left to re-serve.
391
+ return this.inProgress.size === 0 ? { status: 'finished' } : { status: 'waiting' };
373
392
  }
374
393
  /**
375
394
  * @inheritDoc
376
395
  */
377
396
  async fetchNextRequest() {
378
- this._ensureIsInitialized();
379
- // First return reclaimed requests if any.
380
- const uniqueKey = this.reclaimed.values().next().value;
397
+ this.#ensureIsInitialized();
398
+ // First re-serve any requests that were interrupted before the last state persist.
399
+ const uniqueKey = this.#requestsToRetry.shift();
381
400
  if (uniqueKey) {
382
- this.reclaimed.delete(uniqueKey);
383
- const index = this.uniqueKeyToIndex[uniqueKey];
384
- return this.ensureRequest(this.requests[index], index);
401
+ const index = this.#uniqueKeyToIndex[uniqueKey];
402
+ return this.#ensureRequest(this.requests[index], index);
385
403
  }
386
404
  // Otherwise return next request.
387
- if (this.nextIndex < this.requests.length) {
388
- const index = this.nextIndex;
405
+ const state = this.#state.currentValue;
406
+ if (state.nextIndex < this.requests.length) {
407
+ const index = state.nextIndex;
389
408
  const request = this.requests[index];
390
- this.inProgress.add(request.uniqueKey);
391
- this.nextIndex++;
392
- this.isStatePersisted = false;
393
- return this.ensureRequest(request, index);
409
+ state.inProgress.add(request.uniqueKey);
410
+ state.nextIndex++;
411
+ return this.#ensureRequest(request, index);
394
412
  }
395
413
  return null;
396
414
  }
@@ -405,7 +423,7 @@ export class RequestList {
405
423
  yield req;
406
424
  }
407
425
  }
408
- ensureRequest(requestLike, index) {
426
+ #ensureRequest(requestLike, index) {
409
427
  if (requestLike instanceof Request) {
410
428
  return requestLike;
411
429
  }
@@ -415,34 +433,23 @@ export class RequestList {
415
433
  /**
416
434
  * @inheritDoc
417
435
  */
418
- async markRequestHandled(request) {
436
+ async markRequestAsHandled(request) {
419
437
  const { uniqueKey } = request;
420
- this._ensureUniqueKeyValid(uniqueKey);
421
- this._ensureInProgressAndNotReclaimed(uniqueKey);
422
- this._ensureIsInitialized();
438
+ this.#ensureUniqueKeyValid(uniqueKey);
439
+ this.#ensureInProgress(uniqueKey);
440
+ this.#ensureIsInitialized();
423
441
  this.inProgress.delete(uniqueKey);
424
- this.isStatePersisted = false;
425
- }
426
- /**
427
- * @inheritDoc
428
- */
429
- async reclaimRequest(request) {
430
- const { uniqueKey } = request;
431
- this._ensureUniqueKeyValid(uniqueKey);
432
- this._ensureInProgressAndNotReclaimed(uniqueKey);
433
- this._ensureIsInitialized();
434
- this.reclaimed.add(uniqueKey);
435
442
  }
436
443
  /**
437
444
  * Adds all fetched requests from a URL from a remote resource.
438
445
  */
439
- async _addFetchedRequests(source, fetchedRequests) {
446
+ async #addFetchedRequests(source, fetchedRequests) {
440
447
  const { requestsFromUrl, regex } = source;
441
448
  const originalLength = this.requests.length;
442
- fetchedRequests.forEach((request) => this._addRequest(request));
449
+ fetchedRequests.forEach((request) => this.#addRequest(request));
443
450
  const fetchedCount = fetchedRequests.length;
444
451
  const importedCount = this.requests.length - originalLength;
445
- this.log.info('Fetched and loaded Requests from a remote resource.', {
452
+ this.#log.info('Fetched and loaded Requests from a remote resource.', {
446
453
  requestsFromUrl,
447
454
  regex,
448
455
  fetchedCount,
@@ -451,23 +458,23 @@ export class RequestList {
451
458
  sample: JSON.stringify(fetchedRequests.slice(0, 5)),
452
459
  });
453
460
  }
454
- async _getPersistedState(key) {
455
- this.store ??= await KeyValueStore.open();
456
- const state = await this.store.getValue(key);
461
+ async #getPersistedState(key) {
462
+ this.#store ??= await KeyValueStore.open();
463
+ const state = await this.#store.getValue(key);
457
464
  return state;
458
465
  }
459
466
  /**
460
467
  * Fetches URLs from requestsFromUrl and returns them in format of list of requests
461
468
  */
462
- async _fetchRequestsFromUrl(source) {
469
+ async #fetchRequestsFromUrl(source) {
463
470
  const { requestsFromUrl, regex, ...sharedOpts } = source;
464
471
  // Download remote resource and parse URLs.
465
472
  let urlsArr;
466
473
  try {
467
- urlsArr = await this._downloadListOfUrls({
474
+ urlsArr = await this.#downloadListOfUrls({
468
475
  url: requestsFromUrl,
469
476
  urlRegExp: regex,
470
- proxyUrl: await this.proxyConfiguration?.newUrl(),
477
+ proxyUrl: (await this.#proxyConfiguration?.newProxyInfo())?.url,
471
478
  });
472
479
  }
473
480
  catch (err) {
@@ -475,7 +482,7 @@ export class RequestList {
475
482
  }
476
483
  // Skip if resource contained no URLs.
477
484
  if (!urlsArr.length) {
478
- this.log.warning('The fetched list contains no valid URLs.', { requestsFromUrl, regex });
485
+ this.#log.warning('The fetched list contains no valid URLs.', { requestsFromUrl, regex });
479
486
  return [];
480
487
  }
481
488
  return urlsArr.map((url) => ({ url, ...sharedOpts }));
@@ -485,7 +492,7 @@ export class RequestList {
485
492
  * If the `source` parameter is a string or plain object and not an instance
486
493
  * of a `Request`, then the function creates a `Request` instance.
487
494
  */
488
- _addRequest(source) {
495
+ #addRequest(source) {
489
496
  let request;
490
497
  const type = typeof source;
491
498
  if (type === 'string') {
@@ -503,61 +510,76 @@ export class RequestList {
503
510
  const hasUniqueKey = Reflect.has(Object(source), 'uniqueKey');
504
511
  request.uniqueKey ??= Request.computeUniqueKey(request);
505
512
  // Add index to uniqueKey if duplicates are to be kept
506
- if (this.keepDuplicateUrls && !hasUniqueKey) {
513
+ if (this.#keepDuplicateUrls && !hasUniqueKey) {
507
514
  request.uniqueKey += `-${this.requests.length}`;
508
515
  }
509
516
  const { uniqueKey } = request;
510
- this._ensureUniqueKeyValid(uniqueKey);
517
+ this.#ensureUniqueKeyValid(uniqueKey);
511
518
  // Skip requests with duplicate uniqueKey
512
- if (!Object.hasOwn(this.uniqueKeyToIndex, uniqueKey)) {
513
- this.uniqueKeyToIndex[uniqueKey] = this.requests.length;
519
+ if (!Object.hasOwn(this.#uniqueKeyToIndex, uniqueKey)) {
520
+ this.#uniqueKeyToIndex[uniqueKey] = this.requests.length;
514
521
  this.requests.push(request);
515
522
  }
516
- else if (this.keepDuplicateUrls) {
517
- this.log.warning(`Duplicate uniqueKey: ${uniqueKey} found while the keepDuplicateUrls option was set. Check your sources' unique keys.`);
523
+ else if (this.#keepDuplicateUrls) {
524
+ this.#log.warning(`Duplicate uniqueKey: ${uniqueKey} found while the keepDuplicateUrls option was set. Check your sources' unique keys.`);
518
525
  }
519
526
  }
520
527
  /**
521
528
  * Helper function that validates unique key.
522
529
  * Throws an error if uniqueKey is not a non-empty string.
523
530
  */
524
- _ensureUniqueKeyValid(uniqueKey) {
531
+ #ensureUniqueKeyValid(uniqueKey) {
525
532
  if (typeof uniqueKey !== 'string' || !uniqueKey) {
526
533
  throw new Error("Request object's uniqueKey must be a non-empty string");
527
534
  }
528
535
  }
529
536
  /**
530
- * Checks that request is not reclaimed and throws an error if so.
537
+ * Checks that a request is currently being processed and throws an error if not.
531
538
  */
532
- _ensureInProgressAndNotReclaimed(uniqueKey) {
539
+ #ensureInProgress(uniqueKey) {
533
540
  if (!this.inProgress.has(uniqueKey)) {
534
541
  throw new Error(`The request is not being processed (uniqueKey: ${uniqueKey})`);
535
542
  }
536
- if (this.reclaimed.has(uniqueKey)) {
537
- throw new Error(`The request was already reclaimed (uniqueKey: ${uniqueKey})`);
538
- }
539
543
  }
540
544
  /**
541
545
  * Throws an error if request list wasn't initialized.
542
546
  */
543
- _ensureIsInitialized() {
544
- if (!this.isInitialized) {
547
+ #ensureIsInitialized() {
548
+ if (!this.#isInitialized) {
545
549
  throw new Error('RequestList is not initialized; you must call "await requestList.initialize()" before using it!');
546
550
  }
547
551
  }
548
552
  /**
549
553
  * Returns the total number of unique requests present in the `RequestList`.
550
554
  */
551
- length() {
552
- this._ensureIsInitialized();
555
+ async getTotalCount() {
556
+ this.#ensureIsInitialized();
553
557
  return this.requests.length;
554
558
  }
559
+ /**
560
+ * Returns the number of pending requests in the `RequestList`.
561
+ */
562
+ async getPendingCount() {
563
+ this.#ensureIsInitialized();
564
+ return this.requests.length - (this.#state.currentValue.nextIndex - this.inProgress.size);
565
+ }
566
+ /**
567
+ * Combines this list with a request manager (a {@link RequestQueue} by default) into a
568
+ * {@link RequestManagerTandem}, allowing requests to be added and reclaimed while still
569
+ * being read from this list first.
570
+ */
571
+ async toTandem(requestManager) {
572
+ // Import here to avoid circular imports.
573
+ const { RequestManagerTandem } = await import('./request_manager_tandem.js');
574
+ const { RequestQueue } = await import('./request_queue.js');
575
+ return new RequestManagerTandem(this, requestManager ?? (await RequestQueue.open()));
576
+ }
555
577
  /**
556
578
  * @inheritDoc
557
579
  */
558
- handledCount() {
559
- this._ensureIsInitialized();
560
- return this.nextIndex - this.inProgress.size;
580
+ async getHandledCount() {
581
+ this.#ensureIsInitialized();
582
+ return this.#state.currentValue.nextIndex - this.inProgress.size;
561
583
  }
562
584
  /**
563
585
  * Opens a request list and returns a promise resolving to an instance
@@ -616,30 +638,29 @@ export class RequestList {
616
638
  if (listNameOrOptions != null && typeof listNameOrOptions === 'object') {
617
639
  options = { ...listNameOrOptions, ...options };
618
640
  const rl = new RequestList(options);
619
- await rl.initialize();
641
+ await rl.#initialize();
620
642
  return rl;
621
643
  }
622
644
  const listName = listNameOrOptions;
623
- ow(listName, ow.optional.any(ow.string, ow.null));
624
- ow(sources, ow.array);
625
- ow(options, ow.object.is((v) => !Array.isArray(v)));
645
+ parseArgument(listName, listNameSchema);
646
+ parseArgument(sources, schemas.anyArray);
647
+ parseArgument(options, openOptionsSchema);
626
648
  const rl = new RequestList({
627
649
  ...options,
628
650
  persistStateKey: listName ? `${listName}-${STATE_PERSISTENCE_KEY}` : options.persistStateKey,
629
651
  persistRequestsKey: listName ? `${listName}-${REQUESTS_PERSISTENCE_KEY}` : options.persistRequestsKey,
630
652
  sources: sources ?? options.sources,
631
653
  });
632
- await rl.initialize();
654
+ await rl.#initialize();
633
655
  return rl;
634
656
  }
635
657
  /**
636
658
  * @internal wraps public utility for mocking purposes
637
659
  */
638
- async _downloadListOfUrls(options) {
660
+ async #downloadListOfUrls(options) {
639
661
  return downloadListOfUrls({
640
662
  ...options,
641
- httpClient: this.httpClient,
663
+ httpClient: this.#httpClient,
642
664
  });
643
665
  }
644
666
  }
645
- //# sourceMappingURL=request_list.js.map