@crawlee/core 4.0.0-beta.99 → 4.0.0-rc.0

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.
Files changed (109) hide show
  1. package/autoscaling/autoscaled_pool.d.ts +3 -21
  2. package/autoscaling/autoscaled_pool.js +105 -97
  3. package/autoscaling/concurrency_system.d.ts +7 -22
  4. package/autoscaling/concurrency_system.js +112 -100
  5. package/autoscaling/cpu_load_signal.d.ts +1 -2
  6. package/autoscaling/cpu_load_signal.js +11 -10
  7. package/autoscaling/event_loop_load_signal.d.ts +1 -4
  8. package/autoscaling/event_loop_load_signal.js +18 -18
  9. package/autoscaling/index.d.ts +1 -1
  10. package/autoscaling/index.js +1 -1
  11. package/autoscaling/load_signal.d.ts +8 -7
  12. package/autoscaling/load_signal.js +14 -12
  13. package/autoscaling/memory_load_signal.d.ts +3 -12
  14. package/autoscaling/memory_load_signal.js +41 -41
  15. package/autoscaling/snapshotter.d.ts +7 -10
  16. package/autoscaling/snapshotter.js +18 -18
  17. package/autoscaling/{client_load_signal.d.ts → storage_backend_load_signal.d.ts} +14 -17
  18. package/autoscaling/{client_load_signal.js → storage_backend_load_signal.js} +27 -27
  19. package/autoscaling/system_status.d.ts +9 -11
  20. package/autoscaling/system_status.js +13 -13
  21. package/configuration.d.ts +16 -16
  22. package/configuration.js +6 -6
  23. package/crawlers/context_pipeline.js +6 -6
  24. package/crawlers/crawler_commons.d.ts +14 -112
  25. package/crawlers/crawler_commons.js +1 -107
  26. package/crawlers/error_snapshotter.d.ts +2 -4
  27. package/crawlers/error_snapshotter.js +5 -5
  28. package/crawlers/index.d.ts +1 -1
  29. package/crawlers/index.js +0 -1
  30. package/crawlers/statistics.d.ts +164 -45
  31. package/crawlers/statistics.js +339 -153
  32. package/debug.js +4 -4
  33. package/enqueue_links/enqueue_links.d.ts +51 -159
  34. package/enqueue_links/enqueue_links.js +38 -231
  35. package/enqueue_links/shared.d.ts +34 -24
  36. package/enqueue_links/shared.js +54 -36
  37. package/errors.d.ts +43 -12
  38. package/errors.js +48 -9
  39. package/events/event_manager.d.ts +2 -2
  40. package/events/event_manager.js +7 -6
  41. package/events/local_event_manager.d.ts +1 -1
  42. package/events/local_event_manager.js +5 -5
  43. package/http.d.ts +9 -0
  44. package/http.js +28 -0
  45. package/index.d.ts +1 -1
  46. package/index.js +1 -1
  47. package/log.js +5 -1
  48. package/memory-storage/memory-storage.d.ts +3 -11
  49. package/memory-storage/memory-storage.js +42 -48
  50. package/memory-storage/resource-clients/dataset.d.ts +1 -1
  51. package/memory-storage/resource-clients/dataset.js +8 -13
  52. package/memory-storage/resource-clients/key-value-store.d.ts +1 -1
  53. package/memory-storage/resource-clients/key-value-store.js +36 -38
  54. package/memory-storage/resource-clients/request-queue.d.ts +4 -23
  55. package/memory-storage/resource-clients/request-queue.js +68 -80
  56. package/owned_or_injected.d.ts +1 -3
  57. package/owned_or_injected.js +17 -17
  58. package/package.json +8 -9
  59. package/proxy_configuration.d.ts +1 -3
  60. package/proxy_configuration.js +18 -14
  61. package/recoverable_state.d.ts +80 -45
  62. package/recoverable_state.js +163 -75
  63. package/request.d.ts +3 -4
  64. package/request.js +57 -44
  65. package/router.d.ts +6 -9
  66. package/router.js +23 -23
  67. package/serialization.js +14 -13
  68. package/service_locator.d.ts +19 -10
  69. package/service_locator.js +57 -48
  70. package/session_pool/session.d.ts +3 -14
  71. package/session_pool/session.js +76 -74
  72. package/session_pool/session_pool.d.ts +6 -12
  73. package/session_pool/session_pool.js +85 -77
  74. package/storages/batched_adds.d.ts +37 -0
  75. package/storages/batched_adds.js +73 -0
  76. package/storages/dataset.d.ts +12 -1
  77. package/storages/dataset.js +133 -32
  78. package/storages/index.d.ts +2 -1
  79. package/storages/index.js +2 -1
  80. package/storages/key_value_store.d.ts +19 -4
  81. package/storages/key_value_store.js +204 -75
  82. package/storages/request_dedup_cache.d.ts +1 -2
  83. package/storages/request_dedup_cache.js +9 -9
  84. package/storages/request_list.d.ts +4 -23
  85. package/storages/request_list.js +103 -96
  86. package/storages/request_loader.d.ts +5 -0
  87. package/storages/request_manager_tandem.d.ts +1 -10
  88. package/storages/request_manager_tandem.js +27 -27
  89. package/storages/request_queue.d.ts +21 -18
  90. package/storages/request_queue.js +318 -177
  91. package/storages/sitemap_request_loader.d.ts +22 -70
  92. package/storages/sitemap_request_loader.js +122 -116
  93. package/storages/storage_instance_manager.d.ts +1 -2
  94. package/storages/storage_instance_manager.js +17 -17
  95. package/storages/storage_stats.d.ts +1 -1
  96. package/storages/storage_stats.js +4 -4
  97. package/storages/throttling_request_manager.d.ts +239 -0
  98. package/storages/throttling_request_manager.js +646 -0
  99. package/storages/transaction.d.ts +252 -0
  100. package/storages/transaction.js +251 -0
  101. package/storages/utils.d.ts +6 -3
  102. package/storages/utils.js +11 -2
  103. package/system-info/runtime.js +7 -7
  104. package/url.d.ts +9 -0
  105. package/url.js +11 -0
  106. package/validators.d.ts +22 -25
  107. package/validators.js +13 -25
  108. package/storages/access_checking.d.ts +0 -12
  109. package/storages/access_checking.js +0 -17
