@crawlee/core 4.0.0-beta.15 → 4.0.0-beta.150
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -14
- package/autoscaling/autoscaled_pool.d.ts +73 -177
- package/autoscaling/autoscaled_pool.js +182 -329
- package/autoscaling/concurrency_system.d.ts +268 -0
- package/autoscaling/concurrency_system.js +364 -0
- package/autoscaling/cpu_load_signal.d.ts +43 -0
- package/autoscaling/cpu_load_signal.js +47 -0
- package/autoscaling/event_loop_load_signal.d.ts +51 -0
- package/autoscaling/event_loop_load_signal.js +60 -0
- package/autoscaling/index.d.ts +6 -1
- package/autoscaling/index.js +6 -1
- package/autoscaling/load_signal.d.ts +98 -0
- package/autoscaling/load_signal.js +103 -0
- package/autoscaling/memory_load_signal.d.ts +47 -0
- package/autoscaling/memory_load_signal.js +106 -0
- package/autoscaling/snapshotter.d.ts +58 -163
- package/autoscaling/snapshotter.js +45 -263
- package/autoscaling/storage_backend_load_signal.d.ts +56 -0
- package/autoscaling/storage_backend_load_signal.js +73 -0
- package/autoscaling/system_status.d.ts +67 -89
- package/autoscaling/system_status.js +92 -122
- package/autoscaling/weighted_avg.d.ts +5 -0
- package/autoscaling/weighted_avg.js +14 -0
- package/byte_utils.d.ts +17 -0
- package/byte_utils.js +42 -0
- package/configuration.d.ts +96 -223
- package/configuration.js +170 -222
- package/cookie_utils.d.ts +3 -2
- package/cookie_utils.js +18 -7
- package/crawlers/context_pipeline.d.ts +10 -1
- package/crawlers/context_pipeline.js +31 -8
- package/crawlers/crawler_commons.d.ts +90 -126
- package/crawlers/crawler_commons.js +1 -108
- package/crawlers/error_snapshotter.d.ts +2 -5
- package/crawlers/error_snapshotter.js +7 -8
- package/crawlers/error_tracker.d.ts +0 -1
- package/crawlers/error_tracker.js +0 -1
- package/crawlers/index.d.ts +1 -3
- package/crawlers/index.js +0 -3
- package/crawlers/internals/types.d.ts +0 -1
- package/crawlers/internals/types.js +0 -1
- package/crawlers/statistics.d.ts +187 -64
- package/crawlers/statistics.js +354 -165
- package/debug.d.ts +36 -0
- package/debug.js +70 -0
- package/enqueue_links/enqueue_links.d.ts +61 -154
- package/enqueue_links/enqueue_links.js +40 -232
- package/enqueue_links/index.d.ts +0 -1
- package/enqueue_links/index.js +0 -1
- package/enqueue_links/shared.d.ts +55 -28
- package/enqueue_links/shared.js +116 -69
- package/errors.d.ts +72 -4
- package/errors.js +89 -5
- package/events/event_manager.d.ts +35 -9
- package/events/event_manager.js +12 -13
- package/events/index.d.ts +0 -1
- package/events/index.js +0 -1
- package/events/local_event_manager.d.ts +15 -3
- package/events/local_event_manager.js +39 -13
- package/http.d.ts +9 -0
- package/http.js +28 -0
- package/index.d.ts +7 -5
- package/index.js +8 -4
- package/iterables.d.ts +79 -0
- package/iterables.js +134 -0
- package/log.d.ts +77 -1
- package/log.js +104 -1
- package/memory-storage/consts.d.ts +4 -0
- package/memory-storage/consts.js +4 -0
- package/memory-storage/index.d.ts +1 -0
- package/memory-storage/index.js +1 -0
- package/memory-storage/memory-storage.d.ts +44 -0
- package/memory-storage/memory-storage.js +160 -0
- package/memory-storage/resource-clients/common/base-client.d.ts +4 -0
- package/memory-storage/resource-clients/common/base-client.js +6 -0
- package/memory-storage/resource-clients/dataset.d.ts +40 -0
- package/memory-storage/resource-clients/dataset.js +106 -0
- package/memory-storage/resource-clients/key-value-store.d.ts +63 -0
- package/memory-storage/resource-clients/key-value-store.js +199 -0
- package/memory-storage/resource-clients/request-queue.d.ts +77 -0
- package/memory-storage/resource-clients/request-queue.js +407 -0
- package/memory-storage/utils.d.ts +16 -0
- package/memory-storage/utils.js +41 -0
- package/owned_or_injected.d.ts +58 -0
- package/owned_or_injected.js +98 -0
- package/package.json +13 -12
- package/proxy_configuration.d.ts +22 -128
- package/proxy_configuration.js +32 -144
- package/recoverable_state.d.ts +83 -51
- package/recoverable_state.js +163 -72
- package/request.d.ts +57 -16
- package/request.js +130 -69
- package/router.d.ts +193 -21
- package/router.js +188 -43
- package/serialization.d.ts +0 -1
- package/serialization.js +15 -15
- package/service_locator.d.ts +165 -0
- package/service_locator.js +253 -0
- package/session_pool/consts.d.ts +1 -2
- package/session_pool/consts.js +1 -2
- package/session_pool/errors.d.ts +0 -1
- package/session_pool/errors.js +0 -1
- package/session_pool/fingerprint.d.ts +9 -0
- package/session_pool/fingerprint.js +30 -0
- package/session_pool/index.d.ts +0 -2
- package/session_pool/index.js +0 -2
- package/session_pool/session.d.ts +39 -89
- package/session_pool/session.js +102 -159
- package/session_pool/session_pool.d.ts +67 -91
- package/session_pool/session_pool.js +196 -187
- package/storages/batched_adds.d.ts +37 -0
- package/storages/batched_adds.js +73 -0
- package/storages/dataset.d.ts +109 -56
- package/storages/dataset.js +284 -149
- package/storages/index.d.ts +9 -9
- package/storages/index.js +7 -9
- package/storages/key_value_store.d.ts +183 -48
- package/storages/key_value_store.js +444 -171
- package/storages/key_value_store_codec.d.ts +32 -0
- package/storages/key_value_store_codec.js +113 -0
- package/storages/request_dedup_cache.d.ts +22 -0
- package/storages/request_dedup_cache.js +48 -0
- package/storages/request_list.d.ts +53 -115
- package/storages/request_list.js +194 -167
- package/storages/request_loader.d.ts +133 -0
- package/storages/request_loader.js +36 -0
- package/storages/request_manager.d.ts +109 -0
- package/storages/request_manager.js +1 -0
- package/storages/request_manager_tandem.d.ts +55 -23
- package/storages/request_manager_tandem.js +139 -57
- package/storages/request_queue.d.ts +286 -48
- package/storages/request_queue.js +757 -218
- package/storages/{sitemap_request_list.d.ts → sitemap_request_loader.d.ts} +43 -91
- package/storages/sitemap_request_loader.js +439 -0
- package/storages/storage_instance_manager.d.ts +87 -0
- package/storages/storage_instance_manager.js +256 -0
- package/storages/storage_stats.d.ts +48 -0
- package/storages/storage_stats.js +29 -0
- package/storages/throttling_request_manager.d.ts +222 -0
- package/storages/throttling_request_manager.js +780 -0
- package/storages/transaction.d.ts +252 -0
- package/storages/transaction.js +251 -0
- package/storages/utils.d.ts +59 -11
- package/storages/utils.js +75 -15
- package/system-info/cpu-info.d.ts +67 -0
- package/system-info/cpu-info.js +216 -0
- package/system-info/memory-info.d.ts +31 -0
- package/system-info/memory-info.js +115 -0
- package/system-info/ps-tree.d.ts +17 -0
- package/system-info/ps-tree.js +144 -0
- package/system-info/runtime.d.ts +14 -0
- package/system-info/runtime.js +80 -0
- package/typedefs.d.ts +0 -6
- package/typedefs.js +0 -1
- package/url.d.ts +9 -0
- package/url.js +11 -0
- package/validators.d.ts +22 -18
- package/validators.js +13 -18
- package/autoscaling/autoscaled_pool.d.ts.map +0 -1
- package/autoscaling/autoscaled_pool.js.map +0 -1
- package/autoscaling/index.d.ts.map +0 -1
- package/autoscaling/index.js.map +0 -1
- package/autoscaling/snapshotter.d.ts.map +0 -1
- package/autoscaling/snapshotter.js.map +0 -1
- package/autoscaling/system_status.d.ts.map +0 -1
- package/autoscaling/system_status.js.map +0 -1
- package/configuration.d.ts.map +0 -1
- package/configuration.js.map +0 -1
- package/cookie_utils.d.ts.map +0 -1
- package/cookie_utils.js.map +0 -1
- package/crawlers/context_pipeline.d.ts.map +0 -1
- package/crawlers/context_pipeline.js.map +0 -1
- package/crawlers/crawler_commons.d.ts.map +0 -1
- package/crawlers/crawler_commons.js.map +0 -1
- package/crawlers/crawler_utils.d.ts +0 -10
- package/crawlers/crawler_utils.d.ts.map +0 -1
- package/crawlers/crawler_utils.js +0 -12
- package/crawlers/crawler_utils.js.map +0 -1
- package/crawlers/error_snapshotter.d.ts.map +0 -1
- package/crawlers/error_snapshotter.js.map +0 -1
- package/crawlers/error_tracker.d.ts.map +0 -1
- package/crawlers/error_tracker.js.map +0 -1
- package/crawlers/index.d.ts.map +0 -1
- package/crawlers/index.js.map +0 -1
- package/crawlers/internals/types.d.ts.map +0 -1
- package/crawlers/internals/types.js.map +0 -1
- package/crawlers/statistics.d.ts.map +0 -1
- package/crawlers/statistics.js.map +0 -1
- package/enqueue_links/enqueue_links.d.ts.map +0 -1
- package/enqueue_links/enqueue_links.js.map +0 -1
- package/enqueue_links/index.d.ts.map +0 -1
- package/enqueue_links/index.js.map +0 -1
- package/enqueue_links/shared.d.ts.map +0 -1
- package/enqueue_links/shared.js.map +0 -1
- package/errors.d.ts.map +0 -1
- package/errors.js.map +0 -1
- package/events/event_manager.d.ts.map +0 -1
- package/events/event_manager.js.map +0 -1
- package/events/index.d.ts.map +0 -1
- package/events/index.js.map +0 -1
- package/events/local_event_manager.d.ts.map +0 -1
- package/events/local_event_manager.js.map +0 -1
- package/http_clients/base-http-client.d.ts +0 -140
- package/http_clients/base-http-client.d.ts.map +0 -1
- package/http_clients/base-http-client.js +0 -40
- package/http_clients/base-http-client.js.map +0 -1
- package/http_clients/form-data-like.d.ts +0 -67
- package/http_clients/form-data-like.d.ts.map +0 -1
- package/http_clients/form-data-like.js +0 -5
- package/http_clients/form-data-like.js.map +0 -1
- package/http_clients/got-scraping-http-client.d.ts +0 -20
- package/http_clients/got-scraping-http-client.d.ts.map +0 -1
- package/http_clients/got-scraping-http-client.js +0 -85
- package/http_clients/got-scraping-http-client.js.map +0 -1
- package/http_clients/index.d.ts +0 -3
- package/http_clients/index.d.ts.map +0 -1
- package/http_clients/index.js +0 -3
- package/http_clients/index.js.map +0 -1
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/log.d.ts.map +0 -1
- package/log.js.map +0 -1
- package/proxy_configuration.d.ts.map +0 -1
- package/proxy_configuration.js.map +0 -1
- package/recoverable_state.d.ts.map +0 -1
- package/recoverable_state.js.map +0 -1
- package/request.d.ts.map +0 -1
- package/request.js.map +0 -1
- package/router.d.ts.map +0 -1
- package/router.js.map +0 -1
- package/serialization.d.ts.map +0 -1
- package/serialization.js.map +0 -1
- package/session_pool/consts.d.ts.map +0 -1
- package/session_pool/consts.js.map +0 -1
- package/session_pool/errors.d.ts.map +0 -1
- package/session_pool/errors.js.map +0 -1
- package/session_pool/events.d.ts +0 -3
- package/session_pool/events.d.ts.map +0 -1
- package/session_pool/events.js +0 -3
- package/session_pool/events.js.map +0 -1
- package/session_pool/index.d.ts.map +0 -1
- package/session_pool/index.js.map +0 -1
- package/session_pool/session.d.ts.map +0 -1
- package/session_pool/session.js.map +0 -1
- package/session_pool/session_pool.d.ts.map +0 -1
- package/session_pool/session_pool.js.map +0 -1
- package/storages/access_checking.d.ts +0 -13
- package/storages/access_checking.d.ts.map +0 -1
- package/storages/access_checking.js +0 -14
- package/storages/access_checking.js.map +0 -1
- package/storages/dataset.d.ts.map +0 -1
- package/storages/dataset.js.map +0 -1
- package/storages/index.d.ts.map +0 -1
- package/storages/index.js.map +0 -1
- package/storages/key_value_store.d.ts.map +0 -1
- package/storages/key_value_store.js.map +0 -1
- package/storages/request_list.d.ts.map +0 -1
- package/storages/request_list.js.map +0 -1
- package/storages/request_list_adapter.d.ts +0 -58
- package/storages/request_list_adapter.d.ts.map +0 -1
- package/storages/request_list_adapter.js +0 -81
- package/storages/request_list_adapter.js.map +0 -1
- package/storages/request_manager_tandem.d.ts.map +0 -1
- package/storages/request_manager_tandem.js.map +0 -1
- package/storages/request_provider.d.ts +0 -371
- package/storages/request_provider.d.ts.map +0 -1
- package/storages/request_provider.js +0 -585
- package/storages/request_provider.js.map +0 -1
- package/storages/request_queue.d.ts.map +0 -1
- package/storages/request_queue.js.map +0 -1
- package/storages/request_queue_v2.d.ts +0 -87
- package/storages/request_queue_v2.d.ts.map +0 -1
- package/storages/request_queue_v2.js +0 -438
- package/storages/request_queue_v2.js.map +0 -1
- package/storages/sitemap_request_list.d.ts.map +0 -1
- package/storages/sitemap_request_list.js +0 -430
- package/storages/sitemap_request_list.js.map +0 -1
- package/storages/storage_manager.d.ts +0 -58
- package/storages/storage_manager.d.ts.map +0 -1
- package/storages/storage_manager.js +0 -105
- package/storages/storage_manager.js.map +0 -1
- package/storages/utils.d.ts.map +0 -1
- package/storages/utils.js.map +0 -1
- package/typedefs.d.ts.map +0 -1
- package/typedefs.js.map +0 -1
- package/validators.d.ts.map +0 -1
- package/validators.js.map +0 -1
package/storages/dataset.js
CHANGED
|
@@ -1,80 +1,40 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
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 {
|
|
6
|
-
import {
|
|
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 {
|
|
9
|
-
import {
|
|
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
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
91
|
+
configuration;
|
|
132
92
|
id;
|
|
133
93
|
name;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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,
|
|
141
|
-
this.
|
|
142
|
-
this.id = options.id;
|
|
143
|
-
this.name = options.name;
|
|
144
|
-
this.
|
|
145
|
-
this.
|
|
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,38 @@ 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
|
|
126
|
+
* The objects must be serializable to JSON.
|
|
170
127
|
*/
|
|
171
128
|
async pushData(data) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
await dispatch(payload);
|
|
180
|
-
return;
|
|
129
|
+
tryCancel();
|
|
130
|
+
const transaction = activeStorageTransaction();
|
|
131
|
+
parseArgument(data, schemas.anyObject);
|
|
132
|
+
// Normalize to array and validate each item
|
|
133
|
+
const items = Array.isArray(data) ? data : [data];
|
|
134
|
+
for (let i = 0; i < items.length; i++) {
|
|
135
|
+
assertJsonSerializable(items[i], i);
|
|
181
136
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
137
|
+
if (transaction) {
|
|
138
|
+
// One snapshot serves both the reads and the commit replay, so the two cannot disagree.
|
|
139
|
+
transaction.recordJournalEntry({
|
|
140
|
+
type: 'dataset',
|
|
141
|
+
participant: this,
|
|
142
|
+
storageId: this.id,
|
|
143
|
+
items: snapshotValue(items),
|
|
144
|
+
recordedAt: new Date(),
|
|
145
|
+
});
|
|
146
|
+
return;
|
|
188
147
|
}
|
|
148
|
+
this.#statsTracker.add('writeCount');
|
|
149
|
+
await this.backend.pushData(items);
|
|
189
150
|
}
|
|
190
151
|
/**
|
|
191
152
|
* Returns {@link DatasetContent} object holding the items in the dataset based on the provided parameters.
|
|
192
153
|
*/
|
|
193
154
|
async getData(options = {}) {
|
|
194
|
-
checkStorageAccess();
|
|
195
155
|
try {
|
|
196
|
-
return await this.
|
|
156
|
+
return await this.readPage(options);
|
|
197
157
|
}
|
|
198
158
|
catch (e) {
|
|
199
159
|
const error = e;
|
|
@@ -203,25 +163,97 @@ export class Dataset {
|
|
|
203
163
|
throw e;
|
|
204
164
|
}
|
|
205
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* The single transaction-aware page read all dataset read paths go through — both `getData()` and
|
|
168
|
+
* the private `fetchPages()`. Returns the real page concatenated with the current transaction's
|
|
169
|
+
* buffered items, with `offset` / `limit` / `desc` windowing applied across the concatenation.
|
|
170
|
+
*/
|
|
171
|
+
async readPage(options) {
|
|
172
|
+
const buffered = this.bufferedJournalEntries()?.flatMap((entry) => entry.items);
|
|
173
|
+
// Every branch below hits the backend exactly once.
|
|
174
|
+
this.#statsTracker.add('readCount');
|
|
175
|
+
if (!buffered?.length) {
|
|
176
|
+
return this.backend.getData(options);
|
|
177
|
+
}
|
|
178
|
+
const { offset = 0, limit, desc = false } = options;
|
|
179
|
+
if (!desc) {
|
|
180
|
+
const realPage = await this.backend.getData(options);
|
|
181
|
+
// Buffered items sit past `realPage.total`, so the window bounds must come from that - not
|
|
182
|
+
// from the page's shortfall, which `skipEmpty` produces without exhausting the real items.
|
|
183
|
+
const bufferedStart = Math.max(0, offset - realPage.total);
|
|
184
|
+
const bufferedEnd = limit === undefined ? buffered.length : Math.max(0, offset + limit - realPage.total);
|
|
185
|
+
const items = [...realPage.items, ...buffered.slice(bufferedStart, bufferedEnd)];
|
|
186
|
+
return {
|
|
187
|
+
items,
|
|
188
|
+
total: realPage.total + buffered.length,
|
|
189
|
+
offset,
|
|
190
|
+
// A caller that passed no limit wants everything, so the backend-reported `limit` is
|
|
191
|
+
// passed through - backends are free to report a page size or a sentinel there.
|
|
192
|
+
limit: limit ?? realPage.limit,
|
|
193
|
+
count: items.length,
|
|
194
|
+
desc,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
// Descending order: the buffered items are the newest, so they come first, reversed.
|
|
198
|
+
const reversedBuffer = [...buffered].reverse();
|
|
199
|
+
const fromBuffer = limit === undefined ? reversedBuffer.slice(offset) : reversedBuffer.slice(offset, offset + limit);
|
|
200
|
+
const needed = limit === undefined ? Infinity : limit - fromBuffer.length;
|
|
201
|
+
if (needed <= 0) {
|
|
202
|
+
// The whole window is served from the buffer; only the real total is missing.
|
|
203
|
+
const { itemCount } = await this.backend.getMetadata();
|
|
204
|
+
return {
|
|
205
|
+
items: fromBuffer,
|
|
206
|
+
total: itemCount + buffered.length,
|
|
207
|
+
offset,
|
|
208
|
+
limit: limit,
|
|
209
|
+
count: fromBuffer.length,
|
|
210
|
+
desc,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const realPage = await this.backend.getData({
|
|
214
|
+
...options,
|
|
215
|
+
offset: Math.max(0, offset - buffered.length),
|
|
216
|
+
...(limit === undefined ? {} : { limit: needed }),
|
|
217
|
+
});
|
|
218
|
+
return {
|
|
219
|
+
items: [...fromBuffer, ...realPage.items],
|
|
220
|
+
total: realPage.total + buffered.length,
|
|
221
|
+
offset,
|
|
222
|
+
limit: limit ?? realPage.limit,
|
|
223
|
+
count: fromBuffer.length + realPage.items.length,
|
|
224
|
+
desc,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/** The active transaction's buffered writes to this dataset, derived from its journal. */
|
|
228
|
+
bufferedJournalEntries() {
|
|
229
|
+
const transaction = activeStorageTransaction();
|
|
230
|
+
return transaction?.journal.filter((entry) => entry.type === 'dataset' && entry.participant === this);
|
|
231
|
+
}
|
|
232
|
+
/** @internal */
|
|
233
|
+
async commitJournalEntries(entries) {
|
|
234
|
+
const items = [];
|
|
235
|
+
for (const entry of entries) {
|
|
236
|
+
if (entry.type === 'dataset') {
|
|
237
|
+
items.push(...entry.items);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// One backend call with all journaled items, in order - as close to atomic as the backend allows.
|
|
241
|
+
// Straight to the backend: the items were validated and snapshotted at write time.
|
|
242
|
+
if (items.length > 0) {
|
|
243
|
+
this.#statsTracker.add('writeCount');
|
|
244
|
+
await this.backend.pushData(items);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
206
247
|
/**
|
|
207
248
|
* Returns all the data from the dataset. This will iterate through the whole dataset
|
|
208
249
|
* via the `listItems()` client method, which gives you only paginated results.
|
|
209
250
|
*/
|
|
210
251
|
async export(options = {}) {
|
|
211
|
-
|
|
252
|
+
tryCancel();
|
|
212
253
|
const items = [];
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
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();
|
|
254
|
+
for await (const page of this.fetchPages(options)) {
|
|
255
|
+
items.push(...page.items);
|
|
256
|
+
}
|
|
225
257
|
return items;
|
|
226
258
|
}
|
|
227
259
|
/**
|
|
@@ -232,7 +264,7 @@ export class Dataset {
|
|
|
232
264
|
* @param [contentType] Only JSON and CSV are supported currently, defaults to JSON.
|
|
233
265
|
*/
|
|
234
266
|
async exportTo(key, options, contentType) {
|
|
235
|
-
const kvStore = await KeyValueStore.open(options?.toKVS ?? null, {
|
|
267
|
+
const kvStore = await KeyValueStore.open(options?.toKVS ?? null, { configuration: this.configuration });
|
|
236
268
|
const items = await this.export(options);
|
|
237
269
|
if (contentType === 'text/csv') {
|
|
238
270
|
// To handle empty dataset exports gracefully.
|
|
@@ -243,6 +275,7 @@ export class Dataset {
|
|
|
243
275
|
const keys = options?.collectAllKeys
|
|
244
276
|
? Array.from(new Set(items.flatMap(Object.keys)))
|
|
245
277
|
: Object.keys(items[0]);
|
|
278
|
+
const { stringify } = await import('csv-stringify/sync');
|
|
246
279
|
const value = stringify([
|
|
247
280
|
keys,
|
|
248
281
|
...items.map((item) => {
|
|
@@ -257,7 +290,6 @@ export class Dataset {
|
|
|
257
290
|
return items;
|
|
258
291
|
}
|
|
259
292
|
throw new Error(`Unsupported content type: ${contentType}`);
|
|
260
|
-
return items;
|
|
261
293
|
}
|
|
262
294
|
/**
|
|
263
295
|
* Save entire default dataset's contents into one JSON file within a key-value store.
|
|
@@ -284,7 +316,7 @@ export class Dataset {
|
|
|
284
316
|
* @param [options] An optional options object where you can provide the dataset and target KVS name.
|
|
285
317
|
*/
|
|
286
318
|
static async exportToJSON(key, options) {
|
|
287
|
-
|
|
319
|
+
tryCancel();
|
|
288
320
|
const dataset = await this.open(options?.fromDataset);
|
|
289
321
|
await dataset.exportToJSON(key, options);
|
|
290
322
|
}
|
|
@@ -295,35 +327,39 @@ export class Dataset {
|
|
|
295
327
|
* @param [options] An optional options object where you can provide the dataset and target KVS name.
|
|
296
328
|
*/
|
|
297
329
|
static async exportToCSV(key, options) {
|
|
298
|
-
|
|
330
|
+
tryCancel();
|
|
299
331
|
const dataset = await this.open(options?.fromDataset);
|
|
300
332
|
await dataset.exportToCSV(key, options);
|
|
301
333
|
}
|
|
302
334
|
/**
|
|
303
335
|
* Returns an object containing general information about the dataset.
|
|
304
336
|
*
|
|
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
337
|
* **Example:**
|
|
312
338
|
* ```
|
|
313
339
|
* {
|
|
314
340
|
* id: "WkzbQMuFYuamGv3YF",
|
|
315
341
|
* name: "my-dataset",
|
|
316
|
-
* userId: "wRsJZtadYvn4mBZmm",
|
|
317
342
|
* createdAt: new Date("2015-12-12T07:34:14.202Z"),
|
|
318
343
|
* modifiedAt: new Date("2015-12-13T08:36:13.202Z"),
|
|
319
344
|
* accessedAt: new Date("2015-12-14T08:36:13.202Z"),
|
|
320
345
|
* itemCount: 14,
|
|
321
346
|
* }
|
|
322
347
|
* ```
|
|
348
|
+
*
|
|
349
|
+
* @throws If the underlying storage no longer exists (e.g. it was deleted externally).
|
|
323
350
|
*/
|
|
324
351
|
async getInfo() {
|
|
325
|
-
|
|
326
|
-
|
|
352
|
+
const buffered = this.bufferedJournalEntries();
|
|
353
|
+
const metadata = await this.backend.getMetadata();
|
|
354
|
+
if (buffered?.length) {
|
|
355
|
+
const lastWriteAt = buffered[buffered.length - 1].recordedAt;
|
|
356
|
+
return {
|
|
357
|
+
...metadata,
|
|
358
|
+
itemCount: metadata.itemCount + buffered.reduce((sum, entry) => sum + entry.items.length, 0),
|
|
359
|
+
modifiedAt: metadata.modifiedAt > lastWriteAt ? metadata.modifiedAt : lastWriteAt,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
return metadata;
|
|
327
363
|
}
|
|
328
364
|
/**
|
|
329
365
|
* Iterates over dataset items, yielding each in turn to an `iteratee` function.
|
|
@@ -346,7 +382,7 @@ export class Dataset {
|
|
|
346
382
|
* @default 0
|
|
347
383
|
*/
|
|
348
384
|
async forEach(iteratee, options = {}, index = 0) {
|
|
349
|
-
|
|
385
|
+
tryCancel();
|
|
350
386
|
if (!options.offset)
|
|
351
387
|
options.offset = 0;
|
|
352
388
|
if (options.format && options.format !== 'json')
|
|
@@ -373,7 +409,7 @@ export class Dataset {
|
|
|
373
409
|
* @param [options] All `map()` parameters.
|
|
374
410
|
*/
|
|
375
411
|
async map(iteratee, options = {}) {
|
|
376
|
-
|
|
412
|
+
tryCancel();
|
|
377
413
|
const result = [];
|
|
378
414
|
await this.forEach(async (item, index) => {
|
|
379
415
|
const res = await iteratee(item, index);
|
|
@@ -382,7 +418,7 @@ export class Dataset {
|
|
|
382
418
|
return result;
|
|
383
419
|
}
|
|
384
420
|
async reduce(iteratee, memo, options = {}) {
|
|
385
|
-
|
|
421
|
+
tryCancel();
|
|
386
422
|
let currentMemo = memo;
|
|
387
423
|
const wrappedFunc = async (item, index) => {
|
|
388
424
|
if (index === 0 && currentMemo === undefined) {
|
|
@@ -397,15 +433,114 @@ export class Dataset {
|
|
|
397
433
|
await this.forEach(wrappedFunc, options);
|
|
398
434
|
return currentMemo;
|
|
399
435
|
}
|
|
436
|
+
async *fetchEntryPages(options) {
|
|
437
|
+
let index = options.offset ?? 0;
|
|
438
|
+
for await (const page of this.fetchPages(options)) {
|
|
439
|
+
yield {
|
|
440
|
+
...page,
|
|
441
|
+
items: page.items.map((item) => [index++, item]),
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
async *fetchPages(options, pageSize = DATASET_ITERATORS_DEFAULT_LIMIT) {
|
|
446
|
+
let offset = options.offset ?? 0;
|
|
447
|
+
const totalLimit = options.limit;
|
|
448
|
+
let yielded = 0;
|
|
449
|
+
while (true) {
|
|
450
|
+
const fetchLimit = totalLimit !== undefined ? Math.min(pageSize, totalLimit - yielded) : pageSize;
|
|
451
|
+
if (fetchLimit <= 0)
|
|
452
|
+
break;
|
|
453
|
+
const page = await this.readPage({ ...options, offset, limit: fetchLimit });
|
|
454
|
+
yield page;
|
|
455
|
+
yielded += page.items.length;
|
|
456
|
+
if (page.items.length < fetchLimit || offset + page.items.length >= page.total)
|
|
457
|
+
break;
|
|
458
|
+
offset += page.items.length;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Returns dataset items.
|
|
463
|
+
*
|
|
464
|
+
* When awaited (`await dataset.values()`), returns all items as a flat `Data[]` array.
|
|
465
|
+
* When used as an async iterable (`for await...of`), iterates over all items across pages
|
|
466
|
+
* without loading everything into memory at once.
|
|
467
|
+
*
|
|
468
|
+
* **Example usage:**
|
|
469
|
+
* ```javascript
|
|
470
|
+
* const dataset = await Dataset.open('my-results');
|
|
471
|
+
*
|
|
472
|
+
* // Iterate over all items (memory-efficient for large datasets)
|
|
473
|
+
* for await (const item of dataset.values()) {
|
|
474
|
+
* console.log(item);
|
|
475
|
+
* }
|
|
476
|
+
*
|
|
477
|
+
* // Or fetch all items at once
|
|
478
|
+
* const items = await dataset.values();
|
|
479
|
+
* console.log(items);
|
|
480
|
+
* ```
|
|
481
|
+
*
|
|
482
|
+
* @param options Options for the iteration.
|
|
483
|
+
*/
|
|
484
|
+
values(options = {}) {
|
|
485
|
+
tryCancel();
|
|
486
|
+
return createDualIterable({
|
|
487
|
+
createPages: () => this.fetchPages(options),
|
|
488
|
+
extractItems: (page) => page.items,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Returns dataset entries (index-value pairs).
|
|
493
|
+
*
|
|
494
|
+
* When awaited (`await dataset.entries()`), returns all entries as a flat `[index, item][]` array.
|
|
495
|
+
* When used as an async iterable (`for await...of`), iterates over all entries across pages
|
|
496
|
+
* without loading everything into memory at once.
|
|
497
|
+
*
|
|
498
|
+
* **Example usage:**
|
|
499
|
+
* ```javascript
|
|
500
|
+
* const dataset = await Dataset.open('my-results');
|
|
501
|
+
*
|
|
502
|
+
* // Iterate over all entries
|
|
503
|
+
* for await (const [index, item] of dataset.entries()) {
|
|
504
|
+
* console.log(`Item at ${index}: ${JSON.stringify(item)}`);
|
|
505
|
+
* }
|
|
506
|
+
*
|
|
507
|
+
* // Or fetch all at once
|
|
508
|
+
* const entries = await dataset.entries();
|
|
509
|
+
* console.log(entries);
|
|
510
|
+
* ```
|
|
511
|
+
*
|
|
512
|
+
* @param options Options for the iteration.
|
|
513
|
+
*/
|
|
514
|
+
entries(options = {}) {
|
|
515
|
+
tryCancel();
|
|
516
|
+
return createDualIterable({
|
|
517
|
+
createPages: () => this.fetchEntryPages(options),
|
|
518
|
+
extractItems: (page) => page.items,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Default async iterator for the dataset, iterating over items.
|
|
523
|
+
* Allows using the dataset directly in a `for await...of` loop.
|
|
524
|
+
*
|
|
525
|
+
* **Example usage:**
|
|
526
|
+
* ```javascript
|
|
527
|
+
* const dataset = await Dataset.open('my-results');
|
|
528
|
+
* for await (const item of dataset) {
|
|
529
|
+
* console.log(item);
|
|
530
|
+
* }
|
|
531
|
+
* ```
|
|
532
|
+
*/
|
|
533
|
+
async *[Symbol.asyncIterator]() {
|
|
534
|
+
yield* this.values();
|
|
535
|
+
}
|
|
400
536
|
/**
|
|
401
537
|
* Removes the dataset either from the Apify cloud storage or from the local directory,
|
|
402
538
|
* depending on the mode of operation.
|
|
403
539
|
*/
|
|
404
540
|
async drop() {
|
|
405
|
-
|
|
406
|
-
await this.
|
|
407
|
-
|
|
408
|
-
manager.closeStorage(this);
|
|
541
|
+
rejectOperationInTransaction('Dataset.drop()');
|
|
542
|
+
await this.backend.drop();
|
|
543
|
+
serviceLocator.getStorageInstanceManager().removeFromCache(this);
|
|
409
544
|
}
|
|
410
545
|
/**
|
|
411
546
|
* Opens a dataset and returns a promise resolving to an instance of the {@link Dataset} class.
|
|
@@ -416,23 +551,24 @@ export class Dataset {
|
|
|
416
551
|
*
|
|
417
552
|
* For more details and code examples, see the {@link Dataset} class.
|
|
418
553
|
*
|
|
419
|
-
* @param [
|
|
420
|
-
* ID or name of the dataset to be opened. If
|
|
421
|
-
*
|
|
554
|
+
* @param [identifier]
|
|
555
|
+
* ID or name of the dataset to be opened. If a string is provided, it will first be
|
|
556
|
+
* looked up as an ID; if no such storage exists, it will be treated as a name.
|
|
557
|
+
* If `null` or `undefined`, the function returns the default dataset associated with the crawler run.
|
|
422
558
|
* @param [options] Storage manager options.
|
|
423
559
|
*/
|
|
424
|
-
static async open(
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
560
|
+
static async open(identifier, options = {}) {
|
|
561
|
+
tryCancel();
|
|
562
|
+
const parsedOptions = parseArgument(options, openOptionsSchema);
|
|
563
|
+
const configuration = parsedOptions.configuration ?? Configuration.getGlobalConfiguration();
|
|
564
|
+
const storageBackend = parsedOptions.storageBackend ?? serviceLocator.getStorageBackend();
|
|
565
|
+
await purgeDefaultStorages({ onlyPurgeOnce: true, storageBackend, configuration });
|
|
566
|
+
const resolved = await resolveStorageIdentifier(identifier, storageBackend, 'Dataset');
|
|
567
|
+
return serviceLocator.getStorageInstanceManager().openStorage(this, {
|
|
568
|
+
...resolved,
|
|
569
|
+
backendOpener: () => storageBackend.createDatasetBackend(resolved),
|
|
570
|
+
backendCacheKey: storageBackend.getStorageBackendCacheKey?.() ?? storageBackend.constructor.name,
|
|
571
|
+
});
|
|
436
572
|
}
|
|
437
573
|
/**
|
|
438
574
|
* Stores an object or an array of objects to the default {@link Dataset} of the current crawler run.
|
|
@@ -470,4 +606,3 @@ export class Dataset {
|
|
|
470
606
|
return dataset.getData(options);
|
|
471
607
|
}
|
|
472
608
|
}
|
|
473
|
-
//# sourceMappingURL=dataset.js.map
|
package/storages/index.d.ts
CHANGED
|
@@ -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 './
|
|
5
|
-
export * from './
|
|
6
|
-
export
|
|
7
|
-
export
|
|
8
|
-
export
|
|
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 './
|
|
12
|
-
export * from './
|
|
11
|
+
export * from './transaction.js';
|
|
12
|
+
export * from './sitemap_request_loader.js';
|
|
13
13
|
export * from './request_manager_tandem.js';
|
|
14
|
-
|
|
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 './
|
|
5
|
-
export * from './
|
|
6
|
-
export
|
|
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 './
|
|
12
|
-
export * from './
|
|
9
|
+
export * from './transaction.js';
|
|
10
|
+
export * from './sitemap_request_loader.js';
|
|
13
11
|
export * from './request_manager_tandem.js';
|
|
14
|
-
|
|
12
|
+
export * from './throttling_request_manager.js';
|