keyv 6.0.0-beta.3 → 6.0.0-beta.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/dist/index.cjs CHANGED
@@ -177,6 +177,26 @@ function detectKeyvStorage(obj) {
177
177
  };
178
178
  }
179
179
  /**
180
+ * Build the capability descriptor for a v6 storage adapter: the structurally detected
181
+ * methods plus `expires: true`, declaring that the adapter accepts an absolute `expires`
182
+ * timestamp on `set`/`setMany`. First-party adapters expose this from their `capabilities`
183
+ * getter so Keyv uses them directly instead of bridging.
184
+ * @param adapter - The storage adapter to describe (typically `this`).
185
+ * @returns A {@link KeyvStorageCapability} with `expires` set to `true`.
186
+ * @example
187
+ * ```typescript
188
+ * public get capabilities(): KeyvStorageCapability {
189
+ * return keyvStorageCapability(this);
190
+ * }
191
+ * ```
192
+ */
193
+ function keyvStorageCapability(adapter) {
194
+ return {
195
+ ...detectKeyvStorage(adapter),
196
+ expires: true
197
+ };
198
+ }
199
+ /**
180
200
  * Detect whether an object implements the Keyv compression adapter interface
181
201
  * @param obj - The object to check
182
202
  * @returns A {@link KeyvCompressionCapability} where `compatible` is `true` when both `compress` and `decompress` methods are present
@@ -479,6 +499,12 @@ var KeyvBridgeAdapter = class extends hookified.Hookified {
479
499
  _keySeparator = ":";
480
500
  _capabilities;
481
501
  /**
502
+ * Whether the wrapped store manages its own namespace (exposes a `namespace` property).
503
+ * When true the bridge propagates its namespace to the store and does not prefix keys,
504
+ * so the store's native, namespace-scoped operations (notably `clear()`) are used directly.
505
+ */
506
+ _storeHandlesNamespace;
507
+ /**
482
508
  * Creates a new KeyvBridgeAdapter instance.
483
509
  * @param store - The underlying promise-based store to bridge
484
510
  * @param options - Configuration options for the adapter
@@ -486,9 +512,11 @@ var KeyvBridgeAdapter = class extends hookified.Hookified {
486
512
  constructor(store, options) {
487
513
  super({ throwOnHookError: false });
488
514
  this._store = store;
515
+ this._capabilities = detectKeyvStorage(store);
516
+ this._storeHandlesNamespace = this._capabilities.store === "keyvStorage" && "namespace" in store;
489
517
  if (options?.keySeparator) this._keySeparator = options.keySeparator;
490
518
  if (options?.namespace) this._namespace = options.namespace;
491
- this._capabilities = detectKeyvStorage(store);
519
+ if (this._storeHandlesNamespace) this._store.namespace = this._namespace;
492
520
  if (typeof store.on === "function") store.on("error", (error) => this.emit("error", error));
493
521
  }
494
522
  /**
@@ -504,10 +532,15 @@ var KeyvBridgeAdapter = class extends hookified.Hookified {
504
532
  this._store = store;
505
533
  }
506
534
  /**
507
- * Gets the detected capabilities of the underlying store.
535
+ * Gets the capabilities of the underlying store, with `expires: true` to declare that
536
+ * the bridge accepts an absolute `expires` timestamp (which it converts to a ttl for the
537
+ * wrapped legacy store).
508
538
  */
509
539
  get capabilities() {
510
- return this._capabilities;
540
+ return {
541
+ ...this._capabilities,
542
+ expires: true
543
+ };
511
544
  }
512
545
  /**
513
546
  * Gets the current key separator used between namespace and key.
@@ -528,10 +561,12 @@ var KeyvBridgeAdapter = class extends hookified.Hookified {
528
561
  return this._namespace;
529
562
  }
530
563
  /**
531
- * Sets the namespace.
564
+ * Sets the namespace. When the wrapped store manages its own namespace, the value is
565
+ * propagated to it so its native scoped operations stay in sync.
532
566
  */
533
567
  set namespace(namespace) {
534
568
  this._namespace = namespace;
569
+ if (this._storeHandlesNamespace) this._store.namespace = namespace;
535
570
  }
