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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +1 -1
  2. package/configuration.d.ts +16 -47
  3. package/configuration.js +13 -25
  4. package/debug.js +4 -4
  5. package/errors.d.ts +28 -38
  6. package/errors.js +33 -47
  7. package/events/event_manager.d.ts +2 -2
  8. package/events/event_manager.js +7 -6
  9. package/events/index.d.ts +1 -0
  10. package/events/local_event_manager.d.ts +1 -8
  11. package/events/local_event_manager.js +13 -13
  12. package/events/system_info.d.ts +38 -0
  13. package/index.d.ts +2 -8
  14. package/index.js +4 -8
  15. package/internal.d.ts +8 -0
  16. package/internal.js +9 -0
  17. package/log.d.ts +10 -11
  18. package/log.js +52 -20
  19. package/memory-storage/memory-storage.d.ts +15 -18
  20. package/memory-storage/memory-storage.js +80 -58
  21. package/memory-storage/resource-clients/dataset.d.ts +1 -6
  22. package/memory-storage/resource-clients/dataset.js +23 -31
  23. package/memory-storage/resource-clients/key-value-store.d.ts +1 -10
  24. package/memory-storage/resource-clients/key-value-store.js +43 -67
  25. package/memory-storage/resource-clients/request-queue.d.ts +1 -42
  26. package/memory-storage/resource-clients/request-queue.js +109 -117
  27. package/owned_or_injected.d.ts +1 -3
  28. package/owned_or_injected.js +17 -17
  29. package/package.json +17 -20
  30. package/proxy_configuration.d.ts +21 -26
  31. package/proxy_configuration.js +35 -25
  32. package/recoverable_state.d.ts +104 -47
  33. package/recoverable_state.js +199 -74
  34. package/request.d.ts +20 -107
  35. package/request.js +78 -244
  36. package/serialization.js +17 -16
  37. package/service_locator.d.ts +22 -10
  38. package/service_locator.js +59 -48
  39. package/storages/batched_adds.d.ts +37 -0
  40. package/storages/batched_adds.js +73 -0
  41. package/storages/dataset.d.ts +13 -8
  42. package/storages/dataset.js +149 -40
  43. package/storages/index.d.ts +4 -4
  44. package/storages/index.js +2 -4
  45. package/storages/key_value_store.d.ts +16 -35
  46. package/storages/key_value_store.js +223 -110
  47. package/storages/key_value_store_codec.js +6 -11
  48. package/storages/request_dedup_cache.d.ts +1 -4
  49. package/storages/request_dedup_cache.js +15 -15
  50. package/storages/request_list.d.ts +9 -104
  51. package/storages/request_list.js +236 -233
  52. package/storages/request_loader.d.ts +49 -18
  53. package/storages/request_loader.js +36 -1
  54. package/storages/request_manager.d.ts +86 -0
  55. package/storages/request_manager_tandem.d.ts +14 -38
  56. package/storages/request_manager_tandem.js +67 -64
  57. package/storages/request_queue.d.ts +23 -50
  58. package/storages/request_queue.js +371 -226
  59. package/storages/storage_instance_manager.d.ts +2 -4
  60. package/storages/storage_instance_manager.js +21 -21
  61. package/storages/storage_stats.d.ts +1 -1
  62. package/storages/storage_stats.js +4 -4
  63. package/storages/transaction.d.ts +270 -0
  64. package/storages/transaction.js +296 -0
  65. package/storages/utils.d.ts +6 -3
  66. package/storages/utils.js +11 -2
  67. package/system-info/runtime.js +7 -7
  68. package/url.d.ts +9 -0
  69. package/url.js +11 -0
  70. package/validators.d.ts +23 -25
  71. package/validators.js +14 -25
  72. package/autoscaling/autoscaled_pool.d.ts +0 -213
  73. package/autoscaling/autoscaled_pool.js +0 -378
  74. package/autoscaling/client_load_signal.d.ts +0 -59
  75. package/autoscaling/client_load_signal.js +0 -73
  76. package/autoscaling/concurrency_system.d.ts +0 -283
  77. package/autoscaling/concurrency_system.js +0 -350
  78. package/autoscaling/cpu_load_signal.d.ts +0 -44
  79. package/autoscaling/cpu_load_signal.js +0 -46
  80. package/autoscaling/event_loop_load_signal.d.ts +0 -54
  81. package/autoscaling/event_loop_load_signal.js +0 -60
  82. package/autoscaling/index.d.ts +0 -9
  83. package/autoscaling/index.js +0 -9
  84. package/autoscaling/load_signal.d.ts +0 -99
  85. package/autoscaling/load_signal.js +0 -103
  86. package/autoscaling/memory_load_signal.d.ts +0 -56
  87. package/autoscaling/memory_load_signal.js +0 -106
  88. package/autoscaling/snapshotter.d.ts +0 -87
  89. package/autoscaling/snapshotter.js +0 -67
  90. package/autoscaling/system_status.d.ts +0 -161
  91. package/autoscaling/system_status.js +0 -139
  92. package/autoscaling/weighted_avg.d.ts +0 -5
  93. package/autoscaling/weighted_avg.js +0 -14
  94. package/cookie_utils.d.ts +0 -44
  95. package/cookie_utils.js +0 -122
  96. package/crawlers/context_pipeline.d.ts +0 -70
  97. package/crawlers/context_pipeline.js +0 -122
  98. package/crawlers/crawler_commons.d.ts +0 -257
  99. package/crawlers/crawler_commons.js +0 -107
  100. package/crawlers/error_snapshotter.d.ts +0 -59
  101. package/crawlers/error_snapshotter.js +0 -117
  102. package/crawlers/error_tracker.d.ts +0 -54
  103. package/crawlers/error_tracker.js +0 -308
  104. package/crawlers/index.d.ts +0 -5
  105. package/crawlers/index.js +0 -5
  106. package/crawlers/internals/types.d.ts +0 -7
  107. package/crawlers/statistics.d.ts +0 -209
  108. package/crawlers/statistics.js +0 -350
  109. package/enqueue_links/enqueue_links.d.ts +0 -264
  110. package/enqueue_links/enqueue_links.js +0 -271
  111. package/enqueue_links/index.d.ts +0 -2
  112. package/enqueue_links/index.js +0 -2
  113. package/enqueue_links/shared.d.ts +0 -83
  114. package/enqueue_links/shared.js +0 -221
  115. package/router.d.ts +0 -309
  116. package/router.js +0 -309
  117. package/session_pool/consts.d.ts +0 -3
  118. package/session_pool/consts.js +0 -3
  119. package/session_pool/errors.d.ts +0 -7
  120. package/session_pool/errors.js +0 -11
  121. package/session_pool/fingerprint.d.ts +0 -9
  122. package/session_pool/fingerprint.js +0 -30
  123. package/session_pool/index.d.ts +0 -4
  124. package/session_pool/index.js +0 -4
  125. package/session_pool/session.d.ts +0 -161
  126. package/session_pool/session.js +0 -218
  127. package/session_pool/session_pool.d.ts +0 -246
  128. package/session_pool/session_pool.js +0 -386
  129. package/storages/access_checking.d.ts +0 -12
  130. package/storages/access_checking.js +0 -17
  131. package/storages/sitemap_request_loader.d.ts +0 -249
  132. package/storages/sitemap_request_loader.js +0 -432
  133. /package/{crawlers/internals/types.js → events/system_info.js} +0 -0
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Represents the current status of the system.
3
+ */
4
+ export interface SystemInfo {
5
+ /** If false, system is being overloaded. */
6
+ isSystemIdle: boolean;
7
+ memInfo: LoadSignalInfo;
8
+ eventLoopInfo: LoadSignalInfo;
9
+ cpuInfo: LoadSignalInfo;
10
+ storageBackendInfo: LoadSignalInfo;
11
+ memTotalBytes?: number;
12
+ memCurrentBytes?: number;
13
+ /**
14
+ * Platform only property
15
+ * @internal
16
+ */
17
+ cpuCurrentUsage?: number;
18
+ /**
19
+ * Platform only property
20
+ * @internal
21
+ */
22
+ isCpuOverloaded?: boolean;
23
+ /**
24
+ * Platform only property
25
+ * @internal
26
+ */
27
+ createdAt?: Date;
28
+ /**
29
+ * Status of additional load signals beyond the built-in four.
30
+ * Keys are `LoadSignal.name` values, values are overload info.
31
+ */
32
+ loadSignalInfo?: Record<string, LoadSignalInfo>;
33
+ }
34
+ export interface LoadSignalInfo {
35
+ isOverloaded: boolean;
36
+ limitRatio: number;
37
+ actualRatio: number;
38
+ }
package/index.d.ts CHANGED
@@ -1,22 +1,16 @@
1
1
  export * from './debug.js';
