@crawlee/core 4.0.0-beta.2 → 4.0.0-beta.200

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 (259) hide show
  1. package/README.md +17 -13
  2. package/byte_utils.d.ts +17 -0
  3. package/byte_utils.js +42 -0
  4. package/configuration.d.ts +88 -234
  5. package/configuration.js +162 -230
  6. package/debug.d.ts +36 -0
  7. package/debug.js +70 -0
  8. package/errors.d.ts +43 -10
  9. package/errors.js +57 -12
  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 -5
  15. package/events/local_event_manager.js +39 -42
  16. package/events/system_info.d.ts +38 -0
  17. package/events/system_info.js +1 -0
  18. package/index.d.ts +8 -11
  19. package/index.js +9 -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 +81 -3
  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 +43 -0
  31. package/memory-storage/memory-storage.js +158 -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 +56 -0
  37. package/memory-storage/resource-clients/key-value-store.js +180 -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 +19 -20
  45. package/proxy_configuration.d.ts +29 -155
  46. package/proxy_configuration.js +38 -183
  47. package/recoverable_state.d.ts +177 -0
  48. package/recoverable_state.js +268 -0
  49. package/request.d.ts +62 -81
  50. package/request.js +125 -220
  51. package/serialization.d.ts +0 -1
  52. package/serialization.js +18 -18
  53. package/service_locator.d.ts +168 -0
  54. package/service_locator.js +255 -0
  55. package/storages/batched_adds.d.ts +37 -0
  56. package/storages/batched_adds.js +73 -0
  57. package/storages/dataset.d.ts +120 -56
  58. package/storages/dataset.js +299 -148
  59. package/storages/index.d.ts +9 -8
  60. package/storages/index.js +5 -8
  61. package/storages/key_value_store.d.ts +192 -65
  62. package/storages/key_value_store.js +453 -193
  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 +55 -137
  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 +119 -0
  72. package/storages/request_manager.js +1 -0
  73. package/storages/request_manager_tandem.d.ts +94 -0
  74. package/storages/request_manager_tandem.js +200 -0
  75. package/storages/request_queue.d.ts +293 -48
  76. package/storages/request_queue.js +766 -216
  77. package/storages/storage_instance_manager.d.ts +86 -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 +271 -0
  82. package/storages/transaction.js +296 -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 -292
  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 -113
  120. package/cookie_utils.js.map +0 -1
  121. package/crawlers/crawler_commons.d.ts +0 -199
  122. package/crawlers/crawler_commons.d.ts.map +0 -1
  123. package/crawlers/crawler_commons.js +0 -116
  124. package/crawlers/crawler_commons.js.map +0 -1
  125. package/crawlers/crawler_extension.d.ts +0 -12
  126. package/crawlers/crawler_extension.d.ts.map +0 -1
  127. package/crawlers/crawler_extension.js +0 -14
  128. package/crawlers/crawler_extension.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 -59
  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 -54
  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/statistics.d.ts +0 -204
  146. package/crawlers/statistics.d.ts.map +0 -1
  147. package/crawlers/statistics.js +0 -346
  148. package/crawlers/statistics.js.map +0 -1
  149. package/enqueue_links/enqueue_links.d.ts +0 -242
  150. package/enqueue_links/enqueue_links.d.ts.map +0 -1
  151. package/enqueue_links/enqueue_links.js +0 -250
  152. package/enqueue_links/enqueue_links.js.map +0 -1
  153. package/enqueue_links/index.d.ts +0 -3
  154. package/enqueue_links/index.d.ts.map +0 -1
  155. package/enqueue_links/index.js +0 -3
  156. package/enqueue_links/index.js.map +0 -1
  157. package/enqueue_links/shared.d.ts +0 -67
  158. package/enqueue_links/shared.d.ts.map +0 -1
  159. package/enqueue_links/shared.js +0 -190
  160. package/enqueue_links/shared.js.map +0 -1
  161. package/errors.d.ts.map +0 -1
  162. package/errors.js.map +0 -1
  163. package/events/event_manager.d.ts.map +0 -1
  164. package/events/event_manager.js.map +0 -1
  165. package/events/index.d.ts.map +0 -1
  166. package/events/index.js.map +0 -1
  167. package/events/local_event_manager.d.ts.map +0 -1
  168. package/events/local_event_manager.js.map +0 -1
  169. package/http_clients/base-http-client.d.ts +0 -134
  170. package/http_clients/base-http-client.d.ts.map +0 -1
  171. package/http_clients/base-http-client.js +0 -33
  172. package/http_clients/base-http-client.js.map +0 -1
  173. package/http_clients/form-data-like.d.ts +0 -67
  174. package/http_clients/form-data-like.d.ts.map +0 -1
  175. package/http_clients/form-data-like.js +0 -5
  176. package/http_clients/form-data-like.js.map +0 -1
  177. package/http_clients/got-scraping-http-client.d.ts +0 -15
  178. package/http_clients/got-scraping-http-client.d.ts.map +0 -1
  179. package/http_clients/got-scraping-http-client.js +0 -69
  180. package/http_clients/got-scraping-http-client.js.map +0 -1
  181. package/http_clients/index.d.ts +0 -3
  182. package/http_clients/index.d.ts.map +0 -1
  183. package/http_clients/index.js +0 -3
  184. package/http_clients/index.js.map +0 -1
  185. package/index.d.ts.map +0 -1
  186. package/index.js.map +0 -1
  187. package/log.d.ts.map +0 -1
  188. package/log.js.map +0 -1
  189. package/proxy_configuration.d.ts.map +0 -1
  190. package/proxy_configuration.js.map +0 -1
  191. package/request.d.ts.map +0 -1
  192. package/request.js.map +0 -1
  193. package/router.d.ts +0 -134
  194. package/router.d.ts.map +0 -1
  195. package/router.js +0 -164
  196. package/router.js.map +0 -1
  197. package/serialization.d.ts.map +0 -1
  198. package/serialization.js.map +0 -1
  199. package/session_pool/consts.d.ts +0 -4
  200. package/session_pool/consts.d.ts.map +0 -1
  201. package/session_pool/consts.js +0 -4
  202. package/session_pool/consts.js.map +0 -1
  203. package/session_pool/errors.d.ts +0 -8
  204. package/session_pool/errors.d.ts.map +0 -1
  205. package/session_pool/errors.js +0 -12
  206. package/session_pool/errors.js.map +0 -1
  207. package/session_pool/events.d.ts +0 -3
  208. package/session_pool/events.d.ts.map +0 -1
  209. package/session_pool/events.js +0 -3
  210. package/session_pool/events.js.map +0 -1
  211. package/session_pool/index.d.ts +0 -6
  212. package/session_pool/index.d.ts.map +0 -1
  213. package/session_pool/index.js +0 -6
  214. package/session_pool/index.js.map +0 -1
  215. package/session_pool/session.d.ts +0 -199
  216. package/session_pool/session.d.ts.map +0 -1
  217. package/session_pool/session.js +0 -271
  218. package/session_pool/session.js.map +0 -1
  219. package/session_pool/session_pool.d.ts +0 -251
  220. package/session_pool/session_pool.d.ts.map +0 -1
  221. package/session_pool/session_pool.js +0 -375
  222. package/session_pool/session_pool.js.map +0 -1
  223. package/storages/access_checking.d.ts +0 -13
  224. package/storages/access_checking.d.ts.map +0 -1
  225. package/storages/access_checking.js +0 -14
  226. package/storages/access_checking.js.map +0 -1
  227. package/storages/dataset.d.ts.map +0 -1
  228. package/storages/dataset.js.map +0 -1
  229. package/storages/index.d.ts.map +0 -1
  230. package/storages/index.js.map +0 -1
  231. package/storages/key_value_store.d.ts.map +0 -1
  232. package/storages/key_value_store.js.map +0 -1
  233. package/storages/request_list.d.ts.map +0 -1
  234. package/storages/request_list.js.map +0 -1
  235. package/storages/request_provider.d.ts +0 -307
  236. package/storages/request_provider.d.ts.map +0 -1
  237. package/storages/request_provider.js +0 -555
  238. package/storages/request_provider.js.map +0 -1
  239. package/storages/request_queue.d.ts.map +0 -1
  240. package/storages/request_queue.js.map +0 -1
  241. package/storages/request_queue_v2.d.ts +0 -87
  242. package/storages/request_queue_v2.d.ts.map +0 -1
  243. package/storages/request_queue_v2.js +0 -438
  244. package/storages/request_queue_v2.js.map +0 -1
  245. package/storages/sitemap_request_list.d.ts +0 -245
  246. package/storages/sitemap_request_list.d.ts.map +0 -1
  247. package/storages/sitemap_request_list.js +0 -430
  248. package/storages/sitemap_request_list.js.map +0 -1
  249. package/storages/storage_manager.d.ts +0 -58
  250. package/storages/storage_manager.d.ts.map +0 -1
  251. package/storages/storage_manager.js +0 -105
  252. package/storages/storage_manager.js.map +0 -1
  253. package/storages/utils.d.ts.map +0 -1
  254. package/storages/utils.js.map +0 -1
  255. package/tsconfig.build.tsbuildinfo +0 -1
  256. package/typedefs.d.ts.map +0 -1
  257. package/typedefs.js.map +0 -1
  258. package/validators.d.ts.map +0 -1
  259. package/validators.js.map +0 -1
