@crawlee/core 4.0.0-beta.15 → 4.0.0-beta.150

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 (287) hide show
  1. package/README.md +14 -14
  2. package/autoscaling/autoscaled_pool.d.ts +73 -177
  3. package/autoscaling/autoscaled_pool.js +182 -329
  4. package/autoscaling/concurrency_system.d.ts +268 -0
  5. package/autoscaling/concurrency_system.js +364 -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 +98 -0
  13. package/autoscaling/load_signal.js +103 -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 -126
  33. package/crawlers/crawler_commons.js +1 -108
  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 -64
  43. package/crawlers/statistics.js +354 -165
  44. package/debug.d.ts +36 -0
  45. package/debug.js +70 -0
  46. package/enqueue_links/enqueue_links.d.ts +61 -154
  47. package/enqueue_links/enqueue_links.js +40 -232
  48. package/enqueue_links/index.d.ts +0 -1
  49. package/enqueue_links/index.js +0 -1
  50. package/enqueue_links/shared.d.ts +55 -28
  51. package/enqueue_links/shared.js +116 -69
  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 -5
  63. package/index.js +8 -4
  64. package/iterables.d.ts +79 -0
  65. package/iterables.js +134 -0
  66. package/log.d.ts +77 -1
  67. package/log.js +104 -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 +44 -0
  73. package/memory-storage/memory-storage.js +160 -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 +106 -0
  78. package/memory-storage/resource-clients/key-value-store.d.ts +63 -0
  79. package/memory-storage/resource-clients/key-value-store.js +199 -0
  80. package/memory-storage/resource-clients/request-queue.d.ts +77 -0
  81. package/memory-storage/resource-clients/request-queue.js +407 -0
  82. package/memory-storage/utils.d.ts +16 -0
  83. package/memory-storage/utils.js +41 -0
  84. package/owned_or_injected.d.ts +58 -0
  85. package/owned_or_injected.js +98 -0
  86. package/package.json +13 -12
  87. package/proxy_configuration.d.ts +22 -128
  88. package/proxy_configuration.js +32 -144
  89. package/recoverable_state.d.ts +83 -51
  90. package/recoverable_state.js +163 -72
  91. package/request.d.ts +57 -16
  92. package/request.js +130 -69
  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 +39 -89
  108. package/session_pool/session.js +102 -159
  109. package/session_pool/session_pool.d.ts +67 -91
  110. package/session_pool/session_pool.js +196 -187
  111. package/storages/batched_adds.d.ts +37 -0
  112. package/storages/batched_adds.js +73 -0
  113. package/storages/dataset.d.ts +109 -56
  114. package/storages/dataset.js +284 -149
  115. package/storages/index.d.ts +9 -9
  116. package/storages/index.js +7 -9
  117. package/storages/key_value_store.d.ts +183 -48
  118. package/storages/key_value_store.js +444 -171
  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 +53 -115
  124. package/storages/request_list.js +194 -167
  125. package/storages/request_loader.d.ts +133 -0
  126. package/storages/request_loader.js +36 -0
  127. package/storages/request_manager.d.ts +109 -0
  128. package/storages/request_manager.js +1 -0
  129. package/storages/request_manager_tandem.d.ts +55 -23
  130. package/storages/request_manager_tandem.js +139 -57
  131. package/storages/request_queue.d.ts +286 -48
  132. package/storages/request_queue.js +757 -218
  133. package/storages/{sitemap_request_list.d.ts → sitemap_request_loader.d.ts} +43 -91
  134. package/storages/sitemap_request_loader.js +439 -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 +222 -0
  140. package/storages/throttling_request_manager.js +780 -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 -85
  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/recoverable_state.d.ts.map +0 -1
  226. package/recoverable_state.js.map +0 -1
  227. package/request.d.ts.map +0 -1
  228. package/request.js.map +0 -1
  229. package/router.d.ts.map +0 -1
  230. package/router.js.map +0 -1
  231. package/serialization.d.ts.map +0 -1
  232. package/serialization.js.map +0 -1
  233. package/session_pool/consts.d.ts.map +0 -1
  234. package/session_pool/consts.js.map +0 -1
  235. package/session_pool/errors.d.ts.map +0 -1
  236. package/session_pool/errors.js.map +0 -1
  237. package/session_pool/events.d.ts +0 -3
  238. package/session_pool/events.d.ts.map +0 -1
  239. package/session_pool/events.js +0 -3
  240. package/session_pool/events.js.map +0 -1
  241. package/session_pool/index.d.ts.map +0 -1
  242. package/session_pool/index.js.map +0 -1
  243. package/session_pool/session.d.ts.map +0 -1
  244. package/session_pool/session.js.map +0 -1
  245. package/session_pool/session_pool.d.ts.map +0 -1
  246. package/session_pool/session_pool.js.map +0 -1
  247. package/storages/access_checking.d.ts +0 -13
  248. package/storages/access_checking.d.ts.map +0 -1
  249. package/storages/access_checking.js +0 -14
  250. package/storages/access_checking.js.map +0 -1
  251. package/storages/dataset.d.ts.map +0 -1
  252. package/storages/dataset.js.map +0 -1
  253. package/storages/index.d.ts.map +0 -1
  254. package/storages/index.js.map +0 -1
  255. package/storages/key_value_store.d.ts.map +0 -1
  256. package/storages/key_value_store.js.map +0 -1
  257. package/storages/request_list.d.ts.map +0 -1
  258. package/storages/request_list.js.map +0 -1
  259. package/storages/request_list_adapter.d.ts +0 -58
  260. package/storages/request_list_adapter.d.ts.map +0 -1
  261. package/storages/request_list_adapter.js +0 -81
  262. package/storages/request_list_adapter.js.map +0 -1
  263. package/storages/request_manager_tandem.d.ts.map +0 -1
  264. package/storages/request_manager_tandem.js.map +0 -1
  265. package/storages/request_provider.d.ts +0 -371
  266. package/storages/request_provider.d.ts.map +0 -1
  267. package/storages/request_provider.js +0 -585
  268. package/storages/request_provider.js.map +0 -1
  269. package/storages/request_queue.d.ts.map +0 -1
  270. package/storages/request_queue.js.map +0 -1
  271. package/storages/request_queue_v2.d.ts +0 -87
  272. package/storages/request_queue_v2.d.ts.map +0 -1
  273. package/storages/request_queue_v2.js +0 -438
  274. package/storages/request_queue_v2.js.map +0 -1
  275. package/storages/sitemap_request_list.d.ts.map +0 -1
  276. package/storages/sitemap_request_list.js +0 -430
  277. package/storages/sitemap_request_list.js.map +0 -1
  278. package/storages/storage_manager.d.ts +0 -58
  279. package/storages/storage_manager.d.ts.map +0 -1
  280. package/storages/storage_manager.js +0 -105
  281. package/storages/storage_manager.js.map +0 -1
  282. package/storages/utils.d.ts.map +0 -1
  283. package/storages/utils.js.map +0 -1
  284. package/typedefs.d.ts.map +0 -1
  285. package/typedefs.js.map +0 -1
  286. package/validators.d.ts.map +0 -1
  287. package/validators.js.map +0 -1
