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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/autoscaling/autoscaled_pool.d.ts +3 -21
  2. package/autoscaling/autoscaled_pool.js +105 -97
  3. package/autoscaling/concurrency_system.d.ts +7 -22
  4. package/autoscaling/concurrency_system.js +112 -100
  5. package/autoscaling/cpu_load_signal.d.ts +1 -2
  6. package/autoscaling/cpu_load_signal.js +11 -10
  7. package/autoscaling/event_loop_load_signal.d.ts +1 -4
  8. package/autoscaling/event_loop_load_signal.js +18 -18
  9. package/autoscaling/index.d.ts +1 -1
  10. package/autoscaling/index.js +1 -1
  11. package/autoscaling/load_signal.d.ts +8 -7
  12. package/autoscaling/load_signal.js +14 -12
  13. package/autoscaling/memory_load_signal.d.ts +3 -12
  14. package/autoscaling/memory_load_signal.js +41 -41
  15. package/autoscaling/snapshotter.d.ts +7 -10
  16. package/autoscaling/snapshotter.js +18 -18
  17. package/autoscaling/{client_load_signal.d.ts → storage_backend_load_signal.d.ts} +14 -17
  18. package/autoscaling/{client_load_signal.js → storage_backend_load_signal.js} +27 -27
  19. package/autoscaling/system_status.d.ts +9 -11
  20. package/autoscaling/system_status.js +13 -13
  21. package/configuration.d.ts +16 -16
  22. package/configuration.js +6 -6
  23. package/crawlers/context_pipeline.js +6 -6
  24. package/crawlers/crawler_commons.d.ts +14 -112
  25. package/crawlers/crawler_commons.js +1 -107
  26. package/crawlers/error_snapshotter.d.ts +2 -4
  27. package/crawlers/error_snapshotter.js +5 -5
  28. package/crawlers/index.d.ts +1 -1
  29. package/crawlers/index.js +0 -1
  30. package/crawlers/statistics.d.ts +164 -45
  31. package/crawlers/statistics.js +339 -153
  32. package/debug.js +4 -4
  33. package/enqueue_links/enqueue_links.d.ts +51 -159
  34. package/enqueue_links/enqueue_links.js +38 -231
  35. package/enqueue_links/shared.d.ts +34 -24
  36. package/enqueue_links/shared.js +54 -36
  37. package/errors.d.ts +43 -12
  38. package/errors.js +48 -9
  39. package/events/event_manager.d.ts +2 -2
  40. package/events/event_manager.js +7 -6
  41. package/events/local_event_manager.d.ts +1 -1
  42. package/events/local_event_manager.js +5 -5
  43. package/http.d.ts +9 -0
  44. package/http.js +28 -0
  45. package/index.d.ts +1 -1
  46. package/index.js +1 -1
  47. package/log.js +5 -1
  48. package/memory-storage/memory-storage.d.ts +3 -11
  49. package/memory-storage/memory-storage.js +42 -48
  50. package/memory-storage/resource-clients/dataset.d.ts +1 -1
  51. package/memory-storage/resource-clients/dataset.js +8 -13
  52. package/memory-storage/resource-clients/key-value-store.d.ts +1 -1
  53. package/memory-storage/resource-clients/key-value-store.js +36 -38
  54. package/memory-storage/resource-clients/request-queue.d.ts +4 -23
  55. package/memory-storage/resource-clients/request-queue.js +68 -80
  56. package/owned_or_injected.d.ts +1 -3
  57. package/owned_or_injected.js +17 -17
  58. package/package.json +8 -9
  59. package/proxy_configuration.d.ts +1 -3
  60. package/proxy_configuration.js +18 -14
  61. package/recoverable_state.d.ts +80 -45
  62. package/recoverable_state.js +163 -75
  63. package/request.d.ts +3 -4
  64. package/request.js +57 -44
  65. package/router.d.ts +6 -9
  66. package/router.js +23 -23
  67. package/serialization.js +14 -13
  68. package/service_locator.d.ts +19 -10
  69. package/service_locator.js +57 -48
  70. package/session_pool/session.d.ts +3 -14
  71. package/session_pool/session.js +76 -74
  72. package/session_pool/session_pool.d.ts +6 -12
  73. package/session_pool/session_pool.js +85 -77
  74. package/storages/batched_adds.d.ts +37 -0
  75. package/storages/batched_adds.js +73 -0
  76. package/storages/dataset.d.ts +12 -1
  77. package/storages/dataset.js +133 -32
  78. package/storages/index.d.ts +2 -1
  79. package/storages/index.js +2 -1
  80. package/storages/key_value_store.d.ts +19 -4
  81. package/storages/key_value_store.js +204 -75
  82. package/storages/request_dedup_cache.d.ts +1 -2
  83. package/storages/request_dedup_cache.js +9 -9
  84. package/storages/request_list.d.ts +4 -23
  85. package/storages/request_list.js +103 -96
  86. package/storages/request_loader.d.ts +5 -0
  87. package/storages/request_manager_tandem.d.ts +1 -10
  88. package/storages/request_manager_tandem.js +27 -27
  89. package/storages/request_queue.d.ts +21 -18
  90. package/storages/request_queue.js +318 -177
  91. package/storages/sitemap_request_loader.d.ts +22 -70
  92. package/storages/sitemap_request_loader.js +122 -116
  93. package/storages/storage_instance_manager.d.ts +1 -2
  94. package/storages/storage_instance_manager.js +17 -17
  95. package/storages/storage_stats.d.ts +1 -1
  96. package/storages/storage_stats.js +4 -4
  97. package/storages/throttling_request_manager.d.ts +239 -0
  98. package/storages/throttling_request_manager.js +646 -0
  99. package/storages/transaction.d.ts +252 -0
  100. package/storages/transaction.js +251 -0
  101. package/storages/utils.d.ts +6 -3
  102. package/storages/utils.js +11 -2
  103. package/system-info/runtime.js +7 -7
  104. package/url.d.ts +9 -0
  105. package/url.js +11 -0
  106. package/validators.d.ts +22 -25
  107. package/validators.js +13 -25
  108. package/storages/access_checking.d.ts +0 -12
  109. package/storages/access_checking.js +0 -17
