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