@grupodiariodaregiao/bunstone 0.4.1 → 0.4.3

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.d.ts CHANGED
@@ -55,6 +55,7 @@ export * from "./lib/jwt";
55
55
  export * from "./lib/jwt/jwt-module";
56
56
  export { JwtService } from "./lib/jwt/jwt.service";
57
57
  export * from "./lib/module";
58
+ export * from "./lib/on-module";
58
59
  export * from "./lib/openapi";
59
60
  export * from "./lib/render";
60
61
  export * from "./lib/types/options";
package/dist/index.js CHANGED
@@ -116627,6 +116627,14 @@ function Head2(pathname = "") {
116627
116627
  return HttpMethodDecorator("HEAD", pathname);
116628
116628
  }
116629
116629
 
116630
+ // lib/on-module/on-module-destroy.ts
116631
+ class OnModuleDestroy {
116632
+ }
116633
+
116634
+ // lib/on-module/on-module-init.ts
116635
+ class OnModuleInit {
116636
+ }
116637
+
116630
116638
  // lib/openapi.ts
116631
116639
  var import_reflect_metadata13 = __toESM(require_Reflect(), 1);
116632
116640
  var API_TAGS_METADATA = "dip:openapi:tags";
@@ -117086,12 +117094,22 @@ class AppStartup {
117086
117094
  static elysia = new Elysia;
117087
117095
  static logger = new Logger(AppStartup.name);
117088
117096
  static registeredSagas = new WeakSet;
117097
+ static initializedModuleHooks = new WeakSet;
117098
+ static destroyedModuleHooks = new WeakSet;
117099
+ static destroyPromise = null;
117100
+ static hasBeenDestroyed = false;
117101
+ static rootModule;
117089
117102
  static viewBundles = new Map;
117090
117103
  static globalRateLimitConfig;
117091
117104
  static rateLimitService = new RateLimitService;
117092
117105
  static async create(module, options) {
117093
117106
  try {
117094
117107
  AppStartup.elysia = new Elysia;
117108
+ AppStartup.rootModule = module;
117109
+ AppStartup.initializedModuleHooks = new WeakSet;
117110
+ AppStartup.destroyedModuleHooks = new WeakSet;
117111
+ AppStartup.destroyPromise = null;
117112
+ AppStartup.hasBeenDestroyed = false;
117095
117113
  const publicExists = await Bun.file("public").exists();
117096
117114
  if (!publicExists)
117097
117115
  await mkdir("./public", { recursive: true });
@@ -117144,6 +117162,9 @@ class AppStartup {
117144
117162
  message: error48 instanceof Error ? error48.message : "Internal Server Error"
117145
117163
  };
117146
117164
  });
117165
+ AppStartup.elysia.onStop(async () => {
117166
+ await AppStartup.executeDestroyLifecycle();
117167
+ });
117147
117168
  if (options?.cors) {
117148
117169
  AppStartup.elysia.use(cors(options.cors));
117149
117170
  }
@@ -117181,7 +117202,7 @@ class AppStartup {
117181
117202
  }));
117182
117203
  }
117183
117204
  AppStartup.globalRateLimitConfig = options?.rateLimit;
