@fluojs/cqrs 1.1.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +29 -9
- package/README.md +29 -9
- package/dist/buses/command-bus.d.ts +19 -2
- package/dist/buses/command-bus.d.ts.map +1 -1
- package/dist/buses/command-bus.js +68 -24
- package/dist/buses/event-bus.d.ts +16 -8
- package/dist/buses/event-bus.d.ts.map +1 -1
- package/dist/buses/event-bus.js +84 -74
- package/dist/buses/event-handler-discovery.d.ts +12 -0
- package/dist/buses/event-handler-discovery.d.ts.map +1 -0
- package/dist/buses/event-handler-discovery.js +35 -0
- package/dist/buses/publish-drain-tracker.d.ts +38 -0
- package/dist/buses/publish-drain-tracker.d.ts.map +1 -0
- package/dist/buses/publish-drain-tracker.js +89 -0
- package/dist/buses/query-bus.d.ts +19 -2
- package/dist/buses/query-bus.d.ts.map +1 -1
- package/dist/buses/query-bus.js +68 -24
- package/dist/buses/saga-bus.d.ts +21 -9
- package/dist/buses/saga-bus.d.ts.map +1 -1
- package/dist/buses/saga-bus.js +113 -112
- package/dist/buses/saga-continuation.d.ts +14 -0
- package/dist/buses/saga-continuation.d.ts.map +1 -0
- package/dist/buses/saga-continuation.js +24 -0
- package/dist/buses/saga-discovery.d.ts +12 -0
- package/dist/buses/saga-discovery.d.ts.map +1 -0
- package/dist/buses/saga-discovery.js +39 -0
- package/dist/buses/saga-drain.d.ts +9 -0
- package/dist/buses/saga-drain.d.ts.map +1 -0
- package/dist/buses/saga-drain.js +34 -0
- package/dist/buses/saga-topology.d.ts +18 -0
- package/dist/buses/saga-topology.d.ts.map +1 -0
- package/dist/buses/saga-topology.js +46 -0
- package/dist/buses/shutdown-deadline.d.ts +23 -0
- package/dist/buses/shutdown-deadline.d.ts.map +1 -0
- package/dist/buses/shutdown-deadline.js +31 -0
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +15 -1
- package/dist/discovery.d.ts +18 -0
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +95 -12
- package/dist/dispatch-context.d.ts +35 -0
- package/dist/dispatch-context.d.ts.map +1 -0
- package/dist/dispatch-context.js +43 -0
- package/dist/errors.d.ts +4 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -2
- package/dist/module.d.ts +4 -1
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +24 -4
- package/dist/status.d.ts +4 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +10 -6
- package/dist/test-setup.d.ts +2 -0
- package/dist/test-setup.d.ts.map +1 -0
- package/dist/test-setup.js +4 -0
- package/dist/types.d.ts +3 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -7
package/dist/buses/event-bus.js
CHANGED
|
@@ -5,14 +5,19 @@ 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 { EVENT_BUS as FLUO_EVENT_BUS } from '@fluojs/event-bus';
|
|
9
|
-
import { APPLICATION_LOGGER, COMPILED_MODULES, RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
|
|
8
|
+
import { EventBusLifecycleService, EVENT_BUS as FLUO_EVENT_BUS } from '@fluojs/event-bus';
|
|
9
|
+
import { APPLICATION_LOGGER, COMPILED_MODULES, RUNTIME_CLEANUP_REGISTRATION, RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
|
|
10
10
|
import { CqrsBusBase } from '../discovery.js';
|
|
11
|
+
import { createInternalCqrsDispatchContext, getInternalCqrsDispatchContextState } from '../dispatch-context.js';
|
|
11
12
|
import { createIsolatedEvent } from '../event-clone.js';
|
|
12
|
-
import { getEventHandlerMetadata } from '../metadata.js';
|
|
13
13
|
import { createCqrsPlatformStatusSnapshot } from '../status.js';
|
|
14
14
|
import { CQRS_MODULE_OPTIONS } from '../tokens.js';
|
|
15
|
-
import {
|
|
15
|
+
import { discoverEventHandlerDescriptors } from './event-handler-discovery.js';
|
|
16
|
+
import { CqrsPublishDrainTracker } from './publish-drain-tracker.js';
|
|
17
|
+
import { CQRS_SAGA_DRAIN_AUTHORIZATION, CqrsSagaLifecycleService } from './saga-bus.js';
|
|
18
|
+
import { CqrsShutdownDeadline } from './shutdown-deadline.js';
|
|
19
|
+
import { CommandBusLifecycleService } from './command-bus.js';
|
|
20
|
+
import { QueryBusLifecycleService } from './query-bus.js';
|
|
16
21
|
const DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS = 5000;
|
|
17
22
|
function isEventHandler(value) {
|
|
18
23
|
if (typeof value !== 'object' || value === null) {
|
|
@@ -30,19 +35,27 @@ function isEventHandler(value) {
|
|
|
30
35
|
let _CqrsEventBusService;
|
|
31
36
|
class CqrsEventBusService extends CqrsBusBase {
|
|
32
37
|
static {
|
|
33
|
-
[_CqrsEventBusService, _initClass] = _applyDecs(this, [Inject(FLUO_EVENT_BUS, CqrsSagaLifecycleService, RUNTIME_CONTAINER, COMPILED_MODULES, APPLICATION_LOGGER, CQRS_MODULE_OPTIONS)], [], 0, void 0, CqrsBusBase).c;
|
|
38
|
+
[_CqrsEventBusService, _initClass] = _applyDecs(this, [Inject(FLUO_EVENT_BUS, CqrsSagaLifecycleService, RUNTIME_CONTAINER, COMPILED_MODULES, APPLICATION_LOGGER, CQRS_MODULE_OPTIONS, RUNTIME_CLEANUP_REGISTRATION, EventBusLifecycleService, CqrsShutdownDeadline, CommandBusLifecycleService, QueryBusLifecycleService)], [], 0, void 0, CqrsBusBase).c;
|
|
34
39
|
}
|
|
35
40
|
descriptors = [];
|
|
36
41
|
discoveryPromise;
|
|
37
42
|
discovered = false;
|
|
38
|
-
|
|
39
|
-
shutdownDrainTimeouts = 0;
|
|
43
|
+
publishDrainTracker;
|
|
40
44
|
lifecycleState = 'created';
|
|
41
|
-
|
|
45
|
+
unregisterShutdownStartCleanup;
|
|
46
|
+
constructor(eventBus, sagaService, runtimeContainer, compiledModules, logger, moduleOptions = {}, registerRuntimeCleanup = () => () => undefined, delegatedEventBus = undefined, shutdownDeadline = new CqrsShutdownDeadline(), commandService = undefined, queryService = undefined) {
|
|
42
47
|
super(runtimeContainer, compiledModules, logger);
|
|
43
48
|
this.eventBus = eventBus;
|
|
44
49
|
this.sagaService = sagaService;
|
|
45
50
|
this.moduleOptions = moduleOptions;
|
|
51
|
+
this.delegatedEventBus = delegatedEventBus;
|
|
52
|
+
this.shutdownDeadline = shutdownDeadline;
|
|
53
|
+
this.commandService = commandService;
|
|
54
|
+
this.queryService = queryService;
|
|
55
|
+
this.publishDrainTracker = new CqrsPublishDrainTracker(logger);
|
|
56
|
+
this.unregisterShutdownStartCleanup = registerRuntimeCleanup(() => {
|
|
57
|
+
this.markApplicationShutdownStarted();
|
|
58
|
+
});
|
|
46
59
|
}
|
|
47
60
|
async onApplicationBootstrap() {
|
|
48
61
|
this.lifecycleState = 'discovering';
|
|
@@ -55,9 +68,11 @@ class CqrsEventBusService extends CqrsBusBase {
|
|
|
55
68
|
}
|
|
56
69
|
}
|
|
57
70
|
async onApplicationShutdown() {
|
|
58
|
-
this.
|
|
59
|
-
|
|
60
|
-
|
|
71
|
+
this.markApplicationShutdownStarted();
|
|
72
|
+
this.unregisterShutdownStartCleanup?.();
|
|
73
|
+
this.unregisterShutdownStartCleanup = undefined;
|
|
74
|
+
if (this.publishDrainTracker.hasActivePipelines) {
|
|
75
|
+
await this.publishDrainTracker.drain(this.resolveRemainingShutdownDrainTimeoutMs());
|
|
61
76
|
}
|
|
62
77
|
this.lifecycleState = 'stopped';
|
|
63
78
|
}
|
|
@@ -68,16 +83,22 @@ class CqrsEventBusService extends CqrsBusBase {
|
|
|
68
83
|
* @returns A structured snapshot describing CQRS event-handler discovery and saga lifecycle state.
|
|
69
84
|
*/
|
|
70
85
|
createPlatformStatusSnapshot() {
|
|
86
|
+
const commandSnapshot = this.commandService?.getRuntimeSnapshot();
|
|
87
|
+
const querySnapshot = this.queryService?.getRuntimeSnapshot();
|
|
71
88
|
const sagaSnapshot = this.sagaService.getRuntimeSnapshot();
|
|
72
89
|
return createCqrsPlatformStatusSnapshot({
|
|
90
|
+
commandHandlersDiscovered: commandSnapshot?.commandHandlersDiscovered,
|
|
91
|
+
commandLifecycleState: commandSnapshot?.lifecycleState,
|
|
73
92
|
eventHandlersDiscovered: this.descriptors.length,
|
|
74
93
|
inFlightSagaExecutions: sagaSnapshot.inFlightSagaExecutions,
|
|
75
94
|
lifecycleState: this.lifecycleState,
|
|
95
|
+
queryHandlersDiscovered: querySnapshot?.queryHandlersDiscovered,
|
|
96
|
+
queryLifecycleState: querySnapshot?.lifecycleState,
|
|
76
97
|
sagaLifecycleState: sagaSnapshot.lifecycleState,
|
|
77
98
|
sagaShutdownDrainTimeouts: sagaSnapshot.shutdownDrainTimeouts,
|
|
78
99
|
sagasDiscovered: sagaSnapshot.sagasDiscovered,
|
|
79
100
|
shutdownDrainTimeoutMs: this.resolveShutdownDrainTimeoutMs(),
|
|
80
|
-
shutdownDrainTimeouts: this.shutdownDrainTimeouts
|
|
101
|
+
shutdownDrainTimeouts: this.publishDrainTracker.shutdownDrainTimeouts
|
|
81
102
|
});
|
|
82
103
|
}
|
|
83
104
|
|
|
@@ -91,8 +112,9 @@ class CqrsEventBusService extends CqrsBusBase {
|
|
|
91
112
|
* @throws {InvariantError} When a discovered provider does not implement `handle(event)`.
|
|
92
113
|
*/
|
|
93
114
|
async publish(event, context) {
|
|
94
|
-
this.assertAcceptingNewWork('publish');
|
|
95
|
-
|
|
115
|
+
this.assertAcceptingNewWork('publish', context);
|
|
116
|
+
const publishContext = this.createPublishContext(context);
|
|
117
|
+
await this.trackPublishPipeline(publishContext, this.runPublishPipeline(event, publishContext.context));
|
|
96
118
|
}
|
|
97
119
|
|
|
98
120
|
/**
|
|
@@ -103,8 +125,9 @@ class CqrsEventBusService extends CqrsBusBase {
|
|
|
103
125
|
* @returns A promise that resolves once all events are published.
|
|
104
126
|
*/
|
|
105
127
|
async publishAll(events, context) {
|
|
106
|
-
this.assertAcceptingNewWork('publishAll');
|
|
107
|
-
|
|
128
|
+
this.assertAcceptingNewWork('publishAll', context);
|
|
129
|
+
const publishContext = this.createPublishContext(context);
|
|
130
|
+
await this.trackPublishPipeline(publishContext, this.runPublishAllPipeline(events, publishContext.context));
|
|
108
131
|
}
|
|
109
132
|
async runPublishPipeline(event, context) {
|
|
110
133
|
await this.ensureDiscovered();
|
|
@@ -116,51 +139,62 @@ class CqrsEventBusService extends CqrsBusBase {
|
|
|
116
139
|
await instance.handle(createIsolatedEvent(descriptor.eventType, event), context);
|
|
117
140
|
}
|
|
118
141
|
await this.sagaService.dispatch(event, context, {
|
|
119
|
-
|
|
142
|
+
afterSagas: async () => this.eventBus.publish(event),
|
|
143
|
+
drainAuthorization: CQRS_SAGA_DRAIN_AUTHORIZATION
|
|
120
144
|
});
|
|
121
|
-
await this.eventBus.publish(event);
|
|
122
145
|
}
|
|
123
146
|
async runPublishAllPipeline(events, context) {
|
|
124
147
|
for (const event of events) {
|
|
125
148
|
await this.runPublishPipeline(event, context);
|
|
126
149
|
}
|
|
127
150
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
throw new InvariantError(`CQRS event bus cannot ${operation} after shutdown has started.`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
async trackPublishPipeline(pipeline) {
|
|
134
|
-
this.activePublishPipelines.add(pipeline);
|
|
151
|
+
async trackPublishPipeline(publishContext, pipeline) {
|
|
152
|
+
const releaseSagaGraph = this.sagaService.acquireAuthorizedPipelineLease();
|
|
135
153
|
try {
|
|
136
|
-
await pipeline;
|
|
154
|
+
await this.publishDrainTracker.track(pipeline, publishContext.drainToken);
|
|
137
155
|
} finally {
|
|
138
|
-
|
|
156
|
+
releaseSagaGraph();
|
|
139
157
|
}
|
|
140
158
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const drained = await this.awaitShutdownDrain(activePipelines, timeoutMs);
|
|
145
|
-
if (!drained) {
|
|
146
|
-
this.shutdownDrainTimeouts += 1;
|
|
147
|
-
this.logger.warn(`CQRS event shutdown drain exceeded ${String(timeoutMs)}ms with ${String(activePipelines.length)} active publish pipeline(s); continuing shutdown.`, 'CqrsEventBusService');
|
|
159
|
+
assertAcceptingNewWork(operation, context) {
|
|
160
|
+
if (this.lifecycleState === 'stopped') {
|
|
161
|
+
throw new InvariantError(`CQRS event bus cannot ${operation} after shutdown has started.`);
|
|
148
162
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
timeoutId = setTimeout(() => resolve(false), timeoutMs);
|
|
154
|
-
});
|
|
155
|
-
const drain = Promise.allSettled(activePipelines).then(() => true);
|
|
156
|
-
try {
|
|
157
|
-
return await Promise.race([drain, timeout]);
|
|
158
|
-
} finally {
|
|
159
|
-
if (timeoutId) {
|
|
160
|
-
clearTimeout(timeoutId);
|
|
163
|
+
if (this.lifecycleState === 'stopping') {
|
|
164
|
+
const drainToken = getInternalCqrsDispatchContextState(context)?.publishDrainToken;
|
|
165
|
+
if (!drainToken || !this.publishDrainTracker.isActive(drainToken)) {
|
|
166
|
+
throw new InvariantError(`CQRS event bus cannot ${operation} after shutdown has started.`);
|
|
161
167
|
}
|
|
162
168
|
}
|
|
163
169
|
}
|
|
170
|
+
markApplicationShutdownStarted() {
|
|
171
|
+
this.shutdownDeadline.start(this.resolveShutdownDrainTimeoutMs());
|
|
172
|
+
const deadlineAtMs = this.shutdownDeadline.deadlineAtMs();
|
|
173
|
+
if (deadlineAtMs !== undefined && this.delegatedEventBus) {
|
|
174
|
+
this.delegatedEventBus.adoptShutdownDeadline(deadlineAtMs);
|
|
175
|
+
}
|
|
176
|
+
if (this.lifecycleState !== 'stopped') {
|
|
177
|
+
this.lifecycleState = 'stopping';
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
createPublishContext(context) {
|
|
181
|
+
const internalState = getInternalCqrsDispatchContextState(context);
|
|
182
|
+
const drainToken = internalState?.publishDrainToken ?? Symbol('fluo.cqrs.activePublishDrain');
|
|
183
|
+
if (context && internalState?.publishDrainToken) {
|
|
184
|
+
return {
|
|
185
|
+
context,
|
|
186
|
+
drainToken
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
context: createInternalCqrsDispatchContext({
|
|
191
|
+
publishDrainToken: drainToken,
|
|
192
|
+
sagaContinuationScope: internalState?.sagaContinuationScope,
|
|
193
|
+
sagaTopology: internalState?.sagaTopology
|
|
194
|
+
}),
|
|
195
|
+
drainToken
|
|
196
|
+
};
|
|
197
|
+
}
|
|
164
198
|
resolveShutdownDrainTimeoutMs() {
|
|
165
199
|
const timeoutMs = this.moduleOptions.shutdown?.drainTimeoutMs;
|
|
166
200
|
if (typeof timeoutMs !== 'number' || !Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
@@ -168,6 +202,9 @@ class CqrsEventBusService extends CqrsBusBase {
|
|
|
168
202
|
}
|
|
169
203
|
return Math.floor(timeoutMs);
|
|
170
204
|
}
|
|
205
|
+
resolveRemainingShutdownDrainTimeoutMs() {
|
|
206
|
+
return this.shutdownDeadline.remainingTimeoutMs() ?? this.resolveShutdownDrainTimeoutMs();
|
|
207
|
+
}
|
|
171
208
|
matchEventDescriptors(event) {
|
|
172
209
|
return this.descriptors.filter(descriptor => event instanceof descriptor.eventType);
|
|
173
210
|
}
|
|
@@ -195,34 +232,7 @@ class CqrsEventBusService extends CqrsBusBase {
|
|
|
195
232
|
}
|
|
196
233
|
}
|
|
197
234
|
discoverEventDescriptors() {
|
|
198
|
-
|
|
199
|
-
const seenByTarget = new WeakMap();
|
|
200
|
-
for (const candidate of this.discoveryCandidates()) {
|
|
201
|
-
const metadata = getEventHandlerMetadata(candidate.targetType);
|
|
202
|
-
if (!metadata) {
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
if (candidate.scope !== 'singleton') {
|
|
206
|
-
this.logger.warn(`${candidate.targetType.name} in module ${candidate.moduleName} declares @EventHandler() but is registered with ${candidate.scope} scope. Event handlers are registered only for singleton providers.`, 'CqrsEventBusService');
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
const seenEventTypes = seenByTarget.get(candidate.targetType) ?? new Set();
|
|
210
|
-
if (seenEventTypes.has(metadata.eventType)) {
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
seenEventTypes.add(metadata.eventType);
|
|
214
|
-
seenByTarget.set(candidate.targetType, seenEventTypes);
|
|
215
|
-
const alreadyRegistered = descriptors.some(descriptor => descriptor.eventType === metadata.eventType && descriptor.targetType === candidate.targetType);
|
|
216
|
-
if (!alreadyRegistered) {
|
|
217
|
-
descriptors.push({
|
|
218
|
-
eventType: metadata.eventType,
|
|
219
|
-
moduleName: candidate.moduleName,
|
|
220
|
-
targetType: candidate.targetType,
|
|
221
|
-
token: candidate.token
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
return descriptors;
|
|
235
|
+
return discoverEventHandlerDescriptors(this.discoveryCandidates(), this.logger);
|
|
226
236
|
}
|
|
227
237
|
static {
|
|
228
238
|
_initClass();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ApplicationLogger } from '@fluojs/runtime';
|
|
2
|
+
import type { DiscoveryCandidate } from '../discovery.js';
|
|
3
|
+
import type { EventHandlerDescriptor } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Discovers singleton event-handler 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 Event-handler descriptors in discovery order.
|
|
10
|
+
*/
|
|
11
|
+
export declare function discoverEventHandlerDescriptors(candidates: readonly DiscoveryCandidate[], logger: ApplicationLogger): EventHandlerDescriptor[];
|
|
12
|
+
//# sourceMappingURL=event-handler-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-handler-discovery.d.ts","sourceRoot":"","sources":["../../src/buses/event-handler-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,EAAiB,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAEzE;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,SAAS,kBAAkB,EAAE,EACzC,MAAM,EAAE,iBAAiB,GACxB,sBAAsB,EAAE,CAoC1B"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getEventHandlerMetadata } from '../metadata.js';
|
|
2
|
+
/**
|
|
3
|
+
* Discovers singleton event-handler 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 Event-handler descriptors in discovery order.
|
|
8
|
+
*/
|
|
9
|
+
export function discoverEventHandlerDescriptors(candidates, logger) {
|
|
10
|
+
const descriptors = [];
|
|
11
|
+
const seenEventTypesByToken = new Map();
|
|
12
|
+
for (const candidate of candidates) {
|
|
13
|
+
const metadata = getEventHandlerMetadata(candidate.targetType);
|
|
14
|
+
if (!metadata) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (candidate.scope !== 'singleton') {
|
|
18
|
+
logger.warn(`${candidate.targetType.name} in module ${candidate.moduleName} declares @EventHandler() but is registered with ${candidate.scope} scope. Event handlers are registered only for singleton providers.`, 'CqrsEventBusService');
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const seenEventTypes = seenEventTypesByToken.get(candidate.token) ?? new Set();
|
|
22
|
+
if (seenEventTypes.has(metadata.eventType)) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
seenEventTypes.add(metadata.eventType);
|
|
26
|
+
seenEventTypesByToken.set(candidate.token, seenEventTypes);
|
|
27
|
+
descriptors.push({
|
|
28
|
+
eventType: metadata.eventType,
|
|
29
|
+
moduleName: candidate.moduleName,
|
|
30
|
+
targetType: candidate.targetType,
|
|
31
|
+
token: candidate.token
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return descriptors;
|
|
35
|
+
}
|
|
@@ -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;YAoBV,iBAAiB;CAKhC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
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
|
+
if (timeoutMs <= 0) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
let timeoutId;
|
|
72
|
+
const timeout = new Promise(resolve => {
|
|
73
|
+
timeoutId = setTimeout(() => resolve(false), timeoutMs);
|
|
74
|
+
});
|
|
75
|
+
const drain = this.waitForQuiescence().then(() => true);
|
|
76
|
+
try {
|
|
77
|
+
return await Promise.race([drain, timeout]);
|
|
78
|
+
} finally {
|
|
79
|
+
if (timeoutId) {
|
|
80
|
+
clearTimeout(timeoutId);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async waitForQuiescence() {
|
|
85
|
+
while (this.activePipelines.size > 0) {
|
|
86
|
+
await Promise.allSettled([...this.activePipelines]);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -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,25 @@ 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>;
|
|
20
|
+
/**
|
|
21
|
+
* Returns query-handler discovery and lifecycle state for CQRS diagnostics.
|
|
22
|
+
*
|
|
23
|
+
* @returns Current discovery state, lifecycle state, and discovered query-handler count.
|
|
24
|
+
*/
|
|
25
|
+
getRuntimeSnapshot(): {
|
|
26
|
+
discovered: boolean;
|
|
27
|
+
lifecycleState: 'created' | 'discovering' | 'ready' | 'stopping' | 'stopped' | 'failed';
|
|
28
|
+
queryHandlersDiscovered: number;
|
|
29
|
+
};
|
|
15
30
|
/**
|
|
16
31
|
* Executes one query by dispatching it to the discovered handler for its constructor.
|
|
17
32
|
*
|
|
@@ -23,6 +38,8 @@ export declare class QueryBusLifecycleService extends CqrsBusBase implements Que
|
|
|
23
38
|
* @throws {InvariantError} When the resolved provider does not implement `execute(query)`.
|
|
24
39
|
*/
|
|
25
40
|
execute<TQuery extends IQuery<TResult>, TResult = unknown>(query: TQuery, context?: CqrsDispatchContext): Promise<TResult>;
|
|
41
|
+
private assertAcceptingNewWork;
|
|
42
|
+
private markApplicationShutdownStarted;
|
|
26
43
|
private ensureDiscovered;
|
|
27
44
|
private discoverHandlers;
|
|
28
45
|
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;;;;OAIG;IACH,kBAAkB,IAAI;QACpB,UAAU,EAAE,OAAO,CAAC;QACpB,cAAc,EAAE,SAAS,GAAG,aAAa,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;QACxF,uBAAuB,EAAE,MAAM,CAAC;KACjC;IAUD;;;;;;;;;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,52 @@ 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';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Returns query-handler discovery and lifecycle state for CQRS diagnostics.
|
|
64
|
+
*
|
|
65
|
+
* @returns Current discovery state, lifecycle state, and discovered query-handler count.
|
|
66
|
+
*/
|
|
67
|
+
getRuntimeSnapshot() {
|
|
68
|
+
const stopped = this.lifecycleState === 'stopped';
|
|
69
|
+
return {
|
|
70
|
+
discovered: stopped ? false : this.discovered,
|
|
71
|
+
lifecycleState: this.lifecycleState,
|
|
72
|
+
queryHandlersDiscovered: stopped ? 0 : this.descriptors.size
|
|
73
|
+
};
|
|
35
74
|
}
|
|
36
75
|
|
|
37
76
|
/**
|
|
@@ -45,6 +84,7 @@ class QueryBusLifecycleService extends CqrsBusBase {
|
|
|
45
84
|
* @throws {InvariantError} When the resolved provider does not implement `execute(query)`.
|
|
46
85
|
*/
|
|
47
86
|
async execute(query, context) {
|
|
87
|
+
this.assertAcceptingNewWork('execute');
|
|
48
88
|
await this.ensureDiscovered();
|
|
49
89
|
const queryType = query.constructor;
|
|
50
90
|
const descriptor = this.descriptors.get(queryType);
|
|
@@ -57,6 +97,16 @@ class QueryBusLifecycleService extends CqrsBusBase {
|
|
|
57
97
|
}
|
|
58
98
|
return await instance.execute(query, context);
|
|
59
99
|
}
|
|
100
|
+
assertAcceptingNewWork(operation) {
|
|
101
|
+
if (this.lifecycleState === 'stopping' || this.lifecycleState === 'stopped') {
|
|
102
|
+
throw new InvariantError(`CQRS query bus cannot ${operation} after shutdown has started.`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
markApplicationShutdownStarted() {
|
|
106
|
+
if (this.lifecycleState !== 'stopped') {
|
|
107
|
+
this.lifecycleState = 'stopping';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
60
110
|
async ensureDiscovered() {
|
|
61
111
|
if (this.discovered) {
|
|
62
112
|
return;
|
|
@@ -82,7 +132,6 @@ class QueryBusLifecycleService extends CqrsBusBase {
|
|
|
82
132
|
}
|
|
83
133
|
discoverQueryDescriptors() {
|
|
84
134
|
const descriptors = new Map();
|
|
85
|
-
const seenByTarget = new WeakMap();
|
|
86
135
|
for (const candidate of this.discoveryCandidates()) {
|
|
87
136
|
const metadata = getQueryHandlerMetadata(candidate.targetType);
|
|
88
137
|
if (!metadata) {
|
|
@@ -92,27 +141,22 @@ class QueryBusLifecycleService extends CqrsBusBase {
|
|
|
92
141
|
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
142
|
continue;
|
|
94
143
|
}
|
|
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
144
|
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
|
-
});
|
|
145
|
+
const nextDescriptor = {
|
|
146
|
+
moduleName: candidate.moduleName,
|
|
147
|
+
targetType: candidate.targetType,
|
|
148
|
+
token: candidate.token
|
|
149
|
+
};
|
|
150
|
+
if (existing) {
|
|
151
|
+
if (isSameHandlerRegistration(existing, nextDescriptor)) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
throw new DuplicateQueryHandlerError(createDuplicateHandlerMessage('query', metadata.queryType, existing, nextDescriptor));
|
|
115
155
|
}
|
|
156
|
+
descriptors.set(metadata.queryType, {
|
|
157
|
+
queryType: metadata.queryType,
|
|
158
|
+
...nextDescriptor
|
|
159
|
+
});
|
|
116
160
|
}
|
|
117
161
|
return descriptors;
|
|
118
162
|
}
|