@crawlee/core 4.0.0-beta.99 → 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.
Files changed (133) hide show
  1. package/README.md +1 -1
  2. package/configuration.d.ts +16 -47
  3. package/configuration.js +13 -25
  4. package/debug.js +4 -4
  5. package/errors.d.ts +28 -38
  6. package/errors.js +33 -47
  7. package/events/event_manager.d.ts +2 -2
  8. package/events/event_manager.js +7 -6
  9. package/events/index.d.ts +1 -0
  10. package/events/local_event_manager.d.ts +1 -8
  11. package/events/local_event_manager.js +13 -13
  12. package/events/system_info.d.ts +38 -0
  13. package/index.d.ts +2 -8
  14. package/index.js +4 -8
  15. package/internal.d.ts +8 -0
  16. package/internal.js +9 -0
  17. package/log.d.ts +10 -11
  18. package/log.js +52 -20
  19. package/memory-storage/memory-storage.d.ts +15 -18
  20. package/memory-storage/memory-storage.js +80 -58
  21. package/memory-storage/resource-clients/dataset.d.ts +1 -6
  22. package/memory-storage/resource-clients/dataset.js +23 -31
  23. package/memory-storage/resource-clients/key-value-store.d.ts +1 -10
  24. package/memory-storage/resource-clients/key-value-store.js +43 -67
  25. package/memory-storage/resource-clients/request-queue.d.ts +1 -42
  26. package/memory-storage/resource-clients/request-queue.js +109 -117
  27. package/owned_or_injected.d.ts +1 -3
  28. package/owned_or_injected.js +17 -17
  29. package/package.json +17 -20
  30. package/proxy_configuration.d.ts +21 -26
  31. package/proxy_configuration.js +35 -25
  32. package/recoverable_state.d.ts +104 -47
  33. package/recoverable_state.js +199 -74
  34. package/request.d.ts +20 -107
  35. package/request.js +78 -244
  36. package/serialization.js +17 -16
  37. package/service_locator.d.ts +22 -10
  38. package/service_locator.js +59 -48
  39. package/storages/batched_adds.d.ts +37 -0
  40. package/storages/batched_adds.js +73 -0
  41. package/storages/dataset.d.ts +13 -8
  42. package/storages/dataset.js +149 -40
  43. package/storages/index.d.ts +4 -4
  44. package/storages/index.js +2 -4
  45. package/storages/key_value_store.d.ts +16 -35
  46. package/storages/key_value_store.js +223 -110
  47. package/storages/key_value_store_codec.js +6 -11
  48. package/storages/request_dedup_cache.d.ts +1 -4
  49. package/storages/request_dedup_cache.js +15 -15
  50. package/storages/request_list.d.ts +9 -104
  51. package/storages/request_list.js +236 -233
  52. package/storages/request_loader.d.ts +49 -18
  53. package/storages/request_loader.js +36 -1
  54. package/storages/request_manager.d.ts +86 -0
  55. package/storages/request_manager_tandem.d.ts +14 -38
  56. package/storages/request_manager_tandem.js +67 -64
  57. package/storages/request_queue.d.ts +23 -50
  58. package/storages/request_queue.js +371 -226
  59. package/storages/storage_instance_manager.d.ts +2 -4
  60. package/storages/storage_instance_manager.js +21 -21
  61. package/storages/storage_stats.d.ts +1 -1
  62. package/storages/storage_stats.js +4 -4
  63. package/storages/transaction.d.ts +270 -0
  64. package/storages/transaction.js +296 -0
  65. package/storages/utils.d.ts +6 -3
  66. package/storages/utils.js +11 -2
  67. package/system-info/runtime.js +7 -7
  68. package/url.d.ts +9 -0
  69. package/url.js +11 -0
  70. package/validators.d.ts +23 -25
  71. package/validators.js +14 -25
  72. package/autoscaling/autoscaled_pool.d.ts +0 -213
  73. package/autoscaling/autoscaled_pool.js +0 -378
  74. package/autoscaling/client_load_signal.d.ts +0 -59
  75. package/autoscaling/client_load_signal.js +0 -73
  76. package/autoscaling/concurrency_system.d.ts +0 -283
  77. package/autoscaling/concurrency_system.js +0 -350
  78. package/autoscaling/cpu_load_signal.d.ts +0 -44
  79. package/autoscaling/cpu_load_signal.js +0 -46
  80. package/autoscaling/event_loop_load_signal.d.ts +0 -54
  81. package/autoscaling/event_loop_load_signal.js +0 -60
  82. package/autoscaling/index.d.ts +0 -9
  83. package/autoscaling/index.js +0 -9
  84. package/autoscaling/load_signal.d.ts +0 -99
  85. package/autoscaling/load_signal.js +0 -103
  86. package/autoscaling/memory_load_signal.d.ts +0 -56
  87. package/autoscaling/memory_load_signal.js +0 -106
  88. package/autoscaling/snapshotter.d.ts +0 -87
  89. package/autoscaling/snapshotter.js +0 -67
  90. package/autoscaling/system_status.d.ts +0 -161
  91. package/autoscaling/system_status.js +0 -139
  92. package/autoscaling/weighted_avg.d.ts +0 -5
  93. package/autoscaling/weighted_avg.js +0 -14
  94. package/cookie_utils.d.ts +0 -44
  95. package/cookie_utils.js +0 -122
  96. package/crawlers/context_pipeline.d.ts +0 -70
  97. package/crawlers/context_pipeline.js +0 -122
  98. package/crawlers/crawler_commons.d.ts +0 -257
  99. package/crawlers/crawler_commons.js +0 -107
  100. package/crawlers/error_snapshotter.d.ts +0 -59
  101. package/crawlers/error_snapshotter.js +0 -117
  102. package/crawlers/error_tracker.d.ts +0 -54
  103. package/crawlers/error_tracker.js +0 -308
  104. package/crawlers/index.d.ts +0 -5
  105. package/crawlers/index.js +0 -5
  106. package/crawlers/internals/types.d.ts +0 -7
  107. package/crawlers/statistics.d.ts +0 -209
  108. package/crawlers/statistics.js +0 -350
  109. package/enqueue_links/enqueue_links.d.ts +0 -264
  110. package/enqueue_links/enqueue_links.js +0 -271
  111. package/enqueue_links/index.d.ts +0 -2
  112. package/enqueue_links/index.js +0 -2
  113. package/enqueue_links/shared.d.ts +0 -83
  114. package/enqueue_links/shared.js +0 -221
  115. package/router.d.ts +0 -309
  116. package/router.js +0 -309
  117. package/session_pool/consts.d.ts +0 -3
  118. package/session_pool/consts.js +0 -3
  119. package/session_pool/errors.d.ts +0 -7
  120. package/session_pool/errors.js +0 -11
  121. package/session_pool/fingerprint.d.ts +0 -9
  122. package/session_pool/fingerprint.js +0 -30
  123. package/session_pool/index.d.ts +0 -4
  124. package/session_pool/index.js +0 -4
  125. package/session_pool/session.d.ts +0 -161
  126. package/session_pool/session.js +0 -218
  127. package/session_pool/session_pool.d.ts +0 -246
  128. package/session_pool/session_pool.js +0 -386
  129. package/storages/access_checking.d.ts +0 -12
  130. package/storages/access_checking.js +0 -17
  131. package/storages/sitemap_request_loader.d.ts +0 -249
  132. package/storages/sitemap_request_loader.js +0 -432
  133. /package/{crawlers/internals/types.js → events/system_info.js} +0 -0
