@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.
- package/CHANGELOG.md +41 -0
- package/dist/{AWSScheduledFunction-Bn99MceY.d.cts → AWSScheduledFunction-BHHlWlfV.d.cts} +2 -2
- package/dist/{AWSScheduledFunction-ysaCsdWx.d.mts.map → AWSScheduledFunction-BHHlWlfV.d.cts.map} +1 -1
- package/dist/{AWSScheduledFunction-ysaCsdWx.d.mts → AWSScheduledFunction-BzGCUbkk.d.mts} +2 -2
- package/dist/{AWSScheduledFunction-Bn99MceY.d.cts.map → AWSScheduledFunction-BzGCUbkk.d.mts.map} +1 -1
- package/dist/{AWSScheduledFunction-DbkDr8CA.cjs → AWSScheduledFunction-C42adp8A.cjs} +16 -5
- package/dist/AWSScheduledFunction-C42adp8A.cjs.map +1 -0
- package/dist/{AWSScheduledFunction-CufMMhGB.mjs → AWSScheduledFunction-KP6lqbsW.mjs} +15 -4
- package/dist/AWSScheduledFunction-KP6lqbsW.mjs.map +1 -0
- package/dist/{Construct-r0U2zBwW.mjs → Construct-BWF5HxaM.mjs} +5 -2
- package/dist/Construct-BWF5HxaM.mjs.map +1 -0
- package/dist/{Construct-_1wX7p_Y.d.cts → Construct-JlyQkBkv.d.cts} +39 -4
- package/dist/Construct-JlyQkBkv.d.cts.map +1 -0
- package/dist/{Construct-vXMZJgRp.cjs → Construct-TGIM_E4o.cjs} +5 -2
- package/dist/Construct-TGIM_E4o.cjs.map +1 -0
- package/dist/{Construct-DCMuYOox.d.mts → Construct-pxwC6fDD.d.mts} +39 -4
- package/dist/Construct-pxwC6fDD.d.mts.map +1 -0
- package/dist/{Endpoint-CLPSIl6S.d.mts → Endpoint-B0P_pEMr.d.mts} +3 -3
- package/dist/{Endpoint-CLPSIl6S.d.mts.map → Endpoint-B0P_pEMr.d.mts.map} +1 -1
- package/dist/{Endpoint-DgfRslct.d.cts → Endpoint-Cwjhc1Hl.d.cts} +2 -2
- package/dist/Endpoint-Cwjhc1Hl.d.cts.map +1 -0
- package/dist/{HonoEndpointAdaptor-DJDT3PMx.mjs → HonoEndpointAdaptor-BMVwx2cz.mjs} +18 -11
- package/dist/HonoEndpointAdaptor-BMVwx2cz.mjs.map +1 -0
- package/dist/{HonoEndpointAdaptor-CkJENANP.cjs → HonoEndpointAdaptor-DPDsDi-D.cjs} +19 -12
- package/dist/HonoEndpointAdaptor-DPDsDi-D.cjs.map +1 -0
- package/dist/{Queue-HBDf0fqz.d.mts → Queue-BL68oDJ-.d.mts} +2 -2
- package/dist/{Queue-DaxjwmOQ.d.cts.map → Queue-BL68oDJ-.d.mts.map} +1 -1
- package/dist/{Queue-DaxjwmOQ.d.cts → Queue-DxJ6btRE.d.cts} +2 -2
- package/dist/{Queue-HBDf0fqz.d.mts.map → Queue-DxJ6btRE.d.cts.map} +1 -1
- package/dist/{Subscriber-B69ngPwl.d.cts → Subscriber-BpVhcGhX.d.cts} +2 -2
- package/dist/{Subscriber-B69ngPwl.d.cts.map → Subscriber-BpVhcGhX.d.cts.map} +1 -1
- package/dist/{Subscriber-D8Xm-86W.d.mts → Subscriber-DF6xsBAJ.d.mts} +2 -2
- package/dist/{Subscriber-D8Xm-86W.d.mts.map → Subscriber-DF6xsBAJ.d.mts.map} +1 -1
- package/dist/{Topic-DT10AqHk.d.mts → Topic-DJXoDeMP.d.cts} +3 -3
- package/dist/{Topic-C5ZMfveW.d.cts.map → Topic-DJXoDeMP.d.cts.map} +1 -1
- package/dist/{Topic-C5ZMfveW.d.cts → Topic-DxWQBgMN.d.mts} +3 -3
- package/dist/{Topic-DT10AqHk.d.mts.map → Topic-DxWQBgMN.d.mts.map} +1 -1
- package/dist/adaptors/aws.cjs +14 -7
- package/dist/adaptors/aws.cjs.map +1 -1
- package/dist/adaptors/aws.d.cts +6 -6
- package/dist/adaptors/aws.d.cts.map +1 -1
- package/dist/adaptors/aws.d.mts +8 -8
- package/dist/adaptors/aws.d.mts.map +1 -1
- package/dist/adaptors/aws.mjs +14 -7
- package/dist/adaptors/aws.mjs.map +1 -1
- package/dist/adaptors/hono.cjs +7 -6
- package/dist/adaptors/hono.d.cts +3 -3
- package/dist/adaptors/hono.d.cts.map +1 -1
- package/dist/adaptors/hono.d.mts +4 -4
- package/dist/adaptors/hono.d.mts.map +1 -1
- package/dist/adaptors/hono.mjs +7 -6
- package/dist/adaptors/testing.cjs +161 -126
- package/dist/adaptors/testing.cjs.map +1 -1
- package/dist/adaptors/testing.d.cts +7 -5
- package/dist/adaptors/testing.d.cts.map +1 -1
- package/dist/adaptors/testing.d.mts +8 -6
- package/dist/adaptors/testing.d.mts.map +1 -1
- package/dist/adaptors/testing.mjs +162 -127
- package/dist/adaptors/testing.mjs.map +1 -1
- package/dist/clone-I_WIpiFf.cjs +45 -0
- package/dist/clone-I_WIpiFf.cjs.map +1 -0
- package/dist/clone-pdXxc0t3.mjs +39 -0
- package/dist/clone-pdXxc0t3.mjs.map +1 -0
- package/dist/crons/index.cjs +14 -32
- package/dist/crons/index.cjs.map +1 -1
- package/dist/crons/index.d.cts +7 -7
- package/dist/crons/index.d.cts.map +1 -1
- package/dist/crons/index.d.mts +3 -3
- package/dist/crons/index.d.mts.map +1 -1
- package/dist/crons/index.mjs +14 -32
- package/dist/crons/index.mjs.map +1 -1
- package/dist/endpoints/index.cjs +74 -61
- package/dist/endpoints/index.cjs.map +1 -1
- package/dist/endpoints/index.d.cts +14 -6
- package/dist/endpoints/index.d.cts.map +1 -1
- package/dist/endpoints/index.d.mts +13 -5
- package/dist/endpoints/index.d.mts.map +1 -1
- package/dist/endpoints/index.mjs +74 -61
- package/dist/endpoints/index.mjs.map +1 -1
- package/dist/functions/index.cjs +3 -2
- package/dist/functions/index.d.cts +2 -2
- package/dist/functions/index.d.mts +2 -2
- package/dist/functions/index.mjs +3 -2
- package/dist/{functions-B-qt91O7.mjs → functions-EqTloHjo.mjs} +107 -46
- package/dist/functions-EqTloHjo.mjs.map +1 -0
- package/dist/{functions-C8i1kCMM.cjs → functions-SqtCDc9c.cjs} +118 -45
- package/dist/functions-SqtCDc9c.cjs.map +1 -0
- package/dist/{index-DP6fqv0I.d.mts → index-C7iynzLI.d.mts} +8 -2
- package/dist/index-C7iynzLI.d.mts.map +1 -0
- package/dist/{index-BVs9DvIj.d.cts → index-DdgJRm4q.d.cts} +12 -6
- package/dist/index-DdgJRm4q.d.cts.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +1 -1
- package/dist/{lazyAccessors-DfqZR6C4.mjs → lazyAccessors-CCTK19xj.mjs} +3 -3
- package/dist/{lazyAccessors-DfqZR6C4.mjs.map → lazyAccessors-CCTK19xj.mjs.map} +1 -1
- package/dist/{lazyAccessors-DmTG8sQH.cjs → lazyAccessors-D04oftGw.cjs} +3 -3
- package/dist/{lazyAccessors-DmTG8sQH.cjs.map → lazyAccessors-D04oftGw.cjs.map} +1 -1
- package/dist/monitors/index.cjs +49 -0
- package/dist/monitors/index.cjs.map +1 -0
- package/dist/monitors/index.d.cts +45 -0
- package/dist/monitors/index.d.cts.map +1 -0
- package/dist/monitors/index.d.mts +45 -0
- package/dist/monitors/index.d.mts.map +1 -0
- package/dist/monitors/index.mjs +48 -0
- package/dist/monitors/index.mjs.map +1 -0
- package/dist/{processAudits-CKjjivyz.cjs → processAudits-BJZT5gmy.cjs} +2 -2
- package/dist/{processAudits-CKjjivyz.cjs.map → processAudits-BJZT5gmy.cjs.map} +1 -1
- package/dist/{processAudits-ySnizdMG.mjs → processAudits-CwyaaSQc.mjs} +1 -1
- package/dist/{processAudits-ySnizdMG.mjs.map → processAudits-CwyaaSQc.mjs.map} +1 -1
- package/dist/{publisher-lFQleddL.cjs → publisher-BZgcrVPy.cjs} +1 -1
- package/dist/{publisher-lFQleddL.cjs.map → publisher-BZgcrVPy.cjs.map} +1 -1
- package/dist/{publisher-BrFOF847.mjs → publisher-CnpeMv-r.mjs} +1 -1
- package/dist/{publisher-BrFOF847.mjs.map → publisher-CnpeMv-r.mjs.map} +1 -1
- package/dist/queue/index.cjs +9 -22
- package/dist/queue/index.cjs.map +1 -1
- package/dist/queue/index.d.cts +4 -4
- package/dist/queue/index.d.cts.map +1 -1
- package/dist/queue/index.d.mts +2 -2
- package/dist/queue/index.d.mts.map +1 -1
- package/dist/queue/index.mjs +9 -22
- package/dist/queue/index.mjs.map +1 -1
- package/dist/subscribers/index.cjs +9 -22
- package/dist/subscribers/index.cjs.map +1 -1
- package/dist/subscribers/index.d.cts +5 -5
- package/dist/subscribers/index.d.cts.map +1 -1
- package/dist/subscribers/index.d.mts +3 -3
- package/dist/subscribers/index.d.mts.map +1 -1
- package/dist/subscribers/index.mjs +9 -22
- package/dist/subscribers/index.mjs.map +1 -1
- package/dist/{telemetry-3vdxGn4q.d.mts → telemetry-C--EgYVr.d.mts} +1 -1
- package/dist/{telemetry-3vdxGn4q.d.mts.map → telemetry-C--EgYVr.d.mts.map} +1 -1
- package/dist/topic/index.cjs +4 -7
- package/dist/topic/index.cjs.map +1 -1
- package/dist/topic/index.d.cts +2 -2
- package/dist/topic/index.d.cts.map +1 -1
- package/dist/topic/index.d.mts +2 -2
- package/dist/topic/index.d.mts.map +1 -1
- package/dist/topic/index.mjs +4 -7
- package/dist/topic/index.mjs.map +1 -1
- package/dist/{types-BDNGr7pv.d.mts → types-DNngH0fX.d.mts} +1 -1
- package/dist/{types-BDNGr7pv.d.mts.map → types-DNngH0fX.d.mts.map} +1 -1
- package/dist/types.d.mts +1 -1
- package/package.json +32 -22
- package/src/Construct.ts +5 -0
- package/src/__tests__/builder-reuse.spec.ts +587 -0
- package/src/__tests__/monitor.spec.ts +636 -0
- package/src/clone.ts +38 -0
- package/src/crons/CronBuilder.ts +35 -45
- package/src/crons/__tests__/Cron.spec.ts +10 -2
- package/src/crons/__tests__/CronBuilder.state-isolation.spec.ts +64 -18
- package/src/endpoints/AmazonApiGatewayEndpointAdaptor.ts +23 -2
- package/src/endpoints/EndpointBuilder.ts +61 -70
- package/src/endpoints/EndpointFactory.ts +31 -0
- package/src/endpoints/HonoEndpointAdaptor.ts +16 -1
- package/src/endpoints/TestEndpointAdaptor.ts +194 -150
- package/src/endpoints/__tests__/EndpointBuilder.audit.spec.ts +6 -3
- package/src/endpoints/__tests__/EndpointBuilder.responseType.spec.ts +4 -1
- package/src/endpoints/__tests__/EndpointBuilder.spec.ts +16 -8
- package/src/functions/AWSLambdaFunction.ts +28 -2
- package/src/functions/BaseFunctionBuilder.ts +33 -22
- package/src/functions/FunctionBuilder.ts +41 -51
- package/src/functions/TestFunctionAdaptor.ts +66 -31
- package/src/functions/__tests__/FunctionBuilder.state-isolation.spec.ts +55 -12
- package/src/index.ts +6 -0
- package/src/monitor.ts +145 -0
- package/src/monitors/heartbeat.ts +86 -0
- package/src/monitors/index.ts +6 -0
- package/src/queue/QueueBuilder.ts +22 -22
- package/src/subscribers/SubscriberBuilder.ts +19 -22
- package/src/topic/TopicBuilder.ts +9 -7
- package/tsdown.config.ts +1 -0
- package/dist/AWSScheduledFunction-CufMMhGB.mjs.map +0 -1
- package/dist/AWSScheduledFunction-DbkDr8CA.cjs.map +0 -1
- package/dist/Construct-DCMuYOox.d.mts.map +0 -1
- package/dist/Construct-_1wX7p_Y.d.cts.map +0 -1
- package/dist/Construct-r0U2zBwW.mjs.map +0 -1
- package/dist/Construct-vXMZJgRp.cjs.map +0 -1
- package/dist/Endpoint-DgfRslct.d.cts.map +0 -1
- package/dist/HonoEndpointAdaptor-CkJENANP.cjs.map +0 -1
- package/dist/HonoEndpointAdaptor-DJDT3PMx.mjs.map +0 -1
- package/dist/functions-B-qt91O7.mjs.map +0 -1
- package/dist/functions-C8i1kCMM.cjs.map +0 -1
- package/dist/index-BVs9DvIj.d.cts.map +0 -1
- 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
|
-
|
|
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
|
-
|
|
204
|
-
const
|
|
205
|
-
logger,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
265
|
-
|
|
313
|
+
auditContext = { auditor, storage: auditorStorage as AuditStorage };
|
|
314
|
+
}
|
|
266
315
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
351
|
+
// Check if response has metadata
|
|
352
|
+
let data = response;
|
|
353
|
+
let metadata = responseBuilder.getMetadata();
|
|
312
354
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
360
|
+
const output = await this.endpoint.parseOutput(data);
|
|
336
361
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
|
|
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
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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).
|
|
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).
|
|
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).
|
|
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
|
-
|
|
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', () => {
|