@crawlee/core 4.0.0-beta.12 → 4.0.0-beta.121

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 (279) hide show
  1. package/README.md +17 -13
  2. package/autoscaling/autoscaled_pool.d.ts +67 -172
  3. package/autoscaling/autoscaled_pool.js +165 -320
  4. package/autoscaling/client_load_signal.d.ts +55 -0
  5. package/autoscaling/client_load_signal.js +73 -0
  6. package/autoscaling/concurrency_system.d.ts +268 -0
  7. package/autoscaling/concurrency_system.js +351 -0
  8. package/autoscaling/cpu_load_signal.d.ts +43 -0
  9. package/autoscaling/cpu_load_signal.js +47 -0
  10. package/autoscaling/event_loop_load_signal.d.ts +51 -0
  11. package/autoscaling/event_loop_load_signal.js +60 -0
  12. package/autoscaling/index.d.ts +6 -1
  13. package/autoscaling/index.js +6 -1
  14. package/autoscaling/load_signal.d.ts +99 -0
  15. package/autoscaling/load_signal.js +104 -0
  16. package/autoscaling/memory_load_signal.d.ts +47 -0
  17. package/autoscaling/memory_load_signal.js +106 -0
  18. package/autoscaling/snapshotter.d.ts +58 -163
  19. package/autoscaling/snapshotter.js +45 -263
  20. package/autoscaling/system_status.d.ts +62 -84
  21. package/autoscaling/system_status.js +92 -122
  22. package/autoscaling/weighted_avg.d.ts +5 -0
  23. package/autoscaling/weighted_avg.js +14 -0
  24. package/byte_utils.d.ts +17 -0
  25. package/byte_utils.js +42 -0
  26. package/configuration.d.ts +96 -223
  27. package/configuration.js +170 -222
  28. package/cookie_utils.d.ts +4 -3
  29. package/cookie_utils.js +22 -13
  30. package/crawlers/context_pipeline.d.ts +10 -1
  31. package/crawlers/context_pipeline.js +31 -8
  32. package/crawlers/crawler_commons.d.ts +90 -83
  33. package/crawlers/crawler_commons.js +1 -116
  34. package/crawlers/error_snapshotter.d.ts +2 -5
  35. package/crawlers/error_snapshotter.js +7 -8
  36. package/crawlers/error_tracker.d.ts +0 -1
  37. package/crawlers/error_tracker.js +0 -1
  38. package/crawlers/index.d.ts +1 -3
  39. package/crawlers/index.js +0 -3
  40. package/crawlers/internals/types.d.ts +0 -1
  41. package/crawlers/internals/types.js +0 -1
  42. package/crawlers/statistics.d.ts +143 -59
  43. package/crawlers/statistics.js +243 -153
  44. package/debug.d.ts +36 -0
  45. package/debug.js +70 -0
  46. package/enqueue_links/enqueue_links.d.ts +59 -68
  47. package/enqueue_links/enqueue_links.js +57 -62
  48. package/enqueue_links/index.d.ts +0 -1
  49. package/enqueue_links/index.js +0 -1
  50. package/enqueue_links/shared.d.ts +40 -27
  51. package/enqueue_links/shared.js +90 -68
  52. package/errors.d.ts +72 -4
  53. package/errors.js +89 -5
  54. package/events/event_manager.d.ts +35 -9
  55. package/events/event_manager.js +10 -12
  56. package/events/index.d.ts +0 -1
  57. package/events/index.js +0 -1
  58. package/events/local_event_manager.d.ts +15 -3
  59. package/events/local_event_manager.js +39 -13
  60. package/http.d.ts +9 -0
  61. package/http.js +28 -0
  62. package/index.d.ts +7 -4
  63. package/index.js +6 -3
  64. package/iterables.d.ts +79 -0
  65. package/iterables.js +134 -0
  66. package/log.d.ts +82 -3
  67. package/log.js +106 -1
  68. package/memory-storage/consts.d.ts +4 -0
  69. package/memory-storage/consts.js +4 -0
  70. package/memory-storage/index.d.ts +1 -0
  71. package/memory-storage/index.js +1 -0
  72. package/memory-storage/memory-storage.d.ts +38 -0
  73. package/memory-storage/memory-storage.js +130 -0
  74. package/memory-storage/resource-clients/common/base-client.d.ts +4 -0
  75. package/memory-storage/resource-clients/common/base-client.js +6 -0
  76. package/memory-storage/resource-clients/dataset.d.ts +40 -0
  77. package/memory-storage/resource-clients/dataset.js +114 -0
  78. package/memory-storage/resource-clients/key-value-store.d.ts +63 -0
  79. package/memory-storage/resource-clients/key-value-store.js +204 -0
  80. package/memory-storage/resource-clients/request-queue.d.ts +77 -0
  81. package/memory-storage/resource-clients/request-queue.js +422 -0
  82. package/memory-storage/utils.d.ts +16 -0
  83. package/memory-storage/utils.js +41 -0
  84. package/owned_or_injected.d.ts +58 -0
  85. package/owned_or_injected.js +98 -0
  86. package/package.json +13 -12
  87. package/proxy_configuration.d.ts +24 -132
  88. package/proxy_configuration.js +24 -143
  89. package/recoverable_state.d.ts +140 -0
  90. package/recoverable_state.js +212 -0
  91. package/request.d.ts +86 -17
  92. package/request.js +120 -41
  93. package/router.d.ts +193 -21
  94. package/router.js +188 -43
  95. package/serialization.d.ts +0 -1
  96. package/serialization.js +9 -11
  97. package/service_locator.d.ts +165 -0
  98. package/service_locator.js +253 -0
  99. package/session_pool/consts.d.ts +1 -2
  100. package/session_pool/consts.js +1 -2
  101. package/session_pool/errors.d.ts +0 -1
  102. package/session_pool/errors.js +0 -1
  103. package/session_pool/fingerprint.d.ts +9 -0
  104. package/session_pool/fingerprint.js +30 -0
  105. package/session_pool/index.d.ts +0 -2
  106. package/session_pool/index.js +0 -2
  107. package/session_pool/session.d.ts +35 -89
  108. package/session_pool/session.js +82 -142
  109. package/session_pool/session_pool.d.ts +69 -90
  110. package/session_pool/session_pool.js +151 -150
  111. package/storages/batched_adds.d.ts +37 -0
  112. package/storages/batched_adds.js +73 -0
  113. package/storages/dataset.d.ts +114 -54
  114. package/storages/dataset.js +285 -144
  115. package/storages/index.d.ts +10 -8
  116. package/storages/index.js +8 -8
  117. package/storages/key_value_store.d.ts +185 -42
  118. package/storages/key_value_store.js +424 -151
  119. package/storages/key_value_store_codec.d.ts +32 -0
  120. package/storages/key_value_store_codec.js +113 -0
  121. package/storages/request_dedup_cache.d.ts +22 -0
  122. package/storages/request_dedup_cache.js +48 -0
  123. package/storages/request_list.d.ts +52 -116
  124. package/storages/request_list.js +159 -133
  125. package/storages/request_loader.d.ts +101 -0
  126. package/storages/request_loader.js +1 -0
  127. package/storages/request_manager.d.ts +33 -0
  128. package/storages/request_manager.js +1 -0
  129. package/storages/request_manager_tandem.d.ts +97 -0
  130. package/storages/request_manager_tandem.js +197 -0
  131. package/storages/request_queue.d.ts +290 -47
  132. package/storages/request_queue.js +757 -216
  133. package/storages/{sitemap_request_list.d.ts → sitemap_request_loader.d.ts} +37 -88
  134. package/storages/{sitemap_request_list.js → sitemap_request_loader.js} +137 -143
  135. package/storages/storage_instance_manager.d.ts +87 -0
  136. package/storages/storage_instance_manager.js +256 -0
  137. package/storages/storage_stats.d.ts +48 -0
  138. package/storages/storage_stats.js +29 -0
  139. package/storages/throttling_request_manager.d.ts +216 -0
  140. package/storages/throttling_request_manager.js +453 -0
  141. package/storages/transaction.d.ts +252 -0
  142. package/storages/transaction.js +251 -0
  143. package/storages/utils.d.ts +58 -11
  144. package/storages/utils.js +64 -13
  145. package/system-info/cpu-info.d.ts +67 -0
  146. package/system-info/cpu-info.js +216 -0
  147. package/system-info/memory-info.d.ts +31 -0
  148. package/system-info/memory-info.js +115 -0
  149. package/system-info/ps-tree.d.ts +17 -0
  150. package/system-info/ps-tree.js +144 -0
  151. package/system-info/runtime.d.ts +14 -0
  152. package/system-info/runtime.js +80 -0
  153. package/typedefs.d.ts +0 -6
  154. package/typedefs.js +0 -1
  155. package/url.d.ts +9 -0
  156. package/url.js +11 -0
  157. package/validators.d.ts +8 -1
  158. package/validators.js +10 -3
  159. package/autoscaling/autoscaled_pool.d.ts.map +0 -1
  160. package/autoscaling/autoscaled_pool.js.map +0 -1
  161. package/autoscaling/index.d.ts.map +0 -1
  162. package/autoscaling/index.js.map +0 -1
  163. package/autoscaling/snapshotter.d.ts.map +0 -1
  164. package/autoscaling/snapshotter.js.map +0 -1
  165. package/autoscaling/system_status.d.ts.map +0 -1
  166. package/autoscaling/system_status.js.map +0 -1
  167. package/configuration.d.ts.map +0 -1
  168. package/configuration.js.map +0 -1
  169. package/cookie_utils.d.ts.map +0 -1
  170. package/cookie_utils.js.map +0 -1
  171. package/crawlers/context_pipeline.d.ts.map +0 -1
  172. package/crawlers/context_pipeline.js.map +0 -1
  173. package/crawlers/crawler_commons.d.ts.map +0 -1
  174. package/crawlers/crawler_commons.js.map +0 -1
  175. package/crawlers/crawler_utils.d.ts +0 -10
  176. package/crawlers/crawler_utils.d.ts.map +0 -1
  177. package/crawlers/crawler_utils.js +0 -12
  178. package/crawlers/crawler_utils.js.map +0 -1
  179. package/crawlers/error_snapshotter.d.ts.map +0 -1
  180. package/crawlers/error_snapshotter.js.map +0 -1
  181. package/crawlers/error_tracker.d.ts.map +0 -1
  182. package/crawlers/error_tracker.js.map +0 -1
  183. package/crawlers/index.d.ts.map +0 -1
  184. package/crawlers/index.js.map +0 -1
  185. package/crawlers/internals/types.d.ts.map +0 -1
  186. package/crawlers/internals/types.js.map +0 -1
  187. package/crawlers/statistics.d.ts.map +0 -1
  188. package/crawlers/statistics.js.map +0 -1
  189. package/enqueue_links/enqueue_links.d.ts.map +0 -1
  190. package/enqueue_links/enqueue_links.js.map +0 -1
  191. package/enqueue_links/index.d.ts.map +0 -1
  192. package/enqueue_links/index.js.map +0 -1
  193. package/enqueue_links/shared.d.ts.map +0 -1
  194. package/enqueue_links/shared.js.map +0 -1
  195. package/errors.d.ts.map +0 -1
  196. package/errors.js.map +0 -1
  197. package/events/event_manager.d.ts.map +0 -1
  198. package/events/event_manager.js.map +0 -1
  199. package/events/index.d.ts.map +0 -1
  200. package/events/index.js.map +0 -1
  201. package/events/local_event_manager.d.ts.map +0 -1
  202. package/events/local_event_manager.js.map +0 -1
  203. package/http_clients/base-http-client.d.ts +0 -134
  204. package/http_clients/base-http-client.d.ts.map +0 -1
  205. package/http_clients/base-http-client.js +0 -33
  206. package/http_clients/base-http-client.js.map +0 -1
  207. package/http_clients/form-data-like.d.ts +0 -67
  208. package/http_clients/form-data-like.d.ts.map +0 -1
  209. package/http_clients/form-data-like.js +0 -5
  210. package/http_clients/form-data-like.js.map +0 -1
  211. package/http_clients/got-scraping-http-client.d.ts +0 -15
  212. package/http_clients/got-scraping-http-client.d.ts.map +0 -1
  213. package/http_clients/got-scraping-http-client.js +0 -69
  214. package/http_clients/got-scraping-http-client.js.map +0 -1
  215. package/http_clients/index.d.ts +0 -3
  216. package/http_clients/index.d.ts.map +0 -1
  217. package/http_clients/index.js +0 -3
  218. package/http_clients/index.js.map +0 -1
  219. package/index.d.ts.map +0 -1
  220. package/index.js.map +0 -1
  221. package/log.d.ts.map +0 -1
  222. package/log.js.map +0 -1
  223. package/proxy_configuration.d.ts.map +0 -1
  224. package/proxy_configuration.js.map +0 -1
  225. package/request.d.ts.map +0 -1
  226. package/request.js.map +0 -1
  227. package/router.d.ts.map +0 -1
  228. package/router.js.map +0 -1
  229. package/serialization.d.ts.map +0 -1
  230. package/serialization.js.map +0 -1
  231. package/session_pool/consts.d.ts.map +0 -1
  232. package/session_pool/consts.js.map +0 -1
  233. package/session_pool/errors.d.ts.map +0 -1
  234. package/session_pool/errors.js.map +0 -1
  235. package/session_pool/events.d.ts +0 -3
  236. package/session_pool/events.d.ts.map +0 -1
  237. package/session_pool/events.js +0 -3
  238. package/session_pool/events.js.map +0 -1
  239. package/session_pool/index.d.ts.map +0 -1
  240. package/session_pool/index.js.map +0 -1
  241. package/session_pool/session.d.ts.map +0 -1
  242. package/session_pool/session.js.map +0 -1
  243. package/session_pool/session_pool.d.ts.map +0 -1
  244. package/session_pool/session_pool.js.map +0 -1
  245. package/storages/access_checking.d.ts +0 -13
  246. package/storages/access_checking.d.ts.map +0 -1
  247. package/storages/access_checking.js +0 -14
  248. package/storages/access_checking.js.map +0 -1
  249. package/storages/dataset.d.ts.map +0 -1
  250. package/storages/dataset.js.map +0 -1
  251. package/storages/index.d.ts.map +0 -1
  252. package/storages/index.js.map +0 -1
  253. package/storages/key_value_store.d.ts.map +0 -1
  254. package/storages/key_value_store.js.map +0 -1
  255. package/storages/request_list.d.ts.map +0 -1
  256. package/storages/request_list.js.map +0 -1
  257. package/storages/request_provider.d.ts +0 -308
  258. package/storages/request_provider.d.ts.map +0 -1
  259. package/storages/request_provider.js +0 -555
  260. package/storages/request_provider.js.map +0 -1
  261. package/storages/request_queue.d.ts.map +0 -1
  262. package/storages/request_queue.js.map +0 -1
  263. package/storages/request_queue_v2.d.ts +0 -87
  264. package/storages/request_queue_v2.d.ts.map +0 -1
  265. package/storages/request_queue_v2.js +0 -438
  266. package/storages/request_queue_v2.js.map +0 -1
  267. package/storages/sitemap_request_list.d.ts.map +0 -1
  268. package/storages/sitemap_request_list.js.map +0 -1
  269. package/storages/storage_manager.d.ts +0 -58
  270. package/storages/storage_manager.d.ts.map +0 -1
  271. package/storages/storage_manager.js +0 -105
  272. package/storages/storage_manager.js.map +0 -1
  273. package/storages/utils.d.ts.map +0 -1
  274. package/storages/utils.js.map +0 -1
  275. package/tsconfig.build.tsbuildinfo +0 -1
  276. package/typedefs.d.ts.map +0 -1
  277. package/typedefs.js.map +0 -1
  278. package/validators.d.ts.map +0 -1
  279. package/validators.js.map +0 -1
