@crawlee/core 4.0.0-beta.18 → 4.0.0-beta.180

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 (270) hide show
  1. package/README.md +14 -14
  2. package/byte_utils.d.ts +17 -0
  3. package/byte_utils.js +42 -0
  4. package/configuration.d.ts +90 -226
  5. package/configuration.js +164 -225
  6. package/debug.d.ts +36 -0
  7. package/debug.js +70 -0
  8. package/errors.d.ts +40 -19
  9. package/errors.js +53 -28
  10. package/events/event_manager.d.ts +35 -9
  11. package/events/event_manager.js +12 -13
  12. package/events/index.d.ts +1 -1
  13. package/events/index.js +0 -1
  14. package/events/local_event_manager.d.ts +15 -3
  15. package/events/local_event_manager.js +39 -13
  16. package/events/system_info.d.ts +38 -0
  17. package/events/system_info.js +1 -0
  18. package/index.d.ts +7 -11
  19. package/index.js +8 -10
  20. package/internal.d.ts +8 -0
  21. package/internal.js +9 -0
  22. package/iterables.d.ts +79 -0
  23. package/iterables.js +134 -0
  24. package/log.d.ts +77 -1
  25. package/log.js +134 -1
  26. package/memory-storage/consts.d.ts +4 -0
  27. package/memory-storage/consts.js +4 -0
  28. package/memory-storage/index.d.ts +1 -0
  29. package/memory-storage/index.js +1 -0
  30. package/memory-storage/memory-storage.d.ts +44 -0
  31. package/memory-storage/memory-storage.js +160 -0
  32. package/memory-storage/resource-clients/common/base-client.d.ts +4 -0
  33. package/memory-storage/resource-clients/common/base-client.js +6 -0
  34. package/memory-storage/resource-clients/dataset.d.ts +40 -0
  35. package/memory-storage/resource-clients/dataset.js +106 -0
  36. package/memory-storage/resource-clients/key-value-store.d.ts +63 -0
  37. package/memory-storage/resource-clients/key-value-store.js +199 -0
  38. package/memory-storage/resource-clients/request-queue.d.ts +77 -0
  39. package/memory-storage/resource-clients/request-queue.js +414 -0
  40. package/memory-storage/utils.d.ts +16 -0
  41. package/memory-storage/utils.js +41 -0
  42. package/owned_or_injected.d.ts +58 -0
  43. package/owned_or_injected.js +98 -0
  44. package/package.json +18 -19
  45. package/proxy_configuration.d.ts +22 -128
  46. package/proxy_configuration.js +32 -144
  47. package/recoverable_state.d.ts +85 -51
  48. package/recoverable_state.js +178 -72
  49. package/request.d.ts +60 -17
  50. package/request.js +130 -69
  51. package/serialization.d.ts +0 -1
  52. package/serialization.js +15 -15
  53. package/service_locator.d.ts +165 -0
  54. package/service_locator.js +254 -0
  55. package/storages/batched_adds.d.ts +37 -0
  56. package/storages/batched_adds.js +73 -0
  57. package/storages/dataset.d.ts +114 -56
  58. package/storages/dataset.js +292 -149
  59. package/storages/index.d.ts +7 -9
  60. package/storages/index.js +5 -9
  61. package/storages/key_value_store.d.ts +187 -50
  62. package/storages/key_value_store.js +453 -172
  63. package/storages/key_value_store_codec.d.ts +32 -0
  64. package/storages/key_value_store_codec.js +113 -0
  65. package/storages/request_dedup_cache.d.ts +22 -0
  66. package/storages/request_dedup_cache.js +48 -0
  67. package/storages/request_list.d.ts +54 -129
  68. package/storages/request_list.js +272 -245
  69. package/storages/request_loader.d.ts +127 -0
  70. package/storages/request_loader.js +36 -0
  71. package/storages/request_manager.d.ts +109 -0
  72. package/storages/request_manager.js +1 -0
  73. package/storages/request_manager_tandem.d.ts +50 -23
  74. package/storages/request_manager_tandem.js +132 -57
  75. package/storages/request_queue.d.ts +286 -48
  76. package/storages/request_queue.js +757 -218
  77. package/storages/storage_instance_manager.d.ts +87 -0
  78. package/storages/storage_instance_manager.js +256 -0
  79. package/storages/storage_stats.d.ts +48 -0
  80. package/storages/storage_stats.js +29 -0
  81. package/storages/transaction.d.ts +260 -0
  82. package/storages/transaction.js +287 -0
  83. package/storages/utils.d.ts +59 -11
  84. package/storages/utils.js +75 -15
  85. package/system-info/cpu-info.d.ts +67 -0
  86. package/system-info/cpu-info.js +216 -0
  87. package/system-info/memory-info.d.ts +31 -0
  88. package/system-info/memory-info.js +115 -0
  89. package/system-info/ps-tree.d.ts +17 -0
  90. package/system-info/ps-tree.js +144 -0
  91. package/system-info/runtime.d.ts +14 -0
  92. package/system-info/runtime.js +80 -0
  93. package/typedefs.d.ts +0 -6
  94. package/typedefs.js +0 -1
  95. package/url.d.ts +9 -0
  96. package/url.js +11 -0
  97. package/validators.d.ts +23 -18
  98. package/validators.js +14 -18
  99. package/autoscaling/autoscaled_pool.d.ts +0 -300
  100. package/autoscaling/autoscaled_pool.d.ts.map +0 -1
  101. package/autoscaling/autoscaled_pool.js +0 -533
  102. package/autoscaling/autoscaled_pool.js.map +0 -1
  103. package/autoscaling/index.d.ts +0 -4
  104. package/autoscaling/index.d.ts.map +0 -1
  105. package/autoscaling/index.js +0 -4
  106. package/autoscaling/index.js.map +0 -1
  107. package/autoscaling/snapshotter.d.ts +0 -189
  108. package/autoscaling/snapshotter.d.ts.map +0 -1
  109. package/autoscaling/snapshotter.js +0 -285
  110. package/autoscaling/snapshotter.js.map +0 -1
  111. package/autoscaling/system_status.d.ts +0 -181
  112. package/autoscaling/system_status.d.ts.map +0 -1
  113. package/autoscaling/system_status.js +0 -169
  114. package/autoscaling/system_status.js.map +0 -1
  115. package/configuration.d.ts.map +0 -1
  116. package/configuration.js.map +0 -1
  117. package/cookie_utils.d.ts +0 -43
  118. package/cookie_utils.d.ts.map +0 -1
  119. package/cookie_utils.js +0 -111
  120. package/cookie_utils.js.map +0 -1
  121. package/crawlers/context_pipeline.d.ts +0 -61
  122. package/crawlers/context_pipeline.d.ts.map +0 -1
  123. package/crawlers/context_pipeline.js +0 -99
  124. package/crawlers/context_pipeline.js.map +0 -1
  125. package/crawlers/crawler_commons.d.ts +0 -195
  126. package/crawlers/crawler_commons.d.ts.map +0 -1
  127. package/crawlers/crawler_commons.js +0 -108
  128. package/crawlers/crawler_commons.js.map +0 -1
  129. package/crawlers/crawler_utils.d.ts +0 -10
  130. package/crawlers/crawler_utils.d.ts.map +0 -1
  131. package/crawlers/crawler_utils.js +0 -12
  132. package/crawlers/crawler_utils.js.map +0 -1
  133. package/crawlers/error_snapshotter.d.ts +0 -60
  134. package/crawlers/error_snapshotter.d.ts.map +0 -1
  135. package/crawlers/error_snapshotter.js +0 -118
  136. package/crawlers/error_snapshotter.js.map +0 -1
  137. package/crawlers/error_tracker.d.ts +0 -55
  138. package/crawlers/error_tracker.d.ts.map +0 -1
  139. package/crawlers/error_tracker.js +0 -309
  140. package/crawlers/error_tracker.js.map +0 -1
  141. package/crawlers/index.d.ts +0 -7
  142. package/crawlers/index.d.ts.map +0 -1
  143. package/crawlers/index.js +0 -7
  144. package/crawlers/index.js.map +0 -1
  145. package/crawlers/internals/types.d.ts +0 -8
  146. package/crawlers/internals/types.d.ts.map +0 -1
  147. package/crawlers/internals/types.js +0 -2
  148. package/crawlers/internals/types.js.map +0 -1
  149. package/crawlers/statistics.d.ts +0 -204
  150. package/crawlers/statistics.d.ts.map +0 -1
  151. package/crawlers/statistics.js +0 -346
  152. package/crawlers/statistics.js.map +0 -1
  153. package/enqueue_links/enqueue_links.d.ts +0 -249
  154. package/enqueue_links/enqueue_links.d.ts.map +0 -1
  155. package/enqueue_links/enqueue_links.js +0 -269
  156. package/enqueue_links/enqueue_links.js.map +0 -1
  157. package/enqueue_links/index.d.ts +0 -3
  158. package/enqueue_links/index.d.ts.map +0 -1
  159. package/enqueue_links/index.js +0 -3
  160. package/enqueue_links/index.js.map +0 -1
  161. package/enqueue_links/shared.d.ts +0 -73
  162. package/enqueue_links/shared.d.ts.map +0 -1
  163. package/enqueue_links/shared.js +0 -212
  164. package/enqueue_links/shared.js.map +0 -1
  165. package/errors.d.ts.map +0 -1
  166. package/errors.js.map +0 -1
  167. package/events/event_manager.d.ts.map +0 -1
  168. package/events/event_manager.js.map +0 -1
  169. package/events/index.d.ts.map +0 -1
  170. package/events/index.js.map +0 -1
  171. package/events/local_event_manager.d.ts.map +0 -1
  172. package/events/local_event_manager.js.map +0 -1
  173. package/http_clients/base-http-client.d.ts +0 -91
  174. package/http_clients/base-http-client.d.ts.map +0 -1
  175. package/http_clients/base-http-client.js +0 -44
  176. package/http_clients/base-http-client.js.map +0 -1
  177. package/http_clients/form-data-like.d.ts +0 -67
  178. package/http_clients/form-data-like.d.ts.map +0 -1
  179. package/http_clients/form-data-like.js +0 -5
  180. package/http_clients/form-data-like.js.map +0 -1
  181. package/http_clients/got-scraping-http-client.d.ts +0 -20
  182. package/http_clients/got-scraping-http-client.d.ts.map +0 -1
  183. package/http_clients/got-scraping-http-client.js +0 -102
  184. package/http_clients/got-scraping-http-client.js.map +0 -1
  185. package/http_clients/index.d.ts +0 -3
  186. package/http_clients/index.d.ts.map +0 -1
  187. package/http_clients/index.js +0 -3
  188. package/http_clients/index.js.map +0 -1
  189. package/index.d.ts.map +0 -1
  190. package/index.js.map +0 -1
  191. package/log.d.ts.map +0 -1
  192. package/log.js.map +0 -1
  193. package/proxy_configuration.d.ts.map +0 -1
  194. package/proxy_configuration.js.map +0 -1
  195. package/recoverable_state.d.ts.map +0 -1
  196. package/recoverable_state.js.map +0 -1
  197. package/request.d.ts.map +0 -1
  198. package/request.js.map +0 -1
  199. package/router.d.ts +0 -134
  200. package/router.d.ts.map +0 -1
  201. package/router.js +0 -164
  202. package/router.js.map +0 -1
  203. package/serialization.d.ts.map +0 -1
  204. package/serialization.js.map +0 -1
  205. package/session_pool/consts.d.ts +0 -4
  206. package/session_pool/consts.d.ts.map +0 -1
  207. package/session_pool/consts.js +0 -4
  208. package/session_pool/consts.js.map +0 -1
  209. package/session_pool/errors.d.ts +0 -8
  210. package/session_pool/errors.d.ts.map +0 -1
  211. package/session_pool/errors.js +0 -12
  212. package/session_pool/errors.js.map +0 -1
  213. package/session_pool/events.d.ts +0 -3
  214. package/session_pool/events.d.ts.map +0 -1
  215. package/session_pool/events.js +0 -3
  216. package/session_pool/events.js.map +0 -1
  217. package/session_pool/index.d.ts +0 -6
  218. package/session_pool/index.d.ts.map +0 -1
  219. package/session_pool/index.js +0 -6
  220. package/session_pool/index.js.map +0 -1
  221. package/session_pool/session.d.ts +0 -203
  222. package/session_pool/session.d.ts.map +0 -1
  223. package/session_pool/session.js +0 -278
  224. package/session_pool/session.js.map +0 -1
  225. package/session_pool/session_pool.d.ts +0 -258
  226. package/session_pool/session_pool.d.ts.map +0 -1
  227. package/session_pool/session_pool.js +0 -387
  228. package/session_pool/session_pool.js.map +0 -1
  229. package/storages/access_checking.d.ts +0 -13
  230. package/storages/access_checking.d.ts.map +0 -1
  231. package/storages/access_checking.js +0 -14
  232. package/storages/access_checking.js.map +0 -1
  233. package/storages/dataset.d.ts.map +0 -1
  234. package/storages/dataset.js.map +0 -1
  235. package/storages/index.d.ts.map +0 -1
  236. package/storages/index.js.map +0 -1
  237. package/storages/key_value_store.d.ts.map +0 -1
  238. package/storages/key_value_store.js.map +0 -1
  239. package/storages/request_list.d.ts.map +0 -1
  240. package/storages/request_list.js.map +0 -1
  241. package/storages/request_list_adapter.d.ts +0 -58
  242. package/storages/request_list_adapter.d.ts.map +0 -1
  243. package/storages/request_list_adapter.js +0 -81
  244. package/storages/request_list_adapter.js.map +0 -1
  245. package/storages/request_manager_tandem.d.ts.map +0 -1
  246. package/storages/request_manager_tandem.js.map +0 -1
  247. package/storages/request_provider.d.ts +0 -371
  248. package/storages/request_provider.d.ts.map +0 -1
  249. package/storages/request_provider.js +0 -585
  250. package/storages/request_provider.js.map +0 -1
  251. package/storages/request_queue.d.ts.map +0 -1
  252. package/storages/request_queue.js.map +0 -1
  253. package/storages/request_queue_v2.d.ts +0 -87
  254. package/storages/request_queue_v2.d.ts.map +0 -1
  255. package/storages/request_queue_v2.js +0 -438
  256. package/storages/request_queue_v2.js.map +0 -1
  257. package/storages/sitemap_request_list.d.ts +0 -245
  258. package/storages/sitemap_request_list.d.ts.map +0 -1
  259. package/storages/sitemap_request_list.js +0 -430
  260. package/storages/sitemap_request_list.js.map +0 -1
  261. package/storages/storage_manager.d.ts +0 -58
  262. package/storages/storage_manager.d.ts.map +0 -1
  263. package/storages/storage_manager.js +0 -105
  264. package/storages/storage_manager.js.map +0 -1
  265. package/storages/utils.d.ts.map +0 -1
  266. package/storages/utils.js.map +0 -1
  267. package/typedefs.d.ts.map +0 -1
  268. package/typedefs.js.map +0 -1
  269. package/validators.d.ts.map +0 -1
  270. package/validators.js.map +0 -1
