@fluojs/cqrs 1.0.0-beta.5 → 1.0.0-beta.6

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 CHANGED
@@ -110,7 +110,7 @@ Saga 실행은 같은 프로세스 안에서 동일 saga route로 순환 재진
110
110
 
111
111
  ### Event 발행 계약
112
112
 
113
- `CqrsEventBusService.publish(event)`는 CQRS event pipeline을 고정된 순서로 실행합니다. 먼저 일치하는 `@EventHandler(...)` provider를 실행하고, 그다음 일치하는 `@Saga(...)` provider를 실행한 뒤, 마지막으로 `@fluojs/event-bus`로 위임 발행합니다. `publishAll(events)`는 각 event의 CQRS handler, saga, 위임 발행 호출을 기다린 뒤 다음 event를 발행하므로 입력 순서를 보존합니다. `CqrsModule.forRoot({ eventBus: { publish: { waitForHandlers: false } } })`로 설정한 경우 위임 발행 호출은 일치하는 `@OnEvent(...)` subscriber가 완료되기 전에 resolve될 수 있으므로, 이 모드에서 `publish(...)`와 `publishAll(...)`는 subscriber 완료를 의미하지 않습니다.
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가 settle될 때까지 기다린 뒤 stopped 상태로 전환합니다. `CqrsModule.forRoot({ eventBus: { publish: { waitForHandlers: false } } })`로 설정한 경우 위임 발행 호출은 일치하는 `@OnEvent(...)` subscriber가 완료되기 전에 resolve될 수 있으므로, 이 모드에서 `publish(...)`, `publishAll(...)`, shutdown drain 완료는 subscriber 완료를 의미하지 않습니다.
114
114
 
115
115
  각 CQRS event handler와 saga는 매칭된 event prototype이 복원된 격리 event 복사본을 받습니다. 이 복사본을 mutate해도 변경은 현재 handler 또는 saga route 안에만 머물며, 다른 CQRS handler, saga, 원본 event 객체, 또는 위임된 `@fluojs/event-bus` subscriber에는 보이지 않습니다. 위임된 event-bus 발행은 CQRS side effect가 끝난 뒤 원본 event를 받으므로, `@OnEvent(...)` projection과 transport는 CQRS handler가 mutate한 복사본이 아니라 호출자가 소유한 payload를 관찰합니다.
116
116
 
