Skip to content

Commit b387417

Browse files
authored
Merge pull request #42 from AsenaJs/release-v0.6.0
Release v0.6.0
2 parents 6caf1b7 + 26db534 commit b387417

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+3823
-180
lines changed

CHANGELOG.md

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

3+
## 0.6.0
4+
5+
### Minor Changes
6+
7+
- d1fd783: ## Event System
8+
9+
Added Spring-like event-driven architecture.
10+
11+
```typescript
12+
@EventService({ prefix: 'user' })
13+
export class UserEventService {
14+
@On('created')
15+
handleUserCreated(eventName: string, data: any) {
16+
console.log('User created:', data);
17+
}
18+
19+
@On('*.error') // Wildcard support
20+
handleErrors(eventName: string, data: any) {
21+
console.error('Error:', eventName);
22+
}
23+
}
24+
```
25+
26+
**Features:**
27+
- `@EventService` and `@On` decorators
28+
- Wildcard pattern support (`user.*`, `*.error`)
29+
- Fire-and-forget pattern
30+
- Async/sync handler support
31+
- Error isolation
32+
- Event chaining
33+
34+
**Exports:**
35+
36+
```typescript
37+
import { EventService, On } from '@asenajs/asena/decorators';
38+
import { EventEmitter } from '@asenajs/asena/event';
39+
```
40+
41+
## Breaking Changes (Adapter Developers Only)
42+
43+
**WebSocket Refactoring - Circular Dependency Removal**
44+
45+
`AsenaSocket` no longer holds a reference to `AsenaWebSocketService`.
46+
47+
**Changes:**
48+
- `AsenaSocket` constructor: removed `websocketService` parameter, added `namespace: string`
49+
- Removed `cleanup()` method
50+
- Removed manual `rooms` management (using Bun native pub/sub)
51+
- Removed `getSocketsByRoom()` method from `AsenaWebSocketService`
52+
53+
**Impact:**
54+
- ⚠️ HTTP/WebSocket adapter developers must update their code
55+
- ✅ End users are not affected
56+
57+
**For adapter developers:**
58+
59+
```typescript
60+
// Before
61+
new AsenaSocket(ws, websocketService);
62+
63+
// After
64+
new AsenaSocket(ws, namespace);
65+
```
66+
67+
### Patch Changes
68+
69+
- ## Windows Path Compatibility Fix
70+
71+
Fixed route path joining issue on Windows by normalizing backslashes to forward slashes.
72+
73+
**Issue:** `path.join()` was using Windows backslashes (`\`) for route paths, causing adapter registration failures on Windows.
74+
75+
**Solution:** Route paths are now normalized using `.replace(/\\/g, '/')` to ensure cross-platform compatibility.
76+
77+
**Impact:**
78+
- ✅ Routes now work correctly on Windows
79+
- ✅ No breaking changes for users or adapter developers
80+
- ✅ Test coverage added for path normalization
81+
82+
**Related:** Fixes #41
83+
384
## 0.5.0
485

586
### Minor Changes

bun.lock

Lines changed: 11 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bunfig.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ coverage = true
44

55
coverageSkipTestFiles = true
66

7+
coveragePathIgnorePatterns = [
8+
"test/utils/*.ts",
9+
]
10+
711

812

913
[test.reporter]

index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
export { AsenaServerFactory, AsenaServer } from './lib/server';
2-
export { ICoreServiceNames, ComponentConstants } from './lib/ioc';

lib/ioc/CoreContainer.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ 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 { PrepareEventService } from '../server/src/services/PrepareEventService';
1112
import { Ulak } from '../server/messaging/Ulak';
13+
import { EventDispatchService } from '../server/event/EventDispatchService';
14+
import { EventEmitter } from '../server/event/EventEmitter';
1215

1316
/**
1417
* @description CoreContainer manages framework-level services
@@ -123,12 +126,17 @@ export class CoreContainer {
123126
// Internal messaging system (must be registered before prepare services)
124127
{ name: ICoreServiceNames.__ULAK__, Class: Ulak },
125128

129+
// Event system (must be registered before PrepareEventService)
130+
{ name: ICoreServiceNames.EVENT_DISPATCH_SERVICE, Class: EventDispatchService },
131+
{ name: ICoreServiceNames.EVENT_EMITTER, Class: EventEmitter },
132+
126133
// Prepare services
127134
{ name: ICoreServiceNames.PREPARE_MIDDLEWARE_SERVICE, Class: PrepareMiddlewareService },
128135
{ name: ICoreServiceNames.PREPARE_CONFIG_SERVICE, Class: PrepareConfigService },
129136
{ name: ICoreServiceNames.PREPARE_WEBSOCKET_SERVICE, Class: PrepareWebsocketService },
130137
{ name: ICoreServiceNames.PREPARE_VALIDATOR_SERVICE, Class: PrepareValidatorService },
131138
{ name: ICoreServiceNames.PREPARE_STATIC_SERVE_CONFIG_SERVICE, Class: PrepareStaticServeConfigService },
139+
{ name: ICoreServiceNames.PREPARE_EVENT_SERVICE, Class: PrepareEventService },
132140
];
133141

134142
for (const service of services) {

lib/ioc/constants/ComponentConstants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,9 @@ export class ComponentConstants {
5656

5757
// Static Serve specific
5858
public static readonly StaticServeRootKey = Symbol('staticServe:root');
59+
60+
// Event specific
61+
public static readonly EventHandlersKey = Symbol('event:handlers');
62+
63+
public static readonly EventPrefixKey = Symbol('event:prefix');
5964
}

lib/ioc/types/ICoreServiceNamesType.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@ export enum ICoreServiceNames {
1313
// Internal Messaging Services (Phase 5)
1414
__ULAK__ = '__Ulak__',
1515

16+
// Event Services (Phase 5)
17+
EVENT_EMITTER = 'EventEmitter',
18+
EVENT_DISPATCH_SERVICE = 'EventDispatchService',
19+
1620
// Prepare Services (Phase 5)
1721
PREPARE_MIDDLEWARE_SERVICE = 'PrepareMiddlewareService',
1822
PREPARE_CONFIG_SERVICE = 'PrepareConfigService',
1923
PREPARE_WEBSOCKET_SERVICE = 'PrepareWebsocketService',
2024
PREPARE_VALIDATOR_SERVICE = 'PrepareValidatorService',
2125
PREPARE_STATIC_SERVE_CONFIG_SERVICE = 'PrepareStaticServeConfigService',
26+
PREPARE_EVENT_SERVICE = 'PrepareEventService',
2227

2328
// Factory Services (Phase 6+)
2429
CORE_CONTAINER = 'CoreContainer',

lib/ioc/types/decorators/ComponentType.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ export enum ComponentType {
66
MIDDLEWARE = 'MIDDLEWARE',
77
WEBSOCKET = 'WEBSOCKET',
88
CONFIG = 'CONFIG',
9+
EVENT = 'EVENT',
910
}

lib/server/AsenaServer.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import type { PrepareValidatorService } from './src/services/PrepareValidatorSer
2929
import type { PrepareStaticServeConfigService } from './src/services/PrepareStaticServeConfigService';
3030
import { Inject, PostConstruct } from '../ioc/component';
3131
import type { GlobalMiddlewareConfig, GlobalMiddlewareEntry } from './config/AsenaConfig';
32+
import type { PrepareEventService } from './src/services/PrepareEventService';
3233

3334
/**
3435
* @description AsenaServer - Main server class for Asena framework
@@ -62,6 +63,9 @@ export class AsenaServer<A extends AsenaAdapter<any, any>> implements ICoreServi
6263
@Inject(ICoreServiceNames.PREPARE_STATIC_SERVE_CONFIG_SERVICE)
6364
private prepareStaticServeConfigService!: PrepareStaticServeConfigService;
6465

66+
@Inject(ICoreServiceNames.PREPARE_EVENT_SERVICE)
67+
private prepareEventService: PrepareEventService;
68+
6569
// Instance state
6670
private _port!: number;
6771

@@ -91,6 +95,7 @@ export class AsenaServer<A extends AsenaAdapter<any, any>> implements ICoreServi
9195
// Phase 7: Application setup
9296
this._coreContainer.setPhase(CoreBootstrapPhase.APPLICATION_SETUP);
9397
await this.prepareConfigs();
98+
await this.prepareEventService.prepare();
9499
await this.initializeControllers();
95100
await this.prepareWebSocket();
96101

@@ -138,7 +143,7 @@ export class AsenaServer<A extends AsenaAdapter<any, any>> implements ICoreServi
138143
await this.prepareTopMiddlewares({ controller, routePath });
139144

140145
for (const [name, params] of Object.entries(routes)) {
141-
const lastPath = path.join(`${routePath}/`, params.path);
146+
const lastPath = path.join(`${routePath}/`, params.path).replace(/\\/g, '/');
142147

143148
const middlewares = await this.prepareRouteMiddleware(params);
144149
const validatorInstance = await this.prepareValidator(params.validator);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { ComponentConstants, ComponentType } from '../../../ioc';
2+
import { defineComponent } from '../../../ioc/component';
3+
import { defineTypedMetadata } from '../../../utils';
4+
import type { EventServiceParams } from '../../event';
5+
6+
/**
7+
* @description EventService decorator - Marks a class as an event service
8+
*
9+
* Event services contain @On decorated methods that handle events.
10+
*
11+
* @param params - Optional configuration or prefix string
12+
*
13+
* @example
14+
* // With prefix
15+
* @EventService({ prefix: 'user' })
16+
* export class UserEventService {
17+
* @On('created') // Handles 'user.created' event
18+
* handleUserCreated(eventName: string, data: any) {
19+
* console.log('User created:', data);
20+
* }
21+
* }
22+
*
23+
* @example
24+
* // Without prefix
25+
* @EventService()
26+
* export class GlobalEventService {
27+
* @On('app.started')
28+
* handleAppStarted() {
29+
* console.log('App started');
30+
* }
31+
* }
32+
*
33+
* @example
34+
* // Shorthand with prefix string
35+
* @EventService('download')
36+
* export class DownloadEventService {
37+
* @On('complete') // Handles 'download.complete'
38+
* handleComplete(eventName: string, data: any) { }
39+
* }
40+
*/
41+
export const EventService = (params?: EventServiceParams | string): ClassDecorator => {
42+
// Normalize params - support both object and string
43+
const _params =
44+
typeof params === 'string' ? { prefix: params, name: undefined } : params || { prefix: undefined, name: undefined };
45+
46+
return defineComponent(ComponentType.EVENT, _params, (target) => {
47+
// Store prefix metadata for PrepareEventService
48+
defineTypedMetadata<string>(ComponentConstants.EventPrefixKey, _params.prefix || '', target);
49+
});
50+
};

0 commit comments

Comments
 (0)