@crawlee/core 4.0.0-rc.0 → 4.0.0-rc.1
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 +1 -1
- package/configuration.d.ts +15 -46
- package/configuration.js +8 -20
- package/errors.d.ts +12 -53
- package/errors.js +13 -66
- package/events/index.d.ts +1 -0
- package/events/local_event_manager.d.ts +0 -7
- package/events/local_event_manager.js +8 -8
- package/events/system_info.d.ts +38 -0
- package/index.d.ts +2 -8
- package/index.js +4 -8
- package/internal.d.ts +8 -0
- package/internal.js +9 -0
- package/log.d.ts +10 -11
- package/log.js +53 -25
- package/memory-storage/memory-storage.d.ts +12 -7
- package/memory-storage/memory-storage.js +45 -17
- package/memory-storage/resource-clients/dataset.d.ts +0 -5
- package/memory-storage/resource-clients/dataset.js +17 -20
- package/memory-storage/resource-clients/key-value-store.d.ts +0 -9
- package/memory-storage/resource-clients/key-value-store.js +11 -33
- package/memory-storage/resource-clients/request-queue.d.ts +0 -22
- package/memory-storage/resource-clients/request-queue.js +57 -53
- package/package.json +16 -18
- package/proxy_configuration.d.ts +20 -23
- package/proxy_configuration.js +18 -12
- package/recoverable_state.d.ts +28 -6
- package/recoverable_state.js +51 -14
- package/request.d.ts +18 -104
- package/request.js +41 -220
- package/serialization.js +3 -3
- package/service_locator.d.ts +3 -0
- package/service_locator.js +2 -0
- package/storages/dataset.d.ts +9 -15
- package/storages/dataset.js +22 -14
- package/storages/index.d.ts +3 -4
- package/storages/index.js +1 -4
- package/storages/key_value_store.d.ts +12 -46
- package/storages/key_value_store.js +31 -47
- package/storages/key_value_store_codec.js +6 -11
- package/storages/request_dedup_cache.d.ts +0 -2
- package/storages/request_dedup_cache.js +8 -8
- package/storages/request_list.d.ts +6 -82
- package/storages/request_list.js +175 -179
- package/storages/request_loader.d.ts +48 -22
- package/storages/request_loader.js +36 -1
- package/storages/request_manager.d.ts +86 -0
- package/storages/request_manager_tandem.d.ts +13 -28
- package/storages/request_manager_tandem.js +46 -43
- package/storages/request_queue.d.ts +19 -49
- package/storages/request_queue.js +92 -88
- package/storages/storage_instance_manager.d.ts +1 -2
- package/storages/storage_instance_manager.js +4 -4
- package/storages/transaction.d.ts +27 -9
- package/storages/transaction.js +56 -11
- package/storages/utils.d.ts +2 -2
- package/validators.d.ts +3 -2
- package/validators.js +3 -2
- package/autoscaling/autoscaled_pool.d.ts +0 -195
- package/autoscaling/autoscaled_pool.js +0 -386
- package/autoscaling/concurrency_system.d.ts +0 -268
- package/autoscaling/concurrency_system.js +0 -362
- package/autoscaling/cpu_load_signal.d.ts +0 -43
- package/autoscaling/cpu_load_signal.js +0 -47
- package/autoscaling/event_loop_load_signal.d.ts +0 -51
- package/autoscaling/event_loop_load_signal.js +0 -60
- package/autoscaling/index.d.ts +0 -9
- package/autoscaling/index.js +0 -9
- package/autoscaling/load_signal.d.ts +0 -100
- package/autoscaling/load_signal.js +0 -105
- package/autoscaling/memory_load_signal.d.ts +0 -47
- package/autoscaling/memory_load_signal.js +0 -106
- package/autoscaling/snapshotter.d.ts +0 -84
- package/autoscaling/snapshotter.js +0 -67
- package/autoscaling/storage_backend_load_signal.d.ts +0 -56
- package/autoscaling/storage_backend_load_signal.js +0 -73
- package/autoscaling/system_status.d.ts +0 -159
- package/autoscaling/system_status.js +0 -139
- package/autoscaling/weighted_avg.d.ts +0 -5
- package/autoscaling/weighted_avg.js +0 -14
- package/cookie_utils.d.ts +0 -44
- package/cookie_utils.js +0 -122
- package/crawlers/context_pipeline.d.ts +0 -70
- package/crawlers/context_pipeline.js +0 -122
- package/crawlers/crawler_commons.d.ts +0 -159
- package/crawlers/error_snapshotter.d.ts +0 -57
- package/crawlers/error_snapshotter.js +0 -117
- package/crawlers/error_tracker.d.ts +0 -54
- package/crawlers/error_tracker.js +0 -308
- package/crawlers/index.d.ts +0 -5
- package/crawlers/index.js +0 -4
- package/crawlers/internals/types.d.ts +0 -7
- package/crawlers/internals/types.js +0 -1
- package/crawlers/statistics.d.ts +0 -328
- package/crawlers/statistics.js +0 -536
- package/enqueue_links/enqueue_links.d.ts +0 -156
- package/enqueue_links/enqueue_links.js +0 -78
- package/enqueue_links/index.d.ts +0 -2
- package/enqueue_links/index.js +0 -2
- package/enqueue_links/shared.d.ts +0 -93
- package/enqueue_links/shared.js +0 -239
- package/http.d.ts +0 -9
- package/http.js +0 -28
- package/router.d.ts +0 -306
- package/router.js +0 -309
- package/session_pool/consts.d.ts +0 -3
- package/session_pool/consts.js +0 -3
- package/session_pool/errors.d.ts +0 -7
- package/session_pool/errors.js +0 -11
- package/session_pool/fingerprint.d.ts +0 -9
- package/session_pool/fingerprint.js +0 -30
- package/session_pool/index.d.ts +0 -4
- package/session_pool/index.js +0 -4
- package/session_pool/session.d.ts +0 -150
- package/session_pool/session.js +0 -220
- package/session_pool/session_pool.d.ts +0 -240
- package/session_pool/session_pool.js +0 -394
- package/storages/sitemap_request_loader.d.ts +0 -201
- package/storages/sitemap_request_loader.js +0 -438
- package/storages/throttling_request_manager.d.ts +0 -239
- package/storages/throttling_request_manager.js +0 -646
- /package/{crawlers/crawler_commons.js → events/system_info.js} +0 -0
|
@@ -38,19 +38,20 @@ const addRequestsBatchedOptionsSchema = z.strictObject({
|
|
|
38
38
|
waitBetweenBatchesMillis: schemas.anyNumber.default(1000),
|
|
39
39
|
maxNewRequests: schemas.anyNumber.optional(),
|
|
40
40
|
});
|
|
41
|
-
|
|
41
|
+
// Compiled: these run once per request.
|
|
42
|
+
const newRequestLikeSchema = z.compile(z.looseObject({
|
|
42
43
|
url: z.string(),
|
|
43
44
|
id: z.undefined().optional(),
|
|
44
|
-
});
|
|
45
|
-
const handledRequestSchema = z.looseObject({
|
|
45
|
+
}));
|
|
46
|
+
const handledRequestSchema = z.compile(z.looseObject({
|
|
46
47
|
id: z.string(),
|
|
47
48
|
uniqueKey: z.string(),
|
|
48
49
|
handledAt: z.string().optional(),
|
|
49
|
-
});
|
|
50
|
-
const reclaimedRequestSchema = z.looseObject({
|
|
50
|
+
}));
|
|
51
|
+
const reclaimedRequestSchema = z.compile(z.looseObject({
|
|
51
52
|
id: z.string(),
|
|
52
53
|
uniqueKey: z.string(),
|
|
53
|
-
});
|
|
54
|
+
}));
|
|
54
55
|
const uniqueKeySchema = z.string();
|
|
55
56
|
const openOptionsSchema = z.strictObject({
|
|
56
57
|
configuration: z.instanceof(Configuration).optional(),
|
|
@@ -98,8 +99,7 @@ export class RequestQueue {
|
|
|
98
99
|
backend;
|
|
99
100
|
#proxyConfiguration;
|
|
100
101
|
log;
|
|
101
|
-
|
|
102
|
-
requestCache;
|
|
102
|
+
#requestCache;
|
|
103
103
|
/**
|
|
104
104
|
* Remembers the `requestId` of every request already submitted to the client — including background
|
|
105
105
|
* batches that `requestCache` skips — so overlapping URL sets aren't re-submitted.
|
|
@@ -107,8 +107,7 @@ export class RequestQueue {
|
|
|
107
107
|
*/
|
|
108
108
|
#requestSeenCache;
|
|
109
109
|
#queuePausedForMigration = false;
|
|
110
|
-
|
|
111
|
-
inProgressRequestBatchCount = 0;
|
|
110
|
+
#inProgressRequestBatchCount = 0;
|
|
112
111
|
/**
|
|
113
112
|
* The largest expected request-processing time (in seconds) seen so far via
|
|
114
113
|
* {@link setExpectedRequestProcessingTimeSecs}. Used to ensure that value is only ever raised, never
|
|
@@ -137,7 +136,7 @@ export class RequestQueue {
|
|
|
137
136
|
this.#events = serviceLocator.getEventManager();
|
|
138
137
|
this.backend = options.backend;
|
|
139
138
|
this.#proxyConfiguration = options.proxyConfiguration;
|
|
140
|
-
this
|
|
139
|
+
this.#requestCache = new LruCache({ maxLength: MAX_CACHED_REQUESTS });
|
|
141
140
|
this.#requestSeenCache = new RequestDeduplicationCache();
|
|
142
141
|
this.log = serviceLocator.getLogger().child({ prefix: `RequestQueue(${this.id}, ${this.name ?? 'no-name'})` });
|
|
143
142
|
this.#events.on(EventType.MIGRATING, async () => {
|
|
@@ -181,20 +180,20 @@ export class RequestQueue {
|
|
|
181
180
|
parseArgument(requestLike, schemas.anyObject);
|
|
182
181
|
const { forefront } = parseArgument(options, operationOptionsSchema);
|
|
183
182
|
if ('requestsFromUrl' in requestLike) {
|
|
184
|
-
const requests = await this
|
|
185
|
-
const processedRequests = await this
|
|
183
|
+
const requests = await this.#fetchRequestsFromUrl(requestLike);
|
|
184
|
+
const processedRequests = await this.#addFetchedRequests(requestLike, requests, options);
|
|
186
185
|
return { ...processedRequests[0], forefront };
|
|
187
186
|
}
|
|
188
187
|
parseArgument(requestLike, newRequestLikeSchema);
|
|
189
188
|
const request = requestLike instanceof Request ? requestLike : new Request(requestLike);
|
|
190
189
|
if (transaction?.policy.requestQueue === 'deferred') {
|
|
191
|
-
return this
|
|
190
|
+
return this.#addRequestDeferred(transaction, request, forefront);
|
|
192
191
|
}
|
|
193
192
|
const cacheKey = getRequestId(request.uniqueKey);
|
|
194
|
-
const cachedInfo = this
|
|
193
|
+
const cachedInfo = this.#requestCache.get(cacheKey);
|
|
195
194
|
if (cachedInfo) {
|
|
196
195
|
request.id = cachedInfo.id;
|
|
197
|
-
this
|
|
196
|
+
this.#recordRequestJournalEntry(transaction, [request], forefront, true);
|
|
198
197
|
return {
|
|
199
198
|
wasAlreadyPresent: true,
|
|
200
199
|
// We may assume that if request is in local cache then also the information if the
|
|
@@ -207,13 +206,13 @@ export class RequestQueue {
|
|
|
207
206
|
}
|
|
208
207
|
this.#statsTracker.add('writeCount');
|
|
209
208
|
const { processedRequests } = await this.backend.addBatchOfRequests([request], { forefront });
|
|
210
|
-
this
|
|
209
|
+
this.#recordRequestJournalEntry(transaction, [request], forefront, true);
|
|
211
210
|
const queueOperationInfo = {
|
|
212
211
|
...processedRequests[0],
|
|
213
212
|
uniqueKey: request.uniqueKey,
|
|
214
213
|
forefront,
|
|
215
214
|
};
|
|
216
|
-
this
|
|
215
|
+
this.#cacheRequest(cacheKey, queueOperationInfo);
|
|
217
216
|
this.#requestSeenCache.add(cacheKey, request.id);
|
|
218
217
|
return queueOperationInfo;
|
|
219
218
|
}
|
|
@@ -221,7 +220,7 @@ export class RequestQueue {
|
|
|
221
220
|
* Journals an addition for introspection only; these entries are never replayed. A no-op unless the
|
|
222
221
|
* transaction is open, so detached and outliving writers stay out of the journal.
|
|
223
222
|
*/
|
|
224
|
-
recordRequestJournalEntry(transaction, requests, forefront, writeThrough) {
|
|
223
|
+
#recordRequestJournalEntry(transaction, requests, forefront, writeThrough) {
|
|
225
224
|
if (!transaction?.isActive || requests.length === 0)
|
|
226
225
|
return;
|
|
227
226
|
transaction.recordJournalEntry({
|
|
@@ -240,7 +239,7 @@ export class RequestQueue {
|
|
|
240
239
|
* The requests buffered by the given transaction for this queue, keyed by `uniqueKey` — a dedup
|
|
241
240
|
* index derived from the transaction journal.
|
|
242
241
|
*/
|
|
243
|
-
bufferedRequests(transaction) {
|
|
242
|
+
#bufferedRequests(transaction) {
|
|
244
243
|
const buffered = new Map();
|
|
245
244
|
// Only `deferred` records snapshots, so scanning the journal under `writeThrough` never finds any.
|
|
246
245
|
if (transaction.policy.requestQueue !== 'deferred')
|
|
@@ -260,10 +259,10 @@ export class RequestQueue {
|
|
|
260
259
|
* A new request's `requestId` is the local `uniqueKey` hash and is **provisional** — never write it
|
|
261
260
|
* to `request.id` or the dedup caches. Dedup is cheapest-first: buffer, caches, then a backend probe.
|
|
262
261
|
*/
|
|
263
|
-
async addRequestDeferred(transaction, request, forefront, buffered = this
|
|
262
|
+
async #addRequestDeferred(transaction, request, forefront, buffered = this.#bufferedRequests(transaction)) {
|
|
264
263
|
// This transaction's own buffered adds; the shared caches never see them (provisional ids).
|
|
265
264
|
if (buffered.has(request.uniqueKey)) {
|
|
266
|
-
this
|
|
265
|
+
this.#recordRequestJournalEntry(transaction, [request], forefront, false);
|
|
267
266
|
return {
|
|
268
267
|
wasAlreadyPresent: true,
|
|
269
268
|
wasAlreadyHandled: false,
|
|
@@ -275,10 +274,10 @@ export class RequestQueue {
|
|
|
275
274
|
// The caches hold real backend ids. Only *writing* provisional ids to them would be wrong;
|
|
276
275
|
// reading saves a probe. Same lookup as the write-through path.
|
|
277
276
|
const cacheKey = getRequestId(request.uniqueKey);
|
|
278
|
-
const cachedInfo = this
|
|
277
|
+
const cachedInfo = this.#requestCache.get(cacheKey);
|
|
279
278
|
const knownRequestId = cachedInfo?.id ?? this.#requestSeenCache.get(cacheKey);
|
|
280
279
|
if (knownRequestId) {
|
|
281
|
-
this
|
|
280
|
+
this.#recordRequestJournalEntry(transaction, [request], forefront, false);
|
|
282
281
|
return {
|
|
283
282
|
wasAlreadyPresent: true,
|
|
284
283
|
// The dedup cache doesn't track the handled state; only the full record does.
|
|
@@ -291,7 +290,7 @@ export class RequestQueue {
|
|
|
291
290
|
// The caches are bounded, so a miss is not proof of absence - probe for an accurate answer.
|
|
292
291
|
const existing = await this.backend.getRequest(request.uniqueKey);
|
|
293
292
|
if (existing) {
|
|
294
|
-
this
|
|
293
|
+
this.#recordRequestJournalEntry(transaction, [request], forefront, false);
|
|
295
294
|
return {
|
|
296
295
|
wasAlreadyPresent: true,
|
|
297
296
|
wasAlreadyHandled: existing.handledAt != null,
|
|
@@ -338,7 +337,7 @@ export class RequestQueue {
|
|
|
338
337
|
? // Requests without a snapshot were deduplicated or written through; nothing to replay.
|
|
339
338
|
entry.requests
|
|
340
339
|
.filter((journaled) => journaled.snapshot !== undefined)
|
|
341
|
-
.map((journaled) =>
|
|
340
|
+
.map((journaled) => Request.fromSchema(journaled.snapshot))
|
|
342
341
|
: []);
|
|
343
342
|
if (requests.length === 0)
|
|
344
343
|
continue;
|
|
@@ -349,7 +348,7 @@ export class RequestQueue {
|
|
|
349
348
|
// Only now, with the real backend-assigned ids, may the shared dedup caches be populated.
|
|
350
349
|
for (const processed of processedRequests) {
|
|
351
350
|
const cacheKey = getRequestId(processed.uniqueKey);
|
|
352
|
-
this
|
|
351
|
+
this.#cacheRequest(cacheKey, { ...processed, forefront });
|
|
353
352
|
this.#requestSeenCache.add(cacheKey, processed.requestId);
|
|
354
353
|
}
|
|
355
354
|
if (unprocessedRequests.length > 0) {
|
|
@@ -399,26 +398,26 @@ export class RequestQueue {
|
|
|
399
398
|
requests.push(new Request({ url: requestLike }));
|
|
400
399
|
}
|
|
401
400
|
else if ('requestsFromUrl' in requestLike) {
|
|
402
|
-
const fetchedRequests = await this
|
|
403
|
-
await this
|
|
401
|
+
const fetchedRequests = await this.#fetchRequestsFromUrl(requestLike);
|
|
402
|
+
await this.#addFetchedRequests(requestLike, fetchedRequests, options);
|
|
404
403
|
}
|
|
405
404
|
else {
|
|
406
405
|
requests.push(requestLike instanceof Request ? requestLike : new Request(requestLike));
|
|
407
406
|
}
|
|
408
407
|
}
|
|
409
408
|
if (transaction?.policy.requestQueue === 'deferred') {
|
|
410
|
-
const buffered = this
|
|
409
|
+
const buffered = this.#bufferedRequests(transaction);
|
|
411
410
|
for (const request of requests) {
|
|
412
|
-
results.processedRequests.push(await this
|
|
411
|
+
results.processedRequests.push(await this.#addRequestDeferred(transaction, request, forefront, buffered));
|
|
413
412
|
}
|
|
414
413
|
return results;
|
|
415
414
|
}
|
|
416
|
-
this
|
|
415
|
+
this.#recordRequestJournalEntry(transaction, requests, forefront, true);
|
|
417
416
|
const requestsToAdd = new Map();
|
|
418
417
|
for (const request of requests) {
|
|
419
418
|
const cacheKey = getCachedRequestId(request.uniqueKey);
|
|
420
419
|
// Prefer the full `requestCache` record; fall back to the dedup cache for background batches it skips.
|
|
421
|
-
const cachedInfo = this
|
|
420
|
+
const cachedInfo = this.#requestCache.get(cacheKey);
|
|
422
421
|
const knownRequestId = cachedInfo?.id ?? this.#requestSeenCache.get(cacheKey);
|
|
423
422
|
if (knownRequestId) {
|
|
424
423
|
request.id = knownRequestId;
|
|
@@ -448,7 +447,7 @@ export class RequestQueue {
|
|
|
448
447
|
results.processedRequests.push(newRequest);
|
|
449
448
|
const cacheKey = getCachedRequestId(newRequest.uniqueKey);
|
|
450
449
|
if (cache) {
|
|
451
|
-
this
|
|
450
|
+
this.#cacheRequest(cacheKey, { ...newRequest, forefront });
|
|
452
451
|
}
|
|
453
452
|
// Unlike `requestCache`, populate this on every batch (including background ones).
|
|
454
453
|
this.#requestSeenCache.add(cacheKey, newRequest.requestId);
|
|
@@ -475,7 +474,7 @@ export class RequestQueue {
|
|
|
475
474
|
if (opts.url !== undefined && typeof opts.url !== 'string') {
|
|
476
475
|
throw new Error(`Request options are not valid, the 'url' property is not a string. Input: ${inspect(opts)}`);
|
|
477
476
|
}
|
|
478
|
-
if (opts.id !== undefined) {
|
|
477
|
+
if ('id' in opts && opts.id !== undefined) {
|
|
479
478
|
throw new Error(`Request options are not valid, the 'id' property must not be present. Input: ${inspect(opts)}`);
|
|
480
479
|
}
|
|
481
480
|
if (opts.requestsFromUrl !== undefined &&
|
|
@@ -517,9 +516,9 @@ export class RequestQueue {
|
|
|
517
516
|
return processedRequests;
|
|
518
517
|
},
|
|
519
518
|
trackBackgroundBatches: (batches) => {
|
|
520
|
-
this
|
|
519
|
+
this.#inProgressRequestBatchCount += 1;
|
|
521
520
|
void batches.finally(() => {
|
|
522
|
-
this
|
|
521
|
+
this.#inProgressRequestBatchCount -= 1;
|
|
523
522
|
});
|
|
524
523
|
},
|
|
525
524
|
});
|
|
@@ -534,14 +533,14 @@ export class RequestQueue {
|
|
|
534
533
|
const transaction = activeStorageTransaction();
|
|
535
534
|
parseArgument(uniqueKey, uniqueKeySchema);
|
|
536
535
|
// Requests buffered by the active transaction (under the `deferred` write policy) are visible to it.
|
|
537
|
-
const buffered = transaction && this
|
|
536
|
+
const buffered = transaction && this.#bufferedRequests(transaction).get(uniqueKey);
|
|
538
537
|
if (buffered) {
|
|
539
|
-
return
|
|
538
|
+
return Request.fromSchema(buffered);
|
|
540
539
|
}
|
|
541
|
-
const
|
|
542
|
-
if (!
|
|
540
|
+
const schema = await this.backend.getRequest(uniqueKey);
|
|
541
|
+
if (!schema)
|
|
543
542
|
return null;
|
|
544
|
-
return
|
|
543
|
+
return Request.fromSchema(schema);
|
|
545
544
|
}
|
|
546
545
|
/**
|
|
547
546
|
* Returns a next request in the queue to be processed, or `null` if there are no more pending requests.
|
|
@@ -555,7 +554,7 @@ export class RequestQueue {
|
|
|
555
554
|
* Note that the `null` return value doesn't mean the queue processing finished,
|
|
556
555
|
* it means there are currently no pending requests.
|
|
557
556
|
* To check whether all requests in queue were finished,
|
|
558
|
-
* use {@link RequestQueue.
|
|
557
|
+
* use {@link RequestQueue.checkReadiness} instead.
|
|
559
558
|
*
|
|
560
559
|
* @returns
|
|
561
560
|
* Returns the request object or `null` if there are no more pending requests.
|
|
@@ -566,10 +565,10 @@ export class RequestQueue {
|
|
|
566
565
|
return null;
|
|
567
566
|
}
|
|
568
567
|
this.#statsTracker.add('headItemReadCount');
|
|
569
|
-
const
|
|
570
|
-
if (!
|
|
568
|
+
const schema = await this.backend.fetchNextRequest();
|
|
569
|
+
if (!schema)
|
|
571
570
|
return null;
|
|
572
|
-
return
|
|
571
|
+
return Request.fromSchema(schema);
|
|
573
572
|
}
|
|
574
573
|
/**
|
|
575
574
|
* Marks a request that was previously returned by the
|
|
@@ -580,7 +579,7 @@ export class RequestQueue {
|
|
|
580
579
|
async markRequestAsHandled(request) {
|
|
581
580
|
rejectOperationInTransaction('RequestQueue.markRequestAsHandled()', 'it is part of the crawler request-processing bookkeeping, which a transaction must not affect.');
|
|
582
581
|
parseArgument(request, handledRequestSchema);
|
|
583
|
-
const forefront = this
|
|
582
|
+
const forefront = this.#requestCache.get(getRequestId(request.uniqueKey))?.forefront ?? false;
|
|
584
583
|
const handledAt = request.handledAt ?? new Date().toISOString();
|
|
585
584
|
this.#statsTracker.add('writeCount');
|
|
586
585
|
const processedRequest = await this.backend.markRequestAsHandled({
|
|
@@ -597,7 +596,7 @@ export class RequestQueue {
|
|
|
597
596
|
uniqueKey: request.uniqueKey,
|
|
598
597
|
forefront,
|
|
599
598
|
};
|
|
600
|
-
this
|
|
599
|
+
this.#cacheRequest(getRequestId(request.uniqueKey), queueOperationInfo);
|
|
601
600
|
return queueOperationInfo;
|
|
602
601
|
}
|
|
603
602
|
/**
|
|
@@ -623,46 +622,41 @@ export class RequestQueue {
|
|
|
623
622
|
uniqueKey: request.uniqueKey,
|
|
624
623
|
forefront,
|
|
625
624
|
};
|
|
626
|
-
this
|
|
625
|
+
this.#cacheRequest(getRequestId(request.uniqueKey), queueOperationInfo);
|
|
627
626
|
return queueOperationInfo;
|
|
628
627
|
}
|
|
629
628
|
/**
|
|
630
|
-
*
|
|
631
|
-
*
|
|
632
|
-
*
|
|
633
|
-
* Note that even if the queue is empty, there might be some requests currently being processed
|
|
634
|
-
* (fetched but not yet handled or reclaimed). An empty queue therefore does not mean crawling is
|
|
635
|
-
* finished — those in-progress requests may still be reclaimed, and background tasks may still be
|
|
636
|
-
* adding more requests. To check whether all activity in the queue has finished, use
|
|
637
|
-
* {@link RequestQueue.isFinished}.
|
|
629
|
+
* A queue hands requests out as fast as they are asked for; pacing is a job for a manager wrapped around it,
|
|
630
|
+
* such as {@link ThrottlingRequestManager}.
|
|
631
|
+
* @inheritdoc
|
|
638
632
|
*/
|
|
639
|
-
|
|
640
|
-
|
|
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
|
-
}
|
|
645
|
-
return this.backend.isEmpty();
|
|
633
|
+
recordPacingSignal(_signal) {
|
|
634
|
+
return false;
|
|
646
635
|
}
|
|
647
636
|
/**
|
|
648
|
-
*
|
|
649
|
-
* requests currently in progress (fetched but not yet handled or reclaimed, including requests
|
|
650
|
-
* locked by other clients sharing the same queue) and no background add operations still in flight.
|
|
637
|
+
* Reports whether the queue has a request to hand over, is waiting on one, or is done.
|
|
651
638
|
*
|
|
652
|
-
*
|
|
653
|
-
* a
|
|
639
|
+
* `waiting` means requests are in progress (fetched but not yet handled or reclaimed, possibly by another
|
|
640
|
+
* client sharing the queue) or a background add is still landing; neither has a clock, so no `readyAt`.
|
|
641
|
+
*
|
|
642
|
+
* Due to the nature of distributed storage used by the queue, `finished` may occasionally arrive a probe or
|
|
643
|
+
* two late, but it is never reported early.
|
|
654
644
|
*/
|
|
655
|
-
async
|
|
645
|
+
async checkReadiness() {
|
|
656
646
|
const transaction = activeStorageTransaction();
|
|
657
|
-
// We are not finished if we're still adding new requests in the background.
|
|
658
|
-
if (this.inProgressRequestBatchCount > 0) {
|
|
659
|
-
return false;
|
|
660
|
-
}
|
|
661
647
|
// Requests buffered by the active transaction count as pending from its point of view.
|
|
662
|
-
if (transaction && this
|
|
663
|
-
return
|
|
648
|
+
if (transaction && this.#bufferedRequests(transaction).size > 0) {
|
|
649
|
+
return { status: 'ready' };
|
|
650
|
+
}
|
|
651
|
+
// Something fetchable outranks everything below, so this is the only backend call a probe needs.
|
|
652
|
+
if (!(await this.backend.isEmpty())) {
|
|
653
|
+
return { status: 'ready' };
|
|
664
654
|
}
|
|
665
|
-
|
|
655
|
+
// We are not finished if we're still adding new requests in the background.
|
|
656
|
+
if (this.#inProgressRequestBatchCount > 0) {
|
|
657
|
+
return { status: 'waiting' };
|
|
658
|
+
}
|
|
659
|
+
return (await this.backend.isFinished()) ? { status: 'finished' } : { status: 'waiting' };
|
|
666
660
|
}
|
|
667
661
|
/**
|
|
668
662
|
* Tells the queue how long a consumer expects to hold a fetched request before marking it handled
|
|
@@ -681,18 +675,28 @@ export class RequestQueue {
|
|
|
681
675
|
this.#expectedRequestProcessingSecs = secs;
|
|
682
676
|
await this.backend.setExpectedRequestProcessingTimeSecs?.(secs);
|
|
683
677
|
}
|
|
678
|
+
/**
|
|
679
|
+
* @inheritdoc
|
|
680
|
+
* Unlike {@link RequestQueue.setExpectedRequestProcessingTimeSecs}, which sizes every future lock,
|
|
681
|
+
* this only touches the one request it is given.
|
|
682
|
+
*/
|
|
683
|
+
async extendRequestProcessingTimeSecs(request, secs) {
|
|
684
|
+
if (!request.id) {
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
return (await this.backend.extendRequestProcessingTimeSecs?.(request.id, secs)) ?? false;
|
|
688
|
+
}
|
|
684
689
|
/**
|
|
685
690
|
* Caches information about request to beware of unneeded addRequest() calls.
|
|
686
691
|
*/
|
|
687
|
-
cacheRequest(cacheKey, queueOperationInfo) {
|
|
692
|
+
#cacheRequest(cacheKey, queueOperationInfo) {
|
|
688
693
|
// Remove the previous entry, as otherwise our cache will never update 👀
|
|
689
|
-
this
|
|
690
|
-
this
|
|
694
|
+
this.#requestCache.remove(cacheKey);
|
|
695
|
+
this.#requestCache.add(cacheKey, {
|
|
691
696
|
id: queueOperationInfo.requestId,
|
|
692
697
|
isHandled: queueOperationInfo.wasAlreadyHandled,
|
|
693
698
|
uniqueKey: queueOperationInfo.uniqueKey,
|
|
694
699
|
hydrated: null,
|
|
695
|
-
lockExpiresAt: null,
|
|
696
700
|
forefront: queueOperationInfo.forefront,
|
|
697
701
|
});
|
|
698
702
|
}
|
|
@@ -713,9 +717,9 @@ export class RequestQueue {
|
|
|
713
717
|
rejectOperationInTransaction('RequestQueue.purge()');
|
|
714
718
|
await this.backend.purge();
|
|
715
719
|
// Reset in-memory bookkeeping so the queue behaves as if freshly opened.
|
|
716
|
-
this
|
|
720
|
+
this.#requestCache.clear();
|
|
717
721
|
this.#requestSeenCache.clear();
|
|
718
|
-
this
|
|
722
|
+
this.#inProgressRequestBatchCount = 0;
|
|
719
723
|
// Reset the expected-processing-time high-water mark too, otherwise the monotonic-raise guard
|
|
720
724
|
// in `setExpectedRequestProcessingTimeSecs` would let a value raised in an earlier run leak into a
|
|
721
725
|
// later one and silently swallow a lower hint (the queue is meant to be reusable across runs).
|
|
@@ -769,7 +773,7 @@ export class RequestQueue {
|
|
|
769
773
|
async getInfo() {
|
|
770
774
|
const transaction = activeStorageTransaction();
|
|
771
775
|
const metadata = await this.backend.getMetadata();
|
|
772
|
-
const bufferedCount = transaction ? this
|
|
776
|
+
const bufferedCount = transaction ? this.#bufferedRequests(transaction).size : 0;
|
|
773
777
|
if (bufferedCount > 0) {
|
|
774
778
|
return {
|
|
775
779
|
...metadata,
|
|
@@ -782,12 +786,12 @@ export class RequestQueue {
|
|
|
782
786
|
/**
|
|
783
787
|
* Fetches URLs from requestsFromUrl and returns them in format of list of requests
|
|
784
788
|
*/
|
|
785
|
-
async fetchRequestsFromUrl(source) {
|
|
789
|
+
async #fetchRequestsFromUrl(source) {
|
|
786
790
|
const { requestsFromUrl, regex, ...sharedOpts } = source;
|
|
787
791
|
// Download remote resource and parse URLs.
|
|
788
792
|
let urlsArr;
|
|
789
793
|
try {
|
|
790
|
-
urlsArr = await this
|
|
794
|
+
urlsArr = await this.#downloadListOfUrls({
|
|
791
795
|
url: requestsFromUrl,
|
|
792
796
|
urlRegExp: regex,
|
|
793
797
|
proxyUrl: (await this.#proxyConfiguration?.newProxyInfo())?.url,
|
|
@@ -806,7 +810,7 @@ export class RequestQueue {
|
|
|
806
810
|
/**
|
|
807
811
|
* Adds all fetched requests from a URL from a remote resource.
|
|
808
812
|
*/
|
|
809
|
-
async addFetchedRequests(source, fetchedRequests, options) {
|
|
813
|
+
async #addFetchedRequests(source, fetchedRequests, options) {
|
|
810
814
|
const { requestsFromUrl, regex } = source;
|
|
811
815
|
const { addedRequests } = await this.addRequestsBatched(fetchedRequests, options);
|
|
812
816
|
this.log.info('Fetched and loaded Requests from a remote resource.', {
|
|
@@ -822,7 +826,7 @@ export class RequestQueue {
|
|
|
822
826
|
/**
|
|
823
827
|
* @internal wraps public utility for mocking purposes
|
|
824
828
|
*/
|
|
825
|
-
async downloadListOfUrls(options) {
|
|
829
|
+
async #downloadListOfUrls(options) {
|
|
826
830
|
return downloadListOfUrls({
|
|
827
831
|
...options,
|
|
828
832
|
httpClient: this.#httpClient,
|
|
@@ -7,7 +7,6 @@ export interface IStorage {
|
|
|
7
7
|
id: string;
|
|
8
8
|
name?: string;
|
|
9
9
|
}
|
|
10
|
-
type Hashable = string;
|
|
11
10
|
/**
|
|
12
11
|
* Unified manager for opening and caching storage instances (Dataset, KeyValueStore, RequestQueue).
|
|
13
12
|
*
|
|
@@ -35,7 +34,7 @@ export declare class StorageInstanceManager {
|
|
|
35
34
|
*/
|
|
36
35
|
openStorage<TStorage extends IStorage>(cls: Constructor<TStorage>, { id, name, alias, backendOpener, backendCacheKey, }: (ExplicitStorageIdentifier | DefaultStorageIdentifier) & {
|
|
37
36
|
backendOpener: () => Promise<DatasetBackend | KeyValueStoreBackend | RequestQueueBackend>;
|
|
38
|
-
backendCacheKey:
|
|
37
|
+
backendCacheKey: string;
|
|
39
38
|
}): Promise<TStorage>;
|
|
40
39
|
/**
|
|
41
40
|
* Remove a storage instance from the cache (called from `storage.drop()`).
|
|
@@ -32,7 +32,7 @@ class StorageCache {
|
|
|
32
32
|
return undefined;
|
|
33
33
|
}
|
|
34
34
|
/** Write a single entry into a given tier. */
|
|
35
|
-
setInMap(tier, cls, key, instance, backendCacheKey) {
|
|
35
|
+
#setInMap(tier, cls, key, instance, backendCacheKey) {
|
|
36
36
|
if (!tier.has(cls))
|
|
37
37
|
tier.set(cls, new Map());
|
|
38
38
|
const keyMap = tier.get(cls);
|
|
@@ -45,14 +45,14 @@ class StorageCache {
|
|
|
45
45
|
*/
|
|
46
46
|
set(cls, instance, backendCacheKey, alias) {
|
|
47
47
|
// Always cache by id.
|
|
48
|
-
this
|
|
48
|
+
this.#setInMap(this.byId, cls, instance.id, instance, backendCacheKey);
|
|
49
49
|
// Cache by name — only for named storages.
|
|
50
50
|
if (instance.name) {
|
|
51
|
-
this
|
|
51
|
+
this.#setInMap(this.byName, cls, instance.name, instance, backendCacheKey);
|
|
52
52
|
}
|
|
53
53
|
// Cache by alias — only for unnamed storages opened via alias.
|
|
54
54
|
if (alias !== undefined) {
|
|
55
|
-
this
|
|
55
|
+
this.#setInMap(this.byAlias, cls, alias, instance, backendCacheKey);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
removeFromCache(instance) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Awaitable, Dictionary } from '@crawlee/types';
|
|
1
|
+
import type { Awaitable, Dictionary, RequestSchema } from '@crawlee/types';
|
|
2
2
|
import type { RecordOptions } from './key_value_store.js';
|
|
3
3
|
/**
|
|
4
4
|
* Governs whether writes of a given storage type performed inside a {@link StorageTransaction} are
|
|
@@ -38,6 +38,7 @@ export interface TransactionParticipant {
|
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
40
|
* A single dataset write (`pushData`) recorded in a transaction journal.
|
|
41
|
+
* @internal
|
|
41
42
|
*/
|
|
42
43
|
export interface DatasetJournalEntry {
|
|
43
44
|
type: 'dataset';
|
|
@@ -50,6 +51,7 @@ export interface DatasetJournalEntry {
|
|
|
50
51
|
}
|
|
51
52
|
/**
|
|
52
53
|
* A single key-value store write (`setValue`) recorded in a transaction journal.
|
|
54
|
+
* @internal
|
|
53
55
|
*/
|
|
54
56
|
export interface KeyValueStoreJournalEntry {
|
|
55
57
|
type: 'keyValueStore';
|
|
@@ -63,6 +65,7 @@ export interface KeyValueStoreJournalEntry {
|
|
|
63
65
|
}
|
|
64
66
|
/**
|
|
65
67
|
* A request recorded in a transaction journal.
|
|
68
|
+
* @internal
|
|
66
69
|
*/
|
|
67
70
|
export interface JournaledRequest {
|
|
68
71
|
url: string;
|
|
@@ -72,10 +75,11 @@ export interface JournaledRequest {
|
|
|
72
75
|
* A full JSON snapshot of the request for the commit replay. Only present for buffered additions —
|
|
73
76
|
* deduplicated and write-through ones are journaled for introspection only.
|
|
74
77
|
*/
|
|
75
|
-
snapshot?:
|
|
78
|
+
snapshot?: RequestSchema;
|
|
76
79
|
}
|
|
77
80
|
/**
|
|
78
81
|
* A batch of request queue additions recorded in a transaction journal.
|
|
82
|
+
* @internal
|
|
79
83
|
*/
|
|
80
84
|
export interface RequestQueueJournalEntry {
|
|
81
85
|
type: 'requestQueue';
|
|
@@ -86,6 +90,7 @@ export interface RequestQueueJournalEntry {
|
|
|
86
90
|
/** Write-through entries were applied immediately; they are never replayed. */
|
|
87
91
|
writeThrough: boolean;
|
|
88
92
|
}
|
|
93
|
+
/** @internal */
|
|
89
94
|
export type JournalEntry = DatasetJournalEntry | KeyValueStoreJournalEntry | RequestQueueJournalEntry;
|
|
90
95
|
/**
|
|
91
96
|
* A read-only view of a {@link StorageTransaction}: only the journal-backed introspection accessors,
|
|
@@ -143,12 +148,18 @@ export interface StorageTransactionOptions {
|
|
|
143
148
|
*/
|
|
144
149
|
export declare class StorageTransaction implements StorageTransactionView {
|
|
145
150
|
#private;
|
|
146
|
-
/**
|
|
151
|
+
/**
|
|
152
|
+
* The ordered, append-only journal — the source of truth for commit, introspection and reads.
|
|
153
|
+
* @internal
|
|
154
|
+
*/
|
|
147
155
|
readonly journal: JournalEntry[];
|
|
148
|
-
/** Per-storage-type write policy. */
|
|
149
|
-
readonly policy: StorageWritePolicy;
|
|
150
156
|
/** @internal */
|
|
151
157
|
constructor(options?: StorageTransactionOptions);
|
|
158
|
+
/**
|
|
159
|
+
* Per-storage-type write policy.
|
|
160
|
+
* @internal
|
|
161
|
+
*/
|
|
162
|
+
get policy(): StorageWritePolicy;
|
|
152
163
|
get state(): StorageTransactionState;
|
|
153
164
|
/**
|
|
154
165
|
* `true` only while `state === 'open'`. This is the single predicate every storage operation
|
|
@@ -162,6 +173,13 @@ export declare class StorageTransaction implements StorageTransactionView {
|
|
|
162
173
|
* @internal
|
|
163
174
|
*/
|
|
164
175
|
recordJournalEntry(entry: JournalEntry): void;
|
|
176
|
+
/**
|
|
177
|
+
* Registers a callback to run once a commit has been attempted, whether it succeeded (`error` is
|
|
178
|
+
* `undefined`) or failed - the only point at which a deferred write can be reacted to where it was
|
|
179
|
+
* made. Callbacks run in registration order; the first to throw propagates, the rest do not run, and
|
|
180
|
+
* on a failed commit its error replaces the commit error. Not run on rollback: nothing was written.
|
|
181
|
+
*/
|
|
182
|
+
afterCommit(callback: (error?: Error) => Awaitable<void>): void;
|
|
165
183
|
/**
|
|
166
184
|
* Replays the journaled writes into real storage. A no-op unless the transaction is `open`.
|
|
167
185
|
*
|
|
@@ -171,7 +189,6 @@ export declare class StorageTransaction implements StorageTransactionView {
|
|
|
171
189
|
* partway may have applied some of the writes already.
|
|
172
190
|
*/
|
|
173
191
|
commit(): Promise<void>;
|
|
174
|
-
private flush;
|
|
175
192
|
/**
|
|
176
193
|
* Discards the journaled writes. A no-op unless the transaction is `open` — in particular, calling it
|
|
177
194
|
* after a successful `commit()` (which the crawler's error handling can legitimately do) does nothing
|
|
@@ -179,9 +196,10 @@ export declare class StorageTransaction implements StorageTransactionView {
|
|
|
179
196
|
*/
|
|
180
197
|
rollback(): void;
|
|
181
198
|
/**
|
|
182
|
-
* Releases the journal
|
|
183
|
-
* state, `failed` included. Idempotent, never throws, and does not
|
|
184
|
-
* {@link StorageTransactionView} of this transaction is only valid until this
|
|
199
|
+
* Releases the journal, the write-time snapshots it holds and any registered commit callbacks. Must
|
|
200
|
+
* be called for *every* terminal state, `failed` included. Idempotent, never throws, and does not
|
|
201
|
+
* change `state`. Any {@link StorageTransactionView} of this transaction is only valid until this
|
|
202
|
+
* is called.
|
|
185
203
|
*/
|
|
186
204
|
dispose(): void;
|
|
187
205
|
get datasetItems(): {
|