@@ -153,7 +153,8 @@ class TokenInjectedService {
153
153
 
154
154
  ### 오류
155
155
  - `CommandHandlerNotFoundException`, `QueryHandlerNotFoundException`: bus에 일치하는 handler가 없을 때 발생합니다.
156
- - `DuplicateCommandHandlerError`, `DuplicateQueryHandlerError`, `DuplicateEventHandlerError`: 중복 handler registration에서 발생합니다.
156
+ - `DuplicateCommandHandlerError`, `DuplicateQueryHandlerError`: 서로 다른 singleton provider가 같은 command 또는 query type을 claim할 때 발생합니다.
157
+ - `DuplicateEventHandlerError`: 충돌하는 event-handler discovery 실패를 위해 export됩니다. 일반적으로 같은 event에 여러 `@EventHandler(...)` provider를 등록하는 것은 유효하며 discovery 순서대로 fan-out됩니다.
157
158
  - `SagaExecutionError`: 예상하지 못한 non-Fluo saga 실패를 감쌉니다.
158
159
  - `SagaTopologyError`: 자기 트리거, 순환, 또는 과도하게 깊은 in-process saga graph를 감지했을 때 발생합니다.
159
160
 
package/README.md CHANGED
@@ -110,7 +110,7 @@ Saga execution fails fast with `SagaTopologyError` when an in-process publish ch
110
110
 
111
111
  ### Event Publishing Contracts
112
112
 
113
- `CqrsEventBusService.publish(event)` runs the CQRS event pipeline in a fixed order: matching `@EventHandler(...)` providers first, matching `@Saga(...)` providers second, and delegated `@fluojs/event-bus` publication last. `publishAll(events)` preserves the input order by awaiting each event's CQRS handlers, sagas, and delegated publication call before publishing the next event. When `CqrsModule.forRoot({ eventBus: { publish: { waitForHandlers: false } } })` is configured, the delegated publication call can resolve before matching `@OnEvent(...)` subscribers finish, so `publish(...)` and `publishAll(...)` do not imply subscriber completion in that mode.
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 and `publishAll(...)` sequences to settle before marking itself stopped. When `CqrsModule.forRoot({ eventBus: { publish: { waitForHandlers: false } } })` is configured, the delegated publication call can resolve before matching `@OnEvent(...)` subscribers finish, so `publish(...)`, `publishAll(...)`, and shutdown drain completion do not imply subscriber completion in that mode.
114
114
 
115
115
  Each CQRS event handler and saga receives an isolated event copy with the matched event prototype restored. Mutating that copy is local to the current handler or saga route; those mutations are not visible to other CQRS handlers, sagas, the original event object, or delegated `@fluojs/event-bus` subscribers. The delegated event-bus publication receives the original event after CQRS side effects complete, so `@OnEvent(...)` projections and transports observe the caller-owned payload rather than a CQRS handler's mutated copy.
116
116
 
@@ -153,7 +153,8 @@ class TokenInjectedService {
153
153
 
154
154
  ### Errors
155
155
  - `CommandHandlerNotFoundException`, `QueryHandlerNotFoundException`: Raised when a bus has no matching handler.
156
- - `DuplicateCommandHandlerError`, `DuplicateQueryHandlerError`, `DuplicateEventHandlerError`: Raised for duplicate handler registrations.
156
+ - `DuplicateCommandHandlerError`, `DuplicateQueryHandlerError`: Raised when different singleton providers claim the same command or query type.
157
+ - `DuplicateEventHandlerError`: Exported for conflicting event-handler discovery failures; ordinary multiple `@EventHandler(...)` providers for the same event are valid and fan out in discovery order.
157
158
  - `SagaExecutionError`: Wraps unexpected non-Fluo saga failures.
158
159
  - `SagaTopologyError`: Raised when saga orchestration detects a self-triggering, cyclic, or over-deep in-process saga graph.
159
160
 
@@ -15,6 +15,7 @@ export declare class CqrsEventBusService extends CqrsBusBase implements CqrsEven
15
15
  private descriptors;
16
16
  private discoveryPromise;
17
17
  private discovered;
18
+ private readonly activePublishPipelines;
18
19
  private lifecycleState;
19
20
  constructor(eventBus: EventBus, sagaService: CqrsSagaLifecycleService, runtimeContainer: ConstructorParameters<typeof CqrsBusBase>[0], compiledModules: ConstructorParameters<typeof CqrsBusBase>[1], logger: ConstructorParameters<typeof CqrsBusBase>[2]);
20
21
  onApplicationBootstrap(): Promise<void>;
@@ -41,6 +42,9 @@ export declare class CqrsEventBusService extends CqrsBusBase implements CqrsEven
41
42
  * @returns A promise that resolves once all events are published.
42
43
  */
43
44
  publishAll<TEvent extends IEvent>(events: readonly TEvent[]): Promise<void>;
45
+ private runPublishPipeline;
46
+ private runPublishAllPipeline;
47
+ private trackPublishPipeline;
44
48
  private matchEventDescriptors;
45
49
  private ensureDiscovered;
46
50
  private discoverHandlers;
@@ -1 +1 @@
1
- {"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../../src/buses/event-bus.ts"],"names":[],"mappings":"AACA,OAAO,EAA+B,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,KAAK,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAGrF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAG9C,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAEzD,OAAO,KAAK,EAAE,YAAY,EAAyC,MAAM,EAAiB,MAAM,aAAa,CAAC;AAU9G;;;;;GAKG;AACH,qBACa,mBAAoB,SAAQ,WAAY,YAAW,YAAY,EAAE,sBAAsB,EAAE,qBAAqB;IAOvH,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAP9B,OAAO,CAAC,WAAW,CAAgC;IACnD,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,UAAU,CAAS;IAC3B,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;IAKhD,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAYvC,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5C;;;;OAIG;IACH,4BAA4B;IAY5B;;;;;;;OAOG;IACG,OAAO,CAAC,MAAM,SAAS,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBlE;;;;;OAKG;IACG,UAAU,CAAC,MAAM,SAAS,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjF,OAAO,CAAC,qBAAqB;YAIf,gBAAgB;YAchB,gBAAgB;IAe9B,OAAO,CAAC,wBAAwB;CA4CjC"}
1
+ {"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../../src/buses/event-bus.ts"],"names":[],"mappings":"AACA,OAAO,EAA+B,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,KAAK,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAGrF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAG9C,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAEzD,OAAO,KAAK,EAAE,YAAY,EAAyC,MAAM,EAAiB,MAAM,aAAa,CAAC;AAU9G;;;;;GAKG;AACH,qBACa,mBAAoB,SAAQ,WAAY,YAAW,YAAY,EAAE,sBAAsB,EAAE,qBAAqB;IAQvH,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAR9B,OAAO,CAAC,WAAW,CAAgC;IACnD,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAA4B;IACnE,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;IAKhD,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAYvC,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAU5C;;;;OAIG;IACH,4BAA4B;IAY5B;;;;;;;OAOG;IACG,OAAO,CAAC,MAAM,SAAS,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlE;;;;;OAKG;IACG,UAAU,CAAC,MAAM,SAAS,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;YAInE,kBAAkB;YAiBlB,qBAAqB;YAMrB,oBAAoB;IAUlC,OAAO,CAAC,qBAAqB;YAIf,gBAAgB;YAchB,gBAAgB;IAe9B,OAAO,CAAC,wBAAwB;CA4CjC"}
@@ -33,6 +33,7 @@ class CqrsEventBusService extends CqrsBusBase {
33
33
  descriptors = [];
34
34
  discoveryPromise;
35
35
  discovered = false;
36
+ activePublishPipelines = new Set();
36
37
  lifecycleState = 'created';
37
38
  constructor(eventBus, sagaService, runtimeContainer, compiledModules, logger) {
38
39
  super(runtimeContainer, compiledModules, logger);
@@ -51,6 +52,9 @@ class CqrsEventBusService extends CqrsBusBase {
51
52
  }
52
53
  async onApplicationShutdown() {
53
54
  this.lifecycleState = 'stopping';
55
+ while (this.activePublishPipelines.size > 0) {
56
+ await Promise.allSettled(Array.from(this.activePublishPipelines));
57
+ }
54
58
  this.lifecycleState = 'stopped';
55
59
  }
56
60
 
@@ -79,6 +83,19 @@ class CqrsEventBusService extends CqrsBusBase {
79
83
  * @throws {InvariantError} When a discovered provider does not implement `handle(event)`.
80
84
  */
81
85
  async publish(event) {
86
+ await this.trackPublishPipeline(this.runPublishPipeline(event));
87
+ }
88
+
89
+ /**
90
+ * Publishes a batch of events sequentially through the CQRS event pipeline.
91
+ *
92
+ * @param events Event instances to publish in order.
93
+ * @returns A promise that resolves once all events are published.
94
+ */
95
+ async publishAll(events) {
96
+ await this.trackPublishPipeline(this.runPublishAllPipeline(events));
97
+ }
98
+ async runPublishPipeline(event) {
82
99
  await this.ensureDiscovered();
83
100
  for (const descriptor of this.matchEventDescriptors(event)) {
84
101
  const instance = await this.resolveHandlerInstance(descriptor.token);
@@ -90,18 +107,19 @@ class CqrsEventBusService extends CqrsBusBase {
90
107
  await this.sagaService.dispatch(event);
91
108
  await this.eventBus.publish(event);
92
109
  }
93
-
94
- /**
95
- * Publishes a batch of events sequentially through the CQRS event pipeline.
96
- *
97
- * @param events Event instances to publish in order.
98
- * @returns A promise that resolves once all events are published.
99
- */
100
- async publishAll(events) {
110
+ async runPublishAllPipeline(events) {
101
111
  for (const event of events) {
102
112
  await this.publish(event);
103
113
  }
104
114
  }
115
+ async trackPublishPipeline(pipeline) {
116
+ this.activePublishPipelines.add(pipeline);
117
+ try {
118
+ await pipeline;
119
+ } finally {
120
+ this.activePublishPipelines.delete(pipeline);
121
+ }
122
+ }
105
123
  matchEventDescriptors(event) {
106
124
  return this.descriptors.filter(descriptor => event instanceof descriptor.eventType);
107
125
  }
package/dist/errors.d.ts CHANGED
@@ -35,7 +35,11 @@ export declare class DuplicateQueryHandlerError extends FluoError {
35
35
  */
36
36
  constructor(message: string);
37
37
  }
38
- /** Raised when conflicting event-handler registrations break the CQRS discovery contract. */
38
+ /**
39
+ * Raised when conflicting event-handler registrations break CQRS discovery invariants.
40
+ *
41
+ * Ordinary multiple `@EventHandler(...)` providers for the same event are valid and fan out.
42
+ */
39
43
  export declare class DuplicateEventHandlerError extends FluoError {
40
44
  /**
41
45
  * Creates a duplicate-event-handler error.
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,wFAAwF;AACxF,qBAAa,+BAAgC,SAAQ,SAAS;IAC5D;;;;OAIG;gBACS,OAAO,EAAE,MAAM;CAG5B;AAED,iFAAiF;AACjF,qBAAa,4BAA6B,SAAQ,SAAS;IACzD;;;;OAIG;gBACS,OAAO,EAAE,MAAM;CAG5B;AAED,oFAAoF;AACpF,qBAAa,6BAA8B,SAAQ,SAAS;IAC1D;;;;OAIG;gBACS,OAAO,EAAE,MAAM;CAG5B;AAED,+EAA+E;AAC/E,qBAAa,0BAA2B,SAAQ,SAAS;IACvD;;;;OAIG;gBACS,OAAO,EAAE,MAAM;CAG5B;AAED,6FAA6F;AAC7F,qBAAa,0BAA2B,SAAQ,SAAS;IACvD;;;;OAIG;gBACS,OAAO,EAAE,MAAM;CAG5B;AAED,0EAA0E;AAC1E,qBAAa,kBAAmB,SAAQ,SAAS;IAC/C;;;;OAIG;gBACS,OAAO,EAAE,MAAM;CAG5B;AAED,8EAA8E;AAC9E,qBAAa,iBAAkB,SAAQ,SAAS;IAC9C;;;;OAIG;gBACS,OAAO,EAAE,MAAM;CAG5B"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,wFAAwF;AACxF,qBAAa,+BAAgC,SAAQ,SAAS;IAC5D;;;;OAIG;gBACS,OAAO,EAAE,MAAM;CAG5B;AAED,iFAAiF;AACjF,qBAAa,4BAA6B,SAAQ,SAAS;IACzD;;;;OAIG;gBACS,OAAO,EAAE,MAAM;CAG5B;AAED,oFAAoF;AACpF,qBAAa,6BAA8B,SAAQ,SAAS;IAC1D;;;;OAIG;gBACS,OAAO,EAAE,MAAM;CAG5B;AAED,+EAA+E;AAC/E,qBAAa,0BAA2B,SAAQ,SAAS;IACvD;;;;OAIG;gBACS,OAAO,EAAE,MAAM;CAG5B;AAED;;;;GAIG;AACH,qBAAa,0BAA2B,SAAQ,SAAS;IACvD;;;;OAIG;gBACS,OAAO,EAAE,MAAM;CAG5B;AAED,0EAA0E;AAC1E,qBAAa,kBAAmB,SAAQ,SAAS;IAC/C;;;;OAIG;gBACS,OAAO,EAAE,MAAM;CAG5B;AAED,8EAA8E;AAC9E,qBAAa,iBAAkB,SAAQ,SAAS;IAC9C;;;;OAIG;gBACS,OAAO,EAAE,MAAM;CAG5B"}
package/dist/errors.js CHANGED
@@ -56,7 +56,11 @@ export class DuplicateQueryHandlerError extends FluoError {
56
56
  }
57
57
  }
58
58
 
59
- /** Raised when conflicting event-handler registrations break the CQRS discovery contract. */
59
+ /**
60
+ * Raised when conflicting event-handler registrations break CQRS discovery invariants.
61
+ *
62
+ * Ordinary multiple `@EventHandler(...)` providers for the same event are valid and fan out.
63
+ */
60
64
  export class DuplicateEventHandlerError extends FluoError {
61
65
  /**
62
66
  * Creates a duplicate-event-handler error.
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "saga",
10
10
  "event-sourcing"
11
11
  ],
12
- "version": "1.0.0-beta.5",
12
+ "version": "1.0.0-beta.6",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -36,10 +36,10 @@
36
36
  "dist"
37
37
  ],
38
38
  "dependencies": {
39
- "@fluojs/core": "^1.0.0-beta.4",
40
- "@fluojs/di": "^1.0.0-beta.6",
41
- "@fluojs/event-bus": "^1.0.0-beta.5",
42
- "@fluojs/runtime": "^1.0.0-beta.11"
39
+ "@fluojs/core": "^1.0.0-beta.5",
40
+ "@fluojs/di": "^1.0.0-beta.7",
41
+ "@fluojs/event-bus": "^1.0.0-beta.6",
42
+ "@fluojs/runtime": "^1.0.0-beta.12"
43
43
  },
44
44
  "devDependencies": {
45
45
  "vitest": "^3.2.4"