@crawlee/core 4.0.0-beta.105 → 4.0.0-beta.107
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.
- package/autoscaling/autoscaled_pool.d.ts +3 -21
- package/autoscaling/autoscaled_pool.js +85 -85
- package/autoscaling/client_load_signal.d.ts +1 -5
- package/autoscaling/client_load_signal.js +20 -20
- package/autoscaling/concurrency_system.d.ts +5 -20
- package/autoscaling/concurrency_system.js +81 -80
- package/autoscaling/cpu_load_signal.d.ts +1 -2
- package/autoscaling/cpu_load_signal.js +10 -10
- package/autoscaling/event_loop_load_signal.d.ts +1 -4
- package/autoscaling/event_loop_load_signal.js +18 -18
- package/autoscaling/load_signal.d.ts +1 -1
- package/autoscaling/load_signal.js +12 -11
- package/autoscaling/memory_load_signal.d.ts +3 -12
- package/autoscaling/memory_load_signal.js +40 -41
- package/autoscaling/snapshotter.d.ts +1 -4
- package/autoscaling/snapshotter.js +12 -12
- package/autoscaling/system_status.d.ts +1 -3
- package/autoscaling/system_status.js +11 -11
- package/configuration.d.ts +1 -1
- package/configuration.js +3 -3
- package/crawlers/context_pipeline.js +6 -6
- package/crawlers/statistics.d.ts +1 -8
- package/crawlers/statistics.js +45 -44
- package/events/event_manager.d.ts +1 -1
- package/events/event_manager.js +3 -3
- package/events/local_event_manager.d.ts +1 -1
- package/events/local_event_manager.js +3 -3
- package/log.js +5 -1
- package/memory-storage/memory-storage.d.ts +1 -5
- package/memory-storage/memory-storage.js +2 -2
- package/memory-storage/resource-clients/dataset.d.ts +1 -1
- package/memory-storage/resource-clients/dataset.js +6 -5
- package/memory-storage/resource-clients/key-value-store.d.ts +1 -1
- package/memory-storage/resource-clients/key-value-store.js +13 -12
- package/memory-storage/resource-clients/request-queue.d.ts +4 -23
- package/memory-storage/resource-clients/request-queue.js +59 -58
- package/owned_or_injected.d.ts +1 -3
- package/owned_or_injected.js +17 -17
- package/package.json +5 -5
- package/proxy_configuration.d.ts +1 -3
- package/proxy_configuration.js +8 -8
- package/recoverable_state.d.ts +1 -10
- package/recoverable_state.js +41 -41
- package/request.d.ts +1 -2
- package/request.js +10 -13
- package/router.d.ts +1 -4
- package/router.js +23 -23
- package/serialization.js +8 -9
- package/service_locator.d.ts +1 -10
- package/service_locator.js +48 -48
- package/session_pool/session.d.ts +3 -14
- package/session_pool/session.js +54 -54
- package/session_pool/session_pool.d.ts +2 -11
- package/session_pool/session_pool.js +59 -58
- package/storages/dataset.d.ts +1 -1
- package/storages/dataset.js +5 -5
- package/storages/key_value_store.d.ts +1 -4
- package/storages/key_value_store.js +21 -20
- package/storages/request_dedup_cache.d.ts +1 -2
- package/storages/request_dedup_cache.js +9 -9
- package/storages/request_list.d.ts +2 -22
- package/storages/request_list.js +74 -73
- package/storages/request_manager_tandem.d.ts +1 -10
- package/storages/request_manager_tandem.js +27 -27
- package/storages/request_queue.d.ts +2 -18
- package/storages/request_queue.js +37 -35
- package/storages/sitemap_request_loader.d.ts +1 -44
- package/storages/sitemap_request_loader.js +87 -87
- package/storages/storage_instance_manager.d.ts +1 -2
- package/storages/storage_instance_manager.js +17 -17
- package/storages/storage_stats.d.ts +1 -1
- package/storages/storage_stats.js +4 -4
- package/storages/transaction.d.ts +1 -3
- package/storages/transaction.js +17 -17
- package/system-info/runtime.js +7 -7
|
@@ -56,26 +56,28 @@ export class RequestQueue {
|
|
|
56
56
|
id;
|
|
57
57
|
name;
|
|
58
58
|
backend;
|
|
59
|
-
proxyConfiguration;
|
|
59
|
+
#proxyConfiguration;
|
|
60
60
|
log;
|
|
61
|
+
// kept as TS-private: request_queue tests read this cache directly
|
|
61
62
|
requestCache;
|
|
62
63
|
/**
|
|
63
64
|
* Remembers the `requestId` of every request already submitted to the client — including background
|
|
64
65
|
* batches that `requestCache` skips — so overlapping URL sets aren't re-submitted.
|
|
65
66
|
* See {@link RequestDeduplicationCache} for why this is a separate, cheaper cache.
|
|
66
67
|
*/
|
|
67
|
-
requestSeenCache;
|
|
68
|
-
queuePausedForMigration = false;
|
|
68
|
+
#requestSeenCache;
|
|
69
|
+
#queuePausedForMigration = false;
|
|
70
|
+
// kept as TS-private: packages/core/test request-queue tests write this counter directly
|
|
69
71
|
inProgressRequestBatchCount = 0;
|
|
70
72
|
/**
|
|
71
73
|
* The largest expected request-processing time (in seconds) seen so far via
|
|
72
74
|
* {@link setExpectedRequestProcessingTimeSecs}. Used to ensure that value is only ever raised, never
|
|
73
75
|
* lowered, before being forwarded to the storage backend.
|
|
74
76
|
*/
|
|
75
|
-
expectedRequestProcessingSecs = 0;
|
|
76
|
-
httpClient;
|
|
77
|
-
events;
|
|
78
|
-
statsTracker = new StorageStatsTracker({
|
|
77
|
+
#expectedRequestProcessingSecs = 0;
|
|
78
|
+
#httpClient;
|
|
79
|
+
#events;
|
|
80
|
+
#statsTracker = new StorageStatsTracker({
|
|
79
81
|
writeCount: 0,
|
|
80
82
|
headItemReadCount: 0,
|
|
81
83
|
});
|
|
@@ -84,7 +86,7 @@ export class RequestQueue {
|
|
|
84
86
|
* queue-head reads issued to the underlying storage backend). Counted per backend call.
|
|
85
87
|
*/
|
|
86
88
|
get stats() {
|
|
87
|
-
return this
|
|
89
|
+
return this.#statsTracker.current;
|
|
88
90
|
}
|
|
89
91
|
/**
|
|
90
92
|
* @internal
|
|
@@ -92,14 +94,14 @@ export class RequestQueue {
|
|
|
92
94
|
constructor(options) {
|
|
93
95
|
this.id = options.metadata.id;
|
|
94
96
|
this.name = options.metadata.name;
|
|
95
|
-
this
|
|
97
|
+
this.#events = serviceLocator.getEventManager();
|
|
96
98
|
this.backend = options.backend;
|
|
97
|
-
this
|
|
99
|
+
this.#proxyConfiguration = options.proxyConfiguration;
|
|
98
100
|
this.requestCache = new LruCache({ maxLength: MAX_CACHED_REQUESTS });
|
|
99
|
-
this
|
|
101
|
+
this.#requestSeenCache = new RequestDeduplicationCache();
|
|
100
102
|
this.log = serviceLocator.getLogger().child({ prefix: `RequestQueue(${this.id}, ${this.name ?? 'no-name'})` });
|
|
101
|
-
this
|
|
102
|
-
this
|
|
103
|
+
this.#events.on("migrating" /* EventType.MIGRATING */, async () => {
|
|
104
|
+
this.#queuePausedForMigration = true;
|
|
103
105
|
});
|
|
104
106
|
}
|
|
105
107
|
/**
|
|
@@ -169,7 +171,7 @@ export class RequestQueue {
|
|
|
169
171
|
forefront,
|
|
170
172
|
};
|
|
171
173
|
}
|
|
172
|
-
this
|
|
174
|
+
this.#statsTracker.add('writeCount');
|
|
173
175
|
const { processedRequests } = await this.backend.addBatchOfRequests([request], { forefront });
|
|
174
176
|
this.recordRequestJournalEntry(transaction, [request], forefront, true);
|
|
175
177
|
const queueOperationInfo = {
|
|
@@ -178,7 +180,7 @@ export class RequestQueue {
|
|
|
178
180
|
forefront,
|
|
179
181
|
};
|
|
180
182
|
this.cacheRequest(cacheKey, queueOperationInfo);
|
|
181
|
-
this
|
|
183
|
+
this.#requestSeenCache.add(cacheKey, request.id);
|
|
182
184
|
return queueOperationInfo;
|
|
183
185
|
}
|
|
184
186
|
/**
|
|
@@ -240,7 +242,7 @@ export class RequestQueue {
|
|
|
240
242
|
// reading saves a probe. Same lookup as the write-through path.
|
|
241
243
|
const cacheKey = getRequestId(request.uniqueKey);
|
|
242
244
|
const cachedInfo = this.requestCache.get(cacheKey);
|
|
243
|
-
const knownRequestId = cachedInfo?.id ?? this
|
|
245
|
+
const knownRequestId = cachedInfo?.id ?? this.#requestSeenCache.get(cacheKey);
|
|
244
246
|
if (knownRequestId) {
|
|
245
247
|
this.recordRequestJournalEntry(transaction, [request], forefront, false);
|
|
246
248
|
return {
|
|
@@ -306,7 +308,7 @@ export class RequestQueue {
|
|
|
306
308
|
: []);
|
|
307
309
|
if (requests.length === 0)
|
|
308
310
|
continue;
|
|
309
|
-
this
|
|
311
|
+
this.#statsTracker.add('writeCount');
|
|
310
312
|
const { processedRequests, unprocessedRequests } = await this.backend.addBatchOfRequests(requests, {
|
|
311
313
|
forefront,
|
|
312
314
|
});
|
|
@@ -314,7 +316,7 @@ export class RequestQueue {
|
|
|
314
316
|
for (const processed of processedRequests) {
|
|
315
317
|
const cacheKey = getRequestId(processed.uniqueKey);
|
|
316
318
|
this.cacheRequest(cacheKey, { ...processed, forefront });
|
|
317
|
-
this
|
|
319
|
+
this.#requestSeenCache.add(cacheKey, processed.requestId);
|
|
318
320
|
}
|
|
319
321
|
if (unprocessedRequests.length > 0) {
|
|
320
322
|
// Warn and skip, rather than retry or fail. `unprocessedRequests` is what remains after
|
|
@@ -389,7 +391,7 @@ export class RequestQueue {
|
|
|
389
391
|
const cacheKey = getCachedRequestId(request.uniqueKey);
|
|
390
392
|
// Prefer the full `requestCache` record; fall back to the dedup cache for background batches it skips.
|
|
391
393
|
const cachedInfo = this.requestCache.get(cacheKey);
|
|
392
|
-
const knownRequestId = cachedInfo?.id ?? this
|
|
394
|
+
const knownRequestId = cachedInfo?.id ?? this.#requestSeenCache.get(cacheKey);
|
|
393
395
|
if (knownRequestId) {
|
|
394
396
|
request.id = knownRequestId;
|
|
395
397
|
results.processedRequests.push({
|
|
@@ -408,7 +410,7 @@ export class RequestQueue {
|
|
|
408
410
|
if (!requestsToAdd.size) {
|
|
409
411
|
return results;
|
|
410
412
|
}
|
|
411
|
-
this
|
|
413
|
+
this.#statsTracker.add('writeCount');
|
|
412
414
|
const apiResults = await this.backend.addBatchOfRequests([...requestsToAdd.values()], { forefront });
|
|
413
415
|
// Report unprocessed requests
|
|
414
416
|
results.unprocessedRequests = apiResults.unprocessedRequests;
|
|
@@ -421,7 +423,7 @@ export class RequestQueue {
|
|
|
421
423
|
this.cacheRequest(cacheKey, { ...newRequest, forefront });
|
|
422
424
|
}
|
|
423
425
|
// Unlike `requestCache`, populate this on every batch (including background ones).
|
|
424
|
-
this
|
|
426
|
+
this.#requestSeenCache.add(cacheKey, newRequest.requestId);
|
|
425
427
|
}
|
|
426
428
|
return results;
|
|
427
429
|
}
|
|
@@ -609,10 +611,10 @@ export class RequestQueue {
|
|
|
609
611
|
*/
|
|
610
612
|
async fetchNextRequest() {
|
|
611
613
|
rejectOperationInTransaction('RequestQueue.fetchNextRequest()', 'it is part of the crawler request-processing bookkeeping, which a transaction must not affect.');
|
|
612
|
-
if (this
|
|
614
|
+
if (this.#queuePausedForMigration) {
|
|
613
615
|
return null;
|
|
614
616
|
}
|
|
615
|
-
this
|
|
617
|
+
this.#statsTracker.add('headItemReadCount');
|
|
616
618
|
const requestOptions = await this.backend.fetchNextRequest();
|
|
617
619
|
if (!requestOptions)
|
|
618
620
|
return null;
|
|
@@ -633,7 +635,7 @@ export class RequestQueue {
|
|
|
633
635
|
}));
|
|
634
636
|
const forefront = this.requestCache.get(getRequestId(request.uniqueKey))?.forefront ?? false;
|
|
635
637
|
const handledAt = request.handledAt ?? new Date().toISOString();
|
|
636
|
-
this
|
|
638
|
+
this.#statsTracker.add('writeCount');
|
|
637
639
|
const processedRequest = await this.backend.markRequestAsHandled({
|
|
638
640
|
...request,
|
|
639
641
|
handledAt,
|
|
@@ -667,7 +669,7 @@ export class RequestQueue {
|
|
|
667
669
|
forefront: ow.optional.boolean,
|
|
668
670
|
}));
|
|
669
671
|
const { forefront = false } = options;
|
|
670
|
-
this
|
|
672
|
+
this.#statsTracker.add('writeCount');
|
|
671
673
|
const processedRequest = await this.backend.reclaimRequest(request, { forefront });
|
|
672
674
|
// The request was not in progress — nothing to reclaim.
|
|
673
675
|
if (!processedRequest) {
|
|
@@ -730,10 +732,10 @@ export class RequestQueue {
|
|
|
730
732
|
* short the reservation of a long-lived one and have its in-flight request stolen.
|
|
731
733
|
*/
|
|
732
734
|
async setExpectedRequestProcessingTimeSecs(secs) {
|
|
733
|
-
if (secs <= this
|
|
735
|
+
if (secs <= this.#expectedRequestProcessingSecs) {
|
|
734
736
|
return;
|
|
735
737
|
}
|
|
736
|
-
this
|
|
738
|
+
this.#expectedRequestProcessingSecs = secs;
|
|
737
739
|
await this.backend.setExpectedRequestProcessingTimeSecs?.(secs);
|
|
738
740
|
}
|
|
739
741
|
/**
|
|
@@ -769,12 +771,12 @@ export class RequestQueue {
|
|
|
769
771
|
await this.backend.purge();
|
|
770
772
|
// Reset in-memory bookkeeping so the queue behaves as if freshly opened.
|
|
771
773
|
this.requestCache.clear();
|
|
772
|
-
this
|
|
774
|
+
this.#requestSeenCache.clear();
|
|
773
775
|
this.inProgressRequestBatchCount = 0;
|
|
774
776
|
// Reset the expected-processing-time high-water mark too, otherwise the monotonic-raise guard
|
|
775
777
|
// in `setExpectedRequestProcessingTimeSecs` would let a value raised in an earlier run leak into a
|
|
776
778
|
// later one and silently swallow a lower hint (the queue is meant to be reusable across runs).
|
|
777
|
-
this
|
|
779
|
+
this.#expectedRequestProcessingSecs = 0;
|
|
778
780
|
}
|
|
779
781
|
/**
|
|
780
782
|
* @inheritdoc
|
|
@@ -842,10 +844,10 @@ export class RequestQueue {
|
|
|
842
844
|
// Download remote resource and parse URLs.
|
|
843
845
|
let urlsArr;
|
|
844
846
|
try {
|
|
845
|
-
urlsArr = await this.
|
|
847
|
+
urlsArr = await this.downloadListOfUrls({
|
|
846
848
|
url: requestsFromUrl,
|
|
847
849
|
urlRegExp: regex,
|
|
848
|
-
proxyUrl: (await this
|
|
850
|
+
proxyUrl: (await this.#proxyConfiguration?.newProxyInfo())?.url,
|
|
849
851
|
});
|
|
850
852
|
}
|
|
851
853
|
catch (err) {
|
|
@@ -877,10 +879,10 @@ export class RequestQueue {
|
|
|
877
879
|
/**
|
|
878
880
|
* @internal wraps public utility for mocking purposes
|
|
879
881
|
*/
|
|
880
|
-
async
|
|
882
|
+
async downloadListOfUrls(options) {
|
|
881
883
|
return downloadListOfUrls({
|
|
882
884
|
...options,
|
|
883
|
-
httpClient: this
|
|
885
|
+
httpClient: this.#httpClient,
|
|
884
886
|
});
|
|
885
887
|
}
|
|
886
888
|
/**
|
|
@@ -919,8 +921,8 @@ export class RequestQueue {
|
|
|
919
921
|
backendOpener: () => storageBackend.createRequestQueueBackend(resolved),
|
|
920
922
|
backendCacheKey: storageBackend.getStorageBackendCacheKey?.() ?? storageBackend.constructor.name,
|
|
921
923
|
});
|
|
922
|
-
queue
|
|
923
|
-
queue
|
|
924
|
+
queue.#proxyConfiguration = options.proxyConfiguration;
|
|
925
|
+
queue.#httpClient = options.httpClient;
|
|
924
926
|
return queue;
|
|
925
927
|
}
|
|
926
928
|
}
|
|
@@ -80,55 +80,12 @@ export interface SitemapRequestLoaderOptions extends UrlConstraints {
|
|
|
80
80
|
* The loading of the sitemap is performed in the background so that crawling can start before the sitemap is fully loaded.
|
|
81
81
|
*/
|
|
82
82
|
export declare class SitemapRequestLoader implements IRequestLoader {
|
|
83
|
+
#private;
|
|
83
84
|
/**
|
|
84
85
|
* Set of URLs that were returned by `fetchNextRequest()` and not marked as handled yet.
|
|
85
86
|
* @internal
|
|
86
87
|
*/
|
|
87
88
|
inProgress: Set<string>;
|
|
88
|
-
/**
|
|
89
|
-
* Map of returned Request objects that have not been marked as handled yet.
|
|
90
|
-
*
|
|
91
|
-
* We use this to persist custom user fields on the in-progress requests.
|
|
92
|
-
*/
|
|
93
|
-
private requestData;
|
|
94
|
-
/**
|
|
95
|
-
* Object for keeping track of the sitemap parsing progress.
|
|
96
|
-
*/
|
|
97
|
-
private sitemapParsingProgress;
|
|
98
|
-
/**
|
|
99
|
-
* Object stream of URLs parsed from the sitemaps.
|
|
100
|
-
* Using `highWaterMark`, this can manage the speed of the sitemap loading.
|
|
101
|
-
*
|
|
102
|
-
* Fetch the next URL to be processed using `fetchNextRequest()`.
|
|
103
|
-
*/
|
|
104
|
-
private urlQueueStream;
|
|
105
|
-
/**
|
|
106
|
-
* Indicates whether the request list sitemap loading was aborted.
|
|
107
|
-
*
|
|
108
|
-
* If the loading was aborted before the sitemaps were fully loaded, the request list might be missing some URLs.
|
|
109
|
-
* The `isSitemapFullyLoaded` method can be used to check if the sitemaps were fully loaded.
|
|
110
|
-
*
|
|
111
|
-
* If the loading is aborted and all the requests are handled, `isFinished()` will return `true`.
|
|
112
|
-
*/
|
|
113
|
-
private abortLoading;
|
|
114
|
-
/** Number of URLs that were marked as handled */
|
|
115
|
-
private handledUrlCount;
|
|
116
|
-
private persistStateKey?;
|
|
117
|
-
private store?;
|
|
118
|
-
private closed;
|
|
119
|
-
/**
|
|
120
|
-
* Proxy URL to be used for sitemap loading.
|
|
121
|
-
*/
|
|
122
|
-
private proxyUrl?;
|
|
123
|
-
/**
|
|
124
|
-
* Logger instance.
|
|
125
|
-
*/
|
|
126
|
-
private log;
|
|
127
|
-
private urlExcludePatternObjects;
|
|
128
|
-
private urlPatternObjects;
|
|
129
|
-
/** EventManager used to handle persistence */
|
|
130
|
-
private events;
|
|
131
|
-
private persistenceOptions;
|
|
132
89
|
/** @internal */
|
|
133
90
|
private constructor();
|
|
134
91
|
/**
|
|
@@ -25,11 +25,11 @@ export class SitemapRequestLoader {
|
|
|
25
25
|
*
|
|
26
26
|
* We use this to persist custom user fields on the in-progress requests.
|
|
27
27
|
*/
|
|
28
|
-
requestData = new Map();
|
|
28
|
+
#requestData = new Map();
|
|
29
29
|
/**
|
|
30
30
|
* Object for keeping track of the sitemap parsing progress.
|
|
31
31
|
*/
|
|
32
|
-
sitemapParsingProgress = {
|
|
32
|
+
#sitemapParsingProgress = {
|
|
33
33
|
/**
|
|
34
34
|
* URL of the sitemap that is currently being parsed. `null` if no sitemap is being parsed.
|
|
35
35
|
*/
|
|
@@ -49,7 +49,7 @@ export class SitemapRequestLoader {
|
|
|
49
49
|
*
|
|
50
50
|
* Fetch the next URL to be processed using `fetchNextRequest()`.
|
|
51
51
|
*/
|
|
52
|
-
urlQueueStream;
|
|
52
|
+
#urlQueueStream;
|
|
53
53
|
/**
|
|
54
54
|
* Indicates whether the request list sitemap loading was aborted.
|
|
55
55
|
*
|
|
@@ -58,25 +58,25 @@ export class SitemapRequestLoader {
|
|
|
58
58
|
*
|
|
59
59
|
* If the loading is aborted and all the requests are handled, `isFinished()` will return `true`.
|
|
60
60
|
*/
|
|
61
|
-
abortLoading = false;
|
|
61
|
+
#abortLoading = false;
|
|
62
62
|
/** Number of URLs that were marked as handled */
|
|
63
|
-
handledUrlCount = 0;
|
|
64
|
-
persistStateKey;
|
|
65
|
-
store;
|
|
66
|
-
closed = false;
|
|
63
|
+
#handledUrlCount = 0;
|
|
64
|
+
#persistStateKey;
|
|
65
|
+
#store;
|
|
66
|
+
#closed = false;
|
|
67
67
|
/**
|
|
68
68
|
* Proxy URL to be used for sitemap loading.
|
|
69
69
|
*/
|
|
70
|
-
proxyUrl;
|
|
70
|
+
#proxyUrl;
|
|
71
71
|
/**
|
|
72
72
|
* Logger instance.
|
|
73
73
|
*/
|
|
74
|
-
log;
|
|
75
|
-
urlExcludePatternObjects = [];
|
|
76
|
-
urlPatternObjects = [];
|
|
74
|
+
#log;
|
|
75
|
+
#urlExcludePatternObjects = [];
|
|
76
|
+
#urlPatternObjects = [];
|
|
77
77
|
/** EventManager used to handle persistence */
|
|
78
|
-
events;
|
|
79
|
-
persistenceOptions;
|
|
78
|
+
#events;
|
|
79
|
+
#persistenceOptions;
|
|
80
80
|
/** @internal */
|
|
81
81
|
constructor(options) {
|
|
82
82
|
const urlPatternValidator = ow.any(ow.string, ow.regExp, ow.object.hasKeys('glob'), ow.object.hasKeys('regexp'));
|
|
@@ -94,19 +94,19 @@ export class SitemapRequestLoader {
|
|
|
94
94
|
httpClient: ow.optional.object,
|
|
95
95
|
}));
|
|
96
96
|
const { include, exclude } = options;
|
|
97
|
-
this
|
|
97
|
+
this.#log = serviceLocator.getLogger().child({ prefix: 'SitemapRequestLoader' });
|
|
98
98
|
if (exclude?.length) {
|
|
99
|
-
this
|
|
99
|
+
this.#urlExcludePatternObjects.push(...constructUrlPatternObjects(exclude));
|
|
100
100
|
}
|
|
101
101
|
if (include?.length) {
|
|
102
|
-
this
|
|
102
|
+
this.#urlPatternObjects.push(...constructUrlPatternObjects(include));
|
|
103
103
|
}
|
|
104
|
-
this
|
|
105
|
-
this
|
|
106
|
-
this
|
|
107
|
-
this
|
|
108
|
-
this
|
|
109
|
-
this
|
|
104
|
+
this.#persistStateKey = options.persistStateKey;
|
|
105
|
+
this.#persistenceOptions = { enable: true, ...options.persistenceOptions };
|
|
106
|
+
this.#proxyUrl = options.proxyUrl;
|
|
107
|
+
this.#urlQueueStream = this.createNewStream(options.maxBufferSize ?? 200);
|
|
108
|
+
this.#sitemapParsingProgress.pendingSitemapUrls = new Set(options.sitemapUrls);
|
|
109
|
+
this.#events = serviceLocator.getEventManager();
|
|
110
110
|
this.persistState = this.persistState.bind(this);
|
|
111
111
|
}
|
|
112
112
|
/**
|
|
@@ -139,8 +139,8 @@ export class SitemapRequestLoader {
|
|
|
139
139
|
* @returns `true` if the URL matches the patterns, `false` otherwise.
|
|
140
140
|
*/
|
|
141
141
|
isUrlMatchingPatterns(url) {
|
|
142
|
-
return (!this
|
|
143
|
-
(this
|
|
142
|
+
return (!this.#urlExcludePatternObjects.some(this.matchesUrl(url)) &&
|
|
143
|
+
(this.#urlPatternObjects.length === 0 || this.#urlPatternObjects.some(this.matchesUrl(url))));
|
|
144
144
|
}
|
|
145
145
|
/**
|
|
146
146
|
* Adds a URL to the queue of parsed URLs.
|
|
@@ -149,13 +149,13 @@ export class SitemapRequestLoader {
|
|
|
149
149
|
*/
|
|
150
150
|
async pushNextUrl(url) {
|
|
151
151
|
return new Promise((resolve) => {
|
|
152
|
-
if (this
|
|
152
|
+
if (this.#closed || (url && !this.isUrlMatchingPatterns(url))) {
|
|
153
153
|
resolve();
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
156
|
-
if (!this
|
|
156
|
+
if (!this.#urlQueueStream.push(url)) {
|
|
157
157
|
// This doesn't work with the 'drain' event (it's not emitted for some reason).
|
|
158
|
-
this
|
|
158
|
+
this.#urlQueueStream.once('readdata', () => {
|
|
159
159
|
resolve();
|
|
160
160
|
});
|
|
161
161
|
}
|
|
@@ -172,21 +172,21 @@ export class SitemapRequestLoader {
|
|
|
172
172
|
*/
|
|
173
173
|
async readNextUrl() {
|
|
174
174
|
return new Promise((resolve) => {
|
|
175
|
-
if (this
|
|
175
|
+
if (this.#closed) {
|
|
176
176
|
resolve(null);
|
|
177
177
|
return;
|
|
178
178
|
}
|
|
179
|
-
const result = this
|
|
179
|
+
const result = this.#urlQueueStream.read();
|
|
180
180
|
if (!result && !this.isSitemapFullyLoaded()) {
|
|
181
|
-
this
|
|
182
|
-
const nextUrl = this
|
|
181
|
+
this.#urlQueueStream.once('readable', () => {
|
|
182
|
+
const nextUrl = this.#urlQueueStream.read();
|
|
183
183
|
resolve(nextUrl);
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
186
|
else {
|
|
187
187
|
resolve(result);
|
|
188
188
|
}
|
|
189
|
-
this
|
|
189
|
+
this.#urlQueueStream.emit('readdata');
|
|
190
190
|
});
|
|
191
191
|
}
|
|
192
192
|
/**
|
|
@@ -195,8 +195,8 @@ export class SitemapRequestLoader {
|
|
|
195
195
|
* If this is `false`, the background processing is either still in progress or was aborted.
|
|
196
196
|
*/
|
|
197
197
|
isSitemapFullyLoaded() {
|
|
198
|
-
return (this
|
|
199
|
-
this
|
|
198
|
+
return (this.#sitemapParsingProgress.inProgressSitemapUrl === null &&
|
|
199
|
+
this.#sitemapParsingProgress.pendingSitemapUrls.size === 0);
|
|
200
200
|
}
|
|
201
201
|
/**
|
|
202
202
|
* Start processing the sitemaps and loading the URLs.
|
|
@@ -204,34 +204,34 @@ export class SitemapRequestLoader {
|
|
|
204
204
|
* Resolves once all the sitemaps URLs have been fully loaded (sets `isSitemapFullyLoaded` to `true`).
|
|
205
205
|
*/
|
|
206
206
|
async load({ parseSitemapOptions, }) {
|
|
207
|
-
while (!this.isSitemapFullyLoaded() && !this
|
|
208
|
-
const sitemapUrl = this
|
|
209
|
-
this
|
|
207
|
+
while (!this.isSitemapFullyLoaded() && !this.#abortLoading) {
|
|
208
|
+
const sitemapUrl = this.#sitemapParsingProgress.inProgressSitemapUrl ??
|
|
209
|
+
this.#sitemapParsingProgress.pendingSitemapUrls.values().next().value;
|
|
210
210
|
try {
|
|
211
|
-
for await (const item of parseSitemap([{ type: 'url', url: sitemapUrl }], this
|
|
211
|
+
for await (const item of parseSitemap([{ type: 'url', url: sitemapUrl }], this.#proxyUrl, {
|
|
212
212
|
...parseSitemapOptions,
|
|
213
213
|
maxDepth: 0,
|
|
214
214
|
emitNestedSitemaps: true,
|
|
215
215
|
})) {
|
|
216
216
|
if (!item.originSitemapUrl) {
|
|
217
217
|
// This is a nested sitemap
|
|
218
|
-
this
|
|
218
|
+
this.#sitemapParsingProgress.pendingSitemapUrls.add(item.loc);
|
|
219
219
|
continue;
|
|
220
220
|
}
|
|
221
|
-
if (!this
|
|
221
|
+
if (!this.#sitemapParsingProgress.inProgressEntries.has(item.loc)) {
|
|
222
222
|
await this.pushNextUrl(item.loc);
|
|
223
|
-
this
|
|
223
|
+
this.#sitemapParsingProgress.inProgressEntries.add(item.loc);
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
catch (e) {
|
|
228
|
-
this
|
|
228
|
+
this.#log.error('Error loading sitemap contents:', e);
|
|
229
229
|
}
|
|
230
|
-
this
|
|
231
|
-
this
|
|
232
|
-
this
|
|
230
|
+
this.#sitemapParsingProgress.pendingSitemapUrls.delete(sitemapUrl);
|
|
231
|
+
this.#sitemapParsingProgress.inProgressEntries.clear();
|
|
232
|
+
this.#sitemapParsingProgress.inProgressSitemapUrl = null;
|
|
233
233
|
}
|
|
234
|
-
this
|
|
234
|
+
this.#urlQueueStream.end();
|
|
235
235
|
}
|
|
236
236
|
/**
|
|
237
237
|
* Open a sitemap and start processing it.
|
|
@@ -250,15 +250,15 @@ export class SitemapRequestLoader {
|
|
|
250
250
|
void requestList.load({
|
|
251
251
|
parseSitemapOptions: { logger: serviceLocator.getLogger(), ...options.parseSitemapOptions, httpClient },
|
|
252
252
|
});
|
|
253
|
-
if (requestList
|
|
254
|
-
requestList
|
|
253
|
+
if (requestList.#persistenceOptions.enable) {
|
|
254
|
+
requestList.#events.on("persistState" /* EventType.PERSIST_STATE */, requestList.persistState);
|
|
255
255
|
}
|
|
256
256
|
options?.signal?.addEventListener('abort', () => {
|
|
257
|
-
requestList
|
|
257
|
+
requestList.#abortLoading = true;
|
|
258
258
|
});
|
|
259
259
|
if (options.timeoutMillis) {
|
|
260
260
|
setTimeout(() => {
|
|
261
|
-
requestList
|
|
261
|
+
requestList.#abortLoading = true;
|
|
262
262
|
}, options.timeoutMillis);
|
|
263
263
|
}
|
|
264
264
|
return requestList;
|
|
@@ -269,14 +269,14 @@ export class SitemapRequestLoader {
|
|
|
269
269
|
async getTotalCount() {
|
|
270
270
|
// Total known so far = not-yet-fetched (still buffered in the stream) + in-progress (fetched but not
|
|
271
271
|
// yet handled) + already handled.
|
|
272
|
-
return this
|
|
272
|
+
return this.#urlQueueStream.readableLength + this.inProgress.size + this.#handledUrlCount;
|
|
273
273
|
}
|
|
274
274
|
/**
|
|
275
275
|
* @inheritDoc
|
|
276
276
|
*/
|
|
277
277
|
async getPendingCount() {
|
|
278
278
|
// Pending = everything not yet handled = not-yet-fetched + in-progress.
|
|
279
|
-
return this
|
|
279
|
+
return this.#urlQueueStream.readableLength + this.inProgress.size;
|
|
280
280
|
}
|
|
281
281
|
/**
|
|
282
282
|
* Combines this list with a request manager (a {@link RequestQueue} by default) into a
|
|
@@ -293,31 +293,31 @@ export class SitemapRequestLoader {
|
|
|
293
293
|
* @inheritDoc
|
|
294
294
|
*/
|
|
295
295
|
async isFinished() {
|
|
296
|
-
return ((await this.isEmpty()) && this.inProgress.size === 0 && (this.isSitemapFullyLoaded() || this
|
|
296
|
+
return ((await this.isEmpty()) && this.inProgress.size === 0 && (this.isSitemapFullyLoaded() || this.#abortLoading));
|
|
297
297
|
}
|
|
298
298
|
/**
|
|
299
299
|
* @inheritDoc
|
|
300
300
|
*/
|
|
301
301
|
async isEmpty() {
|
|
302
|
-
return this
|
|
302
|
+
return this.#urlQueueStream.readableLength === 0;
|
|
303
303
|
}
|
|
304
304
|
/**
|
|
305
305
|
* @inheritDoc
|
|
306
306
|
*/
|
|
307
307
|
async getHandledCount() {
|
|
308
|
-
return this
|
|
308
|
+
return this.#handledUrlCount;
|
|
309
309
|
}
|
|
310
310
|
/**
|
|
311
311
|
* @inheritDoc
|
|
312
312
|
*/
|
|
313
313
|
async persistState() {
|
|
314
|
-
if (this
|
|
314
|
+
if (this.#persistStateKey === undefined) {
|
|
315
315
|
return;
|
|
316
316
|
}
|
|
317
|
-
this
|
|
317
|
+
this.#store ??= await KeyValueStore.open();
|
|
318
318
|
const urlQueue = [];
|
|
319
|
-
while (this
|
|
320
|
-
const url = this
|
|
319
|
+
while (this.#urlQueueStream.readableLength > 0) {
|
|
320
|
+
const url = this.#urlQueueStream.read();
|
|
321
321
|
if (url === null) {
|
|
322
322
|
break;
|
|
323
323
|
}
|
|
@@ -325,48 +325,48 @@ export class SitemapRequestLoader {
|
|
|
325
325
|
}
|
|
326
326
|
// Create a new stream, as we have read all the URLs from the current one.
|
|
327
327
|
// Pushing the urls back to the original stream might not be possible if it has been ended.
|
|
328
|
-
const newStream = this.createNewStream(this
|
|
328
|
+
const newStream = this.createNewStream(this.#urlQueueStream.readableHighWaterMark);
|
|
329
329
|
for (const url of urlQueue) {
|
|
330
330
|
newStream.push(url);
|
|
331
331
|
}
|
|
332
|
-
if (this
|
|
332
|
+
if (this.#urlQueueStream.writableEnded) {
|
|
333
333
|
newStream.end();
|
|
334
334
|
}
|
|
335
|
-
this
|
|
336
|
-
await this
|
|
335
|
+
this.#urlQueueStream = newStream;
|
|
336
|
+
await this.#store.setValue(this.#persistStateKey, {
|
|
337
337
|
sitemapParsingProgress: {
|
|
338
|
-
pendingSitemapUrls: Array.from(this
|
|
339
|
-
inProgressSitemapUrl: this
|
|
340
|
-
inProgressEntries: Array.from(this
|
|
338
|
+
pendingSitemapUrls: Array.from(this.#sitemapParsingProgress.pendingSitemapUrls),
|
|
339
|
+
inProgressSitemapUrl: this.#sitemapParsingProgress.inProgressSitemapUrl,
|
|
340
|
+
inProgressEntries: Array.from(this.#sitemapParsingProgress.inProgressEntries),
|
|
341
341
|
},
|
|
342
342
|
// Re-queue in-progress requests to the front so they are retried if the state is restored.
|
|
343
343
|
urlQueue: [...this.inProgress, ...urlQueue],
|
|
344
|
-
requestData: Array.from(this
|
|
345
|
-
abortLoading: this
|
|
346
|
-
closed: this
|
|
344
|
+
requestData: Array.from(this.#requestData.entries()),
|
|
345
|
+
abortLoading: this.#abortLoading,
|
|
346
|
+
closed: this.#closed,
|
|
347
347
|
});
|
|
348
348
|
}
|
|
349
349
|
async restoreState() {
|
|
350
350
|
await purgeDefaultStorages({ onlyPurgeOnce: true });
|
|
351
|
-
if (this
|
|
351
|
+
if (this.#persistStateKey === undefined) {
|
|
352
352
|
return;
|
|
353
353
|
}
|
|
354
|
-
this
|
|
355
|
-
const state = await this
|
|
354
|
+
this.#store ??= await KeyValueStore.open();
|
|
355
|
+
const state = await this.#store.getValue(this.#persistStateKey);
|
|
356
356
|
if (state === null) {
|
|
357
357
|
return;
|
|
358
358
|
}
|
|
359
|
-
this
|
|
359
|
+
this.#sitemapParsingProgress = {
|
|
360
360
|
pendingSitemapUrls: new Set(state.sitemapParsingProgress.pendingSitemapUrls),
|
|
361
361
|
inProgressSitemapUrl: state.sitemapParsingProgress.inProgressSitemapUrl,
|
|
362
362
|
inProgressEntries: new Set(state.sitemapParsingProgress.inProgressEntries),
|
|
363
363
|
};
|
|
364
|
-
this
|
|
364
|
+
this.#requestData = new Map(state.requestData ?? []);
|
|
365
365
|
for (const url of state.urlQueue) {
|
|
366
|
-
this
|
|
366
|
+
this.#urlQueueStream.push(url);
|
|
367
367
|
}
|
|
368
|
-
this
|
|
369
|
-
this
|
|
368
|
+
this.#abortLoading = state.abortLoading;
|
|
369
|
+
this.#closed = state.closed;
|
|
370
370
|
}
|
|
371
371
|
/**
|
|
372
372
|
* @inheritDoc
|
|
@@ -377,11 +377,11 @@ export class SitemapRequestLoader {
|
|
|
377
377
|
return null;
|
|
378
378
|
}
|
|
379
379
|
// A restored in-progress request already has its Request data; don't overwrite it.
|
|
380
|
-
if (!this
|
|
381
|
-
this
|
|
380
|
+
if (!this.#requestData.has(nextUrl)) {
|
|
381
|
+
this.#requestData.set(nextUrl, new Request({ url: nextUrl }));
|
|
382
382
|
}
|
|
383
383
|
this.inProgress.add(nextUrl);
|
|
384
|
-
return this
|
|
384
|
+
return this.#requestData.get(nextUrl);
|
|
385
385
|
}
|
|
386
386
|
/**
|
|
387
387
|
* @inheritDoc
|
|
@@ -400,20 +400,20 @@ export class SitemapRequestLoader {
|
|
|
400
400
|
* Calling `fetchNextRequest()` after this method will always return `null`.
|
|
401
401
|
*/
|
|
402
402
|
async teardown() {
|
|
403
|
-
this
|
|
404
|
-
this
|
|
405
|
-
this
|
|
403
|
+
this.#closed = true;
|
|
404
|
+
this.#abortLoading = true;
|
|
405
|
+
this.#events.off("persistState" /* EventType.PERSIST_STATE */, this.persistState);
|
|
406
406
|
await this.persistState();
|
|
407
|
-
this
|
|
407
|
+
this.#urlQueueStream.emit('readdata'); // unblocks the potentially waiting `pushNextUrl` call
|
|
408
408
|
}
|
|
409
409
|
/**
|
|
410
410
|
* @inheritDoc
|
|
411
411
|
*/
|
|
412
412
|
async markRequestAsHandled(request) {
|
|
413
|
-
this
|
|
413
|
+
this.#handledUrlCount += 1;
|
|
414
414
|
this.ensureInProgress(request.url);
|
|
415
415
|
this.inProgress.delete(request.url);
|
|
416
|
-
this
|
|
416
|
+
this.#requestData.delete(request.url);
|
|
417
417
|
}
|
|
418
418
|
ensureInProgress(url) {
|
|
419
419
|
if (!this.inProgress.has(url)) {
|