@crawlee/core 4.0.0-beta.13 → 4.0.0-beta.131

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 (280) hide show
  1. package/README.md +17 -13
  2. package/autoscaling/autoscaled_pool.d.ts +67 -172
  3. package/autoscaling/autoscaled_pool.js +182 -329
  4. package/autoscaling/concurrency_system.d.ts +268 -0
  5. package/autoscaling/concurrency_system.js +362 -0
  6. package/autoscaling/cpu_load_signal.d.ts +43 -0
  7. package/autoscaling/cpu_load_signal.js +47 -0
  8. package/autoscaling/event_loop_load_signal.d.ts +51 -0
  9. package/autoscaling/event_loop_load_signal.js +60 -0
  10. package/autoscaling/index.d.ts +6 -1
  11. package/autoscaling/index.js +6 -1
  12. package/autoscaling/load_signal.d.ts +100 -0
  13. package/autoscaling/load_signal.js +105 -0
  14. package/autoscaling/memory_load_signal.d.ts +47 -0
  15. package/autoscaling/memory_load_signal.js +106 -0
  16. package/autoscaling/snapshotter.d.ts +58 -163
  17. package/autoscaling/snapshotter.js +45 -263
  18. package/autoscaling/storage_backend_load_signal.d.ts +56 -0
  19. package/autoscaling/storage_backend_load_signal.js +73 -0
  20. package/autoscaling/system_status.d.ts +67 -89
  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 +3 -2
  29. package/cookie_utils.js +18 -7
  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 -129
  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 +187 -63
  43. package/crawlers/statistics.js +354 -164
  44. package/debug.d.ts +36 -0
  45. package/debug.js +70 -0
  46. package/enqueue_links/enqueue_links.d.ts +68 -154
  47. package/enqueue_links/enqueue_links.js +38 -210
  48. package/enqueue_links/index.d.ts +0 -1
  49. package/enqueue_links/index.js +0 -1
  50. package/enqueue_links/shared.d.ts +52 -26
  51. package/enqueue_links/shared.js +115 -66
  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 +12 -13
  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 +108 -0
  78. package/memory-storage/resource-clients/key-value-store.d.ts +63 -0
  79. package/memory-storage/resource-clients/key-value-store.js +201 -0
  80. package/memory-storage/resource-clients/request-queue.d.ts +77 -0
  81. package/memory-storage/resource-clients/request-queue.js +409 -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 +14 -13
  87. package/proxy_configuration.d.ts +24 -132
  88. package/proxy_configuration.js +32 -147
  89. package/recoverable_state.d.ts +155 -0
  90. package/recoverable_state.js +231 -0
  91. package/request.d.ts +80 -16
  92. package/request.js +147 -68
  93. package/router.d.ts +193 -21
  94. package/router.js +188 -43
  95. package/serialization.d.ts +0 -1
  96. package/serialization.js +15 -15
  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 -88
  108. package/session_pool/session.js +101 -159
  109. package/session_pool/session_pool.d.ts +73 -91
  110. package/session_pool/session_pool.js +172 -165
  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 +291 -148
  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 +445 -169
  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 -115
  124. package/storages/request_list.js +183 -152
  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 +762 -216
  133. package/storages/{sitemap_request_list.d.ts → sitemap_request_loader.d.ts} +45 -89
  134. package/storages/sitemap_request_loader.js +438 -0
  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 +239 -0
  140. package/storages/throttling_request_manager.js +646 -0
  141. package/storages/transaction.d.ts +252 -0
  142. package/storages/transaction.js +251 -0
  143. package/storages/utils.d.ts +59 -11
  144. package/storages/utils.js +75 -15
  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 +22 -18
  158. package/validators.js +13 -18
  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 -140
  204. package/http_clients/base-http-client.d.ts.map +0 -1
  205. package/http_clients/base-http-client.js +0 -40
  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 -20
  212. package/http_clients/got-scraping-http-client.d.ts.map +0 -1
  213. package/http_clients/got-scraping-http-client.js +0 -82
  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 +0 -430
  269. package/storages/sitemap_request_list.js.map +0 -1
  270. package/storages/storage_manager.d.ts +0 -58
  271. package/storages/storage_manager.d.ts.map +0 -1
  272. package/storages/storage_manager.js +0 -105
  273. package/storages/storage_manager.js.map +0 -1
  274. package/storages/utils.d.ts.map +0 -1
  275. package/storages/utils.js.map +0 -1
  276. package/tsconfig.build.tsbuildinfo +0 -1
  277. package/typedefs.d.ts.map +0 -1
  278. package/typedefs.js.map +0 -1
  279. package/validators.d.ts.map +0 -1
  280. package/validators.js.map +0 -1
