keyv 5.5.3 → 5.5.5

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)
@@ -208,8 +209,12 @@ Keyv supports hooks for `get`, `set`, and `delete` methods. Hooks are useful for
208
209
  ```
209
210
  PRE_GET
210
211
  POST_GET
212
+ PRE_GET_RAW
213
+ POST_GET_RAW
211
214
  PRE_GET_MANY
212
215
  POST_GET_MANY
216
+ PRE_GET_MANY_RAW
217
+ POST_GET_MANY_RAW
213
218
  PRE_SET
214
219
  POST_SET
215
220
  PRE_DELETE
@@ -222,6 +227,37 @@ You can access this by importing `KeyvHooks` from the main Keyv package.
222
227
  import Keyv, { KeyvHooks } from 'keyv';
223
228
  ```
224
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
+
225
261
  ```js
226
262
  //PRE_SET hook
227
263
  const keyv = new Keyv();
@@ -245,6 +281,8 @@ keyv.hooks.addHandler(KeyvHooks.PRE_SET, (data) => {
245
281
 
246
282
  Now this key will have prefix- added to it before it is set.
247
283
 
284
+ ## Delete Hooks
285
+
248
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.
249
287
 
250
288
 
@@ -313,16 +351,78 @@ const keyv = new Keyv({ store: lru });
313
351
 
314
352
  The following are third-party storage adapters compatible with Keyv:
315
353
 
316
- - [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
317
360
  - [keyv-file](https://github.com/zaaack/keyv-file) - File system storage adapter for Keyv
318
- - [keyv-lru](https://www.npmjs.com/package/keyv-lru) - LRU storage adapter for Keyv
319
- - [keyv-null](https://www.npmjs.com/package/keyv-null) - Null storage adapter for Keyv
320
361
  - [keyv-firestore ](https://github.com/goto-bus-stop/keyv-firestore) – Firebase Cloud Firestore adapter for Keyv
321
- - [keyv-mssql](https://github.com/pmorgan3/keyv-mssql) - Microsoft Sql Server adapter for Keyv
322
- - [keyv-azuretable](https://github.com/howlowck/keyv-azuretable) - Azure Table Storage/API adapter for Keyv
323
- - [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
324
363
  - [keyv-momento](https://github.com/momentohq/node-keyv-adaptor/) - Momento storage adapter for Keyv
325
- - [@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).
326
426
 
327
427
  # Compression
328
428
 
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;
@@ -968,3 +984,4 @@ var index_default = Keyv;
968
984
  Keyv,
969
985
  KeyvHooks
970
986
  });
987
+ /* v8 ignore next -- @preserve */
package/dist/index.d.cts CHANGED
@@ -89,7 +89,7 @@ type IEventEmitter = {
89
89
  };
90
90
  type KeyvStoreAdapter = {
91
91
  opts: any;
92
- namespace?: string;
92
+ namespace?: string | undefined;
93
93
  get<Value>(key: string): Promise<StoredData<Value> | undefined>;
94
94
  set(key: string, value: any, ttl?: number): any;
95
95
  setMany?(values: Array<{
@@ -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
@@ -89,7 +89,7 @@ type IEventEmitter = {
89
89
  };
90
90
  type KeyvStoreAdapter = {
91
91
  opts: any;
92
- namespace?: string;
92
+ namespace?: string | undefined;
93
93
  get<Value>(key: string): Promise<StoredData<Value> | undefined>;
94
94
  set(key: string, value: any, ttl?: number): any;
95
95
  setMany?(values: Array<{
@@ -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;
@@ -942,3 +958,4 @@ export {
942
958
  KeyvHooks,
943
959
  index_default as default
944
960
  };
961
+ /* v8 ignore next -- @preserve */
package/package.json CHANGED
@@ -1,15 +1,21 @@
1
1
  {
2
2
  "name": "keyv",
3
- "version": "5.5.3",
3
+ "version": "5.5.5",
4
4
  "description": "Simple key-value storage with support for multiple backends",
5
5
  "type": "module",
6
- "main": "dist/index.cjs",
6
+ "main": "dist/index.js",
7
7
  "module": "dist/index.js",
8
8
  "types": "dist/index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
- "require": "./dist/index.cjs",
12
- "import": "./dist/index.js"
11
+ "require": {
12
+ "types": "./dist/index.d.cts",
13
+ "default": "./dist/index.cjs"
14
+ },
15
+ "import": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ }
13
19
  }
14
20
  },
15
21
  "repository": {
@@ -47,20 +53,20 @@
47
53
  "@keyv/serialize": "^1.1.1"
48
54
  },
49
55
  "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",
56
+ "@biomejs/biome": "^2.3.8",
57
+ "@faker-js/faker": "^10.1.0",
58
+ "@vitest/coverage-v8": "^4.0.14",
59
+ "rimraf": "^6.1.2",
54
60
  "timekeeper": "^2.3.1",
55
61
  "tsd": "^0.33.0",
56
- "vitest": "^3.2.4",
57
- "@keyv/mongo": "^3.0.3",
58
- "@keyv/compress-gzip": "^2.0.3",
59
- "@keyv/compress-lz4": "^1.0.1",
62
+ "vitest": "^4.0.14",
63
+ "@keyv/mongo": "^3.0.5",
60
64
  "@keyv/memcache": "^2.0.2",
61
- "@keyv/sqlite": "^4.0.5",
62
65
  "@keyv/compress-brotli": "^2.0.5",
63
- "@keyv/test-suite": "^2.1.1"
66
+ "@keyv/sqlite": "^4.0.6",
67
+ "@keyv/compress-lz4": "^1.0.1",
68
+ "@keyv/compress-gzip": "^2.0.3",
69
+ "@keyv/test-suite": "^2.1.2"
64
70
  },
65
71
  "tsd": {
66
72
  "directory": "test"