@marufzak/rlimiter 1.0.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 ADDED
@@ -0,0 +1 @@
1
+ # rlimiter
@@ -0,0 +1,8 @@
1
+ import type { Context, Middleware } from 'koa';
2
+ import type { RateLimiterOpts } from '../index.js';
3
+ export interface KoaRateLimiterMiddlewareOpts extends RateLimiterOpts {
4
+ getKey: (ctx: Context) => string;
5
+ onLimit?: (key: string) => void;
6
+ onProceed?: (key: string) => void;
7
+ }
8
+ export declare const koaRateLimiterMiddleware: ({ maxTokens, refillSeconds, redisClient, getKey, onLimit, onProceed, }: KoaRateLimiterMiddlewareOpts) => Middleware;
@@ -0,0 +1,20 @@
1
+ import RateLimiter from '../index.js';
2
+ export const koaRateLimiterMiddleware = ({ maxTokens, refillSeconds, redisClient, getKey, onLimit, onProceed, }) => {
3
+ const limiter = new RateLimiter({
4
+ maxTokens,
5
+ refillSeconds,
6
+ redisClient,
7
+ });
8
+ return async (ctx, next) => {
9
+ const key = getKey(ctx);
10
+ const isAllowed = await limiter.check(key);
11
+ if (!isAllowed) {
12
+ ctx.status = 429;
13
+ onLimit?.(key);
14
+ return;
15
+ }
16
+ onProceed?.(key);
17
+ await next();
18
+ };
19
+ };
20
+ //# sourceMappingURL=koa.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"koa.js","sourceRoot":"","sources":["../../src/adapters/koa.ts"],"names":[],"mappings":"AAEA,OAAO,WAAW,MAAM,aAAa,CAAC;AAQtC,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,EACvC,SAAS,EACT,aAAa,EACb,WAAW,EACX,MAAM,EACN,OAAO,EACP,SAAS,GACoB,EAAc,EAAE;IAC7C,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC;QAC9B,SAAS;QACT,aAAa;QACb,WAAW;KACZ,CAAC,CAAC;IAEH,OAAO,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACzB,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACxB,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAE3C,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;YACjB,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;YACf,OAAO;QACT,CAAC;QAED,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC;QACjB,MAAM,IAAI,EAAE,CAAC;IACf,CAAC,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { RedisClientType } from 'redis';
2
+ export interface RateLimiterOpts {
3
+ maxTokens: number;
4
+ refillSeconds: number;
5
+ redisClient: RedisClientType;
6
+ }
7
+ declare class RateLimiter {
8
+ private maxTokens;
9
+ private refillSeconds;
10
+ private redisClient;
11
+ constructor({ maxTokens, refillSeconds, redisClient }: RateLimiterOpts);
12
+ check(key: string): Promise<boolean>;
13
+ }
14
+ export default RateLimiter;
package/dist/index.js ADDED
@@ -0,0 +1,41 @@
1
+ class RateLimiter {
2
+ maxTokens = 0;
3
+ refillSeconds = 0;
4
+ redisClient;
5
+ constructor({ maxTokens, refillSeconds, redisClient }) {
6
+ this.maxTokens = maxTokens;
7
+ this.refillSeconds = refillSeconds;
8
+ this.redisClient = redisClient;
9
+ }
10
+ async check(key) {
11
+ const response = Boolean(await this.redisClient.eval(`
12
+ local countKey = KEYS[1]
13
+
14
+ local maxTokens = tonumber(ARGV[1])
15
+ local refillSeconds = tonumber(ARGV[2])
16
+
17
+ local count = tonumber(redis.call("GET", countKey))
18
+
19
+ if not count then
20
+ count = maxTokens
21
+ redis.call("SETEX", countKey, refillSeconds, maxTokens)
22
+ end
23
+
24
+ count = count - 1
25
+
26
+ if count < 0 then
27
+ return false
28
+ end
29
+
30
+ redis.call("SET", countKey, count, "KEEPTTL")
31
+
32
+ return true
33
+ `, {
34
+ keys: [key],
35
+ arguments: [this.maxTokens.toString(), this.refillSeconds.toString()],
36
+ }));
37
+ return response;
38
+ }
39
+ }
40
+ export default RateLimiter;
41
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW;IACP,SAAS,GAAG,CAAC,CAAC;IACd,aAAa,GAAG,CAAC,CAAC;IAClB,WAAW,CAAkB;IAErC,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAmB;QACpE,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,GAAW;QACrB,MAAM,QAAQ,GAAG,OAAO,CACtB,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CACzB;;;;;;;;;;;;;;;;;;;;;;SAsBC,EACD;YACE,IAAI,EAAE,CAAC,GAAG,CAAC;YACX,SAAS,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,CAAC;SACtE,CACF,CACF,CAAC;QAEF,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF;AAED,eAAe,WAAW,CAAC"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@marufzak/rlimiter",
3
+ "version": "1.0.0",
4
+ "description": "Rate limiter for NodeJS",
5
+ "type": "module",
6
+ "keywords": [
7
+ "rate-limiter",
8
+ "nodejs"
9
+ ],
10
+ "files": [
11
+ "dist",
12
+ "package.json",
13
+ "README.md"
14
+ ],
15
+ "author": "marufzak",
16
+ "license": "MIT",
17
+ "devDependencies": {
18
+ "@eslint/js": "^10.0.1",
19
+ "@testcontainers/redis": "^11.11.0",
20
+ "@types/koa": "^3.0.1",
21
+ "@types/node": "^25.2.2",
22
+ "@types/supertest": "^6.0.3",
23
+ "eslint": "^10.0.0",
24
+ "globals": "^17.3.0",
25
+ "jiti": "^2.6.1",
26
+ "koa": "^3.1.1",
27
+ "prettier": "3.8.1",
28
+ "redis": "^5.10.0",
29
+ "supertest": "^7.2.2",
30
+ "tsx": "^4.21.0",
31
+ "typescript": "^5.9.3",
32
+ "typescript-eslint": "^8.54.0",
33
+ "vitest": "^4.0.18"
34
+ },
35
+ "scripts": {
36
+ "dev": "tsx watch src/index.ts",
37
+ "build": "rm -rf dist && tsc -p ./tsconfig.json",
38
+ "test": "vitest",
39
+ "typecheck": "tsc --noEmit -p ./tsconfig.json",
40
+ "lint:check": "eslint . --ext .ts",
41
+ "lint:write": "eslint . --ext .ts --fix",
42
+ "format:write": "prettier --cache --write \"**/*.{ts,json}\"",
43
+ "format:check": "prettier --cache --check \"**/*.{ts,json}\""
44
+ }
45
+ }