2
2
  export * from './errors.js';
3
- export * from './autoscaling/index.js';
4
3
  export * from './configuration.js';
5
4
  export * from './service_locator.js';
6
- export * from './crawlers/index.js';
7
- export * from './enqueue_links/index.js';
8
5
  export * from './events/index.js';
9
6
  export * from './log.js';
10
7
  export * from './owned_or_injected.js';
11
8
  export * from './proxy_configuration.js';
12
9
  export * from './request.js';
13
- export * from './router.js';
14
10
  export * from './serialization.js';
15
- export * from './session_pool/index.js';
16
11
  export * from './storages/index.js';
17
12
  export * from './memory-storage/index.js';
18
- export * from './validators.js';
19
- export * from './cookie_utils.js';
13
+ export { ArgumentValidationError, validators } from './validators.js';
20
14
  export * from './recoverable_state.js';
21
- export { PseudoUrl } from '@apify/pseudo_url';
22
15
  export type { StorageBackend } from '@crawlee/types';
16
+ export { EnqueueStrategy } from '@crawlee/utils';
package/index.js CHANGED
@@ -1,21 +1,17 @@
1
1
  export * from './debug.js';
2
2
  export * from './errors.js';
3
- export * from './autoscaling/index.js';
4
3
  export * from './configuration.js';
