@grupodiariodaregiao/bunstone 0.3.3 → 0.3.4

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.
Files changed (35) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +353 -26
  3. package/dist/lib/app-startup.d.ts +6 -0
  4. package/dist/lib/ratelimit/constants.d.ts +12 -0
  5. package/dist/lib/ratelimit/index.d.ts +6 -0
  6. package/dist/lib/ratelimit/interfaces/storage.interface.d.ts +41 -0
  7. package/dist/lib/ratelimit/ratelimit.decorator.d.ts +52 -0
  8. package/dist/lib/ratelimit/ratelimit.service.d.ts +88 -0
  9. package/dist/lib/ratelimit/storage/memory.storage.d.ts +28 -0
  10. package/dist/lib/ratelimit/storage/redis.storage.d.ts +47 -0
  11. package/dist/lib/types/options.d.ts +27 -0
  12. package/lib/app-startup.ts +113 -5
  13. package/lib/bullmq/queue.service.ts +3 -2
  14. package/lib/cqrs/decorators/command-handler.decorator.ts +3 -3
  15. package/lib/cqrs/decorators/event-handler.decorator.ts +3 -3
  16. package/lib/cqrs/decorators/query-handler.decorator.ts +3 -3
  17. package/lib/cqrs/decorators/saga.decorator.ts +19 -19
  18. package/lib/cqrs/event-bus.ts +78 -78
  19. package/lib/injectable.ts +3 -3
  20. package/lib/module.ts +51 -30
  21. package/lib/openapi.ts +117 -117
  22. package/lib/ratelimit/constants.ts +15 -0
  23. package/lib/ratelimit/index.ts +6 -0
  24. package/lib/ratelimit/interfaces/storage.interface.ts +49 -0
  25. package/lib/ratelimit/ratelimit.decorator.ts +86 -0
  26. package/lib/ratelimit/ratelimit.service.ts +228 -0
  27. package/lib/ratelimit/storage/memory.storage.ts +98 -0
  28. package/lib/ratelimit/storage/redis.storage.ts +134 -0
  29. package/lib/schedule/cron/cron.ts +26 -26
  30. package/lib/schedule/cron/mappers/map-providers-with-cron.ts +3 -3
  31. package/lib/schedule/timeout/mappers/map-providers-with-timeouts.ts +3 -3
  32. package/lib/schedule/timeout/timeout.ts +21 -21
  33. package/lib/types/options.ts +28 -0
  34. package/package.json +2 -1
  35. package/starter/biome.json +0 -42
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export * from "./lib/adapters/upload-adapter";
7
7
  export * from "./lib/app-startup";
8
8
  export * from "./lib/components/layout";
9
9
  export * from "./lib/controller";
10
+ export * from "./lib/ratelimit";
10
11
  export * from "./lib/cqrs/command-bus";
11
12
  export * from "./lib/cqrs/cqrs-module";
12
13
  export * from "./lib/cqrs/decorators/command-handler.decorator";
@@ -14,7 +15,6 @@ export * from "./lib/cqrs/decorators/event-handler.decorator";
14
15
  export * from "./lib/cqrs/decorators/query-handler.decorator";
15
16
  export * from "./lib/cqrs/decorators/saga.decorator";
16
17
  export * from "./lib/cqrs/event-bus";
17
- export { map, ofType } from "./lib/cqrs/event-bus";
18
18
  export * from "./lib/cqrs/interfaces/command.interface";
19
19
  export * from "./lib/cqrs/interfaces/event.interface";
20
20
  export * from "./lib/cqrs/interfaces/query.interface";
package/dist/index.js CHANGED
@@ -32209,7 +32209,7 @@ var require_node_cron = __commonJS((exports) => {
32209
32209
  });
32210
32210
 
32211
32211
  // index.ts
32212
- var import_reflect_metadata24 = __toESM(require_Reflect(), 1);
32212
+ var import_reflect_metadata25 = __toESM(require_Reflect(), 1);
32213
32213
 
32214
32214
  // lib/adapters/cache-adapter.ts
32215
32215
  var {RedisClient, redis } = globalThis.Bun;
@@ -42871,6 +42871,14 @@ var MapProvidersWithBullMq = {
42871
42871
  }
42872
42872
  };
42873
42873
 
