@podkopaev-tech/nest-infra-modules 0.0.17 → 0.0.19
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/src/modules/cache/cache.module.js +1 -1
- package/dist/src/modules/cache/cache.module.js.map +1 -1
- package/dist/src/modules/cache/decorators/cache.decorator.d.ts +1 -1
- package/dist/src/modules/cache/decorators/cache.decorator.js +2 -2
- package/dist/src/modules/cache/decorators/cache.decorator.js.map +1 -1
- package/dist/src/modules/cache/types.d.ts +2 -0
- package/dist/src/modules/redis/decorators/rate-limiter.decorator.d.ts +33 -1
- package/dist/src/modules/redis/decorators/rate-limiter.decorator.js +29 -3
- package/dist/src/modules/redis/decorators/rate-limiter.decorator.js.map +1 -1
- package/dist/src/modules/redis/types.d.ts +3 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -12,7 +12,7 @@ class CacheModule {
|
|
|
12
12
|
global: true,
|
|
13
13
|
imports: [
|
|
14
14
|
cache_manager_1.CacheModule.registerAsync({
|
|
15
|
-
isGlobal: true,
|
|
15
|
+
isGlobal: true, // Makes the cache manager available throughout the application
|
|
16
16
|
useFactory: (redisClient) => {
|
|
17
17
|
if (!redisClient) {
|
|
18
18
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.module.js","sourceRoot":"","sources":["../../../../src/modules/cache/cache.module.ts"],"names":[],"mappings":";;;AACA,yDAAuE;AAGvE,uCAA6D;AAE7D,MAAa,WAAW;IAItB,MAAM,CAAC,YAAY,CAAC,OAA2B;QAC7C,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,OAAO;YACL,MAAM,EAAE,WAAW;YACnB,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE;gBACP,2BAAe,CAAC,aAAa,CAAC;oBAC5B,QAAQ,EAAE,IAAI;
|
|
1
|
+
{"version":3,"file":"cache.module.js","sourceRoot":"","sources":["../../../../src/modules/cache/cache.module.ts"],"names":[],"mappings":";;;AACA,yDAAuE;AAGvE,uCAA6D;AAE7D,MAAa,WAAW;IAItB,MAAM,CAAC,YAAY,CAAC,OAA2B;QAC7C,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,OAAO;YACL,MAAM,EAAE,WAAW;YACnB,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE;gBACP,2BAAe,CAAC,aAAa,CAAC;oBAC5B,QAAQ,EAAE,IAAI,EAAE,+DAA+D;oBAC/E,UAAU,EAAE,CAAC,WAAyB,EAAE,EAAE;wBACxC,IAAI,CAAC,WAAW,EAAE,CAAC;4BACjB,OAAO;gCACL,GAAG,EAAE,OAAO,CAAC,UAAU;gCACvB,MAAM,EAAE,SAAS;6BAClB,CAAC;wBACJ,CAAC;wBACD,OAAO;4BACL,GAAG,EAAE,OAAO,CAAC,UAAU;4BACvB,MAAM,EAAE;gCACN,IAAA,kBAAU,EACR,WAAW,CAAC,OAAwC,CACrD;6BACF;yBACF,CAAC;oBACJ,CAAC;oBACD,MAAM,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC;iBAC7B,CAAC;aACH;YACD,SAAS,EAAE,EAAE;YACb,OAAO,EAAE,EAAE;SACZ,CAAC;IACJ,CAAC;;AAnCH,kCAoCC;AAnCQ,qBAAS,GAAG,KAAK,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare const CACHE_KEY_PREFIX = "cache:methods:";
|
|
2
2
|
export declare const CACHE_INJECTION_PROP = "$cache";
|
|
3
3
|
export interface CacheDecoratorOptions {
|
|
4
|
-
|
|
4
|
+
keyResolver?: (target: any, propertyKey: string | symbol, args: any[]) => string;
|
|
5
5
|
ttl?: number;
|
|
6
6
|
}
|
|
7
7
|
export declare const Cache: (options: CacheDecoratorOptions) => MethodDecorator;
|
|
@@ -10,11 +10,11 @@ const Cache = (options) => {
|
|
|
10
10
|
return (target, methodKey, descriptor) => {
|
|
11
11
|
const cacheManagerInjection = (0, common_1.Inject)(cache_manager_1.CACHE_MANAGER);
|
|
12
12
|
const originMethod = descriptor.value;
|
|
13
|
-
const cacheKeyBase = `${exports.CACHE_KEY_PREFIX}${options.key}`;
|
|
14
13
|
cacheManagerInjection(target, exports.CACHE_INJECTION_PROP);
|
|
15
14
|
descriptor.value = async function (...args) {
|
|
16
15
|
const { [exports.CACHE_INJECTION_PROP]: cache } = this;
|
|
17
|
-
const
|
|
16
|
+
const resolvedKey = options.keyResolver ? options.keyResolver(target, methodKey, args) : String(methodKey);
|
|
17
|
+
const cacheKey = `${exports.CACHE_KEY_PREFIX}${this.constructor.name}:${resolvedKey}[${args
|
|
18
18
|
.map((res) => JSON.stringify(res))
|
|
19
19
|
.join(',')}]`;
|
|
20
20
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.decorator.js","sourceRoot":"","sources":["../../../../../src/modules/cache/decorators/cache.decorator.ts"],"names":[],"mappings":";;;AAAA,yDAAsD;AACtD,2CAAwC;AAG3B,QAAA,gBAAgB,GAAG,gBAAgB,CAAC;AACpC,QAAA,oBAAoB,GAAG,QAAQ,CAAC;AAOtC,MAAM,KAAK,GAAG,CAAC,OAA8B,EAAmB,EAAE;IACvE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAExB,OAAO,CACL,MAA+B,EAC/B,SAA0B,EAC1B,UAA8B,EAC9B,EAAE;QACF,MAAM,qBAAqB,GAAG,IAAA,eAAM,EAAC,6BAAa,CAAC,CAAC;QACpD,MAAM,YAAY,GAAG,UAAU,CAAC,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"cache.decorator.js","sourceRoot":"","sources":["../../../../../src/modules/cache/decorators/cache.decorator.ts"],"names":[],"mappings":";;;AAAA,yDAAsD;AACtD,2CAAwC;AAG3B,QAAA,gBAAgB,GAAG,gBAAgB,CAAC;AACpC,QAAA,oBAAoB,GAAG,QAAQ,CAAC;AAOtC,MAAM,KAAK,GAAG,CAAC,OAA8B,EAAmB,EAAE;IACvE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAExB,OAAO,CACL,MAA+B,EAC/B,SAA0B,EAC1B,UAA8B,EAC9B,EAAE;QACF,MAAM,qBAAqB,GAAG,IAAA,eAAM,EAAC,6BAAa,CAAC,CAAC;QACpD,MAAM,YAAY,GAAG,UAAU,CAAC,KAAK,CAAC;QAEtC,qBAAqB,CAAC,MAAM,EAAE,4BAAoB,CAAC,CAAC;QAEpD,UAAU,CAAC,KAAK,GAAG,KAAK,WAAW,GAAG,IAAe;YACnD,MAAM,EAAE,CAAC,4BAAoB,CAAC,EAAE,KAAK,EAAE,GAAG,IAEzC,CAAC;YACF,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAG3G,MAAM,QAAQ,GAAG,GAAG,wBAAgB,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,IAAI,WAAW,IAAI,IAAI;iBAChF,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;iBACjC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAEhB,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAC1B,QAAQ,EACR,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,EACtC,EAAE,GAAG,EAAE,CACR,CAAC;gBAEF,OAAO,GAAG,CAAC;YACb,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC;YAC1C,CAAC;QACH,CAAC,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC,CAAC;AArCW,QAAA,KAAK,SAqChB"}
|
|
@@ -1,10 +1,42 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
import { IRateLimiterRedisOptions, RateLimiterRes } from 'rate-limiter-flexible';
|
|
3
3
|
export interface RateLimiterDecoratorOptions extends Omit<IRateLimiterRedisOptions, 'storeClient' | 'points' | 'duration' | 'useRedisPackage'> {
|
|
4
|
+
/**
|
|
5
|
+
* Token to inject the Redis client.
|
|
6
|
+
*/
|
|
4
7
|
redisToken: string | symbol;
|
|
8
|
+
/**
|
|
9
|
+
* aka count of available requests
|
|
10
|
+
*/
|
|
5
11
|
points: number;
|
|
12
|
+
/**
|
|
13
|
+
* duration window in milliseconds
|
|
14
|
+
*/
|
|
6
15
|
durationWindowMs: number;
|
|
7
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Optional function to generate a custom key for rate limiting.
|
|
18
|
+
* By default, uses: `${constructor.name}:${methodName}`
|
|
19
|
+
*/
|
|
20
|
+
keyResolver?: (target: any, propertyKey: string | symbol, args: any[]) => string;
|
|
21
|
+
/**
|
|
22
|
+
* Optional function to customize the error thrown when rate limited.
|
|
23
|
+
* Receives the rate limiter response with retryAfter and remaining points.
|
|
24
|
+
*/
|
|
8
25
|
onLimitReached?: (res: RateLimiterRes) => unknown;
|
|
9
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Decorator that rate limits method calls using Redis.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* @RateLimiter({
|
|
33
|
+
* points: 10, // Number of requests
|
|
34
|
+
* duration: 60, // Per 60 seconds
|
|
35
|
+
* redisToken: 'REDIS_CLIENT', // Token for Redis client injection
|
|
36
|
+
* })
|
|
37
|
+
* async myMethod() {
|
|
38
|
+
* // This method will be rate limited to 10 calls per 60 seconds
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
10
42
|
export declare const RateLimiter: (options: RateLimiterDecoratorOptions) => MethodDecorator;
|
|
@@ -4,6 +4,21 @@ exports.RateLimiter = void 0;
|
|
|
4
4
|
require("reflect-metadata");
|
|
5
5
|
const common_1 = require("@nestjs/common");
|
|
6
6
|
const rate_limiter_flexible_1 = require("rate-limiter-flexible");
|
|
7
|
+
/**
|
|
8
|
+
* Decorator that rate limits method calls using Redis.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* @RateLimiter({
|
|
13
|
+
* points: 10, // Number of requests
|
|
14
|
+
* duration: 60, // Per 60 seconds
|
|
15
|
+
* redisToken: 'REDIS_CLIENT', // Token for Redis client injection
|
|
16
|
+
* })
|
|
17
|
+
* async myMethod() {
|
|
18
|
+
* // This method will be rate limited to 10 calls per 60 seconds
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
7
22
|
const RateLimiter = (options) => {
|
|
8
23
|
return (target, propertyKey, descriptor) => {
|
|
9
24
|
if (!descriptor.value) {
|
|
@@ -11,16 +26,21 @@ const RateLimiter = (options) => {
|
|
|
11
26
|
}
|
|
12
27
|
const originalMethod = descriptor.value;
|
|
13
28
|
const redisToken = options.redisToken;
|
|
29
|
+
// Set up property injection for Redis client if not already present
|
|
14
30
|
if (!(redisToken in target)) {
|
|
15
31
|
(0, common_1.Inject)(redisToken)(target, redisToken);
|
|
16
32
|
}
|
|
17
|
-
|
|
33
|
+
// Remove redisToken from options before passing to RateLimiterRedis
|
|
34
|
+
const { redisToken: _, keyResolver, onLimitReached, ...rateLimiterOptions } = options;
|
|
35
|
+
// Use a symbol to store the rate limiter instance on each instance
|
|
18
36
|
const rateLimiterKey = Symbol(`rateLimiter_${String(propertyKey)}`);
|
|
19
37
|
descriptor.value = async function (...args) {
|
|
38
|
+
// Access Redis client from the instance (injected by NestJS at runtime)
|
|
20
39
|
const redisClient = this[redisToken];
|
|
21
40
|
if (!redisClient) {
|
|
22
41
|
throw new Error(`Redis client not injected. Make sure to provide the redisToken or inject Redis client with token: ${String(redisToken)}`);
|
|
23
42
|
}
|
|
43
|
+
// Lazy initialization: create rate limiter on first call and cache it
|
|
24
44
|
if (!this[rateLimiterKey]) {
|
|
25
45
|
this[rateLimiterKey] = new rate_limiter_flexible_1.RateLimiterRedis({
|
|
26
46
|
points: options.points,
|
|
@@ -31,25 +51,31 @@ const RateLimiter = (options) => {
|
|
|
31
51
|
});
|
|
32
52
|
}
|
|
33
53
|
const rateLimiter = this[rateLimiterKey];
|
|
54
|
+
// Generate the key for rate limiting
|
|
34
55
|
let key;
|
|
35
|
-
if (
|
|
36
|
-
key =
|
|
56
|
+
if (keyResolver) {
|
|
57
|
+
key = keyResolver(this, propertyKey, args);
|
|
37
58
|
}
|
|
38
59
|
else {
|
|
39
60
|
key = `${this.constructor.name}:${String(propertyKey)}`;
|
|
40
61
|
}
|
|
41
62
|
try {
|
|
63
|
+
// Attempt to consume a point (this will throw if rate limit is exceeded)
|
|
42
64
|
const rateLimiterRes = await rateLimiter.consume(key);
|
|
65
|
+
// If we get here, rate limit check passed, proceed with original method
|
|
43
66
|
return originalMethod.apply(this, args);
|
|
44
67
|
}
|
|
45
68
|
catch (rejRes) {
|
|
69
|
+
// Rate limit exceeded
|
|
46
70
|
if (rejRes instanceof Error) {
|
|
47
71
|
throw rejRes;
|
|
48
72
|
}
|
|
73
|
+
// rejRes is RateLimiterRes with msBeforeNext, remainingPoints, totalHitsPerKey
|
|
49
74
|
const rateLimiterRes = rejRes;
|
|
50
75
|
if (onLimitReached) {
|
|
51
76
|
throw onLimitReached(rateLimiterRes);
|
|
52
77
|
}
|
|
78
|
+
// Default error
|
|
53
79
|
throw new common_1.HttpException(`Rate limit exceeded. Try again in ${Math.round(rateLimiterRes.msBeforeNext / 1000)} seconds.`, common_1.HttpStatus.TOO_MANY_REQUESTS);
|
|
54
80
|
}
|
|
55
81
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limiter.decorator.js","sourceRoot":"","sources":["../../../../../src/modules/redis/decorators/rate-limiter.decorator.ts"],"names":[],"mappings":";;;AAAA,4BAA0B;AAC1B,2CAAwF;AACxF,iEAAmG;
|
|
1
|
+
{"version":3,"file":"rate-limiter.decorator.js","sourceRoot":"","sources":["../../../../../src/modules/redis/decorators/rate-limiter.decorator.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type Redis from 'ioredis';
|
|
2
2
|
export type RedisModuleOptions = {
|
|
3
|
+
/** Inject token for the redis-io client instance */
|
|
3
4
|
token: string | symbol;
|
|
5
|
+
/** Redis client options */
|
|
4
6
|
options: IRedisClientOptions;
|
|
5
7
|
}[];
|
|
8
|
+
/** Redis client interface that extends the original redis-io class */
|
|
6
9
|
export interface IRedisClient extends Redis {
|
|
7
10
|
}
|
|
8
11
|
export interface IRedisClientOptions {
|