536
571
  /**
537
572
  * Creates a prefixed key by combining the namespace and key with the separator.
@@ -540,6 +575,7 @@ var KeyvBridgeAdapter = class extends hookified.Hookified {
540
575
  * @returns The prefixed key if namespace is provided, otherwise the original key
541
576
  */
542
577
  getKeyPrefix(key, namespace) {
578
+ if (this._storeHandlesNamespace) return key;
543
579
  if (namespace) return `${namespace}${this._keySeparator}${key}`;
544
580
  return key;
545
581
  }
@@ -605,35 +641,49 @@ var KeyvBridgeAdapter = class extends hookified.Hookified {
605
641
  return values;
606
642
  }
607
643
  /**
608
- * Stores a value in the store with an optional TTL.
644
+ * Stores a value in the store with an optional absolute expiry.
645
+ * The wrapped store's `set(key, value, ttl?)` expects a relative duration, so the absolute
646
+ * `expires` is converted to a remaining ttl (`undefined` when already expired or absent).
647
+ * The value is passed through unchanged — the bridge does not wrap it in an envelope — so
648
+ * expiry is enforced by the wrapped legacy store's own ttl handling. (The read-side
649
+ * {@link isDataExpired} check only fires when a caller stores a raw `{ value, expires }`
650
+ * object directly; when Keyv core drives the bridge the value arrives already encoded.)
609
651
  * @param key - The key to store the value under
610
652
  * @param value - The value to store
611
- * @param ttl - Optional time-to-live in milliseconds
653
+ * @param expires - Optional absolute expiry as Unix ms since epoch
612
654
  * @returns Always returns true indicating success
613
655
  */
