@crawlee/core 4.0.0-beta.14 → 4.0.0-beta.140

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 +67 -172
  3. package/autoscaling/autoscaled_pool.js +182 -329
  4. package/autoscaling/concurrency_system.d.ts +269 -0
  5. package/autoscaling/concurrency_system.js +365 -0
  6. package/autoscaling/cpu_load_signal.d.ts +43 -0
  7. package/autoscaling/cpu_load_signal.js +47 -0
  8. package/autoscaling/event_loop_load_signal.d.ts +51 -0
  9. package/autoscaling/event_loop_load_signal.js +60 -0
  10. package/autoscaling/index.d.ts +6 -1
  11. package/autoscaling/index.js +6 -1
  12. package/autoscaling/load_signal.d.ts +100 -0
  13. package/autoscaling/load_signal.js +105 -0
  14. package/autoscaling/memory_load_signal.d.ts +47 -0
  15. package/autoscaling/memory_load_signal.js +106 -0
  16. package/autoscaling/snapshotter.d.ts +58 -163
  17. package/autoscaling/snapshotter.js +45 -263
  18. package/autoscaling/storage_backend_load_signal.d.ts +56 -0
  19. package/autoscaling/storage_backend_load_signal.js +73 -0
  20. package/autoscaling/system_status.d.ts +67 -89
  21. package/autoscaling/system_status.js +92 -122
  22. package/autoscaling/weighted_avg.d.ts +5 -0
  23. package/autoscaling/weighted_avg.js +14 -0
  24. package/byte_utils.d.ts +17 -0
  25. package/byte_utils.js +42 -0
  26. package/configuration.d.ts +96 -223
  27. package/configuration.js +170 -222
  28. package/cookie_utils.d.ts +3 -2
  29. package/cookie_utils.js +18 -7
  30. package/crawlers/context_pipeline.d.ts +10 -1
  31. package/crawlers/context_pipeline.js +31 -8
  32. package/crawlers/crawler_commons.d.ts +90 -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 -63
  43. package/crawlers/statistics.js +354 -164
  44. package/debug.d.ts +36 -0
  45. package/debug.js +70 -0
  46. package/enqueue_links/enqueue_links.d.ts +60 -153
  47. package/enqueue_links/enqueue_links.js +38 -229
  48. package/enqueue_links/index.d.ts +0 -1
  49. package/enqueue_links/index.js +0 -1
  50. package/enqueue_links/shared.d.ts +49 -30
  51. package/enqueue_links/shared.js +94 -68
  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 +78 -1
  67. package/log.js +106 -1
  68. package/memory-storage/consts.d.ts +4 -0
  69. package/memory-storage/consts.js +4 -0
  70. package/memory-storage/index.d.ts +1 -0
  71. package/memory-storage/index.js +1 -0
  72. package/memory-storage/memory-storage.d.ts +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 +35 -88
  108. package/session_pool/session.js +101 -159
  109. package/session_pool/session_pool.d.ts +74 -91
  110. package/session_pool/session_pool.js +175 -165
  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 +283 -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 +445 -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 +52 -109
  124. package/storages/request_list.js +183 -152
  125. package/storages/request_loader.d.ts +101 -0
  126. package/storages/request_loader.js +1 -0
  127. package/storages/request_manager.d.ts +33 -0
  128. package/storages/request_manager.js +1 -0
  129. package/storages/request_manager_tandem.d.ts +48 -19
  130. package/storages/request_manager_tandem.js +118 -45
  131. package/storages/request_queue.d.ts +290 -47
  132. package/storages/request_queue.js +762 -216
  133. package/storages/{sitemap_request_list.d.ts → sitemap_request_loader.d.ts} +45 -89
  134. package/storages/sitemap_request_loader.js +438 -0
  135. package/storages/storage_instance_manager.d.ts +87 -0
  136. package/storages/storage_instance_manager.js +256 -0
  137. package/storages/storage_stats.d.ts +48 -0
  138. package/storages/storage_stats.js +29 -0
  139. package/storages/throttling_request_manager.d.ts +239 -0
  140. package/storages/throttling_request_manager.js +646 -0
  141. package/storages/transaction.d.ts +252 -0
  142. package/storages/transaction.js +251 -0
  143. package/storages/utils.d.ts +59 -11
  144. package/storages/utils.js +75 -15
  145. package/system-info/cpu-info.d.ts +67 -0
  146. package/system-info/cpu-info.js +216 -0
  147. package/system-info/memory-info.d.ts +31 -0
  148. package/system-info/memory-info.js +115 -0
  149. package/system-info/ps-tree.d.ts +17 -0
  150. package/system-info/ps-tree.js +144 -0
  151. package/system-info/runtime.d.ts +14 -0
  152. package/system-info/runtime.js +80 -0
  153. package/typedefs.d.ts +0 -6
  154. package/typedefs.js +0 -1
  155. package/url.d.ts +9 -0
  156. package/url.js +11 -0
  157. package/validators.d.ts +22 -18
  158. package/validators.js +13 -18
  159. package/autoscaling/autoscaled_pool.d.ts.map +0 -1
  160. package/autoscaling/autoscaled_pool.js.map +0 -1
  161. package/autoscaling/index.d.ts.map +0 -1
  162. package/autoscaling/index.js.map +0 -1
  163. package/autoscaling/snapshotter.d.ts.map +0 -1
  164. package/autoscaling/snapshotter.js.map +0 -1
  165. package/autoscaling/system_status.d.ts.map +0 -1
  166. package/autoscaling/system_status.js.map +0 -1
  167. package/configuration.d.ts.map +0 -1
  168. package/configuration.js.map +0 -1
  169. package/cookie_utils.d.ts.map +0 -1
  170. package/cookie_utils.js.map +0 -1
  171. package/crawlers/context_pipeline.d.ts.map +0 -1
  172. package/crawlers/context_pipeline.js.map +0 -1
  173. package/crawlers/crawler_commons.d.ts.map +0 -1
  174. package/crawlers/crawler_commons.js.map +0 -1
  175. package/crawlers/crawler_utils.d.ts +0 -10
  176. package/crawlers/crawler_utils.d.ts.map +0 -1
  177. package/crawlers/crawler_utils.js +0 -12
  178. package/crawlers/crawler_utils.js.map +0 -1
  179. package/crawlers/error_snapshotter.d.ts.map +0 -1
  180. package/crawlers/error_snapshotter.js.map +0 -1
  181. package/crawlers/error_tracker.d.ts.map +0 -1
  182. package/crawlers/error_tracker.js.map +0 -1
  183. package/crawlers/index.d.ts.map +0 -1
  184. package/crawlers/index.js.map +0 -1
  185. package/crawlers/internals/types.d.ts.map +0 -1
  186. package/crawlers/internals/types.js.map +0 -1
  187. package/crawlers/statistics.d.ts.map +0 -1
  188. package/crawlers/statistics.js.map +0 -1
  189. package/enqueue_links/enqueue_links.d.ts.map +0 -1
  190. package/enqueue_links/enqueue_links.js.map +0 -1
  191. package/enqueue_links/index.d.ts.map +0 -1
  192. package/enqueue_links/index.js.map +0 -1
  193. package/enqueue_links/shared.d.ts.map +0 -1
  194. package/enqueue_links/shared.js.map +0 -1
  195. package/errors.d.ts.map +0 -1
  196. package/errors.js.map +0 -1
  197. package/events/event_manager.d.ts.map +0 -1
  198. package/events/event_manager.js.map +0 -1
  199. package/events/index.d.ts.map +0 -1
  200. package/events/index.js.map +0 -1
  201. package/events/local_event_manager.d.ts.map +0 -1
  202. package/events/local_event_manager.js.map +0 -1
  203. package/http_clients/base-http-client.d.ts +0 -140
  204. package/http_clients/base-http-client.d.ts.map +0 -1
  205. package/http_clients/base-http-client.js +0 -40
  206. package/http_clients/base-http-client.js.map +0 -1
  207. package/http_clients/form-data-like.d.ts +0 -67
  208. package/http_clients/form-data-like.d.ts.map +0 -1
  209. package/http_clients/form-data-like.js +0 -5
  210. package/http_clients/form-data-like.js.map +0 -1
  211. package/http_clients/got-scraping-http-client.d.ts +0 -20
  212. package/http_clients/got-scraping-http-client.d.ts.map +0 -1
  213. package/http_clients/got-scraping-http-client.js +0 -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
