@crawlee/core 4.0.0-beta.142 → 4.0.0-beta.144

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.
@@ -20,8 +20,15 @@ export interface TaskLoopPredicates {
20
20
  */
21
21
  isFinishedFunction?: () => Promise<boolean>;
22
22
  }
23
+ export interface TaskLoopOptions extends TaskLoopPredicates {
24
+ /**
25
+ * How often the pool should check if a new task is ready, in seconds.
26
+ * @default 0.5
27
+ */
28
+ maybeRunIntervalSecs?: number;
29
+ }
23
30
  /** @internal */
24
- export interface AutoscaledPoolOptions extends TaskLoopPredicates {
31
+ export interface AutoscaledPoolOptions extends TaskLoopOptions {
25
32
  /**
26
33
  * The governor that decides whether there is free compute for one more task. Typically a
27
34
  * {@link ConcurrencySystem}, but any {@link IConcurrencySystem} works. Share a single instance across
@@ -41,12 +48,6 @@ export interface AutoscaledPoolOptions extends TaskLoopPredicates {
41
48
  * The function must either be labeled `async` or return a promise.
42
49
  */
43
50
  runTaskFunction?: () => Promise<unknown>;
44
- /**
45
- * Indicates how often the pool should call the `runTaskFunction()` to start a new task, in seconds.
46
- * This has no effect on starting new tasks immediately after a task completes.
47
- * @default 0.5
48
- */
49
- maybeRunIntervalSecs?: number;
50
51
  /**
51
52
  * Timeout in which the `runTaskFunction` needs to finish, given in seconds.
52
53
  * @default 0
@@ -19,7 +19,7 @@ const autoscaledPoolOptionsSchema = z.strictObject({
19
19
  .default(0),
20
20
  log: validators.logger.default(() => serviceLocator.getLogger()),
21
21
  concurrencySystem: schemas.anyObject,
22
- consumer: schemas.anyObject.refine((value) => typeof value.id === 'string' && value.id.length > 0, "Expected an object with a non-empty string 'id'"),
22
+ consumer: schemas.anyObject.refine((value) => typeof value?.id === 'string' && value.id.length > 0, "Expected an object with a non-empty string 'id'"),
23
23
  });
24
24
  /**
25
25
  * Manages a pool of asynchronous resource-intensive tasks that are executed in parallel.
@@ -158,7 +158,6 @@ export declare class ConcurrencySystem implements IConcurrencySystem {
158
158
  private readonly scaleUpStepRatio;
159
159
  private readonly scaleDownStepRatio;
160
160
  private readonly maxTasksPerMinute;
161
- private _currentConcurrency;
162
161
  private readonly snapshotter;
163
162
  private readonly systemStatus;
164
163
  constructor(options?: ConcurrencySystemOptions);
@@ -54,8 +54,7 @@ export class ConcurrencySystem {
54
54
  #minConcurrency;
55
55
  #maxConcurrency;
56
56
  #desiredConcurrency;
57
- // kept as TS-private _-prefixed: autoscaled_pool tests write this backing field directly
58
- _currentConcurrency = 0;
57
+ #currentConcurrency = 0;
59
58
  #lastLoggingTime;
60
59
  #tasksPerMinute = Array.from({ length: 60 }, () => 0);
61
60
  snapshotter;
@@ -158,7 +157,7 @@ export class ConcurrencySystem {
158
157
  this.#desiredConcurrency = Math.min(atLeastMin, this.#maxConcurrency);
159
158
  }
160
159
  get currentConcurrency() {
161
- return this._currentConcurrency;
160
+ return this.#currentConcurrency;
162
161
  }
163
162
  /** Whether the system is currently monitoring load and autoscaling the budget. */
164
163
  get isRunning() {
@@ -247,13 +246,13 @@ export class ConcurrencySystem {
247
246
  */
248
247
  hasCapacityForTask(_consumer) {
249
248
  this.warnIfNotRunning();
250
- if (this._currentConcurrency >= this.#desiredConcurrency) {
249
+ if (this.#currentConcurrency >= this.#desiredConcurrency) {
251
250
  this.log.perf('Task will not run. Desired concurrency achieved.');
252
251
  return false;
253
252
  }
254
253
  const currentStatus = this.systemStatus.getCurrentStatus();
255
254
  const { isSystemIdle } = currentStatus;
256
- if (!isSystemIdle && this._currentConcurrency >= this.#minConcurrency) {
255
+ if (!isSystemIdle && this.#currentConcurrency >= this.#minConcurrency) {
257
256
  this.log.perf('Task will not be run. System is overloaded.', currentStatus);
258
257
  return false;
259
258
  }
@@ -283,13 +282,13 @@ export class ConcurrencySystem {
283
282
  this.log.perf('Task will not run. Maximum tasks per minute reached.');
284
283
  return false;
285
284
  }
286
- this._currentConcurrency++;
285
+ this.#currentConcurrency++;
287
286
  this.#tasksPerMinute[0]++;
288
287
  return true;
289
288
  }
290
289
  /** Returns a slot to the shared budget, whoever booked it. */
291
290
  registerTaskEnd(_consumer) {
292
- this._currentConcurrency--;
291
+ this.#currentConcurrency--;
293
292
  }
294
293
  /**
295
294
  * What the system currently makes of the machine: the per-signal overload verdicts, evaluated over the
@@ -310,7 +309,7 @@ export class ConcurrencySystem {
310
309
  const { isSystemIdle } = systemStatus;
311
310
  const weAreNotAtMax = this.#desiredConcurrency < this.#maxConcurrency;
312
311
  const minCurrentConcurrency = Math.floor(this.#desiredConcurrency * this.desiredConcurrencyRatio);
313
- const weAreReachingDesiredConcurrency = this._currentConcurrency >= minCurrentConcurrency;
312
+ const weAreReachingDesiredConcurrency = this.#currentConcurrency >= minCurrentConcurrency;
314
313
  if (isSystemIdle && weAreNotAtMax && weAreReachingDesiredConcurrency)
315
314
  this.scaleUp(systemStatus);
316
315
  const isSystemOverloaded = !isSystemIdle;
@@ -325,7 +324,7 @@ export class ConcurrencySystem {
325
324
  else if (now > this.#lastLoggingTime + this.#loggingIntervalMillis) {
326
325
  this.#lastLoggingTime = now;
327
326
  this.log.info('state', {
328
- currentConcurrency: this._currentConcurrency,
327
+ currentConcurrency: this.#currentConcurrency,
329
328
  desiredConcurrency: this.#desiredConcurrency,
330
329
  systemStatus,
331
330
  });
@@ -61,8 +61,6 @@ export interface LoadSignal {
61
61
  */
62
62
  export declare class SnapshotStore<T extends LoadSnapshot = LoadSnapshot> {
63
63
  #private;
64
- /** Retention window in milliseconds. Unbounded until {@link SnapshotStore.useSampleWindow|`useSampleWindow()`}. */
65
- private historyMillis;
66
64
  /**
67
65
  * Sizes retention to the window the signal will be sampled over, as handed to it in
68
66
  * {@link LoadSignal.start|`start()`}. Until this is called nothing is pruned at all, so a signal that ignores
@@ -5,16 +5,14 @@ import { weightedAvg } from './weighted_avg.js';
5
5
  */
6
6
  export class SnapshotStore {
7
7
  #snapshots = [];
8
- /** Retention window in milliseconds. Unbounded until {@link SnapshotStore.useSampleWindow|`useSampleWindow()`}. */
9
- // kept as TS-private: concurrency_system tests read this retention window directly
10
- historyMillis = Infinity;
8
+ #historyMillis = Infinity;
11
9
  /**
12
10
  * Sizes retention to the window the signal will be sampled over, as handed to it in
13
11
  * {@link LoadSignal.start|`start()`}. Until this is called nothing is pruned at all, so a signal that ignores
14
12
  * its start context grows unboundedly.
15
13
  */
16
14
  useSampleWindow(maxSampleWindowMillis) {
17
- this.historyMillis = maxSampleWindowMillis;
15
+ this.#historyMillis = maxSampleWindowMillis;
18
16
  }
19
17
  /**
20
18
  * Add a snapshot and prune entries older than the history window.
@@ -24,7 +22,7 @@ export class SnapshotStore {
24
22
  let oldCount = 0;
25
23
  for (let i = 0; i < this.#snapshots.length; i++) {
26
24
  const { createdAt } = this.#snapshots[i];
27
- if (now.getTime() - new Date(createdAt).getTime() > this.historyMillis)
25
+ if (now.getTime() - new Date(createdAt).getTime() > this.#historyMillis)
28
26
  oldCount++;
29
27
  else
30
28
  break;
@@ -89,7 +89,6 @@ export interface CalculatedStatistics {
89
89
  */
90
90
  export declare class Statistics<StateExtension extends object = {}, PersistedStateExtension extends object = StateExtension> implements IStatistics<StateExtension> {
91
91
  #private;
92
- private static id;
93
92
  /**
94
93
  * An error tracker for final retry errors.
95
94
  */
@@ -158,8 +158,7 @@ function buildStatisticStateCodec(statistics) {
158
158
  * @category Crawlers
159
159
  */
160
160
  export class Statistics {
161
- // kept as TS-private: statistics tests read the static counter directly
162
- static id = 0;
161
+ static #id = 0;
163
162
  /**
164
163
  * An error tracker for final retry errors.
165
164
  */
@@ -201,7 +200,7 @@ export class Statistics {
201
200
  */
202
201
  constructor(options = {}) {
203
202
  const { logIntervalSecs, logMessage, log, keyValueStore, persistenceOptions, saveErrorSnapshots, id, stateExtension, } = parseArgument(options, statisticsOptionsSchema);
204
- this.id = id ?? String(Statistics.id++);
203
+ this.id = id ?? String(Statistics.#id++);
205
204
  this.#persistStateKey = `CRAWLEE_CRAWLER_STATISTICS_${this.id}`;
206
205
  this.log = (log ?? serviceLocator.getLogger()).child({ prefix: 'Statistics' });
207
206
  this.errorTracker = new ErrorTracker({ ...errorTrackerConfig, saveErrorSnapshots });
package/log.d.ts CHANGED
@@ -33,8 +33,7 @@ export type { CrawleeLogger, CrawleeLoggerOptions };
33
33
  * ```
34
34
  */
35
35
  export declare abstract class BaseCrawleeLogger implements CrawleeLogger {
36
- private options;
37
- private readonly warningsLogged;
36
+ #private;
38
37
  constructor(options?: Partial<CrawleeLoggerOptions>);
39
38
  /**
40
39
  * Core logging method. Subclasses must implement this to dispatch log messages
@@ -73,7 +72,7 @@ export declare abstract class BaseCrawleeLogger implements CrawleeLogger {
73
72
  * Users who want to use a different logging library should implement {@link BaseCrawleeLogger} directly.
74
73
  */
75
74
  export declare class ApifyLogAdapter extends BaseCrawleeLogger {
76
- private readonly apifyLog;
75
+ #private;
77
76
  constructor(apifyLog: Log, options?: Partial<CrawleeLoggerOptions>);
78
77
  logWithLevel(level: number, message: string, data?: Record<string, unknown>): void;
79
78
  protected createChild(options: Partial<CrawleeLoggerOptions>): CrawleeLogger;
package/log.js CHANGED
@@ -30,18 +30,18 @@ import log, { Log, Logger, LoggerJson, LoggerText, LogLevel } from '@apify/log';
30
30
  * ```
31
31
  */
32
32
  export class BaseCrawleeLogger {
33
- // kept as TS-private: the adaptive crawler's log proxy calls non-intercepted methods with `this === proxy`,
34
- // where `#`-field access would throw at runtime
35
- options;
36
- warningsLogged = new Set();
33
+ // Note: If wrapping logger in a Proxy, unbound methods calling #-fields throw TypeError
34
+ // unless bound to the target (see createLogProxy in adaptive-playwright-crawler.ts).
35
+ #options;
36
+ #warningsLogged = new Set();
37
37
  constructor(options = {}) {
38
- this.options = options;
38
+ this.#options = options;
39
39
  }
40
40
  getOptions() {
41
- return this.options;
41
+ return this.#options;
42
42
  }
43
43
  setOptions(options) {
44
- this.options = { ...this.options, ...options };
44
+ this.#options = { ...this.#options, ...options };
45
45
  }
46
46
  child(options) {
47
47
  return this.createChild(options);
@@ -63,8 +63,8 @@ export class BaseCrawleeLogger {
63
63
  this.logWithLevel(LogLevel.WARNING, message, data);
64
64
  }
65
65
  warningOnce(message) {
66
- if (!this.warningsLogged.has(message)) {
67
- this.warningsLogged.add(message);
66
+ if (!this.#warningsLogged.has(message)) {
67
+ this.#warningsLogged.add(message);
68
68
  this.warning(message);
69
69
  }
70
70
  }
@@ -88,18 +88,16 @@ export class BaseCrawleeLogger {
88
88
  * Users who want to use a different logging library should implement {@link BaseCrawleeLogger} directly.
89
89
  */
90
90
  export class ApifyLogAdapter extends BaseCrawleeLogger {
91
- apifyLog;
92
- constructor(
93
- // kept as a TS-private parameter property: reached through the adaptive crawler's log proxy, see above
94
- apifyLog, options) {
91
+ #apifyLog;
92
+ constructor(apifyLog, options) {
95
93
  super(options ?? {});
96
- this.apifyLog = apifyLog;
94
+ this.#apifyLog = apifyLog;
97
95
  }
98
96
  logWithLevel(level, message, data) {
99
- this.apifyLog.internal(level, message, data);
97
+ this.#apifyLog.internal(level, message, data);
100
98
  }
101
99
  createChild(options) {
102
- return new ApifyLogAdapter(this.apifyLog.child({ prefix: options.prefix ?? null }), {
100
+ return new ApifyLogAdapter(this.#apifyLog.child({ prefix: options.prefix ?? null }), {
103
101
  ...this.getOptions(),
104
102
  ...options,
105
103
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/core",
3
- "version": "4.0.0-beta.142",
3
+ "version": "4.0.0-beta.144",
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.0.0"
@@ -52,10 +52,10 @@
52
52
  "@apify/log": "^2.5.18",
53
53
  "@apify/timeout": "^0.4.4",
54
54
  "@apify/utilities": "^2.15.5",
55
- "@crawlee/fs-storage": "4.0.0-beta.142",
56
- "@crawlee/http-client": "4.0.0-beta.142",
57
- "@crawlee/types": "4.0.0-beta.142",
58
- "@crawlee/utils": "4.0.0-beta.142",
55
+ "@crawlee/fs-storage": "4.0.0-beta.144",
56
+ "@crawlee/http-client": "4.0.0-beta.144",
57
+ "@crawlee/types": "4.0.0-beta.144",
58
+ "@crawlee/utils": "4.0.0-beta.144",
59
59
  "@sapphire/async-queue": "^1.5.5",
60
60
  "@standard-schema/spec": "^1.0.0",
61
61
  "@vladfrangu/async_event_emitter": "^2.4.6",
@@ -78,5 +78,5 @@
78
78
  }
79
79
  }
80
80
  },
81
- "gitHead": "d40a48fcb78865bd622a05027103155ac08a227f"
81
+ "gitHead": "4acf68a225909523047f872cbdeb87428bfc560d"
82
82
  }
@@ -28,7 +28,10 @@ export interface SessionOptions {
28
28
  errorScoreDecrement?: number;
29
29
  /** Date of creation. */
30
30
  createdAt?: Date;
31
- /** Date of expiration. */
31
+ /**
32
+ * Date of expiration.
33
+ * @default createdAt + maxAgeSecs
34
+ */
32
35
  expiresAt?: Date;
33
36
  /**
34
37
  * Indicates how many times the session has been used.
@@ -1,7 +1,6 @@
1
1
  import { CookieJar } from 'tough-cookie';
2
2
  import { z } from 'zod';
3
3
  import { cryptoRandomObjectId } from '@apify/utilities';
4
- import { getDefaultCookieExpirationDate } from '../cookie_utils.js';
5
4
  import { serviceLocator } from '../service_locator.js';
6
5
  import { parseArgument, schemas, validators } from '../validators.js';
7
6
  // `schemas.anyObject` passes values through by reference (object schemas return a pruned plain
@@ -88,7 +87,9 @@ export class Session {
88
87
  * Session configuration.
89
88
  */
90
89
  constructor(options = {}) {
91
- const { id, cookieJar, proxyInfo, maxAgeSecs, userData, maxErrorScore, errorScoreDecrement, createdAt, usageCount, errorScore, maxUsageCount, retired, log, fingerprint, expiresAt = getDefaultCookieExpirationDate(maxAgeSecs), } = parseArgument(options, sessionOptionsSchema);
90
+ const { id, cookieJar, proxyInfo, maxAgeSecs, userData, maxErrorScore, errorScoreDecrement, createdAt, usageCount, errorScore, maxUsageCount, retired, log, fingerprint,
91
+ // Anchored to `createdAt` rather than to "now", so the documented `createdAt + maxAgeSecs` holds.
92
+ expiresAt = new Date(createdAt.getTime() + maxAgeSecs * 1000), } = parseArgument(options, sessionOptionsSchema);
92
93
  this.#log = log.child({ prefix: 'Session' });
93
94
  this.#cookieJar = cookieJar.setCookie ? cookieJar : CookieJar.fromJSON(JSON.stringify(cookieJar));
94
95
  this.#proxyInfo = proxyInfo;
@@ -114,13 +114,6 @@ export interface SessionPoolOptions {
114
114
  export declare class SessionPool implements ISessionPool {
115
115
  #private;
116
116
  readonly id: string;
117
- private maxPoolSize;
118
- private createSessionFunction;
119
- private keyValueStore?;
120
- private sessionMap;
121
- private sessionOptions;
122
- private persistStateKeyValueStoreId?;
123
- private persistStateKey;
124
117
  constructor(options?: SessionPoolOptions);
125
118
  /**
126
119
  * Gets count of usable sessions in the pool.
@@ -82,14 +82,13 @@ export class SessionPool {
82
82
  id;
83
83
  #log;
84
84
  #sessions = [];
85
- // kept as TS-private: session_pool tests read/override the members below directly
86
- maxPoolSize;
87
- createSessionFunction;
88
- keyValueStore;
89
- sessionMap = new Map();
90
- sessionOptions;
91
- persistStateKeyValueStoreId;
92
- persistStateKey;
85
+ #maxPoolSize;
86
+ #createSessionFunction;
87
+ #keyValueStore;
88
+ #sessionMap = new Map();
89
+ #sessionOptions;
90
+ #persistStateKeyValueStoreId;
91
+ #persistStateKey;
93
92
  #listener;
94
93
  #events;
95
94
  #persistenceOptions;
@@ -105,18 +104,18 @@ export class SessionPool {
105
104
  this.#log = log.child({ prefix: 'SessionPool' });
106
105
  this.#persistenceOptions = persistenceOptions;
107
106
  // Pool Configuration
108
- this.maxPoolSize = maxPoolSize;
109
- this.createSessionFunction = createSessionFunction || this.defaultCreateSessionFunction;
107
+ this.#maxPoolSize = maxPoolSize;
108
+ this.#createSessionFunction = createSessionFunction || this.defaultCreateSessionFunction;
110
109
  // Session configuration. The pool-scoped logger is merged into per-call sessionOptions inside
111
110
  // `invokeCreateSessionFunction`, so every Session inherits it without custom createSessionFunctions
112
111
  // having to know about it.
113
- this.sessionOptions = {
112
+ this.#sessionOptions = {
114
113
  ...sessionOptions,
115
114
  log: this.#log,
116
115
  };
117
116
  // Session keyValueStore
118
- this.persistStateKeyValueStoreId = persistStateKeyValueStoreId;
119
- this.persistStateKey = persistStateKey ?? `${PERSIST_STATE_KEY}_${this.id}`;
117
+ this.#persistStateKeyValueStoreId = persistStateKeyValueStoreId;
118
+ this.#persistStateKey = persistStateKey ?? `${PERSIST_STATE_KEY}_${this.id}`;
120
119
  }
121
120
  /**
122
121
  * Gets count of usable sessions in the pool.
@@ -146,11 +145,11 @@ export class SessionPool {
146
145
  if (!this.#persistenceOptions.enable) {
147
146
  return;
148
147
  }
149
- this.keyValueStore = await KeyValueStore.open(this.persistStateKeyValueStoreId ? { id: this.persistStateKeyValueStoreId } : null, {
148
+ this.#keyValueStore = await KeyValueStore.open(this.#persistStateKeyValueStoreId ? { id: this.#persistStateKeyValueStoreId } : null, {
150
149
  configuration: serviceLocator.getConfiguration(),
151
150
  });
152
- if (!this.persistStateKeyValueStoreId) {
153
- this.#log.debug(`No 'persistStateKeyValueStoreId' options specified, this session pool's data has been saved in the KeyValueStore with the id: ${this.keyValueStore.id}`);
151
+ if (!this.#persistStateKeyValueStoreId) {
152
+ this.#log.debug(`No 'persistStateKeyValueStoreId' options specified, this session pool's data has been saved in the KeyValueStore with the id: ${this.#keyValueStore.id}`);
154
153
  }
155
154
  // in case of migration happened and SessionPool state should be restored from the keyValueStore.
156
155
  await this.maybeLoadSessionPool();
@@ -167,7 +166,7 @@ export class SessionPool {
167
166
  await this.ensureInitialized();
168
167
  const { id } = options;
169
168
  if (id) {
170
- const sessionExists = this.sessionMap.has(id);
169
+ const sessionExists = this.#sessionMap.has(id);
171
170
  if (sessionExists) {
172
171
  throw new Error(`Cannot add session with id '${id}' as it already exists in the pool`);
173
172
  }
@@ -203,7 +202,7 @@ export class SessionPool {
203
202
  await this.#queue.wait();
204
203
  try {
205
204
  if (sessionId) {
206
- const session = this.sessionMap.get(sessionId);
205
+ const session = this.#sessionMap.get(sessionId);
207
206
  if (session?.isUsable())
208
207
  return session;
209
208
  return undefined;
@@ -229,7 +228,7 @@ export class SessionPool {
229
228
  return;
230
229
  }
231
230
  await this.ensureInitialized();
232
- await this.keyValueStore?.setValue(this.persistStateKey, null);
231
+ await this.#keyValueStore?.setValue(this.#persistStateKey, null);
233
232
  }
234
233
  /**
235
234
  * Returns an object representing the internal state of the `SessionPool` instance.
@@ -254,12 +253,12 @@ export class SessionPool {
254
253
  }
255
254
  await this.ensureInitialized();
256
255
  this.#log.debug('Persisting state', {
257
- persistStateKeyValueStoreId: this.persistStateKeyValueStoreId,
258
- persistStateKey: this.persistStateKey,
256
+ persistStateKeyValueStoreId: this.#persistStateKeyValueStoreId,
257
+ persistStateKey: this.#persistStateKey,
259
258
  });
260
- await this.keyValueStore
261
- ?.setValue(this.persistStateKey, await this.getState())
262
- .catch((error) => this.#log.warning(`Failed to persist the session pool stats to ${this.persistStateKey}`, { error }));
259
+ await this.#keyValueStore
260
+ ?.setValue(this.#persistStateKey, await this.getState())
261
+ .catch((error) => this.#log.warning(`Failed to persist the session pool stats to ${this.#persistStateKey}`, { error }));
263
262
  }
264
263
  async [Symbol.asyncDispose]() {
265
264
  await this.teardown({ persistState: true });
@@ -287,7 +286,7 @@ export class SessionPool {
287
286
  this.#sessions = this.#sessions.filter((storedSession) => {
288
287
  if (storedSession.isUsable())
289
288
  return true;
290
- this.sessionMap.delete(storedSession.id);
289
+ this.#sessionMap.delete(storedSession.id);
291
290
  this.#log.debug(`Removed Session - ${storedSession.id}`);
292
291
  return false;
293
292
  });
@@ -298,7 +297,7 @@ export class SessionPool {
298
297
  */
299
298
  registerSession(newSession) {
300
299
  this.#sessions.push(newSession);
301
- this.sessionMap.set(newSession.id, newSession);
300
+ this.#sessionMap.set(newSession.id, newSession);
302
301
  }
303
302
  /**
304
303
  * Gets random index.
@@ -329,10 +328,10 @@ export class SessionPool {
329
328
  async invokeCreateSessionFunction(perCallOptions) {
330
329
  const sessionOptions = {
331
330
  fingerprint: createDefaultSessionFingerprint(),
332
- ...this.sessionOptions,
331
+ ...this.#sessionOptions,
333
332
  ...perCallOptions,
334
333
  };
335
- return this.createSessionFunction({ sessionOptions });
334
+ return this.#createSessionFunction({ sessionOptions });
336
335
  }
337
336
  /**
338
337
  * Creates new session and adds it to the pool.
@@ -348,7 +347,7 @@ export class SessionPool {
348
347
  * Decides whether there is enough space for creating new session.
349
348
  */
350
349
  hasSpaceForSession() {
351
- return this.#sessions.length < this.maxPoolSize;
350
+ return this.#sessions.length < this.#maxPoolSize;
352
351
  }
353
352
  /**
354
353
  * Picks a session from the `SessionPool` according to the configured `sessionReuseStrategy`.
@@ -376,13 +375,13 @@ export class SessionPool {
376
375
  * If the state was persisted it loads the `SessionPool` from the persisted state.
377
376
  */
378
377
  async maybeLoadSessionPool() {
379
- const loadedSessionPool = await this.keyValueStore?.getValue(this.persistStateKey);
378
+ const loadedSessionPool = await this.#keyValueStore?.getValue(this.#persistStateKey);
380
379
  if (!loadedSessionPool)
381
380
  return;
382
381
  // Invalidate old sessions and load active sessions only
383
382
  this.#log.debug('Recreating state from KeyValueStore', {
384
- persistStateKeyValueStoreId: this.persistStateKeyValueStoreId,
385
- persistStateKey: this.persistStateKey,
383
+ persistStateKeyValueStoreId: this.#persistStateKeyValueStoreId,
384
+ persistStateKey: this.#persistStateKey,
386
385
  });
387
386
  for (const sessionObject of loadedSessionPool.sessions) {
388
387
  sessionObject.createdAt = new Date(sessionObject.createdAt);
@@ -126,6 +126,7 @@ export class Dataset {
126
126
  * The objects must be serializable to JSON.
127
127
  */
128
128
  async pushData(data) {
129
+ tryCancel();
129
130
  const transaction = activeStorageTransaction();
130
131
  parseArgument(data, schemas.anyObject);
131
132
  // Normalize to array and validate each item
@@ -65,7 +65,7 @@ export declare class KeyValueStore {
65
65
  readonly configuration: Configuration;
66
66
  readonly id: string;
67
67
  readonly name?: string;
68
- private readonly backend;
68
+ readonly backend: KeyValueStoreBackend;
69
69
  /**
70
70
  * @internal
71
71
  */
@@ -86,7 +86,6 @@ export class KeyValueStore {
86
86
  configuration;
87
87
  id;
88
88
  name;
89
- // kept as TS-private: key_value_store tests spy on the backend directly
90
89
  backend;
91
90
  #persistStateEventStarted = false;
92
91
  /** Cache for persistent (auto-saved) values. When we try to set such value, the cache will be updated automatically. */
@@ -239,7 +239,6 @@ export declare class RequestList implements IRequestLoader {
239
239
  * @internal
240
240
  */
241
241
  areRequestsPersisted: boolean;
242
- private sources;
243
242
  /**
244
243
  * To create new instance of `RequestList` we need to use `RequestList.open()` factory method.
245
244
  * @param options All `RequestList` configuration options
@@ -131,8 +131,7 @@ export class RequestList {
131
131
  #initialState;
132
132
  #store;
133
133
  #keepDuplicateUrls;
134
- // kept as TS-private: request_list tests read this field directly
135
- sources;
134
+ #sources;
136
135
  #sourcesFunction;
137
136
  #proxyConfiguration;
138
137
  #httpClient;
@@ -153,7 +152,7 @@ export class RequestList {
153
152
  // If this option is set then all requests will get a pre-generated unique ID and duplicate URLs will be kept in the list.
154
153
  this.#keepDuplicateUrls = keepDuplicateUrls;
155
154
  // Will be empty after initialization to save memory.
156
- this.sources = sources ? [...sources] : [];
155
+ this.#sources = sources ? [...sources] : [];
157
156
  this.#sourcesFunction = sourcesFunction;
158
157
  // The proxy configuration used for `requestsFromUrl` requests.
159
158
  this.#proxyConfiguration = proxyConfiguration;
@@ -195,11 +194,11 @@ export class RequestList {
195
194
  async addPersistedRequests(persistedRequests) {
196
195
  // We don't need the sources so we purge them to
197
196
  // prevent them from hanging in memory.
198
- for (let i = 0; i < this.sources.length; i++) {
197
+ for (let i = 0; i < this.#sources.length; i++) {
199
198
  // oxlint-disable-next-line typescript/no-array-delete -- intentional, drop the slot so V8 can collect the object
200
- delete this.sources[i];
199
+ delete this.#sources[i];
201
200
  }
202
- this.sources = [];
201
+ this.#sources = [];
203
202
  this.areRequestsPersisted = true;
204
203
  const requestStream = createDeserialize(persistedRequests);
205
204
  for await (const request of requestStream) {
@@ -214,13 +213,13 @@ export class RequestList {
214
213
  */
215
214
  async addRequestsFromSources() {
216
215
  // We'll load all sources in sequence to ensure that they get loaded in the right order.
217
- const sourcesCount = this.sources.length;
216
+ const sourcesCount = this.#sources.length;
218
217
  for (let i = 0; i < sourcesCount; i++) {
219
- const source = this.sources[i];
218
+ const source = this.#sources[i];
220
219
  // Using delete here to drop the original object ASAP to free memory
221
220
  // .pop would reverse the array and .shift is SLOW.
222
221
  // oxlint-disable-next-line typescript/no-array-delete
223
- delete this.sources[i];
222
+ delete this.#sources[i];
224
223
  if (typeof source === 'object' && source.requestsFromUrl) {
225
224
  const fetchedRequests = await this.fetchRequestsFromUrl(source);
226
225
  await this.addFetchedRequests(source, fetchedRequests);
@@ -230,7 +229,7 @@ export class RequestList {
230
229
  }
231
230
  }
232
231
  // Drop the original array full of empty indexes.
233
- this.sources = [];
232
+ this.#sources = [];
234
233
  if (this.#sourcesFunction) {
235
234
  try {
236
235
  const sourcesFromFunction = await this.#sourcesFunction();
@@ -49,8 +49,6 @@ export declare class RequestQueue implements IStorage, IRequestManager {
49
49
  readonly name?: string;
50
50
  readonly backend: RequestQueueBackend;
51
51
  readonly log: CrawleeLogger;
52
- private requestCache;
53
- private inProgressRequestBatchCount;
54
52
  /**
55
53
  * Backend-independent usage counters tracked for this request queue (write operations and
56
54
  * queue-head reads issued to the underlying storage backend). Counted per backend call.
@@ -98,8 +98,7 @@ export class RequestQueue {
98
98
  backend;
99
99
  #proxyConfiguration;
100
100
  log;
101
- // kept as TS-private: request_queue tests read this cache directly
102
- requestCache;
101
+ #requestCache;
103
102
  /**
104
103
  * Remembers the `requestId` of every request already submitted to the client — including background
105
104
  * batches that `requestCache` skips — so overlapping URL sets aren't re-submitted.
@@ -107,8 +106,7 @@ export class RequestQueue {
107
106
  */
108
107
  #requestSeenCache;
109
108
  #queuePausedForMigration = false;
110
- // kept as TS-private: packages/core/test request-queue tests write this counter directly
111
- inProgressRequestBatchCount = 0;
109
+ #inProgressRequestBatchCount = 0;
112
110
  /**
113
111
  * The largest expected request-processing time (in seconds) seen so far via
114
112
  * {@link setExpectedRequestProcessingTimeSecs}. Used to ensure that value is only ever raised, never
@@ -137,7 +135,7 @@ export class RequestQueue {
137
135
  this.#events = serviceLocator.getEventManager();
138
136
  this.backend = options.backend;
139
137
  this.#proxyConfiguration = options.proxyConfiguration;
140
- this.requestCache = new LruCache({ maxLength: MAX_CACHED_REQUESTS });
138
+ this.#requestCache = new LruCache({ maxLength: MAX_CACHED_REQUESTS });
141
139
  this.#requestSeenCache = new RequestDeduplicationCache();
142
140
  this.log = serviceLocator.getLogger().child({ prefix: `RequestQueue(${this.id}, ${this.name ?? 'no-name'})` });
143
141
  this.#events.on(EventType.MIGRATING, async () => {
@@ -191,7 +189,7 @@ export class RequestQueue {
191
189
  return this.addRequestDeferred(transaction, request, forefront);
192
190
  }
193
191
  const cacheKey = getRequestId(request.uniqueKey);
194
- const cachedInfo = this.requestCache.get(cacheKey);
192
+ const cachedInfo = this.#requestCache.get(cacheKey);
195
193
  if (cachedInfo) {
196
194
  request.id = cachedInfo.id;
197
195
  this.recordRequestJournalEntry(transaction, [request], forefront, true);
@@ -275,7 +273,7 @@ export class RequestQueue {
275
273
  // The caches hold real backend ids. Only *writing* provisional ids to them would be wrong;
276
274
  // reading saves a probe. Same lookup as the write-through path.
277
275
  const cacheKey = getRequestId(request.uniqueKey);
278
- const cachedInfo = this.requestCache.get(cacheKey);
276
+ const cachedInfo = this.#requestCache.get(cacheKey);
279
277
  const knownRequestId = cachedInfo?.id ?? this.#requestSeenCache.get(cacheKey);
280
278
  if (knownRequestId) {
281
279
  this.recordRequestJournalEntry(transaction, [request], forefront, false);
@@ -418,7 +416,7 @@ export class RequestQueue {
418
416
  for (const request of requests) {
419
417
  const cacheKey = getCachedRequestId(request.uniqueKey);
420
418
  // Prefer the full `requestCache` record; fall back to the dedup cache for background batches it skips.
421
- const cachedInfo = this.requestCache.get(cacheKey);
419
+ const cachedInfo = this.#requestCache.get(cacheKey);
422
420
  const knownRequestId = cachedInfo?.id ?? this.#requestSeenCache.get(cacheKey);
423
421
  if (knownRequestId) {
424
422
  request.id = knownRequestId;
@@ -517,9 +515,9 @@ export class RequestQueue {
517
515
  return processedRequests;
518
516
  },
519
517
  trackBackgroundBatches: (batches) => {
520
- this.inProgressRequestBatchCount += 1;
518
+ this.#inProgressRequestBatchCount += 1;
521
519
  void batches.finally(() => {
522
- this.inProgressRequestBatchCount -= 1;
520
+ this.#inProgressRequestBatchCount -= 1;
523
521
  });
524
522
  },
525
523
  });
@@ -580,7 +578,7 @@ export class RequestQueue {
580
578
  async markRequestAsHandled(request) {
581
579
  rejectOperationInTransaction('RequestQueue.markRequestAsHandled()', 'it is part of the crawler request-processing bookkeeping, which a transaction must not affect.');
582
580
  parseArgument(request, handledRequestSchema);
583
- const forefront = this.requestCache.get(getRequestId(request.uniqueKey))?.forefront ?? false;
581
+ const forefront = this.#requestCache.get(getRequestId(request.uniqueKey))?.forefront ?? false;
584
582
  const handledAt = request.handledAt ?? new Date().toISOString();
585
583
  this.#statsTracker.add('writeCount');
586
584
  const processedRequest = await this.backend.markRequestAsHandled({
@@ -655,7 +653,7 @@ export class RequestQueue {
655
653
  async isFinished() {
656
654
  const transaction = activeStorageTransaction();
657
655
  // We are not finished if we're still adding new requests in the background.
658
- if (this.inProgressRequestBatchCount > 0) {
656
+ if (this.#inProgressRequestBatchCount > 0) {
659
657
  return false;
660
658
  }
661
659
  // Requests buffered by the active transaction count as pending from its point of view.
@@ -686,8 +684,8 @@ export class RequestQueue {
686
684
  */
687
685
  cacheRequest(cacheKey, queueOperationInfo) {
688
686
  // Remove the previous entry, as otherwise our cache will never update 👀
689
- this.requestCache.remove(cacheKey);
690
- this.requestCache.add(cacheKey, {
687
+ this.#requestCache.remove(cacheKey);
688
+ this.#requestCache.add(cacheKey, {
691
689
  id: queueOperationInfo.requestId,
692
690
  isHandled: queueOperationInfo.wasAlreadyHandled,
693
691
  uniqueKey: queueOperationInfo.uniqueKey,
@@ -713,9 +711,9 @@ export class RequestQueue {
713
711
  rejectOperationInTransaction('RequestQueue.purge()');
714
712
  await this.backend.purge();
715
713
  // Reset in-memory bookkeeping so the queue behaves as if freshly opened.
716
- this.requestCache.clear();
714
+ this.#requestCache.clear();
717
715
  this.#requestSeenCache.clear();
718
- this.inProgressRequestBatchCount = 0;
716
+ this.#inProgressRequestBatchCount = 0;
719
717
  // Reset the expected-processing-time high-water mark too, otherwise the monotonic-raise guard
720
718
  // in `setExpectedRequestProcessingTimeSecs` would let a value raised in an earlier run leak into a
721
719
  // later one and silently swallow a lower hint (the queue is meant to be reusable across runs).