@@ -1,8 +1,10 @@
1
1
  import { downloadListOfUrls } from '@crawlee/utils';
2
- import ow, { ArgumentError } from 'ow';
2
+ import { z } from 'zod';
3
+ import { EventType } from '../events/event_manager.js';
3
4
  import { Request } from '../request.js';
4
5
  import { createDeserialize, serializeArray } from '../serialization.js';
5
6
  import { serviceLocator } from '../service_locator.js';
7
+ import { parseArgument, schemas, validators } from '../validators.js';
6
8
  import { KeyValueStore } from './key_value_store.js';
7
9
  import { purgeDefaultStorages } from './utils.js';
8
10
  /** @internal */
@@ -10,6 +12,24 @@ export const STATE_PERSISTENCE_KEY = 'REQUEST_LIST_STATE';
10
12
  /** @internal */
11
13
  export const REQUESTS_PERSISTENCE_KEY = 'REQUEST_LIST_REQUESTS';
12
14
  const CONTENT_TYPE_BINARY = 'application/octet-stream';
15
+ const requestListOptionsSchema = z.strictObject({
16
+ sources: schemas.anyArray.optional(), // check only for array and not subtypes to avoid iteration over the whole thing
17
+ sourcesFunction: schemas.anyFunction.optional(),
18
+ persistStateKey: z.string().optional(),
19
+ persistRequestsKey: z.string().optional(),
20
+ state: z
21
+ .strictObject({
22
+ nextIndex: schemas.anyNumber,
23
+ nextUniqueKey: z.string(),
24
+ inProgress: schemas.anyObject, // persisted as an array of unique keys
25
+ })
26
+ .optional(),
27
+ keepDuplicateUrls: z.boolean().default(false),
28
+ proxyConfiguration: validators.proxyConfiguration.optional(),
29
+ httpClient: schemas.httpClient.optional(),
30
+ });
31
+ const listNameSchema = z.string().nullish();
32
+ const openOptionsSchema = z.looseObject({});
13
33
  /**
14
34
  * Represents a static list of URLs to crawl.
15
35
  * The URLs can be provided either in code or parsed from a text file hosted on the web.
@@ -72,7 +92,7 @@ const CONTENT_TYPE_BINARY = 'application/octet-stream';
72
92
  * @category Sources
73
93
  */
