@fluojs/cqrs 1.0.0-beta.6 → 1.0.0
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 +1 -1
- package/README.md +1 -1
- package/dist/buses/event-bus.d.ts +8 -2
- package/dist/buses/event-bus.d.ts.map +1 -1
- package/dist/buses/event-bus.js +43 -6
- package/dist/buses/saga-bus.d.ts +8 -0
- package/dist/buses/saga-bus.d.ts.map +1 -1
- package/dist/buses/saga-bus.js +44 -6
- package/dist/module.d.ts +4 -0
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +5 -2
- package/dist/status.d.ts +3 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +10 -1
- package/dist/tokens.d.ts +2 -0
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +3 -1
- package/package.json +5 -5
package/README.ko.md
CHANGED
|
@@ -110,7 +110,7 @@ Saga 실행은 같은 프로세스 안에서 동일 saga route로 순환 재진
|
|
|
110
110
|
|
|
111
111
|
### Event 발행 계약
|
|
112
112
|
|
|
113
|
-
`CqrsEventBusService.publish(event)`는 CQRS event pipeline을 고정된 순서로 실행합니다. 먼저 일치하는 `@EventHandler(...)` provider를 실행하고, 그다음 일치하는 `@Saga(...)` provider를 실행한 뒤, 마지막으로 `@fluojs/event-bus`로 위임 발행합니다. `publishAll(events)`는 각 event의 CQRS handler, saga, 위임 발행 호출을 기다린 뒤 다음 event를 발행하므로 입력 순서를 보존합니다. 애플리케이션 shutdown 중에는 CQRS event bus가 진행 중인 `publish(...)` pipeline
|
|
113
|
+
`CqrsEventBusService.publish(event)`는 CQRS event pipeline을 고정된 순서로 실행합니다. 먼저 일치하는 `@EventHandler(...)` provider를 실행하고, 그다음 일치하는 `@Saga(...)` provider를 실행한 뒤, 마지막으로 `@fluojs/event-bus`로 위임 발행합니다. `publishAll(events)`는 각 event의 CQRS handler, saga, 위임 발행 호출을 기다린 뒤 다음 event를 발행하므로 입력 순서를 보존합니다. 애플리케이션 shutdown 중에는 CQRS event bus가 진행 중인 `publish(...)` pipeline, `publishAll(...)` sequence, saga execution chain이 settle될 때까지 기다린 뒤 stopped 상태로 전환합니다. Shutdown drain은 기본값이 5000ms인 `CqrsModule.forRoot({ shutdown: { drainTimeoutMs } })`로 제한됩니다. CQRS handler, saga 또는 위임 publish chain이 이 bound 이후에도 멈춰 있으면 CQRS는 degraded status diagnostic을 기록하고 경고를 남긴 뒤 애플리케이션 close를 무기한 hang시키지 않고 계속 진행합니다. `CqrsModule.forRoot({ eventBus: { publish: { waitForHandlers: false } } })`로 설정한 경우 위임 발행 호출은 일치하는 `@OnEvent(...)` subscriber가 완료되기 전에 resolve될 수 있으므로, 이 모드에서 `publish(...)`, `publishAll(...)`, shutdown drain 완료는 subscriber 완료를 의미하지 않습니다.
|
|
114
114
|
|
|
115
115
|
각 CQRS event handler와 saga는 매칭된 event prototype이 복원된 격리 event 복사본을 받습니다. 이 복사본을 mutate해도 변경은 현재 handler 또는 saga route 안에만 머물며, 다른 CQRS handler, saga, 원본 event 객체, 또는 위임된 `@fluojs/event-bus` subscriber에는 보이지 않습니다. 위임된 event-bus 발행은 CQRS side effect가 끝난 뒤 원본 event를 받으므로, `@OnEvent(...)` projection과 transport는 CQRS handler가 mutate한 복사본이 아니라 호출자가 소유한 payload를 관찰합니다.
|
|
116
116
|
|
package/README.md
CHANGED
|
@@ -110,7 +110,7 @@ Saga execution fails fast with `SagaTopologyError` when an in-process publish ch
|
|
|
110
110
|
|
|
111
111
|
### Event Publishing Contracts
|
|
112
112
|
|
|
113
|
-
`CqrsEventBusService.publish(event)` runs the CQRS event pipeline in a fixed order: matching `@EventHandler(...)` providers first, matching `@Saga(...)` providers second, and delegated `@fluojs/event-bus` publication last. `publishAll(events)` preserves the input order by awaiting each event's CQRS handlers, sagas, and delegated publication call before publishing the next event. During application shutdown, the CQRS event bus waits for active `publish(...)` pipelines
|
|
113
|
+
`CqrsEventBusService.publish(event)` runs the CQRS event pipeline in a fixed order: matching `@EventHandler(...)` providers first, matching `@Saga(...)` providers second, and delegated `@fluojs/event-bus` publication last. `publishAll(events)` preserves the input order by awaiting each event's CQRS handlers, sagas, and delegated publication call before publishing the next event. During application shutdown, the CQRS event bus waits for active `publish(...)` pipelines, `publishAll(...)` sequences, and saga execution chains to settle before marking itself stopped. Shutdown drain is bounded by `CqrsModule.forRoot({ shutdown: { drainTimeoutMs } })`, which defaults to 5000ms; if a CQRS handler, saga, or delegated publish chain is still stuck after the bound, CQRS records degraded status diagnostics, logs a warning, and lets application close continue instead of hanging indefinitely. When `CqrsModule.forRoot({ eventBus: { publish: { waitForHandlers: false } } })` is configured, the delegated publication call can resolve before matching `@OnEvent(...)` subscribers finish, so `publish(...)`, `publishAll(...)`, and shutdown drain completion do not imply subscriber completion in that mode.
|
|
114
114
|
|
|
115
115
|
Each CQRS event handler and saga receives an isolated event copy with the matched event prototype restored. Mutating that copy is local to the current handler or saga route; those mutations are not visible to other CQRS handlers, sagas, the original event object, or delegated `@fluojs/event-bus` subscribers. The delegated event-bus publication receives the original event after CQRS side effects complete, so `@OnEvent(...)` projections and transports observe the caller-owned payload rather than a CQRS handler's mutated copy.
|
|
116
116
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type EventBus } from '@fluojs/event-bus';
|
|
2
2
|
import type { OnApplicationShutdown, OnApplicationBootstrap } from '@fluojs/runtime';
|
|
3
3
|
import { CqrsBusBase } from '../discovery.js';
|
|
4
|
-
import {
|
|
4
|
+
import type { CqrsModuleOptions } from '../module.js';
|
|
5
5
|
import type { CqrsEventBus, IEvent } from '../types.js';
|
|
6
|
+
import { CqrsSagaLifecycleService } from './saga-bus.js';
|
|
6
7
|
/**
|
|
7
8
|
* CQRS-facing event bus that dispatches local event handlers, sagas, and the shared event transport.
|
|
8
9
|
*
|
|
@@ -12,12 +13,14 @@ import type { CqrsEventBus, IEvent } from '../types.js';
|
|
|
12
13
|
export declare class CqrsEventBusService extends CqrsBusBase implements CqrsEventBus, OnApplicationBootstrap, OnApplicationShutdown {
|
|
13
14
|
private readonly eventBus;
|
|
14
15
|
private readonly sagaService;
|
|
16
|
+
private readonly moduleOptions;
|
|
15
17
|
private descriptors;
|
|
16
18
|
private discoveryPromise;
|
|
17
19
|
private discovered;
|
|
18
20
|
private readonly activePublishPipelines;
|
|
21
|
+
private shutdownDrainTimeouts;
|
|
19
22
|
private lifecycleState;
|
|
20
|
-
constructor(eventBus: EventBus, sagaService: CqrsSagaLifecycleService, runtimeContainer: ConstructorParameters<typeof CqrsBusBase>[0], compiledModules: ConstructorParameters<typeof CqrsBusBase>[1], logger: ConstructorParameters<typeof CqrsBusBase>[2]);
|
|
23
|
+
constructor(eventBus: EventBus, sagaService: CqrsSagaLifecycleService, runtimeContainer: ConstructorParameters<typeof CqrsBusBase>[0], compiledModules: ConstructorParameters<typeof CqrsBusBase>[1], logger: ConstructorParameters<typeof CqrsBusBase>[2], moduleOptions?: CqrsModuleOptions);
|
|
21
24
|
onApplicationBootstrap(): Promise<void>;
|
|
22
25
|
onApplicationShutdown(): Promise<void>;
|
|
23
26
|
/**
|
|
@@ -45,6 +48,9 @@ export declare class CqrsEventBusService extends CqrsBusBase implements CqrsEven
|
|
|
45
48
|
private runPublishPipeline;
|
|
46
49
|
private runPublishAllPipeline;
|
|
47
50
|
private trackPublishPipeline;
|
|
51
|
+
private drainActivePublishPipelines;
|
|
52
|
+
private awaitShutdownDrain;
|
|
53
|
+
private resolveShutdownDrainTimeoutMs;
|
|
48
54
|
private matchEventDescriptors;
|
|
49
55
|
private ensureDiscovered;
|
|
50
56
|
private discoverHandlers;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../../src/buses/event-bus.ts"],"names":[],"mappings":"AACA,OAAO,EAA+B,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,KAAK,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAGrF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../../src/buses/event-bus.ts"],"names":[],"mappings":"AACA,OAAO,EAA+B,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,KAAK,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAGrF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAK9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAyC,MAAM,EAAiB,MAAM,aAAa,CAAC;AAC9G,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAYzD;;;;;GAKG;AACH,qBACa,mBAAoB,SAAQ,WAAY,YAAW,YAAY,EAAE,sBAAsB,EAAE,qBAAqB;IASvH,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAI5B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAbhC,OAAO,CAAC,WAAW,CAAgC;IACnD,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAA4B;IACnE,OAAO,CAAC,qBAAqB,CAAK;IAClC,OAAO,CAAC,cAAc,CAAsF;gBAGzF,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,wBAAwB,EACtD,gBAAgB,EAAE,qBAAqB,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,EAC9D,eAAe,EAAE,qBAAqB,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,EAC7D,MAAM,EAAE,qBAAqB,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,EACnC,aAAa,GAAE,iBAAsB;IAKlD,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAYvC,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAU5C;;;;OAIG;IACH,4BAA4B;IAe5B;;;;;;;OAOG;IACG,OAAO,CAAC,MAAM,SAAS,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlE;;;;;OAKG;IACG,UAAU,CAAC,MAAM,SAAS,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;YAInE,kBAAkB;YAiBlB,qBAAqB;YAMrB,oBAAoB;YAUpB,2BAA2B;YAc3B,kBAAkB;IAiBhC,OAAO,CAAC,6BAA6B;IAUrC,OAAO,CAAC,qBAAqB;YAIf,gBAAgB;YAchB,gBAAgB;IAe9B,OAAO,CAAC,wBAAwB;CA4CjC"}
|
package/dist/buses/event-bus.js
CHANGED
|
@@ -10,8 +10,10 @@ import { APPLICATION_LOGGER, COMPILED_MODULES, RUNTIME_CONTAINER } from '@fluojs
|
|
|
10
10
|
import { CqrsBusBase } from '../discovery.js';
|
|
11
11
|
import { createIsolatedEvent } from '../event-clone.js';
|
|
12
12
|
import { getEventHandlerMetadata } from '../metadata.js';
|
|
13
|
-
import {
|
|
13
|
+
import { CQRS_MODULE_OPTIONS } from '../tokens.js';
|
|
14
14
|
import { createCqrsPlatformStatusSnapshot } from '../status.js';
|
|
15
|
+
import { CqrsSagaLifecycleService } from './saga-bus.js';
|
|
16
|
+
const DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS = 5000;
|
|
15
17
|
function isEventHandler(value) {
|
|
16
18
|
if (typeof value !== 'object' || value === null) {
|
|
17
19
|
return false;
|
|
@@ -28,17 +30,19 @@ function isEventHandler(value) {
|
|
|
28
30
|
let _CqrsEventBusService;
|
|
29
31
|
class CqrsEventBusService extends CqrsBusBase {
|
|
30
32
|
static {
|
|
31
|
-
[_CqrsEventBusService, _initClass] = _applyDecs(this, [Inject(FLUO_EVENT_BUS, CqrsSagaLifecycleService, RUNTIME_CONTAINER, COMPILED_MODULES, APPLICATION_LOGGER)], [], 0, void 0, CqrsBusBase).c;
|
|
33
|
+
[_CqrsEventBusService, _initClass] = _applyDecs(this, [Inject(FLUO_EVENT_BUS, CqrsSagaLifecycleService, RUNTIME_CONTAINER, COMPILED_MODULES, APPLICATION_LOGGER, CQRS_MODULE_OPTIONS)], [], 0, void 0, CqrsBusBase).c;
|
|
32
34
|
}
|
|
33
35
|
descriptors = [];
|
|
34
36
|
discoveryPromise;
|
|
35
37
|
discovered = false;
|
|
36
38
|
activePublishPipelines = new Set();
|
|
39
|
+
shutdownDrainTimeouts = 0;
|
|
37
40
|
lifecycleState = 'created';
|
|
38
|
-
constructor(eventBus, sagaService, runtimeContainer, compiledModules, logger) {
|
|
41
|
+
constructor(eventBus, sagaService, runtimeContainer, compiledModules, logger, moduleOptions = {}) {
|
|
39
42
|
super(runtimeContainer, compiledModules, logger);
|
|
40
43
|
this.eventBus = eventBus;
|
|
41
44
|
this.sagaService = sagaService;
|
|
45
|
+
this.moduleOptions = moduleOptions;
|
|
42
46
|
}
|
|
43
47
|
async onApplicationBootstrap() {
|
|
44
48
|
this.lifecycleState = 'discovering';
|
|
@@ -52,8 +56,8 @@ class CqrsEventBusService extends CqrsBusBase {
|
|
|
52
56
|
}
|
|
53
57
|
async onApplicationShutdown() {
|
|
54
58
|
this.lifecycleState = 'stopping';
|
|
55
|
-
|
|
56
|
-
await
|
|
59
|
+
if (this.activePublishPipelines.size > 0) {
|
|
60
|
+
await this.drainActivePublishPipelines();
|
|
57
61
|
}
|
|
58
62
|
this.lifecycleState = 'stopped';
|
|
59
63
|
}
|
|
@@ -70,7 +74,10 @@ class CqrsEventBusService extends CqrsBusBase {
|
|
|
70
74
|
inFlightSagaExecutions: sagaSnapshot.inFlightSagaExecutions,
|
|
71
75
|
lifecycleState: this.lifecycleState,
|
|
72
76
|
sagaLifecycleState: sagaSnapshot.lifecycleState,
|
|
73
|
-
|
|
77
|
+
sagaShutdownDrainTimeouts: sagaSnapshot.shutdownDrainTimeouts,
|
|
78
|
+
sagasDiscovered: sagaSnapshot.sagasDiscovered,
|
|
79
|
+
shutdownDrainTimeoutMs: this.resolveShutdownDrainTimeoutMs(),
|
|
80
|
+
shutdownDrainTimeouts: this.shutdownDrainTimeouts
|
|
74
81
|
});
|
|
75
82
|
}
|
|
76
83
|
|
|
@@ -120,6 +127,36 @@ class CqrsEventBusService extends CqrsBusBase {
|
|
|
120
127
|
this.activePublishPipelines.delete(pipeline);
|
|
121
128
|
}
|
|
122
129
|
}
|
|
130
|
+
async drainActivePublishPipelines() {
|
|
131
|
+
const activePipelines = Array.from(this.activePublishPipelines);
|
|
132
|
+
const timeoutMs = this.resolveShutdownDrainTimeoutMs();
|
|
133
|
+
const drained = await this.awaitShutdownDrain(activePipelines, timeoutMs);
|
|
134
|
+
if (!drained) {
|
|
135
|
+
this.shutdownDrainTimeouts += 1;
|
|
136
|
+
this.logger.warn(`CQRS event shutdown drain exceeded ${String(timeoutMs)}ms with ${String(activePipelines.length)} active publish pipeline(s); continuing shutdown.`, 'CqrsEventBusService');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async awaitShutdownDrain(activePipelines, timeoutMs) {
|
|
140
|
+
let timeoutId;
|
|
141
|
+
const timeout = new Promise(resolve => {
|
|
142
|
+
timeoutId = setTimeout(() => resolve(false), timeoutMs);
|
|
143
|
+
});
|
|
144
|
+
const drain = Promise.allSettled(activePipelines).then(() => true);
|
|
145
|
+
try {
|
|
146
|
+
return await Promise.race([drain, timeout]);
|
|
147
|
+
} finally {
|
|
148
|
+
if (timeoutId) {
|
|
149
|
+
clearTimeout(timeoutId);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
resolveShutdownDrainTimeoutMs() {
|
|
154
|
+
const timeoutMs = this.moduleOptions.shutdown?.drainTimeoutMs;
|
|
155
|
+
if (typeof timeoutMs !== 'number' || !Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
156
|
+
return DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS;
|
|
157
|
+
}
|
|
158
|
+
return Math.floor(timeoutMs);
|
|
159
|
+
}
|
|
123
160
|
matchEventDescriptors(event) {
|
|
124
161
|
return this.descriptors.filter(descriptor => event instanceof descriptor.eventType);
|
|
125
162
|
}
|
package/dist/buses/saga-bus.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { OnApplicationBootstrap, OnApplicationShutdown } from '@fluojs/runtime';
|
|
2
2
|
import { CqrsBusBase } from '../discovery.js';
|
|
3
|
+
import type { CqrsModuleOptions } from '../module.js';
|
|
3
4
|
import type { IEvent } from '../types.js';
|
|
4
5
|
/**
|
|
5
6
|
* Runtime saga coordinator that discovers `@Saga()` providers and serializes execution per saga token.
|
|
@@ -8,6 +9,7 @@ import type { IEvent } from '../types.js';
|
|
|
8
9
|
* in-flight saga chains during shutdown so lifecycle guarantees remain predictable.
|
|
9
10
|
*/
|
|
10
11
|
export declare class CqrsSagaLifecycleService extends CqrsBusBase implements OnApplicationBootstrap, OnApplicationShutdown {
|
|
12
|
+
private readonly moduleOptions;
|
|
11
13
|
private descriptorsByEvent;
|
|
12
14
|
private discoveryPromise;
|
|
13
15
|
private discovered;
|
|
@@ -15,6 +17,8 @@ export declare class CqrsSagaLifecycleService extends CqrsBusBase implements OnA
|
|
|
15
17
|
private lifecycleState;
|
|
16
18
|
private readonly pendingDispatches;
|
|
17
19
|
private readonly dispatchContext;
|
|
20
|
+
private shutdownDrainTimeouts;
|
|
21
|
+
constructor(runtimeContainer: ConstructorParameters<typeof CqrsBusBase>[0], compiledModules: ConstructorParameters<typeof CqrsBusBase>[1], logger: ConstructorParameters<typeof CqrsBusBase>[2], moduleOptions?: CqrsModuleOptions);
|
|
18
22
|
onApplicationBootstrap(): Promise<void>;
|
|
19
23
|
onApplicationShutdown(): Promise<void>;
|
|
20
24
|
/**
|
|
@@ -27,6 +31,7 @@ export declare class CqrsSagaLifecycleService extends CqrsBusBase implements OnA
|
|
|
27
31
|
inFlightSagaExecutions: number;
|
|
28
32
|
lifecycleState: 'created' | 'discovering' | 'ready' | 'stopping' | 'stopped' | 'failed';
|
|
29
33
|
sagasDiscovered: number;
|
|
34
|
+
shutdownDrainTimeouts: number;
|
|
30
35
|
};
|
|
31
36
|
/**
|
|
32
37
|
* Dispatches one event to every matching saga descriptor.
|
|
@@ -37,6 +42,9 @@ export declare class CqrsSagaLifecycleService extends CqrsBusBase implements OnA
|
|
|
37
42
|
dispatch<TEvent extends IEvent>(event: TEvent): Promise<void>;
|
|
38
43
|
private matchSagaDescriptors;
|
|
39
44
|
private dispatchWithOrdering;
|
|
45
|
+
private drainActiveSagaWork;
|
|
46
|
+
private awaitShutdownDrain;
|
|
47
|
+
private resolveShutdownDrainTimeoutMs;
|
|
40
48
|
private runInDispatchContext;
|
|
41
49
|
private invokeSaga;
|
|
42
50
|
private ensureDiscovered;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"saga-bus.d.ts","sourceRoot":"","sources":["../../src/buses/saga-bus.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAGrF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"saga-bus.d.ts","sourceRoot":"","sources":["../../src/buses/saga-bus.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAGrF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAK9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,KAAK,EAAiB,MAAM,EAAyB,MAAM,aAAa,CAAC;AA2BhF;;;;;GAKG;AACH,qBACa,wBAAyB,SAAQ,WAAY,YAAW,sBAAsB,EAAE,qBAAqB;IAc9G,OAAO,CAAC,QAAQ,CAAC,aAAa;IAbhC,OAAO,CAAC,kBAAkB,CAA8C;IACxE,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAmC;IACnE,OAAO,CAAC,cAAc,CAAsF;IAC5G,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA4B;IAC9D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAgD;IAChF,OAAO,CAAC,qBAAqB,CAAK;gBAGhC,gBAAgB,EAAE,qBAAqB,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,EAC9D,eAAe,EAAE,qBAAqB,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,EAC7D,MAAM,EAAE,qBAAqB,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,EACnC,aAAa,GAAE,iBAAsB;IAKlD,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAYvC,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAa5C;;;;OAIG;IACH,kBAAkB,IAAI;QACpB,UAAU,EAAE,OAAO,CAAC;QACpB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,cAAc,EAAE,SAAS,GAAG,aAAa,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;QACxF,eAAe,EAAE,MAAM,CAAC;QACxB,qBAAqB,EAAE,MAAM,CAAC;KAC/B;IAUD;;;;;OAKG;IACG,QAAQ,CAAC,MAAM,SAAS,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYnE,OAAO,CAAC,oBAAoB;YAYd,oBAAoB;YA+CpB,mBAAmB;YAmBnB,kBAAkB;IAiBhC,OAAO,CAAC,6BAA6B;YAUvB,oBAAoB;YAepB,UAAU;YAoBV,gBAAgB;YAchB,gBAAgB;IAiB9B,OAAO,CAAC,uBAAuB;CA+ChC"}
|
package/dist/buses/saga-bus.js
CHANGED
|
@@ -11,7 +11,9 @@ import { CqrsBusBase } from '../discovery.js';
|
|
|
11
11
|
import { SagaExecutionError, SagaTopologyError } from '../errors.js';
|
|
12
12
|
import { createIsolatedEvent } from '../event-clone.js';
|
|
13
13
|
import { getSagaMetadata } from '../metadata.js';
|
|
14
|
+
import { CQRS_MODULE_OPTIONS } from '../tokens.js';
|
|
14
15
|
const MAX_NESTED_SAGA_DEPTH = 32;
|
|
16
|
+
const DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS = 5000;
|
|
15
17
|
function isSaga(value) {
|
|
16
18
|
if (typeof value !== 'object' || value === null) {
|
|
17
19
|
return false;
|
|
@@ -34,7 +36,7 @@ function toErrorMessage(error) {
|
|
|
34
36
|
let _CqrsSagaLifecycleSer;
|
|
35
37
|
class CqrsSagaLifecycleService extends CqrsBusBase {
|
|
36
38
|
static {
|
|
37
|
-
[_CqrsSagaLifecycleSer, _initClass] = _applyDecs(this, [Inject(RUNTIME_CONTAINER, COMPILED_MODULES, APPLICATION_LOGGER)], [], 0, void 0, CqrsBusBase).c;
|
|
39
|
+
[_CqrsSagaLifecycleSer, _initClass] = _applyDecs(this, [Inject(RUNTIME_CONTAINER, COMPILED_MODULES, APPLICATION_LOGGER, CQRS_MODULE_OPTIONS)], [], 0, void 0, CqrsBusBase).c;
|
|
38
40
|
}
|
|
39
41
|
descriptorsByEvent = new Map();
|
|
40
42
|
discoveryPromise;
|
|
@@ -43,6 +45,11 @@ class CqrsSagaLifecycleService extends CqrsBusBase {
|
|
|
43
45
|
lifecycleState = 'created';
|
|
44
46
|
pendingDispatches = new Set();
|
|
45
47
|
dispatchContext = new AsyncLocalStorage();
|
|
48
|
+
shutdownDrainTimeouts = 0;
|
|
49
|
+
constructor(runtimeContainer, compiledModules, logger, moduleOptions = {}) {
|
|
50
|
+
super(runtimeContainer, compiledModules, logger);
|
|
51
|
+
this.moduleOptions = moduleOptions;
|
|
52
|
+
}
|
|
46
53
|
async onApplicationBootstrap() {
|
|
47
54
|
this.lifecycleState = 'discovering';
|
|
48
55
|
try {
|
|
@@ -55,10 +62,7 @@ class CqrsSagaLifecycleService extends CqrsBusBase {
|
|
|
55
62
|
}
|
|
56
63
|
async onApplicationShutdown() {
|
|
57
64
|
this.lifecycleState = 'stopping';
|
|
58
|
-
|
|
59
|
-
await Promise.allSettled(Array.from(this.pendingDispatches));
|
|
60
|
-
}
|
|
61
|
-
await Promise.allSettled(this.executionChains.values());
|
|
65
|
+
await this.drainActiveSagaWork();
|
|
62
66
|
this.executionChains.clear();
|
|
63
67
|
this.handlerInstances.clear();
|
|
64
68
|
this.descriptorsByEvent.clear();
|
|
@@ -77,7 +81,8 @@ class CqrsSagaLifecycleService extends CqrsBusBase {
|
|
|
77
81
|
discovered: this.discovered,
|
|
78
82
|
inFlightSagaExecutions: this.pendingDispatches.size,
|
|
79
83
|
lifecycleState: this.lifecycleState,
|
|
80
|
-
sagasDiscovered: new Set(Array.from(this.descriptorsByEvent.values()).flatMap(descriptors => descriptors.map(d => d.token))).size
|
|
84
|
+
sagasDiscovered: new Set(Array.from(this.descriptorsByEvent.values()).flatMap(descriptors => descriptors.map(d => d.token))).size,
|
|
85
|
+
shutdownDrainTimeouts: this.shutdownDrainTimeouts
|
|
81
86
|
};
|
|
82
87
|
}
|
|
83
88
|
|
|
@@ -135,6 +140,39 @@ class CqrsSagaLifecycleService extends CqrsBusBase {
|
|
|
135
140
|
this.pendingDispatches.delete(current);
|
|
136
141
|
}
|
|
137
142
|
}
|
|
143
|
+
async drainActiveSagaWork() {
|
|
144
|
+
const activeWork = [...this.pendingDispatches, ...this.executionChains.values()];
|
|
145
|
+
if (activeWork.length === 0) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const timeoutMs = this.resolveShutdownDrainTimeoutMs();
|
|
149
|
+
const drained = await this.awaitShutdownDrain(activeWork, timeoutMs);
|
|
150
|
+
if (!drained) {
|
|
151
|
+
this.shutdownDrainTimeouts += 1;
|
|
152
|
+
this.logger.warn(`CQRS saga shutdown drain exceeded ${String(timeoutMs)}ms with ${String(activeWork.length)} active saga task(s); continuing shutdown.`, 'CqrsSagaLifecycleService');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async awaitShutdownDrain(activeWork, timeoutMs) {
|
|
156
|
+
let timeoutId;
|
|
157
|
+
const timeout = new Promise(resolve => {
|
|
158
|
+
timeoutId = setTimeout(() => resolve(false), timeoutMs);
|
|
159
|
+
});
|
|
160
|
+
const drain = Promise.allSettled(activeWork).then(() => true);
|
|
161
|
+
try {
|
|
162
|
+
return await Promise.race([drain, timeout]);
|
|
163
|
+
} finally {
|
|
164
|
+
if (timeoutId) {
|
|
165
|
+
clearTimeout(timeoutId);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
resolveShutdownDrainTimeoutMs() {
|
|
170
|
+
const timeoutMs = this.moduleOptions.shutdown?.drainTimeoutMs;
|
|
171
|
+
if (typeof timeoutMs !== 'number' || !Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
172
|
+
return DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS;
|
|
173
|
+
}
|
|
174
|
+
return Math.floor(timeoutMs);
|
|
175
|
+
}
|
|
138
176
|
async runInDispatchContext(activeContext, descriptor, routeLabel, callback) {
|
|
139
177
|
const nextContext = {
|
|
140
178
|
activeRoutes: [...(activeContext?.activeRoutes ?? []), {
|
package/dist/module.d.ts
CHANGED
|
@@ -11,6 +11,10 @@ export interface CqrsModuleOptions {
|
|
|
11
11
|
global?: boolean;
|
|
12
12
|
queryHandlers?: readonly QueryHandlerClass[];
|
|
13
13
|
sagas?: readonly SagaClass[];
|
|
14
|
+
/** Shutdown drain policy for CQRS event pipelines and saga execution. `drainTimeoutMs` defaults to 5000ms. */
|
|
15
|
+
shutdown?: {
|
|
16
|
+
drainTimeoutMs?: number;
|
|
17
|
+
};
|
|
14
18
|
}
|
|
15
19
|
/**
|
|
16
20
|
* Creates the providers required for CQRS buses, compatibility aliases, and optional handler registration.
|
package/dist/module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAkB,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAOhE,OAAO,KAAK,EACV,mBAAmB,EACnB,iBAAiB,EAIjB,iBAAiB,EACjB,SAAS,EACV,MAAM,YAAY,CAAC;AAEpB,4FAA4F;AAC5F,MAAM,WAAW,iBAAiB;IAChC,eAAe,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACjD,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,aAAa,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC7C,iFAAiF;IACjF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC7C,KAAK,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAkB,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAOhE,OAAO,KAAK,EACV,mBAAmB,EACnB,iBAAiB,EAIjB,iBAAiB,EACjB,SAAS,EACV,MAAM,YAAY,CAAC;AAEpB,4FAA4F;AAC5F,MAAM,WAAW,iBAAiB;IAChC,eAAe,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACjD,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,aAAa,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC7C,iFAAiF;IACjF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC7C,KAAK,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;IAC7B,8GAA8G;IAC9G,QAAQ,CAAC,EAAE;QACT,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AAwBD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,iBAAsB,GAAG,QAAQ,EAAE,CAkC/E;AAED,iFAAiF;AACjF,qBAAa,UAAU;IACrB;;;;;OAKG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,iBAAsB,GAAG,UAAU;CAiB5D"}
|
package/dist/module.js
CHANGED
|
@@ -4,7 +4,7 @@ import { CommandBusLifecycleService } from './buses/command-bus.js';
|
|
|
4
4
|
import { CqrsEventBusService } from './buses/event-bus.js';
|
|
5
5
|
import { QueryBusLifecycleService } from './buses/query-bus.js';
|
|
6
6
|
import { CqrsSagaLifecycleService } from './buses/saga-bus.js';
|
|
7
|
-
import { COMMAND_BUS, EVENT_BUS, QUERY_BUS } from './tokens.js';
|
|
7
|
+
import { COMMAND_BUS, CQRS_MODULE_OPTIONS, EVENT_BUS, QUERY_BUS } from './tokens.js';
|
|
8
8
|
|
|
9
9
|
/** Module options for registering first-party handler classes and event-bus integration. */
|
|
10
10
|
|
|
@@ -32,7 +32,10 @@ function collectOptionHandlerProviders(options) {
|
|
|
32
32
|
* @returns Providers for the command, query, event, and saga runtimes plus compatibility tokens.
|
|
33
33
|
*/
|
|
34
34
|
export function createCqrsProviders(options = {}) {
|
|
35
|
-
return [
|
|
35
|
+
return [{
|
|
36
|
+
provide: CQRS_MODULE_OPTIONS,
|
|
37
|
+
useValue: options
|
|
38
|
+
}, CommandBusLifecycleService, {
|
|
36
39
|
inject: [CommandBusLifecycleService],
|
|
37
40
|
provide: COMMAND_BUS,
|
|
38
41
|
useFactory: service => ({
|
package/dist/status.d.ts
CHANGED
|
@@ -11,7 +11,10 @@ export interface CqrsStatusAdapterInput {
|
|
|
11
11
|
inFlightSagaExecutions: number;
|
|
12
12
|
lifecycleState: CqrsLifecycleState;
|
|
13
13
|
sagaLifecycleState: CqrsLifecycleState;
|
|
14
|
+
sagaShutdownDrainTimeouts: number;
|
|
14
15
|
sagasDiscovered: number;
|
|
16
|
+
shutdownDrainTimeoutMs: number;
|
|
17
|
+
shutdownDrainTimeouts: number;
|
|
15
18
|
}
|
|
16
19
|
/**
|
|
17
20
|
* Describes the cqrs platform status snapshot contract.
|
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,kBAAkB,GAAG,SAAS,GAAG,aAAa,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEzG;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,uBAAuB,EAAE,MAAM,CAAC;IAChC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,cAAc,EAAE,kBAAkB,CAAC;IACnC,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,eAAe,EAAE,MAAM,CAAC;
|
|
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,kBAAkB,GAAG,SAAS,GAAG,aAAa,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEzG;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,uBAAuB,EAAE,MAAM,CAAC;IAChC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,cAAc,EAAE,kBAAkB,CAAC;IACnC,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,yBAAyB,EAAE,MAAM,CAAC;IAClC,eAAe,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,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;AAmFD;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,sBAAsB,GAAG,0BAA0B,CAoB1G"}
|
package/dist/status.js
CHANGED
|
@@ -57,6 +57,12 @@ function createHealth(input) {
|
|
|
57
57
|
status: 'degraded'
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
|
+
if (input.shutdownDrainTimeouts > 0 || input.sagaShutdownDrainTimeouts > 0) {
|
|
61
|
+
return {
|
|
62
|
+
reason: 'CQRS event/saga pipeline reported bounded shutdown drain timeouts.',
|
|
63
|
+
status: 'degraded'
|
|
64
|
+
};
|
|
65
|
+
}
|
|
60
66
|
return {
|
|
61
67
|
status: 'healthy'
|
|
62
68
|
};
|
|
@@ -76,7 +82,10 @@ export function createCqrsPlatformStatusSnapshot(input) {
|
|
|
76
82
|
inFlightSagaExecutions: input.inFlightSagaExecutions,
|
|
77
83
|
lifecycleState: input.lifecycleState,
|
|
78
84
|
sagaLifecycleState: input.sagaLifecycleState,
|
|
79
|
-
|
|
85
|
+
sagaShutdownDrainTimeouts: input.sagaShutdownDrainTimeouts,
|
|
86
|
+
sagasDiscovered: input.sagasDiscovered,
|
|
87
|
+
shutdownDrainTimeoutMs: input.shutdownDrainTimeoutMs,
|
|
88
|
+
shutdownDrainTimeouts: input.shutdownDrainTimeouts
|
|
80
89
|
},
|
|
81
90
|
health: createHealth(input),
|
|
82
91
|
ownership: {
|
package/dist/tokens.d.ts
CHANGED
|
@@ -6,4 +6,6 @@ export declare const COMMAND_BUS: Token<CommandBus>;
|
|
|
6
6
|
export declare const QUERY_BUS: Token<QueryBus>;
|
|
7
7
|
/** Compatibility injection token for the CQRS event bus facade. */
|
|
8
8
|
export declare const EVENT_BUS: Token<CqrsEventBus>;
|
|
9
|
+
/** Internal injection token for CQRS module options. */
|
|
10
|
+
export declare const CQRS_MODULE_OPTIONS: Token<unknown>;
|
|
9
11
|
//# sourceMappingURL=tokens.d.ts.map
|
package/dist/tokens.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAErE,gEAAgE;AAChE,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,UAAU,CAAuC,CAAC;AAClF,8DAA8D;AAC9D,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,QAAQ,CAAqC,CAAC;AAC5E,mEAAmE;AACnE,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,YAAY,CAAqC,CAAC"}
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAErE,gEAAgE;AAChE,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,UAAU,CAAuC,CAAC;AAClF,8DAA8D;AAC9D,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,QAAQ,CAAqC,CAAC;AAC5E,mEAAmE;AACnE,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,YAAY,CAAqC,CAAC;AAChF,wDAAwD;AACxD,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,OAAO,CAA0C,CAAC"}
|
package/dist/tokens.js
CHANGED
|
@@ -3,4 +3,6 @@ export const COMMAND_BUS = Symbol.for('fluo.cqrs.command-bus');
|
|
|
3
3
|
/** Compatibility injection token for the query bus facade. */
|
|
4
4
|
export const QUERY_BUS = Symbol.for('fluo.cqrs.query-bus');
|
|
5
5
|
/** Compatibility injection token for the CQRS event bus facade. */
|
|
6
|
-
export const EVENT_BUS = Symbol.for('fluo.cqrs.event-bus');
|
|
6
|
+
export const EVENT_BUS = Symbol.for('fluo.cqrs.event-bus');
|
|
7
|
+
/** Internal injection token for CQRS module options. */
|
|
8
|
+
export const CQRS_MODULE_OPTIONS = Symbol.for('fluo.cqrs.module-options');
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"saga",
|
|
10
10
|
"event-sourcing"
|
|
11
11
|
],
|
|
12
|
-
"version": "1.0.0
|
|
12
|
+
"version": "1.0.0",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
"dist"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@fluojs/core": "^1.0.0
|
|
40
|
-
"@fluojs/di": "^1.0.0
|
|
41
|
-
"@fluojs/event-bus": "^1.0.0
|
|
42
|
-
"@fluojs/runtime": "^1.0.0
|
|
39
|
+
"@fluojs/core": "^1.0.0",
|
|
40
|
+
"@fluojs/di": "^1.0.0",
|
|
41
|
+
"@fluojs/event-bus": "^1.0.0",
|
|
42
|
+
"@fluojs/runtime": "^1.0.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"vitest": "^3.2.4"
|