@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.
- package/autoscaling/autoscaled_pool.d.ts +3 -21
- package/autoscaling/autoscaled_pool.js +105 -97
- package/autoscaling/concurrency_system.d.ts +7 -22
- package/autoscaling/concurrency_system.js +112 -100
- package/autoscaling/cpu_load_signal.d.ts +1 -2
- package/autoscaling/cpu_load_signal.js +11 -10
- package/autoscaling/event_loop_load_signal.d.ts +1 -4
- package/autoscaling/event_loop_load_signal.js +18 -18
- package/autoscaling/index.d.ts +1 -1
- package/autoscaling/index.js +1 -1
- package/autoscaling/load_signal.d.ts +8 -7
- package/autoscaling/load_signal.js +14 -12
- package/autoscaling/memory_load_signal.d.ts +3 -12
- package/autoscaling/memory_load_signal.js +41 -41
- package/autoscaling/snapshotter.d.ts +7 -10
- package/autoscaling/snapshotter.js +18 -18
- package/autoscaling/{client_load_signal.d.ts → storage_backend_load_signal.d.ts} +14 -17
- package/autoscaling/{client_load_signal.js → storage_backend_load_signal.js} +27 -27
- package/autoscaling/system_status.d.ts +9 -11
- package/autoscaling/system_status.js +13 -13
- package/configuration.d.ts +16 -16
- package/configuration.js +6 -6
- package/crawlers/context_pipeline.js +6 -6
- package/crawlers/crawler_commons.d.ts +14 -112
- package/crawlers/crawler_commons.js +1 -107
- package/crawlers/error_snapshotter.d.ts +2 -4
- package/crawlers/error_snapshotter.js +5 -5
- package/crawlers/index.d.ts +1 -1
- package/crawlers/index.js +0 -1
- package/crawlers/statistics.d.ts +164 -45
- package/crawlers/statistics.js +339 -153
- package/debug.js +4 -4
- package/enqueue_links/enqueue_links.d.ts +51 -159
- package/enqueue_links/enqueue_links.js +38 -231
- package/enqueue_links/shared.d.ts +34 -24
- package/enqueue_links/shared.js +54 -36
- package/errors.d.ts +43 -12
- package/errors.js +48 -9
- package/events/event_manager.d.ts +2 -2
- package/events/event_manager.js +7 -6
- package/events/local_event_manager.d.ts +1 -1
- package/events/local_event_manager.js +5 -5
- package/http.d.ts +9 -0
- package/http.js +28 -0
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/log.js +5 -1
- package/memory-storage/memory-storage.d.ts +3 -11
- package/memory-storage/memory-storage.js +42 -48
- package/memory-storage/resource-clients/dataset.d.ts +1 -1
- package/memory-storage/resource-clients/dataset.js +8 -13
- package/memory-storage/resource-clients/key-value-store.d.ts +1 -1
- package/memory-storage/resource-clients/key-value-store.js +36 -38
- package/memory-storage/resource-clients/request-queue.d.ts +4 -23
- package/memory-storage/resource-clients/request-queue.js +68 -80
- package/owned_or_injected.d.ts +1 -3
- package/owned_or_injected.js +17 -17
- package/package.json +8 -9
- package/proxy_configuration.d.ts +1 -3
- package/proxy_configuration.js +18 -14
- package/recoverable_state.d.ts +80 -45
- package/recoverable_state.js +163 -75
- package/request.d.ts +3 -4
- package/request.js +57 -44
- package/router.d.ts +6 -9
- package/router.js +23 -23
- package/serialization.js +14 -13
- package/service_locator.d.ts +19 -10
- package/service_locator.js +57 -48
- package/session_pool/session.d.ts +3 -14
- package/session_pool/session.js +76 -74
- package/session_pool/session_pool.d.ts +6 -12
- package/session_pool/session_pool.js +85 -77
- package/storages/batched_adds.d.ts +37 -0
- package/storages/batched_adds.js +73 -0
- package/storages/dataset.d.ts +12 -1
- package/storages/dataset.js +133 -32
- package/storages/index.d.ts +2 -1
- package/storages/index.js +2 -1
- package/storages/key_value_store.d.ts +19 -4
- package/storages/key_value_store.js +204 -75
- package/storages/request_dedup_cache.d.ts +1 -2
- package/storages/request_dedup_cache.js +9 -9
- package/storages/request_list.d.ts +4 -23
- package/storages/request_list.js +103 -96
- package/storages/request_loader.d.ts +5 -0
- package/storages/request_manager_tandem.d.ts +1 -10
- package/storages/request_manager_tandem.js +27 -27
- package/storages/request_queue.d.ts +21 -18
- package/storages/request_queue.js +318 -177
- package/storages/sitemap_request_loader.d.ts +22 -70
- package/storages/sitemap_request_loader.js +122 -116
- 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/throttling_request_manager.d.ts +239 -0
- package/storages/throttling_request_manager.js +646 -0
- package/storages/transaction.d.ts +252 -0
- package/storages/transaction.js +251 -0
- package/storages/utils.d.ts +6 -3
- package/storages/utils.js +11 -2
- package/system-info/runtime.js +7 -7
- package/url.d.ts +9 -0
- package/url.js +11 -0
- package/validators.d.ts +22 -25
- package/validators.js +13 -25
- package/storages/access_checking.d.ts +0 -12
- package/storages/access_checking.js +0 -17
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { inspect } from 'node:util';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { isAsyncIterable, isIterable } from '@crawlee/utils/internal';
|
|
3
|
+
import { downloadListOfUrls } from '@crawlee/utils';
|
|
4
|
+
import { z } from 'zod';
|
|
4
5
|
import { LruCache } from '@apify/datastructures';
|
|
6
|
+
import { tryCancel } from '@apify/timeout';
|
|
5
7
|
import { Configuration } from '../configuration.js';
|
|
6
8
|
import { getObjectType } from '../debug.js';
|
|
7
|
-
import {
|
|
9
|
+
import { EventType } from '../events/event_manager.js';
|
|
8
10
|
import { Request } from '../request.js';
|
|
9
11
|
import { serviceLocator } from '../service_locator.js';
|
|
10
|
-
import {
|
|
12
|
+
import { parseArgument, schemas, validators } from '../validators.js';
|
|
13
|
+
import { activeStorageTransaction, rejectOperationInTransaction } from './transaction.js';
|
|
14
|
+
import { drainRequestBatches } from './batched_adds.js';
|
|
11
15
|
import { StorageStatsTracker } from './storage_stats.js';
|
|
12
16
|
import { resolveStorageIdentifier } from './storage_instance_manager.js';
|
|
13
17
|
import { getRequestId, purgeDefaultStorages } from './utils.js';
|
|
@@ -17,6 +21,43 @@ import { RequestDeduplicationCache } from './request_dedup_cache.js';
|
|
|
17
21
|
* @internal
|
|
18
22
|
*/
|
|
19
23
|
const MAX_CACHED_REQUESTS = 2_000_000;
|
|
24
|
+
const iterableSchema = z.custom((value) => isIterable(value) || isAsyncIterable(value), {
|
|
25
|
+
error: (issue) => `Expected an iterable or async iterable, got ${getObjectType(issue.input)}`,
|
|
26
|
+
});
|
|
27
|
+
const operationOptionsSchema = z.strictObject({
|
|
28
|
+
forefront: z.boolean().default(false),
|
|
29
|
+
});
|
|
30
|
+
const addRequestsOptionsSchema = z.strictObject({
|
|
31
|
+
forefront: z.boolean().default(false),
|
|
32
|
+
cache: z.boolean().default(true),
|
|
33
|
+
});
|
|
34
|
+
const addRequestsBatchedOptionsSchema = z.strictObject({
|
|
35
|
+
forefront: z.boolean().optional(),
|
|
36
|
+
waitForAllRequestsToBeAdded: z.boolean().default(false),
|
|
37
|
+
batchSize: schemas.anyNumber.default(1000),
|
|
38
|
+
waitBetweenBatchesMillis: schemas.anyNumber.default(1000),
|
|
39
|
+
maxNewRequests: schemas.anyNumber.optional(),
|
|
40
|
+
});
|
|
41
|
+
const newRequestLikeSchema = z.looseObject({
|
|
42
|
+
url: z.string(),
|
|
43
|
+
id: z.undefined().optional(),
|
|
44
|
+
});
|
|
45
|
+
const handledRequestSchema = z.looseObject({
|
|
46
|
+
id: z.string(),
|
|
47
|
+
uniqueKey: z.string(),
|
|
48
|
+
handledAt: z.string().optional(),
|
|
49
|
+
});
|
|
50
|
+
const reclaimedRequestSchema = z.looseObject({
|
|
51
|
+
id: z.string(),
|
|
52
|
+
uniqueKey: z.string(),
|
|
53
|
+
});
|
|
54
|
+
const uniqueKeySchema = z.string();
|
|
55
|
+
const openOptionsSchema = z.strictObject({
|
|
56
|
+
configuration: z.instanceof(Configuration).optional(),
|
|
57
|
+
storageBackend: validators.storageBackend.optional(),
|
|
58
|
+
proxyConfiguration: validators.proxyConfiguration.optional(),
|
|
59
|
+
httpClient: schemas.httpClient.optional(),
|
|
60
|
+
});
|
|
20
61
|
/**
|
|
21
62
|
* Represents a queue of URLs to crawl, which is used for deep crawling of websites
|
|
22
63
|
* where you start with several URLs and then recursively
|
|
@@ -55,26 +96,28 @@ export class RequestQueue {
|
|
|
55
96
|
id;
|
|
56
97
|
name;
|
|
57
98
|
backend;
|
|
58
|
-
proxyConfiguration;
|
|
99
|
+
#proxyConfiguration;
|
|
59
100
|
log;
|
|
101
|
+
// kept as TS-private: request_queue tests read this cache directly
|
|
60
102
|
requestCache;
|
|
61
103
|
/**
|
|
62
104
|
* Remembers the `requestId` of every request already submitted to the client — including background
|
|
63
105
|
* batches that `requestCache` skips — so overlapping URL sets aren't re-submitted.
|
|
64
106
|
* See {@link RequestDeduplicationCache} for why this is a separate, cheaper cache.
|
|
65
107
|
*/
|
|
66
|
-
requestSeenCache;
|
|
67
|
-
queuePausedForMigration = false;
|
|
108
|
+
#requestSeenCache;
|
|
109
|
+
#queuePausedForMigration = false;
|
|
110
|
+
// kept as TS-private: packages/core/test request-queue tests write this counter directly
|
|
68
111
|
inProgressRequestBatchCount = 0;
|
|
69
112
|
/**
|
|
70
113
|
* The largest expected request-processing time (in seconds) seen so far via
|
|
71
114
|
* {@link setExpectedRequestProcessingTimeSecs}. Used to ensure that value is only ever raised, never
|
|
72
115
|
* lowered, before being forwarded to the storage backend.
|
|
73
116
|
*/
|
|
74
|
-
expectedRequestProcessingSecs = 0;
|
|
75
|
-
httpClient;
|
|
76
|
-
events;
|
|
77
|
-
statsTracker = new StorageStatsTracker({
|
|
117
|
+
#expectedRequestProcessingSecs = 0;
|
|
118
|
+
#httpClient;
|
|
119
|
+
#events;
|
|
120
|
+
#statsTracker = new StorageStatsTracker({
|
|
78
121
|
writeCount: 0,
|
|
79
122
|
headItemReadCount: 0,
|
|
80
123
|
});
|
|
@@ -83,7 +126,7 @@ export class RequestQueue {
|
|
|
83
126
|
* queue-head reads issued to the underlying storage backend). Counted per backend call.
|
|
84
127
|
*/
|
|
85
128
|
get stats() {
|
|
86
|
-
return this
|
|
129
|
+
return this.#statsTracker.current;
|
|
87
130
|
}
|
|
88
131
|
/**
|
|
89
132
|
* @internal
|
|
@@ -91,14 +134,14 @@ export class RequestQueue {
|
|
|
91
134
|
constructor(options) {
|
|
92
135
|
this.id = options.metadata.id;
|
|
93
136
|
this.name = options.metadata.name;
|
|
94
|
-
this
|
|
137
|
+
this.#events = serviceLocator.getEventManager();
|
|
95
138
|
this.backend = options.backend;
|
|
96
|
-
this
|
|
139
|
+
this.#proxyConfiguration = options.proxyConfiguration;
|
|
97
140
|
this.requestCache = new LruCache({ maxLength: MAX_CACHED_REQUESTS });
|
|
98
|
-
this
|
|
141
|
+
this.#requestSeenCache = new RequestDeduplicationCache();
|
|
99
142
|
this.log = serviceLocator.getLogger().child({ prefix: `RequestQueue(${this.id}, ${this.name ?? 'no-name'})` });
|
|
100
|
-
this
|
|
101
|
-
this
|
|
143
|
+
this.#events.on(EventType.MIGRATING, async () => {
|
|
144
|
+
this.#queuePausedForMigration = true;
|
|
102
145
|
});
|
|
103
146
|
}
|
|
104
147
|
/**
|
|
@@ -134,26 +177,24 @@ export class RequestQueue {
|
|
|
134
177
|
* @param [options] Request queue operation options.
|
|
135
178
|
*/
|
|
136
179
|
async addRequest(requestLike, options = {}) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
forefront: ow.optional.boolean,
|
|
141
|
-
}));
|
|
142
|
-
const { forefront = false } = options;
|
|
180
|
+
const transaction = activeStorageTransaction();
|
|
181
|
+
parseArgument(requestLike, schemas.anyObject);
|
|
182
|
+
const { forefront } = parseArgument(options, operationOptionsSchema);
|
|
143
183
|
if ('requestsFromUrl' in requestLike) {
|
|
144
184
|
const requests = await this.fetchRequestsFromUrl(requestLike);
|
|
145
185
|
const processedRequests = await this.addFetchedRequests(requestLike, requests, options);
|
|
146
186
|
return { ...processedRequests[0], forefront };
|
|
147
187
|
}
|
|
148
|
-
|
|
149
|
-
url: ow.string,
|
|
150
|
-
id: ow.undefined,
|
|
151
|
-
}));
|
|
188
|
+
parseArgument(requestLike, newRequestLikeSchema);
|
|
152
189
|
const request = requestLike instanceof Request ? requestLike : new Request(requestLike);
|
|
190
|
+
if (transaction?.policy.requestQueue === 'deferred') {
|
|
191
|
+
return this.addRequestDeferred(transaction, request, forefront);
|
|
192
|
+
}
|
|
153
193
|
const cacheKey = getRequestId(request.uniqueKey);
|
|
154
194
|
const cachedInfo = this.requestCache.get(cacheKey);
|
|
155
195
|
if (cachedInfo) {
|
|
156
196
|
request.id = cachedInfo.id;
|
|
197
|
+
this.recordRequestJournalEntry(transaction, [request], forefront, true);
|
|
157
198
|
return {
|
|
158
199
|
wasAlreadyPresent: true,
|
|
159
200
|
// We may assume that if request is in local cache then also the information if the
|
|
@@ -164,17 +205,163 @@ export class RequestQueue {
|
|
|
164
205
|
forefront,
|
|
165
206
|
};
|
|
166
207
|
}
|
|
167
|
-
this
|
|
208
|
+
this.#statsTracker.add('writeCount');
|
|
168
209
|
const { processedRequests } = await this.backend.addBatchOfRequests([request], { forefront });
|
|
210
|
+
this.recordRequestJournalEntry(transaction, [request], forefront, true);
|
|
169
211
|
const queueOperationInfo = {
|
|
170
212
|
...processedRequests[0],
|
|
171
213
|
uniqueKey: request.uniqueKey,
|
|
172
214
|
forefront,
|
|
173
215
|
};
|
|
174
216
|
this.cacheRequest(cacheKey, queueOperationInfo);
|
|
175
|
-
this
|
|
217
|
+
this.#requestSeenCache.add(cacheKey, request.id);
|
|
176
218
|
return queueOperationInfo;
|
|
177
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Journals an addition for introspection only; these entries are never replayed. A no-op unless the
|
|
222
|
+
* transaction is open, so detached and outliving writers stay out of the journal.
|
|
223
|
+
*/
|
|
224
|
+
recordRequestJournalEntry(transaction, requests, forefront, writeThrough) {
|
|
225
|
+
if (!transaction?.isActive || requests.length === 0)
|
|
226
|
+
return;
|
|
227
|
+
transaction.recordJournalEntry({
|
|
228
|
+
type: 'requestQueue',
|
|
229
|
+
participant: this,
|
|
230
|
+
requests: requests.map((request) => ({
|
|
231
|
+
url: request.url,
|
|
232
|
+
uniqueKey: request.uniqueKey,
|
|
233
|
+
label: request.label,
|
|
234
|
+
})),
|
|
235
|
+
forefront,
|
|
236
|
+
writeThrough,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* The requests buffered by the given transaction for this queue, keyed by `uniqueKey` — a dedup
|
|
241
|
+
* index derived from the transaction journal.
|
|
242
|
+
*/
|
|
243
|
+
bufferedRequests(transaction) {
|
|
244
|
+
const buffered = new Map();
|
|
245
|
+
// Only `deferred` records snapshots, so scanning the journal under `writeThrough` never finds any.
|
|
246
|
+
if (transaction.policy.requestQueue !== 'deferred')
|
|
247
|
+
return buffered;
|
|
248
|
+
for (const entry of transaction.journal) {
|
|
249
|
+
if (entry.type !== 'requestQueue' || entry.participant !== this)
|
|
250
|
+
continue;
|
|
251
|
+
for (const request of entry.requests) {
|
|
252
|
+
if (request.snapshot !== undefined)
|
|
253
|
+
buffered.set(request.uniqueKey, request.snapshot);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return buffered;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Adds a request under the `deferred` policy: journaled now, really added by the commit replay.
|
|
260
|
+
* A new request's `requestId` is the local `uniqueKey` hash and is **provisional** — never write it
|
|
261
|
+
* to `request.id` or the dedup caches. Dedup is cheapest-first: buffer, caches, then a backend probe.
|
|
262
|
+
*/
|
|
263
|
+
async addRequestDeferred(transaction, request, forefront, buffered = this.bufferedRequests(transaction)) {
|
|
264
|
+
// This transaction's own buffered adds; the shared caches never see them (provisional ids).
|
|
265
|
+
if (buffered.has(request.uniqueKey)) {
|
|
266
|
+
this.recordRequestJournalEntry(transaction, [request], forefront, false);
|
|
267
|
+
return {
|
|
268
|
+
wasAlreadyPresent: true,
|
|
269
|
+
wasAlreadyHandled: false,
|
|
270
|
+
requestId: getRequestId(request.uniqueKey),
|
|
271
|
+
uniqueKey: request.uniqueKey,
|
|
272
|
+
forefront,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
// The caches hold real backend ids. Only *writing* provisional ids to them would be wrong;
|
|
276
|
+
// reading saves a probe. Same lookup as the write-through path.
|
|
277
|
+
const cacheKey = getRequestId(request.uniqueKey);
|
|
278
|
+
const cachedInfo = this.requestCache.get(cacheKey);
|
|
279
|
+
const knownRequestId = cachedInfo?.id ?? this.#requestSeenCache.get(cacheKey);
|
|
280
|
+
if (knownRequestId) {
|
|
281
|
+
this.recordRequestJournalEntry(transaction, [request], forefront, false);
|
|
282
|
+
return {
|
|
283
|
+
wasAlreadyPresent: true,
|
|
284
|
+
// The dedup cache doesn't track the handled state; only the full record does.
|
|
285
|
+
wasAlreadyHandled: cachedInfo?.isHandled ?? false,
|
|
286
|
+
requestId: knownRequestId,
|
|
287
|
+
uniqueKey: request.uniqueKey,
|
|
288
|
+
forefront,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
// The caches are bounded, so a miss is not proof of absence - probe for an accurate answer.
|
|
292
|
+
const existing = await this.backend.getRequest(request.uniqueKey);
|
|
293
|
+
if (existing) {
|
|
294
|
+
this.recordRequestJournalEntry(transaction, [request], forefront, false);
|
|
295
|
+
return {
|
|
296
|
+
wasAlreadyPresent: true,
|
|
297
|
+
wasAlreadyHandled: existing.handledAt != null,
|
|
298
|
+
requestId: existing.id,
|
|
299
|
+
uniqueKey: request.uniqueKey,
|
|
300
|
+
forefront,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
// The entry below *is* the write, so a transaction closed during the probe must not receive it -
|
|
304
|
+
// pass through instead, per the closed-transaction rule. Under `deferred` that can land an
|
|
305
|
+
// addition a rollback would have discarded; dedup bounds that cost, silent loss is unbounded.
|
|
306
|
+
if (!transaction.isActive) {
|
|
307
|
+
return await this.addRequest(request, { forefront });
|
|
308
|
+
}
|
|
309
|
+
const snapshot = JSON.parse(JSON.stringify(request));
|
|
310
|
+
// Strip-list, not allow-list: every user-facing field flows through, including ones added to
|
|
311
|
+
// `Request` in the future. The exceptions are `id` and `handledAt`, the two backend-owned
|
|
312
|
+
// lifecycle fields.
|
|
313
|
+
delete snapshot.id;
|
|
314
|
+
delete snapshot.handledAt;
|
|
315
|
+
transaction.recordJournalEntry({
|
|
316
|
+
type: 'requestQueue',
|
|
317
|
+
participant: this,
|
|
318
|
+
requests: [{ url: request.url, uniqueKey: request.uniqueKey, label: request.label, snapshot }],
|
|
319
|
+
forefront,
|
|
320
|
+
writeThrough: false,
|
|
321
|
+
});
|
|
322
|
+
buffered.set(request.uniqueKey, snapshot);
|
|
323
|
+
return {
|
|
324
|
+
wasAlreadyPresent: false,
|
|
325
|
+
wasAlreadyHandled: false,
|
|
326
|
+
requestId: getRequestId(request.uniqueKey),
|
|
327
|
+
uniqueKey: request.uniqueKey,
|
|
328
|
+
forefront,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
/** @internal */
|
|
332
|
+
async commitJournalEntries(entries) {
|
|
333
|
+
// Replay through `backend.addBatchOfRequests`, *not* the batched frontend wrapper - the wrapper
|
|
334
|
+
// resolves after the first chunk and sleeps between the rest, neither of which commit may
|
|
335
|
+
// inherit. One call per `forefront` flag; the order of forefront additions is arbitrary anyway.
|
|
336
|
+
for (const forefront of [false, true]) {
|
|
337
|
+
const requests = entries.flatMap((entry) => entry.type === 'requestQueue' && entry.forefront === forefront
|
|
338
|
+
? // Requests without a snapshot were deduplicated or written through; nothing to replay.
|
|
339
|
+
entry.requests
|
|
340
|
+
.filter((journaled) => journaled.snapshot !== undefined)
|
|
341
|
+
.map((journaled) => new Request(journaled.snapshot))
|
|
342
|
+
: []);
|
|
343
|
+
if (requests.length === 0)
|
|
344
|
+
continue;
|
|
345
|
+
this.#statsTracker.add('writeCount');
|
|
346
|
+
const { processedRequests, unprocessedRequests } = await this.backend.addBatchOfRequests(requests, {
|
|
347
|
+
forefront,
|
|
348
|
+
});
|
|
349
|
+
// Only now, with the real backend-assigned ids, may the shared dedup caches be populated.
|
|
350
|
+
for (const processed of processedRequests) {
|
|
351
|
+
const cacheKey = getRequestId(processed.uniqueKey);
|
|
352
|
+
this.cacheRequest(cacheKey, { ...processed, forefront });
|
|
353
|
+
this.#requestSeenCache.add(cacheKey, processed.requestId);
|
|
354
|
+
}
|
|
355
|
+
if (unprocessedRequests.length > 0) {
|
|
356
|
+
// Warn and skip, rather than retry or fail. `unprocessedRequests` is what remains after
|
|
357
|
+
// the backend's own transient-error handling - a semantic rejection that retrying here
|
|
358
|
+
// would only re-poke. And failing the commit would let one malformed request hold the
|
|
359
|
+
// whole transaction hostage.
|
|
360
|
+
this.log.warning('Some requests were rejected by the request queue while committing a storage transaction and will be skipped. ' +
|
|
361
|
+
"This usually means the request data is malformed (e.g. an invalid 'userData' shape).", { unprocessedRequests });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
178
365
|
/**
|
|
179
366
|
* Adds requests to the queue in batches of 25. This method will wait till all the requests are added
|
|
180
367
|
* to the queue before resolving. You should prefer using `queue.addRequestsBatched()` or `crawler.addRequests()`
|
|
@@ -190,15 +377,9 @@ export class RequestQueue {
|
|
|
190
377
|
* @param [options] Request queue operation options.
|
|
191
378
|
*/
|
|
192
379
|
async addRequests(requestsLike, options = {}) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
.message((value) => `Expected an iterable or async iterable, got ${getObjectType(value)}`));
|
|
197
|
-
ow(options, ow.object.exactShape({
|
|
198
|
-
forefront: ow.optional.boolean,
|
|
199
|
-
cache: ow.optional.boolean,
|
|
200
|
-
}));
|
|
201
|
-
const { forefront = false, cache = true } = options;
|
|
380
|
+
const transaction = activeStorageTransaction();
|
|
381
|
+
parseArgument(requestsLike, iterableSchema);
|
|
382
|
+
const { forefront, cache } = parseArgument(options, addRequestsOptionsSchema);
|
|
202
383
|
const uniqueKeyToCacheKey = new Map();
|
|
203
384
|
const getCachedRequestId = (uniqueKey) => {
|
|
204
385
|
const cached = uniqueKeyToCacheKey.get(uniqueKey);
|
|
@@ -225,12 +406,20 @@ export class RequestQueue {
|
|
|
225
406
|
requests.push(requestLike instanceof Request ? requestLike : new Request(requestLike));
|
|
226
407
|
}
|
|
227
408
|
}
|
|
409
|
+
if (transaction?.policy.requestQueue === 'deferred') {
|
|
410
|
+
const buffered = this.bufferedRequests(transaction);
|
|
411
|
+
for (const request of requests) {
|
|
412
|
+
results.processedRequests.push(await this.addRequestDeferred(transaction, request, forefront, buffered));
|
|
413
|
+
}
|
|
414
|
+
return results;
|
|
415
|
+
}
|
|
416
|
+
this.recordRequestJournalEntry(transaction, requests, forefront, true);
|
|
228
417
|
const requestsToAdd = new Map();
|
|
229
418
|
for (const request of requests) {
|
|
230
419
|
const cacheKey = getCachedRequestId(request.uniqueKey);
|
|
231
420
|
// Prefer the full `requestCache` record; fall back to the dedup cache for background batches it skips.
|
|
232
421
|
const cachedInfo = this.requestCache.get(cacheKey);
|
|
233
|
-
const knownRequestId = cachedInfo?.id ?? this
|
|
422
|
+
const knownRequestId = cachedInfo?.id ?? this.#requestSeenCache.get(cacheKey);
|
|
234
423
|
if (knownRequestId) {
|
|
235
424
|
request.id = knownRequestId;
|
|
236
425
|
results.processedRequests.push({
|
|
@@ -249,7 +438,7 @@ export class RequestQueue {
|
|
|
249
438
|
if (!requestsToAdd.size) {
|
|
250
439
|
return results;
|
|
251
440
|
}
|
|
252
|
-
this
|
|
441
|
+
this.#statsTracker.add('writeCount');
|
|
253
442
|
const apiResults = await this.backend.addBatchOfRequests([...requestsToAdd.values()], { forefront });
|
|
254
443
|
// Report unprocessed requests
|
|
255
444
|
results.unprocessedRequests = apiResults.unprocessedRequests;
|
|
@@ -262,7 +451,7 @@ export class RequestQueue {
|
|
|
262
451
|
this.cacheRequest(cacheKey, { ...newRequest, forefront });
|
|
263
452
|
}
|
|
264
453
|
// Unlike `requestCache`, populate this on every batch (including background ones).
|
|
265
|
-
this
|
|
454
|
+
this.#requestSeenCache.add(cacheKey, newRequest.requestId);
|
|
266
455
|
}
|
|
267
456
|
return results;
|
|
268
457
|
}
|
|
@@ -276,17 +465,8 @@ export class RequestQueue {
|
|
|
276
465
|
* @param options Options for the request queue
|
|
277
466
|
*/
|
|
278
467
|
async addRequestsBatched(requests, options = {}) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
.is((value) => isIterable(value) || isAsyncIterable(value))
|
|
282
|
-
.message((value) => `Expected an iterable or async iterable, got ${getObjectType(value)}`));
|
|
283
|
-
ow(options, ow.object.exactShape({
|
|
284
|
-
forefront: ow.optional.boolean,
|
|
285
|
-
waitForAllRequestsToBeAdded: ow.optional.boolean,
|
|
286
|
-
batchSize: ow.optional.number,
|
|
287
|
-
waitBetweenBatchesMillis: ow.optional.number,
|
|
288
|
-
maxNewRequests: ow.optional.number,
|
|
289
|
-
}));
|
|
468
|
+
parseArgument(requests, iterableSchema);
|
|
469
|
+
const { forefront, waitForAllRequestsToBeAdded, batchSize, waitBetweenBatchesMillis, maxNewRequests } = parseArgument(options, addRequestsBatchedOptionsSchema);
|
|
290
470
|
const addRequest = this.addRequest.bind(this);
|
|
291
471
|
async function* generateRequests() {
|
|
292
472
|
for await (const opts of requests) {
|
|
@@ -305,7 +485,7 @@ export class RequestQueue {
|
|
|
305
485
|
}
|
|
306
486
|
if (opts && typeof opts === 'object' && 'requestsFromUrl' in opts) {
|
|
307
487
|
// Handle URL lists right away
|
|
308
|
-
await addRequest(opts, { forefront
|
|
488
|
+
await addRequest(opts, { forefront });
|
|
309
489
|
}
|
|
310
490
|
else {
|
|
311
491
|
// Yield valid requests
|
|
@@ -313,84 +493,36 @@ export class RequestQueue {
|
|
|
313
493
|
}
|
|
314
494
|
}
|
|
315
495
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
cache,
|
|
337
|
-
});
|
|
338
|
-
if (unprocessedRequests.length > 0) {
|
|
339
|
-
this.log.warning('Some requests were rejected by the request queue and will be skipped. ' +
|
|
340
|
-
"This usually means the request data is malformed (e.g. an invalid 'userData' shape).", { unprocessedRequests });
|
|
341
|
-
}
|
|
342
|
-
if (maxNewRequests !== undefined) {
|
|
343
|
-
remainingBudget -= processedRequests.filter((r) => !r.wasAlreadyPresent).length;
|
|
344
|
-
}
|
|
345
|
-
return processedRequests;
|
|
346
|
-
};
|
|
347
|
-
/**
|
|
348
|
-
* Build the final result. When maxNewRequests is set, drains any remaining items
|
|
349
|
-
* from the underlying request iterator into requestsOverLimit.
|
|
350
|
-
*
|
|
351
|
-
* We accept the iterator explicitly (rather than closing over it) to make it obvious
|
|
352
|
-
* that this is the *same* iterator that `chunkedAsyncIterable` has been consuming —
|
|
353
|
-
* so only unconsumed items are drained. We drain `requestIterator` (not `chunks`)
|
|
354
|
-
* because `chunkedAsyncIterable` stops yielding when the budget-based chunk size
|
|
355
|
-
* drops to 0, leaving unconsumed items in the underlying iterator.
|
|
356
|
-
*/
|
|
357
|
-
const buildResult = async (addedRequests, waitForAllRequestsToBeAdded, unconsumedIterator) => {
|
|
358
|
-
if (maxNewRequests !== undefined) {
|
|
359
|
-
for await (const request of unconsumedIterator) {
|
|
360
|
-
requestsOverLimit.push(request);
|
|
496
|
+
return drainRequestBatches({
|
|
497
|
+
items: generateRequests(),
|
|
498
|
+
batchSize,
|
|
499
|
+
waitBetweenBatchesMillis,
|
|
500
|
+
waitForAllRequestsToBeAdded,
|
|
501
|
+
maxNewRequests,
|
|
502
|
+
/**
|
|
503
|
+
* Requests the backend reports as unprocessed are warned about and skipped rather than retried:
|
|
504
|
+
* `unprocessedRequests` is what remains after the backend's own transient-error handling - a
|
|
505
|
+
* semantic rejection (e.g. a malformed `userData` shape) that re-sending would only re-poke.
|
|
506
|
+
* Retrying transient failures is the storage backend's job, not the frontend's.
|
|
507
|
+
*/
|
|
508
|
+
processChunk: async (chunk, isInitial) => {
|
|
509
|
+
const { processedRequests, unprocessedRequests } = await this.addRequests(chunk, {
|
|
510
|
+
forefront,
|
|
511
|
+
cache: isInitial,
|
|
512
|
+
});
|
|
513
|
+
if (unprocessedRequests.length > 0) {
|
|
514
|
+
this.log.warning('Some requests were rejected by the request queue and will be skipped. ' +
|
|
515
|
+
"This usually means the request data is malformed (e.g. an invalid 'userData' shape).", { unprocessedRequests });
|
|
361
516
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const addedRequests = await processChunk(initialChunk);
|
|
371
|
-
await chunksIterator.next();
|
|
372
|
-
// If we have no more requests to add (either exhausted or budget hit), return immediately
|
|
373
|
-
if ((await chunksIterator.peek()) === undefined) {
|
|
374
|
-
return buildResult(addedRequests, Promise.resolve([]), requestIterator);
|
|
375
|
-
}
|
|
376
|
-
// eslint-disable-next-line no-async-promise-executor
|
|
377
|
-
const promise = new Promise(async (resolve) => {
|
|
378
|
-
const finalAddedRequests = [];
|
|
379
|
-
for await (const requestChunk of chunks) {
|
|
380
|
-
finalAddedRequests.push(...(await processChunk(requestChunk, false)));
|
|
381
|
-
await sleep(waitBetweenBatchesMillis);
|
|
382
|
-
}
|
|
383
|
-
resolve(finalAddedRequests);
|
|
384
|
-
});
|
|
385
|
-
this.inProgressRequestBatchCount += 1;
|
|
386
|
-
void promise.finally(() => {
|
|
387
|
-
this.inProgressRequestBatchCount -= 1;
|
|
517
|
+
return processedRequests;
|
|
518
|
+
},
|
|
519
|
+
trackBackgroundBatches: (batches) => {
|
|
520
|
+
this.inProgressRequestBatchCount += 1;
|
|
521
|
+
void batches.finally(() => {
|
|
522
|
+
this.inProgressRequestBatchCount -= 1;
|
|
523
|
+
});
|
|
524
|
+
},
|
|
388
525
|
});
|
|
389
|
-
// When maxNewRequests is set, we must wait for all batches so we can accurately report skipped requests.
|
|
390
|
-
if (options.waitForAllRequestsToBeAdded || maxNewRequests !== undefined) {
|
|
391
|
-
addedRequests.push(...(await promise));
|
|
392
|
-
}
|
|
393
|
-
return buildResult(addedRequests, promise, requestIterator);
|
|
394
526
|
}
|
|
395
527
|
/**
|
|
396
528
|
* Gets the request from the queue specified by its `uniqueKey`.
|
|
@@ -399,8 +531,13 @@ export class RequestQueue {
|
|
|
399
531
|
* @returns Returns the request object, or `null` if it was not found.
|
|
400
532
|
*/
|
|
401
533
|
async getRequest(uniqueKey) {
|
|
402
|
-
|
|
403
|
-
|
|
534
|
+
const transaction = activeStorageTransaction();
|
|
535
|
+
parseArgument(uniqueKey, uniqueKeySchema);
|
|
536
|
+
// Requests buffered by the active transaction (under the `deferred` write policy) are visible to it.
|
|
537
|
+
const buffered = transaction && this.bufferedRequests(transaction).get(uniqueKey);
|
|
538
|
+
if (buffered) {
|
|
539
|
+
return new Request(buffered);
|
|
540
|
+
}
|
|
404
541
|
const requestOptions = await this.backend.getRequest(uniqueKey);
|
|
405
542
|
if (!requestOptions)
|
|
406
543
|
return null;
|
|
@@ -424,11 +561,11 @@ export class RequestQueue {
|
|
|
424
561
|
* Returns the request object or `null` if there are no more pending requests.
|
|
425
562
|
*/
|
|
426
563
|
async fetchNextRequest() {
|
|
427
|
-
|
|
428
|
-
if (this
|
|
564
|
+
rejectOperationInTransaction('RequestQueue.fetchNextRequest()', 'it is part of the crawler request-processing bookkeeping, which a transaction must not affect.');
|
|
565
|
+
if (this.#queuePausedForMigration) {
|
|
429
566
|
return null;
|
|
430
567
|
}
|
|
431
|
-
this
|
|
568
|
+
this.#statsTracker.add('headItemReadCount');
|
|
432
569
|
const requestOptions = await this.backend.fetchNextRequest();
|
|
433
570
|
if (!requestOptions)
|
|
434
571
|
return null;
|
|
@@ -441,15 +578,11 @@ export class RequestQueue {
|
|
|
441
578
|
* Handled requests will never again be returned by the `fetchNextRequest` function.
|
|
442
579
|
*/
|
|
443
580
|
async markRequestAsHandled(request) {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
id: ow.string,
|
|
447
|
-
uniqueKey: ow.string,
|
|
448
|
-
handledAt: ow.optional.string,
|
|
449
|
-
}));
|
|
581
|
+
rejectOperationInTransaction('RequestQueue.markRequestAsHandled()', 'it is part of the crawler request-processing bookkeeping, which a transaction must not affect.');
|
|
582
|
+
parseArgument(request, handledRequestSchema);
|
|
450
583
|
const forefront = this.requestCache.get(getRequestId(request.uniqueKey))?.forefront ?? false;
|
|
451
584
|
const handledAt = request.handledAt ?? new Date().toISOString();
|
|
452
|
-
this
|
|
585
|
+
this.#statsTracker.add('writeCount');
|
|
453
586
|
const processedRequest = await this.backend.markRequestAsHandled({
|
|
454
587
|
...request,
|
|
455
588
|
handledAt,
|
|
@@ -474,17 +607,13 @@ export class RequestQueue {
|
|
|
474
607
|
* For example, this lets you store the number of retries or error messages for the request.
|
|
475
608
|
*/
|
|
476
609
|
async reclaimRequest(request, options = {}) {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
}));
|
|
485
|
-
const { forefront = false } = options;
|
|
486
|
-
this.statsTracker.add('writeCount');
|
|
487
|
-
const processedRequest = await this.backend.reclaimRequest(request, { forefront });
|
|
610
|
+
rejectOperationInTransaction('RequestQueue.reclaimRequest()', 'it is part of the crawler request-processing bookkeeping, which a transaction must not affect.');
|
|
611
|
+
parseArgument(request, reclaimedRequestSchema);
|
|
612
|
+
const { forefront } = parseArgument(options, operationOptionsSchema);
|
|
613
|
+
this.#statsTracker.add('writeCount');
|
|
614
|
+
const processedRequest = await this.backend.reclaimRequest(request, {
|
|
615
|
+
forefront,
|
|
616
|
+
});
|
|
488
617
|
// The request was not in progress — nothing to reclaim.
|
|
489
618
|
if (!processedRequest) {
|
|
490
619
|
return null;
|
|
@@ -508,7 +637,11 @@ export class RequestQueue {
|
|
|
508
637
|
* {@link RequestQueue.isFinished}.
|
|
509
638
|
*/
|
|
510
639
|
async isEmpty() {
|
|
511
|
-
|
|
640
|
+
const transaction = activeStorageTransaction();
|
|
641
|
+
// Requests buffered by the active transaction count as pending from its point of view.
|
|
642
|
+
if (transaction && this.bufferedRequests(transaction).size > 0) {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
512
645
|
return this.backend.isEmpty();
|
|
513
646
|
}
|
|
514
647
|
/**
|
|
@@ -520,11 +653,15 @@ export class RequestQueue {
|
|
|
520
653
|
* a false negative, but it shall never return a false positive.
|
|
521
654
|
*/
|
|
522
655
|
async isFinished() {
|
|
523
|
-
|
|
656
|
+
const transaction = activeStorageTransaction();
|
|
524
657
|
// We are not finished if we're still adding new requests in the background.
|
|
525
658
|
if (this.inProgressRequestBatchCount > 0) {
|
|
526
659
|
return false;
|
|
527
660
|
}
|
|
661
|
+
// Requests buffered by the active transaction count as pending from its point of view.
|
|
662
|
+
if (transaction && this.bufferedRequests(transaction).size > 0) {
|
|
663
|
+
return false;
|
|
664
|
+
}
|
|
528
665
|
return this.backend.isFinished();
|
|
529
666
|
}
|
|
530
667
|
/**
|
|
@@ -538,10 +675,10 @@ export class RequestQueue {
|
|
|
538
675
|
* short the reservation of a long-lived one and have its in-flight request stolen.
|
|
539
676
|
*/
|
|
540
677
|
async setExpectedRequestProcessingTimeSecs(secs) {
|
|
541
|
-
if (secs <= this
|
|
678
|
+
if (secs <= this.#expectedRequestProcessingSecs) {
|
|
542
679
|
return;
|
|
543
680
|
}
|
|
544
|
-
this
|
|
681
|
+
this.#expectedRequestProcessingSecs = secs;
|
|
545
682
|
await this.backend.setExpectedRequestProcessingTimeSecs?.(secs);
|
|
546
683
|
}
|
|
547
684
|
/**
|
|
@@ -564,7 +701,7 @@ export class RequestQueue {
|
|
|
564
701
|
* depending on the mode of operation.
|
|
565
702
|
*/
|
|
566
703
|
async drop() {
|
|
567
|
-
|
|
704
|
+
rejectOperationInTransaction('RequestQueue.drop()');
|
|
568
705
|
await this.backend.drop();
|
|
569
706
|
serviceLocator.getStorageInstanceManager().removeFromCache(this);
|
|
570
707
|
}
|
|
@@ -573,16 +710,16 @@ export class RequestQueue {
|
|
|
573
710
|
* so it can be reused (e.g. across multiple `crawler.run()` calls).
|
|
574
711
|
*/
|
|
575
712
|
async purge() {
|
|
576
|
-
|
|
713
|
+
rejectOperationInTransaction('RequestQueue.purge()');
|
|
577
714
|
await this.backend.purge();
|
|
578
715
|
// Reset in-memory bookkeeping so the queue behaves as if freshly opened.
|
|
579
716
|
this.requestCache.clear();
|
|
580
|
-
this
|
|
717
|
+
this.#requestSeenCache.clear();
|
|
581
718
|
this.inProgressRequestBatchCount = 0;
|
|
582
719
|
// Reset the expected-processing-time high-water mark too, otherwise the monotonic-raise guard
|
|
583
720
|
// in `setExpectedRequestProcessingTimeSecs` would let a value raised in an earlier run leak into a
|
|
584
721
|
// later one and silently swallow a lower hint (the queue is meant to be reusable across runs).
|
|
585
|
-
this
|
|
722
|
+
this.#expectedRequestProcessingSecs = 0;
|
|
586
723
|
}
|
|
587
724
|
/**
|
|
588
725
|
* @inheritdoc
|
|
@@ -630,8 +767,17 @@ export class RequestQueue {
|
|
|
630
767
|
* @throws If the underlying storage no longer exists (e.g. it was deleted externally).
|
|
631
768
|
*/
|
|
632
769
|
async getInfo() {
|
|
633
|
-
|
|
634
|
-
|
|
770
|
+
const transaction = activeStorageTransaction();
|
|
771
|
+
const metadata = await this.backend.getMetadata();
|
|
772
|
+
const bufferedCount = transaction ? this.bufferedRequests(transaction).size : 0;
|
|
773
|
+
if (bufferedCount > 0) {
|
|
774
|
+
return {
|
|
775
|
+
...metadata,
|
|
776
|
+
totalRequestCount: metadata.totalRequestCount + bufferedCount,
|
|
777
|
+
pendingRequestCount: metadata.pendingRequestCount + bufferedCount,
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
return metadata;
|
|
635
781
|
}
|
|
636
782
|
/**
|
|
637
783
|
* Fetches URLs from requestsFromUrl and returns them in format of list of requests
|
|
@@ -641,10 +787,10 @@ export class RequestQueue {
|
|
|
641
787
|
// Download remote resource and parse URLs.
|
|
642
788
|
let urlsArr;
|
|
643
789
|
try {
|
|
644
|
-
urlsArr = await this.
|
|
790
|
+
urlsArr = await this.downloadListOfUrls({
|
|
645
791
|
url: requestsFromUrl,
|
|
646
792
|
urlRegExp: regex,
|
|
647
|
-
proxyUrl: (await this
|
|
793
|
+
proxyUrl: (await this.#proxyConfiguration?.newProxyInfo())?.url,
|
|
648
794
|
});
|
|
649
795
|
}
|
|
650
796
|
catch (err) {
|
|
@@ -676,10 +822,10 @@ export class RequestQueue {
|
|
|
676
822
|
/**
|
|
677
823
|
* @internal wraps public utility for mocking purposes
|
|
678
824
|
*/
|
|
679
|
-
async
|
|
825
|
+
async downloadListOfUrls(options) {
|
|
680
826
|
return downloadListOfUrls({
|
|
681
827
|
...options,
|
|
682
|
-
httpClient: this
|
|
828
|
+
httpClient: this.#httpClient,
|
|
683
829
|
});
|
|
684
830
|
}
|
|
685
831
|
/**
|
|
@@ -700,15 +846,10 @@ export class RequestQueue {
|
|
|
700
846
|
* @param [options] Open Request Queue options.
|
|
701
847
|
*/
|
|
702
848
|
static async open(identifier, options = {}) {
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
proxyConfiguration: ow.optional.object,
|
|
708
|
-
httpClient: ow.optional.object,
|
|
709
|
-
}));
|
|
710
|
-
const storageBackend = options.storageBackend ?? serviceLocator.getStorageBackend();
|
|
711
|
-
const configuration = options.configuration ?? serviceLocator.getConfiguration();
|
|
849
|
+
tryCancel();
|
|
850
|
+
const parsedOptions = parseArgument(options, openOptionsSchema);
|
|
851
|
+
const storageBackend = parsedOptions.storageBackend ?? serviceLocator.getStorageBackend();
|
|
852
|
+
const configuration = parsedOptions.configuration ?? serviceLocator.getConfiguration();
|
|
712
853
|
await purgeDefaultStorages({ onlyPurgeOnce: true, storageBackend, configuration });
|
|
713
854
|
const resolved = await resolveStorageIdentifier(identifier, storageBackend, 'RequestQueue');
|
|
714
855
|
const queue = await serviceLocator
|
|
@@ -718,8 +859,8 @@ export class RequestQueue {
|
|
|
718
859
|
backendOpener: () => storageBackend.createRequestQueueBackend(resolved),
|
|
719
860
|
backendCacheKey: storageBackend.getStorageBackendCacheKey?.() ?? storageBackend.constructor.name,
|
|
720
861
|
});
|
|
721
|
-
queue
|
|
722
|
-
queue
|
|
862
|
+
queue.#proxyConfiguration = parsedOptions.proxyConfiguration;
|
|
863
|
+
queue.#httpClient = parsedOptions.httpClient;
|
|
723
864
|
return queue;
|
|
724
865
|
}
|
|
725
866
|
}
|