package/configuration.js CHANGED
@@ -1,15 +1,86 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks';
2
1
  import { EventEmitter } from 'node:events';
3
2
  import { readFileSync } from 'node:fs';
4
3
  import { join } from 'node:path';
5
- import { MemoryStorage } from '@crawlee/memory-storage';
6
- import { pathExistsSync } from 'fs-extra/esm';
7
- import log, { LogLevel } from '@apify/log';
8
- import { LocalEventManager } from './events/local_event_manager.js';
9
- import { entries } from './typedefs.js';
4
+ import { z } from 'zod';
5
+ import { log, LogLevel } from './log.js';
6
+ import { serviceLocator } from './service_locator.js';
7
+ // Crawlee attaches many listeners to shared EventEmitters (one per crawler/session/autoscaled pool),
8
+ // which can exceed Node's default limit of 10 and trigger spurious MaxListenersExceededWarning logs.
9
+ // Raising the global default avoids false positives; real leaks will still manifest as unbounded growth.
10
+ // TODO: tracked in https://github.com/apify/crawlee/issues/3615 — find a less side-effecting place for this.
11
+ EventEmitter.defaultMaxListeners = 50;
12
+ export function field(schema, envVar) {
13
+ return { schema, envVar };
14
+ }
15
+ // --- Zod preprocessors ---
16
+ /** Zod preprocessor treating `'0'` and `'false'` as falsy. */
17
+ export const coerceBoolean = z.preprocess((val) => {
18
+ if (typeof val === 'string') {
19
+ return !['0', 'false'].includes(val.toLowerCase());
20
+ }
21
+ return val;
22
+ }, z.boolean());
23
+ export const coerceNumber = z.preprocess((val) => {
24
+ if (typeof val === 'string')
25
+ return Number(val);
26
+ return val;
27
+ }, z.number());
28
+ /** Zod schema accepting both LogLevel enum values and string names (case-insensitive). */
29
+ const logLevelSchema = z.preprocess((val) => {
30
+ if (val == null)
31
+ return val;
32
+ const s = String(val);
33
+ if (Number.isFinite(+s))
34
+ return +s;
35
+ const key = s.toUpperCase();
36
+ if (key in LogLevel)
37
+ return LogLevel[key];
38
+ return val;
39
+ }, z.enum(LogLevel));
40
+ // --- Crawlee config field definitions ---
41
+ export const crawleeConfigFields = {
42
+ /** @default 'default' */
43
+ defaultDatasetId: field(z.string().default('default'), 'CRAWLEE_DEFAULT_DATASET_ID'),
44
+ /** @default true */
45
+ purgeOnStart: field(coerceBoolean.default(true), 'CRAWLEE_PURGE_ON_START'),
46
+ /** @default 'default' */
47
+ defaultKeyValueStoreId: field(z.string().default('default'), 'CRAWLEE_DEFAULT_KEY_VALUE_STORE_ID'),
48
+ /** @default 'default' */
49
+ defaultRequestQueueId: field(z.string().default('default'), 'CRAWLEE_DEFAULT_REQUEST_QUEUE_ID'),
50
+ /** @default 0.95 */
51
+ maxUsedCpuRatio: field(coerceNumber.default(0.95)),
52
+ /** @default 0.25 */
53
+ availableMemoryRatio: field(coerceNumber.default(0.25), 'CRAWLEE_AVAILABLE_MEMORY_RATIO'),
54
+ memoryMbytes: field(coerceNumber.optional(), 'CRAWLEE_MEMORY_MBYTES'),
55
+ /** @default 60_000 */
56
+ persistStateIntervalMillis: field(coerceNumber.default(60_000), 'CRAWLEE_PERSIST_STATE_INTERVAL_MILLIS'),
57
+ /**
58
+ * Internal safety-net timeout for a single request, in milliseconds. When unset the crawler derives it from
59
+ * the request handler timeout (twice it, and never below 5 minutes).
60
+ */
61
+ internalTimeoutMillis: field(coerceNumber.optional(), 'CRAWLEE_INTERNAL_TIMEOUT'),
62
+ /** @default 1_000 */
63
+ systemInfoIntervalMillis: field(coerceNumber.default(1_000)),
64
+ /** @default 'INPUT' */
65
+ inputKey: field(z.string().default('INPUT'), 'CRAWLEE_INPUT_KEY'),
66
+ /** @default true */
67
+ headless: field(coerceBoolean.default(true), 'CRAWLEE_HEADLESS'),
68
+ /** @default false */
69
+ xvfb: field(coerceBoolean.default(false), 'CRAWLEE_XVFB'),
70
+ chromeExecutablePath: field(z.string().optional(), 'CRAWLEE_CHROME_EXECUTABLE_PATH'),
71
+ defaultBrowserPath: field(z.string().optional(), 'CRAWLEE_DEFAULT_BROWSER_PATH'),
72
+ /** @default false */
73
+ disableBrowserSandbox: field(coerceBoolean.default(false), 'CRAWLEE_DISABLE_BROWSER_SANDBOX'),
74
+ logLevel: field(logLevelSchema.optional(), 'CRAWLEE_LOG_LEVEL'),
75
+ /** @default true */
76
+ persistStorage: field(coerceBoolean.default(true), 'CRAWLEE_PERSIST_STORAGE'),
77
+ /** @default './storage' */
78
+ storageDir: field(z.string().default('./storage'), 'CRAWLEE_STORAGE_DIR'),
79
+ containerized: field(coerceBoolean.optional(), 'CRAWLEE_CONTAINERIZED'),
80
+ };
10
81
  /**
11
82
  * `Configuration` is a value object holding Crawlee configuration. By default, there is a
12
- * global singleton instance of this class available via `Configuration.getGlobalConfig()`.
83
+ * global singleton instance of this class available via `Configuration.getGlobalConfiguration()`.
13
84
  * Places that depend on a configurable behaviour depend on this class, as they have the global
14
85
  * instance as the default value.
15
86
  *
@@ -18,14 +89,10 @@ import { entries } from './typedefs.js';
18
89
  * import { BasicCrawler, Configuration } from 'crawlee';
19
90
  *
20
91
  * // Get the global configuration
21
- * const config = Configuration.getGlobalConfig();
22
- * // Set the 'persistStateIntervalMillis' option
23
- * // of global configuration to 10 seconds
24
- * config.set('persistStateIntervalMillis', 10_000);
25
- *
26
- * // No need to pass the configuration to the crawler,
27
- * // as it's using the global configuration by default
28
- * const crawler = new BasicCrawler();
92
+ * const config = Configuration.getGlobalConfiguration();
93
+ * // Access configuration values directly as properties
94
+ * console.log(config.headless);
95
+ * console.log(config.persistStateIntervalMillis);
29
96
  * ```
30
97
  *
31
98
  * *Using custom configuration:*
@@ -35,15 +102,14 @@ import { entries } from './typedefs.js';
35
102
  * // Create a new configuration
36
103
  * const config = new Configuration({ persistStateIntervalMillis: 30_000 });
37
104
  * // Pass the configuration to the crawler
38
- * const crawler = new BasicCrawler({ ... }, config);
105
+ * const crawler = new BasicCrawler({ configuration: config });
39
106
  * ```
40
107
  *
41
- * The configuration provided via environment variables always takes precedence. We can also
42
- * define the `crawlee.json` file in the project root directory which will serve as a baseline,
43
- * so the options provided in constructor will override those. In other words, the precedence is:
108
+ * Configuration is immutable — values are set via the constructor and cannot be changed afterwards.
109
+ * The priority order for resolving values is (highest to lowest):
44
110
  *
45
111
  * ```text
46
- * crawlee.json < constructor options < environment variables
112
+ * constructor options > environment variables > crawlee.json > schema defaults
47
113
  * ```
48
114
  *
49
115
  * ## Supported Configuration Options
@@ -57,246 +123,128 @@ import { entries } from './typedefs.js';
57
123
  * `defaultKeyValueStoreId` | `CRAWLEE_DEFAULT_KEY_VALUE_STORE_ID` | `'default'`
58
124
  * `defaultRequestQueueId` | `CRAWLEE_DEFAULT_REQUEST_QUEUE_ID` | `'default'`
59
125
  * `persistStateIntervalMillis` | `CRAWLEE_PERSIST_STATE_INTERVAL_MILLIS` | `60_000`
126
+ * `internalTimeoutMillis` | `CRAWLEE_INTERNAL_TIMEOUT` | -
60
127
  * `purgeOnStart` | `CRAWLEE_PURGE_ON_START` | `true`
61
128
  * `persistStorage` | `CRAWLEE_PERSIST_STORAGE` | `true`
129
+ * `storageDir` | `CRAWLEE_STORAGE_DIR` | `'./storage'`
62
130
  *
63
131
  * ## Advanced Configuration Options
64
132
  *
65
133
  * Key | Environment Variable | Default Value
66
134
  * ---|---|---
67
135
  * `inputKey` | `CRAWLEE_INPUT_KEY` | `'INPUT'`
68
- * `xvfb` | `CRAWLEE_XVFB` | -
136
+ * `xvfb` | `CRAWLEE_XVFB` | `false`
69
137
  * `chromeExecutablePath` | `CRAWLEE_CHROME_EXECUTABLE_PATH` | -
70
138
  * `defaultBrowserPath` | `CRAWLEE_DEFAULT_BROWSER_PATH` | -
71
139
  * `disableBrowserSandbox` | `CRAWLEE_DISABLE_BROWSER_SANDBOX` | -
72
140
  * `availableMemoryRatio` | `CRAWLEE_AVAILABLE_MEMORY_RATIO` | `0.25`
73
- * `containerized | `CRAWLEE_CONTAINERIZED | -
141
+ * `containerized` | `CRAWLEE_CONTAINERIZED` | -
74
142
  */
