@chromahq/core 1.0.56 → 1.0.57

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.
@@ -1,6 +1,126 @@
1
1
  'use strict';
2
2
 
3
3
  var container$1 = require('@inversifyjs/container');
4
+ var core = require('@inversifyjs/core');
5
+
6
+ const SUBSCRIBE_METADATA_KEY = "chroma:subscribe";
7
+ function Subscribe(eventName) {
8
+ return function(target, propertyKey, _descriptor) {
9
+ const key = "chroma:subscribe";
10
+ const constructor = target.constructor;
11
+ const existing = Reflect.getMetadata(key, constructor) || [];
12
+ existing.push({ eventName, methodName: propertyKey });
13
+ Reflect.defineMetadata(key, existing, constructor);
14
+ };
15
+ }
16
+ function getSubscribeMetadata(constructor) {
17
+ return Reflect.getMetadata("chroma:subscribe", constructor) || [];
18
+ }
19
+
20
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
21
+ var __decorateClass = (decorators, target, key, kind) => {
22
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
23
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
24
+ if (decorator = decorators[i])
25
+ result = (decorator(result)) || result;
26
+ return result;
27
+ };
28
+ const EventBusToken = /* @__PURE__ */ Symbol.for("EventBus");
29
+ exports.AppEventBus = class AppEventBus {
30
+ constructor() {
31
+ /** Registry of all active subscriptions */
32
+ this.subscriptions = [];
33
+ /** Auto-incrementing ID counter */
34
+ this.nextId = 0;
35
+ }
36
+ // ─────────────────────────────────────────────────────────────────────────
37
+ // Subscribe
38
+ // ─────────────────────────────────────────────────────────────────────────
39
+ /**
40
+ * Subscribe to a named event.
41
+ *
42
+ * @param eventName - the event to listen for
43
+ * @param handler - callback invoked with the event payload
44
+ * @param handlerName - human-readable name for logging
45
+ * @returns an unsubscribe function
46
+ */
47
+ on(eventName, handler, handlerName) {
48
+ const id = ++this.nextId;
49
+ this.subscriptions.push({
50
+ id,
51
+ eventName,
52
+ handler,
53
+ handlerName
54
+ });
55
+ return () => {
56
+ this.subscriptions = this.subscriptions.filter((s) => s.id !== id);
57
+ };
58
+ }
59
+ // ─────────────────────────────────────────────────────────────────────────
60
+ // Emit
61
+ // ─────────────────────────────────────────────────────────────────────────
62
+ /**
63
+ * Emit a named event to all matching subscribers.
64
+ *
65
+ * Handlers execute in parallel. Individual handler failures are caught
66
+ * and logged — they do not propagate or block other handlers.
67
+ *
68
+ * @param eventName - the event to emit
69
+ * @param payload - optional data passed to every handler
70
+ */
71
+ async emit(eventName, payload) {
72
+ const matching = this.subscriptions.filter((s) => s.eventName === eventName);
73
+ if (matching.length === 0) {
74
+ return;
75
+ }
76
+ const results = await Promise.allSettled(
77
+ matching.map(async (sub) => {
78
+ try {
79
+ await sub.handler(payload);
80
+ } catch (error) {
81
+ console.error(
82
+ `[AppEventBus] Handler "${sub.handlerName}" failed for event "${eventName}":`,
83
+ error
84
+ );
85
+ throw error;
86
+ }
87
+ })
88
+ );
89
+ const failed = results.filter((r) => r.status === "rejected").length;
90
+ if (failed > 0) {
91
+ console.warn(
92
+ `[AppEventBus] ${failed}/${matching.length} handler(s) failed for event "${eventName}"`
93
+ );
94
+ }
95
+ }
96
+ // ─────────────────────────────────────────────────────────────────────────
97
+ // Utilities
98
+ // ─────────────────────────────────────────────────────────────────────────
99
+ /**
100
+ * Get the total number of active subscriptions.
101
+ * Useful for debugging and testing.
102
+ */
103
+ getSubscriptionCount() {
104
+ return this.subscriptions.length;
105
+ }
106
+ /**
107
+ * Get the number of subscriptions for a specific event.
108
+ *
109
+ * @param eventName - the event to count subscriptions for
110
+ */
111
+ getSubscriptionCountForEvent(eventName) {
112
+ return this.subscriptions.filter((s) => s.eventName === eventName).length;
113
+ }
114
+ /**
115
+ * Remove all subscriptions. Primarily for testing.
116
+ */
117
+ clearAllSubscriptions() {
118
+ this.subscriptions = [];
119
+ }
120
+ };
121
+ exports.AppEventBus = __decorateClass([
122
+ core.injectable()
123
+ ], exports.AppEventBus);
4
124
 
5
125
  const NONCE_STORE_STORAGE_KEY = "__CHROMA_NONCE_STORE__";