@@ -0,0 +1,133 @@
1
+ import type { Dictionary } from '@crawlee/types';
2
+ import type { Request } from '../request.js';
3
+ import type { IRequestManager } from './request_manager.js';
4
+ import type { RequestQueueOperationInfo } from './request_queue.js';
5
+ /**
6
+ * A request source's own availability, in a single answer.
7
+ *
8
+ * - `ready` — the next {@link IRequestLoader.fetchNextRequest} is expected to hand something over.
9
+ * - `waiting` — nothing to fetch right now, but the source is not done: requests are in progress, are being
10
+ * added in the background, or are held back until `readyAt`.
11
+ * - `stalled` — the source holds requests it cannot make progress on. Only a manager that paces its own
12
+ * dispatch can reach this; see {@link ThrottlingRequestManager}.
13
+ * - `finished` — everything has been handled.
14
+ */
15
+ export type RequestSourceStatus = {
16
+ status: 'ready';
17
+ } | {
18
+ status: 'waiting';
19
+ /**
20
+ * A `Date.now()` timestamp at which the source expects to become `ready`. Absent when the wait has
21
+ * no clock (an in-progress request, a background add), leaving a consumer to poll.
22
+ */
23
+ readyAt?: number;
24
+ } | {
25
+ status: 'stalled';
26
+ reason: string;
27
+ } | {
28
+ status: 'finished';
29
+ };
30
+ /** Loaders never stall — only a manager that paces its own dispatch can. */
31
+ export type RequestLoaderStatus = Exclude<RequestSourceStatus, {
32
+ status: 'stalled';
33
+ }>;
34
+ /**
35
+ * Combines two request sources' statuses, with the precedence `ready` > `stalled` > `waiting` > `finished`.
36
+ *
37
+ * Binary rather than variadic on purpose: it is on the task loop's probe path and folding a pair allocates
38
+ * nothing.
39
+ *
40
+ * @internal
41
+ */
42
+ export declare function joinRequestSourceStatuses(a: RequestSourceStatus, b: RequestSourceStatus): RequestSourceStatus;
43
+ /**
44
+ * An abstract interface defining a read-only stream of requests to crawl.
45
+ *
46
+ * Request loaders are used to manage and provide access to a storage of crawling requests.
47
+ *
48
+ * Key responsibilities:
49
+ * - Fetching the next request to be processed.
50
+ * - Marking requests as handled once they are no longer in progress.
51
+ * - Managing state information such as the total and handled request counts.
52
+ *
53
+ * ## Request lifecycle contract
54
+ *
55
+ * Every request returned by {@link IRequestLoader.fetchNextRequest} is considered **in progress**
56
+ * until it is passed to {@link IRequestLoader.markRequestAsHandled}. Once you fetch a request, you are
57
+ * obligated to eventually mark it as handled — there is no way to hand a request back to a loader
58
+ * (only an {@link IRequestManager} can reclaim requests for a retry). "Handled" therefore means
59
+ * "finished with this request", whether processing succeeded or was abandoned after exhausting retries.
60
+ *
61
+ * Honoring this contract matters for three reasons:
62
+ * - **Restarts and migrations:** loaders that persist their state (see {@link IRequestLoader.persistState})
63
+ * treat in-progress requests as interrupted and re-serve them after a restart. A request that is fetched
64
+ * but never marked handled will be crawled again.
65
+ * - **Termination detection:** {@link IRequestLoader.checkReadiness} only reports `finished` once nothing is
66
+ * in progress. Leaving a request unmarked keeps the crawler running indefinitely.
67
+ * - **Bookkeeping:** the handled and pending counts are derived from the set of in-progress requests, so
68
+ * skipping {@link IRequestLoader.markRequestAsHandled} corrupts {@link IRequestLoader.getHandledCount}
69
+ * and {@link IRequestLoader.getPendingCount}.
70
+ *
71
+ * Concrete implementations such as {@link RequestList} or {@link SitemapRequestLoader} build on this interface.
72
+ * The {@link IRequestManager} interface extends it with the capability to enqueue and reclaim requests.
73
+ */
74
+ export interface IRequestLoader {
75
+ /**
76
+ * Returns an approximation of the total number of requests in the loader (i.e. pending + handled).
77
+ */
78
+ getTotalCount(): Promise<number>;
79
+ /**
80
+ * Returns an approximation of the number of pending requests in the loader.
81
+ */
82
+ getPendingCount(): Promise<number>;
83
+ /**
84
+ * Returns the number of requests in the loader that have been handled.
85
+ */
86
+ getHandledCount(): Promise<number>;
87
+ /**
88
+ * Reports whether the loader has a request to hand over, is waiting on one, or is done — see
89
+ * {@link RequestSourceStatus}.
90
+ *
91
+ * A consumer's task loop is gated on this, so implementations MUST answer `ready` before evaluating
92
+ * anything else. `finished` may arrive late behind distributed storage, but it is never wrong.
93
+ */
94
+ checkReadiness(): Promise<RequestSourceStatus>;
95
+ /**
96
+ * Gets the next {@link Request} to process, or `null` if there are no more pending requests.
97
+ *
98
+ * The returned request is marked as **in progress** and remains so until it is passed to
99
+ * {@link IRequestLoader.markRequestAsHandled}. The caller is responsible for eventually marking
100
+ * every fetched request as handled; otherwise the loader never considers itself finished and the
101
+ * request may be re-served after a restart. See the request lifecycle contract on {@link IRequestLoader}.
102
+ */
103
+ fetchNextRequest<T extends Dictionary = Dictionary>(): Promise<Request<T> | null>;
104
+ /**
105
+ * Can be used to iterate over the loader instance in a `for await .. of` loop.
106
+ * Provides an alternative for the repeated use of `fetchNextRequest`.
107
+ */
108
+ [Symbol.asyncIterator](): AsyncGenerator<Request>;
109
+ /**
110
+ * Marks a request previously returned by {@link IRequestLoader.fetchNextRequest} as handled,
111
+ * removing it from the set of in-progress requests.
112
+ *
113
+ * Call this once you are done with the request — whether processing succeeded or was abandoned after
114
+ * exhausting retries. Because a loader cannot take a request back, marking it handled is the only way to
115
+ * signal completion; failing to do so prevents {@link IRequestLoader.checkReadiness} from ever reporting
116
+ * `finished` and skews the handled and pending counts. See the request lifecycle contract on
117
+ * {@link IRequestLoader}.
118
+ */
119
+ markRequestAsHandled(request: Request): Promise<RequestQueueOperationInfo | void | null>;
120
+ /**
121
+ * Persists the current state of the loader into the default {@link KeyValueStore}.
122
+ *
123
+ * Not all loaders support persistence; implementations that do not should leave this `undefined`.
124
+ */
125
+ persistState?(): Promise<void>;
126
+ /**
127
+ * Combines the loader with a request manager to support adding and reclaiming requests.
128
+ *
129
+ * @param requestManager Request manager to combine the loader with. If not provided, the default
130
+ * {@link RequestQueue} is used.
131
+ */
132
+ toTandem?(requestManager?: IRequestManager): Promise<IRequestManager>;
133
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Combines two request sources' statuses, with the precedence `ready` > `stalled` > `waiting` > `finished`.
3
+ *
4
+ * Binary rather than variadic on purpose: it is on the task loop's probe path and folding a pair allocates
5
+ * nothing.
6
+ *
7
+ * @internal
8
+ */
9
+ export function joinRequestSourceStatuses(a, b) {
10
+ if (a.status === 'ready') {
11
+ return a;
12
+ }
13
+ if (b.status === 'ready') {
14
+ return b;
15
+ }
16
+ // `ready` outranking `stalled` masks a stalled source while the other still has work. That is parity with
17
+ // the crawler before this was a single answer: the stall check was only reached from `isFinishedFunction`,
18
+ // which the task loop calls only when nothing is in flight and nothing is ready. "Fixing" the masking
19
+ // turns a crawl that is progressing elsewhere into a `PersistentRateLimitError`. `stalled` outranking
20
+ // `waiting` is the same parity - that call site fired regardless of other domains' clocks.
21
+ if (a.status === 'stalled') {
22
+ return a;
23
+ }
24
+ if (b.status === 'stalled') {
25
+ return b;
26
+ }
27
+ if (a.status === 'waiting') {
28
+ // The earlier of the two known wake-up times - unknown only if neither source announced one.
29
+ if (b.status !== 'waiting' || b.readyAt === undefined) {
30
+ return a;
31
+ }
32
+ return a.readyAt !== undefined && a.readyAt <= b.readyAt ? a : b;
33
+ }
34
+ // `a` is finished, so `b` decides.
35
+ return b;
36
+ }
@@ -0,0 +1,109 @@
1
+ import type { LiteralUnion } from 'type-fest';
2
+ import type { Request, Source } from '../request.js';
3
+ import type { IRequestLoader } from './request_loader.js';
4
+ import type { AddRequestsBatchedOptions, AddRequestsBatchedResult, RequestQueueOperationInfo, RequestQueueOperationOptions } from './request_queue.js';
5
+ export type RequestsLike = AsyncIterable<Source | string> | Iterable<Source | string> | (Source | string)[];
6
+ /**
7
+ * Extends the read-only {@link IRequestLoader} interface with the capability to enqueue new requests
8
+ * and reclaim failed ones.
9
+ */
10
+ export interface IRequestManager extends IRequestLoader {
11
+ /**
12
+ * Reclaims request to the provider if its processing failed.
13
+ * The request will be returned by some subsequent `fetchNextRequest()` call.
14
+ */
15
+ reclaimRequest(request: Request, options?: RequestQueueOperationOptions): Promise<RequestQueueOperationInfo | null>;
16
+ addRequest(requestLike: Source, options?: RequestQueueOperationOptions): Promise<RequestQueueOperationInfo>;
17
+ addRequestsBatched(requests: RequestsLike, options?: AddRequestsBatchedOptions): Promise<AddRequestsBatchedResult>;
18
+ /**
19
+ * Remove all requests from the queue but keep the queue itself, resetting it
20
+ * so it can be reused (e.g. across multiple `crawler.run()` calls).
21
+ *
22
+ * Implementations that do not support purging may leave this `undefined`.
23
+ */
24
+ purge?(): Promise<void>;
25
+ /**
26
+ * Tells the manager how long a consumer expects to hold a request fetched via `fetchNextRequest()`
27
+ * before marking it handled or reclaiming it (typically the request-handler timeout plus padding).
28
+ *
29
+ * Managers backed by a storage backend that reserves requests via locking use this to avoid handing
30
+ * the same request out again while it is still being processed. Implementations that do not need
31
+ * this hint may leave it `undefined`.
32
+ */
33
+ setExpectedRequestProcessingTimeSecs?(secs: number): Promise<void>;
34
+ /**
35
+ * Records something said about the pace requests should go out at, so that a manager which paces its own
36
+ * dispatch can hold requests back.
37
+ *
38
+ * Required rather than optional, so that a wrapping manager always forwards it and a pacer nested in a
39
+ * composition still receives it; a manager that does not pace returns `false`.
40
+ *
41
+ * @returns `true` if anything in the composition took responsibility for the signal.
42
+ */
43
+ recordPacingSignal(signal: PacingSignal): boolean;
44
+ }
45
+ /**
46
+ * How much of the URL space a {@link PacingSignal} covers.
47
+ *
48
+ * Open on purpose: `'hostname'` and `'registrableDomain'` are what Crawlee's own reporters send and what
49
+ * {@link ThrottlingRequestManager} understands, but any string is accepted, so a pacer keyed on something
50
+ * else can be reported to in its own vocabulary.
51
+ */
52
+ export type PacingScope = LiteralUnion<'hostname' | 'registrableDomain', string>;
53
+ /**
54
+ * Something said about the pace requests should go out at, reported to a request manager through
55
+ * {@link IRequestManager.recordPacingSignal}.
56
+ *
57
+ * One shape rather than a method per channel: a pacing manager switches on `reason`, a wrapping one forwards the
58
+ * value without knowing what is in it, and a new kind of signal costs the interface nothing. The `url` travels
59
+ * inside the value because the crawl-wide variant has none. Nothing here names the mechanism a signal came
60
+ * from - status codes, headers and robots.txt are the crawler's business - and every delay is in milliseconds.
61
+ *
62
+ * ## Scope
63
+ *
64
+ * A manager may apply a signal to a **wider** scope than it was given - a floor that holds for one host still
65
+ * holds when a whole site is paced by it - but never to a narrower one, which would leave some of the URLs the
66
+ * signal covers running unpaced. A manager that can only do the latter, or that does not recognise the scope at
67
+ * all, MUST throw rather than quietly under-apply it.
68
+ */
69
+ export type PacingSignal = {
70
+ /**
71
+ * The source turned a request away because we were going too fast — an HTTP 429 or 503, an exhausted
72
+ * quota. Reactive and transient: a pacer typically backs off while refusals continue, and lets that
73
+ * decay once they stop.
74
+ */
75
+ reason: 'rateLimited';
76
+ /** The URL that was turned away. */
77
+ url: string;
78
+ /** How long the source asked us to wait before trying again, if it said. */
79
+ waitMs?: number;
80
+ /**
81
+ * How far this refusal reaches, if the reporter can tell — a 429 rarely says. Left out, it asks the
82
+ * manager to apply the signal however it happens to group requests.
83
+ */
84
+ scope?: PacingScope;
85
+ } | {
86
+ /**
87
+ * The source declared a standing floor on how often it may be requested — a robots.txt `Crawl-delay`,
88
+ * a documented quota. A property of the source rather than of the run, so a pacer keeps it for the
89
+ * whole crawl.
90
+ */
91
+ reason: 'minInterval';
92
+ /** A URL of the source that declared the interval. */
93
+ url: string;
94
+ /** The declared minimum interval between two requests to the source. */
95
+ intervalMs: number;
96
+ /** Required, since whoever declares an interval knows what it applies to. */
97
+ scope: PacingScope;
98
+ } | {
99
+ /**
100
+ * A standing floor under the pace of **every** domain the manager dispatches to, declared by whoever
101
+ * owns the crawl rather than by a source — a crawler's `sameDomainDelaySecs`. A manager that paces only
102
+ * some of its domains MUST throw rather than under-apply it.
103
+ */
104
+ reason: 'minIntervalEverywhere';
105
+ /** The declared minimum interval between two requests to any one source. */
106
+ intervalMs: number;
107
+ /** At what granularity the floor applies. */
108
+ scope: PacingScope;
109
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,49 +1,58 @@
1
1
  import type { Dictionary } from '@crawlee/types';
2
2
  import type { Request, Source } from '../request.js';
3
- import type { IRequestList } from './request_list.js';
4
- import type { AddRequestsBatchedOptions, AddRequestsBatchedResult, IRequestManager, RequestQueueOperationInfo, RequestQueueOperationOptions, RequestsLike } from './request_provider.js';
3
+ import type { IRequestLoader, RequestSourceStatus } from './request_loader.js';
4
+ import type { IRequestManager, PacingSignal, RequestsLike } from './request_manager.js';
5
+ import type { AddRequestsBatchedOptions, AddRequestsBatchedResult, RequestQueueOperationInfo, RequestQueueOperationOptions } from './request_queue.js';
5
6
  /**
6
- * A request manager that combines a RequestList and a RequestQueue.
7
- * It first reads requests from the RequestList and then, when needed,
8
- * transfers them in batches to the RequestQueue.
7
+ * A request manager that combines a {@link IRequestLoader} (such as a `RequestList`) with a writable
8
+ * {@link IRequestManager} (such as a `RequestQueue`).
9
+ * It first reads requests from the loader and then, when needed, transfers them in batches to the manager.
9
10
  */
10
11
  export declare class RequestManagerTandem implements IRequestManager {
11
- private log;
12
- private requestList;
13
- private requestQueue;
14
- constructor(requestList: IRequestList, requestQueue: IRequestManager);
12
+ #private;
15
13
  /**
16
- * Transfers a batch of requests from the RequestList to the RequestQueue.
17
- * Handles both successful transfers and failures appropriately.
14
+ * @param requestLoader The read-only loader to read requests from first.
15
+ * @param requestManager The writable manager to transfer requests into and enqueue new ones. May be passed as a
16
+ * factory function so that the tandem can be constructed synchronously and the manager opened lazily on first use
17
+ * (e.g. a lazily-opened default {@link RequestQueue}).
18
+ */
19
+ constructor(requestLoader: IRequestLoader, requestManager: IRequestManager | (() => IRequestManager | Promise<IRequestManager>));
20
+ /**
21
+ * Resolves the writable request manager, opening it lazily (via the factory) on first use and memoizing the result.
18
22
  * @private
19
23
  */
20
- private transferNextBatchToQueue;
24
+ private getRequestManager;
21
25
  /**
22
- * Fetches the next request from the RequestQueue. If the queue is empty and the RequestList
23
- * is not finished, it will transfer a batch of requests from the list to the queue first.
24
- * @inheritdoc
26
+ * Transfers a single request from the read-only loader to the writable manager.
27
+ * If the transfer fails, the request is dropped (and logged) rather than reclaimed.
28
+ *
29
+ * @returns `true` if a request was successfully transferred (or there was nothing to transfer), and `false` if a
30
+ * transfer was attempted but failed - in which case the caller should not fetch from the manager this round.
31
+ * @private
25
32
  */
26
- fetchNextRequest<T extends Dictionary = Dictionary>(): Promise<Request<T> | null>;
33
+ private transferNextRequestToQueue;
27
34
  /**
35
+ * Fetches the next request, transferring one from the loader first if the loader still has work.
28
36
  * @inheritdoc
29
37
  */
30
- isFinished(): Promise<boolean>;
38
+ fetchNextRequest<T extends Dictionary = Dictionary>(): Promise<Request<T> | null>;
31
39
  /**
40
+ * The loader and the manager read as one source.
32
41
  * @inheritdoc
33
42
  */
34
- isEmpty(): Promise<boolean>;
43
+ checkReadiness(): Promise<RequestSourceStatus>;
35
44
  /**
36
45
  * @inheritdoc
37
46
  */
38
- handledCount(): Promise<number>;
47
+ getHandledCount(): Promise<number>;
39
48
  /**
40
49
  * @inheritdoc
41
50
  */
42
- getTotalCount(): number;
51
+ getTotalCount(): Promise<number>;
43
52
  /**
44
53
  * @inheritdoc
45
54
  */
46
- getPendingCount(): number;
55
+ getPendingCount(): Promise<number>;
47
56
  /**
48
57
  * @inheritdoc
49
58
  */
@@ -51,7 +60,7 @@ export declare class RequestManagerTandem implements IRequestManager {
51
60
  /**
52
61
  * @inheritdoc
53
62
  */
54
- markRequestHandled(request: Request): Promise<RequestQueueOperationInfo | void | null>;
63
+ markRequestAsHandled(request: Request): Promise<RequestQueueOperationInfo | void | null>;
55
64
  /**
56
65
  * @inheritdoc
57
66
  */
@@ -64,5 +73,28 @@ export declare class RequestManagerTandem implements IRequestManager {
64
73
  * @inheritdoc
65
74
  */
66
75
  addRequestsBatched(requests: RequestsLike, options?: AddRequestsBatchedOptions): Promise<AddRequestsBatchedResult>;
76
+ /**
77
+ * Persists the state of the underlying read-only loader, if it supports persistence.
78
+ * @inheritdoc
79
+ */
80
+ persistState(): Promise<void>;
81
+ /**
82
+ * Purges the writable request manager so the tandem can be reused (e.g. across repeated `crawler.run()` calls).
83
+ * The read-only loader is immutable and cannot be purged, so only the manager side is reset.
84
+ * @inheritdoc
85
+ */
86
+ purge(): Promise<void>;
87
+ /**
88
+ * Forwards the hint to the writable request manager — that is where requests are fetched from and
89
+ * reserved. The manager is opened lazily, so the value is remembered and applied once it resolves.
90
+ * @inheritdoc
91
+ */
92
+ setExpectedRequestProcessingTimeSecs(secs: number): Promise<void>;
93
+ /**
94
+ * Forwards a pacing signal to the writable manager - the loader side is read-only and dispatches nothing of
95
+ * its own. Only a resolved manager is signalled; the tandem will not open a queue to answer a question about
96
+ * pacing.
97
+ * @inheritdoc
98
+ */
99
+ recordPacingSignal(signal: PacingSignal): boolean;
67
100
  }
68
- //# sourceMappingURL=request_manager_tandem.d.ts.map
@@ -1,89 +1,139 @@
1
- import { log } from '../log.js';
1
+ import { serviceLocator } from '../service_locator.js';
2
+ import { joinRequestSourceStatuses } from './request_loader.js';
2
3
  /**
3
- * A request manager that combines a RequestList and a RequestQueue.
4
- * It first reads requests from the RequestList and then, when needed,
5
- * transfers them in batches to the RequestQueue.
4
+ * A request manager that combines a {@link IRequestLoader} (such as a `RequestList`) with a writable
5
+ * {@link IRequestManager} (such as a `RequestQueue`).
6
+ * It first reads requests from the loader and then, when needed, transfers them in batches to the manager.
6
7
  */
7
8
  export class RequestManagerTandem {
8
- log;
9
- requestList;
10
- requestQueue;
11
- constructor(requestList, requestQueue) {
12
- this.log = log.child({ prefix: 'RequestManagerTandem' });
13
- this.requestList = requestList;
14
- this.requestQueue = requestQueue;
9
+ #log;
10
+ #requestLoader;
11
+ #requestManagerPromise;
12
+ #resolvedRequestManager;
13
+ #requestManagerFactory;
14
+ /**
15
+ * The latest expected request-processing time hinted via {@link setExpectedRequestProcessingTimeSecs}.
16
+ * Remembered so it can be applied to the writable manager once it is lazily resolved.
17
+ */
18
+ #expectedRequestProcessingSecs;
19
+ /**
20
+ * @param requestLoader The read-only loader to read requests from first.
21
+ * @param requestManager The writable manager to transfer requests into and enqueue new ones. May be passed as a
22
+ * factory function so that the tandem can be constructed synchronously and the manager opened lazily on first use
23
+ * (e.g. a lazily-opened default {@link RequestQueue}).
24
+ */
25
+ constructor(requestLoader, requestManager) {
26
+ this.#log = serviceLocator.getLogger().child({ prefix: 'RequestManagerTandem' });
27
+ this.#requestLoader = requestLoader;
28
+ if (typeof requestManager === 'function') {
29
+ this.#requestManagerFactory = requestManager;
30
+ }
31
+ else {
32
+ // Nothing to open, so mark it resolved up front - synchronous pacing signals can then reach it.
33
+ this.#resolvedRequestManager = requestManager;
34
+ this.#requestManagerFactory = () => requestManager;
35
+ }
36
+ }
37
+ /**
38
+ * Resolves the writable request manager, opening it lazily (via the factory) on first use and memoizing the result.
39
+ * @private
40
+ */
41
+ async getRequestManager() {
42
+ if (this.#resolvedRequestManager === undefined) {
43
+ this.#requestManagerPromise ??= Promise.resolve(this.#requestManagerFactory());
44
+ this.#resolvedRequestManager = await this.#requestManagerPromise;
45
+ // Apply any hint received before the manager was resolved.
46
+ if (this.#expectedRequestProcessingSecs !== undefined) {
47
+ await this.#resolvedRequestManager.setExpectedRequestProcessingTimeSecs?.(this.#expectedRequestProcessingSecs);
48
+ }
49
+ }
50
+ return this.#resolvedRequestManager;
15
51
  }
16
52
  /**
17
- * Transfers a batch of requests from the RequestList to the RequestQueue.
18
- * Handles both successful transfers and failures appropriately.
53
+ * Transfers a single request from the read-only loader to the writable manager.
54
+ * If the transfer fails, the request is dropped (and logged) rather than reclaimed.
55
+ *
56
+ * @returns `true` if a request was successfully transferred (or there was nothing to transfer), and `false` if a
57
+ * transfer was attempted but failed - in which case the caller should not fetch from the manager this round.
19
58
  * @private
20
59
  */
21
- async transferNextBatchToQueue() {
22
- const request = await this.requestList.fetchNextRequest();
60
+ async transferNextRequestToQueue() {
61
+ const request = await this.#requestLoader.fetchNextRequest();
23
62
  if (request === null) {
24
- return;
63
+ return true;
25
64
  }
65
+ const requestManager = await this.getRequestManager();
26
66
  try {
27
- await this.requestQueue.addRequest(request, { forefront: true });
67
+ await requestManager.addRequest(request, { forefront: true });
68
+ return true;
28
69
  }
29
70
  catch (error) {
30
- // If requestQueue.addRequest() fails here then we must reclaim it back to
31
- // the RequestList because probably it's not yet in the queue!
32
- this.log.error('Adding of request from the RequestList to the RequestQueue failed, reclaiming request back to the list.', { request });
33
- await this.requestList.reclaimRequest(request);
34
- return;
71
+ this.#log.exception(error, 'Adding request from the RequestLoader to the RequestManager failed, the request has been dropped.', { url: request.url, uniqueKey: request.uniqueKey });
72
+ return false;
73
+ }
74
+ finally {
75
+ // Mark it as handled so that the request doesn't get stuck in the `inProgress` state in the loader.
76
+ await this.#requestLoader.markRequestAsHandled(request);
35
77
  }
36
- await this.requestList.markRequestHandled(request);
37
78
  }
38
79
  /**
39
- * Fetches the next request from the RequestQueue. If the queue is empty and the RequestList
40
- * is not finished, it will transfer a batch of requests from the list to the queue first.
80
+ * Fetches the next request, transferring one from the loader first if the loader still has work.
41
81
  * @inheritdoc
42
82
  */
43
83
  async fetchNextRequest() {
44
- // First, try to transfer a request from the requestList
45
- const [listEmpty, listFinished] = await Promise.all([
46
- this.requestList.isEmpty(),
47
- this.requestList.isFinished(),
48
- ]);
49
- if (!listEmpty && !listFinished) {
50
- await this.transferNextBatchToQueue();
84
+ // Only the loader's own state decides this: a manager waiting out a backoff must not freeze the
85
+ // loader's unrelated requests for the length of it.
86
+ if ((await this.#requestLoader.checkReadiness()).status === 'ready') {
87
+ // If the transfer failed, the request was dropped; don't fetch from the manager this round (matching
88
+ // crawlee-python behaviour). The next `fetchNextRequest()` call will pick up where we left off.
89
+ if (!(await this.transferNextRequestToQueue())) {
90
+ return null;
91
+ }
51
92
  }
52
- // Try to fetch from queue after potential transfer
53
- return this.requestQueue.fetchNextRequest();
93
+ // Try to fetch from manager after the transfer
94
+ return (await this.getRequestManager()).fetchNextRequest();
54
95
  }
55
96
  /**
97
+ * The loader and the manager read as one source.
56
98
  * @inheritdoc
57
99
  */
58
- async isFinished() {
59
- const storagesFinished = await Promise.all([this.requestList.isFinished(), this.requestQueue.isFinished()]);
60
- return storagesFinished.every(Boolean);
61
- }
62
- /**
63
- * @inheritdoc
64
- */
65
- async isEmpty() {
66
- const storagesEmpty = await Promise.all([this.requestList.isEmpty(), this.requestQueue.isEmpty()]);
67
- return storagesEmpty.every(Boolean);
100
+ async checkReadiness() {
101
+ const requestManager = await this.getRequestManager();
102
+ const [loaderStatus, managerStatus] = await Promise.all([
103
+ this.#requestLoader.checkReadiness(),
104
+ requestManager.checkReadiness(),
105
+ ]);
106
+ return joinRequestSourceStatuses(loaderStatus, managerStatus);
68
107
  }
69
108
  /**
70
109
  * @inheritdoc
71
110
  */
72
- async handledCount() {
73
- // Since one of the stores needs to have priority when both are present, we query the request queue - the request list will first be dumped into the queue and then left empty.
74
- return await this.requestQueue.handledCount();
111
+ async getHandledCount() {
112
+ // Since one of the stores needs to have priority when both are present, we query the request manager - the request loader will first be dumped into the manager and then left empty.
113
+ return (await this.getRequestManager()).getHandledCount();
75
114
  }
76
115
  /**
77
116
  * @inheritdoc
78
117
  */
79
- getTotalCount() {
80
- return this.requestQueue.getTotalCount();
118
+ async getTotalCount() {
119
+ const requestManager = await this.getRequestManager();
120
+ const [managerTotal, loaderTotal] = await Promise.all([
121
+ requestManager.getTotalCount(),
122
+ // count only pending to avoid double counting, requests marked as "handled" have been moved to requestManager
123
+ this.#requestLoader.getPendingCount(),
124
+ ]);
125
+ return managerTotal + loaderTotal;
81
126
  }
82
127
  /**
83
128
  * @inheritdoc
84
129
  */
85
- getPendingCount() {
86
- return this.requestQueue.getPendingCount() + this.requestList.length() - this.requestList.handledCount();
130
+ async getPendingCount() {
131
+ const requestManager = await this.getRequestManager();
132
+ const [managerPending, loaderPending] = await Promise.all([
133
+ requestManager.getPendingCount(),
134
+ this.#requestLoader.getPendingCount(),
135
+ ]);
136
+ return managerPending + loaderPending;
87
137
  }
88
138
  /**
89
139
  * @inheritdoc
@@ -99,26 +149,58 @@ export class RequestManagerTandem {
99
149
  /**
100
150
  * @inheritdoc
101
151
  */
102
- async markRequestHandled(request) {
103
- return this.requestQueue.markRequestHandled(request);
152
+ async markRequestAsHandled(request) {
153
+ return (await this.getRequestManager()).markRequestAsHandled(request);
104
154
  }
105
155
  /**
106
156
  * @inheritdoc
107
157
  */
108
158
  async reclaimRequest(request, options) {
109
- return await this.requestQueue.reclaimRequest(request, options);
159
+ return (await this.getRequestManager()).reclaimRequest(request, options);
110
160
  }
111
161
  /**
112
162
  * @inheritdoc
113
163
  */
114
164
  async addRequest(requestLike, options) {
115
- return await this.requestQueue.addRequest(requestLike, options);
165
+ return (await this.getRequestManager()).addRequest(requestLike, options);
116
166
  }
117
167
  /**
118
168
  * @inheritdoc
119
169
  */
120
170
  async addRequestsBatched(requests, options) {
121
- return await this.requestQueue.addRequestsBatched(requests, options);
171
+ return (await this.getRequestManager()).addRequestsBatched(requests, options);
172
+ }
173
+ /**
174
+ * Persists the state of the underlying read-only loader, if it supports persistence.
175
+ * @inheritdoc
176
+ */
177
+ async persistState() {
178
+ await this.#requestLoader.persistState?.();
179
+ }
180
+ /**
181
+ * Purges the writable request manager so the tandem can be reused (e.g. across repeated `crawler.run()` calls).
182
+ * The read-only loader is immutable and cannot be purged, so only the manager side is reset.
183
+ * @inheritdoc
184
+ */
185
+ async purge() {
186
+ await (await this.getRequestManager()).purge?.();
187
+ }
188
+ /**
189
+ * Forwards the hint to the writable request manager — that is where requests are fetched from and
190
+ * reserved. The manager is opened lazily, so the value is remembered and applied once it resolves.
191
+ * @inheritdoc
192
+ */
193
+ async setExpectedRequestProcessingTimeSecs(secs) {
194
+ this.#expectedRequestProcessingSecs = secs;
195
+ await this.#resolvedRequestManager?.setExpectedRequestProcessingTimeSecs?.(secs);
196
+ }
197
+ /**
198
+ * Forwards a pacing signal to the writable manager - the loader side is read-only and dispatches nothing of
199
+ * its own. Only a resolved manager is signalled; the tandem will not open a queue to answer a question about
200
+ * pacing.
201
+ * @inheritdoc
202
+ */
203
+ recordPacingSignal(signal) {
204
+ return this.#resolvedRequestManager?.recordPacingSignal(signal) ?? false;
122
205
  }
123
206
  }
124
- //# sourceMappingURL=request_manager_tandem.js.map