74
94
  export class RequestList {
75
- log = serviceLocator.getLogger().child({ prefix: 'RequestList' });
95
+ #log = serviceLocator.getLogger().child({ prefix: 'RequestList' });
76
96
  /**
77
97
  * Array of all requests from all sources, in the order as they appeared in sources.
78
98
  * All requests in the array have distinct uniqueKey!
@@ -80,9 +100,9 @@ export class RequestList {
80
100
  */
81
101
  requests = [];
82
102
  /** Index to the next item in requests array to fetch. All previous requests are either handled or in progress. */
83
- nextIndex = 0;
103
+ #nextIndex = 0;
84
104
  /** Dictionary, key is Request.uniqueKey, value is corresponding index in the requests array. */
85
- uniqueKeyToIndex = {};
105
+ #uniqueKeyToIndex = {};
86
106
  /**
87
107
  * Set of `uniqueKey`s of requests that were returned by fetchNextRequest().
88
108
  * @internal
@@ -93,7 +113,7 @@ export class RequestList {
93
113
  * re-crawled after a restart. They are served before advancing through the rest of the sources.
94
114
  * @internal
95
115
  */
96
- requestsToRetry = [];
116
+ #requestsToRetry = [];
97
117
  /**
98
118
  * Starts as true because until we handle the first request, the list is effectively persisted by doing nothing.
99
119
  * @internal
@@ -104,52 +124,39 @@ export class RequestList {
104
124
  * @internal
105
125
  */
106
126
  areRequestsPersisted = false;
107
- isLoading = false;
108
- isInitialized = false;
109
- persistStateKey;
110
- persistRequestsKey;
111
- initialState;
112
- store;
113
- keepDuplicateUrls;
127
+ #isLoading = false;
128
+ #isInitialized = false;
129
+ #persistStateKey;
130
+ #persistRequestsKey;
131
+ #initialState;
132
+ #store;
133
+ #keepDuplicateUrls;
134
+ // kept as TS-private: request_list tests read this field directly
114
135
  sources;
115
- sourcesFunction;
116
- proxyConfiguration;
117
- httpClient;
136
+ #sourcesFunction;
137
+ #proxyConfiguration;
138
+ #httpClient;
118
139
  /**
119
140
  * To create new instance of `RequestList` we need to use `RequestList.open()` factory method.
120
141
  * @param options All `RequestList` configuration options
121
142
  * @internal
122
143
  */
123
144
  constructor(options = {}) {
124
- const { sources, sourcesFunction, persistStateKey, persistRequestsKey, state, proxyConfiguration, keepDuplicateUrls = false, httpClient, } = options;
145
+ const { sources, sourcesFunction, persistStateKey, persistRequestsKey, state, proxyConfiguration, keepDuplicateUrls, httpClient, } = parseArgument(options, requestListOptionsSchema);
125
146
  if (!(sources || sourcesFunction)) {
126
- throw new ArgumentError('At least one of "sources" or "sourcesFunction" must be provided.', this.constructor);
127
- }
128
- ow(options, ow.object.exactShape({
129
- sources: ow.optional.array, // check only for array and not subtypes to avoid iteration over the whole thing
130
- sourcesFunction: ow.optional.function,
131
- persistStateKey: ow.optional.string,
132
- persistRequestsKey: ow.optional.string,
133
- state: ow.optional.object.exactShape({
134
- nextIndex: ow.number,
135
- nextUniqueKey: ow.string,
136
- inProgress: ow.object,
137
- }),
138
- keepDuplicateUrls: ow.optional.boolean,
139
- proxyConfiguration: ow.optional.object,
140
- httpClient: ow.optional.object,
141
- }));
142
- this.persistStateKey = persistStateKey ? `CRAWLEE_${persistStateKey}` : persistStateKey;
143
- this.persistRequestsKey = persistRequestsKey ? `CRAWLEE_${persistRequestsKey}` : persistRequestsKey;
144
- this.initialState = state;
145
- this.httpClient = httpClient;
147
+ throw new Error('At least one of "sources" or "sourcesFunction" must be provided.');
148
+ }
149
+ this.#persistStateKey = persistStateKey ? `CRAWLEE_${persistStateKey}` : persistStateKey;
150
+ this.#persistRequestsKey = persistRequestsKey ? `CRAWLEE_${persistRequestsKey}` : persistRequestsKey;
151
+ this.#initialState = state;
152
+ this.#httpClient = httpClient;
146
153
  // If this option is set then all requests will get a pre-generated unique ID and duplicate URLs will be kept in the list.
147
- this.keepDuplicateUrls = keepDuplicateUrls;
154
+ this.#keepDuplicateUrls = keepDuplicateUrls;
148
155
  // Will be empty after initialization to save memory.
149
156
  this.sources = sources ? [...sources] : [];
150
- this.sourcesFunction = sourcesFunction;
157
+ this.#sourcesFunction = sourcesFunction;
151
158
  // The proxy configuration used for `requestsFromUrl` requests.
152
- this.proxyConfiguration = proxyConfiguration;
159
+ this.#proxyConfiguration = proxyConfiguration;
153
160
  this.persistState = this.persistState.bind(this);
154
161
  }
155
162
  /**
@@ -157,10 +164,10 @@ export class RequestList {
157
164
  * This function must be called before you can start using the instance in a meaningful way.
158
165
  */
