@crawlee/core 4.0.0-beta.70 → 4.0.0-beta.72

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 (249) hide show
  1. package/autoscaling/autoscaled_pool.d.ts +2 -1
  2. package/autoscaling/autoscaled_pool.js +9 -2
  3. package/autoscaling/client_load_signal.d.ts +25 -0
  4. package/autoscaling/client_load_signal.js +36 -0
  5. package/autoscaling/cpu_load_signal.d.ts +28 -0
  6. package/autoscaling/cpu_load_signal.js +24 -0
  7. package/autoscaling/event_loop_load_signal.d.ts +23 -0
  8. package/autoscaling/event_loop_load_signal.js +35 -0
  9. package/autoscaling/index.d.ts +5 -1
  10. package/autoscaling/index.js +5 -1
  11. package/autoscaling/load_signal.d.ts +99 -0
  12. package/autoscaling/load_signal.js +135 -0
  13. package/autoscaling/memory_load_signal.d.ts +43 -0
  14. package/autoscaling/memory_load_signal.js +101 -0
  15. package/autoscaling/snapshotter.d.ts +36 -63
  16. package/autoscaling/snapshotter.js +83 -175
  17. package/autoscaling/system_status.d.ts +21 -33
  18. package/autoscaling/system_status.js +45 -82
  19. package/configuration.d.ts +0 -1
  20. package/configuration.js +0 -1
  21. package/cookie_utils.d.ts +0 -1
  22. package/cookie_utils.js +2 -1
  23. package/crawlers/context_pipeline.d.ts +0 -1
  24. package/crawlers/context_pipeline.js +0 -1
  25. package/crawlers/crawler_commons.d.ts +0 -1
  26. package/crawlers/crawler_commons.js +0 -1
  27. package/crawlers/crawler_utils.d.ts +0 -1
  28. package/crawlers/crawler_utils.js +0 -1
  29. package/crawlers/error_snapshotter.d.ts +0 -1
  30. package/crawlers/error_snapshotter.js +0 -1
  31. package/crawlers/error_tracker.d.ts +0 -1
  32. package/crawlers/error_tracker.js +0 -1
  33. package/crawlers/index.d.ts +0 -1
  34. package/crawlers/index.js +0 -1
  35. package/crawlers/internals/types.d.ts +0 -1
  36. package/crawlers/internals/types.js +0 -1
  37. package/crawlers/statistics.d.ts +6 -1
  38. package/crawlers/statistics.js +8 -1
  39. package/enqueue_links/enqueue_links.d.ts +8 -1
  40. package/enqueue_links/enqueue_links.js +9 -9
  41. package/enqueue_links/index.d.ts +0 -1
  42. package/enqueue_links/index.js +0 -1
  43. package/enqueue_links/shared.d.ts +0 -1
  44. package/enqueue_links/shared.js +0 -1
  45. package/errors.d.ts +20 -1
  46. package/errors.js +22 -1
  47. package/events/event_manager.d.ts +0 -1
  48. package/events/event_manager.js +0 -1
  49. package/events/index.d.ts +0 -1
  50. package/events/index.js +0 -1
  51. package/events/local_event_manager.d.ts +1 -1
  52. package/events/local_event_manager.js +9 -6
  53. package/index.d.ts +0 -1
  54. package/index.js +0 -1
  55. package/log.d.ts +0 -1
  56. package/log.js +0 -1
  57. package/memory-storage/consts.d.ts +0 -1
  58. package/memory-storage/consts.js +0 -1
  59. package/memory-storage/index.d.ts +0 -1
  60. package/memory-storage/index.js +0 -1
  61. package/memory-storage/memory-storage.d.ts +0 -1
  62. package/memory-storage/memory-storage.js +0 -1
  63. package/memory-storage/resource-clients/common/base-client.d.ts +0 -1
  64. package/memory-storage/resource-clients/common/base-client.js +0 -1
  65. package/memory-storage/resource-clients/dataset.d.ts +0 -1
  66. package/memory-storage/resource-clients/dataset.js +0 -1
  67. package/memory-storage/resource-clients/key-value-store.d.ts +0 -1
  68. package/memory-storage/resource-clients/key-value-store.js +0 -1
  69. package/memory-storage/resource-clients/request-queue.d.ts +0 -1
  70. package/memory-storage/resource-clients/request-queue.js +0 -1
  71. package/memory-storage/utils.d.ts +6 -1
  72. package/memory-storage/utils.js +15 -1
  73. package/package.json +5 -5
  74. package/proxy_configuration.d.ts +0 -1
  75. package/proxy_configuration.js +0 -1
  76. package/recoverable_state.d.ts +0 -1
  77. package/recoverable_state.js +0 -1
  78. package/request.d.ts +0 -1
  79. package/request.js +0 -1
  80. package/router.d.ts +117 -15
  81. package/router.js +124 -30
  82. package/serialization.d.ts +0 -1
  83. package/serialization.js +0 -1
  84. package/service_locator.d.ts +0 -1
  85. package/service_locator.js +0 -1
  86. package/session_pool/consts.d.ts +0 -1
  87. package/session_pool/consts.js +0 -1
  88. package/session_pool/errors.d.ts +0 -1
  89. package/session_pool/errors.js +0 -1
  90. package/session_pool/fingerprint.d.ts +0 -1
  91. package/session_pool/fingerprint.js +0 -1
  92. package/session_pool/index.d.ts +0 -1
  93. package/session_pool/index.js +0 -1
  94. package/session_pool/session.d.ts +0 -1
  95. package/session_pool/session.js +0 -1
  96. package/session_pool/session_pool.d.ts +0 -1
  97. package/session_pool/session_pool.js +0 -1
  98. package/storages/access_checking.d.ts +0 -1
  99. package/storages/access_checking.js +0 -1
  100. package/storages/dataset.d.ts +1 -2
  101. package/storages/dataset.js +0 -1
  102. package/storages/index.d.ts +0 -1
  103. package/storages/index.js +0 -1
  104. package/storages/key_value_store.d.ts +0 -1
  105. package/storages/key_value_store.js +0 -1
  106. package/storages/key_value_store_codec.d.ts +0 -1
  107. package/storages/key_value_store_codec.js +0 -1
  108. package/storages/request_dedup_cache.d.ts +23 -0
  109. package/storages/request_dedup_cache.js +48 -0
  110. package/storages/request_list.d.ts +6 -1
  111. package/storages/request_list.js +16 -3
  112. package/storages/request_loader.d.ts +0 -1
  113. package/storages/request_loader.js +0 -1
  114. package/storages/request_manager.d.ts +0 -1
  115. package/storages/request_manager.js +0 -1
  116. package/storages/request_manager_tandem.d.ts +0 -1
  117. package/storages/request_manager_tandem.js +0 -1
  118. package/storages/request_queue.d.ts +24 -1
  119. package/storages/request_queue.js +80 -27
  120. package/storages/sitemap_request_loader.d.ts +0 -1
  121. package/storages/sitemap_request_loader.js +0 -1
  122. package/storages/storage_instance_manager.d.ts +0 -1
  123. package/storages/storage_instance_manager.js +0 -1
  124. package/storages/storage_stats.d.ts +0 -1
  125. package/storages/storage_stats.js +0 -1
  126. package/storages/utils.d.ts +0 -1
  127. package/storages/utils.js +0 -1
  128. package/typedefs.d.ts +0 -1
  129. package/typedefs.js +0 -1
  130. package/validators.d.ts +0 -1
  131. package/validators.js +0 -1
  132. package/autoscaling/autoscaled_pool.d.ts.map +0 -1
  133. package/autoscaling/autoscaled_pool.js.map +0 -1
  134. package/autoscaling/index.d.ts.map +0 -1
  135. package/autoscaling/index.js.map +0 -1
  136. package/autoscaling/snapshotter.d.ts.map +0 -1
  137. package/autoscaling/snapshotter.js.map +0 -1
  138. package/autoscaling/system_status.d.ts.map +0 -1
  139. package/autoscaling/system_status.js.map +0 -1
  140. package/configuration.d.ts.map +0 -1
  141. package/configuration.js.map +0 -1
  142. package/cookie_utils.d.ts.map +0 -1
  143. package/cookie_utils.js.map +0 -1
  144. package/crawlers/context_pipeline.d.ts.map +0 -1
  145. package/crawlers/context_pipeline.js.map +0 -1
  146. package/crawlers/crawler_commons.d.ts.map +0 -1
  147. package/crawlers/crawler_commons.js.map +0 -1
  148. package/crawlers/crawler_utils.d.ts.map +0 -1
  149. package/crawlers/crawler_utils.js.map +0 -1
  150. package/crawlers/error_snapshotter.d.ts.map +0 -1
  151. package/crawlers/error_snapshotter.js.map +0 -1
  152. package/crawlers/error_tracker.d.ts.map +0 -1
  153. package/crawlers/error_tracker.js.map +0 -1
  154. package/crawlers/index.d.ts.map +0 -1
  155. package/crawlers/index.js.map +0 -1
  156. package/crawlers/internals/types.d.ts.map +0 -1
  157. package/crawlers/internals/types.js.map +0 -1
  158. package/crawlers/statistics.d.ts.map +0 -1
  159. package/crawlers/statistics.js.map +0 -1
  160. package/enqueue_links/enqueue_links.d.ts.map +0 -1
  161. package/enqueue_links/enqueue_links.js.map +0 -1
  162. package/enqueue_links/index.d.ts.map +0 -1
  163. package/enqueue_links/index.js.map +0 -1
  164. package/enqueue_links/shared.d.ts.map +0 -1
  165. package/enqueue_links/shared.js.map +0 -1
  166. package/errors.d.ts.map +0 -1
  167. package/errors.js.map +0 -1
  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.map +0 -1
  173. package/events/local_event_manager.js.map +0 -1
  174. package/index.d.ts.map +0 -1
  175. package/index.js.map +0 -1
  176. package/log.d.ts.map +0 -1
  177. package/log.js.map +0 -1
  178. package/memory-storage/consts.d.ts.map +0 -1
  179. package/memory-storage/consts.js.map +0 -1
  180. package/memory-storage/index.d.ts.map +0 -1
  181. package/memory-storage/index.js.map +0 -1
  182. package/memory-storage/memory-storage.d.ts.map +0 -1
  183. package/memory-storage/memory-storage.js.map +0 -1
  184. package/memory-storage/resource-clients/common/base-client.d.ts.map +0 -1
  185. package/memory-storage/resource-clients/common/base-client.js.map +0 -1
  186. package/memory-storage/resource-clients/dataset.d.ts.map +0 -1
  187. package/memory-storage/resource-clients/dataset.js.map +0 -1
  188. package/memory-storage/resource-clients/key-value-store.d.ts.map +0 -1
  189. package/memory-storage/resource-clients/key-value-store.js.map +0 -1
  190. package/memory-storage/resource-clients/request-queue.d.ts.map +0 -1
  191. package/memory-storage/resource-clients/request-queue.js.map +0 -1
  192. package/memory-storage/utils.d.ts.map +0 -1
  193. package/memory-storage/utils.js.map +0 -1
  194. package/proxy_configuration.d.ts.map +0 -1
  195. package/proxy_configuration.js.map +0 -1
  196. package/recoverable_state.d.ts.map +0 -1
  197. package/recoverable_state.js.map +0 -1
  198. package/request.d.ts.map +0 -1
  199. package/request.js.map +0 -1
  200. package/router.d.ts.map +0 -1
  201. package/router.js.map +0 -1
  202. package/serialization.d.ts.map +0 -1
  203. package/serialization.js.map +0 -1
  204. package/service_locator.d.ts.map +0 -1
  205. package/service_locator.js.map +0 -1
  206. package/session_pool/consts.d.ts.map +0 -1
  207. package/session_pool/consts.js.map +0 -1
  208. package/session_pool/errors.d.ts.map +0 -1
  209. package/session_pool/errors.js.map +0 -1
  210. package/session_pool/fingerprint.d.ts.map +0 -1
  211. package/session_pool/fingerprint.js.map +0 -1
  212. package/session_pool/index.d.ts.map +0 -1
  213. package/session_pool/index.js.map +0 -1
  214. package/session_pool/session.d.ts.map +0 -1
  215. package/session_pool/session.js.map +0 -1
  216. package/session_pool/session_pool.d.ts.map +0 -1
  217. package/session_pool/session_pool.js.map +0 -1
  218. package/storages/access_checking.d.ts.map +0 -1
  219. package/storages/access_checking.js.map +0 -1
  220. package/storages/dataset.d.ts.map +0 -1
  221. package/storages/dataset.js.map +0 -1
  222. package/storages/index.d.ts.map +0 -1
  223. package/storages/index.js.map +0 -1
  224. package/storages/key_value_store.d.ts.map +0 -1
  225. package/storages/key_value_store.js.map +0 -1
  226. package/storages/key_value_store_codec.d.ts.map +0 -1
  227. package/storages/key_value_store_codec.js.map +0 -1
  228. package/storages/request_list.d.ts.map +0 -1
  229. package/storages/request_list.js.map +0 -1
  230. package/storages/request_loader.d.ts.map +0 -1
  231. package/storages/request_loader.js.map +0 -1
  232. package/storages/request_manager.d.ts.map +0 -1
  233. package/storages/request_manager.js.map +0 -1
  234. package/storages/request_manager_tandem.d.ts.map +0 -1
  235. package/storages/request_manager_tandem.js.map +0 -1
  236. package/storages/request_queue.d.ts.map +0 -1
  237. package/storages/request_queue.js.map +0 -1
  238. package/storages/sitemap_request_loader.d.ts.map +0 -1
  239. package/storages/sitemap_request_loader.js.map +0 -1
  240. package/storages/storage_instance_manager.d.ts.map +0 -1
  241. package/storages/storage_instance_manager.js.map +0 -1
  242. package/storages/storage_stats.d.ts.map +0 -1
  243. package/storages/storage_stats.js.map +0 -1
  244. package/storages/utils.d.ts.map +0 -1
  245. package/storages/utils.js.map +0 -1
  246. package/typedefs.d.ts.map +0 -1
  247. package/typedefs.js.map +0 -1
  248. package/validators.d.ts.map +0 -1
  249. package/validators.js.map +0 -1
