@fluojs/cqrs 1.1.2 → 3.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 +29 -9
- package/README.md +29 -9
- package/dist/buses/command-bus.d.ts +19 -2
- package/dist/buses/command-bus.d.ts.map +1 -1
- package/dist/buses/command-bus.js +68 -24
- package/dist/buses/event-bus.d.ts +16 -8
- package/dist/buses/event-bus.d.ts.map +1 -1
- package/dist/buses/event-bus.js +84 -74
- package/dist/buses/event-handler-discovery.d.ts +12 -0
- package/dist/buses/event-handler-discovery.d.ts.map +1 -0
- package/dist/buses/event-handler-discovery.js +35 -0
- package/dist/buses/publish-drain-tracker.d.ts +38 -0
- package/dist/buses/publish-drain-tracker.d.ts.map +1 -0
- package/dist/buses/publish-drain-tracker.js +89 -0
- package/dist/buses/query-bus.d.ts +19 -2
- package/dist/buses/query-bus.d.ts.map +1 -1
- package/dist/buses/query-bus.js +68 -24
- package/dist/buses/saga-bus.d.ts +21 -9
- package/dist/buses/saga-bus.d.ts.map +1 -1
- package/dist/buses/saga-bus.js +113 -112
- package/dist/buses/saga-continuation.d.ts +14 -0
- package/dist/buses/saga-continuation.d.ts.map +1 -0
- package/dist/buses/saga-continuation.js +24 -0
- package/dist/buses/saga-discovery.d.ts +12 -0
- package/dist/buses/saga-discovery.d.ts.map +1 -0
- package/dist/buses/saga-discovery.js +39 -0
- package/dist/buses/saga-drain.d.ts +9 -0
- package/dist/buses/saga-drain.d.ts.map +1 -0
- package/dist/buses/saga-drain.js +34 -0
- package/dist/buses/saga-topology.d.ts +18 -0
- package/dist/buses/saga-topology.d.ts.map +1 -0
- package/dist/buses/saga-topology.js +46 -0
- package/dist/buses/shutdown-deadline.d.ts +23 -0
- package/dist/buses/shutdown-deadline.d.ts.map +1 -0
- package/dist/buses/shutdown-deadline.js +31 -0
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +15 -1
- package/dist/discovery.d.ts +18 -0
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +95 -12
- package/dist/dispatch-context.d.ts +35 -0
- package/dist/dispatch-context.d.ts.map +1 -0
- package/dist/dispatch-context.js +43 -0
- package/dist/errors.d.ts +4 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -2
- package/dist/module.d.ts +4 -1
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +24 -4
- package/dist/status.d.ts +4 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +10 -6
- package/dist/test-setup.d.ts +2 -0
- package/dist/test-setup.d.ts.map +1 -0
- package/dist/test-setup.js +4 -0
- package/dist/types.d.ts +3 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -7
package/README.ko.md
CHANGED
|
@@ -24,6 +24,10 @@ fluo 애플리케이션을 위한 CQRS 패키지입니다. 부트스트랩 시
|
|
|
24
24
|
npm install @fluojs/cqrs
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
### Node.js 지원
|
|
28
|
+
|
|
29
|
+
`@fluojs/cqrs`는 패키지 자체의 지원 계약으로 Node.js `>=24.0.0 <27`을 지원합니다. Node.js 24 미만과 Node.js 27+는 지원하지 않습니다.
|
|
30
|
+
|
|
27
31
|
## 사용 시점
|
|
28
32
|
|
|
29
33
|
- "의도"(Command/Query)와 "실행"(Handler)을 분리하고 싶을 때 사용합니다.
|
|
@@ -139,7 +143,7 @@ class GetOrderSummaryHandler
|
|
|
139
143
|
}
|
|
140
144
|
```
|
|
141
145
|
|
|
142
|
-
`CqrsModule.forRoot(...)`를 import하는 애플리케이션 모듈에 projection handler, query handler, projection store를 singleton provider로 등록하세요. `CqrsEventBusService.publish(new OrderPlacedEvent(...))`는 일치하는 `@EventHandler(...)` provider를 saga와 위임 `@fluojs/event-bus` 발행보다 먼저 실행하므로, read model은 문서화된 CQRS event pipeline을 통해 write-side fact를 관찰합니다. Event replay, retry, 외부 transport가 같은 business fact를 두 번 이상 전달할 수 있으므로 projection handler는 idempotent하게 유지하세요.
|
|
146
|
+
`CqrsModule.forRoot(...)`를 import하는 애플리케이션 모듈에 projection handler, query handler, projection store를 singleton provider로 등록하세요. CQRS handler discovery는 provider registration만 검사합니다. HTTP controller는 request boundary에 남으며 controller class가 실수로 CQRS handler decorator를 가지고 있어도 무시됩니다. `CqrsModule.forRoot(...)`는 기본적으로 bus를 global로 export하며, `CqrsModule.forRoot({ global: false })`는 `eventBus.global`을 명시적으로 override하지 않는 한 해당 CQRS module을 import한 module을 통해서만 bus provider와 위임 `@fluojs/event-bus` provider가 보이도록 유지합니다. `CqrsEventBusService.publish(new OrderPlacedEvent(...))`는 일치하는 `@EventHandler(...)` provider를 saga와 위임 `@fluojs/event-bus` 발행보다 먼저 실행하므로, read model은 문서화된 CQRS event pipeline을 통해 write-side fact를 관찰합니다. Fan-out identity는 singleton provider token을 따릅니다. Decorated handler class 하나를 서로 다른 두 token으로 등록하면 두 registration이 모두 의도적으로 호출되고, 같은 token과 event route가 반복 discovery될 때만 deduplicate됩니다. Event replay, retry, 외부 transport가 같은 business fact를 두 번 이상 전달할 수 있으므로 projection handler는 idempotent하게 유지하세요.
|
|
143
147
|
|
|
144
148
|
### Saga 프로세스 매니저
|
|
145
149
|
|
|
@@ -168,19 +172,19 @@ class UserSaga implements ISaga<UserCreatedEvent> {
|
|
|
168
172
|
}
|
|
169
173
|
```
|
|
170
174
|
|
|
171
|
-
Saga 실행은
|
|
175
|
+
Saga 실행은 singleton provider token마다 하나의 활성 `handle(...)` 호출만 유지합니다. Saga가 동일 token이 소유한 다른 event route를 publish하면 CQRS는 이를 token의 전역 FIFO execution chain에 serialized continuation으로 기록하고, 현재 `handle(...)` 호출을 끝낸 뒤 enqueue 순서대로 queued work를 실행합니다. 이 공유 순서는 nested continuation, 외부 publication, awaited delegated `@OnEvent(...)` subscriber에서 발생한 publication에 모두 적용되므로 기본 `waitForHandlers` 재진입이 deadlock하지 않고 이미 token에 enqueue된 work를 추월하지 않습니다. 따라서 nested `publish(...)`는 막힌 token을 기다리지 않고 resolve되며, 바깥 publication은 continuation과 위임 event publication이 settle될 때까지 계속 기다립니다. 이미 활성 상태인 provider-token/event route로 다시 진입하면 여전히 `SagaTopologyError`로 즉시 실패하고, 중첩 hop 수가 32를 넘어도 실패합니다. 의도적인 순환/피드백 루프나 더 긴 chain은 외부 transport, scheduler, 또는 다른 bounded boundary 뒤로 이동해야 합니다.
|
|
172
176
|
|
|
173
|
-
Saga, command handler, query handler, event handler 안에서 다시 CQRS `execute(...)`, `publish(...)`, `publishAll(...)`를 호출할 때는 optional `CqrsDispatchContext` 인자를 그대로 전달하세요. CQRS는 이 명시적인 runtime-agnostic context로 Node.js async-local API에 의존하지 않고 nested dispatch 전반의 saga topology check를 유지합니다. 이 context는 opaque
|
|
177
|
+
Saga, command handler, query handler, event handler 안에서 다시 CQRS `execute(...)`, `publish(...)`, `publishAll(...)`를 호출할 때는 optional `CqrsDispatchContext` 인자를 그대로 전달하세요. CQRS는 이 명시적인 runtime-agnostic context로 Node.js async-local API에 의존하지 않고 nested dispatch 전반의 saga topology check를 유지합니다. 이 context는 opaque, frozen fieldless pass-through value이며, 신뢰하는 topology와 shutdown-drain state는 CQRS 내부에 비공개로 유지됩니다. Caller-shaped object와 복사된 값은 신뢰된 runtime state를 운반하지 않으므로 context를 직접 생성, 복제, 검사, mutate하지 마세요.
|
|
174
178
|
|
|
175
179
|
### Event 발행 계약
|
|
176
180
|
|
|
177
|
-
`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 상태로 전환합니다.
|
|
181
|
+
`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 상태로 전환합니다. Command bus와 query bus는 shutdown이 시작되면 새로운 `execute(...)` 호출을 거부하고 shutdown 중 preload된 handler cache를 정리하므로, close 이후 dispatch가 오래된 provider instance를 재사용할 수 없습니다. Shutdown이 시작되면 brand-new external `publish(...)`, `publishAll(...)`, direct saga dispatch 호출은 거부됩니다. 이미 활성화된 handler나 saga에서 호출되는 nested `publish(...)` 또는 `publishAll(...)`은 CQRS가 제공한 `CqrsDispatchContext`를 그대로 전달할 때만 계속 진행할 수 있습니다. 이렇게 하면 drain 작업은 활성 pipeline 안에 머무르고 관련 없는 caller는 계속 거부됩니다. 이미 진행 중인 publish와 saga 작업은 하나의 absolute shutdown window 안에서 drain됩니다. `CqrsModule.forRoot({ shutdown: { drainTimeoutMs } })`는 기본값 5000ms의 CQRS 전체 bound를 설정하며, 위임 `@fluojs/event-bus` shutdown은 항상 남은 budget을 상속합니다. 명시적인 `eventBus.shutdown.drainTimeoutMs`는 이 cap을 더 줄일 수만 있고 공유 CQRS deadline을 연장할 수 없습니다. 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 완료를 의미하지 않습니다.
|
|
178
182
|
|
|
179
183
|
각 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를 관찰합니다.
|
|
180
184
|
|
|
181
185
|
Event class는 payload state를 clone 가능하고 enumerable하게 유지해야 합니다. 문자열 key와 symbol key를 가진 enumerable payload field는 shared core clone fallback으로 보존되지만, 열린 socket, function, process-local handle처럼 의도적으로 clone할 수 없는 resource는 발행 전에 ID나 다른 serializable boundary로 표현해야 합니다.
|
|
182
186
|
|
|
183
|
-
CQRS handler, event handler, saga는 singleton provider에서만 discovery됩니다. Non-singleton registration은 경고와 함께 건너뜁니다.
|
|
187
|
+
CQRS handler, event handler, saga는 singleton provider에서만 discovery됩니다. Discovery는 direct class와 `useClass` provider, class token이 CQRS metadata를 가진 `useFactory` provider, instance constructor가 CQRS metadata를 가진 `useValue` provider를 지원합니다. Non-singleton registration은 경고와 함께 건너뜁니다. Event handler와 saga fan-out은 singleton provider token으로 구분되므로 같은 decorated class를 사용해도 서로 다른 token은 별도 route로 유지됩니다.
|
|
184
188
|
|
|
185
189
|
### 심볼 토큰
|
|
186
190
|
|
|
@@ -199,7 +203,7 @@ class TokenInjectedService {
|
|
|
199
203
|
## 공개 API 개요
|
|
200
204
|
|
|
201
205
|
### 모듈 및 프로바이더
|
|
202
|
-
- `CqrsModule.forRoot(options)`: 메인 진입점입니다. 버스를 등록하고
|
|
206
|
+
- `CqrsModule.forRoot(options)`: 메인 진입점입니다. 버스를 등록하고 provider-only discovery를 시작합니다. Bus provider는 기본적으로 global이며 module-local visibility가 필요하면 `global: false`를 전달합니다.
|
|
203
207
|
- Module option은 명시적인 `commandHandlers`, `queryHandlers`, `eventHandlers`, `sagas`, 위임 `eventBus` option을 받을 수 있습니다.
|
|
204
208
|
- `CommandBusLifecycleService`: Command 실행을 위한 기본 서비스입니다.
|
|
205
209
|
- `QueryBusLifecycleService`: Query 실행을 위한 기본 서비스입니다.
|
|
@@ -219,14 +223,30 @@ class TokenInjectedService {
|
|
|
219
223
|
### 오류
|
|
220
224
|
- `CommandHandlerNotFoundException`, `QueryHandlerNotFoundException`: bus에 일치하는 handler가 없을 때 발생합니다.
|
|
221
225
|
- `DuplicateCommandHandlerError`, `DuplicateQueryHandlerError`: 서로 다른 singleton provider가 같은 command 또는 query type을 claim할 때 발생합니다.
|
|
222
|
-
- `DuplicateEventHandlerError`:
|
|
226
|
+
- `DuplicateEventHandlerError`: 호환성을 위해서만 export가 유지되며, event-handler discovery는 이 오류를 throw하거나 중복 registration을 failure로 취급하지 않습니다. 같은 provider token과 event route가 반복 discovery되면 조용히 deduplicate하고, 서로 다른 singleton provider token은 discovery 순서대로 fan-out되는 유효한 route로 유지합니다.
|
|
223
227
|
- `SagaExecutionError`: 예상하지 못한 non-Fluo saga 실패를 감쌉니다.
|
|
224
|
-
- `SagaTopologyError`:
|
|
228
|
+
- `SagaTopologyError`: 활성 provider-token/event-route cycle 또는 과도하게 깊은 in-process saga graph를 감지했을 때 발생합니다.
|
|
225
229
|
|
|
226
230
|
### status와 metadata
|
|
227
|
-
- `createCqrsPlatformStatusSnapshot(...)`: diagnostics와 health surface를 위한 CQRS status snapshot을 생성합니다.
|
|
231
|
+
- `createCqrsPlatformStatusSnapshot(...)`: diagnostics와 health surface를 위한 CQRS status snapshot을 생성합니다. Snapshot `details`는 Command, Query, Event handler, saga의 탐색된 개수와 각 lifecycle summary를 보고합니다. Command와 Query adapter input은 호환성을 위해 optional로 유지하며, 생략하면 탐색된 handler 수는 0이고 lifecycle은 CQRS event lifecycle을 사용합니다.
|
|
232
|
+
- `CqrsEventBusService.createPlatformStatusSnapshot()`: live bus state에서 Command와 Query discovery summary를 채웁니다. Snapshot details는 handler descriptor, provider token, saga topology를 절대 노출하지 않으며 Command와 Query summary는 기존 event/saga readiness 또는 health semantics를 바꾸지 않습니다.
|
|
228
233
|
- command, query, event, saga registration을 검사해야 하는 framework package를 위해 metadata helper와 symbol이 export됩니다.
|
|
229
234
|
|
|
235
|
+
#### Status snapshot field
|
|
236
|
+
|
|
237
|
+
모든 CQRS snapshot은 `readiness`, `health`, `ownership`, `details`를 가집니다. `ownership`은 항상 `externallyManaged: false`, `ownsResources: false`를 보고합니다. CQRS는 자체 in-process lifecycle을 관찰하며 caller-owned external resource를 소유한다고 주장하지 않습니다.
|
|
238
|
+
|
|
239
|
+
| `details` field | 의미 |
|
|
240
|
+
| --- | --- |
|
|
241
|
+
| `dependencies` | 위임된 event-bus dependency를 나타내는 항상 `['event-bus.default']` 값입니다. |
|
|
242
|
+
| `commandHandlersDiscovered`, `queryHandlersDiscovered`, `eventHandlersDiscovered`, `sagasDiscovered` | 현재 탐색된 singleton handler 또는 saga의 개수입니다. Command/Query adapter input을 생략하면 `0`을 사용하며, shutdown 후 live-bus count는 `0`입니다. |
|
|
243
|
+
| `commandLifecycleState`, `queryLifecycleState`, `lifecycleState`, `sagaLifecycleState` | Command, Query, event-pipeline, saga runtime의 lifecycle state입니다. Command/Query adapter input을 생략하면 `lifecycleState`로 fallback합니다. |
|
|
244
|
+
| `inFlightSagaExecutions` | 현재 runtime이 소유한 saga execution 수입니다. |
|
|
245
|
+
| `shutdownDrainTimeoutMs` | 설정된 bounded shutdown-drain window입니다. |
|
|
246
|
+
| `shutdownDrainTimeouts`, `sagaShutdownDrainTimeouts` | event pipeline과 saga runtime에서 기록된 bounded drain timeout입니다. |
|
|
247
|
+
|
|
248
|
+
유효한 lifecycle state는 `created`, `discovering`, `ready`, `stopping`, `stopped`, `failed`입니다. Readiness는 event와 saga state를 다음 순서로 평가합니다. 둘 다 `ready`이면 `ready`, 그 외에는 하나라도 `discovering`이면 `degraded`, 그 외에는 하나라도 `stopping`이면 `not-ready`, 그 외에는 하나라도 `stopped` 또는 `failed`이면 `not-ready`, `created`를 포함한 나머지 조합은 `not-ready`입니다. Health는 다음 순서로 평가합니다. 0이 아닌 drain-timeout counter가 하나라도 있으면 `degraded`, 그 외에는 하나라도 `stopped` 또는 `failed`이면 `unhealthy`, 그 외에는 하나라도 `discovering` 또는 `stopping`이면 `degraded`, 나머지 조합은 `healthy`입니다. Command와 Query lifecycle field는 diagnostic 전용이며 기존 event/saga readiness 또는 health rule을 바꾸지 않습니다.
|
|
249
|
+
|
|
230
250
|
## 관련 패키지
|
|
231
251
|
|
|
232
252
|
- `@fluojs/event-bus`: `CqrsEventBusService`에서 사용하는 하위 이벤트 분산 패키지입니다.
|
package/README.md
CHANGED
|
@@ -24,6 +24,10 @@ CQRS primitives for fluo applications with bootstrap-time handler discovery, com
|
|
|
24
24
|
npm install @fluojs/cqrs
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
### Node.js Support
|
|
28
|
+
|
|
29
|
+
`@fluojs/cqrs` supports Node.js `>=24.0.0 <27` as its package-owned support contract. Node.js versions below 24 and Node.js 27+ are not supported.
|
|
30
|
+
|
|
27
31
|
## When to Use
|
|
28
32
|
|
|
29
33
|
- When you want to decouple the "intent" (Commands/Queries) from the "execution" (Handlers).
|
|
@@ -139,7 +143,7 @@ class GetOrderSummaryHandler
|
|
|
139
143
|
}
|
|
140
144
|
```
|
|
141
145
|
|
|
142
|
-
Register the projection handler, query handler, and projection store as singleton providers in the same application module that imports `CqrsModule.forRoot(...)`. `CqrsEventBusService.publish(new OrderPlacedEvent(...))` runs matching `@EventHandler(...)` providers before sagas and delegated `@fluojs/event-bus` publication, so the read model observes the write-side fact through the documented CQRS event pipeline. Keep projection handlers idempotent because event replay, retries, or external transports can deliver the same business fact more than once.
|
|
146
|
+
Register the projection handler, query handler, and projection store as singleton providers in the same application module that imports `CqrsModule.forRoot(...)`. CQRS handler discovery inspects provider registrations only; HTTP controllers stay on the request boundary and are ignored even when a controller class accidentally carries a CQRS handler decorator. `CqrsModule.forRoot(...)` exports the buses globally by default, and `CqrsModule.forRoot({ global: false })` keeps those bus providers and the delegated `@fluojs/event-bus` providers visible only through modules that import the CQRS module unless `eventBus.global` is explicitly overridden. `CqrsEventBusService.publish(new OrderPlacedEvent(...))` runs matching `@EventHandler(...)` providers before sagas and delegated `@fluojs/event-bus` publication, so the read model observes the write-side fact through the documented CQRS event pipeline. Fan-out identity follows the singleton provider token: registering one decorated handler class under two distinct tokens intentionally invokes both registrations, while repeated discovery of the same token and event route is deduplicated. Keep projection handlers idempotent because event replay, retries, or external transports can deliver the same business fact more than once.
|
|
143
147
|
|
|
144
148
|
### Saga Process Managers
|
|
145
149
|
|
|
@@ -168,19 +172,19 @@ class UserSaga implements ISaga<UserCreatedEvent> {
|
|
|
168
172
|
}
|
|
169
173
|
```
|
|
170
174
|
|
|
171
|
-
Saga execution
|
|
175
|
+
Saga execution keeps one active `handle(...)` call per singleton provider token. When a saga publishes a different event route owned by that same token, CQRS records a serialized continuation on the token's global FIFO execution chain, lets the current `handle(...)` call finish, and then runs queued work in enqueue order. This shared ordering covers nested continuations, external publications, and publications from awaited delegated `@OnEvent(...)` subscribers, so default `waitForHandlers` re-entry does not deadlock and work already enqueued for the token is not overtaken. The nested `publish(...)` resolves without awaiting the blocked token, while the outer publication still waits for the continuation and delegated event publication to settle. Re-entering an already active provider-token/event route still fails fast with `SagaTopologyError`, as does exceeding 32 nested saga hops. Move intentionally cyclic or long-running feedback loops behind an external transport, scheduler, or other bounded boundary.
|
|
172
176
|
|
|
173
|
-
When a saga, command handler, query handler, or event handler performs another CQRS `execute(...)`, `publish(...)`, or `publishAll(...)` call, pass the optional `CqrsDispatchContext` argument through unchanged. CQRS uses this explicit runtime-agnostic context to keep saga topology checks intact across nested dispatch without relying on Node.js async-local APIs. The context is opaque
|
|
177
|
+
When a saga, command handler, query handler, or event handler performs another CQRS `execute(...)`, `publish(...)`, or `publishAll(...)` call, pass the optional `CqrsDispatchContext` argument through unchanged. CQRS uses this explicit runtime-agnostic context to keep saga topology checks intact across nested dispatch without relying on Node.js async-local APIs. The context is an opaque, frozen fieldless pass-through value; trusted topology and shutdown-drain state remains private to CQRS. Do not construct, clone, inspect, or mutate it because caller-shaped objects and copied values do not carry trusted runtime state.
|
|
174
178
|
|
|
175
179
|
### Event Publishing Contracts
|
|
176
180
|
|
|
177
|
-
`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. Once shutdown starts, new `publish(...)`, `publishAll(...)`, and direct saga dispatch calls are rejected
|
|
181
|
+
`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. Command and query buses reject new `execute(...)` calls once shutdown starts and clear their preloaded handler caches during shutdown, so post-close dispatch cannot reuse stale provider instances. Once shutdown starts, brand-new external `publish(...)`, `publishAll(...)`, and direct saga dispatch calls are rejected. A nested `publish(...)` or `publishAll(...)` invoked from an already active handler or saga may continue only when it passes through the CQRS-provided `CqrsDispatchContext`; this keeps drain work inside the active pipeline while still rejecting unrelated callers. Already active publish and saga work drains inside one absolute shutdown window. `CqrsModule.forRoot({ shutdown: { drainTimeoutMs } })` sets that CQRS-wide bound and defaults to 5000ms; delegated `@fluojs/event-bus` shutdown always inherits its remaining budget. An explicit `eventBus.shutdown.drainTimeoutMs` may tighten that cap, but never extend the shared CQRS deadline. If a CQRS handler, saga, or delegated publish chain is still stuck when that bound expires, 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.
|
|
178
182
|
|
|
179
183
|
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.
|
|
180
184
|
|
|
181
185
|
Event classes should keep their payload state cloneable and enumerable. String-keyed and symbol-keyed enumerable payload fields are preserved by the shared core clone fallback, while intentionally non-cloneable resources such as open sockets, functions, or process-local handles should be represented by IDs or other serializable boundaries before publishing.
|
|
182
186
|
|
|
183
|
-
CQRS handlers, event handlers, and sagas are discovered only on singleton providers. Non-singleton registrations are skipped with warnings.
|
|
187
|
+
CQRS handlers, event handlers, and sagas are discovered only on singleton providers. Discovery supports direct class and `useClass` providers, `useFactory` providers whose class token carries CQRS metadata, and `useValue` providers whose instance constructor carries CQRS metadata. Non-singleton registrations are skipped with warnings. Event-handler and saga fan-out is keyed by singleton provider token, so distinct tokens remain distinct routes even when they use the same decorated class.
|
|
184
188
|
|
|
185
189
|
### Symbol Tokens
|
|
186
190
|
|
|
@@ -199,7 +203,7 @@ class TokenInjectedService {
|
|
|
199
203
|
## Public API Overview
|
|
200
204
|
|
|
201
205
|
### Modules & Providers
|
|
202
|
-
- `CqrsModule.forRoot(options)`: Main entry point. Registers buses and starts discovery.
|
|
206
|
+
- `CqrsModule.forRoot(options)`: Main entry point. Registers buses and starts provider-only discovery. Bus providers are global by default; pass `global: false` for module-local visibility.
|
|
203
207
|
- Module options can provide explicit `commandHandlers`, `queryHandlers`, `eventHandlers`, `sagas`, and delegated `eventBus` options.
|
|
204
208
|
- `CommandBusLifecycleService`: Primary service for executing commands.
|
|
205
209
|
- `QueryBusLifecycleService`: Primary service for executing queries.
|
|
@@ -219,14 +223,30 @@ class TokenInjectedService {
|
|
|
219
223
|
### Errors
|
|
220
224
|
- `CommandHandlerNotFoundException`, `QueryHandlerNotFoundException`: Raised when a bus has no matching handler.
|
|
221
225
|
- `DuplicateCommandHandlerError`, `DuplicateQueryHandlerError`: Raised when different singleton providers claim the same command or query type.
|
|
222
|
-
- `DuplicateEventHandlerError`:
|
|
226
|
+
- `DuplicateEventHandlerError`: Retained only as a compatibility export; event-handler discovery does not throw it or treat duplicate registrations as failures. Repeated discovery of the same provider token and event route is silently deduplicated, while distinct singleton provider tokens remain valid fan-out routes in discovery order.
|
|
223
227
|
- `SagaExecutionError`: Wraps unexpected non-Fluo saga failures.
|
|
224
|
-
- `SagaTopologyError`: Raised when saga orchestration detects
|
|
228
|
+
- `SagaTopologyError`: Raised when saga orchestration detects an active provider-token/event-route cycle or an over-deep in-process saga graph.
|
|
225
229
|
|
|
226
230
|
### Status and metadata
|
|
227
|
-
- `createCqrsPlatformStatusSnapshot(...)`: Creates CQRS status snapshots for diagnostics and health surfaces.
|
|
231
|
+
- `createCqrsPlatformStatusSnapshot(...)`: Creates CQRS status snapshots for diagnostics and health surfaces. Command and query adapter inputs remain optional for compatibility and default to zero discovered handlers plus the CQRS event lifecycle when omitted.
|
|
232
|
+
- `CqrsEventBusService.createPlatformStatusSnapshot()`: Populates all discovery and lifecycle summaries from live bus state. Snapshot details never expose handler descriptors, provider tokens, or saga topology; command and query summaries do not change the existing event/saga readiness or health semantics.
|
|
228
233
|
- Metadata helpers and symbols are exported for framework packages that need to inspect command, query, event, or saga registrations.
|
|
229
234
|
|
|
235
|
+
#### Status snapshot fields
|
|
236
|
+
|
|
237
|
+
Every CQRS snapshot has `readiness`, `health`, `ownership`, and `details`. `ownership` always reports `externallyManaged: false` and `ownsResources: false`: CQRS observes its own in-process lifecycle and does not claim a caller-owned external resource.
|
|
238
|
+
|
|
239
|
+
| `details` field | Meaning |
|
|
240
|
+
| --- | --- |
|
|
241
|
+
| `dependencies` | Always `['event-bus.default']`, identifying the delegated event-bus dependency. |
|
|
242
|
+
| `commandHandlersDiscovered`, `queryHandlersDiscovered`, `eventHandlersDiscovered`, `sagasDiscovered` | The currently discovered singleton handler or saga counts. Command/query adapter inputs default to `0` when omitted; after shutdown, live-bus counts are `0`. |
|
|
243
|
+
| `commandLifecycleState`, `queryLifecycleState`, `lifecycleState`, `sagaLifecycleState` | The command, query, event-pipeline, and saga runtime states. When command/query adapter inputs are omitted, their states fall back to `lifecycleState`. |
|
|
244
|
+
| `inFlightSagaExecutions` | Saga executions currently owned by the runtime. |
|
|
245
|
+
| `shutdownDrainTimeoutMs` | The configured bounded shutdown-drain window. |
|
|
246
|
+
| `shutdownDrainTimeouts`, `sagaShutdownDrainTimeouts` | Recorded bounded drain timeouts for the event pipeline and saga runtime. |
|
|
247
|
+
|
|
248
|
+
The lifecycle states are `created`, `discovering`, `ready`, `stopping`, `stopped`, and `failed`. Readiness evaluates event and saga state in this order: both `ready` reports `ready`; otherwise any `discovering` reports `degraded`; otherwise any `stopping` reports `not-ready`; otherwise any `stopped` or `failed` reports `not-ready`; every remaining combination, including `created`, reports `not-ready`. Health evaluates in this order: any nonzero drain-timeout counter reports `degraded`; otherwise any `stopped` or `failed` reports `unhealthy`; otherwise any `discovering` or `stopping` reports `degraded`; every remaining combination reports `healthy`. Command and query lifecycle fields remain diagnostic only and do not alter these event/saga readiness or health rules.
|
|
249
|
+
|
|
230
250
|
## Related Packages
|
|
231
251
|
|
|
232
252
|
- `@fluojs/event-bus`: Underlying event distribution used by `CqrsEventBusService`.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Container } from '@fluojs/di';
|
|
2
|
+
import type { ApplicationLogger, CompiledModule, OnApplicationBootstrap, OnApplicationShutdown, RuntimeCleanupRegistration } from '@fluojs/runtime';
|
|
2
3
|
import { CqrsBusBase } from '../discovery.js';
|
|
3
4
|
import type { CommandBus, CqrsDispatchContext, ICommand } from '../types.js';
|
|
4
5
|
/**
|
|
@@ -7,11 +8,25 @@ import type { CommandBus, CqrsDispatchContext, ICommand } from '../types.js';
|
|
|
7
8
|
* The command bus resolves singleton handlers only, warns on unsupported scopes,
|
|
8
9
|
* and throws explicit contract errors when no handler or multiple handlers exist.
|
|
9
10
|
*/
|
|
10
|
-
export declare class CommandBusLifecycleService extends CqrsBusBase implements CommandBus, OnApplicationBootstrap {
|
|
11
|
+
export declare class CommandBusLifecycleService extends CqrsBusBase implements CommandBus, OnApplicationBootstrap, OnApplicationShutdown {
|
|
11
12
|
private descriptors;
|
|
12
13
|
private discoveryPromise;
|
|
13
14
|
private discovered;
|
|
15
|
+
private lifecycleState;
|
|
16
|
+
private unregisterShutdownStartCleanup;
|
|
17
|
+
constructor(runtimeContainer: Container, compiledModules: readonly CompiledModule[], logger: ApplicationLogger, registerRuntimeCleanup?: RuntimeCleanupRegistration);
|
|
14
18
|
onApplicationBootstrap(): Promise<void>;
|
|
19
|
+
onApplicationShutdown(): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Returns command-handler discovery and lifecycle state for CQRS diagnostics.
|
|
22
|
+
*
|
|
23
|
+
* @returns Current discovery state, lifecycle state, and discovered command-handler count.
|
|
24
|
+
*/
|
|
25
|
+
getRuntimeSnapshot(): {
|
|
26
|
+
discovered: boolean;
|
|
27
|
+
commandHandlersDiscovered: number;
|
|
28
|
+
lifecycleState: 'created' | 'discovering' | 'ready' | 'stopping' | 'stopped' | 'failed';
|
|
29
|
+
};
|
|
15
30
|
/**
|
|
16
31
|
* Executes one command by dispatching it to the discovered handler for its constructor.
|
|
17
32
|
*
|
|
@@ -23,6 +38,8 @@ export declare class CommandBusLifecycleService extends CqrsBusBase implements C
|
|
|
23
38
|
* @throws {InvariantError} When the resolved provider does not implement `execute(command)`.
|
|
24
39
|
*/
|
|
25
40
|
execute<TCommand extends ICommand, TResult = void>(command: TCommand, context?: CqrsDispatchContext): Promise<TResult>;
|
|
41
|
+
private assertAcceptingNewWork;
|
|
42
|
+
private markApplicationShutdownStarted;
|
|
26
43
|
private ensureDiscovered;
|
|
27
44
|
private discoverHandlers;
|
|
28
45
|
private discoverCommandDescriptors;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-bus.d.ts","sourceRoot":"","sources":["../../src/buses/command-bus.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"command-bus.d.ts","sourceRoot":"","sources":["../../src/buses/command-bus.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAEpJ,OAAO,EAAE,WAAW,EAA4D,MAAM,iBAAiB,CAAC;AAGxG,OAAO,KAAK,EACV,UAAU,EAGV,mBAAmB,EACnB,QAAQ,EAET,MAAM,aAAa,CAAC;AAUrB;;;;;GAKG;AACH,qBACa,0BAA2B,SAAQ,WAAY,YAAW,UAAU,EAAE,sBAAsB,EAAE,qBAAqB;IAC9H,OAAO,CAAC,WAAW,CAAoD;IACvE,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,cAAc,CAAsF;IAC5G,OAAO,CAAC,8BAA8B,CAA2B;gBAG/D,gBAAgB,EAAE,SAAS,EAC3B,eAAe,EAAE,SAAS,cAAc,EAAE,EAC1C,MAAM,EAAE,iBAAiB,EACzB,sBAAsB,GAAE,0BAAkD;IAStE,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAYvC,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAW5C;;;;OAIG;IACH,kBAAkB,IAAI;QACpB,UAAU,EAAE,OAAO,CAAC;QACpB,yBAAyB,EAAE,MAAM,CAAC;QAClC,cAAc,EAAE,SAAS,GAAG,aAAa,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;KACzF;IAUD;;;;;;;;;OASG;IACG,OAAO,CAAC,QAAQ,SAAS,QAAQ,EAAE,OAAO,GAAG,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;IAoB5H,OAAO,CAAC,sBAAsB;IAM9B,OAAO,CAAC,8BAA8B;YAMxB,gBAAgB;YAchB,gBAAgB;IAe9B,OAAO,CAAC,0BAA0B;CA2CnC"}
|
|
@@ -5,8 +5,8 @@ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e =
|
|
|
5
5
|
function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
|
|
6
6
|
function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
|
|
7
7
|
import { Inject, InvariantError } from '@fluojs/core';
|
|
8
|
-
import { APPLICATION_LOGGER, COMPILED_MODULES, RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
|
|
9
|
-
import { CqrsBusBase, createDuplicateHandlerMessage } from '../discovery.js';
|
|
8
|
+
import { APPLICATION_LOGGER, COMPILED_MODULES, RUNTIME_CLEANUP_REGISTRATION, RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
|
|
9
|
+
import { CqrsBusBase, createDuplicateHandlerMessage, isSameHandlerRegistration } from '../discovery.js';
|
|
10
10
|
import { CommandHandlerNotFoundException, DuplicateCommandHandlerError } from '../errors.js';
|
|
11
11
|
import { getCommandHandlerMetadata } from '../metadata.js';
|
|
12
12
|
function isCommandHandler(value) {
|
|
@@ -25,13 +25,52 @@ function isCommandHandler(value) {
|
|
|
25
25
|
let _CommandBusLifecycleS;
|
|
26
26
|
class CommandBusLifecycleService extends CqrsBusBase {
|
|
27
27
|
static {
|
|
28
|
-
[_CommandBusLifecycleS, _initClass] = _applyDecs(this, [Inject(RUNTIME_CONTAINER, COMPILED_MODULES, APPLICATION_LOGGER)], [], 0, void 0, CqrsBusBase).c;
|
|
28
|
+
[_CommandBusLifecycleS, _initClass] = _applyDecs(this, [Inject(RUNTIME_CONTAINER, COMPILED_MODULES, APPLICATION_LOGGER, RUNTIME_CLEANUP_REGISTRATION)], [], 0, void 0, CqrsBusBase).c;
|
|
29
29
|
}
|
|
30
30
|
descriptors = new Map();
|
|
31
31
|
discoveryPromise;
|
|
32
32
|
discovered = false;
|
|
33
|
+
lifecycleState = 'created';
|
|
34
|
+
unregisterShutdownStartCleanup;
|
|
35
|
+
constructor(runtimeContainer, compiledModules, logger, registerRuntimeCleanup = () => () => undefined) {
|
|
36
|
+
super(runtimeContainer, compiledModules, logger);
|
|
37
|
+
this.unregisterShutdownStartCleanup = registerRuntimeCleanup(() => {
|
|
38
|
+
this.markApplicationShutdownStarted();
|
|
39
|
+
});
|
|
40
|
+
}
|
|
33
41
|
async onApplicationBootstrap() {
|
|
34
|
-
|
|
42
|
+
this.lifecycleState = 'discovering';
|
|
43
|
+
try {
|
|
44
|
+
await this.ensureDiscovered();
|
|
45
|
+
this.lifecycleState = 'ready';
|
|
46
|
+
} catch (error) {
|
|
47
|
+
this.lifecycleState = 'failed';
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async onApplicationShutdown() {
|
|
52
|
+
this.markApplicationShutdownStarted();
|
|
53
|
+
this.unregisterShutdownStartCleanup?.();
|
|
54
|
+
this.unregisterShutdownStartCleanup = undefined;
|
|
55
|
+
this.descriptors.clear();
|
|
56
|
+
this.handlerInstances.clear();
|
|
57
|
+
this.discovered = false;
|
|
58
|
+
this.discoveryPromise = undefined;
|
|
59
|
+
this.lifecycleState = 'stopped';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Returns command-handler discovery and lifecycle state for CQRS diagnostics.
|
|
64
|
+
*
|
|
65
|
+
* @returns Current discovery state, lifecycle state, and discovered command-handler count.
|
|
66
|
+
*/
|
|
67
|
+
getRuntimeSnapshot() {
|
|
68
|
+
const stopped = this.lifecycleState === 'stopped';
|
|
69
|
+
return {
|
|
70
|
+
commandHandlersDiscovered: stopped ? 0 : this.descriptors.size,
|
|
71
|
+
discovered: stopped ? false : this.discovered,
|
|
72
|
+
lifecycleState: this.lifecycleState
|
|
73
|
+
};
|
|
35
74
|
}
|
|
36
75
|
|
|
37
76
|
/**
|
|
@@ -45,6 +84,7 @@ class CommandBusLifecycleService extends CqrsBusBase {
|
|
|
45
84
|
* @throws {InvariantError} When the resolved provider does not implement `execute(command)`.
|
|
46
85
|
*/
|
|
47
86
|
async execute(command, context) {
|
|
87
|
+
this.assertAcceptingNewWork('execute');
|
|
48
88
|
await this.ensureDiscovered();
|
|
49
89
|
const commandType = command.constructor;
|
|
50
90
|
const descriptor = this.descriptors.get(commandType);
|
|
@@ -57,6 +97,16 @@ class CommandBusLifecycleService extends CqrsBusBase {
|
|
|
57
97
|
}
|
|
58
98
|
return await instance.execute(command, context);
|
|
59
99
|
}
|
|
100
|
+
assertAcceptingNewWork(operation) {
|
|
101
|
+
if (this.lifecycleState === 'stopping' || this.lifecycleState === 'stopped') {
|
|
102
|
+
throw new InvariantError(`CQRS command bus cannot ${operation} after shutdown has started.`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
markApplicationShutdownStarted() {
|
|
106
|
+
if (this.lifecycleState !== 'stopped') {
|
|
107
|
+
this.lifecycleState = 'stopping';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
60
110
|
async ensureDiscovered() {
|
|
61
111
|
if (this.discovered) {
|
|
62
112
|
return;
|
|
@@ -82,7 +132,6 @@ class CommandBusLifecycleService extends CqrsBusBase {
|
|
|
82
132
|
}
|
|
83
133
|
discoverCommandDescriptors() {
|
|
84
134
|
const descriptors = new Map();
|
|
85
|
-
const seenByTarget = new WeakMap();
|
|
86
135
|
for (const candidate of this.discoveryCandidates()) {
|
|
87
136
|
const metadata = getCommandHandlerMetadata(candidate.targetType);
|
|
88
137
|
if (!metadata) {
|
|
@@ -92,27 +141,22 @@ class CommandBusLifecycleService extends CqrsBusBase {
|
|
|
92
141
|
this.logger.warn(`${candidate.targetType.name} in module ${candidate.moduleName} declares @CommandHandler() but is registered with ${candidate.scope} scope. Command handlers are registered only for singleton providers.`, 'CommandBusLifecycleService');
|
|
93
142
|
continue;
|
|
94
143
|
}
|
|
95
|
-
const seenCommandTypes = seenByTarget.get(candidate.targetType) ?? new Set();
|
|
96
|
-
if (seenCommandTypes.has(metadata.commandType)) {
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
seenCommandTypes.add(metadata.commandType);
|
|
100
|
-
seenByTarget.set(candidate.targetType, seenCommandTypes);
|
|
101
144
|
const existing = descriptors.get(metadata.commandType);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
targetType: candidate.targetType,
|
|
113
|
-
token: candidate.token
|
|
114
|
-
});
|
|
145
|
+
const nextDescriptor = {
|
|
146
|
+
moduleName: candidate.moduleName,
|
|
147
|
+
targetType: candidate.targetType,
|
|
148
|
+
token: candidate.token
|
|
149
|
+
};
|
|
150
|
+
if (existing) {
|
|
151
|
+
if (isSameHandlerRegistration(existing, nextDescriptor)) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
throw new DuplicateCommandHandlerError(createDuplicateHandlerMessage('command', metadata.commandType, existing, nextDescriptor));
|
|
115
155
|
}
|
|
156
|
+
descriptors.set(metadata.commandType, {
|
|
157
|
+
commandType: metadata.commandType,
|
|
158
|
+
...nextDescriptor
|
|
159
|
+
});
|
|
116
160
|
}
|
|
117
161
|
return descriptors;
|
|
118
162
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { type EventBus } from '@fluojs/event-bus';
|
|
2
|
-
import type { OnApplicationBootstrap, OnApplicationShutdown } from '@fluojs/runtime';
|
|
1
|
+
import { type EventBus, EventBusLifecycleService } from '@fluojs/event-bus';
|
|
2
|
+
import type { OnApplicationBootstrap, OnApplicationShutdown, RuntimeCleanupRegistration } from '@fluojs/runtime';
|
|
3
3
|
import { CqrsBusBase } from '../discovery.js';
|
|
4
4
|
import type { CqrsModuleOptions } from '../module.js';
|
|
5
5
|
import type { CqrsDispatchContext, CqrsEventBus, IEvent } from '../types.js';
|
|
6
6
|
import { CqrsSagaLifecycleService } from './saga-bus.js';
|
|
7
|
+
import { CqrsShutdownDeadline } from './shutdown-deadline.js';
|
|
8
|
+
import { CommandBusLifecycleService } from './command-bus.js';
|
|
9
|
+
import { QueryBusLifecycleService } from './query-bus.js';
|
|
7
10
|
/**
|
|
8
11
|
* CQRS-facing event bus that dispatches local event handlers, sagas, and the shared event transport.
|
|
9
12
|
*
|
|
@@ -14,13 +17,17 @@ export declare class CqrsEventBusService extends CqrsBusBase implements CqrsEven
|
|
|
14
17
|
private readonly eventBus;
|
|
15
18
|
private readonly sagaService;
|
|
16
19
|
private readonly moduleOptions;
|
|
20
|
+
private readonly delegatedEventBus;
|
|
21
|
+
private readonly shutdownDeadline;
|
|
22
|
+
private readonly commandService;
|
|
23
|
+
private readonly queryService;
|
|
17
24
|
private descriptors;
|
|
18
25
|
private discoveryPromise;
|
|
19
26
|
private discovered;
|
|
20
|
-
private readonly
|
|
21
|
-
private shutdownDrainTimeouts;
|
|
27
|
+
private readonly publishDrainTracker;
|
|
22
28
|
private lifecycleState;
|
|
23
|
-
|
|
29
|
+
private unregisterShutdownStartCleanup;
|
|
30
|
+
constructor(eventBus: EventBus, sagaService: CqrsSagaLifecycleService, runtimeContainer: ConstructorParameters<typeof CqrsBusBase>[0], compiledModules: ConstructorParameters<typeof CqrsBusBase>[1], logger: ConstructorParameters<typeof CqrsBusBase>[2], moduleOptions?: CqrsModuleOptions, registerRuntimeCleanup?: RuntimeCleanupRegistration, delegatedEventBus?: EventBusLifecycleService | undefined, shutdownDeadline?: CqrsShutdownDeadline, commandService?: CommandBusLifecycleService | undefined, queryService?: QueryBusLifecycleService | undefined);
|
|
24
31
|
onApplicationBootstrap(): Promise<void>;
|
|
25
32
|
onApplicationShutdown(): Promise<void>;
|
|
26
33
|
/**
|
|
@@ -49,11 +56,12 @@ export declare class CqrsEventBusService extends CqrsBusBase implements CqrsEven
|
|
|
49
56
|
publishAll<TEvent extends IEvent>(events: readonly TEvent[], context?: CqrsDispatchContext): Promise<void>;
|
|
50
57
|
private runPublishPipeline;
|
|
51
58
|
private runPublishAllPipeline;
|
|
52
|
-
private assertAcceptingNewWork;
|
|
53
59
|
private trackPublishPipeline;
|
|
54
|
-
private
|
|
55
|
-
private
|
|
60
|
+
private assertAcceptingNewWork;
|
|
61
|
+
private markApplicationShutdownStarted;
|
|
62
|
+
private createPublishContext;
|
|
56
63
|
private resolveShutdownDrainTimeoutMs;
|
|
64
|
+
private resolveRemainingShutdownDrainTimeoutMs;
|
|
57
65
|
private matchEventDescriptors;
|
|
58
66
|
private ensureDiscovered;
|
|
59
67
|
private discoverHandlers;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../../src/buses/event-bus.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,QAAQ,EAA+B,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../../src/buses/event-bus.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,QAAQ,EAAE,wBAAwB,EAA+B,MAAM,mBAAmB,CAAC;AACzG,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAGjH,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAM9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAyC,MAAM,EAAiB,MAAM,aAAa,CAAC;AAGnI,OAAO,EAAiC,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACxF,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAiB1D;;;;;GAKG;AACH,qBAaa,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;IAE9B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAlB/B,OAAO,CAAC,WAAW,CAAgC;IACnD,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAA0B;IAC9D,OAAO,CAAC,cAAc,CAAsF;IAC5G,OAAO,CAAC,8BAA8B,CAA2B;gBAG9C,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,EACtD,sBAAsB,GAAE,0BAAkD,EACzD,iBAAiB,GAAE,wBAAwB,GAAG,SAAqB,EACnE,gBAAgB,GAAE,oBAAiD,EACnE,cAAc,GAAE,0BAA0B,GAAG,SAAqB,EAClE,YAAY,GAAE,wBAAwB,GAAG,SAAqB;IAU3E,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAYvC,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5C;;;;OAIG;IACH,4BAA4B;IAqB5B;;;;;;;;OAQG;IACG,OAAO,CAAC,MAAM,SAAS,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IASjG;;;;;;OAMG;IACG,UAAU,CAAC,MAAM,SAAS,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;YASlG,kBAAkB;YAuBlB,qBAAqB;YAMrB,oBAAoB;IAUlC,OAAO,CAAC,sBAAsB;IAc9B,OAAO,CAAC,8BAA8B;IAatC,OAAO,CAAC,oBAAoB;IAkB5B,OAAO,CAAC,6BAA6B;IAUrC,OAAO,CAAC,sCAAsC;IAI9C,OAAO,CAAC,qBAAqB;YAIf,gBAAgB;YAchB,gBAAgB;IAe9B,OAAO,CAAC,wBAAwB;CAGjC"}
|