@asenajs/asena 0.7.1 → 0.8.1

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 (148) hide show
  1. package/README.md +2 -2
  2. package/dist/lib/adapter/AsenaAdapter.d.ts +2 -2
  3. package/dist/lib/adapter/AsenaAdapter.js.map +1 -1
  4. package/dist/lib/adapter/types/ServeOptions.d.ts +16 -0
  5. package/dist/lib/adapter/types/ValidationError.d.ts +53 -0
  6. package/dist/lib/adapter/types/ValidationError.js +24 -0
  7. package/dist/lib/adapter/types/ValidationError.js.map +1 -0
  8. package/dist/lib/adapter/types/index.d.ts +1 -0
  9. package/dist/lib/adapter/types/index.js +1 -0
  10. package/dist/lib/adapter/types/index.js.map +1 -1
  11. package/dist/lib/ioc/Container.d.ts +25 -0
  12. package/dist/lib/ioc/Container.js +47 -0
  13. package/dist/lib/ioc/Container.js.map +1 -1
  14. package/dist/lib/ioc/CoreContainer.d.ts +5 -3
  15. package/dist/lib/ioc/CoreContainer.js +9 -3
  16. package/dist/lib/ioc/CoreContainer.js.map +1 -1
  17. package/dist/lib/ioc/IocEngine.d.ts +50 -4
  18. package/dist/lib/ioc/IocEngine.js +138 -12
  19. package/dist/lib/ioc/IocEngine.js.map +1 -1
  20. package/dist/lib/ioc/component/componentRegistry.d.ts +22 -0
  21. package/dist/lib/ioc/component/componentRegistry.js +119 -0
  22. package/dist/lib/ioc/component/componentRegistry.js.map +1 -0
  23. package/dist/lib/ioc/component/componentUtils.js +4 -0
  24. package/dist/lib/ioc/component/componentUtils.js.map +1 -1
  25. package/dist/lib/ioc/component/decorators/Inject.js +16 -0
  26. package/dist/lib/ioc/component/decorators/Inject.js.map +1 -1
  27. package/dist/lib/ioc/component/index.d.ts +1 -0
  28. package/dist/lib/ioc/component/index.js +1 -0
  29. package/dist/lib/ioc/component/index.js.map +1 -1
  30. package/dist/lib/ioc/constants/ComponentConstants.d.ts +4 -0
  31. package/dist/lib/ioc/constants/ComponentConstants.js +5 -0
  32. package/dist/lib/ioc/constants/ComponentConstants.js.map +1 -1
  33. package/dist/lib/ioc/helper/fileHelper.d.ts +9 -0
  34. package/dist/lib/ioc/helper/fileHelper.js +41 -11
  35. package/dist/lib/ioc/helper/fileHelper.js.map +1 -1
  36. package/dist/lib/ioc/types/ICoreServiceNamesType.d.ts +1 -0
  37. package/dist/lib/ioc/types/ICoreServiceNamesType.js +1 -0
  38. package/dist/lib/ioc/types/ICoreServiceNamesType.js.map +1 -1
  39. package/dist/lib/ioc/types/InjectableComponent.d.ts +10 -0
  40. package/dist/lib/ioc/types/IocConfig.d.ts +7 -0
  41. package/dist/lib/ioc/types/decorators/ComponentType.d.ts +2 -1
  42. package/dist/lib/ioc/types/decorators/ComponentType.js +1 -0
  43. package/dist/lib/ioc/types/decorators/ComponentType.js.map +1 -1
  44. package/dist/lib/server/AsenaServer.d.ts +47 -3
  45. package/dist/lib/server/AsenaServer.js +167 -28
  46. package/dist/lib/server/AsenaServer.js.map +1 -1
  47. package/dist/lib/server/AsenaServerFactory.d.ts +38 -3
  48. package/dist/lib/server/AsenaServerFactory.js +21 -2
  49. package/dist/lib/server/AsenaServerFactory.js.map +1 -1
  50. package/dist/lib/server/config/AsenaConfig.d.ts +82 -4
  51. package/dist/lib/server/config/AsenaConfig.js +39 -1
  52. package/dist/lib/server/config/AsenaConfig.js.map +1 -1
  53. package/dist/lib/server/decorators/components/MessageController.d.ts +49 -0
  54. package/dist/lib/server/decorators/components/MessageController.js +62 -0
  55. package/dist/lib/server/decorators/components/MessageController.js.map +1 -0
  56. package/dist/lib/server/decorators/components/index.d.ts +1 -0
  57. package/dist/lib/server/decorators/components/index.js +1 -0
  58. package/dist/lib/server/decorators/components/index.js.map +1 -1
  59. package/dist/lib/server/event/EventDispatchService.d.ts +3 -7
  60. package/dist/lib/server/event/EventDispatchService.js +9 -40
  61. package/dist/lib/server/event/EventDispatchService.js.map +1 -1
  62. package/dist/lib/server/event/PatternHandlerIndex.d.ts +53 -0
  63. package/dist/lib/server/event/PatternHandlerIndex.js +100 -0
  64. package/dist/lib/server/event/PatternHandlerIndex.js.map +1 -0
  65. package/dist/lib/server/event/decorators/On.d.ts +4 -0
  66. package/dist/lib/server/event/decorators/On.js +6 -0
  67. package/dist/lib/server/event/decorators/On.js.map +1 -1
  68. package/dist/lib/server/event/index.d.ts +1 -0
  69. package/dist/lib/server/event/index.js +2 -0
  70. package/dist/lib/server/event/index.js.map +1 -1
  71. package/dist/lib/server/event/types.d.ts +12 -0
  72. package/dist/lib/server/health/HealthServer.d.ts +26 -0
  73. package/dist/lib/server/health/HealthServer.js +63 -0
  74. package/dist/lib/server/health/HealthServer.js.map +1 -0
  75. package/dist/lib/server/health/index.d.ts +1 -0
  76. package/dist/lib/server/health/index.js +2 -0
  77. package/dist/lib/server/health/index.js.map +1 -0
  78. package/dist/lib/server/messaging/Ulak.d.ts +156 -0
  79. package/dist/lib/server/messaging/Ulak.js +174 -0
  80. package/dist/lib/server/messaging/Ulak.js.map +1 -1
  81. package/dist/lib/server/messaging/types.d.ts +5 -1
  82. package/dist/lib/server/messaging/types.js +5 -0
  83. package/dist/lib/server/messaging/types.js.map +1 -1
  84. package/dist/lib/server/microservice/InMemoryTransport.d.ts +45 -0
  85. package/dist/lib/server/microservice/InMemoryTransport.js +124 -0
  86. package/dist/lib/server/microservice/InMemoryTransport.js.map +1 -0
  87. package/dist/lib/server/microservice/MicroserviceTransport.d.ts +132 -0
  88. package/dist/lib/server/microservice/MicroserviceTransport.js +2 -0
  89. package/dist/lib/server/microservice/MicroserviceTransport.js.map +1 -0
  90. package/dist/lib/server/microservice/decorators/EventPattern.d.ts +38 -0
  91. package/dist/lib/server/microservice/decorators/EventPattern.js +59 -0
  92. package/dist/lib/server/microservice/decorators/EventPattern.js.map +1 -0
  93. package/dist/lib/server/microservice/decorators/MessagePattern.d.ts +31 -0
  94. package/dist/lib/server/microservice/decorators/MessagePattern.js +55 -0
  95. package/dist/lib/server/microservice/decorators/MessagePattern.js.map +1 -0
  96. package/dist/lib/server/microservice/decorators/index.d.ts +2 -0
  97. package/dist/lib/server/microservice/decorators/index.js +3 -0
  98. package/dist/lib/server/microservice/decorators/index.js.map +1 -0
  99. package/dist/lib/server/microservice/index.d.ts +5 -0
  100. package/dist/lib/server/microservice/index.js +11 -0
  101. package/dist/lib/server/microservice/index.js.map +1 -0
  102. package/dist/lib/server/microservice/interceptorChain.d.ts +20 -0
  103. package/dist/lib/server/microservice/interceptorChain.js +39 -0
  104. package/dist/lib/server/microservice/interceptorChain.js.map +1 -0
  105. package/dist/lib/server/microservice/types.d.ts +142 -0
  106. package/dist/lib/server/microservice/types.js +5 -0
  107. package/dist/lib/server/microservice/types.js.map +1 -0
  108. package/dist/lib/server/src/services/PrepareEventService.d.ts +4 -1
  109. package/dist/lib/server/src/services/PrepareEventService.js +10 -3
  110. package/dist/lib/server/src/services/PrepareEventService.js.map +1 -1
  111. package/dist/lib/server/src/services/PrepareMicroserviceService.d.ts +66 -0
  112. package/dist/lib/server/src/services/PrepareMicroserviceService.js +193 -0
  113. package/dist/lib/server/src/services/PrepareMicroserviceService.js.map +1 -0
  114. package/dist/lib/test/factory/deepMock.d.ts +25 -0
  115. package/dist/lib/test/factory/deepMock.js +105 -0
  116. package/dist/lib/test/factory/deepMock.js.map +1 -0
  117. package/dist/lib/test/factory/ulakStub.d.ts +44 -0
  118. package/dist/lib/test/factory/ulakStub.js +40 -0
  119. package/dist/lib/test/factory/ulakStub.js.map +1 -0
  120. package/dist/lib/test/harness/createTestApp.d.ts +33 -0
  121. package/dist/lib/test/harness/createTestApp.js +105 -0
  122. package/dist/lib/test/harness/createTestApp.js.map +1 -0
  123. package/dist/lib/test/harness/createWebTest.d.ts +28 -0
  124. package/dist/lib/test/harness/createWebTest.js +127 -0
  125. package/dist/lib/test/harness/createWebTest.js.map +1 -0
  126. package/dist/lib/test/harness/silentLogger.d.ts +27 -0
  127. package/dist/lib/test/harness/silentLogger.js +54 -0
  128. package/dist/lib/test/harness/silentLogger.js.map +1 -0
  129. package/dist/lib/test/harness/types.d.ts +147 -0
  130. package/dist/lib/test/harness/types.js +2 -0
  131. package/dist/lib/test/harness/types.js.map +1 -0
  132. package/dist/lib/test/http/TestHttpCall.d.ts +78 -0
  133. package/dist/lib/test/http/TestHttpCall.js +149 -0
  134. package/dist/lib/test/http/TestHttpCall.js.map +1 -0
  135. package/dist/lib/test/http/TestHttpResponse.d.ts +46 -0
  136. package/dist/lib/test/http/TestHttpResponse.js +70 -0
  137. package/dist/lib/test/http/TestHttpResponse.js.map +1 -0
  138. package/dist/lib/test/index.d.ts +9 -0
  139. package/dist/lib/test/index.js +8 -0
  140. package/dist/lib/test/index.js.map +1 -1
  141. package/dist/lib/test/metadata/discovery.d.ts +21 -0
  142. package/dist/lib/test/metadata/discovery.js +37 -8
  143. package/dist/lib/test/metadata/discovery.js.map +1 -1
  144. package/dist/lib/test/mockComponent.d.ts +25 -0
  145. package/dist/lib/test/mockComponent.js +39 -6
  146. package/dist/lib/test/mockComponent.js.map +1 -1
  147. package/dist/lib/test/types.d.ts +13 -0
  148. package/package.json +11 -7
