@crawlee/core 4.0.0-beta.25 → 4.0.0-beta.250

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 (265) hide show
  1. package/README.md +15 -15
  2. package/byte-utils.d.ts +17 -0
  3. package/byte-utils.js +42 -0
  4. package/configuration.d.ts +77 -235
  5. package/configuration.js +159 -227
  6. package/debug.d.ts +36 -0
  7. package/debug.js +70 -0
  8. package/errors.d.ts +45 -18
  9. package/errors.js +58 -27
  10. package/events/event-manager.d.ts +78 -0
  11. package/events/{event_manager.js → event-manager.js} +12 -13
  12. package/events/index.d.ts +3 -3
  13. package/events/index.js +2 -3
  14. package/events/local-event-manager.d.ts +32 -0
  15. package/events/local-event-manager.js +101 -0
  16. package/events/system-info.d.ts +38 -0
  17. package/events/system-info.js +1 -0
  18. package/index.d.ts +9 -12
  19. package/index.js +10 -11
  20. package/internal.d.ts +8 -0
  21. package/internal.js +9 -0
  22. package/iterables.d.ts +79 -0
  23. package/iterables.js +134 -0
  24. package/log.d.ts +77 -1
  25. package/log.js +134 -1
  26. package/memory-storage/consts.d.ts +4 -0
  27. package/memory-storage/consts.js +4 -0
  28. package/memory-storage/index.d.ts +1 -0
  29. package/memory-storage/index.js +1 -0
  30. package/memory-storage/memory-storage.d.ts +43 -0
  31. package/memory-storage/memory-storage.js +158 -0
  32. package/memory-storage/resource-clients/common/base-client.d.ts +4 -0
  33. package/memory-storage/resource-clients/common/base-client.js +6 -0
  34. package/memory-storage/resource-clients/dataset.d.ts +35 -0
  35. package/memory-storage/resource-clients/dataset.js +108 -0
  36. package/memory-storage/resource-clients/key-value-store.d.ts +54 -0
  37. package/memory-storage/resource-clients/key-value-store.js +179 -0
  38. package/memory-storage/resource-clients/request-queue.d.ts +55 -0
  39. package/memory-storage/resource-clients/request-queue.js +413 -0
  40. package/memory-storage/utils.d.ts +16 -0
  41. package/memory-storage/utils.js +41 -0
  42. package/owned-or-injected.d.ts +58 -0
  43. package/owned-or-injected.js +98 -0
  44. package/package.json +23 -23
  45. package/proxy-configuration.d.ts +118 -0
  46. package/proxy-configuration.js +141 -0
  47. package/recoverable-state.d.ts +177 -0
  48. package/recoverable-state.js +268 -0
  49. package/request.d.ts +48 -93
  50. package/request.js +133 -230
  51. package/serialization.d.ts +0 -1
  52. package/serialization.js +18 -18
  53. package/service-locator.d.ts +168 -0
  54. package/service-locator.js +255 -0
  55. package/storages/batched-adds.d.ts +37 -0
  56. package/storages/batched-adds.js +73 -0
  57. package/storages/dataset.d.ts +105 -58
  58. package/storages/dataset.js +292 -150
  59. package/storages/index.d.ts +11 -12
  60. package/storages/index.js +7 -12
  61. package/storages/key-value-store-codec.d.ts +32 -0
  62. package/storages/key-value-store-codec.js +108 -0
  63. package/storages/{key_value_store.d.ts → key-value-store.d.ts} +169 -72
  64. package/storages/key-value-store.js +767 -0
  65. package/storages/request-dedup-cache.d.ts +20 -0
  66. package/storages/request-dedup-cache.js +48 -0
  67. package/storages/{request_list.d.ts → request-list.d.ts} +33 -173
  68. package/storages/{request_list.js → request-list.js} +276 -255
  69. package/storages/request-loader.d.ts +127 -0
  70. package/storages/request-loader.js +36 -0
  71. package/storages/request-manager-tandem.d.ts +82 -0
  72. package/storages/request-manager-tandem.js +200 -0
  73. package/storages/request-manager.d.ts +119 -0
  74. package/storages/request-manager.js +1 -0
  75. package/storages/{request_provider.d.ts → request-queue.d.ts} +128 -159
  76. package/storages/request-queue.js +870 -0
  77. package/storages/storage-instance-manager.d.ts +86 -0
  78. package/storages/storage-instance-manager.js +256 -0
  79. package/storages/storage-stats.d.ts +48 -0
  80. package/storages/storage-stats.js +29 -0
  81. package/storages/transaction.d.ts +270 -0
  82. package/storages/transaction.js +296 -0
  83. package/storages/utils.d.ts +59 -11
  84. package/storages/utils.js +76 -16
  85. package/system-info/cpu-info.d.ts +67 -0
  86. package/system-info/cpu-info.js +216 -0
  87. package/system-info/memory-info.d.ts +31 -0
  88. package/system-info/memory-info.js +115 -0
  89. package/system-info/ps-tree.d.ts +17 -0
  90. package/system-info/ps-tree.js +144 -0
  91. package/system-info/runtime.d.ts +14 -0
  92. package/system-info/runtime.js +80 -0
  93. package/typedefs.d.ts +0 -6
  94. package/typedefs.js +0 -1
  95. package/url.d.ts +9 -0
  96. package/url.js +11 -0
  97. package/validators.d.ts +23 -18
  98. package/validators.js +14 -18
  99. package/autoscaling/autoscaled_pool.d.ts +0 -300
  100. package/autoscaling/autoscaled_pool.d.ts.map +0 -1
  101. package/autoscaling/autoscaled_pool.js +0 -533
  102. package/autoscaling/autoscaled_pool.js.map +0 -1
  103. package/autoscaling/index.d.ts +0 -4
  104. package/autoscaling/index.d.ts.map +0 -1
  105. package/autoscaling/index.js +0 -4
  106. package/autoscaling/index.js.map +0 -1
  107. package/autoscaling/snapshotter.d.ts +0 -189
  108. package/autoscaling/snapshotter.d.ts.map +0 -1
  109. package/autoscaling/snapshotter.js +0 -285
  110. package/autoscaling/snapshotter.js.map +0 -1
  111. package/autoscaling/system_status.d.ts +0 -181
  112. package/autoscaling/system_status.d.ts.map +0 -1
  113. package/autoscaling/system_status.js +0 -169
  114. package/autoscaling/system_status.js.map +0 -1
  115. package/configuration.d.ts.map +0 -1
  116. package/configuration.js.map +0 -1
  117. package/cookie_utils.d.ts +0 -43
  118. package/cookie_utils.d.ts.map +0 -1
  119. package/cookie_utils.js +0 -111
  120. package/cookie_utils.js.map +0 -1
  121. package/crawlers/context_pipeline.d.ts +0 -61
  122. package/crawlers/context_pipeline.d.ts.map +0 -1
  123. package/crawlers/context_pipeline.js +0 -99
  124. package/crawlers/context_pipeline.js.map +0 -1
  125. package/crawlers/crawler_commons.d.ts +0 -191
  126. package/crawlers/crawler_commons.d.ts.map +0 -1
  127. package/crawlers/crawler_commons.js +0 -108
  128. package/crawlers/crawler_commons.js.map +0 -1
  129. package/crawlers/crawler_utils.d.ts +0 -10
  130. package/crawlers/crawler_utils.d.ts.map +0 -1
  131. package/crawlers/crawler_utils.js +0 -12
  132. package/crawlers/crawler_utils.js.map +0 -1
  133. package/crawlers/error_snapshotter.d.ts +0 -60
  134. package/crawlers/error_snapshotter.d.ts.map +0 -1
  135. package/crawlers/error_snapshotter.js +0 -118
  136. package/crawlers/error_snapshotter.js.map +0 -1
  137. package/crawlers/error_tracker.d.ts +0 -55
  138. package/crawlers/error_tracker.d.ts.map +0 -1
  139. package/crawlers/error_tracker.js +0 -309
  140. package/crawlers/error_tracker.js.map +0 -1
  141. package/crawlers/index.d.ts +0 -7
  142. package/crawlers/index.d.ts.map +0 -1
  143. package/crawlers/index.js +0 -7
  144. package/crawlers/index.js.map +0 -1
  145. package/crawlers/internals/types.d.ts +0 -8
  146. package/crawlers/internals/types.d.ts.map +0 -1
  147. package/crawlers/internals/types.js +0 -2
  148. package/crawlers/internals/types.js.map +0 -1
  149. package/crawlers/statistics.d.ts +0 -213
  150. package/crawlers/statistics.d.ts.map +0 -1
  151. package/crawlers/statistics.js +0 -349
  152. package/crawlers/statistics.js.map +0 -1
  153. package/enqueue_links/enqueue_links.d.ts +0 -249
  154. package/enqueue_links/enqueue_links.d.ts.map +0 -1
  155. package/enqueue_links/enqueue_links.js +0 -269
  156. package/enqueue_links/enqueue_links.js.map +0 -1
  157. package/enqueue_links/index.d.ts +0 -3
  158. package/enqueue_links/index.d.ts.map +0 -1
  159. package/enqueue_links/index.js +0 -3
  160. package/enqueue_links/index.js.map +0 -1
  161. package/enqueue_links/shared.d.ts +0 -73
  162. package/enqueue_links/shared.d.ts.map +0 -1
  163. package/enqueue_links/shared.js +0 -212
  164. package/enqueue_links/shared.js.map +0 -1
  165. package/errors.d.ts.map +0 -1
  166. package/errors.js.map +0 -1
  167. package/events/event_manager.d.ts +0 -52
  168. package/events/event_manager.d.ts.map +0 -1
  169. package/events/event_manager.js.map +0 -1
  170. package/events/index.d.ts.map +0 -1
  171. package/events/index.js.map +0 -1
  172. package/events/local_event_manager.d.ts +0 -27
  173. package/events/local_event_manager.d.ts.map +0 -1
  174. package/events/local_event_manager.js +0 -75
  175. package/events/local_event_manager.js.map +0 -1
  176. package/index.d.ts.map +0 -1
  177. package/index.js.map +0 -1
  178. package/log.d.ts.map +0 -1
  179. package/log.js.map +0 -1
  180. package/proxy_configuration.d.ts +0 -169
  181. package/proxy_configuration.d.ts.map +0 -1
  182. package/proxy_configuration.js +0 -247
  183. package/proxy_configuration.js.map +0 -1
  184. package/recoverable_state.d.ts +0 -123
  185. package/recoverable_state.d.ts.map +0 -1
  186. package/recoverable_state.js +0 -140
  187. package/recoverable_state.js.map +0 -1
  188. package/request.d.ts.map +0 -1
  189. package/request.js.map +0 -1
  190. package/router.d.ts +0 -134
  191. package/router.d.ts.map +0 -1
  192. package/router.js +0 -164
  193. package/router.js.map +0 -1
  194. package/serialization.d.ts.map +0 -1
  195. package/serialization.js.map +0 -1
  196. package/session_pool/consts.d.ts +0 -4
  197. package/session_pool/consts.d.ts.map +0 -1
  198. package/session_pool/consts.js +0 -4
  199. package/session_pool/consts.js.map +0 -1
  200. package/session_pool/errors.d.ts +0 -8
  201. package/session_pool/errors.d.ts.map +0 -1
  202. package/session_pool/errors.js +0 -12
  203. package/session_pool/errors.js.map +0 -1
  204. package/session_pool/events.d.ts +0 -3
  205. package/session_pool/events.d.ts.map +0 -1
  206. package/session_pool/events.js +0 -3
  207. package/session_pool/events.js.map +0 -1
  208. package/session_pool/index.d.ts +0 -6
  209. package/session_pool/index.d.ts.map +0 -1
  210. package/session_pool/index.js +0 -6
  211. package/session_pool/index.js.map +0 -1
  212. package/session_pool/session.d.ts +0 -186
  213. package/session_pool/session.d.ts.map +0 -1
  214. package/session_pool/session.js +0 -278
  215. package/session_pool/session.js.map +0 -1
  216. package/session_pool/session_pool.d.ts +0 -258
  217. package/session_pool/session_pool.d.ts.map +0 -1
  218. package/session_pool/session_pool.js +0 -387
  219. package/session_pool/session_pool.js.map +0 -1
  220. package/storages/access_checking.d.ts +0 -13
  221. package/storages/access_checking.d.ts.map +0 -1
  222. package/storages/access_checking.js +0 -14
  223. package/storages/access_checking.js.map +0 -1
  224. package/storages/dataset.d.ts.map +0 -1
  225. package/storages/dataset.js.map +0 -1
  226. package/storages/index.d.ts.map +0 -1
  227. package/storages/index.js.map +0 -1
  228. package/storages/key_value_store.d.ts.map +0 -1
  229. package/storages/key_value_store.js +0 -512
  230. package/storages/key_value_store.js.map +0 -1
  231. package/storages/request_list.d.ts.map +0 -1
  232. package/storages/request_list.js.map +0 -1
  233. package/storages/request_list_adapter.d.ts +0 -58
  234. package/storages/request_list_adapter.d.ts.map +0 -1
  235. package/storages/request_list_adapter.js +0 -81
  236. package/storages/request_list_adapter.js.map +0 -1
  237. package/storages/request_manager_tandem.d.ts +0 -68
  238. package/storages/request_manager_tandem.d.ts.map +0 -1
  239. package/storages/request_manager_tandem.js +0 -124
  240. package/storages/request_manager_tandem.js.map +0 -1
  241. package/storages/request_provider.d.ts.map +0 -1
  242. package/storages/request_provider.js +0 -591
  243. package/storages/request_provider.js.map +0 -1
  244. package/storages/request_queue.d.ts +0 -128
  245. package/storages/request_queue.d.ts.map +0 -1
  246. package/storages/request_queue.js +0 -320
  247. package/storages/request_queue.js.map +0 -1
  248. package/storages/request_queue_v2.d.ts +0 -87
  249. package/storages/request_queue_v2.d.ts.map +0 -1
  250. package/storages/request_queue_v2.js +0 -438
  251. package/storages/request_queue_v2.js.map +0 -1
  252. package/storages/sitemap_request_list.d.ts +0 -250
  253. package/storages/sitemap_request_list.d.ts.map +0 -1
  254. package/storages/sitemap_request_list.js +0 -431
  255. package/storages/sitemap_request_list.js.map +0 -1
  256. package/storages/storage_manager.d.ts +0 -62
  257. package/storages/storage_manager.d.ts.map +0 -1
  258. package/storages/storage_manager.js +0 -105
  259. package/storages/storage_manager.js.map +0 -1
  260. package/storages/utils.d.ts.map +0 -1
  261. package/storages/utils.js.map +0 -1
  262. package/typedefs.d.ts.map +0 -1
  263. package/typedefs.js.map +0 -1
  264. package/validators.d.ts.map +0 -1
  265. package/validators.js.map +0 -1