@@ -1,4 +1,10 @@
1
1
  import { isBuffer, isStream, toBuffer } from '@crawlee/utils';
2
+ /**
3
+ * Resolves `segment` against `baseDirectory` and ensures the result stays within `baseDirectory`.
4
+ * Storage names and record keys are used as filesystem path components, so a value containing `..`
5
+ * or an absolute path could otherwise escape the intended directory.
6
+ */
7
+ export declare function resolveWithinDirectory(baseDirectory: string, segment: string): string;
2
8
  /**
3
9
  * Removes all properties with a null value
4
10
  * from the provided object.
@@ -9,4 +15,3 @@ export declare function purgeNullsFromObject<T>(object: T): T;
9
15
  */
10
16
  export declare function uniqueKeyToRequestId(uniqueKey: string): string;
11
17
  export { isBuffer, isStream, toBuffer };
12
- //# sourceMappingURL=utils.d.ts.map
@@ -1,6 +1,21 @@
1
1
  import { createHash } from 'node:crypto';
2
+ import { resolve, sep } from 'node:path';
2
3
  import { isBuffer, isStream, toBuffer } from '@crawlee/utils';
3
4
  import { REQUEST_ID_LENGTH } from './consts.js';
5
+ /**
6
+ * Resolves `segment` against `baseDirectory` and ensures the result stays within `baseDirectory`.
7
+ * Storage names and record keys are used as filesystem path components, so a value containing `..`
8
+ * or an absolute path could otherwise escape the intended directory.
9
+ */
10
+ export function resolveWithinDirectory(baseDirectory, segment) {
11
+ const base = resolve(baseDirectory);
12
+ const resolved = resolve(base, segment);
13
+ if (resolved !== base && !resolved.startsWith(`${base}${sep}`)) {
14
+ throw new Error(`"${segment}" is not allowed because it would resolve outside of the storage directory. ` +
15
+ `Storage names and record keys must not contain path traversal segments ("..") or absolute paths.`);
16
+ }
17
+ return resolved;
18
+ }
4
19
  /**
5
20
  * Removes all properties with a null value
6
21
  * from the provided object.
@@ -25,4 +40,3 @@ export function uniqueKeyToRequestId(uniqueKey) {
25
40
  return str.length > REQUEST_ID_LENGTH ? str.slice(0, REQUEST_ID_LENGTH) : str;
26
41
  }
27
42
  export { isBuffer, isStream, toBuffer };
28
- //# sourceMappingURL=utils.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/core",
3
- "version": "4.0.0-beta.70",
3
+ "version": "4.0.0-beta.72",
4
4
  "description": "The scalable web crawling and scraping library for JavaScript/Node.js. Enables development of data extraction and web automation jobs (not only) with headless Chrome and Puppeteer.",
5
5
  "engines": {
6
6
  "node": ">=22.0.0"
@@ -53,9 +53,9 @@
53
53
  "@apify/pseudo_url": "^2.0.59",
54
54
  "@apify/timeout": "^0.3.2",
55
55
  "@apify/utilities": "^2.15.5",
56
- "@crawlee/fs-storage": "4.0.0-beta.70",
57
- "@crawlee/types": "4.0.0-beta.70",
58
- "@crawlee/utils": "4.0.0-beta.70",
56
+ "@crawlee/fs-storage": "4.0.0-beta.72",
57
+ "@crawlee/types": "4.0.0-beta.72",
58
+ "@crawlee/utils": "4.0.0-beta.72",
59
59
  "@sapphire/async-queue": "^1.5.5",
60
60
  "@sapphire/shapeshift": "^4.0.0",
61
61
  "@vladfrangu/async_event_emitter": "^2.4.6",
@@ -79,5 +79,5 @@
79
79
  }
80
80
  }
81
81
  },
82
- "gitHead": "66a90e59b656456fad4c448b98698a009aa0ecfb"
82
+ "gitHead": "3f698f0120bc3e6cc87b66d0127b5f36c125c255"
83
83
  }
@@ -109,4 +109,3 @@ export declare class ProxyConfiguration {
109
109
  protected _throwNoOptionsProvided(): never;
110
110
  }
111
111
  export {};
112
- //# sourceMappingURL=proxy_configuration.d.ts.map
@@ -132,4 +132,3 @@ export class ProxyConfiguration {
132
132
  throw new Error('One of "options.proxyUrls" or "options.newUrlFunction" needs to be provided.');
133
133
  }
134
134
  }
135
- //# sourceMappingURL=proxy_configuration.js.map
@@ -118,4 +118,3 @@ export declare class RecoverableState<TStateModel = Record<string, unknown>> {
118
118
  */