package/request.js CHANGED
@@ -1,11 +1,10 @@
1
1
  import crypto from 'node:crypto';
2
2
  import util from 'node:util';
3
3
  import ow from 'ow';
4
- import { normalizeUrl } from '@apify/utilities';
5
- import { log as defaultLog } from './log.js';
4
+ import { cryptoRandomObjectId, normalizeUrl } from '@apify/utilities';
5
+ import { serviceLocator } from './service_locator.js';
6
6
  import { keys } from './typedefs.js';
7
7
  // new properties on the Request object breaks serialization
8
- const log = defaultLog.child({ prefix: 'Request' });
9
8
  const requestOptionalPredicates = {
10
9
  id: ow.optional.string,
11
10
  loadedUrl: ow.optional.string.url,
@@ -14,7 +13,7 @@ const requestOptionalPredicates = {
14
13
  payload: ow.optional.any(ow.string, ow.uint8Array),
15
14
  noRetry: ow.optional.boolean,
16
15
  retryCount: ow.optional.number,
17
- sessionRotationCount: ow.optional.number,
16
+ sessionId: ow.optional.string,
18
17
  maxRetries: ow.optional.number,
19
18
  errorMessages: ow.optional.array.ofType(ow.string),
20
19
  headers: ow.optional.object,
@@ -23,7 +22,9 @@ const requestOptionalPredicates = {
23
22
  handledAt: ow.optional.any(ow.string.date, ow.date),
24
23
  keepUrlFragment: ow.optional.boolean,
25
24
  useExtendedUniqueKey: ow.optional.boolean,
25
+ alwaysEnqueue: ow.optional.boolean,
26
26
  skipNavigation: ow.optional.boolean,
27
+ crawlDepth: ow.optional.number.greaterThanOrEqual(0),
27
28
  state: ow.optional.number.greaterThanOrEqual(0).lessThanOrEqual(6),
28
29
  };
29
30
  export var RequestState;
@@ -68,7 +69,7 @@ export var RequestState;
68
69
  * ```
69
70
  * @category Sources
70
71
  */
71
- export class Request {
72
+ class CrawleeRequest {
72
73
  /** Request ID */
73
74
  id;
74
75
  /** URL of the web page to crawl. */
@@ -100,8 +101,13 @@ export class Request {
100
101
  /** Object with HTTP headers. Key is header name, value is the value. */
101
102
  headers;
102
103
  /** Private store for the custom user data assigned to the request. */
103
- _userData = {};
104
- /** Custom user data assigned to the request. */
104
+ #userData = {};
105
+ /**
106
+ * Custom user data assigned to the request.
107
+ *
108
+ * All data stored in `userData` must be JSON-serializable.
109
+ * Storing non-serializable values (e.g. functions, symbols) may result in unexpected results.
110
+ */
105
111
  userData = {};
106
112
  /**
107
113
  * ISO datetime string that indicates the time when the request has been processed.
@@ -130,57 +136,67 @@ export class Request {
130
136
  ow(value, `RequestOptions.${prop}`, predicate);
131
137
  }
132
138
  });
133
- const { id, url, loadedUrl, uniqueKey, payload, noRetry = false, retryCount = 0, sessionRotationCount = 0, maxRetries, errorMessages = [], headers = {}, userData = {}, label, handledAt, keepUrlFragment = false, useExtendedUniqueKey = false, skipNavigation, enqueueStrategy, } = options;
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;
134
140
  let { method = 'GET' } = options;
135
141
  method = method.toUpperCase();
136
142
  if (method === 'GET' && payload)
137
143
  throw new Error('Request with GET method cannot have a payload.');
144
+ if (uniqueKey && alwaysEnqueue) {
145
+ throw new Error('`alwaysEnqueue` cannot be used together with a custom `uniqueKey`.');
146
+ }
138
147
  this.id = id;
139
148
  this.url = url;
140
149
  this.loadedUrl = loadedUrl;
141
150
  this.uniqueKey =
142
- uniqueKey || Request.computeUniqueKey({ url, method, payload, keepUrlFragment, useExtendedUniqueKey });
151
+ uniqueKey ||
152
+ CrawleeRequest.computeUniqueKey({
153
+ url,
154
+ method,
155
+ payload,
156
+ keepUrlFragment,
157
+ useExtendedUniqueKey,
158
+ alwaysEnqueue,
159
+ });
143
160
  this.method = method;
144
161
  this.payload = payload;
145
162
  this.noRetry = noRetry;
146
163
  this.retryCount = retryCount;
147
- this.sessionRotationCount = sessionRotationCount;
148
164
  this.errorMessages = [...errorMessages];
149
165
  this.headers = { ...headers };
150
166
  this.handledAt = handledAt instanceof Date ? handledAt.toISOString() : handledAt;
151
167
  if (label) {
152
168
  userData.label = label;
153
169
  }
170
+ // Read `__crawlee` explicitly - on a `userData` coming from another Request instance the
171
+ // bag is non-enumerable, so the spread alone would silently drop the internal state
172
+ // (e.g. `skipNavigation`) when a request is re-wrapped after a storage round trip.
173
+ this.#userData = { __crawlee: userData.__crawlee ?? {}, ...userData };
174
+ // `userData` must stay an enumerable own accessor — serialization in the storages relies on it
154
175
  Object.defineProperties(this, {
155
- _userData: {
156
- value: { __crawlee: {}, ...userData },
157
- enumerable: false,
158
- writable: true,
159
- },
160
176
  userData: {
161
- get: () => this._userData,
177
+ get: () => this.#userData,
162
178
  set: (value) => {
163
179
  Object.defineProperties(value, {
164
180
  __crawlee: {
165
- value: this._userData.__crawlee,
181
+ value: this.#userData.__crawlee,
166
182
  enumerable: false,
167
183
  writable: true,
168
184
  },
169
185
  toJSON: {
170
186
  value: () => {
171
- if (Object.keys(this._userData.__crawlee).length > 0) {
187
+ if (Object.keys(this.#userData.__crawlee).length > 0) {
172
188
  return {
173
- ...this._userData,
174
- __crawlee: this._userData.__crawlee,
189
+ ...this.#userData,
190
+ __crawlee: this.#userData.__crawlee,
175
191
  };
176
192
  }
177
- return this._userData;
193
+ return this.#userData;
178
194
  },
179
195
  enumerable: false,
180
196
  writable: true,
181
197
  },
182
198
  });
183
- this._userData = value;
199
+ this.#userData = value;
184
200
  },
185
201
  enumerable: true,
186
202
  },
@@ -191,16 +207,43 @@ export class Request {
191
207
  this.skipNavigation = skipNavigation;
192
208
  if (maxRetries != null)
193
209
  this.maxRetries = maxRetries;
210
+ if (crawlDepth != null)
211
+ this.userData.__crawlee.crawlDepth ??= crawlDepth;
212
+ if (sessionId)
213
+ this.sessionId = sessionId;
194
214
  // If it's already set, don't override it (for instance when fetching from storage)
195
215
  if (enqueueStrategy) {
196
216
  this.enqueueStrategy ??= enqueueStrategy;
197
217
  }
198
218
  }
199
- /** Tells the crawler processing this request to skip the navigation and process the request directly. */
219
+ /**
220
+ * Converts the Crawlee Request object to a `fetch` API Request object.
221
+ * @returns The native `fetch` API Request object.
222
+ */
223
+ intoFetchAPIRequest() {
224
+ return new Request(this.url, {
225
+ method: this.method,
226
+ headers: this.headers,
227
+ body: this.payload,
228
+ });
229
+ }
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
+ */
200
237
  get skipNavigation() {
201
238
  return this.userData.__crawlee?.skipNavigation ?? false;
202
239
  }
203
- /** Tells the crawler processing this request to skip the navigation and process the request directly. */
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
+ */
204
247
  set skipNavigation(value) {
205
248
  if (!this.userData.__crawlee) {
206
249
  this.userData.__crawlee = { skipNavigation: value };
@@ -209,18 +252,28 @@ export class Request {
209
252
  this.userData.__crawlee.skipNavigation = value;
210
253
  }
211
254
  }
212
- /** Indicates the number of times the crawling of the request has rotated the session due to a session or a proxy error. */
213
- get sessionRotationCount() {
214
- return this.userData.__crawlee?.sessionRotationCount ?? 0;
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;
215
261
  }
216
- /** Indicates the number of times the crawling of the request has rotated the session due to a session or a proxy error. */
217
- set sessionRotationCount(value) {
218
- if (!this.userData.__crawlee) {
219
- this.userData.__crawlee = { sessionRotationCount: value };
220
- }
221
- else {
222
- this.userData.__crawlee.sessionRotationCount = value;
223
- }
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;
224
277
  }
225
278
  /** shortcut for getting `request.userData.label` */
226
279
  get label() {
@@ -256,6 +309,23 @@ export class Request {
256
309
  this.userData.__crawlee.state = value;
257
310
  }
258
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
+ }
259
329
  get enqueueStrategy() {
260
330
  return this.userData.__crawlee?.enqueueStrategy;
261
331
  }
@@ -318,23 +388,32 @@ export class Request {
318
388
  this.errorMessages.push(message);
319
389
  }
320
390
  /** @internal */
321
- static computeUniqueKey({ url, method = 'GET', payload, keepUrlFragment = false, useExtendedUniqueKey = false, }) {
391
+ static computeUniqueKey({ url, method = 'GET', payload, keepUrlFragment = false, useExtendedUniqueKey = false, alwaysEnqueue = false, }) {
322
392
  const normalizedMethod = method.toUpperCase();
323
393
  const normalizedUrl = normalizeUrl(url, keepUrlFragment) || url; // It returns null when url is invalid, causing weird errors.
394
+ let uniqueKey;
324
395
  if (!useExtendedUniqueKey) {
325
396
  if (normalizedMethod !== 'GET' && payload) {
326
- log.warningOnce(`We've encountered a ${normalizedMethod} Request with a payload. ` +
397
+ serviceLocator
398
+ .getLogger()
399
+ .warningOnce(`We've encountered a ${normalizedMethod} Request with a payload. ` +
327
400
  'This is fine. Just letting you know that if your requests point to the same URL ' +
328
401
  'and differ only in method and payload, you should see the "useExtendedUniqueKey" option of Request constructor.');
329
402
  }
330
- return normalizedUrl;
403
+ uniqueKey = normalizedUrl;
404
+ }
405
+ else {
406
+ const payloadHash = payload ? CrawleeRequest.hashPayload(payload) : '';
407
+ uniqueKey = `${normalizedMethod}|${payloadHash}|${normalizedUrl}`;
408
+ }
409
+ if (alwaysEnqueue) {
410
+ uniqueKey = `${cryptoRandomObjectId(17)}|${uniqueKey}`;
331
411
  }