package/request.js CHANGED
@@ -1,46 +1,54 @@
1
1
  import crypto from 'node:crypto';
2
- import util from 'node:util';
3
- import ow from 'ow';
4
- import { normalizeUrl } from '@apify/utilities';
5
- import { log as defaultLog } from './log.js';
6
- import { keys } from './typedefs.js';
2
+ import { z } from 'zod';
3
+ import { cryptoRandomObjectId, normalizeUrl } from '@apify/utilities';
4
+ import { serviceLocator } from './service-locator.js';
5
+ import { parseArgument, schemas } from './validators.js';
7
6
  // new properties on the Request object breaks serialization
8
- const log = defaultLog.child({ prefix: 'Request' });
9
- const requestOptionalPredicates = {
10
- id: ow.optional.string,
11
- loadedUrl: ow.optional.string.url,
12
- uniqueKey: ow.optional.string,
13
- method: ow.optional.string,
14
- payload: ow.optional.any(ow.string, ow.uint8Array),
15
- noRetry: ow.optional.boolean,
16
- retryCount: ow.optional.number,
17
- sessionRotationCount: ow.optional.number,
18
- maxRetries: ow.optional.number,
19
- errorMessages: ow.optional.array.ofType(ow.string),
20
- headers: ow.optional.object,
21
- userData: ow.optional.object,
22
- label: ow.optional.string,
23
- handledAt: ow.optional.any(ow.string.date, ow.date),
24
- keepUrlFragment: ow.optional.boolean,
25
- useExtendedUniqueKey: 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
+ }));
27
+ /** Storages return `null` for fields left unset by other clients, e.g. the Python SDK. */
28
+ const unset = (schema) => schema.nullish().transform((value) => value ?? undefined);
29
+ /** A request as storages return it. Accepts no less than the constructor does for the same fields. */
30
+ const storedRequestSchema = z.compile(z.object({
31
+ id: z.string().optional(),
32
+ url: z.string(),
33
+ uniqueKey: z.string(),
34
+ method: z.custom((value) => typeof value === 'string').optional(),
35
+ payload: unset(z.custom((value) => typeof value === 'string' || value instanceof Uint8Array)),
36
+ noRetry: unset(z.boolean()),
37
+ retryCount: z.number().optional(),
38
+ // Entries are not checked, persisted requests can hold `null`s there (#4087).
39
+ errorMessages: unset(z.custom(Array.isArray)),
40
+ headers: unset(z.custom((value) => typeof value === 'object' && value !== null)),
41
+ userData: schemas.anyObject.optional(),
42
+ // `apify-client` parses `*At` fields into `Date`s, whatever the schema says.
43
+ handledAt: unset(z.custom((value) => typeof value === 'string' || value instanceof Date)),
44
+ loadedUrl: unset(z.string()),
45
+ }));
46
+ // `@crawlee/types` cannot depend on `zod`, so this keeps `RequestSchema` in sync with the schema above.
47
+ true;
41
48
  /**
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.
49
+ * Represents a URL to be crawled, optionally including HTTP method, headers, payload and other metadata, together
50
+ * with the processing state a request queue keeps about it (`retryCount`, `errorMessages`, `handledAt`, ...).
51
+ * Inside a crawler, requests are {@link CrawlingRequest}s, which add the crawler's own per-request settings.
44
52
  *
45
53
  * Each `Request` instance has the `uniqueKey` property, which can be either specified
46
54
  * manually in the constructor or generated automatically from the URL. Two requests with the same `uniqueKey`
@@ -56,21 +64,15 @@ export var RequestState;
56
64
  * const request = new Request({
57
65
  * url: 'http://www.example.com',
58
66
  * headers: { Accept: 'application/json' },
67
+ * userData: { foo: 'bar' },
59
68
  * });
60
69
  *
61
- * ...
62
- *
63
- * request.userData.foo = 'bar';
64
- * request.pushErrorMessage(new Error('Request failed!'));
65
- *
66
- * ...
67
- *
68
- * const foo = request.userData.foo;
70
+ * await requestQueue.addRequest(request);
69
71
  * ```
70
72
  * @category Sources
71
73
  */
