@nestjs-transactional/cqrs 1.0.0-alpha.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/LICENSE +21 -0
- package/README.md +431 -0
- package/dist/decorators/integration-events-handler.decorator.d.ts +125 -0
- package/dist/decorators/integration-events-handler.decorator.js +57 -0
- package/dist/decorators/integration-events-handler.decorator.js.map +1 -0
- package/dist/decorators/transactional-events-handler.decorator.d.ts +138 -0
- package/dist/decorators/transactional-events-handler.decorator.js +65 -0
- package/dist/decorators/transactional-events-handler.decorator.js.map +1 -0
- package/dist/event-dispatcher/event-dispatcher.d.ts +100 -0
- package/dist/event-dispatcher/event-dispatcher.js +229 -0
- package/dist/event-dispatcher/event-dispatcher.js.map +1 -0
- package/dist/event-publisher/hybrid-event-publisher.d.ts +76 -0
- package/dist/event-publisher/hybrid-event-publisher.js +86 -0
- package/dist/event-publisher/hybrid-event-publisher.js.map +1 -0
- package/dist/event-publisher/transactional-event-publisher-adapter.d.ts +32 -0
- package/dist/event-publisher/transactional-event-publisher-adapter.js +72 -0
- package/dist/event-publisher/transactional-event-publisher-adapter.js.map +1 -0
- package/dist/event-publisher/transactional-event-publisher.d.ts +33 -0
- package/dist/event-publisher/transactional-event-publisher.js +58 -0
- package/dist/event-publisher/transactional-event-publisher.js.map +1 -0
- package/dist/handlers/bootstrap.d.ts +18 -0
- package/dist/handlers/bootstrap.js +39 -0
- package/dist/handlers/bootstrap.js.map +1 -0
- package/dist/handlers/handler-wrapper.d.ts +77 -0
- package/dist/handlers/handler-wrapper.js +183 -0
- package/dist/handlers/handler-wrapper.js.map +1 -0
- package/dist/handlers/integration-events-handler-scanner.d.ts +37 -0
- package/dist/handlers/integration-events-handler-scanner.js +144 -0
- package/dist/handlers/integration-events-handler-scanner.js.map +1 -0
- package/dist/handlers/listener-scanner.d.ts +33 -0
- package/dist/handlers/listener-scanner.js +86 -0
- package/dist/handlers/listener-scanner.js.map +1 -0
- package/dist/handlers/outbox-listener-registrar.d.ts +49 -0
- package/dist/handlers/outbox-listener-registrar.js +17 -0
- package/dist/handlers/outbox-listener-registrar.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/integration-event-handler.interface.d.ts +35 -0
- package/dist/interfaces/integration-event-handler.interface.js +3 -0
- package/dist/interfaces/integration-event-handler.interface.js.map +1 -0
- package/dist/interfaces/transactional-event-handler.interface.d.ts +31 -0
- package/dist/interfaces/transactional-event-handler.interface.js +3 -0
- package/dist/interfaces/transactional-event-handler.interface.js.map +1 -0
- package/dist/module/cqrs-transactional.module.d.ts +92 -0
- package/dist/module/cqrs-transactional.module.js +137 -0
- package/dist/module/cqrs-transactional.module.js.map +1 -0
- package/dist/types/transactional-listener.types.d.ts +21 -0
- package/dist/types/transactional-listener.types.js +25 -0
- package/dist/types/transactional-listener.types.js.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { type Type } from '@nestjs/common';
|
|
3
|
+
import { TransactionPhase } from '../types/transactional-listener.types';
|
|
4
|
+
/**
|
|
5
|
+
* Metadata key under which {@link TransactionalEventsHandlerMetadata} is
|
|
6
|
+
* stored on classes decorated with {@link TransactionalEventsHandler}.
|
|
7
|
+
*
|
|
8
|
+
* The key is a fresh `Symbol` (not `Symbol.for`) — this metadata is
|
|
9
|
+
* private to the cqrs package and does not need to be shared across
|
|
10
|
+
* package boundaries.
|
|
11
|
+
*/
|
|
12
|
+
export declare const TRANSACTIONAL_EVENTS_HANDLER_METADATA: unique symbol;
|
|
13
|
+
/**
|
|
14
|
+
* Options accepted by the long form of {@link TransactionalEventsHandler}.
|
|
15
|
+
*
|
|
16
|
+
* Use this form when the handler needs any non-default behaviour (a
|
|
17
|
+
* different `phase`, `async` delivery, or `fallbackExecution` outside a
|
|
18
|
+
* transaction). When defaults are acceptable, prefer the rest-params
|
|
19
|
+
* short form: `@TransactionalEventsHandler(EventA, EventB)`.
|
|
20
|
+
*/
|
|
21
|
+
export interface TransactionalEventsHandlerOptions {
|
|
22
|
+
/** Domain event classes the handler subscribes to. Must be non-empty. */
|
|
23
|
+
readonly events: Type[];
|
|
24
|
+
/**
|
|
25
|
+
* Transaction phase to attach the handler to. Defaults to
|
|
26
|
+
* {@link TransactionPhase.AFTER_COMMIT} — the canonical "publish
|
|
27
|
+
* domain event" phase.
|
|
28
|
+
*/
|
|
29
|
+
readonly phase?: TransactionPhase;
|
|
30
|
+
/**
|
|
31
|
+
* When `true`, the handler is invoked on a microtask and its failures
|
|
32
|
+
* never reach the surrounding transaction — including BEFORE_COMMIT,
|
|
33
|
+
* which therefore cannot cause a rollback. Defaults to `false`.
|
|
34
|
+
*/
|
|
35
|
+
readonly async?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* When `true`, the handler fires even for events published outside
|
|
38
|
+
* any active transaction (direct `eventBus.publish(event)` calls).
|
|
39
|
+
* When `false` (default), such events are dropped with a warning.
|
|
40
|
+
*/
|
|
41
|
+
readonly fallbackExecution?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* dataSource the handler's phase hooks attach to (Phase 14.3.1).
|
|
44
|
+
* Defaults to {@link DEFAULT_DATA_SOURCE_NAME} (`'default'`) —
|
|
45
|
+
* single-dataSource apps can omit it.
|
|
46
|
+
*
|
|
47
|
+
* Multi-dataSource apps with handlers belonging to a non-default
|
|
48
|
+
* dataSource MUST set this — the dispatcher uses it to find the
|
|
49
|
+
* matching active transaction via
|
|
50
|
+
* `TransactionContext.getActiveTransactionByDataSource(dataSource)`
|
|
51
|
+
* and pushes phase hooks directly onto that transaction's hook
|
|
52
|
+
* lists. Without it, the dispatcher falls back to `'default'` and
|
|
53
|
+
* the handler may attach to the wrong transaction (or none at all
|
|
54
|
+
* if the default-DS has no active tx in the current async context).
|
|
55
|
+
*
|
|
56
|
+
* Unlike `@OutboxEventsHandler` and `@IntegrationEventsHandler`'s
|
|
57
|
+
* outbox path — both of which auto-resolve the dataSource by
|
|
58
|
+
* walking per-DS event-type registries — the in-memory dispatcher
|
|
59
|
+
* has no event-type registry to consult. The cqrs package is
|
|
60
|
+
* decoupled from outbox by design (Phase 14.7), so the dataSource
|
|
61
|
+
* is declared explicitly on the decorator.
|
|
62
|
+
*/
|
|
63
|
+
readonly dataSource?: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolved metadata attached to a handler class. All option fields are
|
|
67
|
+
* required (the decorator fills in defaults). `eventTypes` is the
|
|
68
|
+
* normalised list of event constructors the handler is registered for.
|
|
69
|
+
*/
|
|
70
|
+
export interface TransactionalEventsHandlerMetadata {
|
|
71
|
+
readonly eventTypes: Type[];
|
|
72
|
+
readonly phase: TransactionPhase;
|
|
73
|
+
readonly async: boolean;
|
|
74
|
+
readonly fallbackExecution: boolean;
|
|
75
|
+
readonly dataSource: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Mark a class as a transactional event handler. The class must
|
|
79
|
+
* expose a `handle(event): void | Promise<void>` method (enforce this
|
|
80
|
+
* at the type level by implementing {@link ITransactionalEventHandler}).
|
|
81
|
+
*
|
|
82
|
+
* Two forms:
|
|
83
|
+
*
|
|
84
|
+
* ```ts
|
|
85
|
+
* // Short form — defaults (AFTER_COMMIT, sync, no fallback):
|
|
86
|
+
* @TransactionalEventsHandler(OrderPlacedEvent, OrderCancelledEvent)
|
|
87
|
+
*
|
|
88
|
+
* // Long form — explicit options:
|
|
89
|
+
* @TransactionalEventsHandler({
|
|
90
|
+
* events: [OrderPlacedEvent],
|
|
91
|
+
* phase: TransactionPhase.BEFORE_COMMIT,
|
|
92
|
+
* async: false,
|
|
93
|
+
* })
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* Class-level only — this decorator does not accept methods. Multiple
|
|
97
|
+
* handlers may subscribe to the same event type; each is an independent
|
|
98
|
+
* class and runs in registration order.
|
|
99
|
+
*
|
|
100
|
+
* The metadata is written by `Reflect.defineMetadata`. The actual
|
|
101
|
+
* dispatcher registration happens at application bootstrap via
|
|
102
|
+
* `TransactionalListenerScanner`.
|
|
103
|
+
*
|
|
104
|
+
* **Multi-dataSource semantics (Phase 14.3.1).** The dispatcher pushes
|
|
105
|
+
* phase hooks directly onto the per-dataSource active transaction
|
|
106
|
+
* resolved via
|
|
107
|
+
* `TransactionContext.getActiveTransactionByDataSource(dataSource)`,
|
|
108
|
+
* bypassing `TransactionManager.registerBeforeCommit`'s first-active-tx
|
|
109
|
+
* semantics. The dataSource defaults to `'default'`; multi-DS apps
|
|
110
|
+
* pass `dataSource: 'billing'` (or similar) on the long form to attach
|
|
111
|
+
* the handler to a specific dataSource's transaction:
|
|
112
|
+
*
|
|
113
|
+
* ```ts
|
|
114
|
+
* @TransactionalEventsHandler({
|
|
115
|
+
* events: [BillingEvent],
|
|
116
|
+
* dataSource: 'billing',
|
|
117
|
+
* })
|
|
118
|
+
* class BillingHandler { handle(event: BillingEvent) {} }
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* Unlike `@OutboxEventsHandler` (which auto-resolves the dataSource
|
|
122
|
+
* by walking per-DS event-type registries), the in-memory dispatcher
|
|
123
|
+
* has no event-type registry — the dataSource is declared explicitly
|
|
124
|
+
* on the decorator.
|
|
125
|
+
*
|
|
126
|
+
* @throws {Error} If no event types are supplied.
|
|
127
|
+
*/
|
|
128
|
+
export declare function TransactionalEventsHandler(...events: Type[]): ClassDecorator;
|
|
129
|
+
export declare function TransactionalEventsHandler(options: TransactionalEventsHandlerOptions): ClassDecorator;
|
|
130
|
+
/**
|
|
131
|
+
* Read the {@link TransactionalEventsHandlerMetadata} attached to
|
|
132
|
+
* `target` by {@link TransactionalEventsHandler}. Returns `undefined`
|
|
133
|
+
* when the class was not decorated.
|
|
134
|
+
*
|
|
135
|
+
* @param target - The class constructor.
|
|
136
|
+
*/
|
|
137
|
+
export declare function getTransactionalEventsHandlerMetadata(target: object): TransactionalEventsHandlerMetadata | undefined;
|
|
138
|
+
//# sourceMappingURL=transactional-events-handler.decorator.d.ts.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TRANSACTIONAL_EVENTS_HANDLER_METADATA = void 0;
|
|
4
|
+
exports.TransactionalEventsHandler = TransactionalEventsHandler;
|
|
5
|
+
exports.getTransactionalEventsHandlerMetadata = getTransactionalEventsHandlerMetadata;
|
|
6
|
+
require("reflect-metadata");
|
|
7
|
+
const core_1 = require("@nestjs-transactional/core");
|
|
8
|
+
const transactional_listener_types_1 = require("../types/transactional-listener.types");
|
|
9
|
+
/**
|
|
10
|
+
* Metadata key under which {@link TransactionalEventsHandlerMetadata} is
|
|
11
|
+
* stored on classes decorated with {@link TransactionalEventsHandler}.
|
|
12
|
+
*
|
|
13
|
+
* The key is a fresh `Symbol` (not `Symbol.for`) — this metadata is
|
|
14
|
+
* private to the cqrs package and does not need to be shared across
|
|
15
|
+
* package boundaries.
|
|
16
|
+
*/
|
|
17
|
+
exports.TRANSACTIONAL_EVENTS_HANDLER_METADATA = Symbol('TRANSACTIONAL_EVENTS_HANDLER_METADATA');
|
|
18
|
+
function TransactionalEventsHandler(...args) {
|
|
19
|
+
const metadata = resolveMetadata(args);
|
|
20
|
+
if (metadata.eventTypes.length === 0) {
|
|
21
|
+
throw new Error('@TransactionalEventsHandler requires at least one event type. ' +
|
|
22
|
+
'Pass class constructors as rest arguments or via the `events` option.');
|
|
23
|
+
}
|
|
24
|
+
return (target) => {
|
|
25
|
+
Reflect.defineMetadata(exports.TRANSACTIONAL_EVENTS_HANDLER_METADATA, metadata, target);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function resolveMetadata(args) {
|
|
29
|
+
if (args.length === 1 && isOptionsObject(args[0])) {
|
|
30
|
+
const options = args[0];
|
|
31
|
+
return {
|
|
32
|
+
eventTypes: [...options.events],
|
|
33
|
+
phase: options.phase ?? transactional_listener_types_1.TransactionPhase.AFTER_COMMIT,
|
|
34
|
+
async: options.async ?? false,
|
|
35
|
+
fallbackExecution: options.fallbackExecution ?? false,
|
|
36
|
+
dataSource: options.dataSource ?? core_1.DEFAULT_DATA_SOURCE_NAME,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
eventTypes: args,
|
|
41
|
+
phase: transactional_listener_types_1.TransactionPhase.AFTER_COMMIT,
|
|
42
|
+
async: false,
|
|
43
|
+
fallbackExecution: false,
|
|
44
|
+
dataSource: core_1.DEFAULT_DATA_SOURCE_NAME,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function isOptionsObject(candidate) {
|
|
48
|
+
return (candidate !== null &&
|
|
49
|
+
typeof candidate === 'object' &&
|
|
50
|
+
!Array.isArray(candidate) &&
|
|
51
|
+
typeof candidate !== 'function' &&
|
|
52
|
+
'events' in candidate);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Read the {@link TransactionalEventsHandlerMetadata} attached to
|
|
56
|
+
* `target` by {@link TransactionalEventsHandler}. Returns `undefined`
|
|
57
|
+
* when the class was not decorated.
|
|
58
|
+
*
|
|
59
|
+
* @param target - The class constructor.
|
|
60
|
+
*/
|
|
61
|
+
function getTransactionalEventsHandlerMetadata(target) {
|
|
62
|
+
const value = Reflect.getMetadata(exports.TRANSACTIONAL_EVENTS_HANDLER_METADATA, target);
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=transactional-events-handler.decorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transactional-events-handler.decorator.js","sourceRoot":"","sources":["../../src/decorators/transactional-events-handler.decorator.ts"],"names":[],"mappings":";;;AA4IA,gEAeC;AA4CD,sFAKC;AA5MD,4BAA0B;AAG1B,qDAAsE;AAEtE,wFAAyE;AAEzE;;;;;;;GAOG;AACU,QAAA,qCAAqC,GAAG,MAAM,CACzD,uCAAuC,CACxC,CAAC;AA2HF,SAAgB,0BAA0B,CACxC,GAAG,IAAkD;IAErD,MAAM,QAAQ,GAAuC,eAAe,CAAC,IAAI,CAAC,CAAC;IAE3E,IAAI,QAAQ,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CACb,gEAAgE;YAC9D,uEAAuE,CAC1E,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,MAAc,EAAQ,EAAE;QAC9B,OAAO,CAAC,cAAc,CAAC,6CAAqC,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAClF,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CACtB,IAAkD;IAElD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAClD,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACxB,OAAO;YACL,UAAU,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;YAC/B,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,+CAAgB,CAAC,YAAY;YACrD,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,KAAK;YAC7B,iBAAiB,EAAE,OAAO,CAAC,iBAAiB,IAAI,KAAK;YACrD,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,+BAAwB;SAC3D,CAAC;IACJ,CAAC;IAED,OAAO;QACL,UAAU,EAAE,IAAc;QAC1B,KAAK,EAAE,+CAAgB,CAAC,YAAY;QACpC,KAAK,EAAE,KAAK;QACZ,iBAAiB,EAAE,KAAK;QACxB,UAAU,EAAE,+BAAwB;KACrC,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CACtB,SAAkB;IAElB,OAAO,CACL,SAAS,KAAK,IAAI;QAClB,OAAO,SAAS,KAAK,QAAQ;QAC7B,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;QACzB,OAAO,SAAS,KAAK,UAAU;QAC/B,QAAQ,IAAI,SAAS,CACtB,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,qCAAqC,CACnD,MAAc;IAEd,MAAM,KAAK,GAAY,OAAO,CAAC,WAAW,CAAC,6CAAqC,EAAE,MAAM,CAAC,CAAC;IAC1F,OAAO,KAAuD,CAAC;AACjE,CAAC"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { type Type } from '@nestjs/common';
|
|
2
|
+
import { TransactionManager } from '@nestjs-transactional/core';
|
|
3
|
+
import { TransactionPhase } from '../types/transactional-listener.types';
|
|
4
|
+
/**
|
|
5
|
+
* Resolved per-event-type listener configuration used by
|
|
6
|
+
* {@link TransactionalEventDispatcher.registerListener}. Scanners flatten
|
|
7
|
+
* class-level `@TransactionalEventsHandler` metadata (which lists many
|
|
8
|
+
* event types per class) into one of these entries per event type.
|
|
9
|
+
*/
|
|
10
|
+
export interface DispatcherListenerMetadata {
|
|
11
|
+
readonly eventType: Type;
|
|
12
|
+
readonly phase: TransactionPhase;
|
|
13
|
+
readonly fallbackExecution: boolean;
|
|
14
|
+
readonly async: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* dataSource the listener's phase hooks attach to (Phase 14.3.1).
|
|
17
|
+
* Optional for backward compatibility — when omitted, defaults to
|
|
18
|
+
* `'default'`. Scanners normalise from decorator metadata before
|
|
19
|
+
* calling {@link TransactionalEventDispatcher.registerListener}.
|
|
20
|
+
*/
|
|
21
|
+
readonly dataSource?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Routes domain events to methods registered by scanners for
|
|
25
|
+
* `@TransactionalEventsHandler`-annotated classes, honouring the
|
|
26
|
+
* requested {@link TransactionPhase}:
|
|
27
|
+
*
|
|
28
|
+
* - Inside an active transaction, the listener is attached to the
|
|
29
|
+
* transaction as a before-commit / after-commit / after-rollback
|
|
30
|
+
* hook via {@link TransactionManager}. Phase AFTER_COMPLETION
|
|
31
|
+
* attaches to both after-commit and after-rollback.
|
|
32
|
+
* - Outside any transaction, listeners with `fallbackExecution: true`
|
|
33
|
+
* are invoked via `queueMicrotask`; others are dropped with a
|
|
34
|
+
* warning.
|
|
35
|
+
*
|
|
36
|
+
* Error propagation:
|
|
37
|
+
* - Errors thrown from a BEFORE_COMMIT listener propagate through the
|
|
38
|
+
* manager's commit path and trigger rollback (matches Spring).
|
|
39
|
+
* - Errors thrown from an AFTER_COMMIT / AFTER_ROLLBACK /
|
|
40
|
+
* AFTER_COMPLETION listener are logged and swallowed by
|
|
41
|
+
* {@link TransactionManager}'s hook runner — the transaction
|
|
42
|
+
* outcome is already decided.
|
|
43
|
+
* - `async: true` listeners are fire-and-forget via `queueMicrotask`:
|
|
44
|
+
* their failures are logged but never reach the enclosing
|
|
45
|
+
* transaction, even in BEFORE_COMMIT phase.
|
|
46
|
+
*
|
|
47
|
+
* **Multi-dataSource (Phase 14.3.1).** Hook attachment is per-dataSource:
|
|
48
|
+
* the dispatcher resolves the listener's bound dataSource via
|
|
49
|
+
* `TransactionContext.getActiveTransactionByDataSource(dataSource)`
|
|
50
|
+
* and pushes hooks directly onto that transaction's hook list,
|
|
51
|
+
* bypassing `TransactionManager.registerBeforeCommit`'s first-active-tx
|
|
52
|
+
* semantics. The dataSource comes from the listener metadata (defaults
|
|
53
|
+
* to `'default'`); scanners populate it from the
|
|
54
|
+
* `@TransactionalEventsHandler({ dataSource })` /
|
|
55
|
+
* `@IntegrationEventsHandler({ dataSource })` option. Cross-DS
|
|
56
|
+
* simultaneous transactions therefore route deterministically — a
|
|
57
|
+
* billing-bound handler attaches to the billing transaction's hooks
|
|
58
|
+
* even when an inventory transaction is also active on the same
|
|
59
|
+
* async stack.
|
|
60
|
+
*/
|
|
61
|
+
export declare class TransactionalEventDispatcher {
|
|
62
|
+
private readonly manager;
|
|
63
|
+
private readonly logger;
|
|
64
|
+
private readonly listenersByType;
|
|
65
|
+
/**
|
|
66
|
+
* `manager` is no longer consulted after Phase 14.3.1 — the
|
|
67
|
+
* dispatcher pushes hooks directly onto the per-dataSource
|
|
68
|
+
* `ActiveTransaction` resolved via `TransactionContext`. The
|
|
69
|
+
* parameter is preserved as a constructor argument so existing
|
|
70
|
+
* `new TransactionalEventDispatcher(manager)` callsites in tests
|
|
71
|
+
* keep compiling. NestJS DI continues to inject the manager
|
|
72
|
+
* automatically. A future cleanup phase may drop it.
|
|
73
|
+
*/
|
|
74
|
+
constructor(manager: TransactionManager);
|
|
75
|
+
/**
|
|
76
|
+
* Register a handler method discovered on `instance`. The lookup key
|
|
77
|
+
* is `metadata.eventType.name` — see {@link scheduleDispatch} for
|
|
78
|
+
* matching semantics. Multiple handlers for the same event are
|
|
79
|
+
* invoked in registration order within the hook runner.
|
|
80
|
+
*
|
|
81
|
+
* @throws {TypeError} If `instance[methodName]` is not a function.
|
|
82
|
+
* This is a caller contract violation — the scanner must only pass
|
|
83
|
+
* method names that actually resolve to methods on the instance.
|
|
84
|
+
*/
|
|
85
|
+
registerListener(instance: object, methodName: string, metadata: DispatcherListenerMetadata): void;
|
|
86
|
+
/**
|
|
87
|
+
* Route `event` to every listener registered for its exact
|
|
88
|
+
* constructor name. Listener matching is nominal and non-inheriting:
|
|
89
|
+
* a listener registered for `Parent` is NOT invoked for a `Child
|
|
90
|
+
* extends Parent` event, because the lookup uses
|
|
91
|
+
* `event.constructor.name`.
|
|
92
|
+
*/
|
|
93
|
+
scheduleDispatch(event: object): void;
|
|
94
|
+
private attachHookToTransaction;
|
|
95
|
+
private invokeListener;
|
|
96
|
+
private scheduleFallback;
|
|
97
|
+
private callListener;
|
|
98
|
+
private logListenerFailure;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=event-dispatcher.d.ts.map
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var TransactionalEventDispatcher_1;
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.TransactionalEventDispatcher = void 0;
|
|
14
|
+
const common_1 = require("@nestjs/common");
|
|
15
|
+
const core_1 = require("@nestjs-transactional/core");
|
|
16
|
+
const transactional_listener_types_1 = require("../types/transactional-listener.types");
|
|
17
|
+
/**
|
|
18
|
+
* Routes domain events to methods registered by scanners for
|
|
19
|
+
* `@TransactionalEventsHandler`-annotated classes, honouring the
|
|
20
|
+
* requested {@link TransactionPhase}:
|
|
21
|
+
*
|
|
22
|
+
* - Inside an active transaction, the listener is attached to the
|
|
23
|
+
* transaction as a before-commit / after-commit / after-rollback
|
|
24
|
+
* hook via {@link TransactionManager}. Phase AFTER_COMPLETION
|
|
25
|
+
* attaches to both after-commit and after-rollback.
|
|
26
|
+
* - Outside any transaction, listeners with `fallbackExecution: true`
|
|
27
|
+
* are invoked via `queueMicrotask`; others are dropped with a
|
|
28
|
+
* warning.
|
|
29
|
+
*
|
|
30
|
+
* Error propagation:
|
|
31
|
+
* - Errors thrown from a BEFORE_COMMIT listener propagate through the
|
|
32
|
+
* manager's commit path and trigger rollback (matches Spring).
|
|
33
|
+
* - Errors thrown from an AFTER_COMMIT / AFTER_ROLLBACK /
|
|
34
|
+
* AFTER_COMPLETION listener are logged and swallowed by
|
|
35
|
+
* {@link TransactionManager}'s hook runner — the transaction
|
|
36
|
+
* outcome is already decided.
|
|
37
|
+
* - `async: true` listeners are fire-and-forget via `queueMicrotask`:
|
|
38
|
+
* their failures are logged but never reach the enclosing
|
|
39
|
+
* transaction, even in BEFORE_COMMIT phase.
|
|
40
|
+
*
|
|
41
|
+
* **Multi-dataSource (Phase 14.3.1).** Hook attachment is per-dataSource:
|
|
42
|
+
* the dispatcher resolves the listener's bound dataSource via
|
|
43
|
+
* `TransactionContext.getActiveTransactionByDataSource(dataSource)`
|
|
44
|
+
* and pushes hooks directly onto that transaction's hook list,
|
|
45
|
+
* bypassing `TransactionManager.registerBeforeCommit`'s first-active-tx
|
|
46
|
+
* semantics. The dataSource comes from the listener metadata (defaults
|
|
47
|
+
* to `'default'`); scanners populate it from the
|
|
48
|
+
* `@TransactionalEventsHandler({ dataSource })` /
|
|
49
|
+
* `@IntegrationEventsHandler({ dataSource })` option. Cross-DS
|
|
50
|
+
* simultaneous transactions therefore route deterministically — a
|
|
51
|
+
* billing-bound handler attaches to the billing transaction's hooks
|
|
52
|
+
* even when an inventory transaction is also active on the same
|
|
53
|
+
* async stack.
|
|
54
|
+
*/
|
|
55
|
+
let TransactionalEventDispatcher = TransactionalEventDispatcher_1 = class TransactionalEventDispatcher {
|
|
56
|
+
manager;
|
|
57
|
+
logger = new common_1.Logger(TransactionalEventDispatcher_1.name);
|
|
58
|
+
listenersByType = new Map();
|
|
59
|
+
/**
|
|
60
|
+
* `manager` is no longer consulted after Phase 14.3.1 — the
|
|
61
|
+
* dispatcher pushes hooks directly onto the per-dataSource
|
|
62
|
+
* `ActiveTransaction` resolved via `TransactionContext`. The
|
|
63
|
+
* parameter is preserved as a constructor argument so existing
|
|
64
|
+
* `new TransactionalEventDispatcher(manager)` callsites in tests
|
|
65
|
+
* keep compiling. NestJS DI continues to inject the manager
|
|
66
|
+
* automatically. A future cleanup phase may drop it.
|
|
67
|
+
*/
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
69
|
+
constructor(manager) {
|
|
70
|
+
this.manager = manager;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Register a handler method discovered on `instance`. The lookup key
|
|
74
|
+
* is `metadata.eventType.name` — see {@link scheduleDispatch} for
|
|
75
|
+
* matching semantics. Multiple handlers for the same event are
|
|
76
|
+
* invoked in registration order within the hook runner.
|
|
77
|
+
*
|
|
78
|
+
* @throws {TypeError} If `instance[methodName]` is not a function.
|
|
79
|
+
* This is a caller contract violation — the scanner must only pass
|
|
80
|
+
* method names that actually resolve to methods on the instance.
|
|
81
|
+
*/
|
|
82
|
+
registerListener(instance, methodName, metadata) {
|
|
83
|
+
const rawMethod = instance[methodName];
|
|
84
|
+
if (typeof rawMethod !== 'function') {
|
|
85
|
+
throw new TypeError(`Transactional event handler target ${instance.constructor.name}.${methodName} ` +
|
|
86
|
+
`is not a function — cannot register as a listener`);
|
|
87
|
+
}
|
|
88
|
+
const typeName = metadata.eventType.name;
|
|
89
|
+
const instanceLabel = instance.constructor.name;
|
|
90
|
+
const entry = {
|
|
91
|
+
handler: rawMethod.bind(instance),
|
|
92
|
+
instanceLabel,
|
|
93
|
+
methodName,
|
|
94
|
+
metadata,
|
|
95
|
+
};
|
|
96
|
+
const listeners = this.listenersByType.get(typeName);
|
|
97
|
+
if (listeners === undefined) {
|
|
98
|
+
this.listenersByType.set(typeName, [entry]);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
listeners.push(entry);
|
|
102
|
+
}
|
|
103
|
+
this.logger.debug(`Registered handler ${instanceLabel}.${methodName} for ${typeName} phase=${metadata.phase}`);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Route `event` to every listener registered for its exact
|
|
107
|
+
* constructor name. Listener matching is nominal and non-inheriting:
|
|
108
|
+
* a listener registered for `Parent` is NOT invoked for a `Child
|
|
109
|
+
* extends Parent` event, because the lookup uses
|
|
110
|
+
* `event.constructor.name`.
|
|
111
|
+
*/
|
|
112
|
+
scheduleDispatch(event) {
|
|
113
|
+
const typeName = event.constructor.name;
|
|
114
|
+
const listeners = this.listenersByType.get(typeName);
|
|
115
|
+
if (listeners === undefined || listeners.length === 0) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const store = core_1.TransactionContext.getStore();
|
|
119
|
+
const anyTxActive = store !== undefined && store.activeTransactions.size > 0;
|
|
120
|
+
for (const listener of listeners) {
|
|
121
|
+
const dataSource = listener.metadata.dataSource ?? core_1.DEFAULT_DATA_SOURCE_NAME;
|
|
122
|
+
const tx = core_1.TransactionContext.getActiveTransactionByDataSource(dataSource);
|
|
123
|
+
if (tx === undefined) {
|
|
124
|
+
// No active transaction for this listener's dataSource. Two
|
|
125
|
+
// sub-cases:
|
|
126
|
+
// - There IS at least one active tx (just not for this DS):
|
|
127
|
+
// the event is "in a transaction" only for some other DS.
|
|
128
|
+
// A listener bound to a different DS has no business
|
|
129
|
+
// firing — skip silently. This preserves DD-023's
|
|
130
|
+
// independent-context semantics (cross-dataSource calls
|
|
131
|
+
// do not silently enrol).
|
|
132
|
+
// - No active tx anywhere: classic out-of-transaction case —
|
|
133
|
+
// fallbackExecution: true fires immediately, others warn
|
|
134
|
+
// and drop.
|
|
135
|
+
if (!anyTxActive && listener.metadata.fallbackExecution) {
|
|
136
|
+
this.scheduleFallback(listener, event);
|
|
137
|
+
}
|
|
138
|
+
else if (!anyTxActive) {
|
|
139
|
+
this.logger.warn(`Event ${typeName} published outside a transaction; handler ` +
|
|
140
|
+
`${listener.instanceLabel}.${listener.methodName} has no ` +
|
|
141
|
+
`fallbackExecution=true — skipping.`);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
this.logger.debug(`Event ${typeName}: handler ${listener.instanceLabel}.${listener.methodName} ` +
|
|
145
|
+
`bound to dataSource '${dataSource}' has no matching active transaction — skipping.`);
|
|
146
|
+
}
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
this.attachHookToTransaction(listener, event, tx);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
attachHookToTransaction(listener, event, tx) {
|
|
153
|
+
const invoke = () => this.invokeListener(listener, event);
|
|
154
|
+
const invokeWithError = (error) => this.invokeListener(listener, event, error);
|
|
155
|
+
// Push hooks directly onto THIS specific transaction's hook lists,
|
|
156
|
+
// bypassing TransactionManager.registerBeforeCommit's
|
|
157
|
+
// first-active-tx resolution. Same pattern as
|
|
158
|
+
// DataSourceOutboxPublisher.scheduleForPublication (Phase 14.3).
|
|
159
|
+
switch (listener.metadata.phase) {
|
|
160
|
+
case transactional_listener_types_1.TransactionPhase.BEFORE_COMMIT:
|
|
161
|
+
tx.beforeCommitHooks.push(invoke);
|
|
162
|
+
return;
|
|
163
|
+
case transactional_listener_types_1.TransactionPhase.AFTER_COMMIT:
|
|
164
|
+
tx.afterCommitHooks.push(invoke);
|
|
165
|
+
return;
|
|
166
|
+
case transactional_listener_types_1.TransactionPhase.AFTER_ROLLBACK:
|
|
167
|
+
tx.afterRollbackHooks.push(invokeWithError);
|
|
168
|
+
return;
|
|
169
|
+
case transactional_listener_types_1.TransactionPhase.AFTER_COMPLETION:
|
|
170
|
+
tx.afterCommitHooks.push(invoke);
|
|
171
|
+
tx.afterRollbackHooks.push(invokeWithError);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async invokeListener(listener, event, error) {
|
|
176
|
+
if (listener.metadata.async) {
|
|
177
|
+
queueMicrotask(() => {
|
|
178
|
+
try {
|
|
179
|
+
const result = this.callListener(listener, event, error);
|
|
180
|
+
if (result instanceof Promise) {
|
|
181
|
+
result.catch((err) => this.logListenerFailure(listener, err));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
this.logListenerFailure(listener, err);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
const result = this.callListener(listener, event, error);
|
|
192
|
+
if (result instanceof Promise) {
|
|
193
|
+
await result;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
this.logListenerFailure(listener, err);
|
|
198
|
+
// Rethrow so BEFORE_COMMIT failures reach the manager's rollback path.
|
|
199
|
+
// For AFTER_COMMIT/AFTER_ROLLBACK/AFTER_COMPLETION the manager's
|
|
200
|
+
// hook runner swallows the error after a warn-level log.
|
|
201
|
+
throw err;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
scheduleFallback(listener, event) {
|
|
205
|
+
queueMicrotask(() => {
|
|
206
|
+
try {
|
|
207
|
+
const result = this.callListener(listener, event);
|
|
208
|
+
if (result instanceof Promise) {
|
|
209
|
+
result.catch((err) => this.logListenerFailure(listener, err));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
this.logListenerFailure(listener, err);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
callListener(listener, event, error) {
|
|
218
|
+
return listener.handler(event, error);
|
|
219
|
+
}
|
|
220
|
+
logListenerFailure(listener, err) {
|
|
221
|
+
this.logger.error(`Transactional event handler ${listener.instanceLabel}.${listener.methodName} failed`, err instanceof Error ? err.stack : String(err));
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
exports.TransactionalEventDispatcher = TransactionalEventDispatcher;
|
|
225
|
+
exports.TransactionalEventDispatcher = TransactionalEventDispatcher = TransactionalEventDispatcher_1 = __decorate([
|
|
226
|
+
(0, common_1.Injectable)(),
|
|
227
|
+
__metadata("design:paramtypes", [core_1.TransactionManager])
|
|
228
|
+
], TransactionalEventDispatcher);
|
|
229
|
+
//# sourceMappingURL=event-dispatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-dispatcher.js","sourceRoot":"","sources":["../../src/event-dispatcher/event-dispatcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { IEvent, IEventPublisher } from '@nestjs/cqrs';
|
|
2
|
+
import { TransactionalEventDispatcher } from '../event-dispatcher/event-dispatcher';
|
|
3
|
+
/**
|
|
4
|
+
* Minimal structural contract for the outbox-side publisher. Declared
|
|
5
|
+
* here (and injected via the {@link OUTBOX_PUBLICATION_SCHEDULER}
|
|
6
|
+
* token) rather than importing from
|
|
7
|
+
* `@nestjs-transactional/outbox` directly — keeps `cqrs` usable
|
|
8
|
+
* without pulling in the outbox stack.
|
|
9
|
+
*
|
|
10
|
+
* `@nestjs-transactional/outbox`'s `OutboxEventPublisher`
|
|
11
|
+
* satisfies this interface structurally (it exposes
|
|
12
|
+
* `scheduleForPublication`). Wire the token in the host application
|
|
13
|
+
* when the outbox is enabled:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* providers: [
|
|
17
|
+
* {
|
|
18
|
+
* provide: OUTBOX_PUBLICATION_SCHEDULER,
|
|
19
|
+
* useExisting: OutboxEventPublisher,
|
|
20
|
+
* },
|
|
21
|
+
* ]
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export interface OutboxPublicationScheduler {
|
|
25
|
+
scheduleForPublication(event: unknown): void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* DI token for the optional outbox scheduler injected into
|
|
29
|
+
* {@link HybridEventPublisher}. When unbound, the hybrid publisher
|
|
30
|
+
* delegates only to the in-memory dispatcher.
|
|
31
|
+
*/
|
|
32
|
+
export declare const OUTBOX_PUBLICATION_SCHEDULER: unique symbol;
|
|
33
|
+
/**
|
|
34
|
+
* `IEventPublisher` implementation that routes aggregate-emitted
|
|
35
|
+
* events through BOTH the in-memory transactional dispatcher
|
|
36
|
+
* (`@TransactionalEventsHandler`) AND — when wired — the outbox
|
|
37
|
+
* (`@OutboxEventsHandler`, introduced in a later phase). Both paths
|
|
38
|
+
* run inside the surrounding transaction:
|
|
39
|
+
*
|
|
40
|
+
* - In-memory: the dispatcher attaches hooks to the current
|
|
41
|
+
* transaction so listeners fire at the configured phase
|
|
42
|
+
* (`AFTER_COMMIT` by default). No database rows are written.
|
|
43
|
+
* - Outbox: {@link OutboxPublicationScheduler.scheduleForPublication}
|
|
44
|
+
* buffers the event and flushes the buffer via one `beforeCommit`
|
|
45
|
+
* hook per transaction. Publication rows commit atomically with
|
|
46
|
+
* the business write; rollback skips the flush.
|
|
47
|
+
*
|
|
48
|
+
* When no outbox scheduler is bound, behaves identically to
|
|
49
|
+
* {@link TransactionalEventPublisher}. Callers get outbox semantics
|
|
50
|
+
* automatically as soon as the scheduler is wired — no code change
|
|
51
|
+
* at the call site.
|
|
52
|
+
*
|
|
53
|
+
* Important: the outbox path is best-effort from the perspective of
|
|
54
|
+
* `AggregateRoot.commit()`. `commit()` is synchronous, so we cannot
|
|
55
|
+
* await the DB write here. Errors raised while the `beforeCommit`
|
|
56
|
+
* hook flushes the buffer DO bubble up — they cause the transaction
|
|
57
|
+
* to roll back, which is the intended behavior.
|
|
58
|
+
*
|
|
59
|
+
* **Multi-dataSource (Phase 14.7).** The outbox scheduler is the
|
|
60
|
+
* smart-facade `OutboxEventPublisher` (DD-024). When wired,
|
|
61
|
+
* AggregateRoot events are routed to the per-dataSource publisher
|
|
62
|
+
* that owns the event class — the same routing semantics as
|
|
63
|
+
* `OutboxEventPublisher.publish()`. Multi-DS apps with multiple
|
|
64
|
+
* outbox stacks (one `OutboxModule.forRoot()` per dataSource per
|
|
65
|
+
* ADR-019) bind `OUTBOX_PUBLICATION_SCHEDULER` to the smart facade
|
|
66
|
+
* via `useExisting: OutboxEventPublisher`; the facade's internal
|
|
67
|
+
* Map fans out to the correct per-DS publisher.
|
|
68
|
+
*/
|
|
69
|
+
export declare class HybridEventPublisher implements IEventPublisher {
|
|
70
|
+
private readonly dispatcher;
|
|
71
|
+
private readonly outbox?;
|
|
72
|
+
constructor(dispatcher: TransactionalEventDispatcher, outbox?: OutboxPublicationScheduler | undefined);
|
|
73
|
+
publish<T extends IEvent>(event: T): void;
|
|
74
|
+
publishAll<T extends IEvent>(events: T[]): void;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=hybrid-event-publisher.d.ts.map
|