@crawlee/core 4.0.0-beta.8 → 4.0.0-beta.80

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 (257) hide show
  1. package/README.md +17 -13
  2. package/autoscaling/autoscaled_pool.d.ts +5 -6
  3. package/autoscaling/autoscaled_pool.js +12 -11
  4. package/autoscaling/client_load_signal.d.ts +25 -0
  5. package/autoscaling/client_load_signal.js +36 -0
  6. package/autoscaling/cpu_load_signal.d.ts +28 -0
  7. package/autoscaling/cpu_load_signal.js +24 -0
  8. package/autoscaling/event_loop_load_signal.d.ts +23 -0
  9. package/autoscaling/event_loop_load_signal.js +35 -0
  10. package/autoscaling/index.d.ts +5 -1
  11. package/autoscaling/index.js +5 -1
  12. package/autoscaling/load_signal.d.ts +99 -0
  13. package/autoscaling/load_signal.js +135 -0
  14. package/autoscaling/memory_load_signal.d.ts +43 -0
  15. package/autoscaling/memory_load_signal.js +102 -0
  16. package/autoscaling/snapshotter.d.ts +35 -72
  17. package/autoscaling/snapshotter.js +78 -181
  18. package/autoscaling/system_status.d.ts +21 -36
  19. package/autoscaling/system_status.js +46 -84
  20. package/configuration.d.ts +88 -228
  21. package/configuration.js +162 -224
  22. package/cookie_utils.d.ts +4 -3
  23. package/cookie_utils.js +20 -13
  24. package/crawlers/context_pipeline.d.ts +70 -0
  25. package/crawlers/context_pipeline.js +122 -0
  26. package/crawlers/crawler_commons.d.ts +20 -30
  27. package/crawlers/crawler_commons.js +12 -21
  28. package/crawlers/crawler_utils.d.ts +2 -3
  29. package/crawlers/crawler_utils.js +1 -2
  30. package/crawlers/error_snapshotter.d.ts +3 -3
  31. package/crawlers/error_snapshotter.js +2 -3
  32. package/crawlers/error_tracker.d.ts +2 -2
  33. package/crawlers/error_tracker.js +0 -1
  34. package/crawlers/index.d.ts +1 -2
  35. package/crawlers/index.js +1 -2
  36. package/crawlers/internals/types.d.ts +7 -0
  37. package/crawlers/internals/types.js +1 -0
  38. package/crawlers/statistics.d.ts +22 -17
  39. package/crawlers/statistics.js +30 -26
  40. package/enqueue_links/enqueue_links.d.ts +44 -22
  41. package/enqueue_links/enqueue_links.js +51 -30
  42. package/enqueue_links/index.d.ts +0 -1
  43. package/enqueue_links/index.js +0 -1
  44. package/enqueue_links/shared.d.ts +25 -9
  45. package/enqueue_links/shared.js +69 -38
  46. package/errors.d.ts +53 -4
  47. package/errors.js +70 -5
  48. package/events/event_manager.d.ts +34 -8
  49. package/events/event_manager.js +8 -10
  50. package/events/index.d.ts +0 -1
  51. package/events/index.js +0 -1
  52. package/events/local_event_manager.d.ts +15 -5
  53. package/events/local_event_manager.js +37 -40
  54. package/index.d.ts +4 -3
  55. package/index.js +3 -2
  56. package/log.d.ts +82 -3
  57. package/log.js +102 -1
  58. package/memory-storage/consts.d.ts +4 -0
  59. package/memory-storage/consts.js +4 -0
  60. package/memory-storage/index.d.ts +1 -0
  61. package/memory-storage/index.js +1 -0
  62. package/memory-storage/memory-storage.d.ts +46 -0
  63. package/memory-storage/memory-storage.js +136 -0
  64. package/memory-storage/resource-clients/common/base-client.d.ts +4 -0
  65. package/memory-storage/resource-clients/common/base-client.js +6 -0
  66. package/memory-storage/resource-clients/dataset.d.ts +40 -0
  67. package/memory-storage/resource-clients/dataset.js +113 -0
  68. package/memory-storage/resource-clients/key-value-store.d.ts +63 -0
  69. package/memory-storage/resource-clients/key-value-store.js +203 -0
  70. package/memory-storage/resource-clients/request-queue.d.ts +96 -0
  71. package/memory-storage/resource-clients/request-queue.js +421 -0
  72. package/memory-storage/utils.d.ts +17 -0
  73. package/memory-storage/utils.js +42 -0
  74. package/package.json +12 -10
  75. package/proxy_configuration.d.ts +14 -149
  76. package/proxy_configuration.js +19 -168
  77. package/recoverable_state.d.ts +120 -0
  78. package/recoverable_state.js +141 -0
  79. package/request.d.ts +84 -13
  80. package/request.js +107 -28
  81. package/router.d.ts +117 -15
  82. package/router.js +124 -30
  83. package/serialization.d.ts +0 -1
  84. package/serialization.js +1 -2
  85. package/service_locator.d.ts +156 -0
  86. package/service_locator.js +238 -0
  87. package/session_pool/consts.d.ts +1 -2
  88. package/session_pool/consts.js +1 -2
  89. package/session_pool/errors.d.ts +0 -1
  90. package/session_pool/errors.js +0 -1
  91. package/session_pool/fingerprint.d.ts +9 -0
  92. package/session_pool/fingerprint.js +30 -0
  93. package/session_pool/index.d.ts +0 -2
  94. package/session_pool/index.js +0 -2
  95. package/session_pool/session.d.ts +35 -73
  96. package/session_pool/session.js +46 -99
  97. package/session_pool/session_pool.d.ts +71 -72
  98. package/session_pool/session_pool.js +111 -100
  99. package/storages/access_checking.d.ts +0 -1
  100. package/storages/access_checking.js +5 -2
  101. package/storages/dataset.d.ts +101 -51
  102. package/storages/dataset.js +168 -126
  103. package/storages/index.d.ts +8 -7
  104. package/storages/index.js +6 -7
  105. package/storages/key_value_store.d.ts +165 -36
  106. package/storages/key_value_store.js +269 -122
  107. package/storages/key_value_store_codec.d.ts +32 -0
  108. package/storages/key_value_store_codec.js +113 -0
  109. package/storages/request_dedup_cache.d.ts +23 -0
  110. package/storages/request_dedup_cache.js +48 -0
  111. package/storages/request_list.d.ts +38 -82
  112. package/storages/request_list.js +65 -41
  113. package/storages/request_loader.d.ts +96 -0
  114. package/storages/request_loader.js +1 -0
  115. package/storages/request_manager.d.ts +33 -0
  116. package/storages/request_manager.js +1 -0
  117. package/storages/request_manager_tandem.d.ts +106 -0
  118. package/storages/request_manager_tandem.js +197 -0
  119. package/storages/request_queue.d.ts +303 -46
  120. package/storages/request_queue.js +629 -214
  121. package/storages/{sitemap_request_list.d.ts → sitemap_request_loader.d.ts} +28 -24
  122. package/storages/{sitemap_request_list.js → sitemap_request_loader.js} +49 -46
  123. package/storages/storage_instance_manager.d.ts +91 -0
  124. package/storages/storage_instance_manager.js +258 -0
  125. package/storages/storage_stats.d.ts +48 -0
  126. package/storages/storage_stats.js +29 -0
  127. package/storages/utils.d.ts +51 -6
  128. package/storages/utils.js +60 -9
  129. package/system-info/cpu-info.d.ts +67 -0
  130. package/system-info/cpu-info.js +216 -0
  131. package/system-info/memory-info.d.ts +31 -0
  132. package/system-info/memory-info.js +115 -0
  133. package/system-info/ps-tree.d.ts +17 -0
  134. package/system-info/ps-tree.js +144 -0
  135. package/system-info/runtime.d.ts +14 -0
  136. package/system-info/runtime.js +80 -0
  137. package/typedefs.d.ts +1 -2
  138. package/typedefs.js +0 -1
  139. package/validators.d.ts +8 -1
  140. package/validators.js +8 -1
  141. package/autoscaling/autoscaled_pool.d.ts.map +0 -1
  142. package/autoscaling/autoscaled_pool.js.map +0 -1
  143. package/autoscaling/index.d.ts.map +0 -1
  144. package/autoscaling/index.js.map +0 -1
  145. package/autoscaling/snapshotter.d.ts.map +0 -1
  146. package/autoscaling/snapshotter.js.map +0 -1
  147. package/autoscaling/system_status.d.ts.map +0 -1
  148. package/autoscaling/system_status.js.map +0 -1
  149. package/configuration.d.ts.map +0 -1
  150. package/configuration.js.map +0 -1
  151. package/cookie_utils.d.ts.map +0 -1
  152. package/cookie_utils.js.map +0 -1
  153. package/crawlers/crawler_commons.d.ts.map +0 -1
  154. package/crawlers/crawler_commons.js.map +0 -1
  155. package/crawlers/crawler_extension.d.ts +0 -12
  156. package/crawlers/crawler_extension.d.ts.map +0 -1
  157. package/crawlers/crawler_extension.js +0 -14
  158. package/crawlers/crawler_extension.js.map +0 -1
  159. package/crawlers/crawler_utils.d.ts.map +0 -1
  160. package/crawlers/crawler_utils.js.map +0 -1
  161. package/crawlers/error_snapshotter.d.ts.map +0 -1
  162. package/crawlers/error_snapshotter.js.map +0 -1
  163. package/crawlers/error_tracker.d.ts.map +0 -1
  164. package/crawlers/error_tracker.js.map +0 -1
  165. package/crawlers/index.d.ts.map +0 -1
  166. package/crawlers/index.js.map +0 -1
  167. package/crawlers/statistics.d.ts.map +0 -1
  168. package/crawlers/statistics.js.map +0 -1
  169. package/enqueue_links/enqueue_links.d.ts.map +0 -1
  170. package/enqueue_links/enqueue_links.js.map +0 -1
  171. package/enqueue_links/index.d.ts.map +0 -1
  172. package/enqueue_links/index.js.map +0 -1
  173. package/enqueue_links/shared.d.ts.map +0 -1
  174. package/enqueue_links/shared.js.map +0 -1
  175. package/errors.d.ts.map +0 -1
  176. package/errors.js.map +0 -1
  177. package/events/event_manager.d.ts.map +0 -1
  178. package/events/event_manager.js.map +0 -1
  179. package/events/index.d.ts.map +0 -1
  180. package/events/index.js.map +0 -1
  181. package/events/local_event_manager.d.ts.map +0 -1
  182. package/events/local_event_manager.js.map +0 -1
  183. package/http_clients/base-http-client.d.ts +0 -134
  184. package/http_clients/base-http-client.d.ts.map +0 -1
  185. package/http_clients/base-http-client.js +0 -33
  186. package/http_clients/base-http-client.js.map +0 -1
  187. package/http_clients/form-data-like.d.ts +0 -67
  188. package/http_clients/form-data-like.d.ts.map +0 -1
  189. package/http_clients/form-data-like.js +0 -5
  190. package/http_clients/form-data-like.js.map +0 -1
  191. package/http_clients/got-scraping-http-client.d.ts +0 -15
  192. package/http_clients/got-scraping-http-client.d.ts.map +0 -1
  193. package/http_clients/got-scraping-http-client.js +0 -69
  194. package/http_clients/got-scraping-http-client.js.map +0 -1
  195. package/http_clients/index.d.ts +0 -3
  196. package/http_clients/index.d.ts.map +0 -1
  197. package/http_clients/index.js +0 -3
  198. package/http_clients/index.js.map +0 -1
  199. package/index.d.ts.map +0 -1
  200. package/index.js.map +0 -1
  201. package/log.d.ts.map +0 -1
  202. package/log.js.map +0 -1
  203. package/proxy_configuration.d.ts.map +0 -1
  204. package/proxy_configuration.js.map +0 -1
  205. package/request.d.ts.map +0 -1
  206. package/request.js.map +0 -1
  207. package/router.d.ts.map +0 -1
  208. package/router.js.map +0 -1
  209. package/serialization.d.ts.map +0 -1
  210. package/serialization.js.map +0 -1
  211. package/session_pool/consts.d.ts.map +0 -1
  212. package/session_pool/consts.js.map +0 -1
  213. package/session_pool/errors.d.ts.map +0 -1
  214. package/session_pool/errors.js.map +0 -1
  215. package/session_pool/events.d.ts +0 -3
  216. package/session_pool/events.d.ts.map +0 -1
  217. package/session_pool/events.js +0 -3
  218. package/session_pool/events.js.map +0 -1
  219. package/session_pool/index.d.ts.map +0 -1
  220. package/session_pool/index.js.map +0 -1
  221. package/session_pool/session.d.ts.map +0 -1
  222. package/session_pool/session.js.map +0 -1
  223. package/session_pool/session_pool.d.ts.map +0 -1
  224. package/session_pool/session_pool.js.map +0 -1
  225. package/storages/access_checking.d.ts.map +0 -1
  226. package/storages/access_checking.js.map +0 -1
  227. package/storages/dataset.d.ts.map +0 -1
  228. package/storages/dataset.js.map +0 -1
  229. package/storages/index.d.ts.map +0 -1
  230. package/storages/index.js.map +0 -1
  231. package/storages/key_value_store.d.ts.map +0 -1
  232. package/storages/key_value_store.js.map +0 -1
  233. package/storages/request_list.d.ts.map +0 -1
  234. package/storages/request_list.js.map +0 -1
  235. package/storages/request_provider.d.ts +0 -307
  236. package/storages/request_provider.d.ts.map +0 -1
  237. package/storages/request_provider.js +0 -555
  238. package/storages/request_provider.js.map +0 -1
  239. package/storages/request_queue.d.ts.map +0 -1
  240. package/storages/request_queue.js.map +0 -1
  241. package/storages/request_queue_v2.d.ts +0 -87
  242. package/storages/request_queue_v2.d.ts.map +0 -1
  243. package/storages/request_queue_v2.js +0 -438
  244. package/storages/request_queue_v2.js.map +0 -1
  245. package/storages/sitemap_request_list.d.ts.map +0 -1
  246. package/storages/sitemap_request_list.js.map +0 -1
  247. package/storages/storage_manager.d.ts +0 -58
  248. package/storages/storage_manager.d.ts.map +0 -1
  249. package/storages/storage_manager.js +0 -105
  250. package/storages/storage_manager.js.map +0 -1
  251. package/storages/utils.d.ts.map +0 -1
  252. package/storages/utils.js.map +0 -1
  253. package/tsconfig.build.tsbuildinfo +0 -1
  254. package/typedefs.d.ts.map +0 -1
  255. package/typedefs.js.map +0 -1
  256. package/validators.d.ts.map +0 -1
  257. package/validators.js.map +0 -1
