@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
|
@@ -25,11 +25,11 @@ export class SitemapRequestLoader {
|
|
|
25
25
|
*
|
|
26
26
|
* We use this to persist custom user fields on the in-progress requests.
|
|
27
27
|
*/
|
|
28
|
-
requestData = new Map();
|
|
28
|
+
#requestData = new Map();
|
|
29
29
|
/**
|
|
30
30
|
* Object for keeping track of the sitemap parsing progress.
|
|
31
31
|
*/
|
|
32
|
-
sitemapParsingProgress = {
|
|
32
|
+
#sitemapParsingProgress = {
|
|
33
33
|
/**
|
|
34
34
|
* URL of the sitemap that is currently being parsed. `null` if no sitemap is being parsed.
|
|
35
35
|
*/
|
|
@@ -49,7 +49,7 @@ export class SitemapRequestLoader {
|
|
|
49
49
|
*
|
|
50
50
|
* Fetch the next URL to be processed using `fetchNextRequest()`.
|
|
51
51
|
*/
|
|
52
|
-
urlQueueStream;
|
|
52
|
+
#urlQueueStream;
|
|
53
53
|
/**
|
|
54
54
|
* Indicates whether the request list sitemap loading was aborted.
|
|
55
55
|
*
|
|
@@ -58,25 +58,25 @@ export class SitemapRequestLoader {
|
|
|
58
58
|
*
|
|
59
59
|
* If the loading is aborted and all the requests are handled, `isFinished()` will return `true`.
|
|
60
60
|
*/
|
|
61
|
-
abortLoading = false;
|
|
61
|
+
#abortLoading = false;
|
|
62
62
|
/** Number of URLs that were marked as handled */
|
|
63
|
-
handledUrlCount = 0;
|
|
64
|
-
persistStateKey;
|
|
65
|
-
store;
|
|
66
|
-
closed = false;
|
|
63
|
+
#handledUrlCount = 0;
|
|
64
|
+
#persistStateKey;
|
|
65
|
+
#store;
|
|
66
|
+
#closed = false;
|
|
67
67
|
/**
|
|
68
68
|
* Proxy URL to be used for sitemap loading.
|
|
69
69
|
*/
|
|
70
|
-
proxyUrl;
|
|
70
|
+
#proxyUrl;
|
|
71
71
|
/**
|
|
72
72
|
* Logger instance.
|
|
73
73
|
*/
|
|
74
|
-
log;
|
|
75
|
-
urlExcludePatternObjects = [];
|
|
76
|
-
urlPatternObjects = [];
|
|
74
|
+
#log;
|
|
75
|
+
#urlExcludePatternObjects = [];
|
|
76
|
+
#urlPatternObjects = [];
|
|
77
77
|
/** EventManager used to handle persistence */
|
|
78
|
-
events;
|
|
79
|
-
persistenceOptions;
|
|
78
|
+
#events;
|
|
79
|
+
#persistenceOptions;
|
|
80
80
|
/** @internal */
|
|
81
81
|
constructor(options) {
|
|
82
82
|
const urlPatternValidator = ow.any(ow.string, ow.regExp, ow.object.hasKeys('glob'), ow.object.hasKeys('regexp'));
|
|
@@ -94,19 +94,19 @@ export class SitemapRequestLoader {
|
|
|
94
94
|
httpClient: ow.optional.object,
|
|
95
95
|
}));
|
|
96
96
|
const { include, exclude } = options;
|
|
97
|
-
this
|
|
97
|
+
this.#log = serviceLocator.getLogger().child({ prefix: 'SitemapRequestLoader' });
|
|
98
98
|
if (exclude?.length) {
|
|
99
|
-
this
|
|
99
|
+
this.#urlExcludePatternObjects.push(...constructUrlPatternObjects(exclude));
|
|
100
100
|
}
|
|
101
101
|
if (include?.length) {
|
|
102
|
-
this
|
|
102
|
+
this.#urlPatternObjects.push(...constructUrlPatternObjects(include));
|
|
103
103
|
}
|
|
104
|
-
this
|
|
105
|
-
this
|
|
106
|
-
this
|
|
107
|
-
this
|
|
108
|
-
this
|
|
109
|
-
this
|
|
104
|
+
this.#persistStateKey = options.persistStateKey;
|
|
105
|
+
this.#persistenceOptions = { enable: true, ...options.persistenceOptions };
|
|
106
|
+
this.#proxyUrl = options.proxyUrl;
|
|
107
|
+
this.#urlQueueStream = this.createNewStream(options.maxBufferSize ?? 200);
|
|
108
|
+
this.#sitemapParsingProgress.pendingSitemapUrls = new Set(options.sitemapUrls);
|
|
109
|
+
this.#events = serviceLocator.getEventManager();
|
|
110
110
|
this.persistState = this.persistState.bind(this);
|
|
111
111
|
}
|
|
112
112
|
/**
|
|
@@ -139,8 +139,8 @@ export class SitemapRequestLoader {
|
|
|
139
139
|
* @returns `true` if the URL matches the patterns, `false` otherwise.
|
|
140
140
|
*/
|
|
141
141
|
isUrlMatchingPatterns(url) {
|
|
142
|
-
return (!this
|
|
143
|
-
(this
|
|
142
|
+
return (!this.#urlExcludePatternObjects.some(this.matchesUrl(url)) &&
|
|
143
|
+
(this.#urlPatternObjects.length === 0 || this.#urlPatternObjects.some(this.matchesUrl(url))));
|
|
144
144
|
}
|
|
145
145
|
/**
|
|
146
146
|
* Adds a URL to the queue of parsed URLs.
|
|
@@ -149,13 +149,13 @@ export class SitemapRequestLoader {
|
|
|
149
149
|
*/
|
|
150
150
|
async pushNextUrl(url) {
|
|
151
151
|
return new Promise((resolve) => {
|
|
152
|
-
if (this
|
|
152
|
+
if (this.#closed || (url && !this.isUrlMatchingPatterns(url))) {
|
|
153
153
|
resolve();
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
156
|
-
if (!this
|
|
156
|
+
if (!this.#urlQueueStream.push(url)) {
|
|
157
157
|
// This doesn't work with the 'drain' event (it's not emitted for some reason).
|
|
158
|
-
this
|
|
158
|
+
this.#urlQueueStream.once('readdata', () => {
|
|
159
159
|
resolve();
|
|
160
160
|
});
|
|
161
161
|
}
|
|
@@ -172,21 +172,21 @@ export class SitemapRequestLoader {
|
|
|
172
172
|
*/
|
|
173
173
|
async readNextUrl() {
|
|
174
174
|
return new Promise((resolve) => {
|
|
175
|
-
if (this
|
|
175
|
+
if (this.#closed) {
|
|
176
176
|
resolve(null);
|
|
177
177
|
return;
|
|
178
178
|
}
|
|
179
|
-
const result = this
|
|
179
|
+
const result = this.#urlQueueStream.read();
|
|
180
180
|
if (!result && !this.isSitemapFullyLoaded()) {
|
|
181
|
-
this
|
|
182
|
-
const nextUrl = this
|
|
181
|
+
this.#urlQueueStream.once('readable', () => {
|
|
182
|
+
const nextUrl = this.#urlQueueStream.read();
|
|
183
183
|
resolve(nextUrl);
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
186
|
else {
|
|
187
187
|
resolve(result);
|
|
188
188
|
}
|
|
189
|
-
this
|
|
189
|
+
this.#urlQueueStream.emit('readdata');
|
|
190
190
|
});
|
|
191
191
|
}
|
|
192
192
|
/**
|
|
@@ -195,8 +195,8 @@ export class SitemapRequestLoader {
|
|
|
195
195
|
* If this is `false`, the background processing is either still in progress or was aborted.
|
|
196
196
|
*/
|
|
197
197
|
isSitemapFullyLoaded() {
|
|
198
|
-
return (this
|
|
199
|
-
this
|
|
198
|
+
return (this.#sitemapParsingProgress.inProgressSitemapUrl === null &&
|
|
199
|
+
this.#sitemapParsingProgress.pendingSitemapUrls.size === 0);
|
|
200
200
|
}
|
|
201
201
|
/**
|
|
202
202
|
* Start processing the sitemaps and loading the URLs.
|
|
@@ -204,34 +204,34 @@ export class SitemapRequestLoader {
|
|
|
204
204
|
* Resolves once all the sitemaps URLs have been fully loaded (sets `isSitemapFullyLoaded` to `true`).
|
|
205
205
|
*/
|
|
206
206
|
async load({ parseSitemapOptions, }) {
|
|
207
|
-
while (!this.isSitemapFullyLoaded() && !this
|
|
208
|
-
const sitemapUrl = this
|
|
209
|
-
this
|
|
207
|
+
while (!this.isSitemapFullyLoaded() && !this.#abortLoading) {
|
|
208
|
+
const sitemapUrl = this.#sitemapParsingProgress.inProgressSitemapUrl ??
|
|
209
|
+
this.#sitemapParsingProgress.pendingSitemapUrls.values().next().value;
|
|
210
210
|
try {
|
|
211
|
-
for await (const item of parseSitemap([{ type: 'url', url: sitemapUrl }], this
|
|
211
|
+
for await (const item of parseSitemap([{ type: 'url', url: sitemapUrl }], this.#proxyUrl, {
|
|
212
212
|
...parseSitemapOptions,
|
|
213
213
|
maxDepth: 0,
|
|
214
214
|
emitNestedSitemaps: true,
|
|
215
215
|
})) {
|
|
216
216
|
if (!item.originSitemapUrl) {
|
|
217
217
|
// This is a nested sitemap
|
|
218
|
-
this
|
|
218
|
+
this.#sitemapParsingProgress.pendingSitemapUrls.add(item.loc);
|
|
219
219
|
continue;
|
|
220
220
|
}
|
|
221
|
-
if (!this
|
|
221
|
+
if (!this.#sitemapParsingProgress.inProgressEntries.has(item.loc)) {
|
|
222
222
|
await this.pushNextUrl(item.loc);
|
|
223
|
-
this
|
|
223
|
+
this.#sitemapParsingProgress.inProgressEntries.add(item.loc);
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
catch (e) {
|
|
228
|
-
this
|
|
228
|
+
this.#log.error('Error loading sitemap contents:', e);
|
|
229
229
|
}
|
|
230
|
-
this
|
|
231
|
-
this
|
|
232
|
-
this
|
|
230
|
+
this.#sitemapParsingProgress.pendingSitemapUrls.delete(sitemapUrl);
|
|
231
|
+
this.#sitemapParsingProgress.inProgressEntries.clear();
|
|
232
|
+
this.#sitemapParsingProgress.inProgressSitemapUrl = null;
|
|
233
233
|
}
|
|
234
|
-
this
|
|
234
|
+
this.#urlQueueStream.end();
|
|
235
235
|
}
|
|
236
236
|
/**
|
|
237
237
|
* Open a sitemap and start processing it.
|
|
@@ -250,15 +250,15 @@ export class SitemapRequestLoader {
|
|
|
250
250
|
void requestList.load({
|
|
251
251
|
parseSitemapOptions: { logger: serviceLocator.getLogger(), ...options.parseSitemapOptions, httpClient },
|
|
252
252
|
});
|
|
253
|
-
if (requestList
|
|
254
|
-
requestList
|
|
253
|
+
if (requestList.#persistenceOptions.enable) {
|
|
254
|
+
requestList.#events.on("persistState" /* EventType.PERSIST_STATE */, requestList.persistState);
|
|
255
255
|
}
|
|
256
256
|
options?.signal?.addEventListener('abort', () => {
|
|
257
|
-
requestList
|
|
257
|
+
requestList.#abortLoading = true;
|
|
258
258
|
});
|
|
259
259
|
if (options.timeoutMillis) {
|
|
260
260
|
setTimeout(() => {
|
|
261
|
-
requestList
|
|
261
|
+
requestList.#abortLoading = true;
|
|
262
262
|
}, options.timeoutMillis);
|
|
263
263
|
}
|
|
264
264
|
return requestList;
|
|
@@ -269,14 +269,14 @@ export class SitemapRequestLoader {
|
|
|
269
269
|
async getTotalCount() {
|
|
270
270
|
// Total known so far = not-yet-fetched (still buffered in the stream) + in-progress (fetched but not
|
|
271
271
|
// yet handled) + already handled.
|
|
272
|
-
return this
|
|
272
|
+
return this.#urlQueueStream.readableLength + this.inProgress.size + this.#handledUrlCount;
|
|
273
273
|
}
|
|
274
274
|
/**
|
|
275
275
|
* @inheritDoc
|
|
276
276
|
*/
|
|
277
277
|
async getPendingCount() {
|
|
278
278
|
// Pending = everything not yet handled = not-yet-fetched + in-progress.
|
|
279
|
-
return this
|
|
279
|
+
return this.#urlQueueStream.readableLength + this.inProgress.size;
|
|
280
280
|
}
|
|
281
281
|
/**
|
|
282
282
|
* Combines this list with a request manager (a {@link RequestQueue} by default) into a
|
|
@@ -293,31 +293,31 @@ export class SitemapRequestLoader {
|
|
|
293
293
|
* @inheritDoc
|
|
294
294
|
*/
|
|
295
295
|
async isFinished() {
|
|
296
|
-
return ((await this.isEmpty()) && this.inProgress.size === 0 && (this.isSitemapFullyLoaded() || this
|
|
296
|
+
return ((await this.isEmpty()) && this.inProgress.size === 0 && (this.isSitemapFullyLoaded() || this.#abortLoading));
|
|
297
297
|
}
|
|
298
298
|
/**
|
|
299
299
|
* @inheritDoc
|
|
300
300
|
*/
|
|
301
301
|
async isEmpty() {
|
|
302
|
-
return this
|
|
302
|
+
return this.#urlQueueStream.readableLength === 0;
|
|
303
303
|
}
|
|
304
304
|
/**
|
|
305
305
|
* @inheritDoc
|
|
306
306
|
*/
|
|
307
307
|
async getHandledCount() {
|
|
308
|
-
return this
|
|
308
|
+
return this.#handledUrlCount;
|
|
309
309
|
}
|
|
310
310
|
/**
|
|
311
311
|
* @inheritDoc
|
|
312
312
|
*/
|
|
313
313
|
async persistState() {
|
|
314
|
-
if (this
|
|
314
|
+
if (this.#persistStateKey === undefined) {
|
|
315
315
|
return;
|
|
316
316
|
}
|
|
317
|
-
this
|
|
317
|
+
this.#store ??= await KeyValueStore.open();
|
|
318
318
|
const urlQueue = [];
|
|
319
|
-
while (this
|
|
320
|
-
const url = this
|
|
319
|
+
while (this.#urlQueueStream.readableLength > 0) {
|
|
320
|
+
const url = this.#urlQueueStream.read();
|
|
321
321
|
if (url === null) {
|
|
322
322
|
break;
|
|
323
323
|
}
|
|
@@ -325,48 +325,48 @@ export class SitemapRequestLoader {
|
|
|
325
325
|
}
|
|
326
326
|
// Create a new stream, as we have read all the URLs from the current one.
|
|
327
327
|
// Pushing the urls back to the original stream might not be possible if it has been ended.
|
|
328
|
-
const newStream = this.createNewStream(this
|
|
328
|
+
const newStream = this.createNewStream(this.#urlQueueStream.readableHighWaterMark);
|
|
329
329
|
for (const url of urlQueue) {
|
|
330
330
|
newStream.push(url);
|
|
331
331
|
}
|
|
332
|
-
if (this
|
|
332
|
+
if (this.#urlQueueStream.writableEnded) {
|
|
333
333
|
newStream.end();
|
|
334
334
|
}
|
|
335
|
-
this
|
|
336
|
-
await this
|
|
335
|
+
this.#urlQueueStream = newStream;
|
|
336
|
+
await this.#store.setValue(this.#persistStateKey, {
|
|
337
337
|
sitemapParsingProgress: {
|
|
338
|
-
pendingSitemapUrls: Array.from(this
|
|
339
|
-
inProgressSitemapUrl: this
|
|
340
|
-
inProgressEntries: Array.from(this
|
|
338
|
+
pendingSitemapUrls: Array.from(this.#sitemapParsingProgress.pendingSitemapUrls),
|
|
339
|
+
inProgressSitemapUrl: this.#sitemapParsingProgress.inProgressSitemapUrl,
|
|
340
|
+
inProgressEntries: Array.from(this.#sitemapParsingProgress.inProgressEntries),
|
|
341
341
|
},
|
|
342
342
|
// Re-queue in-progress requests to the front so they are retried if the state is restored.
|
|
343
343
|
urlQueue: [...this.inProgress, ...urlQueue],
|
|
344
|
-
requestData: Array.from(this
|
|
345
|
-
abortLoading: this
|
|
346
|
-
closed: this
|
|
344
|
+
requestData: Array.from(this.#requestData.entries()),
|
|
345
|
+
abortLoading: this.#abortLoading,
|
|
346
|
+
closed: this.#closed,
|
|
347
347
|
});
|
|
348
348
|
}
|
|
349
349
|
async restoreState() {
|
|
350
350
|
await purgeDefaultStorages({ onlyPurgeOnce: true });
|
|
351
|
-
if (this
|
|
351
|
+
if (this.#persistStateKey === undefined) {
|
|
352
352
|
return;
|
|
353
353
|
}
|
|
354
|
-
this
|
|
355
|
-
const state = await this
|
|
354
|
+
this.#store ??= await KeyValueStore.open();
|
|
355
|
+
const state = await this.#store.getValue(this.#persistStateKey);
|
|
356
356
|
if (state === null) {
|
|
357
357
|
return;
|
|
358
358
|
}
|
|
359
|
-
this
|
|
359
|
+
this.#sitemapParsingProgress = {
|
|
360
360
|
pendingSitemapUrls: new Set(state.sitemapParsingProgress.pendingSitemapUrls),
|
|
361
361
|
inProgressSitemapUrl: state.sitemapParsingProgress.inProgressSitemapUrl,
|
|
362
362
|
inProgressEntries: new Set(state.sitemapParsingProgress.inProgressEntries),
|
|
363
363
|
};
|
|
364
|
-
this
|
|
364
|
+
this.#requestData = new Map(state.requestData ?? []);
|
|
365
365
|
for (const url of state.urlQueue) {
|
|
366
|
-
this
|
|
366
|
+
this.#urlQueueStream.push(url);
|
|
367
367
|
}
|
|
368
|
-
this
|
|
369
|
-
this
|
|
368
|
+
this.#abortLoading = state.abortLoading;
|
|
369
|
+
this.#closed = state.closed;
|
|
370
370
|
}
|
|
371
371
|
/**
|
|
372
372
|
* @inheritDoc
|
|
@@ -377,11 +377,11 @@ export class SitemapRequestLoader {
|
|
|
377
377
|
return null;
|
|
378
378
|
}
|
|
379
379
|
// A restored in-progress request already has its Request data; don't overwrite it.
|
|
380
|
-
if (!this
|
|
381
|
-
this
|
|
380
|
+
if (!this.#requestData.has(nextUrl)) {
|
|
381
|
+
this.#requestData.set(nextUrl, new Request({ url: nextUrl }));
|
|
382
382
|
}
|
|
383
383
|
this.inProgress.add(nextUrl);
|
|
384
|
-
return this
|
|
384
|
+
return this.#requestData.get(nextUrl);
|
|
385
385
|
}
|
|
386
386
|
/**
|
|
387
387
|
* @inheritDoc
|
|
@@ -400,20 +400,20 @@ export class SitemapRequestLoader {
|
|
|
400
400
|
* Calling `fetchNextRequest()` after this method will always return `null`.
|
|
401
401
|
*/
|
|
402
402
|
async teardown() {
|
|
403
|
-
this
|
|
404
|
-
this
|
|
405
|
-
this
|
|
403
|
+
this.#closed = true;
|
|
404
|
+
this.#abortLoading = true;
|
|
405
|
+
this.#events.off("persistState" /* EventType.PERSIST_STATE */, this.persistState);
|
|
406
406
|
await this.persistState();
|
|
407
|
-
this
|
|
407
|
+
this.#urlQueueStream.emit('readdata'); // unblocks the potentially waiting `pushNextUrl` call
|
|
408
408
|
}
|
|
409
409
|
/**
|
|
410
410
|
* @inheritDoc
|
|
411
411
|
*/
|
|
412
412
|
async markRequestAsHandled(request) {
|
|
413
|
-
this
|
|
413
|
+
this.#handledUrlCount += 1;
|
|
414
414
|
this.ensureInProgress(request.url);
|
|
415
415
|
this.inProgress.delete(request.url);
|
|
416
|
-
this
|
|
416
|
+
this.#requestData.delete(request.url);
|
|
417
417
|
}
|
|
418
418
|
ensureInProgress(url) {
|
|
419
419
|
if (!this.inProgress.has(url)) {
|
|
@@ -120,8 +120,8 @@ class StorageCache {
|
|
|
120
120
|
* assigns a reserved default alias.
|
|
121
121
|
*/
|
|
122
122
|
export class StorageInstanceManager {
|
|
123
|
-
cache = new StorageCache();
|
|
124
|
-
openerLocks = new Map();
|
|
123
|
+
#cache = new StorageCache();
|
|
124
|
+
#openerLocks = new Map();
|
|
125
125
|
/**
|
|
126
126
|
* Open (or retrieve from cache) a storage instance.
|
|
127
127
|
*
|
|
@@ -142,46 +142,46 @@ export class StorageInstanceManager {
|
|
|
142
142
|
}
|
|
143
143
|
// Fast-path cache check (no lock).
|
|
144
144
|
if (alias !== undefined) {
|
|
145
|
-
const cached = this
|
|
145
|
+
const cached = this.#cache.get(cls, { alias, backendCacheKey });
|
|
146
146
|
if (cached)
|
|
147
147
|
return cached;
|
|
148
148
|
}
|
|
149
149
|
else if (id) {
|
|
150
|
-
const cached = this
|
|
150
|
+
const cached = this.#cache.get(cls, { id, backendCacheKey });
|
|
151
151
|
if (cached)
|
|
152
152
|
return cached;
|
|
153
153
|
}
|
|
154
154
|
else if (name) {
|
|
155
|
-
const cached = this
|
|
155
|
+
const cached = this.#cache.get(cls, { name, backendCacheKey });
|
|
156
156
|
if (cached)
|
|
157
157
|
return cached;
|
|
158
158
|
}
|
|
159
159
|
const identifierKey = id ?? name ?? alias ?? DEFAULT_STORAGE_ALIAS;
|
|
160
160
|
const lockKey = `${cls.name}:${identifierKey}:${backendCacheKey}`;
|
|
161
|
-
if (!this
|
|
162
|
-
this
|
|
161
|
+
if (!this.#openerLocks.has(lockKey)) {
|
|
162
|
+
this.#openerLocks.set(lockKey, new AsyncQueue());
|
|
163
163
|
}
|
|
164
|
-
const queue = this
|
|
164
|
+
const queue = this.#openerLocks.get(lockKey);
|
|
165
165
|
await queue.wait();
|
|
166
166
|
try {
|
|
167
167
|
// Double-check cache under lock (another caller may have filled it while we waited).
|
|
168
168
|
if (alias !== undefined) {
|
|
169
|
-
const cached = this
|
|
169
|
+
const cached = this.#cache.get(cls, { alias, backendCacheKey });
|
|
170
170
|
if (cached)
|
|
171
171
|
return cached;
|
|
172
172
|
}
|
|
173
173
|
else if (id) {
|
|
174
|
-
const cached = this
|
|
174
|
+
const cached = this.#cache.get(cls, { id, backendCacheKey });
|
|
175
175
|
if (cached)
|
|
176
176
|
return cached;
|
|
177
177
|
}
|
|
178
178
|
else if (name) {
|
|
179
|
-
const cached = this
|
|
179
|
+
const cached = this.#cache.get(cls, { name, backendCacheKey });
|
|
180
180
|
if (cached)
|
|
181
181
|
return cached;
|
|
182
182
|
}
|
|
183
183
|
// Prevent the same string from being used as both a name and an alias.
|
|
184
|
-
this
|
|
184
|
+
this.#cache.checkNameAliasConflict(cls, { name, alias, backendCacheKey });
|
|
185
185
|
// Cache miss — create the sub-backend and storage instance.
|
|
186
186
|
const subBackend = await backendOpener();
|
|
187
187
|
const storageInfo = await subBackend.getMetadata();
|
|
@@ -189,7 +189,7 @@ export class StorageInstanceManager {
|
|
|
189
189
|
// we just fetched (so `id`/`name` etc. are available synchronously) along with the backend.
|
|
190
190
|
const instance = new cls({ metadata: storageInfo, backend: subBackend });
|
|
191
191
|
// Atomic cache writes (no awaits between these).
|
|
192
|
-
this
|
|
192
|
+
this.#cache.set(cls, instance, backendCacheKey, alias);
|
|
193
193
|
return instance;
|
|
194
194
|
}
|
|
195
195
|
finally {
|
|
@@ -197,7 +197,7 @@ export class StorageInstanceManager {
|
|
|
197
197
|
// Clean up idle locks so the map doesn't grow unboundedly
|
|
198
198
|
// (mirrors crawlee-python's WeakValueDictionary behaviour).
|
|
199
199
|
if (queue.remaining === 0) {
|
|
200
|
-
this
|
|
200
|
+
this.#openerLocks.delete(lockKey);
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
203
|
}
|
|
@@ -205,7 +205,7 @@ export class StorageInstanceManager {
|
|
|
205
205
|
* Remove a storage instance from the cache (called from `storage.drop()`).
|
|
206
206
|
*/
|
|
207
207
|
removeFromCache(instance) {
|
|
208
|
-
this
|
|
208
|
+
this.#cache.removeFromCache(instance);
|
|
209
209
|
}
|
|
210
210
|
/**
|
|
211
211
|
* Clear the entire cache. Also calls `clearCache()` on any cached KeyValueStore
|
|
@@ -213,12 +213,12 @@ export class StorageInstanceManager {
|
|
|
213
213
|
* Called during service locator reset.
|
|
214
214
|
*/
|
|
215
215
|
clearCache() {
|
|
216
|
-
for (const instance of this
|
|
216
|
+
for (const instance of this.#cache.allValues()) {
|
|
217
217
|
if ('clearCache' in instance && typeof instance.clearCache === 'function') {
|
|
218
218
|
instance.clearCache();
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
|
-
this
|
|
221
|
+
this.#cache.clear();
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
/**
|
|
@@ -39,7 +39,7 @@ export interface RequestQueueStats {
|
|
|
39
39
|
* the buckets that make sense for it.
|
|
40
40
|
*/
|
|
41
41
|
export declare class StorageStatsTracker<T extends Record<keyof T, number>> {
|
|
42
|
-
private
|
|
42
|
+
#private;
|
|
43
43
|
constructor(initial: T);
|
|
44
44
|
/** Increment a counter bucket by `by` (default `1`). */
|
|
45
45
|
add(key: keyof T, by?: number): void;
|
|
@@ -14,16 +14,16 @@
|
|
|
14
14
|
* the buckets that make sense for it.
|
|
15
15
|
*/
|
|
16
16
|
export class StorageStatsTracker {
|
|
17
|
-
counters;
|
|
17
|
+
#counters;
|
|
18
18
|
constructor(initial) {
|
|
19
|
-
this
|
|
19
|
+
this.#counters = { ...initial };
|
|
20
20
|
}
|
|
21
21
|
/** Increment a counter bucket by `by` (default `1`). */
|
|
22
22
|
add(key, by = 1) {
|
|
23
|
-
this
|
|
23
|
+
this.#counters[key] += by;
|
|
24
24
|
}
|
|
25
25
|
/** Return a snapshot of the current counters. The returned object is a copy and safe to keep. */
|
|
26
26
|
get current() {
|
|
27
|
-
return { ...this
|
|
27
|
+
return { ...this.#counters };
|
|
28
28
|
}
|
|
29
29
|
}
|