@crawlee/core 4.0.0-beta.133 → 4.0.0-beta.135

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.
@@ -2,7 +2,6 @@ import type { Awaitable, Dictionary } from '@crawlee/types';
2
2
  import { z } from 'zod';
3
3
  import type { RequestOptions } from '../request.js';
4
4
  import type { EnqueueStrategyOption } from './enqueue_links.js';
5
- export { tryAbsoluteURL } from '@crawlee/utils/internal';
6
5
  export interface UrlPatternObject {
7
6
  glob?: string;
8
7
  regexp?: RegExp;
@@ -2,7 +2,6 @@ import { URL } from 'node:url';
2
2
  import { Minimatch } from 'minimatch';
3
3
  import { z } from 'zod';
4
4
  import { schemas } from '../validators.js';
5
- export { tryAbsoluteURL } from '@crawlee/utils/internal';
6
5
  const MAX_ENQUEUE_LINKS_CACHE_SIZE = 1000;
7
6
  /**
8
7
  * To keep high performance when the same patterns are passed on every `enqueueLinks()` call,
package/index.d.ts CHANGED
@@ -15,7 +15,7 @@ export * from './serialization.js';
15
15
  export * from './session_pool/index.js';
16
16
  export * from './storages/index.js';
17
17
  export * from './memory-storage/index.js';
18
- export * from './validators.js';
18
+ export { ArgumentValidationError, validators } from './validators.js';
19
19
  export * from './cookie_utils.js';
20
20
  export * from './http.js';
21
21
  export * from './recoverable_state.js';
package/index.js CHANGED
@@ -15,7 +15,9 @@ export * from './serialization.js';
15
15
  export * from './session_pool/index.js';
16
16
  export * from './storages/index.js';
17
17
  export * from './memory-storage/index.js';
18
- export * from './validators.js';
18
+ // Not `export *`: the rest of the module re-exports `@crawlee/utils/internal` symbols, which carry no
19
+ // semver guarantees and must not reach the public surface. Internal consumers import them directly.
20
+ export { ArgumentValidationError, validators } from './validators.js';
19
21
  export * from './cookie_utils.js';
20
22
  export * from './http.js';
21
23
  export * from './recoverable_state.js';
@@ -1,7 +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
3
  import { RequestQueueBackend } from './resource-clients/request-queue.js';
6
4
  export interface MemoryStorageOptions {
7
5
  /**
@@ -12,15 +10,23 @@ export interface MemoryStorageOptions {
12
10
  export declare class MemoryStorageBackend implements storage.StorageBackend {
13
11
  #private;
14
12
  readonly logger?: CrawleeLogger;
15
- readonly keyValueStoreBackendCache: KeyValueStoreBackend[];
16
- readonly datasetBackendCache: DatasetBackend[];
17
- readonly requestQueueBackendCache: RequestQueueBackend[];
18
13
  constructor(options?: MemoryStorageOptions);
19
14
  /**
20
15
  * Return a per-instance unique cache key so that distinct `MemoryStorageBackend` instances get separate
21
16
  * cache partitions in the storage backend cache.
22
17
  */
23
18
  getStorageBackendCacheKey(): string;
19
+ /**
20
+ * Evict a cached backend so that a dropped storage is no longer resolved by `createXBackend`,
21
+ * reported by `storageExists` or visited by `purge`. Returns whether the backend was cached, which
22
+ * tells the caller whether it still owns in-memory state worth clearing.
23
+ *
24
+ * The resource clients own their own entry's lifetime but must not reach into the caches directly.
25
+ * Because a client is only ever constructed by `createXBackend`, which caches it immediately, the
26
+ * entry matching `id` is always the caller itself.
27
+ * @internal
28
+ */
29
+ evictBackend(type: 'Dataset' | 'KeyValueStore' | 'RequestQueue', id: string): boolean;
24
30
  createDatasetBackend(options?: storage.StorageIdentifier): Promise<storage.DatasetBackend>;
25
31
  createKeyValueStoreBackend(options?: storage.StorageIdentifier): Promise<storage.KeyValueStoreBackend>;
26
32
  createRequestQueueBackend(options?: storage.StorageIdentifier): Promise<RequestQueueBackend>;
@@ -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.datasetBackendCache.find((store) => store.id === cacheKey ||
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.datasetBackendCache.push(newStore);
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.keyValueStoreBackendCache.find((store) => store.id === cacheKey ||
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.keyValueStoreBackendCache.push(newStore);
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.requestQueueBackendCache.find((queue) => queue.id === cacheKey ||
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.requestQueueBackendCache.push(newStore);
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.datasetBackendCache;
120
+ backends = this.#datasetBackendCache;
91
121
  break;
92
122
  case 'KeyValueStore':
93
- backends = this.keyValueStoreBackendCache;
123
+ backends = this.#keyValueStoreBackendCache;
94
124
  break;
95
125
  case 'RequestQueue':
96
- backends = this.requestQueueBackendCache;
126
+ backends = this.#requestQueueBackendCache;
97
127
  break;
98
128
  default:
99
129
  return false;
@@ -115,9 +145,9 @@ export class MemoryStorageBackend {
115
145
  };
116
146
  await Promise.all([
117
147
  // Only the default store holds the run input, so it is the only one that keeps `INPUT`.
118
- purgeRunScoped(this.keyValueStoreBackendCache, async (store) => isDefault(store) ? store.purgeExceptInput() : store.purge()),
119
- purgeRunScoped(this.datasetBackendCache, async (store) => store.purge()),
120
- purgeRunScoped(this.requestQueueBackendCache, async (store) => store.purge()),
148
+ purgeRunScoped(this.#keyValueStoreBackendCache, async (store) => isDefault(store) ? store.purgeExceptInput() : store.purge()),
149
+ purgeRunScoped(this.#datasetBackendCache, async (store) => store.purge()),
150
+ purgeRunScoped(this.#requestQueueBackendCache, async (store) => store.purge()),
121
151
  ]);
122
152
  }
123
153
  /**
@@ -36,11 +36,9 @@ export class DatasetBackend extends BaseClient {
36
36
  return this.toDatasetInfo();
37
37
  }
38
38
  async drop() {
39
- const storeIndex = this.storageBackend.datasetBackendCache.findIndex((store) => store.id === this.id);
40
- if (storeIndex !== -1) {
41
- const [oldBackend] = this.storageBackend.datasetBackendCache.splice(storeIndex, 1);
42
- oldBackend.itemCount = 0;
43
- oldBackend.#datasetEntries.clear();
39
+ if (this.storageBackend.evictBackend('Dataset', this.id)) {
40
+ this.itemCount = 0;
41
+ this.#datasetEntries.clear();
44
42
  }
45
43
  }
46
44
  async purge() {
@@ -49,10 +49,8 @@ export class KeyValueStoreBackend extends BaseClient {
49
49
  return this.toKeyValueStoreInfo();
50
50
  }
51
51
  async drop() {
52
- const storeIndex = this.storageBackend.keyValueStoreBackendCache.findIndex((store) => store.id === this.id);
53
- if (storeIndex !== -1) {
54
- const [oldBackend] = this.storageBackend.keyValueStoreBackendCache.splice(storeIndex, 1);
55
- oldBackend.#keyValueEntries.clear();
52
+ if (this.storageBackend.evictBackend('KeyValueStore', this.id)) {
53
+ this.#keyValueEntries.clear();
56
54
  }
57
55
  }
58
56
  async purge() {
@@ -55,16 +55,14 @@ export class RequestQueueBackend extends BaseClient {
55
55
  // removed, which `listPendingHead` would then dereference as `undefined`.
56
56
  await this.#queueStateMutex.wait();
57
57
  try {
58
- const storeIndex = this.storageBackend.requestQueueBackendCache.findIndex((queue) => queue.id === this.id);
59
- if (storeIndex !== -1) {
60
- const [oldBackend] = this.storageBackend.requestQueueBackendCache.splice(storeIndex, 1);
61
- oldBackend.pendingRequestCount = 0;
58
+ if (this.storageBackend.evictBackend('RequestQueue', this.id)) {
59
+ this.pendingRequestCount = 0;
62
60
  // Clear all in-memory state, consistent with `purge`. Clearing `requests` alone would
63
61
  // leave dangling ids in `forefrontRequestIds`/`inProgressRequestIds`, which a later head
64
62
  // scan would resolve to a missing request and dereference.
65
- oldBackend.#requests.clear();
66
- oldBackend.#forefrontRequestIds = [];
67
- oldBackend.#inProgressRequestIds.clear();
63
+ this.#requests.clear();
64
+ this.#forefrontRequestIds = [];
65
+ this.#inProgressRequestIds.clear();
68
66
  }
69
67
  }
70
68
  finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/core",
3
- "version": "4.0.0-beta.133",
3
+ "version": "4.0.0-beta.135",
4
4
  "description": "The scalable web crawling and scraping library for JavaScript/Node.js. Enables development of data extraction and web automation jobs (not only) with headless Chrome and Puppeteer.",
5
5
  "engines": {
6
6
  "node": ">=22.0.0"
@@ -52,10 +52,10 @@
52
52
  "@apify/log": "^2.5.18",
53
53
  "@apify/timeout": "^0.4.4",
54
54
  "@apify/utilities": "^2.15.5",
55
- "@crawlee/fs-storage": "4.0.0-beta.133",
56
- "@crawlee/http-client": "4.0.0-beta.133",
57
- "@crawlee/types": "4.0.0-beta.133",
58
- "@crawlee/utils": "4.0.0-beta.133",
55
+ "@crawlee/fs-storage": "4.0.0-beta.135",
56
+ "@crawlee/http-client": "4.0.0-beta.135",
57
+ "@crawlee/types": "4.0.0-beta.135",
58
+ "@crawlee/utils": "4.0.0-beta.135",
59
59
  "@sapphire/async-queue": "^1.5.5",
60
60
  "@standard-schema/spec": "^1.0.0",
61
61
  "@vladfrangu/async_event_emitter": "^2.4.6",
@@ -78,5 +78,5 @@
78
78
  }
79
79
  }
80
80
  },
81
- "gitHead": "ace617a523c893bf2eb1ac113412205fc05b2964"
81
+ "gitHead": "ca0325880bbcc753d97506796e9bda3b35bca201"
82
82
  }