@@ -1,43 +1,31 @@
1
- import type { BaseHttpClient } from '@crawlee/types';
2
- import { type ParseSitemapOptions } from '@crawlee/utils';
3
- import type { GlobInput, RegExpInput } from '../enqueue_links/shared.js';
1
+ import type { BaseHttpClient } from '@crawlee/http-client';
2
+ import { EnqueueStrategy, type ParseSitemapOptions } from '@crawlee/utils';
3
+ import type { UrlPatternInput } from '../enqueue_links/shared.js';
4
4
  import { Request } from '../request.js';
5
5
  import type { IRequestLoader } from './request_loader.js';
6
6
  import type { IRequestManager } from './request_manager.js';
7
7
  interface UrlConstraints {
8
8
  /**
9
- * An array of glob pattern strings or plain objects
10
- * containing glob pattern strings matching the URLs to be enqueued.
9
+ * An array of URL patterns that URLs must match to be included.
11
10
  *
12
- * The plain objects must include at least the `glob` property, which holds the glob pattern string.
11
+ * Accepts glob pattern strings, `{ glob: string }` objects, `RegExp` instances, or `{ regexp: RegExp }` objects.
13
12
  *
14
- * The matching is always case-insensitive.
15
- * If you need case-sensitive matching, use `regexps` property directly.
13
+ * Glob matching is always case-insensitive.
14
+ * If you need case-sensitive matching, use a `RegExp`.
16
15
  *
17
- * If `globs` is an empty array or `undefined`, and `regexps` are also not defined, then the `SitemapRequestLoader`
16
+ * If `include` is an empty array or `undefined`, then the `SitemapRequestLoader`
18
17
  * includes all the URLs from the sitemap.
19
18
  */
20
- globs?: readonly GlobInput[];
19
+ include?: readonly UrlPatternInput[];
21
20
  /**
22
- * An array of glob pattern strings, regexp patterns or plain objects
23
- * containing patterns matching URLs that will **never** be included.
21
+ * An array of URL patterns. Matching URLs will **not** be included.
24
22
  *
25
- * The plain objects must include either the `glob` property or the `regexp` property.
23
+ * Accepts glob pattern strings, `{ glob: string }` objects, `RegExp` instances, or `{ regexp: RegExp }` objects.
26
24
  *
27
25
  * Glob matching is always case-insensitive.
28
- * If you need case-sensitive matching, provide a regexp.
26
+ * If you need case-sensitive matching, use a `RegExp`.
29
27
  */
30
- exclude?: readonly (GlobInput | RegExp)[];
31
- /**
32
- * An array of regular expressions or plain objects
33
- * containing regular expressions matching the URLs to be enqueued.
34
- *
35
- * The plain objects must include at least the `regexp` property, which holds the regular expression.
36
- *
37
- * If `regexps` is an empty array or `undefined`, and `globs` are also not defined, then the `SitemapRequestLoader`
38
- * includes all the URLs from the sitemap.
39
- */
40
- regexps?: readonly RegExpInput[];
28
+ exclude?: readonly UrlPatternInput[];
41
29
  }