@@ -1,80 +1,40 @@
1
- import { stringify } from 'csv-stringify/sync';
2
- import ow from 'ow';
3
- import { MAX_PAYLOAD_SIZE_BYTES } from '@apify/consts';
1
+ import { z } from 'zod';
2
+ import { tryCancel } from '@apify/timeout';
4
3
  import { Configuration } from '../configuration.js';
5
- import { log } from '../log.js';
6
- import { checkStorageAccess } from './access_checking.js';
4
+ import { serviceLocator } from '../service_locator.js';
5
+ import { parseArgument, schemas, validators } from '../validators.js';
6
+ import { activeStorageTransaction, rejectOperationInTransaction, snapshotValue } from './transaction.js';
7
7
  import { KeyValueStore } from './key_value_store.js';
8
- import { StorageManager } from './storage_manager.js';
9
- import { purgeDefaultStorages } from './utils.js';
8
+ import { StorageStatsTracker } from './storage_stats.js';
9
+ import { resolveStorageIdentifier } from './storage_instance_manager.js';
10
+ import { createDualIterable, purgeDefaultStorages } from './utils.js';
11
+ const openOptionsSchema = z.strictObject({
12
+ configuration: z.instanceof(Configuration).optional(),
13
+ storageBackend: validators.storageBackend.optional(),
14
+ });
10
15
  /** @internal */