5
4
  export * from './service_locator.js';
6
- export * from './crawlers/index.js';
7
- export * from './enqueue_links/index.js';
8
5
  export * from './events/index.js';
9
6
  export * from './log.js';
10
7
  export * from './owned_or_injected.js';
11
8
  export * from './proxy_configuration.js';
12
9
  export * from './request.js';
13
- export * from './router.js';
14
10
  export * from './serialization.js';
15
- export * from './session_pool/index.js';
16
11
  export * from './storages/index.js';
17
12
  export * from './memory-storage/index.js';
18
- export * from './validators.js';
19
- export * from './cookie_utils.js';
13
+ // Not `export *`: the rest of the module re-exports `@crawlee/utils/internal` symbols, which carry no
14
+ // semver guarantees and must not reach the public surface. Internal consumers import them directly.
15
+ export { ArgumentValidationError, validators } from './validators.js';
20
16
  export * from './recoverable_state.js';
21
- export { PseudoUrl } from '@apify/pseudo_url';
17
+ export { EnqueueStrategy } from '@crawlee/utils';
package/internal.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export * from './iterables.js';
2
+ export * from './storages/batched_adds.js';
3
+ export { joinRequestSourceStatuses } from './storages/request_loader.js';
4
+ export * from './system-info/cpu-info.js';
5
+ export * from './system-info/memory-info.js';
6
+ export * from './system-info/ps-tree.js';
7
+ export * from './system-info/runtime.js';
8
+ export * from './url.js';
package/internal.js ADDED
@@ -0,0 +1,9 @@
1
+ // Not part of the public API: internals that other Crawlee packages need but that carry no semver guarantees.
2
+ export * from './iterables.js';
3
+ export * from './storages/batched_adds.js';
4
+ export { joinRequestSourceStatuses } from './storages/request_loader.js';
5
+ export * from './system-info/cpu-info.js';
6
+ export * from './system-info/memory-info.js';
7
+ export * from './system-info/ps-tree.js';
8
+ export * from './system-info/runtime.js';
9
+ export * from './url.js';
package/log.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import type { CrawleeLogger, CrawleeLoggerOptions } from '@crawlee/types';
1
+ import type { CrawleeLogger, CrawleeLoggerOptions, LogOptions } from '@crawlee/types';
2
2
  import type { LoggerOptions } from '@apify/log';
3
3
  import log, { Log, Logger, LoggerJson, LoggerText, LogLevel } from '@apify/log';
4
- export type { CrawleeLogger, CrawleeLoggerOptions };
4
+ export type { CrawleeLogger, CrawleeLoggerOptions, LogOptions };
5
5
  /**
6
6
  * Abstract base class for custom Crawlee logger implementations.
7
7
  *
@@ -33,8 +33,7 @@ export type { CrawleeLogger, CrawleeLoggerOptions };
33
33
  * ```
34
34
  */
