@fluojs/event-bus 1.0.0-beta.5 → 1.0.0-beta.7
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.ko.md +11 -11
- package/README.md +11 -11
- package/dist/service.d.ts +13 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +110 -11
- package/dist/status.d.ts +2 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +4 -2
- package/dist/types.d.ts +6 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
package/README.ko.md
CHANGED
|
@@ -58,24 +58,24 @@ export class NotificationService {
|
|
|
58
58
|
import { Module, Inject } from '@fluojs/core';
|
|
59
59
|
import { EventBusModule, EventBusLifecycleService } from '@fluojs/event-bus';
|
|
60
60
|
|
|
61
|
-
@
|
|
62
|
-
imports: [EventBusModule.forRoot()],
|
|
63
|
-
providers: [NotificationService],
|
|
64
|
-
})
|
|
65
|
-
export class AppModule {}
|
|
66
|
-
|
|
61
|
+
@Inject(EventBusLifecycleService)
|
|
67
62
|
export class UserService {
|
|
68
|
-
|
|
69
|
-
private readonly eventBus: EventBusLifecycleService;
|
|
63
|
+
constructor(private readonly eventBus: EventBusLifecycleService) {}
|
|
70
64
|
|
|
71
65
|
async signUp(email: string) {
|
|
72
66
|
// 사용자 저장 로직...
|
|
73
67
|
await this.eventBus.publish(new UserSignedUpEvent(email));
|
|
74
68
|
}
|
|
75
69
|
}
|
|
70
|
+
|
|
71
|
+
@Module({
|
|
72
|
+
imports: [EventBusModule.forRoot()],
|
|
73
|
+
providers: [NotificationService, UserService],
|
|
74
|
+
})
|
|
75
|
+
export class AppModule {}
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
`publish(event, options?)`는 `signal`, `timeoutMs`, `waitForHandlers`를 지원합니다. `waitForHandlers`의 기본값은 `true`이며, `false`로 설정하면 publish가 즉시 반환되고 timeout bound를 적용하지 않습니다.
|
|
78
|
+
`publish(event, options?)`는 `signal`, `timeoutMs`, `waitForHandlers`를 지원합니다. `waitForHandlers`의 기본값은 `true`이며, 기다리는 로컬 핸들러와 기다리는 트랜스포트 publish는 동일한 timeout 및 cancellation bound를 공유합니다. `waitForHandlers`를 `false`로 설정하면 publish가 즉시 반환되고 timeout bound를 적용하지 않습니다. Shutdown 중에는 이벤트 버스가 진행 중인 awaited publish 및 inbound transport handler 작업을 drain한 뒤 트랜스포트를 닫고, lifecycle이 stopping에 진입한 뒤의 새 publish 호출과 shutdown 시작 뒤 도착한 inbound transport callback은 무시합니다. Shutdown drain은 기본값이 5000ms인 `EventBusModule.forRoot({ shutdown: { drainTimeoutMs } })`로 제한됩니다. 활성 dispatch 작업이 이 bound 이후에도 멈춰 있으면 bus는 degraded status diagnostic을 기록하고 경고를 남긴 뒤, 애플리케이션 close를 무기한 hang시키지 않고 transport cleanup을 계속합니다.
|
|
79
79
|
|
|
80
80
|
## 일반적인 패턴
|
|
81
81
|
|
|
@@ -104,7 +104,7 @@ class UserRegisteredEvent {
|
|
|
104
104
|
}
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
핸들러는 imported module의 singleton provider와 controller에서 발견됩니다. 각 핸들러는 격리된 clone payload를 받으며, class inheritance는 `instanceof` 매칭으로 지원됩니다.
|
|
107
|
+
핸들러는 imported module의 singleton provider와 controller에서 발견됩니다. 각 핸들러는 격리된 clone payload를 받으며, class inheritance는 `instanceof` 매칭으로 지원됩니다. 외부 트랜스포트를 구성하면 subclass event publish는 publisher process에 해당 타입의 local handler가 없더라도 subclass channel과 prototype chain의 모든 inherited event channel로 fan-out됩니다. Subclass가 직접 `static eventKey`를 선언한 경우에만 그 값을 사용하며, 그렇지 않으면 subclass channel은 class name을 유지하고 base class는 자신의 stable key를 유지합니다.
|
|
108
108
|
|
|
109
109
|
## 공개 API 개요
|
|
110
110
|
|
|
@@ -120,7 +120,7 @@ class UserRegisteredEvent {
|
|
|
120
120
|
- `EventBus`, `EventPublishOptions`, `EventBusModuleOptions`, `EventType`: 발행, 기본값, 트랜스포트, 안정적인 이벤트 키를 위한 타입 전용 계약입니다.
|
|
121
121
|
- `EventBusLifecycleState`, `EventBusStatusAdapterInput`, `EventBusPlatformStatusSnapshot`: status snapshot 계약입니다.
|
|
122
122
|
|
|
123
|
-
Transport bootstrap은 unique event channel마다 한 번만 subscribe합니다. `eventKey`가 있으면 transport channel 이름을 제어합니다. 잘못된 JSON transport message는 무시됩니다.
|
|
123
|
+
Transport bootstrap은 unique event channel마다 한 번만 subscribe합니다. `eventKey`가 있으면 transport channel 이름을 제어합니다. 잘못된 JSON transport message는 무시되며, shutdown 시작 뒤 도착한 inbound transport message는 local handler dispatch 전에 무시됩니다.
|
|
124
124
|
|
|
125
125
|
## 런타임별 및 통합 서브패스
|
|
126
126
|
|
package/README.md
CHANGED
|
@@ -58,24 +58,24 @@ Use `EventBusModule.forRoot(...)` to wire the in-process event bus.
|
|
|
58
58
|
import { Module, Inject } from '@fluojs/core';
|
|
59
59
|
import { EventBusModule, EventBusLifecycleService } from '@fluojs/event-bus';
|
|
60
60
|
|
|
61
|
-
@
|
|
62
|
-
imports: [EventBusModule.forRoot()],
|
|
63
|
-
providers: [NotificationService],
|
|
64
|
-
})
|
|
65
|
-
export class AppModule {}
|
|
66
|
-
|
|
61
|
+
@Inject(EventBusLifecycleService)
|
|
67
62
|
export class UserService {
|
|
68
|
-
|
|
69
|
-
private readonly eventBus: EventBusLifecycleService;
|
|
63
|
+
constructor(private readonly eventBus: EventBusLifecycleService) {}
|
|
70
64
|
|
|
71
65
|
async signUp(email: string) {
|
|
72
66
|
// Logic to save user...
|
|
73
67
|
await this.eventBus.publish(new UserSignedUpEvent(email));
|
|
74
68
|
}
|
|
75
69
|
}
|
|
70
|
+
|
|
71
|
+
@Module({
|
|
72
|
+
imports: [EventBusModule.forRoot()],
|
|
73
|
+
providers: [NotificationService, UserService],
|
|
74
|
+
})
|
|
75
|
+
export class AppModule {}
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
`publish(event, options?)` supports `signal`, `timeoutMs`, and `waitForHandlers`. `waitForHandlers` defaults to `true`;
|
|
78
|
+
`publish(event, options?)` supports `signal`, `timeoutMs`, and `waitForHandlers`. `waitForHandlers` defaults to `true`; awaited local handlers and awaited transport publishes share the same timeout and cancellation bounds. When `waitForHandlers` is set to `false`, publishing returns immediately and skips timeout bounds. During shutdown, the event bus drains in-flight awaited publish and inbound transport handler work before closing the transport, ignores new publish calls after the lifecycle has started stopping, and ignores inbound transport callbacks that arrive after shutdown begins. Shutdown drain is bounded by `EventBusModule.forRoot({ shutdown: { drainTimeoutMs } })`, which defaults to 5000ms; if active dispatch work is still stuck after the bound, the bus records a degraded status diagnostic, logs a warning, and continues transport cleanup instead of hanging application close indefinitely.
|
|
79
79
|
|
|
80
80
|
## Common Patterns
|
|
81
81
|
|
|
@@ -104,7 +104,7 @@ class UserRegisteredEvent {
|
|
|
104
104
|
}
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
Handlers are discovered from singleton providers and controllers across imported modules. Each handler receives an isolated cloned payload, and class inheritance is supported through `instanceof` matching.
|
|
107
|
+
Handlers are discovered from singleton providers and controllers across imported modules. Each handler receives an isolated cloned payload, and class inheritance is supported through `instanceof` matching. With an external transport configured, publishing a subclass event fans out to the subclass channel and every inherited event channel in its prototype chain, even when the publisher process has no matching local handlers for those types. A subclass uses its own `static eventKey` only when it declares one directly; otherwise its class name remains the subclass channel while base classes keep their own stable keys.
|
|
108
108
|
|
|
109
109
|
## Public API Overview
|
|
110
110
|
|
|
@@ -120,7 +120,7 @@ Handlers are discovered from singleton providers and controllers across imported
|
|
|
120
120
|
- `EventBus`, `EventPublishOptions`, `EventBusModuleOptions`, `EventType`: Type-only contracts for publishing, defaults, transports, and stable event keys.
|
|
121
121
|
- `EventBusLifecycleState`, `EventBusStatusAdapterInput`, `EventBusPlatformStatusSnapshot`: Status snapshot contracts.
|
|
122
122
|
|
|
123
|
-
Transport bootstrap subscribes once per unique event channel. `eventKey` controls the transport channel name when present. Invalid JSON transport messages are ignored.
|
|
123
|
+
Transport bootstrap subscribes once per unique event channel. `eventKey` controls the transport channel name when present. Invalid JSON transport messages are ignored, and inbound transport messages that arrive after shutdown starts are ignored before local handler dispatch.
|
|
124
124
|
|
|
125
125
|
## Runtime-Specific and Integration Subpaths
|
|
126
126
|
|
package/dist/service.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export declare class EventBusLifecycleService implements EventBus, OnApplication
|
|
|
21
21
|
private transportCloseFailures;
|
|
22
22
|
private transportPublishFailures;
|
|
23
23
|
private transportSubscribeFailures;
|
|
24
|
+
private shutdownDrainTimeouts;
|
|
25
|
+
private readonly activeDispatches;
|
|
24
26
|
private readonly transport;
|
|
25
27
|
constructor(runtimeContainer: Container, compiledModules: readonly CompiledModule[], logger: ApplicationLogger, moduleOptions: EventBusModuleOptions);
|
|
26
28
|
onApplicationBootstrap(): Promise<void>;
|
|
@@ -39,6 +41,12 @@ export declare class EventBusLifecycleService implements EventBus, OnApplication
|
|
|
39
41
|
* @returns A promise that resolves once the configured local/transport publication completes.
|
|
40
42
|
*/
|
|
41
43
|
publish(event: object, options?: EventPublishOptions): Promise<void>;
|
|
44
|
+
private executePublish;
|
|
45
|
+
private canPublishInCurrentLifecycle;
|
|
46
|
+
private drainActiveDispatches;
|
|
47
|
+
private trackActiveDispatch;
|
|
48
|
+
private awaitShutdownDrain;
|
|
49
|
+
private resolveShutdownDrainTimeoutMs;
|
|
42
50
|
private matchEventDescriptors;
|
|
43
51
|
private createInvocationTasks;
|
|
44
52
|
private createBackgroundInvocationTasks;
|
|
@@ -50,9 +58,14 @@ export declare class EventBusLifecycleService implements EventBus, OnApplication
|
|
|
50
58
|
private discoverHandlers;
|
|
51
59
|
private channelFromEventType;
|
|
52
60
|
private channelsForTransportPublish;
|
|
61
|
+
private eventTypeLineage;
|
|
53
62
|
private publishToTransport;
|
|
63
|
+
private logTransportPublishCancelledBeforeDispatch;
|
|
64
|
+
private logBoundedTransportPublishError;
|
|
54
65
|
private subscribeTransportChannels;
|
|
55
66
|
private subscribeTransportChannel;
|
|
67
|
+
private canDispatchIncomingTransportMessage;
|
|
68
|
+
private dispatchIncomingTransportMessage;
|
|
56
69
|
private preloadHandlerInstances;
|
|
57
70
|
private invokeHandlerWithBounds;
|
|
58
71
|
private logPublishCancelledBeforeDispatch;
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAY,MAAM,YAAY,CAAC;AACtD,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC3B,MAAM,iBAAiB,CAAC;AAMzB,OAAO,KAAK,EACV,QAAQ,EACR,qBAAqB,EAGrB,mBAAmB,EAEpB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAY,MAAM,YAAY,CAAC;AACtD,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC3B,MAAM,iBAAiB,CAAC;AAMzB,OAAO,KAAK,EACV,QAAQ,EACR,qBAAqB,EAGrB,mBAAmB,EAEpB,MAAM,YAAY,CAAC;AAgEpB;;;;;GAKG;AACH,qBACa,wBAAyB,YAAW,QAAQ,EAAE,sBAAsB,EAAE,qBAAqB;IAepG,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAjBhC,OAAO,CAAC,WAAW,CAAgC;IACnD,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,cAAc,CAAsF;IAC5G,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsC;IACvE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAqB;IACxD,OAAO,CAAC,sBAAsB,CAAK;IACnC,OAAO,CAAC,wBAAwB,CAAK;IACrC,OAAO,CAAC,0BAA0B,CAAK;IACvC,OAAO,CAAC,qBAAqB,CAAK;IAClC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA4B;IAC7D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;gBAGvC,gBAAgB,EAAE,SAAS,EAC3B,eAAe,EAAE,SAAS,cAAc,EAAE,EAC1C,MAAM,EAAE,iBAAiB,EACzB,aAAa,EAAE,qBAAqB;IAKjD,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAavC,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB5C;;;;OAIG;IACH,4BAA4B;IAe5B;;;;;;OAMG;IACG,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;YAY5D,cAAc;IA+B5B,OAAO,CAAC,4BAA4B;YAItB,qBAAqB;YAcrB,mBAAmB;YAUnB,kBAAkB;IAiBhC,OAAO,CAAC,6BAA6B;IAIrC,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,qBAAqB;IAW7B,OAAO,CAAC,+BAA+B;IAWvC,OAAO,CAAC,8BAA8B;YAMxB,yBAAyB;YAazB,gBAAgB;IAqB9B,OAAO,CAAC,qBAAqB;IAY7B,OAAO,CAAC,kBAAkB;YAQZ,gBAAgB;IAW9B,OAAO,CAAC,oBAAoB;IAY5B,OAAO,CAAC,2BAA2B;IAkBnC,OAAO,CAAC,gBAAgB;YAiBV,kBAAkB;IA8BhC,OAAO,CAAC,0CAA0C;IAOlD,OAAO,CAAC,+BAA+B;YAwBzB,0BAA0B;YAmB1B,yBAAyB;IAiCvC,OAAO,CAAC,mCAAmC;YAI7B,gCAAgC;YAmBhC,uBAAuB;YAUvB,uBAAuB;IAmBrC,OAAO,CAAC,iCAAiC;IAOzC,OAAO,CAAC,yBAAyB;YAwBnB,qBAAqB;IAuBnC,OAAO,CAAC,sBAAsB;IAqB9B,OAAO,CAAC,kBAAkB;IAiB1B,OAAO,CAAC,gBAAgB;IAmBxB,OAAO,CAAC,0BAA0B;IAyBlC,OAAO,CAAC,+BAA+B;IAevC,OAAO,CAAC,8BAA8B;IA4BtC,OAAO,CAAC,uBAAuB;IAe/B,OAAO,CAAC,mBAAmB;YAsCb,aAAa;YA4Bb,sBAAsB;CAsBrC"}
|
package/dist/service.js
CHANGED
|
@@ -10,6 +10,7 @@ import { APPLICATION_LOGGER, COMPILED_MODULES, RUNTIME_CONTAINER } from '@fluojs
|
|
|
10
10
|
import { getEventHandlerMetadataEntries } from './metadata.js';
|
|
11
11
|
import { createEventBusPlatformStatusSnapshot } from './status.js';
|
|
12
12
|
import { EVENT_BUS_OPTIONS } from './tokens.js';
|
|
13
|
+
const DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS = 5000;
|
|
13
14
|
function createIsolatedEvent(eventType, source) {
|
|
14
15
|
const clonedPayload = cloneWithFallback(source);
|
|
15
16
|
if (typeof clonedPayload !== 'object' || clonedPayload === null) {
|
|
@@ -64,6 +65,8 @@ class EventBusLifecycleService {
|
|
|
64
65
|
transportCloseFailures = 0;
|
|
65
66
|
transportPublishFailures = 0;
|
|
66
67
|
transportSubscribeFailures = 0;
|
|
68
|
+
shutdownDrainTimeouts = 0;
|
|
69
|
+
activeDispatches = new Set();
|
|
67
70
|
transport;
|
|
68
71
|
constructor(runtimeContainer, compiledModules, logger, moduleOptions) {
|
|
69
72
|
this.runtimeContainer = runtimeContainer;
|
|
@@ -85,6 +88,9 @@ class EventBusLifecycleService {
|
|
|
85
88
|
}
|
|
86
89
|
async onApplicationShutdown() {
|
|
87
90
|
this.lifecycleState = 'stopping';
|
|
91
|
+
if (this.activeDispatches.size > 0) {
|
|
92
|
+
await this.drainActiveDispatches();
|
|
93
|
+
}
|
|
88
94
|
if (this.transport) {
|
|
89
95
|
try {
|
|
90
96
|
await this.transport.close();
|
|
@@ -108,6 +114,8 @@ class EventBusLifecycleService {
|
|
|
108
114
|
return createEventBusPlatformStatusSnapshot({
|
|
109
115
|
handlersDiscovered: this.descriptors.length,
|
|
110
116
|
lifecycleState: this.lifecycleState,
|
|
117
|
+
shutdownDrainTimeoutMs: this.resolveShutdownDrainTimeoutMs(),
|
|
118
|
+
shutdownDrainTimeouts: this.shutdownDrainTimeouts,
|
|
111
119
|
subscribedChannels: this.subscribedChannels.size,
|
|
112
120
|
transportCloseFailures: this.transportCloseFailures,
|
|
113
121
|
transportConfigured: this.transport !== undefined,
|
|
@@ -125,16 +133,27 @@ class EventBusLifecycleService {
|
|
|
125
133
|
* @returns A promise that resolves once the configured local/transport publication completes.
|
|
126
134
|
*/
|
|
127
135
|
async publish(event, options) {
|
|
136
|
+
if (!this.canPublishInCurrentLifecycle()) {
|
|
137
|
+
this.logger.warn(`EventBus.publish() was ignored because the event bus is ${this.lifecycleState}.`, 'EventBusLifecycleService');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
await this.trackActiveDispatch(this.executePublish(event, options));
|
|
141
|
+
}
|
|
142
|
+
async executePublish(event, options) {
|
|
128
143
|
await this.ensureDiscovered();
|
|
129
144
|
const matchingDescriptors = this.matchEventDescriptors(event);
|
|
130
145
|
const publishOptions = this.resolvePublishOptions(options);
|
|
131
146
|
const transportPayload = createIsolatedEvent(event.constructor, event);
|
|
132
|
-
const transportPublish = this.publishToTransport(transportPayload, matchingDescriptors);
|
|
133
147
|
if (!publishOptions.waitForHandlers) {
|
|
148
|
+
const transportPublish = this.publishToTransport(transportPayload, matchingDescriptors, {
|
|
149
|
+
...publishOptions,
|
|
150
|
+
timeoutMs: undefined
|
|
151
|
+
});
|
|
134
152
|
const backgroundTasks = this.createBackgroundInvocationTasks(matchingDescriptors, event, publishOptions.signal);
|
|
135
153
|
this.runInvocationTasksInBackground([...backgroundTasks, transportPublish]);
|
|
136
154
|
return;
|
|
137
155
|
}
|
|
156
|
+
const transportPublish = this.publishToTransport(transportPayload, matchingDescriptors, publishOptions);
|
|
138
157
|
if (matchingDescriptors.length === 0) {
|
|
139
158
|
await transportPublish;
|
|
140
159
|
return;
|
|
@@ -142,6 +161,43 @@ class EventBusLifecycleService {
|
|
|
142
161
|
const invocationTasks = this.createInvocationTasks(matchingDescriptors, event, publishOptions);
|
|
143
162
|
await Promise.allSettled([...invocationTasks, transportPublish]);
|
|
144
163
|
}
|
|
164
|
+
canPublishInCurrentLifecycle() {
|
|
165
|
+
return !['failed', 'stopped', 'stopping'].includes(this.lifecycleState);
|
|
166
|
+
}
|
|
167
|
+
async drainActiveDispatches() {
|
|
168
|
+
const activeDispatches = Array.from(this.activeDispatches);
|
|
169
|
+
const timeoutMs = this.resolveShutdownDrainTimeoutMs();
|
|
170
|
+
const drained = await this.awaitShutdownDrain(activeDispatches, timeoutMs);
|
|
171
|
+
if (!drained) {
|
|
172
|
+
this.shutdownDrainTimeouts += 1;
|
|
173
|
+
this.logger.warn(`Event bus shutdown drain exceeded ${String(timeoutMs)}ms with ${String(activeDispatches.length)} active dispatch workflow(s); continuing shutdown.`, 'EventBusLifecycleService');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async trackActiveDispatch(dispatchWorkflow) {
|
|
177
|
+
this.activeDispatches.add(dispatchWorkflow);
|
|
178
|
+
try {
|
|
179
|
+
await dispatchWorkflow;
|
|
180
|
+
} finally {
|
|
181
|
+
this.activeDispatches.delete(dispatchWorkflow);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async awaitShutdownDrain(activePublishes, timeoutMs) {
|
|
185
|
+
let timeoutId;
|
|
186
|
+
const timeout = new Promise(resolve => {
|
|
187
|
+
timeoutId = setTimeout(() => resolve(false), timeoutMs);
|
|
188
|
+
});
|
|
189
|
+
const drain = Promise.allSettled(activePublishes).then(() => true);
|
|
190
|
+
try {
|
|
191
|
+
return await Promise.race([drain, timeout]);
|
|
192
|
+
} finally {
|
|
193
|
+
if (timeoutId) {
|
|
194
|
+
clearTimeout(timeoutId);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
resolveShutdownDrainTimeoutMs() {
|
|
199
|
+
return this.normalizeTimeoutMs(this.moduleOptions.shutdown?.drainTimeoutMs) ?? DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS;
|
|
200
|
+
}
|
|
145
201
|
matchEventDescriptors(event) {
|
|
146
202
|
return this.descriptors.filter(descriptor => event instanceof descriptor.eventType);
|
|
147
203
|
}
|
|
@@ -210,7 +266,7 @@ class EventBusLifecycleService {
|
|
|
210
266
|
}
|
|
211
267
|
}
|
|
212
268
|
channelFromEventType(eventType) {
|
|
213
|
-
if (typeof eventType.eventKey === 'string') {
|
|
269
|
+
if (Object.hasOwn(eventType, 'eventKey') && typeof eventType.eventKey === 'string') {
|
|
214
270
|
const eventKey = eventType.eventKey.trim();
|
|
215
271
|
if (eventKey.length > 0) {
|
|
216
272
|
return eventKey;
|
|
@@ -220,6 +276,9 @@ class EventBusLifecycleService {
|
|
|
220
276
|
}
|
|
221
277
|
channelsForTransportPublish(event, descriptors) {
|
|
222
278
|
const channels = new Set();
|
|
279
|
+
for (const eventType of this.eventTypeLineage(event)) {
|
|
280
|
+
channels.add(this.channelFromEventType(eventType));
|
|
281
|
+
}
|
|
223
282
|
for (const descriptor of descriptors) {
|
|
224
283
|
channels.add(this.channelFromEventType(descriptor.eventType));
|
|
225
284
|
}
|
|
@@ -228,22 +287,52 @@ class EventBusLifecycleService {
|
|
|
228
287
|
}
|
|
229
288
|
return Array.from(channels);
|
|
230
289
|
}
|
|
231
|
-
|
|
290
|
+
eventTypeLineage(event) {
|
|
291
|
+
const eventTypes = [];
|
|
292
|
+
let prototype = Object.getPrototypeOf(event);
|
|
293
|
+
while (prototype && prototype !== Object.prototype) {
|
|
294
|
+
const constructor = prototype.constructor;
|
|
295
|
+
if (typeof constructor === 'function') {
|
|
296
|
+
eventTypes.push(constructor);
|
|
297
|
+
}
|
|
298
|
+
prototype = Object.getPrototypeOf(prototype);
|
|
299
|
+
}
|
|
300
|
+
return eventTypes;
|
|
301
|
+
}
|
|
302
|
+
async publishToTransport(event, descriptors, publishOptions) {
|
|
232
303
|
if (!this.transport) {
|
|
233
304
|
return;
|
|
234
305
|
}
|
|
235
306
|
const channels = this.channelsForTransportPublish(event, descriptors);
|
|
236
307
|
const publishTasks = channels.map(async channel => {
|
|
237
308
|
const payload = createIsolatedEvent(event.constructor, event);
|
|
309
|
+
if (publishOptions.signal?.aborted) {
|
|
310
|
+
this.logTransportPublishCancelledBeforeDispatch(channel);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
238
313
|
try {
|
|
239
|
-
await this.transport.publish(channel, payload);
|
|
314
|
+
await this.awaitInvocationBounds(this.transport.publish(channel, payload), publishOptions);
|
|
240
315
|
} catch (error) {
|
|
241
316
|
this.transportPublishFailures += 1;
|
|
242
|
-
this.
|
|
317
|
+
this.logBoundedTransportPublishError(channel, error);
|
|
243
318
|
}
|
|
244
319
|
});
|
|
245
320
|
await Promise.allSettled(publishTasks);
|
|
246
321
|
}
|
|
322
|
+
logTransportPublishCancelledBeforeDispatch(channel) {
|
|
323
|
+
this.logger.warn(`Event publish was cancelled before publishing transport channel "${channel}".`, 'EventBusLifecycleService');
|
|
324
|
+
}
|
|
325
|
+
logBoundedTransportPublishError(channel, error) {
|
|
326
|
+
if (error instanceof EventPublishTimeoutError) {
|
|
327
|
+
this.logger.warn(`EventBusTransport publish to channel "${channel}" exceeded publish timeout of ${String(error.timeoutMs)}ms.`, 'EventBusLifecycleService');
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (error instanceof EventPublishAbortError) {
|
|
331
|
+
this.logger.warn(`Event publish was cancelled while waiting for transport channel "${channel}".`, 'EventBusLifecycleService');
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
this.logger.error(`EventBusTransport failed to publish to channel "${channel}".`, error, 'EventBusLifecycleService');
|
|
335
|
+
}
|
|
247
336
|
async subscribeTransportChannels() {
|
|
248
337
|
if (!this.transport) {
|
|
249
338
|
return;
|
|
@@ -262,15 +351,14 @@ class EventBusLifecycleService {
|
|
|
262
351
|
async subscribeTransportChannel(channel, channelDescriptors) {
|
|
263
352
|
try {
|
|
264
353
|
await this.transport.subscribe(channel, async payload => {
|
|
354
|
+
if (!this.canDispatchIncomingTransportMessage()) {
|
|
355
|
+
this.logger.warn(`EventBusTransport message on channel "${channel}" was ignored because the event bus is ${this.lifecycleState}.`, 'EventBusLifecycleService');
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
265
358
|
if (channelDescriptors.length === 0) {
|
|
266
359
|
return;
|
|
267
360
|
}
|
|
268
|
-
|
|
269
|
-
signal: undefined,
|
|
270
|
-
timeoutMs: this.normalizeTimeoutMs(this.moduleOptions.publish?.timeoutMs),
|
|
271
|
-
waitForHandlers: this.moduleOptions.publish?.waitForHandlers ?? true
|
|
272
|
-
}));
|
|
273
|
-
await Promise.allSettled(invocationTasks);
|
|
361
|
+
await this.trackActiveDispatch(this.dispatchIncomingTransportMessage(channelDescriptors, payload));
|
|
274
362
|
});
|
|
275
363
|
this.subscribedChannels.add(channel);
|
|
276
364
|
} catch (error) {
|
|
@@ -279,6 +367,17 @@ class EventBusLifecycleService {
|
|
|
279
367
|
throw error;
|
|
280
368
|
}
|
|
281
369
|
}
|
|
370
|
+
canDispatchIncomingTransportMessage() {
|
|
371
|
+
return this.lifecycleState === 'ready';
|
|
372
|
+
}
|
|
373
|
+
async dispatchIncomingTransportMessage(channelDescriptors, payload) {
|
|
374
|
+
const invocationTasks = channelDescriptors.map(descriptor => this.invokeHandlerWithBounds(descriptor, createIsolatedEvent(descriptor.eventType, payload), {
|
|
375
|
+
signal: undefined,
|
|
376
|
+
timeoutMs: this.normalizeTimeoutMs(this.moduleOptions.publish?.timeoutMs),
|
|
377
|
+
waitForHandlers: this.moduleOptions.publish?.waitForHandlers ?? true
|
|
378
|
+
}));
|
|
379
|
+
await Promise.allSettled(invocationTasks);
|
|
380
|
+
}
|
|
282
381
|
async preloadHandlerInstances(descriptors) {
|
|
283
382
|
for (const descriptor of descriptors) {
|
|
284
383
|
if (this.handlerInstances.has(descriptor.token)) {
|
package/dist/status.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export type EventBusLifecycleState = 'created' | 'discovering' | 'ready' | 'stop
|
|
|
9
9
|
export interface EventBusStatusAdapterInput {
|
|
10
10
|
handlersDiscovered: number;
|
|
11
11
|
lifecycleState: EventBusLifecycleState;
|
|
12
|
+
shutdownDrainTimeoutMs: number;
|
|
13
|
+
shutdownDrainTimeouts: number;
|
|
12
14
|
subscribedChannels: number;
|
|
13
15
|
transportCloseFailures: number;
|
|
14
16
|
transportConfigured: boolean;
|
package/dist/status.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEvG;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,aAAa,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE7G;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,sBAAsB,CAAC;IACvC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,wBAAwB,EAAE,MAAM,CAAC;IACjC,0BAA0B,EAAE,MAAM,CAAC;IACnC,sBAAsB,EAAE,OAAO,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,uBAAuB,CAAC;IACnC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACzC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEvG;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,aAAa,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE7G;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,sBAAsB,CAAC;IACvC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,wBAAwB,EAAE,MAAM,CAAC;IACjC,0BAA0B,EAAE,MAAM,CAAC;IACnC,sBAAsB,EAAE,OAAO,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,uBAAuB,CAAC;IACnC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACzC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AA6FD;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAAC,KAAK,EAAE,0BAA0B,GAAG,8BAA8B,CAuBtH"}
|
package/dist/status.js
CHANGED
|
@@ -71,9 +71,9 @@ function createHealth(input) {
|
|
|
71
71
|
status: 'degraded'
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
-
if (input.transportPublishFailures > 0 || input.transportSubscribeFailures > 0 || input.transportCloseFailures > 0) {
|
|
74
|
+
if (input.shutdownDrainTimeouts > 0 || input.transportPublishFailures > 0 || input.transportSubscribeFailures > 0 || input.transportCloseFailures > 0) {
|
|
75
75
|
return {
|
|
76
|
-
reason: 'Event bus
|
|
76
|
+
reason: 'Event bus reported recoverable runtime failures.',
|
|
77
77
|
status: 'degraded'
|
|
78
78
|
};
|
|
79
79
|
}
|
|
@@ -98,6 +98,8 @@ export function createEventBusPlatformStatusSnapshot(input) {
|
|
|
98
98
|
handlersDiscovered: input.handlersDiscovered,
|
|
99
99
|
lifecycleState: input.lifecycleState,
|
|
100
100
|
operationMode: resolveOperationMode(input),
|
|
101
|
+
shutdownDrainTimeoutMs: input.shutdownDrainTimeoutMs,
|
|
102
|
+
shutdownDrainTimeouts: input.shutdownDrainTimeouts,
|
|
101
103
|
subscribedChannels: input.subscribedChannels,
|
|
102
104
|
transportCloseFailures: input.transportCloseFailures,
|
|
103
105
|
transportConfigured: input.transportConfigured,
|
package/dist/types.d.ts
CHANGED
|
@@ -32,8 +32,8 @@ export interface EventBusTransport {
|
|
|
32
32
|
publish(channel: string, payload: unknown): Promise<void>;
|
|
33
33
|
/**
|
|
34
34
|
* Subscribe to incoming messages on the given channel from the external transport.
|
|
35
|
-
* The event bus calls this once per
|
|
36
|
-
* Received messages are
|
|
35
|
+
* The event bus calls this once per unique event channel during bootstrap.
|
|
36
|
+
* Received messages are dispatched to every matching local handler for that channel.
|
|
37
37
|
*/
|
|
38
38
|
subscribe(channel: string, handler: (payload: unknown) => Promise<void>): Promise<void>;
|
|
39
39
|
/**
|
|
@@ -49,6 +49,10 @@ export interface EventBusModuleOptions {
|
|
|
49
49
|
timeoutMs?: number;
|
|
50
50
|
waitForHandlers?: boolean;
|
|
51
51
|
};
|
|
52
|
+
/** Shutdown drain policy. `drainTimeoutMs` defaults to 5000ms. */
|
|
53
|
+
shutdown?: {
|
|
54
|
+
drainTimeoutMs?: number;
|
|
55
|
+
};
|
|
52
56
|
/**
|
|
53
57
|
* Optional external transport adapter (e.g. Redis Pub/Sub).
|
|
54
58
|
* When provided, `publish()` fans out to the transport in addition to local handlers,
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE/D,qGAAqG;AACrG,MAAM,WAAW,SAAS,CAAC,MAAM,SAAS,MAAM,GAAG,MAAM;IACvD,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,0CAA0C;AAC1C,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,kEAAkE;AAClE,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,mBAAmB,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC;CACd;AAED,8EAA8E;AAC9E,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,kGAAkG;AAClG,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1D;;;;OAIG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAExF;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,sFAAsF;AACtF,MAAM,WAAW,qBAAqB;IACpC,kFAAkF;IAClF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE;QACR,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,eAAe,CAAC,EAAE,OAAO,CAAC;KAC3B,CAAC;IACF;;;;OAIG;IACH,SAAS,CAAC,EAAE,iBAAiB,CAAC;CAC/B;AAED,+DAA+D;AAC/D,MAAM,WAAW,QAAQ;IACvB;;;;;;OAMG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtE"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE/D,qGAAqG;AACrG,MAAM,WAAW,SAAS,CAAC,MAAM,SAAS,MAAM,GAAG,MAAM;IACvD,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,0CAA0C;AAC1C,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,kEAAkE;AAClE,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,mBAAmB,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC;CACd;AAED,8EAA8E;AAC9E,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,kGAAkG;AAClG,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1D;;;;OAIG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAExF;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,sFAAsF;AACtF,MAAM,WAAW,qBAAqB;IACpC,kFAAkF;IAClF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE;QACR,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,eAAe,CAAC,EAAE,OAAO,CAAC;KAC3B,CAAC;IACF,kEAAkE;IAClE,QAAQ,CAAC,EAAE;QACT,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IACF;;;;OAIG;IACH,SAAS,CAAC,EAAE,iBAAiB,CAAC;CAC/B;AAED,+DAA+D;AAC/D,MAAM,WAAW,QAAQ;IACvB;;;;;;OAMG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtE"}
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"pubsub",
|
|
9
9
|
"in-process"
|
|
10
10
|
],
|
|
11
|
-
"version": "1.0.0-beta.
|
|
11
|
+
"version": "1.0.0-beta.7",
|
|
12
12
|
"private": false,
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"repository": {
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"dist"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@fluojs/core": "^1.0.0-beta.
|
|
43
|
-
"@fluojs/di": "^1.0.0-beta.
|
|
44
|
-
"@fluojs/runtime": "^1.0.0-beta.
|
|
42
|
+
"@fluojs/core": "^1.0.0-beta.6",
|
|
43
|
+
"@fluojs/di": "^1.0.0-beta.8",
|
|
44
|
+
"@fluojs/runtime": "^1.0.0-beta.12"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"ioredis": "^5.0.0"
|