@@ -1,42 +1,31 @@
1
- import { readFile } from 'node:fs/promises';
2
- import { join } from 'node:path';
3
- import JSON5 from 'json5';
4
- import ow, { ArgumentError } from 'ow';
1
+ import { z } from 'zod';
5
2
  import { KEY_VALUE_STORE_KEY_REGEX } from '@apify/consts';
6
- import log from '@apify/log';
7
- import { jsonStringifyExtended } from '@apify/utilities';
3
+ import { tryCancel } from '@apify/timeout';
8
4
  import { Configuration } from '../configuration.js';
9
- import { checkStorageAccess } from './access_checking.js';
10
- import { StorageManager } from './storage_manager.js';
11
- import { purgeDefaultStorages } from './utils.js';
12
- /**
13
- * Helper function to possibly stringify value if options.contentType is not set.
14
- *
15
- * @ignore
16
- */
17
- export const maybeStringify = (value, options) => {
18
- // If contentType is missing, value will be stringified to JSON
19
- if (options.contentType === null || options.contentType === undefined) {
20
- options.contentType = 'application/json; charset=utf-8';
21
- try {
22
- // Format JSON to simplify debugging, the overheads with compression is negligible
23
- value = jsonStringifyExtended(value, null, 2);
24
- }
25
- catch (e) {
26
- const error = e;
27
- // Give more meaningful error message
28
- if (error.message?.includes('Invalid string length')) {
29
- error.message = 'Object is too large';
30
- }
31
- throw new Error(`The "value" parameter cannot be stringified to JSON: ${error.message}`);
32
- }
33
- if (value === undefined) {
34
- throw new Error('The "value" parameter was stringified to JSON and returned undefined. ' +
35
- "Make sure you're not trying to stringify an undefined value.");
36
- }
37
- }
38
- return value;
39
- };
5
+ import { serviceLocator } from '../service_locator.js';
6
+ import { parseArgument, schemas, validators } from '../validators.js';
7
+ import { activeStorageTransaction, operationRejectedInTransaction, rejectOperationInTransaction, snapshotValue, withDirectStorageAccess, } from './transaction.js';
8
+ import { parseValue, serializeValue } from './key_value_store_codec.js';
9
+ import { StorageStatsTracker } from './storage_stats.js';
10
+ import { resolveStorageIdentifier } from './storage_instance_manager.js';
11
+ import { createDualIterable, purgeDefaultStorages } from './utils.js';
12
+ import { isBuffer, isStream } from '../byte_utils.js';
13
+ /** @internal */
14
+ const KVS_KEYS_DEFAULT_LIMIT = 1000;
15
+ const keySchema = z.string().nonempty();
16
+ const setValueKeySchema = z.string().nonempty().regex(KEY_VALUE_STORE_KEY_REGEX, {
17
+ message: `The "key" argument must be at most 256 characters long and only contain the following characters: a-zA-Z0-9!-_.'()`,
18
+ });
19
+ const recordOptionsSchema = z.strictObject({
20
+ contentType: z.string().nonempty().optional(),
21
+ });
22
+ const iteratorOptionsSchema = z.strictObject({
23
+ prefix: z.string().optional(),
24
+ });
25
+ const openOptionsSchema = z.strictObject({
26
+ configuration: z.instanceof(Configuration).optional(),
27
+ storageBackend: validators.storageBackend.optional(),
28
+ });
40
29
  /**
41
30
  * The `KeyValueStore` class represents a key-value store, a simple data storage that is used
42
31
  * for saving and reading data records or files. Each data record is
@@ -53,8 +42,6 @@ export const maybeStringify = (value, options) => {
53
42
  * To access the default key-value store directly, you can use the
54
43
  * {@link KeyValueStore.getValue} and {@link KeyValueStore.setValue} convenience functions.
55
44
  *
56
- * To access the input, you can also use the {@link KeyValueStore.getInput} convenience function.
57
- *
58
45
  * `KeyValueStore` stores its data on a local disk.
59
46
  *
60
47
  * If the `CRAWLEE_STORAGE_DIR` environment variable is set, the data is stored in
@@ -62,15 +49,14 @@ export const maybeStringify = (value, options) => {
62
49
  * ```
63
50
  * {CRAWLEE_STORAGE_DIR}/key_value_stores/{STORE_ID}/{INDEX}.{EXT}
64
51
  * ```
65
- * Note that `{STORE_ID}` is the name or ID of the key-value store. The default key-value store has ID: `default`,
66
- * unless you override it by setting the `CRAWLEE_DEFAULT_KEY_VALUE_STORE_ID` environment variable.
52
+ * Note that `{STORE_ID}` is the name or ID of the key-value store. The default key-value store has ID: `default`.
67
53
  * The `{KEY}` is the key of the record and `{EXT}` corresponds to the MIME content type of the data value.
68
54
  *
69
55
  * **Example usage:**
70
56
  *
71
57
  * ```javascript
72
58
  * // Get crawler input from the default key-value store.
73
- * const input = await KeyValueStore.getInput();
59
+ * const input = await KeyValueStore.getValue('INPUT');
74
60
  * // Get some value from the default key-value store.
75
61
  * const otherValue = await KeyValueStore.getValue('my-key');
76
62
  *
@@ -94,23 +80,34 @@ export const maybeStringify = (value, options) => {
94
80
  * @category Result Stores
95
81
  */