35
35
  export declare abstract class BaseCrawleeLogger implements CrawleeLogger {
36
- private options;
37
- private readonly warningsLogged;
36
+ #private;
38
37
  constructor(options?: Partial<CrawleeLoggerOptions>);
39
38
  /**
40
39
  * Core logging method. Subclasses must implement this to dispatch log messages
@@ -56,14 +55,14 @@ export declare abstract class BaseCrawleeLogger implements CrawleeLogger {
56
55
  getOptions(): CrawleeLoggerOptions;
57
56
  setOptions(options: Partial<CrawleeLoggerOptions>): void;
58
57
  child(options: Partial<CrawleeLoggerOptions>): CrawleeLogger;
59
- error(message: string, data?: Record<string, unknown>): void;
58
+ error(message: string, data?: Record<string, unknown>, options?: LogOptions): void;
60
59
  exception(exception: Error, message: string, data?: Record<string, unknown>): void;
61
- softFail(message: string, data?: Record<string, unknown>): void;
62
- warning(message: string, data?: Record<string, unknown>): void;
60
+ softFail(message: string, data?: Record<string, unknown>, options?: LogOptions): void;
61
+ warning(message: string, data?: Record<string, unknown>, options?: LogOptions): void;
63
62
  warningOnce(message: string): void;
64
- info(message: string, data?: Record<string, unknown>): void;
65
- debug(message: string, data?: Record<string, unknown>): void;
66
- perf(message: string, data?: Record<string, unknown>): void;
63
+ info(message: string, data?: Record<string, unknown>, options?: LogOptions): void;
64
+ debug(message: string, data?: Record<string, unknown>, options?: LogOptions): void;
65
+ perf(message: string, data?: Record<string, unknown>, options?: LogOptions): void;
67
66
  deprecated(message: string): void;
68
67
  }
69
68
  /**
@@ -73,7 +72,7 @@ export declare abstract class BaseCrawleeLogger implements CrawleeLogger {
73
72
  * Users who want to use a different logging library should implement {@link BaseCrawleeLogger} directly.
74
73
  */
75
74
  export declare class ApifyLogAdapter extends BaseCrawleeLogger {
76
- private readonly apifyLog;
75
+ #private;
77
76
  constructor(apifyLog: Log, options?: Partial<CrawleeLoggerOptions>);
78
77
  logWithLevel(level: number, message: string, data?: Record<string, unknown>): void;
79
78
  protected createChild(options: Partial<CrawleeLoggerOptions>): CrawleeLogger;
package/log.js CHANGED
@@ -30,21 +30,43 @@ import log, { Log, Logger, LoggerJson, LoggerText, LogLevel } from '@apify/log';
30
30
  * ```
31
31
  */
32
32
  export class BaseCrawleeLogger {
33
- options;
34
- warningsLogged = new Set();
33
+ // Note: If wrapping logger in a Proxy, unbound methods calling #-fields throw TypeError
34
+ // unless bound to the target (see createLogProxy in adaptive-playwright-crawler.ts).
35
+ #options;
36
+ #loggedOnce = new Set();
35
37
  constructor(options = {}) {
36
- this.options = options;
38
+ this.#options = options;
37
39
  }
38
40
  getOptions() {
39
- return this.options;
41
+ return this.#options;
40
42
  }
41
43
  setOptions(options) {
42
- this.options = { ...this.options, ...options };
44
+ this.#options = { ...this.#options, ...options };
43
45
  }
44
46
  child(options) {
45
47
  return this.createChild(options);
46
48
  }
47
- error(message, data) {
49
+ /**
50
+ * Whether `once` suppresses this message. Keyed by level as well as text, so that a message logged once as
51
+ * `info` does not swallow the same text logged as a `warning`.
52
+ *
53
+ * Handled here rather than in {@link BaseCrawleeLogger.logWithLevel} so that every logger gets it, instead
54
+ * of each implementation having to dedupe for itself.
55
+ */
56
+ #suppressedAsRepeat(level, message, options) {
57
+ if (!options?.once) {
58
+ return false;
59
+ }
60
+ const key = `${level}:${message}`;
61
+ if (this.#loggedOnce.has(key)) {
62
+ return true;
63
+ }
64
+ this.#loggedOnce.add(key);
65
+ return false;
66
+ }
67
+ error(message, data, options) {
68
+ if (this.#suppressedAsRepeat(LogLevel.ERROR, message, options))
69
+ return;
48
70
  this.logWithLevel(LogLevel.ERROR, message, data);
49
71
  }
50
72
  exception(exception, message, data) {
@@ -54,25 +76,35 @@ export class BaseCrawleeLogger {
54
76
  exception,
55
77
  });
56
78
  }
57
- softFail(message, data) {
79
+ softFail(message, data, options) {
80
+ if (this.#suppressedAsRepeat(LogLevel.SOFT_FAIL, message, options))
81
+ return;
58
82
  this.logWithLevel(LogLevel.SOFT_FAIL, message, data);
59
83
  }
60
- warning(message, data) {
84
+ warning(message, data, options) {
85
+ if (this.#suppressedAsRepeat(LogLevel.WARNING, message, options))
86
+ return;
61
87
  this.logWithLevel(LogLevel.WARNING, message, data);
62
88
  }
63
89
  warningOnce(message) {
64
- if (!this.warningsLogged.has(message)) {
65
- this.warningsLogged.add(message);
66
- this.warning(message);
67
- }
68
- }
69
- info(message, data) {
90
+ // Gates before delegating, so that a suppressed repeat is not even a `warning()` call.
91
+ if (this.#suppressedAsRepeat(LogLevel.WARNING, message, { once: true }))
92
+ return;
93
+ this.warning(message);
94
+ }
95
+ info(message, data, options) {
96
+ if (this.#suppressedAsRepeat(LogLevel.INFO, message, options))
97
+ return;
70
98
  this.logWithLevel(LogLevel.INFO, message, data);
71
99
  }
72
- debug(message, data) {
100
+ debug(message, data, options) {
101
+ if (this.#suppressedAsRepeat(LogLevel.DEBUG, message, options))
102
+ return;
73
103
  this.logWithLevel(LogLevel.DEBUG, message, data);
74
104
  }
75
- perf(message, data) {
105
+ perf(message, data, options) {
106
+ if (this.#suppressedAsRepeat(LogLevel.PERF, message, options))
107
+ return;
76
108
  this.logWithLevel(LogLevel.PERF, `[PERF] ${message}`, data);
77
109
  }
78
110
  deprecated(message) {
@@ -86,16 +118,16 @@ export class BaseCrawleeLogger {
86
118
  * Users who want to use a different logging library should implement {@link BaseCrawleeLogger} directly.
87
119
  */
88
120
  export class ApifyLogAdapter extends BaseCrawleeLogger {
89
- apifyLog;
121
+ #apifyLog;
90
122
  constructor(apifyLog, options) {
91
123
  super(options ?? {});
92
- this.apifyLog = apifyLog;
124
+ this.#apifyLog = apifyLog;
93
125
  }
94
126
  logWithLevel(level, message, data) {
95
- this.apifyLog.internal(level, message, data);
127
+ this.#apifyLog.internal(level, message, data);
96
128
  }
97
129
  createChild(options) {
98
- return new ApifyLogAdapter(this.apifyLog.child({ prefix: options.prefix ?? null }), {
130
+ return new ApifyLogAdapter(this.#apifyLog.child({ prefix: options.prefix ?? null }), {
99
131
  ...this.getOptions(),
100
132
  ...options,
101
133
  });
@@ -1,8 +1,5 @@
1
1
  import type * as storage from '@crawlee/types';
2
2
  import type { CrawleeLogger } from '@crawlee/types';
3
- import { DatasetBackend } from './resource-clients/dataset.js';
4
- import { KeyValueStoreBackend } from './resource-clients/key-value-store.js';
5
- import { RequestQueueBackend } from './resource-clients/request-queue.js';
6
3
  export interface MemoryStorageOptions {
7
4
  /**
8
5
  * Optional logger for MemoryStorageBackend warnings.
@@ -10,32 +7,32 @@ export interface MemoryStorageOptions {
10
7
  logger?: CrawleeLogger;
11
8
  }
12
9
  export declare class MemoryStorageBackend implements storage.StorageBackend {
10
+ #private;
13
11
  readonly logger?: CrawleeLogger;
14
- /**
15
- * Unique per-instance cache partition key. Mirrors the way `FileSystemStorageBackend` partitions its
16
- * cache by storage directory: two distinct `MemoryStorageBackend` instances must not share cached backends.
17
- */
18
- private readonly instanceCacheKey;
19
- readonly keyValueStoreBackendCache: KeyValueStoreBackend[];
20
- readonly datasetBackendCache: DatasetBackend[];
21
- readonly requestQueueBackendCache: RequestQueueBackend[];
22
12
  constructor(options?: MemoryStorageOptions);
23
13
  /**
24
14
  * Return a per-instance unique cache key so that distinct `MemoryStorageBackend` instances get separate
25
15
  * cache partitions in the storage backend cache.
26
16
  */
27
17
  getStorageBackendCacheKey(): string;
28
- private static resolveStorageKey;
18
+ /**
19
+ * Evict a cached backend so that a dropped storage is no longer resolved by `createXBackend`,
20
+ * reported by `storageExists` or visited by `purge`. Returns whether the backend was cached, which
21
+ * tells the caller whether it still owns in-memory state worth clearing.
22
+ *
23
+ * The resource clients own their own entry's lifetime but must not reach into the caches directly.
24
+ * Because a client is only ever constructed by `createXBackend`, which caches it immediately, the
25
+ * entry matching `id` is always the caller itself.
26
+ * @internal
27
+ */
28
+ evictBackend(type: 'Dataset' | 'KeyValueStore' | 'RequestQueue', id: string): boolean;
29
29
  createDatasetBackend(options?: storage.StorageIdentifier): Promise<storage.DatasetBackend>;
30
30
  createKeyValueStoreBackend(options?: storage.StorageIdentifier): Promise<storage.KeyValueStoreBackend>;
31
- createRequestQueueBackend(options?: storage.StorageIdentifier): Promise<RequestQueueBackend>;
31
+ createRequestQueueBackend(options?: storage.StorageIdentifier): Promise<storage.RequestQueueBackend>;
32
32
  storageExists(id: string, type: 'Dataset' | 'KeyValueStore' | 'RequestQueue'): Promise<boolean>;
33
33
  /**
34
- * Cleans up the default storages before the run starts. For the in-memory storage this simply
35
- * resets the in-memory state of the cached default dataset, key-value store and request queue.
36
- *
37
- * As with `FileSystemStorageBackend`, the run's input (the `INPUT` key in the default key-value
38
- * store) is preserved — only the rest of the default storages is cleared.
34
+ * Cleans up the run-scoped storages before the run starts. For the in-memory storage this simply
35
+ * resets the in-memory state of the cached backends.
39
36
  */
40
37
  purge(): Promise<void>;
41
38
  /**
@@ -2,16 +2,18 @@ import { randomUUID } from 'node:crypto';
2
2
  import { DatasetBackend } from './resource-clients/dataset.js';
3
3
  import { KeyValueStoreBackend } from './resource-clients/key-value-store.js';
4
4
  import { RequestQueueBackend } from './resource-clients/request-queue.js';
5
+ /** The alias the default (unnamed) storage is opened under. */
6
+ const DEFAULT_STORAGE_ALIAS = '__default__';
5
7
  export class MemoryStorageBackend {
6
8
  logger;
7
9
  /**
8
10
  * Unique per-instance cache partition key. Mirrors the way `FileSystemStorageBackend` partitions its
9
11
  * cache by storage directory: two distinct `MemoryStorageBackend` instances must not share cached backends.
10
12
  */
11
- instanceCacheKey = `MemoryStorageBackend:${randomUUID()}`;
12
- keyValueStoreBackendCache = [];
13
- datasetBackendCache = [];
14
- requestQueueBackendCache = [];
13
+ #instanceCacheKey = `MemoryStorageBackend:${randomUUID()}`;
14
+ #keyValueStoreBackendCache = [];
15
+ #datasetBackendCache = [];
16
+ #requestQueueBackendCache = [];
15
17
  constructor(options = {}) {
16
18
  this.logger = options.logger;
17
19
  }
@@ -20,80 +22,108 @@ export class MemoryStorageBackend {
20
22
  * cache partitions in the storage backend cache.
21
23
  */
22
24
  getStorageBackendCacheKey() {
23
- return this.instanceCacheKey;
25
+ return this.#instanceCacheKey;
24
26
  }
25
- static resolveStorageKey(options) {
26
- const isAlias = 'alias' in options && !!options.alias;
27
- const rawKey = isAlias ? options.alias : (options.name ?? options.id);
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
+ }
57
+ static #resolveStorageKey(options) {
58
+ // No identifier at all means the default storage, which is opened under the reserved alias —
59
+ // same rule as `resolveStorageIdentifier` in the storage frontends, so that a backend used
60
+ // directly lands on the very storage the frontends would have opened.
61
+ const alias = options.alias || (!options.id && !options.name ? DEFAULT_STORAGE_ALIAS : undefined);
62
+ // `alias` covers the identifier-less case, so one of the three is always set.
63
+ const rawKey = alias ?? options.name ?? options.id;
28
64
  // Normalize the internal __default__ alias to the user-facing 'default' name.
29
- const cacheKey = rawKey === '__default__' ? 'default' : rawKey;
30
- return { isAlias, cacheKey };
65
+ const cacheKey = rawKey === DEFAULT_STORAGE_ALIAS ? 'default' : rawKey;
66
+ return { isAlias: alias !== undefined, cacheKey };
31
67
  }
32
68
  async createDatasetBackend(options = {}) {
33
- const { isAlias, cacheKey } = MemoryStorageBackend.resolveStorageKey(options);
34
- if (cacheKey) {
35
- const found = this.datasetBackendCache.find((store) => store.id === cacheKey ||
36
- store.name?.toLowerCase() === cacheKey.toLowerCase() ||
37
- store.cacheKey.toLowerCase() === cacheKey.toLowerCase());
38
- if (found) {
39
- return found;
40
- }
69
+ const { isAlias, cacheKey } = MemoryStorageBackend.#resolveStorageKey(options);
70
+ const found = this.#datasetBackendCache.find((store) => store.id === cacheKey ||
71
+ store.name?.toLowerCase() === cacheKey.toLowerCase() ||
72
+ store.cacheKey.toLowerCase() === cacheKey.toLowerCase());
73
+ if (found) {
74
+ return found;
41
75
  }
42
76
  const newStore = new DatasetBackend({
43
77
  name: isAlias ? undefined : cacheKey,
44
78
  cacheKey,
45
79
  storageBackend: this,
46
80
  });
47
- this.datasetBackendCache.push(newStore);
81
+ this.#datasetBackendCache.push(newStore);
48
82
  return newStore;
49
83
  }
50
84
  async createKeyValueStoreBackend(options = {}) {
51
- const { isAlias, cacheKey } = MemoryStorageBackend.resolveStorageKey(options);
52
- if (cacheKey) {
53
- const found = this.keyValueStoreBackendCache.find((store) => store.id === cacheKey ||
54
- store.name?.toLowerCase() === cacheKey.toLowerCase() ||
55
- store.cacheKey.toLowerCase() === cacheKey.toLowerCase());
56
- if (found) {
57
- return found;
58
- }
85
+ const { isAlias, cacheKey } = MemoryStorageBackend.#resolveStorageKey(options);
86
+ const found = this.#keyValueStoreBackendCache.find((store) => store.id === cacheKey ||
87
+ store.name?.toLowerCase() === cacheKey.toLowerCase() ||
88
+ store.cacheKey.toLowerCase() === cacheKey.toLowerCase());
89
+ if (found) {
90
+ return found;
59
91
  }
60
92
  const newStore = new KeyValueStoreBackend({
61
93
  name: isAlias ? undefined : cacheKey,
62
94
  cacheKey,
63
95
  storageBackend: this,
64
96
  });
65
- this.keyValueStoreBackendCache.push(newStore);
97
+ this.#keyValueStoreBackendCache.push(newStore);
66
98
  return newStore;
67
99
  }
68
100
  async createRequestQueueBackend(options = {}) {
69
- const { isAlias, cacheKey } = MemoryStorageBackend.resolveStorageKey(options);
70
- if (cacheKey) {
71
- const found = this.requestQueueBackendCache.find((queue) => queue.id === cacheKey ||
72
- queue.name?.toLowerCase() === cacheKey.toLowerCase() ||
73
- queue.cacheKey.toLowerCase() === cacheKey.toLowerCase());
74
- if (found) {
75
- return found;
76
- }
101
+ const { isAlias, cacheKey } = MemoryStorageBackend.#resolveStorageKey(options);
102
+ const found = this.#requestQueueBackendCache.find((queue) => queue.id === cacheKey ||
103
+ queue.name?.toLowerCase() === cacheKey.toLowerCase() ||
104
+ queue.cacheKey.toLowerCase() === cacheKey.toLowerCase());
105
+ if (found) {
106
+ return found;
77
107
  }
78
108
  const newStore = new RequestQueueBackend({
79
109
  name: isAlias ? undefined : cacheKey,
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;
@@ -102,28 +132,20 @@ export class MemoryStorageBackend {
102
132
  return backends.some((store) => store.id === id);
103
133
  }
104
134
  /**
105
- * Cleans up the default storages before the run starts. For the in-memory storage this simply
106
- * resets the in-memory state of the cached default dataset, key-value store and request queue.
107
- *
108
- * As with `FileSystemStorageBackend`, the run's input (the `INPUT` key in the default key-value
109
- * store) is preserved — only the rest of the default storages is cleared.
135
+ * Cleans up the run-scoped storages before the run starts. For the in-memory storage this simply
136
+ * resets the in-memory state of the cached backends.
110
137
  */
111
138
  async purge() {
112
- // The run default is opened via `{ alias: '__default__' }`, which `resolveStorageKey`
113
- // normalizes to `cacheKey === 'default'` (with `name === undefined`) — that is the clause
114
- // that actually matches it. The `name === 'default'` clause additionally covers a store a user
115
- // explicitly opened via `{ name: 'default' }`. (`'__default__'` never reaches `cacheKey`,
116
- // as it is always normalized to `'default'` first, so it does not need to be checked here.)
117
- const isDefault = (store) => store.name === 'default' || store.cacheKey === 'default';
118
- const purgeDefaults = async (cache, purgeStore) => {
119
- await Promise.all(cache.filter(isDefault).map(async (store) => purgeStore(store)));
139
+ // `#resolveStorageKey` leaves `name` unset for the default and alias-keyed storages, which is what
140
+ // marks them as run-scoped. `'default'` is the exception — it collapses onto the default storage.
141
+ const isRunScoped = (store) => store.name === undefined || store.name === 'default';
142
+ const purgeRunScoped = async (cache, purgeStore) => {
143
+ await Promise.all(cache.filter(isRunScoped).map(async (store) => purgeStore(store)));
120
144
  };
121
145
  await Promise.all([
122
- // Preserve the run input (INPUT) when purging the default key-value store, matching
123
- // `FileSystemStorageBackend`.
124
- purgeDefaults(this.keyValueStoreBackendCache, async (store) => store.purgeExceptInput()),
125
- purgeDefaults(this.datasetBackendCache, async (store) => store.purge()),
126
- purgeDefaults(this.requestQueueBackendCache, async (store) => store.purge()),
146
+ purgeRunScoped(this.#keyValueStoreBackendCache, async (store) => store.purge()),
147
+ purgeRunScoped(this.#datasetBackendCache, async (store) => store.purge()),
148
+ purgeRunScoped(this.#requestQueueBackendCache, async (store) => store.purge()),
127
149
  ]);
128
150
  }
129
151
  /**
@@ -14,6 +14,7 @@ export interface DatasetBackendOptions {
14
14
  storageBackend: MemoryStorageBackend;
15
15
  }
16
16
  export declare class DatasetBackend<Data extends Dictionary = Dictionary> extends BaseClient implements storage.DatasetBackend<Data> {
17
+ #private;
17
18
  name?: string;
18
19
  /**
19
20
  * The key used for cache lookup. For named storages, this equals the name. For alias (unnamed)
@@ -24,17 +25,11 @@ export declare class DatasetBackend<Data extends Dictionary = Dictionary> extend
24
25
  accessedAt: Date;
25
26
  modifiedAt: Date;
26
27
  itemCount: number;
27
- private readonly datasetEntries;
28
- private readonly storageBackend;
29
28
  constructor(options: DatasetBackendOptions);
30
29
  getMetadata(): Promise<storage.DatasetInfo>;
31
30
  drop(): Promise<void>;
32
31
  purge(): Promise<void>;
33
32
  getData(options?: storage.DatasetBackendListOptions): Promise<storage.PaginatedList<Data>>;
34
- private getDataPage;
35
33
  pushData(items: Data[]): Promise<void>;
36
34
  toDatasetInfo(): storage.DatasetInfo;
37
- private generateLocalEntryName;
38
- private getStartAndEndIndexes;
39
- private updateTimestamps;
40
35
  }