119
119
  private loadSavedState;
120
120
  }
121
- //# sourceMappingURL=recoverable_state.d.ts.map
@@ -139,4 +139,3 @@ export class RecoverableState {
139
139
  }
140
140
  }
141
141
  }
142
- //# sourceMappingURL=recoverable_state.js.map
package/request.d.ts CHANGED
@@ -310,4 +310,3 @@ export interface InternalSource {
310
310
  regex?: RegExp;
311
311
  }
312
312
  export { CrawleeRequest as Request };
313
- //# sourceMappingURL=request.d.ts.map
package/request.js CHANGED
@@ -417,4 +417,3 @@ class CrawleeRequest {
417
417
  }
418
418
  }
419
419
  export { CrawleeRequest as Request };
420
- //# sourceMappingURL=request.js.map
package/router.d.ts CHANGED
@@ -1,14 +1,57 @@
1
1
  import type { Dictionary } from '@crawlee/types';
2
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
2
3
  import type { CrawlingContext, LoadedRequest, RestrictedCrawlingContext } from './crawlers/crawler_commons.js';
3
4
  import type { Request } from './request.js';
4
5
  import type { Awaitable } from './typedefs.js';
5
- export interface RouterHandler<Context extends Omit<RestrictedCrawlingContext, 'enqueueLinks'> = CrawlingContext> extends Router<Context> {
6
+ /**
7
+ * The key of the default route — the fallback handler registered via {@link Router.addDefaultHandler}.
8
+ * Use it in a {@link RouteSchemas} map to register a schema that validates the `userData` of every request
9
+ * that falls through to the default handler (i.e. whose label has no route of its own).
10
+ */
11
+ export declare const defaultRoute: unique symbol;
12
+ /**
13
+ * The crawling context received by a route handler, with `request.userData` narrowed to `UserData`.
14
+ */
15
+ export type RouterHandlerContext<Context, UserData extends Dictionary> = Omit<Context, 'request'> & {
16
+ request: LoadedRequest<Request<UserData>>;
17
+ };
18
+ /**
19
+ * A map of request labels to a [Standard Schema](https://standardschema.dev) (Zod, Valibot, ArkType, …)
20
+ * validating that label's `request.userData`. Pass it to {@link Router.create} or a `createXRouter`
21
+ * factory to derive the per-label `request.userData` types *and* validate them at runtime. The optional
22
+ * {@link defaultRoute} key registers a schema for requests handled by the default route.
23
+ */
24
+ export type RouteSchemas = Record<string, StandardSchemaV1> & {
25
+ [defaultRoute]?: StandardSchemaV1;
26
+ };
27
+ /**
28
+ * Derives a route map (label → `userData` type) from a {@link RouteSchemas} map by inferring each
29
+ * schema's output type. Outputs that are not object-shaped fall back to a plain {@link Dictionary}. The
30
+ * {@link defaultRoute} schema drives runtime validation only, so it is excluded from the typed route map.
31
+ */
32
+ export type RoutesFromSchemas<Schemas extends RouteSchemas> = {
33
+ [Label in Extract<keyof Schemas, string>]: StandardSchemaV1.InferOutput<Schemas[Label]> extends Dictionary ? StandardSchemaV1.InferOutput<Schemas[Label]> : Dictionary;
34
+ };
35
+ /**
36
+ * Validates `userData` against a {@link RouteSchemas|Standard Schema}, returning the parsed (and coerced)
37
+ * value. Throws a {@link RequestValidationError} when validation fails.
38
+ * @internal
39
+ */
40
+ export declare function validateUserData(label: string | symbol, schema: StandardSchemaV1, userData: unknown): Promise<Dictionary>;
41
+ /**
42
+ * The set of labels accepted by {@link Router.addHandler}. When the router declares a concrete
43
+ * route map (e.g. `{ PRODUCT: ...; CATEGORY: ... }`), only those labels (plus symbols) are
44
+ * allowed — unknown labels become a compile-time error. When the map is left open (the default
45
+ * `Record<string, ...>`), any string or symbol label is accepted, preserving the original behaviour.
46
+ */
47
+ export type RouterLabel<Routes extends Record<keyof Routes, Dictionary>> = string extends keyof Routes ? string | symbol : (keyof Routes & string) | symbol;
48
+ 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
49
  (ctx: Context): Awaitable<void>;
7
50
  }