11
16
  export const DATASET_ITERATORS_DEFAULT_LIMIT = 10000;
12
- const SAFETY_BUFFER_PERCENT = 0.01 / 100; // 0.01%
13
17
  /**
14
- * Accepts a JSON serializable object as an input, validates its serializability,
15
- * and validates its serialized size against limitBytes. Optionally accepts its index
16
- * in an array to provide better error messages. Returns serialized object.
18
+ * Validates that the given value is a plain JSON-serializable object
19
+ * (not an array, not a primitive, not circular).
20
+ *
21
+ * @param item The value to validate.
22
+ * @param index Optional index for error messages when validating inside an array.
17
23
  * @ignore
18
24
  */
19
- export function checkAndSerialize(item, limitBytes, index) {
25
+ export function assertJsonSerializable(item, index) {
20
26
  const s = typeof index === 'number' ? ` at index ${index} ` : ' ';
21
27
  const isItemObject = item && typeof item === 'object' && !Array.isArray(item);
22
28
  if (!isItemObject) {
23
29
  throw new Error(`Data item${s}is not an object. You can push only objects into a dataset.`);
24
30
  }
25
- let payload;
26
31
  try {
27
- payload = JSON.stringify(item);
32
+ JSON.stringify(item);
28
33
  }
29
34
  catch (e) {
30
35
  const err = e;
31
36
  throw new Error(`Data item${s}is not serializable to JSON.\nCause: ${err.message}`);
32
37
  }
33
- const bytes = Buffer.byteLength(payload);
34
- if (bytes > limitBytes) {
35
- throw new Error(`Data item${s}is too large (size: ${bytes} bytes, limit: ${limitBytes} bytes)`);
36
- }
37
- return payload;
38
- }
39
- /**
40
- * Takes an array of JSONs (payloads) as input and produces an array of JSON strings
41
- * where each string is a JSON array of payloads with a maximum size of limitBytes per one
42
- * JSON array. Fits as many payloads as possible into a single JSON array and then moves
43
- * on to the next, preserving item order.
44
- *
45
- * The function assumes that none of the items is larger than limitBytes and does not validate.
46
- * @ignore
47
- */
48
- export function chunkBySize(items, limitBytes) {
49
- if (!items.length)
50
- return [];
51
- if (items.length === 1)
52
- return items;
53
- // Split payloads into buckets of valid size.
54
- let lastChunkBytes = 2; // Add 2 bytes for [] wrapper.
55
- const chunks = [];
56
- for (const payload of items) {
57
- const bytes = Buffer.byteLength(payload);
58
- if (bytes <= limitBytes && bytes + 2 > limitBytes) {
59
- // Handle cases where wrapping with [] would fail, but solo object is fine.
60
- chunks.push(payload);
61
- lastChunkBytes = bytes;
62
- }
63
- else if (lastChunkBytes + bytes <= limitBytes) {
64
- // ensure array
65
- if (!Array.isArray(chunks[chunks.length - 1])) {
66
- chunks.push([]);
67
- }
68
- chunks[chunks.length - 1].push(payload);
69
- lastChunkBytes += bytes + 1; // Add 1 byte for ',' separator.
70
- }
71
- else {
72
- chunks.push([payload]);
73
- lastChunkBytes = bytes + 2; // Add 2 bytes for [] wrapper.
74
- }
75
- }
76
- // Stringify array chunks.
77
- return chunks.map((chunk) => (typeof chunk === 'string' ? chunk : `[${chunk.join(',')}]`));
78
38
  }
79
39
  /**
80
40
  * The `Dataset` class represents a store for structured data where each object stored has the same attributes,
@@ -128,21 +88,31 @@ export function chunkBySize(items, limitBytes) {
128
88
  * @category Result Stores
129
89
  */
130
90
  export class Dataset {
131
- config;
91
+ configuration;
132
92
  id;
133
93
  name;
134
- client;
135
- storageObject;
136
- log = log.child({ prefix: 'Dataset' });
94
+ backend;
95
+ log;
96
+ #statsTracker = new StorageStatsTracker({
97
+ readCount: 0,
98
+ writeCount: 0,
99
+ });
137
100
  /**
138
101
  * @internal
139
102
  */