package/request.js CHANGED
@@ -1,46 +1,33 @@
1
1
  import crypto from 'node:crypto';
2
- import util from 'node:util';
3
- import ow from 'ow';
2
+ import { z } from 'zod';
4
3
  import { cryptoRandomObjectId, normalizeUrl } from '@apify/utilities';
5
4
  import { serviceLocator } from './service_locator.js';
6
- import { keys } from './typedefs.js';
5
+ import { parseArgument, schemas } from './validators.js';
7
6
  // new properties on the Request object breaks serialization
8
- const requestOptionalPredicates = {
9
- id: ow.optional.string,
10
- loadedUrl: ow.optional.string.url,
11
- uniqueKey: ow.optional.string,
12
- method: ow.optional.string,
13
- payload: ow.optional.any(ow.string, ow.uint8Array),
14
- noRetry: ow.optional.boolean,
15
- retryCount: ow.optional.number,
16
- sessionId: ow.optional.string,
17
- maxRetries: ow.optional.number,
18
- errorMessages: ow.optional.array.ofType(ow.string),
19
- headers: ow.optional.object,
20
- userData: ow.optional.object,
21
- label: ow.optional.string,
22
- handledAt: ow.optional.any(ow.string.date, ow.date),
23
- keepUrlFragment: ow.optional.boolean,
24
- useExtendedUniqueKey: ow.optional.boolean,
25
- alwaysEnqueue: ow.optional.boolean,
26
- skipNavigation: ow.optional.boolean,
27
- crawlDepth: ow.optional.number.greaterThanOrEqual(0),
28
- state: ow.optional.number.greaterThanOrEqual(0).lessThanOrEqual(6),
29
- };
30
- export var RequestState;
31
- (function (RequestState) {
32
- RequestState[RequestState["UNPROCESSED"] = 0] = "UNPROCESSED";
33
- RequestState[RequestState["BEFORE_NAV"] = 1] = "BEFORE_NAV";
34
- RequestState[RequestState["AFTER_NAV"] = 2] = "AFTER_NAV";
35
- RequestState[RequestState["REQUEST_HANDLER"] = 3] = "REQUEST_HANDLER";
36
- RequestState[RequestState["DONE"] = 4] = "DONE";
37
- RequestState[RequestState["ERROR_HANDLER"] = 5] = "ERROR_HANDLER";
38
- RequestState[RequestState["ERROR"] = 6] = "ERROR";
39
- RequestState[RequestState["SKIPPED"] = 7] = "SKIPPED";
40
- })(RequestState || (RequestState = {}));
7
+ // Compiled once: every `Request` runs it, and the generated fast path skips zod's interpreter.
8
+ const requestOptionsSchema = z.compile(z.looseObject({
9
+ url: z.string(),
10
+ uniqueKey: z.string().optional(),
11
+ method: z.string().optional(),
12
+ payload: z.union([z.string(), z.instanceof(Uint8Array)]).optional(),
13
+ noRetry: z.boolean().optional(),
14
+ sessionId: z.string().optional(),
15
+ maxRetries: schemas.anyNumber.optional(),
16
+ headers: z.looseObject({}).optional(),
17
+ userData: z.looseObject({}).optional(),
18
+ label: z.string().optional(),
19
+ keepUrlFragment: z.boolean().optional(),
20
+ useExtendedUniqueKey: z.boolean().optional(),
21
+ alwaysEnqueue: z.boolean().optional(),
22
+ skipNavigation: z.boolean().optional(),
23
+ crawlDepth: schemas.anyNumber
24
+ .refine((value) => value >= 0, 'Expected a number greater than or equal to 0')
25
+ .optional(),
26
+ }));
41
27
  /**
42
- * Represents a URL to be crawled, optionally including HTTP method, headers, payload and other metadata.
43
- * The `Request` object also stores information about errors that occurred during processing of the request.
28
+ * Represents a URL to be crawled, optionally including HTTP method, headers, payload and other metadata, together
29
+ * with the processing state a request queue keeps about it (`retryCount`, `errorMessages`, `handledAt`, ...).
30
+ * Inside a crawler, requests are {@link CrawlingRequest}s, which add the crawler's own per-request settings.
44
31
  *
45
32
  * Each `Request` instance has the `uniqueKey` property, which can be either specified
46
33
  * manually in the constructor or generated automatically from the URL. Two requests with the same `uniqueKey`
@@ -56,21 +43,15 @@ export var RequestState;
56
43
  * const request = new Request({
57
44
  * url: 'http://www.example.com',
58
45
  * headers: { Accept: 'application/json' },
46
+ * userData: { foo: 'bar' },
59
47
  * });
60
48
  *
61
- * ...
62
- *
63
- * request.userData.foo = 'bar';
64
- * request.pushErrorMessage(new Error('Request failed!'));
65
- *
66
- * ...
67
- *
68
- * const foo = request.userData.foo;
49
+ * await requestQueue.addRequest(request);
69
50
  * ```
70
51
  * @category Sources
71
52
  */
