@chromahq/core 1.0.55 → 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
  }
@@ -2232,7 +2353,8 @@ class ApplicationBootstrap {
2232
2353
  }) {
2233
2354
  try {
2234
2355
  this.logger = new BootstrapLogger(enableLogs);
2235
- this.logger.info("\u{1F680} Starting Chroma application bootstrap...");
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,10 +2374,11 @@ class ApplicationBootstrap {
2252
2374
  if (!disableBootMethods) {
2253
2375
  await this.bootMessages();
2254
2376
  await this.bootServices();
2377
+ this.wireEventSubscriptions();
2255
2378
  }
2256
- this.logger.success("\u{1F389} Chroma application initialization complete!");
2379
+ this.logger.success("Chroma application initialization complete");
2257
2380
  } catch (error) {
2258
- this.logger.error("\u{1F4A5} Application bootstrap failed:", error);
2381
+ this.logger.error("Application bootstrap failed:", error);
2259
2382
  throw error;
2260
2383
  }
2261
2384
  }
@@ -2263,7 +2386,7 @@ class ApplicationBootstrap {
2263
2386
  * Boot all registered services by calling their onBoot method if present
2264
2387
  */
2265
2388
  async bootServices() {
2266
- this.logger.info("\u{1F680} Booting services...");
2389
+ this.logger.info("Booting services...");
2267
2390
  const bootPromises = Array.from(this.serviceRegistry.entries()).map(
2268
2391
  async ([serviceName, ServiceClass]) => {
2269
2392
  try {
@@ -2282,11 +2405,67 @@ 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
  */
2288
2467
  async discoverServices() {
2289
- this.logger.info("\u{1F50D} Discovering services...");
2468
+ this.logger.info("Discovering services...");
2290
2469
  const serviceModules = undefined(
2291
2470
  "/src/app/services/**/*.service.{ts,js}",
2292
2471
  { eager: true }
@@ -2301,7 +2480,7 @@ class ApplicationBootstrap {
2301
2480
  }
2302
2481
  const circularDepsResult = this.detectCircularDependencies(serviceClasses);
2303
2482
  if (circularDepsResult.hasCircularDependencies) {
2304
- this.logger.error("\u{1F4A5} Circular dependencies detected!");
2483
+ this.logger.error("Circular dependencies detected!");
2305
2484
  circularDepsResult.cycles.forEach((cycle, index) => {
2306
2485
  this.logger.error(`Cycle ${index + 1}: ${cycle.cycle.join(" \u2192 ")} \u2192 ${cycle.cycle[0]}`);
2307
2486
  });
@@ -2309,10 +2488,10 @@ class ApplicationBootstrap {
2309
2488
  }
2310
2489
  for (const ServiceClass of serviceClasses) {
2311
2490
  container.bind(ServiceClass).toSelf().inSingletonScope();
2312
- this.logger.debug(`\u{1F4E6} Discovered service: ${ServiceClass.name}`);
2491
+ this.logger.debug(`Discovered service: ${ServiceClass.name}`);
2313
2492
  }
2314
2493
  this.logger.success(
2315
- `\u2705 Registered ${serviceClasses.length} services without circular dependencies`
2494
+ `Registered ${serviceClasses.length} services without circular dependencies`
2316
2495
  );
2317
2496
  }
2318
2497
  /**
@@ -2444,17 +2623,17 @@ class ApplicationBootstrap {
2444
2623
  analyzeDependencies() {
2445
2624
  const serviceClasses = Array.from(this.serviceRegistry.values());
2446
2625
  const result = this.detectCircularDependencies(serviceClasses);
2447
- this.logger.info("\u{1F4CA} Dependency Analysis Report:");
2626
+ this.logger.info("Dependency Analysis Report:");
2448
2627
  this.logger.info(`Total Services: ${result.dependencyGraph.size}`);
2449
2628
  if (result.hasCircularDependencies) {
2450
- this.logger.error(`\u{1F504} Circular Dependencies Found: ${result.cycles.length}`);
2629
+ this.logger.error(`Circular Dependencies Found: ${result.cycles.length}`);
2451
2630
  result.cycles.forEach((cycle, index) => {
2452
2631
  this.logger.error(` Cycle ${index + 1}: ${cycle.cycle.join(" \u2192 ")} \u2192 ${cycle.cycle[0]}`);
2453
2632
  });
2454
2633
  } else {
2455
- this.logger.success("\u2705 No circular dependencies detected");
2634
+ this.logger.success("No circular dependencies detected");
2456
2635
  }
2457
- this.logger.info("\u{1F333} Service Dependency Tree:");
2636
+ this.logger.info("Service Dependency Tree:");
2458
2637
  for (const [serviceName, node] of result.dependencyGraph) {
2459
2638
  if (node.dependencies.length > 0) {
2460
2639
  this.logger.info(` ${serviceName} depends on:`);
@@ -2472,7 +2651,7 @@ class ApplicationBootstrap {
2472
2651
  async discoverAndInitializeStores() {
2473
2652
  try {
2474
2653
  if (this.storeDefinitions.length === 0) {
2475
- this.logger.debug("\u{1F4ED} No store definitions provided");
2654
+ this.logger.debug("No store definitions provided");
2476
2655
  return;
2477
2656
  }
2478
2657
  this.logger.info(`Initializing ${this.storeDefinitions.length} store(s)...`);
@@ -2483,7 +2662,7 @@ class ApplicationBootstrap {
2483
2662
  const classes = store.classes;
2484
2663
  container.bind(diKey).toConstantValue(storeInstance);
2485
2664
  if (isFirstStore) {
2486
- container.bind(Symbol.for("Store")).toConstantValue(storeInstance);
2665
+ container.bind(/* @__PURE__ */ Symbol.for("Store")).toConstantValue(storeInstance);
2487
2666
  isFirstStore = false;
2488
2667
  }
2489
2668
  await this.registerMessageClass(
@@ -2500,18 +2679,18 @@ class ApplicationBootstrap {
2500
2679
  `store:${store.def.name}:reset`
2501
2680
  );
2502
2681
  }
2503
- this.logger.debug(`\u2705 Initialized store: ${store.def.name}`);
2682
+ this.logger.debug(`Initialized store: ${store.def.name}`);
2504
2683
  }
2505
- this.logger.success(`\u2705 Initialized ${this.storeDefinitions.length} store(s)`);
2684
+ this.logger.success(`Initialized ${this.storeDefinitions.length} store(s)`);
2506
2685
  } catch (error) {
2507
- this.logger.error("\u274C Failed to initialize stores:", error);
2686
+ this.logger.error("Failed to initialize stores:", error);
2508
2687
  }
2509
2688
  }
2510
2689
  /**
2511
2690
  * Register message handlers
2512
2691
  */
2513
2692
  async registerMessages() {
2514
- this.logger.info("\u{1F4E8} Registering messages...");
2693
+ this.logger.info("Registering messages...");
2515
2694
  const messageModules = undefined(
2516
2695
  "/src/app/messages/**/*.message.{ts,js}",
2517
2696
  { eager: true }
@@ -2524,7 +2703,7 @@ class ApplicationBootstrap {
2524
2703
  if (messageClasses.length > 0) {
2525
2704
  const circularDepsResult = this.detectCircularDependencies(messageClasses);
2526
2705
  if (circularDepsResult.hasCircularDependencies) {
2527
- this.logger.error("\u{1F4A5} Circular dependencies detected in messages!");
2706
+ this.logger.error("Circular dependencies detected in messages!");
2528
2707
  circularDepsResult.cycles.forEach((cycle, index) => {
2529
2708
  this.logger.error(
2530
2709
  `Message Cycle ${index + 1}: ${cycle.cycle.join(" \u2192 ")} \u2192 ${cycle.cycle[0]}`
@@ -2539,30 +2718,30 @@ class ApplicationBootstrap {
2539
2718
  try {
2540
2719
  for (const [name, ServiceClass] of this.serviceRegistry) {
2541
2720
  if (!ServiceClass) {
2542
- this.logger.warn(`\u26A0\uFE0F Service not found in registry: ${name}`);
2721
+ this.logger.warn(`Service not found in registry: ${name}`);
2543
2722
  }
2544
2723
  if (!container.isBound(ServiceClass)) {
2545
- this.logger.warn(`\u26A0\uFE0F Service not bound in container: ${name}`);
2724
+ this.logger.warn(`Service not bound in container: ${name}`);
2546
2725
  }
2547
2726
  }
2548
2727
  const messageMetadata = Reflect.getMetadata("name", MessageClass);
2549
2728
  const messageName = messageMetadata || MessageClass.name;
2550
2729
  container.bind(messageName).to(MessageClass).inSingletonScope();
2551
- this.logger.success(`\u2705 Registered message: ${messageName}`);
2730
+ this.logger.success(`Registered message: ${messageName}`);
2552
2731
  } catch (error) {
2553
- this.logger.error(`\u274C Failed to register message ${MessageClass.name}:`, error);
2732
+ this.logger.error(`Failed to register message ${MessageClass.name}:`, error);
2554
2733
  }
2555
2734
  }
2556
2735
  }
2557
2736
  async registerMessageClass(MessageClass, name) {
2558
2737
  container.bind(name).to(MessageClass).inSingletonScope();
2559
- this.logger.success(`\u2705 Registered message: ${name}`);
2738
+ this.logger.success(`Registered message: ${name}`);
2560
2739
  }
2561
2740
  /**
2562
2741
  * Boot all registered messages
2563
2742
  */
2564
2743
  async bootMessages() {
2565
- this.logger.info("\u{1F680} Booting messages...");
2744
+ this.logger.info("Booting messages...");
2566
2745
  const messageModules = undefined(
2567
2746
  "/src/app/messages/**/*.message.{ts,js}",
2568
2747
  { eager: true }
@@ -2577,10 +2756,10 @@ class ApplicationBootstrap {
2577
2756
  const messageName = messageMetadata || MessageClass.name;
2578
2757
  const messageInstance = container.get(messageName);
2579
2758
  await messageInstance.boot();
2580
- this.logger.success(`\u2705 Booted message: ${messageName}`);
2759
+ this.logger.success(`Booted message: ${messageName}`);
2581
2760
  return { messageName, success: true };
2582
2761
  } catch (error) {
2583
- this.logger.error(`\u274C Failed to boot message ${MessageClass.name}:`, error);
2762
+ this.logger.error(`Failed to boot message ${MessageClass.name}:`, error);
2584
2763
  return { messageName: MessageClass.name, success: false, error };
2585
2764
  }
2586
2765
  });
@@ -2590,7 +2769,7 @@ class ApplicationBootstrap {
2590
2769
  * Register jobs for scheduled execution
2591
2770
  */
2592
2771
  async registerJobs() {
2593
- this.logger.info("\u{1F552} Registering jobs...");
2772
+ this.logger.info("Registering jobs...");
2594
2773
  const jobModules = undefined(
2595
2774
  "/src/app/jobs/**/*.job.{ts,js}",
2596
2775
  { eager: true }
@@ -2601,10 +2780,10 @@ class ApplicationBootstrap {
2601
2780
  this.logger.debug("container isBound(Scheduler)", { isBound: container.isBound(Scheduler) });
2602
2781
  for (const [name, ServiceClass] of this.serviceRegistry) {
2603
2782
  if (!ServiceClass) {
2604
- this.logger.warn(`\u26A0\uFE0F Service not found in registry: ${name}`);
2783
+ this.logger.warn(`Service not found in registry: ${name}`);
2605
2784
  }
2606
2785
  if (!container.isBound(ServiceClass)) {
2607
- this.logger.warn(`\u26A0\uFE0F Service not bound in container: ${name}`);
2786
+ this.logger.warn(`Service not bound in container: ${name}`);
2608
2787
  } else {
2609
2788
  container.get(ServiceClass);
2610
2789
  }
@@ -2624,23 +2803,43 @@ 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 });
2627
- this.logger.debug(`\u{1F4E6} Bound job: ${jobName}`);
2806
+ this.jobRegistry.set(jobName, JobClass);
2807
+ this.logger.debug(`Bound job: ${jobName}`);
2628
2808
  } catch (error) {
2629
- this.logger.error(`\u274C Failed to bind job ${JobClass.name}:`, error);
2809
+ this.logger.error(`Failed to bind job ${JobClass.name}:`, error);
2630
2810
  }
2631
2811
  }
2632
2812
  for (const { JobClass, jobName, id, options } of jobEntries) {
2633
2813
  try {
2634
2814
  const instance = container.get(JobClass);
2635
2815
  JobRegistry.instance.register(id, instance, options);
2816
+ if (typeof instance.onBoot === "function") {
2817
+ this.logger.info(`Executing onBoot for job: ${jobName}`);
2818
+ try {
2819
+ if (options.requiresPopup) {
2820
+ const isPopupVisible = PopupVisibilityService.instance.isPopupVisible();
2821
+ if (!isPopupVisible) {
2822
+ this.logger.debug(`Skipping onBoot for job ${jobName} - popup not visible`);
2823
+ } else {
2824
+ await instance.onBoot();
2825
+ this.logger.debug(`Executed onBoot for job: ${jobName}`);
2826
+ }
2827
+ } else {
2828
+ await instance.onBoot();
2829
+ this.logger.debug(`Executed onBoot for job: ${jobName}`);
2830
+ }
2831
+ } catch (error) {
2832
+ this.logger.error(`Failed to execute onBoot for job ${jobName}:`, error);
2833
+ }
2834
+ }
2636
2835
  if (!options.startPaused) {
2637
2836
  this.scheduler.schedule(id, options);
2638
2837
  } else {
2639
- this.logger.info(`\u23F8\uFE0F Job ${jobName} registered but paused (startPaused: true)`);
2838
+ this.logger.info(`Job ${jobName} registered but paused (startPaused: true)`);
2640
2839
  }
2641
- this.logger.success(`\u2705 Registered job: ${jobName}`);
2840
+ this.logger.success(`Registered job: ${jobName}`);
2642
2841
  } catch (error) {
2643
- this.logger.error(`\u274C Failed to register job ${JobClass.name}:`, error);
2842
+ this.logger.error(`Failed to register job ${JobClass.name}:`, error);
2644
2843
  }
2645
2844
  }
2646
2845
  }
@@ -2725,11 +2924,14 @@ class BootstrapBuilder {
2725
2924
  }
2726
2925
  }
2727
2926
 
2927
+ exports.EventBusToken = EventBusToken;
2728
2928
  exports.JobRegistry = JobRegistry;
2729
2929
  exports.JobState = JobState;
2730
2930
  exports.NonceService = NonceService;
2731
2931
  exports.PopupVisibilityService = PopupVisibilityService;
2932
+ exports.SUBSCRIBE_METADATA_KEY = SUBSCRIBE_METADATA_KEY;
2732
2933
  exports.Scheduler = Scheduler;
2934
+ exports.Subscribe = Subscribe;
2733
2935
  exports.arePortsClaimed = arePortsClaimed;
2734
2936
  exports.bootstrap = bootstrap;
2735
2937
  exports.claimEarlyPorts = claimEarlyPorts;
@@ -2737,6 +2939,7 @@ exports.container = container;
2737
2939
  exports.create = create;
2738
2940
  exports.getNonceService = getNonceService;
2739
2941
  exports.getPopupVisibilityService = getPopupVisibilityService;
2942
+ exports.getSubscribeMetadata = getSubscribeMetadata;
2740
2943
  exports.isEarlyListenerSetup = isEarlyListenerSetup;
2741
2944
  exports.setupEarlyListener = setupEarlyListener;
2742
- //# sourceMappingURL=boot-BMZdye6C.js.map
2945
+ //# sourceMappingURL=boot-DIMyFZvd.js.map