layercache 1.3.4 → 1.4.0

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 (40) hide show
  1. package/README.md +42 -41
  2. package/dist/{chunk-BORDQ3LA.js → chunk-7KMKQ6QZ.js} +15 -1
  3. package/dist/{chunk-5RCAX2BQ.js → chunk-FFZCC7EQ.js} +3 -3
  4. package/dist/{chunk-4PPBOOXT.js → chunk-KJDFYE5T.js} +38 -26
  5. package/dist/cli.cjs +9 -9
  6. package/dist/cli.js +4 -4
  7. package/dist/{edge-DKkrQ_Ky.d.cts → edge-D2FpRlyS.d.cts} +71 -22
  8. package/dist/{edge-DKkrQ_Ky.d.ts → edge-D2FpRlyS.d.ts} +71 -22
  9. package/dist/edge.cjs +9 -9
  10. package/dist/edge.d.cts +1 -1
  11. package/dist/edge.d.ts +1 -1
  12. package/dist/edge.js +2 -2
  13. package/dist/index.cjs +399 -164
  14. package/dist/index.d.cts +6 -6
  15. package/dist/index.d.ts +6 -6
  16. package/dist/index.js +294 -81
  17. package/package.json +5 -5
  18. package/benchmarks/direct.ts +0 -221
  19. package/benchmarks/edge-utils.ts +0 -28
  20. package/benchmarks/edge.ts +0 -491
  21. package/benchmarks/http.ts +0 -99
  22. package/benchmarks/latency.ts +0 -45
  23. package/benchmarks/memory-pressure.ts +0 -144
  24. package/benchmarks/multi-process-fanout.ts +0 -231
  25. package/benchmarks/multi-process-worker.ts +0 -151
  26. package/benchmarks/paths.ts +0 -25
  27. package/benchmarks/queue-amplification-utils.ts +0 -48
  28. package/benchmarks/queue-amplification.ts +0 -230
  29. package/benchmarks/redis-latency-proxy.ts +0 -100
  30. package/benchmarks/redis.ts +0 -107
  31. package/benchmarks/scenario-utils.ts +0 -38
  32. package/benchmarks/server.ts +0 -157
  33. package/benchmarks/slow-redis-latency.ts +0 -309
  34. package/benchmarks/slow-redis-utils.ts +0 -29
  35. package/benchmarks/slow-redis.ts +0 -47
  36. package/benchmarks/stampede.ts +0 -26
  37. package/benchmarks/stats.ts +0 -46
  38. package/benchmarks/workload.ts +0 -77
  39. package/examples/express-api/index.ts +0 -31
  40. package/examples/nextjs-api-routes/route.ts +0 -16
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  validateAdaptiveTtlOptions,
4
4
  validateCacheKey,
5
5
  validateCircuitBreakerOptions,
6
+ validateContextEntryOptions,
6
7
  validateLayerNumberOption,
7
8
  validateNonNegativeNumber,
8
9
  validatePattern,
@@ -11,22 +12,23 @@ import {
11
12
  validateTag,
12
13
  validateTags,
13
14
  validateTtlPolicy
14
- } from "./chunk-BORDQ3LA.js";
15
+ } from "./chunk-7KMKQ6QZ.js";
15
16
  import {
16
17
  MemoryLayer,
17
18
  TagIndex,
18
19
  createHonoCacheMiddleware
19
- } from "./chunk-5RCAX2BQ.js";
20
+ } from "./chunk-FFZCC7EQ.js";
20
21
  import {
21
22
  PatternMatcher,
22
23
  createStoredValueEnvelope,
24
+ expireStoredEnvelope,
23
25
  isStoredValueEnvelope,
24
26
  refreshStoredEnvelope,
25
- remainingFreshTtlSeconds,
26
- remainingStoredTtlSeconds,
27
+ remainingFreshTtlMs,
28
+ remainingStoredTtlMs,
27
29
  resolveStoredValue,
28
30
  unwrapStoredValue
29
- } from "./chunk-4PPBOOXT.js";
31
+ } from "./chunk-KJDFYE5T.js";
30
32
 
31
33
  // src/CacheStack.ts
32
34
  import { EventEmitter } from "events";
@@ -219,6 +221,9 @@ var CacheNamespace = class _CacheNamespace {
219
221
  async invalidateByTag(tag) {
220
222
  await this.trackMetrics(() => this.cache.invalidateByTag(this.qualifyTag(tag)));
221
223
  }
224
+ async expireByTag(tag) {
225
+ await this.trackMetrics(() => this.cache.expireByTag(this.qualifyTag(tag)));
226
+ }
222
227
  async invalidateByTags(tags, mode = "any") {
223
228
  await this.trackMetrics(
224
229
  () => this.cache.invalidateByTags(
@@ -227,12 +232,26 @@ var CacheNamespace = class _CacheNamespace {
227
232
  )
228
233
  );
229
234
  }
235
+ async expireByTags(tags, mode = "any") {
236
+ await this.trackMetrics(
237
+ () => this.cache.expireByTags(
238
+ tags.map((tag) => this.qualifyTag(tag)),
239
+ mode
240
+ )
241
+ );
242
+ }
230
243
  async invalidateByPattern(pattern) {
231
244
  await this.trackMetrics(() => this.cache.invalidateByPattern(this.qualify(pattern)));
232
245
  }
246
+ async expireByPattern(pattern) {
247
+ await this.trackMetrics(() => this.cache.expireByPattern(this.qualify(pattern)));
248
+ }
233
249
  async invalidateByPrefix(prefix) {
234
250
  await this.trackMetrics(() => this.cache.invalidateByPrefix(this.qualify(prefix)));
235
251
  }
252
+ async expireByPrefix(prefix) {
253
+ await this.trackMetrics(() => this.cache.expireByPrefix(this.qualify(prefix)));
254
+ }
236
255
  /**
237
256
  * Returns detailed metadata about a single cache key within this namespace.
238
257
  */
@@ -580,6 +599,35 @@ var CacheStackInvalidationSupport = class {
580
599
  })
581
600
  );
582
601
  }