332
- const payloadHash = payload ? Request.hashPayload(payload) : '';
333
- return `${normalizedMethod}(${payloadHash}):${normalizedUrl}`;
412
+ return uniqueKey;
334
413
  }
335
414
  /** @internal */
336
415
  static hashPayload(payload) {
337
416
  return crypto.createHash('sha256').update(payload).digest('base64').replace(/[+/=]/g, '').substring(0, 8);
338
417
  }
339
418
  }
340
- //# sourceMappingURL=request.js.map
419
+ export { CrawleeRequest as Request };
package/router.d.ts CHANGED
@@ -1,14 +1,91 @@
1
- import type { Dictionary } from '@crawlee/types';
2
- import type { CrawlingContext, LoadedRequest, RestrictedCrawlingContext } from './crawlers/crawler_commons.js';
1
+ import type { Awaitable, Dictionary } from '@crawlee/types';
2
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
3
+ import type { CrawlingContext, LoadedRequest, RestrictedCrawlingContext, TypedContextAddRequests, TypedContextEnqueueLinks } from './crawlers/crawler_commons.js';
3
4
  import type { Request } from './request.js';
4
- import type { Awaitable } from './typedefs.js';
5
- export interface RouterHandler<Context extends Omit<RestrictedCrawlingContext, 'enqueueLinks'> = CrawlingContext> extends Router<Context> {
5
+ /**
6
+ * The key of the default route the fallback handler registered via {@link Router.addDefaultHandler}.
7
+ * Use it in a {@link RouteSchemas} map to register a schema that validates the `userData` of every request
8
+ * that falls through to the default handler (i.e. whose label has no route of its own).
9
+ */
10
+ export declare const defaultRoute: unique symbol;
11
+ /**
12
+ * The crawling context received by a route handler, with `request.userData` narrowed to `UserData`, and
13
+ * `addRequests`/`enqueueLinks` typed according to the router's route map (`Routes`) so that enqueuing a
14
+ * request under a declared label requires the matching `userData` shape.
15
+ */
16
+ export type RouterHandlerContext<Context, UserData extends Dictionary, Routes extends Record<keyof Routes, Dictionary>> = Omit<Context, 'request' | 'addRequests' | 'enqueueLinks'> & {
17
+ request: LoadedRequest<Request<UserData>>;
18
+ addRequests: TypedContextAddRequests<Routes>;
19
+ } & (Context extends {
20
+ enqueueLinks: infer EnqueueLinks;
21
+ } ? {
22
+ enqueueLinks: TypedContextEnqueueLinks<EnqueueLinks, Routes>;
23
+ } : {});
24
+ /**
25
+ * A map of request labels to a [Standard Schema](https://standardschema.dev) (Zod, Valibot, ArkType, …)
26
+ * validating that label's `request.userData`. Pass it to {@link Router.create} or a `createXRouter`
27
+ * factory to derive the per-label `request.userData` types *and* validate them at runtime. The optional
28
+ * {@link defaultRoute} key registers a schema for requests handled by the default route.
29
+ */
30
+ export type RouteSchemas = Record<string, StandardSchemaV1> & {
31
+ [defaultRoute]?: StandardSchemaV1;
32
+ };
33
+ /** Infers a label's `userData` type from its schema, falling back to a plain {@link Dictionary}. */
34
+ type SchemaUserData<Schema extends StandardSchemaV1> = StandardSchemaV1.InferOutput<Schema> extends Dictionary ? StandardSchemaV1.InferOutput<Schema> : Dictionary;
35
+ /**
36
+ * Derives a route map (label → `userData` type) from a {@link RouteSchemas} map by inferring each schema's
37
+ * output type. Outputs that are not object-shaped fall back to a plain {@link Dictionary}. The
38
+ * {@link defaultRoute} schema is kept under its symbol key so {@link Router.addDefaultHandler} can pick it
39
+ * up; string labels (the ones {@link Router.addHandler} and the crawler-level typing accept) ignore it.
40
+ */
41
+ export type RoutesFromSchemas<Schemas extends RouteSchemas> = {
42
+ [Label in Extract<keyof Schemas, string>]: SchemaUserData<Schemas[Label]>;
43
+ } & (Schemas extends {
44
+ [defaultRoute]: StandardSchemaV1;
45
+ } ? {
46
+ [defaultRoute]: SchemaUserData<Schemas[typeof defaultRoute]>;
47
+ } : {});
48
+ /**
49
+ * The `userData` type of the default route: inferred from the {@link defaultRoute} schema when the route map
50
+ * carries one, otherwise the provided `Fallback`.
51
+ */
52
+ export type DefaultRouteUserData<Routes, Fallback extends Dictionary> = Routes extends {
53
+ [defaultRoute]: infer DefaultUserData extends Dictionary;
54
+ } ? DefaultUserData : Fallback;
55
+ /**
56
+ * Validates `userData` against a {@link RouteSchemas|Standard Schema}, returning the parsed (and coerced)
57
+ * value. Throws a {@link RequestValidationError} when validation fails.
58
+ * @internal
59
+ */
60
+ export declare function validateUserData(label: string | symbol, schema: StandardSchemaV1, userData: unknown): Promise<Dictionary>;
61
+ /**
62
+ * The set of labels accepted by {@link Router.addHandler}. When the router declares a concrete
63
+ * route map (e.g. `{ PRODUCT: ...; CATEGORY: ... }`), only those labels (plus symbols) are
64
+ * allowed — unknown labels become a compile-time error. When the map is left open (the default
65
+ * `Record<string, ...>`), any string or symbol label is accepted, preserving the original behaviour.
66
+ */
67
+ export type RouterLabel<Routes extends Record<keyof Routes, Dictionary>> = string extends keyof Routes ? string | symbol : (keyof Routes & string) | symbol;
68
+ export interface RouterHandler<Context extends Omit<RestrictedCrawlingContext, 'enqueueLinks'> = CrawlingContext, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>> extends Router<Context, Routes> {
6
69
  (ctx: Context): Awaitable<void>;
7
70
  }
8
71
  export type GetUserDataFromRequest<T> = T extends Request<infer Y> ? Y : never;
9
- export type RouterRoutes<Context, UserData extends Dictionary> = {
10
- [label in string | symbol]: (ctx: Omit<Context, 'request'> & {
11
- request: Request<UserData>;
72
+ /**
73
+ * Per-route overrides, passed as the last argument of {@link Router.addHandler|`addHandler`} and
74
+ * {@link Router.addDefaultHandler|`addDefaultHandler`}.
75
+ */
76
+ export interface RouteOptions {
77
+ /**
78
+ * Overrides the crawler's `requestHandlerTimeoutSecs` for this route only. Useful when one kind of page
79
+ * needs markedly more time than the rest - a listing page behind an infinite scroll, say - and you do not
80
+ * want to raise the timeout for every other page to accommodate it.
81
+ *
82
+ * Applies only to this route's handler. The navigation and the navigation hooks keep their own timeouts.
83
+ */
84
+ requestHandlerTimeoutSecs?: number;
85
+ }
86
+ export type RouterRoutes<Context, Routes extends Record<keyof Routes, Dictionary>> = {
87
+ [Label in keyof Routes]: (ctx: Omit<Context, 'request'> & {
88
+ request: Request<Routes[Label]>;
12
89
  }) => Awaitable<void>;
13
90
  };
14
91
  /**
@@ -75,36 +152,129 @@ export type RouterRoutes<Context, UserData extends Dictionary> = {
75
152
  * ctx.log.info('...');
76
153
  * });
77
154
  * ```
155
+ *
156
+ * To get `request.userData` typed per label, declare a route map and pass it as the second
157
+ * type argument. The label passed to {@link Router.addHandler} then drives the type of
158
+ * `request.userData`, and unknown labels are rejected at compile time:
159
+ *
160
+ * ```ts
161
+ * import { createCheerioRouter, CheerioCrawlingContext } from 'crawlee';
162
+ *
163
+ * interface Routes {
164
+ * PRODUCT: { sku: string; price: number };
165
+ * CATEGORY: { categoryId: string };
166
+ * }
167
+ *
168
+ * const router = createCheerioRouter<CheerioCrawlingContext, Routes>();
169
+ *
170
+ * router.addHandler('PRODUCT', async ({ request }) => {
171
+ * request.userData.sku; // string
172
+ * request.userData.price; // number
173
+ * });
174
+ *
175
+ * router.addHandler('TYPO', async () => {}); // compile error: not a known label
176
+ * ```
177
+ *
178
+ * Passing a [Standard Schema](https://standardschema.dev) per label instead of a plain type both infers the
179
+ * `request.userData` types *and* validates them at runtime — when the request is handled, and when it is
180
+ * added to the crawler (`crawler.addRequests`, `context.addRequests`, `enqueueLinks`). A failing request
181
+ * throws a {@link RequestValidationError}.
182
+ *
183
+ * ```ts
184
+ * import { z } from 'zod';
185
+ * import { createCheerioRouter } from 'crawlee';
186
+ *
187
+ * const router = createCheerioRouter({
188
+ * PRODUCT: z.object({ sku: z.string(), price: z.number() }),
189
+ * CATEGORY: z.object({ categoryId: z.string() }),
190
+ * });
191
+ *
192
+ * router.addHandler('PRODUCT', async ({ request }) => {
193
+ * request.userData.price; // number, inferred from the schema and validated at runtime
194
+ * });
195
+ * ```
196
+ *
197
+ * A single route can take longer than the rest without raising the crawler-wide
198
+ * `requestHandlerTimeoutSecs` for everything - pass a per-route timeout as the last argument:
199
+ *
200
+ * ```ts
201
+ * // LIST pages scroll through a lot of content, DETAIL pages are quick
202
+ * router.addHandler('LIST', async (ctx) => { ... }, { requestHandlerTimeoutSecs: 120 });
203
+ * router.addHandler('DETAIL', async (ctx) => { ... }); // keeps the crawler's default
204
+ * ```
205
+ *
206
+ * When the time a route needs is only apparent once it is already running, call
207
+ * {@link CrawlingContext.extendTimeout|`context.extendTimeout`} from inside the handler:
208
+ *
209
+ * ```ts
210
+ * router.addHandler('LIST', async ({ page, extendTimeout }) => {
211
+ * const pageCount = await countPages(page);
212
+ * extendTimeout(pageCount * 10); // ask for 10 more seconds per page
213
+ * await scrapeAllPages(page);
214
+ * });
215
+ * ```
78
216
  */
79
- export declare class Router<Context extends Omit<RestrictedCrawlingContext, 'enqueueLinks'>> {
80
- private readonly routes;
81
- private readonly middlewares;
217
+ export declare class Router<Context extends Omit<RestrictedCrawlingContext, 'enqueueLinks'>, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>> {
218
+ #private;
82
219
  /**
83
220
  * use Router.create() instead!
84
221
  * @ignore
85
222
  */
86
- protected constructor();
223
+ private constructor();
224
+ /**
225
+ * Registers new route handler for given label. When the router declares a route map, the
226
+ * `label` is restricted to the declared labels and `request.userData` is typed accordingly. Pass
227
+ * {@link RouteOptions|`options`} to give this route its own `requestHandlerTimeoutSecs`,
228
+ * overriding the crawler's default for requests with this label.
229
+ */
230
+ addHandler<Label extends keyof Routes & string>(label: Label, handler: (ctx: RouterHandlerContext<Context, Routes[Label], Routes>) => Awaitable<void>, options?: RouteOptions): void;
231
+ /**
232
+ * Registers new route handler for given label, explicitly typing `request.userData` via the
233
+ * `UserData` type argument. Useful when the router has no declared route map (the open default)
234
+ * and you want to type a single handler, or to register a handler under a `symbol` label.
235
+ */
236
+ addHandler<UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(label: RouterLabel<Routes>, handler: (ctx: RouterHandlerContext<Context, UserData, Routes>) => Awaitable<void>, options?: RouteOptions): void;
87
237
  /**
88
- * Registers new route handler for given label.
238
+ * Registers default route handler. As a fallback it can receive any request (including labels not
239
+ * declared in the route map). When the router was created with a {@link defaultRoute} schema,
240
+ * `request.userData` is typed from it; otherwise it defaults to the context's (loosely typed) `userData`.
241
+ * Pass an explicit `UserData` type argument to narrow it. Pass {@link RouteOptions|`options`} to give the
242
+ * default route its own `requestHandlerTimeoutSecs`, overriding the crawler's default for requests that fall
243
+ * through to it.
89
244
  */
90
- addHandler<UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(label: string | symbol, handler: (ctx: Omit<Context, 'request'> & {
91
- request: LoadedRequest<Request<UserData>>;
92
- }) => Awaitable<void>): void;
245
+ addDefaultHandler<UserData extends Dictionary = DefaultRouteUserData<Routes, GetUserDataFromRequest<Context['request']>>>(handler: (ctx: RouterHandlerContext<Context, UserData, Routes>) => Awaitable<void>, options?: RouteOptions): void;
93
246
  /**
94
- * Registers default route handler.
247
+ * Returns the {@link RouteSchemas|Standard Schema} registered for a label, if any. Used by the crawler
248
+ * to validate `request.userData` when requests are added.
249
+ * @internal
95
250
  */
96
- addDefaultHandler<UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(handler: (ctx: Omit<Context, 'request'> & {
97
- request: LoadedRequest<Request<UserData>>;
98
- }) => Awaitable<void>): void;
251
+ getSchema(label?: string | symbol): StandardSchemaV1 | undefined;
99
252
  /**
100
253
  * Registers a middleware that will be fired before the matching route handler.
101
254
  * Multiple middlewares can be registered, they will be fired in the same order.
102
255
  */
103
256
  use(middleware: (ctx: Context) => Awaitable<void>): void;
257
+ /**
258
+ * Returns the `requestHandlerTimeoutSecs` registered for a label, or `undefined` when the route did not
259
+ * override it and the crawler's own timeout should apply. Falls back to the default route the same way
260
+ * {@link Router.getHandler|`getHandler`} does, so a label with no route of its own inherits whatever
261
+ * the default route asked for. Used by the crawler; not meant to be called directly.
262
+ */
263
+ getTimeoutSecs(label?: string | symbol): number | undefined;
264
+ /**
265
+ * The longest `requestHandlerTimeoutSecs` any route asked for, or `undefined` when no route overrides it.
266
+ * The crawler needs an upper bound up front, before it knows which routes a run will actually hit.
267
+ */
268
+ getMaxTimeoutSecs(): number | undefined;
104
269
  /**
105
270
  * Returns route handler for given label. If no label is provided, the default request handler will be returned.
106
271
  */
107
272
  getHandler(label?: string | symbol): (ctx: Context) => Awaitable<void>;
273
+ /**
274
+ * Validates `request.userData` against the schema registered for its label (if any), replacing it with
275
+ * the parsed value. Throws a {@link RequestValidationError} when validation fails.
276
+ */
277
+ private validateRequest;
108
278
  /**
109
279
  * Throws when the label already exists in our registry.
110
280
  */
@@ -129,6 +299,8 @@ export declare class Router<Context extends Omit<RestrictedCrawlingContext, 'enq
129
299
  * await crawler.run();
130
300
  * ```
131
301
  */
132
- static create<Context extends Omit<RestrictedCrawlingContext, 'enqueueLinks'> = CrawlingContext, UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(routes?: RouterRoutes<Context, UserData>): RouterHandler<Context>;
302
+ static create<Context extends Omit<RestrictedCrawlingContext, 'enqueueLinks'> = CrawlingContext, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>>(routes?: RouterRoutes<Context, Routes>): RouterHandler<Context, Routes>;
303
+ static create<Context extends Omit<RestrictedCrawlingContext, 'enqueueLinks'> = CrawlingContext, UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(routes?: RouterRoutes<Context, Record<string, UserData>>): RouterHandler<Context, Record<string, UserData>>;
304
+ static create<Context extends Omit<RestrictedCrawlingContext, 'enqueueLinks'> = CrawlingContext, const Schemas extends RouteSchemas = RouteSchemas>(schemas: Schemas): RouterHandler<Context, RoutesFromSchemas<Schemas>>;
133
305
  }
134
- //# sourceMappingURL=router.d.ts.map
306
+ export {};