package/configuration.js CHANGED
@@ -1,12 +1,78 @@
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.nativeEnum(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
+ /** @default 1_000 */
58
+ systemInfoIntervalMillis: field(coerceNumber.default(1_000)),
59
+ /** @default 'INPUT' */
60
+ inputKey: field(z.string().default('INPUT'), 'CRAWLEE_INPUT_KEY'),
61
+ /** @default true */
62
+ headless: field(coerceBoolean.default(true), 'CRAWLEE_HEADLESS'),
63
+ /** @default false */
64
+ xvfb: field(coerceBoolean.default(false), 'CRAWLEE_XVFB'),
65
+ chromeExecutablePath: field(z.string().optional(), 'CRAWLEE_CHROME_EXECUTABLE_PATH'),
66
+ defaultBrowserPath: field(z.string().optional(), 'CRAWLEE_DEFAULT_BROWSER_PATH'),
67
+ /** @default false */
68
+ disableBrowserSandbox: field(coerceBoolean.default(false), 'CRAWLEE_DISABLE_BROWSER_SANDBOX'),
69
+ logLevel: field(logLevelSchema.optional(), 'CRAWLEE_LOG_LEVEL'),
70
+ /** @default true */
71
+ persistStorage: field(coerceBoolean.default(true), 'CRAWLEE_PERSIST_STORAGE'),
72
+ /** @default './storage' */
73
+ storageDir: field(z.string().default('./storage'), 'CRAWLEE_STORAGE_DIR'),
74
+ containerized: field(coerceBoolean.optional(), 'CRAWLEE_CONTAINERIZED'),
75
+ };
10
76
  /**
11
77
  * `Configuration` is a value object holding Crawlee configuration. By default, there is a
12
78
  * global singleton instance of this class available via `Configuration.getGlobalConfig()`.
@@ -19,13 +85,9 @@ import { entries } from './typedefs.js';
19
85
  *
20
86
  * // Get the global configuration
21
87
  * 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();
88
+ * // Access configuration values directly as properties
89
+ * console.log(config.headless);
90
+ * console.log(config.persistStateIntervalMillis);
29
91
  * ```
30
92
  *
31
93
  * *Using custom configuration:*
@@ -35,15 +97,14 @@ import { entries } from './typedefs.js';
35
97
  * // Create a new configuration
36
98
  * const config = new Configuration({ persistStateIntervalMillis: 30_000 });
37
99
  * // Pass the configuration to the crawler
38
- * const crawler = new BasicCrawler({ ... }, config);
100
+ * const crawler = new BasicCrawler({ configuration: config });
39
101
  * ```
40
102
  *
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:
103
+ * Configuration is immutable — values are set via the constructor and cannot be changed afterwards.
104
+ * The priority order for resolving values is (highest to lowest):
44
105
  *
45
106
  * ```text
46
- * crawlee.json < constructor options < environment variables
107
+ * constructor options > environment variables > crawlee.json > schema defaults
47
108
  * ```
48
109
  *
49
110
  * ## Supported Configuration Options
@@ -59,248 +120,125 @@ import { entries } from './typedefs.js';
59
120
  * `persistStateIntervalMillis` | `CRAWLEE_PERSIST_STATE_INTERVAL_MILLIS` | `60_000`
60
121
  * `purgeOnStart` | `CRAWLEE_PURGE_ON_START` | `true`
61
122
  * `persistStorage` | `CRAWLEE_PERSIST_STORAGE` | `true`
123
+ * `storageDir` | `CRAWLEE_STORAGE_DIR` | `'./storage'`
62
124
  *
63
125
  * ## Advanced Configuration Options
64
126
  *
65
127
  * Key | Environment Variable | Default Value
66
128
  * ---|---|---
67
129
  * `inputKey` | `CRAWLEE_INPUT_KEY` | `'INPUT'`
68
- * `xvfb` | `CRAWLEE_XVFB` | -
130
+ * `xvfb` | `CRAWLEE_XVFB` | `false`
69
131
  * `chromeExecutablePath` | `CRAWLEE_CHROME_EXECUTABLE_PATH` | -
70
132
  * `defaultBrowserPath` | `CRAWLEE_DEFAULT_BROWSER_PATH` | -
71
133
  * `disableBrowserSandbox` | `CRAWLEE_DISABLE_BROWSER_SANDBOX` | -
72
134
  * `availableMemoryRatio` | `CRAWLEE_AVAILABLE_MEMORY_RATIO` | `0.25`
73
- * `systemInfoV2` | `CRAWLEE_SYSTEM_INFO_V2` | false
74
- * `containerized | `CRAWLEE_CONTAINERIZED | -
135
+ * `containerized` | `CRAWLEE_CONTAINERIZED` | -
75
136
  */
