@crawlee/core 4.0.0-beta.104 → 4.0.0-beta.106
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/autoscaling/autoscaled_pool.d.ts +3 -21
- package/autoscaling/autoscaled_pool.js +85 -85
- package/autoscaling/client_load_signal.d.ts +1 -5
- package/autoscaling/client_load_signal.js +20 -20
- package/autoscaling/concurrency_system.d.ts +5 -20
- package/autoscaling/concurrency_system.js +81 -80
- package/autoscaling/cpu_load_signal.d.ts +1 -2
- package/autoscaling/cpu_load_signal.js +10 -10
- package/autoscaling/event_loop_load_signal.d.ts +1 -4
- package/autoscaling/event_loop_load_signal.js +18 -18
- package/autoscaling/load_signal.d.ts +1 -1
- package/autoscaling/load_signal.js +12 -11
- package/autoscaling/memory_load_signal.d.ts +3 -12
- package/autoscaling/memory_load_signal.js +40 -41
- package/autoscaling/snapshotter.d.ts +1 -4
- package/autoscaling/snapshotter.js +12 -12
- package/autoscaling/system_status.d.ts +1 -3
- package/autoscaling/system_status.js +11 -11
- package/configuration.d.ts +1 -1
- package/configuration.js +3 -3
- package/crawlers/context_pipeline.js +6 -6
- package/crawlers/crawler_commons.d.ts +6 -56
- package/crawlers/crawler_commons.js +1 -107
- package/crawlers/index.d.ts +1 -1
- package/crawlers/index.js +0 -1
- package/crawlers/statistics.d.ts +1 -8
- package/crawlers/statistics.js +45 -44
- package/events/event_manager.d.ts +1 -1
- package/events/event_manager.js +3 -3
- package/events/local_event_manager.d.ts +1 -1
- package/events/local_event_manager.js +3 -3
- package/log.js +5 -1
- package/memory-storage/memory-storage.d.ts +1 -5
- package/memory-storage/memory-storage.js +2 -2
- package/memory-storage/resource-clients/dataset.d.ts +1 -1
- package/memory-storage/resource-clients/dataset.js +6 -5
- package/memory-storage/resource-clients/key-value-store.d.ts +1 -1
- package/memory-storage/resource-clients/key-value-store.js +13 -12
- package/memory-storage/resource-clients/request-queue.d.ts +4 -23
- package/memory-storage/resource-clients/request-queue.js +59 -58
- package/owned_or_injected.d.ts +1 -3
- package/owned_or_injected.js +17 -17
- package/package.json +5 -5
- package/proxy_configuration.d.ts +1 -3
- package/proxy_configuration.js +8 -8
- package/recoverable_state.d.ts +1 -10
- package/recoverable_state.js +41 -41
- package/request.d.ts +1 -2
- package/request.js +10 -13
- package/router.d.ts +1 -4
- package/router.js +23 -23
- package/serialization.js +8 -9
- package/service_locator.d.ts +1 -10
- package/service_locator.js +48 -48
- package/session_pool/session.d.ts +1 -12
- package/session_pool/session.js +50 -50
- package/session_pool/session_pool.d.ts +2 -11
- package/session_pool/session_pool.js +59 -58
- package/storages/dataset.d.ts +12 -1
- package/storages/dataset.js +121 -22
- package/storages/index.d.ts +1 -1
- package/storages/index.js +1 -1
- package/storages/key_value_store.d.ts +19 -4
- package/storages/key_value_store.js +174 -48
- package/storages/request_dedup_cache.d.ts +1 -2
- package/storages/request_dedup_cache.js +9 -9
- package/storages/request_list.d.ts +2 -22
- package/storages/request_list.js +74 -73
- package/storages/request_manager_tandem.d.ts +1 -10
- package/storages/request_manager_tandem.js +27 -27
- package/storages/request_queue.d.ts +21 -18
- package/storages/request_queue.js +256 -53
- package/storages/sitemap_request_loader.d.ts +1 -44
- package/storages/sitemap_request_loader.js +87 -87
- package/storages/storage_instance_manager.d.ts +1 -2
- package/storages/storage_instance_manager.js +17 -17
- package/storages/storage_stats.d.ts +1 -1
- package/storages/storage_stats.js +4 -4
- package/storages/transaction.d.ts +252 -0
- package/storages/transaction.js +251 -0
- package/system-info/runtime.js +7 -7
- package/storages/access_checking.d.ts +0 -12
- package/storages/access_checking.js +0 -17
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import ow, { ArgumentError } from 'ow';
|
|
2
2
|
import { KEY_VALUE_STORE_KEY_REGEX } from '@apify/consts';
|
|
3
|
+
import { tryCancel } from '@apify/timeout';
|
|
3
4
|
import { Configuration } from '../configuration.js';
|
|
4
5
|
import { serviceLocator } from '../service_locator.js';
|
|
5
|
-
import {
|
|
6
|
+
import { activeStorageTransaction, operationRejectedInTransaction, rejectOperationInTransaction, snapshotValue, withDirectStorageAccess, } from './transaction.js';
|
|
6
7
|
import { parseValue, serializeValue } from './key_value_store_codec.js';
|
|
7
8
|
import { StorageStatsTracker } from './storage_stats.js';
|
|
8
9
|
import { resolveStorageIdentifier } from './storage_instance_manager.js';
|
|
@@ -70,11 +71,12 @@ export class KeyValueStore {
|
|
|
70
71
|
configuration;
|
|
71
72
|
id;
|
|
72
73
|
name;
|
|
74
|
+
// kept as TS-private: key_value_store tests spy on the backend directly
|
|
73
75
|
backend;
|
|
74
|
-
persistStateEventStarted = false;
|
|
76
|
+
#persistStateEventStarted = false;
|
|
75
77
|
/** Cache for persistent (auto-saved) values. When we try to set such value, the cache will be updated automatically. */
|
|
76
|
-
cache = new Map();
|
|
77
|
-
statsTracker = new StorageStatsTracker({
|
|
78
|
+
#cache = new Map();
|
|
79
|
+
#statsTracker = new StorageStatsTracker({
|
|
78
80
|
readCount: 0,
|
|
79
81
|
writeCount: 0,
|
|
80
82
|
deleteCount: 0,
|
|
@@ -94,7 +96,7 @@ export class KeyValueStore {
|
|
|
94
96
|
* list operations issued to the underlying storage backend). Counted per backend call.
|
|
95
97
|
*/
|
|
96
98
|
get stats() {
|
|
97
|
-
return this
|
|
99
|
+
return this.#statsTracker.current;
|
|
98
100
|
}
|
|
99
101
|
/**
|
|
100
102
|
* Gets a value from the key-value store.
|
|
@@ -129,10 +131,9 @@ export class KeyValueStore {
|
|
|
129
131
|
* on the MIME content type of the record, or `null` if the key is missing from the store.
|
|
130
132
|
*/
|
|
131
133
|
async getValue(key, defaultValue) {
|
|
132
|
-
|
|
134
|
+
tryCancel();
|
|
133
135
|
ow(key, ow.string.nonEmpty);
|
|
134
|
-
this.
|
|
135
|
-
const record = await this.backend.getValue(key);
|
|
136
|
+
const record = await this.readRecord(key);
|
|
136
137
|
// A missing record falls back to the default; a record that parses to a falsy value (including
|
|
137
138
|
// a stored literal `null`) is returned verbatim, so callers can tell "stored null" from "absent".
|
|
138
139
|
if (!record) {
|
|
@@ -141,6 +142,52 @@ export class KeyValueStore {
|
|
|
141
142
|
// Storage backends are byte transports — the value is raw bytes; the frontend parses it here.
|
|
142
143
|
return parseValue(record.value, record.contentType ?? null);
|
|
143
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* The active transaction's last buffered write per key for this store, derived from its journal.
|
|
147
|
+
* An entry with a `null` value is a tombstone (an in-transaction deletion).
|
|
148
|
+
*/
|
|
149
|
+
bufferedJournalEntries() {
|
|
150
|
+
const transaction = activeStorageTransaction();
|
|
151
|
+
if (!transaction)
|
|
152
|
+
return undefined;
|
|
153
|
+
const lastWritePerKey = new Map();
|
|
154
|
+
for (const entry of transaction.journal) {
|
|
155
|
+
if (entry.type === 'keyValueStore' && entry.participant === this) {
|
|
156
|
+
lastWritePerKey.set(entry.key, entry);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return lastWritePerKey;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The single transaction-aware record read shared by `getValue`, `getRecord`, `recordExists` and the
|
|
163
|
+
* listing paths: buffered key → serialized through the standard codec (same fidelity as a real
|
|
164
|
+
* round-trip); tombstoned key → `null`; otherwise the backend.
|
|
165
|
+
*
|
|
166
|
+
* The per-key buffered lookup requires the whole journal to be reduced to a last-write-per-key map,
|
|
167
|
+
* which is O(journal). Single-record callers let it default (rebuilt per call); the listing paths,
|
|
168
|
+
* which read many keys, pass a map built once so the read stays O(1) per key instead of O(journal).
|
|
169
|
+
*/
|
|
170
|
+
async readRecord(key, buffered = this.bufferedJournalEntries()) {
|
|
171
|
+
const entry = buffered?.get(key);
|
|
172
|
+
if (entry) {
|
|
173
|
+
if (entry.value === null) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
const serialized = serializeValue(entry.value, entry.options?.contentType);
|
|
177
|
+
return {
|
|
178
|
+
value: normalizeSerializedValue(serialized.value),
|
|
179
|
+
contentType: serialized.contentType ?? null,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
this.#statsTracker.add('readCount');
|
|
183
|
+
const record = await this.backend.getValue(key);
|
|
184
|
+
if (!record)
|
|
185
|
+
return null;
|
|
186
|
+
return {
|
|
187
|
+
value: record.value,
|
|
188
|
+
contentType: record.contentType ?? null,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
144
191
|
/**
|
|
145
192
|
* Reads a record from the key-value store without parsing the value.
|
|
146
193
|
*
|
|
@@ -168,16 +215,9 @@ export class KeyValueStore {
|
|
|
168
215
|
* of the following characters: `a`-`z`, `A`-`Z`, `0`-`9` and `!-_.'()`
|
|
169
216
|
*/
|
|
170
217
|
async getRecord(key) {
|
|
171
|
-
|
|
218
|
+
tryCancel();
|
|
172
219
|
ow(key, ow.string.nonEmpty);
|
|
173
|
-
this.
|
|
174
|
-
const record = await this.backend.getValue(key);
|
|
175
|
-
if (!record)
|
|
176
|
-
return null;
|
|
177
|
-
return {
|
|
178
|
-
value: record.value,
|
|
179
|
-
contentType: record.contentType ?? null,
|
|
180
|
-
};
|
|
220
|
+
return this.readRecord(key);
|
|
181
221
|
}
|
|
182
222
|
/**
|
|
183
223
|
* Tests whether a record with the given key exists in the key-value store without retrieving its value.
|
|
@@ -186,46 +226,55 @@ export class KeyValueStore {
|
|
|
186
226
|
* @returns `true` if the record exists, `false` if it does not.
|
|
187
227
|
*/
|
|
188
228
|
async recordExists(key) {
|
|
189
|
-
|
|
229
|
+
tryCancel();
|
|
190
230
|
ow(key, ow.string.nonEmpty);
|
|
231
|
+
const entry = this.bufferedJournalEntries()?.get(key);
|
|
232
|
+
if (entry) {
|
|
233
|
+
return entry.value !== null;
|
|
234
|
+
}
|
|
191
235
|
return this.backend.recordExists(key);
|
|
192
236
|
}
|
|
193
237
|
async getAutoSavedValue(key, defaultValue = {}) {
|
|
194
|
-
|
|
195
|
-
if (this
|
|
196
|
-
return this
|
|
238
|
+
tryCancel();
|
|
239
|
+
if (this.#cache.has(key)) {
|
|
240
|
+
return this.#cache.get(key);
|
|
197
241
|
}
|
|
198
|
-
|
|
242
|
+
// Auto-saved state is deliberately *not* transactional. The direct read bypasses any active
|
|
243
|
+
// transaction - a buffered value seeded into this shared cache would survive a rollback forever.
|
|
244
|
+
const value = await withDirectStorageAccess(async () => this.getValue(key, defaultValue));
|
|
199
245
|
// The await above could have run in parallel with another call to this function. If the other call finished more quickly,
|
|
200
246
|
// the value will in cache at this point, and returning the new fetched value would introduce two different instances of
|
|
201
247
|
// the auto-saved object, and only the latter one would be persisted.
|
|
202
248
|
// Therefore we re-check the cache here, and if such race condition happened, we drop the fetched value and return the cached one.
|
|
203
|
-
if (this
|
|
204
|
-
return this
|
|
249
|
+
if (this.#cache.has(key)) {
|
|
250
|
+
return this.#cache.get(key);
|
|
205
251
|
}
|
|
206
|
-
this
|
|
252
|
+
this.#cache.set(key, value);
|
|
207
253
|
this.ensurePersistStateEvent();
|
|
208
254
|
return value;
|
|
209
255
|
}
|
|
210
256
|
ensurePersistStateEvent() {
|
|
211
|
-
if (this
|
|
257
|
+
if (this.#persistStateEventStarted) {
|
|
212
258
|
return;
|
|
213
259
|
}
|
|
214
260
|
serviceLocator.getEventManager().on('persistState', async () => {
|
|
215
261
|
const promises = [];
|
|
216
|
-
for (const [key, value] of this
|
|
262
|
+
for (const [key, value] of this.#cache) {
|
|
217
263
|
promises.push(this.setValue(key, value).catch((error) => serviceLocator.getLogger().warning(`Failed to persist the state value to ${key}`, { error })));
|
|
218
264
|
}
|
|
219
265
|
await Promise.all(promises);
|
|
220
266
|
});
|
|
221
|
-
this
|
|
267
|
+
this.#persistStateEventStarted = true;
|
|
222
268
|
}
|
|
223
269
|
async *fetchKeyValuePages(options, mapRecord) {
|
|
224
|
-
for
|
|
270
|
+
// Reduce the journal once for the whole iteration, not once per key inside `readRecord`.
|
|
271
|
+
const buffered = this.bufferedJournalEntries();
|
|
272
|
+
for await (const page of this.fetchKeyPages(options, buffered)) {
|
|
225
273
|
const results = [];
|
|
226
274
|
for (const item of page) {
|
|
227
|
-
|
|
228
|
-
|
|
275
|
+
// The shared transaction-aware read, so a key that exists only in the transaction resolves
|
|
276
|
+
// here instead of being dropped (`values()` would disagree with `keys()` on length).
|
|
277
|
+
const record = await this.readRecord(item.key, buffered);
|
|
229
278
|
if (record) {
|
|
230
279
|
const parsed = parseValue(record.value, record.contentType ?? null);
|
|
231
280
|
results.push(mapRecord(item.key, parsed));
|
|
@@ -234,18 +283,37 @@ export class KeyValueStore {
|
|
|
234
283
|
yield results;
|
|
235
284
|
}
|
|
236
285
|
}
|
|
237
|
-
async *fetchKeyPages(options, limit = KVS_KEYS_DEFAULT_LIMIT) {
|
|
286
|
+
async *fetchKeyPages(options, buffered = this.bufferedJournalEntries(), limit = KVS_KEYS_DEFAULT_LIMIT) {
|
|
287
|
+
// Buffered keys are emitted first, then the real pages with any buffered (or tombstoned) key
|
|
288
|
+
// skipped - a merge-join is not an option, since `listKeys` promises no sort order.
|
|
289
|
+
const shadowedKeys = new Set();
|
|
290
|
+
if (buffered) {
|
|
291
|
+
const bufferedItems = [];
|
|
292
|
+
for (const [key, entry] of buffered) {
|
|
293
|
+
shadowedKeys.add(key);
|
|
294
|
+
if (entry.value === null)
|
|
295
|
+
continue;
|
|
296
|
+
if (options.prefix !== undefined && !key.startsWith(options.prefix))
|
|
297
|
+
continue;
|
|
298
|
+
bufferedItems.push(bufferedKeyItemData(key, entry));
|
|
299
|
+
}
|
|
300
|
+
if (bufferedItems.length > 0) {
|
|
301
|
+
bufferedItems.sort((a, b) => (a.key < b.key ? -1 : 1));
|
|
302
|
+
yield bufferedItems;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
238
305
|
let exclusiveStartKey;
|
|
239
306
|
while (true) {
|
|
240
|
-
this
|
|
307
|
+
this.#statsTracker.add('listCount');
|
|
241
308
|
const { items, isTruncated, nextExclusiveStartKey } = await this.backend.listKeys({
|
|
242
309
|
...options,
|
|
243
310
|
exclusiveStartKey,
|
|
244
311
|
limit,
|
|
245
312
|
});
|
|
246
|
-
yield items;
|
|
313
|
+
yield shadowedKeys.size > 0 ? items.filter((item) => !shadowedKeys.has(item.key)) : items;
|
|
247
314
|
if (!isTruncated)
|
|
248
315
|
break;
|
|
316
|
+
// Paginate from the raw backend cursor - it may reject a key it did not hand out.
|
|
249
317
|
exclusiveStartKey = nextExclusiveStartKey;
|
|
250
318
|
}
|
|
251
319
|
}
|
|
@@ -293,7 +361,7 @@ export class KeyValueStore {
|
|
|
293
361
|
* @param [options] Record options.
|
|
294
362
|
*/
|
|
295
363
|
async setValue(key, value, options = {}) {
|
|
296
|
-
|
|
364
|
+
const transaction = activeStorageTransaction();
|
|
297
365
|
ow(key, 'key', ow.string.nonEmpty);
|
|
298
366
|
ow(key, ow.string.validate((k) => ({
|
|
299
367
|
validator: ow.isValid(k, ow.string.matches(KEY_VALUE_STORE_KEY_REGEX)),
|
|
@@ -307,48 +375,87 @@ export class KeyValueStore {
|
|
|
307
375
|
}));
|
|
308
376
|
// Make copy of options, don't update what user passed.
|
|
309
377
|
const optionsCopy = { ...options };
|
|
378
|
+
// The whole transaction branch sits *above* the auto-saved cache update below, so a buffered
|
|
379
|
+
// write touches nothing outside the journal. That cache is shared, process-lifetime frontend
|
|
380
|
+
// state, so mutating it here would survive a rollback and later be persisted by `persistState`.
|
|
381
|
+
// The commit replay re-enters this method with no active transaction and updates it then.
|
|
382
|
+
if (transaction) {
|
|
383
|
+
if (isStream(value)) {
|
|
384
|
+
// A stream cannot serve both a read-your-own-writes read and the commit replay. The
|
|
385
|
+
// transaction is known-active here, so throw directly rather than via the conditional guard.
|
|
386
|
+
throw operationRejectedInTransaction(`KeyValueStore.setValue() with a stream value (key "${key}")`, 'a stream can only be consumed once, so it cannot be buffered until commit.');
|
|
387
|
+
}
|
|
388
|
+
// Validation only, result discarded: the journal snapshot (`structuredClone`) accepts values
|
|
389
|
+
// JSON cannot, which would otherwise only throw at a later read or at commit.
|
|
390
|
+
if (value !== null) {
|
|
391
|
+
serializeValue(value, optionsCopy.contentType);
|
|
392
|
+
}
|
|
393
|
+
// One snapshot serves both the reads and the commit replay; `null` is a tombstone.
|
|
394
|
+
transaction.recordJournalEntry({
|
|
395
|
+
type: 'keyValueStore',
|
|
396
|
+
participant: this,
|
|
397
|
+
storageId: this.id,
|
|
398
|
+
key,
|
|
399
|
+
value: value === null ? null : snapshotValue(value),
|
|
400
|
+
options: optionsCopy,
|
|
401
|
+
});
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
310
404
|
// If we try to set the value of a cached state to a different reference, we need to update the cache accordingly.
|
|
311
|
-
const cachedValue = this
|
|
405
|
+
const cachedValue = this.#cache.get(key);
|
|
312
406
|
if (cachedValue && cachedValue !== value) {
|
|
313
407
|
if (value === null) {
|
|
314
408
|
// Cached state can be only object, so a propagation of `null` means removing all its properties.
|
|
315
|
-
Object.keys(cachedValue).forEach((k) => this
|
|
409
|
+
Object.keys(cachedValue).forEach((k) => this.#cache.delete(k));
|
|
316
410
|
}
|
|
317
411
|
else if (typeof value === 'object') {
|
|
318
412
|
// We need to remove the keys that are no longer present in the new value.
|
|
319
413
|
Object.keys(cachedValue)
|
|
320
414
|
.filter((k) => !(k in value))
|
|
321
|
-
.forEach((k) => this
|
|
415
|
+
.forEach((k) => this.#cache.delete(k));
|
|
322
416
|
// And update the existing ones + add new ones.
|
|
323
417
|
Object.assign(cachedValue, value);
|
|
324
418
|
}
|
|
325
419
|
}
|
|
326
420
|
// In this case delete the record.
|
|
327
421
|
if (value === null) {
|
|
328
|
-
this
|
|
422
|
+
this.#statsTracker.add('deleteCount');
|
|
329
423
|
return this.backend.deleteValue(key);
|
|
330
424
|
}
|
|
331
425
|
const serialized = serializeValue(value, optionsCopy.contentType);
|
|
332
|
-
this
|
|
426
|
+
this.#statsTracker.add('writeCount');
|
|
333
427
|
return this.backend.setValue({
|
|
334
428
|
key,
|
|
335
429
|
value: serialized.value,
|
|
336
430
|
contentType: serialized.contentType,
|
|
337
431
|
});
|
|
338
432
|
}
|
|
433
|
+
/** @internal */
|
|
434
|
+
async commitJournalEntries(entries) {
|
|
435
|
+
// One `setValue` per key, last write wins - idempotent under retry.
|
|
436
|
+
const lastWritePerKey = new Map();
|
|
437
|
+
for (const entry of entries) {
|
|
438
|
+
if (entry.type === 'keyValueStore') {
|
|
439
|
+
lastWritePerKey.set(entry.key, { value: entry.value, options: entry.options });
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
for (const [key, { value, options }] of lastWritePerKey) {
|
|
443
|
+
await this.setValue(key, value, options);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
339
446
|
/**
|
|
340
447
|
* Removes the key-value store either from the Apify cloud storage or from the local directory,
|
|
341
448
|
* depending on the mode of operation.
|
|
342
449
|
*/
|
|
343
450
|
async drop() {
|
|
344
|
-
|
|
451
|
+
rejectOperationInTransaction('KeyValueStore.drop()');
|
|
345
452
|
await this.backend.drop();
|
|
346
453
|
serviceLocator.getStorageInstanceManager().removeFromCache(this);
|
|
347
454
|
}
|
|
348
455
|
/** @internal */
|
|
349
456
|
clearCache() {
|
|
350
|
-
|
|
351
|
-
this
|
|
457
|
+
rejectOperationInTransaction('KeyValueStore.clearCache()');
|
|
458
|
+
this.#cache.clear();
|
|
352
459
|
}
|
|
353
460
|
/**
|
|
354
461
|
* Iterates over key-value store keys, yielding each in turn to an `iteratee` function.
|
|
@@ -372,7 +479,7 @@ export class KeyValueStore {
|
|
|
372
479
|
* @param [options] All `forEachKey()` parameters.
|
|
373
480
|
*/
|
|
374
481
|
async forEachKey(iteratee, options = {}) {
|
|
375
|
-
|
|
482
|
+
tryCancel();
|
|
376
483
|
ow(iteratee, ow.function);
|
|
377
484
|
ow(options, ow.object.exactShape({
|
|
378
485
|
prefix: ow.optional.string,
|
|
@@ -408,7 +515,7 @@ export class KeyValueStore {
|
|
|
408
515
|
* @param options Options for the iteration.
|
|
409
516
|
*/
|
|
410
517
|
keys(options = {}) {
|
|
411
|
-
|
|
518
|
+
tryCancel();
|
|
412
519
|
return createDualIterable({
|
|
413
520
|
createPages: () => this.fetchKeyPages(options),
|
|
414
521
|
extractItems: (page) => page.map((item) => item.key),
|
|
@@ -438,7 +545,7 @@ export class KeyValueStore {
|
|
|
438
545
|
* @param options Options for the iteration.
|
|
439
546
|
*/
|
|
440
547
|
values(options = {}) {
|
|
441
|
-
|
|
548
|
+
tryCancel();
|
|
442
549
|
return createDualIterable({
|
|
443
550
|
createPages: () => this.fetchKeyValuePages(options, (_key, value) => value),
|
|
444
551
|
extractItems: (page) => page,
|
|
@@ -468,7 +575,7 @@ export class KeyValueStore {
|
|
|
468
575
|
* @param options Options for the iteration.
|
|
469
576
|
*/
|
|
470
577
|
entries(options = {}) {
|
|
471
|
-
|
|
578
|
+
tryCancel();
|
|
472
579
|
return createDualIterable({
|
|
473
580
|
createPages: () => this.fetchKeyValuePages(options, (key, value) => [key, value]),
|
|
474
581
|
extractItems: (page) => page,
|
|
@@ -515,7 +622,7 @@ export class KeyValueStore {
|
|
|
515
622
|
* @param [options] Storage manager options.
|
|
516
623
|
*/
|
|
517
624
|
static async open(identifier, options = {}) {
|
|
518
|
-
|
|
625
|
+
tryCancel();
|
|
519
626
|
ow(options, ow.object.exactShape({
|
|
520
627
|
configuration: ow.optional.object.instanceOf(Configuration),
|
|
521
628
|
storageBackend: ow.optional.object,
|
|
@@ -652,3 +759,22 @@ export class KeyValueStore {
|
|
|
652
759
|
return store.getValue(store.configuration.inputKey);
|
|
653
760
|
}
|
|
654
761
|
}
|
|
762
|
+
/** Normalizes a codec-serialized value into the `Buffer | ArrayBuffer` shape raw record reads promise. */
|
|
763
|
+
function normalizeSerializedValue(value) {
|
|
764
|
+
if (typeof value === 'string') {
|
|
765
|
+
return Buffer.from(value);
|
|
766
|
+
}
|
|
767
|
+
if (ArrayBuffer.isView(value)) {
|
|
768
|
+
return Buffer.isBuffer(value) ? value : Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
769
|
+
}
|
|
770
|
+
return value;
|
|
771
|
+
}
|
|
772
|
+
/** Computes the key listing item (serialized byte size and content type) of a buffered entry. */
|
|
773
|
+
function bufferedKeyItemData(key, entry) {
|
|
774
|
+
const serialized = serializeValue(entry.value, entry.options?.contentType);
|
|
775
|
+
return {
|
|
776
|
+
key,
|
|
777
|
+
size: normalizeSerializedValue(serialized.value).byteLength,
|
|
778
|
+
contentType: serialized.contentType,
|
|
779
|
+
};
|
|
780
|
+
}
|
|
@@ -12,9 +12,8 @@
|
|
|
12
12
|
* @internal
|
|
13
13
|
*/
|
|
14
14
|
export declare class RequestDeduplicationCache {
|
|
15
|
+
#private;
|
|
15
16
|
private readonly size;
|
|
16
|
-
private keys;
|
|
17
|
-
private ids;
|
|
18
17
|
constructor(size?: number);
|
|
19
18
|
get(cacheKey: string): string | null;
|
|
20
19
|
add(cacheKey: string, requestId: string): void;
|
|
@@ -13,26 +13,26 @@
|
|
|
13
13
|
*/
|
|
14
14
|
export class RequestDeduplicationCache {
|
|
15
15
|
size;
|
|
16
|
-
keys;
|
|
17
|
-
ids;
|
|
16
|
+
#keys;
|
|
17
|
+
#ids;
|
|
18
18
|
// The slot count is the same for every queue, so it's a fixed default rather than a per-consumer option.
|
|
19
19
|
constructor(size = 1_000_000) {
|
|
20
20
|
this.size = size;
|
|
21
|
-
this
|
|
22
|
-
this
|
|
21
|
+
this.#keys = new Array(size);
|
|
22
|
+
this.#ids = new Array(size);
|
|
23
23
|
}
|
|
24
24
|
get(cacheKey) {
|
|
25
25
|
const index = this.indexOf(cacheKey);
|
|
26
|
-
return this
|
|
26
|
+
return this.#keys[index] === cacheKey ? this.#ids[index] : null;
|
|
27
27
|
}
|
|
28
28
|
add(cacheKey, requestId) {
|
|
29
29
|
const index = this.indexOf(cacheKey);
|
|
30
|
-
this
|
|
31
|
-
this
|
|
30
|
+
this.#keys[index] = cacheKey;
|
|
31
|
+
this.#ids[index] = requestId;
|
|
32
32
|
}
|
|
33
33
|
clear() {
|
|
34
|
-
this
|
|
35
|
-
this
|
|
34
|
+
this.#keys = new Array(this.size);
|
|
35
|
+
this.#ids = new Array(this.size);
|
|
36
36
|
}
|
|
37
37
|
// A cheap FNV-1a hash of the cache key — avoids pulling in a dedicated hashing dependency.
|
|
38
38
|
indexOf(cacheKey) {
|
|
@@ -216,28 +216,18 @@ export interface RequestListOptions {
|
|
|
216
216
|
* @category Sources
|
|
217
217
|
*/
|
|
218
218
|
export declare class RequestList implements IRequestLoader {
|
|
219
|
-
private
|
|
219
|
+
#private;
|
|
220
220
|
/**
|
|
221
221
|
* Array of all requests from all sources, in the order as they appeared in sources.
|
|
222
222
|
* All requests in the array have distinct uniqueKey!
|
|
223
223
|
* @internal
|
|
224
224
|
*/
|
|
225
225
|
readonly requests: (Request | RequestOptions)[];
|
|
226
|
-
/** Index to the next item in requests array to fetch. All previous requests are either handled or in progress. */
|
|
227
|
-
private nextIndex;
|
|
228
|
-
/** Dictionary, key is Request.uniqueKey, value is corresponding index in the requests array. */
|
|
229
|
-
private uniqueKeyToIndex;
|
|
230
226
|
/**
|
|
231
227
|
* Set of `uniqueKey`s of requests that were returned by fetchNextRequest().
|
|
232
228
|
* @internal
|
|
233
229
|
*/
|
|
234
230
|
inProgress: Set<string>;
|
|
235
|
-
/**
|
|
236
|
-
* `uniqueKey`s of requests that were in progress when the state was last persisted and thus need to be
|
|
237
|
-
* re-crawled after a restart. They are served before advancing through the rest of the sources.
|
|
238
|
-
* @internal
|
|
239
|
-
*/
|
|
240
|
-
private requestsToRetry;
|
|
241
231
|
/**
|
|
242
232
|
* Starts as true because until we handle the first request, the list is effectively persisted by doing nothing.
|
|
243
233
|
* @internal
|
|
@@ -248,17 +238,7 @@ export declare class RequestList implements IRequestLoader {
|
|
|
248
238
|
* @internal
|
|
249
239
|
*/
|
|
250
240
|
areRequestsPersisted: boolean;
|
|
251
|
-
private isLoading;
|
|
252
|
-
private isInitialized;
|
|
253
|
-
private persistStateKey?;
|
|
254
|
-
private persistRequestsKey?;
|
|
255
|
-
private initialState?;
|
|
256
|
-
private store?;
|
|
257
|
-
private keepDuplicateUrls;
|
|
258
241
|
private sources;
|
|
259
|
-
private sourcesFunction?;
|
|
260
|
-
private proxyConfiguration?;
|
|
261
|
-
private httpClient?;
|
|
262
242
|
/**
|
|
263
243
|
* To create new instance of `RequestList` we need to use `RequestList.open()` factory method.
|
|
264
244
|
* @param options All `RequestList` configuration options
|
|
@@ -437,7 +417,7 @@ export declare class RequestList implements IRequestLoader {
|
|
|
437
417
|
/**
|
|
438
418
|
* @internal wraps public utility for mocking purposes
|
|
439
419
|
*/
|
|
440
|
-
private
|
|
420
|
+
private downloadListOfUrls;
|
|
441
421
|
}
|
|
442
422
|
/**
|
|
443
423
|
* Represents state of a {@link RequestList}. It can be used to resume a {@link RequestList} which has been previously processed.
|