keyv 5.5.2 → 5.5.4

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.
package/README.md CHANGED
@@ -34,6 +34,7 @@ There are a few existing modules similar to Keyv, however Keyv is different beca
34
34
  - [Custom Serializers](#custom-serializers)
35
35
  - [Official Storage Adapters](#official-storage-adapters)
36
36
  - [Third-party Storage Adapters](#third-party-storage-adapters)
37
+ - [Using BigMap to Scale](#using-bigmap-to-scale)
37
38
  - [Compression](#compression)
38
39
  - [API](#api)
39
40
  - [new Keyv([storage-adapter], [options]) or new Keyv([options])](#new-keyvstorage-adapter-options-or-new-keyvoptions)
@@ -44,6 +45,7 @@ There are a few existing modules similar to Keyv, however Keyv is different beca
44
45
  - [.deserialize](#deserialize)
45
46
  - [.compression](#compression)
46
47
  - [.useKeyPrefix](#usekeyprefix)
48
+ - [.stats](#stats)
47
49
  - [Keyv Instance](#keyv-instance)
48
50
  - [.set(key, value, [ttl])](#setkey-value-ttl)
49
51
  - [.setMany(entries)](#setmanyentries)
@@ -207,8 +209,12 @@ Keyv supports hooks for `get`, `set`, and `delete` methods. Hooks are useful for
207
209
  ```
208
210
  PRE_GET
209
211
  POST_GET
212
+ PRE_GET_RAW
213
+ POST_GET_RAW
210
214
  PRE_GET_MANY
211
215
  POST_GET_MANY
216
+ PRE_GET_MANY_RAW
217
+ POST_GET_MANY_RAW
212
218
  PRE_SET
213
219
  POST_SET
214
220
  PRE_DELETE
@@ -221,6 +227,37 @@ You can access this by importing `KeyvHooks` from the main Keyv package.
221
227
  import Keyv, { KeyvHooks } from 'keyv';
222
228
  ```
223
229
 
230
+ ## Get Hooks
231
+
232
+ The `POST_GET` and `POST_GET_RAW` hooks fire on both cache hits and misses. When a cache miss occurs (key doesn't exist or is expired), the hooks receive `undefined` as the value.
233
+
234
+ ```js
235
+ // POST_GET hook - fires on both hits and misses
236
+ const keyv = new Keyv();
237
+ keyv.hooks.addHandler(KeyvHooks.POST_GET, (data) => {
238
+ if (data.value === undefined) {
239
+ console.log(`Cache miss for key: ${data.key}`);
240
+ } else {
241
+ console.log(`Cache hit for key: ${data.key}`, data.value);
242
+ }
243
+ });
244
+
245
+ await keyv.get('existing-key'); // Logs cache hit with value
246
+ await keyv.get('missing-key'); // Logs cache miss with undefined
247
+ ```
248
+
249
+ ```js
250
+ // POST_GET_RAW hook - same behavior as POST_GET
251
+ const keyv = new Keyv();
252
+ keyv.hooks.addHandler(KeyvHooks.POST_GET_RAW, (data) => {
253
+ console.log(`Key: ${data.key}, Value:`, data.value);
254
+ });
255
+
256
+ await keyv.getRaw('foo'); // Logs with value or undefined
257
+ ```
258
+
259
+ ## Set Hooks
260
+
224
261
  ```js
225
262
  //PRE_SET hook
226
263
  const keyv = new Keyv();
@@ -244,6 +281,8 @@ keyv.hooks.addHandler(KeyvHooks.PRE_SET, (data) => {
244
281
 
245
282
  Now this key will have prefix- added to it before it is set.
246
283
 
284
+ ## Delete Hooks
285
+
247
286
  In `PRE_DELETE` and `POST_DELETE` hooks, the value could be a single item or an `Array`. This is based on the fact that `delete` can accept a single key or an `Array` of keys.
248
287
 
249
288
 
@@ -312,16 +351,78 @@ const keyv = new Keyv({ store: lru });
312
351
 
313
352
  The following are third-party storage adapters compatible with Keyv:
314
353
 
315
- - [quick-lru](https://github.com/sindresorhus/quick-lru) - Simple "Least Recently Used" (LRU) cache
354
+ - [@resolid/keyv-sqlite](https://github.com/huijiewei/keyv-sqlite) - A new SQLite storage adapter for Keyv
355
+ - [keyv-arango](https://github.com/TimMikeladze/keyv-arango) - ArangoDB storage adapter for Keyv
356
+ - [keyv-azuretable](https://github.com/howlowck/keyv-azuretable) - Azure Table Storage/API adapter for Keyv
357
+ - [keyv-browser](https://github.com/zaaack/keyv-browser) - Browser storage adapter for Keyv, including localStorage and indexedDB.
358
+ - [keyv-cloudflare](https://npm.im/keyv-cloudflare) - Storage adapter for Cloudflare Workers KV
359
+ - [keyv-dynamodb](https://www.npmjs.com/package/keyv-dynamodb) - DynamoDB storage adapter for Keyv
316
360
  - [keyv-file](https://github.com/zaaack/keyv-file) - File system storage adapter for Keyv
317
- - [keyv-lru](https://www.npmjs.com/package/keyv-lru) - LRU storage adapter for Keyv
318
- - [keyv-null](https://www.npmjs.com/package/keyv-null) - Null storage adapter for Keyv
319
361
  - [keyv-firestore ](https://github.com/goto-bus-stop/keyv-firestore) – Firebase Cloud Firestore adapter for Keyv
320
- - [keyv-mssql](https://github.com/pmorgan3/keyv-mssql) - Microsoft Sql Server adapter for Keyv
321
- - [keyv-azuretable](https://github.com/howlowck/keyv-azuretable) - Azure Table Storage/API adapter for Keyv
322
- - [keyv-arango](https://github.com/TimMikeladze/keyv-arango) - ArangoDB storage adapter for Keyv
362
+ - [keyv-lru](https://www.npmjs.com/package/keyv-lru) - LRU storage adapter for Keyv
323
363
  - [keyv-momento](https://github.com/momentohq/node-keyv-adaptor/) - Momento storage adapter for Keyv
324
- - [@resolid/keyv-sqlite](https://github.com/huijiewei/keyv-sqlite) - A new SQLite storage adapter for Keyv
364
+ - [keyv-mssql](https://github.com/pmorgan3/keyv-mssql) - Microsoft Sql Server adapter for Keyv
365
+ - [keyv-null](https://www.npmjs.com/package/keyv-null) - Null storage adapter for Keyv
366
+ - [keyv-upstash](https://github.com/mahdavipanah/keyv-upstash) - Upstash Redis adapter for Keyv
367
+ - [quick-lru](https://github.com/sindresorhus/quick-lru) - Simple "Least Recently Used" (LRU) cache
368
+
369
+ # Using BigMap to Scale
370
+
371
+ ## Understanding JavaScript Map Limitations
372
+
373
+ JavaScript's built-in `Map` object has a practical limit of approximately **16.7 million entries** (2^24). When you try to store more entries than this limit, you'll encounter performance degradation or runtime errors. This limitation is due to how JavaScript engines internally manage Map objects.
374
+
375
+ For applications that need to cache millions of entries in memory, this becomes a significant constraint. Common scenarios include:
376
+ - High-traffic caching layers
377
+ - Session stores for large-scale applications
378
+ - In-memory data processing of large datasets
379
+ - Real-time analytics with millions of data points
380
+
381
+ ## Why BigMap?
382
+
383
+ `@keyv/bigmap` solves this limitation by using a **distributed hash approach** with multiple internal Map instances. Instead of storing all entries in a single Map, BigMap distributes entries across multiple Maps using a hash function. This allows you to scale beyond the 16.7 million entry limit while maintaining the familiar Map API.
384
+
385
+ ### Key Benefits:
386
+ - **Scales beyond Map limits**: Store 20+ million entries without issues
387
+ - **Map-compatible API**: Drop-in replacement for standard Map
388
+ - **Performance**: Uses efficient DJB2 hashing for fast key distribution
389
+ - **Type-safe**: Built with TypeScript and supports generics
390
+ - **Customizable**: Configure store size and hash functions
391
+
392
+ ## Using BigMap with Keyv
393
+
394
+ BigMap can be used directly with Keyv as a storage adapter, providing scalable in-memory storage with full TTL support.
395
+
396
+ ### Installation
397
+
398
+ ```bash
399
+ npm install --save keyv @keyv/bigmap
400
+ ```
401
+
402
+ ### Basic Usage
403
+
404
+ The simplest way to use BigMap with Keyv is through the `createKeyv` helper function:
405
+
406
+ ```js
407
+ import { createKeyv } from '@keyv/bigmap';
408
+
409
+ const keyv = createKeyv();
410
+
411
+ // Set values with TTL (time in milliseconds)
412
+ await keyv.set('user:1', { name: 'Alice', email: 'alice@example.com' }, 60000); // Expires in 60 seconds
413
+
414
+ // Get values
415
+ const user = await keyv.get('user:1');
416
+ console.log(user); // { name: 'Alice', email: 'alice@example.com' }
417
+
418
+ // Delete values
419
+ await keyv.delete('user:1');
420
+
421
+ // Clear all values
422
+ await keyv.clear();
423
+ ```
424
+
425
+ For more details about BigMap, see the [@keyv/bigmap documentation](https://github.com/jaredwray/keyv/tree/main/packages/bigmap).
325
426
 
326
427
  # Compression
327
428
 
@@ -672,6 +773,55 @@ const keyv = new Keyv({ store: keyvRedis, throwOnErrors: true });
672
773
 
673
774
  What this does is it only throw on connection errors with the Redis client.
674
775
 
776
+ ## .stats
777
+ Type: `StatsManager`<br />
778
+ Default: `StatsManager` instance with `enabled: false`
779
+
780
+ The stats property provides access to statistics tracking for cache operations. When enabled via the `stats` option during initialization, it tracks hits, misses, sets, deletes, and errors.
781
+
782
+ ### Enabling Stats:
783
+ ```js
784
+ const keyv = new Keyv({ stats: true });
785
+ console.log(keyv.stats.enabled); // true
786
+ ```
787
+
788
+ ### Available Statistics:
789
+ - `hits`: Number of successful cache retrievals
790
+ - `misses`: Number of failed cache retrievals
791
+ - `sets`: Number of set operations
792
+ - `deletes`: Number of delete operations
793
+ - `errors`: Number of errors encountered
794
+
795
+ ### Accessing Stats:
796
+ ```js
797
+ const keyv = new Keyv({ stats: true });
798
+
799
+ await keyv.set('foo', 'bar');
800
+ await keyv.get('foo'); // cache hit
801
+ await keyv.get('nonexistent'); // cache miss
802
+ await keyv.delete('foo');
803
+
804
+ console.log(keyv.stats.hits); // 1
805
+ console.log(keyv.stats.misses); // 1
806
+ console.log(keyv.stats.sets); // 1
807
+ console.log(keyv.stats.deletes); // 1
808
+ ```
809
+
810
+ ### Resetting Stats:
811
+ ```js
812
+ keyv.stats.reset();
813
+ console.log(keyv.stats.hits); // 0
814
+ ```
815
+
816
+ ### Manual Control:
817
+ You can also manually enable/disable stats tracking at runtime:
818
+ ```js
819
+ const keyv = new Keyv({ stats: false });
820
+ keyv.stats.enabled = true; // Enable stats tracking
821
+ // ... perform operations ...
822
+ keyv.stats.enabled = false; // Disable stats tracking
823
+ ```
824
+
675
825
  # How to Contribute
676
826
 
677
827
  We welcome contributions to Keyv! 🎉 Here are some guides to get you started with contributing:
package/dist/index.cjs CHANGED
@@ -394,14 +394,14 @@ var Keyv = class extends event_manager_default {
394
394
  }
395
395
  /**
396
396
  * Get the current TTL.
397
- * @returns {number} The current TTL.
397
+ * @returns {number} The current TTL in milliseconds.
398
398
  */
399
399
  get ttl() {
400
400
  return this._ttl;
401
401
  }
402
402
  /**
403
403
  * Set the current TTL.
404
- * @param {number} ttl The TTL to set.
404
+ * @param {number} ttl The TTL to set in milliseconds.
405
405
  */
406
406
  set ttl(ttl) {
407
407
  this.opts.ttl = ttl;
@@ -540,11 +540,19 @@ var Keyv = class extends event_manager_default {
540
540
  }
541
541
  const deserializedData = typeof rawData === "string" || this.opts.compression ? await this.deserializeData(rawData) : rawData;
542
542
  if (deserializedData === void 0 || deserializedData === null) {
543
+ this.hooks.trigger("postGet" /* POST_GET */, {
544
+ key: keyPrefixed,
545
+ value: void 0
546
+ });
543
547
  this.stats.miss();
544
548
  return void 0;
545
549
  }
546
550
  if (isDataExpired(deserializedData)) {
547
551
  await this.delete(key);
552
+ this.hooks.trigger("postGet" /* POST_GET */, {
553
+ key: keyPrefixed,
554
+ value: void 0
555
+ });
548
556
  this.stats.miss();
549
557
  return void 0;
550
558
  }
@@ -624,12 +632,20 @@ var Keyv = class extends event_manager_default {
624
632
  this.hooks.trigger("preGetRaw" /* PRE_GET_RAW */, { key: keyPrefixed });
625
633
  const rawData = await store.get(keyPrefixed);
626
634
  if (rawData === void 0 || rawData === null) {
635
+ this.hooks.trigger("postGetRaw" /* POST_GET_RAW */, {
636
+ key: keyPrefixed,
637
+ value: void 0
638
+ });
627
639
  this.stats.miss();
628
640
  return void 0;
629
641
  }
630
642
  const deserializedData = typeof rawData === "string" || this.opts.compression ? await this.deserializeData(rawData) : rawData;
631
643
  if (deserializedData !== void 0 && deserializedData.expires !== void 0 && deserializedData.expires !== null && // biome-ignore lint/style/noNonNullAssertion: need to fix
632
644
  deserializedData.expires < Date.now()) {
645
+ this.hooks.trigger("postGetRaw" /* POST_GET_RAW */, {
646
+ key: keyPrefixed,
647
+ value: void 0
648
+ });
633
649
  this.stats.miss();
634
650
  await this.delete(key);
635
651
  return void 0;
@@ -776,7 +792,8 @@ var Keyv = class extends event_manager_default {
776
792
  }
777
793
  const formattedValue = { value, expires };
778
794
  const serializedValue = await this.serializeData(formattedValue);
779
- return { key, value: serializedValue, ttl };
795
+ const keyPrefixed = this._getKeyPrefix(key);
796
+ return { key: keyPrefixed, value: serializedValue, ttl };
780
797
  })
781
798
  );
782
799
  results = await this._store.setMany(serializedEntries);
@@ -967,3 +984,4 @@ var index_default = Keyv;
967
984
  Keyv,
968
985
  KeyvHooks
969
986
  });
987
+ /* v8 ignore next -- @preserve */
package/dist/index.d.cts CHANGED
@@ -133,7 +133,7 @@ type KeyvOptions = {
133
133
  */
134
134
  store?: KeyvStoreAdapter | Map<any, any> | any;
135
135
  /**
136
- * Default TTL. Can be overridden by specifying a TTL on `.set()`.
136
+ * Default TTL in milliseconds. Can be overridden by specifying a TTL on `.set()`.
137
137
  * @default undefined
138
138
  */
139
139
  ttl?: number;
@@ -226,12 +226,12 @@ declare class Keyv<GenericValue = any> extends EventManager {
226
226
  set namespace(namespace: string | undefined);
227
227
  /**
228
228
  * Get the current TTL.
229
- * @returns {number} The current TTL.
229
+ * @returns {number} The current TTL in milliseconds.
230
230
  */
231
231
  get ttl(): number | undefined;
232
232
  /**
233
233
  * Set the current TTL.
234
- * @param {number} ttl The TTL to set.
234
+ * @param {number} ttl The TTL to set in milliseconds.
235
235
  */
236
236
  set ttl(ttl: number | undefined);
237
237
  /**
package/dist/index.d.ts CHANGED
@@ -133,7 +133,7 @@ type KeyvOptions = {
133
133
  */
134
134
  store?: KeyvStoreAdapter | Map<any, any> | any;
135
135
  /**
136
- * Default TTL. Can be overridden by specifying a TTL on `.set()`.
136
+ * Default TTL in milliseconds. Can be overridden by specifying a TTL on `.set()`.
137
137
  * @default undefined
138
138
  */
139
139
  ttl?: number;
@@ -226,12 +226,12 @@ declare class Keyv<GenericValue = any> extends EventManager {
226
226
  set namespace(namespace: string | undefined);
227
227
  /**
228
228
  * Get the current TTL.
229
- * @returns {number} The current TTL.
229
+ * @returns {number} The current TTL in milliseconds.
230
230
  */
231
231
  get ttl(): number | undefined;
232
232
  /**
233
233
  * Set the current TTL.
234
- * @param {number} ttl The TTL to set.
234
+ * @param {number} ttl The TTL to set in milliseconds.
235
235
  */
236
236
  set ttl(ttl: number | undefined);
237
237
  /**
package/dist/index.js CHANGED
@@ -368,14 +368,14 @@ var Keyv = class extends event_manager_default {
368
368
  }
369
369
  /**
370
370
  * Get the current TTL.
371
- * @returns {number} The current TTL.
371
+ * @returns {number} The current TTL in milliseconds.
372
372
  */
373
373
  get ttl() {
374
374
  return this._ttl;
375
375
  }
376
376
  /**
377
377
  * Set the current TTL.
378
- * @param {number} ttl The TTL to set.
378
+ * @param {number} ttl The TTL to set in milliseconds.
379
379
  */
380
380
  set ttl(ttl) {
381
381
  this.opts.ttl = ttl;
@@ -514,11 +514,19 @@ var Keyv = class extends event_manager_default {
514
514
  }
515
515
  const deserializedData = typeof rawData === "string" || this.opts.compression ? await this.deserializeData(rawData) : rawData;
516
516
  if (deserializedData === void 0 || deserializedData === null) {
517
+ this.hooks.trigger("postGet" /* POST_GET */, {
518
+ key: keyPrefixed,
519
+ value: void 0
520
+ });
517
521
  this.stats.miss();
518
522
  return void 0;
519
523
  }
520
524
  if (isDataExpired(deserializedData)) {
521
525
  await this.delete(key);
526
+ this.hooks.trigger("postGet" /* POST_GET */, {
527
+ key: keyPrefixed,
528
+ value: void 0
529
+ });
522
530
  this.stats.miss();
523
531
  return void 0;
524
532
  }
@@ -598,12 +606,20 @@ var Keyv = class extends event_manager_default {
598
606
  this.hooks.trigger("preGetRaw" /* PRE_GET_RAW */, { key: keyPrefixed });
599
607
  const rawData = await store.get(keyPrefixed);
600
608
  if (rawData === void 0 || rawData === null) {
609
+ this.hooks.trigger("postGetRaw" /* POST_GET_RAW */, {
610
+ key: keyPrefixed,
611
+ value: void 0
612
+ });
601
613
  this.stats.miss();
602
614
  return void 0;
603
615
  }
604
616
  const deserializedData = typeof rawData === "string" || this.opts.compression ? await this.deserializeData(rawData) : rawData;
605
617
  if (deserializedData !== void 0 && deserializedData.expires !== void 0 && deserializedData.expires !== null && // biome-ignore lint/style/noNonNullAssertion: need to fix
606
618
  deserializedData.expires < Date.now()) {
619
+ this.hooks.trigger("postGetRaw" /* POST_GET_RAW */, {
620
+ key: keyPrefixed,
621
+ value: void 0
622
+ });
607
623
  this.stats.miss();
608
624
  await this.delete(key);
609
625
  return void 0;
@@ -750,7 +766,8 @@ var Keyv = class extends event_manager_default {
750
766
  }
751
767
  const formattedValue = { value, expires };
752
768
  const serializedValue = await this.serializeData(formattedValue);
753
- return { key, value: serializedValue, ttl };
769
+ const keyPrefixed = this._getKeyPrefix(key);
770
+ return { key: keyPrefixed, value: serializedValue, ttl };
754
771
  })
755
772
  );
756
773
  results = await this._store.setMany(serializedEntries);
@@ -941,3 +958,4 @@ export {
941
958
  KeyvHooks,
942
959
  index_default as default
943
960
  };
961
+ /* v8 ignore next -- @preserve */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keyv",
3
- "version": "5.5.2",
3
+ "version": "5.5.4",
4
4
  "description": "Simple key-value storage with support for multiple backends",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -47,20 +47,20 @@
47
47
  "@keyv/serialize": "^1.1.1"
48
48
  },
49
49
  "devDependencies": {
50
- "@biomejs/biome": "^2.2.3",
51
- "@faker-js/faker": "^10.0.0",
52
- "@vitest/coverage-v8": "^3.2.4",
53
- "rimraf": "^6.0.1",
50
+ "@biomejs/biome": "^2.3.3",
51
+ "@faker-js/faker": "^10.1.0",
52
+ "@vitest/coverage-v8": "^4.0.6",
53
+ "rimraf": "^6.1.0",
54
54
  "timekeeper": "^2.3.1",
55
55
  "tsd": "^0.33.0",
56
- "vitest": "^3.2.4",
56
+ "vitest": "^4.0.6",
57
57
  "@keyv/compress-brotli": "^2.0.5",
58
58
  "@keyv/compress-lz4": "^1.0.1",
59
- "@keyv/mongo": "^3.0.3",
60
- "@keyv/memcache": "^2.0.2",
61
59
  "@keyv/compress-gzip": "^2.0.3",
62
- "@keyv/sqlite": "^4.0.5",
63
- "@keyv/test-suite": "^2.1.1"
60
+ "@keyv/memcache": "^2.0.2",
61
+ "@keyv/sqlite": "^4.0.6",
62
+ "@keyv/test-suite": "^2.1.1",
63
+ "@keyv/mongo": "^3.0.5"
64
64
  },
65
65
  "tsd": {
66
66
  "directory": "test"