@crawlee/core 4.0.0-beta.28 → 4.0.0-beta.280
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/README.md +15 -15
- package/byte-utils.d.ts +17 -0
- package/byte-utils.js +42 -0
- package/configuration.d.ts +75 -167
- package/configuration.js +164 -140
- package/debug.d.ts +36 -0
- package/debug.js +70 -0
- package/errors.d.ts +40 -19
- package/errors.js +52 -30
- package/events/{event_manager.d.ts → event-manager.d.ts} +29 -6
- package/events/{event_manager.js → event-manager.js} +10 -9
- package/events/index.d.ts +3 -3
- package/events/index.js +2 -3
- package/events/{local_event_manager.d.ts → local-event-manager.d.ts} +3 -10
- package/events/local-event-manager.js +101 -0
- package/events/system-info.d.ts +38 -0
- package/events/system-info.js +1 -0
- package/index.d.ts +9 -13
- package/index.js +10 -12
- package/internal.d.ts +8 -0
- package/internal.js +9 -0
- package/iterables.d.ts +79 -0
- package/iterables.js +134 -0
- package/log.d.ts +77 -1
- package/log.js +134 -1
- package/memory-storage/consts.d.ts +4 -0
- package/memory-storage/consts.js +4 -0
- package/memory-storage/index.d.ts +1 -0
- package/memory-storage/index.js +1 -0
- package/memory-storage/memory-storage.d.ts +43 -0
- package/memory-storage/memory-storage.js +158 -0
- package/memory-storage/resource-clients/common/base-client.d.ts +4 -0
- package/memory-storage/resource-clients/common/base-client.js +6 -0
- package/memory-storage/resource-clients/dataset.d.ts +35 -0
- package/memory-storage/resource-clients/dataset.js +108 -0
- package/memory-storage/resource-clients/key-value-store.d.ts +54 -0
- package/memory-storage/resource-clients/key-value-store.js +179 -0
- package/memory-storage/resource-clients/request-queue.d.ts +55 -0
- package/memory-storage/resource-clients/request-queue.js +413 -0
- package/memory-storage/utils.d.ts +16 -0
- package/memory-storage/utils.js +41 -0
- package/owned-or-injected.d.ts +58 -0
- package/owned-or-injected.js +98 -0
- package/package.json +23 -23
- package/proxy-configuration.d.ts +118 -0
- package/proxy-configuration.js +141 -0
- package/recoverable-state.d.ts +177 -0
- package/recoverable-state.js +268 -0
- package/request.d.ts +48 -101
- package/request.js +139 -249
- package/serialization.d.ts +0 -1
- package/serialization.js +18 -18
- package/{service_locator.d.ts → service-locator.d.ts} +66 -37
- package/service-locator.js +255 -0
- package/storages/batched-adds.d.ts +39 -0
- package/storages/batched-adds.js +81 -0
- package/storages/dataset.d.ts +105 -58
- package/storages/dataset.js +292 -151
- package/storages/index.d.ts +11 -12
- package/storages/index.js +7 -12
- package/storages/key-value-store-codec.d.ts +32 -0
- package/storages/key-value-store-codec.js +108 -0
- package/storages/{key_value_store.d.ts → key-value-store.d.ts} +169 -72
- package/storages/key-value-store.js +767 -0
- package/storages/request-dedup-cache.d.ts +20 -0
- package/storages/request-dedup-cache.js +48 -0
- package/storages/{request_list.d.ts → request-list.d.ts} +32 -171
- package/storages/{request_list.js → request-list.js} +276 -253
- package/storages/request-loader.d.ts +127 -0
- package/storages/request-loader.js +34 -0
- package/storages/request-manager-tandem.d.ts +82 -0
- package/storages/request-manager-tandem.js +200 -0
- package/storages/request-manager.d.ts +119 -0
- package/storages/request-manager.js +1 -0
- package/storages/{request_provider.d.ts → request-queue.d.ts} +127 -161
- package/storages/request-queue.js +871 -0
- package/storages/storage-instance-manager.d.ts +86 -0
- package/storages/storage-instance-manager.js +256 -0
- package/storages/storage-stats.d.ts +48 -0
- package/storages/storage-stats.js +29 -0
- package/storages/transaction.d.ts +270 -0
- package/storages/transaction.js +296 -0
- package/storages/utils.d.ts +59 -11
- package/storages/utils.js +76 -17
- package/system-info/cpu-info.d.ts +67 -0
- package/system-info/cpu-info.js +216 -0
- package/system-info/memory-info.d.ts +31 -0
- package/system-info/memory-info.js +115 -0
- package/system-info/ps-tree.d.ts +17 -0
- package/system-info/ps-tree.js +144 -0
- package/system-info/runtime.d.ts +14 -0
- package/system-info/runtime.js +80 -0
- package/typedefs.d.ts +0 -6
- package/typedefs.js +0 -1
- package/url.d.ts +9 -0
- package/url.js +11 -0
- package/validators.d.ts +23 -18
- package/validators.js +14 -18
- package/autoscaling/autoscaled_pool.d.ts +0 -298
- package/autoscaling/autoscaled_pool.d.ts.map +0 -1
- package/autoscaling/autoscaled_pool.js +0 -527
- package/autoscaling/autoscaled_pool.js.map +0 -1
- package/autoscaling/index.d.ts +0 -4
- package/autoscaling/index.d.ts.map +0 -1
- package/autoscaling/index.js +0 -4
- package/autoscaling/index.js.map +0 -1
- package/autoscaling/snapshotter.d.ts +0 -179
- package/autoscaling/snapshotter.d.ts.map +0 -1
- package/autoscaling/snapshotter.js +0 -279
- package/autoscaling/snapshotter.js.map +0 -1
- package/autoscaling/system_status.d.ts +0 -178
- package/autoscaling/system_status.d.ts.map +0 -1
- package/autoscaling/system_status.js +0 -168
- package/autoscaling/system_status.js.map +0 -1
- package/configuration.d.ts.map +0 -1
- package/configuration.js.map +0 -1
- package/cookie_utils.d.ts +0 -43
- package/cookie_utils.d.ts.map +0 -1
- package/cookie_utils.js +0 -111
- package/cookie_utils.js.map +0 -1
- package/crawlers/context_pipeline.d.ts +0 -61
- package/crawlers/context_pipeline.d.ts.map +0 -1
- package/crawlers/context_pipeline.js +0 -99
- package/crawlers/context_pipeline.js.map +0 -1
- package/crawlers/crawler_commons.d.ts +0 -191
- package/crawlers/crawler_commons.d.ts.map +0 -1
- package/crawlers/crawler_commons.js +0 -108
- package/crawlers/crawler_commons.js.map +0 -1
- package/crawlers/crawler_utils.d.ts +0 -10
- package/crawlers/crawler_utils.d.ts.map +0 -1
- package/crawlers/crawler_utils.js +0 -12
- package/crawlers/crawler_utils.js.map +0 -1
- package/crawlers/error_snapshotter.d.ts +0 -60
- package/crawlers/error_snapshotter.d.ts.map +0 -1
- package/crawlers/error_snapshotter.js +0 -118
- package/crawlers/error_snapshotter.js.map +0 -1
- package/crawlers/error_tracker.d.ts +0 -55
- package/crawlers/error_tracker.d.ts.map +0 -1
- package/crawlers/error_tracker.js +0 -309
- package/crawlers/error_tracker.js.map +0 -1
- package/crawlers/index.d.ts +0 -7
- package/crawlers/index.d.ts.map +0 -1
- package/crawlers/index.js +0 -7
- package/crawlers/index.js.map +0 -1
- package/crawlers/internals/types.d.ts +0 -8
- package/crawlers/internals/types.d.ts.map +0 -1
- package/crawlers/internals/types.js +0 -2
- package/crawlers/internals/types.js.map +0 -1
- package/crawlers/statistics.d.ts +0 -204
- package/crawlers/statistics.d.ts.map +0 -1
- package/crawlers/statistics.js +0 -350
- package/crawlers/statistics.js.map +0 -1
- package/enqueue_links/enqueue_links.d.ts +0 -254
- package/enqueue_links/enqueue_links.d.ts.map +0 -1
- package/enqueue_links/enqueue_links.js +0 -268
- package/enqueue_links/enqueue_links.js.map +0 -1
- package/enqueue_links/index.d.ts +0 -3
- package/enqueue_links/index.d.ts.map +0 -1
- package/enqueue_links/index.js +0 -3
- package/enqueue_links/index.js.map +0 -1
- package/enqueue_links/shared.d.ts +0 -84
- package/enqueue_links/shared.d.ts.map +0 -1
- package/enqueue_links/shared.js +0 -219
- package/enqueue_links/shared.js.map +0 -1
- package/errors.d.ts.map +0 -1
- package/errors.js.map +0 -1
- package/events/event_manager.d.ts.map +0 -1
- package/events/event_manager.js.map +0 -1
- package/events/index.d.ts.map +0 -1
- package/events/index.js.map +0 -1
- package/events/local_event_manager.d.ts.map +0 -1
- package/events/local_event_manager.js +0 -93
- package/events/local_event_manager.js.map +0 -1
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/log.d.ts.map +0 -1
- package/log.js.map +0 -1
- package/proxy_configuration.d.ts +0 -169
- package/proxy_configuration.d.ts.map +0 -1
- package/proxy_configuration.js +0 -247
- package/proxy_configuration.js.map +0 -1
- package/recoverable_state.d.ts +0 -122
- package/recoverable_state.d.ts.map +0 -1
- package/recoverable_state.js +0 -138
- package/recoverable_state.js.map +0 -1
- package/request.d.ts.map +0 -1
- package/request.js.map +0 -1
- package/router.d.ts +0 -134
- package/router.d.ts.map +0 -1
- package/router.js +0 -164
- package/router.js.map +0 -1
- package/serialization.d.ts.map +0 -1
- package/serialization.js.map +0 -1
- package/service_locator.d.ts.map +0 -1
- package/service_locator.js +0 -252
- package/service_locator.js.map +0 -1
- package/session_pool/consts.d.ts +0 -4
- package/session_pool/consts.d.ts.map +0 -1
- package/session_pool/consts.js +0 -4
- package/session_pool/consts.js.map +0 -1
- package/session_pool/errors.d.ts +0 -8
- package/session_pool/errors.d.ts.map +0 -1
- package/session_pool/errors.js +0 -12
- package/session_pool/errors.js.map +0 -1
- package/session_pool/events.d.ts +0 -3
- package/session_pool/events.d.ts.map +0 -1
- package/session_pool/events.js +0 -3
- package/session_pool/events.js.map +0 -1
- package/session_pool/index.d.ts +0 -6
- package/session_pool/index.d.ts.map +0 -1
- package/session_pool/index.js +0 -6
- package/session_pool/index.js.map +0 -1
- package/session_pool/session.d.ts +0 -186
- package/session_pool/session.d.ts.map +0 -1
- package/session_pool/session.js +0 -278
- package/session_pool/session.js.map +0 -1
- package/session_pool/session_pool.d.ts +0 -256
- package/session_pool/session_pool.d.ts.map +0 -1
- package/session_pool/session_pool.js +0 -386
- package/session_pool/session_pool.js.map +0 -1
- package/storages/access_checking.d.ts +0 -13
- package/storages/access_checking.d.ts.map +0 -1
- package/storages/access_checking.js +0 -14
- package/storages/access_checking.js.map +0 -1
- package/storages/dataset.d.ts.map +0 -1
- package/storages/dataset.js.map +0 -1
- package/storages/index.d.ts.map +0 -1
- package/storages/index.js.map +0 -1
- package/storages/key_value_store.d.ts.map +0 -1
- package/storages/key_value_store.js +0 -513
- package/storages/key_value_store.js.map +0 -1
- package/storages/request_list.d.ts.map +0 -1
- package/storages/request_list.js.map +0 -1
- package/storages/request_list_adapter.d.ts +0 -58
- package/storages/request_list_adapter.d.ts.map +0 -1
- package/storages/request_list_adapter.js +0 -81
- package/storages/request_list_adapter.js.map +0 -1
- package/storages/request_manager_tandem.d.ts +0 -68
- package/storages/request_manager_tandem.d.ts.map +0 -1
- package/storages/request_manager_tandem.js +0 -124
- package/storages/request_manager_tandem.js.map +0 -1
- package/storages/request_provider.d.ts.map +0 -1
- package/storages/request_provider.js +0 -592
- package/storages/request_provider.js.map +0 -1
- package/storages/request_queue.d.ts +0 -128
- package/storages/request_queue.d.ts.map +0 -1
- package/storages/request_queue.js +0 -320
- package/storages/request_queue.js.map +0 -1
- package/storages/request_queue_v2.d.ts +0 -87
- package/storages/request_queue_v2.d.ts.map +0 -1
- package/storages/request_queue_v2.js +0 -437
- package/storages/request_queue_v2.js.map +0 -1
- package/storages/sitemap_request_list.d.ts +0 -245
- package/storages/sitemap_request_list.d.ts.map +0 -1
- package/storages/sitemap_request_list.js +0 -431
- package/storages/sitemap_request_list.js.map +0 -1
- package/storages/storage_manager.d.ts +0 -60
- package/storages/storage_manager.d.ts.map +0 -1
- package/storages/storage_manager.js +0 -95
- package/storages/storage_manager.js.map +0 -1
- package/storages/utils.d.ts.map +0 -1
- package/storages/utils.js.map +0 -1
- package/typedefs.d.ts.map +0 -1
- package/typedefs.js.map +0 -1
- package/validators.d.ts.map +0 -1
- package/validators.js.map +0 -1
|
@@ -1,16 +1,98 @@
|
|
|
1
1
|
import { downloadListOfUrls } from '@crawlee/utils';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { convertStateSync, RecoverableState } from '../recoverable-state.js';
|
|
4
4
|
import { Request } from '../request.js';
|
|
5
5
|
import { createDeserialize, serializeArray } from '../serialization.js';
|
|
6
|
-
import { serviceLocator } from '../
|
|
7
|
-
import {
|
|
6
|
+
import { serviceLocator } from '../service-locator.js';
|
|
7
|
+
import { parseArgument, schemas, validators } from '../validators.js';
|
|
8
|
+
import { KeyValueStore } from './key-value-store.js';
|
|
8
9
|
import { purgeDefaultStorages } from './utils.js';
|
|
9
10
|
/** @internal */
|
|
10
11
|
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
|
|
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,110 +155,124 @@ const CONTENT_TYPE_BINARY = 'application/octet-stream';
|
|
|
73
155
|
* @category Sources
|
|
74
156
|
*/
|
|
75
157
|
export class RequestList {
|
|
76
|
-
log =
|
|
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
|
-
*
|
|
89
|
-
*
|
|
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
|
-
|
|
171
|
+
#state;
|
|
172
|
+
#stateCodec;
|
|
92
173
|
/**
|
|
93
|
-
* Set of `uniqueKey`s of requests
|
|
174
|
+
* Set of `uniqueKey`s of requests that were returned by fetchNextRequest().
|
|
94
175
|
* @internal
|
|
95
176
|
*/
|
|
96
|
-
|
|
177
|
+
get inProgress() {
|
|
178
|
+
return this.#state.currentValue.inProgress;
|
|
179
|
+
}
|
|
97
180
|
/**
|
|
98
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
httpClient;
|
|
191
|
+
#isLoading = false;
|
|
192
|
+
#isInitialized = false;
|
|
193
|
+
#persistStateKey;
|
|
194
|
+
#persistRequestsKey;
|
|
195
|
+
#store;
|
|
196
|
+
#keepDuplicateUrls;
|
|
197
|
+
#sources;
|
|
198
|
+
#sourcesFunction;
|
|
199
|
+
#proxyConfiguration;
|
|
200
|
+
#httpClient;
|
|
118
201
|
/**
|
|
119
202
|
* To create new instance of `RequestList` we need to use `RequestList.open()` factory method.
|
|
120
203
|
* @param options All `RequestList` configuration options
|
|
121
204
|
* @internal
|
|
122
205
|
*/
|
|
123
206
|
constructor(options = {}) {
|
|
124
|
-
const { sources, sourcesFunction, persistStateKey, persistRequestsKey, state, proxyConfiguration, keepDuplicateUrls
|
|
207
|
+
const { sources, sourcesFunction, persistStateKey, persistRequestsKey, state, proxyConfiguration, keepDuplicateUrls, httpClient, } = parseArgument(options, requestListOptionsSchema);
|
|
125
208
|
if (!(sources || sourcesFunction)) {
|
|
126
|
-
throw new
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
+
});
|
|
146
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.
|
|
147
|
-
this
|
|
237
|
+
this.#keepDuplicateUrls = keepDuplicateUrls;
|
|
148
238
|
// Will be empty after initialization to save memory.
|
|
149
|
-
this
|
|
150
|
-
this
|
|
239
|
+
this.#sources = sources ? [...sources] : [];
|
|
240
|
+
this.#sourcesFunction = sourcesFunction;
|
|
151
241
|
// The proxy configuration used for `requestsFromUrl` requests.
|
|
152
|
-
this
|
|
242
|
+
this.#proxyConfiguration = proxyConfiguration;
|
|
153
243
|
}
|
|
154
244
|
/**
|
|
155
245
|
* Loads all remote sources of URLs and potentially starts periodic state persistence.
|
|
156
246
|
* This function must be called before you can start using the instance in a meaningful way.
|
|
157
247
|
*/
|
|
158
|
-
async initialize() {
|
|
159
|
-
if (this
|
|
248
|
+
async #initialize() {
|
|
249
|
+
if (this.#isLoading) {
|
|
160
250
|
throw new Error('RequestList sources are already loading or were loaded.');
|
|
161
251
|
}
|
|
162
|
-
this
|
|
252
|
+
this.#isLoading = true;
|
|
163
253
|
await purgeDefaultStorages({ onlyPurgeOnce: true });
|
|
164
|
-
|
|
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
|
+
}
|
|
165
260
|
// Add persisted requests / new sources in a memory efficient way because with very
|
|
166
261
|
// large lists, we were running out of memory.
|
|
167
262
|
if (persistedRequests) {
|
|
168
|
-
await this
|
|
263
|
+
await this.#addPersistedRequests(persistedRequests);
|
|
169
264
|
}
|
|
170
265
|
else {
|
|
171
|
-
await this
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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();
|
|
180
276
|
return this;
|
|
181
277
|
}
|
|
182
278
|
/**
|
|
@@ -184,17 +280,18 @@ export class RequestList {
|
|
|
184
280
|
* This needs to be done in a memory efficient way. We should update the input
|
|
185
281
|
* to a Stream once apify-client supports streams.
|
|
186
282
|
*/
|
|
187
|
-
async
|
|
283
|
+
async #addPersistedRequests(persistedRequests) {
|
|
188
284
|
// We don't need the sources so we purge them to
|
|
189
285
|
// prevent them from hanging in memory.
|
|
190
|
-
for (let i = 0; i < this
|
|
191
|
-
delete
|
|
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];
|
|
192
289
|
}
|
|
193
|
-
this
|
|
290
|
+
this.#sources = [];
|
|
194
291
|
this.areRequestsPersisted = true;
|
|
195
292
|
const requestStream = createDeserialize(persistedRequests);
|
|
196
293
|
for await (const request of requestStream) {
|
|
197
|
-
this
|
|
294
|
+
this.#addRequest(request);
|
|
198
295
|
}
|
|
199
296
|
}
|
|
200
297
|
/**
|
|
@@ -203,32 +300,36 @@ export class RequestList {
|
|
|
203
300
|
* We need to avoid keeping both sources and requests in memory
|
|
204
301
|
* to reduce memory footprint with very large sources.
|
|
205
302
|
*/
|
|
206
|
-
async
|
|
303
|
+
async #addRequestsFromSources() {
|
|
207
304
|
// We'll load all sources in sequence to ensure that they get loaded in the right order.
|
|
208
|
-
const sourcesCount = this
|
|
305
|
+
const sourcesCount = this.#sources.length;
|
|
209
306
|
for (let i = 0; i < sourcesCount; i++) {
|
|
210
|
-
const source = this
|
|
307
|
+
const source = this.#sources[i];
|
|
211
308
|
// Using delete here to drop the original object ASAP to free memory
|
|
212
309
|
// .pop would reverse the array and .shift is SLOW.
|
|
213
|
-
delete
|
|
310
|
+
// oxlint-disable-next-line typescript/no-array-delete
|
|
311
|
+
delete this.#sources[i];
|
|
214
312
|
if (typeof source === 'object' && source.requestsFromUrl) {
|
|
215
|
-
const fetchedRequests = await this
|
|
216
|
-
await this
|
|
313
|
+
const fetchedRequests = await this.#fetchRequestsFromUrl(source);
|
|
314
|
+
await this.#addFetchedRequests(source, fetchedRequests);
|
|
217
315
|
}
|
|
218
316
|
else {
|
|
219
|
-
this
|
|
317
|
+
this.#addRequest(source);
|
|
220
318
|
}
|
|
221
319
|
}
|
|
222
320
|
// Drop the original array full of empty indexes.
|
|
223
|
-
this
|
|
224
|
-
if (this
|
|
321
|
+
this.#sources = [];
|
|
322
|
+
if (this.#sourcesFunction) {
|
|
225
323
|
try {
|
|
226
|
-
const sourcesFromFunction = await this
|
|
324
|
+
const sourcesFromFunction = await this.#sourcesFunction();
|
|
227
325
|
const sourcesFromFunctionCount = sourcesFromFunction.length;
|
|
228
326
|
for (let i = 0; i < sourcesFromFunctionCount; i++) {
|
|
229
|
-
const source = sourcesFromFunction
|
|
230
|
-
|
|
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);
|
|
231
331
|
}
|
|
332
|
+
sourcesFromFunction.length = 0;
|
|
232
333
|
}
|
|
233
334
|
catch (e) {
|
|
234
335
|
const err = e;
|
|
@@ -240,155 +341,74 @@ export class RequestList {
|
|
|
240
341
|
* @inheritDoc
|
|
241
342
|
*/
|
|
242
343
|
async persistState() {
|
|
243
|
-
if (!this
|
|
344
|
+
if (!this.#persistStateKey) {
|
|
244
345
|
throw new Error('Cannot persist state. options.persistStateKey is not set.');
|
|
245
346
|
}
|
|
246
|
-
if (this.isStatePersisted)
|
|
247
|
-
return;
|
|
248
347
|
try {
|
|
249
|
-
|
|
250
|
-
await this.store.setValue(this.persistStateKey, this.getState());
|
|
251
|
-
this.isStatePersisted = true;
|
|
348
|
+
await this.#state.persistState();
|
|
252
349
|
}
|
|
253
350
|
catch (e) {
|
|
254
351
|
const err = e;
|
|
255
|
-
this
|
|
352
|
+
this.#log.exception(err, 'Attempted to persist state, but failed.');
|
|
256
353
|
}
|
|
257
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
|
+
}
|
|
258
363
|
/**
|
|
259
364
|
* Unlike persistState(), this is used only internally, since the sources
|
|
260
365
|
* are automatically persisted at RequestList initialization (if the persistRequestsKey is set),
|
|
261
366
|
* but there's no reason to persist it again afterwards, because RequestList is immutable.
|
|
262
367
|
*/
|
|
263
|
-
async
|
|
368
|
+
async #persistRequests() {
|
|
264
369
|
const serializedRequests = await serializeArray(this.requests);
|
|
265
|
-
this
|
|
266
|
-
await this
|
|
370
|
+
this.#store ??= await KeyValueStore.open();
|
|
371
|
+
await this.#store.setValue(this.#persistRequestsKey, serializedRequests, { contentType: CONTENT_TYPE_BINARY });
|
|
267
372
|
this.areRequestsPersisted = true;
|
|
268
373
|
}
|
|
269
|
-
/**
|
|
270
|
-
* Restores RequestList state from a state object.
|
|
271
|
-
*/
|
|
272
|
-
_restoreState(state) {
|
|
273
|
-
// If there's no state it means we've not persisted any (yet).
|
|
274
|
-
if (!state)
|
|
275
|
-
return;
|
|
276
|
-
// Restore previous state.
|
|
277
|
-
if (typeof state.nextIndex !== 'number' || state.nextIndex < 0) {
|
|
278
|
-
throw new Error('The state object is invalid: nextIndex must be a non-negative number.');
|
|
279
|
-
}
|
|
280
|
-
if (state.nextIndex > this.requests.length) {
|
|
281
|
-
throw new Error('The state object is not consistent with RequestList, too few requests loaded.');
|
|
282
|
-
}
|
|
283
|
-
if (state.nextIndex < this.requests.length &&
|
|
284
|
-
this.requests[state.nextIndex].uniqueKey !== state.nextUniqueKey) {
|
|
285
|
-
throw new Error('The state object is not consistent with RequestList the order of URLs seems to have changed.');
|
|
286
|
-
}
|
|
287
|
-
const deleteFromInProgress = [];
|
|
288
|
-
state.inProgress.forEach((uniqueKey) => {
|
|
289
|
-
const index = this.uniqueKeyToIndex[uniqueKey];
|
|
290
|
-
if (typeof index !== 'number') {
|
|
291
|
-
throw new Error('The state object is not consistent with RequestList. Unknown uniqueKey is present in the state.');
|
|
292
|
-
}
|
|
293
|
-
if (index >= state.nextIndex) {
|
|
294
|
-
deleteFromInProgress.push(uniqueKey);
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
this.nextIndex = state.nextIndex;
|
|
298
|
-
this.inProgress = new Set(state.inProgress);
|
|
299
|
-
// WORKAROUND:
|
|
300
|
-
// It happened to some users that state object contained something like:
|
|
301
|
-
// {
|
|
302
|
-
// "nextIndex": 11308,
|
|
303
|
-
// "nextUniqueKey": "https://www.anychart.com",
|
|
304
|
-
// "inProgress": {
|
|
305
|
-
// "https://www.ams360.com": true,
|
|
306
|
-
// ...
|
|
307
|
-
// "https://www.anychart.com": true,
|
|
308
|
-
// }
|
|
309
|
-
// Which then caused error "The request is not being processed (uniqueKey: https://www.anychart.com)"
|
|
310
|
-
// As a workaround, we just remove all inProgress requests whose index >= nextIndex,
|
|
311
|
-
// since they will be crawled again.
|
|
312
|
-
if (deleteFromInProgress.length) {
|
|
313
|
-
this.log.warning("RequestList's in-progress field is not consistent, skipping invalid in-progress entries", {
|
|
314
|
-
deleteFromInProgress,
|
|
315
|
-
});
|
|
316
|
-
for (const uniqueKey of deleteFromInProgress) {
|
|
317
|
-
this.inProgress.delete(uniqueKey);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
// All in-progress requests need to be re-crawled
|
|
321
|
-
this.reclaimed = new Set(this.inProgress);
|
|
322
|
-
}
|
|
323
|
-
/**
|
|
324
|
-
* Attempts to load state and requests using the `RequestList` configuration
|
|
325
|
-
* and returns a tuple of [state, requests] where each may be null if not loaded.
|
|
326
|
-
*/
|
|
327
|
-
async _loadStateAndPersistedRequests() {
|
|
328
|
-
let state;
|
|
329
|
-
let persistedRequests;
|
|
330
|
-
if (this.initialState) {
|
|
331
|
-
state = this.initialState;
|
|
332
|
-
this.log.debug('Loaded state from options.state argument.');
|
|
333
|
-
}
|
|
334
|
-
else if (this.persistStateKey) {
|
|
335
|
-
state = await this._getPersistedState(this.persistStateKey);
|
|
336
|
-
if (state)
|
|
337
|
-
this.log.debug('Loaded state from key value store using the persistStateKey.');
|
|
338
|
-
}
|
|
339
|
-
if (this.persistRequestsKey) {
|
|
340
|
-
persistedRequests = await this._getPersistedState(this.persistRequestsKey);
|
|
341
|
-
if (persistedRequests)
|
|
342
|
-
this.log.debug('Loaded requests from key value store using the persistRequestsKey.');
|
|
343
|
-
}
|
|
344
|
-
return [state, persistedRequests];
|
|
345
|
-
}
|
|
346
374
|
/**
|
|
347
375
|
* Returns an object representing the internal state of the `RequestList` instance.
|
|
348
376
|
* Note that the object's fields can change in future releases.
|
|
349
377
|
*/
|
|
350
378
|
getState() {
|
|
351
|
-
this
|
|
352
|
-
return
|
|
353
|
-
nextIndex: this.nextIndex,
|
|
354
|
-
nextUniqueKey: this.nextIndex < this.requests.length ? this.requests[this.nextIndex].uniqueKey : null,
|
|
355
|
-
inProgress: [...this.inProgress],
|
|
356
|
-
};
|
|
379
|
+
this.#ensureIsInitialized();
|
|
380
|
+
return this.#stateCodec.encode(this.#state.currentValue);
|
|
357
381
|
}
|
|
358
382
|
/**
|
|
359
383
|
* @inheritDoc
|
|
360
384
|
*/
|
|
361
|
-
async
|
|
362
|
-
this
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
async isFinished() {
|
|
369
|
-
this._ensureIsInitialized();
|
|
370
|
-
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' };
|
|
371
392
|
}
|
|
372
393
|
/**
|
|
373
394
|
* @inheritDoc
|
|
374
395
|
*/
|
|
375
396
|
async fetchNextRequest() {
|
|
376
|
-
this
|
|
377
|
-
// First
|
|
378
|
-
const uniqueKey = this.
|
|
397
|
+
this.#ensureIsInitialized();
|
|
398
|
+
// First re-serve any requests that were interrupted before the last state persist.
|
|
399
|
+
const uniqueKey = this.#requestsToRetry.shift();
|
|
379
400
|
if (uniqueKey) {
|
|
380
|
-
this
|
|
381
|
-
|
|
382
|
-
return this.ensureRequest(this.requests[index], index);
|
|
401
|
+
const index = this.#uniqueKeyToIndex[uniqueKey];
|
|
402
|
+
return this.#ensureRequest(this.requests[index], index);
|
|
383
403
|
}
|
|
384
404
|
// Otherwise return next request.
|
|
385
|
-
|
|
386
|
-
|
|
405
|
+
const state = this.#state.currentValue;
|
|
406
|
+
if (state.nextIndex < this.requests.length) {
|
|
407
|
+
const index = state.nextIndex;
|
|
387
408
|
const request = this.requests[index];
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
this
|
|
391
|
-
return this.ensureRequest(request, index);
|
|
409
|
+
state.inProgress.add(request.uniqueKey);
|
|
410
|
+
state.nextIndex++;
|
|
411
|
+
return this.#ensureRequest(request, index);
|
|
392
412
|
}
|
|
393
413
|
return null;
|
|
394
414
|
}
|
|
@@ -403,7 +423,7 @@ export class RequestList {
|
|
|
403
423
|
yield req;
|
|
404
424
|
}
|
|
405
425
|
}
|
|
406
|
-
ensureRequest(requestLike, index) {
|
|
426
|
+
#ensureRequest(requestLike, index) {
|
|
407
427
|
if (requestLike instanceof Request) {
|
|
408
428
|
return requestLike;
|
|
409
429
|
}
|
|
@@ -413,34 +433,23 @@ export class RequestList {
|
|
|
413
433
|
/**
|
|
414
434
|
* @inheritDoc
|
|
415
435
|
*/
|
|
416
|
-
async
|
|
436
|
+
async markRequestAsHandled(request) {
|
|
417
437
|
const { uniqueKey } = request;
|
|
418
|
-
this
|
|
419
|
-
this
|
|
420
|
-
this
|
|
438
|
+
this.#ensureUniqueKeyValid(uniqueKey);
|
|
439
|
+
this.#ensureInProgress(uniqueKey);
|
|
440
|
+
this.#ensureIsInitialized();
|
|
421
441
|
this.inProgress.delete(uniqueKey);
|
|
422
|
-
this.isStatePersisted = false;
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* @inheritDoc
|
|
426
|
-
*/
|
|
427
|
-
async reclaimRequest(request) {
|
|
428
|
-
const { uniqueKey } = request;
|
|
429
|
-
this._ensureUniqueKeyValid(uniqueKey);
|
|
430
|
-
this._ensureInProgressAndNotReclaimed(uniqueKey);
|
|
431
|
-
this._ensureIsInitialized();
|
|
432
|
-
this.reclaimed.add(uniqueKey);
|
|
433
442
|
}
|
|
434
443
|
/**
|
|
435
444
|
* Adds all fetched requests from a URL from a remote resource.
|
|
436
445
|
*/
|
|
437
|
-
async
|
|
446
|
+
async #addFetchedRequests(source, fetchedRequests) {
|
|
438
447
|
const { requestsFromUrl, regex } = source;
|
|
439
448
|
const originalLength = this.requests.length;
|
|
440
|
-
fetchedRequests.forEach((request) => this
|
|
449
|
+
fetchedRequests.forEach((request) => this.#addRequest(request));
|
|
441
450
|
const fetchedCount = fetchedRequests.length;
|
|
442
451
|
const importedCount = this.requests.length - originalLength;
|
|
443
|
-
this
|
|
452
|
+
this.#log.info('Fetched and loaded Requests from a remote resource.', {
|
|
444
453
|
requestsFromUrl,
|
|
445
454
|
regex,
|
|
446
455
|
fetchedCount,
|
|
@@ -449,23 +458,23 @@ export class RequestList {
|
|
|
449
458
|
sample: JSON.stringify(fetchedRequests.slice(0, 5)),
|
|
450
459
|
});
|
|
451
460
|
}
|
|
452
|
-
async
|
|
453
|
-
this
|
|
454
|
-
const state = await this
|
|
461
|
+
async #getPersistedState(key) {
|
|
462
|
+
this.#store ??= await KeyValueStore.open();
|
|
463
|
+
const state = await this.#store.getValue(key);
|
|
455
464
|
return state;
|
|
456
465
|
}
|
|
457
466
|
/**
|
|
458
467
|
* Fetches URLs from requestsFromUrl and returns them in format of list of requests
|
|
459
468
|
*/
|
|
460
|
-
async
|
|
469
|
+
async #fetchRequestsFromUrl(source) {
|
|
461
470
|
const { requestsFromUrl, regex, ...sharedOpts } = source;
|
|
462
471
|
// Download remote resource and parse URLs.
|
|
463
472
|
let urlsArr;
|
|
464
473
|
try {
|
|
465
|
-
urlsArr = await this
|
|
474
|
+
urlsArr = await this.#downloadListOfUrls({
|
|
466
475
|
url: requestsFromUrl,
|
|
467
476
|
urlRegExp: regex,
|
|
468
|
-
proxyUrl: await this
|
|
477
|
+
proxyUrl: (await this.#proxyConfiguration?.newProxyInfo())?.url,
|
|
469
478
|
});
|
|
470
479
|
}
|
|
471
480
|
catch (err) {
|
|
@@ -473,7 +482,7 @@ export class RequestList {
|
|
|
473
482
|
}
|
|
474
483
|
// Skip if resource contained no URLs.
|
|
475
484
|
if (!urlsArr.length) {
|
|
476
|
-
this
|
|
485
|
+
this.#log.warning('The fetched list contains no valid URLs.', { requestsFromUrl, regex });
|
|
477
486
|
return [];
|
|
478
487
|
}
|
|
479
488
|
return urlsArr.map((url) => ({ url, ...sharedOpts }));
|
|
@@ -483,7 +492,7 @@ export class RequestList {
|
|
|
483
492
|
* If the `source` parameter is a string or plain object and not an instance
|
|
484
493
|
* of a `Request`, then the function creates a `Request` instance.
|
|
485
494
|
*/
|
|
486
|
-
|
|
495
|
+
#addRequest(source) {
|
|
487
496
|
let request;
|
|
488
497
|
const type = typeof source;
|
|
489
498
|
if (type === 'string') {
|
|
@@ -501,61 +510,76 @@ export class RequestList {
|
|
|
501
510
|
const hasUniqueKey = Reflect.has(Object(source), 'uniqueKey');
|
|
502
511
|
request.uniqueKey ??= Request.computeUniqueKey(request);
|
|
503
512
|
// Add index to uniqueKey if duplicates are to be kept
|
|
504
|
-
if (this
|
|
513
|
+
if (this.#keepDuplicateUrls && !hasUniqueKey) {
|
|
505
514
|
request.uniqueKey += `-${this.requests.length}`;
|
|
506
515
|
}
|
|
507
516
|
const { uniqueKey } = request;
|
|
508
|
-
this
|
|
517
|
+
this.#ensureUniqueKeyValid(uniqueKey);
|
|
509
518
|
// Skip requests with duplicate uniqueKey
|
|
510
|
-
if (!Object.hasOwn(this
|
|
511
|
-
this
|
|
519
|
+
if (!Object.hasOwn(this.#uniqueKeyToIndex, uniqueKey)) {
|
|
520
|
+
this.#uniqueKeyToIndex[uniqueKey] = this.requests.length;
|
|
512
521
|
this.requests.push(request);
|
|
513
522
|
}
|
|
514
|
-
else if (this
|
|
515
|
-
this
|
|
523
|
+
else if (this.#keepDuplicateUrls) {
|
|
524
|
+
this.#log.warning(`Duplicate uniqueKey: ${uniqueKey} found while the keepDuplicateUrls option was set. Check your sources' unique keys.`);
|
|
516
525
|
}
|
|
517
526
|
}
|
|
518
527
|
/**
|
|
519
528
|
* Helper function that validates unique key.
|
|
520
529
|
* Throws an error if uniqueKey is not a non-empty string.
|
|
521
530
|
*/
|
|
522
|
-
|
|
531
|
+
#ensureUniqueKeyValid(uniqueKey) {
|
|
523
532
|
if (typeof uniqueKey !== 'string' || !uniqueKey) {
|
|
524
533
|
throw new Error("Request object's uniqueKey must be a non-empty string");
|
|
525
534
|
}
|
|
526
535
|
}
|
|
527
536
|
/**
|
|
528
|
-
* Checks that request is
|
|
537
|
+
* Checks that a request is currently being processed and throws an error if not.
|
|
529
538
|
*/
|
|
530
|
-
|
|
539
|
+
#ensureInProgress(uniqueKey) {
|
|
531
540
|
if (!this.inProgress.has(uniqueKey)) {
|
|
532
541
|
throw new Error(`The request is not being processed (uniqueKey: ${uniqueKey})`);
|
|
533
542
|
}
|
|
534
|
-
if (this.reclaimed.has(uniqueKey)) {
|
|
535
|
-
throw new Error(`The request was already reclaimed (uniqueKey: ${uniqueKey})`);
|
|
536
|
-
}
|
|
537
543
|
}
|
|
538
544
|
/**
|
|
539
545
|
* Throws an error if request list wasn't initialized.
|
|
540
546
|
*/
|
|
541
|
-
|
|
542
|
-
if (!this
|
|
547
|
+
#ensureIsInitialized() {
|
|
548
|
+
if (!this.#isInitialized) {
|
|
543
549
|
throw new Error('RequestList is not initialized; you must call "await requestList.initialize()" before using it!');
|
|
544
550
|
}
|
|
545
551
|
}
|
|
546
552
|
/**
|
|
547
553
|
* Returns the total number of unique requests present in the `RequestList`.
|
|
548
554
|
*/
|
|
549
|
-
|
|
550
|
-
this
|
|
555
|
+
async getTotalCount() {
|
|
556
|
+
this.#ensureIsInitialized();
|
|
551
557
|
return this.requests.length;
|
|
552
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
|
+
}
|
|
553
577
|
/**
|
|
554
578
|
* @inheritDoc
|
|
555
579
|
*/
|
|
556
|
-
|
|
557
|
-
this
|
|
558
|
-
return this.nextIndex - this.inProgress.size;
|
|
580
|
+
async getHandledCount() {
|
|
581
|
+
this.#ensureIsInitialized();
|
|
582
|
+
return this.#state.currentValue.nextIndex - this.inProgress.size;
|
|
559
583
|
}
|
|
560
584
|
/**
|
|
561
585
|
* Opens a request list and returns a promise resolving to an instance
|
|
@@ -614,30 +638,29 @@ export class RequestList {
|
|
|
614
638
|
if (listNameOrOptions != null && typeof listNameOrOptions === 'object') {
|
|
615
639
|
options = { ...listNameOrOptions, ...options };
|
|
616
640
|
const rl = new RequestList(options);
|
|
617
|
-
await rl
|
|
641
|
+
await rl.#initialize();
|
|
618
642
|
return rl;
|
|
619
643
|
}
|
|
620
644
|
const listName = listNameOrOptions;
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
645
|
+
parseArgument(listName, listNameSchema);
|
|
646
|
+
parseArgument(sources, schemas.anyArray);
|
|
647
|
+
parseArgument(options, openOptionsSchema);
|
|
624
648
|
const rl = new RequestList({
|
|
625
649
|
...options,
|
|
626
650
|
persistStateKey: listName ? `${listName}-${STATE_PERSISTENCE_KEY}` : options.persistStateKey,
|
|
627
651
|
persistRequestsKey: listName ? `${listName}-${REQUESTS_PERSISTENCE_KEY}` : options.persistRequestsKey,
|
|
628
652
|
sources: sources ?? options.sources,
|
|
629
653
|
});
|
|
630
|
-
await rl
|
|
654
|
+
await rl.#initialize();
|
|
631
655
|
return rl;
|
|
632
656
|
}
|
|
633
657
|
/**
|
|
634
658
|
* @internal wraps public utility for mocking purposes
|
|
635
659
|
*/
|
|
636
|
-
async
|
|
660
|
+
async #downloadListOfUrls(options) {
|
|
637
661
|
return downloadListOfUrls({
|
|
638
662
|
...options,
|
|
639
|
-
httpClient: this
|
|
663
|
+
httpClient: this.#httpClient,
|
|
640
664
|
});
|
|
641
665
|
}
|
|
642
666
|
}
|
|
643
|
-
//# sourceMappingURL=request_list.js.map
|