@crawlee/fs-storage 4.0.0-beta.197 → 4.0.0-beta.198

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.
@@ -1,5 +1,16 @@
1
1
  import type * as storage from '@crawlee/types';
2
2
  import type { CrawleeLogger } from '@crawlee/types';
3
+ import type { AdoptionCandidate } from '@crawlee/fs-storage-native';
4
+ /** What the key-value store hooks of {@link FileSystemStorageBackend} learn about the store at hand. */
5
+ export interface KeyValueStoreHookOptions {
6
+ /** Whether the store is the run's default key-value store. */
7
+ isDefaultStore: boolean;
8
+ }
9
+ /** A file-system key-value store backend as seen by {@link FileSystemStorageBackend.purgeKeyValueStore}. */
10
+ export interface PurgeableKeyValueStoreBackend extends storage.KeyValueStoreBackend {
11
+ /** Remove every record from the store except the given keys. */
12
+ purgeExcept(keys: string[]): Promise<void>;
13
+ }
3
14
  export interface FileSystemStorageOptions {
4
15
  /**
5
16
  * Path to directory where the data will be saved.
@@ -25,18 +36,6 @@ export interface FileSystemStorageOptions {
25
36
  * @default 'single'
26
37
  */
27
38
  requestQueueAccess?: 'single' | 'shared';
28
- /**
29
- * The key the run input is read from — Crawlee's `inputKey` (`CRAWLEE_INPUT_KEY`).
30
- *
31
- * Like the conventional `INPUT`, this key may arrive in the default key-value store as a bare value
32
- * file with no metadata sidecar (e.g. the Apify CLI writes the effective input to `__CLI_INPUT.json`
33
- * and points the run at that key). Such a file is adopted into a record under this key when the
34
- * store is opened, and the key is preserved when the default store is purged — exactly like
35
- * `INPUT`, which is always treated as an input key regardless of this setting.
36
- *
37
- * @default 'INPUT'
38
- */
39
- inputKey?: string;
40
39
  }
41
40
  /**
42
41
  * A file-system storage backend backed by the native `@crawlee/fs-storage-native` Rust extension.
@@ -64,6 +63,26 @@ export declare class FileSystemStorageBackend implements storage.StorageBackend
64
63
  createDatasetBackend(options?: storage.StorageIdentifier): Promise<storage.DatasetBackend>;
65
64
  createKeyValueStoreBackend(options?: storage.StorageIdentifier): Promise<storage.KeyValueStoreBackend>;
66
65
  createRequestQueueBackend(options?: storage.StorageIdentifier): Promise<storage.RequestQueueBackend>;
66
+ /**
67
+ * What the native `open` may turn into records: value files sitting in the store directory with no
68
+ * metadata sidecar, written out-of-band by a CLI, a project template, a v3 Crawlee or a text editor.
69
+ * Each becomes a record keyed by its filename, with the content type derived from the extension alone.
70
+ *
71
+ * Subclasses may claim specific files under a key of their own by prepending candidates — the Apify
72
+ * SDK adopts a bare `INPUT` or `INPUT.json` in the default store as the record `INPUT` this way.
73
+ *
74
+ * Once adopted, the file is an ordinary record: readable, listed, deletable, and — in a run-scoped
75
+ * store — purged on start unless {@link purgeKeyValueStore} spares it. Files adoption skips (dotfiles)
76
+ * are purged all the same: the purge sweeps the directory and keeps only the store metadata and the
77
+ * spared keys.
78
+ *
79
+ */
80
+ protected keyValueStoreAdoptionCandidates(_options: KeyValueStoreHookOptions): AdoptionCandidate[];
81
+ /**
82
+ * Empties one run-scoped key-value store during {@link purge}. Subclasses may spare some keys of the
83
+ * default store with {@link PurgeableKeyValueStoreBackend.purgeExcept} — the Apify SDK keeps its run input.
84
+ */
85
+ protected purgeKeyValueStore(store: PurgeableKeyValueStoreBackend, _options: KeyValueStoreHookOptions): Promise<void>;
67
86
  storageExists(id: string, type: 'Dataset' | 'KeyValueStore' | 'RequestQueue'): Promise<boolean>;
