@crawlee/core 4.0.0-beta.266 → 4.0.0-beta.268

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/core",
3
- "version": "4.0.0-beta.266",
3
+ "version": "4.0.0-beta.268",
4
4
  "description": "The scalable web crawling and scraping library for JavaScript/Node.js. Enables development of data extraction and web automation jobs (not only) with headless Chrome and Puppeteer.",
5
5
  "engines": {
6
6
  "node": ">=22.13.0"
@@ -53,10 +53,10 @@
53
53
  "@apify/log": "^3.0.1",
54
54
  "@apify/timeout": "^1.0.1",
55
55
  "@apify/utilities": "^3.1.0",
56
- "@crawlee/fs-storage": "4.0.0-beta.266",
57
- "@crawlee/http-client": "4.0.0-beta.266",
58
- "@crawlee/types": "4.0.0-beta.266",
59
- "@crawlee/utils": "4.0.0-beta.266",
56
+ "@crawlee/fs-storage": "4.0.0-beta.268",
57
+ "@crawlee/http-client": "4.0.0-beta.268",
58
+ "@crawlee/types": "4.0.0-beta.268",
59
+ "@crawlee/utils": "4.0.0-beta.268",
60
60
  "@sapphire/async-queue": "^1.5.5",
61
61
  "@standard-schema/spec": "^1.1.0",
62
62
  "@vladfrangu/async_event_emitter": "^2.4.7",
@@ -76,5 +76,5 @@
76
76
  }
77
77
  }
78
78
  },
79
- "gitHead": "1b0e67fa736823f6ffd42537c87e41fe373394fd"
79
+ "gitHead": "9462257a02015e7ef40ebc16d8fdd0665c9554fe"
80
80
  }
