@fluojs/cqrs 1.1.0 → 1.1.2
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 +69 -4
- package/README.md +69 -4
- package/dist/buses/command-bus.d.ts +3 -2
- package/dist/buses/command-bus.d.ts.map +1 -1
- package/dist/buses/command-bus.js +4 -3
- package/dist/buses/event-bus.d.ts +7 -4
- package/dist/buses/event-bus.d.ts.map +1 -1
- package/dist/buses/event-bus.js +21 -10
- package/dist/buses/query-bus.d.ts +4 -3
- package/dist/buses/query-bus.d.ts.map +1 -1
- package/dist/buses/query-bus.js +4 -3
- package/dist/buses/saga-bus.d.ts +9 -5
- package/dist/buses/saga-bus.d.ts.map +1 -1
- package/dist/buses/saga-bus.js +33 -23
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/module.d.ts +0 -8
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +35 -11
- package/dist/types.d.ts +24 -8
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
package/README.ko.md
CHANGED
|
@@ -10,6 +10,7 @@ fluo 애플리케이션을 위한 CQRS 패키지입니다. 부트스트랩 시
|
|
|
10
10
|
- [사용 시점](#사용-시점)
|
|
11
11
|
- [빠른 시작](#빠른-시작)
|
|
12
12
|
- [공통 패턴](#공통-패턴)
|
|
13
|
+
- [Read Projection](#read-projection)
|
|
13
14
|
- [Saga 프로세스 매니저](#saga-프로세스-매니저)
|
|
14
15
|
- [Event 발행 계약](#event-발행-계약)
|
|
15
16
|
- [심볼 토큰](#심볼-토큰)
|
|
@@ -79,13 +80,74 @@ class AppModule {}
|
|
|
79
80
|
|
|
80
81
|
## 공통 패턴
|
|
81
82
|
|
|
83
|
+
### Read Projection
|
|
84
|
+
|
|
85
|
+
Read projection은 query에 맞춘 데이터를 write model과 분리해서 유지합니다. write가 성공한 뒤 domain event를 publish하고, `@EventHandler(...)`에서 projection을 갱신한 다음, `@QueryHandler(...)`에서 그 denormalized view를 제공합니다.
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { Inject } from '@fluojs/core';
|
|
89
|
+
import {
|
|
90
|
+
EventHandler,
|
|
91
|
+
IEvent,
|
|
92
|
+
IEventHandler,
|
|
93
|
+
IQuery,
|
|
94
|
+
IQueryHandler,
|
|
95
|
+
QueryHandler,
|
|
96
|
+
} from '@fluojs/cqrs';
|
|
97
|
+
|
|
98
|
+
interface OrderSummaryView {
|
|
99
|
+
id: string;
|
|
100
|
+
customerId: string;
|
|
101
|
+
status: 'placed';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
class OrderPlacedEvent implements IEvent {
|
|
105
|
+
constructor(
|
|
106
|
+
public readonly orderId: string,
|
|
107
|
+
public readonly customerId: string,
|
|
108
|
+
) {}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
class GetOrderSummaryQuery implements IQuery<OrderSummaryView | undefined> {
|
|
112
|
+
constructor(public readonly orderId: string) {}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@Inject(OrderSummaryProjectionStore)
|
|
116
|
+
@EventHandler(OrderPlacedEvent)
|
|
117
|
+
class OrderSummaryProjectionHandler implements IEventHandler<OrderPlacedEvent> {
|
|
118
|
+
constructor(private readonly store: OrderSummaryProjectionStore) {}
|
|
119
|
+
|
|
120
|
+
async handle(event: OrderPlacedEvent): Promise<void> {
|
|
121
|
+
await this.store.upsert({
|
|
122
|
+
id: event.orderId,
|
|
123
|
+
customerId: event.customerId,
|
|
124
|
+
status: 'placed',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@Inject(OrderSummaryProjectionStore)
|
|
130
|
+
@QueryHandler(GetOrderSummaryQuery)
|
|
131
|
+
class GetOrderSummaryHandler
|
|
132
|
+
implements IQueryHandler<GetOrderSummaryQuery, OrderSummaryView | undefined>
|
|
133
|
+
{
|
|
134
|
+
constructor(private readonly store: OrderSummaryProjectionStore) {}
|
|
135
|
+
|
|
136
|
+
async execute(query: GetOrderSummaryQuery): Promise<OrderSummaryView | undefined> {
|
|
137
|
+
return this.store.findById(query.orderId);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
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하게 유지하세요.
|
|
143
|
+
|
|
82
144
|
### Saga 프로세스 매니저
|
|
83
145
|
|
|
84
146
|
Saga를 사용하면 이벤트를 구독하고 새로운 Command를 트리거하여 복잡한 장기 실행 워크플로우를 구성할 수 있습니다.
|
|
85
147
|
|
|
86
148
|
```typescript
|
|
87
149
|
import { Inject } from '@fluojs/core';
|
|
88
|
-
import { Saga, ISaga, IEvent, ICommand, CommandBusLifecycleService } from '@fluojs/cqrs';
|
|
150
|
+
import { Saga, ISaga, IEvent, ICommand, CqrsDispatchContext, CommandBusLifecycleService } from '@fluojs/cqrs';
|
|
89
151
|
|
|
90
152
|
class UserCreatedEvent implements IEvent {
|
|
91
153
|
constructor(public readonly userId: string) {}
|
|
@@ -100,17 +162,19 @@ class SendWelcomeEmailCommand implements ICommand {
|
|
|
100
162
|
class UserSaga implements ISaga<UserCreatedEvent> {
|
|
101
163
|
constructor(private readonly commandBus: CommandBusLifecycleService) {}
|
|
102
164
|
|
|
103
|
-
async handle(event: UserCreatedEvent): Promise<void> {
|
|
104
|
-
await this.commandBus.execute(new SendWelcomeEmailCommand(event.userId));
|
|
165
|
+
async handle(event: UserCreatedEvent, context?: CqrsDispatchContext): Promise<void> {
|
|
166
|
+
await this.commandBus.execute(new SendWelcomeEmailCommand(event.userId), context);
|
|
105
167
|
}
|
|
106
168
|
}
|
|
107
169
|
```
|
|
108
170
|
|
|
109
171
|
Saga 실행은 같은 프로세스 안에서 동일 saga route로 순환 재진입하거나 중첩 hop 수가 32를 넘으면 `SagaTopologyError`로 즉시 실패합니다. 서로 다른 이벤트 단계를 순차 처리하는 multi-stage saga는 계속 허용되지만, in-process saga graph 전체는 비순환(acyclic) 구조를 유지해야 하며, 의도적인 순환/피드백 루프나 더 긴 체인은 외부 transport, scheduler, 또는 다른 bounded boundary 뒤로 이동해야 합니다.
|
|
110
172
|
|
|
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입니다. 직접 생성하거나 검사하거나 topology field에 의존하지 마세요. 해당 field는 내부 runtime state입니다.
|
|
174
|
+
|
|
111
175
|
### Event 발행 계약
|
|
112
176
|
|
|
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 완료를 의미하지 않습니다.
|
|
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 상태로 전환합니다. Shutdown이 시작되면 새로운 `publish(...)`, `publishAll(...)`, direct saga dispatch 호출은 거부됩니다. 이미 진행 중인 publish와 saga 작업은 bounded shutdown window 안에서 계속 drain됩니다. 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
178
|
|
|
115
179
|
각 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
180
|
|
|
@@ -150,6 +214,7 @@ class TokenInjectedService {
|
|
|
150
214
|
### 인터페이스
|
|
151
215
|
- `ICommand`, `IQuery<T>`, `IEvent`: 메시지 마커 인터페이스입니다.
|
|
152
216
|
- `ICommandHandler<C, R>`, `IQueryHandler<Q, R>`, `IEventHandler<E>`, `ISaga<E>`: 핸들러 계약입니다.
|
|
217
|
+
- `CqrsDispatchContext`: handler와 saga에서 nested CQRS dispatch로 그대로 전달하는 opaque optional context 값입니다. 공개 field를 노출하지 않으며, provider assembly는 공개 `createCqrsProviders(...)` helper가 아니라 `CqrsModule.forRoot(...)` facade 뒤에 유지됩니다.
|
|
153
218
|
|
|
154
219
|
### 오류
|
|
155
220
|
- `CommandHandlerNotFoundException`, `QueryHandlerNotFoundException`: bus에 일치하는 handler가 없을 때 발생합니다.
|
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ CQRS primitives for fluo applications with bootstrap-time handler discovery, com
|
|
|
10
10
|
- [When to Use](#when-to-use)
|
|
11
11
|
- [Quick Start](#quick-start)
|
|
12
12
|
- [Common Patterns](#common-patterns)
|
|
13
|
+
- [Read Projections](#read-projections)
|
|
13
14
|
- [Saga Process Managers](#saga-process-managers)
|
|
14
15
|
- [Event Publishing Contracts](#event-publishing-contracts)
|
|
15
16
|
- [Symbol Tokens](#symbol-tokens)
|
|
@@ -79,13 +80,74 @@ class AppModule {}
|
|
|
79
80
|
|
|
80
81
|
## Common Patterns
|
|
81
82
|
|
|
83
|
+
### Read Projections
|
|
84
|
+
|
|
85
|
+
Read projections keep query-shaped data separate from the write model. Publish a domain event after the write succeeds, update the projection from an `@EventHandler(...)`, and serve that denormalized view from a `@QueryHandler(...)`.
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { Inject } from '@fluojs/core';
|
|
89
|
+
import {
|
|
90
|
+
EventHandler,
|
|
91
|
+
IEvent,
|
|
92
|
+
IEventHandler,
|
|
93
|
+
IQuery,
|
|
94
|
+
IQueryHandler,
|
|
95
|
+
QueryHandler,
|
|
96
|
+
} from '@fluojs/cqrs';
|
|
97
|
+
|
|
98
|
+
interface OrderSummaryView {
|
|
99
|
+
id: string;
|
|
100
|
+
customerId: string;
|
|
101
|
+
status: 'placed';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
class OrderPlacedEvent implements IEvent {
|
|
105
|
+
constructor(
|
|
106
|
+
public readonly orderId: string,
|
|
107
|
+
public readonly customerId: string,
|
|
108
|
+
) {}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
class GetOrderSummaryQuery implements IQuery<OrderSummaryView | undefined> {
|
|
112
|
+
constructor(public readonly orderId: string) {}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@Inject(OrderSummaryProjectionStore)
|
|
116
|
+
@EventHandler(OrderPlacedEvent)
|
|
117
|
+
class OrderSummaryProjectionHandler implements IEventHandler<OrderPlacedEvent> {
|
|
118
|
+
constructor(private readonly store: OrderSummaryProjectionStore) {}
|
|
119
|
+
|
|
120
|
+
async handle(event: OrderPlacedEvent): Promise<void> {
|
|
121
|
+
await this.store.upsert({
|
|
122
|
+
id: event.orderId,
|
|
123
|
+
customerId: event.customerId,
|
|
124
|
+
status: 'placed',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@Inject(OrderSummaryProjectionStore)
|
|
130
|
+
@QueryHandler(GetOrderSummaryQuery)
|
|
131
|
+
class GetOrderSummaryHandler
|
|
132
|
+
implements IQueryHandler<GetOrderSummaryQuery, OrderSummaryView | undefined>
|
|
133
|
+
{
|
|
134
|
+
constructor(private readonly store: OrderSummaryProjectionStore) {}
|
|
135
|
+
|
|
136
|
+
async execute(query: GetOrderSummaryQuery): Promise<OrderSummaryView | undefined> {
|
|
137
|
+
return this.store.findById(query.orderId);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
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.
|
|
143
|
+
|
|
82
144
|
### Saga Process Managers
|
|
83
145
|
|
|
84
146
|
Sagas allow you to listen for events and trigger new commands, enabling complex long-running workflows.
|
|
85
147
|
|
|
86
148
|
```typescript
|
|
87
149
|
import { Inject } from '@fluojs/core';
|
|
88
|
-
import { Saga, ISaga, IEvent, ICommand, CommandBusLifecycleService } from '@fluojs/cqrs';
|
|
150
|
+
import { Saga, ISaga, IEvent, ICommand, CqrsDispatchContext, CommandBusLifecycleService } from '@fluojs/cqrs';
|
|
89
151
|
|
|
90
152
|
class UserCreatedEvent implements IEvent {
|
|
91
153
|
constructor(public readonly userId: string) {}
|
|
@@ -100,17 +162,19 @@ class SendWelcomeEmailCommand implements ICommand {
|
|
|
100
162
|
class UserSaga implements ISaga<UserCreatedEvent> {
|
|
101
163
|
constructor(private readonly commandBus: CommandBusLifecycleService) {}
|
|
102
164
|
|
|
103
|
-
async handle(event: UserCreatedEvent): Promise<void> {
|
|
104
|
-
await this.commandBus.execute(new SendWelcomeEmailCommand(event.userId));
|
|
165
|
+
async handle(event: UserCreatedEvent, context?: CqrsDispatchContext): Promise<void> {
|
|
166
|
+
await this.commandBus.execute(new SendWelcomeEmailCommand(event.userId), context);
|
|
105
167
|
}
|
|
106
168
|
}
|
|
107
169
|
```
|
|
108
170
|
|
|
109
171
|
Saga execution fails fast with `SagaTopologyError` when an in-process publish chain re-enters the same saga route cyclically or exceeds 32 nested saga hops. Multi-stage sagas may still react to different event types in sequence, but in-process saga graphs must stay acyclic overall; move intentionally cyclic or long-running feedback loops behind an external transport, scheduler, or other bounded boundary.
|
|
110
172
|
|
|
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: do not construct it, inspect it, or depend on topology fields because those fields are internal runtime state.
|
|
174
|
+
|
|
111
175
|
### Event Publishing Contracts
|
|
112
176
|
|
|
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.
|
|
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; already active publish and saga work continues draining inside the bounded shutdown window. 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
178
|
|
|
115
179
|
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
180
|
|
|
@@ -150,6 +214,7 @@ class TokenInjectedService {
|
|
|
150
214
|
### Interfaces
|
|
151
215
|
- `ICommand`, `IQuery<T>`, `IEvent`: Marker interfaces for messages.
|
|
152
216
|
- `ICommandHandler<C, R>`, `IQueryHandler<Q, R>`, `IEventHandler<E>`, `ISaga<E>`: Handler contracts.
|
|
217
|
+
- `CqrsDispatchContext`: Opaque optional context value to pass through nested CQRS dispatch from handlers and sagas. It exposes no public fields; provider assembly remains behind `CqrsModule.forRoot(...)` rather than a public `createCqrsProviders(...)` helper.
|
|
153
218
|
|
|
154
219
|
### Errors
|
|
155
220
|
- `CommandHandlerNotFoundException`, `QueryHandlerNotFoundException`: Raised when a bus has no matching handler.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { OnApplicationBootstrap } from '@fluojs/runtime';
|
|
2
2
|
import { CqrsBusBase } from '../discovery.js';
|
|
3
|
-
import type { CommandBus, ICommand } from '../types.js';
|
|
3
|
+
import type { CommandBus, CqrsDispatchContext, ICommand } from '../types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Discovers and executes command handlers during application bootstrap and runtime dispatch.
|
|
6
6
|
*
|
|
@@ -16,12 +16,13 @@ export declare class CommandBusLifecycleService extends CqrsBusBase implements C
|
|
|
16
16
|
* Executes one command by dispatching it to the discovered handler for its constructor.
|
|
17
17
|
*
|
|
18
18
|
* @param command Command instance to execute.
|
|
19
|
+
* @param context Optional saga dispatch context to pass through nested CQRS calls.
|
|
19
20
|
* @returns The resolved handler result.
|
|
20
21
|
*
|
|
21
22
|
* @throws {CommandHandlerNotFoundException} When no handler is registered for the command type.
|
|
22
23
|
* @throws {InvariantError} When the resolved provider does not implement `execute(command)`.
|
|
23
24
|
*/
|
|
24
|
-
execute<TCommand extends ICommand, TResult = void>(command: TCommand): Promise<TResult>;
|
|
25
|
+
execute<TCommand extends ICommand, TResult = void>(command: TCommand, context?: CqrsDispatchContext): Promise<TResult>;
|
|
25
26
|
private ensureDiscovered;
|
|
26
27
|
private discoverHandlers;
|
|
27
28
|
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,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAE9D,OAAO,EAAE,WAAW,EAAiC,MAAM,iBAAiB,CAAC;AAG7E,OAAO,KAAK,EACV,UAAU,EAGV,mBAAmB,EACnB,QAAQ,EAET,MAAM,aAAa,CAAC;AAUrB;;;;;GAKG;AACH,qBACa,0BAA2B,SAAQ,WAAY,YAAW,UAAU,EAAE,sBAAsB;IACvG,OAAO,CAAC,WAAW,CAAoD;IACvE,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,UAAU,CAAS;IAErB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7C;;;;;;;;;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;YAmB9G,gBAAgB;YAchB,gBAAgB;IAe9B,OAAO,CAAC,0BAA0B;CAmDnC"}
|
|
@@ -6,9 +6,9 @@ function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.descrip
|
|
|
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
8
|
import { APPLICATION_LOGGER, COMPILED_MODULES, RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
|
|
9
|
+
import { CqrsBusBase, createDuplicateHandlerMessage } from '../discovery.js';
|
|
9
10
|
import { CommandHandlerNotFoundException, DuplicateCommandHandlerError } from '../errors.js';
|
|
10
11
|
import { getCommandHandlerMetadata } from '../metadata.js';
|
|
11
|
-
import { CqrsBusBase, createDuplicateHandlerMessage } from '../discovery.js';
|
|
12
12
|
function isCommandHandler(value) {
|
|
13
13
|
if (typeof value !== 'object' || value === null) {
|
|
14
14
|
return false;
|
|
@@ -38,12 +38,13 @@ class CommandBusLifecycleService extends CqrsBusBase {
|
|
|
38
38
|
* Executes one command by dispatching it to the discovered handler for its constructor.
|
|
39
39
|
*
|
|
40
40
|
* @param command Command instance to execute.
|
|
41
|
+
* @param context Optional saga dispatch context to pass through nested CQRS calls.
|
|
41
42
|
* @returns The resolved handler result.
|
|
42
43
|
*
|
|
43
44
|
* @throws {CommandHandlerNotFoundException} When no handler is registered for the command type.
|
|
44
45
|
* @throws {InvariantError} When the resolved provider does not implement `execute(command)`.
|
|
45
46
|
*/
|
|
46
|
-
async execute(command) {
|
|
47
|
+
async execute(command, context) {
|
|
47
48
|
await this.ensureDiscovered();
|
|
48
49
|
const commandType = command.constructor;
|
|
49
50
|
const descriptor = this.descriptors.get(commandType);
|
|
@@ -54,7 +55,7 @@ class CommandBusLifecycleService extends CqrsBusBase {
|
|
|
54
55
|
if (!isCommandHandler(instance)) {
|
|
55
56
|
throw new InvariantError(`Command handler ${descriptor.targetType.name} must implement execute(command).`);
|
|
56
57
|
}
|
|
57
|
-
return await instance.execute(command);
|
|
58
|
+
return await instance.execute(command, context);
|
|
58
59
|
}
|
|
59
60
|
async ensureDiscovered() {
|
|
60
61
|
if (this.discovered) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type EventBus } from '@fluojs/event-bus';
|
|
2
|
-
import type {
|
|
2
|
+
import type { OnApplicationBootstrap, OnApplicationShutdown } from '@fluojs/runtime';
|
|
3
3
|
import { CqrsBusBase } from '../discovery.js';
|
|
4
4
|
import type { CqrsModuleOptions } from '../module.js';
|
|
5
|
-
import type { CqrsEventBus, IEvent } from '../types.js';
|
|
5
|
+
import type { CqrsDispatchContext, CqrsEventBus, IEvent } from '../types.js';
|
|
6
6
|
import { CqrsSagaLifecycleService } from './saga-bus.js';
|
|
7
7
|
/**
|
|
8
8
|
* CQRS-facing event bus that dispatches local event handlers, sagas, and the shared event transport.
|
|
@@ -33,20 +33,23 @@ export declare class CqrsEventBusService extends CqrsBusBase implements CqrsEven
|
|
|
33
33
|
* Publishes one event to matching CQRS handlers, sagas, and the shared event bus.
|
|
34
34
|
*
|
|
35
35
|
* @param event Event instance to publish.
|
|
36
|
+
* @param context Optional saga dispatch context to pass through nested CQRS calls.
|
|
36
37
|
* @returns A promise that resolves once all local CQRS side effects and delegated publication complete.
|
|
37
38
|
*
|
|
38
39
|
* @throws {InvariantError} When a discovered provider does not implement `handle(event)`.
|
|
39
40
|
*/
|
|
40
|
-
publish<TEvent extends IEvent>(event: TEvent): Promise<void>;
|
|
41
|
+
publish<TEvent extends IEvent>(event: TEvent, context?: CqrsDispatchContext): Promise<void>;
|
|
41
42
|
/**
|
|
42
43
|
* Publishes a batch of events sequentially through the CQRS event pipeline.
|
|
43
44
|
*
|
|
44
45
|
* @param events Event instances to publish in order.
|
|
46
|
+
* @param context Optional saga dispatch context to pass through nested CQRS calls.
|
|
45
47
|
* @returns A promise that resolves once all events are published.
|
|
46
48
|
*/
|
|
47
|
-
publishAll<TEvent extends IEvent>(events: readonly TEvent[]): Promise<void>;
|
|
49
|
+
publishAll<TEvent extends IEvent>(events: readonly TEvent[], context?: CqrsDispatchContext): Promise<void>;
|
|
48
50
|
private runPublishPipeline;
|
|
49
51
|
private runPublishAllPipeline;
|
|
52
|
+
private assertAcceptingNewWork;
|
|
50
53
|
private trackPublishPipeline;
|
|
51
54
|
private drainActivePublishPipelines;
|
|
52
55
|
private awaitShutdownDrain;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../../src/buses/event-bus.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
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;AAC/E,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAGrF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAG9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAyC,MAAM,EAAiB,MAAM,aAAa,CAAC;AACnI,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;;;;;;;;OAQG;IACG,OAAO,CAAC,MAAM,SAAS,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjG;;;;;;OAMG;IACG,UAAU,CAAC,MAAM,SAAS,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;YAKlG,kBAAkB;YAiBlB,qBAAqB;IAMnC,OAAO,CAAC,sBAAsB;YAMhB,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,8 @@ 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 { CQRS_MODULE_OPTIONS } from '../tokens.js';
|
|
14
13
|
import { createCqrsPlatformStatusSnapshot } from '../status.js';
|
|
14
|
+
import { CQRS_MODULE_OPTIONS } from '../tokens.js';
|
|
15
15
|
import { CqrsSagaLifecycleService } from './saga-bus.js';
|
|
16
16
|
const DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS = 5000;
|
|
17
17
|
function isEventHandler(value) {
|
|
@@ -85,38 +85,49 @@ class CqrsEventBusService extends CqrsBusBase {
|
|
|
85
85
|
* Publishes one event to matching CQRS handlers, sagas, and the shared event bus.
|
|
86
86
|
*
|
|
87
87
|
* @param event Event instance to publish.
|
|
88
|
+
* @param context Optional saga dispatch context to pass through nested CQRS calls.
|
|
88
89
|
* @returns A promise that resolves once all local CQRS side effects and delegated publication complete.
|
|
89
90
|
*
|
|
90
91
|
* @throws {InvariantError} When a discovered provider does not implement `handle(event)`.
|
|
91
92
|
*/
|
|
92
|
-
async publish(event) {
|
|
93
|
-
|
|
93
|
+
async publish(event, context) {
|
|
94
|
+
this.assertAcceptingNewWork('publish');
|
|
95
|
+
await this.trackPublishPipeline(this.runPublishPipeline(event, context));
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
/**
|
|
97
99
|
* Publishes a batch of events sequentially through the CQRS event pipeline.
|
|
98
100
|
*
|
|
99
101
|
* @param events Event instances to publish in order.
|
|
102
|
+
* @param context Optional saga dispatch context to pass through nested CQRS calls.
|
|
100
103
|
* @returns A promise that resolves once all events are published.
|
|
101
104
|
*/
|
|
102
|
-
async publishAll(events) {
|
|
103
|
-
|
|
105
|
+
async publishAll(events, context) {
|
|
106
|
+
this.assertAcceptingNewWork('publishAll');
|
|
107
|
+
await this.trackPublishPipeline(this.runPublishAllPipeline(events, context));
|
|
104
108
|
}
|
|
105
|
-
async runPublishPipeline(event) {
|
|
109
|
+
async runPublishPipeline(event, context) {
|
|
106
110
|
await this.ensureDiscovered();
|
|
107
111
|
for (const descriptor of this.matchEventDescriptors(event)) {
|
|
108
112
|
const instance = await this.resolveHandlerInstance(descriptor.token);
|
|
109
113
|
if (!isEventHandler(instance)) {
|
|
110
114
|
throw new InvariantError(`Event handler ${descriptor.targetType.name} must implement handle(event).`);
|
|
111
115
|
}
|
|
112
|
-
await instance.handle(createIsolatedEvent(descriptor.eventType, event));
|
|
116
|
+
await instance.handle(createIsolatedEvent(descriptor.eventType, event), context);
|
|
113
117
|
}
|
|
114
|
-
await this.sagaService.dispatch(event
|
|
118
|
+
await this.sagaService.dispatch(event, context, {
|
|
119
|
+
allowDuringShutdown: true
|
|
120
|
+
});
|
|
115
121
|
await this.eventBus.publish(event);
|
|
116
122
|
}
|
|
117
|
-
async runPublishAllPipeline(events) {
|
|
123
|
+
async runPublishAllPipeline(events, context) {
|
|
118
124
|
for (const event of events) {
|
|
119
|
-
await this.
|
|
125
|
+
await this.runPublishPipeline(event, context);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
assertAcceptingNewWork(operation) {
|
|
129
|
+
if (this.lifecycleState === 'stopping' || this.lifecycleState === 'stopped') {
|
|
130
|
+
throw new InvariantError(`CQRS event bus cannot ${operation} after shutdown has started.`);
|
|
120
131
|
}
|
|
121
132
|
}
|
|
122
133
|
async trackPublishPipeline(pipeline) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { OnApplicationBootstrap } from '@fluojs/runtime';
|
|
2
2
|
import { CqrsBusBase } from '../discovery.js';
|
|
3
|
-
import type { IQuery, QueryBus } from '../types.js';
|
|
3
|
+
import type { CqrsDispatchContext, IQuery, QueryBus } from '../types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Discovers and executes query handlers during bootstrap and runtime dispatch.
|
|
6
6
|
*
|
|
@@ -16,12 +16,13 @@ export declare class QueryBusLifecycleService extends CqrsBusBase implements Que
|
|
|
16
16
|
* Executes one query by dispatching it to the discovered handler for its constructor.
|
|
17
17
|
*
|
|
18
18
|
* @param query Query instance to execute.
|
|
19
|
+
* @param context Optional saga dispatch context to pass through nested CQRS calls.
|
|
19
20
|
* @returns The resolved handler result.
|
|
20
21
|
*
|
|
21
22
|
* @throws {QueryHandlerNotFoundException} When no handler is registered for the query type.
|
|
22
23
|
* @throws {InvariantError} When the resolved provider does not implement `execute(query)`.
|
|
23
24
|
*/
|
|
24
|
-
execute<TQuery extends IQuery<TResult>, TResult = unknown>(query: TQuery): Promise<TResult>;
|
|
25
|
+
execute<TQuery extends IQuery<TResult>, TResult = unknown>(query: TQuery, context?: CqrsDispatchContext): Promise<TResult>;
|
|
25
26
|
private ensureDiscovered;
|
|
26
27
|
private discoverHandlers;
|
|
27
28
|
private discoverQueryDescriptors;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-bus.d.ts","sourceRoot":"","sources":["../../src/buses/query-bus.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"query-bus.d.ts","sourceRoot":"","sources":["../../src/buses/query-bus.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,sBAAsB,EACvB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,WAAW,EAAiC,MAAM,iBAAiB,CAAC;AAG7E,OAAO,KAAK,EACV,mBAAmB,EACnB,MAAM,EAEN,QAAQ,EAGT,MAAM,aAAa,CAAC;AAUrB;;;;;GAKG;AACH,qBACa,wBAAyB,SAAQ,WAAY,YAAW,QAAQ,EAAE,sBAAsB;IACnG,OAAO,CAAC,WAAW,CAAgD;IACnE,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,UAAU,CAAS;IAErB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7C;;;;;;;;;OASG;IACG,OAAO,CAAC,MAAM,SAAS,MAAM,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;YAmBlH,gBAAgB;YAchB,gBAAgB;IAe9B,OAAO,CAAC,wBAAwB;CAmDjC"}
|
package/dist/buses/query-bus.js
CHANGED
|
@@ -6,9 +6,9 @@ function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.descrip
|
|
|
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
8
|
import { APPLICATION_LOGGER, COMPILED_MODULES, RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
|
|
9
|
+
import { CqrsBusBase, createDuplicateHandlerMessage } from '../discovery.js';
|
|
9
10
|
import { DuplicateQueryHandlerError, QueryHandlerNotFoundException } from '../errors.js';
|
|
10
11
|
import { getQueryHandlerMetadata } from '../metadata.js';
|
|
11
|
-
import { CqrsBusBase, createDuplicateHandlerMessage } from '../discovery.js';
|
|
12
12
|
function isQueryHandler(value) {
|
|
13
13
|
if (typeof value !== 'object' || value === null) {
|
|
14
14
|
return false;
|
|
@@ -38,12 +38,13 @@ class QueryBusLifecycleService extends CqrsBusBase {
|
|
|
38
38
|
* Executes one query by dispatching it to the discovered handler for its constructor.
|
|
39
39
|
*
|
|
40
40
|
* @param query Query instance to execute.
|
|
41
|
+
* @param context Optional saga dispatch context to pass through nested CQRS calls.
|
|
41
42
|
* @returns The resolved handler result.
|
|
42
43
|
*
|
|
43
44
|
* @throws {QueryHandlerNotFoundException} When no handler is registered for the query type.
|
|
44
45
|
* @throws {InvariantError} When the resolved provider does not implement `execute(query)`.
|
|
45
46
|
*/
|
|
46
|
-
async execute(query) {
|
|
47
|
+
async execute(query, context) {
|
|
47
48
|
await this.ensureDiscovered();
|
|
48
49
|
const queryType = query.constructor;
|
|
49
50
|
const descriptor = this.descriptors.get(queryType);
|
|
@@ -54,7 +55,7 @@ class QueryBusLifecycleService extends CqrsBusBase {
|
|
|
54
55
|
if (!isQueryHandler(instance)) {
|
|
55
56
|
throw new InvariantError(`Query handler ${descriptor.targetType.name} must implement execute(query).`);
|
|
56
57
|
}
|
|
57
|
-
return await instance.execute(query);
|
|
58
|
+
return await instance.execute(query, context);
|
|
58
59
|
}
|
|
59
60
|
async ensureDiscovered() {
|
|
60
61
|
if (this.discovered) {
|
package/dist/buses/saga-bus.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { OnApplicationBootstrap, OnApplicationShutdown } from '@fluojs/runtime';
|
|
2
2
|
import { CqrsBusBase } from '../discovery.js';
|
|
3
3
|
import type { CqrsModuleOptions } from '../module.js';
|
|
4
|
-
import type { IEvent } from '../types.js';
|
|
4
|
+
import type { CqrsDispatchContext, IEvent } from '../types.js';
|
|
5
|
+
interface SagaDispatchOptions {
|
|
6
|
+
readonly allowDuringShutdown?: boolean;
|
|
7
|
+
}
|
|
5
8
|
/**
|
|
6
9
|
* Runtime saga coordinator that discovers `@Saga()` providers and serializes execution per saga token.
|
|
7
10
|
*
|
|
8
|
-
* The service prevents re-entrant dispatch loops within the same
|
|
11
|
+
* The service prevents re-entrant dispatch loops within the same explicit dispatch context and waits for
|
|
9
12
|
* in-flight saga chains during shutdown so lifecycle guarantees remain predictable.
|
|
10
13
|
*/
|
|
11
14
|
export declare class CqrsSagaLifecycleService extends CqrsBusBase implements OnApplicationBootstrap, OnApplicationShutdown {
|
|
@@ -16,7 +19,6 @@ export declare class CqrsSagaLifecycleService extends CqrsBusBase implements OnA
|
|
|
16
19
|
private readonly executionChains;
|
|
17
20
|
private lifecycleState;
|
|
18
21
|
private readonly pendingDispatches;
|
|
19
|
-
private readonly dispatchContext;
|
|
20
22
|
private shutdownDrainTimeouts;
|
|
21
23
|
constructor(runtimeContainer: ConstructorParameters<typeof CqrsBusBase>[0], compiledModules: ConstructorParameters<typeof CqrsBusBase>[1], logger: ConstructorParameters<typeof CqrsBusBase>[2], moduleOptions?: CqrsModuleOptions);
|
|
22
24
|
onApplicationBootstrap(): Promise<void>;
|
|
@@ -39,16 +41,18 @@ export declare class CqrsSagaLifecycleService extends CqrsBusBase implements OnA
|
|
|
39
41
|
* @param event Event instance that may trigger one or more sagas.
|
|
40
42
|
* @returns A promise that resolves once all matching saga chains for the event complete.
|
|
41
43
|
*/
|
|
42
|
-
dispatch<TEvent extends IEvent>(event: TEvent): Promise<void>;
|
|
44
|
+
dispatch<TEvent extends IEvent>(event: TEvent, context?: CqrsDispatchContext, options?: SagaDispatchOptions): Promise<void>;
|
|
45
|
+
private assertAcceptingNewWork;
|
|
43
46
|
private matchSagaDescriptors;
|
|
44
47
|
private dispatchWithOrdering;
|
|
45
48
|
private drainActiveSagaWork;
|
|
46
49
|
private awaitShutdownDrain;
|
|
47
50
|
private resolveShutdownDrainTimeoutMs;
|
|
48
|
-
private
|
|
51
|
+
private createDispatchContext;
|
|
49
52
|
private invokeSaga;
|
|
50
53
|
private ensureDiscovered;
|
|
51
54
|
private discoverHandlers;
|
|
52
55
|
private discoverSagaDescriptors;
|
|
53
56
|
}
|
|
57
|
+
export {};
|
|
54
58
|
//# sourceMappingURL=saga-bus.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"saga-bus.d.ts","sourceRoot":"","sources":["../../src/buses/saga-bus.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"saga-bus.d.ts","sourceRoot":"","sources":["../../src/buses/saga-bus.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAGrF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAI9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,OAAO,KAAK,EAAE,mBAAmB,EAAiB,MAAM,EAAyB,MAAM,aAAa,CAAC;AAMrG,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACxC;AAsCD;;;;;GAKG;AACH,qBACa,wBAAyB,SAAQ,WAAY,YAAW,sBAAsB,EAAE,qBAAqB;IAa9G,OAAO,CAAC,QAAQ,CAAC,aAAa;IAZhC,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,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,EAAE,OAAO,CAAC,EAAE,mBAAmB,EAAE,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAarI,OAAO,CAAC,sBAAsB;IAU9B,OAAO,CAAC,oBAAoB;YAYd,oBAAoB;YA0CpB,mBAAmB;YAmBnB,kBAAkB;IAiBhC,OAAO,CAAC,6BAA6B;IAUrC,OAAO,CAAC,qBAAqB;YAaf,UAAU;YAoBV,gBAAgB;YAchB,gBAAgB;IAiB9B,OAAO,CAAC,uBAAuB;CA+ChC"}
|
package/dist/buses/saga-bus.js
CHANGED
|
@@ -4,8 +4,7 @@ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol"
|
|
|
4
4
|
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
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
|
-
import {
|
|
8
|
-
import { Inject, InvariantError, FluoError } from '@fluojs/core';
|
|
7
|
+
import { FluoError, Inject, InvariantError } from '@fluojs/core';
|
|
9
8
|
import { APPLICATION_LOGGER, COMPILED_MODULES, RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
|
|
10
9
|
import { CqrsBusBase } from '../discovery.js';
|
|
11
10
|
import { SagaExecutionError, SagaTopologyError } from '../errors.js';
|
|
@@ -14,6 +13,7 @@ import { getSagaMetadata } from '../metadata.js';
|
|
|
14
13
|
import { CQRS_MODULE_OPTIONS } from '../tokens.js';
|
|
15
14
|
const MAX_NESTED_SAGA_DEPTH = 32;
|
|
16
15
|
const DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS = 5000;
|
|
16
|
+
const cqrsDispatchContextStateBrand = Symbol('fluo.cqrs.dispatchContextState');
|
|
17
17
|
function isSaga(value) {
|
|
18
18
|
if (typeof value !== 'object' || value === null) {
|
|
19
19
|
return false;
|
|
@@ -26,11 +26,17 @@ function toErrorMessage(error) {
|
|
|
26
26
|
}
|
|
27
27
|
return String(error);
|
|
28
28
|
}
|
|
29
|
+
function isCqrsDispatchContextState(context) {
|
|
30
|
+
if (typeof context !== 'object' || context === null) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return context[cqrsDispatchContextStateBrand] === true;
|
|
34
|
+
}
|
|
29
35
|
|
|
30
36
|
/**
|
|
31
37
|
* Runtime saga coordinator that discovers `@Saga()` providers and serializes execution per saga token.
|
|
32
38
|
*
|
|
33
|
-
* The service prevents re-entrant dispatch loops within the same
|
|
39
|
+
* The service prevents re-entrant dispatch loops within the same explicit dispatch context and waits for
|
|
34
40
|
* in-flight saga chains during shutdown so lifecycle guarantees remain predictable.
|
|
35
41
|
*/
|
|
36
42
|
let _CqrsSagaLifecycleSer;
|
|
@@ -44,7 +50,6 @@ class CqrsSagaLifecycleService extends CqrsBusBase {
|
|
|
44
50
|
executionChains = new Map();
|
|
45
51
|
lifecycleState = 'created';
|
|
46
52
|
pendingDispatches = new Set();
|
|
47
|
-
dispatchContext = new AsyncLocalStorage();
|
|
48
53
|
shutdownDrainTimeouts = 0;
|
|
49
54
|
constructor(runtimeContainer, compiledModules, logger, moduleOptions = {}) {
|
|
50
55
|
super(runtimeContainer, compiledModules, logger);
|
|
@@ -92,13 +97,22 @@ class CqrsSagaLifecycleService extends CqrsBusBase {
|
|
|
92
97
|
* @param event Event instance that may trigger one or more sagas.
|
|
93
98
|
* @returns A promise that resolves once all matching saga chains for the event complete.
|
|
94
99
|
*/
|
|
95
|
-
async dispatch(event) {
|
|
100
|
+
async dispatch(event, context, options = {}) {
|
|
101
|
+
this.assertAcceptingNewWork(options);
|
|
96
102
|
await this.ensureDiscovered();
|
|
97
103
|
const descriptors = this.matchSagaDescriptors(event);
|
|
98
104
|
if (descriptors.length === 0) {
|
|
99
105
|
return;
|
|
100
106
|
}
|
|
101
|
-
await Promise.all(descriptors.map(descriptor => this.dispatchWithOrdering(descriptor, event)));
|
|
107
|
+
await Promise.all(descriptors.map(descriptor => this.dispatchWithOrdering(descriptor, event, context)));
|
|
108
|
+
}
|
|
109
|
+
assertAcceptingNewWork(options) {
|
|
110
|
+
if (options.allowDuringShutdown) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (this.lifecycleState === 'stopping' || this.lifecycleState === 'stopped') {
|
|
114
|
+
throw new InvariantError('CQRS saga bus cannot dispatch after shutdown has started.');
|
|
115
|
+
}
|
|
102
116
|
}
|
|
103
117
|
matchSagaDescriptors(event) {
|
|
104
118
|
const descriptors = [];
|
|
@@ -109,28 +123,24 @@ class CqrsSagaLifecycleService extends CqrsBusBase {
|
|
|
109
123
|
}
|
|
110
124
|
return descriptors;
|
|
111
125
|
}
|
|
112
|
-
async dispatchWithOrdering(descriptor, event) {
|
|
113
|
-
const
|
|
126
|
+
async dispatchWithOrdering(descriptor, event, activeContext) {
|
|
127
|
+
const activeState = isCqrsDispatchContextState(activeContext) ? activeContext : undefined;
|
|
114
128
|
const routeLabel = `${descriptor.targetType.name}(${descriptor.eventType.name})`;
|
|
115
|
-
const isActiveRoute =
|
|
116
|
-
const isActiveToken =
|
|
129
|
+
const isActiveRoute = activeState?.activeRoutes.some(route => route.token === descriptor.token && route.eventType === descriptor.eventType);
|
|
130
|
+
const isActiveToken = activeState?.activeRoutes.some(route => route.token === descriptor.token) ?? false;
|
|
117
131
|
if (isActiveRoute) {
|
|
118
|
-
throw new SagaTopologyError(`Saga ${descriptor.targetType.name} re-entered an unsafe cycle while handling ${descriptor.eventType.name}. ` + `Active saga path: ${[...(
|
|
132
|
+
throw new SagaTopologyError(`Saga ${descriptor.targetType.name} re-entered an unsafe cycle while handling ${descriptor.eventType.name}. ` + `Active saga path: ${[...(activeState?.path ?? []), routeLabel].join(' -> ')}.`);
|
|
119
133
|
}
|
|
120
|
-
if ((
|
|
134
|
+
if ((activeState?.depth ?? 0) >= MAX_NESTED_SAGA_DEPTH) {
|
|
121
135
|
throw new SagaTopologyError(`Saga ${descriptor.targetType.name} exceeded the maximum nested saga depth of ${MAX_NESTED_SAGA_DEPTH} while handling ${descriptor.eventType.name}. ` + 'Keep in-process saga graphs acyclic and externally bounded.');
|
|
122
136
|
}
|
|
123
137
|
if (isActiveToken) {
|
|
124
|
-
await this.
|
|
125
|
-
await this.invokeSaga(descriptor, event);
|
|
126
|
-
});
|
|
138
|
+
await this.invokeSaga(descriptor, event, this.createDispatchContext(activeState, descriptor, routeLabel));
|
|
127
139
|
return;
|
|
128
140
|
}
|
|
129
141
|
const previous = this.executionChains.get(descriptor.token) ?? Promise.resolve();
|
|
130
142
|
const current = previous.then(async () => {
|
|
131
|
-
await this.
|
|
132
|
-
await this.invokeSaga(descriptor, event);
|
|
133
|
-
});
|
|
143
|
+
await this.invokeSaga(descriptor, event, this.createDispatchContext(activeState, descriptor, routeLabel));
|
|
134
144
|
});
|
|
135
145
|
this.executionChains.set(descriptor.token, current.catch(() => undefined));
|
|
136
146
|
this.pendingDispatches.add(current);
|
|
@@ -173,8 +183,9 @@ class CqrsSagaLifecycleService extends CqrsBusBase {
|
|
|
173
183
|
}
|
|
174
184
|
return Math.floor(timeoutMs);
|
|
175
185
|
}
|
|
176
|
-
|
|
177
|
-
|
|
186
|
+
createDispatchContext(activeContext, descriptor, routeLabel) {
|
|
187
|
+
return {
|
|
188
|
+
[cqrsDispatchContextStateBrand]: true,
|
|
178
189
|
activeRoutes: [...(activeContext?.activeRoutes ?? []), {
|
|
179
190
|
eventType: descriptor.eventType,
|
|
180
191
|
token: descriptor.token
|
|
@@ -182,15 +193,14 @@ class CqrsSagaLifecycleService extends CqrsBusBase {
|
|
|
182
193
|
depth: (activeContext?.depth ?? 0) + 1,
|
|
183
194
|
path: [...(activeContext?.path ?? []), routeLabel]
|
|
184
195
|
};
|
|
185
|
-
await this.dispatchContext.run(nextContext, callback);
|
|
186
196
|
}
|
|
187
|
-
async invokeSaga(descriptor, event) {
|
|
197
|
+
async invokeSaga(descriptor, event, context) {
|
|
188
198
|
const instance = await this.resolveHandlerInstance(descriptor.token);
|
|
189
199
|
if (!isSaga(instance)) {
|
|
190
200
|
throw new InvariantError(`Saga ${descriptor.targetType.name} must implement handle(event).`);
|
|
191
201
|
}
|
|
192
202
|
try {
|
|
193
|
-
await instance.handle(createIsolatedEvent(descriptor.eventType, event));
|
|
203
|
+
await instance.handle(createIsolatedEvent(descriptor.eventType, event), context);
|
|
194
204
|
} catch (error) {
|
|
195
205
|
if (error instanceof FluoError) {
|
|
196
206
|
throw error;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
export { CommandHandler, EventHandler, QueryHandler, Saga } from './decorators.js';
|
|
2
|
-
export { DuplicateCommandHandlerError, DuplicateEventHandlerError, DuplicateQueryHandlerError, CommandHandlerNotFoundException, QueryHandlerNotFoundException, SagaExecutionError, SagaTopologyError, } from './errors.js';
|
|
3
|
-
export { commandHandlerMetadataSymbol, defineCommandHandlerMetadata, defineEventHandlerMetadata, defineQueryHandlerMetadata, eventHandlerMetadataSymbol, getCommandHandlerMetadata, getCommandHandlerMetadataEntry, getEventHandlerMetadata, getQueryHandlerMetadata, getQueryHandlerMetadataEntry, queryHandlerMetadataSymbol, defineSagaMetadata, getSagaMetadata, sagaMetadataSymbol, } from './metadata.js';
|
|
4
|
-
export { CqrsModule, type CqrsModuleOptions } from './module.js';
|
|
5
|
-
export * from './status.js';
|
|
6
1
|
export { CommandBusLifecycleService } from './buses/command-bus.js';
|
|
7
2
|
export { CqrsEventBusService } from './buses/event-bus.js';
|
|
8
3
|
export { QueryBusLifecycleService } from './buses/query-bus.js';
|
|
4
|
+
export { CommandHandler, EventHandler, QueryHandler, Saga } from './decorators.js';
|
|
5
|
+
export { CommandHandlerNotFoundException, DuplicateCommandHandlerError, DuplicateEventHandlerError, DuplicateQueryHandlerError, QueryHandlerNotFoundException, SagaExecutionError, SagaTopologyError, } from './errors.js';
|
|
6
|
+
export { commandHandlerMetadataSymbol, defineCommandHandlerMetadata, defineEventHandlerMetadata, defineQueryHandlerMetadata, defineSagaMetadata, eventHandlerMetadataSymbol, getCommandHandlerMetadata, getCommandHandlerMetadataEntry, getEventHandlerMetadata, getQueryHandlerMetadata, getQueryHandlerMetadataEntry, getSagaMetadata, queryHandlerMetadataSymbol, sagaMetadataSymbol, } from './metadata.js';
|
|
7
|
+
export { CqrsModule, type CqrsModuleOptions } from './module.js';
|
|
8
|
+
export * from './status.js';
|
|
9
9
|
export { COMMAND_BUS, EVENT_BUS, QUERY_BUS } from './tokens.js';
|
|
10
|
-
export type { CommandBus, CommandHandlerClass, CommandHandlerDescriptor, CommandHandlerMetadata, CommandType, CqrsEventBus, CqrsEventType,
|
|
10
|
+
export type { CommandBus, CommandHandlerClass, CommandHandlerDescriptor, CommandHandlerMetadata, CommandType, CqrsDispatchContext, CqrsEventBus, CqrsEventType, EventHandlerClass, EventHandlerDescriptor, EventHandlerMetadata, ICommand, ICommandHandler, IEvent, IEventHandler, IQuery, IQueryHandler, ISaga, QueryBus, QueryHandlerClass, QueryHandlerDescriptor, QueryHandlerMetadata, QueryType, SagaClass, SagaDescriptor, SagaMetadata, } from './types.js';
|
|
11
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACnF,OAAO,EACL,4BAA4B,EAC5B,0BAA0B,EAC1B,0BAA0B,EAC1B
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACnF,OAAO,EACL,+BAA+B,EAC/B,4BAA4B,EAC5B,0BAA0B,EAC1B,0BAA0B,EAC1B,6BAA6B,EAC7B,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,4BAA4B,EAC5B,4BAA4B,EAC5B,0BAA0B,EAC1B,0BAA0B,EAC1B,kBAAkB,EAClB,0BAA0B,EAC1B,yBAAyB,EACzB,8BAA8B,EAC9B,uBAAuB,EACvB,uBAAuB,EACvB,4BAA4B,EAC5B,eAAe,EACf,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACjE,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAChE,YAAY,EACV,UAAU,EACV,mBAAmB,EACnB,wBAAwB,EACxB,sBAAsB,EACtB,WAAW,EACX,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,sBAAsB,EACtB,oBAAoB,EACpB,QAAQ,EACR,eAAe,EACf,MAAM,EACN,aAAa,EACb,MAAM,EACN,aAAa,EACb,KAAK,EACL,QAAQ,EACR,iBAAiB,EACjB,sBAAsB,EACtB,oBAAoB,EACpB,SAAS,EACT,SAAS,EACT,cAAc,EACd,YAAY,GACb,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export { CommandHandler, EventHandler, QueryHandler, Saga } from './decorators.js';
|
|
2
|
-
export { DuplicateCommandHandlerError, DuplicateEventHandlerError, DuplicateQueryHandlerError, CommandHandlerNotFoundException, QueryHandlerNotFoundException, SagaExecutionError, SagaTopologyError } from './errors.js';
|
|
3
|
-
export { commandHandlerMetadataSymbol, defineCommandHandlerMetadata, defineEventHandlerMetadata, defineQueryHandlerMetadata, eventHandlerMetadataSymbol, getCommandHandlerMetadata, getCommandHandlerMetadataEntry, getEventHandlerMetadata, getQueryHandlerMetadata, getQueryHandlerMetadataEntry, queryHandlerMetadataSymbol, defineSagaMetadata, getSagaMetadata, sagaMetadataSymbol } from './metadata.js';
|
|
4
|
-
export { CqrsModule } from './module.js';
|
|
5
|
-
export * from './status.js';
|
|
6
1
|
export { CommandBusLifecycleService } from './buses/command-bus.js';
|
|
7
2
|
export { CqrsEventBusService } from './buses/event-bus.js';
|
|
8
3
|
export { QueryBusLifecycleService } from './buses/query-bus.js';
|
|
4
|
+
export { CommandHandler, EventHandler, QueryHandler, Saga } from './decorators.js';
|
|
5
|
+
export { CommandHandlerNotFoundException, DuplicateCommandHandlerError, DuplicateEventHandlerError, DuplicateQueryHandlerError, QueryHandlerNotFoundException, SagaExecutionError, SagaTopologyError } from './errors.js';
|
|
6
|
+
export { commandHandlerMetadataSymbol, defineCommandHandlerMetadata, defineEventHandlerMetadata, defineQueryHandlerMetadata, defineSagaMetadata, eventHandlerMetadataSymbol, getCommandHandlerMetadata, getCommandHandlerMetadataEntry, getEventHandlerMetadata, getQueryHandlerMetadata, getQueryHandlerMetadataEntry, getSagaMetadata, queryHandlerMetadataSymbol, sagaMetadataSymbol } from './metadata.js';
|
|
7
|
+
export { CqrsModule } from './module.js';
|
|
8
|
+
export * from './status.js';
|
|
9
9
|
export { COMMAND_BUS, EVENT_BUS, QUERY_BUS } from './tokens.js';
|
package/dist/module.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Provider } from '@fluojs/di';
|
|
2
1
|
import { type EventBusModuleOptions } from '@fluojs/event-bus';
|
|
3
2
|
import { type ModuleType } from '@fluojs/runtime';
|
|
4
3
|
import type { CommandHandlerClass, EventHandlerClass, QueryHandlerClass, SagaClass } from './types.js';
|
|
@@ -16,13 +15,6 @@ export interface CqrsModuleOptions {
|
|
|
16
15
|
drainTimeoutMs?: number;
|
|
17
16
|
};
|
|
18
17
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Creates the providers required for CQRS buses, compatibility aliases, and optional handler registration.
|
|
21
|
-
*
|
|
22
|
-
* @param options CQRS module options including eager handler classes and event-bus configuration.
|
|
23
|
-
* @returns Providers for the command, query, event, and saga runtimes plus compatibility tokens.
|
|
24
|
-
*/
|
|
25
|
-
export declare function createCqrsProviders(options?: CqrsModuleOptions): Provider[];
|
|
26
18
|
/** Runtime module entrypoint for CQRS bus registration and handler discovery. */
|
|
27
19
|
export declare class CqrsModule {
|
|
28
20
|
/**
|
package/dist/module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAOhE,OAAO,KAAK,EACV,mBAAmB,EAEnB,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;AAgGD,iFAAiF;AACjF,qBAAa,UAAU;IACrB;;;;;OAKG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,iBAAsB,GAAG,UAAU;CAiB5D"}
|
package/dist/module.js
CHANGED
|
@@ -24,6 +24,21 @@ function collectOptionHandlerProviders(options) {
|
|
|
24
24
|
}
|
|
25
25
|
return providers;
|
|
26
26
|
}
|
|
27
|
+
function assertCommandBusService(service) {
|
|
28
|
+
if (!(service instanceof CommandBusLifecycleService)) {
|
|
29
|
+
throw new TypeError('CQRS command bus alias expected CommandBusLifecycleService.');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function assertQueryBusService(service) {
|
|
33
|
+
if (!(service instanceof QueryBusLifecycleService)) {
|
|
34
|
+
throw new TypeError('CQRS query bus alias expected QueryBusLifecycleService.');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function assertCqrsEventBusService(service) {
|
|
38
|
+
if (!(service instanceof CqrsEventBusService)) {
|
|
39
|
+
throw new TypeError('CQRS event bus alias expected CqrsEventBusService.');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
27
42
|
|
|
28
43
|
/**
|
|
29
44
|
* Creates the providers required for CQRS buses, compatibility aliases, and optional handler registration.
|
|
@@ -31,29 +46,38 @@ function collectOptionHandlerProviders(options) {
|
|
|
31
46
|
* @param options CQRS module options including eager handler classes and event-bus configuration.
|
|
32
47
|
* @returns Providers for the command, query, event, and saga runtimes plus compatibility tokens.
|
|
33
48
|
*/
|
|
34
|
-
|
|
49
|
+
function createCqrsProviders(options = {}) {
|
|
35
50
|
return [{
|
|
36
51
|
provide: CQRS_MODULE_OPTIONS,
|
|
37
52
|
useValue: options
|
|
38
53
|
}, CommandBusLifecycleService, {
|
|
39
54
|
inject: [CommandBusLifecycleService],
|
|
40
55
|
provide: COMMAND_BUS,
|
|
41
|
-
useFactory: service =>
|
|
42
|
-
|
|
43
|
-
|
|
56
|
+
useFactory: service => {
|
|
57
|
+
assertCommandBusService(service);
|
|
58
|
+
return {
|
|
59
|
+
execute: (command, context) => service.execute(command, context)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
44
62
|
}, QueryBusLifecycleService, {
|
|
45
63
|
inject: [QueryBusLifecycleService],
|
|
46
64
|
provide: QUERY_BUS,
|
|
47
|
-
useFactory: service =>
|
|
48
|
-
|
|
49
|
-
|
|
65
|
+
useFactory: service => {
|
|
66
|
+
assertQueryBusService(service);
|
|
67
|
+
return {
|
|
68
|
+
execute: (query, context) => service.execute(query, context)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
50
71
|
}, CqrsSagaLifecycleService, CqrsEventBusService, {
|
|
51
72
|
inject: [CqrsEventBusService],
|
|
52
73
|
provide: EVENT_BUS,
|
|
53
|
-
useFactory: service =>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
74
|
+
useFactory: service => {
|
|
75
|
+
assertCqrsEventBusService(service);
|
|
76
|
+
return {
|
|
77
|
+
publish: (event, context) => service.publish(event, context),
|
|
78
|
+
publishAll: (events, context) => service.publishAll(events, context)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
57
81
|
}, ...collectOptionHandlerProviders(options)];
|
|
58
82
|
}
|
|
59
83
|
|
package/dist/types.d.ts
CHANGED
|
@@ -15,9 +15,10 @@ export interface ICommandHandler<TCommand extends ICommand, TResult = void> {
|
|
|
15
15
|
* Executes one command instance.
|
|
16
16
|
*
|
|
17
17
|
* @param command Command payload to handle.
|
|
18
|
+
* @param context Optional saga dispatch context to pass through nested CQRS calls.
|
|
18
19
|
* @returns The handler result returned to the command bus caller.
|
|
19
20
|
*/
|
|
20
|
-
execute(command: TCommand): TResult | Promise<TResult>;
|
|
21
|
+
execute(command: TCommand, context?: CqrsDispatchContext): TResult | Promise<TResult>;
|
|
21
22
|
}
|
|
22
23
|
/** Contract implemented by classes decorated with {@link QueryHandler}. */
|
|
23
24
|
export interface IQueryHandler<TQuery extends IQuery<TResult>, TResult = unknown> {
|
|
@@ -25,9 +26,10 @@ export interface IQueryHandler<TQuery extends IQuery<TResult>, TResult = unknown
|
|
|
25
26
|
* Executes one query instance.
|
|
26
27
|
*
|
|
27
28
|
* @param query Query payload to handle.
|
|
29
|
+
* @param context Optional saga dispatch context to pass through nested CQRS calls.
|
|
28
30
|
* @returns The query result returned to the caller.
|
|
29
31
|
*/
|
|
30
|
-
execute(query: TQuery): TResult | Promise<TResult>;
|
|
32
|
+
execute(query: TQuery, context?: CqrsDispatchContext): TResult | Promise<TResult>;
|
|
31
33
|
}
|
|
32
34
|
/** Contract implemented by classes decorated with {@link EventHandler}. */
|
|
33
35
|
export interface IEventHandler<TEvent extends IEvent> {
|
|
@@ -35,9 +37,10 @@ export interface IEventHandler<TEvent extends IEvent> {
|
|
|
35
37
|
* Reacts to one isolated copy of a published event instance.
|
|
36
38
|
*
|
|
37
39
|
* @param event Event payload cloned for this handler before delegated event-bus publication.
|
|
40
|
+
* @param context Optional saga dispatch context to pass through nested CQRS calls.
|
|
38
41
|
* @returns A promise or void once side effects complete.
|
|
39
42
|
*/
|
|
40
|
-
handle(event: TEvent): void | Promise<void>;
|
|
43
|
+
handle(event: TEvent, context?: CqrsDispatchContext): void | Promise<void>;
|
|
41
44
|
}
|
|
42
45
|
/** Contract implemented by classes decorated with {@link Saga}. */
|
|
43
46
|
export interface ISaga<TEvent extends IEvent = IEvent> {
|
|
@@ -45,9 +48,20 @@ export interface ISaga<TEvent extends IEvent = IEvent> {
|
|
|
45
48
|
* Reacts to one isolated copy of an event and typically emits follow-up commands.
|
|
46
49
|
*
|
|
47
50
|
* @param event Event payload cloned for this saga route before delegated event-bus publication.
|
|
51
|
+
* @param context Optional saga dispatch context to pass through nested CQRS calls.
|
|
48
52
|
* @returns A promise or void once orchestration side effects complete.
|
|
49
53
|
*/
|
|
50
|
-
handle(event: TEvent): void | Promise<void>;
|
|
54
|
+
handle(event: TEvent, context?: CqrsDispatchContext): void | Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Opaque dispatch context used to preserve saga topology guards across nested CQRS calls.
|
|
58
|
+
*
|
|
59
|
+
* CQRS passes this value to command handlers, query handlers, event handlers, and sagas when a
|
|
60
|
+
* nested dispatch chain is active. Application code should pass the value through unchanged to
|
|
61
|
+
* nested `execute(...)`, `publish(...)`, or `publishAll(...)` calls. The context intentionally
|
|
62
|
+
* exposes no public topology fields and should not be inspected or constructed by callers.
|
|
63
|
+
*/
|
|
64
|
+
export interface CqrsDispatchContext {
|
|
51
65
|
}
|
|
52
66
|
/** Constructor type used to identify a command message class. */
|
|
53
67
|
export interface CommandType<TCommand extends ICommand = ICommand> {
|
|
@@ -129,7 +143,7 @@ export interface CommandBus {
|
|
|
129
143
|
* @param command Command instance to dispatch.
|
|
130
144
|
* @returns The handler result.
|
|
131
145
|
*/
|
|
132
|
-
execute<TCommand extends ICommand, TResult = void>(command: TCommand): Promise<TResult>;
|
|
146
|
+
execute<TCommand extends ICommand, TResult = void>(command: TCommand, context?: CqrsDispatchContext): Promise<TResult>;
|
|
133
147
|
}
|
|
134
148
|
/** Query dispatch facade exposed by the CQRS module. */
|
|
135
149
|
export interface QueryBus {
|
|
@@ -139,7 +153,7 @@ export interface QueryBus {
|
|
|
139
153
|
* @param query Query instance to dispatch.
|
|
140
154
|
* @returns The handler result.
|
|
141
155
|
*/
|
|
142
|
-
execute<TQuery extends IQuery<TResult>, TResult = unknown>(query: TQuery): Promise<TResult>;
|
|
156
|
+
execute<TQuery extends IQuery<TResult>, TResult = unknown>(query: TQuery, context?: CqrsDispatchContext): Promise<TResult>;
|
|
143
157
|
}
|
|
144
158
|
/** Event publishing facade exposed by the CQRS module. */
|
|
145
159
|
export interface CqrsEventBus {
|
|
@@ -150,15 +164,17 @@ export interface CqrsEventBus {
|
|
|
150
164
|
* subscribers receive the original event after local CQRS side effects complete.
|
|
151
165
|
*
|
|
152
166
|
* @param event Event instance to publish.
|
|
167
|
+
* @param context Optional saga dispatch context to pass through nested CQRS calls.
|
|
153
168
|
* @returns A promise that resolves once publication completes.
|
|
154
169
|
*/
|
|
155
|
-
publish<TEvent extends IEvent>(event: TEvent): Promise<void>;
|
|
170
|
+
publish<TEvent extends IEvent>(event: TEvent, context?: CqrsDispatchContext): Promise<void>;
|
|
156
171
|
/**
|
|
157
172
|
* Publishes a batch of events in order.
|
|
158
173
|
*
|
|
159
174
|
* @param events Event instances to publish.
|
|
175
|
+
* @param context Optional saga dispatch context to pass through nested CQRS calls.
|
|
160
176
|
* @returns A promise that resolves once all events are published.
|
|
161
177
|
*/
|
|
162
|
-
publishAll<TEvent extends IEvent>(events: readonly TEvent[]): Promise<void>;
|
|
178
|
+
publishAll<TEvent extends IEvent>(events: readonly TEvent[], context?: CqrsDispatchContext): Promise<void>;
|
|
163
179
|
}
|
|
164
180
|
//# sourceMappingURL=types.d.ts.map
|
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,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C,2EAA2E;AAC3E,MAAM,WAAW,QAAQ;CAAG;AAE5B,mGAAmG;AACnG,MAAM,WAAW,MAAM,CAAC,OAAO,GAAG,OAAO;IACvC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACxC;AAED,yFAAyF;AACzF,MAAM,WAAW,MAAM;CAAG;AAE1B,6EAA6E;AAC7E,MAAM,WAAW,eAAe,CAAC,QAAQ,SAAS,QAAQ,EAAE,OAAO,GAAG,IAAI;IACxE
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"saga",
|
|
10
10
|
"event-sourcing"
|
|
11
11
|
],
|
|
12
|
-
"version": "1.1.
|
|
12
|
+
"version": "1.1.2",
|
|
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.
|
|
40
|
-
"@fluojs/di": "^1.0
|
|
41
|
-
"@fluojs/event-bus": "^1.0.
|
|
42
|
-
"@fluojs/runtime": "^1.1.
|
|
39
|
+
"@fluojs/core": "^1.0.3",
|
|
40
|
+
"@fluojs/di": "^1.1.0",
|
|
41
|
+
"@fluojs/event-bus": "^1.0.1",
|
|
42
|
+
"@fluojs/runtime": "^1.1.8"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"vitest": "^3.2.4"
|