159
166
  async initialize() {
160
- if (this.isLoading) {
167
+ if (this.#isLoading) {
161
168
  throw new Error('RequestList sources are already loading or were loaded.');
162
169
  }
163
- this.isLoading = true;
170
+ this.#isLoading = true;
164
171
  await purgeDefaultStorages({ onlyPurgeOnce: true });
165
172
  const [state, persistedRequests] = await this.loadStateAndPersistedRequests();
166
173
  // Add persisted requests / new sources in a memory efficient way because with very
@@ -172,11 +179,11 @@ export class RequestList {
172
179
  await this.addRequestsFromSources();
173
180
  }
174
181
  this.restoreState(state);
175
- this.isInitialized = true;
176
- if (this.persistRequestsKey && !this.areRequestsPersisted)
182
+ this.#isInitialized = true;
183
+ if (this.#persistRequestsKey && !this.areRequestsPersisted)
177
184
  await this.persistRequests();
178
- if (this.persistStateKey) {
179
- serviceLocator.getEventManager().on("persistState" /* EventType.PERSIST_STATE */, this.persistState);
185
+ if (this.#persistStateKey) {
186
+ serviceLocator.getEventManager().on(EventType.PERSIST_STATE, this.persistState);
180
187
  }
181
188
  return this;
182
189
  }
@@ -224,9 +231,9 @@ export class RequestList {
224
231
  }
225
232
  // Drop the original array full of empty indexes.
226
233
  this.sources = [];
227
- if (this.sourcesFunction) {
234
+ if (this.#sourcesFunction) {
228
235
  try {
229
- const sourcesFromFunction = await this.sourcesFunction();
236
+ const sourcesFromFunction = await this.#sourcesFunction();
230
237
  const sourcesFromFunctionCount = sourcesFromFunction.length;
231
238
  for (let i = 0; i < sourcesFromFunctionCount; i++) {
232
239
  const source = sourcesFromFunction[i];
@@ -246,19 +253,19 @@ export class RequestList {
246
253
  * @inheritDoc
247
254
  */
248
255
  async persistState() {
249
- if (!this.persistStateKey) {
256
+ if (!this.#persistStateKey) {
250
257
  throw new Error('Cannot persist state. options.persistStateKey is not set.');
251
258
  }
252
259
  if (this.isStatePersisted)
253
260
  return;
254
261
  try {
255
- this.store ??= await KeyValueStore.open();
256
- await this.store.setValue(this.persistStateKey, this.getState());
262
+ this.#store ??= await KeyValueStore.open();
263
+ await this.#store.setValue(this.#persistStateKey, this.getState());
257
264
  this.isStatePersisted = true;
258
265
  }
259
266
  catch (e) {
260
267
  const err = e;
261
- this.log.exception(err, 'Attempted to persist state, but failed.');
268
+ this.#log.exception(err, 'Attempted to persist state, but failed.');
262
269
  }
263
270
  }
264
271
  /**
@@ -267,8 +274,8 @@ export class RequestList {
267
274
  * leaking the listener (and the requests it retains) on the shared event manager.
268
275
  */
269
276
  async teardown() {
270
- serviceLocator.getEventManager().off("persistState" /* EventType.PERSIST_STATE */, this.persistState);
271
- if (this.persistStateKey) {
277
+ serviceLocator.getEventManager().off(EventType.PERSIST_STATE, this.persistState);
278
+ if (this.#persistStateKey) {
272
279
  await this.persistState();
273
280
  }
274
281
  }
@@ -279,8 +286,8 @@ export class RequestList {
279
286
  */
280
287
  async persistRequests() {
281
288
  const serializedRequests = await serializeArray(this.requests);
282
- this.store ??= await KeyValueStore.open();
283
- await this.store.setValue(this.persistRequestsKey, serializedRequests, { contentType: CONTENT_TYPE_BINARY });
289
+ this.#store ??= await KeyValueStore.open();
290
+ await this.#store.setValue(this.#persistRequestsKey, serializedRequests, { contentType: CONTENT_TYPE_BINARY });
284
291
  this.areRequestsPersisted = true;
285
292
  }
286
293
  /**
@@ -303,7 +310,7 @@ export class RequestList {
303
310
  }
304
311
  const deleteFromInProgress = [];
305
312
  state.inProgress.forEach((uniqueKey) => {
306
- const index = this.uniqueKeyToIndex[uniqueKey];
313
+ const index = this.#uniqueKeyToIndex[uniqueKey];
307
314
  if (typeof index !== 'number') {
308
315
  throw new Error('The state object is not consistent with RequestList. Unknown uniqueKey is present in the state.');
309
316
  }
@@ -311,7 +318,7 @@ export class RequestList {
311
318
  deleteFromInProgress.push(uniqueKey);
312
319
  }
313
320
  });
314
- this.nextIndex = state.nextIndex;
321
+ this.#nextIndex = state.nextIndex;
315
322
  this.inProgress = new Set(state.inProgress);
316
323
  // WORKAROUND:
317
324
  // It happened to some users that state object contained something like:
@@ -327,7 +334,7 @@ export class RequestList {
327
334
  // As a workaround, we just remove all inProgress requests whose index >= nextIndex,
328
335
  // since they will be crawled again.
329
336
  if (deleteFromInProgress.length) {
330
- this.log.warning("RequestList's in-progress field is not consistent, skipping invalid in-progress entries", {
337
+ this.#log.warning("RequestList's in-progress field is not consistent, skipping invalid in-progress entries", {
331
338
  deleteFromInProgress,
332
339
  });
333
340
  for (const uniqueKey of deleteFromInProgress) {
@@ -335,7 +342,7 @@ export class RequestList {
335
342
  }
336
343
  }
337
344
  // All in-progress requests were interrupted and need to be re-crawled.
338
- this.requestsToRetry = [...this.inProgress];
345
+ this.#requestsToRetry = [...this.inProgress];
339
346
  }
340
347
  /**
341
348
  * Attempts to load state and requests using the `RequestList` configuration
@@ -344,19 +351,19 @@ export class RequestList {
344
351
  async loadStateAndPersistedRequests() {
345
352
  let state;
346
353
  let persistedRequests;
347
- if (this.initialState) {
348
- state = this.initialState;
349
- this.log.debug('Loaded state from options.state argument.');
354
+ if (this.#initialState) {
355
+ state = this.#initialState;
356
+ this.#log.debug('Loaded state from options.state argument.');
350
357
  }
351
- else if (this.persistStateKey) {
352
- state = await this.getPersistedState(this.persistStateKey);
358
+ else if (this.#persistStateKey) {
359
+ state = await this.getPersistedState(this.#persistStateKey);
353
360
  if (state)
354
- this.log.debug('Loaded state from key value store using the persistStateKey.');
361
+ this.#log.debug('Loaded state from key value store using the persistStateKey.');
355
362
  }
356
- if (this.persistRequestsKey) {
357
- persistedRequests = await this.getPersistedState(this.persistRequestsKey);
363
+ if (this.#persistRequestsKey) {
364
+ persistedRequests = await this.getPersistedState(this.#persistRequestsKey);
358
365
  if (persistedRequests)
359
- this.log.debug('Loaded requests from key value store using the persistRequestsKey.');
366
+ this.#log.debug('Loaded requests from key value store using the persistRequestsKey.');
360
367
  }
361
368
  return [state, persistedRequests];
362
369
  }
@@ -367,8 +374,8 @@ export class RequestList {
367
374
  getState() {
368
375
  this.ensureIsInitialized();
369
376
  return {
370
- nextIndex: this.nextIndex,
371
- nextUniqueKey: this.nextIndex < this.requests.length ? this.requests[this.nextIndex].uniqueKey : null,
377
+ nextIndex: this.#nextIndex,
378
+ nextUniqueKey: this.#nextIndex < this.requests.length ? this.requests[this.#nextIndex].uniqueKey : null,
372
379
  inProgress: [...this.inProgress],
373
380
  };
374
381
  }
@@ -377,14 +384,14 @@ export class RequestList {
377
384
  */
378
385
  async isEmpty() {
379
386
  this.ensureIsInitialized();
380
- return this.requestsToRetry.length === 0 && this.nextIndex >= this.requests.length;
387
+ return this.#requestsToRetry.length === 0 && this.#nextIndex >= this.requests.length;
381
388
  }
382
389
  /**
383
390
  * @inheritDoc
384
391
  */
385
392
  async isFinished() {
386
393
  this.ensureIsInitialized();
387
- return this.inProgress.size === 0 && this.nextIndex >= this.requests.length;
394
+ return this.inProgress.size === 0 && this.#nextIndex >= this.requests.length;
388
395
  }
389
396
  /**
390
397
  * @inheritDoc
@@ -392,17 +399,17 @@ export class RequestList {
392
399
  async fetchNextRequest() {
393
400
  this.ensureIsInitialized();
394
401
  // First re-serve any requests that were interrupted before the last state persist.
395
- const uniqueKey = this.requestsToRetry.shift();
402
+ const uniqueKey = this.#requestsToRetry.shift();
396
403
  if (uniqueKey) {
397
- const index = this.uniqueKeyToIndex[uniqueKey];
404
+ const index = this.#uniqueKeyToIndex[uniqueKey];
398
405
  return this.ensureRequest(this.requests[index], index);
399
406
  }
400
407
  // Otherwise return next request.
401
- if (this.nextIndex < this.requests.length) {
402
- const index = this.nextIndex;
408
+ if (this.#nextIndex < this.requests.length) {
409
+ const index = this.#nextIndex;
403
410
  const request = this.requests[index];
404
411
  this.inProgress.add(request.uniqueKey);
405
- this.nextIndex++;
412
+ this.#nextIndex++;
406
413
  this.isStatePersisted = false;
407
414
  return this.ensureRequest(request, index);
408
415
  }
@@ -446,7 +453,7 @@ export class RequestList {
446
453
  fetchedRequests.forEach((request) => this.addRequest(request));
447
454
  const fetchedCount = fetchedRequests.length;
448
455
  const importedCount = this.requests.length - originalLength;
449
- this.log.info('Fetched and loaded Requests from a remote resource.', {
456
+ this.#log.info('Fetched and loaded Requests from a remote resource.', {
450
457
  requestsFromUrl,
451
458
  regex,
452
459
  fetchedCount,
@@ -456,8 +463,8 @@ export class RequestList {
456
463
  });
457
464
  }
458
465
  async getPersistedState(key) {
459
- this.store ??= await KeyValueStore.open();
460
- const state = await this.store.getValue(key);
466
+ this.#store ??= await KeyValueStore.open();
467
+ const state = await this.#store.getValue(key);
461
468
  return state;
462
469
  }
463
470
  /**
@@ -468,10 +475,10 @@ export class RequestList {
468
475
  // Download remote resource and parse URLs.
469
476
  let urlsArr;
470
477
  try {
471
- urlsArr = await this._downloadListOfUrls({
478
+ urlsArr = await this.downloadListOfUrls({
472
479
  url: requestsFromUrl,
473
480
  urlRegExp: regex,
474
- proxyUrl: (await this.proxyConfiguration?.newProxyInfo())?.url,
481
+ proxyUrl: (await this.#proxyConfiguration?.newProxyInfo())?.url,
475
482
  });
476
483
  }
477
484
  catch (err) {
@@ -479,7 +486,7 @@ export class RequestList {
479
486
  }
480
487
  // Skip if resource contained no URLs.
481
488
  if (!urlsArr.length) {
482
- this.log.warning('The fetched list contains no valid URLs.', { requestsFromUrl, regex });
489
+ this.#log.warning('The fetched list contains no valid URLs.', { requestsFromUrl, regex });
483
490
  return [];
484
491
  }
485
492
  return urlsArr.map((url) => ({ url, ...sharedOpts }));
@@ -507,18 +514,18 @@ export class RequestList {
507
514
  const hasUniqueKey = Reflect.has(Object(source), 'uniqueKey');
508
515
  request.uniqueKey ??= Request.computeUniqueKey(request);
509
516
  // Add index to uniqueKey if duplicates are to be kept
510
- if (this.keepDuplicateUrls && !hasUniqueKey) {
517
+ if (this.#keepDuplicateUrls && !hasUniqueKey) {
511
518
  request.uniqueKey += `-${this.requests.length}`;
512
519
  }
513
520
  const { uniqueKey } = request;
514
521
  this.ensureUniqueKeyValid(uniqueKey);
515
522
  // Skip requests with duplicate uniqueKey
516
- if (!Object.hasOwn(this.uniqueKeyToIndex, uniqueKey)) {
517
- this.uniqueKeyToIndex[uniqueKey] = this.requests.length;
523
+ if (!Object.hasOwn(this.#uniqueKeyToIndex, uniqueKey)) {
524
+ this.#uniqueKeyToIndex[uniqueKey] = this.requests.length;
518
525
  this.requests.push(request);
519
526
  }
520
- else if (this.keepDuplicateUrls) {
521
- this.log.warning(`Duplicate uniqueKey: ${uniqueKey} found while the keepDuplicateUrls option was set. Check your sources' unique keys.`);
527
+ else if (this.#keepDuplicateUrls) {
528
+ this.#log.warning(`Duplicate uniqueKey: ${uniqueKey} found while the keepDuplicateUrls option was set. Check your sources' unique keys.`);
522
529
  }
523
530
  }
524
531
  /**
@@ -542,7 +549,7 @@ export class RequestList {
542
549
  * Throws an error if request list wasn't initialized.
543
550
  */
544
551
  ensureIsInitialized() {
545
- if (!this.isInitialized) {
552
+ if (!this.#isInitialized) {
546
553
  throw new Error('RequestList is not initialized; you must call "await requestList.initialize()" before using it!');
547
554
  }
548
555
  }
@@ -558,7 +565,7 @@ export class RequestList {
558
565
  */
559
566
  async getPendingCount() {
560
567
  this.ensureIsInitialized();
561
- return this.requests.length - (this.nextIndex - this.inProgress.size);
568
+ return this.requests.length - (this.#nextIndex - this.inProgress.size);
562
569
  }
563
570
  /**
564
571
  * Combines this list with a request manager (a {@link RequestQueue} by default) into a
@@ -576,7 +583,7 @@ export class RequestList {
576
583
  */
577
584
  async getHandledCount() {
578
585
  this.ensureIsInitialized();
579
- return this.nextIndex - this.inProgress.size;
586
+ return this.#nextIndex - this.inProgress.size;
580
587
  }
581
588
  /**
582
589
  * Opens a request list and returns a promise resolving to an instance
@@ -639,9 +646,9 @@ export class RequestList {
639
646
  return rl;
640
647
  }
641
648
  const listName = listNameOrOptions;
642
- ow(listName, ow.optional.any(ow.string, ow.null));
643
- ow(sources, ow.array);
644
- ow(options, ow.object.is((v) => !Array.isArray(v)));
649
+ parseArgument(listName, listNameSchema);
650
+ parseArgument(sources, schemas.anyArray);
651
+ parseArgument(options, openOptionsSchema);
645
652
  const rl = new RequestList({
646
653
  ...options,
647
654
  persistStateKey: listName ? `${listName}-${STATE_PERSISTENCE_KEY}` : options.persistStateKey,
@@ -654,10 +661,10 @@ export class RequestList {
654
661
  /**
655
662
  * @internal wraps public utility for mocking purposes
656
663
  */
657
- async _downloadListOfUrls(options) {
664
+ async downloadListOfUrls(options) {
658
665
  return downloadListOfUrls({
659
666
  ...options,
660
- httpClient: this.httpClient,
667
+ httpClient: this.#httpClient,
661
668
  });
662
669
  }
663
670
  }
@@ -54,6 +54,11 @@ export interface IRequestLoader {
54
54
  * Resolves to `true` if the next call to {@link IRequestLoader.fetchNextRequest} function
55
55
  * would return `null`, otherwise it resolves to `false`.
56
56
  * Note that even if the loader is empty, there might be some pending requests currently being processed.
57
+ *
58
+ * This is a statement about what the *next fetch* would return, not about how much work is left, so it
59
+ * may report `true` while {@link IRequestLoader.getPendingCount} is non-zero - a loader that withholds
60
+ * requests for a while (as {@link ThrottlingRequestManager} does for a rate-limited domain) is empty
61
+ * for as long as it will not hand anything over. Use `isFinished()` to ask whether the work is done.
57
62
  */
58
63
  isEmpty(): Promise<boolean>;
59
64
  /**
@@ -9,16 +9,7 @@ import type { AddRequestsBatchedOptions, AddRequestsBatchedResult, RequestQueueO
9
9
  * It first reads requests from the loader and then, when needed, transfers them in batches to the manager.
10
10
  */
11
11
  export declare class RequestManagerTandem implements IRequestManager {
12
- private log;
13
- private requestLoader;
14
- private requestManagerPromise?;
15
- private resolvedRequestManager?;
16
- private requestManagerFactory;
17
- /**
18
- * The latest expected request-processing time hinted via {@link setExpectedRequestProcessingTimeSecs}.
19
- * Remembered so it can be applied to the writable manager once it is lazily resolved.
20
- */
21
- private expectedRequestProcessingSecs?;
12
+ #private;
22
13
  /**
23
14
  * @param requestLoader The read-only loader to read requests from first.
24
15
  * @param requestManager The writable manager to transfer requests into and enqueue new ones. May be passed as a
@@ -5,16 +5,16 @@ import { serviceLocator } from '../service_locator.js';
5
5
  * It first reads requests from the loader and then, when needed, transfers them in batches to the manager.
6
6
  */
7
7
  export class RequestManagerTandem {
8
- log;
9
- requestLoader;
10
- requestManagerPromise;
11
- resolvedRequestManager;
12
- requestManagerFactory;
8
+ #log;
9
+ #requestLoader;
10
+ #requestManagerPromise;
11
+ #resolvedRequestManager;
12
+ #requestManagerFactory;
13
13
  /**
14
14
  * The latest expected request-processing time hinted via {@link setExpectedRequestProcessingTimeSecs}.
15
15
  * Remembered so it can be applied to the writable manager once it is lazily resolved.
16
16
  */
17
- expectedRequestProcessingSecs;
17
+ #expectedRequestProcessingSecs;
18
18
  /**
19
19
  * @param requestLoader The read-only loader to read requests from first.
20
20
  * @param requestManager The writable manager to transfer requests into and enqueue new ones. May be passed as a
@@ -22,24 +22,24 @@ export class RequestManagerTandem {
22
22
  * (e.g. a lazily-opened default {@link RequestQueue}).
23
23
  */
24
24
  constructor(requestLoader, requestManager) {
25
- this.log = serviceLocator.getLogger().child({ prefix: 'RequestManagerTandem' });
26
- this.requestLoader = requestLoader;
27
- this.requestManagerFactory = typeof requestManager === 'function' ? requestManager : () => requestManager;
25
+ this.#log = serviceLocator.getLogger().child({ prefix: 'RequestManagerTandem' });
26
+ this.#requestLoader = requestLoader;
27
+ this.#requestManagerFactory = typeof requestManager === 'function' ? requestManager : () => requestManager;
28
28
  }
29
29
  /**
30
30
  * Resolves the writable request manager, opening it lazily (via the factory) on first use and memoizing the result.
31
31
  * @private
32
32
  */
33
33
  async getRequestManager() {
34
- if (this.resolvedRequestManager === undefined) {
35
- this.requestManagerPromise ??= Promise.resolve(this.requestManagerFactory());
36
- this.resolvedRequestManager = await this.requestManagerPromise;
34
+ if (this.#resolvedRequestManager === undefined) {
35
+ this.#requestManagerPromise ??= Promise.resolve(this.#requestManagerFactory());
36
+ this.#resolvedRequestManager = await this.#requestManagerPromise;
37
37
  // Apply any hint received before the manager was resolved.
38
- if (this.expectedRequestProcessingSecs !== undefined) {
39
- await this.resolvedRequestManager.setExpectedRequestProcessingTimeSecs?.(this.expectedRequestProcessingSecs);
38
+ if (this.#expectedRequestProcessingSecs !== undefined) {
39
+ await this.#resolvedRequestManager.setExpectedRequestProcessingTimeSecs?.(this.#expectedRequestProcessingSecs);
40
40
  }
41
41
  }
42
- return this.resolvedRequestManager;
42
+ return this.#resolvedRequestManager;
43
43
  }
44
44
  /**
45
45
  * Transfers a single request from the read-only loader to the writable manager.
@@ -50,7 +50,7 @@ export class RequestManagerTandem {
50
50
  * @private
51
51
  */
52
52
  async transferNextRequestToQueue() {
53
- const request = await this.requestLoader.fetchNextRequest();
53
+ const request = await this.#requestLoader.fetchNextRequest();
54
54
  if (request === null) {
55
55
  return true;
56
56
  }
@@ -60,12 +60,12 @@ export class RequestManagerTandem {
60
60
  return true;
61
61
  }
62
62
  catch (error) {
63
- this.log.exception(error, 'Adding request from the RequestLoader to the RequestManager failed, the request has been dropped.', { url: request.url, uniqueKey: request.uniqueKey });
63
+ this.#log.exception(error, 'Adding request from the RequestLoader to the RequestManager failed, the request has been dropped.', { url: request.url, uniqueKey: request.uniqueKey });
64
64
  return false;
65
65
  }
66
66
  finally {
67
67
  // Mark it as handled so that the request doesn't get stuck in the `inProgress` state in the loader.
68
- await this.requestLoader.markRequestAsHandled(request);
68
+ await this.#requestLoader.markRequestAsHandled(request);
69
69
  }
70
70
  }
71
71
  /**
@@ -76,8 +76,8 @@ export class RequestManagerTandem {
76
76
  async fetchNextRequest() {
77
77
  // First, try to transfer a request from the requestList
78
78
  const [listEmpty, listFinished] = await Promise.all([
79
- this.requestLoader.isEmpty(),
80
- this.requestLoader.isFinished(),
79
+ this.#requestLoader.isEmpty(),
80
+ this.#requestLoader.isFinished(),
81
81
  ]);
82
82
  if (!listEmpty && !listFinished) {
83
83
  // If the transfer failed, the request was dropped; don't fetch from the manager this round (matching
@@ -94,7 +94,7 @@ export class RequestManagerTandem {
94
94
  */
95
95
  async isFinished() {
96
96
  const requestManager = await this.getRequestManager();
97
- const storagesFinished = await Promise.all([this.requestLoader.isFinished(), requestManager.isFinished()]);
97
+ const storagesFinished = await Promise.all([this.#requestLoader.isFinished(), requestManager.isFinished()]);
98
98
  return storagesFinished.every(Boolean);
99
99
  }
100
100
  /**
@@ -102,7 +102,7 @@ export class RequestManagerTandem {
102
102
  */
103
103
  async isEmpty() {
104
104
  const requestManager = await this.getRequestManager();
105
- const storagesEmpty = await Promise.all([this.requestLoader.isEmpty(), requestManager.isEmpty()]);
105
+ const storagesEmpty = await Promise.all([this.#requestLoader.isEmpty(), requestManager.isEmpty()]);
106
106
  return storagesEmpty.every(Boolean);
107
107
  }
108
108
  /**
@@ -120,7 +120,7 @@ export class RequestManagerTandem {
120
120
  const [managerTotal, loaderTotal] = await Promise.all([
121
121
  requestManager.getTotalCount(),
122
122
  // count only pending to avoid double counting, requests marked as "handled" have been moved to requestManager
123
- this.requestLoader.getPendingCount(),
123
+ this.#requestLoader.getPendingCount(),
124
124
  ]);
125
125
  return managerTotal + loaderTotal;
126
126
  }
@@ -131,7 +131,7 @@ export class RequestManagerTandem {
131
131
  const requestManager = await this.getRequestManager();
132
132
  const [managerPending, loaderPending] = await Promise.all([
133
133
  requestManager.getPendingCount(),
134
- this.requestLoader.getPendingCount(),
134
+ this.#requestLoader.getPendingCount(),
135
135
  ]);
136
136
  return managerPending + loaderPending;
137
137
  }
@@ -175,7 +175,7 @@ export class RequestManagerTandem {
175
175
  * @inheritdoc
176
176
  */
177
177
  async persistState() {
178
- await this.requestLoader.persistState?.();
178
+ await this.#requestLoader.persistState?.();
179
179
  }
180
180
  /**
181
181
  * Purges the writable request manager so the tandem can be reused (e.g. across repeated `crawler.run()` calls).
@@ -191,7 +191,7 @@ export class RequestManagerTandem {
191
191
  * @inheritdoc
192
192
  */
193
193
  async setExpectedRequestProcessingTimeSecs(secs) {
194
- this.expectedRequestProcessingSecs = secs;
195
- await this.resolvedRequestManager?.setExpectedRequestProcessingTimeSecs?.(secs);
194
+ this.#expectedRequestProcessingSecs = secs;
195
+ await this.#resolvedRequestManager?.setExpectedRequestProcessingTimeSecs?.(secs);
196
196
  }
197
197
  }