@asenajs/asena 0.6.0 → 0.6.2

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 CHANGED
@@ -1,218 +1,44 @@
1
- # Asena
1
+ <p width="%100" align="center">
2
+ <img src="https://avatars.githubusercontent.com/u/179836938?s=200&v=4" width="150" align="center"/>
3
+ </p>
2
4
 
3
- A high-performance, NestJS-like IoC web framework built on Bun runtime with full dependency injection support.
5
+ # Asena
4
6
 
5
- ## Documentation
7
+ [![Version](https://img.shields.io/badge/version-0.6.2-blue.svg)](https://asena.sh)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
9
+ [![Bun Version](https://img.shields.io/badge/Bun-1.3.2%2B-blueviolet)](https://bun.sh)
6
10
 
7
- For detailed documentation, visit [asena.sh](https://asena.sh).
11
+ A high-performance IoC web framework for Bun runtime, bringing Spring Boot's automatic component discovery and field-based dependency injection to TypeScript.
8
12
 
9
- Also check out [AsenaExample](https://github.com/LibirSoft/AsenaExample) for latest usage patterns.
13
+ ## Philosophy
10
14
 
11
- ## Key Features
15
+ `Spring Boot` and `Quarkus` have established proven patterns for enterprise application development, but developers transitioning to TypeScript often find themselves reimplementing familiar concepts or adapting to unfamiliar architectures. `AsenaJS` addresses this gap by bringing automatic component discovery, field-based dependency injection to the `Bun` ecosystem.
12
16
 
13
- - **Full IoC Container**: Field-based dependency injection for all components
14
- - **Decorator-based**: TypeScript decorators for routing and DI (similar to NestJS)
15
- - **High Performance**: Built on Bun runtime for optimal speed
16
- - **WebSocket Support**: Native WebSocket handling
17
- - **Flexible Middleware**: Multi-level middleware system (global, controller, route)
18
- - **Adapter System**: Pluggable HTTP adapters (Hono default)
19
- - **Type-Safe**: Full TypeScript support with strict mode
20
- - **Zero Config**: Minimal setup required
17
+ The framework eliminates boilerplate through automatic scanning of decorator-annotated classes, removing the need for explicit module declarations or manual wiring. Components marked with `@Controller`, `@Service`, or `@Repository` (via asena-drizzle package) are discovered and registered automatically, allowing developers to focus on business logic rather than configuration. This approach, combined with Bun's native performance characteristics, delivers both the familiar developer experience of Spring Boot and the speed expected from modern JavaScript runtimes.
21
18
 
22
- ## Quick Start
19
+ `AsenaJS` is designed to make developers familiar with `Spring Boot` and `Quarkus` feel at home in the TypeScript ecosystem. As the framework evolves, we remain committed to this philosophy—bringing more proven patterns from the Java world while maintaining the performance advantages that `Bun` provides and flexiblity of Typescript.
23
20
 
24
- ### Using CLI (Recommended)
25
-
26
- ```bash
27
- bun add -D @asenajs/asena-cli
28
- asena create my-project
29
- cd my-project
30
- asena dev start
31
- ```
21
+ ## Documentation
32
22
 
33
- ### Manual Setup
23
+ 📚 **Full documentation:** [asena.sh](https://asena.sh)
34
24
 
35
- Install dependencies:
25
+ For complete guides, API reference, and advanced topics, visit our documentation site.
36
26
 
37
- ```bash
38
- bun add @asenajs/asena @asenajs/hono-adapter @asenajs/asena-logger
39
- bun add -D @asenajs/asena-cli
40
- ```
27
+ **Example Project:** [AsenaExample](https://github.com/LibirSoft/AsenaExample) - See latest usage patterns and best practices.
41
28
 
42
- Configure TypeScript (`tsconfig.json`):
43
-
44
- ```json
45
- {
46
- "compilerOptions": {
47
- "target": "ESNext",
48
- "module": "ESNext",
49
- "moduleResolution": "bundler",
50
- "experimentalDecorators": true,
51
- "emitDecoratorMetadata": true,
52
- "strict": true,
53
- "skipLibCheck": true
54
- }
55
- }
56
- ```
57
-
58
- Initialize config:
29
+ ## Quick Start
59
30
 
60
31
  ```bash
61
- asena init
62
- ```
63
-
64
- Create your server (`src/index.ts`):
65
-
66
- ```typescript
67
- import { AsenaServerFactory } from '@asenajs/asena';
68
- import { createHonoAdapter } from '@asenajs/hono-adapter';
69
- import { logger } from './logger';
70
-
71
- const [adapter, asenaLogger] = createHonoAdapter(logger);
72
-
73
- const server = await AsenaServerFactory.create({
74
- adapter,
75
- logger: asenaLogger,
76
- port: 3000
77
- });
78
-
79
- await server.start();
80
- ```
81
-
82
- Create a controller (`src/controllers/HelloController.ts`):
83
-
84
- ```typescript
85
- import { Controller } from '@asenajs/asena/server';
86
- import { Get } from '@asenajs/asena/web';
87
- import type { Context } from '@asenajs/hono-adapter';
88
-
89
- @Controller('/hello')
90
- export class HelloController {
91
- @Get('/world')
92
- async getHello(context: Context) {
93
- return context.send('Hello World');
94
- }
95
- }
96
- ```
97
-
98
- ## Core Concepts
99
-
100
- ### Controllers
101
-
102
- Handle HTTP requests with decorators:
103
-
104
- ```typescript
105
- @Controller('/users')
106
- export class UserController {
107
- @Get('/:id')
108
- async getUser(context: Context) {
109
- const id = context.req.param('id');
110
- return context.json({ id, name: 'John' });
111
- }
112
-
113
- @Post('/')
114
- async createUser(context: Context) {
115
- const body = await context.req.json();
116
- return context.json({ success: true });
117
- }
118
- }
119
- ```
120
-
121
- ### Services
122
-
123
- Business logic with dependency injection:
124
-
125
- ```typescript
126
- @Service()
127
- export class UserService {
128
- async findUser(id: string) {
129
- // Your business logic
130
- return { id, name: 'John' };
131
- }
132
- }
133
-
134
- @Controller('/users')
135
- export class UserController {
136
- @Inject(UserService)
137
- private userService: UserService;
138
-
139
- @Get('/:id')
140
- async getUser(context: Context) {
141
- const user = await this.userService.findUser(context.req.param('id'));
142
- return context.json(user);
143
- }
144
- }
145
- ```
146
-
147
- ### Middleware
148
-
149
- Multi-level middleware support:
150
-
151
- ```typescript
152
- @Middleware()
153
- export class AuthMiddleware extends MiddlewareService {
154
- async handle(context: Context, next: Function) {
155
- // Check authentication
156
- await next();
157
- }
158
- }
159
-
160
- // Global middleware via Config
161
- @Config()
162
- export class AppConfig {
163
- globalMiddlewares() {
164
- return [
165
- LoggerMiddleware, // Apply to all routes
166
-
167
- // Pattern-based
168
- {
169
- middleware: AuthMiddleware,
170
- routes: { include: ['/api/*', '/admin/*'] }
171
- },
172
- {
173
- middleware: RateLimitMiddleware,
174
- routes: { exclude: ['/health', '/metrics'] }
175
- }
176
- ];
177
- }
178
- }
179
-
180
- // Controller-level
181
- @Controller({ path: '/admin', middlewares: [AuthMiddleware] })
182
- export class AdminController { }
183
-
184
- // Route-level
185
- @Get({ path: '/users', middlewares: [AuthMiddleware] })
186
- async getUsers(context: Context) { }
187
- ```
188
-
189
- ### WebSocket
190
-
191
- Built-in WebSocket support:
192
-
193
- ```typescript
194
- @WebSocket('/chat')
195
- export class ChatSocket extends AsenaWebSocketService {
196
- onOpen(ws: Socket) {
197
- ws.send('Welcome!');
198
- }
199
-
200
- onMessage(ws: Socket, message: string) {
201
- ws.send(`Echo: ${message}`);
202
- }
203
- }
204
- ```
205
-
206
- ## CLI Commands
32
+ # Create new project
33
+ bun add -g @asenajs/asena-cli
34
+ asena create my-project --adapter=hono --logger --eslint --prettier
35
+ cd my-project
207
36
 
208
- ```bash
209
- asena create # Create new project
210
- asena init # Initialize config
211
- asena dev start # Development mode
212
- asena build # Production build
37
+ # Start development
38
+ asena dev start
213
39
  ```
214
40
 
215
- Full CLI documentation: [asena.sh/docs/cli](https://asena.sh/docs/cli/overview.html)
41
+ Visit [asena.sh/docs/get-started](https://asena.sh/docs/get-started) for detailed setup instructions.
216
42
 
217
43
  ## Performance
218
44
 
@@ -220,27 +46,17 @@ Built on Bun runtime for exceptional performance:
220
46
 
221
47
  | Framework | Requests/sec | Latency (avg) |
222
48
  |--------------------------|-----------------------|---------------|
223
- | **Asena + Ergenecore** | **294962.61** | **1.34ms** |
224
- | Hono | 266476.83 | 1.49ms |
225
- | **Asena + Hono-adapter** | **233182.35** | **1.70ms** |
226
- | NestJS (Bun) | 100975.20 | 3.92ms |
227
- | NestJS (Node) | 88083.22 | 5.33ms |
49
+ | **Asena + Ergenecore** | **294,962.61** | **1.34ms** |
50
+ | Hono | 266,476.83 | 1.49ms |
51
+ | **Asena + Hono-adapter** | **233,182.35** | **1.70ms** |
52
+ | NestJS (Bun) | 100,975.20 | 3.92ms |
53
+ | NestJS (Node) | 88,083.22 | 5.33ms |
228
54
 
229
55
  > Benchmark: 12 threads, 400 connections, 120s duration, Hello World endpoint
230
56
 
231
- ## Architecture
232
-
233
- ```
234
- lib/
235
- ├── ioc/ # IoC container & dependency injection
236
- ├── server/ # Core server & routing
237
- ├── adapter/ # HTTP adapter abstraction
238
- └── utils/ # Utilities & helpers
239
- ```
240
-
241
57
  ## Contributing
242
58
 
243
- Contributions welcome! Submit a Pull Request on [GitHub](https://github.com/AsenaJs/Asena).
59
+ Contributions are welcome! Submit a Pull Request on [GitHub](https://github.com/AsenaJs/Asena).
244
60
 
245
61
  ## License
246
62
 
@@ -16,6 +16,7 @@ import type { WebSocketData } from './types';
16
16
  * ```
17
17
  */
18
18
  export declare class AsenaSocket<T> implements ServerWebSocket<WebSocketData<T>> {
19
+ readonly subscriptions: string[];
19
20
  /**
20
21
  * The underlying Bun ServerWebSocket instance.
21
22
  * @private
@@ -14,6 +14,7 @@
14
14
  * ```
15
15
  */
16
16
  export class AsenaSocket {
17
+ subscriptions;
17
18
  /**
18
19
  * The underlying Bun ServerWebSocket instance.
19
20
  * @private
@@ -63,6 +64,7 @@ export class AsenaSocket {
63
64
  this._binaryType = ws.binaryType;
64
65
  this._readyState = ws.readyState;
65
66
  this._namespace = namespace;
67
+ this.subscriptions = ws.subscriptions;
66
68
  }
67
69
  /**
68
70
  * Sends data to the client. Automatically detects whether data is text or binary.
@@ -1 +1 @@
1
- {"version":3,"file":"AsenaSocket.js","sourceRoot":"","sources":["../../../../../lib/server/web/websocket/AsenaSocket.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AsenaSocket.js","sourceRoot":"","sources":["../../../../../lib/server/web/websocket/AsenaSocket.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Creates a mock object from a class type
3
+ * Automatically generates Bun mock functions for all class methods
4
+ *
5
+ * @param classType - Class to create mock from
6
+ * @returns Mock object with all methods mocked
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const UserServiceMock = createMockFromClass(UserService);
11
+ * // Returns:
12
+ * // {
13
+ * // createUser: mock(async () => null),
14
+ * // findByEmail: mock(async () => null),
15
+ * // findById: mock(async () => null),
16
+ * // }
17
+ *
18
+ * // Can configure mocks:
19
+ * UserServiceMock.createUser.mockResolvedValue({ id: '123', name: 'John' });
20
+ * ```
21
+ */
22
+ export declare function createMockFromClass(classType: any): any;
23
+ /**
24
+ * Creates a simple spy that tracks calls without mocking behavior
25
+ * Useful for partial mocking scenarios
26
+ *
27
+ * @returns Mock function that returns undefined
28
+ *
29
+ * @internal
30
+ */
31
+ export declare function createSimpleSpy(): any;
@@ -0,0 +1,103 @@
1
+ import { mock } from 'bun:test';
2
+ /**
3
+ * Detects all methods in a class (excluding constructor)
4
+ *
5
+ * @param classType - Class to inspect
6
+ * @returns Array of method names
7
+ *
8
+ * @internal
9
+ */
10
+ function detectClassMethods(classType) {
11
+ if (!classType?.prototype) {
12
+ return [];
13
+ }
14
+ const methods = [];
15
+ const prototype = classType.prototype;
16
+ // Get all property names from prototype
17
+ const propertyNames = Object.getOwnPropertyNames(prototype);
18
+ for (const name of propertyNames) {
19
+ // Skip constructor
20
+ if (name === 'constructor') {
21
+ continue;
22
+ }
23
+ // Check if it's a function
24
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
25
+ if (descriptor && typeof descriptor.value === 'function') {
26
+ methods.push(name);
27
+ }
28
+ }
29
+ return methods;
30
+ }
31
+ /**
32
+ * Checks if a value is mockable (is a class with prototype)
33
+ *
34
+ * @param classType - Value to check
35
+ * @returns True if value is a class
36
+ *
37
+ * @internal
38
+ */
39
+ function isMockable(classType) {
40
+ return (classType !== null &&
41
+ classType !== undefined &&
42
+ typeof classType === 'function' &&
43
+ classType.prototype !== undefined);
44
+ }
45
+ /**
46
+ * Creates a mock object from a class type
47
+ * Automatically generates Bun mock functions for all class methods
48
+ *
49
+ * @param classType - Class to create mock from
50
+ * @returns Mock object with all methods mocked
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * const UserServiceMock = createMockFromClass(UserService);
55
+ * // Returns:
56
+ * // {
57
+ * // createUser: mock(async () => null),
58
+ * // findByEmail: mock(async () => null),
59
+ * // findById: mock(async () => null),
60
+ * // }
61
+ *
62
+ * // Can configure mocks:
63
+ * UserServiceMock.createUser.mockResolvedValue({ id: '123', name: 'John' });
64
+ * ```
65
+ */
66
+ export function createMockFromClass(classType) {
67
+ // Handle non-mockable types - return empty object as placeholder
68
+ if (!isMockable(classType)) {
69
+ return {};
70
+ }
71
+ // Detect all methods in the class
72
+ const methods = detectClassMethods(classType);
73
+ // Create mock object
74
+ const mockObject = {};
75
+ // Create Bun mock for each method
76
+ for (const methodName of methods) {
77
+ // Check if method is async by inspecting the original
78
+ const originalMethod = classType.prototype[methodName];
79
+ const isAsync = originalMethod?.constructor?.name === 'AsyncFunction';
80
+ // Create mock with appropriate default return value
81
+ if (isAsync) {
82
+ // Async methods return Promise.resolve(null) by default
83
+ mockObject[methodName] = mock(async () => null);
84
+ }
85
+ else {
86
+ // Sync methods return undefined by default
87
+ mockObject[methodName] = mock(() => undefined);
88
+ }
89
+ }
90
+ return mockObject;
91
+ }
92
+ /**
93
+ * Creates a simple spy that tracks calls without mocking behavior
94
+ * Useful for partial mocking scenarios
95
+ *
96
+ * @returns Mock function that returns undefined
97
+ *
98
+ * @internal
99
+ */
100
+ export function createSimpleSpy() {
101
+ return mock(() => undefined);
102
+ }
103
+ //# sourceMappingURL=mockFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mockFactory.js","sourceRoot":"","sources":["../../../../lib/test/factory/mockFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAEhC;;;;;;;GAOG;AACH,SAAS,kBAAkB,CAAC,SAAc;IACxC,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS,CAAC;IAEtC,wCAAwC;IACxC,MAAM,aAAa,GAAG,MAAM,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAC;IAE5D,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACjC,mBAAmB;QACnB,IAAI,IAAI,KAAK,aAAa,EAAE,CAAC;YAC3B,SAAS;QACX,CAAC;QAED,2BAA2B;QAC3B,MAAM,UAAU,GAAG,MAAM,CAAC,wBAAwB,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACpE,IAAI,UAAU,IAAI,OAAO,UAAU,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;YACzD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,UAAU,CAAC,SAAc;IAChC,OAAO,CACL,SAAS,KAAK,IAAI;QAClB,SAAS,KAAK,SAAS;QACvB,OAAO,SAAS,KAAK,UAAU;QAC/B,SAAS,CAAC,SAAS,KAAK,SAAS,CAClC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,mBAAmB,CAAC,SAAc;IAChD,iEAAiE;IACjE,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,kCAAkC;IAClC,MAAM,OAAO,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;IAE9C,qBAAqB;IACrB,MAAM,UAAU,GAAwB,EAAE,CAAC;IAE3C,kCAAkC;IAClC,KAAK,MAAM,UAAU,IAAI,OAAO,EAAE,CAAC;QACjC,sDAAsD;QACtD,MAAM,cAAc,GAAG,SAAS,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QACvD,MAAM,OAAO,GAAG,cAAc,EAAE,WAAW,EAAE,IAAI,KAAK,eAAe,CAAC;QAEtE,oDAAoD;QACpD,IAAI,OAAO,EAAE,CAAC;YACZ,wDAAwD;YACxD,UAAU,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC;QAClD,CAAC;aAAM,CAAC;YACN,2CAA2C;YAC3C,UAAU,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { mockComponent, mockComponentAsync } from './mockComponent';
2
+ export type { MockComponentOptions, MockedComponent } from './types';
3
+ export { createMockFromClass } from './factory/mockFactory';
@@ -0,0 +1,3 @@
1
+ export { mockComponent, mockComponentAsync } from './mockComponent';
2
+ export { createMockFromClass } from './factory/mockFactory';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/test/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,52 @@
1
+ import type { FieldMetadata } from '../types';
2
+ /**
3
+ * Discovers all fields with @Inject decorator in a component
4
+ * Traverses prototype chain to support inheritance
5
+ *
6
+ * @param instance - Component instance to inspect
7
+ * @returns Array of field metadata containing field names, service names, and expressions
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const fields = discoverInjectedFields(new AuthService());
12
+ * // Returns: [
13
+ * // { fieldName: 'userService', serviceName: 'UserService', expression: undefined },
14
+ * // { fieldName: 'loginService', serviceName: 'LoginService', expression: (s) => s.login() }
15
+ * // ]
16
+ * ```
17
+ */
18
+ export declare function discoverInjectedFields(instance: any): FieldMetadata[];
19
+ /**
20
+ * Checks if a component has any injected dependencies
21
+ *
22
+ * @param ComponentClass - Component class to check
23
+ * @returns True if component has @Inject decorated fields
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * if (hasInjectedFields(AuthService)) {
28
+ * console.log('AuthService has dependencies');
29
+ * }
30
+ * ```
31
+ */
32
+ export declare function hasInjectedFields(ComponentClass: new (...args: any[]) => any): boolean;
33
+ /**
34
+ * Gets the service name for a specific field
35
+ *
36
+ * @param ComponentClass - Component class
37
+ * @param fieldName - Name of the field
38
+ * @returns Service name if field is injected, undefined otherwise
39
+ *
40
+ * @internal
41
+ */
42
+ export declare function getFieldServiceName(ComponentClass: new (...args: any[]) => any, fieldName: string): string | undefined;
43
+ /**
44
+ * Gets the expression function for a specific field
45
+ *
46
+ * @param ComponentClass - Component class
47
+ * @param fieldName - Name of the field
48
+ * @returns Expression function if defined, undefined otherwise
49
+ *
50
+ * @internal
51
+ */
52
+ export declare function getFieldExpression(ComponentClass: new (...args: any[]) => any, fieldName: string): ((service: any) => any) | undefined;
@@ -0,0 +1,93 @@
1
+ import { ComponentConstants } from '../../ioc';
2
+ import { getTypedMetadata, getOwnTypedMetadata } from '../../utils';
3
+ /**
4
+ * Discovers all fields with @Inject decorator in a component
5
+ * Traverses prototype chain to support inheritance
6
+ *
7
+ * @param instance - Component instance to inspect
8
+ * @returns Array of field metadata containing field names, service names, and expressions
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * const fields = discoverInjectedFields(new AuthService());
13
+ * // Returns: [
14
+ * // { fieldName: 'userService', serviceName: 'UserService', expression: undefined },
15
+ * // { fieldName: 'loginService', serviceName: 'LoginService', expression: (s) => s.login() }
16
+ * // ]
17
+ * ```
18
+ */
19
+ export function discoverInjectedFields(instance) {
20
+ const fields = [];
21
+ const processedFields = new Set();
22
+ // Traverse prototype chain (similar to how Container does it)
23
+ let currentPrototype = Object.getPrototypeOf(instance);
24
+ while (currentPrototype && currentPrototype !== Object.prototype) {
25
+ const constructor = currentPrototype.constructor;
26
+ // Get dependencies metadata for this level
27
+ const dependencies = getOwnTypedMetadata(ComponentConstants.DependencyKey, constructor);
28
+ // Get expressions metadata for this level
29
+ const expressions = getOwnTypedMetadata(ComponentConstants.ExpressionKey, constructor);
30
+ if (dependencies) {
31
+ // Process each dependency field
32
+ for (const [fieldName, serviceName] of Object.entries(dependencies)) {
33
+ // Skip if already processed (child class overrides)
34
+ if (processedFields.has(fieldName)) {
35
+ continue;
36
+ }
37
+ processedFields.add(fieldName);
38
+ fields.push({
39
+ fieldName,
40
+ serviceName,
41
+ expression: expressions?.[fieldName],
42
+ });
43
+ }
44
+ }
45
+ // Move up the prototype chain
46
+ currentPrototype = Object.getPrototypeOf(currentPrototype);
47
+ }
48
+ return fields;
49
+ }
50
+ /**
51
+ * Checks if a component has any injected dependencies
52
+ *
53
+ * @param ComponentClass - Component class to check
54
+ * @returns True if component has @Inject decorated fields
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * if (hasInjectedFields(AuthService)) {
59
+ * console.log('AuthService has dependencies');
60
+ * }
61
+ * ```
62
+ */
63
+ export function hasInjectedFields(ComponentClass) {
64
+ const dependencies = getTypedMetadata(ComponentConstants.DependencyKey, ComponentClass);
65
+ return dependencies !== undefined && Object.keys(dependencies).length > 0;
66
+ }
67
+ /**
68
+ * Gets the service name for a specific field
69
+ *
70
+ * @param ComponentClass - Component class
71
+ * @param fieldName - Name of the field
72
+ * @returns Service name if field is injected, undefined otherwise
73
+ *
74
+ * @internal
75
+ */
76
+ export function getFieldServiceName(ComponentClass, fieldName) {
77
+ const dependencies = getTypedMetadata(ComponentConstants.DependencyKey, ComponentClass);
78
+ return dependencies?.[fieldName];
79
+ }
80
+ /**
81
+ * Gets the expression function for a specific field
82
+ *
83
+ * @param ComponentClass - Component class
84
+ * @param fieldName - Name of the field
85
+ * @returns Expression function if defined, undefined otherwise
86
+ *
87
+ * @internal
88
+ */
89
+ export function getFieldExpression(ComponentClass, fieldName) {
90
+ const expressions = getTypedMetadata(ComponentConstants.ExpressionKey, ComponentClass);
91
+ return expressions?.[fieldName];
92
+ }
93
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../../lib/test/metadata/discovery.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEpE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAa;IAClD,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,MAAM,eAAe,GAAG,IAAI,GAAG,EAAU,CAAC;IAE1C,8DAA8D;IAC9D,IAAI,gBAAgB,GAAG,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;IAEvD,OAAO,gBAAgB,IAAI,gBAAgB,KAAK,MAAM,CAAC,SAAS,EAAE,CAAC;QACjE,MAAM,WAAW,GAAG,gBAAgB,CAAC,WAAW,CAAC;QAEjD,2CAA2C;QAC3C,MAAM,YAAY,GAAG,mBAAmB,CAAe,kBAAkB,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;QAEtG,0CAA0C;QAC1C,MAAM,WAAW,GAAG,mBAAmB,CAAc,kBAAkB,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;QAEpG,IAAI,YAAY,EAAE,CAAC;YACjB,gCAAgC;YAChC,KAAK,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;gBACpE,oDAAoD;gBACpD,IAAI,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;oBACnC,SAAS;gBACX,CAAC;gBAED,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBAE/B,MAAM,CAAC,IAAI,CAAC;oBACV,SAAS;oBACT,WAAW;oBACX,UAAU,EAAE,WAAW,EAAE,CAAC,SAAS,CAAC;iBACrC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,8BAA8B;QAC9B,gBAAgB,GAAG,MAAM,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;IAC7D,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,iBAAiB,CAAC,cAA2C;IAC3E,MAAM,YAAY,GAAG,gBAAgB,CAAe,kBAAkB,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IACtG,OAAO,YAAY,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;AAC5E,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,cAA2C,EAC3C,SAAiB;IAEjB,MAAM,YAAY,GAAG,gBAAgB,CAAe,kBAAkB,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IACtG,OAAO,YAAY,EAAE,CAAC,SAAS,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAChC,cAA2C,EAC3C,SAAiB;IAEjB,MAAM,WAAW,GAAG,gBAAgB,CAAc,kBAAkB,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IACpG,OAAO,WAAW,EAAE,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,75 @@
1
+ import type { MockComponentOptions, MockedComponent } from './types';
2
+ /**
3
+ * Creates a test instance of a component with all @Inject dependencies automatically mocked
4
+ *
5
+ * This is the main API for testing Asena components. It discovers all @Inject decorated
6
+ * fields, generates mock objects for them, and injects them into a new instance.
7
+ *
8
+ * @template T - Type of the component being mocked
9
+ * @param ComponentClass - Component class to instantiate and mock
10
+ * @param options - Optional configuration for mocking behavior
11
+ * @returns Object containing the component instance and all mocked dependencies
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { mockComponent } from '@asenajs/asena';
16
+ * import { describe, test, expect, beforeEach } from 'bun:test';
17
+ *
18
+ * describe('AuthService', () => {
19
+ * let authService: AuthService;
20
+ * let mocks: Record<string, any>;
21
+ *
22
+ * beforeEach(() => {
23
+ * const result = mockComponent(AuthService);
24
+ * authService = result.instance;
25
+ * mocks = result.mocks;
26
+ * });
27
+ *
28
+ * test('register should create user', async () => {
29
+ * mocks.userService.createUser.mockResolvedValue({
30
+ * id: 'user-123',
31
+ * name: 'John Doe'
32
+ * });
33
+ *
34
+ * const result = await authService.register('John', 'john@example.com', 'pass');
35
+ *
36
+ * expect(result.user.id).toBe('user-123');
37
+ * expect(mocks.userService.createUser).toHaveBeenCalledTimes(1);
38
+ * });
39
+ * });
40
+ * ```
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * // With options - selective mocking
45
+ * const { instance, mocks } = mockComponent(PaymentService, {
46
+ * injections: ['stripeClient'],
47
+ * overrides: {
48
+ * stripeClient: myCustomStripeMock
49
+ * },
50
+ * postConstruct: (inst) => {
51
+ * console.log('Test instance ready');
52
+ * }
53
+ * });
54
+ * ```
55
+ */
56
+ export declare function mockComponent<T extends object>(ComponentClass: new (...args: any[]) => T, options?: MockComponentOptions): MockedComponent<T>;
57
+ /**
58
+ * Async version of mockComponent for components with async postConstruct
59
+ * Use this when your postConstruct hook is async
60
+ *
61
+ * @template T - Type of the component being mocked
62
+ * @param ComponentClass - Component class to instantiate and mock
63
+ * @param options - Optional configuration for mocking behavior
64
+ * @returns Promise resolving to object containing the component instance and mocked dependencies
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * const { instance, mocks } = await mockComponentAsync(DatabaseService, {
69
+ * postConstruct: async (inst) => {
70
+ * await inst.initialize();
71
+ * }
72
+ * });
73
+ * ```
74
+ */
75
+ export declare function mockComponentAsync<T extends object>(ComponentClass: new (...args: any[]) => T, options?: MockComponentOptions): Promise<MockedComponent<T>>;
@@ -0,0 +1,115 @@
1
+ import { discoverInjectedFields } from './metadata/discovery';
2
+ import { createMockFromClass } from './factory/mockFactory';
3
+ /**
4
+ * Creates a test instance of a component with all @Inject dependencies automatically mocked
5
+ *
6
+ * This is the main API for testing Asena components. It discovers all @Inject decorated
7
+ * fields, generates mock objects for them, and injects them into a new instance.
8
+ *
9
+ * @template T - Type of the component being mocked
10
+ * @param ComponentClass - Component class to instantiate and mock
11
+ * @param options - Optional configuration for mocking behavior
12
+ * @returns Object containing the component instance and all mocked dependencies
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { mockComponent } from '@asenajs/asena';
17
+ * import { describe, test, expect, beforeEach } from 'bun:test';
18
+ *
19
+ * describe('AuthService', () => {
20
+ * let authService: AuthService;
21
+ * let mocks: Record<string, any>;
22
+ *
23
+ * beforeEach(() => {
24
+ * const result = mockComponent(AuthService);
25
+ * authService = result.instance;
26
+ * mocks = result.mocks;
27
+ * });
28
+ *
29
+ * test('register should create user', async () => {
30
+ * mocks.userService.createUser.mockResolvedValue({
31
+ * id: 'user-123',
32
+ * name: 'John Doe'
33
+ * });
34
+ *
35
+ * const result = await authService.register('John', 'john@example.com', 'pass');
36
+ *
37
+ * expect(result.user.id).toBe('user-123');
38
+ * expect(mocks.userService.createUser).toHaveBeenCalledTimes(1);
39
+ * });
40
+ * });
41
+ * ```
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * // With options - selective mocking
46
+ * const { instance, mocks } = mockComponent(PaymentService, {
47
+ * injections: ['stripeClient'],
48
+ * overrides: {
49
+ * stripeClient: myCustomStripeMock
50
+ * },
51
+ * postConstruct: (inst) => {
52
+ * console.log('Test instance ready');
53
+ * }
54
+ * });
55
+ * ```
56
+ */
57
+ export function mockComponent(ComponentClass, options = {}) {
58
+ if (!ComponentClass || typeof ComponentClass !== 'function') {
59
+ throw new Error(`mockComponent expects a class constructor, but received: ${typeof ComponentClass}`);
60
+ }
61
+ const instance = new ComponentClass();
62
+ const allInjectedFields = discoverInjectedFields(instance);
63
+ const fieldsToMock = options.injections
64
+ ? allInjectedFields.filter((field) => options.injections.includes(field.fieldName))
65
+ : allInjectedFields;
66
+ const mocks = {};
67
+ for (const field of fieldsToMock) {
68
+ const { fieldName, expression } = field;
69
+ let mockValue;
70
+ if (options.overrides?.[fieldName]) {
71
+ mockValue = options.overrides[fieldName];
72
+ }
73
+ else {
74
+ mockValue = createMockFromClass(undefined);
75
+ }
76
+ if (expression) {
77
+ mockValue = expression(mockValue);
78
+ }
79
+ instance[fieldName] = mockValue;
80
+ mocks[fieldName] = mockValue;
81
+ }
82
+ if (options.postConstruct) {
83
+ const result = options.postConstruct(instance);
84
+ if (result instanceof Promise) {
85
+ return result.then(() => ({ instance, mocks }));
86
+ }
87
+ }
88
+ return { instance, mocks };
89
+ }
90
+ /**
91
+ * Async version of mockComponent for components with async postConstruct
92
+ * Use this when your postConstruct hook is async
93
+ *
94
+ * @template T - Type of the component being mocked
95
+ * @param ComponentClass - Component class to instantiate and mock
96
+ * @param options - Optional configuration for mocking behavior
97
+ * @returns Promise resolving to object containing the component instance and mocked dependencies
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * const { instance, mocks } = await mockComponentAsync(DatabaseService, {
102
+ * postConstruct: async (inst) => {
103
+ * await inst.initialize();
104
+ * }
105
+ * });
106
+ * ```
107
+ */
108
+ export async function mockComponentAsync(ComponentClass, options = {}) {
109
+ const result = mockComponent(ComponentClass, options);
110
+ if (result instanceof Promise) {
111
+ return result;
112
+ }
113
+ return result;
114
+ }
115
+ //# sourceMappingURL=mockComponent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mockComponent.js","sourceRoot":"","sources":["../../../lib/test/mockComponent.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,MAAM,UAAU,aAAa,CAC3B,cAAyC,EACzC,UAAgC,EAAE;IAElC,IAAI,CAAC,cAAc,IAAI,OAAO,cAAc,KAAK,UAAU,EAAE,CAAC;QAC5D,MAAM,IAAI,KAAK,CAAC,4DAA4D,OAAO,cAAc,EAAE,CAAC,CAAC;IACvG,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,cAAc,EAAE,CAAC;IACtC,MAAM,iBAAiB,GAAG,sBAAsB,CAAC,QAAQ,CAAC,CAAC;IAE3D,MAAM,YAAY,GAAG,OAAO,CAAC,UAAU;QACrC,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACnF,CAAC,CAAC,iBAAiB,CAAC;IAEtB,MAAM,KAAK,GAAwB,EAAE,CAAC;IAEtC,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;QACjC,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC;QAExC,IAAI,SAAc,CAAC;QAEnB,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC;YACnC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QAC3C,CAAC;aAAM,CAAC;YACN,SAAS,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;QAC7C,CAAC;QAED,IAAI,UAAU,EAAE,CAAC;YACf,SAAS,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;QACpC,CAAC;QAEA,QAAgB,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;QACzC,KAAK,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;IAC/B,CAAC;IAED,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAE/C,IAAI,MAAM,YAAY,OAAO,EAAE,CAAC;YAC9B,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAQ,CAAC;QACzD,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AAC7B,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,cAAyC,EACzC,UAAgC,EAAE;IAElC,MAAM,MAAM,GAAG,aAAa,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;IAEtD,IAAI,MAAM,YAAY,OAAO,EAAE,CAAC;QAC9B,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Options for configuring component mocking behavior
3
+ */
4
+ export interface MockComponentOptions {
5
+ /**
6
+ * Only mock specific injected fields (optional)
7
+ * If not provided, all @Inject decorated fields will be mocked
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * mockComponent(AuthService, {
12
+ * injections: ['userService'] // Only mock userService
13
+ * })
14
+ * ```
15
+ */
16
+ injections?: string[];
17
+ /**
18
+ * Provide custom mocks instead of auto-generated ones (optional)
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * mockComponent(AuthService, {
23
+ * overrides: {
24
+ * userService: myCustomMock
25
+ * }
26
+ * })
27
+ * ```
28
+ */
29
+ overrides?: Record<string, any>;
30
+ /**
31
+ * Lifecycle hook called after dependency injection (optional)
32
+ * Can be async
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * mockComponent(AuthService, {
37
+ * postConstruct: (instance) => {
38
+ * // Setup after injection
39
+ * }
40
+ * })
41
+ * ```
42
+ */
43
+ postConstruct?: (instance: any) => void | Promise<void>;
44
+ }
45
+ /**
46
+ * Result of mockComponent function
47
+ */
48
+ export interface MockedComponent<T> {
49
+ /**
50
+ * Component instance with mocked dependencies
51
+ */
52
+ instance: T;
53
+ /**
54
+ * Object containing all mocked dependencies
55
+ * Keys are field names, values are mock objects
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * const { instance, mocks } = mockComponent(AuthService);
60
+ * mocks.userService.createUser.mockResolvedValue({...});
61
+ * ```
62
+ */
63
+ mocks: Record<string, any>;
64
+ }
65
+ /**
66
+ * Internal type for field metadata discovery
67
+ * @internal
68
+ */
69
+ export interface FieldMetadata {
70
+ /**
71
+ * Name of the field
72
+ */
73
+ fieldName: string;
74
+ /**
75
+ * Service name (from @Inject decorator)
76
+ */
77
+ serviceName: string;
78
+ /**
79
+ * Optional expression function for transformation
80
+ */
81
+ expression?: (service: any) => any;
82
+ }
83
+ /**
84
+ * Type alias for mock factory function
85
+ * @internal
86
+ */
87
+ export type MockFactory = (classType: any) => any;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../lib/test/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asenajs/asena",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "author": "LibirSoft",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,13 +14,13 @@
14
14
  "devDependencies": {
15
15
  "@changesets/cli": "^2.29.7",
16
16
  "@types/bun": "latest",
17
- "@typescript-eslint/eslint-plugin": "^8.46.2",
18
- "@typescript-eslint/parser": "^8.46.2",
19
- "eslint": "^9.39.0",
17
+ "@typescript-eslint/eslint-plugin": "^8.46.4",
18
+ "@typescript-eslint/parser": "^8.46.4",
19
+ "eslint": "^9.39.1",
20
20
  "eslint-config-prettier": "^9.1.2",
21
21
  "prettier": "^3.6.2",
22
22
  "typescript": "^5.9.3",
23
- "typescript-eslint": "^8.46.2"
23
+ "typescript-eslint": "^8.46.4"
24
24
  },
25
25
  "exports": {
26
26
  ".": {
@@ -86,6 +86,10 @@
86
86
  "./utlis": {
87
87
  "import": "./dist/lib/utils/index.js",
88
88
  "types": "./dist/lib/utils/index.d.ts"
89
+ },
90
+ "./test": {
91
+ "import": "./dist/lib/test/index.js",
92
+ "types": "./dist/lib/test/index.d.ts"
89
93
  }
90
94
  },
91
95
  "description": "Asena is a spring like framework for building web applications built with bun",