@crawlee/core 4.0.0-rc.0 → 4.0.0-rc.1
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 +1 -1
- package/configuration.d.ts +15 -46
- package/configuration.js +8 -20
- package/errors.d.ts +12 -53
- package/errors.js +13 -66
- package/events/index.d.ts +1 -0
- package/events/local_event_manager.d.ts +0 -7
- package/events/local_event_manager.js +8 -8
- package/events/system_info.d.ts +38 -0
- package/index.d.ts +2 -8
- package/index.js +4 -8
- package/internal.d.ts +8 -0
- package/internal.js +9 -0
- package/log.d.ts +10 -11
- package/log.js +53 -25
- package/memory-storage/memory-storage.d.ts +12 -7
- package/memory-storage/memory-storage.js +45 -17
- package/memory-storage/resource-clients/dataset.d.ts +0 -5
- package/memory-storage/resource-clients/dataset.js +17 -20
- package/memory-storage/resource-clients/key-value-store.d.ts +0 -9
- package/memory-storage/resource-clients/key-value-store.js +11 -33
- package/memory-storage/resource-clients/request-queue.d.ts +0 -22
- package/memory-storage/resource-clients/request-queue.js +57 -53
- package/package.json +16 -18
- package/proxy_configuration.d.ts +20 -23
- package/proxy_configuration.js +18 -12
- package/recoverable_state.d.ts +28 -6
- package/recoverable_state.js +51 -14
- package/request.d.ts +18 -104
- package/request.js +41 -220
- package/serialization.js +3 -3
- package/service_locator.d.ts +3 -0
- package/service_locator.js +2 -0
- package/storages/dataset.d.ts +9 -15
- package/storages/dataset.js +22 -14
- package/storages/index.d.ts +3 -4
- package/storages/index.js +1 -4
- package/storages/key_value_store.d.ts +12 -46
- package/storages/key_value_store.js +31 -47
- package/storages/key_value_store_codec.js +6 -11
- package/storages/request_dedup_cache.d.ts +0 -2
- package/storages/request_dedup_cache.js +8 -8
- package/storages/request_list.d.ts +6 -82
- package/storages/request_list.js +175 -179
- package/storages/request_loader.d.ts +48 -22
- package/storages/request_loader.js +36 -1
- package/storages/request_manager.d.ts +86 -0
- package/storages/request_manager_tandem.d.ts +13 -28
- package/storages/request_manager_tandem.js +46 -43
- package/storages/request_queue.d.ts +19 -49
- package/storages/request_queue.js +92 -88
- package/storages/storage_instance_manager.d.ts +1 -2
- package/storages/storage_instance_manager.js +4 -4
- package/storages/transaction.d.ts +27 -9
- package/storages/transaction.js +56 -11
- package/storages/utils.d.ts +2 -2
- package/validators.d.ts +3 -2
- package/validators.js +3 -2
- package/autoscaling/autoscaled_pool.d.ts +0 -195
- package/autoscaling/autoscaled_pool.js +0 -386
- package/autoscaling/concurrency_system.d.ts +0 -268
- package/autoscaling/concurrency_system.js +0 -362
- package/autoscaling/cpu_load_signal.d.ts +0 -43
- package/autoscaling/cpu_load_signal.js +0 -47
- package/autoscaling/event_loop_load_signal.d.ts +0 -51
- package/autoscaling/event_loop_load_signal.js +0 -60
- package/autoscaling/index.d.ts +0 -9
- package/autoscaling/index.js +0 -9
- package/autoscaling/load_signal.d.ts +0 -100
- package/autoscaling/load_signal.js +0 -105
- package/autoscaling/memory_load_signal.d.ts +0 -47
- package/autoscaling/memory_load_signal.js +0 -106
- package/autoscaling/snapshotter.d.ts +0 -84
- package/autoscaling/snapshotter.js +0 -67
- package/autoscaling/storage_backend_load_signal.d.ts +0 -56
- package/autoscaling/storage_backend_load_signal.js +0 -73
- package/autoscaling/system_status.d.ts +0 -159
- package/autoscaling/system_status.js +0 -139
- package/autoscaling/weighted_avg.d.ts +0 -5
- package/autoscaling/weighted_avg.js +0 -14
- package/cookie_utils.d.ts +0 -44
- package/cookie_utils.js +0 -122
- package/crawlers/context_pipeline.d.ts +0 -70
- package/crawlers/context_pipeline.js +0 -122
- package/crawlers/crawler_commons.d.ts +0 -159
- package/crawlers/error_snapshotter.d.ts +0 -57
- package/crawlers/error_snapshotter.js +0 -117
- package/crawlers/error_tracker.d.ts +0 -54
- package/crawlers/error_tracker.js +0 -308
- package/crawlers/index.d.ts +0 -5
- package/crawlers/index.js +0 -4
- package/crawlers/internals/types.d.ts +0 -7
- package/crawlers/internals/types.js +0 -1
- package/crawlers/statistics.d.ts +0 -328
- package/crawlers/statistics.js +0 -536
- package/enqueue_links/enqueue_links.d.ts +0 -156
- package/enqueue_links/enqueue_links.js +0 -78
- package/enqueue_links/index.d.ts +0 -2
- package/enqueue_links/index.js +0 -2
- package/enqueue_links/shared.d.ts +0 -93
- package/enqueue_links/shared.js +0 -239
- package/http.d.ts +0 -9
- package/http.js +0 -28
- package/router.d.ts +0 -306
- package/router.js +0 -309
- package/session_pool/consts.d.ts +0 -3
- package/session_pool/consts.js +0 -3
- package/session_pool/errors.d.ts +0 -7
- package/session_pool/errors.js +0 -11
- package/session_pool/fingerprint.d.ts +0 -9
- package/session_pool/fingerprint.js +0 -30
- package/session_pool/index.d.ts +0 -4
- package/session_pool/index.js +0 -4
- package/session_pool/session.d.ts +0 -150
- package/session_pool/session.js +0 -220
- package/session_pool/session_pool.d.ts +0 -240
- package/session_pool/session_pool.js +0 -394
- package/storages/sitemap_request_loader.d.ts +0 -201
- package/storages/sitemap_request_loader.js +0 -438
- package/storages/throttling_request_manager.d.ts +0 -239
- package/storages/throttling_request_manager.js +0 -646
- /package/{crawlers/crawler_commons.js → events/system_info.js} +0 -0
package/log.js
CHANGED
|
@@ -30,23 +30,43 @@ import log, { Log, Logger, LoggerJson, LoggerText, LogLevel } from '@apify/log';
|
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
32
|
export class BaseCrawleeLogger {
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
options;
|
|
36
|
-
|
|
33
|
+
// Note: If wrapping logger in a Proxy, unbound methods calling #-fields throw TypeError
|
|
34
|
+
// unless bound to the target (see createLogProxy in adaptive-playwright-crawler.ts).
|
|
35
|
+
#options;
|
|
36
|
+
#loggedOnce = new Set();
|
|
37
37
|
constructor(options = {}) {
|
|
38
|
-
this
|
|
38
|
+
this.#options = options;
|
|
39
39
|
}
|
|
40
40
|
getOptions() {
|
|
41
|
-
return this
|
|
41
|
+
return this.#options;
|
|
42
42
|
}
|
|
43
43
|
setOptions(options) {
|
|
44
|
-
this
|
|
44
|
+
this.#options = { ...this.#options, ...options };
|
|
45
45
|
}
|
|
46
46
|
child(options) {
|
|
47
47
|
return this.createChild(options);
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Whether `once` suppresses this message. Keyed by level as well as text, so that a message logged once as
|
|
51
|
+
* `info` does not swallow the same text logged as a `warning`.
|
|
52
|
+
*
|
|
53
|
+
* Handled here rather than in {@link BaseCrawleeLogger.logWithLevel} so that every logger gets it, instead
|
|
54
|
+
* of each implementation having to dedupe for itself.
|
|
55
|
+
*/
|
|
56
|
+
#suppressedAsRepeat(level, message, options) {
|
|
57
|
+
if (!options?.once) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const key = `${level}:${message}`;
|
|
61
|
+
if (this.#loggedOnce.has(key)) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
this.#loggedOnce.add(key);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
error(message, data, options) {
|
|
68
|
+
if (this.#suppressedAsRepeat(LogLevel.ERROR, message, options))
|
|
69
|
+
return;
|
|
50
70
|
this.logWithLevel(LogLevel.ERROR, message, data);
|
|
51
71
|
}
|
|
52
72
|
exception(exception, message, data) {
|
|
@@ -56,25 +76,35 @@ export class BaseCrawleeLogger {
|
|
|
56
76
|
exception,
|
|
57
77
|
});
|
|
58
78
|
}
|
|
59
|
-
softFail(message, data) {
|
|
79
|
+
softFail(message, data, options) {
|
|
80
|
+
if (this.#suppressedAsRepeat(LogLevel.SOFT_FAIL, message, options))
|
|
81
|
+
return;
|
|
60
82
|
this.logWithLevel(LogLevel.SOFT_FAIL, message, data);
|
|
61
83
|
}
|
|
62
|
-
warning(message, data) {
|
|
84
|
+
warning(message, data, options) {
|
|
85
|
+
if (this.#suppressedAsRepeat(LogLevel.WARNING, message, options))
|
|
86
|
+
return;
|
|
63
87
|
this.logWithLevel(LogLevel.WARNING, message, data);
|
|
64
88
|
}
|
|
65
89
|
warningOnce(message) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
info(message, data) {
|
|
90
|
+
// Gates before delegating, so that a suppressed repeat is not even a `warning()` call.
|
|
91
|
+
if (this.#suppressedAsRepeat(LogLevel.WARNING, message, { once: true }))
|
|
92
|
+
return;
|
|
93
|
+
this.warning(message);
|
|
94
|
+
}
|
|
95
|
+
info(message, data, options) {
|
|
96
|
+
if (this.#suppressedAsRepeat(LogLevel.INFO, message, options))
|
|
97
|
+
return;
|
|
72
98
|
this.logWithLevel(LogLevel.INFO, message, data);
|
|
73
99
|
}
|
|
74
|
-
debug(message, data) {
|
|
100
|
+
debug(message, data, options) {
|
|
101
|
+
if (this.#suppressedAsRepeat(LogLevel.DEBUG, message, options))
|
|
102
|
+
return;
|
|
75
103
|
this.logWithLevel(LogLevel.DEBUG, message, data);
|
|
76
104
|
}
|
|
77
|
-
perf(message, data) {
|
|
105
|
+
perf(message, data, options) {
|
|
106
|
+
if (this.#suppressedAsRepeat(LogLevel.PERF, message, options))
|
|
107
|
+
return;
|
|
78
108
|
this.logWithLevel(LogLevel.PERF, `[PERF] ${message}`, data);
|
|
79
109
|
}
|
|
80
110
|
deprecated(message) {
|
|
@@ -88,18 +118,16 @@ export class BaseCrawleeLogger {
|
|
|
88
118
|
* Users who want to use a different logging library should implement {@link BaseCrawleeLogger} directly.
|
|
89
119
|
*/
|
|
90
120
|
export class ApifyLogAdapter extends BaseCrawleeLogger {
|
|
91
|
-
apifyLog;
|
|
92
|
-
constructor(
|
|
93
|
-
// kept as a TS-private parameter property: reached through the adaptive crawler's log proxy, see above
|
|
94
|
-
apifyLog, options) {
|
|
121
|
+
#apifyLog;
|
|
122
|
+
constructor(apifyLog, options) {
|
|
95
123
|
super(options ?? {});
|
|
96
|
-
this
|
|
124
|
+
this.#apifyLog = apifyLog;
|
|
97
125
|
}
|
|
98
126
|
logWithLevel(level, message, data) {
|
|
99
|
-
this
|
|
127
|
+
this.#apifyLog.internal(level, message, data);
|
|
100
128
|
}
|
|
101
129
|
createChild(options) {
|
|
102
|
-
return new ApifyLogAdapter(this
|
|
130
|
+
return new ApifyLogAdapter(this.#apifyLog.child({ prefix: options.prefix ?? null }), {
|
|
103
131
|
...this.getOptions(),
|
|
104
132
|
...options,
|
|
105
133
|
});
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import type * as storage from '@crawlee/types';
|
|
2
2
|
import type { CrawleeLogger } from '@crawlee/types';
|
|
3
|
-
import { DatasetBackend } from './resource-clients/dataset.js';
|
|
4
|
-
import { KeyValueStoreBackend } from './resource-clients/key-value-store.js';
|
|
5
|
-
import { RequestQueueBackend } from './resource-clients/request-queue.js';
|
|
6
3
|
export interface MemoryStorageOptions {
|
|
7
4
|
/**
|
|
8
5
|
* Optional logger for MemoryStorageBackend warnings.
|
|
@@ -12,18 +9,26 @@ export interface MemoryStorageOptions {
|
|
|
12
9
|
export declare class MemoryStorageBackend implements storage.StorageBackend {
|
|
13
10
|
#private;
|
|
14
11
|
readonly logger?: CrawleeLogger;
|
|
15
|
-
readonly keyValueStoreBackendCache: KeyValueStoreBackend[];
|
|
16
|
-
readonly datasetBackendCache: DatasetBackend[];
|
|
17
|
-
readonly requestQueueBackendCache: RequestQueueBackend[];
|
|
18
12
|
constructor(options?: MemoryStorageOptions);
|
|
19
13
|
/**
|
|
20
14
|
* Return a per-instance unique cache key so that distinct `MemoryStorageBackend` instances get separate
|
|
21
15
|
* cache partitions in the storage backend cache.
|
|
22
16
|
*/
|
|
23
17
|
getStorageBackendCacheKey(): string;
|
|
18
|
+
/**
|
|
19
|
+
* Evict a cached backend so that a dropped storage is no longer resolved by `createXBackend`,
|
|
20
|
+
* reported by `storageExists` or visited by `purge`. Returns whether the backend was cached, which
|
|
21
|
+
* tells the caller whether it still owns in-memory state worth clearing.
|
|
22
|
+
*
|
|
23
|
+
* The resource clients own their own entry's lifetime but must not reach into the caches directly.
|
|
24
|
+
* Because a client is only ever constructed by `createXBackend`, which caches it immediately, the
|
|
25
|
+
* entry matching `id` is always the caller itself.
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
evictBackend(type: 'Dataset' | 'KeyValueStore' | 'RequestQueue', id: string): boolean;
|
|
24
29
|
createDatasetBackend(options?: storage.StorageIdentifier): Promise<storage.DatasetBackend>;
|
|
25
30
|
createKeyValueStoreBackend(options?: storage.StorageIdentifier): Promise<storage.KeyValueStoreBackend>;
|
|
26
|
-
createRequestQueueBackend(options?: storage.StorageIdentifier): Promise<RequestQueueBackend>;
|
|
31
|
+
createRequestQueueBackend(options?: storage.StorageIdentifier): Promise<storage.RequestQueueBackend>;
|
|
27
32
|
storageExists(id: string, type: 'Dataset' | 'KeyValueStore' | 'RequestQueue'): Promise<boolean>;
|
|
28
33
|
/**
|
|
29
34
|
* Cleans up the run-scoped storages before the run starts. For the in-memory storage this simply
|
|
@@ -11,9 +11,9 @@ export class MemoryStorageBackend {
|
|
|
11
11
|
* cache by storage directory: two distinct `MemoryStorageBackend` instances must not share cached backends.
|
|
12
12
|
*/
|
|
13
13
|
#instanceCacheKey = `MemoryStorageBackend:${randomUUID()}`;
|
|
14
|
-
keyValueStoreBackendCache = [];
|
|
15
|
-
datasetBackendCache = [];
|
|
16
|
-
requestQueueBackendCache = [];
|
|
14
|
+
#keyValueStoreBackendCache = [];
|
|
15
|
+
#datasetBackendCache = [];
|
|
16
|
+
#requestQueueBackendCache = [];
|
|
17
17
|
constructor(options = {}) {
|
|
18
18
|
this.logger = options.logger;
|
|
19
19
|
}
|
|
@@ -24,6 +24,36 @@ export class MemoryStorageBackend {
|
|
|
24
24
|
getStorageBackendCacheKey() {
|
|
25
25
|
return this.#instanceCacheKey;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Evict a cached backend so that a dropped storage is no longer resolved by `createXBackend`,
|
|
29
|
+
* reported by `storageExists` or visited by `purge`. Returns whether the backend was cached, which
|
|
30
|
+
* tells the caller whether it still owns in-memory state worth clearing.
|
|
31
|
+
*
|
|
32
|
+
* The resource clients own their own entry's lifetime but must not reach into the caches directly.
|
|
33
|
+
* Because a client is only ever constructed by `createXBackend`, which caches it immediately, the
|
|
34
|
+
* entry matching `id` is always the caller itself.
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
evictBackend(type, id) {
|
|
38
|
+
let cache;
|
|
39
|
+
switch (type) {
|
|
40
|
+
case 'Dataset':
|
|
41
|
+
cache = this.#datasetBackendCache;
|
|
42
|
+
break;
|
|
43
|
+
case 'KeyValueStore':
|
|
44
|
+
cache = this.#keyValueStoreBackendCache;
|
|
45
|
+
break;
|
|
46
|
+
case 'RequestQueue':
|
|
47
|
+
cache = this.#requestQueueBackendCache;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
const index = cache.findIndex((entry) => entry.id === id);
|
|
51
|
+
if (index === -1) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
cache.splice(index, 1);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
27
57
|
static #resolveStorageKey(options) {
|
|
28
58
|
// No identifier at all means the default storage, which is opened under the reserved alias —
|
|
29
59
|
// same rule as `resolveStorageIdentifier` in the storage frontends, so that a backend used
|
|
@@ -37,7 +67,7 @@ export class MemoryStorageBackend {
|
|
|
37
67
|
}
|
|
38
68
|
async createDatasetBackend(options = {}) {
|
|
39
69
|
const { isAlias, cacheKey } = MemoryStorageBackend.#resolveStorageKey(options);
|
|
40
|
-
const found = this
|
|
70
|
+
const found = this.#datasetBackendCache.find((store) => store.id === cacheKey ||
|
|
41
71
|
store.name?.toLowerCase() === cacheKey.toLowerCase() ||
|
|
42
72
|
store.cacheKey.toLowerCase() === cacheKey.toLowerCase());
|
|
43
73
|
if (found) {
|
|
@@ -48,12 +78,12 @@ export class MemoryStorageBackend {
|
|
|
48
78
|
cacheKey,
|
|
49
79
|
storageBackend: this,
|
|
50
80
|
});
|
|
51
|
-
this
|
|
81
|
+
this.#datasetBackendCache.push(newStore);
|
|
52
82
|
return newStore;
|
|
53
83
|
}
|
|
54
84
|
async createKeyValueStoreBackend(options = {}) {
|
|
55
85
|
const { isAlias, cacheKey } = MemoryStorageBackend.#resolveStorageKey(options);
|
|
56
|
-
const found = this
|
|
86
|
+
const found = this.#keyValueStoreBackendCache.find((store) => store.id === cacheKey ||
|
|
57
87
|
store.name?.toLowerCase() === cacheKey.toLowerCase() ||
|
|
58
88
|
store.cacheKey.toLowerCase() === cacheKey.toLowerCase());
|
|
59
89
|
if (found) {
|
|
@@ -64,12 +94,12 @@ export class MemoryStorageBackend {
|
|
|
64
94
|
cacheKey,
|
|
65
95
|
storageBackend: this,
|
|
66
96
|
});
|
|
67
|
-
this
|
|
97
|
+
this.#keyValueStoreBackendCache.push(newStore);
|
|
68
98
|
return newStore;
|
|
69
99
|
}
|
|
70
100
|
async createRequestQueueBackend(options = {}) {
|
|
71
101
|
const { isAlias, cacheKey } = MemoryStorageBackend.#resolveStorageKey(options);
|
|
72
|
-
const found = this
|
|
102
|
+
const found = this.#requestQueueBackendCache.find((queue) => queue.id === cacheKey ||
|
|
73
103
|
queue.name?.toLowerCase() === cacheKey.toLowerCase() ||
|
|
74
104
|
queue.cacheKey.toLowerCase() === cacheKey.toLowerCase());
|
|
75
105
|
if (found) {
|
|
@@ -80,20 +110,20 @@ export class MemoryStorageBackend {
|
|
|
80
110
|
cacheKey,
|
|
81
111
|
storageBackend: this,
|
|
82
112
|
});
|
|
83
|
-
this
|
|
113
|
+
this.#requestQueueBackendCache.push(newStore);
|
|
84
114
|
return newStore;
|
|
85
115
|
}
|
|
86
116
|
async storageExists(id, type) {
|
|
87
117
|
let backends;
|
|
88
118
|
switch (type) {
|
|
89
119
|
case 'Dataset':
|
|
90
|
-
backends = this
|
|
120
|
+
backends = this.#datasetBackendCache;
|
|
91
121
|
break;
|
|
92
122
|
case 'KeyValueStore':
|
|
93
|
-
backends = this
|
|
123
|
+
backends = this.#keyValueStoreBackendCache;
|
|
94
124
|
break;
|
|
95
125
|
case 'RequestQueue':
|
|
96
|
-
backends = this
|
|
126
|
+
backends = this.#requestQueueBackendCache;
|
|
97
127
|
break;
|
|
98
128
|
default:
|
|
99
129
|
return false;
|
|
@@ -109,15 +139,13 @@ export class MemoryStorageBackend {
|
|
|
109
139
|
// `#resolveStorageKey` leaves `name` unset for the default and alias-keyed storages, which is what
|
|
110
140
|
// marks them as run-scoped. `'default'` is the exception — it collapses onto the default storage.
|
|
111
141
|
const isRunScoped = (store) => store.name === undefined || store.name === 'default';
|
|
112
|
-
const isDefault = (store) => store.name === 'default' || store.cacheKey === 'default';
|
|
113
142
|
const purgeRunScoped = async (cache, purgeStore) => {
|
|
114
143
|
await Promise.all(cache.filter(isRunScoped).map(async (store) => purgeStore(store)));
|
|
115
144
|
};
|
|
116
145
|
await Promise.all([
|
|
117
|
-
|
|
118
|
-
purgeRunScoped(this
|
|
119
|
-
purgeRunScoped(this
|
|
120
|
-
purgeRunScoped(this.requestQueueBackendCache, async (store) => store.purge()),
|
|
146
|
+
purgeRunScoped(this.#keyValueStoreBackendCache, async (store) => store.purge()),
|
|
147
|
+
purgeRunScoped(this.#datasetBackendCache, async (store) => store.purge()),
|
|
148
|
+
purgeRunScoped(this.#requestQueueBackendCache, async (store) => store.purge()),
|
|
121
149
|
]);
|
|
122
150
|
}
|
|
123
151
|
/**
|
|
@@ -25,16 +25,11 @@ export declare class DatasetBackend<Data extends Dictionary = Dictionary> extend
|
|
|
25
25
|
accessedAt: Date;
|
|
26
26
|
modifiedAt: Date;
|
|
27
27
|
itemCount: number;
|
|
28
|
-
private readonly storageBackend;
|
|
29
28
|
constructor(options: DatasetBackendOptions);
|
|
30
29
|
getMetadata(): Promise<storage.DatasetInfo>;
|
|
31
30
|
drop(): Promise<void>;
|
|
32
31
|
purge(): Promise<void>;
|
|
33
32
|
getData(options?: storage.DatasetBackendListOptions): Promise<storage.PaginatedList<Data>>;
|
|
34
|
-
private getDataPage;
|
|
35
33
|
pushData(items: Data[]): Promise<void>;
|
|
36
34
|
toDatasetInfo(): storage.DatasetInfo;
|
|
37
|
-
private generateLocalEntryName;
|
|
38
|
-
private getStartAndEndIndexes;
|
|
39
|
-
private updateTimestamps;
|
|
40
35
|
}
|
|
@@ -23,48 +23,45 @@ export class DatasetBackend extends BaseClient {
|
|
|
23
23
|
modifiedAt = new Date();
|
|
24
24
|
itemCount = 0;
|
|
25
25
|
#datasetEntries = new Map();
|
|
26
|
-
|
|
27
|
-
storageBackend;
|
|
26
|
+
#storageBackend;
|
|
28
27
|
constructor(options) {
|
|
29
28
|
super(options.id ?? randomUUID());
|
|
30
29
|
this.name = options.name;
|
|
31
30
|
this.cacheKey = options.cacheKey ?? this.name ?? this.id;
|
|
32
|
-
this
|
|
31
|
+
this.#storageBackend = options.storageBackend;
|
|
33
32
|
}
|
|
34
33
|
async getMetadata() {
|
|
35
|
-
this
|
|
34
|
+
this.#updateTimestamps(false);
|
|
36
35
|
return this.toDatasetInfo();
|
|
37
36
|
}
|
|
38
37
|
async drop() {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
oldBackend.itemCount = 0;
|
|
43
|
-
oldBackend.#datasetEntries.clear();
|
|
38
|
+
if (this.#storageBackend.evictBackend('Dataset', this.id)) {
|
|
39
|
+
this.itemCount = 0;
|
|
40
|
+
this.#datasetEntries.clear();
|
|
44
41
|
}
|
|
45
42
|
}
|
|
46
43
|
async purge() {
|
|
47
44
|
this.itemCount = 0;
|
|
48
45
|
this.#datasetEntries.clear();
|
|
49
|
-
this
|
|
46
|
+
this.#updateTimestamps(true);
|
|
50
47
|
}
|
|
51
48
|
getData(options = {}) {
|
|
52
49
|
const { desc, limit, offset } = parseArgument(options, schemas.datasetListItemsOptions);
|
|
53
|
-
return this
|
|
50
|
+
return this.#getDataPage({
|
|
54
51
|
desc,
|
|
55
52
|
offset: offset ?? 0,
|
|
56
53
|
limit: Math.min(limit ?? LIST_ITEMS_LIMIT, LIST_ITEMS_LIMIT),
|
|
57
54
|
});
|
|
58
55
|
}
|
|
59
|
-
async getDataPage(options = {}) {
|
|
56
|
+
async #getDataPage(options = {}) {
|
|
60
57
|
const { limit = LIST_ITEMS_LIMIT, offset = 0, desc } = options;
|
|
61
|
-
const [start, end] = this
|
|
58
|
+
const [start, end] = this.#getStartAndEndIndexes(desc ? Math.max(this.itemCount - offset - limit, 0) : offset, limit);
|
|
62
59
|
const items = [];
|
|
63
60
|
for (let idx = start; idx < end; idx++) {
|
|
64
|
-
const entryNumber = this
|
|
61
|
+
const entryNumber = this.#generateLocalEntryName(idx);
|
|
65
62
|
items.push(this.#datasetEntries.get(entryNumber));
|
|
66
63
|
}
|
|
67
|
-
this
|
|
64
|
+
this.#updateTimestamps(false);
|
|
68
65
|
return {
|
|
69
66
|
count: items.length,
|
|
70
67
|
desc: desc ?? false,
|
|
@@ -76,10 +73,10 @@ export class DatasetBackend extends BaseClient {
|
|
|
76
73
|
}
|
|
77
74
|
async pushData(items) {
|
|
78
75
|
for (const entry of items) {
|
|
79
|
-
const idx = this
|
|
76
|
+
const idx = this.#generateLocalEntryName(++this.itemCount);
|
|
80
77
|
this.#datasetEntries.set(idx, JSON.parse(JSON.stringify(entry)));
|
|
81
78
|
}
|
|
82
|
-
this
|
|
79
|
+
this.#updateTimestamps(true);
|
|
83
80
|
}
|
|
84
81
|
toDatasetInfo() {
|
|
85
82
|
return {
|
|
@@ -91,15 +88,15 @@ export class DatasetBackend extends BaseClient {
|
|
|
91
88
|
name: this.name,
|
|
92
89
|
};
|
|
93
90
|
}
|
|
94
|
-
generateLocalEntryName(idx) {
|
|
91
|
+
#generateLocalEntryName(idx) {
|
|
95
92
|
return idx.toString().padStart(LOCAL_ENTRY_NAME_DIGITS, '0');
|
|
96
93
|
}
|
|
97
|
-
getStartAndEndIndexes(offset, limit = this.itemCount) {
|
|
94
|
+
#getStartAndEndIndexes(offset, limit = this.itemCount) {
|
|
98
95
|
const start = offset + 1;
|
|
99
96
|
const end = Math.min(offset + limit, this.itemCount) + 1;
|
|
100
97
|
return [start, end];
|
|
101
98
|
}
|
|
102
|
-
updateTimestamps(hasBeenModified) {
|
|
99
|
+
#updateTimestamps(hasBeenModified) {
|
|
103
100
|
this.accessedAt = new Date();
|
|
104
101
|
if (hasBeenModified) {
|
|
105
102
|
this.modifiedAt = new Date();
|
|
@@ -29,18 +29,10 @@ export declare class KeyValueStoreBackend extends BaseClient implements storage.
|
|
|
29
29
|
createdAt: Date;
|
|
30
30
|
accessedAt: Date;
|
|
31
31
|
modifiedAt: Date;
|
|
32
|
-
private readonly storageBackend;
|
|
33
32
|
constructor(options: KeyValueStoreBackendOptions);
|
|
34
33
|
getMetadata(): Promise<storage.KeyValueStoreInfo>;
|
|
35
34
|
drop(): Promise<void>;
|
|
36
35
|
purge(): Promise<void>;
|
|
37
|
-
/**
|
|
38
|
-
* Purges every record except the run's input. Used by {@link MemoryStorageBackend.purge} for the
|
|
39
|
-
* default key-value store, mirroring `FileSystemStorageBackend`, which preserves `INPUT` (and its
|
|
40
|
-
* extension variants) when purging the default store. The in-memory key has no extension, so we
|
|
41
|
-
* preserve the bare `INPUT` key only.
|
|
42
|
-
*/
|
|
43
|
-
purgeExceptInput(): Promise<void>;
|
|
44
36
|
listKeys(options?: storage.KeyValueStoreListKeysOptions): Promise<storage.KeyValueStoreListKeysResult>;
|
|
45
37
|
/**
|
|
46
38
|
* In-memory records are not file-backed, so there is no public file URL to return.
|
|
@@ -59,5 +51,4 @@ export declare class KeyValueStoreBackend extends BaseClient implements storage.
|
|
|
59
51
|
setValue(record: storage.KeyValueStoreInputRecord): Promise<void>;
|
|
60
52
|
deleteValue(key: string): Promise<void>;
|
|
61
53
|
toKeyValueStoreInfo(): storage.KeyValueStoreInfo;
|
|
62
|
-
private updateTimestamps;
|
|
63
54
|
}
|
|
@@ -20,11 +20,6 @@ const inputRecordSchema = z.object({
|
|
|
20
20
|
]),
|
|
21
21
|
contentType: z.string().min(1).optional(),
|
|
22
22
|
});
|
|
23
|
-
/**
|
|
24
|
-
* Key under which a run's input is stored in the default key-value store. Matches Crawlee's default
|
|
25
|
-
* `inputKey` (`CRAWLEE_INPUT_KEY`) and the `INPUT` files `FileSystemStorageBackend` preserves on purge.
|
|
26
|
-
*/
|
|
27
|
-
const KEY_VALUE_STORE_INPUT_KEY = 'INPUT';
|
|
28
23
|
export class KeyValueStoreBackend extends BaseClient {
|
|
29
24
|
name;
|
|
30
25
|
/**
|
|
@@ -36,42 +31,25 @@ export class KeyValueStoreBackend extends BaseClient {
|
|
|
36
31
|
accessedAt = new Date();
|
|
37
32
|
modifiedAt = new Date();
|
|
38
33
|
#keyValueEntries = new Map();
|
|
39
|
-
|
|
40
|
-
storageBackend;
|
|
34
|
+
#storageBackend;
|
|
41
35
|
constructor(options) {
|
|
42
36
|
super(options.id ?? randomUUID());
|
|
43
37
|
this.name = options.name;
|
|
44
38
|
this.cacheKey = options.cacheKey ?? this.name ?? this.id;
|
|
45
|
-
this
|
|
39
|
+
this.#storageBackend = options.storageBackend;
|
|
46
40
|
}
|
|
47
41
|
async getMetadata() {
|
|
48
|
-
this
|
|
42
|
+
this.#updateTimestamps(false);
|
|
49
43
|
return this.toKeyValueStoreInfo();
|
|
50
44
|
}
|
|
51
45
|
async drop() {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const [oldBackend] = this.storageBackend.keyValueStoreBackendCache.splice(storeIndex, 1);
|
|
55
|
-
oldBackend.#keyValueEntries.clear();
|
|
46
|
+
if (this.#storageBackend.evictBackend('KeyValueStore', this.id)) {
|
|
47
|
+
this.#keyValueEntries.clear();
|
|
56
48
|
}
|
|
57
49
|
}
|
|
58
50
|
async purge() {
|
|
59
51
|
this.#keyValueEntries.clear();
|
|
60
|
-
this
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Purges every record except the run's input. Used by {@link MemoryStorageBackend.purge} for the
|
|
64
|
-
* default key-value store, mirroring `FileSystemStorageBackend`, which preserves `INPUT` (and its
|
|
65
|
-
* extension variants) when purging the default store. The in-memory key has no extension, so we
|
|
66
|
-
* preserve the bare `INPUT` key only.
|
|
67
|
-
*/
|
|
68
|
-
async purgeExceptInput() {
|
|
69
|
-
for (const key of this.#keyValueEntries.keys()) {
|
|
70
|
-
if (key !== KEY_VALUE_STORE_INPUT_KEY) {
|
|
71
|
-
this.#keyValueEntries.delete(key);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
this.updateTimestamps(true);
|
|
52
|
+
this.#updateTimestamps(true);
|
|
75
53
|
}
|
|
76
54
|
async listKeys(options = {}) {
|
|
77
55
|
const { prefix, exclusiveStartKey, limit } = parseArgument(options, schemas.keyValueStoreListKeysOptions);
|
|
@@ -98,7 +76,7 @@ export class KeyValueStoreBackend extends BaseClient {
|
|
|
98
76
|
const isTruncated = limit !== undefined && filteredItems.length > limit;
|
|
99
77
|
const pageItems = isTruncated ? filteredItems.slice(0, limit) : filteredItems;
|
|
100
78
|
const nextExclusiveStartKey = isTruncated ? pageItems[pageItems.length - 1].key : undefined;
|
|
101
|
-
this
|
|
79
|
+
this.#updateTimestamps(false);
|
|
102
80
|
return {
|
|
103
81
|
items: pageItems,
|
|
104
82
|
count: pageItems.length,
|
|
@@ -142,7 +120,7 @@ export class KeyValueStoreBackend extends BaseClient {
|
|
|
142
120
|
// frontend treats it as "no content type" rather than a bogus value.
|
|
143
121
|
contentType: entry.contentType ?? (mime.contentType(entry.extension) || undefined),
|
|
144
122
|
};
|
|
145
|
-
this
|
|
123
|
+
this.#updateTimestamps(false);
|
|
146
124
|
return record;
|
|
147
125
|
}
|
|
148
126
|
async setValue(record) {
|
|
@@ -174,13 +152,13 @@ export class KeyValueStoreBackend extends BaseClient {
|
|
|
174
152
|
contentType,
|
|
175
153
|
};
|
|
176
154
|
this.#keyValueEntries.set(key, normalizedRecord);
|
|
177
|
-
this
|
|
155
|
+
this.#updateTimestamps(true);
|
|
178
156
|
}
|
|
179
157
|
async deleteValue(key) {
|
|
180
158
|
parseArgument(key, keySchema);
|
|
181
159
|
if (this.#keyValueEntries.has(key)) {
|
|
182
160
|
this.#keyValueEntries.delete(key);
|
|
183
|
-
this
|
|
161
|
+
this.#updateTimestamps(true);
|
|
184
162
|
}
|
|
185
163
|
}
|
|
186
164
|
toKeyValueStoreInfo() {
|
|
@@ -192,7 +170,7 @@ export class KeyValueStoreBackend extends BaseClient {
|
|
|
192
170
|
modifiedAt: this.modifiedAt,
|
|
193
171
|
};
|
|
194
172
|
}
|
|
195
|
-
updateTimestamps(hasBeenModified) {
|
|
173
|
+
#updateTimestamps(hasBeenModified) {
|
|
196
174
|
this.accessedAt = new Date();
|
|
197
175
|
if (hasBeenModified) {
|
|
198
176
|
this.modifiedAt = new Date();
|
|
@@ -34,28 +34,10 @@ export declare class RequestQueueBackend extends BaseClient implements storage.R
|
|
|
34
34
|
modifiedAt: Date;
|
|
35
35
|
handledRequestCount: number;
|
|
36
36
|
pendingRequestCount: number;
|
|
37
|
-
private readonly storageBackend;
|
|
38
37
|
constructor(options: RequestQueueBackendOptions);
|
|
39
38
|
getMetadata(): Promise<storage.RequestQueueInfo>;
|
|
40
39
|
drop(): Promise<void>;
|
|
41
40
|
purge(): Promise<void>;
|
|
42
|
-
private requestKeyIterator;
|
|
43
|
-
/**
|
|
44
|
-
* Scans the queue and returns the pending head — requests that are neither handled nor currently
|
|
45
|
-
* in progress — ordered by `orderNo`, deduplicated.
|
|
46
|
-
*
|
|
47
|
-
* When `detectInProgressRequests` is set, the result also carries an `hasInProgressRequests` flag
|
|
48
|
-
* telling whether any unhandled-but-in-progress request was skipped along the way. It lets
|
|
49
|
-
* {@link isFinished} distinguish "no work left at all" from "work remains, but it is currently being
|
|
50
|
-
* processed". Without it, a consumer with concurrency could consider the queue finished and shut the
|
|
51
|
-
* crawler down while it is still handling the last requests.
|
|
52
|
-
*
|
|
53
|
-
* Computing the flag is expensive: because an in-progress request may sit anywhere in the queue, it
|
|
54
|
-
* forces a scan of every pending entry even when only `limit` items are wanted. Callers that only
|
|
55
|
-
* need the head (e.g. {@link fetchNextRequest}, {@link isEmpty}) leave it off so the scan can stop as
|
|
56
|
-
* soon as the page is filled, keeping those calls O(head) instead of O(N).
|
|
57
|
-
*/
|
|
58
|
-
private listPendingHead;
|
|
59
41
|
fetchNextRequest(): Promise<storage.UpdateRequestSchema | undefined>;
|
|
60
42
|
addBatchOfRequests(requests: storage.RequestSchema[], options?: storage.RequestQueueOperationOptions): Promise<storage.BatchAddRequestsResult>;
|
|
61
43
|
getRequest(uniqueKey: string): Promise<storage.UpdateRequestSchema | undefined>;
|
|
@@ -70,8 +52,4 @@ export declare class RequestQueueBackend extends BaseClient implements storage.R
|
|
|
70
52
|
*/
|
|
71
53
|
listItems(): Promise<storage.UpdateRequestSchema[]>;
|
|
72
54
|
toRequestQueueInfo(): storage.RequestQueueInfo;
|
|
73
|
-
private updateTimestamps;
|
|
74
|
-
private jsonToRequest;
|
|
75
|
-
private createInternalRequest;
|
|
76
|
-
private calculateOrderNo;
|
|
77
55
|
}
|