42874
+ // lib/ratelimit/constants.ts
42875
+ var RATELIMIT_METADATA_KEY = "dip:ratelimit";
42876
+ var RATELIMIT_CONTROLLER_METADATA_KEY = "dip:ratelimit:controller";
42877
+ var DEFAULT_RATELIMIT_CONFIG = {
42878
+ max: 100,
42879
+ windowMs: 60000
42880
+ };
42881
+
42874
42882
  // lib/schedule/cron/mappers/map-providers-with-cron.ts
42875
42883
  var MapProvidersWithCron = {
42876
42884
  execute(providers = []) {
@@ -43082,21 +43090,24 @@ function mapControllers(controllers = []) {
43082
43090
  const controllersMap = new Map;
43083
43091
  for (const controller of controllers) {
43084
43092
  controllersMap.set(controller, []);
43085
- for (const controllerSymbol of Object.getOwnPropertySymbols(controller)) {
43086
- const controllerPathname = Reflect.getOwnMetadata("dip:controller:pathname", controller);
43087
- const controllerGuard = Reflect.getMetadata("dip:guard", controller);
43088
- const controllerMethods = controller[controllerSymbol];
43089
- controllerMethods.forEach((cm) => {
43090
- const pathname = `${controllerPathname === "/" ? "" : controllerPathname}${cm.pathname}`;
43091
- const methodGuard = Reflect.getMetadata("dip:guard", controller.prototype, cm.methodName);
43092
- controllersMap.get(controller)?.push({
43093
- httpMethod: cm.httpMethod,
43094
- pathname,
43095
- methodName: cm.methodName,
43096
- guard: methodGuard || controllerGuard
43097
- });
43093
+ const controllerRateLimit = Reflect.getMetadata(RATELIMIT_CONTROLLER_METADATA_KEY, controller);
43094
+ const controllerPathname = Reflect.getOwnMetadata("dip:controller:pathname", controller);
43095
+ const controllerGuard = Reflect.getMetadata("dip:guard", controller);
43096
+ const methodsSymbol = Symbol.for("dip:controller:http-methods");
43097
+ const controllerMethods = controller[methodsSymbol] || [];
43098
+ controllerMethods.forEach((cm) => {
43099
+ const pathname = `${controllerPathname === "/" ? "" : controllerPathname}${cm.pathname}`;
43100
+ const methodGuard = Reflect.getMetadata("dip:guard", controller.prototype, cm.methodName);
43101
+ const methodRateLimit = Reflect.getMetadata(RATELIMIT_METADATA_KEY, controller.prototype[cm.methodName]);
43102
+ const effectiveRateLimit = methodRateLimit || controllerRateLimit;
43103
+ controllersMap.get(controller)?.push({
43104
+ httpMethod: cm.httpMethod,
43105
+ pathname,
43106
+ methodName: cm.methodName,
43107
+ guard: methodGuard || controllerGuard,
43108
+ rateLimit: effectiveRateLimit
43098
43109
  });
43099
- }
43110
+ });
43100
43111
  }
43101
43112
  return controllersMap;
43102
43113
  }
@@ -102964,8 +102975,9 @@ class QueueService {
102964
102975
  return await queue2.add(jobName, data, opts);
102965
102976
  }
102966
102977
  getQueue(queueName) {
102967
- if (this.queues.has(queueName)) {
102968
- return this.queues.get(queueName);
102978
+ const existingQueue = this.queues.get(queueName);
102979
+ if (existingQueue) {
102980
+ return existingQueue;
102969
102981
  }
102970
102982
  if (!QueueService.redisOptions) {
102971
102983
  this.logger.error(`Redis options not set for BullMQ. Ensure BullMqModule.register() is called in your AppModule imports.`);
@@ -103335,6 +103347,160 @@ function ApiHeaders(headers) {
103335
103347
  };
103336
103348
  }
103337
103349
 
103350
+ // lib/ratelimit/storage/memory.storage.ts
103351
+ class MemoryStorage {
103352
+ storage = new Map;
103353
+ cleanupInterval = null;
103354
+ constructor() {
103355
+ this.cleanupInterval = setInterval(() => {
103356
+ const now = Date.now();
103357
+ for (const [key, entry] of this.storage.entries()) {
103358
+ if (entry.resetTime <= now) {
103359
+ this.storage.delete(key);
103360
+ }
103361
+ }
103362
+ }, 5 * 60 * 1000);
103363
+ }
103364
+ increment(key, windowMs) {
103365
+ const now = Date.now();
103366
+ const entry = this.storage.get(key);
103367
+ if (!entry || entry.resetTime <= now) {
103368
+ const newEntry = {
103369
+ count: 1,
103370
+ resetTime: now + windowMs
103371
+ };
103372
+ this.storage.set(key, newEntry);
103373
+ return {
103374
+ totalHits: 1,
103375
+ remaining: Infinity,
103376
+ resetTime: newEntry.resetTime
103377
+ };
103378
+ }
103379
+ entry.count++;
103380
+ return {
103381
+ totalHits: entry.count,
103382
+ remaining: Infinity,
103383
+ resetTime: entry.resetTime
103384
+ };
103385
+ }
103386
+ decrement(key) {
103387
+ const entry = this.storage.get(key);
103388
+ if (entry && entry.count > 0) {
103389
+ entry.count--;
103390
+ }
103391
+ }
103392
+ resetKey(key) {
103393
+ this.storage.delete(key);
103394
+ }
103395
+ close() {
103396
+ if (this.cleanupInterval) {
103397
+ clearInterval(this.cleanupInterval);
103398
+ this.cleanupInterval = null;
103399
+ }
103400
+ this.storage.clear();
103401
+ }
103402
+ }
103403
+
103404
+ // lib/ratelimit/ratelimit.service.ts
103405
+ class RateLimitExceededException extends Error {
103406
+ limit;
103407
+ resetTime;
103408
+ constructor(limit, resetTime) {
103409
+ super(`Rate limit exceeded. Limit: ${limit}. Retry after ${new Date(resetTime).toISOString()}`);
103410
+ this.limit = limit;
103411
+ this.resetTime = resetTime;
103412
+ this.name = "RateLimitExceededException";
103413
+ }
103414
+ }
103415
+
103416
+ class RateLimitService {
103417
+ defaultStorage = new MemoryStorage;
103418
+ async process(req, config) {
103419
+ if (config.skip?.(req)) {
103420
+ return this.createSkipResult(config.max);
103421
+ }
103422
+ if (config.skipHeader && req.headers[config.skipHeader.toLowerCase()]) {
103423
+ return this.createSkipResult(config.max);
103424
+ }
103425
+ const storage = config.storage || this.defaultStorage;
103426
+ const key = this.generateKey(req, config.keyGenerator);
103427
+ const info = await storage.increment(key, config.windowMs);
103428
+ info.remaining = Math.max(0, config.max - info.totalHits);
103429
+ const allowed = info.totalHits <= config.max;
103430
+ const headers = this.generateHeaders(info, config.max, allowed);
103431
+ return {
103432
+ allowed,
103433
+ headers,
103434
+ info
103435
+ };
103436
+ }
103437
+ async decrement(req, config) {
103438
+ const storage = config.storage || this.defaultStorage;
103439
+ const key = this.generateKey(req, config.keyGenerator);
103440
+ await storage.decrement(key);
103441
+ }
103442
+ generateKey(req, keyGenerator) {
103443
+ if (keyGenerator) {
103444
+ return keyGenerator(req);
103445
+ }
103446
+ const ip = this.extractIp(req);
103447
+ const method = req.request?.method || req.method || "GET";
103448
+ const path3 = req.request?.url || req.path || req.url || "/";
103449
+ return `${ip}:${method}:${path3}`;
103450
+ }
103451
+ extractIp(req) {
103452
+ const forwarded = req.headers["x-forwarded-for"];
103453
+ if (forwarded && typeof forwarded === "string") {
103454
+ const firstIp = forwarded.split(",")[0];
103455
+ if (firstIp) {
103456
+ return firstIp.trim();
103457
+ }
103458
+ }
103459
+ const realIp = req.headers["x-real-ip"];
103460
+ if (realIp && typeof realIp === "string") {
103461
+ return realIp;
103462
+ }
103463
+ const ip = req.ip || req.request?.ip;
103464
+ if (ip) {
103465
+ return ip;
103466
+ }
103467
+ return "unknown";
103468
+ }
103469
+ generateHeaders(info, max, allowed) {
103470
+ const headers = {
103471
+ "X-RateLimit-Limit": max.toString(),
103472
+ "X-RateLimit-Remaining": info.remaining.toString(),
103473
+ "X-RateLimit-Reset": Math.ceil(info.resetTime / 1000).toString()
103474
+ };
103475
+ if (!allowed) {
103476
+ const retryAfterSeconds = Math.ceil((info.resetTime - Date.now()) / 1000);
103477
+ headers["Retry-After"] = Math.max(0, retryAfterSeconds).toString();
103478
+ }
103479
+ return headers;
103480
+ }
103481
+ createSkipResult(max) {
103482
+ return {
103483
+ allowed: true,
103484
+ headers: {
103485
+ "X-RateLimit-Limit": max.toString(),
103486
+ "X-RateLimit-Remaining": max.toString(),
103487
+ "X-RateLimit-Reset": "0"
103488
+ },
103489
+ info: {
103490
+ totalHits: 0,
103491
+ remaining: max,
103492
+ resetTime: Date.now()
103493
+ }
103494
+ };
103495
+ }
103496
+ getDefaultStorage() {
103497
+ return this.defaultStorage;
103498
+ }
103499
+ }
103500
+ RateLimitService = __legacyDecorateClassTS([
103501
+ Injectable()
103502
+ ], RateLimitService);
103503
+
103338
103504
  // lib/render.ts
103339
103505
  var import_reflect_metadata14 = __toESM(require_Reflect(), 1);
103340
103506
  var RENDER_METADATA = "dip:render:component";
@@ -103356,6 +103522,8 @@ class AppStartup {
103356
103522
  static logger = new Logger(AppStartup.name);
103357
103523
  static registeredSagas = new WeakSet;
103358
103524
  static viewBundles = new Map;
103525
+ static globalRateLimitConfig;
103526
+ static rateLimitService = new RateLimitService;
103359
103527
  static async create(module, options) {
103360
103528
  try {
103361
103529
  AppStartup.elysia = new Elysia;
@@ -103417,6 +103585,7 @@ class AppStartup {
103417
103585
  documentation: options.swagger.documentation
103418
103586
  }));
103419
103587
  }
103588
+ AppStartup.globalRateLimitConfig = options?.rateLimit;
103420
103589
  AppStartup.registerModules(module);
103421
103590
  return {
103422
103591
  listen: AppStartup.listen,
@@ -103627,6 +103796,23 @@ if (document.readyState === 'loading') {
103627
103796
  } : undefined
103628
103797
  };
103629
103798
  });
103799
+ const hasRateLimit = method.rateLimit || AppStartup.globalRateLimitConfig?.enabled !== false && AppStartup.globalRateLimitConfig;
103800
+ if (hasRateLimit) {
103801
+ responses["429"] = {
103802
+ description: "Too Many Requests - Rate limit exceeded",
103803
+ content: {
103804
+ "application/json": {
103805
+ schema: {
103806
+ type: "object",
103807
+ properties: {
103808
+ status: { type: "number" },
103809
+ message: { type: "string" }
103810
+ }
103811
+ }
103812
+ }
103813
+ }
103814
+ };
103815
+ }
103630
103816
  const controllerHeaders = Reflect.getMetadata(API_HEADERS_METADATA, controllerInstance) || [];
103631
103817
  const methodHeaders = Reflect.getMetadata(API_HEADERS_METADATA, controllerInstance.prototype, method.methodName) || [];
103632
103818
  const allHeaders = [...controllerHeaders, ...methodHeaders];
@@ -103647,6 +103833,7 @@ if (document.readyState === 'loading') {
103647
103833
  const guardDependencies = resolveDependencies(guardParamsTypes, injectables);
103648
103834
  guardInstance = new method.guard(...guardDependencies);
103649
103835
  }
103836
+ const effectiveRateLimit = AppStartup.buildEffectiveRateLimit(method.rateLimit);
103650
103837
  AppStartup.elysia[httpMethod](method.pathname, (req) => AppStartup.executeControllerMethod(req, controller, method.methodName), {
103651
103838
  body: bodySchema,
103652
103839
  query: querySchema,
@@ -103658,7 +103845,24 @@ if (document.readyState === 'loading') {
103658
103845
  responses,
103659
103846
  parameters
103660
103847
  },
103661
- beforeHandle(req) {
103848
+ async beforeHandle(req) {
103849
+ if (effectiveRateLimit) {
103850
+ const result = await AppStartup.rateLimitService.process(req, effectiveRateLimit);
103851
+ if (req.set?.headers) {
103852
+ Object.entries(result.headers).forEach(([key, value]) => {
103853
+ if (value !== undefined) {
103854
+ req.set.headers[key] = value;
103855
+ }
103856
+ });
103857
+ }
103858
+ if (!result.allowed) {
103859
+ req.set.status = 429;
103860
+ return {
103861
+ status: 429,
103862
+ message: effectiveRateLimit.message || "Too many requests, please try again later."
103863
+ };
103864
+ }
103865
+ }
103662
103866
  if (!guardInstance)
103663
103867
  return;
103664
103868
  const isValid = guardInstance.validate(req);
@@ -103678,6 +103882,37 @@ if (document.readyState === 'loading') {
103678
103882
  }
103679
103883
  }
103680
103884
  }
103885
+ static buildEffectiveRateLimit(methodRateLimit) {
103886
+ const global2 = AppStartup.globalRateLimitConfig;
103887
+ if (methodRateLimit) {
103888
+ if (methodRateLimit.enabled === false) {
103889
+ return;
103890
+ }
103891
+ return {
103892
+ enabled: true,
103893
+ max: methodRateLimit.max ?? global2?.max ?? 100,
103894
+ windowMs: methodRateLimit.windowMs ?? global2?.windowMs ?? 60000,
103895
+ storage: methodRateLimit.storage ?? global2?.storage,
103896
+ keyGenerator: methodRateLimit.keyGenerator ?? global2?.keyGenerator,
103897
+ skipHeader: methodRateLimit.skipHeader ?? global2?.skipHeader,
103898
+ skip: methodRateLimit.skip ?? global2?.skip,
103899
+ message: methodRateLimit.message ?? global2?.message
103900
+ };
103901
+ }
103902
+ if (global2?.enabled !== false && global2) {
103903
+ return {
103904
+ enabled: true,
103905
+ max: global2.max ?? 100,
103906
+ windowMs: global2.windowMs ?? 60000,
103907
+ storage: global2.storage,
103908
+ keyGenerator: global2.keyGenerator,
103909
+ skipHeader: global2.skipHeader,
103910
+ skip: global2.skip,
103911
+ message: global2.message
103912
+ };
103913
+ }
103914
+ return;
103915
+ }
103681
103916
  static registerTimeouts(module) {
103682
103917
  const providersTimeouts = Reflect.getMetadata("dip:timeouts", module);
103683
103918
  if (!providersTimeouts) {
@@ -103718,7 +103953,7 @@ if (document.readyState === 'loading') {
103718
103953
  return;
103719
103954
  }
103720
103955
  const injectables = Reflect.getMetadata("dip:injectables", module);
103721
- const queueService = injectables?.get(QueueService);
103956
+ const _queueService = injectables?.get(QueueService);
103722
103957
  const redisOptions = QueueService.redisOptions;
103723
103958
  for (const item of providersBullMq.entries()) {
103724
103959
  const [providerClass, methods] = item;
@@ -103849,6 +104084,90 @@ function Controller(pathname = "/") {
103849
104084
  Reflect.defineMetadata("dip:controller:pathname", pathname, target2);
103850
104085
  };
103851
104086
  }
104087
+ // lib/ratelimit/ratelimit.decorator.ts
104088
+ var import_reflect_metadata17 = __toESM(require_Reflect(), 1);
104089
+ function RateLimit(options = {}) {
104090
+ return (target2, _propertyKey, descriptor) => {
104091
+ const config = {
104092
+ enabled: options.enabled ?? true,
104093
+ max: options.max ?? 100,
104094
+ windowMs: options.windowMs ?? 60000,
104095
+ message: options.message,
104096
+ storage: options.storage,
104097
+ keyGenerator: options.keyGenerator,
104098
+ skipHeader: options.skipHeader,
104099
+ skip: options.skip
104100
+ };
104101
+ if (descriptor) {
104102
+ Reflect.defineMetadata(RATELIMIT_METADATA_KEY, config, descriptor.value);
104103
+ return descriptor;
104104
+ }
104105
+ Reflect.defineMetadata(RATELIMIT_CONTROLLER_METADATA_KEY, config, target2);
104106
+ return target2;
104107
+ };
104108
+ }
104109
+ // lib/ratelimit/storage/redis.storage.ts
104110
+ class RedisStorage {
104111
+ redisClient;
104112
+ prefix = "ratelimit:";
104113
+ constructor(redisClient, prefix) {
104114
+ this.redisClient = redisClient;
104115
+ if (prefix) {
104116
+ this.prefix = prefix;
104117
+ }
104118
+ }
104119
+ getKey(key) {
104120
+ return `${this.prefix}${key}`;
104121
+ }
104122
+ async increment(key, windowMs) {
104123
+ const fullKey = this.getKey(key);
104124
+ const now = Date.now();
104125
+ const multi = this.redisClient.multi();
104126
+ multi.incr(fullKey);
104127
+ multi.pexpire(fullKey, windowMs);
104128
+ multi.pttl(fullKey);
104129
+ const result = await multi.exec();
104130
+ if (!result) {
104131
+ throw new Error("Failed to execute Redis transaction");
104132
+ }
104133
+ const results = result[1];
104134
+ if (!results || !Array.isArray(results)) {
104135
+ throw new Error("Invalid Redis transaction response");
104136
+ }
104137
+ const countResult = results[0];
104138
+ const ttlResult = results[2];
104139
+ const incrError = countResult[0];
104140
+ const count = countResult[1];
104141
+ if (incrError) {
104142
+ throw incrError;
104143
+ }
104144
+ const ttl = ttlResult?.[1] ?? -1;
104145
+ let resetTime;
104146
+ if (typeof ttl === "number" && ttl > 0) {
104147
+ resetTime = now + ttl;
104148
+ } else {
104149
+ resetTime = now + windowMs;
104150
+ }
104151
+ return {
104152
+ totalHits: typeof count === "string" ? Number.parseInt(count, 10) : count || 1,
104153
+ remaining: Infinity,
104154
+ resetTime
104155
+ };
104156
+ }
104157
+ async decrement(key) {
104158
+ const fullKey = this.getKey(key);
104159
+ await this.redisClient.decr(fullKey);
104160
+ }
104161
+ async resetKey(key) {
104162
+ const fullKey = this.getKey(key);
104163
+ await this.redisClient.del(fullKey);
104164
+ }
104165
+ async close() {
104166
+ if (this.redisClient.quit) {
104167
+ await this.redisClient.quit();
104168
+ }
104169
+ }
104170
+ }
103852
104171
  // lib/cqrs/cqrs-module.ts
103853
104172
  class CqrsModule {
103854
104173
  }
@@ -103923,7 +104242,7 @@ BullMqModule = __legacyDecorateClassTS([
103923
104242
  })
103924
104243
  ], BullMqModule);
103925
104244
  // lib/bullmq/decorators/processor.decorator.ts
103926
- var import_reflect_metadata17 = __toESM(require_Reflect(), 1);
104245
+ var import_reflect_metadata18 = __toESM(require_Reflect(), 1);
103927
104246
  function Processor(options) {
103928
104247
  const processorOptions = typeof options === "string" ? { queueName: options } : options;
103929
104248
  return (target2) => {
@@ -103932,7 +104251,7 @@ function Processor(options) {
103932
104251
  };
103933
104252
  }
103934
104253
  // lib/bullmq/decorators/process.decorator.ts
103935
- var import_reflect_metadata18 = __toESM(require_Reflect(), 1);
104254
+ var import_reflect_metadata19 = __toESM(require_Reflect(), 1);
103936
104255
  function Process(name3) {
103937
104256
  return (target2, propertyKey, _descriptor) => {
103938
104257
  const sym = Symbol.for("dip:providers:bullmq");
@@ -103946,7 +104265,7 @@ function Process(name3) {
103946
104265
  };
103947
104266
  }
103948
104267
  // lib/guard.ts
103949
- var import_reflect_metadata19 = __toESM(require_Reflect(), 1);
104268
+ var import_reflect_metadata20 = __toESM(require_Reflect(), 1);
103950
104269
 
103951
104270
  // lib/utils/is-class.ts
103952
104271
  function isClass(fn3) {
@@ -104018,7 +104337,7 @@ function Jwt() {
104018
104337
  };
104019
104338
  }
104020
104339
  // lib/jwt/jwt-module.ts
104021
- var import_reflect_metadata20 = __toESM(require_Reflect(), 1);
104340
+ var import_reflect_metadata21 = __toESM(require_Reflect(), 1);
104022
104341
 
104023
104342
  class JwtModule {
104024
104343
  static options;
@@ -104034,7 +104353,7 @@ class JwtModule {
104034
104353
  }
104035
104354
  }
104036
104355
  // lib/schedule/cron/cron.ts
104037
- var import_reflect_metadata21 = __toESM(require_Reflect(), 1);
104356
+ var import_reflect_metadata22 = __toESM(require_Reflect(), 1);
104038
104357
  function Cron(expression) {
104039
104358
  if (!expression) {
104040
104359
  throw new Error("Invalid cron expression.");
@@ -104054,7 +104373,7 @@ function Cron(expression) {
104054
104373
  };
104055
104374
  }
104056
104375
  // lib/schedule/timeout/timeout.ts
104057
- var import_reflect_metadata22 = __toESM(require_Reflect(), 1);
104376
+ var import_reflect_metadata23 = __toESM(require_Reflect(), 1);
104058
104377
  function Timeout(delay2) {
104059
104378
  if (!delay2 || delay2 < 0) {
104060
104379
  throw new Error("Delay must be a positive number.");
@@ -104074,7 +104393,7 @@ function Timeout(delay2) {
104074
104393
  };
104075
104394
  }
104076
104395
  // lib/testing/testing-module-builder.ts
104077
- var import_reflect_metadata23 = __toESM(require_Reflect(), 1);
104396
+ var import_reflect_metadata24 = __toESM(require_Reflect(), 1);
104078
104397
 
104079
104398
  // lib/testing/test-app.ts
104080
104399
  class TestApp {
@@ -104229,7 +104548,13 @@ export {
104229
104548
  SAGA_METADATA,
104230
104549
  Request2 as Request,
104231
104550
  Render,
104551
+ RedisStorage,
104552
+ RateLimitService,
104553
+ RateLimitExceededException,
104554
+ RateLimit,
104232
104555
  RENDER_METADATA,
104556
+ RATELIMIT_METADATA_KEY,
104557
+ RATELIMIT_CONTROLLER_METADATA_KEY,
104233
104558
  QueueService,
104234
104559
  QueryHandler,
104235
104560
  QueryBus,
@@ -104248,6 +104573,7 @@ export {
104248
104573
  NoContentResponse,
104249
104574
  ModuleInitializationError,
104250
104575
  Module,
104576
+ MemoryStorage,
104251
104577
  Logger,
104252
104578
  LogLevel,
104253
104579
  Layout,
@@ -104274,6 +104600,7 @@ export {
104274
104600
  DependencyResolutionError,
104275
104601
  Delete,
104276
104602
  DatabaseError,
104603
+ DEFAULT_RATELIMIT_CONFIG,
104277
104604
  Cron,
104278
104605
  CreatedResponse,
104279
104606
  CqrsModule,
@@ -10,6 +10,8 @@ export declare class AppStartup {
10
10
  private static readonly logger;
11
11
  private static readonly registeredSagas;
12
12
  private static readonly viewBundles;
13
+ private static globalRateLimitConfig;
14
+ private static rateLimitService;
13
15
  /**
14
16
  * Initializes the application from a root module.
15
17
  *
@@ -68,6 +70,10 @@ export declare class AppStartup {
68
70
  private static executeControllerMethod;
69
71
  private static registerModules;
70
72
  private static registerRoutes;
73
+ /**
74
+ * Builds effective rate limit configuration by merging global config with method config
75
+ */
76
+ private static buildEffectiveRateLimit;
71
77
  private static registerTimeouts;
72
78
  private static registerCronJobs;
73
79
  private static registerBullMqWorkers;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Metadados utilizados pelo sistema de rate limit
3
+ */
4
+ /** Chave de metadado para configuração de rate limit em métodos */
5
+ export declare const RATELIMIT_METADATA_KEY = "dip:ratelimit";
6
+ /** Chave de metadado para configuração de rate limit em controllers */
7
+ export declare const RATELIMIT_CONTROLLER_METADATA_KEY = "dip:ratelimit:controller";
8
+ /** Configurações padrão de rate limit */
9
+ export declare const DEFAULT_RATELIMIT_CONFIG: {
10
+ max: number;
11
+ windowMs: number;
12
+ };
@@ -0,0 +1,6 @@
1
+ export * from "./constants";
2
+ export * from "./interfaces/storage.interface";
3
+ export * from "./ratelimit.decorator";
4
+ export * from "./ratelimit.service";
5
+ export * from "./storage/memory.storage";
6
+ export * from "./storage/redis.storage";
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Interface que define o contrato para storage de rate limiting.
3
+ * Suporta implementações em memória e Redis.
4
+ */
5
+ /**
6
+ * Informações de consumo de rate limit
7
+ */
8
+ export interface RateLimitInfo {
9
+ /** Total de requisições permitidas no window */
10
+ totalHits: number;
11
+ /** Quantidade de requisições restantes */
12
+ remaining: number;
13
+ /** Timestamp (ms) quando o window reseta */
14
+ resetTime: number;
15
+ }
16
+ /**
17
+ * Interface base para storage de rate limit
18
+ */
19
+ export interface RateLimitStorage {
20
+ /**
21
+ * Incrementa o contador para uma chave específica
22
+ * @param key Chave única (IP + endpoint)
23
+ * @param windowMs Janela de tempo em milissegundos
24
+ * @returns Informações atualizadas de rate limit
25
+ */
26
+ increment(key: string, windowMs: number): Promise<RateLimitInfo> | RateLimitInfo;
27
+ /**
28
+ * Decrementa o contador (útil quando requisição é rejeitada antes do processamento)
29
+ * @param key Chave única
30
+ */
31
+ decrement(key: string): Promise<void> | void;
32
+ /**
33
+ * Reseta o contador para uma chave específica
34
+ * @param key Chave única
35
+ */
36
+ resetKey(key: string): Promise<void> | void;
37
+ /**
38
+ * Fecha a conexão (para storage externo como Redis)
39
+ */
40
+ close?(): Promise<void> | void;
41
+ }
@@ -0,0 +1,52 @@
1
+ import "reflect-metadata";
2
+ import type { HttpRequest } from "../types/http-request";
3
+ import type { RateLimitStorage } from "./interfaces/storage.interface";
4
+ import type { RateLimitConfig } from "./ratelimit.service";
5
+ /**
6
+ * Opções para o decorator @RateLimit
7
+ */
8
+ export interface RateLimitOptions {
9
+ /** Se o rate limit está habilitado (padrão: true) */
10
+ enabled?: boolean;
11
+ /** Máximo de requisições permitidas na janela de tempo (padrão: 100) */
12
+ max?: number;
13
+ /** Janela de tempo em milissegundos (padrão: 60000 = 1 minuto) */
14
+ windowMs?: number;
15
+ /** Mensagem de erro customizada quando excede o limite */
16
+ message?: string;
17
+ /** Storage customizado (padrão: MemoryStorage) */
18
+ storage?: RateLimitStorage;
19
+ /** Função para extrair a chave de identificação */
20
+ keyGenerator?: (req: HttpRequest) => string;
21
+ /** Header que permite pular o rate limit */
22
+ skipHeader?: string;
23
+ /** Função que determina se deve pular o rate limit */
24
+ skip?: (req: HttpRequest) => boolean;
25
+ }
26
+ /**
27
+ * Configuração interna completa de rate limit
28
+ */
29
+ export interface RateLimitMetadata extends RateLimitConfig {
30
+ enabled: boolean;
31
+ }
32
+ /**
33
+ * Decorator para aplicar rate limiting em métodos de controller ou em todo o controller.
34
+ *
35
+ * @param options Opções de configuração do rate limit
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * // No controller (afeta todos os métodos)
40
+ * @RateLimit({ max: 50, windowMs: 60000 })
41
+ * @Controller('api')
42
+ * class MyController {
43
+ * // No método (sobrescreve o do controller)
44
+ * @Get('data')
45
+ * @RateLimit({ max: 100, windowMs: 60000 })
46
+ * getData() {
47
+ * return { data: [] };
48
+ * }
49
+ * }
50
+ * ```
51
+ */
52
+ export declare function RateLimit(options?: RateLimitOptions): any;