@fluojs/cqrs 1.1.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +7 -7
- package/README.md +7 -7
- package/dist/buses/command-bus.d.ts +9 -2
- package/dist/buses/command-bus.d.ts.map +1 -1
- package/dist/buses/command-bus.js +54 -24
- package/dist/buses/event-bus.d.ts +6 -7
- package/dist/buses/event-bus.d.ts.map +1 -1
- package/dist/buses/event-bus.js +52 -74
- package/dist/buses/event-handler-discovery.d.ts +12 -0
- package/dist/buses/event-handler-discovery.d.ts.map +1 -0
- package/dist/buses/event-handler-discovery.js +35 -0
- package/dist/buses/publish-drain-tracker.d.ts +38 -0
- package/dist/buses/publish-drain-tracker.d.ts.map +1 -0
- package/dist/buses/publish-drain-tracker.js +86 -0
- package/dist/buses/query-bus.d.ts +9 -2
- package/dist/buses/query-bus.d.ts.map +1 -1
- package/dist/buses/query-bus.js +54 -24
- package/dist/buses/saga-bus.d.ts +8 -9
- package/dist/buses/saga-bus.d.ts.map +1 -1
- package/dist/buses/saga-bus.js +37 -96
- package/dist/buses/saga-discovery.d.ts +12 -0
- package/dist/buses/saga-discovery.d.ts.map +1 -0
- package/dist/buses/saga-discovery.js +39 -0
- package/dist/buses/saga-drain.d.ts +9 -0
- package/dist/buses/saga-drain.d.ts.map +1 -0
- package/dist/buses/saga-drain.js +34 -0
- package/dist/buses/saga-topology.d.ts +15 -0
- package/dist/buses/saga-topology.d.ts.map +1 -0
- package/dist/buses/saga-topology.js +39 -0
- package/dist/discovery.d.ts +16 -0
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +16 -9
- package/dist/dispatch-context.d.ts +33 -0
- package/dist/dispatch-context.d.ts.map +1 -0
- package/dist/dispatch-context.js +42 -0
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +8 -1
- package/dist/test-setup.d.ts +2 -0
- package/dist/test-setup.d.ts.map +1 -0
- package/dist/test-setup.js +4 -0
- package/dist/types.d.ts +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ApplicationLogger } from '@fluojs/runtime';
|
|
2
|
+
/** Tracks active CQRS publish pipelines and their shutdown-drain capabilities. */
|
|
3
|
+
export declare class CqrsPublishDrainTracker {
|
|
4
|
+
private readonly logger;
|
|
5
|
+
private readonly activePipelines;
|
|
6
|
+
private readonly activeTokens;
|
|
7
|
+
private drainTimeouts;
|
|
8
|
+
constructor(logger: ApplicationLogger);
|
|
9
|
+
/** Current count of bounded shutdown-drain timeouts. */
|
|
10
|
+
get shutdownDrainTimeouts(): number;
|
|
11
|
+
/** Whether at least one publish pipeline remains active. */
|
|
12
|
+
get hasActivePipelines(): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Returns whether a private publish capability belongs to active work.
|
|
15
|
+
*
|
|
16
|
+
* @param token Private publish-drain token from an opaque context.
|
|
17
|
+
* @returns `true` while at least one associated pipeline is active.
|
|
18
|
+
*/
|
|
19
|
+
isActive(token: symbol): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Tracks one publish pipeline and releases its private capability on settlement.
|
|
22
|
+
*
|
|
23
|
+
* @param pipeline Publish work to track.
|
|
24
|
+
* @param token Private drain token associated with the pipeline.
|
|
25
|
+
* @returns A promise that mirrors the tracked pipeline.
|
|
26
|
+
*/
|
|
27
|
+
track(pipeline: Promise<void>, token: symbol): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Waits for publish quiescence within the configured shutdown bound.
|
|
30
|
+
*
|
|
31
|
+
* @param timeoutMs Maximum drain duration in milliseconds.
|
|
32
|
+
* @returns A promise that resolves after quiescence or timeout reporting.
|
|
33
|
+
*/
|
|
34
|
+
drain(timeoutMs: number): Promise<void>;
|
|
35
|
+
private awaitDrain;
|
|
36
|
+
private waitForQuiescence;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=publish-drain-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish-drain-tracker.d.ts","sourceRoot":"","sources":["../../src/buses/publish-drain-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,kFAAkF;AAClF,qBAAa,uBAAuB;IAKtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJnC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA4B;IAC5D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;IAC1D,OAAO,CAAC,aAAa,CAAK;gBAEG,MAAM,EAAE,iBAAiB;IAEtD,wDAAwD;IACxD,IAAI,qBAAqB,IAAI,MAAM,CAElC;IAED,4DAA4D;IAC5D,IAAI,kBAAkB,IAAI,OAAO,CAEhC;IAED;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAIhC;;;;;;OAMG;IACG,KAAK,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBlE;;;;;OAKG;IACG,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAc/B,UAAU;YAgBV,iBAAiB;CAKhC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** Tracks active CQRS publish pipelines and their shutdown-drain capabilities. */
|
|
2
|
+
export class CqrsPublishDrainTracker {
|
|
3
|
+
activePipelines = new Set();
|
|
4
|
+
activeTokens = new Map();
|
|
5
|
+
drainTimeouts = 0;
|
|
6
|
+
constructor(logger) {
|
|
7
|
+
this.logger = logger;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Current count of bounded shutdown-drain timeouts. */
|
|
11
|
+
get shutdownDrainTimeouts() {
|
|
12
|
+
return this.drainTimeouts;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Whether at least one publish pipeline remains active. */
|
|
16
|
+
get hasActivePipelines() {
|
|
17
|
+
return this.activePipelines.size > 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns whether a private publish capability belongs to active work.
|
|
22
|
+
*
|
|
23
|
+
* @param token Private publish-drain token from an opaque context.
|
|
24
|
+
* @returns `true` while at least one associated pipeline is active.
|
|
25
|
+
*/
|
|
26
|
+
isActive(token) {
|
|
27
|
+
return (this.activeTokens.get(token) ?? 0) > 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Tracks one publish pipeline and releases its private capability on settlement.
|
|
32
|
+
*
|
|
33
|
+
* @param pipeline Publish work to track.
|
|
34
|
+
* @param token Private drain token associated with the pipeline.
|
|
35
|
+
* @returns A promise that mirrors the tracked pipeline.
|
|
36
|
+
*/
|
|
37
|
+
async track(pipeline, token) {
|
|
38
|
+
this.activePipelines.add(pipeline);
|
|
39
|
+
this.activeTokens.set(token, (this.activeTokens.get(token) ?? 0) + 1);
|
|
40
|
+
try {
|
|
41
|
+
await pipeline;
|
|
42
|
+
} finally {
|
|
43
|
+
this.activePipelines.delete(pipeline);
|
|
44
|
+
const activeCount = this.activeTokens.get(token) ?? 0;
|
|
45
|
+
if (activeCount <= 1) {
|
|
46
|
+
this.activeTokens.delete(token);
|
|
47
|
+
} else {
|
|
48
|
+
this.activeTokens.set(token, activeCount - 1);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Waits for publish quiescence within the configured shutdown bound.
|
|
55
|
+
*
|
|
56
|
+
* @param timeoutMs Maximum drain duration in milliseconds.
|
|
57
|
+
* @returns A promise that resolves after quiescence or timeout reporting.
|
|
58
|
+
*/
|
|
59
|
+
async drain(timeoutMs) {
|
|
60
|
+
const drained = await this.awaitDrain(timeoutMs);
|
|
61
|
+
if (drained) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.drainTimeouts += 1;
|
|
65
|
+
this.logger.warn(`CQRS event shutdown drain exceeded ${String(timeoutMs)}ms with ${String(this.activePipelines.size)} active publish pipeline(s); continuing shutdown.`, 'CqrsEventBusService');
|
|
66
|
+
}
|
|
67
|
+
async awaitDrain(timeoutMs) {
|
|
68
|
+
let timeoutId;
|
|
69
|
+
const timeout = new Promise(resolve => {
|
|
70
|
+
timeoutId = setTimeout(() => resolve(false), timeoutMs);
|
|
71
|
+
});
|
|
72
|
+
const drain = this.waitForQuiescence().then(() => true);
|
|
73
|
+
try {
|
|
74
|
+
return await Promise.race([drain, timeout]);
|
|
75
|
+
} finally {
|
|
76
|
+
if (timeoutId) {
|
|
77
|
+
clearTimeout(timeoutId);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async waitForQuiescence() {
|
|
82
|
+
while (this.activePipelines.size > 0) {
|
|
83
|
+
await Promise.allSettled([...this.activePipelines]);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Container } from '@fluojs/di';
|
|
2
|
+
import type { ApplicationLogger, CompiledModule, OnApplicationBootstrap, OnApplicationShutdown, RuntimeCleanupRegistration } from '@fluojs/runtime';
|
|
2
3
|
import { CqrsBusBase } from '../discovery.js';
|
|
3
4
|
import type { CqrsDispatchContext, IQuery, QueryBus } from '../types.js';
|
|
4
5
|
/**
|
|
@@ -7,11 +8,15 @@ import type { CqrsDispatchContext, IQuery, QueryBus } from '../types.js';
|
|
|
7
8
|
* The query bus resolves singleton handlers only, warns on unsupported scopes,
|
|
8
9
|
* and preserves the one-query-to-one-handler contract used by the CQRS surface.
|
|
9
10
|
*/
|
|
10
|
-
export declare class QueryBusLifecycleService extends CqrsBusBase implements QueryBus, OnApplicationBootstrap {
|
|
11
|
+
export declare class QueryBusLifecycleService extends CqrsBusBase implements QueryBus, OnApplicationBootstrap, OnApplicationShutdown {
|
|
11
12
|
private descriptors;
|
|
12
13
|
private discoveryPromise;
|
|
13
14
|
private discovered;
|
|
15
|
+
private lifecycleState;
|
|
16
|
+
private unregisterShutdownStartCleanup;
|
|
17
|
+
constructor(runtimeContainer: Container, compiledModules: readonly CompiledModule[], logger: ApplicationLogger, registerRuntimeCleanup?: RuntimeCleanupRegistration);
|
|
14
18
|
onApplicationBootstrap(): Promise<void>;
|
|
19
|
+
onApplicationShutdown(): Promise<void>;
|
|
15
20
|
/**
|
|
16
21
|
* Executes one query by dispatching it to the discovered handler for its constructor.
|
|
17
22
|
*
|
|
@@ -23,6 +28,8 @@ export declare class QueryBusLifecycleService extends CqrsBusBase implements Que
|
|
|
23
28
|
* @throws {InvariantError} When the resolved provider does not implement `execute(query)`.
|
|
24
29
|
*/
|
|
25
30
|
execute<TQuery extends IQuery<TResult>, TResult = unknown>(query: TQuery, context?: CqrsDispatchContext): Promise<TResult>;
|
|
31
|
+
private assertAcceptingNewWork;
|
|
32
|
+
private markApplicationShutdownStarted;
|
|
26
33
|
private ensureDiscovered;
|
|
27
34
|
private discoverHandlers;
|
|
28
35
|
private discoverQueryDescriptors;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-bus.d.ts","sourceRoot":"","sources":["../../src/buses/query-bus.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,sBAAsB,
|
|
1
|
+
{"version":3,"file":"query-bus.d.ts","sourceRoot":"","sources":["../../src/buses/query-bus.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACd,sBAAsB,EACtB,qBAAqB,EACrB,0BAA0B,EAC3B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,WAAW,EAA4D,MAAM,iBAAiB,CAAC;AAGxG,OAAO,KAAK,EACV,mBAAmB,EACnB,MAAM,EAEN,QAAQ,EAGT,MAAM,aAAa,CAAC;AAUrB;;;;;GAKG;AACH,qBACa,wBAAyB,SAAQ,WAAY,YAAW,QAAQ,EAAE,sBAAsB,EAAE,qBAAqB;IAC1H,OAAO,CAAC,WAAW,CAAgD;IACnE,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,cAAc,CAAsF;IAC5G,OAAO,CAAC,8BAA8B,CAA2B;gBAG/D,gBAAgB,EAAE,SAAS,EAC3B,eAAe,EAAE,SAAS,cAAc,EAAE,EAC1C,MAAM,EAAE,iBAAiB,EACzB,sBAAsB,GAAE,0BAAkD;IAStE,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAYvC,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAW5C;;;;;;;;;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;IAoBhI,OAAO,CAAC,sBAAsB;IAM9B,OAAO,CAAC,8BAA8B;YAMxB,gBAAgB;YAchB,gBAAgB;IAe9B,OAAO,CAAC,wBAAwB;CA2CjC"}
|
package/dist/buses/query-bus.js
CHANGED
|
@@ -5,8 +5,8 @@ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e =
|
|
|
5
5
|
function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
|
|
6
6
|
function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
|
|
7
7
|
import { Inject, InvariantError } from '@fluojs/core';
|
|
8
|
-
import { APPLICATION_LOGGER, COMPILED_MODULES, RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
|
|
9
|
-
import { CqrsBusBase, createDuplicateHandlerMessage } from '../discovery.js';
|
|
8
|
+
import { APPLICATION_LOGGER, COMPILED_MODULES, RUNTIME_CLEANUP_REGISTRATION, RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
|
|
9
|
+
import { CqrsBusBase, createDuplicateHandlerMessage, isSameHandlerRegistration } from '../discovery.js';
|
|
10
10
|
import { DuplicateQueryHandlerError, QueryHandlerNotFoundException } from '../errors.js';
|
|
11
11
|
import { getQueryHandlerMetadata } from '../metadata.js';
|
|
12
12
|
function isQueryHandler(value) {
|
|
@@ -25,13 +25,38 @@ function isQueryHandler(value) {
|
|
|
25
25
|
let _QueryBusLifecycleSer;
|
|
26
26
|
class QueryBusLifecycleService extends CqrsBusBase {
|
|
27
27
|
static {
|
|
28
|
-
[_QueryBusLifecycleSer, _initClass] = _applyDecs(this, [Inject(RUNTIME_CONTAINER, COMPILED_MODULES, APPLICATION_LOGGER)], [], 0, void 0, CqrsBusBase).c;
|
|
28
|
+
[_QueryBusLifecycleSer, _initClass] = _applyDecs(this, [Inject(RUNTIME_CONTAINER, COMPILED_MODULES, APPLICATION_LOGGER, RUNTIME_CLEANUP_REGISTRATION)], [], 0, void 0, CqrsBusBase).c;
|
|
29
29
|
}
|
|
30
30
|
descriptors = new Map();
|
|
31
31
|
discoveryPromise;
|
|
32
32
|
discovered = false;
|
|
33
|
+
lifecycleState = 'created';
|
|
34
|
+
unregisterShutdownStartCleanup;
|
|
35
|
+
constructor(runtimeContainer, compiledModules, logger, registerRuntimeCleanup = () => () => undefined) {
|
|
36
|
+
super(runtimeContainer, compiledModules, logger);
|
|
37
|
+
this.unregisterShutdownStartCleanup = registerRuntimeCleanup(() => {
|
|
38
|
+
this.markApplicationShutdownStarted();
|
|
39
|
+
});
|
|
40
|
+
}
|
|
33
41
|
async onApplicationBootstrap() {
|
|
34
|
-
|
|
42
|
+
this.lifecycleState = 'discovering';
|
|
43
|
+
try {
|
|
44
|
+
await this.ensureDiscovered();
|
|
45
|
+
this.lifecycleState = 'ready';
|
|
46
|
+
} catch (error) {
|
|
47
|
+
this.lifecycleState = 'failed';
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async onApplicationShutdown() {
|
|
52
|
+
this.markApplicationShutdownStarted();
|
|
53
|
+
this.unregisterShutdownStartCleanup?.();
|
|
54
|
+
this.unregisterShutdownStartCleanup = undefined;
|
|
55
|
+
this.descriptors.clear();
|
|
56
|
+
this.handlerInstances.clear();
|
|
57
|
+
this.discovered = false;
|
|
58
|
+
this.discoveryPromise = undefined;
|
|
59
|
+
this.lifecycleState = 'stopped';
|
|
35
60
|
}
|
|
36
61
|
|
|
37
62
|
/**
|
|
@@ -45,6 +70,7 @@ class QueryBusLifecycleService extends CqrsBusBase {
|
|
|
45
70
|
* @throws {InvariantError} When the resolved provider does not implement `execute(query)`.
|
|
46
71
|
*/
|
|
47
72
|
async execute(query, context) {
|
|
73
|
+
this.assertAcceptingNewWork('execute');
|
|
48
74
|
await this.ensureDiscovered();
|
|
49
75
|
const queryType = query.constructor;
|
|
50
76
|
const descriptor = this.descriptors.get(queryType);
|
|
@@ -57,6 +83,16 @@ class QueryBusLifecycleService extends CqrsBusBase {
|
|
|
57
83
|
}
|
|
58
84
|
return await instance.execute(query, context);
|
|
59
85
|
}
|
|
86
|
+
assertAcceptingNewWork(operation) {
|
|
87
|
+
if (this.lifecycleState === 'stopping' || this.lifecycleState === 'stopped') {
|
|
88
|
+
throw new InvariantError(`CQRS query bus cannot ${operation} after shutdown has started.`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
markApplicationShutdownStarted() {
|
|
92
|
+
if (this.lifecycleState !== 'stopped') {
|
|
93
|
+
this.lifecycleState = 'stopping';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
60
96
|
async ensureDiscovered() {
|
|
61
97
|
if (this.discovered) {
|
|
62
98
|
return;
|
|
@@ -82,7 +118,6 @@ class QueryBusLifecycleService extends CqrsBusBase {
|
|
|
82
118
|
}
|
|
83
119
|
discoverQueryDescriptors() {
|
|
84
120
|
const descriptors = new Map();
|
|
85
|
-
const seenByTarget = new WeakMap();
|
|
86
121
|
for (const candidate of this.discoveryCandidates()) {
|
|
87
122
|
const metadata = getQueryHandlerMetadata(candidate.targetType);
|
|
88
123
|
if (!metadata) {
|
|
@@ -92,27 +127,22 @@ class QueryBusLifecycleService extends CqrsBusBase {
|
|
|
92
127
|
this.logger.warn(`${candidate.targetType.name} in module ${candidate.moduleName} declares @QueryHandler() but is registered with ${candidate.scope} scope. Query handlers are registered only for singleton providers.`, 'QueryBusLifecycleService');
|
|
93
128
|
continue;
|
|
94
129
|
}
|
|
95
|
-
const seenQueryTypes = seenByTarget.get(candidate.targetType) ?? new Set();
|
|
96
|
-
if (seenQueryTypes.has(metadata.queryType)) {
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
seenQueryTypes.add(metadata.queryType);
|
|
100
|
-
seenByTarget.set(candidate.targetType, seenQueryTypes);
|
|
101
130
|
const existing = descriptors.get(metadata.queryType);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
targetType: candidate.targetType,
|
|
113
|
-
token: candidate.token
|
|
114
|
-
});
|
|
131
|
+
const nextDescriptor = {
|
|
132
|
+
moduleName: candidate.moduleName,
|
|
133
|
+
targetType: candidate.targetType,
|
|
134
|
+
token: candidate.token
|
|
135
|
+
};
|
|
136
|
+
if (existing) {
|
|
137
|
+
if (isSameHandlerRegistration(existing, nextDescriptor)) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
throw new DuplicateQueryHandlerError(createDuplicateHandlerMessage('query', metadata.queryType, existing, nextDescriptor));
|
|
115
141
|
}
|
|
142
|
+
descriptors.set(metadata.queryType, {
|
|
143
|
+
queryType: metadata.queryType,
|
|
144
|
+
...nextDescriptor
|
|
145
|
+
});
|
|
116
146
|
}
|
|
117
147
|
return descriptors;
|
|
118
148
|
}
|
package/dist/buses/saga-bus.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import type { OnApplicationBootstrap, OnApplicationShutdown } from '@fluojs/runtime';
|
|
1
|
+
import type { OnApplicationBootstrap, OnApplicationShutdown, RuntimeCleanupRegistration } from '@fluojs/runtime';
|
|
2
2
|
import { CqrsBusBase } from '../discovery.js';
|
|
3
3
|
import type { CqrsModuleOptions } from '../module.js';
|
|
4
4
|
import type { CqrsDispatchContext, IEvent } from '../types.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
5
|
+
/** Private capability authorizing saga dispatch from an already active publish drain. */
|
|
6
|
+
export declare const CQRS_SAGA_DRAIN_AUTHORIZATION: unique symbol;
|
|
8
7
|
/**
|
|
9
8
|
* Runtime saga coordinator that discovers `@Saga()` providers and serializes execution per saga token.
|
|
10
9
|
*
|
|
@@ -20,7 +19,8 @@ export declare class CqrsSagaLifecycleService extends CqrsBusBase implements OnA
|
|
|
20
19
|
private lifecycleState;
|
|
21
20
|
private readonly pendingDispatches;
|
|
22
21
|
private shutdownDrainTimeouts;
|
|
23
|
-
|
|
22
|
+
private unregisterShutdownStartCleanup;
|
|
23
|
+
constructor(runtimeContainer: ConstructorParameters<typeof CqrsBusBase>[0], compiledModules: ConstructorParameters<typeof CqrsBusBase>[1], logger: ConstructorParameters<typeof CqrsBusBase>[2], moduleOptions?: CqrsModuleOptions, registerRuntimeCleanup?: RuntimeCleanupRegistration);
|
|
24
24
|
onApplicationBootstrap(): Promise<void>;
|
|
25
25
|
onApplicationShutdown(): Promise<void>;
|
|
26
26
|
/**
|
|
@@ -41,18 +41,17 @@ export declare class CqrsSagaLifecycleService extends CqrsBusBase implements OnA
|
|
|
41
41
|
* @param event Event instance that may trigger one or more sagas.
|
|
42
42
|
* @returns A promise that resolves once all matching saga chains for the event complete.
|
|
43
43
|
*/
|
|
44
|
-
dispatch<TEvent extends IEvent>(event: TEvent, context?: CqrsDispatchContext,
|
|
44
|
+
dispatch<TEvent extends IEvent>(event: TEvent, context?: CqrsDispatchContext, drainAuthorization?: typeof CQRS_SAGA_DRAIN_AUTHORIZATION): Promise<void>;
|
|
45
45
|
private assertAcceptingNewWork;
|
|
46
|
+
private markApplicationShutdownStarted;
|
|
46
47
|
private matchSagaDescriptors;
|
|
47
48
|
private dispatchWithOrdering;
|
|
48
49
|
private drainActiveSagaWork;
|
|
49
|
-
private
|
|
50
|
+
private reportShutdownDrainTimeout;
|
|
50
51
|
private resolveShutdownDrainTimeoutMs;
|
|
51
|
-
private createDispatchContext;
|
|
52
52
|
private invokeSaga;
|
|
53
53
|
private ensureDiscovered;
|
|
54
54
|
private discoverHandlers;
|
|
55
55
|
private discoverSagaDescriptors;
|
|
56
56
|
}
|
|
57
|
-
export {};
|
|
58
57
|
//# 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":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;
|
|
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,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAGjH,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAG9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,OAAO,KAAK,EAAE,mBAAmB,EAAiB,MAAM,EAAyB,MAAM,aAAa,CAAC;AAOrG,yFAAyF;AACzF,eAAO,MAAM,6BAA6B,EAAE,OAAO,MAAmD,CAAC;AAkBvG;;;;;GAKG;AACH,qBACa,wBAAyB,SAAQ,WAAY,YAAW,sBAAsB,EAAE,qBAAqB;IAc9G,OAAO,CAAC,QAAQ,CAAC,aAAa;IAbhC,OAAO,CAAC,kBAAkB,CAA8C;IACxE,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAmC;IACnE,OAAO,CAAC,cAAc,CAAsF;IAC5G,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA4B;IAC9D,OAAO,CAAC,qBAAqB,CAAK;IAClC,OAAO,CAAC,8BAA8B,CAA2B;gBAG/D,gBAAgB,EAAE,qBAAqB,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,EAC9D,eAAe,EAAE,qBAAqB,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,EAC7D,MAAM,EAAE,qBAAqB,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,EACnC,aAAa,GAAE,iBAAsB,EACtD,sBAAsB,GAAE,0BAAkD;IAStE,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAYvC,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAe5C;;;;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,EAClC,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,mBAAmB,EAC7B,kBAAkB,CAAC,EAAE,OAAO,6BAA6B,GACxD,OAAO,CAAC,IAAI,CAAC;IAahB,OAAO,CAAC,sBAAsB;IAS9B,OAAO,CAAC,8BAA8B;IAMtC,OAAO,CAAC,oBAAoB;YAYd,oBAAoB;YAuBpB,mBAAmB;IAUjC,OAAO,CAAC,0BAA0B;IAQlC,OAAO,CAAC,6BAA6B;YAUvB,UAAU;YAoBV,gBAAgB;YAchB,gBAAgB;IAiB9B,OAAO,CAAC,uBAAuB;CAGhC"}
|
package/dist/buses/saga-bus.js
CHANGED
|
@@ -5,15 +5,18 @@ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e =
|
|
|
5
5
|
function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
|
|
6
6
|
function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
|
|
7
7
|
import { FluoError, Inject, InvariantError } from '@fluojs/core';
|
|
8
|
-
import { APPLICATION_LOGGER, COMPILED_MODULES, RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
|
|
8
|
+
import { APPLICATION_LOGGER, COMPILED_MODULES, RUNTIME_CLEANUP_REGISTRATION, RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
|
|
9
9
|
import { CqrsBusBase } from '../discovery.js';
|
|
10
|
-
import { SagaExecutionError
|
|
10
|
+
import { SagaExecutionError } from '../errors.js';
|
|
11
11
|
import { createIsolatedEvent } from '../event-clone.js';
|
|
12
|
-
import { getSagaMetadata } from '../metadata.js';
|
|
13
12
|
import { CQRS_MODULE_OPTIONS } from '../tokens.js';
|
|
14
|
-
|
|
13
|
+
import { discoverSagaDescriptors } from './saga-discovery.js';
|
|
14
|
+
import { drainPendingSagaDispatches } from './saga-drain.js';
|
|
15
|
+
import { enterSagaTopology } from './saga-topology.js';
|
|
15
16
|
const DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS = 5000;
|
|
16
|
-
|
|
17
|
+
|
|
18
|
+
/** Private capability authorizing saga dispatch from an already active publish drain. */
|
|
19
|
+
export const CQRS_SAGA_DRAIN_AUTHORIZATION = Symbol('fluo.cqrs.sagaDrainAuthorization');
|
|
17
20
|
function isSaga(value) {
|
|
18
21
|
if (typeof value !== 'object' || value === null) {
|
|
19
22
|
return false;
|
|
@@ -26,12 +29,6 @@ function toErrorMessage(error) {
|
|
|
26
29
|
}
|
|
27
30
|
return String(error);
|
|
28
31
|
}
|
|
29
|
-
function isCqrsDispatchContextState(context) {
|
|
30
|
-
if (typeof context !== 'object' || context === null) {
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
return context[cqrsDispatchContextStateBrand] === true;
|
|
34
|
-
}
|
|
35
32
|
|
|
36
33
|
/**
|
|
37
34
|
* Runtime saga coordinator that discovers `@Saga()` providers and serializes execution per saga token.
|
|
@@ -42,7 +39,7 @@ function isCqrsDispatchContextState(context) {
|
|
|
42
39
|
let _CqrsSagaLifecycleSer;
|
|
43
40
|
class CqrsSagaLifecycleService extends CqrsBusBase {
|
|
44
41
|
static {
|
|
45
|
-
[_CqrsSagaLifecycleSer, _initClass] = _applyDecs(this, [Inject(RUNTIME_CONTAINER, COMPILED_MODULES, APPLICATION_LOGGER, CQRS_MODULE_OPTIONS)], [], 0, void 0, CqrsBusBase).c;
|
|
42
|
+
[_CqrsSagaLifecycleSer, _initClass] = _applyDecs(this, [Inject(RUNTIME_CONTAINER, COMPILED_MODULES, APPLICATION_LOGGER, CQRS_MODULE_OPTIONS, RUNTIME_CLEANUP_REGISTRATION)], [], 0, void 0, CqrsBusBase).c;
|
|
46
43
|
}
|
|
47
44
|
descriptorsByEvent = new Map();
|
|
48
45
|
discoveryPromise;
|
|
@@ -51,9 +48,13 @@ class CqrsSagaLifecycleService extends CqrsBusBase {
|
|
|
51
48
|
lifecycleState = 'created';
|
|
52
49
|
pendingDispatches = new Set();
|
|
53
50
|
shutdownDrainTimeouts = 0;
|
|
54
|
-
|
|
51
|
+
unregisterShutdownStartCleanup;
|
|
52
|
+
constructor(runtimeContainer, compiledModules, logger, moduleOptions = {}, registerRuntimeCleanup = () => () => undefined) {
|
|
55
53
|
super(runtimeContainer, compiledModules, logger);
|
|
56
54
|
this.moduleOptions = moduleOptions;
|
|
55
|
+
this.unregisterShutdownStartCleanup = registerRuntimeCleanup(() => {
|
|
56
|
+
this.markApplicationShutdownStarted();
|
|
57
|
+
});
|
|
57
58
|
}
|
|
58
59
|
async onApplicationBootstrap() {
|
|
59
60
|
this.lifecycleState = 'discovering';
|
|
@@ -66,7 +67,9 @@ class CqrsSagaLifecycleService extends CqrsBusBase {
|
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
69
|
async onApplicationShutdown() {
|
|
69
|
-
this.
|
|
70
|
+
this.markApplicationShutdownStarted();
|
|
71
|
+
this.unregisterShutdownStartCleanup?.();
|
|
72
|
+
this.unregisterShutdownStartCleanup = undefined;
|
|
70
73
|
await this.drainActiveSagaWork();
|
|
71
74
|
this.executionChains.clear();
|
|
72
75
|
this.handlerInstances.clear();
|
|
@@ -97,8 +100,8 @@ class CqrsSagaLifecycleService extends CqrsBusBase {
|
|
|
97
100
|
* @param event Event instance that may trigger one or more sagas.
|
|
98
101
|
* @returns A promise that resolves once all matching saga chains for the event complete.
|
|
99
102
|
*/
|
|
100
|
-
async dispatch(event, context,
|
|
101
|
-
this.assertAcceptingNewWork(
|
|
103
|
+
async dispatch(event, context, drainAuthorization) {
|
|
104
|
+
this.assertAcceptingNewWork(drainAuthorization);
|
|
102
105
|
await this.ensureDiscovered();
|
|
103
106
|
const descriptors = this.matchSagaDescriptors(event);
|
|
104
107
|
if (descriptors.length === 0) {
|
|
@@ -106,14 +109,16 @@ class CqrsSagaLifecycleService extends CqrsBusBase {
|
|
|
106
109
|
}
|
|
107
110
|
await Promise.all(descriptors.map(descriptor => this.dispatchWithOrdering(descriptor, event, context)));
|
|
108
111
|
}
|
|
109
|
-
assertAcceptingNewWork(
|
|
110
|
-
if (
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
if (this.lifecycleState === 'stopping' || this.lifecycleState === 'stopped') {
|
|
112
|
+
assertAcceptingNewWork(drainAuthorization) {
|
|
113
|
+
if ((this.lifecycleState === 'stopping' || this.lifecycleState === 'stopped') && drainAuthorization !== CQRS_SAGA_DRAIN_AUTHORIZATION) {
|
|
114
114
|
throw new InvariantError('CQRS saga bus cannot dispatch after shutdown has started.');
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
+
markApplicationShutdownStarted() {
|
|
118
|
+
if (this.lifecycleState !== 'stopped') {
|
|
119
|
+
this.lifecycleState = 'stopping';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
117
122
|
matchSagaDescriptors(event) {
|
|
118
123
|
const descriptors = [];
|
|
119
124
|
for (const [eventType, eventDescriptors] of this.descriptorsByEvent.entries()) {
|
|
@@ -124,23 +129,14 @@ class CqrsSagaLifecycleService extends CqrsBusBase {
|
|
|
124
129
|
return descriptors;
|
|
125
130
|
}
|
|
126
131
|
async dispatchWithOrdering(descriptor, event, activeContext) {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const isActiveToken = activeState?.activeRoutes.some(route => route.token === descriptor.token) ?? false;
|
|
131
|
-
if (isActiveRoute) {
|
|
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(' -> ')}.`);
|
|
133
|
-
}
|
|
134
|
-
if ((activeState?.depth ?? 0) >= MAX_NESTED_SAGA_DEPTH) {
|
|
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.');
|
|
136
|
-
}
|
|
137
|
-
if (isActiveToken) {
|
|
138
|
-
await this.invokeSaga(descriptor, event, this.createDispatchContext(activeState, descriptor, routeLabel));
|
|
132
|
+
const topology = enterSagaTopology(activeContext, descriptor);
|
|
133
|
+
if (topology.reentrantToken) {
|
|
134
|
+
await this.invokeSaga(descriptor, event, topology.context);
|
|
139
135
|
return;
|
|
140
136
|
}
|
|
141
137
|
const previous = this.executionChains.get(descriptor.token) ?? Promise.resolve();
|
|
142
138
|
const current = previous.then(async () => {
|
|
143
|
-
await this.invokeSaga(descriptor, event,
|
|
139
|
+
await this.invokeSaga(descriptor, event, topology.context);
|
|
144
140
|
});
|
|
145
141
|
this.executionChains.set(descriptor.token, current.catch(() => undefined));
|
|
146
142
|
this.pendingDispatches.add(current);
|
|
@@ -151,30 +147,16 @@ class CqrsSagaLifecycleService extends CqrsBusBase {
|
|
|
151
147
|
}
|
|
152
148
|
}
|
|
153
149
|
async drainActiveSagaWork() {
|
|
154
|
-
const activeWork = [...this.pendingDispatches, ...this.executionChains.values()];
|
|
155
|
-
if (activeWork.length === 0) {
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
150
|
const timeoutMs = this.resolveShutdownDrainTimeoutMs();
|
|
159
|
-
const
|
|
151
|
+
const activeWorkCount = this.pendingDispatches.size;
|
|
152
|
+
const drained = await drainPendingSagaDispatches(this.pendingDispatches, timeoutMs);
|
|
160
153
|
if (!drained) {
|
|
161
|
-
this.
|
|
162
|
-
this.logger.warn(`CQRS saga shutdown drain exceeded ${String(timeoutMs)}ms with ${String(activeWork.length)} active saga task(s); continuing shutdown.`, 'CqrsSagaLifecycleService');
|
|
154
|
+
this.reportShutdownDrainTimeout(timeoutMs, activeWorkCount);
|
|
163
155
|
}
|
|
164
156
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
timeoutId = setTimeout(() => resolve(false), timeoutMs);
|
|
169
|
-
});
|
|
170
|
-
const drain = Promise.allSettled(activeWork).then(() => true);
|
|
171
|
-
try {
|
|
172
|
-
return await Promise.race([drain, timeout]);
|
|
173
|
-
} finally {
|
|
174
|
-
if (timeoutId) {
|
|
175
|
-
clearTimeout(timeoutId);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
157
|
+
reportShutdownDrainTimeout(timeoutMs, activeWorkCount) {
|
|
158
|
+
this.shutdownDrainTimeouts += 1;
|
|
159
|
+
this.logger.warn(`CQRS saga shutdown drain exceeded ${String(timeoutMs)}ms with ${String(activeWorkCount)} active saga task(s); continuing shutdown.`, 'CqrsSagaLifecycleService');
|
|
178
160
|
}
|
|
179
161
|
resolveShutdownDrainTimeoutMs() {
|
|
180
162
|
const timeoutMs = this.moduleOptions.shutdown?.drainTimeoutMs;
|
|
@@ -183,17 +165,6 @@ class CqrsSagaLifecycleService extends CqrsBusBase {
|
|
|
183
165
|
}
|
|
184
166
|
return Math.floor(timeoutMs);
|
|
185
167
|
}
|
|
186
|
-
createDispatchContext(activeContext, descriptor, routeLabel) {
|
|
187
|
-
return {
|
|
188
|
-
[cqrsDispatchContextStateBrand]: true,
|
|
189
|
-
activeRoutes: [...(activeContext?.activeRoutes ?? []), {
|
|
190
|
-
eventType: descriptor.eventType,
|
|
191
|
-
token: descriptor.token
|
|
192
|
-
}],
|
|
193
|
-
depth: (activeContext?.depth ?? 0) + 1,
|
|
194
|
-
path: [...(activeContext?.path ?? []), routeLabel]
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
168
|
async invokeSaga(descriptor, event, context) {
|
|
198
169
|
const instance = await this.resolveHandlerInstance(descriptor.token);
|
|
199
170
|
if (!isSaga(instance)) {
|
|
@@ -234,37 +205,7 @@ class CqrsSagaLifecycleService extends CqrsBusBase {
|
|
|
234
205
|
}
|
|
235
206
|
}
|
|
236
207
|
discoverSagaDescriptors() {
|
|
237
|
-
|
|
238
|
-
const seenByTarget = new WeakMap();
|
|
239
|
-
for (const candidate of this.discoveryCandidates()) {
|
|
240
|
-
const metadata = getSagaMetadata(candidate.targetType);
|
|
241
|
-
if (!metadata) {
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
if (candidate.scope !== 'singleton') {
|
|
245
|
-
this.logger.warn(`${candidate.targetType.name} in module ${candidate.moduleName} declares @Saga() but is registered with ${candidate.scope} scope. Sagas are registered only for singleton providers.`, 'CqrsSagaLifecycleService');
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
const seenEventTypes = seenByTarget.get(candidate.targetType) ?? new Set();
|
|
249
|
-
for (const eventType of metadata.eventTypes) {
|
|
250
|
-
if (seenEventTypes.has(eventType)) {
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
seenEventTypes.add(eventType);
|
|
254
|
-
const descriptors = descriptorsByEvent.get(eventType) ?? [];
|
|
255
|
-
if (!descriptors.some(descriptor => descriptor.targetType === candidate.targetType)) {
|
|
256
|
-
descriptors.push({
|
|
257
|
-
eventType,
|
|
258
|
-
moduleName: candidate.moduleName,
|
|
259
|
-
targetType: candidate.targetType,
|
|
260
|
-
token: candidate.token
|
|
261
|
-
});
|
|
262
|
-
descriptorsByEvent.set(eventType, descriptors);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
seenByTarget.set(candidate.targetType, seenEventTypes);
|
|
266
|
-
}
|
|
267
|
-
return descriptorsByEvent;
|
|
208
|
+
return discoverSagaDescriptors(this.discoveryCandidates(), this.logger);
|
|
268
209
|
}
|
|
269
210
|
static {
|
|
270
211
|
_initClass();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ApplicationLogger } from '@fluojs/runtime';
|
|
2
|
+
import type { DiscoveryCandidate } from '../discovery.js';
|
|
3
|
+
import type { CqrsEventType, SagaDescriptor } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Discovers singleton saga registrations by provider-token identity.
|
|
6
|
+
*
|
|
7
|
+
* @param candidates Provider registrations compiled from the application module graph.
|
|
8
|
+
* @param logger Application logger used for non-singleton discovery warnings.
|
|
9
|
+
* @returns Saga descriptors grouped by event type in discovery order.
|
|
10
|
+
*/
|
|
11
|
+
export declare function discoverSagaDescriptors(candidates: readonly DiscoveryCandidate[], logger: ApplicationLogger): Map<CqrsEventType, SagaDescriptor[]>;
|
|
12
|
+
//# sourceMappingURL=saga-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"saga-discovery.d.ts","sourceRoot":"","sources":["../../src/buses/saga-discovery.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAEjE;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,SAAS,kBAAkB,EAAE,EACzC,MAAM,EAAE,iBAAiB,GACxB,GAAG,CAAC,aAAa,EAAE,cAAc,EAAE,CAAC,CAyCtC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getSagaMetadata } from '../metadata.js';
|
|
2
|
+
/**
|
|
3
|
+
* Discovers singleton saga registrations by provider-token identity.
|
|
4
|
+
*
|
|
5
|
+
* @param candidates Provider registrations compiled from the application module graph.
|
|
6
|
+
* @param logger Application logger used for non-singleton discovery warnings.
|
|
7
|
+
* @returns Saga descriptors grouped by event type in discovery order.
|
|
8
|
+
*/
|
|
9
|
+
export function discoverSagaDescriptors(candidates, logger) {
|
|
10
|
+
const descriptorsByEvent = new Map();
|
|
11
|
+
const seenEventTypesByToken = new Map();
|
|
12
|
+
for (const candidate of candidates) {
|
|
13
|
+
const metadata = getSagaMetadata(candidate.targetType);
|
|
14
|
+
if (!metadata) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (candidate.scope !== 'singleton') {
|
|
18
|
+
logger.warn(`${candidate.targetType.name} in module ${candidate.moduleName} declares @Saga() but is registered with ${candidate.scope} scope. Sagas are registered only for singleton providers.`, 'CqrsSagaLifecycleService');
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const seenEventTypes = seenEventTypesByToken.get(candidate.token) ?? new Set();
|
|
22
|
+
for (const eventType of metadata.eventTypes) {
|
|
23
|
+
if (seenEventTypes.has(eventType)) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
seenEventTypes.add(eventType);
|
|
27
|
+
const descriptors = descriptorsByEvent.get(eventType) ?? [];
|
|
28
|
+
descriptors.push({
|
|
29
|
+
eventType,
|
|
30
|
+
moduleName: candidate.moduleName,
|
|
31
|
+
targetType: candidate.targetType,
|
|
32
|
+
token: candidate.token
|
|
33
|
+
});
|
|
34
|
+
descriptorsByEvent.set(eventType, descriptors);
|
|
35
|
+
}
|
|
36
|
+
seenEventTypesByToken.set(candidate.token, seenEventTypes);
|
|
37
|
+
}
|
|
38
|
+
return descriptorsByEvent;
|
|
39
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Waits until a mutable set of saga tasks becomes quiescent within one deadline.
|
|
3
|
+
*
|
|
4
|
+
* @param pendingDispatches Live saga task set owned by the lifecycle service.
|
|
5
|
+
* @param timeoutMs Maximum drain duration in milliseconds.
|
|
6
|
+
* @returns `true` when all current and late-added tasks settle before the deadline.
|
|
7
|
+
*/
|
|
8
|
+
export declare function drainPendingSagaDispatches(pendingDispatches: ReadonlySet<Promise<void>>, timeoutMs: number): Promise<boolean>;
|
|
9
|
+
//# sourceMappingURL=saga-drain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"saga-drain.d.ts","sourceRoot":"","sources":["../../src/buses/saga-drain.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAsB,0BAA0B,CAC9C,iBAAiB,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAC7C,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAgBlB"}
|