@@ -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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"}
@@ -0,0 +1,28 @@
1
+ import type { AsenaAdapter } from '../../adapter';
2
+ import type { WebTestOptions, WebTestResult } from './types';
3
+ /**
4
+ * Boots only the web layer of an application - the equivalent of Spring's `@WebMvcTest`.
5
+ *
6
+ * The controllers, their middlewares and their validators run for real. Every other
7
+ * dependency they inject is replaced by an auto-generated mock shaped like the real class,
8
+ * so a controller test never drags in a database or an HTTP client.
9
+ *
10
+ * Core services (the container, the logger, `ulak(...)`, the event emitter, ...) are always
11
+ * real - they are wired during bootstrap, before user components exist.
12
+ *
13
+ * @param options - Adapter, controllers and optional explicit overrides
14
+ * @returns The started app plus every double in play, keyed by service name
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const { app, mocks } = await createWebTest({ adapter, controllers: [UserController] });
19
+ *
20
+ * mocks.UserService.findById.mockResolvedValue({ id: '1', name: 'Ada' });
21
+ *
22
+ * await app.get('/users/1').expectStatus(200).expectJson({ id: '1', name: 'Ada' });
23
+ * expect(mocks.UserService.findById).toHaveBeenCalledWith('1');
24
+ *
25
+ * await app.stop();
26
+ * ```
27
+ */
28
+ export declare function createWebTest<A extends AsenaAdapter<any, any> = AsenaAdapter<any, any>>(options: WebTestOptions<A>): Promise<WebTestResult>;