614
- async set(key, value, ttl) {
656
+ async set(key, value, expires) {
615
657
  const keyPrefix = this.getKeyPrefix(key, this._namespace);
616
- const result = await this._store.set(keyPrefix, value, ttl);
658
+ if (typeof expires === "number" && expires <= Date.now()) {
659
+ await this._store.delete(keyPrefix);
660
+ return true;
661
+ }
662
+ const result = await this._store.set(keyPrefix, value, ttlFromExpires(expires));
617
663
  if (typeof result === "boolean") return result;
618
664
  return true;
619
665
  }
620
666
  /**
621
667
  * Stores multiple entries in the store at once.
622
668
  * Delegates to the store's native setMany if available, otherwise loops over set.
623
- * @param entries - Array of entries containing key, value, and optional TTL
669
+ * @param entries - Array of entries containing key, value, and optional absolute `expires`
624
670
  */
625
671
  async setMany(entries) {
626
672
  if (this._capabilities.methods.setMany.exists) {
627
- const prefixedEntries = entries.map((entry) => ({
628
- ...entry,
629
- key: this.getKeyPrefix(entry.key, this._namespace)
630
- }));
631
- await this._store.setMany?.(prefixedEntries);
673
+ const now = Date.now();
674
+ const isExpired = (entry) => typeof entry.expires === "number" && entry.expires <= now;
675
+ const live = entries.filter((entry) => !isExpired(entry));
676
+ if (live.length > 0) await this._store.setMany?.(live.map((entry) => ({
677
+ key: this.getKeyPrefix(entry.key, this._namespace),
678
+ value: entry.value,
679
+ ttl: ttlFromExpires(entry.expires)
680
+ })));
681
+ for (const entry of entries) if (isExpired(entry)) await this._store.delete(this.getKeyPrefix(entry.key, this._namespace));
632
682
  return entries.map(() => true);
633
683
  }
634
684
  const results = [];
635
685
  for (const entry of entries) {
636
- await this.set(entry.key, entry.value, entry.ttl);
686
+ await this.set(entry.key, entry.value, entry.expires);
637
687
  results.push(true);
638
688
  }
639
689
  return results;
@@ -707,6 +757,10 @@ var KeyvBridgeAdapter = class extends hookified.Hookified {
707
757
  * entire store is cleared.
708
758
  */
709
759
  async clear() {
760
+ if (this._namespace && this._storeHandlesNamespace) {
761
+ await this._store.clear();
762
+ return;
763
+ }
710
764
  if (!this._namespace || !this._capabilities.methods.iterator.exists) {
711
765
  await this._store.clear();
712
766
  return;
@@ -728,7 +782,7 @@ var KeyvBridgeAdapter = class extends hookified.Hookified {
728
782
  async *iterator() {
729
783
  if (!this._capabilities.methods.iterator.exists) return;
730
784
  const namespace = this._namespace;
731
- const prefix = namespace ? `${namespace}${this._keySeparator}` : void 0;
785
+ const prefix = namespace && !this._storeHandlesNamespace ? `${namespace}${this._keySeparator}` : void 0;
732
786
  /* v8 ignore next -- @preserve */
733
787
  for await (const entry of this._store.iterator?.(this._namespace) ?? []) {
734
788
  const [key, data] = Array.isArray(entry) ? entry : [entry];
@@ -1168,6 +1222,29 @@ var KeyvStats = class {
1168
1222
  //#endregion
1169
1223
  //#region src/keyv.ts
1170
1224
  var Keyv = class Keyv extends hookified.Hookified {
1225
+ /**
1226
+ * Keyv Constructor
1227
+ * @param {KeyvStorageAdapter | KeyvOptions} store
1228
+ * @param {Omit<KeyvOptions, 'store'>} [options] if you provide the store you can then provide the Keyv Options
1229
+ */
1230
+ constructor(store, options) {
1231
+ const mergedOptions = Keyv.resolveOptions(store, options);
1232
+ super({
1233
+ throwOnHookError: false,
1234
+ throwOnEmptyListeners: true,
1235
+ throwOnEmitError: mergedOptions.throwOnErrors ?? false
1236
+ });
1237
+ this.deprecatedHooks = buildDeprecatedHooks();
1238
+ this._compression = mergedOptions.compression;
1239
+ this._encryption = mergedOptions.encryption;
1240
+ this.initSerialization(mergedOptions);
1241
+ this.initSanitize(mergedOptions);
1242
+ this.initNamespace(mergedOptions.namespace);
1243
+ this.initStats(mergedOptions);
1244
+ if (mergedOptions.store) this.setStore(mergedOptions.store);
1245
+ this.setTtl(mergedOptions.ttl);
1246
+ this._checkExpired = mergedOptions.checkExpired ?? false;
1247
+ }
1171
1248
  /**
1172
1249
  * Stats manager for tracking cache operation metrics (hits, misses, sets, deletes, errors).
1173
1250
  * @default this is disabled.
@@ -1207,29 +1284,6 @@ var Keyv = class Keyv extends hookified.Hookified {
1207
1284
  */
1208
1285
  _checkExpired = false;
1209
1286
  /**
1210
- * Keyv Constructor
1211
- * @param {KeyvStorageAdapter | KeyvOptions} store
1212
- * @param {Omit<KeyvOptions, 'store'>} [options] if you provide the store you can then provide the Keyv Options
1213
- */
1214
- constructor(store, options) {
1215
- const mergedOptions = Keyv.resolveOptions(store, options);
1216
- super({
1217
- throwOnHookError: false,
1218
- throwOnEmptyListeners: true,
1219
- throwOnEmitError: mergedOptions.throwOnErrors ?? false
1220
- });
1221
- this.deprecatedHooks = buildDeprecatedHooks();
1222
- this._compression = mergedOptions.compression;
1223
- this._encryption = mergedOptions.encryption;
1224
- this.initSerialization(mergedOptions);
1225
- this.initSanitize(mergedOptions);
1226
- this.initNamespace(mergedOptions.namespace);
1227
- this.initStats(mergedOptions);
1228
- if (mergedOptions.store) this.setStore(mergedOptions.store);
1229
- this.setTtl(mergedOptions.ttl);
1230
- this._checkExpired = mergedOptions.checkExpired ?? false;
1231
- }
1232
- /**
1233
1287
  * Get the current storage adapter.
1234
1288
  * @returns {KeyvStorageAdapter} The current storage adapter.
1235
1289
  */
@@ -1357,13 +1411,6 @@ var Keyv = class Keyv extends hookified.Hookified {
1357
1411
  return this._stats;
1358
1412
  }
1359
1413
  /**
1360
- * When true, Keyv checks expiry at its layer on get/getMany/has/hasMany.
1361
- * When false (default), trusts the storage adapter.
1362
- */
1363
- get checkExpired() {
1364
- return this._checkExpired;
1365
- }
1366
- /**
1367
1414
  * Set the stats. When setting a new instance it will unsubscribe the old listeners
1368
1415
  * and subscribe the new instance.
1369
1416
  * @param {KeyvStats} stats The stats instance to set.
@@ -1374,19 +1421,32 @@ var Keyv = class Keyv extends hookified.Hookified {
1374
1421
  this._stats.subscribe(this);
1375
1422
  }
1376
1423
  /**
1377
- * Resolves a store to a fully-compliant KeyvStorageAdapter using a 3-tier detection chain:
1378
- * 1. If the store already implements the full KeyvStorageAdapter interface, use it directly.
1379
- * 2. If the store is map-like (synchronous get/set/delete/has), wrap it in KeyvMemoryAdapter.
1380
- * 3. If the store has async get/set/delete/clear, wrap it in KeyvBridgeAdapter.
1381
- * 4. Otherwise, emit an error and fall back to a default in-memory KeyvMemoryAdapter.
1424
+ * Get whether Keyv checks expiry at its own layer on get/getMany/has/hasMany.
1425
+ * When false (default), it trusts the storage adapter to handle expiry.
1426
+ * @returns {boolean} `true` if Keyv checks expiry at its layer.
1427
+ */
1428
+ get checkExpired() {
1429
+ return this._checkExpired;
1430
+ }
1431
+ /**
1432
+ * Resolves a store to a fully-compliant KeyvStorageAdapter:
1433
+ * 1. If the store declares the v6 `capabilities.expires` contract, use it directly (this takes
1434
+ * precedence over structural detection, so a full adapter whose async methods aren't written
1435
+ * with the `async` keyword is not mis-bridged).
1436
+ * 2. If the store implements the full async storage interface (but doesn't declare `expires`),
1437
+ * treat it as a legacy relative-`ttl` adapter and wrap it in KeyvBridgeAdapter.
1438
+ * 3. If the store is map-like (synchronous get/set/delete/has), wrap it in KeyvMemoryAdapter.
1439
+ * 4. If the store has async get/set/delete/clear, wrap it in KeyvBridgeAdapter.
1440
+ * 5. Otherwise, emit an error and fall back to a default in-memory KeyvMemoryAdapter.
1382
1441
  *
1383
1442
  * NOTE: this is used for internal but provided public for custom adapter testing
1384
1443
  * @param {unknown} store The store to resolve.
1385
1444
  * @returns {KeyvStorageAdapter} A fully-compliant storage adapter.
1386
1445
  */
1387
1446
  resolveStore(store) {
1447
+ if (store?.capabilities?.expires === true) return store;
1388
1448
  const cap = detectKeyvStorage(store);
1389
- if (cap.store === "keyvStorage") return store;
1449
+ if (cap.store === "keyvStorage") return new KeyvBridgeAdapter(store);
1390
1450
  if (cap.store === "mapLike") return new KeyvMemoryAdapter(store);
1391
1451
  if (cap.store === "asyncMap") return new KeyvBridgeAdapter(store);
1392
1452
  this.emit("error", /* @__PURE__ */ new Error("Could not use the provided storage adapter, falling back to KeyvMemoryAdapter with Map"));
@@ -1444,13 +1504,27 @@ var Keyv = class Keyv extends hookified.Hookified {
1444
1504
  return data.value;
1445
1505
  }
1446
1506
  /**
1447
- * Get many values of keys
1448
- * @param {string[]} keys passing in a single key or multiple as an array
1507
+ * Reads many keys from the store, preferring its native `getMany` and falling back to parallel
1508
+ * single `get`s when an adapter does not implement it. A directly-used v6 adapter is not
1509
+ * structurally required to provide `getMany` (the bridge and memory adapters always do), so
1510
+ * this keeps `getMany`/`getManyRaw` working regardless of the resolved adapter.
1511
+ * @param keys - the keys to read
1512
+ * @returns the raw store results in the same order as `keys`
1513
+ */
1514
+ async storeGetMany(keys) {
1515
+ if (typeof this._store.getMany === "function") return this._store.getMany(keys);
1516
+ return Promise.all(keys.map(async (key) => this._store.get(key)));
1517
+ }
1518
+ /**
1519
+ * Get many values for an array of keys.
1520
+ * @param {string[]} keys the keys to get
1521
+ * @returns {Promise<Array<Value | undefined>>} an array of values in the same order as the
1522
+ * keys, with `undefined` for keys that do not exist or are expired.
1449
1523
  */
1450
1524
  async getMany(keys) {
1451
1525
  keys = this._sanitize.enabled ? this._sanitize.cleanKeys(keys) : keys;
1452
1526
  await this.hookWithDeprecated("before:getMany", { keys });
1453
- const rawData = await this._store.getMany(keys);
1527
+ const rawData = await this.storeGetMany(keys);
1454
1528
  let deserialized;
1455
1529
  if (this._checkExpired) deserialized = await this.decodeWithExpire(keys, rawData);
1456
1530
  else deserialized = await Promise.all(rawData.map(async (row) => {
@@ -1509,7 +1583,7 @@ var Keyv = class Keyv extends hookified.Hookified {
1509
1583
  });
1510
1584
  return result;
1511
1585
  }
1512
- const rawData = await this._store.getMany(keys);
1586
+ const rawData = await this.storeGetMany(keys);
1513
1587
  let result;
1514
1588
  if (this._checkExpired) result = await this.decodeWithExpire(keys, rawData);
1515
1589
  else result = await Promise.all(rawData.map(async (row) => {
@@ -1526,10 +1600,10 @@ var Keyv = class Keyv extends hookified.Hookified {
1526
1600
  }
1527
1601
  /**
1528
1602
  * Set an item to the store
1529
- * @param {string | Array<KeyvEntry<Value>>} key the key to use. If you pass in an array of KeyvEntry it will set many items
1603
+ * @param {string} key the key to use
1530
1604
  * @param {Value} value the value of the key
1531
- * @param {number} [ttl] time to live in milliseconds
1532
- * @returns {boolean} if it sets then it will return a true. On failure will return false.
1605
+ * @param {number} [ttl] time to live in milliseconds. Overrides the instance-level `ttl`.
1606
+ * @returns {Promise<boolean>} `true` if it was set successfully, `false` on failure.
1533
1607
  */
1534
1608
  async set(key, value, ttl) {
1535
1609
  key = this._sanitize.enabled ? this._sanitize.cleanKey(key) : key;
@@ -1555,7 +1629,7 @@ var Keyv = class Keyv extends hookified.Hookified {
1555
1629
  let encodedValue = formattedValue;
1556
1630
  try {
1557
1631
  encodedValue = await this.encode(formattedValue);
1558
- result = await this._store.set(data.key, encodedValue, data.ttl);
1632
+ result = await this._store.set(data.key, encodedValue, expires);
1559
1633
  } catch (error) {
1560
1634
  result = false;
1561
1635
  this.emit("error", error);
@@ -1572,7 +1646,7 @@ var Keyv = class Keyv extends hookified.Hookified {
1572
1646
  /**
1573
1647
  * Set many items to the store
1574
1648
  * @param {Array<KeyvEntry<Value>>} entries the entries to set
1575
- * @returns {boolean[]} will return an array of booleans if it sets then it will return a true. On failure will return false.
1649
+ * @returns {Promise<boolean[]>} an array of booleans, one per entry: `true` if set successfully, `false` on failure.
1576
1650
  */
1577
1651
  async setMany(entries) {
1578
1652
  entries = entries.map((e) => ({
@@ -1601,7 +1675,7 @@ var Keyv = class Keyv extends hookified.Hookified {
1601
1675
  return {
1602
1676
  key,
1603
1677
  value: await this.encode(formattedValue),
1604
- ttl
1678
+ expires
1605
1679
  };
1606
1680
  }));
1607
1681
  const storeResult = await this._store.setMany(serializedEntries);
@@ -1626,7 +1700,7 @@ var Keyv = class Keyv extends hookified.Hookified {
1626
1700
  * The store-level TTL is derived automatically from `value.expires`.
1627
1701
  * @param {string} key the key to set
1628
1702
  * @param {KeyvValue<Value>} value the raw value envelope to store
1629
- * @returns {boolean} if it sets then it will return a true. On failure will return false.
1703
+ * @returns {Promise<boolean>} `true` if it was set successfully, `false` on failure.
1630
1704
  */
1631
1705
  async setRaw(key, value) {
1632
1706
  key = this._sanitize.enabled ? this._sanitize.cleanKey(key) : key;
@@ -1636,11 +1710,12 @@ var Keyv = class Keyv extends hookified.Hookified {
1636
1710
  value
1637
1711
  };
1638
1712
  await this.hookWithDeprecated("before:setRaw", data);
1639
- const ttl = ttlFromExpires(data.value.expires);
1713
+ const expires = data.value.expires;
1714
+ const ttl = ttlFromExpires(expires);
1640
1715
  let result = true;
1641
1716
  try {
1642
1717
  const encodedValue = await this.encode(data.value);
1643
- const storeResult = await this._store.set(data.key, encodedValue, ttl);
1718
+ const storeResult = await this._store.set(data.key, encodedValue, expires);
1644
1719
  if (typeof storeResult === "boolean") result = storeResult;
1645
1720
  } catch (error) {
1646
1721
  result = false;
@@ -1660,7 +1735,7 @@ var Keyv = class Keyv extends hookified.Hookified {
1660
1735
  * Each entry's value should be a KeyvValue object with { value, expires? }. If you need TTL-based expiration,
1661
1736
  * set `expires` on each value directly. The store-level TTL is derived automatically from `value.expires`.
1662
1737
  * @param {KeyvEntry<KeyvValue<Value>>[]} entries the raw entries to set
1663
- * @returns {boolean[]} will return an array of booleans if it sets then it will return a true. On failure will return false.
1738
+ * @returns {Promise<boolean[]>} an array of booleans, one per entry: `true` if set successfully, `false` on failure.
1664
1739
  */
1665
1740
  async setManyRaw(entries) {
1666
1741
  entries = entries.map((e) => ({
@@ -1672,11 +1747,10 @@ var Keyv = class Keyv extends hookified.Hookified {
1672
1747
  await this.hookWithDeprecated("before:setManyRaw", { entries });
1673
1748
  try {
1674
1749
  const rawEntries = await Promise.all(entries.map(async ({ key, value }) => {
1675
- const ttl = ttlFromExpires(value.expires);
1676
1750
  return {
1677
1751
  key,
1678
1752
  value: await this.encode(value),
1679
- ttl
1753
+ expires: value.expires
1680
1754
  };
1681
1755
  }));
1682
1756
  const storeResult = await this._store.setMany(rawEntries);
@@ -1716,7 +1790,7 @@ var Keyv = class Keyv extends hookified.Hookified {
1716
1790
  /**
1717
1791
  * Delete many items from the store
1718
1792
  * @param {string[]} keys the keys to be deleted
1719
- * @returns {boolean[]} array of booleans indicating success for each key
1793
+ * @returns {Promise<boolean[]>} an array of booleans indicating success for each key.
1720
1794
  */
1721
1795
  async deleteMany(keys) {
1722
1796
  /* v8 ignore next -- @preserve */
@@ -1769,7 +1843,7 @@ var Keyv = class Keyv extends hookified.Hookified {
1769
1843
  /**
1770
1844
  * Check if many keys exist
1771
1845
  * @param {string[]} keys the keys to check
1772
- * @returns {boolean[]} will return an array of booleans if the keys exist
1846
+ * @returns {Promise<boolean[]>} an array of booleans in the same order as the keys, `true` if the key exists.
1773
1847
  */
1774
1848
  async hasMany(keys) {
1775
1849
  keys = this._sanitize.enabled ? this._sanitize.cleanKeys(keys) : keys;
@@ -1792,8 +1866,9 @@ var Keyv = class Keyv extends hookified.Hookified {
1792
1866
  return results;
1793
1867
  }
1794
1868
  /**
1795
- * Clear the store
1796
- * @returns {void}
1869
+ * Clear the store. If a namespace is set only entries in that namespace are removed.
1870
+ * Emits a `clear` event.
1871
+ * @returns {Promise<void>} resolves once the entries have been cleared.
1797
1872
  */
1798
1873
  async clear() {
1799
1874
  this.emit("clear");
@@ -2009,10 +2084,14 @@ var KeyvMemoryAdapter = class extends hookified.Hookified {
2009
2084
  if (options?.namespace) this._namespace = options?.namespace;
2010
2085
  }
2011
2086
  /**
2012
- * Gets the detected capabilities of the underlying store.
2087
+ * Gets the capabilities of the underlying store, with `expires: true` to declare that
2088
+ * this adapter accepts an absolute `expires` timestamp (the v6 storage contract).
2013
2089
  */
2014
2090
  get capabilities() {
2015
- return this._capabilities;
2091
+ return {
2092
+ ...this._capabilities,
2093
+ expires: true
2094
+ };
2016
2095
  }
2017
2096
  /**
2018
2097
  * Gets the underlying store instance.
@@ -2089,24 +2168,24 @@ var KeyvMemoryAdapter = class extends hookified.Hookified {
2089
2168
  return entry.value;
2090
2169
  }
2091
2170
  /**
2092
- * Stores a value in the store with an optional TTL.
2171
+ * Stores a value in the store with an optional absolute expiry.
2093
2172
  * @param key - The key to store the value under
2094
2173
  * @param value - The value to store
2095
- * @param ttl - Optional time-to-live in milliseconds
2174
+ * @param expires - Optional absolute expiry as Unix ms since epoch
2096
2175
  * @returns Always returns true indicating success
2097
2176
  */
2098
- async set(key, value, ttl) {
2177
+ async set(key, value, expires) {
2099
2178
  const keyPrefix = this.getKeyPrefix(key, this._namespace);
2100
2179
  const entry = {
2101
2180
  value,
2102
- expires: ttl ? Date.now() + ttl : void 0
2181
+ expires: typeof expires === "number" ? expires : void 0
2103
2182
  };
2104
- this._store.set(keyPrefix, entry, ttl);
2183
+ this._store.set(keyPrefix, entry, ttlFromExpires(expires));
2105
2184
  return true;
2106
2185
  }
2107
2186
  /**
2108
2187
  * Stores multiple entries in the store at once.
2109
- * @param entries - Array of entries containing key, value, and optional TTL
2188
+ * @param entries - Array of entries containing key, value, and optional absolute `expires`
2110
2189
  */
2111
2190
  async setMany(entries) {
2112
2191
  const results = [];
@@ -2114,9 +2193,9 @@ var KeyvMemoryAdapter = class extends hookified.Hookified {
2114
2193
  const keyPrefix = this.getKeyPrefix(entry.key, this._namespace);
2115
2194
  const memEntry = {
2116
2195
  value: entry.value,
2117
- expires: entry.ttl ? Date.now() + entry.ttl : void 0
2196
+ expires: typeof entry.expires === "number" ? entry.expires : void 0
2118
2197
  };
2119
- this._store.set(keyPrefix, memEntry, entry.ttl);
2198
+ this._store.set(keyPrefix, memEntry, ttlFromExpires(entry.expires));
2120
2199
  results.push(true);
2121
2200
  }
2122
2201
  return results;
@@ -2285,3 +2364,4 @@ exports.detectKeyvEncryption = detectKeyvEncryption;
2285
2364
  exports.detectKeyvSerialization = detectKeyvSerialization;
2286
2365
  exports.detectKeyvStorage = detectKeyvStorage;
2287
2366
  exports.jsonSerializer = jsonSerializer;
2367
+ exports.keyvStorageCapability = keyvStorageCapability;