143
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
75
144
  export class Configuration {
76
145
  /**
77
- * Maps environment variables to config keys (e.g. `CRAWLEE_MEMORY_MBYTES` to `memoryMbytes`)
78
- */
79
- static ENV_MAP = {
80
- CRAWLEE_AVAILABLE_MEMORY_RATIO: 'availableMemoryRatio',
81
- CRAWLEE_PURGE_ON_START: 'purgeOnStart',
82
- CRAWLEE_MEMORY_MBYTES: 'memoryMbytes',
83
- CRAWLEE_DEFAULT_DATASET_ID: 'defaultDatasetId',
84
- CRAWLEE_DEFAULT_KEY_VALUE_STORE_ID: 'defaultKeyValueStoreId',
85
- CRAWLEE_DEFAULT_REQUEST_QUEUE_ID: 'defaultRequestQueueId',
86
- CRAWLEE_INPUT_KEY: 'inputKey',
87
- CRAWLEE_PERSIST_STATE_INTERVAL_MILLIS: 'persistStateIntervalMillis',
88
- CRAWLEE_HEADLESS: 'headless',
89
- CRAWLEE_XVFB: 'xvfb',
90
- CRAWLEE_CHROME_EXECUTABLE_PATH: 'chromeExecutablePath',
91
- CRAWLEE_DEFAULT_BROWSER_PATH: 'defaultBrowserPath',
92
- CRAWLEE_DISABLE_BROWSER_SANDBOX: 'disableBrowserSandbox',
93
- CRAWLEE_LOG_LEVEL: 'logLevel',
94
- CRAWLEE_PERSIST_STORAGE: 'persistStorage',
95
- CRAWLEE_CONTAINERIZED: 'containerized',
96
- };
97
- static BOOLEAN_VARS = [
98
- 'purgeOnStart',
99
- 'headless',
100
- 'xvfb',
101
- 'disableBrowserSandbox',
102
- 'persistStorage',
103
- 'containerized',
104
- ];
105
- static INTEGER_VARS = ['memoryMbytes', 'persistStateIntervalMillis', 'systemInfoIntervalMillis'];
106
- static COMMA_SEPARATED_LIST_VARS = [];
107
- static DEFAULTS = {
108
- defaultKeyValueStoreId: 'default',
109
- defaultDatasetId: 'default',
110
- defaultRequestQueueId: 'default',
111
- inputKey: 'INPUT',
112
- maxUsedCpuRatio: 0.95,
113
- availableMemoryRatio: 0.25,
114
- storageClientOptions: {},
115
- purgeOnStart: true,
116
- headless: true,
117
- persistStateIntervalMillis: 60_000,
118
- systemInfoIntervalMillis: 1_000,
119
- persistStorage: true,
120
- };
121
- /**
122
- * Provides access to the current-instance-scoped Configuration without passing it around in parameters.
123
- * @internal
146
+ * Field definitions for this configuration class.
147
+ * Subclasses override this to register additional fields.
124
148
  */
125
- static storage = new AsyncLocalStorage();
126
- options;
127
- services = new Map();
128
- /** @internal */
129
- static globalConfig;
130
- storageManagers = new Map();
149
+ static fields = crawleeConfigFields;
150
+ #resolvedValues;
131
151
  /**
132
- * Creates new `Configuration` instance with provided options. Env vars will have precedence over those.
152
+ * Creates new `Configuration` instance with provided options.
153
+ * Constructor options take precedence over environment variables, which take precedence
154
+ * over crawlee.json values, which take precedence over schema defaults.
133
155
  */
134
156
  constructor(options = {}) {
135
- this.buildOptions(options);
136
- // Increase the global limit for event emitter memory leak warnings.
137
- EventEmitter.defaultMaxListeners = 50;
138
- // set the log level to support CRAWLEE_ prefixed env var too
139
- const logLevel = this.get('logLevel');
140
- if (logLevel) {
141
- const level = Number.isFinite(+logLevel)
142
- ? +logLevel
143
- : LogLevel[String(logLevel).toUpperCase()];
144
- log.setLevel(level);
145
- }
146
- }
147
- /**
148
- * Returns configured value. First checks the environment variables, then provided configuration,
149
- * fallbacks to the `defaultValue` argument if provided, otherwise uses the default value as described
150
- * in the above section.
151
- */
152
- get(key, defaultValue) {
153
- // prefer env vars, always iterate through the whole map as there might be duplicate env vars for the same option
154
- let envValue;
155
- for (const [k, v] of entries(Configuration.ENV_MAP)) {
156
- if (key === v) {
157
- envValue = process.env[k];
158
- if (envValue) {
159
- break;
160
- }
161
- }
157
+ const fields = this.constructor.fields;
158
+ const fileOptions = Configuration.loadFileOptions();
159
+ this.#resolvedValues = Configuration.resolveAll(fields, options, fileOptions);
160
+ this.registerAccessors();
161
+ // Set the log level
162
+ const logLevel = this.logLevel;
163
+ if (logLevel != null) {
164
+ log.setLevel(logLevel);
162
165
  }
163
- if (envValue != null) {
164
- return this._castEnvValue(key, envValue);
165
- }
166
- // check instance level options
167
- if (this.options.has(key)) {
168
- return this.options.get(key);
169
- }
170
- // fallback to defaults
171
- return (defaultValue ?? Configuration.DEFAULTS[key] ?? envValue);
172
- }
173
- _castEnvValue(key, value) {
174
- if (Configuration.INTEGER_VARS.includes(key)) {
175
- return +value;
176
- }
177
- if (Configuration.BOOLEAN_VARS.includes(key)) {
178
- // 0, false and empty string are considered falsy values
179
- return !['0', 'false', ''].includes(String(value).toLowerCase());
180
- }
181
- if (Configuration.COMMA_SEPARATED_LIST_VARS.includes(key)) {
182
- if (!value)
183
- return [];
184
- return String(value)
185
- .split(',')
186
- .map((v) => v.trim());
187
- }
188
- return value;
189
166
  }
190
167
  /**
191
- * Sets value for given option. Only affects this `Configuration` instance, the value will not be propagated down to the env var.
192
- * To reset a value, we can omit the `value` argument or pass `undefined` there.
193
- */
194
- set(key, value) {
195
- this.options.set(key, value);
196
- }
197
- /**
198
- * Sets value for given option. Only affects the global `Configuration` instance, the value will not be propagated down to the env var.
199
- * To reset a value, we can omit the `value` argument or pass `undefined` there.
200
- */
201
- static set(key, value) {
202
- this.getGlobalConfig().set(key, value);
203
- }
204
- /**
205
- * Returns cached instance of {@link StorageClient} using options as defined in the environment variables or in
206
- * this {@link Configuration} instance. Only first call of this method will create the client, following calls will
207
- * return the same client instance.
168
+ * Returns the global configuration instance. It will respect the environment variables.
208
169
  *
209
- * Caching works based on the `storageClientOptions`, so calling this method with different options will return
210
- * multiple instances, one for each variant of the options.
211
- * @internal
170
+ * Delegates to the global ServiceLocator, making it the single source of truth for service management.
212
171
  */
213
- getStorageClient() {
214
- if (this.options.has('storageClient')) {
215
- return this.options.get('storageClient');
216
- }
217
- const options = this.options.get('storageClientOptions');
218
- return this.createMemoryStorage(options);
219
- }
220
- getEventManager() {
221
- if (this.options.has('eventManager')) {
222
- return this.options.get('eventManager');
223
- }
224
- if (this.services.has('eventManager')) {
225
- return this.services.get('eventManager');
226
- }
227
- const eventManager = new LocalEventManager(this);
228
- this.services.set('eventManager', eventManager);
229
- return eventManager;
172
+ static getGlobalConfiguration() {
173
+ return serviceLocator.getConfiguration();
230
174
  }
231
175
  /**
232
- * Creates an instance of MemoryStorage using options as defined in the environment variables or in this `Configuration` instance.
233
- * @internal
176
+ * Resolves all field values once using the priority chain:
177
+ * constructor options > env vars > crawlee.json > schema defaults.
234
178
  */
235
- createMemoryStorage(options = {}) {
236
- const cacheKey = `MemoryStorage-${JSON.stringify(options)}`;
237
- if (this.services.has(cacheKey)) {
238
- return this.services.get(cacheKey);
179
+ static resolveAll(fields, userOptions, fileOptions) {
180
+ const values = {};
181
+ for (const [key, fieldDef] of Object.entries(fields)) {
182
+ // 1. Constructor options (highest priority)
183
+ if (key in userOptions && userOptions[key] !== undefined) {
184
+ values[key] = fieldDef.schema.parse(userOptions[key]);
185
+ continue;
186
+ }
187
+ // 2. Environment variables
188
+ const envValue = Configuration.readEnvVar(fieldDef);
189
+ if (envValue != null) {
190
+ values[key] = fieldDef.schema.parse(envValue);
191
+ continue;
192
+ }
193
+ // 3. crawlee.json file options
194
+ if (key in fileOptions && fileOptions[key] !== undefined) {
195
+ values[key] = fieldDef.schema.parse(fileOptions[key]);
196
+ continue;
197
+ }
198
+ // 4. Schema default (by parsing undefined through the schema)
199
+ const parsed = fieldDef.schema.safeParse(undefined);
200
+ values[key] = parsed.success ? parsed.data : undefined;
239
201
  }
240
- const storage = new MemoryStorage({
241
- persistStorage: this.get('persistStorage'),
242
- // Override persistStorage if user provides it via storageClientOptions
243
- ...options,
244
- });
245
- this.services.set(cacheKey, storage);
246
- return storage;
247
- }
248
- useStorageClient(client) {
249
- this.options.set('storageClient', client);
250
- }
251
- static useStorageClient(client) {
252
- this.getGlobalConfig().useStorageClient(client);
253
- }
254
- useEventManager(events) {
255
- this.options.set('eventManager', events);
202
+ return values;
256
203
  }
257
204
  /**
258
- * Returns the global configuration instance. It will respect the environment variables.
205
+ * Registers getters (and throwing setters) on the instance for each field.
259
206
  */
260
- static getGlobalConfig() {
261
- if (Configuration.storage.getStore()) {
262
- return Configuration.storage.getStore();
207
+ registerAccessors() {
208
+ const fields = this.constructor.fields;
209
+ const descriptors = {};
210
+ for (const key of Object.keys(fields)) {
211
+ descriptors[key] = {
212
+ get: () => this.#resolvedValues[key],
213
+ set() {
214
+ throw new TypeError('Configuration is immutable. Pass options via the constructor instead.');
215
+ },
216
+ enumerable: true,
217
+ configurable: false,
218
+ };
263
219
  }
264
- Configuration.globalConfig ??= new Configuration();
265
- return Configuration.globalConfig;
266
- }
267
- /**
268
- * Gets default {@link StorageClient} instance.
269
- */
270
- static getStorageClient() {
271
- return this.getGlobalConfig().getStorageClient();
220
+ Object.defineProperties(this, descriptors);
272
221
  }
273
222
  /**
274
- * Gets default {@link EventManager} instance.
223
+ * Reads the first defined env var value for a field definition.
224
+ * Empty strings are treated as unset, falling through to crawlee.json or schema defaults.
225
+ * (Crawlee v3 coerced `''` to `false`/`0`/`''` per type — v4 drops that for consistency.)
275
226
  */
276
- static getEventManager() {
277
- return this.getGlobalConfig().getEventManager();
227
+ static readEnvVar(fieldDef) {
228
+ if (!fieldDef.envVar)
229
+ return undefined;
230
+ const envVars = Array.isArray(fieldDef.envVar) ? fieldDef.envVar : [fieldDef.envVar];
231
+ for (const envVar of envVars) {
232
+ const value = process.env[envVar];
233
+ if (value != null && value !== '')
234
+ return value;
235
+ }
236
+ return undefined;
278
237
  }
279
238
  /**
280
- * Resets global configuration instance. The default instance holds configuration based on env vars,
281
- * if we want to change them, we need to first reset the global state. Used mainly for testing purposes.
239
+ * Loads config options from crawlee.json in the current working directory.
282
240
  */
283
- static resetGlobalState() {
284
- delete this.globalConfig;
285
- }
286
- buildOptions(options) {
287
- // try to load configuration from crawlee.json as the baseline
288
- const path = join(process.cwd(), 'crawlee.json');
289
- if (pathExistsSync(path)) {
290
- try {
291
- const file = readFileSync(path);
292
- const optionsFromFileConfig = JSON.parse(file.toString());
293
- Object.assign(options, optionsFromFileConfig);
294
- }
295
- catch {
296
- // ignore
297
- }
241
+ static loadFileOptions() {
242
+ try {
243
+ const file = readFileSync(join(process.cwd(), 'crawlee.json'));
244
+ return JSON.parse(file.toString());
245
+ }
246
+ catch {
247
+ return {};
298
248
  }
299
- this.options = new Map(entries(options));
300
249
  }
301
250
  }
302
- //# sourceMappingURL=configuration.js.map
package/cookie_utils.d.ts CHANGED
@@ -25,9 +25,11 @@ export declare function toughCookieToBrowserPoolCookie(toughCookie: Cookie): Coo
25
25
  /**
26
26
  * Transforms browser-pool cookie to tough-cookie.
27
27
  * @param cookieObject Cookie object (for instance from the `page.cookies` method).
28
+ * @param maxAgeSecs Fallback expiration in seconds when the cookie itself has no `expires`.
29
+ * When omitted, such a cookie is stored as a session cookie (no automatic expiration).
28
30
  * @internal
29
31
  */
30
- export declare function browserPoolCookieToToughCookie(cookieObject: CookieObject, maxAgeSecs: number): Cookie;
32
+ export declare function browserPoolCookieToToughCookie(cookieObject: CookieObject, maxAgeSecs?: number): Cookie;
31
33
  /**
32
34
  * @internal
33
35
  * @param cookieString The cookie string to attempt parsing
@@ -40,4 +42,3 @@ export declare function cookieStringToToughCookie(cookieString: string): CookieO
40
42
  * @internal
41
43
  */
42
44
  export declare function mergeCookies(url: string, sourceCookies: string[]): string;
43
- //# sourceMappingURL=cookie_utils.d.ts.map
package/cookie_utils.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Cookie, CookieJar } from 'tough-cookie';
2
- import { log } from './log.js';
2
+ import { serviceLocator } from './service_locator.js';
3
3
  import { CookieParseError } from './session_pool/errors.js';
4
4
  /**
5
5
  * @internal
@@ -47,19 +47,25 @@ export function toughCookieToBrowserPoolCookie(toughCookie) {
47
47
  /**
48
48
  * Transforms browser-pool cookie to tough-cookie.
49
49
  * @param cookieObject Cookie object (for instance from the `page.cookies` method).
50
+ * @param maxAgeSecs Fallback expiration in seconds when the cookie itself has no `expires`.
51
+ * When omitted, such a cookie is stored as a session cookie (no automatic expiration).
50
52
  * @internal
51
53
  */
52
54
  export function browserPoolCookieToToughCookie(cookieObject, maxAgeSecs) {
53
55
  const isExpiresValid = cookieObject.expires && typeof cookieObject.expires === 'number' && cookieObject.expires > 0;
54
- const expires = isExpiresValid
55
- ? new Date(cookieObject.expires * 1000)
56
- : getDefaultCookieExpirationDate(maxAgeSecs);
56
+ let expires;
57
+ if (isExpiresValid) {
58
+ expires = new Date(cookieObject.expires * 1000);
59
+ }
60
+ else if (maxAgeSecs != null) {
61
+ expires = getDefaultCookieExpirationDate(maxAgeSecs);
62
+ }
57
63
  const domainHasLeadingDot = cookieObject.domain?.startsWith?.('.');
58
64
  const domain = domainHasLeadingDot ? cookieObject.domain?.slice?.(1) : cookieObject.domain;
59
65
  return new Cookie({
60
66
  key: cookieObject.name,
61
67
  value: cookieObject.value,
62
- expires,
68
+ ...(expires !== undefined && { expires }),
63
69
  domain,
64
70
  path: cookieObject.path,
65
71
  secure: cookieObject.secure,
@@ -97,15 +103,20 @@ export function mergeCookies(url, sourceCookies) {
97
103
  if (!cookieString)
98
104
  continue;
99
105
  const cookie = Cookie.parse(cookieString);
106
+ if (!cookie) {
107
+ serviceLocator.getLogger().warning(`Skipping malformed cookie fragment: '${cookieString}'`);
108
+ continue;
109
+ }
100
110
  const similarKeyCookie = jar.getCookiesSync(url).find((c) => {
101
111
  return cookie.key !== c.key && cookie.key.toLowerCase() === c.key.toLowerCase();
102
112
  });
103
113
  if (similarKeyCookie) {
104
- log.warningOnce(`Found cookies with similar name during cookie merging: '${cookie.key}' and '${similarKeyCookie.key}'`);
114
+ serviceLocator
115
+ .getLogger()
116
+ .warningOnce(`Found cookies with similar name during cookie merging: '${cookie.key}' and '${similarKeyCookie.key}'`);
105
117
  }
106
118
  jar.setCookieSync(cookie, url);
107
119
  }
108
120
  }
109
121
  return jar.getCookieStringSync(url);
110
122
  }
111
- //# sourceMappingURL=cookie_utils.js.map
@@ -40,6 +40,16 @@ export declare abstract class ContextPipeline<TContextBase, TCrawlingContext ext
40
40
  * @returns A new ContextPipeline instance with the added middleware
41
41
  */
42
42
  abstract compose<TCrawlingContextExtension>(middleware: ContextMiddleware<TCrawlingContext, TCrawlingContextExtension>): ContextPipeline<TContextBase, TCrawlingContext & TCrawlingContextExtension>;
43
+ /**
44
+ * Chains another pipeline onto this one. The other pipeline's base context must match
45
+ * this pipeline's output context. Returns a new pipeline that runs this pipeline's
46
+ * middlewares first, then the other pipeline's middlewares.
47
+ *
48
+ * @template TFinalContext - The final context type after the chained pipeline's transformations
49
+ * @param other - The pipeline to append after this one
50
+ * @returns A new ContextPipeline combining both pipelines' middlewares
51
+ */
52
+ abstract chain<TFinalContext extends TCrawlingContext>(other: ContextPipeline<TCrawlingContext, TFinalContext>): ContextPipeline<TContextBase, TFinalContext>;
43
53
  /**
44
54
  * Executes the middleware pipeline and passes the final context to a consumer function.
45
55
  *
@@ -58,4 +68,3 @@ export declare abstract class ContextPipeline<TContextBase, TCrawlingContext ext
58
68
  */
59
69
  abstract call(crawlingContext: TContextBase, finalContextConsumer: (finalContext: TCrawlingContext) => Awaitable<unknown>): Promise<void>;
60
70
  }
61
- //# sourceMappingURL=context_pipeline.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { ContextPipelineCleanupError, ContextPipelineInitializationError, ContextPipelineInterruptedError, RequestHandlerError, SessionError, } from '../errors.js';
2
+ import { serviceLocator } from '../service_locator.js';
2
3
  /**
3
4
  * Encapsulates the logic of gradually enhancing the crawling context with additional information and utilities.
4
5
  *
@@ -25,12 +26,12 @@ export class ContextPipeline {
25
26
  * properties from the `ContextPipeline` interface, making type checking more reliable.
26
27
  */
27
28
  class ContextPipelineImpl extends ContextPipeline {
28
- middleware;
29
- parent;
29
+ #middleware;
30
+ #parent;
30
31
  constructor(middleware, parent) {
31
32
  super();
32
- this.middleware = middleware;
33
- this.parent = parent;
33
+ this.#middleware = middleware;
34
+ this.#parent = parent;
34
35
  }
35
36
  /**
36
37
  * @inheritdoc
@@ -38,11 +39,19 @@ class ContextPipelineImpl extends ContextPipeline {
38
39
  compose(middleware) {
39
40
  return new ContextPipelineImpl(middleware, this);
40
41
  }
42
+ chain(other) {
43
+ const otherMiddlewares = Array.from(other.middlewareChain()).reverse();
44
+ let result = this;
45
+ for (const middleware of otherMiddlewares) {
46
+ result = result.compose(middleware);
47
+ }
48
+ return result;
49
+ }
41
50
  *middlewareChain() {
42
51
  let step = this;
43
52
  while (step !== undefined) {
44
- yield step.middleware;
45
- step = step.parent;
53
+ yield step.#middleware;
54
+ step = step.#parent;
46
55
  }
47
56
  }
48
57
  /**
@@ -56,7 +65,22 @@ class ContextPipelineImpl extends ContextPipeline {
56
65
  for (const { action, cleanup } of middlewares) {
57
66
  try {
58
67
  const contextExtension = await action(crawlingContext);
59
- Object.defineProperties(crawlingContext, Object.getOwnPropertyDescriptors(contextExtension));
68
+ const extensionNames = [
69
+ ...Object.getOwnPropertyNames(contextExtension),
70
+ ...Object.getOwnPropertySymbols(contextExtension),
71
+ ];
72
+ for (const key of extensionNames) {
73
+ try {
74
+ if (Object.getOwnPropertyDescriptor(crawlingContext, key)?.configurable !== false) {
75
+ Object.defineProperty(crawlingContext, key, Object.getOwnPropertyDescriptor(contextExtension, key));
76
+ }
77
+ }
78
+ catch (error) {
79
+ serviceLocator
80
+ .getLogger()
81
+ .debug(`Context pipeline failed to define property ${key.toString()}:`, error);
82
+ }
83
+ }
60
84
  if (cleanup) {
61
85
  cleanupStack.push(cleanup);
62
86
  }
@@ -96,4 +120,3 @@ class ContextPipelineImpl extends ContextPipeline {
96
120
  }
97
121
  }
98
122
  }
99
- //# sourceMappingURL=context_pipeline.js.map