8
51
  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>;
52
+ export type RouterRoutes<Context, Routes extends Record<keyof Routes, Dictionary>> = {
53
+ [Label in keyof Routes]: (ctx: Omit<Context, 'request'> & {
54
+ request: Request<Routes[Label]>;
12
55
  }) => Awaitable<void>;
13
56
  };
14
57
  /**
@@ -75,9 +118,51 @@ export type RouterRoutes<Context, UserData extends Dictionary> = {
75
118
  * ctx.log.info('...');
76
119
  * });
77
120
  * ```
121
+ *
122
+ * To get `request.userData` typed per label, declare a route map and pass it as the second
123
+ * type argument. The label passed to {@link Router.addHandler} then drives the type of
124
+ * `request.userData`, and unknown labels are rejected at compile time:
125
+ *
126
+ * ```ts
127
+ * import { createCheerioRouter, CheerioCrawlingContext } from 'crawlee';
128
+ *
129
+ * interface Routes {
130
+ * PRODUCT: { sku: string; price: number };
131
+ * CATEGORY: { categoryId: string };
132
+ * }
133
+ *
134
+ * const router = createCheerioRouter<CheerioCrawlingContext, Routes>();
135
+ *
136
+ * router.addHandler('PRODUCT', async ({ request }) => {
137
+ * request.userData.sku; // string
138
+ * request.userData.price; // number
139
+ * });
140
+ *
141
+ * router.addHandler('TYPO', async () => {}); // compile error: not a known label
142
+ * ```
143
+ *
144
+ * Passing a [Standard Schema](https://standardschema.dev) per label instead of a plain type both infers the
145
+ * `request.userData` types *and* validates them at runtime — when the request is handled, and when it is
146
+ * added to the crawler (`crawler.addRequests`, `context.addRequests`, `enqueueLinks`). A failing request
147
+ * throws a {@link RequestValidationError}.
148
+ *
149
+ * ```ts
150
+ * import { z } from 'zod';
151
+ * import { createCheerioRouter } from 'crawlee';
152
+ *
153
+ * const router = createCheerioRouter({
154
+ * PRODUCT: z.object({ sku: z.string(), price: z.number() }),
155
+ * CATEGORY: z.object({ categoryId: z.string() }),
156
+ * });
157
+ *
158
+ * router.addHandler('PRODUCT', async ({ request }) => {
159
+ * request.userData.price; // number, inferred from the schema and validated at runtime
160
+ * });
161
+ * ```
78
162
  */
