@geekmidas/constructs 9.0.2 → 9.1.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.
Files changed (186) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/{AWSScheduledFunction-Bn99MceY.d.cts → AWSScheduledFunction-BHHlWlfV.d.cts} +2 -2
  3. package/dist/{AWSScheduledFunction-ysaCsdWx.d.mts.map → AWSScheduledFunction-BHHlWlfV.d.cts.map} +1 -1
  4. package/dist/{AWSScheduledFunction-ysaCsdWx.d.mts → AWSScheduledFunction-BzGCUbkk.d.mts} +2 -2
  5. package/dist/{AWSScheduledFunction-Bn99MceY.d.cts.map → AWSScheduledFunction-BzGCUbkk.d.mts.map} +1 -1
  6. package/dist/{AWSScheduledFunction-DbkDr8CA.cjs → AWSScheduledFunction-C42adp8A.cjs} +16 -5
  7. package/dist/AWSScheduledFunction-C42adp8A.cjs.map +1 -0
  8. package/dist/{AWSScheduledFunction-CufMMhGB.mjs → AWSScheduledFunction-KP6lqbsW.mjs} +15 -4
  9. package/dist/AWSScheduledFunction-KP6lqbsW.mjs.map +1 -0
  10. package/dist/{Construct-r0U2zBwW.mjs → Construct-BWF5HxaM.mjs} +5 -2
  11. package/dist/Construct-BWF5HxaM.mjs.map +1 -0
  12. package/dist/{Construct-_1wX7p_Y.d.cts → Construct-JlyQkBkv.d.cts} +39 -4
  13. package/dist/Construct-JlyQkBkv.d.cts.map +1 -0
  14. package/dist/{Construct-vXMZJgRp.cjs → Construct-TGIM_E4o.cjs} +5 -2
  15. package/dist/Construct-TGIM_E4o.cjs.map +1 -0
  16. package/dist/{Construct-DCMuYOox.d.mts → Construct-pxwC6fDD.d.mts} +39 -4
  17. package/dist/Construct-pxwC6fDD.d.mts.map +1 -0
  18. package/dist/{Endpoint-CLPSIl6S.d.mts → Endpoint-B0P_pEMr.d.mts} +3 -3
  19. package/dist/{Endpoint-CLPSIl6S.d.mts.map → Endpoint-B0P_pEMr.d.mts.map} +1 -1
  20. package/dist/{Endpoint-DgfRslct.d.cts → Endpoint-Cwjhc1Hl.d.cts} +2 -2
  21. package/dist/Endpoint-Cwjhc1Hl.d.cts.map +1 -0
  22. package/dist/{HonoEndpointAdaptor-DJDT3PMx.mjs → HonoEndpointAdaptor-BMVwx2cz.mjs} +18 -11
  23. package/dist/HonoEndpointAdaptor-BMVwx2cz.mjs.map +1 -0
  24. package/dist/{HonoEndpointAdaptor-CkJENANP.cjs → HonoEndpointAdaptor-DPDsDi-D.cjs} +19 -12
  25. package/dist/HonoEndpointAdaptor-DPDsDi-D.cjs.map +1 -0
  26. package/dist/{Queue-HBDf0fqz.d.mts → Queue-BL68oDJ-.d.mts} +2 -2
  27. package/dist/{Queue-DaxjwmOQ.d.cts.map → Queue-BL68oDJ-.d.mts.map} +1 -1
  28. package/dist/{Queue-DaxjwmOQ.d.cts → Queue-DxJ6btRE.d.cts} +2 -2
  29. package/dist/{Queue-HBDf0fqz.d.mts.map → Queue-DxJ6btRE.d.cts.map} +1 -1
  30. package/dist/{Subscriber-B69ngPwl.d.cts → Subscriber-BpVhcGhX.d.cts} +2 -2
  31. package/dist/{Subscriber-B69ngPwl.d.cts.map → Subscriber-BpVhcGhX.d.cts.map} +1 -1
  32. package/dist/{Subscriber-D8Xm-86W.d.mts → Subscriber-DF6xsBAJ.d.mts} +2 -2
  33. package/dist/{Subscriber-D8Xm-86W.d.mts.map → Subscriber-DF6xsBAJ.d.mts.map} +1 -1
  34. package/dist/{Topic-DT10AqHk.d.mts → Topic-DJXoDeMP.d.cts} +3 -3
  35. package/dist/{Topic-C5ZMfveW.d.cts.map → Topic-DJXoDeMP.d.cts.map} +1 -1
  36. package/dist/{Topic-C5ZMfveW.d.cts → Topic-DxWQBgMN.d.mts} +3 -3
  37. package/dist/{Topic-DT10AqHk.d.mts.map → Topic-DxWQBgMN.d.mts.map} +1 -1
  38. package/dist/adaptors/aws.cjs +14 -7
  39. package/dist/adaptors/aws.cjs.map +1 -1
  40. package/dist/adaptors/aws.d.cts +6 -6
  41. package/dist/adaptors/aws.d.cts.map +1 -1
  42. package/dist/adaptors/aws.d.mts +8 -8
  43. package/dist/adaptors/aws.d.mts.map +1 -1
  44. package/dist/adaptors/aws.mjs +14 -7
  45. package/dist/adaptors/aws.mjs.map +1 -1
  46. package/dist/adaptors/hono.cjs +7 -6
  47. package/dist/adaptors/hono.d.cts +3 -3
  48. package/dist/adaptors/hono.d.cts.map +1 -1
  49. package/dist/adaptors/hono.d.mts +4 -4
  50. package/dist/adaptors/hono.d.mts.map +1 -1
  51. package/dist/adaptors/hono.mjs +7 -6
  52. package/dist/adaptors/testing.cjs +161 -126
  53. package/dist/adaptors/testing.cjs.map +1 -1
  54. package/dist/adaptors/testing.d.cts +7 -5
  55. package/dist/adaptors/testing.d.cts.map +1 -1
  56. package/dist/adaptors/testing.d.mts +8 -6
  57. package/dist/adaptors/testing.d.mts.map +1 -1
  58. package/dist/adaptors/testing.mjs +162 -127
  59. package/dist/adaptors/testing.mjs.map +1 -1
  60. package/dist/clone-I_WIpiFf.cjs +45 -0
  61. package/dist/clone-I_WIpiFf.cjs.map +1 -0
  62. package/dist/clone-pdXxc0t3.mjs +39 -0
  63. package/dist/clone-pdXxc0t3.mjs.map +1 -0
  64. package/dist/crons/index.cjs +14 -32
  65. package/dist/crons/index.cjs.map +1 -1
  66. package/dist/crons/index.d.cts +7 -7
  67. package/dist/crons/index.d.cts.map +1 -1
  68. package/dist/crons/index.d.mts +3 -3
  69. package/dist/crons/index.d.mts.map +1 -1
  70. package/dist/crons/index.mjs +14 -32
  71. package/dist/crons/index.mjs.map +1 -1
  72. package/dist/endpoints/index.cjs +74 -61
  73. package/dist/endpoints/index.cjs.map +1 -1
  74. package/dist/endpoints/index.d.cts +14 -6
  75. package/dist/endpoints/index.d.cts.map +1 -1
  76. package/dist/endpoints/index.d.mts +13 -5
  77. package/dist/endpoints/index.d.mts.map +1 -1
  78. package/dist/endpoints/index.mjs +74 -61
  79. package/dist/endpoints/index.mjs.map +1 -1
  80. package/dist/functions/index.cjs +3 -2
  81. package/dist/functions/index.d.cts +2 -2
  82. package/dist/functions/index.d.mts +2 -2
  83. package/dist/functions/index.mjs +3 -2
  84. package/dist/{functions-B-qt91O7.mjs → functions-EqTloHjo.mjs} +107 -46
  85. package/dist/functions-EqTloHjo.mjs.map +1 -0
  86. package/dist/{functions-C8i1kCMM.cjs → functions-SqtCDc9c.cjs} +118 -45
  87. package/dist/functions-SqtCDc9c.cjs.map +1 -0
  88. package/dist/{index-DP6fqv0I.d.mts → index-C7iynzLI.d.mts} +8 -2
  89. package/dist/index-C7iynzLI.d.mts.map +1 -0
  90. package/dist/{index-BVs9DvIj.d.cts → index-DdgJRm4q.d.cts} +12 -6
  91. package/dist/index-DdgJRm4q.d.cts.map +1 -0
  92. package/dist/index.cjs +1 -1
  93. package/dist/index.d.cts +2 -2
  94. package/dist/index.d.mts +4 -4
  95. package/dist/index.mjs +1 -1
  96. package/dist/{lazyAccessors-DfqZR6C4.mjs → lazyAccessors-CCTK19xj.mjs} +3 -3
  97. package/dist/{lazyAccessors-DfqZR6C4.mjs.map → lazyAccessors-CCTK19xj.mjs.map} +1 -1
  98. package/dist/{lazyAccessors-DmTG8sQH.cjs → lazyAccessors-D04oftGw.cjs} +3 -3
  99. package/dist/{lazyAccessors-DmTG8sQH.cjs.map → lazyAccessors-D04oftGw.cjs.map} +1 -1
  100. package/dist/monitors/index.cjs +49 -0
  101. package/dist/monitors/index.cjs.map +1 -0
  102. package/dist/monitors/index.d.cts +45 -0
  103. package/dist/monitors/index.d.cts.map +1 -0
  104. package/dist/monitors/index.d.mts +45 -0
  105. package/dist/monitors/index.d.mts.map +1 -0
  106. package/dist/monitors/index.mjs +48 -0
  107. package/dist/monitors/index.mjs.map +1 -0
  108. package/dist/{processAudits-CKjjivyz.cjs → processAudits-BJZT5gmy.cjs} +2 -2
  109. package/dist/{processAudits-CKjjivyz.cjs.map → processAudits-BJZT5gmy.cjs.map} +1 -1
  110. package/dist/{processAudits-ySnizdMG.mjs → processAudits-CwyaaSQc.mjs} +1 -1
  111. package/dist/{processAudits-ySnizdMG.mjs.map → processAudits-CwyaaSQc.mjs.map} +1 -1
  112. package/dist/{publisher-lFQleddL.cjs → publisher-BZgcrVPy.cjs} +1 -1
  113. package/dist/{publisher-lFQleddL.cjs.map → publisher-BZgcrVPy.cjs.map} +1 -1
  114. package/dist/{publisher-BrFOF847.mjs → publisher-CnpeMv-r.mjs} +1 -1
  115. package/dist/{publisher-BrFOF847.mjs.map → publisher-CnpeMv-r.mjs.map} +1 -1
  116. package/dist/queue/index.cjs +9 -22
  117. package/dist/queue/index.cjs.map +1 -1
  118. package/dist/queue/index.d.cts +4 -4
  119. package/dist/queue/index.d.cts.map +1 -1
  120. package/dist/queue/index.d.mts +2 -2
  121. package/dist/queue/index.d.mts.map +1 -1
  122. package/dist/queue/index.mjs +9 -22
  123. package/dist/queue/index.mjs.map +1 -1
  124. package/dist/subscribers/index.cjs +9 -22
  125. package/dist/subscribers/index.cjs.map +1 -1
  126. package/dist/subscribers/index.d.cts +5 -5
  127. package/dist/subscribers/index.d.cts.map +1 -1
  128. package/dist/subscribers/index.d.mts +3 -3
  129. package/dist/subscribers/index.d.mts.map +1 -1
  130. package/dist/subscribers/index.mjs +9 -22
  131. package/dist/subscribers/index.mjs.map +1 -1
  132. package/dist/{telemetry-3vdxGn4q.d.mts → telemetry-C--EgYVr.d.mts} +1 -1
  133. package/dist/{telemetry-3vdxGn4q.d.mts.map → telemetry-C--EgYVr.d.mts.map} +1 -1
  134. package/dist/topic/index.cjs +4 -7
  135. package/dist/topic/index.cjs.map +1 -1
  136. package/dist/topic/index.d.cts +2 -2
  137. package/dist/topic/index.d.cts.map +1 -1
  138. package/dist/topic/index.d.mts +2 -2
  139. package/dist/topic/index.d.mts.map +1 -1
  140. package/dist/topic/index.mjs +4 -7
  141. package/dist/topic/index.mjs.map +1 -1
  142. package/dist/{types-BDNGr7pv.d.mts → types-DNngH0fX.d.mts} +1 -1
  143. package/dist/{types-BDNGr7pv.d.mts.map → types-DNngH0fX.d.mts.map} +1 -1
  144. package/dist/types.d.mts +1 -1
  145. package/package.json +32 -22
  146. package/src/Construct.ts +5 -0
  147. package/src/__tests__/builder-reuse.spec.ts +587 -0
  148. package/src/__tests__/monitor.spec.ts +636 -0
  149. package/src/clone.ts +38 -0
  150. package/src/crons/CronBuilder.ts +35 -45
  151. package/src/crons/__tests__/Cron.spec.ts +10 -2
  152. package/src/crons/__tests__/CronBuilder.state-isolation.spec.ts +64 -18
  153. package/src/endpoints/AmazonApiGatewayEndpointAdaptor.ts +23 -2
  154. package/src/endpoints/EndpointBuilder.ts +61 -70
  155. package/src/endpoints/EndpointFactory.ts +31 -0
  156. package/src/endpoints/HonoEndpointAdaptor.ts +16 -1
  157. package/src/endpoints/TestEndpointAdaptor.ts +194 -150
  158. package/src/endpoints/__tests__/EndpointBuilder.audit.spec.ts +6 -3
  159. package/src/endpoints/__tests__/EndpointBuilder.responseType.spec.ts +4 -1
  160. package/src/endpoints/__tests__/EndpointBuilder.spec.ts +16 -8
  161. package/src/functions/AWSLambdaFunction.ts +28 -2
  162. package/src/functions/BaseFunctionBuilder.ts +33 -22
  163. package/src/functions/FunctionBuilder.ts +41 -51
  164. package/src/functions/TestFunctionAdaptor.ts +66 -31
  165. package/src/functions/__tests__/FunctionBuilder.state-isolation.spec.ts +55 -12
  166. package/src/index.ts +6 -0
  167. package/src/monitor.ts +145 -0
  168. package/src/monitors/heartbeat.ts +86 -0
  169. package/src/monitors/index.ts +6 -0
  170. package/src/queue/QueueBuilder.ts +22 -22
  171. package/src/subscribers/SubscriberBuilder.ts +19 -22
  172. package/src/topic/TopicBuilder.ts +9 -7
  173. package/tsdown.config.ts +1 -0
  174. package/dist/AWSScheduledFunction-CufMMhGB.mjs.map +0 -1
  175. package/dist/AWSScheduledFunction-DbkDr8CA.cjs.map +0 -1
  176. package/dist/Construct-DCMuYOox.d.mts.map +0 -1
  177. package/dist/Construct-_1wX7p_Y.d.cts.map +0 -1
  178. package/dist/Construct-r0U2zBwW.mjs.map +0 -1
  179. package/dist/Construct-vXMZJgRp.cjs.map +0 -1
  180. package/dist/Endpoint-DgfRslct.d.cts.map +0 -1
  181. package/dist/HonoEndpointAdaptor-CkJENANP.cjs.map +0 -1
  182. package/dist/HonoEndpointAdaptor-DJDT3PMx.mjs.map +0 -1
  183. package/dist/functions-B-qt91O7.mjs.map +0 -1
  184. package/dist/functions-C8i1kCMM.cjs.map +0 -1
  185. package/dist/index-BVs9DvIj.d.cts.map +0 -1
  186. package/dist/index-DP6fqv0I.d.mts.map +0 -1