68
87
  /**
69
88
  * Cleans up the run-scoped storages before the run starts, sweeping the storage directories so that
@@ -8,7 +8,6 @@ import { RequestQueueBackend } from './resource-clients/request-queue.js';
8
8
  const fileSystemStorageOptionsSchema = z.object({
9
9
  localDataDirectory: z.string(),
10
10
  requestQueueAccess: z.enum(['single', 'shared']).default('single'),
11
- inputKey: z.string().min(1).default('INPUT'),
12
11
  logger: schemas.logger.optional(),
13
12
  });
14
13
  // The native package throws at load time on platforms without a published binary (e.g.
@@ -24,8 +23,6 @@ async function importNativeModule() {
24
23
  const DEFAULT_STORAGE_ALIAS = '__default__';
25
24
  /** The directory the default storage lives in, one level below `datasets` / `key_value_stores` / etc. */
26
25
  const DEFAULT_STORAGE_DIRECTORY = 'default';
27
- /** The conventional run-input key, always treated as one alongside the configured `inputKey`. */
28
- const DEFAULT_INPUT_KEY = 'INPUT';
29
26
  /**
30
27
  * Content types declared for adopted files. The native client infers nothing from extensions, and
31
28
  * neither do we beyond this: a `.json` file is JSON, anything else is bytes for the
@@ -48,16 +45,13 @@ export class FileSystemStorageBackend {
48
45
  requestQueuesDirectory;
49
46
  logger;
50
47
  requestQueueAccess;
51
- /** `INPUT` plus the configured `inputKey`, deduplicated. */
52
- #inputKeys;
53
48
  #keyValueStoreBackendCache = [];
54
49
  #datasetBackendCache = [];
55
50
  #requestQueueBackendCache = [];
