@nestjs-redisx/cache 1.0.1 → 1.0.3
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/cache/api/decorators/cached.decorator.d.ts +1 -0
- package/dist/cache/api/decorators/cached.decorator.d.ts.map +1 -1
- package/dist/cache/application/ports/l1-cache-store.port.d.ts +3 -2
- package/dist/cache/application/ports/l1-cache-store.port.d.ts.map +1 -1
- package/dist/cache/application/services/cache.service.d.ts.map +1 -1
- package/dist/cache/infrastructure/adapters/l1-memory-store.adapter.d.ts.map +1 -1
- package/dist/cache.plugin.d.ts +22 -3
- package/dist/cache.plugin.d.ts.map +1 -1
- package/dist/index.js +147 -98
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +147 -98
- package/dist/index.mjs.map +1 -1
- package/dist/shared/types/index.d.ts +2 -0
- package/dist/shared/types/index.d.ts.map +1 -1
- package/dist/swr/infrastructure/swr-manager.service.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -23,6 +23,9 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
23
23
|
};
|
|
24
24
|
var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
25
25
|
|
|
26
|
+
// package.json
|
|
27
|
+
var version = "1.0.3";
|
|
28
|
+
|
|
26
29
|
// src/shared/constants/index.ts
|
|
27
30
|
var CACHE_PLUGIN_OPTIONS = /* @__PURE__ */ Symbol.for("CACHE_PLUGIN_OPTIONS");
|
|
28
31
|
var CACHE_SERVICE = /* @__PURE__ */ Symbol.for("CACHE_SERVICE");
|
|
@@ -114,20 +117,19 @@ function Cached(options = {}) {
|
|
|
114
117
|
return originalMethod.apply(this, args);
|
|
115
118
|
}
|
|
116
119
|
const key = buildCacheKey(this, propertyKey.toString(), args, options);
|
|
120
|
+
const tags = typeof options.tags === "function" ? options.tags(...args) : options.tags;
|
|
117
121
|
try {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
+
return await cacheService.getOrSet(key, () => originalMethod.apply(this, args), {
|
|
123
|
+
ttl: options.ttl,
|
|
124
|
+
tags,
|
|
125
|
+
strategy: options.strategy,
|
|
126
|
+
swr: options.swr,
|
|
127
|
+
unless: options.unless ? (result) => options.unless(result, ...args) : void 0
|
|
128
|
+
});
|
|
122
129
|
} catch (error) {
|
|
123
|
-
logger.error(`@Cached:
|
|
124
|
-
|
|
125
|
-
const result = await originalMethod.apply(this, args);
|
|
126
|
-
if (options.unless?.(result, ...args)) {
|
|
127
|
-
return result;
|
|
130
|
+
logger.error(`@Cached: getOrSet error for key ${key}:`, error);
|
|
131
|
+
return originalMethod.apply(this, args);
|
|
128
132
|
}
|
|
129
|
-
await cacheResult(cacheService, key, result, options, args);
|
|
130
|
-
return result;
|
|
131
133
|
};
|
|
132
134
|
Object.defineProperty(descriptor.value, "name", {
|
|
133
135
|
value: originalMethod.name,
|
|
@@ -159,6 +161,10 @@ function enrichWithContext(key, options) {
|
|
|
159
161
|
for (const ctxKey of contextKeys) {
|
|
160
162
|
const value = pluginOpts.contextProvider.get(ctxKey);
|
|
161
163
|
if (value !== void 0 && value !== null) {
|
|
164
|
+
if (typeof value === "object") {
|
|
165
|
+
logger.warn(`Context key "${ctxKey}" has object value, skipping (use primitives for context keys)`);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
162
168
|
contextMap.set(ctxKey, String(value));
|
|
163
169
|
}
|
|
164
170
|
}
|
|
@@ -167,6 +173,10 @@ function enrichWithContext(key, options) {
|
|
|
167
173
|
if (!contextMap.has(name)) {
|
|
168
174
|
const value = pluginOpts.contextProvider.get(name);
|
|
169
175
|
if (value !== void 0 && value !== null) {
|
|
176
|
+
if (typeof value === "object") {
|
|
177
|
+
logger.warn(`varyBy key "${name}" has object value, skipping (use primitives for varyBy keys)`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
170
180
|
contextMap.set(name, String(value));
|
|
171
181
|
}
|
|
172
182
|
}
|
|
@@ -181,7 +191,7 @@ function sanitizeForKey(value) {
|
|
|
181
191
|
return String(value).replace(/[^a-zA-Z0-9\-_]/g, "_");
|
|
182
192
|
}
|
|
183
193
|
function interpolateKey(template, args) {
|
|
184
|
-
return template.replace(/\{(\d+)
|
|
194
|
+
return template.replace(/\{(\d+)}/g, (match, index) => {
|
|
185
195
|
const argIndex = parseInt(index, 10);
|
|
186
196
|
if (argIndex < args.length) {
|
|
187
197
|
return serializeArg(args[argIndex]);
|
|
@@ -198,38 +208,43 @@ function serializeArg(arg) {
|
|
|
198
208
|
}
|
|
199
209
|
if (typeof arg === "object") {
|
|
200
210
|
try {
|
|
201
|
-
return
|
|
211
|
+
return stableStringify(arg);
|
|
202
212
|
} catch {
|
|
203
213
|
return "object";
|
|
204
214
|
}
|
|
205
215
|
}
|
|
206
216
|
return "unknown";
|
|
207
217
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
218
|
+
function stableStringify(value) {
|
|
219
|
+
if (value === null || value === void 0) {
|
|
220
|
+
return "null";
|
|
221
|
+
}
|
|
222
|
+
if (typeof value === "function" || typeof value === "symbol") {
|
|
223
|
+
return "null";
|
|
224
|
+
}
|
|
225
|
+
if (typeof value !== "object") {
|
|
226
|
+
if (typeof value === "bigint") {
|
|
227
|
+
return String(value);
|
|
228
|
+
}
|
|
229
|
+
return JSON.stringify(value);
|
|
230
|
+
}
|
|
231
|
+
if (Array.isArray(value)) {
|
|
232
|
+
return "[" + value.map((item) => item === void 0 || typeof item === "function" || typeof item === "symbol" ? "null" : stableStringify(item)).join(",") + "]";
|
|
233
|
+
}
|
|
234
|
+
if (value instanceof Date) {
|
|
235
|
+
return JSON.stringify(value);
|
|
236
|
+
}
|
|
237
|
+
const obj = value;
|
|
238
|
+
const keys = Object.keys(obj).sort();
|
|
239
|
+
const parts = [];
|
|
240
|
+
for (const key of keys) {
|
|
241
|
+
const v = obj[key];
|
|
242
|
+
if (v === void 0 || typeof v === "function" || typeof v === "symbol") {
|
|
243
|
+
continue;
|
|
229
244
|
}
|
|
230
|
-
|
|
231
|
-
logger.error(`@Cached: Failed to cache result for key ${key}:`, error);
|
|
245
|
+
parts.push(JSON.stringify(key) + ":" + stableStringify(v));
|
|
232
246
|
}
|
|
247
|
+
return "{" + parts.join(",") + "}";
|
|
233
248
|
}
|
|
234
249
|
|
|
235
250
|
// src/invalidation/infrastructure/decorators/invalidate-on.decorator.ts
|
|
@@ -871,7 +886,7 @@ var CacheService = class {
|
|
|
871
886
|
async getOrSet(key, loader, options = {}) {
|
|
872
887
|
const normalizedKey = this.validateAndNormalizeKey(key);
|
|
873
888
|
const enrichedKey = this.enrichKeyWithContext(normalizedKey, options.varyBy);
|
|
874
|
-
const swrEnabled =
|
|
889
|
+
const swrEnabled = options.swr?.enabled ?? this.swrEnabled;
|
|
875
890
|
if (swrEnabled) {
|
|
876
891
|
const swrEntry = await this.l2Store.getSwr(enrichedKey);
|
|
877
892
|
if (swrEntry) {
|
|
@@ -883,11 +898,14 @@ var CacheService = class {
|
|
|
883
898
|
enrichedKey,
|
|
884
899
|
loader,
|
|
885
900
|
async (freshValue) => {
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
901
|
+
const staleTime = options.swr?.staleTime ?? this.options.swr?.defaultStaleTime ?? 60;
|
|
902
|
+
const ttl = options.ttl ?? this.options.l2?.defaultTtl ?? 3600;
|
|
903
|
+
const swrEntryNew = this.swrManager.createSwrEntry(freshValue, ttl, staleTime);
|
|
904
|
+
await this.l2Store.setSwr(enrichedKey, swrEntryNew);
|
|
905
|
+
if (this.l1Enabled) {
|
|
906
|
+
const entry = CacheEntry.create(freshValue, ttl);
|
|
907
|
+
await this.l1Store.set(enrichedKey, entry, this.options.l1?.ttl);
|
|
908
|
+
}
|
|
891
909
|
},
|
|
892
910
|
(error) => {
|
|
893
911
|
this.logger.error(`SWR revalidation failed for key ${enrichedKey}:`, error);
|
|
@@ -898,10 +916,12 @@ var CacheService = class {
|
|
|
898
916
|
}
|
|
899
917
|
}
|
|
900
918
|
const value = await this.loadWithStampede(enrichedKey, loader, options);
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
919
|
+
if (!options.unless?.(value)) {
|
|
920
|
+
const staleTime = options.swr?.staleTime ?? this.options.swr?.defaultStaleTime ?? 60;
|
|
921
|
+
const ttl = options.ttl ?? this.options.l2?.defaultTtl ?? 3600;
|
|
922
|
+
const swrEntryNew = this.swrManager.createSwrEntry(value, ttl, staleTime);
|
|
923
|
+
await this.l2Store.setSwr(enrichedKey, swrEntryNew);
|
|
924
|
+
}
|
|
905
925
|
return value;
|
|
906
926
|
}
|
|
907
927
|
const cached = await this.get(enrichedKey);
|
|
@@ -926,19 +946,23 @@ var CacheService = class {
|
|
|
926
946
|
this.metrics?.incrementCounter("redisx_cache_stampede_prevented_total");
|
|
927
947
|
return result.value;
|
|
928
948
|
}
|
|
929
|
-
|
|
949
|
+
if (!options.unless?.(result.value)) {
|
|
950
|
+
await this.set(key, result.value, {
|
|
951
|
+
ttl: options.ttl,
|
|
952
|
+
tags: options.tags,
|
|
953
|
+
strategy: options.strategy
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
return result.value;
|
|
957
|
+
}
|
|
958
|
+
const value = await loader();
|
|
959
|
+
if (!options.unless?.(value)) {
|
|
960
|
+
await this.set(key, value, {
|
|
930
961
|
ttl: options.ttl,
|
|
931
962
|
tags: options.tags,
|
|
932
963
|
strategy: options.strategy
|
|
933
964
|
});
|
|
934
|
-
return result.value;
|
|
935
965
|
}
|
|
936
|
-
const value = await loader();
|
|
937
|
-
await this.set(key, value, {
|
|
938
|
-
ttl: options.ttl,
|
|
939
|
-
tags: options.tags,
|
|
940
|
-
strategy: options.strategy
|
|
941
|
-
});
|
|
942
966
|
return value;
|
|
943
967
|
}
|
|
944
968
|
async delete(key) {
|
|
@@ -981,11 +1005,12 @@ var CacheService = class {
|
|
|
981
1005
|
if (normalizedKeys.length === 0) {
|
|
982
1006
|
return 0;
|
|
983
1007
|
}
|
|
984
|
-
|
|
1008
|
+
const deleted = new Array(normalizedKeys.length).fill(false);
|
|
985
1009
|
if (this.l1Enabled) {
|
|
986
|
-
for (
|
|
987
|
-
|
|
988
|
-
|
|
1010
|
+
for (let i = 0; i < normalizedKeys.length; i++) {
|
|
1011
|
+
if (await this.l1Store.delete(normalizedKeys[i])) {
|
|
1012
|
+
deleted[i] = true;
|
|
1013
|
+
}
|
|
989
1014
|
}
|
|
990
1015
|
}
|
|
991
1016
|
if (this.l2Enabled && normalizedKeys.length > 0) {
|
|
@@ -995,17 +1020,16 @@ var CacheService = class {
|
|
|
995
1020
|
pipeline.del(fullKey);
|
|
996
1021
|
}
|
|
997
1022
|
const results = await pipeline.exec();
|
|
998
|
-
let l2Count = 0;
|
|
999
1023
|
if (results) {
|
|
1000
|
-
for (
|
|
1024
|
+
for (let i = 0; i < results.length; i++) {
|
|
1025
|
+
const [error, result] = results[i];
|
|
1001
1026
|
if (!error && typeof result === "number" && result > 0) {
|
|
1002
|
-
|
|
1027
|
+
deleted[i] = true;
|
|
1003
1028
|
}
|
|
1004
1029
|
}
|
|
1005
1030
|
}
|
|
1006
|
-
deletedCount = Math.max(deletedCount, l2Count);
|
|
1007
1031
|
}
|
|
1008
|
-
return
|
|
1032
|
+
return deleted.filter(Boolean).length;
|
|
1009
1033
|
} catch (error) {
|
|
1010
1034
|
throw new CacheError(`Failed to delete multiple keys: ${error.message}`, ErrorCode.CACHE_DELETE_FAILED, error);
|
|
1011
1035
|
}
|
|
@@ -1142,10 +1166,10 @@ var CacheService = class {
|
|
|
1142
1166
|
const maxTtl = this.options.l2?.maxTtl ?? 86400;
|
|
1143
1167
|
const defaultTtl = this.options.l2?.defaultTtl ?? 3600;
|
|
1144
1168
|
const cacheEntries = entries.map(({ key, value, ttl }) => {
|
|
1145
|
-
const entryTtl = ttl ?? defaultTtl;
|
|
1169
|
+
const entryTtl = Math.min(ttl ?? defaultTtl, maxTtl);
|
|
1146
1170
|
return {
|
|
1147
1171
|
key: this.enrichKeyWithContext(this.validateAndNormalizeKey(key)),
|
|
1148
|
-
entry: CacheEntry.create(value,
|
|
1172
|
+
entry: CacheEntry.create(value, entryTtl),
|
|
1149
1173
|
ttl: entryTtl
|
|
1150
1174
|
};
|
|
1151
1175
|
});
|
|
@@ -1270,6 +1294,10 @@ var CacheService = class {
|
|
|
1270
1294
|
for (const ctxKey of contextKeys) {
|
|
1271
1295
|
const value = contextProvider.get(ctxKey);
|
|
1272
1296
|
if (value !== void 0 && value !== null) {
|
|
1297
|
+
if (typeof value === "object") {
|
|
1298
|
+
this.logger.warn(`Context key "${ctxKey}" has object value, skipping (use primitives for context keys)`);
|
|
1299
|
+
continue;
|
|
1300
|
+
}
|
|
1273
1301
|
contextMap.set(ctxKey, String(value));
|
|
1274
1302
|
}
|
|
1275
1303
|
}
|
|
@@ -1507,12 +1535,6 @@ var L1MemoryStoreAdapter = class {
|
|
|
1507
1535
|
}
|
|
1508
1536
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1509
1537
|
async size() {
|
|
1510
|
-
const now = Date.now();
|
|
1511
|
-
for (const [key, node] of this.cache.entries()) {
|
|
1512
|
-
if (now > node.expiresAt) {
|
|
1513
|
-
void this.delete(key);
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
1538
|
return this.cache.size;
|
|
1517
1539
|
}
|
|
1518
1540
|
moveToFront(node) {
|
|
@@ -2710,9 +2732,6 @@ var SwrManagerService = class {
|
|
|
2710
2732
|
async delete(_key) {
|
|
2711
2733
|
}
|
|
2712
2734
|
isStale(entry) {
|
|
2713
|
-
if (!this.enabled) {
|
|
2714
|
-
return false;
|
|
2715
|
-
}
|
|
2716
2735
|
const now = Date.now();
|
|
2717
2736
|
return now > entry.staleAt;
|
|
2718
2737
|
}
|
|
@@ -3045,33 +3064,63 @@ LuaScriptLoader = __decorateClass([
|
|
|
3045
3064
|
], LuaScriptLoader);
|
|
3046
3065
|
|
|
3047
3066
|
// src/cache.plugin.ts
|
|
3048
|
-
var CachePlugin = class {
|
|
3067
|
+
var CachePlugin = class _CachePlugin {
|
|
3049
3068
|
constructor(options = {}) {
|
|
3050
3069
|
this.options = options;
|
|
3051
3070
|
}
|
|
3052
3071
|
name = "cache";
|
|
3053
|
-
version =
|
|
3072
|
+
version = version;
|
|
3054
3073
|
description = "Advanced caching with L1+L2, anti-stampede, SWR, and tag invalidation";
|
|
3074
|
+
asyncOptions;
|
|
3075
|
+
/**
|
|
3076
|
+
* Create a CachePlugin with async configuration from DI.
|
|
3077
|
+
*
|
|
3078
|
+
* @example
|
|
3079
|
+
* ```typescript
|
|
3080
|
+
* CachePlugin.registerAsync({
|
|
3081
|
+
* imports: [ConfigModule],
|
|
3082
|
+
* inject: [ConfigService],
|
|
3083
|
+
* useFactory: (config: ConfigService) => ({
|
|
3084
|
+
* l1: { maxSize: config.get('CACHE_L1_MAX_SIZE', 1000) },
|
|
3085
|
+
* swr: { enabled: config.get('CACHE_SWR_ENABLED', false) },
|
|
3086
|
+
* }),
|
|
3087
|
+
* })
|
|
3088
|
+
* ```
|
|
3089
|
+
*/
|
|
3090
|
+
static registerAsync(asyncOptions) {
|
|
3091
|
+
const plugin = new _CachePlugin();
|
|
3092
|
+
plugin.asyncOptions = asyncOptions;
|
|
3093
|
+
return plugin;
|
|
3094
|
+
}
|
|
3095
|
+
static mergeDefaults(options) {
|
|
3096
|
+
return {
|
|
3097
|
+
l1: { ...DEFAULT_CACHE_CONFIG.l1, ...options.l1 },
|
|
3098
|
+
l2: { ...DEFAULT_CACHE_CONFIG.l2, ...options.l2 },
|
|
3099
|
+
stampede: { ...DEFAULT_CACHE_CONFIG.stampede, ...options.stampede },
|
|
3100
|
+
swr: { ...DEFAULT_CACHE_CONFIG.swr, ...options.swr },
|
|
3101
|
+
tags: { ...DEFAULT_CACHE_CONFIG.tags, ...options.tags },
|
|
3102
|
+
warmup: { ...DEFAULT_CACHE_CONFIG.warmup, ...options.warmup },
|
|
3103
|
+
keys: { ...DEFAULT_CACHE_CONFIG.keys, ...options.keys },
|
|
3104
|
+
invalidation: { ...DEFAULT_CACHE_CONFIG.invalidation, ...options.invalidation }
|
|
3105
|
+
};
|
|
3106
|
+
}
|
|
3107
|
+
getImports() {
|
|
3108
|
+
return this.asyncOptions?.imports ?? [];
|
|
3109
|
+
}
|
|
3055
3110
|
getProviders() {
|
|
3056
|
-
const
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
...this.options.invalidation
|
|
3067
|
-
}
|
|
3111
|
+
const optionsProvider = this.asyncOptions ? {
|
|
3112
|
+
provide: CACHE_PLUGIN_OPTIONS,
|
|
3113
|
+
useFactory: async (...args) => {
|
|
3114
|
+
const userOptions = await this.asyncOptions.useFactory(...args);
|
|
3115
|
+
return _CachePlugin.mergeDefaults(userOptions);
|
|
3116
|
+
},
|
|
3117
|
+
inject: this.asyncOptions.inject || []
|
|
3118
|
+
} : {
|
|
3119
|
+
provide: CACHE_PLUGIN_OPTIONS,
|
|
3120
|
+
useValue: _CachePlugin.mergeDefaults(this.options)
|
|
3068
3121
|
};
|
|
3069
3122
|
return [
|
|
3070
|
-
|
|
3071
|
-
{
|
|
3072
|
-
provide: CACHE_PLUGIN_OPTIONS,
|
|
3073
|
-
useValue: config
|
|
3074
|
-
},
|
|
3123
|
+
optionsProvider,
|
|
3075
3124
|
// Domain services
|
|
3076
3125
|
{
|
|
3077
3126
|
provide: SERIALIZER,
|
|
@@ -3129,9 +3178,9 @@ var CachePlugin = class {
|
|
|
3129
3178
|
// Factory for registering static invalidation rules
|
|
3130
3179
|
{
|
|
3131
3180
|
provide: INVALIDATION_RULES_INIT,
|
|
3132
|
-
useFactory: (registry,
|
|
3133
|
-
if (
|
|
3134
|
-
const rules =
|
|
3181
|
+
useFactory: (registry, config) => {
|
|
3182
|
+
if (config.invalidation?.rules && config.invalidation.rules.length > 0) {
|
|
3183
|
+
const rules = config.invalidation.rules.map((ruleProps) => InvalidationRule.create(ruleProps));
|
|
3135
3184
|
registry.registerMany(rules);
|
|
3136
3185
|
}
|
|
3137
3186
|
return true;
|
|
@@ -3997,8 +4046,8 @@ var KeyBuilder = class _KeyBuilder {
|
|
|
3997
4046
|
* @param version - Version value (e.g., 'v1', 'v2')
|
|
3998
4047
|
* @returns This builder for chaining
|
|
3999
4048
|
*/
|
|
4000
|
-
version(
|
|
4001
|
-
return this.segment(
|
|
4049
|
+
version(version2) {
|
|
4050
|
+
return this.segment(version2);
|
|
4002
4051
|
}
|
|
4003
4052
|
/**
|
|
4004
4053
|
* Adds a segment to the key.
|