@asenajs/asena 0.7.1 → 0.8.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/README.md +2 -2
- package/dist/lib/adapter/AsenaAdapter.d.ts +2 -2
- package/dist/lib/adapter/AsenaAdapter.js.map +1 -1
- package/dist/lib/adapter/types/ServeOptions.d.ts +16 -0
- package/dist/lib/ioc/Container.d.ts +25 -0
- package/dist/lib/ioc/Container.js +47 -0
- package/dist/lib/ioc/Container.js.map +1 -1
- package/dist/lib/ioc/CoreContainer.d.ts +5 -3
- package/dist/lib/ioc/CoreContainer.js +9 -3
- package/dist/lib/ioc/CoreContainer.js.map +1 -1
- package/dist/lib/ioc/IocEngine.js +3 -1
- package/dist/lib/ioc/IocEngine.js.map +1 -1
- package/dist/lib/ioc/component/decorators/Inject.js +16 -0
- package/dist/lib/ioc/component/decorators/Inject.js.map +1 -1
- package/dist/lib/ioc/constants/ComponentConstants.d.ts +4 -0
- package/dist/lib/ioc/constants/ComponentConstants.js +5 -0
- package/dist/lib/ioc/constants/ComponentConstants.js.map +1 -1
- package/dist/lib/ioc/types/ICoreServiceNamesType.d.ts +1 -0
- package/dist/lib/ioc/types/ICoreServiceNamesType.js +1 -0
- package/dist/lib/ioc/types/ICoreServiceNamesType.js.map +1 -1
- package/dist/lib/ioc/types/InjectableComponent.d.ts +10 -0
- package/dist/lib/ioc/types/decorators/ComponentType.d.ts +2 -1
- package/dist/lib/ioc/types/decorators/ComponentType.js +1 -0
- package/dist/lib/ioc/types/decorators/ComponentType.js.map +1 -1
- package/dist/lib/server/AsenaServer.d.ts +46 -3
- package/dist/lib/server/AsenaServer.js +158 -28
- package/dist/lib/server/AsenaServer.js.map +1 -1
- package/dist/lib/server/AsenaServerFactory.d.ts +38 -3
- package/dist/lib/server/AsenaServerFactory.js +21 -2
- package/dist/lib/server/AsenaServerFactory.js.map +1 -1
- package/dist/lib/server/config/AsenaConfig.d.ts +82 -4
- package/dist/lib/server/config/AsenaConfig.js +39 -1
- package/dist/lib/server/config/AsenaConfig.js.map +1 -1
- package/dist/lib/server/decorators/components/MessageController.d.ts +49 -0
- package/dist/lib/server/decorators/components/MessageController.js +62 -0
- package/dist/lib/server/decorators/components/MessageController.js.map +1 -0
- package/dist/lib/server/decorators/components/index.d.ts +1 -0
- package/dist/lib/server/decorators/components/index.js +1 -0
- package/dist/lib/server/decorators/components/index.js.map +1 -1
- package/dist/lib/server/event/EventDispatchService.d.ts +3 -7
- package/dist/lib/server/event/EventDispatchService.js +9 -40
- package/dist/lib/server/event/EventDispatchService.js.map +1 -1
- package/dist/lib/server/event/PatternHandlerIndex.d.ts +53 -0
- package/dist/lib/server/event/PatternHandlerIndex.js +100 -0
- package/dist/lib/server/event/PatternHandlerIndex.js.map +1 -0
- package/dist/lib/server/event/decorators/On.d.ts +4 -0
- package/dist/lib/server/event/decorators/On.js +6 -0
- package/dist/lib/server/event/decorators/On.js.map +1 -1
- package/dist/lib/server/event/index.d.ts +1 -0
- package/dist/lib/server/event/index.js +2 -0
- package/dist/lib/server/event/index.js.map +1 -1
- package/dist/lib/server/event/types.d.ts +12 -0
- package/dist/lib/server/health/HealthServer.d.ts +26 -0
- package/dist/lib/server/health/HealthServer.js +63 -0
- package/dist/lib/server/health/HealthServer.js.map +1 -0
- package/dist/lib/server/health/index.d.ts +1 -0
- package/dist/lib/server/health/index.js +2 -0
- package/dist/lib/server/health/index.js.map +1 -0
- package/dist/lib/server/messaging/Ulak.d.ts +156 -0
- package/dist/lib/server/messaging/Ulak.js +174 -0
- package/dist/lib/server/messaging/Ulak.js.map +1 -1
- package/dist/lib/server/messaging/types.d.ts +5 -1
- package/dist/lib/server/messaging/types.js +5 -0
- package/dist/lib/server/messaging/types.js.map +1 -1
- package/dist/lib/server/microservice/InMemoryTransport.d.ts +45 -0
- package/dist/lib/server/microservice/InMemoryTransport.js +124 -0
- package/dist/lib/server/microservice/InMemoryTransport.js.map +1 -0
- package/dist/lib/server/microservice/MicroserviceTransport.d.ts +132 -0
- package/dist/lib/server/microservice/MicroserviceTransport.js +2 -0
- package/dist/lib/server/microservice/MicroserviceTransport.js.map +1 -0
- package/dist/lib/server/microservice/decorators/EventPattern.d.ts +38 -0
- package/dist/lib/server/microservice/decorators/EventPattern.js +59 -0
- package/dist/lib/server/microservice/decorators/EventPattern.js.map +1 -0
- package/dist/lib/server/microservice/decorators/MessagePattern.d.ts +31 -0
- package/dist/lib/server/microservice/decorators/MessagePattern.js +55 -0
- package/dist/lib/server/microservice/decorators/MessagePattern.js.map +1 -0
- package/dist/lib/server/microservice/decorators/index.d.ts +2 -0
- package/dist/lib/server/microservice/decorators/index.js +3 -0
- package/dist/lib/server/microservice/decorators/index.js.map +1 -0
- package/dist/lib/server/microservice/index.d.ts +5 -0
- package/dist/lib/server/microservice/index.js +11 -0
- package/dist/lib/server/microservice/index.js.map +1 -0
- package/dist/lib/server/microservice/interceptorChain.d.ts +20 -0
- package/dist/lib/server/microservice/interceptorChain.js +39 -0
- package/dist/lib/server/microservice/interceptorChain.js.map +1 -0
- package/dist/lib/server/microservice/types.d.ts +142 -0
- package/dist/lib/server/microservice/types.js +5 -0
- package/dist/lib/server/microservice/types.js.map +1 -0
- package/dist/lib/server/src/services/PrepareEventService.d.ts +4 -1
- package/dist/lib/server/src/services/PrepareEventService.js +10 -3
- package/dist/lib/server/src/services/PrepareEventService.js.map +1 -1
- package/dist/lib/server/src/services/PrepareMicroserviceService.d.ts +66 -0
- package/dist/lib/server/src/services/PrepareMicroserviceService.js +193 -0
- package/dist/lib/server/src/services/PrepareMicroserviceService.js.map +1 -0
- package/dist/lib/test/factory/deepMock.d.ts +25 -0
- package/dist/lib/test/factory/deepMock.js +105 -0
- package/dist/lib/test/factory/deepMock.js.map +1 -0
- package/dist/lib/test/factory/ulakStub.d.ts +44 -0
- package/dist/lib/test/factory/ulakStub.js +40 -0
- package/dist/lib/test/factory/ulakStub.js.map +1 -0
- package/dist/lib/test/harness/createTestApp.d.ts +33 -0
- package/dist/lib/test/harness/createTestApp.js +105 -0
- package/dist/lib/test/harness/createTestApp.js.map +1 -0
- package/dist/lib/test/harness/createWebTest.d.ts +28 -0
- package/dist/lib/test/harness/createWebTest.js +127 -0
- package/dist/lib/test/harness/createWebTest.js.map +1 -0
- package/dist/lib/test/harness/silentLogger.d.ts +27 -0
- package/dist/lib/test/harness/silentLogger.js +54 -0
- package/dist/lib/test/harness/silentLogger.js.map +1 -0
- package/dist/lib/test/harness/types.d.ts +147 -0
- package/dist/lib/test/harness/types.js +2 -0
- package/dist/lib/test/harness/types.js.map +1 -0
- package/dist/lib/test/http/TestHttpCall.d.ts +78 -0
- package/dist/lib/test/http/TestHttpCall.js +149 -0
- package/dist/lib/test/http/TestHttpCall.js.map +1 -0
- package/dist/lib/test/http/TestHttpResponse.d.ts +46 -0
- package/dist/lib/test/http/TestHttpResponse.js +70 -0
- package/dist/lib/test/http/TestHttpResponse.js.map +1 -0
- package/dist/lib/test/index.d.ts +9 -0
- package/dist/lib/test/index.js +8 -0
- package/dist/lib/test/index.js.map +1 -1
- package/dist/lib/test/metadata/discovery.d.ts +21 -0
- package/dist/lib/test/metadata/discovery.js +37 -8
- package/dist/lib/test/metadata/discovery.js.map +1 -1
- package/dist/lib/test/mockComponent.d.ts +25 -0
- package/dist/lib/test/mockComponent.js +39 -6
- package/dist/lib/test/mockComponent.js.map +1 -1
- package/dist/lib/test/types.d.ts +13 -0
- package/package.json +11 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PrepareEventService.js","sourceRoot":"","sources":["../../../../../lib/server/src/services/PrepareEventService.ts"],"names":[],"mappings":";;;;;;;;;AACA,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjG,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAGlD
|
|
1
|
+
{"version":3,"file":"PrepareEventService.js","sourceRoot":"","sources":["../../../../../lib/server/src/services/PrepareEventService.ts"],"names":[],"mappings":";;;;;;;;;AACA,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjG,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAGlD;;;;;;;;;;;;;;;;;GAiBG;AAEI,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IACd,WAAW,GAAG,qBAAqB,CAAC;IAG5C,SAAS,CAAa;IAGtB,eAAe,CAAwB;IAE/C;;OAEG;IACI,KAAK,CAAC,OAAO;QAClB,gCAAgC;QAChC,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,UAAU,CAAM,aAAa,CAAC,KAAK,CAAC,CAAC;QAEhF,IAAI,CAAC,aAAa,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjD,sCAAsC;YACtC,OAAO;QACT,CAAC;QAED,gCAAgC;QAChC,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;YACpC,MAAM,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,oBAAoB,CAAC,OAAY;QAC7C,0BAA0B;QAC1B,MAAM,MAAM,GAAG,gBAAgB,CAAS,kBAAkB,CAAC,cAAc,EAAE,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QAEtG,4BAA4B;QAC5B,MAAM,QAAQ,GAAG,gBAAgB,CAC/B,kBAAkB,CAAC,gBAAgB,EACnC,OAAO,CAAC,WAAW,CACpB,CAAC;QAEF,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,wBAAwB;YACxB,OAAO;QACT,CAAC;QAED,wBAAwB;QACxB,KAAK,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9D,4BAA4B;YAC5B,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;gBAClB,SAAS;YACX,CAAC;YAED,4BAA4B;YAC5B,wEAAwE;YACxE,8DAA8D;YAC9D,MAAM,YAAY,GAAG,IAAI,CAAC,iBAAiB,CAAC,MAAM,EAAE,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC;YAEjG,kCAAkC;YAClC,MAAM,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAEvD,6BAA6B;YAC7B,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;IAED;;;;;;;;;OASG;IACK,iBAAiB,CAAC,MAAc,EAAE,KAAa,EAAE,WAAoB;QAC3E,IAAI,CAAC,WAAW;YAAE,OAAO,KAAK,CAAC;QAC/B,IAAI,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAC1B,IAAI,CAAC,KAAK;YAAE,OAAO,MAAM,CAAC;QAC1B,OAAO,GAAG,MAAM,IAAI,KAAK,EAAE,CAAC;IAC9B,CAAC;CACF,CAAA;AA7ES;IADP,MAAM,CAAC,iBAAiB,CAAC,SAAS,CAAC;;sDACN;AAGtB;IADP,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,CAAC;;4DACF;AAPpC,mBAAmB;IAD/B,WAAW,CAAC,iBAAiB,CAAC,qBAAqB,CAAC;GACxC,mBAAmB,CAiF/B"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { ICoreService } from '../../../ioc';
|
|
2
|
+
import type { MessagingInterceptor } from '../../microservice/types';
|
|
3
|
+
import type { DestroyOptions, MicroserviceTransport } from '../../microservice/MicroserviceTransport';
|
|
4
|
+
/**
|
|
5
|
+
* @description PrepareMicroserviceService - Registers microservice message handlers during bootstrap
|
|
6
|
+
*
|
|
7
|
+
* Scan Process:
|
|
8
|
+
* 1. Resolve all ComponentType.MESSAGE_CONTROLLER from container
|
|
9
|
+
* 2. Extract prefix + transport binding metadata from class
|
|
10
|
+
* 3. Extract handler metadata (@MessagePattern/@EventPattern methods)
|
|
11
|
+
* 4. Build final pattern: the controller prefix is joined onto BOTH message and event
|
|
12
|
+
* patterns, unless the handler opted out with prefix: false
|
|
13
|
+
* 5. Bind method to instance, wrap with interceptor onReceive chain
|
|
14
|
+
* 6. Register into the bound transport, then init() + listen() every transport
|
|
15
|
+
* 7. Wire transports into Ulak (send/emit client side)
|
|
16
|
+
*
|
|
17
|
+
* Rules:
|
|
18
|
+
* - Controllers found but no transport configured → throw (fail fast)
|
|
19
|
+
* - Transport configured but no controllers → still init + wire Ulak (client-only mode);
|
|
20
|
+
* the transport contract guarantees no consumer loop starts with zero handlers
|
|
21
|
+
* - Controller bound to an unknown named transport → throw listing available names
|
|
22
|
+
* - A handler with prefix: false registers its pattern verbatim (external topics,
|
|
23
|
+
* another service's event vocabulary, global catch-alls)
|
|
24
|
+
* - A prefix containing '*' is a boot error - the prefix must be a literal path segment
|
|
25
|
+
*/
|
|
26
|
+
export declare class PrepareMicroserviceService implements ICoreService {
|
|
27
|
+
readonly serviceName = "PrepareMicroserviceService";
|
|
28
|
+
private container;
|
|
29
|
+
private logger;
|
|
30
|
+
private ulak;
|
|
31
|
+
private transports;
|
|
32
|
+
/**
|
|
33
|
+
* Prepare microservice handlers - Called during bootstrap (before the HTTP adapter starts)
|
|
34
|
+
*
|
|
35
|
+
* @param transports - Named transports from the transport() config hook
|
|
36
|
+
* @param interceptors - Messaging interceptors applied to all transports
|
|
37
|
+
*/
|
|
38
|
+
prepare(transports?: Map<string, MicroserviceTransport>, interceptors?: MessagingInterceptor[]): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Gracefully shut down all transports - Called from AsenaServer.stop()
|
|
41
|
+
*/
|
|
42
|
+
destroy(options?: DestroyOptions): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Register all handlers from a single message controller
|
|
45
|
+
*/
|
|
46
|
+
private registerController;
|
|
47
|
+
/**
|
|
48
|
+
* Build the final pattern from the controller prefix and the handler pattern
|
|
49
|
+
*
|
|
50
|
+
* Rules (identical for @MessagePattern and @EventPattern, and identical to
|
|
51
|
+
* UlakMessages.buildPattern on the outbound side and PrepareEventService
|
|
52
|
+
* .buildEventPattern for the in-process event system):
|
|
53
|
+
* - Handler opted out (prefix: false) → pattern
|
|
54
|
+
* - No prefix → pattern
|
|
55
|
+
* - No pattern → prefix
|
|
56
|
+
* - Both → prefix.pattern
|
|
57
|
+
*/
|
|
58
|
+
private buildPattern;
|
|
59
|
+
/**
|
|
60
|
+
* Log one line per controller with the RESOLVED patterns
|
|
61
|
+
*
|
|
62
|
+
* Asena 0.8 changed the prefix rule for @EventPattern, so the joined result
|
|
63
|
+
* must be visible at boot - but per-handler lines would drown the startup output.
|
|
64
|
+
*/
|
|
65
|
+
private logResolved;
|
|
66
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { ComponentConstants, ComponentType, CoreService, ICoreServiceNames } from '../../../ioc';
|
|
11
|
+
import { Inject } from '../../../ioc/component';
|
|
12
|
+
import { getTypedMetadata } from '../../../utils';
|
|
13
|
+
import { blue, yellow } from '../../../logger';
|
|
14
|
+
import { DEFAULT_TRANSPORT_NAME } from '../../microservice/types';
|
|
15
|
+
import { composeOnReceive } from '../../microservice/interceptorChain';
|
|
16
|
+
/**
|
|
17
|
+
* @description PrepareMicroserviceService - Registers microservice message handlers during bootstrap
|
|
18
|
+
*
|
|
19
|
+
* Scan Process:
|
|
20
|
+
* 1. Resolve all ComponentType.MESSAGE_CONTROLLER from container
|
|
21
|
+
* 2. Extract prefix + transport binding metadata from class
|
|
22
|
+
* 3. Extract handler metadata (@MessagePattern/@EventPattern methods)
|
|
23
|
+
* 4. Build final pattern: the controller prefix is joined onto BOTH message and event
|
|
24
|
+
* patterns, unless the handler opted out with prefix: false
|
|
25
|
+
* 5. Bind method to instance, wrap with interceptor onReceive chain
|
|
26
|
+
* 6. Register into the bound transport, then init() + listen() every transport
|
|
27
|
+
* 7. Wire transports into Ulak (send/emit client side)
|
|
28
|
+
*
|
|
29
|
+
* Rules:
|
|
30
|
+
* - Controllers found but no transport configured → throw (fail fast)
|
|
31
|
+
* - Transport configured but no controllers → still init + wire Ulak (client-only mode);
|
|
32
|
+
* the transport contract guarantees no consumer loop starts with zero handlers
|
|
33
|
+
* - Controller bound to an unknown named transport → throw listing available names
|
|
34
|
+
* - A handler with prefix: false registers its pattern verbatim (external topics,
|
|
35
|
+
* another service's event vocabulary, global catch-alls)
|
|
36
|
+
* - A prefix containing '*' is a boot error - the prefix must be a literal path segment
|
|
37
|
+
*/
|
|
38
|
+
let PrepareMicroserviceService = class PrepareMicroserviceService {
|
|
39
|
+
serviceName = 'PrepareMicroserviceService';
|
|
40
|
+
container;
|
|
41
|
+
logger;
|
|
42
|
+
ulak;
|
|
43
|
+
transports = new Map();
|
|
44
|
+
/**
|
|
45
|
+
* Prepare microservice handlers - Called during bootstrap (before the HTTP adapter starts)
|
|
46
|
+
*
|
|
47
|
+
* @param transports - Named transports from the transport() config hook
|
|
48
|
+
* @param interceptors - Messaging interceptors applied to all transports
|
|
49
|
+
*/
|
|
50
|
+
async prepare(transports = new Map(), interceptors = []) {
|
|
51
|
+
const controllers = await this.container.resolveAll(ComponentType.MESSAGE_CONTROLLER);
|
|
52
|
+
const hasControllers = controllers !== null && controllers.length > 0;
|
|
53
|
+
if (transports.size === 0) {
|
|
54
|
+
if (hasControllers) {
|
|
55
|
+
throw new Error('@MessageController components found but no microservice transport configured - configure a microservice transport in your @Config transport()');
|
|
56
|
+
}
|
|
57
|
+
// No transports, no controllers - nothing to do
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
this.transports = transports;
|
|
61
|
+
if (hasControllers) {
|
|
62
|
+
for (const controller of controllers) {
|
|
63
|
+
this.registerController(controller, interceptors);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Connect and start consuming (transports with zero handlers start no consumer)
|
|
67
|
+
for (const [name, transport] of transports) {
|
|
68
|
+
await transport.init();
|
|
69
|
+
await transport.listen();
|
|
70
|
+
this.logger.info(`${blue('[Microservice]')} Transport "${name}" (${transport.name}) ready`);
|
|
71
|
+
}
|
|
72
|
+
// Wire the client side (Ulak.send/emit)
|
|
73
|
+
this.ulak.setMicroserviceTransports(transports, interceptors);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Gracefully shut down all transports - Called from AsenaServer.stop()
|
|
77
|
+
*/
|
|
78
|
+
async destroy(options) {
|
|
79
|
+
for (const [name, transport] of this.transports) {
|
|
80
|
+
try {
|
|
81
|
+
await transport.destroy(options);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
this.logger.error(`${blue('[Microservice]')} Transport "${name}" destroy failed`, error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
this.transports = new Map();
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Register all handlers from a single message controller
|
|
91
|
+
*/
|
|
92
|
+
registerController(controller, interceptors) {
|
|
93
|
+
const prefix = getTypedMetadata(ComponentConstants.MessagePrefixKey, controller.constructor) || '';
|
|
94
|
+
const transportName = getTypedMetadata(ComponentConstants.MessageTransportKey, controller.constructor) ||
|
|
95
|
+
DEFAULT_TRANSPORT_NAME;
|
|
96
|
+
const transport = this.transports.get(transportName);
|
|
97
|
+
if (!transport) {
|
|
98
|
+
const available = Array.from(this.transports.keys()).join(', ');
|
|
99
|
+
throw new Error(`@MessageController "${controller.constructor.name}" is bound to transport "${transportName}" but it is not configured - available transports: ${available}`);
|
|
100
|
+
}
|
|
101
|
+
const handlers = getTypedMetadata(ComponentConstants.MessageHandlersKey, controller.constructor);
|
|
102
|
+
if (!handlers) {
|
|
103
|
+
// No @MessagePattern/@EventPattern methods - skip
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const resolved = [];
|
|
107
|
+
for (const [methodName, metadata] of Object.entries(handlers)) {
|
|
108
|
+
if (metadata.skip) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
// `!== false`: metadata written by a pre-0.8 decorator build carries no
|
|
112
|
+
// flag and must follow the current default (apply the prefix)
|
|
113
|
+
const applyPrefix = metadata.prefix !== false;
|
|
114
|
+
// A wildcard prefix is nonsense in every direction: it produced a loud
|
|
115
|
+
// transport error on the message side and would now silently produce a
|
|
116
|
+
// bizarre event subscription ('order.*.created')
|
|
117
|
+
if (applyPrefix && prefix.includes('*')) {
|
|
118
|
+
throw new Error(`@MessageController "${controller.constructor.name}" has a wildcard prefix "${prefix}" - ` +
|
|
119
|
+
'the prefix is joined onto @MessagePattern and @EventPattern patterns and must be a literal ' +
|
|
120
|
+
'path segment (use prefix: false on a handler that must keep its pattern verbatim)');
|
|
121
|
+
}
|
|
122
|
+
const bound = controller[methodName].bind(controller);
|
|
123
|
+
const wrapped = (data, context) => composeOnReceive(interceptors, context, () => Promise.resolve(bound(data, context)));
|
|
124
|
+
const finalPattern = this.buildPattern(prefix, metadata.pattern, applyPrefix);
|
|
125
|
+
if (metadata.type === 'message') {
|
|
126
|
+
transport.registerMessageHandler(finalPattern, wrapped);
|
|
127
|
+
resolved.push({ kind: 'msg', pattern: finalPattern });
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
transport.registerEventHandler(finalPattern, wrapped);
|
|
131
|
+
resolved.push({ kind: 'evt', pattern: finalPattern });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
this.logResolved(controller.constructor.name, transportName, prefix, resolved);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Build the final pattern from the controller prefix and the handler pattern
|
|
138
|
+
*
|
|
139
|
+
* Rules (identical for @MessagePattern and @EventPattern, and identical to
|
|
140
|
+
* UlakMessages.buildPattern on the outbound side and PrepareEventService
|
|
141
|
+
* .buildEventPattern for the in-process event system):
|
|
142
|
+
* - Handler opted out (prefix: false) → pattern
|
|
143
|
+
* - No prefix → pattern
|
|
144
|
+
* - No pattern → prefix
|
|
145
|
+
* - Both → prefix.pattern
|
|
146
|
+
*/
|
|
147
|
+
buildPattern(prefix, pattern, applyPrefix) {
|
|
148
|
+
if (!applyPrefix)
|
|
149
|
+
return pattern;
|
|
150
|
+
if (!prefix)
|
|
151
|
+
return pattern;
|
|
152
|
+
if (!pattern)
|
|
153
|
+
return prefix;
|
|
154
|
+
return `${prefix}.${pattern}`;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Log one line per controller with the RESOLVED patterns
|
|
158
|
+
*
|
|
159
|
+
* Asena 0.8 changed the prefix rule for @EventPattern, so the joined result
|
|
160
|
+
* must be visible at boot - but per-handler lines would drown the startup output.
|
|
161
|
+
*/
|
|
162
|
+
logResolved(controllerName, transportName, prefix, resolved) {
|
|
163
|
+
if (!resolved.length) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const parts = [];
|
|
167
|
+
const messages = resolved.filter((entry) => entry.kind === 'msg').map((entry) => entry.pattern);
|
|
168
|
+
const events = resolved.filter((entry) => entry.kind === 'evt').map((entry) => entry.pattern);
|
|
169
|
+
if (messages.length)
|
|
170
|
+
parts.push(`msg: ${messages.join(', ')}`);
|
|
171
|
+
if (events.length)
|
|
172
|
+
parts.push(`evt: ${events.join(', ')}`);
|
|
173
|
+
this.logger.info(`${blue('[Microservice]')} ${yellow(controllerName)} → "${transportName}"` +
|
|
174
|
+
`${prefix ? ` (prefix "${prefix}")` : ''} ${parts.join(' | ')}`);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
__decorate([
|
|
178
|
+
Inject(ICoreServiceNames.CONTAINER),
|
|
179
|
+
__metadata("design:type", Function)
|
|
180
|
+
], PrepareMicroserviceService.prototype, "container", void 0);
|
|
181
|
+
__decorate([
|
|
182
|
+
Inject(ICoreServiceNames.SERVER_LOGGER),
|
|
183
|
+
__metadata("design:type", Object)
|
|
184
|
+
], PrepareMicroserviceService.prototype, "logger", void 0);
|
|
185
|
+
__decorate([
|
|
186
|
+
Inject(ICoreServiceNames.__ULAK__),
|
|
187
|
+
__metadata("design:type", Function)
|
|
188
|
+
], PrepareMicroserviceService.prototype, "ulak", void 0);
|
|
189
|
+
PrepareMicroserviceService = __decorate([
|
|
190
|
+
CoreService(ICoreServiceNames.PREPARE_MICROSERVICE_SERVICE)
|
|
191
|
+
], PrepareMicroserviceService);
|
|
192
|
+
export { PrepareMicroserviceService };
|
|
193
|
+
//# sourceMappingURL=PrepareMicroserviceService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PrepareMicroserviceService.js","sourceRoot":"","sources":["../../../../../lib/server/src/services/PrepareMicroserviceService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a deep mock object backed by native Proxy
|
|
3
|
+
*
|
|
4
|
+
* Any property access returns a memoized Bun mock function, and calling one
|
|
5
|
+
* returns another deep mock node - so arbitrary call chains work without any
|
|
6
|
+
* upfront shape definition. Used by mockComponent to evaluate @Inject expression
|
|
7
|
+
* transformations (e.g. the ulak() helper) without a running application.
|
|
8
|
+
*
|
|
9
|
+
* @returns Deep mock root node
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const deepMock = createDeepMock();
|
|
14
|
+
*
|
|
15
|
+
* // Any chain works out of the box
|
|
16
|
+
* const channel = deepMock.namespace('/chat');
|
|
17
|
+
* await channel.broadcast({ text: 'hello' });
|
|
18
|
+
*
|
|
19
|
+
* expect(channel.broadcast).toHaveBeenCalledWith({ text: 'hello' });
|
|
20
|
+
*
|
|
21
|
+
* // Mocks are configurable like any Bun mock
|
|
22
|
+
* channel.getSocketCount.mockReturnValue(5);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function createDeepMock(): any;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { mock } from 'bun:test';
|
|
2
|
+
/**
|
|
3
|
+
* Keys that must NOT resolve to a mock function on a deep mock node.
|
|
4
|
+
*
|
|
5
|
+
* `then`/`catch`/`finally`: if property access returned a function, the node
|
|
6
|
+
* would be treated as a thenable and `await deepMock` would never resolve.
|
|
7
|
+
* `toJSON`: keeps JSON.stringify from invoking an auto-generated mock.
|
|
8
|
+
*/
|
|
9
|
+
const IGNORED_KEYS = new Set(['then', 'catch', 'finally', 'toJSON']);
|
|
10
|
+
/**
|
|
11
|
+
* Creates a leaf mock: a REAL Bun mock function (no Proxy wrapper)
|
|
12
|
+
*
|
|
13
|
+
* Leaves must stay raw because Bun's expect() matchers and mock configuration
|
|
14
|
+
* methods (mockReturnValue, mockResolvedValue...) brand-check their target -
|
|
15
|
+
* a Proxy-wrapped mock fails "Expected value must be a mock function". The
|
|
16
|
+
* default implementation returns a memoized deep node so call chains continue.
|
|
17
|
+
*
|
|
18
|
+
* @param path - Access path of this mock, used as the function name for debugging
|
|
19
|
+
* @returns Bun mock function
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
function createDeepMockLeaf(path) {
|
|
24
|
+
let callResult;
|
|
25
|
+
const leaf = mock(() => {
|
|
26
|
+
callResult ??= createDeepMockNode(`${path}()`);
|
|
27
|
+
return callResult;
|
|
28
|
+
});
|
|
29
|
+
Object.defineProperty(leaf, 'name', { value: path, configurable: true });
|
|
30
|
+
return leaf;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Creates a deep mock node
|
|
34
|
+
*
|
|
35
|
+
* A node is a Proxy whose target is a Bun mock function: the node is callable
|
|
36
|
+
* (returning a memoized child node) and any unknown property resolves to a
|
|
37
|
+
* memoized raw leaf mock.
|
|
38
|
+
*
|
|
39
|
+
* @param path - Access path of this node, used as the function name for debugging
|
|
40
|
+
* @returns Proxy-wrapped mock function
|
|
41
|
+
*
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
function createDeepMockNode(path) {
|
|
45
|
+
let callResult;
|
|
46
|
+
const fn = mock(() => {
|
|
47
|
+
callResult ??= createDeepMockNode(`${path}()`);
|
|
48
|
+
return callResult;
|
|
49
|
+
});
|
|
50
|
+
Object.defineProperty(fn, 'name', { value: path, configurable: true });
|
|
51
|
+
const children = new Map();
|
|
52
|
+
return new Proxy(fn, {
|
|
53
|
+
get(target, key) {
|
|
54
|
+
// Pass through everything that exists on the mock function itself
|
|
55
|
+
// (mock state, Function.prototype members). Methods are bound to the
|
|
56
|
+
// raw target so native brand checks keep working through the Proxy.
|
|
57
|
+
if (Reflect.has(target, key)) {
|
|
58
|
+
const value = Reflect.get(target, key, target);
|
|
59
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
60
|
+
}
|
|
61
|
+
if (IGNORED_KEYS.has(key)) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
// Well-known symbols (Symbol.iterator, Symbol.toPrimitive, inspect
|
|
65
|
+
// symbols...) fall back to engine defaults instead of becoming mocks
|
|
66
|
+
if (typeof key === 'symbol') {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
// Memoized per key so repeated access returns the SAME mock function
|
|
70
|
+
// and call counts accumulate across accesses
|
|
71
|
+
if (!children.has(key)) {
|
|
72
|
+
children.set(key, createDeepMockLeaf(`${path}.${String(key)}`));
|
|
73
|
+
}
|
|
74
|
+
return children.get(key);
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Creates a deep mock object backed by native Proxy
|
|
80
|
+
*
|
|
81
|
+
* Any property access returns a memoized Bun mock function, and calling one
|
|
82
|
+
* returns another deep mock node - so arbitrary call chains work without any
|
|
83
|
+
* upfront shape definition. Used by mockComponent to evaluate @Inject expression
|
|
84
|
+
* transformations (e.g. the ulak() helper) without a running application.
|
|
85
|
+
*
|
|
86
|
+
* @returns Deep mock root node
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* const deepMock = createDeepMock();
|
|
91
|
+
*
|
|
92
|
+
* // Any chain works out of the box
|
|
93
|
+
* const channel = deepMock.namespace('/chat');
|
|
94
|
+
* await channel.broadcast({ text: 'hello' });
|
|
95
|
+
*
|
|
96
|
+
* expect(channel.broadcast).toHaveBeenCalledWith({ text: 'hello' });
|
|
97
|
+
*
|
|
98
|
+
* // Mocks are configurable like any Bun mock
|
|
99
|
+
* channel.getSocketCount.mockReturnValue(5);
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export function createDeepMock() {
|
|
103
|
+
return createDeepMockNode('deepMock');
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=deepMock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deepMock.js","sourceRoot":"","sources":["../../../../lib/test/factory/deepMock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAEhC;;;;;;GAMG;AACH,MAAM,YAAY,GAAG,IAAI,GAAG,CAAc,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;AAElF;;;;;;;;;;;;GAYG;AACH,SAAS,kBAAkB,CAAC,IAAY;IACtC,IAAI,UAAe,CAAC;IAEpB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE;QACrB,UAAU,KAAK,kBAAkB,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;QAE/C,OAAO,UAAU,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzE,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,kBAAkB,CAAC,IAAY;IACtC,IAAI,UAAe,CAAC;IAEpB,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE;QACnB,UAAU,KAAK,kBAAkB,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;QAE/C,OAAO,UAAU,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;IAEvE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAoB,CAAC;IAE7C,OAAO,IAAI,KAAK,CAAC,EAAE,EAAE;QACnB,GAAG,CAAC,MAAM,EAAE,GAAG;YACb,kEAAkE;YAClE,qEAAqE;YACrE,oEAAoE;YACpE,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;gBAC7B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;gBAE/C,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;YAClE,CAAC;YAED,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC1B,OAAO,SAAS,CAAC;YACnB,CAAC;YAED,mEAAmE;YACnE,qEAAqE;YACrE,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;gBAC5B,OAAO,SAAS,CAAC;YACnB,CAAC;YAED,qEAAqE;YACrE,6CAA6C;YAC7C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACvB,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,kBAAkB,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;YAClE,CAAC;YAED,OAAO,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,kBAAkB,CAAC,UAAU,CAAC,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Mock } from 'bun:test';
|
|
2
|
+
import type { Ulak } from '../../server/messaging';
|
|
3
|
+
/**
|
|
4
|
+
* A Ulak.NameSpace stub whose methods are all Bun mock functions
|
|
5
|
+
*
|
|
6
|
+
* Extends Ulak.NameSpace so any method added to the interface in the future
|
|
7
|
+
* breaks this file's compilation instead of silently drifting out of sync.
|
|
8
|
+
*/
|
|
9
|
+
export interface TestUlakNameSpace<T extends string = string> extends Ulak.NameSpace<T> {
|
|
10
|
+
broadcast: Mock<(data: any) => Promise<void>>;
|
|
11
|
+
to: Mock<(room: string, data: any) => Promise<void>>;
|
|
12
|
+
toSocket: Mock<(socketId: string, data: any) => Promise<void>>;
|
|
13
|
+
toMany: Mock<(rooms: string[], data: any) => Promise<void>>;
|
|
14
|
+
getSocketCount: Mock<() => number>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Creates a typed Ulak namespace stub for tests
|
|
18
|
+
*
|
|
19
|
+
* Use it as an explicit override for @Inject(ulak(...)) fields when you want
|
|
20
|
+
* a fully typed mock instead of the automatic deep mock generated by
|
|
21
|
+
* mockComponent.
|
|
22
|
+
*
|
|
23
|
+
* @param path - The namespace path the stub reports (defaults to '/')
|
|
24
|
+
* @returns Ulak.NameSpace implementation with all methods mocked
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import { mockComponent, createTestUlakStub } from '@asenajs/asena/test';
|
|
29
|
+
*
|
|
30
|
+
* const statsChannel = createTestUlakStub('/ws/public/stats');
|
|
31
|
+
*
|
|
32
|
+
* const { instance } = mockComponent(UserService, {
|
|
33
|
+
* overrides: { statsChannel },
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* await instance.createAnonUser('John');
|
|
37
|
+
*
|
|
38
|
+
* expect(statsChannel.broadcast).toHaveBeenCalledWith({
|
|
39
|
+
* action: 'update',
|
|
40
|
+
* data: { newUser: 1 },
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare function createTestUlakStub<T extends string = string>(path?: T): TestUlakNameSpace<T>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { mock } from 'bun:test';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a typed Ulak namespace stub for tests
|
|
4
|
+
*
|
|
5
|
+
* Use it as an explicit override for @Inject(ulak(...)) fields when you want
|
|
6
|
+
* a fully typed mock instead of the automatic deep mock generated by
|
|
7
|
+
* mockComponent.
|
|
8
|
+
*
|
|
9
|
+
* @param path - The namespace path the stub reports (defaults to '/')
|
|
10
|
+
* @returns Ulak.NameSpace implementation with all methods mocked
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { mockComponent, createTestUlakStub } from '@asenajs/asena/test';
|
|
15
|
+
*
|
|
16
|
+
* const statsChannel = createTestUlakStub('/ws/public/stats');
|
|
17
|
+
*
|
|
18
|
+
* const { instance } = mockComponent(UserService, {
|
|
19
|
+
* overrides: { statsChannel },
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* await instance.createAnonUser('John');
|
|
23
|
+
*
|
|
24
|
+
* expect(statsChannel.broadcast).toHaveBeenCalledWith({
|
|
25
|
+
* action: 'update',
|
|
26
|
+
* data: { newUser: 1 },
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function createTestUlakStub(path = '/') {
|
|
31
|
+
return {
|
|
32
|
+
path,
|
|
33
|
+
broadcast: mock(async () => undefined),
|
|
34
|
+
to: mock(async () => undefined),
|
|
35
|
+
toSocket: mock(async () => undefined),
|
|
36
|
+
toMany: mock(async () => undefined),
|
|
37
|
+
getSocketCount: mock(() => 0),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=ulakStub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ulakStub.js","sourceRoot":"","sources":["../../../../lib/test/factory/ulakStub.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAkBhC;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,kBAAkB,CAA4B,OAAU,GAAQ;IAC9E,OAAO;QACL,IAAI;QACJ,SAAS,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC;QACtC,EAAE,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC;QAC/B,QAAQ,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC;QACrC,MAAM,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC;QACnC,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;KAC9B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AsenaAdapter } from '../../adapter';
|
|
2
|
+
import type { TestApp, TestAppOptions } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Boots a full Asena application for testing - the equivalent of Spring's `@SpringBootTest`.
|
|
5
|
+
*
|
|
6
|
+
* Everything runs for real: the IoC container, all bootstrap phases, the adapter and its
|
|
7
|
+
* routing pipeline. Only the components you name are registered, and any of them can be
|
|
8
|
+
* swapped for a double through `overrides`.
|
|
9
|
+
*
|
|
10
|
+
* @param options - Adapter, components and optional overrides
|
|
11
|
+
* @returns A started app with a fluent HTTP client attached
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const [adapter] = createHonoAdapter({ logger: silentLogger });
|
|
16
|
+
* const app = await createTestApp({
|
|
17
|
+
* adapter,
|
|
18
|
+
* components: [UserController, UserService],
|
|
19
|
+
* overrides: { UserService: { findById: mock(async () => ({ id: '1', name: 'Ada' })) } },
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* await app.get('/users/1').expectStatus(200).expectJson({ id: '1', name: 'Ada' });
|
|
23
|
+
*
|
|
24
|
+
* await app.stop();
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* // Automatic cleanup, and no TCP port taken at all
|
|
30
|
+
* await using app = await createTestApp({ adapter, components, dispatch: 'socket' });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function createTestApp<A extends AsenaAdapter<any, any> = AsenaAdapter<any, any>>(options: TestAppOptions<A>): Promise<TestApp>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { unlink } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { AsenaServerFactory } from '../../server/AsenaServerFactory';
|
|
5
|
+
import { TestHttpCall } from '../http/TestHttpCall';
|
|
6
|
+
import { silentLogger } from './silentLogger';
|
|
7
|
+
// Monotonic, not random: workflow scripts and test runners may run many apps per process
|
|
8
|
+
// and a collision would surface as a confusing EADDRINUSE on a socket file
|
|
9
|
+
let socketCounter = 0;
|
|
10
|
+
function nextSocketPath() {
|
|
11
|
+
socketCounter += 1;
|
|
12
|
+
return join(tmpdir(), `asena-test-${process.pid}-${socketCounter}.sock`);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Boots a full Asena application for testing - the equivalent of Spring's `@SpringBootTest`.
|
|
16
|
+
*
|
|
17
|
+
* Everything runs for real: the IoC container, all bootstrap phases, the adapter and its
|
|
18
|
+
* routing pipeline. Only the components you name are registered, and any of them can be
|
|
19
|
+
* swapped for a double through `overrides`.
|
|
20
|
+
*
|
|
21
|
+
* @param options - Adapter, components and optional overrides
|
|
22
|
+
* @returns A started app with a fluent HTTP client attached
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const [adapter] = createHonoAdapter({ logger: silentLogger });
|
|
27
|
+
* const app = await createTestApp({
|
|
28
|
+
* adapter,
|
|
29
|
+
* components: [UserController, UserService],
|
|
30
|
+
* overrides: { UserService: { findById: mock(async () => ({ id: '1', name: 'Ada' })) } },
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* await app.get('/users/1').expectStatus(200).expectJson({ id: '1', name: 'Ada' });
|
|
34
|
+
*
|
|
35
|
+
* await app.stop();
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* // Automatic cleanup, and no TCP port taken at all
|
|
41
|
+
* await using app = await createTestApp({ adapter, components, dispatch: 'socket' });
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export async function createTestApp(options) {
|
|
45
|
+
const { adapter, components, overrides, logger = silentLogger, dispatch = 'server' } = options;
|
|
46
|
+
const socketPath = dispatch === 'socket' ? nextSocketPath() : undefined;
|
|
47
|
+
const server = await AsenaServerFactory.create({
|
|
48
|
+
adapter,
|
|
49
|
+
logger,
|
|
50
|
+
components,
|
|
51
|
+
overrides,
|
|
52
|
+
// Port 0 lets Bun assign a free ephemeral port, which removes the random-port race
|
|
53
|
+
port: options.port ?? 0,
|
|
54
|
+
});
|
|
55
|
+
await server.start(socketPath ? { unix: socketPath } : undefined);
|
|
56
|
+
const port = server.httpServer?.port ?? 0;
|
|
57
|
+
const baseUrl = socketPath ? 'http://localhost' : `http://localhost:${port}`;
|
|
58
|
+
const call = (path, init, method) => {
|
|
59
|
+
const requestInit = { ...init };
|
|
60
|
+
if (method) {
|
|
61
|
+
requestInit.method = method;
|
|
62
|
+
}
|
|
63
|
+
if (socketPath) {
|
|
64
|
+
// Bun routes the request over the socket while still parsing the URL for path/query
|
|
65
|
+
requestInit.unix = socketPath;
|
|
66
|
+
}
|
|
67
|
+
return new TestHttpCall(`${baseUrl}${path}`, requestInit);
|
|
68
|
+
};
|
|
69
|
+
let stopped = false;
|
|
70
|
+
const app = {
|
|
71
|
+
server,
|
|
72
|
+
container: server.coreContainer.container,
|
|
73
|
+
port,
|
|
74
|
+
baseUrl,
|
|
75
|
+
socketPath,
|
|
76
|
+
resolve: async (name) => (await server.coreContainer.container.resolve(name)),
|
|
77
|
+
wsUrl: (path) => (socketPath ? `ws+unix://${socketPath}:${path}` : `ws://localhost:${port}${path}`),
|
|
78
|
+
request: (path, init) => call(path, init),
|
|
79
|
+
get: (path, init) => call(path, init, 'GET'),
|
|
80
|
+
post: (path, init) => call(path, init, 'POST'),
|
|
81
|
+
put: (path, init) => call(path, init, 'PUT'),
|
|
82
|
+
patch: (path, init) => call(path, init, 'PATCH'),
|
|
83
|
+
delete: (path, init) => call(path, init, 'DELETE'),
|
|
84
|
+
head: (path, init) => call(path, init, 'HEAD'),
|
|
85
|
+
options: (path, init) => call(path, init, 'OPTIONS'),
|
|
86
|
+
stop: async () => {
|
|
87
|
+
if (stopped) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
stopped = true;
|
|
91
|
+
await server.stop();
|
|
92
|
+
if (socketPath) {
|
|
93
|
+
// Bun removes the socket file on a clean stop; this only matters after a crash
|
|
94
|
+
await unlink(socketPath).catch(() => {
|
|
95
|
+
// Already gone - nothing to clean up
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
[Symbol.asyncDispose]: async () => {
|
|
100
|
+
await app.stop();
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
return app;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=createTestApp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createTestApp.js","sourceRoot":"","sources":["../../../../lib/test/harness/createTestApp.ts"],"names":[],"mappings":"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"}
|