@crawlee/core 4.0.0-beta.13 → 4.0.0-beta.131
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 +17 -13
- package/autoscaling/autoscaled_pool.d.ts +67 -172
- package/autoscaling/autoscaled_pool.js +182 -329
- package/autoscaling/concurrency_system.d.ts +268 -0
- package/autoscaling/concurrency_system.js +362 -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 +100 -0
- package/autoscaling/load_signal.js +105 -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 -129
- package/crawlers/crawler_commons.js +1 -116
- 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 -63
- package/crawlers/statistics.js +354 -164
- package/debug.d.ts +36 -0
- package/debug.js +70 -0
- package/enqueue_links/enqueue_links.d.ts +68 -154
- package/enqueue_links/enqueue_links.js +38 -210
- package/enqueue_links/index.d.ts +0 -1
- package/enqueue_links/index.js +0 -1
- package/enqueue_links/shared.d.ts +52 -26
- package/enqueue_links/shared.js +115 -66
- 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 -4
- package/index.js +6 -3
- package/iterables.d.ts +79 -0
- package/iterables.js +134 -0
- package/log.d.ts +82 -3
- package/log.js +106 -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 +38 -0
- package/memory-storage/memory-storage.js +130 -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 +108 -0
- package/memory-storage/resource-clients/key-value-store.d.ts +63 -0
- package/memory-storage/resource-clients/key-value-store.js +201 -0
- package/memory-storage/resource-clients/request-queue.d.ts +77 -0
- package/memory-storage/resource-clients/request-queue.js +409 -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 +14 -13
- package/proxy_configuration.d.ts +24 -132
- package/proxy_configuration.js +32 -147
- package/recoverable_state.d.ts +155 -0
- package/recoverable_state.js +231 -0
- package/request.d.ts +80 -16
- package/request.js +147 -68
- 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 +35 -88
- package/session_pool/session.js +101 -159
- package/session_pool/session_pool.d.ts +73 -91
- package/session_pool/session_pool.js +172 -165
- package/storages/batched_adds.d.ts +37 -0
- package/storages/batched_adds.js +73 -0
- package/storages/dataset.d.ts +114 -54
- package/storages/dataset.js +291 -148
- package/storages/index.d.ts +10 -8
- package/storages/index.js +8 -8
- package/storages/key_value_store.d.ts +185 -42
- package/storages/key_value_store.js +445 -169
- 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 +52 -115
- package/storages/request_list.js +183 -152
- package/storages/request_loader.d.ts +101 -0
- package/storages/request_loader.js +1 -0
- package/storages/request_manager.d.ts +33 -0
- package/storages/request_manager.js +1 -0
- package/storages/request_manager_tandem.d.ts +97 -0
- package/storages/request_manager_tandem.js +197 -0
- package/storages/request_queue.d.ts +290 -47
- package/storages/request_queue.js +762 -216
- package/storages/{sitemap_request_list.d.ts → sitemap_request_loader.d.ts} +45 -89
- package/storages/sitemap_request_loader.js +438 -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 +239 -0
- package/storages/throttling_request_manager.js +646 -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 -82
- 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/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_provider.d.ts +0 -308
- package/storages/request_provider.d.ts.map +0 -1
- package/storages/request_provider.js +0 -555
- 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/tsconfig.build.tsbuildinfo +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
|
@@ -1,42 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import JSON5 from 'json5';
|
|
4
|
-
import ow, { ArgumentError } from 'ow';
|
|
1
|
+
import { z } from 'zod';
|
|
5
2
|
import { KEY_VALUE_STORE_KEY_REGEX } from '@apify/consts';
|
|
6
|
-
import
|
|
7
|
-
import { jsonStringifyExtended } from '@apify/utilities';
|
|
3
|
+
import { tryCancel } from '@apify/timeout';
|
|
8
4
|
import { Configuration } from '../configuration.js';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (value === undefined) {
|
|
34
|
-
throw new Error('The "value" parameter was stringified to JSON and returned undefined. ' +
|
|
35
|
-
"Make sure you're not trying to stringify an undefined value.");
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return value;
|
|
39
|
-
};
|
|
5
|
+
import { serviceLocator } from '../service_locator.js';
|
|
6
|
+
import { parseArgument, schemas, validators } from '../validators.js';
|
|
7
|
+
import { activeStorageTransaction, operationRejectedInTransaction, rejectOperationInTransaction, snapshotValue, withDirectStorageAccess, } from './transaction.js';
|
|
8
|
+
import { parseValue, serializeValue } from './key_value_store_codec.js';
|
|
9
|
+
import { StorageStatsTracker } from './storage_stats.js';
|
|
10
|
+
import { resolveStorageIdentifier } from './storage_instance_manager.js';
|
|
11
|
+
import { createDualIterable, purgeDefaultStorages } from './utils.js';
|
|
12
|
+
import { isBuffer, isStream } from '../byte_utils.js';
|
|
13
|
+
/** @internal */
|
|
14
|
+
const KVS_KEYS_DEFAULT_LIMIT = 1000;
|
|
15
|
+
const keySchema = z.string().nonempty();
|
|
16
|
+
const setValueKeySchema = z.string().nonempty().regex(KEY_VALUE_STORE_KEY_REGEX, {
|
|
17
|
+
message: `The "key" argument must be at most 256 characters long and only contain the following characters: a-zA-Z0-9!-_.'()`,
|
|
18
|
+
});
|
|
19
|
+
const recordOptionsSchema = z.strictObject({
|
|
20
|
+
contentType: z.string().nonempty().optional(),
|
|
21
|
+
});
|
|
22
|
+
const iteratorOptionsSchema = z.strictObject({
|
|
23
|
+
prefix: z.string().optional(),
|
|
24
|
+
});
|
|
25
|
+
const openOptionsSchema = z.strictObject({
|
|
26
|
+
configuration: z.instanceof(Configuration).optional(),
|
|
27
|
+
storageBackend: validators.storageBackend.optional(),
|
|
28
|
+
});
|
|
40
29
|
/**
|
|
41
30
|
* The `KeyValueStore` class represents a key-value store, a simple data storage that is used
|
|
42
31
|
* for saving and reading data records or files. Each data record is
|
|
@@ -94,23 +83,35 @@ export const maybeStringify = (value, options) => {
|
|
|
94
83
|
* @category Result Stores
|
|
95
84
|
*/
|
|
96
85
|
export class KeyValueStore {
|
|
97
|
-
|
|
86
|
+
configuration;
|
|
98
87
|
id;
|
|
99
88
|
name;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
persistStateEventStarted = false;
|
|
89
|
+
// kept as TS-private: key_value_store tests spy on the backend directly
|
|
90
|
+
backend;
|
|
91
|
+
#persistStateEventStarted = false;
|
|
103
92
|
/** Cache for persistent (auto-saved) values. When we try to set such value, the cache will be updated automatically. */
|
|
104
|
-
cache = new Map();
|
|
93
|
+
#cache = new Map();
|
|
94
|
+
#statsTracker = new StorageStatsTracker({
|
|
95
|
+
readCount: 0,
|
|
96
|
+
writeCount: 0,
|
|
97
|
+
deleteCount: 0,
|
|
98
|
+
listCount: 0,
|
|
99
|
+
});
|
|
105
100
|
/**
|
|
106
101
|
* @internal
|
|
107
102
|
*/
|
|
108
|
-
constructor(options,
|
|
109
|
-
this.
|
|
110
|
-
this.id = options.id;
|
|
111
|
-
this.name = options.name;
|
|
112
|
-
this.
|
|
113
|
-
|
|
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 key-value store (read / write / delete /
|
|
111
|
+
* list operations issued to the underlying storage backend). Counted per backend call.
|
|
112
|
+
*/
|
|
113
|
+
get stats() {
|
|
114
|
+
return this.#statsTracker.current;
|
|
114
115
|
}
|
|
115
116
|
/**
|
|
116
117
|
* Gets a value from the key-value store.
|
|
@@ -145,10 +146,93 @@ export class KeyValueStore {
|
|
|
145
146
|
* on the MIME content type of the record, or `null` if the key is missing from the store.
|
|
146
147
|
*/
|
|
147
148
|
async getValue(key, defaultValue) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const record = await this.
|
|
151
|
-
|
|
149
|
+
tryCancel();
|
|
150
|
+
parseArgument(key, keySchema);
|
|
151
|
+
const record = await this.readRecord(key);
|
|
152
|
+
// A missing record falls back to the default; a record that parses to a falsy value (including
|
|
153
|
+
// a stored literal `null`) is returned verbatim, so callers can tell "stored null" from "absent".
|
|
154
|
+
if (!record) {
|
|
155
|
+
return defaultValue ?? null;
|
|
156
|
+
}
|
|
157
|
+
// Storage backends are byte transports — the value is raw bytes; the frontend parses it here.
|
|
158
|
+
return parseValue(record.value, record.contentType ?? null);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The active transaction's last buffered write per key for this store, derived from its journal.
|
|
162
|
+
* An entry with a `null` value is a tombstone (an in-transaction deletion).
|
|
163
|
+
*/
|
|
164
|
+
bufferedJournalEntries() {
|
|
165
|
+
const transaction = activeStorageTransaction();
|
|
166
|
+
if (!transaction)
|
|
167
|
+
return undefined;
|
|
168
|
+
const lastWritePerKey = new Map();
|
|
169
|
+
for (const entry of transaction.journal) {
|
|
170
|
+
if (entry.type === 'keyValueStore' && entry.participant === this) {
|
|
171
|
+
lastWritePerKey.set(entry.key, entry);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return lastWritePerKey;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The single transaction-aware record read shared by `getValue`, `getRecord`, `recordExists` and the
|
|
178
|
+
* listing paths: buffered key → serialized through the standard codec (same fidelity as a real
|
|
179
|
+
* round-trip); tombstoned key → `null`; otherwise the backend.
|
|
180
|
+
*
|
|
181
|
+
* The per-key buffered lookup requires the whole journal to be reduced to a last-write-per-key map,
|
|
182
|
+
* which is O(journal). Single-record callers let it default (rebuilt per call); the listing paths,
|
|
183
|
+
* which read many keys, pass a map built once so the read stays O(1) per key instead of O(journal).
|
|
184
|
+
*/
|
|
185
|
+
async readRecord(key, buffered = this.bufferedJournalEntries()) {
|
|
186
|
+
const entry = buffered?.get(key);
|
|
187
|
+
if (entry) {
|
|
188
|
+
if (entry.value === null) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
const serialized = serializeValue(entry.value, entry.options?.contentType);
|
|
192
|
+
return {
|
|
193
|
+
value: normalizeSerializedValue(serialized.value),
|
|
194
|
+
contentType: serialized.contentType ?? null,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
this.#statsTracker.add('readCount');
|
|
198
|
+
const record = await this.backend.getValue(key);
|
|
199
|
+
if (!record)
|
|
200
|
+
return null;
|
|
201
|
+
return {
|
|
202
|
+
value: record.value,
|
|
203
|
+
contentType: record.contentType ?? null,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Reads a record from the key-value store without parsing the value.
|
|
208
|
+
*
|
|
209
|
+
* Use this when you need the raw bytes and the content type — for example, to run your own
|
|
210
|
+
* parser (`simdjson`, a custom XML library, etc.) or to forward the bytes verbatim.
|
|
211
|
+
*
|
|
212
|
+
* There is no symmetric `setRecord` method, because {@link KeyValueStore.setValue} already
|
|
213
|
+
* passes a `Buffer` (or `string` / `Stream`) through unchanged when an explicit `contentType`
|
|
214
|
+
* is provided. To write pre-serialized bytes, call
|
|
215
|
+
* `setValue(key, buffer, { contentType: 'application/json; charset=utf-8' })`.
|
|
216
|
+
*
|
|
217
|
+
* Returns `null` if the record does not exist.
|
|
218
|
+
*
|
|
219
|
+
* **Example usage:**
|
|
220
|
+
* ```javascript
|
|
221
|
+
* const store = await KeyValueStore.open();
|
|
222
|
+
* const record = await store.getRecord('huge.json');
|
|
223
|
+
* if (record) {
|
|
224
|
+
* const data = simdjson.parse(record.value);
|
|
225
|
+
* }
|
|
226
|
+
* ```
|
|
227
|
+
*
|
|
228
|
+
* @param key
|
|
229
|
+
* Unique key of the record. It can be at most 256 characters long and only consist
|
|
230
|
+
* of the following characters: `a`-`z`, `A`-`Z`, `0`-`9` and `!-_.'()`
|
|
231
|
+
*/
|
|
232
|
+
async getRecord(key) {
|
|
233
|
+
tryCancel();
|
|
234
|
+
parseArgument(key, keySchema);
|
|
235
|
+
return this.readRecord(key);
|
|
152
236
|
}
|
|
153
237
|
/**
|
|
154
238
|
* Tests whether a record with the given key exists in the key-value store without retrieving its value.
|
|
@@ -157,45 +241,96 @@ export class KeyValueStore {
|
|
|
157
241
|
* @returns `true` if the record exists, `false` if it does not.
|
|
158
242
|
*/
|
|
159
243
|
async recordExists(key) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
244
|
+
tryCancel();
|
|
245
|
+
parseArgument(key, keySchema);
|
|
246
|
+
const entry = this.bufferedJournalEntries()?.get(key);
|
|
247
|
+
if (entry) {
|
|
248
|
+
return entry.value !== null;
|
|
249
|
+
}
|
|
250
|
+
return this.backend.recordExists(key);
|
|
163
251
|
}
|
|
164
252
|
async getAutoSavedValue(key, defaultValue = {}) {
|
|
165
|
-
|
|
166
|
-
if (this
|
|
167
|
-
return this
|
|
253
|
+
tryCancel();
|
|
254
|
+
if (this.#cache.has(key)) {
|
|
255
|
+
return this.#cache.get(key);
|
|
168
256
|
}
|
|
169
|
-
|
|
257
|
+
// Auto-saved state is deliberately *not* transactional. The direct read bypasses any active
|
|
258
|
+
// transaction - a buffered value seeded into this shared cache would survive a rollback forever.
|
|
259
|
+
const value = await withDirectStorageAccess(async () => this.getValue(key, defaultValue));
|
|
170
260
|
// The await above could have run in parallel with another call to this function. If the other call finished more quickly,
|
|
171
261
|
// the value will in cache at this point, and returning the new fetched value would introduce two different instances of
|
|
172
262
|
// the auto-saved object, and only the latter one would be persisted.
|
|
173
263
|
// Therefore we re-check the cache here, and if such race condition happened, we drop the fetched value and return the cached one.
|
|
174
|
-
if (this
|
|
175
|
-
return this
|
|
264
|
+
if (this.#cache.has(key)) {
|
|
265
|
+
return this.#cache.get(key);
|
|
176
266
|
}
|
|
177
|
-
this
|
|
267
|
+
this.#cache.set(key, value);
|
|
178
268
|
this.ensurePersistStateEvent();
|
|
179
269
|
return value;
|
|
180
270
|
}
|
|
181
271
|
ensurePersistStateEvent() {
|
|
182
|
-
if (this
|
|
272
|
+
if (this.#persistStateEventStarted) {
|
|
183
273
|
return;
|
|
184
274
|
}
|
|
185
|
-
|
|
186
|
-
const persistStateIntervalMillis = this.config.get('persistStateIntervalMillis');
|
|
187
|
-
const timeoutSecs = persistStateIntervalMillis / 2_000;
|
|
188
|
-
this.config.getEventManager().on('persistState', async () => {
|
|
275
|
+
serviceLocator.getEventManager().on('persistState', async () => {
|
|
189
276
|
const promises = [];
|
|
190
|
-
for (const [key, value] of this
|
|
191
|
-
promises.push(this.setValue(key, value
|
|
192
|
-
timeoutSecs,
|
|
193
|
-
doNotRetryTimeouts: true,
|
|
194
|
-
}).catch((error) => log.warning(`Failed to persist the state value to ${key}`, { error })));
|
|
277
|
+
for (const [key, value] of this.#cache) {
|
|
278
|
+
promises.push(this.setValue(key, value).catch((error) => serviceLocator.getLogger().warning(`Failed to persist the state value to ${key}`, { error })));
|
|
195
279
|
}
|
|
196
280
|
await Promise.all(promises);
|
|
197
281
|
});
|
|
198
|
-
this
|
|
282
|
+
this.#persistStateEventStarted = true;
|
|
283
|
+
}
|
|
284
|
+
async *fetchKeyValuePages(options, mapRecord) {
|
|
285
|
+
// Reduce the journal once for the whole iteration, not once per key inside `readRecord`.
|
|
286
|
+
const buffered = this.bufferedJournalEntries();
|
|
287
|
+
for await (const page of this.fetchKeyPages(options, buffered)) {
|
|
288
|
+
const results = [];
|
|
289
|
+
for (const item of page) {
|
|
290
|
+
// The shared transaction-aware read, so a key that exists only in the transaction resolves
|
|
291
|
+
// here instead of being dropped (`values()` would disagree with `keys()` on length).
|
|
292
|
+
const record = await this.readRecord(item.key, buffered);
|
|
293
|
+
if (record) {
|
|
294
|
+
const parsed = parseValue(record.value, record.contentType ?? null);
|
|
295
|
+
results.push(mapRecord(item.key, parsed));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
yield results;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async *fetchKeyPages(options, buffered = this.bufferedJournalEntries(), limit = KVS_KEYS_DEFAULT_LIMIT) {
|
|
302
|
+
// Buffered keys are emitted first, then the real pages with any buffered (or tombstoned) key
|
|
303
|
+
// skipped - a merge-join is not an option, since `listKeys` promises no sort order.
|
|
304
|
+
const shadowedKeys = new Set();
|
|
305
|
+
if (buffered) {
|
|
306
|
+
const bufferedItems = [];
|
|
307
|
+
for (const [key, entry] of buffered) {
|
|
308
|
+
shadowedKeys.add(key);
|
|
309
|
+
if (entry.value === null)
|
|
310
|
+
continue;
|
|
311
|
+
if (options.prefix !== undefined && !key.startsWith(options.prefix))
|
|
312
|
+
continue;
|
|
313
|
+
bufferedItems.push(bufferedKeyItemData(key, entry));
|
|
314
|
+
}
|
|
315
|
+
if (bufferedItems.length > 0) {
|
|
316
|
+
bufferedItems.sort((a, b) => (a.key < b.key ? -1 : 1));
|
|
317
|
+
yield bufferedItems;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
let exclusiveStartKey;
|
|
321
|
+
while (true) {
|
|
322
|
+
this.#statsTracker.add('listCount');
|
|
323
|
+
const { items, isTruncated, nextExclusiveStartKey } = await this.backend.listKeys({
|
|
324
|
+
...options,
|
|
325
|
+
exclusiveStartKey,
|
|
326
|
+
limit,
|
|
327
|
+
});
|
|
328
|
+
yield shadowedKeys.size > 0 ? items.filter((item) => !shadowedKeys.has(item.key)) : items;
|
|
329
|
+
if (!isTruncated)
|
|
330
|
+
break;
|
|
331
|
+
// Paginate from the raw backend cursor - it may reject a key it did not hand out.
|
|
332
|
+
exclusiveStartKey = nextExclusiveStartKey;
|
|
333
|
+
}
|
|
199
334
|
}
|
|
200
335
|
/**
|
|
201
336
|
* Saves or deletes a record in the key-value store.
|
|
@@ -241,73 +376,100 @@ export class KeyValueStore {
|
|
|
241
376
|
* @param [options] Record options.
|
|
242
377
|
*/
|
|
243
378
|
async setValue(key, value, options = {}) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
379
|
+
const transaction = activeStorageTransaction();
|
|
380
|
+
parseArgument(key, setValueKeySchema);
|
|
381
|
+
if (options.contentType && !(typeof value === 'string' || isBuffer(value) || isStream(value))) {
|
|
382
|
+
throw new Error('The "value" parameter must be a String, Buffer, ArrayBuffer, TypedArray, or Stream when "options.contentType" is specified.');
|
|
383
|
+
}
|
|
384
|
+
// The parse result is a fresh copy, so we never update what user passed.
|
|
385
|
+
const optionsCopy = parseArgument(options, recordOptionsSchema);
|
|
386
|
+
// The whole transaction branch sits *above* the auto-saved cache update below, so a buffered
|
|
387
|
+
// write touches nothing outside the journal. That cache is shared, process-lifetime frontend
|
|
388
|
+
// state, so mutating it here would survive a rollback and later be persisted by `persistState`.
|
|
389
|
+
// The commit replay re-enters this method with no active transaction and updates it then.
|
|
390
|
+
if (transaction) {
|
|
391
|
+
if (isStream(value)) {
|
|
392
|
+
// A stream cannot serve both a read-your-own-writes read and the commit replay. The
|
|
393
|
+
// transaction is known-active here, so throw directly rather than via the conditional guard.
|
|
394
|
+
throw operationRejectedInTransaction(`KeyValueStore.setValue() with a stream value (key "${key}")`, 'a stream can only be consumed once, so it cannot be buffered until commit.');
|
|
395
|
+
}
|
|
396
|
+
// Validation only, result discarded: the journal snapshot (`structuredClone`) accepts values
|
|
397
|
+
// JSON cannot, which would otherwise only throw at a later read or at commit.
|
|
398
|
+
if (value !== null) {
|
|
399
|
+
serializeValue(value, optionsCopy.contentType);
|
|
400
|
+
}
|
|
401
|
+
// One snapshot serves both the reads and the commit replay; `null` is a tombstone.
|
|
402
|
+
transaction.recordJournalEntry({
|
|
403
|
+
type: 'keyValueStore',
|
|
404
|
+
participant: this,
|
|
405
|
+
storageId: this.id,
|
|
406
|
+
key,
|
|
407
|
+
value: value === null ? null : snapshotValue(value),
|
|
408
|
+
options: optionsCopy,
|
|
409
|
+
});
|
|
410
|
+
return;
|
|
254
411
|
}
|
|
255
|
-
ow(options, ow.object.exactShape({
|
|
256
|
-
contentType: ow.optional.string.nonEmpty,
|
|
257
|
-
timeoutSecs: ow.optional.number,
|
|
258
|
-
doNotRetryTimeouts: ow.optional.boolean,
|
|
259
|
-
}));
|
|
260
|
-
// Make copy of options, don't update what user passed.
|
|
261
|
-
const optionsCopy = { ...options };
|
|
262
412
|
// If we try to set the value of a cached state to a different reference, we need to update the cache accordingly.
|
|
263
|
-
const cachedValue = this
|
|
413
|
+
const cachedValue = this.#cache.get(key);
|
|
264
414
|
if (cachedValue && cachedValue !== value) {
|
|
265
415
|
if (value === null) {
|
|
266
416
|
// Cached state can be only object, so a propagation of `null` means removing all its properties.
|
|
267
|
-
Object.keys(cachedValue).forEach((k) => this
|
|
417
|
+
Object.keys(cachedValue).forEach((k) => this.#cache.delete(k));
|
|
268
418
|
}
|
|
269
419
|
else if (typeof value === 'object') {
|
|
270
420
|
// We need to remove the keys that are no longer present in the new value.
|
|
271
421
|
Object.keys(cachedValue)
|
|
272
422
|
.filter((k) => !(k in value))
|
|
273
|
-
.forEach((k) => this
|
|
423
|
+
.forEach((k) => this.#cache.delete(k));
|
|
274
424
|
// And update the existing ones + add new ones.
|
|
275
425
|
Object.assign(cachedValue, value);
|
|
276
426
|
}
|
|
277
427
|
}
|
|
278
428
|
// In this case delete the record.
|
|
279
|
-
if (value === null)
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
429
|
+
if (value === null) {
|
|
430
|
+
this.#statsTracker.add('deleteCount');
|
|
431
|
+
return this.backend.deleteValue(key);
|
|
432
|
+
}
|
|
433
|
+
const serialized = serializeValue(value, optionsCopy.contentType);
|
|
434
|
+
this.#statsTracker.add('writeCount');
|
|
435
|
+
return this.backend.setValue({
|
|
283
436
|
key,
|
|
284
|
-
value,
|
|
285
|
-
contentType:
|
|
286
|
-
}, {
|
|
287
|
-
timeoutSecs: optionsCopy.timeoutSecs,
|
|
288
|
-
doNotRetryTimeouts: optionsCopy.doNotRetryTimeouts,
|
|
437
|
+
value: serialized.value,
|
|
438
|
+
contentType: serialized.contentType,
|
|
289
439
|
});
|
|
290
440
|
}
|
|
441
|
+
/** @internal */
|
|
442
|
+
async commitJournalEntries(entries) {
|
|
443
|
+
// One `setValue` per key, last write wins - idempotent under retry.
|
|
444
|
+
const lastWritePerKey = new Map();
|
|
445
|
+
for (const entry of entries) {
|
|
446
|
+
if (entry.type === 'keyValueStore') {
|
|
447
|
+
lastWritePerKey.set(entry.key, { value: entry.value, options: entry.options });
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
for (const [key, { value, options }] of lastWritePerKey) {
|
|
451
|
+
await this.setValue(key, value, options);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
291
454
|
/**
|
|
292
455
|
* Removes the key-value store either from the Apify cloud storage or from the local directory,
|
|
293
456
|
* depending on the mode of operation.
|
|
294
457
|
*/
|
|
295
458
|
async drop() {
|
|
296
|
-
|
|
297
|
-
await this.
|
|
298
|
-
|
|
299
|
-
manager.closeStorage(this);
|
|
459
|
+
rejectOperationInTransaction('KeyValueStore.drop()');
|
|
460
|
+
await this.backend.drop();
|
|
461
|
+
serviceLocator.getStorageInstanceManager().removeFromCache(this);
|
|
300
462
|
}
|
|
301
463
|
/** @internal */
|
|
302
464
|
clearCache() {
|
|
303
|
-
|
|
304
|
-
this
|
|
465
|
+
rejectOperationInTransaction('KeyValueStore.clearCache()');
|
|
466
|
+
this.#cache.clear();
|
|
305
467
|
}
|
|
306
468
|
/**
|
|
307
469
|
* Iterates over key-value store keys, yielding each in turn to an `iteratee` function.
|
|
308
470
|
* Each invocation of `iteratee` is called with three arguments: `(key, index, info)`, where `key`
|
|
309
471
|
* is the record key, `index` is a zero-based index of the key in the current iteration
|
|
310
|
-
*
|
|
472
|
+
* and `info` is an object that contains a single property `size`
|
|
311
473
|
* indicating size of the record in bytes.
|
|
312
474
|
*
|
|
313
475
|
* If the `iteratee` function returns a Promise then it is awaited before the next call.
|
|
@@ -325,30 +487,130 @@ export class KeyValueStore {
|
|
|
325
487
|
* @param [options] All `forEachKey()` parameters.
|
|
326
488
|
*/
|
|
327
489
|
async forEachKey(iteratee, options = {}) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}));
|
|
337
|
-
const response = await this.client.listKeys({ exclusiveStartKey });
|
|
338
|
-
const { nextExclusiveStartKey, isTruncated, items } = response;
|
|
339
|
-
for (const item of items) {
|
|
340
|
-
await iteratee(item.key, index++, { size: item.size });
|
|
490
|
+
tryCancel();
|
|
491
|
+
parseArgument(iteratee, schemas.anyFunction);
|
|
492
|
+
const parsedOptions = parseArgument(options, iteratorOptionsSchema);
|
|
493
|
+
let index = 0;
|
|
494
|
+
for await (const page of this.fetchKeyPages(parsedOptions)) {
|
|
495
|
+
for (const item of page) {
|
|
496
|
+
await iteratee(item.key, index++, { size: item.size });
|
|
497
|
+
}
|
|
341
498
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Returns key-value store keys.
|
|
502
|
+
*
|
|
503
|
+
* When awaited (`await store.keys()`), returns all keys as a flat `string[]` array.
|
|
504
|
+
* When used as an async iterable (`for await...of`), iterates over all keys across pages
|
|
505
|
+
* without loading everything into memory at once.
|
|
506
|
+
*
|
|
507
|
+
* **Example usage:**
|
|
508
|
+
* ```javascript
|
|
509
|
+
* const keyValueStore = await KeyValueStore.open();
|
|
510
|
+
*
|
|
511
|
+
* // Iterate over all keys (memory-efficient for large stores)
|
|
512
|
+
* for await (const key of keyValueStore.keys()) {
|
|
513
|
+
* console.log(key);
|
|
514
|
+
* }
|
|
515
|
+
*
|
|
516
|
+
* // Or fetch all keys at once
|
|
517
|
+
* const allKeys = await keyValueStore.keys();
|
|
518
|
+
* console.log(allKeys);
|
|
519
|
+
* ```
|
|
520
|
+
*
|
|
521
|
+
* @param options Options for the iteration.
|
|
522
|
+
*/
|
|
523
|
+
keys(options = {}) {
|
|
524
|
+
tryCancel();
|
|
525
|
+
return createDualIterable({
|
|
526
|
+
createPages: () => this.fetchKeyPages(options),
|
|
527
|
+
extractItems: (page) => page.map((item) => item.key),
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Returns key-value store values.
|
|
532
|
+
*
|
|
533
|
+
* When awaited (`await store.values()`), returns all values as a flat `T[]` array.
|
|
534
|
+
* When used as an async iterable (`for await...of`), iterates over all values across pages
|
|
535
|
+
* without loading everything into memory at once.
|
|
536
|
+
*
|
|
537
|
+
* **Example usage:**
|
|
538
|
+
* ```javascript
|
|
539
|
+
* const keyValueStore = await KeyValueStore.open();
|
|
540
|
+
*
|
|
541
|
+
* // Iterate over all values (memory-efficient for large stores)
|
|
542
|
+
* for await (const value of keyValueStore.values()) {
|
|
543
|
+
* console.log(value);
|
|
544
|
+
* }
|
|
545
|
+
*
|
|
546
|
+
* // Or fetch all values at once
|
|
547
|
+
* const allValues = await keyValueStore.values();
|
|
548
|
+
* console.log(allValues);
|
|
549
|
+
* ```
|
|
550
|
+
*
|
|
551
|
+
* @param options Options for the iteration.
|
|
552
|
+
*/
|
|
553
|
+
values(options = {}) {
|
|
554
|
+
tryCancel();
|
|
555
|
+
return createDualIterable({
|
|
556
|
+
createPages: () => this.fetchKeyValuePages(options, (_key, value) => value),
|
|
557
|
+
extractItems: (page) => page,
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Returns key-value store entries (key-value pairs).
|
|
562
|
+
*
|
|
563
|
+
* When awaited (`await store.entries()`), returns all entries as a flat `[key, value][]` array.
|
|
564
|
+
* When used as an async iterable (`for await...of`), iterates over all entries across pages
|
|
565
|
+
* without loading everything into memory at once.
|
|
566
|
+
*
|
|
567
|
+
* **Example usage:**
|
|
568
|
+
* ```javascript
|
|
569
|
+
* const keyValueStore = await KeyValueStore.open();
|
|
570
|
+
*
|
|
571
|
+
* // Iterate over all entries (memory-efficient for large stores)
|
|
572
|
+
* for await (const [key, value] of keyValueStore.entries()) {
|
|
573
|
+
* console.log(`${key}: ${value}`);
|
|
574
|
+
* }
|
|
575
|
+
*
|
|
576
|
+
* // Or fetch all entries at once
|
|
577
|
+
* const allEntries = await keyValueStore.entries();
|
|
578
|
+
* console.log(allEntries);
|
|
579
|
+
* ```
|
|
580
|
+
*
|
|
581
|
+
* @param options Options for the iteration.
|
|
582
|
+
*/
|
|
583
|
+
entries(options = {}) {
|
|
584
|
+
tryCancel();
|
|
585
|
+
return createDualIterable({
|
|
586
|
+
createPages: () => this.fetchKeyValuePages(options, (key, value) => [key, value]),
|
|
587
|
+
extractItems: (page) => page,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Default async iterator for the key-value store, iterating over entries (key-value pairs).
|
|
592
|
+
* Allows using the store directly in a `for await...of` loop.
|
|
593
|
+
*
|
|
594
|
+
* **Example usage:**
|
|
595
|
+
* ```javascript
|
|
596
|
+
* const keyValueStore = await KeyValueStore.open();
|
|
597
|
+
* for await (const [key, value] of keyValueStore) {
|
|
598
|
+
* console.log(`${key}: ${value}`);
|
|
599
|
+
* }
|
|
600
|
+
* ```
|
|
601
|
+
*/
|
|
602
|
+
async *[Symbol.asyncIterator]() {
|
|
603
|
+
yield* this.entries();
|
|
345
604
|
}
|
|
346
605
|
/**
|
|
347
606
|
* Returns a file URL for the given key.
|
|
607
|
+
*
|
|
608
|
+
* If the record does not exist or has no associated file path (i.e., it is not stored as a file), returns `undefined`.
|
|
609
|
+
*
|
|
610
|
+
* @param key The key of the record to generate the public URL for.
|
|
348
611
|
*/
|
|
349
|
-
getPublicUrl(key) {
|
|
350
|
-
|
|
351
|
-
return `file://${process.cwd()}/storage/key_value_stores/${name}/${key}`;
|
|
612
|
+
async getPublicUrl(key) {
|
|
613
|
+
return this.backend.getPublicUrl(key);
|
|
352
614
|
}
|
|
353
615
|
/**
|
|
354
616
|
* Opens a key-value store and returns a promise resolving to an instance of the {@link KeyValueStore} class.
|
|
@@ -359,23 +621,24 @@ export class KeyValueStore {
|
|
|
359
621
|
*
|
|
360
622
|
* For more details and code examples, see the {@link KeyValueStore} class.
|
|
361
623
|
*
|
|
362
|
-
* @param [
|
|
363
|
-
* ID or name of the key-value store to be opened. If
|
|
364
|
-
*
|
|
624
|
+
* @param [identifier]
|
|
625
|
+
* ID or name of the key-value store to be opened. If a string is provided, it will first be
|
|
626
|
+
* looked up as an ID; if no such storage exists, it will be treated as a name.
|
|
627
|
+
* If `null` or `undefined`, the function returns the default key-value store associated with the crawler run.
|
|
365
628
|
* @param [options] Storage manager options.
|
|
366
629
|
*/
|
|
367
|
-
static async open(
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
630
|
+
static async open(identifier, options = {}) {
|
|
631
|
+
tryCancel();
|
|
632
|
+
const parsedOptions = parseArgument(options, openOptionsSchema);
|
|
633
|
+
const configuration = parsedOptions.configuration ?? Configuration.getGlobalConfiguration();
|
|
634
|
+
const storageBackend = parsedOptions.storageBackend ?? serviceLocator.getStorageBackend();
|
|
635
|
+
await purgeDefaultStorages({ onlyPurgeOnce: true, storageBackend, configuration });
|
|
636
|
+
const resolved = await resolveStorageIdentifier(identifier, storageBackend, 'KeyValueStore');
|
|
637
|
+
return serviceLocator.getStorageInstanceManager().openStorage(this, {
|
|
638
|
+
...resolved,
|
|
639
|
+
backendOpener: () => storageBackend.createKeyValueStoreBackend(resolved),
|
|
640
|
+
backendCacheKey: storageBackend.getStorageBackendCacheKey?.() ?? storageBackend.constructor.name,
|
|
641
|
+
});
|
|
379
642
|
}
|
|
380
643
|
/**
|
|
381
644
|
* Gets a value from the default {@link KeyValueStore} associated with the current crawler run.
|
|
@@ -410,6 +673,22 @@ export class KeyValueStore {
|
|
|
410
673
|
const store = await this.open();
|
|
411
674
|
return store.getValue(key, defaultValue);
|
|
412
675
|
}
|
|
676
|
+
/**
|
|
677
|
+
* Reads a record from the default {@link KeyValueStore} associated with the current crawler run
|
|
678
|
+
* without parsing the value.
|
|
679
|
+
*
|
|
680
|
+
* This is just a convenient shortcut for {@link KeyValueStore.getRecord}. Returns `null` if the
|
|
681
|
+
* record does not exist.
|
|
682
|
+
*
|
|
683
|
+
* @param key
|
|
684
|
+
* Unique key of the record. It can be at most 256 characters long and only consist
|
|
685
|
+
* of the following characters: `a`-`z`, `A`-`Z`, `0`-`9` and `!-_.'()`
|
|
686
|
+
* @ignore
|
|
687
|
+
*/
|
|
688
|
+
static async getRecord(key) {
|
|
689
|
+
const store = await this.open();
|
|
690
|
+
return store.getRecord(key);
|
|
691
|
+
}
|
|
413
692
|
/**
|
|
414
693
|
* Tests whether a record with the given key exists in the default {@link KeyValueStore} associated with the current crawler run.
|
|
415
694
|
* @param key The queried record key.
|
|
@@ -460,8 +739,9 @@ export class KeyValueStore {
|
|
|
460
739
|
}
|
|
461
740
|
/**
|
|
462
741
|
* Gets the crawler input value from the default {@link KeyValueStore} associated with the current crawler run.
|
|
463
|
-
*
|
|
464
|
-
*
|
|
742
|
+
*
|
|
743
|
+
* The input is read from the default {@link KeyValueStore} under the configured input key
|
|
744
|
+
* (`CRAWLEE_INPUT_KEY`, default `INPUT`).
|
|
465
745
|
*
|
|
466
746
|
* Note that the `getInput()` function does not cache the value read from the key-value store.
|
|
467
747
|
* If you need to use the input multiple times in your crawler,
|
|
@@ -479,29 +759,25 @@ export class KeyValueStore {
|
|
|
479
759
|
*/
|
|
480
760
|
static async getInput() {
|
|
481
761
|
const store = await this.open();
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
}
|
|
493
|
-
catch {
|
|
494
|
-
continue;
|
|
495
|
-
}
|
|
496
|
-
// Attempt to parse as JSON, or return the input as is otherwise
|
|
497
|
-
try {
|
|
498
|
-
return JSON5.parse(input.toString());
|
|
499
|
-
}
|
|
500
|
-
catch {
|
|
501
|
-
return input;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
return store.getValue(inputKey);
|
|
762
|
+
return store.getValue(store.configuration.inputKey);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
/** Normalizes a codec-serialized value into the `Buffer | ArrayBuffer` shape raw record reads promise. */
|
|
766
|
+
function normalizeSerializedValue(value) {
|
|
767
|
+
if (typeof value === 'string') {
|
|
768
|
+
return Buffer.from(value);
|
|
769
|
+
}
|
|
770
|
+
if (ArrayBuffer.isView(value)) {
|
|
771
|
+
return Buffer.isBuffer(value) ? value : Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
505
772
|
}
|
|
773
|
+
return value;
|
|
774
|
+
}
|
|
775
|
+
/** Computes the key listing item (serialized byte size and content type) of a buffered entry. */
|
|
776
|
+
function bufferedKeyItemData(key, entry) {
|
|
777
|
+
const serialized = serializeValue(entry.value, entry.options?.contentType);
|
|
778
|
+
return {
|
|
779
|
+
key,
|
|
780
|
+
size: normalizeSerializedValue(serialized.value).byteLength,
|
|
781
|
+
contentType: serialized.contentType,
|
|
782
|
+
};
|
|
506
783
|
}
|
|
507
|
-
//# sourceMappingURL=key_value_store.js.map
|