96
82
  export class KeyValueStore {
97
- config;
83
+ configuration;
98
84
  id;
99
85
  name;
100
- storageObject;
101
- client;
102
- persistStateEventStarted = false;
86
+ backend;
87
+ #persistStateEventStarted = false;
103
88
  /** Cache for persistent (auto-saved) values. When we try to set such value, the cache will be updated automatically. */
104
- cache = new Map();
89
+ #cache = new Map();
90
+ #statsTracker = new StorageStatsTracker({
91
+ readCount: 0,
92
+ writeCount: 0,
93
+ deleteCount: 0,
94
+ listCount: 0,
95
+ });
105
96
  /**
106
97
  * @internal
107
98
  */
108
- constructor(options, config = Configuration.getGlobalConfig()) {
109
- this.config = config;
110
- this.id = options.id;
111
- this.name = options.name;
112
- this.storageObject = options.storageObject;
113
- this.client = options.client.keyValueStore(this.id);
99
+ constructor(options, configuration = Configuration.getGlobalConfiguration()) {
100
+ this.configuration = configuration;
101
+ this.id = options.metadata.id;
102
+ this.name = options.metadata.name;
103
+ this.backend = options.backend;
104
+ }
105
+ /**
106
+ * Backend-independent usage counters tracked for this key-value store (read / write / delete /
107
+ * list operations issued to the underlying storage backend). Counted per backend call.
108
+ */
109
+ get stats() {
110
+ return this.#statsTracker.current;
114
111
  }
115
112
  /**
116
113
  * Gets a value from the key-value store.
@@ -145,10 +142,93 @@ export class KeyValueStore {
145
142
  * on the MIME content type of the record, or `null` if the key is missing from the store.
146
143
  */
147
144
  async getValue(key, defaultValue) {
148
- checkStorageAccess();
149
- ow(key, ow.string.nonEmpty);
150
- const record = await this.client.getRecord(key);
151
- return record?.value ?? defaultValue ?? null;
145
+ tryCancel();
146
+ parseArgument(key, keySchema);
147
+ const record = await this.readRecord(key);
148
+ // A missing record falls back to the default; a record that parses to a falsy value (including
149
+ // a stored literal `null`) is returned verbatim, so callers can tell "stored null" from "absent".
150
+ if (!record) {
151
+ return defaultValue ?? null;
152
+ }
153
+ // Storage backends are byte transports — the value is raw bytes; the frontend parses it here.
154
+ return parseValue(record.value, record.contentType ?? null);
155
+ }
156
+ /**
157
+ * The active transaction's last buffered write per key for this store, derived from its journal.
158
+ * An entry with a `null` value is a tombstone (an in-transaction deletion).
159
+ */
160
+ bufferedJournalEntries() {
161
+ const transaction = activeStorageTransaction();
162
+ if (!transaction)
163
+ return undefined;
164
+ const lastWritePerKey = new Map();
165
+ for (const entry of transaction.journal) {
166
+ if (entry.type === 'keyValueStore' && entry.participant === this) {
167
+ lastWritePerKey.set(entry.key, entry);
168
+ }
169
+ }
170
+ return lastWritePerKey;
171
+ }
172
+ /**
173
+ * The single transaction-aware record read shared by `getValue`, `getRecord`, `recordExists` and the
174
+ * listing paths: buffered key → serialized through the standard codec (same fidelity as a real
175
+ * round-trip); tombstoned key → `null`; otherwise the backend.
176
+ *
177
+ * The per-key buffered lookup requires the whole journal to be reduced to a last-write-per-key map,
178
+ * which is O(journal). Single-record callers let it default (rebuilt per call); the listing paths,
179
+ * which read many keys, pass a map built once so the read stays O(1) per key instead of O(journal).
180
+ */
181
+ async readRecord(key, buffered = this.bufferedJournalEntries()) {
182
+ const entry = buffered?.get(key);
183
+ if (entry) {
184
+ if (entry.value === null) {
185
+ return null;
186
+ }
187
+ const serialized = serializeValue(entry.value, entry.options?.contentType);
188
+ return {
189
+ value: normalizeSerializedValue(serialized.value),
190
+ contentType: serialized.contentType ?? null,
191
+ };
192
+ }
193
+ this.#statsTracker.add('readCount');
194
+ const record = await this.backend.getValue(key);
195
+ if (!record)
196
+ return null;
197
+ return {
198
+ value: record.value,
199
+ contentType: record.contentType ?? null,
200
+ };
201
+ }
202
+ /**
203
+ * Reads a record from the key-value store without parsing the value.
204
+ *
205
+ * Use this when you need the raw bytes and the content type — for example, to run your own
206
+ * parser (`simdjson`, a custom XML library, etc.) or to forward the bytes verbatim.
207
+ *
208
+ * There is no symmetric `setRecord` method, because {@link KeyValueStore.setValue} already
209
+ * passes a `Buffer` (or `string` / `Stream`) through unchanged when an explicit `contentType`
210
+ * is provided. To write pre-serialized bytes, call
211
+ * `setValue(key, buffer, { contentType: 'application/json; charset=utf-8' })`.
212
+ *
213
+ * Returns `null` if the record does not exist.
214
+ *
215
+ * **Example usage:**
216
+ * ```javascript
217
+ * const store = await KeyValueStore.open();
218
+ * const record = await store.getRecord('huge.json');
219
+ * if (record) {
220
+ * const data = simdjson.parse(record.value);
221
+ * }
222
+ * ```
223
+ *
224
+ * @param key
225
+ * Unique key of the record. It can be at most 256 characters long and only consist
226
+ * of the following characters: `a`-`z`, `A`-`Z`, `0`-`9` and `!-_.'()`
227
+ */
228
+ async getRecord(key) {
229
+ tryCancel();
230
+ parseArgument(key, keySchema);
231
+ return this.readRecord(key);
152
232
  }
153
233
  /**
154
234
  * Tests whether a record with the given key exists in the key-value store without retrieving its value.
@@ -157,45 +237,96 @@ export class KeyValueStore {
157
237
  * @returns `true` if the record exists, `false` if it does not.
158
238
  */
159
239
  async recordExists(key) {
160
- checkStorageAccess();
161
- ow(key, ow.string.nonEmpty);
162
- return this.client.recordExists(key);
240
+ tryCancel();
241
+ parseArgument(key, keySchema);
242
+ const entry = this.bufferedJournalEntries()?.get(key);
243
+ if (entry) {
244
+ return entry.value !== null;
245
+ }
246
+ return this.backend.recordExists(key);
163
247
  }
164
248
  async getAutoSavedValue(key, defaultValue = {}) {
165
- checkStorageAccess();
166
- if (this.cache.has(key)) {
167
- return this.cache.get(key);
249
+ tryCancel();
250
+ if (this.#cache.has(key)) {
251
+ return this.#cache.get(key);
168
252
  }
169
- const value = await this.getValue(key, defaultValue);
253
+ // Auto-saved state is deliberately *not* transactional. The direct read bypasses any active
254
+ // transaction - a buffered value seeded into this shared cache would survive a rollback forever.
255
+ const value = await withDirectStorageAccess(async () => this.getValue(key, defaultValue));
170
256
  // The await above could have run in parallel with another call to this function. If the other call finished more quickly,
171
257
  // the value will in cache at this point, and returning the new fetched value would introduce two different instances of
172
258
  // the auto-saved object, and only the latter one would be persisted.
173
259
  // Therefore we re-check the cache here, and if such race condition happened, we drop the fetched value and return the cached one.
174
- if (this.cache.has(key)) {
175
- return this.cache.get(key);
260
+ if (this.#cache.has(key)) {
261
+ return this.#cache.get(key);
176
262
  }
177
- this.cache.set(key, value);
263
+ this.#cache.set(key, value);
178
264
  this.ensurePersistStateEvent();
179
265
  return value;
180
266
  }
181
267
  ensurePersistStateEvent() {
182
- if (this.persistStateEventStarted) {
268
+ if (this.#persistStateEventStarted) {
183
269
  return;
184
270
  }
185
- // use half the interval of `persistState` to avoid race conditions
186
- const persistStateIntervalMillis = this.config.get('persistStateIntervalMillis');
187
- const timeoutSecs = persistStateIntervalMillis / 2_000;
188
- this.config.getEventManager().on('persistState', async () => {
271
+ serviceLocator.getEventManager().on('persistState', async () => {
189
272
  const promises = [];
190
- for (const [key, value] of this.cache) {
191
- promises.push(this.setValue(key, value, {
192
- timeoutSecs,
193
- doNotRetryTimeouts: true,
194
- }).catch((error) => log.warning(`Failed to persist the state value to ${key}`, { error })));
273
+ for (const [key, value] of this.#cache) {
274
+ promises.push(this.setValue(key, value).catch((error) => serviceLocator.getLogger().warning(`Failed to persist the state value to ${key}`, { error })));
195
275
  }
196
276
  await Promise.all(promises);
197
277
  });
198
- this.persistStateEventStarted = true;
278
+ this.#persistStateEventStarted = true;
279
+ }
280
+ async *fetchKeyValuePages(options, mapRecord) {
281
+ // Reduce the journal once for the whole iteration, not once per key inside `readRecord`.
282
+ const buffered = this.bufferedJournalEntries();
283
+ for await (const page of this.fetchKeyPages(options, buffered)) {
284
+ const results = [];
285
+ for (const item of page) {
286
+ // The shared transaction-aware read, so a key that exists only in the transaction resolves
287
+ // here instead of being dropped (`values()` would disagree with `keys()` on length).
288
+ const record = await this.readRecord(item.key, buffered);
289
+ if (record) {
290
+ const parsed = parseValue(record.value, record.contentType ?? null);
291
+ results.push(mapRecord(item.key, parsed));
292
+ }
293
+ }
294
+ yield results;
295
+ }
296
+ }
297
+ async *fetchKeyPages(options, buffered = this.bufferedJournalEntries(), limit = KVS_KEYS_DEFAULT_LIMIT) {
298
+ // Buffered keys are emitted first, then the real pages with any buffered (or tombstoned) key
299
+ // skipped - a merge-join is not an option, since `listKeys` promises no sort order.
300
+ const shadowedKeys = new Set();
301
+ if (buffered) {
302
+ const bufferedItems = [];
303
+ for (const [key, entry] of buffered) {
304
+ shadowedKeys.add(key);
305
+ if (entry.value === null)
306
+ continue;
307
+ if (options.prefix !== undefined && !key.startsWith(options.prefix))
308
+ continue;
309
+ bufferedItems.push(bufferedKeyItemData(key, entry));
310
+ }
311
+ if (bufferedItems.length > 0) {
312
+ bufferedItems.sort((a, b) => (a.key < b.key ? -1 : 1));
313
+ yield bufferedItems;
314
+ }
315
+ }
316
+ let exclusiveStartKey;
317
+ while (true) {
318
+ this.#statsTracker.add('listCount');
319
+ const { items, isTruncated, nextExclusiveStartKey } = await this.backend.listKeys({
320
+ ...options,
321
+ exclusiveStartKey,
322
+ limit,
323
+ });
324
+ yield shadowedKeys.size > 0 ? items.filter((item) => !shadowedKeys.has(item.key)) : items;
325
+ if (!isTruncated)
326
+ break;
327
+ // Paginate from the raw backend cursor - it may reject a key it did not hand out.
328
+ exclusiveStartKey = nextExclusiveStartKey;
329
+ }
199
330
  }
200
331
  /**
201
332
  * Saves or deletes a record in the key-value store.
@@ -241,73 +372,110 @@ export class KeyValueStore {
241
372
  * @param [options] Record options.
242
373
  */
243
374
  async setValue(key, value, options = {}) {
244
- checkStorageAccess();
245
- ow(key, 'key', ow.string.nonEmpty);
246
- ow(key, ow.string.validate((k) => ({
247
- validator: ow.isValid(k, ow.string.matches(KEY_VALUE_STORE_KEY_REGEX)),
248
- message: `The "key" argument "${key}" must be at most 256 characters long and only contain the following characters: a-zA-Z0-9!-_.'()`,
249
- })));
250
- if (options.contentType &&
251
- !(ow.isValid(value, ow.any(ow.string, ow.uint8Array)) ||
252
- (ow.isValid(value, ow.object) && typeof value.pipe === 'function'))) {
253
- throw new ArgumentError('The "value" parameter must be a String, Buffer or Stream when "options.contentType" is specified.', this.setValue);
375
+ const transaction = activeStorageTransaction();
376
+ parseArgument(key, setValueKeySchema);
377
+ if (options.contentType && !(typeof value === 'string' || isBuffer(value) || isStream(value))) {
378
+ throw new Error('The "value" parameter must be a String, Buffer, ArrayBuffer, TypedArray, or Stream when "options.contentType" is specified.');
379
+ }
380
+ // The parse result is a fresh copy, so we never update what user passed.
381
+ const optionsCopy = parseArgument(options, recordOptionsSchema);
382
+ // The whole transaction branch sits *above* the auto-saved cache update below, so a buffered
383
+ // write touches nothing outside the journal. That cache is shared, process-lifetime frontend
384
+ // state, so mutating it here would survive a rollback and later be persisted by `persistState`.
385
+ // The commit replay re-enters this method with no active transaction and updates it then.
386
+ if (transaction) {
387
+ if (isStream(value)) {
388
+ // A stream cannot serve both a read-your-own-writes read and the commit replay. The
389
+ // transaction is known-active here, so throw directly rather than via the conditional guard.
390
+ throw operationRejectedInTransaction(`KeyValueStore.setValue() with a stream value (key "${key}")`, 'a stream can only be consumed once, so it cannot be buffered until commit.');
391
+ }
392
+ // Validation only, result discarded: the journal snapshot (`structuredClone`) accepts values
393
+ // JSON cannot, which would otherwise only throw at a later read or at commit.
394
+ if (value !== null) {
395
+ serializeValue(value, optionsCopy.contentType);
396
+ }
397
+ // One snapshot serves both the reads and the commit replay; `null` is a tombstone.
398
+ transaction.recordJournalEntry({
399
+ type: 'keyValueStore',
400
+ participant: this,
401
+ storageId: this.id,
402
+ key,
403
+ value: value === null ? null : snapshotValue(value),
404
+ options: optionsCopy,
405
+ });
406
+ return;
254
407
  }
255
- ow(options, ow.object.exactShape({
256
- contentType: ow.optional.string.nonEmpty,
257
- timeoutSecs: ow.optional.number,
258
- doNotRetryTimeouts: ow.optional.boolean,
259
- }));
260
- // Make copy of options, don't update what user passed.
261
- const optionsCopy = { ...options };
262
408
  // If we try to set the value of a cached state to a different reference, we need to update the cache accordingly.
263
- const cachedValue = this.cache.get(key);
409
+ const cachedValue = this.#cache.get(key);
264
410
  if (cachedValue && cachedValue !== value) {
265
411
  if (value === null) {
266
412
  // Cached state can be only object, so a propagation of `null` means removing all its properties.
267
- Object.keys(cachedValue).forEach((k) => this.cache.delete(k));
413
+ Object.keys(cachedValue).forEach((k) => this.#cache.delete(k));
268
414
  }
269
415
  else if (typeof value === 'object') {
270
416
  // We need to remove the keys that are no longer present in the new value.
271
417
  Object.keys(cachedValue)
272
418
  .filter((k) => !(k in value))
273
- .forEach((k) => this.cache.delete(k));
419
+ .forEach((k) => this.#cache.delete(k));
274
420
  // And update the existing ones + add new ones.
275
421
  Object.assign(cachedValue, value);
276
422
  }
277
423
  }
278
424
  // In this case delete the record.
279
- if (value === null)
280
- return this.client.deleteRecord(key);
281
- value = maybeStringify(value, optionsCopy);
282
- return this.client.setRecord({
425
+ if (value === null) {
426
+ this.#statsTracker.add('deleteCount');
427
+ return this.backend.deleteValue(key);
428
+ }
429
+ const serialized = serializeValue(value, optionsCopy.contentType);
430
+ this.#statsTracker.add('writeCount');
431
+ return this.backend.setValue({
283
432
  key,
284
- value,
285
- contentType: optionsCopy.contentType,
286
- }, {
287
- timeoutSecs: optionsCopy.timeoutSecs,
288
- doNotRetryTimeouts: optionsCopy.doNotRetryTimeouts,
433
+ value: serialized.value,
434
+ contentType: serialized.contentType,
289
435
  });
290
436
  }
437
+ /** @internal */
438
+ async commitJournalEntries(entries) {
439
+ // One `setValue` per key, last write wins - idempotent under retry.
440
+ const lastWritePerKey = new Map();
441
+ for (const entry of entries) {
442
+ if (entry.type === 'keyValueStore') {
443
+ lastWritePerKey.set(entry.key, { value: entry.value, options: entry.options });
444
+ }
445
+ }
446
+ for (const [key, { value, options }] of lastWritePerKey) {
447
+ await this.setValue(key, value, options);
448
+ }
449
+ }
291
450
  /**
292
451
  * Removes the key-value store either from the Apify cloud storage or from the local directory,
293
452
  * depending on the mode of operation.
294
453
  */
295
454
  async drop() {
296
- checkStorageAccess();
297
- await this.client.delete();
298
- const manager = StorageManager.getManager(KeyValueStore, this.config);
299
- manager.closeStorage(this);
455
+ rejectOperationInTransaction('KeyValueStore.drop()');
456
+ await this.backend.drop();
457
+ serviceLocator.getStorageInstanceManager().removeFromCache(this);
458
+ }
459
+ /**
460
+ * Removes all records from the store but keeps the store itself, along with its
461
+ * {@link KeyValueStore.id|`id`} and {@link KeyValueStore.name|`name`}.
462
+ */
463
+ async purge() {
464
+ rejectOperationInTransaction('KeyValueStore.purge()');
465
+ await this.backend.purge();
466
+ // The auto-saved values this cache holds are no longer in the store.
467
+ this.#cache.clear();
300
468
  }
301
469
  /** @internal */
302
470
  clearCache() {
303
- checkStorageAccess();
304
- this.cache.clear();
471
+ rejectOperationInTransaction('KeyValueStore.clearCache()');
472
+ this.#cache.clear();
305
473
  }
306
474
  /**
307
475
  * Iterates over key-value store keys, yielding each in turn to an `iteratee` function.
308
476
  * Each invocation of `iteratee` is called with three arguments: `(key, index, info)`, where `key`
309
477
  * is the record key, `index` is a zero-based index of the key in the current iteration
310
- * (regardless of `options.exclusiveStartKey`) and `info` is an object that contains a single property `size`
478
+ * and `info` is an object that contains a single property `size`
311
479
  * indicating size of the record in bytes.
312
480
  *
313
481
  * If the `iteratee` function returns a Promise then it is awaited before the next call.
@@ -325,30 +493,132 @@ export class KeyValueStore {
325
493
  * @param [options] All `forEachKey()` parameters.
326
494
  */
327
495
  async forEachKey(iteratee, options = {}) {
328
- checkStorageAccess();
329
- return this._forEachKey(iteratee, options);
330
- }
331
- async _forEachKey(iteratee, options = {}, index = 0) {
332
- const { exclusiveStartKey } = options;
333
- ow(iteratee, ow.function);
334
- ow(options, ow.object.exactShape({
335
- exclusiveStartKey: ow.optional.string,
336
- }));
337
- const response = await this.client.listKeys({ exclusiveStartKey });
338
- const { nextExclusiveStartKey, isTruncated, items } = response;
339
- for (const item of items) {
340
- await iteratee(item.key, index++, { size: item.size });
496
+ tryCancel();
497
+ parseArgument(iteratee, schemas.anyFunction);
498
+ const parsedOptions = parseArgument(options, iteratorOptionsSchema);
499
+ let index = 0;
500
+ for await (const page of this.fetchKeyPages(parsedOptions)) {
501
+ for (const item of page) {
502
+ await iteratee(item.key, index++, { size: item.size });
503
+ }
341
504
  }
342
- return isTruncated
343
- ? this._forEachKey(iteratee, { exclusiveStartKey: nextExclusiveStartKey }, index)
344
- : undefined; // [].forEach() returns undefined.
505
+ }
506
+ /**
507
+ * Returns key-value store keys.
508
+ *
509
+ * When awaited (`await store.keys()`), returns all keys as a flat `string[]` array.
510
+ * When used as an async iterable (`for await...of`), iterates over all keys across pages
511
+ * without loading everything into memory at once.
512
+ *
513
+ * **Example usage:**
514
+ * ```javascript
515
+ * const keyValueStore = await KeyValueStore.open();
516
+ *
517
+ * // Iterate over all keys (memory-efficient for large stores)
518
+ * for await (const key of keyValueStore.keys()) {
519
+ * console.log(key);
520
+ * }
521
+ *
522
+ * // Or fetch all keys at once
523
+ * const allKeys = await keyValueStore.keys();
524
+ * console.log(allKeys);
525
+ * ```
526
+ *
527
+ * @param options Options for the iteration.
528
+ */
529
+ keys(options = {}) {
530
+ tryCancel();
531
+ return createDualIterable({
532
+ createPages: () => this.fetchKeyPages(options),
533
+ extractItems: (page) => page.map((item) => item.key),
534
+ });
535
+ }
536
+ /**
537
+ * Returns key-value store values.
538
+ *
539
+ * When awaited (`await store.values()`), returns all values as a flat `T[]` array.
540
+ * When used as an async iterable (`for await...of`), iterates over all values across pages
541
+ * without loading everything into memory at once.
542
+ *
543
+ * **Example usage:**
544
+ * ```javascript
545
+ * const keyValueStore = await KeyValueStore.open();
546
+ *
547
+ * // Iterate over all values (memory-efficient for large stores)
548
+ * for await (const value of keyValueStore.values()) {
549
+ * console.log(value);
550
+ * }
551
+ *
552
+ * // Or fetch all values at once
553
+ * const allValues = await keyValueStore.values();
554
+ * console.log(allValues);
555
+ * ```
556
+ *
557
+ * @param options Options for the iteration.
558
+ */
559
+ values(options = {}) {
560
+ tryCancel();
561
+ return createDualIterable({
562
+ createPages: () => this.fetchKeyValuePages(options, (_key, value) => value),
563
+ extractItems: (page) => page,
564
+ });
565
+ }
566
+ /**
567
+ * Returns key-value store entries (key-value pairs).
568
+ *
569
+ * When awaited (`await store.entries()`), returns all entries as a flat `[key, value][]` array.
570
+ * When used as an async iterable (`for await...of`), iterates over all entries across pages
571
+ * without loading everything into memory at once.
572
+ *
573
+ * **Example usage:**
574
+ * ```javascript
575
+ * const keyValueStore = await KeyValueStore.open();
576
+ *
577
+ * // Iterate over all entries (memory-efficient for large stores)
578
+ * for await (const [key, value] of keyValueStore.entries()) {
579
+ * console.log(`${key}: ${value}`);
580
+ * }
581
+ *
582
+ * // Or fetch all entries at once
583
+ * const allEntries = await keyValueStore.entries();
584
+ * console.log(allEntries);
585
+ * ```
586
+ *
587
+ * @param options Options for the iteration.
588
+ */
589
+ entries(options = {}) {
590
+ tryCancel();
591
+ return createDualIterable({
592
+ createPages: () => this.fetchKeyValuePages(options, (key, value) => [key, value]),
593
+ extractItems: (page) => page,
594
+ });
595
+ }
596
+ /**
597
+ * Default async iterator for the key-value store, iterating over entries (key-value pairs).
598
+ * Allows using the store directly in a `for await...of` loop.
599
+ *
600
+ * **Example usage:**
601
+ * ```javascript
602
+ * const keyValueStore = await KeyValueStore.open();
603
+ * for await (const [key, value] of keyValueStore) {
604
+ * console.log(`${key}: ${value}`);
605
+ * }
606
+ * ```
607
+ */
608
+ async *[Symbol.asyncIterator]() {
609
+ yield* this.entries();
345
610
  }
346
611
  /**
347
612
  * Returns a file URL for the given key.
613
+ *
614
+ * The URL is derived from the key, so it is also returned for a record that does not exist (yet) —
615
+ * including one written earlier in an uncommitted storage transaction. Returns `undefined` only when
616
+ * the storage has no file URLs at all (e.g. the in-memory storage).
617
+ *
618
+ * @param key The key of the record to generate the public URL for.
348
619
  */
349
- getPublicUrl(key) {
350
- const name = this.name ?? this.config.get('defaultKeyValueStoreId');
351
- return `file://${process.cwd()}/storage/key_value_stores/${name}/${key}`;
620
+ async getPublicUrl(key) {
621
+ return this.backend.getPublicUrl(key);
352
622
  }
353
623
  /**
354
624
  * Opens a key-value store and returns a promise resolving to an instance of the {@link KeyValueStore} class.
@@ -359,23 +629,24 @@ export class KeyValueStore {
359
629
  *
360
630
  * For more details and code examples, see the {@link KeyValueStore} class.
361
631
  *
362
- * @param [storeIdOrName]
363
- * ID or name of the key-value store to be opened. If `null` or `undefined`,
364
- * the function returns the default key-value store associated with the crawler run.
632
+ * @param [identifier]
633
+ * ID or name of the key-value store to be opened. If a string is provided, it will first be
634
+ * looked up as an ID; if no such storage exists, it will be treated as a name.
635
+ * If `null` or `undefined`, the function returns the default key-value store associated with the crawler run.
365
636
  * @param [options] Storage manager options.
366
637
  */
367
- static async open(storeIdOrName, options = {}) {
368
- checkStorageAccess();
369
- ow(storeIdOrName, ow.optional.any(ow.string, ow.null));
370
- ow(options, ow.object.exactShape({
371
- config: ow.optional.object.instanceOf(Configuration),
372
- storageClient: ow.optional.object,
373
- }));
374
- options.config ??= Configuration.getGlobalConfig();
375
- options.storageClient ??= options.config.getStorageClient();
376
- await purgeDefaultStorages({ onlyPurgeOnce: true, client: options.storageClient, config: options.config });
377
- const manager = StorageManager.getManager(this, options.config);
378
- return manager.openStorage(storeIdOrName, options.storageClient);
638
+ static async open(identifier, options = {}) {
639
+ tryCancel();
640
+ const parsedOptions = parseArgument(options, openOptionsSchema);
641
+ const configuration = parsedOptions.configuration ?? Configuration.getGlobalConfiguration();
642
+ const storageBackend = parsedOptions.storageBackend ?? serviceLocator.getStorageBackend();
643
+ await purgeDefaultStorages({ onlyPurgeOnce: true, storageBackend, configuration });
644
+ const resolved = await resolveStorageIdentifier(identifier, storageBackend, 'KeyValueStore');
645
+ return serviceLocator.getStorageInstanceManager().openStorage(this, {
646
+ ...resolved,
647
+ backendOpener: () => storageBackend.createKeyValueStoreBackend(resolved),
648
+ backendCacheKey: storageBackend.getStorageBackendCacheKey?.() ?? storageBackend.constructor.name,
649
+ });
379
650
  }
380
651
  /**
381
652
  * Gets a value from the default {@link KeyValueStore} associated with the current crawler run.
@@ -410,6 +681,22 @@ export class KeyValueStore {
410
681
  const store = await this.open();
411
682
  return store.getValue(key, defaultValue);
412
683
  }
684
+ /**
685
+ * Reads a record from the default {@link KeyValueStore} associated with the current crawler run
686
+ * without parsing the value.
687
+ *
688
+ * This is just a convenient shortcut for {@link KeyValueStore.getRecord}. Returns `null` if the
689
+ * record does not exist.
690
+ *
691
+ * @param key
692
+ * Unique key of the record. It can be at most 256 characters long and only consist
693
+ * of the following characters: `a`-`z`, `A`-`Z`, `0`-`9` and `!-_.'()`
694
+ * @ignore
695
+ */
696
+ static async getRecord(key) {
697
+ const store = await this.open();
698
+ return store.getRecord(key);
699
+ }
413
700
  /**
414
701
  * Tests whether a record with the given key exists in the default {@link KeyValueStore} associated with the current crawler run.
415
702
  * @param key The queried record key.
@@ -458,50 +745,23 @@ export class KeyValueStore {
458
745
  const store = await this.open();
459
746
  return store.setValue(key, value, options);
460
747
  }
461
- /**
462
- * Gets the crawler input value from the default {@link KeyValueStore} associated with the current crawler run.
463
- * By default, it will try to find root input files (either extension-less, `.json` or `.txt`),
464
- * or alternatively read the input from the default {@link KeyValueStore}.
465
- *
466
- * Note that the `getInput()` function does not cache the value read from the key-value store.
467
- * If you need to use the input multiple times in your crawler,
468
- * it is far more efficient to read it once and store it locally.
469
- *
470
- * For more information, see {@link KeyValueStore.open}
471
- * and {@link KeyValueStore.getValue}.
472
- *
473
- * @returns
474
- * Returns a promise that resolves to an object, string
475
- * or [`Buffer`](https://nodejs.org/api/buffer.html), depending
476
- * on the MIME content type of the record, or `null`
477
- * if the record is missing.
478
- * @ignore
479
- */
480
- static async getInput() {
481
- const store = await this.open();
482
- const inputKey = store.config.get('inputKey');
483
- const cwd = process.cwd();
484
- const possibleExtensions = ['', '.json', '.txt'];
485
- // Attempt to read input from root file instead of key-value store
486
- for (const extension of possibleExtensions) {
487
- const inputFile = join(cwd, `${inputKey}${extension}`);
488
- let input;
489
- // Try getting the file from the file system
490
- try {
491
- input = await readFile(inputFile);
492
- }
493
- catch {
494
- continue;
495
- }
496
- // Attempt to parse as JSON, or return the input as is otherwise
497
- try {
498
- return JSON5.parse(input.toString());
499
- }
500
- catch {
501
- return input;
502
- }
503
- }
504
- return store.getValue(inputKey);
748
+ }
749
+ /** Normalizes a codec-serialized value into the `Buffer | ArrayBuffer` shape raw record reads promise. */
750
+ function normalizeSerializedValue(value) {
751
+ if (typeof value === 'string') {
752
+ return Buffer.from(value);
753
+ }
754
+ if (ArrayBuffer.isView(value)) {
755
+ return Buffer.isBuffer(value) ? value : Buffer.from(value.buffer, value.byteOffset, value.byteLength);
505
756
  }
757
+ return value;
758
+ }
759
+ /** Computes the key listing item (serialized byte size and content type) of a buffered entry. */
760
+ function bufferedKeyItemData(key, entry) {
761
+ const serialized = serializeValue(entry.value, entry.options?.contentType);
762
+ return {
763
+ key,
764
+ size: normalizeSerializedValue(serialized.value).byteLength,
765
+ contentType: serialized.contentType,
766
+ };
506
767
  }
507
- //# sourceMappingURL=key_value_store.js.map