Skip to content

Commit 1b8bcf0

Browse files
authored
Merge pull request #34 from AsenaJs/release-v0.5.0
Release v0.5.0
2 parents bce547b + d1531fe commit 1b8bcf0

35 files changed

+1592
-124
lines changed

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,9 @@
22
node_modules
33
dist
44
build
5-
.env
5+
roadmaps
6+
.env
7+
/.claude/
8+
/.cursor/
9+
/junit.xml
10+
/CLAUDE.md

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,31 @@
11
# @asenajs/asena
22

3+
## 0.5.0
4+
5+
### Minor Changes
6+
7+
- # Ulak WebSocket Messaging System
8+
9+
Add centralized WebSocket message broker (Ulak) that eliminates circular dependencies in WebSocket communication.
10+
11+
**Features:**
12+
- Namespace-based routing for WebSocket messages
13+
- Type-safe messaging with full TypeScript support
14+
- Bulk operations for efficient multi-namespace broadcasting
15+
- Comprehensive error handling with UlakError
16+
- Pattern matching for exact and wildcard namespaces
17+
18+
**Breaking Changes:**
19+
- `AsenaWebSocketServer` constructor no longer accepts `topic` parameter
20+
- `AsenaWebSocketServer.websocketCount` getter removed
21+
- Custom adapter implementations need to update their constructor calls
22+
23+
**Bug Fixes:**
24+
- Fix IocEngine empty dependency error in non-minified codebases
25+
26+
**Enhanced Features:**
27+
- @Inject decorator now supports tuple injection pattern for advanced DI scenarios
28+
329
## 0.4.0
430

531
### Minor Changes

