@crawlee/fs-storage 4.0.0-beta.67

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.
@@ -0,0 +1,76 @@
1
+ import { s } from '@sapphire/shapeshift';
2
+ import { CachedIdClient } from './cached-id-client.js';
3
+ /**
4
+ * `getData` options accepted by the high-level `Dataset` frontend but not supported by the native
5
+ * file-system backend (it can only paginate raw items by `offset`/`limit`/`desc`). They are silently
6
+ * ignored, so we warn once if a caller passes any of them.
7
+ *
8
+ * Implementing these in the native client is tracked in
9
+ * https://github.com/apify/crawlee-storage/issues/8.
10
+ */
11
+ const UNSUPPORTED_GET_DATA_OPTIONS = ['clean', 'fields', 'omit', 'skipHidden', 'skipEmpty'];
12
+ /**
13
+ * A file-system dataset backend backed by the native `@crawlee/fs-storage-native` Rust extension.
14
+ *
15
+ * This class is a thin adapter: it forwards each operation to the native client (which owns the
16
+ * on-disk format, timestamps and item counting) and converts results into the shapes expected by
17
+ * the `@crawlee/types` interfaces.
18
+ */
19
+ export class DatasetBackend extends CachedIdClient {
20
+ name;
21
+ cacheKey;
22
+ nativeBackend;
23
+ logger;
24
+ constructor(options) {
25
+ super();
26
+ this.name = options.name;
27
+ this.cacheKey = options.cacheKey;
28
+ this.nativeBackend = options.nativeBackend;
29
+ this.logger = options.logger;
30
+ }
31
+ get datasetDirectory() {
32
+ return this.nativeBackend.pathToDataset;
33
+ }
34
+ static async create(options) {
35
+ const backend = new DatasetBackend(options);
36
+ backend._cachedId = (await options.nativeBackend.getMetadata()).id;
37
+ return backend;
38
+ }
39
+ async getMetadata() {
40
+ return this.nativeBackend.getMetadata();
41
+ }
42
+ async drop() {
43
+ await this.nativeBackend.dropStorage();
44
+ }
45
+ async purge() {
46
+ await this.nativeBackend.purge();
47
+ }
48
+ async pushData(items) {
49
+ await this.nativeBackend.pushData(items);
50
+ }
51
+ async getData(options = {}) {
52
+ const passedOptions = options;
53
+ const ignored = UNSUPPORTED_GET_DATA_OPTIONS.filter((key) => passedOptions[key] !== undefined);
54
+ if (ignored.length > 0) {
55
+ this.logger?.warning?.(`getData() options [${ignored.join(', ')}] are not supported by the file-system dataset ` +
56
+ `and were ignored. Only "offset", "limit" and "desc" are honored.`);
57
+ }
58
+ const { desc, limit, offset } = s
59
+ .object({
60
+ desc: s.boolean().optional(),
61
+ limit: s.number().int().optional(),
62
+ offset: s.number().int().optional(),
63
+ })
64
+ .parse(options);
65
+ const page = await this.nativeBackend.getData(offset ?? 0, limit, desc ?? false, false);
66
+ return {
67
+ count: page.count,
68
+ desc: page.desc,
69
+ items: page.items,
70
+ limit: page.limit,
71
+ offset: page.offset,
72
+ total: page.total,
73
+ };
74
+ }
75
+ }
76
+ //# sourceMappingURL=dataset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataset.js","sourceRoot":"","sources":["../../src/resource-clients/dataset.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ import type * as storage from '@crawlee/types';
2
+ import type { CrawleeLogger } from '@crawlee/types';
3
+ import type { FileSystemKeyValueStoreClient as NativeFileSystemKeyValueStoreBackend } from '@crawlee/fs-storage-native';
4
+ import { CachedIdClient } from './cached-id-client.js';
5
+ export interface KeyValueStoreBackendOptions {
6
+ /** The user-facing storage name, or `undefined` for unnamed (alias / default) storages. */
7
+ name?: string;
8
+ /**
9
+ * The key used for cache lookup in {@link FileSystemStorageBackend}. For named storages this equals
10
+ * the name; for alias (unnamed) storages it is the alias string. Falls back to the storage id.
11
+ */
12
+ cacheKey: string;
13
+ nativeBackend: NativeFileSystemKeyValueStoreBackend;
14
+ logger?: CrawleeLogger;
15
+ }
16
+ /**
17
+ * A file-system key-value store backend backed by the native `@crawlee/fs-storage-native` Rust
18
+ * extension.
19
+ *
20
+ * This adapter is a plain byte transport: values are written and read verbatim as `Buffer`s with a
21
+ * content type carried alongside them. Serializing arbitrary values into bytes and parsing them back
22
+ * is the {@link KeyValueStore} frontend codec's job, not this backend's.
23
+ */
24
+ export declare class KeyValueStoreBackend extends CachedIdClient implements storage.KeyValueStoreBackend {
25
+ readonly name?: string;
26
+ readonly cacheKey: string;
27
+ private readonly nativeBackend;
28
+ constructor(options: KeyValueStoreBackendOptions);
29
+ get keyValueStoreDirectory(): string;
30
+ static create(options: KeyValueStoreBackendOptions): Promise<KeyValueStoreBackend>;
31
+ getMetadata(): Promise<storage.KeyValueStoreInfo>;
32
+ drop(): Promise<void>;
33
+ purge(): Promise<void>;
34
+ /**
35
+ * Remove every record from the store except the run input. Used by
36
+ * {@link FileSystemStorageBackend.purge} to clean the default key-value store at the start of a run
37
+ * while preserving the run's input, matching the historical file-system storage behavior.
38
+ *
39
+ * The native `purge` keep-list matches by exact key with no extension globbing, so we pass every
40
+ * filename the input might live under (`INPUT`, `INPUT.json`, `INPUT.txt`, `INPUT.bin`).
41
+ */
42
+ purgeExceptInput(): Promise<void>;
43
+ listKeys(options?: storage.KeyValueStoreListKeysOptions): Promise<storage.KeyValueStoreListKeysResult>;
44
+ /**
45
+ * Generates a public `file://` URL for accessing a specific record in the key-value store.
46
+ *
47
+ * Returns `undefined` if the record does not exist.
48
+ * @param key The key of the record to generate the public URL for.
49
+ */
50
+ getPublicUrl(key: string): Promise<string | undefined>;
51
+ /**
52
+ * Tests whether a record with the given key exists without retrieving its value.
53
+ *
54
+ * @param key The queried record key.
55
+ * @returns `true` if the record exists, `false` otherwise.
56
+ */
57
+ recordExists(key: string): Promise<boolean>;
58
+ getValue(key: string): Promise<storage.KeyValueStoreRecord | undefined>;
59
+ setValue(record: storage.KeyValueStoreInputRecord): Promise<void>;
60
+ deleteValue(key: string): Promise<void>;
61
+ /**
62
+ * Resolve `key` to the on-disk key that actually exists, or `undefined` if nothing does. Every
63
+ * key is checked against its tracked record; the run-input keys additionally fall back to
64
+ * out-of-band bare files, in which case the matched on-disk key is returned so callers like
65
+ * `getPublicUrl` point at the file that exists. Two run-input shapes are handled (see
66
+ * {@link bareFallbacksFor}): the logical `INPUT`, which probes the conventional extensions, and a
67
+ * literal bare filename such as `INPUT.json` as listed by `listKeys`, which resolves itself.
68
+ */
69
+ private resolveExistingKey;
70
+ /**
71
+ * The native `resolveValue`/`resolveExistingKey` bare-file fallbacks to use for `key`, or
72
+ * `undefined` if `key` is a plain tracked-record lookup with no bare-file probing.
73
+ *
74
+ * - The logical run-input key (`INPUT`) probes the full extension ladder (`INPUT`, `INPUT.json`,
75
+ * `INPUT.txt`, `INPUT.bin`), matching how Crawlee reads run input.
76
+ * - A literal bare filename as surfaced by `listKeys` (`INPUT.json`/`.txt`/`.bin`) resolves itself:
77
+ * the tracked record first, then the bare file at that exact name (a single empty-extension
78
+ * fallback), so a listed key round-trips through `getValue`/`recordExists`.
79
+ */
80
+ private bareFallbacksFor;
81
+ }
82
+ //# sourceMappingURL=key-value-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-value-store.d.ts","sourceRoot":"","sources":["../../src/resource-clients/key-value-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGpD,OAAO,KAAK,EACR,6BAA6B,IAAI,oCAAoC,EAExE,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAoDvD,MAAM,WAAW,2BAA2B;IACxC,2FAA2F;IAC3F,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,oCAAoC,CAAC;IACpD,MAAM,CAAC,EAAE,aAAa,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,qBAAa,oBAAqB,SAAQ,cAAe,YAAW,OAAO,CAAC,oBAAoB;IAC5F,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAuC;gBAEzD,OAAO,EAAE,2BAA2B;IAOhD,IAAI,sBAAsB,IAAI,MAAM,CAEnC;WAEY,MAAM,CAAC,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAMlF,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC;IAIjD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B;;;;;;;OAOG;IACG,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC,QAAQ,CAAC,OAAO,GAAE,OAAO,CAAC,4BAAiC,GAAG,OAAO,CAAC,OAAO,CAAC,2BAA2B,CAAC;IAuChH;;;;;OAKG;IACG,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAa5D;;;;;OAKG;IACG,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK3C,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAmBvE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IA4CjE,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK7C;;;;;;;OAOG;YACW,kBAAkB;IAahC;;;;;;;;;OASG;IAEH,OAAO,CAAC,gBAAgB;CAU3B"}
@@ -0,0 +1,240 @@
1
+ import { Readable } from 'node:stream';
2
+ import { s } from '@sapphire/shapeshift';
3
+ import { isStream } from '../utils.js';
4
+ import { CachedIdClient } from './cached-id-client.js';
5
+ /**
6
+ * Out-of-band ("bare") value-file fallbacks tried when the {@link ALLOWED_BARE_FILES} lookup misses the tracked
7
+ * record, so a lookup for `INPUT` also matches a hand-placed `INPUT.json`/`.txt`/`.bin`. Passed to the
8
+ * native `resolveValue`/`resolveExistingKey`, which do the probing and re-keying.
9
+ *
10
+ * Each entry declares the content type to report on a match — the native client does no MIME
11
+ * inference. An empty `contentType` is its sentinel for "keep the synthesized
12
+ * `application/octet-stream`", used for the extensionless key and `.bin`.
13
+ */
14
+ const BARE_FILE_FALLBACKS = [
15
+ { extension: '', contentType: '' },
16
+ { extension: '.json', contentType: 'application/json; charset=utf-8' },
17
+ { extension: '.txt', contentType: 'text/plain; charset=utf-8' },
18
+ { extension: '.bin', contentType: '' },
19
+ ];
20
+ const ALLOWED_BARE_FILES = ['INPUT'];
21
+ /**
22
+ * The out-of-band ("bare") files to surface from the native `listKeys`, derived from
23
+ * {@link ALLOWED_BARE_FILES} × {@link BARE_FILE_FALLBACKS}. Each native {@link ListBareFallback}
24
+ * `name` is the literal on-disk filename to probe (e.g. `INPUT.json`), and the native lists a match
25
+ * under that same `name` — which is exactly the key we return, so a listed bare file round-trips
26
+ * through `getValue`/`recordExists` (see {@link BARE_FILE_CONTENT_TYPES}).
27
+ */
28
+ const LIST_BARE_FALLBACKS = ALLOWED_BARE_FILES.flatMap((key) => BARE_FILE_FALLBACKS.map(({ extension, contentType }) => ({ name: `${key}${extension}`, contentType })));
29
+ /**
30
+ * Lookup from a bare file's literal on-disk name (e.g. `INPUT.json`) to the content type to report
31
+ * for it, used to read a listed bare key back directly (`getValue('INPUT.json')`). The empty-extension
32
+ * entry (`INPUT`) is intentionally excluded: an extensionless `INPUT` lookup goes through the
33
+ * `resolveValue` fallback probing instead, which already covers the extensionless file.
34
+ */
35
+ const BARE_FILE_CONTENT_TYPES = new Map(ALLOWED_BARE_FILES.flatMap((key) => BARE_FILE_FALLBACKS.filter(({ extension }) => extension !== '').map(({ extension, contentType }) => [`${key}${extension}`, contentType])));
36
+ /** Maps a bare file's on-disk name (e.g. `INPUT.json`) to its logical key (e.g. `INPUT`), for dedup. */
37
+ const BARE_FILE_LOGICAL_KEYS = new Map(ALLOWED_BARE_FILES.flatMap((key) => BARE_FILE_FALLBACKS.map(({ extension }) => [`${key}${extension}`, key])));
38
+ /**
39
+ * A file-system key-value store backend backed by the native `@crawlee/fs-storage-native` Rust
40
+ * extension.
41
+ *
42
+ * This adapter is a plain byte transport: values are written and read verbatim as `Buffer`s with a
43
+ * content type carried alongside them. Serializing arbitrary values into bytes and parsing them back
44
+ * is the {@link KeyValueStore} frontend codec's job, not this backend's.
45
+ */
46
+ export class KeyValueStoreBackend extends CachedIdClient {
47
+ name;
48
+ cacheKey;
49
+ nativeBackend;
50
+ constructor(options) {
51
+ super();
52
+ this.name = options.name;
53
+ this.cacheKey = options.cacheKey;
54
+ this.nativeBackend = options.nativeBackend;
55
+ }
56
+ get keyValueStoreDirectory() {
57
+ return this.nativeBackend.pathToKvs;
58
+ }
59
+ static async create(options) {
60
+ const backend = new KeyValueStoreBackend(options);
61
+ backend._cachedId = (await options.nativeBackend.getMetadata()).id;
62
+ return backend;
63
+ }
64
+ async getMetadata() {
65
+ return this.nativeBackend.getMetadata();
66
+ }
67
+ async drop() {
68
+ await this.nativeBackend.dropStorage();
69
+ }
70
+ async purge() {
71
+ await this.nativeBackend.purge();
72
+ }
73
+ /**
74
+ * Remove every record from the store except the run input. Used by
75
+ * {@link FileSystemStorageBackend.purge} to clean the default key-value store at the start of a run
76
+ * while preserving the run's input, matching the historical file-system storage behavior.
77
+ *
78
+ * The native `purge` keep-list matches by exact key with no extension globbing, so we pass every
79
+ * filename the input might live under (`INPUT`, `INPUT.json`, `INPUT.txt`, `INPUT.bin`).
80
+ */
81
+ async purgeExceptInput() {
82
+ await this.nativeBackend.purge(BARE_FILE_FALLBACKS.flatMap(({ extension }) => `INPUT${extension}`));
83
+ }
84
+ async listKeys(options = {}) {
85
+ const { prefix, exclusiveStartKey, limit } = s
86
+ .object({
87
+ prefix: s.string().optional(),
88
+ exclusiveStartKey: s.string().optional(),
89
+ limit: s.number().int().greaterThan(0).optional(),
90
+ })
91
+ .parse(options);
92
+ // Pass the bare-file fallbacks so out-of-band value files (e.g. a hand-placed `INPUT.json`)
93
+ // are enumerated alongside tracked records, under their actual on-disk name. The native reads
94
+ // everything it needs off the filesystem index — no per-file reads — so this stays cheap.
95
+ // The native `listKeys` already returns a self-describing page (items + pagination cursors)
96
+ // matching the `KeyValueStoreListKeysResult` contract, so we only post-process the items.
97
+ const page = await this.nativeBackend.listKeys(exclusiveStartKey, limit, prefix, LIST_BARE_FALLBACKS);
98
+ const presentKeys = new Set(page.items.map((record) => record.key));
99
+ // A bare value file is listed under its actual name (`INPUT.json`), which already round-trips
100
+ // through `getValue`/`recordExists`. The only collision is a tracked record occupying the
101
+ // logical key itself (`INPUT`): it shadows the extension-bearing bare variants (`INPUT.json`
102
+ // etc.) for the same logical key, so drop those. The extensionless bare file *is* the logical
103
+ // key, so it is never a separate duplicate.
104
+ const items = page.items.filter((record) => {
105
+ const logicalKey = BARE_FILE_LOGICAL_KEYS.get(record.key);
106
+ const isExtensionBearingBareFile = logicalKey !== undefined && logicalKey !== record.key;
107
+ return !(isExtensionBearingBareFile && presentKeys.has(logicalKey));
108
+ });
109
+ return {
110
+ items,
111
+ count: items.length,
112
+ limit: page.limit,
113
+ exclusiveStartKey: page.exclusiveStartKey,
114
+ isTruncated: page.isTruncated,
115
+ nextExclusiveStartKey: page.nextExclusiveStartKey,
116
+ };
117
+ }
118
+ /**
119
+ * Generates a public `file://` URL for accessing a specific record in the key-value store.
120
+ *
121
+ * Returns `undefined` if the record does not exist.
122
+ * @param key The key of the record to generate the public URL for.
123
+ */
124
+ async getPublicUrl(key) {
125
+ s.string().parse(key);
126
+ // The native `getPublicUrl` stats the encoded path but does not probe bare-file extensions,
127
+ // so we resolve the on-disk key first (handling e.g. `INPUT` -> `INPUT.json`) and normalize
128
+ // the native `null`-for-missing result to the historical `undefined` contract.
129
+ const resolvedKey = await this.resolveExistingKey(key);
130
+ if (resolvedKey === undefined) {
131
+ return undefined;
132
+ }
133
+ return (await this.nativeBackend.getPublicUrl(resolvedKey)) ?? undefined;
134
+ }
135
+ /**
136
+ * Tests whether a record with the given key exists without retrieving its value.
137
+ *
138
+ * @param key The queried record key.
139
+ * @returns `true` if the record exists, `false` otherwise.
140
+ */
141
+ async recordExists(key) {
142
+ s.string().parse(key);
143
+ return (await this.resolveExistingKey(key)) !== undefined;
144
+ }
145
+ async getValue(key) {
146
+ s.string().parse(key);
147
+ const fallbacks = this.bareFallbacksFor(key);
148
+ const record = fallbacks
149
+ ? await this.nativeBackend.resolveValue(key, fallbacks)
150
+ : await this.nativeBackend.getValue(key);
151
+ if (record) {
152
+ return {
153
+ key: record.key,
154
+ value: record.value,
155
+ contentType: record.contentType,
156
+ };
157
+ }
158
+ return undefined;
159
+ }
160
+ async setValue(record) {
161
+ // By the time a value reaches the backend the frontend (KeyValueStore codec) has already
162
+ // serialized it: non-bytes become a `string`, everything else is a `Buffer`/typed array or a
163
+ // stream. So we only accept those shapes here — there is no JSON inference or `String(value)`
164
+ // coercion left to do.
165
+ s.object({
166
+ key: s.string().lengthGreaterThan(0),
167
+ value: s.union([
168
+ s.string(),
169
+ s.instance(Buffer),
170
+ s.instance(ArrayBuffer),
171
+ s.typedArray(),
172
+ // A stream is an object; disabling validation makes shapeshift only check it is a
173
+ // non-null, non-array object (the stream guard below does the real check).
174
+ s.object({}).setValidationEnabled(false),
175
+ ]),
176
+ contentType: s.string().lengthGreaterThan(0).optional(),
177
+ }).parse(record);
178
+ const { key, value } = record;
179
+ // The frontend resolves the content type before it reaches the backend; this backend is a plain
180
+ // byte transport and does not infer content types.
181
+ const contentType = record.contentType ?? 'application/octet-stream';
182
+ // Stream the value straight to disk without buffering it all into memory. The native client
183
+ // consumes a Web `ReadableStream`, so convert the Node `Readable` we get from the frontend.
184
+ if (isStream(value)) {
185
+ const webStream = Readable.toWeb(value);
186
+ await this.nativeBackend.setValueStream(key, webStream, contentType);
187
+ return;
188
+ }
189
+ // Normalize the remaining (already-serialized) value into a Buffer for the native client.
190
+ const buffer = Buffer.isBuffer(value)
191
+ ? value
192
+ : value instanceof ArrayBuffer
193
+ ? Buffer.from(value)
194
+ : ArrayBuffer.isView(value)
195
+ ? Buffer.from(value.buffer, value.byteOffset, value.byteLength)
196
+ : Buffer.from(value);
197
+ await this.nativeBackend.setValue(key, buffer, contentType);
198
+ }
199
+ async deleteValue(key) {
200
+ s.string().parse(key);
201
+ await this.nativeBackend.deleteValue(key);
202
+ }
203
+ /**
204
+ * Resolve `key` to the on-disk key that actually exists, or `undefined` if nothing does. Every
205
+ * key is checked against its tracked record; the run-input keys additionally fall back to
206
+ * out-of-band bare files, in which case the matched on-disk key is returned so callers like
207
+ * `getPublicUrl` point at the file that exists. Two run-input shapes are handled (see
208
+ * {@link bareFallbacksFor}): the logical `INPUT`, which probes the conventional extensions, and a
209
+ * literal bare filename such as `INPUT.json` as listed by `listKeys`, which resolves itself.
210
+ */
211
+ async resolveExistingKey(key) {
212
+ const fallbacks = this.bareFallbacksFor(key);
213
+ if (fallbacks) {
214
+ return ((await this.nativeBackend.resolveExistingKey(key, fallbacks.map(({ extension }) => extension))) ?? undefined);
215
+ }
216
+ return (await this.nativeBackend.recordExists(key)) ? key : undefined;
217
+ }
218
+ /**
219
+ * The native `resolveValue`/`resolveExistingKey` bare-file fallbacks to use for `key`, or
220
+ * `undefined` if `key` is a plain tracked-record lookup with no bare-file probing.
221
+ *
222
+ * - The logical run-input key (`INPUT`) probes the full extension ladder (`INPUT`, `INPUT.json`,
223
+ * `INPUT.txt`, `INPUT.bin`), matching how Crawlee reads run input.
224
+ * - A literal bare filename as surfaced by `listKeys` (`INPUT.json`/`.txt`/`.bin`) resolves itself:
225
+ * the tracked record first, then the bare file at that exact name (a single empty-extension
226
+ * fallback), so a listed key round-trips through `getValue`/`recordExists`.
227
+ */
228
+ // eslint-disable-next-line class-methods-use-this
229
+ bareFallbacksFor(key) {
230
+ if (ALLOWED_BARE_FILES.includes(key)) {
231
+ return BARE_FILE_FALLBACKS;
232
+ }
233
+ const contentType = BARE_FILE_CONTENT_TYPES.get(key);
234
+ if (contentType !== undefined) {
235
+ return [{ extension: '', contentType }];
236
+ }
237
+ return undefined;
238
+ }
239
+ }
240
+ //# sourceMappingURL=key-value-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-value-store.js","sourceRoot":"","sources":["../../src/resource-clients/key-value-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import type * as storage from '@crawlee/types';
2
+ import type { CrawleeLogger } from '@crawlee/types';
3
+ import type { FileSystemRequestQueueClient as NativeFileSystemRequestQueueBackend } from '@crawlee/fs-storage-native';
4
+ import { CachedIdClient } from './cached-id-client.js';
5
+ export interface RequestQueueBackendOptions {
6
+ /** The user-facing storage name, or `undefined` for unnamed (alias / default) storages. */
7
+ name?: string;
8
+ /**
9
+ * The key used for cache lookup in {@link FileSystemStorageBackend}. For named storages this equals
10
+ * the name; for alias (unnamed) storages it is the alias string. Falls back to the storage id.
11
+ */
12
+ cacheKey: string;
13
+ nativeBackend: NativeFileSystemRequestQueueBackend;
14
+ logger?: CrawleeLogger;
15
+ }
16
+ /**
17
+ * A file-system request queue backend backed by the native `@crawlee/fs-storage-native` Rust
18
+ * extension.
19
+ *
20
+ * Request ordering, in-progress locking and state persistence are all owned by the native client.
21
+ * This adapter forwards each operation and converts result shapes to the `@crawlee/types` interfaces.
22
+ */
23
+ export declare class RequestQueueBackend extends CachedIdClient implements storage.RequestQueueBackend {
24
+ readonly name?: string;
25
+ readonly cacheKey: string;
26
+ private readonly nativeBackend;
27
+ constructor(options: RequestQueueBackendOptions);
28
+ get requestQueueDirectory(): string;
29
+ static create(options: RequestQueueBackendOptions): Promise<RequestQueueBackend>;
30
+ /**
31
+ * Tells the native client how long (in seconds) a fetched request stays locked before it becomes
32
+ * available again.
33
+ */
34
+ setExpectedRequestProcessingTimeSecs(secs: number): Promise<void>;
35
+ getMetadata(): Promise<storage.RequestQueueInfo>;
36
+ drop(): Promise<void>;
37
+ purge(): Promise<void>;
38
+ addBatchOfRequests(requests: storage.RequestSchema[], options?: storage.RequestQueueOperationOptions): Promise<storage.BatchAddRequestsResult>;
39
+ getRequest(uniqueKey: string): Promise<storage.UpdateRequestSchema | undefined>;
40
+ fetchNextRequest(): Promise<storage.UpdateRequestSchema | undefined>;
41
+ markRequestAsHandled(request: storage.UpdateRequestSchema): Promise<storage.QueueOperationInfo | undefined>;
42
+ reclaimRequest(request: storage.UpdateRequestSchema, options?: storage.RequestQueueOperationOptions): Promise<storage.QueueOperationInfo | undefined>;
43
+ isEmpty(): Promise<boolean>;
44
+ isFinished(): Promise<boolean>;
45
+ /**
46
+ * Persist the native client's in-memory state to disk. Called by
47
+ * {@link FileSystemStorageBackend.teardown} so that fetched-but-unhandled requests are not stuck
48
+ * for the next consumer of the same on-disk queue.
49
+ */
50
+ persistState(): Promise<void>;
51
+ }
52
+ //# sourceMappingURL=request-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-queue.d.ts","sourceRoot":"","sources":["../../src/resource-clients/request-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGpD,OAAO,KAAK,EAAE,4BAA4B,IAAI,mCAAmC,EAAE,MAAM,4BAA4B,CAAC;AAEtH,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAoCvD,MAAM,WAAW,0BAA0B;IACvC,2FAA2F;IAC3F,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,mCAAmC,CAAC;IACnD,MAAM,CAAC,EAAE,aAAa,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,qBAAa,mBAAoB,SAAQ,cAAe,YAAW,OAAO,CAAC,mBAAmB;IAC1F,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsC;gBAExD,OAAO,EAAE,0BAA0B;IAO/C,IAAI,qBAAqB,IAAI,MAAM,CAElC;WAEY,MAAM,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAMtF;;;OAGG;IACG,oCAAoC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjE,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC;IAIhD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,kBAAkB,CACpB,QAAQ,EAAE,OAAO,CAAC,aAAa,EAAE,EACjC,OAAO,GAAE,OAAO,CAAC,4BAAiC,GACnD,OAAO,CAAC,OAAO,CAAC,sBAAsB,CAAC;IAkBpC,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAQ/E,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAIpE,oBAAoB,CAAC,OAAO,EAAE,OAAO,CAAC,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC;IAK3G,cAAc,CAChB,OAAO,EAAE,OAAO,CAAC,mBAAmB,EACpC,OAAO,GAAE,OAAO,CAAC,4BAAiC,GACnD,OAAO,CAAC,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC;IAQ5C,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAI3B,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;IAIpC;;;;OAIG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;CAGtC"}
@@ -0,0 +1,120 @@
1
+ import { s } from '@sapphire/shapeshift';
2
+ import { CachedIdClient } from './cached-id-client.js';
3
+ /**
4
+ * Convert a request (either a Crawlee `Request` instance or a plain schema object) into a plain object
5
+ * whose properties are all enumerable.
6
+ *
7
+ * Crawlee's `Request` stores internal metadata (crawl depth, enqueue strategy, session id, ...) in a
8
+ * *non-enumerable* `userData.__crawlee` bag. The native `@crawlee/fs-storage-native` client reads
9
+ * request properties directly over the N-API boundary, which only exposes enumerable own properties
10
+ * and does not honor `toJSON`. Passing a `Request` straight through would therefore silently drop the
11
+ * `__crawlee` metadata, resetting `crawlDepth` to 0 on the next `fetchNextRequest` (breaking e.g.
12
+ * `maxCrawlDepth` and enqueue-strategy handling). Round-tripping through JSON invokes the request's
13
+ * `toJSON`, flattening everything into enumerable properties the native client can persist.
14
+ */
15
+ function plainifyRequest(request) {
16
+ return JSON.parse(JSON.stringify(request));
17
+ }
18
+ const requestShape = s
19
+ .object({
20
+ id: s.string(),
21
+ url: s.string().url({ allowedProtocols: ['http:', 'https:'] }),
22
+ uniqueKey: s.string(),
23
+ method: s.string().optional(),
24
+ retryCount: s.number().int().optional(),
25
+ handledAt: s.union([s.string(), s.date().valid()]).optional(),
26
+ })
27
+ .passthrough();
28
+ const requestShapeWithoutId = requestShape.omit(['id']);
29
+ const batchRequestShapeWithoutId = requestShapeWithoutId.array();
30
+ const requestOptionsShape = s.object({
31
+ forefront: s.boolean().optional(),
32
+ });
33
+ /**
34
+ * A file-system request queue backend backed by the native `@crawlee/fs-storage-native` Rust
35
+ * extension.
36
+ *
37
+ * Request ordering, in-progress locking and state persistence are all owned by the native client.
38
+ * This adapter forwards each operation and converts result shapes to the `@crawlee/types` interfaces.
39
+ */
40
+ export class RequestQueueBackend extends CachedIdClient {
41
+ name;
42
+ cacheKey;
43
+ nativeBackend;
44
+ constructor(options) {
45
+ super();
46
+ this.name = options.name;
47
+ this.cacheKey = options.cacheKey;
48
+ this.nativeBackend = options.nativeBackend;
49
+ }
50
+ get requestQueueDirectory() {
51
+ return this.nativeBackend.pathToRq;
52
+ }
53
+ static async create(options) {
54
+ const backend = new RequestQueueBackend(options);
55
+ backend._cachedId = (await options.nativeBackend.getMetadata()).id;
56
+ return backend;
57
+ }
58
+ /**
59
+ * Tells the native client how long (in seconds) a fetched request stays locked before it becomes
60
+ * available again.
61
+ */
62
+ async setExpectedRequestProcessingTimeSecs(secs) {
63
+ await this.nativeBackend.setExpectedRequestProcessingTime(secs);
64
+ }
65
+ async getMetadata() {
66
+ return this.nativeBackend.getMetadata();
67
+ }
68
+ async drop() {
69
+ await this.nativeBackend.dropStorage();
70
+ }
71
+ async purge() {
72
+ await this.nativeBackend.purge();
73
+ }
74
+ async addBatchOfRequests(requests, options = {}) {
75
+ batchRequestShapeWithoutId.parse(requests);
76
+ requestOptionsShape.parse(options);
77
+ const response = await this.nativeBackend.addBatchOfRequests(requests.map((request) => plainifyRequest(request)), options.forefront ?? false);
78
+ // `processedRequests` is structurally identical between the native and `storage` types, so it
79
+ // passes through unchanged. `unprocessedRequests` only differs in that the native `method` is
80
+ // a plain `string`, hence the cast to the narrower `AllowedHttpMethods` union.
81
+ return {
82
+ processedRequests: response.processedRequests,
83
+ unprocessedRequests: response.unprocessedRequests,
84
+ };
85
+ }
86
+ async getRequest(uniqueKey) {
87
+ s.string().parse(uniqueKey);
88
+ // The native client tags requests with an internal `orderNo`; it's harmless to leak, so we
89
+ // hand the request back as-is rather than copying it just to drop one undeclared property.
90
+ // The native client already returns `undefined` for a missing request, matching this contract.
91
+ return (await this.nativeBackend.getRequest(uniqueKey));
92
+ }
93
+ async fetchNextRequest() {
94
+ return (await this.nativeBackend.fetchNextRequest());
95
+ }
96
+ async markRequestAsHandled(request) {
97
+ requestShape.parse(request);
98
+ return (await this.nativeBackend.markRequestAsHandled(plainifyRequest(request))) ?? undefined;
99
+ }
100
+ async reclaimRequest(request, options = {}) {
101
+ requestShape.parse(request);
102
+ requestOptionsShape.parse(options);
103
+ return ((await this.nativeBackend.reclaimRequest(plainifyRequest(request), options.forefront ?? false)) ?? undefined);
104
+ }
105
+ async isEmpty() {
106
+ return this.nativeBackend.isEmpty();
107
+ }
108
+ async isFinished() {
109
+ return this.nativeBackend.isFinished();
110
+ }
111
+ /**
112
+ * Persist the native client's in-memory state to disk. Called by
113
+ * {@link FileSystemStorageBackend.teardown} so that fetched-but-unhandled requests are not stuck
114
+ * for the next consumer of the same on-disk queue.
115
+ */
116
+ async persistState() {
117
+ await this.nativeBackend.persistState();
118
+ }
119
+ }
120
+ //# sourceMappingURL=request-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-queue.js","sourceRoot":"","sources":["../../src/resource-clients/request-queue.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,sBAAsB,CAAC;AAIzC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD;;;;;;;;;;;GAWG;AACH,SAAS,eAAe,CAAC,OAAgB;IACrC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAA4B,CAAC;AAC1E,CAAC;AAED,MAAM,YAAY,GAAG,CAAC;KACjB,MAAM,CAAC;IACJ,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,gBAAgB,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,CAAC;IAC9D,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACvC,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE;CAChE,CAAC;KACD,WAAW,EAAE,CAAC;AAEnB,MAAM,qBAAqB,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACxD,MAAM,0BAA0B,GAAG,qBAAqB,CAAC,KAAK,EAAE,CAAC;AAEjE,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IACjC,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;CACpC,CAAC,CAAC;AAcH;;;;;;GAMG;AACH,MAAM,OAAO,mBAAoB,SAAQ,cAAc;IAC1C,IAAI,CAAU;IACd,QAAQ,CAAS;IAET,aAAa,CAAsC;IAEpE,YAAY,OAAmC;QAC3C,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QACzB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;IAC/C,CAAC;IAED,IAAI,qBAAqB;QACrB,OAAO,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC;IACvC,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAmC;QACnD,MAAM,OAAO,GAAG,IAAI,mBAAmB,CAAC,OAAO,CAAC,CAAC;QACjD,OAAO,CAAC,SAAS,GAAG,CAAC,MAAM,OAAO,CAAC,aAAa,CAAC,WAAW,EAAE,CAAC,CAAC,EAAE,CAAC;QACnE,OAAO,OAAO,CAAC;IACnB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,oCAAoC,CAAC,IAAY;QACnD,MAAM,IAAI,CAAC,aAAa,CAAC,gCAAgC,CAAC,IAAI,CAAC,CAAC;IACpE,CAAC;IAED,KAAK,CAAC,WAAW;QACb,OAAO,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,IAAI;QACN,MAAM,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,KAAK;QACP,MAAM,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,kBAAkB,CACpB,QAAiC,EACjC,UAAgD,EAAE;QAElD,0BAA0B,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC3C,mBAAmB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAEnC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,kBAAkB,CACxD,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,EACnD,OAAO,CAAC,SAAS,IAAI,KAAK,CAC7B,CAAC;QAEF,8FAA8F;QAC9F,8FAA8F;QAC9F,+EAA+E;QAC/E,OAAO;YACH,iBAAiB,EAAE,QAAQ,CAAC,iBAAiB;YAC7C,mBAAmB,EAAE,QAAQ,CAAC,mBAA4E;SAC7G,CAAC;IACN,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,SAAiB;QAC9B,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC5B,2FAA2F;QAC3F,2FAA2F;QAC3F,+FAA+F;QAC/F,OAAO,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,SAAS,CAAC,CAA4C,CAAC;IACvG,CAAC;IAED,KAAK,CAAC,gBAAgB;QAClB,OAAO,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC,gBAAgB,EAAE,CAA4C,CAAC;IACpG,CAAC;IAED,KAAK,CAAC,oBAAoB,CAAC,OAAoC;QAC3D,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5B,OAAO,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC,oBAAoB,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC;IAClG,CAAC;IAED,KAAK,CAAC,cAAc,CAChB,OAAoC,EACpC,UAAgD,EAAE;QAElD,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5B,mBAAmB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACnC,OAAO,CACH,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC,cAAc,CAAC,eAAe,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,SAAS,IAAI,KAAK,CAAC,CAAC,IAAI,SAAS,CAC/G,CAAC;IACN,CAAC;IAED,KAAK,CAAC,OAAO;QACT,OAAO,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,UAAU;QACZ,OAAO,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,CAAC;IAC3C,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,YAAY;QACd,MAAM,IAAI,CAAC,aAAa,CAAC,YAAY,EAAE,CAAC;IAC5C,CAAC;CACJ"}
package/utils.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function isStream(value: any): boolean;
2
+ //# sourceMappingURL=utils.d.ts.map
package/utils.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,QAAQ,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,CAM5C"}
package/utils.js ADDED
@@ -0,0 +1,6 @@
1
+ export function isStream(value) {
2
+ return (typeof value === 'object' &&
3
+ value &&
4
+ ['on', 'pipe'].every((key) => key in value && typeof value[key] === 'function'));
5
+ }
6
+ //# sourceMappingURL=utils.js.map
package/utils.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,QAAQ,CAAC,KAAU;IAC/B,OAAO,CACH,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK;QACL,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,UAAU,CAAC,CAClF,CAAC;AACN,CAAC"}