72
74
  class CrawleeRequest {
73
- /** Request ID */
75
+ /** Storage-assigned request ID. Only present on requests that went through a {@link RequestQueue}. */
74
76
  id;
75
77
  /** URL of the web page to crawl. */
76
78
  url;
@@ -101,8 +103,13 @@ class CrawleeRequest {
101
103
  /** Object with HTTP headers. Key is header name, value is the value. */
102
104
  headers;
103
105
  /** Private store for the custom user data assigned to the request. */
104
- _userData = {};
105
- /** Custom user data assigned to the request. */
106
+ #userData = {};
107
+ /**
108
+ * Custom user data assigned to the request.
109
+ *
110
+ * All data stored in `userData` must be JSON-serializable.
111
+ * Storing non-serializable values (e.g. functions, symbols) may result in unexpected results.
112
+ */
106
113
  userData = {};
107
114
  /**
108
115
  * ISO datetime string that indicates the time when the request has been processed.
@@ -111,94 +118,110 @@ class CrawleeRequest {
111
118
  handledAt;
112
119
  /**
113
120
  * `Request` parameters including the URL, HTTP method and headers, and others.
121
+ *
122
+ * Processing state (`retryCount`, `errorMessages`, `handledAt`, `loadedUrl`, `id`) is not an option;
123
+ * requests coming back from a storage are rebuilt with {@link Request.fromSchema}.
114
124
  */
115
125
  constructor(options) {
116
- ow(options, 'RequestOptions', ow.object);
117
- ow(options.url, 'RequestOptions.url', ow.string);
118
- // 'ow' validation is slow, because it checks all predicates
119
- // even if the validated object has only 1 property.
120
- // This custom validation loop iterates only over existing
121
- // properties and speeds up the validation cca 3-fold.
122
- // See https://github.com/sindresorhus/ow/issues/193
123
- keys(options).forEach((prop) => {
124
- // skip url, because it is validated above
125
- if (prop === 'url') {
126
- return;
127
- }
128
- const predicate = requestOptionalPredicates[prop];
129
- const value = options[prop];
130
- if (predicate) {
131
- ow(value, `RequestOptions.${prop}`, predicate);
132
- }
133
- });
134
- const { id, url, loadedUrl, uniqueKey, payload, noRetry = false, retryCount = 0, sessionRotationCount = 0, maxRetries, errorMessages = [], headers = {}, userData = {}, label, handledAt, keepUrlFragment = false, useExtendedUniqueKey = false, skipNavigation, enqueueStrategy, crawlDepth, } = options;
126
+ // A bare URL is a common slip — point at the object form instead of a generic type error.
127
+ if (typeof options === 'string') {
128
+ throw new TypeError(`\`Request\` options must be an object, got the string '${options}'. ` +
129
+ 'Did you mean `new Request({ url })`?');
130
+ }
131
+ parseArgument(options, schemas.anyObject, 'RequestOptions');
132
+ parseArgument(options, requestOptionsSchema, 'RequestOptions');
133
+ const { url, uniqueKey, payload, noRetry = false, sessionId, maxRetries, headers = {}, userData = {}, label, keepUrlFragment = false, useExtendedUniqueKey = false, alwaysEnqueue = false, skipNavigation, enqueueStrategy, crawlDepth, } = options;
135
134
  let { method = 'GET' } = options;
136
135
  method = method.toUpperCase();
137
136
  if (method === 'GET' && payload)
138
137
  throw new Error('Request with GET method cannot have a payload.');
139
- this.id = id;
138
+ if (uniqueKey && alwaysEnqueue) {
139
+ throw new Error('`alwaysEnqueue` cannot be used together with a custom `uniqueKey`.');
140
+ }
140
141
  this.url = url;
141
- this.loadedUrl = loadedUrl;
142
142
  this.uniqueKey =
143
143
  uniqueKey ||
144
- CrawleeRequest.computeUniqueKey({ url, method, payload, keepUrlFragment, useExtendedUniqueKey });
144
+ CrawleeRequest.computeUniqueKey({
145
+ url,
146
+ method,
147
+ payload,
148
+ keepUrlFragment,
149
+ useExtendedUniqueKey,
150
+ alwaysEnqueue,
151
+ });
145
152
  this.method = method;
146
153
  this.payload = payload;
147
154
  this.noRetry = noRetry;
148
- this.retryCount = retryCount;
149
- this.sessionRotationCount = sessionRotationCount;
150
- this.errorMessages = [...errorMessages];
155
+ this.retryCount = 0;
156
+ this.errorMessages = [];
151
157
  this.headers = { ...headers };
152
- this.handledAt = handledAt instanceof Date ? handledAt.toISOString() : handledAt;
153
158
  if (label) {
154
159
  userData.label = label;
155
160
  }
161
+ // Read `__crawlee` explicitly - on a `userData` coming from another Request instance the
162
+ // bag is non-enumerable, so the spread alone would silently drop the internal state
163
+ // (e.g. `skipNavigation`) when a request is re-wrapped after a storage round trip.
164
+ this.#userData = { __crawlee: userData.__crawlee ?? {}, ...userData };
165
+ // `userData` must stay an enumerable own accessor — serialization in the storages relies on it
156
166
  Object.defineProperties(this, {
157
- _userData: {
158
- value: { __crawlee: {}, ...userData },
159
- enumerable: false,
160
- writable: true,
161
- },
162
167
  userData: {
163
- get: () => this._userData,
168
+ get: () => this.#userData,
164
169
  set: (value) => {
165
170
  Object.defineProperties(value, {
166
171
  __crawlee: {
167
- value: this._userData.__crawlee,
172
+ value: this.#userData.__crawlee,
168
173
  enumerable: false,
169
174
  writable: true,
170
175
  },
171
176
  toJSON: {
172
177
  value: () => {
173
- if (Object.keys(this._userData.__crawlee).length > 0) {
178
+ if (Object.keys(this.#userData.__crawlee).length > 0) {
174
179
  return {
175
- ...this._userData,
176
- __crawlee: this._userData.__crawlee,
180
+ ...this.#userData,
181
+ __crawlee: this.#userData.__crawlee,
177
182
  };
178
183
  }
179
- return this._userData;
184
+ return this.#userData;
180
185
  },
181
186
  enumerable: false,
182
187
  writable: true,
183
188
  },
184
189
  });
185
- this._userData = value;
190
+ this.#userData = value;
186
191
  },
187
192
  enumerable: true,
188
193
  },
189
194
  });
190
195
  // reassign userData to ensure internal `__crawlee` object is non-enumerable
191
196
  this.userData = userData;
197
+ const crawlee = (this.userData.__crawlee ??= {});
192
198
  if (skipNavigation != null)
193
- this.skipNavigation = skipNavigation;
199
+ crawlee.skipNavigation = skipNavigation;
194
200
  if (maxRetries != null)
195
- this.maxRetries = maxRetries;
201
+ crawlee.maxRetries = maxRetries;
196
202
  if (crawlDepth != null)
197
- this.userData.__crawlee.crawlDepth ??= crawlDepth;
198
- // If it's already set, don't override it (for instance when fetching from storage)
199
- if (enqueueStrategy) {
200
- this.enqueueStrategy ??= enqueueStrategy;
201
- }
203
+ crawlee.crawlDepth ??= crawlDepth;
204
+ if (sessionId)
205
+ crawlee.sessionId = sessionId;
206
+ // A stored request keeps the strategy it was enqueued under.
207
+ if (enqueueStrategy)
208
+ crawlee.enqueueStrategy ??= enqueueStrategy;
209
+ }
210
+ /**
211
+ * Rebuilds a request from its stored form, including the processing state a
212
+ * {@link RequestOptions} object cannot carry. Subclasses get instances of themselves.
213
+ */
214
+ static fromSchema(schema) {
215
+ const stored = parseArgument(schema, storedRequestSchema, 'RequestSchema');
216
+ const { id, retryCount = 0, errorMessages = [], handledAt, loadedUrl, ...options } = stored;
217
+ const request = new this(options);
218
+ request.id = id;
219
+ request.retryCount = retryCount;
220
+ request.errorMessages = [...errorMessages];
221
+ request.loadedUrl = loadedUrl;
222
+ request.handledAt =
223
+ handledAt instanceof Date ? handledAt.toISOString() : handledAt;
224
+ return request;
202
225
  }
203
226
  /**
204
227
  * Converts the Crawlee Request object to a `fetch` API Request object.
@@ -211,47 +234,6 @@ class CrawleeRequest {
211
234
  body: this.payload,
212
235
  });
213
236
  }
214
- /** Tells the crawler processing this request to skip the navigation and process the request directly. */
215
- get skipNavigation() {
216
- return this.userData.__crawlee?.skipNavigation ?? false;
217
- }
218
- /** Tells the crawler processing this request to skip the navigation and process the request directly. */
219
- set skipNavigation(value) {
220
- if (!this.userData.__crawlee) {
221
- this.userData.__crawlee = { skipNavigation: value };
222
- }
223
- else {
224
- this.userData.__crawlee.skipNavigation = value;
225
- }
226
- }
227
- /**
228
- * Depth of the request in the current crawl tree.
229
- * Note that this is dependent on the crawler setup and might produce unexpected results when used with multiple crawlers.
230
- */
231
- get crawlDepth() {
232
- return this.userData.__crawlee?.crawlDepth ?? 0;
233
- }
234
- /**
235
- * Depth of the request in the current crawl tree.
236
- * Note that this is dependent on the crawler setup and might produce unexpected results when used with multiple crawlers.
237
- */
238
- set crawlDepth(value) {
239
- this.userData.__crawlee ??= {};
240
- this.userData.__crawlee.crawlDepth = value;
241
- }
242
- /** Indicates the number of times the crawling of the request has rotated the session due to a session or a proxy error. */
243
- get sessionRotationCount() {
244
- return this.userData.__crawlee?.sessionRotationCount ?? 0;
245
- }
246
- /** Indicates the number of times the crawling of the request has rotated the session due to a session or a proxy error. */
247
- set sessionRotationCount(value) {
248
- if (!this.userData.__crawlee) {
249
- this.userData.__crawlee = { sessionRotationCount: value };
250
- }
251
- else {
252
- this.userData.__crawlee.sessionRotationCount = value;
253
- }
254
- }
255
237
  /** shortcut for getting `request.userData.label` */
256
238
  get label() {
257
239
  return this.userData.label;
@@ -260,107 +242,29 @@ class CrawleeRequest {
260
242
  set label(value) {
261
243
  this.userData.label = value;
262
244
  }
263
- /** Maximum number of retries for this request. Allows to override the global `maxRequestRetries` option of `BasicCrawler`. */
264
- get maxRetries() {
265
- return this.userData.__crawlee?.maxRetries;
266
- }
267
- /** Maximum number of retries for this request. Allows to override the global `maxRequestRetries` option of `BasicCrawler`. */
268
- set maxRetries(value) {
269
- if (!this.userData.__crawlee) {
270
- this.userData.__crawlee = { maxRetries: value };
271
- }
272
- else {
273
- this.userData.__crawlee.maxRetries = value;
274
- }
275
- }
276
- /** Describes the request's current lifecycle state. */
277
- get state() {
278
- return this.userData.__crawlee?.state ?? RequestState.UNPROCESSED;
279
- }
280
- /** Describes the request's current lifecycle state. */
281
- set state(value) {
282
- if (!this.userData.__crawlee) {
283
- this.userData.__crawlee = { state: value };
284
- }
285
- else {
286
- this.userData.__crawlee.state = value;
287
- }
288
- }
289
- get enqueueStrategy() {
290
- return this.userData.__crawlee?.enqueueStrategy;
291
- }
292
- set enqueueStrategy(value) {
293
- if (!this.userData.__crawlee) {
294
- this.userData.__crawlee = { enqueueStrategy: value };
295
- }
296
- else {
297
- this.userData.__crawlee.enqueueStrategy = value;
298
- }
299
- }
300
- /**
301
- * Stores information about an error that occurred during processing of this request.
302
- *
303
- * You should always use Error instances when throwing errors in JavaScript.
304
- *
305
- * Nevertheless, to improve the debugging experience when using third party libraries
306
- * that may not always throw an Error instance, the function performs a type
307
- * inspection of the passed argument and attempts to extract as much information
308
- * as possible, since just throwing a bad type error makes any debugging rather difficult.
309
- *
310
- * @param errorOrMessage Error object or error message to be stored in the request.
311
- * @param [options]
312
- */
313
- pushErrorMessage(errorOrMessage, options = {}) {
314
- const { omitStack } = options;
315
- let message;
316
- const type = typeof errorOrMessage;
317
- if (type === 'object') {
318
- if (!errorOrMessage) {
319
- message = 'null';
320
- }
321
- else if (errorOrMessage instanceof Error) {
322
- message = omitStack
323
- ? errorOrMessage.message
324
- : // .stack includes the message
325
- errorOrMessage.stack;
326
- }
327
- else if (Reflect.has(Object(errorOrMessage), 'message')) {
328
- message = Reflect.get(Object(errorOrMessage), 'message');
329
- }
330
- else if (errorOrMessage.toString() !== '[object Object]') {
331
- message = errorOrMessage.toString();
332
- }
333
- else {
334
- try {
335
- message = util.inspect(errorOrMessage);
336
- }
337
- catch (err) {
338
- message = 'Unable to extract any message from the received object.';
339
- }
340
- }
341
- }
342
- else if (type === 'undefined') {
343
- message = 'undefined';
344
- }
345
- else {
346
- message = errorOrMessage.toString();
347
- }
348
- this.errorMessages.push(message);
349
- }
350
245
  /** @internal */
351
- static computeUniqueKey({ url, method = 'GET', payload, keepUrlFragment = false, useExtendedUniqueKey = false, }) {
246
+ static computeUniqueKey({ url, method = 'GET', payload, keepUrlFragment = false, useExtendedUniqueKey = false, alwaysEnqueue = false, }) {
352
247
  const normalizedMethod = method.toUpperCase();
353
248
  const normalizedUrl = normalizeUrl(url, keepUrlFragment) || url; // It returns null when url is invalid, causing weird errors.
249
+ let uniqueKey;
354
250
  if (!useExtendedUniqueKey) {
355
251
  if (normalizedMethod !== 'GET' && payload) {
356
- log.warningOnce(`We've encountered a ${normalizedMethod} Request with a payload. ` +
252
+ serviceLocator
253
+ .getLogger()
254
+ .warningOnce(`We've encountered a ${normalizedMethod} Request with a payload. ` +
357
255
  'This is fine. Just letting you know that if your requests point to the same URL ' +
358
256
  'and differ only in method and payload, you should see the "useExtendedUniqueKey" option of Request constructor.');
359
257
  }
360
- return normalizedUrl;
258
+ uniqueKey = normalizedUrl;
259
+ }
260
+ else {
261
+ const payloadHash = payload ? CrawleeRequest.hashPayload(payload) : '';
262
+ uniqueKey = `${normalizedMethod}|${payloadHash}|${normalizedUrl}`;
263
+ }
264
+ if (alwaysEnqueue) {
265
+ uniqueKey = `${cryptoRandomObjectId(17)}|${uniqueKey}`;
361
266
  }
362
- const payloadHash = payload ? CrawleeRequest.hashPayload(payload) : '';
363
- return `${normalizedMethod}(${payloadHash}):${normalizedUrl}`;
267
+ return uniqueKey;
364
268
  }
365
269
  /** @internal */
366
270
  static hashPayload(payload) {
@@ -368,4 +272,3 @@ class CrawleeRequest {
368
272
  }
369
273
  }
370
274
  export { CrawleeRequest as Request };
371
- //# sourceMappingURL=request.js.map
@@ -29,4 +29,3 @@ export declare function deserializeArray<T extends string | Buffer>(compressedDa
29
29
  * @internal
30
30
  */
31
31
  export declare function createDeserialize(compressedData: Buffer | Uint8Array): Readable;
32
- //# sourceMappingURL=serialization.d.ts.map
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;
@@ -116,7 +117,6 @@ function createChunkCollector(options = {}) {
116
117
  }
117
118
  function pluckValue(streamArray) {
118
119
  const realPush = streamArray.push.bind(streamArray);
119
- streamArray.push = (obj) => realPush(obj && obj.value);
120
+ streamArray.push = (obj) => realPush(obj?.value ?? null);
120
121
  return streamArray;
121
122
  }
122
- //# sourceMappingURL=serialization.js.map