140
- constructor(options, config = Configuration.getGlobalConfig()) {
141
- this.config = config;
142
- this.id = options.id;
143
- this.name = options.name;
144
- this.client = options.client.dataset(this.id);
145
- this.storageObject = options.storageObject;
103
+ constructor(options, configuration = Configuration.getGlobalConfiguration()) {
104
+ this.configuration = configuration;
105
+ this.id = options.metadata.id;
106
+ this.name = options.metadata.name;
107
+ this.backend = options.backend;
108
+ this.log = serviceLocator.getLogger().child({ prefix: 'Dataset' });
109
+ }
110
+ /**
111
+ * Backend-independent usage counters tracked for this dataset (read / write operations issued to
112
+ * the underlying storage backend). Counted per backend call.
113
+ */
114
+ get stats() {
115
+ return this.#statsTracker.current;
146
116
  }
147
117
  /**
148
118
  * Stores an object or an array of objects to the dataset.
@@ -152,48 +122,37 @@ export class Dataset {
152
122
  * **IMPORTANT**: Make sure to use the `await` keyword when calling `pushData()`,
153
123
  * otherwise the crawler process might finish before the data is stored!
154
124
  *
155
- * The size of the data is limited by the receiving API and therefore `pushData()` will only
156
- * allow objects whose JSON representation is smaller than 9MB. When an array is passed,
157
- * none of the included objects
158
- * may be larger than 9MB, but the array itself may be of any size.
159
- *
160
- * The function internally
161
- * chunks the array into separate items and pushes them sequentially.
162
- * The chunking process is stable (keeps order of data), but it does not provide a transaction
163
- * safety mechanism. Therefore, in the event of an uploading error (after several automatic retries),
164
- * the function's Promise will reject and the dataset will be left in a state where some of
165
- * the items have already been saved to the dataset while other items from the source array were not.
166
- * To overcome this limitation, the developer may, for example, read the last item saved in the dataset
167
- * and re-attempt the save of the data from this item onwards to prevent duplicates.
168
125
  * @param data Object or array of objects containing data to be stored in the default dataset.
169
- * The objects must be serializable to JSON and the JSON representation of each object must be smaller than 9MB.
126
+ * The objects must be serializable to JSON.
170
127
  */
171
128
  async pushData(data) {
172
- checkStorageAccess();
173
- ow(data, 'data', ow.object);
174
- const dispatch = async (payload) => this.client.pushItems(payload);
175
- const limit = MAX_PAYLOAD_SIZE_BYTES - Math.ceil(MAX_PAYLOAD_SIZE_BYTES * SAFETY_BUFFER_PERCENT);
176
- // Handle singular Objects
177
- if (!Array.isArray(data)) {
178
- const payload = checkAndSerialize(data, limit);
179
- await dispatch(payload);
180
- return;
129
+ const transaction = activeStorageTransaction();
130
+ parseArgument(data, schemas.anyObject);
131
+ // Normalize to array and validate each item
132
+ const items = Array.isArray(data) ? data : [data];
133
+ for (let i = 0; i < items.length; i++) {
134
+ assertJsonSerializable(items[i], i);
181
135
  }
182
- // Handle Arrays
183
- const payloads = data.map((item, index) => checkAndSerialize(item, limit, index));
184
- const chunks = chunkBySize(payloads, limit);
185
- // Invoke client in series to preserve order of data
186
- for (const chunk of chunks) {
187
- await dispatch(chunk);
136
+ if (transaction) {
137
+ // One snapshot serves both the reads and the commit replay, so the two cannot disagree.
138
+ transaction.recordJournalEntry({
139
+ type: 'dataset',
140
+ participant: this,
141
+ storageId: this.id,
142
+ items: snapshotValue(items),
143
+ recordedAt: new Date(),
144
+ });
145
+ return;
188
146
  }
147
+ this.#statsTracker.add('writeCount');
148
+ await this.backend.pushData(items);
189
149
  }
190
150
  /**
191
151
  * Returns {@link DatasetContent} object holding the items in the dataset based on the provided parameters.
192
152
  */
193
153
  async getData(options = {}) {
194
- checkStorageAccess();
195
154
  try {
196
- return await this.client.listItems(options);
155
+ return await this.readPage(options);
197
156
  }
198
157
  catch (e) {
199
158
  const error = e;
@@ -203,25 +162,97 @@ export class Dataset {
203
162
  throw e;
204
163
  }
205
164
  }
165
+ /**
166
+ * The single transaction-aware page read all dataset read paths go through — both `getData()` and
167
+ * the private `fetchPages()`. Returns the real page concatenated with the current transaction's
168
+ * buffered items, with `offset` / `limit` / `desc` windowing applied across the concatenation.
169
+ */
170
+ async readPage(options) {
171
+ const buffered = this.bufferedJournalEntries()?.flatMap((entry) => entry.items);
172
+ // Every branch below hits the backend exactly once.
173
+ this.#statsTracker.add('readCount');
174
+ if (!buffered?.length) {
175
+ return this.backend.getData(options);
176
+ }
177
+ const { offset = 0, limit, desc = false } = options;
178
+ if (!desc) {
179
+ const realPage = await this.backend.getData(options);
180
+ // Buffered items sit past `realPage.total`, so the window bounds must come from that - not
181
+ // from the page's shortfall, which `skipEmpty` produces without exhausting the real items.
182
+ const bufferedStart = Math.max(0, offset - realPage.total);
183
+ const bufferedEnd = limit === undefined ? buffered.length : Math.max(0, offset + limit - realPage.total);
184
+ const items = [...realPage.items, ...buffered.slice(bufferedStart, bufferedEnd)];
185
+ return {
186
+ items,
187
+ total: realPage.total + buffered.length,
188
+ offset,
189
+ // A caller that passed no limit wants everything, so the backend-reported `limit` is
190
+ // passed through - backends are free to report a page size or a sentinel there.
191
+ limit: limit ?? realPage.limit,
192
+ count: items.length,
193
+ desc,
194
+ };
195
+ }
196
+ // Descending order: the buffered items are the newest, so they come first, reversed.
197
+ const reversedBuffer = [...buffered].reverse();
198
+ const fromBuffer = limit === undefined ? reversedBuffer.slice(offset) : reversedBuffer.slice(offset, offset + limit);
199
+ const needed = limit === undefined ? Infinity : limit - fromBuffer.length;
200
+ if (needed <= 0) {
201
+ // The whole window is served from the buffer; only the real total is missing.
202
+ const { itemCount } = await this.backend.getMetadata();
203
+ return {
204
+ items: fromBuffer,
205
+ total: itemCount + buffered.length,
206
+ offset,
207
+ limit: limit,
208
+ count: fromBuffer.length,
209
+ desc,
210
+ };
211
+ }
212
+ const realPage = await this.backend.getData({
213
+ ...options,
214
+ offset: Math.max(0, offset - buffered.length),
215
+ ...(limit === undefined ? {} : { limit: needed }),
216
+ });
217
+ return {
218
+ items: [...fromBuffer, ...realPage.items],
219
+ total: realPage.total + buffered.length,
220
+ offset,
221
+ limit: limit ?? realPage.limit,
222
+ count: fromBuffer.length + realPage.items.length,
223
+ desc,
224
+ };
225
+ }
226
+ /** The active transaction's buffered writes to this dataset, derived from its journal. */
227
+ bufferedJournalEntries() {
228
+ const transaction = activeStorageTransaction();
229
+ return transaction?.journal.filter((entry) => entry.type === 'dataset' && entry.participant === this);
230
+ }
231
+ /** @internal */
232
+ async commitJournalEntries(entries) {
233
+ const items = [];
234
+ for (const entry of entries) {
235
+ if (entry.type === 'dataset') {
236
+ items.push(...entry.items);
237
+ }
238
+ }
239
+ // One backend call with all journaled items, in order - as close to atomic as the backend allows.
240
+ // Straight to the backend: the items were validated and snapshotted at write time.
241
+ if (items.length > 0) {
242
+ this.#statsTracker.add('writeCount');
243
+ await this.backend.pushData(items);
244
+ }
245
+ }
206
246
  /**
207
247
  * Returns all the data from the dataset. This will iterate through the whole dataset
208
248
  * via the `listItems()` client method, which gives you only paginated results.
209
249
  */
210
250
  async export(options = {}) {
211
- checkStorageAccess();
251
+ tryCancel();
212
252
  const items = [];
213
- const fetchNextChunk = async (offset = 0) => {
214
- const limit = 1000;
215
- const value = await this.client.listItems({ offset, limit, ...options });
216
- if (value.count === 0) {
217
- return;
218
- }
219
- items.push(...value.items);
220
- if (value.total > offset + value.count) {
221
- await fetchNextChunk(offset + value.count);
222
- }
223
- };
224
- await fetchNextChunk();
253
+ for await (const page of this.fetchPages(options)) {
254
+ items.push(...page.items);
255
+ }
225
256
  return items;
226
257
  }
227
258
  /**
@@ -232,7 +263,7 @@ export class Dataset {
232
263
  * @param [contentType] Only JSON and CSV are supported currently, defaults to JSON.
233
264
  */
234
265
  async exportTo(key, options, contentType) {
235
- const kvStore = await KeyValueStore.open(options?.toKVS ?? null, { config: this.config });
266
+ const kvStore = await KeyValueStore.open(options?.toKVS ?? null, { configuration: this.configuration });
236
267
  const items = await this.export(options);
237
268
  if (contentType === 'text/csv') {
238
269
  // To handle empty dataset exports gracefully.
@@ -243,6 +274,7 @@ export class Dataset {
243
274
  const keys = options?.collectAllKeys
244
275
  ? Array.from(new Set(items.flatMap(Object.keys)))
245
276
  : Object.keys(items[0]);
277
+ const { stringify } = await import('csv-stringify/sync');
246
278
  const value = stringify([
247
279
  keys,
248
280
  ...items.map((item) => {
@@ -257,7 +289,6 @@ export class Dataset {
257
289
  return items;
258
290
  }
259
291
  throw new Error(`Unsupported content type: ${contentType}`);
260
- return items;
261
292
  }
262
293
  /**
263
294
  * Save entire default dataset's contents into one JSON file within a key-value store.
@@ -284,7 +315,7 @@ export class Dataset {
284
315
  * @param [options] An optional options object where you can provide the dataset and target KVS name.
285
316
  */
286
317
  static async exportToJSON(key, options) {
287
- checkStorageAccess();
318
+ tryCancel();
288
319
  const dataset = await this.open(options?.fromDataset);
289
320
  await dataset.exportToJSON(key, options);
290
321
  }
@@ -295,35 +326,39 @@ export class Dataset {
295
326
  * @param [options] An optional options object where you can provide the dataset and target KVS name.
296
327
  */
297
328
  static async exportToCSV(key, options) {
298
- checkStorageAccess();
329
+ tryCancel();
299
330
  const dataset = await this.open(options?.fromDataset);
300
331
  await dataset.exportToCSV(key, options);
301
332
  }
302
333
  /**
303
334
  * Returns an object containing general information about the dataset.
304
335
  *
305
- * The function returns the same object as the Apify API Client's
306
- * [getDataset](https://docs.apify.com/api/apify-client-js/latest#ApifyClient-datasets-getDataset)
307
- * function, which in turn calls the
308
- * [Get dataset](https://apify.com/docs/api/v2#/reference/datasets/dataset/get-dataset)
309
- * API endpoint.
310
- *
311
336
  * **Example:**
312
337
  * ```
313
338
  * {
314
339
  * id: "WkzbQMuFYuamGv3YF",
315
340
  * name: "my-dataset",
316
- * userId: "wRsJZtadYvn4mBZmm",
317
341
  * createdAt: new Date("2015-12-12T07:34:14.202Z"),
318
342
  * modifiedAt: new Date("2015-12-13T08:36:13.202Z"),
319
343
  * accessedAt: new Date("2015-12-14T08:36:13.202Z"),
320
344
  * itemCount: 14,
321
345
  * }
322
346
  * ```
347
+ *
348
+ * @throws If the underlying storage no longer exists (e.g. it was deleted externally).
323
349
  */
324
350
  async getInfo() {
325
- checkStorageAccess();
326
- return this.client.get();
351
+ const buffered = this.bufferedJournalEntries();
352
+ const metadata = await this.backend.getMetadata();
353
+ if (buffered?.length) {
354
+ const lastWriteAt = buffered[buffered.length - 1].recordedAt;
355
+ return {
356
+ ...metadata,
357
+ itemCount: metadata.itemCount + buffered.reduce((sum, entry) => sum + entry.items.length, 0),
358
+ modifiedAt: metadata.modifiedAt > lastWriteAt ? metadata.modifiedAt : lastWriteAt,
359
+ };
360
+ }
361
+ return metadata;
327
362
  }
328
363
  /**
329
364
  * Iterates over dataset items, yielding each in turn to an `iteratee` function.
@@ -346,7 +381,7 @@ export class Dataset {
346
381
  * @default 0
347
382
  */
348
383
  async forEach(iteratee, options = {}, index = 0) {
349
- checkStorageAccess();
384
+ tryCancel();
350
385
  if (!options.offset)
351
386
  options.offset = 0;
352
387
  if (options.format && options.format !== 'json')
@@ -373,7 +408,7 @@ export class Dataset {
373
408
  * @param [options] All `map()` parameters.
374
409
  */
375
410
  async map(iteratee, options = {}) {
376
- checkStorageAccess();
411
+ tryCancel();
377
412
  const result = [];
378
413
  await this.forEach(async (item, index) => {
379
414
  const res = await iteratee(item, index);
@@ -382,7 +417,7 @@ export class Dataset {
382
417
  return result;
383
418
  }
384
419
  async reduce(iteratee, memo, options = {}) {
385
- checkStorageAccess();
420
+ tryCancel();
386
421
  let currentMemo = memo;
387
422
  const wrappedFunc = async (item, index) => {
388
423
  if (index === 0 && currentMemo === undefined) {
@@ -397,15 +432,114 @@ export class Dataset {
397
432
  await this.forEach(wrappedFunc, options);
398
433
  return currentMemo;
399
434
  }
435
+ async *fetchEntryPages(options) {
436
+ let index = options.offset ?? 0;
437
+ for await (const page of this.fetchPages(options)) {
438
+ yield {
439
+ ...page,
440
+ items: page.items.map((item) => [index++, item]),
441
+ };
442
+ }
443
+ }
444
+ async *fetchPages(options, pageSize = DATASET_ITERATORS_DEFAULT_LIMIT) {
445
+ let offset = options.offset ?? 0;
446
+ const totalLimit = options.limit;
447
+ let yielded = 0;
448
+ while (true) {
449
+ const fetchLimit = totalLimit !== undefined ? Math.min(pageSize, totalLimit - yielded) : pageSize;
450
+ if (fetchLimit <= 0)
451
+ break;
452
+ const page = await this.readPage({ ...options, offset, limit: fetchLimit });
453
+ yield page;
454
+ yielded += page.items.length;
455
+ if (page.items.length < fetchLimit || offset + page.items.length >= page.total)
456
+ break;
457
+ offset += page.items.length;
458
+ }
459
+ }
460
+ /**
461
+ * Returns dataset items.
462
+ *
463
+ * When awaited (`await dataset.values()`), returns all items as a flat `Data[]` array.
464
+ * When used as an async iterable (`for await...of`), iterates over all items across pages
465
+ * without loading everything into memory at once.
466
+ *
467
+ * **Example usage:**
468
+ * ```javascript
469
+ * const dataset = await Dataset.open('my-results');
470
+ *
471
+ * // Iterate over all items (memory-efficient for large datasets)
472
+ * for await (const item of dataset.values()) {
473
+ * console.log(item);
474
+ * }
475
+ *
476
+ * // Or fetch all items at once
477
+ * const items = await dataset.values();
478
+ * console.log(items);
479
+ * ```
480
+ *
481
+ * @param options Options for the iteration.
482
+ */
483
+ values(options = {}) {
484
+ tryCancel();
485
+ return createDualIterable({
486
+ createPages: () => this.fetchPages(options),
487
+ extractItems: (page) => page.items,
488
+ });
489
+ }
490
+ /**
491
+ * Returns dataset entries (index-value pairs).
492
+ *
493
+ * When awaited (`await dataset.entries()`), returns all entries as a flat `[index, item][]` array.
494
+ * When used as an async iterable (`for await...of`), iterates over all entries across pages
495
+ * without loading everything into memory at once.
496
+ *
497
+ * **Example usage:**
498
+ * ```javascript
499
+ * const dataset = await Dataset.open('my-results');
500
+ *
501
+ * // Iterate over all entries
502
+ * for await (const [index, item] of dataset.entries()) {
503
+ * console.log(`Item at ${index}: ${JSON.stringify(item)}`);
504
+ * }
505
+ *
506
+ * // Or fetch all at once
507
+ * const entries = await dataset.entries();
508
+ * console.log(entries);
509
+ * ```
510
+ *
511
+ * @param options Options for the iteration.
512
+ */
513
+ entries(options = {}) {
514
+ tryCancel();
515
+ return createDualIterable({
516
+ createPages: () => this.fetchEntryPages(options),
517
+ extractItems: (page) => page.items,
518
+ });
519
+ }
520
+ /**
521
+ * Default async iterator for the dataset, iterating over items.
522
+ * Allows using the dataset directly in a `for await...of` loop.
523
+ *
524
+ * **Example usage:**
525
+ * ```javascript
526
+ * const dataset = await Dataset.open('my-results');
527
+ * for await (const item of dataset) {
528
+ * console.log(item);
529
+ * }
530
+ * ```
531
+ */
532
+ async *[Symbol.asyncIterator]() {
533
+ yield* this.values();
534
+ }
400
535
  /**
401
536
  * Removes the dataset either from the Apify cloud storage or from the local directory,
402
537
  * depending on the mode of operation.
403
538
  */
404
539
  async drop() {
405
- checkStorageAccess();
406
- await this.client.delete();
407
- const manager = StorageManager.getManager(Dataset, this.config);
408
- manager.closeStorage(this);
540
+ rejectOperationInTransaction('Dataset.drop()');
541
+ await this.backend.drop();
542
+ serviceLocator.getStorageInstanceManager().removeFromCache(this);
409
543
  }
410
544
  /**
411
545
  * Opens a dataset and returns a promise resolving to an instance of the {@link Dataset} class.
@@ -416,23 +550,24 @@ export class Dataset {
416
550
  *
417
551
  * For more details and code examples, see the {@link Dataset} class.
418
552
  *
419
- * @param [datasetIdOrName]
420
- * ID or name of the dataset to be opened. If `null` or `undefined`,
421
- * the function returns the default dataset associated with the crawler run.
553
+ * @param [identifier]
554
+ * ID or name of the dataset to be opened. If a string is provided, it will first be
555
+ * looked up as an ID; if no such storage exists, it will be treated as a name.
556
+ * If `null` or `undefined`, the function returns the default dataset associated with the crawler run.
422
557
  * @param [options] Storage manager options.
423
558
  */
424
- static async open(datasetIdOrName, options = {}) {
425
- checkStorageAccess();
426
- ow(datasetIdOrName, ow.optional.string);
427
- ow(options, ow.object.exactShape({
428
- config: ow.optional.object.instanceOf(Configuration),
429
- storageClient: ow.optional.object,
430
- }));
431
- options.config ??= Configuration.getGlobalConfig();
432
- options.storageClient ??= options.config.getStorageClient();
433
- await purgeDefaultStorages({ onlyPurgeOnce: true, client: options.storageClient, config: options.config });
434
- const manager = StorageManager.getManager(this, options.config);
435
- return manager.openStorage(datasetIdOrName, options.storageClient);
559
+ static async open(identifier, options = {}) {
560
+ tryCancel();
561
+ const parsedOptions = parseArgument(options, openOptionsSchema);
562
+ const configuration = parsedOptions.configuration ?? Configuration.getGlobalConfiguration();
563
+ const storageBackend = parsedOptions.storageBackend ?? serviceLocator.getStorageBackend();
564
+ await purgeDefaultStorages({ onlyPurgeOnce: true, storageBackend, configuration });
565
+ const resolved = await resolveStorageIdentifier(identifier, storageBackend, 'Dataset');
566
+ return serviceLocator.getStorageInstanceManager().openStorage(this, {
567
+ ...resolved,
568
+ backendOpener: () => storageBackend.createDatasetBackend(resolved),
569
+ backendCacheKey: storageBackend.getStorageBackendCacheKey?.() ?? storageBackend.constructor.name,
570
+ });
436
571
  }
437
572
  /**
438
573
  * Stores an object or an array of objects to the default {@link Dataset} of the current crawler run.
@@ -470,4 +605,3 @@ export class Dataset {
470
605
  return dataset.getData(options);
471
606
  }
472
607
  }
473
- //# sourceMappingURL=dataset.js.map
@@ -1,14 +1,14 @@
1
1
  export * from './dataset.js';
2
2
  export * from './key_value_store.js';
3
+ export * from './key_value_store_codec.js';
3
4
  export * from './request_list.js';
4
- export * from './request_list_adapter.js';
5
- export * from './request_provider.js';
6
- export { RequestQueueV1 } from './request_queue.js';
7
- export { RequestQueue } from './request_queue_v2.js';
8
- export { RequestQueue as RequestQueueV2 } from './request_queue_v2.js';
9
- export * from './storage_manager.js';
5
+ export type * from './request_loader.js';
6
+ export type * from './request_manager.js';
7
+ export * from './request_queue.js';
8
+ export * from './storage_instance_manager.js';
9
+ export * from './storage_stats.js';
10
10
  export * from './utils.js';
11
- export * from './access_checking.js';
12
- export * from './sitemap_request_list.js';
11
+ export * from './transaction.js';
12
+ export * from './sitemap_request_loader.js';
13
13
  export * from './request_manager_tandem.js';
14
- //# sourceMappingURL=index.d.ts.map
14
+ export * from './throttling_request_manager.js';
package/storages/index.js CHANGED
@@ -1,14 +1,12 @@
1
1
  export * from './dataset.js';
2
2
  export * from './key_value_store.js';
3
+ export * from './key_value_store_codec.js';
3
4
  export * from './request_list.js';
4
- export * from './request_list_adapter.js';
5
- export * from './request_provider.js';
6
- export { RequestQueueV1 } from './request_queue.js';
7
- export { RequestQueue } from './request_queue_v2.js';
8
- export { RequestQueue as RequestQueueV2 } from './request_queue_v2.js';
9
- export * from './storage_manager.js';
5
+ export * from './request_queue.js';
6
+ export * from './storage_instance_manager.js';
7
+ export * from './storage_stats.js';
10
8
  export * from './utils.js';
11
- export * from './access_checking.js';
12
- export * from './sitemap_request_list.js';
9
+ export * from './transaction.js';
10
+ export * from './sitemap_request_loader.js';
13
11
  export * from './request_manager_tandem.js';
14
- //# sourceMappingURL=index.js.map
12
+ export * from './throttling_request_manager.js';