@devbro/pashmak 0.1.37 → 0.1.38

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.
@@ -32,15 +32,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
32
32
  // src/middlewares.mts
33
33
  var middlewares_exports = {};
34
34
  __export(middlewares_exports, {
35
+ RateLimiterMiddleware: () => RateLimiterMiddleware,
35
36
  cors: () => cors,
36
37
  dbTransaction: () => dbTransaction
37
38
  });
38
39
  module.exports = __toCommonJS(middlewares_exports);
39
40
 
40
- // src/router.mts
41
- var import_neko_context = require("@devbro/neko-context");
42
- var import_errors = require("@devbro/neko-http/errors");
43
-
44
41
  // ../neko-router/dist/CompiledRoute.mjs
45
42
  var import_stream = require("stream");
46
43
 
@@ -457,6 +454,8 @@ var Router = class {
457
454
  };
458
455
 
459
456
  // src/router.mts
457
+ var import_neko_context = require("@devbro/neko-context");
458
+ var import_errors = require("@devbro/neko-http/errors");
460
459
  var import_neko_config = require("@devbro/neko-config");
461
460
 
462
461
  // src/facades.mts
@@ -756,6 +755,7 @@ var cache = (0, import_neko_helper2.createSingleton)((label) => {
756
755
  });
757
756
 
758
757
  // src/middlewares.mts