6
126
  class NonceService {
@@ -2202,6 +2322,7 @@ class ApplicationBootstrap {
2202
2322
  constructor() {
2203
2323
  this.serviceDependencies = /* @__PURE__ */ new Map();
2204
2324
  this.serviceRegistry = /* @__PURE__ */ new Map();
2325
+ this.jobRegistry = /* @__PURE__ */ new Map();
2205
2326
  this.logger = new BootstrapLogger();
2206
2327
  this.storeDefinitions = [];
2207
2328
  }
@@ -2233,6 +2354,7 @@ class ApplicationBootstrap {
2233
2354
  try {
2234
2355
  this.logger = new BootstrapLogger(enableLogs);
2235
2356
  this.logger.info("Starting Chroma application bootstrap...");
2357
+ this.initializeEventBus();
2236
2358
  await this.discoverAndInitializeStores();
2237
2359
  await this.discoverServices();
2238
2360
  const store = this.storeDefinitions[0].store;
@@ -2252,6 +2374,7 @@ class ApplicationBootstrap {
2252
2374
  if (!disableBootMethods) {
2253
2375
  await this.bootMessages();
2254
2376
  await this.bootServices();
2377
+ this.wireEventSubscriptions();
2255
2378
  }
2256
2379
  this.logger.success("Chroma application initialization complete");
2257
2380
  } catch (error) {
@@ -2282,6 +2405,62 @@ class ApplicationBootstrap {
2282
2405
  );
2283
2406
  await Promise.all(bootPromises);
2284
2407
  }
2408
+ /**
2409
+ * Create and bind the global AppEventBus singleton to the DI container.
2410
+ * Called early in bootstrap so any service can inject it.
2411
+ */
2412
+ initializeEventBus() {
2413
+ if (!container.isBound(exports.AppEventBus)) {
2414
+ container.bind(exports.AppEventBus).toSelf().inSingletonScope();
2415
+ }
2416
+ if (!container.isBound(EventBusToken)) {
2417
+ container.bind(EventBusToken).toDynamicValue(() => container.get(exports.AppEventBus)).inSingletonScope();
2418
+ }
2419
+ this.logger.debug("AppEventBus bound to DI container");
2420
+ }
2421
+ /**
2422
+ * Scan all registered services and jobs for @Subscribe metadata and
2423
+ * wire the decorated methods to the AppEventBus.
2424
+ *
2425
+ * This runs after bootServices so every singleton is already instantiated.
2426
+ */
2427
+ wireEventSubscriptions() {
2428
+ this.logger.info("Wiring @Subscribe event subscriptions...");
2429
+ const bus = container.get(exports.AppEventBus);
2430
+ let wiredCount = 0;
2431
+ const scan = (name, Constructor) => {
2432
+ const metadata = getSubscribeMetadata(Constructor);
2433
+ if (metadata.length === 0) {
2434
+ return;
2435
+ }
2436
+ let instance;
2437
+ try {
2438
+ instance = container.get(Constructor);
2439
+ } catch {
2440
+ this.logger.warn(`Could not resolve instance for ${name}, skipping @Subscribe wiring`);
2441
+ return;
2442
+ }
2443
+ for (const { eventName, methodName } of metadata) {
2444
+ const method = instance[methodName];
2445
+ if (typeof method !== "function") {
2446
+ this.logger.warn(
2447
+ `@Subscribe('${eventName}') on ${name}.${methodName} is not a function, skipping`
2448
+ );
2449
+ continue;
2450
+ }
2451
+ bus.on(eventName, method.bind(instance), `${name}.${methodName}`);
2452
+ wiredCount++;
2453
+ this.logger.debug(`Wired @Subscribe('${eventName}') \u2192 ${name}.${methodName}`);
2454
+ }
2455
+ };
2456
+ for (const [name, Constructor] of this.serviceRegistry) {
2457
+ scan(name, Constructor);
2458
+ }
2459
+ for (const [name, Constructor] of this.jobRegistry) {
2460
+ scan(name, Constructor);
2461
+ }
2462
+ this.logger.success(`Wired ${wiredCount} @Subscribe handler(s) to AppEventBus`);
2463
+ }
2285
2464
  /**
2286
2465
  * Discover all services in the application directory
2287
2466
  */
@@ -2624,6 +2803,7 @@ class ApplicationBootstrap {
2624
2803
  container.bind(id).to(JobClass).inSingletonScope();
2625
2804
  const options = Reflect.getMetadata("job:options", JobClass) || {};
2626
2805
  jobEntries.push({ JobClass, jobName, id, options });
2806
+ this.jobRegistry.set(jobName, JobClass);
2627
2807
  this.logger.debug(`Bound job: ${jobName}`);
2628
2808
  } catch (error) {
2629
2809
  this.logger.error(`Failed to bind job ${JobClass.name}:`, error);
@@ -2744,11 +2924,14 @@ class BootstrapBuilder {
2744
2924
  }
2745
2925
  }
2746
2926
 
2927
+ exports.EventBusToken = EventBusToken;
2747
2928
  exports.JobRegistry = JobRegistry;
2748
2929
  exports.JobState = JobState;
2749
2930
  exports.NonceService = NonceService;
2750
2931
  exports.PopupVisibilityService = PopupVisibilityService;
2932
+ exports.SUBSCRIBE_METADATA_KEY = SUBSCRIBE_METADATA_KEY;
2751
2933
  exports.Scheduler = Scheduler;
2934
+ exports.Subscribe = Subscribe;
2752
2935
  exports.arePortsClaimed = arePortsClaimed;
2753
2936
  exports.bootstrap = bootstrap;
2754
2937
  exports.claimEarlyPorts = claimEarlyPorts;
@@ -2756,6 +2939,7 @@ exports.container = container;
2756
2939
  exports.create = create;
2757
2940
  exports.getNonceService = getNonceService;
2758
2941
  exports.getPopupVisibilityService = getPopupVisibilityService;
2942
+ exports.getSubscribeMetadata = getSubscribeMetadata;
2759
2943
  exports.isEarlyListenerSetup = isEarlyListenerSetup;
2760
2944
  exports.setupEarlyListener = setupEarlyListener;
2761
- //# sourceMappingURL=boot-CUFlC4bu.js.map
2945
+ //# sourceMappingURL=boot-DIMyFZvd.js.map