79
- export declare class Router<Context extends Omit<RestrictedCrawlingContext, 'enqueueLinks'>> {
163
+ export declare class Router<Context extends Omit<RestrictedCrawlingContext, 'enqueueLinks'>, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>> {
80
164
  private readonly routes;
165
+ private readonly schemas;
81
166
  private readonly middlewares;
82
167
  /**
83
168
  * use Router.create() instead!
@@ -85,17 +170,28 @@ export declare class Router<Context extends Omit<RestrictedCrawlingContext, 'enq
85
170
  */
86
171
  protected constructor();
87
172
  /**
88
- * Registers new route handler for given label.
173
+ * Registers new route handler for given label. When the router declares a route map, the
174
+ * `label` is restricted to the declared labels and `request.userData` is typed accordingly.
175
+ */
176
+ addHandler<Label extends keyof Routes & string>(label: Label, handler: (ctx: RouterHandlerContext<Context, Routes[Label]>) => Awaitable<void>): void;
177
+ /**
178
+ * Registers new route handler for given label, explicitly typing `request.userData` via the
179
+ * `UserData` type argument. Useful when the router has no declared route map (the open default)
180
+ * and you want to type a single handler, or to register a handler under a `symbol` label.
89
181
  */
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;
182
+ addHandler<UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(label: RouterLabel<Routes>, handler: (ctx: RouterHandlerContext<Context, UserData>) => Awaitable<void>): void;
93
183
  /**
94
- * Registers default route handler.
184
+ * Registers default route handler. As a fallback it can receive any request (including labels not
185
+ * declared in the route map), so `request.userData` defaults to the context's `userData` type
186
+ * (loosely typed by default). Pass an explicit `UserData` type argument to narrow it.
95
187
  */
96
- addDefaultHandler<UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(handler: (ctx: Omit<Context, 'request'> & {
97
- request: LoadedRequest<Request<UserData>>;
98
- }) => Awaitable<void>): void;
188
+ addDefaultHandler<UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(handler: (ctx: RouterHandlerContext<Context, UserData>) => Awaitable<void>): void;
189
+ /**
190
+ * Returns the {@link RouteSchemas|Standard Schema} registered for a label, if any. Used by the crawler
191
+ * to validate `request.userData` when requests are added.
192
+ * @internal
193
+ */
194
+ getSchema(label?: string | symbol): StandardSchemaV1 | undefined;
99
195
  /**
100
196
  * Registers a middleware that will be fired before the matching route handler.
101
197
  * Multiple middlewares can be registered, they will be fired in the same order.
@@ -105,6 +201,11 @@ export declare class Router<Context extends Omit<RestrictedCrawlingContext, 'enq
105
201
  * Returns route handler for given label. If no label is provided, the default request handler will be returned.
106
202
  */
107
203
  getHandler(label?: string | symbol): (ctx: Context) => Awaitable<void>;
204
+ /**
205
+ * Validates `request.userData` against the schema registered for its label (if any), replacing it with
206
+ * the parsed value. Throws a {@link RequestValidationError} when validation fails.
207
+ */
208
+ private validateRequest;
108
209
  /**
109
210
  * Throws when the label already exists in our registry.
110
211
  */
@@ -129,6 +230,7 @@ export declare class Router<Context extends Omit<RestrictedCrawlingContext, 'enq
129
230
  * await crawler.run();
130
231
  * ```
131
232
  */
132
- static create<Context extends Omit<RestrictedCrawlingContext, 'enqueueLinks'> = CrawlingContext, UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(routes?: RouterRoutes<Context, UserData>): RouterHandler<Context>;
233
+ 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>;
234
+ 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>>;
235
+ static create<Context extends Omit<RestrictedCrawlingContext, 'enqueueLinks'> = CrawlingContext, const Schemas extends RouteSchemas = RouteSchemas>(schemas: Schemas): RouterHandler<Context, RoutesFromSchemas<Schemas>>;
133
236
  }
134
- //# sourceMappingURL=router.d.ts.map
package/router.js CHANGED
@@ -1,5 +1,39 @@
1
- import { MissingRouteError } from './errors.js';
2
- const defaultRoute = Symbol('default-route');
1
+ import { MissingRouteError, RequestValidationError } from './errors.js';
2
+ /**
3
+ * The key of the default route — the fallback handler registered via {@link Router.addDefaultHandler}.
4
+ * Use it in a {@link RouteSchemas} map to register a schema that validates the `userData` of every request
5
+ * that falls through to the default handler (i.e. whose label has no route of its own).
6
+ */
7
+ export const defaultRoute = Symbol('default-route');
8
+ /** Whether a validation issue points at the top-level `label` key. */
9
+ function isLabelIssue(issue) {
10
+ if (issue.path?.length !== 1) {
11
+ return false;
12
+ }
13
+ const [segment] = issue.path;
14
+ return (typeof segment === 'object' ? segment.key : segment) === 'label';
15
+ }
16
+ /**
17
+ * Validates `userData` against a {@link RouteSchemas|Standard Schema}, returning the parsed (and coerced)
18
+ * value. Throws a {@link RequestValidationError} when validation fails.
19
+ * @internal
20
+ */
21
+ export async function validateUserData(label, schema, userData) {
22
+ const { label: _label, ...rest } = (userData ?? {});
23
+ // `label` is a Crawlee-managed key that lives inside `userData`, so validating it is opt-in: we validate
24
+ // without it first, letting schemas that don't describe it pass (including `.strict()` ones). A schema that
25
+ // *does* declare `label` reports an issue for the now-missing key — so we re-validate with it included,
26
+ // honouring the declaration. Unlike `userData.__crawlee`, `label` is enumerable, so schemas do see it.
27
+ let result = await schema['~standard'].validate(rest);
28
+ if (result.issues?.some(isLabelIssue)) {
29
+ result = await schema['~standard'].validate({ ...rest, label });
30
+ }
31
+ if (result.issues) {
32
+ throw new RequestValidationError(label, result.issues);
33
+ }
34
+ // Restore the label so it survives schemas that strip undeclared keys.
35
+ return { ...result.value, label };
36
+ }
3
37
  /**
4
38
  * Simple router that works based on request labels. This instance can then serve as a `requestHandler` of your crawler.
5
39
  *
@@ -64,29 +98,90 @@ const defaultRoute = Symbol('default-route');
64
98
  * ctx.log.info('...');
65
99
  * });
66
100
  * ```
101
+ *
102
+ * To get `request.userData` typed per label, declare a route map and pass it as the second
103
+ * type argument. The label passed to {@link Router.addHandler} then drives the type of
104
+ * `request.userData`, and unknown labels are rejected at compile time:
105
+ *
106
+ * ```ts
107
+ * import { createCheerioRouter, CheerioCrawlingContext } from 'crawlee';
108
+ *
109
+ * interface Routes {
110
+ * PRODUCT: { sku: string; price: number };
111
+ * CATEGORY: { categoryId: string };
112
+ * }
113
+ *
114
+ * const router = createCheerioRouter<CheerioCrawlingContext, Routes>();
115
+ *
116
+ * router.addHandler('PRODUCT', async ({ request }) => {
117
+ * request.userData.sku; // string
118
+ * request.userData.price; // number
119
+ * });
120
+ *
121
+ * router.addHandler('TYPO', async () => {}); // compile error: not a known label
122
+ * ```
123
+ *
124
+ * Passing a [Standard Schema](https://standardschema.dev) per label instead of a plain type both infers the
125
+ * `request.userData` types *and* validates them at runtime — when the request is handled, and when it is
126
+ * added to the crawler (`crawler.addRequests`, `context.addRequests`, `enqueueLinks`). A failing request
127
+ * throws a {@link RequestValidationError}.
128
+ *
129
+ * ```ts
130
+ * import { z } from 'zod';
131
+ * import { createCheerioRouter } from 'crawlee';
132
+ *
133
+ * const router = createCheerioRouter({
134
+ * PRODUCT: z.object({ sku: z.string(), price: z.number() }),
135
+ * CATEGORY: z.object({ categoryId: z.string() }),
136
+ * });
137
+ *
138
+ * router.addHandler('PRODUCT', async ({ request }) => {
139
+ * request.userData.price; // number, inferred from the schema and validated at runtime
140
+ * });
141
+ * ```
67
142
  */
68
143
  export class Router {
69
144
  routes = new Map();
145
+ schemas = new Map();
70
146
  middlewares = [];
71
147
  /**
72
148
  * use Router.create() instead!
73
149
  * @ignore
74
150
  */
75
151
  constructor() { }
76
- /**
77
- * Registers new route handler for given label.
78
- */
79
152
  addHandler(label, handler) {
80
153
  this.validate(label);
81
154
  this.routes.set(label, handler);
82
155
  }
83
156
  /**
84
- * Registers default route handler.
157
+ * Registers default route handler. As a fallback it can receive any request (including labels not
158
+ * declared in the route map), so `request.userData` defaults to the context's `userData` type
159
+ * (loosely typed by default). Pass an explicit `UserData` type argument to narrow it.
85
160
  */
86
161
  addDefaultHandler(handler) {
87
162
  this.validate(defaultRoute);
88
163
  this.routes.set(defaultRoute, handler);
89
164
  }
165
+ /**
166
+ * Returns the {@link RouteSchemas|Standard Schema} registered for a label, if any. Used by the crawler
167
+ * to validate `request.userData` when requests are added.
168
+ * @internal
169
+ */
170
+ getSchema(label) {
171
+ if (label != null) {
172
+ const schema = this.schemas.get(label);
173
+ if (schema) {
174
+ return schema;
175
+ }
176
+ // A label with its own route is fully specified; don't fall back to the default-route schema.
177
+ if (this.routes.has(label)) {
178
+ return undefined;
179
+ }
180
+ }
181
+ // Requests with no route of their own fall through to the default handler, so validate their
182
+ // `userData` against the default-route schema, if one was registered.
183
+ return this.schemas.get(defaultRoute);
184
+ }
90
185
  /**
91
186
  * Registers a middleware that will be fired before the matching route handler.
92
187
  * Multiple middlewares can be registered, they will be fired in the same order.
@@ -108,6 +203,17 @@ export class Router {
108
203
  ' You must set up a route for this label or a default route.' +
109
204
  ' Use `requestHandler`, `router.addHandler` or `router.addDefaultHandler`.');
110
205
  }
206
+ /**
207
+ * Validates `request.userData` against the schema registered for its label (if any), replacing it with
208
+ * the parsed value. Throws a {@link RequestValidationError} when validation fails.
209
+ */
210
+ async validateRequest(context) {
211
+ const label = context.request.label;
212
+ const schema = this.getSchema(label);
213
+ if (schema) {
214
+ context.request.userData = (await validateUserData(label, schema, context.request.userData));
215
+ }
216
+ }
111
217
  /**
112
218
  * Throws when the label already exists in our registry.
113
219
  */
@@ -119,39 +225,28 @@ export class Router {
119
225
  throw new Error(message);
120
226
  }
121
227
  }
122
- /**
123
- * Creates new router instance. This instance can then serve as a `requestHandler` of your crawler.
124
- *
125
- * ```ts
126
- * import { Router, CheerioCrawler, CheerioCrawlingContext } from 'crawlee';
127
- *
128
- * const router = Router.create<CheerioCrawlingContext>();
129
- * router.addHandler('label-a', async (ctx) => {
130
- * ctx.log.info('...');
131
- * });
132
- * router.addDefaultHandler(async (ctx) => {
133
- * ctx.log.info('...');
134
- * });
135
- *
136
- * const crawler = new CheerioCrawler({
137
- * requestHandler: router,
138
- * });
139
- * await crawler.run();
140
- * ```
141
- */
142
- static create(routes) {
228
+ static create(routesOrSchemas) {
143
229
  const router = new Router();
144
230
  const obj = Object.create(Function.prototype);
145
231
  obj.addHandler = router.addHandler.bind(router);
146
232
  obj.addDefaultHandler = router.addDefaultHandler.bind(router);
233
+ obj.getSchema = router.getSchema.bind(router);
147
234
  obj.getHandler = router.getHandler.bind(router);
148
235
  obj.use = router.use.bind(router);
149
- for (const [label, handler] of Object.entries(routes ?? {})) {
150
- router.addHandler(label, handler);
236
+ // `Reflect.ownKeys` (unlike `Object.entries`) also yields the `defaultRoute` symbol key.
237
+ for (const label of Reflect.ownKeys(routesOrSchemas ?? {})) {
238
+ const value = routesOrSchemas[label];
239
+ if (typeof value === 'function') {
240
+ router.addHandler(label, value);
241
+ }
242
+ else {
243
+ router.schemas.set(label, value);
244
+ }
151
245
  }
152
246
  const func = async function (context) {
153
247
  const { url, loadedUrl, label } = context.request;
154
248
  context.log.debug('Page opened.', { label, url: loadedUrl ?? url });
249
+ await router.validateRequest(context);
155
250
  for (const middleware of router.middlewares) {
156
251
  await middleware(context);
157
252
  }
@@ -161,4 +256,3 @@ export class Router {
161
256
  return func;
162
257
  }
163
258
  }
164
- //# sourceMappingURL=router.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
@@ -119,4 +119,3 @@ function pluckValue(streamArray) {
119
119
  streamArray.push = (obj) => realPush(obj?.value ?? null);
120
120
  return streamArray;
121
121
  }
122
- //# sourceMappingURL=serialization.js.map
@@ -154,4 +154,3 @@ export declare function bindMethodsToServiceLocator(serviceLocator: ServiceLocat
154
154
  };
155
155
  export declare const serviceLocator: ServiceLocatorInterface;
156
156
  export {};
157
- //# sourceMappingURL=service_locator.d.ts.map
@@ -236,4 +236,3 @@ export const serviceLocator = new Proxy({}, {
236
236
  throw new TypeError(`Cannot set property '${String(prop)}' on serviceLocator directly. Use the setter methods (e.g. setConfiguration(), setStorageBackend()) instead.`);
237
237
  },
238
238
  });
239
- //# sourceMappingURL=service_locator.js.map
@@ -1,4 +1,3 @@
1
1
  export declare const BLOCKED_STATUS_CODES: number[];
2
2
  export declare const PERSIST_STATE_KEY = "CRAWLEE_SESSION_POOL_STATE";
3
3
  export declare const MAX_POOL_SIZE = 1000;
4
- //# sourceMappingURL=consts.d.ts.map
@@ -1,4 +1,3 @@
1
1
  export const BLOCKED_STATUS_CODES = [401, 403, 429];
2
2
  export const PERSIST_STATE_KEY = 'CRAWLEE_SESSION_POOL_STATE';
3
3
  export const MAX_POOL_SIZE = 1000;
4
- //# sourceMappingURL=consts.js.map
@@ -5,4 +5,3 @@ export declare class CookieParseError extends Error {
5
5
  readonly cookieHeaderString: unknown;
6
6
  constructor(cookieHeaderString: unknown);
7
7
  }
8
- //# sourceMappingURL=errors.d.ts.map
@@ -9,4 +9,3 @@ export class CookieParseError extends Error {
9
9
  Error.captureStackTrace(this, CookieParseError);
10
10
  }
11
11
  }
12
- //# sourceMappingURL=errors.js.map
@@ -7,4 +7,3 @@ import type { SessionFingerprint } from '@crawlee/types';
7
7
  * `fingerprint` in `sessionOptions`.
8
8
  */
9
9
  export declare function createDefaultSessionFingerprint(): SessionFingerprint;
10
- //# sourceMappingURL=fingerprint.d.ts.map
@@ -28,4 +28,3 @@ const PROFILES_BY_PLATFORM = [
28
28
  export function createDefaultSessionFingerprint() {
29
29
  return { ...PROFILES_BY_PLATFORM[Math.floor(Math.random() * PROFILES_BY_PLATFORM.length)] };
30
30
  }
31
- //# sourceMappingURL=fingerprint.js.map
@@ -2,4 +2,3 @@ export * from './errors.js';
2
2
  export * from './session.js';
3
3
  export * from './session_pool.js';
4
4
  export * from './consts.js';
5
- //# sourceMappingURL=index.d.ts.map
@@ -2,4 +2,3 @@ export * from './errors.js';
2
2
  export * from './session.js';
3
3
  export * from './session_pool.js';
4
4
  export * from './consts.js';
5
- //# sourceMappingURL=index.js.map
@@ -159,4 +159,3 @@ export declare class Session implements ISession {
159
159
  */
160
160
  protected _maybeSelfRetire(): void;
161
161
  }
162
- //# sourceMappingURL=session.d.ts.map
@@ -216,4 +216,3 @@ export class Session {
216
216
  }
217
217
  }
218
218
  }
219
- //# sourceMappingURL=session.js.map
@@ -248,4 +248,3 @@ export declare class SessionPool implements ISessionPool {
248
248
  protected _maybeLoadSessionPool(): Promise<void>;
249
249
  }
250
250
  export {};
251
- //# sourceMappingURL=session_pool.d.ts.map
@@ -384,4 +384,3 @@ export class SessionPool {
384
384
  this.log.debug(`${this.sessions.length} active sessions loaded from KeyValueStore`);
385
385
  }
386
386
  }
387
- //# sourceMappingURL=session_pool.js.map
@@ -10,4 +10,3 @@ export declare const checkStorageAccess: () => void | undefined;
10
10
  * @param callback The code that should be invoked with the `checkFunction` setting
11
11
  */
12
12
  export declare const withCheckedStorageAccess: <T>(checkFunction: () => void, callback: () => Awaitable<T>) => Promise<T>;
13
- //# sourceMappingURL=access_checking.d.ts.map
@@ -15,4 +15,3 @@ export const checkStorageAccess = () => {
15
15
  * @param callback The code that should be invoked with the `checkFunction` setting
16
16
  */
17
17
  export const withCheckedStorageAccess = async (checkFunction, callback) => storage.run({ checkFunction }, callback);
18
- //# sourceMappingURL=access_checking.js.map
@@ -295,7 +295,7 @@ export declare class Dataset<Data extends Dictionary = Dictionary> {
295
295
  * If the dataset is empty, reduce will return undefined.
296
296
  *
297
297
  * @param iteratee
298
- * @param memo Unset parameter, neccesary to be able to pass options
298
+ * @param memo Unset parameter, necessary to be able to pass options
299
299
  * @param [options] An object containing extra options for `reduce()`
300
300
  */
301
301
  reduce(iteratee: DatasetReducer<Data, Data>, memo: undefined, options: DatasetIteratorOptions): Promise<Data | undefined>;
@@ -478,4 +478,3 @@ export interface DatasetContent<Data> {
478
478
  /** Should the results be in descending order. */
479
479
  desc?: boolean;
480
480
  }
481
- //# sourceMappingURL=dataset.d.ts.map
@@ -504,4 +504,3 @@ export class Dataset {
504
504
  return dataset.getData(options);
505
505
  }
506
506
  }
507
- //# sourceMappingURL=dataset.js.map
@@ -11,4 +11,3 @@ export * from './utils.js';
11
11
  export * from './access_checking.js';
12
12
  export * from './sitemap_request_loader.js';
13
13
  export * from './request_manager_tandem.js';
14
- //# sourceMappingURL=index.d.ts.map
package/storages/index.js CHANGED
@@ -9,4 +9,3 @@ export * from './utils.js';
9
9
  export * from './access_checking.js';
10
10
  export * from './sitemap_request_loader.js';
11
11
  export * from './request_manager_tandem.js';
12
- //# sourceMappingURL=index.js.map
@@ -533,4 +533,3 @@ export interface KeyValueStoreIteratorOptions {
533
533
  */
534
534
  prefix?: string;
535
535
  }
536
- //# sourceMappingURL=key_value_store.d.ts.map