72
53
  class CrawleeRequest {
73
- /** Request ID */
54
+ /** Storage-assigned request ID. Only present on requests that went through a {@link RequestQueue}. */
74
55
  id;
75
56
  /** URL of the web page to crawl. */
76
57
  url;
@@ -101,7 +82,7 @@ class CrawleeRequest {
101
82
  /** Object with HTTP headers. Key is header name, value is the value. */
102
83
  headers;
103
84
  /** Private store for the custom user data assigned to the request. */
104
- _userData = {};
85
+ #userData = {};
105
86
  /**
106
87
  * Custom user data assigned to the request.
107
88
  *
@@ -116,27 +97,19 @@ class CrawleeRequest {
116
97
  handledAt;
117
98
  /**
118
99
  * `Request` parameters including the URL, HTTP method and headers, and others.
100
+ *
101
+ * Processing state (`retryCount`, `errorMessages`, `handledAt`, `loadedUrl`, `id`) is not an option;
102
+ * requests coming back from a storage are rebuilt with {@link Request.fromSchema}.
119
103
  */
120
104
  constructor(options) {
121
- ow(options, 'RequestOptions', ow.object);
122
- ow(options.url, 'RequestOptions.url', ow.string);
123
- // 'ow' validation is slow, because it checks all predicates
124
- // even if the validated object has only 1 property.
125
- // This custom validation loop iterates only over existing
126
- // properties and speeds up the validation cca 3-fold.
127
- // See https://github.com/sindresorhus/ow/issues/193
128
- keys(options).forEach((prop) => {
129
- // skip url, because it is validated above
130
- if (prop === 'url') {
131
- return;
132
- }
133
- const predicate = requestOptionalPredicates[prop];
134
- const value = options[prop];
135
- if (predicate) {
136
- ow(value, `RequestOptions.${prop}`, predicate);
137
- }
138
- });
139
- const { id, url, loadedUrl, uniqueKey, payload, noRetry = false, retryCount = 0, sessionId, maxRetries, errorMessages = [], headers = {}, userData = {}, label, handledAt, keepUrlFragment = false, useExtendedUniqueKey = false, alwaysEnqueue = false, skipNavigation, enqueueStrategy, crawlDepth, } = options;
105
+ // A bare URL is a common slip — point at the object form instead of a generic type error.
106
+ if (typeof options === 'string') {
107
+ throw new TypeError(`\`Request\` options must be an object, got the string '${options}'. ` +
108
+ 'Did you mean `new Request({ url })`?');
109
+ }
110
+ parseArgument(options, schemas.anyObject, 'RequestOptions');
111
+ parseArgument(options, requestOptionsSchema, 'RequestOptions');
112
+ const { url, uniqueKey, payload, noRetry = false, sessionId, maxRetries, headers = {}, userData = {}, label, keepUrlFragment = false, useExtendedUniqueKey = false, alwaysEnqueue = false, skipNavigation, enqueueStrategy, crawlDepth, } = options;
140
113
  let { method = 'GET' } = options;
141
114
  method = method.toUpperCase();
142
115
  if (method === 'GET' && payload)
@@ -144,9 +117,7 @@ class CrawleeRequest {
144
117
  if (uniqueKey && alwaysEnqueue) {
145
118
  throw new Error('`alwaysEnqueue` cannot be used together with a custom `uniqueKey`.');
146
119
  }
147
- this.id = id;
148
120
  this.url = url;
149
- this.loadedUrl = loadedUrl;
150
121
  this.uniqueKey =
151
122
  uniqueKey ||
152
123
  CrawleeRequest.computeUniqueKey({
@@ -160,61 +131,76 @@ class CrawleeRequest {
160
131
  this.method = method;
161
132
  this.payload = payload;
162
133
  this.noRetry = noRetry;
163
- this.retryCount = retryCount;
164
- this.errorMessages = [...errorMessages];
134
+ this.retryCount = 0;
135
+ this.errorMessages = [];
165
136
  this.headers = { ...headers };
166
- this.handledAt = handledAt instanceof Date ? handledAt.toISOString() : handledAt;
167
137
  if (label) {
168
138
  userData.label = label;
169
139
  }
140
+ // Read `__crawlee` explicitly - on a `userData` coming from another Request instance the
141
+ // bag is non-enumerable, so the spread alone would silently drop the internal state
142
+ // (e.g. `skipNavigation`) when a request is re-wrapped after a storage round trip.
143
+ this.#userData = { __crawlee: userData.__crawlee ?? {}, ...userData };
144
+ // `userData` must stay an enumerable own accessor — serialization in the storages relies on it
170
145
  Object.defineProperties(this, {
171
- _userData: {
172
- value: { __crawlee: {}, ...userData },
173
- enumerable: false,
174
- writable: true,
175
- },
176
146
  userData: {
177
- get: () => this._userData,
147
+ get: () => this.#userData,
178
148
  set: (value) => {
179
149
  Object.defineProperties(value, {
180
150
  __crawlee: {
181
- value: this._userData.__crawlee,
151
+ value: this.#userData.__crawlee,
182
152
  enumerable: false,
183
153
  writable: true,
184
154
  },
185
155
  toJSON: {
186
156
  value: () => {
187
- if (Object.keys(this._userData.__crawlee).length > 0) {
157
+ if (Object.keys(this.#userData.__crawlee).length > 0) {
188
158
  return {
189
- ...this._userData,
190
- __crawlee: this._userData.__crawlee,
159
+ ...this.#userData,
160
+ __crawlee: this.#userData.__crawlee,
191
161
  };
192
162
  }
193
- return this._userData;
163
+ return this.#userData;
194
164
  },
195
165
  enumerable: false,
196
166
  writable: true,
197
167
  },
198
168
  });
199
- this._userData = value;
169
+ this.#userData = value;
200
170
  },
201
171
  enumerable: true,
202
172
  },
203
173
  });
204
174
  // reassign userData to ensure internal `__crawlee` object is non-enumerable
205
175
  this.userData = userData;
176
+ const crawlee = (this.userData.__crawlee ??= {});
206
177
  if (skipNavigation != null)
207
- this.skipNavigation = skipNavigation;
178
+ crawlee.skipNavigation = skipNavigation;
208
179
  if (maxRetries != null)
209
- this.maxRetries = maxRetries;
180
+ crawlee.maxRetries = maxRetries;
210
181
  if (crawlDepth != null)
211
- this.userData.__crawlee.crawlDepth ??= crawlDepth;
182
+ crawlee.crawlDepth ??= crawlDepth;
212
183
  if (sessionId)
213
- this.sessionId = sessionId;
214
- // If it's already set, don't override it (for instance when fetching from storage)
215
- if (enqueueStrategy) {
216
- this.enqueueStrategy ??= enqueueStrategy;
217
- }
184
+ crawlee.sessionId = sessionId;
185
+ // A stored request keeps the strategy it was enqueued under.
186
+ if (enqueueStrategy)
187
+ crawlee.enqueueStrategy ??= enqueueStrategy;
188
+ }
189
+ /**
190
+ * Rebuilds a request from its stored form, including the processing state a
191
+ * {@link RequestOptions} object cannot carry. Subclasses get instances of themselves.
192
+ */
193
+ static fromSchema(schema) {
194
+ const { id, retryCount = 0, errorMessages = [], handledAt, loadedUrl, ...options } = schema;
195
+ const request = new this(options);
196
+ request.id = id;
197
+ request.retryCount = retryCount;
198
+ request.errorMessages = [...errorMessages];
199
+ request.loadedUrl = loadedUrl;
200
+ // `apify-client` parses `*At` fields into `Date`s, whatever the schema says.
201
+ request.handledAt =
202
+ handledAt instanceof Date ? handledAt.toISOString() : handledAt;
203
+ return request;
218
204
  }
219
205
  /**
220
206
  * Converts the Crawlee Request object to a `fetch` API Request object.
@@ -227,54 +213,6 @@ class CrawleeRequest {
227
213
  body: this.payload,
228
214
  });
229
215
  }
230
- /**
231
- * Tells the crawler processing this request to skip the navigation and process the request directly.
232
- *
233
- * When this is set to `true`, the crawling context will not contain the results of the navigation
234
- * (e.g. `response`, `body`, `contentType`, `$` or `request.loadedUrl`).
235
- * Accessing these properties will throw a {@link NavigationSkippedError} at runtime.
236
- */
237
- get skipNavigation() {
238
- return this.userData.__crawlee?.skipNavigation ?? false;
239
- }
240
- /**
241
- * Tells the crawler processing this request to skip the navigation and process the request directly.
242
- *
243
- * When this is set to `true`, the crawling context will not contain the results of the navigation
244
- * (e.g. `response`, `body`, `contentType`, `$` or `request.loadedUrl`).
245
- * Accessing these properties will throw a {@link NavigationSkippedError} at runtime.
246
- */
247
- set skipNavigation(value) {
248
- if (!this.userData.__crawlee) {
249
- this.userData.__crawlee = { skipNavigation: value };
250
- }
251
- else {
252
- this.userData.__crawlee.skipNavigation = value;
253
- }
254
- }
255
- /**
256
- * Depth of the request in the current crawl tree.
257
- * Note that this is dependent on the crawler setup and might produce unexpected results when used with multiple crawlers.
258
- */
259
- get crawlDepth() {
260
- return this.userData.__crawlee?.crawlDepth ?? 0;
261
- }
262
- /**
263
- * Depth of the request in the current crawl tree.
264
- * Note that this is dependent on the crawler setup and might produce unexpected results when used with multiple crawlers.
265
- */
266
- set crawlDepth(value) {
267
- this.userData.__crawlee ??= {};
268
- this.userData.__crawlee.crawlDepth = value;
269
- }
270
- /** ID of a session to use for this request. When set, the crawler will fetch this session from the session pool instead of creating a new one. */
271
- get sessionId() {
272
- return this.userData.__crawlee?.sessionId;
273
- }
274
- set sessionId(value) {
275
- this.userData.__crawlee ??= {};
276
- this.userData.__crawlee.sessionId = value;
277
- }
278
216
  /** shortcut for getting `request.userData.label` */
279
217
  get label() {
280
218
  return this.userData.label;
@@ -283,110 +221,6 @@ class CrawleeRequest {
283
221
  set label(value) {
284
222
  this.userData.label = value;
285
223
  }
286
- /** Maximum number of retries for this request. Allows to override the global `maxRequestRetries` option of `BasicCrawler`. */
287
- get maxRetries() {
288
- return this.userData.__crawlee?.maxRetries;
289
- }
290
- /** Maximum number of retries for this request. Allows to override the global `maxRequestRetries` option of `BasicCrawler`. */
291
- set maxRetries(value) {
292
- if (!this.userData.__crawlee) {
293
- this.userData.__crawlee = { maxRetries: value };
294
- }
295
- else {
296
- this.userData.__crawlee.maxRetries = value;
297
- }
298
- }
299
- /** Describes the request's current lifecycle state. */
300
- get state() {
301
- return this.userData.__crawlee?.state ?? RequestState.UNPROCESSED;
302
- }
303
- /** Describes the request's current lifecycle state. */
304
- set state(value) {
305
- if (!this.userData.__crawlee) {
306
- this.userData.__crawlee = { state: value };
307
- }
308
- else {
309
- this.userData.__crawlee.state = value;
310
- }
311
- }
312
- /**
313
- * Reason for skipping this request.
314
- */
315
- get skippedReason() {
316
- return this.userData.__crawlee?.skippedReason;
317
- }
318
- /**
319
- * Reason for skipping this request.
320
- */
321
- set skippedReason(value) {
322
- if (!this.userData.__crawlee) {
323
- this.userData.__crawlee = { skippedReason: value };
324
- }
325
- else {
326
- this.userData.__crawlee.skippedReason = value;
327
- }
328
- }
329
- get enqueueStrategy() {
330
- return this.userData.__crawlee?.enqueueStrategy;
331
- }
332
- set enqueueStrategy(value) {
333
- if (!this.userData.__crawlee) {
334
- this.userData.__crawlee = { enqueueStrategy: value };
335
- }
336
- else {
337
- this.userData.__crawlee.enqueueStrategy = value;
338
- }
339
- }
340
- /**
341
- * Stores information about an error that occurred during processing of this request.
342
- *
343
- * You should always use Error instances when throwing errors in JavaScript.
344
- *
345
- * Nevertheless, to improve the debugging experience when using third party libraries
346
- * that may not always throw an Error instance, the function performs a type
347
- * inspection of the passed argument and attempts to extract as much information
348
- * as possible, since just throwing a bad type error makes any debugging rather difficult.
349
- *
350
- * @param errorOrMessage Error object or error message to be stored in the request.
351
- * @param [options]
352
- */
353
- pushErrorMessage(errorOrMessage, options = {}) {
354
- const { omitStack } = options;
355
- let message;
356
- const type = typeof errorOrMessage;
357
- if (type === 'object') {
358
- if (!errorOrMessage) {
359
- message = 'null';
360
- }
361
- else if (errorOrMessage instanceof Error) {
362
- message = omitStack
363
- ? errorOrMessage.message
364
- : // .stack includes the message
365
- errorOrMessage.stack;
366
- }
367
- else if (Reflect.has(Object(errorOrMessage), 'message')) {
368
- message = Reflect.get(Object(errorOrMessage), 'message');
369
- }
370
- else if (errorOrMessage.toString() !== '[object Object]') {
371
- message = errorOrMessage.toString();
372
- }
373
- else {
374
- try {
375
- message = util.inspect(errorOrMessage);
376
- }
377
- catch (err) {
378
- message = 'Unable to extract any message from the received object.';
379
- }
380
- }
381
- }
382
- else if (type === 'undefined') {
383
- message = 'undefined';
384
- }
385
- else {
386
- message = errorOrMessage.toString();
387
- }
388
- this.errorMessages.push(message);
389
- }
390
224
  /** @internal */
391
225
  static computeUniqueKey({ url, method = 'GET', payload, keepUrlFragment = false, useExtendedUniqueKey = false, alwaysEnqueue = false, }) {
392
226
  const normalizedMethod = method.toUpperCase();
package/serialization.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { pipeline as streamPipeline, Readable, Writable } from 'node:stream';
2
2
  import util from 'node:util';
3
3
  import zlib from 'node:zlib';
4
- import ow from 'ow';
5
- import StreamArray from 'stream-json/streamers/StreamArray.js';
4
+ import StreamArray from 'stream-json/streamers/stream-array.js';
5
+ import { z } from 'zod';
6
+ import { parseArgument, schemas } from './validators.js';
6
7
  const pipeline = util.promisify(streamPipeline);
8
+ const uint8ArraySchema = z.instanceof(Uint8Array);
7
9
  /**
8
10
  * Transforms an array of items to a JSON in a streaming
9
11
  * fashion to save memory. It operates in batches to speed
@@ -11,32 +13,31 @@ const pipeline = util.promisify(streamPipeline);
11
13
  * @internal
12
14
  */
13
15
  class ArrayToJson extends Readable {
14
- data;
15
- offset = 0;
16
- batchSize;
16
+ #offset = 0;
17
+ #batchSize;
18
+ #data;
17
19
  constructor(data, options = {}) {
18
20
  super({
19
21
  ...options,
20
22
  autoDestroy: true,
21
23
  emitClose: true,
22
24
  });
23
- this.data = data;
24
25
  const { batchSize = 10000 } = options;
25
- this.batchSize = batchSize;
26
- this.data = data;
26
+ this.#batchSize = batchSize;
27
+ this.#data = data;
27
28
  this.push('[');
28
29
  }
29
30
  _read() {
30
31
  try {
31
- const items = this.data.slice(this.offset, this.offset + this.batchSize);
32
+ const items = this.#data.slice(this.#offset, this.#offset + this.#batchSize);
32
33
  if (items.length) {
33
34
  const json = JSON.stringify(items);
34
35
  // Strip brackets to flatten the batch.
35
36
  const itemString = json.substring(1, json.length - 1);
36
- if (this.offset > 0)
37
+ if (this.#offset > 0)
37
38
  this.push(',', 'utf8');
38
39
  this.push(itemString, 'utf8');
39
- this.offset += this.batchSize;
40
+ this.#offset += this.#batchSize;
40
41
  }
41
42
  else {
42
43
  this.push(']');
@@ -58,7 +59,7 @@ class ArrayToJson extends Readable {
58
59
  * @internal
59
60
  */
60
61
  export async function serializeArray(data) {
61
- ow(data, ow.array);
62
+ parseArgument(data, schemas.anyArray);
62
63
  const { chunks, collector } = createChunkCollector();
63
64
  await pipeline(new ArrayToJson(data), zlib.createGzip(), collector);
64
65
  return Buffer.concat(chunks);
@@ -73,9 +74,9 @@ export async function serializeArray(data) {
73
74
  * @internal
74
75
  */
75
76
  export async function deserializeArray(compressedData) {
76
- ow(compressedData, ow.uint8Array);
77
+ parseArgument(compressedData, uint8ArraySchema);
77
78
  const { chunks, collector } = createChunkCollector({ fromValuesStream: true });
78
- await pipeline(Readable.from([compressedData]), zlib.createGunzip(), StreamArray.withParser(), collector);
79
+ await pipeline(Readable.from([compressedData]), zlib.createGunzip(), StreamArray.withParserAsStream(), collector);
79
80
  return chunks;
80
81
  }
81
82
  /**
@@ -88,8 +89,8 @@ export async function deserializeArray(compressedData) {
88
89
  * @internal
89
90
  */
90
91
  export function createDeserialize(compressedData) {
91
- ow(compressedData, ow.uint8Array);
92
- const streamArray = StreamArray.withParser();
92
+ parseArgument(compressedData, uint8ArraySchema);
93
+ const streamArray = StreamArray.withParserAsStream();
93
94
  const destination = pluckValue(streamArray);
94
95
  streamPipeline(Readable.from([compressedData]), zlib.createGunzip(), destination, (err) => destination.emit(err));
95
96
  return destination;
@@ -60,8 +60,20 @@ interface ServiceLocatorInterface {
60
60
  getChildLog(prefix: string): CrawleeLogger;
61
61
  /**
62
62
  * Get the storage instance manager (shared across all storage types).
63
+ * @internal
63
64
  */
64
65
  getStorageInstanceManager(): StorageInstanceManager;
66
+ /**
67
+ * Returns the currently set services without triggering the implicit creation of defaults.
68
+ * Used to inherit already-materialized services into crawler-scoped service locators.
69
+ * @internal
70
+ */
71
+ getServicesIfSet(): {
72
+ configuration?: Configuration;
73
+ eventManager?: EventManager;
74
+ storageBackend?: StorageBackend;
75
+ logger?: CrawleeLogger;
76
+ };
65
77
  /**
66
78
  * Resets the service locator to its initial state.
67
79
  * Used mainly for testing purposes.
@@ -101,16 +113,7 @@ interface ServiceLocatorInterface {
101
113
  * ```
102
114
  */
103
115
  export declare class ServiceLocator implements ServiceLocatorInterface {
104
- private configuration?;
105
- private eventManager?;
106
- private storageBackend?;
107
- private logger?;
108
- /**
109
- * Unified storage instance manager for Dataset, KeyValueStore, and RequestQueue.
110
- * Shared across all ServiceLocator instances (global singleton), matching crawlee-python.
111
- * Per-crawler isolation is achieved via `clientCacheKey`, not separate manager instances.
112
- */
113
- private static storageInstanceManager?;
116
+ #private;
114
117
  /**
115
118
  * Creates a new ServiceLocator instance.
116
119
  *
@@ -120,6 +123,13 @@ export declare class ServiceLocator implements ServiceLocatorInterface {
120
123
  * @param logger Optional logger instance to use
121
124
  */
122
125
  constructor(configuration?: Configuration, eventManager?: EventManager, storageBackend?: StorageBackend, logger?: CrawleeLogger);
126
+ /** @internal */
127
+ getServicesIfSet(): {
128
+ configuration?: Configuration;
129
+ eventManager?: EventManager;
130
+ storageBackend?: StorageBackend;
131
+ logger?: CrawleeLogger;
132
+ };
123
133
  getConfiguration(): Configuration;
124
134
  setConfiguration(configuration: Configuration): void;
125
135
  getEventManager(): EventManager;
@@ -129,7 +139,9 @@ export declare class ServiceLocator implements ServiceLocatorInterface {
129
139
  getLogger(): CrawleeLogger;
130
140
  setLogger(logger: CrawleeLogger): void;
131
141
  getChildLog(prefix: string): CrawleeLogger;
142
+ /** @internal */
132
143
  getStorageInstanceManager(): StorageInstanceManager;
144
+ /** @internal */
133
145
  reset(): void;
134
146
  }
135
147
  /**