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.
- package/README.md +42 -41
- package/dist/{chunk-BORDQ3LA.js → chunk-7KMKQ6QZ.js} +15 -1
- package/dist/{chunk-5RCAX2BQ.js → chunk-FFZCC7EQ.js} +3 -3
- package/dist/{chunk-4PPBOOXT.js → chunk-KJDFYE5T.js} +38 -26
- package/dist/cli.cjs +9 -9
- package/dist/cli.js +4 -4
- package/dist/{edge-DKkrQ_Ky.d.cts → edge-D2FpRlyS.d.cts} +71 -22
- package/dist/{edge-DKkrQ_Ky.d.ts → edge-D2FpRlyS.d.ts} +71 -22
- package/dist/edge.cjs +9 -9
- package/dist/edge.d.cts +1 -1
- package/dist/edge.d.ts +1 -1
- package/dist/edge.js +2 -2
- package/dist/index.cjs +399 -164
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +294 -81
- package/package.json +5 -5
- package/benchmarks/direct.ts +0 -221
- package/benchmarks/edge-utils.ts +0 -28
- package/benchmarks/edge.ts +0 -491
- package/benchmarks/http.ts +0 -99
- package/benchmarks/latency.ts +0 -45
- package/benchmarks/memory-pressure.ts +0 -144
- package/benchmarks/multi-process-fanout.ts +0 -231
- package/benchmarks/multi-process-worker.ts +0 -151
- package/benchmarks/paths.ts +0 -25
- package/benchmarks/queue-amplification-utils.ts +0 -48
- package/benchmarks/queue-amplification.ts +0 -230
- package/benchmarks/redis-latency-proxy.ts +0 -100
- package/benchmarks/redis.ts +0 -107
- package/benchmarks/scenario-utils.ts +0 -38
- package/benchmarks/server.ts +0 -157
- package/benchmarks/slow-redis-latency.ts +0 -309
- package/benchmarks/slow-redis-utils.ts +0 -29
- package/benchmarks/slow-redis.ts +0 -47
- package/benchmarks/stampede.ts +0 -26
- package/benchmarks/stats.ts +0 -46
- package/benchmarks/workload.ts +0 -77
- package/examples/express-api/index.ts +0 -31
- 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-
|
|
15
|
+
} from "./chunk-7KMKQ6QZ.js";
|
|
15
16
|
import {
|
|
16
17
|
MemoryLayer,
|
|
17
18
|
TagIndex,
|
|
18
19
|
createHonoCacheMiddleware
|
|
19
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-FFZCC7EQ.js";
|
|
20
21
|
import {
|
|
21
22
|
PatternMatcher,
|
|
22
23
|
createStoredValueEnvelope,
|
|
24
|
+
expireStoredEnvelope,
|
|
23
25
|
isStoredValueEnvelope,
|
|
24
26
|
refreshStoredEnvelope,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
remainingFreshTtlMs,
|
|
28
|
+
remainingStoredTtlMs,
|
|
27
29
|
resolveStoredValue,
|
|
28
30
|
unwrapStoredValue
|
|
29
|
-
} from "./chunk-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
715
|
-
|
|
716
|
-
|
|
762
|
+
freshTtlMs: freshTtl,
|
|
763
|
+
staleWhileRevalidateMs: staleWhileRevalidate,
|
|
764
|
+
staleIfErrorMs: staleIfError,
|
|
717
765
|
now
|
|
718
766
|
});
|
|
719
|
-
const ttl =
|
|
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
|
-
|
|
910
|
+
refreshAheadMs
|
|
863
911
|
}) {
|
|
864
912
|
const refreshedStored = slidingTtl && isStoredValueEnvelope(stored) ? refreshStoredEnvelope(stored) : void 0;
|
|
865
|
-
const refreshedStoredTtl = refreshedStored ?
|
|
866
|
-
const remainingFreshTtl =
|
|
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 &&
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
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
|
|
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.
|
|
2080
|
+
const baseTtl = kind === "empty" ? this.resolveLayerMs(
|
|
1991
2081
|
layerName,
|
|
1992
2082
|
options?.negativeTtl,
|
|
1993
2083
|
globalNegativeTtl,
|
|
1994
|
-
this.
|
|
1995
|
-
) : this.
|
|
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.
|
|
2087
|
+
const jitter = this.resolveLayerMs(layerName, options?.ttlJitter, void 0);
|
|
1998
2088
|
return this.applyJitter(adaptiveTtl, jitter);
|
|
1999
2089
|
}
|
|
2000
|
-
|
|
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.
|
|
2023
|
-
const maxTtl = this.
|
|
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(
|
|
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(
|
|
2140
|
+
return Math.max(1, Math.ceil(nextHour.getTime() - now.getTime()));
|
|
2051
2141
|
}
|
|
2052
|
-
const
|
|
2053
|
-
const
|
|
2054
|
-
const nextBoundary = Math.ceil((
|
|
2055
|
-
return Math.max(1, nextBoundary -
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
2774
|
-
let
|
|
2775
|
-
let
|
|
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
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
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,
|
|
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,
|
|
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 (
|
|
2866
|
-
await this.tagIndex.track(key,
|
|
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:
|
|
2872
|
-
this.emit("set", { key, kind, 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
|
|
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
|
|
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
|
-
|
|
2917
|
-
return this.ttlResolver.
|
|
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, "
|
|
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, "
|
|
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(
|
|
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.
|
|
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, "
|
|
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
|
|
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(
|
|
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) {
|