@crawlee/core 4.0.0-beta.17 → 4.0.0-beta.171

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 (270) hide show
  1. package/README.md +14 -14
  2. package/byte_utils.d.ts +17 -0
  3. package/byte_utils.js +42 -0
  4. package/configuration.d.ts +96 -223
  5. package/configuration.js +170 -222
  6. package/debug.d.ts +36 -0
  7. package/debug.js +70 -0
  8. package/errors.d.ts +40 -19
  9. package/errors.js +53 -28
  10. package/events/event_manager.d.ts +35 -9
  11. package/events/event_manager.js +12 -13
  12. package/events/index.d.ts +1 -1
  13. package/events/index.js +0 -1
  14. package/events/local_event_manager.d.ts +15 -3
  15. package/events/local_event_manager.js +39 -13
  16. package/events/system_info.d.ts +38 -0
  17. package/events/system_info.js +1 -0
  18. package/index.d.ts +7 -11
  19. package/index.js +8 -10
  20. package/internal.d.ts +8 -0
  21. package/internal.js +9 -0
  22. package/iterables.d.ts +79 -0
  23. package/iterables.js +134 -0
  24. package/log.d.ts +77 -1
  25. package/log.js +134 -1
  26. package/memory-storage/consts.d.ts +4 -0
  27. package/memory-storage/consts.js +4 -0
  28. package/memory-storage/index.d.ts +1 -0
  29. package/memory-storage/index.js +1 -0
  30. package/memory-storage/memory-storage.d.ts +44 -0
  31. package/memory-storage/memory-storage.js +160 -0
  32. package/memory-storage/resource-clients/common/base-client.d.ts +4 -0
  33. package/memory-storage/resource-clients/common/base-client.js +6 -0
  34. package/memory-storage/resource-clients/dataset.d.ts +40 -0
  35. package/memory-storage/resource-clients/dataset.js +106 -0
  36. package/memory-storage/resource-clients/key-value-store.d.ts +63 -0
  37. package/memory-storage/resource-clients/key-value-store.js +199 -0
  38. package/memory-storage/resource-clients/request-queue.d.ts +77 -0
  39. package/memory-storage/resource-clients/request-queue.js +414 -0
  40. package/memory-storage/utils.d.ts +16 -0
  41. package/memory-storage/utils.js +41 -0
  42. package/owned_or_injected.d.ts +58 -0
  43. package/owned_or_injected.js +98 -0
  44. package/package.json +18 -19
  45. package/proxy_configuration.d.ts +22 -128
  46. package/proxy_configuration.js +32 -144
  47. package/recoverable_state.d.ts +84 -51
  48. package/recoverable_state.js +175 -72
  49. package/request.d.ts +60 -17
  50. package/request.js +130 -69
  51. package/serialization.d.ts +0 -1
  52. package/serialization.js +15 -15
  53. package/service_locator.d.ts +165 -0
  54. package/service_locator.js +253 -0
  55. package/storages/batched_adds.d.ts +37 -0
  56. package/storages/batched_adds.js +73 -0
  57. package/storages/dataset.d.ts +114 -56
  58. package/storages/dataset.js +292 -149
  59. package/storages/index.d.ts +7 -9
  60. package/storages/index.js +5 -9
  61. package/storages/key_value_store.d.ts +186 -48
  62. package/storages/key_value_store.js +452 -170
  63. package/storages/key_value_store_codec.d.ts +32 -0
  64. package/storages/key_value_store_codec.js +113 -0
  65. package/storages/request_dedup_cache.d.ts +22 -0
  66. package/storages/request_dedup_cache.js +48 -0
  67. package/storages/request_list.d.ts +54 -129
  68. package/storages/request_list.js +272 -245
  69. package/storages/request_loader.d.ts +133 -0
  70. package/storages/request_loader.js +36 -0
  71. package/storages/request_manager.d.ts +109 -0
  72. package/storages/request_manager.js +1 -0
  73. package/storages/request_manager_tandem.d.ts +55 -23
  74. package/storages/request_manager_tandem.js +139 -57
  75. package/storages/request_queue.d.ts +286 -48
  76. package/storages/request_queue.js +757 -218
  77. package/storages/storage_instance_manager.d.ts +87 -0
  78. package/storages/storage_instance_manager.js +256 -0
  79. package/storages/storage_stats.d.ts +48 -0
  80. package/storages/storage_stats.js +29 -0
  81. package/storages/transaction.d.ts +260 -0
  82. package/storages/transaction.js +287 -0
  83. package/storages/utils.d.ts +59 -11
  84. package/storages/utils.js +75 -15
  85. package/system-info/cpu-info.d.ts +67 -0
  86. package/system-info/cpu-info.js +216 -0
  87. package/system-info/memory-info.d.ts +31 -0
  88. package/system-info/memory-info.js +115 -0
  89. package/system-info/ps-tree.d.ts +17 -0
  90. package/system-info/ps-tree.js +144 -0
  91. package/system-info/runtime.d.ts +14 -0
  92. package/system-info/runtime.js +80 -0
  93. package/typedefs.d.ts +0 -6
  94. package/typedefs.js +0 -1
  95. package/url.d.ts +9 -0
  96. package/url.js +11 -0
  97. package/validators.d.ts +23 -18
  98. package/validators.js +14 -18
  99. package/autoscaling/autoscaled_pool.d.ts +0 -300
  100. package/autoscaling/autoscaled_pool.d.ts.map +0 -1
  101. package/autoscaling/autoscaled_pool.js +0 -533
  102. package/autoscaling/autoscaled_pool.js.map +0 -1
  103. package/autoscaling/index.d.ts +0 -4
  104. package/autoscaling/index.d.ts.map +0 -1
  105. package/autoscaling/index.js +0 -4
  106. package/autoscaling/index.js.map +0 -1
  107. package/autoscaling/snapshotter.d.ts +0 -189
  108. package/autoscaling/snapshotter.d.ts.map +0 -1
  109. package/autoscaling/snapshotter.js +0 -285
  110. package/autoscaling/snapshotter.js.map +0 -1
  111. package/autoscaling/system_status.d.ts +0 -181
  112. package/autoscaling/system_status.d.ts.map +0 -1
  113. package/autoscaling/system_status.js +0 -169
  114. package/autoscaling/system_status.js.map +0 -1
  115. package/configuration.d.ts.map +0 -1
  116. package/configuration.js.map +0 -1
  117. package/cookie_utils.d.ts +0 -43
  118. package/cookie_utils.d.ts.map +0 -1
  119. package/cookie_utils.js +0 -111
  120. package/cookie_utils.js.map +0 -1
  121. package/crawlers/context_pipeline.d.ts +0 -61
  122. package/crawlers/context_pipeline.d.ts.map +0 -1
  123. package/crawlers/context_pipeline.js +0 -99
  124. package/crawlers/context_pipeline.js.map +0 -1
  125. package/crawlers/crawler_commons.d.ts +0 -195
  126. package/crawlers/crawler_commons.d.ts.map +0 -1
  127. package/crawlers/crawler_commons.js +0 -108
  128. package/crawlers/crawler_commons.js.map +0 -1
  129. package/crawlers/crawler_utils.d.ts +0 -10
  130. package/crawlers/crawler_utils.d.ts.map +0 -1
  131. package/crawlers/crawler_utils.js +0 -12
  132. package/crawlers/crawler_utils.js.map +0 -1
  133. package/crawlers/error_snapshotter.d.ts +0 -60
  134. package/crawlers/error_snapshotter.d.ts.map +0 -1
  135. package/crawlers/error_snapshotter.js +0 -118
  136. package/crawlers/error_snapshotter.js.map +0 -1
  137. package/crawlers/error_tracker.d.ts +0 -55
  138. package/crawlers/error_tracker.d.ts.map +0 -1
  139. package/crawlers/error_tracker.js +0 -309
  140. package/crawlers/error_tracker.js.map +0 -1
  141. package/crawlers/index.d.ts +0 -7
  142. package/crawlers/index.d.ts.map +0 -1
  143. package/crawlers/index.js +0 -7
  144. package/crawlers/index.js.map +0 -1
  145. package/crawlers/internals/types.d.ts +0 -8
  146. package/crawlers/internals/types.d.ts.map +0 -1
  147. package/crawlers/internals/types.js +0 -2
  148. package/crawlers/internals/types.js.map +0 -1
  149. package/crawlers/statistics.d.ts +0 -204
  150. package/crawlers/statistics.d.ts.map +0 -1
  151. package/crawlers/statistics.js +0 -346
  152. package/crawlers/statistics.js.map +0 -1
  153. package/enqueue_links/enqueue_links.d.ts +0 -249
  154. package/enqueue_links/enqueue_links.d.ts.map +0 -1
  155. package/enqueue_links/enqueue_links.js +0 -269
  156. package/enqueue_links/enqueue_links.js.map +0 -1
  157. package/enqueue_links/index.d.ts +0 -3
  158. package/enqueue_links/index.d.ts.map +0 -1
  159. package/enqueue_links/index.js +0 -3
  160. package/enqueue_links/index.js.map +0 -1
  161. package/enqueue_links/shared.d.ts +0 -73
  162. package/enqueue_links/shared.d.ts.map +0 -1
  163. package/enqueue_links/shared.js +0 -212
  164. package/enqueue_links/shared.js.map +0 -1
  165. package/errors.d.ts.map +0 -1
  166. package/errors.js.map +0 -1
  167. package/events/event_manager.d.ts.map +0 -1
  168. package/events/event_manager.js.map +0 -1
  169. package/events/index.d.ts.map +0 -1
  170. package/events/index.js.map +0 -1
  171. package/events/local_event_manager.d.ts.map +0 -1
  172. package/events/local_event_manager.js.map +0 -1
  173. package/http_clients/base-http-client.d.ts +0 -140
  174. package/http_clients/base-http-client.d.ts.map +0 -1
  175. package/http_clients/base-http-client.js +0 -40
  176. package/http_clients/base-http-client.js.map +0 -1
  177. package/http_clients/form-data-like.d.ts +0 -67
  178. package/http_clients/form-data-like.d.ts.map +0 -1
  179. package/http_clients/form-data-like.js +0 -5
  180. package/http_clients/form-data-like.js.map +0 -1
  181. package/http_clients/got-scraping-http-client.d.ts +0 -20
  182. package/http_clients/got-scraping-http-client.d.ts.map +0 -1
  183. package/http_clients/got-scraping-http-client.js +0 -85
  184. package/http_clients/got-scraping-http-client.js.map +0 -1
  185. package/http_clients/index.d.ts +0 -3
  186. package/http_clients/index.d.ts.map +0 -1
  187. package/http_clients/index.js +0 -3
  188. package/http_clients/index.js.map +0 -1
  189. package/index.d.ts.map +0 -1
  190. package/index.js.map +0 -1
  191. package/log.d.ts.map +0 -1
  192. package/log.js.map +0 -1
  193. package/proxy_configuration.d.ts.map +0 -1
  194. package/proxy_configuration.js.map +0 -1
  195. package/recoverable_state.d.ts.map +0 -1
  196. package/recoverable_state.js.map +0 -1
  197. package/request.d.ts.map +0 -1
  198. package/request.js.map +0 -1
  199. package/router.d.ts +0 -134
  200. package/router.d.ts.map +0 -1
  201. package/router.js +0 -164
  202. package/router.js.map +0 -1
  203. package/serialization.d.ts.map +0 -1
  204. package/serialization.js.map +0 -1
  205. package/session_pool/consts.d.ts +0 -4
  206. package/session_pool/consts.d.ts.map +0 -1
  207. package/session_pool/consts.js +0 -4
  208. package/session_pool/consts.js.map +0 -1
  209. package/session_pool/errors.d.ts +0 -8
  210. package/session_pool/errors.d.ts.map +0 -1
  211. package/session_pool/errors.js +0 -12
  212. package/session_pool/errors.js.map +0 -1
  213. package/session_pool/events.d.ts +0 -3
  214. package/session_pool/events.d.ts.map +0 -1
  215. package/session_pool/events.js +0 -3
  216. package/session_pool/events.js.map +0 -1
  217. package/session_pool/index.d.ts +0 -6
  218. package/session_pool/index.d.ts.map +0 -1
  219. package/session_pool/index.js +0 -6
  220. package/session_pool/index.js.map +0 -1
  221. package/session_pool/session.d.ts +0 -203
  222. package/session_pool/session.d.ts.map +0 -1
  223. package/session_pool/session.js +0 -278
  224. package/session_pool/session.js.map +0 -1
  225. package/session_pool/session_pool.d.ts +0 -258
  226. package/session_pool/session_pool.d.ts.map +0 -1
  227. package/session_pool/session_pool.js +0 -387
  228. package/session_pool/session_pool.js.map +0 -1
  229. package/storages/access_checking.d.ts +0 -13
  230. package/storages/access_checking.d.ts.map +0 -1
  231. package/storages/access_checking.js +0 -14
  232. package/storages/access_checking.js.map +0 -1
  233. package/storages/dataset.d.ts.map +0 -1
  234. package/storages/dataset.js.map +0 -1
  235. package/storages/index.d.ts.map +0 -1
  236. package/storages/index.js.map +0 -1
  237. package/storages/key_value_store.d.ts.map +0 -1
  238. package/storages/key_value_store.js.map +0 -1
  239. package/storages/request_list.d.ts.map +0 -1
  240. package/storages/request_list.js.map +0 -1
  241. package/storages/request_list_adapter.d.ts +0 -58
  242. package/storages/request_list_adapter.d.ts.map +0 -1
  243. package/storages/request_list_adapter.js +0 -81
  244. package/storages/request_list_adapter.js.map +0 -1
  245. package/storages/request_manager_tandem.d.ts.map +0 -1
  246. package/storages/request_manager_tandem.js.map +0 -1
  247. package/storages/request_provider.d.ts +0 -371
  248. package/storages/request_provider.d.ts.map +0 -1
  249. package/storages/request_provider.js +0 -585
  250. package/storages/request_provider.js.map +0 -1
  251. package/storages/request_queue.d.ts.map +0 -1
  252. package/storages/request_queue.js.map +0 -1
  253. package/storages/request_queue_v2.d.ts +0 -87
  254. package/storages/request_queue_v2.d.ts.map +0 -1
  255. package/storages/request_queue_v2.js +0 -438
  256. package/storages/request_queue_v2.js.map +0 -1
  257. package/storages/sitemap_request_list.d.ts +0 -245
  258. package/storages/sitemap_request_list.d.ts.map +0 -1
  259. package/storages/sitemap_request_list.js +0 -430
  260. package/storages/sitemap_request_list.js.map +0 -1
  261. package/storages/storage_manager.d.ts +0 -58
  262. package/storages/storage_manager.d.ts.map +0 -1
  263. package/storages/storage_manager.js +0 -105
  264. package/storages/storage_manager.js.map +0 -1
  265. package/storages/utils.d.ts.map +0 -1
  266. package/storages/utils.js.map +0 -1
  267. package/typedefs.d.ts.map +0 -1
  268. package/typedefs.js.map +0 -1
  269. package/validators.d.ts.map +0 -1
  270. package/validators.js.map +0 -1
