@crawlee/core 4.0.0-beta.211 → 4.0.0-beta.212

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.
@@ -129,23 +129,4 @@ export declare class Configuration {
129
129
  * Delegates to the global ServiceLocator, making it the single source of truth for service management.
130
130
  */
131
131
  static getGlobalConfiguration(): Configuration;
132
- /**
133
- * Resolves all field values once using the priority chain:
134
- * constructor options > env vars > crawlee.json > schema defaults.
135
- */
136
- private static resolveAll;
137
- /**
138
- * Registers getters (and throwing setters) on the instance for each field.
139
- */
140
- private registerAccessors;
141
- /**
142
- * Reads the first defined env var value for a field definition.
143
- * Empty strings are treated as unset, falling through to crawlee.json or schema defaults.
144
- * (Crawlee v3 coerced `''` to `false`/`0`/`''` per type — v4 drops that for consistency.)
145
- */
146
- private static readEnvVar;
147
- /**
148
- * Loads config options from crawlee.json in the current working directory.
149
- */
150
- private static loadFileOptions;
151
132
  }
package/configuration.js CHANGED
@@ -143,9 +143,9 @@ export class Configuration {
143
143
  */
144
144
  constructor(options = {}) {
145
145
  const fields = this.constructor.fields;
146
- const fileOptions = Configuration.loadFileOptions();
147
- this.#resolvedValues = Configuration.resolveAll(fields, options, fileOptions);
148
- this.registerAccessors();
146
+ const fileOptions = Configuration.#loadFileOptions();
147
+ this.#resolvedValues = Configuration.#resolveAll(fields, options, fileOptions);
148
+ this.#registerAccessors();
149
149
  // Set the log level
150
150
  const logLevel = this.logLevel;
151
151
  if (logLevel != null) {
@@ -164,7 +164,7 @@ export class Configuration {
164
164
  * Resolves all field values once using the priority chain:
165
165
  * constructor options > env vars > crawlee.json > schema defaults.
166
166
  */
167
- static resolveAll(fields, userOptions, fileOptions) {
167
+ static #resolveAll(fields, userOptions, fileOptions) {
168
168
  const values = {};
169
169
  for (const [key, fieldDef] of Object.entries(fields)) {
170
170
  // 1. Constructor options (highest priority)
@@ -173,7 +173,7 @@ export class Configuration {
173
173
  continue;
174
174
  }
175
175
  // 2. Environment variables
176
- const envValue = Configuration.readEnvVar(fieldDef);
176
+ const envValue = Configuration.#readEnvVar(fieldDef);
177
177
  if (envValue != null) {
178
178
  values[key] = fieldDef.schema.parse(envValue);
179
179
  continue;
@@ -192,7 +192,7 @@ export class Configuration {
192
192
  /**
193
193
  * Registers getters (and throwing setters) on the instance for each field.
194
194
  */
195
- registerAccessors() {
195
+ #registerAccessors() {
196
196
  const fields = this.constructor.fields;
197
197
  const descriptors = {};
198
198
  for (const key of Object.keys(fields)) {
@@ -212,7 +212,7 @@ export class Configuration {
212
212
  * Empty strings are treated as unset, falling through to crawlee.json or schema defaults.
213
213
  * (Crawlee v3 coerced `''` to `false`/`0`/`''` per type — v4 drops that for consistency.)
214
214
  */
215
- static readEnvVar(fieldDef) {
215
+ static #readEnvVar(fieldDef) {
216
216
  if (!fieldDef.envVar)
217
217
  return undefined;
218
218
  const envVars = Array.isArray(fieldDef.envVar) ? fieldDef.envVar : [fieldDef.envVar];
@@ -226,7 +226,7 @@ export class Configuration {
226
226
  /**
227
227
  * Loads config options from crawlee.json in the current working directory.
228
228
  */
229
- static loadFileOptions() {
229
+ static #loadFileOptions() {
230
230
  try {
231
231
  const file = readFileSync(join(process.cwd(), 'crawlee.json'));
232
232
  return JSON.parse(file.toString());
@@ -29,11 +29,4 @@ export declare class LocalEventManager extends EventManager {
29
29
  * @internal
30
30
  */
31
31
  isContainerizedWrapper(): Promise<boolean>;
32
- /**
33
- * Creates a SystemInfo object based on local metrics.
34
- */
35
- private createSystemInfo;
36
- private createCpuInfo;
37
- private createMemoryInfo;
38
- private getMemoryInfo;
39
32
  }
@@ -44,7 +44,7 @@ export class LocalEventManager extends EventManager {
44
44
  * @internal
45
45
  */
46
46
  async emitSystemInfoEvent(intervalCallback) {
47
- const info = await this.createSystemInfo({
47
+ const info = await this.#createSystemInfo({
48
48
  maxUsedCpuRatio: serviceLocator.getConfiguration().maxUsedCpuRatio,
49
49
  });
50
50
  this.events.emit(EventType.SYSTEM_INFO, info);
@@ -60,14 +60,14 @@ export class LocalEventManager extends EventManager {
60
60
  /**
61
61
  * Creates a SystemInfo object based on local metrics.
62
62
  */
63
- async createSystemInfo(options) {
63
+ async #createSystemInfo(options) {
64
64
  return {
65
65
  createdAt: new Date(),
66
- ...(await this.createCpuInfo(options)),
67
- ...(await this.createMemoryInfo()),
66
+ ...(await this.#createCpuInfo(options)),
67
+ ...(await this.#createMemoryInfo()),
68
68
  };
69
69
  }
70
- async createCpuInfo(options) {
70
+ async #createCpuInfo(options) {
71
71
  const { getCurrentCpuTicksV2 } = await import('../system-info/cpu-info.js');
72
72
  const usedCpuRatio = await getCurrentCpuTicksV2({
73
73
  containerized: await this.isContainerizedWrapper(),
@@ -78,9 +78,9 @@ export class LocalEventManager extends EventManager {
78
78
  isCpuOverloaded: usedCpuRatio > options.maxUsedCpuRatio,
79
79
  };
80
80
  }
81
- async createMemoryInfo() {
81
+ async #createMemoryInfo() {
82
82
  try {
83
- const memInfo = await this.getMemoryInfo();
83
+ const memInfo = await this.#getMemoryInfo();
84
84
  return {
85
85
  memTotalBytes: memInfo.totalBytes,
86
86
  memCurrentBytes: memInfo.mainProcessBytes + memInfo.childProcessesBytes,
@@ -91,7 +91,7 @@ export class LocalEventManager extends EventManager {
91
91
  return {};
92
92
  }
93
93
  }
94
- async getMemoryInfo() {
94
+ async #getMemoryInfo() {
95
95
  const { getMemoryInfo } = await import('../system-info/memory-info.js');
96
96
  return getMemoryInfo({
97
97
  containerized: await this.isContainerizedWrapper(),
@@ -25,16 +25,11 @@ export declare class DatasetBackend<Data extends Dictionary = Dictionary> extend
25
25
  accessedAt: Date;
26
26
  modifiedAt: Date;
27
27
  itemCount: number;
28
- private readonly storageBackend;
29
28
  constructor(options: DatasetBackendOptions);
30
29
  getMetadata(): Promise<storage.DatasetInfo>;
31
30
  drop(): Promise<void>;
32
31
  purge(): Promise<void>;
33
32
  getData(options?: storage.DatasetBackendListOptions): Promise<storage.PaginatedList<Data>>;
34
- private getDataPage;
35
33
  pushData(items: Data[]): Promise<void>;
36
34
  toDatasetInfo(): storage.DatasetInfo;
37
- private generateLocalEntryName;
38
- private getStartAndEndIndexes;
39
- private updateTimestamps;
40
35
  }
@@ -23,20 +23,19 @@ export class DatasetBackend extends BaseClient {
23
23
  modifiedAt = new Date();
24
24
  itemCount = 0;
25
25
  #datasetEntries = new Map();
26
- // kept as TS-private: storage-backend tests read this field at runtime
27
- storageBackend;
26
+ #storageBackend;
28
27
  constructor(options) {
29
28
  super(options.id ?? randomUUID());
30
29
  this.name = options.name;
31
30
  this.cacheKey = options.cacheKey ?? this.name ?? this.id;
32
- this.storageBackend = options.storageBackend;
31
+ this.#storageBackend = options.storageBackend;
33
32
  }
34
33
  async getMetadata() {
35
- this.updateTimestamps(false);
34
+ this.#updateTimestamps(false);
36
35
  return this.toDatasetInfo();
37
36
  }
38
37
  async drop() {
39
- if (this.storageBackend.evictBackend('Dataset', this.id)) {
38
+ if (this.#storageBackend.evictBackend('Dataset', this.id)) {
40
39
  this.itemCount = 0;
41
40
  this.#datasetEntries.clear();
42
41
  }
@@ -44,25 +43,25 @@ export class DatasetBackend extends BaseClient {
44
43
  async purge() {
45
44
  this.itemCount = 0;
46
45
  this.#datasetEntries.clear();
47
- this.updateTimestamps(true);
46
+ this.#updateTimestamps(true);
48
47
  }
49
48
  getData(options = {}) {
50
49
  const { desc, limit, offset } = parseArgument(options, schemas.datasetListItemsOptions);
51
- return this.getDataPage({
50
+ return this.#getDataPage({
52
51
  desc,
53
52
  offset: offset ?? 0,
54
53
  limit: Math.min(limit ?? LIST_ITEMS_LIMIT, LIST_ITEMS_LIMIT),
55
54
  });
56
55
  }
57
- async getDataPage(options = {}) {
56
+ async #getDataPage(options = {}) {
58
57
  const { limit = LIST_ITEMS_LIMIT, offset = 0, desc } = options;
59
- const [start, end] = this.getStartAndEndIndexes(desc ? Math.max(this.itemCount - offset - limit, 0) : offset, limit);
58
+ const [start, end] = this.#getStartAndEndIndexes(desc ? Math.max(this.itemCount - offset - limit, 0) : offset, limit);
60
59
  const items = [];
61
60
  for (let idx = start; idx < end; idx++) {
62
- const entryNumber = this.generateLocalEntryName(idx);
61
+ const entryNumber = this.#generateLocalEntryName(idx);
63
62
  items.push(this.#datasetEntries.get(entryNumber));
64
63
  }
65
- this.updateTimestamps(false);
64
+ this.#updateTimestamps(false);
66
65
  return {
67
66
  count: items.length,
68
67
  desc: desc ?? false,
@@ -74,10 +73,10 @@ export class DatasetBackend extends BaseClient {
74
73
  }
75
74
  async pushData(items) {
76
75
  for (const entry of items) {
77
- const idx = this.generateLocalEntryName(++this.itemCount);
76
+ const idx = this.#generateLocalEntryName(++this.itemCount);
78
77
  this.#datasetEntries.set(idx, JSON.parse(JSON.stringify(entry)));
79
78
  }
80
- this.updateTimestamps(true);
79
+ this.#updateTimestamps(true);
81
80
  }
82
81
  toDatasetInfo() {
83
82
  return {
@@ -89,15 +88,15 @@ export class DatasetBackend extends BaseClient {
89
88
  name: this.name,
90
89
  };
91
90
  }
92
- generateLocalEntryName(idx) {
91
+ #generateLocalEntryName(idx) {
93
92
  return idx.toString().padStart(LOCAL_ENTRY_NAME_DIGITS, '0');
94
93
  }
95
- getStartAndEndIndexes(offset, limit = this.itemCount) {
94
+ #getStartAndEndIndexes(offset, limit = this.itemCount) {
96
95
  const start = offset + 1;
97
96
  const end = Math.min(offset + limit, this.itemCount) + 1;
98
97
  return [start, end];
99
98
  }
100
- updateTimestamps(hasBeenModified) {
99
+ #updateTimestamps(hasBeenModified) {
101
100
  this.accessedAt = new Date();
102
101
  if (hasBeenModified) {
103
102
  this.modifiedAt = new Date();
@@ -29,7 +29,6 @@ export declare class KeyValueStoreBackend extends BaseClient implements storage.
29
29
  createdAt: Date;
30
30
  accessedAt: Date;
31
31
  modifiedAt: Date;
32
- private readonly storageBackend;
33
32
  constructor(options: KeyValueStoreBackendOptions);
34
33
  getMetadata(): Promise<storage.KeyValueStoreInfo>;
35
34
  drop(): Promise<void>;
@@ -52,5 +51,4 @@ export declare class KeyValueStoreBackend extends BaseClient implements storage.
52
51
  setValue(record: storage.KeyValueStoreInputRecord): Promise<void>;
53
52
  deleteValue(key: string): Promise<void>;
54
53
  toKeyValueStoreInfo(): storage.KeyValueStoreInfo;
55
- private updateTimestamps;
56
54
  }
@@ -31,26 +31,25 @@ export class KeyValueStoreBackend extends BaseClient {
31
31
  accessedAt = new Date();
32
32
  modifiedAt = new Date();
33
33
  #keyValueEntries = new Map();
34
- // kept as TS-private: storage-backend tests read this field at runtime
35
- storageBackend;
34
+ #storageBackend;
36
35
  constructor(options) {
37
36
  super(options.id ?? randomUUID());
38
37
  this.name = options.name;
39
38
  this.cacheKey = options.cacheKey ?? this.name ?? this.id;
40
- this.storageBackend = options.storageBackend;
39
+ this.#storageBackend = options.storageBackend;
41
40
  }
42
41
  async getMetadata() {
43
- this.updateTimestamps(false);
42
+ this.#updateTimestamps(false);
44
43
  return this.toKeyValueStoreInfo();
45
44
  }
46
45
  async drop() {
47
- if (this.storageBackend.evictBackend('KeyValueStore', this.id)) {
46
+ if (this.#storageBackend.evictBackend('KeyValueStore', this.id)) {
48
47
  this.#keyValueEntries.clear();
49
48
  }
50
49
  }
51
50
  async purge() {
52
51
  this.#keyValueEntries.clear();
53
- this.updateTimestamps(true);
52
+ this.#updateTimestamps(true);
54
53
  }
55
54
  async listKeys(options = {}) {
56
55
  const { prefix, exclusiveStartKey, limit } = parseArgument(options, schemas.keyValueStoreListKeysOptions);
@@ -77,7 +76,7 @@ export class KeyValueStoreBackend extends BaseClient {
77
76
  const isTruncated = limit !== undefined && filteredItems.length > limit;
78
77
  const pageItems = isTruncated ? filteredItems.slice(0, limit) : filteredItems;
79
78
  const nextExclusiveStartKey = isTruncated ? pageItems[pageItems.length - 1].key : undefined;
80
- this.updateTimestamps(false);
79
+ this.#updateTimestamps(false);
81
80
  return {
82
81
  items: pageItems,
83
82
  count: pageItems.length,
@@ -121,7 +120,7 @@ export class KeyValueStoreBackend extends BaseClient {
121
120
  // frontend treats it as "no content type" rather than a bogus value.
122
121
  contentType: entry.contentType ?? (mime.contentType(entry.extension) || undefined),
123
122
  };
124
- this.updateTimestamps(false);
123
+ this.#updateTimestamps(false);
125
124
  return record;
126
125
  }
127
126
  async setValue(record) {
@@ -153,13 +152,13 @@ export class KeyValueStoreBackend extends BaseClient {
153
152
  contentType,
154
153
  };
155
154
  this.#keyValueEntries.set(key, normalizedRecord);
156
- this.updateTimestamps(true);
155
+ this.#updateTimestamps(true);
157
156
  }
158
157
  async deleteValue(key) {
159
158
  parseArgument(key, keySchema);
160
159
  if (this.#keyValueEntries.has(key)) {
161
160
  this.#keyValueEntries.delete(key);
162
- this.updateTimestamps(true);
161
+ this.#updateTimestamps(true);
163
162
  }
164
163
  }
165
164
  toKeyValueStoreInfo() {
@@ -171,7 +170,7 @@ export class KeyValueStoreBackend extends BaseClient {
171
170
  modifiedAt: this.modifiedAt,
172
171
  };
173
172
  }
174
- updateTimestamps(hasBeenModified) {
173
+ #updateTimestamps(hasBeenModified) {
175
174
  this.accessedAt = new Date();
176
175
  if (hasBeenModified) {
177
176
  this.modifiedAt = new Date();
@@ -34,28 +34,10 @@ export declare class RequestQueueBackend extends BaseClient implements storage.R
34
34
  modifiedAt: Date;
35
35
  handledRequestCount: number;
36
36
  pendingRequestCount: number;
37
- private readonly storageBackend;
38
37
  constructor(options: RequestQueueBackendOptions);
39
38
  getMetadata(): Promise<storage.RequestQueueInfo>;
40
39
  drop(): Promise<void>;
41
40
  purge(): Promise<void>;
42
- private requestKeyIterator;
43
- /**
44
- * Scans the queue and returns the pending head — requests that are neither handled nor currently
45
- * in progress — ordered by `orderNo`, deduplicated.
46
- *
47
- * When `detectInProgressRequests` is set, the result also carries an `hasInProgressRequests` flag
48
- * telling whether any unhandled-but-in-progress request was skipped along the way. It lets
49
- * {@link isFinished} distinguish "no work left at all" from "work remains, but it is currently being
50
- * processed". Without it, a consumer with concurrency could consider the queue finished and shut the
51
- * crawler down while it is still handling the last requests.
52
- *
53
- * Computing the flag is expensive: because an in-progress request may sit anywhere in the queue, it
54
- * forces a scan of every pending entry even when only `limit` items are wanted. Callers that only
55
- * need the head (e.g. {@link fetchNextRequest}, {@link isEmpty}) leave it off so the scan can stop as
56
- * soon as the page is filled, keeping those calls O(head) instead of O(pending).
57
- */
58
- private listPendingHead;
59
41
  fetchNextRequest(): Promise<storage.UpdateRequestSchema | undefined>;
60
42
  addBatchOfRequests(requests: storage.RequestSchema[], options?: storage.RequestQueueOperationOptions): Promise<storage.BatchAddRequestsResult>;
61
43
  getRequest(uniqueKey: string): Promise<storage.UpdateRequestSchema | undefined>;
@@ -70,8 +52,4 @@ export declare class RequestQueueBackend extends BaseClient implements storage.R
70
52
  */
71
53
  listItems(): Promise<storage.UpdateRequestSchema[]>;
72
54
  toRequestQueueInfo(): storage.RequestQueueInfo;
73
- private updateTimestamps;
74
- private jsonToRequest;
75
- private createInternalRequest;
76
- private calculateOrderNo;
77
55
  }
@@ -43,16 +43,15 @@ export class RequestQueueBackend extends BaseClient {
43
43
  * ever walk the unhandled tail instead of every request the queue has ever seen.
44
44
  */
45
45
  #pendingRequestIds = new Set();
46
- // kept as TS-private: storage-backend tests read this field at runtime
47
- storageBackend;
46
+ #storageBackend;
48
47
  constructor(options) {
49
48
  super(options.id ?? randomUUID());
50
49
  this.name = options.name;
51
50
  this.cacheKey = options.cacheKey ?? this.name ?? this.id;
52
- this.storageBackend = options.storageBackend;
51
+ this.#storageBackend = options.storageBackend;
53
52
  }
54
53
  async getMetadata() {
55
- this.updateTimestamps(false);
54
+ this.#updateTimestamps(false);
56
55
  return this.toRequestQueueInfo();
57
56
  }
58
57
  async drop() {
@@ -61,7 +60,7 @@ export class RequestQueueBackend extends BaseClient {
61
60
  // removed, which `listPendingHead` would then dereference as `undefined`.
62
61
  await this.#queueStateMutex.wait();
63
62
  try {
64
- if (this.storageBackend.evictBackend('RequestQueue', this.id)) {
63
+ if (this.#storageBackend.evictBackend('RequestQueue', this.id)) {
65
64
  this.pendingRequestCount = 0;
66
65
  // Clear all in-memory state, consistent with `purge`. Clearing `requests` alone would
67
66
  // leave dangling ids in `forefrontRequestIds`/`inProgressRequestIds`, which a later head
@@ -88,13 +87,13 @@ export class RequestQueueBackend extends BaseClient {
88
87
  this.#inProgressRequestIds.clear();
89
88
  this.handledRequestCount = 0;
90
89
  this.pendingRequestCount = 0;
91
- this.updateTimestamps(true);
90
+ this.#updateTimestamps(true);
92
91
  }
93
92
  finally {
94
93
  this.#queueStateMutex.shift();
95
94
  }
96
95
  }
97
- *requestKeyIterator() {
96
+ *#requestKeyIterator() {
98
97
  for (let i = this.#forefrontRequestIds.length - 1; i >= 0; i--) {
99
98
  yield this.#forefrontRequestIds[i];
100
99
  }
@@ -115,14 +114,14 @@ export class RequestQueueBackend extends BaseClient {
115
114
  * need the head (e.g. {@link fetchNextRequest}, {@link isEmpty}) leave it off so the scan can stop as
116
115
  * soon as the page is filled, keeping those calls O(head) instead of O(pending).
117
116
  */
118
- async listPendingHead(limit, detectInProgressRequests = false) {
117
+ async #listPendingHead(limit, detectInProgressRequests = false) {
119
118
  const items = [];
120
119
  let hasInProgressRequests = false;
121
120
  // Tracks processed request IDs to avoid duplicates (request in both `forefrontRequestIds` and `requests`).
122
121
  const seenRequestIds = new Set();
123
122
  // Tracks handled request IDs from `forefrontRequestIds` to be removed.
124
123
  const handledForefrontIds = new Set();
125
- for (const requestId of this.requestKeyIterator()) {
124
+ for (const requestId of this.#requestKeyIterator()) {
126
125
  // Once the requested page is filled we can stop — unless the caller asked us to detect
127
126
  // in-progress requests and we have not yet seen one, in which case we must keep scanning.
128
127
  if (items.length >= limit && (!detectInProgressRequests || hasInProgressRequests)) {
@@ -156,17 +155,17 @@ export class RequestQueueBackend extends BaseClient {
156
155
  };
157
156
  }
158
157
  async fetchNextRequest() {
159
- this.updateTimestamps(false);
158
+ this.#updateTimestamps(false);
160
159
  await this.#queueStateMutex.wait();
161
160
  try {
162
- const { items: [head], } = await this.listPendingHead(1);
161
+ const { items: [head], } = await this.#listPendingHead(1);
163
162
  if (!head) {
164
163
  return undefined;
165
164
  }
166
165
  // Mark the request as in progress so it is not handed out again until it is handled or
167
166
  // reclaimed. The request keeps its `orderNo` (and thus its forefront / normal ordering).
168
167
  this.#inProgressRequestIds.add(head.id);
169
- return this.jsonToRequest(head.json) ?? undefined;
168
+ return this.#jsonToRequest(head.json) ?? undefined;
170
169
  }
171
170
  finally {
172
171
  this.#queueStateMutex.shift();
@@ -185,7 +184,7 @@ export class RequestQueueBackend extends BaseClient {
185
184
  unprocessedRequests: [],
186
185
  };
187
186
  for (const model of requests) {
188
- const requestModel = this.createInternalRequest(model, options.forefront);
187
+ const requestModel = this.#createInternalRequest(model, options.forefront);
189
188
  const existingRequestWithId = this.#requests.get(requestModel.id);
190
189
  if (existingRequestWithId) {
191
190
  result.processedRequests.push({
@@ -216,7 +215,7 @@ export class RequestQueueBackend extends BaseClient {
216
215
  wasAlreadyPresent: false,
217
216
  });
218
217
  }
219
- this.updateTimestamps(true);
218
+ this.#updateTimestamps(true);
220
219
  return result;
221
220
  }
222
221
  finally {
@@ -225,14 +224,14 @@ export class RequestQueueBackend extends BaseClient {
225
224
  }
226
225
  async getRequest(uniqueKey) {
227
226
  parseArgument(uniqueKey, uniqueKeySchema);
228
- this.updateTimestamps(false);
227
+ this.#updateTimestamps(false);
229
228
  const id = uniqueKeyToRequestId(uniqueKey);
230
229
  const json = this.#requests.get(id)?.json;
231
- return this.jsonToRequest(json);
230
+ return this.#jsonToRequest(json);
232
231
  }
233
232
  async markRequestAsHandled(request) {
234
233
  parseArgument(request, schemas.storageRequest);
235
- this.updateTimestamps(false);
234
+ this.#updateTimestamps(false);
236
235
  // Serialize against other mutators (and the head scans in `isEmpty`/`isFinished`) so the shared
237
236
  // `requests` map, `inProgressRequestIds` set and request counts stay consistent across the
238
237
  // `await` points below.
@@ -249,7 +248,7 @@ export class RequestQueueBackend extends BaseClient {
249
248
  // A handled request has `orderNo === null`. Marking it again is an idempotent no-op.
250
249
  const wasAlreadyHandled = existingRequest.orderNo === null;
251
250
  const handledAt = request.handledAt ?? new Date().toISOString();
252
- const requestModel = this.createInternalRequest({ ...request, handledAt }, false);
251
+ const requestModel = this.#createInternalRequest({ ...request, handledAt }, false);
253
252
  this.#requests.set(id, requestModel);
254
253
  this.#pendingRequestIds.delete(id);
255
254
  // The request is no longer in progress for this client.
@@ -258,7 +257,7 @@ export class RequestQueueBackend extends BaseClient {
258
257
  this.pendingRequestCount -= 1;
259
258
  this.handledRequestCount += 1;
260
259
  }
261
- this.updateTimestamps(true);
260
+ this.#updateTimestamps(true);
262
261
  return {
263
262
  requestId: id,
264
263
  wasAlreadyHandled,
@@ -272,7 +271,7 @@ export class RequestQueueBackend extends BaseClient {
272
271
  async reclaimRequest(request, options = {}) {
273
272
  parseArgument(request, schemas.storageRequest);
274
273
  parseArgument(options, schemas.requestQueueOperationOptions);
275
- this.updateTimestamps(false);
274
+ this.#updateTimestamps(false);
276
275
  // Serialize against other mutators (and the head scans in `isEmpty`/`isFinished`) so the shared
277
276
  // `requests` map, `forefrontRequestIds` array and `inProgressRequestIds` set stay consistent
278
277
  // across the `await` points below.
@@ -289,14 +288,14 @@ export class RequestQueueBackend extends BaseClient {
289
288
  }
290
289
  // Reclaiming resets the `orderNo` to a fresh timestamp, restoring the request to the queue
291
290
  // (at the front if `forefront`).
292
- const requestModel = this.createInternalRequest(request, options.forefront);
291
+ const requestModel = this.#createInternalRequest(request, options.forefront);
293
292
  this.#requests.set(id, requestModel);
294
293
  // The request is no longer in progress for this client.
295
294
  this.#inProgressRequestIds.delete(id);
296
295
  if (options.forefront) {
297
296
  this.#forefrontRequestIds.push(id);
298
297
  }
299
- this.updateTimestamps(true);
298
+ this.#updateTimestamps(true);
300
299
  return {
301
300
  requestId: id,
302
301
  wasAlreadyHandled: false,
@@ -308,7 +307,7 @@ export class RequestQueueBackend extends BaseClient {
308
307
  }
309
308
  }
310
309
  async isEmpty() {
311
- this.updateTimestamps(false);
310
+ this.#updateTimestamps(false);
312
311
  // "Empty" means there is nothing left to fetch right now — i.e. the next `fetchNextRequest`
313
312
  // would return `null`. Requests that are currently in progress are intentionally NOT counted
314
313
  // here: they are not fetchable, so the queue is empty from a consumer's point of view. Whether
@@ -319,7 +318,7 @@ export class RequestQueueBackend extends BaseClient {
319
318
  // racing a concurrent mutator (e.g. `addBatchOfRequests`) at its `await` points.
320
319
  await this.#queueStateMutex.wait();
321
320
  try {
322
- const { items } = await this.listPendingHead(1);
321
+ const { items } = await this.#listPendingHead(1);
323
322
  return items.length === 0;
324
323
  }
325
324
  finally {
@@ -327,7 +326,7 @@ export class RequestQueueBackend extends BaseClient {
327
326
  }
328
327
  }
329
328
  async isFinished() {
330
- this.updateTimestamps(false);
329
+ this.#updateTimestamps(false);
331
330
  // The queue is finished only when there is nothing left to fetch AND nothing currently in
332
331
  // progress. Counting in-progress requests is what allows a crawler with concurrency to keep
333
332
  // waiting while it still holds the last requests, instead of finishing prematurely.
@@ -339,7 +338,7 @@ export class RequestQueueBackend extends BaseClient {
339
338
  // racing a concurrent mutator (e.g. `addBatchOfRequests`) at its `await` points.
340
339
  await this.#queueStateMutex.wait();
341
340
  try {
342
- const { items, hasInProgressRequests } = await this.listPendingHead(1, true);
341
+ const { items, hasInProgressRequests } = await this.#listPendingHead(1, true);
343
342
  return items.length === 0 && !hasInProgressRequests;
344
343
  }
345
344
  finally {
@@ -352,13 +351,13 @@ export class RequestQueueBackend extends BaseClient {
352
351
  * nothing is marked in progress.
353
352
  */
354
353
  async listItems() {
355
- this.updateTimestamps(false);
354
+ this.#updateTimestamps(false);
356
355
  // `listPendingHead` prunes `forefrontRequestIds` as it scans, so we must hold the queue-state
357
356
  // mutex to avoid racing a concurrent mutator at its `await` points.
358
357
  await this.#queueStateMutex.wait();
359
358
  try {
360
- const { items } = await this.listPendingHead(Number.POSITIVE_INFINITY);
361
- return items.map((request) => this.jsonToRequest(request.json));
359
+ const { items } = await this.#listPendingHead(Number.POSITIVE_INFINITY);
360
+ return items.map((request) => this.#jsonToRequest(request.json));
362
361
  }
363
362
  finally {
364
363
  this.#queueStateMutex.shift();
@@ -376,20 +375,20 @@ export class RequestQueueBackend extends BaseClient {
376
375
  totalRequestCount: this.#requests.size,
377
376
  };
378
377
  }
379
- updateTimestamps(hasBeenModified) {
378
+ #updateTimestamps(hasBeenModified) {
380
379
  this.accessedAt = new Date();
381
380
  if (hasBeenModified) {
382
381
  this.modifiedAt = new Date();
383
382
  }
384
383
  }
385
- jsonToRequest(requestJson) {
384
+ #jsonToRequest(requestJson) {
386
385
  if (!requestJson)
387
386
  return undefined;
388
387
  const request = JSON.parse(requestJson);
389
388
  return purgeNullsFromObject(request);
390
389
  }
391
- createInternalRequest(request, forefront) {
392
- const orderNo = this.calculateOrderNo(request, forefront);
390
+ #createInternalRequest(request, forefront) {
391
+ const orderNo = this.#calculateOrderNo(request, forefront);
393
392
  const id = uniqueKeyToRequestId(request.uniqueKey);
394
393
  if (request.id && request.id !== id) {
395
394
  throw new Error('Request ID does not match its uniqueKey.');
@@ -405,7 +404,7 @@ export class RequestQueueBackend extends BaseClient {
405
404
  url: request.url,
406
405
  };
407
406
  }
408
- calculateOrderNo(request, forefront) {
407
+ #calculateOrderNo(request, forefront) {
409
408
  if (request.handledAt)
410
409
  return null;
411
410
  const timestamp = Date.now();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/core",
3
- "version": "4.0.0-beta.211",
3
+ "version": "4.0.0-beta.212",
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"
@@ -53,10 +53,10 @@
53
53
  "@apify/log": "^3.0.1",
54
54
  "@apify/timeout": "^1.0.1",
55
55
  "@apify/utilities": "^3.0.1",
56
- "@crawlee/fs-storage": "4.0.0-beta.211",
57
- "@crawlee/http-client": "4.0.0-beta.211",
58
- "@crawlee/types": "4.0.0-beta.211",
59
- "@crawlee/utils": "4.0.0-beta.211",
56
+ "@crawlee/fs-storage": "4.0.0-beta.212",
57
+ "@crawlee/http-client": "4.0.0-beta.212",
58
+ "@crawlee/types": "4.0.0-beta.212",
59
+ "@crawlee/utils": "4.0.0-beta.212",
60
60
  "@sapphire/async-queue": "^1.5.5",
61
61
  "@standard-schema/spec": "^1.0.0",
62
62
  "@vladfrangu/async_event_emitter": "^2.4.6",
@@ -76,5 +76,5 @@
76
76
  }
77
77
  }
78
78
  },
79
- "gitHead": "17bab8ab5b98554ca2db1d09bc5f61c59194a760"
79
+ "gitHead": "171a63a5d7f2a6d74cbce58479204ac20d30b87e"
80
80
  }
@@ -110,11 +110,4 @@ export declare class ProxyConfiguration implements IProxyConfiguration {
110
110
  * For example, `http://bob:password123@proxy.example.com:8000`
111
111
  */
112
112
  newUrl(): Promise<string | undefined>;
113
- private handleProxyUrlsList;
114
- /**
115
- * Calls the custom newUrlFunction and checks format of its return value
116
- */
117
- private callNewUrlFunction;
118
- private throwCannotCombineCustomMethods;
119
- private throwNoOptionsProvided;
120
113
  }