@@ -8,6 +8,8 @@ import type { Logger } from '@geekmidas/logger';
8
8
  import { ConsoleLogger } from '@geekmidas/logger/console';
9
9
  import type { Service } from '@geekmidas/services';
10
10
  import uniqBy from 'lodash.uniqby';
11
+ import { cloneWith } from '../clone';
12
+ import { addMonitor, type MonitorService } from '../monitor';
11
13
  import type { HttpMethod } from '../types';
12
14
  import {
13
15
  type Authorizer,
@@ -79,6 +81,7 @@ export class EndpointFactory<
79
81
  private defaultActorExtractor?: ActorExtractor<TServices, TSession, TLogger>;
80
82
  private customSecuritySchemes: TSecuritySchemes = {} as TSecuritySchemes;
81
83
  private defaultRlsConfig?: TRlsConfig;
84
+ private defaultMonitors: MonitorService[] = [];
82
85
 
83
86
  constructor({
84
87
  basePath,
@@ -95,6 +98,7 @@ export class EndpointFactory<
95
98
  defaultActorExtractor,
96
99
  customSecuritySchemes = {} as TSecuritySchemes,
97
100
  defaultRlsConfig,
101
+ defaultMonitors = [],
98
102
  }: EndpointFactoryOptions<
99
103
  TServices,
100
104
  TBasePath,
@@ -128,6 +132,17 @@ export class EndpointFactory<
128
132
  this.defaultActorExtractor = defaultActorExtractor;
129
133
  this.customSecuritySchemes = customSecuritySchemes;
130
134
  this.defaultRlsConfig = defaultRlsConfig;
135
+ this.defaultMonitors = defaultMonitors;
136
+ }
137
+
138
+ /**
139
+ * Report every endpoint's requests to a monitor. Endpoints built from this
140
+ * factory inherit it, and can add their own with `.monitor()`.
141
+ */
142
+ monitor(monitor: MonitorService): this {
143
+ return cloneWith(this, {
144
+ defaultMonitors: addMonitor(this.defaultMonitors, monitor),
145
+ });
131
146
  }
132
147
 
133
148
  static joinPaths<TBasePath extends string, P extends string>(
@@ -212,6 +227,7 @@ export class EndpointFactory<
212
227
  defaultLogger: this.defaultLogger,
213
228
  defaultSessionExtractor: this.defaultSessionExtractor,
214
229
  defaultEventPublisher: this.defaultEventPublisher,
230
+ defaultMonitors: this.defaultMonitors,
215
231
  availableAuthorizers: authorizerConfigs,
216
232
  defaultAuthorizerName: this.defaultAuthorizerName,
217
233
  defaultAuditorStorage: this.defaultAuditorStorage,
@@ -278,6 +294,7 @@ export class EndpointFactory<
278
294
  defaultLogger: this.defaultLogger,
279
295
  defaultSessionExtractor: this.defaultSessionExtractor,
280
296
  defaultEventPublisher: this.defaultEventPublisher,
297
+ defaultMonitors: this.defaultMonitors,
281
298
  availableAuthorizers: this.availableAuthorizers,
282
299
  defaultAuthorizerName: this.defaultAuthorizerName,
283
300
  defaultAuditorStorage: this.defaultAuditorStorage,
@@ -360,6 +377,7 @@ export class EndpointFactory<
360
377
  defaultLogger: this.defaultLogger,
361
378
  defaultSessionExtractor: this.defaultSessionExtractor,
362
379
  defaultEventPublisher: this.defaultEventPublisher,
380
+ defaultMonitors: this.defaultMonitors,
363
381
  availableAuthorizers: this.availableAuthorizers,
364
382
  defaultAuthorizerName:
365
383
  name === 'none' ? undefined : (name as TAuthorizers[number]),
@@ -413,6 +431,7 @@ export class EndpointFactory<
413
431
  defaultLogger: this.defaultLogger,
414
432
  defaultSessionExtractor: this.defaultSessionExtractor,
415
433
  defaultEventPublisher: this.defaultEventPublisher,
434
+ defaultMonitors: this.defaultMonitors,
416
435
  availableAuthorizers: this.availableAuthorizers,
417
436
  defaultAuthorizerName: this.defaultAuthorizerName,
418
437
  defaultAuditorStorage: this.defaultAuditorStorage,
@@ -464,6 +483,7 @@ export class EndpointFactory<
464
483
  defaultLogger: this.defaultLogger,
465
484
  defaultSessionExtractor: this.defaultSessionExtractor,
466
485
  defaultEventPublisher: this.defaultEventPublisher,
486
+ defaultMonitors: this.defaultMonitors,
467
487
  availableAuthorizers: this.availableAuthorizers,
468
488
  defaultAuthorizerName: this.defaultAuthorizerName,
469
489
  defaultAuditorStorage: this.defaultAuditorStorage,
@@ -521,6 +541,7 @@ export class EndpointFactory<
521
541
  | SessionFn<[...S, ...TServices], TLogger, TSession, TDatabase>
522
542
  | undefined,
523
543
  defaultEventPublisher: this.defaultEventPublisher,
544
+ defaultMonitors: this.defaultMonitors,
524
545
  availableAuthorizers: this.availableAuthorizers,
525
546
  defaultAuthorizerName: this.defaultAuthorizerName,
526
547
  defaultAuditorStorage: this.defaultAuditorStorage,
@@ -584,6 +605,7 @@ export class EndpointFactory<
584
605
  TSession
585
606
  >,
586
607
  defaultEventPublisher: this.defaultEventPublisher,
608
+ defaultMonitors: this.defaultMonitors,
587
609
  availableAuthorizers: this.availableAuthorizers,
588
610
  defaultAuthorizerName: this.defaultAuthorizerName,
589
611
  defaultAuditorStorage: this.defaultAuditorStorage,
@@ -644,6 +666,7 @@ export class EndpointFactory<
644
666
  defaultLogger: this.defaultLogger,
645
667
  defaultSessionExtractor: this.defaultSessionExtractor,
646
668
  defaultEventPublisher: publisher,
669
+ defaultMonitors: this.defaultMonitors,
647
670
  availableAuthorizers: this.availableAuthorizers,
648
671
  defaultAuthorizerName: this.defaultAuthorizerName,
649
672
  defaultAuditorStorage: this.defaultAuditorStorage,
@@ -698,6 +721,7 @@ export class EndpointFactory<
698
721
  defaultLogger: this.defaultLogger,
699
722
  defaultSessionExtractor: session,
700
723
  defaultEventPublisher: this.defaultEventPublisher,
724
+ defaultMonitors: this.defaultMonitors,
701
725
  availableAuthorizers: this.availableAuthorizers,
702
726
  defaultAuthorizerName: this.defaultAuthorizerName,
703
727
  defaultAuditorStorage: this.defaultAuditorStorage,
@@ -765,6 +789,7 @@ export class EndpointFactory<
765
789
  | SessionFn<TServices, TLogger, TSession, T>
766
790
  | undefined,
767
791
  defaultEventPublisher: this.defaultEventPublisher,
792
+ defaultMonitors: this.defaultMonitors,
768
793
  availableAuthorizers: this.availableAuthorizers,
769
794
  defaultAuthorizerName: this.defaultAuthorizerName,
770
795
  defaultAuditorStorage: this.defaultAuditorStorage,
@@ -819,6 +844,7 @@ export class EndpointFactory<
819
844
  defaultLogger: this.defaultLogger,
820
845
  defaultSessionExtractor: this.defaultSessionExtractor,
821
846
  defaultEventPublisher: this.defaultEventPublisher,
847
+ defaultMonitors: this.defaultMonitors,
822
848
  availableAuthorizers: this.availableAuthorizers,
823
849
  defaultAuthorizerName: this.defaultAuthorizerName,
824
850
  defaultAuditorStorage: storage,
@@ -878,6 +904,7 @@ export class EndpointFactory<
878
904
  defaultLogger: this.defaultLogger,
879
905
  defaultSessionExtractor: this.defaultSessionExtractor,
880
906
  defaultEventPublisher: this.defaultEventPublisher,
907
+ defaultMonitors: this.defaultMonitors,
881
908
  availableAuthorizers: this.availableAuthorizers,
882
909
  defaultAuthorizerName: this.defaultAuthorizerName,
883
910
  defaultAuditorStorage: this.defaultAuditorStorage,
@@ -946,6 +973,7 @@ export class EndpointFactory<
946
973
  defaultLogger: this.defaultLogger,
947
974
  defaultSessionExtractor: this.defaultSessionExtractor,
948
975
  defaultEventPublisher: this.defaultEventPublisher,
976
+ defaultMonitors: this.defaultMonitors,
949
977
  availableAuthorizers: this.availableAuthorizers,
950
978
  defaultAuthorizerName: this.defaultAuthorizerName,
951
979
  defaultAuditorStorage: this.defaultAuditorStorage,
@@ -1019,6 +1047,8 @@ export class EndpointFactory<
1019
1047
  builder._setPublisher(this.defaultEventPublisher);
1020
1048
  }
1021
1049
 
1050
+ builder._monitors = [...this.defaultMonitors];
1051
+
1022
1052
  // Set available authorizers and default
1023
1053
  builder._availableAuthorizers = this.availableAuthorizers;
1024
1054
  if (this.defaultAuthorizerName) {
@@ -1143,6 +1173,7 @@ export interface EndpointFactoryOptions<
1143
1173
  defaultActorExtractor?: ActorExtractor<TServices, TSession, TLogger>;
1144
1174
  customSecuritySchemes?: TSecuritySchemes;
1145
1175
  defaultRlsConfig?: TRlsConfig;
1176
+ defaultMonitors?: MonitorService[];
1146
1177
  }
1147
1178
 
1148
1179
  export const e = new EndpointFactory();
@@ -18,6 +18,7 @@ import { logger as honoLogger } from 'hono/logger';
18
18
  import { timing } from 'hono/timing';
19
19
  import type { ContentfulStatusCode } from 'hono/utils/http-status';
20
20
  import { validator } from 'hono/validator';
21
+ import { startMonitors } from '../monitor';
21
22
  import { publishConstructEvents } from '../publisher';
22
23
  import type { HttpMethod, LowerHttpMethod } from '../types';
23
24
  import type { MappedAudit } from './audit';
@@ -66,6 +67,7 @@ interface EndpointFeatures {
66
67
  hasEvents: boolean;
67
68
  hasRateLimit: boolean;
68
69
  hasRls: boolean;
70
+ hasMonitors: boolean;
69
71
  }
70
72
 
71
73
  /**
@@ -111,6 +113,7 @@ function analyzeEndpointFeatures(
111
113
  hasEvents: (endpoint.events?.length ?? 0) > 0,
112
114
  hasRateLimit: !!endpoint.rateLimit,
113
115
  hasRls: !!endpoint.rlsConfig && !endpoint.rlsBypass,
116
+ hasMonitors: endpoint.monitors.length > 0,
114
117
  };
115
118
  }
116
119
 
@@ -361,8 +364,13 @@ export class HonoEndpoint<
361
364
  // Set response header
362
365
  c.header('X-Request-ID', requestId);
363
366
 
367
+ const monitor = features.hasMonitors
368
+ ? await startMonitors(endpoint, serviceDiscovery, logger)
369
+ : undefined;
370
+ let failure: unknown;
371
+
364
372
  // Wrap entire handler in request context for services to access
365
- return runWithRequestContext(
373
+ const response = await runWithRequestContext(
366
374
  { logger, requestId, startTime },
367
375
  async () => {
368
376
  try {
@@ -631,6 +639,7 @@ export class HonoEndpoint<
631
639
  return c.json(error, error.statusCode as ContentfulStatusCode);
632
640
  }
633
641
  } catch (e: any) {
642
+ failure = e;
634
643
  logger.error(e, 'Error processing endpoint request');
635
644
  const error = wrapError(e, 500, 'Internal Server Error');
636
645
  if (HonoEndpoint.isDev) {
@@ -643,6 +652,12 @@ export class HonoEndpoint<
643
652
  }
644
653
  },
645
654
  );
655
+
656
+ // Not awaited: a server stays up after responding, so the request
657
+ // does not wait on the monitoring service. `settle` never throws.
658
+ void monitor?.settle(response.status, failure);
659
+
660
+ return response;
646
661
  };
647
662
 
648
663
  // Register route with conditional validators
@@ -5,7 +5,7 @@ import type {
5
5
  } from '@geekmidas/audit';
6
6
  import { DefaultAuditor } from '@geekmidas/audit';
7
7
  import { EnvironmentParser } from '@geekmidas/envkit';
8
- import { ForbiddenError } from '@geekmidas/errors';
8
+ import { ForbiddenError, wrapError } from '@geekmidas/errors';
9
9
  import type { EventPublisher } from '@geekmidas/events';
10
10
  import type { Logger } from '@geekmidas/logger';
11
11
  import type {
@@ -19,6 +19,7 @@ import {
19
19
  type ServiceRecord,
20
20
  } from '@geekmidas/services';
21
21
  import type { StandardSchemaV1 } from '@standard-schema/spec';
22
+ import { startMonitors } from '../monitor';
22
23
  import { publishConstructEvents } from '../publisher';
23
24
  import type { HttpMethod } from '../types';
24
25
  import type { MappedAudit } from './audit';
@@ -199,170 +200,213 @@ export class TestEndpointAdaptor<
199
200
  // Get database from context for session extraction
200
201
  const rawDb = (ctx as any).database as TDatabase;
201
202
 
203
+ const monitor = await startMonitors(
204
+ this.endpoint,
205
+ this.serviceDiscovery,
206
+ logger,
207
+ );
208
+
202
209
  // Wrap handler execution in request context
203
- return runWithRequestContext({ logger, requestId, startTime }, async () => {
204
- const session = await this.endpoint.getSession({
205
- logger,
206
- services: ctx.services,
207
- header,
208
- cookie,
209
- ...(rawDb !== undefined && { db: rawDb }),
210
- } as any);
211
-
212
- // Check authorization
213
- const isAuthorized = await this.endpoint.authorize({
214
- header,
215
- cookie,
216
- services: ctx.services,
217
- logger,
218
- session,
219
- ...(rawDb !== undefined && { db: rawDb }),
220
- body,
221
- query,
222
- params,
223
- } as any);
224
-
225
- if (!isAuthorized) {
226
- logger.warn('Forbidden access attempt');
227
- throw new ForbiddenError(
228
- 'Forbidden access to the endpoint',
229
- 'You do not have permission to access this resource.',
230
- );
231
- }
210
+ try {
211
+ const response = await runWithRequestContext(
212
+ { logger, requestId, startTime },
213
+ async () =>
214
+ this.run(ctx, { body, query, params, header, cookie, logger, rawDb }),
215
+ );
216
+ await monitor.settle(response.status);
217
+ return response;
218
+ } catch (error) {
219
+ await monitor.settle(wrapError(error).statusCode, error);
220
+ throw error;
221
+ }
222
+ }
232
223
 
233
- // Create audit context if audit storage is provided
234
- // The auditorStorage instance is required when endpoint uses .auditor()
235
- const auditorStorage = (ctx as any).auditorStorage as TAuditStorage;
236
- let auditContext: AuditExecutionContext<TAuditAction> | undefined;
237
-
238
- if (auditorStorage) {
239
- // Extract actor if configured
240
- let actor: AuditActor = { id: 'system', type: 'system' };
241
- if (this.endpoint.actorExtractor) {
242
- try {
243
- actor = await this.endpoint.actorExtractor({
244
- services: ctx.services as any,
245
- session,
246
- header,
247
- cookie,
248
- logger,
249
- });
250
- } catch (error) {
251
- logger.error(error as Error, 'Failed to extract actor for audits');
252
- }
224
+ private async run(
225
+ ctx: TestRequestAdaptor<
226
+ TInput,
227
+ TServices,
228
+ TEventPublisher,
229
+ TEventPublisherServiceName,
230
+ TAuditStorage,
231
+ TAuditStorageServiceName,
232
+ TDatabase,
233
+ TDatabaseServiceName
234
+ >,
235
+ {
236
+ body,
237
+ query,
238
+ params,
239
+ header,
240
+ cookie,
241
+ logger,
242
+ rawDb,
243
+ }: {
244
+ body: unknown;
245
+ query: unknown;
246
+ params: unknown;
247
+ header: ReturnType<typeof createObjectHeaders>;
248
+ cookie: ReturnType<typeof createCookieHeaderAccessor>;
249
+ logger: TLogger;
250
+ rawDb: TDatabase;
251
+ },
252
+ ): Promise<TestHttpResponse<InferStandardSchema<TOutSchema>>> {
253
+ const session = await this.endpoint.getSession({
254
+ logger,
255
+ services: ctx.services,
256
+ header,
257
+ cookie,
258
+ ...(rawDb !== undefined && { db: rawDb }),
259
+ } as any);
260
+
261
+ // Check authorization
262
+ const isAuthorized = await this.endpoint.authorize({
263
+ header,
264
+ cookie,
265
+ services: ctx.services,
266
+ logger,
267
+ session,
268
+ ...(rawDb !== undefined && { db: rawDb }),
269
+ body,
270
+ query,
271
+ params,
272
+ } as any);
273
+
274
+ if (!isAuthorized) {
275
+ logger.warn('Forbidden access attempt');
276
+ throw new ForbiddenError(
277
+ 'Forbidden access to the endpoint',
278
+ 'You do not have permission to access this resource.',
279
+ );
280
+ }
281
+
282
+ // Create audit context if audit storage is provided
283
+ // The auditorStorage instance is required when endpoint uses .auditor()
284
+ const auditorStorage = (ctx as any).auditorStorage as TAuditStorage;
285
+ let auditContext: AuditExecutionContext<TAuditAction> | undefined;
286
+
287
+ if (auditorStorage) {
288
+ // Extract actor if configured
289
+ let actor: AuditActor = { id: 'system', type: 'system' };
290
+ if (this.endpoint.actorExtractor) {
291
+ try {
292
+ actor = await this.endpoint.actorExtractor({
293
+ services: ctx.services as any,
294
+ session,
295
+ header,
296
+ cookie,
297
+ logger,
298
+ });
299
+ } catch (error) {
300
+ logger.error(error as Error, 'Failed to extract actor for audits');
253
301
  }
302
+ }
254
303
 
255
- const auditor = new DefaultAuditor<TAuditAction>({
256
- actor,
257
- storage: auditorStorage as AuditStorage,
258
- metadata: {
259
- endpoint: this.endpoint.route,
260
- method: this.endpoint.method,
261
- },
262
- });
304
+ const auditor = new DefaultAuditor<TAuditAction>({
305
+ actor,
306
+ storage: auditorStorage as AuditStorage,
307
+ metadata: {
308
+ endpoint: this.endpoint.route,
309
+ method: this.endpoint.method,
310
+ },
311
+ });
263
312
 
264
- auditContext = { auditor, storage: auditorStorage as AuditStorage };
265
- }
313
+ auditContext = { auditor, storage: auditorStorage as AuditStorage };
314
+ }
266
315
 
267
- // Warn if declarative audits are configured but no audit storage
268
- const audits = this.endpoint.audits as MappedAudit<
269
- TAuditAction,
270
- TOutSchema
271
- >[];
272
- if (!auditContext && audits?.length) {
273
- logger.warn('No auditor storage service available');
274
- }
316
+ // Warn if declarative audits are configured but no audit storage
317
+ const audits = this.endpoint.audits as MappedAudit<
318
+ TAuditAction,
319
+ TOutSchema
320
+ >[];
321
+ if (!auditContext && audits?.length) {
322
+ logger.warn('No auditor storage service available');
323
+ }
275
324
 
276
- // Execute handler with automatic audit transaction support
277
- const result = await executeWithAuditTransaction(
278
- auditContext,
279
- async (auditor) => {
280
- // Use audit transaction as db if available (when storage has same database)
281
- // For testing, the tester controls whether to use transactional auditing
282
- const trx = auditor?.getTransaction?.();
283
- const db = trx ?? rawDb;
284
-
285
- const responseBuilder = new ResponseBuilder();
286
- const response = await this.endpoint.handler(
287
- {
288
- body,
289
- query,
290
- params,
291
- session,
292
- services: ctx.services,
293
- logger,
294
- header,
295
- cookie,
296
- auditor,
297
- db,
298
- } as any,
299
- responseBuilder,
300
- );
301
-
302
- // Check if response has metadata
303
- let data = response;
304
- let metadata = responseBuilder.getMetadata();
305
-
306
- if (Endpoint.hasMetadata(response)) {
307
- data = response.data;
308
- metadata = response.metadata;
309
- }
325
+ // Execute handler with automatic audit transaction support
326
+ const result = await executeWithAuditTransaction(
327
+ auditContext,
328
+ async (auditor) => {
329
+ // Use audit transaction as db if available (when storage has same database)
330
+ // For testing, the tester controls whether to use transactional auditing
331
+ const trx = auditor?.getTransaction?.();
332
+ const db = trx ?? rawDb;
333
+
334
+ const responseBuilder = new ResponseBuilder();
335
+ const response = await this.endpoint.handler(
336
+ {
337
+ body,
338
+ query,
339
+ params,
340
+ session,
341
+ services: ctx.services,
342
+ logger,
343
+ header,
344
+ cookie,
345
+ auditor,
346
+ db,
347
+ } as any,
348
+ responseBuilder,
349
+ );
310
350
 
311
- const output = await this.endpoint.parseOutput(data);
351
+ // Check if response has metadata
352
+ let data = response;
353
+ let metadata = responseBuilder.getMetadata();
312
354
 
313
- return { output, metadata, responseBuilder };
314
- },
315
- // Process declarative audits after handler (inside transaction)
316
- async (result, auditor) => {
317
- if (!audits?.length) return;
318
-
319
- for (const audit of audits) {
320
- if (audit.when && !audit.when(result.output as any)) {
321
- continue;
322
- }
323
- const payload = audit.payload(result.output as any);
324
- const entityId = audit.entityId?.(result.output as any);
325
- auditor.audit(audit.type as any, payload as any, {
326
- table: audit.table,
327
- entityId,
328
- });
329
- }
330
- },
331
- // Pass rawDb so storage can reuse existing transactions
332
- { db: rawDb },
333
- );
355
+ if (Endpoint.hasMetadata(response)) {
356
+ data = response.data;
357
+ metadata = response.metadata;
358
+ }
334
359
 
335
- const { output, metadata } = result;
360
+ const output = await this.endpoint.parseOutput(data);
336
361
 
337
- ctx.publisher && (await this.serviceDiscovery.register([ctx.publisher]));
338
- await publishConstructEvents(
339
- this.endpoint,
340
- output,
341
- this.serviceDiscovery,
342
- );
362
+ return { output, metadata, responseBuilder };
363
+ },
364
+ // Process declarative audits after handler (inside transaction)
365
+ async (result, auditor) => {
366
+ if (!audits?.length) return;
343
367
 
344
- // Convert cookies to Set-Cookie headers
345
- const headers: Record<string, string | string[]> = {
346
- ...(metadata.headers || {}),
347
- };
348
-
349
- if (metadata.cookies && metadata.cookies.size > 0) {
350
- const setCookieValues: string[] = [];
351
- for (const [name, cookie] of metadata.cookies.entries()) {
352
- setCookieValues.push(
353
- serializeCookie(name, cookie.value, cookie.options),
354
- );
368
+ for (const audit of audits) {
369
+ if (audit.when && !audit.when(result.output as any)) {
370
+ continue;
371
+ }
372
+ const payload = audit.payload(result.output as any);
373
+ const entityId = audit.entityId?.(result.output as any);
374
+ auditor.audit(audit.type as any, payload as any, {
375
+ table: audit.table,
376
+ entityId,
377
+ });
355
378
  }
356
- headers['set-cookie'] = setCookieValues;
379
+ },
380
+ // Pass rawDb so storage can reuse existing transactions
381
+ { db: rawDb },
382
+ );
383
+
384
+ const { output, metadata } = result;
385
+
386
+ ctx.publisher && (await this.serviceDiscovery.register([ctx.publisher]));
387
+ await publishConstructEvents(this.endpoint, output, this.serviceDiscovery);
388
+
389
+ // Convert cookies to Set-Cookie headers
390
+ const headers: Record<string, string | string[]> = {
391
+ ...(metadata.headers || {}),
392
+ };
393
+
394
+ if (metadata.cookies && metadata.cookies.size > 0) {
395
+ const setCookieValues: string[] = [];
396
+ for (const [name, cookie] of metadata.cookies.entries()) {
397
+ setCookieValues.push(
398
+ serializeCookie(name, cookie.value, cookie.options),
399
+ );
357
400
  }
401
+ headers['set-cookie'] = setCookieValues;
402
+ }
358
403
 
359
- // Return HTTP response format
360
- return {
361
- body: output,
362
- status: metadata.status || 200,
363
- headers,
364
- };
365
- });
404
+ // Return HTTP response format
405
+ return {
406
+ body: output,
407
+ status: metadata.status || 200,
408
+ headers,
409
+ };
366
410
  }
367
411
 
368
412
  async request(
@@ -49,7 +49,8 @@ describe('EndpointBuilder audit methods', () => {
49
49
  const builder = new EndpointBuilder('/users', 'POST');
50
50
  const result = builder.auditor(auditStorageService);
51
51
 
52
- expect(result).toBe(builder);
52
+ expect(result).toBeInstanceOf(EndpointBuilder);
53
+ expect(result).not.toBe(builder);
53
54
  });
54
55
 
55
56
  it('should pass auditor storage service to endpoint', () => {
@@ -96,7 +97,8 @@ describe('EndpointBuilder audit methods', () => {
96
97
  const builder = new EndpointBuilder('/users', 'POST');
97
98
  const result = builder.actor(() => ({ id: '123', type: 'user' }));
98
99
 
99
- expect(result).toBe(builder);
100
+ expect(result).toBeInstanceOf(EndpointBuilder);
101
+ expect(result).not.toBe(builder);
100
102
  });
101
103
 
102
104
  it('should pass actor extractor to endpoint', () => {
@@ -183,7 +185,8 @@ describe('EndpointBuilder audit methods', () => {
183
185
  const builder = new EndpointBuilder('/users', 'POST');
184
186
  const result = builder.audit([]);
185
187
 
186
- expect(result).toBe(builder);
188
+ expect(result).toBeInstanceOf(EndpointBuilder);
189
+ expect(result).not.toBe(builder);
187
190
  });
188
191
 
189
192
  it('should pass audit definitions to endpoint', () => {
@@ -24,7 +24,10 @@ describe('EndpointBuilder.responseType', () => {
24
24
  const builder = new EndpointBuilder('/users', 'GET');
25
25
  const result = builder.responseType('text/plain');
26
26
 
27
- expect(result).toBe(builder);
27
+ // A new builder rather than the same one: a configured base has to stay
28
+ // reusable, so a chain step cannot write back into what it came from.
29
+ expect(result).toBeInstanceOf(EndpointBuilder);
30
+ expect(result).not.toBe(builder);
28
31
  });
29
32
 
30
33
  it('supports any arbitrary MIME type', () => {