56
51
  constructor(options) {
57
- const { logger, requestQueueAccess, inputKey, localDataDirectory } = parseArgument(options, fileSystemStorageOptionsSchema);
52
+ const { logger, requestQueueAccess, localDataDirectory } = parseArgument(options, fileSystemStorageOptionsSchema);
58
53
  this.logger = logger;
59
54
  this.requestQueueAccess = requestQueueAccess;
60
- this.#inputKeys = [...new Set([DEFAULT_INPUT_KEY, inputKey])];
61
55
  this.localDataDirectory = localDataDirectory;
62
56
  this.datasetsDirectory = resolve(this.localDataDirectory, 'datasets');
63
57
  this.keyValueStoresDirectory = resolve(this.localDataDirectory, 'key_value_stores');
@@ -112,13 +106,12 @@ export class FileSystemStorageBackend {
112
106
  }
113
107
  const nativeBackend = await (await importNativeModule()).FileSystemKeyValueStoreClient.open(id, name, alias, this.localDataDirectory,
114
108
  // useTestClock — always real wall-clock outside of native tests.
115
- undefined, this.#adoptionCandidates(cacheKey === DEFAULT_STORAGE_DIRECTORY));
109
+ undefined, this.keyValueStoreAdoptionCandidates({ isDefaultStore: cacheKey === DEFAULT_STORAGE_DIRECTORY }));
116
110
  const newStore = await KeyValueStoreBackend.create({
117
111
  name: alias ? undefined : (name ?? cacheKey),
118
112
  cacheKey,
119
113
  nativeBackend,
120
114
  logger: this.logger,
121
- inputKeys: this.#inputKeys,
122
115
  });
123
116
  this.#keyValueStoreBackendCache.push(newStore);
124
117
  return newStore;
@@ -146,27 +139,19 @@ export class FileSystemStorageBackend {
146
139
  /**
147
140
  * What the native `open` may turn into records: value files sitting in the store directory with no
148
141
  * metadata sidecar, written out-of-band by a CLI, a project template, a v3 Crawlee or a text editor.
142
+ * Each becomes a record keyed by its filename, with the content type derived from the extension alone.
149
143
  *
150
- * Each run-input key claims a bare `<key>` or `<key>.json` first — that is the layout the Apify CLI
151
- * and the templates produce, and the key must end up being `INPUT` rather than `INPUT.json`. Only
152
- * the default store holds the run input, so elsewhere an `INPUT.json` is just a file named
153
- * `INPUT.json`, adopted by the trailing sweep like every other one.
144
+ * Subclasses may claim specific files under a key of their own by prepending candidates — the Apify
145
+ * SDK adopts a bare `INPUT` or `INPUT.json` in the default store as the record `INPUT` this way.
154
146
  *
155
147
  * Once adopted, the file is an ordinary record: readable, listed, deletable, and — in a run-scoped
156
- * store — purged on start unless its key is a run-input key.
148
+ * store — purged on start unless {@link purgeKeyValueStore} spares it. Files adoption skips (dotfiles)
149
+ * are purged all the same: the purge sweeps the directory and keeps only the store metadata and the
150
+ * spared keys.
151
+ *
157
152
  */
158
- #adoptionCandidates(isDefaultStore) {
159
- const inputCandidates = isDefaultStore
160
- ? this.#inputKeys.map((key) => ({
161
- key,
162
- files: [
163
- { filename: key, contentType: ADOPTED_BINARY_CONTENT_TYPE },
164
- { filename: `${key}.json`, contentType: ADOPTED_JSON_CONTENT_TYPE },
165
- ],
166
- }))
167
- : [];
153
+ keyValueStoreAdoptionCandidates(_options) {
168
154
  return [
169
- ...inputCandidates,
170
155
  {
171
156
  files: [
172
157
  { filename: '*.json', contentType: ADOPTED_JSON_CONTENT_TYPE },
@@ -175,6 +160,13 @@ export class FileSystemStorageBackend {
175
160
  },
176
161
  ];
177
162
  }
163
+ /**
164
+ * Empties one run-scoped key-value store during {@link purge}. Subclasses may spare some keys of the
165
+ * default store with {@link PurgeableKeyValueStoreBackend.purgeExcept} — the Apify SDK keeps its run input.
166
+ */
167
+ async purgeKeyValueStore(store, _options) {
168
+ await store.purge();
169
+ }
178
170
  async storageExists(id, type) {
179
171
  let backends;
180
172
  let baseDir;
@@ -262,9 +254,7 @@ export class FileSystemStorageBackend {
262
254
  */
263
255
  async purge() {
264
256
  await Promise.all([
265
- this.#purgeRunScopedStorages(this.keyValueStoresDirectory, async (alias) => this.createKeyValueStoreBackend({ alias }),
266
- // Only the default store holds the run input, so it is the only one that keeps `INPUT`.
267
- async (store, isDefault) => (isDefault ? store.purgeExceptInput() : store.purge())),
257
+ this.#purgeRunScopedStorages(this.keyValueStoresDirectory, async (alias) => this.createKeyValueStoreBackend({ alias }), async (store, isDefault) => this.purgeKeyValueStore(store, { isDefaultStore: isDefault })),
268
258
  this.#purgeRunScopedStorages(this.datasetsDirectory, async (alias) => this.createDatasetBackend({ alias }), async (store) => store.purge()),
269
259
  this.#purgeRunScopedStorages(this.requestQueuesDirectory, async (alias) => this.createRequestQueueBackend({ alias }), async (store) => store.purge()),
270
260
  ]);
package/index.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export * from './file-system-storage.js';
2
+ export type { AdoptionCandidate } from '@crawlee/fs-storage-native';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/fs-storage",
3
- "version": "4.0.0-beta.197",
3
+ "version": "4.0.0-beta.198",
4
4
  "description": "A file-system storage implementation of the Apify API",
5
5
  "engines": {
6
6
  "node": ">=22.0.0"
@@ -43,8 +43,8 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@crawlee/fs-storage-native": ">=0.2.2 <0.3",
46
- "@crawlee/types": "4.0.0-beta.197",
47
- "@crawlee/utils": "4.0.0-beta.197",
46
+ "@crawlee/types": "4.0.0-beta.198",
47
+ "@crawlee/utils": "4.0.0-beta.198",
48
48
  "zod": "^4.5.4"
49
49
  },
50
50
  "lerna": {
@@ -54,5 +54,5 @@
54
54
  }
55
55
  }
56
56
  },
57
- "gitHead": "6dfbf74898b378341626fbe0c5ff7655b57ac305"
57
+ "gitHead": "889cfffcc20b2803f0d95d7cb006ad1e7f672452"
58
58
  }
@@ -12,11 +12,6 @@ export interface KeyValueStoreBackendOptions {
12
12
  cacheKey: string;
13
13
  nativeBackend: NativeFileSystemKeyValueStoreBackend;
14
14
  logger?: CrawleeLogger;
15
- /**
16
- * The run-input keys of this store, deduplicated — `INPUT` plus `FileSystemStorageOptions.inputKey`.
17
- * All that is left of the run-input special case: the keys {@link purgeExceptInput} spares.
18
- */
19
- inputKeys: string[];
20
15
  }
21
16
  /**
22
17
  * A file-system key-value store backend backed by the native `@crawlee/fs-storage-native` Rust
@@ -37,11 +32,10 @@ export declare class KeyValueStoreBackend extends CachedIdClient implements stor
37
32
  drop(): Promise<void>;
38
33
  purge(): Promise<void>;
39
34
  /**
40
- * Remove every record from the store except the run input. Used by
41
- * {@link FileSystemStorageBackend.purge} to clean the default key-value store at the start of a run
42
- * while preserving the run's input.
35
+ * Remove every record from the store except the given keys. For {@link FileSystemStorageBackend}
36
+ * subclasses that keep some of the default store's records across the purge on start.
43
37
  */
44
- purgeExceptInput(): Promise<void>;
38
+ purgeExcept(keys: string[]): Promise<void>;
45
39
  listKeys(options?: storage.KeyValueStoreListKeysOptions): Promise<storage.KeyValueStoreListKeysResult>;
46
40
  /**
47
41
  * Generates a public `file://` URL for accessing a specific record in the key-value store.
@@ -29,14 +29,11 @@ export class KeyValueStoreBackend extends CachedIdClient {
29
29
  name;
30
30
  cacheKey;
31
31
  #nativeBackend;
32
- /** See {@link KeyValueStoreBackendOptions.inputKeys}. */
33
- #inputKeys;
34
32
  constructor(options) {
35
33
  super();
36
34
  this.name = options.name;
37
35
  this.cacheKey = options.cacheKey;
38
36
  this.#nativeBackend = options.nativeBackend;
39
- this.#inputKeys = options.inputKeys;
40
37
  }
41
38
  get keyValueStoreDirectory() {
42
39
  return this.#nativeBackend.pathToKvs;
@@ -56,12 +53,11 @@ export class KeyValueStoreBackend extends CachedIdClient {
56
53
  await this.#nativeBackend.purge();
57
54
  }
58
55
  /**
59
- * Remove every record from the store except the run input. Used by
60
- * {@link FileSystemStorageBackend.purge} to clean the default key-value store at the start of a run
61
- * while preserving the run's input.
56
+ * Remove every record from the store except the given keys. For {@link FileSystemStorageBackend}
57
+ * subclasses that keep some of the default store's records across the purge on start.
62
58
  */
63
- async purgeExceptInput() {
64
- await this.#nativeBackend.purge(this.#inputKeys);
59
+ async purgeExcept(keys) {
60
+ await this.#nativeBackend.purge(keys);
65
61
  }
66
62
  async listKeys(options = {}) {
67
63
  const { prefix, exclusiveStartKey, limit } = parseArgument(options, schemas.keyValueStoreListKeysOptions);