@@ -0,0 +1,87 @@
1
+ import type { Constructor, DatasetBackend, KeyValueStoreBackend, RequestQueueBackend, StorageBackend, StorageIdentifier } from '@crawlee/types';
2
+ export type { StorageIdentifier } from '@crawlee/types';
3
+ /**
4
+ * Matches an `IStorage` – a storage "frontend" (Dataset, KeyValueStore, RequestQueue).
5
+ */
6
+ export interface IStorage {
7
+ id: string;
8
+ name?: string;
9
+ }
10
+ type Hashable = string;
11
+ /**
12
+ * Unified manager for opening and caching storage instances (Dataset, KeyValueStore, RequestQueue).
13
+ *
14
+ * A single instance manages all storage types. Instances are cached by
15
+ * `(storageClass, id/name/alias, backendCacheKey)` so the same storage is never opened twice.
16
+ *
17
+ * The manager itself does not resolve identifiers — callers pass explicit `id`, `name`, or `alias` (at most one),
18
+ * and a pre-bound `backendOpener` promise. When none of `id`, `name`, `alias` are provided, the manager automatically
19
+ * assigns a reserved default alias.
20
+ */
21
+ export declare class StorageInstanceManager {
22
+ #private;
23
+ /**
24
+ * Open (or retrieve from cache) a storage instance.
25
+ *
26
+ * @param cls The storage class constructor (e.g. `Dataset`, `KeyValueStore`, `RequestQueue`).
27
+ * @param id Storage ID (mutually exclusive with `name` and `alias`).
28
+ * @param name Storage name (mutually exclusive with `id` and `alias`).
29
+ * @param alias Run-scoped alias (mutually exclusive with `id` and `name`).
30
+ * Automatically assigned when no identifier is provided.
31
+ * @param backendOpener A **lazy** factory that creates the sub-backend.
32
+ * Only called on a cache miss.
33
+ * @param backendCacheKey Opaque key identifying the storage backend, so that the same logical
34
+ * storage opened through different clients is cached separately.
35
+ */
36
+ openStorage<TStorage extends IStorage>(cls: Constructor<TStorage>, { id, name, alias, backendOpener, backendCacheKey, }: (ExplicitStorageIdentifier | DefaultStorageIdentifier) & {
37
+ backendOpener: () => Promise<DatasetBackend | KeyValueStoreBackend | RequestQueueBackend>;
38
+ backendCacheKey: Hashable;
39
+ }): Promise<TStorage>;
40
+ /**
41
+ * Remove a storage instance from the cache (called from `storage.drop()`).
42
+ */
43
+ removeFromCache(instance: IStorage): void;
44
+ /**
45
+ * Clear the entire cache. Also calls `clearCache()` on any cached KeyValueStore
46
+ * instances (duck-typed to avoid importing KeyValueStore and circular dependencies).
47
+ * Called during service locator reset.
48
+ */
49
+ clearCache(): void;
50
+ }
51
+ /**
52
+ * A storage identifier where exactly one of `id`, `name`, or `alias` is specified.
53
+ * Produced by {@link resolveStorageIdentifier} from ambiguous user input.
54
+ */
55
+ export type ExplicitStorageIdentifier = {
56
+ id: string;
57
+ name?: never;
58
+ alias?: never;
59
+ } | {
60
+ id?: never;
61
+ name: string;
62
+ alias?: never;
63
+ } | {
64
+ id?: never;
65
+ name?: never;
66
+ alias: string;
67
+ };
68
+ /**
69
+ * Represents the case where no identifier was provided — the caller wants the default storage.
70
+ */
71
+ export interface DefaultStorageIdentifier {
72
+ id?: never;
73
+ name?: never;
74
+ alias?: never;
75
+ }
76
+ /**
77
+ * Decompose a user-provided `identifier` (the `Dataset.open()` / `KeyValueStore.open()` /
78
+ * `RequestQueue.open()` argument) into separate `id`, `name`, and `alias` fields that
79
+ * the `StorageInstanceManager` and `StorageBackend.create*Client` expect.
80
+ *
81
+ * - `null` / `undefined` / `{}` → default storage alias
82
+ * - `string` → resolved via `storageExists` (ID-first, then name)
83
+ * - `{ id }` → `{ id }`
84
+ * - `{ name }` → `{ name }`
85
+ * - `{ alias }` → `{ alias }`
86
+ */
87
+ export declare function resolveStorageIdentifier(identifier: string | StorageIdentifier | null | undefined, storageBackend: StorageBackend, storageType: 'Dataset' | 'KeyValueStore' | 'RequestQueue'): Promise<ExplicitStorageIdentifier>;
@@ -0,0 +1,256 @@
1
+ import { AsyncQueue } from '@sapphire/async-queue';
2
+ /** Reserved alias for the default (unnamed) storage. */
3
+ const DEFAULT_STORAGE_ALIAS = '__default__';
4
+ /**
5
+ * Three-tier cache for storage instances, modelled after crawlee-python's `_StorageCache`.
6
+ *
7
+ * Each tier maps `[storageClass][key][backendCacheKey] → instance`:
8
+ * - `byId` — keyed by the backend-assigned storage id
9
+ * - `byName` — keyed by the persistent storage name
10
+ * - `byAlias` — keyed by a run-scoped alias (e.g. `'__default__'` for unnamed storages)
11
+ */
12
+ class StorageCache {
13
+ byId = new Map();
14
+ byName = new Map();
15
+ byAlias = new Map();
16
+ get(cls, { id, name, alias, backendCacheKey, }) {
17
+ for (const [tier, key] of [
18
+ [this.byId, id],
19
+ [this.byName, name],
20
+ [this.byAlias, alias],
21
+ ]) {
22
+ if (key === undefined)
23
+ continue;
24
+ const cached = tier.get(cls)?.get(key)?.get(backendCacheKey);
25
+ if (cached) {
26
+ if (cached instanceof cls) {
27
+ return cached;
28
+ }
29
+ throw new Error('Cached storage instance type mismatch.');
30
+ }
31
+ }
32
+ return undefined;
33
+ }
34
+ /** Write a single entry into a given tier. */
35
+ setInMap(tier, cls, key, instance, backendCacheKey) {
36
+ if (!tier.has(cls))
37
+ tier.set(cls, new Map());
38
+ const keyMap = tier.get(cls);
39
+ if (!keyMap.has(key))
40
+ keyMap.set(key, new Map());
41
+ keyMap.get(key).set(backendCacheKey, instance);
42
+ }
43
+ /**
44
+ * Cache an instance under its actual id, name, and an optional alias.
45
+ */
46
+ set(cls, instance, backendCacheKey, alias) {
47
+ // Always cache by id.
48
+ this.setInMap(this.byId, cls, instance.id, instance, backendCacheKey);
49
+ // Cache by name — only for named storages.
50
+ if (instance.name) {
51
+ this.setInMap(this.byName, cls, instance.name, instance, backendCacheKey);
52
+ }
53
+ // Cache by alias — only for unnamed storages opened via alias.
54
+ if (alias !== undefined) {
55
+ this.setInMap(this.byAlias, cls, alias, instance, backendCacheKey);
56
+ }
57
+ }
58
+ removeFromCache(instance) {
59
+ const storageType = instance.constructor;
60
+ for (const tier of [this.byId, this.byName, this.byAlias]) {
61
+ const classMap = tier.get(storageType);
62
+ if (!classMap)
63
+ continue;
64
+ for (const keyMap of classMap.values()) {
65
+ for (const [cacheKey, cached] of keyMap) {
66
+ if (cached === instance) {
67
+ keyMap.delete(cacheKey);
68
+ }
69
+ }
70
+ }
71
+ }
72
+ }
73
+ /**
74
+ * Ensure that the same string is not used as both a name and an alias for the same
75
+ * storage class + backend combination. Mirrors crawlee-python's `_check_name_alias_conflict`.
76
+ */
77
+ checkNameAliasConflict(cls, { name, alias, backendCacheKey }) {
78
+ if (alias) {
79
+ const existingByName = this.byName.get(cls)?.get(alias)?.get(backendCacheKey);
80
+ if (existingByName) {
81
+ throw new Error(`Cannot open storage with alias "${alias}" because a named storage with the same identifier already exists.`);
82
+ }
83
+ }
84
+ if (name) {
85
+ const existingByAlias = this.byAlias.get(cls)?.get(name)?.get(backendCacheKey);
86
+ if (existingByAlias) {
87
+ throw new Error(`Cannot open storage with name "${name}" because an alias storage with the same identifier already exists.` +
88
+ ` If you meant to open the alias storage, use { alias: "${name}" } instead.`);
89
+ }
90
+ }
91
+ }
92
+ /** Iterate all cached instances across all storage types. */
93
+ *allValues() {
94
+ const seen = new Set();
95
+ for (const classMap of this.byId.values()) {
96
+ for (const keyMap of classMap.values()) {
97
+ for (const instance of keyMap.values()) {
98
+ if (!seen.has(instance)) {
99
+ seen.add(instance);
100
+ yield instance;
101
+ }
102
+ }
103
+ }
104
+ }
105
+ }
106
+ clear() {
107
+ this.byId.clear();
108
+ this.byName.clear();
109
+ this.byAlias.clear();
110
+ }
111
+ }
112
+ /**
113
+ * Unified manager for opening and caching storage instances (Dataset, KeyValueStore, RequestQueue).
114
+ *
115
+ * A single instance manages all storage types. Instances are cached by
116
+ * `(storageClass, id/name/alias, backendCacheKey)` so the same storage is never opened twice.
117
+ *
118
+ * The manager itself does not resolve identifiers — callers pass explicit `id`, `name`, or `alias` (at most one),
119
+ * and a pre-bound `backendOpener` promise. When none of `id`, `name`, `alias` are provided, the manager automatically
120
+ * assigns a reserved default alias.
121
+ */
122
+ export class StorageInstanceManager {
123
+ #cache = new StorageCache();
124
+ #openerLocks = new Map();
125
+ /**
126
+ * Open (or retrieve from cache) a storage instance.
127
+ *
128
+ * @param cls The storage class constructor (e.g. `Dataset`, `KeyValueStore`, `RequestQueue`).
129
+ * @param id Storage ID (mutually exclusive with `name` and `alias`).
130
+ * @param name Storage name (mutually exclusive with `id` and `alias`).
131
+ * @param alias Run-scoped alias (mutually exclusive with `id` and `name`).
132
+ * Automatically assigned when no identifier is provided.
133
+ * @param backendOpener A **lazy** factory that creates the sub-backend.
134
+ * Only called on a cache miss.
135
+ * @param backendCacheKey Opaque key identifying the storage backend, so that the same logical
136
+ * storage opened through different clients is cached separately.
137
+ */
138
+ async openStorage(cls, { id, name, alias, backendOpener, backendCacheKey, }) {
139
+ // Auto-set alias='__default__' when no parameters are specified (mirrors crawlee-python).
140
+ if (!id && !name && !alias) {
141
+ alias = DEFAULT_STORAGE_ALIAS;
142
+ }
143
+ // Fast-path cache check (no lock).
144
+ if (alias !== undefined) {
145
+ const cached = this.#cache.get(cls, { alias, backendCacheKey });
146
+ if (cached)
147
+ return cached;
148
+ }
149
+ else if (id) {
150
+ const cached = this.#cache.get(cls, { id, backendCacheKey });
151
+ if (cached)
152
+ return cached;
153
+ }
154
+ else if (name) {
155
+ const cached = this.#cache.get(cls, { name, backendCacheKey });
156
+ if (cached)
157
+ return cached;
158
+ }
159
+ const identifierKey = id ?? name ?? alias ?? DEFAULT_STORAGE_ALIAS;
160
+ const lockKey = `${cls.name}:${identifierKey}:${backendCacheKey}`;
161
+ if (!this.#openerLocks.has(lockKey)) {
162
+ this.#openerLocks.set(lockKey, new AsyncQueue());
163
+ }
164
+ const queue = this.#openerLocks.get(lockKey);
165
+ await queue.wait();
166
+ try {
167
+ // Double-check cache under lock (another caller may have filled it while we waited).
168
+ if (alias !== undefined) {
169
+ const cached = this.#cache.get(cls, { alias, backendCacheKey });
170
+ if (cached)
171
+ return cached;
172
+ }
173
+ else if (id) {
174
+ const cached = this.#cache.get(cls, { id, backendCacheKey });
175
+ if (cached)
176
+ return cached;
177
+ }
178
+ else if (name) {
179
+ const cached = this.#cache.get(cls, { name, backendCacheKey });
180
+ if (cached)
181
+ return cached;
182
+ }
183
+ // Prevent the same string from being used as both a name and an alias.
184
+ this.#cache.checkNameAliasConflict(cls, { name, alias, backendCacheKey });
185
+ // Cache miss — create the sub-backend and storage instance.
186
+ const subBackend = await backendOpener();
187
+ const storageInfo = await subBackend.getMetadata();
188
+ // Storage frontends are thin wrappers over the backend. We hand them the resolved metadata
189
+ // we just fetched (so `id`/`name` etc. are available synchronously) along with the backend.
190
+ const instance = new cls({ metadata: storageInfo, backend: subBackend });
191
+ // Atomic cache writes (no awaits between these).
192
+ this.#cache.set(cls, instance, backendCacheKey, alias);
193
+ return instance;
194
+ }
195
+ finally {
196
+ queue.shift();
197
+ // Clean up idle locks so the map doesn't grow unboundedly
198
+ // (mirrors crawlee-python's WeakValueDictionary behaviour).
199
+ if (queue.remaining === 0) {
200
+ this.#openerLocks.delete(lockKey);
201
+ }
202
+ }
203
+ }
204
+ /**
205
+ * Remove a storage instance from the cache (called from `storage.drop()`).
206
+ */
207
+ removeFromCache(instance) {
208
+ this.#cache.removeFromCache(instance);
209
+ }
210
+ /**
211
+ * Clear the entire cache. Also calls `clearCache()` on any cached KeyValueStore
212
+ * instances (duck-typed to avoid importing KeyValueStore and circular dependencies).
213
+ * Called during service locator reset.
214
+ */
215
+ clearCache() {
216
+ for (const instance of this.#cache.allValues()) {
217
+ if ('clearCache' in instance && typeof instance.clearCache === 'function') {
218
+ instance.clearCache();
219
+ }
220
+ }
221
+ this.#cache.clear();
222
+ }
223
+ }
224
+ /**
225
+ * Decompose a user-provided `identifier` (the `Dataset.open()` / `KeyValueStore.open()` /
226
+ * `RequestQueue.open()` argument) into separate `id`, `name`, and `alias` fields that
227
+ * the `StorageInstanceManager` and `StorageBackend.create*Client` expect.
228
+ *
229
+ * - `null` / `undefined` / `{}` → default storage alias
230
+ * - `string` → resolved via `storageExists` (ID-first, then name)
231
+ * - `{ id }` → `{ id }`
232
+ * - `{ name }` → `{ name }`
233
+ * - `{ alias }` → `{ alias }`
234
+ */
235
+ export async function resolveStorageIdentifier(identifier, storageBackend, storageType) {
236
+ if (identifier === null || identifier === undefined) {
237
+ return { alias: DEFAULT_STORAGE_ALIAS };
238
+ }
239
+ if (typeof identifier === 'string') {
240
+ if (storageBackend.storageExists && (await storageBackend.storageExists(identifier, storageType))) {
241
+ return { id: identifier };
242
+ }
243
+ return { name: identifier };
244
+ }
245
+ if (identifier.id) {
246
+ return { id: identifier.id };
247
+ }
248
+ if (identifier.name) {
249
+ return { name: identifier.name };
250
+ }
251
+ if ('alias' in identifier && identifier.alias) {
252
+ return { alias: identifier.alias };
253
+ }
254
+ // Empty object — treated as default storage.
255
+ return { alias: DEFAULT_STORAGE_ALIAS };
256
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Backend-independent usage counters tracked by the storage frontend classes
3
+ * ({@link Dataset}, {@link KeyValueStore}, {@link RequestQueue}).
4
+ *
5
+ * These count the operations the frontend issues against its underlying storage backend, so they are
6
+ * meaningful for any storage backend (memory, file system, cloud). They are tallied per client call
7
+ * — e.g. iterating a key-value store increments `readCount` once per record fetched and `listCount`
8
+ * once per listed page. Backend-specific figures that the frontend cannot compute (such as the number
9
+ * of bytes stored) are intentionally not included here; read those from the backend's own API instead.
10
+ */
11
+ /** Usage counters for a {@link Dataset}. */
12
+ export interface DatasetStats {
13
+ /** Number of read operations issued to the dataset client (e.g. `getData`). */
14
+ readCount: number;
15
+ /** Number of write operations issued to the dataset client (e.g. `pushData`). */
16
+ writeCount: number;
17
+ }
18
+ /** Usage counters for a {@link KeyValueStore}. */
19
+ export interface KeyValueStoreStats {
20
+ /** Number of read operations issued to the key-value store client (e.g. `getValue`). */
21
+ readCount: number;
22
+ /** Number of write operations issued to the key-value store client (e.g. `setValue`). */
23
+ writeCount: number;
24
+ /** Number of delete operations issued to the key-value store client (e.g. `deleteValue`). */
25
+ deleteCount: number;
26
+ /** Number of listing operations issued to the key-value store client (e.g. `listKeys`). */
27
+ listCount: number;
28
+ }
29
+ /** Usage counters for a {@link RequestQueue}. */
30
+ export interface RequestQueueStats {
31
+ /** Number of write operations issued to the request queue client (add / handle / reclaim). */
32
+ writeCount: number;
33
+ /** Number of queue-head reads issued to the request queue client (`fetchNextRequest`). */
34
+ headItemReadCount: number;
35
+ }
36
+ /**
37
+ * A tiny mutable counter that the storage frontends increment on each client call and expose through
38
+ * a read-only `stats` snapshot. Generic over the concrete counter shape so each storage type gets only
39
+ * the buckets that make sense for it.
40
+ */
41
+ export declare class StorageStatsTracker<T extends Record<keyof T, number>> {
42
+ #private;
43
+ constructor(initial: T);
44
+ /** Increment a counter bucket by `by` (default `1`). */
45
+ add(key: keyof T, by?: number): void;
46
+ /** Return a snapshot of the current counters. The returned object is a copy and safe to keep. */
47
+ get current(): T;
48
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Backend-independent usage counters tracked by the storage frontend classes
3
+ * ({@link Dataset}, {@link KeyValueStore}, {@link RequestQueue}).
4
+ *
5
+ * These count the operations the frontend issues against its underlying storage backend, so they are
6
+ * meaningful for any storage backend (memory, file system, cloud). They are tallied per client call
7
+ * — e.g. iterating a key-value store increments `readCount` once per record fetched and `listCount`
8
+ * once per listed page. Backend-specific figures that the frontend cannot compute (such as the number
9
+ * of bytes stored) are intentionally not included here; read those from the backend's own API instead.
10
+ */
11
+ /**
12
+ * A tiny mutable counter that the storage frontends increment on each client call and expose through
13
+ * a read-only `stats` snapshot. Generic over the concrete counter shape so each storage type gets only
14
+ * the buckets that make sense for it.
15
+ */
16
+ export class StorageStatsTracker {
17
+ #counters;
18
+ constructor(initial) {
19
+ this.#counters = { ...initial };
20
+ }
21
+ /** Increment a counter bucket by `by` (default `1`). */
22
+ add(key, by = 1) {
23
+ this.#counters[key] += by;
24
+ }
25
+ /** Return a snapshot of the current counters. The returned object is a copy and safe to keep. */
26
+ get current() {
27
+ return { ...this.#counters };
28
+ }
29
+ }
@@ -0,0 +1,260 @@
1
+ import type { Awaitable, Dictionary } from '@crawlee/types';
2
+ import type { RecordOptions } from './key_value_store.js';
3
+ /**
4
+ * Governs whether writes of a given storage type performed inside a {@link StorageTransaction} are
5
+ * applied immediately (`writeThrough`) or recorded and replayed on commit (`deferred`).
6
+ */
7
+ export type StorageWriteMode = 'deferred' | 'writeThrough';
8
+ /**
9
+ * Per-storage-type write policy of a {@link StorageTransaction}. Datasets and key-value stores are
10
+ * always `deferred` and not configurable — deferring is the only safe mode for non-idempotent writes,
11
+ * and {@link withDirectStorageAccess} covers one-off immediate writes.
12
+ */
13
+ export interface StorageWritePolicy {
14
+ /**
15
+ * Write mode for request queue additions. Note that this is a *write policy* for the queue, not the
16
+ * queue instance itself (which is the top-level `requestQueue` crawler option).
17
+ *
18
+ * - `writeThrough` (default): requests are added immediately and are **not** rolled back with the
19
+ * transaction. This is safe (additions are deduplicated by `uniqueKey`, so a retry is idempotent)
20
+ * and keeps new requests visible to the crawler while the handler still runs.
21
+ * - `deferred`: requests are only added when the transaction commits — strict all-or-nothing
22
+ * semantics, at the cost of the crawler not seeing them until the handler finishes.
23
+ */
24
+ requestQueue: StorageWriteMode;
25
+ }
26
+ export type StorageTransactionState = 'open' | 'committing' | 'committed' | 'failed' | 'rolledBack';
27
+ /**
28
+ * A storage frontend that can record operations in a transaction journal.
29
+ * @internal
30
+ */
31
+ export interface TransactionParticipant {
32
+ /**
33
+ * Replay the given buffered journal entries (all recorded by this participant) into the real storage
34
+ * backend. Called during commit, with the transaction already in the `committing` state, so the
35
+ * replayed operations pass through.
36
+ */
37
+ commitJournalEntries(entries: JournalEntry[]): Promise<void>;
38
+ }
39
+ /**
40
+ * A single dataset write (`pushData`) recorded in a transaction journal.
41
+ */
42
+ export interface DatasetJournalEntry {
43
+ type: 'dataset';
44
+ /** @internal **/
45
+ participant: TransactionParticipant;
46
+ storageId: string;
47
+ /** The pushed items, captured by `structuredClone` at write time. */
48
+ items: Dictionary[];
49
+ recordedAt: Date;
50
+ }
51
+ /**
52
+ * A single key-value store write (`setValue`) recorded in a transaction journal.
53
+ */
54
+ export interface KeyValueStoreJournalEntry {
55
+ type: 'keyValueStore';
56
+ /** @internal **/
57
+ participant: TransactionParticipant;
58
+ storageId: string;
59
+ key: string;
60
+ /** The original, pre-serialization value captured by `structuredClone`; `null` denotes a deletion. */
61
+ value: unknown;
62
+ options?: RecordOptions;
63
+ }
64
+ /**
65
+ * A request recorded in a transaction journal.
66
+ */
67
+ export interface JournaledRequest {
68
+ url: string;
69
+ uniqueKey: string;
70
+ label?: string;
71
+ /**
72
+ * A full JSON snapshot of the request for the commit replay. Only present for buffered additions —
73
+ * deduplicated and write-through ones are journaled for introspection only.
74
+ */
75
+ snapshot?: Dictionary;
76
+ }
77
+ /**
78
+ * A batch of request queue additions recorded in a transaction journal.
79
+ */
80
+ export interface RequestQueueJournalEntry {
81
+ type: 'requestQueue';
82
+ /** @internal **/
83
+ participant: TransactionParticipant;
84
+ requests: JournaledRequest[];
85
+ forefront: boolean;
86
+ /** Write-through entries were applied immediately; they are never replayed. */
87
+ writeThrough: boolean;
88
+ }
89
+ export type JournalEntry = DatasetJournalEntry | KeyValueStoreJournalEntry | RequestQueueJournalEntry;
90
+ /**
91
+ * A read-only view of a {@link StorageTransaction}: only the journal-backed introspection accessors,
92
+ * without the lifecycle methods. The accessors are synchronous and expose the original pre-serialization
93
+ * values. They cover every write recorded while the transaction was open, under either write policy —
94
+ * with the one exception noted on {@link StorageTransactionView.enqueuedUrls|`enqueuedUrls`}. A view
95
+ * is valid until the transaction is disposed.
96
+ */
97
+ export interface StorageTransactionView {
98
+ readonly state: StorageTransactionState;
99
+ /** Items pushed to datasets during the transaction, in push order. */
100
+ readonly datasetItems: {
101
+ item: Dictionary;
102
+ datasetId: string;
103
+ }[];
104
+ /**
105
+ * URLs enqueued to request queues during the transaction, under either write policy. Recorded as
106
+ * requested, so duplicate, already-present and backend-rejected URLs are included.
107
+ *
108
+ * One gap: unless a caller of `addRequestsBatched()` waits for every chunk
109
+ * (`waitForAllRequestsToBeAdded` or `maxNewRequests`, both of which {@link enqueueLinks} sets when
110
+ * a crawl limit applies), the chunks after the first are added by a background writer that outlives
111
+ * the transaction and is not recorded here.
112
+ */
113
+ readonly enqueuedUrls: {
114
+ url: string;
115
+ label?: string;
116
+ }[];
117
+ /** Key-value store changes made during the transaction, keyed by store id, last write per key. */
118
+ readonly keyValueStoreChanges: Record<string, Record<string, {
119
+ changedValue: unknown;
120
+ options?: RecordOptions;
121
+ }>>;
122
+ }
123
+ export interface StorageTransactionOptions {
124
+ /** Overrides of the per-storage-type write policy. See {@link StorageWritePolicy}. */
125
+ policy?: Partial<StorageWritePolicy>;
126
+ /**
127
+ * How long a commit may take before it fails, in milliseconds. There is no automatic retry — the
128
+ * replay of dataset items is not idempotent.
129
+ * @default 300000
130
+ */
131
+ commitTimeoutMillis?: number;
132
+ }
133
+ /**
134
+ * A storage transaction scoped to a request's lifecycle. Writes made through the storage frontends
135
+ * ({@link Dataset}, {@link KeyValueStore}, {@link RequestQueue}) while the transaction is active
136
+ * are recorded rather than applied; on {@link StorageTransaction.commit|`commit()`} they are replayed
137
+ * into real storage, on {@link StorageTransaction.rollback|`rollback()`} they are dropped. Reads consult
138
+ * the recorded writes first, so a handler sees its own writes.
139
+ *
140
+ * Create one with {@link createStorageTransaction} (explicit commit/rollback) or
141
+ * {@link withStorageTransaction} (scoped sugar). Crawlers open one automatically around every request
142
+ * handler unless `transactionalStorage: false` is set.
143
+ */
144
+ export declare class StorageTransaction implements StorageTransactionView {
145
+ #private;
146
+ /** The ordered, append-only journal — the source of truth for commit, introspection and reads. */
147
+ readonly journal: JournalEntry[];
148
+ /** Per-storage-type write policy. */
149
+ readonly policy: StorageWritePolicy;
150
+ /** @internal */
151
+ constructor(options?: StorageTransactionOptions);
152
+ get state(): StorageTransactionState;
153
+ /**
154
+ * `true` only while `state === 'open'`. This is the single predicate every storage operation
155
+ * consults — operations performed after the transaction is closed pass through to the real backend.
156
+ */
157
+ get isActive(): boolean;
158
+ /** Runs `callback` with this transaction installed in the async context. */
159
+ run<T>(callback: () => Awaitable<T>): Promise<T>;
160
+ /**
161
+ * Records a write operation in the journal.
162
+ * @internal
163
+ */
164
+ recordJournalEntry(entry: JournalEntry): void;
165
+ /**
166
+ * Registers a callback to run once a commit has been attempted, whether it succeeded (`error` is
167
+ * `undefined`) or failed - the only point at which a deferred write can be reacted to where it was
168
+ * made. Callbacks run in registration order; the first to throw propagates, the rest do not run, and
169
+ * on a failed commit its error replaces the commit error. Not run on rollback: nothing was written.
170
+ */
171
+ afterCommit(callback: (error?: Error) => Awaitable<void>): void;
172
+ /**
173
+ * Replays the journaled writes into real storage. A no-op unless the transaction is `open`.
174
+ *
175
+ * The transaction transitions to `committing` *before* anything is flushed, so a commit that throws
176
+ * partway lands in `failed` (never back in `open`) and subsequent storage operations pass through
177
+ * rather than recording into a dead transaction. Delivery is at-least-once — a commit that fails
178
+ * partway may have applied some of the writes already.
179
+ */
180
+ commit(): Promise<void>;
181
+ private flush;
182
+ /**
183
+ * Discards the journaled writes. A no-op unless the transaction is `open` — in particular, calling it
184
+ * after a successful `commit()` (which the crawler's error handling can legitimately do) does nothing
185
+ * and never throws.
186
+ */
187
+ rollback(): void;
188
+ /**
189
+ * Releases the journal, the write-time snapshots it holds and any registered commit callbacks. Must
190
+ * be called for *every* terminal state, `failed` included. Idempotent, never throws, and does not
191
+ * change `state`. Any {@link StorageTransactionView} of this transaction is only valid until this
192
+ * is called.
193
+ */
194
+ dispose(): void;
195
+ get datasetItems(): {
196
+ item: Dictionary;
197
+ datasetId: string;
198
+ }[];
199
+ get enqueuedUrls(): {
200
+ url: string;
201
+ label?: string;
202
+ }[];
203
+ get keyValueStoreChanges(): Record<string, Record<string, {
204
+ changedValue: unknown;
205
+ options?: RecordOptions;
206
+ }>>;
207
+ }
208
+ /**
209
+ * Opens a {@link StorageTransaction} without running anything yet. The caller owns the outcome:
210
+ * `run()`, then `commit()` or `rollback()`, and always `dispose()` when done. For the common
211
+ * open-run-commit flow, prefer {@link withStorageTransaction}.
212
+ */
213
+ export declare function createStorageTransaction(options?: StorageTransactionOptions): StorageTransaction;
214
+ /**
215
+ * Runs `callback` inside a new {@link StorageTransaction}: storage writes made in the callback are
216
+ * committed when it returns and rolled back when it throws. If a transaction is already active in the
217
+ * current async context, it is reused and its outcome is left to its owner (and `options` are ignored)
218
+ * — there are no nested transaction semantics.
219
+ */
220
+ export declare function withStorageTransaction<T>(callback: (transaction: StorageTransaction) => Awaitable<T>, options?: StorageTransactionOptions): Promise<T>;
221
+ /**
222
+ * Runs `callback` outside of any storage transaction — the per-call-site escape hatch. Storage operations
223
+ * made inside it hit the real backend directly, are not rolled back, and operations that a transaction
224
+ * rejects (`drop`, stream-valued `setValue`, request queue internals, ...) are permitted.
225
+ */
226
+ export declare function withDirectStorageAccess<T>(callback: () => Awaitable<T>): Promise<T>;
227
+ /**
228
+ * The per-operation hook consulted by every storage frontend method: performs the cancellation check
229
+ * that aborts storage operations when the request handler times out, and returns the active storage
230
+ * transaction. Returns `undefined` when there is no transaction in the async context *or* when it is no
231
+ * longer open — operations on a closed transaction deliberately pass through to the real backend.
232
+ * @internal
233
+ */
234
+ export declare function activeStorageTransaction(): StorageTransaction | undefined;
235
+ /**
236
+ * Returns the transaction installed in the current async context, regardless of its state. Used by the
237
+ * crawler to drive the outcome of the transaction it opened.
238
+ * @internal
239
+ */
240
+ export declare function currentStorageTransaction(): StorageTransaction | undefined;
241
+ /**
242
+ * Captures a value at write time, so that later mutations of the caller's object affect neither the
243
+ * read-your-own-writes reads nor the commit replay. `structuredClone` for fidelity (`Date`, `Map`, `Set`,
244
+ * typed arrays, `undefined`); values it cannot handle fall back to the JSON round-trip the storage
245
+ * backends perform anyway.
246
+ * @internal
247
+ */
248
+ export declare function snapshotValue<T>(value: T): T;
249
+ /**
250
+ * The guard for operations that cannot be performed inside a storage transaction: throws when one is
251
+ * active, and performs the per-operation cancellation check either way.
252
+ * @internal
253
+ */
254
+ export declare function rejectOperationInTransaction(operation: string, reason?: string): void;
255
+ /**
256
+ * Builds the "operation not allowed in a transaction" error, for a call site that has already
257
+ * established a transaction is active and so wants to `throw` unconditionally.
258
+ * @internal
259
+ */
260
+ export declare function operationRejectedInTransaction(operation: string, reason?: string): Error;