@fluojs/event-bus 1.0.0-beta.1

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.
@@ -0,0 +1,500 @@
1
+ let _initClass;
2
+ function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; }
3
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
4
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
5
+ 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
+ function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
7
+ import { Inject } from '@fluojs/core';
8
+ import { cloneWithFallback, getClassDiMetadata } from '@fluojs/core/internal';
9
+ import { APPLICATION_LOGGER, COMPILED_MODULES, RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
10
+ import { getEventHandlerMetadataEntries } from './metadata.js';
11
+ import { createEventBusPlatformStatusSnapshot } from './status.js';
12
+ import { EVENT_BUS_OPTIONS } from './tokens.js';
13
+ function createIsolatedEvent(eventType, source) {
14
+ const clonedPayload = cloneWithFallback(source);
15
+ if (typeof clonedPayload !== 'object' || clonedPayload === null) {
16
+ return clonedPayload;
17
+ }
18
+ return Object.assign(Object.create(eventType.prototype), clonedPayload);
19
+ }
20
+ class EventPublishTimeoutError extends Error {
21
+ constructor(timeoutMs) {
22
+ super(`Event publish timed out after ${String(timeoutMs)}ms.`);
23
+ this.timeoutMs = timeoutMs;
24
+ }
25
+ }
26
+ class EventPublishAbortError extends Error {
27
+ constructor() {
28
+ super('Event publish was aborted.');
29
+ }
30
+ }
31
+ function scopeFromProvider(provider) {
32
+ if (typeof provider === 'function') {
33
+ return getClassDiMetadata(provider)?.scope ?? 'singleton';
34
+ }
35
+ if ('useClass' in provider) {
36
+ return provider.scope ?? getClassDiMetadata(provider.useClass)?.scope ?? 'singleton';
37
+ }
38
+ return 'scope' in provider ? provider.scope ?? 'singleton' : 'singleton';
39
+ }
40
+ function methodKeyToName(methodKey) {
41
+ return typeof methodKey === 'symbol' ? methodKey.toString() : methodKey;
42
+ }
43
+ function isClassProvider(provider) {
44
+ return typeof provider === 'object' && provider !== null && 'useClass' in provider;
45
+ }
46
+
47
+ /**
48
+ * Lifecycle-managed in-process event bus with optional external transport fan-out.
49
+ *
50
+ * The service discovers `@OnEvent()` handlers, clones payloads before dispatch,
51
+ * and can publish the same events to an external transport such as Redis Pub/Sub.
52
+ */
53
+ let _EventBusLifecycleSer;
54
+ class EventBusLifecycleService {
55
+ static {
56
+ [_EventBusLifecycleSer, _initClass] = _applyDecs(this, [Inject(RUNTIME_CONTAINER, COMPILED_MODULES, APPLICATION_LOGGER, EVENT_BUS_OPTIONS)], []).c;
57
+ }
58
+ descriptors = [];
59
+ discoveryPromise;
60
+ discovered = false;
61
+ lifecycleState = 'created';
62
+ handlerInstances = new Map();
63
+ subscribedChannels = new Set();
64
+ transportCloseFailures = 0;
65
+ transportPublishFailures = 0;
66
+ transportSubscribeFailures = 0;
67
+ transport;
68
+ constructor(runtimeContainer, compiledModules, logger, moduleOptions) {
69
+ this.runtimeContainer = runtimeContainer;
70
+ this.compiledModules = compiledModules;
71
+ this.logger = logger;
72
+ this.moduleOptions = moduleOptions;
73
+ this.transport = moduleOptions.transport;
74
+ }
75
+ async onApplicationBootstrap() {
76
+ this.lifecycleState = 'discovering';
77
+ try {
78
+ await this.ensureDiscovered();
79
+ await this.subscribeTransportChannels();
80
+ this.lifecycleState = 'ready';
81
+ } catch (error) {
82
+ this.lifecycleState = 'failed';
83
+ throw error;
84
+ }
85
+ }
86
+ async onApplicationShutdown() {
87
+ this.lifecycleState = 'stopping';
88
+ if (this.transport) {
89
+ try {
90
+ await this.transport.close();
91
+ } catch (error) {
92
+ this.transportCloseFailures += 1;
93
+ this.lifecycleState = 'failed';
94
+ this.logger.error('EventBusTransport failed to close.', error, 'EventBusLifecycleService');
95
+ }
96
+ }
97
+ if (this.lifecycleState !== 'failed') {
98
+ this.lifecycleState = 'stopped';
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Creates a platform status snapshot for health checks and diagnostics.
104
+ *
105
+ * @returns A structured snapshot describing discovery state, transport wiring, and failure counters.
106
+ */
107
+ createPlatformStatusSnapshot() {
108
+ return createEventBusPlatformStatusSnapshot({
109
+ handlersDiscovered: this.descriptors.length,
110
+ lifecycleState: this.lifecycleState,
111
+ subscribedChannels: this.subscribedChannels.size,
112
+ transportCloseFailures: this.transportCloseFailures,
113
+ transportConfigured: this.transport !== undefined,
114
+ transportPublishFailures: this.transportPublishFailures,
115
+ transportSubscribeFailures: this.transportSubscribeFailures,
116
+ waitForHandlersDefault: this.moduleOptions.publish?.waitForHandlers ?? true
117
+ });
118
+ }
119
+
120
+ /**
121
+ * Publishes one event to matching local handlers and, when configured, to the external transport.
122
+ *
123
+ * @param event Event instance to publish.
124
+ * @param options Optional timeout, abort signal, and wait-for-handler controls.
125
+ * @returns A promise that resolves once the configured local/transport publication completes.
126
+ */
127
+ async publish(event, options) {
128
+ await this.ensureDiscovered();
129
+ const matchingDescriptors = this.matchEventDescriptors(event);
130
+ const publishOptions = this.resolvePublishOptions(options);
131
+ const transportPayload = createIsolatedEvent(event.constructor, event);
132
+ const transportPublish = this.publishToTransport(transportPayload, matchingDescriptors);
133
+ if (!publishOptions.waitForHandlers) {
134
+ const backgroundTasks = this.createBackgroundInvocationTasks(matchingDescriptors, event, publishOptions.signal);
135
+ this.runInvocationTasksInBackground([...backgroundTasks, transportPublish]);
136
+ return;
137
+ }
138
+ if (matchingDescriptors.length === 0) {
139
+ await transportPublish;
140
+ return;
141
+ }
142
+ const invocationTasks = this.createInvocationTasks(matchingDescriptors, event, publishOptions);
143
+ await Promise.allSettled([...invocationTasks, transportPublish]);
144
+ }
145
+ matchEventDescriptors(event) {
146
+ return this.descriptors.filter(descriptor => event instanceof descriptor.eventType);
147
+ }
148
+ createInvocationTasks(descriptors, event, publishOptions) {
149
+ return descriptors.map(descriptor => {
150
+ const isolatedEvent = createIsolatedEvent(descriptor.eventType, event);
151
+ return this.invokeHandlerWithBounds(descriptor, isolatedEvent, publishOptions);
152
+ });
153
+ }
154
+ createBackgroundInvocationTasks(descriptors, event, signal) {
155
+ return descriptors.map(descriptor => {
156
+ const isolatedEvent = createIsolatedEvent(descriptor.eventType, event);
157
+ return this.invokeHandlerInBackground(descriptor, isolatedEvent, signal);
158
+ });
159
+ }
160
+ runInvocationTasksInBackground(invocationTasks) {
161
+ for (const task of invocationTasks) {
162
+ void task;
163
+ }
164
+ }
165
+ async invokeHandlerInBackground(descriptor, event, signal) {
166
+ if (signal?.aborted) {
167
+ this.logPublishCancelledBeforeDispatch(descriptor);
168
+ return;
169
+ }
170
+ await this.invokeHandler(descriptor, event);
171
+ }
172
+ async ensureDiscovered() {
173
+ if (this.discovered) {
174
+ return;
175
+ }
176
+ if (this.discoveryPromise) {
177
+ await this.discoveryPromise;
178
+ return;
179
+ }
180
+ if (this.compiledModules.length === 0) {
181
+ this.logger.warn('EventBus.publish() was called before onApplicationBootstrap completed. Handlers may not yet be registered.', 'EventBusLifecycleService');
182
+ }
183
+ this.discoveryPromise = this.discoverHandlers();
184
+ await this.discoveryPromise;
185
+ }
186
+ resolvePublishOptions(options) {
187
+ const defaults = this.moduleOptions.publish;
188
+ const timeoutMs = this.normalizeTimeoutMs(options?.timeoutMs ?? defaults?.timeoutMs);
189
+ const waitForHandlers = options?.waitForHandlers ?? defaults?.waitForHandlers ?? true;
190
+ return {
191
+ signal: options?.signal,
192
+ timeoutMs,
193
+ waitForHandlers
194
+ };
195
+ }
196
+ normalizeTimeoutMs(timeoutMs) {
197
+ if (typeof timeoutMs !== 'number' || !Number.isFinite(timeoutMs) || timeoutMs <= 0) {
198
+ return undefined;
199
+ }
200
+ return Math.floor(timeoutMs);
201
+ }
202
+ async discoverHandlers() {
203
+ try {
204
+ this.descriptors = this.discoverHandlerDescriptors();
205
+ this.handlerInstances.clear();
206
+ await this.preloadHandlerInstances(this.descriptors);
207
+ this.discovered = true;
208
+ } finally {
209
+ this.discoveryPromise = undefined;
210
+ }
211
+ }
212
+ channelFromEventType(eventType) {
213
+ if (typeof eventType.eventKey === 'string') {
214
+ const eventKey = eventType.eventKey.trim();
215
+ if (eventKey.length > 0) {
216
+ return eventKey;
217
+ }
218
+ }
219
+ return eventType.name;
220
+ }
221
+ channelsForTransportPublish(event, descriptors) {
222
+ const channels = new Set();
223
+ for (const descriptor of descriptors) {
224
+ channels.add(this.channelFromEventType(descriptor.eventType));
225
+ }
226
+ if (channels.size === 0) {
227
+ channels.add(this.channelFromEventType(event.constructor));
228
+ }
229
+ return Array.from(channels);
230
+ }
231
+ async publishToTransport(event, descriptors) {
232
+ if (!this.transport) {
233
+ return;
234
+ }
235
+ const channels = this.channelsForTransportPublish(event, descriptors);
236
+ const publishTasks = channels.map(async channel => {
237
+ const payload = createIsolatedEvent(event.constructor, event);
238
+ try {
239
+ await this.transport.publish(channel, payload);
240
+ } catch (error) {
241
+ this.transportPublishFailures += 1;
242
+ this.logger.error(`EventBusTransport failed to publish to channel "${channel}".`, error, 'EventBusLifecycleService');
243
+ }
244
+ });
245
+ await Promise.allSettled(publishTasks);
246
+ }
247
+ async subscribeTransportChannels() {
248
+ if (!this.transport) {
249
+ return;
250
+ }
251
+ const descriptorsByChannel = new Map();
252
+ for (const descriptor of this.descriptors) {
253
+ const channel = this.channelFromEventType(descriptor.eventType);
254
+ const channelDescriptors = descriptorsByChannel.get(channel) ?? [];
255
+ channelDescriptors.push(descriptor);
256
+ descriptorsByChannel.set(channel, channelDescriptors);
257
+ }
258
+ for (const [channel, channelDescriptors] of descriptorsByChannel) {
259
+ await this.subscribeTransportChannel(channel, channelDescriptors);
260
+ }
261
+ }
262
+ async subscribeTransportChannel(channel, channelDescriptors) {
263
+ try {
264
+ await this.transport.subscribe(channel, async payload => {
265
+ if (channelDescriptors.length === 0) {
266
+ return;
267
+ }
268
+ const invocationTasks = channelDescriptors.map(descriptor => this.invokeHandlerWithBounds(descriptor, createIsolatedEvent(descriptor.eventType, payload), {
269
+ signal: undefined,
270
+ timeoutMs: this.normalizeTimeoutMs(this.moduleOptions.publish?.timeoutMs),
271
+ waitForHandlers: this.moduleOptions.publish?.waitForHandlers ?? true
272
+ }));
273
+ await Promise.allSettled(invocationTasks);
274
+ });
275
+ this.subscribedChannels.add(channel);
276
+ } catch (error) {
277
+ this.transportSubscribeFailures += 1;
278
+ this.logger.error(`EventBusTransport failed to subscribe to channel "${channel}".`, error, 'EventBusLifecycleService');
279
+ throw error;
280
+ }
281
+ }
282
+ async preloadHandlerInstances(descriptors) {
283
+ for (const descriptor of descriptors) {
284
+ if (this.handlerInstances.has(descriptor.token)) {
285
+ continue;
286
+ }
287
+ await this.resolveHandlerInstance(descriptor);
288
+ }
289
+ }
290
+ async invokeHandlerWithBounds(descriptor, event, publishOptions) {
291
+ if (publishOptions.signal?.aborted) {
292
+ this.logPublishCancelledBeforeDispatch(descriptor);
293
+ return;
294
+ }
295
+ const invocation = this.invokeHandler(descriptor, event);
296
+ try {
297
+ await this.awaitInvocationBounds(invocation, publishOptions);
298
+ } catch (error) {
299
+ this.logBoundedInvocationError(descriptor, error);
300
+ }
301
+ }
302
+ logPublishCancelledBeforeDispatch(descriptor) {
303
+ this.logger.warn(`Event publish was cancelled before dispatching handler ${descriptor.targetName}.${descriptor.methodName}.`, 'EventBusLifecycleService');
304
+ }
305
+ logBoundedInvocationError(descriptor, error) {
306
+ if (error instanceof EventPublishTimeoutError) {
307
+ this.logger.warn(`Event handler ${descriptor.targetName}.${descriptor.methodName} exceeded publish timeout of ${String(error.timeoutMs)}ms.`, 'EventBusLifecycleService');
308
+ return;
309
+ }
310
+ if (error instanceof EventPublishAbortError) {
311
+ this.logger.warn(`Event publish was cancelled while waiting for handler ${descriptor.targetName}.${descriptor.methodName}.`, 'EventBusLifecycleService');
312
+ return;
313
+ }
314
+ this.logger.error(`Event handler ${descriptor.targetName}.${descriptor.methodName} failed while applying publish bounds.`, error, 'EventBusLifecycleService');
315
+ }
316
+ async awaitInvocationBounds(invocation, publishOptions) {
317
+ const timeoutMs = publishOptions.timeoutMs;
318
+ const signal = publishOptions.signal;
319
+ if (timeoutMs === undefined && !signal) {
320
+ await invocation;
321
+ return;
322
+ }
323
+ const bounds = this.createInvocationBounds(timeoutMs, signal);
324
+ try {
325
+ await Promise.race([invocation, ...bounds.map(bound => bound.promise)]);
326
+ } finally {
327
+ for (const bound of bounds) {
328
+ bound.cleanup();
329
+ }
330
+ }
331
+ }
332
+ createInvocationBounds(timeoutMs, signal) {
333
+ const bounds = [];
334
+ if (timeoutMs !== undefined) {
335
+ bounds.push(this.createTimeoutBound(timeoutMs));
336
+ }
337
+ if (signal) {
338
+ if (signal.aborted) {
339
+ throw new EventPublishAbortError();
340
+ }
341
+ bounds.push(this.createAbortBound(signal));
342
+ }
343
+ return bounds;
344
+ }
345
+ createTimeoutBound(timeoutMs) {
346
+ let timeoutId;
347
+ return {
348
+ cleanup() {
349
+ if (timeoutId) {
350
+ clearTimeout(timeoutId);
351
+ }
352
+ },
353
+ promise: new Promise((_resolve, reject) => {
354
+ timeoutId = setTimeout(() => {
355
+ reject(new EventPublishTimeoutError(timeoutMs));
356
+ }, timeoutMs);
357
+ })
358
+ };
359
+ }
360
+ createAbortBound(signal) {
361
+ let abortListener;
362
+ return {
363
+ cleanup() {
364
+ if (abortListener) {
365
+ signal.removeEventListener('abort', abortListener);
366
+ }
367
+ },
368
+ promise: new Promise((_resolve, reject) => {
369
+ abortListener = () => {
370
+ reject(new EventPublishAbortError());
371
+ };
372
+ signal.addEventListener('abort', abortListener, {
373
+ once: true
374
+ });
375
+ })
376
+ };
377
+ }
378
+ discoverHandlerDescriptors() {
379
+ const seen = new WeakMap();
380
+ const descriptors = [];
381
+ for (const candidate of this.discoveryCandidates()) {
382
+ const entries = getEventHandlerMetadataEntries(candidate.targetType.prototype);
383
+ if (this.shouldSkipNonSingletonCandidate(candidate, entries.length)) {
384
+ continue;
385
+ }
386
+ for (const entry of entries) {
387
+ const eventType = entry.metadata.eventType;
388
+ if (this.isDuplicateHandlerRegistration(seen, candidate.targetType, entry.propertyKey, eventType)) {
389
+ continue;
390
+ }
391
+ descriptors.push(this.createHandlerDescriptor(candidate, entry.propertyKey, eventType));
392
+ }
393
+ }
394
+ return descriptors;
395
+ }
396
+ shouldSkipNonSingletonCandidate(candidate, entryCount) {
397
+ if (candidate.scope === 'singleton') {
398
+ return false;
399
+ }
400
+ if (entryCount > 0) {
401
+ this.logger.warn(`${candidate.targetType.name} in module ${candidate.moduleName} declares @OnEvent() methods but is registered with ${candidate.scope} scope. Event handlers are registered only for singleton providers.`, 'EventBusLifecycleService');
402
+ }
403
+ return true;
404
+ }
405
+ isDuplicateHandlerRegistration(seen, targetType, methodKey, eventType) {
406
+ let methodsByKey = seen.get(targetType);
407
+ if (!methodsByKey) {
408
+ methodsByKey = new Map();
409
+ seen.set(targetType, methodsByKey);
410
+ }
411
+ let seenEventTypes = methodsByKey.get(methodKey);
412
+ if (!seenEventTypes) {
413
+ seenEventTypes = new Set();
414
+ methodsByKey.set(methodKey, seenEventTypes);
415
+ }
416
+ if (seenEventTypes.has(eventType)) {
417
+ return true;
418
+ }
419
+ seenEventTypes.add(eventType);
420
+ return false;
421
+ }
422
+ createHandlerDescriptor(candidate, methodKey, eventType) {
423
+ return {
424
+ eventType,
425
+ methodKey,
426
+ methodName: methodKeyToName(methodKey),
427
+ moduleName: candidate.moduleName,
428
+ targetName: candidate.targetType.name,
429
+ token: candidate.token
430
+ };
431
+ }
432
+ discoveryCandidates() {
433
+ const candidates = [];
434
+ for (const compiledModule of this.compiledModules) {
435
+ for (const provider of compiledModule.definition.providers ?? []) {
436
+ if (typeof provider === 'function') {
437
+ candidates.push({
438
+ moduleName: compiledModule.type.name,
439
+ scope: scopeFromProvider(provider),
440
+ targetType: provider,
441
+ token: provider
442
+ });
443
+ continue;
444
+ }
445
+ if (isClassProvider(provider)) {
446
+ candidates.push({
447
+ moduleName: compiledModule.type.name,
448
+ scope: scopeFromProvider(provider),
449
+ targetType: provider.useClass,
450
+ token: provider.provide
451
+ });
452
+ }
453
+ }
454
+ for (const controller of compiledModule.definition.controllers ?? []) {
455
+ candidates.push({
456
+ moduleName: compiledModule.type.name,
457
+ scope: scopeFromProvider(controller),
458
+ targetType: controller,
459
+ token: controller
460
+ });
461
+ }
462
+ }
463
+ return candidates;
464
+ }
465
+ async invokeHandler(descriptor, event) {
466
+ const instance = await this.resolveHandlerInstance(descriptor);
467
+ if (instance === undefined) {
468
+ return;
469
+ }
470
+ const value = instance[descriptor.methodKey];
471
+ if (typeof value !== 'function') {
472
+ this.logger.warn(`Event handler ${descriptor.targetName}.${descriptor.methodName} is not callable and was skipped.`, 'EventBusLifecycleService');
473
+ return;
474
+ }
475
+ try {
476
+ await Promise.resolve(value.call(instance, event));
477
+ } catch (error) {
478
+ this.logger.error(`Event handler ${descriptor.targetName}.${descriptor.methodName} failed.`, error, 'EventBusLifecycleService');
479
+ }
480
+ }
481
+ async resolveHandlerInstance(descriptor) {
482
+ const cached = this.handlerInstances.get(descriptor.token);
483
+ if (cached) {
484
+ return await cached;
485
+ }
486
+ const resolving = this.runtimeContainer.resolve(descriptor.token);
487
+ this.handlerInstances.set(descriptor.token, resolving);
488
+ try {
489
+ return await resolving;
490
+ } catch (error) {
491
+ this.handlerInstances.delete(descriptor.token);
492
+ this.logger.error(`Failed to resolve event handler target ${descriptor.targetName} from module ${descriptor.moduleName}.`, error, 'EventBusLifecycleService');
493
+ return undefined;
494
+ }
495
+ }
496
+ static {
497
+ _initClass();
498
+ }
499
+ }
500
+ export { _EventBusLifecycleSer as EventBusLifecycleService };
@@ -0,0 +1,20 @@
1
+ import type { PlatformHealthReport, PlatformReadinessReport, PlatformSnapshot } from '@fluojs/runtime';
2
+ export type EventBusLifecycleState = 'created' | 'discovering' | 'ready' | 'stopping' | 'stopped' | 'failed';
3
+ export interface EventBusStatusAdapterInput {
4
+ handlersDiscovered: number;
5
+ lifecycleState: EventBusLifecycleState;
6
+ subscribedChannels: number;
7
+ transportCloseFailures: number;
8
+ transportConfigured: boolean;
9
+ transportPublishFailures: number;
10
+ transportSubscribeFailures: number;
11
+ waitForHandlersDefault: boolean;
12
+ }
13
+ export interface EventBusPlatformStatusSnapshot {
14
+ readiness: PlatformReadinessReport;
15
+ health: PlatformHealthReport;
16
+ ownership: PlatformSnapshot['ownership'];
17
+ details: Record<string, unknown>;
18
+ }
19
+ export declare function createEventBusPlatformStatusSnapshot(input: EventBusStatusAdapterInput): EventBusPlatformStatusSnapshot;
20
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEvG,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,aAAa,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE7G,MAAM,WAAW,0BAA0B;IACzC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,sBAAsB,CAAC;IACvC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,wBAAwB,EAAE,MAAM,CAAC;IACjC,0BAA0B,EAAE,MAAM,CAAC;IACnC,sBAAsB,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,uBAAuB,CAAC;IACnC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACzC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAwFD,wBAAgB,oCAAoC,CAAC,KAAK,EAAE,0BAA0B,GAAG,8BAA8B,CAqBtH"}
package/dist/status.js ADDED
@@ -0,0 +1,96 @@
1
+ function createReadiness(input) {
2
+ if (input.lifecycleState === 'ready' && input.transportSubscribeFailures === 0) {
3
+ return {
4
+ critical: true,
5
+ status: 'ready'
6
+ };
7
+ }
8
+ if (input.lifecycleState === 'ready' && input.transportSubscribeFailures > 0) {
9
+ return {
10
+ critical: true,
11
+ reason: 'Event bus transport subscription is partially degraded.',
12
+ status: 'degraded'
13
+ };
14
+ }
15
+ if (input.lifecycleState === 'discovering') {
16
+ return {
17
+ critical: true,
18
+ reason: 'Event handlers are still being discovered.',
19
+ status: 'degraded'
20
+ };
21
+ }
22
+ if (input.lifecycleState === 'stopping') {
23
+ return {
24
+ critical: true,
25
+ reason: 'Event bus is shutting down.',
26
+ status: 'not-ready'
27
+ };
28
+ }
29
+ if (input.lifecycleState === 'stopped') {
30
+ return {
31
+ critical: true,
32
+ reason: 'Event bus is stopped.',
33
+ status: 'not-ready'
34
+ };
35
+ }
36
+ if (input.lifecycleState === 'failed') {
37
+ return {
38
+ critical: true,
39
+ reason: 'Event bus failed to complete startup or shutdown.',
40
+ status: 'not-ready'
41
+ };
42
+ }
43
+ return {
44
+ critical: true,
45
+ reason: 'Event bus has not completed discovery yet.',
46
+ status: 'not-ready'
47
+ };
48
+ }
49
+ function createHealth(input) {
50
+ if (input.lifecycleState === 'failed' || input.lifecycleState === 'stopped') {
51
+ return {
52
+ reason: 'Event bus transport lifecycle is not active.',
53
+ status: 'unhealthy'
54
+ };
55
+ }
56
+ if (input.lifecycleState === 'discovering' || input.lifecycleState === 'stopping') {
57
+ return {
58
+ reason: 'Event bus is transitioning lifecycle state.',
59
+ status: 'degraded'
60
+ };
61
+ }
62
+ if (input.transportPublishFailures > 0 || input.transportSubscribeFailures > 0 || input.transportCloseFailures > 0) {
63
+ return {
64
+ reason: 'Event bus transport reported recoverable runtime failures.',
65
+ status: 'degraded'
66
+ };
67
+ }
68
+ return {
69
+ status: 'healthy'
70
+ };
71
+ }
72
+ function resolveOperationMode(input) {
73
+ return input.transportConfigured ? 'transport-backed' : 'local-only';
74
+ }
75
+ export function createEventBusPlatformStatusSnapshot(input) {
76
+ return {
77
+ details: {
78
+ dependencies: input.transportConfigured ? ['transport.external'] : [],
79
+ handlersDiscovered: input.handlersDiscovered,
80
+ lifecycleState: input.lifecycleState,
81
+ operationMode: resolveOperationMode(input),
82
+ subscribedChannels: input.subscribedChannels,
83
+ transportCloseFailures: input.transportCloseFailures,
84
+ transportConfigured: input.transportConfigured,
85
+ transportPublishFailures: input.transportPublishFailures,
86
+ transportSubscribeFailures: input.transportSubscribeFailures,
87
+ waitForHandlersDefault: input.waitForHandlersDefault
88
+ },
89
+ health: createHealth(input),
90
+ ownership: {
91
+ externallyManaged: input.transportConfigured,
92
+ ownsResources: false
93
+ },
94
+ readiness: createReadiness(input)
95
+ };
96
+ }
@@ -0,0 +1,7 @@
1
+ import type { Token } from '@fluojs/core';
2
+ import type { EventBus, EventBusModuleOptions } from './types.js';
3
+ /** Compatibility injection token for the event bus facade. */
4
+ export declare const EVENT_BUS: Token<EventBus>;
5
+ /** Injection token for event-bus module defaults and optional transport wiring. */
6
+ export declare const EVENT_BUS_OPTIONS: Token<EventBusModuleOptions>;
7
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,KAAK,EAAE,QAAQ,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAElE,8DAA8D;AAC9D,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,QAAQ,CAAgC,CAAC;AACvE,mFAAmF;AACnF,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,qBAAqB,CAAwC,CAAC"}
package/dist/tokens.js ADDED
@@ -0,0 +1,4 @@
1
+ /** Compatibility injection token for the event bus facade. */
2
+ export const EVENT_BUS = Symbol.for('fluo.event-bus');
3
+ /** Injection token for event-bus module defaults and optional transport wiring. */
4
+ export const EVENT_BUS_OPTIONS = Symbol.for('fluo.event-bus.options');