@ooneex/rate-limit 0.0.12 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +90 -2
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,92 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
2
|
+
// src/RateLimitException.ts
|
|
3
|
+
import { Exception } from "@ooneex/exception";
|
|
4
|
+
import { HttpStatus } from "@ooneex/http-status";
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
class RateLimitException extends Exception {
|
|
7
|
+
constructor(message, data = {}) {
|
|
8
|
+
super(message, {
|
|
9
|
+
status: HttpStatus.Code.TooManyRequests,
|
|
10
|
+
data
|
|
11
|
+
});
|
|
12
|
+
this.name = "RateLimitException";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
// src/RedisRateLimiter.ts
|
|
16
|
+
class RedisRateLimiter {
|
|
17
|
+
client;
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
const connectionString = options.connectionString || Bun.env.RATE_LIMIT_REDIS_URL;
|
|
20
|
+
if (!connectionString) {
|
|
21
|
+
throw new RateLimitException("Redis connection string is required. Please provide a connection string either through the constructor options or set the RATE_LIMIT_REDIS_URL environment variable.");
|
|
22
|
+
}
|
|
23
|
+
const { connectionString: _, ...userOptions } = options;
|
|
24
|
+
const defaultOptions = {
|
|
25
|
+
connectionTimeout: 1e4,
|
|
26
|
+
idleTimeout: 30000,
|
|
27
|
+
autoReconnect: true,
|
|
28
|
+
maxRetries: 3,
|
|
29
|
+
enableOfflineQueue: true,
|
|
30
|
+
enableAutoPipelining: true
|
|
31
|
+
};
|
|
32
|
+
const clientOptions = { ...defaultOptions, ...userOptions };
|
|
33
|
+
this.client = new Bun.RedisClient(connectionString, clientOptions);
|
|
34
|
+
}
|
|
35
|
+
async connect() {
|
|
36
|
+
if (!this.client.connected) {
|
|
37
|
+
await this.client.connect();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
getKey(key) {
|
|
41
|
+
return `ratelimit:${key}`;
|
|
42
|
+
}
|
|
43
|
+
async check(key, limit, windowSeconds) {
|
|
44
|
+
try {
|
|
45
|
+
await this.connect();
|
|
46
|
+
const rateLimitKey = this.getKey(key);
|
|
47
|
+
const count = await this.client.incr(rateLimitKey);
|
|
48
|
+
if (count === 1) {
|
|
49
|
+
await this.client.expire(rateLimitKey, windowSeconds);
|
|
50
|
+
}
|
|
51
|
+
const ttl = await this.client.ttl(rateLimitKey);
|
|
52
|
+
const resetAt = new Date(Date.now() + ttl * 1000);
|
|
53
|
+
return {
|
|
54
|
+
limited: count > limit,
|
|
55
|
+
remaining: Math.max(0, limit - count),
|
|
56
|
+
total: limit,
|
|
57
|
+
resetAt
|
|
58
|
+
};
|
|
59
|
+
} catch (error) {
|
|
60
|
+
throw new RateLimitException(`Failed to check rate limit for key "${key}": ${error}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async reset(key) {
|
|
64
|
+
try {
|
|
65
|
+
await this.connect();
|
|
66
|
+
const rateLimitKey = this.getKey(key);
|
|
67
|
+
const result = await this.client.del(rateLimitKey);
|
|
68
|
+
return result > 0;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
throw new RateLimitException(`Failed to reset rate limit for key "${key}": ${error}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async getCount(key) {
|
|
74
|
+
try {
|
|
75
|
+
await this.connect();
|
|
76
|
+
const rateLimitKey = this.getKey(key);
|
|
77
|
+
const value = await this.client.get(rateLimitKey);
|
|
78
|
+
if (value === null) {
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
return Number.parseInt(value, 10);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
throw new RateLimitException(`Failed to get count for key "${key}": ${error}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export {
|
|
88
|
+
RedisRateLimiter,
|
|
89
|
+
RateLimitException
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
//# debugId=8B4110D29147325A64756E2164756E21
|
package/dist/index.js.map
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"import { Exception } from \"@ooneex/exception\";\nimport { HttpStatus } from \"@ooneex/http-status\";\n\nexport class RateLimitException extends Exception {\n constructor(message: string, data: Record<string, unknown> = {}) {\n super(message, {\n status: HttpStatus.Code.TooManyRequests,\n data,\n });\n this.name = \"RateLimitException\";\n }\n}\n",
|
|
6
6
|
"import { RateLimitException } from \"./RateLimitException\";\nimport type { IRateLimiter, RateLimitResultType, RedisRateLimiterOptionsType } from \"./types\";\n\nexport class RedisRateLimiter implements IRateLimiter {\n private client: Bun.RedisClient;\n\n constructor(options: RedisRateLimiterOptionsType = {}) {\n const connectionString = options.connectionString || Bun.env.RATE_LIMIT_REDIS_URL;\n\n if (!connectionString) {\n throw new RateLimitException(\n \"Redis connection string is required. Please provide a connection string either through the constructor options or set the RATE_LIMIT_REDIS_URL environment variable.\",\n );\n }\n\n const { connectionString: _, ...userOptions } = options;\n\n const defaultOptions = {\n connectionTimeout: 10_000,\n idleTimeout: 30_000,\n autoReconnect: true,\n maxRetries: 3,\n enableOfflineQueue: true,\n enableAutoPipelining: true,\n };\n\n const clientOptions = { ...defaultOptions, ...userOptions };\n\n this.client = new Bun.RedisClient(connectionString, clientOptions);\n }\n\n private async connect(): Promise<void> {\n if (!this.client.connected) {\n await this.client.connect();\n }\n }\n\n private getKey(key: string): string {\n return `ratelimit:${key}`;\n }\n\n public async check(key: string, limit: number, windowSeconds: number): Promise<RateLimitResultType> {\n try {\n await this.connect();\n\n const rateLimitKey = this.getKey(key);\n\n // Increment counter\n const count = await this.client.incr(rateLimitKey);\n\n // Set expiry if this is the first request in window\n if (count === 1) {\n await this.client.expire(rateLimitKey, windowSeconds);\n }\n\n // Get TTL for reset time calculation\n const ttl = await this.client.ttl(rateLimitKey);\n const resetAt = new Date(Date.now() + ttl * 1000);\n\n return {\n limited: count > limit,\n remaining: Math.max(0, limit - count),\n total: limit,\n resetAt,\n };\n } catch (error) {\n throw new RateLimitException(`Failed to check rate limit for key \"${key}\": ${error}`);\n }\n }\n\n public async reset(key: string): Promise<boolean> {\n try {\n await this.connect();\n\n const rateLimitKey = this.getKey(key);\n const result = await this.client.del(rateLimitKey);\n\n return result > 0;\n } catch (error) {\n throw new RateLimitException(`Failed to reset rate limit for key \"${key}\": ${error}`);\n }\n }\n\n public async getCount(key: string): Promise<number> {\n try {\n await this.connect();\n\n const rateLimitKey = this.getKey(key);\n const value = await this.client.get(rateLimitKey);\n\n if (value === null) {\n return 0;\n }\n\n return Number.parseInt(value, 10);\n } catch (error) {\n throw new RateLimitException(`Failed to get count for key \"${key}\": ${error}`);\n }\n }\n}\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": ";AAAA,
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": ";;AAAA;AACA;AAAA;AAEO,MAAM,2BAA2B,UAAU;AAAA,EAChD,WAAW,CAAC,SAAiB,OAAgC,CAAC,GAAG;AAAA,IAC/D,MAAM,SAAS;AAAA,MACb,QAAQ,WAAW,KAAK;AAAA,MACxB;AAAA,IACF,CAAC;AAAA,IACD,KAAK,OAAO;AAAA;AAEhB;;ACRO,MAAM,iBAAyC;AAAA,EAC5C;AAAA,EAER,WAAW,CAAC,UAAuC,CAAC,GAAG;AAAA,IACrD,MAAM,mBAAmB,QAAQ,oBAAoB,IAAI,IAAI;AAAA,IAE7D,IAAI,CAAC,kBAAkB;AAAA,MACrB,MAAM,IAAI,mBACR,sKACF;AAAA,IACF;AAAA,IAEA,QAAQ,kBAAkB,MAAM,gBAAgB;AAAA,IAEhD,MAAM,iBAAiB;AAAA,MACrB,mBAAmB;AAAA,MACnB,aAAa;AAAA,MACb,eAAe;AAAA,MACf,YAAY;AAAA,MACZ,oBAAoB;AAAA,MACpB,sBAAsB;AAAA,IACxB;AAAA,IAEA,MAAM,gBAAgB,KAAK,mBAAmB,YAAY;AAAA,IAE1D,KAAK,SAAS,IAAI,IAAI,YAAY,kBAAkB,aAAa;AAAA;AAAA,OAGrD,QAAO,GAAkB;AAAA,IACrC,IAAI,CAAC,KAAK,OAAO,WAAW;AAAA,MAC1B,MAAM,KAAK,OAAO,QAAQ;AAAA,IAC5B;AAAA;AAAA,EAGM,MAAM,CAAC,KAAqB;AAAA,IAClC,OAAO,aAAa;AAAA;AAAA,OAGT,MAAK,CAAC,KAAa,OAAe,eAAqD;AAAA,IAClG,IAAI;AAAA,MACF,MAAM,KAAK,QAAQ;AAAA,MAEnB,MAAM,eAAe,KAAK,OAAO,GAAG;AAAA,MAGpC,MAAM,QAAQ,MAAM,KAAK,OAAO,KAAK,YAAY;AAAA,MAGjD,IAAI,UAAU,GAAG;AAAA,QACf,MAAM,KAAK,OAAO,OAAO,cAAc,aAAa;AAAA,MACtD;AAAA,MAGA,MAAM,MAAM,MAAM,KAAK,OAAO,IAAI,YAAY;AAAA,MAC9C,MAAM,UAAU,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI;AAAA,MAEhD,OAAO;AAAA,QACL,SAAS,QAAQ;AAAA,QACjB,WAAW,KAAK,IAAI,GAAG,QAAQ,KAAK;AAAA,QACpC,OAAO;AAAA,QACP;AAAA,MACF;AAAA,MACA,OAAO,OAAO;AAAA,MACd,MAAM,IAAI,mBAAmB,uCAAuC,SAAS,OAAO;AAAA;AAAA;AAAA,OAI3E,MAAK,CAAC,KAA+B;AAAA,IAChD,IAAI;AAAA,MACF,MAAM,KAAK,QAAQ;AAAA,MAEnB,MAAM,eAAe,KAAK,OAAO,GAAG;AAAA,MACpC,MAAM,SAAS,MAAM,KAAK,OAAO,IAAI,YAAY;AAAA,MAEjD,OAAO,SAAS;AAAA,MAChB,OAAO,OAAO;AAAA,MACd,MAAM,IAAI,mBAAmB,uCAAuC,SAAS,OAAO;AAAA;AAAA;AAAA,OAI3E,SAAQ,CAAC,KAA8B;AAAA,IAClD,IAAI;AAAA,MACF,MAAM,KAAK,QAAQ;AAAA,MAEnB,MAAM,eAAe,KAAK,OAAO,GAAG;AAAA,MACpC,MAAM,QAAQ,MAAM,KAAK,OAAO,IAAI,YAAY;AAAA,MAEhD,IAAI,UAAU,MAAM;AAAA,QAClB,OAAO;AAAA,MACT;AAAA,MAEA,OAAO,OAAO,SAAS,OAAO,EAAE;AAAA,MAChC,OAAO,OAAO;AAAA,MACd,MAAM,IAAI,mBAAmB,gCAAgC,SAAS,OAAO;AAAA;AAAA;AAGnF;",
|
|
9
|
+
"debugId": "8B4110D29147325A64756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|