602
+ async expireKeysInLayers(layers, keys) {
603
+ const foundKeys = /* @__PURE__ */ new Set();
604
+ await Promise.all(
605
+ layers.map(async (layer) => {
606
+ if (this.options.shouldSkipLayer(layer)) {
607
+ return;
608
+ }
609
+ await Promise.all(
610
+ keys.map(async (key) => {
611
+ try {
612
+ const stored = layer.getEntry ? await layer.getEntry(key) : await layer.get(key);
613
+ if (stored === null) {
614
+ return;
615
+ }
616
+ foundKeys.add(key);
617
+ const expired = expireStoredEnvelope(stored);
618
+ if (expired === stored) {
619
+ return;
620
+ }
621
+ await layer.set(key, expired, remainingStoredTtlMs(expired));
622
+ } catch (error) {
623
+ await this.options.handleLayerFailure(layer, "expire", error);
624
+ }
625
+ })
626
+ );
627
+ })
628
+ );
629
+ return foundKeys;
630
+ }
583
631
  assertWithinInvalidationKeyLimit(size, maxKeys) {
584
632
  if (maxKeys !== false && size > maxKeys) {
585
633
  throw new Error(`Invalidation matched too many keys (${size} > ${maxKeys}).`);
@@ -698,12 +746,12 @@ var CacheStackLayerWriter = class {
698
746
  }
699
747
  buildLayerSetEntry(layer, key, kind, value, writeOptions, now) {
700
748
  const freshTtl = this.options.resolveFreshTtl(key, layer.name, kind, writeOptions, layer.defaultTtl, value);
701
- const staleWhileRevalidate = this.options.resolveLayerSeconds(
749
+ const staleWhileRevalidate = this.options.resolveLayerMs(
702
750
  layer.name,
703
751
  writeOptions?.staleWhileRevalidate,
704
752
  this.options.globalStaleWhileRevalidate
705
753
  );
706
- const staleIfError = this.options.resolveLayerSeconds(
754
+ const staleIfError = this.options.resolveLayerMs(
707
755
  layer.name,
708
756
  writeOptions?.staleIfError,
709
757
  this.options.globalStaleIfError
@@ -711,12 +759,12 @@ var CacheStackLayerWriter = class {
711
759
  const payload = createStoredValueEnvelope({
712
760
  kind,
713
761
  value,
714
- freshTtlSeconds: freshTtl,
715
- staleWhileRevalidateSeconds: staleWhileRevalidate,
716
- staleIfErrorSeconds: staleIfError,
762
+ freshTtlMs: freshTtl,
763
+ staleWhileRevalidateMs: staleWhileRevalidate,
764
+ staleIfErrorMs: staleIfError,
717
765
  now
718
766
  });
719
- const ttl = remainingStoredTtlSeconds(payload, now) ?? freshTtl;
767
+ const ttl = remainingStoredTtlMs(payload, now) ?? freshTtl;
720
768
  return {
721
769
  key,
722
770
  value: payload,
@@ -859,15 +907,15 @@ function planFreshReadPolicies({
859
907
  stored,
860
908
  hasFetcher,
861
909
  slidingTtl,
862
- refreshAheadSeconds
910
+ refreshAheadMs
863
911
  }) {
864
912
  const refreshedStored = slidingTtl && isStoredValueEnvelope(stored) ? refreshStoredEnvelope(stored) : void 0;
865
- const refreshedStoredTtl = refreshedStored ? remainingStoredTtlSeconds(refreshedStored) ?? void 0 : void 0;
866
- const remainingFreshTtl = remainingFreshTtlSeconds(stored) ?? 0;
913
+ const refreshedStoredTtl = refreshedStored ? remainingStoredTtlMs(refreshedStored) ?? void 0 : void 0;
914
+ const remainingFreshTtl = remainingFreshTtlMs(stored) ?? 0;
867
915
  return {
868
916
  refreshedStored,
869
917
  refreshedStoredTtl,
870
- shouldScheduleBackgroundRefresh: hasFetcher && refreshAheadSeconds > 0 && remainingFreshTtl > 0 && remainingFreshTtl <= refreshAheadSeconds
918
+ shouldScheduleBackgroundRefresh: hasFetcher && refreshAheadMs > 0 && remainingFreshTtl > 0 && remainingFreshTtl <= refreshAheadMs
871
919
  };
872
920
  }
873
921
 
@@ -903,7 +951,7 @@ var CacheStackReader = class {
903
951
  this.options.metricsCollector.increment("staleHits");
904
952
  this.options.emit("stale-serve", { key: normalizedKey, state: hit.state, layer: hit.layerName });
905
953
  if (fetcher) {
906
- this.scheduleBackgroundRefresh(normalizedKey, fetcher, options);
954
+ this.scheduleBackgroundRefresh(normalizedKey, fetcher, options, this.createFetcherContext(normalizedKey, hit));
907
955
  }
908
956
  return hit.value;
909
957
  }
@@ -914,7 +962,15 @@ var CacheStackReader = class {
914
962
  return hit.value;
915
963
  }
916
964
  try {
917
- return await this.fetchWithGuards(normalizedKey, fetcher, options);
965
+ return await this.fetchWithGuards(
966
+ normalizedKey,
967
+ fetcher,
968
+ options,
969
+ void 0,
970
+ void 0,
971
+ false,
972
+ this.createFetcherContext(normalizedKey, hit)
973
+ );
918
974
  } catch (error) {
919
975
  this.options.metricsCollector.increment("staleHits");
920
976
  this.options.metricsCollector.increment("refreshErrors");
@@ -929,7 +985,11 @@ var CacheStackReader = class {
929
985
  if (!fetcher) {
930
986
  return null;
931
987
  }
932
- return this.fetchWithGuards(normalizedKey, fetcher, options, void 0, void 0, true);
988
+ return this.fetchWithGuards(normalizedKey, fetcher, options, void 0, void 0, true, {
989
+ key: normalizedKey,
990
+ currentValue: void 0,
991
+ state: "miss"
992
+ });
933
993
  }
934
994
  async readLayerEntry(layer, key) {
935
995
  if (this.options.shouldSkipLayer(layer)) {
@@ -957,7 +1017,7 @@ var CacheStackReader = class {
957
1017
  if (!layer || this.options.shouldSkipLayer(layer)) {
958
1018
  continue;
959
1019
  }
960
- const ttl = remainingStoredTtlSeconds(stored) ?? this.options.resolveLayerSeconds(layer.name, options?.ttl, void 0, layer.defaultTtl);
1020
+ const ttl = remainingStoredTtlMs(stored) ?? this.options.resolveLayerMs(layer.name, options?.ttl, void 0, layer.defaultTtl);
961
1021
  try {
962
1022
  await layer.set(key, stored, ttl);
963
1023
  } catch (error) {
@@ -1024,7 +1084,11 @@ var CacheStackReader = class {
1024
1084
  this.options.emit("miss", { key, mode });
1025
1085
  return { found: false, value: null, stored: null, state: "miss" };
1026
1086
  }
1027
- async fetchWithGuards(key, fetcher, options, expectedClearEpoch, expectedKeyEpoch, initialMissConfirmed = false) {
1087
+ async fetchWithGuards(key, fetcher, options, expectedClearEpoch, expectedKeyEpoch, initialMissConfirmed = false, fetcherContext = {
1088
+ key,
1089
+ currentValue: void 0,
1090
+ state: "miss"
1091
+ }) {
1028
1092
  const fetchTask = async () => {
1029
1093
  const shouldRecheckFreshLayers = !(initialMissConfirmed && this.options.singleFlightCoordinator);
1030
1094
  if (shouldRecheckFreshLayers) {
@@ -1034,7 +1098,7 @@ var CacheStackReader = class {
1034
1098
  return secondHit.value;
1035
1099
  }
1036
1100
  }
1037
- return this.fetchAndPopulate(key, fetcher, options, expectedClearEpoch, expectedKeyEpoch);
1101
+ return this.fetchAndPopulate(key, fetcher, options, expectedClearEpoch, expectedKeyEpoch, fetcherContext);
1038
1102
  };
1039
1103
  const singleFlightTask = async () => {
1040
1104
  if (!this.options.singleFlightCoordinator) {
@@ -1045,7 +1109,7 @@ var CacheStackReader = class {
1045
1109
  key,
1046
1110
  this.resolveSingleFlightOptions(),
1047
1111
  fetchTask,
1048
- () => this.waitForFreshValue(key, fetcher, options, expectedClearEpoch, expectedKeyEpoch)
1112
+ () => this.waitForFreshValue(key, fetcher, options, expectedClearEpoch, expectedKeyEpoch, fetcherContext)
1049
1113
  );
1050
1114
  } catch (error) {
1051
1115
  if (!this.options.isGracefulDegradationEnabled()) {
@@ -1069,7 +1133,11 @@ var CacheStackReader = class {
1069
1133
  }
1070
1134
  return this.options.stampedeGuard.execute(key, singleFlightTask);
1071
1135
  }
1072
- async waitForFreshValue(key, fetcher, options, expectedClearEpoch, expectedKeyEpoch) {
1136
+ async waitForFreshValue(key, fetcher, options, expectedClearEpoch, expectedKeyEpoch, fetcherContext = {
1137
+ key,
1138
+ currentValue: void 0,
1139
+ state: "miss"
1140
+ }) {
1073
1141
  const timeoutMs = this.options.singleFlightTimeoutMs ?? DEFAULT_SINGLE_FLIGHT_TIMEOUT_MS;
1074
1142
  const pollIntervalMs = this.options.singleFlightPollMs ?? DEFAULT_SINGLE_FLIGHT_POLL_MS;
1075
1143
  const deadline = Date.now() + timeoutMs;
@@ -1083,9 +1151,13 @@ var CacheStackReader = class {
1083
1151
  }
1084
1152
  await this.options.sleep(pollIntervalMs);
1085
1153
  }
1086
- return this.fetchAndPopulate(key, fetcher, options, expectedClearEpoch, expectedKeyEpoch);
1154
+ return this.fetchAndPopulate(key, fetcher, options, expectedClearEpoch, expectedKeyEpoch, fetcherContext);
1087
1155
  }
1088
- async fetchAndPopulate(key, fetcher, options, expectedClearEpoch, expectedKeyEpoch) {
1156
+ async fetchAndPopulate(key, fetcher, options, expectedClearEpoch, expectedKeyEpoch, fetcherContext = {
1157
+ key,
1158
+ currentValue: void 0,
1159
+ state: "miss"
1160
+ }) {
1089
1161
  this.options.circuitBreakerManager.assertClosed(key, options?.circuitBreaker ?? this.options.circuitBreaker);
1090
1162
  this.options.metricsCollector.increment("fetches");
1091
1163
  const fetchStart = Date.now();
@@ -1094,7 +1166,7 @@ var CacheStackReader = class {
1094
1166
  fetched = await this.options.fetchRateLimiter.schedule(
1095
1167
  options?.fetcherRateLimit ?? this.options.fetcherRateLimit,
1096
1168
  { key, fetcher },
1097
- fetcher
1169
+ () => fetcher(fetcherContext)
1098
1170
  );
1099
1171
  this.options.circuitBreakerManager.recordSuccess(key);
1100
1172
  this.options.logger.debug?.("fetch", { key, durationMs: Date.now() - fetchStart });
@@ -1144,10 +1216,14 @@ var CacheStackReader = class {
1144
1216
  await this.options.storeEntry(key, "value", fetched, options);
1145
1217
  return fetched;
1146
1218
  }
1147
- runScheduleBackgroundRefresh(key, fetcher, options) {
1148
- this.scheduleBackgroundRefresh(key, fetcher, options);
1219
+ runScheduleBackgroundRefresh(key, fetcher, options, fetcherContext) {
1220
+ this.scheduleBackgroundRefresh(key, fetcher, options, fetcherContext);
1149
1221
  }
1150
- scheduleBackgroundRefresh(key, fetcher, options) {
1222
+ scheduleBackgroundRefresh(key, fetcher, options, fetcherContext = {
1223
+ key,
1224
+ currentValue: void 0,
1225
+ state: "miss"
1226
+ }) {
1151
1227
  if (!shouldStartBackgroundRefresh({
1152
1228
  isDisconnecting: this.options.isDisconnecting(),
1153
1229
  hasRefreshInFlight: this.backgroundRefreshes.has(key)
@@ -1161,7 +1237,7 @@ var CacheStackReader = class {
1161
1237
  this.options.metricsCollector.increment("refreshes");
1162
1238
  try {
1163
1239
  if (this.backgroundRefreshAbort.get(key)) return;
1164
- await this.runBackgroundRefresh(key, fetcher, options, clearEpoch, keyEpoch);
1240
+ await this.runBackgroundRefresh(key, fetcher, options, clearEpoch, keyEpoch, fetcherContext);
1165
1241
  } catch (error) {
1166
1242
  if (this.backgroundRefreshAbort.get(key)) return;
1167
1243
  this.options.metricsCollector.increment("refreshErrors");
@@ -1176,16 +1252,22 @@ var CacheStackReader = class {
1176
1252
  })();
1177
1253
  this.backgroundRefreshes.set(key, refresh);
1178
1254
  }
1179
- async runBackgroundRefresh(key, fetcher, options, expectedClearEpoch, expectedKeyEpoch) {
1255
+ async runBackgroundRefresh(key, fetcher, options, expectedClearEpoch, expectedKeyEpoch, fetcherContext = {
1256
+ key,
1257
+ currentValue: void 0,
1258
+ state: "miss"
1259
+ }) {
1180
1260
  const timeoutMs = this.options.backgroundRefreshTimeoutMs ?? DEFAULT_BACKGROUND_REFRESH_TIMEOUT_MS;
1181
1261
  await this.fetchWithGuards(
1182
1262
  key,
1183
- () => this.options.withTimeout(fetcher(), timeoutMs, () => {
1263
+ (context) => this.options.withTimeout(fetcher(context), timeoutMs, () => {
1184
1264
  return new Error(`Background refresh timed out after ${timeoutMs}ms for key "${key}".`);
1185
1265
  }),
1186
1266
  options,
1187
1267
  expectedClearEpoch,
1188
- expectedKeyEpoch
1268
+ expectedKeyEpoch,
1269
+ false,
1270
+ fetcherContext
1189
1271
  );
1190
1272
  }
1191
1273
  async runApplyFreshReadPolicies(key, hit, options, fetcher) {
@@ -1196,7 +1278,7 @@ var CacheStackReader = class {
1196
1278
  stored: hit.stored,
1197
1279
  hasFetcher: Boolean(fetcher),
1198
1280
  slidingTtl: options?.slidingTtl ?? false,
1199
- refreshAheadSeconds: this.options.resolveLayerSeconds(hit.layerName, options?.refreshAhead, this.options.refreshAhead, 0) ?? 0
1281
+ refreshAheadMs: this.options.resolveLayerMs(hit.layerName, options?.refreshAhead, this.options.refreshAhead, 0) ?? 0
1200
1282
  });
1201
1283
  if (plan.refreshedStored) {
1202
1284
  for (let index = 0; index <= hit.layerIndex; index += 1) {
@@ -1212,9 +1294,17 @@ var CacheStackReader = class {
1212
1294
  }
1213
1295
  }
1214
1296
  if (fetcher && plan.shouldScheduleBackgroundRefresh) {
1215
- this.options.scheduleBackgroundRefreshDispatch(key, fetcher, options);
1297
+ this.options.scheduleBackgroundRefreshDispatch(key, fetcher, options, this.createFetcherContext(key, hit));
1216
1298
  }
1217
1299
  }
1300
+ createFetcherContext(key, hit) {
1301
+ return {
1302
+ key,
1303
+ currentValue: hit.value === null ? void 0 : hit.value,
1304
+ state: hit.state,
1305
+ layer: hit.layerName
1306
+ };
1307
+ }
1218
1308
  resolveSingleFlightOptions() {
1219
1309
  return {
1220
1310
  leaseMs: this.options.singleFlightLeaseMs ?? DEFAULT_SINGLE_FLIGHT_LEASE_MS,
@@ -1496,7 +1586,7 @@ var CacheStackSnapshotManager = class {
1496
1586
  await visitor({
1497
1587
  key: exportedKey,
1498
1588
  value: stored,
1499
- ttl: remainingStoredTtlSeconds(stored)
1589
+ ttl: remainingStoredTtlMs(stored)
1500
1590
  });
1501
1591
  };
1502
1592
  if (layer.forEachKey) {
@@ -1960,7 +2050,7 @@ var MetricsCollector = class {
1960
2050
 
1961
2051
  // src/internal/TtlResolver.ts
1962
2052
  import { randomBytes as randomBytes2 } from "crypto";
1963
- var DEFAULT_NEGATIVE_TTL_SECONDS = 60;
2053
+ var DEFAULT_NEGATIVE_TTL_MS = 6e4;
1964
2054
  var secureRandom = {
1965
2055
  value() {
1966
2056
  return randomBytes2(4).readUInt32BE(0) / 4294967296;
@@ -1987,17 +2077,17 @@ var TtlResolver = class {
1987
2077
  }
1988
2078
  resolveFreshTtl(key, layerName, kind, options, fallbackTtl, globalNegativeTtl, globalTtl, value) {
1989
2079
  const policyTtl = kind === "value" ? this.resolvePolicyTtl(key, value, options?.ttlPolicy) : void 0;
1990
- const baseTtl = kind === "empty" ? this.resolveLayerSeconds(
2080
+ const baseTtl = kind === "empty" ? this.resolveLayerMs(
1991
2081
  layerName,
1992
2082
  options?.negativeTtl,
1993
2083
  globalNegativeTtl,
1994
- this.resolveLayerSeconds(layerName, options?.ttl, globalTtl, policyTtl ?? fallbackTtl) ?? DEFAULT_NEGATIVE_TTL_SECONDS
1995
- ) : this.resolveLayerSeconds(layerName, options?.ttl, globalTtl, policyTtl ?? fallbackTtl);
2084
+ this.resolveLayerMs(layerName, options?.ttl, globalTtl, policyTtl ?? fallbackTtl) ?? DEFAULT_NEGATIVE_TTL_MS
2085
+ ) : this.resolveLayerMs(layerName, options?.ttl, globalTtl, policyTtl ?? fallbackTtl);
1996
2086
  const adaptiveTtl = this.applyAdaptiveTtl(key, layerName, baseTtl, options?.adaptiveTtl);
1997
- const jitter = this.resolveLayerSeconds(layerName, options?.ttlJitter, void 0);
2087
+ const jitter = this.resolveLayerMs(layerName, options?.ttlJitter, void 0);
1998
2088
  return this.applyJitter(adaptiveTtl, jitter);
1999
2089
  }
2000
- resolveLayerSeconds(layerName, override, globalDefault, fallback) {
2090
+ resolveLayerMs(layerName, override, globalDefault, fallback) {
2001
2091
  if (override !== void 0) {
2002
2092
  return this.readLayerNumber(layerName, override) ?? fallback;
2003
2093
  }
@@ -2019,8 +2109,8 @@ var TtlResolver = class {
2019
2109
  if (profile.hits < hotAfter) {
2020
2110
  return ttl;
2021
2111
  }
2022
- const step = this.resolveLayerSeconds(layerName, config.step, void 0, Math.max(1, Math.round(ttl / 2))) ?? 0;
2023
- const maxTtl = this.resolveLayerSeconds(layerName, config.maxTtl, void 0, ttl + step * 4) ?? ttl;
2112
+ const step = this.resolveLayerMs(layerName, config.step, void 0, Math.max(1, Math.round(ttl / 2))) ?? 0;
2113
+ const maxTtl = this.resolveLayerMs(layerName, config.maxTtl, void 0, ttl + step * 4) ?? ttl;
2024
2114
  const multiplier = Math.floor(profile.hits / hotAfter);
2025
2115
  return Math.min(maxTtl, ttl + step * multiplier);
2026
2116
  }
@@ -2042,17 +2132,17 @@ var TtlResolver = class {
2042
2132
  if (policy === "until-midnight") {
2043
2133
  const nextMidnight = new Date(now);
2044
2134
  nextMidnight.setHours(24, 0, 0, 0);
2045
- return Math.max(1, Math.ceil((nextMidnight.getTime() - now.getTime()) / 1e3));
2135
+ return Math.max(1, Math.ceil(nextMidnight.getTime() - now.getTime()));
2046
2136
  }
2047
2137
  if (policy === "next-hour") {
2048
2138
  const nextHour = new Date(now);
2049
2139
  nextHour.setMinutes(60, 0, 0);
2050
- return Math.max(1, Math.ceil((nextHour.getTime() - now.getTime()) / 1e3));
2140
+ return Math.max(1, Math.ceil(nextHour.getTime() - now.getTime()));
2051
2141
  }
2052
- const alignToSeconds = policy.alignTo;
2053
- const currentSeconds = Math.floor(Date.now() / 1e3);
2054
- const nextBoundary = Math.ceil((currentSeconds + 1) / alignToSeconds) * alignToSeconds;
2055
- return Math.max(1, nextBoundary - currentSeconds);
2142
+ const alignToMs = policy.alignTo;
2143
+ const currentMs = Date.now();
2144
+ const nextBoundary = Math.ceil((currentMs + 1) / alignToMs) * alignToMs;
2145
+ return Math.max(1, nextBoundary - currentMs);
2056
2146
  }
2057
2147
  readLayerNumber(layerName, value) {
2058
2148
  if (typeof value === "number") {
@@ -2254,7 +2344,7 @@ var CacheStack = class extends EventEmitter {
2254
2344
  },
2255
2345
  enqueueWriteBehind: this.enqueueWriteBehind.bind(this),
2256
2346
  resolveFreshTtl: this.resolveFreshTtl.bind(this),
2257
- resolveLayerSeconds: this.resolveLayerSeconds.bind(this),
2347
+ resolveLayerMs: this.resolveLayerMs.bind(this),
2258
2348
  globalStaleWhileRevalidate: this.options.staleWhileRevalidate,
2259
2349
  globalStaleIfError: this.options.staleIfError,
2260
2350
  writePolicy: this.options.writePolicy,
@@ -2310,12 +2400,12 @@ var CacheStack = class extends EventEmitter {
2310
2400
  formatError: (error) => this.formatError(error),
2311
2401
  storeEntry: (key, kind, value, options2) => this.storeEntry(key, kind, value, options2),
2312
2402
  recordCircuitFailure: (key, options2, error) => this.recordCircuitFailure(key, options2, error),
2313
- resolveLayerSeconds: (layerName, override, globalDefault, fallback) => this.resolveLayerSeconds(layerName, override, globalDefault, fallback),
2403
+ resolveLayerMs: (layerName, override, globalDefault, fallback) => this.resolveLayerMs(layerName, override, globalDefault, fallback),
2314
2404
  sleep: (ms) => this.sleep(ms),
2315
2405
  withTimeout: (promise, ms, createError) => this.withTimeout(promise, ms, createError),
2316
2406
  isDisconnecting: () => this.isDisconnecting,
2317
2407
  isGracefulDegradationEnabled: () => this.isGracefulDegradationEnabled(),
2318
- scheduleBackgroundRefreshDispatch: (key, fetcher, options2) => this.scheduleBackgroundRefresh(key, fetcher, options2),
2408
+ scheduleBackgroundRefreshDispatch: (key, fetcher, options2, fetcherContext) => this.scheduleBackgroundRefresh(key, fetcher, options2, fetcherContext),
2319
2409
  stampedePrevention: options.stampedePrevention,
2320
2410
  singleFlightCoordinator: options.singleFlightCoordinator,
2321
2411
  singleFlightLeaseMs: options.singleFlightLeaseMs,
@@ -2421,7 +2511,7 @@ var CacheStack = class extends EventEmitter {
2421
2511
  return false;
2422
2512
  }
2423
2513
  /**
2424
- * Returns the remaining TTL in seconds for the key in the fastest layer
2514
+ * Returns the remaining TTL in milliseconds for the key in the fastest layer
2425
2515
  * that has it, or null if the key is not found / has no TTL.
2426
2516
  */
2427
2517
  async ttl(key) {
@@ -2657,6 +2747,15 @@ var CacheStack = class extends EventEmitter {
2657
2747
  await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
2658
2748
  });
2659
2749
  }
2750
+ async expireByTag(tag) {
2751
+ await this.observeOperation("layercache.expire_by_tag", void 0, async () => {
2752
+ validateTag(tag);
2753
+ await this.awaitStartup("expireByTag");
2754
+ const keys = await this.invalidation.collectKeysForTag(tag, this.invalidationMaxKeys());
2755
+ await this.expireKeys(keys);
2756
+ await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "expire" });
2757
+ });
2758
+ }
2660
2759
  async invalidateByTags(tags, mode = "any") {
2661
2760
  await this.observeOperation("layercache.invalidate_by_tags", void 0, async () => {
2662
2761
  if (tags.length === 0) {
@@ -2673,6 +2772,22 @@ var CacheStack = class extends EventEmitter {
2673
2772
  await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
2674
2773
  });
2675
2774
  }
2775
+ async expireByTags(tags, mode = "any") {
2776
+ await this.observeOperation("layercache.expire_by_tags", void 0, async () => {
2777
+ if (tags.length === 0) {
2778
+ return;
2779
+ }
2780
+ validateTags(tags);
2781
+ await this.awaitStartup("expireByTags");
2782
+ const keysByTag = await Promise.all(
2783
+ tags.map((tag) => this.invalidation.collectKeysForTag(tag, this.invalidationMaxKeys()))
2784
+ );
2785
+ const keys = mode === "all" ? this.invalidation.intersectKeys(keysByTag) : [...new Set(keysByTag.flat())];
2786
+ this.invalidation.assertWithinInvalidationKeyLimit(keys.length, this.invalidationMaxKeys());
2787
+ await this.expireKeys(keys);
2788
+ await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "expire" });
2789
+ });
2790
+ }
2676
2791
  async invalidateByPattern(pattern) {
2677
2792
  await this.observeOperation("layercache.invalidate_by_pattern", void 0, async () => {
2678
2793
  validatePattern(pattern);
@@ -2685,6 +2800,18 @@ var CacheStack = class extends EventEmitter {
2685
2800
  await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
2686
2801
  });
2687
2802
  }
2803
+ async expireByPattern(pattern) {
2804
+ await this.observeOperation("layercache.expire_by_pattern", void 0, async () => {
2805
+ validatePattern(pattern);
2806
+ await this.awaitStartup("expireByPattern");
2807
+ const keys = await this.keyDiscovery.collectKeysMatchingPattern(
2808
+ this.qualifyPattern(pattern),
2809
+ this.invalidationMaxKeys()
2810
+ );
2811
+ await this.expireKeys(keys);
2812
+ await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "expire" });
2813
+ });
2814
+ }
2688
2815
  async invalidateByPrefix(prefix) {
2689
2816
  await this.observeOperation("layercache.invalidate_by_prefix", void 0, async () => {
2690
2817
  await this.awaitStartup("invalidateByPrefix");
@@ -2694,6 +2821,15 @@ var CacheStack = class extends EventEmitter {
2694
2821
  await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
2695
2822
  });
2696
2823
  }
2824
+ async expireByPrefix(prefix) {
2825
+ await this.observeOperation("layercache.expire_by_prefix", void 0, async () => {
2826
+ await this.awaitStartup("expireByPrefix");
2827
+ const qualifiedPrefix = this.qualifyKey(validateCacheKey(prefix));
2828
+ const keys = await this.keyDiscovery.collectKeysWithPrefix(qualifiedPrefix, this.invalidationMaxKeys());
2829
+ await this.expireKeys(keys);
2830
+ await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "expire" });
2831
+ });
2832
+ }
2697
2833
  getMetrics() {
2698
2834
  return this.metricsCollector.snapshot;
2699
2835
  }
@@ -2770,9 +2906,9 @@ var CacheStack = class extends EventEmitter {
2770
2906
  const normalizedKey = this.qualifyKey(userKey);
2771
2907
  await this.awaitStartup("inspect");
2772
2908
  const foundInLayers = [];
2773
- let freshTtlSeconds = null;
2774
- let staleTtlSeconds = null;
2775
- let errorTtlSeconds = null;
2909
+ let freshTtlMs = null;
2910
+ let staleTtlMs = null;
2911
+ let errorTtlMs = null;
2776
2912
  let isStale = false;
2777
2913
  for (const layer of this.layers) {
2778
2914
  if (this.shouldSkipLayer(layer)) {
@@ -2789,9 +2925,9 @@ var CacheStack = class extends EventEmitter {
2789
2925
  foundInLayers.push(layer.name);
2790
2926
  if (foundInLayers.length === 1 && resolved.envelope) {
2791
2927
  const now = Date.now();
2792
- freshTtlSeconds = resolved.envelope.freshUntil !== null ? Math.max(0, Math.ceil((resolved.envelope.freshUntil - now) / 1e3)) : null;
2793
- staleTtlSeconds = resolved.envelope.staleUntil !== null ? Math.max(0, Math.ceil((resolved.envelope.staleUntil - now) / 1e3)) : null;
2794
- errorTtlSeconds = resolved.envelope.errorUntil !== null ? Math.max(0, Math.ceil((resolved.envelope.errorUntil - now) / 1e3)) : null;
2928
+ freshTtlMs = resolved.envelope.freshUntil !== null ? Math.max(0, Math.ceil(resolved.envelope.freshUntil - now)) : null;
2929
+ staleTtlMs = resolved.envelope.staleUntil !== null ? Math.max(0, Math.ceil(resolved.envelope.staleUntil - now)) : null;
2930
+ errorTtlMs = resolved.envelope.errorUntil !== null ? Math.max(0, Math.ceil(resolved.envelope.errorUntil - now)) : null;
2795
2931
  isStale = resolved.state === "stale-while-revalidate" || resolved.state === "stale-if-error";
2796
2932
  }
2797
2933
  }
@@ -2799,7 +2935,7 @@ var CacheStack = class extends EventEmitter {
2799
2935
  return null;
2800
2936
  }
2801
2937
  const tags = await this.getTagsForKey(normalizedKey);
2802
- return { key: userKey, foundInLayers, freshTtlSeconds, staleTtlSeconds, errorTtlSeconds, isStale, tags };
2938
+ return { key: userKey, foundInLayers, freshTtlMs, staleTtlMs, errorTtlMs, isStale, tags };
2803
2939
  }
2804
2940
  async exportState() {
2805
2941
  await this.awaitStartup("exportState");
@@ -2856,30 +2992,35 @@ var CacheStack = class extends EventEmitter {
2856
2992
  });
2857
2993
  }
2858
2994
  async storeEntry(key, kind, value, options) {
2995
+ const resolvedOptions = this.resolveContextOptions(key, kind, value, options);
2859
2996
  const clearEpoch = this.maintenance.currentClearEpoch();
2860
2997
  const keyEpoch = this.maintenance.currentKeyEpoch(key);
2861
- await this.layerWriter.writeAcrossLayers(key, kind, value, options);
2998
+ await this.layerWriter.writeAcrossLayers(key, kind, value, resolvedOptions);
2862
2999
  if (this.maintenance.isWriteOutdated(key, clearEpoch, keyEpoch)) {
2863
3000
  return;
2864
3001
  }
2865
- if (options?.tags) {
2866
- await this.tagIndex.track(key, options.tags);
3002
+ if (resolvedOptions?.tags) {
3003
+ await this.tagIndex.track(key, resolvedOptions.tags);
2867
3004
  } else {
2868
3005
  await this.tagIndex.touch(key);
2869
3006
  }
2870
3007
  this.metricsCollector.increment("sets");
2871
- this.logger.debug?.("set", { key, kind, tags: options?.tags });
2872
- this.emit("set", { key, kind, tags: options?.tags });
3008
+ this.logger.debug?.("set", { key, kind, tags: resolvedOptions?.tags });
3009
+ this.emit("set", { key, kind, tags: resolvedOptions?.tags });
2873
3010
  if (this.shouldBroadcastL1Invalidation()) {
2874
3011
  await this.publishInvalidation({ scope: "key", keys: [key], sourceId: this.instanceId, operation: "write" });
2875
3012
  }
2876
3013
  }
2877
3014
  async writeBatch(entries) {
2878
- const { clearEpoch, entryEpochs } = await this.layerWriter.writeBatch(entries);
3015
+ const resolvedEntries = entries.map((entry) => ({
3016
+ ...entry,
3017
+ options: this.resolveContextOptions(entry.key, "value", entry.value, entry.options)
3018
+ }));
3019
+ const { clearEpoch, entryEpochs } = await this.layerWriter.writeBatch(resolvedEntries);
2879
3020
  if (clearEpoch !== this.maintenance.currentClearEpoch()) {
2880
3021
  return;
2881
3022
  }
2882
- for (const entry of entries) {
3023
+ for (const entry of resolvedEntries) {
2883
3024
  if (this.maintenance.isWriteOutdated(entry.key, clearEpoch, entryEpochs.get(entry.key))) {
2884
3025
  continue;
2885
3026
  }
@@ -2913,8 +3054,46 @@ var CacheStack = class extends EventEmitter {
2913
3054
  value
2914
3055
  );
2915
3056
  }
2916
- resolveLayerSeconds(layerName, override, globalDefault, fallback) {
2917
- return this.ttlResolver.resolveLayerSeconds(layerName, override, globalDefault, fallback);
3057
+ resolveLayerMs(layerName, override, globalDefault, fallback) {
3058
+ return this.ttlResolver.resolveLayerMs(layerName, override, globalDefault, fallback);
3059
+ }
3060
+ resolveContextOptions(key, kind, value, options) {
3061
+ if (!options?.contextOptions) {
3062
+ return options;
3063
+ }
3064
+ const { contextOptions, ...baseOptions } = options;
3065
+ let overrides;
3066
+ try {
3067
+ overrides = contextOptions({ key, value, kind });
3068
+ } catch (error) {
3069
+ throw new Error(`options.contextOptions() failed for key "${key}": ${this.formatError(error)}`);
3070
+ }
3071
+ if (!overrides) {
3072
+ return baseOptions;
3073
+ }
3074
+ if (!this.isPlainObject(overrides)) {
3075
+ throw new Error(
3076
+ `options.contextOptions() must return a plain object or undefined for key "${key}". Async resolvers are not supported.`
3077
+ );
3078
+ }
3079
+ try {
3080
+ validateContextEntryOptions("options.contextOptions()", overrides);
3081
+ } catch (error) {
3082
+ throw new Error(
3083
+ `options.contextOptions() returned invalid entry options for key "${key}": ${this.formatError(error)}`
3084
+ );
3085
+ }
3086
+ return {
3087
+ ...baseOptions,
3088
+ ...overrides
3089
+ };
3090
+ }
3091
+ isPlainObject(value) {
3092
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3093
+ return false;
3094
+ }
3095
+ const prototype = Object.getPrototypeOf(value);
3096
+ return prototype === Object.prototype || prototype === null;
2918
3097
  }
2919
3098
  async deleteKeys(keys) {
2920
3099
  if (keys.length === 0) {
@@ -2932,6 +3111,30 @@ var CacheStack = class extends EventEmitter {
2932
3111
  this.logger.debug?.("delete", { keys });
2933
3112
  this.emit("delete", { keys });
2934
3113
  }
3114
+ async expireKeys(keys) {
3115
+ if (keys.length === 0) {
3116
+ return;
3117
+ }
3118
+ this.maintenance.bumpKeyEpochs(keys);
3119
+ const foundKeys = await this.expireKeysInLayers(keys, this.layers);
3120
+ for (const key of keys) {
3121
+ if (foundKeys.has(key)) {
3122
+ continue;
3123
+ }
3124
+ await this.tagIndex.remove(key);
3125
+ this.ttlResolver.deleteProfile(key);
3126
+ this.circuitBreakerManager.delete(key);
3127
+ }
3128
+ this.metricsCollector.increment("invalidations");
3129
+ this.logger.debug?.("expire", { keys });
3130
+ this.emit("expire", { keys });
3131
+ }
3132
+ async expireKeysInLayers(keys, layers) {
3133
+ if (keys.length === 0) {
3134
+ return /* @__PURE__ */ new Set();
3135
+ }
3136
+ return this.invalidation.expireKeysInLayers(layers, keys);
3137
+ }
2935
3138
  async publishInvalidation(message) {
2936
3139
  if (!this.options.invalidationBus) {
2937
3140
  return;
@@ -2953,6 +3156,10 @@ var CacheStack = class extends EventEmitter {
2953
3156
  }
2954
3157
  const keys = message.keys ?? [];
2955
3158
  this.maintenance.bumpKeyEpochs(keys);
3159
+ if (message.operation === "expire") {
3160
+ await this.expireKeysInLayers(keys, localLayers);
3161
+ return;
3162
+ }
2956
3163
  await this.invalidation.deleteKeysFromLayers(localLayers, keys);
2957
3164
  if (message.operation !== "write") {
2958
3165
  for (const key of keys) {
@@ -3150,6 +3357,9 @@ var CacheStack = class extends EventEmitter {
3150
3357
  validateCircuitBreakerOptions(options.circuitBreaker);
3151
3358
  validateRateLimitOptions("options.fetcherRateLimit", options.fetcherRateLimit);
3152
3359
  validateTags(options.tags);
3360
+ if (options.contextOptions && typeof options.contextOptions !== "function") {
3361
+ throw new Error("options.contextOptions must be a function.");
3362
+ }
3153
3363
  }
3154
3364
  assertActive(operation) {
3155
3365
  if (this.isDisconnecting) {
@@ -3164,8 +3374,8 @@ var CacheStack = class extends EventEmitter {
3164
3374
  async readLayerEntry(layer, key) {
3165
3375
  return this.reader.readLayerEntry(layer, key);
3166
3376
  }
3167
- scheduleBackgroundRefresh(key, fetcher, options) {
3168
- this.reader.runScheduleBackgroundRefresh(key, fetcher, options);
3377
+ scheduleBackgroundRefresh(key, fetcher, options, fetcherContext) {
3378
+ this.reader.runScheduleBackgroundRefresh(key, fetcher, options, fetcherContext);
3169
3379
  }
3170
3380
  async applyFreshReadPolicies(key, hit, options, fetcher) {
3171
3381
  return this.reader.runApplyFreshReadPolicies(key, hit, options, fetcher);
@@ -3309,7 +3519,7 @@ var RedisInvalidationBus = class {
3309
3519
  }
3310
3520
  const candidate = value;
3311
3521
  const validScope = candidate.scope === "key" || candidate.scope === "keys" || candidate.scope === "clear";
3312
- const validOperation = candidate.operation === void 0 || candidate.operation === "write" || candidate.operation === "delete" || candidate.operation === "invalidate" || candidate.operation === "clear";
3522
+ const validOperation = candidate.operation === void 0 || candidate.operation === "write" || candidate.operation === "delete" || candidate.operation === "invalidate" || candidate.operation === "expire" || candidate.operation === "clear";
3313
3523
  const validKeys = candidate.keys === void 0 || Array.isArray(candidate.keys) && candidate.keys.every((key) => typeof key === "string");
3314
3524
  return validScope && typeof candidate.sourceId === "string" && candidate.sourceId.length > 0 && validOperation && validKeys;
3315
3525
  }
@@ -3640,7 +3850,7 @@ var RedisLayer = class {
3640
3850
  const payload = await this.encodePayload(serialized);
3641
3851
  const normalizedKey = this.withPrefix(entry.key);
3642
3852
  if (entry.ttl && entry.ttl > 0) {
3643
- pipeline.set(normalizedKey, payload, "EX", entry.ttl);
3853
+ pipeline.set(normalizedKey, payload, "PX", entry.ttl);
3644
3854
  } else {
3645
3855
  pipeline.set(normalizedKey, payload);
3646
3856
  }
@@ -3655,7 +3865,7 @@ var RedisLayer = class {
3655
3865
  if (ttl && ttl > 0) {
3656
3866
  await this.runCommand(
3657
3867
  `set(${this.displayKey(key)})`,
3658
- () => this.client.set(normalizedKey, payload, "EX", ttl)
3868
+ () => this.client.set(normalizedKey, payload, "PX", ttl)
3659
3869
  );
3660
3870
  return;
3661
3871
  }
@@ -3684,7 +3894,10 @@ var RedisLayer = class {
3684
3894
  }
3685
3895
  async ttl(key) {
3686
3896
  this.validateKey(key);
3687
- const remaining = await this.runCommand(`ttl(${this.displayKey(key)})`, () => this.client.ttl(this.withPrefix(key)));
3897
+ const remaining = await this.runCommand(
3898
+ `ttl(${this.displayKey(key)})`,
3899
+ () => this.client.pttl(this.withPrefix(key))
3900
+ );
3688
3901
  if (remaining < 0) {
3689
3902
  return null;
3690
3903
  }
@@ -3835,12 +4048,12 @@ var RedisLayer = class {
3835
4048
  const payload = await this.encodePayload(serialized);
3836
4049
  const ttl = await this.runCommand(
3837
4050
  `rewrite-ttl(${this.displayKey(key)})`,
3838
- () => this.client.ttl(this.withPrefix(key))
4051
+ () => this.client.pttl(this.withPrefix(key))
3839
4052
  );
3840
4053
  if (ttl > 0) {
3841
4054
  await this.runCommand(
3842
4055
  `rewrite-set(${this.displayKey(key)})`,
3843
- () => this.client.set(this.withPrefix(key), payload, "EX", ttl)
4056
+ () => this.client.set(this.withPrefix(key), payload, "PX", ttl)
3844
4057
  );
3845
4058
  return;
3846
4059
  }
@@ -4151,7 +4364,7 @@ var DiskLayer = class {
4151
4364
  const entry = {
4152
4365
  key,
4153
4366
  value,
4154
- expiresAt: ttl && ttl > 0 ? Date.now() + ttl * 1e3 : null
4367
+ expiresAt: ttl && ttl > 0 ? Date.now() + ttl : null
4155
4368
  };
4156
4369
  const payload = this.serializer.serialize(entry);
4157
4370
  const raw = Buffer.isBuffer(payload) ? payload : Buffer.from(payload, "utf8");
@@ -4196,7 +4409,7 @@ var DiskLayer = class {
4196
4409
  if (entry.expiresAt === null) {
4197
4410
  return null;
4198
4411
  }
4199
- const remaining = Math.ceil((entry.expiresAt - Date.now()) / 1e3);
4412
+ const remaining = Math.ceil(entry.expiresAt - Date.now());
4200
4413
  if (remaining <= 0) {
4201
4414
  return null;
4202
4415
  }
@@ -4486,7 +4699,7 @@ var MemcachedLayer = class {
4486
4699
  this.validateKey(key);
4487
4700
  const payload = this.serializer.serialize(value);
4488
4701
  await this.client.set(this.withPrefix(key), payload, {
4489
- expires: ttl && ttl > 0 ? ttl : void 0
4702
+ expires: ttl && ttl > 0 ? Math.ceil(ttl / 1e3) : void 0
4490
4703
  });
4491
4704
  }
4492
4705
  async has(key) {