@@ -0,0 +1,287 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { addTimeoutToPromise, storage as timeoutStorage, tryCancel } from '@apify/timeout';
3
+ import { serviceLocator } from '../service_locator.js';
4
+ import { AfterCommitError, NonRetryableError } from '../errors.js';
5
+ const DEFAULT_STORAGE_WRITE_POLICY = { requestQueue: 'writeThrough' };
6
+ const DEFAULT_COMMIT_TIMEOUT_MILLIS = 300_000;
7
+ const transactionStorage = new AsyncLocalStorage();
8
+ const COMMIT_ORDER = ['keyValueStore', 'requestQueue', 'dataset'];
9
+ /**
10
+ * A storage transaction scoped to a request's lifecycle. Writes made through the storage frontends
11
+ * ({@link Dataset}, {@link KeyValueStore}, {@link RequestQueue}) while the transaction is active
12
+ * are recorded rather than applied; on {@link StorageTransaction.commit|`commit()`} they are replayed
13
+ * into real storage, on {@link StorageTransaction.rollback|`rollback()`} they are dropped. Reads consult
14
+ * the recorded writes first, so a handler sees its own writes.
15
+ *
16
+ * Create one with {@link createStorageTransaction} (explicit commit/rollback) or
17
+ * {@link withStorageTransaction} (scoped sugar). Crawlers open one automatically around every request
18
+ * handler unless `transactionalStorage: false` is set.
19
+ */
20
+ export class StorageTransaction {
21
+ /** The ordered, append-only journal — the source of truth for commit, introspection and reads. */
22
+ journal = [];
23
+ /** Per-storage-type write policy. */
24
+ policy;
25
+ #commitTimeoutMillis;
26
+ #state = 'open';
27
+ #disposed = false;
28
+ /** Callbacks registered via `afterCommit()`, run in registration order once a commit has been attempted. */
29
+ #afterCommitCallbacks = [];
30
+ /** @internal */
31
+ constructor(options = {}) {
32
+ this.policy = { ...DEFAULT_STORAGE_WRITE_POLICY, ...options.policy };
33
+ this.#commitTimeoutMillis = options.commitTimeoutMillis ?? DEFAULT_COMMIT_TIMEOUT_MILLIS;
34
+ }
35
+ get state() {
36
+ return this.#state;
37
+ }
38
+ /**
39
+ * `true` only while `state === 'open'`. This is the single predicate every storage operation
40
+ * consults — operations performed after the transaction is closed pass through to the real backend.
41
+ */
42
+ get isActive() {
43
+ return this.#state === 'open';
44
+ }
45
+ /** Runs `callback` with this transaction installed in the async context. */
46
+ async run(callback) {
47
+ return transactionStorage.run(this, async () => callback());
48
+ }
49
+ /**
50
+ * Records a write operation in the journal.
51
+ * @internal
52
+ */
53
+ recordJournalEntry(entry) {
54
+ if (!this.isActive) {
55
+ throw new Error(`Cannot record a journal entry on a transaction in the '${this.#state}' state`);
56
+ }
57
+ this.journal.push(entry);
58
+ }
59
+ /**
60
+ * Registers a callback to run once a commit has been attempted, whether it succeeded (`error` is
61
+ * `undefined`) or failed - the only point at which a deferred write can be reacted to where it was
62
+ * made. Callbacks run in registration order; the first to throw propagates, the rest do not run, and
63
+ * on a failed commit its error replaces the commit error. Not run on rollback: nothing was written.
64
+ */
65
+ afterCommit(callback) {
66
+ if (!this.isActive) {
67
+ throw new Error(`Cannot register a commit callback on a transaction in the '${this.#state}' state`);
68
+ }
69
+ this.#afterCommitCallbacks.push(callback);
70
+ }
71
+ /**
72
+ * Replays the journaled writes into real storage. A no-op unless the transaction is `open`.
73
+ *
74
+ * The transaction transitions to `committing` *before* anything is flushed, so a commit that throws
75
+ * partway lands in `failed` (never back in `open`) and subsequent storage operations pass through
76
+ * rather than recording into a dead transaction. Delivery is at-least-once — a commit that fails
77
+ * partway may have applied some of the writes already.
78
+ */
79
+ async commit() {
80
+ if (this.#state !== 'open') {
81
+ return;
82
+ }
83
+ this.#state = 'committing';
84
+ try {
85
+ // The replay re-drives the frontend write path, which checks for cancellation (`tryCancel`)
86
+ // on every operation - and `@apify/timeout` shares one `AbortController` across nested
87
+ // frames, so a request-handler timeout that already fired would abort the commit of a
88
+ // handler that succeeded. Hence a fresh timeout context, which also provides the time bound.
89
+ await timeoutStorage.exit(async () => addTimeoutToPromise(async () => this.flush(), this.#commitTimeoutMillis, `Committing the storage transaction timed out after ${this.#commitTimeoutMillis / 1000} seconds.`));
90
+ }
91
+ catch (error) {
92
+ this.#state = 'failed';
93
+ // Callbacks are handed an `Error`, so a backend that rejects with anything else gets wrapped.
94
+ const commitError = error instanceof Error ? error : new Error(String(error), { cause: error });
95
+ // A callback that throws here propagates instead: it was handed the failure and decided what
96
+ // the request should fail with.
97
+ await this.#runAfterCommitCallbacks(commitError);
98
+ throw commitError;
99
+ }
100
+ this.#state = 'committed';
101
+ try {
102
+ await this.#runAfterCommitCallbacks();
103
+ }
104
+ catch (error) {
105
+ throw error instanceof NonRetryableError ? error : new AfterCommitError(error);
106
+ }
107
+ }
108
+ /** Fresh timeout context for the same reason as the flush. */
109
+ async #runAfterCommitCallbacks(error) {
110
+ await timeoutStorage.exit(async () => {
111
+ for (const callback of this.#afterCommitCallbacks) {
112
+ await callback(error);
113
+ }
114
+ });
115
+ }
116
+ async flush() {
117
+ // Each participating frontend replays all of its buffered entries in one call. Frontends are
118
+ // ordered by storage type: key-value stores and request queues first (idempotent under retry),
119
+ // datasets last (not idempotent), minimizing the blast radius of a partial commit failure.
120
+ const groups = new Map();
121
+ for (const entry of this.journal) {
122
+ if (entry.type === 'requestQueue' && entry.writeThrough)
123
+ continue;
124
+ const group = groups.get(entry.participant);
125
+ if (group)
126
+ group.push(entry);
127
+ else
128
+ groups.set(entry.participant, [entry]);
129
+ }
130
+ // A participant only records entries of its own storage type, so the first entry determines
131
+ // the group's place in the commit order.
132
+ const orderedGroups = [...groups.values()].sort((a, b) => COMMIT_ORDER.indexOf(a[0].type) - COMMIT_ORDER.indexOf(b[0].type));
133
+ for (const entries of orderedGroups) {
134
+ await entries[0].participant.commitJournalEntries(entries);
135
+ }
136
+ }
137
+ /**
138
+ * Discards the journaled writes. A no-op unless the transaction is `open` — in particular, calling it
139
+ * after a successful `commit()` (which the crawler's error handling can legitimately do) does nothing
140
+ * and never throws.
141
+ */
142
+ rollback() {
143
+ if (this.#state !== 'open') {
144
+ return;
145
+ }
146
+ this.#state = 'rolledBack';
147
+ }
148
+ /**
149
+ * Releases the journal, the write-time snapshots it holds and any registered commit callbacks. Must
150
+ * be called for *every* terminal state, `failed` included. Idempotent, never throws, and does not
151
+ * change `state`. Any {@link StorageTransactionView} of this transaction is only valid until this
152
+ * is called.
153
+ */
154
+ dispose() {
155
+ if (this.#disposed) {
156
+ return;
157
+ }
158
+ if (this.#state === 'open') {
159
+ // Disposing an open transaction is an internal invariant violation - roll back first.
160
+ try {
161
+ serviceLocator
162
+ .getLogger()
163
+ .warning('Internal error: a storage transaction was disposed while still open; rolling it back.');
164
+ }
165
+ catch {
166
+ // Never throw from dispose.
167
+ }
168
+ this.rollback();
169
+ }
170
+ this.#disposed = true;
171
+ this.journal.length = 0;
172
+ this.#afterCommitCallbacks.length = 0;
173
+ }
174
+ get datasetItems() {
175
+ return this.journal.flatMap((entry) => entry.type === 'dataset' ? entry.items.map((item) => ({ item, datasetId: entry.storageId })) : []);
176
+ }
177
+ get enqueuedUrls() {
178
+ return this.journal.flatMap((entry) => entry.type === 'requestQueue' ? entry.requests.map(({ url, label }) => ({ url, label })) : []);
179
+ }
180
+ get keyValueStoreChanges() {
181
+ const result = {};
182
+ for (const entry of this.journal) {
183
+ if (entry.type !== 'keyValueStore')
184
+ continue;
185
+ result[entry.storageId] ??= {};
186
+ result[entry.storageId][entry.key] = { changedValue: entry.value, options: entry.options };
187
+ }
188
+ return result;
189
+ }
190
+ }
191
+ /**
192
+ * Opens a {@link StorageTransaction} without running anything yet. The caller owns the outcome:
193
+ * `run()`, then `commit()` or `rollback()`, and always `dispose()` when done. For the common
194
+ * open-run-commit flow, prefer {@link withStorageTransaction}.
195
+ */
196
+ export function createStorageTransaction(options = {}) {
197
+ return new StorageTransaction(options);
198
+ }
199
+ /**
200
+ * Runs `callback` inside a new {@link StorageTransaction}: storage writes made in the callback are
201
+ * committed when it returns and rolled back when it throws. If a transaction is already active in the
202
+ * current async context, it is reused and its outcome is left to its owner (and `options` are ignored)
203
+ * — there are no nested transaction semantics.
204
+ */
205
+ export async function withStorageTransaction(callback, options = {}) {
206
+ const existing = transactionStorage.getStore();
207
+ if (existing?.isActive) {
208
+ return callback(existing);
209
+ }
210
+ const transaction = createStorageTransaction(options);
211
+ try {
212
+ const result = await transaction.run(async () => callback(transaction));
213
+ await transaction.commit();
214
+ return result;
215
+ }
216
+ catch (error) {
217
+ transaction.rollback();
218
+ throw error;
219
+ }
220
+ finally {
221
+ transaction.dispose();
222
+ }
223
+ }
224
+ /**
225
+ * Runs `callback` outside of any storage transaction — the per-call-site escape hatch. Storage operations
226
+ * made inside it hit the real backend directly, are not rolled back, and operations that a transaction
227
+ * rejects (`drop`, stream-valued `setValue`, request queue internals, ...) are permitted.
228
+ */
229
+ export async function withDirectStorageAccess(callback) {
230
+ return transactionStorage.exit(async () => callback());
231
+ }
232
+ /**
233
+ * The per-operation hook consulted by every storage frontend method: performs the cancellation check
234
+ * that aborts storage operations when the request handler times out, and returns the active storage
235
+ * transaction. Returns `undefined` when there is no transaction in the async context *or* when it is no
236
+ * longer open — operations on a closed transaction deliberately pass through to the real backend.
237
+ * @internal
238
+ */
239
+ export function activeStorageTransaction() {
240
+ tryCancel();
241
+ const transaction = transactionStorage.getStore();
242
+ return transaction?.isActive ? transaction : undefined;
243
+ }
244
+ /**
245
+ * Returns the transaction installed in the current async context, regardless of its state. Used by the
246
+ * crawler to drive the outcome of the transaction it opened.
247
+ * @internal
248
+ */
249
+ export function currentStorageTransaction() {
250
+ return transactionStorage.getStore();
251
+ }
252
+ /**
253
+ * Captures a value at write time, so that later mutations of the caller's object affect neither the
254
+ * read-your-own-writes reads nor the commit replay. `structuredClone` for fidelity (`Date`, `Map`, `Set`,
255
+ * typed arrays, `undefined`); values it cannot handle fall back to the JSON round-trip the storage
256
+ * backends perform anyway.
257
+ * @internal
258
+ */
259
+ export function snapshotValue(value) {
260
+ try {
261
+ return structuredClone(value);
262
+ }
263
+ catch {
264
+ return JSON.parse(JSON.stringify(value));
265
+ }
266
+ }
267
+ /**
268
+ * The guard for operations that cannot be performed inside a storage transaction: throws when one is
269
+ * active, and performs the per-operation cancellation check either way.
270
+ * @internal
271
+ */
272
+ export function rejectOperationInTransaction(operation, reason = 'it cannot be rolled back.') {
273
+ if (activeStorageTransaction() === undefined) {
274
+ return;
275
+ }
276
+ throw operationRejectedInTransaction(operation, reason);
277
+ }
278
+ /**
279
+ * Builds the "operation not allowed in a transaction" error, for a call site that has already
280
+ * established a transaction is active and so wants to `throw` unconditionally.
281
+ * @internal
282
+ */
283
+ export function operationRejectedInTransaction(operation, reason = 'it cannot be rolled back.') {
284
+ return new Error(`${operation} cannot be used inside a storage transaction: ${reason} ` +
285
+ 'If you really need it, wrap the call in withDirectStorageAccess(() => ...) - operations ' +
286
+ 'performed there are applied immediately and are not rolled back.');
287
+ }
@@ -1,5 +1,7 @@
1
- import type { Dictionary, StorageClient } from '@crawlee/types';
1
+ import type { BaseHttpClient } from '@crawlee/http-client';
2
+ import type { Dictionary, StorageBackend } from '@crawlee/types';
2
3
  import { Configuration } from '../configuration.js';
4
+ import type { IProxyConfiguration } from '../proxy_configuration.js';
3
5
  /**
4
6
  * Options for purging default storage.
5
7
  */
@@ -8,18 +10,19 @@ interface PurgeDefaultStorageOptions {
8
10
  * If set to `true`, calling multiple times will only have effect at the first time.
9
11
  */
10
12
  onlyPurgeOnce?: boolean;
11
- config?: Configuration;
12
- client?: StorageClient;
13
+ configuration?: Configuration;
14
+ storageBackend?: StorageBackend;
13
15
  }
14
16
  /**
15
17
  * Cleans up the local storage folder (defaults to `./storage`) created when running code locally.
16
- * Purging will remove all the files in all storages except for INPUT.json in the default KV store.
18
+ * Purging empties the storages that belong to a single run — the default one and every alias-keyed one —
19
+ * keeping only INPUT.json in the default KV store. Named storages persist across runs and are not touched.
17
20
  *
18
21
  * Purging of storages is happening automatically when we run our crawler (or when we open some storage
19
22
  * explicitly, e.g. via `RequestList.open()`). We can disable that via `purgeOnStart` {@link Configuration}
20
23
  * option or by setting `CRAWLEE_PURGE_ON_START` environment variable to `0` or `false`.
21
24
  *
22
- * This is a shortcut for running (optional) `purge` method on the StorageClient interface, in other words
25
+ * This is a shortcut for running (optional) `purge` method on the StorageBackend interface, in other words
23
26
  * it will call the `purge` method of the underlying storage implementation we are currently using. You can
24
27
  * make sure the storage is purged only once for a given execution context if you set `onlyPurgeOnce` to `true` in
25
28
  * the `options` object
@@ -27,18 +30,19 @@ interface PurgeDefaultStorageOptions {
27
30
  export declare function purgeDefaultStorages(options?: PurgeDefaultStorageOptions): Promise<void>;
28
31
  /**
29
32
  * Cleans up the local storage folder (defaults to `./storage`) created when running code locally.
30
- * Purging will remove all the files in all storages except for INPUT.json in the default KV store.
33
+ * Purging empties the storages that belong to a single run — the default one and every alias-keyed one —
34
+ * keeping only INPUT.json in the default KV store. Named storages persist across runs and are not touched.
31
35
  *
32
36
  * Purging of storages is happening automatically when we run our crawler (or when we open some storage
33
37
  * explicitly, e.g. via `RequestList.open()`). We can disable that via `purgeOnStart` {@link Configuration}
34
38
  * option or by setting `CRAWLEE_PURGE_ON_START` environment variable to `0` or `false`.
35
39
  *
36
- * This is a shortcut for running (optional) `purge` method on the StorageClient interface, in other words
40
+ * This is a shortcut for running (optional) `purge` method on the StorageBackend interface, in other words
37
41
  * it will call the `purge` method of the underlying storage implementation we are currently using.
38
42
  */
39
- export declare function purgeDefaultStorages(config?: Configuration, client?: StorageClient): Promise<void>;
43
+ export declare function purgeDefaultStorages(configuration?: Configuration, storageBackend?: StorageBackend): Promise<void>;
40
44
  export interface UseStateOptions {
41
- config?: Configuration;
45
+ configuration?: Configuration;
42
46
  /**
43
47
  * The name of the key-value store you'd like the state to be stored in.
44
48
  * If not provided, the default store will be used.
@@ -52,7 +56,7 @@ export interface UseStateOptions {
52
56
  *
53
57
  * @param name The name of the store to use.
54
58
  * @param defaultValue If the store does not yet have a value in it, the value will be initialized with the `defaultValue` you provide.
55
- * @param options An optional object parameter where a custom `keyValueStoreName` and `config` can be passed in.
59
+ * @param options An optional object parameter where a custom `keyValueStoreName` and `configuration` can be passed in.
56
60
  */
57
61
  export declare function useState<State extends Dictionary = Dictionary>(name?: string, defaultValue?: State, options?: UseStateOptions): Promise<State>;
58
62
  /**
@@ -89,5 +93,49 @@ export declare const API_PROCESSED_REQUESTS_DELAY_MILLIS = 10000;
89
93
  * @internal
90
94
  */
91
95
  export declare const MAX_QUERIES_FOR_CONSISTENCY = 6;
96
+ /** @internal */
97
+ export interface DualIterableOptions<TItem, TRawPage> {
98
+ /** Factory that returns an async generator yielding pages. */
99
+ createPages: () => AsyncGenerator<TRawPage>;
100
+ /** Extracts individual items from a page (for iteration). */
101
+ extractItems: (page: TRawPage) => TItem[];
102
+ }
103
+ /**
104
+ * Creates an object that is both an `AsyncIterable<TItem>` (for `for await...of`)
105
+ * and a `Promise<TItem[]>` (for `await`) from a single async page generator.
106
+ *
107
+ * - `await result` drains all pages from a fresh generator and returns every
108
+ * item as a flat array.
109
+ * - `for await (const item of result)` streams all items across all pages,
110
+ * yielding them one by one without buffering everything in memory.
111
+ *
112
+ * Each usage path creates its own generator instance, so `await` and
113
+ * `for await...of` never interfere with each other.
114
+ *
115
+ * @internal
116
+ */
117
+ export declare function createDualIterable<TItem, TRawPage>(options: DualIterableOptions<TItem, TRawPage>): AsyncIterable<TItem> & Promise<TItem[]>;
118
+ /**
119
+ * Options for the static `open()` method on storage classes ({@link Dataset}, {@link KeyValueStore}, {@link RequestQueue}).
120
+ */
121
+ export interface StorageOpenOptions {
122
+ /**
123
+ * SDK configuration instance, defaults to the static register.
124
+ */
125
+ configuration?: Configuration;
126
+ /**
127
+ * Optional storage backend that should be used to open storages.
128
+ */
129
+ storageBackend?: StorageBackend;
130
+ /**
131
+ * Used to pass the proxy configuration for the `requestsFromUrl` objects.
132
+ * Takes advantage of the internal address rotation and authentication process.
133
+ * If undefined, the `requestsFromUrl` requests will be made without proxy.
134
+ */
135
+ proxyConfiguration?: IProxyConfiguration;
136
+ /**
137
+ * HTTP client to be used to download the list of URLs in `RequestQueue`.
138
+ */
139
+ httpClient?: BaseHttpClient;
140
+ }
92
141
  export {};
93
- //# sourceMappingURL=utils.d.ts.map
package/storages/utils.js CHANGED
@@ -1,21 +1,31 @@
1
1
  import crypto from 'node:crypto';
2
2
  import { Configuration } from '../configuration.js';
3
+ import { serviceLocator } from '../service_locator.js';
3
4
  import { KeyValueStore } from './key_value_store.js';
4
- export async function purgeDefaultStorages(configOrOptions, client) {
5
- const options = configOrOptions instanceof Configuration
5
+ export async function purgeDefaultStorages(configurationOrOptions, storageBackend) {
6
+ const options = configurationOrOptions instanceof Configuration
6
7
  ? {
7
- client,
8
- config: configOrOptions,
8
+ storageBackend,
9
+ configuration: configurationOrOptions,
9
10
  }
10
- : (configOrOptions ?? {});
11
- const { config = Configuration.getGlobalConfig(), onlyPurgeOnce = false } = options;
12
- ({ client = config.getStorageClient() } = options);
13
- const casted = client;
11
+ : (configurationOrOptions ?? {});
12
+ const { configuration = serviceLocator.getConfiguration(), onlyPurgeOnce = false } = options;
13
+ ({ storageBackend = serviceLocator.getStorageBackend() } = options);
14
+ const casted = storageBackend;
15
+ const runPurge = async () => {
16
+ try {
17
+ await casted.purge?.();
18
+ }
19
+ catch (e) {
20
+ casted.__purged = undefined;
21
+ throw e;
22
+ }
23
+ };
14
24
  // if `onlyPurgeOnce` is true, will purge anytime this function is called, otherwise - only on start
15
- if (!onlyPurgeOnce || (config.get('purgeOnStart') && !casted.__purged)) {
16
- casted.__purged = true;
17
- await casted.purge?.();
25
+ if (!onlyPurgeOnce || (configuration.purgeOnStart && !casted.__purged)) {
26
+ casted.__purged = runPurge();
18
27
  }
28
+ await casted.__purged;
19
29
  }
20
30
  /**
21
31
  * Easily create and manage state values. All state values are automatically persisted.
@@ -24,11 +34,11 @@ export async function purgeDefaultStorages(configOrOptions, client) {
24
34
  *
25
35
  * @param name The name of the store to use.
26
36
  * @param defaultValue If the store does not yet have a value in it, the value will be initialized with the `defaultValue` you provide.
27
- * @param options An optional object parameter where a custom `keyValueStoreName` and `config` can be passed in.
37
+ * @param options An optional object parameter where a custom `keyValueStoreName` and `configuration` can be passed in.
28
38
  */
29
39
  export async function useState(name, defaultValue = {}, options) {
30
- const kvStore = await KeyValueStore.open(options?.keyValueStoreName, {
31
- config: options?.config || Configuration.getGlobalConfig(),
40
+ const kvStore = await KeyValueStore.open(options?.keyValueStoreName ? { name: options.keyValueStoreName } : null, {
41
+ configuration: options?.configuration || serviceLocator.getConfiguration(),
32
42
  });
33
43
  return kvStore.getAutoSavedValue(name || 'CRAWLEE_GLOBAL_STATE', defaultValue);
34
44
  }
@@ -69,4 +79,54 @@ export const API_PROCESSED_REQUESTS_DELAY_MILLIS = 10_000;
69
79
  * @internal
70
80
  */
71
81
  export const MAX_QUERIES_FOR_CONSISTENCY = 6;
72
- //# sourceMappingURL=utils.js.map
82
+ /**
83
+ * Creates an object that is both an `AsyncIterable<TItem>` (for `for await...of`)
84
+ * and a `Promise<TItem[]>` (for `await`) from a single async page generator.
85
+ *
86
+ * - `await result` drains all pages from a fresh generator and returns every
87
+ * item as a flat array.
88
+ * - `for await (const item of result)` streams all items across all pages,
89
+ * yielding them one by one without buffering everything in memory.
90
+ *
91
+ * Each usage path creates its own generator instance, so `await` and
92
+ * `for await...of` never interfere with each other.
93
+ *
94
+ * @internal
95
+ */
96
+ export function createDualIterable(options) {
97
+ const { createPages, extractItems } = options;
98
+ let cached = null;
99
+ function getOrCreate() {
100
+ if (!cached) {
101
+ cached = (async () => {
102
+ const items = [];
103
+ for await (const page of createPages()) {
104
+ items.push(...extractItems(page));
105
+ }
106
+ return items;
107
+ })();
108
+ }
109
+ return cached;
110
+ }
111
+ async function* iterateAll() {
112
+ for await (const page of createPages()) {
113
+ yield* extractItems(page);
114
+ }
115
+ }
116
+ const result = {
117
+ [Symbol.asyncIterator]() {
118
+ return iterateAll();
119
+ },
120
+ then(onfulfilled, onrejected) {
121
+ return getOrCreate().then(onfulfilled, onrejected);
122
+ },
123
+ catch(onrejected) {
124
+ return getOrCreate().catch(onrejected);
125
+ },
126
+ finally(onfinally) {
127
+ return getOrCreate().finally(onfinally);
128
+ },
129
+ [Symbol.toStringTag]: 'DualIterable',
130
+ };
131
+ return result;
132
+ }
@@ -0,0 +1,67 @@
1
+ import type { CrawleeLogger } from '@crawlee/types';
2
+ /**
3
+ * Gets the "bare metal" cpu load.
4
+ * Used in
5
+ * - AWS Lambda
6
+ * - Containers without a cGroup quota
7
+ * - Uncontainerized environments
8
+ * @returns a number between 0 and 1 for the cpu load
9
+ * @internal
10
+ */
11
+ export declare function getCurrentCpuTicks(): number;
12
+ /**
13
+ * Reads the cgroup cpu quota.
14
+ * In V1, a quota of -1 means “unlimited.”
15
+ * In V2, a first field of "max" means unlimited.
16
+ * @param cgroupsVersion the cGroup version
17
+ * @returns The Cpu Quota
18
+ * @internal
19
+ */
20
+ export declare function getCpuQuota(cgroupsVersion: string): Promise<number | null>;
21
+ /**
22
+ * Reads the cgroup cpu period.
23
+ * @param cgroupsVersion the cGroup version
24
+ * @returns The Cpu quota period
25
+ * @internal
26
+ */
27
+ export declare function getCpuPeriod(cgroupsVersion: string): Promise<number>;
28
+ /**
29
+ * Reads the cgroup cpu usage of the container
30
+ *
31
+ * @param cgroupsVersion the cGroup version
32
+ * @returns the cpu usage
33
+ * @internal
34
+ */
35
+ export declare function getContainerCpuUsage(cgroupsVersion: string): Promise<number>;
36
+ /**
37
+ * Reads the cgroup cpu usage of the system from cgroup
38
+ *
39
+ * @returns the cpu usage
40
+ * @internal
41
+ */
42
+ export declare function getSystemCpuUsage(): Promise<number>;
43
+ /**
44
+ * a cpu sample with the container usage and system usage
45
+ */
46
+ export interface CpuSample {
47
+ containerUsage: number;
48
+ systemUsage: number;
49
+ }
50
+ /**
51
+ * Takes a CPU usage sample for both the container and the system.
52
+ *
53
+ * @returns An object containing the container and system CPU usage.
54
+ * @internal
55
+ */
56
+ export declare function sampleCpuUsage(cGroupsVersion: string): Promise<CpuSample>;
57
+ /**
58
+ * Gets the cpu usage of the system.
59
+ * If the crawler is running in a containerized environment, crawlee will check for a cgroup enforced cpu limit.
60
+ * If a cgroup limit is found, it will be taken as the maximum load against which the current load will be gauged.
61
+ * @returns a number between 0 and 1 for the cpu load
62
+ * @internal
63
+ */
64
+ export declare function getCurrentCpuTicksV2(options?: {
65
+ containerized?: boolean;
66
+ logger?: CrawleeLogger;
67
+ }): Promise<number>;