42
30
  export interface SitemapRequestLoaderOptions extends UrlConstraints {
43
31
  /**
@@ -77,6 +65,13 @@ export interface SitemapRequestLoaderOptions extends UrlConstraints {
77
65
  * @default 200
78
66
  */
79
67
  maxBufferSize?: number;
68
+ /**
69
+ * Keep only sitemap-derived URLs matching this strategy relative to the parent sitemap URL; non-`http(s)`
70
+ * schemes are always dropped. The filtering stays enforced after navigation (e.g. across redirects).
71
+ * Pass `'all'` to disable host filtering.
72
+ * @default EnqueueStrategy.SameHostname
73
+ */
74
+ enqueueStrategy?: EnqueueStrategy | `${EnqueueStrategy}`;
80
75
  /**
81
76
  * Advanced options for the underlying `parseSitemap` call.
82
77
  */
@@ -92,55 +87,12 @@ export interface SitemapRequestLoaderOptions extends UrlConstraints {
92
87
  * The loading of the sitemap is performed in the background so that crawling can start before the sitemap is fully loaded.
93
88
  */
94
89
  export declare class SitemapRequestLoader implements IRequestLoader {
90
+ #private;
95
91
  /**
96
92
  * Set of URLs that were returned by `fetchNextRequest()` and not marked as handled yet.
97
93
  * @internal
98
94
  */
99
95
  inProgress: Set<string>;
100
- /**
101
- * Map of returned Request objects that have not been marked as handled yet.
102
- *
103
- * We use this to persist custom user fields on the in-progress requests.
104
- */
105
- private requestData;
106
- /**
107
- * Object for keeping track of the sitemap parsing progress.
108
- */
109
- private sitemapParsingProgress;
110
- /**
111
- * Object stream of URLs parsed from the sitemaps.
112
- * Using `highWaterMark`, this can manage the speed of the sitemap loading.
113
- *
114
- * Fetch the next URL to be processed using `fetchNextRequest()`.
115
- */
116
- private urlQueueStream;
117
- /**
118
- * Indicates whether the request list sitemap loading was aborted.
119
- *
120
- * If the loading was aborted before the sitemaps were fully loaded, the request list might be missing some URLs.
121
- * The `isSitemapFullyLoaded` method can be used to check if the sitemaps were fully loaded.
122
- *
123
- * If the loading is aborted and all the requests are handled, `isFinished()` will return `true`.
124
- */
125
- private abortLoading;
126
- /** Number of URLs that were marked as handled */
127
- private handledUrlCount;
128
- private persistStateKey?;
129
- private store?;
130
- private closed;
131
- /**
132
- * Proxy URL to be used for sitemap loading.
133
- */
134
- private proxyUrl?;
135
- /**
136
- * Logger instance.
137
- */
138
- private log;
139
- private urlExcludePatternObjects;
140
- private urlPatternObjects;
141
- /** EventManager used to handle persistence */
142
- private events;
143
- private persistenceOptions;
144
96
  /** @internal */
145
97
  private constructor();
146
98
  /**
@@ -156,7 +108,7 @@ export declare class SitemapRequestLoader implements IRequestLoader {
156
108
  */
157
109
  private matchesUrl;
158
110
  /**
159
- * Checks whether the URL matches the `globs` / `regexps` / `exclude` provided in the `options`.
111
+ * Checks whether the URL matches the `include` / `exclude` patterns provided in the `options`.
160
112
  * @param url URL to be checked.
161
113
  * @returns `true` if the URL matches the patterns, `false` otherwise.
162
114
  */
@@ -1,12 +1,28 @@
1
1
  import { Transform } from 'node:stream';
2
- import { parseSitemap } from '@crawlee/utils';
2
+ import { EnqueueStrategy, parseSitemap } from '@crawlee/utils';
3
3
  import { minimatch } from 'minimatch';
4
- import ow from 'ow';
5
- import { constructGlobObjectsFromGlobs, constructRegExpObjectsFromRegExps } from '../enqueue_links/shared.js';
4
+ import { z } from 'zod';
5
+ import { constructUrlPatternObjects, urlPatternSchema } from '../enqueue_links/shared.js';
6
+ import { EventType } from '../events/event_manager.js';
6
7
  import { Request } from '../request.js';
7
8
  import { serviceLocator } from '../service_locator.js';
9
+ import { parseArgument, schemas } from '../validators.js';
8
10
  import { KeyValueStore } from './key_value_store.js';
9
11
  import { purgeDefaultStorages } from './utils.js';
12
+ const sitemapRequestLoaderOptionsSchema = z.strictObject({
13
+ sitemapUrls: schemas.arrayOf(z.string(), 'strings'),
14
+ proxyUrl: z.string().optional(),
15
+ persistStateKey: z.string().optional(),
16
+ signal: z.unknown().optional(),
17
+ timeoutMillis: schemas.anyNumber.optional(),
18
+ maxBufferSize: schemas.anyNumber.default(200),
19
+ enqueueStrategy: z.enum(EnqueueStrategy).default(EnqueueStrategy.SameHostname),
20
+ parseSitemapOptions: z.looseObject({}).optional(),
21
+ include: schemas.arrayOf(urlPatternSchema, 'URL patterns').optional(),
22
+ exclude: schemas.arrayOf(urlPatternSchema, 'URL patterns').optional(),
23
+ persistenceOptions: z.looseObject({}).optional(),
24
+ httpClient: schemas.httpClient.optional(),
25
+ });
10
26
  /** @internal */
11
27
  const STATE_PERSISTENCE_KEY = 'SITEMAP_REQUEST_LOADER_STATE';
12
28
  /**
@@ -25,11 +41,11 @@ export class SitemapRequestLoader {
25
41
  *
26
42
  * We use this to persist custom user fields on the in-progress requests.
27
43
  */
28
- requestData = new Map();
44
+ #requestData = new Map();
29
45
  /**
30
46
  * Object for keeping track of the sitemap parsing progress.
31
47
  */
32
- sitemapParsingProgress = {
48
+ #sitemapParsingProgress = {
33
49
  /**
34
50
  * URL of the sitemap that is currently being parsed. `null` if no sitemap is being parsed.
35
51
  */
@@ -49,7 +65,7 @@ export class SitemapRequestLoader {
49
65
  *
50
66
  * Fetch the next URL to be processed using `fetchNextRequest()`.
51
67
  */
52
- urlQueueStream;
68
+ #urlQueueStream;
53
69
  /**
54
70
  * Indicates whether the request list sitemap loading was aborted.
55
71
  *
@@ -58,64 +74,46 @@ export class SitemapRequestLoader {
58
74
  *
59
75
  * If the loading is aborted and all the requests are handled, `isFinished()` will return `true`.
60
76
  */
61
- abortLoading = false;
77
+ #abortLoading = false;
62
78
  /** Number of URLs that were marked as handled */
63
- handledUrlCount = 0;
64
- persistStateKey;
65
- store;
66
- closed = false;
79
+ #handledUrlCount = 0;
80
+ #persistStateKey;
81
+ #store;
82
+ #closed = false;
67
83
  /**
68
84
  * Proxy URL to be used for sitemap loading.
69
85
  */
70
- proxyUrl;
86
+ #proxyUrl;
87
+ /**
88
+ * Enqueue strategy applied to sitemap-derived URLs and stamped onto the emitted `Request` objects.
89
+ */
90
+ #enqueueStrategy;
71
91
  /**
72
92
  * Logger instance.
73
93
  */
74
- log;
75
- urlExcludePatternObjects = [];
76
- urlPatternObjects = [];
94
+ #log;
95
+ #urlExcludePatternObjects = [];
96
+ #urlPatternObjects = [];
77
97
  /** EventManager used to handle persistence */
78
- events;
79
- persistenceOptions;
98
+ #events;
99
+ #persistenceOptions;
80
100
  /** @internal */
81
101
  constructor(options) {
82
- ow(options, ow.object.exactShape({
83
- sitemapUrls: ow.array.ofType(ow.string),
84
- proxyUrl: ow.optional.string,
85
- persistStateKey: ow.optional.string,
86
- signal: ow.optional.any(),
87
- timeoutMillis: ow.optional.number,
88
- maxBufferSize: ow.optional.number,
89
- parseSitemapOptions: ow.optional.object,
90
- globs: ow.optional.array.ofType(ow.any(ow.string, ow.object.hasKeys('glob'))),
91
- exclude: ow.optional.array.ofType(ow.any(ow.string, ow.regExp, ow.object.hasKeys('glob'), ow.object.hasKeys('regexp'))),
92
- regexps: ow.optional.array.ofType(ow.any(ow.regExp, ow.object.hasKeys('regexp'))),
93
- persistenceOptions: ow.optional.object,
94
- }));
95
- const { globs, exclude, regexps } = options;
96
- this.log = serviceLocator.getLogger().child({ prefix: 'SitemapRequestLoader' });
102
+ const { include, exclude, persistStateKey, persistenceOptions, proxyUrl, maxBufferSize, sitemapUrls, enqueueStrategy, } = parseArgument(options, sitemapRequestLoaderOptionsSchema, 'SitemapRequestLoaderOptions');
103
+ this.#log = serviceLocator.getLogger().child({ prefix: 'SitemapRequestLoader' });
97
104
  if (exclude?.length) {
98
- for (const excl of exclude) {
99
- if (typeof excl === 'string' || 'glob' in excl) {
100
- this.urlExcludePatternObjects.push(...constructGlobObjectsFromGlobs([excl]));
101
- }
102
- else if (excl instanceof RegExp || 'regexp' in excl) {
103
- this.urlExcludePatternObjects.push(...constructRegExpObjectsFromRegExps([excl]));
104
- }
105
- }
106
- }
107
- if (globs?.length) {
108
- this.urlPatternObjects.push(...constructGlobObjectsFromGlobs(globs));
105
+ this.#urlExcludePatternObjects.push(...constructUrlPatternObjects(exclude));
109
106
  }
110
- if (regexps?.length) {
111
- this.urlPatternObjects.push(...constructRegExpObjectsFromRegExps(regexps));
107
+ if (include?.length) {
108
+ this.#urlPatternObjects.push(...constructUrlPatternObjects(include));
112
109
  }
113
- this.persistStateKey = options.persistStateKey;
114
- this.persistenceOptions = { enable: true, ...options.persistenceOptions };
115
- this.proxyUrl = options.proxyUrl;
116
- this.urlQueueStream = this.createNewStream(options.maxBufferSize ?? 200);
117
- this.sitemapParsingProgress.pendingSitemapUrls = new Set(options.sitemapUrls);
118
- this.events = serviceLocator.getEventManager();
110
+ this.#persistStateKey = persistStateKey;
111
+ this.#persistenceOptions = { enable: true, ...persistenceOptions };
112
+ this.#proxyUrl = proxyUrl;
113
+ this.#enqueueStrategy = enqueueStrategy;
114
+ this.#urlQueueStream = this.createNewStream(maxBufferSize);
115
+ this.#sitemapParsingProgress.pendingSitemapUrls = new Set(sitemapUrls);
116
+ this.#events = serviceLocator.getEventManager();
119
117
  this.persistState = this.persistState.bind(this);
120
118
  }
121
119
  /**
@@ -143,13 +141,13 @@ export class SitemapRequestLoader {
143
141
  };
144
142
  }
145
143
  /**
146
- * Checks whether the URL matches the `globs` / `regexps` / `exclude` provided in the `options`.
144
+ * Checks whether the URL matches the `include` / `exclude` patterns provided in the `options`.
147
145
  * @param url URL to be checked.
148
146
  * @returns `true` if the URL matches the patterns, `false` otherwise.
149
147
  */
150
148
  isUrlMatchingPatterns(url) {
151
- return (!this.urlExcludePatternObjects.some(this.matchesUrl(url)) &&
152
- (this.urlPatternObjects.length === 0 || this.urlPatternObjects.some(this.matchesUrl(url))));
149
+ return (!this.#urlExcludePatternObjects.some(this.matchesUrl(url)) &&
150
+ (this.#urlPatternObjects.length === 0 || this.#urlPatternObjects.some(this.matchesUrl(url))));
153
151
  }
154
152
  /**
155
153
  * Adds a URL to the queue of parsed URLs.
@@ -158,13 +156,13 @@ export class SitemapRequestLoader {
158
156
  */
159
157
  async pushNextUrl(url) {
160
158
  return new Promise((resolve) => {
161
- if (this.closed || (url && !this.isUrlMatchingPatterns(url))) {
159
+ if (this.#closed || (url && !this.isUrlMatchingPatterns(url))) {
162
160
  resolve();
163
161
  return;
164
162
  }
165
- if (!this.urlQueueStream.push(url)) {
163
+ if (!this.#urlQueueStream.push(url)) {
166
164
  // This doesn't work with the 'drain' event (it's not emitted for some reason).
167
- this.urlQueueStream.once('readdata', () => {
165
+ this.#urlQueueStream.once('readdata', () => {
168
166
  resolve();
169
167
  });
170
168
  }
@@ -181,21 +179,21 @@ export class SitemapRequestLoader {
181
179
  */
182
180
  async readNextUrl() {
183
181
  return new Promise((resolve) => {
184
- if (this.closed) {
182
+ if (this.#closed) {
185
183
  resolve(null);
186
184
  return;
187
185
  }
188
- const result = this.urlQueueStream.read();
186
+ const result = this.#urlQueueStream.read();
189
187
  if (!result && !this.isSitemapFullyLoaded()) {
190
- this.urlQueueStream.once('readable', () => {
191
- const nextUrl = this.urlQueueStream.read();
188
+ this.#urlQueueStream.once('readable', () => {
189
+ const nextUrl = this.#urlQueueStream.read();
192
190
  resolve(nextUrl);
193
191
  });
194
192
  }
195
193
  else {
196
194
  resolve(result);
197
195
  }
198
- this.urlQueueStream.emit('readdata');
196
+ this.#urlQueueStream.emit('readdata');
199
197
  });
200
198
  }
201
199
  /**
@@ -204,8 +202,8 @@ export class SitemapRequestLoader {
204
202
  * If this is `false`, the background processing is either still in progress or was aborted.
205
203
  */
206
204
  isSitemapFullyLoaded() {
207
- return (this.sitemapParsingProgress.inProgressSitemapUrl === null &&
208
- this.sitemapParsingProgress.pendingSitemapUrls.size === 0);
205
+ return (this.#sitemapParsingProgress.inProgressSitemapUrl === null &&
206
+ this.#sitemapParsingProgress.pendingSitemapUrls.size === 0);
209
207
  }
210
208
  /**
211
209
  * Start processing the sitemaps and loading the URLs.
@@ -213,34 +211,35 @@ export class SitemapRequestLoader {
213
211
  * Resolves once all the sitemaps URLs have been fully loaded (sets `isSitemapFullyLoaded` to `true`).
214
212
  */
215
213
  async load({ parseSitemapOptions, }) {
216
- while (!this.isSitemapFullyLoaded() && !this.abortLoading) {
217
- const sitemapUrl = this.sitemapParsingProgress.inProgressSitemapUrl ??
218
- this.sitemapParsingProgress.pendingSitemapUrls.values().next().value;
214
+ while (!this.isSitemapFullyLoaded() && !this.#abortLoading) {
215
+ const sitemapUrl = this.#sitemapParsingProgress.inProgressSitemapUrl ??
216
+ this.#sitemapParsingProgress.pendingSitemapUrls.values().next().value;
219
217
  try {
220
- for await (const item of parseSitemap([{ type: 'url', url: sitemapUrl }], this.proxyUrl, {
218
+ for await (const item of parseSitemap([{ type: 'url', url: sitemapUrl }], this.#proxyUrl, {
221
219
  ...parseSitemapOptions,
222
220
  maxDepth: 0,
223
221
  emitNestedSitemaps: true,
222
+ enqueueStrategy: this.#enqueueStrategy,
224
223
  })) {
225
224
  if (!item.originSitemapUrl) {
226
225
  // This is a nested sitemap
227
- this.sitemapParsingProgress.pendingSitemapUrls.add(item.loc);
226
+ this.#sitemapParsingProgress.pendingSitemapUrls.add(item.loc);
228
227
  continue;
229
228
  }
230
- if (!this.sitemapParsingProgress.inProgressEntries.has(item.loc)) {
229
+ if (!this.#sitemapParsingProgress.inProgressEntries.has(item.loc)) {
231
230
  await this.pushNextUrl(item.loc);
232
- this.sitemapParsingProgress.inProgressEntries.add(item.loc);
231
+ this.#sitemapParsingProgress.inProgressEntries.add(item.loc);
233
232
  }
234
233
  }
235
234
  }
236
235
  catch (e) {
237
- this.log.error('Error loading sitemap contents:', e);
236
+ this.#log.error('Error loading sitemap contents:', e);
238
237
  }
239
- this.sitemapParsingProgress.pendingSitemapUrls.delete(sitemapUrl);
240
- this.sitemapParsingProgress.inProgressEntries.clear();
241
- this.sitemapParsingProgress.inProgressSitemapUrl = null;
238
+ this.#sitemapParsingProgress.pendingSitemapUrls.delete(sitemapUrl);
239
+ this.#sitemapParsingProgress.inProgressEntries.clear();
240
+ this.#sitemapParsingProgress.inProgressSitemapUrl = null;
242
241
  }
243
- this.urlQueueStream.end();
242
+ this.#urlQueueStream.end();
244
243
  }
245
244
  /**
246
245
  * Open a sitemap and start processing it.
@@ -259,15 +258,15 @@ export class SitemapRequestLoader {
259
258
  void requestList.load({
260
259
  parseSitemapOptions: { logger: serviceLocator.getLogger(), ...options.parseSitemapOptions, httpClient },
261
260
  });
262
- if (requestList.persistenceOptions.enable) {
263
- requestList.events.on("persistState" /* EventType.PERSIST_STATE */, requestList.persistState);
261
+ if (requestList.#persistenceOptions.enable) {
262
+ requestList.#events.on(EventType.PERSIST_STATE, requestList.persistState);
264
263
  }
265
264
  options?.signal?.addEventListener('abort', () => {
266
- requestList.abortLoading = true;
265
+ requestList.#abortLoading = true;
267
266
  });
268
267
  if (options.timeoutMillis) {
269
268
  setTimeout(() => {
270
- requestList.abortLoading = true;
269
+ requestList.#abortLoading = true;
271
270
  }, options.timeoutMillis);
272
271
  }
273
272
  return requestList;
@@ -278,14 +277,14 @@ export class SitemapRequestLoader {
278
277
  async getTotalCount() {
279
278
  // Total known so far = not-yet-fetched (still buffered in the stream) + in-progress (fetched but not
280
279
  // yet handled) + already handled.
281
- return this.urlQueueStream.readableLength + this.inProgress.size + this.handledUrlCount;
280
+ return this.#urlQueueStream.readableLength + this.inProgress.size + this.#handledUrlCount;
282
281
  }
283
282
  /**
284
283
  * @inheritDoc
285
284
  */
286
285
  async getPendingCount() {
287
286
  // Pending = everything not yet handled = not-yet-fetched + in-progress.
288
- return this.urlQueueStream.readableLength + this.inProgress.size;
287
+ return this.#urlQueueStream.readableLength + this.inProgress.size;
289
288
  }
290
289
  /**
291
290
  * Combines this list with a request manager (a {@link RequestQueue} by default) into a
@@ -302,31 +301,31 @@ export class SitemapRequestLoader {
302
301
  * @inheritDoc
303
302
  */
304
303
  async isFinished() {
305
- return ((await this.isEmpty()) && this.inProgress.size === 0 && (this.isSitemapFullyLoaded() || this.abortLoading));
304
+ return ((await this.isEmpty()) && this.inProgress.size === 0 && (this.isSitemapFullyLoaded() || this.#abortLoading));
306
305
  }
307
306
  /**
308
307
  * @inheritDoc
309
308
  */
310
309
  async isEmpty() {
311
- return this.urlQueueStream.readableLength === 0;
310
+ return this.#urlQueueStream.readableLength === 0;
312
311
  }
313
312
  /**
314
313
  * @inheritDoc
315
314
  */
316
315
  async getHandledCount() {
317
- return this.handledUrlCount;
316
+ return this.#handledUrlCount;
318
317
  }
319
318
  /**
320
319
  * @inheritDoc
321
320
  */
322
321
  async persistState() {
323
- if (this.persistStateKey === undefined) {
322
+ if (this.#persistStateKey === undefined) {
324
323
  return;
325
324
  }
326
- this.store ??= await KeyValueStore.open();
325
+ this.#store ??= await KeyValueStore.open();
327
326
  const urlQueue = [];
328
- while (this.urlQueueStream.readableLength > 0) {
329
- const url = this.urlQueueStream.read();
327
+ while (this.#urlQueueStream.readableLength > 0) {
328
+ const url = this.#urlQueueStream.read();
330
329
  if (url === null) {
331
330
  break;
332
331
  }
@@ -334,48 +333,55 @@ export class SitemapRequestLoader {
334
333
  }
335
334
  // Create a new stream, as we have read all the URLs from the current one.
336
335
  // Pushing the urls back to the original stream might not be possible if it has been ended.
337
- const newStream = this.createNewStream(this.urlQueueStream.readableHighWaterMark);
336
+ const previousStream = this.#urlQueueStream;
337
+ const newStream = this.createNewStream(previousStream.readableHighWaterMark);
338
338
  for (const url of urlQueue) {
339
339
  newStream.push(url);
340
340
  }
341
- if (this.urlQueueStream.writableEnded) {
341
+ if (previousStream.writableEnded) {
342
342
  newStream.end();
343
343
  }
344
- this.urlQueueStream = newStream;
345
- await this.store.setValue(this.persistStateKey, {
344
+ this.#urlQueueStream = newStream;
345
+ // A `pushNextUrl()` call may be blocked on backpressure, waiting for a `readdata` event on the
346
+ // previous stream. That event is only ever emitted by `readNextUrl()` on the current stream, so
347
+ // after the swap the waiter would never be notified and the background sitemap loading would hang.
348
+ // Re-emit `readdata` on the previous stream to release any such pending waiter (its URL has already
349
+ // been transferred to the new stream above).
350
+ previousStream.emit('readdata');
351
+ await this.#store.setValue(this.#persistStateKey, {
346
352
  sitemapParsingProgress: {
347
- pendingSitemapUrls: Array.from(this.sitemapParsingProgress.pendingSitemapUrls),
348
- inProgressSitemapUrl: this.sitemapParsingProgress.inProgressSitemapUrl,
349
- inProgressEntries: Array.from(this.sitemapParsingProgress.inProgressEntries),
353
+ pendingSitemapUrls: Array.from(this.#sitemapParsingProgress.pendingSitemapUrls),
354
+ inProgressSitemapUrl: this.#sitemapParsingProgress.inProgressSitemapUrl,
355
+ inProgressEntries: Array.from(this.#sitemapParsingProgress.inProgressEntries),
350
356
  },
351
357
  // Re-queue in-progress requests to the front so they are retried if the state is restored.
352
358
  urlQueue: [...this.inProgress, ...urlQueue],
353
- requestData: Array.from(this.requestData.entries()),
354
- abortLoading: this.abortLoading,
355
- closed: this.closed,
359
+ requestData: Array.from(this.#requestData.entries()),
360
+ abortLoading: this.#abortLoading,
361
+ closed: this.#closed,
356
362
  });
357
363
  }
358
364
  async restoreState() {
359
365
  await purgeDefaultStorages({ onlyPurgeOnce: true });
360
- if (this.persistStateKey === undefined) {
366
+ if (this.#persistStateKey === undefined) {
361
367
  return;
362
368
  }
363
- this.store ??= await KeyValueStore.open();
364
- const state = await this.store.getValue(this.persistStateKey);
369
+ this.#store ??= await KeyValueStore.open();
370
+ const state = await this.#store.getValue(this.#persistStateKey);
365
371
  if (state === null) {
366
372
  return;
367
373
  }
368
- this.sitemapParsingProgress = {
374
+ this.#sitemapParsingProgress = {
369
375
  pendingSitemapUrls: new Set(state.sitemapParsingProgress.pendingSitemapUrls),
370
376
  inProgressSitemapUrl: state.sitemapParsingProgress.inProgressSitemapUrl,
371
377
  inProgressEntries: new Set(state.sitemapParsingProgress.inProgressEntries),
372
378
  };
373
- this.requestData = new Map(state.requestData ?? []);
379
+ this.#requestData = new Map(state.requestData ?? []);
374
380
  for (const url of state.urlQueue) {
375
- this.urlQueueStream.push(url);
381
+ this.#urlQueueStream.push(url);
376
382
  }
377
- this.abortLoading = state.abortLoading;
378
- this.closed = state.closed;
383
+ this.#abortLoading = state.abortLoading;
384
+ this.#closed = state.closed;
379
385
  }
380
386
  /**
381
387
  * @inheritDoc
@@ -386,11 +392,11 @@ export class SitemapRequestLoader {
386
392
  return null;
387
393
  }
388
394
  // A restored in-progress request already has its Request data; don't overwrite it.
389
- if (!this.requestData.has(nextUrl)) {
390
- this.requestData.set(nextUrl, new Request({ url: nextUrl }));
395
+ if (!this.#requestData.has(nextUrl)) {
396
+ this.#requestData.set(nextUrl, new Request({ url: nextUrl, enqueueStrategy: this.#enqueueStrategy }));
391
397
  }
392
398
  this.inProgress.add(nextUrl);
393
- return this.requestData.get(nextUrl);
399
+ return this.#requestData.get(nextUrl);
394
400
  }
395
401
  /**
396
402
  * @inheritDoc
@@ -409,20 +415,20 @@ export class SitemapRequestLoader {
409
415
  * Calling `fetchNextRequest()` after this method will always return `null`.
410
416
  */
411
417
  async teardown() {
412
- this.closed = true;
413
- this.abortLoading = true;
414
- this.events.off("persistState" /* EventType.PERSIST_STATE */, this.persistState);
418
+ this.#closed = true;
419
+ this.#abortLoading = true;
420
+ this.#events.off(EventType.PERSIST_STATE, this.persistState);
415
421
  await this.persistState();
416
- this.urlQueueStream.emit('readdata'); // unblocks the potentially waiting `pushNextUrl` call
422
+ this.#urlQueueStream.emit('readdata'); // unblocks the potentially waiting `pushNextUrl` call
417
423
  }
418
424
  /**
419
425
  * @inheritDoc
420
426
  */
421
427
  async markRequestAsHandled(request) {
422
- this.handledUrlCount += 1;
428
+ this.#handledUrlCount += 1;
423
429
  this.ensureInProgress(request.url);
424
430
  this.inProgress.delete(request.url);
425
- this.requestData.delete(request.url);
431
+ this.#requestData.delete(request.url);
426
432
  }
427
433
  ensureInProgress(url) {
428
434
  if (!this.inProgress.has(url)) {
@@ -19,8 +19,7 @@ type Hashable = string;
19
19
  * assigns a reserved default alias.
20
20
  */
21
21
  export declare class StorageInstanceManager {
22
- private readonly cache;
23
- private readonly openerLocks;
22
+ #private;
24
23
  /**
25
24
  * Open (or retrieve from cache) a storage instance.
26
25
  *