lib/ioc/Container.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { ComponentType, ContainerService, Dependencies, Expressions, Strate
33
import { ComponentConstants } from './constants';
44
import { getOwnTypedMetadata, getTypedMetadata } from '../utils/typedMetadata';
55
import { CircularDependencyDetector } from './CircularDependencyDetector';
6+
import { CORE_SERVICE } from './decorators/CoreService';
67

78
export class Container {
89

@@ -259,15 +260,36 @@ export class Container {
259260
}
260261
}
261262

263+
/**
264+
* @description Checks if a class is a framework class (CoreService)
265+
* Framework classes can traverse the prototype chain even if they contain native code patterns
266+
* @param {any} cls - The class to check
267+
* @returns {boolean} True if the class is a framework class
268+
*/
269+
private isFrameworkClass(cls: any): boolean {
270+
if (!cls || cls === Object.prototype) {
271+
return false;
272+
}
273+
274+
// Check if class is marked as a CoreService
275+
const isCoreService = getTypedMetadata<boolean>(CORE_SERVICE, cls);
276+
277+
return Boolean(isCoreService);
278+
}
279+
262280
private getPrototypeChain(Class: any): any[] {
263281
const chain: any[] = [];
264282
let currentClass = Class;
265283

266-
while (
267-
currentClass &&
268-
currentClass !== Object.prototype &&
269-
(currentClass.name === 'IocEngine' || !currentClass.toString().includes('[native code]'))
270-
) {
284+
while (currentClass && currentClass !== Object.prototype) {
285+
const classSource = currentClass.toString();
286+
const isNativeCode = classSource.includes('[native code]');
287+
288+
// Stop at native code unless it's a framework class
289+
if (isNativeCode && !this.isFrameworkClass(currentClass)) {
290+
break;
291+
}
292+
271293
chain.push(currentClass);
272294
currentClass = Object.getPrototypeOf(currentClass);
273295
}

lib/ioc/CoreContainer.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { PrepareConfigService } from '../server/src/services/PrepareConfigServic
88
import { PrepareWebsocketService } from '../server/src/services/PrepareWebsocketService';
99
import { PrepareValidatorService } from '../server/src/services/PrepareValidatorService';
1010
import { PrepareStaticServeConfigService } from '../server/src/services/PrepareStaticServeConfigService';
11+
import { Ulak } from '../server/messaging/Ulak';
1112

1213
/**
1314
* @description CoreContainer manages framework-level services
@@ -114,12 +115,16 @@ export class CoreContainer {
114115
}
115116

116117
/**
117-
* @description Phase 5: Register all Prepare Services
118+
* @description Phase 5: Register all Prepare Services and Internal Services
118119
* They will inject Container and Logger automatically
119120
* @returns {Promise<void>}
120121
*/
121122
private async registerPrepareServices(): Promise<void> {
122123
const services = [
124+
// Internal messaging system (must be registered before prepare services)
125+
{ name: ICoreServiceNames.__ULAK__, Class: Ulak },
126+
127+
// Prepare services
123128
{ name: ICoreServiceNames.PREPARE_MIDDLEWARE_SERVICE, Class: PrepareMiddlewareService },
124129
{ name: ICoreServiceNames.PREPARE_CONFIG_SERVICE, Class: PrepareConfigService },
125130
{ name: ICoreServiceNames.PREPARE_WEBSOCKET_SERVICE, Class: PrepareWebsocketService },

lib/ioc/component/decorators/Inject.ts

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,70 @@ import { defineTypedMetadata, getOwnTypedMetadata, getTypedMetadata } from '../.
66
/**
77
* Property decorator to inject a dependency.
88
*
9-
* @param {Class } Injection - The class of component to inject.
10-
* @param expression - The expression to evaluate on the injected class.
11-
* @returns {PropertyDecorator} - The property decorator function.
9+
* Supports three syntaxes:
10+
* 1. Class injection: `@Inject(UserService)`
11+
* 2. String injection with expression: `@Inject('UserService', (s) => s.getUsers())`
12+
* 3. Tuple injection (helper pattern): `@Inject(ulak('/chat'))` where ulak() returns [serviceName, expression]
13+
*
14+
* @param {Class | string | readonly [string, (injectedClass: any) => any]} Injection - The dependency to inject
15+
* @param expression - Optional expression to evaluate on the injected class (only used with Class or string)
16+
* @returns {PropertyDecorator} - The property decorator function
17+
*
18+
* @example
19+
* ```typescript
20+
* // Class injection
21+
* @Inject(UserService)
22+
* private userService: UserService;
23+
*
24+
* // String injection with expression
25+
* @Inject('UserService', (s) => s.getAllUsers())
26+
* private users: User[];
27+
*
28+
* // Tuple injection (helper pattern)
29+
* @Inject(ulak('/chat'))
30+
* private chat: Ulak.NameSpace<'/chat'>;
31+
* ```
1232
*/
13-
export const Inject = (Injection: Class | string, expression?: (injectedClass: any) => any): PropertyDecorator => {
33+
export const Inject = (
34+
Injection: Class | string | readonly [string | Class, (injectedClass: any) => any],
35+
expression?: (injectedClass: any) => any,
36+
): PropertyDecorator => {
1437
return (target: object, propertyKey: string): void => {
38+
let dependencyName: string;
39+
let resolvedExpression: ((injectedClass: any) => any) | undefined = expression;
40+
41+
// Check if Injection is a tuple [serviceName, expression]
42+
if (Array.isArray(Injection) && Injection.length === 2) {
43+
const [name, tupleExpression] = Injection;
44+
45+
// Check Name type, it can be a Class or String
46+
dependencyName = typeof name === 'string' ? name : getTypedMetadata<string>(ComponentConstants.NameKey, name);
47+
48+
resolvedExpression = tupleExpression;
49+
} else if (typeof Injection === 'string') {
50+
dependencyName = Injection;
51+
} else {
52+
// Injection is a Class
53+
dependencyName = getTypedMetadata<string>(ComponentConstants.NameKey, Injection);
54+
}
55+
56+
// Store dependency
1557
const dependencies: Dependencies =
1658
getOwnTypedMetadata<Dependencies>(ComponentConstants.DependencyKey, target.constructor) || {};
1759

1860
if (!dependencies[propertyKey]) {
19-
dependencies[propertyKey] =
20-
typeof Injection === 'string' ? Injection : getTypedMetadata<string>(ComponentConstants.NameKey, Injection);
61+
dependencies[propertyKey] = dependencyName;
2162
}
2263

2364
defineTypedMetadata<Dependencies>(ComponentConstants.DependencyKey, dependencies, target.constructor);
2465

25-
if (expression) {
66+
// Store expression if provided
67+
if (resolvedExpression) {
2668
const expressions: Expressions =
2769
getOwnTypedMetadata<Expressions>(ComponentConstants.ExpressionKey, target.constructor) || {};
2870

2971
if (!expressions[propertyKey]) {
30-
expressions[propertyKey] = expression;
72+
expressions[propertyKey] = resolvedExpression;
3173
}
3274

3375
defineTypedMetadata<Expressions>(ComponentConstants.ExpressionKey, expressions, target.constructor);

lib/ioc/types/ICoreServiceNamesType.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ export enum ICoreServiceNames {
1010
IOC_ENGINE = 'IocEngine',
1111
ASENA_ADAPTER = 'AsenaAdapter',
1212

13+
// Internal Messaging Services (Phase 5)
14+
__ULAK__ = '__Ulak__',
15+
1316
// Prepare Services (Phase 5)
1417
PREPARE_MIDDLEWARE_SERVICE = 'PrepareMiddlewareService',
1518
PREPARE_CONFIG_SERVICE = 'PrepareConfigService',

lib/server/AsenaServer.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ import * as path from 'node:path';
2020
import type { MiddlewareClass, ValidatorClass } from './web/middleware';
2121
import { ComponentConstants } from '../ioc/constants';
2222
import * as bun from 'bun';
23-
import { green, type ServerLogger, yellow } from '../logger';
23+
import { blue, green, type ServerLogger, yellow } from '../logger';
2424
import { getOwnTypedMetadata, getTypedMetadata } from '../utils/typedMetadata';
2525
import type { PrepareMiddlewareService } from './src/services/PrepareMiddlewareService';
2626
import type { PrepareConfigService } from './src/services/PrepareConfigService';
2727
import type { PrepareWebsocketService } from './src/services/PrepareWebsocketService';
2828
import type { PrepareValidatorService } from './src/services/PrepareValidatorService';
2929
import type { PrepareStaticServeConfigService } from './src/services/PrepareStaticServeConfigService';
30-
import { Inject } from '../ioc/component';
31-
import type { GlobalMiddlewareEntry, GlobalMiddlewareConfig } from './config/AsenaConfig';
30+
import { Inject, PostConstruct } from '../ioc/component';
31+
import type { GlobalMiddlewareConfig, GlobalMiddlewareEntry } from './config/AsenaConfig';
3232

3333
/**
3434
* @description AsenaServer - Main server class for Asena framework
@@ -74,14 +74,9 @@ export class AsenaServer<A extends AsenaAdapter<any, any>> implements ICoreServi
7474
* @description Lifecycle hook - called after dependencies are injected
7575
* @returns {void}
7676
*/
77+
@PostConstruct()
7778
public onInit(): void {
78-
this._logger.info(`
79-
___ _____ ______ _ __ ___
80-
/ | / ___/ / ____// | / // |
81-
/ /| | \\__ \\ / __/ / |/ // /| |
82-
/ ___ | ___/ // /___ / /| // ___ |
83-
/_/ |_|/____//_____//_/ |_//_/ |_|
84-
`);
79+
this._logger.info(`${blue('[AsenaServer]')} is initialized`);
8580
}
8681

8782
/**

lib/server/AsenaServerFactory.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ export class AsenaServerFactory {
3535
): Promise<AsenaServer<A>> {
3636
const { adapter, logger, port, components, gc } = options;
3737

38+
logger.info(`
39+
___ _____ ______ _ __ ___
40+
/ | / ___/ / ____// | / // |
41+
/ /| | \\__ \\ / __/ / |/ // /| |
42+
/ ___ | ___/ // /___ / /| // ___ |
43+
/_/ |_|/____//_____//_/ |_//_/ |_|
44+
------------------------------------------------------------
45+
`);
46+
3847
// Read config file
3948
const config = await readConfigFile();
4049

lib/server/decorators/components/Controller.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { ComponentType, type ControllerParams } from '../../../ioc';
1+
import { ComponentConstants, ComponentType, type ControllerParams } from '../../../ioc';
22
import { defineComponent } from '../../../ioc/component';
3-
import { ComponentConstants } from '../../../ioc';
43
import { defineMiddleware } from '../../web/helper';
54
import { defineTypedMetadata } from '../../../utils/typedMetadata';
65

lib/server/decorators/components/Middleware.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { MiddlewareParams } from '../../../ioc/types/decorators/MiddlewareParams';
22
import { defineComponent } from '../../../ioc/component';
3-
import { ComponentType } from '../../../ioc';
4-
import { ComponentConstants } from '../../../ioc';
3+
import { ComponentConstants, ComponentType } from '../../../ioc';
54
import { defineTypedMetadata } from '../../../utils/typedMetadata';
65
import { VALIDATOR_METHODS } from '../../../adapter';
76

0 commit comments

Comments
 (0)