758
+ var import_neko_http = require("@devbro/neko-http");
759
759
  function cors(options = {}) {
760
760
  return async (req, res, next) => {
761
761
  const allowedOrigins = options.allowedOrigins || ["*"];
@@ -787,8 +787,65 @@ async function dbTransaction(req, res, next) {
787
787
  }
788
788
  }
789
789
  __name(dbTransaction, "dbTransaction");
790
+ var RateLimiterMiddleware = class _RateLimiterMiddleware extends Middleware {
791
+ constructor(params) {
792
+ super();
793
+ this.params = params;
794
+ }
795
+ static {
796
+ __name(this, "RateLimiterMiddleware");
797
+ }
798
+ static singletonInstance = void 0;
799
+ static getInstance(params = {}) {
800
+ if (_RateLimiterMiddleware.singletonInstance) {
801
+ return _RateLimiterMiddleware.singletonInstance;
802
+ }
803
+ let default_params = {
804
+ generateIdentifier: /* @__PURE__ */ __name((req) => req.socket.remoteAddress || "unknown", "generateIdentifier"),
805
+ maxRequests: 200,
806
+ windowTimeSize: 30,
807
+ windowCount: 4
808
+ };
809
+ const merged_params = { ...default_params, ...params };
810
+ return _RateLimiterMiddleware.singletonInstance = new _RateLimiterMiddleware(
811
+ merged_params
812
+ );
813
+ }
814
+ async call(req, res, next) {
815
+ let window = parseInt(
816
+ (Date.now() / 1e3 / this.params.windowTimeSize).toString()
817
+ );
818
+ let key = `rate_limiter:${this.params.generateIdentifier(req)}:${window}`;
819
+ let count = await cache().get(key) || 0;
820
+ if (!count) {
821
+ await cache().put(
822
+ key,
823
+ 1,
824
+ this.params.windowTimeSize * (this.params.windowCount + 1)
825
+ );
826
+ count = 1;
827
+ } else {
828
+ count = await cache().increment(key, 1);
829
+ }
830
+ for (let i = 1; i < this.params.windowCount; i++) {
831
+ count += parseInt(
832
+ await cache().get(
833
+ `rate_limiter:${this.params.generateIdentifier(req)}:${window - i}`
834
+ ) || "0"
835
+ );
836
+ }
837
+ if (count > this.params.maxRequests) {
838
+ throw new import_neko_http.HttpTooManyRequestsError(
839
+ "Too many requests. Please try again later."
840
+ );
841
+ }
842
+ await next();
843
+ return;
844
+ }
845
+ };
790
846
  // Annotate the CommonJS export names for ESM import in node:
791
847
  0 && (module.exports = {
848
+ RateLimiterMiddleware,
792
849
  cors,
793
850
  dbTransaction
794
851
  });
@@ -1,8 +1,51 @@
1
- import { Request, Response } from '@devbro/neko-router';
1
+ import { Request, Response, Middleware } from '@devbro/neko-router';
2
2
 
3
3
  declare function cors(options?: {
4
4
  allowedOrigins?: (string | RegExp)[];
5
5
  }): (req: Request, res: Response, next: () => Promise<void>) => Promise<void>;
6
6
  declare function dbTransaction(req: Request, res: Response, next: () => Promise<void>): Promise<void>;
7
+ type RateLimiterMiddlewareParams = {
8
+ generateIdentifier: (req: Request) => string;
9
+ maxRequests: number;
10
+ windowTimeSize: number;
11
+ windowCount: number;
12
+ };
13
+ /**
14
+ * Rate limiter middleware using a sliding window algorithm.
15
+ *
16
+ * This middleware tracks request counts across multiple time windows to prevent abuse.
17
+ * It uses atomic cache increments to ensure accurate counting in concurrent environments.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * // Basic usage with defaults (200 requests per 2 minutes)
22
+ * authnedRouter.addGlobalMiddleware(RateLimiterMiddleware);
23
+ *
24
+ * // Custom configuration
25
+ * authnedRouter.addGlobalMiddleware(RateLimiterMiddleware.getInstance({
26
+ * generateIdentifier: (req) => req.headers['x-api-key'] || req.socket.remoteAddress,
27
+ * maxRequests: 200, // Allow 200 requests
28
+ * windowTimeSize: 30, // in 30 second windows
29
+ * windowCount: 4 // looking back 4 windows (total: 120 seconds)
30
+ * }));
31
+ * ```
32
+ *
33
+ * @param params.generateIdentifier - Function to generate unique identifier for rate limiting (default: IP address)
34
+ * @param params.maxRequests - Maximum number of requests allowed across all windows (default: 200)
35
+ * @param params.windowTimeSize - Size of each time window in seconds (default: 30)
36
+ * @param params.windowCount - Number of previous windows to check (default: 4)
37
+ *
38
+ * The total time period checked is `windowTimeSize * windowCount` seconds.
39
+ * Default configuration: 200 requests per 120 seconds (30s × 4 windows).
40
+ *
41
+ * @throws {HttpTooManyRequestsError} When rate limit is exceeded
42
+ */
43
+ declare class RateLimiterMiddleware extends Middleware {
44
+ private params;
45
+ static singletonInstance: Middleware | undefined;
46
+ static getInstance(params?: Partial<RateLimiterMiddlewareParams>): Middleware;
47
+ constructor(params: RateLimiterMiddlewareParams);
48
+ call(req: Request, res: Response, next: () => Promise<void>): Promise<void>;
49
+ }
7
50
 
8
- export { cors, dbTransaction };
51
+ export { RateLimiterMiddleware, type RateLimiterMiddlewareParams, cors, dbTransaction };
@@ -1,6 +1,8 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
- import { db } from "./facades.mjs";
3
+ import { Middleware } from "@devbro/neko-router";
4
+ import { db, cache } from "./facades.mjs";
5
+ import { HttpTooManyRequestsError } from "@devbro/neko-http";
4
6
  function cors(options = {}) {
5
7
  return async (req, res, next) => {
6
8
  const allowedOrigins = options.allowedOrigins || ["*"];
@@ -32,7 +34,64 @@ async function dbTransaction(req, res, next) {
32
34
  }
33
35
  }
34
36
  __name(dbTransaction, "dbTransaction");
37
+ class RateLimiterMiddleware extends Middleware {
38
+ constructor(params) {
39
+ super();
40
+ this.params = params;
41
+ }
42
+ static {
43
+ __name(this, "RateLimiterMiddleware");
44
+ }
45
+ static singletonInstance = void 0;
46
+ static getInstance(params = {}) {
47
+ if (RateLimiterMiddleware.singletonInstance) {
48
+ return RateLimiterMiddleware.singletonInstance;
49
+ }
50
+ let default_params = {
51
+ generateIdentifier: /* @__PURE__ */ __name((req) => req.socket.remoteAddress || "unknown", "generateIdentifier"),
52
+ maxRequests: 200,
53
+ windowTimeSize: 30,
54
+ windowCount: 4
55
+ };
56
+ const merged_params = { ...default_params, ...params };
57
+ return RateLimiterMiddleware.singletonInstance = new RateLimiterMiddleware(
58
+ merged_params
59
+ );
60
+ }
61
+ async call(req, res, next) {
62
+ let window = parseInt(
63
+ (Date.now() / 1e3 / this.params.windowTimeSize).toString()
64
+ );
65
+ let key = `rate_limiter:${this.params.generateIdentifier(req)}:${window}`;
66
+ let count = await cache().get(key) || 0;
67
+ if (!count) {
68
+ await cache().put(
69
+ key,
70
+ 1,
71
+ this.params.windowTimeSize * (this.params.windowCount + 1)
72
+ );
73
+ count = 1;
74
+ } else {
75
+ count = await cache().increment(key, 1);
76
+ }
77
+ for (let i = 1; i < this.params.windowCount; i++) {
78
+ count += parseInt(
79
+ await cache().get(
80
+ `rate_limiter:${this.params.generateIdentifier(req)}:${window - i}`
81
+ ) || "0"
82
+ );
83
+ }
84
+ if (count > this.params.maxRequests) {
85
+ throw new HttpTooManyRequestsError(
86
+ "Too many requests. Please try again later."
87
+ );
88
+ }
89
+ await next();
90
+ return;
91
+ }
92
+ }
35
93
  export {
94
+ RateLimiterMiddleware,
36
95
  cors,
37
96
  dbTransaction
38
97
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/middlewares.mts"],"sourcesContent":["import { Request, Response } from \"@devbro/neko-router\";\nimport { logger, db } from \"./facades.mjs\";\n\nexport function cors(\n options: { allowedOrigins?: (string | RegExp)[] } = {},\n): (req: Request, res: Response, next: () => Promise<void>) => Promise<void> {\n return async (\n req: Request,\n res: Response,\n next: () => Promise<void>,\n ): Promise<void> => {\n const allowedOrigins = options.allowedOrigins || [\"*\"];\n const origin = req.headers.origin || \"*\";\n\n for (const allowedOrigin of allowedOrigins) {\n if (typeof allowedOrigin === \"string\" && allowedOrigin === origin) {\n res.setHeader(\"Access-Control-Allow-Origin\", allowedOrigin);\n break;\n } else if (\n allowedOrigin instanceof RegExp &&\n allowedOrigin.test(origin)\n ) {\n res.setHeader(\"Access-Control-Allow-Origin\", origin);\n break;\n } else if (allowedOrigin === \"*\") {\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n break;\n }\n }\n\n res.setHeader(\"Access-Control-Allow-Headers\", \"*\");\n await next();\n };\n}\n\nexport async function dbTransaction(\n req: Request,\n res: Response,\n next: () => Promise<void>,\n): Promise<void> {\n try {\n await db().beginTransaction();\n await next();\n await db().commit();\n } finally {\n await db().rollback();\n }\n}\n"],"mappings":";;AACA,SAAiB,UAAU;AAEpB,SAAS,KACd,UAAoD,CAAC,GACsB;AAC3E,SAAO,OACL,KACA,KACA,SACkB;AAClB,UAAM,iBAAiB,QAAQ,kBAAkB,CAAC,GAAG;AACrD,UAAM,SAAS,IAAI,QAAQ,UAAU;AAErC,eAAW,iBAAiB,gBAAgB;AAC1C,UAAI,OAAO,kBAAkB,YAAY,kBAAkB,QAAQ;AACjE,YAAI,UAAU,+BAA+B,aAAa;AAC1D;AAAA,MACF,WACE,yBAAyB,UACzB,cAAc,KAAK,MAAM,GACzB;AACA,YAAI,UAAU,+BAA+B,MAAM;AACnD;AAAA,MACF,WAAW,kBAAkB,KAAK;AAChC,YAAI,UAAU,+BAA+B,GAAG;AAChD;AAAA,MACF;AAAA,IACF;AAEA,QAAI,UAAU,gCAAgC,GAAG;AACjD,UAAM,KAAK;AAAA,EACb;AACF;AA9BgB;AAgChB,eAAsB,cACpB,KACA,KACA,MACe;AACf,MAAI;AACF,UAAM,GAAG,EAAE,iBAAiB;AAC5B,UAAM,KAAK;AACX,UAAM,GAAG,EAAE,OAAO;AAAA,EACpB,UAAE;AACA,UAAM,GAAG,EAAE,SAAS;AAAA,EACtB;AACF;AAZsB;","names":[]}
1
+ {"version":3,"sources":["../src/middlewares.mts"],"sourcesContent":["import { Middleware, Request, Response } from \"@devbro/neko-router\";\nimport { logger, db, cache } from \"./facades.mjs\";\nimport { HttpTooManyRequestsError } from \"@devbro/neko-http\";\n\nexport function cors(\n options: { allowedOrigins?: (string | RegExp)[] } = {},\n): (req: Request, res: Response, next: () => Promise<void>) => Promise<void> {\n return async (\n req: Request,\n res: Response,\n next: () => Promise<void>,\n ): Promise<void> => {\n const allowedOrigins = options.allowedOrigins || [\"*\"];\n const origin = req.headers.origin || \"*\";\n\n for (const allowedOrigin of allowedOrigins) {\n if (typeof allowedOrigin === \"string\" && allowedOrigin === origin) {\n res.setHeader(\"Access-Control-Allow-Origin\", allowedOrigin);\n break;\n } else if (\n allowedOrigin instanceof RegExp &&\n allowedOrigin.test(origin)\n ) {\n res.setHeader(\"Access-Control-Allow-Origin\", origin);\n break;\n } else if (allowedOrigin === \"*\") {\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n break;\n }\n }\n\n res.setHeader(\"Access-Control-Allow-Headers\", \"*\");\n await next();\n };\n}\n\nexport async function dbTransaction(\n req: Request,\n res: Response,\n next: () => Promise<void>,\n): Promise<void> {\n try {\n await db().beginTransaction();\n await next();\n await db().commit();\n } finally {\n await db().rollback();\n }\n}\n\nexport type RateLimiterMiddlewareParams = {\n generateIdentifier: (req: Request) => string;\n maxRequests: number;\n windowTimeSize: number;\n windowCount: number;\n};\n\n/**\n * Rate limiter middleware using a sliding window algorithm.\n *\n * This middleware tracks request counts across multiple time windows to prevent abuse.\n * It uses atomic cache increments to ensure accurate counting in concurrent environments.\n *\n * @example\n * ```typescript\n * // Basic usage with defaults (200 requests per 2 minutes)\n * authnedRouter.addGlobalMiddleware(RateLimiterMiddleware);\n *\n * // Custom configuration\n * authnedRouter.addGlobalMiddleware(RateLimiterMiddleware.getInstance({\n * generateIdentifier: (req) => req.headers['x-api-key'] || req.socket.remoteAddress,\n * maxRequests: 200, // Allow 200 requests\n * windowTimeSize: 30, // in 30 second windows\n * windowCount: 4 // looking back 4 windows (total: 120 seconds)\n * }));\n * ```\n *\n * @param params.generateIdentifier - Function to generate unique identifier for rate limiting (default: IP address)\n * @param params.maxRequests - Maximum number of requests allowed across all windows (default: 200)\n * @param params.windowTimeSize - Size of each time window in seconds (default: 30)\n * @param params.windowCount - Number of previous windows to check (default: 4)\n *\n * The total time period checked is `windowTimeSize * windowCount` seconds.\n * Default configuration: 200 requests per 120 seconds (30s × 4 windows).\n *\n * @throws {HttpTooManyRequestsError} When rate limit is exceeded\n */\nexport class RateLimiterMiddleware extends Middleware {\n static singletonInstance: Middleware | undefined = undefined;\n static getInstance(\n params: Partial<RateLimiterMiddlewareParams> = {},\n ): Middleware {\n if (RateLimiterMiddleware.singletonInstance) {\n return RateLimiterMiddleware.singletonInstance;\n }\n let default_params: RateLimiterMiddlewareParams = {\n generateIdentifier: (req: Request) =>\n req.socket.remoteAddress || \"unknown\",\n maxRequests: 200,\n windowTimeSize: 30,\n windowCount: 4,\n };\n const merged_params = { ...default_params, ...params };\n return (RateLimiterMiddleware.singletonInstance = new RateLimiterMiddleware(\n merged_params,\n ));\n }\n\n constructor(private params: RateLimiterMiddlewareParams) {\n super();\n }\n\n async call(\n req: Request,\n res: Response,\n next: () => Promise<void>,\n ): Promise<void> {\n let window = parseInt(\n (Date.now() / 1000 / this.params.windowTimeSize).toString(),\n );\n let key = `rate_limiter:${this.params.generateIdentifier(req)}:${window}`;\n let count: number = (await cache().get(key)) || 0;\n if (!count) {\n await cache().put(\n key,\n 1,\n this.params.windowTimeSize * (this.params.windowCount + 1),\n );\n count = 1;\n } else {\n count = await cache().increment(key, 1);\n }\n\n for (let i = 1; i < this.params.windowCount; i++) {\n count += parseInt(\n (await cache().get(\n `rate_limiter:${this.params.generateIdentifier(req)}:${window - i}`,\n )) || \"0\",\n );\n }\n\n if (count > this.params.maxRequests) {\n throw new HttpTooManyRequestsError(\n \"Too many requests. Please try again later.\",\n );\n }\n\n await next();\n return;\n }\n}\n"],"mappings":";;AAAA,SAAS,kBAAqC;AAC9C,SAAiB,IAAI,aAAa;AAClC,SAAS,gCAAgC;AAElC,SAAS,KACd,UAAoD,CAAC,GACsB;AAC3E,SAAO,OACL,KACA,KACA,SACkB;AAClB,UAAM,iBAAiB,QAAQ,kBAAkB,CAAC,GAAG;AACrD,UAAM,SAAS,IAAI,QAAQ,UAAU;AAErC,eAAW,iBAAiB,gBAAgB;AAC1C,UAAI,OAAO,kBAAkB,YAAY,kBAAkB,QAAQ;AACjE,YAAI,UAAU,+BAA+B,aAAa;AAC1D;AAAA,MACF,WACE,yBAAyB,UACzB,cAAc,KAAK,MAAM,GACzB;AACA,YAAI,UAAU,+BAA+B,MAAM;AACnD;AAAA,MACF,WAAW,kBAAkB,KAAK;AAChC,YAAI,UAAU,+BAA+B,GAAG;AAChD;AAAA,MACF;AAAA,IACF;AAEA,QAAI,UAAU,gCAAgC,GAAG;AACjD,UAAM,KAAK;AAAA,EACb;AACF;AA9BgB;AAgChB,eAAsB,cACpB,KACA,KACA,MACe;AACf,MAAI;AACF,UAAM,GAAG,EAAE,iBAAiB;AAC5B,UAAM,KAAK;AACX,UAAM,GAAG,EAAE,OAAO;AAAA,EACpB,UAAE;AACA,UAAM,GAAG,EAAE,SAAS;AAAA,EACtB;AACF;AAZsB;AAmDf,MAAM,8BAA8B,WAAW;AAAA,EAqBpD,YAAoB,QAAqC;AACvD,UAAM;AADY;AAAA,EAEpB;AAAA,EA9GF,OAuFsD;AAAA;AAAA;AAAA,EACpD,OAAO,oBAA4C;AAAA,EACnD,OAAO,YACL,SAA+C,CAAC,GACpC;AACZ,QAAI,sBAAsB,mBAAmB;AAC3C,aAAO,sBAAsB;AAAA,IAC/B;AACA,QAAI,iBAA8C;AAAA,MAChD,oBAAoB,wBAAC,QACnB,IAAI,OAAO,iBAAiB,WADV;AAAA,MAEpB,aAAa;AAAA,MACb,gBAAgB;AAAA,MAChB,aAAa;AAAA,IACf;AACA,UAAM,gBAAgB,EAAE,GAAG,gBAAgB,GAAG,OAAO;AACrD,WAAQ,sBAAsB,oBAAoB,IAAI;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AAAA,EAMA,MAAM,KACJ,KACA,KACA,MACe;AACf,QAAI,SAAS;AAAA,OACV,KAAK,IAAI,IAAI,MAAO,KAAK,OAAO,gBAAgB,SAAS;AAAA,IAC5D;AACA,QAAI,MAAM,gBAAgB,KAAK,OAAO,mBAAmB,GAAG,CAAC,IAAI,MAAM;AACvE,QAAI,QAAiB,MAAM,MAAM,EAAE,IAAI,GAAG,KAAM;AAChD,QAAI,CAAC,OAAO;AACV,YAAM,MAAM,EAAE;AAAA,QACZ;AAAA,QACA;AAAA,QACA,KAAK,OAAO,kBAAkB,KAAK,OAAO,cAAc;AAAA,MAC1D;AACA,cAAQ;AAAA,IACV,OAAO;AACL,cAAQ,MAAM,MAAM,EAAE,UAAU,KAAK,CAAC;AAAA,IACxC;AAEA,aAAS,IAAI,GAAG,IAAI,KAAK,OAAO,aAAa,KAAK;AAChD,eAAS;AAAA,QACN,MAAM,MAAM,EAAE;AAAA,UACb,gBAAgB,KAAK,OAAO,mBAAmB,GAAG,CAAC,IAAI,SAAS,CAAC;AAAA,QACnE,KAAM;AAAA,MACR;AAAA,IACF;AAEA,QAAI,QAAQ,KAAK,OAAO,aAAa;AACnC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,KAAK;AACX;AAAA,EACF;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devbro/pashmak",
3
- "version": "0.1.37",
3
+ "version": "0.1.38",
4
4
  "description": "testing application for the entire repo",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",