@crawlee/core 4.0.0-beta.11 → 4.0.0-beta.111

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