@crawlee/core 4.0.0-beta.28 → 4.0.0-beta.280
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 +15 -15
- package/byte-utils.d.ts +17 -0
- package/byte-utils.js +42 -0
- package/configuration.d.ts +75 -167
- package/configuration.js +164 -140
- package/debug.d.ts +36 -0
- package/debug.js +70 -0
- package/errors.d.ts +40 -19
- package/errors.js +52 -30
- package/events/{event_manager.d.ts → event-manager.d.ts} +29 -6
- package/events/{event_manager.js → event-manager.js} +10 -9
- package/events/index.d.ts +3 -3
- package/events/index.js +2 -3
- package/events/{local_event_manager.d.ts → local-event-manager.d.ts} +3 -10
- package/events/local-event-manager.js +101 -0
- package/events/system-info.d.ts +38 -0
- package/events/system-info.js +1 -0
- package/index.d.ts +9 -13
- package/index.js +10 -12
- package/internal.d.ts +8 -0
- package/internal.js +9 -0
- package/iterables.d.ts +79 -0
- package/iterables.js +134 -0
- package/log.d.ts +77 -1
- package/log.js +134 -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 +43 -0
- package/memory-storage/memory-storage.js +158 -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 +35 -0
- package/memory-storage/resource-clients/dataset.js +108 -0
- package/memory-storage/resource-clients/key-value-store.d.ts +54 -0
- package/memory-storage/resource-clients/key-value-store.js +179 -0
- package/memory-storage/resource-clients/request-queue.d.ts +55 -0
- package/memory-storage/resource-clients/request-queue.js +413 -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 +23 -23
- package/proxy-configuration.d.ts +118 -0
- package/proxy-configuration.js +141 -0
- package/recoverable-state.d.ts +177 -0
- package/recoverable-state.js +268 -0
- package/request.d.ts +48 -101
- package/request.js +139 -249
- package/serialization.d.ts +0 -1
- package/serialization.js +18 -18
- package/{service_locator.d.ts → service-locator.d.ts} +66 -37
- package/service-locator.js +255 -0
- package/storages/batched-adds.d.ts +39 -0
- package/storages/batched-adds.js +81 -0
- package/storages/dataset.d.ts +105 -58
- package/storages/dataset.js +292 -151
- package/storages/index.d.ts +11 -12
- package/storages/index.js +7 -12
- package/storages/key-value-store-codec.d.ts +32 -0
- package/storages/key-value-store-codec.js +108 -0
- package/storages/{key_value_store.d.ts → key-value-store.d.ts} +169 -72
- package/storages/key-value-store.js +767 -0
- package/storages/request-dedup-cache.d.ts +20 -0
- package/storages/request-dedup-cache.js +48 -0
- package/storages/{request_list.d.ts → request-list.d.ts} +32 -171
- package/storages/{request_list.js → request-list.js} +276 -253
- package/storages/request-loader.d.ts +127 -0
- package/storages/request-loader.js +34 -0
- package/storages/request-manager-tandem.d.ts +82 -0
- package/storages/request-manager-tandem.js +200 -0
- package/storages/request-manager.d.ts +119 -0
- package/storages/request-manager.js +1 -0
- package/storages/{request_provider.d.ts → request-queue.d.ts} +127 -161
- package/storages/request-queue.js +871 -0
- package/storages/storage-instance-manager.d.ts +86 -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/transaction.d.ts +270 -0
- package/storages/transaction.js +296 -0
- package/storages/utils.d.ts +59 -11
- package/storages/utils.js +76 -17
- 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 +23 -18
- package/validators.js +14 -18
- package/autoscaling/autoscaled_pool.d.ts +0 -298
- package/autoscaling/autoscaled_pool.d.ts.map +0 -1
- package/autoscaling/autoscaled_pool.js +0 -527
- package/autoscaling/autoscaled_pool.js.map +0 -1
- package/autoscaling/index.d.ts +0 -4
- package/autoscaling/index.d.ts.map +0 -1
- package/autoscaling/index.js +0 -4
- package/autoscaling/index.js.map +0 -1
- package/autoscaling/snapshotter.d.ts +0 -179
- package/autoscaling/snapshotter.d.ts.map +0 -1
- package/autoscaling/snapshotter.js +0 -279
- package/autoscaling/snapshotter.js.map +0 -1
- package/autoscaling/system_status.d.ts +0 -178
- package/autoscaling/system_status.d.ts.map +0 -1
- package/autoscaling/system_status.js +0 -168
- 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 +0 -43
- package/cookie_utils.d.ts.map +0 -1
- package/cookie_utils.js +0 -111
- package/cookie_utils.js.map +0 -1
- package/crawlers/context_pipeline.d.ts +0 -61
- package/crawlers/context_pipeline.d.ts.map +0 -1
- package/crawlers/context_pipeline.js +0 -99
- package/crawlers/context_pipeline.js.map +0 -1
- package/crawlers/crawler_commons.d.ts +0 -191
- package/crawlers/crawler_commons.d.ts.map +0 -1
- package/crawlers/crawler_commons.js +0 -108
- 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 +0 -60
- package/crawlers/error_snapshotter.d.ts.map +0 -1
- package/crawlers/error_snapshotter.js +0 -118
- package/crawlers/error_snapshotter.js.map +0 -1
- package/crawlers/error_tracker.d.ts +0 -55
- package/crawlers/error_tracker.d.ts.map +0 -1
- package/crawlers/error_tracker.js +0 -309
- package/crawlers/error_tracker.js.map +0 -1
- package/crawlers/index.d.ts +0 -7
- package/crawlers/index.d.ts.map +0 -1
- package/crawlers/index.js +0 -7
- package/crawlers/index.js.map +0 -1
- package/crawlers/internals/types.d.ts +0 -8
- package/crawlers/internals/types.d.ts.map +0 -1
- package/crawlers/internals/types.js +0 -2
- package/crawlers/internals/types.js.map +0 -1
- package/crawlers/statistics.d.ts +0 -204
- package/crawlers/statistics.d.ts.map +0 -1
- package/crawlers/statistics.js +0 -350
- package/crawlers/statistics.js.map +0 -1
- package/enqueue_links/enqueue_links.d.ts +0 -254
- package/enqueue_links/enqueue_links.d.ts.map +0 -1
- package/enqueue_links/enqueue_links.js +0 -268
- package/enqueue_links/enqueue_links.js.map +0 -1
- package/enqueue_links/index.d.ts +0 -3
- package/enqueue_links/index.d.ts.map +0 -1
- package/enqueue_links/index.js +0 -3
- package/enqueue_links/index.js.map +0 -1
- package/enqueue_links/shared.d.ts +0 -84
- package/enqueue_links/shared.d.ts.map +0 -1
- package/enqueue_links/shared.js +0 -219
- 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 +0 -93
- package/events/local_event_manager.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 +0 -169
- package/proxy_configuration.d.ts.map +0 -1
- package/proxy_configuration.js +0 -247
- package/proxy_configuration.js.map +0 -1
- package/recoverable_state.d.ts +0 -122
- package/recoverable_state.d.ts.map +0 -1
- package/recoverable_state.js +0 -138
- package/recoverable_state.js.map +0 -1
- package/request.d.ts.map +0 -1
- package/request.js.map +0 -1
- package/router.d.ts +0 -134
- package/router.d.ts.map +0 -1
- package/router.js +0 -164
- package/router.js.map +0 -1
- package/serialization.d.ts.map +0 -1
- package/serialization.js.map +0 -1
- package/service_locator.d.ts.map +0 -1
- package/service_locator.js +0 -252
- package/service_locator.js.map +0 -1
- package/session_pool/consts.d.ts +0 -4
- package/session_pool/consts.d.ts.map +0 -1
- package/session_pool/consts.js +0 -4
- package/session_pool/consts.js.map +0 -1
- package/session_pool/errors.d.ts +0 -8
- package/session_pool/errors.d.ts.map +0 -1
- package/session_pool/errors.js +0 -12
- 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 +0 -6
- package/session_pool/index.d.ts.map +0 -1
- package/session_pool/index.js +0 -6
- package/session_pool/index.js.map +0 -1
- package/session_pool/session.d.ts +0 -186
- package/session_pool/session.d.ts.map +0 -1
- package/session_pool/session.js +0 -278
- package/session_pool/session.js.map +0 -1
- package/session_pool/session_pool.d.ts +0 -256
- package/session_pool/session_pool.d.ts.map +0 -1
- package/session_pool/session_pool.js +0 -386
- 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 +0 -513
- 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 +0 -68
- package/storages/request_manager_tandem.d.ts.map +0 -1
- package/storages/request_manager_tandem.js +0 -124
- package/storages/request_manager_tandem.js.map +0 -1
- package/storages/request_provider.d.ts.map +0 -1
- package/storages/request_provider.js +0 -592
- package/storages/request_provider.js.map +0 -1
- package/storages/request_queue.d.ts +0 -128
- package/storages/request_queue.d.ts.map +0 -1
- package/storages/request_queue.js +0 -320
- 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 -437
- package/storages/request_queue_v2.js.map +0 -1
- package/storages/sitemap_request_list.d.ts +0 -245
- package/storages/sitemap_request_list.d.ts.map +0 -1
- package/storages/sitemap_request_list.js +0 -431
- package/storages/sitemap_request_list.js.map +0 -1
- package/storages/storage_manager.d.ts +0 -60
- package/storages/storage_manager.d.ts.map +0 -1
- package/storages/storage_manager.js +0 -95
- 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,81 +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 {
|
|
7
|
-
import {
|
|
8
|
-
import { KeyValueStore } from './
|
|
9
|
-
import {
|
|
10
|
-
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
|
+
import { KeyValueStore } from './key-value-store.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
|
+
});
|
|
11
15
|
/** @internal */
|
|
12
16
|
export const DATASET_ITERATORS_DEFAULT_LIMIT = 10000;
|
|
13
|
-
const SAFETY_BUFFER_PERCENT = 0.01 / 100; // 0.01%
|
|
14
17
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
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.
|
|
18
23
|
* @ignore
|
|
19
24
|
*/
|
|
20
|
-
export function
|
|
25
|
+
export function assertJsonSerializable(item, index) {
|
|
21
26
|
const s = typeof index === 'number' ? ` at index ${index} ` : ' ';
|
|
22
27
|
const isItemObject = item && typeof item === 'object' && !Array.isArray(item);
|
|
23
28
|
if (!isItemObject) {
|
|
24
29
|
throw new Error(`Data item${s}is not an object. You can push only objects into a dataset.`);
|
|
25
30
|
}
|
|
26
|
-
let payload;
|
|
27
31
|
try {
|
|
28
|
-
|
|
32
|
+
JSON.stringify(item);
|
|
29
33
|
}
|
|
30
34
|
catch (e) {
|
|
31
35
|
const err = e;
|
|
32
36
|
throw new Error(`Data item${s}is not serializable to JSON.\nCause: ${err.message}`);
|
|
33
37
|
}
|
|
34
|
-
const bytes = Buffer.byteLength(payload);
|
|
35
|
-
if (bytes > limitBytes) {
|
|
36
|
-
throw new Error(`Data item${s}is too large (size: ${bytes} bytes, limit: ${limitBytes} bytes)`);
|
|
37
|
-
}
|
|
38
|
-
return payload;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Takes an array of JSONs (payloads) as input and produces an array of JSON strings
|
|
42
|
-
* where each string is a JSON array of payloads with a maximum size of limitBytes per one
|
|
43
|
-
* JSON array. Fits as many payloads as possible into a single JSON array and then moves
|
|
44
|
-
* on to the next, preserving item order.
|
|
45
|
-
*
|
|
46
|
-
* The function assumes that none of the items is larger than limitBytes and does not validate.
|
|
47
|
-
* @ignore
|
|
48
|
-
*/
|
|
49
|
-
export function chunkBySize(items, limitBytes) {
|
|
50
|
-
if (!items.length)
|
|
51
|
-
return [];
|
|
52
|
-
if (items.length === 1)
|
|
53
|
-
return items;
|
|
54
|
-
// Split payloads into buckets of valid size.
|
|
55
|
-
let lastChunkBytes = 2; // Add 2 bytes for [] wrapper.
|
|
56
|
-
const chunks = [];
|
|
57
|
-
for (const payload of items) {
|
|
58
|
-
const bytes = Buffer.byteLength(payload);
|
|
59
|
-
if (bytes <= limitBytes && bytes + 2 > limitBytes) {
|
|
60
|
-
// Handle cases where wrapping with [] would fail, but solo object is fine.
|
|
61
|
-
chunks.push(payload);
|
|
62
|
-
lastChunkBytes = bytes;
|
|
63
|
-
}
|
|
64
|
-
else if (lastChunkBytes + bytes <= limitBytes) {
|
|
65
|
-
// ensure array
|
|
66
|
-
if (!Array.isArray(chunks[chunks.length - 1])) {
|
|
67
|
-
chunks.push([]);
|
|
68
|
-
}
|
|
69
|
-
chunks[chunks.length - 1].push(payload);
|
|
70
|
-
lastChunkBytes += bytes + 1; // Add 1 byte for ',' separator.
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
chunks.push([payload]);
|
|
74
|
-
lastChunkBytes = bytes + 2; // Add 2 bytes for [] wrapper.
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
// Stringify array chunks.
|
|
78
|
-
return chunks.map((chunk) => (typeof chunk === 'string' ? chunk : `[${chunk.join(',')}]`));
|
|
79
38
|
}
|
|
80
39
|
/**
|
|
81
40
|
* The `Dataset` class represents a store for structured data where each object stored has the same attributes,
|
|
@@ -129,21 +88,30 @@ export function chunkBySize(items, limitBytes) {
|
|
|
129
88
|
* @category Result Stores
|
|
130
89
|
*/
|
|
131
90
|
export class Dataset {
|
|
132
|
-
|
|
91
|
+
configuration;
|
|
133
92
|
id;
|
|
134
93
|
name;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
94
|
+
// oxlint-disable-next-line crawlee/prefer-private-fields -- tests spy on the backend directly
|
|
95
|
+
backend;
|
|
96
|
+
#statsTracker = new StorageStatsTracker({
|
|
97
|
+
readCount: 0,
|
|
98
|
+
writeCount: 0,
|
|
99
|
+
});
|
|
138
100
|
/**
|
|
139
101
|
* @internal
|
|
140
102
|
*/
|
|
141
|
-
constructor(options,
|
|
142
|
-
this.
|
|
143
|
-
this.id = options.id;
|
|
144
|
-
this.name = options.name;
|
|
145
|
-
this.
|
|
146
|
-
|
|
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
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Backend-independent usage counters tracked for this dataset (read / write operations issued to
|
|
111
|
+
* the underlying storage backend). Counted per backend call.
|
|
112
|
+
*/
|
|
113
|
+
get stats() {
|
|
114
|
+
return this.#statsTracker.current;
|
|
147
115
|
}
|
|
148
116
|
/**
|
|
149
117
|
* Stores an object or an array of objects to the dataset.
|
|
@@ -153,48 +121,38 @@ export class Dataset {
|
|
|
153
121
|
* **IMPORTANT**: Make sure to use the `await` keyword when calling `pushData()`,
|
|
154
122
|
* otherwise the crawler process might finish before the data is stored!
|
|
155
123
|
*
|
|
156
|
-
* The size of the data is limited by the receiving API and therefore `pushData()` will only
|
|
157
|
-
* allow objects whose JSON representation is smaller than 9MB. When an array is passed,
|
|
158
|
-
* none of the included objects
|
|
159
|
-
* may be larger than 9MB, but the array itself may be of any size.
|
|
160
|
-
*
|
|
161
|
-
* The function internally
|
|
162
|
-
* chunks the array into separate items and pushes them sequentially.
|
|
163
|
-
* The chunking process is stable (keeps order of data), but it does not provide a transaction
|
|
164
|
-
* safety mechanism. Therefore, in the event of an uploading error (after several automatic retries),
|
|
165
|
-
* the function's Promise will reject and the dataset will be left in a state where some of
|
|
166
|
-
* the items have already been saved to the dataset while other items from the source array were not.
|
|
167
|
-
* To overcome this limitation, the developer may, for example, read the last item saved in the dataset
|
|
168
|
-
* and re-attempt the save of the data from this item onwards to prevent duplicates.
|
|
169
124
|
* @param data Object or array of objects containing data to be stored in the default dataset.
|
|
170
|
-
* The objects must be serializable to JSON
|
|
125
|
+
* The objects must be serializable to JSON.
|
|
171
126
|
*/
|
|
172
127
|
async pushData(data) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
await dispatch(payload);
|
|
181
|
-
return;
|
|
128
|
+
tryCancel();
|
|
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);
|
|
182
135
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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;
|
|
189
146
|
}
|
|
147
|
+
this.#statsTracker.add('writeCount');
|
|
148
|
+
await this.backend.pushData(items);
|
|
190
149
|
}
|
|
191
150
|
/**
|
|
192
151
|
* Returns {@link DatasetContent} object holding the items in the dataset based on the provided parameters.
|
|
193
152
|
*/
|
|
194
153
|
async getData(options = {}) {
|
|
195
|
-
checkStorageAccess();
|
|
196
154
|
try {
|
|
197
|
-
return await this
|
|
155
|
+
return await this.#readPage(options);
|
|
198
156
|
}
|
|
199
157
|
catch (e) {
|
|
200
158
|
const error = e;
|
|
@@ -204,25 +162,97 @@ export class Dataset {
|
|
|
204
162
|
throw e;
|
|
205
163
|
}
|
|
206
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
|
+
}
|
|
207
246
|
/**
|
|
208
247
|
* Returns all the data from the dataset. This will iterate through the whole dataset
|
|
209
248
|
* via the `listItems()` client method, which gives you only paginated results.
|
|
210
249
|
*/
|
|
211
250
|
async export(options = {}) {
|
|
212
|
-
|
|
251
|
+
tryCancel();
|
|
213
252
|
const items = [];
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (value.count === 0) {
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
items.push(...value.items);
|
|
221
|
-
if (value.total > offset + value.count) {
|
|
222
|
-
await fetchNextChunk(offset + value.count);
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
await fetchNextChunk();
|
|
253
|
+
for await (const page of this.#fetchPages(options)) {
|
|
254
|
+
items.push(...page.items);
|
|
255
|
+
}
|
|
226
256
|
return items;
|
|
227
257
|
}
|
|
228
258
|
/**
|
|
@@ -233,7 +263,7 @@ export class Dataset {
|
|
|
233
263
|
* @param [contentType] Only JSON and CSV are supported currently, defaults to JSON.
|
|
234
264
|
*/
|
|
235
265
|
async exportTo(key, options, contentType) {
|
|
236
|
-
const kvStore = await KeyValueStore.open(options?.toKVS ?? null, {
|
|
266
|
+
const kvStore = await KeyValueStore.open(options?.toKVS ?? null, { configuration: this.configuration });
|
|
237
267
|
const items = await this.export(options);
|
|
238
268
|
if (contentType === 'text/csv') {
|
|
239
269
|
// To handle empty dataset exports gracefully.
|
|
@@ -244,6 +274,7 @@ export class Dataset {
|
|
|
244
274
|
const keys = options?.collectAllKeys
|
|
245
275
|
? Array.from(new Set(items.flatMap(Object.keys)))
|
|
246
276
|
: Object.keys(items[0]);
|
|
277
|
+
const { stringify } = await import('csv-stringify/sync');
|
|
247
278
|
const value = stringify([
|
|
248
279
|
keys,
|
|
249
280
|
...items.map((item) => {
|
|
@@ -258,7 +289,6 @@ export class Dataset {
|
|
|
258
289
|
return items;
|
|
259
290
|
}
|
|
260
291
|
throw new Error(`Unsupported content type: ${contentType}`);
|
|
261
|
-
return items;
|
|
262
292
|
}
|
|
263
293
|
/**
|
|
264
294
|
* Save entire default dataset's contents into one JSON file within a key-value store.
|
|
@@ -285,7 +315,7 @@ export class Dataset {
|
|
|
285
315
|
* @param [options] An optional options object where you can provide the dataset and target KVS name.
|
|
286
316
|
*/
|
|
287
317
|
static async exportToJSON(key, options) {
|
|
288
|
-
|
|
318
|
+
tryCancel();
|
|
289
319
|
const dataset = await this.open(options?.fromDataset);
|
|
290
320
|
await dataset.exportToJSON(key, options);
|
|
291
321
|
}
|
|
@@ -296,35 +326,39 @@ export class Dataset {
|
|
|
296
326
|
* @param [options] An optional options object where you can provide the dataset and target KVS name.
|
|
297
327
|
*/
|
|
298
328
|
static async exportToCSV(key, options) {
|
|
299
|
-
|
|
329
|
+
tryCancel();
|
|
300
330
|
const dataset = await this.open(options?.fromDataset);
|
|
301
331
|
await dataset.exportToCSV(key, options);
|
|
302
332
|
}
|
|
303
333
|
/**
|
|
304
334
|
* Returns an object containing general information about the dataset.
|
|
305
335
|
*
|
|
306
|
-
* The function returns the same object as the Apify API Client's
|
|
307
|
-
* [getDataset](https://docs.apify.com/api/apify-client-js/latest#ApifyClient-datasets-getDataset)
|
|
308
|
-
* function, which in turn calls the
|
|
309
|
-
* [Get dataset](https://apify.com/docs/api/v2#/reference/datasets/dataset/get-dataset)
|
|
310
|
-
* API endpoint.
|
|
311
|
-
*
|
|
312
336
|
* **Example:**
|
|
313
337
|
* ```
|
|
314
338
|
* {
|
|
315
339
|
* id: "WkzbQMuFYuamGv3YF",
|
|
316
340
|
* name: "my-dataset",
|
|
317
|
-
* userId: "wRsJZtadYvn4mBZmm",
|
|
318
341
|
* createdAt: new Date("2015-12-12T07:34:14.202Z"),
|
|
319
342
|
* modifiedAt: new Date("2015-12-13T08:36:13.202Z"),
|
|
320
343
|
* accessedAt: new Date("2015-12-14T08:36:13.202Z"),
|
|
321
344
|
* itemCount: 14,
|
|
322
345
|
* }
|
|
323
346
|
* ```
|
|
347
|
+
*
|
|
348
|
+
* @throws If the underlying storage no longer exists (e.g. it was deleted externally).
|
|
324
349
|
*/
|
|
325
350
|
async getInfo() {
|
|
326
|
-
|
|
327
|
-
|
|
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;
|
|
328
362
|
}
|
|
329
363
|
/**
|
|
330
364
|
* Iterates over dataset items, yielding each in turn to an `iteratee` function.
|
|
@@ -347,7 +381,7 @@ export class Dataset {
|
|
|
347
381
|
* @default 0
|
|
348
382
|
*/
|
|
349
383
|
async forEach(iteratee, options = {}, index = 0) {
|
|
350
|
-
|
|
384
|
+
tryCancel();
|
|
351
385
|
if (!options.offset)
|
|
352
386
|
options.offset = 0;
|
|
353
387
|
if (options.format && options.format !== 'json')
|
|
@@ -374,7 +408,7 @@ export class Dataset {
|
|
|
374
408
|
* @param [options] All `map()` parameters.
|
|
375
409
|
*/
|
|
376
410
|
async map(iteratee, options = {}) {
|
|
377
|
-
|
|
411
|
+
tryCancel();
|
|
378
412
|
const result = [];
|
|
379
413
|
await this.forEach(async (item, index) => {
|
|
380
414
|
const res = await iteratee(item, index);
|
|
@@ -383,7 +417,7 @@ export class Dataset {
|
|
|
383
417
|
return result;
|
|
384
418
|
}
|
|
385
419
|
async reduce(iteratee, memo, options = {}) {
|
|
386
|
-
|
|
420
|
+
tryCancel();
|
|
387
421
|
let currentMemo = memo;
|
|
388
422
|
const wrappedFunc = async (item, index) => {
|
|
389
423
|
if (index === 0 && currentMemo === undefined) {
|
|
@@ -398,15 +432,122 @@ export class Dataset {
|
|
|
398
432
|
await this.forEach(wrappedFunc, options);
|
|
399
433
|
return currentMemo;
|
|
400
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
|
+
}
|
|
401
535
|
/**
|
|
402
536
|
* Removes the dataset either from the Apify cloud storage or from the local directory,
|
|
403
537
|
* depending on the mode of operation.
|
|
404
538
|
*/
|
|
405
539
|
async drop() {
|
|
406
|
-
|
|
407
|
-
await this.
|
|
408
|
-
|
|
409
|
-
|
|
540
|
+
rejectOperationInTransaction('Dataset.drop()');
|
|
541
|
+
await this.backend.drop();
|
|
542
|
+
serviceLocator.getStorageInstanceManager().removeFromCache(this);
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Removes all items from the dataset but keeps the dataset itself, along with its
|
|
546
|
+
* {@link Dataset.id|`id`} and {@link Dataset.name|`name`}.
|
|
547
|
+
*/
|
|
548
|
+
async purge() {
|
|
549
|
+
rejectOperationInTransaction('Dataset.purge()');
|
|
550
|
+
await this.backend.purge();
|
|
410
551
|
}
|
|
411
552
|
/**
|
|
412
553
|
* Opens a dataset and returns a promise resolving to an instance of the {@link Dataset} class.
|
|
@@ -417,23 +558,24 @@ export class Dataset {
|
|
|
417
558
|
*
|
|
418
559
|
* For more details and code examples, see the {@link Dataset} class.
|
|
419
560
|
*
|
|
420
|
-
* @param [
|
|
421
|
-
* ID or name of the dataset to be opened. If
|
|
422
|
-
*
|
|
561
|
+
* @param [identifier]
|
|
562
|
+
* ID or name of the dataset to be opened. If a string is provided, it will first be
|
|
563
|
+
* looked up as an ID; if no such storage exists, it will be treated as a name.
|
|
564
|
+
* If `null` or `undefined`, the function returns the default dataset associated with the crawler run.
|
|
423
565
|
* @param [options] Storage manager options.
|
|
424
566
|
*/
|
|
425
|
-
static async open(
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
567
|
+
static async open(identifier, options = {}) {
|
|
568
|
+
tryCancel();
|
|
569
|
+
const parsedOptions = parseArgument(options, openOptionsSchema);
|
|
570
|
+
const configuration = parsedOptions.configuration ?? Configuration.getGlobalConfiguration();
|
|
571
|
+
const storageBackend = parsedOptions.storageBackend ?? serviceLocator.getStorageBackend();
|
|
572
|
+
await purgeDefaultStorages({ onlyPurgeOnce: true, storageBackend, configuration });
|
|
573
|
+
const resolved = await resolveStorageIdentifier(identifier, storageBackend, 'Dataset');
|
|
574
|
+
return serviceLocator.getStorageInstanceManager().openStorage(this, {
|
|
575
|
+
...resolved,
|
|
576
|
+
backendOpener: () => storageBackend.createDatasetBackend(resolved),
|
|
577
|
+
backendCacheKey: storageBackend.getStorageBackendCacheKey?.() ?? storageBackend.constructor.name,
|
|
578
|
+
});
|
|
437
579
|
}
|
|
438
580
|
/**
|
|
439
581
|
* Stores an object or an array of objects to the default {@link Dataset} of the current crawler run.
|
|
@@ -471,4 +613,3 @@ export class Dataset {
|
|
|
471
613
|
return dataset.getData(options);
|
|
472
614
|
}
|
|
473
615
|
}
|
|
474
|
-
//# sourceMappingURL=dataset.js.map
|
package/storages/index.d.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
export * from './dataset.js';
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './
|
|
5
|
-
export * from './
|
|
6
|
-
export
|
|
7
|
-
export
|
|
8
|
-
export {
|
|
9
|
-
export
|
|
2
|
+
export * from './key-value-store.js';
|
|
3
|
+
export * from './key-value-store-codec.js';
|
|
4
|
+
export * from './request-list.js';
|
|
5
|
+
export type * from './request-loader.js';
|
|
6
|
+
export type * from './request-manager.js';
|
|
7
|
+
export * from './request-queue.js';
|
|
8
|
+
export type { DefaultStorageIdentifier, ExplicitStorageIdentifier, IStorage, StorageIdentifier, } from './storage-instance-manager.js';
|
|
9
|
+
export { StorageInstanceManager } from './storage-instance-manager.js';
|
|
10
|
+
export type { DatasetStats, KeyValueStoreStats, RequestQueueStats } from './storage-stats.js';
|
|
10
11
|
export * from './utils.js';
|
|
11
|
-
export * from './
|
|
12
|
-
export * from './
|
|
13
|
-
export * from './request_manager_tandem.js';
|
|
14
|
-
//# sourceMappingURL=index.d.ts.map
|
|
12
|
+
export * from './transaction.js';
|
|
13
|
+
export * from './request-manager-tandem.js';
|
package/storages/index.js
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
export * from './dataset.js';
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './
|
|
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';
|
|
2
|
+
export * from './key-value-store.js';
|
|
3
|
+
export * from './key-value-store-codec.js';
|
|
4
|
+
export * from './request-list.js';
|
|
5
|
+
export * from './request-queue.js';
|
|
6
|
+
export { StorageInstanceManager } from './storage-instance-manager.js';
|
|
10
7
|
export * from './utils.js';
|
|
11
|
-
export * from './
|
|
12
|
-
export * from './
|
|
13
|
-
export * from './request_manager_tandem.js';
|
|
14
|
-
//# sourceMappingURL=index.js.map
|
|
8
|
+
export * from './transaction.js';
|
|
9
|
+
export * from './request-manager-tandem.js';
|