137
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
76
138
  export class Configuration {
77
139
  /**
78
- * Maps environment variables to config keys (e.g. `CRAWLEE_MEMORY_MBYTES` to `memoryMbytes`)
79
- */
80
- static ENV_MAP = {
81
- CRAWLEE_AVAILABLE_MEMORY_RATIO: 'availableMemoryRatio',
82
- CRAWLEE_PURGE_ON_START: 'purgeOnStart',
83
- CRAWLEE_MEMORY_MBYTES: 'memoryMbytes',
84
- CRAWLEE_DEFAULT_DATASET_ID: 'defaultDatasetId',
85
- CRAWLEE_DEFAULT_KEY_VALUE_STORE_ID: 'defaultKeyValueStoreId',
86
- CRAWLEE_DEFAULT_REQUEST_QUEUE_ID: 'defaultRequestQueueId',
87
- CRAWLEE_INPUT_KEY: 'inputKey',
88
- CRAWLEE_PERSIST_STATE_INTERVAL_MILLIS: 'persistStateIntervalMillis',
89
- CRAWLEE_HEADLESS: 'headless',
90
- CRAWLEE_XVFB: 'xvfb',
91
- CRAWLEE_CHROME_EXECUTABLE_PATH: 'chromeExecutablePath',
92
- CRAWLEE_DEFAULT_BROWSER_PATH: 'defaultBrowserPath',
93
- CRAWLEE_DISABLE_BROWSER_SANDBOX: 'disableBrowserSandbox',
94
- CRAWLEE_LOG_LEVEL: 'logLevel',
95
- CRAWLEE_PERSIST_STORAGE: 'persistStorage',
96
- CRAWLEE_SYSTEM_INFO_V2: 'systemInfoV2',
97
- CRAWLEE_CONTAINERIZED: 'containerized',
98
- };
99
- static BOOLEAN_VARS = [
100
- 'purgeOnStart',
101
- 'headless',
102
- 'xvfb',
103
- 'disableBrowserSandbox',
104
- 'persistStorage',
105
- 'systemInfoV2',
106
- 'containerized',
107
- ];
108
- static INTEGER_VARS = ['memoryMbytes', 'persistStateIntervalMillis', 'systemInfoIntervalMillis'];
109
- static COMMA_SEPARATED_LIST_VARS = [];
110
- static DEFAULTS = {
111
- defaultKeyValueStoreId: 'default',
112
- defaultDatasetId: 'default',
113
- defaultRequestQueueId: 'default',
114
- inputKey: 'INPUT',
115
- maxUsedCpuRatio: 0.95,
116
- availableMemoryRatio: 0.25,
117
- storageClientOptions: {},
118
- purgeOnStart: true,
119
- headless: true,
120
- persistStateIntervalMillis: 60_000,
121
- systemInfoIntervalMillis: 1_000,
122
- persistStorage: true,
123
- systemInfoV2: false,
124
- };
125
- /**
126
- * Provides access to the current-instance-scoped Configuration without passing it around in parameters.
127
- * @internal
140
+ * Field definitions for this configuration class.
141
+ * Subclasses override this to register additional fields.
128
142
  */
129
- static storage = new AsyncLocalStorage();
130
- options;
131
- services = new Map();
132
- /** @internal */
133
- static globalConfig;
134
- storageManagers = new Map();
143
+ static fields = crawleeConfigFields;
144
+ resolvedValues;
135
145
  /**
136
- * Creates new `Configuration` instance with provided options. Env vars will have precedence over those.
146
+ * Creates new `Configuration` instance with provided options.
147
+ * Constructor options take precedence over environment variables, which take precedence
148
+ * over crawlee.json values, which take precedence over schema defaults.
137
149
  */
138
150
  constructor(options = {}) {
139
- this.buildOptions(options);
140
- // Increase the global limit for event emitter memory leak warnings.
141
- EventEmitter.defaultMaxListeners = 50;
142
- // set the log level to support CRAWLEE_ prefixed env var too
143
- const logLevel = this.get('logLevel');
144
- if (logLevel) {
145
- const level = Number.isFinite(+logLevel)
146
- ? +logLevel
147
- : LogLevel[String(logLevel).toUpperCase()];
148
- log.setLevel(level);
149
- }
150
- }
151
- /**
152
- * Returns configured value. First checks the environment variables, then provided configuration,
153
- * fallbacks to the `defaultValue` argument if provided, otherwise uses the default value as described
154
- * in the above section.
155
- */
156
- get(key, defaultValue) {
157
- // prefer env vars, always iterate through the whole map as there might be duplicate env vars for the same option
158
- let envValue;
159
- for (const [k, v] of entries(Configuration.ENV_MAP)) {
160
- if (key === v) {
161
- envValue = process.env[k];
162
- if (envValue) {
163
- break;
164
- }
165
- }
166
- }
167
- if (envValue != null) {
168
- return this._castEnvValue(key, envValue);
169
- }
170
- // check instance level options
171
- if (this.options.has(key)) {
172
- return this.options.get(key);
151
+ const fields = this.constructor.fields;
152
+ const fileOptions = Configuration.loadFileOptions();
153
+ this.resolvedValues = Configuration.resolveAll(fields, options, fileOptions);
154
+ this.registerAccessors();
155
+ // Set the log level
156
+ const logLevel = this.logLevel;
157
+ if (logLevel != null) {
158
+ log.setLevel(logLevel);
173
159
  }
174
- // fallback to defaults
175
- return (defaultValue ?? Configuration.DEFAULTS[key] ?? envValue);
176
- }
177
- _castEnvValue(key, value) {
178
- if (Configuration.INTEGER_VARS.includes(key)) {
179
- return +value;
180
- }
181
- if (Configuration.BOOLEAN_VARS.includes(key)) {
182
- // 0, false and empty string are considered falsy values
183
- return !['0', 'false', ''].includes(String(value).toLowerCase());
184
- }
185
- if (Configuration.COMMA_SEPARATED_LIST_VARS.includes(key)) {
186
- if (!value)
187
- return [];
188
- return String(value)
189
- .split(',')
190
- .map((v) => v.trim());
191
- }
192
- return value;
193
160
  }
194
161
  /**
195
- * Sets value for given option. Only affects this `Configuration` instance, the value will not be propagated down to the env var.
196
- * To reset a value, we can omit the `value` argument or pass `undefined` there.
197
- */
198
- set(key, value) {
199
- this.options.set(key, value);
200
- }
201
- /**
202
- * Sets value for given option. Only affects the global `Configuration` instance, the value will not be propagated down to the env var.
203
- * To reset a value, we can omit the `value` argument or pass `undefined` there.
204
- */
205
- static set(key, value) {
206
- this.getGlobalConfig().set(key, value);
207
- }
208
- /**
209
- * Returns cached instance of {@link StorageClient} using options as defined in the environment variables or in
210
- * this {@link Configuration} instance. Only first call of this method will create the client, following calls will
211
- * return the same client instance.
162
+ * Returns the global configuration instance. It will respect the environment variables.
212
163
  *
213
- * Caching works based on the `storageClientOptions`, so calling this method with different options will return
214
- * multiple instances, one for each variant of the options.
215
- * @internal
164
+ * Delegates to the global ServiceLocator, making it the single source of truth for service management.
216
165
  */
217
- getStorageClient() {
218
- if (this.options.has('storageClient')) {
219
- return this.options.get('storageClient');
220
- }
221
- const options = this.options.get('storageClientOptions');
222
- return this.createMemoryStorage(options);
223
- }
224
- getEventManager() {
225
- if (this.options.has('eventManager')) {
226
- return this.options.get('eventManager');
227
- }
228
- if (this.services.has('eventManager')) {
229
- return this.services.get('eventManager');
230
- }
231
- const eventManager = new LocalEventManager(this);
232
- this.services.set('eventManager', eventManager);
233
- return eventManager;
166
+ static getGlobalConfig() {
167
+ return serviceLocator.getConfiguration();
234
168
  }
235
169
  /**
236
- * Creates an instance of MemoryStorage using options as defined in the environment variables or in this `Configuration` instance.
237
- * @internal
170
+ * Resolves all field values once using the priority chain:
171
+ * constructor options > env vars > crawlee.json > schema defaults.
238
172
  */
239
- createMemoryStorage(options = {}) {
240
- const cacheKey = `MemoryStorage-${JSON.stringify(options)}`;
241
- if (this.services.has(cacheKey)) {
242
- return this.services.get(cacheKey);
173
+ static resolveAll(fields, userOptions, fileOptions) {
174
+ const values = {};
175
+ for (const [key, fieldDef] of Object.entries(fields)) {
176
+ // 1. Constructor options (highest priority)
177
+ if (key in userOptions && userOptions[key] !== undefined) {
178
+ values[key] = fieldDef.schema.parse(userOptions[key]);
179
+ continue;
180
+ }
181
+ // 2. Environment variables
182
+ const envValue = Configuration.readEnvVar(fieldDef);
183
+ if (envValue != null) {
184
+ values[key] = fieldDef.schema.parse(envValue);
185
+ continue;
186
+ }
187
+ // 3. crawlee.json file options
188
+ if (key in fileOptions && fileOptions[key] !== undefined) {
189
+ values[key] = fieldDef.schema.parse(fileOptions[key]);
190
+ continue;
191
+ }
192
+ // 4. Schema default (by parsing undefined through the schema)
193
+ const result = fieldDef.schema.safeParse(undefined);
194
+ values[key] = result.success ? result.data : undefined;
243
195
  }
244
- const storage = new MemoryStorage({
245
- persistStorage: this.get('persistStorage'),
246
- // Override persistStorage if user provides it via storageClientOptions
247
- ...options,
248
- });
249
- this.services.set(cacheKey, storage);
250
- return storage;
251
- }
252
- useStorageClient(client) {
253
- this.options.set('storageClient', client);
254
- }
255
- static useStorageClient(client) {
256
- this.getGlobalConfig().useStorageClient(client);
257
- }
258
- useEventManager(events) {
259
- this.options.set('eventManager', events);
196
+ return values;
260
197
  }
261
198
  /**
262
- * Returns the global configuration instance. It will respect the environment variables.
199
+ * Registers getters (and throwing setters) on the instance for each field.
263
200
  */
264
- static getGlobalConfig() {
265
- if (Configuration.storage.getStore()) {
266
- return Configuration.storage.getStore();
201
+ registerAccessors() {
202
+ const fields = this.constructor.fields;
203
+ const descriptors = {};
204
+ for (const key of Object.keys(fields)) {
205
+ descriptors[key] = {
206
+ get: () => this.resolvedValues[key],
207
+ set() {
208
+ throw new TypeError('Configuration is immutable. Pass options via the constructor instead.');
209
+ },
210
+ enumerable: true,
211
+ configurable: false,
212
+ };
267
213
  }
268
- Configuration.globalConfig ??= new Configuration();
269
- return Configuration.globalConfig;
270
- }
271
- /**
272
- * Gets default {@link StorageClient} instance.
273
- */
274
- static getStorageClient() {
275
- return this.getGlobalConfig().getStorageClient();
214
+ Object.defineProperties(this, descriptors);
276
215
  }
277
216
  /**
278
- * Gets default {@link EventManager} instance.
217
+ * Reads the first defined env var value for a field definition.
218
+ * Empty strings are treated as unset, falling through to crawlee.json or schema defaults.
219
+ * (Crawlee v3 coerced `''` to `false`/`0`/`''` per type — v4 drops that for consistency.)
279
220
  */
280
- static getEventManager() {
281
- return this.getGlobalConfig().getEventManager();
221
+ static readEnvVar(fieldDef) {
222
+ if (!fieldDef.envVar)
223
+ return undefined;
224
+ const envVars = Array.isArray(fieldDef.envVar) ? fieldDef.envVar : [fieldDef.envVar];
225
+ for (const envVar of envVars) {
226
+ const value = process.env[envVar];
227
+ if (value != null && value !== '')
228
+ return value;
229
+ }
230
+ return undefined;
282
231
  }
283
232
  /**
284
- * Resets global configuration instance. The default instance holds configuration based on env vars,
285
- * if we want to change them, we need to first reset the global state. Used mainly for testing purposes.
233
+ * Loads config options from crawlee.json in the current working directory.
286
234
  */
287
- static resetGlobalState() {
288
- delete this.globalConfig;
289
- }
290
- buildOptions(options) {
291
- // try to load configuration from crawlee.json as the baseline
292
- const path = join(process.cwd(), 'crawlee.json');
293
- if (pathExistsSync(path)) {
294
- try {
295
- const file = readFileSync(path);
296
- const optionsFromFileConfig = JSON.parse(file.toString());
297
- Object.assign(options, optionsFromFileConfig);
298
- }
299
- catch {
300
- // ignore
301
- }
235
+ static loadFileOptions() {
236
+ try {
237
+ const file = readFileSync(join(process.cwd(), 'crawlee.json'));
238
+ return JSON.parse(file.toString());
239
+ }
240
+ catch {
241
+ return {};
302
242
  }
303
- this.options = new Map(entries(options));
304
243
  }
305
244
  }
306
- //# sourceMappingURL=configuration.js.map
package/cookie_utils.d.ts CHANGED
@@ -7,7 +7,7 @@ export interface ResponseLike {
7
7
  /**
8
8
  * @internal
9
9
  */
10
- export declare function getCookiesFromResponse(response: ResponseLike): Cookie[];
10
+ export declare function getCookiesFromResponse(response: Response): Cookie[];
11
11
  /**
12
12
  * Calculate cookie expiration date
13
13
  * @param maxAgeSecs
@@ -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,19 +1,17 @@
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
6
6
  */
7
7
  export function getCookiesFromResponse(response) {
8
- const headers = typeof response.headers === 'function' ? response.headers() : response.headers;
9
- const cookieHeader = headers?.['set-cookie'] || '';
8
+ const headers = response.headers;
9
+ const cookieHeaders = headers.getSetCookie();
10
10
  try {
11
- return Array.isArray(cookieHeader)
12
- ? cookieHeader.map((cookie) => Cookie.parse(cookie))
13
- : [Cookie.parse(cookieHeader)];
11
+ return cookieHeaders.map((cookie) => Cookie.parse(cookie));
14
12
  }
15
13
  catch (e) {
16
- throw new CookieParseError(cookieHeader);
14
+ throw new CookieParseError(cookieHeaders);
17
15
  }
18
16
  }
19
17
  /**
@@ -49,19 +47,25 @@ export function toughCookieToBrowserPoolCookie(toughCookie) {
49
47
  /**
50
48
  * Transforms browser-pool cookie to tough-cookie.
51
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).
52
52
  * @internal
53
53
  */
54
54
  export function browserPoolCookieToToughCookie(cookieObject, maxAgeSecs) {
55
55
  const isExpiresValid = cookieObject.expires && typeof cookieObject.expires === 'number' && cookieObject.expires > 0;
56
- const expires = isExpiresValid
57
- ? new Date(cookieObject.expires * 1000)
58
- : 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
+ }
59
63
  const domainHasLeadingDot = cookieObject.domain?.startsWith?.('.');
60
64
  const domain = domainHasLeadingDot ? cookieObject.domain?.slice?.(1) : cookieObject.domain;
61
65
  return new Cookie({
62
66
  key: cookieObject.name,
63
67
  value: cookieObject.value,
64
- expires,
68
+ ...(expires !== undefined && { expires }),
65
69
  domain,
66
70
  path: cookieObject.path,
67
71
  secure: cookieObject.secure,
@@ -99,15 +103,18 @@ export function mergeCookies(url, sourceCookies) {
99
103
  if (!cookieString)
100
104
  continue;
101
105
  const cookie = Cookie.parse(cookieString);
106
+ if (!cookie)
107
+ throw new CookieParseError(cookieString);
102
108
  const similarKeyCookie = jar.getCookiesSync(url).find((c) => {
103
109
  return cookie.key !== c.key && cookie.key.toLowerCase() === c.key.toLowerCase();
104
110
  });
105
111
  if (similarKeyCookie) {
106
- log.warningOnce(`Found cookies with similar name during cookie merging: '${cookie.key}' and '${similarKeyCookie.key}'`);
112
+ serviceLocator
113
+ .getLogger()
114
+ .warningOnce(`Found cookies with similar name during cookie merging: '${cookie.key}' and '${similarKeyCookie.key}'`);
107
115
  }
108
116
  jar.setCookieSync(cookie, url);
109
117
  }
110
118
  }
111
119
  return jar.getCookieStringSync(url);
112
120
  }
113
- //# sourceMappingURL=cookie_utils.js.map
@@ -0,0 +1,70 @@
1
+ import type { Awaitable } from '@crawlee/types';
2
+ /**
3
+ * Represents a middleware step in the context pipeline.
4
+ *
5
+ * @template TCrawlingContext - The input context type for this middleware
6
+ * @template TCrawlingContextExtension - The enhanced output context type
7
+ */
8
+ export interface ContextMiddleware<TCrawlingContext, TCrawlingContextExtension> {
9
+ /** The main middleware function that enhances the context */
10
+ action: (context: TCrawlingContext) => Awaitable<TCrawlingContextExtension>;
11
+ /** Optional cleanup function called after the consumer finishes or fails */
12
+ cleanup?: (context: TCrawlingContext & TCrawlingContextExtension, error?: unknown) => Awaitable<void>;
13
+ }
14
+ /**
15
+ * Encapsulates the logic of gradually enhancing the crawling context with additional information and utilities.
16
+ *
17
+ * The enhancement is done by a chain of middlewares that are added to the pipeline after its creation.
18
+ * This class provides a type-safe way to build a pipeline of context transformations where each step
19
+ * can enhance the context with additional properties or utilities.
20
+ *
21
+ * @template TContextBase - The base context type that serves as the starting point
22
+ * @template TCrawlingContext - The final context type after all middleware transformations
23
+ */
24
+ export declare abstract class ContextPipeline<TContextBase, TCrawlingContext extends TContextBase> {
25
+ /**
26
+ * Creates a new empty context pipeline.
27
+ *
28
+ * @template TContextBase - The base context type for the pipeline
29
+ * @returns A new ContextPipeline instance with no transformations
30
+ */
31
+ static create<TContextBase>(): ContextPipeline<TContextBase, TContextBase>;
32
+ /**
33
+ * Adds a middleware to the pipeline, creating a new pipeline instance.
34
+ *
35
+ * This method provides a fluent interface for building context transformation pipelines.
36
+ * Each middleware can enhance the context with additional properties or utilities.
37
+ *
38
+ * @template TCrawlingContextExtension - The enhanced context type produced by this middleware
39
+ * @param middleware - The middleware to add to the pipeline
40
+ * @returns A new ContextPipeline instance with the added middleware
41
+ */
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>;
53
+ /**
54
+ * Executes the middleware pipeline and passes the final context to a consumer function.
55
+ *
56
+ * This method runs the crawling context through the entire middleware chain, enhancing it
57
+ * at each step, and then passes the final enhanced context to the provided consumer function.
58
+ * Proper cleanup is performed even if exceptions occur during processing.
59
+ *
60
+ * @param crawlingContext - The initial context to process through the pipeline
61
+ * @param finalContextConsumer - The function that will receive the final enhanced context
62
+ *
63
+ * @throws {ContextPipelineInitializationError} When a middleware fails during initialization
64
+ * @throws {ContextPipelineInterruptedError} When the pipeline is intentionally interrupted during initialization
65
+ * @throws {RequestHandlerError} When the final context consumer throws an exception
66
+ * @throws {ContextPipelineCleanupError} When cleanup operations fail
67
+ * @throws {SessionError} Session errors are re-thrown as-is for special handling
68
+ */
69
+ abstract call(crawlingContext: TContextBase, finalContextConsumer: (finalContext: TCrawlingContext) => Awaitable<unknown>): Promise<void>;
70
+ }