117184
- AppStartup.registerModules(module);
117205
+ await AppStartup.registerModules(module);
117185
117206
  return {
117186
117207
  listen: AppStartup.listen,
117187
117208
  getElysia: () => AppStartup.elysia
@@ -117293,6 +117314,28 @@ if (document.readyState === 'loading') {
117293
117314
  AppStartup.logger.log(`App is running at http://localhost:${port}`);
117294
117315
  AppStartup.elysia.listen(port);
117295
117316
  }
117317
+ static async executeDestroyLifecycle() {
117318
+ if (!AppStartup.rootModule) {
117319
+ return;
117320
+ }
117321
+ if (AppStartup.hasBeenDestroyed) {
117322
+ return;
117323
+ }
117324
+ if (AppStartup.destroyPromise) {
117325
+ await AppStartup.destroyPromise;
117326
+ return;
117327
+ }
117328
+ AppStartup.destroyPromise = (async () => {
117329
+ await AppStartup.destroyModules(AppStartup.rootModule);
117330
+ AppStartup.hasBeenDestroyed = true;
117331
+ })().catch((error48) => {
117332
+ AppStartup.logger.error(`Error while executing OnModuleDestroy hooks: ${error48?.message || error48}`);
117333
+ throw error48;
117334
+ }).finally(() => {
117335
+ AppStartup.destroyPromise = null;
117336
+ });
117337
+ await AppStartup.destroyPromise;
117338
+ }
117296
117339
  static async executeControllerMethod(context, controller, method) {
117297
117340
  const args = await processParameters(context, controller, method);
117298
117341
  const result = await controller[method](...args);
@@ -117336,7 +117379,7 @@ if (document.readyState === 'loading') {
117336
117379
  }
117337
117380
  return result;
117338
117381
  }
117339
- static registerModules(module) {
117382
+ static async registerModules(module) {
117340
117383
  const isGlobal = Reflect.getMetadata("dip:module:global", module);
117341
117384
  if (isGlobal) {
117342
117385
  const injectables = Reflect.getMetadata("dip:injectables", module);
@@ -117355,7 +117398,47 @@ if (document.readyState === 'loading') {
117355
117398
  AppStartup.registerRabbitMQConsumers(module);
117356
117399
  const modules = Reflect.getMetadata("dip:modules", module) || [];
117357
117400
  for (const mod of modules) {
117358
- AppStartup.registerModules(mod);
117401
+ await AppStartup.registerModules(mod);
117402
+ }
117403
+ await AppStartup.executeOnModuleInit(module);
117404
+ }
117405
+ static async destroyModules(module) {
117406
+ const modules = Reflect.getMetadata("dip:modules", module) || [];
117407
+ for (const mod of modules) {
117408
+ await AppStartup.destroyModules(mod);
117409
+ }
117410
+ await AppStartup.executeOnModuleDestroy(module);
117411
+ }
117412
+ static async executeOnModuleInit(module) {
117413
+ const injectables = Reflect.getMetadata("dip:injectables", module);
117414
+ if (!injectables) {
117415
+ return;
117416
+ }
117417
+ for (const provider of injectables.values()) {
117418
+ if (!(provider instanceof OnModuleInit)) {
117419
+ continue;
117420
+ }
117421
+ if (AppStartup.initializedModuleHooks.has(provider)) {
117422
+ continue;
117423
+ }
117424
+ AppStartup.initializedModuleHooks.add(provider);
117425
+ await provider.onModuleInit();
117426
+ }
117427
+ }
117428
+ static async executeOnModuleDestroy(module) {
117429
+ const injectables = Reflect.getMetadata("dip:injectables", module);
117430
+ if (!injectables) {
117431
+ return;
117432
+ }
117433
+ for (const provider of injectables.values()) {
117434
+ if (!(provider instanceof OnModuleDestroy)) {
117435
+ continue;
117436
+ }
117437
+ if (AppStartup.destroyedModuleHooks.has(provider)) {
117438
+ continue;
117439
+ }
117440
+ AppStartup.destroyedModuleHooks.add(provider);
117441
+ await provider.onModuleDestroy();
117359
117442
  }
117360
117443
  }
117361
117444
  static registerRoutes(module) {
@@ -117583,6 +117666,33 @@ if (document.readyState === 'loading') {
117583
117666
  return;
117584
117667
  }
117585
117668
  const injectables = Reflect.getMetadata("dip:injectables", module);
117669
+ function matchRoutingKey(pattern, routingKey) {
117670
+ if (pattern === routingKey)
117671
+ return true;
117672
+ if (!pattern.includes("*") && !pattern.includes("#"))
117673
+ return false;
117674
+ const pp = pattern.split(".");
117675
+ const kp = routingKey.split(".");
117676
+ function go2(pi3, ki3) {
117677
+ if (pi3 === pp.length && ki3 === kp.length)
117678
+ return true;
117679
+ if (pi3 === pp.length)
117680
+ return false;
117681
+ if (pp[pi3] === "#") {
117682
+ for (let j4 = ki3;j4 <= kp.length; j4++) {
117683
+ if (go2(pi3 + 1, j4))
117684
+ return true;
117685
+ }
117686
+ return false;
117687
+ }
117688
+ if (ki3 === kp.length)
117689
+ return false;
117690
+ if (pp[pi3] === "*" || pp[pi3] === kp[ki3])
117691
+ return go2(pi3 + 1, ki3 + 1);
117692
+ return false;
117693
+ }
117694
+ return go2(0, 0);
117695
+ }
117586
117696
  (async () => {
117587
117697
  try {
117588
117698
  await RabbitMQConnection.initialise();
@@ -117652,7 +117762,10 @@ if (document.readyState === 'loading') {
117652
117762
  }
117653
117763
  for (const [queue2, handlers] of queueMap.entries()) {
117654
117764
  const noAck = handlers.every((h3) => h3.noAck);
117655
- const handlerList = handlers.map((h3) => `${h3.providerName}.${h3.descriptor.methodName}()`).join(", ");
117765
+ const handlerList = handlers.map((h3) => {
117766
+ const rk = h3.descriptor.options.routingKey;
117767
+ return `${h3.providerName}.${h3.descriptor.methodName}()${rk ? ` [${rk}]` : ""}`;
117768
+ }).join(", ");
117656
117769
  AppStartup.logger.log(`Registering RabbitMQ consumer for queue: "${queue2}" \u2192 [${handlerList}]`);
117657
117770
  try {
117658
117771
  const channel = await RabbitMQConnection.getConsumerChannel(queue2);
@@ -117686,6 +117799,10 @@ if (document.readyState === 'loading') {
117686
117799
  noAck: handlerNoAck,
117687
117800
  providerName
117688
117801
  } of handlers) {
117802
+ const handlerRoutingKey = descriptor.options.routingKey;
117803
+ if (handlerRoutingKey && !matchRoutingKey(handlerRoutingKey, raw.fields.routingKey)) {
117804
+ continue;
117805
+ }
117689
117806
  try {
117690
117807
  await instance[descriptor.methodName](msg);
117691
117808
  } catch (err) {
@@ -117695,6 +117812,9 @@ if (document.readyState === 'loading') {
117695
117812
  }
117696
117813
  }
117697
117814
  }
117815
+ if (!noAck && !settled) {
117816
+ settle(() => channel.ack(raw));
117817
+ }
117698
117818
  }, { noAck });
117699
117819
  } catch (err) {
117700
117820
  AppStartup.logger.error(`Failed to register consumer for queue "${queue2}": ${err.message}`);
@@ -119939,6 +120059,8 @@ export {
119939
120059
  ParamType,
119940
120060
  Param,
119941
120061
  Options,
120062
+ OnModuleInit,
120063
+ OnModuleDestroy,
119942
120064
  OkResponse,
119943
120065
  NotFoundException,
119944
120066
  NoContentResponse,
@@ -9,6 +9,11 @@ export declare class AppStartup {
9
9
  private static elysia;
10
10
  private static readonly logger;
11
11
  private static readonly registeredSagas;
12
+ private static initializedModuleHooks;
13
+ private static destroyedModuleHooks;
14
+ private static destroyPromise;
15
+ private static hasBeenDestroyed;
16
+ private static rootModule;
12
17
  private static readonly viewBundles;
13
18
  private static globalRateLimitConfig;
14
19
  private static rateLimitService;
@@ -67,8 +72,12 @@ export declare class AppStartup {
67
72
  * @param port The port number to listen on.
68
73
  */
69
74
  static listen(port: number): void;
75
+ private static executeDestroyLifecycle;
70
76
  private static executeControllerMethod;
71
77
  private static registerModules;
78
+ private static destroyModules;
79
+ private static executeOnModuleInit;
80
+ private static executeOnModuleDestroy;
72
81
  private static registerRoutes;
73
82
  /**
74
83
  * Builds effective rate limit configuration by merging global config with method config
@@ -0,0 +1,2 @@
1
+ export * from "./on-module-destroy";
2
+ export * from "./on-module-init";
@@ -0,0 +1,3 @@
1
+ export declare abstract class OnModuleDestroy {
2
+ abstract onModuleDestroy(): Promise<void> | void;
3
+ }
@@ -0,0 +1,3 @@
1
+ export declare abstract class OnModuleInit {
2
+ abstract onModuleInit(): Promise<void> | void;
3
+ }
@@ -28,6 +28,8 @@ import { ConfigurationError } from "./errors";
28
28
  import { HttpException } from "./http-exceptions";
29
29
  import { HTTP_HEADERS_METADATA } from "./http-methods";
30
30
  import { ParamType, processParameters } from "./http-params";
31
+ import { OnModuleDestroy } from "./on-module/on-module-destroy";
32
+ import { OnModuleInit } from "./on-module/on-module-init";
31
33
  import {
32
34
  API_HEADERS_METADATA,
33
35
  API_OPERATION_METADATA,
@@ -57,6 +59,11 @@ export class AppStartup {
57
59
  private static elysia: Elysia = new Elysia();
58
60
  private static readonly logger = new Logger(AppStartup.name);
59
61
  private static readonly registeredSagas = new WeakSet<any>();
62
+ private static initializedModuleHooks = new WeakSet<OnModuleInit>();
63
+ private static destroyedModuleHooks = new WeakSet<OnModuleDestroy>();
64
+ private static destroyPromise: Promise<void> | null = null;
65
+ private static hasBeenDestroyed = false;
66
+ private static rootModule: any;
60
67
  private static readonly viewBundles = new Map<string, string>();
61
68
  private static globalRateLimitConfig: RateLimitGlobalConfig | undefined;
62
69
  private static rateLimitService: RateLimitService = new RateLimitService();
@@ -71,6 +78,11 @@ export class AppStartup {
71
78
  static async create(module: any, options?: Options) {
72
79
  try {
73
80
  AppStartup.elysia = new Elysia(); // Reset for each creation
81
+ AppStartup.rootModule = module;
82
+ AppStartup.initializedModuleHooks = new WeakSet<OnModuleInit>();
83
+ AppStartup.destroyedModuleHooks = new WeakSet<OnModuleDestroy>();
84
+ AppStartup.destroyPromise = null;
85
+ AppStartup.hasBeenDestroyed = false;
74
86
 
75
87
  const publicExists = await Bun.file("public").exists();
76
88
  // Ensure public directory exists before static plugin uses it
@@ -149,6 +161,10 @@ export class AppStartup {
149
161
  };
150
162
  });
151
163
 
164
+ AppStartup.elysia.onStop(async () => {
165
+ await AppStartup.executeDestroyLifecycle();
166
+ });
167
+
152
168
  if (options?.cors) {
153
169
  AppStartup.elysia.use(cors(options.cors));
154
170
  }
@@ -202,7 +218,7 @@ export class AppStartup {
202
218
  // Store global rate limit config
203
219
  AppStartup.globalRateLimitConfig = options?.rateLimit;
204
220
 
205
- AppStartup.registerModules(module);
221
+ await AppStartup.registerModules(module);
206
222
  return {
207
223
  /**
208
224
  * Starts the server on the specified port.
@@ -352,6 +368,37 @@ if (document.readyState === 'loading') {
352
368
  AppStartup.elysia.listen(port);
353
369
  }
354
370
 
371
+ private static async executeDestroyLifecycle() {
372
+ if (!AppStartup.rootModule) {
373
+ return;
374
+ }
375
+
376
+ if (AppStartup.hasBeenDestroyed) {
377
+ return;
378
+ }
379
+
380
+ if (AppStartup.destroyPromise) {
381
+ await AppStartup.destroyPromise;
382
+ return;
383
+ }
384
+
385
+ AppStartup.destroyPromise = (async () => {
386
+ await AppStartup.destroyModules(AppStartup.rootModule);
387
+ AppStartup.hasBeenDestroyed = true;
388
+ })()
389
+ .catch((error) => {
390
+ AppStartup.logger.error(
391
+ `Error while executing OnModuleDestroy hooks: ${error?.message || error}`,
392
+ );
393
+ throw error;
394
+ })
395
+ .finally(() => {
396
+ AppStartup.destroyPromise = null;
397
+ });
398
+
399
+ await AppStartup.destroyPromise;
400
+ }
401
+
355
402
  private static async executeControllerMethod(
356
403
  context: any,
357
404
  controller: any,
@@ -436,7 +483,7 @@ if (document.readyState === 'loading') {
436
483
  return result;
437
484
  }
438
485
 
439
- private static registerModules(module: any) {
486
+ private static async registerModules(module: any) {
440
487
  const isGlobal = Reflect.getMetadata("dip:module:global", module);
441
488
  if (isGlobal) {
442
489
  const injectables: Map<any, any> = Reflect.getMetadata(
@@ -461,7 +508,67 @@ if (document.readyState === 'loading') {
461
508
  const modules = Reflect.getMetadata("dip:modules", module) || [];
462
509
 
463
510
  for (const mod of modules) {
464
- AppStartup.registerModules(mod);
511
+ await AppStartup.registerModules(mod);
512
+ }
513
+
514
+ await AppStartup.executeOnModuleInit(module);
515
+ }
516
+
517
+ private static async destroyModules(module: any) {
518
+ const modules = Reflect.getMetadata("dip:modules", module) || [];
519
+
520
+ for (const mod of modules) {
521
+ await AppStartup.destroyModules(mod);
522
+ }
523
+
524
+ await AppStartup.executeOnModuleDestroy(module);
525
+ }
526
+
527
+ private static async executeOnModuleInit(module: any) {
528
+ const injectables: Map<any, any> | undefined = Reflect.getMetadata(
529
+ "dip:injectables",
530
+ module,
531
+ );
532
+
533
+ if (!injectables) {
534
+ return;
535
+ }
536
+
537
+ for (const provider of injectables.values()) {
538
+ if (!(provider instanceof OnModuleInit)) {
539
+ continue;
540
+ }
541
+
542
+ if (AppStartup.initializedModuleHooks.has(provider)) {
543
+ continue;
544
+ }
545
+
546
+ AppStartup.initializedModuleHooks.add(provider);
547
+ await provider.onModuleInit();
548
+ }
549
+ }
550
+
551
+ private static async executeOnModuleDestroy(module: any) {
552
+ const injectables: Map<any, any> | undefined = Reflect.getMetadata(
553
+ "dip:injectables",
554
+ module,
555
+ );
556
+
557
+ if (!injectables) {
558
+ return;
559
+ }
560
+
561
+ for (const provider of injectables.values()) {
562
+ if (!(provider instanceof OnModuleDestroy)) {
563
+ continue;
564
+ }
565
+
566
+ if (AppStartup.destroyedModuleHooks.has(provider)) {
567
+ continue;
568
+ }
569
+
570
+ AppStartup.destroyedModuleHooks.add(provider);
571
+ await provider.onModuleDestroy();
465
572
  }
466
573
  }
467
574
 
@@ -879,6 +986,34 @@ if (document.readyState === 'loading') {
879
986
  module,
880
987
  );
881
988
 
989
+ /**
990
+ * Matches a RabbitMQ topic routing key pattern against a concrete routing key.
991
+ * Supports `*` (exactly one word) and `#` (zero or more words).
992
+ */
993
+ function matchRoutingKey(pattern: string, routingKey: string): boolean {
994
+ if (pattern === routingKey) return true;
995
+ if (!pattern.includes("*") && !pattern.includes("#")) return false;
996
+
997
+ const pp = pattern.split(".");
998
+ const kp = routingKey.split(".");
999
+
1000
+ function go(pi: number, ki: number): boolean {
1001
+ if (pi === pp.length && ki === kp.length) return true;
1002
+ if (pi === pp.length) return false;
1003
+ if (pp[pi] === "#") {
1004
+ for (let j = ki; j <= kp.length; j++) {
1005
+ if (go(pi + 1, j)) return true;
1006
+ }
1007
+ return false;
1008
+ }
1009
+ if (ki === kp.length) return false;
1010
+ if (pp[pi] === "*" || pp[pi] === kp[ki]) return go(pi + 1, ki + 1);
1011
+ return false;
1012
+ }
1013
+
1014
+ return go(0, 0);
1015
+ }
1016
+
882
1017
  type QueueHandler = {
883
1018
  instance: any;
884
1019
  descriptor: RabbitMQMethodDescriptor;
@@ -995,7 +1130,10 @@ if (document.readyState === 'loading') {
995
1130
  const noAck = handlers.every((h) => h.noAck);
996
1131
 
997
1132
  const handlerList = handlers
998
- .map((h) => `${h.providerName}.${h.descriptor.methodName}()`)
1133
+ .map((h) => {
1134
+ const rk = h.descriptor.options.routingKey;
1135
+ return `${h.providerName}.${h.descriptor.methodName}()${rk ? ` [${rk}]` : ""}`;
1136
+ })
999
1137
  .join(", ");
1000
1138
 
1001
1139
  AppStartup.logger.log(
@@ -1043,6 +1181,17 @@ if (document.readyState === 'loading') {
1043
1181
  noAck: handlerNoAck,
1044
1182
  providerName,
1045
1183
  } of handlers) {
1184
+ // ── Routing-key filter ─────────────────────────────────────────────
1185
+ // When { queue, routingKey } is set without exchange, only dispatch
1186
+ // if the message's routing key matches the declared pattern.
1187
+ const handlerRoutingKey = descriptor.options.routingKey;
1188
+ if (
1189
+ handlerRoutingKey &&
1190
+ !matchRoutingKey(handlerRoutingKey, raw.fields.routingKey)
1191
+ ) {
1192
+ continue;
1193
+ }
1194
+
1046
1195
  try {
1047
1196
  await instance[descriptor.methodName](msg);
1048
1197
  } catch (err: any) {
@@ -1054,6 +1203,13 @@ if (document.readyState === 'loading') {
1054
1203
  }
1055
1204
  }
1056
1205
  }
1206
+
1207
+ // ── Auto-ack if no handler consumed the message ────────────────
1208
+ // All handlers were filtered out by routingKey – ack silently
1209
+ // to prevent the message from piling up as unacked.
1210
+ if (!noAck && !settled) {
1211
+ settle(() => channel.ack(raw));
1212
+ }
1057
1213
  },
1058
1214
  { noAck },
1059
1215
  );
@@ -0,0 +1,2 @@
1
+ export * from "./on-module-destroy";
2
+ export * from "./on-module-init";
@@ -0,0 +1,3 @@
1
+ export abstract class OnModuleDestroy {
2
+ abstract onModuleDestroy(): Promise<void> | void;
3
+ }
@@ -0,0 +1,3 @@
1
+ export abstract class OnModuleInit {
2
+ abstract onModuleInit(): Promise<void> | void;
3
+ }
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "types": "./dist/*.d.ts"
14
14
  }
15
15
  },
16
- "version": "0.4.1",
16
+ "version": "0.4.3",
17
17
  "homepage": "https://bunstone.diario.one/",
18
18
  "repository": {
19
19
  "url": "https://github.com/diariodaregiao/bunstone.git",