@@ -14,8 +14,10 @@ import { activeStorageTransaction, withDirectStorageAccess } from './transaction
14
14
  export async function drainRequestBatches(options) {
15
15
  const { items, batchSize, waitBetweenBatchesMillis, waitForAllRequestsToBeAdded, maxNewRequests, log, processChunk, trackBackgroundBatches, } = options;
16
16
  const deferred = activeStorageTransaction()?.policy.requestQueue === 'deferred';
17
+ // Leftovers are only reported when the caller waits anyway: draining an async producer just to find
18
+ // them could block indefinitely.
19
+ const reportsOverLimit = maxNewRequests !== undefined && waitForAllRequestsToBeAdded;
17
20
  let remainingBudget = maxNewRequests ?? Infinity;
18
- const requestsOverLimit = [];
19
21
  // Never hand a chunk more than the budget allows, so an over-large final batch cannot overshoot.
20
22
  const effectiveChunkSize = maxNewRequests !== undefined ? () => Math.min(batchSize, remainingBudget) : batchSize;
21
23
  const chunks = peekableAsyncIterable(chunkedAsyncIterable(items, effectiveChunkSize));
@@ -28,12 +30,14 @@ export async function drainRequestBatches(options) {
28
30
  return processedRequests;
29
31
  };
30
32
  const buildResult = async (addedRequests, waitForAll) => {
31
- if (maxNewRequests !== undefined) {
32
- // `chunkedAsyncIterable` stops pulling once the budget-derived chunk size hits zero, so whatever
33
- // is left is still sitting in `items` rather than in a chunk we have seen.
34
- for await (const item of items) {
35
- requestsOverLimit.push(item);
36
- }
33
+ if (!reportsOverLimit) {
34
+ return { addedRequests, waitForAllRequestsToBeAdded: waitForAll };
35
+ }
36
+ // `chunkedAsyncIterable` stops pulling once the budget-derived chunk size hits zero, so whatever
37
+ // is left is still sitting in `items` rather than in a chunk we have seen.
38
+ const requestsOverLimit = [];
39
+ for await (const item of items) {
40
+ requestsOverLimit.push(item);
37
41
  }
38
42
  return { addedRequests, waitForAllRequestsToBeAdded: waitForAll, requestsOverLimit };
39
43
  };
@@ -55,9 +59,8 @@ export async function drainRequestBatches(options) {
55
59
  }
56
60
  return added;
57
61
  };
58
- // With a budget we must drain everything before we can report what went over it; under `deferred` a
59
- // writer that finishes after commit would have nowhere to put its journal entries.
60
- const awaitsRemainder = waitForAllRequestsToBeAdded || maxNewRequests !== undefined || deferred;
62
+ // Under `deferred` a writer that finishes after commit would have nowhere to put its journal entries.
63
+ const awaitsRemainder = waitForAllRequestsToBeAdded || deferred;
61
64
  // An un-awaited writer outlives the transaction scope it inherits, so it must not record into a
62
65
  // transaction that may already be closed. It writes directly - its write-through additions were never
63
66
  // going to be rolled back anyway - which means the requests it adds are not journaled.
@@ -32,7 +32,7 @@ export type RequestLoaderStatus = Exclude<RequestSourceStatus, {
32
32
  status: 'stalled';
33
33
  }>;
34
34
  /**
35
- * Combines two request sources' statuses, with the precedence `ready` > `stalled` > `waiting` > `finished`.
35
+ * Combines two request sources' statuses, with the precedence `ready` > `waiting` > `stalled` > `finished`.
36
36
  *
37
37
  * Binary rather than variadic on purpose: it is on the task loop's probe path and folding a pair allocates
38
38
  * nothing.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Combines two request sources' statuses, with the precedence `ready` > `stalled` > `waiting` > `finished`.
2
+ * Combines two request sources' statuses, with the precedence `ready` > `waiting` > `stalled` > `finished`.
3
3
  *
4
4
  * Binary rather than variadic on purpose: it is on the task loop's probe path and folding a pair allocates
5
5
  * nothing.
@@ -13,17 +13,9 @@ export function joinRequestSourceStatuses(a, b) {
13
13
  if (b.status === 'ready') {
14
14
  return b;
15
15
  }
16
- // `ready` outranking `stalled` masks a stalled source while the other still has work. That is parity with
17
- // the crawler before this was a single answer: the stall check was only reached from `isFinishedFunction`,
18
- // which the task loop calls only when nothing is in flight and nothing is ready. "Fixing" the masking
19
- // turns a crawl that is progressing elsewhere into a `PersistentRateLimitError`. `stalled` outranking
20
- // `waiting` is the same parity - that call site fired regardless of other domains' clocks.
21
- if (a.status === 'stalled') {
22
- return a;
23
- }
24
- if (b.status === 'stalled') {
25
- return b;
26
- }
16
+ // `ready` and `waiting` outranking `stalled` mask a stalled source while the other still has work, now or
17
+ // later. The crawler turns `stalled` into a `PersistentRateLimitError`, so "fixing" the masking turns a crawl
18
+ // that can still progress elsewhere into a failure. The stall surfaces once the other source runs out of work.
27
19
  if (a.status === 'waiting') {
28
20
  // The earlier of the two known wake-up times - unknown only if neither source announced one.
29
21
  if (b.status !== 'waiting' || b.readyAt === undefined) {
@@ -31,6 +23,12 @@ export function joinRequestSourceStatuses(a, b) {
31
23
  }
32
24
  return a.readyAt !== undefined && a.readyAt <= b.readyAt ? a : b;
33
25
  }
26
+ if (b.status === 'waiting') {
27
+ return b;
28
+ }
29
+ if (a.status === 'stalled') {
30
+ return a;
31
+ }
34
32
  // `a` is finished, so `b` decides.
35
33
  return b;
36
34
  }
@@ -303,13 +303,11 @@ export interface AddRequestsBatchedOptions extends RequestQueueOperationOptions
303
303
  waitBetweenBatchesMillis?: number;
304
304
  /**
305
305
  * If set, only this many *actually new* requests (i.e. not already present in the queue) will be added.
306
- * Once the budget is reached, remaining requests from the iterable will be collected in
307
- * {@link AddRequestsBatchedResult.requestsOverLimit|`requestsOverLimit`} instead.
306
+ * Once the budget is reached, remaining requests from the iterable are left unconsumed, or collected in
307
+ * {@link AddRequestsBatchedResult.requestsOverLimit|`requestsOverLimit`} if
308
+ * {@link AddRequestsBatchedOptions.waitForAllRequestsToBeAdded|`waitForAllRequestsToBeAdded`} is set.
308
309
  *
309
310
  * This is useful in combination with `maxRequestsPerCrawl` to avoid duplicate URLs consuming the budget.
310
- *
311
- * **Note:** Setting this option implicitly enables {@link AddRequestsBatchedOptions.waitForAllRequestsToBeAdded|`waitForAllRequestsToBeAdded`},
312
- * since all batches must complete before leftover requests can be accurately reported.
313
311
  */
314
312
  maxNewRequests?: number;
315
313
  }
@@ -335,7 +333,8 @@ export interface AddRequestsBatchedResult {
335
333
  /**
336
334
  * Requests from the input that were not added to the queue because the
337
335
  * {@link AddRequestsBatchedOptions.maxNewRequests|`maxNewRequests`} budget was reached.
338
- * Empty when `maxNewRequests` is not set.
336
+ * `undefined` unless both `maxNewRequests` and
337
+ * {@link AddRequestsBatchedOptions.waitForAllRequestsToBeAdded|`waitForAllRequestsToBeAdded`} are set.
339
338
  */
340
339
  requestsOverLimit?: Source[];
341
340
  }